-
-
Notifications
You must be signed in to change notification settings - Fork 54
Make the triangle example simpler #810
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
a8efa5a
dbdd243
3d65f3c
3230cbe
42f52c6
d11ba23
4bedb4e
31510ad
58d2593
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,9 +16,9 @@ | |
| from rendercanvas.offscreen import RenderCanvas | ||
|
|
||
| try: | ||
| from .triangle import setup_drawing_sync | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ... and problems we missed. I ran this locally and got a messed up image. As I was on main for rendercanvas I had to go back to v2.5.0 to get it to work again. Didn't bisect it fully but between v2.5.0 and v2.6.0 is what I could find on the quick. I suspect something about the bitorder might be wrong:
I will check the rendercanvas code when I find time. Maybe a numpy kwargs is all we need
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe width/height reversed?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. wasn't it. I tried around with different shapes and all. But the individual pixel values don't make sense. I think it's somewhere in the new bitmapcontext in rendercanvas. I had a quick glance and it looked like it always writes bytes as uint8... so it could also be a platform specific problem with order across bytes. I will try some variations and file this for rendercanvas hopefully in a few days. |
||
| 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) | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -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<f32>, | ||||||
| @builtin(position) pos: vec4<f32>, | ||||||
| @location(0) color : vec4f, | ||||||
| @builtin(position) pos: vec4f, | ||||||
| }; | ||||||
|
|
||||||
| @vertex | ||||||
| fn vs_main(in: VertexInput) -> VertexOutput { | ||||||
| var positions = array<vec2<f32>, 3>( | ||||||
| vec2<f32>(0.0, -0.5), | ||||||
| vec2<f32>(0.5, 0.5), | ||||||
| vec2<f32>(-0.5, 0.75), | ||||||
| fn vs_main(@builtin(vertex_index) index: u32) -> VertexOutput { | ||||||
| var positions = array<vec2f, 3>( | ||||||
| vec2(0.0, -0.5), | ||||||
| vec2(0.5, 0.5), | ||||||
| vec2(-0.5, 0.75), | ||||||
| ); | ||||||
| var colors = array<vec3<f32>, 3>( // srgb colors | ||||||
| vec3<f32>(1.0, 1.0, 0.0), | ||||||
| vec3<f32>(1.0, 0.0, 1.0), | ||||||
| vec3<f32>(0.0, 1.0, 1.0), | ||||||
| // vertex attributes are interpolated in the fragment shader. | ||||||
| var colors = array<vec3f, 3>( // 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<f32>(positions[index], 0.0, 1.0); | ||||||
| out.color = vec4<f32>(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<f32> { | ||||||
| let physical_color = pow(in.color.rgb, vec3<f32>(2.2)); // gamma correct | ||||||
| return vec4<f32>(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. | ||||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| # 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() | ||||||

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I disabled this and removed the screenshot. Since we get one for cube and one for triangle anyway. Perhaps not the best idea as we could miss problems