Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 29 additions & 7 deletions webgpu/renderer.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import contextlib
import itertools
from typing import Callable

Expand Down Expand Up @@ -73,12 +74,14 @@ class RenderOptions:
command_encoder: CommandEncoder
timestamp: float
model_view_proj: object # numpy array, updated by update_buffers
render_pass: object = None

def __init__(self, camera: Camera, light: Light):
self.light = light
self.camera = camera
self._camera_uniforms = None
self.model_view_proj = None
self.render_pass = None
self._extra_binding_providers = []

def __getstate__(self):
Expand All @@ -89,6 +92,7 @@ def __setstate__(self, state):
self.light = state["light"]
self._camera_uniforms = None
self.model_view_proj = None
self.render_pass = None
self._extra_binding_providers = []

def add_bindings(self, provider):
Expand Down Expand Up @@ -138,6 +142,25 @@ def begin_render_pass(self, **kwargs):

return render_pass_encoder

@contextlib.contextmanager
def render_pass_scope(self):
"""Yield the render pass a renderer should record into.

When the Scene has opened a shared frame pass (``self.render_pass`` is
set), reuse it and do NOT end it here — the Scene ends it once, after
all objects have been recorded, so the MSAA target is resolved a single
time per frame. When no shared pass is active (e.g. a renderer rendered
in isolation), open and close a private pass for backwards behaviour.
"""
if self.render_pass is not None:
yield self.render_pass
else:
render_pass = self.begin_render_pass()
try:
yield render_pass
finally:
render_pass.end()

def begin_select_pass(self, x, y, **kwargs):
load_op = self.command_encoder.getLoadOp()

Expand Down Expand Up @@ -500,13 +523,12 @@ def create_render_pipeline(self, options: RenderOptions) -> None:
self._last_transparent = self.transparent

def render(self, options: RenderOptions) -> None:
render_pass = options.begin_render_pass()
render_pass.setPipeline(self.pipeline)
render_pass.setBindGroup(0, self.group)
for i, vertex_buffer in enumerate(self.vertex_buffers):
render_pass.setVertexBuffer(i, vertex_buffer)
render_pass.draw(self.n_vertices, self.n_instances)
render_pass.end()
with options.render_pass_scope() as render_pass:
render_pass.setPipeline(self.pipeline)
render_pass.setBindGroup(0, self.group)
for i, vertex_buffer in enumerate(self.vertex_buffers):
render_pass.setVertexBuffer(i, vertex_buffer)
render_pass.draw(self.n_vertices, self.n_instances)

def render_opaque(self, options: RenderOptions) -> None:
self.render(options)
Expand Down
18 changes: 12 additions & 6 deletions webgpu/scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,12 +425,18 @@ def _render_objects(self, to_canvas=True, update_pipelines=True):
print("warning: object still needs update after update was done:", obj)

options.command_encoder = self.device.createCommandEncoder()
for obj in self.render_objects:
if obj.active:
obj.render_opaque(options)
for obj in self.render_objects:
if obj.active:
obj.render_transparent(options)
render_pass = options.begin_render_pass()
options.render_pass = render_pass
try:
for obj in self.render_objects:
if obj.active:
obj.render_opaque(options)
for obj in self.render_objects:
if obj.active:
obj.render_transparent(options)
finally:
render_pass.end()
options.render_pass = None

if to_canvas:
target_texture = self.canvas.target_texture
Expand Down
21 changes: 10 additions & 11 deletions webgpu/shapes.py
Original file line number Diff line number Diff line change
Expand Up @@ -486,17 +486,16 @@ def get_shader_code(self) -> str:
return read_shader_file("shapes.wgsl")

def render(self, options: RenderOptions) -> None:
render_pass = options.begin_render_pass()
render_pass.setPipeline(self.pipeline)
render_pass.setBindGroup(0, self.group)
for i, vertex_buffer in enumerate(self.vertex_buffers):
render_pass.setVertexBuffer(i, vertex_buffer)
render_pass.setIndexBuffer(self.triangle_buffer, IndexFormat.uint32)
render_pass.drawIndexed(
self.n_vertices,
self.n_instances,
)
render_pass.end()
with options.render_pass_scope() as render_pass:
render_pass.setPipeline(self.pipeline)
render_pass.setBindGroup(0, self.group)
for i, vertex_buffer in enumerate(self.vertex_buffers):
render_pass.setVertexBuffer(i, vertex_buffer)
render_pass.setIndexBuffer(self.triangle_buffer, IndexFormat.uint32)
render_pass.drawIndexed(
self.n_vertices,
self.n_instances,
)

def select(self, options: RenderOptions, x, y) -> None:
render_pass = options.begin_select_pass(x, y)
Expand Down
Loading