basis.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. import { Tools } from "./tools.js";
  2. import { Texture } from "../Materials/Textures/texture.js";
  3. import { InternalTexture, InternalTextureSource } from "../Materials/Textures/internalTexture.js";
  4. import { Scalar } from "../Maths/math.scalar.js";
  5. import { initializeWebWorker, workerFunction } from "./basisWorker.js";
  6. /**
  7. * Info about the .basis files
  8. */
  9. export class BasisFileInfo {
  10. }
  11. /**
  12. * Result of transcoding a basis file
  13. */
  14. class TranscodeResult {
  15. }
  16. /**
  17. * Configuration options for the Basis transcoder
  18. */
  19. export class BasisTranscodeConfiguration {
  20. }
  21. /**
  22. * @internal
  23. * Enum of basis transcoder formats
  24. */
  25. var BASIS_FORMATS;
  26. (function (BASIS_FORMATS) {
  27. BASIS_FORMATS[BASIS_FORMATS["cTFETC1"] = 0] = "cTFETC1";
  28. BASIS_FORMATS[BASIS_FORMATS["cTFETC2"] = 1] = "cTFETC2";
  29. BASIS_FORMATS[BASIS_FORMATS["cTFBC1"] = 2] = "cTFBC1";
  30. BASIS_FORMATS[BASIS_FORMATS["cTFBC3"] = 3] = "cTFBC3";
  31. BASIS_FORMATS[BASIS_FORMATS["cTFBC4"] = 4] = "cTFBC4";
  32. BASIS_FORMATS[BASIS_FORMATS["cTFBC5"] = 5] = "cTFBC5";
  33. BASIS_FORMATS[BASIS_FORMATS["cTFBC7"] = 6] = "cTFBC7";
  34. BASIS_FORMATS[BASIS_FORMATS["cTFPVRTC1_4_RGB"] = 8] = "cTFPVRTC1_4_RGB";
  35. BASIS_FORMATS[BASIS_FORMATS["cTFPVRTC1_4_RGBA"] = 9] = "cTFPVRTC1_4_RGBA";
  36. BASIS_FORMATS[BASIS_FORMATS["cTFASTC_4x4"] = 10] = "cTFASTC_4x4";
  37. BASIS_FORMATS[BASIS_FORMATS["cTFATC_RGB"] = 11] = "cTFATC_RGB";
  38. BASIS_FORMATS[BASIS_FORMATS["cTFATC_RGBA_INTERPOLATED_ALPHA"] = 12] = "cTFATC_RGBA_INTERPOLATED_ALPHA";
  39. BASIS_FORMATS[BASIS_FORMATS["cTFRGBA32"] = 13] = "cTFRGBA32";
  40. BASIS_FORMATS[BASIS_FORMATS["cTFRGB565"] = 14] = "cTFRGB565";
  41. BASIS_FORMATS[BASIS_FORMATS["cTFBGR565"] = 15] = "cTFBGR565";
  42. BASIS_FORMATS[BASIS_FORMATS["cTFRGBA4444"] = 16] = "cTFRGBA4444";
  43. BASIS_FORMATS[BASIS_FORMATS["cTFFXT1_RGB"] = 17] = "cTFFXT1_RGB";
  44. BASIS_FORMATS[BASIS_FORMATS["cTFPVRTC2_4_RGB"] = 18] = "cTFPVRTC2_4_RGB";
  45. BASIS_FORMATS[BASIS_FORMATS["cTFPVRTC2_4_RGBA"] = 19] = "cTFPVRTC2_4_RGBA";
  46. BASIS_FORMATS[BASIS_FORMATS["cTFETC2_EAC_R11"] = 20] = "cTFETC2_EAC_R11";
  47. BASIS_FORMATS[BASIS_FORMATS["cTFETC2_EAC_RG11"] = 21] = "cTFETC2_EAC_RG11";
  48. })(BASIS_FORMATS || (BASIS_FORMATS = {}));
  49. /**
  50. * Used to load .Basis files
  51. * See https://github.com/BinomialLLC/basis_universal/tree/master/webgl
  52. */
  53. export const BasisToolsOptions = {
  54. /**
  55. * URL to use when loading the basis transcoder
  56. */
  57. JSModuleURL: `${Tools._DefaultCdnUrl}/basisTranscoder/1/basis_transcoder.js`,
  58. /**
  59. * URL to use when loading the wasm module for the transcoder
  60. */
  61. WasmModuleURL: `${Tools._DefaultCdnUrl}/basisTranscoder/1/basis_transcoder.wasm`,
  62. };
  63. /**
  64. * Get the internal format to be passed to texImage2D corresponding to the .basis format value
  65. * @param basisFormat format chosen from GetSupportedTranscodeFormat
  66. * @param engine
  67. * @returns internal format corresponding to the Basis format
  68. */
  69. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  70. export const GetInternalFormatFromBasisFormat = (basisFormat, engine) => {
  71. let format;
  72. switch (basisFormat) {
  73. case BASIS_FORMATS.cTFETC1:
  74. format = 36196;
  75. break;
  76. case BASIS_FORMATS.cTFBC1:
  77. format = 33776;
  78. break;
  79. case BASIS_FORMATS.cTFBC4:
  80. format = 33779;
  81. break;
  82. case BASIS_FORMATS.cTFASTC_4x4:
  83. format = 37808;
  84. break;
  85. case BASIS_FORMATS.cTFETC2:
  86. format = 37496;
  87. break;
  88. case BASIS_FORMATS.cTFBC7:
  89. format = 36492;
  90. break;
  91. }
  92. if (format === undefined) {
  93. // eslint-disable-next-line no-throw-literal
  94. throw "The chosen Basis transcoder format is not currently supported";
  95. }
  96. return format;
  97. };
  98. let _WorkerPromise = null;
  99. let _Worker = null;
  100. let _actionId = 0;
  101. const _IgnoreSupportedFormats = false;
  102. const _CreateWorkerAsync = () => {
  103. if (!_WorkerPromise) {
  104. _WorkerPromise = new Promise((res, reject) => {
  105. if (_Worker) {
  106. res(_Worker);
  107. }
  108. else {
  109. Tools.LoadFileAsync(Tools.GetBabylonScriptURL(BasisToolsOptions.WasmModuleURL))
  110. .then((wasmBinary) => {
  111. if (typeof URL !== "function") {
  112. return reject("Basis transcoder requires an environment with a URL constructor");
  113. }
  114. const workerBlobUrl = URL.createObjectURL(new Blob([`(${workerFunction})()`], { type: "application/javascript" }));
  115. _Worker = new Worker(workerBlobUrl);
  116. initializeWebWorker(_Worker, wasmBinary, BasisToolsOptions.JSModuleURL).then(res, reject);
  117. })
  118. .catch(reject);
  119. }
  120. });
  121. }
  122. return _WorkerPromise;
  123. };
  124. /**
  125. * Set the worker to use for transcoding
  126. * @param worker The worker that will be used for transcoding
  127. */
  128. export const SetBasisTranscoderWorker = (worker) => {
  129. _Worker = worker;
  130. };
  131. /**
  132. * Transcodes a loaded image file to compressed pixel data
  133. * @param data image data to transcode
  134. * @param config configuration options for the transcoding
  135. * @returns a promise resulting in the transcoded image
  136. */
  137. export const TranscodeAsync = (data, config) => {
  138. const dataView = data instanceof ArrayBuffer ? new Uint8Array(data) : data;
  139. return new Promise((res, rej) => {
  140. _CreateWorkerAsync().then(() => {
  141. const actionId = _actionId++;
  142. const messageHandler = (msg) => {
  143. if (msg.data.action === "transcode" && msg.data.id === actionId) {
  144. _Worker.removeEventListener("message", messageHandler);
  145. if (!msg.data.success) {
  146. rej("Transcode is not supported on this device");
  147. }
  148. else {
  149. res(msg.data);
  150. }
  151. }
  152. };
  153. _Worker.addEventListener("message", messageHandler);
  154. const dataViewCopy = new Uint8Array(dataView.byteLength);
  155. dataViewCopy.set(new Uint8Array(dataView.buffer, dataView.byteOffset, dataView.byteLength));
  156. _Worker.postMessage({ action: "transcode", id: actionId, imageData: dataViewCopy, config: config, ignoreSupportedFormats: _IgnoreSupportedFormats }, [
  157. dataViewCopy.buffer,
  158. ]);
  159. }, (error) => {
  160. rej(error);
  161. });
  162. });
  163. };
  164. /**
  165. * Binds a texture according to its underlying target.
  166. * @param texture texture to bind
  167. * @param engine the engine to bind the texture in
  168. */
  169. const BindTexture = (texture, engine) => {
  170. let target = engine._gl?.TEXTURE_2D;
  171. if (texture.isCube) {
  172. target = engine._gl?.TEXTURE_CUBE_MAP;
  173. }
  174. engine._bindTextureDirectly(target, texture, true);
  175. };
  176. /**
  177. * Loads a texture from the transcode result
  178. * @param texture texture load to
  179. * @param transcodeResult the result of transcoding the basis file to load from
  180. */
  181. export const LoadTextureFromTranscodeResult = (texture, transcodeResult) => {
  182. const engine = texture.getEngine();
  183. for (let i = 0; i < transcodeResult.fileInfo.images.length; i++) {
  184. const rootImage = transcodeResult.fileInfo.images[i].levels[0];
  185. texture._invertVScale = texture.invertY;
  186. if (transcodeResult.format === -1 || transcodeResult.format === BASIS_FORMATS.cTFRGB565) {
  187. // No compatable compressed format found, fallback to RGB
  188. texture.type = 10;
  189. texture.format = 4;
  190. if (engine._features.basisNeedsPOT && (Scalar.Log2(rootImage.width) % 1 !== 0 || Scalar.Log2(rootImage.height) % 1 !== 0)) {
  191. // Create non power of two texture
  192. const source = new InternalTexture(engine, InternalTextureSource.Temp);
  193. texture._invertVScale = texture.invertY;
  194. source.type = 10;
  195. source.format = 4;
  196. // Fallback requires aligned width/height
  197. source.width = (rootImage.width + 3) & ~3;
  198. source.height = (rootImage.height + 3) & ~3;
  199. BindTexture(source, engine);
  200. engine._uploadDataToTextureDirectly(source, new Uint16Array(rootImage.transcodedPixels.buffer), i, 0, 4, true);
  201. // Resize to power of two
  202. engine._rescaleTexture(source, texture, engine.scenes[0], engine._getInternalFormat(4), () => {
  203. engine._releaseTexture(source);
  204. BindTexture(texture, engine);
  205. });
  206. }
  207. else {
  208. // Fallback is already inverted
  209. texture._invertVScale = !texture.invertY;
  210. // Upload directly
  211. texture.width = (rootImage.width + 3) & ~3;
  212. texture.height = (rootImage.height + 3) & ~3;
  213. texture.samplingMode = 2;
  214. BindTexture(texture, engine);
  215. engine._uploadDataToTextureDirectly(texture, new Uint16Array(rootImage.transcodedPixels.buffer), i, 0, 4, true);
  216. }
  217. }
  218. else {
  219. texture.width = rootImage.width;
  220. texture.height = rootImage.height;
  221. texture.generateMipMaps = transcodeResult.fileInfo.images[i].levels.length > 1;
  222. const format = BasisTools.GetInternalFormatFromBasisFormat(transcodeResult.format, engine);
  223. texture.format = format;
  224. BindTexture(texture, engine);
  225. // Upload all mip levels in the file
  226. transcodeResult.fileInfo.images[i].levels.forEach((level, index) => {
  227. engine._uploadCompressedDataToTextureDirectly(texture, format, level.width, level.height, level.transcodedPixels, i, index);
  228. });
  229. if (engine._features.basisNeedsPOT && (Scalar.Log2(texture.width) % 1 !== 0 || Scalar.Log2(texture.height) % 1 !== 0)) {
  230. Tools.Warn("Loaded .basis texture width and height are not a power of two. Texture wrapping will be set to Texture.CLAMP_ADDRESSMODE as other modes are not supported with non power of two dimensions in webGL 1.");
  231. texture._cachedWrapU = Texture.CLAMP_ADDRESSMODE;
  232. texture._cachedWrapV = Texture.CLAMP_ADDRESSMODE;
  233. }
  234. }
  235. }
  236. };
  237. /**
  238. * Used to load .Basis files
  239. * See https://github.com/BinomialLLC/basis_universal/tree/master/webgl
  240. */
  241. export const BasisTools = {
  242. /**
  243. * URL to use when loading the basis transcoder
  244. */
  245. JSModuleURL: BasisToolsOptions.JSModuleURL,
  246. /**
  247. * URL to use when loading the wasm module for the transcoder
  248. */
  249. WasmModuleURL: BasisToolsOptions.WasmModuleURL,
  250. /**
  251. * Get the internal format to be passed to texImage2D corresponding to the .basis format value
  252. * @param basisFormat format chosen from GetSupportedTranscodeFormat
  253. * @returns internal format corresponding to the Basis format
  254. */
  255. GetInternalFormatFromBasisFormat,
  256. /**
  257. * Transcodes a loaded image file to compressed pixel data
  258. * @param data image data to transcode
  259. * @param config configuration options for the transcoding
  260. * @returns a promise resulting in the transcoded image
  261. */
  262. TranscodeAsync,
  263. /**
  264. * Loads a texture from the transcode result
  265. * @param texture texture load to
  266. * @param transcodeResult the result of transcoding the basis file to load from
  267. */
  268. LoadTextureFromTranscodeResult,
  269. };
  270. Object.defineProperty(BasisTools, "JSModuleURL", {
  271. get: function () {
  272. return BasisToolsOptions.JSModuleURL;
  273. },
  274. set: function (value) {
  275. BasisToolsOptions.JSModuleURL = value;
  276. },
  277. });
  278. Object.defineProperty(BasisTools, "WasmModuleURL", {
  279. get: function () {
  280. return BasisToolsOptions.WasmModuleURL;
  281. },
  282. set: function (value) {
  283. BasisToolsOptions.WasmModuleURL = value;
  284. },
  285. });
  286. //# sourceMappingURL=basis.js.map