dds.ts 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795
  1. import { Scalar } from "../Maths/math.scalar";
  2. import { SphericalPolynomial } from "../Maths/sphericalPolynomial";
  3. import { Constants } from "../Engines/constants";
  4. import { InternalTexture, InternalTextureSource } from "../Materials/Textures/internalTexture";
  5. import { Nullable } from "../types";
  6. import { Logger } from "../Misc/logger";
  7. import { CubeMapToSphericalPolynomialTools } from "../Misc/HighDynamicRange/cubemapToSphericalPolynomial";
  8. import { Scene } from '../scene';
  9. import { BaseTexture } from '../Materials/Textures/baseTexture';
  10. import "../Engines/Extensions/engine.cubeTexture";
  11. import { ThinEngine } from '../Engines/thinEngine';
  12. // Based on demo done by Brandon Jones - http://media.tojicode.com/webgl-samples/dds.html
  13. // All values and structures referenced from:
  14. // http://msdn.microsoft.com/en-us/library/bb943991.aspx/
  15. var DDS_MAGIC = 0x20534444;
  16. var
  17. //DDSD_CAPS = 0x1,
  18. //DDSD_HEIGHT = 0x2,
  19. //DDSD_WIDTH = 0x4,
  20. //DDSD_PITCH = 0x8,
  21. //DDSD_PIXELFORMAT = 0x1000,
  22. DDSD_MIPMAPCOUNT = 0x20000;
  23. //DDSD_LINEARSIZE = 0x80000,
  24. //DDSD_DEPTH = 0x800000;
  25. // var DDSCAPS_COMPLEX = 0x8,
  26. // DDSCAPS_MIPMAP = 0x400000,
  27. // DDSCAPS_TEXTURE = 0x1000;
  28. var DDSCAPS2_CUBEMAP = 0x200;
  29. // DDSCAPS2_CUBEMAP_POSITIVEX = 0x400,
  30. // DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800,
  31. // DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000,
  32. // DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000,
  33. // DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000,
  34. // DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000,
  35. // DDSCAPS2_VOLUME = 0x200000;
  36. var
  37. //DDPF_ALPHAPIXELS = 0x1,
  38. //DDPF_ALPHA = 0x2,
  39. DDPF_FOURCC = 0x4,
  40. DDPF_RGB = 0x40,
  41. //DDPF_YUV = 0x200,
  42. DDPF_LUMINANCE = 0x20000;
  43. function FourCCToInt32(value: string) {
  44. return value.charCodeAt(0) +
  45. (value.charCodeAt(1) << 8) +
  46. (value.charCodeAt(2) << 16) +
  47. (value.charCodeAt(3) << 24);
  48. }
  49. function Int32ToFourCC(value: number) {
  50. return String.fromCharCode(
  51. value & 0xff,
  52. (value >> 8) & 0xff,
  53. (value >> 16) & 0xff,
  54. (value >> 24) & 0xff
  55. );
  56. }
  57. var FOURCC_DXT1 = FourCCToInt32("DXT1");
  58. var FOURCC_DXT3 = FourCCToInt32("DXT3");
  59. var FOURCC_DXT5 = FourCCToInt32("DXT5");
  60. var FOURCC_DX10 = FourCCToInt32("DX10");
  61. var FOURCC_D3DFMT_R16G16B16A16F = 113;
  62. var FOURCC_D3DFMT_R32G32B32A32F = 116;
  63. var DXGI_FORMAT_R32G32B32A32_FLOAT = 2;
  64. var DXGI_FORMAT_R16G16B16A16_FLOAT = 10;
  65. var DXGI_FORMAT_B8G8R8X8_UNORM = 88;
  66. var headerLengthInt = 31; // The header length in 32 bit ints
  67. // Offsets into the header array
  68. var off_magic = 0;
  69. var off_size = 1;
  70. var off_flags = 2;
  71. var off_height = 3;
  72. var off_width = 4;
  73. var off_mipmapCount = 7;
  74. var off_pfFlags = 20;
  75. var off_pfFourCC = 21;
  76. var off_RGBbpp = 22;
  77. var off_RMask = 23;
  78. var off_GMask = 24;
  79. var off_BMask = 25;
  80. var off_AMask = 26;
  81. // var off_caps1 = 27;
  82. var off_caps2 = 28;
  83. // var off_caps3 = 29;
  84. // var off_caps4 = 30;
  85. var off_dxgiFormat = 32;
  86. /**
  87. * Direct draw surface info
  88. * @see https://docs.microsoft.com/en-us/windows/desktop/direct3ddds/dx-graphics-dds-pguide
  89. */
  90. export interface DDSInfo {
  91. /**
  92. * Width of the texture
  93. */
  94. width: number;
  95. /**
  96. * Width of the texture
  97. */
  98. height: number;
  99. /**
  100. * Number of Mipmaps for the texture
  101. * @see https://en.wikipedia.org/wiki/Mipmap
  102. */
  103. mipmapCount: number;
  104. /**
  105. * If the textures format is a known fourCC format
  106. * @see https://www.fourcc.org/
  107. */
  108. isFourCC: boolean;
  109. /**
  110. * If the texture is an RGB format eg. DXGI_FORMAT_B8G8R8X8_UNORM format
  111. */
  112. isRGB: boolean;
  113. /**
  114. * If the texture is a lumincance format
  115. */
  116. isLuminance: boolean;
  117. /**
  118. * If this is a cube texture
  119. * @see https://docs.microsoft.com/en-us/windows/desktop/direct3ddds/dds-file-layout-for-cubic-environment-maps
  120. */
  121. isCube: boolean;
  122. /**
  123. * If the texture is a compressed format eg. FOURCC_DXT1
  124. */
  125. isCompressed: boolean;
  126. /**
  127. * The dxgiFormat of the texture
  128. * @see https://docs.microsoft.com/en-us/windows/desktop/api/dxgiformat/ne-dxgiformat-dxgi_format
  129. */
  130. dxgiFormat: number;
  131. /**
  132. * Texture type eg. Engine.TEXTURETYPE_UNSIGNED_INT, Engine.TEXTURETYPE_FLOAT
  133. */
  134. textureType: number;
  135. /**
  136. * Sphericle polynomial created for the dds texture
  137. */
  138. sphericalPolynomial?: SphericalPolynomial;
  139. }
  140. /**
  141. * Class used to provide DDS decompression tools
  142. */
  143. export class DDSTools {
  144. /**
  145. * Gets or sets a boolean indicating that LOD info is stored in alpha channel (false by default)
  146. */
  147. public static StoreLODInAlphaChannel = false;
  148. /**
  149. * Gets DDS information from an array buffer
  150. * @param data defines the array buffer view to read data from
  151. * @returns the DDS information
  152. */
  153. public static GetDDSInfo(data: ArrayBufferView): DDSInfo {
  154. var header = new Int32Array(data.buffer, data.byteOffset, headerLengthInt);
  155. var extendedHeader = new Int32Array(data.buffer, data.byteOffset, headerLengthInt + 4);
  156. var mipmapCount = 1;
  157. if (header[off_flags] & DDSD_MIPMAPCOUNT) {
  158. mipmapCount = Math.max(1, header[off_mipmapCount]);
  159. }
  160. var fourCC = header[off_pfFourCC];
  161. var dxgiFormat = (fourCC === FOURCC_DX10) ? extendedHeader[off_dxgiFormat] : 0;
  162. var textureType = Constants.TEXTURETYPE_UNSIGNED_INT;
  163. switch (fourCC) {
  164. case FOURCC_D3DFMT_R16G16B16A16F:
  165. textureType = Constants.TEXTURETYPE_HALF_FLOAT;
  166. break;
  167. case FOURCC_D3DFMT_R32G32B32A32F:
  168. textureType = Constants.TEXTURETYPE_FLOAT;
  169. break;
  170. case FOURCC_DX10:
  171. if (dxgiFormat === DXGI_FORMAT_R16G16B16A16_FLOAT) {
  172. textureType = Constants.TEXTURETYPE_HALF_FLOAT;
  173. break;
  174. }
  175. if (dxgiFormat === DXGI_FORMAT_R32G32B32A32_FLOAT) {
  176. textureType = Constants.TEXTURETYPE_FLOAT;
  177. break;
  178. }
  179. }
  180. return {
  181. width: header[off_width],
  182. height: header[off_height],
  183. mipmapCount: mipmapCount,
  184. isFourCC: (header[off_pfFlags] & DDPF_FOURCC) === DDPF_FOURCC,
  185. isRGB: (header[off_pfFlags] & DDPF_RGB) === DDPF_RGB,
  186. isLuminance: (header[off_pfFlags] & DDPF_LUMINANCE) === DDPF_LUMINANCE,
  187. isCube: (header[off_caps2] & DDSCAPS2_CUBEMAP) === DDSCAPS2_CUBEMAP,
  188. isCompressed: (fourCC === FOURCC_DXT1 || fourCC === FOURCC_DXT3 || fourCC === FOURCC_DXT5),
  189. dxgiFormat: dxgiFormat,
  190. textureType: textureType
  191. };
  192. }
  193. // ref: http://stackoverflow.com/questions/32633585/how-do-you-convert-to-half-floats-in-javascript
  194. private static _FloatView: Float32Array;
  195. private static _Int32View: Int32Array;
  196. private static _ToHalfFloat(value: number): number {
  197. if (!DDSTools._FloatView) {
  198. DDSTools._FloatView = new Float32Array(1);
  199. DDSTools._Int32View = new Int32Array(DDSTools._FloatView.buffer);
  200. }
  201. DDSTools._FloatView[0] = value;
  202. var x = DDSTools._Int32View[0];
  203. var bits = (x >> 16) & 0x8000; /* Get the sign */
  204. var m = (x >> 12) & 0x07ff; /* Keep one extra bit for rounding */
  205. var e = (x >> 23) & 0xff; /* Using int is faster here */
  206. /* If zero, or denormal, or exponent underflows too much for a denormal
  207. * half, return signed zero. */
  208. if (e < 103) {
  209. return bits;
  210. }
  211. /* If NaN, return NaN. If Inf or exponent overflow, return Inf. */
  212. if (e > 142) {
  213. bits |= 0x7c00;
  214. /* If exponent was 0xff and one mantissa bit was set, it means NaN,
  215. * not Inf, so make sure we set one mantissa bit too. */
  216. bits |= ((e == 255) ? 0 : 1) && (x & 0x007fffff);
  217. return bits;
  218. }
  219. /* If exponent underflows but not too much, return a denormal */
  220. if (e < 113) {
  221. m |= 0x0800;
  222. /* Extra rounding may overflow and set mantissa to 0 and exponent
  223. * to 1, which is OK. */
  224. bits |= (m >> (114 - e)) + ((m >> (113 - e)) & 1);
  225. return bits;
  226. }
  227. bits |= ((e - 112) << 10) | (m >> 1);
  228. bits += m & 1;
  229. return bits;
  230. }
  231. private static _FromHalfFloat(value: number): number {
  232. var s = (value & 0x8000) >> 15;
  233. var e = (value & 0x7C00) >> 10;
  234. var f = value & 0x03FF;
  235. if (e === 0) {
  236. return (s ? -1 : 1) * Math.pow(2, -14) * (f / Math.pow(2, 10));
  237. } else if (e == 0x1F) {
  238. return f ? NaN : ((s ? -1 : 1) * Infinity);
  239. }
  240. return (s ? -1 : 1) * Math.pow(2, e - 15) * (1 + (f / Math.pow(2, 10)));
  241. }
  242. private static _GetHalfFloatAsFloatRGBAArrayBuffer(width: number, height: number, dataOffset: number, dataLength: number, arrayBuffer: ArrayBuffer, lod: number): Float32Array {
  243. var destArray = new Float32Array(dataLength);
  244. var srcData = new Uint16Array(arrayBuffer, dataOffset);
  245. var index = 0;
  246. for (var y = 0; y < height; y++) {
  247. for (var x = 0; x < width; x++) {
  248. var srcPos = (x + y * width) * 4;
  249. destArray[index] = DDSTools._FromHalfFloat(srcData[srcPos]);
  250. destArray[index + 1] = DDSTools._FromHalfFloat(srcData[srcPos + 1]);
  251. destArray[index + 2] = DDSTools._FromHalfFloat(srcData[srcPos + 2]);
  252. if (DDSTools.StoreLODInAlphaChannel) {
  253. destArray[index + 3] = lod;
  254. } else {
  255. destArray[index + 3] = DDSTools._FromHalfFloat(srcData[srcPos + 3]);
  256. }
  257. index += 4;
  258. }
  259. }
  260. return destArray;
  261. }
  262. private static _GetHalfFloatRGBAArrayBuffer(width: number, height: number, dataOffset: number, dataLength: number, arrayBuffer: ArrayBuffer, lod: number): Uint16Array {
  263. if (DDSTools.StoreLODInAlphaChannel) {
  264. var destArray = new Uint16Array(dataLength);
  265. var srcData = new Uint16Array(arrayBuffer, dataOffset);
  266. var index = 0;
  267. for (var y = 0; y < height; y++) {
  268. for (var x = 0; x < width; x++) {
  269. var srcPos = (x + y * width) * 4;
  270. destArray[index] = srcData[srcPos];
  271. destArray[index + 1] = srcData[srcPos + 1];
  272. destArray[index + 2] = srcData[srcPos + 2];
  273. destArray[index + 3] = DDSTools._ToHalfFloat(lod);
  274. index += 4;
  275. }
  276. }
  277. return destArray;
  278. }
  279. return new Uint16Array(arrayBuffer, dataOffset, dataLength);
  280. }
  281. private static _GetFloatRGBAArrayBuffer(width: number, height: number, dataOffset: number, dataLength: number, arrayBuffer: ArrayBuffer, lod: number): Float32Array {
  282. if (DDSTools.StoreLODInAlphaChannel) {
  283. var destArray = new Float32Array(dataLength);
  284. var srcData = new Float32Array(arrayBuffer, dataOffset);
  285. var index = 0;
  286. for (var y = 0; y < height; y++) {
  287. for (var x = 0; x < width; x++) {
  288. var srcPos = (x + y * width) * 4;
  289. destArray[index] = srcData[srcPos];
  290. destArray[index + 1] = srcData[srcPos + 1];
  291. destArray[index + 2] = srcData[srcPos + 2];
  292. destArray[index + 3] = lod;
  293. index += 4;
  294. }
  295. }
  296. return destArray;
  297. }
  298. return new Float32Array(arrayBuffer, dataOffset, dataLength);
  299. }
  300. private static _GetFloatAsUIntRGBAArrayBuffer(width: number, height: number, dataOffset: number, dataLength: number, arrayBuffer: ArrayBuffer, lod: number): Float32Array {
  301. var destArray = new Uint8Array(dataLength);
  302. var srcData = new Float32Array(arrayBuffer, dataOffset);
  303. var index = 0;
  304. for (var y = 0; y < height; y++) {
  305. for (var x = 0; x < width; x++) {
  306. var srcPos = (x + y * width) * 4;
  307. destArray[index] = Scalar.Clamp(srcData[srcPos]) * 255;
  308. destArray[index + 1] = Scalar.Clamp(srcData[srcPos + 1]) * 255;
  309. destArray[index + 2] = Scalar.Clamp(srcData[srcPos + 2]) * 255;
  310. if (DDSTools.StoreLODInAlphaChannel) {
  311. destArray[index + 3] = lod;
  312. } else {
  313. destArray[index + 3] = Scalar.Clamp(srcData[srcPos + 3]) * 255;
  314. }
  315. index += 4;
  316. }
  317. }
  318. return destArray;
  319. }
  320. private static _GetHalfFloatAsUIntRGBAArrayBuffer(width: number, height: number, dataOffset: number, dataLength: number, arrayBuffer: ArrayBuffer, lod: number): Float32Array {
  321. var destArray = new Uint8Array(dataLength);
  322. var srcData = new Uint16Array(arrayBuffer, dataOffset);
  323. var index = 0;
  324. for (var y = 0; y < height; y++) {
  325. for (var x = 0; x < width; x++) {
  326. var srcPos = (x + y * width) * 4;
  327. destArray[index] = Scalar.Clamp(DDSTools._FromHalfFloat(srcData[srcPos])) * 255;
  328. destArray[index + 1] = Scalar.Clamp(DDSTools._FromHalfFloat(srcData[srcPos + 1])) * 255;
  329. destArray[index + 2] = Scalar.Clamp(DDSTools._FromHalfFloat(srcData[srcPos + 2])) * 255;
  330. if (DDSTools.StoreLODInAlphaChannel) {
  331. destArray[index + 3] = lod;
  332. } else {
  333. destArray[index + 3] = Scalar.Clamp(DDSTools._FromHalfFloat(srcData[srcPos + 3])) * 255;
  334. }
  335. index += 4;
  336. }
  337. }
  338. return destArray;
  339. }
  340. private static _GetRGBAArrayBuffer(width: number, height: number, dataOffset: number, dataLength: number, arrayBuffer: ArrayBuffer, rOffset: number, gOffset: number, bOffset: number, aOffset: number): Uint8Array {
  341. var byteArray = new Uint8Array(dataLength);
  342. var srcData = new Uint8Array(arrayBuffer, dataOffset);
  343. var index = 0;
  344. for (var y = 0; y < height; y++) {
  345. for (var x = 0; x < width; x++) {
  346. var srcPos = (x + y * width) * 4;
  347. byteArray[index] = srcData[srcPos + rOffset];
  348. byteArray[index + 1] = srcData[srcPos + gOffset];
  349. byteArray[index + 2] = srcData[srcPos + bOffset];
  350. byteArray[index + 3] = srcData[srcPos + aOffset];
  351. index += 4;
  352. }
  353. }
  354. return byteArray;
  355. }
  356. private static _ExtractLongWordOrder(value: number): number {
  357. if (value === 0 || value === 255 || value === -16777216) {
  358. return 0;
  359. }
  360. return 1 + DDSTools._ExtractLongWordOrder(value >> 8);
  361. }
  362. private static _GetRGBArrayBuffer(width: number, height: number, dataOffset: number, dataLength: number, arrayBuffer: ArrayBuffer, rOffset: number, gOffset: number, bOffset: number): Uint8Array {
  363. var byteArray = new Uint8Array(dataLength);
  364. var srcData = new Uint8Array(arrayBuffer, dataOffset);
  365. var index = 0;
  366. for (var y = 0; y < height; y++) {
  367. for (var x = 0; x < width; x++) {
  368. var srcPos = (x + y * width) * 3;
  369. byteArray[index] = srcData[srcPos + rOffset];
  370. byteArray[index + 1] = srcData[srcPos + gOffset];
  371. byteArray[index + 2] = srcData[srcPos + bOffset];
  372. index += 3;
  373. }
  374. }
  375. return byteArray;
  376. }
  377. private static _GetLuminanceArrayBuffer(width: number, height: number, dataOffset: number, dataLength: number, arrayBuffer: ArrayBuffer): Uint8Array {
  378. var byteArray = new Uint8Array(dataLength);
  379. var srcData = new Uint8Array(arrayBuffer, dataOffset);
  380. var index = 0;
  381. for (var y = 0; y < height; y++) {
  382. for (var x = 0; x < width; x++) {
  383. var srcPos = (x + y * width);
  384. byteArray[index] = srcData[srcPos];
  385. index++;
  386. }
  387. }
  388. return byteArray;
  389. }
  390. /**
  391. * Uploads DDS Levels to a Babylon Texture
  392. * @hidden
  393. */
  394. public static UploadDDSLevels(engine: ThinEngine, texture: InternalTexture, data: ArrayBufferView, info: DDSInfo, loadMipmaps: boolean, faces: number, lodIndex = -1, currentFace?: number) {
  395. var sphericalPolynomialFaces: Nullable<Array<ArrayBufferView>> = null;
  396. if (info.sphericalPolynomial) {
  397. sphericalPolynomialFaces = new Array<ArrayBufferView>();
  398. }
  399. var ext = engine.getCaps().s3tc;
  400. var header = new Int32Array(data.buffer, data.byteOffset, headerLengthInt);
  401. var fourCC: number, width: number, height: number, dataLength: number = 0, dataOffset: number;
  402. var byteArray: Uint8Array, mipmapCount: number, mip: number;
  403. let internalCompressedFormat = 0;
  404. let blockBytes = 1;
  405. if (header[off_magic] !== DDS_MAGIC) {
  406. Logger.Error("Invalid magic number in DDS header");
  407. return;
  408. }
  409. if (!info.isFourCC && !info.isRGB && !info.isLuminance) {
  410. Logger.Error("Unsupported format, must contain a FourCC, RGB or LUMINANCE code");
  411. return;
  412. }
  413. if (info.isCompressed && !ext) {
  414. Logger.Error("Compressed textures are not supported on this platform.");
  415. return;
  416. }
  417. var bpp = header[off_RGBbpp];
  418. dataOffset = header[off_size] + 4;
  419. let computeFormats = false;
  420. if (info.isFourCC) {
  421. fourCC = header[off_pfFourCC];
  422. switch (fourCC) {
  423. case FOURCC_DXT1:
  424. blockBytes = 8;
  425. internalCompressedFormat = (<WEBGL_compressed_texture_s3tc>ext).COMPRESSED_RGBA_S3TC_DXT1_EXT;
  426. break;
  427. case FOURCC_DXT3:
  428. blockBytes = 16;
  429. internalCompressedFormat = (<WEBGL_compressed_texture_s3tc>ext).COMPRESSED_RGBA_S3TC_DXT3_EXT;
  430. break;
  431. case FOURCC_DXT5:
  432. blockBytes = 16;
  433. internalCompressedFormat = (<WEBGL_compressed_texture_s3tc>ext).COMPRESSED_RGBA_S3TC_DXT5_EXT;
  434. break;
  435. case FOURCC_D3DFMT_R16G16B16A16F:
  436. computeFormats = true;
  437. break;
  438. case FOURCC_D3DFMT_R32G32B32A32F:
  439. computeFormats = true;
  440. break;
  441. case FOURCC_DX10:
  442. // There is an additionnal header so dataOffset need to be changed
  443. dataOffset += 5 * 4; // 5 uints
  444. let supported = false;
  445. switch (info.dxgiFormat) {
  446. case DXGI_FORMAT_R16G16B16A16_FLOAT:
  447. case DXGI_FORMAT_R32G32B32A32_FLOAT:
  448. computeFormats = true;
  449. supported = true;
  450. break;
  451. case DXGI_FORMAT_B8G8R8X8_UNORM:
  452. info.isRGB = true;
  453. info.isFourCC = false;
  454. bpp = 32;
  455. supported = true;
  456. break;
  457. }
  458. if (supported) {
  459. break;
  460. }
  461. default:
  462. console.error("Unsupported FourCC code:", Int32ToFourCC(fourCC));
  463. return;
  464. }
  465. }
  466. let rOffset = DDSTools._ExtractLongWordOrder(header[off_RMask]);
  467. let gOffset = DDSTools._ExtractLongWordOrder(header[off_GMask]);
  468. let bOffset = DDSTools._ExtractLongWordOrder(header[off_BMask]);
  469. let aOffset = DDSTools._ExtractLongWordOrder(header[off_AMask]);
  470. if (computeFormats) {
  471. internalCompressedFormat = engine._getRGBABufferInternalSizedFormat(info.textureType);
  472. }
  473. mipmapCount = 1;
  474. if (header[off_flags] & DDSD_MIPMAPCOUNT && loadMipmaps !== false) {
  475. mipmapCount = Math.max(1, header[off_mipmapCount]);
  476. }
  477. const startFace = currentFace || 0;
  478. for (var face = startFace; face < faces; face++) {
  479. width = header[off_width];
  480. height = header[off_height];
  481. for (mip = 0; mip < mipmapCount; ++mip) {
  482. if (lodIndex === -1 || lodIndex === mip) {
  483. // In case of fixed LOD, if the lod has just been uploaded, early exit.
  484. const i = (lodIndex === -1) ? mip : 0;
  485. if (!info.isCompressed && info.isFourCC) {
  486. texture.format = Constants.TEXTUREFORMAT_RGBA;
  487. dataLength = width * height * 4;
  488. var floatArray: Nullable<ArrayBufferView> = null;
  489. if (engine._badOS || engine._badDesktopOS || (!engine.getCaps().textureHalfFloat && !engine.getCaps().textureFloat)) { // Required because iOS has many issues with float and half float generation
  490. if (bpp === 128) {
  491. floatArray = DDSTools._GetFloatAsUIntRGBAArrayBuffer(width, height, data.byteOffset + dataOffset, dataLength, data.buffer, i);
  492. if (sphericalPolynomialFaces && i == 0) {
  493. sphericalPolynomialFaces.push(DDSTools._GetFloatRGBAArrayBuffer(width, height, data.byteOffset + dataOffset, dataLength, data.buffer, i));
  494. }
  495. }
  496. else if (bpp === 64) {
  497. floatArray = DDSTools._GetHalfFloatAsUIntRGBAArrayBuffer(width, height, data.byteOffset + dataOffset, dataLength, data.buffer, i);
  498. if (sphericalPolynomialFaces && i == 0) {
  499. sphericalPolynomialFaces.push(DDSTools._GetHalfFloatAsFloatRGBAArrayBuffer(width, height, data.byteOffset + dataOffset, dataLength, data.buffer, i));
  500. }
  501. }
  502. texture.type = Constants.TEXTURETYPE_UNSIGNED_INT;
  503. }
  504. else {
  505. if (bpp === 128) {
  506. texture.type = Constants.TEXTURETYPE_FLOAT;
  507. floatArray = DDSTools._GetFloatRGBAArrayBuffer(width, height, data.byteOffset + dataOffset, dataLength, data.buffer, i);
  508. if (sphericalPolynomialFaces && i == 0) {
  509. sphericalPolynomialFaces.push(floatArray);
  510. }
  511. } else if (bpp === 64 && !engine.getCaps().textureHalfFloat) {
  512. texture.type = Constants.TEXTURETYPE_FLOAT;
  513. floatArray = DDSTools._GetHalfFloatAsFloatRGBAArrayBuffer(width, height, data.byteOffset + dataOffset, dataLength, data.buffer, i);
  514. if (sphericalPolynomialFaces && i == 0) {
  515. sphericalPolynomialFaces.push(floatArray);
  516. }
  517. } else { // 64
  518. texture.type = Constants.TEXTURETYPE_HALF_FLOAT;
  519. floatArray = DDSTools._GetHalfFloatRGBAArrayBuffer(width, height, data.byteOffset + dataOffset, dataLength, data.buffer, i);
  520. if (sphericalPolynomialFaces && i == 0) {
  521. sphericalPolynomialFaces.push(DDSTools._GetHalfFloatAsFloatRGBAArrayBuffer(width, height, dataOffset, dataLength, data.buffer, i));
  522. }
  523. }
  524. }
  525. if (floatArray) {
  526. engine._uploadDataToTextureDirectly(texture, floatArray, face, i);
  527. }
  528. } else if (info.isRGB) {
  529. texture.type = Constants.TEXTURETYPE_UNSIGNED_INT;
  530. if (bpp === 24) {
  531. texture.format = Constants.TEXTUREFORMAT_RGB;
  532. dataLength = width * height * 3;
  533. byteArray = DDSTools._GetRGBArrayBuffer(width, height, data.byteOffset + dataOffset, dataLength, data.buffer, rOffset, gOffset, bOffset);
  534. engine._uploadDataToTextureDirectly(texture, byteArray, face, i);
  535. } else { // 32
  536. texture.format = Constants.TEXTUREFORMAT_RGBA;
  537. dataLength = width * height * 4;
  538. byteArray = DDSTools._GetRGBAArrayBuffer(width, height, data.byteOffset + dataOffset, dataLength, data.buffer, rOffset, gOffset, bOffset, aOffset);
  539. engine._uploadDataToTextureDirectly(texture, byteArray, face, i);
  540. }
  541. } else if (info.isLuminance) {
  542. var unpackAlignment = engine._getUnpackAlignement();
  543. var unpaddedRowSize = width;
  544. var paddedRowSize = Math.floor((width + unpackAlignment - 1) / unpackAlignment) * unpackAlignment;
  545. dataLength = paddedRowSize * (height - 1) + unpaddedRowSize;
  546. byteArray = DDSTools._GetLuminanceArrayBuffer(width, height, data.byteOffset + dataOffset, dataLength, data.buffer);
  547. texture.format = Constants.TEXTUREFORMAT_LUMINANCE;
  548. texture.type = Constants.TEXTURETYPE_UNSIGNED_INT;
  549. engine._uploadDataToTextureDirectly(texture, byteArray, face, i);
  550. } else {
  551. dataLength = Math.max(4, width) / 4 * Math.max(4, height) / 4 * blockBytes;
  552. byteArray = new Uint8Array(data.buffer, data.byteOffset + dataOffset, dataLength);
  553. texture.type = Constants.TEXTURETYPE_UNSIGNED_INT;
  554. engine._uploadCompressedDataToTextureDirectly(texture, internalCompressedFormat, width, height, byteArray, face, i);
  555. }
  556. }
  557. dataOffset += bpp ? (width * height * (bpp / 8)) : dataLength;
  558. width *= 0.5;
  559. height *= 0.5;
  560. width = Math.max(1.0, width);
  561. height = Math.max(1.0, height);
  562. }
  563. if (currentFace !== undefined) {
  564. // Loading a single face
  565. break;
  566. }
  567. }
  568. if (sphericalPolynomialFaces && sphericalPolynomialFaces.length > 0) {
  569. info.sphericalPolynomial = CubeMapToSphericalPolynomialTools.ConvertCubeMapToSphericalPolynomial({
  570. size: header[off_width],
  571. right: sphericalPolynomialFaces[0],
  572. left: sphericalPolynomialFaces[1],
  573. up: sphericalPolynomialFaces[2],
  574. down: sphericalPolynomialFaces[3],
  575. front: sphericalPolynomialFaces[4],
  576. back: sphericalPolynomialFaces[5],
  577. format: Constants.TEXTUREFORMAT_RGBA,
  578. type: Constants.TEXTURETYPE_FLOAT,
  579. gammaSpace: false,
  580. });
  581. } else {
  582. info.sphericalPolynomial = undefined;
  583. }
  584. }
  585. }
  586. declare module "../Engines/thinEngine" {
  587. export interface ThinEngine {
  588. /**
  589. * Create a cube texture from prefiltered data (ie. the mipmaps contain ready to use data for PBR reflection)
  590. * @param rootUrl defines the url where the file to load is located
  591. * @param scene defines the current scene
  592. * @param lodScale defines scale to apply to the mip map selection
  593. * @param lodOffset defines offset to apply to the mip map selection
  594. * @param onLoad defines an optional callback raised when the texture is loaded
  595. * @param onError defines an optional callback raised if there is an issue to load the texture
  596. * @param format defines the format of the data
  597. * @param forcedExtension defines the extension to use to pick the right loader
  598. * @param createPolynomials defines wheter or not to create polynomails harmonics for the texture
  599. * @returns the cube texture as an InternalTexture
  600. */
  601. createPrefilteredCubeTexture(rootUrl: string, scene: Nullable<Scene>, lodScale: number, lodOffset: number,
  602. onLoad?: Nullable<(internalTexture: Nullable<InternalTexture>) => void>,
  603. onError?: Nullable<(message?: string, exception?: any) => void>,
  604. format?: number, forcedExtension?: any,
  605. createPolynomials?: boolean): InternalTexture;
  606. }
  607. }
  608. /**
  609. * Create a cube texture from prefiltered data (ie. the mipmaps contain ready to use data for PBR reflection)
  610. * @param rootUrl defines the url where the file to load is located
  611. * @param scene defines the current scene
  612. * @param lodScale defines scale to apply to the mip map selection
  613. * @param lodOffset defines offset to apply to the mip map selection
  614. * @param onLoad defines an optional callback raised when the texture is loaded
  615. * @param onError defines an optional callback raised if there is an issue to load the texture
  616. * @param format defines the format of the data
  617. * @param forcedExtension defines the extension to use to pick the right loader
  618. * @param createPolynomials defines wheter or not to create polynomails harmonics for the texture
  619. * @returns the cube texture as an InternalTexture
  620. */
  621. ThinEngine.prototype.createPrefilteredCubeTexture = function(rootUrl: string, scene: Nullable<Scene>, lodScale: number, lodOffset: number,
  622. onLoad: Nullable<(internalTexture: Nullable<InternalTexture>) => void> = null,
  623. onError: Nullable<(message?: string, exception?: any) => void> = null,
  624. format?: number, forcedExtension: any = null,
  625. createPolynomials: boolean = true): InternalTexture {
  626. var callback = (loadData: any) => {
  627. if (!loadData) {
  628. if (onLoad) {
  629. onLoad(null);
  630. }
  631. return;
  632. }
  633. let texture = loadData.texture as InternalTexture;
  634. if (!createPolynomials) {
  635. texture._sphericalPolynomial = new SphericalPolynomial();
  636. }
  637. else if (loadData.info.sphericalPolynomial) {
  638. texture._sphericalPolynomial = loadData.info.sphericalPolynomial;
  639. }
  640. texture._source = InternalTextureSource.CubePrefiltered;
  641. if (this.getCaps().textureLOD) {
  642. // Do not add extra process if texture lod is supported.
  643. if (onLoad) {
  644. onLoad(texture);
  645. }
  646. return;
  647. }
  648. const mipSlices = 3;
  649. var gl = this._gl;
  650. const width = loadData.width;
  651. if (!width) {
  652. return;
  653. }
  654. const textures: BaseTexture[] = [];
  655. for (let i = 0; i < mipSlices; i++) {
  656. //compute LOD from even spacing in smoothness (matching shader calculation)
  657. let smoothness = i / (mipSlices - 1);
  658. let roughness = 1 - smoothness;
  659. let minLODIndex = lodOffset; // roughness = 0
  660. let maxLODIndex = Scalar.Log2(width) * lodScale + lodOffset; // roughness = 1
  661. let lodIndex = minLODIndex + (maxLODIndex - minLODIndex) * roughness;
  662. let mipmapIndex = Math.round(Math.min(Math.max(lodIndex, 0), maxLODIndex));
  663. var glTextureFromLod = new InternalTexture(this, InternalTextureSource.Temp);
  664. glTextureFromLod.type = texture.type;
  665. glTextureFromLod.format = texture.format;
  666. glTextureFromLod.width = Math.pow(2, Math.max(Scalar.Log2(width) - mipmapIndex, 0));
  667. glTextureFromLod.height = glTextureFromLod.width;
  668. glTextureFromLod.isCube = true;
  669. this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, glTextureFromLod, true);
  670. gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
  671. gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
  672. gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  673. gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  674. if (loadData.isDDS) {
  675. var info: DDSInfo = loadData.info;
  676. var data: any = loadData.data;
  677. this._unpackFlipY(info.isCompressed);
  678. DDSTools.UploadDDSLevels(this, glTextureFromLod, data, info, true, 6, mipmapIndex);
  679. }
  680. else {
  681. Logger.Warn("DDS is the only prefiltered cube map supported so far.");
  682. }
  683. this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, null);
  684. // Wrap in a base texture for easy binding.
  685. const lodTexture = new BaseTexture(scene);
  686. lodTexture.isCube = true;
  687. lodTexture._texture = glTextureFromLod;
  688. glTextureFromLod.isReady = true;
  689. textures.push(lodTexture);
  690. }
  691. texture._lodTextureHigh = textures[2];
  692. texture._lodTextureMid = textures[1];
  693. texture._lodTextureLow = textures[0];
  694. if (onLoad) {
  695. onLoad(texture);
  696. }
  697. };
  698. return this.createCubeTexture(rootUrl, scene, null, false, callback, onError, format, forcedExtension, createPolynomials, lodScale, lodOffset);
  699. };