mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-14 21:43:24 +09:00
- A mode change on its own, so it does not ride inside a code commit. The other three of the four files created with 0755 were already corrected; this is the last one.
197 lines
10 KiB
C++
197 lines
10 KiB
C++
// 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, kMGPipeRenderStateChunkTableSeed));
|
|
}
|
|
|
|
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
|