Skip to content
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

Refactor wgpu.gui for scheduling #618

Closed
wants to merge 51 commits into from
Closed
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
6442ecc
Refactor gui for scheduling
almarklein Oct 14, 2024
b45f41b
Move scheduling to separate class
almarklein Oct 15, 2024
0be2d24
add events enum
almarklein Oct 15, 2024
7d19522
cleanup / lint
almarklein Oct 15, 2024
798a9d1
cleanup
almarklein Oct 15, 2024
9ee8cf1
More cleaning
almarklein Oct 15, 2024
baf4785
fix canvas cleanup
almarklein Oct 15, 2024
2d3d926
Cleanup
almarklein Oct 15, 2024
fad1f68
Implement offscreen
almarklein Oct 15, 2024
ed6f81b
Start on wx adjustments. Need to finish on machine with wx :)
almarklein Oct 15, 2024
ebf3286
Implement jupyter canvas - untested
almarklein Oct 15, 2024
dbd105f
small simplification to CanvasInterface
almarklein Oct 16, 2024
0dc5764
Merge branch 'main' into scheduling
almarklein Oct 17, 2024
bcce81a
work
almarklein Oct 17, 2024
dfed135
test event merging
almarklein Oct 17, 2024
69cb310
improve close event being emitted consistently
almarklein Oct 17, 2024
77b7b68
Make/check force_draw work for qt and glfw
almarklein Oct 17, 2024
8ca7800
Use a timer
almarklein Oct 21, 2024
c2a29bd
glfw not draw when minimized
almarklein Oct 21, 2024
f483e71
little cleanup
almarklein Oct 21, 2024
d0f5d5f
Add example for multiple canvases
almarklein Oct 21, 2024
6e16339
Update offscreen.py
almarklein Oct 21, 2024
6e713ff
Add threading example
almarklein Oct 21, 2024
e751438
fix
almarklein Oct 21, 2024
19fce16
fix more
almarklein Oct 21, 2024
7c738b5
More tweaks. Tests pass now
almarklein Oct 22, 2024
2a0b0be
Implement tests for scheduling.
almarklein Oct 22, 2024
85d6a27
enum for update modes
almarklein Oct 22, 2024
d198955
More tests and docs on event order
almarklein Oct 22, 2024
23e1a62
forgot to add new test
almarklein Oct 22, 2024
4e9b483
Change qt behavior for set_title
almarklein Oct 22, 2024
42ae8b7
Improvements
almarklein Oct 24, 2024
d4f6f89
Prevent/detect drawing while drawing
almarklein Oct 24, 2024
4493b49
fix event processing issue leading to re-entrent drawing
almarklein Oct 24, 2024
1a17427
tweak example and ruff
almarklein Oct 24, 2024
2e0a206
Fix jupyter
almarklein Oct 24, 2024
c7e32df
implement wx more (untested)
almarklein Oct 24, 2024
31cf8d4
Fix for glfw on linux
almarklein Oct 24, 2024
8463a54
qt specialized call_soon, and no need to process events on windows
almarklein Oct 24, 2024
a7b024c
Fix wx, and small tweaks to qt
almarklein Oct 24, 2024
fe9f63e
Add comment
almarklein Oct 24, 2024
edf5706
expose loop.run for backwards compat
almarklein Oct 25, 2024
b1096ef
self review, and title logic
almarklein Oct 28, 2024
f13250f
docs
almarklein Oct 28, 2024
d053bc7
update tests
almarklein Oct 28, 2024
4dcc589
disable a subtest on ci, plus fix docs
almarklein Oct 28, 2024
d209a53
fix test
almarklein Oct 28, 2024
3de43de
Merge branch 'main' into scheduling
almarklein Oct 28, 2024
3fab756
Overload qt canvas.update() to request a draw
almarklein Nov 7, 2024
3d22fcb
better
almarklein Nov 7, 2024
fc05ed7
no actually, this is better
almarklein Nov 7, 2024
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ To render to the screen you can use a variety of GUI toolkits:

```py
# The auto backend selects either the glfw, qt or jupyter backend
from wgpu.gui.auto import WgpuCanvas, run, call_later
from wgpu.gui.auto import WgpuCanvas, loop

# Visualizations can be embedded as a widget in a Qt application.
# Import PySide6, PyQt6, PySide2 or PyQt5 before running the line below.
Expand Down
6 changes: 5 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,11 @@ def resolve_crossrefs(text):
cls.__doc__ = docs or None
# Docstring of methods
for method in cls.__dict__.values():
if callable(method) and hasattr(method, "__code__"):
if (
callable(method)
and hasattr(method, "__code__")
and not method.__name__.startswith("_")
):
docs = resolve_crossrefs(method.__doc__)
if (
method.__code__.co_argcount == 1
Expand Down
30 changes: 15 additions & 15 deletions docs/gui.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,20 @@ The Canvas base classes

~WgpuCanvasInterface
~WgpuCanvasBase
~WgpuAutoGui


For each supported GUI toolkit there is a module that implements a ``WgpuCanvas`` class,
which inherits from :class:`WgpuCanvasBase`, providing a common API.
The GLFW, Qt, and Jupyter backends also inherit from :class:`WgpuAutoGui` to include
support for events (interactivity). In the next sections we demonstrates the different
canvas classes that you can use.


Events
------

To implement interaction with a ``WgpuCanvas``, use the :func:`WgpuCanvasBase.add_event_handler()` method.
Events come in the following flavours:

.. autoclass:: WgpuEventType
:members:


The auto GUI backend
Expand All @@ -39,22 +45,16 @@ across different machines and environments. Using ``wgpu.gui.auto`` selects a
suitable backend depending on the environment and more. See
:ref:`interactive_use` for details.

To implement interaction, the ``canvas`` has a :func:`WgpuAutoGui.handle_event()` method
that can be overloaded. Alternatively you can use it's :func:`WgpuAutoGui.add_event_handler()`
method. See the `event spec <https://jupyter-rfb.readthedocs.io/en/stable/events.html>`_
for details about the event objects.

Also see the `triangle auto <https://github.com/pygfx/wgpu-py/blob/main/examples/triangle_auto.py>`_
and `cube <https://github.com/pygfx/wgpu-py/blob/main/examples/cube.py>`_ examples that demonstrate the auto gui.
Also see the e.g. the `gui_auto.py <https://github.com/pygfx/wgpu-py/blob/main/examples/gui_auto.py>`_ example.

.. code-block:: py

from wgpu.gui.auto import WgpuCanvas, run, call_later
from wgpu.gui.auto import WgpuCanvas, loop

canvas = WgpuCanvas(title="Example")
canvas.request_draw(your_draw_function)

run()
loop.run()


Support for GLFW
Expand All @@ -66,12 +66,12 @@ but you can replace ``from wgpu.gui.auto`` with ``from wgpu.gui.glfw`` to force

.. code-block:: py

from wgpu.gui.glfw import WgpuCanvas, run, call_later
from wgpu.gui.glfw import WgpuCanvas, loop

canvas = WgpuCanvas(title="Example")
canvas.request_draw(your_draw_function)

run()
loop.run()


Support for Qt
Expand Down
2 changes: 1 addition & 1 deletion docs/guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ GUI toolkits are supported, see the :doc:`gui`. In general, it's easiest to let

.. code-block:: py

from wgpu.gui.auto import WgpuCanvas, run
from wgpu.gui.auto import WgpuCanvas

canvas = WgpuCanvas(title="a wgpu example")

Expand Down
4 changes: 2 additions & 2 deletions examples/gui_auto.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

# test_example = true

from wgpu.gui.auto import WgpuCanvas, run
from wgpu.gui.auto import WgpuCanvas, loop

from triangle import setup_drawing_sync
# from cube import setup_drawing_sync
Expand All @@ -21,4 +21,4 @@ def animate():


if __name__ == "__main__":
run()
loop.run()
40 changes: 36 additions & 4 deletions examples/gui_events.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,50 @@
"""
A simple example to demonstrate events.

Also serves as a test-app for the canvas backends.
"""

from wgpu.gui.auto import WgpuCanvas, run
import time

from wgpu.gui.auto import WgpuCanvas, loop

from cube import setup_drawing_sync


canvas = WgpuCanvas(size=(640, 480), title="wgpu events")
canvas = WgpuCanvas(
size=(640, 480),
title="wgpu events",
max_fps=10,
update_mode="continuous",
present_method="",
)


draw_frame = setup_drawing_sync(canvas)
canvas.request_draw(lambda: (draw_frame(), canvas.request_draw()))


@canvas.add_event_handler("*")
def process_event(event):
if event["event_type"] != "pointer_move":
if event["event_type"] not in ["pointer_move", "before_draw", "animate"]:
print(event)

if event["event_type"] == "key_down":
if event["key"] == "Escape":
canvas.close()
elif event["key"] == " ":
etime = time.time() + 2
i = 0
while time.time() < etime:
i += 1
canvas.force_draw()
print(f"force-drawed {i} frames in 2s.")
elif event["event_type"] == "close":
# Should see this exactly once, either when pressing escape, or
# when pressing the window close button.
print("Close detected!")
assert canvas.is_closed()


if __name__ == "__main__":
run()
loop.run()
6 changes: 3 additions & 3 deletions examples/gui_glfw.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

# run_example = false

from wgpu.gui.glfw import WgpuCanvas, run
from wgpu.gui.glfw import WgpuCanvas

from triangle import setup_drawing_sync
# from cube import setup_drawing_sync
Expand All @@ -17,8 +17,8 @@
@canvas.request_draw
def animate():
draw_frame()
canvas.request_draw()
# canvas.request_draw()


if __name__ == "__main__":
run()
canvas.loop.run()
23 changes: 23 additions & 0 deletions examples/gui_multiple.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""
Run triangle and cube examples two canvases.
"""

# test_example = true

from wgpu.gui.auto import WgpuCanvas, loop

from triangle import setup_drawing_sync as setup_drawing_sync_triangle
from cube import setup_drawing_sync as setup_drawing_sync_cube


canvas1 = WgpuCanvas(title=f"Triangle example on {WgpuCanvas.__name__}")
draw_frame1 = setup_drawing_sync_triangle(canvas1)
canvas1.request_draw(draw_frame1)

canvas2 = WgpuCanvas(title=f"Cube example on {WgpuCanvas.__name__}")
draw_frame2 = setup_drawing_sync_cube(canvas2)
canvas2.request_draw(draw_frame2)


if __name__ == "__main__":
loop.run()
7 changes: 5 additions & 2 deletions examples/gui_qt.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,18 @@


app = QtWidgets.QApplication([])
canvas = WgpuCanvas(title=f"Triangle example on {WgpuCanvas.__name__}")
canvas = WgpuCanvas(
title=f"Triangle example on {WgpuCanvas.__name__}",
# present_method="image"
)

draw_frame = setup_drawing_sync(canvas)


@canvas.request_draw
def animate():
draw_frame()
canvas.request_draw()
# canvas.request_draw()


# Enter Qt event loop (compatible with qt5/qt6)
Expand Down
52 changes: 52 additions & 0 deletions examples/gui_threading.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""
Example that renders frames in a separate thread.

This uses an offscreen canvas, the result is only used to print the
frame shape. But one can see how one can e.g. render a movie this way.

Threaded rendering using a real GUI is not supported right now, since
this is tricky to do with both Qt and glfw. Plus in general its a bad
idea to run your UI in anything other than the main thread. In other
words, you should probably only use threaded rendering for off-screen
stuff.

"""

# test_example = true

import time
import threading

from wgpu.gui.offscreen import WgpuCanvas

from cube import setup_drawing_sync


# create canvas
canvas = WgpuCanvas()
draw_frame = setup_drawing_sync(canvas)


def main():
frame_count = 0
canvas.request_draw(draw_frame)

while not canvas.is_closed():
image = canvas.draw()
frame_count += 1
print(f"Rendered {frame_count} frames, last shape is {image.shape}")


if __name__ == "__main__":
t1 = threading.Thread(target=main)
t1.start()

# In the main thread, we wait a little
time.sleep(1)

# ... then change the canvas size, and wait some more
canvas.set_logical_size(200, 200)
time.sleep(1)

# Close the canvas to stop the tread
canvas.close()
83 changes: 52 additions & 31 deletions examples/wgpu-examples.ipynb

Large diffs are not rendered by default.

6 changes: 2 additions & 4 deletions tests/renderutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ def render_to_screen(
):
"""Render to a window on screen, for debugging purposes."""
import glfw
from wgpu.gui.glfw import WgpuCanvas, update_glfw_canvasses
from wgpu.gui.glfw import WgpuCanvas, loop

vbos = vbos or []
vbo_views = vbo_views or []
Expand Down Expand Up @@ -327,6 +327,4 @@ def draw_frame():
canvas.request_draw(draw_frame)

# Enter main loop
while update_glfw_canvasses():
glfw.poll_events()
glfw.terminate()
loop.run()
66 changes: 0 additions & 66 deletions tests/test_gui_auto_offscreen.py

This file was deleted.

Loading
Loading