mirror of
https://github.com/mitch030504/Wiicompiled_VR_Frame.git
synced 2026-10-06 01:00:14 +02:00
Added Vulkan PC OpenXR Support
This commit is contained in:
1 parent
fbc9ce7217
commit
e7b670b2cc
23 files changed
+2174
-22
No files matched your search
@@ -0,0 +1,52 @@
|
||||
# Builds the pinned Dawn DLL with Aurora's native Windows Vulkan/OpenXR bridge.
|
||||
# Run on a maintainer machine with VS 2022 C++ tools, CMake, Git and Python 3.
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$WorkDirectory = (Join-Path $PSScriptRoot 'artifacts\dawn-vulkan-build'),
|
||||
[string]$Destination = (Join-Path $PSScriptRoot 'artifacts\dawn-vulkan'),
|
||||
[string]$Python = 'python',
|
||||
[int]$Jobs = 8
|
||||
)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Set-StrictMode -Version 3.0
|
||||
$revision = '13abc3bc8ea2d3c2050f9e77a12d012108ceee24'
|
||||
$archiveHash = '713bea5b92d4f6c5175752fd7cbf1c3c5ce36598ff5dd98685d8a1216614ebba'
|
||||
$WorkDirectory = [IO.Path]::GetFullPath($WorkDirectory)
|
||||
$Destination = [IO.Path]::GetFullPath($Destination)
|
||||
[IO.Directory]::CreateDirectory($WorkDirectory) | Out-Null
|
||||
$archive = Join-Path $WorkDirectory 'dawn-source.tar.gz'
|
||||
if (-not (Test-Path -LiteralPath $archive)) {
|
||||
Invoke-WebRequest "https://github.com/google/dawn/archive/$revision.tar.gz" -OutFile $archive -UseBasicParsing
|
||||
}
|
||||
if ((Get-FileHash -LiteralPath $archive -Algorithm SHA256).Hash.ToLowerInvariant() -ne $archiveHash) {
|
||||
throw 'Dawn source archive does not match the pinned SHA-256.'
|
||||
}
|
||||
$source = Join-Path $WorkDirectory "dawn-$revision"
|
||||
if (-not (Test-Path -LiteralPath $source)) {
|
||||
& $Python -c 'import sys,tarfile; tarfile.open(sys.argv[1]).extractall(sys.argv[2], filter="data")' $archive $WorkDirectory
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Dawn source extraction failed (Python 3.12+ required).' }
|
||||
}
|
||||
$patch = Join-Path $PSScriptRoot '..\aurora-main\patches\dawn\apply.py'
|
||||
& $Python $patch $source
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Applying the Aurora Dawn bridge failed.' }
|
||||
$build = Join-Path $WorkDirectory 'build'
|
||||
& cmake -S $source -B $build -G 'Visual Studio 17 2022' -A x64 `
|
||||
"-DPython3_EXECUTABLE=$Python" -DDAWN_FETCH_DEPENDENCIES=ON `
|
||||
-DDAWN_BUILD_MONOLITHIC_LIBRARY=SHARED -DDAWN_ENABLE_INSTALL=ON `
|
||||
-DDAWN_BUILD_SAMPLES=OFF -DDAWN_BUILD_TESTS=OFF -DDAWN_BUILD_BENCHMARKS=OFF `
|
||||
-DTINT_BUILD_TESTS=OFF -DTINT_BUILD_CMD_TOOLS=OFF -DDAWN_USE_GLFW=OFF `
|
||||
-DDAWN_ENABLE_D3D11=OFF -DDAWN_ENABLE_D3D12=ON -DDAWN_ENABLE_VULKAN=ON `
|
||||
-DDAWN_ENABLE_DESKTOP_GL=OFF -DDAWN_ENABLE_OPENGLES=OFF "-DCMAKE_INSTALL_PREFIX=$Destination"
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Dawn configuration failed.' }
|
||||
& cmake --build $build --config Release --parallel $Jobs
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Dawn build failed.' }
|
||||
& cmake --install $build --config Release
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Dawn installation failed.' }
|
||||
$dll = Join-Path $Destination 'bin\webgpu_dawn.dll'
|
||||
if (-not (Test-Path -LiteralPath $dll)) { throw "Dawn DLL missing: $dll" }
|
||||
[ordered]@{
|
||||
SourceRevision = $revision
|
||||
AuroraVulkanAbi = 1
|
||||
DllSha256 = (Get-FileHash -LiteralPath $dll -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||
} | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $Destination 'aurora-vulkan.json') -Encoding UTF8
|
||||
Write-Host "Custom Dawn package ready: $Destination"
|
||||
@@ -99,7 +99,7 @@ Assert-File (Join-Path $portableTools 'Ninja\ninja.exe') 'Portable Ninja'
|
||||
# to compile (launcher/Prepare-NativePrebuilt.ps1).
|
||||
# Kept in step with InstalledLayout.DependencyNames by Test-PinnedFacts.ps1: the installed host
|
||||
# refuses to call a toolkit complete unless every one of these directories is present.
|
||||
$requiredDependencies = @('abseil-cpp','cppwinrt','dawn_prebuilt','fmt','freetype','imgui','libusb','native_prebuilt','openxr','png','SDL','sqlite3','tracy','xxhash','zlib','zstd')
|
||||
$requiredDependencies = @('abseil-cpp','cppwinrt','dawn_prebuilt','fmt','freetype','imgui','libusb','native_prebuilt','openxr','png','SDL','sqlite3','tracy','vulkan_headers','xxhash','zlib','zstd')
|
||||
$missingSources = @($requiredDependencies | Where-Object { $_ -ne 'native_prebuilt' } |
|
||||
Where-Object { -not (Test-Path -LiteralPath (Join-Path $dependencySources $_) -PathType Container) })
|
||||
if ($missingSources.Count -gt 0) {
|
||||
|
||||
@@ -9,7 +9,9 @@ param(
|
||||
[string]$Destination,
|
||||
# Windows metadata source for cppwinrt.exe: 'local' (this machine's WinMetadata), 'sdk', or an
|
||||
# installed SDK version such as 10.0.26100.0.
|
||||
[string]$CppWinRtInput = 'local'
|
||||
[string]$CppWinRtInput = 'local',
|
||||
# Output of Build-DawnVulkan.ps1. Rebuild native_prebuilt after changing Dawn.
|
||||
[string]$DawnVulkanPackage
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
@@ -27,6 +29,11 @@ $runtimeCMake = Join-Path $repoRoot 'runtime\CMakeLists.txt'
|
||||
# FETCHCONTENT_SOURCE_DIR_<UPPERCASE NAME> (NativeBuildFlags.ps1), so the names are the declared
|
||||
# FetchContent names, not the upstream project names.
|
||||
$packages = @(
|
||||
[pscustomobject]@{
|
||||
Name = 'vulkan_headers'; File = 'vulkan-headers-015e25c3c91b70eb1a754d36fb14c4ba6ad9b0b9.tar.gz'
|
||||
Uris = @('https://github.com/KhronosGroup/Vulkan-Headers/archive/015e25c3c91b70eb1a754d36fb14c4ba6ad9b0b9.tar.gz')
|
||||
Pins = @(@{ File = $runtimeCMake; Text = 'Vulkan-Headers/archive/015e25c3c91b70eb1a754d36fb14c4ba6ad9b0b9.tar.gz' })
|
||||
},
|
||||
[pscustomobject]@{
|
||||
Name = 'SDL'; File = 'SDL3-3.4.4.tar.gz'
|
||||
Uris = @('https://github.com/libsdl-org/SDL/releases/download/release-3.4.4/SDL3-3.4.4.tar.gz')
|
||||
@@ -175,6 +182,22 @@ function Expand-Package([string]$Archive, [string]$Target) {
|
||||
foreach ($package in $packages) {
|
||||
Assert-Pinned $package
|
||||
$target = Join-Path $Destination $package.Name
|
||||
if ($package.Name -eq 'dawn_prebuilt' -and $DawnVulkanPackage) {
|
||||
$custom = [IO.Path]::GetFullPath($DawnVulkanPackage)
|
||||
$manifest = Get-Content -LiteralPath (Join-Path $custom 'aurora-vulkan.json') -Raw | ConvertFrom-Json
|
||||
$dll = Join-Path $custom 'bin\webgpu_dawn.dll'
|
||||
if ($manifest.SourceRevision -ne '13abc3bc8ea2d3c2050f9e77a12d012108ceee24' -or
|
||||
$manifest.AuroraVulkanAbi -ne 1 -or
|
||||
(Get-FileHash -LiteralPath $dll -Algorithm SHA256).Hash.ToLowerInvariant() -ne $manifest.DllSha256) {
|
||||
throw 'Custom Dawn package provenance or DLL hash does not match.'
|
||||
}
|
||||
if (Test-Path -LiteralPath $target) {
|
||||
throw "Use a fresh dependency destination for custom Dawn: $target already exists."
|
||||
}
|
||||
Copy-Item -LiteralPath $custom -Destination $target -Recurse
|
||||
Write-Host 'Prepared custom Dawn with Windows Vulkan OpenXR support'
|
||||
continue
|
||||
}
|
||||
if (Test-Path -LiteralPath $target -PathType Container) { continue }
|
||||
Expand-Package (Get-Archive $package) $target
|
||||
Write-Host "Prepared $($package.Name)"
|
||||
|
||||
@@ -27,7 +27,7 @@ internal static class InstalledLayout
|
||||
public static readonly string[] DependencyNames =
|
||||
[
|
||||
"abseil-cpp", "cppwinrt", "dawn_prebuilt", "fmt", "freetype", "imgui", "libusb", "native_prebuilt",
|
||||
"openxr", "png", "SDL", "sqlite3", "tracy", "xxhash", "zlib", "zstd"
|
||||
"openxr", "png", "SDL", "sqlite3", "tracy", "vulkan_headers", "xxhash", "zlib", "zstd"
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
WiiCompiled has an opt-in OpenXR rendering path. The first functional backend is Windows D3D12.
|
||||
It asks the OpenXR runtime for the required GPU before Aurora creates Dawn, then copies each eye
|
||||
on that same D3D12 device and queue into the acquired OpenXR swapchain images. Eye submission
|
||||
stays on the GPU; there is no CPU texture readback and no second graphics device.
|
||||
stays on the GPU; there is no CPU texture readback and no second graphics device. Windows Vulkan is
|
||||
an opt-in second binding built on the same design: the OpenXR runtime creates Dawn's Vulkan instance
|
||||
and device (`XR_KHR_vulkan_enable2`) and eyes are copied on that same queue. It needs a custom Dawn
|
||||
build; see [Windows Vulkan](#windows-vulkan).
|
||||
|
||||
This is an experimental renderer, not yet a release-ready VR mode.
|
||||
|
||||
@@ -11,14 +14,16 @@ This is an experimental renderer, not yet a release-ready VR mode.
|
||||
|
||||
- A Windows OpenXR runtime selected as the system's active runtime.
|
||||
- A connected headset supported by that runtime.
|
||||
- A D3D12-capable GPU and driver accepted by both OpenXR and Dawn.
|
||||
- A D3D12-capable GPU and driver accepted by both OpenXR and Dawn, or for the opt-in Vulkan
|
||||
binding a Vulkan 1.1+ driver plus the custom Dawn described under [Windows Vulkan](#windows-vulkan).
|
||||
- A build made with `MKW_ENABLE_OPENXR=ON`, which defaults on for Windows and off elsewhere while
|
||||
the Vulkan bridge remains capability-gated.
|
||||
|
||||
For managed installation, use [WheelWizard VR](https://github.com/iChris4/WheelWizard_VR/releases/latest)
|
||||
and enable **Settings → Other → WiiCompiled (beta) → Enable WiiCompiled OpenXR VR (beta)**.
|
||||
The launcher sets `vr.enabled=true`, `vr.required=false`, and `video.graphics_api="d3d12"` before
|
||||
each VR launch, preserving other preferences. Its portable configuration lives at
|
||||
each VR launch, preserving other preferences. Setting `video.graphics_api = "vulkan"` in that
|
||||
configuration selects the Vulkan binding instead. Its portable configuration lives at
|
||||
`RecompVR/UserData/Config.toml` beneath WheelWizard's data folder. Normal graphics settings remain
|
||||
in `Recomp/UserData/Config.toml`. Both backends use the normal installation's effective NAND.
|
||||
|
||||
@@ -320,7 +325,7 @@ short-lived immutable stereo packet. Each sealed GX frame and immersive packet c
|
||||
policy-generation tag; a mismatch is rendered in mono and the acquired XR frame is canceled, so an
|
||||
asynchronous menu/race transition cannot replay race transforms over unsafe content.
|
||||
|
||||
With interpolation off, both PC (D3D12) and standalone (Vulkan) pace render-first:
|
||||
With interpolation off, PC (D3D12 and Windows Vulkan) and standalone (Android Vulkan) pace render-first:
|
||||
the pacing thread locates views for an estimated display time (two periods past the last
|
||||
prediction), hands Aurora a packet without leaving a compositor frame open, and waits for
|
||||
rendering. A 50 ms stall repeats the retained layer; cancellation also advances a keep-alive
|
||||
@@ -328,16 +333,17 @@ cycle to refresh timing. Once rendering is submitted, the thread calls xrWaitFra
|
||||
xrBeginFrame, completes backend-specific copy/release work, and ends the frame using the
|
||||
packet's original render poses with the current compositor display time.
|
||||
|
||||
Vulkan renders into shared buffers and copies them into newly acquired XR images afterward.
|
||||
D3D12 acquires images from its non-retained swapchain pair before rendering; Aurora queues the
|
||||
copy on the session's D3D12 queue before reporting completion. PC therefore needs no additional
|
||||
Android Vulkan renders into shared buffers and copies them into newly acquired XR images afterward.
|
||||
Both PC bindings acquire images from their non-retained swapchain pair before rendering; Aurora
|
||||
queues the copy on the session's queue before reporting completion. PC therefore needs no additional
|
||||
copy in the short compositor cycle. Pending images remain acquired and separate from the
|
||||
retained pair until completion or confirmed cancellation before encoding. GPU failure still
|
||||
requires the existing queue-drain teardown. Rendered poses keep the session/reference-space
|
||||
serials recorded when the packet was prepared, so changes during rendering invalidate them.
|
||||
|
||||
VR interpolation keeps the frame-first order on both backends because it renders for the
|
||||
frame's own predicted display time. The log announces `OpenXR D3D12 pacing: render-first` or
|
||||
frame's own predicted display time. The log announces `OpenXR D3D12 pacing: render-first` (or
|
||||
`OpenXR Vulkan pacing: …` on the Vulkan binding) or
|
||||
`frame-first (VR interpolation)` on each transition. For PC testing, disable **VR** frame
|
||||
interpolation for a race capture; changing desktop interpolation alone does not select this
|
||||
path. Menus use render-first even when VR interpolation is configured for races. Compare the
|
||||
@@ -456,7 +462,7 @@ When it is on, `console.log` receives lines tagged `[runtime] [xr-diag]`
|
||||
| `poll-events`, `begin-call`, `locate-views` | Event polling, the xrBeginFrame call itself, and xrLocateViews. |
|
||||
| `input-sync`, `sync-actions` | Complete input update and its xrSyncActions call. |
|
||||
| `publish`, `withdraw` | Packet construction/publication and withdrawal, including mutex waits. |
|
||||
| `set-targets` | D3D12 bridge target registration, including its mutex wait. |
|
||||
| `set-targets` | D3D12/Vulkan bridge target registration, including its mutex wait. |
|
||||
| `submission-wait` | Actual time waiting for a render result, including timeout paths; compare against the requested 50 ms. |
|
||||
| `cancel` | Bridge cancellation attempt, whether it succeeds or fails. |
|
||||
| `cancel-age` | Publication to cancellation, including packets never picked up. |
|
||||
@@ -491,11 +497,58 @@ The current `console.log` is copied through a shared-read stream while it is sti
|
||||
The copy runs on SDL's dialog thread (`runtime/src/log_export.cpp`), and the outcome is shown under
|
||||
the button. `mkw_openxr_diagnostics_tests` and `mkw_log_export_tests` cover both without a headset.
|
||||
|
||||
## Windows Vulkan
|
||||
|
||||
`video.graphics_api = "vulkan"` selects a second Windows binding, `runtime/src/vr/openxr_vulkan_win32.cpp`,
|
||||
with the same pacing thread, retained-layer protocol and policy as D3D12
|
||||
(`runtime/include/vr/openxr_windows.h` picks the backend at startup). It is opt-in. It has raced on
|
||||
a headset (SteamVR/OpenXR with a PlayStation VR2): immersive projection held the headset's full
|
||||
90 Hz with no skipped display slots, and a 646-second session recorded no rejected or discarded
|
||||
layers and no failed submissions. Other runtimes are still unexercised.
|
||||
|
||||
**Why a custom Dawn.** The pinned prebuilt Dawn DLL exposes no native Vulkan device, so
|
||||
`aurora-main/patches/dawn` adds a small versioned C ABI to the pinned Dawn source
|
||||
(`aurora_dawn_vulkan_abi.h`, `AURORA_DAWN_VULKAN_ABI = 1`): hooks that let the OpenXR runtime
|
||||
create Dawn's `VkInstance`, choose the physical device and create the `VkDevice`; wrapping of a
|
||||
borrowed `VkImage` as a Dawn texture; the release barrier back to `COLOR_ATTACHMENT_OPTIMAL`; a
|
||||
device-guard lock; and a queue drain. `Launcher/Build-DawnVulkan.ps1` builds that DLL from the pinned
|
||||
revision on a machine with Visual Studio 2022, Python 3.12+ and CMake, and writes `aurora-vulkan.json`
|
||||
(revision, ABI, DLL hash). `Launcher/Prepare-Dependencies.ps1 -DawnVulkanPackage <dir>` installs it as
|
||||
`dawn_prebuilt` in a fresh dependency destination after checking that provenance; re-harvest
|
||||
`native_prebuilt` afterwards because the archives are pinned to the Dawn DLL hash. The runtime
|
||||
build also fetches Vulkan headers (`vulkan_headers` dependency).
|
||||
|
||||
**Startup.** `QueryGraphicsRequirements` loads `xrGetVulkanGraphicsRequirements2KHR`,
|
||||
`xrCreateVulkanInstanceKHR`, `xrCreateVulkanDeviceKHR` and `xrGetVulkanGraphicsDevice2KHR`, then
|
||||
installs the hooks in Dawn. Without the custom DLL it fails with *"requires the custom Dawn library
|
||||
with Aurora Vulkan ABI 1"* and the game continues on the desktop renderer. During
|
||||
`aurora_initialize` the runtime creates Dawn's instance (Vulkan 1.2 is requested when the loader
|
||||
and runtime allow it, so that timeline semaphores, which PC runtimes create on the application's
|
||||
device, are a core feature the device hook can enable) and device on the runtime's physical GPU.
|
||||
`BindAurora` confirms that Dawn's physical device is the one the runtime selected, creates the
|
||||
session on Dawn's graphics queue, picks the sRGB sibling of Aurora's UNORM colour format (with
|
||||
`XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT`) so the compositor decodes the gamma-encoded bytes, and
|
||||
enables the bridge.
|
||||
|
||||
**Frames.** Each acquired XR image is wrapped once as a Dawn texture and reused. Aurora's frame
|
||||
worker records the eye copies into its own command buffer; immediately after `queue.Submit`, still
|
||||
under Aurora's submit mutex, the bridge appends the release barrier through Dawn's queue and
|
||||
publishes the token that the pacing thread's `WaitForSubmission` consumes. The runtime may use
|
||||
the VkQueue only inside `xrBeginFrame`, `xrEndFrame`, `xrAcquireSwapchainImage` and
|
||||
`xrReleaseSwapchainImage`, so `OpenXRRuntime::LockGraphicsQueue` holds Dawn's device guard around
|
||||
exactly those four calls and never across `xrWaitFrame` or `xrWaitSwapchainImage`.
|
||||
|
||||
**Tests.** `mkw_openxr_vulkan_replay_tests` compiles the real backend against the deterministic
|
||||
compositor of the D3D12 replay tests, including the queue-guard requirement on acquire and release.
|
||||
`vulkan_native_bridge_smoke` (aurora, `AURORA_GPU_SMOKE_TESTS=ON`, real GPU, no headset) drives the
|
||||
custom DLL's ABI through three borrowed-image copy/readback cycles; run it with that DLL beside it.
|
||||
|
||||
## Backend status
|
||||
|
||||
| Backend | Status |
|
||||
| --- | --- |
|
||||
| Windows D3D12 | Implemented: same-adapter, same-device asynchronous OpenXR submission. |
|
||||
| Windows Vulkan | Implemented, opt-in (`video.graphics_api = "vulkan"`): the runtime creates Dawn's Vulkan instance and device through `XR_KHR_vulkan_enable2`, eyes are copied on the same queue, and Dawn's device guard is held around the four queue-touching OpenXR calls. Needs the custom Dawn from `Launcher/Build-DawnVulkan.ps1`. Raced on SteamVR/PSVR2 at the headset's full rate; other runtimes unexercised. See [Windows Vulkan](#windows-vulkan). |
|
||||
| Android Vulkan (Meta Quest) | Implemented and running on a Quest 3: the OpenXR side owns its own Vulkan device (`XR_KHR_vulkan_enable2`, `XR_KHR_vulkan_enable` fallback) and shares eyes with Dawn through `AHardwareBuffer`s ordered by sync-fd fences. Controllers arrive through OpenXR actions as a virtual SDL gamepad. See `docs/quest-port.md`. |
|
||||
| Linux Vulkan | Not wired. The pinned Dawn package does not expose a native Vulkan device, and the AHardwareBuffer bridge is Android-only; a dma-buf/opaque-fd variant of the same design would cover desktop Linux. |
|
||||
| Other platforms | Not wired yet. |
|
||||
|
||||
@@ -50,7 +50,8 @@ The graphics layer is built on
|
||||
Play at several times the console's resolution.
|
||||
|
||||
**Experimental OpenXR VR.**
|
||||
Windows builds can render through a D3D12 OpenXR runtime without CPU readback. Menus and
|
||||
Windows builds can render through an OpenXR runtime on D3D12, or on Vulkan with a custom Dawn
|
||||
build, without CPU readback. Menus and
|
||||
unsupported scenes appear as a head-locked virtual screen; a validated single-camera race switches
|
||||
to immersive stereo rendering. VR is opt-in and falls back to the normal desktop renderer if the
|
||||
runtime or headset is unavailable. See [`OPENXR.md`](OPENXR.md) for setup, configuration, and the
|
||||
@@ -131,7 +132,8 @@ Saves and Miis use the normal installation's effective NAND; Retro Rewind retain
|
||||
XML-directed saves and ghosts. Graphics, VR preferences, caches, and compiled binaries stay separate.
|
||||
Uninstalling either backend in WheelWizard VR preserves configuration and shared progress.
|
||||
|
||||
Managed VR launches enable OpenXR with D3D12. If the runtime or headset is unavailable, the game
|
||||
Managed VR launches enable OpenXR with D3D12; the Vulkan binding is opt-in through
|
||||
`video.graphics_api` (see [OPENXR.md](OPENXR.md)). If the runtime or headset is unavailable, the game
|
||||
continues on the desktop and displays the failure briefly; **F10 → VR** retains the explanation.
|
||||
See [OpenXR configuration](OPENXR.md) and [distribution and validation](DISTRIBUTION.md).
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ if (AURORA_ENABLE_GX)
|
||||
# OpenXR-enabled runtime can fail back to desktop mode at runtime instead
|
||||
# of producing unresolved interop references.
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL Windows)
|
||||
target_sources(aurora_core PRIVATE lib/webgpu/d3d12_interop.cpp)
|
||||
target_sources(aurora_core PRIVATE lib/webgpu/d3d12_interop.cpp lib/webgpu/vulkan_win32_interop.cpp)
|
||||
endif ()
|
||||
# Android/Vulkan counterpart: the AHardwareBuffer stereo bridge. The file
|
||||
# compiles to C ABI stubs on every other platform so the runtime's OpenXR
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
// Versioned C ABI: no STL objects or compiler-specific C++ symbols cross the
|
||||
// MSVC Dawn DLL / LLVM-MinGW runtime boundary. Vulkan handles are borrowed.
|
||||
#define AURORA_DAWN_VULKAN_ABI 1
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
typedef struct {
|
||||
void* userdata;
|
||||
int32_t (*createInstance)(void*, void* getProc, const void* info, const void* allocator, void** instance);
|
||||
int32_t (*createDevice)(void*, void* getProc, void* physical, const void* info, const void* allocator, void** device);
|
||||
int32_t (*getPhysicalDevice)(void*, void* instance, void** physical);
|
||||
} AuroraDawnVulkanHooks;
|
||||
typedef struct {
|
||||
void* instance;
|
||||
void* physicalDevice;
|
||||
void* device;
|
||||
uint32_t queueFamily;
|
||||
uint32_t queueIndex;
|
||||
} AuroraDawnVulkanHandles;
|
||||
typedef uint32_t (*AuroraDawnVulkanVersionFn)(void);
|
||||
typedef int (*AuroraDawnVulkanConfigureFn)(const AuroraDawnVulkanHooks*);
|
||||
typedef int (*AuroraDawnVulkanHandlesFn)(void* device, AuroraDawnVulkanHandles*);
|
||||
typedef void* (*AuroraDawnVulkanWrapFn)(void* device, const void* textureDescriptor, uint64_t image);
|
||||
typedef int (*AuroraDawnVulkanReleaseFn)(void* device, void* const* textures, uint32_t count);
|
||||
typedef void* (*AuroraDawnVulkanLockFn)(void* device);
|
||||
typedef void (*AuroraDawnVulkanUnlockFn)(void* guard);
|
||||
typedef int (*AuroraDawnVulkanDrainFn)(void* device);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,20 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#pragma once
|
||||
#include <aurora/dawn_vulkan_abi.h>
|
||||
#include <aurora/d3d12_interop.h>
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
// Same pending-target/callback contract as D3D12. resource carries a VkImage
|
||||
// encoded as a pointer-sized value; colorDxgiFormat carries a VkFormat.
|
||||
bool aurora_vulkan_win32_configure(const AuroraDawnVulkanHooks* hooks);
|
||||
bool aurora_vulkan_win32_get_handles(AuroraDawnVulkanHandles* handles, int64_t* colorFormat);
|
||||
bool aurora_vulkan_win32_enable(AuroraD3D12StereoSubmittedCallback submitted, void* userdata);
|
||||
bool aurora_vulkan_win32_set_targets(uint64_t token, const AuroraD3D12StereoTarget* targets, uint32_t count);
|
||||
bool aurora_vulkan_win32_cancel(uint64_t token);
|
||||
bool aurora_vulkan_win32_disable();
|
||||
void* aurora_vulkan_win32_lock_queue();
|
||||
void aurora_vulkan_win32_unlock_queue(void* guard);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,197 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#include <aurora/vulkan_win32_interop.h>
|
||||
#include "../internal.hpp"
|
||||
#include "../stereo.hpp"
|
||||
#include "gpu.hpp"
|
||||
#if defined(_WIN32) && defined(WEBGPU_DAWN) && defined(DAWN_ENABLE_BACKEND_VULKAN)
|
||||
#include <windows.h>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
namespace aurora::vulkan_win32 {
|
||||
namespace {
|
||||
Module Log("aurora::vulkan_win32");
|
||||
struct Api {
|
||||
AuroraDawnVulkanConfigureFn configure = nullptr;
|
||||
AuroraDawnVulkanHandlesFn handles = nullptr;
|
||||
AuroraDawnVulkanWrapFn wrap = nullptr;
|
||||
AuroraDawnVulkanReleaseFn release = nullptr;
|
||||
AuroraDawnVulkanLockFn lock = nullptr;
|
||||
AuroraDawnVulkanUnlockFn unlock = nullptr;
|
||||
AuroraDawnVulkanDrainFn drain = nullptr;
|
||||
bool Load() {
|
||||
HMODULE module = GetModuleHandleW(L"webgpu_dawn.dll");
|
||||
if (!module) return false;
|
||||
auto version = reinterpret_cast<AuroraDawnVulkanVersionFn>(GetProcAddress(module, "AuroraDawnVulkanVersion"));
|
||||
if (!version || version() != AURORA_DAWN_VULKAN_ABI) return false;
|
||||
#define LOAD(member, type, name) member = reinterpret_cast<type>(GetProcAddress(module, name)); if (!member) return false
|
||||
LOAD(configure, AuroraDawnVulkanConfigureFn, "AuroraDawnVulkanConfigure");
|
||||
LOAD(handles, AuroraDawnVulkanHandlesFn, "AuroraDawnVulkanGetHandles");
|
||||
LOAD(wrap, AuroraDawnVulkanWrapFn, "AuroraDawnVulkanWrap");
|
||||
LOAD(release, AuroraDawnVulkanReleaseFn, "AuroraDawnVulkanRelease");
|
||||
LOAD(lock, AuroraDawnVulkanLockFn, "AuroraDawnVulkanLock");
|
||||
LOAD(unlock, AuroraDawnVulkanUnlockFn, "AuroraDawnVulkanUnlock");
|
||||
LOAD(drain, AuroraDawnVulkanDrainFn, "AuroraDawnVulkanDrain");
|
||||
#undef LOAD
|
||||
return true;
|
||||
}
|
||||
} api;
|
||||
int64_t VkFormat(wgpu::TextureFormat format) {
|
||||
switch (format) {
|
||||
case wgpu::TextureFormat::RGBA8Unorm: return 37;
|
||||
case wgpu::TextureFormat::RGBA8UnormSrgb: return 43;
|
||||
case wgpu::TextureFormat::BGRA8Unorm: return 44;
|
||||
case wgpu::TextureFormat::BGRA8UnormSrgb: return 50;
|
||||
case wgpu::TextureFormat::RGBA16Float: return 97;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
bool CopyCompatible(int64_t a, int64_t b) {
|
||||
return a == b || ((a == 37 || a == 43) && (b == 37 || b == 43)) ||
|
||||
((a == 44 || a == 50) && (b == 44 || b == 50));
|
||||
}
|
||||
struct Import { uint64_t image; uint32_t width, height; wgpu::TextureFormat format; wgpu::Texture texture; };
|
||||
class Bridge {
|
||||
public:
|
||||
std::mutex mutex;
|
||||
std::vector<Import> imports;
|
||||
std::array<AuroraD3D12StereoTarget, 2> targets{};
|
||||
std::array<wgpu::Texture, 2> active{};
|
||||
uint64_t token = 0;
|
||||
uint32_t count = 0;
|
||||
bool encoded = false;
|
||||
AuroraD3D12StereoSubmittedCallback callback;
|
||||
void* userdata;
|
||||
Bridge(AuroraD3D12StereoSubmittedCallback cb, void* data) : callback(cb), userdata(data) {}
|
||||
bool Set(uint64_t next, const AuroraD3D12StereoTarget* data, uint32_t n) {
|
||||
if (!next || !data || !n || n > 2) return false;
|
||||
std::lock_guard guard(mutex);
|
||||
if (token) return false;
|
||||
for (uint32_t i = 0; i < n; ++i) {
|
||||
if (!data[i].resource || !data[i].width || !data[i].height) return false;
|
||||
targets[i] = data[i];
|
||||
}
|
||||
token = next; count = n; return true;
|
||||
}
|
||||
bool Cancel(uint64_t wanted) {
|
||||
std::unique_lock guard(mutex, std::try_to_lock);
|
||||
if (!guard.owns_lock() || encoded || !token || token != wanted) return false;
|
||||
token = 0; return true;
|
||||
}
|
||||
bool Encode(wgpu::CommandEncoder& encoder, const stereo::SinkFrame& frame) {
|
||||
std::lock_guard guard(mutex);
|
||||
if (!token || encoded || frame.frameToken != token) return false;
|
||||
// Validate/import every target before recording any copy.
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
const auto& eye = frame.eyes[i];
|
||||
const auto& target = targets[i];
|
||||
if (!eye.texture || eye.size.width != target.width || eye.size.height != target.height ||
|
||||
!CopyCompatible(VkFormat(eye.format), target.dxgiFormat)) return false;
|
||||
const uint64_t image = reinterpret_cast<uintptr_t>(target.resource);
|
||||
auto it = std::find_if(imports.begin(), imports.end(), [&](const Import& entry) { return entry.image == image; });
|
||||
if (it == imports.end()) {
|
||||
wgpu::TextureDescriptor descriptor;
|
||||
descriptor.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment;
|
||||
descriptor.size = {target.width, target.height, 1};
|
||||
descriptor.format = eye.format;
|
||||
void* wrapped = api.wrap(webgpu::g_device.Get(), &descriptor, image);
|
||||
if (!wrapped) return false;
|
||||
imports.push_back({image, target.width, target.height, eye.format,
|
||||
wgpu::Texture::Acquire(static_cast<WGPUTexture>(wrapped))});
|
||||
it = imports.end() - 1;
|
||||
}
|
||||
if (it->width != target.width || it->height != target.height || it->format != eye.format) {
|
||||
// The runtime reuses VkImage handles across swapchain recreation, and
|
||||
// Aurora's eye format can change with the surface. Re-wrap rather than
|
||||
// rejecting every future frame for this image.
|
||||
imports.erase(it);
|
||||
--i;
|
||||
continue;
|
||||
}
|
||||
active[i] = it->texture;
|
||||
}
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
wgpu::TexelCopyTextureInfo source, destination;
|
||||
source.texture = *frame.eyes[i].texture;
|
||||
destination.texture = active[i];
|
||||
wgpu::Extent3D size{targets[i].width, targets[i].height, 1};
|
||||
encoder.CopyTextureToTexture(&source, &destination, &size);
|
||||
}
|
||||
encoded = true;
|
||||
return true;
|
||||
}
|
||||
void Submitted(const stereo::SinkFrame& frame) {
|
||||
std::lock_guard guard(mutex);
|
||||
if (!token || !encoded || token != frame.frameToken) return;
|
||||
std::array<void*, 2> textures{};
|
||||
for (uint32_t i = 0; i < count; ++i) textures[i] = active[i].Get();
|
||||
// Append the COLOR_ATTACHMENT_OPTIMAL release barriers to Dawn's queue,
|
||||
// flush them under its device guard, then allow the XR thread to release.
|
||||
const bool success = api.release(webgpu::g_device.Get(), textures.data(), count) != 0;
|
||||
const auto completed = token;
|
||||
token = 0; encoded = false;
|
||||
callback(completed, success, userdata);
|
||||
}
|
||||
};
|
||||
std::unique_ptr<Bridge> bridge;
|
||||
}
|
||||
}
|
||||
bool aurora_vulkan_win32_configure(const AuroraDawnVulkanHooks* hooks) {
|
||||
using namespace aurora::vulkan_win32;
|
||||
return api.Load() && api.configure(hooks);
|
||||
}
|
||||
bool aurora_vulkan_win32_get_handles(AuroraDawnVulkanHandles* handles, int64_t* format) {
|
||||
using namespace aurora;
|
||||
if (!webgpu::g_device || webgpu::g_backendType != wgpu::BackendType::Vulkan || !vulkan_win32::api.Load()) return false;
|
||||
*format = vulkan_win32::VkFormat(webgpu::g_graphicsConfig.surfaceConfiguration.format);
|
||||
return *format && vulkan_win32::api.handles(webgpu::g_device.Get(), handles);
|
||||
}
|
||||
bool aurora_vulkan_win32_enable(AuroraD3D12StereoSubmittedCallback cb, void* data) {
|
||||
using namespace aurora::vulkan_win32;
|
||||
if (bridge || !cb || !api.Load()) return false;
|
||||
bridge = std::make_unique<Bridge>(cb, data);
|
||||
aurora::stereo::set_sink(
|
||||
[](wgpu::CommandEncoder& encoder, const aurora::stereo::SinkFrame& frame, void* self) noexcept {
|
||||
return static_cast<Bridge*>(self)->Encode(encoder, frame);
|
||||
}, [](const aurora::stereo::SinkFrame& frame, void* self) noexcept { static_cast<Bridge*>(self)->Submitted(frame); }, bridge.get());
|
||||
return true;
|
||||
}
|
||||
bool aurora_vulkan_win32_set_targets(uint64_t token, const AuroraD3D12StereoTarget* targets, uint32_t count) {
|
||||
using namespace aurora::vulkan_win32;
|
||||
return bridge && bridge->Set(token, targets, count);
|
||||
}
|
||||
bool aurora_vulkan_win32_cancel(uint64_t token) {
|
||||
using namespace aurora::vulkan_win32;
|
||||
return bridge && bridge->Cancel(token);
|
||||
}
|
||||
bool aurora_vulkan_win32_disable() {
|
||||
using namespace aurora::vulkan_win32;
|
||||
if (!bridge) return true;
|
||||
aurora::stereo::set_sink(nullptr, nullptr, nullptr);
|
||||
if (!api.drain(aurora::webgpu::g_device.Get())) { bridge.release(); return false; }
|
||||
bridge.reset();
|
||||
return true;
|
||||
}
|
||||
void* aurora_vulkan_win32_lock_queue() {
|
||||
return aurora::vulkan_win32::api.lock(aurora::webgpu::g_device.Get());
|
||||
}
|
||||
void aurora_vulkan_win32_unlock_queue(void* guard) { aurora::vulkan_win32::api.unlock(guard); }
|
||||
#else
|
||||
// C ABI stubs keep the runtime's OpenXR integration linkable on Windows GX
|
||||
// builds whose Dawn has no Vulkan backend; the backend then reports that the
|
||||
// bridge is unavailable and the game falls back to the desktop renderer.
|
||||
bool aurora_vulkan_win32_configure(const AuroraDawnVulkanHooks*) { return false; }
|
||||
bool aurora_vulkan_win32_get_handles(AuroraDawnVulkanHandles* handles, int64_t* format) {
|
||||
if (handles) *handles = {};
|
||||
if (format) *format = 0;
|
||||
return false;
|
||||
}
|
||||
bool aurora_vulkan_win32_enable(AuroraD3D12StereoSubmittedCallback, void*) { return false; }
|
||||
bool aurora_vulkan_win32_set_targets(uint64_t, const AuroraD3D12StereoTarget*, uint32_t) { return false; }
|
||||
bool aurora_vulkan_win32_cancel(uint64_t) { return false; }
|
||||
bool aurora_vulkan_win32_disable() { return true; }
|
||||
void* aurora_vulkan_win32_lock_queue() { return nullptr; }
|
||||
void aurora_vulkan_win32_unlock_queue(void*) {}
|
||||
#endif
|
||||
@@ -0,0 +1,38 @@
|
||||
"""Apply the versioned Aurora native Vulkan ABI to the pinned Dawn source tree."""
|
||||
from pathlib import Path
|
||||
import shutil, sys
|
||||
root = Path(sys.argv[1]).resolve()
|
||||
here = Path(__file__).resolve().parent
|
||||
for name in ("aurora_vulkan_hooks.h", "aurora_vulkan_interop.inc"):
|
||||
shutil.copyfile(here / name, root / "src/dawn/native/vulkan" / name)
|
||||
shutil.copyfile(here.parent.parent / "include/aurora/dawn_vulkan_abi.h",
|
||||
root / "src/dawn/native/vulkan/aurora_dawn_vulkan_abi.h")
|
||||
p = root / "src/dawn/native/vulkan/VulkanBackend.cpp"
|
||||
s = p.read_text()
|
||||
if '#include "aurora_vulkan_interop.inc"' not in s:
|
||||
s += '\n#include "aurora_vulkan_interop.inc"\n'
|
||||
p.write_text(s)
|
||||
p = root / "src/dawn/common/DynamicLib.cpp"
|
||||
s = p.read_text()
|
||||
# LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR requires an absolute filename. Vulkan's
|
||||
# system loader is also tried by bare name; preserve the restricted default
|
||||
# search directories for that case instead of failing with ERROR_INVALID_PARAMETER.
|
||||
old = 'LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS;'
|
||||
new = '''((filename.size() > 2 && filename[1] == ':') || filename.starts_with("\\\\\\\\")
|
||||
? LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR : 0) | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS;'''
|
||||
if old in s:
|
||||
s = s.replace(old, new, 1)
|
||||
p.write_text(s)
|
||||
p = root / "src/dawn/native/vulkan/VulkanFunctions.cpp"
|
||||
s = p.read_text()
|
||||
if '#include "aurora_vulkan_hooks.h"' not in s:
|
||||
marker = 'namespace dawn::native::vulkan {'
|
||||
assert marker in s
|
||||
s = s.replace(marker, '#include "aurora_vulkan_hooks.h"\n\n' + marker, 1)
|
||||
for old, new in [
|
||||
('GET_GLOBAL_PROC(CreateInstance);', 'CreateInstance = AuroraCreateInstance(GetInstanceProcAddr);'),
|
||||
('GET_INSTANCE_PROC(CreateDevice);', 'CreateDevice = AuroraCreateDevice(GetInstanceProcAddr, instance);'),
|
||||
('GET_INSTANCE_PROC(EnumeratePhysicalDevices);', 'EnumeratePhysicalDevices = AuroraEnumeratePhysicalDevices(GetInstanceProcAddr, instance);')]:
|
||||
assert old in s, old
|
||||
s = s.replace(old, old + '\n ' + new, 1)
|
||||
p.write_text(s)
|
||||
@@ -0,0 +1,9 @@
|
||||
// Included only by the pinned Dawn source build.
|
||||
#pragma once
|
||||
#include "dawn/native/DawnNative.h"
|
||||
#include "aurora_dawn_vulkan_abi.h"
|
||||
namespace dawn::native::vulkan {
|
||||
PFN_vkCreateInstance AuroraCreateInstance(PFN_vkGetInstanceProcAddr proc);
|
||||
PFN_vkCreateDevice AuroraCreateDevice(PFN_vkGetInstanceProcAddr proc, VkInstance instance);
|
||||
PFN_vkEnumeratePhysicalDevices AuroraEnumeratePhysicalDevices(PFN_vkGetInstanceProcAddr proc, VkInstance instance);
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
// Compiled inside VulkanBackend.cpp, using the pinned Dawn implementation.
|
||||
#include "aurora_vulkan_hooks.h"
|
||||
#include "src/dawn/native/ChainUtils.h"
|
||||
#include "src/dawn/native/vulkan/PhysicalDeviceVk.h"
|
||||
#include "src/dawn/native/vulkan/QueueVk.h"
|
||||
#include <mutex>
|
||||
namespace dawn::native::vulkan {
|
||||
namespace {
|
||||
AuroraDawnVulkanHooks auroraHooks{};
|
||||
std::recursive_mutex auroraHooksMutex;
|
||||
PFN_vkGetInstanceProcAddr auroraGetProc = nullptr;
|
||||
VkInstance auroraInstance = VK_NULL_HANDLE;
|
||||
::VkResult VKAPI_CALL AuroraCreateInstanceImpl(const VkInstanceCreateInfo* info,
|
||||
const VkAllocationCallbacks* allocator, VkInstance* instance) {
|
||||
std::lock_guard lock(auroraHooksMutex);
|
||||
if (auroraHooks.createInstance) return static_cast<::VkResult>(auroraHooks.createInstance(
|
||||
auroraHooks.userdata, reinterpret_cast<void*>(auroraGetProc), info, allocator,
|
||||
reinterpret_cast<void**>(instance)));
|
||||
return reinterpret_cast<PFN_vkCreateInstance>(auroraGetProc(nullptr, "vkCreateInstance"))(info, allocator, instance);
|
||||
}
|
||||
::VkResult VKAPI_CALL AuroraCreateDeviceImpl(VkPhysicalDevice physical, const VkDeviceCreateInfo* info,
|
||||
const VkAllocationCallbacks* allocator, VkDevice* device) {
|
||||
std::lock_guard lock(auroraHooksMutex);
|
||||
if (auroraHooks.createDevice) return static_cast<::VkResult>(auroraHooks.createDevice(
|
||||
auroraHooks.userdata, reinterpret_cast<void*>(auroraGetProc), physical, info, allocator,
|
||||
reinterpret_cast<void**>(device)));
|
||||
return reinterpret_cast<PFN_vkCreateDevice>(auroraGetProc(auroraInstance, "vkCreateDevice"))(physical, info, allocator, device);
|
||||
}
|
||||
::VkResult VKAPI_CALL AuroraEnumerateImpl(VkInstance instance, uint32_t* count, VkPhysicalDevice* devices) {
|
||||
std::lock_guard lock(auroraHooksMutex);
|
||||
if (!auroraHooks.getPhysicalDevice) return reinterpret_cast<PFN_vkEnumeratePhysicalDevices>(
|
||||
auroraGetProc(instance, "vkEnumeratePhysicalDevices"))(instance, count, devices);
|
||||
void* physical = nullptr;
|
||||
const auto result = static_cast<::VkResult>(auroraHooks.getPhysicalDevice(auroraHooks.userdata, instance, &physical));
|
||||
if (result != VK_SUCCESS) return result;
|
||||
if (!devices) { *count = 1; return VK_SUCCESS; }
|
||||
if (*count == 0) return VK_INCOMPLETE;
|
||||
devices[0] = static_cast<VkPhysicalDevice>(physical);
|
||||
*count = 1;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
struct AuroraDeviceGuard { decltype(std::declval<Device*>()->GetGuard()) guard; explicit AuroraDeviceGuard(Device* device) : guard(device->GetGuard()) {} };
|
||||
}
|
||||
PFN_vkCreateInstance AuroraCreateInstance(PFN_vkGetInstanceProcAddr proc) {
|
||||
std::lock_guard lock(auroraHooksMutex);
|
||||
if (!auroraHooks.createInstance) return reinterpret_cast<PFN_vkCreateInstance>(proc(nullptr, "vkCreateInstance"));
|
||||
auroraGetProc = proc;
|
||||
return AuroraCreateInstanceImpl;
|
||||
}
|
||||
PFN_vkCreateDevice AuroraCreateDevice(PFN_vkGetInstanceProcAddr proc, VkInstance instance) {
|
||||
std::lock_guard lock(auroraHooksMutex);
|
||||
if (!auroraHooks.createDevice) return reinterpret_cast<PFN_vkCreateDevice>(proc(instance, "vkCreateDevice"));
|
||||
auroraGetProc = proc; auroraInstance = instance;
|
||||
return AuroraCreateDeviceImpl;
|
||||
}
|
||||
PFN_vkEnumeratePhysicalDevices AuroraEnumeratePhysicalDevices(PFN_vkGetInstanceProcAddr proc, VkInstance instance) {
|
||||
std::lock_guard lock(auroraHooksMutex);
|
||||
if (!auroraHooks.getPhysicalDevice) return reinterpret_cast<PFN_vkEnumeratePhysicalDevices>(proc(instance, "vkEnumeratePhysicalDevices"));
|
||||
auroraGetProc = proc; auroraInstance = instance;
|
||||
return AuroraEnumerateImpl;
|
||||
}
|
||||
extern "C" DAWN_NATIVE_EXPORT uint32_t AuroraDawnVulkanVersion() { return AURORA_DAWN_VULKAN_ABI; }
|
||||
extern "C" DAWN_NATIVE_EXPORT int AuroraDawnVulkanConfigure(const AuroraDawnVulkanHooks* hooks) {
|
||||
std::lock_guard lock(auroraHooksMutex);
|
||||
// Called before adapter discovery, or cleared after Aurora shutdown. The
|
||||
// runtime and hook storage must outlive all device creation calls.
|
||||
auroraHooks = hooks ? *hooks : AuroraDawnVulkanHooks{};
|
||||
return 1;
|
||||
}
|
||||
extern "C" DAWN_NATIVE_EXPORT int AuroraDawnVulkanGetHandles(void* handle, AuroraDawnVulkanHandles* out) {
|
||||
auto* device = ToBackend(FromAPI(static_cast<WGPUDevice>(handle)));
|
||||
auto guard = device->GetGuard();
|
||||
if (!device->HasFeature(Feature::ImplicitDeviceSynchronization)) return 0;
|
||||
*out = {device->GetVkInstance(), ToBackend(device->GetPhysicalDevice())->GetVkPhysicalDevice(),
|
||||
device->GetVkDevice(), device->GetGraphicsQueueFamily(), 0};
|
||||
return 1;
|
||||
}
|
||||
extern "C" DAWN_NATIVE_EXPORT void* AuroraDawnVulkanWrap(void* handle, const void* desc, uint64_t image) {
|
||||
auto* device = ToBackend(FromAPI(static_cast<WGPUDevice>(handle)));
|
||||
auto guard = device->GetGuard();
|
||||
// Mirror DeviceBase::CreateTexture: fill the trivial frontend defaults
|
||||
// (dimension, mip and sample counts) and validate before the backend reads
|
||||
// them. An Undefined dimension would otherwise abort the first copy.
|
||||
TextureDescriptor raw = reinterpret_cast<const TextureDescriptor*>(desc)->WithTrivialFrontendDefaults();
|
||||
UnpackedPtr<TextureDescriptor> descriptor;
|
||||
if (device->ConsumedError(ValidateAndUnpack(&raw), &descriptor)) return nullptr;
|
||||
if (device->ConsumedError(ValidateTextureDescriptor(device, descriptor, AllowMultiPlanarTextureFormat::No))) return nullptr;
|
||||
auto texture = SwapChainTexture::Create(device, descriptor, VkImage::CreateFromHandle(reinterpret_cast<::VkImage>(image)));
|
||||
texture->SetIsSubresourceContentInitialized(true, texture->GetAllSubresources());
|
||||
texture->UpdateUsage(wgpu::TextureUsage::RenderAttachment, wgpu::ShaderStage::None, texture->GetAllSubresources());
|
||||
return ToAPI(ReturnToAPI(std::move(texture)));
|
||||
}
|
||||
extern "C" DAWN_NATIVE_EXPORT int AuroraDawnVulkanRelease(void* handle, void* const* textures, uint32_t count) {
|
||||
auto* device = ToBackend(FromAPI(static_cast<WGPUDevice>(handle)));
|
||||
auto guard = device->GetGuard();
|
||||
auto* queue = ToBackend(device->GetQueue());
|
||||
auto* context = queue->GetPendingRecordingContext();
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
auto* texture = ToBackend(FromAPI(static_cast<WGPUTexture>(textures[i])));
|
||||
texture->TransitionUsageNow(context, wgpu::TextureUsage::RenderAttachment,
|
||||
wgpu::ShaderStage::None, texture->GetAllSubresources());
|
||||
}
|
||||
return !device->ConsumedError(queue->SubmitPendingCommands());
|
||||
}
|
||||
extern "C" DAWN_NATIVE_EXPORT void* AuroraDawnVulkanLock(void* handle) {
|
||||
auto* device = ToBackend(FromAPI(static_cast<WGPUDevice>(handle)));
|
||||
return new AuroraDeviceGuard(device);
|
||||
}
|
||||
extern "C" DAWN_NATIVE_EXPORT void AuroraDawnVulkanUnlock(void* guard) {
|
||||
delete static_cast<AuroraDeviceGuard*>(guard);
|
||||
}
|
||||
extern "C" DAWN_NATIVE_EXPORT int AuroraDawnVulkanDrain(void* handle) {
|
||||
auto* device = ToBackend(FromAPI(static_cast<WGPUDevice>(handle)));
|
||||
auto guard = device->GetGuard();
|
||||
if (device->ConsumedError(ToBackend(device->GetQueue())->SubmitPendingCommands())) return 0;
|
||||
return device->fn.QueueWaitIdle(ToBackend(device->GetQueue())->GetVkQueue()) == VK_SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,18 @@ include(GoogleTest)
|
||||
|
||||
option(AURORA_GPU_SMOKE_TESTS "Build opt-in tests requiring a desktop GPU" OFF)
|
||||
if (AURORA_GPU_SMOKE_TESTS AND AURORA_ENABLE_GX AND WIN32)
|
||||
# Exercises the custom Dawn DLL's Aurora Vulkan ABI (patches/dawn). Run it with that
|
||||
# webgpu_dawn.dll beside the executable; the stock prebuilt Dawn lacks the exports and the
|
||||
# test reports that at startup. Vulkan headers come from the SDK or any tree on
|
||||
# AURORA_TEST_VULKAN_INCLUDE (the runtime's FetchContent copy works).
|
||||
find_path(AURORA_TEST_VULKAN_INCLUDE vulkan/vulkan.h HINTS "$ENV{VULKAN_SDK}/Include")
|
||||
if (AURORA_TEST_VULKAN_INCLUDE)
|
||||
add_executable(vulkan_native_bridge_smoke vulkan_native_bridge_smoke.cpp)
|
||||
target_include_directories(vulkan_native_bridge_smoke PRIVATE ../include "${AURORA_TEST_VULKAN_INCLUDE}")
|
||||
target_link_libraries(vulkan_native_bridge_smoke PRIVATE dawn::webgpu_dawn dawn::dawncpp_headers)
|
||||
else ()
|
||||
message(STATUS "vulkan_native_bridge_smoke skipped: no vulkan/vulkan.h (set VULKAN_SDK or AURORA_TEST_VULKAN_INCLUDE)")
|
||||
endif ()
|
||||
add_executable(stereo_frame_worker_smoke stereo_frame_worker_smoke.cpp)
|
||||
target_include_directories(stereo_frame_worker_smoke PRIVATE ../lib)
|
||||
target_link_libraries(stereo_frame_worker_smoke PRIVATE aurora::core aurora::gx aurora::main aurora::vi
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
// Opt-in real GPU test for the custom Dawn DLL. No headset required.
|
||||
// Exercises the MinGW/MSVC C ABI, borrowed-image lifetime and repeated layout
|
||||
// transitions. Run with VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation as well.
|
||||
#define NOMINMAX
|
||||
#include <windows.h>
|
||||
#include <vulkan/vulkan.h>
|
||||
#include <dawn/webgpu_cpp.h>
|
||||
#include <aurora/dawn_vulkan_abi.h>
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
static void Check(bool ok) { if (!ok) { std::fputs("Native Vulkan bridge smoke failed\n", stderr); std::abort(); } }
|
||||
static PFN_vkGetInstanceProcAddr hookProc;
|
||||
static VkInstance hookInstance;
|
||||
static int instances = 0, devices = 0, selections = 0;
|
||||
static int32_t CreateInstance(void*, void* proc, const void* info, const void* allocator, void** out) {
|
||||
++instances; hookProc = reinterpret_cast<PFN_vkGetInstanceProcAddr>(proc);
|
||||
auto create = reinterpret_cast<PFN_vkCreateInstance>(hookProc(nullptr, "vkCreateInstance"));
|
||||
auto result = create(static_cast<const VkInstanceCreateInfo*>(info), static_cast<const VkAllocationCallbacks*>(allocator), &hookInstance);
|
||||
*out = hookInstance; return result;
|
||||
}
|
||||
static int32_t CreateDevice(void*, void*, void* physical, const void* info, const void* allocator, void** out) {
|
||||
++devices;
|
||||
auto create = reinterpret_cast<PFN_vkCreateDevice>(hookProc(hookInstance, "vkCreateDevice"));
|
||||
return create(static_cast<VkPhysicalDevice>(physical), static_cast<const VkDeviceCreateInfo*>(info),
|
||||
static_cast<const VkAllocationCallbacks*>(allocator), reinterpret_cast<VkDevice*>(out));
|
||||
}
|
||||
static int32_t SelectPhysical(void*, void* instance, void** out) {
|
||||
++selections;
|
||||
auto enumerate = reinterpret_cast<PFN_vkEnumeratePhysicalDevices>(hookProc(static_cast<VkInstance>(instance), "vkEnumeratePhysicalDevices"));
|
||||
uint32_t count = 1;
|
||||
auto result = enumerate(static_cast<VkInstance>(instance), &count, reinterpret_cast<VkPhysicalDevice*>(out));
|
||||
return result == VK_INCOMPLETE ? VK_SUCCESS : result;
|
||||
}
|
||||
int main() {
|
||||
auto dll = LoadLibraryW(L"webgpu_dawn.dll"); Check(dll != nullptr);
|
||||
#define API(name, type) auto name = reinterpret_cast<type>(GetProcAddress(dll, "AuroraDawnVulkan" #name)); Check(name != nullptr)
|
||||
API(Version, AuroraDawnVulkanVersionFn);
|
||||
API(Configure, AuroraDawnVulkanConfigureFn);
|
||||
API(GetHandles, AuroraDawnVulkanHandlesFn);
|
||||
API(Wrap, AuroraDawnVulkanWrapFn);
|
||||
API(Release, AuroraDawnVulkanReleaseFn);
|
||||
API(Lock, AuroraDawnVulkanLockFn);
|
||||
API(Unlock, AuroraDawnVulkanUnlockFn);
|
||||
API(Drain, AuroraDawnVulkanDrainFn);
|
||||
Check(Version() == AURORA_DAWN_VULKAN_ABI);
|
||||
AuroraDawnVulkanHooks hooks{nullptr, CreateInstance, CreateDevice, SelectPhysical};
|
||||
Check(Configure(&hooks));
|
||||
wgpu::InstanceFeatureName feature = wgpu::InstanceFeatureName::TimedWaitAny;
|
||||
wgpu::InstanceDescriptor instanceDesc;
|
||||
instanceDesc.requiredFeatureCount = 1; instanceDesc.requiredFeatures = &feature;
|
||||
std::fputs("Creating WebGPU instance\n", stderr);
|
||||
auto instance = wgpu::CreateInstance(&instanceDesc);
|
||||
wgpu::RequestAdapterOptions options; options.backendType = wgpu::BackendType::Vulkan;
|
||||
wgpu::Adapter adapter;
|
||||
std::fputs("Requesting Vulkan adapter\n", stderr);
|
||||
auto future = instance.RequestAdapter(&options, wgpu::CallbackMode::WaitAnyOnly,
|
||||
[&](wgpu::RequestAdapterStatus status, wgpu::Adapter value, wgpu::StringView) {
|
||||
Check(status == wgpu::RequestAdapterStatus::Success); adapter = std::move(value);
|
||||
});
|
||||
Check(instance.WaitAny(future, 10'000'000'000) == wgpu::WaitStatus::Success);
|
||||
wgpu::FeatureName sync = wgpu::FeatureName::ImplicitDeviceSynchronization;
|
||||
wgpu::DeviceDescriptor deviceDesc;
|
||||
deviceDesc.requiredFeatureCount = 1; deviceDesc.requiredFeatures = &sync;
|
||||
deviceDesc.SetUncapturedErrorCallback([](const wgpu::Device&, wgpu::ErrorType, wgpu::StringView message) {
|
||||
std::fprintf(stderr, "Dawn: %.*s\n", static_cast<int>(message.length), message.data); Check(false);
|
||||
});
|
||||
std::fputs("Creating Vulkan device\n", stderr);
|
||||
auto device = adapter.CreateDevice(&deviceDesc); Check(!!device);
|
||||
Check(instances > 0 && devices > 0 && selections > 0);
|
||||
std::fputs("Getting native device\n", stderr);
|
||||
AuroraDawnVulkanHandles handles{}; Check(GetHandles(device.Get(), &handles));
|
||||
VkDevice vkDevice = static_cast<VkDevice>(handles.device);
|
||||
VkPhysicalDevice physical = static_cast<VkPhysicalDevice>(handles.physicalDevice);
|
||||
auto loader = LoadLibraryW(L"vulkan-1.dll"); Check(loader != nullptr);
|
||||
auto getProc = reinterpret_cast<PFN_vkGetInstanceProcAddr>(GetProcAddress(loader, "vkGetInstanceProcAddr"));
|
||||
#define VK(name) auto name = reinterpret_cast<PFN_##name>(getProc(static_cast<VkInstance>(handles.instance), #name)); Check(name != nullptr)
|
||||
VK(vkCreateImage); VK(vkGetImageMemoryRequirements); VK(vkGetPhysicalDeviceMemoryProperties);
|
||||
VK(vkAllocateMemory); VK(vkBindImageMemory); VK(vkCreateCommandPool); VK(vkAllocateCommandBuffers);
|
||||
VK(vkBeginCommandBuffer); VK(vkCmdPipelineBarrier); VK(vkEndCommandBuffer); VK(vkGetDeviceQueue);
|
||||
VK(vkQueueSubmit); VK(vkQueueWaitIdle); VK(vkDestroyCommandPool); VK(vkDestroyImage); VK(vkFreeMemory);
|
||||
VkImageCreateInfo imageInfo{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
|
||||
imageInfo.imageType = VK_IMAGE_TYPE_2D; imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
imageInfo.extent = {64, 16, 1}; imageInfo.mipLevels = 1; imageInfo.arrayLayers = 1;
|
||||
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
||||
std::fputs("Creating borrowed Vulkan image\n", stderr);
|
||||
VkImage image; Check(vkCreateImage(vkDevice, &imageInfo, nullptr, &image) == VK_SUCCESS);
|
||||
VkMemoryRequirements requirements; vkGetImageMemoryRequirements(vkDevice, image, &requirements);
|
||||
VkPhysicalDeviceMemoryProperties properties; vkGetPhysicalDeviceMemoryProperties(physical, &properties);
|
||||
uint32_t memoryType = 0;
|
||||
while (!(requirements.memoryTypeBits & (1u << memoryType))) ++memoryType;
|
||||
VkMemoryAllocateInfo allocation{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
|
||||
allocation.allocationSize = requirements.size; allocation.memoryTypeIndex = memoryType;
|
||||
VkDeviceMemory memory; Check(vkAllocateMemory(vkDevice, &allocation, nullptr, &memory) == VK_SUCCESS);
|
||||
Check(vkBindImageMemory(vkDevice, image, memory, 0) == VK_SUCCESS);
|
||||
VkCommandPoolCreateInfo poolInfo{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO}; poolInfo.queueFamilyIndex = handles.queueFamily;
|
||||
VkCommandPool pool; Check(vkCreateCommandPool(vkDevice, &poolInfo, nullptr, &pool) == VK_SUCCESS);
|
||||
VkCommandBufferAllocateInfo alloc{VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO};
|
||||
alloc.commandPool = pool; alloc.commandBufferCount = 1; alloc.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||||
VkCommandBuffer commands; Check(vkAllocateCommandBuffers(vkDevice, &alloc, &commands) == VK_SUCCESS);
|
||||
VkCommandBufferBeginInfo begin{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
|
||||
Check(vkBeginCommandBuffer(commands, &begin) == VK_SUCCESS);
|
||||
VkImageMemoryBarrier barrier{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
|
||||
barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
barrier.srcQueueFamilyIndex = barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.image = image; barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||
barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
vkCmdPipelineBarrier(commands, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||
0, 0, nullptr, 0, nullptr, 1, &barrier);
|
||||
Check(vkEndCommandBuffer(commands) == VK_SUCCESS);
|
||||
VkQueue queue; vkGetDeviceQueue(vkDevice, handles.queueFamily, handles.queueIndex, &queue);
|
||||
VkSubmitInfo submit{VK_STRUCTURE_TYPE_SUBMIT_INFO}; submit.commandBufferCount = 1; submit.pCommandBuffers = &commands;
|
||||
std::fputs("Locking native queue\n", stderr);
|
||||
auto guard = Lock(device.Get());
|
||||
Check(vkQueueSubmit(queue, 1, &submit, VK_NULL_HANDLE) == VK_SUCCESS);
|
||||
Check(vkQueueWaitIdle(queue) == VK_SUCCESS); Unlock(guard);
|
||||
wgpu::TextureDescriptor textureDesc;
|
||||
textureDesc.size = {64, 16, 1}; textureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
|
||||
textureDesc.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment;
|
||||
std::fputs("Wrapping borrowed image\n", stderr);
|
||||
auto borrowed = wgpu::Texture::Acquire(static_cast<WGPUTexture>(Wrap(device.Get(), &textureDesc, reinterpret_cast<uint64_t>(image))));
|
||||
Check(!!borrowed);
|
||||
std::fputs("Creating copy resources\n", stderr);
|
||||
auto source = device.CreateTexture(&textureDesc);
|
||||
wgpu::BufferDescriptor bufferDesc; bufferDesc.size = 4096;
|
||||
bufferDesc.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::MapRead;
|
||||
auto readback = device.CreateBuffer(&bufferDesc);
|
||||
std::fputs("Running GPU copy\n", stderr);
|
||||
for (uint8_t value : {17, 99, 201}) {
|
||||
std::array<uint8_t, 4096> pixels; pixels.fill(value);
|
||||
wgpu::TexelCopyTextureInfo sourceInfo; sourceInfo.texture = source;
|
||||
wgpu::TexelCopyTextureInfo targetInfo; targetInfo.texture = borrowed;
|
||||
wgpu::TexelCopyBufferLayout layout; layout.bytesPerRow = 256; layout.rowsPerImage = 16;
|
||||
wgpu::Extent3D extent{64, 16, 1};
|
||||
device.GetQueue().WriteTexture(&sourceInfo, pixels.data(), pixels.size(), &layout, &extent);
|
||||
std::fputs("WriteTexture done\n", stderr);
|
||||
auto encoder = device.CreateCommandEncoder(); encoder.CopyTextureToTexture(&sourceInfo, &targetInfo, &extent);
|
||||
auto copy = encoder.Finish(); device.GetQueue().Submit(1, ©);
|
||||
std::fputs("Copy submitted\n", stderr);
|
||||
void* textures[] = {borrowed.Get()}; Check(Release(device.Get(), textures, 1));
|
||||
std::fputs("Release transition done\n", stderr);
|
||||
encoder = device.CreateCommandEncoder();
|
||||
wgpu::TexelCopyBufferInfo destination; destination.buffer = readback; destination.layout = layout;
|
||||
encoder.CopyTextureToBuffer(&targetInfo, &destination, &extent);
|
||||
copy = encoder.Finish(); device.GetQueue().Submit(1, ©);
|
||||
Check(Release(device.Get(), textures, 1));
|
||||
auto mapped = readback.MapAsync(wgpu::MapMode::Read, 0, 4096, wgpu::CallbackMode::WaitAnyOnly,
|
||||
[](wgpu::MapAsyncStatus status, wgpu::StringView) { Check(status == wgpu::MapAsyncStatus::Success); });
|
||||
Check(instance.WaitAny(mapped, 10'000'000'000) == wgpu::WaitStatus::Success);
|
||||
const auto* bytes = static_cast<const uint8_t*>(readback.GetConstMappedRange());
|
||||
for (size_t i = 0; i < pixels.size(); ++i) Check(bytes[i] == value);
|
||||
readback.Unmap();
|
||||
}
|
||||
Check(Drain(device.Get())); borrowed = nullptr;
|
||||
// The wrapper must not free the runtime-owned image or its memory.
|
||||
vkDestroyImage(vkDevice, image, nullptr); vkFreeMemory(vkDevice, memory, nullptr);
|
||||
vkDestroyCommandPool(vkDevice, pool, nullptr);
|
||||
Check(Configure(nullptr));
|
||||
std::puts("Native Vulkan bridge: three GPU copy/readback cycles passed");
|
||||
}
|
||||
@@ -286,6 +286,15 @@ if(MKW_PROJECT_COMPILE_DEFINITIONS)
|
||||
add_compile_definitions(${MKW_PROJECT_COMPILE_DEFINITIONS})
|
||||
endif()
|
||||
|
||||
if(MKW_PLATFORM_WINDOWS AND MKW_ENABLE_OPENXR)
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(vulkan_headers
|
||||
URL "https://github.com/KhronosGroup/Vulkan-Headers/archive/015e25c3c91b70eb1a754d36fb14c4ba6ad9b0b9.tar.gz"
|
||||
DOWNLOAD_EXTRACT_TIMESTAMP TRUE)
|
||||
FetchContent_MakeAvailable(vulkan_headers)
|
||||
include_directories("${vulkan_headers_SOURCE_DIR}/include")
|
||||
endif()
|
||||
|
||||
set(MKW_OPENXR_TARGET "")
|
||||
if(MKW_ENABLE_OPENXR)
|
||||
if(NOT MKW_PLATFORM_WINDOWS AND NOT MKW_PLATFORM_LINUX AND NOT MKW_PLATFORM_ANDROID)
|
||||
@@ -340,6 +349,7 @@ endif()
|
||||
# a registration file is silently never compiled and never errors. The stale-glob
|
||||
# failure mode is worth far more than the milliseconds.
|
||||
file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS "src/*.cpp")
|
||||
set_source_files_properties(src/vr/openxr_vulkan_win32.cpp PROPERTIES SKIP_UNITY_BUILD_INCLUSION ON)
|
||||
if(MKW_PLATFORM_MACOS)
|
||||
list(REMOVE_ITEM SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/guest_flat_memory.cpp")
|
||||
# HostContext's Apple Silicon backend is implemented in a small assembly
|
||||
@@ -453,6 +463,16 @@ if(MKW_ENABLE_OPENXR AND MKW_PLATFORM_WINDOWS)
|
||||
target_compile_features(mkw_openxr_replay_tests PRIVATE cxx_std_17)
|
||||
set_target_properties(mkw_openxr_replay_tests PROPERTIES UNITY_BUILD OFF)
|
||||
add_test(NAME mkw_openxr_replay_tests COMMAND mkw_openxr_replay_tests)
|
||||
add_executable(mkw_openxr_vulkan_replay_tests
|
||||
tests/openxr_d3d12_replay_tests.cpp src/vr/openxr_vulkan_win32.cpp src/vr/openxr_diagnostics.cpp)
|
||||
target_include_directories(mkw_openxr_vulkan_replay_tests PRIVATE
|
||||
"${CMAKE_CURRENT_LIST_DIR}/include"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/../aurora-main/include"
|
||||
"$<TARGET_PROPERTY:${MKW_OPENXR_TARGET},INTERFACE_INCLUDE_DIRECTORIES>")
|
||||
target_compile_definitions(mkw_openxr_vulkan_replay_tests PRIVATE MKW_ENABLE_OPENXR=1 TEST_WINDOWS_VULKAN=1)
|
||||
target_compile_features(mkw_openxr_vulkan_replay_tests PRIVATE cxx_std_17)
|
||||
set_target_properties(mkw_openxr_vulkan_replay_tests PROPERTIES UNITY_BUILD OFF)
|
||||
add_test(NAME mkw_openxr_vulkan_replay_tests COMMAND mkw_openxr_vulkan_replay_tests)
|
||||
endif()
|
||||
|
||||
add_executable(mkw_nand_save_tests "${CMAKE_CURRENT_LIST_DIR}/tests/nand_save_tests.cpp")
|
||||
|
||||
@@ -106,6 +106,23 @@ public:
|
||||
OpenXRRuntime(OpenXRRuntime&&) = delete;
|
||||
OpenXRRuntime& operator=(OpenXRRuntime&&) = delete;
|
||||
|
||||
// Same-device Vulkan runtimes may touch Dawn's queue in frame/swapchain
|
||||
// calls. Share Dawn's device guard; never hold it across xrWaitFrame.
|
||||
struct GraphicsQueueGuard {
|
||||
GraphicsQueueGuard(void* value, void (*release)(void*)) : token(value), unlock(release) {}
|
||||
GraphicsQueueGuard(const GraphicsQueueGuard&) = delete;
|
||||
GraphicsQueueGuard& operator=(const GraphicsQueueGuard&) = delete;
|
||||
void* token;
|
||||
void (*unlock)(void*);
|
||||
~GraphicsQueueGuard() { if (token && unlock) unlock(token); }
|
||||
};
|
||||
void SetGraphicsQueueGuard(void* (*lock)(), void (*unlock)(void*)) {
|
||||
m_queue_lock = lock; m_queue_unlock = unlock;
|
||||
}
|
||||
GraphicsQueueGuard LockGraphicsQueue() const {
|
||||
return {m_queue_lock ? m_queue_lock() : nullptr, m_queue_unlock};
|
||||
}
|
||||
|
||||
// Creates the instance, resolves the HMD system, and enumerates the stereo
|
||||
// view configuration. Returns false without terminating the application;
|
||||
// the caller should continue in non-VR mode.
|
||||
@@ -214,6 +231,8 @@ public:
|
||||
const OpenXRError& LastError() const { return m_last_error; }
|
||||
|
||||
private:
|
||||
void* (*m_queue_lock)() = nullptr;
|
||||
void (*m_queue_unlock)(void*) = nullptr;
|
||||
enum class FramePhase {
|
||||
Idle,
|
||||
Waited,
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined(MKW_ENABLE_OPENXR) && defined(_WIN32)
|
||||
|
||||
#include "vr/openxr_backend.h"
|
||||
#include "vr/openxr_runtime.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace mkw::vr {
|
||||
|
||||
// Shared frame vocabulary for the Windows pacing and replay paths.
|
||||
using OpenXRWindowsVulkanFrameMode = OpenXRFrameMode;
|
||||
using OpenXRWindowsVulkanBeginStatus = OpenXRBeginStatus;
|
||||
using OpenXRWindowsVulkanSubmissionStatus = OpenXRSubmissionStatus;
|
||||
using OpenXRWindowsVulkanPresentation = OpenXRPresentation;
|
||||
using OpenXRWindowsVulkanFrame = OpenXRBackendFrame;
|
||||
|
||||
struct OpenXRWindowsVulkanGraphicsRequirements {
|
||||
XrVersion min_api_version = 0;
|
||||
XrVersion max_api_version = 0;
|
||||
};
|
||||
|
||||
// Same-device Dawn/OpenXR Vulkan backend.
|
||||
//
|
||||
// Startup is deliberately split in two. QueryGraphicsRequirements() runs
|
||||
// after OpenXRRuntime::Initialize() but before aurora_initialize(), allowing
|
||||
// OpenXR to create Dawn's Vulkan instance/device and select the physical GPU.
|
||||
// BindAurora() runs afterwards and checks the device before creating a session.
|
||||
//
|
||||
// All methods from BeginFrame() through FinishFrame(), plus PollEvents on the
|
||||
// associated OpenXRRuntime, belong to one XR pacing thread. Aurora's frame
|
||||
// worker never calls OpenXR: its post-submit callback only publishes a token
|
||||
// that WaitForSubmission() consumes. This is the synchronization boundary
|
||||
// required by the asynchronous sealed-frame renderer.
|
||||
class OpenXRWindowsVulkanBackend final {
|
||||
public:
|
||||
explicit OpenXRWindowsVulkanBackend(OpenXRLogCallback logger = {});
|
||||
~OpenXRWindowsVulkanBackend();
|
||||
|
||||
OpenXRWindowsVulkanBackend(const OpenXRWindowsVulkanBackend&) = delete;
|
||||
OpenXRWindowsVulkanBackend& operator=(const OpenXRWindowsVulkanBackend&) = delete;
|
||||
OpenXRWindowsVulkanBackend(OpenXRWindowsVulkanBackend&&) = delete;
|
||||
OpenXRWindowsVulkanBackend& operator=(OpenXRWindowsVulkanBackend&&) = delete;
|
||||
|
||||
bool QueryGraphicsRequirements(OpenXRRuntime& runtime);
|
||||
bool BindAurora(OpenXRRuntime& runtime);
|
||||
|
||||
OpenXRWindowsVulkanBeginStatus BeginFrame(const OpenXRWindowsVulkanPresentation& presentation,
|
||||
OpenXRWindowsVulkanFrame& frame);
|
||||
|
||||
// timeout_ms == UINT32_MAX waits until Aurora publishes this token or
|
||||
// Shutdown() interrupts the wait. A timeout does not release XR images;
|
||||
// the caller may keep pacing with RepeatFrame while Aurora still owns them.
|
||||
OpenXRWindowsVulkanSubmissionStatus WaitForSubmission(const OpenXRWindowsVulkanFrame& frame,
|
||||
uint32_t timeout_ms = UINT32_MAX);
|
||||
|
||||
// Withdraws this token only if Aurora has not encoded it. On success no GPU
|
||||
// command can reference the acquired images, and FinishFrame(frame, false)
|
||||
// is required to release them and close the compositor frame.
|
||||
bool TryCancelPendingFrame(OpenXRWindowsVulkanFrame& frame);
|
||||
|
||||
// Ends the current compositor cycle with the last completed layer and starts
|
||||
// another, without releasing or changing Aurora's pending images/render token.
|
||||
// The original render poses remain attached to the pending and retained images.
|
||||
bool RepeatFrame(const OpenXRWindowsVulkanFrame& frame);
|
||||
|
||||
// Releases acquired images and calls xrEndFrame. submit_layer must only be
|
||||
// true after WaitForSubmission returned Success. Immersive frames submit
|
||||
// XrCompositionLayerProjection; virtual-screen frames submit an
|
||||
// XrCompositionLayerQuad using the single mono target, placed as the
|
||||
// presentation's quad_anchored/quad_pose describe. If no new usable layer is
|
||||
// available, resubmits the retained layer using the current display time.
|
||||
bool FinishFrame(OpenXRWindowsVulkanFrame& frame, bool submit_layer);
|
||||
|
||||
// Render-first path when interpolation is off. Aurora renders into acquired
|
||||
// non-retained XR images while no compositor frame is open. BeginFrameForPacket
|
||||
// accepts only a completed packet; CopyRenderedEyes verifies the already queued
|
||||
// bridge copy. FinishFrame releases the images and submits their original poses.
|
||||
OpenXRBeginStatus PreparePacket(const OpenXRPresentation& presentation, OpenXRBackendFrame& packet);
|
||||
bool TryCancelPendingPacket(OpenXRBackendFrame& packet);
|
||||
OpenXRBeginStatus BeginFrameForPacket(const OpenXRBackendFrame& packet, OpenXRBackendFrame& frame);
|
||||
OpenXRSubmissionStatus CopyRenderedEyes(const OpenXRBackendFrame& frame);
|
||||
OpenXRBeginStatus KeepAliveCycle();
|
||||
|
||||
// Call on the XR owner thread after Aurora's worker is idle and before
|
||||
// aurora_shutdown(). Safe to repeat. False means a submitted Vulkan command
|
||||
// could not be fenced; the caller must retain this backend and its runtime
|
||||
// for the process lifetime instead of destroying possibly live resources.
|
||||
bool Shutdown();
|
||||
|
||||
bool IsBound() const;
|
||||
const OpenXRWindowsVulkanGraphicsRequirements& GraphicsRequirements() const;
|
||||
int64_t SwapchainFormat() const;
|
||||
const std::string& LastError() const;
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> m_impl;
|
||||
};
|
||||
|
||||
} // namespace mkw::vr
|
||||
|
||||
#endif // defined(MKW_ENABLE_OPENXR) && defined(_WIN32)
|
||||
@@ -0,0 +1,64 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#pragma once
|
||||
#include "vr/openxr_d3d12.h"
|
||||
#include "vr/openxr_vulkan_win32.h"
|
||||
#include <variant>
|
||||
namespace mkw::vr {
|
||||
// Runtime API selection; both backends expose the same pacing/ownership contract.
|
||||
class OpenXRWindowsBackend {
|
||||
std::variant<std::unique_ptr<OpenXRD3D12Backend>, std::unique_ptr<OpenXRWindowsVulkanBackend>> backend_;
|
||||
public:
|
||||
OpenXRWindowsBackend(OpenXRLogCallback logger, bool vulkan) {
|
||||
if (vulkan) backend_ = std::make_unique<OpenXRWindowsVulkanBackend>(logger);
|
||||
else backend_ = std::make_unique<OpenXRD3D12Backend>(logger);
|
||||
}
|
||||
const OpenXRD3D12GraphicsRequirements& GraphicsRequirements() {
|
||||
return std::get<0>(backend_)->GraphicsRequirements();
|
||||
}
|
||||
bool QueryGraphicsRequirements(OpenXRRuntime& runtime) {
|
||||
return std::visit([&](auto& backend) -> bool { return backend->QueryGraphicsRequirements(runtime); }, backend_);
|
||||
}
|
||||
bool BindAurora(OpenXRRuntime& runtime) {
|
||||
return std::visit([&](auto& backend) -> bool { return backend->BindAurora(runtime); }, backend_);
|
||||
}
|
||||
OpenXRBeginStatus BeginFrame(const OpenXRPresentation& presentation, OpenXRBackendFrame& frame) {
|
||||
return std::visit([&](auto& backend) -> OpenXRBeginStatus { return backend->BeginFrame(presentation, frame); }, backend_);
|
||||
}
|
||||
OpenXRSubmissionStatus WaitForSubmission(const OpenXRBackendFrame& frame, uint32_t timeout) {
|
||||
return std::visit([&](auto& backend) -> OpenXRSubmissionStatus { return backend->WaitForSubmission(frame, timeout); }, backend_);
|
||||
}
|
||||
bool TryCancelPendingFrame(OpenXRBackendFrame& frame) {
|
||||
return std::visit([&](auto& backend) -> bool { return backend->TryCancelPendingFrame(frame); }, backend_);
|
||||
}
|
||||
bool RepeatFrame(const OpenXRBackendFrame& frame) {
|
||||
return std::visit([&](auto& backend) -> bool { return backend->RepeatFrame(frame); }, backend_);
|
||||
}
|
||||
bool FinishFrame(OpenXRBackendFrame& frame, bool submit) {
|
||||
return std::visit([&](auto& backend) -> bool { return backend->FinishFrame(frame, submit); }, backend_);
|
||||
}
|
||||
OpenXRBeginStatus PreparePacket(const OpenXRPresentation& presentation, OpenXRBackendFrame& frame) {
|
||||
return std::visit([&](auto& backend) -> OpenXRBeginStatus { return backend->PreparePacket(presentation, frame); }, backend_);
|
||||
}
|
||||
bool TryCancelPendingPacket(OpenXRBackendFrame& frame) {
|
||||
return std::visit([&](auto& backend) -> bool { return backend->TryCancelPendingPacket(frame); }, backend_);
|
||||
}
|
||||
OpenXRBeginStatus BeginFrameForPacket(const OpenXRBackendFrame& packet, OpenXRBackendFrame& frame) {
|
||||
return std::visit([&](auto& backend) -> OpenXRBeginStatus { return backend->BeginFrameForPacket(packet, frame); }, backend_);
|
||||
}
|
||||
OpenXRSubmissionStatus CopyRenderedEyes(const OpenXRBackendFrame& frame) {
|
||||
return std::visit([&](auto& backend) -> OpenXRSubmissionStatus { return backend->CopyRenderedEyes(frame); }, backend_);
|
||||
}
|
||||
OpenXRBeginStatus KeepAliveCycle() {
|
||||
return std::visit([&](auto& backend) -> OpenXRBeginStatus { return backend->KeepAliveCycle(); }, backend_);
|
||||
}
|
||||
bool Shutdown() {
|
||||
return std::visit([&](auto& backend) -> bool { return backend->Shutdown(); }, backend_);
|
||||
}
|
||||
int64_t SwapchainFormat() {
|
||||
return std::visit([&](auto& backend) -> int64_t { return backend->SwapchainFormat(); }, backend_);
|
||||
}
|
||||
const std::string& LastError() {
|
||||
return std::visit([&](auto& backend) -> const std::string& { return backend->LastError(); }, backend_);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -34,7 +34,7 @@
|
||||
#include "vr/openxr_input.h"
|
||||
#include "vr/openxr_runtime.h"
|
||||
#if defined(_WIN32)
|
||||
#include "vr/openxr_d3d12.h"
|
||||
#include "vr/openxr_windows.h"
|
||||
#define MKW_OPENXR_GRAPHICS_BACKEND 1
|
||||
#elif defined(__ANDROID__)
|
||||
#include "vr/openxr_android.h"
|
||||
@@ -78,8 +78,8 @@ void ConfigurePolicy(bool enabled) noexcept {
|
||||
#if MKW_OPENXR_GRAPHICS_BACKEND
|
||||
|
||||
#if defined(_WIN32)
|
||||
using GraphicsBackend = OpenXRD3D12Backend;
|
||||
inline constexpr const char* kGraphicsBackendName = "D3D12";
|
||||
using GraphicsBackend = OpenXRWindowsBackend;
|
||||
|
||||
#else
|
||||
using GraphicsBackend = OpenXRVulkanBackend;
|
||||
inline constexpr const char* kGraphicsBackendName = "Vulkan";
|
||||
@@ -360,7 +360,11 @@ public:
|
||||
}
|
||||
#endif
|
||||
runtime_ = std::make_unique<OpenXRRuntime>(logger_);
|
||||
#if defined(_WIN32)
|
||||
backend_ = std::make_unique<GraphicsBackend>(logger_, kRequiredAuroraBackend == BACKEND_VULKAN);
|
||||
#else
|
||||
backend_ = std::make_unique<GraphicsBackend>(logger_);
|
||||
#endif
|
||||
|
||||
OpenXRConfig config{};
|
||||
config.application_name = aurora_config.appName != nullptr ? aurora_config.appName
|
||||
@@ -368,7 +372,7 @@ public:
|
||||
config.engine_name = "Aurora";
|
||||
config.resolution_scale = RuntimeConfigFile::VrRenderScale();
|
||||
#if defined(_WIN32)
|
||||
config.required_extensions = {"XR_KHR_D3D12_enable"};
|
||||
config.required_extensions = {kRequiredAuroraBackend == BACKEND_VULKAN ? "XR_KHR_vulkan_enable2" : "XR_KHR_D3D12_enable"};
|
||||
config.optional_extensions = {"XR_KHR_win32_convert_performance_counter_time",
|
||||
"XR_FB_display_refresh_rate", "XR_EXT_performance_settings"};
|
||||
#else
|
||||
@@ -567,7 +571,8 @@ private:
|
||||
};
|
||||
|
||||
#if defined(_WIN32)
|
||||
static constexpr AuroraBackend kRequiredAuroraBackend = BACKEND_D3D12;
|
||||
AuroraBackend kRequiredAuroraBackend = BACKEND_D3D12;
|
||||
const char* kGraphicsBackendName = "D3D12";
|
||||
#else
|
||||
static constexpr AuroraBackend kRequiredAuroraBackend = BACKEND_VULKAN;
|
||||
#endif
|
||||
@@ -576,6 +581,10 @@ private:
|
||||
static constexpr uint32_t kMaxConsecutiveSkips = 300;
|
||||
|
||||
bool BackendMatchesConfiguredGraphicsApi(const AuroraConfig& aurora_config) {
|
||||
#if defined(_WIN32)
|
||||
kRequiredAuroraBackend = aurora_config.desiredBackend == BACKEND_VULKAN ? BACKEND_VULKAN : BACKEND_D3D12;
|
||||
kGraphicsBackendName = kRequiredAuroraBackend == BACKEND_VULKAN ? "Vulkan" : "D3D12";
|
||||
#endif
|
||||
if (aurora_config.desiredBackend == BACKEND_AUTO ||
|
||||
aurora_config.desiredBackend == kRequiredAuroraBackend) {
|
||||
return true;
|
||||
@@ -589,6 +598,7 @@ private:
|
||||
aurora_config.desiredBackend = kRequiredAuroraBackend;
|
||||
aurora_config.xrInterop = true;
|
||||
#if defined(_WIN32)
|
||||
if (kRequiredAuroraBackend != BACKEND_D3D12) return;
|
||||
const auto& requirements = backend_->GraphicsRequirements();
|
||||
aurora_config.hasD3D12AdapterLuid = true;
|
||||
aurora_config.d3d12AdapterLuidLow = requirements.adapter_luid_low;
|
||||
|
||||
@@ -724,6 +724,7 @@ bool OpenXRRuntime::BeginFrame(const OpenXRFrame& frame) {
|
||||
|
||||
XrFrameBeginInfo begin_info{XR_TYPE_FRAME_BEGIN_INFO};
|
||||
const XrResult result = diagnostics::Measure(diagnostics::Stage::BeginCall, [&] {
|
||||
const auto queue_guard = LockGraphicsQueue();
|
||||
return xrBeginFrame(m_session, &begin_info);
|
||||
});
|
||||
if (XR_FAILED(result)) {
|
||||
@@ -816,7 +817,10 @@ bool OpenXRRuntime::EndFrame(
|
||||
end_info.layerCount = layer_count;
|
||||
end_info.layers = layers;
|
||||
const diagnostics::Stopwatch end_timer;
|
||||
const XrResult result = xrEndFrame(m_session, &end_info);
|
||||
const XrResult result = [&] {
|
||||
const auto queue_guard = LockGraphicsQueue();
|
||||
return xrEndFrame(m_session, &end_info);
|
||||
}();
|
||||
diagnostics::OnEndFrame(end_timer);
|
||||
m_frame_phase = FramePhase::Idle;
|
||||
m_active_frame_serial = 0;
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,6 +1,23 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Exercise the real backend against a deterministic compositor and Aurora sink.
|
||||
// No headset, graphics driver, OpenXR loader, or translated game is required.
|
||||
#if defined(TEST_WINDOWS_VULKAN)
|
||||
#define XR_USE_GRAPHICS_API_VULKAN
|
||||
#include <vulkan/vulkan.h>
|
||||
#include <openxr/openxr_platform.h>
|
||||
#include <aurora/vulkan_win32_interop.h>
|
||||
#include "vr/openxr_vulkan_win32.h"
|
||||
#define OpenXRD3D12Backend OpenXRWindowsVulkanBackend
|
||||
#define OpenXRD3D12BeginStatus OpenXRWindowsVulkanBeginStatus
|
||||
#define OpenXRD3D12SubmissionStatus OpenXRWindowsVulkanSubmissionStatus
|
||||
#define OpenXRD3D12Frame OpenXRWindowsVulkanFrame
|
||||
#define OpenXRD3D12Presentation OpenXRWindowsVulkanPresentation
|
||||
#define OpenXRD3D12FrameMode OpenXRWindowsVulkanFrameMode
|
||||
#define aurora_d3d12_enable_stereo_bridge aurora_vulkan_win32_enable
|
||||
#define aurora_d3d12_set_stereo_targets aurora_vulkan_win32_set_targets
|
||||
#define aurora_d3d12_cancel_stereo_targets aurora_vulkan_win32_cancel
|
||||
#define aurora_d3d12_disable_stereo_bridge aurora_vulkan_win32_disable
|
||||
#else
|
||||
#define CINTERFACE
|
||||
#define XR_USE_GRAPHICS_API_D3D12
|
||||
#ifndef NOMINMAX
|
||||
@@ -10,8 +27,10 @@
|
||||
#include <openxr/openxr_platform.h>
|
||||
#include <aurora/d3d12_interop.h>
|
||||
#include "vr/openxr_d3d12.h"
|
||||
#endif
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
@@ -65,6 +84,26 @@ void Complete(bool success = true) {
|
||||
encoded = false;
|
||||
}
|
||||
|
||||
#if defined(TEST_WINDOWS_VULKAN)
|
||||
AuroraDawnVulkanHooks hooks{};
|
||||
bool queue_locked = false;
|
||||
XrResult XRAPI_CALL Requirements(XrInstance, XrSystemId, XrGraphicsRequirementsVulkanKHR* out) {
|
||||
out->minApiVersionSupported = XR_MAKE_VERSION(1, 1, 0);
|
||||
out->maxApiVersionSupported = XR_MAKE_VERSION(1, 3, 0);
|
||||
return XR_SUCCESS;
|
||||
}
|
||||
XrResult XRAPI_CALL Physical(XrInstance, const XrVulkanGraphicsDeviceGetInfoKHR*, VkPhysicalDevice* out) {
|
||||
*out = reinterpret_cast<VkPhysicalDevice>(2); return XR_SUCCESS;
|
||||
}
|
||||
XrResult XRAPI_CALL CreateInstance(XrInstance, const XrVulkanInstanceCreateInfoKHR* info, VkInstance* out, VkResult* result) {
|
||||
Require(info->vulkanCreateInfo->pApplicationInfo->apiVersion >= VK_API_VERSION_1_1);
|
||||
*out = reinterpret_cast<VkInstance>(1); *result = VK_SUCCESS; return XR_SUCCESS;
|
||||
}
|
||||
XrResult XRAPI_CALL CreateDevice(XrInstance, const XrVulkanDeviceCreateInfoKHR* info, VkDevice* out, VkResult* result) {
|
||||
Require(info->vulkanPhysicalDevice == reinterpret_cast<VkPhysicalDevice>(2));
|
||||
*out = reinterpret_cast<VkDevice>(3); *result = VK_SUCCESS; return XR_SUCCESS;
|
||||
}
|
||||
#else
|
||||
HRESULT STDMETHODCALLTYPE FeatureSupport(ID3D12Device*, D3D12_FEATURE,
|
||||
void* data, UINT) {
|
||||
static_cast<D3D12_FEATURE_DATA_FEATURE_LEVELS*>(data)->MaxSupportedFeatureLevel =
|
||||
@@ -77,10 +116,28 @@ XrResult XRAPI_CALL Requirements(XrInstance, XrSystemId,
|
||||
requirements->minFeatureLevel = D3D_FEATURE_LEVEL_11_0;
|
||||
return XR_SUCCESS;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// A COM vtable in its C representation supplies the single device operation
|
||||
// used by BindAurora. The production backend is compiled normally as C++.
|
||||
#if defined(TEST_WINDOWS_VULKAN)
|
||||
bool aurora_vulkan_win32_configure(const AuroraDawnVulkanHooks* value) {
|
||||
hooks = value ? *value : AuroraDawnVulkanHooks{}; return true;
|
||||
}
|
||||
bool aurora_vulkan_win32_get_handles(AuroraDawnVulkanHandles* handles, int64_t* format) {
|
||||
VkApplicationInfo app{VK_STRUCTURE_TYPE_APPLICATION_INFO};
|
||||
app.apiVersion = VK_API_VERSION_1_0;
|
||||
VkInstanceCreateInfo instance{VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO}; instance.pApplicationInfo = &app;
|
||||
Require(hooks.createInstance(hooks.userdata, nullptr, &instance, nullptr, &handles->instance) == VK_SUCCESS);
|
||||
Require(hooks.getPhysicalDevice(hooks.userdata, handles->instance, &handles->physicalDevice) == VK_SUCCESS);
|
||||
VkDeviceCreateInfo device{VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO};
|
||||
Require(hooks.createDevice(hooks.userdata, nullptr, handles->physicalDevice, &device, nullptr, &handles->device) == VK_SUCCESS);
|
||||
*format = VK_FORMAT_R8G8B8A8_UNORM; return true;
|
||||
}
|
||||
void* aurora_vulkan_win32_lock_queue() { Require(!queue_locked); queue_locked = true; return &queue_locked; }
|
||||
void aurora_vulkan_win32_unlock_queue(void*) { Require(queue_locked); queue_locked = false; }
|
||||
#else
|
||||
bool aurora_d3d12_get_native_handles(AuroraD3D12NativeHandles* handles) {
|
||||
static ID3D12DeviceVtbl vtable{};
|
||||
vtable.CheckFeatureSupport = FeatureSupport;
|
||||
@@ -88,6 +145,7 @@ bool aurora_d3d12_get_native_handles(AuroraD3D12NativeHandles* handles) {
|
||||
*handles = {&device, &device, DXGI_FORMAT_R8G8B8A8_UNORM, 0, 0};
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
bool aurora_d3d12_enable_stereo_bridge(AuroraD3D12StereoSubmittedCallback cb, void* data) {
|
||||
callback = cb;
|
||||
callback_data = data;
|
||||
@@ -119,13 +177,21 @@ XrResult XRAPI_CALL xrCreateSwapchain(XrSession, const XrSwapchainCreateInfo*, X
|
||||
XrResult XRAPI_CALL xrEnumerateSwapchainImages(XrSwapchain handle, uint32_t capacity,
|
||||
uint32_t* count, XrSwapchainImageBaseHeader* images) {
|
||||
*count = 1; // Single-image swapchains also require a separate retained pair.
|
||||
#if defined(TEST_WINDOWS_VULKAN)
|
||||
if (capacity) reinterpret_cast<XrSwapchainImageVulkanKHR*>(images)->image =
|
||||
reinterpret_cast<VkImage>(&reinterpret_cast<Swapchain*>(handle)->image);
|
||||
#else
|
||||
if (capacity) reinterpret_cast<XrSwapchainImageD3D12KHR*>(images)->texture =
|
||||
reinterpret_cast<ID3D12Resource*>(&reinterpret_cast<Swapchain*>(handle)->image);
|
||||
#endif
|
||||
return XR_SUCCESS;
|
||||
}
|
||||
XrResult XRAPI_CALL xrAcquireSwapchainImage(XrSwapchain handle,
|
||||
const XrSwapchainImageAcquireInfo*, uint32_t* index) {
|
||||
auto& chain = *reinterpret_cast<Swapchain*>(handle);
|
||||
#if defined(TEST_WINDOWS_VULKAN)
|
||||
Require(queue_locked);
|
||||
#endif
|
||||
Require(!chain.acquired);
|
||||
chain.acquired = true;
|
||||
chain.waited = false;
|
||||
@@ -141,6 +207,9 @@ XrResult XRAPI_CALL xrWaitSwapchainImage(XrSwapchain handle, const XrSwapchainIm
|
||||
XrResult XRAPI_CALL xrReleaseSwapchainImage(XrSwapchain handle,
|
||||
const XrSwapchainImageReleaseInfo*) {
|
||||
auto& chain = *reinterpret_cast<Swapchain*>(handle);
|
||||
#if defined(TEST_WINDOWS_VULKAN)
|
||||
Require(queue_locked); // The runtime may touch Dawn's VkQueue here.
|
||||
#endif
|
||||
Require(chain.acquired && chain.waited);
|
||||
chain.acquired = false;
|
||||
chain.released = true;
|
||||
@@ -158,14 +227,24 @@ XrResult XRAPI_CALL xrDestroySwapchain(XrSwapchain handle) {
|
||||
namespace mkw::vr {
|
||||
OpenXRRuntime::OpenXRRuntime(OpenXRLogCallback) {
|
||||
m_instance = reinterpret_cast<XrInstance>(this);
|
||||
#if defined(TEST_WINDOWS_VULKAN)
|
||||
m_swapchain_formats = {VK_FORMAT_R8G8B8A8_SRGB};
|
||||
#else
|
||||
m_swapchain_formats = {DXGI_FORMAT_R8G8B8A8_UNORM_SRGB};
|
||||
#endif
|
||||
for (auto& view : m_view_configuration) {
|
||||
view.render_width = 100;
|
||||
view.render_height = 80;
|
||||
}
|
||||
}
|
||||
OpenXRRuntime::~OpenXRRuntime() = default;
|
||||
bool OpenXRRuntime::GetInstanceProcAddress(const char*, PFN_xrVoidFunction* out) {
|
||||
bool OpenXRRuntime::GetInstanceProcAddress(const char* name, PFN_xrVoidFunction* out) {
|
||||
#if defined(TEST_WINDOWS_VULKAN)
|
||||
if (std::strcmp(name, "xrCreateVulkanInstanceKHR") == 0) *out = reinterpret_cast<PFN_xrVoidFunction>(::CreateInstance);
|
||||
else if (std::strcmp(name, "xrCreateVulkanDeviceKHR") == 0) *out = reinterpret_cast<PFN_xrVoidFunction>(CreateDevice);
|
||||
else if (std::strcmp(name, "xrGetVulkanGraphicsDevice2KHR") == 0) *out = reinterpret_cast<PFN_xrVoidFunction>(Physical);
|
||||
else
|
||||
#endif
|
||||
*out = reinterpret_cast<PFN_xrVoidFunction>(Requirements);
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user