From 4ce808b9f2d2441a4d8714f484ca9e7e26d9dc09 Mon Sep 17 00:00:00 2001 From: BZLZHH Date: Wed, 5 Aug 2026 07:20:42 -0400 Subject: [PATCH] [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. --- MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp | 12 ++-- .../DirectVulkan/Renderer/VulkanRenderer.cpp | 8 +-- .../MG_Impl/GLImpl/Drawing/GL_Drawing.cpp | 8 +-- .../MG_Impl/GLImpl/Exporting/Definitions.cpp | 2 +- .../MG_Impl/GLImpl/Program/GL_Program.cpp | 72 +++++++++++++++---- MobileGL/MG_Impl/GLImpl/Program/GL_Program.h | 1 + MobileGL/MG_State/GLState/Core.cpp | 49 +++++++++++++ MobileGL/MG_State/GLState/Core.h | 6 ++ .../GLState/ProgramState/ProgramObject.h | 13 ++++ .../ProgramState/ProgramPipelineObject.h | 31 ++++++++ 10 files changed, 172 insertions(+), 30 deletions(-) diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index 41e88bbd..bf046386 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -753,7 +753,7 @@ namespace MobileGL::MG_Backend::DirectGLES { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif - const auto& program = MG_State::pGLContext->GetCurrentProgram(); + const auto& program = MG_State::pGLContext->GetProgramForDraw(); if (!program) return; const auto& vao = MG_State::pGLContext->GetBoundVertexArray(); @@ -1377,7 +1377,7 @@ namespace MobileGL::MG_Backend::DirectGLES { g_backendProgramObjects.CollectGarbageIfNeeded(); SamplerImpl::g_backendSamplerObjects.CollectGarbageIfNeeded(); - auto& currentProgram = MG_State::pGLContext->GetCurrentProgram(); + auto& currentProgram = MG_State::pGLContext->GetProgramForDraw(); if (!currentProgram || !currentProgram->GetLinkStatus()) { g_GLESFuncs.glUseProgram(0); g_lastUsedBackendProgramId = 0; @@ -1546,7 +1546,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // Frontend target the current program samples at a given unit; resolves an // aliased native binding when two real textures compete for it (see below). // Only consulted on a conflict, so the ordinary unit costs nothing. - const auto& currentProgram = MG_State::pGLContext->GetCurrentProgram(); + const auto& currentProgram = MG_State::pGLContext->GetProgramForDraw(); const auto sampledTargetForUnit = [¤tProgram](Int unit) { if (!currentProgram || !currentProgram->GetLinkStatus()) { return TextureTarget::Unknown; @@ -1679,7 +1679,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // this as much as draws do — e.g. Flywheel's cull shader reads the // _FlwFrameUniforms block and the _flw_depthPyramid sampler. static void BindCurrentProgramWithResources() { - const auto& currentProgram = MG_State::pGLContext->GetCurrentProgram(); + const auto& currentProgram = MG_State::pGLContext->GetProgramForDraw(); if (currentProgram && currentProgram->GetLinkStatus()) { #ifdef TRACY_ENABLE ZoneScopedNC("BindCurrentProgram", TRACY_ZONECOLOR_BACKEND); @@ -1865,7 +1865,7 @@ namespace MobileGL::MG_Backend::DirectGLES { } static SharedPtr GetCurrentBackendProgram() { - const auto& currentProgram = MG_State::pGLContext->GetCurrentProgram(); + const auto& currentProgram = MG_State::pGLContext->GetProgramForDraw(); if (!currentProgram || !currentProgram->GetLinkStatus()) { return nullptr; } @@ -2012,7 +2012,7 @@ namespace MobileGL::MG_Backend::DirectGLES { TextureImpl::SyncImageTextureBindings(); PrgramImpl::SyncCurrentProgram(); - const auto& currentProgram = MG_State::pGLContext->GetCurrentProgram(); + const auto& currentProgram = MG_State::pGLContext->GetProgramForDraw(); if (!currentProgram || !currentProgram->GetLinkStatus()) { g_GLESFuncs.glUseProgram(0); PrgramImpl::g_lastUsedBackendProgramId = 0; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 96cd704c..ac5042b3 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -4666,7 +4666,7 @@ void main() { snap.imageIndex != m_imageIndexAcquired) { return false; } - const auto& program = *MG_State::pGLContext->GetCurrentProgram(); + const auto& program = *MG_State::pGLContext->GetProgramForDraw(); if (program.GetLifetimeId() != snap.programLifetimeId || program.GetBackendStateVersion() != snap.programVersion) { return false; @@ -4792,7 +4792,7 @@ void main() { return false; } const auto& vao = *MG_State::pGLContext->GetBoundVertexArray(); - const auto& program = *MG_State::pGLContext->GetCurrentProgram(); + const auto& program = *MG_State::pGLContext->GetProgramForDraw(); ProgramFactory::CompileOptionFlags transformFlags = GetShaderTransformFlags(m_swapchainObject.GetPreTransform()); // Captured draws take the xfb-decorated program variant. if (m_transformFeedbackFeatureEnabled && MG_State::pGLContext->IsTransformFeedbackActive() && @@ -5176,7 +5176,7 @@ void main() { void VulkanRenderer::DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ) { m_textureManager->CollectGarbage(); auto& frame = m_frameContext.GetCurrent(); - const auto& program = *MG_State::pGLContext->GetCurrentProgram(); + const auto& program = *MG_State::pGLContext->GetProgramForDraw(); ProgramFactory::CompileOptionFlags transformFlags = 0; const auto& programObj = m_programFactory->GetOrCreateProgram(program, transformFlags); @@ -5216,7 +5216,7 @@ void main() { void VulkanRenderer::DispatchComputeIndirect(GLintptr indirect) { m_textureManager->CollectGarbage(); auto& frame = m_frameContext.GetCurrent(); - const auto& program = *MG_State::pGLContext->GetCurrentProgram(); + const auto& program = *MG_State::pGLContext->GetProgramForDraw(); ProgramFactory::CompileOptionFlags transformFlags = 0; const auto& programObj = m_programFactory->GetOrCreateProgram(program, transformFlags); diff --git a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp index f64d9a92..39ab46f9 100644 --- a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp +++ b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp @@ -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("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, diff --git a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp index 933306a4..ad198528 100644 --- a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp +++ b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp @@ -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) diff --git a/MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp b/MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp index 996e52f7..03a3ed85 100644 --- a/MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp +++ b/MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp @@ -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("MG_Impl/GLImpl", __func__, "pname is not an accepted value.")); @@ -2758,9 +2761,48 @@ namespace MobileGL::MG_Impl::GLImpl { MakeUnique("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; diff --git a/MobileGL/MG_Impl/GLImpl/Program/GL_Program.h b/MobileGL/MG_Impl/GLImpl/Program/GL_Program.h index 7ac81a1b..f8bf8361 100644 --- a/MobileGL/MG_Impl/GLImpl/Program/GL_Program.h +++ b/MobileGL/MG_Impl/GLImpl/Program/GL_Program.h @@ -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); diff --git a/MobileGL/MG_State/GLState/Core.cpp b/MobileGL/MG_State/GLState/Core.cpp index 2af10d3d..08a51e02 100644 --- a/MobileGL/MG_State/GLState/Core.cpp +++ b/MobileGL/MG_State/GLState/Core.cpp @@ -343,6 +343,55 @@ namespace MobileGL::MG_State { return m_programState.GetCurrentProgram(); } + const SharedPtr& GLContext::GetProgramForDraw() { + static const SharedPtr nullProgram = nullptr; + const auto& currentProgram = m_programState.GetCurrentProgram(); + if (currentProgram) return currentProgram; + if (m_boundProgramPipeline == 0) return nullProgram; + const auto& pipeline = GetBoundProgramPipeline(); + if (!pipeline) return nullProgram; + + const auto signature = pipeline->ComputeDrawProgramSignature(); + if (const auto& cached = pipeline->GetCachedDrawProgram(signature)) return cached; + + // Everything downstream of here - the backends, the uniform plumbing, the draw + // validation - is written against a single linked program, so the pipeline is + // flattened into one. Each stage contributes only the shaders that serve it, so a + // program bound to two stages is not pulled in twice and a program bound to a + // stage it does not implement contributes nothing. + // Deliberately not a named program: it is reachable only through the pipeline, it + // must not answer glIsProgram, and it must not consume a name the application + // could otherwise be handed. Backend registries key on the object, not the name. + auto composite = MakeShared(0u); + + Bool anyStage = false; + for (SizeT stage = 0; stage < static_cast(ShaderStage::ShaderStageCount); ++stage) { + const auto& stageProgram = pipeline->GetStageProgram(static_cast(stage)); + if (!stageProgram) continue; + for (const auto& shader : stageProgram->GetAttachedShaders()) { + if (!shader || static_cast(shader->GetShaderStage()) != stage) continue; + composite->AttachShader(shader); + anyStage = true; + } + } + if (!anyStage) return nullProgram; + // A pipeline with no fragment stage still rasterises, so the default fragment + // shader is wanted here even though the separable stage programs never get one. + composite->Link(true); + pipeline->SetCachedDrawProgram(signature, Move(composite)); + return pipeline->GetCachedDrawProgram(signature); + } + + const SharedPtr& GLContext::GetProgramForUniform() { + const auto& currentProgram = m_programState.GetCurrentProgram(); + if (currentProgram) return currentProgram; + static const SharedPtr nullProgram = nullptr; + if (m_boundProgramPipeline == 0) return nullProgram; + const auto& pipeline = GetBoundProgramPipeline(); + if (!pipeline) return nullProgram; + return pipeline->GetActiveProgram(); + } + // RenderState Uint GLContext::GetRenderStateParametersVersion() const { return m_renderState.GetVersion(); diff --git a/MobileGL/MG_State/GLState/Core.h b/MobileGL/MG_State/GLState/Core.h index 2c90e6c3..f3a8e620 100644 --- a/MobileGL/MG_State/GLState/Core.h +++ b/MobileGL/MG_State/GLState/Core.h @@ -137,6 +137,12 @@ namespace MobileGL { const SharedPtr& GetShaderObject(Uint index); void UseProgram(Uint program); const SharedPtr& GetCurrentProgram(); + // What a draw or dispatch actually executes: the program in use, or - when + // there is none - the bound pipeline's stages composited into one program. + const SharedPtr& GetProgramForDraw(); + // What glUniform* addresses: the program in use, or the bound pipeline's + // active program (GL 4.6 core 7.6.1). + const SharedPtr& GetProgramForUniform(); // 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 diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h index 4c5c1d0a..c19b8c69 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h @@ -45,6 +45,13 @@ namespace MobileGL::MG_State::GLState { Vector>& GetAttachedShaders(); const Vector>& GetAttachedShaders() const; const String& GetInfoLog() const { return m_infoLog; } + // glCreateShaderProgramv folds the shader's compile log into the program's log, which + // is the only place a caller can read it from once the shader name is gone. + void AppendInfoLog(const String& text) { + if (text.empty()) return; + if (!m_infoLog.empty() && m_infoLog.back() != '\n') m_infoLog += '\n'; + m_infoLog += text; + } Int GetUniformMaxLength() const { return m_uniformNameMaxLength; } Uint GetUniformCount() const { return m_activeUniformCount; } Uint GetMaxUniformLocation() const { return m_maxUniformLocation; } @@ -382,6 +389,11 @@ namespace MobileGL::MG_State::GLState { // ARB_get_program_binary requires of it. Bool GetBinaryRetrievableHint() const { return m_binaryRetrievableHint; } void SetBinaryRetrievableHint(Bool hint) { m_binaryRetrievableHint = hint; } + // GL_PROGRAM_SEPARABLE (GL_ARB_separate_shader_objects): the program may supply a + // subset of the stages of a program pipeline. Only takes effect on the next link, + // which is why it is plain state here rather than something Link() consults. + Bool GetSeparable() const { return m_separable; } + void SetSeparable(Bool separable) { m_separable = separable; } // glProgramBinary always fails here (there is no format it could accept) and the // spec then requires the program's LINK_STATUS to read FALSE. void MarkLinkFailedByProgramBinary() { @@ -605,6 +617,7 @@ namespace MobileGL::MG_State::GLState { Bool m_deleteStatus = false; Bool m_linkStatus = false; Bool m_binaryRetrievableHint = false; + Bool m_separable = false; Bool m_validateStatus = true; Uint32 m_backendStateVersion = 0; diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramPipelineObject.h b/MobileGL/MG_State/GLState/ProgramState/ProgramPipelineObject.h index a5a8fcf6..165de4e9 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramPipelineObject.h +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramPipelineObject.h @@ -40,9 +40,40 @@ namespace MobileGL { Uint GetExternalIndex() const { return m_externalIndex; } + // A draw sees one program, but a pipeline holds one program per stage. The + // stages are composited into a single hidden program object, rebuilt whenever + // the stage set - or any stage program's own link - changes. The signature is + // what that "changes" means: a stage program's lifetime id pins the object and + // its backend state version pins the link generation. + using DrawProgramSignature = + Array(ShaderStage::ShaderStageCount) * 2>; + + DrawProgramSignature ComputeDrawProgramSignature() const { + DrawProgramSignature signature{}; + for (SizeT stage = 0; stage < static_cast(ShaderStage::ShaderStageCount); ++stage) { + const auto& program = m_stagePrograms[stage]; + if (!program) continue; + signature[stage * 2] = program->GetLifetimeId(); + signature[stage * 2 + 1] = program->GetBackendStateVersion(); + } + return signature; + } + + const SharedPtr& GetCachedDrawProgram(const DrawProgramSignature& signature) const { + static const SharedPtr nullProgram = nullptr; + if (!m_drawProgram || m_drawProgramSignature != signature) return nullProgram; + return m_drawProgram; + } + void SetCachedDrawProgram(const DrawProgramSignature& signature, SharedPtr program) { + m_drawProgramSignature = signature; + m_drawProgram = Move(program); + } + private: Array, static_cast(ShaderStage::ShaderStageCount)> m_stagePrograms{}; SharedPtr m_activeProgram; + SharedPtr m_drawProgram; + DrawProgramSignature m_drawProgramSignature{}; String m_infoLog; const Uint m_externalIndex = 0; Bool m_validateStatus = false;