Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/rendering/BuildingRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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;
}

Expand Down
15 changes: 15 additions & 0 deletions src/rendering/InstancedDecorations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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;
}

Expand Down
45 changes: 41 additions & 4 deletions src/rendering/ResourceRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
45 changes: 41 additions & 4 deletions src/rendering/UnitRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down
13 changes: 13 additions & 0 deletions src/rendering/compute/GPUIndirectRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down