Kaynağa Gözat

Dump framebuffer at the end of a frame

Popov72 4 yıl önce
ebeveyn
işleme
c79cd38465
3 değiştirilmiş dosya ile 92 ekleme ve 37 silme
  1. 31 0
      src/Engines/thinEngine.ts
  2. 17 5
      src/Misc/observable.ts
  3. 44 32
      src/Misc/tools.ts

+ 31 - 0
src/Engines/thinEngine.ts

@@ -522,6 +522,12 @@ export class ThinEngine {
     }
 
     /**
+     * Observable raised when a screenshot has been taken
+     * Note that a screenshot will be taken only if the observable is not empty!
+     */
+    public onScreenshotObservable = new Observable<Uint8Array>();
+
+    /**
      * Creates a new engine
      * @param canvasOrContext defines the canvas or WebGL context to use for rendering. If you provide a WebGL context, Babylon.js will not hook events on the canvas (like pointers, keyboards, etc...) so no event observables will be available. This is mostly used when Babylon.js is used as a plugin on a system which alreay used the WebGL context
      * @param antialias defines enable antialiasing (default: false)
@@ -1323,6 +1329,31 @@ export class ThinEngine {
         if (this._badOS) {
             this.flushFramebuffer();
         }
+
+        if (this.onScreenshotObservable.hasObservers()) {
+            const observers = this.onScreenshotObservable.observers;
+            for (const observer of observers) {
+                let width: Nullable<number> = observer.customData?.width,
+                    height: Nullable<number> = observer.customData?.height;
+
+                if (width === null) {
+                    width = this.getRenderWidth();
+                }
+                if (height === null) {
+                    height = this.getRenderHeight();
+                }
+
+                let data = this.readPixels(0, 0, width, height);
+
+                if ((data as Uint8Array).byteLength !== undefined) {
+                    this.onScreenshotObservable.notifyObserver(observer, data as Uint8Array);
+                } else {
+                    (data as Promise<Uint8Array>).then((data) => {
+                        this.onScreenshotObservable.notifyObserver(observer, data);
+                    });
+                }
+            }
+        }
     }
 
     /**

+ 17 - 5
src/Misc/observable.ts

@@ -88,7 +88,11 @@ export class Observer<T> {
         /**
          * Defines the current scope used to restore the JS context
          */
-        public scope: any = null) {
+        public scope: any = null,
+        /**
+         * Defines custom data attached to this observer
+         */
+        public customData: any = null) {
     }
 }
 
@@ -181,12 +185,12 @@ export class Observable<T> {
      * @param unregisterOnFirstCall defines if the observer as to be unregistered after the next notification
      * @returns the new observer created for the callback
      */
-    public add(callback: (eventData: T, eventState: EventState) => void, mask: number = -1, insertFirst = false, scope: any = null, unregisterOnFirstCall = false): Nullable<Observer<T>> {
+    public add(callback: (eventData: T, eventState: EventState) => void, mask: number = -1, insertFirst = false, scope: any = null, unregisterOnFirstCall = false, customData: any = null): Nullable<Observer<T>> {
         if (!callback) {
             return null;
         }
 
-        var observer = new Observer(callback, mask, scope);
+        var observer = new Observer(callback, mask, scope, customData);
         observer.unregisterOnNextCall = unregisterOnFirstCall;
 
         if (insertFirst) {
@@ -207,8 +211,8 @@ export class Observable<T> {
      * @param callback the callback that will be executed for that Observer
      * @returns the new observer created for the callback
      */
-    public addOnce(callback: (eventData: T, eventState: EventState) => void): Nullable<Observer<T>> {
-        return this.add(callback, undefined, undefined, undefined, true);
+    public addOnce(callback: (eventData: T, eventState: EventState) => void, customData: any = null): Nullable<Observer<T>> {
+        return this.add(callback, undefined, undefined, undefined, true, customData);
     }
 
     /**
@@ -406,11 +410,19 @@ export class Observable<T> {
      * @param mask is used to filter observers defaults to -1
      */
     public notifyObserver(observer: Observer<T>, eventData: T, mask: number = -1): void {
+        if (observer._willBeUnregistered) {
+            return;
+        }
+
         let state = this._eventState;
         state.mask = mask;
         state.skipNextObservers = false;
 
         observer.callback(eventData, state);
+
+        if (observer.unregisterOnNextCall) {
+            this._deferUnregister(observer);
+        }
     }
 
     /**

+ 44 - 32
src/Misc/tools.ts

@@ -574,44 +574,56 @@ 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
      */
-    public static DumpFramebuffer(width: number, height: number, engine: Engine, successCallback?: (data: string) => void, mimeType: string = "image/png", fileName?: string): void {
+    public static DumpFramebuffer(width: number, height: number, engine: Engine, successCallback?: (data: string) => void, mimeType: string = "image/png", fileName?: string) {
         // Read the contents of the framebuffer
         var numberOfChannelsByLine = width * 4;
         var halfHeight = height / 2;
 
-        //Reading datas from WebGL
-        var data = engine.readPixels(0, 0, width, height);
-
-        //To flip image on Y axis.
-        for (var i = 0; i < halfHeight; i++) {
-            for (var j = 0; j < numberOfChannelsByLine; j++) {
-                var currentCell = j + i * numberOfChannelsByLine;
-                var targetLine = height - i - 1;
-                var targetCell = j + targetLine * numberOfChannelsByLine;
-
-                var temp = data[currentCell];
-                data[currentCell] = data[targetCell];
-                data[targetCell] = temp;
+        engine.onScreenshotObservable.addOnce((data) => {
+            // TODO WEBGPU Would be better to test ThinEngine.Features.framebuffersHaveYTopToBottom than engine.isWebGPU...
+            if (!engine.isWebGPU) {
+                // To flip image on Y axis.
+                for (var i = 0; i < halfHeight; i++) {
+                    for (var j = 0; j < numberOfChannelsByLine; j++) {
+                        var currentCell = j + i * numberOfChannelsByLine;
+                        var targetLine = height - i - 1;
+                        var targetCell = j + targetLine * numberOfChannelsByLine;
+
+                        var temp = data[currentCell];
+                        data[currentCell] = data[targetCell];
+                        data[targetCell] = temp;
+                    }
+                }
+            } else {
+                // TODO WEBGPU Add a feature in ThinEngine.Features for that?
+                // flip red and blue channels as swap chain is in BGRA format
+                for (var i = 0; i < width * height * 4; i += 4) {
+                    var temp = data[i + 0];
+                    data[i + 0] = data[i + 2];
+                    data[i + 2] = temp;
+                }
             }
-        }
 
-        // Create a 2D canvas to store the result
-        if (!Tools._ScreenshotCanvas) {
-            Tools._ScreenshotCanvas = document.createElement('canvas');
-        }
-        Tools._ScreenshotCanvas.width = width;
-        Tools._ScreenshotCanvas.height = height;
-        var context = Tools._ScreenshotCanvas.getContext('2d');
-
-        if (context) {
-            // Copy the pixels to a 2D canvas
-            var imageData = context.createImageData(width, height);
-            var castData = <any>(imageData.data);
-            castData.set(data);
-            context.putImageData(imageData, 0, 0);
-
-            Tools.EncodeScreenshotCanvasData(successCallback, mimeType, fileName);
-        }
+            // Create a 2D canvas to store the result
+            if (!Tools._ScreenshotCanvas) {
+                Tools._ScreenshotCanvas = document.createElement('canvas');
+            }
+            Tools._ScreenshotCanvas.width = width;
+            Tools._ScreenshotCanvas.height = height;
+            var context = Tools._ScreenshotCanvas.getContext('2d');
+
+            if (context) {
+                // Copy the pixels to a 2D canvas
+                var imageData = context.createImageData(width, height);
+                var castData = <any>(imageData.data);
+                castData.set(data);
+                context.putImageData(imageData, 0, 0);
+
+                Tools.EncodeScreenshotCanvasData(successCallback, mimeType, fileName);
+            }
+        }, {
+            width, height
+        });
     }
 
     /**