mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 13:48:30 +09:00
[Feat] (MG_Impl, MG_State, MG_Util): the GL_KHR_parallel_shader_compile surface (P1 stage 5)
GL_COMPLETION_STATUS_KHR in both object getters, reading the non-joining node-direct state - the one query that must never block is asserted never to reach a join gate. glMaxShaderCompilerThreadsKHR/ARB share one implementation: a zero count suspends async FIRST and then joins every outstanding compile and link this context owns (suspend-before-join is the only order whose post-condition is 'nothing in flight'), a nonzero count restores; the suspension is a process latch the extension controls, kept distinct from the configuration flag that gates the ADVERTISEMENT - an app that turned threading off has not made the extension disappear. GL_MAX_SHADER_COMPILER_THREADS_KHR reports the thread count. DriverPost gains the MobileGL-side async row (PASS/INFO naming the env knob) and an informational host-driver row backed by a new GLES capability probe. The extension string itself lands per backend in the two follow-up commits, keeping this one green stand-alone.
This commit is contained in:
@@ -357,6 +357,10 @@ namespace MobileGL::MG_State {
|
||||
return m_programState.GetShaderObject(index);
|
||||
}
|
||||
|
||||
void GLContext::JoinAllPendingShaderWork() {
|
||||
m_programState.JoinAllPendingWork();
|
||||
}
|
||||
|
||||
void GLContext::UseProgram(Uint program) {
|
||||
return m_programState.UseProgram(program);
|
||||
}
|
||||
|
||||
@@ -153,6 +153,9 @@ namespace MobileGL {
|
||||
Bool ValidateShaderName(Uint index) const;
|
||||
const SharedPtr<ProgramObject>& GetProgramObject(Uint index);
|
||||
const SharedPtr<ShaderObject>& GetShaderObject(Uint index);
|
||||
// Settles every compile and link this context still owns; see
|
||||
// ProgramState::JoinAllPendingWork. Called by glMaxShaderCompilerThreadsKHR(0).
|
||||
void JoinAllPendingShaderWork();
|
||||
void UseProgram(Uint program);
|
||||
const SharedPtr<ProgramObject>& GetCurrentProgram();
|
||||
// What a draw or dispatch actually executes: the program in use, or - when
|
||||
|
||||
@@ -292,10 +292,11 @@ namespace MobileGL::MG_State::GLState {
|
||||
|
||||
m_pendingLink = task;
|
||||
|
||||
// Flag off: byte-identical to the synchronous implementation. RunInline() executes
|
||||
// the same body on this thread and the join below publishes through the same code, so
|
||||
// the two modes differ only in WHICH thread ran RunBody().
|
||||
if (!MG_Util::Async::AsyncShaderCompileEnabled()) {
|
||||
// Flag off - or glMaxShaderCompilerThreadsKHR(0), see AsyncShaderCompileActive():
|
||||
// byte-identical to the synchronous implementation. RunInline() executes the same
|
||||
// body on this thread and the join below publishes through the same code, so the two
|
||||
// modes differ only in WHICH thread ran RunBody().
|
||||
if (!MG_Util::Async::AsyncShaderCompileActive()) {
|
||||
task->RunInline();
|
||||
EnsureLinkJoined();
|
||||
return;
|
||||
|
||||
@@ -99,6 +99,31 @@ namespace MobileGL::MG_State::GLState {
|
||||
return m_shaderObjects[shader];
|
||||
}
|
||||
|
||||
void ProgramState::JoinAllPendingWork() {
|
||||
// Programs first: a link joins the compiles it depends on, so the shader pass that
|
||||
// follows finds most of them already settled. The reverse order would be correct but
|
||||
// would wait on each compile twice - once here, once inside the link's own prologue.
|
||||
//
|
||||
// A copy of each slot rather than a reference into the vector, and an index rather
|
||||
// than an iterator: publishing a link replays deferred diagnostics, which reach
|
||||
// pGLContext->RecordError. That does not touch these tables today, but it is a sink
|
||||
// that can grow, and a reallocation underneath this loop would be a use-after-free
|
||||
// that only shows up on the one GL call that walks the whole table. The copy costs a
|
||||
// refcount bump on a path a mode switch takes at most once.
|
||||
for (SizeT i = 0; i < m_programObjects.size(); ++i) {
|
||||
const SharedPtr<ProgramObject> program = m_programObjects[i];
|
||||
if (program) program->JoinLink();
|
||||
}
|
||||
for (SizeT i = 0; i < m_shaderObjects.size(); ++i) {
|
||||
const SharedPtr<ShaderObject> shader = m_shaderObjects[i];
|
||||
if (shader) shader->JoinCompile();
|
||||
}
|
||||
// The currently-used program is reachable through m_programObjects unless
|
||||
// glDeleteProgram already freed its slot while it stayed current. Nothing else holds
|
||||
// a GL-visible name for it, but a draw would still join it, so settle it here too.
|
||||
if (m_currentProgram) m_currentProgram->JoinLink();
|
||||
}
|
||||
|
||||
void ProgramState::MarkShaderObjectForDeletion(Uint shader) {
|
||||
if (!CheckIndexAvail(shader, m_shaderObjects)) return;
|
||||
auto& shaderObject = m_shaderObjects[shader];
|
||||
|
||||
@@ -34,6 +34,19 @@ namespace MobileGL::MG_State::GLState {
|
||||
|
||||
const SharedPtr<ProgramObject>& GetCurrentProgram() const { return m_currentProgram; }
|
||||
|
||||
// Joins every outstanding compile and link this context 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();
|
||||
|
||||
// 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().
|
||||
|
||||
@@ -115,7 +115,11 @@ namespace MobileGL::MG_State::GLState {
|
||||
// path must be byte-identical to the synchronous implementation, and a cache-less
|
||||
// object is an internal shader that compiles and reads its status in the same
|
||||
// breath (see the constructor comment) - a job would only add a round trip.
|
||||
if (!m_preprocessCache || !MG_Util::Async::AsyncShaderCompileEnabled()) {
|
||||
// AsyncShaderCompileActive(), not ...Enabled(): a glMaxShaderCompilerThreadsKHR(0)
|
||||
// has to put compilation back on this thread even though the extension is still
|
||||
// advertised, and that is exactly what makes the GL_COMPLETION_STATUS_KHR the
|
||||
// extension mandates after a zero count (immediately GL_TRUE) fall out for free.
|
||||
if (!m_preprocessCache || !MG_Util::Async::AsyncShaderCompileActive()) {
|
||||
m_compiled->RunInline();
|
||||
// Inline means the node is already terminal, so this join only replays
|
||||
// diagnostics; it is here so the synchronous and asynchronous paths publish
|
||||
|
||||
Reference in New Issue
Block a user