File size: 4,203 Bytes
84e8204 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | extends Node
##
## VertexBufferManager - VRAM-aware helpers for building and editing meshes.
##
## Two jobs:
## 1. estimate_cost_mb(): predict a mesh's RAM/VRAM footprint *before* build so
## MemoryManager can decide to evict something else first.
## 2. surface builders that operate on PackedArrays (CPU side) and upload once,
## avoiding per-vertex set_vertex() calls which are catastrophic on mobile.
##
## Mobile rule of thumb used here: ONE glTF/ArrayMesh upload per edit, never a
## stream of tiny updates. Sculpt strokes batch into a dirty region and flush.
##
## Bytes per vertex for the attributes we upload: position(12) + normal(12) + uv(8)
const BYTES_PER_VERTEX := 32
## Extra overhead for indices (~4 bytes/vert on a typical closed mesh).
const INDEX_OVERHEAD := 4
## Conservative VRAM residency multiplier (driver copies, alignment, etc.).
const VRAM_FACTOR := 1.35
## Estimate the RAM+VRAM cost of a mesh with `vertex_count` vertices in MB.
func estimate_cost_mb(vertex_count: int) -> float:
var bytes := float(vertex_count) * float(BYTES_PER_VERTEX + INDEX_OVERHEAD)
return (bytes / 1048576.0) * VRAM_FACTOR
## Build an ArrayMesh from parallel PackedArrays in a single surface upload.
## `arrays` must follow Godot's Mesh.ARRAY_* layout.
func build_surface(vertices: PackedVector3Array, normals: PackedVector3Array,
uvs: PackedVector2Array, indices: PackedInt32Array,
mesh: ArrayMesh = null) -> ArrayMesh:
var out := mesh
if out == null:
out = MeshPool.acquire_mesh()
var arrays: Array = []
arrays.resize(Mesh.ARRAY_MAX)
arrays[Mesh.ARRAY_VERTEX] = vertices
arrays[Mesh.ARRAY_NORMAL] = normals
arrays[Mesh.ARRAY_TEX_UV] = uvs
arrays[Mesh.ARRAY_INDEX] = indices
out.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
return out
## Replace the geometry of an existing mesh in place (single edit surface).
## Keeps the same ArrayMesh object so MeshPool accounting stays correct.
func update_surface(mesh: ArrayMesh, surface_idx: int,
vertices: PackedVector3Array, normals: PackedVector3Array,
uvs: PackedVector2Array, indices: PackedInt32Array) -> void:
mesh.clear_surfaces()
build_surface(vertices, normals, uvs, indices, mesh)
# Recompute AABB for correct culling; without this, culling breaks after
# large edits and the object may vanish from the frustum.
mesh.custom_aabb = _compute_aabb(vertices)
func _compute_aabb(vertices: PackedVector3Array) -> AABB:
if vertices.is_empty():
return AABB()
var mn := vertices[0]
var mx := vertices[0]
for v in vertices:
mn = mn.min(v)
mx = mx.max(v)
return AABB(mn, mx - mn)
## Recompute smooth normals for a triangle soup. Used after sculpting/vertex
## edits where normals must reflect the new shape (otherwise lighting freezes).
func recompute_normals(vertices: PackedVector3Array,
indices: PackedInt32Array) -> PackedVector3Array:
var normals := PackedVector3Array()
normals.resize(vertices.size())
for i in range(normals.size()):
normals[i] = Vector3.ZERO
var tri := indices.size() / 3
for t in range(tri):
var i0 := indices[t * 3]
var i1 := indices[t * 3 + 1]
var i2 := indices[t * 3 + 2]
var n := (vertices[i1] - vertices[i0]).cross(vertices[i2] - vertices[i0])
normals[i0] = normals[i0] + n
normals[i1] = normals[i1] + n
normals[i2] = normals[i2] + n
for i in range(normals.size()):
var n := normals[i]
if n.length_squared() > 0.000001:
normals[i] = n.normalized()
else:
normals[i] = Vector3.UP
return normals
## Convenience: full rebuild + normal recompute + upload in one call.
func commit(vertices: PackedVector3Array, indices: PackedInt32Array,
uvs: PackedVector2Array, mesh: ArrayMesh) -> ArrayMesh:
var normals := recompute_normals(vertices, indices)
update_surface(mesh, 0, vertices, normals, uvs, indices)
return mesh
func debug_vram_mb(meshes: Array) -> float:
var total := 0.0
for m in meshes:
if m is ArrayMesh:
var vcount := 0
for s in range(m.get_surface_count()):
var arr: Array = m.surface_get_arrays(s)
if arr.size() > Mesh.ARRAY_VERTEX and arr[Mesh.ARRAY_VERTEX] != null:
vcount += (arr[Mesh.ARRAY_VERTEX] as PackedVector3Array).size()
total += estimate_cost_mb(vcount)
return total
|