diff --git a/examples/cube.py b/examples/cube.py index 607a76b9..868e6528 100644 --- a/examples/cube.py +++ b/examples/cube.py @@ -24,7 +24,7 @@ def setup_drawing_sync( - context, power_preference="high-performance", limits=None + context, power_preference="high-performance", limits=None, format=None ) -> Callable: """Setup to draw a rotating cube on the given context. @@ -38,7 +38,9 @@ def setup_drawing_sync( ) pipeline_layout, uniform_buffer, bind_group = create_pipeline_layout(device) - pipeline_kwargs = get_render_pipeline_kwargs(context, device, pipeline_layout) + pipeline_kwargs = get_render_pipeline_kwargs( + context, device, pipeline_layout, render_texture_format=format + ) render_pipeline = device.create_render_pipeline(**pipeline_kwargs) @@ -84,9 +86,13 @@ def get_drawing_func(context, device): def get_render_pipeline_kwargs( - context, device: wgpu.GPUDevice, pipeline_layout: wgpu.GPUPipelineLayout + context, + device: wgpu.GPUDevice, + pipeline_layout: wgpu.GPUPipelineLayout, + render_texture_format: wgpu.TextureFormat | None = None, ) -> wgpu.RenderPipelineDescriptor: - render_texture_format = context.get_preferred_format(device.adapter) + if render_texture_format is None: + render_texture_format = context.get_preferred_format(device.adapter) context.configure(device=device, format=render_texture_format) shader = device.create_shader_module( diff --git a/examples/gui_auto.py b/examples/gui_auto.py index f1f728bf..6c8216d9 100644 --- a/examples/gui_auto.py +++ b/examples/gui_auto.py @@ -1,5 +1,5 @@ """ -Run the triangle example in an automatically selected GUI backend. +Run the cube example in an automatically selected GUI backend. The rendercanvas automatically selects one of its available GUI backends. E.g. running this in a notebook will use the @@ -12,14 +12,12 @@ """ -# test_example = true - from rendercanvas.auto import RenderCanvas, loop try: - from .triangle import setup_drawing_sync + from .cube import setup_drawing_sync except ImportError: - from triangle import setup_drawing_sync + from cube import setup_drawing_sync canvas = RenderCanvas(title="Cube example on $backend") draw_frame = setup_drawing_sync(canvas.get_wgpu_context()) diff --git a/examples/offscreen_hdr.py b/examples/offscreen_hdr.py index 54d87bc5..9128469d 100644 --- a/examples/offscreen_hdr.py +++ b/examples/offscreen_hdr.py @@ -16,9 +16,9 @@ from rendercanvas.offscreen import RenderCanvas try: - from .triangle import setup_drawing_sync + from .cube import setup_drawing_sync except ImportError: - from triangle import setup_drawing_sync + from cube import setup_drawing_sync canvas = RenderCanvas(size=(640, 480), pixel_ratio=2) diff --git a/examples/screenshots/gui_auto.png b/examples/screenshots/gui_auto.png deleted file mode 100644 index 643c3977..00000000 Binary files a/examples/screenshots/gui_auto.png and /dev/null differ diff --git a/examples/triangle.py b/examples/triangle.py index 8bb05d97..248761a1 100644 --- a/examples/triangle.py +++ b/examples/triangle.py @@ -5,176 +5,108 @@ visualisation because it does not need buffers or textures. The same example in other languages / API's: +* JavaScript WebGPU: + https://webgpu.github.io/webgpu-samples/?sample=helloTriangle * Rust wgpu: - https://github.com/gfx-rs/wgpu-rs/blob/master/examples/hello-triangle/main.rs + https://github.com/gfx-rs/wgpu/blob/trunk/examples/features/src/hello_triangle/mod.rs * C wgpu: - https://github.com/gfx-rs/wgpu/blob/master/examples/triangle/main.c + https://github.com/gfx-rs/wgpu-native/blob/trunk/examples/triangle/main.c * Python Vulkan: https://github.com/realitix/vulkan/blob/master/example/contribs/example_glfw.py -This example is set up so it can be run with any canvas. Running this file -as a script will use rendercanvas with the auto-backend. - +This example is meant as a standalone starting point. And is therefore as minimal as possible. """ -from typing import Callable - import wgpu -# %% Entrypoints (sync and async) - - -def setup_drawing_sync( - context, power_preference="high-performance", limits=None, format=None -) -> Callable: - """Setup to draw a triangle on the given context. - - Returns the draw function. - """ - - adapter = wgpu.gpu.request_adapter_sync(power_preference=power_preference) - device = adapter.request_device_sync(required_limits=limits) - - pipeline_kwargs = get_render_pipeline_kwargs(context, device, format) - - render_pipeline = device.create_render_pipeline(**pipeline_kwargs) - - return get_draw_function(context, device, render_pipeline, asynchronous=False) - - -async def setup_drawing_async(context, limits=None, format=None) -> Callable: - """Setup to async-draw a triangle on the given context. +from rendercanvas.auto import RenderCanvas, loop - Returns the draw function. - """ - adapter = await wgpu.gpu.request_adapter_async(power_preference="high-performance") - device = await adapter.request_device_async(required_limits=limits) - - pipeline_kwargs = get_render_pipeline_kwargs(context, device, format) - - render_pipeline = await device.create_render_pipeline_async(**pipeline_kwargs) - - return get_draw_function(context, device, render_pipeline, asynchronous=True) - - -# %% Functions to create wgpu objects - - -def get_render_pipeline_kwargs( - context, device, render_texture_format -) -> wgpu.RenderPipelineDescriptor: - if render_texture_format is None: - render_texture_format = context.get_preferred_format(device.adapter) - context.configure(device=device, format=render_texture_format) - - shader = device.create_shader_module(code=shader_source) - pipeline_layout = device.create_pipeline_layout(bind_group_layouts=[]) - - return wgpu.RenderPipelineDescriptor( - layout=pipeline_layout, - vertex=wgpu.VertexState( - module=shader, - entry_point="vs_main", - ), - depth_stencil=None, - multisample=None, - fragment=wgpu.FragmentState( - module=shader, - entry_point="fs_main", - targets=[ - wgpu.ColorTargetState( - format=render_texture_format, - blend={"color": {}, "alpha": {}}, - ) - ], - ), - ) - - -def get_draw_function( - context, - device: wgpu.GPUDevice, - render_pipeline: wgpu.GPURenderPipeline, - *, - asynchronous: bool, -) -> Callable: - def draw_frame_sync(): - current_texture = context.get_current_texture() - command_encoder = device.create_command_encoder() - - render_pass = command_encoder.begin_render_pass( - color_attachments=[ - wgpu.RenderPassColorAttachment( - view=current_texture.create_view(), - resolve_target=None, - clear_value=(0, 0, 0, 1), - load_op="clear", - store_op="store", - ) - ], - ) - - render_pass.set_pipeline(render_pipeline) - # render_pass.set_bind_group(0, no_bind_group) - render_pass.draw(3, 1, 0, 0) - render_pass.end() - device.queue.submit([command_encoder.finish()]) - - async def draw_frame_async(): - draw_frame_sync() # nothing async here - - if asynchronous: - return draw_frame_async - else: - return draw_frame_sync - - -# %% WGSL - - -shader_source = """ -struct VertexInput { - @builtin(vertex_index) vertex_index : u32, -}; +# the shader code is provided as a string literal for protability +wgsl_shader_source = """ struct VertexOutput { - @location(0) color : vec4, - @builtin(position) pos: vec4, + @location(0) color : vec4f, + @builtin(position) pos: vec4f, }; @vertex -fn vs_main(in: VertexInput) -> VertexOutput { - var positions = array, 3>( - vec2(0.0, -0.5), - vec2(0.5, 0.5), - vec2(-0.5, 0.75), +fn vs_main(@builtin(vertex_index) index: u32) -> VertexOutput { + var positions = array( + vec2(0.0, -0.5), + vec2(0.5, 0.5), + vec2(-0.5, 0.75), ); - var colors = array, 3>( // srgb colors - vec3(1.0, 1.0, 0.0), - vec3(1.0, 0.0, 1.0), - vec3(0.0, 1.0, 1.0), + // vertex attributes are interpolated in the fragment shader. + var colors = array( // srgb colors + vec3(1.0, 1.0, 0.0), + vec3(1.0, 0.0, 1.0), + vec3(0.0, 1.0, 1.0), ); - let index = i32(in.vertex_index); var out: VertexOutput; - out.pos = vec4(positions[index], 0.0, 1.0); - out.color = vec4(colors[index], 1.0); + out.pos = vec4(positions[index], 0.0, 1.0); + out.color = vec4(colors[index], 1.0); return out; } @fragment -fn fs_main(in: VertexOutput) -> @location(0) vec4 { - let physical_color = pow(in.color.rgb, vec3(2.2)); // gamma correct - return vec4(physical_color, in.color.a); +fn fs_main(in: VertexOutput) -> @location(0) vec4f { + let physical_color = pow(in.color.rgb, vec3(2.2)); // gamma correct + return vec4(physical_color, in.color.a); } """ -if __name__ == "__main__": - from rendercanvas.auto import RenderCanvas, loop +# adapter provides allows us to create a single device, which is the general entrypoint to create most wgpu objects. +# for convenience and interoperability `wgpu.utils.get_default_device()` and associated configuration are provided. +adapter = wgpu.gpu.request_adapter_sync() +device = adapter.request_device_sync() + +# setting up a canvas, so we can see what we draw +canvas = RenderCanvas(size=(640, 480), title="wgpu triangle example") +context = canvas.get_wgpu_context() +render_texture_format = context.get_preferred_format(device.adapter) +context.configure(device=device, format=render_texture_format) - canvas = RenderCanvas(size=(640, 480), title="wgpu triangle example") - context = canvas.get_wgpu_context() +# creating the shader module compiles the shader code for your GPU. +shader = device.create_shader_module(code=wgsl_shader_source) - draw_frame = setup_drawing_sync(context) - canvas.request_draw(draw_frame) +# in wgpu-py, methods that take descriptors will take the keyword arguments instead. +# descriptors and other structs can still be accessed via wgpu.structs or top level wgpu. +render_pipeline = device.create_render_pipeline( + **wgpu.RenderPipelineDescriptor( + layout=wgpu.AutoLayoutMode.auto, + vertex=wgpu.VertexState(module=shader), + depth_stencil=None, + multisample=None, + fragment=wgpu.FragmentState( + module=shader, + targets=[wgpu.ColorTargetState(format=render_texture_format)], + ), + ) +) + + +# this function gets called for every frame. It ends with submitting a buffer of work onto the GPU queue. +def drawing_function(): + command_encoder = device.create_command_encoder() + current_texture_view = context.get_current_texture().create_view() + + render_pass = command_encoder.begin_render_pass( + color_attachments=[ + wgpu.RenderPassColorAttachment( + view=current_texture_view, + clear_value=(0, 1, 0, 1), # a green background + load_op=wgpu.LoadOp.clear, + store_op=wgpu.StoreOp.store, + ) + ], + ) + render_pass.set_pipeline(render_pipeline) + render_pass.draw(3) + render_pass.end() + device.queue.submit([command_encoder.finish()]) + + +if __name__ == "__main__": + canvas.request_draw(drawing_function) loop.run()