From b68ed10f4557d0455efba4ae189c835aea343b35 Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 16 Aug 2026 21:33:31 +0200 Subject: [PATCH 1/4] handle texture resource --- wgpu/_classes.py | 2 +- wgpu/backends/wgpu_native/_api.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/wgpu/_classes.py b/wgpu/_classes.py index adb19a5c..09ed8b12 100644 --- a/wgpu/_classes.py +++ b/wgpu/_classes.py @@ -965,7 +965,7 @@ def create_bind_group( layout (GPUBindGroupLayout): The layout (abstract representation) for this bind group. entries (list): A list of `structs.BindGroupEntry`s. The ``resource`` field - is either `GPUSampler`, `GPUTextureView` or `structs.BufferBinding`. + is either `GPUBuffer`, `GPUSampler`, `GPUTexture`, `GPUTextureView` or `structs.BufferBinding`. Example entry dicts: diff --git a/wgpu/backends/wgpu_native/_api.py b/wgpu/backends/wgpu_native/_api.py index c59d8dd0..71511985 100644 --- a/wgpu/backends/wgpu_native/_api.py +++ b/wgpu/backends/wgpu_native/_api.py @@ -1701,7 +1701,7 @@ def create_bind_group( c_entries_list = [] for entry in entries: check_struct("BindGroupEntry", entry) - # The resource can be a buffer, sampler, texture view, or buffer descriptor + # The resource can be a buffer, sampler, texture, texture view, or buffer descriptor resource = entry["resource"] if isinstance(resource, GPUBuffer): # H: nextInChain: WGPUChainedStruct *, binding: int, buffer: WGPUBuffer, offset: int, size: int, sampler: WGPUSampler, textureView: WGPUTextureView @@ -1727,7 +1727,9 @@ def create_bind_group( sampler=resource._internal, textureView=ffi.NULL, ) - elif isinstance(resource, GPUTextureView): + elif isinstance(resource, (GPUTextureView, GPUTexture)): + if isinstance(resource, GPUTexture): + resource = resource.create_view() # also see # H: nextInChain: WGPUChainedStruct *, binding: int, buffer: WGPUBuffer, offset: int, size: int, sampler: WGPUSampler, textureView: WGPUTextureView c_entry = new_struct( "WGPUBindGroupEntry", From e6cadf0f378b4f47c20d93b955afbdd84a63665d Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 16 Aug 2026 23:41:04 +0200 Subject: [PATCH 2/4] add test --- tests/test_wgpu_native_basics.py | 87 ++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/tests/test_wgpu_native_basics.py b/tests/test_wgpu_native_basics.py index fa662d31..25347b37 100644 --- a/tests/test_wgpu_native_basics.py +++ b/tests/test_wgpu_native_basics.py @@ -480,6 +480,93 @@ def test_limits_are_legal(): def test_limits_are_not_legal(): assert not are_limits_wgpu_legal({"max-bind-group": 8}) +def test_bind_group_resources(): + # make sure every resource type can be created in a bind group. + device = wgpu.utils.get_default_device() + + sampler = device.create_sampler() + texture1 = device.create_texture( + label="texture1", + size = (8, 8, 1), + format = wgpu.TextureFormat.rgba8unorm, + usage=wgpu.TextureUsage.TEXTURE_BINDING | wgpu.TextureUsage.COPY_DST, + ) + texture2 = device.create_texture( + label="texture2", + size = (8, 8, 1), + format = wgpu.TextureFormat.rgba8unorm, + usage=wgpu.TextureUsage.TEXTURE_BINDING | wgpu.TextureUsage.COPY_DST, + ) + texture_view = texture2.create_view() + + buffer = device.create_buffer( + size = 256, + usage = wgpu.BufferUsage.UNIFORM, + ) + + entires = [ + wgpu.structs.BindGroupEntry( + binding=0, + resource=sampler + ), + wgpu.structs.BindGroupEntry( + binding=1, + resource=texture1 + ), + wgpu.structs.BindGroupEntry( + binding=2, + resource=texture_view + ), + wgpu.structs.BindGroupEntry( + binding=3, + resource=buffer, + ), + wgpu.structs.BindGroupEntry( + binding=4, + resource=wgpu.structs.BufferBinding( + buffer=buffer, + ) + ), + # maybe external texture one day + ] + + # maybe we should write a bit of compute shader so we can use auto layout mode instead. + layout_entries = [ + wgpu.BindGroupLayoutEntry( + binding=0, + visibility=wgpu.ShaderStage.COMPUTE, + sampler=wgpu.structs.SamplerBindingLayout(), + ), + wgpu.BindGroupLayoutEntry( + binding=1, + visibility=wgpu.ShaderStage.COMPUTE, + texture=wgpu.structs.TextureBindingLayout(), + ), + wgpu.BindGroupLayoutEntry( + binding=2, + visibility=wgpu.ShaderStage.COMPUTE, + texture=wgpu.structs.TextureBindingLayout(), + ), + wgpu.BindGroupLayoutEntry( + binding=3, + visibility=wgpu.ShaderStage.COMPUTE, + buffer=wgpu.structs.BufferBindingLayout() + ), + wgpu.BindGroupLayoutEntry( + binding=4, + visibility=wgpu.ShaderStage.COMPUTE, + buffer=wgpu.structs.BufferBindingLayout() + ), + ] + + layout = device.create_bind_group_layout(entries=layout_entries) + + bind_group = device.create_bind_group( + layout=layout, + entries=entires + ) + + assert bind_group if __name__ == "__main__": run_tests(globals()) From 4513e7fdf730e59e7fad4034dad68996e75f7247 Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 16 Aug 2026 23:41:28 +0200 Subject: [PATCH 3/4] ensure lifetimes are kept --- wgpu/backends/wgpu_native/_api.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/wgpu/backends/wgpu_native/_api.py b/wgpu/backends/wgpu_native/_api.py index 71511985..f8af0a1a 100644 --- a/wgpu/backends/wgpu_native/_api.py +++ b/wgpu/backends/wgpu_native/_api.py @@ -1699,6 +1699,7 @@ def create_bind_group( entries: Sequence[structs.BindGroupEntryStruct], ) -> GPUBindGroup: c_entries_list = [] + _keep_alive = [] for entry in entries: check_struct("BindGroupEntry", entry) # The resource can be a buffer, sampler, texture, texture view, or buffer descriptor @@ -1728,8 +1729,9 @@ def create_bind_group( textureView=ffi.NULL, ) elif isinstance(resource, (GPUTextureView, GPUTexture)): - if isinstance(resource, GPUTexture): - resource = resource.create_view() # also see + if type(resource) is GPUTexture: + resource = resource.create_view() # also see https://github.com/pygfx/wgpu-py/issues/825 + _keep_alive.append(resource) # H: nextInChain: WGPUChainedStruct *, binding: int, buffer: WGPUBuffer, offset: int, size: int, sampler: WGPUSampler, textureView: WGPUTextureView c_entry = new_struct( "WGPUBindGroupEntry", @@ -1769,6 +1771,7 @@ def create_bind_group( # H: WGPUBindGroup f(WGPUDevice device, WGPUBindGroupDescriptor const * descriptor) id = libf.wgpuDeviceCreateBindGroup(self._internal, struct) + del _keep_alive return GPUBindGroup(label, id, self) def create_pipeline_layout( From f6b877b9094dc5886c794a2eaa5686fc0a02fb88 Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 16 Aug 2026 23:43:59 +0200 Subject: [PATCH 4/4] ruff format --- tests/test_wgpu_native_basics.py | 40 ++++++++++++------------------- wgpu/backends/wgpu_native/_api.py | 3 ++- 2 files changed, 17 insertions(+), 26 deletions(-) diff --git a/tests/test_wgpu_native_basics.py b/tests/test_wgpu_native_basics.py index 25347b37..61a65625 100644 --- a/tests/test_wgpu_native_basics.py +++ b/tests/test_wgpu_native_basics.py @@ -480,6 +480,7 @@ def test_limits_are_legal(): def test_limits_are_not_legal(): assert not are_limits_wgpu_legal({"max-bind-group": 8}) + def test_bind_group_resources(): # make sure every resource type can be created in a bind group. device = wgpu.utils.get_default_device() @@ -487,36 +488,27 @@ def test_bind_group_resources(): sampler = device.create_sampler() texture1 = device.create_texture( label="texture1", - size = (8, 8, 1), - format = wgpu.TextureFormat.rgba8unorm, + size=(8, 8, 1), + format=wgpu.TextureFormat.rgba8unorm, usage=wgpu.TextureUsage.TEXTURE_BINDING | wgpu.TextureUsage.COPY_DST, ) texture2 = device.create_texture( label="texture2", - size = (8, 8, 1), - format = wgpu.TextureFormat.rgba8unorm, + size=(8, 8, 1), + format=wgpu.TextureFormat.rgba8unorm, usage=wgpu.TextureUsage.TEXTURE_BINDING | wgpu.TextureUsage.COPY_DST, ) texture_view = texture2.create_view() buffer = device.create_buffer( - size = 256, - usage = wgpu.BufferUsage.UNIFORM, + size=256, + usage=wgpu.BufferUsage.UNIFORM, ) entires = [ - wgpu.structs.BindGroupEntry( - binding=0, - resource=sampler - ), - wgpu.structs.BindGroupEntry( - binding=1, - resource=texture1 - ), - wgpu.structs.BindGroupEntry( - binding=2, - resource=texture_view - ), + wgpu.structs.BindGroupEntry(binding=0, resource=sampler), + wgpu.structs.BindGroupEntry(binding=1, resource=texture1), + wgpu.structs.BindGroupEntry(binding=2, resource=texture_view), wgpu.structs.BindGroupEntry( binding=3, resource=buffer, @@ -525,7 +517,7 @@ def test_bind_group_resources(): binding=4, resource=wgpu.structs.BufferBinding( buffer=buffer, - ) + ), ), # maybe external texture one day ] @@ -550,23 +542,21 @@ def test_bind_group_resources(): wgpu.BindGroupLayoutEntry( binding=3, visibility=wgpu.ShaderStage.COMPUTE, - buffer=wgpu.structs.BufferBindingLayout() + buffer=wgpu.structs.BufferBindingLayout(), ), wgpu.BindGroupLayoutEntry( binding=4, visibility=wgpu.ShaderStage.COMPUTE, - buffer=wgpu.structs.BufferBindingLayout() + buffer=wgpu.structs.BufferBindingLayout(), ), ] layout = device.create_bind_group_layout(entries=layout_entries) - bind_group = device.create_bind_group( - layout=layout, - entries=entires - ) + bind_group = device.create_bind_group(layout=layout, entries=entires) assert bind_group + if __name__ == "__main__": run_tests(globals()) diff --git a/wgpu/backends/wgpu_native/_api.py b/wgpu/backends/wgpu_native/_api.py index f8af0a1a..a4398585 100644 --- a/wgpu/backends/wgpu_native/_api.py +++ b/wgpu/backends/wgpu_native/_api.py @@ -1730,7 +1730,8 @@ def create_bind_group( ) elif isinstance(resource, (GPUTextureView, GPUTexture)): if type(resource) is GPUTexture: - resource = resource.create_view() # also see https://github.com/pygfx/wgpu-py/issues/825 + # also see https://github.com/pygfx/wgpu-py/issues/825 + resource = resource.create_view() _keep_alive.append(resource) # H: nextInChain: WGPUChainedStruct *, binding: int, buffer: WGPUBuffer, offset: int, size: int, sampler: WGPUSampler, textureView: WGPUTextureView c_entry = new_struct(