gpuBlockLayoutOf function

({int byteLength, int bytesPerRow, int rowsPerImage}) gpuBlockLayoutOf(
  1. TextureFormat format,
  2. int width,
  3. int height
)

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);
}