From 9cfbd272b84e6d59fcd2020d501d4291761538fd Mon Sep 17 00:00:00 2001 From: Mark Harfouche Date: Thu, 20 Aug 2026 16:05:45 -0400 Subject: [PATCH] Implement view_formats in create_texture `create_texture()` accepted a `view_formats` argument and then raised NotImplementedError for any non-empty value, while the descriptor it fills already carried `viewFormatCount` / `viewFormats`, commented out as unused. This wires them up using the same conversion the surface configuration path in this file already performs. WebGPU allows a texture view to reinterpret the texture as any format listed in `viewFormats` at creation, and only those. The common use is the srgb/non-srgb pair: sampling an srgb texture applies the transfer function, so reading back the bytes that were actually written requires a view in the plain format. Without `view_formats` there is no way to ask for that view, and the only workaround is to copy the whole texture into a second one of the plain format -- a full-frame copy, every frame, plus the memory for the duplicate. Adds a test covering a declared view format, the texture's own format, an undeclared format still being rejected, and a texture that declares none keeping the previous behaviour. --- tests/test_wgpu_native_texture.py | 32 +++++++++++++++++++++++++++++++ wgpu/backends/wgpu_native/_api.py | 13 +++++++------ 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/tests/test_wgpu_native_texture.py b/tests/test_wgpu_native_texture.py index e36b0d85..5e4ff58a 100644 --- a/tests/test_wgpu_native_texture.py +++ b/tests/test_wgpu_native_texture.py @@ -324,5 +324,37 @@ def test_16bit_norm(): assert "r16unorm" in texture.format +@mark.skipif(not can_use_wgpu_lib, reason="Needs wgpu lib") +def test_view_formats(): + # A view may reinterpret a texture as any format declared in view_formats, + # and only those. The srgb/non-srgb pair is the useful case: it reads the + # bytes that were written, without the transfer function. + device = wgpu.utils.get_default_device() + + usage = wgpu.TextureUsage.RENDER_ATTACHMENT | wgpu.TextureUsage.TEXTURE_BINDING + tex = device.create_texture( + size=(64, 64, 1), + format=wgpu.TextureFormat.rgba8unorm_srgb, + usage=usage, + view_formats=[wgpu.TextureFormat.rgba8unorm], + ) + view = tex.create_view(format=wgpu.TextureFormat.rgba8unorm) + assert view is not None + + # The texture's own format is always viewable. + assert tex.create_view(format=wgpu.TextureFormat.rgba8unorm_srgb) is not None + + # A format that was not declared is still rejected. + with raises(wgpu.GPUValidationError): + tex.create_view(format=wgpu.TextureFormat.rgba8snorm) + + # And declaring none keeps the old behaviour. + plain = device.create_texture( + size=(64, 64, 1), format=wgpu.TextureFormat.rgba8unorm_srgb, usage=usage + ) + with raises(wgpu.GPUValidationError): + plain.create_view(format=wgpu.TextureFormat.rgba8unorm) + + if __name__ == "__main__": run_tests(globals()) diff --git a/wgpu/backends/wgpu_native/_api.py b/wgpu/backends/wgpu_native/_api.py index c59d8dd0..c29194c2 100644 --- a/wgpu/backends/wgpu_native/_api.py +++ b/wgpu/backends/wgpu_native/_api.py @@ -1498,10 +1498,11 @@ def create_texture( depthOrArrayLayers=size[2], ) - if view_formats: - raise NotImplementedError( - "create_texture(.. view_formats is not yet supported." - ) + # Formats that views of this texture may reinterpret it as. The main + # use is taking a non-srgb view of an srgb target (or the reverse) to + # read or write the raw bytes without the transfer function applied. + view_formats_list = [enummap["TextureFormat." + x] for x in view_formats] + c_view_formats = new_array("WGPUTextureFormat[]", view_formats_list) if not mip_level_count: mip_level_count = 1 # or lib.WGPU_MIP_LEVEL_COUNT_UNDEFINED ? @@ -1522,8 +1523,8 @@ def create_texture( dimension=dimension, format=format, usage=usage, - # not used: viewFormatCount - # not used: viewFormats + viewFormatCount=len(view_formats), + viewFormats=c_view_formats, ) # H: WGPUTexture f(WGPUDevice device, WGPUTextureDescriptor const * descriptor) id = libf.wgpuDeviceCreateTexture(self._internal, struct)