Pārlūkot izejas kodu

Merge pull request #9438 from Popov72/WebGPU

WebGPU branch merging
sebavan 4 gadi atpakaļ
vecāks
revīzija
b8238023e5
100 mainītis faili ar 11352 papildinājumiem un 941 dzēšanām
  1. 3 0
      .gitignore
  2. 15 0
      .vscode/launch.json
  3. 1 2
      .vscode/settings.json
  4. 16 1
      Tools/DevLoader/BabylonLoader.js
  5. 6 2
      Tools/Gulp/helpers/gulp-processConstants.js
  6. 5 0
      Tools/Gulp/tasks/gulpTasks-localRun.js
  7. 2 2
      Viewer/src/labs/texture.ts
  8. 40 19
      Viewer/tests/validation/validation.js
  9. 1 1
      dist/preview release/packagesSizeBaseLine.json
  10. 2 1
      dist/preview release/what's new.md
  11. 1 1
      inspector/src/components/actionTabs/tabs/propertyGrids/materials/textures/propertiesBar.tsx
  12. 37 39
      inspector/src/components/actionTabs/tabs/propertyGrids/materials/textures/textureCanvasManager.ts
  13. 1 1
      inspector/src/components/actionTabs/tabs/statisticsTabComponent.tsx
  14. 17 18
      inspector/src/components/sceneExplorer/sceneExplorerComponent.tsx
  15. 11 13
      inspector/src/textureHelper.ts
  16. 2 0
      localDev/index.html
  17. 165 0
      localDevWebGPU/index.html
  18. 56 3
      materialsLibrary/index.html
  19. 2 2
      materialsLibrary/src/cell/cell.fragment.fx
  20. 2 2
      materialsLibrary/src/fire/fire.fragment.fx
  21. 2 2
      materialsLibrary/src/fur/fur.fragment.fx
  22. 2 2
      materialsLibrary/src/gradient/gradient.fragment.fx
  23. 2 2
      materialsLibrary/src/lava/lava.fragment.fx
  24. 2 2
      materialsLibrary/src/mix/mix.fragment.fx
  25. 2 2
      materialsLibrary/src/normal/normal.fragment.fx
  26. 2 2
      materialsLibrary/src/shadowOnly/shadowOnly.fragment.fx
  27. 2 2
      materialsLibrary/src/simple/simple.fragment.fx
  28. 2 2
      materialsLibrary/src/terrain/terrain.fragment.fx
  29. 2 2
      materialsLibrary/src/triPlanar/triplanar.fragment.fx
  30. 5 3
      materialsLibrary/src/water/water.fragment.fx
  31. 1 0
      materialsLibrary/test/index.js
  32. 4 3
      nodeEditor/src/sharedComponents/textureLineComponent.tsx
  33. 8 0
      proceduralTexturesLibrary/src/normalMap/normalMapProceduralTexture.ts
  34. 0 17
      serializers/src/glTF/2.0/glTFExporter.ts
  35. 15 59
      serializers/src/glTF/2.0/glTFMaterialExporter.ts
  36. 56 37
      src/Engines/Extensions/engine.cubeTexture.ts
  37. 4 4
      src/Engines/Extensions/engine.multiRender.ts
  38. 4 2
      src/Engines/Extensions/engine.multiview.ts
  39. 1 0
      src/Engines/Extensions/engine.rawTexture.ts
  40. 9 5
      src/Engines/Extensions/engine.readTexture.ts
  41. 1 1
      src/Engines/Extensions/engine.renderTarget.ts
  42. 3 2
      src/Engines/Extensions/engine.uniformBuffer.ts
  43. 1 1
      src/Engines/Extensions/engine.views.ts
  44. 207 0
      src/Engines/IPipelineContext.ts
  45. 13 8
      src/Engines/Processors/iShaderProcessor.ts
  46. 58 3
      src/Engines/Processors/shaderCodeInliner.ts
  47. 11 11
      src/Engines/Processors/shaderCodeNode.ts
  48. 6 1
      src/Engines/Processors/shaderProcessingOptions.ts
  49. 16 2
      src/Engines/Processors/shaderProcessor.ts
  50. 5 1
      src/Engines/WebGL/webGL2ShaderProcessors.ts
  51. 42 0
      src/Engines/WebGL/webGLHardwareTexture.ts
  52. 467 1
      src/Engines/WebGL/webGLPipelineContext.ts
  53. 4 2
      src/Engines/WebGL/webGLShaderProcessors.ts
  54. 193 0
      src/Engines/WebGPU/webgpuBufferManager.ts
  55. 247 0
      src/Engines/WebGPU/webgpuCacheSampler.ts
  56. 306 0
      src/Engines/WebGPU/webgpuConstants.ts
  57. 70 0
      src/Engines/WebGPU/webgpuHardwareTexture.ts
  58. 475 0
      src/Engines/WebGPU/webgpuPipelineContext.ts
  59. 27 0
      src/Engines/WebGPU/webgpuRenderPassWrapper.ts
  60. 34 0
      src/Engines/WebGPU/webgpuShaderManager.ts
  61. 119 0
      src/Engines/WebGPU/webgpuShaderProcessingContext.ts
  62. 448 0
      src/Engines/WebGPU/webgpuShaderProcessors.ts
  63. 1129 0
      src/Engines/WebGPU/webgpuTextureHelper.ts
  64. 27 0
      src/Engines/constants.ts
  65. 2 0
      src/Engines/depthTextureCreationOptions.ts
  66. 93 57
      src/Engines/engine.ts
  67. 50 0
      src/Engines/engineFeatures.ts
  68. 3 0
      src/Engines/index.ts
  69. 568 21
      src/Engines/nativeEngine.ts
  70. 20 1
      src/Engines/nullEngine.ts
  71. 337 116
      src/Engines/thinEngine.ts
  72. 4376 0
      src/Engines/webgpuEngine.ts
  73. 1 0
      src/LibDeclarations/browser.d.ts
  74. 979 0
      src/LibDeclarations/webgpu.d.ts
  75. 11 7
      src/Lights/Shadows/cascadedShadowGenerator.ts
  76. 43 28
      src/Lights/Shadows/shadowGenerator.ts
  77. 1 1
      src/Lights/light.ts
  78. 2 2
      src/Materials/Background/backgroundMaterial.ts
  79. 4 2
      src/Materials/Node/Blocks/Dual/lightBlock.ts
  80. 2 0
      src/Materials/Node/Blocks/Fragment/perturbNormalBlock.ts
  81. 1 1
      src/Materials/Node/Blocks/Input/inputBlock.ts
  82. 14 5
      src/Materials/Node/Blocks/PBR/pbrMetallicRoughnessBlock.ts
  83. 57 45
      src/Materials/PBR/pbrBaseMaterial.ts
  84. 3 2
      src/Materials/Textures/Filtering/hdrFiltering.ts
  85. 42 26
      src/Materials/Textures/Procedurals/proceduralTexture.ts
  86. 14 4
      src/Materials/Textures/baseTexture.polynomial.ts
  87. 5 4
      src/Materials/Textures/baseTexture.ts
  88. 2 2
      src/Materials/Textures/colorGradingTexture.ts
  89. 10 0
      src/Materials/Textures/hardwareTextureWrapper.ts
  90. 1 1
      src/Materials/Textures/hdrCubeTexture.ts
  91. 27 9
      src/Materials/Textures/internalTexture.ts
  92. 10 0
      src/Materials/Textures/mirrorTexture.ts
  93. 1 1
      src/Materials/Textures/multiRenderTarget.ts
  94. 7 1
      src/Materials/Textures/renderTargetCreationOptions.ts
  95. 41 20
      src/Materials/Textures/renderTargetTexture.ts
  96. 1 1
      src/Materials/Textures/texture.ts
  97. 1 4
      src/Materials/Textures/videoTexture.ts
  98. 155 285
      src/Materials/effect.ts
  99. 49 7
      src/Materials/material.ts
  100. 0 0
      src/Materials/materialHelper.ts

+ 3 - 0
.gitignore

@@ -182,6 +182,9 @@ localDev/src/*
 package-lock.json
 dist/preview release/package/
 
+# local dev WebGPU
+localDevWebGPU/src/*
+
 # viewer dist files
 /Viewer/dist/viewer.js
 /Viewer/dist/viewer.min.js

+ 15 - 0
.vscode/launch.json

@@ -226,6 +226,21 @@
             ]
         },        
         {
+            "name": "Launch Local Dev (Chrome Canary)",
+            "type": "chrome",
+            "request": "launch",
+            "url": "http://localhost:1338/localDevWebGPU/index.html",
+            "webRoot": "${workspaceRoot}/",
+            "sourceMaps": true,
+            "preLaunchTask": "run",
+            "userDataDir": "${workspaceRoot}/.tempChromeCanaryProfileForDebug",
+            "runtimeExecutable": "C:/Users/alexis/AppData/Local/Google/Chrome SxS/Application/Chrome.exe",
+            "runtimeArgs": [
+                "--enable-unsafe-es3-apis",
+                "--enable-unsafe-webgpu"
+            ]
+        },        
+        {
             "name": "Launch Local Dev (Edge)",
             "type": "edge",
             "version": "dev",

+ 1 - 2
.vscode/settings.json

@@ -58,6 +58,5 @@
     },
     "editor.tabSize": 4,
     "typescript.tsdk": "node_modules\\typescript\\lib",
-    "typescript.preferences.importModuleSpecifier": "relative",
-    "cmake.configureOnOpen": false
+    "typescript.preferences.importModuleSpecifier": "relative"
 }

+ 16 - 1
Tools/DevLoader/BabylonLoader.js

@@ -40,6 +40,8 @@ var BABYLONDEVTOOLS;
         var min;
         var babylonJSPath;
 
+        var coreOnly;
+
         var localDevES6FolderName;
         var localDevUMDFolderName;
 
@@ -57,6 +59,7 @@ var BABYLONDEVTOOLS;
                 workerMode = true;
             }
             babylonJSPath = '';
+            coreOnly = false;
         }
 
         Loader.prototype.debugShortcut = function(engine) {
@@ -234,7 +237,14 @@ var BABYLONDEVTOOLS;
                 if (!useDist && module.isCore) {
                     this.loadCoreDev();
                 }
-                else {
+                else if (!coreOnly || module.isCore) {
+                    this.loadLibrary(moduleName, module.libraries[i], module);
+                }
+                // Allow also loaders in CORE.
+                else if (coreOnly && (moduleName === "loaders" ||
+                    moduleName === "inspector" ||
+                    moduleName === "nodeEditor" ||
+                    moduleName === "materialsLibrary")) {
                     this.loadLibrary(moduleName, module.libraries[i], module);
                 }
             }
@@ -282,6 +292,11 @@ var BABYLONDEVTOOLS;
             }
         }
 
+        Loader.prototype.loadCoreOnly = function() {
+            coreOnly = true;
+            return this;
+        }
+
         Loader.prototype.load = function(newCallback) {
             var self = this;
             if (newCallback) {

+ 6 - 2
Tools/Gulp/helpers/gulp-processConstants.js

@@ -33,9 +33,13 @@ function processConstants(sourceCode) {
     }
 
     for (var constant of constantList) {
-        var value = babylonConstants[constant];
         var regex = new RegExp(`(?<![_0-9a-zA-Z])Constants\.${constant}(?![_0-9a-zA-Z])`, "g");
-        sourceCode = sourceCode.replace(regex, value);
+        var value = babylonConstants[constant];
+        if (typeof(value) === "string") {
+            sourceCode = sourceCode.replace(regex, "`" + value + "`");
+        } else  {
+            sourceCode = sourceCode.replace(regex, value);
+        }
     }
 
     return sourceCode;

+ 5 - 0
Tools/Gulp/tasks/gulpTasks-localRun.js

@@ -43,6 +43,11 @@ gulp.task("webserver", function () {
                             req.url = "/Playground/" + req.url.replace(/localDev/ig, "");
                         }
                     }
+                    if (referer.indexOf('/localdevwebgpu/') !== -1 && referer.indexOf(req.originalUrl) === -1) {
+                        if (!fs.existsSync(rootRelativePath + req.originalUrl)) {
+                            req.url = "/Playground/" + req.url.replace(/localdevwebgpu/ig, "");
+                        }
+                    }
                 }
 
                 const pgMath = req.url.match(/\/Playground\/pg\/(.*)/);

+ 2 - 2
Viewer/src/labs/texture.ts

@@ -220,7 +220,7 @@ export class TextureUtils {
             babylonTexture.gammaSpace = false;
 
             let internalTexture = new InternalTexture(scene.getEngine(), InternalTextureSource.CubeRaw);
-            let glTexture = internalTexture._webGLTexture;
+            let glTexture = internalTexture._hardwareTexture?.underlyingResource;
             //babylon properties
             internalTexture.isCube = true;
             internalTexture.generateMipMaps = false;
@@ -352,7 +352,7 @@ export class TextureUtils {
 
         let internalTexture = babylonTexture._texture;
         if (!internalTexture) { return; }
-        let glTexture = internalTexture._webGLTexture;
+        let glTexture = internalTexture._hardwareTexture?.underlyingResource;
         gl.bindTexture(target, glTexture);
 
         if (parameters.magFilter != null) { gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, parameters.magFilter); }

+ 40 - 19
Viewer/tests/validation/validation.js

@@ -51,27 +51,38 @@ function compare(renderData, referenceCanvas) {
     return (differencesCount * 100) / (width * height) > errorRatio;
 }
 
-function getRenderData(canvas, engine) {
+async function getRenderData(canvas, engine) {
     var width = canvas.width;
     var height = canvas.height;
 
-    var renderData = engine.readPixels(0, 0, width, height);
-    var numberOfChannelsByLine = width * 4;
-    var halfHeight = height / 2;
-
-    for (var i = 0; i < halfHeight; i++) {
-        for (var j = 0; j < numberOfChannelsByLine; j++) {
-            var currentCell = j + i * numberOfChannelsByLine;
-            var targetLine = height - i - 1;
-            var targetCell = j + targetLine * numberOfChannelsByLine;
-
-            var temp = renderData[currentCell];
-            renderData[currentCell] = renderData[targetCell];
-            renderData[targetCell] = temp;
-        }
-    }
+    return new Promise((resolve) => {
+        engine.onEndFrameObservable.addOnce(async () => {
+            var renderData = await engine.readPixels(0, 0, width, height);
+            var numberOfChannelsByLine = width * 4;
+            var halfHeight = height / 2;
+
+            for (var i = 0; i < halfHeight; i++) {
+                for (var j = 0; j < numberOfChannelsByLine; j++) {
+                    var currentCell = j + i * numberOfChannelsByLine;
+                    var targetLine = height - i - 1;
+                    var targetCell = j + targetLine * numberOfChannelsByLine;
+
+                    var temp = renderData[currentCell];
+                    renderData[currentCell] = renderData[targetCell];
+                    renderData[targetCell] = temp;
+                }
+            }
+            if (engine.isWebGPU) {
+                for (var i = 0; i < width * height * 4; i += 4) {
+                    var temp = renderData[i + 0];
+                    renderData[i + 0] = renderData[i + 2];
+                    renderData[i + 2] = temp;
+                }
+            }
 
-    return renderData;
+            resolve(renderData);
+        });
+    });
 }
 
 function saveRenderImage(data, canvas) {
@@ -106,9 +117,9 @@ function downloadDataUrlFromJavascript(filename, dataUrl) {
     document.body.removeChild(link);
 }
 
-function evaluate(test, resultCanvas, result, renderImage, index, waitRing, done) {
+async function evaluate(test, resultCanvas, result, renderImage, index, waitRing, done) {
     seed = 100000;
-    var renderData = getRenderData(currentViewer.canvas, currentViewer.engine);
+    var renderData = await getRenderData(currentViewer.canvas, currentViewer.engine);
     var testRes = true;
 
     // gl check
@@ -312,6 +323,16 @@ function init() {
         wasmMSCTranscoder: GetAbsoluteUrl("../../dist/preview%20release/ktx2Transcoders/msc_basis_transcoder.wasm")
     };
 
+    BABYLON.KhronosTextureContainer2.URLConfig = {
+        jsDecoderModule: GetAbsoluteUrl("../../dist/preview%20release/babylon.ktx2Decoder.js"),
+        wasmUASTCToASTC: GetAbsoluteUrl("../../dist/preview%20release/ktx2Transcoders/uastc_astc.wasm"),
+        wasmUASTCToBC7: GetAbsoluteUrl("../../dist/preview%20release/ktx2Transcoders/uastc_bc7.wasm"),
+        wasmUASTCToRGBA_UNORM: GetAbsoluteUrl("../../dist/preview%20release/ktx2Transcoders/uastc_rgba32_unorm.wasm"),
+        wasmUASTCToRGBA_SRGB: GetAbsoluteUrl("../../dist/preview%20release/ktx2Transcoders/uastc_rgba32_srgb.wasm"),
+        jsMSCTranscoder: GetAbsoluteUrl("../../dist/preview%20release/ktx2Transcoders/msc_basis_transcoder.js"),
+        wasmMSCTranscoder: GetAbsoluteUrl("../../dist/preview%20release/ktx2Transcoders/msc_basis_transcoder.wasm")
+    };
+
     viewerElement = document.createElement("babylon");
     document.body.appendChild(viewerElement);
 

+ 1 - 1
dist/preview release/packagesSizeBaseLine.json

@@ -1 +1 @@
-{"thinEngineOnly":119226,"engineOnly":155666,"sceneOnly":524149,"minGridMaterial":673651,"minStandardMaterial":832312}
+{"thinEngineOnly":129620,"engineOnly":166696,"sceneOnly":519874,"minGridMaterial":693862,"minStandardMaterial":854577}

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

@@ -49,4 +49,5 @@
 ## Breaking changes
 
 - Use both `mesh.visibility` and `material.alpha` values to compute the global alpha value used by the soft transparent shadow rendering code. Formerly was only using `mesh.visibility` ([Popov72](https://github.com/Popov72))
-- The `Texture.hasAlpha` property is automatically set by the KTX2 loader if the texture has an alpha channel ([Popov72](https://github.com/Popov72))
+- The `Texture.hasAlpha` property is automatically set by the KTX2 loader if the texture has an alpha channel ([Popov72](https://github.com/Popov72))
+- Depth renderer: don't render mesh if `infiniteDistance = true` or if `material.disableDepthWrite = true` ([Popov72](https://github.com/Popov72))

+ 1 - 1
inspector/src/components/actionTabs/tabs/propertyGrids/materials/textures/propertiesBar.tsx

@@ -84,7 +84,7 @@ export class PropertiesBar extends React.PureComponent<IPropertiesBarProps,IProp
         const {mipLevel, setMipLevel, pixelData, resizeTexture, texture, face, setFace, saveTexture, resetTexture, uploadTexture} = this.props;
         const maxLevels = Math.floor(Math.log2(Math.max(texture.getSize().width, texture.getSize().height)));
         const engine = texture.getScene()!.getEngine();
-        const mipsEnabled = (!texture.noMipmap && (engine.webGLVersion == 2 || engine._gl.getExtension('EXT_shader_texture_lod')));
+        const mipsEnabled = (!texture.noMipmap && engine.getCaps().textureLOD);
         return <div id='properties'>
                 <div className='tab' id='logo-tab'>
                     <img className='icon' src={this._babylonLogo}/>

+ 37 - 39
inspector/src/components/actionTabs/tabs/propertyGrids/materials/textures/textureCanvasManager.ts

@@ -3,7 +3,7 @@ import { Scene } from 'babylonjs/scene';
 import { Vector3, Vector2 } from 'babylonjs/Maths/math.vector';
 import { Color4, Color3 } from 'babylonjs/Maths/math.color';
 import { FreeCamera } from 'babylonjs/Cameras/freeCamera';
-import { Nullable } from 'babylonjs/types'
+import { Nullable } from 'babylonjs/types';
 import { PlaneBuilder } from 'babylonjs/Meshes/Builders/planeBuilder';
 import { Mesh } from 'babylonjs/Meshes/mesh';
 import { Camera } from 'babylonjs/Cameras/camera';
@@ -31,7 +31,6 @@ import { IMetadata } from './textureEditorComponent';
 
 import { canvasShader } from './canvasShader';
 
-
 export interface IPixelData {
     x? : number;
     y? : number;
@@ -100,9 +99,9 @@ export class TextureCanvasManager {
     private readonly MAX_SCALE : number = 10;
 
     private readonly SELECT_ALL_KEY = 'KeyA';
-    private readonly SAVE_KEY ='KeyS';
+    private readonly SAVE_KEY = 'KeyS';
     private readonly RESET_KEY = 'KeyR';
-    private readonly DESELECT_KEY = 'Escape'
+    private readonly DESELECT_KEY = 'Escape';
 
     /** The number of milliseconds between texture updates */
     private readonly PUSH_FREQUENCY = 32;
@@ -165,10 +164,10 @@ export class TextureCanvasManager {
 
         this._3DEngine = new Engine(this._3DCanvas);
         this._3DScene = new Scene(this._3DEngine, {virtual: true});
-        this._3DScene.clearColor = new Color4(0,0,0,0);
+        this._3DScene.clearColor = new Color4(0, 0, 0, 0);
         this._3DCanvasTexture = new HtmlElementTexture('canvas', this._2DCanvas, {engine: this._3DEngine, scene: this._3DScene});
         this._3DCanvasTexture.hasAlpha = true;
-        const cam = new FreeCamera('camera', new Vector3(0,0,-1), this._3DScene);
+        const cam = new FreeCamera('camera', new Vector3(0, 0, -1), this._3DScene);
         cam.mode = Camera.ORTHOGRAPHIC_CAMERA;
         [cam.orthoBottom, cam.orthoLeft, cam.orthoTop, cam.orthoRight] = [-0.5, -0.5, 0.5, 0.5];
         this._3DPlane = PlaneBuilder.CreatePlane('texture', {width: 1, height: 1}, this._3DScene);
@@ -180,14 +179,13 @@ export class TextureCanvasManager {
         mat.emissiveColor = Color3.White();
         this._3DPlane.material = mat;
 
-
         this._planeMaterial = new ShaderMaterial(
             'canvasShader',
             this._scene,
             canvasShader.path,
             canvasShader.options
         );
-        
+
         this.grabOriginalTexture();
 
         this._planeMaterial.setTexture('textureSampler', this._channelsTexture);
@@ -204,8 +202,8 @@ export class TextureCanvasManager {
         this._planeMaterial.setInt('time', 0);
         this._planeMaterial.setFloat('showGrid', 0.0);
         this._plane.material = this._planeMaterial;
-        
-        this._window.addEventListener('keydown', evt => {
+
+        this._window.addEventListener('keydown', (evt) => {
             this._keyMap[evt.code] = true;
             if (evt.code === this.SELECT_ALL_KEY && evt.ctrlKey) {
                 this._setMetadata({
@@ -234,11 +232,11 @@ export class TextureCanvasManager {
                         x2: -1,
                         y2: -1
                     }
-                })
+                });
             }
         });
-        
-        this._window.addEventListener('keyup', evt => {
+
+        this._window.addEventListener('keyup', (evt) => {
             this._keyMap[evt.code] = false;
         });
 
@@ -259,12 +257,12 @@ export class TextureCanvasManager {
                 this._planeMaterial.setFloat('showGrid', 0.0);
             }
             const ratio = this._UICanvas?.width / this._UICanvas?.height;
-            const {x,y} = this._cameraPos;
+            const {x, y} = this._cameraPos;
             this._camera.orthoBottom = y - 1 / this._scale;
             this._camera.orthoTop = y + 1 / this._scale;
             this._camera.orthoLeft =  x - ratio / this._scale;
             this._camera.orthoRight = x + ratio / this._scale;
-        })
+        });
 
         this._scene.onPointerObservable.add((pointerInfo) => {
             switch (pointerInfo.type) {
@@ -295,16 +293,16 @@ export class TextureCanvasManager {
                     if (pointerInfo.pickInfo?.hit) {
                         const pos = this.getMouseCoordinates(pointerInfo);
                         const idx = (pos.x + pos.y * this._size.width) * 4;
-                        this._setPixelData({x: pos.x, y: pos.y, r:this._imageData[idx], g:this._imageData[idx + 1], b:this._imageData[idx + 2], a:this._imageData[idx + 3]});
+                        this._setPixelData({x: pos.x, y: pos.y, r: this._imageData[idx], g: this._imageData[idx + 1], b: this._imageData[idx + 2], a: this._imageData[idx + 3]});
                     } else {
                         this._setPixelData({});
                     }
                     break;
             }
-        })
+        });
 
         this._scene.onKeyboardObservable.add((kbInfo) => {
-            switch(kbInfo.type) {
+            switch (kbInfo.type) {
                 case KeyboardEventTypes.KEYDOWN:
                     this._keyMap[kbInfo.event.key] = true;
                     switch (kbInfo.event.key) {
@@ -323,9 +321,8 @@ export class TextureCanvasManager {
         });
     }
 
-
     public async updateTexture() {
-        if (this._mipLevel !== 0) await this._setMipLevel(0);
+        if (this._mipLevel !== 0) { await this._setMipLevel(0); }
         this._didEdit = true;
         const element = this._editing3D ? this._3DCanvas : this._2DCanvas;
         if (this._editing3D) {
@@ -357,12 +354,13 @@ export class TextureCanvasManager {
         this._onUpdate();
     }
 
-    private pushTexture() {
+    private async pushTexture() {
         if (this._canPush) {
             (this._target as HtmlElementTexture).update((this._originalTexture as Texture).invertY);
             this._target._texture?.updateSize(this._size.width, this._size.height);
             if (this._editing3D) {
-                this._imageData = this._3DEngine.readPixels(0, 0, this._size.width, this._size.height);
+                const bufferView = await this._3DEngine.readPixels(0, 0, this._size.width, this._size.height);
+                this._imageData = new Uint8Array(bufferView.buffer);
             } else {
                 this._imageData = this._2DCanvas.getContext('2d')!.getImageData(0, 0, this._size.width, this._size.height).data;
             }
@@ -380,7 +378,7 @@ export class TextureCanvasManager {
     }
 
     public async startPainting() : Promise<CanvasRenderingContext2D> {
-        if (this._mipLevel != 0) await this._setMipLevel(0);
+        if (this._mipLevel != 0) { await this._setMipLevel(0); }
         let x = 0, y = 0, w = this._size.width, h = this._size.height;
         if (this._metadata.select.x1 != -1) {
             x = this._metadata.select.x1;
@@ -405,9 +403,9 @@ export class TextureCanvasManager {
             h = this._metadata.select.y2 - this._metadata.select.y1;
         }
         let editingAllChannels = true;
-        this._channels.forEach(channel => {
-            if (!channel.editable) editingAllChannels = false;
-        })
+        this._channels.forEach((channel) => {
+            if (!channel.editable) { editingAllChannels = false; }
+        });
         let oldData : Uint8ClampedArray;
         if (!editingAllChannels) {
             oldData = this._2DCanvas.getContext('2d')!.getImageData(x, y, w, h).data;
@@ -417,7 +415,7 @@ export class TextureCanvasManager {
         ctx2D.globalAlpha = 1.0;
         ctx2D.globalCompositeOperation = 'destination-out';
         ctx2D.fillStyle = 'white';
-        ctx2D.fillRect(x,y,w,h);
+        ctx2D.fillRect(x, y, w, h);
         ctx2D.imageSmoothingEnabled = false;
         // If we're not editing all channels, we must process the pixel data
         if (!editingAllChannels) {
@@ -425,7 +423,7 @@ export class TextureCanvasManager {
             const nd = newData.data;
             this._channels.forEach((channel, index) => {
                 if (!channel.editable) {
-                    for(let i = index; i < w * h * 4; i += 4) {
+                    for (let i = index; i < w * h * 4; i += 4) {
                         nd[i] = oldData[i];
                     }
                 }
@@ -459,7 +457,7 @@ export class TextureCanvasManager {
         }
         else {
             channels.forEach(
-                (channel,index) => {
+                (channel, index) => {
                     if (channel.visible !== this._channels[index].visible) {
                         needsRender = true;
                         this._planeMaterial.setFloat(channel.id.toLowerCase(), channel.visible ? 1.0 : 0.0);
@@ -488,7 +486,7 @@ export class TextureCanvasManager {
             this._size.width,
             this._size.height,
             this._face,
-            {R:true, G:true, B:true, A:true},
+            {R: true, G: true, B: true, A: true},
             undefined,
             this._mipLevel
         );
@@ -503,7 +501,7 @@ export class TextureCanvasManager {
         if (pointerInfo.pickInfo?.hit) {
             const x = Math.floor(pointerInfo.pickInfo.getTextureCoordinates()!.x * this._size.width);
             const y = Math.floor((1 - pointerInfo.pickInfo.getTextureCoordinates()!.y) * this._size.height);
-            return new Vector2(x,y);
+            return new Vector2(x, y);
         } else {
             return new Vector2();
         }
@@ -552,7 +550,7 @@ export class TextureCanvasManager {
     }
 
     public set mipLevel(mipLevel : number) {
-        if (this._mipLevel === mipLevel) return;
+        if (this._mipLevel === mipLevel) { return; }
         this._mipLevel = mipLevel;
         this.grabOriginalTexture();
     }
@@ -564,7 +562,7 @@ export class TextureCanvasManager {
 
     public set metadata(metadata: IMetadata) {
         this._metadata = metadata;
-        const {x1,y1,x2,y2} = metadata.select;
+        const {x1, y1, x2, y2} = metadata.select;
         this._planeMaterial.setInt('x1', x1);
         this._planeMaterial.setInt('y1', y1);
         this._planeMaterial.setInt('x2', x2);
@@ -572,11 +570,11 @@ export class TextureCanvasManager {
     }
 
     private makePlane() {
-        if (this._plane) this._plane.dispose();
+        if (this._plane) { this._plane.dispose(); }
         this._plane = PlaneBuilder.CreatePlane("plane", {width: this._size.width, height: this._size.height}, this._scene);
         this._plane.enableEdgesRendering();
         this._plane.edgesWidth = 4.0;
-        this._plane.edgesColor = new Color4(1,1,1,1);
+        this._plane.edgesColor = new Color4(1, 1, 1, 1);
         this._plane.enablePointerMoveEvents = true;
         this._plane.material = this._planeMaterial;
     }
@@ -593,7 +591,7 @@ export class TextureCanvasManager {
     }
 
     public async resize(newSize : ISize) {
-        const data = await TextureHelper.GetTextureDataAsync(this._originalTexture, newSize.width, newSize.height, this._face, {R: true,G: true,B: true,A: true});
+        const data = await TextureHelper.GetTextureDataAsync(this._originalTexture, newSize.width, newSize.height, this._face, {R: true, G: true, B: true, A: true});
         this.setSize(newSize);
         this.paintPixelsOnCanvas(data, this._2DCanvas);
         this.updateTexture();
@@ -627,9 +625,9 @@ export class TextureCanvasManager {
                 extension = ".env";
             }
             var reader = new FileReader();
-            reader.readAsDataURL(blob); 
+            reader.readAsDataURL(blob);
             reader.onloadend = () => {
-                let base64data = reader.result as string;     
+                let base64data = reader.result as string;
 
                 if (extension === '.dds' || extension === '.env') {
                     (this._originalTexture as CubeTexture).updateURL(base64data, extension, () => this.grabOriginalTexture());
@@ -686,4 +684,4 @@ export class TextureCanvasManager {
         this._scene.dispose();
         this._engine.dispose();
     }
-} 
+}

+ 1 - 1
inspector/src/components/actionTabs/tabs/statisticsTabComponent.tsx

@@ -102,7 +102,7 @@ export class StatisticsTabComponent extends PaneComponent {
                 <LineContainerComponent globalState={this.props.globalState} title="SYSTEM INFO">
                     <TextLineComponent label="Resolution" value={engine.getRenderWidth() + "x" + engine.getRenderHeight()} />
                     <TextLineComponent label="Hardware scaling level" value={engine.getHardwareScalingLevel().toString()} />
-                    <TextLineComponent label="WebGL version" value={engine.webGLVersion.toString()} />
+                    <TextLineComponent label="Engine" value={engine.description} />
                     <BooleanLineComponent label="Std derivatives" value={caps.standardDerivatives} />
                     <BooleanLineComponent label="Compressed textures" value={caps.s3tc !== undefined} />
                     <BooleanLineComponent label="Hardware instances" value={caps.instancedArrays} />

+ 17 - 18
inspector/src/components/sceneExplorer/sceneExplorerComponent.tsx

@@ -68,7 +68,6 @@ export class SceneExplorerComponent extends React.Component<ISceneExplorerCompon
     private _onNewSceneObserver: Nullable<Observer<Scene>>;
     private sceneExplorerRef: React.RefObject<Resizable>;
 
-
     private _once = true;
     private _hooked = false;
 
@@ -86,14 +85,14 @@ export class SceneExplorerComponent extends React.Component<ISceneExplorerCompon
             this.setState({
                 scene
             });
-        })
+        });
     }
 
     processMutation() {
         if (this.props.globalState.blockMutationUpdates) {
             return;
         }
-        
+
         setTimeout(() => this.forceUpdate());
     }
 
@@ -106,7 +105,7 @@ export class SceneExplorerComponent extends React.Component<ISceneExplorerCompon
 
         this._onSelectionRenamedObserver = this.props.globalState.onSelectionRenamedObservable.add(() => {
             this.forceUpdate();
-        })
+        });
     }
 
     componentWillUnmount() {
@@ -122,7 +121,7 @@ export class SceneExplorerComponent extends React.Component<ISceneExplorerCompon
             EngineStore.LastCreatedEngine!.onNewSceneAddedObservable.remove(this._onNewSceneAddedObserver);
         }
 
-        if(this._onNewSceneObserver){
+        if (this._onNewSceneObserver) {
             this.props.globalState.onNewSceneObservable.remove(this._onNewSceneObserver);
         }
 
@@ -271,7 +270,7 @@ export class SceneExplorerComponent extends React.Component<ISceneExplorerCompon
         let pipelineContextMenus: { label: string, action: () => void }[] = [];
 
         if (scene.activeCamera) {
-            if (!pipelines.some(p => p.getClassName() === "DefaultRenderingPipeline")) {
+            if (!pipelines.some((p) => p.getClassName() === "DefaultRenderingPipeline")) {
                 pipelineContextMenus.push({
                     label: "Add new Default Rendering Pipeline",
                     action: () => {
@@ -281,7 +280,7 @@ export class SceneExplorerComponent extends React.Component<ISceneExplorerCompon
                 });
             }
 
-            if (!pipelines.some(p => p.getClassName() === "SSAORenderingPipeline")) {
+            if (!pipelines.some((p) => p.getClassName() === "SSAORenderingPipeline")) {
                 pipelineContextMenus.push({
                     label: "Add new SSAO Rendering Pipeline",
                     action: () => {
@@ -291,7 +290,7 @@ export class SceneExplorerComponent extends React.Component<ISceneExplorerCompon
                 });
             }
 
-            if (scene.getEngine().webGLVersion > 1 && !pipelines.some(p => p.getClassName() === "SSAORenderingPipeline")) {
+            if (scene.getEngine().getCaps().drawBuffersExtension && !pipelines.some((p) => p.getClassName() === "SSAORenderingPipeline")) {
                 pipelineContextMenus.push({
                     label: "Add new SSAO2 Rendering Pipeline",
                     action: () => {
@@ -329,7 +328,7 @@ export class SceneExplorerComponent extends React.Component<ISceneExplorerCompon
                         newFreeCamera.setTarget((scene.activeCamera as TargetCamera).getTarget());
                     }
                 }
-    
+
                 this.props.globalState.onSelectionChangedObservable.notifyObservers(newFreeCamera);
             }
         });
@@ -342,14 +341,14 @@ export class SceneExplorerComponent extends React.Component<ISceneExplorerCompon
                 let newStdMaterial = new StandardMaterial("Standard material", scene);
                 this.props.globalState.onSelectionChangedObservable.notifyObservers(newStdMaterial);
             }
-        });        
+        });
         materialsContextMenus.push({
             label: "Add new PBR material",
             action: () => {
                 let newPBRMaterial = new PBRMaterial("PBR material", scene);
                 this.props.globalState.onSelectionChangedObservable.notifyObservers(newPBRMaterial);
             }
-        });        
+        });
         materialsContextMenus.push({
             label: "Add new node material",
             action: () => {
@@ -376,7 +375,7 @@ export class SceneExplorerComponent extends React.Component<ISceneExplorerCompon
                 let newSpriteManager = new SpriteManager("Default sprite manager", "//playground.babylonjs.com/textures/player.png", 500, 64, scene);
                 this.props.globalState.onSelectionChangedObservable.notifyObservers(newSpriteManager);
             }
-        });            
+        });
 
         // Particle systems
         let particleSystemsContextMenus: { label: string, action: () => void }[] = [];
@@ -403,7 +402,7 @@ export class SceneExplorerComponent extends React.Component<ISceneExplorerCompon
         }
 
         return (
-            <div id="tree" onContextMenu={e => e.preventDefault()}>
+            <div id="tree" onContextMenu={(e) => e.preventDefault()}>
                 <SceneExplorerFilterComponent onFilter={(filter) => this.filterContent(filter)} />
                 <SceneTreeItemComponent globalState={this.props.globalState}
                     extensibilityGroups={this.props.extensibilityGroups} selectedEntity={this.state.selectedEntity} scene={scene} onRefresh={() => this.forceUpdate()} onSelectionChangedObservable={this.props.globalState.onSelectionChangedObservable} />
@@ -414,7 +413,7 @@ export class SceneExplorerComponent extends React.Component<ISceneExplorerCompon
                     scene.skeletons.length > 0 &&
                     <TreeItemComponent globalState={this.props.globalState} extensibilityGroups={this.props.extensibilityGroups} selectedEntity={this.state.selectedEntity} items={scene.skeletons} label="Skeletons" offset={1} filter={this.state.filter} />
                 }
-                <TreeItemComponent globalState={this.props.globalState} extensibilityGroups={this.props.extensibilityGroups} selectedEntity={this.state.selectedEntity} items={materials} 
+                <TreeItemComponent globalState={this.props.globalState} extensibilityGroups={this.props.extensibilityGroups} selectedEntity={this.state.selectedEntity} items={materials}
                     contextMenuItems={materialsContextMenus}
                     label="Materials" offset={1} filter={this.state.filter} />
                 <TreeItemComponent globalState={this.props.globalState} extensibilityGroups={this.props.extensibilityGroups} selectedEntity={this.state.selectedEntity} items={textures} label="Textures" offset={1} filter={this.state.filter} />
@@ -425,11 +424,11 @@ export class SceneExplorerComponent extends React.Component<ISceneExplorerCompon
                 <TreeItemComponent globalState={this.props.globalState} extensibilityGroups={this.props.extensibilityGroups}
                     contextMenuItems={pipelineContextMenus}
                     selectedEntity={this.state.selectedEntity} items={pipelines} label="Rendering pipelines" offset={1} filter={this.state.filter} />
-                <TreeItemComponent globalState={this.props.globalState} 
-                    contextMenuItems={particleSystemsContextMenus} 
+                <TreeItemComponent globalState={this.props.globalState}
+                    contextMenuItems={particleSystemsContextMenus}
                     extensibilityGroups={this.props.extensibilityGroups} selectedEntity={this.state.selectedEntity} items={scene.particleSystems} label="Particle systems" offset={1} filter={this.state.filter} />
-                <TreeItemComponent globalState={this.props.globalState} 
-                    contextMenuItems={spriteManagersContextMenus} 
+                <TreeItemComponent globalState={this.props.globalState}
+                    contextMenuItems={spriteManagersContextMenus}
                     forceSubitems={true}
                     extensibilityGroups={this.props.extensibilityGroups} selectedEntity={this.state.selectedEntity} items={scene.spriteManagers} label="Sprite managers" offset={1} filter={this.state.filter} />
                 {

+ 11 - 13
inspector/src/textureHelper.ts

@@ -8,7 +8,6 @@ import { Nullable } from 'babylonjs/types';
 import "./lod";
 import "./lodCube";
 
-
 export interface TextureChannelsToDisplay {
     R: boolean;
     G: boolean;
@@ -18,7 +17,7 @@ export interface TextureChannelsToDisplay {
 
 export class TextureHelper {
 
-    private static _ProcessAsync(texture: BaseTexture, width: number, height: number, face: number, channels: TextureChannelsToDisplay, lod: number, globalState: Nullable<GlobalState>, resolve: (result: Uint8Array) => void, reject: () => void) {
+    private static async _ProcessAsync(texture: BaseTexture, width: number, height: number, face: number, channels: TextureChannelsToDisplay, lod: number, globalState: Nullable<GlobalState>, resolve: (result: Uint8Array) => void, reject: () => void) {
         var scene = texture.getScene()!;
         var engine = scene.getEngine();
 
@@ -38,8 +37,6 @@ export class TextureHelper {
             lodPostProcess = new PostProcess("lodCube", "lodCube", ["lod"], null, 1.0, null, Texture.NEAREST_NEAREST_MIPNEAREST, engine, false, faceDefines[face]);
         }
 
-        
-
         if (!lodPostProcess.getEffect().isReady()) {
             // Try again later
             lodPostProcess.dispose();
@@ -79,19 +76,20 @@ export class TextureHelper {
             var halfHeight = height / 2;
 
             //Reading datas from WebGL
-            var data = engine.readPixels(0, 0, width, height);
+            const bufferView = await engine.readPixels(0, 0, width, height);
+            const data = new Uint8Array(bufferView.buffer);
 
             if (!channels.R || !channels.G || !channels.B || !channels.A) {
                 for (var i = 0; i < width * height * 4; i += 4) {
                     // If alpha is the only channel, just display alpha across all channels
                     if (channels.A && !channels.R && !channels.G && !channels.B) {
-                        data[i] = data[i+3];
-                        data[i+1] = data[i+3];
-                        data[i+2] = data[i+3];
-                        data[i+3] = 255;
+                        data[i] = data[i + 3];
+                        data[i + 1] = data[i + 3];
+                        data[i + 2] = data[i + 3];
+                        data[i + 3] = 255;
                         continue;
                     }
-                    let r = data[i], g = data[i+1], b = data[i+2], a = data[i+3];
+                    let r = data[i], g = data[i + 1], b = data[i + 2], a = data[i + 3];
                     // If alpha is not visible, make everything 100% alpha
                     if (!channels.A) {
                         a = 255;
@@ -145,7 +143,7 @@ export class TextureHelper {
                     }
                 }
             }
-            
+
             resolve(data);
 
             // Unbind
@@ -156,7 +154,7 @@ export class TextureHelper {
 
         rtt.dispose();
         lodPostProcess.dispose();
-        
+
         if (globalState) {
             globalState.blockMutationUpdates = false;
         }
@@ -169,7 +167,7 @@ export class TextureHelper {
                     this._ProcessAsync(texture, width, height, face, channels, lod, globalState || null, resolve, reject);
                 });
                 return;
-            }        
+            }
 
             this._ProcessAsync(texture, width, height, face, channels, lod, globalState || null, resolve, reject);
         });

+ 2 - 0
localDev/index.html

@@ -112,6 +112,8 @@
                         engine = new BABYLON.Engine(canvas, true, { premultipliedAlpha: false, stencil: true, disableWebGL2Support: false, preserveDrawingBuffer: true });
                     }
 
+                    //engine.getCaps().textureAnisotropicFilterExtension = false;
+
                     BABYLONDEVTOOLS.Loader.debugShortcut(engine);
 
                     // call the scene creation from the js.

+ 165 - 0
localDevWebGPU/index.html

@@ -0,0 +1,165 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+    <title>Local Development</title>
+
+    <script src="https://code.jquery.com/pep/0.4.2/pep.min.js"></script>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.6.2/dat.gui.min.js"></script>
+    <script src="../dist/preview%20release/cannon.js"></script>
+    <script src="../dist/preview%20release/Oimo.js"></script>
+    <script src="../dist/preview%20release/ammo.js"></script>
+    <script src="../dist/preview%20release/gltf_validator.js"></script>
+    <script src="../Tools/DevLoader/BabylonLoader.js"></script>
+
+    <style>
+        html,
+        body {
+            width: 100%;
+            height: 100%;
+            padding: 0;
+            margin: 0;
+            overflow: hidden;
+        }
+
+        #renderCanvas {
+            width: 100%;
+            height: 100%;
+            display: block;
+            font-size: 0;
+        }
+
+        #fps {
+            position: absolute;
+            background-color: black;
+            border: 2px solid red;
+            text-align: center;
+            font-size: 16px;
+            color: white;
+            top: 15px;
+            right: 10px;
+            width: 60px;
+            height: 20px;
+            z-index: 10;
+        }
+
+        @font-face {
+            font-family: BabylonJSglyphs;
+           /* src: url("http://www.killer-squid.com/fonts/BabylonJSglyphs.otf"); */
+            src: local("BabylonJSglyphs");
+        }
+        
+    </style>
+</head>
+
+<body>
+    <div id="fps">0</div>
+    <canvas id="renderCanvas" touch-action="none" width="1024" height="768"></canvas>
+
+    <script>
+        var canvas = document.getElementById("renderCanvas");
+        var divFps = document.getElementById("fps");
+
+        // Global to simulate PG.
+        var engine = null;
+
+        // Allow querystring to navigate easily in debug in local samples.
+        var indexjs = '/localDev/src/index';
+        var sampleSearch = /sample=([0-9]+)/i;
+        var matches = null;
+        if ((matches = sampleSearch.exec(window.location)) !== null) {
+            indexjs += '.';
+            indexjs += matches[1];
+        }
+        indexjs += '.js';
+
+        // Load the scripts + map file to allow vscode debug.
+        BABYLONDEVTOOLS.Loader
+            .require(indexjs)
+            .load(function() {
+                if (BABYLON.Engine.isSupported()) {
+                    var onLoad = (engine) => {
+                        // call the scene creation from the js.
+                        if (typeof delayCreateScene !== "undefined") {
+                            var scene = delayCreateScene(engine);
+
+                            if (scene) {
+                                // Register a render loop to repeatedly render the scene
+
+                                engine.runRenderLoop(function() {
+                                    if (scene.activeCamera) {
+                                        scene.render();
+                                    }
+                                    divFps.innerHTML = engine.getFps().toFixed() + " fps";
+                                });
+                            }
+                        }
+                        else {
+                            var scene = createScene(engine);
+
+                            if (scene) {
+
+                                var processCurrentScene = function(scene) {
+                                    engine.runRenderLoop(function() {
+                                        scene.render();
+                                        divFps.innerHTML = engine.getFps().toFixed() + " fps";
+                                    });
+                                }
+
+                                if (scene.then) {
+                                    // Handle if createScene returns a promise
+                                    scene.then(function(currentScene) {
+                                        processCurrentScene(currentScene);
+                                    }).catch(function(e) {
+                                        console.error(e);
+                                        onError();
+                                    });
+                                } else {
+                                    // Register a render loop to repeatedly render the scene
+                                    processCurrentScene(scene);
+                                }
+                            }
+                        }
+
+                        // Resize
+                        window.addEventListener("resize", function() {
+                            engine.resize();
+                        });
+                    }
+
+                    var glslangOptions = { 
+                        jsPath: "/dist/preview release/glslang/glslang.js",
+                        wasmPath: "/dist/preview release/glslang/glslang.wasm"
+                    };
+
+                    if (typeof createEngine !== "undefined") {
+                        engine = createEngine();
+                        engine.initAsync(glslangOptions).then(() => onLoad(engine));
+                    } else {
+                        engine = new BABYLON.WebGPUEngine(canvas, {
+                            deviceDescriptor: {
+                                extensions: [
+                                    "texture-compression-bc",
+                                    "timestamp-query",
+                                    "pipeline-statistics-query",
+                                    "depth-clamping",
+                                    "depth24unorm-stencil8",
+                                    "depth32float-stencil8"
+                                ]
+                            },
+                            enableGPUDebugMarkers: true,
+                            antialiasing: true,
+                        });
+                        engine.initAsync(glslangOptions).then(() => onLoad(engine));
+                    }
+
+                    BABYLONDEVTOOLS.Loader.debugShortcut(engine);
+                }
+                else {
+                    alert('BabylonJS is not supported.')
+                }
+            });
+    </script>
+</body>
+
+</html>

+ 56 - 3
materialsLibrary/index.html

@@ -56,11 +56,52 @@
 	<script src="test/addMix.js"></script>
 	
 	<script>
+    function createEngine(engineName, canvas) {
+        if (engineName === "webgpu") {
+            const glslangOptions = { 
+                jsPath: "../dist/preview%20release/glslang/glslang.js",
+                wasmPath: "../dist/preview%20release/glslang/glslang.wasm"
+            };
+
+            engine = new BABYLON.WebGPUEngine(canvas, {
+                deviceDescriptor: {
+                    extensions: [
+                        "texture-compression-bc",
+                        "timestamp-query",
+                        "pipeline-statistics-query",
+                        "depth-clamping",
+                        "depth24unorm-stencil8",
+                        "depth32float-stencil8"
+                    ]
+                },
+                antialiasing: true,
+            });
+            return new Promise((resolve) => {
+                engine.initAsync(glslangOptions).then(() => resolve(engine));
+            });
+        } else {
+            engine = new BABYLON.Engine(canvas, true, { disableWebGL2Support: engineName === "webgl1" ? true : false });
+            return Promise.resolve(engine);
+        }
+    }
+
 	var backgroundSkybox = null;
-	BABYLONDEVTOOLS.Loader.load(function() {
+	BABYLONDEVTOOLS.Loader.load(async function() {
 		if (BABYLON.Engine.isSupported()) {
-			var canvas = document.getElementById("renderCanvas");
-			var engine = new BABYLON.Engine(canvas, true);
+            var url = document.location.href;
+            var engineName = options.engine;
+            var canvas = document.getElementById("renderCanvas");
+            
+            var idx = url.indexOf("engine=");
+            if (idx >= 0) {
+                idx += 7;
+                const idx2 = url.substring(idx).search(/\W+/g);
+                engineName = url.substring(idx, idx + (idx2 >= 0 ? idx2 : url.length));
+            }
+
+            options.engine = engineName;
+
+			var engine = await createEngine(engineName, canvas);
 			BABYLONDEVTOOLS.Loader.debugShortcut(engine);
 
 			var divFps = document.getElementById("fps");
@@ -224,6 +265,18 @@
 				sphere.material = std;				
 				sphere.receiveShadows = true;
 
+                gui.add(options, 'engine', { 'WebGL 1': 'webgl1', 'WebGL 2': 'webgl2', 'WebGPU': 'webgpu' }).onFinishChange(function () {
+                    const newEngine = options.engine;
+                    let url = document.location.href;
+                    const idx = url.indexOf("engine=");
+                    if (idx < 0) {
+                        url += "&engine=" + newEngine;
+                    } else {
+                        url = url.replace(/engine=\w+/g, "engine=" + newEngine);
+                    }
+                    document.location.href = url;
+                });
+
 				gui.add(options, 'material', ['standard', 'simple', 'water', 'fire', 'lava', 'normal', 'terrain', 'pbr', 'pbrmetallicroughness', 'pbrspecularglossiness', 'fur', 'triPlanar', 'gradient', 'sky', 'grid', 'shadowOnly', 'cell', 'background', 'mix']).onFinishChange(function () {
 					water.enableRenderTargets(false);
 					skybox.material = skyboxMaterial;

+ 2 - 2
materialsLibrary/src/cell/cell.fragment.fx

@@ -1,7 +1,7 @@
 precision highp float;
 
 // Constants
-uniform vec3 vEyePosition;
+uniform vec4 vEyePosition;
 uniform vec4 vDiffuseColor;
 
 // Input
@@ -90,7 +90,7 @@ void main(void)
 {
 #include<clipPlaneFragment>
 
-	vec3 viewDirectionW = normalize(vEyePosition - vPositionW);
+	vec3 viewDirectionW = normalize(vEyePosition.xyz - vPositionW);
 
 	// Base color
 	vec4 baseColor = vec4(1., 1., 1., 1.);

+ 2 - 2
materialsLibrary/src/fire/fire.fragment.fx

@@ -1,7 +1,7 @@
 precision highp float;
 
 // Constants
-uniform vec3 vEyePosition;
+uniform vec4 vEyePosition;
 
 // Input
 varying vec3 vPositionW;
@@ -41,7 +41,7 @@ void main(void) {
 	// Clip plane
 #include<clipPlaneFragment>
 
-	vec3 viewDirectionW = normalize(vEyePosition - vPositionW);
+	vec3 viewDirectionW = normalize(vEyePosition.xyz - vPositionW);
 
 	// Base color
 	vec4 baseColor = vec4(1., 1., 1., 1.);

+ 2 - 2
materialsLibrary/src/fur/fur.fragment.fx

@@ -1,7 +1,7 @@
 precision highp float;
 
 // Constants
-uniform vec3 vEyePosition;
+uniform vec4 vEyePosition;
 uniform vec4 vDiffuseColor;
 
 // Input
@@ -54,7 +54,7 @@ void main(void) {
 	// Clip plane
 	#include<clipPlaneFragment>
 	
-	vec3 viewDirectionW = normalize(vEyePosition - vPositionW);
+	vec3 viewDirectionW = normalize(vEyePosition.xyz - vPositionW);
 
 	// Base color
 	vec4 baseColor = furColor;

+ 2 - 2
materialsLibrary/src/gradient/gradient.fragment.fx

@@ -1,7 +1,7 @@
 precision highp float;
 
 // Constants
-uniform vec3 vEyePosition;
+uniform vec4 vEyePosition;
 
 // Gradient variables
 uniform vec4 topColor;
@@ -43,7 +43,7 @@ varying vec4 vColor;
 void main(void) {
 #include<clipPlaneFragment>
 
-	vec3 viewDirectionW = normalize(vEyePosition - vPositionW);
+	vec3 viewDirectionW = normalize(vEyePosition.xyz - vPositionW);
 
     float h = vPosition.y * scale + offset;
     float mysmoothness = clamp(smoothness, 0.01, max(smoothness, 10.));

+ 2 - 2
materialsLibrary/src/lava/lava.fragment.fx

@@ -1,7 +1,7 @@
 precision highp float;
 
 // Constants
-uniform vec3 vEyePosition;
+uniform vec4 vEyePosition;
 uniform vec4 vDiffuseColor;
 
 // Input
@@ -60,7 +60,7 @@ float random( vec3 scale, float seed ){
 void main(void) {
 #include<clipPlaneFragment>
 
-	vec3 viewDirectionW = normalize(vEyePosition - vPositionW);
+	vec3 viewDirectionW = normalize(vEyePosition.xyz - vPositionW);
 
 	// Base color
 	vec4 baseColor = vec4(1., 1., 1., 1.);

+ 2 - 2
materialsLibrary/src/mix/mix.fragment.fx

@@ -1,7 +1,7 @@
 precision highp float;
 
 // Constants
-uniform vec3 vEyePosition;
+uniform vec4 vEyePosition;
 uniform vec4 vDiffuseColor;
 
 #ifdef SPECULARTERM
@@ -71,7 +71,7 @@ void main(void) {
 	// Clip plane
 	#include<clipPlaneFragment>
 
-	vec3 viewDirectionW = normalize(vEyePosition - vPositionW);
+	vec3 viewDirectionW = normalize(vEyePosition.xyz - vPositionW);
 
 	// Base color
 	vec4 finalMixColor = vec4(1., 1., 1., 1.);

+ 2 - 2
materialsLibrary/src/normal/normal.fragment.fx

@@ -1,7 +1,7 @@
 precision highp float;
 
 // Constants
-uniform vec3 vEyePosition;
+uniform vec4 vEyePosition;
 uniform vec4 vDiffuseColor;
 
 // Input
@@ -41,7 +41,7 @@ uniform vec2 vDiffuseInfos;
 void main(void) {
 #include<clipPlaneFragment>
 
-	vec3 viewDirectionW = normalize(vEyePosition - vPositionW);
+	vec3 viewDirectionW = normalize(vEyePosition.xyz - vPositionW);
 
 	// Base color
 	vec4 baseColor = vec4(1., 1., 1., 1.);

+ 2 - 2
materialsLibrary/src/shadowOnly/shadowOnly.fragment.fx

@@ -1,7 +1,7 @@
 precision highp float;
 
 // Constants
-uniform vec3 vEyePosition;
+uniform vec4 vEyePosition;
 uniform float alpha;
 uniform vec3 shadowColor;
 
@@ -29,7 +29,7 @@ varying vec3 vNormalW;
 void main(void) {
 #include<clipPlaneFragment>
 
-	vec3 viewDirectionW = normalize(vEyePosition - vPositionW);
+	vec3 viewDirectionW = normalize(vEyePosition.xyz - vPositionW);
 
 	// Normal
 #ifdef NORMAL

+ 2 - 2
materialsLibrary/src/simple/simple.fragment.fx

@@ -1,7 +1,7 @@
 precision highp float;
 
 // Constants
-uniform vec3 vEyePosition;
+uniform vec4 vEyePosition;
 uniform vec4 vDiffuseColor;
 
 // Input
@@ -39,7 +39,7 @@ uniform vec2 vDiffuseInfos;
 void main(void) {
 #include<clipPlaneFragment>
 
-	vec3 viewDirectionW = normalize(vEyePosition - vPositionW);
+	vec3 viewDirectionW = normalize(vEyePosition.xyz - vPositionW);
 
 	// Base color
 	vec4 baseColor = vec4(1., 1., 1., 1.);

+ 2 - 2
materialsLibrary/src/terrain/terrain.fragment.fx

@@ -1,7 +1,7 @@
 precision highp float;
 
 // Constants
-uniform vec3 vEyePosition;
+uniform vec4 vEyePosition;
 uniform vec4 vDiffuseColor;
 
 #ifdef SPECULARTERM
@@ -99,7 +99,7 @@ void main(void) {
 	// Clip plane
 	#include<clipPlaneFragment>
 
-	vec3 viewDirectionW = normalize(vEyePosition - vPositionW);
+	vec3 viewDirectionW = normalize(vEyePosition.xyz - vPositionW);
 
 	// Base color
 	vec4 baseColor = vec4(1., 1., 1., 1.);

+ 2 - 2
materialsLibrary/src/triPlanar/triplanar.fragment.fx

@@ -1,7 +1,7 @@
 precision highp float;
 
 // Constants
-uniform vec3 vEyePosition;
+uniform vec4 vEyePosition;
 uniform vec4 vDiffuseColor;
 
 #ifdef SPECULARTERM
@@ -59,7 +59,7 @@ void main(void) {
 	// Clip plane
 	#include<clipPlaneFragment>
 
-	vec3 viewDirectionW = normalize(vEyePosition - vPositionW);
+	vec3 viewDirectionW = normalize(vEyePosition.xyz - vPositionW);
 
 	// Base color
 	vec4 baseColor = vec4(0., 0., 0., 1.);

+ 5 - 3
materialsLibrary/src/water/water.fragment.fx

@@ -5,7 +5,7 @@
 precision highp float;
 
 // Constants
-uniform vec3 vEyePosition;
+uniform vec4 vEyePosition;
 uniform vec4 vDiffuseColor;
 
 #ifdef SPECULARTERM
@@ -38,7 +38,9 @@ varying vec4 vColor;
 // Samplers
 #ifdef BUMP
 varying vec2 vNormalUV;
-varying vec2 vNormalUV2;
+#ifdef BUMPSUPERIMPOSE
+    varying vec2 vNormalUV2;
+#endif
 uniform sampler2D normalSampler;
 uniform vec2 vNormalInfos;
 #endif
@@ -76,7 +78,7 @@ void main(void) {
 	// Clip plane
     #include<clipPlaneFragment>
 
-	vec3 viewDirectionW = normalize(vEyePosition - vPositionW);
+	vec3 viewDirectionW = normalize(vEyePosition.xyz - vPositionW);
 
 	// Base color
 	vec4 baseColor = vec4(1., 1., 1., 1.);

+ 1 - 0
materialsLibrary/test/index.js

@@ -1,6 +1,7 @@
 //UI
 var gui = new dat.GUI();
 var options = {
+    engine: "webgl2",
 	material: "standard",
 	mesh: "sphere",
 	hemisphericLight: true,

+ 4 - 3
nodeEditor/src/sharedComponents/textureLineComponent.tsx

@@ -56,11 +56,11 @@ export class TextureLineComponent extends React.Component<ITextureLineComponentP
         TextureLineComponent.UpdatePreview(this.canvasRef.current as HTMLCanvasElement, this.props.texture, this.props.width, this.state, undefined, this.props.globalState);
     }
 
-    public static UpdatePreview(previewCanvas: HTMLCanvasElement, texture: BaseTexture, width: number, options: ITextureLineComponentState, onReady?: ()=> void, globalState?: any) {
+    public static async UpdatePreview(previewCanvas: HTMLCanvasElement, texture: BaseTexture, width: number, options: ITextureLineComponentState, onReady?: () => void, globalState?: any) {
         if (!texture.isReady() && texture._texture) {
             texture._texture.onLoadedObservable.addOnce(() => {
                 TextureLineComponent.UpdatePreview(previewCanvas, texture, width, options, onReady, globalState);
-            })
+            });
         }
         var scene = texture.getScene()!;
         var engine = scene.getEngine();
@@ -111,7 +111,8 @@ export class TextureLineComponent extends React.Component<ITextureLineComponentP
             var halfHeight = height / 2;
 
             //Reading datas from WebGL
-            var data = engine.readPixels(0, 0, width, height);
+            const bufferView = await engine.readPixels(0, 0, width, height);
+            const data = new Uint8Array(bufferView.buffer);
 
             if (!texture.isCube) {
                 if (!options.displayRed || !options.displayGreen || !options.displayBlue) {

+ 8 - 0
proceduralTexturesLibrary/src/normalMap/normalMapProceduralTexture.ts

@@ -30,6 +30,14 @@ export class NormalMapProceduralTexture extends ProceduralTexture {
         this.updateShaderUniforms();
     }
 
+    public isReady(): boolean {
+        if (!this._baseTexture || !this._baseTexture.isReady()) {
+            return false;
+        }
+
+        return super.isReady();
+    }
+
     @serializeAsTexture()
     public get baseTexture(): Texture {
         return this._baseTexture;

+ 0 - 17
serializers/src/glTF/2.0/glTFExporter.ts

@@ -28,7 +28,6 @@ import { IExportOptions } from "./glTFSerializer";
 import { _GLTFUtilities } from "./glTFUtilities";
 import { GLTFData } from "./glTFData";
 import { _GLTFAnimation } from "./glTFAnimation";
-import { Viewport } from 'babylonjs/Maths/math.viewport';
 import { Epsilon } from 'babylonjs/Maths/math.constants';
 
 /**
@@ -355,22 +354,6 @@ export class _Exporter {
         return true;
     }
 
-    /**
-     * Lazy load a local engine
-     */
-    public _getLocalEngine(): Engine {
-        if (!this._localEngine) {
-            const localCanvas = document.createElement('canvas');
-            localCanvas.id = "WriteCanvas";
-            localCanvas.width = 2048;
-            localCanvas.height = 2048;
-            this._localEngine = new Engine(localCanvas, true, { premultipliedAlpha: Tools.IsSafari(), preserveDrawingBuffer: true });
-            this._localEngine.setViewport(new Viewport(0, 0, 1, 1));
-        }
-
-        return this._localEngine;
-    }
-
     private reorderIndicesBasedOnPrimitiveMode(submesh: SubMesh, primitiveMode: number, babylonIndices: IndicesArray, byteOffset: number, binaryWriter: _BinaryWriter) {
         switch (primitiveMode) {
             case Material.TriangleFillMode: {

+ 15 - 59
serializers/src/glTF/2.0/glTFMaterialExporter.ts

@@ -16,7 +16,6 @@ import { PBRBaseSimpleMaterial } from "babylonjs/Materials/PBR/pbrBaseSimpleMate
 import { PBRMaterial } from "babylonjs/Materials/PBR/pbrMaterial";
 import { PBRMetallicRoughnessMaterial } from "babylonjs/Materials/PBR/pbrMetallicRoughnessMaterial";
 import { PBRSpecularGlossinessMaterial } from "babylonjs/Materials/PBR/pbrSpecularGlossinessMaterial";
-import { PostProcess } from "babylonjs/PostProcesses/postProcess";
 import { Scene } from "babylonjs/scene";
 
 import { _Exporter } from "./glTFExporter";
@@ -492,66 +491,22 @@ export class _GLTFMaterialExporter {
      * @returns base64 image string
      */
     private _createBase64FromCanvasAsync(buffer: Uint8Array | Float32Array, width: number, height: number, mimeType: ImageMimeType): Promise<string> {
-        return new Promise<string>((resolve, reject) => {
-            let hostingScene: Scene;
-
+        return new Promise<string>(async (resolve, reject) => {
             const textureType = Constants.TEXTURETYPE_UNSIGNED_INT;
-            const engine = this._exporter._getLocalEngine();
 
-            hostingScene = new Scene(engine);
+            const hostingScene = this._exporter._babylonScene;
+            const engine = hostingScene.getEngine();
 
             // Create a temporary texture with the texture buffer data
             const tempTexture = engine.createRawTexture(buffer, width, height, Constants.TEXTUREFORMAT_RGBA, false, true, Texture.NEAREST_SAMPLINGMODE, null, textureType);
-            const postProcess = new PostProcess("pass", "pass", null, null, 1, null, Texture.NEAREST_SAMPLINGMODE, engine, false, undefined, Constants.TEXTURETYPE_UNSIGNED_INT, undefined, null, false);
-            postProcess.getEffect().executeWhenCompiled(() => {
-                postProcess.onApply = (effect) => {
-                    effect._bindTexture("textureSampler", tempTexture);
-                };
-
-                // Set the size of the texture
-                engine.setSize(width, height);
-                hostingScene.postProcessManager.directRender([postProcess], null);
-                postProcess.dispose();
-                tempTexture.dispose();
-
-                // Read data from WebGL
-                const canvas0 = engine.getRenderingCanvas();
 
-                let canvas: Nullable<HTMLCanvasElement> = document.createElement("canvas");
+            await TextureTools.ApplyPostProcess("pass", tempTexture, hostingScene, textureType, Constants.TEXTURE_NEAREST_SAMPLINGMODE, Constants.TEXTUREFORMAT_RGBA);
 
-                canvas.width = canvas0?.width ?? 0;
-                canvas.height = canvas0?.height ?? 0;
+            const data = await engine._readTexturePixels(tempTexture, width, height);
 
-                var destCtx = canvas.getContext('2d');
-                destCtx!.drawImage(canvas0!, 0, 0);
+            const base64: string = await (Tools.DumpDataAsync(width, height, data, mimeType, undefined, true, false) as Promise<string>);
 
-                if (canvas) {
-                    if (!canvas.toBlob) { // fallback for browsers without "canvas.toBlob"
-                        const dataURL = canvas.toDataURL();
-                        resolve(dataURL);
-                    }
-                    else {
-                        Tools.ToBlob(canvas, (blob) => {
-                            canvas = null;
-                            if (blob) {
-                                let fileReader = new FileReader();
-                                fileReader.onload = (event: any) => {
-                                    let base64String = event.target.result as string;
-                                    hostingScene.dispose();
-                                    resolve(base64String);
-                                };
-                                fileReader.readAsDataURL(blob);
-                            }
-                            else {
-                                reject("gltfMaterialExporter: Failed to get blob from image canvas!");
-                            }
-                        }, mimeType);
-                    }
-                }
-                else {
-                    reject("Engine is missing a canvas!");
-                }
-            });
+            resolve(base64);
         });
     }
 
@@ -649,7 +604,7 @@ export class _GLTFMaterialExporter {
      * @param mimeType the mime type to use for the texture
      * @returns pbr metallic roughness interface or null
      */
-    private _convertSpecularGlossinessTexturesToMetallicRoughnessAsync(diffuseTexture: BaseTexture, specularGlossinessTexture: BaseTexture, factors: _IPBRSpecularGlossiness, mimeType: ImageMimeType): Promise<_IPBRMetallicRoughness> {
+    private async _convertSpecularGlossinessTexturesToMetallicRoughnessAsync(diffuseTexture: BaseTexture, specularGlossinessTexture: BaseTexture, factors: _IPBRSpecularGlossiness, mimeType: ImageMimeType): Promise<_IPBRMetallicRoughness> {
         let promises = [];
         if (!(diffuseTexture || specularGlossinessTexture)) {
             return Promise.reject('_ConvertSpecularGlosinessTexturesToMetallicRoughness: diffuse and specular glossiness textures are not defined!');
@@ -667,8 +622,8 @@ export class _GLTFMaterialExporter {
             const width = diffuseSize.width;
             const height = diffuseSize.height;
 
-            let diffusePixels = resizedTextures.texture1.readPixels();
-            let specularPixels = resizedTextures.texture2.readPixels();
+            let diffusePixels = await resizedTextures.texture1.readPixels();
+            let specularPixels = await resizedTextures.texture2.readPixels();
 
             if (diffusePixels) {
                 diffuseBuffer = this._convertPixelArrayToFloat32(diffusePixels);
@@ -1164,8 +1119,9 @@ export class _GLTFMaterialExporter {
         return this._finishMaterial(promises, glTFMaterial, babylonPBRMaterial, mimeType);
     }
 
-    private getPixelsFromTexture(babylonTexture: BaseTexture): Nullable<Uint8Array | Float32Array> {
-        const pixels = babylonTexture.textureType === Constants.TEXTURETYPE_UNSIGNED_INT ? babylonTexture.readPixels() as Uint8Array : babylonTexture.readPixels() as Float32Array;
+    private getPixelsFromTexture(babylonTexture: BaseTexture): Promise<Nullable<Uint8Array | Float32Array>> {
+        // TODO WEBGPU remove the as unknown cast once using the new babylonjs package to compile the glTF material exporter
+        const pixels = babylonTexture.textureType === Constants.TEXTURETYPE_UNSIGNED_INT ? babylonTexture.readPixels() as unknown as Promise<Uint8Array> : babylonTexture.readPixels() as unknown as Promise<Float32Array>;
         return pixels;
     }
 
@@ -1190,13 +1146,13 @@ export class _GLTFMaterialExporter {
     }
 
     public _exportTextureInfoAsync(babylonTexture: BaseTexture, mimeType: ImageMimeType): Promise<Nullable<ITextureInfo>> {
-        return Promise.resolve().then(() => {
+        return Promise.resolve().then(async () => {
             const textureUid = babylonTexture.uid;
             if (textureUid in this._textureMap) {
                 return this._textureMap[textureUid];
             }
             else {
-                const pixels = this.getPixelsFromTexture(babylonTexture);
+                const pixels = await this.getPixelsFromTexture(babylonTexture);
                 if (!pixels) {
                     return null;
                 }

+ 56 - 37
src/Engines/Extensions/engine.cubeTexture.ts

@@ -5,6 +5,7 @@ import { Nullable } from '../../types';
 import { Scene } from '../../scene';
 import { IInternalTextureLoader } from '../../Materials/Textures/internalTextureLoader';
 import { FileTools } from '../../Misc/fileTools';
+import { GUID } from '../../Misc/guid';
 import { DepthTextureCreationOptions } from '../depthTextureCreationOptions';
 import { IWebRequest } from '../../Misc/interfaces/iWebRequest';
 import { Constants } from '../constants';
@@ -77,16 +78,22 @@ declare module "../../Engines/thinEngine" {
             format: number | undefined, forcedExtension: any, createPolynomials: boolean, lodScale: number, lodOffset: number): InternalTexture;
 
         /** @hidden */
+        createCubeTextureBase(rootUrl: string, scene: Nullable<Scene>, files: Nullable<string[]>, noMipmap: boolean,
+            onLoad: Nullable<(data?: any) => void>, onError: Nullable<(message?: string, exception?: any) => void>,
+            format: number | undefined, forcedExtension: any, createPolynomials: boolean, lodScale: number, lodOffset: number, fallback: Nullable<InternalTexture>,
+            beforeLoadCubeDataCallback: Nullable<(texture: InternalTexture, data: ArrayBufferView | ArrayBufferView[]) => void>, imageHandler: Nullable<(texture: InternalTexture, imgs: HTMLImageElement[] | ImageBitmap[]) => void>): InternalTexture;
+
+        /** @hidden */
         _partialLoadFile(url: string, index: number, loadedFiles: ArrayBuffer[], onfinish: (files: ArrayBuffer[]) => void, onErrorCallBack: Nullable<(message?: string, exception?: any) => void>): void;
 
         /** @hidden */
         _cascadeLoadFiles(scene: Nullable<Scene>, onfinish: (images: ArrayBuffer[]) => void, files: string[], onError: Nullable<(message?: string, exception?: any) => void>): void;
 
         /** @hidden */
-        _cascadeLoadImgs(scene: Nullable<Scene>, onfinish: (images: HTMLImageElement[]) => void, files: string[], onError: Nullable<(message?: string, exception?: any) => void>, mimeType?: string): void;
+        _cascadeLoadImgs(scene: Nullable<Scene>, texture: InternalTexture, onfinish: Nullable<(texture: InternalTexture, images: HTMLImageElement[] | ImageBitmap[]) => void>, files: string[], onError: Nullable<(message?: string, exception?: any) => void>, mimeType?: string): void;
 
         /** @hidden */
-        _partialLoadImg(url: string, index: number, loadedImages: HTMLImageElement[], scene: Nullable<Scene>, onfinish: (images: HTMLImageElement[]) => void, onErrorCallBack: Nullable<(message?: string, exception?: any) => void>, mimeType?: string): void;
+        _partialLoadImg(url: string, index: number, loadedImages: HTMLImageElement[] | ImageBitmap[], scene: Nullable<Scene>, texture: InternalTexture, onfinish: Nullable<(texture: InternalTexture, images: HTMLImageElement[] | ImageBitmap[]) => void>, onErrorCallBack: Nullable<(message?: string, exception?: any) => void>, mimeType?: string): void;
 
         /**
          * @hidden
@@ -160,40 +167,38 @@ ThinEngine.prototype._cascadeLoadFiles = function(scene: Nullable<Scene>, onfini
     }
 };
 
-ThinEngine.prototype._cascadeLoadImgs = function(scene: Nullable<Scene>,
-    onfinish: (images: HTMLImageElement[]) => void, files: string[], onError: Nullable<(message?: string, exception?: any) => void> = null, mimeType?: string) {
+ThinEngine.prototype._cascadeLoadImgs = function(scene: Nullable<Scene>, texture: InternalTexture,
+    onfinish: Nullable<(texture: InternalTexture, images: HTMLImageElement[] | ImageBitmap[]) => void>, files: string[], onError: Nullable<(message?: string, exception?: any) => void> = null, mimeType?: string) {
 
-    var loadedImages: HTMLImageElement[] = [];
+    var loadedImages: HTMLImageElement[] | ImageBitmap[] = [];
     (<any>loadedImages)._internalCount = 0;
 
     for (let index = 0; index < 6; index++) {
-        this._partialLoadImg(files[index], index, loadedImages, scene, onfinish, onError, mimeType);
+        this._partialLoadImg(files[index], index, loadedImages, scene, texture, onfinish, onError, mimeType);
     }
 };
 
-ThinEngine.prototype._partialLoadImg = function(url: string, index: number, loadedImages: HTMLImageElement[], scene: Nullable<Scene>,
-    onfinish: (images: HTMLImageElement[]) => void, onErrorCallBack: Nullable<(message?: string, exception?: any) => void> = null, mimeType?: string) {
+ThinEngine.prototype._partialLoadImg = function(url: string, index: number, loadedImages: HTMLImageElement[] | ImageBitmap[], scene: Nullable<Scene>, texture: InternalTexture,
+    onfinish: Nullable<(texture: InternalTexture, images: HTMLImageElement[] | ImageBitmap[]) => void>, onErrorCallBack: Nullable<(message?: string, exception?: any) => void> = null, mimeType?: string) {
 
-    var img: Nullable<HTMLImageElement>;
+    var tokenPendingData = GUID.RandomId();
 
-    var onload = () => {
-        if (img) {
-            loadedImages[index] = img;
-            (<any>loadedImages)._internalCount++;
+    var onload = (img: HTMLImageElement | ImageBitmap) => {
+        loadedImages[index] = img;
+        (<any>loadedImages)._internalCount++;
 
-            if (scene) {
-                scene._removePendingData(img);
-            }
+        if (scene) {
+            scene._removePendingData(tokenPendingData);
         }
 
-        if ((<any>loadedImages)._internalCount === 6) {
-            onfinish(loadedImages);
+        if ((<any>loadedImages)._internalCount === 6 && onfinish) {
+            onfinish(texture, loadedImages);
         }
     };
 
     var onerror = (message?: string, exception?: any) => {
         if (scene) {
-            scene._removePendingData(img);
+            scene._removePendingData(tokenPendingData);
         }
 
         if (onErrorCallBack) {
@@ -201,9 +206,9 @@ ThinEngine.prototype._partialLoadImg = function(url: string, index: number, load
         }
     };
 
-    img = FileTools.LoadImage(url, onload, onerror, scene ? scene.offlineProvider : null, mimeType);
-    if (scene && img) {
-        scene._addPendingData(img);
+    FileTools.LoadImage(url, onload, onerror, scene ? scene.offlineProvider : null, mimeType);
+    if (scene) {
+        scene._addPendingData(tokenPendingData);
     }
 };
 
@@ -218,12 +223,10 @@ ThinEngine.prototype._setCubeMapTextureParams = function(texture: InternalTextur
     this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, null);
 };
 
-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, loaderOptions?: any): InternalTexture
-{
-    const gl = this._gl;
-
+ThinEngine.prototype.createCubeTextureBase = 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, beforeLoadCubeDataCallback: Nullable<(texture: InternalTexture, data: ArrayBufferView | ArrayBufferView[]) => void> = null,
+        imageHandler: Nullable<(texture: InternalTexture, imgs: HTMLImageElement[] | ImageBitmap[]) => void> = null): InternalTexture {
     const texture = fallback ? fallback : new InternalTexture(this, InternalTextureSource.Cube);
     texture.isCube = true;
     texture.url = rootUrl;
@@ -261,13 +264,15 @@ ThinEngine.prototype.createCubeTexture = function(rootUrl: string, scene: Nullab
         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, loaderOptions);
+            this.createCubeTextureBase(originalRootUrl, scene, files, !!noMipmap, onLoad, onError, format, forcedExtension, createPolynomials, lodScale, lodOffset, texture, beforeLoadCubeDataCallback, imageHandler);
         }
     };
 
     if (loader) {
         const onloaddata = (data: ArrayBufferView | ArrayBufferView[]) => {
-            this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, texture, true);
+            if (beforeLoadCubeDataCallback) {
+                beforeLoadCubeDataCallback(texture, data);
+            }
             loader!.loadCubeData(data, texture, createPolynomials, onLoad, onError);
         };
         if (files && files.length === 6) {
@@ -291,7 +296,25 @@ ThinEngine.prototype.createCubeTexture = function(rootUrl: string, scene: Nullab
             throw new Error("Cannot load cubemap because files were not defined");
         }
 
-        this._cascadeLoadImgs(scene, (imgs) => {
+        this._cascadeLoadImgs(scene, texture, (texture: InternalTexture, imgs: HTMLImageElement[] | ImageBitmap[]) => {
+            if (imageHandler) {
+                imageHandler(texture, imgs);
+            }
+        }, files, onError);
+    }
+
+    this._internalTexturesCache.push(texture);
+
+    return texture;
+};
+
+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 {
+    const gl = this._gl;
+
+    return this.createCubeTextureBase(
+        rootUrl, scene, files, !!noMipmap, onLoad, onError, format, forcedExtension, createPolynomials, lodScale, lodOffset, fallback,
+        (texture: InternalTexture, data: ArrayBufferView | ArrayBufferView[]) => this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, texture, true),
+        (texture: InternalTexture, imgs: HTMLImageElement[] | ImageBitmap[]) => {
             const width = this.needPOTTextures ? ThinEngine.GetExponentOfTwo(imgs[0].width, this._caps.maxCubemapTextureSize) : imgs[0].width;
             const height = width;
 
@@ -342,10 +365,6 @@ ThinEngine.prototype.createCubeTexture = function(rootUrl: string, scene: Nullab
             if (onLoad) {
                 onLoad();
             }
-        }, files, onError);
-    }
-
-    this._internalTexturesCache.push(texture);
-
-    return texture;
+        }
+    );
 };

+ 4 - 4
src/Engines/Extensions/engine.multiRender.ts

@@ -205,7 +205,7 @@ ThinEngine.prototype.createMultipleRenderTarget = function(size: any, options: I
         attachments.push(attachment);
 
         gl.activeTexture((<any>gl)["TEXTURE" + i]);
-        gl.bindTexture(gl.TEXTURE_2D, texture._webGLTexture);
+        gl.bindTexture(gl.TEXTURE_2D, texture._hardwareTexture?.underlyingResource);
 
         gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filters.mag);
         gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filters.min);
@@ -214,7 +214,7 @@ ThinEngine.prototype.createMultipleRenderTarget = function(size: any, options: I
 
         gl.texImage2D(gl.TEXTURE_2D, 0, this._getRGBABufferInternalSizedFormat(type), width, height, 0, gl.RGBA, this._getWebGLTextureType(type), null);
 
-        gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, attachment, gl.TEXTURE_2D, texture._webGLTexture, 0);
+        gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, attachment, gl.TEXTURE_2D, texture._hardwareTexture?.underlyingResource, 0);
 
         if (generateMipMaps) {
             this._gl.generateMipmap(this._gl.TEXTURE_2D);
@@ -247,7 +247,7 @@ ThinEngine.prototype.createMultipleRenderTarget = function(size: any, options: I
         var depthTexture = new InternalTexture(this, InternalTextureSource.MultiRenderTarget);
 
         gl.activeTexture(gl.TEXTURE0);
-        gl.bindTexture(gl.TEXTURE_2D, depthTexture._webGLTexture);
+        gl.bindTexture(gl.TEXTURE_2D, depthTexture._hardwareTexture?.underlyingResource);
         gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
         gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
         gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
@@ -268,7 +268,7 @@ ThinEngine.prototype.createMultipleRenderTarget = function(size: any, options: I
             gl.FRAMEBUFFER,
             gl.DEPTH_ATTACHMENT,
             gl.TEXTURE_2D,
-            depthTexture._webGLTexture,
+            depthTexture._hardwareTexture?.underlyingResource,
             0
         );
 

+ 4 - 2
src/Engines/Extensions/engine.multiview.ts

@@ -123,10 +123,12 @@ declare module "../../scene" {
 Scene.prototype._transformMatrixR = Matrix.Zero();
 Scene.prototype._multiviewSceneUbo = null;
 Scene.prototype._createMultiviewUbo = function() {
-    this._multiviewSceneUbo = new UniformBuffer(this.getEngine(), undefined, true);
+    this._multiviewSceneUbo = new UniformBuffer(this.getEngine(), undefined, true, "scene_multiview");
     this._multiviewSceneUbo.addUniform("viewProjection", 16);
     this._multiviewSceneUbo.addUniform("viewProjectionR", 16);
     this._multiviewSceneUbo.addUniform("view", 16);
+    this._multiviewSceneUbo.addUniform("projection", 16);
+    this._multiviewSceneUbo.addUniform("viewPosition", 4);
 };
 Scene.prototype._updateMultiviewUbo = function(viewR?: Matrix, projectionR?: Matrix) {
     if (viewR && projectionR) {
@@ -142,7 +144,7 @@ Scene.prototype._updateMultiviewUbo = function(viewR?: Matrix, projectionR?: Mat
         this._multiviewSceneUbo.updateMatrix("viewProjection", this.getTransformMatrix());
         this._multiviewSceneUbo.updateMatrix("viewProjectionR", this._transformMatrixR);
         this._multiviewSceneUbo.updateMatrix("view", this._viewMatrix);
-        this._multiviewSceneUbo.update();
+        this._multiviewSceneUbo.updateMatrix("projection", this._projectionMatrix);
     }
 };
 Scene.prototype._renderMultiviewToSingleView = function(camera: Camera) {

+ 1 - 0
src/Engines/Extensions/engine.rawTexture.ts

@@ -357,6 +357,7 @@ ThinEngine.prototype.createRawCubeTexture = function(data: Nullable<ArrayBufferV
     this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, null);
 
     texture.generateMipMaps = generateMipMaps;
+    texture.samplingMode = samplingMode;
 
     return texture;
 };

+ 9 - 5
src/Engines/Extensions/engine.readTexture.ts

@@ -5,11 +5,11 @@ import { Nullable } from '../../types';
 declare module "../../Engines/thinEngine" {
     export interface ThinEngine {
         /** @hidden */
-        _readTexturePixels(texture: InternalTexture, width: number, height: number, faceIndex?: number, level?: number, buffer?: Nullable<ArrayBufferView>): ArrayBufferView;
+        _readTexturePixels(texture: InternalTexture, width: number, height: number, faceIndex?: number, level?: number, buffer?: Nullable<ArrayBufferView>, flushRenderer?: boolean): Promise<ArrayBufferView>;
     }
 }
 
-ThinEngine.prototype._readTexturePixels = function(texture: InternalTexture, width: number, height: number, faceIndex = -1, level = 0, buffer: Nullable<ArrayBufferView> = null): ArrayBufferView {
+ThinEngine.prototype._readTexturePixels = function(texture: InternalTexture, width: number, height: number, faceIndex = -1, level = 0, buffer: Nullable<ArrayBufferView> = null, flushRenderer = true): Promise<ArrayBufferView> {
     let gl = this._gl;
     if (!gl) {
         throw new Error ("Engine does not have gl rendering context.");
@@ -26,9 +26,9 @@ ThinEngine.prototype._readTexturePixels = function(texture: InternalTexture, wid
     gl.bindFramebuffer(gl.FRAMEBUFFER, this._dummyFramebuffer);
 
     if (faceIndex > -1) {
-        gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex, texture._webGLTexture, level);
+        gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex, texture._hardwareTexture?.underlyingResource, level);
     } else {
-        gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture._webGLTexture, level);
+        gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture._hardwareTexture?.underlyingResource, level);
     }
 
     let readType = (texture.type !== undefined) ? this._getWebGLTextureType(texture.type) : gl.UNSIGNED_BYTE;
@@ -48,8 +48,12 @@ ThinEngine.prototype._readTexturePixels = function(texture: InternalTexture, wid
             break;
     }
 
+    if (flushRenderer) {
+        this.flushFramebuffer();
+    }
+
     gl.readPixels(0, 0, width, height, gl.RGBA, readType, <DataView>buffer);
     gl.bindFramebuffer(gl.FRAMEBUFFER, this._currentFramebuffer);
 
-    return buffer;
+    return Promise.resolve(buffer);
 };

+ 1 - 1
src/Engines/Extensions/engine.renderTarget.ts

@@ -109,7 +109,7 @@ ThinEngine.prototype.createRenderTargetTexture = function(this: ThinEngine, size
 
     // No need to rebind on every frame
     if (!texture.is2DArray) {
-        gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture._webGLTexture, 0);
+        gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture._hardwareTexture?.underlyingResource, 0);
     }
 
     this._bindUnboundFramebuffer(currentFrameBuffer);

+ 3 - 2
src/Engines/Extensions/engine.uniformBuffer.ts

@@ -43,8 +43,9 @@ declare module "../../Engines/thinEngine" {
          * Bind a buffer to the current webGL context at a given location
          * @param buffer defines the buffer to bind
          * @param location defines the index where to bind the buffer
+         * @param name Name of the uniform variable to bind
          */
-        bindUniformBufferBase(buffer: DataBuffer, location: number): void;
+        bindUniformBufferBase(buffer: DataBuffer, location: number, name: string): void;
 
          /**
           * Bind a specific block at a given index in a specific shader program
@@ -128,7 +129,7 @@ ThinEngine.prototype.bindUniformBuffer = function(buffer: Nullable<DataBuffer>):
     this._gl.bindBuffer(this._gl.UNIFORM_BUFFER, buffer ? buffer.underlyingResource : null);
 };
 
-ThinEngine.prototype.bindUniformBufferBase = function(buffer: DataBuffer, location: number): void {
+ThinEngine.prototype.bindUniformBufferBase = function(buffer: DataBuffer, location: number, name: string): void {
     this._gl.bindBufferBase(this._gl.UNIFORM_BUFFER, location, buffer ? buffer.underlyingResource : null);
 };
 

+ 1 - 1
src/Engines/Extensions/engine.views.ts

@@ -142,7 +142,7 @@ Engine.prototype._renderViews = function() {
             canvas.height = canvas.clientHeight;
             parent.width = canvas.clientWidth;
             parent.height = canvas.clientHeight;
-            this.resize();
+            this.resize(true);
         }
 
         if (!parent.width || !parent.height) {

+ 207 - 0
src/Engines/IPipelineContext.ts

@@ -1,3 +1,6 @@
+import { Nullable } from '../types';
+import { Effect } from '../Materials/effect';
+import { IMatrixLike, IVector2Like, IVector3Like, IVector4Like, IColor3Like, IColor4Like } from '../Maths/math.like';
 
 /**
  * Class used to store and describe the pipeline context associated with an effect
@@ -13,6 +16,9 @@ export interface IPipelineContext {
     isReady: boolean;
 
     /** @hidden */
+    _name?:  string;
+
+    /** @hidden */
     _getVertexShaderCode(): string | null;
 
     /** @hidden */
@@ -20,4 +26,205 @@ export interface IPipelineContext {
 
     /** @hidden */
     _handlesSpectorRebuildCallback(onCompiled: (compiledObject: any) => void): void;
+
+    /** @hidden */
+    _fillEffectInformation(effect: Effect, uniformBuffersNames: { [key: string]: number }, uniformsNames: string[], uniforms: { [key: string]: Nullable<WebGLUniformLocation> }, samplerList: string[], samplers: { [key: string]: number }, attributesNames: string[], attributes: number[]): void;
+
+    /** Releases the resources associated with the pipeline. */
+    dispose(): void;
+
+    /**
+     * Sets an integer value on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param value Value to be set.
+     */
+    setInt(uniformName: string, value: number): void;
+
+    /**
+     * Sets an int2 value on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param x First int in int2.
+     * @param y Second int in int2.
+     */
+    setInt2(uniformName: string, x: number, y: number): void;
+
+    /**
+     * Sets an int3 value on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param x First int in int3.
+     * @param y Second int in int3.
+     * @param z Third int in int3.
+     */
+    setInt3(uniformName: string, x: number, y: number, z: number): void;
+
+    /**
+     * Sets an int4 value on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param x First int in int4.
+     * @param y Second int in int4.
+     * @param z Third int in int4.
+     * @param w Fourth int in int4.
+     */
+    setInt4(uniformName: string, x: number, y: number, z: number, w: number): void;
+
+    /**
+     * Sets an int array on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param array array to be set.
+     */
+    setIntArray(uniformName: string, array: Int32Array): void;
+
+    /**
+     * Sets an int array 2 on a uniform variable. (Array is specified as single array eg. [1,2,3,4] will result in [[1,2],[3,4]] in the shader)
+     * @param uniformName Name of the variable.
+     * @param array array to be set.
+     */
+    setIntArray2(uniformName: string, array: Int32Array): void;
+
+    /**
+     * Sets an int array 3 on a uniform variable. (Array is specified as single array eg. [1,2,3,4,5,6] will result in [[1,2,3],[4,5,6]] in the shader)
+     * @param uniformName Name of the variable.
+     * @param array array to be set.
+     */
+    setIntArray3(uniformName: string, array: Int32Array): void;
+
+    /**
+     * Sets an int array 4 on a uniform variable. (Array is specified as single array eg. [1,2,3,4,5,6,7,8] will result in [[1,2,3,4],[5,6,7,8]] in the shader)
+     * @param uniformName Name of the variable.
+     * @param array array to be set.
+     */
+    setIntArray4(uniformName: string, array: Int32Array): void;
+
+    /**
+     * Sets an array on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param array array to be set.
+     */
+    setArray(uniformName: string, array: number[] | Float32Array): void;
+
+    /**
+     * Sets an array 2 on a uniform variable. (Array is specified as single array eg. [1,2,3,4] will result in [[1,2],[3,4]] in the shader)
+     * @param uniformName Name of the variable.
+     * @param array array to be set.
+     */
+    setArray2(uniformName: string, array: number[] | Float32Array): void;
+
+    /**
+     * Sets an array 3 on a uniform variable. (Array is specified as single array eg. [1,2,3,4,5,6] will result in [[1,2,3],[4,5,6]] in the shader)
+     * @param uniformName Name of the variable.
+     * @param array array to be set.
+     */
+    setArray3(uniformName: string, array: number[] | Float32Array): void;
+
+    /**
+     * Sets an array 4 on a uniform variable. (Array is specified as single array eg. [1,2,3,4,5,6,7,8] will result in [[1,2,3,4],[5,6,7,8]] in the shader)
+     * @param uniformName Name of the variable.
+     * @param array array to be set.
+     */
+    setArray4(uniformName: string, array: number[] | Float32Array): void;
+
+    /**
+     * Sets matrices on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param matrices matrices to be set.
+     */
+    setMatrices(uniformName: string, matrices: Float32Array): void;
+
+    /**
+     * Sets matrix on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param matrix matrix to be set.
+     */
+    setMatrix(uniformName: string, matrix: IMatrixLike): void;
+
+    /**
+     * Sets a 3x3 matrix on a uniform variable. (Speicified as [1,2,3,4,5,6,7,8,9] will result in [1,2,3][4,5,6][7,8,9] matrix)
+     * @param uniformName Name of the variable.
+     * @param matrix matrix to be set.
+     */
+    setMatrix3x3(uniformName: string, matrix: Float32Array): void;
+
+    /**
+     * Sets a 2x2 matrix on a uniform variable. (Speicified as [1,2,3,4] will result in [1,2][3,4] matrix)
+     * @param uniformName Name of the variable.
+     * @param matrix matrix to be set.
+     */
+    setMatrix2x2(uniformName: string, matrix: Float32Array): void;
+
+    /**
+     * Sets a float on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param value value to be set.
+     */
+    setFloat(uniformName: string, value: number): void;
+
+    /**
+     * Sets a Vector2 on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param vector2 vector2 to be set.
+     */
+    setVector2(uniformName: string, vector2: IVector2Like): void;
+
+    /**
+     * Sets a float2 on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param x First float in float2.
+     * @param y Second float in float2.
+     */
+    setFloat2(uniformName: string, x: number, y: number): void;
+
+    /**
+     * Sets a Vector3 on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param vector3 Value to be set.
+     */
+    setVector3(uniformName: string, vector3: IVector3Like): void;
+
+    /**
+     * Sets a float3 on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param x First float in float3.
+     * @param y Second float in float3.
+     * @param z Third float in float3.
+     */
+    setFloat3(uniformName: string, x: number, y: number, z: number): void;
+
+    /**
+     * Sets a Vector4 on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param vector4 Value to be set.
+     */
+    setVector4(uniformName: string, vector4: IVector4Like): void;
+
+    /**
+     * Sets a float4 on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param x First float in float4.
+     * @param y Second float in float4.
+     * @param z Third float in float4.
+     * @param w Fourth float in float4.
+     */
+    setFloat4(uniformName: string, x: number, y: number, z: number, w: number): void;
+
+    /**
+     * Sets a Color3 on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param color3 Value to be set.
+     */
+    setColor3(uniformName: string, color3: IColor3Like): void;
+
+    /**
+     * Sets a Color4 on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param color3 Value to be set.
+     * @param alpha Alpha value to be set.
+     */
+    setColor4(uniformName: string, color3: IColor3Like, alpha: number): void;
+
+    /**
+     * Sets a Color4 on a uniform variable
+     * @param uniformName defines the name of the variable
+     * @param color4 defines the value to be set
+     */
+    setDirectColor4(uniformName: string, color4: IColor4Like): void;
 }

+ 13 - 8
src/Engines/Processors/iShaderProcessor.ts

@@ -1,13 +1,18 @@
+import { Nullable } from "../../types";
+import { ShaderProcessingContext } from "./shaderProcessingOptions";
+
 declare type ThinEngine = import("../thinEngine").ThinEngine;
 
 /** @hidden */
 export interface IShaderProcessor {
-    attributeProcessor?: (attribute: string) => string;
-    varyingProcessor?: (varying: string, isFragment: boolean) => string;
-    uniformProcessor?: (uniform: string, isFragment: boolean) => string;
-    uniformBufferProcessor?: (uniformBuffer: string, isFragment: boolean) => string;
-    endOfUniformBufferProcessor?: (closingBracketLine: string, isFragment: boolean) => string;
-    lineProcessor?: (line: string, isFragment: boolean) => string;
-    preProcessor?: (code: string, defines: string[], isFragment: boolean) => string;
-    postProcessor?: (code: string, defines: string[], isFragment: boolean, engine: ThinEngine) => string;
+    attributeProcessor?: (attribute: string, preProcessors: { [key: string]: string }, processingContext: Nullable<ShaderProcessingContext>) => string;
+    varyingProcessor?: (varying: string, isFragment: boolean, preProcessors: { [key: string]: string }, processingContext: Nullable<ShaderProcessingContext>) => string;
+    uniformProcessor?: (uniform: string, isFragment: boolean, preProcessors: { [key: string]: string }, processingContext: Nullable<ShaderProcessingContext>) => string;
+    uniformBufferProcessor?: (uniformBuffer: string, isFragment: boolean, processingContext: Nullable<ShaderProcessingContext>) => string;
+    endOfUniformBufferProcessor?: (closingBracketLine: string, isFragment: boolean, processingContext: Nullable<ShaderProcessingContext>) => string;
+    lineProcessor?: (line: string, isFragment: boolean, processingContext: Nullable<ShaderProcessingContext>) => string;
+    preProcessor?: (code: string, defines: string[], isFragment: boolean, processingContext: Nullable<ShaderProcessingContext>) => string;
+    postProcessor?: (code: string, defines: string[], isFragment: boolean, processingContext: Nullable<ShaderProcessingContext>, engine: ThinEngine) => string;
+    initializeShaders?: (processingContext: Nullable<ShaderProcessingContext>) => void;
+    finalizeShaders?: (vertexCode: string, fragmentCode: string, processingContext: Nullable<ShaderProcessingContext>) => { vertexCode: string, fragmentCode: string };
 }

+ 58 - 3
src/Engines/Processors/shaderCodeInliner.ts

@@ -239,6 +239,14 @@ export class ShaderCodeInliner {
         return index;
     }
 
+    private _isIdentifierChar(c: string): boolean {
+        const v = c.charCodeAt(0);
+        return (v >= 48 && v <= 57) || // 0-9
+            (v >= 65 && v <= 90) || // A-Z
+            (v >= 97 && v <= 122) || // a-z
+            (v == 95); // _
+    }
+
     private _removeComments(block: string): string {
         let currPos = 0,
             waitForChar = '',
@@ -315,6 +323,12 @@ export class ShaderCodeInliner {
                     break;
                 }
 
+                // Make sure "name" is not part of a bigger string
+                if (functionCallIndex === 0 || this._isIdentifierChar(this._sourceCode.charAt(functionCallIndex - 1))) {
+                    startIndex = functionCallIndex + name.length;
+                    continue;
+                }
+
                 // Find the opening parenthesis
                 const callParamsStartIndex = this._skipWhitespaces(this._sourceCode, functionCallIndex + name.length);
                 if (callParamsStartIndex === this._sourceCode.length || this._sourceCode.charAt(callParamsStartIndex) !== '(') {
@@ -334,7 +348,41 @@ export class ShaderCodeInliner {
                 const callParams = this._sourceCode.substring(callParamsStartIndex + 1, callParamsEndIndex);
 
                 // process the parameter call: extract each names
-                const params = this._removeComments(callParams).split(",");
+
+                // this function split the parameter list used in the function call at ',' boundaries by taking care of potential parenthesis like in:
+                //      myfunc(a, vec2(1., 0.), 4.)
+                const splitParameterCall = (s: string) => {
+                    const parameters = [];
+                    let curIdx = 0, startParamIdx = 0;
+                    while (curIdx < s.length) {
+                        if (s.charAt(curIdx) === '(') {
+                            const idx2 = this._extractBetweenMarkers('(', ')', s, curIdx);
+                            if (idx2 < 0) {
+                                return null;
+                            }
+                            curIdx = idx2;
+                        } else if (s.charAt(curIdx) === ',') {
+                            parameters.push(s.substring(startParamIdx, curIdx));
+                            startParamIdx = curIdx + 1;
+                        }
+                        curIdx++;
+                    }
+                    if (startParamIdx < curIdx) {
+                        parameters.push(s.substring(startParamIdx, curIdx));
+                    }
+                    return parameters;
+                };
+
+                const params = splitParameterCall(this._removeComments(callParams));
+
+                if (params === null) {
+                    if (this.debug) {
+                        console.warn(`Invalid function call: can't extract the parameters of the function call. Function '${name}' (type=${type}). callParamsStartIndex=${callParamsStartIndex}, callParams=` + callParams);
+                    }
+                    startIndex = functionCallIndex + name.length;
+                    continue;
+                }
+
                 const paramNames = [];
 
                 for (let p = 0; p < params.length; ++p) {
@@ -410,12 +458,19 @@ export class ShaderCodeInliner {
     }
 
     private _replaceNames(code: string, sources: string[], destinations: string[]): string {
-
         for (let i = 0; i < sources.length; ++i) {
             const source = new RegExp(this._escapeRegExp(sources[i]), 'g'),
+                  sourceLen = sources[i].length,
                   destination = destinations[i];
 
-            code = code.replace(source, destination);
+            code = code.replace(source, (match, ...args) => {
+                const offset: number = args[0];
+                // Make sure "source" is not part of a bigger identifier (for eg, if source=view and we matched it with viewDirection)
+                if (this._isIdentifierChar(code.charAt(offset - 1)) || this._isIdentifierChar(code.charAt(offset + sourceLen))) {
+                    return sources[i];
+                }
+                return destination;
+            });
         }
 
         return code;

+ 11 - 11
src/Engines/Processors/shaderCodeNode.ts

@@ -20,32 +20,32 @@ export class ShaderCodeNode {
             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);
+                    value = processor.lineProcessor(value, options.isFragment, options.processingContext);
                 }
 
                 if (processor.attributeProcessor && StringTools.StartsWith(this.line, "attribute")) {
-                    value = processor.attributeProcessor(this.line);
+                    value = processor.attributeProcessor(this.line, preprocessors, options.processingContext);
                 } else if (processor.varyingProcessor && StringTools.StartsWith(this.line, "varying")) {
-                    value = processor.varyingProcessor(this.line, options.isFragment);
-                } else if ((processor.uniformProcessor || processor.uniformBufferProcessor) && StringTools.StartsWith(this.line, "uniform")) {
-                    let regex = /uniform (.+) (.+)/;
+                    value = processor.varyingProcessor(this.line, options.isFragment, preprocessors, options.processingContext);
+                } else if ((processor.uniformProcessor || processor.uniformBufferProcessor) && StringTools.StartsWith(this.line, "uniform") && !options.lookForClosingBracketForUniformBuffer) {
+                    let regex = /uniform\s+(?:(?:highp)?|(?:lowp)?)\s*(\S+)\s+(\S+)\s*;/;
 
                     if (regex.test(this.line)) { // uniform
                         if (processor.uniformProcessor) {
-                            value = processor.uniformProcessor(this.line, options.isFragment);
+                            value = processor.uniformProcessor(this.line, options.isFragment, preprocessors, options.processingContext);
                         }
                     } else { // Uniform buffer
                         if (processor.uniformBufferProcessor) {
-                            value = processor.uniformBufferProcessor(this.line, options.isFragment);
+                            value = processor.uniformBufferProcessor(this.line, options.isFragment, options.processingContext);
                             options.lookForClosingBracketForUniformBuffer = true;
                         }
                     }
                 }
 
-                if (processor.endOfUniformBufferProcessor) {
-                    if (options.lookForClosingBracketForUniformBuffer && this.line.indexOf("}") !== -1) {
-                        options.lookForClosingBracketForUniformBuffer = false;
-                        value = processor.endOfUniformBufferProcessor(this.line, options.isFragment);
+                if (options.lookForClosingBracketForUniformBuffer && this.line.indexOf("}") !== -1) {
+                    options.lookForClosingBracketForUniformBuffer = false;
+                    if (processor.endOfUniformBufferProcessor) {
+                        value = processor.endOfUniformBufferProcessor(this.line, options.isFragment, options.processingContext);
                     }
                 }
             }

+ 6 - 1
src/Engines/Processors/shaderProcessingOptions.ts

@@ -1,4 +1,8 @@
 import { IShaderProcessor } from './iShaderProcessor';
+import { Nullable } from '../../types';
+
+/** @hidden */
+export interface ShaderProcessingContext { }
 
 /** @hidden */
 export interface ProcessingOptions {
@@ -9,8 +13,9 @@ export interface ProcessingOptions {
     supportsUniformBuffers: boolean;
     shadersRepository: string;
     includesShadersStore: { [key: string]: string };
-    processor?: IShaderProcessor;
+    processor: Nullable<IShaderProcessor>;
     version: string;
     platformName: string;
     lookForClosingBracketForUniformBuffer?: boolean;
+    processingContext: Nullable<ShaderProcessingContext>;
 }

+ 16 - 2
src/Engines/Processors/shaderProcessor.ts

@@ -21,6 +21,12 @@ const regexSERevert = /defined\s*?\[(.+?)\]/g;
 
 /** @hidden */
 export class ShaderProcessor {
+    public static Initialize(options: ProcessingOptions): void {
+        if (options.processor && options.processor.initializeShaders) {
+            options.processor.initializeShaders(options.processingContext);
+        }
+    }
+
     public static Process(sourceCode: string, options: ProcessingOptions, callback: (migratedCode: string) => void, engine: ThinEngine) {
         this._ProcessIncludes(sourceCode, options, (codeWithIncludes) => {
             let migratedCode = this._ProcessShaderConversion(codeWithIncludes, options, engine);
@@ -28,6 +34,14 @@ export class ShaderProcessor {
         });
     }
 
+    public static Finalize(vertexCode: string, fragmentCode: string, options: ProcessingOptions): { vertexCode: string, fragmentCode: string } {
+        if (!options.processor || !options.processor.finalizeShaders) {
+            return { vertexCode, fragmentCode };
+        }
+
+        return options.processor.finalizeShaders(vertexCode, fragmentCode, options.processingContext);
+    }
+
     private static _ProcessPrecision(source: string, options: ProcessingOptions): string {
         const shouldUseHighPrecisionShader = options.shouldUseHighPrecisionShader;
 
@@ -273,14 +287,14 @@ export class ShaderProcessor {
 
         // General pre processing
         if (options.processor.preProcessor) {
-            preparedSourceCode = options.processor.preProcessor(preparedSourceCode, defines, options.isFragment);
+            preparedSourceCode = options.processor.preProcessor(preparedSourceCode, defines, options.isFragment, options.processingContext);
         }
 
         preparedSourceCode = this._EvaluatePreProcessors(preparedSourceCode, preprocessors, options);
 
         // Post processing
         if (options.processor.postProcessor) {
-            preparedSourceCode = options.processor.postProcessor(preparedSourceCode, defines, options.isFragment, engine);
+            preparedSourceCode = options.processor.postProcessor(preparedSourceCode, defines, options.isFragment, options.processingContext, engine);
         }
 
         return preparedSourceCode;

+ 5 - 1
src/Engines/WebGL/webGL2ShaderProcessors.ts

@@ -1,4 +1,8 @@
+import { Nullable } from '../../types';
 import { IShaderProcessor } from '../Processors/iShaderProcessor';
+import { ShaderProcessingContext } from '../Processors/shaderProcessingOptions';
+
+declare type ThinEngine = import("../thinEngine").ThinEngine;
 
 /** @hidden */
 export class WebGL2ShaderProcessor implements IShaderProcessor {
@@ -10,7 +14,7 @@ export class WebGL2ShaderProcessor implements IShaderProcessor {
         return varying.replace("varying", isFragment ? "in" : "out");
     }
 
-    public postProcessor(code: string, defines: string[], isFragment: boolean) {
+    public postProcessor(code: string, defines: string[], isFragment: boolean, processingContext: Nullable<ShaderProcessingContext>, engine: ThinEngine) {
         const hasDrawBuffersExtension = code.search(/#extension.+GL_EXT_draw_buffers.+require/) !== -1;
 
         // Remove extensions

+ 42 - 0
src/Engines/WebGL/webGLHardwareTexture.ts

@@ -0,0 +1,42 @@
+import { HardwareTextureWrapper } from '../../Materials/Textures/hardwareTextureWrapper';
+import { Nullable } from '../../types';
+
+/** @hidden */
+export class WebGLHardwareTexture implements HardwareTextureWrapper {
+
+    private _webGLTexture: WebGLTexture;
+    private _context: WebGLRenderingContext;
+
+    public get underlyingResource(): Nullable<WebGLTexture> {
+        return this._webGLTexture;
+    }
+
+    constructor(existingTexture: Nullable<WebGLTexture> = null, context: WebGLRenderingContext) {
+        this._context = context as WebGLRenderingContext;
+        if (!existingTexture) {
+            existingTexture = context.createTexture();
+            if (!existingTexture) {
+                throw new Error("Unable to create webGL texture");
+            }
+        }
+        this.set(existingTexture);
+    }
+
+    public setUsage(textureSource: number, generateMipMaps: boolean, isCube: boolean, width: number, height: number): void {
+    }
+
+    public set(hardwareTexture: WebGLTexture) {
+        this._webGLTexture = hardwareTexture;
+    }
+
+    public reset() {
+        this._webGLTexture = null as any;
+    }
+
+    public release() {
+        if (this._webGLTexture) {
+            this._context.deleteTexture(this._webGLTexture);
+        }
+        this.reset();
+    }
+}

+ 467 - 1
src/Engines/WebGL/webGLPipelineContext.ts

@@ -1,9 +1,14 @@
 import { IPipelineContext } from '../IPipelineContext';
 import { Nullable } from '../../types';
-import { ThinEngine } from '../thinEngine';
+import { Effect } from '../../Materials/effect';
+import { IMatrixLike, IVector2Like, IVector3Like, IVector4Like, IColor3Like, IColor4Like } from '../../Maths/math.like';
+import { ThinEngine } from "../thinEngine";
 
 /** @hidden */
 export class WebGLPipelineContext implements IPipelineContext {
+    private _valueCache: { [key: string]: any } = {};
+    private _uniforms: { [key: string]: Nullable<WebGLUniformLocation> };
+
     public engine: ThinEngine;
     public program: Nullable<WebGLProgram>;
     public context?: WebGLRenderingContext;
@@ -39,6 +44,467 @@ export class WebGLPipelineContext implements IPipelineContext {
         }
     }
 
+    public _fillEffectInformation(effect: Effect, uniformBuffersNames: { [key: string]: number }, uniformsNames: string[], uniforms: { [key: string]: Nullable<WebGLUniformLocation> }, samplerList: string[], samplers: { [key: string]: number }, attributesNames: string[], attributes: number[]) {
+        const engine = this.engine;
+        if (engine.supportsUniformBuffers) {
+            for (var name in uniformBuffersNames) {
+                effect.bindUniformBlock(name, uniformBuffersNames[name]);
+            }
+        }
+
+        const effectAvailableUniforms = this.engine.getUniforms(this, uniformsNames);
+        effectAvailableUniforms.forEach((uniform, index) => {
+            uniforms[uniformsNames[index]] = uniform;
+        });
+        this._uniforms = uniforms;
+
+        let index: number;
+        for (index = 0; index < samplerList.length; index++) {
+            const sampler = effect.getUniform(samplerList[index]);
+            if (sampler == null) {
+                samplerList.splice(index, 1);
+                index--;
+            }
+        }
+
+        samplerList.forEach((name, index) => {
+            samplers[name] = index;
+        });
+
+        for (let attr of engine.getAttributes(this, attributesNames)) {
+            attributes.push(attr);
+        }
+    }
+
+    /**
+     * Release all associated resources.
+     **/
+    public dispose() {
+        this._uniforms = { };
+    }
+
+    /** @hidden */
+    public _cacheMatrix(uniformName: string, matrix: IMatrixLike): boolean {
+        var cache = this._valueCache[uniformName];
+        var flag = matrix.updateFlag;
+        if (cache !== undefined && cache === flag) {
+            return false;
+        }
+
+        this._valueCache[uniformName] = flag;
+
+        return true;
+    }
+
+    /** @hidden */
+    public _cacheFloat2(uniformName: string, x: number, y: number): boolean {
+        var cache = this._valueCache[uniformName];
+        if (!cache || cache.length !== 2) {
+            cache = [x, y];
+            this._valueCache[uniformName] = cache;
+            return true;
+        }
+
+        var changed = false;
+        if (cache[0] !== x) {
+            cache[0] = x;
+            changed = true;
+        }
+        if (cache[1] !== y) {
+            cache[1] = y;
+            changed = true;
+        }
+
+        return changed;
+    }
+
+    /** @hidden */
+    public _cacheFloat3(uniformName: string, x: number, y: number, z: number): boolean {
+        var cache = this._valueCache[uniformName];
+        if (!cache || cache.length !== 3) {
+            cache = [x, y, z];
+            this._valueCache[uniformName] = cache;
+            return true;
+        }
+
+        var changed = false;
+        if (cache[0] !== x) {
+            cache[0] = x;
+            changed = true;
+        }
+        if (cache[1] !== y) {
+            cache[1] = y;
+            changed = true;
+        }
+        if (cache[2] !== z) {
+            cache[2] = z;
+            changed = true;
+        }
+
+        return changed;
+    }
+
+    /** @hidden */
+    public _cacheFloat4(uniformName: string, x: number, y: number, z: number, w: number): boolean {
+        var cache = this._valueCache[uniformName];
+        if (!cache || cache.length !== 4) {
+            cache = [x, y, z, w];
+            this._valueCache[uniformName] = cache;
+            return true;
+        }
+
+        var changed = false;
+        if (cache[0] !== x) {
+            cache[0] = x;
+            changed = true;
+        }
+        if (cache[1] !== y) {
+            cache[1] = y;
+            changed = true;
+        }
+        if (cache[2] !== z) {
+            cache[2] = z;
+            changed = true;
+        }
+        if (cache[3] !== w) {
+            cache[3] = w;
+            changed = true;
+        }
+
+        return changed;
+    }
+
+    /**
+     * Sets an interger value on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param value Value to be set.
+     */
+    public setInt(uniformName: string, value: number): void {
+        var cache = this._valueCache[uniformName];
+        if (cache !== undefined && cache === value) {
+            return;
+        }
+
+        if (this.engine.setInt(this._uniforms[uniformName], value)) {
+            this._valueCache[uniformName] = value;
+        }
+    }
+
+    /**
+     * Sets a int2 on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param x First int in int2.
+     * @param y Second int in int2.
+     */
+    public setInt2(uniformName: string, x: number, y: number): void {
+        if (this._cacheFloat2(uniformName, x, y)) {
+            if (!this.engine.setInt2(this._uniforms[uniformName], x, y)) {
+                this._valueCache[uniformName] = null;
+            }
+        }
+    }
+
+    /**
+     * Sets a int3 on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param x First int in int3.
+     * @param y Second int in int3.
+     * @param y Third int in int3.
+     */
+    public setInt3(uniformName: string, x: number, y: number, z: number): void {
+        if (this._cacheFloat3(uniformName, x, y, z)) {
+            if (!this.engine.setInt3(this._uniforms[uniformName], x, y, z)) {
+                this._valueCache[uniformName] = null;
+            }
+        }
+    }
+
+    /**
+     * Sets a int4 on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param x First int in int4.
+     * @param y Second int in int4.
+     * @param y Third int in int4.
+     * @param w Fourth int in int4.
+     */
+    public setInt4(uniformName: string, x: number, y: number, z: number, w: number): void {
+        if (this._cacheFloat4(uniformName, x, y, z, w)) {
+            if (!this.engine.setInt4(this._uniforms[uniformName], x, y, z, w)) {
+                this._valueCache[uniformName] = null;
+            }
+        }
+    }
+
+    /**
+     * Sets an int array on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param array array to be set.
+     */
+    public setIntArray(uniformName: string, array: Int32Array): void {
+        this._valueCache[uniformName] = null;
+        this.engine.setIntArray(this._uniforms[uniformName], array);
+    }
+
+    /**
+     * Sets an int array 2 on a uniform variable. (Array is specified as single array eg. [1,2,3,4] will result in [[1,2],[3,4]] in the shader)
+     * @param uniformName Name of the variable.
+     * @param array array to be set.
+     */
+    public setIntArray2(uniformName: string, array: Int32Array): void {
+        this._valueCache[uniformName] = null;
+        this.engine.setIntArray2(this._uniforms[uniformName], array);
+    }
+
+    /**
+     * Sets an int array 3 on a uniform variable. (Array is specified as single array eg. [1,2,3,4,5,6] will result in [[1,2,3],[4,5,6]] in the shader)
+     * @param uniformName Name of the variable.
+     * @param array array to be set.
+     */
+    public setIntArray3(uniformName: string, array: Int32Array): void {
+        this._valueCache[uniformName] = null;
+        this.engine.setIntArray3(this._uniforms[uniformName], array);
+    }
+
+    /**
+     * Sets an int array 4 on a uniform variable. (Array is specified as single array eg. [1,2,3,4,5,6,7,8] will result in [[1,2,3,4],[5,6,7,8]] in the shader)
+     * @param uniformName Name of the variable.
+     * @param array array to be set.
+     */
+    public setIntArray4(uniformName: string, array: Int32Array): void {
+        this._valueCache[uniformName] = null;
+        this.engine.setIntArray4(this._uniforms[uniformName], array);
+    }
+
+    /**
+     * Sets an array on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param array array to be set.
+     */
+    public setArray(uniformName: string, array: number[]): void {
+        this._valueCache[uniformName] = null;
+        this.engine.setArray(this._uniforms[uniformName], array);
+    }
+
+    /**
+     * Sets an array 2 on a uniform variable. (Array is specified as single array eg. [1,2,3,4] will result in [[1,2],[3,4]] in the shader)
+     * @param uniformName Name of the variable.
+     * @param array array to be set.
+     */
+    public setArray2(uniformName: string, array: number[]): void {
+        this._valueCache[uniformName] = null;
+        this.engine.setArray2(this._uniforms[uniformName], array);
+    }
+
+    /**
+     * Sets an array 3 on a uniform variable. (Array is specified as single array eg. [1,2,3,4,5,6] will result in [[1,2,3],[4,5,6]] in the shader)
+     * @param uniformName Name of the variable.
+     * @param array array to be set.
+     * @returns this effect.
+     */
+    public setArray3(uniformName: string, array: number[]): void {
+        this._valueCache[uniformName] = null;
+        this.engine.setArray3(this._uniforms[uniformName], array);
+    }
+
+    /**
+     * Sets an array 4 on a uniform variable. (Array is specified as single array eg. [1,2,3,4,5,6,7,8] will result in [[1,2,3,4],[5,6,7,8]] in the shader)
+     * @param uniformName Name of the variable.
+     * @param array array to be set.
+     */
+    public setArray4(uniformName: string, array: number[]): void {
+        this._valueCache[uniformName] = null;
+        this.engine.setArray4(this._uniforms[uniformName], array);
+    }
+
+    /**
+     * Sets matrices on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param matrices matrices to be set.
+     */
+    public setMatrices(uniformName: string, matrices: Float32Array): void {
+        if (!matrices) {
+            return;
+        }
+
+        this._valueCache[uniformName] = null;
+        this.engine.setMatrices(this._uniforms[uniformName], matrices);
+    }
+
+    /**
+     * Sets matrix on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param matrix matrix to be set.
+     */
+    public setMatrix(uniformName: string, matrix: IMatrixLike): void {
+        if (this._cacheMatrix(uniformName, matrix)) {
+            if (!this.engine.setMatrices(this._uniforms[uniformName], matrix.toArray() as Float32Array)) {
+                this._valueCache[uniformName] = null;
+            }
+        }
+    }
+
+    /**
+     * Sets a 3x3 matrix on a uniform variable. (Speicified as [1,2,3,4,5,6,7,8,9] will result in [1,2,3][4,5,6][7,8,9] matrix)
+     * @param uniformName Name of the variable.
+     * @param matrix matrix to be set.
+     */
+    public setMatrix3x3(uniformName: string, matrix: Float32Array): void {
+        this._valueCache[uniformName] = null;
+        this.engine.setMatrix3x3(this._uniforms[uniformName], matrix);
+    }
+
+    /**
+     * Sets a 2x2 matrix on a uniform variable. (Speicified as [1,2,3,4] will result in [1,2][3,4] matrix)
+     * @param uniformName Name of the variable.
+     * @param matrix matrix to be set.
+     */
+    public setMatrix2x2(uniformName: string, matrix: Float32Array): void {
+        this._valueCache[uniformName] = null;
+        this.engine.setMatrix2x2(this._uniforms[uniformName], matrix);
+    }
+
+    /**
+     * Sets a float on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param value value to be set.
+     * @returns this effect.
+     */
+    public setFloat(uniformName: string, value: number): void {
+        var cache = this._valueCache[uniformName];
+        if (cache !== undefined && cache === value) {
+            return;
+        }
+
+        if (this.engine.setFloat(this._uniforms[uniformName], value)) {
+            this._valueCache[uniformName] = value;
+        }
+    }
+
+    /**
+     * Sets a Vector2 on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param vector2 vector2 to be set.
+     */
+    public setVector2(uniformName: string, vector2: IVector2Like): void {
+        if (this._cacheFloat2(uniformName, vector2.x, vector2.y)) {
+            if (!this.engine.setFloat2(this._uniforms[uniformName], vector2.x, vector2.y)) {
+                this._valueCache[uniformName] = null;
+            }
+        }
+    }
+
+    /**
+     * Sets a float2 on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param x First float in float2.
+     * @param y Second float in float2.
+     */
+    public setFloat2(uniformName: string, x: number, y: number): void {
+        if (this._cacheFloat2(uniformName, x, y)) {
+            if (!this.engine.setFloat2(this._uniforms[uniformName], x, y)) {
+                this._valueCache[uniformName] = null;
+            }
+        }
+    }
+
+    /**
+     * Sets a Vector3 on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param vector3 Value to be set.
+     */
+    public setVector3(uniformName: string, vector3: IVector3Like): void {
+        if (this._cacheFloat3(uniformName, vector3.x, vector3.y, vector3.z)) {
+            if (!this.engine.setFloat3(this._uniforms[uniformName], vector3.x, vector3.y, vector3.z)) {
+                this._valueCache[uniformName] = null;
+            }
+        }
+    }
+
+    /**
+     * Sets a float3 on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param x First float in float3.
+     * @param y Second float in float3.
+     * @param z Third float in float3.
+     */
+    public setFloat3(uniformName: string, x: number, y: number, z: number): void {
+        if (this._cacheFloat3(uniformName, x, y, z)) {
+            if (!this.engine.setFloat3(this._uniforms[uniformName], x, y, z)) {
+                this._valueCache[uniformName] = null;
+            }
+        }
+    }
+
+    /**
+     * Sets a Vector4 on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param vector4 Value to be set.
+     */
+    public setVector4(uniformName: string, vector4: IVector4Like): void {
+        if (this._cacheFloat4(uniformName, vector4.x, vector4.y, vector4.z, vector4.w)) {
+            if (!this.engine.setFloat4(this._uniforms[uniformName], vector4.x, vector4.y, vector4.z, vector4.w)) {
+                this._valueCache[uniformName] = null;
+            }
+        }
+    }
+
+    /**
+     * Sets a float4 on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param x First float in float4.
+     * @param y Second float in float4.
+     * @param z Third float in float4.
+     * @param w Fourth float in float4.
+     * @returns this effect.
+     */
+    public setFloat4(uniformName: string, x: number, y: number, z: number, w: number): void {
+        if (this._cacheFloat4(uniformName, x, y, z, w)) {
+            if (!this.engine.setFloat4(this._uniforms[uniformName], x, y, z, w)) {
+                this._valueCache[uniformName] = null;
+            }
+        }
+    }
+
+    /**
+     * Sets a Color3 on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param color3 Value to be set.
+     */
+    public setColor3(uniformName: string, color3: IColor3Like): void {
+        if (this._cacheFloat3(uniformName, color3.r, color3.g, color3.b)) {
+            if (!this.engine.setFloat3(this._uniforms[uniformName], color3.r, color3.g, color3.b)) {
+                this._valueCache[uniformName] = null;
+            }
+        }
+    }
+
+    /**
+     * Sets a Color4 on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param color3 Value to be set.
+     * @param alpha Alpha value to be set.
+     */
+    public setColor4(uniformName: string, color3: IColor3Like, alpha: number): void {
+        if (this._cacheFloat4(uniformName, color3.r, color3.g, color3.b, alpha)) {
+            if (!this.engine.setFloat4(this._uniforms[uniformName], color3.r, color3.g, color3.b, alpha)) {
+                this._valueCache[uniformName] = null;
+            }
+        }
+    }
+
+    /**
+     * Sets a Color4 on a uniform variable
+     * @param uniformName defines the name of the variable
+     * @param color4 defines the value to be set
+     */
+    public setDirectColor4(uniformName: string, color4: IColor4Like): void {
+        if (this._cacheFloat4(uniformName, color4.r, color4.g, color4.b, color4.a)) {
+            if (!this.engine.setFloat4(this._uniforms[uniformName], color4.r, color4.g, color4.b, color4.a)) {
+                this._valueCache[uniformName] = null;
+            }
+        }
+    }
+
     public _getVertexShaderCode(): string | null {
         return this.vertexShader ? this.engine._getShaderSource(this.vertexShader) : null;
     }

+ 4 - 2
src/Engines/WebGL/webGLShaderProcessors.ts

@@ -1,10 +1,12 @@
+import { Nullable } from '../../types';
 import { IShaderProcessor } from '../Processors/iShaderProcessor';
+import { ShaderProcessingContext } from '../Processors/shaderProcessingOptions';
 
-import { ThinEngine } from '../thinEngine';
+declare type ThinEngine = import("../thinEngine").ThinEngine;
 
 /** @hidden */
 export class WebGLShaderProcessor implements IShaderProcessor {
-    public postProcessor(code: string, defines: string[], isFragment: boolean, engine: ThinEngine) {
+    public postProcessor(code: string, defines: string[], isFragment: boolean, processingContext: Nullable<ShaderProcessingContext>, engine: ThinEngine) {
 
         // Remove extensions
         if (!engine.getCaps().drawBuffersExtension) {

+ 193 - 0
src/Engines/WebGPU/webgpuBufferManager.ts

@@ -0,0 +1,193 @@
+import { DataBuffer } from '../../Meshes/dataBuffer';
+import { WebGPUDataBuffer } from '../../Meshes/WebGPU/webgpuDataBuffer';
+import { Nullable } from '../../types';
+import * as WebGPUConstants from './webgpuConstants';
+
+/** @hidden */
+export class WebGPUBufferManager {
+
+    private _device: GPUDevice;
+    private _deferredReleaseBuffers: Array<GPUBuffer> = [];
+
+    private static _IsGPUBuffer(buffer: DataBuffer | GPUBuffer): buffer is GPUBuffer {
+        return (buffer as DataBuffer).underlyingResource === undefined;
+    }
+
+    constructor(device: GPUDevice) {
+        this._device = device;
+    }
+
+    public createRawBuffer(viewOrSize: ArrayBufferView | number, flags: GPUBufferUsageFlags, mappedAtCreation = false): GPUBuffer {
+        const alignedLength = (viewOrSize as ArrayBufferView).byteLength !== undefined ? ((viewOrSize as ArrayBufferView).byteLength + 3) & ~3 : ((viewOrSize as number) + 3) & ~3; // 4 bytes alignments (because of the upload which requires this)
+        const verticesBufferDescriptor = {
+            mappedAtCreation,
+            size: alignedLength,
+            usage: flags
+        };
+
+        return this._device.createBuffer(verticesBufferDescriptor);
+    }
+
+    public createBuffer(viewOrSize: ArrayBufferView | number, flags: GPUBufferUsageFlags): DataBuffer {
+        const isView = (viewOrSize as ArrayBufferView).byteLength !== undefined;
+        const buffer = this.createRawBuffer(viewOrSize, flags);
+        const dataBuffer = new WebGPUDataBuffer(buffer);
+        dataBuffer.references = 1;
+        dataBuffer.capacity = isView ? (viewOrSize as ArrayBufferView).byteLength : viewOrSize as number;
+
+        if (isView) {
+            this.setSubData(dataBuffer, 0, viewOrSize as ArrayBufferView);
+        }
+
+        return dataBuffer;
+    }
+
+    public setSubData(dataBuffer: WebGPUDataBuffer, dstByteOffset: number, src: ArrayBufferView, srcByteOffset = 0, byteLength = 0): void {
+        const buffer = dataBuffer.underlyingResource as GPUBuffer;
+
+        byteLength = byteLength || src.byteLength;
+        byteLength = Math.min(byteLength, dataBuffer.capacity - dstByteOffset);
+
+        // After Migration to Canary
+        let chunkStart = src.byteOffset + srcByteOffset;
+        let chunkEnd = chunkStart + byteLength;
+
+        // 4 bytes alignments for upload
+        const alignedLength = (byteLength + 3) & ~3;
+        if (alignedLength !== byteLength) {
+            const tempView = new Uint8Array(src.buffer.slice(chunkStart, chunkEnd));
+            src = new Uint8Array(alignedLength);
+            (src as Uint8Array).set(tempView);
+            srcByteOffset = 0;
+            chunkStart = 0;
+            chunkEnd = alignedLength;
+            byteLength = alignedLength;
+        }
+
+        // Chunk
+        const maxChunk = 1024 * 1024 * 15;
+        let offset = 0;
+        while ((chunkEnd - (chunkStart + offset)) > maxChunk) {
+            this._device.defaultQueue.writeBuffer(buffer, dstByteOffset + offset, src.buffer, chunkStart + offset, maxChunk);
+            offset += maxChunk;
+        }
+
+        this._device.defaultQueue.writeBuffer(buffer, dstByteOffset + offset, src.buffer, chunkStart + offset, byteLength - offset);
+    }
+
+    private _FromHalfFloat(value: number): number {
+        const s = (value & 0x8000) >> 15;
+        const e = (value & 0x7C00) >> 10;
+        const f = value & 0x03FF;
+
+        if (e === 0) {
+            return (s ? -1 : 1) * Math.pow(2, -14) * (f / Math.pow(2, 10));
+        } else if (e == 0x1F) {
+            return f ? NaN : ((s ? -1 : 1) * Infinity);
+        }
+
+        return (s ? -1 : 1) * Math.pow(2, e - 15) * (1 + (f / Math.pow(2, 10)));
+    }
+
+    private _GetHalfFloatAsFloatRGBAArrayBuffer(dataLength: number, arrayBuffer: ArrayBuffer, destArray?: Float32Array): Float32Array {
+        if (!destArray) {
+            destArray = new Float32Array(dataLength);
+        }
+        const srcData = new Uint16Array(arrayBuffer);
+        while (dataLength--) {
+            destArray[dataLength] = this._FromHalfFloat(srcData[dataLength]);
+        }
+
+        return destArray;
+    }
+
+    public readDataFromBuffer(gpuBuffer: GPUBuffer, size: number, width: number, height: number, bytesPerRow: number, bytesPerRowAligned: number, floatFormat = 0, offset = 0, buffer: Nullable<ArrayBufferView> = null, destroyBuffer = true): Promise<ArrayBufferView> {
+        return new Promise((resolve, reject) => {
+            gpuBuffer.mapAsync(WebGPUConstants.MapMode.Read, offset, size).then(() => {
+                const copyArrayBuffer = gpuBuffer.getMappedRange(offset, size);
+                let data: Nullable<ArrayBufferView> | Uint8Array | Float32Array = buffer;
+                if (data === null) {
+                    switch (floatFormat) {
+                        case 0: // byte format
+                            data = new Uint8Array(size);
+                            (data as Uint8Array).set(new Uint8Array(copyArrayBuffer));
+                            break;
+                        case 1: // half float
+                            // TODO WEBGPU use computer shaders (or render pass) to make the conversion?
+                            data = this._GetHalfFloatAsFloatRGBAArrayBuffer(size / 2, copyArrayBuffer);
+                            break;
+                        case 2: // float
+                            data = new Float32Array(size / 4);
+                            (data as Float32Array).set(new Float32Array(copyArrayBuffer));
+                            break;
+                    }
+                } else {
+                    switch (floatFormat) {
+                        case 0: // byte format
+                            data = new Uint8Array(data.buffer);
+                            (data as Uint8Array).set(new Uint8Array(copyArrayBuffer));
+                            break;
+                        case 1: // half float
+                            // TODO WEBGPU use computer shaders (or render pass) to make the conversion?
+                            data = this._GetHalfFloatAsFloatRGBAArrayBuffer(size / 2, copyArrayBuffer, buffer as Float32Array);
+                            break;
+                        case 2: // float
+                            data = new Float32Array(data.buffer);
+                            (data as Float32Array).set(new Float32Array(copyArrayBuffer));
+                            break;
+                    }
+                }
+                if (bytesPerRow !== bytesPerRowAligned) {
+                    // TODO WEBGPU use computer shaders (or render pass) to build the final buffer data?
+                    if (floatFormat === 1) {
+                        // half float have been converted to float above
+                        bytesPerRow *= 2;
+                        bytesPerRowAligned *= 2;
+                    }
+                    const data2 = new Uint8Array(data!.buffer);
+                    let offset = bytesPerRow, offset2 = 0;
+                    for (let y = 1; y < height; ++y) {
+                        offset2 = y * bytesPerRowAligned;
+                        for (let x = 0; x < bytesPerRow; ++x) {
+                            data2[offset++] = data2[offset2++];
+                        }
+                    }
+                    if (floatFormat !== 0) {
+                        data = new Float32Array(data2.buffer, 0, offset / 4);
+                    } else {
+                        data = new Uint8Array(data2.buffer, 0, offset);
+                    }
+                }
+                gpuBuffer.unmap();
+                if (destroyBuffer) {
+                    this.releaseBuffer(gpuBuffer);
+                }
+                resolve(data!);
+            }, (reason) => reject(reason));
+        });
+    }
+
+    public releaseBuffer(buffer: DataBuffer | GPUBuffer): boolean {
+        if (WebGPUBufferManager._IsGPUBuffer(buffer)) {
+            this._deferredReleaseBuffers.push(buffer);
+            return true;
+        }
+
+        buffer.references--;
+
+        if (buffer.references === 0) {
+            this._deferredReleaseBuffers.push(buffer.underlyingResource as GPUBuffer);
+            return true;
+        }
+
+        return false;
+    }
+
+    public destroyDeferredBuffers(): void {
+        for (let i = 0; i < this._deferredReleaseBuffers.length; ++i) {
+            this._deferredReleaseBuffers[i].destroy();
+        }
+
+        this._deferredReleaseBuffers.length = 0;
+   }
+}

+ 247 - 0
src/Engines/WebGPU/webgpuCacheSampler.ts

@@ -0,0 +1,247 @@
+import * as WebGPUConstants from './webgpuConstants';
+import { Constants } from '../constants';
+import { InternalTexture } from '../../Materials/Textures/internalTexture';
+import { WebGPUTextureHelper } from "./webgpuTextureHelper";
+
+const filterToBits = [
+    0 | 0 << 1 | 0 << 2, // not used
+    0 | 0 << 1 | 0 << 2, // TEXTURE_NEAREST_SAMPLINGMODE / TEXTURE_NEAREST_NEAREST
+    1 | 1 << 1 | 0 << 2, // TEXTURE_BILINEAR_SAMPLINGMODE / TEXTURE_LINEAR_LINEAR
+    1 | 1 << 1 | 1 << 2, // TEXTURE_TRILINEAR_SAMPLINGMODE / TEXTURE_LINEAR_LINEAR_MIPLINEAR
+    0 | 0 << 1 | 0 << 2, // TEXTURE_NEAREST_NEAREST_MIPNEAREST
+    0 | 1 << 1 | 0 << 2, // TEXTURE_NEAREST_LINEAR_MIPNEAREST
+    0 | 1 << 1 | 1 << 2, // TEXTURE_NEAREST_LINEAR_MIPLINEAR
+    0 | 1 << 1 | 0 << 2, // TEXTURE_NEAREST_LINEAR
+    0 | 0 << 1 | 1 << 2, // TEXTURE_NEAREST_NEAREST_MIPLINEAR
+    1 | 0 << 1 | 0 << 2, // TEXTURE_LINEAR_NEAREST_MIPNEAREST
+    1 | 0 << 1 | 1 << 2, // TEXTURE_LINEAR_NEAREST_MIPLINEAR
+    1 | 1 << 1 | 0 << 2, // TEXTURE_LINEAR_LINEAR_MIPNEAREST
+    1 | 0 << 1 | 0 << 2, // TEXTURE_LINEAR_NEAREST
+];
+
+// subtract 0x01FF from the comparison function value before indexing this array!
+const comparisonFunctionToBits = [
+    0 << 3 | 0 << 4 | 0 << 5 | 0 << 6, // undefined
+    0 << 3 | 0 << 4 | 0 << 5 | 1 << 6, // NEVER
+    0 << 3 | 0 << 4 | 1 << 5 | 0 << 6, // LESS
+    0 << 3 | 0 << 4 | 1 << 5 | 1 << 6, // EQUAL
+    0 << 3 | 1 << 4 | 0 << 5 | 0 << 6, // LEQUAL
+    0 << 3 | 1 << 4 | 0 << 5 | 1 << 6, // GREATER
+    0 << 3 | 1 << 4 | 1 << 5 | 0 << 6, // NOTEQUAL
+    0 << 3 | 1 << 4 | 1 << 5 | 1 << 6, // GEQUAL
+    1 << 3 | 0 << 4 | 0 << 5 | 0 << 6, // ALWAYS
+];
+
+const filterNoMipToBits = [
+    0 << 7, // not used
+    1 << 7, // TEXTURE_NEAREST_SAMPLINGMODE / TEXTURE_NEAREST_NEAREST
+    1 << 7, // TEXTURE_BILINEAR_SAMPLINGMODE / TEXTURE_LINEAR_LINEAR
+    0 << 7, // TEXTURE_TRILINEAR_SAMPLINGMODE / TEXTURE_LINEAR_LINEAR_MIPLINEAR
+    0 << 7, // TEXTURE_NEAREST_NEAREST_MIPNEAREST
+    0 << 7, // TEXTURE_NEAREST_LINEAR_MIPNEAREST
+    0 << 7, // TEXTURE_NEAREST_LINEAR_MIPLINEAR
+    1 << 7, // TEXTURE_NEAREST_LINEAR
+    0 << 7, // TEXTURE_NEAREST_NEAREST_MIPLINEAR
+    0 << 7, // TEXTURE_LINEAR_NEAREST_MIPNEAREST
+    0 << 7, // TEXTURE_LINEAR_NEAREST_MIPLINEAR
+    0 << 7, // TEXTURE_LINEAR_LINEAR_MIPNEAREST
+    1 << 7, // TEXTURE_LINEAR_NEAREST
+];
+
+/** @hidden */
+export class WebGPUCacheSampler {
+
+    private _samplers: { [hash: number]: GPUSampler } = {};
+    private _device: GPUDevice;
+
+    constructor(device: GPUDevice) {
+        this._device = device;
+    }
+
+    private static _GetSamplerHashCode(texture: InternalTexture): number {
+        let code =
+            filterToBits[texture.samplingMode] +
+            comparisonFunctionToBits[(texture._comparisonFunction || 0x0202) - 0x0200 + 1] +
+            filterNoMipToBits[texture.samplingMode] + // handle the lodMinClamp = lodMaxClamp = 0 case when no filter used for mip mapping
+            ((texture._cachedWrapU ?? 1) << 8) +
+            ((texture._cachedWrapV ?? 1) << 10) +
+            ((texture._cachedWrapR ?? 1) << 12) +
+            ((texture.generateMipMaps ? 1 : 0) << 14) + // need to factor this in because _getSamplerFilterDescriptor depends on samplingMode AND generateMipMaps!
+            ((texture._cachedAnisotropicFilteringLevel ?? 1) << 15);
+
+        return code;
+    }
+
+    private static _GetSamplerFilterDescriptor(internalTexture: InternalTexture): {
+        magFilter: GPUFilterMode,
+        minFilter: GPUFilterMode,
+        mipmapFilter: GPUFilterMode,
+        lodMinClamp?: number,
+        lodMaxClamp?: number,
+    } {
+        let magFilter: GPUFilterMode, minFilter: GPUFilterMode, mipmapFilter: GPUFilterMode, lodMinClamp: number | undefined, lodMaxClamp: number | undefined;
+        const useMipMaps = internalTexture.generateMipMaps;
+        switch (internalTexture.samplingMode) {
+            case Constants.TEXTURE_LINEAR_LINEAR_MIPNEAREST:
+                magFilter = WebGPUConstants.FilterMode.Linear;
+                minFilter = WebGPUConstants.FilterMode.Linear;
+                mipmapFilter = WebGPUConstants.FilterMode.Nearest;
+                if (!useMipMaps) {
+                    lodMinClamp = lodMaxClamp = 0;
+                }
+                break;
+            case Constants.TEXTURE_LINEAR_LINEAR_MIPLINEAR:
+            case Constants.TEXTURE_TRILINEAR_SAMPLINGMODE:
+                magFilter = WebGPUConstants.FilterMode.Linear;
+                minFilter = WebGPUConstants.FilterMode.Linear;
+                if (!useMipMaps) {
+                    mipmapFilter = WebGPUConstants.FilterMode.Nearest;
+                    lodMinClamp = lodMaxClamp = 0;
+                } else {
+                    mipmapFilter = WebGPUConstants.FilterMode.Linear;
+                }
+                break;
+            case Constants.TEXTURE_NEAREST_NEAREST_MIPLINEAR:
+                magFilter = WebGPUConstants.FilterMode.Nearest;
+                minFilter = WebGPUConstants.FilterMode.Nearest;
+                if (!useMipMaps) {
+                    mipmapFilter = WebGPUConstants.FilterMode.Nearest;
+                    lodMinClamp = lodMaxClamp = 0;
+                } else {
+                    mipmapFilter = WebGPUConstants.FilterMode.Linear;
+                }
+                break;
+            case Constants.TEXTURE_NEAREST_NEAREST_MIPNEAREST:
+                magFilter = WebGPUConstants.FilterMode.Nearest;
+                minFilter = WebGPUConstants.FilterMode.Nearest;
+                mipmapFilter = WebGPUConstants.FilterMode.Nearest;
+                if (!useMipMaps) {
+                    lodMinClamp = lodMaxClamp = 0;
+                }
+                break;
+            case Constants.TEXTURE_NEAREST_LINEAR_MIPNEAREST:
+                magFilter = WebGPUConstants.FilterMode.Nearest;
+                minFilter = WebGPUConstants.FilterMode.Linear;
+                mipmapFilter = WebGPUConstants.FilterMode.Nearest;
+                if (!useMipMaps) {
+                    lodMinClamp = lodMaxClamp = 0;
+                }
+                break;
+            case Constants.TEXTURE_NEAREST_LINEAR_MIPLINEAR:
+                magFilter = WebGPUConstants.FilterMode.Nearest;
+                minFilter = WebGPUConstants.FilterMode.Linear;
+                if (!useMipMaps) {
+                    mipmapFilter = WebGPUConstants.FilterMode.Nearest;
+                    lodMinClamp = lodMaxClamp = 0;
+                } else {
+                    mipmapFilter = WebGPUConstants.FilterMode.Linear;
+                }
+                break;
+            case Constants.TEXTURE_NEAREST_LINEAR:
+                magFilter = WebGPUConstants.FilterMode.Nearest;
+                minFilter = WebGPUConstants.FilterMode.Linear;
+                mipmapFilter = WebGPUConstants.FilterMode.Nearest;
+                lodMinClamp = lodMaxClamp = 0;
+                break;
+            case Constants.TEXTURE_NEAREST_NEAREST:
+            case Constants.TEXTURE_NEAREST_SAMPLINGMODE:
+                magFilter = WebGPUConstants.FilterMode.Nearest;
+                minFilter = WebGPUConstants.FilterMode.Nearest;
+                mipmapFilter = WebGPUConstants.FilterMode.Nearest;
+                lodMinClamp = lodMaxClamp = 0;
+                break;
+            case Constants.TEXTURE_LINEAR_NEAREST_MIPNEAREST:
+                magFilter = WebGPUConstants.FilterMode.Linear;
+                minFilter = WebGPUConstants.FilterMode.Nearest;
+                mipmapFilter = WebGPUConstants.FilterMode.Nearest;
+                if (!useMipMaps) {
+                    lodMinClamp = lodMaxClamp = 0;
+                }
+                break;
+            case Constants.TEXTURE_LINEAR_NEAREST_MIPLINEAR:
+                magFilter = WebGPUConstants.FilterMode.Linear;
+                minFilter = WebGPUConstants.FilterMode.Nearest;
+                if (!useMipMaps) {
+                    mipmapFilter = WebGPUConstants.FilterMode.Nearest;
+                    lodMinClamp = lodMaxClamp = 0;
+                } else {
+                    mipmapFilter = WebGPUConstants.FilterMode.Linear;
+                }
+                break;
+            case Constants.TEXTURE_LINEAR_LINEAR:
+            case Constants.TEXTURE_BILINEAR_SAMPLINGMODE:
+                magFilter = WebGPUConstants.FilterMode.Linear;
+                minFilter = WebGPUConstants.FilterMode.Linear;
+                mipmapFilter = WebGPUConstants.FilterMode.Nearest;
+                lodMinClamp = lodMaxClamp = 0;
+                break;
+            case Constants.TEXTURE_LINEAR_NEAREST:
+                magFilter = WebGPUConstants.FilterMode.Linear;
+                minFilter = WebGPUConstants.FilterMode.Nearest;
+                mipmapFilter = WebGPUConstants.FilterMode.Nearest;
+                lodMinClamp = lodMaxClamp = 0;
+                break;
+            default:
+                magFilter = WebGPUConstants.FilterMode.Nearest;
+                minFilter = WebGPUConstants.FilterMode.Nearest;
+                mipmapFilter = WebGPUConstants.FilterMode.Nearest;
+                lodMinClamp = lodMaxClamp = 0;
+                break;
+        }
+
+        return {
+            magFilter,
+            minFilter,
+            mipmapFilter,
+            lodMinClamp,
+            lodMaxClamp,
+        };
+    }
+
+    private static _GetWrappingMode(mode: number): GPUAddressMode {
+        switch (mode) {
+            case Constants.TEXTURE_WRAP_ADDRESSMODE:
+                return WebGPUConstants.AddressMode.Repeat;
+            case Constants.TEXTURE_CLAMP_ADDRESSMODE:
+                return WebGPUConstants.AddressMode.ClampToEdge;
+            case Constants.TEXTURE_MIRROR_ADDRESSMODE:
+                return WebGPUConstants.AddressMode.MirrorRepeat;
+        }
+        return WebGPUConstants.AddressMode.Repeat;
+    }
+
+    private static _GetSamplerWrappingDescriptor(internalTexture: InternalTexture): {
+        addressModeU: GPUAddressMode,
+        addressModeV: GPUAddressMode,
+        addressModeW: GPUAddressMode
+    } {
+        return {
+            addressModeU: this._GetWrappingMode(internalTexture._cachedWrapU!),
+            addressModeV: this._GetWrappingMode(internalTexture._cachedWrapV!),
+            addressModeW: this._GetWrappingMode(internalTexture._cachedWrapR!),
+        };
+    }
+
+    private static _GetSamplerDescriptor(internalTexture: InternalTexture): GPUSamplerDescriptor {
+        return {
+            ...this._GetSamplerFilterDescriptor(internalTexture),
+            ...this._GetSamplerWrappingDescriptor(internalTexture),
+            compare: internalTexture._comparisonFunction ? WebGPUTextureHelper.GetCompareFunction(internalTexture._comparisonFunction) : undefined,
+            maxAnisotropy: internalTexture._cachedAnisotropicFilteringLevel ?? 1,
+        };
+    }
+
+    public getSampler(internalTexture: InternalTexture, bypassCache = false): GPUSampler {
+        const hash = bypassCache ? 0 : WebGPUCacheSampler._GetSamplerHashCode(internalTexture);
+
+        let sampler = bypassCache ? undefined : this._samplers[hash];
+        if (!sampler) {
+            sampler =  this._device.createSampler(WebGPUCacheSampler._GetSamplerDescriptor(internalTexture));
+            if (!bypassCache) {
+                this._samplers[hash] = sampler;
+            }
+        }
+
+        return sampler;
+    }
+}

+ 306 - 0
src/Engines/WebGPU/webgpuConstants.ts

@@ -0,0 +1,306 @@
+/** @hidden */
+export enum ExtensionName {
+    DepthClamping = "depth-clamping",
+    Depth24UnormStencil8 = "depth24unorm-stencil8",
+    Depth32FloatStencil8 = "depth32float-stencil8",
+    PipelineStatisticsQuery = "pipeline-statistics-query",
+    TextureCompressionBC = "texture-compression-bc",
+    TimestampQuery = "timestamp-query"
+}
+/** @hidden */
+export enum AddressMode {
+    ClampToEdge = "clamp-to-edge",
+    Repeat = "repeat",
+    MirrorRepeat = "mirror-repeat"
+}
+/** @hidden */
+export enum BindingType {
+    UniformBuffer = "uniform-buffer",
+    StorageBuffer = "storage-buffer",
+    ReadonlyStorageBuffer = "readonly-storage-buffer",
+    Sampler = "sampler",
+    ComparisonSampler = "comparison-sampler",
+    SampledTexture = "sampled-texture",
+    ReadonlyStorageTexture = "readonly-storage-texture",
+    WriteonlyStorageTexture = "writeonly-storage-texture"
+}
+/** @hidden */
+export enum BlendFactor {
+    Zero = "zero",
+    One = "one",
+    SrcColor = "src-color",
+    OneMinusSrcColor = "one-minus-src-color",
+    SrcAlpha = "src-alpha",
+    OneMinusSrcAlpha = "one-minus-src-alpha",
+    DstColor = "dst-color",
+    OneMinusDstColor = "one-minus-dst-color",
+    DstAlpha = "dst-alpha",
+    OneMinusDstAlpha = "one-minus-dst-alpha",
+    SrcAlphaSaturated = "src-alpha-saturated",
+    BlendColor = "blend-color",
+    OneMinusBlendColor = "one-minus-blend-color"
+}
+/** @hidden */
+export enum BlendOperation {
+    Add = "add",
+    Subtract = "subtract",
+    ReverseSubtract = "reverse-subtract",
+    Min = "min",
+    Max = "max"
+}
+/** @hidden */
+export enum CompareFunction {
+    Never = "never",
+    Less = "less",
+    Equal = "equal",
+    LessEqual = "less-equal",
+    Greater = "greater",
+    NotEqual = "not-equal",
+    GreaterEqual = "greater-equal",
+    Always = "always"
+}
+/** @hidden */
+export enum CullMode {
+    None = "none",
+    Front = "front",
+    Back = "back"
+}
+/** @hidden */
+export enum FilterMode {
+    Nearest = "nearest",
+    Linear = "linear"
+}
+/** @hidden */
+export enum FrontFace {
+    CCW = "ccw",
+    CW = "cw"
+}
+/** @hidden */
+export enum IndexFormat {
+    Uint16 = "uint16",
+    Uint32 = "uint32"
+}
+/** @hidden */
+export enum InputStepMode {
+    Vertex = "vertex",
+    Instance = "instance"
+}
+/** @hidden */
+export enum LoadOp {
+    Load = "load"
+}
+/** @hidden */
+export enum PrimitiveTopology {
+    PointList = "point-list",
+    LineList = "line-list",
+    LineStrip = "line-strip",
+    TriangleList = "triangle-list",
+    TriangleStrip = "triangle-strip"
+}
+/** @hidden */
+export enum StencilOperation {
+    Keep = "keep",
+    Zero = "zero",
+    Replace = "replace",
+    Invert = "invert",
+    IncrementClamp = "increment-clamp",
+    DecrementClamp = "decrement-clamp",
+    IncrementWrap = "increment-wrap",
+    DecrementWrap = "decrement-wrap"
+}
+/** @hidden */
+export enum StoreOp {
+    Store = "store",
+    Clear = "clear"
+}
+/** @hidden */
+export enum TextureDimension {
+    E1d = "1d",
+    E2d = "2d",
+    E3d = "3d"
+}
+/** @hidden */
+export enum TextureFormat {
+    // 8-bit formats
+    R8Unorm = "r8unorm",
+    R8Snorm = "r8snorm",
+    R8Uint = "r8uint",
+    R8Sint = "r8sint",
+
+    // 16-bit formats
+    R16Uint = "r16uint",
+    R16Sint = "r16sint",
+    R16Float = "r16float",
+    RG8Unorm = "rg8unorm",
+    RG8Snorm = "rg8snorm",
+    RG8Uint = "rg8uint",
+    RG8Sint = "rg8sint",
+
+    // 32-bit formats
+    R32Uint = "r32uint",
+    R32Sint = "r32sint",
+    R32Float = "r32float",
+    RG16Uint = "rg16uint",
+    RG16Sint = "rg16sint",
+    RG16Float = "rg16float",
+    RGBA8Unorm = "rgba8unorm",
+    RGBA8UnormSRGB = "rgba8unorm-srgb",
+    RGBA8Snorm = "rgba8snorm",
+    RGBA8Uint = "rgba8uint",
+    RGBA8Sint = "rgba8sint",
+    BGRA8Unorm = "bgra8unorm",
+    BGRA8UnormSRGB = "bgra8unorm-srgb",
+    // Packed 32-bit formats
+    RGB9E5UFloat = "rgb9e5ufloat",
+    RGB10A2Unorm = "rgb10a2unorm",
+    RG11B10UFloat = "rg11b10ufloat",
+
+    // 64-bit formats
+    RG32Uint = "rg32uint",
+    RG32Sint = "rg32sint",
+    RG32Float = "rg32float",
+    RGBA16Uint = "rgba16uint",
+    RGBA16Sint = "rgba16sint",
+    RGBA16Float = "rgba16float",
+
+    // 128-bit formats
+    RGBA32Uint = "rgba32uint",
+    RGBA32Sint = "rgba32sint",
+    RGBA32Float = "rgba32float",
+
+    // Depth and stencil formats
+    Stencil8 = "stencil8",
+    Depth16Unorm = "depth16unorm",
+    Depth24Plus = "depth24plus",
+    Depth24PlusStencil8 = "depth24plus-stencil8",
+    Depth32Float = "depth32float",
+
+    // BC compressed formats usable if "texture-compression-bc" is both
+    // supported by the device/user agent and enabled in requestDevice.
+    BC1RGBAUNorm = "bc1-rgba-unorm",
+    BC1RGBAUnormSRGB = "bc1-rgba-unorm-srgb",
+    BC2RGBAUnorm = "bc2-rgba-unorm",
+    BC2RGBAUnormSRGB = "bc2-rgba-unorm-srgb",
+    BC3RGBAUnorm = "bc3-rgba-unorm",
+    BC3RGBAUnormSRGB = "bc3-rgba-unorm-srgb",
+    BC4RUnorm = "bc4-r-unorm",
+    BC4RSnorm = "bc4-r-snorm",
+    BC5RGUnorm = "bc5-rg-unorm",
+    BC5RGSnorm = "bc5-rg-snorm",
+    BC6HRGBUFloat = "bc6h-rgb-ufloat",
+    BC6HRGBFloat = "bc6h-rgb-float",
+    BC7RGBAUnorm = "bc7-rgba-unorm",
+    BC7RGBAUnormSRGB = "bc7-rgba-unorm-srgb",
+
+    // "depth24unorm-stencil8" feature
+    Depth24UnormStencil8 = "depth24unorm-stencil8",
+
+    // "depth32float-stencil8" feature
+    Depth32FloatStencil8 = "depth32float-stencil8"
+}
+/** @hidden */
+export enum TextureComponentType {
+    Float = "float",
+    Sint = "sint",
+    Uint = "uint",
+    // Texture is used with comparison sampling only.
+    DepthComparison = "depth-comparison"
+}
+/** @hidden */
+export enum TextureViewDimension {
+    E1d = "1d",
+    E2d = "2d",
+    E2dArray = "2d-array",
+    Cube = "cube",
+    CubeArray = "cube-array",
+    E3d = "3d"
+}
+/** @hidden */
+export enum VertexFormat {
+    Uchar2 = "uchar2",
+    Uchar4 = "uchar4",
+    Char2 = "char2",
+    Char4 = "char4",
+    Uchar2Norm = "uchar2norm",
+    Uchar4Norm = "uchar4norm",
+    Char2Norm = "char2norm",
+    Char4Norm = "char4norm",
+    Ushort2 = "ushort2",
+    Ushort4 = "ushort4",
+    Short2 = "short2",
+    Short4 = "short4",
+    Ushort2Norm = "ushort2norm",
+    Ushort4Norm = "ushort4norm",
+    Short2Norm = "short2norm",
+    Short4Norm = "short4norm",
+    Half2 = "half2",
+    Half4 = "half4",
+    Float = "float",
+    Float2 = "float2",
+    Float3 = "float3",
+    Float4 = "float4",
+    Uint = "uint",
+    Uint2 = "uint2",
+    Uint3 = "uint3",
+    Uint4 = "uint4",
+    Int = "int",
+    Int2 = "int2",
+    Int3 = "int3",
+    Int4 = "int4"
+}
+/** @hidden */
+export enum TextureAspect {
+    All = "all",
+    StencilOnly = "stencil-only",
+    DepthOnly = "depth-only"
+}
+/** @hidden */
+export enum CompilationMessageType {
+    Error = "error",
+    Warning = "warning",
+    Info = "info"
+}
+/** @hidden */
+export enum QueryType {
+    Occlusion = "occlusion"
+}
+/** @hidden */
+export enum BufferUsage {
+    MapRead = 1,
+    MapWrite = 2,
+    CopySrc = 4,
+    CopyDst = 8,
+    Index = 16,
+    Vertex = 32,
+    Uniform = 64,
+    Storage = 128,
+    Indirect = 256,
+    QueryResolve = 512
+}
+/** @hidden */
+export enum ColorWrite {
+    Red = 1,
+    Green = 2,
+    Blue = 4,
+    Alpha = 8,
+    All = 15
+}
+/** @hidden */
+export enum ShaderStage {
+    Vertex = 1,
+    Fragment = 2,
+    Compute = 4
+}
+/** @hidden */
+export enum TextureUsage {
+    CopySrc = 1,
+    CopyDst = 2,
+    Sampled = 4,
+    Storage = 8,
+    OutputAttachment = 16
+}
+/** @hidden */
+export enum MapMode {
+    Read = 1,
+    Write = 2
+}

+ 70 - 0
src/Engines/WebGPU/webgpuHardwareTexture.ts

@@ -0,0 +1,70 @@
+import { HardwareTextureWrapper } from '../../Materials/Textures/hardwareTextureWrapper';
+import { InternalTextureSource } from '../../Materials/Textures/internalTexture';
+import { Scalar } from '../../Maths/math.scalar';
+import { Nullable } from '../../types';
+import * as WebGPUConstants from './webgpuConstants';
+
+/** @hidden */
+export class WebGPUHardwareTexture implements HardwareTextureWrapper {
+
+    private _webgpuTexture: Nullable<GPUTexture>;
+    private _webgpuMSAATexture: Nullable<GPUTexture>;
+
+    public get underlyingResource(): Nullable<GPUTexture> {
+        return this._webgpuTexture;
+    }
+
+    public get msaaTexture(): Nullable<GPUTexture> {
+        return this._webgpuMSAATexture;
+    }
+
+    public set msaaTexture(texture: Nullable<GPUTexture>) {
+        this._webgpuMSAATexture = texture;
+    }
+
+    public view: Nullable<GPUTextureView>;
+    public format: GPUTextureFormat = WebGPUConstants.TextureFormat.RGBA8Unorm;
+    public textureUsages = 0;
+
+    constructor(existingTexture: Nullable<GPUTexture> = null) {
+        this._webgpuTexture = existingTexture;
+        this._webgpuMSAATexture = null;
+        this.view = null;
+    }
+
+    public set(hardwareTexture: GPUTexture): void {
+        this._webgpuTexture = hardwareTexture;
+    }
+
+    public setMSAATexture(hardwareTexture: GPUTexture): void {
+        this._webgpuMSAATexture = hardwareTexture;
+    }
+
+    public setUsage(textureSource: number, generateMipMaps: boolean, isCube: boolean, width: number, height: number): void {
+        generateMipMaps = textureSource === InternalTextureSource.RenderTarget ? false : generateMipMaps;
+
+        this.createView({
+            dimension: isCube ? WebGPUConstants.TextureViewDimension.Cube : WebGPUConstants.TextureViewDimension.E2d,
+            mipLevelCount: generateMipMaps ? Scalar.ILog2(Math.max(width, height)) + 1 : 1,
+            baseArrayLayer: 0,
+            baseMipLevel: 0,
+            aspect: WebGPUConstants.TextureAspect.All
+        });
+    }
+
+    public createView(descriptor?: GPUTextureViewDescriptor): void {
+        this.view = this._webgpuTexture!.createView(descriptor);
+    }
+
+    public reset(): void {
+        this._webgpuTexture = null;
+        this._webgpuMSAATexture = null;
+        this.view = null;
+    }
+
+    public release(): void {
+        this._webgpuTexture?.destroy();
+        this._webgpuMSAATexture?.destroy();
+        this.reset();
+    }
+}

+ 475 - 0
src/Engines/WebGPU/webgpuPipelineContext.ts

@@ -0,0 +1,475 @@
+import { IPipelineContext } from '../IPipelineContext';
+import { Nullable } from '../../types';
+import { WebGPUEngine } from '../webgpuEngine';
+import { InternalTexture } from '../../Materials/Textures/internalTexture';
+import { Effect } from '../../Materials/effect';
+import { WebGPUShaderProcessingContext } from './webgpuShaderProcessingContext';
+import { UniformBuffer } from "../../Materials/uniformBuffer";
+import { IMatrixLike, IVector2Like, IVector3Like, IVector4Like, IColor3Like, IColor4Like } from '../../Maths/math.like';
+
+const _uniformSizes: { [type: string]: number } = {
+    "bool": 1,
+    "int": 1,
+    "float": 1,
+    "vec2": 2,
+    "ivec2": 2,
+    "vec3": 3,
+    "ivec3": 3,
+    "vec4": 4,
+    "ivec4": 4,
+    "mat2": 4,
+    "mat3": 9,
+    "mat4": 16
+};
+
+/** @hidden */
+export interface IWebGPUPipelineContextSamplerCache {
+    samplerBinding: number;
+    firstTextureName: string;
+}
+
+/** @hidden */
+export interface IWebGPUPipelineContextTextureCache {
+    textureBinding: number;
+    texture: InternalTexture;
+}
+
+/** @hidden */
+export interface IWebGPUPipelineContextVertexInputsCache {
+    indexBuffer: Nullable<GPUBuffer>;
+    indexOffset: number;
+
+    vertexStartSlot: number;
+    vertexBuffers: GPUBuffer[];
+    vertexOffsets: number[];
+}
+
+/** @hidden */
+export interface IWebGPURenderPipelineStageDescriptor {
+    vertexStage: GPUProgrammableStageDescriptor;
+    fragmentStage?: GPUProgrammableStageDescriptor;
+}
+
+/** @hidden */
+export class WebGPUPipelineContext implements IPipelineContext {
+    public engine: WebGPUEngine;
+
+    public shaderProcessingContext: WebGPUShaderProcessingContext;
+
+    public leftOverUniformsByName: { [name: string]: string };
+
+    public sources: {
+        vertex: string,
+        fragment: string,
+        rawVertex: string,
+        rawFragment: string,
+    };
+
+    public stages: Nullable<IWebGPURenderPipelineStageDescriptor>;
+
+    public samplers: { [name: string]: Nullable<IWebGPUPipelineContextSamplerCache> } = { };
+    public textures: { [name: string]: Nullable<IWebGPUPipelineContextTextureCache> } = { };
+
+    public bindGroupLayouts: GPUBindGroupLayout[];
+
+    /**
+     * Stores the uniform buffer
+     */
+    public uniformBuffer: Nullable<UniformBuffer>;
+
+    // Default implementation.
+    public onCompiled?: () => void;
+
+    public get isAsync() {
+        return false;
+    }
+
+    public get isReady(): boolean {
+        if (this.stages) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /** @hidden */
+    public _name: string;
+
+    constructor(shaderProcessingContext: WebGPUShaderProcessingContext, engine: WebGPUEngine) {
+        this._name = "unnamed";
+        this.shaderProcessingContext = shaderProcessingContext;
+        this.leftOverUniformsByName = {};
+    }
+
+    public _handlesSpectorRebuildCallback(onCompiled: (program: any) => void): void {
+        // Nothing to do yet for spector.
+    }
+
+    public _fillEffectInformation(effect: Effect, uniformBuffersNames: { [key: string]: number }, uniformsNames: string[], uniforms: { [key: string]: Nullable<WebGLUniformLocation> }, samplerList: string[], samplers: { [key: string]: number }, attributesNames: string[], attributes: number[]) {
+        const engine = this.engine;
+
+        // TODO WEBGPU. Cleanup SEB on this entire function. Should not need anything in here or almost.
+        let effectAvailableUniforms = engine.getUniforms(this, uniformsNames);
+        effectAvailableUniforms.forEach((uniform, index) => {
+            uniforms[uniformsNames[index]] = uniform;
+        });
+
+        // Prevent Memory Leak by reducing the number of string, refer to the string instead of copy.
+        effect._fragmentSourceCode = "";
+        effect._vertexSourceCode = "";
+        // this._fragmentSourceCodeOverride = "";
+        // this._vertexSourceCodeOverride = "";
+
+        const foundSamplers = this.shaderProcessingContext.availableSamplers;
+        let index: number;
+        for (index = 0; index < samplerList.length; index++) {
+            const name = samplerList[index];
+            const sampler = foundSamplers[samplerList[index]];
+
+            if (sampler == null || sampler == undefined) {
+                samplerList.splice(index, 1);
+                index--;
+            }
+            else {
+                samplers[name] = index;
+            }
+        }
+
+        for (let attr of engine.getAttributes(this, attributesNames)) {
+            attributes.push(attr);
+        }
+
+        // Build the uniform layout for the left over uniforms.
+        this.buildUniformLayout();
+    }
+
+    /** @hidden */
+    /**
+     * Build the uniform buffer used in the material.
+     */
+    public buildUniformLayout(): void {
+        if (!this.shaderProcessingContext.leftOverUniforms.length) {
+            return;
+        }
+
+        this.uniformBuffer = new UniformBuffer(this.engine, undefined, undefined, "leftOver-" + this._name);
+
+        for (let leftOverUniform of this.shaderProcessingContext.leftOverUniforms) {
+            const size = _uniformSizes[leftOverUniform.type];
+            this.uniformBuffer.addUniform(leftOverUniform.name, size, leftOverUniform.length);
+            // TODO WEBGPU. Replace with info from uniform buffer class
+            this.leftOverUniformsByName[leftOverUniform.name] = leftOverUniform.type;
+        }
+
+        this.uniformBuffer.create();
+    }
+
+    /**
+     * Release all associated resources.
+     **/
+    public dispose() {
+        if (this.uniformBuffer) {
+            this.uniformBuffer.dispose();
+        }
+    }
+
+    /**
+     * Sets an integer value on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param value Value to be set.
+     */
+    public setInt(uniformName: string, value: number): void {
+        if (!this.uniformBuffer || !this.leftOverUniformsByName[uniformName]) {
+            return;
+        }
+        this.uniformBuffer.updateInt(uniformName, value);
+    }
+
+    /**
+     * Sets an int2 value on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param x First int in int2.
+     * @param y Second int in int2.
+     */
+    public setInt2(uniformName: string, x: number, y: number): void {
+        if (!this.uniformBuffer || !this.leftOverUniformsByName[uniformName]) {
+            return;
+        }
+        this.uniformBuffer.updateInt2(uniformName, x, y);
+    }
+
+    /**
+     * Sets an int3 value on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param x First int in int3.
+     * @param y Second int in int3.
+     * @param z Third int in int3.
+     */
+    public setInt3(uniformName: string, x: number, y: number, z: number): void {
+        if (!this.uniformBuffer || !this.leftOverUniformsByName[uniformName]) {
+            return;
+        }
+        this.uniformBuffer.updateInt3(uniformName, x, y, z);
+    }
+
+    /**
+     * Sets an int4 value on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param x First int in int4.
+     * @param y Second int in int4.
+     * @param z Third int in int4.
+     * @param w Fourth int in int4.
+     */
+    public setInt4(uniformName: string, x: number, y: number, z: number, w: number): void {
+        if (!this.uniformBuffer || !this.leftOverUniformsByName[uniformName]) {
+            return;
+        }
+        this.uniformBuffer.updateInt4(uniformName, x, y, z, w);
+    }
+
+    /**
+     * Sets an int array on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param array array to be set.
+     */
+    public setIntArray(uniformName: string, array: Int32Array): void {
+        if (!this.uniformBuffer || !this.leftOverUniformsByName[uniformName]) {
+            return;
+        }
+        this.uniformBuffer.updateIntArray(uniformName, array);
+    }
+
+    /**
+     * Sets an int array 2 on a uniform variable. (Array is specified as single array eg. [1,2,3,4] will result in [[1,2],[3,4]] in the shader)
+     * @param uniformName Name of the variable.
+     * @param array array to be set.
+     */
+    public setIntArray2(uniformName: string, array: Int32Array): void {
+        this.setIntArray(uniformName, array);
+    }
+
+    /**
+     * Sets an int array 3 on a uniform variable. (Array is specified as single array eg. [1,2,3,4,5,6] will result in [[1,2,3],[4,5,6]] in the shader)
+     * @param uniformName Name of the variable.
+     * @param array array to be set.
+     */
+    public setIntArray3(uniformName: string, array: Int32Array): void {
+        this.setIntArray(uniformName, array);
+    }
+
+    /**
+     * Sets an int array 4 on a uniform variable. (Array is specified as single array eg. [1,2,3,4,5,6,7,8] will result in [[1,2,3,4],[5,6,7,8]] in the shader)
+     * @param uniformName Name of the variable.
+     * @param array array to be set.
+     */
+    public setIntArray4(uniformName: string, array: Int32Array): void {
+        this.setIntArray(uniformName, array);
+    }
+
+    /**
+     * Sets an array on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param array array to be set.
+     */
+    public setArray(uniformName: string, array: number[]): void {
+        if (!this.uniformBuffer || !this.leftOverUniformsByName[uniformName]) {
+            return;
+        }
+        this.uniformBuffer.updateArray(uniformName, array);
+    }
+
+    /**
+     * Sets an array 2 on a uniform variable. (Array is specified as single array eg. [1,2,3,4] will result in [[1,2],[3,4]] in the shader)
+     * @param uniformName Name of the variable.
+     * @param array array to be set.
+     */
+    public setArray2(uniformName: string, array: number[]): void {
+        this.setArray(uniformName, array);
+    }
+
+    /**
+     * Sets an array 3 on a uniform variable. (Array is specified as single array eg. [1,2,3,4,5,6] will result in [[1,2,3],[4,5,6]] in the shader)
+     * @param uniformName Name of the variable.
+     * @param array array to be set.
+     * @returns this effect.
+     */
+    public setArray3(uniformName: string, array: number[]): void {
+        this.setArray(uniformName, array);
+    }
+
+    /**
+     * Sets an array 4 on a uniform variable. (Array is specified as single array eg. [1,2,3,4,5,6,7,8] will result in [[1,2,3,4],[5,6,7,8]] in the shader)
+     * @param uniformName Name of the variable.
+     * @param array array to be set.
+     */
+    public setArray4(uniformName: string, array: number[]): void {
+        this.setArray(uniformName, array);
+    }
+
+    /**
+     * Sets matrices on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param matrices matrices to be set.
+     */
+    public setMatrices(uniformName: string, matrices: Float32Array): void {
+        if (!this.uniformBuffer || !this.leftOverUniformsByName[uniformName]) {
+            return;
+        }
+        this.uniformBuffer.updateMatrices(uniformName, matrices);
+    }
+
+    /**
+     * Sets matrix on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param matrix matrix to be set.
+     */
+    public setMatrix(uniformName: string, matrix: IMatrixLike): void {
+        if (!this.uniformBuffer || !this.leftOverUniformsByName[uniformName]) {
+            return;
+        }
+        this.uniformBuffer.updateMatrix(uniformName, matrix);
+    }
+
+    /**
+     * Sets a 3x3 matrix on a uniform variable. (Speicified as [1,2,3,4,5,6,7,8,9] will result in [1,2,3][4,5,6][7,8,9] matrix)
+     * @param uniformName Name of the variable.
+     * @param matrix matrix to be set.
+     */
+    public setMatrix3x3(uniformName: string, matrix: Float32Array): void {
+        if (!this.uniformBuffer || !this.leftOverUniformsByName[uniformName]) {
+            return;
+        }
+        this.uniformBuffer.updateMatrix3x3(uniformName, matrix);
+    }
+
+    /**
+     * Sets a 2x2 matrix on a uniform variable. (Speicified as [1,2,3,4] will result in [1,2][3,4] matrix)
+     * @param uniformName Name of the variable.
+     * @param matrix matrix to be set.
+     */
+    public setMatrix2x2(uniformName: string, matrix: Float32Array): void {
+        if (!this.uniformBuffer || !this.leftOverUniformsByName[uniformName]) {
+            return;
+        }
+        this.uniformBuffer.updateMatrix2x2(uniformName, matrix);
+    }
+
+    /**
+     * Sets a float on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param value value to be set.
+     * @returns this effect.
+     */
+    public setFloat(uniformName: string, value: number): void {
+        if (!this.uniformBuffer || !this.leftOverUniformsByName[uniformName]) {
+            return;
+        }
+        this.uniformBuffer.updateFloat(uniformName, value);
+    }
+
+    /**
+     * Sets a Vector2 on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param vector2 vector2 to be set.
+     */
+    public setVector2(uniformName: string, vector2: IVector2Like): void {
+        this.setFloat2(uniformName, vector2.x, vector2.y);
+    }
+
+    /**
+     * Sets a float2 on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param x First float in float2.
+     * @param y Second float in float2.
+     */
+    public setFloat2(uniformName: string, x: number, y: number): void {
+        if (!this.uniformBuffer || !this.leftOverUniformsByName[uniformName]) {
+            return;
+        }
+        this.uniformBuffer.updateFloat2(uniformName, x, y);
+    }
+
+    /**
+     * Sets a Vector3 on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param vector3 Value to be set.
+     */
+    public setVector3(uniformName: string, vector3: IVector3Like): void {
+        this.setFloat3(uniformName, vector3.x, vector3.y, vector3.z);
+    }
+
+    /**
+     * Sets a float3 on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param x First float in float3.
+     * @param y Second float in float3.
+     * @param z Third float in float3.
+     */
+    public setFloat3(uniformName: string, x: number, y: number, z: number): void {
+        if (!this.uniformBuffer || !this.leftOverUniformsByName[uniformName]) {
+            return;
+        }
+        this.uniformBuffer.updateFloat3(uniformName, x, y, z);
+    }
+
+    /**
+     * Sets a Vector4 on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param vector4 Value to be set.
+     */
+    public setVector4(uniformName: string, vector4: IVector4Like): void {
+        this.setFloat4(uniformName, vector4.x, vector4.y, vector4.z, vector4.w);
+    }
+
+    /**
+     * Sets a float4 on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param x First float in float4.
+     * @param y Second float in float4.
+     * @param z Third float in float4.
+     * @param w Fourth float in float4.
+     * @returns this effect.
+     */
+    public setFloat4(uniformName: string, x: number, y: number, z: number, w: number): void {
+        if (!this.uniformBuffer || !this.leftOverUniformsByName[uniformName]) {
+            return;
+        }
+        this.uniformBuffer.updateFloat4(uniformName, x, y, z, w);
+    }
+
+    /**
+     * Sets a Color3 on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param color3 Value to be set.
+     */
+    public setColor3(uniformName: string, color3: IColor3Like): void {
+        this.setFloat3(uniformName, color3.r, color3.g, color3.b);
+    }
+
+    /**
+     * Sets a Color4 on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param color3 Value to be set.
+     * @param alpha Alpha value to be set.
+     */
+    public setColor4(uniformName: string, color3: IColor3Like, alpha: number): void {
+        this.setFloat4(uniformName, color3.r, color3.g, color3.b, alpha);
+    }
+
+    /**
+     * Sets a Color4 on a uniform variable
+     * @param uniformName defines the name of the variable
+     * @param color4 defines the value to be set
+     */
+    public setDirectColor4(uniformName: string, color4: IColor4Like): void {
+        this.setFloat4(uniformName, color4.r, color4.g, color4.b, color4.a);
+    }
+
+    public _getVertexShaderCode(): string | null {
+        return this.sources?.vertex;
+    }
+
+    public _getFragmentShaderCode(): string | null {
+        return this.sources?.fragment;
+    }
+}

+ 27 - 0
src/Engines/WebGPU/webgpuRenderPassWrapper.ts

@@ -0,0 +1,27 @@
+import { Nullable } from '../../types';
+import { WebGPUHardwareTexture } from './webgpuHardwareTexture';
+
+/** @hidden */
+export class WebGPURenderPassWrapper {
+    public renderPassDescriptor: Nullable<GPURenderPassDescriptor>;
+    public renderPass: Nullable<GPURenderPassEncoder>;
+    public colorAttachmentViewDescriptor: Nullable<GPUTextureViewDescriptor>;
+    public depthAttachmentViewDescriptor: Nullable<GPUTextureViewDescriptor>;
+    public colorAttachmentGPUTextures: WebGPUHardwareTexture[] = [];
+    public depthTextureFormat: GPUTextureFormat | undefined;
+
+    constructor() {
+        this.reset();
+    }
+
+    public reset(fullReset = false): void {
+        this.renderPass = null;
+        if (fullReset) {
+            this.renderPassDescriptor = null;
+            this.colorAttachmentViewDescriptor = null;
+            this.depthAttachmentViewDescriptor = null;
+            this.colorAttachmentGPUTextures = [];
+            this.depthTextureFormat = undefined;
+        }
+    }
+}

+ 34 - 0
src/Engines/WebGPU/webgpuShaderManager.ts

@@ -0,0 +1,34 @@
+import { Effect } from "../../Materials/effect";
+import { IWebGPURenderPipelineStageDescriptor } from "./webgpuPipelineContext";
+
+/** @hidden */
+export class WebGPUShaderManager {
+
+    private _shaders: { [name: string]: IWebGPURenderPipelineStageDescriptor } = {};
+    private _device: GPUDevice;
+
+    constructor(device: GPUDevice) {
+        this._device = device;
+    }
+
+    public getCompiledShaders(name: string): IWebGPURenderPipelineStageDescriptor {
+        let shaders = this._shaders[name];
+        if (!shaders) {
+            shaders = this._shaders[name] = {
+                vertexStage: {
+                    module: this._device.createShaderModule({
+                        code: Effect.ShadersStore[name + "VertexShader"]
+                    }),
+                    entryPoint: 'main'
+                },
+                fragmentStage: {
+                    module: this._device.createShaderModule({
+                        code: Effect.ShadersStore[name + "PixelShader"]
+                    }),
+                    entryPoint: 'main'
+                }
+            };
+        }
+        return shaders;
+    }
+}

+ 119 - 0
src/Engines/WebGPU/webgpuShaderProcessingContext.ts

@@ -0,0 +1,119 @@
+import { ShaderProcessingContext } from "../Processors/shaderProcessingOptions";
+
+const _maxSets = 4;
+const _maxBindingsPerSet = 16;
+
+// all types not listed are assumed to consume 1 location
+const _webGLTypeToLocationSize: { [key: string]: number } = {
+    "mat2": 2,
+    "mat3": 3,
+    "mat4": 4,
+};
+
+/** @hidden */
+export interface WebGPUBindingInfo {
+    setIndex: number;
+    bindingIndex: number;
+}
+
+/** @hidden */
+export interface WebGPUTextureSamplerBindingDescription {
+    sampler: WebGPUBindingInfo;
+    isTextureArray: boolean;
+    textures: Array<WebGPUBindingInfo>;
+}
+
+/** @hidden
+ *  If the binding is a UBO, isSampler=isTexture=false
+*/
+export interface WebGPUBindingDescription {
+    name: string;
+    usedInVertex: boolean;
+    usedInFragment: boolean;
+
+    isSampler: boolean;
+    isComparisonSampler?: boolean;
+
+    isTexture: boolean;
+    componentType?: GPUTextureComponentType;
+    textureDimension?: GPUTextureViewDimension;
+}
+
+/**
+ * @hidden
+ */
+export class WebGPUShaderProcessingContext implements ShaderProcessingContext {
+    public uboNextBindingIndex: number;
+    public freeSetIndex: number;
+    public freeBindingIndex: number;
+
+    public availableVaryings: { [key: string]: number };
+    public availableAttributes: { [key: string]: number };
+    public availableUBOs: { [key: string]: { setIndex: number, bindingIndex: number} };
+    public availableSamplers: { [key: string]: WebGPUTextureSamplerBindingDescription };
+
+    public leftOverUniforms: { name: string, type: string, length: number }[];
+
+    public orderedAttributes: string[];
+    public orderedUBOsAndSamplers: WebGPUBindingDescription[][];
+
+    private _attributeNextLocation: number;
+    private _varyingNextLocation: number;
+
+    constructor() {
+        this._attributeNextLocation = 0;
+        this._varyingNextLocation = 0;
+        this.freeSetIndex = 2;
+        this.freeBindingIndex = 0;
+
+        this.availableVaryings = { };
+        this.availableAttributes = { };
+        this.availableUBOs = { };
+        this.availableSamplers = { };
+
+        this.orderedAttributes = [];
+        this.orderedUBOsAndSamplers = [];
+
+        this.leftOverUniforms = [];
+    }
+
+    public getAttributeNextLocation(dataType: string, arrayLength: number = 0): number {
+        const index = this._attributeNextLocation;
+
+        this._attributeNextLocation += (_webGLTypeToLocationSize[dataType] ?? 1) * (arrayLength || 1);
+
+        return index;
+    }
+
+    public getVaryingNextLocation(dataType: string, arrayLength: number = 0): number {
+        const index = this._varyingNextLocation;
+
+        this._varyingNextLocation += (_webGLTypeToLocationSize[dataType] ?? 1) * (arrayLength || 1);
+
+        return index;
+    }
+
+    public getNextFreeUBOBinding() {
+        return this._getNextFreeBinding(1);
+    }
+
+    private _getNextFreeBinding(bindingCount: number) {
+        if (this.freeBindingIndex > _maxBindingsPerSet - bindingCount) {
+            this.freeSetIndex++;
+            this.freeBindingIndex = 0;
+        }
+
+        if (this.freeSetIndex === _maxSets) {
+            throw "Too many textures or UBOs have been declared and it is not supprted in WebGPU.";
+        }
+
+        const returnValue = {
+            setIndex: this.freeSetIndex,
+            bindingIndex: this.freeBindingIndex
+        };
+
+        this.freeBindingIndex += bindingCount;
+
+        return returnValue;
+    }
+}

+ 448 - 0
src/Engines/WebGPU/webgpuShaderProcessors.ts

@@ -0,0 +1,448 @@
+import { Nullable } from '../../types';
+import { IShaderProcessor } from '../Processors/iShaderProcessor';
+import { ShaderProcessingContext } from "../Processors/shaderProcessingOptions";
+import { WebGPUTextureSamplerBindingDescription, WebGPUShaderProcessingContext } from './webgpuShaderProcessingContext';
+import * as WebGPUConstants from './webgpuConstants';
+import { ShaderCodeInliner } from '../Processors/shaderCodeInliner';
+import { Logger } from '../../Misc/logger';
+
+const dbgShowDebugInliningProcess = false;
+
+const _knownUBOs: { [key: string]: { setIndex: number, bindingIndex: number} } = {
+    "Scene": { setIndex: 0, bindingIndex: 0 },
+    "Light0": { setIndex: 0, bindingIndex: 5 },
+    "Light1": { setIndex: 0, bindingIndex: 6 },
+    "Light2": { setIndex: 0, bindingIndex: 7 },
+    "Light3": { setIndex: 0, bindingIndex: 8 },
+    "Light4": { setIndex: 0, bindingIndex: 9 },
+    "Light5": { setIndex: 0, bindingIndex: 10 },
+    "Light6": { setIndex: 0, bindingIndex: 11 },
+    "Light7": { setIndex: 0, bindingIndex: 12 },
+    "Light8": { setIndex: 0, bindingIndex: 13 },
+    "Material": { setIndex: 1, bindingIndex: 0 },
+    "Mesh": { setIndex: 1, bindingIndex: 1 },
+};
+
+const _knownSamplers: { [key: string]: WebGPUTextureSamplerBindingDescription } = {
+    "environmentBrdfSampler": { sampler: { setIndex: 0, bindingIndex: 1 }, isTextureArray: false, textures: [{ setIndex: 0, bindingIndex: 2 }] },
+    // "reflectionSampler": { setIndex: 0, bindingIndex: 3 },
+};
+
+// TODO WEBGPU. sampler3D
+const _samplerFunctionByWebGLSamplerType: { [key: string]: string } = {
+    "sampler2D": "sampler2D",
+    "sampler2DShadow": "sampler2DShadow",
+    "sampler2DArrayShadow": "sampler2DArrayShadow",
+    "samplerCube": "samplerCube"
+};
+
+const _textureTypeByWebGLSamplerType: { [key: string]: string } = {
+    "sampler2D": "texture2D",
+    "sampler2DShadow": "texture2D",
+    "sampler2DArrayShadow": "texture2DArray",
+    "samplerCube": "textureCube",
+    "samplerCubeArray": "textureCubeArray"
+};
+
+const _gpuTextureViewDimensionByWebGPUTextureType: { [key: string]: GPUTextureViewDimension } = {
+    "textureCube": WebGPUConstants.TextureViewDimension.Cube,
+    "textureCubeArray": WebGPUConstants.TextureViewDimension.CubeArray,
+    "texture2D": WebGPUConstants.TextureViewDimension.E2d,
+    "texture2DArray": WebGPUConstants.TextureViewDimension.E2dArray,
+};
+
+// if the webgl sampler type is not listed in this array, "sampler" is taken by default
+const _samplerTypeByWebGLSamplerType: { [key: string]: string } = {
+    "sampler2DShadow": "samplerShadow",
+    "sampler2DArrayShadow": "samplerShadow",
+};
+
+const _isComparisonSamplerByWebGPUSamplerType: { [key: string]: boolean } = {
+    "samplerShadow": true,
+    "samplerArrayShadow": true,
+    "sampler": false,
+};
+
+/** @hidden */
+export class WebGPUShaderProcessor implements IShaderProcessor {
+
+    protected _missingVaryings: Array<string> = [];
+    protected _textureArrayProcessing: Array<string> = [];
+    protected _preProcessors: { [key: string]: string };
+
+    private _getArraySize(name: string, preProcessors: { [key: string]: string }): [string, number] {
+        let length = 0;
+        const startArray = name.indexOf("[");
+        const endArray = name.indexOf("]");
+        if (startArray > 0 && endArray > 0) {
+            const lengthInString = name.substring(startArray + 1, endArray);
+            length = +(lengthInString);
+            if (isNaN(length)) {
+                length = +(preProcessors[lengthInString.trim()]);
+            }
+            name = name.substr(0, startArray);
+        }
+        return [name, length];
+    }
+
+    public initializeShaders(processingContext: Nullable<ShaderProcessingContext>): void {
+        this._missingVaryings.length = 0;
+        this._textureArrayProcessing.length = 0;
+    }
+
+    public varyingProcessor(varying: string, isFragment: boolean, preProcessors: { [key: string]: string }, processingContext: Nullable<ShaderProcessingContext>) {
+        this._preProcessors = preProcessors;
+
+        const webgpuProcessingContext = processingContext! as WebGPUShaderProcessingContext;
+
+        const varyingRegex = new RegExp(/\s*varying\s+(?:(?:highp)?|(?:lowp)?)\s*(\S+)\s+(\S+)\s*;/gm);
+        const match = varyingRegex.exec(varying);
+        if (match != null) {
+            const varyingType = match[1];
+            const name = match[2];
+            let location: number;
+            if (isFragment) {
+                location = webgpuProcessingContext.availableVaryings[name];
+                this._missingVaryings[location] = "";
+                if (location === undefined) {
+                    Logger.Warn(`Invalid fragment shader: The varying named "${name}" is not declared in the vertex shader! This declaration will be ignored.`);
+                }
+            }
+            else {
+                location = webgpuProcessingContext.getVaryingNextLocation(varyingType, this._getArraySize(name, preProcessors)[1]);
+                webgpuProcessingContext.availableVaryings[name] = location;
+                this._missingVaryings[location] = `layout(location = ${location}) in ${varyingType} ${name};`;
+            }
+
+            varying = varying.replace(match[0], location === undefined ? "" : `layout(location = ${location}) ${isFragment ? "in" : "out"} ${varyingType} ${name};`);
+        }
+        return varying;
+    }
+
+    public attributeProcessor(attribute: string, preProcessors: { [key: string]: string }, processingContext: Nullable<ShaderProcessingContext>) {
+        this._preProcessors = preProcessors;
+
+        const webgpuProcessingContext = processingContext! as WebGPUShaderProcessingContext;
+
+        const attribRegex = new RegExp(/\s*attribute\s+(\S+)\s+(\S+)\s*;/gm);
+        const match = attribRegex.exec(attribute);
+        if (match != null) {
+            const varyingType = match[1];
+            const name = match[2];
+            const location = webgpuProcessingContext.getAttributeNextLocation(varyingType, this._getArraySize(name, preProcessors)[1]);
+
+            webgpuProcessingContext.availableAttributes[name] = location;
+            webgpuProcessingContext.orderedAttributes[location] = name;
+
+            attribute = attribute.replace(match[0], `layout(location = ${location}) in ${varyingType} ${name};`);
+        }
+        return attribute;
+    }
+
+    public uniformProcessor(uniform: string, isFragment: boolean, preProcessors: { [key: string]: string }, processingContext: Nullable<ShaderProcessingContext>): string {
+        this._preProcessors = preProcessors;
+
+        const webgpuProcessingContext = processingContext! as WebGPUShaderProcessingContext;
+
+        const uniformRegex = new RegExp(/\s*uniform\s+(?:(?:highp)?|(?:lowp)?)\s*(\S+)\s+(\S+)\s*;/gm);
+
+        const match = uniformRegex.exec(uniform);
+        if (match != null) {
+            let uniformType = match[1];
+            let name = match[2];
+
+            if (uniformType.indexOf("sampler") === 0 || uniformType.indexOf("sampler") === 1) {
+                let samplerInfo = _knownSamplers[name];
+                let arraySize = 0; // 0 means the sampler/texture is not declared as an array
+                if (!samplerInfo) {
+                    [name, arraySize] = this._getArraySize(name, preProcessors);
+                    samplerInfo = webgpuProcessingContext.availableSamplers[name];
+                    if (!samplerInfo) {
+                        samplerInfo = {
+                            sampler: webgpuProcessingContext.getNextFreeUBOBinding(),
+                            isTextureArray: arraySize > 0,
+                            textures: [],
+                        };
+                        for (let i = 0; i < (arraySize || 1); ++i) {
+                            samplerInfo.textures.push(webgpuProcessingContext.getNextFreeUBOBinding());
+                        }
+                    } else {
+                        arraySize = samplerInfo.isTextureArray ? samplerInfo.textures.length : 0;
+                    }
+                }
+
+                const componentType = uniformType.charAt(0) === 'u' ? 'u' : uniformType.charAt(0) === 'i' ? 'i' : '';
+
+                if (componentType) {
+                    uniformType = uniformType.substr(1);
+                }
+
+                const isTextureArray = arraySize > 0;
+                const samplerSetIndex = samplerInfo.sampler.setIndex;
+                const samplerBindingIndex = samplerInfo.sampler.bindingIndex;
+                const samplerFunction = _samplerFunctionByWebGLSamplerType[uniformType];
+                const samplerType = _samplerTypeByWebGLSamplerType[uniformType] ?? "sampler";
+                const textureType = _textureTypeByWebGLSamplerType[uniformType];
+                const textureDimension = _gpuTextureViewDimensionByWebGPUTextureType[textureType];
+                const isComparisonSampler = !!_isComparisonSamplerByWebGPUSamplerType[samplerType];
+
+                // Manage textures and samplers.
+                if (!isTextureArray) {
+                    arraySize = 1;
+                    uniform = `layout(set = ${samplerSetIndex}, binding = ${samplerBindingIndex}) uniform ${componentType}${samplerType} ${name}Sampler;
+                        layout(set = ${samplerInfo.textures[0].setIndex}, binding = ${samplerInfo.textures[0].bindingIndex}) uniform ${textureType} ${name}Texture;
+                        #define ${name} ${componentType}${samplerFunction}(${name}Texture, ${name}Sampler)`;
+                } else {
+                    let layouts = [];
+                    layouts.push(`layout(set = ${samplerSetIndex}, binding = ${samplerBindingIndex}) uniform ${componentType}${samplerType} ${name}Sampler;`);
+                    uniform = `\r\n`;
+                    for (let i = 0; i < arraySize; ++i) {
+                        const textureSetIndex = samplerInfo.textures[i].setIndex;
+                        const textureBindingIndex = samplerInfo.textures[i].bindingIndex;
+
+                        layouts.push(`layout(set = ${textureSetIndex}, binding = ${textureBindingIndex}) uniform ${textureType} ${name}Texture${i};`);
+
+                        uniform += `${i > 0 ? '\r\n' : ''}#define ${name}${i} ${componentType}${samplerFunction}(${name}Texture${i}, ${name}Sampler)`;
+                    }
+                    uniform = layouts.join('\r\n') + uniform;
+                    this._textureArrayProcessing.push(name);
+                }
+
+                webgpuProcessingContext.availableSamplers[name] = samplerInfo;
+
+                if (!webgpuProcessingContext.orderedUBOsAndSamplers[samplerSetIndex]) {
+                    webgpuProcessingContext.orderedUBOsAndSamplers[samplerSetIndex] = [];
+                }
+                if (!webgpuProcessingContext.orderedUBOsAndSamplers[samplerSetIndex][samplerBindingIndex]) {
+                    webgpuProcessingContext.orderedUBOsAndSamplers[samplerSetIndex][samplerBindingIndex] = {
+                        isSampler: true,
+                        isTexture: false,
+                        isComparisonSampler,
+                        usedInVertex: false,
+                        usedInFragment: false,
+                        name,
+                    };
+                }
+
+                if (isFragment) {
+                    webgpuProcessingContext.orderedUBOsAndSamplers[samplerSetIndex][samplerBindingIndex].usedInFragment = true;
+                } else {
+                    webgpuProcessingContext.orderedUBOsAndSamplers[samplerSetIndex][samplerBindingIndex].usedInVertex = true;
+                }
+
+                for (let i = 0; i < arraySize; ++i) {
+                    const textureSetIndex = samplerInfo.textures[i].setIndex;
+                    const textureBindingIndex = samplerInfo.textures[i].bindingIndex;
+
+                    if (!webgpuProcessingContext.orderedUBOsAndSamplers[textureSetIndex]) {
+                        webgpuProcessingContext.orderedUBOsAndSamplers[textureSetIndex] = [];
+                    }
+                    if (!webgpuProcessingContext.orderedUBOsAndSamplers[textureSetIndex][textureBindingIndex]) {
+                        webgpuProcessingContext.orderedUBOsAndSamplers[textureSetIndex][textureBindingIndex] = {
+                            isSampler: false,
+                            isTexture: true,
+                            componentType: isComparisonSampler ? WebGPUConstants.TextureComponentType.DepthComparison :
+                                        componentType === 'u' ? WebGPUConstants.TextureComponentType.Uint :
+                                        componentType === 'i' ? WebGPUConstants.TextureComponentType.Sint : WebGPUConstants.TextureComponentType.Float,
+                            textureDimension,
+                            usedInVertex: false,
+                            usedInFragment: false,
+                            name: isTextureArray ? name + i.toString() : name,
+                        };
+                    }
+                    if (isFragment) {
+                        webgpuProcessingContext.orderedUBOsAndSamplers[textureSetIndex][textureBindingIndex].usedInFragment = true;
+                    } else {
+                        webgpuProcessingContext.orderedUBOsAndSamplers[textureSetIndex][textureBindingIndex].usedInVertex = true;
+                    }
+                }
+            }
+            else {
+                // Check the size of the uniform array in case of array.
+                let length = 0;
+
+                [name, length] = this._getArraySize(name, preProcessors);
+
+                for (let i = 0; i < webgpuProcessingContext.leftOverUniforms.length; i++) {
+                    if (webgpuProcessingContext.leftOverUniforms[i].name === name) {
+                        return "";
+                    }
+                }
+
+                webgpuProcessingContext.leftOverUniforms.push({
+                    name,
+                    type: uniformType,
+                    length
+                });
+                uniform = "";
+            }
+        }
+        return uniform;
+    }
+
+    public uniformBufferProcessor(uniformBuffer: string, isFragment: boolean, processingContext: Nullable<ShaderProcessingContext>): string {
+        const webgpuProcessingContext = processingContext! as WebGPUShaderProcessingContext;
+        const uboRegex = new RegExp(/uniform\s+(\w+)/gm);
+
+        const match = uboRegex.exec(uniformBuffer);
+        if (match != null) {
+            const name = match[1];
+            let setIndex: number;
+            let bindingIndex: number;
+            const knownUBO = _knownUBOs[name];
+            if (knownUBO) {
+                setIndex = knownUBO.setIndex;
+                bindingIndex = knownUBO.bindingIndex;
+            }
+            else {
+                if (isFragment) {
+                    const availableUBO = webgpuProcessingContext.availableUBOs[name];
+                    if (availableUBO) {
+                        setIndex = availableUBO.setIndex;
+                        bindingIndex = availableUBO.bindingIndex;
+                    }
+                    else {
+                        const nextBinding = webgpuProcessingContext.getNextFreeUBOBinding();
+                        setIndex = nextBinding.setIndex;
+                        bindingIndex = nextBinding.bindingIndex;
+                    }
+                }
+                else {
+                    const nextBinding = webgpuProcessingContext.getNextFreeUBOBinding();
+                    setIndex = nextBinding.setIndex;
+                    bindingIndex = nextBinding.bindingIndex;
+                }
+            }
+
+            webgpuProcessingContext.availableUBOs[name] = { setIndex, bindingIndex };
+            if (!webgpuProcessingContext.orderedUBOsAndSamplers[setIndex]) {
+                webgpuProcessingContext.orderedUBOsAndSamplers[setIndex] = [];
+            }
+            if (!webgpuProcessingContext.orderedUBOsAndSamplers[setIndex][bindingIndex]) {
+                webgpuProcessingContext.orderedUBOsAndSamplers[setIndex][bindingIndex] = { isSampler: false, isTexture: false, name, usedInVertex: false, usedInFragment: false };
+            }
+            if (isFragment) {
+                webgpuProcessingContext.orderedUBOsAndSamplers[setIndex][bindingIndex].usedInFragment = true;
+            } else {
+                webgpuProcessingContext.orderedUBOsAndSamplers[setIndex][bindingIndex].usedInVertex = true;
+            }
+
+            uniformBuffer = uniformBuffer.replace("uniform", `layout(set = ${setIndex}, binding = ${bindingIndex}) uniform`);
+        }
+        return uniformBuffer;
+    }
+
+    // public endOfUniformBufferProcessor(closingBracketLine: string, isFragment: boolean): string {
+    //     console.log("uniformBuffer closingBracketLine ", closingBracketLine);
+    //     return closingBracketLine;
+    // }
+
+    public postProcessor(code: string, defines: string[], isFragment: boolean, processingContext: Nullable<ShaderProcessingContext>) {
+        const hasDrawBuffersExtension = code.search(/#extension.+GL_EXT_draw_buffers.+require/) !== -1;
+
+        // Remove extensions
+        var regex = /#extension.+(GL_OVR_multiview2|GL_OES_standard_derivatives|GL_EXT_shader_texture_lod|GL_EXT_frag_depth|GL_EXT_draw_buffers).+(enable|require)/g;
+        code = code.replace(regex, "");
+
+        // Replace instructions
+        code = code.replace(/texture2D\s*\(/g, "texture(");
+        if (isFragment) {
+            code = code.replace(/texture2DLodEXT\s*\(/g, "textureLod(");
+            code = code.replace(/textureCubeLodEXT\s*\(/g, "textureLod(");
+            code = code.replace(/textureCube\s*\(/g, "texture(");
+            code = code.replace(/gl_FragDepthEXT/g, "gl_FragDepth");
+            code = code.replace(/gl_FragColor/g, "glFragColor");
+            code = code.replace(/gl_FragData/g, "glFragData");
+            code = code.replace(/void\s+?main\s*\(/g, (hasDrawBuffersExtension ? "" : "layout(location = 0) out vec4 glFragColor;\n") + "void main(");
+        } else {
+            code = code.replace(/gl_InstanceID/g, "gl_InstanceIndex");
+            code = code.replace(/gl_VertexID/g, "gl_VertexIndex");
+            var hasMultiviewExtension = defines.indexOf("#define MULTIVIEW") !== -1;
+            if (hasMultiviewExtension) {
+                return "#extension GL_OVR_multiview2 : require\nlayout (num_views = 2) in;\n" + code;
+            }
+        }
+
+        // Flip Y + convert z range from [-1,1] to [0,1]
+        if (!isFragment) {
+            const lastClosingCurly = code.lastIndexOf("}");
+            code = code.substring(0, lastClosingCurly);
+            code += "gl_Position.y *= -1.;\ngl_Position.z = (gl_Position.z + gl_Position.w) / 2.0; }";
+        }
+
+        let sci = new ShaderCodeInliner(code);
+        sci.debug = dbgShowDebugInliningProcess;
+        sci.processCode();
+        return sci.code;
+    }
+
+    private _applyTextureArrayProcessing(code: string, name: string): string {
+        // Replaces the occurrences of name[XX] by nameXX
+        const regex = new RegExp(name + "\\s*\\[(.+)?\\]", "gm");
+        let match = regex.exec(code);
+
+        while (match != null) {
+            let index = match[1];
+            let iindex = +(index);
+            if (this._preProcessors && isNaN(iindex)) {
+                iindex = +(this._preProcessors[index.trim()]);
+            }
+            code = code.replace(match[0], name + iindex);
+            match = regex.exec(code);
+        }
+
+        return code;
+    }
+
+    public finalizeShaders(vertexCode: string, fragmentCode: string, processingContext: Nullable<ShaderProcessingContext>): { vertexCode: string, fragmentCode: string } {
+        const webgpuProcessingContext = processingContext! as WebGPUShaderProcessingContext;
+
+        // make replacements for texture names in the texture array case
+        for (let i = 0; i < this._textureArrayProcessing.length; ++i) {
+            const name = this._textureArrayProcessing[i];
+            vertexCode = this._applyTextureArrayProcessing(vertexCode, name);
+            fragmentCode = this._applyTextureArrayProcessing(fragmentCode, name);
+        }
+
+        // inject the missing varying in the fragment shader
+        for (let i = 0; i < this._missingVaryings.length; ++i) {
+            const decl = this._missingVaryings[i];
+            if (decl && decl.length > 0) {
+                fragmentCode = decl + "\n" + fragmentCode;
+            }
+        }
+
+        // Builds the leftover UBOs.
+        if (webgpuProcessingContext.leftOverUniforms.length) {
+            const name = "LeftOver";
+            let availableUBO = webgpuProcessingContext.availableUBOs[name];
+            if (!availableUBO) {
+                availableUBO = webgpuProcessingContext.getNextFreeUBOBinding();
+                webgpuProcessingContext.availableUBOs[name] = availableUBO;
+                if (!webgpuProcessingContext.orderedUBOsAndSamplers[availableUBO.setIndex]) {
+                    webgpuProcessingContext.orderedUBOsAndSamplers[availableUBO.setIndex] = [];
+                }
+                webgpuProcessingContext.orderedUBOsAndSamplers[availableUBO.setIndex][availableUBO.bindingIndex] = { isSampler: false, isTexture: false, usedInVertex: true, usedInFragment: true, name };
+            }
+
+            let ubo = `layout(set = ${availableUBO.setIndex}, binding = ${availableUBO.bindingIndex}) uniform ${name} {\n    `;
+            for (let leftOverUniform of webgpuProcessingContext.leftOverUniforms) {
+                if (leftOverUniform.length > 0) {
+                    ubo += `    ${leftOverUniform.type} ${leftOverUniform.name}[${leftOverUniform.length}];\n`;
+                }
+                else {
+                    ubo += `    ${leftOverUniform.type} ${leftOverUniform.name};\n`;
+                }
+            }
+            ubo += "};\n\n";
+
+            // Currently set in both vert and frag but could be optim away if necessary.
+            vertexCode = ubo + vertexCode;
+            fragmentCode = ubo + fragmentCode;
+        }
+
+        this._preProcessors = null as any;
+
+        return { vertexCode, fragmentCode };
+    }
+}

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 1129 - 0
src/Engines/WebGPU/webgpuTextureHelper.ts


+ 27 - 0
src/Engines/constants.ts

@@ -104,6 +104,8 @@ export class Constants {
     // Stencil Actions Constants.
     /** Passed to stencilOperation to specify that stencil value must be kept */
     public static readonly KEEP = 0x1E00;
+    /** Passed to stencilOperation to specify that stencil value must be zero */
+    public static readonly ZERO = 0x0000;
     /** Passed to stencilOperation to specify that stencil value must be replaced */
     public static readonly REPLACE = 0x1E01;
     /** Passed to stencilOperation to specify that stencil value must be incremented */
@@ -150,6 +152,26 @@ export class Constants {
     public static readonly TEXTUREFORMAT_RGB_INTEGER = 10;
     /** RGBA_INTEGER */
     public static readonly TEXTUREFORMAT_RGBA_INTEGER = 11;
+    /** BGRA */
+    public static readonly TEXTUREFORMAT_BGRA = 12;
+
+    /** Depth 24 bits + Stencil 8 bits */
+    public static readonly TEXTUREFORMAT_DEPTH24_STENCIL8 = 13;
+    /** Depth 32 bits float */
+    public static readonly TEXTUREFORMAT_DEPTH32_FLOAT = 14;
+
+    /** 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 = 33777;
 
     /** UNSIGNED_BYTE */
     public static readonly TEXTURETYPE_UNSIGNED_BYTE = 0;
@@ -524,4 +546,9 @@ export class Constants {
      * using the getIndex(Constants.PREPASS_ALBEDO_TEXTURE_TYPE)
      */
     public static readonly PREPASS_ALBEDO_TEXTURE_TYPE = 6;
+
+    /**
+     * Prefixes used by the engine for custom effects
+     */
+    public static readonly CUSTOMEFFECT_PREFIX_SHADOWGENERATOR = "bjs_shadowgenerator_";
 }

+ 2 - 0
src/Engines/depthTextureCreationOptions.ts

@@ -10,4 +10,6 @@ export class DepthTextureCreationOptions {
     comparisonFunction?: number;
     /** Specifies if the created texture is a cube texture */
     isCube?: boolean;
+    /** Specifies the sample count of the depth/stencil texture texture */
+    samples?: number;
 }

+ 93 - 57
src/Engines/engine.ts

@@ -195,7 +195,7 @@ export class Engine extends ThinEngine {
     /** FLOAT_32_UNSIGNED_INT_24_8_REV */
     public static readonly TEXTURETYPE_FLOAT_32_UNSIGNED_INT_24_8_REV = Constants.TEXTURETYPE_FLOAT_32_UNSIGNED_INT_24_8_REV;
 
-    /** nearest is mag = nearest and min = nearest and mip = linear */
+    /** nearest is mag = nearest and min = nearest and mip = none */
     public static readonly TEXTURE_NEAREST_SAMPLINGMODE = Constants.TEXTURE_NEAREST_SAMPLINGMODE;
     /** Bilinear is mag = linear and min = linear and mip = nearest */
     public static readonly TEXTURE_BILINEAR_SAMPLINGMODE = Constants.TEXTURE_BILINEAR_SAMPLINGMODE;
@@ -422,9 +422,9 @@ export class Engine extends ThinEngine {
     private _rescalePostProcess: PostProcess;
 
     // Deterministic lockstepMaxSteps
-    private _deterministicLockstep: boolean = false;
-    private _lockstepMaxSteps: number = 4;
-    private _timeStep: number = 1 / 60;
+    protected _deterministicLockstep: boolean = false;
+    protected _lockstepMaxSteps: number = 4;
+    protected _timeStep: number = 1 / 60;
 
     protected get _supportsHardwareTextureRescaling() {
         return !!Engine._RescalePostProcessFactory;
@@ -495,47 +495,13 @@ export class Engine extends ThinEngine {
         if ((<any>canvasOrContext).getContext) {
             let canvas = <HTMLCanvasElement>canvasOrContext;
 
-            this._onCanvasFocus = () => {
-                this.onCanvasFocusObservable.notifyObservers(this);
-            };
-
-            this._onCanvasBlur = () => {
-                this.onCanvasBlurObservable.notifyObservers(this);
-            };
-
-            canvas.addEventListener("focus", this._onCanvasFocus);
-            canvas.addEventListener("blur", this._onCanvasBlur);
-
-            this._onBlur = () => {
-                if (this.disablePerformanceMonitorInBackground) {
-                    this._performanceMonitor.disable();
-                }
-                this._windowIsBackground = true;
-            };
-
-            this._onFocus = () => {
-                if (this.disablePerformanceMonitorInBackground) {
-                    this._performanceMonitor.enable();
-                }
-                this._windowIsBackground = false;
-            };
-
-            this._onCanvasPointerOut = (ev) => {
-                this.onCanvasPointerOutObservable.notifyObservers(ev);
-            };
-
-            canvas.addEventListener("pointerout", this._onCanvasPointerOut);
+            this._sharedInit(canvas, !!options.doNotHandleTouchAction, options.audioEngine!);
 
             if (DomManagement.IsWindowObjectExist()) {
-                let hostWindow = this.getHostWindow()!;
-                hostWindow.addEventListener("blur", this._onBlur);
-                hostWindow.addEventListener("focus", this._onFocus);
-
-                let anyDoc = document as any;
+                const anyDoc = document as any;
 
                 // Fullscreen
                 this._onFullscreenChange = () => {
-
                     if (anyDoc.fullscreen !== undefined) {
                         this.isFullscreen = anyDoc.fullscreen;
                     } else if (anyDoc.mozFullScreen !== undefined) {
@@ -581,10 +547,6 @@ export class Engine extends ThinEngine {
 
             this.enableOfflineSupport = Engine.OfflineProviderFactory !== undefined;
 
-            if (!options.doNotHandleTouchAction) {
-                this._disableTouchAction();
-            }
-
             this._deterministicLockstep = !!options.deterministicLockstep;
             this._lockstepMaxSteps = options.lockstepMaxSteps || 0;
             this._timeStep = options.timeStep || 1 / 60;
@@ -599,6 +561,64 @@ export class Engine extends ThinEngine {
     }
 
     /**
+     * Shared initialization across engines types.
+     * @param canvas The canvas associated with this instance of the engine.
+     * @param doNotHandleTouchAction Defines that engine should ignore modifying touch action attribute and style
+     * @param audioEngine Defines if an audio engine should be created by default
+     */
+    protected _sharedInit(canvas: HTMLCanvasElement, doNotHandleTouchAction: boolean, audioEngine: boolean) {
+        super._sharedInit(canvas, doNotHandleTouchAction, audioEngine);
+
+        this._onCanvasFocus = () => {
+            this.onCanvasFocusObservable.notifyObservers(this);
+        };
+
+        this._onCanvasBlur = () => {
+            this.onCanvasBlurObservable.notifyObservers(this);
+        };
+
+        canvas.addEventListener("focus", this._onCanvasFocus);
+        canvas.addEventListener("blur", this._onCanvasBlur);
+
+        this._onBlur = () => {
+            if (this.disablePerformanceMonitorInBackground) {
+                this._performanceMonitor.disable();
+            }
+            this._windowIsBackground = true;
+        };
+
+        this._onFocus = () => {
+            if (this.disablePerformanceMonitorInBackground) {
+                this._performanceMonitor.enable();
+            }
+            this._windowIsBackground = false;
+        };
+
+        this._onCanvasPointerOut = (ev) => {
+            this.onCanvasPointerOutObservable.notifyObservers(ev);
+        };
+
+        if (DomManagement.IsWindowObjectExist()) {
+            const hostWindow = this.getHostWindow();
+            if (hostWindow) {
+                hostWindow.addEventListener("blur", this._onBlur);
+                hostWindow.addEventListener("focus", this._onFocus);
+            }
+        }
+
+        canvas.addEventListener("pointerout", this._onCanvasPointerOut);
+
+        if (!doNotHandleTouchAction) {
+            this._disableTouchAction();
+        }
+
+        // Create Audio Engine if needed.
+        if (!Engine.audioEngine && audioEngine && Engine.AudioEngineFactory) {
+            Engine.audioEngine = Engine.AudioEngineFactory(this.getRenderingCanvas());
+        }
+    }
+
+    /**
      * Gets current aspect ratio
      * @param viewportOwner defines the camera to use to get the aspect ratio
      * @param useScreen defines if screen size must be used (or the current render target if any)
@@ -729,6 +749,14 @@ export class Engine extends ThinEngine {
     }
 
     /**
+     * Gets a boolean indicating if depth testing is enabled
+     * @returns the current state
+     */
+    public getDepthBuffer(): boolean {
+        return this._depthCullingState.depthTest;
+    }
+
+    /**
      * Enable or disable depth buffering
      * @param enable defines the state to set
      */
@@ -1132,8 +1160,9 @@ export class Engine extends ThinEngine {
      * @param channel The texture channel
      * @param uniform The uniform to set
      * @param texture The render target texture containing the depth stencil texture to apply
+     * @param name The texture name
      */
-    public setDepthStencilTexture(channel: number, uniform: Nullable<WebGLUniformLocation>, texture: Nullable<RenderTargetTexture>): void {
+    public setDepthStencilTexture(channel: number, uniform: Nullable<WebGLUniformLocation>, texture: Nullable<RenderTargetTexture>, name?: string): void {
         if (channel === undefined) {
             return;
         }
@@ -1143,10 +1172,10 @@ export class Engine extends ThinEngine {
         }
 
         if (!texture || !texture.depthStencilTexture) {
-            this._setTexture(channel, null);
+            this._setTexture(channel, null, undefined, undefined, name);
         }
         else {
-            this._setTexture(channel, texture, false, true);
+            this._setTexture(channel, texture, false, true, name);
         }
     }
 
@@ -1154,18 +1183,20 @@ export class Engine extends ThinEngine {
      * Sets a texture to the webGL context from a postprocess
      * @param channel defines the channel to use
      * @param postProcess defines the source postprocess
+     * @param name name of the channel
      */
-    public setTextureFromPostProcess(channel: number, postProcess: Nullable<PostProcess>): void {
-        this._bindTexture(channel, postProcess ? postProcess._textures.data[postProcess._currentRenderTextureInd] : null);
+    public setTextureFromPostProcess(channel: number, postProcess: Nullable<PostProcess>, name: string): void {
+        this._bindTexture(channel, postProcess ? postProcess._textures.data[postProcess._currentRenderTextureInd] : null, name);
     }
 
     /**
      * Binds the output of the passed in post process to the texture channel specified
      * @param channel The channel the texture should be bound to
      * @param postProcess The post process which's output should be bound
+     * @param name name of the channel
      */
-    public setTextureFromPostProcessOutput(channel: number, postProcess: Nullable<PostProcess>): void {
-        this._bindTexture(channel, postProcess ? postProcess._outputTexture : null);
+    public setTextureFromPostProcessOutput(channel: number, postProcess: Nullable<PostProcess>, name: string): void {
+        this._bindTexture(channel, postProcess ? postProcess._outputTexture : null, name);
     }
 
     protected _rebuildBuffers(): void {
@@ -1291,7 +1322,7 @@ export class Engine extends ThinEngine {
     }
 
     /**
-     * Enf the current frame
+     * End the current frame
      */
     public endFrame(): void {
         super.endFrame();
@@ -1300,27 +1331,32 @@ export class Engine extends ThinEngine {
         this.onEndFrameObservable.notifyObservers(this);
     }
 
-    public resize(): void {
+    /**
+     * Resize the view according to the canvas' size
+     * @param forceSetSize true to force setting the sizes of the underlying canvas
+     */
+    public resize(forceSetSize = false): void {
         // We're not resizing the size of the canvas while in VR mode & presenting
         if (this.isVRPresenting()) {
             return;
         }
 
-        super.resize();
+        super.resize(forceSetSize);
     }
 
     /**
      * Force a specific size of the canvas
      * @param width defines the new canvas' width
      * @param height defines the new canvas' height
+     * @param forceSetSize true to force setting the sizes of the underlying canvas
      * @returns true if the size was changed
      */
-    public setSize(width: number, height: number): boolean {
+    public setSize(width: number, height: number, forceSetSize = false): boolean {
         if (!this._renderingCanvas) {
             return false;
         }
 
-        if (!super.setSize(width, height)) {
+        if (!super.setSize(width, height, forceSetSize)) {
             return false;
         }
 
@@ -1918,4 +1954,4 @@ export class Engine extends ThinEngine {
             anyDoc.msCancelFullScreen();
         }
     }
-}
+}

+ 50 - 0
src/Engines/engineFeatures.ts

@@ -0,0 +1,50 @@
+/** @hidden */
+export interface EngineFeatures {
+    /** Force using Bitmap when Bitmap or HTMLImageElement can be used */
+    forceBitmapOverHTMLImageElement: boolean;
+
+    /** Indicates that the engine support rendering to as well as copying to lod float textures */
+    supportRenderAndCopyToLodForFloatTextures: boolean;
+
+    /** Indicates that the engine support handling depth/stencil textures */
+    supportDepthStencilTexture: boolean;
+
+    /** Indicates that the engine support shadow samplers */
+    supportShadowSamplers: boolean;
+
+    /** Indicates to check the matrix bytes per bytes to know if it has changed or not. If false, only the updateFlag of the matrix is checked */
+    uniformBufferHardCheckMatrix: boolean;
+
+    /** Indicates that prefiltered mipmaps can be generated in some processes (for eg when loading an HDR cube texture) */
+    allowTexturePrefiltering: boolean;
+
+    /** Indicates to track the usage of ubos and to create new ones as necessary during a frame duration */
+    trackUbosInFrame: boolean;
+
+    /** Indicates that the Cascaded Shadow Map technic is supported */
+    supportCSM: boolean;
+
+    /** Indicates that the textures transcoded by the basis transcoder must have power of 2 width and height */
+    basisNeedsPOT: boolean;
+
+    /** Indicates that the engine supports 3D textures */
+    support3DTextures: boolean;
+
+    /** Indicates that constants need a type suffix in shaders (used by realtime filtering...) */
+    needTypeSuffixInShaderConstants: boolean;
+
+    /** Indicates that MSAA is supported */
+    supportMSAA: boolean;
+
+    /** Indicates that SSAO2 is supported */
+    supportSSAO2: boolean;
+
+    /** Indicates that some additional texture formats are supported (like TEXTUREFORMAT_R for eg) */
+    supportExtendedTextureFormats: boolean;
+
+    /** Indicates that the switch/case construct is supported in shaders */
+    supportSwitchCaseInShader: boolean;
+
+    /** @hidden */
+    _collectUbosUpdatedInFrame: boolean;
+}

+ 3 - 0
src/Engines/index.ts

@@ -8,7 +8,10 @@ export * from "./nullEngine";
 export * from "./Extensions/index";
 export * from "./IPipelineContext";
 export * from "./WebGL/webGLPipelineContext";
+export * from "./WebGPU/webgpuConstants";
+export * from "./webgpuEngine";
 export * from "./WebGL/webGL2ShaderProcessors";
 export * from "./nativeEngine";
 export * from "./Processors/shaderCodeInliner";
 export * from "./performanceConfigurator";
+export * from "./engineFeatures";

+ 568 - 21
src/Engines/nativeEngine.ts

@@ -12,11 +12,10 @@ import { DataBuffer } from '../Meshes/dataBuffer';
 import { Tools } from "../Misc/tools";
 import { Observer } from "../Misc/observable";
 import { EnvironmentTextureTools, EnvironmentTextureSpecularInfoV1 } from "../Misc/environmentTextureTools";
-import { Matrix, Viewport, Color3 } from "../Maths/math";
-import { IColor4Like } from '../Maths/math.like';
 import { Scene } from "../scene";
 import { RenderTargetCreationOptions } from "../Materials/Textures/renderTargetCreationOptions";
 import { IPipelineContext } from './IPipelineContext';
+import { IMatrixLike, IVector2Like, IVector3Like, IVector4Like, IColor3Like, IColor4Like, IViewportLike } from '../Maths/math.like';
 import { Logger } from "../Misc/logger";
 import { Constants } from './constants';
 import { ThinEngine, ISceneLike } from './thinEngine';
@@ -114,6 +113,9 @@ interface INativeEngine {
 
     setMatrix(uniform: WebGLUniformLocation, matrix: Float32Array): void;
     setInt(uniform: WebGLUniformLocation, int: number): void;
+    setInt2(uniform: WebGLUniformLocation, int1: number, int2: number): void;
+    setInt3(uniform: WebGLUniformLocation, int1: number, int2: number, int3: number): void;
+    setInt4(uniform: WebGLUniformLocation, int1: number, int2: number, int3: number, int4: number): void;
     setIntArray(uniform: WebGLUniformLocation, array: Int32Array): void;
     setIntArray2(uniform: WebGLUniformLocation, array: Int32Array): void;
     setIntArray3(uniform: WebGLUniformLocation, array: Int32Array): void;
@@ -181,6 +183,532 @@ class NativePipelineContext implements IPipelineContext {
     }
 
     public nativeProgram: any;
+
+    private _valueCache: { [key: string]: any } = {};
+    private _uniforms: { [key: string]: Nullable<WebGLUniformLocation> };
+
+    public engine: NativeEngine;
+    public context?: WebGLRenderingContext;
+    public vertexShader?: WebGLShader;
+    public fragmentShader?: WebGLShader;
+    public isParallelCompiled: boolean;
+    public onCompiled?: () => void;
+    public transformFeedback?: WebGLTransformFeedback | null;
+
+    public _fillEffectInformation(effect: Effect, uniformBuffersNames: { [key: string]: number }, uniformsNames: string[], uniforms: { [key: string]: Nullable<WebGLUniformLocation> }, samplerList: string[], samplers: { [key: string]: number }, attributesNames: string[], attributes: number[]) {
+        const engine = this.engine;
+        if (engine.supportsUniformBuffers) {
+            for (var name in uniformBuffersNames) {
+                effect.bindUniformBlock(name, uniformBuffersNames[name]);
+            }
+        }
+
+        const effectAvailableUniforms = this.engine.getUniforms(this, uniformsNames);
+        effectAvailableUniforms.forEach((uniform, index) => {
+            uniforms[uniformsNames[index]] = uniform;
+        });
+        this._uniforms = uniforms;
+
+        let index: number;
+        for (index = 0; index < samplerList.length; index++) {
+            const sampler = effect.getUniform(samplerList[index]);
+            if (sampler == null) {
+                samplerList.splice(index, 1);
+                index--;
+            }
+        }
+
+        samplerList.forEach((name, index) => {
+            samplers[name] = index;
+        });
+
+        attributes.push(...engine.getAttributes(this, attributesNames));
+    }
+
+    /**
+     * Release all associated resources.
+     **/
+    public dispose() {
+        this._uniforms = { };
+    }
+
+    /** @hidden */
+    public _cacheMatrix(uniformName: string, matrix: IMatrixLike): boolean {
+        var cache = this._valueCache[uniformName];
+        var flag = matrix.updateFlag;
+        if (cache !== undefined && cache === flag) {
+            return false;
+        }
+
+        this._valueCache[uniformName] = flag;
+
+        return true;
+    }
+
+    /** @hidden */
+    public _cacheFloat2(uniformName: string, x: number, y: number): boolean {
+        var cache = this._valueCache[uniformName];
+        if (!cache) {
+            cache = [x, y];
+            this._valueCache[uniformName] = cache;
+            return true;
+        }
+
+        var changed = false;
+        if (cache[0] !== x) {
+            cache[0] = x;
+            changed = true;
+        }
+        if (cache[1] !== y) {
+            cache[1] = y;
+            changed = true;
+        }
+
+        return changed;
+    }
+
+    /** @hidden */
+    public _cacheFloat3(uniformName: string, x: number, y: number, z: number): boolean {
+        var cache = this._valueCache[uniformName];
+        if (!cache) {
+            cache = [x, y, z];
+            this._valueCache[uniformName] = cache;
+            return true;
+        }
+
+        var changed = false;
+        if (cache[0] !== x) {
+            cache[0] = x;
+            changed = true;
+        }
+        if (cache[1] !== y) {
+            cache[1] = y;
+            changed = true;
+        }
+        if (cache[2] !== z) {
+            cache[2] = z;
+            changed = true;
+        }
+
+        return changed;
+    }
+
+    /** @hidden */
+    public _cacheFloat4(uniformName: string, x: number, y: number, z: number, w: number): boolean {
+        var cache = this._valueCache[uniformName];
+        if (!cache) {
+            cache = [x, y, z, w];
+            this._valueCache[uniformName] = cache;
+            return true;
+        }
+
+        var changed = false;
+        if (cache[0] !== x) {
+            cache[0] = x;
+            changed = true;
+        }
+        if (cache[1] !== y) {
+            cache[1] = y;
+            changed = true;
+        }
+        if (cache[2] !== z) {
+            cache[2] = z;
+            changed = true;
+        }
+        if (cache[3] !== w) {
+            cache[3] = w;
+            changed = true;
+        }
+
+        return changed;
+    }
+
+    /**
+     * Sets an interger value on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param value Value to be set.
+     */
+    public setInt(uniformName: string, value: number): void {
+        var cache = this._valueCache[uniformName];
+        if (cache !== undefined && cache === value) {
+            return;
+        }
+
+        if (this.engine.setInt(this._uniforms[uniformName]!, value)) {
+            this._valueCache[uniformName] = value;
+        }
+    }
+
+    /**
+     * Sets a int2 on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param x First int in int2.
+     * @param y Second int in int2.
+     */
+    public setInt2(uniformName: string, x: number, y: number): void {
+        if (this._cacheFloat2(uniformName, x, y)) {
+            if (!this.engine.setInt2(this._uniforms[uniformName], x, y)) {
+                this._valueCache[uniformName] = null;
+            }
+        }
+    }
+
+    /**
+     * Sets a int3 on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param x First int in int3.
+     * @param y Second int in int3.
+     * @param y Third int in int3.
+     */
+    public setInt3(uniformName: string, x: number, y: number, z: number): void {
+        if (this._cacheFloat3(uniformName, x, y, z)) {
+            if (!this.engine.setInt3(this._uniforms[uniformName], x, y, z)) {
+                this._valueCache[uniformName] = null;
+            }
+        }
+    }
+
+    /**
+     * Sets a int4 on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param x First int in int4.
+     * @param y Second int in int4.
+     * @param y Third int in int4.
+     * @param w Fourth int in int4.
+     */
+    public setInt4(uniformName: string, x: number, y: number, z: number, w: number): void {
+        if (this._cacheFloat4(uniformName, x, y, z, w)) {
+            if (!this.engine.setInt4(this._uniforms[uniformName], x, y, z, w)) {
+                this._valueCache[uniformName] = null;
+            }
+        }
+    }
+
+    /**
+     * Sets an int array on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param array array to be set.
+     */
+    public setIntArray(uniformName: string, array: Int32Array): void {
+        this._valueCache[uniformName] = null;
+        this.engine.setIntArray(this._uniforms[uniformName]!, array);
+    }
+
+    /**
+     * Sets an int array 2 on a uniform variable. (Array is specified as single array eg. [1,2,3,4] will result in [[1,2],[3,4]] in the shader)
+     * @param uniformName Name of the variable.
+     * @param array array to be set.
+     */
+    public setIntArray2(uniformName: string, array: Int32Array): void {
+        this._valueCache[uniformName] = null;
+        this.engine.setIntArray2(this._uniforms[uniformName]!, array);
+    }
+
+    /**
+     * Sets an int array 3 on a uniform variable. (Array is specified as single array eg. [1,2,3,4,5,6] will result in [[1,2,3],[4,5,6]] in the shader)
+     * @param uniformName Name of the variable.
+     * @param array array to be set.
+     */
+    public setIntArray3(uniformName: string, array: Int32Array): void {
+        this._valueCache[uniformName] = null;
+        this.engine.setIntArray3(this._uniforms[uniformName]!, array);
+    }
+
+    /**
+     * Sets an int array 4 on a uniform variable. (Array is specified as single array eg. [1,2,3,4,5,6,7,8] will result in [[1,2,3,4],[5,6,7,8]] in the shader)
+     * @param uniformName Name of the variable.
+     * @param array array to be set.
+     */
+    public setIntArray4(uniformName: string, array: Int32Array): void {
+        this._valueCache[uniformName] = null;
+        this.engine.setIntArray4(this._uniforms[uniformName]!, array);
+    }
+
+    /**
+     * Sets an float array on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param array array to be set.
+     */
+    public setFloatArray(uniformName: string, array: Float32Array): void {
+        this._valueCache[uniformName] = null;
+        this.engine.setFloatArray(this._uniforms[uniformName]!, array);
+    }
+
+    /**
+     * Sets an float array 2 on a uniform variable. (Array is specified as single array eg. [1,2,3,4] will result in [[1,2],[3,4]] in the shader)
+     * @param uniformName Name of the variable.
+     * @param array array to be set.
+     */
+    public setFloatArray2(uniformName: string, array: Float32Array): void {
+        this._valueCache[uniformName] = null;
+        this.engine.setFloatArray2(this._uniforms[uniformName]!, array);
+    }
+
+    /**
+     * Sets an float array 3 on a uniform variable. (Array is specified as single array eg. [1,2,3,4,5,6] will result in [[1,2,3],[4,5,6]] in the shader)
+     * @param uniformName Name of the variable.
+     * @param array array to be set.
+     */
+    public setFloatArray3(uniformName: string, array: Float32Array): void {
+        this._valueCache[uniformName] = null;
+        this.engine.setFloatArray3(this._uniforms[uniformName]!, array);
+    }
+
+    /**
+     * Sets an float array 4 on a uniform variable. (Array is specified as single array eg. [1,2,3,4,5,6,7,8] will result in [[1,2,3,4],[5,6,7,8]] in the shader)
+     * @param uniformName Name of the variable.
+     * @param array array to be set.
+     */
+    public setFloatArray4(uniformName: string, array: Float32Array): void {
+        this._valueCache[uniformName] = null;
+        this.engine.setFloatArray4(this._uniforms[uniformName]!, array);
+    }
+
+    /**
+     * Sets an array on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param array array to be set.
+     */
+    public setArray(uniformName: string, array: number[]): void {
+        this._valueCache[uniformName] = null;
+        this.engine.setArray(this._uniforms[uniformName]!, array);
+    }
+
+    /**
+     * Sets an array 2 on a uniform variable. (Array is specified as single array eg. [1,2,3,4] will result in [[1,2],[3,4]] in the shader)
+     * @param uniformName Name of the variable.
+     * @param array array to be set.
+     */
+    public setArray2(uniformName: string, array: number[]): void {
+        this._valueCache[uniformName] = null;
+        this.engine.setArray2(this._uniforms[uniformName]!, array);
+    }
+
+    /**
+     * Sets an array 3 on a uniform variable. (Array is specified as single array eg. [1,2,3,4,5,6] will result in [[1,2,3],[4,5,6]] in the shader)
+     * @param uniformName Name of the variable.
+     * @param array array to be set.
+     * @returns this effect.
+     */
+    public setArray3(uniformName: string, array: number[]): void {
+        this._valueCache[uniformName] = null;
+        this.engine.setArray3(this._uniforms[uniformName]!, array);
+    }
+
+    /**
+     * Sets an array 4 on a uniform variable. (Array is specified as single array eg. [1,2,3,4,5,6,7,8] will result in [[1,2,3,4],[5,6,7,8]] in the shader)
+     * @param uniformName Name of the variable.
+     * @param array array to be set.
+     */
+    public setArray4(uniformName: string, array: number[]): void {
+        this._valueCache[uniformName] = null;
+        this.engine.setArray4(this._uniforms[uniformName]!, array);
+    }
+
+    /**
+     * Sets matrices on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param matrices matrices to be set.
+     */
+    public setMatrices(uniformName: string, matrices: Float32Array): void {
+        if (!matrices) {
+            return;
+        }
+
+        this._valueCache[uniformName] = null;
+        this.engine.setMatrices(this._uniforms[uniformName]!, matrices);
+    }
+
+    /**
+     * Sets matrix on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param matrix matrix to be set.
+     */
+    public setMatrix(uniformName: string, matrix: IMatrixLike): void {
+        if (this._cacheMatrix(uniformName, matrix)) {
+            if (!this.engine.setMatrices(this._uniforms[uniformName]!, matrix.toArray() as Float32Array)) {
+                this._valueCache[uniformName] = null;
+            }
+        }
+    }
+
+    /**
+     * Sets a 3x3 matrix on a uniform variable. (Speicified as [1,2,3,4,5,6,7,8,9] will result in [1,2,3][4,5,6][7,8,9] matrix)
+     * @param uniformName Name of the variable.
+     * @param matrix matrix to be set.
+     */
+    public setMatrix3x3(uniformName: string, matrix: Float32Array): void {
+        this._valueCache[uniformName] = null;
+        this.engine.setMatrix3x3(this._uniforms[uniformName]!, matrix);
+    }
+
+    /**
+     * Sets a 2x2 matrix on a uniform variable. (Speicified as [1,2,3,4] will result in [1,2][3,4] matrix)
+     * @param uniformName Name of the variable.
+     * @param matrix matrix to be set.
+     */
+    public setMatrix2x2(uniformName: string, matrix: Float32Array): void {
+        this._valueCache[uniformName] = null;
+        this.engine.setMatrix2x2(this._uniforms[uniformName]!, matrix);
+    }
+
+    /**
+     * Sets a float on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param value value to be set.
+     * @returns this effect.
+     */
+    public setFloat(uniformName: string, value: number): void {
+        var cache = this._valueCache[uniformName];
+        if (cache !== undefined && cache === value) {
+            return;
+        }
+
+        if (this.engine.setFloat(this._uniforms[uniformName]!, value)) {
+            this._valueCache[uniformName] = value;
+        }
+    }
+
+    /**
+     * Sets a boolean on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param bool value to be set.
+     */
+    public setBool(uniformName: string, bool: boolean): void {
+        var cache = this._valueCache[uniformName];
+        if (cache !== undefined && cache === bool) {
+            return;
+        }
+
+        if (this.engine.setInt(this._uniforms[uniformName]!, bool ? 1 : 0)) {
+            this._valueCache[uniformName] = bool ? 1 : 0;
+        }
+    }
+
+    /**
+     * Sets a Vector2 on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param vector2 vector2 to be set.
+     */
+    public setVector2(uniformName: string, vector2: IVector2Like): void {
+        if (this._cacheFloat2(uniformName, vector2.x, vector2.y)) {
+            if (!this.engine.setFloat2(this._uniforms[uniformName]!, vector2.x, vector2.y)) {
+                this._valueCache[uniformName] = null;
+            }
+        }
+    }
+
+    /**
+     * Sets a float2 on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param x First float in float2.
+     * @param y Second float in float2.
+     */
+    public setFloat2(uniformName: string, x: number, y: number): void {
+        if (this._cacheFloat2(uniformName, x, y)) {
+            if (!this.engine.setFloat2(this._uniforms[uniformName]!, x, y)) {
+                this._valueCache[uniformName] = null;
+            }
+        }
+    }
+
+    /**
+     * Sets a Vector3 on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param vector3 Value to be set.
+     */
+    public setVector3(uniformName: string, vector3: IVector3Like): void {
+        if (this._cacheFloat3(uniformName, vector3.x, vector3.y, vector3.z)) {
+            if (!this.engine.setFloat3(this._uniforms[uniformName]!, vector3.x, vector3.y, vector3.z)) {
+                this._valueCache[uniformName] = null;
+            }
+        }
+    }
+
+    /**
+     * Sets a float3 on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param x First float in float3.
+     * @param y Second float in float3.
+     * @param z Third float in float3.
+     */
+    public setFloat3(uniformName: string, x: number, y: number, z: number): void {
+        if (this._cacheFloat3(uniformName, x, y, z)) {
+            if (!this.engine.setFloat3(this._uniforms[uniformName]!, x, y, z)) {
+                this._valueCache[uniformName] = null;
+            }
+        }
+    }
+
+    /**
+     * Sets a Vector4 on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param vector4 Value to be set.
+     */
+    public setVector4(uniformName: string, vector4: IVector4Like): void {
+        if (this._cacheFloat4(uniformName, vector4.x, vector4.y, vector4.z, vector4.w)) {
+            if (!this.engine.setFloat4(this._uniforms[uniformName]!, vector4.x, vector4.y, vector4.z, vector4.w)) {
+                this._valueCache[uniformName] = null;
+            }
+        }
+    }
+
+    /**
+     * Sets a float4 on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param x First float in float4.
+     * @param y Second float in float4.
+     * @param z Third float in float4.
+     * @param w Fourth float in float4.
+     * @returns this effect.
+     */
+    public setFloat4(uniformName: string, x: number, y: number, z: number, w: number): void {
+        if (this._cacheFloat4(uniformName, x, y, z, w)) {
+            if (!this.engine.setFloat4(this._uniforms[uniformName]!, x, y, z, w)) {
+                this._valueCache[uniformName] = null;
+            }
+        }
+    }
+
+    /**
+     * Sets a Color3 on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param color3 Value to be set.
+     */
+    public setColor3(uniformName: string, color3: IColor3Like): void {
+        if (this._cacheFloat3(uniformName, color3.r, color3.g, color3.b)) {
+            if (!this.engine.setFloat3(this._uniforms[uniformName]!, color3.r, color3.g, color3.b)) {
+                this._valueCache[uniformName] = null;
+            }
+        }
+    }
+
+    /**
+     * Sets a Color4 on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param color3 Value to be set.
+     * @param alpha Alpha value to be set.
+     */
+    public setColor4(uniformName: string, color3: IColor3Like, alpha: number): void {
+        if (this._cacheFloat4(uniformName, color3.r, color3.g, color3.b, alpha)) {
+            if (!this.engine.setFloat4(this._uniforms[uniformName]!, color3.r, color3.g, color3.b, alpha)) {
+                this._valueCache[uniformName] = null;
+            }
+        }
+    }
+
+    /**
+     * Sets a Color4 on a uniform variable
+     * @param uniformName defines the name of the variable
+     * @param color4 defines the value to be set
+     */
+    public setDirectColor4(uniformName: string, color4: IColor4Like): void {
+        if (this._cacheFloat4(uniformName, color4.r, color4.g, color4.b, color4.a)) {
+            if (!this.engine.setFloat4(this._uniforms[uniformName]!, color4.r, color4.g, color4.b, color4.a)) {
+                this._valueCache[uniformName] = null;
+            }
+        }
+    }
 }
 
 /**
@@ -271,6 +799,25 @@ export class NativeEngine extends Engine {
             maxMSAASamples: 1
         };
 
+        this._features = {
+            forceBitmapOverHTMLImageElement: false,
+            supportRenderAndCopyToLodForFloatTextures: false,
+            supportDepthStencilTexture: false,
+            supportShadowSamplers: false,
+            uniformBufferHardCheckMatrix: false,
+            allowTexturePrefiltering: false,
+            trackUbosInFrame: false,
+            supportCSM: false,
+            basisNeedsPOT: false,
+            support3DTextures: false,
+            needTypeSuffixInShaderConstants: false,
+            supportMSAA: false,
+            supportSSAO2: false,
+            supportExtendedTextureFormats: false,
+            supportSwitchCaseInShader: false,
+            _collectUbosUpdatedInFrame: false,
+        };
+
         Tools.Log("Babylon Native (v" + Engine.Version + ") launched");
 
         Tools.LoadScript = function (scriptUrl, onSuccess, onError, scriptId) {
@@ -502,7 +1049,7 @@ export class NativeEngine extends Engine {
         return new NativePipelineContext();
     }
 
-    public _preparePipelineContext(pipelineContext: IPipelineContext, vertexSourceCode: string, fragmentSourceCode: string, createAsRaw: boolean, rebuildRebind: any, defines: Nullable<string>, transformFeedbackVaryings: Nullable<string[]>) {
+    public _preparePipelineContext(pipelineContext: IPipelineContext, vertexSourceCode: string, fragmentSourceCode: string, createAsRaw: boolean, rawVertexSourceCode: string, rawFragmentSourceCode: string, rebuildRebind: any, defines: Nullable<string>, transformFeedbackVaryings: Nullable<string[]>) {
         const nativePipelineContext = pipelineContext as NativePipelineContext;
 
         if (createAsRaw) {
@@ -589,7 +1136,7 @@ export class NativeEngine extends Engine {
         this._currentEffect = null;
     }
 
-    public setMatrix(uniform: WebGLUniformLocation, matrix: Matrix): void {
+    public setMatrix(uniform: WebGLUniformLocation, matrix: IMatrixLike): void {
         if (!uniform) {
             return;
         }
@@ -613,7 +1160,7 @@ export class NativeEngine extends Engine {
         return this._native.getRenderHeight();
     }
 
-    public setViewport(viewport: Viewport, requiredWidth?: number, requiredHeight?: number): void {
+    public setViewport(viewport: IViewportLike, requiredWidth?: number, requiredHeight?: number): void {
         this._cachedViewport = viewport;
         this._native.setViewPort(viewport.x, viewport.y, viewport.width, viewport.height);
     }
@@ -921,7 +1468,7 @@ export class NativeEngine extends Engine {
         return true;
     }
 
-    public setColor3(uniform: WebGLUniformLocation, color3: Color3): boolean {
+    public setColor3(uniform: WebGLUniformLocation, color3: IColor3Like): boolean {
         if (!uniform) {
             return false;
         }
@@ -930,7 +1477,7 @@ export class NativeEngine extends Engine {
         return true;
     }
 
-    public setColor4(uniform: WebGLUniformLocation, color3: Color3, alpha: number): boolean {
+    public setColor4(uniform: WebGLUniformLocation, color3: IColor3Like, alpha: number): boolean {
         if (!uniform) {
             return false;
         }
@@ -959,7 +1506,7 @@ export class NativeEngine extends Engine {
         this._cachedEffectForVertexBuffers = null;
     }
 
-    public _createTexture(): WebGLTexture {
+    protected _createTexture(): WebGLTexture {
         return this._native.createTexture();
     }
 
@@ -1083,7 +1630,7 @@ export class NativeEngine extends Engine {
             throw new Error("Loading textures from IInternalTextureLoader not yet implemented.");
         } else {
             const onload = (data: ArrayBufferView) => {
-                const webGLTexture = texture._webGLTexture;
+                const webGLTexture = texture._hardwareTexture?.underlyingResource;
                 if (!webGLTexture) {
                     if (scene) {
                         scene._removePendingData(texture);
@@ -1146,7 +1693,7 @@ export class NativeEngine extends Engine {
         const height = (<{ width: number, height: number, layers?: number }>size).height || <number>size;
 
         var framebuffer = this._native.createDepthTexture(
-            texture._webGLTexture!,
+            texture._hardwareTexture!.underlyingResource,
             width,
             height);
 
@@ -1234,7 +1781,7 @@ export class NativeEngine extends Engine {
                 texture._isRGBD = true;
                 texture.invertY = true;
 
-                this._native.loadCubeTextureWithMips(texture._webGLTexture!, imageData, () => {
+                this._native.loadCubeTextureWithMips(texture._hardwareTexture!.underlyingResource, imageData, () => {
                     texture.isReady = true;
                     if (onLoad) {
                         onLoad();
@@ -1266,7 +1813,7 @@ export class NativeEngine extends Engine {
             const reorderedFiles = [files[0], files[3], files[1], files[4], files[2], files[5]];
             Promise.all(reorderedFiles.map((file) => Tools.LoadFileAsync(file).then((data) => new Uint8Array(data as ArrayBuffer)))).then((data) => {
                 return new Promise<void>((resolve, reject) => {
-                    this._native.loadCubeTexture(texture._webGLTexture!, data, !noMipmap, resolve, reject);
+                    this._native.loadCubeTexture(texture._hardwareTexture!.underlyingResource, data, !noMipmap, resolve, reject);
                 });
             }).then(() => {
                 texture.isReady = true;
@@ -1323,7 +1870,7 @@ export class NativeEngine extends Engine {
         }
 
         var framebuffer = this._native.createFramebuffer(
-            texture._webGLTexture!,
+            texture._hardwareTexture!.underlyingResource,
             width,
             height,
             this._getNativeTextureFormat(fullOptions.format, fullOptions.type),
@@ -1352,9 +1899,9 @@ export class NativeEngine extends Engine {
     }
 
     public updateTextureSamplingMode(samplingMode: number, texture: InternalTexture): void {
-        if (texture._webGLTexture) {
+        if (texture._hardwareTexture?.underlyingResource ?? false) {
             var filter = this._getNativeSamplingMode(samplingMode);
-            this._native.setTextureSampling(texture._webGLTexture, filter);
+            this._native.setTextureSampling(texture._hardwareTexture?.underlyingResource, filter);
         }
         texture.samplingMode = samplingMode;
     }
@@ -1462,18 +2009,18 @@ export class NativeEngine extends Engine {
         this._activeChannel = channel;
 
         if (!internalTexture ||
-            !internalTexture._webGLTexture) {
+            !internalTexture._hardwareTexture?.underlyingResource) {
             return false;
         }
 
         this._native.setTextureWrapMode(
-            internalTexture._webGLTexture,
+            internalTexture._hardwareTexture?.underlyingResource,
             this._getAddressMode(texture.wrapU),
             this._getAddressMode(texture.wrapV),
             this._getAddressMode(texture.wrapR));
         this._updateAnisotropicLevel(texture);
 
-        this._native.setTexture(uniform, internalTexture._webGLTexture);
+        this._native.setTexture(uniform, internalTexture._hardwareTexture?.underlyingResource);
 
         return true;
     }
@@ -1484,12 +2031,12 @@ export class NativeEngine extends Engine {
         var internalTexture = texture.getInternalTexture();
         var value = texture.anisotropicFilteringLevel;
 
-        if (!internalTexture || !internalTexture._webGLTexture) {
+        if (!internalTexture || !internalTexture._hardwareTexture?.underlyingResource) {
             return;
         }
 
         if (internalTexture._cachedAnisotropicFilteringLevel !== value) {
-            this._native.setTextureAnisotropicLevel(internalTexture._webGLTexture, value);
+            this._native.setTextureAnisotropicLevel(internalTexture._hardwareTexture?.underlyingResource, value);
             internalTexture._cachedAnisotropicFilteringLevel = value;
         }
     }
@@ -1514,7 +2061,7 @@ export class NativeEngine extends Engine {
         if (!uniform) {
             return ;
         }
-        this._native.setTexture(uniform, texture._webGLTexture);
+        this._native.setTexture(uniform, texture._hardwareTexture?.underlyingResource);
     }
 
     protected _deleteBuffer(buffer: NativeDataBuffer): void {

+ 20 - 1
src/Engines/nullEngine.ts

@@ -143,6 +143,25 @@ export class NullEngine extends Engine {
             blendMinMax: false
         };
 
+        this._features = {
+            forceBitmapOverHTMLImageElement: false,
+            supportRenderAndCopyToLodForFloatTextures: false,
+            supportDepthStencilTexture: false,
+            supportShadowSamplers: false,
+            uniformBufferHardCheckMatrix: false,
+            allowTexturePrefiltering: false,
+            trackUbosInFrame: false,
+            supportCSM: false,
+            basisNeedsPOT: false,
+            support3DTextures: false,
+            needTypeSuffixInShaderConstants: false,
+            supportMSAA: false,
+            supportSSAO2: false,
+            supportExtendedTextureFormats: false,
+            supportSwitchCaseInShader: false,
+            _collectUbosUpdatedInFrame: false,
+        };
+
         Logger.Log(`Babylon.js v${Engine.Version} - Null engine`);
 
         // Wrappers
@@ -578,7 +597,7 @@ export class NullEngine extends Engine {
     }
 
     /** @hidden */
-    public _createTexture(): WebGLTexture {
+    protected _createTexture(): WebGLTexture {
         return {};
     }
 

+ 337 - 116
src/Engines/thinEngine.ts

@@ -3,6 +3,7 @@ import { IInternalTextureLoader } from '../Materials/Textures/internalTextureLoa
 import { Effect, IEffectCreationOptions } from '../Materials/effect';
 import { _DevTools } from '../Misc/devTools';
 import { IShaderProcessor } from './Processors/iShaderProcessor';
+import { ShaderProcessingContext } from "./Processors/shaderProcessingOptions";
 import { UniformBuffer } from '../Materials/uniformBuffer';
 import { Nullable, DataArray, IndicesArray } from '../types';
 import { EngineCapabilities } from './engineCapabilities';
@@ -30,6 +31,9 @@ import { IEffectFallbacks } from '../Materials/iEffectFallbacks';
 import { IWebRequest } from '../Misc/interfaces/iWebRequest';
 import { CanvasGenerator } from '../Misc/canvasGenerator';
 import { PerformanceConfigurator } from './performanceConfigurator';
+import { EngineFeatures } from './engineFeatures';
+import { HardwareTextureWrapper } from '../Materials/Textures/hardwareTextureWrapper';
+import { WebGLHardwareTexture } from './WebGL/webGLHardwareTexture';
 
 declare type WebRequest = import("../Misc/webRequest").WebRequest;
 declare type LoadFileError = import("../Misc/fileTools").LoadFileError;
@@ -172,7 +176,7 @@ export class ThinEngine {
      * Returns a string describing the current engine
      */
     public get description(): string {
-        let description = "WebGL" + this.webGLVersion;
+        let description = this.name + this.webGLVersion;
 
         if (this._caps.parallelShaderCompile) {
             description += " - Parallel shader compilation";
@@ -181,6 +185,20 @@ export class ThinEngine {
         return description;
     }
 
+    /**
+     * Returns the name of the engine
+     */
+    public get name(): string {
+        return "WebGL";
+    }
+
+    /**
+     * Returns the version of the engine
+     */
+    public get version(): number {
+        return this._webGLVersion;
+    }
+
     // Updatable statics so stick with vars here
 
     /**
@@ -201,7 +219,7 @@ export class ThinEngine {
     // Public members
 
     /** @hidden */
-    public _shaderProcessor: IShaderProcessor;
+    public _shaderProcessor: Nullable<IShaderProcessor>;
 
     /**
      * Gets or sets a boolean that indicates if textures must be forced to power of 2 size even if not required
@@ -243,6 +261,14 @@ export class ThinEngine {
      */
     public disableUniformBuffers = false;
 
+    private _frameId = 0;
+    /**
+     * Gets the current frame id
+     */
+    public get frameId(): number {
+        return this._frameId;
+    }
+
     /** @hidden */
     public _uniformBuffers = new Array<UniformBuffer>();
 
@@ -284,10 +310,12 @@ export class ThinEngine {
     /** @hidden */
     public _badDesktopOS = false;
 
-    private _hardwareScalingLevel: number;
+    protected _hardwareScalingLevel: number;
     /** @hidden */
     public _caps: EngineCapabilities;
-    private _isStencilEnable: boolean;
+    /** @hidden */
+    public _features: EngineFeatures;
+    protected _isStencilEnable: boolean;
 
     private _glVersion: string;
     private _glRenderer: string;
@@ -360,7 +388,7 @@ export class ThinEngine {
     protected _currentEffect: Nullable<Effect>;
     /** @hidden */
     protected _currentProgram: Nullable<WebGLProgram>;
-    private _compiledEffects: { [key: string]: Effect } = {};
+    protected _compiledEffects: { [key: string]: Effect } = {};
     private _vertexAttribArraysEnabled: boolean[] = [];
     /** @hidden */
     protected _cachedViewport: Nullable<IViewportLike>;
@@ -489,13 +517,31 @@ export class ThinEngine {
     /**
      * Defines whether the engine has been created with the premultipliedAlpha option on or not.
      */
-    public readonly premultipliedAlpha: boolean = true;
+    public premultipliedAlpha: boolean = true;
 
     /**
      * Observable event triggered before each texture is initialized
      */
     public onBeforeTextureInitObservable = new Observable<Texture>();
 
+    /** @hidden */
+    protected _isWebGPU: boolean = false;
+    /**
+     * Gets a boolean indicating if the engine runs in WebGPU or not.
+     */
+    public get isWebGPU(): boolean {
+        return this._isWebGPU;
+    }
+
+    /** @hidden */
+    protected _shaderPlatformName: string;
+    /**
+     * Gets the shader platfrom name used by the effects.
+     */
+    public get shaderPlatformName(): string {
+        return this._shaderPlatformName;
+    }
+
     /**
      * Creates a new engine
      * @param canvasOrContext defines the canvas or WebGL context to use for rendering. If you provide a WebGL context, Babylon.js will not hook events on the canvas (like pointers, keyboards, etc...) so no event observables will be available. This is mostly used when Babylon.js is used as a plugin on a system which alreay used the WebGL context
@@ -507,14 +553,14 @@ export class ThinEngine {
 
         let canvas: Nullable<HTMLCanvasElement> = null;
 
-        if (!canvasOrContext) {
-            return;
-        }
-
         options = options || {};
 
         PerformanceConfigurator.SetMatrixPrecision(!!options.useHighPrecisionMatrix);
 
+        if (!canvasOrContext) {
+            return;
+        }
+
         if ((canvasOrContext as any).getContext) {
             canvas = <HTMLCanvasElement>canvasOrContext;
             this._renderingCanvas = canvas;
@@ -639,10 +685,12 @@ export class ThinEngine {
                     this._gl = <any>(canvas.getContext("webgl2", options) || canvas.getContext("experimental-webgl2", options));
                     if (this._gl) {
                         this._webGLVersion = 2.0;
+                        this._shaderPlatformName = "WEBGL2";
 
                         // Prevent weird browsers to lie (yeah that happens!)
                         if (!this._gl.deleteQuery) {
                             this._webGLVersion = 1.0;
+                            this._shaderPlatformName = "WEBGL1";
                         }
                     }
                 } catch (e) {
@@ -670,6 +718,10 @@ export class ThinEngine {
 
             if (this._gl.renderbufferStorageMultisample) {
                 this._webGLVersion = 2.0;
+                this._shaderPlatformName = "WEBGL2";
+            }
+            else {
+                this._shaderPlatformName = "WEBGL1";
             }
 
             const attributes = this._gl.getContextAttributes();
@@ -694,6 +746,7 @@ export class ThinEngine {
 
         this._isStencilEnable = options.stencil ? true : false;
         this._initGLContext();
+        this._initFeatures();
 
         // Prepare buffer pointers
         for (var i = 0; i < this._caps.maxVertexAttribs; i++) {
@@ -726,6 +779,53 @@ export class ThinEngine {
         console.log(`Babylon.js v${ThinEngine.Version} - ${this.description}`);
     }
 
+    /**
+     * @hidden
+     */
+    public _debugPushGroup(groupName: string, targetObject?: number): void {
+    }
+
+    /**
+     * @hidden
+     */
+    public _debugPopGroup(targetObject?: number): void {
+    }
+
+    /**
+     * @hidden
+     */
+    public _debugInsertMarker(text: string, targetObject?: number): void {
+    }
+
+    /**
+     * Shared initialization across engines types.
+     * @param canvas The canvas associated with this instance of the engine.
+     * @param doNotHandleTouchAction Defines that engine should ignore modifying touch action attribute and style
+     * @param audioEngine Defines if an audio engine should be created by default
+     */
+    protected _sharedInit(canvas: HTMLCanvasElement, doNotHandleTouchAction: boolean, audioEngine: boolean) {
+        this._renderingCanvas = canvas;
+
+        // Shader processor
+        this._shaderProcessor = this._getShaderProcessor();
+    }
+
+    /**
+     * Gets a shader processor implementation fitting with the current engine type.
+     * @returns The shader processor implementation.
+     */
+    protected _getShaderProcessor(): Nullable<IShaderProcessor> {
+        if (this.webGLVersion > 1) {
+            return new WebGL2ShaderProcessor();
+        }
+        return null;
+    }
+
+    /** @hidden */
+    public _getShaderProcessingContext(): Nullable<ShaderProcessingContext> {
+        return null;
+    }
+
     private _rebuildInternalTextures(): void {
         let currentState = this._internalTexturesCache.slice(); // Do a copy because the rebuild will add proxies
 
@@ -961,8 +1061,30 @@ export class ThinEngine {
         }
     }
 
+    protected _initFeatures(): void {
+        this._features = {
+            forceBitmapOverHTMLImageElement: false,
+            supportRenderAndCopyToLodForFloatTextures: this._webGLVersion !== 1,
+            supportDepthStencilTexture: this._webGLVersion !== 1,
+            supportShadowSamplers: this._webGLVersion !== 1,
+            uniformBufferHardCheckMatrix: false,
+            allowTexturePrefiltering: this._webGLVersion !== 1,
+            trackUbosInFrame: false,
+            supportCSM: this._webGLVersion !== 1,
+            basisNeedsPOT: this._webGLVersion === 1,
+            support3DTextures: this._webGLVersion !== 1,
+            needTypeSuffixInShaderConstants: this._webGLVersion !== 1,
+            supportMSAA: this._webGLVersion !== 1,
+            supportSSAO2: this._webGLVersion !== 1,
+            supportExtendedTextureFormats: this._webGLVersion !== 1,
+            supportSwitchCaseInShader: this._webGLVersion !== 1,
+            _collectUbosUpdatedInFrame: false,
+        };
+    }
+
     /**
      * Gets version of the current webGL context
+     * Keep it for back compat - use version instead
      */
     public get webGLVersion(): number {
         return this._webGLVersion;
@@ -1215,7 +1337,7 @@ export class ThinEngine {
         this._gl.clear(mode);
     }
 
-    private _viewportCached = { x: 0, y: 0, z: 0, w: 0 };
+    protected _viewportCached = { x: 0, y: 0, z: 0, w: 0 };
 
     /** @hidden */
     public _viewport(x: number, y: number, width: number, height: number): void {
@@ -1263,12 +1385,14 @@ export class ThinEngine {
         if (this._badOS) {
             this.flushFramebuffer();
         }
+        this._frameId++;
     }
 
     /**
      * Resize the view according to the canvas' size
+     * @param forceSetSize true to force setting the sizes of the underlying canvas
      */
-    public resize(): void {
+    public resize(forceSetSize = false): void {
         let width: number;
         let height: number;
 
@@ -1280,16 +1404,17 @@ export class ThinEngine {
             height = this._renderingCanvas ? this._renderingCanvas.height : 100;
         }
 
-        this.setSize(width / this._hardwareScalingLevel, height / this._hardwareScalingLevel);
+        this.setSize(width / this._hardwareScalingLevel, height / this._hardwareScalingLevel, forceSetSize);
     }
 
     /**
      * Force a specific size of the canvas
      * @param width defines the new canvas' width
      * @param height defines the new canvas' height
+     * @param forceSetSize true to force setting the sizes of the underlying canvas
      * @returns true if the size was changed
      */
-    public setSize(width: number, height: number): boolean {
+    public setSize(width: number, height: number, forceSetSize = false): boolean {
         if (!this._renderingCanvas) {
             return false;
         }
@@ -1297,7 +1422,7 @@ export class ThinEngine {
         width = width | 0;
         height = height | 0;
 
-        if (this._renderingCanvas.width === width && this._renderingCanvas.height === height) {
+        if (!forceSetSize && this._renderingCanvas.width === width && this._renderingCanvas.height === height) {
             return false;
         }
 
@@ -1326,23 +1451,23 @@ export class ThinEngine {
 
         const gl = this._gl;
         if (texture.is2DArray) {
-            gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, texture._webGLTexture, lodLevel, layer);
+            gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, texture._hardwareTexture!.underlyingResource, lodLevel, layer);
         }
         else if (texture.isCube) {
-            gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex, texture._webGLTexture, lodLevel);
+            gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex, texture._hardwareTexture!.underlyingResource, lodLevel);
         }
 
         const depthStencilTexture = texture._depthStencilTexture;
         if (depthStencilTexture) {
             const attachment = (depthStencilTexture._generateStencilBuffer) ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT;
             if (texture.is2DArray) {
-                gl.framebufferTextureLayer(gl.FRAMEBUFFER, attachment, depthStencilTexture._webGLTexture, lodLevel, layer);
+                gl.framebufferTextureLayer(gl.FRAMEBUFFER, attachment, depthStencilTexture._hardwareTexture!.underlyingResource, lodLevel, layer);
             }
             else if (texture.isCube) {
-                gl.framebufferTexture2D(gl.FRAMEBUFFER, attachment, gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex, depthStencilTexture._webGLTexture, lodLevel);
+                gl.framebufferTexture2D(gl.FRAMEBUFFER, attachment, gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex, depthStencilTexture._hardwareTexture!.underlyingResource, lodLevel);
             }
             else {
-                gl.framebufferTexture2D(gl.FRAMEBUFFER, attachment, gl.TEXTURE_2D, depthStencilTexture._webGLTexture, lodLevel);
+                gl.framebufferTexture2D(gl.FRAMEBUFFER, attachment, gl.TEXTURE_2D, depthStencilTexture._hardwareTexture!.underlyingResource, lodLevel);
             }
         }
 
@@ -2098,8 +2223,7 @@ export class ThinEngine {
 
             return compiledEffect;
         }
-        var effect = new Effect(baseName, attributesNamesOrOptions, uniformsNamesOrEngine, samplers, this, defines, fallbacks, onCompiled, onError, indexParameters);
-        effect._key = name;
+        var effect = new Effect(baseName, attributesNamesOrOptions, uniformsNamesOrEngine, samplers, this, defines, fallbacks, onCompiled, onError, indexParameters, name);
         this._compiledEffects[name] = effect;
 
         return effect;
@@ -2172,9 +2296,10 @@ export class ThinEngine {
 
     /**
      * Creates a new pipeline context
+     * @param shaderProcessingContext defines the shader processing context used during the processing if available
      * @returns the new pipeline
      */
-    public createPipelineContext(): IPipelineContext {
+    public createPipelineContext(shaderProcessingContext: Nullable<ShaderProcessingContext>): IPipelineContext {
         var pipelineContext = new WebGLPipelineContext();
         pipelineContext.engine = this;
 
@@ -2268,10 +2393,11 @@ export class ThinEngine {
     }
 
     /** @hidden */
-    public _preparePipelineContext(pipelineContext: IPipelineContext, vertexSourceCode: string, fragmentSourceCode: string, createAsRaw: boolean,
+    public _preparePipelineContext(pipelineContext: IPipelineContext, vertexSourceCode: string, fragmentSourceCode: string, createAsRaw: boolean, rawVertexSourceCode: string, rawFragmentSourceCode: string,
         rebuildRebind: any,
         defines: Nullable<string>,
-        transformFeedbackVaryings: Nullable<string[]>) {
+        transformFeedbackVaryings: Nullable<string[]>,
+        key: string) {
         let webGLRenderingState = pipelineContext as WebGLPipelineContext;
 
         if (createAsRaw) {
@@ -2333,7 +2459,7 @@ export class ThinEngine {
     }
 
     /**
-     * Gets the lsit of active attributes for a given webGL program
+     * Gets the list of active attributes for a given webGL program
      * @param pipelineContext defines the pipeline context to use
      * @param attributesNames defines the list of attribute names to get
      * @returns an array of indices indicating the offset of each attribute
@@ -2392,6 +2518,60 @@ export class ThinEngine {
     }
 
     /**
+     * Set the value of an uniform to a int2
+     * @param uniform defines the webGL uniform location where to store the value
+     * @param x defines the 1st component of the value
+     * @param y defines the 2nd component of the value
+     * @returns true if the value was set
+     */
+    public setInt2(uniform: Nullable<WebGLUniformLocation>, x: number, y: number): boolean {
+        if (!uniform) {
+            return false;
+        }
+
+        this._gl.uniform2i(uniform, x, y);
+
+        return true;
+    }
+
+    /**
+     * Set the value of an uniform to a int3
+     * @param uniform defines the webGL uniform location where to store the value
+     * @param x defines the 1st component of the value
+     * @param y defines the 2nd component of the value
+     * @param z defines the 3rd component of the value
+     * @returns true if the value was set
+     */
+    public setInt3(uniform: Nullable<WebGLUniformLocation>, x: number, y: number, z: number): boolean {
+        if (!uniform) {
+            return false;
+        }
+
+        this._gl.uniform3i(uniform, x, y, z);
+
+        return true;
+    }
+
+    /**
+     * Set the value of an uniform to a int4
+     * @param uniform defines the webGL uniform location where to store the value
+     * @param x defines the 1st component of the value
+     * @param y defines the 2nd component of the value
+     * @param z defines the 3rd component of the value
+     * @param w defines the 4th component of the value
+     * @returns true if the value was set
+     */
+    public setInt4(uniform: Nullable<WebGLUniformLocation>, x: number, y: number, z: number, w: number): boolean {
+        if (!uniform) {
+            return false;
+        }
+
+        this._gl.uniform4i(uniform, x, y, z, w);
+
+        return true;
+    }
+
+    /**
      * Set the value of an uniform to an array of int32
      * @param uniform defines the webGL uniform location where to store the value
      * @param array defines the array of int32 to store
@@ -2839,7 +3019,7 @@ export class ThinEngine {
     }
 
     /** @hidden */
-    public _createTexture(): WebGLTexture {
+    protected _createTexture(): WebGLTexture {
         let texture = this._gl.createTexture();
 
         if (!texture) {
@@ -2849,29 +3029,16 @@ export class ThinEngine {
         return texture;
     }
 
-    /**
-     * Usually called from Texture.ts.
-     * Passed information to create a WebGLTexture
-     * @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'
-     * @param noMipmap defines a boolean indicating that no mipmaps shall be generated.  Ignored for compressed textures.  They must be in the file
-     * @param invertY when true, image is flipped when loaded.  You probably want true. Certain compressed textures may invert this if their default is inverted (eg. ktx)
-     * @param scene needed for loading to the correct scene
-     * @param samplingMode mode with should be used sample / access the texture (Default: Texture.TRILINEAR_SAMPLINGMODE)
-     * @param onLoad optional callback to be called upon successful completion
-     * @param onError optional callback to be called upon failure
-     * @param buffer a source of a file previously fetched as either a base64 string, an ArrayBuffer (compressed or image format), HTMLImageElement (image format), or a Blob
-     * @param fallback an internal argument in case the function must be called again, due to etc1 not having alpha capabilities
-     * @param format internal format.  Default: RGB when extension is '.jpg' else RGBA.  Ignored for compressed textures
-     * @param forcedExtension defines the extension to use to pick the right loader
-     * @param mimeType defines an optional mime type
-     * @param loaderOptions options to be passed to the loader
-     * @returns a InternalTexture for assignment back into BABYLON.Texture
-     */
-    public createTexture(url: Nullable<string>, noMipmap: boolean, invertY: boolean, scene: Nullable<ISceneLike>, samplingMode: number = Constants.TEXTURE_TRILINEAR_SAMPLINGMODE,
+    /** @hidden */
+    public _createHardwareTexture(): HardwareTextureWrapper {
+        return new WebGLHardwareTexture(this._createTexture(), this._gl);
+    }
+
+    protected _createTextureBase(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,
+        prepareTexture: (texture: InternalTexture, extension: string, scene: Nullable<ISceneLike>, img: HTMLImageElement | ImageBitmap | { width: number, height: number }, invertY: boolean, noMipmap: boolean, isCompressed: boolean,
+            processFunction: (width: number, height: number, img: HTMLImageElement | ImageBitmap | { width: number, height: number }, extension: string, texture: InternalTexture, continuationCallback: () => void) => boolean, samplingMode: number) => void,
+        prepareTextureProcessFunction: (width: number, height: number, img: HTMLImageElement | ImageBitmap | { width: number, height: number }, extension: string, texture: InternalTexture, continuationCallback: () => void) => boolean,
         buffer: Nullable<string | ArrayBuffer | ArrayBufferView | HTMLImageElement | Blob | ImageBitmap> = null, fallback: Nullable<InternalTexture> = null, format: Nullable<number> = null,
         forcedExtension: Nullable<string> = null, mimeType?: string, loaderOptions?: any): InternalTexture {
         url = url || "";
@@ -2940,7 +3107,7 @@ export class ThinEngine {
                 }
 
                 if (EngineStore.UseFallbackTexture) {
-                    this.createTexture(EngineStore.FallbackTexture, noMipmap, texture.invertY, scene, samplingMode, null, onError, buffer, texture);
+                    this._createTextureBase(EngineStore.FallbackTexture, noMipmap, texture.invertY, scene, samplingMode, null, onError, prepareTexture, prepareTextureProcessFunction, buffer, texture);
                 }
 
                 if (onError) {
@@ -2950,7 +3117,7 @@ export class ThinEngine {
             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, loaderOptions);
+                this._createTextureBase(originalUrl, noMipmap, texture.invertY, scene, samplingMode, onLoad, onError, prepareTexture, prepareTextureProcessFunction, buffer, texture, format, forcedExtension, mimeType, loaderOptions);
             }
         };
 
@@ -2961,7 +3128,7 @@ export class ThinEngine {
                     if (loadFailed) {
                         onInternalError("TextureLoader failed to load data");
                     } else {
-                        this._prepareWebGLTexture(texture, scene, width, height, texture.invertY, !loadMipmap, isCompressed, () => {
+                        prepareTexture(texture, extension, scene, { width, height }, texture.invertY, !loadMipmap, isCompressed, () => {
                             done();
                             return false;
                         }, samplingMode);
@@ -2994,50 +3161,7 @@ export class ThinEngine {
                     texture._buffer = img;
                 }
 
-                this._prepareWebGLTexture(texture, scene, img.width, img.height, texture.invertY, noMipmap, false, (potWidth, potHeight, continuationCallback) => {
-                    let gl = this._gl;
-                    var isPot = (img.width === potWidth && img.height === potHeight);
-                    let internalFormat = format ? this._getInternalFormat(format) : ((extension === ".jpg") ? gl.RGB : gl.RGBA);
-
-                    if (isPot) {
-                        gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, internalFormat, gl.UNSIGNED_BYTE, img);
-                        return false;
-                    }
-
-                    let maxTextureSize = this._caps.maxTextureSize;
-
-                    if (img.width > maxTextureSize || img.height > maxTextureSize || !this._supportsHardwareTextureRescaling) {
-                        this._prepareWorkingCanvas();
-                        if (!this._workingCanvas || !this._workingContext) {
-                            return false;
-                        }
-
-                        this._workingCanvas.width = potWidth;
-                        this._workingCanvas.height = potHeight;
-
-                        this._workingContext.drawImage(img, 0, 0, img.width, img.height, 0, 0, potWidth, potHeight);
-                        gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, internalFormat, gl.UNSIGNED_BYTE, this._workingCanvas);
-
-                        texture.width = potWidth;
-                        texture.height = potHeight;
-
-                        return false;
-                    } else {
-                        // Using shaders when possible to rescale because canvas.drawImage is lossy
-                        let source = new InternalTexture(this, InternalTextureSource.Temp);
-                        this._bindTextureDirectly(gl.TEXTURE_2D, source, true);
-                        gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, internalFormat, gl.UNSIGNED_BYTE, img);
-
-                        this._rescaleTexture(source, texture, scene, internalFormat, () => {
-                            this._releaseTexture(source);
-                            this._bindTextureDirectly(gl.TEXTURE_2D, texture, true);
-
-                            continuationCallback();
-                        });
-                    }
-
-                    return true;
-                }, samplingMode);
+                prepareTexture(texture, extension, scene, img, texture.invertY, noMipmap, false, prepareTextureProcessFunction, samplingMode);
             };
 
             if (!fromData || isBase64) {
@@ -3059,6 +3183,83 @@ export class ThinEngine {
     }
 
     /**
+     * Usually called from Texture.ts.
+     * Passed information to create a WebGLTexture
+     * @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'
+     * @param noMipmap defines a boolean indicating that no mipmaps shall be generated.  Ignored for compressed textures.  They must be in the file
+     * @param invertY when true, image is flipped when loaded.  You probably want true. Certain compressed textures may invert this if their default is inverted (eg. ktx)
+     * @param scene needed for loading to the correct scene
+     * @param samplingMode mode with should be used sample / access the texture (Default: Texture.TRILINEAR_SAMPLINGMODE)
+     * @param onLoad optional callback to be called upon successful completion
+     * @param onError optional callback to be called upon failure
+     * @param buffer a source of a file previously fetched as either a base64 string, an ArrayBuffer (compressed or image format), HTMLImageElement (image format), or a Blob
+     * @param fallback an internal argument in case the function must be called again, due to etc1 not having alpha capabilities
+     * @param format internal format.  Default: RGB when extension is '.jpg' else RGBA.  Ignored for compressed textures
+     * @param forcedExtension defines the extension to use to pick the right loader
+     * @param mimeType defines an optional mime type
+     * @param loaderOptions options to be passed to the loader
+     * @returns a InternalTexture for assignment back into BABYLON.Texture
+     */
+    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, loaderOptions?: any): InternalTexture {
+
+        return this._createTextureBase(
+            url, noMipmap, invertY, scene, samplingMode, onLoad, onError,
+            this._prepareWebGLTexture.bind(this),
+            (potWidth, potHeight, img, extension, texture, continuationCallback) => {
+                let gl = this._gl;
+                var isPot = (img.width === potWidth && img.height === potHeight);
+                let internalFormat = format ? this._getInternalFormat(format) : ((extension === ".jpg") ? gl.RGB : gl.RGBA);
+
+                if (isPot) {
+                    gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, internalFormat, gl.UNSIGNED_BYTE, img as any);
+                    return false;
+                }
+
+                let maxTextureSize = this._caps.maxTextureSize;
+
+                if (img.width > maxTextureSize || img.height > maxTextureSize || !this._supportsHardwareTextureRescaling) {
+                    this._prepareWorkingCanvas();
+                    if (!this._workingCanvas || !this._workingContext) {
+                        return false;
+                    }
+
+                    this._workingCanvas.width = potWidth;
+                    this._workingCanvas.height = potHeight;
+
+                    this._workingContext.drawImage(img as any, 0, 0, img.width, img.height, 0, 0, potWidth, potHeight);
+                    gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, internalFormat, gl.UNSIGNED_BYTE, this._workingCanvas);
+
+                    texture.width = potWidth;
+                    texture.height = potHeight;
+
+                    return false;
+                } else {
+                    // Using shaders when possible to rescale because canvas.drawImage is lossy
+                    let source = new InternalTexture(this, InternalTextureSource.Temp);
+                    this._bindTextureDirectly(gl.TEXTURE_2D, source, true);
+                    gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, internalFormat, gl.UNSIGNED_BYTE, img as any);
+
+                    this._rescaleTexture(source, texture, scene, internalFormat, () => {
+                        this._releaseTexture(source);
+                        this._bindTextureDirectly(gl.TEXTURE_2D, texture, true);
+
+                        continuationCallback();
+                    });
+                }
+
+                return true;
+            },
+            buffer, fallback, format, forcedExtension, mimeType, loaderOptions
+        );
+    }
+
+    /**
      * Loads an image as an HTMLImageElement.
      * @param input url string, ArrayBuffer, or Blob to load
      * @param onLoad callback called when the image successfully loads
@@ -3209,6 +3410,16 @@ export class ThinEngine {
     }
 
     /**
+     * Update the dimensions of a texture
+     * @param texture texture to update
+     * @param width new width of the texture
+     * @param height new height of the texture
+     * @param depth new depth of the texture
+     */
+    public updateTextureDimensions(texture: InternalTexture, width: number, height: number, depth: number = 1): void {
+    }
+
+    /**
      * Update the sampling mode of a given texture
      * @param texture defines the texture to update
      * @param wrapU defines the texture wrap mode of the u coordinates
@@ -3235,7 +3446,7 @@ export class ThinEngine {
     }
 
     /** @hidden */
-    public _setupDepthStencilTexture(internalTexture: InternalTexture, size: number | { width: number, height: number, layers?: number }, generateStencil: boolean, bilinearFiltering: boolean, comparisonFunction: number): void {
+    public _setupDepthStencilTexture(internalTexture: InternalTexture, size: number | { width: number, height: number, layers?: number }, generateStencil: boolean, bilinearFiltering: boolean, comparisonFunction: number, samples = 1): void {
         const width = (<{ width: number, height: number, layers?: number }>size).width || <number>size;
         const height = (<{ width: number, height: number, layers?: number }>size).height || <number>size;
         const layers = (<{ width: number, height: number, layers?: number }>size).layers || 0;
@@ -3247,7 +3458,7 @@ export class ThinEngine {
         internalTexture.is2DArray = layers > 0;
         internalTexture.depth = layers;
         internalTexture.isReady = true;
-        internalTexture.samples = 1;
+        internalTexture.samples = samples;
         internalTexture.generateMipMaps = false;
         internalTexture._generateDepthBuffer = true;
         internalTexture._generateStencilBuffer = generateStencil;
@@ -3373,18 +3584,18 @@ export class ThinEngine {
         texture.onLoadedObservable.clear();
     }
 
-    private _prepareWebGLTexture(texture: InternalTexture, scene: Nullable<ISceneLike>, width: number, height: number, invertY: boolean, noMipmap: boolean, isCompressed: boolean,
-        processFunction: (width: number, height: number, continuationCallback: () => void) => boolean, samplingMode: number = Constants.TEXTURE_TRILINEAR_SAMPLINGMODE): void {
+    private _prepareWebGLTexture(texture: InternalTexture, extension: string, scene: Nullable<ISceneLike>, img: HTMLImageElement | ImageBitmap | { width: number, height: number }, invertY: boolean, noMipmap: boolean, isCompressed: boolean,
+        processFunction: (width: number, height: number, img: HTMLImageElement | ImageBitmap | { width: number, height: number }, extension: string, texture: InternalTexture, continuationCallback: () => void) => boolean, samplingMode: number = Constants.TEXTURE_TRILINEAR_SAMPLINGMODE): void {
         var maxTextureSize = this.getCaps().maxTextureSize;
-        var potWidth = Math.min(maxTextureSize, this.needPOTTextures ? ThinEngine.GetExponentOfTwo(width, maxTextureSize) : width);
-        var potHeight = Math.min(maxTextureSize, this.needPOTTextures ? ThinEngine.GetExponentOfTwo(height, maxTextureSize) : height);
+        var potWidth = Math.min(maxTextureSize, this.needPOTTextures ? ThinEngine.GetExponentOfTwo(img.width, maxTextureSize) : img.width);
+        var potHeight = Math.min(maxTextureSize, this.needPOTTextures ? ThinEngine.GetExponentOfTwo(img.height, maxTextureSize) : img.height);
 
         var gl = this._gl;
         if (!gl) {
             return;
         }
 
-        if (!texture._webGLTexture) {
+        if (!texture._hardwareTexture!.underlyingResource) {
             //  this.resetTextureCache();
             if (scene) {
                 scene._removePendingData(texture);
@@ -3396,13 +3607,13 @@ export class ThinEngine {
         this._bindTextureDirectly(gl.TEXTURE_2D, texture, true);
         this._unpackFlipY(invertY === undefined ? true : (invertY ? true : false));
 
-        texture.baseWidth = width;
-        texture.baseHeight = height;
+        texture.baseWidth = img.width;
+        texture.baseHeight = img.height;
         texture.width = potWidth;
         texture.height = potHeight;
         texture.isReady = true;
 
-        if (processFunction(potWidth, potHeight, () => {
+        if (processFunction(potWidth, potHeight, img, extension, texture, () => {
             this._prepareWebGLTextureContinuation(texture, scene, noMipmap, isCompressed, samplingMode);
         })) {
             // Returning as texture needs extra async steps
@@ -3483,7 +3694,7 @@ export class ThinEngine {
     public _releaseTexture(texture: InternalTexture): void {
         this._releaseFramebufferObjects(texture);
 
-        this._deleteTexture(texture._webGLTexture);
+        this._deleteTexture(texture._hardwareTexture?.underlyingResource);
 
         // Unbind channels
         this.unbindAllTextures();
@@ -3508,10 +3719,14 @@ export class ThinEngine {
         if (texture._irradianceTexture) {
             texture._irradianceTexture.dispose();
         }
+
+        texture._depthStencilTexture?.dispose();
     }
 
     protected _deleteTexture(texture: Nullable<WebGLTexture>): void {
-        this._gl.deleteTexture(texture);
+        if (texture) {
+            this._gl.deleteTexture(texture);
+        }
     }
 
     protected _setProgram(program: WebGLProgram): void {
@@ -3564,7 +3779,7 @@ export class ThinEngine {
             if (texture && texture.isMultiview) {
                 this._gl.bindTexture(target, texture ? texture._colorTextureArray : null);
             } else {
-                this._gl.bindTexture(target, texture ? texture._webGLTexture : null);
+                this._gl.bindTexture(target, texture ? texture._hardwareTexture!.underlyingResource : null);
             }
 
             this._boundTexturesCache[this._activeChannel] = texture;
@@ -3585,7 +3800,7 @@ export class ThinEngine {
     }
 
     /** @hidden */
-    public _bindTexture(channel: number, texture: Nullable<InternalTexture>): void {
+    public _bindTexture(channel: number, texture: Nullable<InternalTexture>, name: string): void {
         if (channel === undefined) {
             return;
         }
@@ -3619,8 +3834,9 @@ export class ThinEngine {
      * @param channel The texture channel
      * @param uniform The uniform to set
      * @param texture The texture to apply
+     * @param name The name of the uniform in the effect
      */
-    public setTexture(channel: number, uniform: Nullable<WebGLUniformLocation>, texture: Nullable<ThinTexture>): void {
+    public setTexture(channel: number, uniform: Nullable<WebGLUniformLocation>, texture: Nullable<ThinTexture>, name: string): void {
         if (channel === undefined) {
             return;
         }
@@ -3653,7 +3869,7 @@ export class ThinEngine {
         return this._gl.REPEAT;
     }
 
-    protected _setTexture(channel: number, texture: Nullable<ThinTexture>, isPartOfTextureArray = false, depthStencilTexture = false): boolean {
+    protected _setTexture(channel: number, texture: Nullable<ThinTexture>, isPartOfTextureArray = false, depthStencilTexture = false, name = ""): boolean {
         // Not ready?
         if (!texture) {
             if (this._boundTexturesCache[channel] != null) {
@@ -3752,8 +3968,9 @@ export class ThinEngine {
      * @param channel defines the channel where the texture array must be set
      * @param uniform defines the associated uniform location
      * @param textures defines the array of textures to bind
+     * @param name name of the channel
      */
-    public setTextureArray(channel: number, uniform: Nullable<WebGLUniformLocation>, textures: ThinTexture[]): void {
+    public setTextureArray(channel: number, uniform: Nullable<WebGLUniformLocation>, textures: ThinTexture[], name: string): void {
         if (channel === undefined || !uniform) {
             return;
         }
@@ -4308,14 +4525,18 @@ export class ThinEngine {
      * @param width defines the width of the rectangle where pixels must be read
      * @param height defines the height of the rectangle where pixels must be read
      * @param hasAlpha defines whether the output should have alpha or not (defaults to true)
-     * @returns a Uint8Array containing RGBA colors
+     * @param flushRenderer true to flush the renderer from the pending commands before reading the pixels
+     * @returns a ArrayBufferView promise (Uint8Array) containing RGBA colors
      */
-    public readPixels(x: number, y: number, width: number, height: number, hasAlpha = true): Uint8Array {
+    public readPixels(x: number, y: number, width: number, height: number, hasAlpha = true, flushRenderer = true): Promise<ArrayBufferView> {
         const numChannels = hasAlpha ? 4 : 3;
         const format = hasAlpha ? this._gl.RGBA : this._gl.RGB;
         const data = new Uint8Array(height * width * numChannels);
+        if (flushRenderer) {
+            this.flushFramebuffer();
+        }
         this._gl.readPixels(x, y, width, height, format, this._gl.UNSIGNED_BYTE, data);
-        return data;
+        return Promise.resolve(data);
     }
 
     // Statics

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 4376 - 0
src/Engines/webgpuEngine.ts


+ 1 - 0
src/LibDeclarations/browser.d.ts

@@ -65,4 +65,5 @@ interface HTMLVideoElement {
 interface Math {
     fround(x: number): number;
     imul(a: number, b: number): number;
+    log2(x: number): number;
 }

+ 979 - 0
src/LibDeclarations/webgpu.d.ts

@@ -0,0 +1,979 @@
+interface Navigator {
+    readonly gpu: GPU | undefined;
+}
+
+interface GPUColorDict {
+    a: number;
+    b: number;
+    g: number;
+    r: number;
+}
+type GPUColor = [number, number, number, number] | GPUColorDict;
+
+interface GPUOrigin2DDict {
+    x?: number;
+    y?: number;
+}
+type GPUOrigin2D = [number, number] | GPUOrigin2DDict;
+
+interface GPUOrigin3DDict {
+    x?: number;
+    y?: number;
+    z?: number;
+}
+type GPUOrigin3D = [number, number, number] | GPUOrigin3DDict;
+
+interface GPUExtent3DDict {
+    width: number;
+    height: number;
+    depth: number;
+}
+type GPUExtent3D = [number, number, number] | GPUExtent3DDict;
+
+type GPUBindingResource =
+    | GPUSampler
+    | GPUTextureView
+    | GPUBufferBinding;
+
+type GPUExtensionName =
+    | "texture-compression-bc"
+    | "timestamp-query"
+    | "pipeline-statistics-query"
+    | "depth-clamping"
+    | "depth24unorm-stencil8"
+    | "depth32float-stencil8";
+type GPUAddressMode = "clamp-to-edge" | "repeat" | "mirror-repeat";
+type GPUBindingType =
+    | "uniform-buffer"
+    | "storage-buffer"
+    | "readonly-storage-buffer"
+    | "sampler"
+    | "comparison-sampler"
+    | "sampled-texture"
+    | "readonly-storage-texture"
+    | "writeonly-storage-texture";
+type GPUBlendFactor =
+    | "zero"
+    | "one"
+    | "src-color"
+    | "one-minus-src-color"
+    | "src-alpha"
+    | "one-minus-src-alpha"
+    | "dst-color"
+    | "one-minus-dst-color"
+    | "dst-alpha"
+    | "one-minus-dst-alpha"
+    | "src-alpha-saturated"
+    | "blend-color"
+    | "one-minus-blend-color";
+type GPUBlendOperation =
+    | "add"
+    | "subtract"
+    | "reverse-subtract"
+    | "min"
+    | "max";
+type GPUCompareFunction =
+    | "never"
+    | "less"
+    | "equal"
+    | "less-equal"
+    | "greater"
+    | "not-equal"
+    | "greater-equal"
+    | "always";
+type GPUCullMode = "none" | "front" | "back";
+type GPUFilterMode = "nearest" | "linear";
+type GPUFrontFace = "ccw" | "cw";
+type GPUIndexFormat = "uint16" | "uint32";
+type GPUInputStepMode = "vertex" | "instance";
+type GPULoadOp = "load";
+type GPUPrimitiveTopology =
+    | "point-list"
+    | "line-list"
+    | "line-strip"
+    | "triangle-list"
+    | "triangle-strip";
+type GPUStencilOperation =
+    | "keep"
+    | "zero"
+    | "replace"
+    | "invert"
+    | "increment-clamp"
+    | "decrement-clamp"
+    | "increment-wrap"
+    | "decrement-wrap";
+type GPUStoreOp = "store" | "clear";
+type GPUTextureDimension = "1d" | "2d" | "3d";
+type GPUTextureFormat =
+    | "r8unorm"
+    | "r8snorm"
+    | "r8uint"
+    | "r8sint"
+    | "r16uint"
+    | "r16sint"
+    | "r16float"
+    | "rg8unorm"
+    | "rg8snorm"
+    | "rg8uint"
+    | "rg8sint"
+    | "r32uint"
+    | "r32sint"
+    | "r32float"
+    | "rg16uint"
+    | "rg16sint"
+    | "rg16float"
+    | "rgba8unorm"
+    | "rgba8unorm-srgb"
+    | "rgba8snorm"
+    | "rgba8uint"
+    | "rgba8sint"
+    | "bgra8unorm"
+    | "bgra8unorm-srgb"
+    | "rgb9e5ufloat"
+    | "rgb10a2unorm"
+    | "rg11b10ufloat"
+    | "rg32uint"
+    | "rg32sint"
+    | "rg32float"
+    | "rgba16uint"
+    | "rgba16sint"
+    | "rgba16float"
+    | "rgba32uint"
+    | "rgba32sint"
+    | "rgba32float"
+    | "stencil8"
+    | "depth16unorm"
+    | "depth24plus"
+    | "depth24plus-stencil8"
+    | "depth32float"
+    | "bc1-rgba-unorm"
+    | "bc1-rgba-unorm-srgb"
+    | "bc2-rgba-unorm"
+    | "bc2-rgba-unorm-srgb"
+    | "bc3-rgba-unorm"
+    | "bc3-rgba-unorm-srgb"
+    | "bc4-r-unorm"
+    | "bc4-r-snorm"
+    | "bc5-rg-unorm"
+    | "bc5-rg-snorm"
+    | "bc6h-rgb-ufloat"
+    | "bc6h-rgb-float"
+    | "bc7-rgba-unorm"
+    | "bc7-rgba-unorm-srgb"
+    | "depth24unorm-stencil8"
+    | "depth32float-stencil8";
+type GPUTextureComponentType = "float" | "sint" | "uint" | "depth-comparison";
+type GPUTextureViewDimension =
+    | "1d"
+    | "2d"
+    | "2d-array"
+    | "cube"
+    | "cube-array"
+    | "3d";
+type GPUVertexFormat =
+    | "uchar2"
+    | "uchar4"
+    | "char2"
+    | "char4"
+    | "uchar2norm"
+    | "uchar4norm"
+    | "char2norm"
+    | "char4norm"
+    | "ushort2"
+    | "ushort4"
+    | "short2"
+    | "short4"
+    | "ushort2norm"
+    | "ushort4norm"
+    | "short2norm"
+    | "short4norm"
+    | "half2"
+    | "half4"
+    | "float"
+    | "float2"
+    | "float3"
+    | "float4"
+    | "uint"
+    | "uint2"
+    | "uint3"
+    | "uint4"
+    | "int"
+    | "int2"
+    | "int3"
+    | "int4";
+
+type GPUTextureAspect = "all" | "stencil-only" | "depth-only";
+
+type GPUBufferUsageFlags = number;
+
+type GPUColorWriteFlags = number;
+
+type GPUShaderStageFlags = number;
+
+type GPUTextureUsageFlags = number;
+
+type GPUMapModeFlags = number;
+
+interface GPUBindGroupEntry {
+    binding: number;
+    resource: GPUBindingResource;
+}
+
+interface GPUBindGroupDescriptor extends GPUObjectDescriptorBase {
+    layout: GPUBindGroupLayout;
+    entries: Iterable<GPUBindGroupEntry>;
+}
+
+interface GPUBindGroupLayoutEntry {
+    binding: number;
+    visibility: GPUShaderStageFlags;
+    type: GPUBindingType;
+    hasDynamicOffset?: boolean;
+    minBufferBindingSize?: number;
+    viewDimension?: GPUTextureViewDimension;
+    textureComponentType?: GPUTextureComponentType;
+    storageTextureFormat?: GPUTextureFormat;
+}
+
+interface GPUBindGroupLayoutDescriptor
+    extends GPUObjectDescriptorBase {
+    entries: Iterable<GPUBindGroupLayoutEntry>;
+}
+
+interface GPUBlendDescriptor {
+    dstFactor?: GPUBlendFactor;
+    operation?: GPUBlendOperation;
+    srcFactor?: GPUBlendFactor;
+}
+
+interface GPUColorStateDescriptor {
+    format: GPUTextureFormat;
+
+    alphaBlend?: GPUBlendDescriptor;
+    colorBlend?: GPUBlendDescriptor;
+    writeMask?: GPUColorWriteFlags;
+}
+
+interface GPUBufferBinding {
+    buffer: GPUBuffer;
+    offset?: number;
+    size?: number;
+}
+
+interface GPUTextureDataLayout {
+    offset?: number;
+    bytesPerRow: number;
+    rowsPerImage?: number;
+}
+
+interface GPUBufferCopyView extends GPUTextureDataLayout {
+    buffer: GPUBuffer;
+}
+
+interface GPUTextureCopyView {
+    texture: GPUTexture;
+    mipLevel?: number;
+    origin?: GPUOrigin3D;
+}
+
+interface GPUImageBitmapCopyView {
+    imageBitmap: ImageBitmap;
+    origin?: GPUOrigin2D;
+}
+
+interface GPUBufferDescriptor extends GPUObjectDescriptorBase {
+    size: number;
+    usage: GPUBufferUsageFlags;
+    mappedAtCreation?: boolean;
+}
+
+interface GPUCommandEncoderDescriptor extends GPUObjectDescriptorBase {
+    label?: string;
+
+    measureExecutionTime?: boolean;
+}
+
+interface GPUComputePipelineDescriptor
+    extends GPUPipelineDescriptorBase {
+    computeStage: GPUProgrammableStageDescriptor;
+}
+
+interface GPUDepthStencilStateDescriptor {
+    format: GPUTextureFormat;
+
+    depthWriteEnabled?: boolean;
+    depthCompare?: GPUCompareFunction;
+
+    stencilFront?: GPUStencilStateFaceDescriptor;
+    stencilBack?: GPUStencilStateFaceDescriptor;
+
+    stencilReadMask?: number;
+    stencilWriteMask?: number;
+}
+
+interface GPUDeviceDescriptor extends GPUObjectDescriptorBase {
+    extensions?: Iterable<GPUExtensionName>;
+    limits?: GPULimits;
+}
+
+interface GPUFenceDescriptor extends GPUObjectDescriptorBase {
+    initialValue?: number;
+    label?: string;
+    signalQueue?: GPUQueue;
+}
+
+interface GPUVertexAttributeDescriptor {
+    format: GPUVertexFormat;
+    offset: number;
+    shaderLocation: number;
+}
+
+interface GPUVertexBufferLayoutDescriptor {
+    arrayStride: number;
+    stepMode?: GPUInputStepMode;
+    attributes: Iterable<GPUVertexAttributeDescriptor>;
+}
+
+interface GPUVertexStateDescriptor {
+    indexFormat?: GPUIndexFormat;
+    vertexBuffers?: Iterable<GPUVertexBufferLayoutDescriptor>;
+}
+
+interface GPULimits {
+    maxBindGroups?: number;
+    maxDynamicUniformBuffersPerPipelineLayout?: number;
+    maxDynamicStorageBuffersPerPipelineLayout?: number;
+    maxSampledTexturesPerShaderStage?: number;
+    maxSamplersPerShaderStage?: number;
+    maxStorageBuffersPerShaderStage?: number;
+    maxStorageTexturesPerShaderStage?: number;
+    maxUniformBuffersPerShaderStage?: number;
+    maxUniformBufferBindingSize?: number;
+}
+
+interface GPUPipelineDescriptorBase {
+    label?: string;
+    layout?: GPUPipelineLayout;
+}
+
+interface GPUPipelineLayoutDescriptor extends GPUObjectDescriptorBase {
+    bindGroupLayouts: Iterable<GPUBindGroupLayout>;
+}
+
+interface GPUProgrammableStageDescriptor {
+    module: GPUShaderModule;
+    entryPoint: string;
+}
+
+interface GPURasterizationStateDescriptor {
+    frontFace?: GPUFrontFace;
+    cullMode?: GPUCullMode;
+    clampDepth?: boolean;
+    depthBias?: number;
+    depthBiasSlopeScale?: number;
+    depthBiasClamp?: number;
+}
+
+interface GPURenderPassColorAttachmentDescriptor {
+    attachment: GPUTextureView;
+    resolveTarget?: GPUTextureView;
+
+    loadValue: GPULoadOp | GPUColor;
+    storeOp?: GPUStoreOp;
+}
+
+interface GPURenderPassDepthStencilAttachmentDescriptor {
+    attachment: GPUTextureView;
+
+    depthLoadValue: GPULoadOp | number;
+    depthStoreOp: GPUStoreOp;
+    depthReadOnly?: boolean;
+
+    stencilLoadValue: GPULoadOp | number;
+    stencilStoreOp: GPUStoreOp;
+    stencilReadOnly?: boolean;
+}
+
+interface GPURenderPassDescriptor extends GPUObjectDescriptorBase {
+    colorAttachments: Iterable<GPURenderPassColorAttachmentDescriptor>;
+    depthStencilAttachment?: GPURenderPassDepthStencilAttachmentDescriptor;
+}
+
+interface GPURenderPipelineDescriptor
+    extends GPUPipelineDescriptorBase {
+    vertexStage: GPUProgrammableStageDescriptor;
+    fragmentStage?: GPUProgrammableStageDescriptor;
+
+    primitiveTopology: GPUPrimitiveTopology;
+    rasterizationState?: GPURasterizationStateDescriptor;
+    colorStates: Iterable<GPUColorStateDescriptor>;
+    depthStencilState?: GPUDepthStencilStateDescriptor;
+    vertexState?: GPUVertexStateDescriptor;
+
+    sampleCount?: number;
+    sampleMask?: number;
+    alphaToCoverageEnabled?: boolean;
+}
+
+interface GPUSamplerDescriptor extends GPUObjectDescriptorBase {
+    addressModeU?: GPUAddressMode;
+    addressModeV?: GPUAddressMode;
+    addressModeW?: GPUAddressMode;
+    magFilter?: GPUFilterMode;
+    minFilter?: GPUFilterMode;
+    mipmapFilter?: GPUFilterMode;
+    lodMinClamp?: number;
+    lodMaxClamp?: number;
+    compare?: GPUCompareFunction;
+    maxAnisotropy?: number;
+}
+
+interface GPUShaderModuleDescriptor extends GPUObjectDescriptorBase {
+    code: Uint32Array | string;
+    label?: string;
+
+    sourceMap?: object;
+}
+
+interface GPUStencilStateFaceDescriptor {
+    compare?: GPUCompareFunction;
+    depthFailOp?: GPUStencilOperation;
+    passOp?: GPUStencilOperation;
+    failOp?: GPUStencilOperation;
+}
+
+interface GPUSwapChainDescriptor extends GPUObjectDescriptorBase {
+    device: GPUDevice;
+    format: GPUTextureFormat;
+    usage?: GPUTextureUsageFlags;
+}
+
+interface GPUTextureDescriptor extends GPUObjectDescriptorBase {
+    size: GPUExtent3D;
+    mipLevelCount?: number;
+    sampleCount?: number;
+    dimension?: GPUTextureDimension;
+    format: GPUTextureFormat;
+    usage: GPUTextureUsageFlags;
+}
+
+interface GPUTextureViewDescriptor extends GPUObjectDescriptorBase {
+    format?: GPUTextureFormat;
+    dimension?: GPUTextureViewDimension;
+    aspect?: GPUTextureAspect;
+    baseArrayLayer?: number;
+    baseMipLevel?: number;
+    arrayLayerCount?: number;
+    mipLevelCount?: number;
+}
+
+declare class GPUAdapter {
+    // https://michalzalecki.com/nominal-typing-in-typescript/#approach-1-class-with-a-private-property
+    private __brand: void;
+    readonly name: string;
+    readonly extensions: GPUExtensionName[];
+    readonly limits: Required<GPULimits>;
+
+    requestDevice(descriptor?: GPUDeviceDescriptor): Promise<GPUDevice | null>;
+}
+
+declare class GPUBindGroup implements GPUObjectBase {
+    private __brand: void;
+    label: string | undefined;
+}
+
+declare class GPUBindGroupLayout implements GPUObjectBase {
+    private __brand: void;
+    label: string | undefined;
+}
+
+declare class GPUBuffer implements GPUObjectBase {
+    private __brand: void;
+    label: string | undefined;
+
+    destroy(): void;
+    unmap(): void;
+
+    mapAsync(mode: GPUMapModeFlags, offset?: number, size?: number): Promise<void>;
+    getMappedRange(offset?: number, size?: number): ArrayBuffer;
+}
+
+declare class GPUCommandBuffer implements GPUObjectBase {
+    private __brand: void;
+    label: string | undefined;
+
+    readonly executionTime: Promise<number>;
+}
+
+interface GPUCommandBufferDescriptor extends GPUObjectDescriptorBase { }
+
+declare class GPUCommandEncoder implements GPUObjectBase {
+    private __brand: void;
+    label: string | undefined;
+
+    beginComputePass(
+        descriptor?: GPUComputePassDescriptor
+    ): GPUComputePassEncoder;
+    beginRenderPass(descriptor: GPURenderPassDescriptor): GPURenderPassEncoder;
+    copyBufferToBuffer(
+        source: GPUBuffer,
+        sourceOffset: number,
+        destination: GPUBuffer,
+        destinationOffset: number,
+        size: number
+    ): void;
+    copyBufferToTexture(
+        source: GPUBufferCopyView,
+        destination: GPUTextureCopyView,
+        copySize: GPUExtent3D
+    ): void;
+    copyTextureToBuffer(
+        source: GPUTextureCopyView,
+        destination: GPUBufferCopyView,
+        copySize: GPUExtent3D
+    ): void;
+    copyTextureToTexture(
+        source: GPUTextureCopyView,
+        destination: GPUTextureCopyView,
+        copySize: GPUExtent3D
+    ): void;
+    finish(descriptor?: GPUCommandBufferDescriptor): GPUCommandBuffer;
+
+    writeTimestamp(querySet: GPUQuerySet, queryIndex: number): void;
+
+    popDebugGroup(): void;
+    pushDebugGroup(groupLabel: string): void;
+    insertDebugMarker(markerLabel: string): void;
+}
+
+interface GPUComputePassDescriptor extends GPUObjectDescriptorBase { }
+
+declare class GPUComputePassEncoder implements GPUObjectBase, GPUProgrammablePassEncoder {
+    private __brand: void;
+    label: string | undefined;
+
+    setBindGroup(
+        index: number,
+        bindGroup: GPUBindGroup,
+        dynamicOffsets?: Iterable<number>
+    ): void;
+
+    popDebugGroup(): void;
+    pushDebugGroup(groupLabel: string): void;
+    insertDebugMarker(markerLabel: string): void;
+
+    setPipeline(pipeline: GPUComputePipeline): void;
+    dispatch(x: number, y?: number, z?: number): void;
+    dispatchIndirect(indirectBuffer: GPUBuffer, indirectOffset: number): void;
+
+    writeTimestamp(querySet: GPUQuerySet, queryIndex: number): void;
+    beginPipelineStatisticsQuery(querySet: GPUQuerySet, queryIndex: number): void;
+    endPipelineStatisticsQuery(querySet: GPUQuerySet, queryIndex: number): void;
+
+    endPass(): void;
+}
+
+declare class GPUComputePipeline implements GPUPipelineBase {
+    private __brand: void;
+    label: string | undefined;
+
+    getBindGroupLayout(index: number): GPUBindGroupLayout;
+}
+
+interface GPUObjectBase {
+    label: string | undefined;
+}
+
+interface GPUObjectDescriptorBase {
+    label?: string;
+}
+
+// SwapChain / CanvasContext
+declare class GPUCanvasContext {
+    private __brand: void;
+    configureSwapChain(descriptor: GPUSwapChainDescriptor): GPUSwapChain;
+
+    getSwapChainPreferredFormat(device: GPUDevice): Promise<GPUTextureFormat>;
+}
+
+declare class GPUDevice extends EventTarget implements GPUObjectBase {
+    private __brand: void;
+    label: string | undefined;
+
+    readonly adapter: GPUAdapter;
+    readonly extensions: GPUExtensionName[];
+    readonly limits: Required<GPULimits>;
+
+    createBindGroup(descriptor: GPUBindGroupDescriptor): GPUBindGroup;
+    createBindGroupLayout(
+        descriptor: GPUBindGroupLayoutDescriptor
+    ): GPUBindGroupLayout;
+    createBuffer(descriptor: GPUBufferDescriptor): GPUBuffer;
+    createPipelineLayout(
+        descriptor: GPUPipelineLayoutDescriptor
+    ): GPUPipelineLayout;
+    createSampler(descriptor?: GPUSamplerDescriptor): GPUSampler;
+    createShaderModule(descriptor: GPUShaderModuleDescriptor): GPUShaderModule;
+    createTexture(descriptor: GPUTextureDescriptor): GPUTexture;
+
+    createComputePipeline(
+        descriptor: GPUComputePipelineDescriptor
+    ): GPUComputePipeline;
+    createRenderPipeline(
+        descriptor: GPURenderPipelineDescriptor
+    ): GPURenderPipeline;
+    createReadyComputePipeline(
+        descriptor: GPUComputePipelineDescriptor
+    ): Promise<GPUComputePipeline>;
+    createReadyRenderPipeline(
+        descriptor: GPURenderPipelineDescriptor
+    ): Promise<GPURenderPipeline>;
+
+    createCommandEncoder(
+        descriptor?: GPUCommandEncoderDescriptor
+    ): GPUCommandEncoder;
+    createRenderBundleEncoder(
+        descriptor: GPURenderBundleEncoderDescriptor
+    ): GPURenderBundleEncoder;
+
+    createQuerySet(descriptor: GPUQuerySetDescriptor): GPUQuerySet;
+
+    defaultQueue: GPUQueue;
+
+    pushErrorScope(filter: GPUErrorFilter): void;
+    popErrorScope(): Promise<GPUError | null>;
+    onuncapturederror: Event | undefined;
+    readonly lost: Promise<GPUDeviceLostInfo>;
+}
+
+declare class GPUFence implements GPUObjectBase {
+    private __brand: void;
+    label: string | undefined;
+
+    getCompletedValue(): number;
+    onCompletion(completionValue: number): Promise<void>;
+}
+
+interface GPUPipelineBase extends GPUObjectBase {
+    getBindGroupLayout(index: number): GPUBindGroupLayout;
+}
+
+declare class GPUPipelineLayout implements GPUObjectBase {
+    private __brand: void;
+    label: string | undefined;
+}
+
+interface GPUProgrammablePassEncoder {
+    setBindGroup(
+        index: number,
+        bindGroup: GPUBindGroup,
+        dynamicOffsets?: Iterable<number>
+    ): void;
+
+    popDebugGroup(): void;
+    pushDebugGroup(groupLabel: string): void;
+    insertDebugMarker(markerLabel: string): void;
+}
+
+declare class GPUQueue implements GPUObjectBase {
+    private __brand: void;
+    label: string | undefined;
+
+    signal(fence: GPUFence, signalValue: number): void;
+    submit(commandBuffers: Iterable<GPUCommandBuffer>): void;
+    createFence(descriptor?: GPUFenceDescriptor): GPUFence;
+
+    writeBuffer(buffer: GPUBuffer,
+        bufferOffset: number,
+        data: BufferSource | ArrayBuffer,
+        dataOffset?: number,
+        size?: number): void;
+    writeTexture(destination: GPUTextureCopyView,
+        data: BufferSource | ArrayBuffer,
+        dataLayout: GPUTextureDataLayout,
+        size: GPUExtent3D): void;
+
+    copyImageBitmapToTexture(
+        source: GPUImageBitmapCopyView,
+        destination: GPUTextureCopyView,
+        copySize: GPUExtent3D
+    ): void;
+}
+
+type GPUQueryType =
+    | "occlusion"
+    | "timestamp"
+    | "pipeline-statistics";
+type GPUPipelineStatisticName =
+    | "vertex-shader-invocations"
+    | "clipper-invocations"
+    | "clipper-primitives-out"
+    | "fragment-shader-invocations"
+    | "compute-shader-invocations";
+
+interface GPUQuerySetDescriptor extends GPUObjectDescriptorBase {
+    type: GPUQueryType;
+    count: number;
+    pipelineStatistics?: Iterable<GPUPipelineStatisticName>;
+}
+
+declare class GPUQuerySet implements GPUObjectBase {
+    private __brand: void;
+    label: string | undefined;
+
+    destroy(): void;
+}
+
+interface GPURenderEncoderBase {
+    setPipeline(pipeline: GPURenderPipeline): void;
+
+    setIndexBuffer(buffer: GPUBuffer, offset?: number, size?: number): void;
+    setIndexBuffer(buffer: GPUBuffer, indexFormat: GPUIndexFormat, offset?: number, size?: number): void;
+    setVertexBuffer(slot: number, buffer: GPUBuffer, offset?: number, size?: number): void;
+
+    draw(
+        vertexCount: number,
+        instanceCount?: number,
+        firstVertex?: number,
+        firstInstance?: number
+    ): void;
+    drawIndexed(
+        indexCount: number,
+        instanceCount?: number,
+        firstIndex?: number,
+        baseVertex?: number,
+        firstInstance?: number
+    ): void;
+
+    drawIndirect(indirectBuffer: GPUBuffer, indirectOffset: number): void;
+    drawIndexedIndirect(
+        indirectBuffer: GPUBuffer,
+        indirectOffset: number
+    ): void;
+}
+
+declare class GPURenderPassEncoder implements GPUObjectBase, GPUProgrammablePassEncoder, GPURenderEncoderBase {
+    private __brand: void;
+    label: string | undefined;
+
+    setBindGroup(
+        index: number,
+        bindGroup: GPUBindGroup,
+        dynamicOffsets?: Iterable<number>
+    ): void;
+
+    popDebugGroup(): void;
+    pushDebugGroup(groupLabel: string): void;
+    insertDebugMarker(markerLabel: string): void;
+
+    setPipeline(pipeline: GPURenderPipeline): void;
+
+    setIndexBuffer(buffer: GPUBuffer, offset?: number): void;
+    setIndexBuffer(buffer: GPUBuffer, indexFormat: GPUIndexFormat, offset?: number, size?: number): void;
+    setVertexBuffer(slot: number, buffer: GPUBuffer, offset?: number): void;
+
+    draw(
+        vertexCount: number,
+        instanceCount?: number,
+        firstVertex?: number,
+        firstInstance?: number
+    ): void;
+    drawIndexed(
+        indexCount: number,
+        instanceCount?: number,
+        firstIndex?: number,
+        baseVertex?: number,
+        firstInstance?: number
+    ): void;
+
+    drawIndirect(indirectBuffer: GPUBuffer, indirectOffset: number): void;
+    drawIndexedIndirect(
+        indirectBuffer: GPUBuffer,
+        indirectOffset: number
+    ): void;
+
+    setViewport(
+        x: number,
+        y: number,
+        width: number,
+        height: number,
+        minDepth: number,
+        maxDepth: number
+    ): void;
+    setScissorRect(x: number, y: number, width: number, height: number): void;
+
+    setBlendColor(color: GPUColor): void;
+    setStencilReference(reference: number): void;
+
+    writeTimestamp(querySet: GPUQuerySet, queryIndex: number): void;
+    beginOcclusionQuery(queryIndex: number): void;
+    endOcclusionQuery(): void;
+    beginPipelineStatisticsQuery(querySet: GPUQuerySet, queryIndex: number): void;
+    endPipelineStatisticsQuery(querySet: GPUQuerySet, queryIndex: number): void;
+
+    executeBundles(bundles: Iterable<GPURenderBundle>): void;
+    endPass(): void;
+}
+
+interface GPURenderBundleDescriptor extends GPUObjectDescriptorBase { }
+
+declare class GPURenderBundle implements GPUObjectBase {
+    private __brand: void;
+    label: string | undefined;
+}
+
+declare class GPURenderBundleEncoder implements GPURenderEncoderBase {
+    private __brand: void;
+    label: string | undefined;
+
+    setBindGroup(
+        index: number,
+        bindGroup: GPUBindGroup,
+        dynamicOffsets?: Iterable<number>
+    ): void;
+
+    popDebugGroup(): void;
+    pushDebugGroup(groupLabel: string): void;
+    insertDebugMarker(markerLabel: string): void;
+
+    setPipeline(pipeline: GPURenderPipeline): void;
+
+    setIndexBuffer(buffer: GPUBuffer, offset?: number): void;
+    setIndexBuffer(buffer: GPUBuffer, indexFormat: GPUIndexFormat, offset?: number, size?: number): void;
+    setVertexBuffer(slot: number, buffer: GPUBuffer, offset?: number): void;
+
+    draw(
+        vertexCount: number,
+        instanceCount?: number,
+        firstVertex?: number,
+        firstInstance?: number
+    ): void;
+    drawIndexed(
+        indexCount: number,
+        instanceCount?: number,
+        firstIndex?: number,
+        baseVertex?: number,
+        firstInstance?: number
+    ): void;
+
+    drawIndirect(indirectBuffer: GPUBuffer, indirectOffset: number): void;
+    drawIndexedIndirect(
+        indirectBuffer: GPUBuffer,
+        indirectOffset: number
+    ): void;
+
+    finish(descriptor?: GPURenderBundleDescriptor): GPURenderBundle;
+}
+
+interface GPURenderBundleEncoderDescriptor
+    extends GPUObjectDescriptorBase {
+    colorFormats: Iterable<GPUTextureFormat>;
+    depthStencilFormat?: GPUTextureFormat;
+    sampleCount?: number;
+}
+
+declare class GPURenderPipeline implements GPUPipelineBase {
+    private __brand: void;
+    label: string | undefined;
+
+    getBindGroupLayout(index: number): GPUBindGroupLayout;
+}
+
+declare class GPUSampler implements GPUObjectBase {
+    private __brand: void;
+    label: string | undefined;
+}
+
+type GPUCompilationMessageType =
+    | "error"
+    | "warning"
+    | "info";
+
+interface GPUCompilationMessage {
+    readonly message: string;
+    readonly type: GPUCompilationMessageType;
+    readonly lineNum: number;
+    readonly linePos: number;
+}
+
+interface GPUCompilationInfo {
+    readonly messages: readonly GPUCompilationMessage[];
+}
+
+declare class GPUShaderModule implements GPUObjectBase {
+    private __brand: void;
+    label: string | undefined;
+
+    compilationInfo(): Promise<GPUCompilationInfo>;
+}
+
+declare class GPUSwapChain implements GPUObjectBase {
+    private __brand: void;
+    label: string | undefined;
+
+    getCurrentTexture(): GPUTexture;
+}
+
+declare class GPUTexture implements GPUObjectBase {
+    private __brand: void;
+    label: string | undefined;
+
+    createView(descriptor?: GPUTextureViewDescriptor): GPUTextureView;
+    destroy(): void;
+}
+
+declare class GPUTextureView implements GPUObjectBase {
+    private __brand: void;
+    label: string | undefined;
+}
+
+type GPUPowerPreference = "low-power" | "high-performance";
+interface GPURequestAdapterOptions {
+    powerPreference?: GPUPowerPreference;
+}
+
+declare class GPU {
+    private __brand: void;
+    requestAdapter(options?: GPURequestAdapterOptions): Promise<GPUAdapter | null>;
+}
+
+// ****************************************************************************
+// ERROR SCOPES
+// ****************************************************************************
+
+type GPUErrorFilter = "out-of-memory" | "validation";
+
+declare class GPUOutOfMemoryError {
+    private __brand: void;
+    constructor();
+}
+
+declare class GPUValidationError {
+    private __brand: void;
+    constructor(message: string);
+    readonly message: string;
+}
+
+type GPUError = GPUOutOfMemoryError | GPUValidationError;
+
+// ****************************************************************************
+// TELEMETRY
+// ****************************************************************************
+
+declare class GPUUncapturedErrorEvent extends Event {
+    private __brand: void;
+    constructor(
+        type: string,
+        gpuUncapturedErrorEventInitDict: GPUUncapturedErrorEventInit
+    );
+    readonly error: GPUError;
+}
+
+interface GPUUncapturedErrorEventInit extends EventInit {
+    error: GPUError;
+}
+
+declare class GPUDeviceLostInfo {
+    private __brand: void;
+    readonly message: string;
+}

+ 11 - 7
src/Lights/Shadows/cascadedShadowGenerator.ts

@@ -697,7 +697,7 @@ export class CascadedShadowGenerator extends ShadowGenerator {
         if (!engine) {
             return false;
         }
-        return engine.webGLVersion != 1;
+        return engine._features.supportCSM;
     }
 
     /** @hidden */
@@ -716,7 +716,7 @@ export class CascadedShadowGenerator extends ShadowGenerator {
      */
     constructor(mapSize: number, light: DirectionalLight, usefulFloatFirst?: boolean) {
         if (!CascadedShadowGenerator.IsSupported) {
-            Logger.Error("CascadedShadowMap needs WebGL 2 support.");
+            Logger.Error("CascadedShadowMap is not supported by the current engine.");
             return;
         }
 
@@ -797,17 +797,21 @@ export class CascadedShadowGenerator extends ShadowGenerator {
             }
         }
 
+        let engine = this._scene.getEngine();
+
+        this._shadowMap.onBeforeBindObservable.clear();
+        this._shadowMap.onBeforeRenderObservable.clear();
+
         this._shadowMap.onBeforeRenderObservable.add((layer: number) => {
             this._currentLayer = layer;
-            if (this._scene.getSceneUniformBuffer().useUbo) {
-                const sceneUBO = this._scene.getSceneUniformBuffer();
-                sceneUBO.updateMatrix("viewProjection", this.getCascadeTransformMatrix(layer)!);
-                sceneUBO.updateMatrix("view", this.getCascadeViewMatrix(layer)!);
-                sceneUBO.update();
+            if (this._filter === ShadowGenerator.FILTER_PCF) {
+                engine.setColorWrite(false);
             }
+            this._scene.setTransformMatrix(this.getCascadeViewMatrix(layer)!, this.getCascadeProjectionMatrix(layer)!);
         });
 
         this._shadowMap.onBeforeBindObservable.add(() => {
+            engine._debugPushGroup(`cascaded shadow map generation for ${this._nameForCustomEffect}`, 1);
             if (this._breaksAreDirty) {
                 this._splitFrustum();
             }

+ 43 - 28
src/Lights/Shadows/shadowGenerator.ts

@@ -12,7 +12,7 @@ import { IShadowLight } from "../../Lights/shadowLight";
 import { Light } from "../../Lights/light";
 import { MaterialDefines } from "../../Materials/materialDefines";
 import { MaterialHelper } from "../../Materials/materialHelper";
-import { Effect } from "../../Materials/effect";
+import { Effect, IEffectCreationOptions } from "../../Materials/effect";
 import { Texture } from "../../Materials/Textures/texture";
 import { RenderTargetTexture } from "../../Materials/Textures/renderTargetTexture";
 
@@ -143,6 +143,8 @@ export interface IShadowGenerator {
  */
 export class ShadowGenerator implements IShadowGenerator {
 
+    private static _Counter = 0;
+
     /**
      * Name of the shadow generator class
      */
@@ -416,7 +418,7 @@ export class ShadowGenerator implements IShadowGenerator {
 
         // Weblg1 fallback for PCF.
         if (value === ShadowGenerator.FILTER_PCF || value === ShadowGenerator.FILTER_PCSS) {
-            if (this._scene.getEngine().webGLVersion === 1) {
+            if (!this._scene.getEngine()._features.supportShadowSamplers) {
                 this.usePoissonSampling = true;
                 return;
             }
@@ -785,8 +787,6 @@ export class ShadowGenerator implements IShadowGenerator {
     protected _scene: Scene;
     protected _lightDirection = Vector3.Zero();
 
-    protected _effect: Effect;
-
     protected _viewMatrix = Matrix.Zero();
     protected _projectionMatrix = Matrix.Zero();
     protected _transformMatrix = Matrix.Zero();
@@ -804,6 +804,7 @@ export class ShadowGenerator implements IShadowGenerator {
     protected _textureType: number;
     protected _defaultTextureMatrix = Matrix.Identity();
     protected _storedUniqueId: Nullable<number>;
+    protected _nameForCustomEffect: string;
 
     /** @hidden */
     public static _SceneComponentInitialization: (scene: Scene) => void = (_) => {
@@ -839,6 +840,8 @@ export class ShadowGenerator implements IShadowGenerator {
         light._shadowGenerator = this;
         this.id = light.id;
 
+        this._nameForCustomEffect = Constants.CUSTOMEFFECT_PREFIX_SHADOWGENERATOR + ShadowGenerator._Counter++;
+
         ShadowGenerator._SceneComponentInitialization(this._scene);
 
         // Texture type fallback from float to int if not supported.
@@ -876,8 +879,7 @@ export class ShadowGenerator implements IShadowGenerator {
     }
 
     protected _createTargetRenderTexture(): void {
-        let engine = this._scene.getEngine();
-        if (engine.webGLVersion > 1) {
+        if (this._scene.getEngine()._features.supportDepthStencilTexture) {
             this._shadowMap = new RenderTargetTexture(this._light.name + "_shadowMap", this._mapSize, this._scene, false, true, this._textureType, this._light.needCube(), undefined, false, false);
             this._shadowMap.createDepthStencilTexture(Constants.LESS, true);
         }
@@ -915,33 +917,29 @@ export class ShadowGenerator implements IShadowGenerator {
 
         let engine = this._scene.getEngine();
 
+        this._shadowMap.onBeforeBindObservable.add(() => {
+            engine._debugPushGroup(`shadow map generation for ${this._nameForCustomEffect}`, 1);
+        });
+
         // Record Face Index before render.
         this._shadowMap.onBeforeRenderObservable.add((faceIndex: number) => {
             this._currentFaceIndex = faceIndex;
             if (this._filter === ShadowGenerator.FILTER_PCF) {
                 engine.setColorWrite(false);
             }
-            if (this._scene.getSceneUniformBuffer().useUbo) {
-                const sceneUBO = this._scene.getSceneUniformBuffer();
-                sceneUBO.updateMatrix("viewProjection", this.getTransformMatrix());
-                sceneUBO.updateMatrix("view", this._viewMatrix);
-                sceneUBO.update();
-            }
+            this.getTransformMatrix(); // generate the view/projection matrix
+            this._scene.setTransformMatrix(this._viewMatrix, this._projectionMatrix);
         });
 
         // Blur if required afer render.
         this._shadowMap.onAfterUnbindObservable.add(() => {
-            if (this._scene.getSceneUniformBuffer().useUbo) {
-                const sceneUBO = this._scene.getSceneUniformBuffer();
-                sceneUBO.updateMatrix("viewProjection", this._scene.getTransformMatrix());
-                sceneUBO.updateMatrix("view", this._scene.getViewMatrix());
-                sceneUBO.update();
-            }
+            this._scene.updateTransformMatrix(); // restore the view/projection matrices of the active camera
 
             if (this._filter === ShadowGenerator.FILTER_PCF) {
                 engine.setColorWrite(true);
             }
             if (!this.useBlurExponentialShadowMap && !this.useBlurCloseExponentialShadowMap) {
+                engine._debugPopGroup(1);
                 return;
             }
             let shadowMap = this.getShadowMapForRendering();
@@ -950,6 +948,7 @@ export class ShadowGenerator implements IShadowGenerator {
                 const texture = shadowMap.getInternalTexture()!;
                 this._scene.postProcessManager.directRender(this._blurPostProcesses, texture, true);
                 engine.unBindFramebuffer(texture, true);
+                engine._debugPopGroup(1);
             }
         });
 
@@ -988,7 +987,7 @@ export class ShadowGenerator implements IShadowGenerator {
         var targetSize = this._mapSize / this.blurScale;
 
         if (!this.useKernelBlur || this.blurScale !== 1.0) {
-            this._shadowMap2 = new RenderTargetTexture(this._light.name + "_shadowMap2", targetSize, this._scene, false, true, this._textureType);
+            this._shadowMap2 = new RenderTargetTexture(this._light.name + "_shadowMap2", targetSize, this._scene, false, true, this._textureType, undefined, undefined, false);
             this._shadowMap2.wrapU = Texture.CLAMP_ADDRESSMODE;
             this._shadowMap2.wrapV = Texture.CLAMP_ADDRESSMODE;
             this._shadowMap2.updateSamplingMode(Texture.BILINEAR_SAMPLINGMODE);
@@ -1112,7 +1111,7 @@ export class ShadowGenerator implements IShadowGenerator {
 
             const shadowDepthWrapper = material.shadowDepthWrapper;
 
-            let effect = shadowDepthWrapper?.getEffect(subMesh, this) ?? this._effect;
+            let effect = shadowDepthWrapper?.getEffect(subMesh, this) ?? subMesh._getCustomEffect(this._nameForCustomEffect, false)!.effect;
 
             engine.enableEffect(effect);
 
@@ -1337,6 +1336,10 @@ export class ShadowGenerator implements IShadowGenerator {
 
         this._prepareShadowDefines(subMesh, useInstances, defines, isTransparent);
 
+        const subMeshEffect = subMesh._getCustomEffect(this._nameForCustomEffect)!;
+
+        let { effect, defines: cachedDefines } = subMeshEffect;
+
         if (shadowDepthWrapper) {
             if (!shadowDepthWrapper.isReadyForSubMesh(subMesh, defines, this, useInstances)) {
                 return false;
@@ -1457,8 +1460,8 @@ export class ShadowGenerator implements IShadowGenerator {
 
             // Get correct effect
             var join = defines.join("\n");
-            if (this._cachedDefines !== join) {
-                this._cachedDefines = join;
+            if (cachedDefines !== join) {
+                cachedDefines = join;
 
                 let shaderName = "shadowMap";
                 let uniforms = ["world", "mBones", "viewProjection", "diffuseMatrix", "lightDataSM", "depthValuesSM", "biasAndScaleSM", "morphTargetInfluences", "boneTextureWidth",
@@ -1494,13 +1497,25 @@ export class ShadowGenerator implements IShadowGenerator {
                     }
                 }
 
-                this._effect = this._scene.getEngine().createEffect(shaderName,
-                    attribs, uniforms,
-                    samplers, join,
-                    fallbacks, undefined, undefined, { maxSimultaneousMorphTargets: morphInfluencers });
+                const engine = this._scene.getEngine();
+
+                effect = engine.createEffect(shaderName, <IEffectCreationOptions>{
+                    attributes: attribs,
+                    uniformsNames: uniforms,
+                    uniformBuffersNames: [],
+                    samplers: samplers,
+                    defines: join,
+                    fallbacks: fallbacks,
+                    onCompiled: null,
+                    onError: null,
+                    indexParameters: { maxSimultaneousMorphTargets: morphInfluencers },
+                }, engine);
             }
 
-            if (!this._effect.isReady()) {
+            subMeshEffect.effect = effect;
+            subMeshEffect.defines = cachedDefines;
+
+            if (!effect.isReady()) {
                 return false;
             }
         }
@@ -1757,7 +1772,7 @@ export class ShadowGenerator implements IShadowGenerator {
 
         serializationObject.className = this.getClassName();
         serializationObject.lightId = this._light.id;
-        serializationObject.id = this._light.id;
+        serializationObject.id = this.id;
         serializationObject.mapSize = shadowMap.getRenderSize();
         serializationObject.forceBackFacesOnly = this.forceBackFacesOnly;
         serializationObject.darkness = this.getDarkness();

+ 1 - 1
src/Lights/light.ts

@@ -345,7 +345,7 @@ export abstract class Light extends Node {
     constructor(name: string, scene: Scene) {
         super(name, scene);
         this.getScene().addLight(this);
-        this._uniformBuffer = new UniformBuffer(this.getScene().getEngine());
+        this._uniformBuffer = new UniformBuffer(this.getScene().getEngine(), undefined,  undefined, name);
         this._buildUniformLayout();
 
         this.includedOnlyMeshes = new Array<AbstractMesh>();

+ 2 - 2
src/Materials/Background/backgroundMaterial.ts

@@ -1149,9 +1149,9 @@ export class BackgroundMaterial extends PushMaterial {
             }
         }
 
-        this._uniformBuffer.update();
-
         this._afterBind(mesh, this._activeEffect);
+
+        this._uniformBuffer.update();
     }
 
     /**

+ 4 - 2
src/Materials/Node/Blocks/Dual/lightBlock.ts

@@ -16,7 +16,9 @@ import { _TypeStore } from '../../../../Misc/typeStore';
 import { Scene } from '../../../../scene';
 
 import "../../../../Shaders/ShadersInclude/lightFragmentDeclaration";
+import "../../../../Shaders/ShadersInclude/lightVxFragmentDeclaration";
 import "../../../../Shaders/ShadersInclude/lightUboDeclaration";
+import "../../../../Shaders/ShadersInclude/lightVxUboDeclaration";
 import "../../../../Shaders/ShadersInclude/lightFragment";
 import "../../../../Shaders/ShadersInclude/helperFunctions";
 import "../../../../Shaders/ShadersInclude/lightsFragmentFunctions";
@@ -210,7 +212,7 @@ export class LightBlock extends NodeMaterialBlock {
 
         // Declaration
         if (!this.light) { // Emit for all lights
-            state._emitFunctionFromInclude(state.supportUniformBuffers ? "lightUboDeclaration" : "lightFragmentDeclaration", comments, {
+            state._emitFunctionFromInclude(state.supportUniformBuffers ? "lightVxUboDeclaration" : "lightVxFragmentDeclaration", comments, {
                 repeatKey: "maxSimultaneousLights"
             });
             this._lightId = 0;
@@ -221,7 +223,7 @@ export class LightBlock extends NodeMaterialBlock {
             this._lightId = (state.counters["lightCounter"] !== undefined ? state.counters["lightCounter"] : -1) + 1;
             state.counters["lightCounter"] = this._lightId;
 
-            state._emitFunctionFromInclude(state.supportUniformBuffers ? "lightUboDeclaration" : "lightFragmentDeclaration", comments, {
+            state._emitFunctionFromInclude(state.supportUniformBuffers ? "lightVxUboDeclaration" : "lightVxFragmentDeclaration", comments, {
                 replaceStrings: [{ search: /{X}/g, replace: this._lightId.toString() }]
             }, this._lightId.toString());
         }

+ 2 - 0
src/Materials/Node/Blocks/Fragment/perturbNormalBlock.ts

@@ -175,6 +175,8 @@ export class PerturbNormalBlock extends NodeMaterialBlock {
                 { search: /vBumpInfos.y/g, replace: replaceForBumpInfos},
                 { search: /vTangentSpaceParams/g, replace: this._tangentSpaceParameterName},
                 { search: /vPositionW/g, replace: worldPosition.associatedVariableName + ".xyz"},
+                { search: /varying vec2 vBumpUV;/g, replace: ""},
+                { search: /uniform sampler2D bumpSampler;[\s\S]*?\}/g, replace: ""},
             ]
         });
 

+ 1 - 1
src/Materials/Node/Blocks/Input/inputBlock.ts

@@ -583,7 +583,7 @@ export class InputBlock extends NodeMaterialBlock {
                     effect.setMatrix(variableName, scene.getTransformMatrix());
                     break;
                 case NodeMaterialSystemValues.CameraPosition:
-                    MaterialHelper.BindEyePosition(effect, scene, variableName);
+                    MaterialHelper.BindEyePosition(effect, scene, variableName, true);
                     break;
                 case NodeMaterialSystemValues.FogColor:
                     effect.setColor3(variableName, scene.fogColor);

+ 14 - 5
src/Materials/Node/Blocks/PBR/pbrMetallicRoughnessBlock.ts

@@ -648,7 +648,9 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
         defines.setValue("SPECULARAA", this._scene.getEngine().getCaps().standardDerivatives && this.enableSpecularAntiAliasing, true);
         defines.setValue("REALTIME_FILTERING", this.realTimeFiltering, true);
 
-        if (this._scene.getEngine().webGLVersion > 1) {
+        const scene = mesh.getScene();
+
+        if (scene.getEngine()._features.needTypeSuffixInShaderConstants) {
             defines.setValue("NUM_SAMPLES", this.realTimeFilteringQuality + "u", true);
         } else {
             defines.setValue("NUM_SAMPLES", "" + this.realTimeFilteringQuality, true);
@@ -674,8 +676,6 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
             return;
         }
 
-        const scene = mesh.getScene();
-
         if (!this.light) {
             // Lights
             MaterialHelper.PrepareDefinesForLights(scene, mesh, defines, true, nodeMaterial.maxSimultaneousLights);
@@ -710,6 +710,14 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
         }
     }
 
+    public isReady() {
+        if (this._environmentBRDFTexture && !this._environmentBRDFTexture.isReady()) {
+            return false;
+        }
+
+        return true;
+    }
+
     public bind(effect: Effect, nodeMaterial: NodeMaterial, mesh?: Mesh) {
         if (!mesh) {
             return;
@@ -761,7 +769,7 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
 
         // Declaration
         if (!this.light) { // Emit for all lights
-            state._emitFunctionFromInclude(state.supportUniformBuffers ? "lightUboDeclaration" : "lightFragmentDeclaration", comments, {
+            state._emitFunctionFromInclude(state.supportUniformBuffers ? "lightVxUboDeclaration" : "lightVxFragmentDeclaration", comments, {
                 repeatKey: "maxSimultaneousLights"
             });
             this._lightId = 0;
@@ -771,7 +779,7 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
             this._lightId = (state.counters["lightCounter"] !== undefined ? state.counters["lightCounter"] : -1) + 1;
             state.counters["lightCounter"] = this._lightId;
 
-            state._emitFunctionFromInclude(state.supportUniformBuffers ? "lightUboDeclaration" : "lightFragmentDeclaration", comments, {
+            state._emitFunctionFromInclude(state.supportUniformBuffers ? "lightVxUboDeclaration" : "lightVxFragmentDeclaration", comments, {
                 replaceStrings: [{ search: /{X}/g, replace: this._lightId.toString() }]
             }, this._lightId.toString());
         }
@@ -928,6 +936,7 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
         // Fragment
         state.sharedData.bindableBlocks.push(this);
         state.sharedData.blocksWithDefines.push(this);
+        state.sharedData.blockingBlocks.push(this);
 
         let comments = `//${this.name}`;
         let worldPosVarName = "v_" + this.worldPosition.associatedVariableName;

+ 57 - 45
src/Materials/PBR/pbrBaseMaterial.ts

@@ -4,7 +4,6 @@ import { Logger } from "../../Misc/logger";
 import { SmartArray } from "../../Misc/smartArray";
 import { BRDFTextureTools } from "../../Misc/brdfTextureTools";
 import { Nullable } from "../../types";
-import { Camera } from "../../Cameras/camera";
 import { Scene } from "../../scene";
 import { Matrix, Vector4 } from "../../Maths/math.vector";
 import { VertexBuffer } from "../../Meshes/buffer";
@@ -1295,7 +1294,7 @@ export abstract class PBRBaseMaterial extends PushMaterial {
             "reflectionSampler", "reflectionSamplerLow", "reflectionSamplerHigh", "irradianceSampler",
             "microSurfaceSampler", "environmentBrdfSampler", "boneSampler", "metallicReflectanceSampler"];
 
-        var uniformBuffers = ["Material", "Scene"];
+        var uniformBuffers = ["Material", "Scene", "Mesh"];
 
         DetailMapConfiguration.AddUniforms(uniforms);
         DetailMapConfiguration.AddSamplers(samplers);
@@ -1405,7 +1404,7 @@ export abstract class PBRBaseMaterial extends PushMaterial {
 
                     if (this.realTimeFiltering && this.realTimeFilteringQuality > 0) {
                         defines.NUM_SAMPLES = "" + this.realTimeFilteringQuality;
-                        if (engine.webGLVersion > 1) {
+                        if (engine._features.needTypeSuffixInShaderConstants) {
                             defines.NUM_SAMPLES = defines.NUM_SAMPLES + "u";
                         }
 
@@ -1569,7 +1568,6 @@ export abstract class PBRBaseMaterial extends PushMaterial {
 
                 if (this._environmentBRDFTexture && MaterialFlags.ReflectionTextureEnabled) {
                     defines.ENVIRONMENTBRDF = true;
-                    // Not actual true RGBD, only the B chanel is encoded as RGBD for sheen.
                     defines.ENVIRONMENTBRDF_RGBD = this._environmentBRDFTexture.isRGBD;
                 } else {
                     defines.ENVIRONMENTBRDF = false;
@@ -1717,7 +1715,10 @@ export abstract class PBRBaseMaterial extends PushMaterial {
         ubo.addUniform("pointSize", 1);
         ubo.addUniform("vReflectivityColor", 4);
         ubo.addUniform("vEmissiveColor", 3);
-        ubo.addUniform("visibility", 1);
+        ubo.addUniform("vAmbientColor", 3);
+
+        ubo.addUniform("vDebugMode", 2);
+
         ubo.addUniform("vMetallicReflectanceFactors", 4);
         ubo.addUniform("vMetallicReflectanceInfos", 2);
         ubo.addUniform("metallicReflectanceMatrix", 16);
@@ -1728,6 +1729,26 @@ export abstract class PBRBaseMaterial extends PushMaterial {
         PBRSubSurfaceConfiguration.PrepareUniformBuffer(ubo);
         DetailMapConfiguration.PrepareUniformBuffer(ubo);
 
+        ubo.addUniform("vSphericalL00", 3);
+        ubo.addUniform("vSphericalL1_1", 3);
+        ubo.addUniform("vSphericalL10", 3);
+        ubo.addUniform("vSphericalL11", 3);
+        ubo.addUniform("vSphericalL2_2", 3);
+        ubo.addUniform("vSphericalL2_1", 3);
+        ubo.addUniform("vSphericalL20", 3);
+        ubo.addUniform("vSphericalL21", 3);
+        ubo.addUniform("vSphericalL22", 3);
+
+        ubo.addUniform("vSphericalX", 3);
+        ubo.addUniform("vSphericalY", 3);
+        ubo.addUniform("vSphericalZ", 3);
+        ubo.addUniform("vSphericalXX_ZZ", 3);
+        ubo.addUniform("vSphericalYY_ZZ", 3);
+        ubo.addUniform("vSphericalZZ", 3);
+        ubo.addUniform("vSphericalXY", 3);
+        ubo.addUniform("vSphericalYZ", 3);
+        ubo.addUniform("vSphericalZX", 3);
+
         ubo.create();
     }
 
@@ -1776,10 +1797,9 @@ export abstract class PBRBaseMaterial extends PushMaterial {
 
         this._activeEffect = effect;
 
-        // Matrices
-        if (!defines.INSTANCES || defines.THIN_INSTANCES) {
-            this.bindOnlyWorldMatrix(world);
-        }
+        // Matrices Mesh.
+        mesh.getMeshUniformBuffer().bindToEffect(effect, "Mesh");
+        mesh.transferToEffect(world);
 
         // PrePass
         this.prePassConfiguration.bindForSubMesh(this._activeEffect, scene, mesh, world, this.isFrozen);
@@ -1844,30 +1864,30 @@ export abstract class PBRBaseMaterial extends PushMaterial {
                             if (defines.USESPHERICALFROMREFLECTIONMAP && polynomials) {
                                 if (defines.SPHERICAL_HARMONICS) {
                                     const preScaledHarmonics = polynomials.preScaledHarmonics;
-                                    this._activeEffect.setVector3("vSphericalL00", preScaledHarmonics.l00);
-                                    this._activeEffect.setVector3("vSphericalL1_1", preScaledHarmonics.l1_1);
-                                    this._activeEffect.setVector3("vSphericalL10", preScaledHarmonics.l10);
-                                    this._activeEffect.setVector3("vSphericalL11", preScaledHarmonics.l11);
-                                    this._activeEffect.setVector3("vSphericalL2_2", preScaledHarmonics.l2_2);
-                                    this._activeEffect.setVector3("vSphericalL2_1", preScaledHarmonics.l2_1);
-                                    this._activeEffect.setVector3("vSphericalL20", preScaledHarmonics.l20);
-                                    this._activeEffect.setVector3("vSphericalL21", preScaledHarmonics.l21);
-                                    this._activeEffect.setVector3("vSphericalL22", preScaledHarmonics.l22);
+                                    ubo.updateVector3("vSphericalL00", preScaledHarmonics.l00);
+                                    ubo.updateVector3("vSphericalL1_1", preScaledHarmonics.l1_1);
+                                    ubo.updateVector3("vSphericalL10", preScaledHarmonics.l10);
+                                    ubo.updateVector3("vSphericalL11", preScaledHarmonics.l11);
+                                    ubo.updateVector3("vSphericalL2_2", preScaledHarmonics.l2_2);
+                                    ubo.updateVector3("vSphericalL2_1", preScaledHarmonics.l2_1);
+                                    ubo.updateVector3("vSphericalL20", preScaledHarmonics.l20);
+                                    ubo.updateVector3("vSphericalL21", preScaledHarmonics.l21);
+                                    ubo.updateVector3("vSphericalL22", preScaledHarmonics.l22);
                                 }
                                 else {
-                                    this._activeEffect.setFloat3("vSphericalX", polynomials.x.x, polynomials.x.y, polynomials.x.z);
-                                    this._activeEffect.setFloat3("vSphericalY", polynomials.y.x, polynomials.y.y, polynomials.y.z);
-                                    this._activeEffect.setFloat3("vSphericalZ", polynomials.z.x, polynomials.z.y, polynomials.z.z);
-                                    this._activeEffect.setFloat3("vSphericalXX_ZZ", polynomials.xx.x - polynomials.zz.x,
+                                    ubo.updateFloat3("vSphericalX", polynomials.x.x, polynomials.x.y, polynomials.x.z);
+                                    ubo.updateFloat3("vSphericalY", polynomials.y.x, polynomials.y.y, polynomials.y.z);
+                                    ubo.updateFloat3("vSphericalZ", polynomials.z.x, polynomials.z.y, polynomials.z.z);
+                                    ubo.updateFloat3("vSphericalXX_ZZ", polynomials.xx.x - polynomials.zz.x,
                                         polynomials.xx.y - polynomials.zz.y,
                                         polynomials.xx.z - polynomials.zz.z);
-                                    this._activeEffect.setFloat3("vSphericalYY_ZZ", polynomials.yy.x - polynomials.zz.x,
+                                    ubo.updateFloat3("vSphericalYY_ZZ", polynomials.yy.x - polynomials.zz.x,
                                         polynomials.yy.y - polynomials.zz.y,
                                         polynomials.yy.z - polynomials.zz.z);
-                                    this._activeEffect.setFloat3("vSphericalZZ", polynomials.zz.x, polynomials.zz.y, polynomials.zz.z);
-                                    this._activeEffect.setFloat3("vSphericalXY", polynomials.xy.x, polynomials.xy.y, polynomials.xy.z);
-                                    this._activeEffect.setFloat3("vSphericalYZ", polynomials.yz.x, polynomials.yz.y, polynomials.yz.z);
-                                    this._activeEffect.setFloat3("vSphericalZX", polynomials.zx.x, polynomials.zx.y, polynomials.zx.z);
+                                    ubo.updateFloat3("vSphericalZZ", polynomials.zz.x, polynomials.zz.y, polynomials.zz.z);
+                                    ubo.updateFloat3("vSphericalXY", polynomials.xy.x, polynomials.xy.y, polynomials.xy.z);
+                                    ubo.updateFloat3("vSphericalYZ", polynomials.yz.x, polynomials.yz.y, polynomials.yz.z);
+                                    ubo.updateFloat3("vSphericalZX", polynomials.zx.x, polynomials.zx.y, polynomials.zx.z);
                                 }
                             }
                         }
@@ -1966,10 +1986,14 @@ export abstract class PBRBaseMaterial extends PushMaterial {
                 this._lightingInfos.w = this._specularIntensity;
 
                 ubo.updateVector4("vLightingIntensity", this._lightingInfos);
-            }
 
-            // Visibility
-            ubo.updateFloat("visibility", mesh.visibility);
+                // Colors
+                scene.ambientColor.multiplyToRef(this._ambientColor, this._globalAmbientColor);
+
+                ubo.updateColor3("vAmbientColor", this._globalAmbientColor);
+
+                ubo.updateFloat2("vDebugMode", this.debugLimit, this.debugFactor);
+            }
 
             // Textures
             if (scene.texturesEnabled) {
@@ -2043,19 +2067,7 @@ export abstract class PBRBaseMaterial extends PushMaterial {
             // Clip plane
             MaterialHelper.BindClipPlane(this._activeEffect, scene);
 
-            // Colors
-            scene.ambientColor.multiplyToRef(this._ambientColor, this._globalAmbientColor);
-
-            var eyePosition = scene._forcedViewPosition ? scene._forcedViewPosition : (scene._mirroredCameraPosition ? scene._mirroredCameraPosition : (<Camera>scene.activeCamera).globalPosition);
-            var invertNormal = (scene.useRightHandedSystem === (scene._mirroredCameraPosition != null));
-            effect.setFloat4("vEyePosition",
-                eyePosition.x,
-                eyePosition.y,
-                eyePosition.z,
-                invertNormal ? -1 : 1);
-            effect.setColor3("vAmbientColor", this._globalAmbientColor);
-
-            effect.setFloat2("vDebugMode", this.debugLimit, this.debugFactor);
+            this.bindEyePosition(effect);
         }
 
         if (mustRebind || !this.isFrozen) {
@@ -2084,9 +2096,9 @@ export abstract class PBRBaseMaterial extends PushMaterial {
             MaterialHelper.BindLogDepth(defines, this._activeEffect, scene);
         }
 
-        ubo.update();
-
         this._afterBind(mesh, this._activeEffect);
+
+        ubo.update();
     }
 
     /**

+ 3 - 2
src/Materials/Textures/Filtering/hdrFiltering.ts

@@ -75,6 +75,7 @@ export class HDRFiltering {
         const texture = this._engine.createRenderTargetCubeTexture(size, {
             format: Constants.TEXTUREFORMAT_RGBA,
             type: textureType,
+            createMipMaps: true,
             generateMipMaps: false,
             generateDepthBuffer: false,
             generateStencilBuffer: false,
@@ -92,7 +93,7 @@ export class HDRFiltering {
 
     private _prefilterInternal(texture: BaseTexture): BaseTexture {
         const width = texture.getSize().width;
-        const mipmapsCount = Math.round(Scalar.Log2(width)) + 1;
+        const mipmapsCount = Scalar.ILog2(width) + 1;
 
         const effect = this._effectWrapper.effect;
         const outputTexture = this._createRenderTarget(width);
@@ -196,7 +197,7 @@ export class HDRFiltering {
       * @return Promise called when prefiltering is done
       */
     public prefilter(texture: BaseTexture, onFinished: Nullable<() => void> = null): Promise<void> {
-        if (this._engine.webGLVersion === 1) {
+        if (!this._engine._features.allowTexturePrefiltering) {
             Logger.Warn("HDR prefiltering is not available in WebGL 1., you can use real time filtering instead.");
             return Promise.reject("HDR prefiltering is not available in WebGL 1., you can use real time filtering instead.");
         }

+ 42 - 26
src/Materials/Textures/Procedurals/proceduralTexture.ts

@@ -77,6 +77,7 @@ export class ProceduralTexture extends Texture {
 
     @serialize()
     private _size: RenderTargetTextureSize;
+    private _textureType: number;
     private _currentRefreshId = -1;
     private _frameId = -1;
     private _refreshRate = 1;
@@ -98,10 +99,10 @@ export class ProceduralTexture extends Texture {
     private _fallbackTextureUsed = false;
     private _fullEngine: Engine;
 
-    private _cachedDefines = "";
+    private _cachedDefines: Nullable<string> = null;
 
     private _contentUpdateId = -1;
-    private _contentData: Nullable<ArrayBufferView>;
+    private _contentData: Nullable<Promise<ArrayBufferView>>;
 
     /**
      * Instantiates a new procedural texture.
@@ -133,6 +134,7 @@ export class ProceduralTexture extends Texture {
         this.name = name;
         this.isRenderTarget = true;
         this._size = size;
+        this._textureType = textureType;
         this._generateMipMaps = generateMipMaps;
 
         this.setFragment(fragment);
@@ -169,15 +171,22 @@ export class ProceduralTexture extends Texture {
 
     /**
      * Gets texture content (Use this function wisely as reading from a texture can be slow)
-     * @returns an ArrayBufferView (Uint8Array or Float32Array)
+     * @returns an ArrayBufferView promise (Uint8Array or Float32Array)
      */
-    public getContent(): Nullable<ArrayBufferView> {
+    public getContent(): Nullable<Promise<ArrayBufferView>> {
         if (this._contentData && this._frameId === this._contentUpdateId) {
             return this._contentData;
         }
 
-        this._contentData = this.readPixels(0, 0, this._contentData);
-        this._contentUpdateId = this._frameId;
+        if (this._contentData) {
+            this._contentData.then((buffer) => {
+                this._contentData = this.readPixels(0, 0, buffer);
+                this._contentUpdateId = this._frameId;
+            });
+        } else {
+            this._contentData = this.readPixels(0, 0);
+            this._contentUpdateId = this._frameId;
+        }
 
         return this._contentData;
     }
@@ -260,25 +269,28 @@ export class ProceduralTexture extends Texture {
             shaders = { vertex: "procedural", fragment: this._fragment };
         }
 
-        this._cachedDefines = defines;
+        if (this._cachedDefines !== defines) {
+            this._cachedDefines = defines;
 
-        this._effect = engine.createEffect(shaders,
-            [VertexBuffer.PositionKind],
-            this._uniforms,
-            this._samplers,
-            defines, undefined, undefined, () => {
-                this.releaseInternalTexture();
+            this._effect = engine.createEffect(shaders,
+                [VertexBuffer.PositionKind],
+                this._uniforms,
+                this._samplers,
+                defines, undefined, undefined, () => {
+                    this.releaseInternalTexture();
 
-                if (this._fallbackTexture) {
-                    this._texture = this._fallbackTexture._texture;
+                    if (this._fallbackTexture) {
+                        this._texture = this._fallbackTexture._texture;
 
-                    if (this._texture) {
-                        this._texture.incrementReferences();
+                        if (this._texture) {
+                            this._texture.incrementReferences();
+                        }
                     }
-                }
 
-                this._fallbackTextureUsed = true;
-            });
+                    this._fallbackTextureUsed = true;
+                }
+            );
+        }
 
         return this._effect.isReady();
     }
@@ -361,7 +373,7 @@ export class ProceduralTexture extends Texture {
         }
 
         this.releaseInternalTexture();
-        this._texture = this._fullEngine.createRenderTargetTexture(size, generateMipMaps);
+        this._texture = this._fullEngine.createRenderTargetTexture(size, { generateMipMaps, generateDepthBuffer: false, generateStencilBuffer: false, type: this._textureType });
 
         // Update properties
         this._size = size;
@@ -563,6 +575,8 @@ export class ProceduralTexture extends Texture {
             return;
         }
 
+        engine._debugPushGroup(`procedural texture generation for ${this.name}`, 1);
+
         if (this.isCube) {
             for (var face = 0; face < 6; face++) {
                 engine.bindFramebuffer(this._texture, face, undefined, undefined, true);
@@ -579,11 +593,6 @@ export class ProceduralTexture extends Texture {
 
                 // Draw order
                 engine.drawElementsType(Material.TriangleFillMode, 0, 6);
-
-                // Mipmaps
-                if (face === 5) {
-                    engine.generateMipMapsForCubemap(this._texture);
-                }
             }
         } else {
             engine.bindFramebuffer(this._texture, 0, undefined, undefined, true);
@@ -603,6 +612,13 @@ export class ProceduralTexture extends Texture {
         // Unbind
         engine.unBindFramebuffer(this._texture, this.isCube);
 
+        // Mipmaps
+        if (this.isCube) {
+            engine.generateMipMapsForCubemap(this._texture);
+        }
+
+        engine._debugPopGroup(1);
+
         if (this.onGenerated) {
             this.onGenerated();
         }

+ 14 - 4
src/Materials/Textures/baseTexture.polynomial.ts

@@ -17,14 +17,24 @@ declare module "./baseTexture" {
 Object.defineProperty(BaseTexture.prototype, "sphericalPolynomial", {
     get: function(this: BaseTexture) {
         if (this._texture) {
-            if (this._texture._sphericalPolynomial) {
+            if (this._texture._sphericalPolynomial || this._texture._sphericalPolynomialComputed) {
                 return this._texture._sphericalPolynomial;
             }
 
             if (this._texture.isReady) {
-                this._texture._sphericalPolynomial =
-                    CubeMapToSphericalPolynomialTools.ConvertCubeMapTextureToSphericalPolynomial(this);
-                return this._texture._sphericalPolynomial;
+                if (!this._texture._sphericalPolynomialPromise) {
+                    this._texture._sphericalPolynomialPromise = CubeMapToSphericalPolynomialTools.ConvertCubeMapTextureToSphericalPolynomial(this);
+                    if (this._texture._sphericalPolynomialPromise === null) {
+                        this._texture._sphericalPolynomialComputed = true;
+                    } else {
+                        this._texture._sphericalPolynomialPromise.then((sphericalPolynomial) => {
+                            this._texture!._sphericalPolynomial = sphericalPolynomial;
+                            this._texture!._sphericalPolynomialComputed = true;
+                        });
+                    }
+                }
+
+                return null;
             }
         }
 

+ 5 - 4
src/Materials/Textures/baseTexture.ts

@@ -610,9 +610,10 @@ export class BaseTexture extends ThinTexture implements IAnimatable {
      * @param faceIndex defines the face of the texture to read (in case of cube texture)
      * @param level defines the LOD level of the texture to read (in case of Mip Maps)
      * @param buffer defines a user defined buffer to fill with data (can be null)
-     * @returns The Array buffer containing the pixels data.
+     * @param flushRenderer true to flush the renderer from the pending commands before reading the pixels
+     * @returns The Array buffer promise containing the pixels data.
      */
-    public readPixels(faceIndex = 0, level = 0, buffer: Nullable<ArrayBufferView> = null): Nullable<ArrayBufferView> {
+    public readPixels(faceIndex = 0, level = 0, buffer: Nullable<ArrayBufferView> = null, flushRenderer = true): Nullable<Promise<ArrayBufferView>> {
         if (!this._texture) {
             return null;
         }
@@ -636,10 +637,10 @@ export class BaseTexture extends ThinTexture implements IAnimatable {
 
         try {
             if (this._texture.isCube) {
-                return engine._readTexturePixels(this._texture, width, height, faceIndex, level, buffer);
+                return engine._readTexturePixels(this._texture, width, height, faceIndex, level, buffer, flushRenderer);
             }
 
-            return engine._readTexturePixels(this._texture, width, height, -1, level, buffer);
+            return engine._readTexturePixels(this._texture, width, height, -1, level, buffer, flushRenderer);
         } catch (e) {
             return null;
         }

+ 2 - 2
src/Materials/Textures/colorGradingTexture.ts

@@ -94,7 +94,7 @@ export class ColorGradingTexture extends BaseTexture {
     private load3dlTexture() {
         var engine = this._getEngine()!;
         var texture: InternalTexture;
-        if (engine.webGLVersion === 1) {
+        if (!engine._features.support3DTextures) {
             texture = engine.createRawTexture(null, 1, 1, Constants.TEXTUREFORMAT_RGBA, false, false, Constants.TEXTURE_BILINEAR_SAMPLINGMODE, null, Constants.TEXTURETYPE_UNSIGNED_INT);
         }
         else {
@@ -105,7 +105,7 @@ export class ColorGradingTexture extends BaseTexture {
         this._texture.isReady = false;
 
         this.isCube = false;
-        this.is3D = engine.webGLVersion > 1;
+        this.is3D = engine._features.support3DTextures;
         this.wrapU = Constants.TEXTURE_CLAMP_ADDRESSMODE;
         this.wrapV = Constants.TEXTURE_CLAMP_ADDRESSMODE;
         this.wrapR = Constants.TEXTURE_CLAMP_ADDRESSMODE;

+ 10 - 0
src/Materials/Textures/hardwareTextureWrapper.ts

@@ -0,0 +1,10 @@
+/** @hidden */
+export interface HardwareTextureWrapper {
+
+    underlyingResource: any;
+
+    set(hardwareTexture: any): void;
+    setUsage(textureSource: number, generateMipMaps: boolean, isCube: boolean, width: number, height: number): void;
+    reset(): void;
+    release(): void;
+}

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

@@ -237,7 +237,7 @@ export class HDRCubeTexture extends BaseTexture {
             return results;
         };
 
-        if (this._getEngine()!.webGLVersion >= 2 && this._prefilterOnLoad) {
+        if (engine._features.allowTexturePrefiltering && this._prefilterOnLoad) {
             const previousOnLoad = this._onLoad;
             const hdrFiltering = new HDRFiltering(engine);
             this._onLoad = () => {

+ 27 - 9
src/Materials/Textures/internalTexture.ts

@@ -4,6 +4,7 @@ import { RenderTargetCreationOptions } from "../../Materials/Textures/renderTarg
 import { Constants } from "../../Engines/constants";
 import { _DevTools } from '../../Misc/devTools';
 import { Engine } from '../../Engines/engine';
+import { HardwareTextureWrapper } from "./hardwareTextureWrapper";
 
 declare type ThinEngine = import("../../Engines/thinEngine").ThinEngine;
 declare type BaseTexture = import("../../Materials/Textures/baseTexture").BaseTexture;
@@ -221,6 +222,10 @@ export class InternalTexture {
     /** @hidden */
     public _sphericalPolynomial: Nullable<SphericalPolynomial> = null;
     /** @hidden */
+    public _sphericalPolynomialPromise: Nullable<Promise<SphericalPolynomial>> = null;
+    /** @hidden */
+    public _sphericalPolynomialComputed = false;
+    /** @hidden */
     public _lodGenerationScale: number = 0;
     /** @hidden */
     public _lodGenerationOffset: number = 0;
@@ -251,7 +256,8 @@ export class InternalTexture {
     public _irradianceTexture: Nullable<BaseTexture> = null;
 
     /** @hidden */
-    public _webGLTexture: Nullable<WebGLTexture> = null;
+    public _hardwareTexture: Nullable<HardwareTextureWrapper> = null;
+
     /** @hidden */
     public _references: number = 1;
 
@@ -261,6 +267,14 @@ export class InternalTexture {
     public _hasAlpha: Nullable<boolean> = null;
 
     private _engine: ThinEngine;
+    private _uniqueId: number;
+
+    private static _Counter = 0;
+
+    /** Gets the unique id of the internal texture */
+    public get uniqueId() {
+        return this._uniqueId;
+    }
 
     /**
      * Gets the Engine the texture belongs to.
@@ -286,9 +300,10 @@ export class InternalTexture {
     constructor(engine: ThinEngine, source: InternalTextureSource, delayAllocation = false) {
         this._engine = engine;
         this._source = source;
+        this._uniqueId = InternalTexture._Counter++;
 
         if (!delayAllocation) {
-            this._webGLTexture = engine._createTexture();
+            this._hardwareTexture = engine._createHardwareTexture();
         }
     }
 
@@ -306,6 +321,8 @@ export class InternalTexture {
      * @param depth defines the new depth (1 by default)
      */
     public updateSize(width: int, height: int, depth: int = 1): void {
+        this._engine.updateTextureDimensions(this, width, height, depth);
+
         this.width = width;
         this.height = height;
         this.depth = depth;
@@ -397,7 +414,8 @@ export class InternalTexture {
                     bilinearFiltering: this.samplingMode !== Constants.TEXTURE_BILINEAR_SAMPLINGMODE,
                     comparisonFunction: this._comparisonFunction,
                     generateStencil: this._generateStencilBuffer,
-                    isCube: this.isCube
+                    isCube: this.isCube,
+                    samples: this.samples
                 };
 
                 let size = {
@@ -447,7 +465,11 @@ export class InternalTexture {
 
     /** @hidden */
     public _swapAndDie(target: InternalTexture): void {
-        target._webGLTexture = this._webGLTexture;
+        // TODO what about refcount on target?
+
+        this._hardwareTexture?.setUsage(target._source, this.generateMipMaps, this.isCube, this.width, this.height);
+
+        target._hardwareTexture = this._hardwareTexture;
         target._isRGBD = this._isRGBD;
 
         if (this._framebuffer) {
@@ -504,14 +526,10 @@ export class InternalTexture {
      * Dispose the current allocated resources
      */
     public dispose(): void {
-        if (!this._webGLTexture) {
-            return;
-        }
-
         this._references--;
         if (this._references === 0) {
             this._engine._releaseTexture(this);
-            this._webGLTexture = null;
+            this._hardwareTexture = null;
         }
     }
 }

+ 10 - 0
src/Materials/Textures/mirrorTexture.ts

@@ -156,6 +156,16 @@ export class MirrorTexture extends RenderTargetTexture {
             this._updateGammaSpace;
         });
 
+        const engine = this.getScene()!.getEngine();
+
+        this.onBeforeBindObservable.add(() => {
+            engine._debugPushGroup(`mirror generation for ${name}`, 1);
+        });
+
+        this.onAfterUnbindObservable.add(() => {
+            engine._debugPopGroup(1);
+        });
+
         this.onBeforeRenderObservable.add(() => {
             Matrix.ReflectionToRef(this.mirrorPlane, this._mirrorMatrix);
             this._savedViewMatrix = scene.getViewMatrix();

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

@@ -66,7 +66,7 @@ export class MultiRenderTarget extends RenderTargetTexture {
      * Get if draw buffers are currently supported by the used hardware and browser.
      */
     public get isSupported(): boolean {
-        return this._getEngine()!.webGLVersion > 1 || this._getEngine()!.getCaps().drawBuffersExtension;
+        return this._engine?.getCaps().drawBuffersExtension ?? false;
     }
 
     /**

+ 7 - 1
src/Materials/Textures/renderTargetCreationOptions.ts

@@ -4,7 +4,11 @@
  */
 export class RenderTargetCreationOptions {
     /**
-     * Specifies is mipmaps must be generated
+     * Specifies if mipmaps must be created. If undefined, the value from generateMipMaps is taken instead
+     */
+    createMipMaps?: boolean;
+    /**
+     * Specifies if mipmaps must be generated
      */
     generateMipMaps?: boolean;
     /** Specifies whether or not a depth should be allocated in the texture (true by default) */
@@ -17,4 +21,6 @@ export class RenderTargetCreationOptions {
     samplingMode?: number;
     /** Defines format (RGBA by default) */
     format?: number;
+    /** Defines sample count (1 by default) */
+    samples?: number;
 }

+ 41 - 20
src/Materials/Textures/renderTargetTexture.ts

@@ -298,9 +298,10 @@ export class RenderTargetTexture extends Texture {
      * @param isMulti True if multiple textures need to be created (Draw Buffers)
      * @param format The internal format of the buffer in the RTT (RED, RG, RGB, RGBA, ALPHA...)
      * @param delayAllocation if the texture allocation should be delayed (default: false)
+     * @param samples sample count to use when creating the RTT
      */
-    constructor(name: string, size: number | { width: number, height: number, layers?: number } | { ratio: number }, scene: Nullable<Scene>, generateMipMaps?: boolean, doNotChangeAspectRatio: boolean = true, type: number = Constants.TEXTURETYPE_UNSIGNED_INT, isCube = false, samplingMode = Texture.TRILINEAR_SAMPLINGMODE, generateDepthBuffer = true, generateStencilBuffer = false, isMulti = false, format = Constants.TEXTUREFORMAT_RGBA, delayAllocation = false) {
-        super(null, scene, !generateMipMaps);
+    constructor(name: string, size: number | { width: number, height: number, layers?: number } | { ratio: number }, scene: Nullable<Scene>, generateMipMaps?: boolean, doNotChangeAspectRatio: boolean = true, type: number = Constants.TEXTURETYPE_UNSIGNED_INT, isCube = false, samplingMode = Texture.TRILINEAR_SAMPLINGMODE, generateDepthBuffer = true, generateStencilBuffer = false, isMulti = false, format = Constants.TEXTUREFORMAT_RGBA, delayAllocation = false, samples?: number) {
+        super(null, scene, !generateMipMaps, undefined, samplingMode, undefined, undefined, undefined, undefined, format);
         scene = this.getScene();
         if (!scene) {
             return;
@@ -331,13 +332,14 @@ export class RenderTargetTexture extends Texture {
         this._renderTargetOptions = {
             generateMipMaps: generateMipMaps,
             type: type,
-            format: format,
-            samplingMode: samplingMode,
+            format: this._format ?? undefined,
+            samplingMode: this.samplingMode,
             generateDepthBuffer: generateDepthBuffer,
-            generateStencilBuffer: generateStencilBuffer
+            generateStencilBuffer: generateStencilBuffer,
+            samples: samples,
         };
 
-        if (samplingMode === Texture.NEAREST_SAMPLINGMODE) {
+        if (this.samplingMode === Texture.NEAREST_SAMPLINGMODE) {
             this.wrapU = Texture.CLAMP_ADDRESSMODE;
             this.wrapV = Texture.CLAMP_ADDRESSMODE;
         }
@@ -350,6 +352,9 @@ export class RenderTargetTexture extends Texture {
             } else {
                 this._texture = scene.getEngine().createRenderTargetTexture(this._size, this._renderTargetOptions);
             }
+            if (samples !== undefined) {
+                this.samples = samples;
+            }
         }
     }
 
@@ -359,19 +364,23 @@ export class RenderTargetTexture extends Texture {
      * @param comparisonFunction Specifies the comparison function to set on the texture. If 0 or undefined, the texture is not in comparison mode
      * @param bilinearFiltering Specifies whether or not bilinear filtering is enable on the texture
      * @param generateStencil Specifies whether or not a stencil should be allocated in the texture
+     * @param samples sample count of the depth/stencil texture
      */
-    public createDepthStencilTexture(comparisonFunction: number = 0, bilinearFiltering: boolean = true, generateStencil: boolean = false): void {
+    public createDepthStencilTexture(comparisonFunction: number = 0, bilinearFiltering: boolean = true, generateStencil: boolean = false, samples: number = 1): void {
         const internalTexture = this.getInternalTexture();
         if (!this.getScene() || !internalTexture) {
             return;
         }
 
+        internalTexture._depthStencilTexture?.dispose();
+
         var engine = this.getScene()!.getEngine();
         internalTexture._depthStencilTexture = engine.createDepthStencilTexture(this._size, {
             bilinearFiltering,
             comparisonFunction,
             generateStencil,
-            isCube: this.isCube
+            isCube: this.isCube,
+            samples
         });
     }
 
@@ -605,6 +614,10 @@ export class RenderTargetTexture extends Texture {
             this._texture = scene.getEngine().createRenderTargetTexture(this._size, this._renderTargetOptions);
         }
 
+        if (this._renderTargetOptions.samples !== undefined) {
+            this.samples = this._renderTargetOptions.samples;
+        }
+
         if (this.onResizeObservable.hasObservers()) {
             this.onResizeObservable.notifyObservers(this);
         }
@@ -848,6 +861,8 @@ export class RenderTargetTexture extends Texture {
             return;
         }
 
+        engine._debugPushGroup(`render to face #${faceIndex} layer #${layer}`, 1);
+
         // Bind
         if (this._postProcessManager) {
             this._postProcessManager._prepareFrame(this._texture, this._postProcesses);
@@ -909,6 +924,12 @@ export class RenderTargetTexture extends Texture {
             step.action(this);
         }
 
+        const saveGenerateMipMaps = this._texture.generateMipMaps;
+
+        this._texture.generateMipMaps = false;  // if left true, the mipmaps will be generated (if this._texture.generateMipMaps = true) when the first post process binds its own RTT: by doing so it will unbind the current RTT,
+                                                // which will trigger a mipmap generation. We don't want this because it's a wasted work, we will do an unbind of the current RTT at the end of the process (see unbindFrameBuffer) which will
+                                                // trigger the generation of the final mipmaps
+
         if (this._postProcessManager) {
             this._postProcessManager._finalizeFrame(false, this._texture, faceIndex, this._postProcesses, this.ignoreCameraViewport);
         }
@@ -916,6 +937,8 @@ export class RenderTargetTexture extends Texture {
             scene.postProcessManager._finalizeFrame(false, this._texture, faceIndex);
         }
 
+        this._texture.generateMipMaps = saveGenerateMipMaps;
+
         if (!this._doNotChangeAspectRatio) {
             scene.updateTransformMatrix(true);
         }
@@ -926,19 +949,13 @@ export class RenderTargetTexture extends Texture {
         }
 
         // Unbind
-        if (!this.isCube || faceIndex === 5) {
-            if (this.isCube) {
+        this.unbindFrameBuffer(engine, faceIndex);
 
-                if (faceIndex === 5) {
-                    engine.generateMipMapsForCubemap(this._texture);
-                }
-            }
-
-            this.unbindFrameBuffer(engine, faceIndex);
-
-        } else {
-            this.onAfterRenderObservable.notifyObservers(faceIndex);
+        if (this.isCube && faceIndex === 5) {
+            engine.generateMipMapsForCubemap(this._texture);
         }
+
+        engine._debugPopGroup(1);
     }
 
     /**
@@ -988,7 +1005,11 @@ export class RenderTargetTexture extends Texture {
             this.isCube,
             this._renderTargetOptions.samplingMode,
             this._renderTargetOptions.generateDepthBuffer,
-            this._renderTargetOptions.generateStencilBuffer
+            this._renderTargetOptions.generateStencilBuffer,
+            undefined,
+            this._renderTargetOptions.format,
+            undefined,
+            this._renderTargetOptions.samples
         );
 
         // Base texture

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

@@ -681,7 +681,7 @@ export class Texture extends BaseTexture {
             } else if (this.url && StringTools.StartsWith(this.url, "data:") && this._buffer instanceof Uint8Array) {
                 serializationObject.base64String = "data:image/png;base64," + StringTools.EncodeArrayBufferToBase64(this._buffer);
             } else if (Texture.ForceSerializeBuffers) {
-                serializationObject.base64String = CopyTools.GenerateBase64StringFromTexture(this);
+                serializationObject.base64String = CopyTools.GenerateBase64StringFromTexture(this); // TODO WEBGPU serialize should turn asynchronous as GenerateBase64StringFromTexture now returns a promise...
             }
         }
 

+ 1 - 4
src/Materials/Textures/videoTexture.ts

@@ -216,7 +216,6 @@ export class VideoTexture extends Texture {
             this.video.onplaying = () => {
                 this.video.muted = oldMuted;
                 this.video.onplaying = oldHandler;
-                this._texture!.isReady = true;
                 this._updateInternalTexture();
                 if (!error) {
                     this.video.pause();
@@ -240,7 +239,6 @@ export class VideoTexture extends Texture {
             }
             else {
                 this.video.onplaying = oldHandler;
-                this._texture.isReady = true;
                 this._updateInternalTexture();
                 if (this.onLoadObservable.hasObservers()) {
                     this.onLoadObservable.notifyObservers(this);
@@ -248,7 +246,6 @@ export class VideoTexture extends Texture {
             }
         }
         else {
-            this._texture.isReady = true;
             this._updateInternalTexture();
             if (this.onLoadObservable.hasObservers()) {
                 this.onLoadObservable.notifyObservers(this);
@@ -302,7 +299,7 @@ export class VideoTexture extends Texture {
     }
 
     protected _updateInternalTexture = (): void => {
-        if (this._texture == null || !this._texture.isReady) {
+        if (this._texture == null) {
             return;
         }
         if (this.video.readyState < this.video.HAVE_CURRENT_DATA) {

+ 155 - 285
src/Materials/effect.ts

@@ -1,5 +1,5 @@
 import { Observable } from "../Misc/observable";
-import { Nullable } from "../types";
+import { FloatArray, Nullable } from "../types";
 import { Constants } from "../Engines/constants";
 import { DomManagement } from "../Misc/domManagement";
 import { Logger } from "../Misc/logger";
@@ -7,6 +7,7 @@ import { IDisposable } from '../scene';
 import { IPipelineContext } from '../Engines/IPipelineContext';
 import { DataBuffer } from '../Meshes/dataBuffer';
 import { ShaderProcessor } from '../Engines/Processors/shaderProcessor';
+import { ProcessingOptions, ShaderProcessingContext } from '../Engines/Processors/shaderProcessingOptions';
 import { IMatrixLike, IVector2Like, IVector3Like, IVector4Like, IColor3Like, IColor4Like } from '../Maths/math.like';
 import { ThinEngine } from '../Engines/thinEngine';
 import { IEffectFallbacks } from './iEffectFallbacks';
@@ -144,14 +145,16 @@ export class Effect implements IDisposable {
     /** @hidden */
     public _bonesComputationForcedToCPU = false;
     /** @hidden */
+    public _uniformBuffersNames: { [key: string]: number } = {};
+    /** @hidden */
+    public _samplerList: string[];
+    /** @hidden */
     public _multiTarget: boolean = false;
 
     private static _uniqueIdSeed = 0;
     private _engine: Engine;
-    private _uniformBuffersNames: { [key: string]: number } = {};
     private _uniformBuffersNamesList: string[];
     private _uniformsNames: string[];
-    private _samplerList: string[];
     private _samplers: { [key: string]: number } = {};
     private _isReady = false;
     private _compilationError = "";
@@ -167,20 +170,26 @@ export class Effect implements IDisposable {
     public _key: string = "";
     private _indexParameters: any;
     private _fallbacks: Nullable<IEffectFallbacks> = null;
-    private _vertexSourceCode: string = "";
-    private _fragmentSourceCode: string = "";
     private _vertexSourceCodeOverride: string = "";
     private _fragmentSourceCodeOverride: string = "";
     private _transformFeedbackVaryings: Nullable<string[]> = null;
-    private _rawVertexSourceCode: string = "";
-    private _rawFragmentSourceCode: string = "";
     /**
      * Compiled shader to webGL program.
      * @hidden
      */
     public _pipelineContext: Nullable<IPipelineContext> = null;
-    private _valueCache: { [key: string]: any } = {};
+    /** @hidden */
+    public _vertexSourceCode: string = "";
+    /** @hidden */
+    public _fragmentSourceCode: string = "";
+
+    /** @hidden */
+    private _rawVertexSourceCode: string = "";
+    /** @hidden */
+    private _rawFragmentSourceCode: string = "";
+
     private static _baseCache: { [key: number]: DataBuffer } = {};
+    private _processingContext: Nullable<ShaderProcessingContext>;
 
     /**
      * Instantiates an effect.
@@ -195,11 +204,14 @@ export class Effect implements IDisposable {
      * @param onCompiled Callback that will be called when the shader is compiled.
      * @param onError Callback that will be called if an error occurs during shader compilation.
      * @param indexParameters Parameters to be used with Babylons include syntax to iterate over an array (eg. {lights: 10})
+     * @param key Effect Key identifying uniquely compiled shader variants
      */
     constructor(baseName: any, attributesNamesOrOptions: string[] | IEffectCreationOptions, uniformsNamesOrEngine: string[] | ThinEngine, samplers: Nullable<string[]> = null,
         engine?: ThinEngine, defines: Nullable<string> = null,
-        fallbacks: Nullable<IEffectFallbacks> = null, onCompiled: Nullable<(effect: Effect) => void> = null, onError: Nullable<(effect: Effect, errors: string) => void> = null, indexParameters?: any) {
+        fallbacks: Nullable<IEffectFallbacks> = null, onCompiled: Nullable<(effect: Effect) => void> = null, onError: Nullable<(effect: Effect, errors: string) => void> = null, indexParameters?: any, key: string = "")
+    {
         this.name = baseName;
+        this._key = key;
 
         let processFinalCode: Nullable<(shaderType: string, code: string) => string> = null;
 
@@ -274,7 +286,9 @@ export class Effect implements IDisposable {
             fragmentSource = baseName.fragment || baseName;
         }
 
-        let processorOptions = {
+        this._processingContext = engine!._getShaderProcessingContext();
+
+        const processorOptions: ProcessingOptions = {
             defines: this.defines.split("\n"),
             indexParameters: this._indexParameters,
             isFragment: false,
@@ -283,27 +297,41 @@ export class Effect implements IDisposable {
             supportsUniformBuffers: this._engine.supportsUniformBuffers,
             shadersRepository: Effect.ShadersRepository,
             includesShadersStore: Effect.IncludesShadersStore,
-            version: (this._engine.webGLVersion * 100).toString(),
-            platformName: this._engine.webGLVersion >= 2 ? "WEBGL2" : "WEBGL1"
+            version: (this._engine.version * 100).toString(),
+            platformName: this._engine.shaderPlatformName,
+            processingContext: this._processingContext
         };
 
-        this._loadShader(vertexSource, "Vertex", "", (vertexCode) => {
-            this._rawVertexSourceCode = vertexCode;
-            this._loadShader(fragmentSource, "Fragment", "Pixel", (fragmentCode) => {
-                this._rawFragmentSourceCode = fragmentCode;
-                ShaderProcessor.Process(vertexCode, processorOptions, (migratedVertexCode) => {
+        let shaderCodes : [string | undefined, string | undefined] = [undefined, undefined];
+        let shadersLoaded = () => {
+            if (shaderCodes[0] && shaderCodes[1]) {
+                processorOptions.isFragment = true;
+                let [migratedVertexCode, fragmentCode] = shaderCodes;
+                ShaderProcessor.Process(fragmentCode, processorOptions, (migratedFragmentCode) => {
                     if (processFinalCode) {
-                        migratedVertexCode = processFinalCode("vertex", migratedVertexCode);
+                        migratedFragmentCode = processFinalCode("fragment", migratedFragmentCode);
                     }
-                    processorOptions.isFragment = true;
-                    ShaderProcessor.Process(fragmentCode, processorOptions, (migratedFragmentCode) => {
-                        if (processFinalCode) {
-                            migratedFragmentCode = processFinalCode("fragment", migratedFragmentCode);
-                        }
-                        this._useFinalCode(migratedVertexCode, migratedFragmentCode, baseName);
-                    }, this._engine);
+                    const finalShaders = ShaderProcessor.Finalize(migratedVertexCode, migratedFragmentCode, processorOptions);
+                    this._useFinalCode(finalShaders.vertexCode, finalShaders.fragmentCode, baseName);
                 }, this._engine);
-            });
+            }
+
+        };
+        this._loadShader(vertexSource, "Vertex", "", (vertexCode) => {
+            ShaderProcessor.Initialize(processorOptions);
+            ShaderProcessor.Process(vertexCode, processorOptions, (migratedVertexCode) => {
+                this._rawVertexSourceCode = vertexCode;
+                if (processFinalCode) {
+                    migratedVertexCode = processFinalCode("vertex", migratedVertexCode);
+                }
+                shaderCodes[0] = migratedVertexCode;
+                shadersLoaded();
+            }, this._engine);
+        });
+        this._loadShader(fragmentSource, "Fragment", "Pixel", (fragmentCode) => {
+            this._rawFragmentSourceCode = fragmentCode;
+            shaderCodes[1] = fragmentCode;
+            shadersLoaded();
         });
     }
 
@@ -553,14 +581,14 @@ export class Effect implements IDisposable {
      * Gets the vertex shader source code of this effect
      */
     public get vertexSourceCode(): string {
-        return this._vertexSourceCodeOverride && this._fragmentSourceCodeOverride ? this._vertexSourceCodeOverride : this._vertexSourceCode;
+        return this._vertexSourceCodeOverride && this._fragmentSourceCodeOverride ? this._vertexSourceCodeOverride : (this._pipelineContext?._getVertexShaderCode() ?? this._vertexSourceCode);
     }
 
     /**
      * Gets the fragment shader source code of this effect
      */
     public get fragmentSourceCode(): string {
-        return this._vertexSourceCodeOverride && this._fragmentSourceCodeOverride ? this._fragmentSourceCodeOverride : this._fragmentSourceCode;
+        return this._vertexSourceCodeOverride && this._fragmentSourceCodeOverride ? this._fragmentSourceCodeOverride : (this._pipelineContext?._getFragmentShaderCode() ?? this._fragmentSourceCode);
     }
 
     /**
@@ -616,36 +644,35 @@ export class Effect implements IDisposable {
     public _prepareEffect() {
         let attributesNames = this._attributesNames;
         let defines = this.defines;
-        this._valueCache = {};
 
         var previousPipelineContext = this._pipelineContext;
 
         try {
             let engine = this._engine;
 
-            this._pipelineContext = engine.createPipelineContext();
+            this._pipelineContext = engine.createPipelineContext(this._processingContext);
+            this._pipelineContext._name = this._key;
 
             let rebuildRebind = this._rebuildProgram.bind(this);
             if (this._vertexSourceCodeOverride && this._fragmentSourceCodeOverride) {
-                engine._preparePipelineContext(this._pipelineContext, this._vertexSourceCodeOverride, this._fragmentSourceCodeOverride, true, rebuildRebind, null, this._transformFeedbackVaryings);
+                engine._preparePipelineContext(this._pipelineContext, this._vertexSourceCodeOverride, this._fragmentSourceCodeOverride, true, this._rawVertexSourceCode, this._rawFragmentSourceCode, rebuildRebind, null, this._transformFeedbackVaryings, this._key);
             }
             else {
-                engine._preparePipelineContext(this._pipelineContext, this._vertexSourceCode, this._fragmentSourceCode, false, rebuildRebind, defines, this._transformFeedbackVaryings);
+                engine._preparePipelineContext(this._pipelineContext, this._vertexSourceCode, this._fragmentSourceCode, false, this._rawVertexSourceCode, this._rawFragmentSourceCode, rebuildRebind, defines, this._transformFeedbackVaryings, this._key);
             }
 
             engine._executeWhenRenderingStateIsCompiled(this._pipelineContext, () => {
-                if (engine.supportsUniformBuffers) {
-                    for (var name in this._uniformBuffersNames) {
-                        this.bindUniformBlock(name, this._uniformBuffersNames[name]);
-                    }
-                }
-
-                let uniforms = engine.getUniforms(this._pipelineContext!, this._uniformsNames);
-                uniforms.forEach((uniform, index) => {
-                    this._uniforms[this._uniformsNames[index]] = uniform;
-                });
-
-                this._attributes = engine.getAttributes(this._pipelineContext!, attributesNames);
+                this._attributes = [];
+                this._pipelineContext!._fillEffectInformation(this,
+                    this._uniformBuffersNames,
+                    this._uniformsNames,
+                    this._uniforms,
+                    this._samplerList,
+                    this._samplers,
+                    attributesNames,
+                    this._attributes);
+
+                // Caches attribute locations.
                 if (attributesNames) {
                     for (let i = 0; i < attributesNames.length; i++) {
                         const name = attributesNames[i];
@@ -653,20 +680,6 @@ export class Effect implements IDisposable {
                     }
                 }
 
-                var index: number;
-                for (index = 0; index < this._samplerList.length; index++) {
-                    var sampler = this.getUniform(this._samplerList[index]);
-
-                    if (sampler == null) {
-                        this._samplerList.splice(index, 1);
-                        index--;
-                    }
-                }
-
-                this._samplerList.forEach((name, index) => {
-                    this._samplers[name] = index;
-                });
-
                 engine.bindSamplers(this);
 
                 this._compilationError = "";
@@ -801,7 +814,7 @@ export class Effect implements IDisposable {
      * @hidden
      */
     public _bindTexture(channel: string, texture: Nullable<InternalTexture>): void {
-        this._engine._bindTexture(this._samplers[channel], texture);
+        this._engine._bindTexture(this._samplers[channel], texture, channel);
     }
 
     /**
@@ -810,7 +823,7 @@ export class Effect implements IDisposable {
      * @param texture Texture to set.
      */
     public setTexture(channel: string, texture: Nullable<ThinTexture>): void {
-        this._engine.setTexture(this._samplers[channel], this._uniforms[channel], texture);
+        this._engine.setTexture(this._samplers[channel], this._uniforms[channel], texture, channel);
     }
 
     /**
@@ -819,7 +832,7 @@ export class Effect implements IDisposable {
      * @param texture Texture to set.
      */
     public setDepthStencilTexture(channel: string, texture: Nullable<RenderTargetTexture>): void {
-        this._engine.setDepthStencilTexture(this._samplers[channel], this._uniforms[channel], texture);
+        this._engine.setDepthStencilTexture(this._samplers[channel], this._uniforms[channel], texture, channel);
     }
 
     /**
@@ -844,7 +857,7 @@ export class Effect implements IDisposable {
             }
         }
 
-        this._engine.setTextureArray(this._samplers[channel], this._uniforms[channel], textures);
+        this._engine.setTextureArray(this._samplers[channel], this._uniforms[channel], textures, channel);
     }
 
     /**
@@ -853,7 +866,7 @@ export class Effect implements IDisposable {
      * @param postProcess Post process to get the input texture from.
      */
     public setTextureFromPostProcess(channel: string, postProcess: Nullable<PostProcess>): void {
-        this._engine.setTextureFromPostProcess(this._samplers[channel], postProcess);
+        this._engine.setTextureFromPostProcess(this._samplers[channel], postProcess, channel);
     }
 
     /**
@@ -863,98 +876,7 @@ export class Effect implements IDisposable {
      * @param postProcess Post process to get the output texture from.
      */
     public setTextureFromPostProcessOutput(channel: string, postProcess: Nullable<PostProcess>): void {
-        this._engine.setTextureFromPostProcessOutput(this._samplers[channel], postProcess);
-    }
-
-    /** @hidden */
-    public _cacheMatrix(uniformName: string, matrix: IMatrixLike): boolean {
-        var cache = this._valueCache[uniformName];
-        var flag = matrix.updateFlag;
-        if (cache !== undefined && cache === flag) {
-            return false;
-        }
-
-        this._valueCache[uniformName] = flag;
-
-        return true;
-    }
-
-    /** @hidden */
-    public _cacheFloat2(uniformName: string, x: number, y: number): boolean {
-        var cache = this._valueCache[uniformName];
-        if (!cache || cache.length !== 2) {
-            cache = [x, y];
-            this._valueCache[uniformName] = cache;
-            return true;
-        }
-
-        var changed = false;
-        if (cache[0] !== x) {
-            cache[0] = x;
-            changed = true;
-        }
-        if (cache[1] !== y) {
-            cache[1] = y;
-            changed = true;
-        }
-
-        return changed;
-    }
-
-    /** @hidden */
-    public _cacheFloat3(uniformName: string, x: number, y: number, z: number): boolean {
-        var cache = this._valueCache[uniformName];
-        if (!cache || cache.length !== 3) {
-            cache = [x, y, z];
-            this._valueCache[uniformName] = cache;
-            return true;
-        }
-
-        var changed = false;
-        if (cache[0] !== x) {
-            cache[0] = x;
-            changed = true;
-        }
-        if (cache[1] !== y) {
-            cache[1] = y;
-            changed = true;
-        }
-        if (cache[2] !== z) {
-            cache[2] = z;
-            changed = true;
-        }
-
-        return changed;
-    }
-
-    /** @hidden */
-    public _cacheFloat4(uniformName: string, x: number, y: number, z: number, w: number): boolean {
-        var cache = this._valueCache[uniformName];
-        if (!cache || cache.length !== 4) {
-            cache = [x, y, z, w];
-            this._valueCache[uniformName] = cache;
-            return true;
-        }
-
-        var changed = false;
-        if (cache[0] !== x) {
-            cache[0] = x;
-            changed = true;
-        }
-        if (cache[1] !== y) {
-            cache[1] = y;
-            changed = true;
-        }
-        if (cache[2] !== z) {
-            cache[2] = z;
-            changed = true;
-        }
-        if (cache[3] !== w) {
-            cache[3] = w;
-            changed = true;
-        }
-
-        return changed;
+        this._engine.setTextureFromPostProcessOutput(this._samplers[channel], postProcess, channel);
     }
 
     /**
@@ -968,7 +890,7 @@ export class Effect implements IDisposable {
             return;
         }
         Effect._baseCache[bufferName] = buffer;
-        this._engine.bindUniformBufferBase(buffer, bufferName);
+        this._engine.bindUniformBufferBase(buffer, bufferName, name);
     }
 
     /**
@@ -987,15 +909,46 @@ export class Effect implements IDisposable {
      * @returns this effect.
      */
     public setInt(uniformName: string, value: number): Effect {
-        var cache = this._valueCache[uniformName];
-        if (cache !== undefined && cache === value) {
-            return this;
-        }
+        this._pipelineContext!.setInt(uniformName, value);
+        return this;
+    }
 
-        if (this._engine.setInt(this._uniforms[uniformName], value)) {
-            this._valueCache[uniformName] = value;
-        }
+    /**
+     * Sets an int2 value on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param x First int in int2.
+     * @param y Second int in int2.
+     * @returns this effect.
+     */
+    public setInt2(uniformName: string, x: number, y: number): Effect {
+        this._pipelineContext!.setInt2(uniformName, x, y);
+        return this;
+    }
 
+    /**
+     * Sets an int3 value on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param x First int in int3.
+     * @param y Second int in int3.
+     * @param z Third int in int3.
+     * @returns this effect.
+     */
+    public setInt3(uniformName: string, x: number, y: number, z: number): Effect {
+        this._pipelineContext!.setInt3(uniformName, x, y, z);
+        return this;
+    }
+
+    /**
+     * Sets an int4 value on a uniform variable.
+     * @param uniformName Name of the variable.
+     * @param x First int in int4.
+     * @param y Second int in int4.
+     * @param z Third int in int4.
+     * @param w Fourth int in int4.
+     * @returns this effect.
+     */
+    public setInt4(uniformName: string, x: number, y: number, z: number, w: number): Effect {
+        this._pipelineContext!.setInt4(uniformName, x, y, z, w);
         return this;
     }
 
@@ -1006,9 +959,7 @@ export class Effect implements IDisposable {
      * @returns this effect.
      */
     public setIntArray(uniformName: string, array: Int32Array): Effect {
-        this._valueCache[uniformName] = null;
-        this._engine.setIntArray(this._uniforms[uniformName], array);
-
+        this._pipelineContext!.setIntArray(uniformName, array);
         return this;
     }
 
@@ -1019,9 +970,7 @@ export class Effect implements IDisposable {
      * @returns this effect.
      */
     public setIntArray2(uniformName: string, array: Int32Array): Effect {
-        this._valueCache[uniformName] = null;
-        this._engine.setIntArray2(this._uniforms[uniformName], array);
-
+        this._pipelineContext!.setIntArray2(uniformName, array);
         return this;
     }
 
@@ -1032,9 +981,7 @@ export class Effect implements IDisposable {
      * @returns this effect.
      */
     public setIntArray3(uniformName: string, array: Int32Array): Effect {
-        this._valueCache[uniformName] = null;
-        this._engine.setIntArray3(this._uniforms[uniformName], array);
-
+        this._pipelineContext!.setIntArray3(uniformName, array);
         return this;
     }
 
@@ -1045,9 +992,7 @@ export class Effect implements IDisposable {
      * @returns this effect.
      */
     public setIntArray4(uniformName: string, array: Int32Array): Effect {
-        this._valueCache[uniformName] = null;
-        this._engine.setIntArray4(this._uniforms[uniformName], array);
-
+        this._pipelineContext!.setIntArray4(uniformName, array);
         return this;
     }
 
@@ -1057,10 +1002,8 @@ export class Effect implements IDisposable {
      * @param array array to be set.
      * @returns this effect.
      */
-    public setFloatArray(uniformName: string, array: Float32Array): Effect {
-        this._valueCache[uniformName] = null;
-        this._engine.setArray(this._uniforms[uniformName], array);
-
+    public setFloatArray(uniformName: string, array: FloatArray): Effect {
+        this._pipelineContext!.setArray(uniformName, array);
         return this;
     }
 
@@ -1070,10 +1013,8 @@ export class Effect implements IDisposable {
      * @param array array to be set.
      * @returns this effect.
      */
-    public setFloatArray2(uniformName: string, array: Float32Array): Effect {
-        this._valueCache[uniformName] = null;
-        this._engine.setArray2(this._uniforms[uniformName], array);
-
+    public setFloatArray2(uniformName: string, array: FloatArray): Effect {
+        this._pipelineContext!.setArray2(uniformName, array);
         return this;
     }
 
@@ -1083,10 +1024,8 @@ export class Effect implements IDisposable {
      * @param array array to be set.
      * @returns this effect.
      */
-    public setFloatArray3(uniformName: string, array: Float32Array): Effect {
-        this._valueCache[uniformName] = null;
-        this._engine.setArray3(this._uniforms[uniformName], array);
-
+    public setFloatArray3(uniformName: string, array: FloatArray): Effect {
+        this._pipelineContext!.setArray3(uniformName, array);
         return this;
     }
 
@@ -1096,10 +1035,8 @@ export class Effect implements IDisposable {
      * @param array array to be set.
      * @returns this effect.
      */
-    public setFloatArray4(uniformName: string, array: Float32Array): Effect {
-        this._valueCache[uniformName] = null;
-        this._engine.setArray4(this._uniforms[uniformName], array);
-
+    public setFloatArray4(uniformName: string, array: FloatArray): Effect {
+        this._pipelineContext!.setArray4(uniformName, array);
         return this;
     }
 
@@ -1110,9 +1047,7 @@ export class Effect implements IDisposable {
      * @returns this effect.
      */
     public setArray(uniformName: string, array: number[]): Effect {
-        this._valueCache[uniformName] = null;
-        this._engine.setArray(this._uniforms[uniformName], array);
-
+        this._pipelineContext!.setArray(uniformName, array);
         return this;
     }
 
@@ -1123,9 +1058,7 @@ export class Effect implements IDisposable {
      * @returns this effect.
      */
     public setArray2(uniformName: string, array: number[]): Effect {
-        this._valueCache[uniformName] = null;
-        this._engine.setArray2(this._uniforms[uniformName], array);
-
+        this._pipelineContext!.setArray2(uniformName, array);
         return this;
     }
 
@@ -1136,9 +1069,7 @@ export class Effect implements IDisposable {
      * @returns this effect.
      */
     public setArray3(uniformName: string, array: number[]): Effect {
-        this._valueCache[uniformName] = null;
-        this._engine.setArray3(this._uniforms[uniformName], array);
-
+        this._pipelineContext!.setArray3(uniformName, array);
         return this;
     }
 
@@ -1149,9 +1080,7 @@ export class Effect implements IDisposable {
      * @returns this effect.
      */
     public setArray4(uniformName: string, array: number[]): Effect {
-        this._valueCache[uniformName] = null;
-        this._engine.setArray4(this._uniforms[uniformName], array);
-
+        this._pipelineContext!.setArray4(uniformName, array);
         return this;
     }
 
@@ -1162,13 +1091,7 @@ export class Effect implements IDisposable {
      * @returns this effect.
      */
     public setMatrices(uniformName: string, matrices: Float32Array | Array<number>): Effect {
-        if (!matrices) {
-            return this;
-        }
-
-        this._valueCache[uniformName] = null;
-        this._engine.setMatrices(this._uniforms[uniformName], matrices as Float32Array); // the cast is ok because it is gl.uniformMatrix4fv() which is called at the end, and this function accepts Float32Array and Array<number>
-
+        this._pipelineContext!.setMatrices(uniformName, matrices as Float32Array);
         return this;
     }
 
@@ -1179,11 +1102,7 @@ export class Effect implements IDisposable {
      * @returns this effect.
      */
     public setMatrix(uniformName: string, matrix: IMatrixLike): Effect {
-        if (this._cacheMatrix(uniformName, matrix)) {
-            if (!this._engine.setMatrices(this._uniforms[uniformName], matrix.toArray() as Float32Array)) {
-                this._valueCache[uniformName] = null;
-            }
-        }
+        this._pipelineContext!.setMatrix(uniformName, matrix);
         return this;
     }
 
@@ -1194,9 +1113,8 @@ export class Effect implements IDisposable {
      * @returns this effect.
      */
     public setMatrix3x3(uniformName: string, matrix: Float32Array | Array<number>): Effect {
-        this._valueCache[uniformName] = null;
-        this._engine.setMatrix3x3(this._uniforms[uniformName], matrix as Float32Array); // the cast is ok because it is gl.uniformMatrix3fv() which is called at the end, and this function accepts Float32Array and Array<number>
-
+        // the cast is ok because it is gl.uniformMatrix3fv() which is called at the end, and this function accepts Float32Array and Array<number>
+        this._pipelineContext!.setMatrix3x3(uniformName, matrix as Float32Array);
         return this;
     }
 
@@ -1207,9 +1125,8 @@ export class Effect implements IDisposable {
      * @returns this effect.
      */
     public setMatrix2x2(uniformName: string, matrix: Float32Array | Array<number>): Effect {
-        this._valueCache[uniformName] = null;
-        this._engine.setMatrix2x2(this._uniforms[uniformName], matrix as Float32Array); // the cast is ok because it is gl.uniformMatrix2fv() which is called at the end, and this function accepts Float32Array and Array<number>
-
+        // the cast is ok because it is gl.uniformMatrix3fv() which is called at the end, and this function accepts Float32Array and Array<number>
+        this._pipelineContext!.setMatrix2x2(uniformName, matrix as Float32Array);
         return this;
     }
 
@@ -1220,15 +1137,7 @@ export class Effect implements IDisposable {
      * @returns this effect.
      */
     public setFloat(uniformName: string, value: number): Effect {
-        var cache = this._valueCache[uniformName];
-        if (cache !== undefined && cache === value) {
-            return this;
-        }
-
-        if (this._engine.setFloat(this._uniforms[uniformName], value)) {
-            this._valueCache[uniformName] = value;
-        }
-
+        this._pipelineContext!.setFloat(uniformName, value);
         return this;
     }
 
@@ -1239,15 +1148,7 @@ export class Effect implements IDisposable {
      * @returns this effect.
      */
     public setBool(uniformName: string, bool: boolean): Effect {
-        var cache = this._valueCache[uniformName];
-        if (cache !== undefined && cache === bool) {
-            return this;
-        }
-
-        if (this._engine.setInt(this._uniforms[uniformName], bool ? 1 : 0)) {
-            this._valueCache[uniformName] = bool;
-        }
-
+        this._pipelineContext!.setInt(uniformName, bool ? 1 : 0);
         return this;
     }
 
@@ -1258,11 +1159,7 @@ export class Effect implements IDisposable {
      * @returns this effect.
      */
     public setVector2(uniformName: string, vector2: IVector2Like): Effect {
-        if (this._cacheFloat2(uniformName, vector2.x, vector2.y)) {
-            if (!this._engine.setFloat2(this._uniforms[uniformName], vector2.x, vector2.y)) {
-                this._valueCache[uniformName] = null;
-            }
-        }
+        this._pipelineContext!.setVector2(uniformName, vector2);
         return this;
     }
 
@@ -1274,11 +1171,7 @@ export class Effect implements IDisposable {
      * @returns this effect.
      */
     public setFloat2(uniformName: string, x: number, y: number): Effect {
-        if (this._cacheFloat2(uniformName, x, y)) {
-            if (!this._engine.setFloat2(this._uniforms[uniformName], x, y)) {
-                this._valueCache[uniformName] = null;
-            }
-        }
+        this._pipelineContext!.setFloat2(uniformName, x, y);
         return this;
     }
 
@@ -1289,11 +1182,7 @@ export class Effect implements IDisposable {
      * @returns this effect.
      */
     public setVector3(uniformName: string, vector3: IVector3Like): Effect {
-        if (this._cacheFloat3(uniformName, vector3.x, vector3.y, vector3.z)) {
-            if (!this._engine.setFloat3(this._uniforms[uniformName], vector3.x, vector3.y, vector3.z)) {
-                this._valueCache[uniformName] = null;
-            }
-        }
+        this._pipelineContext!.setVector3(uniformName, vector3);
         return this;
     }
 
@@ -1306,11 +1195,7 @@ export class Effect implements IDisposable {
      * @returns this effect.
      */
     public setFloat3(uniformName: string, x: number, y: number, z: number): Effect {
-        if (this._cacheFloat3(uniformName, x, y, z)) {
-            if (!this._engine.setFloat3(this._uniforms[uniformName], x, y, z)) {
-                this._valueCache[uniformName] = null;
-            }
-        }
+        this._pipelineContext!.setFloat3(uniformName, x, y, z);
         return this;
     }
 
@@ -1321,11 +1206,7 @@ export class Effect implements IDisposable {
      * @returns this effect.
      */
     public setVector4(uniformName: string, vector4: IVector4Like): Effect {
-        if (this._cacheFloat4(uniformName, vector4.x, vector4.y, vector4.z, vector4.w)) {
-            if (!this._engine.setFloat4(this._uniforms[uniformName], vector4.x, vector4.y, vector4.z, vector4.w)) {
-                this._valueCache[uniformName] = null;
-            }
-        }
+        this._pipelineContext!.setVector4(uniformName, vector4);
         return this;
     }
 
@@ -1339,11 +1220,7 @@ export class Effect implements IDisposable {
      * @returns this effect.
      */
     public setFloat4(uniformName: string, x: number, y: number, z: number, w: number): Effect {
-        if (this._cacheFloat4(uniformName, x, y, z, w)) {
-            if (!this._engine.setFloat4(this._uniforms[uniformName], x, y, z, w)) {
-                this._valueCache[uniformName] = null;
-            }
-        }
+        this._pipelineContext!.setFloat4(uniformName, x, y, z, w);
         return this;
     }
 
@@ -1354,11 +1231,7 @@ export class Effect implements IDisposable {
      * @returns this effect.
      */
     public setColor3(uniformName: string, color3: IColor3Like): Effect {
-        if (this._cacheFloat3(uniformName, color3.r, color3.g, color3.b)) {
-            if (!this._engine.setFloat3(this._uniforms[uniformName], color3.r, color3.g, color3.b)) {
-                this._valueCache[uniformName] = null;
-            }
-        }
+        this._pipelineContext!.setColor3(uniformName, color3);
         return this;
     }
 
@@ -1370,11 +1243,7 @@ export class Effect implements IDisposable {
      * @returns this effect.
      */
     public setColor4(uniformName: string, color3: IColor3Like, alpha: number): Effect {
-        if (this._cacheFloat4(uniformName, color3.r, color3.g, color3.b, alpha)) {
-            if (!this._engine.setFloat4(this._uniforms[uniformName], color3.r, color3.g, color3.b, alpha)) {
-                this._valueCache[uniformName] = null;
-            }
-        }
+        this._pipelineContext!.setColor4(uniformName, color3, alpha);
         return this;
     }
 
@@ -1385,16 +1254,17 @@ export class Effect implements IDisposable {
      * @returns this effect.
      */
     public setDirectColor4(uniformName: string, color4: IColor4Like): Effect {
-        if (this._cacheFloat4(uniformName, color4.r, color4.g, color4.b, color4.a)) {
-            if (!this._engine.setFloat4(this._uniforms[uniformName], color4.r, color4.g, color4.b, color4.a)) {
-                this._valueCache[uniformName] = null;
-            }
-        }
+        this._pipelineContext!.setDirectColor4(uniformName, color4);
         return this;
     }
 
-    /** Release all associated resources */
+    /**
+     * Release all associated resources.
+     **/
     public dispose() {
+        if (this._pipelineContext) {
+            this._pipelineContext.dispose();
+        }
         this._engine._releaseEffect(this);
     }
 

+ 49 - 7
src/Materials/material.ts

@@ -4,7 +4,6 @@ import { IAnimatable } from '../Animations/animatable.interface';
 import { SmartArray } from "../Misc/smartArray";
 import { Observer, Observable } from "../Misc/observable";
 import { Nullable } from "../types";
-import { Scene } from "../scene";
 import { Matrix } from "../Maths/math.vector";
 import { EngineStore } from "../Engines/engineStore";
 import { SubMesh } from "../Meshes/subMesh";
@@ -20,11 +19,13 @@ import { Logger } from "../Misc/logger";
 import { IInspectable } from '../Misc/iInspectable';
 import { Plane } from '../Maths/math.plane';
 import { ShadowDepthWrapper } from './shadowDepthWrapper';
+import { MaterialHelper } from './materialHelper';
 
 declare type PrePassRenderer = import("../Rendering/prePassRenderer").PrePassRenderer;
 declare type Mesh = import("../Meshes/mesh").Mesh;
 declare type Animation = import("../Animations/animation").Animation;
 declare type InstancedMesh = import('../Meshes/instancedMesh').InstancedMesh;
+declare type Scene = import("../scene").Scene;
 
 declare var BABYLON: any;
 
@@ -620,6 +621,7 @@ export class Material implements IAnimatable {
      * Stores a reference to the scene
      */
     private _scene: Scene;
+    private _needToBindSceneUbo: boolean;
 
     /**
      * Stores the fill mode state
@@ -676,7 +678,7 @@ export class Material implements IAnimatable {
             this.sideOrientation = Material.CounterClockWiseSideOrientation;
         }
 
-        this._uniformBuffer = new UniformBuffer(this._scene.getEngine());
+        this._uniformBuffer = new UniformBuffer(this._scene.getEngine(), undefined, undefined, name);
         this._useUBO = this.getScene().getEngine().supportsUniformBuffers;
 
         if (!doNotAdd) {
@@ -931,6 +933,25 @@ export class Material implements IAnimatable {
     }
 
     /**
+     * Update the scene ubo before it can be used in rendering processing
+     * @param scene the scene to retrieve the ubo from
+     * @returns the scene UniformBuffer
+     */
+    public finalizeSceneUbo(scene: Scene): UniformBuffer {
+        const ubo = scene.getSceneUniformBuffer();
+        const eyePosition = MaterialHelper.BindEyePosition(null, scene);
+        ubo.updateFloat4("vEyePosition",
+            eyePosition.x,
+            eyePosition.y,
+            eyePosition.z,
+            eyePosition.w);
+
+        ubo.update();
+
+        return ubo;
+    }
+
+    /**
      * Binds the scene's uniform buffer to the effect.
      * @param effect defines the effect to bind to the scene uniform buffer
      * @param sceneUbo defines the uniform buffer storing scene data
@@ -947,19 +968,33 @@ export class Material implements IAnimatable {
         if (!this._useUBO) {
             effect.setMatrix("view", this.getScene().getViewMatrix());
         } else {
-            this.bindSceneUniformBuffer(effect, this.getScene().getSceneUniformBuffer());
+            this._needToBindSceneUbo = true;
         }
     }
 
     /**
-     * Binds the view projection matrix to the effect
-     * @param effect defines the effect to bind the view projection matrix to
+     * Binds the view projection and projection matrices to the effect
+     * @param effect defines the effect to bind the view projection and projection matrices to
      */
     public bindViewProjection(effect: Effect): void {
         if (!this._useUBO) {
             effect.setMatrix("viewProjection", this.getScene().getTransformMatrix());
+            effect.setMatrix("projection", this.getScene().getProjectionMatrix());
         } else {
-            this.bindSceneUniformBuffer(effect, this.getScene().getSceneUniformBuffer());
+            this._needToBindSceneUbo = true;
+        }
+    }
+
+    /**
+     * Binds the view matrix to the effect
+     * @param effect defines the effect to bind the view matrix to
+     * @param variableName name of the shader variable that will hold the eye position
+     */
+    public bindEyePosition(effect: Effect, variableName?: string): void {
+        if (!this._useUBO) {
+            MaterialHelper.BindEyePosition(effect, this._scene, variableName);
+        } else {
+            this._needToBindSceneUbo = true;
         }
     }
 
@@ -967,8 +1002,15 @@ export class Material implements IAnimatable {
      * Processes to execute after binding the material to a mesh
      * @param mesh defines the rendered mesh
      */
-    protected _afterBind(mesh?: Mesh): void {
+    protected _afterBind(mesh?: Mesh, effect: Nullable<Effect> = null): void {
         this._scene._cachedMaterial = this;
+        if (this._needToBindSceneUbo) {
+            if (effect) {
+                this._needToBindSceneUbo = false;
+                this.finalizeSceneUbo(this.getScene());
+                this.bindSceneUniformBuffer(effect, this.getScene().getSceneUniformBuffer());
+            }
+        }
         if (mesh) {
             this._scene._cachedVisibility = mesh.visibility;
         } else {

+ 0 - 0
src/Materials/materialHelper.ts


Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels