8000 Optional GPU presentation on Wayland (EGL/GLES2) by emoon · Pull Request #434 · emoon/rust_minifb · GitHub
[go: up one dir, main page]

Skip to content

Optional GPU presentation on Wayland (EGL/GLES2) - #434

Merged
emoon merged 6 commits into
masterfrom
feat/wayland-gl
Aug 30, 2026
Merged

emoon merged 6 commits into
masterfrom
feat/wayland-gl

Conversation

@emoon
@emoon emoon commented Aug 29, 2026 •
Copy link
Copy Markdown
Owner

Uploads the caller's buffer as a texture and lets the GPU scale it, so per-frame cost tracks the buffer size instead of the window size.

  • WindowOptions::use_gpu: UseGPU::Auto (default) or UseGPU::Disabled.
  • Everything is dlopened, so GL never becomes a link-time dependency.
  • The context is built on the first update_with_buffer, not at window creation, so a caller driving the wl_surface through raw-window-handle never gets a competing EGL surface on it.
  • The software scaler is unchanged and takes over whenever GL cannot: missing EGL, no GL_EXT_texture_format_BGRA8888, a buffer past GL_MAX_TEXTURE_SIZE, or a CPU rasteriser such as llvmpipe.
  • Where the image lands mirrors src/native/posix/scalar.c pixel for pixel, integer truncation included, so toggling use_gpu cannot move it within the window. Sampling within that rect is nearest at destination pixel centres, the same as the macOS Metal path: identical to the software scaler for a 1:1 blit and whole-number scales, and up to half a destination pixel later than it otherwise. A test reads the chosen indices back out of image_resize_linear itself, per axis, over sizes up to 4K and 2.2M.

Also fixes the C scaler itself, which affects X11 and the software path generally: it stepped a 10.10 fixed-point ratio that drifts on non-integer scale factors (320 -> 3840 picked the wrong source column for 2430 of 3840 columns). It now indexes floor(j * src / dst) exactly.

Only Wayland is wired up for GPU presentation; X11 still scales in software. Adding a field to WindowOptions is a source break for literals without ..WindowOptions::default(), hence the 0.29 bump.

Part of #82

Upload the caller's buffer as a texture and let the GPU scale it, so
per-frame cost tracks the buffer size rather than the window size. The
software scaler is unchanged and still handles everything the GPU path
cannot: it is what a failed EGL setup, a missing driver extension, an
oversized buffer or a CPU rasteriser such as llvmpipe falls back to.

Everything is loaded with dlopen, so GL never becomes a link-time
dependency. The context is built on the first update_with_buffer, not at
window creation, so a caller driving the wl_surface through
raw-window-handle never gets a competing EGL surface on it.

Geometry mirrors src/native/posix/scalar.c pixel for pixel, integer
truncation and nearest-neighbour sampling offset included, so toggling
WindowOptions::use_gpu cannot shift the image.

Only Wayland is wired up; X11 still scales in software.
@emoon
emoon requested a balanced review from Copilot August 29, 2026 18:28
Copilot AI left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

The GL sampler differs from the software scaler, and shader linking fails on GLES2 implementations without fragment high precision.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds optional EGL/GLES2 GPU presentation for Wayland with automatic software fallback.

Changes:

  • Adds the UseGPU window option and Wayland GL lifecycle.
  • Implements dynamic EGL/GLES2 loading, scaling, and tests.
  • Updates versioning and release documentation.
File summaries
File Description
src/os/posix/gl.rs Implements EGL/GLES2 rendering and tests.
src/os/posix/wayland.rs Integrates GPU presentation and fallback.
src/os/posix/mod.rs Registers the GL module.
src/lib.rs Adds the public GPU option.
Cargo.toml Bumps the crate version.
README.md Updates the dependency version.
CHANGELOG.md Documents the feature and API break.
.gitignore Ignores aider state files.
Review details
  • Files reviewed: 7/8 changed files
  • Comments generated: 3
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/os/posix/gl.rs Resolved
Comment thread src/os/posix/gl.rs Resolved
Comment thread src/os/posix/gl.rs Resolved
The resize loops stepped a 10.10 fixed-point ratio, quantising the step
to 1/1024. On a non-integer scale factor that drifts: 320 -> 641 first
picks the wrong source column at 513, and 320 -> 3840 misses on 2430 of
3840 columns by up to two pixels. Walk an integer remainder instead,
which indexes floor(j * src / dst) exactly with no per-pixel divide.

The GPU path maps the exact ratio, so this is also what makes the two
paths pixel-identical rather than merely close; the new test pins the C
indexing to that formula.

Also declare the v_uv varying with matching explicit precision in every
stage. GLSL ES 1.00 does not require varying precision to match, but a
mediump uv cannot address texels of a texture wider than ~1024, which is
what GL_NEAREST needs it to do.
@emoon
emoon requested a balanced review from Copilot August 29, 2026 19:03
Copilot AI left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

The scaler has severe downscaling regressions, while the GPU path can misrender on limited-precision hardware and mishandle oversized dimensions.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (1)

src/native/posix/scalar.c:96

  • This duplicate resize loop has the same unbounded carry cost: a large source scaled into a narrow pillarbox performs roughly src_width carry iterations for every destination row, even though it writes only dst_width pixels. Use precomputed quotient/remainder steps here as well so this path remains O(destination pixels).
            rem_x += src_width;
            while (rem_x >= dst_width) {
                rem_x -= dst_width;
                sx++;
            }
  • Files reviewed: 9/10 changed files
  • Comments generated: 3
  • Review effort level: Balanced

Comment thread src/native/posix/scalar.c Resolved
Comment thread src/os/posix/gl.rs Resolved
Comment thread src/os/posix/wayland.rs Resolved
Three fixes from the PR review:

The exact-indexing rewrite carried `src` and subtracted `dst` until it
fit, which is `src / dst` iterations per pixel -- 220M for the 2.2M -> 100
case the tests cover, against 10k before. Precompute the quotient and
remainder so each pixel costs one add and at most one carry. Same output,
measured 39ms -> 0.01ms for that blit.

Require highp in the fragment language rather than falling back to
mediump. mediump guarantees 2^-10 relative precision, which cannot
resolve adjacent texels above ~1024 wide, so the fallback sampled the
wrong texel on buffers GL_MAX_TEXTURE_SIZE accepts. #error makes the
shader ill-formed, so such a driver takes the software path instead.

Take the buffer dimensions as usize in present() and convert with
try_from. Casting wrapped a width past i32::MAX to a negative one, which
slipped under the size check, skipped the upload and reported success --
presenting a cleared window instead of falling back. A zero height lets
any width through check_buffer_size, so it was reachable from safe code.
@emoon
emoon requested a balanced review from Copilot August 30, 2026 05:30
Copilot AI left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

The EGL fallback can pass an incompatible native display pointer, and texture uploads corrupt colors on big-endian targets.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

src/os/posix/gl.rs:856

  • This upload assumes little-endian u32 layout. On a big-endian Linux/BSD target, an API pixel 0x00RRGGBB is stored as bytes 00 RR GG BB, but GL_BGRA/GL_UNSIGNED_BYTE consumes those bytes as B, G, R, A, so the default GPU path swaps/corrupts channels (and alpha) while the software path retains the documented bit layout. Either disable this path on big-endian targets or byte-swap/repack pixels before upload.
                GL_BGRA,
                GL_UNSIGNED_BYTE,
                pixels as *const c_void,
  • Files reviewed: 9/10 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread src/os/posix/gl.rs Resolved
platform_display no longer falls back to eglGetDisplay when neither
platform entry point resolves. That case is EGL 1.4 without
EGL_EXT_platform_base, which has not shipped alongside a working
libwayland-egl in a decade, so the fallback only traded a guaranteed
software path for a driver that may read the wl_display* as an X11
Display* and fault -- on the path UseGPU::Auto puts everyone by default.

Reject big-endian targets. GL_BGRA with GL_UNSIGNED_BYTE consumes the
texture as bytes, which matches minifb's 0x00RRGGBB u32 only on
little-endian; on big-endian GL reads the padding byte as blue and blue
as alpha. GLES2 has no swizzle or packed 8888 format to say it another
way, and byte-swapping every frame would spend the CPU pass this path
exists to avoid, so those targets take the software scaler.
@emoon
emoon requested a balanced review from Copilot August 30, 2026 05:42
Copilot AI left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Large-dimension sampling precision and zero-dimension behavior can diverge from the software path.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 9/10 changed files
  • Comments generated: 2
  • Review effort level: Balanced

Comment thread src/os/posix/gl.rs Resolved
Comment thread src/os/posix/gl.rs Resolved
The uv range carried a `0.5 / (src * dst)` nudge meant to land
`GL_NEAREST` on the same texel the C scalers index. At real sizes that
nudge is under one f32 ULP -- 1080p into a 4K window is already there --
and GLES2 guarantees `highp` only 2^-16 relative precision, a hundred
times coarser than the gap it has to resolve. The phase it bought could
not be relied on, so drop it and sample the whole texture at fragment
centres, which is what the macOS Metal path has always done.

The two paths now differ by up to half a destination pixel, and by
nothing at all for a 1:1 blit or a whole-number scale. Say so in the
`UseGPU` docs and the changelog instead of claiming they match.

An empty frame also has to follow the scaler it replaces:
`image_resize_linear` returns without writing, leaving the previous
frame up, while the other three clear to the background first. Presenting
a cleared frame for `Stretch` changed what a window showed depending on
`use_gpu`, for a size `check_buffer_size` accepts. The first frame still
swaps -- there is nothing to preserve yet, and an unattached Wayland
surface is not mapped at all.

The sampling test restated `floor(j * src / dst)` instead of calling the
scaler, so it could not disagree with the code it checked; it now reads
the indices back out of `image_resize_linear` itself, per axis, over
sizes up to 4K and 2.2M. Against the pre-90fe901 fixed-point scaler it
fails at column 1027 of 2560 -> 3840, which the old test could not see.
@emoon
emoon requested a balanced review from Copilot August 30, 2026 06:31
Copilot AI left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommend 8BD0 ed

A rebuilt EGL context incorrectly clears zero-dimension Stretch frames instead of preserving existing Wayland surface content.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 9/10 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread src/os/posix/gl.rs Resolved
A zero-dimension buffer under `Stretch` skips the swap whether or not
anything has been presented, dropping the bookkeeping that made the
first frame an exception.
@emoon
emoon merged commit 5313d22 into master Aug 30, 2026
8 checks passed
@emoon
emoon deleted the feat/wayland-gl branch August 30, 2026 06:56
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.

3 participants

0