Skip to content

Commit 171d522

Browse files
committed
Fix: ensure to preserve gl state of calling application
1 parent 3649044 commit 171d522

9 files changed

Lines changed: 204 additions & 11 deletions

File tree

src/libprojectM/MilkdropPreset/MilkdropPreset.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,12 +237,17 @@ void MilkdropPreset::Load(std::istream& stream)
237237

238238
void MilkdropPreset::InitializePreset(PresetFileParser& parsedFile)
239239
{
240+
// Save the currently bound framebuffer so we can restore it after creating attachments.
241+
// This is important when the host application uses a non-default framebuffer.
242+
GLint previousFramebuffer{};
243+
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &previousFramebuffer);
244+
240245
// Create the offscreen rendering surfaces.
241246
m_motionVectorUVMap = std::make_shared<Renderer::TextureAttachment>(GL_RG16F, GL_RG, GL_FLOAT, 0, 0);
242247
m_framebuffer.CreateColorAttachment(0, 0); // Main image 1
243248
m_framebuffer.CreateColorAttachment(1, 0); // Main image 2
244249

245-
Renderer::Framebuffer::Unbind();
250+
glBindFramebuffer(GL_FRAMEBUFFER, previousFramebuffer);
246251

247252
// Load global init variables into the state
248253
m_state.Initialize(parsedFile);

src/libprojectM/ProjectM.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include <Audio/PCM.hpp>
3030

3131
#include <Renderer/CopyTexture.hpp>
32+
#include <Renderer/GLStateGuard.hpp>
3233
#include <Renderer/PresetTransition.hpp>
3334
#include <Renderer/ShaderCache.hpp>
3435
#include <Renderer/TextureManager.hpp>
@@ -121,6 +122,12 @@ void ProjectM::RenderFrame(uint32_t targetFramebufferObject /*= 0*/)
121122
return;
122123
}
123124

125+
// Save the host application's OpenGL state so we can restore it after rendering.
126+
// This is required when libprojectM is used inside a shared OpenGL context,
127+
// e.g. a Qt QOpenGLWidget, where the host expects its GL state to be preserved.
128+
// The guard's destructor restores all captured state when this scope ends.
129+
Renderer::GLStateGuard glStateGuard;
130+
124131
// Update FPS and other timer values.
125132
m_timeKeeper->UpdateTimers();
126133

@@ -356,6 +363,9 @@ auto ProjectM::UserSpriteIdentifiers() const -> std::vector<uint32_t>
356363

357364
void ProjectM::BurnInTexture(uint32_t openGlTextureId, int left, int top, int width, int height)
358365
{
366+
GLint previousFramebuffer{};
367+
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &previousFramebuffer);
368+
359369
if (m_activePreset)
360370
{
361371
m_activePreset->BindFramebuffer();
@@ -368,7 +378,7 @@ void ProjectM::BurnInTexture(uint32_t openGlTextureId, int left, int top, int wi
368378
m_textureCopier->Draw(*m_shaderCache, openGlTextureId, m_windowWidth, m_windowHeight, left, top, width, height);
369379
}
370380

371-
Renderer::Framebuffer::Unbind();
381+
glBindFramebuffer(GL_FRAMEBUFFER, previousFramebuffer);
372382
}
373383

374384
void ProjectM::SetPresetLocked(bool locked)

src/libprojectM/Renderer/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ add_library(Renderer OBJECT
2626
FileScanner.hpp
2727
Framebuffer.cpp
2828
Framebuffer.hpp
29+
GLStateGuard.hpp
2930
IdleTextures.hpp
3031
Mesh.cpp
3132
Mesh.hpp

src/libprojectM/Renderer/CopyTexture.cpp

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,9 @@ void CopyTexture::Draw(ShaderCache& shaderCache,
104104
return;
105105
}
106106

107+
GLint previousFramebuffer{};
108+
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &previousFramebuffer);
109+
107110
std::shared_ptr<class Texture> internalTexture;
108111

109112
m_framebuffer.Bind(0);
@@ -125,7 +128,7 @@ void CopyTexture::Draw(ShaderCache& shaderCache,
125128
m_framebuffer.GetAttachment(0, TextureAttachment::AttachmentType::Color, 0)->Texture(internalTexture);
126129
}
127130

128-
Framebuffer::Unbind();
131+
glBindFramebuffer(GL_FRAMEBUFFER, previousFramebuffer);
129132
}
130133

131134
void CopyTexture::Draw(ShaderCache& shaderCache,
@@ -148,6 +151,9 @@ void CopyTexture::Draw(ShaderCache& shaderCache,
148151
return;
149152
}
150153

154+
GLint previousFramebuffer{};
155+
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &previousFramebuffer);
156+
151157
m_framebuffer.Bind(0);
152158

153159
// Draw from unflipped texture
@@ -162,7 +168,7 @@ void CopyTexture::Draw(ShaderCache& shaderCache,
162168
m_framebuffer.RemoveColorAttachment(0, 0);
163169
m_framebuffer.SetAttachment(0, 0, tempAttachment);
164170

165-
Framebuffer::Unbind();
171+
glBindFramebuffer(GL_FRAMEBUFFER, previousFramebuffer);
166172
}
167173

168174
void CopyTexture::Draw(ShaderCache& shaderCache,
@@ -186,6 +192,9 @@ void CopyTexture::Draw(ShaderCache& shaderCache,
186192
return;
187193
}
188194

195+
GLint previousFramebuffer{};
196+
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &previousFramebuffer);
197+
189198
std::shared_ptr<class Texture> internalTexture;
190199

191200
m_framebuffer.Bind(0);
@@ -200,7 +209,7 @@ void CopyTexture::Draw(ShaderCache& shaderCache,
200209
// Rebind our internal texture.
201210
m_framebuffer.GetAttachment(0, TextureAttachment::AttachmentType::Color, 0)->Texture(internalTexture);
202211

203-
Framebuffer::Unbind();
212+
glBindFramebuffer(GL_FRAMEBUFFER, previousFramebuffer);
204213
}
205214

206215
void CopyTexture::Draw(ShaderCache& shaderCache,

src/libprojectM/Renderer/Framebuffer.cpp

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ void Framebuffer::Unbind()
7878
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
7979
}
8080

81+
void Framebuffer::Unbind(GLuint defaultFramebufferObject)
82+
{
83+
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, defaultFramebufferObject);
84+
}
85+
8186
bool Framebuffer::SetSize(int width, int height)
8287
{
8388
if (width == 0 || height == 0 ||
@@ -86,6 +91,9 @@ bool Framebuffer::SetSize(int width, int height)
8691
return false;
8792
}
8893

94+
GLint previousFramebuffer{};
95+
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &previousFramebuffer);
96+
8997
m_width = width;
9098
m_height = height;
9199

@@ -98,7 +106,7 @@ bool Framebuffer::SetSize(int width, int height)
98106
glFramebufferTexture2D(GL_FRAMEBUFFER, texture.first, GL_TEXTURE_2D, texture.second->Texture()->TextureID(), 0);
99107
}
100108
}
101-
glBindFramebuffer(GL_FRAMEBUFFER, 0);
109+
glBindFramebuffer(GL_FRAMEBUFFER, previousFramebuffer);
102110

103111
return true;
104112
}
@@ -202,6 +210,9 @@ void Framebuffer::CreateColorAttachment(int framebufferIndex, int attachmentInde
202210
return;
203211
}
204212

213+
GLint previousFramebuffer{};
214+
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &previousFramebuffer);
215+
205216
auto textureAttachment = std::make_shared<TextureAttachment>(internalFormat, format, type, m_width, m_height);
206217
const auto texture = textureAttachment->Texture();
207218
m_attachments.at(framebufferIndex).insert({GL_COLOR_ATTACHMENT0 + attachmentIndex, std::move(textureAttachment)});
@@ -212,7 +223,7 @@ void Framebuffer::CreateColorAttachment(int framebufferIndex, int attachmentInde
212223
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachmentIndex, GL_TEXTURE_2D, texture->TextureID(), 0);
213224
}
214225
UpdateDrawBuffers(framebufferIndex);
215-
glBindFramebuffer(GL_FRAMEBUFFER, 0);
226+
glBindFramebuffer(GL_FRAMEBUFFER, previousFramebuffer);
216227
}
217228

218229
void Framebuffer::RemoveColorAttachment(int framebufferIndex, int attachmentIndex)
@@ -243,6 +254,9 @@ void Framebuffer::CreateDepthAttachment(int framebufferIndex)
243254
return;
244255
}
245256

257+
GLint previousFramebuffer{};
258+
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &previousFramebuffer);
259+
246260
auto textureAttachment = std::make_shared<TextureAttachment>(TextureAttachment::AttachmentType::Depth, m_width, m_height);
247261
const auto texture = textureAttachment->Texture();
248262
m_attachments.at(framebufferIndex).insert({GL_DEPTH_ATTACHMENT, std::move(textureAttachment)});
@@ -253,7 +267,7 @@ void Framebuffer::CreateDepthAttachment(int framebufferIndex)
253267
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, texture->TextureID(), 0);
254268
}
255269
UpdateDrawBuffers(framebufferIndex);
256-
glBindFramebuffer(GL_FRAMEBUFFER, 0);
270+
glBindFramebuffer(GL_FRAMEBUFFER, previousFramebuffer);
257271
}
258272

259273
void Framebuffer::RemoveDepthAttachment(int framebufferIndex)
@@ -268,6 +282,9 @@ void Framebuffer::CreateStencilAttachment(int framebufferIndex)
268282
return;
269283
}
270284

285+
GLint previousFramebuffer{};
286+
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &previousFramebuffer);
287+
271288
auto textureAttachment = std::make_shared<TextureAttachment>(TextureAttachment::AttachmentType::Stencil, m_width, m_height);
272289
const auto texture = textureAttachment->Texture();
273290
m_attachments.at(framebufferIndex).insert({GL_STENCIL_ATTACHMENT, std::move(textureAttachment)});
@@ -278,7 +295,7 @@ void Framebuffer::CreateStencilAttachment(int framebufferIndex)
278295
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, texture->TextureID(), 0);
279296
}
280297
UpdateDrawBuffers(framebufferIndex);
281-
glBindFramebuffer(GL_FRAMEBUFFER, 0);
298+
glBindFramebuffer(GL_FRAMEBUFFER, previousFramebuffer);
282299
}
283300

284301
void Framebuffer::RemoveStencilAttachment(int framebufferIndex)
@@ -293,6 +310,9 @@ void Framebuffer::CreateDepthStencilAttachment(int framebufferIndex)
293310
return;
294311
}
295312

313+
GLint previousFramebuffer{};
314+
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &previousFramebuffer);
315+
296316
auto textureAttachment = std::make_shared<TextureAttachment>(TextureAttachment::AttachmentType::DepthStencil, m_width, m_height);
297317
const auto texture = textureAttachment->Texture();
298318
m_attachments.at(framebufferIndex).insert({GL_DEPTH_STENCIL_ATTACHMENT, std::move(textureAttachment)});
@@ -303,7 +323,7 @@ void Framebuffer::CreateDepthStencilAttachment(int framebufferIndex)
303323
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, texture->TextureID(), 0);
304324
}
305325
UpdateDrawBuffers(framebufferIndex);
306-
glBindFramebuffer(GL_FRAMEBUFFER, 0);
326+
glBindFramebuffer(GL_FRAMEBUFFER, previousFramebuffer);
307327
}
308328

309329
void Framebuffer::RemoveDepthStencilAttachment(int framebufferIndex)

src/libprojectM/Renderer/Framebuffer.hpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,23 @@ class Framebuffer
7878

7979
/**
8080
* @brief Binds the default framebuffer for both reading and writing.
81+
*
82+
* When using libprojectM inside a shared OpenGL context,
83+
* the default framebuffer may not be FBO 0. Use the overloaded version with the
84+
* actual default FBO ID in such cases.
8185
*/
8286
static void Unbind();
8387

88+
/**
89+
* @brief Binds the given framebuffer ID as the current read/write framebuffer.
90+
*
91+
* This overload should be used when the host application's default framebuffer
92+
* object is not 0.
93+
*
94+
* @param defaultFramebufferObject The framebuffer ID to bind.
95+
*/
96+
static void Unbind(GLuint defaultFramebufferObject);
97+
8498
/**
8599
* @brief Sets the framebuffer texture size.
86100
*
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/**
2+
* @file GLStateGuard.hpp
3+
* @brief RAII guard that saves and restores OpenGL state around a rendering scope.
4+
*
5+
* This is used to ensure libprojectM doesn't leak GL state into the host
6+
* application's rendering pipeline. This is especially important when hosted
7+
* inside a shared OpenGL context such as Qt's QOpenGLWidget.
8+
*/
9+
#pragma once
10+
11+
#include "OpenGL.h"
12+
13+
namespace libprojectM {
14+
namespace Renderer {
15+
16+
/**
17+
* @brief RAII guard that captures OpenGL state on construction and restores it on destruction.
18+
*
19+
* Create an instance at the beginning of a rendering scope. When the instance goes out
20+
* of scope (or is explicitly destroyed), all captured state is restored.
21+
*/
22+
class GLStateGuard
23+
{
24+
public:
25+
/**
26+
* @brief Captures the current OpenGL state.
27+
*/
28+
GLStateGuard()
29+
{
30+
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &m_framebuffer);
31+
glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &m_vertexArray);
32+
glGetIntegerv(GL_CURRENT_PROGRAM, &m_program);
33+
glGetIntegerv(GL_ACTIVE_TEXTURE, &m_activeTexture);
34+
glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &m_arrayBuffer);
35+
glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &m_elementArrayBuffer);
36+
glGetBooleanv(GL_BLEND, &m_blend);
37+
glGetBooleanv(GL_DEPTH_TEST, &m_depthTest);
38+
glGetIntegerv(GL_BLEND_SRC_RGB, &m_blendSrcRgb);
39+
glGetIntegerv(GL_BLEND_DST_RGB, &m_blendDstRgb);
40+
#ifndef USE_GLES
41+
glGetBooleanv(GL_LINE_SMOOTH, &m_lineSmooth);
42+
#endif
43+
}
44+
45+
/**
46+
* @brief Restores the OpenGL state captured at construction time.
47+
*/
48+
~GLStateGuard()
49+
{
50+
// Restore framebuffer
51+
glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);
52+
53+
// Restore VAO (must be done before restoring buffer bindings)
54+
glBindVertexArray(m_vertexArray);
55+
56+
// Restore shader program
57+
glUseProgram(m_program);
58+
59+
// Restore active texture unit
60+
glActiveTexture(m_activeTexture);
61+
62+
// Restore buffer bindings
63+
glBindBuffer(GL_ARRAY_BUFFER, m_arrayBuffer);
64+
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_elementArrayBuffer);
65+
66+
// Restore blend state
67+
if (m_blend)
68+
{
69+
glEnable(GL_BLEND);
70+
}
71+
else
72+
{
73+
glDisable(GL_BLEND);
74+
}
75+
glBlendFunc(m_blendSrcRgb, m_blendDstRgb);
76+
77+
// Restore depth test state
78+
if (m_depthTest)
79+
{
80+
glEnable(GL_DEPTH_TEST);
81+
}
82+
else
83+
{
84+
glDisable(GL_DEPTH_TEST);
85+
}
86+
87+
#ifndef USE_GLES
88+
// Restore line smooth state
89+
if (m_lineSmooth)
90+
{
91+
glEnable(GL_LINE_SMOOTH);
92+
}
93+
else
94+
{
95+
glDisable(GL_LINE_SMOOTH);
96+
}
97+
#endif
98+
99+
// Reset color mask to default (projectM may have changed it via MaskDrawBuffer)
100+
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
101+
102+
// Unbind any sampler objects left bound by projectM on common texture units
103+
for (GLuint unit = 0; unit < 8; unit++)
104+
{
105+
glBindSampler(unit, 0);
106+
}
107+
}
108+
109+
GLStateGuard(const GLStateGuard&) = delete;
110+
GLStateGuard(GLStateGuard&&) = delete;
111+
auto operator=(const GLStateGuard&) -> GLStateGuard& = delete;
112+
auto operator=(GLStateGuard&&) -> GLStateGuard& = delete;
113+
114+
private:
115+
GLint m_framebuffer{};
116+
GLint m_vertexArray{};
117+
GLint m_program{};
118+
GLint m_activeTexture{};
119+
GLint m_arrayBuffer{};
120+
GLint m_elementArrayBuffer{};
121+
GLboolean m_blend{};
122+
GLboolean m_depthTest{};
123+
GLint m_blendSrcRgb{};
124+
GLint m_blendDstRgb{};
125+
#ifndef USE_GLES
126+
GLboolean m_lineSmooth{};
127+
#endif
128+
};
129+
130+
} // namespace Renderer
131+
} // namespace libprojectM

src/libprojectM/Renderer/PresetTransition.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ void PresetTransition::Draw(const Preset& oldPreset,
124124

125125
for (int i = 2; i < textureUnit; i++)
126126
{
127-
noiseDescriptors[i - 2].Unbind(textureUnit);
127+
noiseDescriptors[i - 2].Unbind(i);
128128
}
129129

130130
Mesh::Unbind();

0 commit comments

Comments
 (0)