ktx2FileReader.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. import { DataReader } from './Misc/dataReader';
  2. import { sourceTextureFormat } from './transcoder';
  3. /** @hidden */
  4. export enum SupercompressionScheme {
  5. None = 0,
  6. BasisLZ = 1,
  7. ZStandard = 2,
  8. ZLib = 3
  9. }
  10. const enum DFDModel {
  11. ETC1S = 163,
  12. UASTC = 166
  13. }
  14. const enum DFDChannel_ETC1S {
  15. RGB = 0,
  16. RRR = 3,
  17. GGG = 4,
  18. AAA = 15
  19. }
  20. const enum DFDChannel_UASTC {
  21. RGB = 0,
  22. RGBA = 3,
  23. RRR = 4,
  24. RRRG = 5
  25. }
  26. const enum DFDTransferFunction {
  27. linear = 1,
  28. sRGB = 2
  29. }
  30. /** @hidden */
  31. export interface IKTX2_Header {
  32. vkFormat: number;
  33. typeSize: number;
  34. pixelWidth: number;
  35. pixelHeight: number;
  36. pixelDepth: number;
  37. layerCount: number;
  38. faceCount: number;
  39. levelCount: number;
  40. supercompressionScheme: number;
  41. dfdByteOffset: number;
  42. dfdByteLength: number;
  43. kvdByteOffset: number;
  44. kvdByteLength: number;
  45. sgdByteOffset: number;
  46. sgdByteLength: number;
  47. }
  48. /** @hidden */
  49. export interface IKTX2_Level {
  50. byteOffset: number;
  51. byteLength: number;
  52. uncompressedByteLength: number;
  53. }
  54. interface IKTX2_Sample {
  55. bitOffset: number;
  56. bitLength: number;
  57. channelType: number;
  58. channelFlags: number;
  59. samplePosition: number[];
  60. sampleLower: number;
  61. sampleUpper: number;
  62. }
  63. /** @hidden */
  64. export interface IKTX2_DFD {
  65. vendorId: number;
  66. descriptorType: number;
  67. versionNumber: number;
  68. descriptorBlockSize: number;
  69. colorModel: number;
  70. colorPrimaries: number;
  71. transferFunction: number;
  72. flags: number;
  73. texelBlockDimension: {
  74. x: number;
  75. y: number;
  76. z: number;
  77. w: number;
  78. };
  79. bytesPlane: Array<number>;
  80. numSamples: number;
  81. samples: Array<IKTX2_Sample>;
  82. }
  83. /** @hidden */
  84. export interface IKTX2_ImageDesc {
  85. imageFlags: number;
  86. rgbSliceByteOffset: number;
  87. rgbSliceByteLength: number;
  88. alphaSliceByteOffset: number;
  89. alphaSliceByteLength: number;
  90. }
  91. /** @hidden */
  92. export interface IKTX2_SupercompressionGlobalData {
  93. endpointCount?: number;
  94. selectorCount?: number;
  95. endpointsByteLength?: number;
  96. selectorsByteLength?: number;
  97. tablesByteLength?: number;
  98. extendedByteLength?: number;
  99. imageDescs?: Array<IKTX2_ImageDesc>;
  100. endpointsData?: Uint8Array;
  101. selectorsData?: Uint8Array;
  102. tablesData?: Uint8Array;
  103. extendedData?: Uint8Array;
  104. }
  105. export class KTX2FileReader {
  106. private _data: Uint8Array;
  107. private _header: IKTX2_Header;
  108. private _levels: Array<IKTX2_Level>;
  109. private _dfdBlock: IKTX2_DFD;
  110. private _supercompressionGlobalData: IKTX2_SupercompressionGlobalData;
  111. /**
  112. * Will throw an exception if the file can't be parsed
  113. */
  114. constructor(data: Uint8Array) {
  115. this._data = data;
  116. }
  117. public get data(): Uint8Array {
  118. return this._data;
  119. }
  120. public get header(): IKTX2_Header {
  121. return this._header;
  122. }
  123. public get levels(): Array<IKTX2_Level> {
  124. return this._levels;
  125. }
  126. public get dfdBlock(): IKTX2_DFD {
  127. return this._dfdBlock;
  128. }
  129. public get supercompressionGlobalData(): IKTX2_SupercompressionGlobalData {
  130. return this._supercompressionGlobalData;
  131. }
  132. public isValid() {
  133. return KTX2FileReader.IsValid(this._data);
  134. }
  135. public parse() {
  136. let offsetInFile = 12; // skip the header
  137. /**
  138. * Get the header
  139. */
  140. const hdrReader = new DataReader(this._data, offsetInFile, 17 * 4);
  141. const header = this._header = {
  142. vkFormat: hdrReader.readUint32(),
  143. typeSize: hdrReader.readUint32(),
  144. pixelWidth: hdrReader.readUint32(),
  145. pixelHeight: hdrReader.readUint32(),
  146. pixelDepth: hdrReader.readUint32(),
  147. layerCount: hdrReader.readUint32(),
  148. faceCount: hdrReader.readUint32(),
  149. levelCount: hdrReader.readUint32(),
  150. supercompressionScheme: hdrReader.readUint32(),
  151. dfdByteOffset: hdrReader.readUint32(),
  152. dfdByteLength: hdrReader.readUint32(),
  153. kvdByteOffset: hdrReader.readUint32(),
  154. kvdByteLength: hdrReader.readUint32(),
  155. sgdByteOffset: hdrReader.readUint64(),
  156. sgdByteLength: hdrReader.readUint64(),
  157. };
  158. if (header.pixelDepth > 0) {
  159. throw new Error(`Failed to parse KTX2 file - Only 2D textures are currently supported.`);
  160. }
  161. if (header.layerCount > 1) {
  162. throw new Error(`Failed to parse KTX2 file - Array textures are not currently supported.`);
  163. }
  164. if (header.faceCount > 1) {
  165. throw new Error(`Failed to parse KTX2 file - Cube textures are not currently supported.`);
  166. }
  167. offsetInFile += hdrReader.byteOffset;
  168. /**
  169. * Get the levels
  170. */
  171. let levelCount = Math.max(1, header.levelCount);
  172. const levelReader = new DataReader(this._data, offsetInFile, levelCount * 3 * (2 * 4));
  173. const levels: Array<IKTX2_Level> = this._levels = [];
  174. while (levelCount--) {
  175. levels.push({
  176. byteOffset: levelReader.readUint64(),
  177. byteLength: levelReader.readUint64(),
  178. uncompressedByteLength: levelReader.readUint64(),
  179. });
  180. }
  181. offsetInFile += levelReader.byteOffset;
  182. /**
  183. * Get the data format descriptor (DFD) blocks
  184. */
  185. const dfdReader = new DataReader(this._data, header.dfdByteOffset, header.dfdByteLength);
  186. const dfdBlock = this._dfdBlock = {
  187. vendorId: dfdReader.skipBytes(4 /* skip totalSize */).readUint16(),
  188. descriptorType: dfdReader.readUint16(),
  189. versionNumber: dfdReader.readUint16(),
  190. descriptorBlockSize: dfdReader.readUint16(),
  191. colorModel: dfdReader.readUint8(),
  192. colorPrimaries: dfdReader.readUint8(),
  193. transferFunction: dfdReader.readUint8(),
  194. flags: dfdReader.readUint8(),
  195. texelBlockDimension: {
  196. x: dfdReader.readUint8() + 1,
  197. y: dfdReader.readUint8() + 1,
  198. z: dfdReader.readUint8() + 1,
  199. w: dfdReader.readUint8() + 1,
  200. },
  201. bytesPlane: [
  202. dfdReader.readUint8(), /* bytesPlane0 */
  203. dfdReader.readUint8(), /* bytesPlane1 */
  204. dfdReader.readUint8(), /* bytesPlane2 */
  205. dfdReader.readUint8(), /* bytesPlane3 */
  206. dfdReader.readUint8(), /* bytesPlane4 */
  207. dfdReader.readUint8(), /* bytesPlane5 */
  208. dfdReader.readUint8(), /* bytesPlane6 */
  209. dfdReader.readUint8(), /* bytesPlane7 */
  210. ],
  211. numSamples: 0,
  212. samples: new Array<IKTX2_Sample>(),
  213. };
  214. dfdBlock.numSamples = (dfdBlock.descriptorBlockSize - 24) / 16;
  215. for (let i = 0; i < dfdBlock.numSamples; i++) {
  216. const sample = {
  217. bitOffset: dfdReader.readUint16(),
  218. bitLength: dfdReader.readUint8() + 1,
  219. channelType: dfdReader.readUint8(),
  220. channelFlags: 0,
  221. samplePosition: [
  222. dfdReader.readUint8(), /* samplePosition0 */
  223. dfdReader.readUint8(), /* samplePosition1 */
  224. dfdReader.readUint8(), /* samplePosition2 */
  225. dfdReader.readUint8(), /* samplePosition3 */
  226. ],
  227. sampleLower: dfdReader.readUint32(),
  228. sampleUpper: dfdReader.readUint32(),
  229. };
  230. sample.channelFlags = (sample.channelType & 0xF0) >> 4;
  231. sample.channelType = sample.channelType & 0x0F;
  232. dfdBlock.samples.push(sample);
  233. }
  234. /**
  235. * Get the Supercompression Global Data (sgd)
  236. */
  237. const sgd: IKTX2_SupercompressionGlobalData = this._supercompressionGlobalData = {};
  238. if (header.sgdByteLength > 0) {
  239. const sgdReader = new DataReader(this._data, header.sgdByteOffset, header.sgdByteLength);
  240. sgd.endpointCount = sgdReader.readUint16();
  241. sgd.selectorCount = sgdReader.readUint16();
  242. sgd.endpointsByteLength = sgdReader.readUint32();
  243. sgd.selectorsByteLength = sgdReader.readUint32();
  244. sgd.tablesByteLength = sgdReader.readUint32();
  245. sgd.extendedByteLength = sgdReader.readUint32();
  246. sgd.imageDescs = [];
  247. const imageCount = this._getImageCount();
  248. for (let i = 0; i < imageCount; i ++) {
  249. sgd.imageDescs.push({
  250. imageFlags: sgdReader.readUint32(),
  251. rgbSliceByteOffset: sgdReader.readUint32(),
  252. rgbSliceByteLength: sgdReader.readUint32(),
  253. alphaSliceByteOffset: sgdReader.readUint32(),
  254. alphaSliceByteLength: sgdReader.readUint32(),
  255. });
  256. }
  257. const endpointsByteOffset = header.sgdByteOffset + sgdReader.byteOffset;
  258. const selectorsByteOffset = endpointsByteOffset + sgd.endpointsByteLength;
  259. const tablesByteOffset = selectorsByteOffset + sgd.selectorsByteLength;
  260. const extendedByteOffset = tablesByteOffset + sgd.tablesByteLength;
  261. sgd.endpointsData = new Uint8Array(this._data.buffer, this._data.byteOffset + endpointsByteOffset, sgd.endpointsByteLength);
  262. sgd.selectorsData = new Uint8Array(this._data.buffer, this._data.byteOffset + selectorsByteOffset, sgd.selectorsByteLength);
  263. sgd.tablesData = new Uint8Array(this._data.buffer, this._data.byteOffset + tablesByteOffset, sgd.tablesByteLength);
  264. sgd.extendedData = new Uint8Array(this._data.buffer, this._data.byteOffset + extendedByteOffset, sgd.extendedByteLength);
  265. }
  266. }
  267. private _getImageCount(): number {
  268. let layerPixelDepth = Math.max(this._header.pixelDepth, 1);
  269. for (let i = 1; i < this._header.levelCount; i++) {
  270. layerPixelDepth += Math.max(this._header.pixelDepth >> i, 1);
  271. }
  272. return Math.max(this._header.layerCount, 1) * this._header.faceCount * layerPixelDepth;
  273. }
  274. public get textureFormat(): sourceTextureFormat {
  275. return this._dfdBlock.colorModel === DFDModel.UASTC ? sourceTextureFormat.UASTC4x4 : sourceTextureFormat.ETC1S;
  276. }
  277. public get hasAlpha(): boolean {
  278. const tformat = this.textureFormat;
  279. switch (tformat) {
  280. case sourceTextureFormat.ETC1S:
  281. return this._dfdBlock.numSamples === 2 && (this._dfdBlock.samples[0].channelType === DFDChannel_ETC1S.AAA || this._dfdBlock.samples[1].channelType === DFDChannel_ETC1S.AAA);
  282. case sourceTextureFormat.UASTC4x4:
  283. return this._dfdBlock.samples[0].channelType === DFDChannel_UASTC.RGBA;
  284. }
  285. return false;
  286. }
  287. public get needZSTDDecoder(): boolean {
  288. return this._header.supercompressionScheme === SupercompressionScheme.ZStandard;
  289. }
  290. public get isInGammaSpace(): boolean {
  291. return this._dfdBlock.transferFunction === DFDTransferFunction.sRGB;
  292. }
  293. public static IsValid(data: ArrayBufferView): boolean {
  294. if (data.byteLength >= 12) {
  295. // '«', 'K', 'T', 'X', ' ', '2', '0', '»', '\r', '\n', '\x1A', '\n'
  296. const identifier = new Uint8Array(data.buffer, data.byteOffset, 12);
  297. if (identifier[0] === 0xAB && identifier[1] === 0x4B && identifier[2] === 0x54 && identifier[3] === 0x58 && identifier[4] === 0x20 && identifier[5] === 0x32 &&
  298. identifier[6] === 0x30 && identifier[7] === 0xBB && identifier[8] === 0x0D && identifier[9] === 0x0A && identifier[10] === 0x1A && identifier[11] === 0x0A) {
  299. return true;
  300. }
  301. }
  302. return false;
  303. }
  304. }