Skip to content

Commit

Permalink
Refactor for new get_context
Browse files Browse the repository at this point in the history
  • Loading branch information
almarklein committed Nov 14, 2024
1 parent 437921d commit dd3fb50
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 43 deletions.
11 changes: 11 additions & 0 deletions wgpu/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,14 @@

# The API entrypoint, from wgpu.classes - gets replaced when a backend loads.
gpu = GPU() # noqa: F405


def rendercanvas_context_hook(canvas, present_methods):
import sys

backend_module = gpu.__module__
if backend_module in ("", "wgpu._classes"):
raise RuntimeError(
"A backend must be selected (e.g. with wgpu.gpu.request_adapter()) before canvas.get_context() can be called."
)
return sys.modules[backend_module].GPUCanvasContext(canvas, present_methods)
64 changes: 25 additions & 39 deletions wgpu/_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ class GPUCanvasContext:

_ot = object_tracker

def __init__(self, canvas):
def __init__(self, canvas, present_methods):
self._ot.increase(self.__class__.__name__)
self._canvas_ref = weakref.ref(canvas)

Expand All @@ -227,12 +227,9 @@ def __init__(self, canvas):
# The last used texture
self._texture = None

# The configuration from the canvas, obtained with canvas.get_present_info()
self._present_info = canvas.get_present_info()
if self._present_info.get("method", None) not in ("screen", "image"):
raise RuntimeError(
"canvas.get_present_info() must produce a dict with a field 'method' that is either 'screen' or 'image'."
)
# Determine the present method
self._present_methods = present_methods
self._present_method = "screen" if "screen" in present_methods else "bitmap"

def _get_canvas(self):
"""Getter method for internal use."""
Expand All @@ -248,7 +245,7 @@ def _get_capabilities(self, adapter):
"""Get dict of capabilities and cache the result."""
if self._capabilities is None:
self._capabilities = {}
if self._present_info["method"] == "screen":
if self._present_method == "screen":
# Query capabilities from the surface
self._capabilities.update(self._get_capabilities_screen(adapter))
else:
Expand All @@ -258,10 +255,6 @@ def _get_capabilities(self, adapter):
"usages": 0xFF,
"alpha_modes": [enums.CanvasAlphaMode.opaque],
}
# If capabilities were provided via surface info, overload them!
for key in ["formats", "alpha_modes"]:
if key in self._present_info:
self._capabilities[key] = self._present_info[key]
# Derived defaults
if "view_formats" not in self._capabilities:
self._capabilities["view_formats"] = self._capabilities["formats"]
Expand Down Expand Up @@ -311,7 +304,6 @@ def configure(
will have on the content of textures returned by ``get_current_texture()``
when read, displayed, or used as an image source. Default "opaque".
"""

# Check types

if not isinstance(device, GPUDevice):
Expand Down Expand Up @@ -370,7 +362,7 @@ def configure(
"alpha_mode": alpha_mode,
}

if self._present_info["method"] == "screen":
if self._present_method == "screen":
self._configure_screen(**self._config)

def _configure_screen(
Expand All @@ -391,7 +383,7 @@ def unconfigure(self):
"""Removes the presentation context configuration.
Destroys any textures produced while configured.
"""
if self._present_info["method"] == "screen":
if self._present_method == "screen":
self._unconfigure_screen()
self._config = None
self._drop_texture()
Expand All @@ -414,14 +406,14 @@ def get_current_texture(self):
# Right now we return the existing texture, so user can retrieve it in different render passes that write to the same frame.

if self._texture is None:
if self._present_info["method"] == "screen":
if self._present_method == "screen":
self._texture = self._create_texture_screen()
else:
self._texture = self._create_texture_image()
self._texture = self._create_texture_bitmap()

return self._texture

def _create_texture_image(self):
def _create_texture_bitmap(self):
canvas = self._get_canvas()
width, height = canvas.get_physical_size()
width, height = max(width, 1), max(height, 1)
Expand All @@ -443,33 +435,29 @@ def _drop_texture(self):
self._texture._release() # not destroy, because it may be in use.
self._texture = None

@apidiff.add("Present method is exposed")
@apidiff.add("The present method is used by the canvas")
def present(self):
"""Present what has been drawn to the current texture, by compositing it
to the canvas. Note that a canvas based on `gui.WgpuCanvasBase` will call this
method automatically at the end of each draw event.
"""Hook for the canvas to present the rendered result.
Present what has been drawn to the current texture, by compositing it to the
canvas. Don't call this yourself; this is called automatically by the canvas.
"""
# todo: can we remove this present() method?

if not self._texture:
# This can happen when a user somehow forgot to call
# get_current_texture(). But then what was this person rendering to
# then? The thing is that this also happens when there is an
# exception in the draw function before the call to
# get_current_texture(). In this scenario our warning may
# add confusion, so provide context and make it a debug level warning.
msg = "Warning in present(): No texture to present, missing call to get_current_texture()?"
logger.debug(msg)
return

if self._present_info["method"] == "screen":
result = {"method": "skip"}
elif self._present_method == "screen":
self._present_screen()
result = {"method": "screen"}
elif self._present_method == "bitmap":
bitmap = self._present_bitmap()
result = {"method": "bitmap", "format": "rgba-u8", "data": bitmap}
else:
self._present_image()
result = {"method": "fail", "message": "incompatible present methods"}

self._drop_texture()
return result

def _present_image(self):
def _present_bitmap(self):
texture = self._texture
device = texture._device

Expand Down Expand Up @@ -505,9 +493,7 @@ def _present_image(self):

# Represent as memory object to avoid numpy dependency
# Equivalent: np.frombuffer(data, np.uint8).reshape(size[1], size[0], nchannels)
data = data.cast("B", (size[1], size[0], nchannels))

self._get_canvas().present_image(data, format=format)
return data.cast("B", (size[1], size[0], nchannels))

def _present_screen(self):
raise NotImplementedError()
Expand Down
8 changes: 4 additions & 4 deletions wgpu/backends/wgpu_native/_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -521,13 +521,13 @@ class GPUCanvasContext(classes.GPUCanvasContext):

_surface_id = ffi.NULL

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

# Obtain the surface id. The lifetime is of the surface is bound
# to the lifetime of this context object.
if self._present_info["method"] == "screen":
self._surface_id = get_surface_id_from_info(self._present_info)
if self._present_method == "screen":
self._surface_id = get_surface_id_from_info(self._present_methods["screen"])
else: # method == "image"
self._surface_id = ffi.NULL

Expand Down

0 comments on commit dd3fb50

Please sign in to comment.