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;