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
9 changes: 9 additions & 0 deletions docs/validation/linux-exact-native-device-hook-646.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ non-Vulkan or incomplete identities. SDK verification covers the installed
header, bridge sources, export allowlist and cache inputs. AppScene includes no
Dawn private header.

The first graphics SDK build exposed one deterministic compatibility defect:
Dawn compiles native objects with `-fno-exceptions`, while the initial registry
used an exception handler around `unordered_map` insertion. Issue #649 replaces
that insertion with a mutex-protected intrusive list whose nodes use
`new(std::nothrow)`. Allocation failure leaves the device unregistered, so the
query continues to reject it as foreign before dereference. The registry itself
uses destructor-free static storage to preserve the original teardown rule.
No exception syntax remains in the bridge.

`bind_dawn_linux_external_device` is the factory consumption point. It copies
the `wgpu::Device` into the lifetime anchor and requires the same shared atomic
loss flag used by the host's Dawn device-lost callback. Allocation, begin,
Expand Down
66 changes: 66 additions & 0 deletions docs/validation/linux-exact-vulkan-instance-query-649.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Exact Dawn Vulkan instance query

Issue: #649. Parent: WebScene #28. This is the WebScene prerequisite for
AppScene #184, #262 and #130.

The pinned Linux Dawn monolith now exports
`websceneDawnQueryVulkanDeviceV2` beside the unchanged v1 entry point. For a
live Vulkan `WGPUDevice`, v2 returns the exact borrowed `VkInstance`,
`VkPhysicalDevice`, `VkDevice`, `VkQueue`, graphics queue family and
device/driver UUIDs. It also returns the exact `vkGetInstanceProcAddr` function
which Dawn loaded for that instance. The public C header represents Vulkan
handles and procedures with opaque ABI-compatible values; all private Dawn and
Vulkan implementation headers remain inside the pinned bridge source.

## Validation and ownership

The exported function validates arguments and the v2 size/version before it
looks up the token. Lookup scans the authoritative live registry while holding
its mutex and acquires a Dawn reference before releasing the mutex. Only then
does the query access private device state. Foreign and stale tokens therefore
fail before query-side private dereference. The bridge also rejects lost and
non-Vulkan devices, null native identities, zero UUIDs and an absent resolver.
It fills a local result and copies it to the caller only on success, leaving the
caller's bytes unchanged on every failure.

V2 reports three required capabilities: enabled `VK_KHR_surface`, enabled
`VK_KHR_xlib_surface`, and the complete set of loaded generic surface and Xlib
creation/presentation-query procedures. A missing extension, build flag or
procedure returns `XLIB_PRESENTATION_UNAVAILABLE`; the provider publishes no
lifetime. The host supplies the X11 `Display` and visual ID and must call
`vkGetPhysicalDeviceXlibPresentationSupportKHR` through the returned resolver
before it creates an Xlib surface.

`bind_dawn_linux_external_device` consumes v2 and copies the public
`wgpu::Device` into the existing native owner. That reference retains the Dawn
backend and every borrowed Vulkan identity. Allocator identity checks now
include the instance, resolver and presentation capability mask. No Vulkan
object returned by this API may be destroyed by the consumer, and no second
instance/device or CPU transfer path is permitted.

## SDK and build contracts

The Linux version script and post-build symbol inspection require both v1 and
v2 while rejecting every other non-WebGPU export. The installed and provenance
copies of `webscene/dawn_native_device.h`, bridge source and internal hook remain
content-hashed by the graphics manifest and `verify-sdk.py`. The graphics SDK
cache identity already hashes those files plus the symbol policy and export
inspector, so this ABI change cannot reuse a v1-only binary. Linux SDK CMake
targets publish the legacy v1 macro and the explicit v2 macro.

The bridge registry was also made compatible with Dawn's `-fno-exceptions`
build. Registration uses a non-throwing intrusive-list allocation and fails
closed if allocation fails. Unregistration removes and frees the exact node;
lookup still retains the live Dawn device under the registry mutex.

Per the active fast policy, authored builds and tests were not run. Validation
for this checkpoint is limited to `git diff --check`.

## Remaining acceptance

AppScene can now create its Xlib `VkSurfaceKHR` and Graphite backend against the
same Dawn instance/device/queue. Physical Linux acceptance must still prove the
display/visual presentation query, surface creation and swapchain format/mode,
rendered pixels, resize and device-loss teardown, queue-completion retirement,
the 30-minute FD/residency plateau, and zero CPU readback/upload in ordinary
frames.
12 changes: 12 additions & 0 deletions docs/validation/linux-external-image-interop-28.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,18 @@ public adapter/device tokens, borrowed native tuple, queue family and Vulkan
device/driver UUIDs. The SDK ships only `webscene/dawn_native_device.h`; no Dawn
private header crosses into AppScene.

Issue #649 adds the v2 query without changing the v1 ABI. V2 also returns the
same Dawn device's borrowed `VkInstance` and the exact
`vkGetInstanceProcAddr` loaded by that Vulkan backend. It succeeds only when
`VK_KHR_surface` and `VK_KHR_xlib_surface` were enabled and Dawn loaded the
surface creation, destruction, capability and Xlib presentation-query
procedures. `bind_dawn_linux_external_device` now consumes v2 and retains its
`wgpu::Device`, keeping the complete borrowed instance/device/queue tuple alive.
The X11 host must still call
`vkGetPhysicalDeviceXlibPresentationSupportKHR` with its own display and visual;
unsupported displays fail before surface creation and cannot select a second
Vulkan instance or a CPU presentation route.

The SDK now exposes `dawn_linux_external_device_factory`. An AppScene host with
an exact-device Dawn integration creates the WebGPU instance/adapter/device and
native allocator atomically, and returns one
Expand Down
2 changes: 1 addition & 1 deletion eng/graphics/DawnSymbolBoundary.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ function(webscene_isolate_dawn)
target_link_options(webgpu_dawn PRIVATE "LINKER:-exported_symbols_list,${CMAKE_BINARY_DIR}/webscene-dawn.exports")
elseif(UNIX)
file(WRITE "${CMAKE_BINARY_DIR}/webscene-dawn.exports"
"{ global: wgpu*; websceneDawnQueryVulkanDeviceV1; local: *; };\n")
"{ global: wgpu*; websceneDawnQueryVulkanDeviceV1; websceneDawnQueryVulkanDeviceV2; local: *; };\n")
target_link_options(webgpu_dawn PRIVATE "LINKER:--version-script=${CMAKE_BINARY_DIR}/webscene-dawn.exports")
endif()
# Windows exports only functions decorated by WGPU_SHARED_LIBRARY;
Expand Down
19 changes: 18 additions & 1 deletion eng/graphics/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,24 @@ Implements part of [#23](https://github.com/wieslawsoltes/WebScene/issues/23), t

`dependencies.lock.json` pins Dawn (including Tint and generated WebGPU headers), ANGLE, depot_tools, WebGPU CTS, Khronos WebGL and WPT to exact commits. Dawn's own pinned `DEPS` supplies its transitive source revisions; ANGLE uses its pinned `DEPS` through gclient with depot_tools self-update disabled. The installed package records the transitive Git revision graph, exact CMake/GN settings, tool identities, source licenses/notices and hashes of every installed header/library. There is no ProGPU or renderer replacement dependency.

Dawn uses a shared `webgpu_dawn` monolith exporting WebGPU C entry points. On Linux it also exports the single versioned `websceneDawnQueryVulkanDeviceV1` C entry point, which returns borrowed native identities only for a live device created by this exact Dawn build. Static co-linkage with V8 exposed conflicting unversioned Abseil symbols and caused native runtime hangs; the shared boundary keeps those implementations private. `DawnSymbolBoundary.cmake` applies the export policy, and the builder checks actual binary exports before sealing the SDK. The Linux builder temporarily connects Dawn's authoritative device add/remove path to the query registry and restores the pinned source before provenance sealing. No private Dawn header enters the installed SDK. ANGLE uses shared EGL/GLESv2 with static internal dependencies and C ABI entry points. Windows release builds use static CRT for Dawn, matching the existing native engine/V8; ANGLE's non-component Chromium configuration controls its runtime. Windows build/link and runtime dependency validation (including D3D shader compiler packaging) remain hardware-runner gates. Never exchange CRT-owned allocations between these libraries.
Dawn uses a shared `webgpu_dawn` monolith exporting WebGPU C entry points. On
Linux it also exports the additive `websceneDawnQueryVulkanDeviceV1` and
`websceneDawnQueryVulkanDeviceV2` C entry points. V1 returns the borrowed native
device tuple; v2 also returns Dawn's exact borrowed `VkInstance`, its exact
`vkGetInstanceProcAddr` resolver, and required Xlib presentation state. Both
accept only a live device created by this exact Dawn build. Static co-linkage
with V8 exposed conflicting unversioned Abseil symbols and caused native
runtime hangs; the shared boundary keeps those implementations private.
`DawnSymbolBoundary.cmake` applies the export policy, and the builder checks
actual binary exports before sealing the SDK. The Linux builder temporarily
connects Dawn's authoritative device add/remove path to the query registry and
restores the pinned source before provenance sealing. No private Dawn header
enters the installed SDK. ANGLE uses shared EGL/GLESv2 with static internal
dependencies and C ABI entry points. Windows release builds use static CRT for
Dawn, matching the existing native engine/V8; ANGLE's non-component Chromium
configuration controls its runtime. Windows build/link and runtime dependency
validation (including D3D shader compiler packaging) remain hardware-runner
gates. Never exchange CRT-owned allocations between these libraries.

`GraphicsDependencies.cmake` verifies the SDK before importing it, including revision, RID, lock, settings, complete installed file inventory and content hashes. CMake cannot silently choose another `Dawn_DIR`. A dependency roll requires rebuilding headers and libraries together. Package manifests prove integrity and provenance, not API conformance or hardware execution.

Expand Down
3 changes: 2 additions & 1 deletion eng/graphics/dawn_exports.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ def inspect_exports(library, rid):
if match:
symbol = match.group(1)
symbols.append(symbol[1:] if rid.startswith("osx-") and symbol.startswith("_") else symbol)
allowed_bridge = ({"websceneDawnQueryVulkanDeviceV1"}
allowed_bridge = ({"websceneDawnQueryVulkanDeviceV1",
"websceneDawnQueryVulkanDeviceV2"}
if rid.startswith("linux-") else set())
unexpected = [name for name in symbols
if not re.fullmatch(r"wgpu[A-Z][A-Za-z0-9]*", name)
Expand Down
132 changes: 112 additions & 20 deletions eng/graphics/dawn_native_device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

#include <cstring>
#include <mutex>
#include <unordered_map>
#include <new>

#include "src/dawn/common/Ref.h"
#include "src/dawn/native/Adapter.h"
Expand All @@ -17,22 +17,31 @@
namespace {
struct live_devices {
std::mutex mutex;
std::unordered_map<WGPUDevice,dawn::native::DeviceBase*> values;
struct entry {
WGPUDevice token;
dawn::native::DeviceBase* device;
entry* next;
};
entry* first{};
};

live_devices& devices() {
// Dawn devices may outlive other process statics. Keep the registry alive
// until process teardown rather than racing its destructor at shutdown.
static auto* value=new live_devices;
// Construct in static storage without registering a destructor. Dawn
// devices may outlive ordinary process statics during loader teardown.
alignas(live_devices) static unsigned char storage[sizeof(live_devices)];
static auto* value=::new(static_cast<void*>(storage)) live_devices;
return *value;
}

dawn::Ref<dawn::native::DeviceBase> find_device(WGPUDevice token) noexcept {
auto& registry=devices();
std::lock_guard lock(registry.mutex);
const auto found=registry.values.find(token);
if(found==registry.values.end()||!found->second->TryAddRef())return {};
return dawn::AcquireRef(found->second);
for(auto* current=registry.first;current;current=current->next) {
if(current->token!=token)continue;
if(!current->device->TryAddRef())return {};
return dawn::AcquireRef(current->device);
}
return {};
}

bool nonzero_uuid(const uint8_t* value) noexcept {
Expand All @@ -44,32 +53,46 @@ bool nonzero_uuid(const uint8_t* value) noexcept {
namespace webscene::dawn_bridge {
void register_device(dawn::native::DeviceBase* device) noexcept {
if(!device)return;
try {
auto& registry=devices();
std::lock_guard lock(registry.mutex);
registry.values[dawn::native::ToAPI(device)]=device;
} catch(...) {
// Device creation remains usable, while the exact-device query fails
// closed because an unregistered token is indistinguishable from one
// created by another WebGPU implementation.
const auto token=dawn::native::ToAPI(device);
auto* value=new(std::nothrow) live_devices::entry{token,device,nullptr};
if(!value)return;
auto& registry=devices();
std::lock_guard lock(registry.mutex);
for(auto* current=registry.first;current;current=current->next) {
if(current->token!=token)continue;
// A duplicate authoritative registration is harmless. A token
// collision remains bound to the first live object and therefore
// makes a query for the second object fail closed.
delete value;
return;
}
value->next=registry.first;
registry.first=value;
}

void unregister_device(dawn::native::DeviceBase* device) noexcept {
if(!device)return;
auto& registry=devices();
std::lock_guard lock(registry.mutex);
const auto token=dawn::native::ToAPI(device);
const auto found=registry.values.find(token);
if(found!=registry.values.end()&&found->second==device)registry.values.erase(found);
auto** link=&registry.first;
while(*link) {
auto* current=*link;
if(current->token==token&&current->device==device) {
*link=current->next;
delete current;
return;
}
link=&current->next;
}
}
}

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;
if(result->struct_size<sizeof(webscene_dawn_native_device_v1)||
result->version!=WEBSCENE_DAWN_NATIVE_DEVICE_ABI_VERSION)
result->version!=WEBSCENE_DAWN_NATIVE_DEVICE_ABI_VERSION_V1)
return WEBSCENE_DAWN_NATIVE_DEVICE_INCOMPATIBLE_ABI_V1;
auto base=find_device(token);
if(!base)return WEBSCENE_DAWN_NATIVE_DEVICE_FOREIGN_DEVICE_V1;
Expand All @@ -93,7 +116,7 @@ extern "C" webscene_dawn_native_device_status_v1 websceneDawnQueryVulkanDeviceV1

webscene_dawn_native_device_v1 value{};
value.struct_size=sizeof(value);
value.version=WEBSCENE_DAWN_NATIVE_DEVICE_ABI_VERSION;
value.version=WEBSCENE_DAWN_NATIVE_DEVICE_ABI_VERSION_V1;
value.adapter=dawn::native::ToAPI(base->GetAdapter());
value.device=token;
value.vk_physical_device=reinterpret_cast<void*>(physical->GetVkPhysicalDevice());
Expand All @@ -105,3 +128,72 @@ extern "C" webscene_dawn_native_device_status_v1 websceneDawnQueryVulkanDeviceV1
*result=value;
return WEBSCENE_DAWN_NATIVE_DEVICE_SUCCESS_V1;
}

extern "C" webscene_dawn_native_device_status_v2 websceneDawnQueryVulkanDeviceV2(
WGPUDevice token,webscene_dawn_native_device_v2* result) {
if(!token||!result)return WEBSCENE_DAWN_NATIVE_DEVICE_INVALID_ARGUMENT_V2;
if(result->struct_size<sizeof(webscene_dawn_native_device_v2)||
result->version!=WEBSCENE_DAWN_NATIVE_DEVICE_ABI_VERSION_V2)
return WEBSCENE_DAWN_NATIVE_DEVICE_INCOMPATIBLE_ABI_V2;
auto base=find_device(token);
if(!base)return WEBSCENE_DAWN_NATIVE_DEVICE_FOREIGN_DEVICE_V2;
auto guard=base->GetGuard();
if(base->GetState()!=dawn::native::DeviceBase::State::Alive||!base->GetQueue())
return WEBSCENE_DAWN_NATIVE_DEVICE_LOST_V2;
if(base->GetPhysicalDevice()->GetBackendType()!=wgpu::BackendType::Vulkan)
return WEBSCENE_DAWN_NATIVE_DEVICE_NOT_VULKAN_V2;

auto* device=dawn::native::vulkan::ToBackend(base.Get());
auto* physical=dawn::native::vulkan::ToBackend(base->GetPhysicalDevice());
auto* queue=dawn::native::vulkan::ToBackend(base->GetQueue());
const auto instance=device->GetVkInstance();
const auto resolver=device->fn.GetInstanceProcAddr;
if(instance==VK_NULL_HANDLE||!resolver)
return WEBSCENE_DAWN_NATIVE_DEVICE_INVALID_IDENTITY_V2;

VkPhysicalDeviceIDProperties ids{};
ids.sType=VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES;
VkPhysicalDeviceProperties2 properties{};
properties.sType=VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
properties.pNext=&ids;
device->fn.GetPhysicalDeviceProperties2(physical->GetVkPhysicalDevice(),&properties);
if(!nonzero_uuid(ids.deviceUUID)||!nonzero_uuid(ids.driverUUID))
return WEBSCENE_DAWN_NATIVE_DEVICE_INVALID_IDENTITY_V2;

uint32_t capabilities=0;
const auto& global=device->GetGlobalInfo();
if(global.HasExt(dawn::native::vulkan::InstanceExt::Surface))
capabilities|=WEBSCENE_DAWN_VULKAN_SURFACE_EXTENSION_ENABLED_V2;
#if defined(DAWN_USE_X11)
if(global.HasExt(dawn::native::vulkan::InstanceExt::XlibSurface))
capabilities|=WEBSCENE_DAWN_VULKAN_XLIB_SURFACE_EXTENSION_ENABLED_V2;
if(device->fn.DestroySurfaceKHR&&device->fn.GetPhysicalDeviceSurfaceSupportKHR&&
device->fn.GetPhysicalDeviceSurfaceCapabilitiesKHR&&
device->fn.GetPhysicalDeviceSurfaceFormatsKHR&&
device->fn.GetPhysicalDeviceSurfacePresentModesKHR&&
device->fn.CreateXlibSurfaceKHR&&
device->fn.GetPhysicalDeviceXlibPresentationSupportKHR)
capabilities|=WEBSCENE_DAWN_VULKAN_XLIB_PRESENTATION_PROCS_AVAILABLE_V2;
#endif
if((capabilities&WEBSCENE_DAWN_VULKAN_XLIB_PRESENTATION_REQUIRED_V2)!=
WEBSCENE_DAWN_VULKAN_XLIB_PRESENTATION_REQUIRED_V2)
return WEBSCENE_DAWN_NATIVE_DEVICE_XLIB_PRESENTATION_UNAVAILABLE_V2;

webscene_dawn_native_device_v2 value{};
value.struct_size=sizeof(value);
value.version=WEBSCENE_DAWN_NATIVE_DEVICE_ABI_VERSION_V2;
value.adapter=dawn::native::ToAPI(base->GetAdapter());
value.device=token;
value.vk_instance=reinterpret_cast<void*>(instance);
value.vk_physical_device=reinterpret_cast<void*>(physical->GetVkPhysicalDevice());
value.vk_device=reinterpret_cast<void*>(device->GetVkDevice());
value.vk_queue=reinterpret_cast<void*>(queue->GetVkQueue());
value.vk_get_instance_proc_addr=
reinterpret_cast<webscene_dawn_vk_get_instance_proc_addr_v2>(resolver);
value.queue_family=device->GetGraphicsQueueFamily();
value.instance_capabilities=capabilities;
std::memcpy(value.device_uuid,ids.deviceUUID,VK_UUID_SIZE);
std::memcpy(value.driver_uuid,ids.driverUUID,VK_UUID_SIZE);
*result=value;
return WEBSCENE_DAWN_NATIVE_DEVICE_SUCCESS_V2;
}
Loading
Loading