mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-07 19:58:32 +09:00
[Feat] (Program): add the reflection-archive field tables and sizeof trip wires to ProgramArtifacts.h
- One VisitFields table per type (ARCHITECTURE.md:259), a free constrained template so the moved struct bodies stay verbatim and one table serves both the const (serialize) and non-const (deserialize) direction. LinkArtifacts::program is deliberately absent: it is null for every archived instance and must never be serialized. - Trip wires: TypeFacts is pinned at 44 bytes on every ABI; the container-bearing four are pinned per standard library - libstdc++ 64-bit here (128/128/1056/88, measured on this build), the libc++ branch left inert for the integrator to pin from the NDK build, MSVC unasserted. A member added without a table entry changes the size and the assertion message sends the author to the table. - ProgramArtifactsTest counts the tables (20/14/11/57/8; const and non-const walks agree, names distinct), proves constness passes through, and records every sizeof as a ctest property so a new toolchain's numbers are readable from any `ctest -V` log.
This commit is contained in:
@@ -397,4 +397,180 @@ namespace MobileGL::MG_State::GLState {
|
||||
// so every query keeps the truthful GL spelling.
|
||||
Bool pointSizeDemoted = false;
|
||||
};
|
||||
|
||||
// ---- the archive field tables (ARCHITECTURE.md:259): ONE table per type, serving both directions ----
|
||||
//
|
||||
// Visitor contract: v(const char* name, Field&) - Field is const when Self is const, so a
|
||||
// single table serves the serializer (const) and the deserializer (non-const); `Self`
|
||||
// deduces either. A visitor recurses into TypeFacts / ResourceReflection / XfbVarying by
|
||||
// calling VisitFields on the element it was handed; the tables never recurse themselves.
|
||||
//
|
||||
// Free constrained templates rather than members so the struct bodies above stay a verbatim
|
||||
// move. The sizeof trip wires below are what keep these tables honest: a member added to a
|
||||
// struct changes its size, trips the assertion, and the message sends the author here.
|
||||
template <class Self, class V>
|
||||
requires std::same_as<std::remove_const_t<Self>, TypeFacts>
|
||||
void VisitFields(Self& a, V&& v) {
|
||||
v("isArray", a.isArray);
|
||||
v("isSizedArray", a.isSizedArray);
|
||||
v("isMatrix", a.isMatrix);
|
||||
v("isVector", a.isVector);
|
||||
v("isOpaque", a.isOpaque);
|
||||
v("isTexture", a.isTexture);
|
||||
v("isImage", a.isImage);
|
||||
v("isDouble", a.isDouble);
|
||||
v("isVoid", a.isVoid);
|
||||
v("isBuffer", a.isBuffer);
|
||||
v("isPatch", a.isPatch);
|
||||
v("hasIndex", a.hasIndex);
|
||||
v("hasFormat", a.hasFormat);
|
||||
v("vectorSize", a.vectorSize);
|
||||
v("matrixCols", a.matrixCols);
|
||||
v("matrixRows", a.matrixRows);
|
||||
v("layoutIndex", a.layoutIndex);
|
||||
v("layoutFormat", a.layoutFormat);
|
||||
v("layoutMatrix", a.layoutMatrix);
|
||||
v("basicType", a.basicType);
|
||||
} // 20 fields
|
||||
|
||||
template <class Self, class V>
|
||||
requires std::same_as<std::remove_const_t<Self>, ResourceReflection>
|
||||
void VisitFields(Self& a, V&& v) {
|
||||
v("name", a.name);
|
||||
v("glDefineType", a.glDefineType);
|
||||
v("offset", a.offset);
|
||||
v("size", a.size);
|
||||
v("index", a.index);
|
||||
v("counterIndex", a.counterIndex);
|
||||
v("arrayStride", a.arrayStride);
|
||||
v("topLevelArraySize", a.topLevelArraySize);
|
||||
v("topLevelArrayStride", a.topLevelArrayStride);
|
||||
v("binding", a.binding);
|
||||
v("location", a.location);
|
||||
v("stages", a.stages);
|
||||
v("arraySize", a.arraySize);
|
||||
v("type", a.type); // visited as a value; the visitor recurses with VisitFields(a.type, v) if it wants to
|
||||
} // 14 fields
|
||||
|
||||
template <class Self, class V>
|
||||
requires std::same_as<std::remove_const_t<Self>, XfbVarying>
|
||||
void VisitFields(Self& a, V&& v) {
|
||||
v("name", a.name);
|
||||
v("type", a.type);
|
||||
v("size", a.size);
|
||||
v("bufferIndex", a.bufferIndex);
|
||||
v("offsetBytes", a.offsetBytes);
|
||||
v("byteSize", a.byteSize);
|
||||
v("packedOffsetBytes", a.packedOffsetBytes);
|
||||
v("blockInstanceName", a.blockInstanceName);
|
||||
v("blockName", a.blockName);
|
||||
v("blockMemberIndex", a.blockMemberIndex);
|
||||
v("blockMemberElement", a.blockMemberElement);
|
||||
} // 11 fields
|
||||
|
||||
// Every member EXCEPT `program`: it is null for every archived instance by construction
|
||||
// (ProgramTranslationCache.h asserts that at insert) and must never be serialized - it is
|
||||
// the live glslang TProgram that only DoReflection may touch. 57 of the 58 members.
|
||||
template <class Self, class V>
|
||||
requires std::same_as<std::remove_const_t<Self>, LinkArtifacts>
|
||||
void VisitFields(Self& a, V&& v) {
|
||||
v("uniformReflection", a.uniformReflection);
|
||||
v("blockReflection", a.blockReflection);
|
||||
v("pipeInputReflection", a.pipeInputReflection);
|
||||
v("pipeOutputReflection", a.pipeOutputReflection);
|
||||
v("lastStageIsFragment", a.lastStageIsFragment);
|
||||
v("computeLocalSize", a.computeLocalSize);
|
||||
v("uniformIndexByName", a.uniformIndexByName);
|
||||
v("attribs", a.attribs);
|
||||
v("attribTypes", a.attribTypes);
|
||||
v("linkedFragDataLocation", a.linkedFragDataLocation);
|
||||
v("linkedFragDataIndex", a.linkedFragDataIndex);
|
||||
v("glUniformIndexToTProgram", a.glUniformIndexToTProgram);
|
||||
v("tProgramUniformIndexToGl", a.tProgramUniformIndexToGl);
|
||||
v("glBlockIndexToTProgram", a.glBlockIndexToTProgram);
|
||||
v("tProgramBlockIndexToGl", a.tProgramBlockIndexToGl);
|
||||
v("glUniformBlockIndexToBlock", a.glUniformBlockIndexToBlock);
|
||||
v("blockIndexToGlUniformBlock", a.blockIndexToGlUniformBlock);
|
||||
v("linkedExplicitUniformLocations", a.linkedExplicitUniformLocations);
|
||||
v("uniformInitialValues", a.uniformInitialValues);
|
||||
v("uniformLocations", a.uniformLocations);
|
||||
v("writtenUniformLocationBits", a.writtenUniformLocationBits);
|
||||
v("writtenUniformIndexBits", a.writtenUniformIndexBits);
|
||||
v("writtenUniformIndices", a.writtenUniformIndices);
|
||||
v("uniformIndexInTProgram", a.uniformIndexInTProgram);
|
||||
v("uniformSamplerOrImageUnitIndex", a.uniformSamplerOrImageUnitIndex);
|
||||
v("explicitOpaqueUniformBindings", a.explicitOpaqueUniformBindings);
|
||||
v("uniformBlockIndexByName", a.uniformBlockIndexByName);
|
||||
v("uniformBlockBinding", a.uniformBlockBinding);
|
||||
v("shaderStorageBlockBinding", a.shaderStorageBlockBinding);
|
||||
v("storageBlocksWithoutBinding", a.storageBlocksWithoutBinding);
|
||||
v("uniformBlocksWithoutBinding", a.uniformBlocksWithoutBinding);
|
||||
v("activeUniformCount", a.activeUniformCount);
|
||||
v("usesReservedNumSamples", a.usesReservedNumSamples);
|
||||
v("maxUniformLocation", a.maxUniformLocation);
|
||||
v("uniformNameMaxLength", a.uniformNameMaxLength);
|
||||
v("attribInNameMaxLength", a.attribInNameMaxLength);
|
||||
v("uniformBlockNameMaxLength", a.uniformBlockNameMaxLength);
|
||||
v("infoLog", a.infoLog);
|
||||
v("linkStatus", a.linkStatus);
|
||||
v("xfbVaryings", a.xfbVaryings);
|
||||
v("xfbInterfaceNames", a.xfbInterfaceNames);
|
||||
v("xfbStrides", a.xfbStrides);
|
||||
v("gsStripTriangles", a.gsStripTriangles);
|
||||
v("gsStripCaptureFixup", a.gsStripCaptureFixup);
|
||||
v("gsInputPrimitive", a.gsInputPrimitive);
|
||||
v("tcsOutputVertices", a.tcsOutputVertices);
|
||||
v("gsOutputPrimitive", a.gsOutputPrimitive);
|
||||
v("gsMaxVertices", a.gsMaxVertices);
|
||||
v("gsInvocations", a.gsInvocations);
|
||||
v("tessGenMode", a.tessGenMode);
|
||||
v("tessGenSpacing", a.tessGenSpacing);
|
||||
v("tessGenVertexOrder", a.tessGenVertexOrder);
|
||||
v("tessGenPointMode", a.tessGenPointMode);
|
||||
v("xfbBufferMode", a.xfbBufferMode);
|
||||
v("xfbVaryingNameMaxLength", a.xfbVaryingNameMaxLength);
|
||||
v("xfbNeedsScatteredCapture", a.xfbNeedsScatteredCapture);
|
||||
v("xfbPackedStride", a.xfbPackedStride);
|
||||
} // 57 fields (58 members minus `program`)
|
||||
|
||||
template <class Self, class V>
|
||||
requires std::same_as<std::remove_const_t<Self>, SpirvArtifacts>
|
||||
void VisitFields(Self& a, V&& v) {
|
||||
v("generatedSpirv", a.generatedSpirv);
|
||||
v("enableSpirvValidation", a.enableSpirvValidation);
|
||||
v("uniformOffsets", a.uniformOffsets);
|
||||
v("globalUboScratch", a.globalUboScratch);
|
||||
v("reservedNumSamplesOffset", a.reservedNumSamplesOffset);
|
||||
v("spirvStatus", a.spirvStatus);
|
||||
v("nativeFloat64", a.nativeFloat64);
|
||||
v("pointSizeDemoted", a.pointSizeDemoted);
|
||||
} // 8 fields
|
||||
|
||||
// ---- trip wires ----
|
||||
// TypeFacts is a POD on every ABI: 13 Bool + 3 bytes of padding + 7 x 4-byte scalars.
|
||||
static_assert(std::is_trivially_copyable_v<TypeFacts> && sizeof(TypeFacts) == 44,
|
||||
"TypeFacts changed: add the field to VisitFields(TypeFacts) (and its serializer when one exists), then update this number");
|
||||
// The container-bearing structs have one size per standard library (std::string and
|
||||
// std::set differ between libstdc++ and libc++), so their numbers are pinned PER STL:
|
||||
// libstdc++ (the Linux CI toolchain) here, libc++ (the NDK) by the integrator, MSVC
|
||||
// unasserted. ProgramArtifactsTest records every sizeof as a ctest property on every
|
||||
// platform, which is where a new toolchain's numbers are read from.
|
||||
#if defined(__GLIBCXX__) && !defined(_GLIBCXX_DEBUG) && (SIZE_MAX == UINT64_MAX)
|
||||
#define MGL_RESOURCEREFLECTION_SIZE 128
|
||||
#define MGL_XFBVARYING_SIZE 128
|
||||
#define MGL_LINKARTIFACTS_SIZE 1056
|
||||
#define MGL_SPIRVARTIFACTS_SIZE 88
|
||||
#elif defined(_LIBCPP_VERSION) && (SIZE_MAX == UINT64_MAX) && defined(MGL_ARTIFACT_SIZES_LIBCXX_PINNED)
|
||||
// The integrator pins these from the NDK build (brief C.4); until then this branch is inert.
|
||||
#endif
|
||||
#ifdef MGL_LINKARTIFACTS_SIZE
|
||||
static_assert(sizeof(ResourceReflection) == MGL_RESOURCEREFLECTION_SIZE,
|
||||
"ResourceReflection changed size: add the field to VisitFields(ResourceReflection) (and its serializer when one exists), then update this number");
|
||||
static_assert(sizeof(XfbVarying) == MGL_XFBVARYING_SIZE,
|
||||
"XfbVarying changed size: add the field to VisitFields(XfbVarying) (and its serializer when one exists), then update this number");
|
||||
static_assert(sizeof(LinkArtifacts) == MGL_LINKARTIFACTS_SIZE,
|
||||
"LinkArtifacts changed size: add the field to VisitFields(LinkArtifacts) (and its serializer when one exists), then update this number");
|
||||
static_assert(sizeof(SpirvArtifacts) == MGL_SPIRVARTIFACTS_SIZE,
|
||||
"SpirvArtifacts changed size: add the field to VisitFields(SpirvArtifacts) (and its serializer when one exists), then update this number");
|
||||
#endif
|
||||
} // namespace MobileGL::MG_State::GLState
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
// For the alias checks only.
|
||||
#include <MG_State/GLState/ProgramState/ProgramObject.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
namespace {
|
||||
@@ -44,4 +47,87 @@ namespace {
|
||||
EXPECT_EQ(sizeof(TypeFacts), 44u);
|
||||
EXPECT_EQ(alignof(TypeFacts), 4u);
|
||||
}
|
||||
|
||||
// A visitor that counts what a table hands it and checks the names are distinct - the
|
||||
// archive's one field table per type has to name every member exactly once.
|
||||
struct CountingVisitor {
|
||||
std::size_t count = 0;
|
||||
std::set<std::string> names;
|
||||
template <class Field>
|
||||
void operator()(const char* name, Field&) {
|
||||
++count;
|
||||
names.insert(name);
|
||||
}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
std::size_t CountFields() {
|
||||
T value{};
|
||||
CountingVisitor mutableVisitor;
|
||||
VisitFields(value, mutableVisitor);
|
||||
const T& constValue = value;
|
||||
CountingVisitor constVisitor;
|
||||
VisitFields(constValue, constVisitor);
|
||||
EXPECT_EQ(mutableVisitor.count, constVisitor.count) << "const and non-const walks disagree";
|
||||
EXPECT_EQ(mutableVisitor.names.size(), mutableVisitor.count) << "a field name is listed twice";
|
||||
EXPECT_EQ(mutableVisitor.names, constVisitor.names);
|
||||
return mutableVisitor.count;
|
||||
}
|
||||
|
||||
// The field counts are the member counts of the moved structs (LinkArtifacts minus its
|
||||
// never-archived `program`). A member added without a table entry is caught by the sizeof
|
||||
// trip wires in the header; a table entry dropped without a member change is caught here.
|
||||
TEST(ProgramArtifacts, VisitFieldsCoversEveryMember) {
|
||||
EXPECT_EQ(CountFields<TypeFacts>(), 20u);
|
||||
EXPECT_EQ(CountFields<ResourceReflection>(), 14u);
|
||||
EXPECT_EQ(CountFields<XfbVarying>(), 11u);
|
||||
EXPECT_EQ(CountFields<LinkArtifacts>(), 57u);
|
||||
EXPECT_EQ(CountFields<SpirvArtifacts>(), 8u);
|
||||
}
|
||||
|
||||
// A const walk hands the visitor const references, a non-const walk mutable ones: the one
|
||||
// table really serves both directions.
|
||||
TEST(ProgramArtifacts, VisitFieldsPassesConstnessThrough) {
|
||||
LinkArtifacts artifacts;
|
||||
std::size_t mutableFields = 0;
|
||||
VisitFields(artifacts, [&](const char*, auto& field) {
|
||||
static_assert(!std::is_const_v<std::remove_reference_t<decltype(field)>>);
|
||||
++mutableFields;
|
||||
});
|
||||
const LinkArtifacts& constArtifacts = artifacts;
|
||||
std::size_t constFields = 0;
|
||||
VisitFields(constArtifacts, [&](const char*, auto& field) {
|
||||
static_assert(std::is_const_v<std::remove_reference_t<decltype(field)>>);
|
||||
++constFields;
|
||||
});
|
||||
EXPECT_EQ(mutableFields, constFields);
|
||||
// The table can write through: a deserializer's shape.
|
||||
VisitFields(artifacts, [](const char* name, auto& field) {
|
||||
if constexpr (std::is_same_v<std::remove_reference_t<decltype(field)>, Bool>) {
|
||||
if (std::string(name) == "linkStatus") field = true;
|
||||
}
|
||||
});
|
||||
EXPECT_TRUE(artifacts.linkStatus);
|
||||
}
|
||||
|
||||
// The sizeof numbers, visible in every `ctest -V` log on every platform: this is where the
|
||||
// integrator reads a new toolchain's values from before pinning them in the header.
|
||||
TEST(ProgramArtifacts, SizesArePinnedOnThisToolchain) {
|
||||
RecordProperty("sizeof_TypeFacts", static_cast<int>(sizeof(TypeFacts)));
|
||||
RecordProperty("sizeof_ResourceReflection", static_cast<int>(sizeof(ResourceReflection)));
|
||||
RecordProperty("sizeof_XfbVarying", static_cast<int>(sizeof(XfbVarying)));
|
||||
RecordProperty("sizeof_LinkArtifacts", static_cast<int>(sizeof(LinkArtifacts)));
|
||||
RecordProperty("sizeof_SpirvArtifacts", static_cast<int>(sizeof(SpirvArtifacts)));
|
||||
std::printf("sizeof: TypeFacts=%zu ResourceReflection=%zu XfbVarying=%zu LinkArtifacts=%zu SpirvArtifacts=%zu\n",
|
||||
sizeof(TypeFacts), sizeof(ResourceReflection), sizeof(XfbVarying), sizeof(LinkArtifacts),
|
||||
sizeof(SpirvArtifacts));
|
||||
#if defined(__GLIBCXX__) && !defined(_GLIBCXX_DEBUG) && (SIZE_MAX == UINT64_MAX)
|
||||
EXPECT_EQ(sizeof(ResourceReflection), static_cast<std::size_t>(MGL_RESOURCEREFLECTION_SIZE));
|
||||
EXPECT_EQ(sizeof(XfbVarying), static_cast<std::size_t>(MGL_XFBVARYING_SIZE));
|
||||
EXPECT_EQ(sizeof(LinkArtifacts), static_cast<std::size_t>(MGL_LINKARTIFACTS_SIZE));
|
||||
EXPECT_EQ(sizeof(SpirvArtifacts), static_cast<std::size_t>(MGL_SPIRVARTIFACTS_SIZE));
|
||||
#else
|
||||
RecordProperty("sizes_pinned", "no: not the libstdc++ 64-bit toolchain");
|
||||
#endif
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Reference in New Issue
Block a user