From 43a3572f17eb7c7f84db043f9231e48f778bfcfb Mon Sep 17 00:00:00 2001 From: Jan Date: Fri, 7 Jun 2024 23:52:38 +0200 Subject: [PATCH] use rgba32float for buffers --- wgpu_shadertoy/passes.py | 33 ++++++++++++--------------------- wgpu_shadertoy/shadertoy.py | 27 +++++++++++++++++++++------ 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/wgpu_shadertoy/passes.py b/wgpu_shadertoy/passes.py index cbf7b5f..43a2567 100644 --- a/wgpu_shadertoy/passes.py +++ b/wgpu_shadertoy/passes.py @@ -477,19 +477,8 @@ def _finish_renderpass(self, device: wgpu.GPUDevice) -> None: "entry_point": "main", "targets": [ { - "format": wgpu.TextureFormat.bgra8unorm, - "blend": { - "color": ( - wgpu.BlendFactor.one, - wgpu.BlendFactor.zero, - wgpu.BlendOperation.add, - ), - "alpha": ( - wgpu.BlendFactor.one, - wgpu.BlendFactor.zero, - wgpu.BlendOperation.add, - ), - }, + "format": self._format, + "blend": None, # maybe fine? }, ], }, @@ -503,6 +492,7 @@ class ImageRenderPass(RenderPass): def __init__(self, **kwargs): super().__init__(**kwargs) + self._format = wgpu.TextureFormat.bgra8unorm # TODO figure out if there is anything specific. Maybe the canvas stuff? perhaps that should stay in the main class... def draw_image(self, device: wgpu.GPUDevice, present_context) -> None: @@ -545,6 +535,7 @@ class BufferRenderPass(RenderPass): def __init__(self, buffer_idx: str = "", **kwargs): super().__init__(**kwargs) self._buffer_idx = buffer_idx + self._format = wgpu.TextureFormat.rgba32float @property def buffer_idx(self) -> str: @@ -573,7 +564,7 @@ def texture_size(self) -> tuple: return self._texture_size - def _pad_columns(self, cols: int, alignment=64) -> int: + def _pad_columns(self, cols: int, alignment=16) -> int: if cols % alignment != 0: cols = (cols // alignment + 1) * alignment return cols @@ -608,7 +599,7 @@ def texture(self) -> wgpu.GPUTexture: # creates the initial texture self._texture = self.main._device.create_texture( size=self.texture_size, - format=wgpu.TextureFormat.bgra8unorm, + format=self._format, usage=wgpu.TextureUsage.COPY_SRC | wgpu.TextureUsage.COPY_DST | wgpu.TextureUsage.RENDER_ATTACHMENT @@ -647,7 +638,7 @@ def draw_buffer(self, device: wgpu.GPUDevice) -> None: # create a temporary texture as a render target target_texture = device.create_texture( size=self.texture_size, - format=wgpu.TextureFormat.bgra8unorm, + format=self._format, usage=wgpu.TextureUsage.COPY_SRC | wgpu.TextureUsage.RENDER_ATTACHMENT, ) @@ -702,7 +693,7 @@ def _download_texture( size = self.texture_size buffer = device.create_buffer( - size=(size[0] * size[1] * 4), + size=(size[0] * size[1] * 16), usage=wgpu.BufferUsage.COPY_SRC | wgpu.BufferUsage.COPY_DST, ) command_encoder.copy_texture_to_buffer( @@ -714,14 +705,14 @@ def _download_texture( { "buffer": buffer, "offset": 0, - "bytes_per_row": size[0] * 4, + "bytes_per_row": size[0] * 16, "rows_per_image": size[1], }, size, ) device.queue.submit([command_encoder.finish()]) frame = device.queue.read_buffer(buffer) - frame = np.frombuffer(frame, dtype=np.uint8).reshape(size[1], size[0], 4) + frame = np.frombuffer(frame, dtype=np.float32).reshape(size[1], size[0], 4) # redundant copy? # self._last_frame = frame return frame @@ -739,7 +730,7 @@ def _upload_texture(self, data, device=None, command_encoder=None): # create a new texture with the changed size? new_texture = device.create_texture( size=(data.shape[1], data.shape[0], 1), - format=wgpu.TextureFormat.bgra8unorm, + format=self._format, usage=wgpu.TextureUsage.COPY_SRC | wgpu.TextureUsage.RENDER_ATTACHMENT | wgpu.TextureUsage.COPY_DST @@ -755,7 +746,7 @@ def _upload_texture(self, data, device=None, command_encoder=None): data, { "offset": 0, - "bytes_per_row": data.shape[1] * 4, + "bytes_per_row": data.shape[1] * 16, "rows_per_image": data.shape[0], }, (data.shape[1], data.shape[0], 1), diff --git a/wgpu_shadertoy/shadertoy.py b/wgpu_shadertoy/shadertoy.py index 2e80972..b65f509 100644 --- a/wgpu_shadertoy/shadertoy.py +++ b/wgpu_shadertoy/shadertoy.py @@ -10,6 +10,9 @@ from .api import shader_args_from_json, shadertoy_from_id from .passes import BufferRenderPass, ImageRenderPass +# TODO: hacky solution, needs to be improved +_default_device = None + class UniformArray: """Convenience class to create a uniform array. @@ -196,6 +199,23 @@ def shader_type(self) -> str: """ return self.image.shader_type + @property + def _device(self): + """ + copy and paste from wgpu.utils.device.get_default_device but with feature enabled + we need to enable float32-filterable for buffer textures. + """ + global _default_device + + if _default_device is None: + import wgpu.backends.auto + + adapter = wgpu.gpu.request_adapter(power_preference="high-performance") + _default_device = adapter.request_device( + required_features=["float32-filterable"] + ) + return _default_device + @classmethod def from_json(cls, dict_or_path, **kwargs): """Builds a `Shadertoy` instance from a JSON-like dict of Shadertoy.com shader data.""" @@ -220,8 +240,6 @@ def _prepare_canvas(self): title=self.title, size=self.resolution, max_fps=60 ) - self._device = wgpu.utils.device.get_default_device() - self._present_context = self._canvas.get_context() # We use "bgra8unorm" not "bgra8unorm-srgb" here because we want to let the shader fully control the color-space. @@ -306,10 +324,7 @@ def _draw_frame(self): for buf in self.buffers.values(): if buf: # checks if not None? - buf.draw_buffer( - self._device - ) # does this need kind of the target to write too? - # TODO: most of the code below here is for the image renderpass... + buf.draw_buffer(self._device) self.image.draw_image(self._device, self._present_context) self._canvas.request_draw()