diff options
Diffstat (limited to 'adapters/fparkan-render-vulkan')
4 files changed, 143 insertions, 19 deletions
diff --git a/adapters/fparkan-render-vulkan/src/asset_mesh.rs b/adapters/fparkan-render-vulkan/src/asset_mesh.rs index 3249012..f691b34 100644 --- a/adapters/fparkan-render-vulkan/src/asset_mesh.rs +++ b/adapters/fparkan-render-vulkan/src/asset_mesh.rs @@ -1379,8 +1379,10 @@ mod tests { normals: None, uv0: None, uv1: None, + type16_present: false, indices, batches, + triangle_descriptors: None, node_names: None, animation: None, } @@ -1438,6 +1440,7 @@ mod tests { normals: None, uv0: None, uv1: None, + type16_present: false, indices: Vec::new(), batches: vec![ batch(0, 0, 0), @@ -1446,6 +1449,7 @@ mod tests { ..batch(0, 0, 0) }, ], + triangle_descriptors: None, node_names: None, animation: None, }; @@ -1684,8 +1688,10 @@ mod tests { normals: None, uv0: None, uv1: None, + type16_present: false, indices: vec![0, 1, 2, 0, 1, 2], batches: vec![batch(0, 3, 0), batch(3, 3, 0)], + triangle_descriptors: None, node_names: None, animation: Some(ModelAnimation { keys: vec![ @@ -1743,8 +1749,10 @@ mod tests { normals: None, uv0: None, uv1: None, + type16_present: false, indices: Vec::new(), batches: Vec::new(), + triangle_descriptors: None, node_names: None, animation: Some(ModelAnimation { keys: vec![ @@ -1849,8 +1857,10 @@ mod tests { normals: None, uv0: None, uv1: None, + type16_present: false, indices: vec![0, 1, 2, 0, 1, 2], batches: vec![batch(0, 3, 0), batch(3, 3, 0)], + triangle_descriptors: None, node_names: None, animation: Some(ModelAnimation { keys: vec![AnimKey24 { @@ -1940,8 +1950,10 @@ mod tests { normals: None, uv0: None, uv1: None, + type16_present: false, indices: vec![0, 1, 2], batches: vec![batch(0, 3, 0)], + triangle_descriptors: None, node_names: None, animation: Some(ModelAnimation { keys: vec![AnimKey24 { diff --git a/adapters/fparkan-render-vulkan/src/ffi/smoke.rs b/adapters/fparkan-render-vulkan/src/ffi/smoke.rs index a2481f7..522f95f 100644 --- a/adapters/fparkan-render-vulkan/src/ffi/smoke.rs +++ b/adapters/fparkan-render-vulkan/src/ffi/smoke.rs @@ -423,6 +423,7 @@ impl VulkanSmokeRenderer { draw_range_sort_keys: vec![None; create_info.mesh.draw_ranges.len()], draw_order_dirty: false, 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(), draw_range_base_pipeline_states: create_info .mesh @@ -901,6 +902,27 @@ impl VulkanSmokeRenderer { 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. /// /// Zero remains an explicit value; use the native-default constant when @@ -1808,6 +1830,7 @@ impl VulkanSmokeRenderer { 0, vk::IndexType::UINT32, ); + let mut viewport_depth = None; for &range_index in &self.draw_order { let range = self.draw_ranges.get(range_index).ok_or( VulkanSmokeRendererError::InvariantViolation { @@ -1873,11 +1896,16 @@ impl VulkanSmokeRenderer { context: "static material alpha", }, )?; - let material_alphas = [ - material_alpha[0].to_ne_bytes(), - material_alpha[1].to_ne_bytes(), - ] - .concat(); + let diffuse_alpha_override = self + .draw_range_diffuse_alpha_overrides + .get(range_index) + .ok_or(VulkanSmokeRendererError::InvariantViolation { + 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( command_buffer, resources.pipeline_layout, @@ -1929,6 +1957,17 @@ impl VulkanSmokeRenderer { } else { *material_mode }; + let viewport = super::swapchain_resources::draw_viewport( + swapchain.report.plan.extent, + material_mode, + ); + let next_viewport_depth = [viewport.min_depth, viewport.max_depth]; + if viewport_depth != Some(next_viewport_depth) { + device + .device() + .cmd_set_viewport(command_buffer, 0, &[viewport]); + viewport_depth = Some(next_viewport_depth); + } let material_mode = [material_mode.to_ne_bytes()]; device.device().cmd_push_constants( command_buffer, @@ -2356,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( original: LegacyPipelineState, category: u8, @@ -2523,10 +2572,10 @@ mod tests { use super::super::{VulkanDynamicDrawRange, VulkanStaticDrawRange, VulkanStaticMesh}; use super::{ apply_geometry_range_updates, apply_world_transparency, completed_readback_buffer_index, - mark_draw_order_dirty_on_activity_change, set_and_mark_dirty_if_changed, - sorted_world_draw_order, take_runtime_children_with_validation_snapshot, - take_runtime_owners_in_dependency_order, validate_geometry_upload, - world_material_pipeline_states, RollbackOnDrop, + draw_material_alphas, mark_draw_order_dirty_on_activity_change, + set_and_mark_dirty_if_changed, sorted_world_draw_order, + take_runtime_children_with_validation_snapshot, take_runtime_owners_in_dependency_order, + validate_geometry_upload, world_material_pipeline_states, RollbackOnDrop, }; use fparkan_render::{LegacyBlendMode, LegacyCullMode, LegacyDepthMode, LegacyPipelineState}; use std::cell::RefCell; @@ -3129,4 +3178,22 @@ mod tests { assert!(world_material_pipeline_states(original, 5, Some(false)).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] + ); + } } diff --git a/adapters/fparkan-render-vulkan/src/ffi/smoke_types.rs b/adapters/fparkan-render-vulkan/src/ffi/smoke_types.rs index 51f8384..f004cb2 100644 --- a/adapters/fparkan-render-vulkan/src/ffi/smoke_types.rs +++ b/adapters/fparkan-render-vulkan/src/ffi/smoke_types.rs @@ -1456,6 +1456,9 @@ pub struct VulkanSmokeRenderer { /// a fixed screen/sky/weather pass; `Some(false)` is world opaque and /// `Some(true)` is world transparent. 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. pub(super) world_transparent_background_ranges: Vec<usize>, /// Pipeline state before transparent queue depth policy is applied. diff --git a/adapters/fparkan-render-vulkan/src/ffi/swapchain_resources.rs b/adapters/fparkan-render-vulkan/src/ffi/swapchain_resources.rs index 89ca43a..05ce927 100644 --- a/adapters/fparkan-render-vulkan/src/ffi/swapchain_resources.rs +++ b/adapters/fparkan-render-vulkan/src/ffi/swapchain_resources.rs @@ -922,8 +922,17 @@ fn create_texture_descriptor_bundle( Ok((layout, pool, descriptor_sets, sampler)) } -fn extent_component_to_f32(value: u32) -> f32 { - value as f32 +pub(super) fn draw_viewport(extent: (u32, u32), material_mode: f32) -> vk::Viewport { + // Ngi32 RVA 0x7170 maps world depth to 0.1..0.99; TL and sky depth bypass it. + let explicit_depth = material_mode.round() as u32 & 12 != 0; + vk::Viewport { + x: 0.0, + y: 0.0, + width: extent.0 as f32, + height: extent.1 as f32, + min_depth: if explicit_depth { 0.0 } else { 0.1 }, + max_depth: if explicit_depth { 1.0 } else { 0.99 }, + } } fn create_graphics_pipeline_cache( @@ -1033,14 +1042,7 @@ fn create_graphics_pipeline( let input_assembly_state = vk::PipelineInputAssemblyStateCreateInfo::default() .topology(vk::PrimitiveTopology::TRIANGLE_LIST) .primitive_restart_enable(false); - let viewports = [vk::Viewport { - x: 0.0, - y: 0.0, - width: extent_component_to_f32(extent.0), - height: extent_component_to_f32(extent.1), - min_depth: 0.0, - max_depth: 1.0, - }]; + let viewports = [draw_viewport(extent, 0.0)]; let scissors = [vk::Rect2D { offset: vk::Offset2D { x: 0, y: 0 }, extent: vk::Extent2D { @@ -1105,6 +1107,9 @@ fn create_graphics_pipeline( let color_blend_state = vk::PipelineColorBlendStateCreateInfo::default() .logic_op_enable(false) .attachments(&color_blend_attachments); + let dynamic_states = [vk::DynamicState::VIEWPORT]; + let dynamic_state = + vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&dynamic_states); let create_info = vk::GraphicsPipelineCreateInfo::default() .stages(&shader_stages) .vertex_input_state(&vertex_input_state) @@ -1114,6 +1119,7 @@ fn create_graphics_pipeline( .multisample_state(&multisample_state) .depth_stencil_state(&depth_stencil_state) .color_blend_state(&color_blend_state) + .dynamic_state(&dynamic_state) .layout(pipeline_layout) .render_pass(render_pass) .subpass(0); @@ -1328,3 +1334,39 @@ fn destroy_partial_swapchain_resources( } } } + +#[cfg(test)] +mod tests { + use super::draw_viewport; + + #[test] + fn world_viewport_depth_keeps_distant_geometry_in_front_of_screen_sun() { + let projection = fparkan_render::LegacyD3d7Projection { + viewport: [0, 0, 1280, 1024], + near_plane: 0.5, + far_plane: 700.0, + field_of_view_radians: 1.04, + } + .try_direct3d7_projection_row_major() + .expect("native projection"); + for mode in [0.0, 1.0, 2.0, 3.0, 16.0, 17.0] { + let viewport = draw_viewport((1280, 1024), mode); + assert_eq!([viewport.width, viewport.height], [1280.0, 1024.0]); + assert_eq!([viewport.min_depth, viewport.max_depth], [0.1, 0.99]); + for distance in [0.5, 100.0, 350.0, 600.0, 700.0] { + let clip_z = distance * projection[10] + projection[14]; + let clip_w = distance * projection[11]; + let depth = viewport.min_depth + + (viewport.max_depth - viewport.min_depth) * (clip_z / clip_w); + assert!( + depth < 0.999, + "mode {mode}, distance {distance}, depth {depth}" + ); + } + } + for mode in [4.0, 5.0, 6.0, 7.0, 9.0] { + let viewport = draw_viewport((640, 480), mode); + assert_eq!([viewport.min_depth, viewport.max_depth], [0.0, 1.0]); + } + } +} |
