mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 20:28:32 +09:00
- FramebufferSrgb, DepthClamp and TextureCubeMapSeamless get real storage. All three fell
to SetCapability's "not supported currently" arm and IsCapabilityEnabled's default:
glEnable was swallowed and glIsEnabled lied, so DirectGLES' sRGB block and the
DirectVulkan read points consumed a constant. The three Bools land in the three
alignment bytes at [581, 584) between ColorMasks and ClearColor, so
sizeof(RenderStateParameters) stays 1168 and NO existing offset moves - Espryt's
kBlendSpanBegin/kBlendSpanEnd (312/536) and the whole chunk table depend on that.
- MGPipeRenderStateSpans.{h,cpp}: the pipeline/dynamic split, written in exactly one place.
The rule is the only rule - a byte is pipeline state iff a public RenderState setter that
calls BumpVersions() writes it - which makes G7's "the subset hash moves iff
m_pipelineStateVersion moves" true by construction. 16 boundaries, all offsetof or
sizeof, alternating dynamic/pipeline: 8 dynamic chunks / 772 bytes and 7 pipeline chunks
/ 396 bytes, partitioning [0, 1168) exactly, asserted at compile time.
MGPipeComputePipelineSubsetHash is XXH64 over the seven pipeline chunks, seeded with a
table version so a chunk-table change invalidates every persisted key.
- The pipeline subset is now a strict SUPERSET of the 24 members ComputePipelineStateHash
hashed: 44 members, adding sample coverage, the front face, the provoking vertex, the
scissor-test mask, the back polygon mode, eleven capability bools the hash never read and
the three above. Demoting those setters to ++m_version instead would have changed
MG_State semantics in the PULL build for the push path's sake. The hash runs only when
m_pipelineStateVersion moves, which is exactly when Magma re-hashed before.
- PipeApply.{h,cpp}: the in-process applier, the server half of the P2 calls. The server's
working RenderStateParameters IS PipeInputs::m_renderState, which is why DirectGLES'
SyncRenderState is not one line changed and why the verify comparator stops being a
tautology. Per-context CSO store indexed by slot, gen-validated; the residual block's
capability bits are compared against the assembled block, so a capability a later call
takes over and forgets to carry is Fatal{PipeResidualDiverged}.
MGPipeDeriveRenderStateFields is a declared STUB - its 29 derivations are commit c1.
- SlotAllocator.{h,cpp}: the client's per-kind {slot, gen} allocator, free list plus
high-water, first allocatable slot 1, gen bumping only on slot REUSE, a debug assert on
gen wrap, the composite ShaderCso band held back, and a lifetimeId -> slot map per kind so
a GL name never enters a key. In the contract because both Track H slices need it.
- ResidualValueBlock 1248 -> 8 bytes, one Uint64 of capability bits.
RenderStateParameters retired to create/bind_render_state and set_dynamic_state, Pack to
set_pixel_pack_state, the patch quintet to set_patch_state. gen_pipe.py now emits the
member-by-member offsetof assertions the ratchet comment always promised.
- gen_pipe.py: PIPELINE_STATE_MEMBERS grows to the 44-member set in declaration order and
PipeSpanTable.inc's "deliberately absent" block records the answers instead of the
questions; Coverage.def gains MGP_COVERAGE_EMITTED_LIST (34 rows) and PipeFilled.inc
gains kMGPipeFieldEmittedBy[], which is what lets the residual fill loop skip a field a
P2 call now supplies. One more --self-test negative control covers the new list.
- MOBILEGL_PIPE_PUSH becomes a per-subsystem bitmask with named bits (0..6 migrated at P2,
bit 63 the CSO-content-addressing negative control), defaulting to 0x7f in a push build
and staying 0 in a pull build. New CMake option MOBILEGL_PIPE_LEGACY_MEMOS, ON, forced ON
when MOBILEGL_PIPE_PUSH=OFF where it is the only arm. New Features.PipeHandleAbaControl
under MOBILEGL_PIPE_PUSH, negative control C for HandleRecycleScenario.
- PipeStats gains CallClass::{RenderStateCsoMints, RenderStateCsoBinds} (csom / csob on the
summary line), and they are PUSH-ONLY: growing the enum in the pull build would resize
the counter arrays, the name table and FormatWindowLine for two counters that could never
leave zero, and G1 admits no such resize.
- Four MG_Test/Pipe stubs plus their CMake registration, so the packages that own their
contents never touch MG_Test/Pipe/CMakeLists.txt.
G1, pull build, symbol_report --threshold 0: 0 added, 0 removed, 0 renamed, 4 resized, and
every resize is attributed:
RenderState::RenderState() 1700 -> 1848 (+148) the three {}
RenderState::SetCapability(CapabilityInput,bool) 850 -> 927 (+77) three switch arms
RenderState::IsCapabilityEnabled(CapabilityInput) 239 -> 268 (+29) three switch arms
_GLOBAL__sub_I_DirectGLES.cpp 1340 -> 1331 (-9) the static
initialiser of DirectGLES.cpp's `static RenderStateParameters
g_syncedRenderStateParameters` re-scheduling around the three new default-initialised
members. A shrink, and the only unforeseen entry; it is a direct consequence of the
struct gaining members and touches no interface.
197 lines
10 KiB
C++
Executable File
197 lines
10 KiB
C++
Executable File
// MobileGL - MobileGL/MG_Pipe/MGPipeRenderStateSpans.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
|
|
|
|
// The definitions behind MGPipeRenderStateSpans.h and behind the two arrays
|
|
// generated/PipeSpanTable.inc has declared since P0. Compiled ONLY under
|
|
// MOBILEGL_PIPE_PUSH (CMakeLists.txt appends it to SOURCE_FILES there), which is how the
|
|
// pull build gains no symbol from the split - a declaration emits nothing.
|
|
//
|
|
// PROVENANCE OF THE PIPELINE HALF. It began as the enumeration
|
|
// VulkanRenderer::ComputePipelineStateHash carried above itself, which was the contract
|
|
// that function had without being able to say so; it moves here because this file is now
|
|
// that contract. Verbatim, from VulkanRenderer.cpp at feat/disaggregated@48268068:
|
|
//
|
|
// Value hash over every fixed-function GL state the pipeline payload reads that
|
|
// the memo key's other fields (mode, program hash, vertex-input hash, render-pass
|
|
// hash, transform flags) do not already pin down. Enumerated against the payload
|
|
// build in GetOrCreatePipeline - any new GL-state read there must be added here:
|
|
// - capability bits: CullFace, DepthTest, PolygonOffsetFill (mode gating rides
|
|
// the memo's mode key), RasterizerDiscard, ColorLogicOp, StencilTest,
|
|
// PrimitiveRestart(+FixedIndex), SampleShading, SampleMask, plus the depth write mask
|
|
// - patch vertices, polygon mode, cull face mode, depth func, logic op,
|
|
// min sample shading, the glSampleMaski word
|
|
// - front/back stencil ops + compare funcs (ref/mask are dynamic state)
|
|
// - per draw buffer up to the render pass's colour span: indexed blend enable,
|
|
// blend factors/equations, indexed colour write mask (broadcast from index 0
|
|
// when the device lacks independentBlend - the same read the payload does)
|
|
// FBO-derived payload inputs (attachment presence/formats/draw-buffer gating) are
|
|
// pinned by the render-pass hash key, exactly as the version-keyed memo relied on.
|
|
//
|
|
// P2's pipeline half is a strict SUPERSET of that list. It adds SampleCoverageValue,
|
|
// SampleCoverageInvert, FrontFaceModeSetting, ProvokingVertexModeSetting,
|
|
// ScissorTestEnabledMask, PolygonModeBack, the eleven capability bools the hash never read
|
|
// (DebugOutput, DebugOutputSynchronous, Dither, LineSmooth, PolygonOffsetLine,
|
|
// PolygonOffsetPoint, PolygonSmooth, SampleAlphaToCoverage, SampleAlphaToOne, SampleCoverage,
|
|
// ProgramPointSize) and the three capabilities P2 gave storage to (FramebufferSrgb,
|
|
// DepthClamp, TextureCubeMapSeamless). All of them are written by a setter that calls
|
|
// BumpVersions(), so under the header's rule they are pipeline. The alternative - demoting
|
|
// those setters to ++m_version - would change MG_State semantics in the PULL build for the
|
|
// sake of the push path. Growing the subset costs nothing measurable: the hash runs only
|
|
// when m_pipelineStateVersion moves, which is exactly when Magma recomputed
|
|
// ComputePipelineStateHash before.
|
|
//
|
|
// The render-pass facts are deliberately NOT here. ComputePipelineStateHash's signature is
|
|
// (colorAttachmentCount, rasterizationSamples) and it folds ResolveEffectiveSampleMask, so
|
|
// it was never a pure function of RenderStateParameters; a CSO handle cannot replace it on
|
|
// its own and Magma keeps renderPassHash as a separate memo-key component.
|
|
#include <MG_Pipe/MGPipe.h>
|
|
#include <MG_Pipe/MGPipeRenderStateSpans.h>
|
|
|
|
#include <cstring>
|
|
|
|
namespace MobileGL::MG_Pipe {
|
|
namespace {
|
|
// Half-local chunk index -> global chunk index. The halves alternate, so this is
|
|
// arithmetic rather than a table.
|
|
constexpr SizeT GlobalPipelineChunk(SizeT halfIndex) { return halfIndex * 2 + 1; }
|
|
constexpr SizeT GlobalDynamicChunk(SizeT halfIndex) { return halfIndex * 2; }
|
|
|
|
const Uint8* BytesOf(const RenderStateParameters& params) {
|
|
return reinterpret_cast<const Uint8*>(¶ms);
|
|
}
|
|
Uint8* BytesOf(RenderStateParameters& params) { return reinterpret_cast<Uint8*>(¶ms); }
|
|
|
|
SizeT BlobBytes(Uint32 chunkMask, SizeT halfCount, SizeT (*toGlobal)(SizeT)) {
|
|
SizeT total = 0;
|
|
for (SizeT i = 0; i < halfCount; ++i) {
|
|
if ((chunkMask & (1u << i)) == 0) continue;
|
|
total += MGPipeRenderStateChunkAt(toGlobal(i)).Length;
|
|
}
|
|
return total;
|
|
}
|
|
|
|
void Gather(const RenderStateParameters& params, Uint32 chunkMask, void* dst, SizeT halfCount,
|
|
SizeT (*toGlobal)(SizeT)) {
|
|
Uint8* out = static_cast<Uint8*>(dst);
|
|
const Uint8* src = BytesOf(params);
|
|
for (SizeT i = 0; i < halfCount; ++i) {
|
|
if ((chunkMask & (1u << i)) == 0) continue;
|
|
const MGPStateChunk chunk = MGPipeRenderStateChunkAt(toGlobal(i));
|
|
std::memcpy(out, src + chunk.Offset, chunk.Length);
|
|
out += chunk.Length;
|
|
}
|
|
}
|
|
|
|
void Scatter(const void* src, Uint32 chunkMask, RenderStateParameters& dst, SizeT halfCount,
|
|
SizeT (*toGlobal)(SizeT)) {
|
|
const Uint8* in = static_cast<const Uint8*>(src);
|
|
Uint8* out = BytesOf(dst);
|
|
for (SizeT i = 0; i < halfCount; ++i) {
|
|
if ((chunkMask & (1u << i)) == 0) continue;
|
|
const MGPStateChunk chunk = MGPipeRenderStateChunkAt(toGlobal(i));
|
|
std::memcpy(out + chunk.Offset, in, chunk.Length);
|
|
in += chunk.Length;
|
|
}
|
|
}
|
|
|
|
Uint32 ChunksThatMoved(const RenderStateParameters& a, const RenderStateParameters& b,
|
|
SizeT halfCount, SizeT (*toGlobal)(SizeT)) {
|
|
const Uint8* left = BytesOf(a);
|
|
const Uint8* right = BytesOf(b);
|
|
Uint32 mask = 0;
|
|
for (SizeT i = 0; i < halfCount; ++i) {
|
|
const MGPStateChunk chunk = MGPipeRenderStateChunkAt(toGlobal(i));
|
|
if (std::memcmp(left + chunk.Offset, right + chunk.Offset, chunk.Length) != 0) {
|
|
mask |= 1u << i;
|
|
}
|
|
}
|
|
return mask;
|
|
}
|
|
|
|
constexpr Uint32 AllChunks(SizeT halfCount) {
|
|
return halfCount >= 32 ? ~Uint32{0} : static_cast<Uint32>((Uint64{1} << halfCount) - 1);
|
|
}
|
|
} // namespace
|
|
|
|
// The two arrays generated/PipeSpanTable.inc declares. Every entry is
|
|
// MGPipeRenderStateChunkAt(), so a boundary can only be written once.
|
|
const MGPStateChunk kMGPipePipelineChunks[kMGPipePipelineChunkCount] = {
|
|
MGPipeRenderStateChunkAt(GlobalPipelineChunk(0)), MGPipeRenderStateChunkAt(GlobalPipelineChunk(1)),
|
|
MGPipeRenderStateChunkAt(GlobalPipelineChunk(2)), MGPipeRenderStateChunkAt(GlobalPipelineChunk(3)),
|
|
MGPipeRenderStateChunkAt(GlobalPipelineChunk(4)), MGPipeRenderStateChunkAt(GlobalPipelineChunk(5)),
|
|
MGPipeRenderStateChunkAt(GlobalPipelineChunk(6)),
|
|
};
|
|
static_assert(sizeof(kMGPipePipelineChunks) / sizeof(kMGPipePipelineChunks[0]) == kMGPipePipelineChunkCount,
|
|
"kMGPipePipelineChunks lost an entry");
|
|
|
|
const MGPStateChunk kMGPipeDynamicChunks[kMGPipeDynamicChunkCount] = {
|
|
MGPipeRenderStateChunkAt(GlobalDynamicChunk(0)), MGPipeRenderStateChunkAt(GlobalDynamicChunk(1)),
|
|
MGPipeRenderStateChunkAt(GlobalDynamicChunk(2)), MGPipeRenderStateChunkAt(GlobalDynamicChunk(3)),
|
|
MGPipeRenderStateChunkAt(GlobalDynamicChunk(4)), MGPipeRenderStateChunkAt(GlobalDynamicChunk(5)),
|
|
MGPipeRenderStateChunkAt(GlobalDynamicChunk(6)), MGPipeRenderStateChunkAt(GlobalDynamicChunk(7)),
|
|
};
|
|
static_assert(sizeof(kMGPipeDynamicChunks) / sizeof(kMGPipeDynamicChunks[0]) == kMGPipeDynamicChunkCount,
|
|
"kMGPipeDynamicChunks lost an entry");
|
|
|
|
void MGPipeGatherPipelineBytes(const RenderStateParameters& params, void* dst) {
|
|
Gather(params, AllChunks(kMGPipePipelineChunkCount), dst, kMGPipePipelineChunkCount,
|
|
GlobalPipelineChunk);
|
|
}
|
|
|
|
void MGPipeScatterPipelineBytes(const void* src, RenderStateParameters& dst) {
|
|
Scatter(src, AllChunks(kMGPipePipelineChunkCount), dst, kMGPipePipelineChunkCount,
|
|
GlobalPipelineChunk);
|
|
}
|
|
|
|
SizeT MGPipePipelineChunkBlobBytes(Uint32 chunkMask) {
|
|
return BlobBytes(chunkMask, kMGPipePipelineChunkCount, GlobalPipelineChunk);
|
|
}
|
|
|
|
void MGPipeGatherPipelineChunks(const RenderStateParameters& params, Uint32 chunkMask, void* dst) {
|
|
Gather(params, chunkMask, dst, kMGPipePipelineChunkCount, GlobalPipelineChunk);
|
|
}
|
|
|
|
void MGPipeScatterPipelineChunks(const void* src, Uint32 chunkMask, RenderStateParameters& dst) {
|
|
Scatter(src, chunkMask, dst, kMGPipePipelineChunkCount, GlobalPipelineChunk);
|
|
}
|
|
|
|
SizeT MGPipeDynamicChunkBlobBytes(Uint32 chunkMask) {
|
|
return BlobBytes(chunkMask, kMGPipeDynamicChunkCount, GlobalDynamicChunk);
|
|
}
|
|
|
|
void MGPipeGatherDynamicChunks(const RenderStateParameters& params, Uint32 chunkMask, void* dst) {
|
|
Gather(params, chunkMask, dst, kMGPipeDynamicChunkCount, GlobalDynamicChunk);
|
|
}
|
|
|
|
void MGPipeScatterDynamicChunks(const void* src, Uint32 chunkMask, RenderStateParameters& dst) {
|
|
Scatter(src, chunkMask, dst, kMGPipeDynamicChunkCount, GlobalDynamicChunk);
|
|
}
|
|
|
|
Uint32 MGPipeDynamicChunksThatMoved(const RenderStateParameters& a, const RenderStateParameters& b) {
|
|
return ChunksThatMoved(a, b, kMGPipeDynamicChunkCount, GlobalDynamicChunk);
|
|
}
|
|
|
|
Uint32 MGPipePipelineChunksThatMoved(const RenderStateParameters& a, const RenderStateParameters& b) {
|
|
return ChunksThatMoved(a, b, kMGPipePipelineChunkCount, GlobalPipelineChunk);
|
|
}
|
|
|
|
Uint64 MGPipeHashPipelineBytes(const void* bytes) {
|
|
return static_cast<Uint64>(
|
|
XXH64(bytes, kMGPipePipelineChunkBytes, kMGPipeRenderStateChunkTableVersion));
|
|
}
|
|
|
|
Uint64 MGPipeComputePipelineSubsetHash(const RenderStateParameters& params) {
|
|
// 396 bytes on the stack. A streaming XXH64_state_t would allocate; gathering first
|
|
// is also what CsoCache wants, because the same bytes are what a hash hit memcmps
|
|
// against before the handle is reused.
|
|
Uint8 gathered[kMGPipePipelineChunkBytes];
|
|
MGPipeGatherPipelineBytes(params, gathered);
|
|
return MGPipeHashPipelineBytes(gathered);
|
|
}
|
|
} // namespace MobileGL::MG_Pipe
|