mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 04:08:32 +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:
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -107,6 +107,25 @@ namespace MobileGL::MG_Util::Async {
|
||||
return kAsyncShaderCompileDefault;
|
||||
}
|
||||
|
||||
namespace {
|
||||
// Written only by glMaxShaderCompilerThreadsKHR/ARB, i.e. only on the GL thread, but
|
||||
// read by every enqueue decision, so it is atomic rather than plain: a worker never
|
||||
// reads it, but a second GL thread in another context shares this process-wide pool.
|
||||
std::atomic<Bool> g_asyncSuspendedByApplication{false};
|
||||
} // namespace
|
||||
|
||||
void SetAsyncShaderCompileSuspended(const Bool suspended) {
|
||||
g_asyncSuspendedByApplication.store(suspended, std::memory_order_release);
|
||||
}
|
||||
|
||||
Bool IsAsyncShaderCompileSuspended() {
|
||||
return g_asyncSuspendedByApplication.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
Bool AsyncShaderCompileActive() {
|
||||
return AsyncShaderCompileEnabled() && !IsAsyncShaderCompileSuspended();
|
||||
}
|
||||
|
||||
Uint DetectShaderCompileThreadCount() {
|
||||
if (const Uint32 configured = MG_Config::Features.AsyncShaderCompileThreads; configured > 0) {
|
||||
// An explicit request is honoured as given - it is the escape hatch for measuring
|
||||
|
||||
@@ -27,11 +27,37 @@ namespace MobileGL::MG_Util::Async {
|
||||
inline constexpr Bool kAsyncShaderCompileDefault = false;
|
||||
|
||||
// MOBILEGL_ASYNC_SHADER_COMPILE forces the answer either way; unset keeps the built-in
|
||||
// default above. Falsy is a complete kill switch: it reverts the threading *and* (from
|
||||
// the extension stage on) withdraws GL_KHR_parallel_shader_compile, so the application
|
||||
// behaviour change goes with it.
|
||||
// default above. Falsy is a complete kill switch: it reverts the threading *and*
|
||||
// withdraws GL_KHR_parallel_shader_compile, so the application behaviour change goes
|
||||
// with it.
|
||||
//
|
||||
// This is the pure CONFIGURATION answer, and it is deliberately not affected by
|
||||
// glMaxShaderCompilerThreadsKHR: it is what decides whether the extension is advertised
|
||||
// at all, and an application that switched threading off through the extension has not
|
||||
// made the extension go away. Code deciding whether to enqueue asks
|
||||
// AsyncShaderCompileActive() instead.
|
||||
Bool AsyncShaderCompileEnabled();
|
||||
|
||||
// ---- GL_KHR_parallel_shader_compile: glMaxShaderCompilerThreadsKHR(count) ----
|
||||
// The extension defines count == 0 as "no compiler threads": compilation must happen on
|
||||
// the application's thread. That is a mode switch, not a concurrency budget of one, so it
|
||||
// is a latch of its own rather than SetMaxConcurrency(1) - a budget of one would still
|
||||
// move the work off-thread and still report GL_COMPLETION_STATUS_KHR = GL_FALSE, both of
|
||||
// which the extension forbids after a zero count.
|
||||
//
|
||||
// The latch is process-wide, matching the pool it suspends. It is released by the next
|
||||
// nonzero glMaxShaderCompilerThreadsKHR/ARB, which is the only thing that releases it:
|
||||
// no implicit re-arm on eglInitialize, on a context switch or at any join, because an
|
||||
// application that asked for serial compilation gets to keep it until it asks otherwise.
|
||||
void SetAsyncShaderCompileSuspended(Bool suspended);
|
||||
Bool IsAsyncShaderCompileSuspended();
|
||||
|
||||
// What every enqueue site branches on: the configuration flag AND the absence of a
|
||||
// glMaxShaderCompilerThreadsKHR(0). False makes glCompileShader/glLinkProgram run their
|
||||
// bodies inline, exactly as the flag-off path does, which is what makes a subsequent
|
||||
// GL_COMPLETION_STATUS_KHR read immediately GL_TRUE.
|
||||
Bool AsyncShaderCompileActive();
|
||||
|
||||
// min(4, big cores), where a big core is one whose cpufreq ceiling is within 15% of the
|
||||
// machine maximum; the whole CPU count where that sysfs tree is absent. Clamped to [1, 4]
|
||||
// because peak RSS scales as workers x largest glslang arena, and four
|
||||
|
||||
@@ -858,6 +858,9 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
if (std::strcmp(extension, "GL_EXT_disjoint_timer_query") == 0) {
|
||||
caps.SupportsDisjointTimerQuery = true;
|
||||
}
|
||||
if (std::strcmp(extension, "GL_KHR_parallel_shader_compile") == 0) {
|
||||
caps.SupportsParallelShaderCompile = true;
|
||||
}
|
||||
if (std::strcmp(extension, "GL_EXT_blend_func_extended") == 0) {
|
||||
caps.SupportsDualSourceBlend = true;
|
||||
}
|
||||
@@ -1220,6 +1223,8 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
caps.IsAngleLlvmpipeRenderer && MG_Config::Features.AvoidSamplerMipmapMinFilter;
|
||||
MGLOG_I(" GL_EXT_disjoint_timer_query supported: %s",
|
||||
caps.SupportsDisjointTimerQuery ? "true" : "false");
|
||||
MGLOG_I(" GL_KHR_parallel_shader_compile supported: %s",
|
||||
caps.SupportsParallelShaderCompile ? "true" : "false");
|
||||
MGLOG_I(" ANGLE renderer: %s", caps.IsAngleRenderer ? "true" : "false");
|
||||
MGLOG_I(" ANGLE llvmpipe renderer: %s", caps.IsAngleLlvmpipeRenderer ? "true" : "false");
|
||||
MGLOG_I(" Avoid sampler mipmap min filter: %s",
|
||||
|
||||
@@ -1053,6 +1053,16 @@ namespace MobileGL {
|
||||
Bool SupportsBaseInstance = false;
|
||||
// GL_EXT_disjoint_timer_query is present in the extension string.
|
||||
Bool SupportsDisjointTimerQuery = false;
|
||||
// GL_KHR_parallel_shader_compile is present in the HOST driver's extension string,
|
||||
// i.e. the device driver can compile its own (ESSL) shaders on its own threads.
|
||||
//
|
||||
// Purely informational today, and NOT what gates MobileGL's advertisement of the
|
||||
// same extension: MobileGL's parallelism is its own compile pool turning GLSL into
|
||||
// SPIR-V, which is where the shaderpack time goes, and it works on a driver that
|
||||
// has never heard of the extension. This flag becomes load-bearing only if the
|
||||
// driver-side glCompileShader of the translated ESSL is parallelised too, at which
|
||||
// point it decides whether that half can overlap.
|
||||
Bool SupportsParallelShaderCompile = false;
|
||||
// glPolygonModeNV/ANGLE loaded (GL_NV_polygon_mode / GL_ANGLE_polygon_mode). GLES core
|
||||
// has no glPolygonMode, so without this the mode stays FILL.
|
||||
Bool SupportsPolygonMode = false;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
// MG_State code: it runs standalone, before MG_State::Init().
|
||||
#include <MG_State/GLState/VertexArrayState/VertexArrayObject.h>
|
||||
#include <MG_Util/Converters/MGToStr/GLExtensionConverter.h>
|
||||
#include <MG_Util/Async/ShaderCompilePool.h>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
@@ -122,6 +123,37 @@ namespace MobileGL::MG_Util::SelfTest {
|
||||
return result;
|
||||
}
|
||||
|
||||
// ---- Asynchronous shader compilation ------------------------------------
|
||||
// MobileGL's OWN capability row, appended for both backends: nothing about it comes
|
||||
// from the device driver, so it is the same fact on Espryt and on Magma. The POST
|
||||
// rule ("every new capability gets a row") applies to frontend capabilities too -
|
||||
// and this one especially, because it is the capability that changes what
|
||||
// applications DO, not just what they can do: with the extension advertised, Iris
|
||||
// and Sodium batch their pipeline compiles and poll GL_COMPLETION_STATUS_KHR.
|
||||
//
|
||||
// PASS when it is on (the intended configuration once the default flips), INFO when
|
||||
// it is off - "off" is a supported configuration, not a degradation, so it must not
|
||||
// colour the verdict. Either way the row names MOBILEGL_ASYNC_SHADER_COMPILE, so a
|
||||
// user reading a POST page can tell which side of the switch they are on and how to
|
||||
// change it.
|
||||
void AppendAsyncShaderCompileRow(ReportBuilder& builder) {
|
||||
constexpr const char* rowName = "Asynchronous shader compilation";
|
||||
if (!MG_Util::Async::AsyncShaderCompileEnabled()) {
|
||||
builder.Info(rowName,
|
||||
"off; glCompileShader and glLinkProgram run on the calling thread and "
|
||||
"GL_KHR_parallel_shader_compile is not advertised (set environment variable "
|
||||
"MOBILEGL_ASYNC_SHADER_COMPILE=1 to enable it)");
|
||||
return;
|
||||
}
|
||||
const Uint threads = MG_Util::Async::DetectShaderCompileThreadCount();
|
||||
builder.Pass(rowName,
|
||||
format("on with {} compiler thread{}; GL_KHR_parallel_shader_compile is advertised "
|
||||
"and GL_MAX_SHADER_COMPILER_THREADS_KHR = {} (set environment variable "
|
||||
"MOBILEGL_ASYNC_SHADER_COMPILE=0 to disable it, or "
|
||||
"MOBILEGL_ASYNC_SHADER_COMPILE_THREADS=n to change the count)",
|
||||
threads, threads == 1 ? "" : "s", threads));
|
||||
}
|
||||
|
||||
// Appends the four "MobileGL reported ..." rows for one backend section.
|
||||
// GL_VENDOR and GL_VERSION only depend on the backend's static identity, so
|
||||
// they are always concrete; GL_RENDERER and GL_EXTENSIONS need data from the
|
||||
@@ -130,6 +162,9 @@ namespace MobileGL::MG_Util::SelfTest {
|
||||
const Optional<String>& backendApiVersionString,
|
||||
const Optional<String>& advertisedExtensions) {
|
||||
static const String Unavailable = "unavailable (backend probe failed)";
|
||||
// Frontend capability, not a probe result, so it is appended on every path -
|
||||
// including one where the device probe failed outright.
|
||||
AppendAsyncShaderCompileRow(builder);
|
||||
builder.MobileGLReported("MobileGL reported GL_VENDOR", BuildReportedGLVendor(identity));
|
||||
builder.MobileGLReported("MobileGL reported GL_VERSION", BuildReportedGLVersion(identity));
|
||||
builder.MobileGLReported("MobileGL reported GL_RENDERER",
|
||||
@@ -416,6 +451,18 @@ namespace MobileGL::MG_Util::SelfTest {
|
||||
"not supported; 16-bit normalized texture formats need emulation");
|
||||
}
|
||||
|
||||
// INFO, never WARN: this is the HOST driver's ability to compile its own ESSL on
|
||||
// its own threads, and MobileGL's asynchronous compilation does not depend on it
|
||||
// in the slightest - the pool parallelises GLSL -> SPIR-V -> ESSL translation,
|
||||
// which is where a shaderpack load actually spends its time, and it does that on
|
||||
// a driver that has never heard of the extension. The row exists so that the day
|
||||
// the driver-side half is overlapped too, the POST already says which devices can.
|
||||
builder.Info("Driver GL_KHR_parallel_shader_compile",
|
||||
caps.SupportsParallelShaderCompile
|
||||
? "supported; the device driver can also compile the translated ESSL off-thread"
|
||||
: "not supported; the device driver compiles the translated ESSL on the calling "
|
||||
"thread (MobileGL's own compile pool is unaffected)");
|
||||
|
||||
builder.Info("Indirect gl_InstanceID semantics",
|
||||
caps.IndirectDrawInstanceIdIncludesBaseInstance
|
||||
? "includes baseInstance (ANGLE-style; MobileGL's shader rewrite keeps gl_InstanceID "
|
||||
|
||||
Reference in New Issue
Block a user