浏览代码

.basis file support

Trevor Baron 6 年之前
父节点
当前提交
98facb9082

+ 1 - 0
Playground/index-local.html

@@ -22,6 +22,7 @@
     <script src="../dist/preview%20release/Oimo.js"></script>
     <script src="../dist/preview%20release/gltf_validator.js"></script>
     <script src="../dist/preview%20release/earcut.min.js"></script>
+    <script src="../dist/preview%20release/basisTranscoder/basis_transcoder.js"></script>
     <!-- Monaco -->
 
     <!-- Babylon.js -->

二进制
Playground/textures/plane.basis


文件差异内容过多而无法显示
+ 1 - 0
dist/preview release/basisTranscoder/basis_transcoder.js


二进制
dist/preview release/basisTranscoder/basis_transcoder.wasm


+ 1 - 0
dist/preview release/what's new.md

@@ -12,6 +12,7 @@
 - Individual gizmos can now be enabled/disabled ([Balupg](https://github.com/balupg))
 - Unify preparation of instance attributes. Added `MaterialHelper.PushAttributesForInstances` ([MarkusBillharz](https://github.com/MarkusBillharz))
 - Added support for PBR [irradiance map](https://doc.babylonjs.com/how_to/physically_based_rendering_master#irradiance-map)
+- .basis file support ([TrevorDev](https://github.com/TrevorDev))
 
 ### Engine
 - Added preprocessors for shaders to improve how shaders are compiled for WebGL1/2 or WebGPU ([Deltakosh](https://github.com/deltakosh/))

+ 100 - 0
src/Materials/Textures/Loaders/basisTextureLoader.ts

@@ -0,0 +1,100 @@
+import { Nullable } from "../../../types";
+import { Engine } from "../../../Engines/engine";
+import { InternalTexture } from "../../../Materials/Textures/internalTexture";
+import { IInternalTextureLoader } from "../../../Materials/Textures/internalTextureLoader";
+import { _TimeToken } from "../../../Instrumentation/timeToken";
+import { _DepthCullingState, _StencilState, _AlphaState } from "../../../States/index";
+import { BasisTools } from "../../../Misc/basis";
+
+export class _BasisTextureLoader implements IInternalTextureLoader {
+    /**
+     * Defines wether the loader supports cascade loading the different faces.
+     */
+    public readonly supportCascades = false;
+
+    /**
+     * This returns if the loader support the current file information.
+     * @param extension defines the file extension of the file being loaded
+     * @param textureFormatInUse defines the current compressed format in use iun the engine
+     * @param fallback defines the fallback internal texture if any
+     * @param isBase64 defines whether the texture is encoded as a base64
+     * @param isBuffer defines whether the texture data are stored as a buffer
+     * @returns true if the loader can load the specified file
+     */
+    public canLoad(extension: string, textureFormatInUse: Nullable<string>, fallback: Nullable<InternalTexture>, isBase64: boolean, isBuffer: boolean): boolean {
+        console.log("basis try");
+        return extension.indexOf(".basis") === 0;
+    }
+
+    /**
+     * Transform the url before loading if required.
+     * @param rootUrl the url of the texture
+     * @param textureFormatInUse defines the current compressed format in use iun the engine
+     * @returns the transformed texture
+     */
+    public transformUrl(rootUrl: string, textureFormatInUse: Nullable<string>): string {
+        return rootUrl;
+    }
+
+    /**
+     * Gets the fallback url in case the load fail. This can return null to allow the default fallback mecanism to work
+     * @param rootUrl the url of the texture
+     * @param textureFormatInUse defines the current compressed format in use iun the engine
+     * @returns the fallback texture
+     */
+    public getFallbackTextureUrl(rootUrl: string, textureFormatInUse: Nullable<string>): Nullable<string> {
+        return null;
+    }
+
+    /**
+     * Uploads the cube texture data to the WebGl Texture. It has alreday been bound.
+     * @param data contains the texture data
+     * @param texture defines the BabylonJS internal texture
+     * @param createPolynomials will be true if polynomials have been requested
+     * @param onLoad defines the callback to trigger once the texture is ready
+     * @param onError defines the callback to trigger in case of error
+     */
+    public loadCubeData(data: string | ArrayBuffer | (string | ArrayBuffer)[], texture: InternalTexture, createPolynomials: boolean, onLoad: Nullable<(data?: any) => void>, onError: Nullable<(message?: string, exception?: any) => void>): void {
+        throw ".basis not supported in Cube.";
+    }
+
+    /**
+     * Uploads the 2D texture data to the WebGl Texture. It has alreday been bound once in the callback.
+     * @param data contains the texture data
+     * @param texture defines the BabylonJS internal texture
+     * @param callback defines the method to call once ready to upload
+     */
+    public loadData(data: ArrayBuffer, texture: InternalTexture,
+        callback: (width: number, height: number, loadMipmap: boolean, isCompressed: boolean, done: () => void) => void): void {
+        // Verify Basis Module is loaded and detect file info and format
+        BasisTools.VerifyBasisModule();
+        var loadedFile = BasisTools.LoadBasisFile(data);
+        var fileInfo = BasisTools.GetFileInfo(loadedFile);
+        var format = BasisTools.GetSupportedTranscodeFormat(texture.getEngine(), fileInfo);
+
+        // TODO this should be done in web worker
+        var transcodeResult = BasisTools.TranscodeFile(format, fileInfo, loadedFile);
+
+        // Upload data to texture
+        callback(fileInfo.width, fileInfo.height, false, true, () => {
+            if (transcodeResult.fallbackToRgb565) {
+                // Load rgb565Data to texture if conversion is needed
+                texture.getEngine()._unpackFlipY(texture.invertY);
+                var gl = texture.getEngine()._gl;
+                gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, fileInfo.alignedWidth, fileInfo.alignedHeight, 0, gl.RGB, gl.UNSIGNED_SHORT_5_6_5, transcodeResult.pixels);
+
+                // TODO this is not working because computed power of 2 image size is larger than rgb565Data causing an error, working around this with below code
+                // texture.type = Engine.TEXTURETYPE_UNSIGNED_SHORT_5_6_5;
+                // texture.format = Engine.TEXTUREFORMAT_RGB;
+                // texture.getEngine()._uploadDataToTextureDirectly(texture, rgb565Data, 0,0)
+            }else {
+                // compress texture needs to be flipped
+                texture._invertVScale = true;
+                texture.getEngine()._uploadCompressedDataToTextureDirectly(texture, BasisTools.GetInternalFormatFromBasisFormat(format!), fileInfo.width, fileInfo.height, transcodeResult.pixels, 0, 0);
+            }
+        });
+    }
+}
+
+// Register the loader.
+Engine._TextureLoaders.push(new _BasisTextureLoader());

+ 2 - 1
src/Materials/Textures/Loaders/index.ts

@@ -1,4 +1,5 @@
 export * from "./ddsTextureLoader";
 export * from "./envTextureLoader";
 export * from "./ktxTextureLoader";
-export * from "./tgaTextureLoader";
+export * from "./tgaTextureLoader";
+export * from "./basisTextureLoader";

+ 0 - 1
src/Materials/effect.ts

@@ -360,7 +360,6 @@ export class Effect implements IDisposable {
             version: (this._engine.webGLVersion * 100).toString(),
         };
 
-
         this._loadVertexShader(vertexSource, (vertexCode) => {
             this._loadFragmentShader(fragmentSource, (fragmentCode) => {
                 ShaderProcessor.Process(vertexCode, processorOptions, (migratedVertexCode) => {

+ 234 - 0
src/Misc/basis.ts

@@ -0,0 +1,234 @@
+import { Logger } from "../Misc/logger";
+import { Nullable } from '../types';
+import { Engine } from '../Engines/engine';
+
+/**
+ * Info about the .basis files
+ */
+class BasisFileInfo {
+    /**
+     * If the file has alpha
+     */
+    public hasAlpha: boolean;
+    /**
+     * Width of the image
+     */
+    public width: number;
+    /**
+     * Height of the image
+     */
+    public height: number;
+    /**
+     * Aligned width used when falling back to Rgb565 ((width + 3) & ~3)
+     */
+    public alignedWidth: number;
+    /**
+     * Aligned height used when falling back to Rgb565 ((height + 3) & ~3)
+     */
+    public alignedHeight: number;
+}
+
+/**
+ * Used to load .Basis files
+ * See https://github.com/BinomialLLC/basis_universal/tree/master/webgl
+ */
+export class BasisTools {
+    private static _Initialized = false;
+    private static _IgnoreSupportedFormats = false;
+    private static _BASIS_FORMAT = {
+        cTFETC1: 0,
+        cTFBC1: 1,
+        cTFBC4: 2,
+        cTFPVRTC1_4_OPAQUE_ONLY: 3,
+        cTFBC7_M6_OPAQUE_ONLY: 4,
+        cTFETC2: 5,
+        cTFBC3: 6,
+        cTFBC5: 7,
+    };
+    /**
+     * Basis module can be aquired from https://github.com/BinomialLLC/basis_universal/tree/master/webgl
+     * This should be set prior to loading a .basis texture
+     */
+    public static BasisModule: Nullable<any> = null;
+
+    /**
+     * Verifies that the BasisModule has been populated and falls back to loading from the web if not availible
+     */
+    public static VerifyBasisModule() {
+        if (!BasisTools.BasisModule) {
+            if ((window as any).Module && (window as any).Module.BasisFile) {
+                Logger.Warn("BasisTools.BasisModule not populated, falling back to window.Module");
+                BasisTools.BasisModule = (window as any).Module;
+            }else {
+                // TODO should load from cdn location as fallback
+                throw "Unable to load .basis texture, BasisTools.BasisModule should be populated";
+            }
+        }
+
+        if (!BasisTools._Initialized && BasisTools.BasisModule) {
+            BasisTools.BasisModule.initializeBasis();
+            BasisTools._Initialized = true;
+        }
+    }
+
+    /**
+     * Verifies that the basis module has been populated and creates a bsis file from the image data
+     * @param data array buffer of the .basis file
+     * @returns the Basis file
+     */
+    public static LoadBasisFile(data: ArrayBuffer) {
+        BasisTools.VerifyBasisModule();
+        return new BasisTools.BasisModule.BasisFile(new Uint8Array(data));
+    }
+
+    /**
+     * Detects the supported transcode format for the file
+     * @param engine Babylon engine
+     * @param fileInfo info about the file
+     * @returns the chosed format or null if none are supported
+     */
+    public static GetSupportedTranscodeFormat(engine: Engine, fileInfo: BasisFileInfo): Nullable<number> {
+        var caps = engine.getCaps();
+        var format = null;
+        if (caps.etc1) {
+            format = BasisTools._BASIS_FORMAT.cTFETC1;
+        }else if (caps.s3tc) {
+            format = fileInfo.hasAlpha ? BasisTools._BASIS_FORMAT.cTFBC3 : BasisTools._BASIS_FORMAT.cTFBC1;
+        }else if (caps.pvrtc) {
+            format = BasisTools._BASIS_FORMAT.cTFPVRTC1_4_OPAQUE_ONLY;
+        }else if (caps.etc2) {
+            format = BasisTools._BASIS_FORMAT.cTFETC2;
+        }
+        return format;
+    }
+
+    /**
+     * Get the internal format to be passed to texImage2D corrisponding to the .basis format value
+     * @param basisFormat format chosen from GetSupportedTranscodeFormat
+     * @returns internal format corrisponding to the Basis format
+     */
+    public static GetInternalFormatFromBasisFormat(basisFormat: number) {
+        // TODO more formats need to be added here and validated
+        var COMPRESSED_RGB_S3TC_DXT1_EXT  = 0x83F0;
+        var COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3;
+        var RGB_ETC1_Format = 36196;
+
+        // var COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1;
+        // var COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2;
+
+        if (basisFormat === this._BASIS_FORMAT.cTFETC1) {
+            return RGB_ETC1_Format;
+        }else if (basisFormat === this._BASIS_FORMAT.cTFBC1) {
+            return COMPRESSED_RGB_S3TC_DXT1_EXT;
+        }else if (basisFormat === this._BASIS_FORMAT.cTFBC3) {
+            return COMPRESSED_RGBA_S3TC_DXT5_EXT;
+        }else {
+            // TODO find value for these formats
+            // else if(basisFormat === this.BASIS_FORMAT.cTFBC4){
+            // }else if(basisFormat === this.BASIS_FORMAT.cTFPVRTC1_4_OPAQUE_ONLY){
+            // }else if(basisFormat === this.BASIS_FORMAT.cTFBC7_M6_OPAQUE_ONLY){
+            // }else if(basisFormat === this.BASIS_FORMAT.cTFETC2){
+            // }else if(basisFormat === this.BASIS_FORMAT.cTFBC5){
+            // }
+            throw "Basis format not found or supported";
+        }
+    }
+
+    /**
+     * Retreives information about the basis file eg. dimensions
+     * @param basisFile the basis file to get the info from
+     * @returns information about the basis file
+     */
+    public static GetFileInfo(basisFile: any): BasisFileInfo {
+        var hasAlpha = basisFile.getHasAlpha();
+        var width = basisFile.getImageWidth(0, 0);
+        var height = basisFile.getImageHeight(0, 0);
+        var alignedWidth = (width + 3) & ~3;
+        var alignedHeight = (height + 3) & ~3;
+        var info = { hasAlpha, width, height, alignedWidth, alignedHeight };
+        return info;
+    }
+
+    /**
+     * Transcodes the basis file to the requested format to be transferred to the gpu
+     * @param format fromat to be transferred to
+     * @param fileInfo information about the loaded file
+     * @param loadedFile the loaded basis file
+     * @returns the resulting pixels and if the transcode fell back to using Rgb565
+     */
+    public static TranscodeFile(format: Nullable<number>, fileInfo: BasisFileInfo, loadedFile: any) {
+        if (BasisTools._IgnoreSupportedFormats) {
+            format = null;
+        }
+        var needsConversion = false;
+        if (format === null) {
+            needsConversion = true;
+            format = fileInfo.hasAlpha ? BasisTools._BASIS_FORMAT.cTFBC3 : BasisTools._BASIS_FORMAT.cTFBC1;
+        }
+
+        if (!loadedFile.startTranscoding()) {
+            loadedFile.close();
+            loadedFile.delete();
+            throw "transcode failed";
+        }
+        var dstSize = loadedFile.getImageTranscodedSizeInBytes(0, 0, format);
+        var dst = new Uint8Array(dstSize);
+        if (!loadedFile.transcodeImage(dst, 0, 0, format, 1, 0)) {
+            loadedFile.close();
+            loadedFile.delete();
+            throw "transcode failed";
+        }
+        loadedFile.close();
+        loadedFile.delete();
+
+        // If no supported format is found, load as dxt and convert to rgb565
+        if (needsConversion) {
+            dst = BasisTools.ConvertDxtToRgb565(dst, 0, fileInfo.alignedWidth, fileInfo.alignedHeight);
+        }
+
+        return {
+            fallbackToRgb565: needsConversion, pixels: dst
+        };
+    }
+
+    /**
+     * From https://github.com/BinomialLLC/basis_universal/blob/master/webgl/texture/dxt-to-rgb565.js
+     * An unoptimized version of dxtToRgb565.  Also, the floating
+     * point math used to compute the colors actually results in
+     * slightly different colors compared to hardware DXT decoders.
+     * @param {Uint8Array} src
+     * @param {number} srcByteOffset
+     * @param {number} width
+     * @param {number} height
+     * @return {Uint16Array} dst
+     */
+    public static ConvertDxtToRgb565(src: Uint16Array, srcByteOffset: number, width: number, height: number): Uint16Array {
+        var c = new Uint16Array(4);
+        var dst = new Uint16Array(width * height);
+
+        var blockWidth = width / 4;
+        var blockHeight = height / 4;
+        for (var blockY = 0; blockY < blockHeight; blockY++) {
+            for (var blockX = 0; blockX < blockWidth; blockX++) {
+            var i = srcByteOffset + 8 * (blockY * blockWidth + blockX);
+            c[0] = src[i] | (src[i + 1] << 8);
+            c[1] = src[i + 2] | (src[i + 3] << 8);
+            c[2] = (2 * (c[0] & 0x1f) + 1 * (c[1] & 0x1f)) / 3
+                    | (((2 * (c[0] & 0x7e0) + 1 * (c[1] & 0x7e0)) / 3) & 0x7e0)
+                    | (((2 * (c[0] & 0xf800) + 1 * (c[1] & 0xf800)) / 3) & 0xf800);
+            c[3] = (2 * (c[1] & 0x1f) + 1 * (c[0] & 0x1f)) / 3
+                    | (((2 * (c[1] & 0x7e0) + 1 * (c[0] & 0x7e0)) / 3) & 0x7e0)
+                    | (((2 * (c[1] & 0xf800) + 1 * (c[0] & 0xf800)) / 3) & 0xf800);
+            for (var row = 0; row < 4; row++) {
+                var m = src[i + 4 + row];
+                var dstI = (blockY * 4 + row) * width + blockX * 4;
+                dst[dstI++] = c[m & 0x3];
+                dst[dstI++] = c[(m >> 2) & 0x3];
+                dst[dstI++] = c[(m >> 4) & 0x3];
+                dst[dstI++] = c[(m >> 6) & 0x3];
+            }
+            }
+        }
+        return dst;
+    }
+}