feat(vulkan): draw original terrain geometry
This commit is contained in:
Generated
+1
@@ -565,6 +565,7 @@ dependencies = [
|
|||||||
"fparkan-msh",
|
"fparkan-msh",
|
||||||
"fparkan-platform",
|
"fparkan-platform",
|
||||||
"fparkan-render",
|
"fparkan-render",
|
||||||
|
"fparkan-terrain-format",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ Open source проект с реализацией компонентов игр
|
|||||||
|
|
||||||
- `fparkan-vulkan-smoke` доказывает живой Stage 0 Vulkan triangle path с native window, swapchain и validation telemetry.
|
- `fparkan-vulkan-smoke` доказывает живой Stage 0 Vulkan triangle path с native window, swapchain и validation telemetry.
|
||||||
- `VulkanPlanningBackend` и `fparkan-game` подтверждают только deterministic command planning/capture, а не draw пикселей.
|
- `VulkanPlanningBackend` и `fparkan-game` подтверждают только deterministic command planning/capture, а не draw пикселей.
|
||||||
- `fparkan-viewer` пока является инспектором ассетов. Stage 3 GPU vertical slice для оригинального `MSH`/`Texm`/`WEAR`/`MAT0`/terrain еще не закрыт.
|
- `fparkan-viewer` пока является инспектором ассетов. `fparkan-vulkan-smoke` имеет live Stage 3 bridge для original `MSH`/`Texm`/`WEAR`/`MAT0` и geometry-only `Land.msh`; полноценный viewer, исходные terrain-material states, camera и pixel parity ещё не закрыты.
|
||||||
- Truth table и evidence-артефакты вынесены в [`docs/rendering/renderer_truth_table.md`](docs/rendering/renderer_truth_table.md) и [`docs/evidence/`](docs/evidence).
|
- Truth table и evidence-артефакты вынесены в [`docs/rendering/renderer_truth_table.md`](docs/rendering/renderer_truth_table.md) и [`docs/evidence/`](docs/evidence).
|
||||||
|
|
||||||
## Тестирование
|
## Тестирование
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ fparkan-binary = { path = "../../crates/fparkan-binary", version = "0.1.0" }
|
|||||||
fparkan-msh = { path = "../../crates/fparkan-msh", version = "0.1.0" }
|
fparkan-msh = { path = "../../crates/fparkan-msh", version = "0.1.0" }
|
||||||
fparkan-platform = { path = "../../crates/fparkan-platform", version = "0.1.0" }
|
fparkan-platform = { path = "../../crates/fparkan-platform", version = "0.1.0" }
|
||||||
fparkan-render = { path = "../../crates/fparkan-render", version = "0.1.0" }
|
fparkan-render = { path = "../../crates/fparkan-render", version = "0.1.0" }
|
||||||
|
fparkan-terrain-format = { path = "../../crates/fparkan-terrain-format", version = "0.1.0" }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
use crate::{VulkanStaticDrawRange, VulkanStaticMesh, VulkanStaticVertex};
|
use crate::{VulkanStaticDrawRange, VulkanStaticMesh, VulkanStaticVertex};
|
||||||
use fparkan_msh::ModelAsset;
|
use fparkan_msh::ModelAsset;
|
||||||
use fparkan_render::LegacyPipelineState;
|
use fparkan_render::LegacyPipelineState;
|
||||||
|
use fparkan_terrain_format::LandMeshDocument;
|
||||||
|
|
||||||
/// Error returned when a validated MSH cannot enter the current static GPU path.
|
/// Error returned when a validated MSH cannot enter the current static GPU path.
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
@@ -132,10 +133,108 @@ pub fn project_msh_to_static_mesh(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Projects validated `Land.msh` terrain geometry into the static Vulkan path.
|
||||||
|
///
|
||||||
|
/// This bridge preserves the source triangle order from `TerrainFace28` and
|
||||||
|
/// consumes its validated position and UV0 streams. It is deliberately a
|
||||||
|
/// geometry-only terrain slice: source slot/material selection, camera
|
||||||
|
/// transforms, fog and surface-mask shading need their own evidence before
|
||||||
|
/// they can be modeled as renderer state.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns [`VulkanAssetMeshError`] if the terrain has no triangles, contains
|
||||||
|
/// non-finite positions, has no usable XZ extent, or references data outside
|
||||||
|
/// the current static Vulkan input contract.
|
||||||
|
pub fn project_land_msh_to_static_mesh(
|
||||||
|
terrain: &LandMeshDocument,
|
||||||
|
) -> Result<VulkanStaticMesh, VulkanAssetMeshError> {
|
||||||
|
let mut indices = Vec::with_capacity(
|
||||||
|
terrain
|
||||||
|
.faces
|
||||||
|
.len()
|
||||||
|
.checked_mul(3)
|
||||||
|
.ok_or(VulkanAssetMeshError::IndexOutOfRange)?,
|
||||||
|
);
|
||||||
|
for face in &terrain.faces {
|
||||||
|
indices.extend(face.vertices);
|
||||||
|
}
|
||||||
|
if indices.is_empty() {
|
||||||
|
return Err(VulkanAssetMeshError::EmptyGeometry);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (min_x, max_x, min_z, max_z) = static_mesh_xz_bounds(&terrain.positions, &indices)?;
|
||||||
|
let extent = (max_x - min_x).max(max_z - min_z);
|
||||||
|
if !extent.is_finite() || extent <= f32::EPSILON {
|
||||||
|
return Err(VulkanAssetMeshError::DegenerateViewExtent);
|
||||||
|
}
|
||||||
|
let center_x = (min_x + max_x) * 0.5;
|
||||||
|
let center_z = (min_z + max_z) * 0.5;
|
||||||
|
let scale = 1.6 / extent;
|
||||||
|
let vertices = terrain
|
||||||
|
.positions
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, position)| VulkanStaticVertex {
|
||||||
|
position: [
|
||||||
|
(position[0] - center_x) * scale,
|
||||||
|
(position[2] - center_z) * scale,
|
||||||
|
],
|
||||||
|
color: [0.31, 0.58, 0.27],
|
||||||
|
uv: terrain.uv0.get(index).map_or(
|
||||||
|
[
|
||||||
|
(position[0] - center_x) / extent + 0.5,
|
||||||
|
(position[2] - center_z) / extent + 0.5,
|
||||||
|
],
|
||||||
|
|uv| [f32::from(uv[0]) / 1024.0, f32::from(uv[1]) / 1024.0],
|
||||||
|
),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(VulkanStaticMesh {
|
||||||
|
vertices,
|
||||||
|
indices,
|
||||||
|
draw_ranges: vec![VulkanStaticDrawRange {
|
||||||
|
first_index: 0,
|
||||||
|
index_count: u32::try_from(terrain.faces.len())
|
||||||
|
.map_err(|_| VulkanAssetMeshError::IndexOutOfRange)?
|
||||||
|
.checked_mul(3)
|
||||||
|
.ok_or(VulkanAssetMeshError::IndexOutOfRange)?,
|
||||||
|
material_index: 0,
|
||||||
|
pipeline_state: LegacyPipelineState::default(),
|
||||||
|
alpha_test_reference: 0,
|
||||||
|
}],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn static_mesh_xz_bounds(
|
||||||
|
positions: &[[f32; 3]],
|
||||||
|
indices: &[u16],
|
||||||
|
) -> Result<(f32, f32, f32, f32), VulkanAssetMeshError> {
|
||||||
|
let mut min_x = f32::INFINITY;
|
||||||
|
let mut max_x = f32::NEG_INFINITY;
|
||||||
|
let mut min_z = f32::INFINITY;
|
||||||
|
let mut max_z = f32::NEG_INFINITY;
|
||||||
|
for &index in indices {
|
||||||
|
let position = positions
|
||||||
|
.get(usize::from(index))
|
||||||
|
.ok_or(VulkanAssetMeshError::IndexOutOfRange)?;
|
||||||
|
if !position.iter().all(|value| value.is_finite()) {
|
||||||
|
return Err(VulkanAssetMeshError::NonFinitePosition);
|
||||||
|
}
|
||||||
|
min_x = min_x.min(position[0]);
|
||||||
|
max_x = max_x.max(position[0]);
|
||||||
|
min_z = min_z.min(position[2]);
|
||||||
|
max_z = max_z.max(position[2]);
|
||||||
|
}
|
||||||
|
Ok((min_x, max_x, min_z, max_z))
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use fparkan_msh::{Batch, ModelAsset};
|
use fparkan_msh::{Batch, ModelAsset};
|
||||||
|
use fparkan_terrain_format::{FullSurfaceMask, TerrainFace28, TerrainSlotTable};
|
||||||
|
|
||||||
fn model(positions: Vec<[f32; 3]>, indices: Vec<u16>, batches: Vec<Batch>) -> ModelAsset {
|
fn model(positions: Vec<[f32; 3]>, indices: Vec<u16>, batches: Vec<Batch>) -> ModelAsset {
|
||||||
ModelAsset {
|
ModelAsset {
|
||||||
@@ -247,4 +346,50 @@ mod tests {
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn projects_land_faces_in_source_order_with_packed_uv0() {
|
||||||
|
let terrain = LandMeshDocument {
|
||||||
|
streams: Vec::new(),
|
||||||
|
nodes_raw: Vec::new(),
|
||||||
|
slots: TerrainSlotTable {
|
||||||
|
header_raw: Vec::new(),
|
||||||
|
slots_raw: Vec::new(),
|
||||||
|
},
|
||||||
|
positions: vec![
|
||||||
|
[-2.0, 5.0, -1.0],
|
||||||
|
[2.0, 3.0, -1.0],
|
||||||
|
[-2.0, 9.0, 3.0],
|
||||||
|
[2.0, 1.0, 3.0],
|
||||||
|
],
|
||||||
|
normals: Vec::new(),
|
||||||
|
uv0: vec![[1024, -512], [0, 2048], [-1024, 512], [512, 0]],
|
||||||
|
accelerator: Vec::new(),
|
||||||
|
aux14: Vec::new(),
|
||||||
|
aux18: Vec::new(),
|
||||||
|
faces: vec![terrain_face([0, 1, 2]), terrain_face([1, 3, 2])],
|
||||||
|
};
|
||||||
|
|
||||||
|
let mesh = project_land_msh_to_static_mesh(&terrain).expect("representable terrain");
|
||||||
|
|
||||||
|
assert_eq!(mesh.indices, vec![0, 1, 2, 1, 3, 2]);
|
||||||
|
assert_eq!(mesh.draw_ranges.len(), 1);
|
||||||
|
assert_eq!(mesh.draw_ranges[0].index_count, 6);
|
||||||
|
assert_eq!(mesh.vertices[0].position, [-0.8, -0.8]);
|
||||||
|
assert_eq!(mesh.vertices[3].position, [0.8, 0.8]);
|
||||||
|
assert_eq!(mesh.vertices[0].uv, [1.0, -0.5]);
|
||||||
|
assert_eq!(mesh.vertices[2].uv, [-1.0, 0.5]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn terrain_face(vertices: [u16; 3]) -> TerrainFace28 {
|
||||||
|
TerrainFace28 {
|
||||||
|
flags: FullSurfaceMask(0),
|
||||||
|
material_tag: 0,
|
||||||
|
aux_tag: 0,
|
||||||
|
vertices,
|
||||||
|
neighbors: [None; 3],
|
||||||
|
tail_raw: [0; 8],
|
||||||
|
raw: [0; 28],
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,9 @@ mod swapchain;
|
|||||||
mod swapchain_resources;
|
mod swapchain_resources;
|
||||||
mod validation;
|
mod validation;
|
||||||
|
|
||||||
pub use self::asset_mesh::{project_msh_to_static_mesh, VulkanAssetMeshError};
|
pub use self::asset_mesh::{
|
||||||
|
project_land_msh_to_static_mesh, project_msh_to_static_mesh, VulkanAssetMeshError,
|
||||||
|
};
|
||||||
pub use self::capabilities::{
|
pub use self::capabilities::{
|
||||||
probe_vulkan_runtime_capabilities, probe_vulkan_runtime_capabilities_for_request,
|
probe_vulkan_runtime_capabilities, probe_vulkan_runtime_capabilities_for_request,
|
||||||
VulkanRuntimeCapabilityError, VulkanRuntimeCapabilityProbe,
|
VulkanRuntimeCapabilityError, VulkanRuntimeCapabilityProbe,
|
||||||
|
|||||||
@@ -14,10 +14,10 @@
|
|||||||
use fparkan_platform::RenderRequest;
|
use fparkan_platform::RenderRequest;
|
||||||
use fparkan_platform_winit::{window_native_handles, WinitWindowPlan};
|
use fparkan_platform_winit::{window_native_handles, WinitWindowPlan};
|
||||||
use fparkan_render_vulkan::{
|
use fparkan_render_vulkan::{
|
||||||
project_msh_to_static_mesh, VulkanSmokeBootstrapProgress, VulkanSmokeFrameOutcome,
|
project_land_msh_to_static_mesh, project_msh_to_static_mesh, VulkanSmokeBootstrapProgress,
|
||||||
VulkanSmokeRenderer, VulkanSmokeRendererCreateInfo, VulkanSmokeRendererReport,
|
VulkanSmokeFrameOutcome, VulkanSmokeRenderer, VulkanSmokeRendererCreateInfo,
|
||||||
VulkanSmokeShutdownReport, VulkanStaticMaterial, VulkanStaticMesh, VulkanStaticTexture,
|
VulkanSmokeRendererReport, VulkanSmokeShutdownReport, VulkanStaticMaterial, VulkanStaticMesh,
|
||||||
VulkanValidationReport,
|
VulkanStaticTexture, VulkanValidationReport,
|
||||||
};
|
};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
@@ -179,6 +179,9 @@ enum MeshInput {
|
|||||||
archive: String,
|
archive: String,
|
||||||
name: String,
|
name: String,
|
||||||
},
|
},
|
||||||
|
LandMsh {
|
||||||
|
path: PathBuf,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MeshInput {
|
impl MeshInput {
|
||||||
@@ -186,13 +189,14 @@ impl MeshInput {
|
|||||||
match self {
|
match self {
|
||||||
Self::SmokeTriangle => "synthetic-smoke-triangle",
|
Self::SmokeTriangle => "synthetic-smoke-triangle",
|
||||||
Self::Msh { .. } => "original-msh",
|
Self::Msh { .. } => "original-msh",
|
||||||
|
Self::LandMsh { .. } => "original-land-msh",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn archive(&self) -> &str {
|
fn archive(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
Self::SmokeTriangle => "",
|
|
||||||
Self::Msh { archive, .. } => archive,
|
Self::Msh { archive, .. } => archive,
|
||||||
|
Self::SmokeTriangle | Self::LandMsh { .. } => "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,6 +204,7 @@ impl MeshInput {
|
|||||||
match self {
|
match self {
|
||||||
Self::SmokeTriangle => "",
|
Self::SmokeTriangle => "",
|
||||||
Self::Msh { name, .. } => name,
|
Self::Msh { name, .. } => name,
|
||||||
|
Self::LandMsh { .. } => "Land.msh",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -214,6 +219,7 @@ impl SmokeOptions {
|
|||||||
let mut model_root = None;
|
let mut model_root = None;
|
||||||
let mut model_archive = None;
|
let mut model_archive = None;
|
||||||
let mut model_name = None;
|
let mut model_name = None;
|
||||||
|
let mut terrain_path = None;
|
||||||
let mut texture_root = None;
|
let mut texture_root = None;
|
||||||
let mut texture_archive = None;
|
let mut texture_archive = None;
|
||||||
let mut texture_name = None;
|
let mut texture_name = None;
|
||||||
@@ -273,6 +279,13 @@ impl SmokeOptions {
|
|||||||
.ok_or_else(|| "--model-name requires a value".to_string())?,
|
.ok_or_else(|| "--model-name requires a value".to_string())?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
"--terrain-path" => {
|
||||||
|
terrain_path = Some(
|
||||||
|
iter.next()
|
||||||
|
.map(PathBuf::from)
|
||||||
|
.ok_or_else(|| "--terrain-path requires a path".to_string())?,
|
||||||
|
);
|
||||||
|
}
|
||||||
"--texture-root" => {
|
"--texture-root" => {
|
||||||
texture_root = Some(
|
texture_root = Some(
|
||||||
iter.next()
|
iter.next()
|
||||||
@@ -335,19 +348,21 @@ impl SmokeOptions {
|
|||||||
if timeout_seconds == 0 {
|
if timeout_seconds == 0 {
|
||||||
return Err("native smoke requires --timeout-seconds >= 1".to_string());
|
return Err("native smoke requires --timeout-seconds >= 1".to_string());
|
||||||
}
|
}
|
||||||
let mesh_input = match (model_root, model_archive, model_name) {
|
let mesh_input = match (terrain_path, model_root, model_archive, model_name) {
|
||||||
(None, None, None) => MeshInput::SmokeTriangle,
|
(Some(path), None, None, None) => MeshInput::LandMsh { path },
|
||||||
(Some(root), Some(archive), Some(name)) => MeshInput::Msh {
|
(None, None, None, None) => MeshInput::SmokeTriangle,
|
||||||
|
(None, Some(root), Some(archive), Some(name)) => MeshInput::Msh {
|
||||||
root,
|
root,
|
||||||
archive,
|
archive,
|
||||||
name,
|
name,
|
||||||
},
|
},
|
||||||
_ => {
|
(None, _, _, _) => {
|
||||||
return Err(
|
return Err(
|
||||||
"--model-root, --model-archive and --model-name must be supplied together"
|
"--model-root, --model-archive and --model-name must be supplied together"
|
||||||
.to_string(),
|
.to_string(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
_ => return Err("--terrain-path cannot be combined with --model-*".to_string()),
|
||||||
};
|
};
|
||||||
let texture_input = match (texture_root, texture_archive, texture_name) {
|
let texture_input = match (texture_root, texture_archive, texture_name) {
|
||||||
(None, None, None) => None,
|
(None, None, None) => None,
|
||||||
@@ -414,6 +429,10 @@ impl SmokeOptions {
|
|||||||
let model = fparkan_inspection::load_model_from_root(root, archive, name)?;
|
let model = fparkan_inspection::load_model_from_root(root, archive, name)?;
|
||||||
project_msh_to_static_mesh(&model).map_err(|err| err.to_string())
|
project_msh_to_static_mesh(&model).map_err(|err| err.to_string())
|
||||||
}
|
}
|
||||||
|
MeshInput::LandMsh { path } => {
|
||||||
|
let terrain = fparkan_inspection::load_land_msh_from_path(path)?;
|
||||||
|
project_land_msh_to_static_mesh(&terrain).map_err(|err| err.to_string())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1487,6 +1506,24 @@ mod tests {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parses_original_land_msh_as_direct_terrain_input() {
|
||||||
|
let parsed = SmokeOptions::parse(&[
|
||||||
|
"--out".to_string(),
|
||||||
|
"target/report.json".to_string(),
|
||||||
|
"--terrain-path".to_string(),
|
||||||
|
"C:/GOG/DATA/MAPS/11/Land.msh".to_string(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
parsed,
|
||||||
|
Ok(SmokeOptions {
|
||||||
|
mesh_input: MeshInput::LandMsh { path },
|
||||||
|
..
|
||||||
|
}) if path == std::path::Path::new("C:/GOG/DATA/MAPS/11/Land.msh")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rejects_partial_original_msh_input() {
|
fn rejects_partial_original_msh_input() {
|
||||||
let parsed = SmokeOptions::parse(&[
|
let parsed = SmokeOptions::parse(&[
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ use fparkan_nres::{decode as decode_nres, NresDocument, ReadProfile};
|
|||||||
use fparkan_path::{normalize_relative, PathPolicy};
|
use fparkan_path::{normalize_relative, PathPolicy};
|
||||||
use fparkan_resource::{archive_path, resource_name, CachedResourceRepository, ResourceRepository};
|
use fparkan_resource::{archive_path, resource_name, CachedResourceRepository, ResourceRepository};
|
||||||
use fparkan_rsli::decode as decode_rsli;
|
use fparkan_rsli::decode as decode_rsli;
|
||||||
use fparkan_terrain_format::{decode_land_map, decode_land_msh};
|
use fparkan_terrain_format::{decode_land_map, decode_land_msh, LandMeshDocument};
|
||||||
use fparkan_texm::decode_texm;
|
use fparkan_texm::decode_texm;
|
||||||
use fparkan_vfs::{DirectoryVfs, Vfs};
|
use fparkan_vfs::{DirectoryVfs, Vfs};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
@@ -437,6 +437,19 @@ pub fn inspect_land_file(path: &Path, kind: LandFileKind) -> Result<MapInspectio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Loads and validates a standalone `Land.msh` file.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns a human-readable error if the file cannot be read, decoded as `NRes`,
|
||||||
|
/// or decoded as the specialized terrain mesh format.
|
||||||
|
pub fn load_land_msh_from_path(path: &Path) -> Result<LandMeshDocument, String> {
|
||||||
|
let bytes = fs::read(path).map_err(|err| format!("{}: {err}", path.display()))?;
|
||||||
|
let document = decode_nres(Arc::from(bytes.into_boxed_slice()), ReadProfile::Compatible)
|
||||||
|
.map_err(|err| err.to_string())?;
|
||||||
|
decode_land_msh(&document).map_err(|err| err.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
fn inspect_land_msh(document: &NresDocument) -> Result<MapInspection, String> {
|
fn inspect_land_msh(document: &NresDocument) -> Result<MapInspection, String> {
|
||||||
let land_msh = decode_land_msh(document).map_err(|err| err.to_string())?;
|
let land_msh = decode_land_msh(document).map_err(|err| err.to_string())?;
|
||||||
Ok(MapInspection {
|
Ok(MapInspection {
|
||||||
|
|||||||
@@ -859,6 +859,15 @@ dynamic material data и не входит в `PipelineKey`. Также не у
|
|||||||
дополнительного дизассемблирования. Это частично реализованная compatibility
|
дополнительного дизассемблирования. Это частично реализованная compatibility
|
||||||
boundary, а не заявление о готовой parity fixed-function state.
|
boundary, а не заявление о готовой parity fixed-function state.
|
||||||
|
|
||||||
|
`Land.msh` использует отдельный geometry-only bridge: validated `TerrainFace28`
|
||||||
|
сохраняет source triangle order, а его positions и packed UV0 попадают в тот же
|
||||||
|
static vertex/index upload path. Для текущего диагностического viewer XZ bounds
|
||||||
|
нормализуются в clip-space, packed UV0 декодируется как signed `int16 / 1024`.
|
||||||
|
Один static draw range намеренно не трактует `material_tag`, surface mask, slots
|
||||||
|
или auxiliary streams как material/pipeline state: их связь с original terrain
|
||||||
|
renderer пока не доказана. GOG `DATA/MAPS/11/Land.msh` дал 6 458 vertices и
|
||||||
|
24 672 indices в 300-frame native Vulkan run без validation warning/error.
|
||||||
|
|
||||||
Выбор depth/stencil attachment отделён от renderer lifetime:
|
Выбор depth/stencil attachment отделён от renderer lifetime:
|
||||||
`select_depth_stencil_attachment_format` применяет тот же фиксированный порядок
|
`select_depth_stencil_attachment_format` применяет тот же фиксированный порядок
|
||||||
форматов, что и capability gate, к фактически поддерживаемому списку GPU.
|
форматов, что и capability gate, к фактически поддерживаемому списку GPU.
|
||||||
|
|||||||
@@ -299,7 +299,7 @@ S3-VK-TEXM-UPLOAD-001 covered-gpu cargo run -p fparkan-vulkan-smoke --release --
|
|||||||
S3-VK-DESCRIPTOR-001 covered-gpu cargo run -p fparkan-vulkan-smoke --release --locked -- --out target/fparkan/stage3-texm-sampled.json --frames 300 --resize-frame 120 --timeout-seconds 90 --model-root <GOG_ROOT> --model-archive system.rlb --model-name MTCHECK.MSH --texture-root <GOG_ROOT> --texture-archive Textures.lib --texture-name DEFAULT.0; original TEXM is bound as set=0,binding=0 combined image sampler and sampled in fragment shader; validation_warning_count=0 and validation_error_count=0
|
S3-VK-DESCRIPTOR-001 covered-gpu cargo run -p fparkan-vulkan-smoke --release --locked -- --out target/fparkan/stage3-texm-sampled.json --frames 300 --resize-frame 120 --timeout-seconds 90 --model-root <GOG_ROOT> --model-archive system.rlb --model-name MTCHECK.MSH --texture-root <GOG_ROOT> --texture-archive Textures.lib --texture-name DEFAULT.0; original TEXM is bound as set=0,binding=0 combined image sampler and sampled in fragment shader; validation_warning_count=0 and validation_error_count=0
|
||||||
S3-VK-PIPELINE-001 covered-gpu cargo run -p fparkan-vulkan-smoke --release --locked -- --out target/fparkan/stage3-alpha-test-smoke.json --frames 300 --resize-frame 120 --timeout-seconds 90 --model-root <GOG_ROOT> --model-archive system.rlb --model-name MTCHECK.MSH --texture-root <GOG_ROOT> --texture-archive Textures.lib --texture-name DEFAULT.0; PipelineKey selects a deduplicated live Vulkan pipeline per static draw range; capability-selected depth attachment supports TestWrite/TestReadOnly; fragment alpha test receives per-range dynamic cutoff through push constants; baseline key is live-proven, while Batch20/MAT0 state/reference mapping remains an evidence gap
|
S3-VK-PIPELINE-001 covered-gpu cargo run -p fparkan-vulkan-smoke --release --locked -- --out target/fparkan/stage3-alpha-test-smoke.json --frames 300 --resize-frame 120 --timeout-seconds 90 --model-root <GOG_ROOT> --model-archive system.rlb --model-name MTCHECK.MSH --texture-root <GOG_ROOT> --texture-archive Textures.lib --texture-name DEFAULT.0; PipelineKey selects a deduplicated live Vulkan pipeline per static draw range; capability-selected depth attachment supports TestWrite/TestReadOnly; fragment alpha test receives per-range dynamic cutoff through push constants; baseline key is live-proven, while Batch20/MAT0 state/reference mapping remains an evidence gap
|
||||||
S3-VK-DRAW-MODEL-001 covered-gpu cargo run -p fparkan-vulkan-smoke --release --locked -- --out target/fparkan/stage3-fr-l-mtp-per-batch-materials.json --frames 300 --resize-frame 120 --timeout-seconds 90 --model-root <GOG_ROOT> --model-archive fortif.rlb --model-name FR_L_MTP.msh --wear-root <GOG_ROOT> --wear-archive fortif.rlb --wear-name FR_L_MTP.WEA; live Win32 Vulkan issues 237 source-preserving indexed batch draws and binds the deduplicated WEAR→MAT0 material descriptor selected by each Batch20.material_index (this corpus model has selector 0 only)
|
S3-VK-DRAW-MODEL-001 covered-gpu cargo run -p fparkan-vulkan-smoke --release --locked -- --out target/fparkan/stage3-fr-l-mtp-per-batch-materials.json --frames 300 --resize-frame 120 --timeout-seconds 90 --model-root <GOG_ROOT> --model-archive fortif.rlb --model-name FR_L_MTP.msh --wear-root <GOG_ROOT> --wear-archive fortif.rlb --wear-name FR_L_MTP.WEA; live Win32 Vulkan issues 237 source-preserving indexed batch draws and binds the deduplicated WEAR→MAT0 material descriptor selected by each Batch20.material_index (this corpus model has selector 0 only)
|
||||||
S3-VK-DRAW-TERRAIN-001 blocked awaits Stage 3 terrain Vulkan draw path
|
S3-VK-DRAW-TERRAIN-001 covered-gpu cargo run -p fparkan-vulkan-smoke --release --locked -- --out target/fparkan/stage3-land-11-smoke.json --frames 300 --resize-frame 120 --timeout-seconds 90 --terrain-path <GOG_ROOT>/DATA/MAPS/11/Land.msh --texture-root <GOG_ROOT> --texture-archive Textures.lib --texture-name DEFAULT.0; validated Land.msh TerrainFace28 order becomes one live indexed Vulkan terrain draw (6458 vertices, 24672 indices); terrain material/slot/camera semantics remain an evidence gap
|
||||||
S3-VK-PIXEL-CAPTURE-001 blocked awaits Stage 3 fixed-camera pixel capture approval flow
|
S3-VK-PIXEL-CAPTURE-001 blocked awaits Stage 3 fixed-camera pixel capture approval flow
|
||||||
S3-VK-VALIDATION-001 covered-gpu cargo run -p fparkan-vulkan-smoke --release --locked -- --out target/fparkan/stage3-mtcheck-msh.json --frames 300 --resize-frame 120 --timeout-seconds 90 --model-root <GOG_ROOT> --model-archive system.rlb --model-name MTCHECK.MSH; validation_warning_count=0 and validation_error_count=0
|
S3-VK-VALIDATION-001 covered-gpu cargo run -p fparkan-vulkan-smoke --release --locked -- --out target/fparkan/stage3-mtcheck-msh.json --frames 300 --resize-frame 120 --timeout-seconds 90 --model-root <GOG_ROOT> --model-archive system.rlb --model-name MTCHECK.MSH; validation_warning_count=0 and validation_error_count=0
|
||||||
S3-GL-001 omitted legacy OpenGL adapters are outside the Windows-only Vulkan renderer scope
|
S3-GL-001 omitted legacy OpenGL adapters are outside the Windows-only Vulkan renderer scope
|
||||||
|
|||||||
|
Reference in New Issue
Block a user