gpuBlockLayoutOf function
The row stride and the number of rows writeTexture wants for a
width by height level of a block-compressed format.
Both numbers are counted in blocks, and that is the whole trap. For
rgba8unorm the stride is a row of texels and rowsPerImage is a row count;
for bc7-rgba-unorm the stride is a row of blocks — sixteen bytes each
covering four texels across — and rowsPerImage counts block rows, so a
64x64 BC7 level is sixteen rows of 256 bytes rather than sixty-four rows of
anything. Handed the texel arithmetic instead, the browser reads four times
the bytes that exist and refuses the write.
The rounding is up, which is what makes a chain work: level three of a 32x32 ASTC 8x8 texture is 4x4 texels and still one whole block, and a level smaller than a block is stored as one block whatever it covers.
Implementation
({int bytesPerRow, int rowsPerImage, int byteLength}) gpuBlockLayoutOf(
TextureFormat format,
int width,
int height,
) {
final block = format.blockLayout;
final wide = (width + block.blockWidth - 1) ~/ block.blockWidth;
final high = (height + block.blockHeight - 1) ~/ block.blockHeight;
final stride = wide * block.bytesPerBlock;
return (bytesPerRow: stride, rowsPerImage: high, byteLength: stride * high);
}