[Feat] (Pipe): give the six value structs G4 field lists and assert the lists cover their members - the memcmp fallback is now a compile error, floats in vector types compare bitwise

- PipeFields.def gains MGP_FIELDS_RenderStateParameters (65 members), PixelStoreParameters (8), PerBufferBlendState (7), StencilFaceState (7), DynamicBackendParameters (85) and MGHostSpan (Ptr, Seg, Size, Offset), all appended to MGP_VERIFY_PAYLOAD_LIST: kMGPipeVerifiedPayloadCount 63 -> 69, and ResidualValueBlock / MGPPixelPackState / MGPCaps are now compared field by field all the way down.
- gen_verify: MEMCMP_FALLBACK_TYPES is empty and the generic MGPipeFieldEqual's last branch is static_assert(sizeof(T) == 0) - a struct without a field list is a compile error, not a padding false positive; Array<T, N> gets an element-wise overload, and a VecBase-derived vector (FloatVec4, IntVec4, BoolVec4...) is detected by a probe and compared bitwise over its data, because VecBase::operator== is IEEE == and a derived-to-base overload would lose resolution to the exact-match generic template.
- check_field_lists_cover_struct_members(): for every payload in MGP_VERIFY_PAYLOAD_LIST, parse `struct <Name> {` out of MGPipeTypes.h / MGPipeValueTypes.h / MGPipeHostSpan.h / BackendObject.h (comments and strings masked, statics, functions, nested types and Pad<n> members excluded) and refuse a member without an F(...) or an F(...) that is not a member; runs in both modes, so it is a pipe-gates gate.
- scan_live_accessors(): every MGB_CTX-> / pGLContext-> read under MG_Backend must have a Coverage.def row (rows nobody reads are printed: today only the dead GetBoundTransformFeedbackName).
- --self-test: six negative controls (struct member without F, F without member, payload without struct, verb missing from FillPoints.def, verb outside GLFunctionsTable, field row naming a non-accessor) that must each trip, plus a positive control; zero trips is itself an error.
This commit is contained in:
2026-09-06 02:02:07 -04:00
parent 275dd3edb4
commit 83b16561c2
3 changed files with 522 additions and 32 deletions
+79 -4
View File
@@ -12,9 +12,11 @@
// exactly what makes a memcmp of RenderStateParameters false-DIFFER
// (DirectGLES.cpp documents that behaviour where it does the same comparison itself).
//
// Hand maintained alongside MGPipeTypes.h. Adding a field to a payload without adding it
// here makes the comparator blind to it; that gap closes in P1, when the verify harness
// goes live and the comparator's coverage is itself asserted.
// Hand maintained alongside MGPipeTypes.h, MGPipeValueTypes.h, MGPipeHostSpan.h and
// MG_Backend/BackendObject.h. Adding a member to one of these structs without adding it here
// would make the comparator blind to it, so gen_pipe.py asserts - in both modes, hence in
// pipe-gates - that every list below names exactly the direct data members of its struct
// (P1 brief D8; a member named Pad<n> is padding and is not listed).
//
// clang-format off
@@ -218,6 +220,77 @@
#define MGP_FIELDS_MGPSurfaceInfo(F) \
F(Width) F(Height) F(InternalFormat) F(Samples) F(Layers) F(IsDefault)
// ---- the value structs and the host span (P1 brief D8). Not call payloads themselves, but
// members of ones (ResidualValueBlock, MGPPixelPackState, MGPCaps) and of PipeInputs, so the
// comparator has to see INTO them: with these lists the memcmp fallback of MGPipeFieldEqual is
// gone (a struct without a list is a compile error), and gen_pipe.py asserts every list names
// every direct data member of its struct - Pad-named members are padding and excluded - so a
// member added to RenderStateParameters without a row here fails pipe-gates.
#define MGP_FIELDS_RenderStateParameters(F) \
F(Viewports) F(LineWidth) F(PointSize) F(PatchVertices) F(PatchDefaultOuterLevel) \
F(PatchDefaultInnerLevel) F(PolygonOffsetFactor) F(PolygonOffsetUnits) F(PolygonOffsetClamp) \
F(ClipOrigin) F(ClipDepthMode) F(BlendStates) F(LogicOp) F(DepthTestEnabled) F(DepthFunc) \
F(DepthMask) F(ColorMasks) F(ClearColor) F(ClearDepth) F(ClearStencil) F(BlendColor) \
F(DepthRanges) F(SampleCoverageValue) F(SampleCoverageInvert) F(SampleMaskValue) \
F(MinSampleShadingValue) F(StencilStates) F(CullFaceEnabled) F(CullFaceModeSetting) \
F(FrontFaceModeSetting) F(ProvokingVertexModeSetting) F(LineSmoothHint) F(PolygonSmoothHint) \
F(TextureCompressionHint) F(FragmentShaderDerivativeHint) F(PointFadeThresholdSize) \
F(PointSpriteCoordOrigin) F(ClampReadColor) F(PolygonModeFront) F(PolygonModeBack) \
F(PrimitiveRestartIndex) F(ColorLogicOpEnabled) F(DebugOutputEnabled) \
F(DebugOutputSynchronousEnabled) F(DitherEnabled) F(LineSmoothEnabled) F(MultisampleEnabled) \
F(PolygonOffsetFillEnabled) F(PolygonOffsetLineEnabled) F(PolygonOffsetPointEnabled) \
F(PolygonSmoothEnabled) F(PrimitiveRestartEnabled) F(PrimitiveRestartFixedIndexEnabled) \
F(RasterizerDiscardEnabled) F(SampleAlphaToCoverageEnabled) F(SampleAlphaToOneEnabled) \
F(SampleCoverageEnabled) F(SampleMaskEnabled) F(SampleShadingEnabled) F(StencilTestEnabled) \
F(ProgramPointSizeEnabled) F(ScissorTestEnabledMask) F(ScissorBoxes) F(ScissorBoxWrittenMask) \
F(ClipDistanceEnabledMask)
#define MGP_FIELDS_PixelStoreParameters(F) \
F(SwapBytes) F(LSBFirst) F(RowLength) F(ImageHeight) F(SkipPixels) F(SkipRows) F(SkipImages) \
F(Alignment)
#define MGP_FIELDS_PerBufferBlendState(F) \
F(Enabled) F(SrcFactorRGB) F(DstFactorRGB) F(SrcFactorAlpha) F(DstFactorAlpha) F(ColorEquation) \
F(AlphaEquation)
#define MGP_FIELDS_StencilFaceState(F) \
F(Func) F(Ref) F(ValueMask) F(WriteMask) F(FailOp) F(PassDepthFailOp) F(PassDepthPassOp)
#define MGP_FIELDS_DynamicBackendParameters(F) \
F(UniformBufferOffsetAlignment) F(ShaderStorageBufferOffsetAlignment) F(MaxTextureMaxAnisotropy) \
F(AliasedLineWidthRangeMin) F(AliasedLineWidthRangeMax) F(SmoothLineWidthRangeMin) \
F(SmoothLineWidthRangeMax) F(SmoothLineWidthGranularity) F(PointSizeRangeMin) \
F(PointSizeRangeMax) F(PointSizeGranularity) F(Max3DTextureSize) F(MaxArrayTextureLayers) \
F(MaxCubeMapTextureSize) F(MaxFramebufferWidth) F(MaxFramebufferHeight) F(MaxFramebufferLayers) \
F(MaxRenderbufferSize) F(MaxTextureSize) F(MaxColorTextureSamples) F(MaxDepthTextureSamples) \
F(MaxFramebufferSamples) F(MaxIntegerSamples) F(MaxSamples) F(MaxSampleMaskWords) \
F(MaxPatchVertices) F(MaxTessGenLevel) F(MinProgramTextureGatherOffset) \
F(MaxProgramTextureGatherOffset) F(MaxTextureImageUnits) F(MaxVertexTextureImageUnits) \
F(MaxComputeTextureImageUnits) F(MaxCombinedTextureImageUnits) F(MaxVertexAttribs) \
F(MaxComputeShaderStorageBlocks) F(MaxCombinedShaderStorageBlocks) \
F(MaxVertexShaderStorageBlocks) F(MaxTessControlShaderStorageBlocks) \
F(MaxTessEvaluationShaderStorageBlocks) F(MaxGeometryShaderStorageBlocks) \
F(MaxFragmentShaderStorageBlocks) F(MaxComputeUniformBlocks) F(MaxComputeWorkGroupInvocations) \
F(MaxComputeWorkGroupCount) F(MaxComputeWorkGroupSize) F(MaxShaderStorageBufferBindings) \
F(MaxTextureBufferSize) F(TextureBufferOffsetAlignment) F(MaxUniformBufferBindings) \
F(MaxUniformBlockSize) F(MaxImageUnits) F(MaxCombinedImageUniforms) F(MaxVertexImageUniforms) \
F(MaxGeometryImageUniforms) F(MaxFragmentImageUniforms) F(MaxComputeImageUniforms) \
F(MaxDrawBuffers) F(MaxColorAttachments) F(MaxClipDistances) F(MaxCullDistances) \
F(MaxCombinedClipAndCullDistances) F(MaxViewports) F(LayerProvokingVertex) \
F(ViewportIndexProvokingVertex) F(MaxViewportWidth) F(MaxViewportHeight) \
F(ViewportBoundsRangeMin) F(ViewportBoundsRangeMax) F(ViewportSubpixelBits) \
F(MinFragmentInterpolationOffset) F(MaxFragmentInterpolationOffset) \
F(FragmentInterpolationOffsetBits) F(SupportsWideLines) \
F(SupportsDistinctDepthStencilAttachments) F(PerLayerFramebufferAttachmentTargets) \
F(SupportsShaderFloat64) F(SupportsFloat64VertexAttributes) F(SupportsTessellationPointSize) \
F(SupportsGeometryPointSize) F(MaxShaderStorageBlockSize) F(SubgroupSize) \
F(SubgroupSupportedStages) F(SubgroupSupportedFeatures) F(SubgroupQuadOperationsInAllStages) \
F(GpuVendor)
#define MGP_FIELDS_MGHostSpan(F) \
F(Ptr) F(Seg) F(Size) F(Offset)
// Every payload above, in the order the comparator is generated. Keep in sync with the
// macros; gen_pipe.py reads THIS list to know what to emit.
#define MGP_VERIFY_PAYLOAD_LIST(P) \
@@ -233,6 +306,8 @@
P(MGPCopyRegion) P(MGPBlit) P(MGPClear) P(MGPMipPlan) P(MGPReadbackInfo) P(MGPDrawInfo) \
P(MGPDrawRange) P(MGPDrawIndirect) P(MGPGridInfo) P(MGPMemoryBarrier) P(MGPStreamOutputBegin) \
P(MGPXfbAccounting) P(MGPStreamOutputControl) P(MGPFlush) P(MGPPresent) P(MGPSwapInterval) \
P(MGPSurfaceInfo)
P(MGPSurfaceInfo) \
P(RenderStateParameters) P(PixelStoreParameters) P(PerBufferBlendState) P(StencilFaceState) \
P(DynamicBackendParameters) P(MGHostSpan)
// clang-format on
+82 -7
View File
@@ -27,6 +27,23 @@
template <class T>
struct MGPipeHasFieldVerifier : std::false_type {};
// A vector type (FloatVec4, IntVec4, BoolVec4...) is detected through its VecBase and
// compared BITWISE over its data: VecBase::operator== is IEEE ==, under which a NaN patch
// level would differ from itself. The probe rather than an overload because a
// derived-to-base conversion loses overload resolution to the exact-match generic template.
template <class Derived, class T, SizeT N>
std::true_type MGPipeVecBaseProbe(const VecBase<Derived, T, N>*);
std::false_type MGPipeVecBaseProbe(const void*);
template <class T>
inline constexpr Bool kMGPipeIsVecBase = decltype(MGPipeVecBaseProbe(static_cast<const T*>(nullptr)))::value;
template <class T>
inline Bool MGPipeFieldEqual(const T& a, const T& b);
template <class T, SizeT N>
inline Bool MGPipeFieldEqual(const Array<T, N>& a, const Array<T, N>& b);
template <class T, SizeT N>
inline Bool MGPipeFieldEqual(const T (&a)[N], const T (&b)[N]);
inline Bool MGPipeVerify(const MGPBlobRef& a, const MGPBlobRef& b, const char** outField);
inline Bool MGPipeVerify(const MGPRange& a, const MGPRange& b, const char** outField);
inline Bool MGPipeVerify(const MGPBox& a, const MGPBox& b, const char** outField);
@@ -90,6 +107,12 @@ inline Bool MGPipeVerify(const MGPFlush& a, const MGPFlush& b, const char** outF
inline Bool MGPipeVerify(const MGPPresent& a, const MGPPresent& b, const char** outField);
inline Bool MGPipeVerify(const MGPSwapInterval& a, const MGPSwapInterval& b, const char** outField);
inline Bool MGPipeVerify(const MGPSurfaceInfo& a, const MGPSurfaceInfo& b, const char** outField);
inline Bool MGPipeVerify(const RenderStateParameters& a, const RenderStateParameters& b, const char** outField);
inline Bool MGPipeVerify(const PixelStoreParameters& a, const PixelStoreParameters& b, const char** outField);
inline Bool MGPipeVerify(const PerBufferBlendState& a, const PerBufferBlendState& b, const char** outField);
inline Bool MGPipeVerify(const StencilFaceState& a, const StencilFaceState& b, const char** outField);
inline Bool MGPipeVerify(const DynamicBackendParameters& a, const DynamicBackendParameters& b, const char** outField);
inline Bool MGPipeVerify(const MGHostSpan& a, const MGHostSpan& b, const char** outField);
template <>
struct MGPipeHasFieldVerifier<MGPBlobRef> : std::true_type {};
@@ -217,12 +240,26 @@ template <>
struct MGPipeHasFieldVerifier<MGPSwapInterval> : std::true_type {};
template <>
struct MGPipeHasFieldVerifier<MGPSurfaceInfo> : std::true_type {};
template <>
struct MGPipeHasFieldVerifier<RenderStateParameters> : std::true_type {};
template <>
struct MGPipeHasFieldVerifier<PixelStoreParameters> : std::true_type {};
template <>
struct MGPipeHasFieldVerifier<PerBufferBlendState> : std::true_type {};
template <>
struct MGPipeHasFieldVerifier<StencilFaceState> : std::true_type {};
template <>
struct MGPipeHasFieldVerifier<DynamicBackendParameters> : std::true_type {};
template <>
struct MGPipeHasFieldVerifier<MGHostSpan> : std::true_type {};
template <class T>
inline Bool MGPipeFieldEqual(const T& a, const T& b) {
if constexpr (MGPipeHasFieldVerifier<T>::value) {
const char* unusedField = nullptr;
return MGPipeVerify(a, b, &unusedField);
} else if constexpr (kMGPipeIsVecBase<T>) {
return std::memcmp(a.data.data(), b.data.data(), sizeof(a.data)) == 0;
} else if constexpr (std::is_floating_point_v<T>) {
return std::memcmp(&a, &b, sizeof(T)) == 0;
} else if constexpr (std::is_scalar_v<T> || std::is_enum_v<T>) {
@@ -230,15 +267,23 @@ inline Bool MGPipeFieldEqual(const T& a, const T& b) {
} else if constexpr (requires(const T& x, const T& y) { x == y; }) {
return a == b;
} else {
// MEMCMP FALLBACK. Only reached by the payload members that are still value structs
// without a field list (RenderStateParameters, PixelStoreParameters,
// DynamicBackendParameters) and by MGHostSpan. P0.5 moved the first two into
// MGPipeValueTypes.h; P1 gives them field lists of their own, at which point this
// branch stops being reachable from any payload.
return std::memcmp(&a, &b, sizeof(T)) == 0;
// NO MEMCMP FALLBACK. Every value struct has a field list in PipeFields.def since P1
// (and gen_pipe.py asserts each list covers its struct's members); a type reaching
// this branch is one nobody gave a field list, and a memcmp would false-differ on
// its padding. A compile error is the honest answer.
static_assert(sizeof(T) == 0, "no field list in PipeFields.def for this type");
return false;
}
}
template <class T, SizeT N>
inline Bool MGPipeFieldEqual(const Array<T, N>& a, const Array<T, N>& b) {
for (SizeT i = 0; i < N; ++i) {
if (!MGPipeFieldEqual(a[i], b[i])) return false;
}
return true;
}
template <class T, SizeT N>
inline Bool MGPipeFieldEqual(const T (&a)[N], const T (&b)[N]) {
for (SizeT i = 0; i < N; ++i) {
@@ -568,6 +613,36 @@ inline Bool MGPipeVerify(const MGPSurfaceInfo& a, const MGPSurfaceInfo& b, const
return true;
}
inline Bool MGPipeVerify(const RenderStateParameters& a, const RenderStateParameters& b, const char** outField) {
MGP_FIELDS_RenderStateParameters(MGP_VERIFY_FIELD)
return true;
}
inline Bool MGPipeVerify(const PixelStoreParameters& a, const PixelStoreParameters& b, const char** outField) {
MGP_FIELDS_PixelStoreParameters(MGP_VERIFY_FIELD)
return true;
}
inline Bool MGPipeVerify(const PerBufferBlendState& a, const PerBufferBlendState& b, const char** outField) {
MGP_FIELDS_PerBufferBlendState(MGP_VERIFY_FIELD)
return true;
}
inline Bool MGPipeVerify(const StencilFaceState& a, const StencilFaceState& b, const char** outField) {
MGP_FIELDS_StencilFaceState(MGP_VERIFY_FIELD)
return true;
}
inline Bool MGPipeVerify(const DynamicBackendParameters& a, const DynamicBackendParameters& b, const char** outField) {
MGP_FIELDS_DynamicBackendParameters(MGP_VERIFY_FIELD)
return true;
}
inline Bool MGPipeVerify(const MGHostSpan& a, const MGHostSpan& b, const char** outField) {
MGP_FIELDS_MGHostSpan(MGP_VERIFY_FIELD)
return true;
}
#undef MGP_VERIFY_FIELD
inline constexpr SizeT kMGPipeVerifiedPayloadCount = 63;
inline constexpr SizeT kMGPipeVerifiedPayloadCount = 69;