From e2e09d998c771ca83e752d1d1b71d32c645e73da Mon Sep 17 00:00:00 2001 From: Thomas Roos Date: Fri, 24 Jul 2026 13:32:08 +0200 Subject: [PATCH 1/2] Fix QueueWorkDone callback signature to include WGPUStringView message `GPUQueue.on_submitted_work_done_async` declared its ffi callback as `void(WGPUQueueWorkDoneStatus, void *, void *)`, but wgpu-native's `WGPUQueueWorkDoneCallback` typedef (webgpu.h) takes a `WGPUStringView message` argument between the status and the two userdata pointers: void (*WGPUQueueWorkDoneCallback)(WGPUQueueWorkDoneStatus status, WGPUStringView message, void *userdata1, void *userdata2); Because the callback stored in `WGPUQueueWorkDoneCallbackInfo.callback` must match that typedef exactly, constructing the struct raised: TypeError: initializer for ctype 'void(*)(WGPUQueueWorkDoneStatus, WGPUStringView, void *, void *)' must be a pointer to same type, not cdata 'void(*)(WGPUQueueWorkDoneStatus, void *, void *)' so any call to `on_submitted_work_done_async()` crashed. Every other status callback in this module (adapter/device request, device-lost, uncaptured-error) already includes the `WGPUStringView` message parameter; this one was simply missing it. Add the missing `message` parameter (ignored, like the userdata args) so the signature matches the header. Verified on an NVIDIA RTX A2000 (wgpu-native 29): the call previously raised the TypeError above and now completes cleanly. --- wgpu/backends/wgpu_native/_api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wgpu/backends/wgpu_native/_api.py b/wgpu/backends/wgpu_native/_api.py index c59d8dd0..f419f9d0 100644 --- a/wgpu/backends/wgpu_native/_api.py +++ b/wgpu/backends/wgpu_native/_api.py @@ -4080,8 +4080,8 @@ def read_texture( return data def on_submitted_work_done_async(self) -> GPUPromise[None]: - @ffi.callback("void(WGPUQueueWorkDoneStatus, void *, void *)") - def work_done_callback(status, _userdata1, _userdata2): + @ffi.callback("void(WGPUQueueWorkDoneStatus, WGPUStringView, void *, void *)") + def work_done_callback(status, _message, _userdata1, _userdata2): token.set_done() if status == lib.WGPUQueueWorkDoneStatus_Success: promise._wgpu_set_input(True) From 880d1fae6e96c1400df08b6df87c9d4abd443259 Mon Sep 17 00:00:00 2001 From: Thomas Roos Date: Fri, 24 Jul 2026 13:32:14 +0200 Subject: [PATCH 2/2] Restore test coverage for on_submitted_work_done and note fix in CHANGELOG The only test that exercises `on_submitted_work_done_async()` was defined as `make_pipeline_async`, so pytest never collected it (it doesn't match the `test_*` prefix) and the queue-work-done regression went completely uncovered in CI. Rename it to `test_pipeline_async` so it actually runs. Collecting it also surfaced a second latent error in the same test: the render pipeline requested `depth_stencil` format `rgba8unorm`, which is a color format, not a depth/stencil format, so wgpu-native rejects it. Use `depth32float` instead. The render pipeline is only created to exercise concurrent async pipeline creation and is never used in a pass. With these fixes the test passes on the patched build and, if the callback signature is reverted, fails at exactly the `on_submitted_work_done` call with the original TypeError -- i.e. it now guards the regression. Verified on both the asyncio and trio anyio backends against an NVIDIA RTX A2000. Also add a CHANGELOG "Fixed" entry. --- CHANGELOG.md | 7 +++++++ tests/test_async.py | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a458f3a2..4adb2e24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,13 @@ Possible sections in each release: Downstream code should capture that error and skip the frame, optionally invoking some sort of sleep to save energy. See https://github.com/pygfx/wgpu-py/pull/820 for context. +### Fixed: +* `GPUQueue.on_submitted_work_done_async()` no longer raises `TypeError` when + building its callback struct. The ffi callback was missing the + `WGPUStringView message` argument that the `WGPUQueueWorkDoneCallback` typedef + requires, so any call to it crashed. Also un-shadowed the test that covers + this path (it was named `make_pipeline_async` so pytest never collected it). + ## [v0.31.1] - 23-06-2026 diff --git a/tests/test_async.py b/tests/test_async.py index efc99307..fc18ed8c 100644 --- a/tests/test_async.py +++ b/tests/test_async.py @@ -769,7 +769,7 @@ async def test_buffer_map_async(): @mark.skipif(not can_use_wgpu_lib, reason="Needs wgpu lib") @mark.anyio -async def make_pipeline_async(): +async def test_pipeline_async(): device = wgpu.utils.get_default_device() shader_source = """ @@ -799,7 +799,7 @@ async def create_render_pipeline(): vertex={ "module": shader, }, - depth_stencil={"format": TextureFormat.rgba8unorm}, + depth_stencil={"format": TextureFormat.depth32float}, ) tg.start_soon(create_compute_pipeline)