From e40c7ebc73b502b826ad526a87a9f5659f503446 Mon Sep 17 00:00:00 2001 From: Almar Klein Date: Tue, 1 Oct 2024 22:38:46 +0200 Subject: [PATCH 1/3] Add proper async support for wgpu-native (to the extend that native supports it) --- wgpu/backends/wgpu_native/_api.py | 249 +++++++++++++++++--------- wgpu/backends/wgpu_native/_helpers.py | 53 ++++++ 2 files changed, 217 insertions(+), 85 deletions(-) diff --git a/wgpu/backends/wgpu_native/_api.py b/wgpu/backends/wgpu_native/_api.py index 463c33be..7ebf742c 100644 --- a/wgpu/backends/wgpu_native/_api.py +++ b/wgpu/backends/wgpu_native/_api.py @@ -36,6 +36,7 @@ get_memoryview_and_address, to_snake_case, ErrorHandler, + WgpuAwaitable, SafeLibCalls, ) @@ -322,12 +323,14 @@ def request_adapter_sync( This is the implementation based on wgpu-native. """ check_can_use_sync_variants() - return self._request_adapter( + awaitable = self._request_adapter( power_preference=power_preference, force_fallback_adapter=force_fallback_adapter, canvas=canvas, ) + return awaitable.sync_wait() + async def request_adapter_async( self, *, power_preference=None, force_fallback_adapter=False, canvas=None ): @@ -343,12 +346,14 @@ async def request_adapter_async( canvas (WgpuCanvasInterface): The canvas that the adapter should be able to render to. This can typically be left to None. """ - return self._request_adapter( + awaitable = self._request_adapter( power_preference=power_preference, force_fallback_adapter=force_fallback_adapter, canvas=canvas, ) # no-cover + return await awaitable + def _request_adapter( self, *, power_preference=None, force_fallback_adapter=False, canvas=None ): @@ -390,29 +395,28 @@ def _request_adapter( # not used: nextInChain ) - adapter_id = None - error_msg = None + awaitable_result = {} @ffi.callback("void(WGPURequestAdapterStatus, WGPUAdapter, char *, void *)") def callback(status, result, message, userdata): if status != 0: - nonlocal error_msg msg = "-" if message == ffi.NULL else ffi.string(message).decode() - error_msg = f"Request adapter failed ({status}): {msg}" + awaitable_result["error"] = f"Request adapter failed ({status}): {msg}" else: - nonlocal adapter_id - adapter_id = result + awaitable_result["result"] = result + + def poll_func(): + pass # actually does not need a poll func at the moment + + def finalizer(adapter_id): + return self._create_adapter(adapter_id) # H: void f(WGPUInstance instance, WGPURequestAdapterOptions const * options, WGPUInstanceRequestAdapterCallback callback, void * userdata) libf.wgpuInstanceRequestAdapter(get_wgpu_instance(), struct, callback, ffi.NULL) - # For now, Rust will call the callback immediately - # todo: when wgpu gets an event loop -> while run wgpu event loop or something - if adapter_id is None: # pragma: no cover - error_msg = error_msg or "Could not obtain new adapter id." - raise RuntimeError(error_msg) - - return self._create_adapter(adapter_id) + return WgpuAwaitable( + "request_adapter", awaitable_result, callback, poll_func, finalizer + ) def enumerate_adapters_sync(self): """Sync version of ``enumerate_adapters_async()``. @@ -845,9 +849,10 @@ def request_device_sync( check_can_use_sync_variants() if default_queue: check_struct("QueueDescriptor", default_queue) - return self._request_device( + awaitable = self._request_device( label, required_features, required_limits, default_queue, "" ) + return awaitable.sync_wait() async def request_device_async( self, @@ -859,7 +864,7 @@ async def request_device_async( ): if default_queue: check_struct("QueueDescriptor", default_queue) - return self._request_device( + awaitable = self._request_device( label, required_features=required_features, required_limits=required_limits, @@ -867,6 +872,8 @@ async def request_device_async( trace_path="", ) + return await awaitable + def _request_device( self, label, required_features, required_limits, default_queue, trace_path ): @@ -1013,45 +1020,40 @@ def uncaptured_error_callback(c_type, c_message, userdata): # not used: deviceLostUserdata ) - device_id = None - error_msg = None + awaitable_result = {} @ffi.callback("void(WGPURequestDeviceStatus, WGPUDevice, char *, void *)") def callback(status, result, message, userdata): if status != 0: - nonlocal error_msg msg = "-" if message == ffi.NULL else ffi.string(message).decode() - error_msg = f"Request device failed ({status}): {msg}" + awaitable_result["error"] = f"Request device failed ({status}): {msg}" else: - nonlocal device_id - device_id = result + awaitable_result["result"] = result - # H: void f(WGPUAdapter adapter, WGPUDeviceDescriptor const * descriptor, WGPUAdapterRequestDeviceCallback callback, void * userdata) - libf.wgpuAdapterRequestDevice(self._internal, struct, callback, ffi.NULL) + def poll_func(): + pass # not needed at the moment. Replace with future poll method later. - if device_id is None: # pragma: no cover - error_msg = error_msg or "Could not obtain new device id." - raise RuntimeError(error_msg) + def finalizer(device_id): + limits = _get_limits(device_id, device=True) + features = _get_features(device_id, device=True) + # H: WGPUQueue f(WGPUDevice device) + queue_id = libf.wgpuDeviceGetQueue(device_id) + queue = GPUQueue("", queue_id, None) - # ----- Get device limits and features - limits = _get_limits(device_id, device=True) - features = _get_features(device_id, device=True) + device = GPUDevice(label, device_id, self, features, limits, queue) - # ---- Get queue + # Bind some things to the lifetime of the device + device._uncaptured_error_callback = uncaptured_error_callback + device._device_lost_callback = device_lost_callback - # H: WGPUQueue f(WGPUDevice device) - queue_id = libf.wgpuDeviceGetQueue(device_id) - queue = GPUQueue("", queue_id, None) + return device - # ----- Done - - device = GPUDevice(label, device_id, self, features, limits, queue) - - # Bind some things to the lifetime of the device - device._uncaptured_error_callback = uncaptured_error_callback - device._device_lost_callback = device_lost_callback + # H: void f(WGPUAdapter adapter, WGPUDeviceDescriptor const * descriptor, WGPUAdapterRequestDeviceCallback callback, void * userdata) + libf.wgpuAdapterRequestDevice(self._internal, struct, callback, ffi.NULL) - return device + return WgpuAwaitable( + "request_device", awaitable_result, callback, poll_func, finalizer + ) def _release(self): if self._internal is not None and libf is not None: @@ -1064,9 +1066,6 @@ class GPUDevice(classes.GPUDevice, GPUObjectBase): # GPUObjectBaseMixin _release_function = libf.wgpuDeviceRelease - def __init__(self, label, internal, adapter, features, limits, queue): - super().__init__(label, internal, adapter, features, limits, queue) - def _poll(self): # Internal function if self._internal: @@ -1595,6 +1594,7 @@ async def create_compute_pipeline_async( ): return self.create_compute_pipeline(label=label, layout=layout, compute=compute) + # FIXME: missing check_struct in create_render_pipeline: ['DepthStencilState', 'FragmentState', 'MultisampleState', 'PrimitiveState', 'StencilFaceState', 'VertexState'] def create_render_pipeline( self, *, @@ -1605,6 +1605,64 @@ def create_render_pipeline( depth_stencil: structs.DepthStencilState = optional, multisample: structs.MultisampleState = {}, fragment: structs.FragmentState = optional, + ): + return self._create_render_pipeline( + label, + layout, + vertex, + primitive, + depth_stencil, + multisample, + fragment, + asynchronous=False, + ) + + # FIXME: missing check_struct in create_render_pipeline_async: ['DepthStencilState', 'FragmentState', 'MultisampleState', 'PrimitiveState', 'StencilFaceState', 'VertexState'] + async def create_render_pipeline_async( + self, + *, + label: str = "", + layout: Union[GPUPipelineLayout, enums.AutoLayoutMode], + vertex: structs.VertexState, + primitive: structs.PrimitiveState = {}, + depth_stencil: structs.DepthStencilState = optional, + multisample: structs.MultisampleState = {}, + fragment: structs.FragmentState = optional, + ): + # TODO: wgpuDeviceCreateRenderPipelineAsync is not yet implemented in wgpu-nat + # awaitable = self._create_render_pipeline( + # label, + # layout, + # vertex, + # primitive, + # depth_stencil, + # multisample, + # fragment, + # asynchronous=True, + # ) + # return await awaitable + return self._create_render_pipeline( + label, + layout, + vertex, + primitive, + depth_stencil, + multisample, + fragment, + asynchronous=False, + ) + + def _create_render_pipeline( + self, + label, + layout, + vertex, + primitive, + depth_stencil, + multisample, + fragment, + *, + asynchronous, ): depth_stencil = depth_stencil or {} multisample = multisample or {} @@ -1788,29 +1846,41 @@ def create_render_pipeline( # not used: nextInChain ) - # H: WGPURenderPipeline f(WGPUDevice device, WGPURenderPipelineDescriptor const * descriptor) - id = libf.wgpuDeviceCreateRenderPipeline(self._internal, struct) - return GPURenderPipeline(label, id, self) + if not asynchronous: + # H: WGPURenderPipeline f(WGPUDevice device, WGPURenderPipelineDescriptor const * descriptor) + id = libf.wgpuDeviceCreateRenderPipeline(self._internal, struct) + return GPURenderPipeline(label, id, self) - async def create_render_pipeline_async( - self, - *, - label: str = "", - layout: Union[GPUPipelineLayout, enums.AutoLayoutMode], - vertex: structs.VertexState, - primitive: structs.PrimitiveState = {}, - depth_stencil: structs.DepthStencilState = optional, - multisample: structs.MultisampleState = {}, - fragment: structs.FragmentState = optional, - ): - return self.create_render_pipeline( - label=label, - layout=layout, - vertex=vertex, - primitive=primitive, - depth_stencil=depth_stencil, - multisample=multisample, - fragment=fragment, + # Else, async approach + + awaitable_result = {} + + @ffi.callback( + "void(WGPUCreatePipelineAsyncStatus, WGPURenderPipeline, char *, void *)" + ) + def callback(status, result, message, userdata): + if status != 0: + msg = "-" if message == ffi.NULL else ffi.string(message).decode() + awaitable_result["error"] = ( + f"Create renderPipeline failed ({status}): {msg}" + ) + else: + awaitable_result["result"] = result + + def poll_func(): + # H: WGPUBool f(WGPUDevice device, WGPUBool wait, WGPUWrappedSubmissionIndex const * wrappedSubmissionIndex) + libf.wgpuDevicePoll(self._device._internal, False, ffi.NULL) + + def finalizer(id): + return GPURenderPipeline(label, id, self) + + # H: void f(WGPUDevice device, WGPURenderPipelineDescriptor const * descriptor, WGPUDeviceCreateRenderPipelineAsyncCallback callback, void * userdata) + libf.wgpuDeviceCreateRenderPipelineAsync( + self._internal, struct, callback, ffi.NULL + ) + + return WgpuAwaitable( + "create_render_pipeline", awaitable_result, callback, poll_func, finalizer ) def create_command_encoder(self, *, label: str = ""): @@ -1942,12 +2012,14 @@ def map_sync( self, mode: flags.MapMode, offset: int = 0, size: Optional[int] = None ): check_can_use_sync_variants() - return self._map(mode, offset, size) + awaitable = self._map(mode, offset, size) + return awaitable.sync_wait() async def map_async( self, mode: flags.MapMode, offset: int = 0, size: Optional[int] = None ): - return self._map(mode, offset, size) # for now + awaitable = self._map(mode, offset, size) # for now + return await awaitable def _map(self, mode, offset=0, size=None): sync_on_read = True @@ -1975,12 +2047,25 @@ def _map(self, mode, offset=0, size=None): command_buffer = encoder.finish() self._device.queue.submit([command_buffer]) - status = 999 + # Setup awaitable + + awaitable_result = {} @ffi.callback("void(WGPUBufferMapAsyncStatus, void*)") - def callback(status_, user_data_p): - nonlocal status - status = status_ + def callback(status, user_data): + awaitable_result["result"] = status + + def poll_func(): + # Doing nothing here will result in timeouts. I.e. unlike request_device, this method is truely async. + # H: WGPUBool f(WGPUDevice device, WGPUBool wait, WGPUWrappedSubmissionIndex const * wrappedSubmissionIndex) + libf.wgpuDevicePoll(self._device._internal, False, ffi.NULL) + + def finalizer(status): + if status != 0: # no-cover + raise RuntimeError(f"Could not map buffer ({status}).") + self._map_state = enums.BufferMapState.mapped + self._mapped_status = offset, offset + size, mode + self._mapped_memoryviews = [] # Map it self._map_state = enums.BufferMapState.pending @@ -1989,15 +2074,9 @@ def callback(status_, user_data_p): self._internal, map_mode, offset, size, callback, ffi.NULL ) - # Wait for the queue to process all tasks (including the mapping of the buffer). - # Also see WebGPU's onSubmittedWorkDone() and C's WGPUQueueWorkDoneCallback. - self._device._poll() - - if status != 0: # no-cover - raise RuntimeError(f"Could not map buffer ({status}).") - self._map_state = enums.BufferMapState.mapped - self._mapped_status = offset, offset + size, mode - self._mapped_memoryviews = [] + return WgpuAwaitable( + "buffer.map", awaitable_result, callback, poll_func, finalizer + ) def unmap(self): if self._map_state != enums.BufferMapState.mapped: @@ -3204,7 +3283,7 @@ def read_buffer(self, buffer, buffer_offset=0, size=None): self.submit([command_buffer]) # Download from mappable buffer - tmp_buffer._map("READ_NOSYNC") + tmp_buffer._map("READ_NOSYNC").sync_wait() data = tmp_buffer.read_mapped() # Explicit drop. @@ -3317,7 +3396,7 @@ def read_texture(self, source, data_layout, size): self.submit([command_buffer]) # Download from mappable buffer - tmp_buffer._map("READ_NOSYNC") + tmp_buffer._map("READ_NOSYNC").sync_wait() data = tmp_buffer.read_mapped() # Explicit drop. diff --git a/wgpu/backends/wgpu_native/_helpers.py b/wgpu/backends/wgpu_native/_helpers.py index 798d01a8..4979fd13 100644 --- a/wgpu/backends/wgpu_native/_helpers.py +++ b/wgpu/backends/wgpu_native/_helpers.py @@ -1,6 +1,7 @@ """Utilities used in the wgpu-native backend.""" import sys +import time import ctypes from queue import deque @@ -224,6 +225,58 @@ def to_camel_case(name): return name2 +class WgpuAwaitable: + """An object that can be waited for, either synchronously using sync_wait() or asynchronously using await. + + The purpose of this class is to implememt the asynchronous methods in a + truely async manner, as well as to support a synchronous version of them. + """ + + def __init__(self, title, result, callback, poll_function, finalizer, timeout=5.0): + self.title = title # for context in error messages + self.result = result # a dict that the callbacks writes to + self.callback = callback # only used to prevent it from being gc'd + self.poll_function = poll_function # call this to poll wgpu + self.finalizer = finalizer # function to finish the result + self.maxtime = time.perf_counter() + float(timeout) + + def is_done(self): + self.poll_function() + if self.result: + return True + if time.perf_counter() > self.maxtime: + self.result["timeout"] = True + return True + return False + + def finish(self): + if "result" in self.result: + return self.finalizer(self.result["result"]) + elif "error" in self.result: + raise RuntimeError(self.result["error"]) + elif "timeout" in self.result: + raise RuntimeError(f"Waiting for {self.title} timed out.") + else: + raise RuntimeError(f"Failed to obtain result for {self.title}.") + + def sync_wait(self): + # Basically a spin-lock, but we sleep(0) to give other threads more room to run. + while not self.is_done(): + time.sleep(0) # yield to the GIL + return self.finish() + + def __await__(self): + # With this approach the CPU is also kept busy, because the task will + # continuously be scheduled to do a next step, but at least other tasks + # will have a chance to run as well (e.g. GUI's running in the same loop + # stay active). In theory we could async-sleep here, but there are two + # problems: how long should we sleep, and using asyncio.sleep() would + # not work on e.g. Trio. + while not self.is_done(): + yield None # yield to the loop + return self.finish() + + class ErrorHandler: """Object that logs errors, with the option to collect incoming errors elsewhere. From f83ec3234c7ff318c3051a01a5dbaf1abe640777 Mon Sep 17 00:00:00 2001 From: Almar Klein Date: Wed, 2 Oct 2024 13:05:05 +0200 Subject: [PATCH 2/3] fix codegen --- codegen/apipatcher.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/codegen/apipatcher.py b/codegen/apipatcher.py index 53d0d869..fa1a2a42 100644 --- a/codegen/apipatcher.py +++ b/codegen/apipatcher.py @@ -648,12 +648,17 @@ def apply(self, code): # Test that a matching check is done unchecked = method_structs.difference(checked) unchecked = list(sorted(unchecked.difference(ignore_structs))) - if ( - methodname.endswith("_async") - and f"return self.{methodname[:-7]}" in code - ): - pass - elif unchecked: + # Must we check, or does this method defer to another + defer_func_name = "_" + methodname + defer_line_starts = ( + f"return self.{defer_func_name[:-7]}", + f"awaitable = self.{defer_func_name[:-7]}", + ) + this_method_defers = any( + line.strip().startswith(defer_line_starts) + for line in code.splitlines() + ) + if not this_method_defers and unchecked: msg = f"missing check_struct in {methodname}: {unchecked}" self.insert_line(j1, f"# FIXME: {msg}") print(f"ERROR: {msg}") From 1046c4145a0c591e3f86b4af6b5750e1001f9e5f Mon Sep 17 00:00:00 2001 From: Almar Klein Date: Wed, 2 Oct 2024 13:05:21 +0200 Subject: [PATCH 3/3] Apply for improved codegen --- wgpu/_classes.py | 16 ++++-- wgpu/backends/wgpu_native/_api.py | 94 +++++++++++++++++++------------ wgpu/resources/codegen_report.md | 6 +- 3 files changed, 73 insertions(+), 43 deletions(-) diff --git a/wgpu/_classes.py b/wgpu/_classes.py index ac80cef8..f665f69c 100644 --- a/wgpu/_classes.py +++ b/wgpu/_classes.py @@ -90,7 +90,11 @@ class GPU: # IDL: Promise requestAdapter(optional GPURequestAdapterOptions options = {}); -> GPUPowerPreference powerPreference, boolean forceFallbackAdapter = false @apidiff.change("arguments include canvas") def request_adapter_sync( - self, *, power_preference=None, force_fallback_adapter=False, canvas=None + self, + *, + power_preference: enums.GPUPowerPreference = None, + force_fallback_adapter: bool = False, + canvas=None, ): """Sync version of `request_adapter_async()`. @@ -108,7 +112,11 @@ def request_adapter_sync( # IDL: Promise requestAdapter(optional GPURequestAdapterOptions options = {}); -> GPUPowerPreference powerPreference, boolean forceFallbackAdapter = false @apidiff.change("arguments include canvas") async def request_adapter_async( - self, *, power_preference=None, force_fallback_adapter=False, canvas=None + self, + *, + power_preference: enums.GPUPowerPreference = None, + force_fallback_adapter: bool = False, + canvas=None, ): """Create a `GPUAdapter`, the object that represents an abstract wgpu implementation, from which one can request a `GPUDevice`. @@ -117,8 +125,8 @@ async def request_adapter_async( power_preference (PowerPreference): "high-performance" or "low-power". force_fallback_adapter (bool): whether to use a (probably CPU-based) fallback adapter. - canvas (WgpuCanvasInterface): The canvas that the adapter should - be able to render to. This can typically be left to None. + canvas : The canvas that the adapter should be able to render to. This can typically + be left to None. If given, the object must implement ``WgpuCanvasInterface``. """ # If this method gets called, no backend has been loaded yet, let's do that now! from .backends.auto import gpu diff --git a/wgpu/backends/wgpu_native/_api.py b/wgpu/backends/wgpu_native/_api.py index 7ebf742c..d996278e 100644 --- a/wgpu/backends/wgpu_native/_api.py +++ b/wgpu/backends/wgpu_native/_api.py @@ -317,7 +317,11 @@ def _get_features(id: int, device: bool = False, adapter: bool = False): class GPU(classes.GPU): def request_adapter_sync( - self, *, power_preference=None, force_fallback_adapter=False, canvas=None + self, + *, + power_preference: enums.GPUPowerPreference = None, + force_fallback_adapter: bool = False, + canvas=None, ): """Async version of ``request_adapter_async()``. This is the implementation based on wgpu-native. @@ -332,7 +336,11 @@ def request_adapter_sync( return awaitable.sync_wait() async def request_adapter_async( - self, *, power_preference=None, force_fallback_adapter=False, canvas=None + self, + *, + power_preference: enums.GPUPowerPreference = None, + force_fallback_adapter: bool = False, + canvas=None, ): """Create a `GPUAdapter`, the object that represents an abstract wgpu implementation, from which one can request a `GPUDevice`. @@ -343,8 +351,8 @@ async def request_adapter_async( power_preference (PowerPreference): "high-performance" or "low-power". force_fallback_adapter (bool): whether to use a (probably CPU-based) fallback adapter. - canvas (WgpuCanvasInterface): The canvas that the adapter should - be able to render to. This can typically be left to None. + canvas : The canvas that the adapter should be able to render to. This can typically + be left to None. If given, the object must implement ``WgpuCanvasInterface``. """ awaitable = self._request_adapter( power_preference=power_preference, @@ -875,7 +883,12 @@ async def request_device_async( return await awaitable def _request_device( - self, label, required_features, required_limits, default_queue, trace_path + self, + label: str, + required_features: List[enums.FeatureName], + required_limits: Dict[str, int], + default_queue: structs.QueueDescriptor, + trace_path: str, ): # ---- Handle features @@ -951,6 +964,8 @@ def canonicalize_limit_name(name): # ---- Set queue descriptor # Note that the default_queue arg is a descriptor (dict for QueueDescriptor), but is currently empty :) + check_struct("QueueDescriptor", {}) + # H: nextInChain: WGPUChainedStruct *, label: char * queue_struct = new_struct( "WGPUQueueDescriptor", @@ -1405,7 +1420,12 @@ def create_pipeline_layout( ): return self._create_pipeline_layout(label, bind_group_layouts, []) - def _create_pipeline_layout(self, label, bind_group_layouts, push_constant_layouts): + def _create_pipeline_layout( + self, + label: str, + bind_group_layouts: List[GPUBindGroupLayout], + push_constant_layouts, + ): bind_group_layouts_ids = [x._internal for x in bind_group_layouts] c_layout_array = ffi.new("WGPUBindGroupLayout []", bind_group_layouts_ids) @@ -1551,6 +1571,26 @@ def create_compute_pipeline( label: str = "", layout: Union[GPUPipelineLayout, enums.AutoLayoutMode], compute: structs.ProgrammableStage, + ): + return self._create_compute_pipeline(label, layout, compute, asynchronous=False) + + async def create_compute_pipeline_async( + self, + *, + label: str = "", + layout: Union[GPUPipelineLayout, enums.AutoLayoutMode], + compute: structs.ProgrammableStage, + ): + # TODO: wgpuDeviceCreateComputePipelineAsync is not yet implemented in wgpu-native + return self._create_compute_pipeline(label, layout, compute, asynchronous=False) + + def _create_compute_pipeline( + self, + label: str, + layout: Union[GPUPipelineLayout, enums.AutoLayoutMode], + compute: structs.ProgrammableStage, + *, + asynchronous, ): check_struct("ProgrammableStage", compute) c_constants, c_constant_entries = _get_override_constant_entries(compute) @@ -1581,20 +1621,14 @@ def create_compute_pipeline( compute=c_compute_stage, # not used: nextInChain ) + + if asynchronous: + raise NotImplementedError() + # H: WGPUComputePipeline f(WGPUDevice device, WGPUComputePipelineDescriptor const * descriptor) id = libf.wgpuDeviceCreateComputePipeline(self._internal, struct) return GPUComputePipeline(label, id, self) - async def create_compute_pipeline_async( - self, - *, - label: str = "", - layout: Union[GPUPipelineLayout, enums.AutoLayoutMode], - compute: structs.ProgrammableStage, - ): - return self.create_compute_pipeline(label=label, layout=layout, compute=compute) - - # FIXME: missing check_struct in create_render_pipeline: ['DepthStencilState', 'FragmentState', 'MultisampleState', 'PrimitiveState', 'StencilFaceState', 'VertexState'] def create_render_pipeline( self, *, @@ -1617,7 +1651,6 @@ def create_render_pipeline( asynchronous=False, ) - # FIXME: missing check_struct in create_render_pipeline_async: ['DepthStencilState', 'FragmentState', 'MultisampleState', 'PrimitiveState', 'StencilFaceState', 'VertexState'] async def create_render_pipeline_async( self, *, @@ -1629,18 +1662,7 @@ async def create_render_pipeline_async( multisample: structs.MultisampleState = {}, fragment: structs.FragmentState = optional, ): - # TODO: wgpuDeviceCreateRenderPipelineAsync is not yet implemented in wgpu-nat - # awaitable = self._create_render_pipeline( - # label, - # layout, - # vertex, - # primitive, - # depth_stencil, - # multisample, - # fragment, - # asynchronous=True, - # ) - # return await awaitable + # TODO: wgpuDeviceCreateRenderPipelineAsync is not yet implemented in wgpu-native return self._create_render_pipeline( label, layout, @@ -1654,13 +1676,13 @@ async def create_render_pipeline_async( def _create_render_pipeline( self, - label, - layout, - vertex, - primitive, - depth_stencil, - multisample, - fragment, + label: str, + layout: Union[GPUPipelineLayout, enums.AutoLayoutMode], + vertex: structs.VertexState, + primitive: structs.PrimitiveState, + depth_stencil: structs.DepthStencilState, + multisample: structs.MultisampleState, + fragment: structs.FragmentState, *, asynchronous, ): diff --git a/wgpu/resources/codegen_report.md b/wgpu/resources/codegen_report.md index 8c866e77..26022791 100644 --- a/wgpu/resources/codegen_report.md +++ b/wgpu/resources/codegen_report.md @@ -20,7 +20,7 @@ * Diffs for GPUQueue: add read_buffer, add read_texture, hide copy_external_image_to_texture * Validated 37 classes, 124 methods, 46 properties ### Patching API for backends/wgpu_native/_api.py -* Validated 37 classes, 105 methods, 0 properties +* Validated 37 classes, 106 methods, 0 properties ## Validating backends/wgpu_native/_api.py * Enum field FeatureName.texture-compression-bc-sliced-3d missing in wgpu.h * Enum field FeatureName.clip-distances missing in wgpu.h @@ -35,6 +35,6 @@ * Enum CanvasAlphaMode missing in wgpu.h * Enum CanvasToneMappingMode missing in wgpu.h * Wrote 236 enum mappings and 47 struct-field mappings to wgpu_native/_mappings.py -* Validated 133 C function calls -* Not using 70 C functions +* Validated 136 C function calls +* Not using 69 C functions * Validated 81 C structs