Skip to content

Spread the credit roll's GPU work out instead of landing it in one frame - #24

Merged
ZombieHDGaming merged 1 commit into
masterfrom
claude/credits-marquee-performance-k3u940
Sep 7, 2026
Merged

Spread the credit roll's GPU work out instead of landing it in one frame#24
ZombieHDGaming merged 1 commit into
masterfrom
claude/credits-marquee-performance-k3u940

Conversation

@ZombieHDGaming

Copy link
Copy Markdown
Contributor

Four things in the Credits Marquee's per-frame chain could stall a frame for long enough to be seen, and none of them had to. Nothing here changes what is drawn — every optimization is quality-neutral by construction, and the layout, rasterization and playback paths are untouched.

The four

A finished strip was uploaded whole, inside the video_render it arrived in. A ten-minute roll is thirty-odd 2048 px tiles and several hundred megabytes of gs_texture_create in a single frame — a hitch not only in the roll but in whatever else OBS was compositing at the time. It landed on every rebuild: a designer edit, a scene collection load, a style the library moved underneath the roll.

The strip is now adopted with its pictures still in hand, and each tile goes to the GPU as it is reached — one further tile per frame ahead of the roll, and never two in the same frame. What reaches the screen does not depend on the spreading: a tile that is about to be drawn is uploaded first, in the same frame, before the pass that draws it is started. The memory peak is no worse either, since every picture was in RAM at once anyway while the old code uploaded them one after another, and each is let go of as it goes up.

Every tile was walked and handed to gs_effect_set_texture on every frame, including the ninety-eight of a hundred with nothing on screen — a per-frame cost that grew with the length of the roll for no picture at all. Tiles are slices of one strip held in strip order, so the visible ones are a single run; the draw walks that run.

The style library was asked whether its file had moved from video_tick, which put a stat on the thread compositing the program, once a second, for every roll on the machine. Rare enough to be invisible on a warm cache, and exactly the kind of stall that reads as the roll catching on a cold one or behind a filter driver. The stat is now asked for on the render thread and the tick compares StyleLibrary::serial() against the number this roll last saw.

That also fixes a real bug on the way: the poll has one answer to give, so with two credit sources on the machine, whichever one's turn came up consumed the change and the other went on rendering the style the library no longer held. A serial per source cannot be consumed by somebody else reading it.

The smaller ones. A sticky block's texture is allocated a screenful before it is needed rather than in the frame a closing card arrives in — the worst moment in the roll to spend several megabytes. Tiles and blocks are created as static textures rather than dynamic ones, since neither is ever rewritten. The animated logos' frame upload moves out of the technique's pass, which gs_effect_loop may run more than once. The render key is compact JSON bytes rather than a pretty document-sized QString, which matters because it is built on every update — once per frame of a slider drag. And a roll with no sticky blocks stops walking a list to find that out.

Testing

  • closing-time-tests: 110 suites, 6052 checks, 0 failures.
  • One new suite, style_library_watchers, pins down the contract the source now relies on: reading the serial does not consume the change, so every watcher learns about it rather than only the first to ask.
  • Plugin and harness build clean with no new warnings; clang-format clean.
  • The changed code is the source's graphics-thread path, which the offscreen harness does not compile — worth a look on air with a long roll and a rebuild mid-playback.

ARCHITECTURE.md is updated for both the upload path and the split poll.

🤖 Generated with Claude Code

https://claude.ai/code/session_01RV76Q3W4ZTYmgVwQNGpise


Generated by Claude Code

Four things in the marquee's per-frame chain could stall a frame for long
enough to be seen, and none of them had to.
A finished strip was uploaded whole, inside the video_render it arrived in.
A ten-minute roll is thirty-odd 2048 px tiles and several hundred megabytes
of gs_texture_create in a single frame -- a hitch not only in the roll but
in whatever else OBS was compositing. The strip is now adopted with its
pictures still in hand and each tile goes up as it is reached, with one
further tile per frame ahead of the roll and never two in the same frame.
Nothing about what reaches the screen depends on the spreading: a tile that
is about to be drawn is uploaded first, in the same frame. The memory peak
is no worse either -- every picture was in RAM at once anyway while the old
code uploaded them one after another, and each is let go of as it goes up.
Every tile was then walked and handed to gs_effect_set_texture every frame,
including the ninety-eight of a hundred with nothing on screen. Tiles are
slices of one strip held in strip order, so the visible ones are a single
run; the draw walks that run.
The style library was asked whether its file had moved from video_tick,
which put a stat on the thread compositing the program once a second per
roll. The stat is now asked for on the render thread and the tick compares
StyleLibrary::serial() against the number this roll last saw. That also
fixes a real bug: the poll has one answer to give, so with two credit
sources on the machine, whichever one's turn came up consumed the change
and the other went on rendering the style the library no longer held.
The rest is smaller. A sticky block's texture is allocated a screenful
before it is needed rather than in the frame a closing card arrives in;
tiles and blocks are created as static textures rather than dynamic ones,
since neither is ever rewritten; the animated logos' frame upload moves out
of the technique's pass, which gs_effect_loop may run more than once; the
render key is compact JSON bytes rather than a pretty document-sized
QString; and a roll with no sticky blocks stops walking a list to find that
out.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RV76Q3W4ZTYmgVwQNGpise
@ZombieHDGaming
ZombieHDGaming merged commit cf258ff into masterSep 7, 2026
6 checks passed
@ZombieHDGaming
ZombieHDGaming deleted the claude/credits-marquee-performance-k3u940 branch September 7, 2026 13:58
Sign up for freeto 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

@ZombieHDGaming@claude