From dd855d8a392e8147059adc5499cfd7c32b587e07 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 30 Jan 2026 16:45:40 +0000 Subject: [PATCH] fix: Prevent WebGPU setIndexBuffer and vertex buffer slot errors - Add cloneGeometryForGPU helper to UnitRenderer and ResourceRenderer - Ensures cloned geometry attributes have needsUpdate=true to force fresh GPU buffer creation - Generates UV coordinates for models missing them (fixes "Vertex buffer slot 1" errors from Tripo/Meshy AI models) - Updates existing cloneGeometryForGPU helpers in BuildingRenderer, InstancedDecorations, and GPUIndirectRenderer with UV generation https://claude.ai/code/session_01RVGDnXRzL5S5tELbcBqYrN --- src/rendering/BuildingRenderer.ts | 15 +++++++ src/rendering/InstancedDecorations.ts | 15 +++++++ src/rendering/ResourceRenderer.ts | 45 ++++++++++++++++++-- src/rendering/UnitRenderer.ts | 45 ++++++++++++++++++-- src/rendering/compute/GPUIndirectRenderer.ts | 13 ++++++ 5 files changed, 125 insertions(+), 8 deletions(-) diff --git a/src/rendering/BuildingRenderer.ts b/src/rendering/BuildingRenderer.ts index 391a0a8d..d0e8e075 100644 --- a/src/rendering/BuildingRenderer.ts +++ b/src/rendering/BuildingRenderer.ts @@ -107,6 +107,7 @@ const MAX_SELECTION_RING_INSTANCES = BUILDING_RENDERER.MAX_SELECTION_RING_INSTAN * Setting needsUpdate on cloned attributes forces WebGPU to create fresh GPU buffers. * Without this, WebGPU may lazily share buffers with the source geometry, which * become invalid when the source is disposed, causing "setIndexBuffer" crashes. + * Also ensures required attributes (like UVs) exist to prevent "Vertex buffer slot" errors. */ function cloneGeometryForGPU(source: THREE.BufferGeometry): THREE.BufferGeometry { const cloned = source.clone(); @@ -121,6 +122,20 @@ function cloneGeometryForGPU(source: THREE.BufferGeometry): THREE.BufferGeometry cloned.index.needsUpdate = true; } + // Ensure UV coordinates exist - required by many shaders (slot 1) + // Some models from Tripo/Meshy AI lack UVs, causing "Vertex buffer slot 1" errors + if (!cloned.attributes.uv && cloned.attributes.position) { + const posCount = cloned.attributes.position.count; + const uvArray = new Float32Array(posCount * 2); + // Generate basic UV coords based on position (simple projection) + const pos = cloned.attributes.position; + for (let i = 0; i < posCount; i++) { + uvArray[i * 2] = pos.getX(i) * 0.5 + 0.5; + uvArray[i * 2 + 1] = pos.getZ(i) * 0.5 + 0.5; + } + cloned.setAttribute('uv', new THREE.BufferAttribute(uvArray, 2)); + } + return cloned; } diff --git a/src/rendering/InstancedDecorations.ts b/src/rendering/InstancedDecorations.ts index f2150525..1680f700 100644 --- a/src/rendering/InstancedDecorations.ts +++ b/src/rendering/InstancedDecorations.ts @@ -30,6 +30,7 @@ const DISTANCE_CULL_MULTIPLIER = DECORATIONS.DISTANCE_CULL_MULTIPLIER; * Setting needsUpdate on cloned attributes forces WebGPU to create fresh GPU buffers. * Without this, WebGPU may lazily share buffers with the source geometry, which * become invalid when the source is disposed, causing "setIndexBuffer" crashes. + * Also ensures required attributes (like UVs) exist to prevent "Vertex buffer slot" errors. */ function cloneGeometryForGPU(source: THREE.BufferGeometry): THREE.BufferGeometry { const cloned = source.clone(); @@ -44,6 +45,20 @@ function cloneGeometryForGPU(source: THREE.BufferGeometry): THREE.BufferGeometry cloned.index.needsUpdate = true; } + // Ensure UV coordinates exist - required by many shaders (slot 1) + // Some models from Tripo/Meshy AI lack UVs, causing "Vertex buffer slot 1" errors + if (!cloned.attributes.uv && cloned.attributes.position) { + const posCount = cloned.attributes.position.count; + const uvArray = new Float32Array(posCount * 2); + // Generate basic UV coords based on position (simple projection) + const pos = cloned.attributes.position; + for (let i = 0; i < posCount; i++) { + uvArray[i * 2] = pos.getX(i) * 0.5 + 0.5; + uvArray[i * 2 + 1] = pos.getZ(i) * 0.5 + 0.5; + } + cloned.setAttribute('uv', new THREE.BufferAttribute(uvArray, 2)); + } + return cloned; } diff --git a/src/rendering/ResourceRenderer.ts b/src/rendering/ResourceRenderer.ts index b0ef7cbd..f2b1895b 100644 --- a/src/rendering/ResourceRenderer.ts +++ b/src/rendering/ResourceRenderer.ts @@ -12,6 +12,43 @@ import { distance } from '@/utils/math'; // NOTE: Resources don't move, so we don't use velocity tracking (AAA optimization) // Velocity node returns zero for meshes without velocity attributes +/** + * Clone geometry with proper GPU buffer initialization for WebGPU. + * Setting needsUpdate on cloned attributes forces WebGPU to create fresh GPU buffers. + * Without this, WebGPU may lazily share buffers with the source geometry, which + * become invalid when the source is disposed, causing "setIndexBuffer" crashes. + * Also ensures required attributes (like UVs) exist to prevent "Vertex buffer slot" errors. + */ +function cloneGeometryForGPU(source: THREE.BufferGeometry): THREE.BufferGeometry { + const cloned = source.clone(); + + // Mark all attributes as needing GPU buffer upload + for (const name of Object.keys(cloned.attributes)) { + cloned.attributes[name].needsUpdate = true; + } + + // Mark index buffer as needing GPU buffer upload if present + if (cloned.index) { + cloned.index.needsUpdate = true; + } + + // Ensure UV coordinates exist - required by many shaders (slot 1) + // Some models from Tripo/Meshy AI lack UVs, causing "Vertex buffer slot 1" errors + if (!cloned.attributes.uv && cloned.attributes.position) { + const posCount = cloned.attributes.position.count; + const uvArray = new Float32Array(posCount * 2); + // Generate basic UV coords based on position (simple projection) + const pos = cloned.attributes.position; + for (let i = 0; i < posCount; i++) { + uvArray[i * 2] = pos.getX(i) * 0.5 + 0.5; + uvArray[i * 2 + 1] = pos.getZ(i) * 0.5 + 0.5; + } + cloned.setAttribute('uv', new THREE.BufferAttribute(uvArray, 2)); + } + + return cloned; +} + interface InstancedResourceGroup { mesh: THREE.InstancedMesh; resourceType: string; @@ -194,10 +231,10 @@ export class ResourceRenderer { // Extract geometry, material, and transform info from first mesh baseMesh.traverse((child: THREE.Object3D) => { if (child instanceof THREE.Mesh && !geometry) { - // Clone geometry to avoid sharing disposal lifecycle with asset cache. - // Without cloning, disposing this mesh would invalidate GPU buffers - // still used by other meshes, causing WebGPU "setIndexBuffer" crashes. - geometry = child.geometry.clone(); + // Clone geometry with proper GPU buffer initialization. + // This avoids sharing disposal lifecycle with asset cache and + // ensures required attributes (UVs) exist for WebGPU shaders. + geometry = cloneGeometryForGPU(child.geometry); material = child.material; // Get world quaternion to extract X/Z rotations that stand the model upright child.getWorldQuaternion(tempQuat); diff --git a/src/rendering/UnitRenderer.ts b/src/rendering/UnitRenderer.ts index 8cacdd4d..08f6adf4 100644 --- a/src/rendering/UnitRenderer.ts +++ b/src/rendering/UnitRenderer.ts @@ -35,6 +35,43 @@ import { CullingService, EntityCategory } from './services/CullingService'; import { LODConfig } from './compute/UnifiedCullingCompute'; import { GPUIndirectRenderer } from './compute/GPUIndirectRenderer'; +/** + * Clone geometry with proper GPU buffer initialization for WebGPU. + * Setting needsUpdate on cloned attributes forces WebGPU to create fresh GPU buffers. + * Without this, WebGPU may lazily share buffers with the source geometry, which + * become invalid when the source is disposed, causing "setIndexBuffer" crashes. + * Also ensures required attributes (like UVs) exist to prevent "Vertex buffer slot" errors. + */ +function cloneGeometryForGPU(source: THREE.BufferGeometry): THREE.BufferGeometry { + const cloned = source.clone(); + + // Mark all attributes as needing GPU buffer upload + for (const name of Object.keys(cloned.attributes)) { + cloned.attributes[name].needsUpdate = true; + } + + // Mark index buffer as needing GPU buffer upload if present + if (cloned.index) { + cloned.index.needsUpdate = true; + } + + // Ensure UV coordinates exist - required by many shaders (slot 1) + // Some models from Tripo/Meshy AI lack UVs, causing "Vertex buffer slot 1" errors + if (!cloned.attributes.uv && cloned.attributes.position) { + const posCount = cloned.attributes.position.count; + const uvArray = new Float32Array(posCount * 2); + // Generate basic UV coords based on position (simple projection) + const pos = cloned.attributes.position; + for (let i = 0; i < posCount; i++) { + uvArray[i * 2] = pos.getX(i) * 0.5 + 0.5; + uvArray[i * 2 + 1] = pos.getZ(i) * 0.5 + 0.5; + } + cloned.setAttribute('uv', new THREE.BufferAttribute(uvArray, 2)); + } + + return cloned; +} + // Instance data for a single unit type + player combo at a specific LOD level interface InstancedUnitGroup { mesh: THREE.InstancedMesh; @@ -765,10 +802,10 @@ export class UnitRenderer { baseMesh.traverse((child) => { if (child instanceof THREE.Mesh && !geometry) { - // Clone geometry to avoid sharing disposal lifecycle with asset cache. - // Without cloning, disposing this mesh would invalidate GPU buffers - // still used by other meshes, causing WebGPU "setIndexBuffer" crashes. - geometry = child.geometry.clone(); + // Clone geometry with proper GPU buffer initialization. + // This avoids sharing disposal lifecycle with asset cache and + // ensures required attributes (UVs) exist for WebGPU shaders. + geometry = cloneGeometryForGPU(child.geometry); material = child.material; // Get the mesh's world position Y - this is lost when extracting geometry const worldPos = new THREE.Vector3(); diff --git a/src/rendering/compute/GPUIndirectRenderer.ts b/src/rendering/compute/GPUIndirectRenderer.ts index a39be1b7..a42fe295 100644 --- a/src/rendering/compute/GPUIndirectRenderer.ts +++ b/src/rendering/compute/GPUIndirectRenderer.ts @@ -212,6 +212,19 @@ export class GPUIndirectRenderer { instancedGeometry.setIndex(clonedIndex); } + // Ensure UV coordinates exist - required by many shaders (slot 1) + // Some models from Tripo/Meshy AI lack UVs, causing "Vertex buffer slot 1" errors + if (!instancedGeometry.attributes.uv && instancedGeometry.attributes.position) { + const posCount = instancedGeometry.attributes.position.count; + const uvArray = new Float32Array(posCount * 2); + const pos = instancedGeometry.attributes.position; + for (let i = 0; i < posCount; i++) { + uvArray[i * 2] = pos.getX(i) * 0.5 + 0.5; + uvArray[i * 2 + 1] = pos.getZ(i) * 0.5 + 0.5; + } + instancedGeometry.setAttribute('uv', new THREE.BufferAttribute(uvArray, 2)); + } + // Create per-instance position offset attribute (vec3) // This is simpler than full mat4 transforms and works with WebGPU const instanceOffsetData = new Float32Array(MAX_UNITS * 3);