瀏覽代碼

Handle compressed textures (no mipmap support yet)

Popov72 4 年之前
父節點
當前提交
7056775669
共有 5 個文件被更改,包括 245 次插入180 次删除
  1. 4 1
      src/Engines/WebGPU/webgpuHardwareTexture.ts
  2. 108 113
      src/Engines/WebGPU/webgpuTextureHelper.ts
  3. 13 0
      src/Engines/constants.ts
  4. 116 62
      src/Engines/webgpuEngine.ts
  5. 4 4
      src/Misc/dds.ts

+ 4 - 1
src/Engines/WebGPU/webgpuHardwareTexture.ts

@@ -1,5 +1,6 @@
 import { HardwareTextureWrapper } from '../../Materials/Textures/hardwareTextureWrapper';
 import { Nullable } from '../../types';
+import * as WebGPUConstants from './webgpuConstants';
 
 /** @hidden */
 export class WebGPUHardwareTexture implements HardwareTextureWrapper {
@@ -20,6 +21,8 @@ export class WebGPUHardwareTexture implements HardwareTextureWrapper {
         return this._webgpuSampler;
     }
 
+    public format: GPUTextureFormat = WebGPUConstants.TextureFormat.RGBA8Unorm;
+
     constructor(existingTexture: Nullable<GPUTexture> = null) {
         this._webgpuTexture = existingTexture;
         this._webgpuTextureView = null;
@@ -48,4 +51,4 @@ export class WebGPUHardwareTexture implements HardwareTextureWrapper {
         this._webgpuTexture?.destroy();
         this.reset();
     }
-};
+}

+ 108 - 113
src/Engines/WebGPU/webgpuTextureHelper.ts

@@ -20,7 +20,6 @@
 import * as WebGPUConstants from './webgpuConstants';
 import { Scalar } from '../../Maths/math.scalar';
 import { WebGPUBufferManager } from './webgpuBufferManager';
-import { Nullable } from '../../types';
 
 export class WebGPUTextureHelper {
 
@@ -90,16 +89,16 @@ export class WebGPUTextureHelper {
         });
     }
 
-    private _isImageBitmap(imageBitmap: ImageBitmap | { width: number, height: number }): imageBitmap is ImageBitmap {
+    public 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[] {
+    public isImageBitmapArray(imageBitmap: ImageBitmap[] | { width: number, height: number }): imageBitmap is ImageBitmap[] {
         return Array.isArray(imageBitmap as ImageBitmap[]) && (imageBitmap as ImageBitmap[])[0].close !== undefined;
     }
 
-    public createTexture(imageBitmap: ImageBitmap | { width: number, height: number }, generateMipmaps = false, invertY = false, premultiplyAlpha = false, format: GPUTextureFormat = WebGPUConstants.TextureFormat.RGBA8Unorm,
-        sampleCount = 1, commandEncoder?: GPUCommandEncoder): [GPUTexture, Nullable<Promise<void>>]
+    public createTexture(imageBitmap: ImageBitmap | { width: number, height: number }, hasMipmaps = false, generateMipmaps = false, invertY = false, premultiplyAlpha = false, format: GPUTextureFormat = WebGPUConstants.TextureFormat.RGBA8Unorm,
+        sampleCount = 1, commandEncoder?: GPUCommandEncoder): GPUTexture
     {
         let textureSize = {
             width: imageBitmap.width,
@@ -107,8 +106,8 @@ export class WebGPUTextureHelper {
             depth: 1,
         };
 
-        const mipLevelCount = generateMipmaps ? WebGPUTextureHelper.computeNumMipmapLevels(imageBitmap.width, imageBitmap.height) : 1;
-        const additionalUsages = generateMipmaps ? WebGPUConstants.TextureUsage.CopySrc | WebGPUConstants.TextureUsage.OutputAttachment : 0;
+        const mipLevelCount = hasMipmaps ? WebGPUTextureHelper.computeNumMipmapLevels(imageBitmap.width, imageBitmap.height) : 1;
+        const additionalUsages = hasMipmaps ? WebGPUConstants.TextureUsage.CopySrc /*| WebGPUConstants.TextureUsage.OutputAttachment*/ : 0;
 
         const gpuTexture = this._device.createTexture({
             size: textureSize,
@@ -119,50 +118,25 @@ export class WebGPUTextureHelper {
             mipLevelCount
         });
 
-        if (this._isImageBitmap(imageBitmap)) {
-            let promise: Promise<ImageBitmap>;
+        if (this.isImageBitmap(imageBitmap)) {
+            this.updateTexture(imageBitmap, gpuTexture, imageBitmap.width, imageBitmap.height, format, 0, 0, invertY, premultiplyAlpha, 0, 0, commandEncoder);
 
-            if (invertY || premultiplyAlpha) {
-                promise = createImageBitmap(imageBitmap, { imageOrientation: invertY ? "flipY" : "none", premultiplyAlpha: premultiplyAlpha ? "premultiply" : "none" });
-            } else {
-                promise = Promise.resolve(imageBitmap);
+            if (hasMipmaps && generateMipmaps) {
+                this.generateMipmaps(gpuTexture, mipLevelCount, 0, commandEncoder);
             }
-
-            return [gpuTexture, new Promise((resolve) => {
-                promise.then((imageBitmap) => {
-                    this._device.defaultQueue.copyImageBitmapToTexture({ imageBitmap }, { texture: gpuTexture }, textureSize);
-
-                    if (generateMipmaps) {
-                        const useOwnCommandEncoder = commandEncoder === undefined;
-
-                        if (useOwnCommandEncoder) {
-                            commandEncoder = this._device.createCommandEncoder({});
-                        }
-
-                        this.generateMipmaps(gpuTexture, mipLevelCount, 0, commandEncoder);
-
-                        if (useOwnCommandEncoder) {
-                            this._device.defaultQueue.submit([commandEncoder!.finish()]);
-                            commandEncoder = null as any;
-                        }
-                    }
-
-                    resolve();
-                });
-            })];
         }
 
-        return [gpuTexture, null];
+        return gpuTexture;
     }
 
-    public createCubeTexture(imageBitmaps: ImageBitmap[] | { width: number, height: number }, generateMipmaps = false, invertY = false, premultiplyAlpha = false, format: GPUTextureFormat = WebGPUConstants.TextureFormat.RGBA8Unorm,
-        sampleCount = 1, commandEncoder?: GPUCommandEncoder): [GPUTexture, Nullable<Promise<void>>]
+    public createCubeTexture(imageBitmaps: ImageBitmap[] | { width: number, height: number }, hasMipmaps = false, generateMipmaps = false, invertY = false, premultiplyAlpha = false, format: GPUTextureFormat = WebGPUConstants.TextureFormat.RGBA8Unorm,
+        sampleCount = 1, commandEncoder?: GPUCommandEncoder): GPUTexture
     {
-        const width = this._isImageBitmapArray(imageBitmaps) ? imageBitmaps[0].width : imageBitmaps.width;
-        const height = this._isImageBitmapArray(imageBitmaps) ? imageBitmaps[0].height : imageBitmaps.height;
+        const width = this.isImageBitmapArray(imageBitmaps) ? imageBitmaps[0].width : imageBitmaps.width;
+        const height = this.isImageBitmapArray(imageBitmaps) ? imageBitmaps[0].height : imageBitmaps.height;
 
-        const mipLevelCount = generateMipmaps ? WebGPUTextureHelper.computeNumMipmapLevels(width, height) : 1;
-        const additionalUsages = generateMipmaps ? WebGPUConstants.TextureUsage.CopySrc | WebGPUConstants.TextureUsage.OutputAttachment : 0;
+        const mipLevelCount = hasMipmaps ? WebGPUTextureHelper.computeNumMipmapLevels(width, height) : 1;
+        const additionalUsages = hasMipmaps ? WebGPUConstants.TextureUsage.CopySrc | WebGPUConstants.TextureUsage.OutputAttachment : 0;
 
         const gpuTexture = this._device.createTexture({
             size: {
@@ -177,71 +151,15 @@ export class WebGPUTextureHelper {
             mipLevelCount
         });
 
-        if (this._isImageBitmapArray(imageBitmaps)) {
-            const textureSizeFace = {
-                width,
-                height,
-                depth: 1,
-            };
-
-            const textureView: GPUTextureCopyView = {
-                texture: gpuTexture,
-                origin: {
-                    x: 0,
-                    y: 0,
-                    z: 0
-                },
-                mipLevel: 0
-            };
-
-            const faces = [0, 3, 1, 4, 2, 5];
+        if (this.isImageBitmapArray(imageBitmaps)) {
+            this.updateCubeTextures(imageBitmaps, gpuTexture, width, height, format, invertY, premultiplyAlpha, 0, 0, commandEncoder);
 
-            const promises: Promise<void>[] = [];
-
-            for (let f = 0; f < faces.length; ++f) {
-                let imageBitmap = imageBitmaps[faces[f]];
-
-                let promise: Promise<ImageBitmap>;
-
-                if (invertY || premultiplyAlpha) {
-                    promise = createImageBitmap(imageBitmap, { imageOrientation: invertY ? "flipY" : "none", premultiplyAlpha: premultiplyAlpha ? "premultiply" : "none" });
-                } else {
-                    promise = Promise.resolve(imageBitmap);
-                }
-
-                promises.push(new Promise((resolve) => {
-                    promise.then((imageBitmap) => {
-                        (textureView.origin as GPUOrigin3DDict).z = f;
-
-                        this._device.defaultQueue.copyImageBitmapToTexture({ imageBitmap }, textureView, textureSizeFace);
-
-                        resolve();
-                    });
-                }));
+            if (hasMipmaps && generateMipmaps) {
+                this.generateCubeMipmaps(gpuTexture, mipLevelCount, commandEncoder);
             }
-
-            return [gpuTexture, new Promise((resolve) => {
-                Promise.all(promises).then(() => {
-                    if (generateMipmaps) {
-                        const useOwnCommandEncoder = commandEncoder === undefined;
-
-                        if (useOwnCommandEncoder) {
-                            commandEncoder = this._device.createCommandEncoder({});
-                        }
-
-                        this.generateCubeMipmaps(gpuTexture, mipLevelCount, commandEncoder);
-
-                        if (useOwnCommandEncoder) {
-                            this._device.defaultQueue.submit([commandEncoder!.finish()]);
-                            commandEncoder = null as any;
-                        }
-                    }
-                    resolve();
-                });
-            })];
         }
 
-        return [gpuTexture, null];
+        return gpuTexture;
     }
 
     public generateCubeMipmaps(gpuTexture: GPUTexture, mipLevelCount: number, commandEncoder?: GPUCommandEncoder): void {
@@ -301,10 +219,51 @@ export class WebGPUTextureHelper {
         }
     }
 
-    public updateTexture(imageBitmap: ImageBitmap, gpuTexture: GPUTexture, width: number, height: number, faceIndex: number = 0, mipLevel: number = 0, invertY = false, premultiplyAlpha = false, offsetX = 0, offsetY = 0,
+    private _getBlockInformationFromFormat(format: GPUTextureFormat): { width: number, height: number, length: number } {
+        switch (format) {
+            case WebGPUConstants.TextureFormat.BC7RGBAUNORM:
+            case WebGPUConstants.TextureFormat.BC7RGBAUNORMSRGB:
+                return { width: 4, height: 4, length: 16 };
+            case WebGPUConstants.TextureFormat.BC6HRGBUFLOAT:
+                return { width: 4, height: 4, length: 16 };
+            case WebGPUConstants.TextureFormat.BC6HRGBSFLOAT:
+                return { width: 4, height: 4, length: 16 };
+            case WebGPUConstants.TextureFormat.BC3RGBAUNORM:
+            case WebGPUConstants.TextureFormat.BC3RGBAUNORMSRGB:
+                return { width: 4, height: 4, length: 16 };
+            case WebGPUConstants.TextureFormat.BC2RGBAUNORM:
+            case WebGPUConstants.TextureFormat.BC2RGBAUNORMSRGB:
+                return { width: 4, height: 4, length: 16 };
+            case WebGPUConstants.TextureFormat.BC1RGBAUNORM:
+            case WebGPUConstants.TextureFormat.BC1RGBAUNORMSRGB:
+                return { width: 4, height: 4, length: 8 };
+
+            case WebGPUConstants.TextureFormat.RGBA16Float:
+                return { width: 1, height: 1, length: 8 };
+            case WebGPUConstants.TextureFormat.RGBA32Float:
+                return { width: 1, height: 1, length: 16 };
+        }
+
+        return { width: 1, height: 1, length: 4 };
+    }
+
+    public updateCubeTextures(imageBitmaps: ImageBitmap[], gpuTexture: GPUTexture, width: number, height: number, format: GPUTextureFormat, invertY = false, premultiplyAlpha = false, offsetX = 0, offsetY = 0,
+        commandEncoder?: GPUCommandEncoder): void {
+        const faces = [0, 3, 1, 4, 2, 5];
+
+        for (let f = 0; f < faces.length; ++f) {
+            let imageBitmap = imageBitmaps[faces[f]];
+
+            this.updateTexture(imageBitmap, gpuTexture, width, height, format, f, 0, invertY, premultiplyAlpha, offsetX, offsetY, commandEncoder);
+        }
+    }
+
+    public updateTexture(imageBitmap: ImageBitmap | Uint8Array, gpuTexture: GPUTexture, width: number, height: number, format: GPUTextureFormat, faceIndex: number = 0, mipLevel: number = 0, invertY = false, premultiplyAlpha = false, offsetX = 0, offsetY = 0,
         commandEncoder?: GPUCommandEncoder): void
     {
-        const textureView: GPUTextureCopyView = {
+        const blockInformation = this._getBlockInformationFromFormat(format);
+
+        const textureCopyView: GPUTextureCopyView = {
             texture: gpuTexture,
             origin: {
                 x: offsetX,
@@ -315,17 +274,53 @@ export class WebGPUTextureHelper {
         };
 
         const textureExtent = {
-            width,
-            height,
+            width: Math.ceil(width / blockInformation.width) * blockInformation.width,
+            height: Math.ceil(height / blockInformation.height) * blockInformation.height,
             depth: 1
         };
 
-        if (invertY || premultiplyAlpha) {
-            createImageBitmap(imageBitmap, { imageOrientation: invertY ? "flipY" : "none", premultiplyAlpha: premultiplyAlpha ? "premultiply" : "none" }).then((imageBitmap) => {
-                this._device.defaultQueue.copyImageBitmapToTexture({ imageBitmap }, textureView, textureExtent);
-            });
+        if ((imageBitmap as Uint8Array).byteLength !== undefined) {
+            imageBitmap = imageBitmap as Uint8Array;
+
+            const aligned = Math.ceil(width * 4 / 256) * 256 === width * 4;
+
+            if (aligned) {
+                const buffer = this._bufferManager.createRawBuffer(imageBitmap.byteLength, GPUBufferUsage.MAP_WRITE | GPUBufferUsage.COPY_SRC, true);
+
+                const arrayBuffer = buffer.getMappedRange();
+
+                new Uint8Array(arrayBuffer).set(imageBitmap);
+
+                buffer.unmap();
+
+                // TODO WEBGPU should we reuse the passed in commandEncoder (if defined)? If yes, we must delay the destroying of the buffer until after the commandEncoder has been submitted...
+                const copyCommandEncoder = this._device.createCommandEncoder({});
+
+                copyCommandEncoder.copyBufferToTexture({
+                    buffer: buffer,
+                    offset: 0,
+                    bytesPerRow: Math.ceil(width / blockInformation.width) * blockInformation.length
+                }, textureCopyView, textureExtent);
+
+                this._device.defaultQueue.submit([copyCommandEncoder.finish()]);
+
+                buffer.destroy();
+            } else {
+                this._device.defaultQueue.writeTexture(textureCopyView, imageBitmap, {
+                    offset: 0,
+                    bytesPerRow: Math.ceil(width / blockInformation.width) * blockInformation.length
+                }, textureExtent);
+            }
         } else {
-            this._device.defaultQueue.copyImageBitmapToTexture({ imageBitmap }, textureView, textureExtent);
+            imageBitmap = imageBitmap as ImageBitmap;
+
+            if (invertY || premultiplyAlpha) {
+                createImageBitmap(imageBitmap, { imageOrientation: invertY ? "flipY" : "none", premultiplyAlpha: premultiplyAlpha ? "premultiply" : "none" }).then((imageBitmap) => {
+                    this._device.defaultQueue.copyImageBitmapToTexture({ imageBitmap }, textureCopyView, textureExtent);
+                });
+            } else {
+                this._device.defaultQueue.copyImageBitmapToTexture({ imageBitmap }, textureCopyView, textureExtent);
+            }
         }
     }
 

+ 13 - 0
src/Engines/constants.ts

@@ -153,6 +153,19 @@ export class Constants {
     /** RGBA_INTEGER */
     public static readonly TEXTUREFORMAT_RGBA_INTEGER = 11;
 
+    /** Compressed BC7 */
+    public static readonly TEXTUREFORMAT_COMPRESSED_RGBA_BPTC_UNORM = 36492;
+    /** Compressed BC6 unsigned float */
+    public static readonly TEXTUREFORMAT_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT = 36495;
+    /** Compressed BC6 signed float */
+    public static readonly TEXTUREFORMAT_COMPRESSED_RGB_BPTC_SIGNED_FLOAT = 36494;
+    /** Compressed BC3 */
+    public static readonly TEXTUREFORMAT_COMPRESSED_RGBA_S3TC_DXT5 = 33779;
+    /** Compressed BC2 */
+    public static readonly TEXTUREFORMAT_COMPRESSED_RGBA_S3TC_DXT3 = 33778;
+    /** Compressed BC1 */
+    public static readonly TEXTUREFORMAT_COMPRESSED_RGBA_S3TC_DXT1 = 33776; // TODO WEBGPU it should be 33777 but the ktx2decoder returns the wrong value! Correct the ktx2 decoder and update the value here
+
     /** UNSIGNED_BYTE */
     public static readonly TEXTURETYPE_UNSIGNED_BYTE = 0;
     /** UNSIGNED_BYTE (2nd reference) */

+ 116 - 62
src/Engines/webgpuEngine.ts

@@ -1,5 +1,5 @@
 import { Logger } from "../Misc/logger";
-import { Nullable, DataArray, IndicesArray, FloatArray } from "../types";
+import { Nullable, DataArray, IndicesArray, FloatArray, Immutable } from "../types";
 import { Color4 } from "../Maths/math";
 import { Engine } from "../Engines/engine";
 import { InstancingAttributeInfo } from "../Engines/instancingAttributeInfo";
@@ -24,7 +24,6 @@ import { Tools } from "../Misc/tools";
 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';
 import { HardwareTextureWrapper } from '../Materials/Textures/hardwareTextureWrapper';
@@ -128,7 +127,9 @@ export class WebGPUEngine extends Engine {
     private _options: WebGPUEngineOptions;
     private _glslang: any = null;
     private _adapter: GPUAdapter;
+    private _adapterSupportedExtensions: GPUExtensionName[];
     private _device: GPUDevice;
+    private _deviceEnabledExtensions: GPUExtensionName[];
     private _context: GPUCanvasContext;
     private _swapChain: GPUSwapChain;
     private _swapChainTexture: GPUTexture;
@@ -186,6 +187,16 @@ export class WebGPUEngine extends Engine {
         return true;
     }
 
+    /** Gets the supported extensions by the WebGPU adapter */
+    public get supportedExtensions(): Immutable<GPUExtensionName[]> {
+        return this._adapterSupportedExtensions;
+    }
+
+    /** Gets the currently enabled extensions on the WebGPU device */
+    public get enabledExtensions(): Immutable<GPUExtensionName[]> {
+        return this._deviceEnabledExtensions;
+    }
+
     /**
      * Create a new instance of the gpu engine.
      * @param canvas Defines the canvas to use to display the result
@@ -255,9 +266,34 @@ export class WebGPUEngine extends Engine {
             })
             .then((adapter: GPUAdapter | null) => {
                 this._adapter = adapter!;
+                this._adapterSupportedExtensions = this._adapter.extensions.slice(0);
+
+                const deviceDescriptor = this._options.deviceDescriptor;
+
+                if (deviceDescriptor?.extensions) {
+                    const requestedExtensions = deviceDescriptor.extensions;
+                    const validExtensions = [];
+
+                    const iterator = requestedExtensions[Symbol.iterator]();
+                    while (true) {
+                        const { done, value : extension } = iterator.next();
+                        if (done) {
+                            break;
+                        }
+                        if (this._adapterSupportedExtensions.indexOf(extension) >= 0) {
+                            validExtensions.push(extension);
+                        }
+                    }
+
+                    deviceDescriptor.extensions = validExtensions;
+                }
+
                 return this._adapter.requestDevice(this._options.deviceDescriptor);
             })
-            .then((device: GPUDevice | null) => this._device = device!)
+            .then((device: GPUDevice | null) => {
+                this._device = device!;
+                this._deviceEnabledExtensions = this._device.extensions.slice(0);
+            })
             .then(() => {
                 this._bufferManager = new WebGPUBufferManager(this._device);
                 this._textureHelper = new WebGPUTextureHelper(this._device, this._glslang, this._bufferManager);
@@ -318,10 +354,11 @@ export class WebGPUEngine extends Engine {
             maxVertexUniformVectors: 1024,
             standardDerivatives: true,
             astc: null,
+            s3tc: (this._deviceEnabledExtensions.indexOf(WebGPUConstants.ExtensionName.TextureCompressionBC) >= 0 ? true : undefined) as any,
             pvrtc: null,
             etc1: null,
             etc2: null,
-            bptc: null,
+            bptc: this._deviceEnabledExtensions.indexOf(WebGPUConstants.ExtensionName.TextureCompressionBC) >= 0 ? true : undefined,
             maxAnisotropy: 0,  // TODO: Retrieve this smartly. Currently set to D3D11 maximum allowable value.
             uintIndices: false,
             fragmentDepthSupported: false,
@@ -1013,10 +1050,25 @@ export class WebGPUEngine extends Engine {
 
     /** @hidden */
     public _getRGBABufferInternalSizedFormat(type: number, format?: number): number {
-        return format ?? Constants.TEXTUREFORMAT_RGBA;
+        return Constants.TEXTUREFORMAT_RGBA;
     }
 
     private _getWebGPUTextureFormat(type: number, format: number): GPUTextureFormat {
+        switch (format) {
+            case Constants.TEXTUREFORMAT_COMPRESSED_RGBA_BPTC_UNORM:
+                return WebGPUConstants.TextureFormat.BC7RGBAUNORM;
+            case Constants.TEXTUREFORMAT_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT:
+                return WebGPUConstants.TextureFormat.BC6HRGBUFLOAT;
+            case Constants.TEXTUREFORMAT_COMPRESSED_RGB_BPTC_SIGNED_FLOAT:
+                return WebGPUConstants.TextureFormat.BC6HRGBSFLOAT;
+            case Constants.TEXTUREFORMAT_COMPRESSED_RGBA_S3TC_DXT5:
+                return WebGPUConstants.TextureFormat.BC3RGBAUNORM;
+            case Constants.TEXTUREFORMAT_COMPRESSED_RGBA_S3TC_DXT3:
+                return WebGPUConstants.TextureFormat.BC2RGBAUNORM;
+            case Constants.TEXTUREFORMAT_COMPRESSED_RGBA_S3TC_DXT1:
+                return WebGPUConstants.TextureFormat.BC1RGBAUNORM;
+        }
+
         switch (type) {
             case Constants.TEXTURETYPE_BYTE:
                 switch (format) {
@@ -1213,16 +1265,17 @@ export class WebGPUEngine extends Engine {
                     texture.baseHeight = imageBitmap.height;
                     texture.width = imageBitmap.width;
                     texture.height = imageBitmap.height;
-                    if (format) {
-                        texture.format = format;
-                    }
+                    texture.format = format ?? -1;
 
-                    // 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);
-                    const [gpuTexture] = this._textureHelper.createTexture(imageBitmap, !noMipmap, invertY, false, this._getWebGPUTextureFormat(texture.type, texture.format), 1, this._uploadEncoder);
+                    if (!texture._hardwareTexture?.underlyingResource) { // the texture could have been created before reaching this point so don't recreate it if already existing
+                        const gpuTextureWrapper = this._createGPUTextureForInternalTexture(texture, imageBitmap.width, imageBitmap.height);
 
-                    texture._hardwareTexture!.set(gpuTexture);
-                    (texture._hardwareTexture as WebGPUHardwareTexture).createView();
+                        if (this._textureHelper.isImageBitmap(imageBitmap)) {
+                            this._textureHelper.updateTexture(imageBitmap, gpuTextureWrapper.underlyingResource!, imageBitmap.width, imageBitmap.height, gpuTextureWrapper.format, 0, 0, invertY, false, 0, 0, this._uploadEncoder);
+                        }
+                    } else if (!noMipmap && !isCompressed) {
+                        this._generateMipmaps(texture, texture._hardwareTexture!.underlyingResource);
+                    }
 
                     if (scene) {
                         scene._removePendingData(texture);
@@ -1260,21 +1313,11 @@ export class WebGPUEngine extends Engine {
 
                 // TODO WEBGPU. Cube Texture Sampling Mode.
                 texture.samplingMode = noMipmap ? Constants.TEXTURE_BILINEAR_SAMPLINGMODE : Constants.TEXTURE_TRILINEAR_SAMPLINGMODE;
+                texture.format = format ?? -1;
 
-                // TODO WEBGPU. handle format if <> 0
-                //const internalFormat = format ? this._getInternalFormat(format) : this._gl.RGBA;
-                const [gpuTexture] = this._textureHelper.createCubeTexture(imageBitmaps, !noMipmap, false, false, this._getWebGPUTextureFormat(texture.type, texture.format), 1, this._uploadEncoder);
-
-                texture._hardwareTexture!.set(gpuTexture);
-                (texture._hardwareTexture as WebGPUHardwareTexture).createView({
-                    dimension: WebGPUConstants.TextureViewDimension.Cube
-                });
+                const gpuTextureWrapper = this._createGPUTextureForInternalTexture(texture, width, height);
 
-                texture.width = width;
-                texture.height = height;
-                if (format) {
-                    texture.format = format;
-                }
+                this._textureHelper.updateCubeTextures(imageBitmaps, gpuTextureWrapper.underlyingResource!, width, height, gpuTextureWrapper.format, false, false, 0, 0, this._uploadEncoder);
 
                 texture.isReady = true;
 
@@ -1377,7 +1420,7 @@ export class WebGPUEngine extends Engine {
         this._bindTextureDirectly(0, texture);
     }
 
-    private _createGPUTextureForInternalTexture(texture: InternalTexture, width?: number, height?: number): GPUTexture {
+    private _createGPUTextureForInternalTexture(texture: InternalTexture, width?: number, height?: number): WebGPUHardwareTexture {
         if (!texture._hardwareTexture) {
             texture._hardwareTexture = this._createHardwareTexture();
         }
@@ -1389,28 +1432,32 @@ export class WebGPUEngine extends Engine {
             height = texture.height;
         }
 
+        const gpuTextureWrapper = texture._hardwareTexture as WebGPUHardwareTexture;
+
+        gpuTextureWrapper.format = this._getWebGPUTextureFormat(texture.type, texture.format);
+
         if (texture.isCube) {
-            const [gpuTexture] = this._textureHelper.createCubeTexture({ width, height }, texture.generateMipMaps, texture.invertY, false, this._getWebGPUTextureFormat(texture.type, texture.format), 1, this._uploadEncoder);
+            const gpuTexture = this._textureHelper.createCubeTexture({ width, height }, texture.generateMipMaps, texture.generateMipMaps, texture.invertY, false, gpuTextureWrapper.format, texture.samples || 1, this._uploadEncoder);
 
-            texture._hardwareTexture!.set(gpuTexture);
-            (texture._hardwareTexture as WebGPUHardwareTexture).createView({
+            gpuTextureWrapper.set(gpuTexture);
+            gpuTextureWrapper.createView({
                 dimension: WebGPUConstants.TextureViewDimension.Cube,
-                mipLevelCount: Math.round(Scalar.Log2(Math.max(width!, height!))) + 1,
+                mipLevelCount: texture.generateMipMaps ? WebGPUTextureHelper.computeNumMipmapLevels(width!, height!) : 1,
                 baseArrayLayer: 0,
                 baseMipLevel: 0,
                 aspect: WebGPUConstants.TextureAspect.All
             });
         } else {
-            const [gpuTexture] = this._textureHelper.createTexture({ width, height }, texture.generateMipMaps, texture.invertY, false, this._getWebGPUTextureFormat(texture.type, texture.format), 1, this._uploadEncoder);
+            const gpuTexture = this._textureHelper.createTexture({ width, height }, texture.generateMipMaps, texture.generateMipMaps, texture.invertY, false, gpuTextureWrapper.format, texture.samples || 1, this._uploadEncoder);
 
-            texture._hardwareTexture!.set(gpuTexture);
-            (texture._hardwareTexture as WebGPUHardwareTexture).createView();
+            gpuTextureWrapper.set(gpuTexture);
+            gpuTextureWrapper.createView();
         }
 
         texture.width = texture.baseWidth = width;
         texture.height = texture.baseHeight = height;
 
-        return texture._hardwareTexture!.underlyingResource;
+        return gpuTextureWrapper;
     }
 
     private _generateMipmaps(texture: InternalTexture, gpuTexture: GPUTexture) {
@@ -1430,10 +1477,10 @@ export class WebGPUEngine extends Engine {
 
         const width = canvas.width, height = canvas.height;
 
-        let gpuTexture = texture._hardwareTexture?.underlyingResource;
+        let gpuTextureWrapper = texture._hardwareTexture as WebGPUHardwareTexture;
 
-        if (!gpuTexture) {
-            gpuTexture = this._createGPUTextureForInternalTexture(texture, width, height);
+        if (!texture._hardwareTexture?.underlyingResource) {
+            gpuTextureWrapper = this._createGPUTextureForInternalTexture(texture, width, height);
         }
 
         // TODO WEBGPU remove debug code
@@ -1463,15 +1510,15 @@ export class WebGPUEngine extends Engine {
                 });*/
             //}
 
-            this._textureHelper.updateTextureTest(canvas as HTMLCanvasElement, gpuTexture, width, height, 0, 0, invertY, premulAlpha, swap, 0/*, this._uploadEncoder*/);
+            this._textureHelper.updateTextureTest(canvas as HTMLCanvasElement, gpuTextureWrapper.underlyingResource!, width, height, 0, 0, invertY, premulAlpha, swap, 0/*, this._uploadEncoder*/);
             texture.isReady = true;
             return;
         }
 
         createImageBitmap(canvas).then((bitmap) => {
-            this._textureHelper.updateTexture(bitmap, gpuTexture, width, height, 0, 0, invertY, premulAlpha, 0, 0, this._uploadEncoder);
+            this._textureHelper.updateTexture(bitmap, gpuTextureWrapper.underlyingResource!, width, height, gpuTextureWrapper.format, 0, 0, invertY, premulAlpha, 0, 0, this._uploadEncoder);
             if (texture.generateMipMaps) {
-                this._generateMipmaps(texture, gpuTexture);
+                this._generateMipmaps(texture, gpuTextureWrapper.underlyingResource!);
             }
 
             texture.isReady = true;
@@ -1479,17 +1526,17 @@ export class WebGPUEngine extends Engine {
     }
 
     public updateTextureData(texture: InternalTexture, imageData: ArrayBufferView, xOffset: number, yOffset: number, width: number, height: number, faceIndex: number = 0, lod: number = 0): void {
-        let gpuTexture = texture._hardwareTexture?.underlyingResource;
+        let gpuTextureWrapper = texture._hardwareTexture as WebGPUHardwareTexture;
 
-        if (!gpuTexture) {
-            gpuTexture = this._createGPUTextureForInternalTexture(texture);
+        if (!texture._hardwareTexture?.underlyingResource) {
+            gpuTextureWrapper = this._createGPUTextureForInternalTexture(texture);
         }
 
         const imgData = new ImageData(new Uint8ClampedArray(imageData.buffer), width, height);
 
-        // TODO WEBGPU directly pass the Uint8ClampedArray
+        // TODO WEBGPU directly pass the Uint8ClampedArray? invertY needs to be handled by updateTexture...
         createImageBitmap(imgData).then((bitmap) => {
-            this._textureHelper.updateTexture(bitmap, gpuTexture, width, height, faceIndex, lod, texture.invertY, false, xOffset, yOffset, this._uploadEncoder);
+            this._textureHelper.updateTexture(bitmap, gpuTextureWrapper?.underlyingResource!, width, height, gpuTextureWrapper.format, faceIndex, lod, texture.invertY, false, xOffset, yOffset, this._uploadEncoder);
         });
     }
 
@@ -1502,16 +1549,16 @@ export class WebGPUEngine extends Engine {
             this._videoTextureSupported = true;
         }
 
-        let gpuTexture = texture._hardwareTexture?.underlyingResource;
+        let gpuTextureWrapper = texture._hardwareTexture as WebGPUHardwareTexture;
 
-        if (!gpuTexture) {
-            gpuTexture = this._createGPUTextureForInternalTexture(texture);
+        if (!texture._hardwareTexture?.underlyingResource) {
+            gpuTextureWrapper = this._createGPUTextureForInternalTexture(texture);
         }
 
         createImageBitmap(video).then((bitmap) => {
-            this._textureHelper.updateTexture(bitmap, gpuTexture, texture.width, texture.height, 0, 0, !invertY, false, 0, 0, this._uploadEncoder);
+            this._textureHelper.updateTexture(bitmap, gpuTextureWrapper.underlyingResource!, texture.width, texture.height, gpuTextureWrapper.format, 0, 0, !invertY, false, 0, 0, this._uploadEncoder);
             if (texture.generateMipMaps) {
-                this._generateMipmaps(texture, gpuTexture);
+                this._generateMipmaps(texture, gpuTextureWrapper.underlyingResource!);
             }
 
             texture.isReady = true;
@@ -1524,7 +1571,14 @@ export class WebGPUEngine extends Engine {
 
     /** @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");
+        let gpuTextureWrapper = texture._hardwareTexture as WebGPUHardwareTexture;
+
+        if (!texture._hardwareTexture?.underlyingResource) {
+            texture.format = internalFormat;
+            gpuTextureWrapper = this._createGPUTextureForInternalTexture(texture, width, height);
+        }
+
+        this._textureHelper.updateTexture(new Uint8Array(data.buffer), gpuTextureWrapper.underlyingResource!, width, height, gpuTextureWrapper.format, faceIndex, lod, texture.invertY, false, 0, 0, this._uploadEncoder);
     }
 
     /** @hidden */
@@ -1536,17 +1590,17 @@ export class WebGPUEngine extends Engine {
         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));
 
-        let gpuTexture = texture._hardwareTexture?.underlyingResource;
+        let gpuTextureWrapper = texture._hardwareTexture as WebGPUHardwareTexture;
 
-        if (!gpuTexture) {
-            gpuTexture = this._createGPUTextureForInternalTexture(texture, width, height);
+        if (!texture._hardwareTexture?.underlyingResource) {
+            gpuTextureWrapper = this._createGPUTextureForInternalTexture(texture, width, height);
         }
 
-        const imgData = new ImageData(new Uint8ClampedArray(imageData.buffer, 0, width * height * 4), width, height);
+        const imgData = new ImageData(new Uint8ClampedArray(imageData.buffer, 0, imageData.byteLength), width, height);
 
-        // TODO WEBGPU don't convert to image bitmap and directly pass the Uint8ClampedArray
+        // TODO WEBGPU don't convert to image bitmap and directly pass the Uint8ClampedArray? updateTexture needs to handle invertY...
         createImageBitmap(imgData).then((bitmap) => {
-            this._textureHelper.updateTexture(bitmap, gpuTexture, width, height, faceIndex, lod, texture.invertY, false, 0, 0, this._uploadEncoder);
+            this._textureHelper.updateTexture(bitmap, gpuTextureWrapper.underlyingResource!, width, height, gpuTextureWrapper.format, faceIndex, lod, texture.invertY, false, 0, 0, this._uploadEncoder);
         });
     }
 
@@ -1557,10 +1611,10 @@ export class WebGPUEngine extends Engine {
 
     /** @hidden */
     public _uploadImageToTexture(texture: InternalTexture, image: HTMLImageElement | ImageBitmap, faceIndex: number = 0, lod: number = 0) {
-        let gpuTexture = texture._hardwareTexture?.underlyingResource;
+        let gpuTextureWrapper = texture._hardwareTexture as WebGPUHardwareTexture;
 
-        if (!gpuTexture) {
-            gpuTexture = this._createGPUTextureForInternalTexture(texture);
+        if (!texture._hardwareTexture?.underlyingResource) {
+            gpuTextureWrapper = this._createGPUTextureForInternalTexture(texture);
         }
 
         const bitmap = image as ImageBitmap; // in WebGPU we will always get an ImageBitmap, not an HTMLImageElement
@@ -1568,7 +1622,7 @@ export class WebGPUEngine extends Engine {
         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, false, 0, 0, this._uploadEncoder);
+        this._textureHelper.updateTexture(bitmap, gpuTextureWrapper.underlyingResource!, width, height, gpuTextureWrapper.format, faceIndex, lod, texture.invertY, false, 0, 0, this._uploadEncoder);
     }
 
     public readPixels(x: number, y: number, width: number, height: number, hasAlpha = true): Promise<Uint8Array> | Uint8Array {

+ 4 - 4
src/Misc/dds.ts

@@ -450,7 +450,7 @@ export class DDSTools {
         if (info.sphericalPolynomial) {
             sphericalPolynomialFaces = new Array<ArrayBufferView>();
         }
-        var ext = engine.getCaps().s3tc;
+        var ext = !!engine.getCaps().s3tc;
 
         var header = new Int32Array(data.buffer, data.byteOffset, headerLengthInt);
         var fourCC: number, width: number, height: number, dataLength: number = 0, dataOffset: number;
@@ -483,15 +483,15 @@ export class DDSTools {
             switch (fourCC) {
                 case FOURCC_DXT1:
                     blockBytes = 8;
-                    internalCompressedFormat = (<WEBGL_compressed_texture_s3tc>ext).COMPRESSED_RGBA_S3TC_DXT1_EXT;
+                    internalCompressedFormat = Constants.TEXTUREFORMAT_COMPRESSED_RGBA_S3TC_DXT1;
                     break;
                 case FOURCC_DXT3:
                     blockBytes = 16;
-                    internalCompressedFormat = (<WEBGL_compressed_texture_s3tc>ext).COMPRESSED_RGBA_S3TC_DXT3_EXT;
+                    internalCompressedFormat = Constants.TEXTUREFORMAT_COMPRESSED_RGBA_S3TC_DXT3;
                     break;
                 case FOURCC_DXT5:
                     blockBytes = 16;
-                    internalCompressedFormat = (<WEBGL_compressed_texture_s3tc>ext).COMPRESSED_RGBA_S3TC_DXT5_EXT;
+                    internalCompressedFormat = Constants.TEXTUREFORMAT_COMPRESSED_RGBA_S3TC_DXT5;
                     break;
                 case FOURCC_D3DFMT_R16G16B16A16F:
                     computeFormats = true;