Преглед изворни кода

Merge pull request #7788 from bghgary/texture-select

Re-add support for selecting texture based on engine capabilities
mergify[bot] пре 5 година
родитељ
комит
d780145531

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

@@ -15,6 +15,7 @@
 ### Engine
 
 - Allow logging of shader code when a compilation error occurs ([Popov72](https://github.com/Popov72))
+- Add back support for selecting textures based on engine capabilities ([bghgary](https://github.com/bghgary))
 
 ### Inspector
 

+ 23 - 11
src/Engines/Extensions/engine.cubeTexture.ts

@@ -216,9 +216,9 @@ ThinEngine.prototype._setCubeMapTextureParams = function(loadMipmap: boolean): v
 };
 
 ThinEngine.prototype.createCubeTexture = 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): InternalTexture {
-    var gl = this._gl;
+    const gl = this._gl;
 
-    var texture = fallback ? fallback : new InternalTexture(this, InternalTextureSource.Cube);
+    const texture = fallback ? fallback : new InternalTexture(this, InternalTextureSource.Cube);
     texture.isCube = true;
     texture.url = rootUrl;
     texture.generateMipMaps = !noMipmap;
@@ -230,8 +230,13 @@ ThinEngine.prototype.createCubeTexture = function(rootUrl: string, scene: Nullab
         texture._files = files;
     }
 
-    var lastDot = rootUrl.lastIndexOf('.');
-    var extension = forcedExtension ? forcedExtension : (lastDot > -1 ? rootUrl.substring(lastDot).toLowerCase() : "");
+    const originalRootUrl = rootUrl;
+    if (this._transformTextureUrl && !fallback) {
+        rootUrl = this._transformTextureUrl(rootUrl);
+    }
+
+    const lastDot = rootUrl.lastIndexOf('.');
+    const extension = forcedExtension ? forcedExtension : (lastDot > -1 ? rootUrl.substring(lastDot).toLowerCase() : "");
 
     let loader: Nullable<IInternalTextureLoader> = null;
     for (let availableLoader of ThinEngine._TextureLoaders) {
@@ -241,9 +246,16 @@ ThinEngine.prototype.createCubeTexture = function(rootUrl: string, scene: Nullab
         }
     }
 
-    let onInternalError = (request?: IWebRequest, exception?: any) => {
-        if (onError && request) {
-            onError(request.status + " " + request.statusText, exception);
+    const onInternalError = (request?: IWebRequest, exception?: any) => {
+        if (rootUrl === originalRootUrl) {
+            if (onError && request) {
+                onError(request.status + " " + request.statusText, exception);
+            }
+        }
+        else {
+            // fall back to the original url if the transformed url fails to load
+            Logger.Warn(`Failed to load ${rootUrl}, falling back to the ${originalRootUrl}`);
+            this.createCubeTexture(originalRootUrl, scene, files, noMipmap, onLoad, onError, format, forcedExtension, createPolynomials, lodScale, lodOffset, texture);
         }
     };
 
@@ -274,10 +286,10 @@ ThinEngine.prototype.createCubeTexture = function(rootUrl: string, scene: Nullab
         }
 
         this._cascadeLoadImgs(scene, (imgs) => {
-            var width = this.needPOTTextures ? ThinEngine.GetExponentOfTwo(imgs[0].width, this._caps.maxCubemapTextureSize) : imgs[0].width;
-            var height = width;
+            const width = this.needPOTTextures ? ThinEngine.GetExponentOfTwo(imgs[0].width, this._caps.maxCubemapTextureSize) : imgs[0].width;
+            const height = width;
 
-            var faces = [
+            const faces = [
                 gl.TEXTURE_CUBE_MAP_POSITIVE_X, gl.TEXTURE_CUBE_MAP_POSITIVE_Y, gl.TEXTURE_CUBE_MAP_POSITIVE_Z,
                 gl.TEXTURE_CUBE_MAP_NEGATIVE_X, gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, gl.TEXTURE_CUBE_MAP_NEGATIVE_Z
             ];
@@ -285,7 +297,7 @@ ThinEngine.prototype.createCubeTexture = function(rootUrl: string, scene: Nullab
             this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, texture, true);
             this._unpackFlipY(false);
 
-            let internalFormat = format ? this._getInternalFormat(format) : this._gl.RGBA;
+            const internalFormat = format ? this._getInternalFormat(format) : this._gl.RGBA;
             for (var index = 0; index < faces.length; index++) {
                 if (imgs[index].width !== width || imgs[index].height !== height) {
 

+ 114 - 0
src/Engines/Extensions/engine.textureSelector.ts

@@ -0,0 +1,114 @@
+import { Nullable } from '../../types';
+import { Engine } from '../engine';
+
+declare module "../../Engines/engine" {
+    export interface Engine {
+        /** @hidden */
+        _excludedCompressedTextures: string[];
+
+        /** @hidden */
+        _textureFormatInUse: string;
+
+        /**
+         * Gets the list of texture formats supported
+         */
+        readonly texturesSupported: Array<string>;
+
+        /**
+         * Gets the texture format in use
+         */
+        readonly textureFormatInUse: Nullable<string>;
+
+        /**
+         * Set the compressed texture extensions or file names to skip.
+         *
+         * @param skippedFiles defines the list of those texture files you want to skip
+         * Example: [".dds", ".env", "myfile.png"]
+         */
+        setCompressedTextureExclusions(skippedFiles: Array<string>): void;
+
+        /**
+         * Set the compressed texture format to use, based on the formats you have, and the formats
+         * supported by the hardware / browser.
+         *
+         * Khronos Texture Container (.ktx) files are used to support this.  This format has the
+         * advantage of being specifically designed for OpenGL.  Header elements directly correspond
+         * to API arguments needed to compressed textures.  This puts the burden on the container
+         * generator to house the arcane code for determining these for current & future formats.
+         *
+         * for description see https://www.khronos.org/opengles/sdk/tools/KTX/
+         * for file layout see https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/
+         *
+         * Note: The result of this call is not taken into account when a texture is base64.
+         *
+         * @param formatsAvailable defines the list of those format families you have created
+         * on your server.  Syntax: '-' + format family + '.ktx'.  (Case and order do not matter.)
+         *
+         * Current families are astc, dxt, pvrtc, etc2, & etc1.
+         * @returns The extension selected.
+         */
+        setTextureFormatToUse(formatsAvailable: Array<string>): Nullable<string>;
+    }
+}
+
+function transformTextureUrl(this: Engine, url: string): string {
+    const excludeFn = (entry: string) => {
+        const strRegExPattern: string = '\\b' + entry + '\\b';
+        return (url && (url === entry || url.match(new RegExp(strRegExPattern, 'g'))));
+    };
+
+    if (this._excludedCompressedTextures && this._excludedCompressedTextures.some(excludeFn)) {
+        return url;
+    }
+
+    const lastDot = url.lastIndexOf('.');
+    return (lastDot > -1 ? url.substring(0, lastDot) : url) + this._textureFormatInUse;
+}
+
+Object.defineProperty(Engine.prototype, "texturesSupported", {
+    get: function(this: Engine) {
+        // Intelligently add supported compressed formats in order to check for.
+        // Check for ASTC support first as it is most powerful and to be very cross platform.
+        // Next PVRTC & DXT, which are probably superior to ETC1/2.
+        // Likely no hardware which supports both PVR & DXT, so order matters little.
+        // ETC2 is newer and handles ETC1 (no alpha capability), so check for first.
+        const texturesSupported = new Array<string>();
+        if (this._caps.astc) { texturesSupported.push('-astc.ktx'); }
+        if (this._caps.s3tc) { texturesSupported.push('-dxt.ktx'); }
+        if (this._caps.pvrtc) { texturesSupported.push('-pvrtc.ktx'); }
+        if (this._caps.etc2) { texturesSupported.push('-etc2.ktx'); }
+        if (this._caps.etc1) { texturesSupported.push('-etc1.ktx'); }
+        return texturesSupported;
+    },
+    enumerable: true,
+    configurable: true
+});
+
+Object.defineProperty(Engine.prototype, "textureFormatInUse", {
+    get: function(this: Engine) {
+        return this._textureFormatInUse || null;
+    },
+    enumerable: true,
+    configurable: true
+});
+
+Engine.prototype.setCompressedTextureExclusions = function(skippedFiles: Array<string>): void {
+    this._excludedCompressedTextures = skippedFiles;
+};
+
+Engine.prototype.setTextureFormatToUse = function(formatsAvailable: Array<string>): Nullable<string> {
+    const texturesSupported = this.texturesSupported;
+    for (let i = 0, len1 = texturesSupported.length; i < len1; i++) {
+        for (let j = 0, len2 = formatsAvailable.length; j < len2; j++) {
+            if (texturesSupported[i] === formatsAvailable[j].toLowerCase()) {
+                this._transformTextureUrl = transformTextureUrl.bind(this);
+                return this._textureFormatInUse = texturesSupported[i];
+            }
+        }
+    }
+    // actively set format to nothing, to allow this to be called more than once
+    // and possibly fail the 2nd time
+    delete this._textureFormatInUse;
+    delete this._transformTextureUrl;
+    return null;
+};

+ 2 - 1
src/Engines/Extensions/index.ts

@@ -11,4 +11,5 @@ export * from "./engine.renderTarget";
 export * from "./engine.renderTargetCube";
 export * from "./engine.webVR";
 export * from "./engine.uniformBuffer";
-export * from "./engine.views";
+export * from "./engine.views";
+export * from "./engine.textureSelector";

+ 33 - 49
src/Engines/thinEngine.ts

@@ -380,11 +380,8 @@ export class ThinEngine {
 
     private _activeRequests = new Array<IFileRequest>();
 
-    // Hardware supported Compressed Textures
-    protected _texturesSupported = new Array<string>();
-
     /** @hidden */
-    public _textureFormatInUse: Nullable<string>;
+    public _transformTextureUrl: Nullable<(url: string) => string> = null;
 
     protected get _supportsHardwareTextureRescaling() {
         return false;
@@ -402,20 +399,6 @@ export class ThinEngine {
     }
 
     /**
-     * Gets the list of texture formats supported
-     */
-    public get texturesSupported(): Array<string> {
-        return this._texturesSupported;
-    }
-
-    /**
-     * Gets the list of texture formats in use
-     */
-    public get textureFormatInUse(): Nullable<string> {
-        return this._textureFormatInUse;
-    }
-
-    /**
      * Gets the current viewport
      */
     public get currentViewport(): Nullable<IViewportLike> {
@@ -730,7 +713,7 @@ export class ThinEngine {
         }
     }
 
-    private _initGLContext(): void {
+    protected _initGLContext(): void {
         // Caps
         this._caps = {
             maxTexturesImageUnits: this._gl.getParameter(this._gl.MAX_TEXTURE_IMAGE_UNITS),
@@ -886,17 +869,6 @@ export class ThinEngine {
             }
         }
 
-        // Intelligently add supported compressed formats in order to check for.
-        // Check for ASTC support first as it is most powerful and to be very cross platform.
-        // Next PVRTC & DXT, which are probably superior to ETC1/2.
-        // Likely no hardware which supports both PVR & DXT, so order matters little.
-        // ETC2 is newer and handles ETC1 (no alpha capability), so check for first.
-        if (this._caps.astc) { this.texturesSupported.push('-astc.ktx'); }
-        if (this._caps.s3tc) { this.texturesSupported.push('-dxt.ktx'); }
-        if (this._caps.pvrtc) { this.texturesSupported.push('-pvrtc.ktx'); }
-        if (this._caps.etc2) { this.texturesSupported.push('-etc2.ktx'); }
-        if (this._caps.etc1) { this.texturesSupported.push('-etc1.ktx'); }
-
         if (this._gl.getShaderPrecisionFormat) {
             var vertex_highp = this._gl.getShaderPrecisionFormat(this._gl.VERTEX_SHADER, this._gl.HIGH_FLOAT);
             var fragment_highp = this._gl.getShaderPrecisionFormat(this._gl.FRAGMENT_SHADER, this._gl.HIGH_FLOAT);
@@ -2768,7 +2740,7 @@ export class ThinEngine {
     /**
      * Usually called from Texture.ts.
      * Passed information to create a WebGLTexture
-     * @param urlArg defines a value which contains one of the following:
+     * @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'
@@ -2785,20 +2757,25 @@ export class ThinEngine {
      * @param mimeType defines an optional mime type
      * @returns a InternalTexture for assignment back into BABYLON.Texture
      */
-    public createTexture(urlArg: Nullable<string>, noMipmap: boolean, invertY: boolean, scene: Nullable<ISceneLike>, samplingMode: number = Constants.TEXTURE_TRILINEAR_SAMPLINGMODE,
+    public createTexture(url: Nullable<string>, noMipmap: boolean, invertY: boolean, scene: Nullable<ISceneLike>, samplingMode: number = Constants.TEXTURE_TRILINEAR_SAMPLINGMODE,
         onLoad: Nullable<() => void> = null, onError: Nullable<(message: string, exception: any) => void> = null,
         buffer: Nullable<string | ArrayBuffer | ArrayBufferView | HTMLImageElement | Blob | ImageBitmap> = null, fallback: Nullable<InternalTexture> = null, format: Nullable<number> = null,
         forcedExtension: Nullable<string> = null, mimeType?: string): InternalTexture {
-        var url = String(urlArg); // assign a new string, so that the original is still available in case of fallback
-        var fromData = url.substr(0, 5) === "data:";
-        var fromBlob = url.substr(0, 5) === "blob:";
-        var isBase64 = fromData && url.indexOf(";base64,") !== -1;
+        url = url || "";
+        const fromData = url.substr(0, 5) === "data:";
+        const fromBlob = url.substr(0, 5) === "blob:";
+        const isBase64 = fromData && url.indexOf(";base64,") !== -1;
 
         let texture = fallback ? fallback : new InternalTexture(this, InternalTextureSource.Url);
 
+        const originalUrl = url;
+        if (this._transformTextureUrl && !isBase64 && !fallback && !buffer) {
+            url = this._transformTextureUrl(url);
+        }
+
         // establish the file extension, if possible
-        var lastDot = url.lastIndexOf('.');
-        var extension = forcedExtension ? forcedExtension : (lastDot > -1 ? url.substring(lastDot).toLowerCase() : "");
+        const lastDot = url.lastIndexOf('.');
+        const extension = forcedExtension ? forcedExtension : (lastDot > -1 ? url.substring(lastDot).toLowerCase() : "");
         let loader: Nullable<IInternalTextureLoader> = null;
 
         for (let availableLoader of ThinEngine._TextureLoaders) {
@@ -2828,27 +2805,34 @@ export class ThinEngine {
 
         if (!fallback) { this._internalTexturesCache.push(texture); }
 
-        let onInternalError = (message?: string, exception?: any) => {
+        const onInternalError = (message?: string, exception?: any) => {
             if (scene) {
                 scene._removePendingData(texture);
             }
 
-            if (onLoadObserver) {
-                texture.onLoadedObservable.remove(onLoadObserver);
-            }
+            if (url === originalUrl) {
+                if (onLoadObserver) {
+                    texture.onLoadedObservable.remove(onLoadObserver);
+                }
 
-            if (EngineStore.UseFallbackTexture) {
-                this.createTexture(EngineStore.FallbackTexture, noMipmap, texture.invertY, scene, samplingMode, null, onError, buffer, texture);
-            }
+                if (EngineStore.UseFallbackTexture) {
+                    this.createTexture(EngineStore.FallbackTexture, noMipmap, texture.invertY, scene, samplingMode, null, onError, buffer, texture);
+                }
 
-            if (onError) {
-                onError((message || "Unknown error") + (EngineStore.UseFallbackTexture ? " - Fallback texture was used" : ""), exception);
+                if (onError) {
+                    onError((message || "Unknown error") + (EngineStore.UseFallbackTexture ? " - Fallback texture was used" : ""), exception);
+                }
+            }
+            else {
+                // fall back to the original url if the transformed url fails to load
+                Logger.Warn(`Failed to load ${url}, falling back to ${originalUrl}`);
+                this.createTexture(originalUrl, noMipmap, texture.invertY, scene, samplingMode, onLoad, onError, buffer, texture, format, forcedExtension, mimeType);
             }
         };
 
         // processing for non-image formats
         if (loader) {
-            var callback = (data: ArrayBufferView) => {
+            const callback = (data: ArrayBufferView) => {
                 loader!.loadData(data, texture, (width: number, height: number, loadMipmap: boolean, isCompressed: boolean, done: () => void, loadFailed) => {
                     if (loadFailed) {
                         onInternalError("TextureLoader failed to load data");
@@ -2879,7 +2863,7 @@ export class ThinEngine {
                 }
             }
         } else {
-            var onload = (img: HTMLImageElement | ImageBitmap) => {
+            const onload = (img: HTMLImageElement | ImageBitmap) => {
                 if (fromBlob && !this._doNotHandleContextLost) {
                     // We need to store the image if we need to rebuild the texture
                     // in case of a webgl context lost

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

@@ -90,6 +90,7 @@ export class _KTXTextureLoader implements IInternalTextureLoader {
             });
         }
         else {
+            Logger.Error("texture missing KTX identifier");
             callback(0, 0, false, false, () => {}, true);
         }
     }