Repository navigation
feat(stlxdm): presented frames through two display buffers - #459
Conversation
Every rect composed in a frame blended the whole pointer again, so a target kept between frames could hold it blended several times over. The resize outline is clipped too, or its fill would cover the pointer.
stlxdm copies each frame into the display buffer off screen, so a working display never shows one partly copied, and composes the next once its present ended. Without such buffers it uses the framebuffer.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit c9a6ecc. Configure here.
| } | ||
|
|
||
| changed.clear(); | ||
| m_present_pending = true; |
There was a problem hiding this comment.
Failed presents are never retried
Medium Severity · Logic Bug
A refused GFXFB_PRESENT keeps m_unpresented but never marks a present as waiting, and compose_tick still clears m_damage. The main loop then sees no compose work and will not call present() again until some later input or animation dirties the screen, so a frame that is already copied into the back buffer can stay invisible after a transient refuse (ENOMEM, ERR_REFUSED).
Additional Locations (2)
Reviewed by Cursor Bugbot for commit c9a6ecc. Configure here.
There was a problem hiding this comment.
Checked: accurate, and kept on purpose. A refused present keeps the back buffer's list of regions changed since its last accepted present, so the refused frame, already copied into that buffer, goes out with the next present, which the next damage brings at most once per refresh period. The memcpy presenter recovers from a failed flush the same way, sending the whole screen with its next flush. The bar's clock repaints every second, so on the desktop that wait is at most a second, and behind the power overlay, whose clock is frozen, it lasts until the next input once the overlay stops animating. Two boots that refused one present with ENOMEM through this path saw the next present 39 ms later at startup and 76 ms later on the idle desktop, each one naming every refused region and latching.
A timed retry would mostly fail again: EIO means a failed device as well as a refused buffer, and EINVAL after a mode change or EAGAIN on a dark output lasts until output events recreate the buffers or relight the output, so retrying every refresh period would keep stlxdm waking on a dead or dark display. Re-presenting after those belongs with the output events. No change made.


Summary
stlxdm, the desktop's compositor, drew every frame straight into the framebuffer the screen shows, on a timer of its own, and used none of /dev/gfxfb's display buffers and presents, which show a frame only once it is complete and report when it reaches the screen.
Changes
flip_presenterin stlxdm's presenter.cpp creates two display buffers at the primary output's mode withGFXFB_CREATE_BUFFERand copies each composed frame's changes into the one off screen. It presents that buffer withGFXFB_PRESENT, naming the regions changed since the buffer's own last accepted present, and the two swap roles only when a present's end reports that it latched, meaning the output shows its buffer from then on.end_fd()andtake_end(), so stlxdm's main loop polls /dev/gfxfb while a present waits for its end and composes the next frame once it has ended, still at most once per refresh period.presenter::present()itself waits for that end when it has not come, since the splash presents without polling.open_presenter()in main.cpp falls back to thememcpy_presenterthat copies into the framebuffer when the display cannot create buffers, as gfxfb's boot display cannot, or any other step of the setup fails, and the boot log names the step. While another program controls the display's buffers (EBUSY) it tries again every 100 ms instead, since frames drawn into the framebuffer would not show.cursor::drawblends the pointer only inside the rect being composed, as the tooltip, star and overlay draw, anddecor::draw_outlineclips the resize outline the same way so its fill cannot cover the pointer. Each composed rect blended the whole pointer again, thickening it by one blend for each rect a frame composed after the one that repainted it, and two display buffers would hold it at different thicknesses, changing its look whenever they swap.Note
Medium Risk
Touches core display presentation and compositor timing on
/dev/gfxfb, with framebuffer fallback; drawing changes are localized to cursor/outline clipping.Overview
stlxdm no longer paints only into the visible framebuffer on a fixed timer. It prefers a
flip_presenterthat composes into a cached buffer, copies dirty regions into one of two/dev/gfxfbdisplay buffers, and **GFXFB_PRESENT**s whole frames; buffer roles swap when a present-end event reports latch.The
presenterAPI addsshutdown(),end_fd(), andtake_end(). The main loop polls the gfxfb fd while a present is pending and skipscompose_tickuntil that present finishes (still capped at one compose per refresh).open_presenter()retriesflip_presenter::init()onEBUSY(another client owns buffers), then falls back tomemcpy_presenter.cursor::drawanddecor::draw_outlinetake a composecliprect and draw through a sub-surface view so the pointer is not alpha-blended multiple times per frame (which had thickened the cursor across partial compose rects and mismatched flip buffers).Reviewed by Cursor Bugbot for commit c9a6ecc. Bugbot is set up for automated code reviews on this repo. Configure here.