basis.ts 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. import { Logger } from "../Misc/logger";
  2. import { Nullable } from '../types';
  3. import { Engine } from '../Engines/engine';
  4. /**
  5. * Info about the .basis files
  6. */
  7. class BasisFileInfo {
  8. /**
  9. * If the file has alpha
  10. */
  11. public hasAlpha: boolean;
  12. /**
  13. * Width of the image
  14. */
  15. public width: number;
  16. /**
  17. * Height of the image
  18. */
  19. public height: number;
  20. /**
  21. * Aligned width used when falling back to Rgb565 ((width + 3) & ~3)
  22. */
  23. public alignedWidth: number;
  24. /**
  25. * Aligned height used when falling back to Rgb565 ((height + 3) & ~3)
  26. */
  27. public alignedHeight: number;
  28. }
  29. /**
  30. * Used to load .Basis files
  31. * See https://github.com/BinomialLLC/basis_universal/tree/master/webgl
  32. */
  33. export class BasisTools {
  34. private static _Initialized = false;
  35. private static _IgnoreSupportedFormats = false;
  36. private static _BASIS_FORMAT = {
  37. cTFETC1: 0,
  38. cTFBC1: 1,
  39. cTFBC4: 2,
  40. cTFPVRTC1_4_OPAQUE_ONLY: 3,
  41. cTFBC7_M6_OPAQUE_ONLY: 4,
  42. cTFETC2: 5,
  43. cTFBC3: 6,
  44. cTFBC5: 7,
  45. };
  46. /**
  47. * Basis module can be aquired from https://github.com/BinomialLLC/basis_universal/tree/master/webgl
  48. * This should be set prior to loading a .basis texture
  49. */
  50. public static BasisModule: Nullable<any> = null;
  51. /**
  52. * Verifies that the BasisModule has been populated and falls back to loading from the web if not availible
  53. */
  54. public static VerifyBasisModule() {
  55. if (!BasisTools.BasisModule) {
  56. if ((window as any).Module && (window as any).Module.BasisFile) {
  57. Logger.Warn("BasisTools.BasisModule not populated, falling back to window.Module");
  58. BasisTools.BasisModule = (window as any).Module;
  59. }else {
  60. // TODO should load from cdn location as fallback
  61. throw "Unable to load .basis texture, BasisTools.BasisModule should be populated";
  62. }
  63. }
  64. if (!BasisTools._Initialized && BasisTools.BasisModule) {
  65. BasisTools.BasisModule.initializeBasis();
  66. BasisTools._Initialized = true;
  67. }
  68. }
  69. /**
  70. * Verifies that the basis module has been populated and creates a bsis file from the image data
  71. * @param data array buffer of the .basis file
  72. * @returns the Basis file
  73. */
  74. public static LoadBasisFile(data: ArrayBuffer) {
  75. BasisTools.VerifyBasisModule();
  76. return new BasisTools.BasisModule.BasisFile(new Uint8Array(data));
  77. }
  78. /**
  79. * Detects the supported transcode format for the file
  80. * @param engine Babylon engine
  81. * @param fileInfo info about the file
  82. * @returns the chosed format or null if none are supported
  83. */
  84. public static GetSupportedTranscodeFormat(engine: Engine, fileInfo: BasisFileInfo): Nullable<number> {
  85. var caps = engine.getCaps();
  86. var format = null;
  87. if (caps.etc1) {
  88. format = BasisTools._BASIS_FORMAT.cTFETC1;
  89. }else if (caps.s3tc) {
  90. format = fileInfo.hasAlpha ? BasisTools._BASIS_FORMAT.cTFBC3 : BasisTools._BASIS_FORMAT.cTFBC1;
  91. }else if (caps.pvrtc) {
  92. format = BasisTools._BASIS_FORMAT.cTFPVRTC1_4_OPAQUE_ONLY;
  93. }else if (caps.etc2) {
  94. format = BasisTools._BASIS_FORMAT.cTFETC2;
  95. }
  96. return format;
  97. }
  98. /**
  99. * Get the internal format to be passed to texImage2D corrisponding to the .basis format value
  100. * @param basisFormat format chosen from GetSupportedTranscodeFormat
  101. * @returns internal format corrisponding to the Basis format
  102. */
  103. public static GetInternalFormatFromBasisFormat(basisFormat: number) {
  104. // TODO more formats need to be added here and validated
  105. var COMPRESSED_RGB_S3TC_DXT1_EXT = 0x83F0;
  106. var COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3;
  107. var RGB_ETC1_Format = 36196;
  108. // var COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1;
  109. // var COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2;
  110. if (basisFormat === this._BASIS_FORMAT.cTFETC1) {
  111. return RGB_ETC1_Format;
  112. }else if (basisFormat === this._BASIS_FORMAT.cTFBC1) {
  113. return COMPRESSED_RGB_S3TC_DXT1_EXT;
  114. }else if (basisFormat === this._BASIS_FORMAT.cTFBC3) {
  115. return COMPRESSED_RGBA_S3TC_DXT5_EXT;
  116. }else {
  117. // TODO find value for these formats
  118. // else if(basisFormat === this.BASIS_FORMAT.cTFBC4){
  119. // }else if(basisFormat === this.BASIS_FORMAT.cTFPVRTC1_4_OPAQUE_ONLY){
  120. // }else if(basisFormat === this.BASIS_FORMAT.cTFBC7_M6_OPAQUE_ONLY){
  121. // }else if(basisFormat === this.BASIS_FORMAT.cTFETC2){
  122. // }else if(basisFormat === this.BASIS_FORMAT.cTFBC5){
  123. // }
  124. throw "Basis format not found or supported";
  125. }
  126. }
  127. /**
  128. * Retreives information about the basis file eg. dimensions
  129. * @param basisFile the basis file to get the info from
  130. * @returns information about the basis file
  131. */
  132. public static GetFileInfo(basisFile: any): BasisFileInfo {
  133. var hasAlpha = basisFile.getHasAlpha();
  134. var width = basisFile.getImageWidth(0, 0);
  135. var height = basisFile.getImageHeight(0, 0);
  136. var alignedWidth = (width + 3) & ~3;
  137. var alignedHeight = (height + 3) & ~3;
  138. var info = { hasAlpha, width, height, alignedWidth, alignedHeight };
  139. return info;
  140. }
  141. /**
  142. * Transcodes the basis file to the requested format to be transferred to the gpu
  143. * @param format fromat to be transferred to
  144. * @param fileInfo information about the loaded file
  145. * @param loadedFile the loaded basis file
  146. * @returns the resulting pixels and if the transcode fell back to using Rgb565
  147. */
  148. public static TranscodeFile(format: Nullable<number>, fileInfo: BasisFileInfo, loadedFile: any) {
  149. if (BasisTools._IgnoreSupportedFormats) {
  150. format = null;
  151. }
  152. var needsConversion = false;
  153. if (format === null) {
  154. needsConversion = true;
  155. format = fileInfo.hasAlpha ? BasisTools._BASIS_FORMAT.cTFBC3 : BasisTools._BASIS_FORMAT.cTFBC1;
  156. }
  157. if (!loadedFile.startTranscoding()) {
  158. loadedFile.close();
  159. loadedFile.delete();
  160. throw "transcode failed";
  161. }
  162. var dstSize = loadedFile.getImageTranscodedSizeInBytes(0, 0, format);
  163. var dst = new Uint8Array(dstSize);
  164. if (!loadedFile.transcodeImage(dst, 0, 0, format, 1, 0)) {
  165. loadedFile.close();
  166. loadedFile.delete();
  167. throw "transcode failed";
  168. }
  169. loadedFile.close();
  170. loadedFile.delete();
  171. // If no supported format is found, load as dxt and convert to rgb565
  172. if (needsConversion) {
  173. dst = BasisTools.ConvertDxtToRgb565(dst, 0, fileInfo.alignedWidth, fileInfo.alignedHeight);
  174. }
  175. return {
  176. fallbackToRgb565: needsConversion, pixels: dst
  177. };
  178. }
  179. /**
  180. * From https://github.com/BinomialLLC/basis_universal/blob/master/webgl/texture/dxt-to-rgb565.js
  181. * An unoptimized version of dxtToRgb565. Also, the floating
  182. * point math used to compute the colors actually results in
  183. * slightly different colors compared to hardware DXT decoders.
  184. * @param {Uint8Array} src
  185. * @param {number} srcByteOffset
  186. * @param {number} width
  187. * @param {number} height
  188. * @return {Uint16Array} dst
  189. */
  190. public static ConvertDxtToRgb565(src: Uint16Array, srcByteOffset: number, width: number, height: number): Uint16Array {
  191. var c = new Uint16Array(4);
  192. var dst = new Uint16Array(width * height);
  193. var blockWidth = width / 4;
  194. var blockHeight = height / 4;
  195. for (var blockY = 0; blockY < blockHeight; blockY++) {
  196. for (var blockX = 0; blockX < blockWidth; blockX++) {
  197. var i = srcByteOffset + 8 * (blockY * blockWidth + blockX);
  198. c[0] = src[i] | (src[i + 1] << 8);
  199. c[1] = src[i + 2] | (src[i + 3] << 8);
  200. c[2] = (2 * (c[0] & 0x1f) + 1 * (c[1] & 0x1f)) / 3
  201. | (((2 * (c[0] & 0x7e0) + 1 * (c[1] & 0x7e0)) / 3) & 0x7e0)
  202. | (((2 * (c[0] & 0xf800) + 1 * (c[1] & 0xf800)) / 3) & 0xf800);
  203. c[3] = (2 * (c[1] & 0x1f) + 1 * (c[0] & 0x1f)) / 3
  204. | (((2 * (c[1] & 0x7e0) + 1 * (c[0] & 0x7e0)) / 3) & 0x7e0)
  205. | (((2 * (c[1] & 0xf800) + 1 * (c[0] & 0xf800)) / 3) & 0xf800);
  206. for (var row = 0; row < 4; row++) {
  207. var m = src[i + 4 + row];
  208. var dstI = (blockY * 4 + row) * width + blockX * 4;
  209. dst[dstI++] = c[m & 0x3];
  210. dst[dstI++] = c[(m >> 2) & 0x3];
  211. dst[dstI++] = c[(m >> 4) & 0x3];
  212. dst[dstI++] = c[(m >> 6) & 0x3];
  213. }
  214. }
  215. }
  216. return dst;
  217. }
  218. }