From 300b458132821ccb9cbc4c69c500f3e51870f5c6 Mon Sep 17 00:00:00 2001 From: BZLZHH Date: Wed, 5 Aug 2026 05:43:13 -0400 Subject: [PATCH] [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. --- CMakeLists.txt | 1 + .../MG_Impl/GLImpl/Exporting/Definitions.cpp | 21 +- MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp | 2 +- .../GLImpl/Program/GL_ProgramPipeline.cpp | 231 ++++++++++++++++++ .../GLImpl/Program/GL_ProgramPipeline.h | 23 ++ MobileGL/MG_State/GLState/Core.cpp | 47 ++++ MobileGL/MG_State/GLState/Core.h | 19 ++ .../ProgramState/ProgramPipelineObject.h | 52 ++++ 8 files changed, 385 insertions(+), 11 deletions(-) create mode 100644 MobileGL/MG_Impl/GLImpl/Program/GL_ProgramPipeline.cpp create mode 100644 MobileGL/MG_Impl/GLImpl/Program/GL_ProgramPipeline.h create mode 100644 MobileGL/MG_State/GLState/ProgramState/ProgramPipelineObject.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c74117b6..5e1e7ea4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -218,6 +218,7 @@ set(SOURCE_FILES MobileGL/MG_Impl/GLImpl/Framebuffer/Validators.cpp MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp + MobileGL/MG_Impl/GLImpl/Program/GL_ProgramPipeline.cpp MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp MobileGL/MG_Impl/GLImpl/Texture/Validators.cpp MobileGL/MG_Impl/GLImpl/Texture/ProxyTexture.cpp diff --git a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp index afe366d4..933306a4 100644 --- a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp +++ b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp @@ -15,6 +15,7 @@ #include "../Texture/GL_Texture.h" #include "../Drawing/GL_Drawing.h" #include "../Program/GL_Program.h" +#include "../Program/GL_ProgramPipeline.h" #include "../RenderState/GL_RenderState.h" #include "../Framebuffer/GL_Framebuffer.h" #include "../VertexArray/GL_VertexArray.h" @@ -317,14 +318,14 @@ DECLARE_GL_FUNCTION_HEAD(GLuint, GetProgramResourceIndex, GLuint program, GLenum DECLARE_GL_FUNCTION_HEAD(void, GetProgramResourceName, GLuint program, GLenum programInterface, GLuint index, GLsizei bufSize, GLsizei* length, GLchar* name) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetProgramResourceName, program, programInterface, index, bufSize, length, name) DECLARE_GL_FUNCTION_HEAD(void, GetProgramResourceiv, GLuint program, GLenum programInterface, GLuint index, GLsizei propCount, const GLenum* props, GLsizei bufSize, GLsizei* length, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetProgramResourceiv, program, programInterface, index, propCount, props, bufSize, length, params) 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_STUB_HEAD(void, UseProgramStages, GLuint pipeline, GLbitfield stages, GLuint program) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, UseProgramStages, pipeline, stages, program) -DECLARE_GL_FUNCTION_STUB_HEAD(void, ActiveShaderProgram, GLuint pipeline, GLuint program) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ActiveShaderProgram, pipeline, program) +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_STUB_HEAD(void, BindProgramPipeline, GLuint pipeline) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BindProgramPipeline, pipeline) -DECLARE_GL_FUNCTION_STUB_HEAD(void, DeleteProgramPipelines, GLsizei n, const GLuint* pipelines) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DeleteProgramPipelines, n, pipelines) -DECLARE_GL_FUNCTION_STUB_HEAD(void, GenProgramPipelines, GLsizei n, GLuint* pipelines) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GenProgramPipelines, n, pipelines) -DECLARE_GL_FUNCTION_STUB_HEAD(GLboolean, IsProgramPipeline, GLuint pipeline) DECLARE_GL_FUNCTION_STUB_END(GLboolean, IsProgramPipeline, pipeline) -DECLARE_GL_FUNCTION_STUB_HEAD(void, GetProgramPipelineiv, GLuint pipeline, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetProgramPipelineiv, pipeline, pname, params) +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) +DECLARE_GL_FUNCTION_HEAD(GLboolean, IsProgramPipeline, GLuint pipeline) DECLARE_GL_FUNCTION_END(GLboolean, IsProgramPipeline, pipeline) +DECLARE_GL_FUNCTION_HEAD(void, GetProgramPipelineiv, GLuint pipeline, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetProgramPipelineiv, pipeline, pname, params) DECLARE_GL_FUNCTION_HEAD(void, ProgramUniform1i, GLuint program, GLint location, GLint v0) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniform1i, program, location, v0) DECLARE_GL_FUNCTION_HEAD(void, ProgramUniform2i, GLuint program, GLint location, GLint v0, GLint v1) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniform2i, program, location, v0, v1) DECLARE_GL_FUNCTION_HEAD(void, ProgramUniform3i, GLuint program, GLint location, GLint v0, GLint v1, GLint v2) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniform3i, program, location, v0, v1, v2) @@ -358,8 +359,8 @@ DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix2x4fv, GLuint program, GLint DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix4x2fv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix4x2fv, program, location, count, transpose, value) DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix3x4fv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix3x4fv, program, location, count, transpose, value) DECLARE_GL_FUNCTION_HEAD(void, ProgramUniformMatrix4x3fv, GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProgramUniformMatrix4x3fv, program, location, count, transpose, value) -DECLARE_GL_FUNCTION_STUB_HEAD(void, ValidateProgramPipeline, GLuint pipeline) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ValidateProgramPipeline, pipeline) -DECLARE_GL_FUNCTION_STUB_HEAD(void, GetProgramPipelineInfoLog, GLuint pipeline, GLsizei bufSize, GLsizei* length, GLchar* infoLog) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetProgramPipelineInfoLog, pipeline, bufSize, length, infoLog) +DECLARE_GL_FUNCTION_HEAD(void, ValidateProgramPipeline, GLuint pipeline) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ValidateProgramPipeline, pipeline) +DECLARE_GL_FUNCTION_HEAD(void, GetProgramPipelineInfoLog, GLuint pipeline, GLsizei bufSize, GLsizei* length, GLchar* infoLog) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetProgramPipelineInfoLog, pipeline, bufSize, length, infoLog) DECLARE_GL_FUNCTION_HEAD(void, BindImageTexture, GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLenum format) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindImageTexture, unit, texture, level, layered, layer, access, format) DECLARE_GL_FUNCTION_HEAD(void, GetBooleani_v, GLenum target, GLuint index, GLboolean* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetBooleani_v, target, index, data) DECLARE_GL_FUNCTION_HEAD(void, MemoryBarrier, GLbitfield barriers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, MemoryBarrier, barriers) @@ -1096,7 +1097,7 @@ DECLARE_GL_FUNCTION_HEAD(void, GetVertexArrayiv, GLuint vaobj, GLenum pname, GLi DECLARE_GL_FUNCTION_HEAD(void, GetVertexArrayIndexediv, GLuint vaobj, GLuint index, GLenum pname, GLint* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetVertexArrayIndexediv, vaobj, index, pname, param) DECLARE_GL_FUNCTION_HEAD(void, GetVertexArrayIndexed64iv, GLuint vaobj, GLuint index, GLenum pname, GLint64* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetVertexArrayIndexed64iv, vaobj, index, pname, param) DECLARE_GL_FUNCTION_HEAD(void, CreateSamplers, GLsizei n, GLuint* samplers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateSamplers, n, samplers) -DECLARE_GL_FUNCTION_STUB_HEAD(void, CreateProgramPipelines, GLsizei n, GLuint* pipelines) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CreateProgramPipelines, n, pipelines) +DECLARE_GL_FUNCTION_HEAD(void, CreateProgramPipelines, GLsizei n, GLuint* pipelines) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateProgramPipelines, n, pipelines) DECLARE_GL_FUNCTION_HEAD(void, CreateQueries, GLenum target, GLsizei n, GLuint* ids) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateQueries, target, n, ids) DECLARE_GL_FUNCTION_HEAD(void, GetQueryBufferObjecti64v, GLuint id, GLuint buffer, GLenum pname, GLintptr offset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetQueryBufferObjecti64v, id, buffer, pname, offset) DECLARE_GL_FUNCTION_HEAD(void, GetQueryBufferObjectiv, GLuint id, GLuint buffer, GLenum pname, GLintptr offset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetQueryBufferObjectiv, id, buffer, pname, offset) diff --git a/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp b/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp index 64cabd04..13b159e2 100644 --- a/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp +++ b/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp @@ -1432,7 +1432,7 @@ namespace MobileGL::MG_Impl::GLImpl { *params = 0; // program-binary entrypoints are stubbed return; case GL_PROGRAM_PIPELINE_BINDING: - *params = 0; // program-pipeline entrypoints are stubbed + *params = static_cast(MG_State::pGLContext->GetBoundProgramPipelineName()); return; case GL_PROGRAM_POINT_SIZE: *params = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::ProgramPointSize) ? GL_TRUE : GL_FALSE; diff --git a/MobileGL/MG_Impl/GLImpl/Program/GL_ProgramPipeline.cpp b/MobileGL/MG_Impl/GLImpl/Program/GL_ProgramPipeline.cpp new file mode 100644 index 00000000..8742de51 --- /dev/null +++ b/MobileGL/MG_Impl/GLImpl/Program/GL_ProgramPipeline.cpp @@ -0,0 +1,231 @@ +// MobileGL - MobileGL/MG_Impl/GLImpl/Program/GL_ProgramPipeline.cpp +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +#include "GL_ProgramPipeline.h" + +#include +#include +#include + +namespace MobileGL::MG_Impl::GLImpl { + namespace { + void RecordPipelineError(ErrorCode code, const char* function, String message) { + MG_State::pGLContext->RecordError( + 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). + const SharedPtr* TryGetPipeline(GLuint pipeline, + const char* function) { + if (!MG_State::pGLContext->IsProgramPipelineObject(pipeline)) { + RecordPipelineError(ErrorCode::InvalidOperation, function, + std::format("Program pipeline {} does not exist.", pipeline)); + return nullptr; + } + return &MG_State::pGLContext->GetProgramPipelineObject(pipeline); + } + + Bool ValidatePipelineCount(GLsizei n, const char* function) { + if (n < 0) { + RecordPipelineError(ErrorCode::InvalidValue, function, "n must be non-negative."); + return false; + } + return true; + } + + // GL 4.6 core table 7.1 maps each stage bit onto a shader stage. + Bool TryResolveStageBit(GLbitfield bit, ShaderStage& outStage) { + switch (bit) { + case GL_VERTEX_SHADER_BIT: outStage = ShaderStage::Vertex; return true; + case GL_TESS_CONTROL_SHADER_BIT: outStage = ShaderStage::TessControl; return true; + case GL_TESS_EVALUATION_SHADER_BIT: outStage = ShaderStage::TessEval; return true; + case GL_GEOMETRY_SHADER_BIT: outStage = ShaderStage::Geometry; return true; + case GL_FRAGMENT_SHADER_BIT: outStage = ShaderStage::Fragment; return true; + case GL_COMPUTE_SHADER_BIT: outStage = ShaderStage::Compute; return true; + default: return false; + } + } + + constexpr GLbitfield kAllStageBits = GL_VERTEX_SHADER_BIT | GL_TESS_CONTROL_SHADER_BIT | + GL_TESS_EVALUATION_SHADER_BIT | GL_GEOMETRY_SHADER_BIT | + GL_FRAGMENT_SHADER_BIT | GL_COMPUTE_SHADER_BIT; + } // namespace + + void GenProgramPipelines(GLsizei n, GLuint* pipelines) { + if (!ValidatePipelineCount(n, __func__)) return; + if (n == 0 || !pipelines) return; + + static thread_local Vector names; + MG_State::pGLContext->GenProgramPipelineNames(static_cast(n), names); + Memcpy(pipelines, names.data(), static_cast(n) * sizeof(GLuint)); + } + + void CreateProgramPipelines(GLsizei n, GLuint* pipelines) { + if (!ValidatePipelineCount(n, __func__)) return; + if (n == 0 || !pipelines) return; + + static thread_local Vector names; + MG_State::pGLContext->GenProgramPipelineNames(static_cast(n), names); + for (GLsizei i = 0; i < n; ++i) { + pipelines[i] = names[static_cast(i)]; + MG_State::pGLContext->CreateProgramPipelineObject(names[static_cast(i)]); + } + } + + void DeleteProgramPipelines(GLsizei n, const GLuint* pipelines) { + if (!ValidatePipelineCount(n, __func__)) return; + if (!pipelines) return; + + for (GLsizei i = 0; i < n; ++i) { + // Deleting zero, an unknown name, or a name that was only reserved is silently ignored. + MG_State::pGLContext->MarkProgramPipelineForDeletion(pipelines[i]); + } + } + + void BindProgramPipeline(GLuint pipeline) { + if (pipeline != 0 && !MG_State::pGLContext->ValidateProgramPipelineName(pipeline)) { + RecordPipelineError(ErrorCode::InvalidOperation, __func__, + std::format("Program pipeline name {} is not valid.", pipeline)); + return; + } + MG_State::pGLContext->BindProgramPipelineObject(pipeline); + } + + GLboolean IsProgramPipeline(GLuint pipeline) { + return MG_State::pGLContext->IsProgramPipelineObject(pipeline) ? GL_TRUE : GL_FALSE; + } + + void GetProgramPipelineiv(GLuint pipeline, GLenum pname, GLint* params) { + const auto* pipelineObject = TryGetPipeline(pipeline, __func__); + if (!pipelineObject || !params) return; + + const auto stageProgramName = [&](ShaderStage stage) -> GLint { + const auto& program = (*pipelineObject)->GetStageProgram(stage); + return program ? static_cast(program->GetExternalIndex()) : 0; + }; + + switch (pname) { + case GL_ACTIVE_PROGRAM: { + const auto& active = (*pipelineObject)->GetActiveProgram(); + *params = active ? static_cast(active->GetExternalIndex()) : 0; + break; + } + case GL_VERTEX_SHADER: *params = stageProgramName(ShaderStage::Vertex); break; + case GL_TESS_CONTROL_SHADER: *params = stageProgramName(ShaderStage::TessControl); break; + case GL_TESS_EVALUATION_SHADER: *params = stageProgramName(ShaderStage::TessEval); break; + case GL_GEOMETRY_SHADER: *params = stageProgramName(ShaderStage::Geometry); break; + case GL_FRAGMENT_SHADER: *params = stageProgramName(ShaderStage::Fragment); break; + case GL_COMPUTE_SHADER: *params = stageProgramName(ShaderStage::Compute); break; + case GL_VALIDATE_STATUS: *params = (*pipelineObject)->GetValidateStatus() ? GL_TRUE : GL_FALSE; break; + case GL_INFO_LOG_LENGTH: { + // GL counts the null terminator, and reports 0 rather than 1 for an empty log. + const auto& log = (*pipelineObject)->GetInfoLog(); + *params = log.empty() ? 0 : static_cast(log.length()) + 1; + break; + } + default: + RecordPipelineError(ErrorCode::InvalidEnum, __func__, + std::format("pname {} is not a program pipeline parameter.", + MG_Util::ConvertGLEnumToString(pname))); + break; + } + } + + void GetProgramPipelineInfoLog(GLuint pipeline, GLsizei bufSize, GLsizei* length, GLchar* infoLog) { + const auto* pipelineObject = TryGetPipeline(pipeline, __func__); + if (!pipelineObject) return; + if (bufSize < 0) { + RecordPipelineError(ErrorCode::InvalidValue, __func__, "bufSize must be non-negative."); + return; + } + if (bufSize == 0 || !infoLog) { + if (length) *length = 0; + return; + } + + const auto& log = (*pipelineObject)->GetInfoLog(); + const auto copied = std::min(bufSize - 1, static_cast(log.length())); + if (copied > 0) Memcpy(infoLog, log.data(), static_cast(copied)); + infoLog[copied] = '\0'; + if (length) *length = copied; + } + + void UseProgramStages(GLuint pipeline, GLbitfield stages, GLuint program) { + if (stages != GL_ALL_SHADER_BITS && (stages & ~kAllStageBits) != 0) { + RecordPipelineError(ErrorCode::InvalidValue, __func__, "stages names a bit that is not a shader stage."); + return; + } + const auto* pipelineObject = TryGetPipeline(pipeline, __func__); + if (!pipelineObject) return; + + SharedPtr programObject; + if (program != 0) { + if (!MG_State::pGLContext->ValidateProgramName(program)) { + RecordPipelineError(ErrorCode::InvalidValue, __func__, + std::format("{} is not the name of a program object.", program)); + return; + } + programObject = MG_State::pGLContext->GetProgramObject(program); + if (!programObject) { + RecordPipelineError(ErrorCode::InvalidValue, __func__, + std::format("{} is not the name of a program object.", program)); + return; + } + if (!programObject->GetLinkStatus()) { + RecordPipelineError(ErrorCode::InvalidOperation, __func__, + std::format("Program {} has not been linked successfully.", program)); + return; + } + } + + const GLbitfield selected = stages == GL_ALL_SHADER_BITS ? kAllStageBits : stages; + for (GLbitfield bit = 1; bit != 0 && bit <= kAllStageBits; bit <<= 1) { + if ((selected & bit) == 0) continue; + ShaderStage stage = ShaderStage::Unknown; + if (!TryResolveStageBit(bit, stage)) continue; + // program == 0 clears the stage, which is what a null program reference means here. + (*pipelineObject)->SetStageProgram(stage, programObject); + } + } + + void ActiveShaderProgram(GLuint pipeline, GLuint program) { + const auto* pipelineObject = TryGetPipeline(pipeline, __func__); + if (!pipelineObject) return; + + if (program == 0) { + (*pipelineObject)->SetActiveProgram(nullptr); + return; + } + if (!MG_State::pGLContext->ValidateProgramName(program)) { + RecordPipelineError(ErrorCode::InvalidValue, __func__, + std::format("{} is not the name of a program object.", program)); + return; + } + auto programObject = MG_State::pGLContext->GetProgramObject(program); + if (!programObject) { + RecordPipelineError(ErrorCode::InvalidValue, __func__, + std::format("{} is not the name of a program object.", program)); + return; + } + if (!programObject->GetLinkStatus()) { + RecordPipelineError(ErrorCode::InvalidOperation, __func__, + std::format("Program {} has not been linked successfully.", program)); + return; + } + (*pipelineObject)->SetActiveProgram(programObject); + } + + void ValidateProgramPipeline(GLuint pipeline) { + const auto* pipelineObject = TryGetPipeline(pipeline, __func__); + if (!pipelineObject) return; + // Nothing here can fail today: MobileGL links each stage program on its own, so there is no + // cross-stage interface to re-check at validation time. The log stays empty, which GL allows. + (*pipelineObject)->SetValidateStatus(true); + } +} // namespace MobileGL::MG_Impl::GLImpl diff --git a/MobileGL/MG_Impl/GLImpl/Program/GL_ProgramPipeline.h b/MobileGL/MG_Impl/GLImpl/Program/GL_ProgramPipeline.h new file mode 100644 index 00000000..0ff735ff --- /dev/null +++ b/MobileGL/MG_Impl/GLImpl/Program/GL_ProgramPipeline.h @@ -0,0 +1,23 @@ +// MobileGL - MobileGL/MG_Impl/GLImpl/Program/GL_ProgramPipeline.h +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +#pragma once +#include + +namespace MobileGL::MG_Impl::GLImpl { + void GenProgramPipelines(GLsizei n, GLuint* pipelines); + void CreateProgramPipelines(GLsizei n, GLuint* pipelines); + void DeleteProgramPipelines(GLsizei n, const GLuint* pipelines); + void BindProgramPipeline(GLuint pipeline); + GLboolean IsProgramPipeline(GLuint pipeline); + void GetProgramPipelineiv(GLuint pipeline, GLenum pname, GLint* params); + void GetProgramPipelineInfoLog(GLuint pipeline, GLsizei bufSize, GLsizei* length, GLchar* infoLog); + void UseProgramStages(GLuint pipeline, GLbitfield stages, GLuint program); + void ActiveShaderProgram(GLuint pipeline, GLuint program); + void ValidateProgramPipeline(GLuint pipeline); +} // namespace MobileGL::MG_Impl::GLImpl diff --git a/MobileGL/MG_State/GLState/Core.cpp b/MobileGL/MG_State/GLState/Core.cpp index 041a29fc..2af10d3d 100644 --- a/MobileGL/MG_State/GLState/Core.cpp +++ b/MobileGL/MG_State/GLState/Core.cpp @@ -802,6 +802,53 @@ namespace MobileGL::MG_State { m_transformFeedbackObjects[id] = {}; } } + // Program pipeline + void GLContext::GenProgramPipelineNames(Uint number, Vector& 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(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(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& GLContext::GetProgramPipelineObject(Uint index) const { + static const SharedPtr kNone; + const auto it = m_programPipelines.find(index); + return it == m_programPipelines.end() ? kNone : it->second; + } + + const SharedPtr& GLContext::GetBoundProgramPipeline() const { + return GetProgramPipelineObject(m_boundProgramPipeline); + } + Bool GLContext::ValidateTransformFeedbackName(Uint index) const { return index == 0 || m_transformFeedbackNames.IsValid(index); diff --git a/MobileGL/MG_State/GLState/Core.h b/MobileGL/MG_State/GLState/Core.h index 69d042f6..2c90e6c3 100644 --- a/MobileGL/MG_State/GLState/Core.h +++ b/MobileGL/MG_State/GLState/Core.h @@ -14,6 +14,7 @@ #include "MG_State/GLState/RenderbufferState/RenderbufferState.h" #include "RenderState/RenderState.h" #include "ProgramState/ProgramState.h" +#include "ProgramState/ProgramPipelineObject.h" #include "SamplerState/SamplerState.h" #include "TextureState/TextureState.h" #include "FramebufferState/FramebufferState.h" @@ -137,6 +138,19 @@ namespace MobileGL { void UseProgram(Uint program); const SharedPtr& GetCurrentProgram(); + // 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. + void GenProgramPipelineNames(Uint number, Vector& pipelines); + void CreateProgramPipelineObject(Uint index); + Bool ValidateProgramPipelineName(Uint index) const; + Bool IsProgramPipelineObject(Uint index) const; + void BindProgramPipelineObject(Uint index); + void MarkProgramPipelineForDeletion(Uint index); + const SharedPtr& GetProgramPipelineObject(Uint index) const; + Uint GetBoundProgramPipelineName() const { return m_boundProgramPipeline; } + const SharedPtr& GetBoundProgramPipeline() const; + // RenderState Uint GetRenderStateParametersVersion() const; const RenderStateParameters& GetRenderStateParameters() const; @@ -390,6 +404,11 @@ namespace MobileGL { UnorderedMap m_transformFeedbackObjects; IndexGenerator m_transformFeedbackNames; Uint m_boundTransformFeedback = 0; + // Map membership IS object existence here: a pipeline has no stateful default + // object 0, so no everBound flag is needed. + UnorderedMap> m_programPipelines; + IndexGenerator m_programPipelineNames; + Uint m_boundProgramPipeline = 0; TextureState m_textureState; ProgramState m_programState; RenderState m_renderState; diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramPipelineObject.h b/MobileGL/MG_State/GLState/ProgramState/ProgramPipelineObject.h new file mode 100644 index 00000000..a5a8fcf6 --- /dev/null +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramPipelineObject.h @@ -0,0 +1,52 @@ +// MobileGL - MobileGL/MG_State/GLState/ProgramState/ProgramPipelineObject.h +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +#pragma once +#include +#include "ProgramObject.h" + +namespace MobileGL { + namespace MG_State { + namespace GLState { + // A GL_ARB_separate_shader_objects program pipeline (GL 4.6 core 7.4): a set of + // per-stage program references plus the program glProgramUniform* addresses when no + // program object is in use. + class ProgramPipelineObject { + public: + explicit ProgramPipelineObject(Uint externalIndex) : m_externalIndex(externalIndex) {} + + const SharedPtr& GetStageProgram(ShaderStage stage) const { + return m_stagePrograms[static_cast(stage)]; + } + void SetStageProgram(ShaderStage stage, const SharedPtr& program) { + m_stagePrograms[static_cast(stage)] = program; + } + + const SharedPtr& GetActiveProgram() const { return m_activeProgram; } + void SetActiveProgram(const SharedPtr& program) { m_activeProgram = program; } + + // Distinct from ProgramObject's, which starts true: a pipeline that has never been + // validated must report GL_VALIDATE_STATUS as 0 (GL 4.6 core table 23.31). + Bool GetValidateStatus() const { return m_validateStatus; } + void SetValidateStatus(Bool value) { m_validateStatus = value; } + + const String& GetInfoLog() const { return m_infoLog; } + void SetInfoLog(String log) { m_infoLog = Move(log); } + + Uint GetExternalIndex() const { return m_externalIndex; } + + private: + Array, static_cast(ShaderStage::ShaderStageCount)> m_stagePrograms{}; + SharedPtr m_activeProgram; + String m_infoLog; + const Uint m_externalIndex = 0; + Bool m_validateStatus = false; + }; + } // namespace GLState + } // namespace MG_State +} // namespace MobileGL