Skip to content

pyglet 3.0.dev6 migration#2865

Open
pvcraven wants to merge 12 commits into
developmentfrom
001-pyglet-3-dev6-migration
Open

pyglet 3.0.dev6 migration#2865
pvcraven wants to merge 12 commits into
developmentfrom
001-pyglet-3-dev6-migration

Conversation

@pvcraven

@pvcraven pvcraven commented Jul 16, 2026

Copy link
Copy Markdown
Member

Summary

Migrate Arcade's pinned pyglet dependency directly from 3.0.dev3 to 3.0.dev6 (no dev4 intermediate). This is a hard cutover: pyglet dev5+ removed
window._matrices and restructured how window view/projection/viewport are stored, which broke Arcade's rendering pipeline in three distinct ways. All three are fixed
here, verified empirically against the real pyglet dev6 source and a full local test run on native Windows.

Background

Between pyglet dev4 and dev5, pyglet:

  • Removed window._matrices (and its .ubo) entirely.
  • Moved window view/projection/viewport onto a lazily-created default_camera (pyglet.window.camera.camera2d.Camera2D), backed by a per-frame ring-buffer UBO.

A naive version bump crashes immediately (AttributeError: 'Window' object has no attribute '_matrices'), and would go on to cause unbounded UBO growth once past
that, because Arcade drives its own render loop and doesn't participate in pyglet's frame-resource lifecycle.

What changed

1. Arcade now owns its window-block UBO directly (arcade/context.py)

ArcadeContext allocates a single fixed-size (128-byte) buffer in __init__ instead of reusing window._matrices.ubo. projection_matrix/view_matrix setters
write straight into this buffer. This buffer's identity never changes for the lifetime of the context — no ring buffer, no reallocation, so growth is impossible by
construction rather than by careful bookkeeping.

OpenGLArcadeContext/WebGLArcadeContext.bind_window_block() (arcade/gl/backends/{opengl,webgl}/context.py) now bind this buffer via the existing
Buffer.bind_to_uniform_block() method instead of reading self._window_block.buffer.id from pyglet.

2. Camera write-path fix (arcade/camera/{orthographic,perspective,camera_2d}.py)

Found via failing camera unit tests, not anticipated in the original migration plan: OrthographicProjector.use(), PerspectiveProjector.use(), and Arcade's own
Camera2D.use() were writing matrices through self._window.projection = ... / self._window.view = ... — i.e. through pyglet's window properties, not Arcade's.
Under dev3 this was harmless (both paths pointed at the same underlying buffer). Under dev6, pyglet's properties write into its own ring buffer — a completely
different buffer from the one Arcade now owns — so camera changes silently never reached the GPU. Fixed by routing all three through
ctx.projection_matrix/ctx.view_matrix instead, matching the pattern DefaultProjector already used.

3. DefaultProjector pyglet-compatibility shim (arcade/camera/default.py)

Found via the example/tutorial integration tests: pyglet's own batch-draw pipeline (used internally by pyglet.text.Label.draw(), which arcade.Text delegates to)
falls back to window.default_camera when no explicit camera is given. Since Arcade's Window.default_camera property override intercepts every access to that
name, pyglet's fallback gets Arcade's DefaultProjector instead of its own Camera2D, and calls methods on it that only a real pyglet camera implements — crashing
with AttributeError.

Rather than reimplementing pyglet's internal camera/view-hierarchy protocol (which would tightly couple Arcade to pyglet internals — exactly what this fix is trying to
avoid), DefaultProjector gained a minimal duck-typing shim:

  • .view → returns self
  • .begin() → no-op (preserve whatever camera state Arcade already has bound)
  • .get_group_scissor_area() → always None
  • .projection / .view_matrix (get/set) → thin aliases over existing state

This also fixes window.projection/window.view (pyglet's base properties, which delegate to self.default_camera.projection/.view_matrix) for any test
infrastructure or downstream code that reads them directly.

New test infrastructure

  • tests/unit/rendering/ — a reference-image comparison harness (scenes.py, image_compare.py, generate_baseline.py, test_dev6_reference_images.py) with 4
    checked-in baseline PNGs captured on dev3 before the pin change. No such harness existed before this PR.
  • tests/integration/test_ubo_stress.py — 100-cycle window/draw/close and ctx.reset() stress tests asserting zero "Growing UniformBufferObject" warnings and a
    stable buffer identity.

Testing performed

  • Camera/window unit suite: 125/125 pass
  • Reference-image comparison: 4/4 scenes pixel-identical to dev3 baselines
  • UBO stress tests: 100 window cycles + 100 reset cycles, zero growth warnings
  • Full suite (pytest tests/) on native Windows: 1283 passed, 10 failed/1 error — confirmed byte-identical to running the exact same suite against the untouched
    dev3 install on the same machine
    . These pre-existing failures (Windows DPI-scaling assumptions in one test, shared-global-window test-order flakiness in a few
    others, an unrelated encoding bug in a docstring checker) are unrelated to pyglet version and out of scope for this PR.
  • .github/workflows/test.yml (Linux + xvfb CI) is untouched — verified via git diff.

Breaking changes

  • Exact pin pyglet==3.0.dev6 (was 3.0.dev3); no dual code paths, dev3/dev4 no longer supported.
  • Internal only: window._matrices no longer exists (removed by pyglet). Advanced users mixing raw pyglet camera/window code with Arcade should note
    window.default_camera is now pyglet's own Camera2D, distinct from arcade.Window.default_camera (Arcade's DefaultProjector) — both exist simultaneously and
    serve different roles. See CHANGELOG.md.
  • No change to Arcade's public API surface (camera classes, ArcadeContext properties, Window.default_camera, get_image()/get_pixel()) — verified by the full
    test suite.

Test plan (for reviewers)

  • uv sync --no-group docs resolves and installs cleanly
  • uv run pytest tests/unit/camera tests/unit/window -v — all pass
  • uv run pytest tests/unit/rendering -v — all 4 reference-image comparisons pass
  • uv run pytest tests/integration/test_ubo_stress.py -v — both stress tests pass
  • uv run pytest tests/integration/examples tests/integration/tutorials -v — no AttributeError from default_camera
  • Existing Linux+xvfb CI stays green

Paul Craven and others added 12 commits July 16, 2026 09:18
Add feature specification and quality checklist for migrating Arcade
from pyglet 3.0.dev3 directly to 3.0.dev6.

Key decisions captured via clarification:
- Arcade fully owns its own matrix UBO, independent of pyglet's
  ring-buffer/frame-resource lifecycle (fixes unbounded UBO growth).
- Hard-cut to exactly pyglet==3.0.dev6; no dev4 checkpoint, no dual paths.
- Full suite/stress/image-comparison runs are gated in a separate
  GPU-capable environment; local WSL runs a targeted subset only.
- Reference-image deviations are investigated before any re-baselining.
…ce and add reference-image comparison tests
…ntext state, preventing leakage and ensuring accurate rendering comparisons
…valid CameraScissor, preventing GL_SCISSOR_TEST from being disabled
…ks, dropping pixel-tolerance comparison due to CI discrepancies
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.

1 participant