From a8efa5a70fa725198e91fd2605805e5e3615db65 Mon Sep 17 00:00:00 2001 From: Jan Date: Thu, 28 May 2026 22:33:14 +0200 Subject: [PATCH 01/15] serialize the existing triangle, plus cleanup default args --- examples/triangle.py | 178 +++++++++++++------------------------------ 1 file changed, 54 insertions(+), 124 deletions(-) diff --git a/examples/triangle.py b/examples/triangle.py index 8bb05d97..d5bc42ce 100644 --- a/examples/triangle.py +++ b/examples/triangle.py @@ -5,134 +5,25 @@ 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. - - 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 +from rendercanvas.auto import RenderCanvas, loop +# the shader code is provided as a string literal for protability +# TODO: maybe simplify in several ways, or make it the red triangle. shader_source = """ struct VertexInput { @builtin(vertex_index) vertex_index : u32, @@ -169,12 +60,51 @@ async def draw_frame_async(): """ -if __name__ == "__main__": - from rendercanvas.auto import RenderCanvas, loop - - canvas = RenderCanvas(size=(640, 480), title="wgpu triangle example") - context = canvas.get_wgpu_context() +# adapter provides allows us to create a single device, which is the general entrypoint to create most wgpu objects. +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) + +# creating the shader module compiles the shader code for your GPU. +shader = device.create_shader_module(code=shader_source) + +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) + ], + ), +)) + +def drawing_function(): + command_encoder = device.create_command_encoder() + current_texture_view: wgpu.GPUTextureView = context.get_current_texture().create_view() #TODO: there is no type hint for GPUTexture from the rendercanvas side. + + 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()]) - draw_frame = setup_drawing_sync(context) - canvas.request_draw(draw_frame) +if __name__ == "__main__": + canvas.request_draw(drawing_function) loop.run() From dbdd243966919d93fbf4e6e5c8042171e2a32ac3 Mon Sep 17 00:00:00 2001 From: Jan Date: Thu, 28 May 2026 22:45:07 +0200 Subject: [PATCH 02/15] simplify the shadercode a little bit --- examples/triangle.py | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/examples/triangle.py b/examples/triangle.py index d5bc42ce..14a276c9 100644 --- a/examples/triangle.py +++ b/examples/triangle.py @@ -25,37 +25,34 @@ # the shader code is provided as a string literal for protability # TODO: maybe simplify in several ways, or make it the red triangle. shader_source = """ -struct VertexInput { - @builtin(vertex_index) vertex_index : u32, -}; 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); } """ From 3d65f3c65684735a0e2b4be59c8e47c6e64cc896 Mon Sep 17 00:00:00 2001 From: Jan Date: Thu, 28 May 2026 22:54:37 +0200 Subject: [PATCH 03/15] ruff format --- examples/triangle.py | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/examples/triangle.py b/examples/triangle.py index 14a276c9..6afd3860 100644 --- a/examples/triangle.py +++ b/examples/triangle.py @@ -24,7 +24,7 @@ # the shader code is provided as a string literal for protability # TODO: maybe simplify in several ways, or make it the red triangle. -shader_source = """ +wgsl_shader_source = """ struct VertexOutput { @location(0) color : vec4f, @builtin(position) pos: vec4f, @@ -68,30 +68,31 @@ context.configure(device=device, format=render_texture_format) # creating the shader module compiles the shader code for your GPU. -shader = device.create_shader_module(code=shader_source) - -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) - ], - ), -)) +shader = device.create_shader_module(code=wgsl_shader_source) + +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)], + ), + ) +) + def drawing_function(): command_encoder = device.create_command_encoder() - current_texture_view: wgpu.GPUTextureView = context.get_current_texture().create_view() #TODO: there is no type hint for GPUTexture from the rendercanvas side. + 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 + clear_value=(0, 1, 0, 1), # a green background load_op=wgpu.LoadOp.clear, store_op=wgpu.StoreOp.store, ) @@ -102,6 +103,7 @@ def drawing_function(): render_pass.end() device.queue.submit([command_encoder.finish()]) + if __name__ == "__main__": canvas.request_draw(drawing_function) loop.run() From 3230cbe02ffd1a2aedd3793b13fdcc2faab8d5ef Mon Sep 17 00:00:00 2001 From: Jan Date: Thu, 28 May 2026 23:05:31 +0200 Subject: [PATCH 04/15] switch auto gui example to cube --- examples/gui_auto.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/gui_auto.py b/examples/gui_auto.py index f1f728bf..91f046de 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 @@ -17,9 +17,9 @@ 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()) From 42f52c60f7d2d87f52a3aaf73e3ad690bdfd2e30 Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 31 May 2026 02:18:45 +0200 Subject: [PATCH 05/15] hdr example to use cube --- examples/cube.py | 9 +++++---- examples/offscreen_hdr.py | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/examples/cube.py b/examples/cube.py index 607a76b9..34c68f2b 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,7 @@ 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 +84,10 @@ 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/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) From d11ba235c248e926fe41481be2d416fec489ebf7 Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 31 May 2026 02:49:58 +0200 Subject: [PATCH 06/15] adjust comments --- examples/triangle.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/triangle.py b/examples/triangle.py index 6afd3860..ecdbcd55 100644 --- a/examples/triangle.py +++ b/examples/triangle.py @@ -23,7 +23,6 @@ # the shader code is provided as a string literal for protability -# TODO: maybe simplify in several ways, or make it the red triangle. wgsl_shader_source = """ struct VertexOutput { @location(0) color : vec4f, @@ -58,6 +57,7 @@ # 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() @@ -83,7 +83,7 @@ ) ) - +# 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() From 4bedb4ed67e43c6c4abe72766298581782b4d88d Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 31 May 2026 02:58:43 +0200 Subject: [PATCH 07/15] explain kwarg api diff --- examples/triangle.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/triangle.py b/examples/triangle.py index ecdbcd55..2ec4418e 100644 --- a/examples/triangle.py +++ b/examples/triangle.py @@ -70,6 +70,8 @@ # creating the shader module compiles the shader code for your GPU. shader = device.create_shader_module(code=wgsl_shader_source) +# 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, From 31510adc089efeca16b27077da5027a07efb48ca Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 31 May 2026 03:02:36 +0200 Subject: [PATCH 08/15] ruff format --- examples/cube.py | 9 +++++++-- examples/triangle.py | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/examples/cube.py b/examples/cube.py index 34c68f2b..868e6528 100644 --- a/examples/cube.py +++ b/examples/cube.py @@ -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, render_texture_format=format) + pipeline_kwargs = get_render_pipeline_kwargs( + context, device, pipeline_layout, render_texture_format=format + ) render_pipeline = device.create_render_pipeline(**pipeline_kwargs) @@ -84,7 +86,10 @@ def get_drawing_func(context, device): def get_render_pipeline_kwargs( - context, device: wgpu.GPUDevice, pipeline_layout: wgpu.GPUPipelineLayout, render_texture_format: wgpu.TextureFormat|None = None + context, + device: wgpu.GPUDevice, + pipeline_layout: wgpu.GPUPipelineLayout, + render_texture_format: wgpu.TextureFormat | None = None, ) -> wgpu.RenderPipelineDescriptor: if render_texture_format is None: render_texture_format = context.get_preferred_format(device.adapter) diff --git a/examples/triangle.py b/examples/triangle.py index 2ec4418e..248761a1 100644 --- a/examples/triangle.py +++ b/examples/triangle.py @@ -85,6 +85,7 @@ ) ) + # 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() From 58d2593601fe58add8f0652cfb36fb2cf7c96bbf Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 31 May 2026 03:09:29 +0200 Subject: [PATCH 09/15] skip screenshot for gui_auto --- examples/gui_auto.py | 2 -- examples/screenshots/gui_auto.png | Bin 25721 -> 0 bytes 2 files changed, 2 deletions(-) delete mode 100644 examples/screenshots/gui_auto.png diff --git a/examples/gui_auto.py b/examples/gui_auto.py index 91f046de..6c8216d9 100644 --- a/examples/gui_auto.py +++ b/examples/gui_auto.py @@ -12,8 +12,6 @@ """ -# test_example = true - from rendercanvas.auto import RenderCanvas, loop try: diff --git a/examples/screenshots/gui_auto.png b/examples/screenshots/gui_auto.png deleted file mode 100644 index 643c39773d5338f94022a699e3f9f802d5b48031..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25721 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 MY;m*n54WfP13#pH)Bpeg From 236082862bec6ab5c17f6515855a73a96a2540d3 Mon Sep 17 00:00:00 2001 From: Almar Klein Date: Mon, 15 Jun 2026 16:13:06 +0200 Subject: [PATCH 10/15] update triangle_glsl.py too --- examples/triangle.py | 2 +- examples/triangle_glsl.py | 136 ++++++++++++++------------------------ 2 files changed, 50 insertions(+), 88 deletions(-) diff --git a/examples/triangle.py b/examples/triangle.py index 248761a1..52ea32d1 100644 --- a/examples/triangle.py +++ b/examples/triangle.py @@ -22,7 +22,7 @@ from rendercanvas.auto import RenderCanvas, loop -# the shader code is provided as a string literal for protability +# the shader code is provided as a string literal for portability wgsl_shader_source = """ struct VertexOutput { @location(0) color : vec4f, diff --git a/examples/triangle_glsl.py b/examples/triangle_glsl.py index 386273cb..ddf86cbb 100644 --- a/examples/triangle_glsl.py +++ b/examples/triangle_glsl.py @@ -1,12 +1,9 @@ """ The triangle example, using GLSL shaders. - """ import wgpu - - -# %% Shaders +from rendercanvas.auto import RenderCanvas, loop vertex_shader = """ @@ -41,93 +38,58 @@ } """ - -# %% The wgpu calls - - -def setup_drawing_sync(context, power_preference="high-performance", limits=None): - """Regular function to set up a viz on the given context.""" - - adapter = wgpu.gpu.request_adapter_sync(power_preference=power_preference) - device = adapter.request_device_sync(required_limits=limits) - - render_pipeline = get_render_pipeline(context, device) - return get_draw_function(context, device, render_pipeline) - - -def get_render_pipeline(context, device): - vert_shader = device.create_shader_module(label="triangle_vert", code=vertex_shader) - frag_shader = device.create_shader_module( - label="triangle_frag", code=fragment_shader - ) - - # No bind group and layout, we should not create empty ones. - pipeline_layout = device.create_pipeline_layout(bind_group_layouts=[]) - - render_texture_format = context.get_preferred_format(device.adapter) - context.configure(device=device, format=render_texture_format) - - return device.create_render_pipeline( - layout=pipeline_layout, - vertex={ - "module": vert_shader, - "entry_point": "main", - }, - primitive={ - "topology": wgpu.PrimitiveTopology.triangle_list, - "front_face": wgpu.FrontFace.ccw, - "cull_mode": wgpu.CullMode.none, - }, +# 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) + +# creating the shader module compiles the shader code for your GPU. +vert_shader = device.create_shader_module(label="triangle_vert", code=vertex_shader) +frag_shader = device.create_shader_module(label="triangle_frag", code=fragment_shader) + +# 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=vert_shader), depth_stencil=None, multisample=None, - fragment={ - "module": frag_shader, - "entry_point": "main", - "targets": [ - { - "format": render_texture_format, - "blend": { - "color": {}, - "alpha": {}, - }, - }, - ], - }, + fragment=wgpu.FragmentState( + module=frag_shader, + targets=[wgpu.ColorTargetState(format=render_texture_format)], + ), ) - - -def get_draw_function(context, device, render_pipeline): - def draw_frame(): - current_texture = context.get_current_texture() - command_encoder = device.create_command_encoder() - - render_pass = command_encoder.begin_render_pass( - color_attachments=[ - { - "view": current_texture.create_view(), - "resolve_target": None, - "clear_value": (0, 0, 0, 1), - "load_op": wgpu.LoadOp.clear, - "store_op": wgpu.StoreOp.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()]) - - return draw_frame +) + + +# 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__": - from rendercanvas.auto import RenderCanvas, loop - - canvas = RenderCanvas(size=(640, 480), title="wgpu triangle glsl example") - context = canvas.get_wgpu_context() - - draw_frame = setup_drawing_sync(context) - canvas.request_draw(draw_frame) + canvas.request_draw(drawing_function) loop.run() From 3da4e04de89fb9756d4d1943b98e725d35dfaab5 Mon Sep 17 00:00:00 2001 From: Almar Klein Date: Mon, 15 Jun 2026 16:13:19 +0200 Subject: [PATCH 11/15] Apply suggestion from @Vipitis Co-authored-by: Jan --- examples/triangle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/triangle.py b/examples/triangle.py index 248761a1..f2857d04 100644 --- a/examples/triangle.py +++ b/examples/triangle.py @@ -56,7 +56,7 @@ """ -# adapter provides allows us to create a single device, which is the general entrypoint to create most wgpu objects. +# adapter is required to create the device object, which is the general entry point 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() From 989fcea328822598e42578f1885c84d69fd442ed Mon Sep 17 00:00:00 2001 From: Almar Klein Date: Mon, 15 Jun 2026 16:35:44 +0200 Subject: [PATCH 12/15] tiny tweak --- examples/offscreen_hdr.py | 2 +- examples/triangle.py | 2 +- examples/triangle_glsl.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/offscreen_hdr.py b/examples/offscreen_hdr.py index 9128469d..d5d43a70 100644 --- a/examples/offscreen_hdr.py +++ b/examples/offscreen_hdr.py @@ -25,8 +25,8 @@ draw_frame = setup_drawing_sync(canvas.get_wgpu_context(), format="rgba16float") canvas.request_draw(draw_frame) +# Draw, and download image image = canvas.draw() -image = np.asarray(image) # Convert to RGB image = image[:, :, :3] diff --git a/examples/triangle.py b/examples/triangle.py index 3b378831..8f6d83ec 100644 --- a/examples/triangle.py +++ b/examples/triangle.py @@ -95,7 +95,7 @@ def drawing_function(): color_attachments=[ wgpu.RenderPassColorAttachment( view=current_texture_view, - clear_value=(0, 1, 0, 1), # a green background + clear_value=(0, 0.2, 0, 1), # a green background load_op=wgpu.LoadOp.clear, store_op=wgpu.StoreOp.store, ) diff --git a/examples/triangle_glsl.py b/examples/triangle_glsl.py index ddf86cbb..a8d3ec34 100644 --- a/examples/triangle_glsl.py +++ b/examples/triangle_glsl.py @@ -38,7 +38,7 @@ } """ -# adapter provides allows us to create a single device, which is the general entrypoint to create most wgpu objects. +# adapter is required to create the device object, which is the general entry point 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() @@ -78,7 +78,7 @@ def drawing_function(): color_attachments=[ wgpu.RenderPassColorAttachment( view=current_texture_view, - clear_value=(0, 1, 0, 1), # a green background + clear_value=(0, 0.2, 0, 1), # a green background load_op=wgpu.LoadOp.clear, store_op=wgpu.StoreOp.store, ) From 828d6a0665504ddf65dfb0277dbb4e5adc915cc0 Mon Sep 17 00:00:00 2001 From: Jan Date: Mon, 15 Jun 2026 18:19:42 +0200 Subject: [PATCH 13/15] enable triangle screenshots --- examples/triangle.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/triangle.py b/examples/triangle.py index 8f6d83ec..32892be2 100644 --- a/examples/triangle.py +++ b/examples/triangle.py @@ -16,6 +16,7 @@ This example is meant as a standalone starting point. And is therefore as minimal as possible. """ +# test_example = true import wgpu From d93ffb5fa19f788f6968d20b9d61ebd464a471b1 Mon Sep 17 00:00:00 2001 From: Jan Date: Mon, 15 Jun 2026 18:40:19 +0200 Subject: [PATCH 14/15] fix draw funciton not registered when screenshot testing --- examples/triangle.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/triangle.py b/examples/triangle.py index 32892be2..e522a445 100644 --- a/examples/triangle.py +++ b/examples/triangle.py @@ -107,7 +107,8 @@ def drawing_function(): render_pass.end() device.queue.submit([command_encoder.finish()]) +canvas.request_draw(drawing_function) + if __name__ == "__main__": - canvas.request_draw(drawing_function) loop.run() From 6348e4536562563f53d084fdde4b9350a28ab54a Mon Sep 17 00:00:00 2001 From: Jan Date: Mon, 15 Jun 2026 18:45:42 +0200 Subject: [PATCH 15/15] use new screenshot in readme --- README.md | 2 +- examples/screenshots/triangle.png | Bin 0 -> 25547 bytes examples/screenshots/triangle_auto.png | Bin 22181 -> 0 bytes examples/triangle.py | 1 + 4 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 examples/screenshots/triangle.png delete mode 100644 examples/screenshots/triangle_auto.png diff --git a/README.md b/README.md index fd50b4eb..503b19dd 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ A Python implementation of WebGPU - the next generation GPU API. 🚀
- +
diff --git a/examples/screenshots/triangle.png b/examples/screenshots/triangle.png new file mode 100644 index 0000000000000000000000000000000000000000..9dfc3dfcfe4a54bf4b89d6f2cc1cfc71c7e20249 GIT binary patch literal 25547 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

}2UMyw?iC-!a?0uA9JjGSpt}51Y z_h*$xbYeQ$m;CGQN`2Du_5KaaV6RSYWU8Zd7v%gAcVCP2qq+PGv@cR%7uk^AV2tGJ z;^SK2cq$=Y-B_&K)S=Fnj+xd-P#+~{pyGmq?<#NM;>B;je(Qrx91fsu&}gaFOKVsk z27f9!nDu87-2bb6TZ{9|?UE1T-=J)3q4|p2KO63-q;U`CHf={a?hSdJFCoIxe8rbY zZ!6N+ewvDGh!4^0iJ4bZ=p)Fd=p_&AuW z%IdS}KEg?5)<5d|1yeO!)8xUSCAcCB0{)2fRxCd@rbDS+xW)#rc*F0^u2e-sV+I?2kBcWrX6HLp%QghL zV{0dKz4GAchl}uo{hfXK)}qrY_iHsR zF1J8oyEqz2bC>^~`=!U3UmJY0q3sm_EO_PHQ&TLhxj8L|MJx5&Kr;ix8ovqjA}k-j zX=uy0h0Kt*SFQhcII=o^ZmnFk(td|uTdQMmTRM5s+bfrVGs0jeWjak`JJX@x3#Ak1 z{k=C%$^Mtb?Y8;(|Tuiyy`L(Q{tz<)b`Z@g6jy~5xP zR^g+DW~Z(ncCq9NjNpn7pX~z$7%_}*k_9}Lb2{TXY=^%Nla*Q$fpSU>mYIPPP4Gw; zxUJ&F8a(PSU|Y~> z<9^m1xJQU@;eS)(EtUJc*(FMbMMM9zURK9p|J6Ef;Wp?mN8C>KQhJ_@qb9_ly%J?s zZm^~D(rkBav!a*yzW~scFTN;jz0x?zrTF)6)NTGs<8ER`0(obdWFv`nbnhgY2nO2j zR=YkJiskgePorhb#z3*b_|Mq$cf5UL_c6k@gQ6ALcG=ZiblWK&@gYFOs`kmiFYBV( z>853v2$F>bn)Bt6M#T0JcjK{IQlc`U`qV09oJjM7ml8}?=d%O19&YXaWs|gb z2>(_Rp2_*~ewSKv%LJuPSG8&1R=4c4v#Fxa>ADmoB3%y{H$z=Y#1V0 zfF0@WxRnoU;J;>UD0bV?pUTX(fzHqZi;MBL<>wGPR#wX&kK>?e7z4-T;EHowy9{U5 zrTYo9;U@1x6LE*C;cWomJqj1}%c*aq`<35){H18kB7`)Pqvg|~96 z`h(^6uNm3X!P};j_HHpn=~YhgGm?n44|hHcdRnKn(AJSu3%PVLr|kSytgzTroX~#q z-o_ojE(F@`+=dFJMsb%yK|e`3_#DoVU%~dc7C)i4E;~IuT$4f>g(t6m zrvxrxs&4UbruO!QMlVLU>lW(CF@=`OO6)s!M9&J9?vy1!*2HdqaGIUn6Z8$}w0)F& zzj~RKhDS2cwpAOjMqF5Cij|dG324)qts}kyd(y~IHn(mN8Qbjrw-L-*Dau#2sbSYI z-Fn44sFxhNz`P-DUE{}g-|eruDUoUCxPF2jH9Mkm{%eZBG7xi|YxZP_rWc;X9Gk(>{V1`Li? zUpSS&O6eT>ympiTG!QAd43tQs)Q|u~6OeOC5GKW6hjEW{jycr2E;i`{m+`7*qV*vG ztX=zpLV1gNo5q#E_MP+bA)Its{iwJBm|Gp<8Dh^_y(~jW9dHk1Pj~lqC>4jDsqbaD zzl;xUY?ViPkPmJ|b zGPA90&lN@XGNAm8q>O$;_?Cm;%^k=dLQ~{Gv29Gmc0}qran$$I18--=zA&}EnEOdj zx>5;y&ysFO=ulJ9_yFu0PQW{9)B|&Ev{#L?Ms@W*k76%>HDb&vZf*&=Tc@Pv({<@l z<3e@#^FSL;?RcrKOt|cJNLv0^(QBr;iJxzNF!kZkQ%ZIG-Z9f76Soz6U*ropc(!?M zd^TJ+x^HUbI}mHOVe#!MwZkLiOxQFIA`*eUO6gc2m0Xnf>Qn5aoOThdg~CkV-|vl6 zOrhJ)pRJ_Bs<2^1h=`*ysa~Y7in`y19Pyi-zsdH4KyzfvyJxme=i4nDp)$t37P^+c z4zIUu2J!gZz4UNWm%enpP_v@Ab(~H73`<a*4X#Mwulmrl*tYP^PiMaQi_1rHaw99DYpNT$)!K;jkQa~n zXjkeSH}bpghj=${Mf4fszY^-4+khyH{^@kdfHib~7szrZDqena`mQRQ$hyC@dpDtE zBJ9&HRe0&n1q}dpmF~e0hQcQ!FfVm9Zz3CWqbPsl+g%fo-qetsptq(2wnUuT@pdN6 z&@*fiC2ts(3Fbd){!b^Sk$#)d7QKMK9k1mVZaF(z6}4w_nx7lHfd8CO5*%7x9h&WH zOO`g}7a?NfHf09E3{qGxD5*Lnvx{q*i{7u6Q)ErMHZZ~bO$cBl3UMf!SfQLX}W1_?TW`W%l} zyMOLi$x7kb<3&FuE_4VYZSM1fQ>tZiue0={&qZ~wE*LAAv_RmtEys?FZeSRJkhhX> zpOFP-6FN z04IOz0lnC1_~d!rP$z=|XV+O7L8dHwvUv6)lUy`d>Q>&aIIV zEVQNmlY^^WHU}Gp;=V7&_IbijpHo-y;FvnR@Ww<8M?(u~_CSiI3V^#pF}v1FulK|t z2wo`fjM(@()7bxe)2giU#FlL)&Nr#iS2vE0Ag?w#<4d+)Yjlzm4t+ub;~J&GqHZ^S zNdzbrA5{+LaX|m}I%*Z5jF1R>y!5XC=Mp@EusK_|0MG^^avmgq0d~l;i{QFsBNppD zn}QOvjr=><&C7gTGLC3Cvi`+ygO@?WcuYlVpek1D0lZJdSa=;lQR;zT!=< zajSH|=E=OB-8RPXx0s)|9$ktJJ^z-~#!85eB1sW>pmF+h@7-k@ABq3g!uFBT-6+jR z0h1xrrT85^sqCtndc-fYrnav#d>BjXy(dZE=`Y@Utv+`pxPG>^BzW0~julN}49Uy3 z?)+gO&ms7GqL-8SFVIIvTCX3Q9f@u9cvf+bVRJ5hc`83TDY{6OYhb1&o4oYM)4n8pBwpZFOjuBY3+qWuRc8lcpO~Qov)E)v zQ{tBEHz*I?6}cXFS5x@f*)}QXxtMd=fDUXtby2>y6DgBn;Q_^sXKw|?Sd0L_G1;0f zFyn20^WXM!?^xX3ItX5p3f~8?(CYjElsYb9fb$9t6W(ae8nk?ah>9FXJRO zL5?L;up7I8LdinP+Ip~&Eu(WQu;isl7)dJQyWVDF+MHNfQi0TdkJMvG!}L>cy5g-& zLF`W0fQ;)|%w^zJ*$67t41a)J7=IgRBemjQqsMG>Z5OE-u_A(o2b$iK>wMxl-KI639!|vxw>P}D{iU(2$7nXM-aWs z;wpN*%Ob`eoLpWrLP(5RL(Un(kJDelrR$r-20+*p#;L%$;I7qh&1p7IN(EY4!y))K z>h0I->-#*G91)w80wW)t-jjYlrCKIRs&{f`HGoEk+P$*LV%MGhsi}FBs(Qa5Bl32< zIa(w#I4^Cg2y8BQ7+Q)+bP643#U^~yF<;$WF&%dxYD>Mo!oHlf)(vIec`FV=*s{x6bmH+mW3O=@*zqq+ihIu zub>p-P4oQ*WdW%LeN1alk`<(iJ+?wdChM0bG0mB9YSX|Cc|St&NVQ;@+{+W?5sOPW z&w%$p47Tf&_Knn%=r(G7{7}HP8rIQZ%gk~axysF8tn4?{jE+#A0i zkw^7`Ig$HcYh=rWu64{>)TutGbeB(8JFYsGN z4dI6rFtc+a`LZuAG+N}k!y$AvLy=pi79LmK=A?*)j~y@tCpxUX@K=bKl}$~x^P91% z(qk(daDMDu6v+xC``9W`4<_YYc%l&B&G$!S><%<<`tbgnv9#-VuNQsd*c=|`Ji9(x zp&reH*USk*Q=(3ZCnju3X`}&Lj|}}RQ2RBRQuc$Ps2Ktbv#KD{{08|&h6NiG*SZ(f z#ZqO=Y6p9qy9%M3wlYNq&%6fZV#VJa-P8}WjH+^9g}?46Ufsk(1lnXE0XF^LU{j~^ zTEVb}TM?E2d^$E{jdXEqkWOPk|AnP3*t1=ttbP-;9e>%_y<#S_w-a(+Soa&>^(Yd86cKWf939}hGH2e_ETEHVVco;c(jluX2sU_EZzC52 zHu#Zo8K&Ug_)XzJLgg+d&{B;{Q2b5c57@@6 zCLeVXUMkrI8icpW3q_9^`J8cz=nJbw+Q z;XyKOc(lqB*vxHSc26|~RxW{=wf*@$m*52vHM`Hlmc7Lkn^R#(r9s~8TWcKe2SVrS zPEQ!>XxV?P`0K>EPZ0SUt1dPe$IjO-0eLCgd4=V;fGwd)UW>pr&w#h-)YrN9t4k|%wuMrvyY>%V;&?5jEHCDKQ1qEpu9hR@Fw^qp=0(* z*ZrTCj49lp?2kXJeHo!jeBx7XoHhfo7PeZMwAUU|M8oL@W7nGOc1t|^z2%C)0!P-6$K(oASY5@W2(Q0 zM?Ab|kfB*;n`w-GohiX!v+oXj+Buf$q}(hg(nVZ;3>(cbFOV+gBxv+T4tKe>)zW$i zF)GOqKyCuA|6^1y3u!j|Nc@`|ABR_VJe_u(=P#;nB7s&gy+=-oj0H2Io4tN=5woa= z0j0t4(gy>k11nw*?o8$~&x52ZC#?mZG-tlR=AYX0V2*;{BQZS_z(sU%lHT4~V@9 zFgxj4?)r1vWhcIXzc%IdhD+q>-%^_;y8jUxU#wXb2L|T@p8g}X(%-91^pEwU1Dri9 zr^>7|CXT1Z-~|$1%egV259Nr@#2qaR>Js5a2CEXub%u&5)j4GTSqQFd)Es6kkhAB8 z6YmyV10CXc13%jwNO}~IldBWX&(#xc1~qgqq^MuE{Rg;C=_XWMUVHLLkiGIp*`$p3 zGf?kec(Kt7voc~F<$Vsn<<-Bl_cBX)giw`hQcw%~R`}n}& z1x9NnWaFGQEz$|j>pBp5U&q4W;hYev$8e)urfYv4veexLPk2ponYo!q4yoQM=;!M0 zlaKB%AQ!W&G?}#i7r{yic+h2X0O!NF0^S-TBdM%H&OgV~##!8DN|X^dSdKO(-TzJk zX)(;riLG?JBpJivR#Db1CpWVbof;fja6&6>i=WZZ<=EicNey?PUOYmRFa9K@&Ba;e ztwZa9MB)9Zma|C}%?Le8aiPyZ;S<@!jIDiPQi6YwL)0VLP%Bp<$D!`()zWwc*twEwrA&SpQNs3wQV1u(Y72WX| zES_ZEDTzj)^73R_&9Z0rhXa3SLm9LTLNuu&C`Szd2fYN6GHOvH#$R57ztHRr`;S=T zhT1!Az2NTl>d^S|%cbCobO?myERL;UEwP*x0WEPfAv}{mDVO^&WNCo5$vHD4S9I?C zmi8I;v^I@~{ipCT-ymixZkUhA23V&U4t}+V>l(Y1jcrEJk>08R2NO<^kztF;w-3L4 z@F0Wq+OinVIo!5BkbuN|_eIY4uzq!PJ+3}@;h_LccORZWw&X4a7NeF#ud5DcJpYs?tO+IP)pXxP+tbqbfd6IXz%{Lg{vRUe3~ZQ2(C| z0Xe#t4GMAkbULQmu!# zp8(o3U01a)Ad#vCYm&+<`d;MslHH3rz0d6Y633^i~ZmHn;+4=>MN6k zj$6?gyksUl15b*EzQ8HB9V@qM#Ap1n^xtY`UF?qpNmhhvO`CU42rX$jxA-5S!Da~} zVH;-3>MDy3GIOx3%K|C6fBP{P43|7;t~~g0ciXb5L`UG2+oHxp$`;IUj4LbTEw@m| zn~dz+@n_ifA^z2Oho)6VckNJ26FkL=_QQftuP1LLxTlXZw%I=12QKi2(Yfh%)=5Bg zYB;x`7W=Nqxb6pQmAN4ub{s#XDTlS@dtVfMVNCYT6p9IAo1ZC~vp??^tkKC;hjV*n zU0q>!`?M44X(?;{-=;OQna<~DUbd%Y7iawV!1*2ta;sZ6UJ?IMoU7(>nVc=fCl{`FO`xi3Q0uRv zWf2BDJ=N!K{Z5~(2lLoe;xaXyD|ou58v?fcZXl8EgOg2;?k84V%^jr!2}`+m_NrME zmsapHE!{JyhpMA}jQjyXEjiLh1Tsp`%k}kZYPHimykMo zvVFYxpA&a0<5HutQHDP%By6@tG#~z>#T~QiD+%SO-#(VIsVhI>Q&++%xi@;;woCPB zw%u548l2Hs37!q?@GLmK67lOFOy(LVYE1%_N`ZCNljp)SW8kEfM%6$4G49h`o3H!4 zjOR~XVD6r$J{xxezhOL$#e1saP1t{!*{Nrvb6bw;&5_;q!Ab{B9p6Q*8BFa`kWRs_GB$cv$bCaLd48h=WC~P zBH5pUrc1gZsiZ1S#sVc~+LLNW=0M==bjRL9pS9DVS*bEx-2A;MqKG{kQPde3Cv8+~ z5YE0$?L#?B25{EZR-Gwh#79d_%Z9fB34&v_BYW6Kq9&yHoA?;>)W8L8xg_0Tx-W@& zdwG~GFoO{Es5AFQc#RT(m-$*dsh?ZYDwo<`!ccmg^^f50R{3aRQ@GQZ4wK;C2tl|DoC8g#;>m|^wt|=K?f|0y(Md^AioGFGV!KCe z8g+uxdd;e9xsUvVch87fdRc}E+gs+4kGBnBEMT`&(pdRkw{wdY{v1DKy21nZ)=H^{ zE_Lpy4Shm?>SraN_6ELUkDMUD5*k?Tm~o9CPapv^YD#nVDiapx<4v>xIOX(cge1W@ zkkfPn;x6yNUHomO&@=~*kT5*yVyE^L#rjsr!AzqJzX>tPdmC{;kx-n}mSeaQU3Qb5 z7lOYSnNj0B-S9&RY$Ja*W3<;iz7ORAWxt*}B#VrO6}KbHi(Ty#&0GdW&_AxEWp)Vg_aE+f_7id&=Y(8 z+x=oPch?_NXj4;1w?rl03r;1l3U#f;clM_13QFbDD&i_^V6D!Va5jy;H`Lkiy)nX~ zug{OByR!3y^Aw$tdCkfII32|h*B3IIlFFKt6SML1HXNcBC>AVA+9kx6UiSxro&9{*8V%&4_Bt-;)Dp|k#di)ya+bOd zI@kEm1`iZ}&(Qhj#a!Y^9V$M)_?Tc_NK1gu(sg-#|HT zi8JfLi(!G~iDh>yI>1J3fl-$~pZ~R!AI-Y~ko{-k2gA25mv=^cp7RR?4o_E0kT#e; zkj$6AI60A{o>~40T-~|aZygvScCoEKnvSLKmkreu(e>XaYv zVP1m#OIU0#hKUs@SpI^lq8MCXhI_!-4|TRjWIdB z@Kq!YV48BM2f)%ZHy0WL<0;PhY-QQ3Qc3a=aWWDd?oTq|XcRrDN{6wu34Qdfs+}6k zj3QJYLpM5hO=sc=a?oc=b6@YI+HE*!L%$hbWEKz_Gs^XXLpwNs*!&Z}zYBlLt9_Wt zzdvK@|7Ga(EkVE1mvT1GrOb$r+jW}EXKP#RJqM{kzcRX;4QnHO3KowQCxTv!#|Gcz zN9+qof%VjsTE*_=rGc6Hf9+Z&`QIOqI8dVdl6x6|Quyb7J{ydmDT(*%^xY|!i|DZ| zSAYz);6&%L$Cq|0131kaqn$#wF(N&kS!sW=(8l7^M}hG6qB99^lC2_cq5?-=5er-c zIve2Py6(wFETAywE8|JQZI0(mfLFZ#aM9O}BzE;}DfDy*F_tOVm-rp6RDBxx5zzV z=B}Oce@hR7|EIn0d}uOj-oBAt*%igcib_)~NEa8R!|q~3B(W^LDMh46LLfw>$Ud%O zp{SIg1mY@11QKbI5R#w>2tg2#o)B6HB}EcQ2%-G$d!P4zc)vWK@*yXu%(><|b7sy= z0`m!1j=*<&pX$+G{!Jm7q6G5rmRl`?YfZtAn1nOWV?VxtjcmaZIrgCzWn~D@o&LlC2wd#s;OHQNJOQGw3U`EOVr(%o^%bZ^pTnu|e z^}Dn;%}GB_=|8+Rl+9-Q2Zf+m(K|Z{ToYhlQW*t9_=Ai&;_|bJ-!!+BHGoT5m z`~V@qDU)?Mx?{S^jNVPSG*hhUbu>M&Nb@xTHmHQ-l?t~S_e9}gZdtRbX$K> zsa4$bdzr%EXwA3g#%zz5+G=hX*`C(Y#9bEACHvEJql8t<{YC~X%8+X`tJ{!`T&;+D z!$dpy5q`JRn@b~`&wY8_3A>VFe8X(B9@mDr9pEa0di0BUCuPGq^)>p9BB{UaSHjnQ z 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 literal 0 HcmV?d00001 diff --git a/examples/screenshots/triangle_auto.png b/examples/screenshots/triangle_auto.png deleted file mode 100644 index 5c9bb7ee723e992879622eced3d6b43da9c9494a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22181 zcmb@tWmKC{(>59?PO+jz3dJc9C~k!or$I^ucPWKHaMw~SXo2Edq=vgoaS8-?flThreEFDA>%{{tU14D^ zVkN>SZeu-DQ%M{7!jbp8#S-2orL~6f@qH6kCof2E;QaC_O}V=@;@j76FBl^R{I0Dl z+GetmfskVp@X8E2pzrecaRb80ePn0>96Wmwn0s8vK7of>2H@zvoe3`X7d$rXl?{M{ z{eeS-y%IggUY`U0^LJcI>@@`NpYQ&EeC7Y-y#W9B$Nz7x>wme|e@*%SZR7vL zz5W*@|6lQ1t#_dS;+XK4LB*u+5Q1O!F}Sm&wzRMGmx||MZG4$s5 zBFBBqO({cMV(?b8-h(>Wt(t>Hr~+hAP1<2!{{$&GH%b;@or*&wc`-7#TO0 zhAFTBF<6xOnQKrkw72ZjhufQVyAC%6tZ|s|iRT9_9(%L=27MQDK&O++isP+W85)Fc zt$U%t6Su<#5Ony3G6N<&aVag@h?ZKv#snKS`%Z6DxbN!E2oejK*DUrc=A#epQHK9# zX}l+Nhh%*d$4x^#BZgPB{l)wBTRB~DG?zJ~p1H?1us|YF{~3`kCipQSW{d|xBf8>Z zvK0IzVU*SpyfodrSrXYFe4FM=9-ByoTjh-=bg*_|5X3;--=uck7EbyhhW{zM*^QIy z_sV&i3GwVHyc~Tc-f%8N%w15g64ifJ5hmMb3<>h)Z!axgS>B3C3+mmaISGs|gGmg2uBWOtMki6V5N9`V2qi`lor5 zAb3$LAOWb7Xz} zN1&8=2qBMSNrWYW`m59j^@^q`sDPZX&Okx>;*Vwf2lkYAi5ps4|(DIF*#A z!gwC@X(?Ri29z`8k%cs;Q6INE^YYEpTyX>9_Eg~zBW6Nj7s?^fP#qf>L=$|Q{69wM z;>-n=RQ}8ICQ190T|D#F3V8dnbyH@$eD`_Is=;zT^8>}4Tstj;ptCps15!xC%FVYH zIVQa|7I>w43dEY|k(RK1Cn{}Q-%J z;^}Xbyqi8;IFkuac(}8TRwcH=Gjkk+V>1C#2GCtH-5dLzx$+ID&$_x=gFhZk6fD_clW~{V>tps zc3g?7(*~$?Il(8K;VLosZ(MgTUPWs6rCH4i3i@}692=Ph3s~1dIYRyUO!Ce(G30{% z-Zy~b9hd7cL1;D?rN{f6MfC8!c5z-H(Ct@4W*@>Di_h#dYZE-yHi!?b;Eq9T@M zNd3^-l;+P1&_Eti;J<4wHvphtF4P&5xz+cmr`qbftw#^lm#9?^e#99Sky&?wyFvEe z6U>5(&a0LvYu*#jKWj&MqGBtsL4ZrS`q~$9&fo4L`vb*sq+KSaQkpU3?~5~f5A?&W zw0NsKzW9r?ffuQeJ@NdZL9Ti7_W)YVg3I&#U5HmbJ|NgFuM>9A#xU(4rUA(D!3~?! zNVCizu#U%vJzWP)h=jJwtJ;;c=ntZ1lXDGCD_c+J6Ed7<;lZW;?KQWGE?VszM-6!$ z@4d+j3X(~mwGq_JGRn>5L@hmv@lN=Dy}+{JWj<;BB(LL#I`|gMaD8AN?54%7aFeOC zn68LQ?jXmcq*Mr?#3$`M?@+E&#rj3vcKNznxOVmw6aTu*Z)<4}wfKaG21eTZhn$85 zWQ?{&0ov}~^|b;4W7b@dDB@IfF*IPp9Yg7uT;+Am8xcYjE|u4!te<-fiWAC#UT-v5 z*`q%Db-O@ie+I_(oa&~2R?9SLuJVTRdc34Af(G13a**Jxx)|U_P~R9$)~{DQ9P(N$ zHb{h^W{h&{QU&ZlgEa`J3wX8dJOBCff)cOhFKy=Q9;w-Bp!|~h>sd}Ct1>}^-_YK& z+tH1|Dc1rzD7~LEtujoYPb8>ebPx<&k&LkxZ>Z~)SKc&#W@$acEsypkQUY}1gvr8M zAVGuKE|!EqS@(chiQs{`>O)#NqSLx1wHD-AV8GJ|$ff#u$NbdUUrn-;?5+_aK@ zu(w)CKD0I$)*?B?P(60R}#^?5Nxk|lXw_^E5J2q6&MsGh0! ztvp%#k=&FCyZ>)n*$=r{O~I|ZsYbJ}qqVq=)f(OB?-Py%jPzO;ZXj9I`?r!4Ba)ys zzeeUHf?4J7s+}sU6 z*`Em|Opc1sJ-VS}&91)86og?WWW6cso_Kt(@1JG)N4KUJ`0aIQpXv zA10p6Xe*>8EUj}R%Q+k)Q~ssg@6^{Xz^pc}wxYQ|??yQ0w%F>|jjHmw=q&B$mkN1* zxV_mPJ}#NOF}qq-H)}rSa|9c7VeTz)T{`NHF3mXV{uOs0hG=5Y4oAD%8i%9O-Q24 zH6^7t4PuXM{O_#GxBek0?t(D1LC)5xbr(@MLMB?T7_{Nnt^$9T%;vK|L}?~vp(k9o zACOX?Am#B1XSCTTV0AcUpUQjIWxe1Xb7yazgy|^!M1Z+L!qt*qJpNj+Y}qh7+YTXB z|6bPZeVVI_z#8(~?1+nd%Y{Sb-OTaEwStc|$q+@r!!Ak;p}Httas^VLM6>R`^fd}( z;a?T6G`de^t!j@*-pcgA| z(2o9|3>yz3pI>WdtB$b3s`r0wCu~h@=`y!IXDG%Q3v_=;4}EQ5H2rd{)<%o_Hjww2 zr%B=`uLn%jFYqCa$##_hY@EhnAqIRw+L;-sK2uulGYSbt|GAh?_l!EeeS+a#3~>$& zvm^xy=JC-HWXn9HiUFBh49z>M7s^Rj9>y8`cC0_5B$YiqQ*} z_TKb#T(eyvY{*cF9(hs{wxehwp*FUge(wXx)YM?jr-CwnTN0U#_OQ@$T2`JH?$P75 z$jOUim9^X8NB;qn!2>5vvC{#qp2e%Wx0`C^y8BN#r^~SLQ&t_lxXEUnXWgxAyk4qd zBc<5P#ILrVLuqel2#MXTwwb#Hu^}#@aPu2*_T4=zIF|s20Xb zFZCs5--&2HuWh9-cA;qnj1e*zmHa7hnsDHDQAZY3i|mlA`hjfWn!HjR%#_9T{wY3L z{6sRC)2cMMeU1I0XVBMs=^g@E#jm_~gw_^7#lM0A&6))SHOO)Z zfaX&pB`E|HcDvqg&jer0%7)Y{@j>(72p$+a9IXqXcja@p+dCq7@i3dVsDY9*oKu8E zg&Rwe(KGYcLJe~jZU6RqY6S4dt2T_|8v}zNW^cj-Us{jz27W8Z(2|-_wiL=D01cF- zm`b)hER?q|^6yYlY}jlS46m=Ehzm2Kw0%R>@s4VC2HIw84V@3;V6X5^)1to$%v({$ z`gBdZ+P#iW{8fsdd`B*%_KlQF=7K9r^VrkZ3eTnXY~oF;P2R>!C;E<^2Pvp>bQR2V zyRj6rSH^N}!nT;<-}eE2bpdU>^CkKrsa+WK$>=bG#nuyOcOCxT~`_5`1jo z{kUUtpE3fgM(u-CS$QeuZX(}lF}gWc?Z||IH%wJGtou@HWG%;Ho9i5ZsdEkZUC31N zPK|G*SOA@?!%BZYa1*O4a{rOJcB*w`e6SfO9rD$}jl&Gr+lZjYnQf;aerJj7pYqp@ zeC|->`bzMIS@J^H2+IC;@D9q0hhc!hrV!AH=PP97_@EJ840X)D$hx7}FDGVr`^ztk z?4|E=TE-*D_xh>&{&t_@OLtTdjy%t>dc&?}!OhnxKM0S$fh`NFpH>PgPP?`ltP zG}MJ3+VvYDX6ypYIbLbETXAu#kYp#p!H^r+H$`6kQ6mcKMy+y?`jDj|qf({_Wi5r~ z+EJ9~`-~zlZV(XgO@@$WOLi0X}S{jXP`rTL#b zulu3@GK=&Y9ddaET4F@jf#hOkAAoch7r|~zPfo8I%%ZvuH;z#6!yaQrAe77?FVWa- zh~3g_z|kzOcj)uIbjaw2HTy{tbYehdKSy=6DQl{rm^@$7u1REHKZ$(HzyQ2Dmco1c>q0L8>1{SPPmP1c?F{;=l7*=KmVMA%K*2CiQhCk5yT-gvT|5E_%(S92NM zN|DEMfGY?E))gCJb~-^SPB;fCJhg*qzYnb?sxwUZd)P3Tmxt#~ zb+8@R_!b+FHr!dWcogcjcu^Q;j$|s3JoNPq(A*At7az!`FI3tTW6nb}lQd$uEYywQ zKpb${OylUL#8&N(dRzEKH3T2~H9vh?CB^FYsCXg8WPdv@r!BGAyxTCv$fw?a$>~Ji zvECp%KrQRwIF`Z!!xknDq>b8BBl{@To`OTlN5Wb9fgg1#eCliNAI{d>HI-Axy%>RK zqPk1YCuiUHk>_(MKU>?7@|cEluAy0G-PTRQhcef|TxVY6Kcv-6G@g{}y(ftl^oT69 z&%S{8ILi34{z@dbH~^t0NihiTBl^#7H-&2ThtZTf>d#gdmf9U9oSRVAoU8MTM=iSc zVp=XoO7K2>MEP6l4AYXL^QSTu1~f7SN=`30%8KEWH5d7k85~ANaeIeiF6%E%o8zOj zma2Vxy_6f{dhgki@w}$%go)3fSEm1DItFaxVhY@LdPt1IeGZ7RmH;EwldbLeuBUQY2o06^y`W zgDGn=V#&g$0`IK7NmYT_KAHlNcFujtRG*`7k8uf9N8V*Zo^&)MxKnmzbaa2!c{%STzX=*Ar=dK+0f-J@!0}OC#M-QN8%2o zKYAu$4r}eVmJrKlc|h(c&G1Ea$jS$~SUp+SUayGS<#0JKN2I&Cr~`~X&PzVayk=+g zY5fIF4uPBc$3}ckh9T!wKJq#GhfQCZhBeB7(ZG|4d;KPrWa3@BDdJ- zDb5IuZ=Coe0)dm2!ffl??=MtX2fWB8`kle+7#v1nhz0s>&|B6g#~rAPMT%P~cWv_M zU_Oc9Fkmu|a*XK!>^!4TvemJb(lmvSS^VN;g{;5FBxgj^y)HZHxi+Iyd}7kx_{K+$XWU;au5V-aU*NYE4;)>pK;+`-8VM$n zvtW=6G5ar4M#%oX`PNV5c};Mpn0GEbJh)0Fp_8<6g+omob^fq0nudB<#+Nl11yGYh zw;zYUvHZ-aClO*+q1$D={)kCj-|I2Qn#3z)+Tr4Zm?Y19{dNH#c5}q+Bdo$D^=S$U zx#=CDXwen$JJ?Pr#8U>X8_nD+8mcg*5~g*XCOLW|9PD!&)I%H2$lO3s@JdP7z+)$q z!)(g6xeKX7gOHopEI%`Gu2K(QM)7u+^z;Q`&$IJQ)t2v(m z^Q9mrY`g{A35h1{ujOKEdL+c$XDMHgl%FwDyg#=xZnb}86`$!|kypnmC?O;8-RZ4T z4!?NP$2q2)N9^wiUD*FU`8d6s-ncrX#9K~HI2M%6u~a)|kVK;4ygNH;^p8=ymi;vC zLS5ybmUDk3AGXv`3r$82c1zsBXoMgm@LwkFrN&;EDyKqU%~$kbcK8R9=|Q%Y4N4H) zMgq(EVLm3rQ6QZ2Z%r!REH0D3Pp*I9fxj6?=wzcVNdSzwzDQc_lx=@+qQQpT!g!v~ zBuM*IN-(o}=?nf~k87p0neB_BsKq>PCQU-p1XWb8k6mC>3u zziRY7gQD66zh<|;Pd02TZa*;#2*GB3eM`s!K`KP_M6A|>sfEm)@Tooe6(5m^P;^h; zlj`^LK0udNTT4(Q3?Ck(5csUfhP`)C^1jMfp5Y0d|WU^2RUkwxigFR`6$qs29%JM8Zi-#PAwuW&U^7rEiDMf1SeKD96sv-(^_$HmKVZGttU4j@+R^{f#$l6Ec^{R1evmW9*@)1T7Bcc zc$$6sIP@v*alH!*l|A~wg+W=q=MpqsP$~r7#LcnAnK1&6EgdSfRb>LL7oR7wfjM%z zKFaReBDF5ys@Z)9xo7>jSiMS1{mQ5f)$M4;3xqpthOlrv_fv?JaLZmlve93}W=R@{~5Q??pzaqmyVFGX4UhfiH+z^HzNTJZ-^Uk^UJ)bhpi z)tY@(`cps}!dg7oZ`6IG!n)AjfN4w6>8m39qvzY|cmB3~*uOr4 z54!<@|B00u$J4I~M%95LMo|~LZ=;6sXEVUWapRoIm)%Ivf z?YbA$;{i43-JZ~O&n_)*+?{;C{EQXEd@UhW0Fu<_v)kuoL+n%$_KLCuFW0`ZrBr`IyG9_%sI{2sy} zUb-&z1;2_nCD<8*BTRhUi2n3+;U0!4w9}8L)dn~gE=s8((~K_~FoNOv0#$()DZ7M} z1Q`7@zR_VqFtVhaQtPWQJVq?V#Kd9S=N+T&FZQ>weQ6(ObDOodx!ViL*!_6g27 zwD8@M_iiI0o?URjJ(gz3Cnyg|o6+xkr`NF?z!Co3CG!$9_k%j*d~1+oMIhOMwRd)7 zs+jJFD!K~j{%O;7>W^(_lLIIXqqdPyJ`Tnh@2KM$inNYaU#MwkaIH^z-Q$rMg-bzH zp<{HuMYOW;x|Z^vNx3KD@kd}yhuJ7p+-7Vhzj>K*xj&jN7+}60w%JZlS zMTB1pmi2-^3ae*VP4)6?GFPEp%ColD8oz%{W*}nVS8}f0uH+{9#He=67_Cd{05gPT zY6LV0P>2EJp~<}KwiZw+4?uD4u{e4DtxT?g2X0<4Qa*qHOA)}+2Z3MY{E+J|#2&m2 zqhu^zVSk<@BgI{<1OL5Tt`%8kcpc?rpP??A$`=X7Zv#6?X%b@EX6omfay$iG&{5yI6oMtolFX?4|v}}muy+Y<$%erbe zeR`W3RRcDrggC_XR!Mi&&5D6p&9A?s4(?&{$#lE-it3aqi+*~;oKAcCit)E%$Ul#j z`Rqls!;t`<#~h#I0vtF!^y>U!9(rs9kG=!>V~ z_0Q&*mkD1%s%Ys*=p^bFb);E?F9$V#3Y#Keev`KI%y=m0a0RAMcBEQiu(@6t%LxTLL;;J(O z^n}-`R{`c2dAC#1#>DD_Vbzc(QUhv8CZT%5Fv986314D~`0EzlKA)<0f*h7ppXx%{?-h*Y{drCIhEWpo#SWQSzQ!e<8aDgPe$0i@N<>nKCgrus)$O!G zHz5lqbS+>V;|WRqwe(jt!M1F(6}G^1E}Lv4xwD#mxkLxSHCt4~n+u2C)0Fba*yh-H z^%5eT$wXPzvV5NOSa(*c@WmKglERngU)@^`i9CJcXKu?RQX8%0a({!DgC8dNP(AFh z6S&31E&$i)fLwg^)?|AYf$?LT;I1y$RCCy5HFpv;JC)GCVW4 zLZ5%fyrG)c#H>(LGlN|wJ+Gu}r-#<0JDSa%JN3u-Wm2=pxQW%n!z&h=(+X5gKr7bG zj%GiC-^n+(u~Fj6NGX;}T{kf2vEQDq4!GMb<}j9jJP>2ZIW z1Qsv-gH|lQ#P4Qa`SaE5YWXpGzhuQDaQ1{cWLY6tXT<7d znkCAH-KzE_-C zNoEaIRR@g2R5JpcMnzw67~*N0-M zAK1G>2`KiLOsfns_~a=4tWBup1-qg9sAPZDR;5IUEo0I5ny%;@ZArbdvoF8}oU?p13g zkyk$a7n~HW@`eVzl!s8dkEtk?@$j8moR75898SGPtqTV$RS2L-(yLe8!JS!cVn*_k z`?JB+H+4m8`XAOoxk#Y{>^XN1mN z2TJ`#*|By;(qmKb*~+(I(?=b?%lY>hVr6Q+aev@Xup-TAhWTn%FA$`KBY(1~xxgbZ zs`=0_m*nSHqZc|i=~mA9P3*gu_3d(B`g)+c5^P8z*3_uv)SgO#4k;SEf~d^`Z$9NR zU5HcboAhFHtT%N3s`bp{Us~4$7wv_>wp#piIE4E{Nq5)jI)P$Si4+5r zi`2?5R({q7)Xcqa?sGN4BZvrb2=sDbZzHoxKvIO3GwM0t^uw3(I=%wR3*hw6Vxc@0 zwEtLm`D5o=g=Wy4nZ(RGc!q~S8F(w#$28T}IRsF}Qii%9$AX12hW_qO^p2j@0PA#) zf=?$x^ImZrM%D8jGpwtQ=gu0d0&1Rh65FLIY2ldkc<`dTE_CGQ8{^66>?+>wo31k+ z48);X4-er=Jb}h3~vo#y0F%c>`jCvgIt16?-Q_Ns{)B z86hPOXSk9)rt#&1wxFHTtt!d=v~W_KUa$i`JpxQTb$VJg#T}~d${xi<@?F83Nz#bz z%aoKnAD(>6&>!uz+PUM=s`3>Jx@t-8qj$$6gtXYFJQ%ANLHB+L<<&knmxeZPOqhofi}8D#r*8WD}6_ z&Ft1)Et#K{7hYCy6cpUyoDgDK@{OFiB|4uIoL3G=vVp?Vdm>uOqmKUD1-3&uzxS_( zM*3>0-YBb(7ft%g)CxE$BMb`0_6nZm>s5BppS{AMD9WRJt6szn}-M$r~Oi;45S$U=@Uj`kqv{kPG_CfjQQ{g zgV^SDmXlu!C+z{kST+ZyIZx<()^S|foE_G>V|zaojG^2@UB{pX$Rk5);&CdI=tum_UJnm?q!Ua{o2Y5e9wr*ZvyEYJLG-+=FX&f7v&J>DF@FF$7 zj#9Cl*|8$ZJuNl&S2nW`&S}&(c^ZeC*S7VJ$GAdzTuhY*SxQ%-!8g+jZ@AwKoph=Q z2$qkgEPRd?w&HLSeYFCCQ@;PgP6v-K=9x8nlrMG|k#e*=YUur?1LsWW98*=ew~D|X|D!*Vm?BQO z>Nj^g?!m-z>e#yUlL;Dpk|wLhsHCi?Tw^oTH7}N+U5km@6h0PyFg_RM=*;VS|K*r# z76P$`kCdA)8R~gvieJU(lR&+cO+GK$!6h#|jmog}ggthwJ0-i~^gZ2X>PvGj& zc#o`MwtIwIAg1lM+C_sVllleo_yTls07vs%+;I7s;+Xg~!Wz^AtwDG^uK7obVY|kr zbdL4)f%xeW)(n1H!8Y=(GmQxbE3p^%g-GdBsTZ%FZ%3=kaAvunjgUy~1PvQUc6I4R z=V0@m2fE!KM+e<|vN)Dz%VA+ZIL*lgexs@>)@ul`h`3)G-P)j-vMS=zp63|VZN-}1 zH3f-2pL;7ptEF)>^!3e)8(T{ub7$o*M=YhFpR}dX<-D_10h+Kg)(8H50rrCh`aL-k zuY+Nyo~NJf8uVPB&`lrH&{2&CU!+~;<6xVXa&NOlKXSn8+)cj}iUl)tfqB8NF!d}> zVN=bRu3mZV%IF2i!0S3)>euwv25KYJ;@Bp*2Fxhd*!W`j<0_3(7}VzDC@c!onl+2F zX(P?}Z=95^g8w-Wypb?Z1|Fu?AlcW%D_1<4d`}hh`oEDQ@b&s@d_gnejmct)-1PNa zmb)hV=(_usZ=}D?Z)_J{Re#>ON^e9ntT(2GcRy@}W!l;zSHvOcG1ePOd5ix{_AoeY z{bU2JO+$IkSc(KjRK??~6Eh>9fW z*4AbCKf#;NN`L!?xH=6s3T@^Il1BE^B~-QTqO(J6ie|k=}0B7rBr2(~{Ny4Via8c40kdJ`bW&*DUM}@t7(o-=_3Y z?o-_St#;uLdyqB4^+HK&hKz+M*FaMvFyi?0oM@_Pq#f>}DWX`{^V29aEd{yd0usm^ z5m>pf3`hrjL2nOiCf^5?$LTnm`=a~(t`lmVw9K<^65&phC~Mo3xxsq(hQaxY0W0Ic z4g`4u!=lGsKYZ@^$W_F*v?^ht>g+l_XTypWz8`*FS&`^FoOHm^5O?d(l4+V*O1>jGev^^aAug60HY^@oM7pi3iiL zoBVn6Lbc}H#9Jil319zEtN>}klwu#k$WycbJ+ARlmME3-VX<*y*U2GlHlu7G##A2! z(fmkt-0Vjthc2Q7+h=1U3Nw~sJY-jISK;1Tw9U)1E|7VY$Qd7pr~PJIThF9sIPi~& zvK?#tF_W29Z~a=WnT+Y$y8>ec;v(1$G%^L zyL!jP%60Iv_xa^LL`7WP-O|bn0QB7YFzp6kIgkRF9|ChChojxmgq2RR5f?LKsOpn| z7R|htMegfhp`(i~w|S>Uv}b)o9ZJH~F}b(?ppm|RhNS^mKWLRz6|8mfxPLy<^KN(} z<*$6ptP@5X?gPuu!h7wOlaJb?-4luf7`s_ z;)Q8#I-J(UMwUeVpF|nANck=0ByYwN@#mUPJ1rg;>gi<27EJST-IgLUm>f*acRAcj zpn4h}Z>|>xam9m2%XPda zx+Im)q`=3fTgw{m0wnuQ7H!kp;`sUY)N(QQ!)PR~=alb|1HP#~WWDm?^^Z8{B%hIz zH)4mSw8Zsf9>ec*Du?1<)g=4OmpxIv_&KW~0CNS6Z7to9Pbjq{@?TfNxO@cTnq9e? zzF9_A7bVm*f40pBH~PoVjgFtXD;EI4@90Xf(S5{x4`MPX$ghdNoBdUV-VNLR0#$B0 zd;=E1@Dz`hq3|4;SLS@%@^t-c^5wy0!;foNzf+GZ)OS>p8XhclA_?N&ANZq}qriMT zumTxKfhYOOiIhA6Af8AZbC44Dy-oN?qsF>$zFw^inQTLT^x~R# z>KCouk=d-~svD2H3>9atgh)r<>rCR-)^od+^QQp-tmBtV>0OSZ&XV~88iq4kEv3a| zgvATXmL%MBvmut>VLHD&gS6tnsRtZ?WIZaJzAgi^%Gae`(}bxFFwlom>M$ZP0rG|1QLN)PybiN2^H| z#0;x5fllB$z)B^`dVkq)s7s^Km8Lr2G5VVdnuBAzppoe=`+&>T|1QRMO<1v)bl@m0 z75Sdb5VQY^O4uJA^E$J9e-QJ7^F#TiTI(ZXSQ!uXdBv8Ww)gcJdeEn^jU#b4g!f2^-^<|XDvra!opb0#3ZmmzFvzqZb-E3no(CuDZq+-G0 zY~ASB6s3Z9aTh9d542WAF=-8mT?ldFjrBcR8t#wI0M1Nl8Asg60P`5BIE@}Dq_u#p z8sD4#brW0GHafDPTWWc2YQ`1871Jf%b=gq$c_$o%h==Y61Nqm@fH3F!!z}s46{nDr zOKSEhOgI{~yCB1_70?=LM=oTUrNorL8Iowc7k&H9;Yis0M12yuQ8B8#oQcgd4Cu+s+a&X#u*Y#$}qchR@hRS2&M;4iQP1i-i$Dp z4{(V##CeeFJYs4NSiB=z((=C(zkqqF=X`b&SFG6GkJ8kEgK#gAI#(qNWMSFYjDMs+an~}NgIl(ql+EA_N2%|v4NTWrF^P1=UQ!` z+uGY`@rcNd`u(@XNAUIZuGrSY+s3e`m=o)HknERYZ=Q_>c{oCsd)7vOWo}yVXkX=w z3GEz|h*Qqc9D)Op&Z`&|ZqZAFXJ0!#aNL(Qgp*H~`@ics6qyk>?GxM;N&N_XPkiC* zwaeIF(I;QJU#OJRl;k~sRIjm6$3FyFI&yvBb)MQBQ|9hj4n12o@-n&CIsFN<*ax-E zLpF`}<2|2G>x&m@nq2}f>ww~PsZhYT)JN7G|i); z4`Ub3!cH~>(jaNHA3Pr$BN$n}pXIjBo!8|JR|s{AKPO}Bg#H~q|F<5y_}iZPbyl0~ z677>D?NlOJ^e)^_gqsWzK=Ue)e*EgP#!;C4*GX?jZkP257E5jy+Zh)r9aM=zin#%n z%9vw>=H*7>HVsI~^#h<^(w;@)hcMR*F!x0dyaVn&qRe7H3)wXAp*=#bH?ua|n8sZ} z%WvoPgBUx00A}xlIsrTvb>*nX-Frgj=Zo1VHiGs9>S^y0IAcgBEj&eAM7E`!RIpTv$Obb(nMkc|h2RaCP znpFa<7>r&y0HS3Q8a<1h0y|w1wZ(CdN&8>=&w}f^)A5D$aZ?gxxPc5gGkqIIrTr}Y zoPOv=n=lKHwlaS&SKui6evmW{gb44&y=*p}X`yHIZ--u)iT*GMb%G%|iK%8(qC6Nh zbMQ5V5QKbX->+G`P@I{+D+n7{r4EnaCr;>M$GB z=?LPeH-d?*rkY!)PsSnA0iWxqU~-Y(tDfTt(OdKA2)NlSAL#F5ckcF1^~pC6IEJH_n^(jy`IZEp{w7 zTDS$%M>cXEdQCgy=>NC z)z)4|OJ5uPo^O(wWpe|Z4aujO%Z4!B$7m_ zx%b^s{dGq}^P)i?VmF#uc;9=gh=OnUu|&L9=AZdAxfFA$5GHbr*3J1!i#+yd8UEl@ z;yvXxTGJeEnx>;-lqCy-ARHU*3f49LFxwmK(gnyBZ&Dc;c^1BN^+m#FY@ zQ;0KC12m$ritKSNT)t)%V3hOYBNO*)kEGJl&Yrpl#K^6;wX z5pz1Yq-Z)Qp~iMGz|bIVsH=D!KCUYwxOUTza zA^C{$0(1dzi9yp5VFB7GkqCAdE^TTT$R-fP%xP&b%P<2~WJW9E1&7prZ4Grzg1vUN zSdt=TF0?J>mtk%4y3(EWx6gvA01zsy&rh=QWH@@*d44sHxHWZc$3j2R^wuzQ<)cIj zj3X2?4*Q75yQ0L!VS!J#M`a!vQav#B9~s=}*x9{#jk6>|6iU$eNlvB1;hM0WLN*uw zwj4f#FAZXTB!Hycb=awbq$Dx+bD0j*aIQ^HQrZxAXCeA|j_MMv#%cp^Iq!Q(n182v zAc*Wd6xKB2B!%W~Q{i?V;$Y-%c|nVBZSDn|M_MO-$*4*(3C^c(a4Dnl!P zI#1Ikk;6Y^reBNry=(P=d#TfdQlf**^??ZR@<?o)Dc;t+?v~0Y;o`Jj{yN%-R-yn>O`26%V&3LY5t0s=6{pP8aj9>+B7j zUX%j&#I*~jGnnLIzYa;u?RSRJZ9?m__^WvN1phg&H?QT97WZA(yx^Ug(io%SU-vG0 zB<$WOoR#BOP(0;Gh7b0|~m7G8*XvhG@fJ zb`3%oi8~Y${kQ|$T>?nGLvL+EyVvbMpr)u9epmn1EQ(@g20D?H2^eupRmh1Tq)sG| zvp=C5Vl!N%Z4h?5yV112F*q((m3a37Nat|+mpou12K&EJjJza;OH13)<%Y%Pbh&La zjw{HqeaK2RM03e8SjI5vOfe(Jc5PvQI4%6vy-g~eE<)irZNXDS;UPf#w8jimn=F1y zI?F#ck@#o&?QE#4=JT3WvZ9m5MKvSn)Vgu_8~L3f*eU=sZ#~U@2{DdUG*7{s6Hu`Bo?)ZuI&06UjHx9?@6q)5PK1O#fod%9o1Vgh~ECJJ#T;O)hv(DUvjF0wvg zj=xLLwfpDOeUmWPtcppNgvQ6?c=GqMB`{8~C=~oLyEqdSr`&pf>U0Dyn4>u{U~NZE z{JDMjsVi7@`dxR^5MnQT^gx=vA1Q4PI|g*}1^19*3eD-8qsnMJ7 zt-Cl!*iGu^04+!BoSMW%iS<)jFzVl21^7U=3`v^ZB;Ie4lgE51lTzh$G+CxnN2lXkxd;C+Lg;_+{lQOY825)GP_$h#d2T zSNW#DhU}b@$C_Rz&dlf>4wzI)gX^3$?{-3A)@_u+fz5o6A-d|^)=ee)z z`rW_ly081XuaGbV2T6L&1tP~X`8BN)$n!%`Xuv26RaBS1o*nZfPJZB_Y@G#VRDO`N zQd0MB$Wp8-qcM2n1BI6=2OZ9UMB*!6k+q$1gIJ{VXjk`k9y2vNv+G>I%R^s~7(yX- zTtmi%t8|UDc+l&Ox*8|#^^U_Jg%mJokP@AAo?zgMZgycymrhVky+0tSthCWsW1Nm* z_I)SO(me0&(&gyU(C1SpyHO0ytwSmr$AId19GM=myyJP|43$OIw2e2ycRX_`2~cxc zzj0$X76>N7W>$pzi8#$;8{-si>Nacw~Z;NW0TYb%&9+T~A4HMsI>c#~x8Y(1d2`7WcUe6JSj%yP(up7bVbf;H}et zyWI50<%Z+_QYvfI>jCi^%|pV_J!M&`K}`Rhd0d;V8B$65vqalTudh6D06EjE-=hZB z@9Rofva9h6e!HJaS~9OK1TP93U3tqZXB$#I=$9-L92<*|WLD|hCYT$6M3_`G=66=2 zT{5aPwHmQKxw9c-(=<$;rSufPkemO-fD~=fgmHW^HXr&t!davMadmMPs&9qzd#GWX z2$WfxfSUAtN~b*KtkSGgUD9&74+ppw#AJwCSW6g%S?g2_*~Cq4@39j?u%OhGv~&=(|1PTvq$jpHS#CtaThz1Zr1<@0Au@DVX#IU>6iTGG4XknmhZC>F5Wpp~~tL zOOjmcGg5d~6MTji+3cB=Y z+W1`1rCg$+F1mTsSgvoo4|G?c`EIli{*35H^!Z7vryaJ_9^u|1vC^>el3J8A+B&#o*P>J(Vlb%= zQ6o47DY4%ZbqCIxMmvE!8cFM1CcE6-LA z_J(;VXevSc@@}fUzs9ZA$ld$SotL{%F3Iz(y;i$h^bL+|h#Z?Nx(vA{PTM-0^#+P~ z0(W1!SbyWLbu5=VDI?E8$X|bl*jDa5{IQbsh1-;8t_6x=ozK$U{S74za!%xhYPTs# zp80vY_{Qvkr!}XRx$a_b=27f!B<$11|}!v9Uh7Z2dsX1`nF$120` zO@}V^pe7TmZ<5MBocO!Hub46_w13RH{3bmbF=GizV;o?ggLPi#@O}%sRaRDG*hfo3 zSLKAZpHgmapyg5J5;8_+Q+<%hAa8_N0i@_uPaaQ9SXni7IrJch6tQN!C5fPjWn3=op;oe@Hg6 zgMu_fInMR#di|v?1=2)dSXYl&RreISp7I_`QOzFf63m+pC1(2_92g{!sr68AjuWlz z2%sUTa)U8*huFd+(I)ywGyQg$yVc66>1^V0Jgrk>pd7p1w{^Eg;_9`nY;$>_AqNpu zZ9F9(vlP>l=@+Kd(u-r>x@U?7`;*eo^23V-VH$BZE<5%Xy{kyN^C=;E@62vsVF2ZA z825!&H;nb$>J%Kzj_2=}zK)cz%(?X-@9)o_n_BG)&3QruNRxw=r!a7<^xo?SwQUnh zG50)pb5CEAnmB>&JEf4P- zI2u^>7zd#=W-=i>3ouHc>=Wo)lDoiu1$8GuUhj{$p{=>r-(-eHL+3yBK^0T#@Y-?N zSl z&LkEAS*-VdnDe046gd<%LQ<(zUEA|Q=|e(e%91z)&JctsRDv?RYM_V?p04y?43Ct zbI)7^cuK?m=y+3V;P`6=wzbd2%`5DF9B?X%qAJ9>Zv4#YHun_cc#PusTzk z$8h=TNLl*KY3v&W=P#H()R%i+PAH;O?EU9LdjmFA)h2cwi4URFeO&rt%F6ge^=N_$ zNtQO8r%i#UEDX$4?&hixuSjj!=9wR7V111##Hdf+dT_&A%<}Y}8aCB<5fgVY=*!j5 zTKg{FZ&CRt)5|$g$X3MZkc>v+$dAvP3&t!fXV&lcQ{T~(L#m^u&73uw?GG=EZ!NAx z7Zi-?i82;QZ)IRp9bc(q(c*40uNaL~7S)_%5`NO|?7HxN&%8k(5_m$bC;;|1dLh-B zHAFpcE`^`gsJW@-i$gbIROZ_EdxmH`{n5kby9S6NK=}suCm4$oLLqt0)hc+ZKjJcu z+dTO}&MLx%7<*P_PIODxd?zS}1E<>lk#p58{ZUSzm2G2QteDN1_Vr4Cj%<9SE(6R( zpd$g!iQuCoR&iCBcZg_EitA|V$u7CUP{paj^KN`}n?Z74PlK$xK84wZ0)zYD&%O=T z&OhO4YfTQCoD)nfF0u!r-HD5Q^CLkW~-cHeNKh`GnOoBz4&9-|^(*wl)` za{*Ks1F%w*{stj3e*c}Aepz$fuXRevEfZsplW$z}Qa+_*8M)*&7LQ=0LM0KP7Cmyg zx?j6Pr)0uCfcz^5xIko#cLq76YwdL9P4K607XWnl9yM*r5+OAsv+fawC06@ssFu3Hf&1k&>xA6>p5M2PJ9m4onilZgUedXV@- zE-z_c9m&w?mC=>+BMa4o+d!=;ETqwv8gykm@EqM-Qz<7*=Ja=fP#yLDfW=S2^wo0u zD-wwH5Jxirwv4Dx8hY+c7_O!Xr1C>$f_P{;TBV}<;PYhHQ4FENAo&2muECL?M@QZ7 zN{Y}evH_wn!HpQpe)59}s4iHoprDRaip&T-1_7LdB11=k4C${P0>Q2Fq1s)47pzyL z->!HZo!iI^3LZspF2eTFpTkgDP5lujQA@M&wZw9Hb0~ zWkJ~KNLxy0wP9R4eS9SPI!WG}1gTKVjqvyo2W8Jd9H*G?s{o|0zkS?qD`j;fN8 zI<87uQe?u&ga~APNeJ|=U&}C6POm0k_Xh~i239dXnJHvLEOm4x($nLgIXFz01l{Wk z8urhW{D_ieLcNt>Bw-zg=GNCkm5Z_6aw^H|Sn5eW@L;0@0bIWkyQtd`qyE8qrykp3 zKE3H7Qv&G2S%_SlpfYlvXl)17QH8{BiJ>w0kvf}z^Ae_bYkP9Y{4=E;L!Tg=S|ea| z`G$K}@z(xAPCJtCG_m4k#eu#890vJ@PrVCIC!KHmxfeC20N3DwCkd|N1?@RE5n#IA z_YXK}MMyb1IA6q>H(EI$7E2R_K0nB!G