Restore caller GL state after output rendering

Step-image and parts-list helpers switched to the shared offscreen context
and left it current after rendering. Framebuffer release and destruction
also replaced a caller's existing binding with the default framebuffer.
This violated the output helpers' state-restoration contract on both
success and failure.

Capture the caller's context, surface, and framebuffer bindings in a scope
guard before asset waits and rendering. Restore them after the temporary
view is destroyed, preserving separate draw/read bindings where supported
and leaving no context current when the caller started without one.

Validation: full debug build and whitespace checks passed. A temporary
OpenGL regression harness verified step-image and parts-list success and
framebuffer creation failure with an alternate caller context, an alternate
surface on the shared context, nondefault framebuffer bindings, and no
initially current context.
This commit is contained in:
Leonardo Zide committed 2026-10-05 14:01:41 -07:00
1 parent 62a84dff86
commit a152d3442c
3 files changed
+69

No files matched your search

+45
View File
@@ -29,6 +29,51 @@ std::unique_ptr<QOffscreenSurface> lcContext::mOffscreenSurface;
std::unique_ptr<lcContext> lcContext::mGlobalOffscreenContext;
lcProgram lcContext::mPrograms[static_cast<int>(lcMaterialType::Count)];
lcScopedGLContextState::lcScopedGLContextState()
: mContext(QOpenGLContext::currentContext()), mSurface(mContext ? mContext->surface() : nullptr)
{
if (!mContext)
return;
QOpenGLFunctions* Functions = mContext->functions();
mSeparateFramebuffers = QOpenGLFramebufferObject::hasOpenGLFramebufferBlit();
if (mSeparateFramebuffers)
{
Functions->glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &mDrawFramebuffer);
Functions->glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &mReadFramebuffer);
}
else
Functions->glGetIntegerv(GL_FRAMEBUFFER_BINDING, &mDrawFramebuffer);
}
lcScopedGLContextState::~lcScopedGLContextState()
{
QOpenGLContext* CurrentContext = QOpenGLContext::currentContext();
if (!mContext)
{
if (CurrentContext)
CurrentContext->doneCurrent();
return;
}
if (CurrentContext != mContext || mContext->surface() != mSurface)
if (!mContext->makeCurrent(mSurface))
return;
QOpenGLFunctions* Functions = mContext->functions();
if (mSeparateFramebuffers)
{
Functions->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mDrawFramebuffer);
Functions->glBindFramebuffer(GL_READ_FRAMEBUFFER, mReadFramebuffer);
}
else
Functions->glBindFramebuffer(GL_FRAMEBUFFER, mDrawFramebuffer);
}
lcContext::lcContext()
{
mVertexBufferObject = 0;
+18
View File
@@ -106,6 +106,24 @@ struct lcVertexAttribState
GLuint VertexBufferObject = 0;
};
// Restores caller state after temporary rendering, including a caller with no current context.
class lcScopedGLContextState
{
public:
lcScopedGLContextState();
~lcScopedGLContextState();
lcScopedGLContextState(const lcScopedGLContextState&) = delete;
lcScopedGLContextState& operator=(const lcScopedGLContextState&) = delete;
private:
QOpenGLContext* mContext;
QSurface* mSurface;
GLint mDrawFramebuffer = 0;
GLint mReadFramebuffer = 0;
bool mSeparateFramebuffers = false;
};
class lcContext : public QOpenGLFunctions
{
public:
+6
View File
@@ -1695,6 +1695,8 @@ void lcModel::AddSubModelRenderMeshes(lcScene* Scene, const lcMatrix44& WorldMat
QImage lcModel::GetStepImage(bool Zoom, int Width, int Height, lcStep Step)
{
lcScopedGLContextState GLState;
WaitForAssets();
const lcResult<QImage> Image = RenderStepImageWithReadyAssets(Zoom, Width, Height, Step, nullptr);
@@ -1712,6 +1714,8 @@ QImage lcModel::GetStepImage(bool Zoom, int Width, int Height, lcStep Step)
lcResult<QImage> lcModel::RenderStepImageWithReadyAssets(bool Zoom, int Width, int Height, lcStep Step, lcCamera* OutputCamera)
{
lcScopedGLContextState GLState;
const lcView* ActiveView = gMainWindow ? gMainWindow->GetActiveView() : nullptr;
const lcStep CurrentStep = mCurrentStep;
@@ -1762,6 +1766,8 @@ lcResult<QImage> lcModel::RenderStepImageWithReadyAssets(bool Zoom, int Width, i
QImage lcModel::GetPartsListImage(int MaxWidth, lcStep Step, quint32 BackgroundColor, QFont Font, QColor TextColor, bool KeepMissingParts) const
{
lcScopedGLContextState GLState;
WaitForAssets();
lcPartsList PartsList;