From f7d63f88fa45d4ba584ec3d15767ad9579d1509b Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Tue, 11 Aug 2026 19:22:25 -0400 Subject: [PATCH] [Fix, Test] (MG_State, MG_Impl): a reserved program-pipeline name takes state from UseProgramStages and its siblings instead of rejecting them --- .../GLImpl/Program/GL_ProgramPipeline.cpp | 18 ++++++++++++++---- MobileGL/MG_State/GLState/Core.cpp | 19 ++++++++++++++++--- MobileGL/MG_State/GLState/Core.h | 6 +++++- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/MobileGL/MG_Impl/GLImpl/Program/GL_ProgramPipeline.cpp b/MobileGL/MG_Impl/GLImpl/Program/GL_ProgramPipeline.cpp index 8742de51..3a462127 100644 --- a/MobileGL/MG_Impl/GLImpl/Program/GL_ProgramPipeline.cpp +++ b/MobileGL/MG_Impl/GLImpl/Program/GL_ProgramPipeline.cpp @@ -19,16 +19,26 @@ namespace MobileGL::MG_Impl::GLImpl { code, MakeUnique("MG_Impl/GLImpl", function, Move(message))); } - // A pipeline name only names an object once it has been bound or created; querying a - // reserved-but-unmaterialised name is INVALID_OPERATION (GL 4.6 core 7.4). + // GL 4.6 core 7.4 asks only that the name came from GenProgramPipelines and has not been + // deleted - so a name that was reserved and never bound is legal here, and the command + // MATERIALIZES it rather than rejecting it. + // + // Requiring a bound object instead is what broke every separable-program conformance case + // across three families: the CTS reserves a name, calls glUseProgramStages three times and + // only then binds, which is the order the spec's own example uses. Each of those calls + // failed with INVALID_OPERATION, so the stage programs were never recorded - the pipeline + // stayed empty, GetProgramForDraw flattened nothing and the draw painted nothing, and the + // rejected calls' error was left in the queue for the harness to find. One cause, both + // symptoms. const SharedPtr* TryGetPipeline(GLuint pipeline, const char* function) { - if (!MG_State::pGLContext->IsProgramPipelineObject(pipeline)) { + const auto& object = MG_State::pGLContext->MaterializeProgramPipelineObject(pipeline); + if (!object) { RecordPipelineError(ErrorCode::InvalidOperation, function, std::format("Program pipeline {} does not exist.", pipeline)); return nullptr; } - return &MG_State::pGLContext->GetProgramPipelineObject(pipeline); + return &object; } Bool ValidatePipelineCount(GLsizei n, const char* function) { diff --git a/MobileGL/MG_State/GLState/Core.cpp b/MobileGL/MG_State/GLState/Core.cpp index a6483b96..6e32398d 100644 --- a/MobileGL/MG_State/GLState/Core.cpp +++ b/MobileGL/MG_State/GLState/Core.cpp @@ -937,13 +937,26 @@ namespace MobileGL::MG_State { } void GLContext::BindProgramPipelineObject(Uint index) { - if (index != 0 && m_programPipelines.find(index) == m_programPipelines.end()) { - // First bind is what turns a reserved name into an object. - m_programPipelines[index] = MakeShared(index); + if (index != 0) { + MaterializeProgramPipelineObject(index); } m_boundProgramPipeline = index; } + // Binding is not the only thing that turns a reserved name into an object. GL 4.6 core + // 7.4 asks of UseProgramStages, ActiveShaderProgram and ValidateProgramPipeline only that + // the name came from GenProgramPipelines and has not been deleted - so a name that was + // reserved and never bound must take state from them, not be rejected. glIsProgramPipeline + // is the one place the distinction survives (it answers FALSE until the name is used), + // which is why IsProgramPipelineObject stays as it is. + const SharedPtr& GLContext::MaterializeProgramPipelineObject(Uint index) { + static const SharedPtr kNone; + if (index == 0 || !m_programPipelineNames.IsValid(index)) return kNone; + const auto it = m_programPipelines.find(index); + if (it != m_programPipelines.end()) return it->second; + return m_programPipelines[index] = MakeShared(index); + } + void GLContext::MarkProgramPipelineForDeletion(Uint index) { if (index == 0 || !m_programPipelineNames.IsValid(index)) return; if (index == m_boundProgramPipeline) { diff --git a/MobileGL/MG_State/GLState/Core.h b/MobileGL/MG_State/GLState/Core.h index dc7e72f9..a2ecd6a4 100644 --- a/MobileGL/MG_State/GLState/Core.h +++ b/MobileGL/MG_State/GLState/Core.h @@ -172,12 +172,16 @@ namespace MobileGL { // Program pipeline (GL_ARB_separate_shader_objects, GL 4.6 core 7.4). Like queries // and transform feedbacks, glGenProgramPipelines only RESERVES a name - the object - // appears on first bind - while glCreateProgramPipelines makes it immediately. + // appears on first USE (any of bind, UseProgramStages, ActiveShaderProgram, + // ValidateProgramPipeline) - while glCreateProgramPipelines makes it immediately. void GenProgramPipelineNames(Uint number, Vector& pipelines); void CreateProgramPipelineObject(Uint index); Bool ValidateProgramPipelineName(Uint index) const; Bool IsProgramPipelineObject(Uint index) const; void BindProgramPipelineObject(Uint index); + // Materializes a reserved name; returns null for 0 or a name that is not a live + // GenProgramPipelines name. + const SharedPtr& MaterializeProgramPipelineObject(Uint index); void MarkProgramPipelineForDeletion(Uint index); const SharedPtr& GetProgramPipelineObject(Uint index) const; Uint GetBoundProgramPipelineName() const { return m_boundProgramPipeline; }