diff options
Diffstat (limited to 'adapters/fparkan-render-vulkan/src/ffi')
| -rw-r--r-- | adapters/fparkan-render-vulkan/src/ffi/smoke.rs | 69 | ||||
| -rw-r--r-- | adapters/fparkan-render-vulkan/src/ffi/smoke_types.rs | 69 | ||||
| -rw-r--r-- | adapters/fparkan-render-vulkan/src/ffi/swapchain_resources.rs | 33 |
3 files changed, 155 insertions, 16 deletions
diff --git a/adapters/fparkan-render-vulkan/src/ffi/smoke.rs b/adapters/fparkan-render-vulkan/src/ffi/smoke.rs index 522f95f..0e1cebb 100644 --- a/adapters/fparkan-render-vulkan/src/ffi/smoke.rs +++ b/adapters/fparkan-render-vulkan/src/ffi/smoke.rs @@ -2577,7 +2577,9 @@ mod tests { 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 fparkan_render::{ + LegacyBlendMode, LegacyCullMode, LegacyDepthBias, LegacyDepthMode, LegacyPipelineState, + }; use std::cell::RefCell; use std::rc::Rc; @@ -3145,12 +3147,74 @@ mod tests { } #[test] + fn control_fx_queue_follows_opaque_and_sky_without_reactivating_empty_ranges() { + const SCREEN_GRADIENT: usize = 0; + const CONTROL_FX: usize = 1; + const TERRAIN: usize = 2; + const BUILDING: usize = 3; + const SHADOW: usize = 4; + const NEBULA: usize = 5; + const DOME: usize = 6; + const CLOUDS: usize = 7; + const EMPTY_CONTROL_FX: usize = 8; + const SUN: usize = 9; + const WEATHER: usize = 10; + let mut ranges = (0..11) + .map(|index| VulkanStaticDrawRange { + first_index: index * 3, + index_count: 3, + material_index: 0, + lightmap_index: u8::MAX, + batch_flags: 0, + pipeline_state: LegacyPipelineState::default(), + terrain_base_only: false, + alpha_test_reference: 0, + }) + .collect::<Vec<_>>(); + ranges[EMPTY_CONTROL_FX].index_count = 0; + let mut world_queue = vec![None; ranges.len()]; + world_queue[TERRAIN] = Some(false); + world_queue[BUILDING] = Some(false); + world_queue[CONTROL_FX] = Some(true); + world_queue[EMPTY_CONTROL_FX] = Some(true); + let mut sort_keys = vec![None; ranges.len()]; + sort_keys[CONTROL_FX] = Some(20.0); + sort_keys[EMPTY_CONTROL_FX] = Some(30.0); + + let order = sorted_world_draw_order( + ranges.len(), + &ranges, + &world_queue, + &sort_keys, + &[NEBULA, DOME, CLOUDS], + ); + assert_eq!( + order, + vec![ + SCREEN_GRADIENT, + TERRAIN, + BUILDING, + SHADOW, + NEBULA, + DOME, + CLOUDS, + CONTROL_FX, + EMPTY_CONTROL_FX, + SUN, + WEATHER, + ] + ); + assert_eq!(ranges[EMPTY_CONTROL_FX].index_count, 0); + } + + #[test] fn material_category_and_transparency_preserve_world_state_in_either_order() { let original = LegacyPipelineState { blend: LegacyBlendMode::Opaque, depth: LegacyDepthMode::TestWrite, cull: LegacyCullMode::FrontFace, alpha_test: false, + depth_bias: LegacyDepthBias::ProjectedDecal, }; let (base, category_first) = world_material_pipeline_states(original, 1, Some(true)).expect("category 1"); @@ -3162,14 +3226,17 @@ mod tests { assert!(base.alpha_test); assert_eq!(base.depth, LegacyDepthMode::TestWrite); assert_eq!(base.cull, LegacyCullMode::FrontFace); + assert_eq!(base.depth_bias, LegacyDepthBias::ProjectedDecal); assert_eq!(category_first.depth, LegacyDepthMode::TestReadOnly); assert_eq!(category_first.cull, LegacyCullMode::FrontFace); + assert_eq!(category_first.depth_bias, LegacyDepthBias::ProjectedDecal); let fixed_state = LegacyPipelineState { blend: LegacyBlendMode::Additive, depth: LegacyDepthMode::Disabled, cull: LegacyCullMode::Disabled, alpha_test: false, + depth_bias: LegacyDepthBias::Disabled, }; assert_eq!( world_material_pipeline_states(fixed_state, 1, None), diff --git a/adapters/fparkan-render-vulkan/src/ffi/smoke_types.rs b/adapters/fparkan-render-vulkan/src/ffi/smoke_types.rs index f004cb2..0996fa1 100644 --- a/adapters/fparkan-render-vulkan/src/ffi/smoke_types.rs +++ b/adapters/fparkan-render-vulkan/src/ffi/smoke_types.rs @@ -284,6 +284,9 @@ impl VulkanFrameUniforms { .chain([self.fog_start, self.fog_end]) .chain(self.camera_position) .all(f32::is_finite) + && (self.fog_end <= self.fog_start + || ((self.fog_start * self.fog_start).is_finite() + && (self.fog_end * self.fog_end).is_finite())) } /// Serializes one frame slot in the exact `std140` member order. @@ -344,7 +347,17 @@ impl VulkanFrameUniforms { self.fog_color[2], 0.0, ]); - values[84..88].copy_from_slice(&[self.fog_start, self.fog_end, 0.0, 0.0]); + let squared_fog_range = if self.fog_end > self.fog_start { + [self.fog_start * self.fog_start, self.fog_end * self.fog_end] + } else { + [0.0; 2] + }; + values[84..88].copy_from_slice(&[ + self.fog_start, + self.fog_end, + squared_fog_range[0], + squared_fog_range[1], + ]); values[88..92].copy_from_slice(&[ self.camera_position[0], self.camera_position[1], @@ -756,8 +769,8 @@ impl VulkanStaticMesh { material_index: 0, lightmap_index: u8::MAX, batch_flags: 0, - terrain_base_only: false, pipeline_state: LegacyPipelineState::default(), + terrain_base_only: false, alpha_test_reference: 0, }], } @@ -875,6 +888,31 @@ mod static_mesh_tests { } #[test] + fn fog_uniforms_match_live_native_squared_distance_range() { + // Terrain ShadeIndexedStrided: far=0x442389e6, far²=0x48d0f1ca; + // distance²=0x47707b04 gives visibility=0x3f5b2b94. + let frame = VulkanFrameUniforms { + fog_start: 0.0, + fog_end: f32::from_bits(0x4423_89e6), + ..Default::default() + }; + assert!(frame.is_finite()); + let bytes = frame.to_ne_bytes(); + let read = |offset| f32::from_ne_bytes(bytes[offset..offset + 4].try_into().unwrap()); + assert_eq!(read(344), 0.0); + assert_eq!(read(348).to_bits(), 0x48d0_f1ca); + let visibility = 1.0 - (f32::from_bits(0x4770_7b04) - read(344)) / (read(348) - read(344)); + assert!((visibility - f32::from_bits(0x3f5b_2b94)).abs() < 1.0e-6); + assert!(VulkanFrameUniforms::default().is_finite()); + assert!(!VulkanFrameUniforms { + fog_start: 0.0, + fog_end: f32::MAX, + ..frame + } + .is_finite()); + } + + #[test] fn static_camera_composes_recovered_d3d7_view_before_projection() { let transform = RawCameraTransform { words: [ @@ -992,8 +1030,8 @@ mod static_mesh_tests { material_index: 0, lightmap_index: u8::MAX, batch_flags: 0, - terrain_base_only: false, pipeline_state: LegacyPipelineState::default(), + terrain_base_only: false, alpha_test_reference: 0, }], }; @@ -1023,8 +1061,8 @@ mod static_mesh_tests { material_index: 7, lightmap_index: u8::MAX, batch_flags: 0, - terrain_base_only: false, pipeline_state: LegacyPipelineState::default(), + terrain_base_only: false, alpha_test_reference: 0, }, VulkanStaticDrawRange { @@ -1033,8 +1071,8 @@ mod static_mesh_tests { material_index: 2, lightmap_index: u8::MAX, batch_flags: 0, - terrain_base_only: false, pipeline_state: LegacyPipelineState::default(), + terrain_base_only: false, alpha_test_reference: 0, }, ]; @@ -1101,6 +1139,11 @@ mod static_mesh_tests { }, ]; + assert_eq!( + resolve_draw_texture_indices(&ranges, &materials), + Ok(vec![1, 0]) + ); + let mut screen_tl = materials[0].clone(); screen_tl.screen_tl = true; screen_tl.sky_far_depth = true; @@ -1109,11 +1152,9 @@ mod static_mesh_tests { far_sky.unlit = true; far_sky.sky_far_depth = true; assert_eq!(far_sky.encoded_material_mode(), 5.0); - - assert_eq!( - resolve_draw_texture_indices(&ranges, &materials), - Ok(vec![1, 0]) - ); + let mut nebula_with_stars = far_sky.clone(); + nebula_with_stars.sky_nebula_stars = true; + assert_eq!(nebula_with_stars.encoded_material_mode(), 6.0); let mut non_finite_base = materials.clone(); non_finite_base[0].specular_rgb[0] = f32::NAN; @@ -1149,6 +1190,7 @@ mod static_mesh_tests { fn alpha_cutoff_is_dynamic_and_disabled_outside_alpha_test_pipeline() { let base = VulkanStaticMesh::smoke_triangle().draw_ranges[0]; let disabled = VulkanStaticDrawRange { + terrain_base_only: false, alpha_test_reference: 200, ..base }; @@ -1157,6 +1199,7 @@ mod static_mesh_tests { alpha_test: true, ..LegacyPipelineState::default() }, + terrain_base_only: false, alpha_test_reference: 128, ..base }; @@ -1167,6 +1210,7 @@ mod static_mesh_tests { alpha_test: true, ..LegacyPipelineState::default() }, + terrain_base_only: false, alpha_test_reference: 0, ..base }; @@ -1176,6 +1220,7 @@ mod static_mesh_tests { alpha_test: true, ..LegacyPipelineState::default() }, + terrain_base_only: false, alpha_test_reference: VulkanStaticDrawRange::NATIVE_DEFAULT_ALPHA_TEST_REFERENCE, ..base }; @@ -1472,8 +1517,8 @@ pub struct VulkanSmokeRenderer { pub(super) material_selectors: Vec<u16>, /// Whether the host-side specular records need a fence-safe GPU upload. pub(super) material_specular_dirty: bool, - /// Encoded material mode pushed at offset 124: 0 lit, 1 unlit, 2 native - /// sky nebula/stars texture composite, 3 native lightmap composite. + /// Encoded material mode pushed at offset 124: low bits select the + /// combiner, bit 4 selects sky far-depth, and bit 8 selects screen/TL. pub(super) material_unlit: Vec<f32>, pub(super) readback_enabled: bool, pub(super) camera: VulkanStaticCamera, diff --git a/adapters/fparkan-render-vulkan/src/ffi/swapchain_resources.rs b/adapters/fparkan-render-vulkan/src/ffi/swapchain_resources.rs index 05ce927..973e8b1 100644 --- a/adapters/fparkan-render-vulkan/src/ffi/swapchain_resources.rs +++ b/adapters/fparkan-render-vulkan/src/ffi/swapchain_resources.rs @@ -3,7 +3,8 @@ use ash::vk; use fparkan_platform::DepthStencilSupport; use fparkan_render::{ - LegacyBlendMode, LegacyCullMode, LegacyDepthMode, LegacyPipelineState, PipelineKey, + LegacyBlendMode, LegacyCullMode, LegacyDepthBias, LegacyDepthMode, LegacyPipelineState, + PipelineKey, }; use std::collections::BTreeMap; @@ -935,6 +936,14 @@ pub(super) fn draw_viewport(extent: (u32, u32), material_mode: f32) -> vk::Viewp } } +fn depth_bias_parameters(depth_bias: LegacyDepthBias) -> (bool, f32, f32) { + match depth_bias { + LegacyDepthBias::Disabled => (false, 0.0, 0.0), + // shortcut: Vulkan constantFactor=-1.0/slopeFactor=0.0 is empirical; verify native raster state before claiming parity. + LegacyDepthBias::ProjectedDecal => (true, -1.0, 0.0), + } +} + fn create_graphics_pipeline_cache( device: &VulkanLogicalDeviceProbe, render_pass: vk::RenderPass, @@ -1053,6 +1062,8 @@ fn create_graphics_pipeline( let viewport_state = vk::PipelineViewportStateCreateInfo::default() .viewports(&viewports) .scissors(&scissors); + let (depth_bias_enable, depth_bias_constant_factor, depth_bias_slope_factor) = + depth_bias_parameters(state.depth_bias); let rasterization_state = vk::PipelineRasterizationStateCreateInfo::default() .depth_clamp_enable(false) .rasterizer_discard_enable(false) @@ -1064,7 +1075,10 @@ fn create_graphics_pipeline( LegacyCullMode::FrontFace => vk::CullModeFlags::FRONT, }) .front_face(vk::FrontFace::CLOCKWISE) - .depth_bias_enable(false); + .depth_bias_enable(depth_bias_enable) + .depth_bias_constant_factor(depth_bias_constant_factor) + .depth_bias_slope_factor(depth_bias_slope_factor) + .depth_bias_clamp(0.0); let multisample_state = vk::PipelineMultisampleStateCreateInfo::default() .sample_shading_enable(false) .rasterization_samples(vk::SampleCountFlags::TYPE_1); @@ -1337,7 +1351,20 @@ fn destroy_partial_swapchain_resources( #[cfg(test)] mod tests { - use super::draw_viewport; + use super::{depth_bias_parameters, draw_viewport}; + use fparkan_render::LegacyDepthBias; + + #[test] + fn projected_decal_bias_parameters_use_empirical_vulkan_factors() { + assert_eq!( + depth_bias_parameters(LegacyDepthBias::Disabled), + (false, 0.0, 0.0) + ); + assert_eq!( + depth_bias_parameters(LegacyDepthBias::ProjectedDecal), + (true, -1.0, 0.0) + ); + } #[test] fn world_viewport_depth_keeps_distant_geometry_in_front_of_screen_sun() { |
