소스 검색

Support of texture arrays in shader code in webgpu

Popov72 4 년 전
부모
커밋
38d23c3baa

+ 10 - 7
src/Engines/WebGPU/webgpuPipelineContext.ts

@@ -3,7 +3,7 @@ import { Nullable } from '../../types';
 import { WebGPUEngine } from '../webgpuEngine';
 import { InternalTexture } from '../../Materials/Textures/internalTexture';
 import { Effect } from '../../Materials/effect';
-import { WebGPUShaderProcessingContext } from './webgpuShaderProcessingContext';
+import { WebGPUTextureSamplerBindingDescription, WebGPUShaderProcessingContext } from './webgpuShaderProcessingContext';
 import { UniformBuffer } from "../../Materials/uniformBuffer";
 import { IMatrixLike, IVector2Like, IVector3Like, IVector4Like, IColor3Like, IColor4Like } from '../../Maths/math.like';
 
@@ -24,12 +24,14 @@ const _uniformSizes: { [type: string]: number } = {
 
 /** @hidden */
 export interface IWebGPUPipelineContextSamplerCache {
-    setIndex: number;
-
-    textureBinding: number;
-
     samplerBinding: number;
+    firstTextureName: string;
+    sampler?: GPUSampler;
+}
 
+/** @hidden */
+export interface IWebGPUPipelineContextTextureCache {
+    textureBinding: number;
     texture: InternalTexture;
 }
 
@@ -54,10 +56,10 @@ export class WebGPUPipelineContext implements IPipelineContext {
 
     public availableAttributes: { [key: string]: number };
     public availableUBOs: { [key: string]: { setIndex: number, bindingIndex: number} };
-    public availableSamplers: { [key: string]: { setIndex: number, bindingIndex: number} };
+    public availableSamplers: { [key: string]: WebGPUTextureSamplerBindingDescription };
 
     public orderedAttributes: string[];
-    public orderedUBOsAndSamplers: { name: string, isSampler: boolean, isComparisonSampler?: boolean, textureDimension?: GPUTextureViewDimension }[][];
+    public orderedUBOsAndSamplers: { name: string, isSampler: boolean, isComparisonSampler?: boolean, isTexture: boolean, textureDimension?: GPUTextureViewDimension }[][];
 
     public leftOverUniforms: { name: string, type: string, length: number }[];
     public leftOverUniformsByName: { [name: string]: string };
@@ -72,6 +74,7 @@ export class WebGPUPipelineContext implements IPipelineContext {
     public stages: Nullable<IWebGPURenderPipelineStageDescriptor>;
 
     public samplers: { [name: string]: Nullable<IWebGPUPipelineContextSamplerCache> } = { };
+    public textures: { [name: string]: Nullable<IWebGPUPipelineContextTextureCache> } = { };
 
     public vertexInputs: IWebGPUPipelineContextVertexInputsCache;
 

+ 31 - 22
src/Engines/WebGPU/webgpuShaderProcessingContext.ts

@@ -10,6 +10,19 @@ const _webGLTypeToLocationSize: { [key: string]: number } = {
     "mat4": 4,
 };
 
+/** @hidden */
+export interface WebGPUBindingInfo {
+    setIndex: number;
+    bindingIndex: number;
+}
+
+/** @hidden */
+export interface WebGPUTextureSamplerBindingDescription {
+    sampler: WebGPUBindingInfo;
+    isTextureArray: boolean;
+    textures: Array<WebGPUBindingInfo>;
+}
+
 /**
  * @hidden
  */
@@ -21,32 +34,16 @@ export class WebGPUShaderProcessingContext implements ShaderProcessingContext {
     public availableVaryings: { [key: string]: number };
     public availableAttributes: { [key: string]: number };
     public availableUBOs: { [key: string]: { setIndex: number, bindingIndex: number} };
-    public availableSamplers: { [key: string]: { setIndex: number, bindingIndex: number} };
+    public availableSamplers: { [key: string]: WebGPUTextureSamplerBindingDescription };
 
     public leftOverUniforms: { name: string, type: string, length: number }[];
 
     public orderedAttributes: string[];
-    public orderedUBOsAndSamplers: { name: string, isSampler: boolean, isComparisonSampler?: boolean, textureDimension?: GPUTextureViewDimension }[][];
+    public orderedUBOsAndSamplers: { name: string, isSampler: boolean, isComparisonSampler?: boolean, isTexture: boolean, textureIndex?: number, textureDimension?: GPUTextureViewDimension }[][];
 
     private _attributeNextLocation: number;
     private _varyingNextLocation: number;
 
-    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;
-    }
-
     constructor() {
         this._attributeNextLocation = 0;
         this._varyingNextLocation = 0;
@@ -64,12 +61,24 @@ export class WebGPUShaderProcessingContext implements ShaderProcessingContext {
         this.leftOverUniforms = [];
     }
 
-    public getNextFreeUBOBinding() {
-        return this._getNextFreeBinding(1);
+    public getAttributeNextLocation(dataType: string, arrayLength: number = 0): number {
+        const index = this._attributeNextLocation;
+
+        this._attributeNextLocation += (_webGLTypeToLocationSize[dataType] ?? 1) * (arrayLength || 1);
+
+        return index;
     }
 
-    public getNextFreeTextureBinding() {
-        return this._getNextFreeBinding(2);
+    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) {

+ 98 - 17
src/Engines/WebGPU/webgpuShaderProcessors.ts

@@ -1,7 +1,7 @@
 import { Nullable } from '../../types';
 import { IShaderProcessor } from '../Processors/iShaderProcessor';
 import { ShaderProcessingContext } from "../Processors/shaderProcessingOptions";
-import { WebGPUShaderProcessingContext } from './webgpuShaderProcessingContext';
+import { WebGPUTextureSamplerBindingDescription, WebGPUShaderProcessingContext } from './webgpuShaderProcessingContext';
 import * as WebGPUConstants from './webgpuConstants';
 import { ShaderCodeInliner } from '../Processors/shaderCodeInliner';
 import { dbgShowDebugInliningProcess } from '../webgpuEngine';
@@ -16,8 +16,8 @@ const _knownUBOs: { [key: string]: { setIndex: number, bindingIndex: number} } =
     "Mesh": { setIndex: 1, bindingIndex: 1 },
 };
 
-const _knownSamplers: { [key: string]: { setIndex: number, bindingIndex: number} } = {
-    "environmentBrdfSampler": { setIndex: 0, 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 },
 };
 
@@ -64,6 +64,8 @@ const _isComparisonSamplerByWebGPUSamplerType: { [key: string]: boolean } = {
 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;
@@ -73,7 +75,7 @@ export class WebGPUShaderProcessor implements IShaderProcessor {
             const lengthInString = name.substring(startArray + 1, endArray);
             length = +(lengthInString);
             if (isNaN(length)) {
-                length = +(preProcessors[lengthInString]);
+                length = +(preProcessors[lengthInString.trim()]);
             }
             name = name.substr(0, startArray);
         }
@@ -82,9 +84,12 @@ export class WebGPUShaderProcessor implements IShaderProcessor {
 
     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+(\S+)\s+(\S+)\s*;/gm);
@@ -109,6 +114,8 @@ export class WebGPUShaderProcessor implements IShaderProcessor {
     }
 
     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);
@@ -127,6 +134,8 @@ export class WebGPUShaderProcessor implements IShaderProcessor {
     }
 
     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);
@@ -138,36 +147,81 @@ export class WebGPUShaderProcessor implements IShaderProcessor {
 
             if (uniformType.indexOf("texture") === 0 || uniformType.indexOf("sampler") === 0) {
                 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 = webgpuProcessingContext.getNextFreeTextureBinding();
+                        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 setIndex = samplerInfo.setIndex;
-                const textureBindingIndex = samplerInfo.bindingIndex;
-                const samplerBindingIndex = samplerInfo.bindingIndex + 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];
 
                 // Manage textures and samplers.
-                uniform = `layout(set = ${setIndex}, binding = ${textureBindingIndex}) uniform ${textureType} ${name}Texture;
-                    layout(set = ${setIndex}, binding = ${samplerBindingIndex}) uniform ${samplerType} ${name}Sampler;
-                    #define ${name} ${samplerFunction}(${name}Texture, ${name}Sampler)`;
+                if (!isTextureArray) {
+                    arraySize = 1;
+                    uniform = `layout(set = ${samplerSetIndex}, binding = ${samplerBindingIndex}) uniform ${samplerType} ${name}Sampler;
+                        layout(set = ${samplerInfo.textures[0].setIndex}, binding = ${samplerInfo.textures[0].bindingIndex}) uniform ${textureType} ${name}Texture;
+                        #define ${name} ${samplerFunction}(${name}Texture, ${name}Sampler)`;
+                } else {
+                    let layouts = [];
+                    layouts.push(`layout(set = ${samplerSetIndex}, binding = ${samplerBindingIndex}) uniform ${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} ${samplerFunction}(${name}Texture${i}, ${name}Sampler)`;
+                    }
+                    uniform = layouts.join('\r\n') + uniform;
+                    this._textureArrayProcessing.push(name);
+                }
 
                 webgpuProcessingContext.availableSamplers[name] = samplerInfo;
-                if (!webgpuProcessingContext.orderedUBOsAndSamplers[setIndex]) {
-                    webgpuProcessingContext.orderedUBOsAndSamplers[setIndex] = [];
+
+                if (!webgpuProcessingContext.orderedUBOsAndSamplers[samplerSetIndex]) {
+                    webgpuProcessingContext.orderedUBOsAndSamplers[samplerSetIndex] = [];
                 }
-                webgpuProcessingContext.orderedUBOsAndSamplers[setIndex][textureBindingIndex] = {
+                webgpuProcessingContext.orderedUBOsAndSamplers[samplerSetIndex][samplerBindingIndex] = {
                     isSampler: true,
+                    isTexture: false,
                     isComparisonSampler: _isComparisonSamplerByWebGPUSamplerType[samplerType] ?? false,
-                    textureDimension,
                     name,
                 };
+
+                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] = [];
+                    }
+                    webgpuProcessingContext.orderedUBOsAndSamplers[textureSetIndex][textureBindingIndex] = {
+                        isSampler: false,
+                        isTexture: true,
+                        textureDimension,
+                        name: isTextureArray ? name + i.toString() : name,
+                        textureIndex: i,
+                    };
+                }
             }
             else {
                 // Check the size of the uniform array in case of array.
@@ -230,7 +284,7 @@ export class WebGPUShaderProcessor implements IShaderProcessor {
             if (!webgpuProcessingContext.orderedUBOsAndSamplers[setIndex]) {
                 webgpuProcessingContext.orderedUBOsAndSamplers[setIndex] = [];
             }
-            webgpuProcessingContext.orderedUBOsAndSamplers[setIndex][bindingIndex] = { isSampler: false, name };
+            webgpuProcessingContext.orderedUBOsAndSamplers[setIndex][bindingIndex] = { isSampler: false, isTexture: false, name };
 
             uniformBuffer = uniformBuffer.replace("uniform", `layout(set = ${setIndex}, binding = ${bindingIndex}) uniform`);
         }
@@ -283,9 +337,34 @@ export class WebGPUShaderProcessor implements IShaderProcessor {
         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];
@@ -304,7 +383,7 @@ export class WebGPUShaderProcessor implements IShaderProcessor {
                 if (!webgpuProcessingContext.orderedUBOsAndSamplers[availableUBO.setIndex]) {
                     webgpuProcessingContext.orderedUBOsAndSamplers[availableUBO.setIndex] = [];
                 }
-                webgpuProcessingContext.orderedUBOsAndSamplers[availableUBO.setIndex][availableUBO.bindingIndex] = { isSampler: false, name };
+                webgpuProcessingContext.orderedUBOsAndSamplers[availableUBO.setIndex][availableUBO.bindingIndex] = { isSampler: false, isTexture: false, name };
             }
 
             let ubo = `layout(set = ${availableUBO.setIndex}, binding = ${availableUBO.bindingIndex}) uniform ${name} {\n    `;
@@ -323,6 +402,8 @@ export class WebGPUShaderProcessor implements IShaderProcessor {
             fragmentCode = ubo + fragmentCode;
         }
 
+        this._preProcessors = null as any;
+
         return { vertexCode, fragmentCode };
     }
 }

+ 1 - 1
src/Engines/thinEngine.ts

@@ -3915,7 +3915,7 @@ export class ThinEngine {
      * @param uniform defines the associated uniform location
      * @param textures defines the array of textures to bind
      */
-    public setTextureArray(channel: number, uniform: Nullable<WebGLUniformLocation>, textures: BaseTexture[]): void {
+    public setTextureArray(channel: number, uniform: Nullable<WebGLUniformLocation>, textures: BaseTexture[], name: string): void {
         if (channel === undefined || !uniform) {
             return;
         }

+ 68 - 33
src/Engines/webgpuEngine.ts

@@ -1624,25 +1624,28 @@ export class WebGPUEngine extends Engine {
         }
     }
 
-    private _setInternalTexture(name: string, internalTexture: Nullable<InternalTexture>): void {
+    private _setInternalTexture(name: string, internalTexture: Nullable<InternalTexture>, baseName?: string, textureIndex = 0): void {
+        baseName = baseName ?? name;
         if (this._currentEffect) {
             const webgpuPipelineContext = this._currentEffect._pipelineContext as WebGPUPipelineContext;
 
-            if (webgpuPipelineContext.samplers[name]) {
-                if (webgpuPipelineContext.samplers[name]!.texture !== internalTexture) {
+            if (webgpuPipelineContext.textures[name]) {
+                if (webgpuPipelineContext.textures[name]!.texture !== internalTexture) {
                     webgpuPipelineContext.bindGroups = null as any; // the bind groups need to be rebuilt (at least the bind group owning this texture, but it's easier to just have them all rebuilt)
                 }
-                webgpuPipelineContext.samplers[name]!.texture = internalTexture!;
+                webgpuPipelineContext.textures[name]!.texture = internalTexture!;
             }
             else {
                 // TODO WEBGPU. 121 mapping samplers <-> availableSamplers
-                const availableSampler = webgpuPipelineContext.availableSamplers[name];
+                const availableSampler = webgpuPipelineContext.availableSamplers[baseName];
                 if (availableSampler) {
-                    webgpuPipelineContext.samplers[name] = {
-                        setIndex: availableSampler.setIndex,
-                        textureBinding: availableSampler.bindingIndex,
-                        samplerBinding: availableSampler.bindingIndex + 1,
-                        texture: internalTexture!
+                    webgpuPipelineContext.samplers[baseName] = {
+                        samplerBinding: availableSampler.sampler.bindingIndex,
+                        firstTextureName: name,
+                    };
+                    webgpuPipelineContext.textures[name] = {
+                        textureBinding: availableSampler.textures[textureIndex].bindingIndex,
+                        texture: internalTexture!,
                     };
                 }
             }
@@ -1650,17 +1653,27 @@ export class WebGPUEngine extends Engine {
     }
 
     public setTexture(channel: number, _: Nullable<WebGLUniformLocation>, texture: Nullable<BaseTexture>, name: string): void {
-        this._setTexture(channel, texture, false, false, name);
+        this._setTexture(channel, texture, false, false, name, name);
+    }
+
+    public setTextureArray(channel: number, _: Nullable<WebGLUniformLocation>, textures: BaseTexture[], name: string): void {
+        for (var index = 0; index < textures.length; index++) {
+            this._setTexture(-1, textures[index], true, false, name + index.toString(), name, index);
+        }
     }
 
-    protected _setTexture(channel: number, texture: Nullable<BaseTexture>, isPartOfTextureArray = false, depthStencilTexture = false, name = ""): boolean {
+    protected _setTexture(channel: number, texture: Nullable<BaseTexture>, isPartOfTextureArray = false, depthStencilTexture = false, name = "", baseName = "", textureIndex = 0): boolean {
+        // name == baseName for a texture that is not part of a texture array
+        // Else, name is something like 'myTexture0' / 'myTexture1' / ... and baseName is 'myTexture'
+        // baseName is used to look up the sampler in the WebGPUPipelineContext.samplers map
+        // name is used to look up the texture in the WebGPUPipelineContext.textures map
         if (this._currentEffect) {
             const webgpuPipelineContext = this._currentEffect._pipelineContext as WebGPUPipelineContext;
             if (!texture) {
-                if (webgpuPipelineContext.samplers[name] && webgpuPipelineContext.samplers[name]!.texture) {
+                if (webgpuPipelineContext.textures[name] && webgpuPipelineContext.textures[name]!.texture) {
                     webgpuPipelineContext.bindGroups = null as any; // the bind groups need to be rebuilt (at least the bind group owning this texture, but it's easier to just have them all rebuilt)
                 }
-                webgpuPipelineContext.samplers[name] = null;
+                webgpuPipelineContext.textures[name] = null;
                 return false;
             }
 
@@ -1728,7 +1741,7 @@ export class WebGPUEngine extends Engine {
                 debugger;
             }
 
-            this._setInternalTexture(name, internalTexture);
+            this._setInternalTexture(name, internalTexture, baseName, textureIndex);
         } else {
             if (dbgVerboseLogsForFirstFrames) {
                 if (!(this as any)._count || (this as any)._count < dbgVerboseLogsNumFrames) {
@@ -3183,6 +3196,12 @@ export class WebGPUEngine extends Engine {
                     entries.push({
                         binding: j,
                         visibility: WebGPUConstants.ShaderStage.Vertex | WebGPUConstants.ShaderStage.Fragment,
+                        type: bindingDefinition.isComparisonSampler ? WebGPUConstants.BindingType.ComparisonSampler : WebGPUConstants.BindingType.Sampler
+                    });
+                } else if (bindingDefinition.isTexture) {
+                    entries.push({
+                        binding: j,
+                        visibility: WebGPUConstants.ShaderStage.Vertex | WebGPUConstants.ShaderStage.Fragment,
                         type: WebGPUConstants.BindingType.SampledTexture,
                         viewDimension: bindingDefinition.textureDimension,
                         // TODO WEBGPU. Handle texture component type properly.
@@ -3191,14 +3210,8 @@ export class WebGPUEngine extends Engine {
                         // hasDynamicOffset?: boolean;
                         // storageTextureFormat?: GPUTextureFormat;
                         // minBufferBindingSize?: number;
-                    }, {
-                        // TODO WEBGPU. No Magic + 1 (coming from current 1 texture 1 sampler startegy).
-                        binding: j + 1,
-                        visibility: WebGPUConstants.ShaderStage.Vertex | WebGPUConstants.ShaderStage.Fragment,
-                        type: bindingDefinition.isComparisonSampler ? WebGPUConstants.BindingType.ComparisonSampler : WebGPUConstants.BindingType.Sampler
                     });
-                }
-                else {
+                } else {
                     entries.push({
                         binding: j,
                         visibility: WebGPUConstants.ShaderStage.Vertex | WebGPUConstants.ShaderStage.Fragment,
@@ -3350,9 +3363,35 @@ export class WebGPUEngine extends Engine {
                 if (bindingDefinition.isSampler) {
                     const bindingInfo = webgpuPipelineContext.samplers[bindingDefinition.name];
                     if (bindingInfo) {
+                        if (!bindingInfo.sampler) {
+                            const texture = webgpuPipelineContext.textures[bindingInfo.firstTextureName]?.texture;
+                            if (!texture) {
+                                Logger.Error(`Could not create the gpu sampler "${bindingDefinition.name}" because no texture can be looked up for the name "${bindingInfo.firstTextureName}". bindingInfo=${JSON.stringify(bindingInfo)}, webgpuPipelineContext.textures=${webgpuPipelineContext.textures}`);
+                                continue;
+                            }
+                            const hardwareTexture = texture._hardwareTexture as WebGPUHardwareTexture;
+                            if (!hardwareTexture.sampler) {
+                                const samplerDescriptor: GPUSamplerDescriptor = this._getSamplerDescriptor(texture);
+                                const gpuSampler = this._device.createSampler(samplerDescriptor);
+                                hardwareTexture.sampler = gpuSampler;
+                            }
+                            bindingInfo.sampler = hardwareTexture.sampler;
+                        }
+
+                        entries.push({
+                            binding: bindingInfo.samplerBinding,
+                            resource: bindingInfo.sampler,
+                        });
+                    }
+                    else {
+                        Logger.Error(`Sampler "${bindingDefinition.name}" could not be bound. bindingDefinition=${JSON.stringify(bindingDefinition)}`);
+                    }
+                } else if (bindingDefinition.isTexture) {
+                    const bindingInfo = webgpuPipelineContext.textures[bindingDefinition.name];
+                    if (bindingInfo) {
                         if (dbgSanityChecks && bindingInfo.texture === null) {
-                            console.error("Trying to bind a null texture! bindingDefinition=", bindingDefinition, " | bindingInfo=", bindingInfo);
-                            debugger;
+                            Logger.Error(`Trying to bind a null texture! bindingDefinition=${JSON.stringify(bindingDefinition)}, bindingInfo=${JSON.stringify(bindingInfo)}`);
+                            continue;
                         }
                         const hardwareTexture = bindingInfo.texture._hardwareTexture as WebGPUHardwareTexture;
                         if (!hardwareTexture.sampler) {
@@ -3362,29 +3401,25 @@ export class WebGPUEngine extends Engine {
                         }
 
                         if (dbgSanityChecks && !hardwareTexture.view) {
-                            console.error("Trying to bind a null gpu texture! bindingDefinition=", bindingDefinition, " | bindingInfo=", bindingInfo, " | isReady=", bindingInfo.texture.isReady);
-                            debugger;
+                            Logger.Error(`Trying to bind a null gpu texture! bindingDefinition=${JSON.stringify(bindingDefinition)}, bindingInfo=${JSON.stringify(bindingInfo)}, isReady=${bindingInfo.texture.isReady}`);
+                            continue;
                         }
 
                         // TODO WEBGPU remove this code
                         if ((bindingInfo.texture as any)._released) {
                             console.error("Trying to bind a released texture!", bindingInfo.texture, bindingInfo);
-                            debugger;
+                            continue;
                         }
 
                         entries.push({
                             binding: bindingInfo.textureBinding,
                             resource: hardwareTexture.view!,
-                        }, {
-                            binding: bindingInfo.samplerBinding,
-                            resource: hardwareTexture.sampler!,
                         });
                     }
                     else {
-                        Logger.Error("Sampler has not been bound: " + bindingDefinition.name);
+                        Logger.Error(`Texture "${bindingDefinition.name}" could not be bound. bindingDefinition=${JSON.stringify(bindingDefinition)}`);
                     }
-                }
-                else {
+                } else {
                     const dataBuffer = this._uniformsBuffers[bindingDefinition.name];
                     if (dataBuffer) {
                         const webgpuBuffer = dataBuffer.underlyingResource as GPUBuffer;

+ 1 - 1
src/Materials/effect.ts

@@ -871,7 +871,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);
     }
 
     /**