[Feat] (MG_State, MG_Impl, MG_Backend): let a bound program pipeline actually draw

The pipeline object bookkeeping landed already - names, stage slots, queries -
but nothing consumed it. Every draw asked the context for the current program,
got null because a pipeline is used with program zero, and drew nothing;
glCreateShaderProgramv was still a stub returning zero, so
direct_state_access.program_pipelines_functional could not even build its stage
programs and reported InternalError on both backends.

glCreateShaderProgramv is written as the exact call sequence the spec defines it
to be, with one deviation that matters: the link goes straight to
ProgramObject::Link(false) rather than through LinkProgram, because LinkProgram
injects a default fragment shader into a program that has none - correct for a
whole program, wrong for a separable vertex-stage one whose fragment stage comes
from the pipeline. glDetachShader defers removal to the next link, so the program
keeps the shader object it was built from while correctly no longer reporting it
attached. GL_PROGRAM_SEPARABLE joins glProgramParameteri and glGetProgramiv.

Everything downstream of a draw - both backends, the uniform plumbing, the draw
validation - is written against one linked program, so rather than teach all of
it about stages, the pipeline is flattened: GetProgramForDraw() composites the
stage programs' shaders into a single hidden program object and caches it against
a signature of each stage program's lifetime id and link generation, so it is
rebuilt exactly when a stage or a stage's link changes. The composite carries no
GL name - it must not answer glIsProgram, and it must not consume a name the
application could be handed.

Uniform entry points get their own resolver rather than sharing that one:
glUniform* addresses the pipeline's active program, not the composited draw
program. GL_CURRENT_PROGRAM still reads the program in use, which is zero here.

Fixes program_pipelines_functional on both backends.
This commit is contained in:
BZLZHH
2026-08-05 07:20:42 -04:00
parent 5545d31c37
commit 4ce808b9f2
10 changed files with 172 additions and 30 deletions
@@ -15,7 +15,7 @@
namespace MobileGL::MG_Impl::GLImpl {
static Bool ValidateCurrentProgramForExecution(const char* functionName) {
const auto& currentProgram = MG_State::pGLContext->GetCurrentProgram();
const auto& currentProgram = MG_State::pGLContext->GetProgramForDraw();
if (!currentProgram) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
@@ -37,7 +37,7 @@ namespace MobileGL::MG_Impl::GLImpl {
static Bool ValidateCurrentProgramForCompute(const char* functionName) {
if (!ValidateCurrentProgramForExecution(functionName)) return false;
const auto& currentProgram = MG_State::pGLContext->GetCurrentProgram();
const auto& currentProgram = MG_State::pGLContext->GetProgramForDraw();
if (currentProgram->GetShaderIndexByStage(ShaderStage::Compute) < 0) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
@@ -173,7 +173,7 @@ namespace MobileGL::MG_Impl::GLImpl {
// input primitive (GL 4.6 core 11.3.1); anything else is INVALID_OPERATION. GL_PATCHES
// is the tessellation pipeline's input and reaches the geometry stage already
// converted, so it is not constrained here.
const auto& currentProgram = MG_State::pGLContext->GetCurrentProgram();
const auto& currentProgram = MG_State::pGLContext->GetProgramForDraw();
const GLenum gsInput = currentProgram ? currentProgram->GetGeometryInputType() : GL_NONE;
if (gsInput != GL_NONE && mode != GL_PATCHES) {
Bool compatible = false;
@@ -707,7 +707,7 @@ namespace MobileGL::MG_Impl::GLImpl {
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Transform feedback is already active."));
return;
}
const auto& program = MG_State::pGLContext->GetCurrentProgram();
const auto& program = MG_State::pGLContext->GetProgramForDraw();
if (!program || !program->GetLinkStatus() || program->GetTransformFeedbackVaryingCount() == 0) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
@@ -320,7 +320,7 @@ DECLARE_GL_FUNCTION_HEAD(void, GetProgramResourceiv, GLuint program, GLenum prog
DECLARE_GL_FUNCTION_HEAD(GLint, GetProgramResourceLocation, GLuint program, GLenum programInterface, const GLchar* name) DECLARE_GL_FUNCTION_END(GLint, GetProgramResourceLocation, program, programInterface, name)
DECLARE_GL_FUNCTION_HEAD(void, UseProgramStages, GLuint pipeline, GLbitfield stages, GLuint program) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UseProgramStages, pipeline, stages, program)
DECLARE_GL_FUNCTION_HEAD(void, ActiveShaderProgram, GLuint pipeline, GLuint program) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ActiveShaderProgram, pipeline, program)
DECLARE_GL_FUNCTION_STUB_HEAD(GLuint, CreateShaderProgramv, GLenum type, GLsizei count, const GLchar* const* strings) DECLARE_GL_FUNCTION_STUB_END(GLuint, CreateShaderProgramv, type, count, strings)
DECLARE_GL_FUNCTION_HEAD(GLuint, CreateShaderProgramv, GLenum type, GLsizei count, const GLchar* const* strings) DECLARE_GL_FUNCTION_END(GLuint, CreateShaderProgramv, type, count, strings)
DECLARE_GL_FUNCTION_HEAD(void, BindProgramPipeline, GLuint pipeline) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindProgramPipeline, pipeline)
DECLARE_GL_FUNCTION_HEAD(void, DeleteProgramPipelines, GLsizei n, const GLuint* pipelines) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DeleteProgramPipelines, n, pipelines)
DECLARE_GL_FUNCTION_HEAD(void, GenProgramPipelines, GLsizei n, GLuint* pipelines) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GenProgramPipelines, n, pipelines)
+57 -15
View File
@@ -701,6 +701,9 @@ namespace MobileGL::MG_Impl::GLImpl {
case GL_PROGRAM_BINARY_RETRIEVABLE_HINT:
*params = programObject->GetBinaryRetrievableHint() ? GL_TRUE : GL_FALSE;
break;
case GL_PROGRAM_SEPARABLE:
*params = programObject->GetSeparable() ? GL_TRUE : GL_FALSE;
break;
case GL_GEOMETRY_VERTICES_OUT:
case GL_GEOMETRY_INPUT_TYPE:
@@ -1094,7 +1097,7 @@ namespace MobileGL::MG_Impl::GLImpl {
void Uniformv_State(GLint location, GLsizei count, T* value) {
if (location == -1) return;
auto& programObject = MG_State::pGLContext->GetCurrentProgram();
auto& programObject = MG_State::pGLContext->GetProgramForUniform();
if (programObject == nullptr) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
@@ -1286,7 +1289,7 @@ namespace MobileGL::MG_Impl::GLImpl {
// If transpose is GL_TRUE, we need to transpose the matrix data
if (location == -1) return;
auto& programObject = MG_State::pGLContext->GetCurrentProgram();
auto& programObject = MG_State::pGLContext->GetProgramForUniform();
if (programObject == nullptr) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
@@ -1321,7 +1324,7 @@ namespace MobileGL::MG_Impl::GLImpl {
// If transpose is GL_TRUE, we need to transpose the matrix data
if (location == -1) return;
auto& programObject = MG_State::pGLContext->GetCurrentProgram();
auto& programObject = MG_State::pGLContext->GetProgramForUniform();
if (programObject == nullptr) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
@@ -1361,7 +1364,7 @@ namespace MobileGL::MG_Impl::GLImpl {
// If transpose is GL_TRUE, we need to transpose the matrix data
if (location == -1) return;
auto& programObject = MG_State::pGLContext->GetCurrentProgram();
auto& programObject = MG_State::pGLContext->GetProgramForUniform();
if (programObject == nullptr) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
@@ -1394,7 +1397,7 @@ namespace MobileGL::MG_Impl::GLImpl {
void UniformMatrixNonSquarefv_State(const char* caller, GLint location, GLsizei count) {
if (location == -1) return;
auto& programObject = MG_State::pGLContext->GetCurrentProgram();
auto& programObject = MG_State::pGLContext->GetProgramForUniform();
if (programObject == nullptr) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
@@ -2055,7 +2058,7 @@ namespace MobileGL::MG_Impl::GLImpl {
}
void UniformMatrix2dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) {
if (location == -1) return;
auto& programObject = MG_State::pGLContext->GetCurrentProgram();
auto& programObject = MG_State::pGLContext->GetProgramForUniform();
if (programObject == nullptr) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
@@ -2081,7 +2084,7 @@ namespace MobileGL::MG_Impl::GLImpl {
}
void UniformMatrix3dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) {
if (location == -1) return;
auto& programObject = MG_State::pGLContext->GetCurrentProgram();
auto& programObject = MG_State::pGLContext->GetProgramForUniform();
if (programObject == nullptr) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
@@ -2107,7 +2110,7 @@ namespace MobileGL::MG_Impl::GLImpl {
}
void UniformMatrix4dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) {
if (location == -1) return;
auto& programObject = MG_State::pGLContext->GetCurrentProgram();
auto& programObject = MG_State::pGLContext->GetProgramForUniform();
if (programObject == nullptr) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
@@ -2133,7 +2136,7 @@ namespace MobileGL::MG_Impl::GLImpl {
}
void UniformMatrix2x3dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) {
if (location == -1) return;
auto& programObject = MG_State::pGLContext->GetCurrentProgram();
auto& programObject = MG_State::pGLContext->GetProgramForUniform();
if (programObject == nullptr) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
@@ -2159,7 +2162,7 @@ namespace MobileGL::MG_Impl::GLImpl {
}
void UniformMatrix2x4dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) {
if (location == -1) return;
auto& programObject = MG_State::pGLContext->GetCurrentProgram();
auto& programObject = MG_State::pGLContext->GetProgramForUniform();
if (programObject == nullptr) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
@@ -2185,7 +2188,7 @@ namespace MobileGL::MG_Impl::GLImpl {
}
void UniformMatrix3x2dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) {
if (location == -1) return;
auto& programObject = MG_State::pGLContext->GetCurrentProgram();
auto& programObject = MG_State::pGLContext->GetProgramForUniform();
if (programObject == nullptr) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
@@ -2211,7 +2214,7 @@ namespace MobileGL::MG_Impl::GLImpl {
}
void UniformMatrix3x4dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) {
if (location == -1) return;
auto& programObject = MG_State::pGLContext->GetCurrentProgram();
auto& programObject = MG_State::pGLContext->GetProgramForUniform();
if (programObject == nullptr) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
@@ -2237,7 +2240,7 @@ namespace MobileGL::MG_Impl::GLImpl {
}
void UniformMatrix4x2dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) {
if (location == -1) return;
auto& programObject = MG_State::pGLContext->GetCurrentProgram();
auto& programObject = MG_State::pGLContext->GetProgramForUniform();
if (programObject == nullptr) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
@@ -2263,7 +2266,7 @@ namespace MobileGL::MG_Impl::GLImpl {
}
void UniformMatrix4x3dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value) {
if (location == -1) return;
auto& programObject = MG_State::pGLContext->GetCurrentProgram();
auto& programObject = MG_State::pGLContext->GetProgramForUniform();
if (programObject == nullptr) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
@@ -2746,7 +2749,7 @@ namespace MobileGL::MG_Impl::GLImpl {
void ProgramParameteri(GLuint program, GLenum pname, GLint value) {
auto& programObject = TryToGetProgramObject(program);
if (!programObject) return;
if (pname != GL_PROGRAM_BINARY_RETRIEVABLE_HINT) {
if (pname != GL_PROGRAM_BINARY_RETRIEVABLE_HINT && pname != GL_PROGRAM_SEPARABLE) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidEnum,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "pname is not an accepted value."));
@@ -2758,9 +2761,48 @@ namespace MobileGL::MG_Impl::GLImpl {
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "value must be GL_TRUE or GL_FALSE."));
return;
}
if (pname == GL_PROGRAM_SEPARABLE) {
programObject->SetSeparable(value == GL_TRUE);
return;
}
programObject->SetBinaryRetrievableHint(value == GL_TRUE);
}
// GL 4.6 core 7.3: glCreateShaderProgramv is defined as the exact sequence below, so it
// is written as that sequence rather than as a private shortcut - every error it can
// raise is one of theirs, raised at the point they would raise it.
GLuint CreateShaderProgramv(GLenum type, GLsizei count, const GLchar* const* strings) {
const GLuint shader = CreateShader_State(type);
if (shader == 0) return 0;
ShaderSource_State(shader, count, strings, nullptr);
CompileShader_State(shader);
const GLuint program = CreateProgram_State();
if (program != 0) {
const auto& shaderObject = MG_State::pGLContext->GetShaderObject(shader);
const auto& programObject = MG_State::pGLContext->GetProgramObject(program);
// The program is separable whether or not the shader compiled: a failed
// compile leaves an unlinked but otherwise well-formed separable program.
if (programObject) programObject->SetSeparable(true);
if (shaderObject && programObject && shaderObject->GetCompileStatus()) {
AttachShader_State(program, shader);
// Not LinkProgram_State: that injects a default fragment shader into a
// program that has none, which is exactly wrong for a separable
// vertex-stage program - the pipeline supplies the real one.
programObject->Link(false);
// glDetachShader defers the removal to the next link, so the program keeps
// the shader object it was built from while no longer reporting it attached.
DetachShader_State(program, shader);
}
if (shaderObject && programObject && !shaderObject->GetInfoLog().empty()) {
programObject->AppendInfoLog(shaderObject->GetInfoLog());
}
}
DeleteShader_State(shader);
return program;
}
void GetProgramBinary(GLuint program, GLsizei bufSize, GLsizei* length, GLenum* binaryFormat, void* binary) {
(void)binaryFormat;
(void)binary;
@@ -174,6 +174,7 @@ namespace MobileGL::MG_Impl::GLImpl {
void GetUniformdv(GLuint program, GLint location, GLdouble* params);
void ValidateProgram(GLuint program);
void ProgramParameteri(GLuint program, GLenum pname, GLint value);
GLuint CreateShaderProgramv(GLenum type, GLsizei count, const GLchar* const* strings);
void GetProgramBinary(GLuint program, GLsizei bufSize, GLsizei* length, GLenum* binaryFormat, void* binary);
void ProgramBinary(GLuint program, GLenum binaryFormat, const void* binary, GLsizei length);
void TransformFeedbackVaryings(GLuint program, GLsizei count, const GLchar* const* varyings, GLenum bufferMode);