feat(render): support full-scene 32-bit indices
This commit is contained in:
@@ -251,7 +251,7 @@ pub fn project_land_msh_to_static_mesh(
|
|||||||
.ok_or(VulkanAssetMeshError::IndexOutOfRange)?,
|
.ok_or(VulkanAssetMeshError::IndexOutOfRange)?,
|
||||||
);
|
);
|
||||||
for face in &terrain.faces {
|
for face in &terrain.faces {
|
||||||
indices.extend(face.vertices);
|
indices.extend(face.vertices.map(u32::from));
|
||||||
}
|
}
|
||||||
if indices.is_empty() {
|
if indices.is_empty() {
|
||||||
return Err(VulkanAssetMeshError::EmptyGeometry);
|
return Err(VulkanAssetMeshError::EmptyGeometry);
|
||||||
@@ -280,7 +280,7 @@ pub fn project_land_msh_to_static_mesh_in_xy_frame(
|
|||||||
.ok_or(VulkanAssetMeshError::IndexOutOfRange)?,
|
.ok_or(VulkanAssetMeshError::IndexOutOfRange)?,
|
||||||
);
|
);
|
||||||
for face in &terrain.faces {
|
for face in &terrain.faces {
|
||||||
indices.extend(face.vertices);
|
indices.extend(face.vertices.map(u32::from));
|
||||||
}
|
}
|
||||||
if indices.is_empty() {
|
if indices.is_empty() {
|
||||||
return Err(VulkanAssetMeshError::EmptyGeometry);
|
return Err(VulkanAssetMeshError::EmptyGeometry);
|
||||||
@@ -357,7 +357,7 @@ pub fn project_land_msh_to_static_mesh_in_world_space(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn static_terrain_indices(terrain: &LandMeshDocument) -> Result<Vec<u16>, VulkanAssetMeshError> {
|
fn static_terrain_indices(terrain: &LandMeshDocument) -> Result<Vec<u32>, VulkanAssetMeshError> {
|
||||||
let mut indices = Vec::with_capacity(
|
let mut indices = Vec::with_capacity(
|
||||||
terrain
|
terrain
|
||||||
.faces
|
.faces
|
||||||
@@ -366,7 +366,7 @@ fn static_terrain_indices(terrain: &LandMeshDocument) -> Result<Vec<u16>, Vulkan
|
|||||||
.ok_or(VulkanAssetMeshError::IndexOutOfRange)?,
|
.ok_or(VulkanAssetMeshError::IndexOutOfRange)?,
|
||||||
);
|
);
|
||||||
for face in &terrain.faces {
|
for face in &terrain.faces {
|
||||||
indices.extend(face.vertices);
|
indices.extend(face.vertices.map(u32::from));
|
||||||
}
|
}
|
||||||
if indices.is_empty() {
|
if indices.is_empty() {
|
||||||
return Err(VulkanAssetMeshError::EmptyGeometry);
|
return Err(VulkanAssetMeshError::EmptyGeometry);
|
||||||
@@ -391,7 +391,7 @@ fn static_terrain_draw_ranges(
|
|||||||
|
|
||||||
fn static_model_indices_and_ranges(
|
fn static_model_indices_and_ranges(
|
||||||
model: &ModelAsset,
|
model: &ModelAsset,
|
||||||
) -> Result<(Vec<u16>, Vec<VulkanStaticDrawRange>), VulkanAssetMeshError> {
|
) -> Result<(Vec<u32>, Vec<VulkanStaticDrawRange>), VulkanAssetMeshError> {
|
||||||
let mut indices = Vec::new();
|
let mut indices = Vec::new();
|
||||||
let mut draw_ranges = Vec::with_capacity(model.batches.len());
|
let mut draw_ranges = Vec::with_capacity(model.batches.len());
|
||||||
for batch in &model.batches {
|
for batch in &model.batches {
|
||||||
@@ -411,7 +411,7 @@ fn static_model_indices_and_ranges(
|
|||||||
.base_vertex
|
.base_vertex
|
||||||
.checked_add(u32::from(raw_index))
|
.checked_add(u32::from(raw_index))
|
||||||
.ok_or(VulkanAssetMeshError::IndexOutOfRange)?;
|
.ok_or(VulkanAssetMeshError::IndexOutOfRange)?;
|
||||||
indices.push(u16::try_from(index).map_err(|_| VulkanAssetMeshError::IndexOutOfRange)?);
|
indices.push(index);
|
||||||
}
|
}
|
||||||
draw_ranges.push(VulkanStaticDrawRange {
|
draw_ranges.push(VulkanStaticDrawRange {
|
||||||
first_index,
|
first_index,
|
||||||
@@ -429,7 +429,7 @@ fn static_model_indices_and_ranges(
|
|||||||
|
|
||||||
fn static_mesh_xy_bounds(
|
fn static_mesh_xy_bounds(
|
||||||
positions: &[[f32; 3]],
|
positions: &[[f32; 3]],
|
||||||
indices: &[u16],
|
indices: &[u32],
|
||||||
) -> Result<(f32, f32, f32, f32), VulkanAssetMeshError> {
|
) -> Result<(f32, f32, f32, f32), VulkanAssetMeshError> {
|
||||||
let mut min_x = f32::INFINITY;
|
let mut min_x = f32::INFINITY;
|
||||||
let mut max_x = f32::NEG_INFINITY;
|
let mut max_x = f32::NEG_INFINITY;
|
||||||
@@ -437,7 +437,7 @@ fn static_mesh_xy_bounds(
|
|||||||
let mut max_y = f32::NEG_INFINITY;
|
let mut max_y = f32::NEG_INFINITY;
|
||||||
for &index in indices {
|
for &index in indices {
|
||||||
let position = positions
|
let position = positions
|
||||||
.get(usize::from(index))
|
.get(usize::try_from(index).map_err(|_| VulkanAssetMeshError::IndexOutOfRange)?)
|
||||||
.ok_or(VulkanAssetMeshError::IndexOutOfRange)?;
|
.ok_or(VulkanAssetMeshError::IndexOutOfRange)?;
|
||||||
if !position.iter().all(|value| value.is_finite()) {
|
if !position.iter().all(|value| value.is_finite()) {
|
||||||
return Err(VulkanAssetMeshError::NonFinitePosition);
|
return Err(VulkanAssetMeshError::NonFinitePosition);
|
||||||
|
|||||||
@@ -236,7 +236,7 @@ pub(super) fn create_static_mesh_index_buffer(
|
|||||||
device: &VulkanLogicalDeviceProbe,
|
device: &VulkanLogicalDeviceProbe,
|
||||||
mesh: &VulkanStaticMesh,
|
mesh: &VulkanStaticMesh,
|
||||||
) -> Result<VulkanAllocatedBuffer, VulkanSmokeRendererError> {
|
) -> Result<VulkanAllocatedBuffer, VulkanSmokeRendererError> {
|
||||||
let mut bytes = Vec::with_capacity(mesh.indices.len() * std::mem::size_of::<u16>());
|
let mut bytes = Vec::with_capacity(mesh.indices.len() * std::mem::size_of::<u32>());
|
||||||
for &index in &mesh.indices {
|
for &index in &mesh.indices {
|
||||||
bytes.extend_from_slice(&index.to_ne_bytes());
|
bytes.extend_from_slice(&index.to_ne_bytes());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -701,7 +701,7 @@ impl VulkanSmokeRenderer {
|
|||||||
command_buffer,
|
command_buffer,
|
||||||
self.index_buffer_ref()?.buffer,
|
self.index_buffer_ref()?.buffer,
|
||||||
0,
|
0,
|
||||||
vk::IndexType::UINT16,
|
vk::IndexType::UINT32,
|
||||||
);
|
);
|
||||||
let clip_from_world = matrix_bytes(self.camera.clip_from_world);
|
let clip_from_world = matrix_bytes(self.camera.clip_from_world);
|
||||||
device.device().cmd_push_constants(
|
device.device().cmd_push_constants(
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ pub struct VulkanStaticMesh {
|
|||||||
/// Vertex data in pipeline order.
|
/// Vertex data in pipeline order.
|
||||||
pub vertices: Vec<VulkanStaticVertex>,
|
pub vertices: Vec<VulkanStaticVertex>,
|
||||||
/// Triangle-list indices into [`Self::vertices`].
|
/// Triangle-list indices into [`Self::vertices`].
|
||||||
pub indices: Vec<u16>,
|
pub indices: Vec<u32>,
|
||||||
/// Source-preserving triangle draw ranges in [`Self::indices`].
|
/// Source-preserving triangle draw ranges in [`Self::indices`].
|
||||||
pub draw_ranges: Vec<VulkanStaticDrawRange>,
|
pub draw_ranges: Vec<VulkanStaticDrawRange>,
|
||||||
}
|
}
|
||||||
@@ -296,11 +296,11 @@ impl VulkanStaticMesh {
|
|||||||
if usize::try_from(expected_first).ok() != Some(self.indices.len()) {
|
if usize::try_from(expected_first).ok() != Some(self.indices.len()) {
|
||||||
return Err("static mesh draw ranges must cover all indices");
|
return Err("static mesh draw ranges must cover all indices");
|
||||||
}
|
}
|
||||||
if self
|
if self.indices.iter().any(|&index| {
|
||||||
.indices
|
usize::try_from(index)
|
||||||
.iter()
|
.ok()
|
||||||
.any(|&index| usize::from(index) >= self.vertices.len())
|
.is_none_or(|index| index >= self.vertices.len())
|
||||||
{
|
}) {
|
||||||
return Err("static mesh index exceeds vertex count");
|
return Err("static mesh index exceeds vertex count");
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -465,28 +465,24 @@ fn append_static_preview_component(
|
|||||||
component: VulkanStaticMesh,
|
component: VulkanStaticMesh,
|
||||||
selector_remap: &[(u16, u16)],
|
selector_remap: &[(u16, u16)],
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let vertex_base = u16::try_from(target.vertices.len()).map_err(|_| {
|
let vertex_base = u32::try_from(target.vertices.len())
|
||||||
"static preview exceeds the available 16-bit vertex index space".to_string()
|
.map_err(|_| "static preview vertex count exceeds u32".to_string())?;
|
||||||
})?;
|
|
||||||
let first_index_base = u32::try_from(target.indices.len())
|
let first_index_base = u32::try_from(target.indices.len())
|
||||||
.map_err(|_| "static preview index count exceeds u32".to_string())?;
|
.map_err(|_| "static preview index count exceeds u32".to_string())?;
|
||||||
target
|
target
|
||||||
.vertices
|
.vertices
|
||||||
.len()
|
.len()
|
||||||
.checked_add(component.vertices.len())
|
.checked_add(component.vertices.len())
|
||||||
.filter(|count| *count <= usize::from(u16::MAX) + 1)
|
.ok_or_else(|| "static preview vertex count exceeds addressable memory".to_string())?;
|
||||||
.ok_or_else(|| {
|
|
||||||
"static preview exceeds the available 16-bit vertex index space".to_string()
|
|
||||||
})?;
|
|
||||||
target.vertices.extend(component.vertices);
|
target.vertices.extend(component.vertices);
|
||||||
target.indices.extend(
|
target.indices.extend(
|
||||||
component
|
component
|
||||||
.indices
|
.indices
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|index| {
|
.map(|index| {
|
||||||
index.checked_add(vertex_base).ok_or_else(|| {
|
index
|
||||||
"static preview exceeds the available 16-bit vertex index space".to_string()
|
.checked_add(vertex_base)
|
||||||
})
|
.ok_or_else(|| "static preview vertex index exceeds u32".to_string())
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>, _>>()?,
|
.collect::<Result<Vec<_>, _>>()?,
|
||||||
);
|
);
|
||||||
@@ -1292,6 +1288,29 @@ mod tests {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn static_preview_component_merge_keeps_indices_above_u16() -> Result<(), String> {
|
||||||
|
let vertex = fparkan_render_vulkan::VulkanStaticVertex {
|
||||||
|
position: [0.0, 0.0, 0.0],
|
||||||
|
color: [1.0, 1.0, 1.0],
|
||||||
|
uv: [0.0, 0.0],
|
||||||
|
};
|
||||||
|
let mut merged = VulkanStaticMesh {
|
||||||
|
vertices: vec![vertex; usize::from(u16::MAX) + 1],
|
||||||
|
indices: Vec::new(),
|
||||||
|
draw_ranges: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
append_static_preview_component(
|
||||||
|
&mut merged,
|
||||||
|
VulkanStaticMesh::smoke_triangle(),
|
||||||
|
&[(0, 0)],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
assert_eq!(merged.indices, vec![65_536, 65_537, 65_538]);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn render_commands_follow_snapshot_order() -> Result<(), String> {
|
fn render_commands_follow_snapshot_order() -> Result<(), String> {
|
||||||
let snapshot = WorldSnapshot {
|
let snapshot = WorldSnapshot {
|
||||||
|
|||||||
@@ -1475,6 +1475,18 @@ is a real original-asset rendering milestone, but not pixel parity or gameplay
|
|||||||
camera parity: it intentionally renders the bounded first preview root and
|
camera parity: it intentionally renders the bounded first preview root and
|
||||||
uses a one-time offline camera capture.
|
uses a one-time offline camera capture.
|
||||||
|
|
||||||
|
Expanding that same preview to all eight AutoDemo mission objects exposed a
|
||||||
|
different, renderer-local limit: the merged static scene crossed 65,535
|
||||||
|
vertices before any Vulkan command was recorded. `VulkanStaticMesh` now uses a
|
||||||
|
32-bit GPU index buffer (`VK_INDEX_TYPE_UINT32`), while source MSH/TerrainFace28
|
||||||
|
indices remain decoded in their original 16-bit representations and are widened
|
||||||
|
only at the static-render bridge. A regression test crosses the former boundary.
|
||||||
|
The full captured-camera AutoDemo preview completed at 13.369 seconds and
|
||||||
|
rendered three 1280×720 native frames: 8 mission objects, 66 mesh components,
|
||||||
|
67 material descriptors, and zero Vulkan validation warnings or errors. It is
|
||||||
|
still a static source-world preview: raw mission orientation, terrain material
|
||||||
|
selection, lighting and animation have not yet been claimed as parity.
|
||||||
|
|
||||||
A fresh no-input launch of the canonical `iron_3d.exe` did create a responsive
|
A fresh no-input launch of the canonical `iron_3d.exe` did create a responsive
|
||||||
window titled `Parkan. Железная Стратегия`. A read-only probe then requested
|
window titled `Parkan. Железная Стратегия`. A read-only probe then requested
|
||||||
`PROCESS_QUERY_INFORMATION | PROCESS_VM_READ` and attempted to read the known
|
`PROCESS_QUERY_INFORMATION | PROCESS_VM_READ` and attempted to read the known
|
||||||
|
|||||||
Reference in New Issue
Block a user