Browse Source

Merge pull request #6766 from syntheticmagus/master

Changes to make Babylon Native work
David Catuhe 6 years ago
parent
commit
97cf296d4c

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

@@ -23,7 +23,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)
-- Ability to set render camera on utility layer instead of using the latest active camera ([TrevorDev](https://github.com/TrevorDev))
+- Added ability to set render camera on utility layer instead of using the latest active camera ([TrevorDev](https://github.com/TrevorDev))
 - Move normalizeToUnitCube to transformNode instead of abstract mesh and add predicate to exclude sub objects when scaling ([TrevorDev](https://github.com/TrevorDev))
 - Method to check if device orientation is available ([TrevorDev](https://github.com/TrevorDev))
 - Added support for sound sprites [Doc](https://doc.babylonjs.com/how_to/playing_sounds_and_music#playing-a-sound-sprite) ([Deltakosh](https://github.com/deltakosh/))

+ 8 - 1
loaders/src/glTF/2.0/glTFLoader.ts

@@ -1546,6 +1546,13 @@ export class GLTFLoader implements IGLTFLoader {
                 return new VertexBuffer(this._babylonScene.getEngine(), data, kind, false);
             });
         }
+        // Load joint indices as a float array since the shaders expect float data but glTF uses unsigned byte/short.
+        // This prevents certain platforms (e.g. D3D) from having to convert the data to float on the fly.
+        else if (kind === VertexBuffer.MatricesIndicesKind) {
+            accessor._babylonVertexBuffer = this._loadFloatAccessorAsync(`/accessors/${accessor.index}`, accessor).then((data) => {
+                return new VertexBuffer(this._babylonScene.getEngine(), data, kind, false);
+            });
+        }
         else {
             const bufferView = ArrayItem.Get(`${context}/bufferView`, this._gltf.bufferViews, accessor.bufferView);
             accessor._babylonVertexBuffer = this._loadVertexBufferViewAsync(bufferView, kind).then((babylonBuffer) => {
@@ -1866,7 +1873,7 @@ export class GLTFLoader implements IGLTFLoader {
             promises.push(this.loadImageAsync(`/images/${image.index}`, image).then((data) => {
                 const name = image.uri || `${this._fileName}#image${image.index}`;
                 const dataUrl = `data:${this._uniqueRootUrl}${name}`;
-                babylonTexture.updateURL(dataUrl, new Blob([data], { type: image.mimeType }));
+                babylonTexture.updateURL(dataUrl, data);
             }));
         }
 

+ 105 - 0
src/Engines/Native/nativeShaderProcessor.ts

@@ -0,0 +1,105 @@
+import { WebGL2ShaderProcessor } from "../WebGL/webGL2ShaderProcessors";
+import { VertexBuffer } from "../../Meshes/buffer";
+
+// These numbers must match the values for bgfx::Attrib::Enum
+const attributeLocations: { [kind: string]: number } = {
+    [VertexBuffer.PositionKind]: 0,
+    [VertexBuffer.NormalKind]: 1,
+    [VertexBuffer.TangentKind]: 2,
+    [VertexBuffer.ColorKind]: 4,
+    [VertexBuffer.MatricesIndicesKind]: 8,
+    [VertexBuffer.MatricesWeightsKind]: 9,
+};
+
+// Must match bgfx::Attrib::TexCoord0
+const firstGenericAttributeLocation = 10;
+
+// Must match bgfx::Attrib::TexCoord7
+const lastGenericAttributeLocation = 17;
+
+/** @hidden */
+export class NativeShaderProcessor extends WebGL2ShaderProcessor {
+    private _genericAttributeLocation: number;
+    private _varyingLocationCount: number;
+    private _varyingLocationMap: { [name: string]: number };
+    private _replacements: Array<{ searchValue: RegExp, replaceValue: string }>;
+    private _textureCount: number;
+    private _uniforms: Array<string>;
+
+    public lineProcessor(line: string): string {
+        for (const replacement of this._replacements) {
+            line = line.replace(replacement.searchValue, replacement.replaceValue);
+        }
+
+        return line;
+    }
+
+    public attributeProcessor(attribute: string): string {
+        const match = attribute.match(/attribute\s+[^\s]+\s+([^\s]+)\s*(?:\[.+\])?\s*;/)!;
+        const name = match[1];
+
+        let location = attributeLocations[name];
+        if (location === undefined) {
+            location = this._genericAttributeLocation++;
+            if (location > lastGenericAttributeLocation) {
+                throw new Error("Exceeded maximum custom attributes");
+            }
+        }
+
+        return `layout(location=${location}) ${super.attributeProcessor(attribute)}`;
+    }
+
+    public varyingProcessor(varying: string, isFragment: boolean): string {
+        let location: number;
+
+        if (isFragment) {
+            location = this._varyingLocationMap[varying];
+        }
+        else {
+            location = this._varyingLocationCount++;
+            this._varyingLocationMap[varying] = location;
+        }
+
+        return `layout(location=${location}) ${super.varyingProcessor(varying, isFragment)}`;
+    }
+
+    public uniformProcessor(uniform: string): string {
+        const match = uniform.match(/uniform\s+([^\s]+)\s+([^\s]+)\s*(?:\[.+\])?\s*;/)!;
+        const type = match[1];
+        const name = match[2];
+
+        switch (type) {
+            case "sampler2D":
+            case "samplerCube": {
+                const suffix = type.substr(7);
+                const binding = this._textureCount++;
+                this._replacements.push({ searchValue: new RegExp(`\\b${name}\\b`), replaceValue: `sampler${suffix}(${name}Texture, ${name})` });
+                return `layout(binding=${binding}) uniform texture${suffix} ${name}Texture;\nlayout(binding=${binding}) uniform sampler ${name};`;
+            }
+        }
+
+        this._uniforms.push(uniform);
+        return this._uniforms.length === 1 ? "<UNIFORM>" : "";
+    }
+
+    public preProcessor(code: string, defines: string[], isFragment: boolean): string {
+        this._genericAttributeLocation = firstGenericAttributeLocation;
+
+        if (!isFragment) {
+            this._varyingLocationCount = 0;
+            this._varyingLocationMap = {};
+        }
+
+        this._replacements = [];
+        this._textureCount = 0;
+        this._uniforms = [];
+        return code;
+    }
+
+   public postProcessor(code: string, defines: string[], isFragment: boolean): string {
+        code = super.postProcessor(code, defines, isFragment);
+        code = code.replace("<UNIFORM>", `uniform Frame {\n${this._uniforms.join("\n")}\n};`);
+        code = code.replace("out vec4 glFragColor", "layout(location=0) out vec4 glFragColor");
+        return code;
+    }
+}

+ 5 - 4
src/Engines/Processors/shaderCodeNode.ts

@@ -18,6 +18,11 @@ export class ShaderCodeNode {
             let value: string = this.line;
             let processor = options.processor;
             if (processor) {
+                // This must be done before other replacements to avoid mistakenly changing something that was already changed.
+                if (processor.lineProcessor) {
+                    value = processor.lineProcessor(value, options.isFragment);
+                }
+
                 if (processor.attributeProcessor && StringTools.StartsWith(this.line, "attribute")) {
                     value = processor.attributeProcessor(this.line);
                 } else if (processor.varyingProcessor && StringTools.StartsWith(this.line, "varying")) {
@@ -43,10 +48,6 @@ export class ShaderCodeNode {
                         value = processor.endOfUniformBufferProcessor(this.line, options.isFragment);
                     }
                 }
-
-                if (processor.lineProcessor) {
-                    value = processor.lineProcessor(value, options.isFragment);
-                }
             }
 
             result += value + "\r\n";

+ 70 - 46
src/Engines/engine.ts

@@ -708,7 +708,7 @@ export class Engine {
     public _gl: WebGLRenderingContext;
     private _renderingCanvas: Nullable<HTMLCanvasElement>;
     private _windowIsBackground = false;
-    private _webGLVersion = 1.0;
+    protected _webGLVersion = 1.0;
 
     protected _highPrecisionShadersAllowed = true;
     /** @hidden */
@@ -764,7 +764,7 @@ export class Engine {
     public _caps: EngineCapabilities;
     private _pointerLockRequested: boolean;
     private _isStencilEnable: boolean;
-    private _colorWrite = true;
+    protected _colorWrite = true;
 
     private _loadingScreen: ILoadingScreen;
 
@@ -2045,12 +2045,12 @@ export class Engine {
         if (this._activeRenderLoops.length > 0) {
             // Register new frame
             if (this.customAnimationFrameRequester) {
-                this.customAnimationFrameRequester.requestID = Engine.QueueNewFrame(this.customAnimationFrameRequester.renderFunction || this._bindedRenderFunction, this.customAnimationFrameRequester);
+                this.customAnimationFrameRequester.requestID = this._queueNewFrame(this.customAnimationFrameRequester.renderFunction || this._bindedRenderFunction, this.customAnimationFrameRequester);
                 this._frameHandler = this.customAnimationFrameRequester.requestID;
             } else if (this.isVRPresenting()) {
                 this._requestVRFrame();
             } else {
-                this._frameHandler = Engine.QueueNewFrame(this._bindedRenderFunction, this.getHostWindow());
+                this._frameHandler = this._queueNewFrame(this._bindedRenderFunction, this.getHostWindow());
             }
         } else {
             this._renderingQueueLaunched = false;
@@ -2058,6 +2058,14 @@ export class Engine {
     }
 
     /**
+     * Can be used to override the current requestAnimationFrame requester.
+     * @hidden
+     */
+    protected _queueNewFrame(bindedRenderFunction: any, requester?: any): number {
+        return Engine.QueueNewFrame(bindedRenderFunction, requester);
+    }
+
+    /**
      * Register and execute a render loop. The engine can have more than one render function
      * @param renderFunction defines the function to continuously execute
      */
@@ -2071,7 +2079,7 @@ export class Engine {
         if (!this._renderingQueueLaunched) {
             this._renderingQueueLaunched = true;
             this._bindedRenderFunction = this._renderLoop.bind(this);
-            this._frameHandler = Engine.QueueNewFrame(this._bindedRenderFunction, this.getHostWindow());
+            this._frameHandler = this._queueNewFrame(this._bindedRenderFunction, this.getHostWindow());
         }
     }
 
@@ -2676,40 +2684,38 @@ export class Engine {
 
         this.bindIndexBuffer(dataBuffer);
 
-        // Check for 32 bits indices
-        var arrayBuffer;
-        var need32Bits = false;
+        const data = this._normalizeIndexData(indices);
+        this._gl.bufferData(this._gl.ELEMENT_ARRAY_BUFFER, data, updatable ? this._gl.DYNAMIC_DRAW : this._gl.STATIC_DRAW);
+        this._resetIndexBufferBinding();
+        dataBuffer.references = 1;
+        dataBuffer.is32Bits = (data.BYTES_PER_ELEMENT === 4);
+        return dataBuffer;
+    }
 
+    protected _normalizeIndexData(indices: IndicesArray): Uint16Array | Uint32Array
+    {
         if (indices instanceof Uint16Array) {
-            arrayBuffer = indices;
-        } else {
-            //check 32 bit support
-            if (this._caps.uintIndices) {
-                if (indices instanceof Uint32Array) {
-                    arrayBuffer = indices;
-                    need32Bits = true;
-                } else {
-                    //number[] or Int32Array, check if 32 bit is necessary
-                    for (var index = 0; index < indices.length; index++) {
-                        if (indices[index] > 65535) {
-                            need32Bits = true;
-                            break;
-                        }
-                    }
+            return indices;
+        }
 
-                    arrayBuffer = need32Bits ? new Uint32Array(indices) : new Uint16Array(indices);
-                }
+        // Check 32 bit support
+        if (this._caps.uintIndices) {
+            if (indices instanceof Uint32Array) {
+                return indices;
             } else {
-                //no 32 bit support, force conversion to 16 bit (values greater 16 bit are lost)
-                arrayBuffer = new Uint16Array(indices);
+                // number[] or Int32Array, check if 32 bit is necessary
+                for (var index = 0; index < indices.length; index++) {
+                    if (indices[index] > 65535) {
+                        return new Uint32Array(indices);
+                    }
+                }
+
+                return new Uint16Array(indices);
             }
         }
 
-        this._gl.bufferData(this._gl.ELEMENT_ARRAY_BUFFER, arrayBuffer, updatable ? this._gl.DYNAMIC_DRAW : this._gl.STATIC_DRAW);
-        this._resetIndexBufferBinding();
-        dataBuffer.references = 1;
-        dataBuffer.is32Bits = need32Bits;
-        return dataBuffer;
+        // No 32 bit support, force conversion to 16 bit (values greater 16 bit are lost)
+        return new Uint16Array(indices);
     }
 
     /**
@@ -2997,13 +3003,17 @@ export class Engine {
         buffer.references--;
 
         if (buffer.references === 0) {
-            this._gl.deleteBuffer(buffer.underlyingResource);
+            this._deleteBuffer(buffer);
             return true;
         }
 
         return false;
     }
 
+    protected _deleteBuffer(buffer: DataBuffer): void {
+        this._gl.deleteBuffer(buffer.underlyingResource);
+    }
+
     /**
      * Creates a webGL buffer to use with instanciation
      * @param capacity defines the size of the buffer
@@ -3252,8 +3262,12 @@ export class Engine {
         return effect;
     }
 
+    protected static _concatenateShader(source: string, defines: Nullable<string>, shaderVersion: string = ""): string {
+        return shaderVersion + (defines ? defines + "\n" : "") + source;
+    }
+
     private _compileShader(source: string, type: string, defines: Nullable<string>, shaderVersion: string): WebGLShader {
-        return this._compileRawShader(shaderVersion + (defines ? defines + "\n" : "") + source, type);
+        return this._compileRawShader(Engine._concatenateShader(source, defines, shaderVersion), type);
     }
 
     private _compileRawShader(source: string, type: string): WebGLShader {
@@ -3318,7 +3332,7 @@ export class Engine {
      * Creates a new pipeline context
      * @returns the new pipeline
      */
-    public createPipelineContext() {
+    public createPipelineContext(): IPipelineContext {
         var pipelineContext = new WebGLPipelineContext();
         pipelineContext.engine = this;
 
@@ -4188,7 +4202,7 @@ export class Engine {
      */
     public createTexture(urlArg: Nullable<string>, noMipmap: boolean, invertY: boolean, scene: Nullable<Scene>, samplingMode: number = Engine.TEXTURE_TRILINEAR_SAMPLINGMODE,
         onLoad: Nullable<() => void> = null, onError: Nullable<(message: string, exception: any) => void> = null,
-        buffer: Nullable<string | ArrayBuffer | HTMLImageElement | Blob> = null, fallback: Nullable<InternalTexture> = null, format: Nullable<number> = null,
+        buffer: Nullable<string | ArrayBuffer | ArrayBufferView | HTMLImageElement | Blob> = null, fallback: Nullable<InternalTexture> = null, format: Nullable<number> = null,
         forcedExtension: Nullable<string> = null, excludeLoaders: Array<IInternalTextureLoader> = []): 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:";
@@ -4285,7 +4299,15 @@ export class Engine {
                     onInternalError("Unable to load " + (request ? request.responseURL : url, exception));
                 });
             } else {
-                callback(buffer as ArrayBuffer);
+                //callback(buffer as ArrayBuffer);
+                if (buffer instanceof ArrayBuffer) {
+                    callback(buffer);
+                }
+                else {
+                    if (onError) {
+                        onError("Unable to load: only ArrayBuffer supported here", null);
+                    }
+                }
             }
         } else {
             var onload = (img: HTMLImageElement) => {
@@ -4348,7 +4370,7 @@ export class Engine {
                     FileTools.LoadImage(url, onload, onInternalError, scene ? scene.offlineProvider : null);
                 }
             }
-            else if (typeof buffer === "string" || buffer instanceof ArrayBuffer || buffer instanceof Blob) {
+            else if (typeof buffer === "string" || buffer instanceof ArrayBuffer || ArrayBuffer.isView(buffer) || buffer instanceof Blob) {
                 FileTools.LoadImage(buffer, onload, onInternalError, scene ? scene.offlineProvider : null);
             }
             else {
@@ -5106,7 +5128,7 @@ export class Engine {
         throw _DevTools.WarnImport("Engine.RawTexture");
     }
 
-    private _prepareWebGLTextureContinuation(texture: InternalTexture, scene: Nullable<Scene>, noMipmap: boolean, isCompressed: boolean, samplingMode: number): void {
+    protected _prepareWebGLTextureContinuation(texture: InternalTexture, scene: Nullable<Scene>, noMipmap: boolean, isCompressed: boolean, samplingMode: number): void {
         var gl = this._gl;
         if (!gl) {
             return;
@@ -5228,11 +5250,9 @@ export class Engine {
 
     /** @hidden */
     public _releaseTexture(texture: InternalTexture): void {
-        var gl = this._gl;
-
         this._releaseFramebufferObjects(texture);
 
-        gl.deleteTexture(texture._webGLTexture);
+        this._deleteTexture(texture._webGLTexture);
 
         // Unbind channels
         this.unbindAllTextures();
@@ -5277,14 +5297,18 @@ export class Engine {
         });
     }
 
-    private setProgram(program: WebGLProgram): void {
+    protected _deleteTexture(texture: Nullable<WebGLTexture>): void {
+        this._gl.deleteTexture(texture);
+    }
+
+    protected _setProgram(program: WebGLProgram): void {
         if (this._currentProgram !== program) {
             this._gl.useProgram(program);
             this._currentProgram = program;
         }
     }
 
-    private _boundUniforms: { [key: number]: WebGLUniformLocation } = {};
+    protected _boundUniforms: { [key: number]: WebGLUniformLocation } = {};
 
     /**
      * Binds an effect to the webGL context
@@ -5292,7 +5316,7 @@ export class Engine {
      */
     public bindSamplers(effect: Effect): void {
         let webGLPipelineContext = effect.getPipelineContext() as WebGLPipelineContext;
-        this.setProgram(webGLPipelineContext.program!);
+        this._setProgram(webGLPipelineContext.program!);
         var samplers = effect.getSamplers();
         for (var index = 0; index < samplers.length; index++) {
             var uniform = effect.getUniform(samplers[index]);
@@ -5455,7 +5479,7 @@ export class Engine {
         return this._gl.REPEAT;
     }
 
-    private _setTexture(channel: number, texture: Nullable<BaseTexture>, isPartOfTextureArray = false, depthStencilTexture = false): boolean {
+    protected _setTexture(channel: number, texture: Nullable<BaseTexture>, isPartOfTextureArray = false, depthStencilTexture = false): boolean {
         // Not ready?
         if (!texture) {
             if (this._boundTexturesCache[channel] != null) {

+ 2 - 1
src/Engines/index.ts

@@ -5,4 +5,5 @@ export * from "./nullEngine";
 export * from "./Extensions/index";
 export * from "./IPipelineContext";
 export * from "./WebGL/webGLPipelineContext";
-export * from "./WebGL/webGL2ShaderProcessors";
+export * from "./WebGL/webGL2ShaderProcessors";
+export * from "./nativeEngine";

File diff suppressed because it is too large
+ 1284 - 0
src/Engines/nativeEngine.ts


+ 1 - 9
src/Engines/nullEngine.ts

@@ -485,15 +485,7 @@ export class NullEngine extends Engine {
         this._bindTextureDirectly(0, texture);
     }
 
-    /** @hidden */
-    public _releaseBuffer(buffer: DataBuffer): boolean {
-        buffer.references--;
-
-        if (buffer.references === 0) {
-            return true;
-        }
-
-        return false;
+    protected _deleteBuffer(buffer: WebGLBuffer): void {
     }
 
     public releaseEffects() {

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

@@ -155,7 +155,7 @@ export class InternalTexture {
     /** @hidden */
     public _dataSource = InternalTexture.DATASOURCE_UNKNOWN;
     /** @hidden */
-    public _buffer: Nullable<string | ArrayBuffer | HTMLImageElement | Blob> = null;
+    public _buffer: Nullable<string | ArrayBuffer | ArrayBufferView | HTMLImageElement | Blob> = null;
     /** @hidden */
     public _bufferView: Nullable<ArrayBufferView> = null;
     /** @hidden */

+ 3 - 3
src/Materials/Textures/texture.ts

@@ -213,7 +213,7 @@ export class Texture extends BaseTexture {
     protected _initialSamplingMode = Texture.BILINEAR_SAMPLINGMODE;
 
     /** @hidden */
-    public _buffer: Nullable<string | ArrayBuffer | HTMLImageElement | Blob> = null;
+    public _buffer: Nullable<string | ArrayBuffer | ArrayBufferView | HTMLImageElement | Blob> = null;
     private _deleteBuffer: boolean = false;
     protected _format: Nullable<number> = null;
     private _delayedOnLoad: Nullable<() => void> = null;
@@ -270,7 +270,7 @@ export class Texture extends BaseTexture {
      * @param deleteBuffer define if the buffer we are loading the texture from should be deleted after load
      * @param format define the format of the texture we are trying to load (Engine.TEXTUREFORMAT_RGBA...)
      */
-    constructor(url: Nullable<string>, sceneOrEngine: Nullable<Scene | Engine>, noMipmap: boolean = false, invertY: boolean = true, samplingMode: number = Texture.TRILINEAR_SAMPLINGMODE, onLoad: Nullable<() => void> = null, onError: Nullable<(message?: string, exception?: any) => void> = null, buffer: Nullable<string | ArrayBuffer | HTMLImageElement | Blob> = null, deleteBuffer: boolean = false, format?: number) {
+    constructor(url: Nullable<string>, sceneOrEngine: Nullable<Scene | Engine>, noMipmap: boolean = false, invertY: boolean = true, samplingMode: number = Texture.TRILINEAR_SAMPLINGMODE, onLoad: Nullable<() => void> = null, onError: Nullable<(message?: string, exception?: any) => void> = null, buffer: Nullable<string | ArrayBuffer | ArrayBufferView | HTMLImageElement | Blob> = null, deleteBuffer: boolean = false, format?: number) {
         super((sceneOrEngine && sceneOrEngine.getClassName() === "Scene") ? (sceneOrEngine as Scene) : null);
 
         this.name = url || "";
@@ -361,7 +361,7 @@ export class Texture extends BaseTexture {
      * @param buffer the buffer of the texture (defaults to null)
      * @param onLoad callback called when the texture is loaded  (defaults to null)
      */
-    public updateURL(url: string, buffer: Nullable<string | ArrayBuffer | HTMLImageElement | Blob> = null, onLoad?: () => void): void {
+    public updateURL(url: string, buffer: Nullable<string | ArrayBuffer | ArrayBufferView | HTMLImageElement | Blob> = null, onLoad?: () => void): void {
         if (this.url) {
             this.releaseInternalTexture();
             this.getScene()!.markAllMaterialsAsDirty(Constants.MATERIAL_TextureDirtyFlag);

+ 2 - 2
src/Materials/effect.ts

@@ -548,7 +548,7 @@ export class Effect implements IDisposable {
 
     /** @hidden */
     public _loadVertexShader(vertex: any, callback: (data: any) => void): void {
-        if (DomManagement.IsWindowObjectExist()) {
+        if (typeof(HTMLElement) !== "undefined") {
             // DOM element ?
             if (vertex instanceof HTMLElement) {
                 var vertexCode = DomManagement.GetDOMTextContent(vertex);
@@ -590,7 +590,7 @@ export class Effect implements IDisposable {
 
     /** @hidden */
     public _loadFragmentShader(fragment: any, callback: (data: any) => void): void {
-        if (DomManagement.IsWindowObjectExist()) {
+        if (typeof(HTMLElement) !== "undefined") {
             // DOM element ?
             if (fragment instanceof HTMLElement) {
                 var fragmentCode = DomManagement.GetDOMTextContent(fragment);

+ 8 - 0
src/Misc/domManagement.ts

@@ -12,6 +12,14 @@ export class DomManagement {
     }
 
     /**
+     * Checks if the navigator object exists
+     * @returns true if the navigator object exists
+     */
+    public static IsNavigatorAvailable(): boolean {
+        return (typeof navigator) !== "undefined";
+    }
+
+    /**
      * Extracts text content from a DOM element hierarchy
      * @param element defines the root element
      * @returns a string

+ 32 - 14
src/Misc/environmentTextureTools.ts

@@ -60,7 +60,7 @@ interface BufferImageData {
  * Defines the specular data enclosed in the file.
  * This corresponds to the version 1 of the data.
  */
-interface EnvironmentTextureSpecularInfoV1 {
+export interface EnvironmentTextureSpecularInfoV1 {
     /**
      * Defines where the specular Payload is located. It is a runtime value only not stored in the file.
      */
@@ -323,22 +323,17 @@ export class EnvironmentTextureTools {
     }
 
     /**
-     * Uploads the texture info contained in the env file to the GPU.
-     * @param texture defines the internal texture to upload to
-     * @param arrayBuffer defines the buffer cotaining the data to load
-     * @param info defines the texture info retrieved through the GetEnvInfo method
-     * @returns a promise
+     * Creates the ArrayBufferViews used for initializing environment texture image data.
+     * @param arrayBuffer the underlying ArrayBuffer to which the views refer
+     * @param info parameters that determine what views will be created for accessing the underlying buffer
+     * @return the views described by info providing access to the underlying buffer
      */
-    public static UploadEnvLevelsAsync(texture: InternalTexture, arrayBuffer: any, info: EnvironmentTextureInfo): Promise<void> {
+    public static CreateImageDataArrayBufferViews(arrayBuffer: any, info: EnvironmentTextureInfo): Array<Array<ArrayBufferView>> {
         if (info.version !== 1) {
             throw new Error(`Unsupported babylon environment map version "${info.version}"`);
         }
 
-        let specularInfo = info.specular as EnvironmentTextureSpecularInfoV1;
-        if (!specularInfo) {
-            // Nothing else parsed so far
-            return Promise.resolve();
-        }
+        const specularInfo = info.specular as EnvironmentTextureSpecularInfoV1;
 
         // Double checks the enclosed info
         let mipmapsCount = Scalar.Log2(info.width);
@@ -347,8 +342,6 @@ export class EnvironmentTextureTools {
             throw new Error(`Unsupported specular mipmaps number "${specularInfo.mipmaps.length}"`);
         }
 
-        texture._lodGenerationScale = specularInfo.lodGenerationScale;
-
         const imageData = new Array<Array<ArrayBufferView>>(mipmapsCount);
         for (let i = 0; i < mipmapsCount; i++) {
             imageData[i] = new Array<ArrayBufferView>(6);
@@ -358,6 +351,31 @@ export class EnvironmentTextureTools {
             }
         }
 
+        return imageData;
+    }
+
+    /**
+     * Uploads the texture info contained in the env file to the GPU.
+     * @param texture defines the internal texture to upload to
+     * @param arrayBuffer defines the buffer cotaining the data to load
+     * @param info defines the texture info retrieved through the GetEnvInfo method
+     * @returns a promise
+     */
+    public static UploadEnvLevelsAsync(texture: InternalTexture, arrayBuffer: any, info: EnvironmentTextureInfo): Promise<void> {
+        if (info.version !== 1) {
+            throw new Error(`Unsupported babylon environment map version "${info.version}"`);
+        }
+
+        const specularInfo = info.specular as EnvironmentTextureSpecularInfoV1;
+        if (!specularInfo) {
+            // Nothing else parsed so far
+            return Promise.resolve();
+        }
+
+        texture._lodGenerationScale = specularInfo.lodGenerationScale;
+
+        const imageData = EnvironmentTextureTools.CreateImageDataArrayBufferViews(arrayBuffer, info);
+
         return EnvironmentTextureTools.UploadLevelsAsync(texture, imageData);
     }
 

+ 2 - 2
src/Misc/fileTools.ts

@@ -77,11 +77,11 @@ export class FileTools {
      * @param offlineProvider offline provider for caching
      * @returns the HTMLImageElement of the loaded image
      */
-    public static LoadImage(input: string | ArrayBuffer | Blob, onLoad: (img: HTMLImageElement) => void, onError: (message?: string, exception?: any) => void, offlineProvider: Nullable<IOfflineProvider>): HTMLImageElement {
+    public static LoadImage(input: string | ArrayBuffer | ArrayBufferView | Blob, onLoad: (img: HTMLImageElement) => void, onError: (message?: string, exception?: any) => void, offlineProvider: Nullable<IOfflineProvider>): HTMLImageElement {
         let url: string;
         let usingObjectURL = false;
 
-        if (input instanceof ArrayBuffer) {
+        if (input instanceof ArrayBuffer || ArrayBuffer.isView(input)) {
             url = URL.createObjectURL(new Blob([input]));
             usingObjectURL = true;
         }

+ 1 - 1
src/Misc/tools.ts

@@ -315,7 +315,7 @@ export class Tools {
         var eventPrefix = "pointer";
 
         // Check if pointer events are supported
-        if (DomManagement.IsWindowObjectExist() && !window.PointerEvent && !navigator.pointerEnabled) {
+        if (DomManagement.IsWindowObjectExist() && !window.PointerEvent && DomManagement.IsNavigatorAvailable() && !navigator.pointerEnabled) {
             eventPrefix = "mouse";
         }
 

+ 5 - 6
src/Shaders/ShadersInclude/bumpFragmentFunctions.fx

@@ -7,16 +7,15 @@
 		uniform mat4 normalMatrix;
 	#endif
 
-	vec3 perturbNormal(mat3 cotangentFrame, vec2 uv, sampler2D textureSampler, float scale)
+	vec3 perturbNormal(mat3 cotangentFrame, vec3 textureSample, float scale)
 	{
-		vec3 map = texture2D(textureSampler, uv).xyz;
-		map = map * 2.0 - 1.0;
+		textureSample = textureSample * 2.0 - 1.0;
 
 		#ifdef NORMALXYSCALE
-			map = normalize(map * vec3(scale, scale, 1.0));
+			textureSample = normalize(textureSample * vec3(scale, scale, 1.0));
 		#endif
 
-		return normalize(cotangentFrame * map);
+		return normalize(cotangentFrame * textureSample);
 	}
 
 	// Thanks to http://www.thetenthplanet.de/archives/1180
@@ -59,7 +58,7 @@
 
 	vec3 perturbNormal(mat3 cotangentFrame, vec2 uv)
 	{
-		return perturbNormal(cotangentFrame, uv, bumpSampler, vBumpInfos.y);
+		return perturbNormal(cotangentFrame, texture2D(bumpSampler, uv).xyz, vBumpInfos.y);
 	}
 
 	// Thanks to http://www.thetenthplanet.de/archives/1180

+ 2 - 2
src/Shaders/ShadersInclude/pbrBRDFFunctions.fx

@@ -15,12 +15,12 @@
 #endif
 
 #ifdef ENVIRONMENTBRDF
-    vec3 getBRDFLookup(float NdotV, float perceptualRoughness, sampler2D brdfSampler) {
+    vec3 getBRDFLookup(float NdotV, float perceptualRoughness) {
         // Indexed on cos(theta) and roughness
         vec2 UV = vec2(NdotV, perceptualRoughness);
         
         // We can find the scale and offset to apply to the specular value.
-        vec4 brdfLookup = texture2D(brdfSampler, UV);
+        vec4 brdfLookup = texture2D(environmentBrdfSampler, UV);
 
         #ifdef ENVIRONMENTBRDF_RGBD
             brdfLookup.rgb = fromRGBD(brdfLookup.rgba);

+ 1 - 1
src/Shaders/ShadersInclude/pbrHelperFunctions.fx

@@ -7,7 +7,7 @@
 
 float convertRoughnessToAverageSlope(float roughness)
 {
-    // Calculate AlphaG as square of roughness; add epsilon to avoid numerical issues
+    // Calculate AlphaG as square of roughness (add epsilon to avoid numerical issues)
     return square(roughness) + MINIMUMVARIANCE;
 }
 

+ 4 - 4
src/Shaders/pbr.fragment.fx

@@ -16,7 +16,7 @@ precision highp float;
 
 // Forces linear space for image processing
 #ifndef FROMLINEARSPACE
-    #define FROMLINEARSPACE;
+    #define FROMLINEARSPACE
 #endif
 
 // Declaration
@@ -662,7 +662,7 @@ void main(void) {
                 clearCoatNormalW = normalize(texture2D(clearCoatBumpSampler, vClearCoatBumpUV + uvOffset).xyz  * 2.0 - 1.0);
                 clearCoatNormalW = normalize(mat3(normalMatrix) * clearCoatNormalW);
             #else
-                clearCoatNormalW = perturbNormal(TBN, vClearCoatBumpUV + uvOffset, clearCoatBumpSampler, vClearCoatBumpInfos.y);
+                clearCoatNormalW = perturbNormal(TBN, texture2D(clearCoatBumpSampler, vClearCoatBumpUV + uvOffset).xyz, vClearCoatBumpInfos.y);
             #endif
         #endif
 
@@ -768,7 +768,7 @@ void main(void) {
     // _____________________________ IBL BRDF + Energy Cons ________________________________
     #if defined(ENVIRONMENTBRDF)
         // BRDF Lookup
-        vec3 environmentBrdf = getBRDFLookup(NdotV, roughness, environmentBrdfSampler);
+        vec3 environmentBrdf = getBRDFLookup(NdotV, roughness);
 
         #ifdef MS_BRDF_ENERGY_CONSERVATION
             vec3 energyConservationFactor = getEnergyConservationFactor(specularEnvironmentR0, environmentBrdf);
@@ -895,7 +895,7 @@ void main(void) {
     #ifdef CLEARCOAT
         #if defined(ENVIRONMENTBRDF) && !defined(REFLECTIONMAP_SKYBOX)
             // BRDF Lookup
-            vec3 environmentClearCoatBrdf = getBRDFLookup(clearCoatNdotV, clearCoatRoughness, environmentBrdfSampler);
+            vec3 environmentClearCoatBrdf = getBRDFLookup(clearCoatNdotV, clearCoatRoughness);
             vec3 clearCoatEnvironmentReflectance = getReflectanceFromBRDFLookup(vec3(vClearCoatRefractionParams.x), environmentClearCoatBrdf);
 
             #ifdef RADIANCEOCCLUSION