Skip to content

ScummVM on Kandelo - SCUMM v0-8 games in the browser on the Wayland/DRI desktop stack - #1368

Draft
mho22 wants to merge 6 commits into
explore-dri-quattrofrom
explore-dri-scummvm
Draft

ScummVM on Kandelo - SCUMM v0-8 games in the browser on the Wayland/DRI desktop stack#1368
mho22 wants to merge 6 commits into
explore-dri-quattrofrom
explore-dri-scummvm

Conversation

@mho22

@mho22 mho22 commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #1354 (Quickshell on Kandelo — Qt 6.10 on wasm32), which carries the desktop stack this builds on: evdev devices, the Wayland compositor, and the Qt/Quickshell packages.

Why

The desktop stack from #1354 could run its own clients, but nothing the size of a real application had been ported onto it. ScummVM exercises the platform end to end — SDL2 over Wayland, the GLES2 OpenGL graphics manager, OSS audio, evdev pointer input, and package runtime data — and the user-visible result is ?demo=scummvm: SCUMM v0–8 games (Monkey Island 1–3, Day of the Tentacle, Full Throttle, The Dig) playable in the browser from a user-supplied game upload. Porting it surfaced two platform bugs that this PR fixes for every GL and pointer consumer, not just ScummVM.

What changed

ScummVM 2026.3.0 joins the registry with the scumm engine and its scumm-7-8 sub-engine enabled, patched only for the host triple and to default to the OpenGL graphics manager. Its GUI themes and data files travel as [[runtime_files]] staged into /usr/share/scummvm, so the engine boots with no game data embedded anywhere; games arrive through the demo's "Load game data" upload, which extracts into the directory the launcher browses.

The GL bridge grows the ops ScummVM renders through (glDetachShader, glVertexAttrib4fv, glDeleteFramebuffers, glGetShaderPrecisionFormat), and host_gl_present now ensures a present target instead of testing for one: destroying an EGL window surface freed the target, but only the first canvas claim ever built one, so recreating a surface on the same context (leaving the launcher for a game) lost the bind-0 redirect and composited every half-drawn frame as a black flash.

/dev/input/event1 becomes a real absolute pointer. It advertised REL_X/REL_Y alongside its ABS axes, so SDL classified it as a relative mouse and discarded every EV_ABS record; the host compensated by pegging the cursor to the origin before each move, and a click processed between the two frames registered at (0,0). The REL bitmap now carries only the wheel axes, sendPointerAbs states the position as EV_ABS, and the advertised EVIOCGABS range is keyed to the connector mode at setKmsDisplaySize time — libinput caches absinfo when it opens the device, so a range corrected any later is one a running compositor never sees.

A one-line quickshell build fix rides along: the CMake link line passes the staged libwayland prefix explicitly.

Validation

Host Vitest: 142 passed, 2 skipped across kandelo-session, browser-binary-dependencies, the ScummVM CLI smoke, the wldesktop liveness gate (now driving the EV_ABS pattern through kmsSetDisplaySize), and sdl2. Playwright chromium: 10/10 across the wayland, hyprland, omarchy, modeset, scummvm, and doom-ingest specs; the scummvm spec also passes on firefox and webkit (4/4). ./run.sh local-build under the source-only policy: all nodes succeeded, products 7/7. cargo test -p runtime-core input::: 16 passed. Monkey Island 2 was played end to end in the browser manually. The branch was later restacked into six commits with identical content apart from dropped npm lock churn and a relocated doc comment; on the restacked tree the Vitest selection (142 passed, 2 skipped), the chromium Playwright specs (10/10), and the cargo suites (wasm-posix-shared gl_tests 4 passed, runtime-core input 36 passed) were rerun green.

@mho22 mho22 changed the title Packages: ScummVM, Qt 6, and Quickshell on the Wayland/DRI desktop stack ScummVM on Kandelo - SCUMM v0-8 games in the browser on the Wayland/DRI desktop stack Sep 3, 2026
@mho22
mho22 changed the base branch from main to explore-dri-quattro September 3, 2026 21:11
Gate 1 polled for WLC_LAYOUT, which the compositor prints before
THEME and WALLPAPER, so the plain assertions that followed raced
the rest of startup. Poll for WALLPAPER — the last startup marker —
and plain-assert the earlier ones, which are already in the stream
once it appears.
…ange

event1 advertised REL_X/REL_Y alongside its ABS axes, and consumers
classify on exactly those two bits: SDL2's evdev backend saw a
relative mouse and discarded every EV_ABS record. The host bridge
compensated by pegging the relative integrator to the origin with
an oversized negative delta before each move, and the compositor
carried a matching suppression for the peg frame. Drop the REL_X/Y
bits, send true EV_ABS coordinates from sendPointerAbs, and delete
the peg workaround on both ends.

The advertised EVIOCGABS range must be right when the device is
OPENED: libevdev caches absinfo at open, and wlcompositor runs
setup_input() before its lazy first-present drmModeSetCrtc, so a
range corrected at SETCRTC is a range libinput never sees.
connectorModeSize() becomes the single mode derivation, and
setKmsDisplaySize advertises it to the input device the moment the
embedder reports the display — before the compositor spawns. The
SETCRTC hook (onKmsScanoutFb) stays for late openers and
mid-session modesets; SDL reads raw ABS values, so it survives
either keying.

The wldesktop liveness smoke drives the real bridge pattern now:
EV_ABS + SYN injection with the range set via kmsSetDisplaySize,
the Node-host regression gate for the open-time keying.
…m renders through

ScummVM's OpenGL graphics manager needs more of GLES2 than the
bridge carried: glDetachShader, glVertexAttrib4fv (the per-draw
color when the attribute array is disabled), glDeleteFramebuffers,
glGetShaderPrecisionFormat, glGetString/glGetIntegerv on the client
side, glBufferSubData, glTexSubImage2D with GL_UNPACK_ALIGNMENT
row strides, and chunked texture uploads: the TLV payload length is
u16, so uploads past ~64 KB stream as row bands instead of being
dropped. eglGetProcAddress answers with the statically linked gl*
entry points, because GL loaders (ScummVM's glad through
SDL_GL_GetProcAddress) fetch every function through it and read a
NULL glGetString as "no GL".

A canvas-backed session gains a durable present target: an
offscreen FBO the guest renders into, blitted to the default
framebuffer once per GLIO_PRESENT. The browser composites an
OffscreenCanvas at the end of any worker task that touched it, so
rendering straight at the canvas puts clears and half-drawn frames
on screen — whole-screen flicker while a program redraws every
frame. The target is re-ensured at present time because ScummVM
recreates its EGL window surface when it leaves the launcher, and
destroy_surface frees the target with the binding's redirect.
The final link names the wayland archives with -l flags, but no -L
pointed at the resolver-staged libwayland prefix. Carry it on
CMAKE_EXE_LINKER_FLAGS so the staged archives resolve.
Two demo-surface capabilities the KMS pane lacked:

A guest that draws its own cursor (modeset, ScummVM, a compositor)
must have the browser pointer hidden over the scanout, or two
arrows stack and drift apart; a guest that draws no cursor (the
sdl2 GLSL playground) leaves the user nothing to aim with. The
image-declared presentation gains hostPointer for the second kind,
default hidden, and the pane sets the canvas cursor from it. The
pane also fits the canvas to the live scanout aspect instead of the
16:9 constant, which only remains the pre-first-frame fallback.

The demo ingest control moves up from the framebuffer pane into a
shared IngestControl with per-surface test ids, and the KMS pane
mounts it: an upload lands while the guest keeps running, because a
KMS program holds DRM master for the whole session and restarting
it would cost the user their place. The toast styles rename from
kframebuffer-* to kdemo-* to match their two consumers.
ScummVM 2026.3.0 (SCUMM engine with scumm-7-8) as a registry
package on the Kandelo SDL2 stack: Wayland video under
wlcompositor with the GLES2 OpenGL graphics manager through
wayland-egl, OSS audio through SDL2's dsp backend, seat input from
the compositor. Two patches: a wasm32posix host triple so the
generic POSIX/SDL backend builds instead of the Emscripten port,
and the OpenGL manager as default because Kandelo's SDL2 compiles
SDL_Render out.

The package declares [[runtime_files]] — the launcher's themes,
icons and fonts — and the resolver mirrors the closure under
programs/wasm32/scummvm/. The browser app restates the declaration
in package-runtime-files.ts (it cannot run xtask), and
browser-binary-dependencies.test.ts fails on drift against the
manifest.

The demo runs ScummVM alone on /dev/dri/card0 as DRM master. No
game ships: the KMS pane's Load game data upload lands a zip that
the demo's shell extracts into the directory the launcher browses.
The config seeds into the demo user's scratch home, writable, with
gui_scale at the device-pixel ratio so the GUI lays out in CSS
pixels while rendering at physical ones.

Gates: the browser spec proves the runtime files, the pointer
ownership, and the upload-extract path through the demo's own
shell; the Node smoke runs scummvm --version to a clean exit.
@mho22
mho22 force-pushed the explore-dri-scummvm branch from f385348 to ad8ea9d Compare September 3, 2026 21:42
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