[MG_Backend, tools] (P7): every WireDeclines.def row has a site and every site a warning-or-error line, and a tree-only audit fails the job when one does not

The B3 review (ID-P7-34) counted 12 of the 52 rows with no site at all -
TexViewFormat, TexNoApplierRecord, TexRecordDeadOrStale and the nine Shape*
rows were declared and never counted - and ten sites that counted with no (MGLOG_D included)
line, so "a name, a tally and a decline line" did not describe the tree.

- SyncWireTextureShape: all nine Shape* exits now count; the ViewOf exit
  and the preserve-flush exit were bare `return false` and are now
  MGL_WIRE_DECLINE_AT; the image-flags exit logged only MGLOG_D, which a
  Release build compiles away, and is promoted likewise.
- The preserve-copy exit was the one Shape exit with no row: appended as
  ShapePreserveCopyFailed (rows are append-only), 53 rows.
- SyncTextureResourceByHandle / UploadPendingWireLevels: the count-only
  sites without a log of their own are promoted to MGL_WIRE_DECLINE_AT;
  the ones already under a W/E _ONCE line keep that line and gain the count.
- scripts/ci/wire_declines_audit.py: fails on a row with no site, a site
  naming no row, or a bare Count() with no MGLOG_W/E within 8 lines above
  it. WireDeclineTally.h (the macro's definition) is not a site. Wired into
  the job that runs the fatal census.

Audit before: 52 rows, 40 with sites, 10 unlogged under the W/E rule (rc 1). After: 53 rows,
53 with sites, 0 unlogged (rc 0). Forced red (ShapeNoVkFormat made true for
1x1 textures, reverted): Split.Fm.F1WireScenario.VertexIdSampler... fails on
pixels with ShapeNoVkFormat=2 / TexShapeSync=2 in the shutdown tally.
This commit is contained in:
2026-09-22 20:24:00 -04:00
parent 613ed05b98
commit b8d10d0730
4 changed files with 187 additions and 16 deletions
+5
View File
@@ -2146,6 +2146,11 @@ jobs:
- name: Fatal census (S7)
run: python3 scripts/ci/fatal_census.py
# The decline half of rule I (P7 B3): every WireDeclines.def row has a site, every site a
# warning-or-error line. Same shape as the census - tree-only, no build.
- name: Wire declines audit (B3)
run: python3 scripts/ci/wire_declines_audit.py
- name: Load trace cases
id: trace-cases
run: |
@@ -2071,13 +2071,21 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (desc.StorageKind != static_cast<Uint8>(TextureStorageType::Mipmap)) {
MGLOG_W_ONCE("Magma wire texture {slot=%u, gen=%u}: buffer-backed textures are not migrated; declined",
desc.Resource.Slot, desc.Resource.Gen);
WireDeclineTally::Count(WireDeclineSite::ShapeBufferBacked);
return false;
}
// ResolveWireTextureStorage has already followed ViewOf. Views share the storage
// owner's image and layout; allocating a second image would break aliasing.
if (!MG_Pipe::MGPipeHandleIsNull(desc.ViewOf)) return false;
if (!MG_Pipe::MGPipeHandleIsNull(desc.ViewOf)) {
MGL_WIRE_DECLINE_AT(ShapeViewOfStorage,
"texture {slot=%u, gen=%u} is a view whose storage owner did not resolve",
desc.Resource.Slot, desc.Resource.Gen);
return false;
}
if (desc.Levels == 0) {
// A create before the first respecify: no storage, nothing to back.
MGL_WIRE_DECLINE_AT(ShapeNoLevels, "texture {slot=%u, gen=%u} has no defined level yet",
desc.Resource.Slot, desc.Resource.Gen);
return false;
}
TextureShapeInfo shapeInfo{};
@@ -2086,6 +2094,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
"(%ux%ux%u, %u layers); declined",
desc.Resource.Slot, desc.Resource.Gen, static_cast<Uint32>(desc.Target), desc.Width,
desc.Height, desc.Depth, static_cast<Uint32>(desc.ArrayLayers));
WireDeclineTally::Count(WireDeclineSite::ShapeNoVulkanShape);
return false;
}
const TextureInternalFormat internalFormat = static_cast<TextureInternalFormat>(desc.InternalFormat);
@@ -2094,6 +2103,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (format == VK_FORMAT_UNDEFINED) {
MGLOG_W_ONCE("Magma wire texture {slot=%u, gen=%u}: no backing VkFormat for internal format 0x%x",
desc.Resource.Slot, desc.Resource.Gen, desc.InternalFormat);
WireDeclineTally::Count(WireDeclineSite::ShapeNoVkFormat);
return false;
}
// X8_D24 lacks optimal-tiling support on several drivers (lavapipe included); the same
@@ -2116,6 +2126,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (!TryResolveSampleCountFlagBits(static_cast<Int>(desc.Samples), resolvedSampleCount)) {
MGLOG_W_ONCE("Magma wire texture {slot=%u, gen=%u}: unsupported sample count %u",
desc.Resource.Slot, desc.Resource.Gen, static_cast<Uint32>(desc.Samples));
WireDeclineTally::Count(WireDeclineSite::ShapeSampleCount);
return false;
}
// glTexStorage*Multisample(samples = 1) is legal GL, but a one-sample image cannot
@@ -2251,7 +2262,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// old image must reach the queue first; waiting only for the preservation
// fence cannot order work that is still in the renderer's open recording.
// The descriptor caller preflights this before capturing its command buffer.
if (preserve && pVulkanRenderer && !pVulkanRenderer->FlushWirePendingCommandsForTextureUpdate()) return false;
if (preserve && pVulkanRenderer && !pVulkanRenderer->FlushWirePendingCommandsForTextureUpdate()) {
MGL_WIRE_DECLINE_AT(ShapePreserveFlushFailed,
"texture {slot=%u, gen=%u}: earlier work could not be submitted before "
"preserving its texels across a reallocation",
desc.Resource.Slot, desc.Resource.Gen);
return false;
}
TextureResource replacement;
VkImageCreateInfo imageInfo{};
@@ -2298,9 +2315,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
if (imageFormatResult != VK_SUCCESS ||
(isMultisample && (imageFormatProperties.sampleCounts & resolvedSampleCount) == 0)) {
MGLOG_D("%s: image flags=0x%x sampleCount=%d are unsupported for texture {slot=%u, gen=%u}",
__func__, static_cast<Uint32>(imageInfo.flags),
static_cast<Int>(imageInfo.samples), desc.Resource.Slot, desc.Resource.Gen);
// Was an MGLOG_D, which a Release build compiles away: the one Shape* exit that
// left no line on the builds that ship.
MGL_WIRE_DECLINE_AT(ShapeImageFlagsUnsupported,
"texture {slot=%u, gen=%u}: image flags=0x%x sampleCount=%d are unsupported "
"by the device",
desc.Resource.Slot, desc.Resource.Gen, static_cast<Uint32>(imageInfo.flags),
static_cast<Int>(imageInfo.samples));
return false;
}
}
@@ -2319,6 +2340,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
imageInfo.extent.height, imageInfo.extent.depth, imageInfo.arrayLayers,
imageInfo.mipLevels, static_cast<Int>(imageInfo.samples),
static_cast<Int>(imageInfo.format));
WireDeclineTally::Count(WireDeclineSite::ShapeCreateImageFailed);
return false;
}
++m_textureImageEpoch; // a new attachment image invalidates cached render passes
@@ -2340,7 +2362,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (preserve) {
FlushPendingUploads();
if (!PreserveTextureContentsOnRecreate(m_device, m_commandPool, m_graphicsQueue,
resource, replacement, true)) return false;
resource, replacement, true)) {
MGL_WIRE_DECLINE_AT(ShapePreserveCopyFailed,
"texture {slot=%u, gen=%u}: its texels could not be copied into the "
"reallocated image",
desc.Resource.Slot, desc.Resource.Gen);
return false;
}
}
DeferResourceRelease(Move(resource));
std::destroy_at(&resource);
@@ -2412,11 +2440,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// NOT `incomplete`: this arm reports SUCCESS while leaving the entry pending,
// so the draw proceeds against texels that were never uploaded. Counted
// separately for exactly that reason (B3).
WireDeclineTally::Count(WireDeclineSite::UploadLevelExtentMismatch);
MGL_WIRE_DECLINE_AT(UploadLevelExtentMismatch,
"texture {slot=%u, gen=%u} level %u: the staged extent does not match the "
"image's extent for that level; left pending and NOT uploaded",
handle.Slot, handle.Gen, static_cast<Uint32>(level));
continue;
}
if (texelSize.x() <= 0 || texelSize.y() <= 0 || byteSize == 0) {
WireDeclineTally::Count(WireDeclineSite::UploadEmptyLevel);
MGL_WIRE_DECLINE_AT(UploadEmptyLevel, "texture {slot=%u, gen=%u} level %u is empty",
handle.Slot, handle.Gen, static_cast<Uint32>(level));
incomplete = true;
continue;
}
@@ -2445,7 +2477,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
(resource.aspect & VK_IMAGE_ASPECT_STENCIL_BIT);
if (vkuFormatIsCompressed(resource.format) || !levelTexels ||
item.byteSize % levelTexels != 0 || item.byteSize / levelTexels == 0) {
WireDeclineTally::Count(WireDeclineSite::UploadTexelArithmetic);
MGL_WIRE_DECLINE_AT(UploadTexelArithmetic,
"texture {slot=%u, gen=%u} level %u: compressed, or its byte size is not a "
"whole number of texels",
handle.Slot, handle.Gen, static_cast<Uint32>(level));
incomplete = true;
continue;
}
@@ -2465,7 +2500,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (offset[axis] < 0 || extent[axis] <= 0 ||
static_cast<Uint64>(offset[axis]) + static_cast<Uint64>(extent[axis]) >
static_cast<Uint64>(texelSize[axis])) {
WireDeclineTally::Count(WireDeclineSite::UploadRegionOutOfBounds);
MGL_WIRE_DECLINE_AT(UploadRegionOutOfBounds,
"texture {slot=%u, gen=%u} level %u: a staged region lies outside "
"the level",
handle.Slot, handle.Gen, static_cast<Uint32>(level));
incomplete = true;
return;
}
@@ -2521,7 +2559,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
WireDeclineTally::SetPendingGauge(static_cast<Uint64>(record.PendingUploads.size()), pendingBytes);
}
if (incomplete) {
WireDeclineTally::Count(WireDeclineSite::UploadIncomplete);
MGL_WIRE_DECLINE_AT(UploadIncomplete,
"texture {slot=%u, gen=%u}: at least one pending level could not be staged; "
"every draw that samples it is declined until it can",
handle.Slot, handle.Gen);
return false;
}
if (items.empty()) {
@@ -2533,7 +2574,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// otherwise Draw(old T), TexSubImage(T), Draw(new T) can upload before
// the first draw. A clean texture never reaches this submission boundary.
if (pVulkanRenderer && !pVulkanRenderer->FlushWirePendingCommandsForTextureUpdate()) {
WireDeclineTally::Count(WireDeclineSite::UploadFlushFailed);
MGL_WIRE_DECLINE_AT(UploadFlushFailed,
"texture {slot=%u, gen=%u}: earlier renderer work could not be submitted before "
"its upload batch",
handle.Slot, handle.Gen);
return false;
}
@@ -2690,7 +2734,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkFormat viewFormat = VK_FORMAT_UNDEFINED;
const auto storage = ResolveWireTextureStorage(handle, level, layer, &viewFormat);
if (MG_Pipe::MGPipeHandleIsNull(storage)) {
WireDeclineTally::Count(WireDeclineSite::TexResolveStorage);
MGL_WIRE_DECLINE_AT(TexResolveStorage,
"texture {slot=%u, gen=%u}: no storage owner resolved", handle.Slot,
handle.Gen);
return nullptr;
}
if (storage != handle) {
@@ -2701,6 +2747,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
vkuFormatCompatibilityClass(viewFormat) != vkuFormatCompatibilityClass(resource->format))) {
MGLOG_E_ONCE("Magma wire view: format %d cannot reinterpret storage format %d",
static_cast<Int>(viewFormat), static_cast<Int>(resource->format));
WireDeclineTally::Count(WireDeclineSite::TexViewFormat);
return nullptr;
}
return resource;
@@ -2711,6 +2758,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (handle.Slot >= records.size()) {
MGLOG_E_ONCE("Magma wire texture {slot=%u, gen=%u}: no applier record at that slot; declined",
handle.Slot, handle.Gen);
WireDeclineTally::Count(WireDeclineSite::TexNoApplierRecord);
return nullptr;
}
auto& record = records[handle.Slot];
@@ -2720,6 +2768,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
MGLOG_E_ONCE("Magma wire texture {slot=%u, gen=%u}: the applier's record is %s; declined",
handle.Slot, handle.Gen,
!record.Live ? "dead" : "of another generation");
WireDeclineTally::Count(WireDeclineSite::TexRecordDeadOrStale);
return nullptr;
}
const Uint64 key = MG_Remote::Server::StagedTextureStore::KeyForHandle(handle);
@@ -2731,15 +2780,22 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return &resource;
}
if (!SyncWireTextureShape(record, resource, requireStorage)) {
WireDeclineTally::Count(WireDeclineSite::TexShapeSync);
MGL_WIRE_DECLINE_AT(TexShapeSync, "texture {slot=%u, gen=%u}: no backing image; see the Shape* tally",
handle.Slot, handle.Gen);
return nullptr;
}
if (!renderbuffer && !UploadPendingWireLevels(handle, record, resource)) {
WireDeclineTally::Count(WireDeclineSite::TexUploadPendingLevels);
MGL_WIRE_DECLINE_AT(TexUploadPendingLevels,
"texture {slot=%u, gen=%u}: pending levels could not be uploaded; see the "
"Upload* tally",
handle.Slot, handle.Gen);
return nullptr;
}
if (requireStorage && (resource.usageFlags & VK_IMAGE_USAGE_STORAGE_BIT) == 0) {
WireDeclineTally::Count(WireDeclineSite::TexStorageUsage);
MGL_WIRE_DECLINE_AT(TexStorageUsage,
"texture {slot=%u, gen=%u} is bound as a shader image but its allocation has no "
"STORAGE usage",
handle.Slot, handle.Gen);
return nullptr;
}
resource.syncedWireSerial = record.Serial;
@@ -20,6 +20,13 @@
// counts, so "which exit did this frame take" is answerable from a counter dump instead of
// from a bisect. Rows are append-only: the integration lane asserts specific counters moved.
//
// EVERY ROW HAS A SITE AND EVERY SITE HAS A LINE. A row with no site is a name that can never
// be counted - it reads as "this cannot happen here" when it only means "nobody wired it up" -
// and a site with no line puts the reader back on a bisect. Sites reached through a macro
// (MGL_WIRE_DECLINE_AT) get both at once; the few that use WireDeclineTally::Count directly do
// so ONLY because the statement above them already carries the same reason as an _ONCE log.
// scripts/ci/wire_declines_audit.py holds both halves.
//
// MGL_WIRE_DECLINE(Name)
// ---- SetupWireDraw, in statement order -------------------------------------------------
@@ -88,3 +95,6 @@ MGL_WIRE_DECLINE(ShapeSampleCount)
MGL_WIRE_DECLINE(ShapePreserveFlushFailed)
MGL_WIRE_DECLINE(ShapeImageFlagsUnsupported)
MGL_WIRE_DECLINE(ShapeCreateImageFailed)
// Appended by the B3 review round: the preserve-copy arm of a reallocation was the one exit in
// SyncWireTextureShape still leaving no trace at all.
MGL_WIRE_DECLINE(ShapePreserveCopyFailed)
+100
View File
@@ -0,0 +1,100 @@
#!/usr/bin/env python3
"""P7 wave 2 package B3: WireDeclines.def rows and their sites must agree.
CONTRACT-P7 §0 rule I admits two shapes for a wire-arm refusal, a decline and a named Fatal.
The decline half is only real if:
(1) every row in WireDeclines.def is actually counted somewhere - a row with no site is a
name that can never move, which reads in a report as "this cannot happen here" when it
only means "nobody wired it up"; and
(2) every site emits a line - a counter with no log puts the next reader back on a bisect,
which is the thing the package exists to remove.
Rows are APPEND-ONLY (the lanes assert specific counters), so (1) cannot be fixed later by
deleting a row once a release has shipped it. This check is why it cannot rot.
Sites reached through MGL_WIRE_DECLINE_AT satisfy both at once. A bare
WireDeclineTally::Count(WireDeclineSite::X) is allowed ONLY when an MGLOG_ statement carrying
the same reason already stands just above it.
usage: wire_declines_audit.py [repo-root]
"""
import os
import re
import sys
RENDERER = "MobileGL/MG_Backend/DirectVulkan/Renderer"
DEF = os.path.join(RENDERER, "WireDeclines.def")
SOURCES = (".cpp", ".inc", ".h")
# How far above a bare Count() an MGLOG_ may stand and still be "the line for this site".
LOG_WINDOW = 8
ROW = re.compile(r"^\s*MGL_WIRE_DECLINE\(([A-Za-z][A-Za-z0-9]*)\)\s*$")
AT_SITE = re.compile(r"MGL_WIRE_DECLINE_AT\(\s*([A-Za-z][A-Za-z0-9]*)\s*,")
COUNT_SITE = re.compile(r"WireDeclineTally::Count\(\s*WireDeclineSite::([A-Za-z][A-Za-z0-9]*)\s*\)")
# Warning or error only: an MGLOG_D / MGLOG_V is compiled away in the Release builds that ship,
# so it is not a line anybody can read off a device.
LOGGED = re.compile(r"MGLOG_[WE](_ONCE)?\s*\(")
def main() -> int:
root = os.path.abspath(sys.argv[1] if len(sys.argv) > 1 else ".")
def_path = os.path.join(root, DEF)
if not os.path.isfile(def_path):
print("wire-declines: %s not found" % DEF, file=sys.stderr)
return 2
rows = []
for line in open(def_path, encoding="utf-8"):
m = ROW.match(line)
if m:
rows.append(m.group(1))
if len(rows) != len(set(rows)):
dupes = sorted({r for r in rows if rows.count(r) > 1})
print("wire-declines: duplicate rows: %s" % ", ".join(dupes), file=sys.stderr)
return 1
counted = {} # name -> [(file, line)]
unlogged_counts = [] # (file, line, name)
src_dir = os.path.join(root, RENDERER)
for entry in sorted(os.listdir(src_dir)):
if not entry.endswith(SOURCES):
continue
# The header that DEFINES the macro and the enum names them all without being a site.
if entry == "WireDeclineTally.h":
continue
path = os.path.join(src_dir, entry)
lines = open(path, encoding="utf-8", errors="replace").read().splitlines()
for i, line in enumerate(lines):
for m in AT_SITE.finditer(line):
counted.setdefault(m.group(1), []).append((entry, i + 1))
for m in COUNT_SITE.finditer(line):
name = m.group(1)
counted.setdefault(name, []).append((entry, i + 1))
window = lines[max(0, i - LOG_WINDOW):i]
if not any(LOGGED.search(w) for w in window):
unlogged_counts.append((entry, i + 1, name))
orphan_rows = [r for r in rows if r not in counted]
unknown_sites = sorted(set(counted) - set(rows))
for name in orphan_rows:
print("wire-declines: row %s has NO SITE - it can never be counted" % name, file=sys.stderr)
for name in unknown_sites:
for where in counted[name]:
print("wire-declines: %s:%d counts %s, which is not a row in WireDeclines.def"
% (where[0], where[1], name), file=sys.stderr)
for entry, line, name in unlogged_counts:
print("wire-declines: %s:%d counts %s with no MGLOG_ within %d lines above it - a decline "
"must be readable from the log, not only from a counter"
% (entry, line, name, LOG_WINDOW), file=sys.stderr)
bad = len(orphan_rows) + len(unknown_sites) + len(unlogged_counts)
print("wire-declines: %d rows, %d with sites, %d site(s) total, %d unlogged, %d unknown"
% (len(rows), len(rows) - len(orphan_rows), sum(len(v) for v in counted.values()),
len(unlogged_counts), len(unknown_sites)))
return 1 if bad else 0
if __name__ == "__main__":
sys.exit(main())