Skip to content

Fix fenced Present freezes without disabling asynchronous rendering - #2

Merged
lfdevs merged 3 commits into
lfdevs:debian-unstablefrom
KiralyCraft:fix/present-fence-lifetime
Oct 8, 2026
Merged

lfdevs merged 3 commits into
lfdevs:debian-unstablefrom
KiralyCraft:fix/present-fence-lifetime

Conversation

@KiralyCraft

Copy link
Copy Markdown
Contributor

I was able to reproduce the freeze discussed in Mesa PR #105 on an unrooted Redmi Pad Pro 5G with an Adreno 710, using Anland and KDE Plasma Wayland. The problem was in XWayland's handling of presentation fences. Fixing it there allows unchanged Mesa with merged PR #96 to keep its asynchronous rendering path and its higher throughput.

What went wrong

A fence tells XWayland when an application's rendered frame is ready to use. In the COPY path, XWayland copies that frame into the window and tells the application that its source buffer is idle. The application is then allowed to reuse the buffer for another frame.

However, XWayland can still owe a completion notification for the old frame. When it returns to send that notification, it was checking the source fence again. By then, the application may have reset that fence for its next frame. XWayland therefore starts waiting for the wrong frame and can leave subsequent presentation requests blocked indefinitely.

There is also a separate callback-lifetime problem: a fence callback must be disarmed before retrying a Present request. Retrying can schedule the request for a later display tick rather than destroy it immediately. Leaving the callback armed allows it to run again for the same request.

Changes

The fixes are separate commits and follow the repository's existing Debian quilt packaging:

  1. Disarm the Present wait-fence callback before re-execution. If the request needs another wait, that path registers a fresh callback. This is the same correction previously made for Termux:X11: KiralyCraft/termux-x11@fc534e5
  2. Backport the upstream Sync trigger-list fix. A callback can remove other list entries, so the walker must restart rather than follow a saved next pointer. Apply this after commit 1, otherwise the restarted walk can keep invoking a still-armed Present callback: https://gitlab.freedesktop.org/xorg/xserver/-/commit/f19ab94ba9c891d801231654267556dc7f32b5e0
  3. In xwl_present_execute(), skip acquisition when event->copy_executed is already true. Acquisition still happens before the original copy; the later completion notification keeps its existing scheduling.

The callback fixes alone did not resolve the original benchmark stall. The COPY correction is also needed. Conversely, a negative-control build with the COPY fix and the upstream restart scan, but without the callback correction, reproduced the callback loop.

These changes do not disable render fences, force a CPU-side wait, change Mesa's presentation worker, increase buffer limits, or introduce a timer workaround. No temporary profiling code is included. The Present protocol already permits Idle before Complete: https://gitlab.freedesktop.org/xorg/proto/presentproto/-/raw/master/presentproto.txt

Validation

The test setup was Android 14 / kernel 5.10.209, Anland 5.13.3, Plasma 6.3.6, KWin 6.3.6-95, and XWayland 24.1.6-91. Benchmark windows were 800x600 on the tablet's approximately 60 Hz display. KWin's Mesa was kept unchanged while switching the client's private Mesa libraries. No screen recording or profiler ran during the performance comparisons.

Both protocol cases passed five times with the submitted series, and both full benchmark runs completed with exit code 0. Results below use the same patched XWayland for both Mesa variants:

Test Merged PR #96 Mesa, unchanged PR #105 Mesa
Full glmark2, all 33 scenes, 3 seconds per scene 418 286
Three-scene glmark2, median of 3 alternating runs 452 322
Uncapped glxgears, median of 6 five-second samples 468.7 FPS 333.8 FPS

The short glmark2 suite contains build without VBO, build with VBO, and texture. It is not a full-suite score. Full-suite throughput with the submitted series was about 46% higher than the PR #105 comparison, and matched the separately tested COPY-only fix's score of 418.

A separate diagnostic run recorded 1,227 Present requests on the final build. All 1,227 used ASYNC | COPY and nonzero wait fences. That run is excluded from the performance table; its purpose was to verify that the fix really retains fenced asynchronous presentation.

To reproduce the benchmark failure on the original server, use merged-PR-96 Mesa with uncapped presentation:

env -u LD_PRELOAD -u MESA_DRI3_DISABLE_PRESENT_WAIT_FENCE \
  vblank_mode=1 glmark2 --benchmark :duration=3

Run the same command with the patched server and the same Mesa. It should complete rather than stall in the first scene. For the uncapped gears comparison:

vblank_mode=0 glxgears -geometry 800x600

Synchronized glmark2 also completed with the final series, but its scene rates varied around 40-59 FPS.

Re-execution can queue a Present request for a later MSC and leave its fence alive. Clear the one-shot callback before retrying so a restarted Sync trigger scan cannot repeatedly fire the same request. A genuine new wait installs its own callback.

Port the independently tested Termux:X11 callback correction through the existing Debian quilt series. The protocol-only divisor/requeue test passes five times with the final series; omitting this correction from the restart-scan build reproduces a busy loop and missing completion.
Backport upstream f19ab94ba9c891d801231654267556dc7f32b5e0 through the Debian quilt series, preserving Olivier Fourdan attribution. A fired callback can remove other triggers, so restart from the live list head rather than dereference a saved next pointer.

Apply after the Present one-shot callback correction to avoid a restart-scan livelock when re-execution queues the request for a future MSC. The combined ARM64 build passes the protocol controls and completes the full 33-scene glmark2 suite with unchanged baseline Mesa.
After Idle, the client can reuse the pixmap and reset its wait fence for another frame. The old copied request must not acquire that source again when delivering its delayed Complete notification. A second wait can overwrite blocking_event with the copied request and leave subsequent ready frames stranded.

Guard the acquisition stage with !event->copy_executed. Keep the original acquisition, asynchronous rendering fences and completion scheduling unchanged. Do not add tracing, extra waits or timer workarounds.

On Adreno 710 Anland/KDE, the Mesa-independent Idle/reset/Complete regression test passes five times. The exact combined quilt series completes all 33 glmark2 scenes: unchanged merged-PR96 Mesa scores 418 versus 286 with PR105 on the same server. Original packaged and rebuilt reference servers stall in the first uncapped scene.
@lfdevs

lfdevs commented Oct 8, 2026

Copy link
Copy Markdown
Owner

Thanks for your PR. It successfully fixed the issue encountered on my device. Therefore, I changed PR lfdevs/mesa-for-android-container#105 to only retain the environment variable MESA_DRI3_DISABLE_PRESENT_WAIT_FENCE to optionally disable the present wait-fence bridge, and no longer change swap-interval handling.

@lfdevs
lfdevs merged commit ec68b90 into lfdevs:debian-unstable Oct 8, 2026
1 check passed
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.

2 participants