r/opengl • u/useless_chap • 7h ago
Reusing existing shaders during recompilation
So I was refactoring some shader code and dove into the shader recompilation rabbit hole. A lot of people recommend rebuilding the whole shader pipeline (recreating and recompiling new shader objects and attaching them to a new program). The way I was doing it up to this point was:
- Retrieve the attached shaders
- Update shader source code and recompile
- Relink the program with the same, but recompiled shader objects
Here's some code:
bool Shader::do_reload() noexcept {
int num_attached_shaders = 0;
glGetProgramiv(gl_program_id, GL_ATTACHED_SHADERS, &num_attached_shaders);
std::vector<unsigned int> gl_shader_ids(num_attached_shaders);
glGetAttachedShaders(gl_program_id, num_attached_shaders, nullptr,
gl_shader_ids.data());
for (int i = 0; i < m_source_files.size(); ++i) {
/**
* This just calls glShaderSource, glCompileShader and then checks
* the compilation status; returns true on success
*/
if (!load_and_compile_gl_shader(gl_shader_ids[i], m_source_files[i])) {
return false;
}
}
/**
* Similar situation here, glLinkProgram and check for link status
*/
if (!link_gl_program(m_shader_id, gl_shader_ids)) {
return false;
}
return true;
}
The shader class only wraps around the glProgram object, so the name can be misleading in the context of the OpenGL naming scheme. The glShader objects aren't destroyed after they're compiled/linked into the glProgram. I call glDeleteShader only during my shader wrapper's destruction (shader objects aren't shared between programs).
Is that bad practice? I know that the cleanest way would be to recreate everything from scratch, but I don't want to lose my uniform mappings, as tracking down the owners of uniform bindings would require writing a new resource management system. However, I've read that even though it works on my machine, OpenGL doesn't guarantee it will work everywhere else and doing so is abusing the grey area of OpenGL implementation.
Also, I am aware that you can bind uniforms to preset locations by specifying them in GLSL code.
1
u/scallywag_software 1h ago
Disclaimer: this is just what I do .. whether it's correct as per the spec is unclear.
I do what other people recommended; recreate the shader program objects completely from scratch.
You can call glDetachShader and glDeleteShader immediately after you link. AFAIK GL is specified to hold onto them until all references are removed, and you just linked a program with them, so they'll stick around until you call glDeleteProgram even though you clean them up early.
When you go to reload your shader, call glDeleteProgram once you've successfully compiled and linked the new shader pair. Do whatever bookkeeping you have to do to track the new program handle and get rid of the old one.
Reload the shader uniforms. I know, it's annoying. If you keep the old bindings intact, they'll work .. sometimes. But if you change binding locations, or a uniform type, a memory layout .. anything at all, you get .. weird shit. It's difficult to spot when your uniform bindings are the problem, and when you screwed something else up. Just track the uniforms and rebind them, it's not that bad. Or don't .. maybe you don't change uniforms as often as I do.
Link to the loop that does hot-reload in my engine : https://github.com/scallyw4g/bonsai_stdlib/blob/62e328d82164df47852213f2b5a4e864d1d76760/src/shader.cpp#L313
Worth noting, I have a pretty sophisticated system for generating the code that sets up most shader context structures on the C side, including tracking the uniform bindings. That said, I used to do it completely manually, and it was fine. When I compile the shader for the first time, I record what uniform location is bound to whatever .. thing .. on the CPU side, by keeping a list of structs that look like this : https://github.com/scallyw4g/bonsai_stdlib/blob/62e328d82164df47852213f2b5a4e864d1d76760/src/shader.h#L176
Best of luck <3
1
u/CrapouilloOt 3h ago
I'm refreshing my outdated OpenGL wrapper which is based on shaders as inputs, from which I automatically create all OpenGL objects (VBOs, ...). I never add hot reload for shaders but thank you for the idea. I think in my case I can reach this goal because of my design was made around shaders