Skip to content

vulkan: enumerate portability-subset Vulkan implementations on macOS#67

Closed
scasekar wants to merge 1 commit into
google:mainfrom
scasekar:spike006/macos-vulkan-via-moltenvk
Closed

vulkan: enumerate portability-subset Vulkan implementations on macOS#67
scasekar wants to merge 1 commit into
google:mainfrom
scasekar:spike006/macos-vulkan-via-moltenvk

Conversation

@scasekar
Copy link
Copy Markdown

@scasekar scasekar commented May 11, 2026

Summary

Three surgical edits that let Dawn's Vulkan backend enumerate and use portability-subset Vulkan implementations (MoltenVK, KosmicKrisp, future) on macOS hosts. Without these, requesting wgpu::BackendType::Vulkan on macOS returns RequestAdapterStatus::Unavailable ("No supported adapters") even when vulkaninfo finds the same ICD through the same loader.

Note: I'm aware Dawn's primary code review happens on Gerrit (dawn-review.googlesource.com). Happy to re-submit there if preferred — opening this here first as draft so I can link upstream PRs to the spike artifacts. Please let me know which workflow you'd like.

Why

Two upstream gaps stack:

Gap 1: vkCreateInstance doesn't opt in to portability enumeration.
Per the Vulkan loader spec, non-conformant (portability-subset) Vulkan implementations are invisible to applications that don't:

  • enable VK_KHR_portability_enumeration, AND
  • set VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR on VkInstanceCreateInfo::flags.

Dawn does neither. Result: vkEnumeratePhysicalDevices returns 0 on macOS even though a portability-subset ICD is loaded.

Gap 2: Backend::DiscoverPhysicalDevices skips ICD::None on macOS.

for (ICD icd : kICDs) {
#if DAWN_PLATFORM_IS(MACOS)
    if (icd == ICD::None) continue;   // "we don't expect non-SwiftShader Vulkan to be available"
#endif

That assumption predates Apple Silicon portability-subset Vulkan implementations (MoltenVK, KosmicKrisp). These ICDs are loaded via the loader's normal ICD path (which is ICD::None from Dawn's perspective), so the skip prevents Dawn from probing them — even after Gap 1 is fixed.

Changes

  • src/dawn/native/vulkan/VulkanExtensions.h: add InstanceExt::PortabilityEnumeration enumerant.
  • src/dawn/native/vulkan/VulkanExtensions.cpp: add {InstanceExt::PortabilityEnumeration, "VK_KHR_portability_enumeration"} to the name table + a matching case in EnsureDependencies (no transitive deps).
  • src/dawn/native/vulkan/BackendVk.cpp:
    • In CreateVkInstance, set VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR on createInfo.flags when the loader advertises the extension.
    • Remove the macOS-only ICD::None skip in Backend::DiscoverPhysicalDevices. Replace it with a comment explaining the now-fixed historical concern.

Behavior on non-Apple platforms

  • Adreno / Android: not a portability-subset device — the loader doesn't advertise the extension, HasExt(...) returns false, the new createInfo.flags |= is a no-op. The ICD::None loop already ran on Android (no #if DAWN_PLATFORM_IS(ANDROID) guard) so removing the macOS skip changes nothing there.
  • Linux/Windows: same — conformant Vulkan, extension not advertised, flag-set is a no-op.

Test plan

  • Build on Apple Silicon (M1, macOS 14, AppleClang 17, Vulkan SDK 1.4.341, MoltenVK 1.4.1) with -DDAWN_ENABLE_VULKAN=ON -DTINT_BUILD_SPV_WRITER=ON.
  • Same backend-agnostic WGSL triangle renders byte-identical PNG (3,885 bytes, MD5 bfb7f207fb7e0535a829e5f0a63df60b) on --backend metal and --backend vulkan from the same triangle.cpp binary.
  • End-to-end stereo XR render via dawnxr against Meta XR Simulator 71.0.0 — 276 frames in 5s at 1680×1760 per eye (Quest 3 native projection resolution), full OpenXR session-state lifecycle.
  • (Not run by me — requires Google's CQ access) Existing Vulkan-backend tests on Linux/Windows/Android. I do not expect regressions because the new code is gated on HasExt(PortabilityEnumeration) which is false on conformant Vulkan implementations.

Context

Discovered while iterating on a Quest 3 (Android NDK + Adreno Vulkan) Dawn-native + OpenXR application using the Meta XR Simulator on Mac as the inner-loop validation target. The portability_enumeration support is broadly useful for any Mac developer working on a Quest-target Vulkan code path locally without a real Vulkan headset — collapsing iteration time from minutes-with-headset-don/doff to seconds at a desktop terminal.

Dawn's Vulkan backend on macOS currently does not enumerate MoltenVK as
an adapter. Two upstream gaps stack:

1. `vkCreateInstance` doesn't request `VK_KHR_portability_enumeration`
   and doesn't set `VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR`
   on `createInfo.flags`. Per the Vulkan loader spec, non-conformant
   (portability-subset) Vulkan implementations like MoltenVK are
   invisible to applications that don't opt-in via this flag.
   `vkEnumeratePhysicalDevices` returns 0 devices, and Dawn reports
   `wgpu::RequestAdapterStatus::Unavailable` / "No supported adapters".

2. `Backend::DiscoverPhysicalDevices` short-circuits with
   `#if DAWN_PLATFORM_IS(MACOS) ... if (icd == ICD::None) continue;`
   based on the historical assumption that no non-SwiftShader Vulkan
   is available on Mac. MoltenVK loads via the normal loader ICD path
   (ICD::None from Dawn's perspective), so this skip prevents Dawn
   from ever probing it — even after gap (1) is fixed.

Three surgical changes:

- VulkanExtensions.h: add `InstanceExt::PortabilityEnumeration` (a new
  enumerant before `EnumCount`).
- VulkanExtensions.cpp: add the `{InstanceExt::PortabilityEnumeration,
  "VK_KHR_portability_enumeration"}` entry to the name table, and a
  matching case in `EnsureDependencies` (no transitive dependencies).
- BackendVk.cpp:
  - In `CreateVkInstance`, set `VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR`
    on `createInfo.flags` when the loader advertises the extension.
  - Remove the `#if DAWN_PLATFORM_IS(MACOS) ... continue` that skips
    `ICD::None` on macOS.

Quest/Android implications: Adreno Vulkan is a fully conformant
implementation, NOT portability-subset, so the loader doesn't advertise
the extension there and the new `if (HasExt(...))` flag-set is a no-op.
The ICD::None loop already runs on Android. So this change is mac-only
behavior in practice.

Verified on Apple Silicon (M1, macOS 14, AppleClang 17, Vulkan SDK
1.4.341, MoltenVK 1.4.1) — same backend-agnostic WGSL triangle renders
byte-identical PNG (3,885 bytes, MD5 bfb7f207fb7e0535a829e5f0a63df60b)
on both `--backend metal` and `--backend vulkan` from the same
triangle.cpp binary.

End-to-end stereo XR render via dawnxr against Meta XR Simulator 71.0.0
also works — 276 frames in 5s at 1680x1760 per eye (Quest 3 native
projection resolution).

Discovered during a pre-proposal feasibility spike for an upcoming Navy
SBIR phase-I VR application (DON26BZ01-NV015) where Dawn-native + OpenXR
is the primary delivery surface for Quest 3 with the same WebGPU
codebase targeting desktop browsers. The portability_enumeration support
is broadly useful for any Mac developer working on the Quest-target
Vulkan code path locally without a real Vulkan headset.
@google-cla
Copy link
Copy Markdown

google-cla Bot commented May 11, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@dj2
Copy link
Copy Markdown
Collaborator

dj2 commented May 11, 2026

Why MoltenVK instead of KosmicKrisp?

@kainino0x
Copy link
Copy Markdown
Member

Also, currently we just use SwiftShader for local development of the Vulkan backend on Mac. Is that insufficient for you?

@scasekar scasekar changed the title vulkan: enable macOS host development via MoltenVK vulkan: enumerate portability-subset Vulkan implementations on macOS May 12, 2026
@scasekar
Copy link
Copy Markdown
Author

scasekar commented May 12, 2026

Thanks both.

@dj2: Why MoltenVK vs KosmicKrisp?

The PR title was misleading and I've retitled it. The patch isn't MoltenVK-specific; it implements what the Vulkan loader spec asks of any application that wants to enumerate portability-subset implementations. Enabling VK_KHR_portability_enumeration + setting VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR is the loader-level opt-in for any non-conformant Vulkan implementation, and removing the macOS-only ICD::None skip in Backend::DiscoverPhysicalDevices affects all loader-discovered ICDs uniformly. So MoltenVK and KosmicKrisp both work through the same code path. I tested with MoltenVK because that's what shipped with the LunarG SDK I had installed, not because the patch is MoltenVK-aware.

@kainino0x: Is SwiftShader insufficient?

We're targeting Quest 3 via Android NDK and using the Meta XR Simulator on Mac for inner-loop work. We tend to iterate on performance enhancements in the simulator, which is hard to do against a CPU rasterizer.

@dj2
Copy link
Copy Markdown
Collaborator

dj2 commented May 12, 2026

I don't know about the ICD::None bit, but KosmicKrisp should not require portability, it's a fully conformant implementation. It should also be available in the latest SDK, so getting a new SDK should just provide it for you.

@scasekar
Copy link
Copy Markdown
Author

Closing in favor of upf-gti#1, which is the more appropriate landing point since wgpuEngine's FetchDawn.cmake fetches from that fork. Empirical test results (KosmicKrisp discovery with and without the skip) and rationale are in the new PR. Per @dj2's KosmicKrisp-is-conformant correction, the new PR drops the VK_KHR_portability_enumeration change and contains only the ICD::None skip removal.

Thanks both for the review, the feedback redirected this toward a cleaner home.

@scasekar scasekar closed this May 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants