mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
Breaks the one process = one server = one session = one context equation
that the whole stack's correctness used to rest on. Design and per-slice
shape: docs/Disaggregated/design/11-state-ownership.md.
S1 wire: bind_context ring record (opcode 84) announces the current
context at the make-current edge; CreateContext/DestroyContext join the
SurfaceOp control frames; control revision 5 -> 6; the server keeps a
per-session token table and refuses unknown tokens by name.
S2 server: SessionRuntime per session with thread-scoped resolution
(singletons become fallbacks), per-thread gPipeInputs, per-session latch
domains, per-thread segment resolvers; the in-process unix supervisor
serves up to MOBILEGL_IPC_INPROC_MAX_SESSIONS (16) concurrent sessions;
surface mode is declared per session and the display lease refuses a
second on-screen session by name.
S3 frontend: ShareGroupState owns the shared object stores and name
generators (buffer/texture/renderbuffer/sampler/program), GLContext keeps
per-context bindings/errors/container objects; pGLContext becomes
thread-local with a process default context preserving the single-context
world verbatim; eglCreateContext builds a real GLContext and joins the
sharing context's group.
S4 backend: native EGL tuples keyed by (session, context token);
InitDisplayAndContext's unconditional teardown is gone - creating a
second context or surface no longer destroys the first; eglCreateContext
sharing is wired to the native driver; bind_context is the native switch
point.
S5 applier: MGPipeApplierState splits into MGPipeObjectRecords (per
share group) and MGPipeWorkingState (per context), resolved through a
{session, token} registry with a thread-local cached fast path.
Tests: MultiSessionTest (incl. two sessions x two contexts interleaved
with real clear/readback witnesses), ApplierOwnershipTest (with a
null-resolver negative control), ContextStateOwnershipTest,
ServerLoopEglTest native-context liveness; 99/99 of the acceptance set
green on the host build.
Known debts (documented in the design doc): twin registries still keyed
by handle alone, no per-record ContextSlot, process-global backend
generations, MakeCurrent dedup per current binding.
105 lines
5.8 KiB
C++
105 lines
5.8 KiB
C++
// 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 <Includes.h>
|
|
#include <MG_Util/Miscellany/IndexGenerator.h>
|
|
#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<ProgramObject>& GetProgramObject(Uint id);
|
|
void MarkProgramObjectForDeletion(Uint program, const SharedPtr<ProgramObject>& 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<ShaderObject>& GetShaderObject(Uint shader);
|
|
void MarkShaderObjectForDeletion(Uint shader, const SharedPtr<ProgramObject>& 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<ProgramObject>& 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<ProgramObject>& 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>& shaderObject,
|
|
const SharedPtr<ProgramObject>& 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<ProgramObject>& currentProgram);
|
|
|
|
template <typename T>
|
|
static Bool CheckIndexAvail(const SizeT idx, const Vector<T>& vec) {
|
|
return idx < vec.size();
|
|
}
|
|
|
|
template <typename T>
|
|
static void EnsureIndexAvail(const SizeT idx, Vector<T>& 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<Uint> 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<ShaderPreprocessCache> m_shaderPreprocessCache = MakeShared<ShaderPreprocessCache>();
|
|
// 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<ShaderCompileAdoptionMap> m_shaderCompileAdoptionMap = MakeShared<ShaderCompileAdoptionMap>();
|
|
|
|
Vector<SharedPtr<ProgramObject>> m_programObjects;
|
|
Vector<SharedPtr<ShaderObject>> m_shaderObjects;
|
|
};
|
|
} // namespace MobileGL::MG_State::GLState
|