mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-10 21:28:32 +09:00
[Refactor] (MG_State, MG_Util): join-by-construction link/compile artifacts (P1 stage 2)
Still fully synchronous - EnsureLinkJoined()/EnsureCompileJoined() are empty
inline no-ops (verified to fold away at every one of the ~1200 call sites;
this project builds without LTO) - but every read of link- or compile-produced
state now goes through a private accessor the compiler enforces, so when
stage 4 moves the bodies onto pool workers, 'which reads must join' is a
type-system fact instead of a 400-line audit.
- ProgramObject: the 31 fields ResetLinkArtifacts clears plus the 5 link
outputs it forgot (infoLog, linkedFragData{Location,Index}, the geometry
strip-capture pair) move into a nested LinkArtifacts behind Artifacts().
ResetLinkArtifacts is now a worker-safe pure clear; the link-observable
version bumps (backendState/link/uboContent) move to a GL-thread-only
BumpLinkObservableVersions() called once from Link()'s prologue and from
glProgramBinary's mandated failure - the link body never writes them, so
a stage-4 worker cannot lose an invalidation against the draw path.
- ShaderObject: compile artifacts (TShader, preprocessed source, side-channel
maps, status/log, consume-once flag) behind Compiled(); the P0b layer-1
memo trio deliberately stays outside as the future non-joining
COMPLETION_STATUS_KHR fast path.
- CompileEnv (new): a GL-thread snapshot of everything the compile pipeline
used to read live from the backend mid-parse - compute limits (the
GetIntegeri_v reach-back is gone from the worker path), advertised
extensions, device quirks, TBuiltInResource inputs. Captured lazily per
backend activation; the consume-once re-parse now runs against the same
env as the original parse.
- The GL-thread prologue / worker-body boundary is marked in Link() where
the stage sort ends; everything below is a pure function of the snapshot.
Public getter signatures unchanged - MG_Impl and both backends compile
untouched. Unit 476/476, Program suites 117/117, DirectGLES retrace 38/39 on
llvmpipe (the one failure is the known pre-existing non-CI iterationrp case;
the NVIDIA userspace driver was updated out from under the running kernel
module mid-session, so GLX there is down until a reboot).
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
// MobileGL - MobileGL/MG_Util/ShaderTranspiler/CompileEnv.cpp
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#include "CompileEnv.h"
|
||||
#include <MG_Backend/BackendObjects.h>
|
||||
#include <MG_State/GLState/Core.h>
|
||||
|
||||
namespace MobileGL::MG_Util::ShaderTranspiler {
|
||||
namespace {
|
||||
void HashBytes(Uint64& state, const void* data, const SizeT length) {
|
||||
state = static_cast<Uint64>(XXH64(data, length, state));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void HashValue(Uint64& state, const T& value) {
|
||||
static_assert(std::is_trivially_copyable_v<T>);
|
||||
HashBytes(state, &value, sizeof(T));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Uint64 ComputeCompileEnvFingerprint(const CompileEnv& env) {
|
||||
Uint64 state = 0x9e3779b97f4a7c15ull;
|
||||
HashValue(state, env.maxComputeWorkGroupSize[0]);
|
||||
HashValue(state, env.maxComputeWorkGroupSize[1]);
|
||||
HashValue(state, env.maxComputeWorkGroupSize[2]);
|
||||
HashValue(state, env.maxComputeWorkGroupInvocations);
|
||||
HashValue(state, env.backend);
|
||||
// DynamicBackendParameters is a plain aggregate of scalars; hashing its object
|
||||
// representation is deliberate - it means a new limit cannot be added without also
|
||||
// changing the fingerprint, which is exactly the memo-hazard property wanted here.
|
||||
HashBytes(state, &env.params, sizeof(env.params));
|
||||
if (!env.advertisedExtensions.empty()) {
|
||||
HashBytes(state, env.advertisedExtensions.data(),
|
||||
env.advertisedExtensions.size() * sizeof(GLExtension));
|
||||
}
|
||||
HashValue(state, env.subgroupPrefixScanQuirk);
|
||||
return state;
|
||||
}
|
||||
|
||||
SharedPtr<const CompileEnv> CaptureCompileEnv() {
|
||||
auto env = MakeShared<CompileEnv>();
|
||||
|
||||
const auto& activeBackend = MG_Backend::pActiveBackendObject;
|
||||
if (activeBackend) {
|
||||
env->backend = activeBackend->GetBackendType();
|
||||
env->params = activeBackend->GetDynamicParameters();
|
||||
env->advertisedExtensions = activeBackend->GetRendererInfo().RendererGLInfo.Extensions;
|
||||
}
|
||||
|
||||
// GL_MAX_COMPUTE_WORK_GROUP_SIZE. This is a REAL driver call on DirectGLES; it must
|
||||
// happen here, on the context thread, and exactly once per context. The frontend
|
||||
// minimum is the floor, matching what GL_Getter reports.
|
||||
// TODO: Share these exposed compute limit helpers with GL_Getter.cpp instead of duplicating the frontend minima.
|
||||
constexpr Uint kFrontendMinComputeWorkGroupSizes[3] = {1024, 1024, 64};
|
||||
for (Uint index = 0; index < 3; ++index) {
|
||||
Int backendValue = 0;
|
||||
if (MG_Backend::gBackendFunctionsTable.GL.GetIntegeri_v) {
|
||||
MG_Backend::gBackendFunctionsTable.GL.GetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_SIZE, index,
|
||||
&backendValue);
|
||||
}
|
||||
env->maxComputeWorkGroupSize[index] =
|
||||
std::max(static_cast<Uint>(std::max(backendValue, 0)), kFrontendMinComputeWorkGroupSizes[index]);
|
||||
}
|
||||
|
||||
constexpr Uint64 kFrontendMaxComputeWorkGroupInvocations = 1024;
|
||||
env->maxComputeWorkGroupInvocations =
|
||||
activeBackend ? std::max(static_cast<Uint64>(std::max(env->params.MaxComputeWorkGroupInvocations, 0)),
|
||||
kFrontendMaxComputeWorkGroupInvocations)
|
||||
: kFrontendMaxComputeWorkGroupInvocations;
|
||||
|
||||
env->subgroupPrefixScanQuirk = MG_Config::Features.SubgroupPrefixScanQuirk;
|
||||
|
||||
env->fingerprint = ComputeCompileEnvFingerprint(*env);
|
||||
return env;
|
||||
}
|
||||
|
||||
const SharedPtr<const CompileEnv>& GetDefaultCompileEnv() {
|
||||
// Function-local static, not a namespace-scope one: the fingerprint has to be
|
||||
// computed, and this must not run before MG_Config is loaded.
|
||||
static const SharedPtr<const CompileEnv> kDefault = [] {
|
||||
auto env = MakeShared<CompileEnv>();
|
||||
env->subgroupPrefixScanQuirk = MG_Config::Features.SubgroupPrefixScanQuirk;
|
||||
env->fingerprint = ComputeCompileEnvFingerprint(*env);
|
||||
return SharedPtr<const CompileEnv>(Move(env));
|
||||
}();
|
||||
return kDefault;
|
||||
}
|
||||
|
||||
const SharedPtr<const CompileEnv>& GetCurrentCompileEnv() {
|
||||
if (MG_State::pGLContext) return MG_State::pGLContext->GetCompileEnv();
|
||||
return GetDefaultCompileEnv();
|
||||
}
|
||||
} // namespace MobileGL::MG_Util::ShaderTranspiler
|
||||
Reference in New Issue
Block a user