transcodeKTX2.js 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. /* global require */
  2. import defined from "../Core/defined.js";
  3. import Check from "../Core/Check.js";
  4. import PixelFormat from "../Core/PixelFormat.js";
  5. import RuntimeError from "../Core/RuntimeError.js";
  6. import VulkanConstants from "../Core//VulkanConstants.js";
  7. import PixelDatatype from "../Renderer/PixelDatatype.js";
  8. import createTaskProcessorWorker from "./createTaskProcessorWorker.js";
  9. import ktx_parse from "../ThirdParty/ktx-parse.js";
  10. const faceOrder = [
  11. "positiveX",
  12. "negativeX",
  13. "positiveY",
  14. "negativeY",
  15. "positiveZ",
  16. "negativeZ",
  17. ];
  18. // Flags
  19. const colorModelETC1S = 163;
  20. const colorModelUASTC = 166;
  21. let transcoderModule;
  22. function transcode(parameters, transferableObjects) {
  23. //>>includeStart('debug', pragmas.debug);
  24. Check.typeOf.object("transcoderModule", transcoderModule);
  25. //>>includeEnd('debug');
  26. const data = parameters.ktx2Buffer;
  27. const supportedTargetFormats = parameters.supportedTargetFormats;
  28. let header;
  29. try {
  30. header = ktx_parse(data);
  31. } catch (e) {
  32. throw new RuntimeError("Invalid KTX2 file.");
  33. }
  34. if (header.layerCount !== 0) {
  35. throw new RuntimeError("KTX2 texture arrays are not supported.");
  36. }
  37. if (header.pixelDepth !== 0) {
  38. throw new RuntimeError("KTX2 3D textures are unsupported.");
  39. }
  40. const dfd = header.dataFormatDescriptor[0];
  41. const result = new Array(header.levelCount);
  42. if (
  43. header.vkFormat === 0x0 &&
  44. (dfd.colorModel === colorModelETC1S || dfd.colorModel === colorModelUASTC)
  45. ) {
  46. // Compressed, initialize transcoder module
  47. transcodeCompressed(
  48. data,
  49. header,
  50. supportedTargetFormats,
  51. transcoderModule,
  52. transferableObjects,
  53. result
  54. );
  55. } else {
  56. transferableObjects.push(data.buffer);
  57. parseUncompressed(header, result);
  58. }
  59. return result;
  60. }
  61. // Parser for uncompressed
  62. function parseUncompressed(header, result) {
  63. const internalFormat =
  64. header.vkFormat === VulkanConstants.VK_FORMAT_R8G8B8_SRGB
  65. ? PixelFormat.RGB
  66. : PixelFormat.RGBA;
  67. let datatype;
  68. if (header.vkFormat === VulkanConstants.VK_FORMAT_R8G8B8A8_UNORM) {
  69. datatype = PixelDatatype.UNSIGNED_BYTE;
  70. } else if (
  71. header.vkFormat === VulkanConstants.VK_FORMAT_R16G16B16A16_SFLOAT
  72. ) {
  73. datatype = PixelDatatype.HALF_FLOAT;
  74. } else if (
  75. header.vkFormat === VulkanConstants.VK_FORMAT_R32G32B32A32_SFLOAT
  76. ) {
  77. datatype = PixelDatatype.FLOAT;
  78. }
  79. for (let i = 0; i < header.levels.length; ++i) {
  80. const level = {};
  81. result[i] = level;
  82. const levelBuffer = header.levels[i].levelData;
  83. const width = header.pixelWidth >> i;
  84. const height = header.pixelHeight >> i;
  85. const faceLength =
  86. width * height * PixelFormat.componentsLength(internalFormat);
  87. for (let j = 0; j < header.faceCount; ++j) {
  88. // multiply levelBuffer.byteOffset by the size in bytes of the pixel data type
  89. const faceByteOffset =
  90. levelBuffer.byteOffset + faceLength * header.typeSize * j;
  91. let faceView;
  92. if (!defined(datatype) || PixelDatatype.sizeInBytes(datatype) === 1) {
  93. faceView = new Uint8Array(
  94. levelBuffer.buffer,
  95. faceByteOffset,
  96. faceLength
  97. );
  98. } else if (PixelDatatype.sizeInBytes(datatype) === 2) {
  99. faceView = new Uint16Array(
  100. levelBuffer.buffer,
  101. faceByteOffset,
  102. faceLength
  103. );
  104. } else {
  105. faceView = new Float32Array(
  106. levelBuffer.buffer,
  107. faceByteOffset,
  108. faceLength
  109. );
  110. }
  111. level[faceOrder[j]] = {
  112. internalFormat: internalFormat,
  113. datatype: datatype,
  114. width: width,
  115. height: height,
  116. levelBuffer: faceView,
  117. };
  118. }
  119. }
  120. }
  121. function transcodeCompressed(
  122. data,
  123. header,
  124. supportedTargetFormats,
  125. transcoderModule,
  126. transferableObjects,
  127. result
  128. ) {
  129. const ktx2File = new transcoderModule.KTX2File(data);
  130. let width = ktx2File.getWidth();
  131. let height = ktx2File.getHeight();
  132. const levels = ktx2File.getLevels();
  133. const hasAlpha = ktx2File.getHasAlpha();
  134. if (!(width > 0) || !(height > 0) || !(levels > 0)) {
  135. ktx2File.close();
  136. ktx2File.delete();
  137. throw new RuntimeError("Invalid KTX2 file");
  138. }
  139. let internalFormat, transcoderFormat;
  140. const dfd = header.dataFormatDescriptor[0];
  141. const BasisFormat = transcoderModule.transcoder_texture_format;
  142. // Determine target format based on platform support
  143. if (dfd.colorModel === colorModelETC1S) {
  144. if (supportedTargetFormats.etc) {
  145. internalFormat = hasAlpha
  146. ? PixelFormat.RGBA8_ETC2_EAC
  147. : PixelFormat.RGB8_ETC2;
  148. transcoderFormat = hasAlpha
  149. ? BasisFormat.cTFETC2_RGBA
  150. : BasisFormat.cTFETC1_RGB;
  151. } else if (supportedTargetFormats.etc1 && !hasAlpha) {
  152. internalFormat = PixelFormat.RGB_ETC1;
  153. transcoderFormat = BasisFormat.cTFETC1_RGB;
  154. } else if (supportedTargetFormats.s3tc) {
  155. internalFormat = hasAlpha ? PixelFormat.RGBA_DXT5 : PixelFormat.RGB_DXT1;
  156. transcoderFormat = hasAlpha
  157. ? BasisFormat.cTFBC3_RGBA
  158. : BasisFormat.cTFBC1_RGB;
  159. } else if (supportedTargetFormats.pvrtc) {
  160. internalFormat = hasAlpha
  161. ? PixelFormat.RGBA_PVRTC_4BPPV1
  162. : PixelFormat.RGB_PVRTC_4BPPV1;
  163. transcoderFormat = hasAlpha
  164. ? BasisFormat.cTFPVRTC1_4_RGBA
  165. : BasisFormat.cTFPVRTC1_4_RGB;
  166. } else if (supportedTargetFormats.astc) {
  167. internalFormat = PixelFormat.RGBA_ASTC;
  168. transcoderFormat = BasisFormat.cTFASTC_4x4_RGBA;
  169. } else if (supportedTargetFormats.bc7) {
  170. internalFormat = PixelFormat.RGBA_BC7;
  171. transcoderFormat = BasisFormat.cTFBC7_RGBA;
  172. } else {
  173. throw new RuntimeError(
  174. "No transcoding format target available for ETC1S compressed ktx2."
  175. );
  176. }
  177. } else if (dfd.colorModel === colorModelUASTC) {
  178. if (supportedTargetFormats.astc) {
  179. internalFormat = PixelFormat.RGBA_ASTC;
  180. transcoderFormat = BasisFormat.cTFASTC_4x4_RGBA;
  181. } else if (supportedTargetFormats.bc7) {
  182. internalFormat = PixelFormat.RGBA_BC7;
  183. transcoderFormat = BasisFormat.cTFBC7_RGBA;
  184. } else if (supportedTargetFormats.s3tc) {
  185. internalFormat = hasAlpha ? PixelFormat.RGBA_DXT5 : PixelFormat.RGB_DXT1;
  186. transcoderFormat = hasAlpha
  187. ? BasisFormat.cTFBC3_RGBA
  188. : BasisFormat.cTFBC1_RGB;
  189. } else if (supportedTargetFormats.etc) {
  190. internalFormat = hasAlpha
  191. ? PixelFormat.RGBA8_ETC2_EAC
  192. : PixelFormat.RGB8_ETC2;
  193. transcoderFormat = hasAlpha
  194. ? BasisFormat.cTFETC2_RGBA
  195. : BasisFormat.cTFETC1_RGB;
  196. } else if (supportedTargetFormats.etc1 && !hasAlpha) {
  197. internalFormat = PixelFormat.RGB_ETC1;
  198. transcoderFormat = BasisFormat.cTFETC1_RGB;
  199. } else if (supportedTargetFormats.pvrtc) {
  200. internalFormat = hasAlpha
  201. ? PixelFormat.RGBA_PVRTC_4BPPV1
  202. : PixelFormat.RGB_PVRTC_4BPPV1;
  203. transcoderFormat = hasAlpha
  204. ? BasisFormat.cTFPVRTC1_4_RGBA
  205. : BasisFormat.cTFPVRTC1_4_RGB;
  206. } else {
  207. throw new RuntimeError(
  208. "No transcoding format target available for UASTC compressed ktx2."
  209. );
  210. }
  211. }
  212. if (!ktx2File.startTranscoding()) {
  213. ktx2File.close();
  214. ktx2File.delete();
  215. throw new RuntimeError("startTranscoding() failed");
  216. }
  217. for (let i = 0; i < header.levels.length; ++i) {
  218. const level = {};
  219. result[i] = level;
  220. width = header.pixelWidth >> i;
  221. height = header.pixelHeight >> i;
  222. // Since supercompressed cubemaps are unsupported, this function
  223. // does not iterate over KTX2 faces and assumes faceCount = 1.
  224. const dstSize = ktx2File.getImageTranscodedSizeInBytes(
  225. i, // level index
  226. 0, // layer index
  227. 0, // face index
  228. transcoderFormat.value
  229. );
  230. const dst = new Uint8Array(dstSize);
  231. const transcoded = ktx2File.transcodeImage(
  232. dst,
  233. i, // level index
  234. 0, // layer index
  235. 0, // face index
  236. transcoderFormat.value,
  237. 0, // get_alpha_for_opaque_formats
  238. -1, // channel0
  239. -1 // channel1
  240. );
  241. if (!defined(transcoded)) {
  242. throw new RuntimeError("transcodeImage() failed.");
  243. }
  244. transferableObjects.push(dst.buffer);
  245. level[faceOrder[0]] = {
  246. internalFormat: internalFormat,
  247. width: width,
  248. height: height,
  249. levelBuffer: dst,
  250. };
  251. }
  252. ktx2File.close();
  253. ktx2File.delete();
  254. return result;
  255. }
  256. function initWorker(compiledModule) {
  257. transcoderModule = compiledModule;
  258. transcoderModule.initializeBasis();
  259. self.onmessage = createTaskProcessorWorker(transcode);
  260. self.postMessage(true);
  261. }
  262. function transcodeKTX2(event) {
  263. const data = event.data;
  264. // Expect the first message to be to load a web assembly module
  265. const wasmConfig = data.webAssemblyConfig;
  266. if (defined(wasmConfig)) {
  267. // Require and compile WebAssembly module, or use fallback if not supported
  268. return require([wasmConfig.modulePath], function (mscBasisTranscoder) {
  269. if (defined(wasmConfig.wasmBinaryFile)) {
  270. if (!defined(mscBasisTranscoder)) {
  271. mscBasisTranscoder = self.MSC_TRANSCODER;
  272. }
  273. mscBasisTranscoder(wasmConfig).then(function (compiledModule) {
  274. initWorker(compiledModule);
  275. });
  276. } else {
  277. return mscBasisTranscoder().then(function (transcoder) {
  278. initWorker(transcoder);
  279. });
  280. }
  281. });
  282. }
  283. }
  284. export default transcodeKTX2;