Browse Source

More WebGPU texture creation / loading / updating work

Popov72 5 years ago
parent
commit
2c63d3791d

+ 25 - 26
src/Engines/Extensions/engine.cubeTexture.ts

@@ -5,6 +5,7 @@ import { Nullable } from '../../types';
 import { Scene } from '../../scene';
 import { IInternalTextureLoader } from '../../Materials/Textures/internalTextureLoader';
 import { FileTools } from '../../Misc/fileTools';
+import { GUID } from '../../Misc/guid';
 import { DepthTextureCreationOptions } from '../depthTextureCreationOptions';
 import { IWebRequest } from '../../Misc/interfaces/iWebRequest';
 
@@ -78,7 +79,7 @@ declare module "../../Engines/thinEngine" {
         createCubeTextureBase(rootUrl: string, scene: Nullable<Scene>, files: Nullable<string[]>, noMipmap: boolean,
             onLoad: Nullable<(data?: any) => void>, onError: Nullable<(message?: string, exception?: any) => void>,
             format: number | undefined, forcedExtension: any, createPolynomials: boolean, lodScale: number, lodOffset: number, fallback: Nullable<InternalTexture>,
-            beforeLoadCubeDataCallback: Nullable<(texture: InternalTexture, data: ArrayBufferView | ArrayBufferView[]) => void>, imageHandler: Nullable<(texture: InternalTexture, imgs: HTMLImageElement[]) => void>): InternalTexture;
+            beforeLoadCubeDataCallback: Nullable<(texture: InternalTexture, data: ArrayBufferView | ArrayBufferView[]) => void>, imageHandler: Nullable<(texture: InternalTexture, imgs: HTMLImageElement[] | ImageBitmap[]) => void>): InternalTexture;
 
         /** @hidden */
         _partialLoadFile(url: string, index: number, loadedFiles: ArrayBuffer[], onfinish: (files: ArrayBuffer[]) => void, onErrorCallBack: Nullable<(message?: string, exception?: any) => void>): void;
@@ -87,15 +88,15 @@ declare module "../../Engines/thinEngine" {
         _cascadeLoadFiles(scene: Nullable<Scene>, onfinish: (images: ArrayBuffer[]) => void, files: string[], onError: Nullable<(message?: string, exception?: any) => void>): void;
 
         /** @hidden */
-        _cascadeLoadImgs(scene: Nullable<Scene>, texture: InternalTexture, onfinish: Nullable<(texture: InternalTexture, images: HTMLImageElement[]) => void>, files: string[], onError: Nullable<(message?: string, exception?: any) => void>, mimeType?: string): void;
+        _cascadeLoadImgs(scene: Nullable<Scene>, texture: InternalTexture, onfinish: Nullable<(texture: InternalTexture, images: HTMLImageElement[] | ImageBitmap[]) => void>, files: string[], onError: Nullable<(message?: string, exception?: any) => void>, mimeType?: string): void;
 
         /** @hidden */
-        _partialLoadImg(url: string, index: number, loadedImages: HTMLImageElement[], scene: Nullable<Scene>, texture: InternalTexture, onfinish: Nullable<(texture: InternalTexture, images: HTMLImageElement[]) => void>, onErrorCallBack: Nullable<(message?: string, exception?: any) => void>, mimeType?: string): void;
+        _partialLoadImg(url: string, index: number, loadedImages: HTMLImageElement[] | ImageBitmap[], scene: Nullable<Scene>, texture: InternalTexture, onfinish: Nullable<(texture: InternalTexture, images: HTMLImageElement[] | ImageBitmap[]) => void>, onErrorCallBack: Nullable<(message?: string, exception?: any) => void>, mimeType?: string): void;
 
         /**
          * @hidden
          */
-        _setCubeMapTextureParams(loadMipmap: boolean): void;
+        _setCubeMapTextureParams(texture: InternalTexture, loadMipmap: boolean): void;
     }
 }
 
@@ -164,10 +165,10 @@ ThinEngine.prototype._cascadeLoadFiles = function(scene: Nullable<Scene>, onfini
     }
 };
 
-ThinEngine.prototype._cascadeLoadImgs = function(scene: Nullable<Scene>, texture: InternalTexture, 
-    onfinish: Nullable<(texture: InternalTexture, images: HTMLImageElement[]) => void>, files: string[], onError: Nullable<(message?: string, exception?: any) => void> = null, mimeType?: string) {
+ThinEngine.prototype._cascadeLoadImgs = function(scene: Nullable<Scene>, texture: InternalTexture,
+    onfinish: Nullable<(texture: InternalTexture, images: HTMLImageElement[] | ImageBitmap[]) => void>, files: string[], onError: Nullable<(message?: string, exception?: any) => void> = null, mimeType?: string) {
 
-    var loadedImages: HTMLImageElement[] = [];
+    var loadedImages: HTMLImageElement[] | ImageBitmap[] = [];
     (<any>loadedImages)._internalCount = 0;
 
     for (let index = 0; index < 6; index++) {
@@ -175,19 +176,17 @@ ThinEngine.prototype._cascadeLoadImgs = function(scene: Nullable<Scene>, texture
     }
 };
 
-ThinEngine.prototype._partialLoadImg = function(url: string, index: number, loadedImages: HTMLImageElement[], scene: Nullable<Scene>, texture: InternalTexture,
-    onfinish: Nullable<(texture: InternalTexture, images: HTMLImageElement[]) => void>, onErrorCallBack: Nullable<(message?: string, exception?: any) => void> = null, mimeType?: string) {
+ThinEngine.prototype._partialLoadImg = function(url: string, index: number, loadedImages: HTMLImageElement[] | ImageBitmap[], scene: Nullable<Scene>, texture: InternalTexture,
+    onfinish: Nullable<(texture: InternalTexture, images: HTMLImageElement[] | ImageBitmap[]) => void>, onErrorCallBack: Nullable<(message?: string, exception?: any) => void> = null, mimeType?: string) {
 
-    var img: Nullable<HTMLImageElement>;
+    var tokenPendingData = GUID.RandomId();
 
-    var onload = () => {
-        if (img) {
-            loadedImages[index] = img;
-            (<any>loadedImages)._internalCount++;
+    var onload = (img: HTMLImageElement | ImageBitmap) => {
+        loadedImages[index] = img;
+        (<any>loadedImages)._internalCount++;
 
-            if (scene) {
-                scene._removePendingData(img);
-            }
+        if (scene) {
+            scene._removePendingData(tokenPendingData);
         }
 
         if ((<any>loadedImages)._internalCount === 6 && onfinish) {
@@ -197,7 +196,7 @@ ThinEngine.prototype._partialLoadImg = function(url: string, index: number, load
 
     var onerror = (message?: string, exception?: any) => {
         if (scene) {
-            scene._removePendingData(img);
+            scene._removePendingData(tokenPendingData);
         }
 
         if (onErrorCallBack) {
@@ -205,13 +204,13 @@ ThinEngine.prototype._partialLoadImg = function(url: string, index: number, load
         }
     };
 
-    img = FileTools.LoadImage(url, onload, onerror, scene ? scene.offlineProvider : null, mimeType);
-    if (scene && img) {
-        scene._addPendingData(img);
+    FileTools.LoadImage(url, onload, onerror, scene ? scene.offlineProvider : null, mimeType);
+    if (scene) {
+        scene._addPendingData(tokenPendingData);
     }
 };
 
-ThinEngine.prototype._setCubeMapTextureParams = function(loadMipmap: boolean): void {
+ThinEngine.prototype._setCubeMapTextureParams = function(texture: InternalTexture, loadMipmap: boolean): void {
     var gl = this._gl;
     gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
     gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, loadMipmap ? gl.LINEAR_MIPMAP_LINEAR : gl.LINEAR);
@@ -224,7 +223,7 @@ ThinEngine.prototype._setCubeMapTextureParams = function(loadMipmap: boolean): v
 ThinEngine.prototype.createCubeTextureBase = function(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, beforeLoadCubeDataCallback: Nullable<(texture: InternalTexture, data: ArrayBufferView | ArrayBufferView[]) => void> = null,
-        imageHandler: Nullable<(texture: InternalTexture, imgs: HTMLImageElement[]) => void> = null): InternalTexture {
+        imageHandler: Nullable<(texture: InternalTexture, imgs: HTMLImageElement[] | ImageBitmap[]) => void> = null): InternalTexture {
     const texture = fallback ? fallback : new InternalTexture(this, InternalTextureSource.Cube);
     texture.isCube = true;
     texture.url = rootUrl;
@@ -294,7 +293,7 @@ ThinEngine.prototype.createCubeTextureBase = function(rootUrl: string, scene: Nu
             throw new Error("Cannot load cubemap because files were not defined");
         }
 
-        this._cascadeLoadImgs(scene, texture, (texture: InternalTexture, imgs: HTMLImageElement[]) => {
+        this._cascadeLoadImgs(scene, texture, (texture: InternalTexture, imgs: HTMLImageElement[] | ImageBitmap[]) => {
             if (imageHandler) {
                 imageHandler(texture, imgs);
             }
@@ -312,7 +311,7 @@ ThinEngine.prototype.createCubeTexture = function(rootUrl: string, scene: Nullab
     return this.createCubeTextureBase(
         rootUrl, scene, files, !!noMipmap, onLoad, onError, format, forcedExtension, createPolynomials, lodScale, lodOffset, fallback,
         (texture: InternalTexture, data: ArrayBufferView | ArrayBufferView[]) => this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, texture, true),
-        (texture: InternalTexture, imgs: HTMLImageElement[]) => {
+        (texture: InternalTexture, imgs: HTMLImageElement[] | ImageBitmap[]) => {
             const width = this.needPOTTextures ? ThinEngine.GetExponentOfTwo(imgs[0].width, this._caps.maxCubemapTextureSize) : imgs[0].width;
             const height = width;
 
@@ -348,7 +347,7 @@ ThinEngine.prototype.createCubeTexture = function(rootUrl: string, scene: Nullab
                 gl.generateMipmap(gl.TEXTURE_CUBE_MAP);
             }
 
-            this._setCubeMapTextureParams(!noMipmap);
+            this._setCubeMapTextureParams(texture, !noMipmap);
 
             texture.width = width;
             texture.height = height;

+ 64 - 0
src/Engines/WebGPU/webgpuBufferManager.ts

@@ -0,0 +1,64 @@
+import { DataBuffer } from '../../Meshes/dataBuffer';
+import { WebGPUDataBuffer } from '../../Meshes/WebGPU/webgpuDataBuffer';
+
+/** @hidden */
+export class WebGPUBufferManager {
+
+    private _device: GPUDevice;
+
+    constructor(device: GPUDevice) {
+        this._device = device;
+    }
+
+    public createBuffer(view: ArrayBufferView, flags: GPUBufferUsageFlags): DataBuffer {
+        const alignedLength = (view.byteLength + 3) & ~3; // 4 bytes alignments (because of the upload which requires this)
+        const verticesBufferDescriptor = {
+            size: alignedLength,
+            usage: flags
+        };
+        const buffer = this._device.createBuffer(verticesBufferDescriptor);
+        const dataBuffer = new WebGPUDataBuffer(buffer);
+        dataBuffer.references = 1;
+        dataBuffer.capacity = view.byteLength;
+
+        this.setSubData(dataBuffer, 0, view);
+
+        return dataBuffer;
+    }
+
+    public setSubData(dataBuffer: WebGPUDataBuffer, dstByteOffset: number, src: ArrayBufferView, srcByteOffset = 0, byteLength = 0): void {
+        const buffer = dataBuffer.underlyingResource as GPUBuffer;
+
+        byteLength = byteLength || src.byteLength;
+        byteLength = Math.min(byteLength, dataBuffer.capacity - dstByteOffset);
+
+        // After Migration to Canary
+        let chunkStart = src.byteOffset + srcByteOffset;
+        let chunkEnd = chunkStart + byteLength;
+
+        // 4 bytes alignments for upload
+        const alignedLength = (byteLength + 3) & ~3;
+        if (alignedLength !== byteLength) {
+            const tempView = new Uint8Array(src.buffer.slice(chunkStart, chunkEnd));
+            src = new Uint8Array(alignedLength);
+            tempView.forEach((element, index) => {
+                (src as Uint8Array)[index] = element;
+            });
+            srcByteOffset = 0;
+            chunkStart = 0;
+            chunkEnd = alignedLength;
+            byteLength = alignedLength;
+        }
+
+        // Chunk
+        const maxChunk = 1024 * 1024 * 15;
+        let offset = 0;
+        while ((chunkEnd - (chunkStart + offset)) > maxChunk) {
+            this._device.defaultQueue.writeBuffer(buffer, dstByteOffset + offset, src.buffer, chunkStart + offset, maxChunk);
+            offset += maxChunk;
+        }
+
+        this._device.defaultQueue.writeBuffer(buffer, dstByteOffset + offset, src.buffer, chunkStart + offset, byteLength - offset);
+    }
+
+}

+ 174 - 69
src/Engines/WebGPU/webgpuTextureHelper.ts

@@ -19,15 +19,23 @@
 // SOFTWARE.
 import * as WebGPUConstants from './webgpuConstants';
 import { Scalar } from '../../Maths/math.scalar';
+//import { DataBuffer } from '../../Meshes/dataBuffer';
+import { WebGPUBufferManager } from './webgpuBufferManager';
 
-export class GPUTextureHelper {
+export class WebGPUTextureHelper {
 
-    private device: GPUDevice;
-    private mipmapSampler: GPUSampler;
-    private mipmapPipeline: GPURenderPipeline;
+    private _device: GPUDevice;
+    //private _bufferManager: WebGPUBufferManager;
+    private _mipmapSampler: GPUSampler;
+    private _mipmapPipeline: GPURenderPipeline;
 
-    constructor(device: GPUDevice, glslang: any) {
-        this.device = device;
+    public static computeNumMipmapLevels(width: number, height: number) {
+        return Math.round(Scalar.Log2(Math.max(width, height))) + 1;
+    }
+
+    constructor(device: GPUDevice, glslang: any, bufferManager: WebGPUBufferManager) {
+        this._device = device;
+        //this._bufferManager = bufferManager;
 
         const mipmapVertexSource = `
             #version 450
@@ -57,9 +65,9 @@ export class GPUTextureHelper {
             }
         `;
 
-        this.mipmapSampler = device.createSampler({ minFilter: WebGPUConstants.FilterMode.Linear });
+        this._mipmapSampler = device.createSampler({ minFilter: WebGPUConstants.FilterMode.Linear });
 
-        this.mipmapPipeline = device.createRenderPipeline({
+        this._mipmapPipeline = device.createRenderPipeline({
             vertexStage: {
                 module: device.createShaderModule({
                     code: glslang.compileGLSL(mipmapVertexSource, 'vertex')
@@ -82,114 +90,135 @@ export class GPUTextureHelper {
         });
     }
 
-    async generateTexture(imageBitmap: ImageBitmap, generateMipmaps = false, invertY = false): Promise<GPUTexture> {
+    private _isImageBitmap(imageBitmap: ImageBitmap | { width: number, height: number }): imageBitmap is ImageBitmap {
+        return (imageBitmap as ImageBitmap).close !== undefined;
+    }
+
+    private _isImageBitmapArray(imageBitmap: ImageBitmap[] | { width: number, height: number }): imageBitmap is ImageBitmap[] {
+        return Array.isArray(imageBitmap as ImageBitmap[]) && (imageBitmap as ImageBitmap[])[0].close !== undefined;
+    }
+
+    public async createTexture(imageBitmap: ImageBitmap | { width: number, height: number }, generateMipmaps = false, invertY = false, premultiplyAlpha = false, format: GPUTextureFormat = WebGPUConstants.TextureFormat.RGBA8Unorm, sampleCount = 1): Promise<GPUTexture> {
         let textureSize = {
             width: imageBitmap.width,
             height: imageBitmap.height,
             depth: 1,
         };
 
-        const mipLevelCount = generateMipmaps ? Math.floor(Scalar.Log2(Math.max(imageBitmap.width, imageBitmap.height))) + 1 : 1;
+        const mipLevelCount = generateMipmaps ? WebGPUTextureHelper.computeNumMipmapLevels(imageBitmap.width, imageBitmap.height) : 1;
         const additionalUsages = generateMipmaps ? WebGPUConstants.TextureUsage.CopySrc | WebGPUConstants.TextureUsage.OutputAttachment : 0;
 
-        const srcTexture = this.device.createTexture({
+        const gpuTexture = this._device.createTexture({
             size: textureSize,
             dimension: WebGPUConstants.TextureDimension.E2d,
-            format: WebGPUConstants.TextureFormat.RGBA8Unorm,
+            format,
             usage:  WebGPUConstants.TextureUsage.CopyDst | WebGPUConstants.TextureUsage.Sampled | additionalUsages,
-            sampleCount: 1,
+            sampleCount,
             mipLevelCount
         });
 
-        if (invertY) {
-            imageBitmap = await createImageBitmap(imageBitmap, { imageOrientation: "flipY" });
-        }
+        if (this._isImageBitmap(imageBitmap)) {
+            if (invertY || premultiplyAlpha) {
+                imageBitmap = await createImageBitmap(imageBitmap, { imageOrientation: "flipY", premultiplyAlpha: premultiplyAlpha ? "premultiply" : "none" });
+            }
 
-        this.device.defaultQueue.copyImageBitmapToTexture({ imageBitmap }, { texture: srcTexture }, textureSize);
+            this._device.defaultQueue.copyImageBitmapToTexture({ imageBitmap: imageBitmap as ImageBitmap }, { texture: gpuTexture }, textureSize);
 
-        if (!generateMipmaps) {
-            return srcTexture;
-        }
+            if (!generateMipmaps) {
+                return gpuTexture;
+            }
 
-        const commandEncoder = this.device.createCommandEncoder({});
+            const commandEncoder = this._device.createCommandEncoder({});
 
-        this._generateMipmaps(srcTexture, commandEncoder, mipLevelCount);
+            this.generateMipmaps(gpuTexture, mipLevelCount, 0, commandEncoder);
 
-        this.device.defaultQueue.submit([commandEncoder.finish()]);
+            this._device.defaultQueue.submit([commandEncoder.finish()]);
+        }
 
-        return srcTexture;
+        return gpuTexture;
     }
 
-    async generateCubeTexture(imageBitmaps: ImageBitmap[], generateMipmaps = false, invertY = false): Promise<GPUTexture> {
-        const width = imageBitmaps[0].width;
-        const height = imageBitmaps[0].height;
+    public async createCubeTexture(imageBitmaps: ImageBitmap[] | { width: number, height: number }, generateMipmaps = false, invertY = false, premultiplyAlpha = false, format: GPUTextureFormat = WebGPUConstants.TextureFormat.RGBA8Unorm, sampleCount = 1): Promise<GPUTexture> {
+        const width = this._isImageBitmapArray(imageBitmaps) ? imageBitmaps[0].width : imageBitmaps.width;
+        const height = this._isImageBitmapArray(imageBitmaps) ? imageBitmaps[0].height : imageBitmaps.height;
 
-        const mipLevelCount = generateMipmaps ? Math.floor(Scalar.Log2(Math.max(width, height))) + 1 : 1;
+        const mipLevelCount = generateMipmaps ? WebGPUTextureHelper.computeNumMipmapLevels(width, height) : 1;
         const additionalUsages = generateMipmaps ? WebGPUConstants.TextureUsage.CopySrc | WebGPUConstants.TextureUsage.OutputAttachment : 0;
 
-        const srcTexture = this.device.createTexture({
+        const gpuTexture = this._device.createTexture({
             size: {
                 width,
                 height,
                 depth: 6,
             },
             dimension: WebGPUConstants.TextureDimension.E2d,
-            format: WebGPUConstants.TextureFormat.RGBA8Unorm,
+            format,
             usage: WebGPUConstants.TextureUsage.CopyDst | WebGPUConstants.TextureUsage.Sampled | additionalUsages,
-            sampleCount: 1,
+            sampleCount,
             mipLevelCount
         });
 
-        const textureSizeFace = {
-            width,
-            height,
-            depth: 1,
-        };
+        if (this._isImageBitmapArray(imageBitmaps)) {
+            const textureSizeFace = {
+                width,
+                height,
+                depth: 1,
+            };
 
-        const textureView: GPUTextureCopyView = {
-            texture: srcTexture,
-            origin: {
-                x: 0,
-                y: 0,
-                z: 0
-            },
-            mipLevel: 0
-        };
+            const textureView: GPUTextureCopyView = {
+                texture: gpuTexture,
+                origin: {
+                    x: 0,
+                    y: 0,
+                    z: 0
+                },
+                mipLevel: 0
+            };
 
-        const faces = [0, 3, 1, 4, 2, 5];
+            const faces = [0, 3, 1, 4, 2, 5];
 
-        for (let f = 0; f < faces.length; ++f) {
-            let imageBitmap = imageBitmaps[faces[f]];
+            for (let f = 0; f < faces.length; ++f) {
+                let imageBitmap = imageBitmaps[faces[f]];
 
-            if (invertY) {
-                imageBitmap = await createImageBitmap(imageBitmap, { imageOrientation: "flipY" });
-            }
+                if (invertY || premultiplyAlpha) {
+                    imageBitmap = await createImageBitmap(imageBitmap, { imageOrientation: "flipY", premultiplyAlpha: premultiplyAlpha ? "premultiply" : "none" });
+                }
 
-            (textureView.origin as GPUOrigin3DDict).z = f;
+                (textureView.origin as GPUOrigin3DDict).z = f;
 
-            this.device.defaultQueue.copyImageBitmapToTexture({ imageBitmap }, textureView, textureSizeFace);
-        }
+                this._device.defaultQueue.copyImageBitmapToTexture({ imageBitmap }, textureView, textureSizeFace);
+            }
 
-        if (generateMipmaps) {
-            const commandEncoder = this.device.createCommandEncoder({});
+            if (generateMipmaps) {
+                const commandEncoder = this._device.createCommandEncoder({});
 
-            for (let f = 0; f < faces.length; ++f) {
-                this._generateMipmaps(srcTexture, commandEncoder, mipLevelCount, f);
-            }
+                this.generateCubeMipmaps(gpuTexture, mipLevelCount, commandEncoder);
 
-            this.device.defaultQueue.submit([commandEncoder.finish()]);
+                this._device.defaultQueue.submit([commandEncoder.finish()]);
+            }
         }
 
-        return srcTexture;
+        return gpuTexture;
     }
 
-    private _generateMipmaps(srcTexture: GPUTexture, commandEncoder: GPUCommandEncoder, mipLevelCount: number, faceIndex= 0): void {
-        const bindGroupLayout = this.mipmapPipeline.getBindGroupLayout(0);
+    public generateCubeMipmaps(gpuTexture: GPUTexture, mipLevelCount: number, commandEncoder?: GPUCommandEncoder): void {
+        for (let f = 0; f < 6; ++f) {
+            this.generateMipmaps(gpuTexture, mipLevelCount, f, commandEncoder);
+        }
+    }
+
+    public generateMipmaps(gpuTexture: GPUTexture, mipLevelCount: number, faceIndex= 0, commandEncoder?: GPUCommandEncoder): void {
+        const useOwnCommandEncoder = commandEncoder === undefined;
+        const bindGroupLayout = this._mipmapPipeline.getBindGroupLayout(0);
+
+        if (useOwnCommandEncoder) {
+            commandEncoder = this._device.createCommandEncoder({});
+        }
 
         for (let i = 1; i < mipLevelCount; ++i) {
-            const passEncoder = commandEncoder.beginRenderPass({
+            const passEncoder = commandEncoder!.beginRenderPass({
                 colorAttachments: [{
-                    attachment: srcTexture.createView({
+                    attachment: gpuTexture.createView({
                         dimension: WebGPUConstants.TextureViewDimension.E2d,
                         baseMipLevel: i,
                         mipLevelCount: 1,
@@ -200,14 +229,14 @@ export class GPUTextureHelper {
                 }],
             });
 
-            const bindGroup = this.device.createBindGroup({
+            const bindGroup = this._device.createBindGroup({
                 layout: bindGroupLayout,
                 entries: [{
                     binding: 0,
-                    resource: this.mipmapSampler,
+                    resource: this._mipmapSampler,
                 }, {
                     binding: 1,
-                    resource: srcTexture.createView({
+                    resource: gpuTexture.createView({
                         dimension: WebGPUConstants.TextureViewDimension.E2d,
                         baseMipLevel: i - 1,
                         mipLevelCount: 1,
@@ -217,10 +246,86 @@ export class GPUTextureHelper {
                 }],
             });
 
-            passEncoder.setPipeline(this.mipmapPipeline);
+            passEncoder.setPipeline(this._mipmapPipeline);
             passEncoder.setBindGroup(0, bindGroup);
             passEncoder.draw(4, 1, 0, 0);
             passEncoder.endPass();
         }
+
+        if (useOwnCommandEncoder) {
+            this._device.defaultQueue.submit([commandEncoder!.finish()]);
+            commandEncoder = null as any;
+        }
     }
+
+    public async updateTexture(imageBitmap: ImageBitmap, gpuTexture: GPUTexture, width: number, height: number, faceIndex: number = 0, mipLevel: number = 0, invertY = false, premultiplyAlpha = false, offsetX = 0, offsetY = 0, commandEncoder?: GPUCommandEncoder) {
+        /*const useOwnCommandEncoder = commandEncoder === undefined;
+
+        if (useOwnCommandEncoder) {
+            commandEncoder = this._device.createCommandEncoder({});
+        }*/
+
+        const textureView: GPUTextureCopyView = {
+            texture: gpuTexture,
+            origin: {
+                x: offsetX,
+                y: offsetY,
+                z: Math.max(faceIndex, 0)
+            },
+            mipLevel: mipLevel
+        };
+
+        const textureExtent = {
+            width,
+            height,
+            depth: 1
+        };
+
+        if (invertY || premultiplyAlpha) {
+            imageBitmap = await createImageBitmap(imageBitmap, { imageOrientation: "flipY", premultiplyAlpha: premultiplyAlpha ? "premultiply" : "none" });
+        }
+
+        this._device.defaultQueue.copyImageBitmapToTexture({ imageBitmap }, textureView, textureExtent);
+
+        /*const bytesPerRow = Math.ceil(width * 4 / 256) * 256;
+
+        let dataBuffer: DataBuffer;
+        if (bytesPerRow === width * 4) {
+            dataBuffer = this._bufferManager.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._bufferManager.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);
+        }
+
+        if (useOwnCommandEncoder) {
+            this._device.defaultQueue.submit([commandEncoder!.finish()]);
+            commandEncoder = null as any;
+        }*/
+   }
 }

+ 7 - 0
src/Engines/engineFeatures.ts

@@ -0,0 +1,7 @@
+export interface EngineFeatures {
+    /** Force using Bitmap when Bitmap or HTMLImageElement can be used */
+    forceBitmapOverHTMLImageElement: boolean;
+
+    /** Indicates that the engine support rendering to as well as copying to lod float textures */
+    supportRenderAndCopyToLodForFloatTextures: boolean;
+}

+ 1 - 0
src/Engines/index.ts

@@ -14,3 +14,4 @@ export * from "./WebGL/webGL2ShaderProcessors";
 export * from "./nativeEngine";
 export * from "./Processors/shaderCodeInliner";
 export * from "./performanceConfigurator";
+export * from "./engineFeatures";

+ 11 - 0
src/Engines/thinEngine.ts

@@ -30,6 +30,7 @@ import { IEffectFallbacks } from '../Materials/iEffectFallbacks';
 import { IWebRequest } from '../Misc/interfaces/iWebRequest';
 import { CanvasGenerator } from '../Misc/canvasGenerator';
 import { PerformanceConfigurator } from './performanceConfigurator';
+import { EngineFeatures } from './engineFeatures';
 
 declare type WebRequest = import("../Misc/webRequest").WebRequest;
 declare type LoadFileError = import("../Misc/fileTools").LoadFileError;
@@ -154,6 +155,14 @@ export class ThinEngine {
     }
 
     /**
+     * Returns the features of the engine
+     */
+    public static Features: EngineFeatures = {
+        forceBitmapOverHTMLImageElement: false,
+        supportRenderAndCopyToLodForFloatTextures: false,
+    }
+
+    /**
      * Returns a string describing the current engine
      */
     public get description(): string {
@@ -678,6 +687,8 @@ export class ThinEngine {
             }
         }
 
+        ThinEngine.Features.supportRenderAndCopyToLodForFloatTextures = this._webGLVersion !== 1;
+
         // Ensures a consistent color space unpacking of textures cross browser.
         this._gl.pixelStorei(this._gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, this._gl.NONE);
 

+ 369 - 144
src/Engines/webgpuEngine.ts

@@ -21,9 +21,12 @@ 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';
+import { WebGPUTextureHelper } from './WebGPU/webgpuTextureHelper';
+import { ISceneLike, ThinEngine } from './thinEngine';
 import { Scene } from '../scene';
+import { Scalar } from '../Maths/math.scalar';
+import { WebGPUBufferManager } from './WebGPU/webgpuBufferManager';
+import { DepthTextureCreationOptions } from './depthTextureCreationOptions';
 
 /**
  * Options to load the associated Glslang library
@@ -125,7 +128,9 @@ export class WebGPUEngine extends Engine {
     private _context: GPUCanvasContext;
     private _swapChain: GPUSwapChain;
     private _mainPassSampleCount: number;
-    private _gpuTextureHelper: GPUTextureHelper;
+    private _textureHelper: WebGPUTextureHelper;
+    private _bufferManager: WebGPUBufferManager;
+    private _cacheTextureCreation: { [id: number] : Promise<GPUTexture> } = {};
 
     // Some of the internal state might change during the render pass.
     // This happens mainly during clear for the state
@@ -185,6 +190,9 @@ export class WebGPUEngine extends Engine {
     public constructor(canvas: HTMLCanvasElement, options: WebGPUEngineOptions = {}) {
         super(null);
 
+        ThinEngine.Features.forceBitmapOverHTMLImageElement = true;
+        ThinEngine.Features.supportRenderAndCopyToLodForFloatTextures = false; // TODO WEBGPU should be true but needs RTT support first for env texture to be generated correctly with this flag on
+
         options.deviceDescriptor = options.deviceDescriptor || { };
         options.swapChainFormat = options.swapChainFormat || WebGPUConstants.TextureFormat.BGRA8Unorm;
         options.antialiasing = options.antialiasing === undefined ? true : options.antialiasing;
@@ -249,7 +257,8 @@ export class WebGPUEngine extends Engine {
             })
             .then((device: GPUDevice | null) => this._device = device!)
             .then(() => {
-                this._gpuTextureHelper = new GPUTextureHelper(this._device, this._glslang);
+                this._bufferManager = new WebGPUBufferManager(this._device);
+                this._textureHelper = new WebGPUTextureHelper(this._device, this._glslang, this._bufferManager);
 
                 this._initializeLimits();
                 this._initializeContextAndSwapChain();
@@ -502,60 +511,6 @@ export class WebGPUEngine extends Engine {
     }
 
     //------------------------------------------------------------------------------
-    //                              WebGPU Buffers
-    //------------------------------------------------------------------------------
-    private _createBuffer(view: ArrayBufferView, flags: GPUBufferUsageFlags): DataBuffer {
-        const padding = view.byteLength % 4;
-        const verticesBufferDescriptor = {
-            size: view.byteLength + padding,
-            usage: flags
-        };
-        const buffer = this._device.createBuffer(verticesBufferDescriptor);
-        const dataBuffer = new WebGPUDataBuffer(buffer);
-        dataBuffer.references = 1;
-        dataBuffer.capacity = view.byteLength;
-
-        this._setSubData(dataBuffer, 0, view);
-
-        return dataBuffer;
-    }
-
-    private _setSubData(dataBuffer: WebGPUDataBuffer, dstByteOffset: number, src: ArrayBufferView, srcByteOffset = 0, byteLength = 0): void {
-        const buffer = dataBuffer.underlyingResource as GPUBuffer;
-
-        byteLength = byteLength || src.byteLength;
-        byteLength = Math.min(byteLength, dataBuffer.capacity - dstByteOffset);
-
-        // After Migration to Canary
-        let chunkStart = src.byteOffset + srcByteOffset;
-        let chunkEnd = chunkStart + byteLength;
-
-        // 4 bytes alignments for upload
-        const padding = byteLength % 4;
-        if (padding !== 0) {
-            const tempView = new Uint8Array(src.buffer.slice(chunkStart, chunkEnd));
-            src = new Uint8Array(byteLength + padding);
-            tempView.forEach((element, index) => {
-                (src as Uint8Array)[index] = element;
-            });
-            srcByteOffset = 0;
-            chunkStart = 0;
-            chunkEnd = byteLength + padding;
-            byteLength = byteLength + padding;
-        }
-
-        // Chunk
-        const maxChunk = 1024 * 1024 * 15;
-        let offset = 0;
-        while ((chunkEnd - (chunkStart + offset)) > maxChunk) {
-            this._device.defaultQueue.writeBuffer(buffer, dstByteOffset + offset, src.buffer, chunkStart + offset, maxChunk);
-            offset += maxChunk;
-        }
-
-        this._device.defaultQueue.writeBuffer(buffer, dstByteOffset + offset, src.buffer, chunkStart + offset, byteLength - offset);
-    }
-
-    //------------------------------------------------------------------------------
     //                              Vertex/Index Buffers
     //------------------------------------------------------------------------------
 
@@ -572,7 +527,7 @@ export class WebGPUEngine extends Engine {
             view = data;
         }
 
-        const dataBuffer = this._createBuffer(view, WebGPUConstants.BufferUsage.Vertex | WebGPUConstants.BufferUsage.CopyDst);
+        const dataBuffer = this._bufferManager.createBuffer(view, WebGPUConstants.BufferUsage.Vertex | WebGPUConstants.BufferUsage.CopyDst);
         return dataBuffer;
     }
 
@@ -610,7 +565,7 @@ export class WebGPUEngine extends Engine {
             }
         }
 
-        this._setSubData(dataBuffer, byteOffset, view, 0, byteLength);
+        this._bufferManager.setSubData(dataBuffer, byteOffset, view, 0, byteLength);
     }
 
     public createIndexBuffer(data: IndicesArray): DataBuffer {
@@ -634,7 +589,7 @@ export class WebGPUEngine extends Engine {
             }
         }
 
-        const dataBuffer = this._createBuffer(view, WebGPUConstants.BufferUsage.Index | WebGPUConstants.BufferUsage.CopyDst);
+        const dataBuffer = this._bufferManager.createBuffer(view, WebGPUConstants.BufferUsage.Index | WebGPUConstants.BufferUsage.CopyDst);
         dataBuffer.is32Bits = is32Bits;
         return dataBuffer;
     }
@@ -668,7 +623,7 @@ export class WebGPUEngine extends Engine {
             }
         }
 
-        this._setSubData(gpuBuffer, offset, view);
+        this._bufferManager.setSubData(gpuBuffer, offset, view);
     }
 
     public bindBuffersDirectly(vertexBuffer: DataBuffer, indexBuffer: DataBuffer, vertexDeclaration: number[], vertexStrideSize: number, effect: Effect): void {
@@ -709,7 +664,7 @@ export class WebGPUEngine extends Engine {
             view = elements;
         }
 
-        const dataBuffer = this._createBuffer(view, WebGPUConstants.BufferUsage.Uniform | WebGPUConstants.BufferUsage.CopyDst);
+        const dataBuffer = this._bufferManager.createBuffer(view, WebGPUConstants.BufferUsage.Uniform | WebGPUConstants.BufferUsage.CopyDst);
         return dataBuffer;
     }
 
@@ -739,7 +694,7 @@ export class WebGPUEngine extends Engine {
             }
         }
 
-        this._setSubData(dataBuffer, offset, view, 0, count);
+        this._bufferManager.setSubData(dataBuffer, offset, view, 0, count);
     }
 
     public bindUniformBufferBase(buffer: DataBuffer, location: number, name: string): void {
@@ -934,6 +889,8 @@ export class WebGPUEngine extends Engine {
         if (texture._webGPUTexture) {
             texture._webGPUTexture.destroy();
         }
+
+        delete this._cacheTextureCreation[texture.id];
     }
 
     private _getSamplerFilterDescriptor(internalTexture: InternalTexture): {
@@ -1017,6 +974,185 @@ export class WebGPUEngine extends Engine {
         };
     }
 
+    /** @hidden */
+    public _getWebGPUInternalFormat(format: number): GPUTextureFormat {
+        let internalFormat = WebGPUConstants.TextureFormat.RGBA8Unorm;
+
+        switch (format) {
+            case Constants.TEXTUREFORMAT_ALPHA:
+                throw "TEXTUREFORMAT_ALPHA format not supported in WebGPU";
+            case Constants.TEXTUREFORMAT_LUMINANCE:
+                throw "TEXTUREFORMAT_LUMINANCE format not supported in WebGPU";
+            case Constants.TEXTUREFORMAT_LUMINANCE_ALPHA:
+                throw "TEXTUREFORMAT_LUMINANCE_ALPHA format not supported in WebGPU";
+            case Constants.TEXTUREFORMAT_RED:
+                internalFormat = WebGPUConstants.TextureFormat.R8Snorm;
+            case Constants.TEXTUREFORMAT_RG:
+                internalFormat = WebGPUConstants.TextureFormat.RG8Snorm;
+            case Constants.TEXTUREFORMAT_RGB:
+                throw "RGB format not supported in WebGPU";
+            case Constants.TEXTUREFORMAT_RGBA:
+                internalFormat = WebGPUConstants.TextureFormat.RGBA8Unorm;
+        }
+
+        return internalFormat;
+    }
+
+    /** @hidden */
+    public _getRGBABufferInternalSizedFormat(type: number, format?: number): number {
+        return format ?? Constants.TEXTUREFORMAT_RGBA;
+    }
+
+    private _getWebGPUTextureFormat(type: number, format: number): GPUTextureFormat {
+        switch (type) {
+            case Constants.TEXTURETYPE_BYTE:
+                switch (format) {
+                    case Constants.TEXTUREFORMAT_RED:
+                        return WebGPUConstants.TextureFormat.R8Snorm;
+                    case Constants.TEXTUREFORMAT_RG:
+                        return WebGPUConstants.TextureFormat.RG8Snorm;
+                    case Constants.TEXTUREFORMAT_RGB:
+                        throw "RGB format not supported in WebGPU";
+                    case Constants.TEXTUREFORMAT_RED_INTEGER:
+                        return WebGPUConstants.TextureFormat.R8Sint;
+                    case Constants.TEXTUREFORMAT_RG_INTEGER:
+                        return WebGPUConstants.TextureFormat.RG8Sint;
+                    case Constants.TEXTUREFORMAT_RGB_INTEGER:
+                        throw "RGB_INTEGER format not supported in WebGPU";
+                    case Constants.TEXTUREFORMAT_RGBA_INTEGER:
+                        return WebGPUConstants.TextureFormat.RGBA8Sint;
+                    default:
+                        return WebGPUConstants.TextureFormat.RGBA8Snorm;
+                }
+            case Constants.TEXTURETYPE_UNSIGNED_BYTE:
+                switch (format) {
+                    case Constants.TEXTUREFORMAT_RED:
+                        return WebGPUConstants.TextureFormat.R8Unorm;
+                    case Constants.TEXTUREFORMAT_RG:
+                        return WebGPUConstants.TextureFormat.RG8Unorm;
+                    case Constants.TEXTUREFORMAT_RGB:
+                        throw "TEXTUREFORMAT_RGB format not supported in WebGPU";
+                    case Constants.TEXTUREFORMAT_RGBA:
+                        return WebGPUConstants.TextureFormat.RGBA8Unorm;
+                    case Constants.TEXTUREFORMAT_RED_INTEGER:
+                        return WebGPUConstants.TextureFormat.R8Uint;
+                    case Constants.TEXTUREFORMAT_RG_INTEGER:
+                        return WebGPUConstants.TextureFormat.RG8Uint;
+                    case Constants.TEXTUREFORMAT_RGB_INTEGER:
+                        throw "RGB_INTEGER format not supported in WebGPU";
+                    case Constants.TEXTUREFORMAT_RGBA_INTEGER:
+                        return WebGPUConstants.TextureFormat.RGBA8Uint;
+                    case Constants.TEXTUREFORMAT_ALPHA:
+                        throw "TEXTUREFORMAT_ALPHA format not supported in WebGPU";
+                    case Constants.TEXTUREFORMAT_LUMINANCE:
+                        throw "TEXTUREFORMAT_LUMINANCE format not supported in WebGPU";
+                    case Constants.TEXTUREFORMAT_LUMINANCE_ALPHA:
+                        throw "TEXTUREFORMAT_LUMINANCE_ALPHA format not supported in WebGPU";
+                    default:
+                        return WebGPUConstants.TextureFormat.RGBA8Unorm;
+                }
+            case Constants.TEXTURETYPE_SHORT:
+                switch (format) {
+                    case Constants.TEXTUREFORMAT_RED_INTEGER:
+                        return WebGPUConstants.TextureFormat.R16Sint;
+                    case Constants.TEXTUREFORMAT_RG_INTEGER:
+                        return WebGPUConstants.TextureFormat.RG16Sint;
+                    case Constants.TEXTUREFORMAT_RGB_INTEGER:
+                        throw "TEXTUREFORMAT_RGB_INTEGER format not supported in WebGPU";
+                    case Constants.TEXTUREFORMAT_RGBA_INTEGER:
+                        return WebGPUConstants.TextureFormat.RGBA16Sint;
+                    default:
+                        return WebGPUConstants.TextureFormat.RGBA16Sint;
+                }
+            case Constants.TEXTURETYPE_UNSIGNED_SHORT:
+                switch (format) {
+                    case Constants.TEXTUREFORMAT_RED_INTEGER:
+                        return WebGPUConstants.TextureFormat.R16Uint;
+                    case Constants.TEXTUREFORMAT_RG_INTEGER:
+                        return WebGPUConstants.TextureFormat.RG16Uint;
+                    case Constants.TEXTUREFORMAT_RGB_INTEGER:
+                        throw "TEXTUREFORMAT_RGB_INTEGER format not supported in WebGPU";
+                    case Constants.TEXTUREFORMAT_RGBA_INTEGER:
+                        return WebGPUConstants.TextureFormat.RGBA16Uint;
+                    default:
+                        return WebGPUConstants.TextureFormat.RGBA16Uint;
+                }
+            case Constants.TEXTURETYPE_INT:
+                switch (format) {
+                    case Constants.TEXTUREFORMAT_RED_INTEGER:
+                        return WebGPUConstants.TextureFormat.R32Sint;
+                    case Constants.TEXTUREFORMAT_RG_INTEGER:
+                        return WebGPUConstants.TextureFormat.RG32Sint;
+                    case Constants.TEXTUREFORMAT_RGB_INTEGER:
+                        throw "TEXTUREFORMAT_RGB_INTEGER format not supported in WebGPU";
+                    case Constants.TEXTUREFORMAT_RGBA_INTEGER:
+                        return WebGPUConstants.TextureFormat.RGBA32Sint;
+                    default:
+                        return WebGPUConstants.TextureFormat.RGBA32Sint;
+                }
+            case Constants.TEXTURETYPE_UNSIGNED_INTEGER: // Refers to UNSIGNED_INT
+                switch (format) {
+                    case Constants.TEXTUREFORMAT_RED_INTEGER:
+                        return WebGPUConstants.TextureFormat.R32Uint;
+                    case Constants.TEXTUREFORMAT_RG_INTEGER:
+                        return WebGPUConstants.TextureFormat.RG32Uint;
+                    case Constants.TEXTUREFORMAT_RGB_INTEGER:
+                        throw "TEXTUREFORMAT_RGB_INTEGER format not supported in WebGPU";
+                    case Constants.TEXTUREFORMAT_RGBA_INTEGER:
+                        return WebGPUConstants.TextureFormat.RGBA32Uint;
+                    default:
+                        return WebGPUConstants.TextureFormat.RGBA32Uint;
+                }
+            case Constants.TEXTURETYPE_FLOAT:
+                switch (format) {
+                    case Constants.TEXTUREFORMAT_RED:
+                        return WebGPUConstants.TextureFormat.R32Float; // By default. Other possibility is R16Float.
+                    case Constants.TEXTUREFORMAT_RG:
+                        return WebGPUConstants.TextureFormat.RG32Float; // By default. Other possibility is RG16Float.
+                    case Constants.TEXTUREFORMAT_RGB:
+                        throw "TEXTUREFORMAT_RGB format not supported in WebGPU";
+                    case Constants.TEXTUREFORMAT_RGBA:
+                        return WebGPUConstants.TextureFormat.RGBA32Float; // By default. Other possibility is RGBA16Float.
+                    default:
+                        return WebGPUConstants.TextureFormat.RGBA32Float;
+                }
+            case Constants.TEXTURETYPE_HALF_FLOAT:
+                switch (format) {
+                    case Constants.TEXTUREFORMAT_RED:
+                        return WebGPUConstants.TextureFormat.R16Float;
+                    case Constants.TEXTUREFORMAT_RG:
+                        return WebGPUConstants.TextureFormat.RG16Float;
+                    case Constants.TEXTUREFORMAT_RGB:
+                        throw "TEXTUREFORMAT_RGB format not supported in WebGPU";
+                    case Constants.TEXTUREFORMAT_RGBA:
+                        return WebGPUConstants.TextureFormat.RGBA16Float;
+                    default:
+                        return WebGPUConstants.TextureFormat.RGBA16Float;
+                }
+            case Constants.TEXTURETYPE_UNSIGNED_SHORT_5_6_5:
+                throw "TEXTURETYPE_UNSIGNED_SHORT_5_6_5 format not supported in WebGPU";
+            case Constants.TEXTURETYPE_UNSIGNED_INT_10F_11F_11F_REV:
+                throw "TEXTURETYPE_UNSIGNED_INT_10F_11F_11F_REV format not supported in WebGPU";
+            case Constants.TEXTURETYPE_UNSIGNED_INT_5_9_9_9_REV:
+                throw "TEXTURETYPE_UNSIGNED_INT_5_9_9_9_REV format not supported in WebGPU";
+            case Constants.TEXTURETYPE_UNSIGNED_SHORT_4_4_4_4:
+                throw "TEXTURETYPE_UNSIGNED_SHORT_4_4_4_4 format not supported in WebGPU";
+            case Constants.TEXTURETYPE_UNSIGNED_SHORT_5_5_5_1:
+                throw "TEXTURETYPE_UNSIGNED_SHORT_5_5_5_1 format not supported in WebGPU";
+            case Constants.TEXTURETYPE_UNSIGNED_INT_2_10_10_10_REV:
+                switch (format) {
+                    case Constants.TEXTUREFORMAT_RGBA:
+                        return WebGPUConstants.TextureFormat.RGB10A2Unorm;
+                    case Constants.TEXTUREFORMAT_RGBA_INTEGER:
+                        throw "TEXTUREFORMAT_RGBA_INTEGER format not supported in WebGPU when type is TEXTURETYPE_UNSIGNED_INT_2_10_10_10_REV";
+                    default:
+                        return WebGPUConstants.TextureFormat.RGB10A2Unorm;
+                }
+        }
+
+        return WebGPUConstants.TextureFormat.RGBA8Unorm;
+    }
+
     private _getWrappingMode(mode: number): GPUAddressMode {
         switch (mode) {
             case Engine.TEXTURE_WRAP_ADDRESSMODE:
@@ -1048,26 +1184,6 @@ export class WebGPUEngine extends Engine {
         };
     }
 
-    /**
-     * 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,
@@ -1076,58 +1192,55 @@ export class WebGPUEngine extends Engine {
         // TODO WEBGPU. this._options.textureSize
         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,
+            async (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;
+                    const imageBitmap = img as (ImageBitmap | { width: number, height: number}); // we will never get an HTMLImageElement in WebGPU
+
+                    texture.baseWidth = imageBitmap.width;
+                    texture.baseHeight = imageBitmap.height;
+                    texture.width = imageBitmap.width;
+                    texture.height = imageBitmap.height;
                     if (format) {
                         texture.format = format;
                     }
 
-                    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(async (img) => {
-                        if (img) {
-                            // TODO WEBGPU: handle format if <> 0. Note that it seems "rgb" formats don't exist in WebGPU...
-                            //let internalFormat = format ? this._getInternalFormat(format) : ((extension === ".jpg") ? gl.RGB : gl.RGBA);
-                            texture._webGPUTexture = await this._gpuTextureHelper.generateTexture(img, !noMipmap, invertY);
-                            texture._webGPUTextureView = texture._webGPUTexture.createView();
-                        }
+                    // TODO WEBGPU: handle format if <> 0. Note that it seems "rgb" formats don't exist in WebGPU...
+                    //let internalFormat = format ? this._getInternalFormat(format) : ((extension === ".jpg") ? gl.RGB : gl.RGBA);
+                    texture._webGPUTexture = await this._textureHelper.createTexture(imageBitmap, !noMipmap, invertY, false, this._getWebGPUTextureFormat(texture.type, texture.format));
+                    texture._webGPUTextureView = texture._webGPUTexture.createView();
 
-                        if (scene) {
-                            scene._removePendingData(texture);
-                        }
+                    if (scene) {
+                        scene._removePendingData(texture);
+                    }
 
-                        texture.isReady = true;
+                    processFunction(texture.width, texture.height, imageBitmap, extension, texture, () => {});
 
-                        texture.onLoadedObservable.notifyObservers(texture);
-                        texture.onLoadedObservable.clear();
-                    });
+                    texture.isReady = true;
+
+                    texture.onLoadedObservable.notifyObservers(texture);
+                    texture.onLoadedObservable.clear();
             },
             () => false,
             buffer, fallback, format, forcedExtension, mimeType
         );
     }
 
+    /** @hidden */
+    public _setCubeMapTextureParams(texture: InternalTexture, loadMipmap: boolean) {
+        texture.samplingMode = loadMipmap ? Engine.TEXTURE_TRILINEAR_SAMPLINGMODE : Engine.TEXTURE_BILINEAR_SAMPLINGMODE;
+
+        // TODO WEBGPU the webgl code also sets wraps / wrapt to CLAMP_TO_EDGE by calling gl.texParameteri but we can't do it as wrapu/wraps are properties of BaseTexture
+    }
+
     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 {
 
         return this.createCubeTextureBase(
             rootUrl, scene, files, !!noMipmap, onLoad, onError, format, forcedExtension, createPolynomials, lodScale, lodOffset, fallback,
             null,
-            (texture: InternalTexture, imgs: HTMLImageElement[]) => {
-                const width = imgs[0].width;
+            async (texture: InternalTexture, imgs: HTMLImageElement[] | ImageBitmap[]) => {
+                const imageBitmaps = imgs as ImageBitmap[]; // we will always get an ImageBitmap array in WebGPU
+                const width = imageBitmaps[0].width;
                 const height = width;
 
                 // TODO WEBGPU. Cube Texture Sampling Mode.
@@ -1135,42 +1248,45 @@ export class WebGPUEngine extends Engine {
 
                 // TODO WEBGPU. handle format if <> 0
                 //const internalFormat = format ? this._getInternalFormat(format) : this._gl.RGBA;
-                const imageBitmapPromises: Array<Promise<ImageBitmap>> = [];
+                texture._webGPUTexture = await this._textureHelper.createCubeTexture(imageBitmaps, !noMipmap, false, false, this._getWebGPUTextureFormat(texture.type, texture.format));
+                texture._webGPUTextureView = texture._webGPUTexture.createView({
+                    dimension: WebGPUConstants.TextureViewDimension.Cube
+                });
 
-                for (var index = 0; index < 6; index++) {
-                    imageBitmapPromises.push(createImageBitmap(imgs[index]));
+                texture.width = width;
+                texture.height = height;
+                texture.isReady = true;
+                if (format) {
+                    texture.format = format;
                 }
 
-                Promise.all(imageBitmapPromises).then(async (imageBitmaps) => {
-                    texture._webGPUTexture = await this._gpuTextureHelper.generateCubeTexture(imageBitmaps, !noMipmap);
-                    texture._webGPUTextureView = texture._webGPUTexture.createView({
-                        dimension: WebGPUConstants.TextureViewDimension.Cube
-                    });
+                texture.onLoadedObservable.notifyObservers(texture);
+                texture.onLoadedObservable.clear();
 
-                    texture.width = width;
-                    texture.height = height;
-                    texture.isReady = true;
-                    if (format) {
-                        texture.format = format;
-                    }
-
-                    texture.onLoadedObservable.notifyObservers(texture);
-                    texture.onLoadedObservable.clear();
-
-                    if (onLoad) {
-                        onLoad();
-                    }
-                });
+                if (onLoad) {
+                    onLoad();
+                }
             }
         );
     }
 
-    public updateTextureSamplingMode(samplingMode: number, texture: InternalTexture): void {
-        texture.samplingMode = samplingMode;
+    public generateMipMapsForCubemap(texture: InternalTexture, unbind = true) {
+        if (texture.generateMipMaps) {
+            this._ensureTextureCreated(texture).then((gpuTexture) => {
+                this._textureHelper.generateCubeMipmaps(gpuTexture, WebGPUTextureHelper.computeNumMipmapLevels(texture.width, texture.height));
+            });
+        }
+    }
+
+    /** @hidden */
+    public _createDepthStencilCubeTexture(size: number, options: DepthTextureCreationOptions): InternalTexture {
+        console.warn("_createDepthStencilCubeTexture not implemented yet in WebGPU");
+
+        return null as any;
     }
 
-    public updateDynamicTexture(texture: Nullable<InternalTexture>, canvas: HTMLCanvasElement, invertY: boolean, premulAlpha: boolean = false, format?: number): void {
-        throw "Unimplemented updateDynamicTexture on WebGPU so far";
+    public updateTextureSamplingMode(samplingMode: number, texture: InternalTexture): void {
+        texture.samplingMode = samplingMode;
     }
 
     public setTexture(channel: number, _: Nullable<WebGLUniformLocation>, texture: Nullable<BaseTexture>, name: string): void {
@@ -1225,20 +1341,123 @@ export class WebGPUEngine extends Engine {
         this._bindTextureDirectly(0, texture);
     }
 
+    private _ensureTextureCreated(texture: InternalTexture, width?: number, height?: number): Promise<GPUTexture> {
+        let promise = this._cacheTextureCreation[texture.id];
+
+        if (promise) {
+            return promise;
+        }
+
+        promise = Promise.resolve(texture._webGPUTexture as GPUTexture);
+
+        if (width === undefined) {
+            width = texture.width;
+        }
+        if (height === undefined) {
+            height = texture.height;
+        }
+
+        if (!texture._webGPUTexture) {
+            if (texture.isCube) {
+                promise = this._textureHelper.createCubeTexture({ width, height }, texture.generateMipMaps, texture.invertY, false, this._getWebGPUTextureFormat(texture.type, texture.format)).then((gpuTexture) => {
+                    texture._webGPUTexture = gpuTexture;
+                    texture._webGPUTextureView = texture._webGPUTexture.createView({
+                        dimension: WebGPUConstants.TextureViewDimension.Cube,
+                        mipLevelCount: Math.round(Scalar.Log2(Math.max(width!, height!))) + 1,
+                        baseArrayLayer: 0,
+                        baseMipLevel: 0,
+                        aspect: WebGPUConstants.TextureAspect.All
+                    });
+                    return gpuTexture;
+                });
+            } else {
+                promise = this._textureHelper.createTexture({ width, height }, texture.generateMipMaps, texture.invertY, false, this._getWebGPUTextureFormat(texture.type, texture.format)).then((gpuTexture) => {
+                    texture._webGPUTexture = gpuTexture;
+                    texture._webGPUTextureView = texture._webGPUTexture.createView();
+                    return gpuTexture;
+                });
+            }
+
+            texture.width = texture.baseWidth = width;
+            texture.height = texture.baseHeight = height;
+        }
+
+        this._cacheTextureCreation[texture.id] = promise;
+
+        return promise;
+    }
+
+    public updateDynamicTexture(texture: Nullable<InternalTexture>, canvas: HTMLCanvasElement | OffscreenCanvas, invertY: boolean, premulAlpha: boolean = false, format?: number, forceBindTexture?: boolean): void {
+        if (!texture) {
+            return;
+        }
+
+        const width = canvas.width, height = canvas.height;
+
+        Promise.all([createImageBitmap(canvas), this._ensureTextureCreated(texture, width, height)]).then(([bitmap, gpuTexture]) => {
+            // TODO WEBGPU: handle format if <> 0
+            // let internalFormat = format ? this._getInternalFormat(format) : this._gl.RGBA;
+            this._textureHelper.updateTexture(bitmap, gpuTexture, width, height, 0, 0, invertY, premulAlpha);
+
+            if (texture.generateMipMaps) {
+                const mipmapCount = WebGPUTextureHelper.computeNumMipmapLevels(width, height);
+
+                if (texture.isCube) {
+                    this._textureHelper.generateCubeMipmaps(gpuTexture, mipmapCount);
+                } else {
+                    this._textureHelper.generateMipmaps(gpuTexture, mipmapCount);
+                }
+            }
+
+            texture.isReady = true;
+        });
+    }
+
+    public updateTextureData(texture: InternalTexture, imageData: ArrayBufferView, xOffset: number, yOffset: number, width: number, height: number, faceIndex: number = 0, lod: number = 0): void {
+        const imgData = new ImageData(new Uint8ClampedArray(imageData.buffer), width, height);
+
+        Promise.all([createImageBitmap(imgData), this._ensureTextureCreated(texture)]).then(([bitmap, gpuTexture]) => {
+            this._textureHelper.updateTexture(bitmap, gpuTexture, width, height, faceIndex, lod, texture.invertY, false, xOffset, yOffset);
+        });
+    }
+
     /** @hidden */
     public _uploadCompressedDataToTextureDirectly(texture: InternalTexture, internalFormat: number, width: number, height: number, data: ArrayBufferView, faceIndex: number = 0, lod: number = 0) {
+        console.warn("_uploadCompressedDataToTextureDirectly not implemented yet in WebGPU");
     }
 
     /** @hidden */
-    public _uploadDataToTextureDirectly(texture: InternalTexture, imageData: ArrayBufferView, faceIndex: number = 0, lod: number = 0): void {
+    public _uploadDataToTextureDirectly(texture: InternalTexture, imageData: ArrayBufferView, faceIndex: number = 0, lod: number = 0, babylonInternalFormat?: number, useTextureWidthAndHeight = false): void {
+        // TODO WEBPU what to do with babylonInternalFormat? Texture format is set at creation time and can't be changed afterwards...
+
+        const lodMaxWidth = Math.round(Math.log(texture.width) * Math.LOG2E);
+        const lodMaxHeight = Math.round(Math.log(texture.height) * Math.LOG2E);
+
+        const width = useTextureWidthAndHeight ? texture.width : Math.pow(2, Math.max(lodMaxWidth - lod, 0));
+        const height = useTextureWidthAndHeight ? texture.height : Math.pow(2, Math.max(lodMaxHeight - lod, 0));
+
+        const imgData = new ImageData(new Uint8ClampedArray(imageData.buffer, 0, width * height * 4), width, height);
+
+        Promise.all([createImageBitmap(imgData), this._ensureTextureCreated(texture, width, height)]).then(([bitmap, gpuTexture]) => {
+            this._textureHelper.updateTexture(bitmap, gpuTexture, width, height, faceIndex, lod, texture.invertY, false);
+        });
     }
 
     /** @hidden */
     public _uploadArrayBufferViewToTexture(texture: InternalTexture, imageData: ArrayBufferView, faceIndex: number = 0, lod: number = 0): void {
+        this._uploadDataToTextureDirectly(texture, imageData, faceIndex, lod);
     }
 
     /** @hidden */
-    public _uploadImageToTexture(texture: InternalTexture, image: HTMLImageElement, faceIndex: number = 0, lod: number = 0) {
+    public _uploadImageToTexture(texture: InternalTexture, image: HTMLImageElement | ImageBitmap, faceIndex: number = 0, lod: number = 0) {
+        const bitmap = image as ImageBitmap; // in WebGPU we will always get an ImageBitmap, not an HTMLImageElement
+
+        this._ensureTextureCreated(texture).then((gpuTexture) => {
+            const width = Math.ceil(texture.width / (1 << lod));
+            const height = Math.ceil(texture.height / (1 << lod));
+
+            this._textureHelper.updateTexture(bitmap, gpuTexture, width, height, faceIndex, lod, texture.invertY);
+        });
     }
 
     //------------------------------------------------------------------------------
@@ -2021,6 +2240,12 @@ export class WebGPUEngine extends Engine {
                             bindingInfo.texture._webGPUSampler = gpuSampler;
                         }
 
+                        // TODO WEBGPU Remove this when all testings are ok
+                        if (!bindingInfo.texture._webGPUTextureView) {
+                            console.error("Trying to bind a null gpu texture! bindingDefinition=", bindingDefinition, " | bindingInfo=", bindingInfo);
+                            debugger;
+                        }
+
                         entries.push({
                             binding: bindingInfo.textureBinding,
                             resource: bindingInfo.texture._webGPUTextureView!,

+ 1 - 1
src/Materials/Textures/Loaders/basisTextureLoader.ts

@@ -48,7 +48,7 @@ export class _BasisTextureLoader implements IInternalTextureLoader {
         BasisTools.TranscodeAsync(data, transcodeConfig).then((result) => {
             var hasMipmap = result.fileInfo.images[0].levels.length > 1 && texture.generateMipMaps;
             BasisTools.LoadTextureFromTranscodeResult(texture, result);
-            (texture.getEngine() as Engine)._setCubeMapTextureParams(hasMipmap);
+            (texture.getEngine() as Engine)._setCubeMapTextureParams(texture, hasMipmap);
             texture.isReady = true;
             texture.onLoadedObservable.notifyObservers(texture);
             texture.onLoadedObservable.clear();

+ 1 - 1
src/Materials/Textures/Loaders/ddsTextureLoader.ts

@@ -76,7 +76,7 @@ export class _DDSTextureLoader implements IInternalTextureLoader {
                 engine.generateMipMapsForCubemap(texture, false);
             }
         }
-        engine._setCubeMapTextureParams(loadMipmap);
+        engine._setCubeMapTextureParams(texture, loadMipmap);
         texture.isReady = true;
         texture.onLoadedObservable.notifyObservers(texture);
         texture.onLoadedObservable.clear();

+ 1 - 1
src/Materials/Textures/Loaders/ktxTextureLoader.ts

@@ -55,7 +55,7 @@ export class _KTXTextureLoader implements IInternalTextureLoader {
         texture.width = ktx.pixelWidth;
         texture.height = ktx.pixelHeight;
 
-        engine._setCubeMapTextureParams(loadMipmap);
+        engine._setCubeMapTextureParams(texture, loadMipmap);
         texture.isReady = true;
         texture.onLoadedObservable.notifyObservers(texture);
         texture.onLoadedObservable.clear();

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

@@ -261,6 +261,14 @@ export class InternalTexture {
     public _references: number = 1;
 
     private _engine: ThinEngine;
+    private _id: number;
+
+    private static _Counter = 0;
+
+    /** Gets the unique id of the internal texture */
+    public get id() {
+        return this._id;
+    }
 
     /**
      * Gets the Engine the texture belongs to.
@@ -286,9 +294,10 @@ export class InternalTexture {
     constructor(engine: ThinEngine, source: InternalTextureSource, delayAllocation = false) {
         this._engine = engine;
         this._source = source;
+        this._id = InternalTexture._Counter++;
 
         if (!delayAllocation) {
-            this._webGLTexture = engine._createTexture();
+            this._webGLTexture = engine._createTexture(); // TODO WebGPU: don't do this in WebGPU
         }
     }
 
@@ -449,6 +458,7 @@ export class InternalTexture {
     public _swapAndDie(target: InternalTexture): void {
         target._webGLTexture = this._webGLTexture;
         target._webGPUTexture = this._webGPUTexture;
+        target._webGPUTextureView = this._webGPUTextureView;
         target._isRGBD = this._isRGBD;
 
         if (this._framebuffer) {

+ 3 - 2
src/Misc/environmentTextureTools.ts

@@ -17,6 +17,7 @@ import "../Materials/Textures/baseTexture.polynomial";
 import "../Shaders/rgbdEncode.fragment";
 import "../Shaders/rgbdDecode.fragment";
 import { Engine } from '../Engines/engine';
+import { ThinEngine } from '../Engines/thinEngine';
 
 /**
  * Raw texture data and descriptor sufficient for WebGL texture upload
@@ -456,7 +457,7 @@ export class EnvironmentTextureTools {
             lodTextures = {};
         }
         // in webgl 1 there are no ways to either render or copy lod level information for float textures.
-        else if (engine.webGLVersion < 2) {
+        else if (!ThinEngine.Features.supportRenderAndCopyToLodForFloatTextures) {
             expandTexture = false;
         }
         // If half float available we can uncompress the texture
@@ -545,7 +546,7 @@ export class EnvironmentTextureTools {
                 let url = URL.createObjectURL(blob);
                 let promise: Promise<void>;
 
-                if (typeof Image === "undefined") {
+                if (typeof Image === "undefined" || ThinEngine.Features.forceBitmapOverHTMLImageElement) {
                     promise = createImageBitmap(blob).then((img) => {
                         return this._OnImageReadyAsync(img, engine, expandTexture, rgbdPostProcess, url, face, i, generateNonLODTextures, lodTextures, cubeRtt, texture);
                     });

+ 1 - 1
src/Misc/fileTools.ts

@@ -155,7 +155,7 @@ export class FileTools {
             url = FileTools.PreprocessUrl(input);
         }
 
-        if (typeof Image === "undefined") {
+        if (typeof Image === "undefined" || ThinEngine.Features.forceBitmapOverHTMLImageElement) {
             FileTools.LoadFile(url, (data) => {
                 createImageBitmap(new Blob([data], { type: mimeType })).then((imgBmp) => {
                     onLoad(imgBmp);