Skip to content

Don't leak the vertex shader if the fragment shader fails to compile - #1063

Open
Coastrnaut wants to merge 1 commit into
projectM-visualizer:masterfrom
Coastrnaut:fix/shader-compile-failure-leak
Open

Coastrnaut wants to merge 1 commit into
projectM-visualizer:masterfrom
Coastrnaut:fix/shader-compile-failure-leak

Conversation

@Coastrnaut

@Coastrnaut Coastrnaut commented Nov 8, 2026 •

Copy link
Copy Markdown

Shader::CompileProgram() compiles the vertex shader, then the fragment shader. If the fragment shader fails to compile, CompileShader() throws and the already compiled vertex shader object is never deleted. This happens every time a preset whose (transpiled) shader doesn't compile is loaded, so apps that keep switching presets slowly accumulate shader objects and the driver memory behind them.

This change deletes the vertex shader before rethrowing. The successful path and the thrown exception are unchanged.

Related to #1000. This removes one source of growth there, but not all of it: in my tests the rest of the growth is held by the GL driver (NVIDIA) and grows mainly the first time each new preset is used.

Measurements

Windows, RTX A6000, GL 3.3 core, presets switched with projectm_playlist_play_next() while the preset is locked (the setup from #1000):

master this PR
200 switches, alternating a preset with a failing shader and a working one: leaked shader objects 100 0
... heap growth 16.8 MB 2.6 MB
1,500 soft-cut switches through cream-of-the-crop: leaked shader objects 3 0
... private memory growth 199.9 MB 144.6 MB
... heap growth 61.5 MB 47.3 MB

Backwards compatibility

I loaded all 14,536 presets from cream-of-the-crop, en-d, milkdrop-original and projectm-classic one after another, rendering 30 frames each with fixed audio and frame times, and logged every glCompileShader/glLinkProgram result, failed preset loads, live shader objects and frame hashes. For the pixel comparison both builds had their random seeds fixed (a local patch only, identical on both sides).

  • Shader compile results, link results and failed loads: identical for all presets.
  • Rendered frames: identical for all 14,535 presets that render deterministically. One preset doesn't render the same way twice even on master; its frames from this PR match one of the master runs.
  • The 65 presets whose fragment shader fails to compile render the same fallback as before.
  • Leaked shader objects at the end: master 65, this PR 0.

Unit tests pass. There's no new unit test because the test suite doesn't create a GL context.

#1026 contained the same change as part of a larger, closed PR.

Retested on current master (2026-11-12)

Shader::CompileProgram() compiled the vertex shader, then threw from
the fragment shader compilation without deleting the vertex shader
object. Every load of a preset whose shader the driver rejects leaked
one shader object, including the driver memory backing it.

Measured with an NVIDIA driver, alternating between such a preset and
a working one for 200 switches: 100 leaked shader objects and 16 MB of
heap growth before, no leaked objects and 2 MB after.

Related to projectM-visualizer#1000.

This branch has not been deployed

No deployments
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.

1 participant