Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Next, we can setup the render context, which we will need later on.

.. code-block:: py

present_context = canvas.get_context("wgpu")
present_context = canvas.get_wgpu_context()
render_texture_format = present_context.get_preferred_format(device.adapter)
present_context.configure(device=device, format=render_texture_format)

Expand Down
14 changes: 6 additions & 8 deletions docs/wgpu.rst
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,14 @@ The async methods return a :class:`GPUPromise`, which resolves to the actual res
* In sync code, you can use ``promise.sync_wait()``. This is similar to the ``_sync()`` flavour mentioned above (it makes your code less portable).


Canvas API
----------

In order for wgpu to render to a canvas (which can be on screen, inside a GUI, offscreen, etc.),
a canvas object is needed. We recommend using the `rendercanvas <https://github.com/pygfx/rendercanvas>`_ library to get a wide variety of canvases.

That said, the canvas object can be any object, as long as it adheres to the
``WgpuCanvasInterface``, see https://github.com/pygfx/wgpu-py/blob/main/wgpu/_canvas.py for details.
Rendering to a canvas
---------------------

In order for wgpu to render to a canvas (which can be on screen, inside a GUI,
offscreen, etc.), we highly recommend using the `rendercanvas <https://github.com/pygfx/rendercanvas>`_ library.
One can then use ``canvas.get_wgpu_context()`` to get a `WgpuContext <https://rendercanvas.readthedocs.io/stable/contexts.html#rendercanvas.contexts.WgpuContext>`_.

For more low-level control, use ```wgpu.gpu.get_canvas_context()`` to get a :class:`GPUCanvasContext` object for drawing directly to a window on screen.


Overview
Expand Down
42 changes: 20 additions & 22 deletions examples/cube.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
because it adds buffers and textures.

This example is set up so it can be run with any canvas. Running this file
as a script will use the auto-backend.
as a script will rendercanvas with the auto-backend.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
as a script will rendercanvas with the auto-backend.
as a script will use rendercanvas with the auto-backend.

"""

# test_example = true
Expand All @@ -24,11 +24,10 @@


def setup_drawing_sync(
canvas, power_preference="high-performance", limits=None
context, power_preference="high-performance", limits=None
) -> Callable:
"""Setup to draw a rotating cube on the given canvas.
"""Setup to draw a rotating cube on the given context.

The given canvas must implement WgpuCanvasInterface, but nothing more.
Returns the draw function.
"""

Expand All @@ -39,19 +38,18 @@ def setup_drawing_sync(
)

pipeline_layout, uniform_buffer, bind_group = create_pipeline_layout(device)
pipeline_kwargs = get_render_pipeline_kwargs(canvas, device, pipeline_layout)
pipeline_kwargs = get_render_pipeline_kwargs(context, device, pipeline_layout)

render_pipeline = device.create_render_pipeline(**pipeline_kwargs)

return get_draw_function(
canvas, device, render_pipeline, uniform_buffer, bind_group
context, device, render_pipeline, uniform_buffer, bind_group
)


async def setup_drawing_async(canvas, limits=None):
"""Setup to async-draw a rotating cube on the given canvas.
async def setup_drawing_async(context, limits=None):
"""Setup to async-draw a rotating cube on the given context.

The given canvas must implement WgpuCanvasInterface, but nothing more.
Returns the draw function.
"""
adapter = await wgpu.gpu.request_adapter_async(power_preference="high-performance")
Expand All @@ -61,34 +59,33 @@ async def setup_drawing_async(canvas, limits=None):
)

pipeline_layout, uniform_buffer, bind_group = create_pipeline_layout(device)
pipeline_kwargs = get_render_pipeline_kwargs(canvas, device, pipeline_layout)
pipeline_kwargs = get_render_pipeline_kwargs(context, device, pipeline_layout)

render_pipeline = await device.create_render_pipeline_async(**pipeline_kwargs)

return get_draw_function(
canvas, device, render_pipeline, uniform_buffer, bind_group
context, device, render_pipeline, uniform_buffer, bind_group
)


def get_drawing_func(canvas, device):
def get_drawing_func(context, device):
pipeline_layout, uniform_buffer, bind_group = create_pipeline_layout(device)
pipeline_kwargs = get_render_pipeline_kwargs(canvas, device, pipeline_layout)
pipeline_kwargs = get_render_pipeline_kwargs(context, device, pipeline_layout)

render_pipeline = device.create_render_pipeline(**pipeline_kwargs)
# render_pipeline = device.create_render_pipeline(**pipeline_kwargs)

return get_draw_function(
canvas, device, render_pipeline, uniform_buffer, bind_group
context, device, render_pipeline, uniform_buffer, bind_group
)


# %% Functions to create wgpu objects


def get_render_pipeline_kwargs(
canvas, device: wgpu.GPUDevice, pipeline_layout: wgpu.GPUPipelineLayout
context, device: wgpu.GPUDevice, pipeline_layout: wgpu.GPUPipelineLayout
) -> wgpu.RenderPipelineDescriptor:
context = canvas.get_context("wgpu")
render_texture_format = context.get_preferred_format(device.adapter)
context.configure(device=device, format=render_texture_format)

Expand Down Expand Up @@ -250,7 +247,7 @@ def create_pipeline_layout(device: wgpu.GPUDevice):


def get_draw_function(
canvas,
context,
device: wgpu.GPUDevice,
render_pipeline: wgpu.GPURenderPipeline,
uniform_buffer: wgpu.GPUBuffer,
Expand Down Expand Up @@ -304,9 +301,9 @@ def upload_uniform_buffer():

def draw_frame():
current_texture_view: wgpu.GPUTextureView = (
canvas.get_context("wgpu")
.get_current_texture()
.create_view(label="Cube Example current surface texture view")
context.get_current_texture().create_view(
label="Cube Example current surface texture view"
)
)
command_encoder = device.create_command_encoder(
label="Cube Example render pass command encoder"
Expand Down Expand Up @@ -481,18 +478,19 @@ def draw_func():
max_fps=60,
vsync=True,
)
context = canvas.get_wgpu_context()

# Pick one

if True:
# Async
@loop.add_task
async def init():
draw_frame = await setup_drawing_async(canvas)
draw_frame = await setup_drawing_async(context)
canvas.request_draw(draw_frame)
else:
# Sync
draw_frame = setup_drawing_sync(canvas)
draw_frame = setup_drawing_sync(context)
canvas.request_draw(draw_frame)

# loop.add_task(poller)
Expand Down
75 changes: 34 additions & 41 deletions examples/gui_direct.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import atexit

import glfw
from wgpu.backends.wgpu_native import GPUCanvasContext
import wgpu

# from triangle import setup_drawing_sync
from cube import setup_drawing_sync
Expand All @@ -26,37 +26,33 @@
api_is_wayland = True


def get_glfw_present_methods(window):
def get_glfw_present_info(window):
Copy link
Collaborator Author

@Korijn Korijn Nov 7, 2025

Choose a reason for hiding this comment

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

This specific function feels like we could have a module with a bunch of helpers like this for different backends. Let's leave it for now though.

Copy link
Member

Choose a reason for hiding this comment

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

That module is called rendercanvas 😉. But I see what you mean, a small util for glfw and qt could fit in wgpu/utils/.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah. I think it's important for people to see how to get a winId in the examples locally here, without having to venture out to rendercanvas. It will put off low level coders, keen on building their own pygfx/rendercanvas libraries.

if sys.platform.startswith("win"):
return {
"screen": {
"platform": "windows",
"window": int(glfw.get_win32_window(window)),
}
"platform": "windows",
"window": int(glfw.get_win32_window(window)),
"vsync": True,
}
elif sys.platform.startswith("darwin"):
return {
"screen": {
"platform": "cocoa",
"window": int(glfw.get_cocoa_window(window)),
}
"platform": "cocoa",
"window": int(glfw.get_cocoa_window(window)),
"vsync": True,
}
elif sys.platform.startswith("linux"):
if api_is_wayland:
return {
"screen": {
"platform": "wayland",
"window": int(glfw.get_wayland_window(window)),
"display": int(glfw.get_wayland_display()),
}
"platform": "wayland",
"window": int(glfw.get_wayland_window(window)),
"display": int(glfw.get_wayland_display()),
"vsync": True,
}
else:
return {
"screen": {
"platform": "x11",
"window": int(glfw.get_x11_window(window)),
"display": int(glfw.get_x11_display()),
}
"platform": "x11",
"window": int(glfw.get_x11_window(window)),
"display": int(glfw.get_x11_display()),
"vsync": True,
}
else:
raise RuntimeError(f"Cannot get GLFW surface info on {sys.platform}.")
Expand All @@ -66,43 +62,39 @@ def get_glfw_present_methods(window):
glfw.init()
atexit.register(glfw.terminate)

# disable automatic API selection, we are not using opengl
glfw.window_hint(glfw.CLIENT_API, glfw.NO_API)
glfw.window_hint(glfw.RESIZABLE, True)

class MinimalGlfwCanvas: # implements WgpuCanvasInterface
"""Minimal canvas interface required by wgpu."""

def __init__(self, title):
# disable automatic API selection, we are not using opengl
glfw.window_hint(glfw.CLIENT_API, glfw.NO_API)
glfw.window_hint(glfw.RESIZABLE, True)

self.window = glfw.create_window(640, 480, title, None, None)
self.context = GPUCanvasContext(self, get_glfw_present_methods(self.window))
title = "wgpu glfw direct"
window = glfw.create_window(640, 480, title, None, None)
present_info = get_glfw_present_info(window)

def get_physical_size(self):
"""get framebuffer size in integer pixels"""
psize = glfw.get_framebuffer_size(self.window)
return int(psize[0]), int(psize[1])
context = wgpu.gpu.get_canvas_context(present_info)

def get_context(self, kind="wgpu"):
return self.context
# Initialize physical size once. For robust apps update this on resize events.
context.set_physical_size(*glfw.get_framebuffer_size(window))


def main():
# create canvas
canvas = MinimalGlfwCanvas("wgpu gui direct")
draw_frame = setup_drawing_sync(canvas)
draw_frame = setup_drawing_sync(context)

last_frame_time = time.perf_counter()
frame_count = 0

# render loop
while not glfw.window_should_close(canvas.window):
while not glfw.window_should_close(window):
# process inputs
glfw.poll_events()

# resize handling
context.set_physical_size(*glfw.get_framebuffer_size(window))

# draw a frame
draw_frame()
# present the frame to the screen
canvas.context.present()
context.present()
# stats
frame_count += 1
etime = time.perf_counter() - last_frame_time
Expand All @@ -111,7 +103,8 @@ def main():
last_frame_time, frame_count = time.perf_counter(), 0

# dispose resources
glfw.destroy_window(canvas.window)
context.unconfigure()
glfw.destroy_window(window)

# allow proper cleanup (workaround for glfw bug)
end_time = time.perf_counter() + 0.1
Expand Down
2 changes: 1 addition & 1 deletion examples/imgui_backend_sea.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
device = adapter.request_device_sync()

# Prepare present context
present_context = canvas.get_context("wgpu")
present_context = canvas.get_wgpu_context()
render_texture_format = wgpu.TextureFormat.bgra8unorm
present_context.configure(device=device, format=render_texture_format)

Expand Down
2 changes: 1 addition & 1 deletion examples/imgui_renderer_sea.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
device = adapter.request_device_sync()

# Prepare present context
present_context = canvas.get_context("wgpu")
present_context = canvas.get_wgpu_context()
render_texture_format = wgpu.TextureFormat.bgra8unorm
present_context.configure(device=device, format=render_texture_format)

Expand Down
Loading
Loading