ソースを参照

Don't use webgl to load texture

Popov72 5 年 前
コミット
e8fe317e70

+ 163 - 0
src/Engines/WebGPU/webgpuTextureHelper.ts

@@ -0,0 +1,163 @@
+// Copyright 2020 Brandon Jones
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+import * as WebGPUConstants from './webgpuConstants';
+import { Scalar } from '../../Maths/math.scalar';
+
+export class GPUTextureHelper {
+
+    private device: GPUDevice;
+    private mipmapSampler: GPUSampler;
+    private mipmapPipeline: GPURenderPipeline;
+
+    constructor(device: GPUDevice, glslang: any) {
+        this.device = device;
+
+        const mipmapVertexSource = `
+            #version 450
+
+            const vec2 pos[4] = vec2[4](vec2(-1.0f, 1.0f), vec2(1.0f, 1.0f), vec2(-1.0f, -1.0f), vec2(1.0f, -1.0f));
+            const vec2 tex[4] = vec2[4](vec2(0.0f, 0.0f), vec2(1.0f, 0.0f), vec2(0.0f, 1.0f), vec2(1.0f, 1.0f));
+
+            layout(location = 0) out vec2 vTex;
+
+            void main() {
+                vTex = tex[gl_VertexIndex];
+                gl_Position = vec4(pos[gl_VertexIndex], 0.0, 1.0);
+            }
+        `;
+
+        const mipmapFragmentSource = `
+            #version 450
+
+            layout(set = 0, binding = 0) uniform sampler imgSampler;
+            layout(set = 0, binding = 1) uniform texture2D img;
+
+            layout(location = 0) in vec2 vTex;
+            layout(location = 0) out vec4 outColor;
+
+            void main() {
+                outColor = texture(sampler2D(img, imgSampler), vTex);
+            }
+        `;
+
+        this.mipmapSampler = device.createSampler({ minFilter: WebGPUConstants.FilterMode.Linear });
+
+        this.mipmapPipeline = device.createRenderPipeline({
+            vertexStage: {
+                module: device.createShaderModule({
+                    code: glslang.compileGLSL(mipmapVertexSource, 'vertex')
+                }),
+                entryPoint: 'main'
+            },
+            fragmentStage: {
+                module: device.createShaderModule({
+                    code: glslang.compileGLSL(mipmapFragmentSource, 'fragment')
+                }),
+                entryPoint: 'main'
+            },
+            primitiveTopology: WebGPUConstants.PrimitiveTopology.TriangleStrip,
+            vertexState: {
+                indexFormat: WebGPUConstants.IndexFormat.Uint16
+            },
+            colorStates: [{
+                format: WebGPUConstants.TextureFormat.RGBA8Unorm,
+            }]
+        });
+    }
+
+    generateTexture(imageBitmap: ImageBitmap): GPUTexture {
+       let textureSize = {
+            width: imageBitmap.width,
+            height: imageBitmap.height,
+            depth: 1,
+        };
+
+        // Populate the top level of the srcTexture with the imageBitmap.
+        const srcTexture = this.device.createTexture({
+            size: textureSize,
+            format: WebGPUConstants.TextureFormat.RGBA8Unorm,
+            usage: WebGPUConstants.TextureUsage.CopyDst | WebGPUConstants.TextureUsage.Sampled,
+            mipLevelCount: 1
+        });
+
+        this.device.defaultQueue.copyImageBitmapToTexture({ imageBitmap }, { texture: srcTexture }, textureSize);
+
+        return srcTexture;
+    }
+
+    generateMipmappedTexture(imageBitmap: ImageBitmap): GPUTexture {
+        let textureSize = {
+            width: imageBitmap.width,
+            height: imageBitmap.height,
+            depth: 1,
+        };
+
+        const mipLevelCount = Math.floor(Scalar.Log2(Math.max(imageBitmap.width, imageBitmap.height))) + 1;
+
+        // Populate the top level of the srcTexture with the imageBitmap.
+        const srcTexture = this.device.createTexture({
+            size: textureSize,
+            format: WebGPUConstants.TextureFormat.RGBA8Unorm,
+            usage: WebGPUConstants.TextureUsage.CopyDst | WebGPUConstants.TextureUsage.Sampled | WebGPUConstants.TextureUsage.OutputAttachment,
+            mipLevelCount
+        });
+
+        this.device.defaultQueue.copyImageBitmapToTexture({ imageBitmap }, { texture: srcTexture }, textureSize);
+
+        const commandEncoder = this.device.createCommandEncoder({});
+
+        const bindGroupLayout = this.mipmapPipeline.getBindGroupLayout(0);
+
+        for (let i = 1; i < mipLevelCount; ++i) {
+            const passEncoder = commandEncoder.beginRenderPass({
+                colorAttachments: [{
+                    attachment: srcTexture.createView({
+                        baseMipLevel: i,
+                        mipLevelCount: 1
+                    }),
+                    loadValue: { r: 0.0, g: 0.0, b: 0.0, a: 0.0 },
+                }],
+            });
+
+            const bindGroup = this.device.createBindGroup({
+                layout: bindGroupLayout,
+                entries: [{
+                    binding: 0,
+                    resource: this.mipmapSampler,
+                }, {
+                    binding: 1,
+                    resource: srcTexture.createView({
+                        baseMipLevel: i - 1,
+                        mipLevelCount: 1
+                    }),
+                }],
+            });
+
+            passEncoder.setPipeline(this.mipmapPipeline);
+            passEncoder.setBindGroup(0, bindGroup);
+            passEncoder.draw(4, 1, 0, 0);
+            passEncoder.endPass();
+        }
+
+        this.device.defaultQueue.submit([commandEncoder.finish()]);
+
+        return srcTexture;
+    }
+}

+ 92 - 75
src/Engines/thinEngine.ts

@@ -2835,30 +2835,14 @@ export class ThinEngine {
         return texture;
     }
 
-    /**
-     * Usually called from Texture.ts.
-     * Passed information to create a WebGLTexture
-     * @param url defines a value which contains one of the following:
-     * * A conventional http URL, e.g. 'http://...' or 'file://...'
-     * * A base64 string of in-line texture data, e.g. 'data:image/jpg;base64,/...'
-     * * An indicator that data being passed using the buffer parameter, e.g. 'data:mytexture.jpg'
-     * @param noMipmap defines a boolean indicating that no mipmaps shall be generated.  Ignored for compressed textures.  They must be in the file
-     * @param invertY when true, image is flipped when loaded.  You probably want true. Certain compressed textures may invert this if their default is inverted (eg. ktx)
-     * @param scene needed for loading to the correct scene
-     * @param samplingMode mode with should be used sample / access the texture (Default: Texture.TRILINEAR_SAMPLINGMODE)
-     * @param onLoad optional callback to be called upon successful completion
-     * @param onError optional callback to be called upon failure
-     * @param buffer a source of a file previously fetched as either a base64 string, an ArrayBuffer (compressed or image format), HTMLImageElement (image format), or a Blob
-     * @param fallback an internal argument in case the function must be called again, due to etc1 not having alpha capabilities
-     * @param format internal format.  Default: RGB when extension is '.jpg' else RGBA.  Ignored for compressed textures
-     * @param forcedExtension defines the extension to use to pick the right loader
-     * @param mimeType defines an optional mime type
-     * @returns a InternalTexture for assignment back into BABYLON.Texture
-     */
-    public createTexture(url: Nullable<string>, noMipmap: boolean, invertY: boolean, scene: Nullable<ISceneLike>, samplingMode: number = Constants.TEXTURE_TRILINEAR_SAMPLINGMODE,
+    protected _createTextureBase(url: Nullable<string>, noMipmap: boolean, invertY: boolean, scene: Nullable<ISceneLike>, samplingMode: number = Constants.TEXTURE_TRILINEAR_SAMPLINGMODE,
         onLoad: Nullable<() => void> = null, onError: Nullable<(message: string, exception: any) => void> = null,
+        prepareTexture: (texture: InternalTexture, extension: string, scene: Nullable<ISceneLike>, img: HTMLImageElement | ImageBitmap | { width: number, height: number }, invertY: boolean, noMipmap: boolean, isCompressed: boolean,
+            processFunction: (width: number, height: number, img: HTMLImageElement | ImageBitmap | { width: number, height: number }, extension: string, texture: InternalTexture, continuationCallback: () => void) => boolean, samplingMode: number) => void,
+        prepareTextureProcessFunction: (width: number, height: number, img: HTMLImageElement | ImageBitmap | { width: number, height: number }, extension: string, texture: InternalTexture, continuationCallback: () => void) => boolean,
         buffer: Nullable<string | ArrayBuffer | ArrayBufferView | HTMLImageElement | Blob | ImageBitmap> = null, fallback: Nullable<InternalTexture> = null, format: Nullable<number> = null,
         forcedExtension: Nullable<string> = null, mimeType?: string): InternalTexture {
+
         url = url || "";
         const fromData = url.substr(0, 5) === "data:";
         const fromBlob = url.substr(0, 5) === "blob:";
@@ -2921,7 +2905,7 @@ export class ThinEngine {
                 }
 
                 if (EngineStore.UseFallbackTexture) {
-                    this.createTexture(EngineStore.FallbackTexture, noMipmap, texture.invertY, scene, samplingMode, null, onError, buffer, texture);
+                    this._createTextureBase(EngineStore.FallbackTexture, noMipmap, texture.invertY, scene, samplingMode, null, onError, prepareTexture, prepareTextureProcessFunction, buffer, texture);
                 }
 
                 if (onError) {
@@ -2931,7 +2915,7 @@ export class ThinEngine {
             else {
                 // fall back to the original url if the transformed url fails to load
                 Logger.Warn(`Failed to load ${url}, falling back to ${originalUrl}`);
-                this.createTexture(originalUrl, noMipmap, texture.invertY, scene, samplingMode, onLoad, onError, buffer, texture, format, forcedExtension, mimeType);
+                this._createTextureBase(originalUrl, noMipmap, texture.invertY, scene, samplingMode, onLoad, onError, prepareTexture, prepareTextureProcessFunction, buffer, texture, format, forcedExtension, mimeType);
             }
         };
 
@@ -2942,7 +2926,7 @@ export class ThinEngine {
                     if (loadFailed) {
                         onInternalError("TextureLoader failed to load data");
                     } else {
-                        this._prepareWebGLTexture(texture, scene, width, height, texture.invertY, !loadMipmap, isCompressed, () => {
+                        prepareTexture(texture, extension, scene, { width, height }, texture.invertY, !loadMipmap, isCompressed, () => {
                             done();
                             return false;
                         }, samplingMode);
@@ -2975,50 +2959,7 @@ export class ThinEngine {
                     texture._buffer = img;
                 }
 
-                this._prepareWebGLTexture(texture, scene, img.width, img.height, texture.invertY, noMipmap, false, (potWidth, potHeight, continuationCallback) => {
-                    let gl = this._gl;
-                    var isPot = (img.width === potWidth && img.height === potHeight);
-                    let internalFormat = format ? this._getInternalFormat(format) : ((extension === ".jpg") ? gl.RGB : gl.RGBA);
-
-                    if (isPot) {
-                        gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, internalFormat, gl.UNSIGNED_BYTE, img);
-                        return false;
-                    }
-
-                    let maxTextureSize = this._caps.maxTextureSize;
-
-                    if (img.width > maxTextureSize || img.height > maxTextureSize || !this._supportsHardwareTextureRescaling) {
-                        this._prepareWorkingCanvas();
-                        if (!this._workingCanvas || !this._workingContext) {
-                            return false;
-                        }
-
-                        this._workingCanvas.width = potWidth;
-                        this._workingCanvas.height = potHeight;
-
-                        this._workingContext.drawImage(img, 0, 0, img.width, img.height, 0, 0, potWidth, potHeight);
-                        gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, internalFormat, gl.UNSIGNED_BYTE, this._workingCanvas);
-
-                        texture.width = potWidth;
-                        texture.height = potHeight;
-
-                        return false;
-                    } else {
-                        // Using shaders when possible to rescale because canvas.drawImage is lossy
-                        let source = new InternalTexture(this, InternalTextureSource.Temp);
-                        this._bindTextureDirectly(gl.TEXTURE_2D, source, true);
-                        gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, internalFormat, gl.UNSIGNED_BYTE, img);
-
-                        this._rescaleTexture(source, texture, scene, internalFormat, () => {
-                            this._releaseTexture(source);
-                            this._bindTextureDirectly(gl.TEXTURE_2D, texture, true);
-
-                            continuationCallback();
-                        });
-                    }
-
-                    return true;
-                }, samplingMode);
+                prepareTexture(texture, extension, scene, img, texture.invertY, noMipmap, false, prepareTextureProcessFunction, samplingMode);
             };
 
             if (!fromData || isBase64) {
@@ -3040,6 +2981,82 @@ export class ThinEngine {
     }
 
     /**
+     * Usually called from Texture.ts.
+     * Passed information to create a WebGLTexture
+     * @param url defines a value which contains one of the following:
+     * * A conventional http URL, e.g. 'http://...' or 'file://...'
+     * * A base64 string of in-line texture data, e.g. 'data:image/jpg;base64,/...'
+     * * An indicator that data being passed using the buffer parameter, e.g. 'data:mytexture.jpg'
+     * @param noMipmap defines a boolean indicating that no mipmaps shall be generated.  Ignored for compressed textures.  They must be in the file
+     * @param invertY when true, image is flipped when loaded.  You probably want true. Certain compressed textures may invert this if their default is inverted (eg. ktx)
+     * @param scene needed for loading to the correct scene
+     * @param samplingMode mode with should be used sample / access the texture (Default: Texture.TRILINEAR_SAMPLINGMODE)
+     * @param onLoad optional callback to be called upon successful completion
+     * @param onError optional callback to be called upon failure
+     * @param buffer a source of a file previously fetched as either a base64 string, an ArrayBuffer (compressed or image format), HTMLImageElement (image format), or a Blob
+     * @param fallback an internal argument in case the function must be called again, due to etc1 not having alpha capabilities
+     * @param format internal format.  Default: RGB when extension is '.jpg' else RGBA.  Ignored for compressed textures
+     * @param forcedExtension defines the extension to use to pick the right loader
+     * @param mimeType defines an optional mime type
+     * @returns a InternalTexture for assignment back into BABYLON.Texture
+     */
+    public createTexture(url: Nullable<string>, noMipmap: boolean, invertY: boolean, scene: Nullable<ISceneLike>, samplingMode: number = Constants.TEXTURE_TRILINEAR_SAMPLINGMODE,
+        onLoad: Nullable<() => void> = null, onError: Nullable<(message: string, exception: any) => void> = null,
+        buffer: Nullable<string | ArrayBuffer | ArrayBufferView | HTMLImageElement | Blob | ImageBitmap> = null, fallback: Nullable<InternalTexture> = null, format: Nullable<number> = null,
+        forcedExtension: Nullable<string> = null, mimeType?: string): InternalTexture {
+
+        return this._createTextureBase(
+            url, noMipmap, invertY, scene, samplingMode, onLoad, onError,
+            this._prepareWebGLTexture,
+            (potWidth, potHeight, img, extension, texture, continuationCallback) => {
+                let gl = this._gl;
+                var isPot = (img.width === potWidth && img.height === potHeight);
+                let internalFormat = format ? this._getInternalFormat(format) : ((extension === ".jpg") ? gl.RGB : gl.RGBA);
+
+                if (isPot) {
+                    gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, internalFormat, gl.UNSIGNED_BYTE, img as any);
+                    return false;
+                }
+
+                let maxTextureSize = this._caps.maxTextureSize;
+
+                if (img.width > maxTextureSize || img.height > maxTextureSize || !this._supportsHardwareTextureRescaling) {
+                    this._prepareWorkingCanvas();
+                    if (!this._workingCanvas || !this._workingContext) {
+                        return false;
+                    }
+
+                    this._workingCanvas.width = potWidth;
+                    this._workingCanvas.height = potHeight;
+
+                    this._workingContext.drawImage(img as any, 0, 0, img.width, img.height, 0, 0, potWidth, potHeight);
+                    gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, internalFormat, gl.UNSIGNED_BYTE, this._workingCanvas);
+
+                    texture.width = potWidth;
+                    texture.height = potHeight;
+
+                    return false;
+                } else {
+                    // Using shaders when possible to rescale because canvas.drawImage is lossy
+                    let source = new InternalTexture(this, InternalTextureSource.Temp);
+                    this._bindTextureDirectly(gl.TEXTURE_2D, source, true);
+                    gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, internalFormat, gl.UNSIGNED_BYTE, img as any);
+
+                    this._rescaleTexture(source, texture, scene, internalFormat, () => {
+                        this._releaseTexture(source);
+                        this._bindTextureDirectly(gl.TEXTURE_2D, texture, true);
+
+                        continuationCallback();
+                    });
+                }
+
+                return true;
+            },
+            buffer, fallback, format, forcedExtension, mimeType
+        );
+    }
+
+    /**
      * Loads an image as an HTMLImageElement.
      * @param input url string, ArrayBuffer, or Blob to load
      * @param onLoad callback called when the image successfully loads
@@ -3354,11 +3371,11 @@ export class ThinEngine {
         texture.onLoadedObservable.clear();
     }
 
-    private _prepareWebGLTexture(texture: InternalTexture, scene: Nullable<ISceneLike>, width: number, height: number, invertY: boolean, noMipmap: boolean, isCompressed: boolean,
-        processFunction: (width: number, height: number, continuationCallback: () => void) => boolean, samplingMode: number = Constants.TEXTURE_TRILINEAR_SAMPLINGMODE): void {
+    private _prepareWebGLTexture(texture: InternalTexture, extension: string, scene: Nullable<ISceneLike>, img: HTMLImageElement | ImageBitmap | { width: number, height: number }, invertY: boolean, noMipmap: boolean, isCompressed: boolean,
+        processFunction: (width: number, height: number, img: HTMLImageElement | ImageBitmap | { width: number, height: number }, extension: string, texture: InternalTexture, continuationCallback: () => void) => boolean, samplingMode: number = Constants.TEXTURE_TRILINEAR_SAMPLINGMODE): void {
         var maxTextureSize = this.getCaps().maxTextureSize;
-        var potWidth = Math.min(maxTextureSize, this.needPOTTextures ? ThinEngine.GetExponentOfTwo(width, maxTextureSize) : width);
-        var potHeight = Math.min(maxTextureSize, this.needPOTTextures ? ThinEngine.GetExponentOfTwo(height, maxTextureSize) : height);
+        var potWidth = Math.min(maxTextureSize, this.needPOTTextures ? ThinEngine.GetExponentOfTwo(img.width, maxTextureSize) : img.width);
+        var potHeight = Math.min(maxTextureSize, this.needPOTTextures ? ThinEngine.GetExponentOfTwo(img.height, maxTextureSize) : img.height);
 
         var gl = this._gl;
         if (!gl) {
@@ -3377,13 +3394,13 @@ export class ThinEngine {
         this._bindTextureDirectly(gl.TEXTURE_2D, texture, true);
         this._unpackFlipY(invertY === undefined ? true : (invertY ? true : false));
 
-        texture.baseWidth = width;
-        texture.baseHeight = height;
+        texture.baseWidth = img.width;
+        texture.baseHeight = img.height;
         texture.width = potWidth;
         texture.height = potHeight;
         texture.isReady = true;
 
-        if (processFunction(potWidth, potHeight, () => {
+        if (processFunction(potWidth, potHeight, img, extension, texture, () => {
             this._prepareWebGLTextureContinuation(texture, scene, noMipmap, isCompressed, samplingMode);
         })) {
             // Returning as texture needs extra async steps

+ 78 - 98
src/Engines/webgpuEngine.ts

@@ -1,8 +1,6 @@
 import { Logger } from "../Misc/logger";
 import { Nullable, DataArray, IndicesArray, FloatArray } from "../types";
-import { Scene } from "../scene";
 import { Color4 } from "../Maths/math";
-import { Scalar } from "../Maths/math.scalar";
 import { Engine } from "../Engines/engine";
 import { InstancingAttributeInfo } from "../Engines/instancingAttributeInfo";
 import { RenderTargetCreationOptions } from "../Materials/Textures/renderTargetCreationOptions";
@@ -23,6 +21,8 @@ import { WebGPUShaderProcessor } from "./WebGPU/webgpuShaderProcessors";
 import { ShaderProcessingContext } from "./Processors/shaderProcessingOptions";
 import { WebGPUShaderProcessingContext } from "./WebGPU/webgpuShaderProcessingContext";
 import { Tools } from "../Misc/tools";
+import { GPUTextureHelper } from './WebGPU/webgpuTextureHelper';
+import { ISceneLike } from './thinEngine';
 
 /**
  * Options to load the associated Glslang library
@@ -124,6 +124,7 @@ export class WebGPUEngine extends Engine {
     private _context: GPUCanvasContext;
     private _swapChain: GPUSwapChain;
     private _mainPassSampleCount: number;
+    private _gpuTextureHelper: GPUTextureHelper;
 
     // Some of the internal state might change during the render pass.
     // This happens mainly during clear for the state
@@ -167,13 +168,6 @@ export class WebGPUEngine extends Engine {
         }
     } } = {};
 
-    // TODO WEBGPU. Texture Management. Temporary...
-    private _decodeCanvas = document.createElement("canvas");
-    private _decodeEngine = new Engine(this._decodeCanvas, false, {
-        alpha: true,
-        premultipliedAlpha: false,
-    }, false);
-
     /**
      * Gets a boolean indicating that the engine supports uniform buffers
      * @see http://doc.babylonjs.com/features/webgl2#uniform-buffer-objets
@@ -194,11 +188,6 @@ export class WebGPUEngine extends Engine {
         options.swapChainFormat = options.swapChainFormat || WebGPUConstants.TextureFormat.BGRA8Unorm;
         options.antialiasing = options.antialiasing === undefined ? true : options.antialiasing;
 
-        this._decodeEngine.getCaps().textureFloat = false;
-        this._decodeEngine.getCaps().textureFloatRender = false;
-        this._decodeEngine.getCaps().textureHalfFloat = false;
-        this._decodeEngine.getCaps().textureHalfFloatRender = false;
-
         Logger.Log(`Babylon.js v${Engine.Version} - WebGPU engine`);
         if (!navigator.gpu) {
             Logger.Error("WebGPU is not supported by your browser.");
@@ -259,6 +248,8 @@ export class WebGPUEngine extends Engine {
             })
             .then((device: GPUDevice | null) => this._device = device!)
             .then(() => {
+                this._gpuTextureHelper = new GPUTextureHelper(this._device, this._glslang);
+
                 this._initializeLimits();
                 this._initializeContextAndSwapChain();
                 this._initializeMainAttachments();
@@ -944,85 +935,6 @@ export class WebGPUEngine extends Engine {
         }
     }
 
-    private _uploadMipMapsFromWebglTexture(mipMaps: number, webglEngineTexture: InternalTexture, gpuTexture: GPUTexture, width: number, height: number, face: number) {
-        this._uploadFromWebglTexture(webglEngineTexture, gpuTexture, width, height, face);
-
-        let faceWidth = width;
-        let faceHeight = height;
-
-        for (let mip = 1; mip <= mipMaps; mip++) {
-            faceWidth = Math.max(Math.floor(faceWidth / 2), 1);
-            faceHeight = Math.max(Math.floor(faceHeight / 2), 1);
-
-            this._uploadFromWebglTexture(webglEngineTexture, gpuTexture, faceWidth, faceHeight, face, mip);
-        }
-    }
-
-    private _uploadFromWebglTexture(webglEngineTexture: InternalTexture, gpuTexture: GPUTexture, width: number, height: number, face: number, mip: number = 0): void {
-        let pixels = this._decodeEngine._readTexturePixels(webglEngineTexture, width, height, face, mip);
-        if (pixels instanceof Float32Array) {
-            const newPixels = new Uint8ClampedArray(pixels.length);
-            pixels.forEach((value, index) => newPixels[index] = value * 255);
-            pixels = newPixels;
-        }
-
-        const textureView: GPUTextureCopyView = {
-            texture: gpuTexture,
-            origin: {
-                x: 0,
-                y: 0,
-                z: Math.max(face, 0)
-            },
-            mipLevel: mip
-        };
-        const textureExtent = {
-            width,
-            height,
-            depth: 1
-        };
-
-        const commandEncoder = this._device.createCommandEncoder({});
-        const bytesPerRow = Math.ceil(width * 4 / 256) * 256;
-
-        let dataBuffer: DataBuffer;
-        if (bytesPerRow == width * 4) {
-            dataBuffer = this._createBuffer(pixels, WebGPUConstants.BufferUsage.CopySrc | WebGPUConstants.BufferUsage.CopyDst);
-            const bufferView: GPUBufferCopyView = {
-                buffer: dataBuffer.underlyingResource,
-                bytesPerRow: bytesPerRow,
-                rowsPerImage: height,
-                offset: 0,
-            };
-            commandEncoder.copyBufferToTexture(bufferView, textureView, textureExtent);
-        } else {
-            const alignedPixels = new Uint8Array(bytesPerRow * height);
-            let pixelsIndex = 0;
-            for (let y = 0; y < height; ++y) {
-                for (let x = 0; x < width; ++x) {
-                    let i = x * 4 + y * bytesPerRow;
-
-                    alignedPixels[i] = (pixels as any)[pixelsIndex];
-                    alignedPixels[i + 1] = (pixels as any)[pixelsIndex + 1];
-                    alignedPixels[i + 2] = (pixels as any)[pixelsIndex + 2];
-                    alignedPixels[i + 3] = (pixels as any)[pixelsIndex + 3];
-                    pixelsIndex += 4;
-                }
-            }
-            dataBuffer = this._createBuffer(alignedPixels, WebGPUConstants.BufferUsage.CopySrc | WebGPUConstants.BufferUsage.CopyDst);
-            const bufferView: GPUBufferCopyView = {
-                buffer: dataBuffer.underlyingResource,
-                bytesPerRow: bytesPerRow,
-                rowsPerImage: height,
-                offset: 0,
-            };
-            commandEncoder.copyBufferToTexture(bufferView, textureView, textureExtent);
-        }
-
-        this._device.defaultQueue.submit([commandEncoder.finish()]);
-
-        this._releaseBuffer(dataBuffer);
-    }
-
     private _getSamplerFilterDescriptor(internalTexture: InternalTexture): {
         magFilter: GPUFilterMode,
         minFilter: GPUFilterMode,
@@ -1135,7 +1047,76 @@ export class WebGPUEngine extends Engine {
         };
     }
 
-    public createTexture(urlArg: string, noMipmap: boolean, invertY: boolean, scene: Scene, samplingMode: number = Constants.TEXTURE_TRILINEAR_SAMPLINGMODE, onLoad: Nullable<() => void> = null, onError: Nullable<(message: string, exception: any) => void> = null, buffer: Nullable<ArrayBuffer | HTMLImageElement> = null, fallBack?: InternalTexture, format?: number): InternalTexture {
+    /**
+     * Usually called from Texture.ts.
+     * Passed information to create a WebGLTexture
+     * @param url defines a value which contains one of the following:
+     * * A conventional http URL, e.g. 'http://...' or 'file://...'
+     * * A base64 string of in-line texture data, e.g. 'data:image/jpg;base64,/...'
+     * * An indicator that data being passed using the buffer parameter, e.g. 'data:mytexture.jpg'
+     * @param noMipmap defines a boolean indicating that no mipmaps shall be generated.  Ignored for compressed textures.  They must be in the file
+     * @param invertY when true, image is flipped when loaded.  You probably want true. Certain compressed textures may invert this if their default is inverted (eg. ktx)
+     * @param scene needed for loading to the correct scene
+     * @param samplingMode mode with should be used sample / access the texture (Default: Texture.TRILINEAR_SAMPLINGMODE)
+     * @param onLoad optional callback to be called upon successful completion
+     * @param onError optional callback to be called upon failure
+     * @param buffer a source of a file previously fetched as either a base64 string, an ArrayBuffer (compressed or image format), HTMLImageElement (image format), or a Blob
+     * @param fallback an internal argument in case the function must be called again, due to etc1 not having alpha capabilities
+     * @param format internal format.  Default: RGB when extension is '.jpg' else RGBA.  Ignored for compressed textures
+     * @param forcedExtension defines the extension to use to pick the right loader
+     * @param mimeType defines an optional mime type
+     * @returns a InternalTexture for assignment back into BABYLON.Texture
+     */
+    public createTexture(url: Nullable<string>, noMipmap: boolean, invertY: boolean, scene: Nullable<ISceneLike>, samplingMode: number = Constants.TEXTURE_TRILINEAR_SAMPLINGMODE,
+        onLoad: Nullable<() => void> = null, onError: Nullable<(message: string, exception: any) => void> = null,
+        buffer: Nullable<string | ArrayBuffer | ArrayBufferView | HTMLImageElement | Blob | ImageBitmap> = null, fallback: Nullable<InternalTexture> = null, format: Nullable<number> = null,
+        forcedExtension: Nullable<string> = null, mimeType?: string): InternalTexture {
+
+        return this._createTextureBase(
+            url, noMipmap, invertY, scene, samplingMode, onLoad, onError,
+            (texture: InternalTexture, extension: string, scene: Nullable<ISceneLike>, img: HTMLImageElement | ImageBitmap | { width: number, height: number }, invertY: boolean, noMipmap: boolean, isCompressed: boolean,
+                processFunction: (width: number, height: number, img: HTMLImageElement | ImageBitmap | { width: number, height: number }, extension: string, texture: InternalTexture, continuationCallback: () => void) => boolean, samplingMode: number) => {
+                    texture.baseWidth = img.width;
+                    texture.baseHeight = img.height;
+                    texture.width = img.width;
+                    texture.height = img.height;
+                    texture.isReady = true;
+
+                    const promise: Promise<Nullable<ImageBitmap>> =
+                        img instanceof String ? Promise.resolve(null) :
+                        img instanceof ImageBitmap ? Promise.resolve(img) :
+                            new Promise((resolve) => {
+                                (img as HTMLImageElement).decode().then(() => {
+                                    createImageBitmap(img as HTMLImageElement).then((imageBitmap) => {
+                                        resolve(imageBitmap);
+                                    });
+                                });
+                            });
+
+                    promise.then((img) => {
+                        if (img) {
+                            //let internalFormat = format ? this._getInternalFormat(format) : ((extension === ".jpg") ? gl.RGB : gl.RGBA);
+                            if (noMipmap) {
+                                this._gpuTextureHelper.generateTexture(img);
+                            } else {
+                                this._gpuTextureHelper.generateMipmappedTexture(img);
+                            }
+                        }
+
+                        if (scene) {
+                            scene._removePendingData(texture);
+                        }
+
+                        texture.onLoadedObservable.notifyObservers(texture);
+                        texture.onLoadedObservable.clear();
+                    });
+            },
+            () => false,
+            buffer, fallback, format, forcedExtension, mimeType
+        );
+    }
+
+    /*public createTexture(urlArg: string, noMipmap: boolean, invertY: boolean, scene: Scene, samplingMode: number = Constants.TEXTURE_TRILINEAR_SAMPLINGMODE, onLoad: Nullable<() => void> = null, onError: Nullable<(message: string, exception: any) => void> = null, buffer: Nullable<ArrayBuffer | HTMLImageElement> = null, fallBack?: InternalTexture, format?: number): InternalTexture {
         const texture = new InternalTexture(this, InternalTextureSource.Url);
         const url = String(urlArg);
 
@@ -1209,8 +1190,8 @@ export class WebGPUEngine extends Engine {
         this._internalTexturesCache.push(texture);
 
         return texture;
-    }
-
+    }*/
+/*
     public createCubeTexture(rootUrl: string, scene: Nullable<Scene>, files: Nullable<string[]>, noMipmap?: boolean, onLoad: Nullable<(data?: any) => void> = null, onError: Nullable<(message?: string, exception?: any) => void> = null, format?: number, forcedExtension: any = null, createPolynomials: boolean = false, lodScale: number = 0, lodOffset: number = 0, fallback: Nullable<InternalTexture> = null): InternalTexture {
         var texture = fallback ? fallback : new InternalTexture(this, InternalTextureSource.Cube);
         texture.isCube = true;
@@ -1289,7 +1270,7 @@ export class WebGPUEngine extends Engine {
 
         return texture;
     }
-
+*/
     public updateTextureSamplingMode(samplingMode: number, texture: InternalTexture): void {
         texture.samplingMode = samplingMode;
     }
@@ -2326,7 +2307,6 @@ export class WebGPUEngine extends Engine {
      * Dispose and release all associated resources
      */
     public dispose(): void {
-        this._decodeEngine.dispose();
         this._compiledShaders = { };
         if (this._mainTexture) {
             this._mainTexture.destroy();

+ 2 - 1
src/Materials/Textures/internalTexture.ts

@@ -448,6 +448,7 @@ export class InternalTexture {
     /** @hidden */
     public _swapAndDie(target: InternalTexture): void {
         target._webGLTexture = this._webGLTexture;
+        target._webGPUTexture = this._webGPUTexture;
         target._isRGBD = this._isRGBD;
 
         if (this._framebuffer) {
@@ -504,7 +505,7 @@ export class InternalTexture {
      * Dispose the current allocated resources
      */
     public dispose(): void {
-        if (!this._webGLTexture) {
+        if (!this._webGLTexture && !this._webGPUTexture) {
             return;
         }