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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
//! VoxelWorld chunk streaming for VkContext: appends a headroom region to the
//! shared vertex/index buffers, builds the chunk descriptor set from the
//! world's chunk material, then allocates / frees per-chunk geometry from that
//! headroom on demand.
use ash::vk;
use concinnity_core::gfx::mesh_payload::Vertex;
use concinnity_core::gfx::render_types::*;
use concinnity_core::render::backend::ChunkMesh;
use concinnity_core::render::draw_slot;
use concinnity_core::render::error;
use super::super::context::*;
use super::super::texture;
impl VkContext {
// Grow the shared vertex/index buffers by a headroom region for streamed
// `VoxelWorld` chunks and seed the chunk sub-allocators with it. The chunk
// material's texture slots ride each chunk's cull record, so no descriptor
// is baked here.
pub(crate) fn setup_chunk_streaming(
&mut self,
chunk_vtx_bytes: usize,
chunk_idx_bytes: usize,
) -> error::RenderResult<()> {
self.wait_idle();
let old_v = self.geometry.vertex_buffer_bytes;
let old_i = self.geometry.index_buffer_bytes;
let new_v = old_v + chunk_vtx_bytes as u64;
let new_i = old_i + chunk_idx_bytes as u64;
let shared = super::shared_geometry_usage(self.hw.rt_capable);
let new_vbuf = self.hw.alloc.create_buffer(
new_v,
vk::BufferUsageFlags::VERTEX_BUFFER | shared,
vk::MemoryPropertyFlags::DEVICE_LOCAL,
)?;
let new_ibuf = self.hw.alloc.create_buffer(
new_i,
vk::BufferUsageFlags::INDEX_BUFFER | shared,
vk::MemoryPropertyFlags::DEVICE_LOCAL,
)?;
// Copy the build-time geometry into the start of the grown buffers so
// every existing draw's offsets stay valid.
texture::one_shot_submit(
&self.hw.device,
self.commands.command_pool,
self.hw.graphics_queue,
|cmd| {
let vcopy = vk::BufferCopy::default().size(old_v);
let icopy = vk::BufferCopy::default().size(old_i);
// SAFETY: `cmd` is a command buffer in the recording state, and every handle and
// slice these commands name is live for the call.
unsafe {
self.hw.device.cmd_copy_buffer(
cmd,
self.geometry.vertex_buffer.buffer(),
new_vbuf.buffer(),
std::slice::from_ref(&vcopy),
);
self.hw.device.cmd_copy_buffer(
cmd,
self.geometry.index_buffer.buffer(),
new_ibuf.buffer(),
std::slice::from_ref(&icopy),
);
}
},
)?;
self.geometry.vertex_buffer = new_vbuf;
self.geometry.index_buffer = new_ibuf;
self.geometry.vertex_buffer_bytes = new_v;
self.geometry.index_buffer_bytes = new_i;
self.chunk_stream
.vtx_alloc
.free(old_v, chunk_vtx_bytes as u64, 0);
self.chunk_stream
.idx_alloc
.free(old_i, chunk_idx_bytes as u64, 0);
Ok(())
}
// Place one streamed chunk's geometry in the chunk headroom region and
// write its `DrawObject` at the engine-allocated destination slot.
pub(crate) fn add_chunk_mesh(
&mut self,
mesh: ChunkMesh<'_>,
dst: draw_slot::SlotAlloc,
) -> error::RenderResult<()> {
let ChunkMesh {
verts: vertices,
idxs: indices,
model,
texture_slot,
normal_map_slot,
material,
frame,
} = mesh;
if vertices.is_empty() || indices.is_empty() {
return Err(error::RenderError::Other(
"add_chunk_mesh: empty chunk geometry".into(),
));
}
self.chunk_stream.vtx_alloc.reclaim(frame);
self.chunk_stream.idx_alloc.reclaim(frame);
let v_len = std::mem::size_of_val(vertices);
let i_len = indices.len() * std::mem::size_of::<u32>();
let (v_off, i_off) = crate::suballoc::geometry::place_mesh(
&mut self.chunk_stream.vtx_alloc,
&mut self.chunk_stream.idx_alloc,
v_len,
i_len,
|| "add_chunk_mesh".to_string(),
)?;
self.wait_idle();
let vert_bytes = bytemuck::cast_slice(vertices);
self.write_geometry_region(
self.geometry.vertex_buffer.buffer(),
v_off as u64,
vert_bytes,
)?;
let widened: Vec<u32> = indices.iter().map(|&i| u32::from(i)).collect();
let idx_bytes = bytemuck::cast_slice(&widened);
self.write_geometry_region(self.geometry.index_buffer.buffer(), i_off as u64, idx_bytes)?;
let base_vertex = (v_off / std::mem::size_of::<Vertex>()) as i32;
let obj = DrawObject {
vertex_offset: v_off,
vertex_count: vertices.len(),
index_offset: i_off / std::mem::size_of::<u32>(),
index_count: indices.len(),
base_vertex,
geometry_generation: 0,
model,
texture_slot,
normal_map_slot,
material,
visible: true,
resident: true,
bb_min: [f32::NAN; 3],
bb_max: [f32::NAN; 3],
cull_distance: 0.0,
lod_alternates: Vec::new(),
// Streamed chunks render through the world default program.
shader_bucket: 0,
};
// Write at the engine-allocated destination slot.
let draw_idx = match dst {
draw_slot::SlotAlloc::Reuse(slot) => {
self.draw.objects[slot] = obj;
slot
}
draw_slot::SlotAlloc::Append(slot) => {
debug_assert_eq!(
slot,
self.draw.objects.len(),
"appended draw slot must match the draw-object count"
);
self.draw.objects.push(obj);
slot
}
};
// The slot's model-history entry belongs to whatever held it before, so
// a chunk that streams in reprojects through its own transform for one
// frame rather than ghosting from the previous occupant's.
self.model_history.borrow_mut().reoccupy_draw(draw_idx);
// A new resident chunk changes the RT-relevant draw set; the next RT
// update folds it into the BVH (building just this chunk's BLAS).
self.rt.topology_dirty = true;
Ok(())
}
// Free a streamed chunk's geometry region and retire its `DrawObject`
// slot for reuse.
//
// `retire_frame` is `current_frame + frames_in_flight` so an in-flight
// submission never has the freed region overwritten by a later
// `add_chunk_mesh`. The region is not zeroed: a non-resident draw is
// skipped everywhere and an `alloc` hands back exactly `size` bytes that
// `add_chunk_mesh` fully overwrites.
pub(crate) fn remove_chunk_mesh(
&mut self,
draw_idx: usize,
retire_frame: u64,
) -> error::RenderResult<()> {
let region = draw_slot::retire_chunk_slot(&mut self.draw.objects, draw_idx)
.map_err(error::RenderError::Other)?;
self.chunk_stream
.vtx_alloc
.free(region.vertex_offset, region.vertex_bytes, retire_frame);
self.chunk_stream
.idx_alloc
.free(region.index_offset, region.index_bytes, retire_frame);
// The removed chunk leaves the RT-relevant draw set; the next RT update
// drops its BLAS (deferred-freed once in-flight traces retire).
self.rt.topology_dirty = true;
Ok(())
}
pub(crate) fn set_chunk_model(
&mut self,
draw_idx: usize,
model: [[f32; 4]; 4],
) -> error::RenderResult<()> {
draw_slot::set_chunk_model(&mut self.draw.objects, draw_idx, model)
.map_err(error::RenderError::Other)
}
}