Skip to content

Commit 379599c

Browse files
authored
Refactor canvas context to allow presenting as image (#586)
* Refactor canvas context to allow presening as image * some cleanup * codegen * Fix flicker * cleaner * fix error on exit * looked into qt image draw performance a bit * Fix/workaround for Qt on Wayland * Fix glfw * Give wx same treatment as qt * Show warning when using offscreen rendering in qt and wx * docs * Update offscreen canvases. No more need for WgpuOfscreenCanvasBase * Update notebook * docs * minor tweaks * update tests * Fix memtest * remove debug text overlay * Bit of docstrings * explaine purpose of canvas context * Rename surface_info -> present_info * draw_to_screen -> present_method * flake
1 parent 8c077c4 commit 379599c

19 files changed

+796
-533
lines changed

docs/gui.rst

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ The Canvas base classes
2121
~WgpuCanvasInterface
2222
~WgpuCanvasBase
2323
~WgpuAutoGui
24-
~WgpuOffscreenCanvasBase
2524

2625

2726
For each supported GUI toolkit there is a module that implements a ``WgpuCanvas`` class,

examples/triangle_glfw_direct.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import glfw
1515

1616
from wgpu.backends.wgpu_native import GPUCanvasContext
17-
from wgpu.gui.glfw import get_surface_info, get_physical_size
17+
from wgpu.gui.glfw import get_glfw_present_info, get_physical_size
1818
from wgpu.utils.device import get_default_device
1919

2020

@@ -29,9 +29,9 @@ class GlfwCanvas:
2929
def __init__(self, window):
3030
self._window = window
3131

32-
def get_surface_info(self):
32+
def get_present_info(self):
3333
"""get window and display id, includes some triage to deal with OS differences"""
34-
return get_surface_info(self._window)
34+
return get_glfw_present_info(self._window)
3535

3636
def get_physical_size(self):
3737
"""get framebuffer size in integer pixels"""

examples/triangle_subprocess.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
app = QtWidgets.QApplication([])
3232
canvas = WgpuCanvas(title="wgpu triangle in Qt subprocess")
3333
34-
print(json.dumps(canvas.get_surface_info()))
34+
print(json.dumps(canvas.get_present_info()))
3535
print(canvas.get_physical_size())
3636
sys.stdout.flush()
3737
@@ -42,15 +42,15 @@
4242
class ProxyCanvas(WgpuCanvasBase):
4343
def __init__(self):
4444
super().__init__()
45-
self._surface_info = json.loads(p.stdout.readline().decode())
45+
self._present_info = json.loads(p.stdout.readline().decode())
4646
self._psize = tuple(
4747
int(x) for x in p.stdout.readline().decode().strip().strip("()").split(",")
4848
)
4949
print(self._psize)
5050
time.sleep(0.2)
5151

52-
def get_surface_info(self):
53-
return self._surface_info
52+
def get_present_info(self):
53+
return self._present_info
5454

5555
def get_physical_size(self):
5656
return self._psize

examples/wgpu-examples.ipynb

Lines changed: 109 additions & 20 deletions
Large diffs are not rendered by default.

tests/test_gui_base.py

Lines changed: 18 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import numpy as np
1010
import wgpu.gui # noqa
1111
from testutils import run_tests, can_use_wgpu_lib, is_pypy
12-
from pytest import mark
12+
from pytest import mark, raises
1313

1414

1515
class TheTestCanvas(wgpu.gui.WgpuCanvasBase):
@@ -37,10 +37,10 @@ def spam_method(self):
3737
def test_base_canvas_context():
3838
assert not issubclass(wgpu.gui.WgpuCanvasInterface, wgpu.GPUCanvasContext)
3939
assert hasattr(wgpu.gui.WgpuCanvasInterface, "get_context")
40-
# Provides good default already
4140
canvas = wgpu.gui.WgpuCanvasInterface()
42-
ctx = wgpu.GPUCanvasContext(canvas)
43-
assert ctx.get_preferred_format(None) == "bgra8unorm-srgb"
41+
# Cannot instantiate, because get_present_info is not implemented
42+
with raises(NotImplementedError):
43+
wgpu.GPUCanvasContext(canvas)
4444

4545

4646
def test_canvas_logging(caplog):
@@ -80,12 +80,22 @@ def test_canvas_logging(caplog):
8080
assert text.count("division by zero") == 4
8181

8282

83-
class MyOffscreenCanvas(wgpu.gui.WgpuOffscreenCanvasBase):
83+
class MyOffscreenCanvas(wgpu.gui.WgpuCanvasBase):
8484
def __init__(self):
8585
super().__init__()
86-
self.textures = []
86+
self.frame_count = 0
8787
self.physical_size = 100, 100
8888

89+
def get_present_info(self):
90+
return {
91+
"method": "image",
92+
"formats": ["rgba8unorm-srgb"],
93+
}
94+
95+
def present_image(self, image, **kwargs):
96+
self.frame_count += 1
97+
self.array = np.frombuffer(image, np.uint8).reshape(image.shape)
98+
8999
def get_pixel_ratio(self):
90100
return 1
91101

@@ -99,26 +109,6 @@ def _request_draw(self):
99109
# Note: this would normally schedule a call in a later event loop iteration
100110
self._draw_frame_and_present()
101111

102-
def present(self, texture):
103-
self.textures.append(texture)
104-
device = texture._device
105-
size = texture.size
106-
bytes_per_pixel = 4
107-
data = device.queue.read_texture(
108-
{
109-
"texture": texture,
110-
"mip_level": 0,
111-
"origin": (0, 0, 0),
112-
},
113-
{
114-
"offset": 0,
115-
"bytes_per_row": bytes_per_pixel * size[0],
116-
"rows_per_image": size[1],
117-
},
118-
size,
119-
)
120-
self.array = np.frombuffer(data, np.uint8).reshape(size[1], size[0], 4)
121-
122112

123113
@mark.skipif(not can_use_wgpu_lib, reason="Needs wgpu lib")
124114
def test_run_bare_canvas():
@@ -181,7 +171,7 @@ def draw_frame():
181171
render_pass.end()
182172
device.queue.submit([command_encoder.finish()])
183173

184-
assert len(canvas.textures) == 0
174+
assert canvas.frame_count == 0
185175

186176
# Draw 1
187177
canvas.request_draw(draw_frame)
@@ -214,8 +204,7 @@ def draw_frame():
214204
assert np.all(canvas.array[:, :, 1] == 255)
215205

216206
# We now have four unique texture objects
217-
assert len(canvas.textures) == 4
218-
assert len(set(canvas.textures)) == 4
207+
assert canvas.frame_count == 4
219208

220209

221210
def test_autogui_mixin():

tests/test_gui_glfw.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ def __init__(self):
171171
self.window = glfw.create_window(300, 200, "canvas", None, None)
172172
self._present_context = None
173173

174-
def get_surface_info(self):
174+
def get_present_info(self):
175175
if sys.platform.startswith("win"):
176176
return {
177177
"platform": "windows",

tests_mem/test_gui.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def make_draw_func_for_canvas(canvas):
2323
so that we can really present something to a canvas being tested.
2424
"""
2525
ctx = canvas.get_context()
26-
ctx.configure(device=DEVICE, format="bgra8unorm-srgb")
26+
ctx.configure(device=DEVICE, format=None)
2727

2828
def draw():
2929
ctx = canvas.get_context()

tests_mem/test_gui_qt.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ def test_release_canvas_context(n):
3636
if app is None:
3737
app = PySide6.QtWidgets.QApplication([""])
3838

39-
yield {}
39+
yield {
40+
"ignore": {"CommandBuffer"},
41+
}
4042

4143
canvases = weakref.WeakSet()
4244

0 commit comments

Comments
 (0)