From 9e617625fe363b1cf7b67fa1df4c2f7befad2a4d Mon Sep 17 00:00:00 2001
From: Popov72 <github@evpopov.com>
Date: Mon, 21 Oct 2024 20:01:13 +0200
Subject: [PATCH] WebGPU: Fix collisions in bind group cache (#15722)

* Fix anisotropy value used in the sampler hash code calculation

* Fix collisions in the bind group cache
---
 .../src/Engines/WebGPU/webgpuCacheBindGroups.ts | 17 +++++++++++++++--
 .../src/Engines/WebGPU/webgpuCacheSampler.ts    |  5 ++---
 2 files changed, 17 insertions(+), 5 deletions(-)

diff --git a/packages/dev/core/src/Engines/WebGPU/webgpuCacheBindGroups.ts b/packages/dev/core/src/Engines/WebGPU/webgpuCacheBindGroups.ts
index 63735bff65a..e981c228446 100644
--- a/packages/dev/core/src/Engines/WebGPU/webgpuCacheBindGroups.ts
+++ b/packages/dev/core/src/Engines/WebGPU/webgpuCacheBindGroups.ts
@@ -10,6 +10,19 @@ import type { InternalTexture } from "../../Materials/Textures/internalTexture";
 import type { ExternalTexture } from "../../Materials/Textures/externalTexture";
 import type { WebGPUDrawContext } from "./webgpuDrawContext";
 
+/**
+ * Sampler hash codes are 19 bits long, so using a start value of 2^20 for buffer ids will ensure we can't have any collision with the sampler hash codes
+ */
+const bufferIdStart = 1 << 20;
+
+/**
+ * textureIdStart is added to texture ids to ensure we can't have any collision with the buffer ids / sampler hash codes.
+ * 2^35 for textureIdStart means we can have:
+ * - 2^(35-20) = 2^15 = 32768 possible buffer ids
+ * - 2^(53-35) = 2^18 = 524288 possible texture ids
+ */
+const textureIdStart = 2 ** 35;
+
 class WebGPUBindGroupCacheNode {
     public values: { [id: number]: WebGPUBindGroupCacheNode };
     public bindGroups: GPUBindGroup[];
@@ -94,7 +107,7 @@ export class WebGPUCacheBindGroups {
             }
 
             for (const bufferName of webgpuPipelineContext.shaderProcessingContext.bufferNames) {
-                const uboId = drawContext.buffers[bufferName]?.uniqueId ?? 0;
+                const uboId = (drawContext.buffers[bufferName]?.uniqueId ?? 0) + bufferIdStart;
                 let nextNode = node.values[uboId];
                 if (!nextNode) {
                     nextNode = new WebGPUBindGroupCacheNode();
@@ -114,7 +127,7 @@ export class WebGPUCacheBindGroups {
             }
 
             for (const textureName of webgpuPipelineContext.shaderProcessingContext.textureNames) {
-                const textureId = materialContext.textures[textureName]?.texture?.uniqueId ?? 0;
+                const textureId = (materialContext.textures[textureName]?.texture?.uniqueId ?? 0) + textureIdStart;
                 let nextNode = node.values[textureId];
                 if (!nextNode) {
                     nextNode = new WebGPUBindGroupCacheNode();
diff --git a/packages/dev/core/src/Engines/WebGPU/webgpuCacheSampler.ts b/packages/dev/core/src/Engines/WebGPU/webgpuCacheSampler.ts
index 2236a95c75a..5d02a39a71d 100644
--- a/packages/dev/core/src/Engines/WebGPU/webgpuCacheSampler.ts
+++ b/packages/dev/core/src/Engines/WebGPU/webgpuCacheSampler.ts
@@ -65,7 +65,7 @@ export class WebGPUCacheSampler {
 
     public static GetSamplerHashCode(sampler: TextureSampler): number {
         // The WebGPU spec currently only allows values 1 and 4 for anisotropy
-        const anisotropy = sampler._cachedAnisotropicFilteringLevel && sampler._cachedAnisotropicFilteringLevel > 1 ? 4 : 1;
+        const anisotropy = sampler._cachedAnisotropicFilteringLevel ? sampler._cachedAnisotropicFilteringLevel : 1;
         const code =
             filterToBits[sampler.samplingMode] +
             comparisonFunctionToBits[(sampler._comparisonFunction || 0x0202) - 0x0200 + 1] +
@@ -243,8 +243,7 @@ export class WebGPUCacheSampler {
     }
 
     private static _GetSamplerDescriptor(sampler: TextureSampler, label?: string): GPUSamplerDescriptor {
-        // The WebGPU spec currently only allows values 1 and 4 for anisotropy
-        const anisotropy = sampler.useMipMaps && sampler._cachedAnisotropicFilteringLevel && sampler._cachedAnisotropicFilteringLevel > 1 ? 4 : 1;
+        const anisotropy = sampler.useMipMaps && sampler._cachedAnisotropicFilteringLevel ? sampler._cachedAnisotropicFilteringLevel : 1;
         const filterDescriptor = this._GetSamplerFilterDescriptor(sampler, anisotropy);
         return {
             label,