Uh oh!
There was an error while loading. Please reload this page.
Spread the credit roll's GPU work out instead of landing it in one frame - #24
Merged
Merged
Conversation
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
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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_renderit arrived in. A ten-minute roll is thirty-odd 2048 px tiles and several hundred megabytes ofgs_texture_createin 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_textureon 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 astaton 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 comparesStyleLibrary::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_loopmay run more than once. The render key is compact JSON bytes rather than a pretty document-sizedQString, 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.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.clang-formatclean.ARCHITECTURE.mdis updated for both the upload path and the split poll.🤖 Generated with Claude Code
https://claude.ai/code/session_01RV76Q3W4ZTYmgVwQNGpise
Generated by Claude Code