checkStd140Offsets function
Holds a packer's std140 arithmetic against glslang's own decorations.
Two independent answers to the same question. The packer computes offsets
from the GLSL because the reflection has to exist before there is any WGSL
to read it out of; glslang writes them into the SPIR-V because that is what
std140 means. They agree here or the build stops.
A block the listing does not mention at all is the sharper failure. It
means the reflection claims a block the compiled shader does not have —
which happens the moment a #ifndef guard is read wrongly, and which the
engine turns into a bind of a slot that is not there. lib/surface.glsl
records what that costs on the other two backends: a native crash inside
Metal, and a draw discarded with nothing logged.
Shared by the two programs beside this file rather than written in the one that needed it first. A bundle somebody else packs is exactly where a block laid out by a rule nobody checked would land, so the loadable path wants this check more than the engine's own table does — and two copies of it would be two rules, one of which nobody would notice going stale.
Implementation
void checkStd140Offsets(
String name,
PreparedStage prepared,
Map<String, Map<String, int>> offsets,
) {
for (final block in prepared.blocks) {
final theirs = offsets[block.name];
if (theirs == null) {
throw WgslSectionException(
'$name: the reflection has the block "${block.name}" and the compiled '
'shader does not',
);
}
for (final member in block.members) {
final expected = theirs[member.name];
if (expected == null) {
throw WgslSectionException(
'$name: "${block.name}.${member.name}" is in the reflection and not '
'in the compiled block',
);
}
if (expected != member.offsetInBytes) {
throw WgslSectionException(
'$name: "${block.name}.${member.name}" is at ${member.offsetInBytes} '
'by std140 and at $expected in the SPIR-V',
);
}
}
}
}