[Feat, Test] (MG_Impl, MG_State, MG_Util): opt-in MOBILEGL_ASYNC_OPTIMISTIC_SHADER_STATUS - shader compile status/log answer without joining, latched per compile

This commit is contained in:
2026-08-10 02:06:17 -04:00
parent d8d7530011
commit 6ea948779e
11 changed files with 898 additions and 2 deletions
@@ -408,6 +408,13 @@ namespace MobileGL::MG_State::GLState {
MG_Util::ConvertGLEnumToString(shaderType).c_str());
if (!compiled.compileStatus) {
// The compile log LEADS the quoted source, and that order is load-bearing:
// under MOBILEGL_ASYNC_OPTIMISTIC_SHADER_STATUS this string is the
// application's ONLY compile diagnostic (the per-shader queries answered
// optimistically), and applications read it through a bounded buffer -
// Iris uses 32768 bytes - so the actionable text must come before the
// potentially-100KB source dump. The full source stays: the device log is
// where a failing pack gets debugged from.
artifacts.infoLog =
std::format("Linking a {} with compilation error, linking will now terminate. Shader error "
"log:\n{}\nShader src:\n{}",
@@ -88,12 +88,19 @@ namespace MobileGL::MG_State::GLState {
// another object, THIS object has not pulled its result yet. (An adopted node may
// already be terminal - the join then only replays what is left of its diagnostics.)
m_compileJoined = false;
// A new compile is a new story: whatever the optimistic getters promised about the
// previous node does not carry over.
m_optimisticAnswerLatched = false;
}
void ShaderObject::DropCompileNode() const {
if (!m_compiled) return;
m_compiled->ReleaseAdopter();
m_compiled.reset();
// No node means IsCompileComplete() is trivially true and the truthful answers are
// "not compiled"; a stale latch would keep reporting a compile that no longer
// exists as GL_TRUE.
m_optimisticAnswerLatched = false;
}
void ShaderObject::InvalidateCompiledState() {
@@ -116,8 +116,10 @@ namespace MobileGL {
Bool GetDeleteStatus() const { return m_deleteStatus; }
// Blocks until a pending compile has published its artifacts. Public for the
// sites that must join without reading anything - ProgramObject::Link's
// prologue, which needs every attached shader settled before it runs.
// sites that must join without reading anything - ProgramState::
// JoinAllPendingWork, the glMaxShaderCompilerThreadsKHR(0) path that settles
// every outstanding job. glLinkProgram deliberately does NOT come through
// here: its prologue takes the nodes unjoined via CompiledNodeForLink().
void JoinCompile() const { EnsureCompileJoined(); }
// True while this object holds the outcome (success OR failure) of a Compile()
@@ -141,6 +143,23 @@ namespace MobileGL {
// outstanding to wait for.
Bool IsCompileComplete() const { return m_compiled == nullptr || m_compiled->IsTerminal(); }
// MOBILEGL_ASYNC_OPTIMISTIC_SHADER_STATUS's one-story-per-compile memory. The
// three optimistic getter sites in GL_Program ask THIS instead of a raw
// IsCompileComplete() peek, and the difference is the latch: without it, a job
// that settles between two adjacent queries hands the application a torn pair -
// an empty info log from the optimistic read, then the real GL_FALSE from the
// truthful one - and an application that aborts on that status never reaches
// the link join that quotes the real log. So the first optimistic answer
// latches: until the next AdoptCompileNode/DropCompileNode this object keeps
// answering optimistically even after the job settles, and a real failure
// surfaces exactly once, at the link. Returns whether the caller should answer
// optimistically; the caller has already checked the quirk is active.
Bool TakeOptimisticCompileAnswer() const {
if (!m_optimisticAnswerLatched && IsCompileComplete()) return false;
m_optimisticAnswerLatched = true;
return true;
}
private:
// ---- The one and only join gate for compile output (P1 invariant I5) ----
// The fast path - no job, or a job whose result this object has already pulled -
@@ -231,6 +250,10 @@ namespace MobileGL {
// Exactly-once latch for the pull above. Armed with every new job node, set by
// the one join that consumes it.
mutable Bool m_compileJoined = false;
// TakeOptimisticCompileAnswer's memory: this object has answered a compile
// query optimistically for the current node. Cleared wherever the node
// changes hands (AdoptCompileNode) or goes away (DropCompileNode).
mutable Bool m_optimisticAnswerLatched = false;
};
} // namespace MG_State::GLState
} // namespace MobileGL