Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
41 changes: 20 additions & 21 deletions examples/cube.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@


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.
The given context must implement ..., but nothing more.
Returns the draw function.
"""

Expand All @@ -39,19 +39,19 @@ 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.
The given context must implement ..., but nothing more.
Returns the draw function.
"""
adapter = await wgpu.gpu.request_adapter_async(power_preference="high-performance")
Expand All @@ -61,34 +61,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 +249,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 +303,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 @@ -488,11 +487,11 @@ def draw_func():
# Async
@loop.add_task
async def init():
draw_frame = await setup_drawing_async(canvas)
draw_frame = await setup_drawing_async(canvas.get_context("wgpu"))
canvas.request_draw(draw_frame)
else:
# Sync
draw_frame = setup_drawing_sync(canvas)
draw_frame = setup_drawing_sync(canvas.get_context("wgpu"))
canvas.request_draw(draw_frame)

# loop.add_task(poller)
Expand Down
44 changes: 23 additions & 21 deletions examples/gui_direct.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,15 @@ def get_glfw_present_methods(window):
"screen": {
"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)),
"vsync": True,
}
}
elif sys.platform.startswith("linux"):
Expand All @@ -48,6 +50,7 @@ def get_glfw_present_methods(window):
"platform": "wayland",
"window": int(glfw.get_wayland_window(window)),
"display": int(glfw.get_wayland_display()),
"vsync": True,
}
}
else:
Expand All @@ -56,6 +59,7 @@ def get_glfw_present_methods(window):
"platform": "x11",
"window": int(glfw.get_x11_window(window)),
"display": int(glfw.get_x11_display()),
"vsync": True,
}
}
else:
Expand All @@ -66,43 +70,41 @@ 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)
title = "wgpu glfw direct"
window = glfw.create_window(640, 480, title, None, None)
present_methods = get_glfw_present_methods(window)

self.window = glfw.create_window(640, 480, title, None, None)
self.context = GPUCanvasContext(self, get_glfw_present_methods(self.window))
context = GPUCanvasContext(present_methods)

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])

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)
# create canvas/context — setup_drawing_sync expects a "canvas" that can
# provide get_context/get_physical_size; GPUCanvasContext now implements those.
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 +113,7 @@ def main():
last_frame_time, frame_count = time.perf_counter(), 0

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

# allow proper cleanup (workaround for glfw bug)
end_time = time.perf_counter() + 0.1
Expand Down
4 changes: 0 additions & 4 deletions wgpu/_canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,6 @@ class WgpuCanvasInterface:

_canvas_context = None

def get_physical_size(self) -> tuple[int, int]:
"""Get the physical size of the canvas in integer pixels."""
return (640, 480)

def get_context(self, context_type: str = "wgpu") -> wgpu.GPUCanvasContext:
"""Get the ``GPUCanvasContext`` object corresponding to this canvas.

Expand Down
39 changes: 25 additions & 14 deletions wgpu/_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
# Allow using class names in type annotations, without Ruff triggering F821
from __future__ import annotations

import weakref
import logging
from typing import Sequence

Expand Down Expand Up @@ -230,9 +229,12 @@ class GPUCanvasContext:

_ot = object_tracker

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

# Buffer to hold new physical size
# will be applied when the context reconfigures
self._new_physical_size = None

# Surface capabilities. Stored the first time it is obtained
self._capabilities = None
Expand All @@ -247,15 +249,16 @@ def __init__(self, canvas, present_methods):
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."""
return self._canvas_ref()
def set_physical_size(self, size):
"""Set the current framebuffer physical size (width, height).

# IDL: readonly attribute (HTMLCanvasElement or OffscreenCanvas) canvas;
@property
def canvas(self) -> CanvasLike:
"""The associated canvas object."""
return self._canvas_ref()
The application must call this to keep the context informed about
the window/framebuffer size.
"""
w, h = size
if w <= 0 or h <= 0:
raise ValueError("Physical size values must be positive.")
self._new_physical_size = int(w), int(h)

def _get_capabilities(self, adapter):
"""Get dict of capabilities and cache the result."""
Expand Down Expand Up @@ -322,6 +325,7 @@ def configure(
color_space: str = "srgb",
tone_mapping: structs.CanvasToneMappingStruct | None = None,
alpha_mode: enums.CanvasAlphaModeEnum = "opaque",
size: tuple[int, int] = (320, 240),
) -> None:
"""Configures the presentation context for the associated canvas.
Destroys any textures produced with a previous configuration.
Expand All @@ -342,6 +346,7 @@ def configure(
alpha_mode (structs.CanvasAlphaMode): Determines the effect that alpha values
will have on the content of textures returned by ``get_current_texture()``
when read, displayed, or used as an image source. Default "opaque".
size (tuple[int, int]): The physical size of the canvas in pixels.
"""
# Check types
tone_mapping = {} if tone_mapping is None else tone_mapping
Expand All @@ -357,6 +362,11 @@ def configure(
if not isinstance(usage, int):
usage = str_flag_to_int(flags.TextureUsage, usage)

if not isinstance(size, tuple) or len(size) != 2:
raise TypeError("Configure: size must be a tuple (width, height).")
if not size[0] > 0 or not size[1] > 0:
raise ValueError("Configure: size values must be positive.")

color_space # noqa - not really supported, just assume srgb for now
tone_mapping # noqa - not supported yet

Expand Down Expand Up @@ -403,6 +413,7 @@ def configure(
"color_space": color_space,
"tone_mapping": tone_mapping,
"alpha_mode": alpha_mode,
"size": size,
}

if self._present_method == "screen":
Expand All @@ -418,6 +429,7 @@ def _configure_screen(
color_space,
tone_mapping,
alpha_mode,
size,
):
raise NotImplementedError()

Expand Down Expand Up @@ -457,8 +469,7 @@ def get_current_texture(self) -> GPUTexture:
return self._texture

def _create_texture_bitmap(self):
canvas = self._get_canvas()
width, height = canvas.get_physical_size()
width, height = (self._wgpu_config.width, self._wgpu_config.height)
width, height = max(width, 1), max(height, 1)

# Note that the label 'present' is used by read_texture() to determine
Expand Down Expand Up @@ -1064,7 +1075,7 @@ def create_bind_group(
binding=2,
resource=wgpu.BufferBinding(
buffer=a_buffer,
offset=0.
offset=0.,
size=812,
)
)
Expand Down
Loading
Loading