Skip to content

Commit

Permalink
Merge branch 'main' into cleanup_doc
Browse files Browse the repository at this point in the history
  • Loading branch information
Korijn authored Nov 24, 2023
2 parents 8b0c1c0 + 8f2f998 commit 33b7546
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 145 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Changed:
* `GPUCommandEncoder.begin_render_pass()` binds the lifetime of passed texture views to
the returned render pass object to prevent premature destruction when no reference to
a texture view is kept.
* Renamed ``wgpu.gui.WgpuOffscreenCanvas` to `WgpuOffscreenCanvasBase`.

Fixed:

Expand Down
2 changes: 1 addition & 1 deletion docs/gui.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ The Canvas base classes
~WgpuCanvasInterface
~WgpuCanvasBase
~WgpuAutoGui
~WgpuOffscreenCanvas
~WgpuOffscreenCanvasBase


For each supported GUI toolkit there is a module that implements a ``WgpuCanvas`` class,
Expand Down
2 changes: 1 addition & 1 deletion tests/test_gui_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def test_canvas_logging(caplog):
assert text.count("division by zero") == 4


class MyOffscreenCanvas(wgpu.gui.WgpuOffscreenCanvas):
class MyOffscreenCanvas(wgpu.gui.WgpuOffscreenCanvasBase):
def __init__(self):
super().__init__()
self.textures = []
Expand Down
4 changes: 2 additions & 2 deletions wgpu/gui/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
"""

from .base import WgpuCanvasInterface, WgpuCanvasBase, WgpuAutoGui # noqa: F401
from ._offscreen import WgpuOffscreenCanvas # noqa: F401
from .offscreen import WgpuOffscreenCanvasBase # noqa: F401

__all__ = [
"WgpuCanvasInterface",
"WgpuCanvasBase",
"WgpuAutoGui",
"WgpuOffscreenCanvas",
"WgpuOffscreenCanvasBase",
]
133 changes: 0 additions & 133 deletions wgpu/gui/_offscreen.py

This file was deleted.

8 changes: 4 additions & 4 deletions wgpu/gui/jupyter.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import weakref
import asyncio

from ._offscreen import WgpuOffscreenCanvas
from .offscreen import WgpuOffscreenCanvasBase
from .base import WgpuAutoGui

import numpy as np
Expand All @@ -18,7 +18,7 @@
pending_jupyter_canvases = []


class JupyterWgpuCanvas(WgpuAutoGui, WgpuOffscreenCanvas, RemoteFrameBuffer):
class JupyterWgpuCanvas(WgpuAutoGui, WgpuOffscreenCanvasBase, RemoteFrameBuffer):
"""An ipywidgets widget providing a wgpu canvas. Needs the jupyter_rfb library."""

def __init__(self, *, size=None, title=None, **kwargs):
Expand Down Expand Up @@ -88,10 +88,10 @@ def _request_draw(self):
self._request_draw_timer_running = True
call_later(self._get_draw_wait_time(), RemoteFrameBuffer.request_draw, self)

# Implementation needed for WgpuOffscreenCanvas
# Implementation needed for WgpuOffscreenCanvasBase

def present(self, texture):
# This gets called at the end of a draw pass via _offscreen.GPUCanvasContext
# This gets called at the end of a draw pass via offscreen.GPUCanvasContext
device = texture._device
size = texture.size
bytes_per_pixel = 4
Expand Down
143 changes: 139 additions & 4 deletions wgpu/gui/offscreen.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,145 @@
import time

from ._offscreen import WgpuOffscreenCanvas
from .base import WgpuAutoGui
from .. import classes, flags
from .base import WgpuCanvasBase, WgpuAutoGui


class GPUCanvasContext(classes.GPUCanvasContext):
"""GPUCanvasContext subclass for rendering to an offscreen texture."""

# In this context implementation, we keep a ref to the texture, to keep
# it alive until at least until present() is called, and to be able to
# pass it to the canvas' present() method. Thereafter, the texture
# reference is removed. If there are no more references to it, it will
# be cleaned up. But if the offscreen canvas uses it for something,
# it'll simply stay alive longer.

def __init__(self, canvas):
super().__init__(canvas)
self._config = None
self._texture = None

def configure(
self,
*,
device,
format,
usage=flags.TextureUsage.RENDER_ATTACHMENT | flags.TextureUsage.COPY_SRC,
view_formats=[],
color_space="srgb",
alpha_mode="opaque"
):
if format is None:
format = self.get_preferred_format(device.adapter)
self._config = {
"device": device,
"format": format,
"usage": usage,
"width": 0,
"height": 0,
# "view_formats": xx,
# "color_space": xx,
# "alpha_mode": xx,
}

def unconfigure(self):
self._texture = None
self._config = None

def get_current_texture(self):
if not self._config:
raise RuntimeError(
"Canvas context must be configured before calling get_current_texture()."
)

width, height = self._get_canvas().get_physical_size()
width, height = max(width, 1), max(height, 1)

self._texture = self._config["device"].create_texture(
label="presentation-context",
size=(width, height, 1),
format=self._config["format"],
usage=self._config["usage"],
)
return self._texture

def present(self):
if not self._texture:
msg = "present() is called without a preceeding call to "
msg += "get_current_texture(). Note that present() is usually "
msg += "called automatically after the draw function returns."
raise RuntimeError(msg)
else:
texture = self._texture
self._texture = None
return self._get_canvas().present(texture)

def get_preferred_format(self, adapter):
canvas = self._get_canvas()
if canvas:
return canvas.get_preferred_format()
else:
return "rgba8unorm-srgb"


class WgpuOffscreenCanvasBase(WgpuCanvasBase):
"""Base class for off-screen canvases.
It provides a custom context that renders to a texture instead of
a surface/screen. On each draw the resulting image is passes as a
texture to the ``present()`` method. Subclasses should (at least)
implement ``present()``
"""

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

def get_window_id(self):
"""This canvas does not correspond to an on-screen window."""
return None

def get_context(self, kind="webgpu"):
"""Get the GPUCanvasContext object to obtain a texture to render to."""
# Normally this creates a GPUCanvasContext object provided by
# the backend (e.g. wgpu-native), but here we use our own context.
assert kind == "webgpu"
if self._canvas_context is None:
self._canvas_context = GPUCanvasContext(self)
return self._canvas_context

def present(self, texture):
"""Method that gets called at the end of each draw event.
The rendered image is represented by the texture argument.
Subclasses should overload this method and use the texture to
process the rendered image.
The texture is a new object at each draw, but is not explicitly
destroyed, so it can be used e.g. as a texture binding (subject
to set TextureUsage).
"""
# Notes: Creating a new texture object for each draw is
# consistent with how real canvas contexts work, plus it avoids
# confusion of re-using the same texture except when the canvas
# changes size. For use-cases where you do want to render to the
# same texture one does not need the canvas API. E.g. in pygfx
# the renderer can also work with a target that is a (fixed
# size) texture.
pass

def get_preferred_format(self):
"""Get the preferred format for this canvas.
This method can be overloaded to control the used texture
format. The default is "rgba8unorm-srgb".
"""
# Use rgba because that order is more common for processing and storage.
# Use srgb because that's what how colors are usually expected to be.
# Use 8unorm because 8bit is enough (when using srgb).
return "rgba8unorm-srgb"


class WgpuManualOffscreenCanvas(WgpuAutoGui, WgpuOffscreenCanvas):
class WgpuManualOffscreenCanvas(WgpuAutoGui, WgpuOffscreenCanvasBase):
"""An offscreen canvas intended for manual use.
Call the ``.draw()`` method to perform a draw and get the result.
Expand Down Expand Up @@ -42,7 +177,7 @@ def _request_draw(self):
pass

def present(self, texture):
# This gets called at the end of a draw pass via _offscreen.GPUCanvasContext
# This gets called at the end of a draw pass via GPUCanvasContext
device = texture._device
size = texture.size
bytes_per_pixel = 4
Expand Down

0 comments on commit 33b7546

Please sign in to comment.