// MobileGL - MobileGL/MG_State/GLState/ProgramState/ProgramState.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 #include "ProgramObject.h" #include "ShaderCompileAdoptionMap.h" #include "ShaderPreprocessCache.h" namespace MobileGL::MG_State::GLState { // The program and shader objects of one share group, and the name space they share. The // program a context has in use is context state and lives on GLContext - the operations // that need it (a delete of the current program, the deferred destroy UseProgram // performs, the orphan test) take it as a parameter. class ProgramState { public: // This function WILL actually create the program object. // To retrieve created program object, use GetProgramObject() Uint CreateProgram(); const SharedPtr& GetProgramObject(Uint id); void MarkProgramObjectForDeletion(Uint program, const SharedPtr& currentProgram); Bool ValidateProgramObject(Uint program) const; // Whether `program` names a slot in the table at all, i.e. in range - not whether that // slot currently holds an object. Bool HasProgramSlot(Uint program) const; Uint CreateShader(ShaderStage stage); const SharedPtr& GetShaderObject(Uint shader); void MarkShaderObjectForDeletion(Uint shader, const SharedPtr& currentProgram); // Frees a deletion-flagged shader's name once no program holds a GL-visible // attachment to it (the deferred half of glDeleteShader-while-attached). void ReleaseShaderNameIfOrphaned(Uint shader, const SharedPtr& currentProgram); Bool ValidateShaderObject(Uint shader) const; // Joins every outstanding compile and link this share group still owns, publishing each // one's artifacts through the ordinary gates. The single caller is // glMaxShaderCompilerThreadsKHR(0): GL_KHR_parallel_shader_compile requires a zero // count to leave nothing in flight, so that every subsequent // GL_COMPLETION_STATUS_KHR reads GL_TRUE. // // NOT a teardown path and NOT ShaderCompilePool::StopAndDrain(): the pool keeps its // threads and stays usable, because a later nonzero count has to bring asynchronous // compilation straight back. Nodes belonging to objects this context has already // dropped are not joined - nothing can observe them, and waiting on them would make // a GL call's cost depend on garbage. void JoinAllPendingWork(const SharedPtr& currentProgram); // P0b layer 2. Exposed for tests and diagnostics; the GL frontend never touches it // directly - shader objects reach it through the pointer they are handed at // CreateShader(). ShaderPreprocessCache& GetShaderPreprocessCache() { return *m_shaderPreprocessCache; } // P1 stage 6, same deal: exposed for tests and diagnostics only. Its adoption counter // is the one number that says how many glCompileShader calls this context turned into // no work at all; nothing in the GL frontend branches on it. ShaderCompileAdoptionMap& GetShaderCompileAdoptionMap() { return *m_shaderCompileAdoptionMap; } private: Bool ShaderHasGLVisibleAttachment(const SharedPtr& shaderObject, const SharedPtr& currentProgram) const; // Frees the name slot and releases orphaned attached shaders; the immediate half // of glDeleteProgram (deferred while the program is current). void DestroyProgramSlot(Uint program, const SharedPtr& currentProgram); template static Bool CheckIndexAvail(const SizeT idx, const Vector& vec) { return idx < vec.size(); } template static void EnsureIndexAvail(const SizeT idx, Vector& vec) { if (CheckIndexAvail(idx, vec)) return; vec.reserve(std::bit_ceil(idx)); vec.resize(idx + 1); } // Programs and shaders share one GL name space (GL 3.3 core 2.11: a shader // name passed where a program is expected must be recognized as a shader and // rejected with INVALID_OPERATION, and vice versa). One generator for both // object kinds keeps the names disjoint; the object tables stay separate. IndexGenerator m_programShaderNameGenerator; // P0b layer 2: every shader object created here is handed shared ownership of this // cache, so its lifetime no longer depends on member destruction order (P1: an // in-flight compile job may outlive the context). The FIRST-member declaration is // kept anyway - it costs nothing and documents the intent. SharedPtr m_shaderPreprocessCache = MakeShared(); // P1 stage 6: the GL-thread-only index of adoptable compile nodes. Shared ownership // for the same reason as the cache above - a ShaderObject held by a ProgramObject can // outlive these tables, and its destructor releases a node - though unlike the cache // no worker ever sees this one, which is why it carries no lock. SharedPtr m_shaderCompileAdoptionMap = MakeShared(); Vector> m_programObjects; Vector> m_shaderObjects; }; } // namespace MobileGL::MG_State::GLState