textureTools.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. import { Texture } from "../Materials/Textures/texture.js";
  2. import { RenderTargetTexture } from "../Materials/Textures/renderTargetTexture.js";
  3. import { PassPostProcess } from "../PostProcesses/passPostProcess.js";
  4. import { PostProcess } from "../PostProcesses/postProcess.js";
  5. /**
  6. * Uses the GPU to create a copy texture rescaled at a given size
  7. * @param texture Texture to copy from
  8. * @param width defines the desired width
  9. * @param height defines the desired height
  10. * @param useBilinearMode defines if bilinear mode has to be used
  11. * @returns the generated texture
  12. */
  13. export function CreateResizedCopy(texture, width, height, useBilinearMode = true) {
  14. const scene = texture.getScene();
  15. const engine = scene.getEngine();
  16. const rtt = new RenderTargetTexture("resized" + texture.name, { width: width, height: height }, scene, !texture.noMipmap, true, texture._texture.type, false, texture.samplingMode, false);
  17. rtt.wrapU = texture.wrapU;
  18. rtt.wrapV = texture.wrapV;
  19. rtt.uOffset = texture.uOffset;
  20. rtt.vOffset = texture.vOffset;
  21. rtt.uScale = texture.uScale;
  22. rtt.vScale = texture.vScale;
  23. rtt.uAng = texture.uAng;
  24. rtt.vAng = texture.vAng;
  25. rtt.wAng = texture.wAng;
  26. rtt.coordinatesIndex = texture.coordinatesIndex;
  27. rtt.level = texture.level;
  28. rtt.anisotropicFilteringLevel = texture.anisotropicFilteringLevel;
  29. rtt._texture.isReady = false;
  30. texture.wrapU = Texture.CLAMP_ADDRESSMODE;
  31. texture.wrapV = Texture.CLAMP_ADDRESSMODE;
  32. const passPostProcess = new PassPostProcess("pass", 1, null, useBilinearMode ? Texture.BILINEAR_SAMPLINGMODE : Texture.NEAREST_SAMPLINGMODE, engine, false, 0);
  33. passPostProcess.externalTextureSamplerBinding = true;
  34. passPostProcess.getEffect().executeWhenCompiled(() => {
  35. passPostProcess.onApply = function (effect) {
  36. effect.setTexture("textureSampler", texture);
  37. };
  38. const internalTexture = rtt.renderTarget;
  39. if (internalTexture) {
  40. scene.postProcessManager.directRender([passPostProcess], internalTexture);
  41. engine.unBindFramebuffer(internalTexture);
  42. rtt.disposeFramebufferObjects();
  43. passPostProcess.dispose();
  44. rtt.getInternalTexture().isReady = true;
  45. }
  46. });
  47. return rtt;
  48. }
  49. /**
  50. * Apply a post process to a texture
  51. * @param postProcessName name of the fragment post process
  52. * @param internalTexture the texture to encode
  53. * @param scene the scene hosting the texture
  54. * @param type type of the output texture. If not provided, use the one from internalTexture
  55. * @param samplingMode sampling mode to use to sample the source texture. If not provided, use the one from internalTexture
  56. * @param format format of the output texture. If not provided, use the one from internalTexture
  57. * @param width width of the output texture. If not provided, use the one from internalTexture
  58. * @param height height of the output texture. If not provided, use the one from internalTexture
  59. * @returns a promise with the internalTexture having its texture replaced by the result of the processing
  60. */
  61. export function ApplyPostProcess(postProcessName, internalTexture, scene, type, samplingMode, format, width, height) {
  62. // Gets everything ready.
  63. const engine = internalTexture.getEngine();
  64. internalTexture.isReady = false;
  65. samplingMode = samplingMode ?? internalTexture.samplingMode;
  66. type = type ?? internalTexture.type;
  67. format = format ?? internalTexture.format;
  68. width = width ?? internalTexture.width;
  69. height = height ?? internalTexture.height;
  70. if (type === -1) {
  71. type = 0;
  72. }
  73. return new Promise((resolve) => {
  74. // Create the post process
  75. const postProcess = new PostProcess("postprocess", postProcessName, null, null, 1, null, samplingMode, engine, false, undefined, type, undefined, null, false, format);
  76. postProcess.externalTextureSamplerBinding = true;
  77. // Hold the output of the decoding.
  78. const encodedTexture = engine.createRenderTargetTexture({ width: width, height: height }, {
  79. generateDepthBuffer: false,
  80. generateMipMaps: false,
  81. generateStencilBuffer: false,
  82. samplingMode,
  83. type,
  84. format,
  85. });
  86. postProcess.getEffect().executeWhenCompiled(() => {
  87. // PP Render Pass
  88. postProcess.onApply = (effect) => {
  89. effect._bindTexture("textureSampler", internalTexture);
  90. effect.setFloat2("scale", 1, 1);
  91. };
  92. scene.postProcessManager.directRender([postProcess], encodedTexture, true);
  93. // Cleanup
  94. engine.restoreDefaultFramebuffer();
  95. engine._releaseTexture(internalTexture);
  96. if (postProcess) {
  97. postProcess.dispose();
  98. }
  99. // Internal Swap
  100. encodedTexture._swapAndDie(internalTexture);
  101. // Ready to get rolling again.
  102. internalTexture.type = type;
  103. internalTexture.format = 5;
  104. internalTexture.isReady = true;
  105. resolve(internalTexture);
  106. });
  107. });
  108. }
  109. // ref: http://stackoverflow.com/questions/32633585/how-do-you-convert-to-half-floats-in-javascript
  110. let floatView;
  111. let int32View;
  112. /**
  113. * Converts a number to half float
  114. * @param value number to convert
  115. * @returns converted number
  116. */
  117. export function ToHalfFloat(value) {
  118. if (!floatView) {
  119. floatView = new Float32Array(1);
  120. int32View = new Int32Array(floatView.buffer);
  121. }
  122. floatView[0] = value;
  123. const x = int32View[0];
  124. let bits = (x >> 16) & 0x8000; /* Get the sign */
  125. let m = (x >> 12) & 0x07ff; /* Keep one extra bit for rounding */
  126. const e = (x >> 23) & 0xff; /* Using int is faster here */
  127. /* If zero, or denormal, or exponent underflows too much for a denormal
  128. * half, return signed zero. */
  129. if (e < 103) {
  130. return bits;
  131. }
  132. /* If NaN, return NaN. If Inf or exponent overflow, return Inf. */
  133. if (e > 142) {
  134. bits |= 0x7c00;
  135. /* If exponent was 0xff and one mantissa bit was set, it means NaN,
  136. * not Inf, so make sure we set one mantissa bit too. */
  137. bits |= (e == 255 ? 0 : 1) && x & 0x007fffff;
  138. return bits;
  139. }
  140. /* If exponent underflows but not too much, return a denormal */
  141. if (e < 113) {
  142. m |= 0x0800;
  143. /* Extra rounding may overflow and set mantissa to 0 and exponent
  144. * to 1, which is OK. */
  145. bits |= (m >> (114 - e)) + ((m >> (113 - e)) & 1);
  146. return bits;
  147. }
  148. bits |= ((e - 112) << 10) | (m >> 1);
  149. bits += m & 1;
  150. return bits;
  151. }
  152. /**
  153. * Converts a half float to a number
  154. * @param value half float to convert
  155. * @returns converted half float
  156. */
  157. export function FromHalfFloat(value) {
  158. const s = (value & 0x8000) >> 15;
  159. const e = (value & 0x7c00) >> 10;
  160. const f = value & 0x03ff;
  161. if (e === 0) {
  162. return (s ? -1 : 1) * Math.pow(2, -14) * (f / Math.pow(2, 10));
  163. }
  164. else if (e == 0x1f) {
  165. return f ? NaN : (s ? -1 : 1) * Infinity;
  166. }
  167. return (s ? -1 : 1) * Math.pow(2, e - 15) * (1 + f / Math.pow(2, 10));
  168. }
  169. const ProcessAsync = async (texture, width, height, face, lod) => {
  170. const scene = texture.getScene();
  171. const engine = scene.getEngine();
  172. let lodPostProcess;
  173. if (!texture.isCube) {
  174. lodPostProcess = new PostProcess("lod", "lod", ["lod", "gamma"], null, 1.0, null, Texture.NEAREST_NEAREST_MIPNEAREST, engine);
  175. }
  176. else {
  177. const faceDefines = ["#define POSITIVEX", "#define NEGATIVEX", "#define POSITIVEY", "#define NEGATIVEY", "#define POSITIVEZ", "#define NEGATIVEZ"];
  178. lodPostProcess = new PostProcess("lodCube", "lodCube", ["lod", "gamma"], null, 1.0, null, Texture.NEAREST_NEAREST_MIPNEAREST, engine, false, faceDefines[face]);
  179. }
  180. await new Promise((resolve) => {
  181. lodPostProcess.getEffect().executeWhenCompiled(() => {
  182. resolve(0);
  183. });
  184. });
  185. const rtt = new RenderTargetTexture("temp", { width: width, height: height }, scene, false);
  186. lodPostProcess.onApply = function (effect) {
  187. effect.setTexture("textureSampler", texture);
  188. effect.setFloat("lod", lod);
  189. effect.setBool("gamma", texture.gammaSpace);
  190. };
  191. const internalTexture = texture.getInternalTexture();
  192. try {
  193. if (rtt.renderTarget && internalTexture) {
  194. const samplingMode = internalTexture.samplingMode;
  195. if (lod !== 0) {
  196. texture.updateSamplingMode(Texture.NEAREST_NEAREST_MIPNEAREST);
  197. }
  198. else {
  199. texture.updateSamplingMode(Texture.NEAREST_NEAREST);
  200. }
  201. scene.postProcessManager.directRender([lodPostProcess], rtt.renderTarget, true);
  202. texture.updateSamplingMode(samplingMode);
  203. //Reading datas from WebGL
  204. const bufferView = await engine.readPixels(0, 0, width, height);
  205. const data = new Uint8Array(bufferView.buffer, 0, bufferView.byteLength);
  206. // Unbind
  207. engine.unBindFramebuffer(rtt.renderTarget);
  208. return data;
  209. }
  210. else {
  211. throw Error("Render to texture failed.");
  212. }
  213. }
  214. finally {
  215. rtt.dispose();
  216. lodPostProcess.dispose();
  217. }
  218. };
  219. /**
  220. * Gets the data of the specified texture by rendering it to an intermediate RGBA texture and retrieving the bytes from it.
  221. * This is convienent to get 8-bit RGBA values for a texture in a GPU compressed format.
  222. * @param texture the source texture
  223. * @param width the width of the result, which does not have to match the source texture width
  224. * @param height the height of the result, which does not have to match the source texture height
  225. * @param face if the texture has multiple faces, the face index to use for the source
  226. * @param lod if the texture has multiple LODs, the lod index to use for the source
  227. * @returns the 8-bit texture data
  228. */
  229. export async function GetTextureDataAsync(texture, width, height, face = 0, lod = 0) {
  230. if (!texture.isReady() && texture._texture) {
  231. await new Promise((resolve, reject) => {
  232. if (texture._texture === null) {
  233. reject(0);
  234. return;
  235. }
  236. texture._texture.onLoadedObservable.addOnce(() => {
  237. resolve(0);
  238. });
  239. });
  240. }
  241. return await ProcessAsync(texture, width, height, face, lod);
  242. }
  243. /**
  244. * Class used to host texture specific utilities
  245. */
  246. export const TextureTools = {
  247. /**
  248. * Uses the GPU to create a copy texture rescaled at a given size
  249. * @param texture Texture to copy from
  250. * @param width defines the desired width
  251. * @param height defines the desired height
  252. * @param useBilinearMode defines if bilinear mode has to be used
  253. * @returns the generated texture
  254. */
  255. CreateResizedCopy,
  256. /**
  257. * Apply a post process to a texture
  258. * @param postProcessName name of the fragment post process
  259. * @param internalTexture the texture to encode
  260. * @param scene the scene hosting the texture
  261. * @param type type of the output texture. If not provided, use the one from internalTexture
  262. * @param samplingMode sampling mode to use to sample the source texture. If not provided, use the one from internalTexture
  263. * @param format format of the output texture. If not provided, use the one from internalTexture
  264. * @returns a promise with the internalTexture having its texture replaced by the result of the processing
  265. */
  266. ApplyPostProcess,
  267. /**
  268. * Converts a number to half float
  269. * @param value number to convert
  270. * @returns converted number
  271. */
  272. ToHalfFloat,
  273. /**
  274. * Converts a half float to a number
  275. * @param value half float to convert
  276. * @returns converted half float
  277. */
  278. FromHalfFloat,
  279. /**
  280. * Gets the data of the specified texture by rendering it to an intermediate RGBA texture and retrieving the bytes from it.
  281. * This is convienent to get 8-bit RGBA values for a texture in a GPU compressed format.
  282. * @param texture the source texture
  283. * @param width the width of the result, which does not have to match the source texture width
  284. * @param height the height of the result, which does not have to match the source texture height
  285. * @param face if the texture has multiple faces, the face index to use for the source
  286. * @param channels a filter for which of the RGBA channels to return in the result
  287. * @param lod if the texture has multiple LODs, the lod index to use for the source
  288. * @returns the 8-bit texture data
  289. */
  290. GetTextureDataAsync,
  291. };
  292. //# sourceMappingURL=textureTools.js.map