Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions docs/validation/windows-exact-dawn-device-812.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Windows exact Dawn D3D12 device bridge (#812)

## Contract

The sealed `win-x64` Dawn SDK exports `websceneDawnQueryD3D12DeviceV1` and
installs `webscene/dawn_native_device.h`. A successful query returns borrowed
identities for the exact live Dawn D3D12 device and its direct command queue,
plus the adapter LUID and the public adapter/device tokens which own them.

The caller keeps its `WGPUDevice` alive, does not release either COM object,
and orders its native submissions on the returned direct queue. Querying adds
no frame-loop work and creates no second D3D12 device or queue.

## Source and package gates

- Null and incompatible result structures fail before registry lookup.
- Foreign tokens fail before a Dawn private object is dereferenced.
- Lost, non-D3D12, missing-queue, missing-device, and zero-LUID cases fail and
leave the caller's result unchanged.
- The result advertises exact-device, direct-queue, and adapter-LUID
capabilities together; partial success is forbidden.
- The bridge is compiled inside the pinned Dawn monolith and only the
size/versioned C function is exported from `webgpu_dawn.dll`.
- The installed header, implementation, internal registry header, symbol
policy, and export inventory are checksummed by the SDK manifest.
- `verify-sdk.py` rejects a stale, missing, or locally modified bridge.

## Deferred Windows runner and VM acceptance

Execution is deferred by the current fast implementation direction. Before
qualification, the cumulative Windows stack must demonstrate:

1. Build and install the sealed `win-x64` Dawn SDK on `windows-2022` and pass
its export audit and SDK verifier.
2. Query a live D3D12 `WGPUDevice`; compare the returned device pointer, direct
queue pointer, adapter LUID, adapter token, and device token with the pinned
Dawn object's authoritative values.
3. Exercise every failure status, including a foreign non-null token, another
backend where available, loss/closing, invalid size/version, and null
arguments. Prove an initialized output sentinel is unchanged on failure.
4. Consume the tuple in AppScene's Graphite/DXGI host and show one shared
device/queue, GPU-only presentation, resize/recreation, device-loss cleanup,
and bounded resource counts.
5. Capture warm visible-idle CPU/GPU, frame latency, resize latency, allocation
plateau, and copied bytes. The acceptance threshold is zero CPU pixel
readback/upload and no per-frame device, queue, or context creation.

This evidence belongs in an immutable dated directory and must record the exact
WebScene, AppScene, Dawn, Windows SDK, driver, adapter, and VM/host revisions.
5 changes: 3 additions & 2 deletions eng/graphics/DawnSymbolBoundary.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ function(webscene_isolate_dawn)
set_property(TARGET webgpu_dawn_objects PROPERTY ${property} "${definitions}")
endif()
endforeach()
if(UNIX AND NOT APPLE)
if(WIN32 OR (UNIX AND NOT APPLE))
# This source is compiled inside Dawn so it can use the pinned private
# types. Consumers receive only its versioned C ABI header.
target_sources(dawn_native_objects PRIVATE
Expand All @@ -20,7 +20,8 @@ function(webscene_isolate_dawn)
"${CMAKE_CURRENT_FUNCTION_LIST_DIR}"
"${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../../experiments/WebScene.NativeEngine.Probe/native/graphics")
target_compile_definitions(dawn_native_objects PRIVATE
WEBSCENE_DAWN_NATIVE_DEVICE_BRIDGE=1)
WEBSCENE_DAWN_NATIVE_DEVICE_BRIDGE=1
WEBSCENE_DAWN_NATIVE_DEVICE_IMPLEMENTATION=1)
endif()
if(APPLE)
file(WRITE "${CMAKE_BINARY_DIR}/webscene-dawn.exports" "_wgpu*\n")
Expand Down
4 changes: 2 additions & 2 deletions eng/graphics/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ def dawn(args):
"-DCMAKE_SHARED_LINKER_FLAGS="] + [f"-D{k}={v}" for k, v in settings.items()])
repair_dawn_dependencies(source)
bridge = (patched_dawn_native_device_bridge(source)
if args.rid.startswith("linux-") else nullcontext())
if args.rid.startswith(("linux-", "win-")) else nullcontext())
with bridge:
run(["cmake", "--build", output, "--parallel", args.jobs])
# Old installed headers/libraries must not survive a dependency roll.
Expand All @@ -261,7 +261,7 @@ def dawn(args):
run(["cmake", "--install", output])
sdk.joinpath("build-info").mkdir()
shutil.copy2(symbol_policy, sdk / "build-info/DawnSymbolBoundary.cmake")
if args.rid.startswith("linux-"):
if args.rid.startswith(("linux-", "win-")):
sdk.joinpath("include/webscene").mkdir()
shutil.copy2(DAWN_NATIVE_DEVICE_HEADER, sdk / "include/webscene/dawn_native_device.h")
bridge_info = sdk / "build-info/webscene-dawn-native-device"
Expand Down
4 changes: 3 additions & 1 deletion eng/graphics/dawn_exports.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ def inspect_exports(library, rid):
"websceneDawnQueryVulkanDeviceV3",
"websceneDawnAcquireVulkanQueueV3",
"websceneDawnReleaseVulkanQueueV3"}
if rid.startswith("linux-") else set())
if rid.startswith("linux-") else
{"websceneDawnQueryD3D12DeviceV1"}
if rid.startswith("win-") else set())
unexpected = [name for name in symbols
if not re.fullmatch(r"wgpu[A-Z][A-Za-z0-9]*", name)
and name not in allowed_bridge]
Expand Down
52 changes: 52 additions & 0 deletions eng/graphics/dawn_native_device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,20 @@
#include "src/dawn/native/Adapter.h"
#include "src/dawn/native/Device.h"
#include "src/dawn/native/PhysicalDevice.h"
#if defined(_WIN32)
#include "src/dawn/native/d3d12/DeviceD3D12.h"
#include "src/dawn/native/d3d12/Forward.h"
#else
#include "src/dawn/native/vulkan/DeviceVk.h"
#include "src/dawn/native/vulkan/Forward.h"
#include "src/dawn/native/vulkan/PhysicalDeviceVk.h"
#include "src/dawn/native/vulkan/QueueVk.h"
#endif

namespace {
#if !defined(_WIN32)
struct queue_access_token;
#endif

struct live_devices {
std::mutex mutex;
Expand All @@ -27,9 +34,12 @@ struct live_devices {
entry* next;
};
entry* first{};
#if !defined(_WIN32)
queue_access_token* first_access{};
#endif
};

#if !defined(_WIN32)
class external_device_reference final {
public:
explicit external_device_reference(dawn::native::DeviceBase* device) noexcept
Expand Down Expand Up @@ -64,6 +74,7 @@ struct queue_access_token final {
};

thread_local queue_access_token* current_queue_access{};
#endif

live_devices& devices() {
// Construct in static storage without registering a destructor. Dawn
Expand All @@ -84,6 +95,7 @@ dawn::Ref<dawn::native::DeviceBase> find_device(WGPUDevice token) noexcept {
return {};
}

#if !defined(_WIN32)
bool nonzero_uuid(const uint8_t* value) noexcept {
for(size_t index=0;index<VK_UUID_SIZE;++index)if(value[index])return true;
return false;
Expand All @@ -107,6 +119,7 @@ uint32_t instance_capabilities(dawn::native::vulkan::Device* device) noexcept {
#endif
return capabilities;
}
#endif
}

namespace webscene::dawn_bridge {
Expand Down Expand Up @@ -147,6 +160,7 @@ void unregister_device(dawn::native::DeviceBase* device) noexcept {
}
}

#if !defined(_WIN32)
extern "C" webscene_dawn_native_device_status_v1 websceneDawnQueryVulkanDeviceV1(
WGPUDevice token,webscene_dawn_native_device_v1* result) {
if(!token||!result)return WEBSCENE_DAWN_NATIVE_DEVICE_INVALID_ARGUMENT_V1;
Expand Down Expand Up @@ -379,3 +393,41 @@ extern "C" webscene_dawn_native_device_status_v3 websceneDawnReleaseVulkanQueueV
return lost ? WEBSCENE_DAWN_NATIVE_DEVICE_LOST_OR_CLOSING_V3
: WEBSCENE_DAWN_NATIVE_DEVICE_SUCCESS_V3;
}
#else
extern "C" webscene_dawn_d3d12_device_status_v1
websceneDawnQueryD3D12DeviceV1(
WGPUDevice token,webscene_dawn_d3d12_device_v1* result) {
if(!token||!result)
return WEBSCENE_DAWN_D3D12_DEVICE_INVALID_ARGUMENT_V1;
if(result->struct_size<sizeof(webscene_dawn_d3d12_device_v1)||
result->version!=WEBSCENE_DAWN_D3D12_DEVICE_ABI_VERSION_V1)
return WEBSCENE_DAWN_D3D12_DEVICE_INCOMPATIBLE_ABI_V1;
auto base=find_device(token);
if(!base)return WEBSCENE_DAWN_D3D12_DEVICE_FOREIGN_DEVICE_V1;
auto guard=base->GetGuard();
if(base->GetState()!=dawn::native::DeviceBase::State::Alive||!base->GetQueue())
return WEBSCENE_DAWN_D3D12_DEVICE_LOST_V1;
if(base->GetPhysicalDevice()->GetBackendType()!=wgpu::BackendType::D3D12)
return WEBSCENE_DAWN_D3D12_DEVICE_NOT_D3D12_V1;

auto* device=dawn::native::d3d12::ToBackend(base.Get());
auto queue=device->GetD3D12CommandQueue();
auto* native_device=device->GetD3D12Device();
const auto luid=native_device?native_device->GetAdapterLuid():LUID{};
if(!native_device||!queue||(luid.LowPart==0U&&luid.HighPart==0))
return WEBSCENE_DAWN_D3D12_DEVICE_INVALID_IDENTITY_V1;

webscene_dawn_d3d12_device_v1 value{};
value.struct_size=sizeof(value);
value.version=WEBSCENE_DAWN_D3D12_DEVICE_ABI_VERSION_V1;
value.adapter=dawn::native::ToAPI(base->GetAdapter());
value.device=token;
value.d3d12_device=native_device;
value.d3d12_direct_queue=queue.Get();
value.adapter_luid_low=luid.LowPart;
value.adapter_luid_high=luid.HighPart;
value.capabilities=WEBSCENE_DAWN_D3D12_REQUIRED_V1;
*result=value;
return WEBSCENE_DAWN_D3D12_DEVICE_SUCCESS_V1;
}
#endif
26 changes: 26 additions & 0 deletions eng/graphics/tests/test_dawn_native_device_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,17 @@ def test_v3_holds_dawns_device_guard_and_balanced_device_references(self):
):
self.assertIn(token, source)

def test_windows_query_validates_before_private_dereference(self):
source = (ROOT / "eng/graphics/dawn_native_device.cpp").read_text()
query = source.split("websceneDawnQueryD3D12DeviceV1(", 1)[1]
self.assertLess(query.index("!token||!result"), query.index("find_device(token)"))
self.assertLess(query.index("result->struct_size"), query.index("find_device(token)"))
self.assertLess(query.index("result->version"), query.index("find_device(token)"))
self.assertLess(query.index("find_device(token)"), query.index("base->GetGuard()"))
for token in ("GetD3D12Device()", "GetD3D12CommandQueue()",
"GetAdapterLuid()", "WEBSCENE_DAWN_D3D12_REQUIRED_V1"):
self.assertIn(token, query)

def test_cpp_wrapper_is_lexical_and_sdk_targets_publish_v3(self):
wrapper = (ROOT / "experiments/WebScene.NativeEngine.Probe/native/graphics/"
"dawn_linux_external_provider.h").read_text()
Expand Down Expand Up @@ -88,6 +99,21 @@ def test_v1_v2_and_v3_are_staged_hashed_and_exported_together(self):
self.assertIn(name, verifier)
self.assertIn(name, cache)

def test_windows_bridge_is_built_staged_verified_and_exported(self):
header = (ROOT / "experiments/WebScene.NativeEngine.Probe/native/graphics/"
"webscene/dawn_native_device.h").read_text()
symbols = (ROOT / "eng/graphics/DawnSymbolBoundary.cmake").read_text()
exports = (ROOT / "eng/graphics/dawn_exports.py").read_text()
builder = (ROOT / "eng/graphics/build.py").read_text()
verifier = (ROOT / "eng/graphics/verify-sdk.py").read_text()
name = "websceneDawnQueryD3D12DeviceV1"
self.assertIn(name, header)
self.assertIn(name, exports)
self.assertIn("WIN32 OR (UNIX AND NOT APPLE)", symbols)
self.assertIn("WEBSCENE_DAWN_NATIVE_DEVICE_IMPLEMENTATION=1", symbols)
self.assertIn('(\"linux-\", \"win-\")', builder)
self.assertIn('(\"linux-\", \"win-\")', verifier)


if __name__ == "__main__":
unittest.main()
2 changes: 1 addition & 1 deletion eng/graphics/verify-sdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def verify(sdk, component, rid):
raise ValueError("dawn: pinned Windows shader compiler or license mismatch")
if sha(sdk / "build-info/DawnSymbolBoundary.cmake") != sha(LOCK_PATH.with_name("DawnSymbolBoundary.cmake")):
raise ValueError("dawn: symbol isolation policy mismatch; rebuild the SDK")
if rid.startswith("linux-"):
if rid.startswith(("linux-", "win-")):
bridge_files = {
"include/webscene/dawn_native_device.h":
ROOT / "experiments/WebScene.NativeEngine.Probe/native/graphics/"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,14 +160,56 @@ typedef struct webscene_dawn_vulkan_queue_access_v3 {
void* access_token;
} webscene_dawn_vulkan_queue_access_v3;

#if defined(_WIN32)
typedef uint32_t webscene_dawn_d3d12_device_status_v1;
#define WEBSCENE_DAWN_D3D12_DEVICE_ABI_VERSION_V1 1U
enum {
WEBSCENE_DAWN_D3D12_DEVICE_SUCCESS_V1 = 0,
WEBSCENE_DAWN_D3D12_DEVICE_INVALID_ARGUMENT_V1 = 1,
WEBSCENE_DAWN_D3D12_DEVICE_INCOMPATIBLE_ABI_V1 = 2,
WEBSCENE_DAWN_D3D12_DEVICE_FOREIGN_DEVICE_V1 = 3,
WEBSCENE_DAWN_D3D12_DEVICE_NOT_D3D12_V1 = 4,
WEBSCENE_DAWN_D3D12_DEVICE_LOST_V1 = 5,
WEBSCENE_DAWN_D3D12_DEVICE_INVALID_IDENTITY_V1 = 6
};

enum {
WEBSCENE_DAWN_D3D12_EXACT_DEVICE_V1 = 1U << 0,
WEBSCENE_DAWN_D3D12_DIRECT_QUEUE_V1 = 1U << 1,
WEBSCENE_DAWN_D3D12_ADAPTER_LUID_V1 = 1U << 2,
WEBSCENE_DAWN_D3D12_REQUIRED_V1 =
WEBSCENE_DAWN_D3D12_EXACT_DEVICE_V1 |
WEBSCENE_DAWN_D3D12_DIRECT_QUEUE_V1 |
WEBSCENE_DAWN_D3D12_ADAPTER_LUID_V1
};

/* Exact D3D12 identities borrowed from one live Dawn WGPUDevice. The caller
* retains that public device while using the returned device and direct queue,
* never releases the native COM objects, and serializes native queue work with
* its own Dawn submissions. Failure leaves the result unchanged. */
typedef struct webscene_dawn_d3d12_device_v1 {
uint32_t struct_size;
uint32_t version;
WGPUAdapter adapter;
WGPUDevice device;
void* d3d12_device;
void* d3d12_direct_queue;
uint32_t adapter_luid_low;
int32_t adapter_luid_high;
uint32_t capabilities;
uint32_t reserved;
} webscene_dawn_d3d12_device_v1;

#if defined(_WIN32) && defined(WEBSCENE_DAWN_NATIVE_DEVICE_IMPLEMENTATION)
#define WEBSCENE_DAWN_NATIVE_DEVICE_EXPORT __declspec(dllexport)
#elif defined(_WIN32)
#define WEBSCENE_DAWN_NATIVE_DEVICE_EXPORT __declspec(dllimport)
#elif defined(__GNUC__)
#define WEBSCENE_DAWN_NATIVE_DEVICE_EXPORT __attribute__((visibility("default")))
#else
#define WEBSCENE_DAWN_NATIVE_DEVICE_EXPORT
#endif

#if !defined(_WIN32)
/* result must carry sizeof(webscene_dawn_native_device_v1) and ABI version 1.
* Failure leaves the caller's result unchanged. Foreign tokens are rejected
* from a live-device registry before Dawn object memory is dereferenced. */
Expand Down Expand Up @@ -199,6 +241,16 @@ websceneDawnAcquireVulkanQueueV3(
WEBSCENE_DAWN_NATIVE_DEVICE_EXPORT webscene_dawn_native_device_status_v3
websceneDawnReleaseVulkanQueueV3(
webscene_dawn_vulkan_queue_access_v3* access);
#endif

#if defined(_WIN32)
/* result must carry sizeof(webscene_dawn_d3d12_device_v1) and version 1.
* Foreign tokens are rejected from the live registry before Dawn private
* object memory is dereferenced. */
WEBSCENE_DAWN_NATIVE_DEVICE_EXPORT webscene_dawn_d3d12_device_status_v1
websceneDawnQueryD3D12DeviceV1(
WGPUDevice device, webscene_dawn_d3d12_device_v1* result);
#endif

#undef WEBSCENE_DAWN_NATIVE_DEVICE_EXPORT

Expand Down
Loading