Переглянути джерело

First try at worker thread but fails miserably...

Popov72 5 роки тому
батько
коміт
556f9d3e95

+ 21 - 2
src/Misc/KTX2/KTX2FileReader.ts

@@ -65,7 +65,8 @@ interface IKTX2_Sample {
     sampleUpper: number;
 }
 
-interface IKTX2_DFD {
+/** @hidden */
+export interface IKTX2_DFD {
     vendorId: number;
     descriptorType: number;
     versionNumber: number;
@@ -144,6 +145,10 @@ export class KTX2FileReader {
         return this._levels;
     }
 
+    public get dfdBlock(): IKTX2_DFD {
+        return this._dfdBlock;
+    }
+
     public get supercompressionGlobalData(): IKTX2_SupercompressionGlobalData {
         return this._supercompressionGlobalData;
     }
@@ -184,7 +189,7 @@ export class KTX2FileReader {
         }
 
         if (header.faceCount > 1) {
-            throw new Error(`Failed to parse KTX2 file - Cube textures are not currently supported.`);
+            //throw new Error(`Failed to parse KTX2 file - Cube textures are not currently supported.`);
         }
 
         console.log(header);
@@ -348,6 +353,20 @@ export class KTX2FileReader {
         return false;
     }
 
+    public getHasAlpha(): boolean {
+        const tformat = this.textureFormat;
+
+        switch (tformat) {
+            case sourceTextureFormat.ETC1S:
+                return this._dfdBlock.numSamples === 2 && (this._dfdBlock.samples[0].channelType === dfdChannel_ETC1S.AAA || this._dfdBlock.samples[1].channelType === dfdChannel_ETC1S.AAA);
+
+            case sourceTextureFormat.UASTC4x4:
+                return this._dfdBlock.samples[0].channelType === dfdChannel_UASTC.RGBA;
+        }
+
+        return false;
+    }
+
     public static IsValid(data: ArrayBufferView): boolean {
         if (data.byteLength >= 12) {
             // '«', 'K', 'T', 'X', ' ', '2', '0', '»', '\r', '\n', '\x1A', '\n'

+ 180 - 71
src/Misc/KTX2/khronosTextureContainer2.ts

@@ -16,53 +16,187 @@ import { MSCTranscoder } from "./mscTranscoder";
 
 const COMPRESSED_RGBA_BPTC_UNORM_EXT = 36492;
 
+interface IMipmap {
+    data: Nullable<Uint8Array>;
+    width: number;
+    height: number;
+    transcodedFormat: number;
+}
+
 /**
  * Class for loading KTX2 files
  * @hidden
  */
 export class KhronosTextureContainer2 {
 
-    private static _Transcoders: Array<typeof Transcoder> = [];
+    /** @hidden */
+    public static _Transcoders: Array<typeof Transcoder> = [];
 
     public static registerTranscoder(transcoder: typeof Transcoder) {
         KhronosTextureContainer2._Transcoders.push(transcoder);
     }
 
     private _engine: ThinEngine;
-    private _wasmMemoryManager: WASMMemoryManager;
-    private _transcoderInstances: { [key: string]: Transcoder };
+
+    private static _WorkerPromise: Nullable<Promise<Worker>> = null;
+    private static _Worker: Nullable<Worker> = null;
+    private static _actionId = 0;
+    private static _CreateWorkerAsync() {
+        if (!this._WorkerPromise) {
+            this._WorkerPromise = new Promise((res) => {
+                if (this._Worker) {
+                    res(this._Worker);
+                } else {
+                    const workerBlobUrl = URL.createObjectURL(new Blob([`(${workerFunc})()`], { type: "application/javascript" }));
+                    this._Worker = new Worker(workerBlobUrl);
+                    URL.revokeObjectURL(workerBlobUrl);
+
+                    const initHandler = (msg: any) => {
+                        if (msg.data.action === "init") {
+                            this._Worker!.removeEventListener("message", initHandler);
+                            res(this._Worker!);
+                        }
+                    };
+                    this._Worker.addEventListener("message", initHandler);
+                    this._Worker.postMessage({ action: "init" });
+                }
+            });
+        }
+        return this._WorkerPromise;
+    }
 
     public constructor(engine: ThinEngine) {
         this._engine = engine;
-        this._transcoderInstances = {};
+        //this._transcoderInstances = {};
+    }
+
+    public uploadAsync(data: ArrayBufferView, internalTexture: InternalTexture): Promise<void> {
+        const kfr = new KTX2FileReader(data);
+
+        return new Promise((res, rej) => {
+            KhronosTextureContainer2._CreateWorkerAsync().then(() => {
+                const actionId = KhronosTextureContainer2._actionId++;
+                const messageHandler = (msg: any) => {
+                    if (msg.data.action === "mipmapsCreated" && msg.data.id === actionId) {
+                        KhronosTextureContainer2._Worker!.removeEventListener("message", messageHandler);
+                        if (!msg.data.success) {
+                            rej();
+                        }else {
+                            this._createTexture(msg.data.mipmaps, internalTexture);
+                            res();
+                        }
+                    }
+                };
+                KhronosTextureContainer2._Worker!.addEventListener("message", messageHandler);
+
+                KhronosTextureContainer2._Worker!.postMessage({
+                    action: "createMipmaps",
+                    id: actionId,
+                    kfr: {
+                        header: kfr.header,
+                        textureFormat: kfr.textureFormat,
+                        dfdBlock: kfr.dfdBlock,
+                        levels: kfr.levels,
+                        bufferData: kfr.data.buffer,
+                        supercompressionGlobalData: kfr.supercompressionGlobalData,
+                    },
+                }, [kfr.data.buffer]);
+            });
+        });
+
+        //return this._createMipmaps(kfr, internalTexture);
+    }
+
+    protected _createTexture(mipmaps: Array<IMipmap>, internalTexture: InternalTexture) {
+        for (let t = 0; t < mipmaps.length; ++t) {
+            let mipmap = mipmaps[t];
+
+            if (!mipmap || !mipmap.data) {
+                throw new Error("KTX2 container - could not transcode one of the image");
+            }
+
+            console.log(`mipmap #${t} byte length=`, mipmap.data.byteLength);
+
+            internalTexture.width = internalTexture.baseWidth = mipmap.width;
+            internalTexture.height = internalTexture.baseHeight = mipmap.height;
+            internalTexture.generateMipMaps = false;
+            internalTexture.invertY = false;
+
+            this._engine._bindTextureDirectly(this._engine._gl.TEXTURE_2D, internalTexture);
+            this._engine._uploadCompressedDataToTextureDirectly(internalTexture, mipmap.transcodedFormat, mipmap.width, mipmap.height, mipmap.data, 0, 0);
+
+            internalTexture.isReady = true;
+            break;
+        }
     }
 
-    private _findTranscoder(src: sourceTextureFormat, dst: transcodeTarget): Nullable<Transcoder> {
+    /**
+     * Checks if the given data starts with a KTX2 file identifier.
+     * @param data the data to check
+     * @returns true if the data is a KTX2 file or false otherwise
+     */
+    public static IsValid(data: ArrayBufferView): boolean {
+        return KTX2FileReader.IsValid(data);
+    }
+}
+
+// Put in the order you want the transcoders to be used in priority
+KhronosTextureContainer2.registerTranscoder(LiteTranscoder_UASTC_ASTC);
+KhronosTextureContainer2.registerTranscoder(LiteTranscoder_UASTC_BC7);
+KhronosTextureContainer2.registerTranscoder(MSCTranscoder);
+
+/**
+ *
+ * Worker thread
+ *
+ */
+
+declare function postMessage(message: any, transfer?: any[]): void;
+function workerFunc(): void {
+
+    let _wasmMemoryManager: WASMMemoryManager;
+    let _transcoderInstances: { [key: string]: Transcoder } = {};
+
+    onmessage = (event) => {
+        if (event.data.action === "init") {
+            postMessage({action: "init"});
+        } else if (event.data.action === "createMipmaps") {
+            _createMipmaps(event.data.kfr).then((mipmaps) => {
+                //if (!success) {
+                //    postMessage({action: "transcode", success: success, id: event.data.id});
+                //} else {
+                    postMessage({ action: "mipmapsCreated", success: true/*success*/, id: event.data.id, mipmaps: mipmaps.mipmaps }, mipmaps.mipmapsData);
+                //}
+            });
+        }
+    };
+
+    const _findTranscoder = (src: sourceTextureFormat, dst: transcodeTarget): Nullable<Transcoder> => {
         let transcoder: Nullable<Transcoder> = null;
 
         for (let i = 0; i < KhronosTextureContainer2._Transcoders.length; ++i) {
             if (KhronosTextureContainer2._Transcoders[i].CanTranscode(src, dst)) {
                 const key = sourceTextureFormat[src] + "_" + transcodeTarget[dst];
-                transcoder = this._transcoderInstances[key];
+                transcoder = _transcoderInstances[key];
                 if (!transcoder) {
                     transcoder = new KhronosTextureContainer2._Transcoders[i]();
                     transcoder!.initialize();
                     if (transcoder!.needMemoryManager()) {
-                        if (!this._wasmMemoryManager) {
-                            this._wasmMemoryManager = new WASMMemoryManager();
+                        if (!_wasmMemoryManager) {
+                            _wasmMemoryManager = new WASMMemoryManager();
                         }
-                        transcoder!.setMemoryManager(this._wasmMemoryManager);
+                        transcoder!.setMemoryManager(_wasmMemoryManager);
                     }
-                    this._transcoderInstances[key] = transcoder;
+                    _transcoderInstances[key] = transcoder;
                 }
                 break;
             }
         }
 
         return transcoder;
-    }
+    };
 
-    private async _createMipmaps(kfr: KTX2FileReader, internalTexture: InternalTexture) {
+    const _createMipmaps = (kfr: KTX2FileReader): Promise<{ mipmaps: Array<IMipmap>, mipmapsData: Array<ArrayBuffer> }> => {
         /*await this.zstd.init();*/
 
         //var mipmaps = [];
@@ -70,16 +204,18 @@ export class KhronosTextureContainer2 {
         const height = kfr.header.pixelHeight;
         const srcTexFormat = kfr.textureFormat;
 
-        let targetFormat = transcodeTarget.BC7_M5_RGBA;
-        let transcodedFormat = COMPRESSED_RGBA_BPTC_UNORM_EXT;
+        let targetFormat = 1/*transcodeTarget.BC7_M5_RGBA*/;
+        let transcodedFormat = 36492/*COMPRESSED_RGBA_BPTC_UNORM_EXT*/;
 
-        const transcoder = this._findTranscoder(srcTexFormat, targetFormat);
+        const transcoder = _findTranscoder(srcTexFormat, targetFormat);
 
         if (transcoder === null) {
             throw new Error(`KTX2 container - no transcoder found to transcode source texture format "${sourceTextureFormat[srcTexFormat]}" to format "${transcodeTarget[targetFormat]}"`);
         }
 
+        const mipmaps: Array<IMipmap> = [];
         const texturePromises: Array<Promise<Nullable<Uint8Array>>> = [];
+        const mipmapsData: Array<ArrayBuffer> = [];
 
         let firstImageDescIndex = 0;
 
@@ -92,85 +228,58 @@ export class KhronosTextureContainer2 {
             const levelHeight = height / Math.pow(2, level);
 
             const numImagesInLevel = kfr.header.faceCount; // note that cubemap are not supported yet (see KTX2FileReader), so faceCount == 1
-            const levelByteLength = kfr.levels[level].byteLength;
+            const levelImageByteLength = ((levelWidth + 3) >> 2) * ((levelHeight + 3) >> 2) * kfr.dfdBlock.bytesPlane[0];
+
             const levelUncompressedByteLength = kfr.levels[level].uncompressedByteLength;
 
             let levelDataBuffer = kfr.data.buffer;
             let levelDataOffset = kfr.levels[level].byteOffset;
             let imageOffsetInLevel = 0;
 
-            if (kfr.header.supercompressionScheme === supercompressionScheme.ZStandard) {
+            if (kfr.header.supercompressionScheme === 2/*supercompressionScheme.ZStandard*/) {
                 //levelDataBuffer = this.zstd.decode(new Uint8Array(levelDataBuffer, levelDataOffset, levelByteLength), levelUncompressedByteLength);
                 levelDataOffset = 0;
             }
 
-            //const levelImageByteLength = imageInfo.numBlocksX * imageInfo.numBlocksY * DFD bytesPlane0;
-
             for (let imageIndex = 0; imageIndex < numImagesInLevel; imageIndex ++) {
                 let encodedData: Uint8Array;
                 let imageDesc: Nullable<IKTX2_ImageDesc> = null;
 
-                if (kfr.header.supercompressionScheme === supercompressionScheme.BasisLZ) {
+                if (kfr.header.supercompressionScheme === 1/*supercompressionScheme.BasisLZ*/) {
                     imageDesc = kfr.supercompressionGlobalData.imageDescs![firstImageDescIndex + imageIndex];
 
                     encodedData = new Uint8Array(levelDataBuffer, levelDataOffset + imageDesc.rgbSliceByteOffset, imageDesc.rgbSliceByteLength + imageDesc.alphaSliceByteLength);
                 } else {
-                    encodedData = new Uint8Array(levelDataBuffer, levelDataOffset + imageOffsetInLevel, levelByteLength);
+                    encodedData = new Uint8Array(levelDataBuffer, levelDataOffset + imageOffsetInLevel, levelImageByteLength);
 
-                    imageOffsetInLevel += levelByteLength;
+                    imageOffsetInLevel += levelImageByteLength;
                 }
 
-                texturePromises.push(transcoder.transcode(srcTexFormat, targetFormat, level, levelWidth, levelHeight, levelUncompressedByteLength, kfr, imageDesc, encodedData));
-            }
-        }
-
-        Promise.all(texturePromises).then((textures) => {
-            for (let t = 0; t < textures.length; ++t) {
-                let textureData = textures[t];
-
-                if (textureData === null) {
-                    throw new Error("KTX2 container - could not transcode one of the image");
-                }
-
-                console.log("texture byte length=", textureData.byteLength);
-
-                //textureData = new Uint8Array(textureData.buffer, 0, textureData.byteLength / 6);
-
-                //console.log("texture byte length=", textureData.byteLength);
-
-                internalTexture.width = internalTexture.baseWidth = width;
-                internalTexture.height = internalTexture.baseHeight = height;
-                internalTexture.generateMipMaps = false;
-                internalTexture.invertY = false;
+                const mipmap: IMipmap = {
+                    data: null,
+                    width: levelWidth,
+                    height: levelHeight,
+                    transcodedFormat: transcodedFormat
+                };
+
+                const transcodedData = transcoder.transcode(srcTexFormat, targetFormat, level, levelWidth, levelHeight, levelUncompressedByteLength, kfr, imageDesc, encodedData).
+                        then((data) => {
+                            mipmap.data = data;
+                            if (data) {
+                                mipmapsData.push(data.buffer);
+                            }
+                            return data;
+                        }
+                      );
 
-                this._engine._bindTextureDirectly(this._engine._gl.TEXTURE_2D, internalTexture);
-                this._engine._uploadCompressedDataToTextureDirectly(internalTexture, transcodedFormat, width, height, textureData, 0, 0);
+                mipmaps.push(mipmap);
 
-                internalTexture.isReady = true;
-                break;
+                texturePromises.push(transcodedData);
             }
-        });
-
-        //this.mipmaps = mipmaps;
-    }
-
-    public uploadAsync(data: ArrayBufferView, internalTexture: InternalTexture): Promise<void> {
-        const kfr = new KTX2FileReader(data);
-
-        return this._createMipmaps(kfr, internalTexture);
-    }
+        }
 
-    /**
-     * Checks if the given data starts with a KTX2 file identifier.
-     * @param data the data to check
-     * @returns true if the data is a KTX2 file or false otherwise
-     */
-    public static IsValid(data: ArrayBufferView): boolean {
-        return KTX2FileReader.IsValid(data);
-    }
+        return Promise.all(texturePromises).then(() => {
+            return { mipmaps, mipmapsData };
+        });
+    };
 }
-
-// Put in the order you want the transcoders to be used in priority
-KhronosTextureContainer2.registerTranscoder(LiteTranscoder_UASTC_ASTC);
-KhronosTextureContainer2.registerTranscoder(LiteTranscoder_UASTC_BC7);
-KhronosTextureContainer2.registerTranscoder(MSCTranscoder);

+ 3 - 0
src/Misc/KTX2/mscTranscoder.ts

@@ -4,6 +4,8 @@ import { KTX2FileReader, IKTX2_ImageDesc } from './KTX2FileReader';
 
 declare var MSC_TRANSCODER: any;
 
+declare function importScripts(...urls: string[]): void;
+
 /**
  * @hidden
  */
@@ -18,6 +20,7 @@ export class MSCTranscoder extends Transcoder {
         }
 
         this._mscBasisTranscoderPromise = new Promise((resolve) => {
+            importScripts();
             MSC_TRANSCODER().then((basisModule: any) => {
                 basisModule.initTranscoders();
                 this._mscBasisModule = basisModule;