fix(render): hide native zero-alpha MSH batches
Docs Deploy / Build and Deploy MkDocs (push) Successful in 1m43s
Docs Deploy / Build and Deploy MkDocs (push) Successful in 1m43s
This commit is contained in:
9 files changed
+234
-12
No files matched your search
@@ -1379,6 +1379,7 @@ mod tests {
|
|||||||
normals: None,
|
normals: None,
|
||||||
uv0: None,
|
uv0: None,
|
||||||
uv1: None,
|
uv1: None,
|
||||||
|
type16_present: false,
|
||||||
indices,
|
indices,
|
||||||
batches,
|
batches,
|
||||||
triangle_descriptors: None,
|
triangle_descriptors: None,
|
||||||
@@ -1439,6 +1440,7 @@ mod tests {
|
|||||||
normals: None,
|
normals: None,
|
||||||
uv0: None,
|
uv0: None,
|
||||||
uv1: None,
|
uv1: None,
|
||||||
|
type16_present: false,
|
||||||
indices: Vec::new(),
|
indices: Vec::new(),
|
||||||
batches: vec![
|
batches: vec![
|
||||||
batch(0, 0, 0),
|
batch(0, 0, 0),
|
||||||
@@ -1686,6 +1688,7 @@ mod tests {
|
|||||||
normals: None,
|
normals: None,
|
||||||
uv0: None,
|
uv0: None,
|
||||||
uv1: None,
|
uv1: None,
|
||||||
|
type16_present: false,
|
||||||
indices: vec![0, 1, 2, 0, 1, 2],
|
indices: vec![0, 1, 2, 0, 1, 2],
|
||||||
batches: vec![batch(0, 3, 0), batch(3, 3, 0)],
|
batches: vec![batch(0, 3, 0), batch(3, 3, 0)],
|
||||||
triangle_descriptors: None,
|
triangle_descriptors: None,
|
||||||
@@ -1746,6 +1749,7 @@ mod tests {
|
|||||||
normals: None,
|
normals: None,
|
||||||
uv0: None,
|
uv0: None,
|
||||||
uv1: None,
|
uv1: None,
|
||||||
|
type16_present: false,
|
||||||
indices: Vec::new(),
|
indices: Vec::new(),
|
||||||
batches: Vec::new(),
|
batches: Vec::new(),
|
||||||
triangle_descriptors: None,
|
triangle_descriptors: None,
|
||||||
@@ -1853,6 +1857,7 @@ mod tests {
|
|||||||
normals: None,
|
normals: None,
|
||||||
uv0: None,
|
uv0: None,
|
||||||
uv1: None,
|
uv1: None,
|
||||||
|
type16_present: false,
|
||||||
indices: vec![0, 1, 2, 0, 1, 2],
|
indices: vec![0, 1, 2, 0, 1, 2],
|
||||||
batches: vec![batch(0, 3, 0), batch(3, 3, 0)],
|
batches: vec![batch(0, 3, 0), batch(3, 3, 0)],
|
||||||
triangle_descriptors: None,
|
triangle_descriptors: None,
|
||||||
@@ -1945,6 +1950,7 @@ mod tests {
|
|||||||
normals: None,
|
normals: None,
|
||||||
uv0: None,
|
uv0: None,
|
||||||
uv1: None,
|
uv1: None,
|
||||||
|
type16_present: false,
|
||||||
indices: vec![0, 1, 2],
|
indices: vec![0, 1, 2],
|
||||||
batches: vec![batch(0, 3, 0)],
|
batches: vec![batch(0, 3, 0)],
|
||||||
triangle_descriptors: None,
|
triangle_descriptors: None,
|
||||||
|
|||||||
@@ -423,6 +423,7 @@ impl VulkanSmokeRenderer {
|
|||||||
draw_range_sort_keys: vec![None; create_info.mesh.draw_ranges.len()],
|
draw_range_sort_keys: vec![None; create_info.mesh.draw_ranges.len()],
|
||||||
draw_order_dirty: false,
|
draw_order_dirty: false,
|
||||||
draw_range_world_queue: vec![None; create_info.mesh.draw_ranges.len()],
|
draw_range_world_queue: vec![None; create_info.mesh.draw_ranges.len()],
|
||||||
|
draw_range_diffuse_alpha_overrides: vec![None; create_info.mesh.draw_ranges.len()],
|
||||||
world_transparent_background_ranges: Vec::new(),
|
world_transparent_background_ranges: Vec::new(),
|
||||||
draw_range_base_pipeline_states: create_info
|
draw_range_base_pipeline_states: create_info
|
||||||
.mesh
|
.mesh
|
||||||
@@ -901,6 +902,27 @@ impl VulkanSmokeRenderer {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Overrides one draw's diffuse alpha without changing its shared material.
|
||||||
|
pub fn set_draw_range_diffuse_alpha_override(
|
||||||
|
&mut self,
|
||||||
|
range_index: usize,
|
||||||
|
diffuse_alpha: Option<f32>,
|
||||||
|
) -> Result<(), VulkanSmokeRendererError> {
|
||||||
|
if diffuse_alpha.is_some_and(|alpha| !alpha.is_finite() || !(0.0..=1.0).contains(&alpha)) {
|
||||||
|
return Err(VulkanSmokeRendererError::InvalidStaticMesh {
|
||||||
|
context: "dynamic draw-range diffuse alpha must be finite and 0..1",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let alpha_override = self
|
||||||
|
.draw_range_diffuse_alpha_overrides
|
||||||
|
.get_mut(range_index)
|
||||||
|
.ok_or(VulkanSmokeRendererError::InvalidStaticMesh {
|
||||||
|
context: "dynamic draw-range alpha index is out of bounds",
|
||||||
|
})?;
|
||||||
|
*alpha_override = diffuse_alpha;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the native per-range alpha reference in legacy 0..=255 units.
|
/// Sets the native per-range alpha reference in legacy 0..=255 units.
|
||||||
///
|
///
|
||||||
/// Zero remains an explicit value; use the native-default constant when
|
/// Zero remains an explicit value; use the native-default constant when
|
||||||
@@ -1874,11 +1896,16 @@ impl VulkanSmokeRenderer {
|
|||||||
context: "static material alpha",
|
context: "static material alpha",
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
let material_alphas = [
|
let diffuse_alpha_override = self
|
||||||
material_alpha[0].to_ne_bytes(),
|
.draw_range_diffuse_alpha_overrides
|
||||||
material_alpha[1].to_ne_bytes(),
|
.get(range_index)
|
||||||
]
|
.ok_or(VulkanSmokeRendererError::InvariantViolation {
|
||||||
.concat();
|
context: "draw-range diffuse-alpha override",
|
||||||
|
})?;
|
||||||
|
let material_alphas =
|
||||||
|
draw_material_alphas(*material_alpha, *diffuse_alpha_override)
|
||||||
|
.map(f32::to_ne_bytes)
|
||||||
|
.concat();
|
||||||
device.device().cmd_push_constants(
|
device.device().cmd_push_constants(
|
||||||
command_buffer,
|
command_buffer,
|
||||||
resources.pipeline_layout,
|
resources.pipeline_layout,
|
||||||
@@ -2368,6 +2395,16 @@ fn apply_world_transparency(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn draw_material_alphas(
|
||||||
|
material_alphas: [f32; 2],
|
||||||
|
diffuse_alpha_override: Option<f32>,
|
||||||
|
) -> [f32; 2] {
|
||||||
|
[
|
||||||
|
diffuse_alpha_override.unwrap_or(material_alphas[0]),
|
||||||
|
material_alphas[1],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
fn world_material_pipeline_states(
|
fn world_material_pipeline_states(
|
||||||
original: LegacyPipelineState,
|
original: LegacyPipelineState,
|
||||||
category: u8,
|
category: u8,
|
||||||
@@ -2535,10 +2572,10 @@ mod tests {
|
|||||||
use super::super::{VulkanDynamicDrawRange, VulkanStaticDrawRange, VulkanStaticMesh};
|
use super::super::{VulkanDynamicDrawRange, VulkanStaticDrawRange, VulkanStaticMesh};
|
||||||
use super::{
|
use super::{
|
||||||
apply_geometry_range_updates, apply_world_transparency, completed_readback_buffer_index,
|
apply_geometry_range_updates, apply_world_transparency, completed_readback_buffer_index,
|
||||||
mark_draw_order_dirty_on_activity_change, set_and_mark_dirty_if_changed,
|
draw_material_alphas, mark_draw_order_dirty_on_activity_change,
|
||||||
sorted_world_draw_order, take_runtime_children_with_validation_snapshot,
|
set_and_mark_dirty_if_changed, sorted_world_draw_order,
|
||||||
take_runtime_owners_in_dependency_order, validate_geometry_upload,
|
take_runtime_children_with_validation_snapshot, take_runtime_owners_in_dependency_order,
|
||||||
world_material_pipeline_states, RollbackOnDrop,
|
validate_geometry_upload, world_material_pipeline_states, RollbackOnDrop,
|
||||||
};
|
};
|
||||||
use fparkan_render::{LegacyBlendMode, LegacyCullMode, LegacyDepthMode, LegacyPipelineState};
|
use fparkan_render::{LegacyBlendMode, LegacyCullMode, LegacyDepthMode, LegacyPipelineState};
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
@@ -3141,4 +3178,22 @@ mod tests {
|
|||||||
assert!(world_material_pipeline_states(original, 5, Some(false)).is_none());
|
assert!(world_material_pipeline_states(original, 5, Some(false)).is_none());
|
||||||
assert!(world_material_pipeline_states(fixed_state, 5, None).is_none());
|
assert!(world_material_pipeline_states(fixed_state, 5, None).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn draw_alpha_override_is_per_range_and_none_restores_material_alpha() {
|
||||||
|
let shared_material = [0.25, 0.5];
|
||||||
|
|
||||||
|
assert_eq!(draw_material_alphas(shared_material, Some(0.0)), [0.0, 0.5]);
|
||||||
|
assert_eq!(draw_material_alphas(shared_material, None), shared_material);
|
||||||
|
|
||||||
|
let updated_material = [0.75, 0.125];
|
||||||
|
assert_eq!(
|
||||||
|
draw_material_alphas(updated_material, None),
|
||||||
|
updated_material
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
draw_material_alphas(updated_material, Some(0.0)),
|
||||||
|
[0.0, 0.125]
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1456,6 +1456,9 @@ pub struct VulkanSmokeRenderer {
|
|||||||
/// a fixed screen/sky/weather pass; `Some(false)` is world opaque and
|
/// a fixed screen/sky/weather pass; `Some(false)` is world opaque and
|
||||||
/// `Some(true)` is world transparent.
|
/// `Some(true)` is world transparent.
|
||||||
pub(super) draw_range_world_queue: Vec<Option<bool>>,
|
pub(super) draw_range_world_queue: Vec<Option<bool>>,
|
||||||
|
/// Optional diffuse-alpha override for each draw range, independent of
|
||||||
|
/// the shared material record.
|
||||||
|
pub(super) draw_range_diffuse_alpha_overrides: Vec<Option<f32>>,
|
||||||
/// Fixed sky background ranges placed between world opaque and transparent.
|
/// Fixed sky background ranges placed between world opaque and transparent.
|
||||||
pub(super) world_transparent_background_ranges: Vec<usize>,
|
pub(super) world_transparent_background_ranges: Vec<usize>,
|
||||||
/// Pipeline state before transparent queue depth policy is applied.
|
/// Pipeline state before transparent queue depth policy is applied.
|
||||||
|
|||||||
@@ -1053,6 +1053,13 @@ fn native_world_transparent(range: &VulkanStaticDrawRange, diffuse_alpha: f32) -
|
|||||||
diffuse_alpha < 1.0 || (range.batch_flags & 0x108) != 0
|
diffuse_alpha < 1.0 || (range.batch_flags & 0x108) != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn native_msh_diffuse_alpha_override(batch_flags: u16, type16_present: bool) -> Option<f32> {
|
||||||
|
if type16_present || batch_flags & 0x0008 == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
(batch_flags & 0x0040 != 0 && batch_flags & 0x0010 == 0).then_some(0.0)
|
||||||
|
}
|
||||||
|
|
||||||
fn native_msh_handedness(transform: LegacyIron3dEulerTransform, scale: [f32; 3]) -> i8 {
|
fn native_msh_handedness(transform: LegacyIron3dEulerTransform, scale: [f32; 3]) -> i8 {
|
||||||
let Some(origin) = transform.try_transform_scaled_point([0.0; 3], scale) else {
|
let Some(origin) = transform.try_transform_scaled_point([0.0; 3], scale) else {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -2273,6 +2280,7 @@ impl EnvironmentGpuScene {
|
|||||||
fn new(
|
fn new(
|
||||||
mesh: &mut VulkanStaticMesh,
|
mesh: &mut VulkanStaticMesh,
|
||||||
native_handedness_by_range: &mut HashMap<usize, i8>,
|
native_handedness_by_range: &mut HashMap<usize, i8>,
|
||||||
|
type16_present_ranges: &mut HashSet<usize>,
|
||||||
materials: &mut Vec<VulkanStaticMaterial>,
|
materials: &mut Vec<VulkanStaticMaterial>,
|
||||||
preview_material_animations: &mut Vec<MaterialAnimationBinding>,
|
preview_material_animations: &mut Vec<MaterialAnimationBinding>,
|
||||||
sun_occlusion_range_indices: &mut Vec<usize>,
|
sun_occlusion_range_indices: &mut Vec<usize>,
|
||||||
@@ -2700,6 +2708,15 @@ impl EnvironmentGpuScene {
|
|||||||
remapped_handedness.insert(new_range_index, handedness);
|
remapped_handedness.insert(new_range_index, handedness);
|
||||||
}
|
}
|
||||||
*native_handedness_by_range = remapped_handedness;
|
*native_handedness_by_range = remapped_handedness;
|
||||||
|
let mut remapped_type16_present_ranges =
|
||||||
|
HashSet::with_capacity(type16_present_ranges.len());
|
||||||
|
for old_range_index in std::mem::take(type16_present_ranges) {
|
||||||
|
let new_range_index = remap.get(old_range_index).copied().ok_or_else(|| {
|
||||||
|
format!("type-16 MSH draw range remap is missing range {old_range_index}")
|
||||||
|
})?;
|
||||||
|
remapped_type16_present_ranges.insert(new_range_index);
|
||||||
|
}
|
||||||
|
*type16_present_ranges = remapped_type16_present_ranges;
|
||||||
for layer in &mut scene.sky_layers {
|
for layer in &mut scene.sky_layers {
|
||||||
layer.range.range_index = *remap
|
layer.range.range_index = *remap
|
||||||
.get(layer.range.range_index)
|
.get(layer.range.range_index)
|
||||||
@@ -3965,6 +3982,7 @@ fn multiply_row_major(left: [f32; 16], right: [f32; 16]) -> [f32; 16] {
|
|||||||
struct StaticPreviewScene {
|
struct StaticPreviewScene {
|
||||||
mesh: VulkanStaticMesh,
|
mesh: VulkanStaticMesh,
|
||||||
native_handedness_by_range: HashMap<usize, i8>,
|
native_handedness_by_range: HashMap<usize, i8>,
|
||||||
|
type16_present_ranges: HashSet<usize>,
|
||||||
materials: Vec<VulkanStaticMaterial>,
|
materials: Vec<VulkanStaticMaterial>,
|
||||||
material_animations: Vec<MaterialAnimationBinding>,
|
material_animations: Vec<MaterialAnimationBinding>,
|
||||||
sun_occlusion_range_indices: Vec<usize>,
|
sun_occlusion_range_indices: Vec<usize>,
|
||||||
@@ -4401,6 +4419,7 @@ fn static_preview_mesh_and_materials(
|
|||||||
draw_ranges: Vec::new(),
|
draw_ranges: Vec::new(),
|
||||||
};
|
};
|
||||||
let mut native_handedness_by_range = HashMap::new();
|
let mut native_handedness_by_range = HashMap::new();
|
||||||
|
let mut type16_present_ranges = HashSet::new();
|
||||||
let mut materials = Vec::new();
|
let mut materials = Vec::new();
|
||||||
let mut material_animations = Vec::new();
|
let mut material_animations = Vec::new();
|
||||||
let mut sun_occlusion_range_indices = Vec::new();
|
let mut sun_occlusion_range_indices = Vec::new();
|
||||||
@@ -4768,6 +4787,9 @@ fn static_preview_mesh_and_materials(
|
|||||||
)?;
|
)?;
|
||||||
for range_index in range_start..mesh.draw_ranges.len() {
|
for range_index in range_start..mesh.draw_ranges.len() {
|
||||||
native_handedness_by_range.insert(range_index, component_handedness);
|
native_handedness_by_range.insert(range_index, component_handedness);
|
||||||
|
if model.validated.type16_present {
|
||||||
|
type16_present_ranges.insert(range_index);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if root_sun_eligible {
|
if root_sun_eligible {
|
||||||
sun_occlusion_range_indices.extend(range_start..mesh.draw_ranges.len());
|
sun_occlusion_range_indices.extend(range_start..mesh.draw_ranges.len());
|
||||||
@@ -4842,6 +4864,7 @@ fn static_preview_mesh_and_materials(
|
|||||||
Ok(StaticPreviewScene {
|
Ok(StaticPreviewScene {
|
||||||
mesh,
|
mesh,
|
||||||
native_handedness_by_range,
|
native_handedness_by_range,
|
||||||
|
type16_present_ranges,
|
||||||
materials,
|
materials,
|
||||||
material_animations,
|
material_animations,
|
||||||
sun_occlusion_range_indices,
|
sun_occlusion_range_indices,
|
||||||
@@ -5562,6 +5585,7 @@ fn run_static_vulkan_mode(
|
|||||||
Some(EnvironmentGpuScene::new(
|
Some(EnvironmentGpuScene::new(
|
||||||
&mut preview.mesh,
|
&mut preview.mesh,
|
||||||
&mut preview.native_handedness_by_range,
|
&mut preview.native_handedness_by_range,
|
||||||
|
&mut preview.type16_present_ranges,
|
||||||
&mut preview.materials,
|
&mut preview.materials,
|
||||||
&mut preview.material_animations,
|
&mut preview.material_animations,
|
||||||
&mut preview.sun_occlusion_range_indices,
|
&mut preview.sun_occlusion_range_indices,
|
||||||
@@ -5677,6 +5701,7 @@ fn replay_environment_delta_seconds(last_applied_ms: u32, current_ms: u32) -> f3
|
|||||||
struct StaticVulkanApp {
|
struct StaticVulkanApp {
|
||||||
mesh: VulkanStaticMesh,
|
mesh: VulkanStaticMesh,
|
||||||
native_handedness_by_range: HashMap<usize, i8>,
|
native_handedness_by_range: HashMap<usize, i8>,
|
||||||
|
type16_present_ranges: HashSet<usize>,
|
||||||
materials: Vec<VulkanStaticMaterial>,
|
materials: Vec<VulkanStaticMaterial>,
|
||||||
material_animations: Vec<MaterialAnimationBinding>,
|
material_animations: Vec<MaterialAnimationBinding>,
|
||||||
sun_occlusion_range_indices: Vec<usize>,
|
sun_occlusion_range_indices: Vec<usize>,
|
||||||
@@ -5765,6 +5790,7 @@ impl StaticVulkanApp {
|
|||||||
Self {
|
Self {
|
||||||
mesh: preview.mesh,
|
mesh: preview.mesh,
|
||||||
native_handedness_by_range: preview.native_handedness_by_range,
|
native_handedness_by_range: preview.native_handedness_by_range,
|
||||||
|
type16_present_ranges: preview.type16_present_ranges,
|
||||||
materials: preview.materials,
|
materials: preview.materials,
|
||||||
material_animations: preview.material_animations,
|
material_animations: preview.material_animations,
|
||||||
sun_occlusion_range_indices: preview.sun_occlusion_range_indices,
|
sun_occlusion_range_indices: preview.sun_occlusion_range_indices,
|
||||||
@@ -5832,7 +5858,17 @@ impl StaticVulkanApp {
|
|||||||
.materials
|
.materials
|
||||||
.get(usize::from(range.material_index))
|
.get(usize::from(range.material_index))
|
||||||
.map_or(1.0, |material| material.diffuse_alpha);
|
.map_or(1.0, |material| material.diffuse_alpha);
|
||||||
let transparent = native_world_transparent(range, alpha);
|
let alpha_override = native_msh_diffuse_alpha_override(
|
||||||
|
range.batch_flags,
|
||||||
|
self.type16_present_ranges.contains(&range_index),
|
||||||
|
);
|
||||||
|
let effective_alpha = alpha_override.unwrap_or(alpha);
|
||||||
|
renderer
|
||||||
|
.set_draw_range_diffuse_alpha_override(range_index, alpha_override)
|
||||||
|
.map_err(|error| {
|
||||||
|
format!("configure world draw range {range_index} alpha: {error}")
|
||||||
|
})?;
|
||||||
|
let transparent = native_world_transparent(range, effective_alpha);
|
||||||
renderer
|
renderer
|
||||||
.set_draw_range_transparency(range_index, transparent)
|
.set_draw_range_transparency(range_index, transparent)
|
||||||
.map_err(|error| format!("configure world draw range {range_index}: {error}"))?;
|
.map_err(|error| format!("configure world draw range {range_index}: {error}"))?;
|
||||||
@@ -5853,7 +5889,7 @@ impl StaticVulkanApp {
|
|||||||
&self.mesh,
|
&self.mesh,
|
||||||
range,
|
range,
|
||||||
range.index_count,
|
range.index_count,
|
||||||
alpha,
|
effective_alpha,
|
||||||
self.view_camera.position,
|
self.view_camera.position,
|
||||||
self.native_handedness_by_range.get(&range_index).copied(),
|
self.native_handedness_by_range.get(&range_index).copied(),
|
||||||
),
|
),
|
||||||
@@ -5869,6 +5905,7 @@ impl StaticVulkanApp {
|
|||||||
mesh: &VulkanStaticMesh,
|
mesh: &VulkanStaticMesh,
|
||||||
materials: &[VulkanStaticMaterial],
|
materials: &[VulkanStaticMaterial],
|
||||||
world_range_indices: &[usize],
|
world_range_indices: &[usize],
|
||||||
|
type16_present_ranges: &HashSet<usize>,
|
||||||
world_range_index_counts: &HashMap<usize, u32>,
|
world_range_index_counts: &HashMap<usize, u32>,
|
||||||
native_handedness_by_range: &HashMap<usize, i8>,
|
native_handedness_by_range: &HashMap<usize, i8>,
|
||||||
eye_world: [f32; 3],
|
eye_world: [f32; 3],
|
||||||
@@ -5881,7 +5918,7 @@ impl StaticVulkanApp {
|
|||||||
.get(range_index)
|
.get(range_index)
|
||||||
.ok_or_else(|| "world draw range index is out of bounds".to_string())?;
|
.ok_or_else(|| "world draw range index is out of bounds".to_string())?;
|
||||||
let state = active_materials.get(&range_index);
|
let state = active_materials.get(&range_index);
|
||||||
let alpha = state.map_or_else(
|
let material_alpha = state.map_or_else(
|
||||||
|| {
|
|| {
|
||||||
materials
|
materials
|
||||||
.get(usize::from(range.material_index))
|
.get(usize::from(range.material_index))
|
||||||
@@ -5889,6 +5926,14 @@ impl StaticVulkanApp {
|
|||||||
},
|
},
|
||||||
|state| state.diffuse_alpha,
|
|state| state.diffuse_alpha,
|
||||||
);
|
);
|
||||||
|
let alpha_override = native_msh_diffuse_alpha_override(
|
||||||
|
range.batch_flags,
|
||||||
|
type16_present_ranges.contains(&range_index),
|
||||||
|
);
|
||||||
|
let alpha = alpha_override.unwrap_or(material_alpha);
|
||||||
|
renderer
|
||||||
|
.set_draw_range_diffuse_alpha_override(range_index, alpha_override)
|
||||||
|
.map_err(|error| format!("update world draw range {range_index} alpha: {error}"))?;
|
||||||
renderer
|
renderer
|
||||||
.set_draw_range_transparency(range_index, native_world_transparent(range, alpha))
|
.set_draw_range_transparency(range_index, native_world_transparent(range, alpha))
|
||||||
.map_err(|error| format!("update world draw range {range_index}: {error}"))?;
|
.map_err(|error| format!("update world draw range {range_index}: {error}"))?;
|
||||||
@@ -6322,6 +6367,7 @@ impl StaticVulkanApp {
|
|||||||
&self.mesh,
|
&self.mesh,
|
||||||
&self.materials,
|
&self.materials,
|
||||||
&self.world_range_indices,
|
&self.world_range_indices,
|
||||||
|
&self.type16_present_ranges,
|
||||||
&self.world_range_index_counts,
|
&self.world_range_index_counts,
|
||||||
&self.native_handedness_by_range,
|
&self.native_handedness_by_range,
|
||||||
frame.camera.position,
|
frame.camera.position,
|
||||||
@@ -7342,6 +7388,7 @@ mod tests {
|
|||||||
normals: None,
|
normals: None,
|
||||||
uv0: None,
|
uv0: None,
|
||||||
uv1: None,
|
uv1: None,
|
||||||
|
type16_present: false,
|
||||||
indices: vec![],
|
indices: vec![],
|
||||||
batches: vec![],
|
batches: vec![],
|
||||||
triangle_descriptors: None,
|
triangle_descriptors: None,
|
||||||
@@ -7671,6 +7718,7 @@ mod tests {
|
|||||||
let preview = StaticPreviewScene {
|
let preview = StaticPreviewScene {
|
||||||
mesh: initial_mesh.clone(),
|
mesh: initial_mesh.clone(),
|
||||||
native_handedness_by_range: HashMap::new(),
|
native_handedness_by_range: HashMap::new(),
|
||||||
|
type16_present_ranges: HashSet::new(),
|
||||||
materials: Vec::new(),
|
materials: Vec::new(),
|
||||||
material_animations: Vec::new(),
|
material_animations: Vec::new(),
|
||||||
sun_occlusion_range_indices: Vec::new(),
|
sun_occlusion_range_indices: Vec::new(),
|
||||||
@@ -8241,6 +8289,17 @@ mod tests {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn native_cpu_shaded_msh_alpha_override_uses_only_the_constant_batch_rule() {
|
||||||
|
assert_eq!(native_msh_diffuse_alpha_override(0x0148, false), Some(0.0));
|
||||||
|
assert_eq!(native_msh_diffuse_alpha_override(0x0108, false), None);
|
||||||
|
assert_eq!(native_msh_diffuse_alpha_override(0x0118, false), None);
|
||||||
|
assert_eq!(native_msh_diffuse_alpha_override(0x0058, false), None);
|
||||||
|
assert_eq!(native_msh_diffuse_alpha_override(0x0148, true), None);
|
||||||
|
assert_eq!(native_msh_diffuse_alpha_override(0x0058, true), None);
|
||||||
|
assert_eq!(native_msh_diffuse_alpha_override(0x0040, false), None);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn material_animation_selects_pipeline_category_with_active_phase() {
|
fn material_animation_selects_pipeline_category_with_active_phase() {
|
||||||
let variant = MaterialVariantSet {
|
let variant = MaterialVariantSet {
|
||||||
@@ -10009,4 +10068,29 @@ mod tests {
|
|||||||
original_count
|
original_count
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
#[test]
|
||||||
|
fn world_draw_range_count_clears_and_reapplies_constant_alpha_override() {
|
||||||
|
let triangle = native_front_triangle();
|
||||||
|
let (mesh, mut range) = facing_mesh(0x0148, &triangle);
|
||||||
|
let original_count = range.index_count;
|
||||||
|
let mut alpha_override = None;
|
||||||
|
let update = |range: &VulkanStaticDrawRange, alpha_override: &mut Option<f32>| {
|
||||||
|
*alpha_override = native_msh_diffuse_alpha_override(range.batch_flags, false);
|
||||||
|
world_draw_range_index_count(
|
||||||
|
&mesh,
|
||||||
|
range,
|
||||||
|
original_count,
|
||||||
|
alpha_override.unwrap_or(1.0),
|
||||||
|
[0.0; 3],
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(update(&range, &mut alpha_override), 0);
|
||||||
|
range.batch_flags = 0x0108;
|
||||||
|
assert_eq!(update(&range, &mut alpha_override), original_count);
|
||||||
|
range.batch_flags = 0x0148;
|
||||||
|
assert_eq!(update(&range, &mut alpha_override), 0);
|
||||||
|
assert_eq!(original_count, 3);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -21,6 +21,8 @@ pub const STREAM_UV0: u32 = 5;
|
|||||||
/// Optional secondary texture coordinate stream used by native lightmap/detail
|
/// Optional secondary texture coordinate stream used by native lightmap/detail
|
||||||
/// stages. MSH stores it as the type-18 four-byte packed UV stream.
|
/// stages. MSH stores it as the type-18 four-byte packed UV stream.
|
||||||
pub const STREAM_UV1: u32 = 18;
|
pub const STREAM_UV1: u32 = 18;
|
||||||
|
/// Optional type-16 stream. Its presence marks MSH vertex-colour data.
|
||||||
|
pub const STREAM_TYPE_16: u32 = 16;
|
||||||
/// Triangle index stream.
|
/// Triangle index stream.
|
||||||
pub const STREAM_INDICES: u32 = 6;
|
pub const STREAM_INDICES: u32 = 6;
|
||||||
/// Optional triangle-descriptor stream.
|
/// Optional triangle-descriptor stream.
|
||||||
@@ -101,6 +103,10 @@ pub struct ModelAsset {
|
|||||||
pub uv0: Option<Vec<[u16; 2]>>,
|
pub uv0: Option<Vec<[u16; 2]>>,
|
||||||
/// Optional secondary packed texture coordinates from type 18.
|
/// Optional secondary packed texture coordinates from type 18.
|
||||||
pub uv1: Option<Vec<[u16; 2]>>,
|
pub uv1: Option<Vec<[u16; 2]>>,
|
||||||
|
/// Whether the source MSH contains a type-16 vertex-colour stream.
|
||||||
|
///
|
||||||
|
/// The stream payload is preserved separately and is not decoded here.
|
||||||
|
pub type16_present: bool,
|
||||||
/// Triangle indices.
|
/// Triangle indices.
|
||||||
pub indices: Vec<u16>,
|
pub indices: Vec<u16>,
|
||||||
/// Draw batches.
|
/// Draw batches.
|
||||||
@@ -1116,6 +1122,7 @@ fn parse_model_document(document: &NresDocument) -> Result<ModelAsset, MshError>
|
|||||||
let uv1 = read_optional_stream(document, STREAM_UV1)?
|
let uv1 = read_optional_stream(document, STREAM_UV1)?
|
||||||
.map(|raw| parse_secondary_uv_stream(raw, positions.len()))
|
.map(|raw| parse_secondary_uv_stream(raw, positions.len()))
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
|
let type16_present = read_optional_stream(document, STREAM_TYPE_16)?.is_some();
|
||||||
let triangle_descriptors = read_optional_triangle_descriptors(document)?
|
let triangle_descriptors = read_optional_triangle_descriptors(document)?
|
||||||
.map(|raw| parse_triangle_descriptors(&raw.bytes))
|
.map(|raw| parse_triangle_descriptors(&raw.bytes))
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
@@ -1135,6 +1142,7 @@ fn parse_model_document(document: &NresDocument) -> Result<ModelAsset, MshError>
|
|||||||
normals,
|
normals,
|
||||||
uv0,
|
uv0,
|
||||||
uv1,
|
uv1,
|
||||||
|
type16_present,
|
||||||
indices,
|
indices,
|
||||||
batches,
|
batches,
|
||||||
triangle_descriptors,
|
triangle_descriptors,
|
||||||
@@ -1641,6 +1649,25 @@ mod tests {
|
|||||||
assert_eq!(model.node_count, 0);
|
assert_eq!(model.node_count, 0);
|
||||||
assert!(model.slots.is_empty());
|
assert!(model.slots.is_empty());
|
||||||
assert_eq!(model.triangle_descriptors, None);
|
assert_eq!(model.triangle_descriptors, None);
|
||||||
|
assert!(!model.type16_present);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn type16_presence_is_preserved_without_decoding_its_payload() {
|
||||||
|
let payload = [0xDE, 0xAD, 0xBE, 0xEF];
|
||||||
|
let document = decode_nested(&build_nres(&[
|
||||||
|
stream(STREAM_NODE_TABLE, 38, b"Res1", &[]),
|
||||||
|
stream(STREAM_SLOTS, 0, b"Res2", &slots_payload(&[])),
|
||||||
|
stream(STREAM_POSITIONS, 0, b"Res3", &[]),
|
||||||
|
stream(STREAM_INDICES, 0, b"Res6", &[]),
|
||||||
|
stream(STREAM_BATCHES, 0, b"Res13", &[]),
|
||||||
|
stream(STREAM_TYPE_16, 0, b"Res16", &payload),
|
||||||
|
]))
|
||||||
|
.expect("nested NRes");
|
||||||
|
let model =
|
||||||
|
validate_msh(&decode_msh(&document).expect("MSH document")).expect("model asset");
|
||||||
|
|
||||||
|
assert!(model.type16_present);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -2262,6 +2289,7 @@ mod tests {
|
|||||||
normals: None,
|
normals: None,
|
||||||
uv0: None,
|
uv0: None,
|
||||||
uv1: None,
|
uv1: None,
|
||||||
|
type16_present: false,
|
||||||
indices: Vec::new(),
|
indices: Vec::new(),
|
||||||
batches: Vec::new(),
|
batches: Vec::new(),
|
||||||
triangle_descriptors: None,
|
triangle_descriptors: None,
|
||||||
@@ -2324,6 +2352,7 @@ mod tests {
|
|||||||
normals: None,
|
normals: None,
|
||||||
uv0: None,
|
uv0: None,
|
||||||
uv1: None,
|
uv1: None,
|
||||||
|
type16_present: false,
|
||||||
indices: Vec::new(),
|
indices: Vec::new(),
|
||||||
batches: Vec::new(),
|
batches: Vec::new(),
|
||||||
triangle_descriptors: None,
|
triangle_descriptors: None,
|
||||||
@@ -2389,6 +2418,7 @@ mod tests {
|
|||||||
normals: None,
|
normals: None,
|
||||||
uv0: None,
|
uv0: None,
|
||||||
uv1: None,
|
uv1: None,
|
||||||
|
type16_present: false,
|
||||||
indices: Vec::new(),
|
indices: Vec::new(),
|
||||||
batches: Vec::new(),
|
batches: Vec::new(),
|
||||||
triangle_descriptors: None,
|
triangle_descriptors: None,
|
||||||
@@ -3282,6 +3312,7 @@ mod tests {
|
|||||||
normals: None,
|
normals: None,
|
||||||
uv0: None,
|
uv0: None,
|
||||||
uv1: None,
|
uv1: None,
|
||||||
|
type16_present: false,
|
||||||
indices,
|
indices,
|
||||||
batches,
|
batches,
|
||||||
node_names: None,
|
node_names: None,
|
||||||
|
|||||||
@@ -6808,6 +6808,7 @@ mod tests {
|
|||||||
normals: None,
|
normals: None,
|
||||||
uv0: None,
|
uv0: None,
|
||||||
uv1: None,
|
uv1: None,
|
||||||
|
type16_present: false,
|
||||||
indices: Vec::new(),
|
indices: Vec::new(),
|
||||||
batches: Vec::new(),
|
batches: Vec::new(),
|
||||||
triangle_descriptors: None,
|
triangle_descriptors: None,
|
||||||
|
|||||||
@@ -18,6 +18,27 @@ state transitions и окончательный порядок части transp
|
|||||||
стене и дверном проёме. Мировое преобразование, фильтры категории и
|
стене и дверном проёме. Мировое преобразование, фильтры категории и
|
||||||
подключение к свободной камере остаются открытыми ([MSH](../reference/msh.md)).
|
подключение к свободной камере остаются открытыми ([MSH](../reference/msh.md)).
|
||||||
|
|
||||||
|
Для MSH без потока цветов type 16 подтверждён только один постоянный alpha
|
||||||
|
override для диапазонов с CPU-цветом и raw-флагом `Batch20` `0x0008`: GOG
|
||||||
|
`Terrain.dll`, SHA-256
|
||||||
|
`af87d1b2e728a0be73c52be3b44cc196ab46da7799f25a15d40f8c9b0b425ead`, RVA
|
||||||
|
`0x1B0F0`, задаёт alpha `0`, если установлен bit `0x0040` и сброшен `0x0010`.
|
||||||
|
Это покрывает `0x0148`; `0x0108`, `0x0118` и `0x0058` сохраняют alpha MAT0,
|
||||||
|
причём в `0x0058` bit `0x0010` имеет приоритет. Поток type 16 или отсутствие
|
||||||
|
`0x0008` исключают override. Проверка заводской сцены с фиксированной нативной
|
||||||
|
камерой подтвердила на одном кадре входа восстановление X-образного рисунка
|
||||||
|
двери, который виден и в оригинале; прежний Vulkan-readback показывал на этом
|
||||||
|
месте сплошной чёрный прямоугольник. Сравнение выполнено при времени сцены
|
||||||
|
2000 мс и трёх кадрах, запуск с Vulkan validation завершился без предупреждений
|
||||||
|
и ошибок. Это подтверждает только структуру проёма в этой сцене: правильность
|
||||||
|
освещения и полный попиксельный паритет не установлены.
|
||||||
|
|
||||||
|
Общее fade-поведение по расстоянию в preview не реализовано. Открыты точная
|
||||||
|
метрика расстояния, выбор первого CPU-принятого треугольника диапазона и
|
||||||
|
эквивалентность frustum/culling-отбора оригинала; эти вопросы нельзя вывести из
|
||||||
|
захвата заводской двери. Данные потока type 16 ещё не декодированы, поэтому
|
||||||
|
правило выше относится только к MSH-диапазонам без этого потока цветов.
|
||||||
|
|
||||||
Закрывающий эксперимент: запустить оригинал в совместимой Windows/DirectX
|
Закрывающий эксперимент: запустить оригинал в совместимой Windows/DirectX
|
||||||
среде, перехватить DirectDraw/Direct3D calls и surface flips, сохранить state
|
среде, перехватить DirectDraw/Direct3D calls и surface flips, сохранить state
|
||||||
log на минимальных сценах с одним типом материала.
|
log на минимальных сценах с одним типом материала.
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ type 9 service stream
|
|||||||
type 10 strings and node names
|
type 10 strings and node names
|
||||||
type 13 Batch20 records
|
type 13 Batch20 records
|
||||||
type 15 auxiliary stream
|
type 15 auxiliary stream
|
||||||
|
type 16 optional vertex-colour data
|
||||||
type 17 auxiliary data
|
type 17 auxiliary data
|
||||||
type 18 packed UV1 (optional secondary coordinates)
|
type 18 packed UV1 (optional secondary coordinates)
|
||||||
type 19 animation frame map
|
type 19 animation frame map
|
||||||
@@ -25,6 +26,9 @@ type 20 rare auxiliary table
|
|||||||
```
|
```
|
||||||
|
|
||||||
Reader ищет entries по type, но сохраняет исходный порядок для roundtrip.
|
Reader ищет entries по type, но сохраняет исходный порядок для roundtrip.
|
||||||
|
Поле `ModelAsset.type16_present` отмечает только наличие entry типа 16. Данные
|
||||||
|
payload сохраняются, но этот путь рендера пока не преобразует их в цвета
|
||||||
|
вершин.
|
||||||
|
|
||||||
## Node and slot selection
|
## Node and slot selection
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,23 @@ Pending frame хранит пакет MAT0 для мира и, при налич
|
|||||||
`batch` `0x108`. Такой диапазон при включённой проверке глубины не записывает
|
`batch` `0x108`. Такой диапазон при включённой проверке глубины не записывает
|
||||||
глубину. За фиксированными диапазонами окружения сохраняются их явные состояния.
|
глубину. За фиксированными диапазонами окружения сохраняются их явные состояния.
|
||||||
|
|
||||||
|
Для MSH без потока цветов type 16 диапазон, цвет которого вычисляет CPU, при
|
||||||
|
raw-флаге `Batch20` `0x0008` получает переопределение alpha только в одном
|
||||||
|
подтверждённом случае. В GOG `Terrain.dll` с SHA-256
|
||||||
|
`af87d1b2e728a0be73c52be3b44cc196ab46da7799f25a15d40f8c9b0b425ead` проверка
|
||||||
|
по RVA `0x1B0F0` задаёт alpha `0`, когда установлен bit `0x0040` и сброшен
|
||||||
|
`0x0010`; поэтому для `0x0148` alpha равна нулю. Для `0x0108` и `0x0118`
|
||||||
|
превью сохраняет alpha MAT0; для `0x0058` действует приоритет bit `0x0010`,
|
||||||
|
также оставляя alpha MAT0. При наличии type 16 или отсутствии `0x0008`
|
||||||
|
переопределения нет. Оно относится к одному draw range и не меняет общий
|
||||||
|
материал; alpha `0` временно задаёт число индексов `0`, а при очистке
|
||||||
|
переопределения исходное число индексов восстанавливается.
|
||||||
|
|
||||||
|
Общий fade по расстоянию пока не реализован. Открыты точная метрика расстояния,
|
||||||
|
выбор первого CPU-принятого треугольника диапазона и эквивалентность frustum- и
|
||||||
|
culling-отбору оригинала. Поэтому состояния `0x0108` и `0x0118` оставляют alpha
|
||||||
|
MAT0, пока эти входные данные не установлены.
|
||||||
|
|
||||||
В превью с фиксированной камерой диапазоны туманности и звёзд, купола и облаков
|
В превью с фиксированной камерой диапазоны туманности и звёзд, купола и облаков
|
||||||
явно помечаются как прозрачный фон. Непрозрачные диапазоны мира остаются на
|
явно помечаются как прозрачный фон. Непрозрачные диапазоны мира остаются на
|
||||||
своих местах; прозрачная очередь мира сортируется от дальних элементов к
|
своих местах; прозрачная очередь мира сортируется от дальних элементов к
|
||||||
|
|||||||
Reference in new issue
Block a user