ソースを参照

Don't break backward compat

Popov72 4 年 前
コミット
362b28d15c

+ 10 - 3
src/Engines/Extensions/engine.readTexture.ts

@@ -6,10 +6,13 @@ declare module "../../Engines/thinEngine" {
     export interface ThinEngine {
         /** @hidden */
         _readTexturePixels(texture: InternalTexture, width: number, height: number, faceIndex?: number, level?: number, buffer?: Nullable<ArrayBufferView>, flushRenderer?: boolean): Promise<ArrayBufferView>;
+
+        /** @hidden */
+        _readTexturePixelsSync(texture: InternalTexture, width: number, height: number, faceIndex?: number, level?: number, buffer?: Nullable<ArrayBufferView>, flushRenderer?: boolean): ArrayBufferView;
     }
 }
 
-ThinEngine.prototype._readTexturePixels = function(texture: InternalTexture, width: number, height: number, faceIndex = -1, level = 0, buffer: Nullable<ArrayBufferView> = null, flushRenderer = true): Promise<ArrayBufferView> {
+ThinEngine.prototype._readTexturePixelsSync = function(texture: InternalTexture, width: number, height: number, faceIndex = -1, level = 0, buffer: Nullable<ArrayBufferView> = null, flushRenderer = true): ArrayBufferView {
     let gl = this._gl;
     if (!gl) {
         throw new Error ("Engine does not have gl rendering context.");
@@ -55,5 +58,9 @@ ThinEngine.prototype._readTexturePixels = function(texture: InternalTexture, wid
     gl.readPixels(0, 0, width, height, gl.RGBA, readType, <DataView>buffer);
     gl.bindFramebuffer(gl.FRAMEBUFFER, this._currentFramebuffer);
 
-    return Promise.resolve(buffer);
-};
+    return buffer;
+};
+
+ThinEngine.prototype._readTexturePixels = function(texture: InternalTexture, width: number, height: number, faceIndex = -1, level = 0, buffer: Nullable<ArrayBufferView> = null, flushRenderer = true): Promise<ArrayBufferView> {
+    return Promise.resolve(this._readTexturePixelsSync(texture, width, height, faceIndex, level, buffer, flushRenderer));
+}

+ 3 - 0
src/Engines/engineFeatures.ts

@@ -45,6 +45,9 @@ export interface EngineFeatures {
     /** Indicates that the switch/case construct is supported in shaders */
     supportSwitchCaseInShader: boolean;
 
+    /** Indicates that synchronous texture reading is supported */
+    supportSyncTextureRead: boolean;
+
     /** @hidden */
     _collectUbosUpdatedInFrame: boolean;
 }

+ 1 - 0
src/Engines/nativeEngine.ts

@@ -815,6 +815,7 @@ export class NativeEngine extends Engine {
             supportSSAO2: false,
             supportExtendedTextureFormats: false,
             supportSwitchCaseInShader: false,
+            supportSyncTextureRead: false,
             _collectUbosUpdatedInFrame: false,
         };
 

+ 1 - 0
src/Engines/nullEngine.ts

@@ -160,6 +160,7 @@ export class NullEngine extends Engine {
             supportSSAO2: false,
             supportExtendedTextureFormats: false,
             supportSwitchCaseInShader: false,
+            supportSyncTextureRead: false,
             _collectUbosUpdatedInFrame: false,
         };
 

+ 1 - 0
src/Engines/thinEngine.ts

@@ -1076,6 +1076,7 @@ export class ThinEngine {
             supportSSAO2: this._webGLVersion !== 1,
             supportExtendedTextureFormats: this._webGLVersion !== 1,
             supportSwitchCaseInShader: this._webGLVersion !== 1,
+            supportSyncTextureRead: true,
             _collectUbosUpdatedInFrame: false,
         };
     }

+ 6 - 0
src/Engines/webgpuEngine.ts

@@ -556,6 +556,7 @@ export class WebGPUEngine extends Engine {
             supportSSAO2: true,
             supportExtendedTextureFormats: true,
             supportSwitchCaseInShader: true,
+            supportSyncTextureRead: false,
             _collectUbosUpdatedInFrame: true,
         };
     }
@@ -2293,6 +2294,11 @@ export class WebGPUEngine extends Engine {
         return this._textureHelper.readPixels(gpuTextureWrapper.underlyingResource!, 0, 0, width, height, gpuTextureWrapper.format, faceIndex, level, buffer);
     }
 
+    /** @hidden */
+    public _readTexturePixelsSync(texture: InternalTexture, width: number, height: number, faceIndex = -1, level = 0, buffer: Nullable<ArrayBufferView> = null, flushRenderer = true): ArrayBufferView {
+        throw "_readTexturePixelsSync is unsupported in WebGPU!";
+    }
+
     //------------------------------------------------------------------------------
     //                              Render Target Textures
     //------------------------------------------------------------------------------

+ 34 - 0
src/Materials/Textures/baseTexture.ts

@@ -629,6 +629,40 @@ export class BaseTexture extends ThinTexture implements IAnimatable {
     }
 
     /** @hidden */
+    public _readPixelsSync(faceIndex = 0, level = 0, buffer: Nullable<ArrayBufferView> = null, flushRenderer = true): Nullable<ArrayBufferView> {
+        if (!this._texture) {
+            return null;
+        }
+
+        var size = this.getSize();
+        var width = size.width;
+        var height = size.height;
+
+        const engine = this._getEngine();
+        if (!engine) {
+            return null;
+        }
+
+        if (level != 0) {
+            width = width / Math.pow(2, level);
+            height = height / Math.pow(2, level);
+
+            width = Math.round(width);
+            height = Math.round(height);
+        }
+
+        try {
+            if (this._texture.isCube) {
+                return engine._readTexturePixelsSync(this._texture, width, height, faceIndex, level, buffer, flushRenderer);
+            }
+
+            return engine._readTexturePixelsSync(this._texture, width, height, -1, level, buffer, flushRenderer);
+        } catch (e) {
+            return null;
+        }
+    }
+
+    /** @hidden */
     public get _lodTextureHigh(): Nullable<BaseTexture> {
         if (this._texture) {
             return this._texture._lodTextureHigh;

+ 1 - 1
src/Materials/Textures/texture.ts

@@ -681,7 +681,7 @@ export class Texture extends BaseTexture {
             } else if (this.url && StringTools.StartsWith(this.url, "data:") && this._buffer instanceof Uint8Array) {
                 serializationObject.base64String = "data:image/png;base64," + StringTools.EncodeArrayBufferToBase64(this._buffer);
             } else if (Texture.ForceSerializeBuffers) {
-                serializationObject.base64String = CopyTools.GenerateBase64StringFromTexture(this);
+                serializationObject.base64String = !this._engine || this._engine._features.supportSyncTextureRead ? CopyTools.GenerateBase64StringFromTexture(this) : CopyTools.GenerateBase64StringFromTextureAsync(this);
             }
         }
 

+ 49 - 18
src/Misc/copyTools.ts

@@ -1,3 +1,4 @@
+import { ISize } from "../Maths/math.size";
 import { Nullable } from "../types";
 
 declare type BaseTexture = import("../Materials/Textures/baseTexture").BaseTexture;
@@ -7,25 +8,13 @@ declare type BaseTexture = import("../Materials/Textures/baseTexture").BaseTextu
  */
 export class CopyTools {
     /**
-     * Reads the pixels stored in the webgl texture and returns them as a base64 string
-     * @param texture defines the texture to read pixels from
-     * @param faceIndex defines the face of the texture to read (in case of cube texture)
-     * @param level defines the LOD level of the texture to read (in case of Mip Maps)
+     * Transform some pixel data to a base64 string
+     * @param pixels defines the pixel data to transform to base64
+     * @param size defines the width and height of the (texture) data
+     * @param invertY true if the data must be inverted for the Y coordinate during the conversion
      * @returns The base64 encoded string or null
      */
-    public static async GenerateBase64StringFromTexture(texture: BaseTexture, faceIndex = 0, level = 0): Promise<Nullable<string>> {
-
-        var internalTexture = texture.getInternalTexture();
-        if (!internalTexture) {
-            return null;
-        }
-
-        var pixels = await texture.readPixels(faceIndex, level);
-        if (!pixels) {
-            return null;
-        }
-
-        var size = texture.getSize();
+    public static GenerateBase64StringFromPixelData(pixels: ArrayBufferView, size: ISize, invertY = false): Nullable<string> {
         var width = size.width;
         var height = size.height;
 
@@ -60,7 +49,7 @@ export class CopyTools {
         castData.set(pixels);
         ctx.putImageData(imageData, 0, 0);
 
-        if (internalTexture.invertY) {
+        if (invertY) {
             var canvas2 = document.createElement('canvas');
             canvas2.width = width;
             canvas2.height = height;
@@ -79,4 +68,46 @@ export class CopyTools {
 
         return canvas.toDataURL('image/png');
     }
+
+    /**
+     * Reads the pixels stored in the webgl texture and returns them as a base64 string
+     * @param texture defines the texture to read pixels from
+     * @param faceIndex defines the face of the texture to read (in case of cube texture)
+     * @param level defines the LOD level of the texture to read (in case of Mip Maps)
+     * @returns The base64 encoded string or null
+     */
+    public static GenerateBase64StringFromTexture(texture: BaseTexture, faceIndex = 0, level = 0): Nullable<string> {
+        var internalTexture = texture.getInternalTexture();
+        if (!internalTexture) {
+            return null;
+        }
+
+        var pixels = texture._readPixelsSync(faceIndex, level);
+        if (!pixels) {
+            return null;
+        }
+
+        return CopyTools.GenerateBase64StringFromPixelData(pixels, texture.getSize(), internalTexture.invertY);
+    }
+
+    /**
+     * Reads the pixels stored in the webgl texture and returns them as a base64 string
+     * @param texture defines the texture to read pixels from
+     * @param faceIndex defines the face of the texture to read (in case of cube texture)
+     * @param level defines the LOD level of the texture to read (in case of Mip Maps)
+     * @returns The base64 encoded string or null wrapped in a promise
+     */
+    public static async GenerateBase64StringFromTextureAsync(texture: BaseTexture, faceIndex = 0, level = 0): Promise<Nullable<string>> {
+        var internalTexture = texture.getInternalTexture();
+        if (!internalTexture) {
+            return null;
+        }
+
+        var pixels = await texture.readPixels(faceIndex, level);
+        if (!pixels) {
+            return null;
+        }
+
+        return CopyTools.GenerateBase64StringFromPixelData(pixels, texture.getSize(), internalTexture.invertY);
+    }
 }

+ 9 - 3
src/Misc/sceneRecorder.ts

@@ -12,6 +12,7 @@ import { ParticleSystem } from '../Particles/particleSystem';
 import { MorphTargetManager } from '../Morph/morphTargetManager';
 import { ShadowGenerator } from '../Lights/Shadows/shadowGenerator';
 import { PostProcess } from '../PostProcesses/postProcess';
+import { Texture } from "../Materials/Textures/texture";
 
 /**
  * Class used to record delta files between 2 scene states
@@ -32,20 +33,25 @@ export class SceneRecorder {
 
     /**
      * Get the delta between current state and original state
-     * @returns a Promise<any> containing the delta
+     * @returns a any containing the delta
      */
-    public async getDelta(): Promise<any> {
+    public getDelta(): any {
         if (!this._trackedScene) {
             return null;
         }
 
-        let newJSON = await SceneSerializer.Serialize(this._trackedScene);
+        const currentForceSerializeBuffers = Texture.ForceSerializeBuffers;
+        Texture.ForceSerializeBuffers = false;
+
+        let newJSON = SceneSerializer.Serialize(this._trackedScene);
         let deltaJSON: any = {};
 
         for (var node in newJSON) {
             this._compareCollections(node, this._savedJSON[node], newJSON[node], deltaJSON);
         }
 
+        Texture.ForceSerializeBuffers = currentForceSerializeBuffers;
+
         return deltaJSON;
     }
 

+ 24 - 2
src/Misc/sceneSerializer.ts

@@ -6,6 +6,7 @@ import { Material } from "../Materials/material";
 import { Scene } from "../scene";
 import { Light } from "../Lights/light";
 import { SerializationHelper } from "./decorators";
+import { Texture } from "../Materials/Textures/texture";
 
 var serializedGeometries: Geometry[] = [];
 var serializeGeometry = (geometry: Geometry, serializationGeometries: any): any => {
@@ -110,12 +111,22 @@ export class SceneSerializer {
 
     /**
      * Serialize a scene into a JSON compatible object
+     * Note that if the current engine does not support synchronous texture reading (like WebGPU), you should use SerializeAsync instead
+     * as else you may not retrieve the proper base64 encoded texture data (when using the Texture.ForceSerializeBuffers flag)
      * @param scene defines the scene to serialize
-     * @returns a JSON promise compatible object
+     * @returns a JSON compatible object
      */
-    public static Serialize(scene: Scene): Promise<any> {
+    public static Serialize(scene: Scene): any {
+        return SceneSerializer._Serialize(scene);
+    }
+
+    private static _Serialize(scene: Scene, checkSyncReadSupported = true): any {
         var serializationObject: any = {};
 
+        if (checkSyncReadSupported && !scene.getEngine()._features.supportSyncTextureRead && Texture.ForceSerializeBuffers) {
+            console.warn("The serialization object may not contain the proper base64 encoded texture data! You should use the SerializeAsync method instead.");
+        }
+
         SceneSerializer.ClearCache();
 
         // Scene
@@ -312,6 +323,17 @@ export class SceneSerializer {
             component.serialize(serializationObject);
         }
 
+        return serializationObject;
+    }
+
+    /**
+     * Serialize a scene into a JSON compatible object
+     * @param scene defines the scene to serialize
+     * @returns a JSON promise compatible object
+     */
+    public static SerializeAsync(scene: Scene): Promise<any> {
+        const serializationObject = SceneSerializer._Serialize(scene, false);
+
         const promises: Array<Promise<any>> = [];
 
         this._CollectPromises(serializationObject, promises);