浏览代码

CreateEnvTextureAsync not using canvas anymore

Popov72 4 年之前
父节点
当前提交
5c2463ec2c
共有 4 个文件被更改,包括 181 次插入109 次删除
  1. 76 105
      src/Misc/environmentTextureTools.ts
  2. 58 0
      src/Misc/rgbdTextureTools.ts
  3. 1 1
      src/Misc/screenshotTools.ts
  4. 46 3
      src/Misc/tools.ts

+ 76 - 105
src/Misc/environmentTextureTools.ts

@@ -18,6 +18,7 @@ import "../Shaders/rgbdEncode.fragment";
 import "../Shaders/rgbdDecode.fragment";
 import { Engine } from '../Engines/engine';
 import { ThinEngine } from '../Engines/thinEngine';
+import { RGBDTextureTools } from './rgbdTextureTools';
 
 /**
  * Raw texture data and descriptor sufficient for WebGL texture upload
@@ -153,19 +154,11 @@ export class EnvironmentTextureTools {
         }
 
         let engine = internalTexture.getEngine() as Engine;
-        if (engine && engine.premultipliedAlpha) {
-            return Promise.reject("Env texture can only be created when the engine is created with the premultipliedAlpha option set to false.");
-        }
 
         if (texture.textureType === Constants.TEXTURETYPE_UNSIGNED_INT) {
             return Promise.reject("The cube texture should allow HDR (Full Float or Half Float).");
         }
 
-        let canvas = engine.getRenderingCanvas();
-        if (!canvas) {
-            return Promise.reject("Env texture can only be created when the engine is associated to a canvas.");
-        }
-
         let textureType = Constants.TEXTURETYPE_FLOAT;
         if (!engine.getCaps().textureFloatRender) {
             textureType = Constants.TEXTURETYPE_HALF_FLOAT;
@@ -177,121 +170,99 @@ export class EnvironmentTextureTools {
         let cubeWidth = internalTexture.width;
         let hostingScene = new Scene(engine);
         let specularTextures: { [key: number]: ArrayBuffer } = {};
-        let promises: Promise<void>[] = [];
+
+        // As we are going to readPixels the faces of the cube, make sure the drawing/update commands for the cube texture are fully sent to the GPU in case it is drawn for the first time in this very frame!
+        engine.flushFramebuffer();
 
         // Read and collect all mipmaps data from the cube.
-        let mipmapsCount = Math.floor(Scalar.ILog2(internalTexture.width));
+        let mipmapsCount = Scalar.ILog2(internalTexture.width);
         for (let i = 0; i <= mipmapsCount; i++) {
             let faceWidth = Math.pow(2, mipmapsCount - i);
 
             // All faces of the cube.
             for (let face = 0; face < 6; face++) {
-                let data = await texture.readPixels(face, i);
-
-                // Creates a temp texture with the face data.
-                let tempTexture = engine.createRawTexture(data, faceWidth, faceWidth, Constants.TEXTUREFORMAT_RGBA, false, false, Constants.TEXTURE_NEAREST_SAMPLINGMODE, null, textureType);
-                // And rgbdEncode them.
-                let promise = new Promise<void>((resolve, reject) => {
-                    let rgbdPostProcess = new PostProcess("rgbdEncode", "rgbdEncode", null, null, 1, null, Constants.TEXTURE_NEAREST_SAMPLINGMODE, engine, false, undefined, Constants.TEXTURETYPE_UNSIGNED_INT, undefined, null, false);
-                    rgbdPostProcess.getEffect().executeWhenCompiled(() => {
-                        rgbdPostProcess.onApply = (effect) => {
-                            effect._bindTexture("textureSampler", tempTexture);
-                        };
+                let faceData = await texture.readPixels(face, i);
 
-                        // As the process needs to happen on the main canvas, keep track of the current size
-                        let currentW = engine.getRenderWidth();
-                        let currentH = engine.getRenderHeight();
-
-                        // Set the desired size for the texture
-                        engine.setSize(faceWidth, faceWidth);
-                        hostingScene.postProcessManager.directRender([rgbdPostProcess], null);
-
-                        // Reading datas from WebGL
-                        Tools.ToBlob(canvas!, (blob) => {
-                            let fileReader = new FileReader();
-                            fileReader.onload = (event: any) => {
-                                let arrayBuffer = event.target!.result as ArrayBuffer;
-                                specularTextures[i * 6 + face] = arrayBuffer;
-                                resolve();
-                            };
-                            fileReader.readAsArrayBuffer(blob!);
-                        });
-
-                        // Reapply the previous canvas size
-                        engine.setSize(currentW, currentH);
-                    });
-                });
-                promises.push(promise);
-            }
-        }
+                let tempTexture = engine.createRawTexture(faceData, faceWidth, faceWidth, Constants.TEXTUREFORMAT_RGBA, false, true, Constants.TEXTURE_NEAREST_SAMPLINGMODE, null, textureType);
 
-        // Once all the textures haves been collected as RGBD stored in PNGs
-        return Promise.all(promises).then(() => {
-            // We can delete the hosting scene keeping track of all the creation objects
-            hostingScene.dispose();
-
-            // Creates the json header for the env texture
-            let info: EnvironmentTextureInfo = {
-                version: 1,
-                width: cubeWidth,
-                irradiance: this._CreateEnvTextureIrradiance(texture),
-                specular: {
-                    mipmaps: [],
-                    lodGenerationScale: texture.lodGenerationScale
-                }
-            };
+                await RGBDTextureTools.EncodeTextureToRGBD(tempTexture, hostingScene, textureType);
 
-            // Sets the specular image data information
-            let position = 0;
-            for (let i = 0; i <= mipmapsCount; i++) {
-                for (let face = 0; face < 6; face++) {
-                    let byteLength = specularTextures[i * 6 + face].byteLength;
-                    info.specular.mipmaps.push({
-                        length: byteLength,
-                        position: position
-                    });
-                    position += byteLength;
-                }
+                engine.flushFramebuffer(); // make sure the tempTexture has its data up to date when reading them just below
+
+                const rgbdEncodedData = await engine._readTexturePixels(tempTexture, faceWidth, faceWidth);
+
+                const pngEncodedata = await Tools.DumpDataAsync(faceWidth, faceWidth, rgbdEncodedData, "image/png", undefined, false, true);
+
+                specularTextures[i * 6 + face] = pngEncodedata as ArrayBuffer;
+
+                tempTexture.dispose();
             }
+        }
 
-            // Encode the JSON as an array buffer
-            let infoString = JSON.stringify(info);
-            let infoBuffer = new ArrayBuffer(infoString.length + 1);
-            let infoView = new Uint8Array(infoBuffer); // Limited to ascii subset matching unicode.
-            for (let i = 0, strLen = infoString.length; i < strLen; i++) {
-                infoView[i] = infoString.charCodeAt(i);
+        // We can delete the hosting scene keeping track of all the creation objects
+        hostingScene.dispose();
+
+        // Creates the json header for the env texture
+        let info: EnvironmentTextureInfo = {
+            version: 1,
+            width: cubeWidth,
+            irradiance: this._CreateEnvTextureIrradiance(texture),
+            specular: {
+                mipmaps: [],
+                lodGenerationScale: texture.lodGenerationScale
             }
-            // Ends up with a null terminator for easier parsing
-            infoView[infoString.length] = 0x00;
-
-            // Computes the final required size and creates the storage
-            let totalSize = EnvironmentTextureTools._MagicBytes.length + position + infoBuffer.byteLength;
-            let finalBuffer = new ArrayBuffer(totalSize);
-            let finalBufferView = new Uint8Array(finalBuffer);
-            let dataView = new DataView(finalBuffer);
-
-            // Copy the magic bytes identifying the file in
-            let pos = 0;
-            for (let i = 0; i < EnvironmentTextureTools._MagicBytes.length; i++) {
-                dataView.setUint8(pos++, EnvironmentTextureTools._MagicBytes[i]);
+        };
+
+        // Sets the specular image data information
+        let position = 0;
+        for (let i = 0; i <= mipmapsCount; i++) {
+            for (let face = 0; face < 6; face++) {
+                let byteLength = specularTextures[i * 6 + face].byteLength;
+                info.specular.mipmaps.push({
+                    length: byteLength,
+                    position: position
+                });
+                position += byteLength;
             }
+        }
 
-            // Add the json info
-            finalBufferView.set(new Uint8Array(infoBuffer), pos);
-            pos += infoBuffer.byteLength;
+        // Encode the JSON as an array buffer
+        let infoString = JSON.stringify(info);
+        let infoBuffer = new ArrayBuffer(infoString.length + 1);
+        let infoView = new Uint8Array(infoBuffer); // Limited to ascii subset matching unicode.
+        for (let i = 0, strLen = infoString.length; i < strLen; i++) {
+            infoView[i] = infoString.charCodeAt(i);
+        }
+        // Ends up with a null terminator for easier parsing
+        infoView[infoString.length] = 0x00;
 
-            // Finally inserts the texture data
-            for (let i = 0; i <= mipmapsCount; i++) {
-                for (let face = 0; face < 6; face++) {
-                    let dataBuffer = specularTextures[i * 6 + face];
-                    finalBufferView.set(new Uint8Array(dataBuffer), pos);
-                    pos += dataBuffer.byteLength;
-                }
+        // Computes the final required size and creates the storage
+        let totalSize = EnvironmentTextureTools._MagicBytes.length + position + infoBuffer.byteLength;
+        let finalBuffer = new ArrayBuffer(totalSize);
+        let finalBufferView = new Uint8Array(finalBuffer);
+        let dataView = new DataView(finalBuffer);
+
+        // Copy the magic bytes identifying the file in
+        let pos = 0;
+        for (let i = 0; i < EnvironmentTextureTools._MagicBytes.length; i++) {
+            dataView.setUint8(pos++, EnvironmentTextureTools._MagicBytes[i]);
+        }
+
+        // Add the json info
+        finalBufferView.set(new Uint8Array(infoBuffer), pos);
+        pos += infoBuffer.byteLength;
+
+        // Finally inserts the texture data
+        for (let i = 0; i <= mipmapsCount; i++) {
+            for (let face = 0; face < 6; face++) {
+                let dataBuffer = specularTextures[i * 6 + face];
+                finalBufferView.set(new Uint8Array(dataBuffer), pos);
+                pos += dataBuffer.byteLength;
             }
+        }
 
-            // Voila
-            return finalBuffer;
-        });
+        // Voila
+        return finalBuffer;
     }
 
     /**

+ 58 - 0
src/Misc/rgbdTextureTools.ts

@@ -6,6 +6,8 @@ import { Engine } from '../Engines/engine';
 import "../Engines/Extensions/engine.renderTarget";
 
 declare type Texture = import("../Materials/Textures/texture").Texture;
+declare type InternalTexture = import("../Materials/Textures/internalTexture").InternalTexture;
+declare type Scene = import("../scene").Scene;
 
 /**
  * Class used to host RGBD texture specific utilities
@@ -85,4 +87,60 @@ export class RGBDTextureTools {
             }
         });
     }
+
+    /**
+     * Encode the texture to RGBD if possible.
+     * @param internalTexture the texture to encode
+     * @param scene the scene hosting the texture
+     * @param outputTextureType type of the texture in which the encoding is performed
+     */
+    public static EncodeTextureToRGBD(internalTexture: InternalTexture, scene: Scene, outputTextureType = Constants.TEXTURETYPE_UNSIGNED_BYTE): Promise<InternalTexture> {
+        // Gets everything ready.
+        const engine = internalTexture.getEngine() as Engine;
+
+        internalTexture.isReady = false;
+
+        return new Promise((resolve) => {
+            // Encode the texture if possible
+            // Simply run through the encode PP.
+            const rgbdPostProcess = new PostProcess("rgbdEncode", "rgbdEncode", null, null, 1, null, Constants.TEXTURE_NEAREST_SAMPLINGMODE, engine, false, undefined, outputTextureType, undefined, null, false);
+
+            // Hold the output of the decoding.
+            const encodedTexture = engine.createRenderTargetTexture({ width: internalTexture.width, height: internalTexture.height }, {
+                generateDepthBuffer: false,
+                generateMipMaps: false,
+                generateStencilBuffer: false,
+                samplingMode: Constants.TEXTURE_NEAREST_SAMPLINGMODE,
+                type: outputTextureType,
+                format: Constants.TEXTUREFORMAT_RGBA
+            });
+
+            rgbdPostProcess.getEffect().executeWhenCompiled(() => {
+                // PP Render Pass
+                rgbdPostProcess.onApply = (effect) => {
+                    effect._bindTexture("textureSampler", internalTexture);
+                    effect.setFloat2("scale", 1, 1);
+                };
+                scene.postProcessManager.directRender([rgbdPostProcess!], encodedTexture, true);
+
+                // Cleanup
+                engine.restoreDefaultFramebuffer();
+                engine._releaseTexture(internalTexture);
+                engine._releaseFramebufferObjects(encodedTexture);
+                if (rgbdPostProcess) {
+                    rgbdPostProcess.dispose();
+                }
+
+                // Internal Swap
+                encodedTexture._swapAndDie(internalTexture);
+
+                // Ready to get rolling again.
+                internalTexture.type = outputTextureType;
+                internalTexture.format = Constants.TEXTUREFORMAT_RGBA;
+                internalTexture.isReady = true;
+
+                resolve(internalTexture);
+            });
+        });
+    }
 }

+ 1 - 1
src/Misc/screenshotTools.ts

@@ -141,7 +141,7 @@ export class ScreenshotTools {
         texture.renderSprites = renderSprites;
         engine.onEndFrameObservable.addOnce(() => {
             texture.readPixels()!.then((data) => {
-                Tools.DumpData(width, height, data, successCallback, mimeType, fileName, true);
+                Tools.DumpData(width, height, data, successCallback as (data: string | ArrayBuffer) => void, mimeType, fileName, true);
                 texture.dispose();
 
                 if (previousCamera) {

+ 46 - 3
src/Misc/tools.ts

@@ -580,7 +580,7 @@ export class Tools {
 
         const data = new Uint8Array(bufferView.buffer);
 
-        Tools.DumpData(width, height, data, successCallback, mimeType, fileName, true);
+        Tools.DumpData(width, height, data, successCallback as (data: string | ArrayBuffer) => void, mimeType, fileName, true);
     }
 
     /**
@@ -592,8 +592,9 @@ export class Tools {
      * @param mimeType defines the mime type of the result
      * @param fileName defines the filename to download. If present, the result will automatically be downloaded
      * @param invertY true to invert the picture in the Y dimension
+     * @param toArrayBuffer true to convert the data to an ArrayBuffer (encoded as `mimeType`) instead of a base64 string
      */
-    public static DumpData(width: number, height: number, data: ArrayBufferView, successCallback?: (data: string) => void, mimeType: string = "image/png", fileName?: string, invertY = false) {
+    public static DumpData(width: number, height: number, data: ArrayBufferView, successCallback?: (data: string | ArrayBuffer) => void, mimeType: string = "image/png", fileName?: string, invertY = false, toArrayBuffer = false) {
         // Create a 2D canvas to store the result
         if (!Tools._ScreenshotCanvas) {
             Tools._ScreenshotCanvas = document.createElement('canvas');
@@ -603,6 +604,17 @@ export class Tools {
         var context = Tools._ScreenshotCanvas.getContext('2d');
 
         if (context) {
+            // Convert if data are float32
+            if (data instanceof Float32Array) {
+                const data2 = new Uint8Array(data.length);
+                let n = data.length;
+                while (n--) {
+                    let v = data[n];
+                    data2[n] = v < 0 ? 0 : v > 1 ? 1 : Math.round(v * 255);
+                }
+                data = data2;
+            }
+
             // Copy the pixels to a 2D canvas
             var imageData = context.createImageData(width, height);
             var castData = <any>(imageData.data);
@@ -628,11 +640,42 @@ export class Tools {
                 canvas = canvas2;
             }
 
-            Tools.EncodeScreenshotCanvasData(successCallback, mimeType, fileName, canvas);
+            if (toArrayBuffer) {
+                Tools.ToBlob(canvas, (blob) => {
+                    let fileReader = new FileReader();
+                    fileReader.onload = (event: any) => {
+                        let arrayBuffer = event.target!.result as ArrayBuffer;
+                        if (successCallback) {
+                            successCallback(arrayBuffer);
+                        }
+                    };
+                    fileReader.readAsArrayBuffer(blob!);
+                });
+            } else {
+                Tools.EncodeScreenshotCanvasData(successCallback, mimeType, fileName, canvas);
+            }
         }
     }
 
     /**
+     * Dumps an array buffer
+     * @param width defines the rendering width
+     * @param height defines the rendering height
+     * @param data the data array
+     * @param successCallback defines the callback triggered once the data are available
+     * @param mimeType defines the mime type of the result
+     * @param fileName defines the filename to download. If present, the result will automatically be downloaded
+     * @param invertY true to invert the picture in the Y dimension
+     * @param toArrayBuffer true to convert the data to an ArrayBuffer (encoded as `mimeType`) instead of a base64 string
+     * @return a promise that resolve to the final data
+     */
+    public static DumpDataAsync(width: number, height: number, data: ArrayBufferView, mimeType: string = "image/png", fileName?: string, invertY = false, toArrayBuffer = false): Promise<string | ArrayBuffer> {
+        return new Promise((resolve) => {
+            Tools.DumpData(width, height, data, (result) => resolve(result), mimeType, fileName, invertY, toArrayBuffer);
+        });
+    }
+
+    /**
      * Converts the canvas data to blob.
      * This acts as a polyfill for browsers not supporting the to blob function.
      * @param canvas Defines the canvas to extract the data from