Add preset preload API with optional background shader compilation (targeting 4.3)#1012
Draft
keeper-of-memes wants to merge 1 commit into
Draft
Conversation
Applications that own their preset rotation (know current/next ahead of time) previously had no way to prepare the next preset before switching: projectm_load_preset_file() creates AND initialises the preset inside the switch, so complex Milkdrop presets stall the render thread for hundreds of milliseconds (600-1200 ms measured on an Apple TV 4K / A15) at the exact moment the transition starts. New C API (all opt-in; existing behaviour unchanged): - projectm_preload_preset_file(): create and fully initialise a preset (including shader compilation) without switching to it. One preset can be staged at a time. - projectm_activate_preloaded_preset(): switch to the staged preset, skipping re-initialisation. Returns false (caller falls back to a normal load) if nothing is staged or the window size changed since the preload. - projectm_has_preloaded_preset(): query whether a preset is staged. - projectm_preloaded_preset_compile_ready(): non-blocking readiness poll for the deferred background shader compiles (below). During preload, Renderer::Shader defers everything past glLinkProgram. Drivers that implement GL_KHR_parallel_shader_compile (ANGLE does) background the compile/link on a worker pool, but ANY program-observing call synchronously resolves the link on the calling thread. That includes the glDetachShader immediately after glLinkProgram in the normal path. With the deferral, the link keeps running in the background during the preload lead time; readiness is polled via the non-resolving GL_COMPLETION_STATUS_KHR query, and activation finalises the program (status check, detach/delete, and the same per-shader failure fallbacks as the synchronous path). Bind()/Validate() finalise lazily as a safety net, so a pending shader is always safe to use. On drivers without background compilation the deferral is harmless; the link is simply already complete at finalise time. Texture purging moves to activation (not preload) so a speculative preload can't age/evict textures the active preset still uses. Measured on tvOS/ANGLE (Metal backend): preset-switch initialisation spike drops from ~651 ms to ~133 ms average with preload alone; with deferred compilation the render-thread cost of glLinkProgram drops from ~33-55 ms to 0.04-0.10 ms per preset, with the background link completing over 1-4 frames and finalisation at activation taking 0.003-3.1 ms.
Author
|
Update from production use: the preload + deferred-compile path is now also running on Apple TV HD (A8) hardware. Two observations relevant to review: (1) the mechanism degrades gracefully on much slower GPUs — background compiles simply take more frames to report ready, and activation still finalises quickly; (2) on constrained devices the application benefits from throttling how much preload work happens per frame, which is entirely app-side policy — no API change needed, the non-blocking readiness poll is sufficient. Still targeting 4.3; will rebase after #970/#971/#1003. |
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 free
to 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.
Problem
projectm_load_preset_file()creates AND initialises the preset inside the switch, so complex Milkdrop presets stall the render thread for hundreds of milliseconds (600–1200 ms measured on an Apple TV 4K / A15) at the exact moment the transition starts. Applications that own their preset rotation, and therefore know the next preset ahead of time, have no way to prepare it in advance.New C API (all opt-in; existing behaviour unchanged)
projectm_preload_preset_file(): create and fully initialise a preset (including shader compilation) without switching to it. One preset can be staged at a time; a subsequent call replaces it. On failure the preset-switch-failed callback fires and nothing is retained.projectm_activate_preloaded_preset(): switch to the staged preset, skipping re-initialisation. Returns false (caller falls back to a normal load) if nothing is staged or the window size changed since the preload. Initialize() bakes the surface size into GL resources, so a stale preload is discarded.projectm_has_preloaded_preset(): query whether a preset is staged.projectm_preloaded_preset_compile_ready(): non-blocking readiness poll for the deferred background shader compiles (below).Texture purging moves to activation (not preload) so a speculative preload can't age or evict textures the active preset still uses.
Background shader compilation
During preload,
Renderer::Shaderdefers everything pastglLinkProgram. Drivers that implementGL_KHR_parallel_shader_compile(ANGLE does) background the compile/link on a worker pool, but ANY program-observing call synchronously resolves the link on the calling thread. That includes theglDetachShaderimmediately afterglLinkProgramin the normal path: in ANGLE,Program::detachShadercallsresolveLink(), which blocks on the main link job plus all Metal shader-library compile subtasks. With the deferral, the link keeps running during the preload lead time; readiness is polled via the non-resolvingGL_COMPLETION_STATUS_KHRquery, and activation finalises the program (status check, detach/delete, and the same per-shader failure fallbacks as the synchronous path).Bind()/Validate()finalise lazily as a safety net, so a pending shader is always safe to use. On drivers without background compilation the deferral is harmless: the link is simply already complete at finalise time.Measurements (tvOS/ANGLE-Metal, Apple TV 4K / A15)
glLinkProgramon render thread25-preset sweep: every preload ready before its switch, 0 fallbacks, 0 link errors. 45-minute burn-in: no leaks, crossfade behaviour untouched.
Notes for reviewers
projectm_load_preset_file().Shader::SetDeferCompilation()is a render/GL-thread-only static flag, set only insidePreloadPresetFile()via a scope guard.Preset::PendingShaderCompileReady()/FinalizePendingShaderCompile()are virtuals with safe defaults (true/no-op), overridden byMilkdropPresetto aggregate warp and composite.