Skip to content

Commit d962be8

Browse files
authored
Handle pixel scaling properly in WebGL backend (#2846)
1 parent 64caf4f commit d962be8

2 files changed

Lines changed: 37 additions & 8 deletions

File tree

arcade/application.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -968,6 +968,16 @@ def get_pixel_ratio(self) -> float:
968968
"""
969969
if self._pixel_perfect:
970970
return 1.0
971+
if is_pyodide:
972+
# Pyglet's emscripten window caches devicePixelRatio at init, but the
973+
# actual canvas drawing buffer is sized via getBoundingClientRect()
974+
# which can be sub-pixel less than logical_size * devicePixelRatio.
975+
# Returning fb_size / logical_size matches the canvas exactly, so
976+
# full-canvas viewport round-trips through Camera2D don't leave a
977+
# 1-2 pixel gap on the top/right edges.
978+
log_w = self._width
979+
if log_w:
980+
return self.get_framebuffer_size()[0] / log_w
971981
return super().get_pixel_ratio()
972982

973983
def _on_resize(self, width: int, height: int) -> EVENT_HANDLE_STATE:
@@ -1038,6 +1048,14 @@ def set_size(self, width: int, height: int) -> None:
10381048

10391049
def get_size(self) -> tuple[int, int]:
10401050
"""Get the size of the window."""
1051+
if is_pyodide:
1052+
# Pyglet's emscripten window returns the canvas drawing-buffer
1053+
# size (physical, DPI-scaled pixels) from get_size(); desktop
1054+
# pyglet returns logical pixels. Return logical pixels here so
1055+
# viewport math in show_view/_on_resize stays consistent across
1056+
# backends and Camera2D doesn't render into a sub-region of the
1057+
# canvas on HiDPI displays.
1058+
return self._width, self._height
10411059
return super().get_size()
10421060

10431061
def get_location(self) -> tuple[int, int]:

arcade/gl/backends/webgl/framebuffer.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -256,15 +256,20 @@ def __init__(self, ctx: WebGLContext):
256256

257257
@DefaultFrameBuffer.viewport.setter
258258
def viewport(self, value: tuple[int, int, int, int]):
259-
# This is very similar to the OpenGL backend setter
260-
# WebGL backend doesn't need to handle pixel scaling for the
261-
# default framebuffer like desktop does, the browser does that
262-
# for us. However we need a separate implementation for the
263-
# function because of ABC
259+
# Pyglet sizes the canvas drawing buffer at physical pixels
260+
# (canvas.width = logical_width * devicePixelRatio), so we apply
261+
# the same pixel-ratio multiply as the OpenGL backend to keep the
262+
# default framebuffer's get/set symmetric in logical pixels.
264263
if not isinstance(value, tuple) or len(value) != 4:
265-
raise ValueError("viewport shouldbe a 4-component tuple")
264+
raise ValueError("viewport should be a 4-component tuple")
266265

267-
self._viewport = value
266+
ratio = self.ctx.window.get_pixel_ratio()
267+
self._viewport = (
268+
int(value[0] * ratio),
269+
int(value[1] * ratio),
270+
int(value[2] * ratio),
271+
int(value[3] * ratio),
272+
)
268273

269274
if self._ctx.active_framebuffer == self:
270275
self._ctx._gl.viewport(*self._viewport)
@@ -280,6 +285,12 @@ def scissor(self, value):
280285
if self._ctx.active_framebuffer == self:
281286
self._ctx._gl.scissor(*self._viewport)
282287
else:
283-
self._scissor = value
288+
ratio = self.ctx.window.get_pixel_ratio()
289+
self._scissor = (
290+
int(value[0] * ratio),
291+
int(value[1] * ratio),
292+
int(value[2] * ratio),
293+
int(value[3] * ratio),
294+
)
284295
if self._ctx.active_framebuffer == self:
285296
self._ctx._gl.scissor(*self._scissor)

0 commit comments

Comments
 (0)