Skip to content

shader-based scrolling wallpaper mode#1802

Draft
PandorasFox wants to merge 2 commits into
AvengeMedia:masterfrom
PandorasFox:master
Draft

shader-based scrolling wallpaper mode#1802
PandorasFox wants to merge 2 commits into
AvengeMedia:masterfrom
PandorasFox:master

Conversation

@PandorasFox
Copy link
Copy Markdown

@PandorasFox PandorasFox commented Feb 23, 2026

some notes:

  • needs to be run with QSG_USE_SIMPLE_ANIMATION_DRIVER=1 qs -p quickshell if your display refresh rate is >60hz (rolled into the .service file, potentially undesirable on devices that might want to throttle based on battery status? needs investigation beyond the desktop case..)
  • plays nice with Lockscreen shader positioning, but haven't tested the greeter (seems fine/non-impacting since the greeter is an earlier stage?)
  • haven't tested edge cases with display plugs/mode changes

overall much happier with this approach than the lazy software render impl; did require some boilerplating of the 8th fill mode across the shaders.

@PandorasFox
Copy link
Copy Markdown
Author

rebased for 85b00d3, i think checks should pass now

@bbedward
Copy link
Copy Markdown
Collaborator

bbedward commented Mar 1, 2026

Sorry, havent had time to take a look at this - will get to it soon.

@Purian23
Copy link
Copy Markdown
Collaborator

Purian23 commented Apr 8, 2026

Thanks for getting it up to date, we will get this one reviewed. Apologies for the delay!

@PandorasFox
Copy link
Copy Markdown
Author

that's alright :) I needed to rebase & I got around to one last fix, it's otherwise been good for my daily driving for the past while

@PandorasFox
Copy link
Copy Markdown
Author

PandorasFox commented May 2, 2026

cleaned up the branch; should be a smaller diff now.

Finally figured out the weird wl_output surface reseating issue I was having, turns out it was racing against if the unlock surface had cleared before or after output reconnect.

@Purian23
Copy link
Copy Markdown
Collaborator

Purian23 commented May 2, 2026

Thanks @PandorasFox, I think the largest hurdle here is having to prefix QSG_USE_SIMPLE_ANIMATION_DRIVER=1 qs -p quickshell to use it. Users will expect it to just work out of the box and somehow we have to bridge that gap. Your latest efforts may have addressed this.

@PandorasFox PandorasFox marked this pull request as draft May 2, 2026 04:47
@PandorasFox
Copy link
Copy Markdown
Author

yeah, the need for the 'simple' animation driver to get >60hz frame callbacks from quickshell is still kinda funky to me. As far as I could tell, the default animation driver always runs in 16ms ticks, so >60hz updates weren't possible - which leads to bad motion on higher refresh rate panels, compared to niri's workspace switch animations. if there's another way to get unthrottled frame hints/callback timers, or properly vsynced frame callbacks in the default driver, that'd be great, but I don't think such an interface existed last I checked.

If there's a way to just dynamically change the QSG animation driver for just the wallpaper renderer, when in scrolling mode, that might also be ideal - though, I think that might have weird implications for blurred foreground surfaces (e.g. popouts) that then update at 60hz?

I'm poking at the output/surface re-init after display events and putting this back in draft for a bit, since I think the advent of blur has given me a good bit more to test with this.

A new `scrolling` wallpaper fill mode that translates the wallpaper
crop with the active workspace, similar to android wallpapers & lateral
launcher pages, but with vertically-scrolling workspaces, instead.

The image is scaled to cover the screen on its non-scroll axis;
the workspace index drives a fractional offset into the cropped overflow
on the scroll axis.

Per-monitor scroll position is published into SessionData so the lock
screen renders the same crop as the active workspace, preserving
visual continuity across lock/unlock. Spring-driven scroll animations
run at native refresh on high-Hz displays
(QSG_USE_SIMPLE_ANIMATION_DRIVER=1 exported to the spawned quickshell
process).

Survives wl_output rebind cycles (e.g. OLED image-cleaning when
DPMS soft-off) via output-lifecycle re-anchoring of the scroll target,
ShaderEffect rebuild against the current render context, and a
wallpaper-layer surface re-attach on unlock for parallax-active monitors.

Issues encountered included, but were not limited to:
- race conditions upon unlock/display reseat: inconsistent void/'stuck'
  wallpaper backgrounds. Resurfacing needed to be guarded against unlock
  state so that the shader gets reliable frame hints.
- default QSG animation driver always throttling/frame-hinting at 16ms,
  which lead to disjointed motion pacing between compositor animations
  and wallpaper shader
@PandorasFox
Copy link
Copy Markdown
Author

here's where things are presently:

  • all the foreground blurs work happily with the constantly-changing background layer :)
  • adding a new window to the last workspace causes an instant shift from niri adding a new workspace after it. when closing those windows, the extra empty workspace isn't popped until after the workspace switch event, leading to some mild resettling
  • frame or two of blank void visible after unlock/relock due to resurfacing
    • could perhaps tighten this to only after display reseats
    • could maybe also try to make the new surface before destroying the old, to try and avoid the one frame of void upon unlock?
2026-05-02.11-52-04.mp4

overall it works reasonably well, but the re-surfacing bits to survive output reseats could perhaps be polished a bit more - i recall seeing a release note a while ago about something similar for the bar re-surfacing itself to survive output reseats, so I can look into standardizing this a bit more?

the other note is that wallpaper switching blanks to void while loading rather than doing a transition effect; I figure there's room for bridging that w/ the state the lockscreen uses (+ not killing the old canvas immediately? haven't dug into that one still)

After the spring settles, the wallpaper window's wayland surface stops
committing — `updatesEnabled` drops, and even within the 1s
`_renderSettling` tail nothing is actually dirty, so QSG skips render+
commit. The compositor in turn drops the surface from its frame
schedule. The next workspace switch then cold-starts: the first ~3
frames land late while the surface is reinserted, producing visible
hitching on the leading edge of the spring.

Add a 30Hz heartbeat timer that, while parallax is active and the
spring isn't running, bumps a `_parallaxHeartbeat` property bound as
an unused uniform on the parallax `ShaderEffect`. Property change
dirties the effect, QSG renders+commits a single frame per tick, the
`wl_surface.frame` callback chain stays alive, and the surface keeps
its slot in the compositor's vsync schedule.

Heartbeat naturally pauses when `frameAnim.running` is true (real
spring motion already drives commits at native rate) and resumes the
moment it stops.
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