[Feat] (MG_State, MG_Impl): give program pipelines their object and their state

Every program pipeline entry point was an export stub, and the stub macro's
`return (type)1` made glIsProgramPipeline answer GL_TRUE for anything - including
the names glGenProgramPipelines had never written. All four
direct_state_access.program_pipelines cases failed.

ProgramPipelineObject holds what GL 4.6 core 7.4 says a pipeline is: a program
reference per shader stage, the active program glProgramUniform* addresses, a
validate status and an info log. Its validate status starts false, unlike
ProgramObject's, because a pipeline that has never been validated must report
GL_VALIDATE_STATUS as 0.

The name rules follow the shape queries and transform feedbacks already use, and
which the CTS checks first: glGenProgramPipelines only RESERVES a name and
glIsProgramPipeline answers GL_FALSE for it; the object appears on first bind, or
immediately from glCreateProgramPipelines. Map membership is object existence -
a pipeline, unlike a transform feedback, has no stateful default object zero, so
no everBound flag is needed.

glGet(GL_PROGRAM_PIPELINE_BINDING) reports the real binding now instead of a
hardcoded zero whose comment said the entry points were stubbed.

This is the state half only. program_pipelines_functional needs mixed-stage
rendering - a vertex-only and a fragment-only program drawn together - and stays
failing; glCreateShaderProgramv is deliberately left stubbed until that lands, so
nothing can half-work in between.

Takes program_pipelines_creation, _defaults and _errors from failing to passing
on both backends.
This commit is contained in:
BZLZHH
2026-08-05 05:43:13 -04:00
parent 1f1a331a44
commit 300b458132
8 changed files with 385 additions and 11 deletions
+47
View File
@@ -802,6 +802,53 @@ namespace MobileGL::MG_State {
m_transformFeedbackObjects[id] = {};
}
}
// Program pipeline
void GLContext::GenProgramPipelineNames(Uint number, Vector<Uint>& pipelines) {
pipelines.resize(number);
// Names only: glIsProgramPipeline must answer GL_FALSE until one is bound or created.
m_programPipelineNames.Generate(number, pipelines.data());
}
void GLContext::CreateProgramPipelineObject(Uint index) {
m_programPipelines[index] = MakeShared<ProgramPipelineObject>(index);
}
Bool GLContext::ValidateProgramPipelineName(Uint index) const {
return index == 0 || m_programPipelineNames.IsValid(index);
}
Bool GLContext::IsProgramPipelineObject(Uint index) const {
if (index == 0 || !m_programPipelineNames.IsValid(index)) return false;
return m_programPipelines.find(index) != m_programPipelines.end();
}
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<ProgramPipelineObject>(index);
}
m_boundProgramPipeline = index;
}
void GLContext::MarkProgramPipelineForDeletion(Uint index) {
if (index == 0 || !m_programPipelineNames.IsValid(index)) return;
if (index == m_boundProgramPipeline) {
m_boundProgramPipeline = 0;
}
m_programPipelines.erase(index);
m_programPipelineNames.Delete(index);
}
const SharedPtr<ProgramPipelineObject>& GLContext::GetProgramPipelineObject(Uint index) const {
static const SharedPtr<ProgramPipelineObject> kNone;
const auto it = m_programPipelines.find(index);
return it == m_programPipelines.end() ? kNone : it->second;
}
const SharedPtr<ProgramPipelineObject>& GLContext::GetBoundProgramPipeline() const {
return GetProgramPipelineObject(m_boundProgramPipeline);
}
Bool GLContext::ValidateTransformFeedbackName(Uint index) const {
return index == 0 || m_transformFeedbackNames.IsValid(index);