Skip to content

Commit 3b8f991

Browse files
committed
Merge branch 'picker_improvements'
2 parents a43dadf + 4d68de3 commit 3b8f991

2 files changed

Lines changed: 40 additions & 34 deletions

File tree

webgpu/canvas.py

Lines changed: 16 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@
1414

1515
@dataclass
1616
class _DebounceData:
17-
t_last: float | None = None
17+
t_last_frame: float = 0
18+
t_last_call: float = 0
1819
timer: threading.Timer | None = None
20+
lock: Lock = None
1921

2022

2123
def debounce(arg=None):
@@ -28,50 +30,37 @@ def debounced(obj, *args, **kwargs):
2830

2931
fname = func.__name__
3032
if obj._debounce_data.get(fname, None) is None:
31-
obj._debounce_data[fname] = _DebounceData(None, None)
33+
obj._debounce_data[fname] = _DebounceData(0, 0, None, Lock())
3234

3335
data = obj._debounce_data[fname]
36+
t_call = time.time()
37+
data.t_last_call = t_call
3438

35-
# check if we already have a render scheduled
36-
if platform.is_pyodide:
37-
if data.timer is not None and not data.timer.done():
38-
return
39-
else:
40-
if data.timer is not None:
41-
return
39+
frame_time = 1.0 / target_fps
4240

4341
def f():
44-
# clear the timer, so we can schedule a new one with the next function call
45-
t = time.time()
46-
data.timer = None
47-
if platform.is_pyodide:
48-
# due to async nature, we need to update t_last before calling func
49-
data.t_last = t
50-
func(obj, *args, **kwargs)
51-
else:
52-
data.t_last = t
42+
with data.lock:
43+
if t_call != data.t_last_call and t_call - data.t_last_frame < frame_time:
44+
return
45+
46+
data.t_last_frame = time.time()
5347
func(obj, *args, **kwargs)
5448

55-
if data.timer is not None:
56-
return
49+
t_wait = frame_time - (t_call - data.t_last_frame)
5750

58-
if data.t_last is None:
59-
# first call -> just call the function immediately
60-
data.t_last = time.time()
51+
if t_wait <= 0:
6152
f()
6253
return
6354

64-
t_wait = max(1 / target_fps - (time.time() - data.t_last), 0)
6555
if platform.is_pyodide:
6656
import asyncio
6757
async def _runner():
6858
if t_wait > 0:
6959
await asyncio.sleep(t_wait)
7060
f()
71-
data.timer = asyncio.create_task(_runner())
61+
asyncio.create_task(_runner())
7262
else:
73-
data.timer = threading.Timer(t_wait, f)
74-
data.timer.start()
63+
threading.Timer(t_wait, f).start()
7564

7665
debounced._original = func
7766
return debounced

webgpu/scene.py

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -233,17 +233,19 @@ def select(self, x: int, y: int):
233233
return ev
234234

235235
# @print_communications
236-
def _render_objects(self, to_canvas=True):
236+
def _render_objects(self, to_canvas=True, update_pipelines=True):
237237
"""Update pipelines and render all active objects, optionally copying to the canvas."""
238238
if self.canvas is None:
239239
return
240-
self._select_buffer_valid = False
241240
options = self.options
242-
for obj in self.render_objects:
243-
if obj.active:
244-
obj._update_and_create_render_pipeline(options)
245-
if obj.needs_update:
246-
print("warning: object still needs update after update was done:", obj)
241+
242+
if update_pipelines:
243+
self._select_buffer_valid = False
244+
for obj in self.render_objects:
245+
if obj.active:
246+
obj._update_and_create_render_pipeline(options)
247+
if obj.needs_update:
248+
print("warning: object still needs update after update was done:", obj)
247249

248250
options.command_encoder = self.device.createCommandEncoder()
249251
for obj in self.render_objects:
@@ -276,6 +278,21 @@ def _render_objects(self, to_canvas=True):
276278
self.device.queue.submit([options.command_encoder.finish()])
277279
options.command_encoder = None
278280

281+
def _render_highlight(self):
282+
"""Fast re-render for highlight-only uniform changes.
283+
284+
Skips pipeline rebuild and select buffer invalidation.
285+
Caller must already hold _render_mutex.
286+
"""
287+
if self.canvas is None or self.canvas.height == 0:
288+
return
289+
self._render_objects(to_canvas=False, update_pipelines=False)
290+
platform.js.patchedRequestAnimationFrame(
291+
self.canvas.device.handle,
292+
self.canvas.context,
293+
self.canvas.target_texture,
294+
)
295+
279296
def redraw(self, blocking=False, fps=10):
280297
"""Request a redraw, either blocking immediately or debounced on the event loop."""
281298
self.options.timestamp = time.time()

0 commit comments

Comments
 (0)