mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 04:08:32 +09:00
[Perf] (MG_State): adopt in-flight compile jobs across shader objects (P1 stage 6)
~21% of a shaderpack's glCompileShader calls hand different shader objects byte-identical source; the P0b cache only helps after one finishes, so under async two workers would run the whole pipeline twice. Now the GL thread consults a per-context (stage, hash, length, envFingerprint) -> weak-node map at enqueue and ADOPTS the in-flight (or completed) node instead of posting a duplicate - a hit is honored only after a full byte comparison (the hash never decides), a cancel-requested or settled-cancelled node is never adopted, and no worker ever waits. Sharing a node makes the unconditional cancel wrong, so release is now adopter-counted: a plain GL-thread Int (every mutation site is a GL entry point; the single-threadedness argument and the terminal-early-out that keeps the count exact are in the header), and the cancel fires only at count zero AND with no pending link pinning the node (the stage-4 MarkLinkReferenced precedence). Adoption also re-points the object's source at the node's snapshot so the layer-1 memo's pointer compare stays armed - without that, an adopter's next glCompileShader would re-enqueue the very duplicate this stage removes. Both guards are negative-control-proven: each removed guard fails exactly its own tests. Count discipline was proven with a temporary hard-abort on underflow/leak across the full suite and retrace corpus - zero hits. 18 new tests (13 GL-surface incl. shared-node re-source/delete/orphan-sweep isolation, shared failure logs, 48-over-6 stress with a deterministic adoption count, flag-off and KHR-suspended zero-adoption guards; 5 direct map cases incl. fingerprint mismatch and cancelled/expired pruning). Gates: 538/538 unit both flag states, async suites x5 no flakes, NVIDIA DirectGLES retrace identical sets both states. Timing: 2-worker (Android-shaped) 1-3% faster consistently on complementary and BSL; 4-worker unchanged - the win this stage exists for lands where CPU is scarce.
This commit is contained in:
@@ -20,6 +20,7 @@
|
||||
#include "MG_Impl/GLImpl/Program/GL_Program.h"
|
||||
#include "MG_State/GLState/Core.h"
|
||||
#include "MG_State/GLState/ProgramState/ShaderPreprocessCache.h"
|
||||
#include "MG_Util/Async/ShaderCompilePool.h"
|
||||
#include "MG_Util/ShaderTranspiler/ShaderCompiler.h"
|
||||
|
||||
using namespace MobileGL;
|
||||
@@ -3006,8 +3007,19 @@ TEST_F(ProgramTest, TwoShaderObjectsWithIdenticalSourceLinkIndependently) {
|
||||
ASSERT_NE(objectA, nullptr);
|
||||
ASSERT_NE(objectB, nullptr);
|
||||
EXPECT_EQ(objectA->GetShaderSource(), objectB->GetShaderSource());
|
||||
// Independent parses despite the shared preprocess.
|
||||
EXPECT_NE(objectA->GetCompiledShader(), objectB->GetCompiledShader());
|
||||
// P0b's layer 2 shares the PREPROCESS and never the parse: glslang's TShader is
|
||||
// consume-once, so a memo hit still has to parse for itself.
|
||||
//
|
||||
// P1 stage 6 shares something stronger when it is active - the whole compile JOB, and
|
||||
// therefore the single parse that job produced - and that sharing is made safe by
|
||||
// ShaderCompileTask::ClaimParsedShader's CAS instead, exactly as it already was for one
|
||||
// shader object attached to two programs. ShaderCompileAdoptionTest is where that is
|
||||
// pinned down (it links both objects and compares the generated SPIR-V). So the
|
||||
// one-parse-per-object assertion belongs to the non-adopting path; the two independent
|
||||
// LINKS below are what both modes have to agree on, and they are the point of this case.
|
||||
if (!MG_Util::Async::AsyncShaderCompileActive()) {
|
||||
EXPECT_NE(objectA->GetCompiledShader(), objectB->GetCompiledShader());
|
||||
}
|
||||
EXPECT_NE(objectA->GetCompiledShader(), nullptr);
|
||||
EXPECT_NE(objectB->GetCompiledShader(), nullptr);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user