encodeEtc2Rgb8 function
Encodes image as ETC2 RGB8 (vkFormat.etc2R8g8b8UNormBlock): one
8-byte block per 4×4 tile.
The ETC1-compatible subset of ETC2's modes, not the full format. ETC2 added three block modes over ETC1 — "T", "H" and "planar" — each a different trade of precision for a specific kind of block (planar for a smooth gradient across all sixteen texels, T/H for a block with one colour cutting sharply through the rest). None is required: every ETC2 decoder — hardware or software — already accepts an ETC1 block as valid ETC2, because ETC2 is specified as a strict superset. What is given up is some quality on the specific block shapes those modes target, not correctness or conformance; picking the better of ETC1's own two modes (individual, differential) per block, done here, is most of the way there and a fraction of the code.
Individual or differential mode, chosen per block, never the flip bit's alternative split. Both encode two 4×2 sub-blocks stacked vertically (rows 0–1, rows 2–3) — flip = 0 throughout — rather than ever trying the 2×4 side-by-side split flip = 1 offers. A real encoder gets a little more from choosing per block; this one accepts the loss for the same reason it skips T/H/planar: the two vertical halves already carry two independent base colours and tables, which is most of what a smarter split would add.
Implementation
Uint8List encodeEtc2Rgb8(Rgba8Image image) {
requireWholeBlocks(image, 'encodeEtc2Rgb8');
final blocksX = image.width ~/ 4;
final blocksY = image.height ~/ 4;
final out = Uint8List(blocksX * blocksY * 8);
var offset = 0;
for (var by = 0; by < blocksY; by++) {
for (var bx = 0; bx < blocksX; bx++) {
final block = encodeEtc2Rgb8Block(readBlock(image, bx, by));
out.setRange(offset, offset + 8, block);
offset += 8;
}
}
return out;
}