basisWorker.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. import { Tools } from "./tools.js";
  2. /**
  3. * The worker function that gets converted to a blob url to pass into a worker.
  4. * To be used if a developer wants to create their own worker instance and inject it instead of using the default worker.
  5. */
  6. export function workerFunction() {
  7. const _BASIS_FORMAT = {
  8. cTFETC1: 0,
  9. cTFETC2: 1,
  10. cTFBC1: 2,
  11. cTFBC3: 3,
  12. cTFBC4: 4,
  13. cTFBC5: 5,
  14. cTFBC7: 6,
  15. cTFPVRTC1_4_RGB: 8,
  16. cTFPVRTC1_4_RGBA: 9,
  17. cTFASTC_4x4: 10,
  18. cTFATC_RGB: 11,
  19. cTFATC_RGBA_INTERPOLATED_ALPHA: 12,
  20. cTFRGBA32: 13,
  21. cTFRGB565: 14,
  22. cTFBGR565: 15,
  23. cTFRGBA4444: 16,
  24. cTFFXT1_RGB: 17,
  25. cTFPVRTC2_4_RGB: 18,
  26. cTFPVRTC2_4_RGBA: 19,
  27. cTFETC2_EAC_R11: 20,
  28. cTFETC2_EAC_RG11: 21,
  29. };
  30. let transcoderModulePromise = null;
  31. onmessage = (event) => {
  32. if (event.data.action === "init") {
  33. // Load the transcoder if it hasn't been yet
  34. if (event.data.url) {
  35. // make sure we loaded the script correctly
  36. try {
  37. importScripts(event.data.url);
  38. }
  39. catch (e) {
  40. postMessage({ action: "error", error: e });
  41. }
  42. }
  43. if (!transcoderModulePromise) {
  44. transcoderModulePromise = BASIS({
  45. // Override wasm binary
  46. wasmBinary: event.data.wasmBinary,
  47. });
  48. }
  49. if (transcoderModulePromise !== null) {
  50. transcoderModulePromise.then((m) => {
  51. BASIS = m;
  52. m.initializeBasis();
  53. postMessage({ action: "init" });
  54. });
  55. }
  56. }
  57. else if (event.data.action === "transcode") {
  58. // Transcode the basis image and return the resulting pixels
  59. const config = event.data.config;
  60. const imgData = event.data.imageData;
  61. const loadedFile = new BASIS.BasisFile(imgData);
  62. const fileInfo = GetFileInfo(loadedFile);
  63. let format = event.data.ignoreSupportedFormats ? null : GetSupportedTranscodeFormat(event.data.config, fileInfo);
  64. let needsConversion = false;
  65. if (format === null) {
  66. needsConversion = true;
  67. format = fileInfo.hasAlpha ? _BASIS_FORMAT.cTFBC3 : _BASIS_FORMAT.cTFBC1;
  68. }
  69. // Begin transcode
  70. let success = true;
  71. if (!loadedFile.startTranscoding()) {
  72. success = false;
  73. }
  74. const buffers = [];
  75. for (let imageIndex = 0; imageIndex < fileInfo.images.length; imageIndex++) {
  76. if (!success) {
  77. break;
  78. }
  79. const image = fileInfo.images[imageIndex];
  80. if (config.loadSingleImage === undefined || config.loadSingleImage === imageIndex) {
  81. let mipCount = image.levels.length;
  82. if (config.loadMipmapLevels === false) {
  83. mipCount = 1;
  84. }
  85. for (let levelIndex = 0; levelIndex < mipCount; levelIndex++) {
  86. const levelInfo = image.levels[levelIndex];
  87. const pixels = TranscodeLevel(loadedFile, imageIndex, levelIndex, format, needsConversion);
  88. if (!pixels) {
  89. success = false;
  90. break;
  91. }
  92. levelInfo.transcodedPixels = pixels;
  93. buffers.push(levelInfo.transcodedPixels.buffer);
  94. }
  95. }
  96. }
  97. // Close file
  98. loadedFile.close();
  99. loadedFile.delete();
  100. if (needsConversion) {
  101. format = -1;
  102. }
  103. if (!success) {
  104. postMessage({ action: "transcode", success: success, id: event.data.id });
  105. }
  106. else {
  107. postMessage({ action: "transcode", success: success, id: event.data.id, fileInfo: fileInfo, format: format }, buffers);
  108. }
  109. }
  110. };
  111. /**
  112. * Detects the supported transcode format for the file
  113. * @param config transcode config
  114. * @param fileInfo info about the file
  115. * @returns the chosed format or null if none are supported
  116. */
  117. function GetSupportedTranscodeFormat(config, fileInfo) {
  118. let format = null;
  119. if (config.supportedCompressionFormats) {
  120. if (config.supportedCompressionFormats.astc) {
  121. format = _BASIS_FORMAT.cTFASTC_4x4;
  122. }
  123. else if (config.supportedCompressionFormats.bc7) {
  124. format = _BASIS_FORMAT.cTFBC7;
  125. }
  126. else if (config.supportedCompressionFormats.s3tc) {
  127. format = fileInfo.hasAlpha ? _BASIS_FORMAT.cTFBC3 : _BASIS_FORMAT.cTFBC1;
  128. }
  129. else if (config.supportedCompressionFormats.pvrtc) {
  130. format = fileInfo.hasAlpha ? _BASIS_FORMAT.cTFPVRTC1_4_RGBA : _BASIS_FORMAT.cTFPVRTC1_4_RGB;
  131. }
  132. else if (config.supportedCompressionFormats.etc2) {
  133. format = _BASIS_FORMAT.cTFETC2;
  134. }
  135. else if (config.supportedCompressionFormats.etc1) {
  136. format = _BASIS_FORMAT.cTFETC1;
  137. }
  138. else {
  139. format = _BASIS_FORMAT.cTFRGB565;
  140. }
  141. }
  142. return format;
  143. }
  144. /**
  145. * Retrieves information about the basis file eg. dimensions
  146. * @param basisFile the basis file to get the info from
  147. * @returns information about the basis file
  148. */
  149. function GetFileInfo(basisFile) {
  150. const hasAlpha = basisFile.getHasAlpha();
  151. const imageCount = basisFile.getNumImages();
  152. const images = [];
  153. for (let i = 0; i < imageCount; i++) {
  154. const imageInfo = {
  155. levels: [],
  156. };
  157. const levelCount = basisFile.getNumLevels(i);
  158. for (let level = 0; level < levelCount; level++) {
  159. const levelInfo = {
  160. width: basisFile.getImageWidth(i, level),
  161. height: basisFile.getImageHeight(i, level),
  162. };
  163. imageInfo.levels.push(levelInfo);
  164. }
  165. images.push(imageInfo);
  166. }
  167. const info = { hasAlpha, images };
  168. return info;
  169. }
  170. function TranscodeLevel(loadedFile, imageIndex, levelIndex, format, convertToRgb565) {
  171. const dstSize = loadedFile.getImageTranscodedSizeInBytes(imageIndex, levelIndex, format);
  172. let dst = new Uint8Array(dstSize);
  173. if (!loadedFile.transcodeImage(dst, imageIndex, levelIndex, format, 1, 0)) {
  174. return null;
  175. }
  176. // If no supported format is found, load as dxt and convert to rgb565
  177. if (convertToRgb565) {
  178. const alignedWidth = (loadedFile.getImageWidth(imageIndex, levelIndex) + 3) & ~3;
  179. const alignedHeight = (loadedFile.getImageHeight(imageIndex, levelIndex) + 3) & ~3;
  180. dst = ConvertDxtToRgb565(dst, 0, alignedWidth, alignedHeight);
  181. }
  182. return dst;
  183. }
  184. /**
  185. * From https://github.com/BinomialLLC/basis_universal/blob/master/webgl/texture/dxt-to-rgb565.js
  186. * An unoptimized version of dxtToRgb565. Also, the floating
  187. * point math used to compute the colors actually results in
  188. * slightly different colors compared to hardware DXT decoders.
  189. * @param src dxt src pixels
  190. * @param srcByteOffset offset for the start of src
  191. * @param width aligned width of the image
  192. * @param height aligned height of the image
  193. * @returns the converted pixels
  194. */
  195. function ConvertDxtToRgb565(src, srcByteOffset, width, height) {
  196. const c = new Uint16Array(4);
  197. const dst = new Uint16Array(width * height);
  198. const blockWidth = width / 4;
  199. const blockHeight = height / 4;
  200. for (let blockY = 0; blockY < blockHeight; blockY++) {
  201. for (let blockX = 0; blockX < blockWidth; blockX++) {
  202. const i = srcByteOffset + 8 * (blockY * blockWidth + blockX);
  203. c[0] = src[i] | (src[i + 1] << 8);
  204. c[1] = src[i + 2] | (src[i + 3] << 8);
  205. c[2] =
  206. ((2 * (c[0] & 0x1f) + 1 * (c[1] & 0x1f)) / 3) |
  207. (((2 * (c[0] & 0x7e0) + 1 * (c[1] & 0x7e0)) / 3) & 0x7e0) |
  208. (((2 * (c[0] & 0xf800) + 1 * (c[1] & 0xf800)) / 3) & 0xf800);
  209. c[3] =
  210. ((2 * (c[1] & 0x1f) + 1 * (c[0] & 0x1f)) / 3) |
  211. (((2 * (c[1] & 0x7e0) + 1 * (c[0] & 0x7e0)) / 3) & 0x7e0) |
  212. (((2 * (c[1] & 0xf800) + 1 * (c[0] & 0xf800)) / 3) & 0xf800);
  213. for (let row = 0; row < 4; row++) {
  214. const m = src[i + 4 + row];
  215. let dstI = (blockY * 4 + row) * width + blockX * 4;
  216. dst[dstI++] = c[m & 0x3];
  217. dst[dstI++] = c[(m >> 2) & 0x3];
  218. dst[dstI++] = c[(m >> 4) & 0x3];
  219. dst[dstI++] = c[(m >> 6) & 0x3];
  220. }
  221. }
  222. }
  223. return dst;
  224. }
  225. }
  226. /**
  227. * Initialize a web worker with the basis transcoder
  228. * @param worker the worker to initialize
  229. * @param wasmBinary the wasm binary to load into the worker
  230. * @param moduleUrl the url to the basis transcoder module
  231. * @returns a promise that resolves when the worker is initialized
  232. */
  233. export function initializeWebWorker(worker, wasmBinary, moduleUrl) {
  234. return new Promise((res, reject) => {
  235. const initHandler = (msg) => {
  236. if (msg.data.action === "init") {
  237. worker.removeEventListener("message", initHandler);
  238. res(worker);
  239. }
  240. else if (msg.data.action === "error") {
  241. reject(msg.data.error || "error initializing worker");
  242. }
  243. };
  244. worker.addEventListener("message", initHandler);
  245. // we can use transferable objects here because the worker will own the ArrayBuffer
  246. worker.postMessage({ action: "init", url: moduleUrl ? Tools.GetBabylonScriptURL(moduleUrl) : undefined, wasmBinary }, [wasmBinary]);
  247. });
  248. }
  249. //# sourceMappingURL=basisWorker.js.map