Skip to content

Commit cec2b7a

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

8 files changed

Lines changed: 197 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: 119 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,37 @@ void ProjectM::RenderFrame(uint32_t targetFramebufferObject /*= 0*/)
121121
return;
122122
}
123123

124+
// Save the host application's OpenGL state so we can restore it after rendering.
125+
// This is required when libprojectM is used inside a shared OpenGL context,
126+
// where the host expects its GL state to be preserved.
127+
GLint previousFramebuffer{};
128+
GLint previousVertexArray{};
129+
GLint previousProgram{};
130+
GLint previousActiveTexture{};
131+
GLint previousArrayBuffer{};
132+
GLint previousElementArrayBuffer{};
133+
GLboolean previousBlend{};
134+
GLboolean previousDepthTest{};
135+
GLint previousBlendSrcRgb{};
136+
GLint previousBlendDstRgb{};
137+
#ifndef USE_GLES
138+
GLboolean previousLineSmooth{};
139+
#endif
140+
141+
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &previousFramebuffer);
142+
glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &previousVertexArray);
143+
glGetIntegerv(GL_CURRENT_PROGRAM, &previousProgram);
144+
glGetIntegerv(GL_ACTIVE_TEXTURE, &previousActiveTexture);
145+
glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &previousArrayBuffer);
146+
glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &previousElementArrayBuffer);
147+
glGetBooleanv(GL_BLEND, &previousBlend);
148+
glGetBooleanv(GL_DEPTH_TEST, &previousDepthTest);
149+
glGetIntegerv(GL_BLEND_SRC_RGB, &previousBlendSrcRgb);
150+
glGetIntegerv(GL_BLEND_DST_RGB, &previousBlendDstRgb);
151+
#ifndef USE_GLES
152+
glGetBooleanv(GL_LINE_SMOOTH, &previousLineSmooth);
153+
#endif
154+
124155
// Update FPS and other timer values.
125156
m_timeKeeper->UpdateTimers();
126157

@@ -153,6 +184,13 @@ void ProjectM::RenderFrame(uint32_t targetFramebufferObject /*= 0*/)
153184
LoadIdlePreset();
154185
if (!m_activePreset)
155186
{
187+
RestoreGLState(previousFramebuffer, previousVertexArray, previousProgram,
188+
previousActiveTexture, previousArrayBuffer, previousElementArrayBuffer,
189+
previousBlend, previousDepthTest, previousBlendSrcRgb, previousBlendDstRgb
190+
#ifndef USE_GLES
191+
, previousLineSmooth
192+
#endif
193+
);
156194
return;
157195
}
158196

@@ -205,6 +243,83 @@ void ProjectM::RenderFrame(uint32_t targetFramebufferObject /*= 0*/)
205243

206244
m_frameCount++;
207245
m_previousFrameVolume = audioData.vol;
246+
247+
// Restore the host application's OpenGL state.
248+
RestoreGLState(previousFramebuffer, previousVertexArray, previousProgram,
249+
previousActiveTexture, previousArrayBuffer, previousElementArrayBuffer,
250+
previousBlend, previousDepthTest, previousBlendSrcRgb, previousBlendDstRgb
251+
#ifndef USE_GLES
252+
, previousLineSmooth
253+
#endif
254+
);
255+
}
256+
257+
void ProjectM::RestoreGLState(GLint framebuffer, GLint vertexArray, GLint program,
258+
GLint activeTexture, GLint arrayBuffer, GLint elementArrayBuffer,
259+
GLboolean blend, GLboolean depthTest,
260+
GLint blendSrcRgb, GLint blendDstRgb
261+
#ifndef USE_GLES
262+
, GLboolean lineSmooth
263+
#endif
264+
)
265+
{
266+
// Restore framebuffer
267+
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
268+
269+
// Restore VAO (must be done before restoring buffer bindings)
270+
glBindVertexArray(vertexArray);
271+
272+
// Restore shader program
273+
glUseProgram(program);
274+
275+
// Restore active texture unit and unbind any textures left on unit 0
276+
glActiveTexture(activeTexture);
277+
278+
// Restore buffer bindings
279+
glBindBuffer(GL_ARRAY_BUFFER, arrayBuffer);
280+
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementArrayBuffer);
281+
282+
// Restore blend state
283+
if (blend)
284+
{
285+
glEnable(GL_BLEND);
286+
}
287+
else
288+
{
289+
glDisable(GL_BLEND);
290+
}
291+
glBlendFunc(blendSrcRgb, blendDstRgb);
292+
293+
// Restore depth test state
294+
if (depthTest)
295+
{
296+
glEnable(GL_DEPTH_TEST);
297+
}
298+
else
299+
{
300+
glDisable(GL_DEPTH_TEST);
301+
}
302+
303+
#ifndef USE_GLES
304+
// Restore line smooth state
305+
if (lineSmooth)
306+
{
307+
glEnable(GL_LINE_SMOOTH);
308+
}
309+
else
310+
{
311+
glDisable(GL_LINE_SMOOTH);
312+
}
313+
#endif
314+
315+
// Reset color mask to default (projectM may have changed it via MaskDrawBuffer)
316+
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
317+
318+
// Unbind any sampler objects left bound by projectM on common texture units
319+
for (GLuint unit = 0; unit < 8; unit++)
320+
{
321+
glBindSampler(unit, 0);
322+
}
208323
}
209324

210325
void ProjectM::Initialize()
@@ -356,6 +471,9 @@ auto ProjectM::UserSpriteIdentifiers() const -> std::vector<uint32_t>
356471

357472
void ProjectM::BurnInTexture(uint32_t openGlTextureId, int left, int top, int width, int height)
358473
{
474+
GLint previousFramebuffer{};
475+
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &previousFramebuffer);
476+
359477
if (m_activePreset)
360478
{
361479
m_activePreset->BindFramebuffer();
@@ -368,7 +486,7 @@ void ProjectM::BurnInTexture(uint32_t openGlTextureId, int left, int top, int wi
368486
m_textureCopier->Draw(*m_shaderCache, openGlTextureId, m_windowWidth, m_windowHeight, left, top, width, height);
369487
}
370488

371-
Renderer::Framebuffer::Unbind();
489+
glBindFramebuffer(GL_FRAMEBUFFER, previousFramebuffer);
372490
}
373491

374492
void ProjectM::SetPresetLocked(bool locked)

src/libprojectM/ProjectM.hpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
#include <projectM-4/projectM_cxx_export.h>
2424

25+
#include <Renderer/OpenGL.h>
2526
#include <Renderer/RenderContext.hpp>
2627
#include <Renderer/TextureTypes.hpp>
2728

@@ -292,6 +293,22 @@ class PROJECTM_CXX_EXPORT ProjectM
292293

293294
auto GetRenderContext() -> Renderer::RenderContext;
294295

296+
/**
297+
* @brief Restores the host application's OpenGL state after rendering a frame.
298+
*
299+
* This ensures libprojectM doesn't leak GL state into the host application's
300+
* rendering pipeline, which is especially important when hosted inside a shared
301+
* OpenGL context.
302+
*/
303+
void RestoreGLState(GLint framebuffer, GLint vertexArray, GLint program,
304+
GLint activeTexture, GLint arrayBuffer, GLint elementArrayBuffer,
305+
GLboolean blend, GLboolean depthTest,
306+
GLint blendSrcRgb, GLint blendDstRgb
307+
#ifndef USE_GLES
308+
, GLboolean lineSmooth
309+
#endif
310+
);
311+
295312
uint32_t m_meshX{32}; //!< Per-point mesh horizontal resolution.
296313
uint32_t m_meshY{24}; //!< Per-point mesh vertical resolution.
297314
uint32_t m_targetFps{35}; //!< Target frames per second.

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
*

0 commit comments

Comments
 (0)