basis.ts 9.5 KB

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