diff options
Diffstat (limited to 'adapters')
| -rw-r--r-- | adapters/fparkan-render-vulkan/shaders/triangle.frag | 19 | ||||
| -rw-r--r-- | adapters/fparkan-render-vulkan/shaders/triangle.frag.spv | bin | 12292 -> 14512 bytes | |||
| -rw-r--r-- | adapters/fparkan-render-vulkan/shaders/triangle.vert | 6 | ||||
| -rw-r--r-- | adapters/fparkan-render-vulkan/shaders/triangle.vert.spv | bin | 20300 -> 20384 bytes | |||
| -rw-r--r-- | adapters/fparkan-render-vulkan/src/ffi/reflection.rs | 129 | ||||
| -rw-r--r-- | adapters/fparkan-render-vulkan/src/ffi/smoke.rs | 13 | ||||
| -rw-r--r-- | adapters/fparkan-render-vulkan/src/ffi/smoke_types.rs | 47 | ||||
| -rw-r--r-- | adapters/fparkan-render-vulkan/src/ffi/swapchain_resources.rs | 20 |
8 files changed, 203 insertions, 31 deletions
diff --git a/adapters/fparkan-render-vulkan/shaders/triangle.frag b/adapters/fparkan-render-vulkan/shaders/triangle.frag index ec0acdb..5d10152 100644 --- a/adapters/fparkan-render-vulkan/shaders/triangle.frag +++ b/adapters/fparkan-render-vulkan/shaders/triangle.frag @@ -9,7 +9,7 @@ layout(location = 5) in vec3 in_base_diffuse; layout(location = 6) in vec3 in_base_specular; layout(location = 7) in vec3 in_overlay_diffuse; layout(location = 8) in vec3 in_overlay_specular; -layout(location = 9) in float in_world_z; +layout(location = 9) in vec3 in_world_position; layout(location = 0) out vec4 out_color; layout(set = 0, binding = 0) uniform sampler2D base_color; @@ -17,6 +17,7 @@ layout(set = 0, binding = 1) uniform sampler2D base_detail; layout(set = 0, binding = 2) uniform sampler2D overlay_color; layout(set = 0, binding = 3) uniform sampler2D overlay_detail; layout(set = 1, binding = 0) uniform sampler2D reflection_color; +layout(set = 1, binding = 1) uniform sampler2D embm_bump; layout(std140, set = 0, binding = 4) uniform FrameConstants { mat4 clip_from_world; @@ -30,6 +31,8 @@ layout(std140, set = 0, binding = 4) uniform FrameConstants { vec4 fog_color; vec4 fog_distances; vec4 camera_position; + vec4 embm_bounds; + vec4 embm_wave; } frame; layout(push_constant) uniform MaterialConstants { @@ -69,11 +72,23 @@ layout(push_constant) uniform MaterialConstants { void main() { int mode = int(round(material.material_mode)); - if ((mode & 32) != 0 && (mode & 4) == 0 && in_world_z < frame.camera_position.w) { + if ((mode & 32) != 0 && (mode & 4) == 0 && in_world_position.z < frame.camera_position.w) { discard; } if ((mode & 64) != 0) { vec2 reflection_uv = gl_FragCoord.xy / vec2(textureSize(reflection_color, 0)); + vec2 span = frame.embm_bounds.zw - frame.embm_bounds.xy; + vec2 plane_uv = vec2(1.0) - (in_world_position.yx - frame.embm_bounds.yx) / span.yx; + vec2 bump_uv = plane_uv * frame.embm_wave.y + frame.embm_wave.x; + vec2 duv = max(vec2(-1.0), (texture(embm_bump, bump_uv).rg * 255.0 - 128.0) / 127.0); + vec2 displacement = duv * frame.embm_wave.zw; + // shortcut: convert native map-UV displacement to the current mirror projection; + // replace this projection when accepting the original shifted reflection camera. + vec3 displaced_world = in_world_position - vec3(displacement.yx * span, 0.0); + vec4 displaced_clip = frame.clip_from_world * vec4(displaced_world, 1.0); + if (displaced_clip.w > 0.000001) { + reflection_uv = displaced_clip.xy / displaced_clip.w * 0.5 + 0.5; + } vec3 rgb = texture(reflection_color, reflection_uv).rgb * in_base_diffuse + in_base_specular; out_color = vec4(mix(rgb * in_color, frame.fog_color.rgb, in_fog_factor), material.diffuse_alpha); if (out_color.a < material.alpha_cutoff) { diff --git a/adapters/fparkan-render-vulkan/shaders/triangle.frag.spv b/adapters/fparkan-render-vulkan/shaders/triangle.frag.spv Binary files differindex 84c8a0c..de3ace6 100644 --- a/adapters/fparkan-render-vulkan/shaders/triangle.frag.spv +++ b/adapters/fparkan-render-vulkan/shaders/triangle.frag.spv diff --git a/adapters/fparkan-render-vulkan/shaders/triangle.vert b/adapters/fparkan-render-vulkan/shaders/triangle.vert index 889a379..8e04536 100644 --- a/adapters/fparkan-render-vulkan/shaders/triangle.vert +++ b/adapters/fparkan-render-vulkan/shaders/triangle.vert @@ -19,7 +19,7 @@ layout(location = 5) out vec3 out_base_diffuse; layout(location = 6) out vec3 out_base_specular; layout(location = 7) out vec3 out_overlay_diffuse; layout(location = 8) out vec3 out_overlay_specular; -layout(location = 9) out float out_world_z; +layout(location = 9) out vec3 out_world_position; layout(std140, set = 0, binding = 4) uniform FrameConstants { mat4 clip_from_world; @@ -33,6 +33,8 @@ layout(std140, set = 0, binding = 4) uniform FrameConstants { vec4 fog_color; vec4 fog_distances; vec4 camera_position; + vec4 embm_bounds; + vec4 embm_wave; } frame; layout(std430, set = 0, binding = 5) readonly buffer MaterialSpecular { @@ -242,7 +244,7 @@ LightingTerms native_material_lighting( } void main() { - out_world_z = in_position.z; + out_world_position = in_position; out_color = in_color; out_uv = in_uv; out_detail_uv = in_detail_uv; diff --git a/adapters/fparkan-render-vulkan/shaders/triangle.vert.spv b/adapters/fparkan-render-vulkan/shaders/triangle.vert.spv Binary files differindex ff28c23..439e2a0 100644 --- a/adapters/fparkan-render-vulkan/shaders/triangle.vert.spv +++ b/adapters/fparkan-render-vulkan/shaders/triangle.vert.spv diff --git a/adapters/fparkan-render-vulkan/src/ffi/reflection.rs b/adapters/fparkan-render-vulkan/src/ffi/reflection.rs index eb4025e..62a0d1e 100644 --- a/adapters/fparkan-render-vulkan/src/ffi/reflection.rs +++ b/adapters/fparkan-render-vulkan/src/ffi/reflection.rs @@ -36,14 +36,42 @@ pub(super) fn mesh_reflection_planes(mesh: &super::VulkanStaticMesh) -> Vec<Opti .collect() } +pub(super) fn mesh_reflection_bounds(mesh: &super::VulkanStaticMesh) -> Option<[f32; 4]> { + let mut bounds = [ + f32::INFINITY, + f32::INFINITY, + f32::NEG_INFINITY, + f32::NEG_INFINITY, + ]; + for range in mesh + .draw_ranges + .iter() + .filter(|range| range.terrain_reflection) + { + for &index in &mesh.indices + [range.first_index as usize..range.first_index as usize + range.index_count as usize] + { + let position = mesh.vertices[index as usize].position; + bounds[0] = bounds[0].min(position[0]); + bounds[1] = bounds[1].min(position[1]); + bounds[2] = bounds[2].max(position[0]); + bounds[3] = bounds[3].max(position[1]); + } + } + (bounds.iter().all(|v| v.is_finite()) && bounds[2] > bounds[0] && bounds[3] > bounds[1]) + .then_some(bounds) +} + /// Owned before pipeline creation; a dummy set prevents attachment feedback in the mirror pass. pub(super) struct ReflectionResources { device: ash::Device, pub layout: vk::DescriptorSetLayout, pool: vk::DescriptorPool, sampler: vk::Sampler, + bump_sampler: vk::Sampler, pub sets: [vk::DescriptorSet; 2], dummy: Option<VulkanAllocatedImage>, + bump: Option<VulkanAllocatedImage>, pub color: Option<VulkanAllocatedImage>, depth: Option<VulkanDepthAttachment>, pub framebuffer: vk::Framebuffer, @@ -54,25 +82,31 @@ impl ReflectionResources { instance: &VulkanInstanceProbe, device: &VulkanLogicalDeviceProbe, command_pool: vk::CommandPool, + enabled: bool, ) -> Result<Self, VulkanSmokeRendererError> { let mut owned = Self { device: device.device().clone(), layout: vk::DescriptorSetLayout::null(), pool: vk::DescriptorPool::null(), sampler: vk::Sampler::null(), + bump_sampler: vk::Sampler::null(), sets: [vk::DescriptorSet::null(); 2], dummy: None, + bump: None, color: None, depth: None, framebuffer: vk::Framebuffer::null(), }; - let binding = vk::DescriptorSetLayoutBinding::default() - .binding(0) - .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) - .descriptor_count(1) - .stage_flags(vk::ShaderStageFlags::FRAGMENT); - let layout_info = - vk::DescriptorSetLayoutCreateInfo::default().bindings(std::slice::from_ref(&binding)); + let bindings: Vec<_> = (0..2) + .map(|binding| { + vk::DescriptorSetLayoutBinding::default() + .binding(binding) + .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) + .descriptor_count(1) + .stage_flags(vk::ShaderStageFlags::FRAGMENT) + }) + .collect(); + let layout_info = vk::DescriptorSetLayoutCreateInfo::default().bindings(&bindings); // SAFETY: The device is live; owned rolls back every handle on later failure. owned.layout = unsafe { owned @@ -85,7 +119,7 @@ impl ReflectionResources { })?; let size = vk::DescriptorPoolSize::default() .ty(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) - .descriptor_count(2); + .descriptor_count(4); let pool_info = vk::DescriptorPoolCreateInfo::default() .max_sets(2) .pool_sizes(std::slice::from_ref(&size)); @@ -136,6 +170,42 @@ impl ReflectionResources { mip_levels: Vec::new(), }, )?); + if enabled { + let sampler = sampler + .address_mode_u(vk::SamplerAddressMode::REPEAT) + .address_mode_v(vk::SamplerAddressMode::REPEAT) + .address_mode_w(vk::SamplerAddressMode::REPEAT); + // SAFETY: The immutable bump map uses supported linear filtering and a single mip level. + owned.bump_sampler = + unsafe { owned.device.create_sampler(&sampler, None) }.map_err(|result| { + VulkanSmokeRendererError::VulkanOperation { + context: "vkCreateSampler(EMBM)", + result, + } + })?; + let rgba8 = fparkan_render::water::legacy_embm_bump_map() + .into_iter() + .flat_map(|duv| { + [ + (i16::from(duv[0]) + 128) as u8, + (i16::from(duv[1]) + 128) as u8, + 0, + 255, + ] + }) + .collect(); + owned.bump = Some(super::create_static_texture_image( + instance, + device, + command_pool, + &super::VulkanStaticTexture { + width: fparkan_render::water::EMBM_BUMP_SIZE, + height: fparkan_render::water::EMBM_BUMP_SIZE, + rgba8, + mip_levels: Vec::new(), + }, + )?); + } for set in owned.sets { owned.write_image(set, owned.dummy.as_ref().expect("dummy image exists").view); } @@ -255,13 +325,35 @@ impl ReflectionResources { .sampler(self.sampler) .image_view(view) .image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL); - let write = vk::WriteDescriptorSet::default() - .dst_set(set) - .dst_binding(0) - .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) - .image_info(std::slice::from_ref(&image)); + let bump = self + .bump + .as_ref() + .or(self.dummy.as_ref()) + .expect("dummy image exists"); + let images = [ + image, + vk::DescriptorImageInfo::default() + .sampler(if self.bump.is_some() { + self.bump_sampler + } else { + self.sampler + }) + .image_view(bump.view) + .image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL), + ]; + let writes: Vec<_> = images + .iter() + .enumerate() + .map(|(binding, image)| { + vk::WriteDescriptorSet::default() + .dst_set(set) + .dst_binding(binding as u32) + .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) + .image_info(std::slice::from_ref(image)) + }) + .collect(); // SAFETY: Sets are initialized before submission; the dummy remains valid for every pass. - unsafe { self.device.update_descriptor_sets(&[write], &[]) }; + unsafe { self.device.update_descriptor_sets(&writes, &[]) }; } } @@ -273,12 +365,14 @@ impl Drop for ReflectionResources { self.device.destroy_framebuffer(self.framebuffer, None); self.device.destroy_descriptor_pool(self.pool, None); self.device.destroy_sampler(self.sampler, None); + self.device.destroy_sampler(self.bump_sampler, None); self.device.destroy_descriptor_set_layout(self.layout, None); for image in self .color .iter() .chain(self.depth.iter().map(|depth| &depth.image)) .chain(self.dummy.iter()) + .chain(self.bump.iter()) { self.device.destroy_image_view(image.view, None); self.device.destroy_image(image.image, None); @@ -320,12 +414,19 @@ mod tests { alpha_test_reference: 1, }], }; + mesh.vertices[1].position = [10.0, 0.0, 150.0]; + mesh.vertices[2].position = [5.0, 20.0, 150.0]; + let mut unused = mesh.vertices[0]; + unused.position = [-10_000.0, -10_000.0, 150.0]; + mesh.vertices.push(unused); + assert_eq!(mesh_reflection_bounds(&mesh), Some([0.0, 0.0, 10.0, 20.0])); assert_eq!(mesh_reflection_planes(&mesh), vec![Some(150.0)]); mesh.vertices[2].position[2] = 151.0; assert_eq!(mesh_reflection_planes(&mesh), vec![None]); mesh.vertices[2].position[2] = 150.0; mesh.draw_ranges[0].terrain_reflection = false; assert_eq!(mesh_reflection_planes(&mesh), vec![None]); + assert_eq!(mesh_reflection_bounds(&mesh), None); for cull in [ fparkan_render::LegacyCullMode::Disabled, fparkan_render::LegacyCullMode::BackFace, diff --git a/adapters/fparkan-render-vulkan/src/ffi/smoke.rs b/adapters/fparkan-render-vulkan/src/ffi/smoke.rs index 5968a47..bd745ad 100644 --- a/adapters/fparkan-render-vulkan/src/ffi/smoke.rs +++ b/adapters/fparkan-render-vulkan/src/ffi/smoke.rs @@ -373,12 +373,17 @@ impl VulkanSmokeRenderer { plane_heights.sort_unstable(); plane_heights.dedup(); if !plane_heights.is_empty() { - eprintln!("render_coverage planar_reflection_embm_unsupported={} (flat reflection; native shifted camera and wave distortion pending)", plane_heights.len()); + eprintln!("render_coverage planar_reflection_shifted_camera_unsupported={} (native bump map and clock; using a projective mirror until shifted-camera mapping is implemented)", plane_heights.len()); eprintln!("render_coverage planar_reflection_other_planes_unsupported={} (nearest active plane is reflected)", plane_heights.len().saturating_sub(1)); } if unsupported_nonplanar != 0 { eprintln!("render_coverage planar_reflection_nonplanar_unsupported={unsupported_nonplanar} (ordinary terrain fallback)"); } + let mut frame_uniforms = super::VulkanFrameUniforms::from_camera(create_info.camera); + if let Some(bounds) = super::reflection::mesh_reflection_bounds(&create_info.mesh) { + frame_uniforms.embm_bounds = bounds; + eprintln!("render_reflection embm_bounds={bounds:?}"); + } let mut renderer = Self { instance: Some(instance), validation, @@ -424,7 +429,7 @@ impl VulkanSmokeRenderer { material_specular_dirty: true, readback_enabled: false, camera: create_info.camera, - frame_uniforms: super::VulkanFrameUniforms::from_camera(create_info.camera), + frame_uniforms, point_lights: Vec::new(), point_lights_explicit: false, frame_sync: Vec::new(), @@ -1571,8 +1576,8 @@ impl VulkanSmokeRenderer { }); } let mut bytes = frame.to_ne_bytes(); - let length = bytes.len(); - bytes[length - 4..].copy_from_slice(&height.to_ne_bytes()); + let offset = super::smoke_types::REFLECTION_CLIP_HEIGHT_OFFSET; + bytes[offset..offset + 4].copy_from_slice(&height.to_ne_bytes()); Ok(bytes) }) .transpose()?; diff --git a/adapters/fparkan-render-vulkan/src/ffi/smoke_types.rs b/adapters/fparkan-render-vulkan/src/ffi/smoke_types.rs index bc8aedd..90fd7b4 100644 --- a/adapters/fparkan-render-vulkan/src/ffi/smoke_types.rs +++ b/adapters/fparkan-render-vulkan/src/ffi/smoke_types.rs @@ -192,7 +192,8 @@ impl Default for VulkanPointLight { /// every device. Four directional lights and one point light fit in a small /// dynamic UBO while the push-constant range remains available for material /// state. -pub const VULKAN_FRAME_UNIFORM_BYTES: usize = 368; +pub const VULKAN_FRAME_UNIFORM_BYTES: usize = 400; +pub(super) const REFLECTION_CLIP_HEIGHT_OFFSET: usize = 364; /// Number of CPU/GPU frame slots shared by the swapchain renderer. pub const VULKAN_FRAME_SLOT_COUNT: usize = 2; @@ -235,6 +236,10 @@ pub struct VulkanFrameUniforms { pub fog_end: f32, /// World-space camera position used for fog distance. pub camera_position: [f32; 3], + /// XY bounds of the original reflective faces: min X/Y, max X/Y. + pub embm_bounds: [f32; 4], + /// Native wave scroll fraction in the ten-second cycle. + pub embm_phase: f32, } impl VulkanFrameUniforms { @@ -258,6 +263,8 @@ impl VulkanFrameUniforms { fog_start: f32::MAX, fog_end: f32::MAX, camera_position: [0.0; 3], + embm_bounds: [0.0, 0.0, 1.0, 1.0], + embm_phase: 0.0, } } @@ -283,7 +290,14 @@ impl VulkanFrameUniforms { .chain(self.fog_color) .chain([self.fog_start, self.fog_end]) .chain(self.camera_position) + .chain(self.embm_bounds) + .chain([self.embm_phase]) .all(f32::is_finite) + && self.embm_bounds[2] > self.embm_bounds[0] + && self.embm_bounds[3] > self.embm_bounds[1] + && (self.embm_bounds[2] - self.embm_bounds[0]).is_finite() + && (self.embm_bounds[3] - self.embm_bounds[1]).is_finite() + && (0.0..1.0).contains(&self.embm_phase) && (self.fog_end <= self.fog_start || ((self.fog_start * self.fog_start).is_finite() && (self.fog_end * self.fog_end).is_finite())) @@ -364,6 +378,13 @@ impl VulkanFrameUniforms { self.camera_position[2], 0.0, ]); + values[92..96].copy_from_slice(&self.embm_bounds); + values[96..100].copy_from_slice(&[ + self.embm_phase, + fparkan_render::water::EMBM_BUMP_TILE, + fparkan_render::water::EMBM_COEFFICIENTS[0], + fparkan_render::water::EMBM_COEFFICIENTS[1], + ]); let mut bytes = [0_u8; VULKAN_FRAME_UNIFORM_BYTES]; for (index, value) in values.into_iter().enumerate() { let offset = index * std::mem::size_of::<f32>(); @@ -862,6 +883,8 @@ mod static_mesh_tests { fog_start: 13.0, fog_end: 14.0, camera_position: [15.0, 16.0, 17.0], + embm_bounds: [100.0, 200.0, 300.0, 600.0], + embm_phase: 0.25, }; let bytes = frame.to_ne_bytes(); assert_eq!(bytes.len(), VULKAN_FRAME_UNIFORM_BYTES); @@ -880,6 +903,28 @@ mod static_mesh_tests { assert_eq!(read(320), 10.0); assert_eq!(read(336), 13.0); assert_eq!(read(352), 15.0); + assert_eq!(read(368), 100.0); + assert_eq!(read(380), 600.0); + assert_eq!(read(384), 0.25); + assert_eq!(read(388), 100.0); + assert_eq!(read(392), 0.01); + } + + #[test] + fn embm_uniforms_reject_invalid_bounds_and_phase() { + for (bounds, phase) in [ + ([0.0; 4], 0.0), + ([-f32::MAX, 0.0, f32::MAX, 1.0], 0.0), + ([0.0, 0.0, 1.0, 1.0], f32::NAN), + ([0.0, 0.0, 1.0, 1.0], 1.0), + ] { + assert!(!VulkanFrameUniforms { + embm_bounds: bounds, + embm_phase: phase, + ..Default::default() + } + .is_finite()); + } } #[test] diff --git a/adapters/fparkan-render-vulkan/src/ffi/swapchain_resources.rs b/adapters/fparkan-render-vulkan/src/ffi/swapchain_resources.rs index 4365cd2..af20dfa 100644 --- a/adapters/fparkan-render-vulkan/src/ffi/swapchain_resources.rs +++ b/adapters/fparkan-render-vulkan/src/ffi/swapchain_resources.rs @@ -249,14 +249,18 @@ pub(super) fn create_swapchain_resources( }; partial.frame_uniform_stride = frame_uniform_stride; partial.frame_uniform_buffer = Some(frame_uniform_buffer); - partial.reflection = - match super::reflection::ReflectionResources::new(instance, device, command_pool) { - Ok(reflection) => Some(reflection), - Err(error) => { - destroy_partial_swapchain_resources(device, command_pool, partial); - return Err(error); - } - }; + partial.reflection = match super::reflection::ReflectionResources::new( + instance, + device, + command_pool, + reflection_enabled, + ) { + Ok(reflection) => Some(reflection), + Err(error) => { + destroy_partial_swapchain_resources(device, command_pool, partial); + return Err(error); + } + }; let material_count = texture_slot_indices.len() / 4; let (material_specular_buffer, material_specular_stride) = match create_material_specular_buffer(instance, device, material_count) { |
