[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:
BZLZHH
2026-08-08 13:17:58 -04:00
parent 6f8b7fbc40
commit bd0def6133
15 changed files with 265 additions and 10 deletions
@@ -1273,7 +1273,7 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, MultiTexCoord4ivARB, GLenum target, const GL
DECLARE_GL_FUNCTION_STUB_HEAD(void, MultiTexCoord4sARB, GLenum target, GLshort s, GLshort t, GLshort r, GLshort q) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, MultiTexCoord4sARB, target, s, t, r, q)
DECLARE_GL_FUNCTION_STUB_HEAD(void, MultiTexCoord4svARB, GLenum target, const GLshort* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, MultiTexCoord4svARB, target, v)
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetQueryObjectivARB, GLuint id, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetQueryObjectivARB, id, pname, params)
DECLARE_GL_FUNCTION_STUB_HEAD(void, MaxShaderCompilerThreadsARB, GLuint count) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, MaxShaderCompilerThreadsARB, count)
DECLARE_GL_FUNCTION_HEAD(void, MaxShaderCompilerThreadsARB, GLuint count) DECLARE_GL_FUNCTION_END_NO_RETURN(void, MaxShaderCompilerThreadsARB, count)
DECLARE_GL_FUNCTION_STUB_HEAD(void, PointParameterfARB, GLenum pname, GLfloat param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PointParameterfARB, pname, param)
DECLARE_GL_FUNCTION_STUB_HEAD(void, PointParameterfvARB, GLenum pname, const GLfloat* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PointParameterfvARB, pname, params)
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnTexImageARB, GLenum target, GLint level, GLenum format, GLenum type, GLsizei bufSize, void* img) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetnTexImageARB, target, level, format, type, bufSize, img)
@@ -1381,7 +1381,7 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, WindowPos3ivARB, const GLint* v) DECLARE_GL_
DECLARE_GL_FUNCTION_STUB_HEAD(void, WindowPos3sARB, GLshort x, GLshort y, GLshort z) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, WindowPos3sARB, x, y, z)
DECLARE_GL_FUNCTION_STUB_HEAD(void, WindowPos3svARB, const GLshort* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, WindowPos3svARB, v)
DECLARE_GL_FUNCTION_STUB_HEAD(void, BlendBarrierKHR, void) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BlendBarrierKHR, )
DECLARE_GL_FUNCTION_STUB_HEAD(void, MaxShaderCompilerThreadsKHR, GLuint count) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, MaxShaderCompilerThreadsKHR, count)
DECLARE_GL_FUNCTION_HEAD(void, MaxShaderCompilerThreadsKHR, GLuint count) DECLARE_GL_FUNCTION_END_NO_RETURN(void, MaxShaderCompilerThreadsKHR, count)
DECLARE_GL_FUNCTION_STUB_HEAD(void, MultiTexCoord1bOES, GLenum texture, GLbyte s) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, MultiTexCoord1bOES, texture, s)
DECLARE_GL_FUNCTION_STUB_HEAD(void, MultiTexCoord1bvOES, GLenum texture, const GLbyte* coords) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, MultiTexCoord1bvOES, texture, coords)
DECLARE_GL_FUNCTION_STUB_HEAD(void, MultiTexCoord2bOES, GLenum texture, GLbyte s, GLbyte t) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, MultiTexCoord2bOES, texture, s, t)
@@ -23,6 +23,7 @@
#include <MG_Util/Converters/MGToGL/RenderStateEnumConverter.h>
#include <MG_State/GLState/FramebufferState/FramebufferObject.h>
#include <MG_Util/Texture/TextureFormatProcessor.h>
#include <MG_Util/Async/ShaderCompilePool.h>
#include <MG_Backend/BackendObjects.h>
namespace MobileGL::MG_Impl::GLImpl {
@@ -1069,6 +1070,20 @@ namespace MobileGL::MG_Impl::GLImpl {
*params = obj ? static_cast<GLint>(obj->GetExternalIndex()) : 0;
return;
}
case GL_MAX_SHADER_COMPILER_THREADS_KHR:
// GL_KHR_parallel_shader_compile (GL_MAX_SHADER_COMPILER_THREADS_ARB is the same
// 0x91B0). The number of threads MobileGL's compile pool would actually use, so
// an application sizing its own submission batches gets a real answer.
//
// Zero when asynchronous compilation is off, which is the honest reply and the
// one the extension defines for an implementation with no compiler threads: the
// extension string is withdrawn in that configuration too, so a conforming
// application never reaches this query, and one that asks anyway is told there
// are none rather than being handed a thread count nothing will use.
*params = MG_Util::Async::AsyncShaderCompileEnabled()
? static_cast<GLint>(MG_Util::Async::ShaderCompilePool::Get().GetThreadCount())
: 0;
return;
case GL_MAX_DEBUG_GROUP_STACK_DEPTH:
*params = 0; // debug-group entrypoints are stubbed
return;
@@ -16,6 +16,7 @@
#include <MG_Util/Converters/GLToMG/ProgramEnumConverter.h>
#include <MG_Util/Converters/MGToGL/ProgramEnumConverter.h>
#include <MG_Util/Converters/SPIRVCrossToGL/SpvcTypeConverter.h>
#include <MG_Util/Async/ShaderCompilePool.h>
#include <MG_Backend/BackendObjects.h>
namespace MobileGL::MG_Impl::GLImpl {
@@ -355,6 +356,54 @@ namespace MobileGL::MG_Impl::GLImpl {
shaderObject->Compile();
}
// glMaxShaderCompilerThreadsKHR / glMaxShaderCompilerThreadsARB - one implementation,
// because GL_KHR_parallel_shader_compile and GL_ARB_parallel_shader_compile define the
// same entry point with the same semantics and GetProcAddress.cpp maps both spellings.
//
// The three cases the extension defines, and what each means here:
//
// count == 0 "no compiler threads": compilation must happen on the
// application's thread. Everything already in flight is joined
// first, so that after this call returns NOTHING is outstanding
// and every GL_COMPLETION_STATUS_KHR reads GL_TRUE - which is
// the observable the extension actually specifies. The pool
// keeps its worker threads (this is not teardown); what changes
// is that AsyncShaderCompileActive() now says no, so
// glCompileShader/glLinkProgram run their bodies inline.
// count == 0xFFFFFFFF "implementation maximum": the pool's full thread count.
// otherwise a concurrency budget, clamped to the thread count - asking for
// more threads than exist cannot conjure any.
//
// A nonzero count is also what LIFTS a previous zero: the suspension lasts exactly until
// the application asks for threads again, and nothing else re-arms it (no implicit
// restore at eglInitialize, at a context switch or at a join). An application that turned
// compiler threads off keeps them off until it says otherwise.
//
// Legal - and a no-op beyond bookkeeping - while MOBILEGL_ASYNC_SHADER_COMPILE is off:
// compilation is already inline, and the call must not fail just because MobileGL had
// nothing to suspend.
void MaxShaderCompilerThreadsKHR_State(GLuint count) {
namespace Async = MG_Util::Async;
if (count == 0) {
MGLOG_D("%s: count = 0; joining all pending shader work and compiling inline", __func__);
Async::SetAsyncShaderCompileSuspended(true);
// Suspend BEFORE joining, not after. The post-condition this call owes the
// application is "nothing is in flight when I return", and only this order
// guarantees it: with the latch already set, anything the join itself causes to
// be compiled runs inline and is therefore already settled when the join ends.
// Joining first would leave a window in which a fresh enqueue is still legal.
if (MG_State::pGLContext) MG_State::pGLContext->JoinAllPendingShaderWork();
return;
}
Async::ShaderCompilePool& pool = Async::ShaderCompilePool::Get();
const Uint threadCount = pool.GetThreadCount();
const Uint requested = count == 0xFFFFFFFFu ? threadCount : std::min<Uint>(count, threadCount);
pool.SetMaxConcurrency(requested);
Async::SetAsyncShaderCompileSuspended(false);
MGLOG_D("%s: count = %u; concurrency = %u of %u threads", __func__, count, requested, threadCount);
}
GLuint CreateProgram_State() {
return MG_State::pGLContext->CreateProgram();
}
@@ -693,6 +742,19 @@ namespace MobileGL::MG_Impl::GLImpl {
break;
}
// GL_KHR_parallel_shader_compile. THIS CASE MUST NOT JOIN - it is the one program
// query whose entire purpose is to answer without waiting, and routing it through
// any of ProgramObject's Artifacts() accessors (the join gate, invariant I5) would
// block the caller and make the extension a lie: an application polling it would
// serialize itself on the very link it is trying to overlap. IsLinkComplete() is the
// node-direct reader that exists for exactly this.
//
// No link at all reads GL_TRUE, which is what the extension requires: the query
// means "is anything still outstanding", not "has this program ever been linked".
case GL_COMPLETION_STATUS_KHR:
*params = programObject->IsLinkComplete() ? GL_TRUE : GL_FALSE;
break;
case GL_PROGRAM_BINARY_LENGTH:
// No program binary format is exposed, so a program never has a retrievable
// binary and its length is zero (ARB_get_program_binary).
@@ -746,6 +808,13 @@ namespace MobileGL::MG_Impl::GLImpl {
case GL_SHADER_SOURCE_LENGTH:
*params = shaderObject->GetShaderSource().empty() ? 0 : (GLint)shaderObject->GetShaderSource().length() + 1;
break;
// GL_KHR_parallel_shader_compile. THIS CASE MUST NOT JOIN - see the identical case in
// GetProgramiv_State. GL_COMPILE_STATUS two cases up deliberately DOES join (it has
// to: it reports the outcome); this one reports whether there is an outcome yet, and
// reading it through Compiled() would defeat the whole extension.
case GL_COMPLETION_STATUS_KHR:
*params = shaderObject->IsCompileComplete() ? GL_TRUE : GL_FALSE;
break;
default:
MG_State::pGLContext->RecordError(
ErrorCode::InvalidEnum,
@@ -1832,6 +1901,16 @@ namespace MobileGL::MG_Impl::GLImpl {
void CompileShader(GLuint shader) {
CompileShader_State(shader);
}
void MaxShaderCompilerThreadsKHR(GLuint count) {
MaxShaderCompilerThreadsKHR_State(count);
}
// GL_ARB_parallel_shader_compile's spelling of the same entry point.
void MaxShaderCompilerThreadsARB(GLuint count) {
MaxShaderCompilerThreadsKHR_State(count);
}
GLuint CreateProgram(void) {
return CreateProgram_State();
}
@@ -42,6 +42,10 @@ namespace MobileGL::MG_Impl::GLImpl {
GLboolean IsProgram(GLuint program);
GLboolean IsShader(GLuint shader);
void LinkProgram(GLuint program);
// GL_KHR_parallel_shader_compile / GL_ARB_parallel_shader_compile. Both names are the
// same entry point; see MaxShaderCompilerThreadsKHR_State for the semantics of count.
void MaxShaderCompilerThreadsKHR(GLuint count);
void MaxShaderCompilerThreadsARB(GLuint count);
void ShaderSource(GLuint shader, GLsizei count, const GLchar* const* string, const GLint* length);
void UseProgram(GLuint program);
void Uniform1f(GLint location, GLfloat v0);