From 72094b05fcc8e945f8783dbed9110c77e7a4b32c Mon Sep 17 00:00:00 2001 From: Korijn van Golen Date: Thu, 11 Apr 2024 11:07:20 +0200 Subject: [PATCH] Manual GLFW integration (#480) * work on manual glfw closed loop * working glfw example * lint with newer versions of flake8 and black * restore test file * add comments * convert some comments to docstrings * rename file --- examples/triangle.py | 15 ++++--- examples/triangle_glfw_direct.py | 77 ++++++++++++++++++++++++++++++++ wgpu/gui/glfw.py | 62 ++++++++++++++----------- 3 files changed, 122 insertions(+), 32 deletions(-) create mode 100644 examples/triangle_glfw_direct.py diff --git a/examples/triangle.py b/examples/triangle.py index 80a14de9..9a203ba9 100644 --- a/examples/triangle.py +++ b/examples/triangle.py @@ -74,15 +74,15 @@ async def main_async(canvas): return _main(canvas, device) -def _main(canvas, device): +def setup_draw(context, device): + """Setup context and device for drawing a triangle and return draw callback""" shader = device.create_shader_module(code=shader_source) # No bind group and layout, we should not create empty ones. pipeline_layout = device.create_pipeline_layout(bind_group_layouts=[]) - present_context = canvas.get_context() - render_texture_format = present_context.get_preferred_format(device.adapter) - present_context.configure(device=device, format=render_texture_format) + render_texture_format = context.get_preferred_format(device.adapter) + context.configure(device=device, format=render_texture_format) render_pipeline = device.create_render_pipeline( layout=pipeline_layout, @@ -122,7 +122,7 @@ def _main(canvas, device): ) def draw_frame(): - current_texture = present_context.get_current_texture() + current_texture = context.get_current_texture() command_encoder = device.create_command_encoder() render_pass = command_encoder.begin_render_pass( @@ -143,6 +143,11 @@ def draw_frame(): render_pass.end() device.queue.submit([command_encoder.finish()]) + return draw_frame + + +def _main(canvas, device): + draw_frame = setup_draw(canvas.get_context(), device) canvas.request_draw(draw_frame) return device diff --git a/examples/triangle_glfw_direct.py b/examples/triangle_glfw_direct.py new file mode 100644 index 00000000..7336eaf3 --- /dev/null +++ b/examples/triangle_glfw_direct.py @@ -0,0 +1,77 @@ +""" +Direct integration of glfw and wgpu-py without using the +wgpu.gui Canvas abstraction/class hierarchy. + +Demonstration for hardcore users that need total low-level +control. + +# run_example = false +""" + +import sys +from pathlib import Path + +import glfw + +from wgpu.backends.wgpu_native import GPUCanvasContext +from wgpu.gui.glfw import get_surface_info, get_physical_size +from wgpu.utils.device import get_default_device + + +sys.path.insert(0, str(Path(__file__).parent)) + +from triangle import setup_draw # noqa: E402 + + +class GlfwCanvas: + """Minimal canvas interface implementation to support GPUCanvasContext""" + + def __init__(self, window): + self._window = window + + def get_surface_info(self): + """get window and display id, includes some triage to deal with OS differences""" + return get_surface_info(self._window) + + def get_physical_size(self): + """get framebuffer size in integer pixels""" + return get_physical_size(self._window) + + +def main(): + # get the gpu device/adapter combo + device = get_default_device() + + # create a window with glfw + glfw.init() + # disable automatic API selection, we are not using opengl + glfw.window_hint(glfw.CLIENT_API, glfw.NO_API) + glfw.window_hint(glfw.RESIZABLE, True) + window = glfw.create_window(640, 480, "glfw window", None, None) + + # create a WGPU context + canvas = GlfwCanvas(window) + context = GPUCanvasContext(canvas) + + # drawing logic + draw_frame = setup_draw(context, device) + + # render loop + while True: + # draw a frame + draw_frame() + # present the frame to the screen + context.present() + # process inputs + glfw.poll_events() + + # break on close + if glfw.window_should_close(window): + break + + # dispose all resources and quit + glfw.terminate() + + +if __name__ == "__main__": + main() diff --git a/wgpu/gui/glfw.py b/wgpu/gui/glfw.py index 7c1fea36..8cadfda1 100644 --- a/wgpu/gui/glfw.py +++ b/wgpu/gui/glfw.py @@ -104,6 +104,39 @@ } +def get_surface_info(window): + if sys.platform.startswith("win"): + return { + "platform": "windows", + "window": int(glfw.get_win32_window(window)), + } + elif sys.platform.startswith("darwin"): + return { + "platform": "cocoa", + "window": int(glfw.get_cocoa_window(window)), + } + elif sys.platform.startswith("linux"): + if is_wayland: + return { + "platform": "wayland", + "window": int(glfw.get_wayland_window(window)), + "display": int(glfw.get_wayland_display()), + } + else: + return { + "platform": "x11", + "window": int(glfw.get_x11_window(window)), + "display": int(glfw.get_x11_display()), + } + else: + raise RuntimeError(f"Cannot get GLFW surafce info on {sys.platform}.") + + +def get_physical_size(window): + psize = glfw.get_framebuffer_size(window) + return int(psize[0]), int(psize[1]) + + class GlfwWgpuCanvas(WgpuAutoGui, WgpuCanvasBase): """A glfw window providing a wgpu canvas.""" @@ -212,8 +245,7 @@ def _determine_size(self): # on some systems and in logical-pixels on other, we use the # framebuffer size and pixel ratio to derive the logical size. pixel_ratio = get_window_content_scale(self._window)[0] - psize = glfw.get_framebuffer_size(self._window) - psize = int(psize[0]), int(psize[1]) + psize = get_physical_size(self._window) self._pixel_ratio = pixel_ratio self._physical_size = psize @@ -266,31 +298,7 @@ def _set_logical_size(self, new_logical_size): # API def get_surface_info(self): - if sys.platform.startswith("win"): - return { - "platform": "windows", - "window": int(glfw.get_win32_window(self._window)), - } - elif sys.platform.startswith("darwin"): - return { - "platform": "cocoa", - "window": int(glfw.get_cocoa_window(self._window)), - } - elif sys.platform.startswith("linux"): - if is_wayland: - return { - "platform": "wayland", - "window": int(glfw.get_wayland_window(self._window)), - "display": int(glfw.get_wayland_display()), - } - else: - return { - "platform": "x11", - "window": int(glfw.get_x11_window(self._window)), - "display": int(glfw.get_x11_display()), - } - else: - raise RuntimeError(f"Cannot get GLFW surafce info on {sys.platform}.") + return get_surface_info(self._window) def get_pixel_ratio(self): return self._pixel_ratio