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

Manual GLFW integration #480

Merged
merged 9 commits into from
Apr 11, 2024
Merged

Manual GLFW integration #480

merged 9 commits into from
Apr 11, 2024

Conversation

Korijn
Copy link
Collaborator

@Korijn Korijn commented Mar 24, 2024

Direct integration of glfw and wgpu-py without using the wgpu.gui Canvas abstraction/class hierarchy.

This is a demonstration for hardcore users that need total low-level control.

Changes:

  • Adds examples/triangle_glfw_direct.py
  • Refactors examples/triangle.py and wgpu/gui/glfw.py to make some utility code more easily accessible.

Background:

  • I was thinking about Update propagation pygfx#495 and decided to see if I can implement a closed loop render mode with wgpu-py and glfw.
  • Turns out it was pretty challenging so this example should support users looking to do the same, for example Use own created glfw window. #430
  • The main thing making this complicated is that instead of providing direct bindings to webgpu-native, we directly map the webgpu-native API to the IDL-standardized webgpu API
  • We could consider "splitting" the webgpu-native backend in 2 parts, one being autogenerated direct python bindings, and one being the translation layer to the IDL-standardized webgpu API, I documented this idea here: Idea: auto-generated bindings for webgpu-native #487

Note: I guess another question is if this can be done with a pygfx renderer.

image

@almarklein
Copy link
Member

Yes, the WgpuCanvasInterface class defines what is needed. It can be used as an interface (as the name suggests)- it does not have to be subclassed.

class WgpuCanvasInterface:
"""The minimal interface to be a valid canvas.
Any object that implements these methods is a canvas that wgpu can work with.
The object does not even have to derive from this class.
In most cases it's more convenient to subclass :class:`WgpuCanvasBase <wgpu.gui.WgpuCanvasBase>`.
"""
def __init__(self, *args, **kwargs):
# The args/kwargs are there because we may be mixed with e.g. a Qt widget
super().__init__(*args, **kwargs)
self._canvas_context = None
def get_surface_info(self):
"""Get information about the native window / surface.
This is used to obtain a surface id, so that wgpu can render to the
region of the screen occupied by the canvas. Should return None for
offscreen canvases. Otherwise, this should return a dict with a "window"
field. On Linux the dict should contain more fields, see the existing
implementations for reference.
"""
return None
def get_physical_size(self):
"""Get the physical size of the canvas in integer pixels."""
raise NotImplementedError()
def get_context(self, kind="webgpu"):
"""Get the ``GPUCanvasContext`` object corresponding to this canvas.
The context is used to obtain a texture to render to, and to
present that texture to the canvas. This class provides a
default implementation to get the appropriate context.
The ``kind`` argument is a remnant from the WebGPU spec and
must always be "webgpu".
"""
# Note that this function is analog to HtmlCanvas.getContext(), except
# here the only valid arg is 'webgpu', which is also made the default.
assert kind == "webgpu"
if self._canvas_context is None:
# Get the active wgpu backend module
backend_module = sys.modules["wgpu"].gpu.__module__
if backend_module == "wgpu._classes":
raise RuntimeError(
"A backend must be selected (e.g. with request_adapter()) before canvas.get_context() can be called."
)
# Instantiate the context
CC = sys.modules[backend_module].GPUCanvasContext # noqa: N806
self._canvas_context = CC(self)
return self._canvas_context

We also have a test to make sure this works:

def test_glfw_canvas_render_custom_canvas():
"""Render an orange square ... in a glfw window. But not using WgpuCanvas.
This helps make sure that WgpuCanvasInterface is indeed the minimal
required canvas API.
"""

The _vsync attribute being checked is something we should probably refactor. Perhaps make it part of the WgpuCanvasInterface too.

@almarklein
Copy link
Member

To respond to your questions:

I can't render a triangle to the screen using the wgpu_native backend with glfw, unless I re-implement the Canvas API.

Yes, some parts. Perhaps we can improve our docs on how this works. The example in this PR will certainly help.

That means I am also bound to its design choices in terms of event loop and abstraction.

I don't think this is the case.

Is our Canvas abstraction and it's API based on any public specs such as webgpu or something by w3c? I don't think so right?

No.

@Korijn
Copy link
Collaborator Author

Korijn commented Mar 25, 2024

I think I can see what your idea is now after having had some more time to play with it. I could implement a minimal Canvas class that exposes only the needed methods, but keep control flow out of it. That's cool. I'll try to do that in this branch next.

@almarklein
Copy link
Member

I think I can see what your idea is now after having had some more time to play with it.

Definitely room to improve the docs in this regard then 😅

@Korijn
Copy link
Collaborator Author

Korijn commented Mar 25, 2024

Well, I think it's just unexpected that the Canvas interface from the gui package defines an API that you must use to work with the rust backend.

In essence that means it's actually a type defined by the rust backend, that is implemented by the gui package. That's the unclear part.

@Korijn Korijn marked this pull request as ready for review March 29, 2024 15:13
@Korijn Korijn requested a review from almarklein March 29, 2024 15:13
Copy link
Member

@almarklein almarklein left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we can get it much more minimal than this.

examples/triangle_glfw_direct.py Show resolved Hide resolved
@Korijn Korijn changed the title Update modes - do not force a programming paradigm Manual GLFW integration Apr 3, 2024
@Korijn Korijn enabled auto-merge (squash) April 11, 2024 09:05
@Korijn Korijn merged commit 72094b0 into main Apr 11, 2024
19 checks passed
@Korijn Korijn deleted the manual-glfw branch April 11, 2024 09:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants