aboutsummaryrefslogtreecommitdiff
path: root/adapters/fparkan-render-vulkan/src/ffi/smoke.rs
diff options
context:
space:
mode:
Diffstat (limited to 'adapters/fparkan-render-vulkan/src/ffi/smoke.rs')
-rw-r--r--adapters/fparkan-render-vulkan/src/ffi/smoke.rs503
1 files changed, 335 insertions, 168 deletions
diff --git a/adapters/fparkan-render-vulkan/src/ffi/smoke.rs b/adapters/fparkan-render-vulkan/src/ffi/smoke.rs
index 1b50914..5968a47 100644
--- a/adapters/fparkan-render-vulkan/src/ffi/smoke.rs
+++ b/adapters/fparkan-render-vulkan/src/ffi/smoke.rs
@@ -357,6 +357,28 @@ impl VulkanSmokeRenderer {
.map(|material| material.material_index)
.collect()
};
+ let reflection_planes = super::reflection::mesh_reflection_planes(&create_info.mesh);
+ let unsupported_nonplanar = create_info
+ .mesh
+ .draw_ranges
+ .iter()
+ .zip(&reflection_planes)
+ .filter(|(range, plane)| range.terrain_reflection && plane.is_none())
+ .count();
+ let mut plane_heights = reflection_planes
+ .iter()
+ .flatten()
+ .map(|z| z.to_bits())
+ .collect::<Vec<_>>();
+ 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_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 renderer = Self {
instance: Some(instance),
validation,
@@ -379,6 +401,7 @@ impl VulkanSmokeRenderer {
.map(|range| range.index_count)
.collect(),
draw_ranges: create_info.mesh.draw_ranges.clone(),
+ reflection_planes,
draw_texture_indices,
draw_order: (0..create_info.mesh.draw_ranges.len()).collect(),
draw_range_sort_keys: vec![None; create_info.mesh.draw_ranges.len()],
@@ -521,6 +544,22 @@ impl VulkanSmokeRenderer {
self.frame_uniforms
}
+ fn active_reflection_plane(&self) -> Option<f32> {
+ // shortcut: one target serves the nearest active water height; add per-plane targets
+ // when accepting maps whose visible water surfaces have different heights.
+ self.draw_ranges
+ .iter()
+ .zip(&self.reflection_planes)
+ .filter(|(range, _)| range.index_count != 0)
+ .filter_map(|(_, plane)| *plane)
+ .filter(|&z| z < self.frame_uniforms.camera_position[2])
+ .min_by(|a, b| {
+ (self.frame_uniforms.camera_position[2] - a)
+ .abs()
+ .total_cmp(&(self.frame_uniforms.camera_position[2] - b).abs())
+ })
+ }
+
/// Replaces the camera for subsequent frames without recreating GPU resources.
///
/// # Errors
@@ -1516,6 +1555,27 @@ impl VulkanSmokeRenderer {
}
self.images_in_flight[image_index_usize] = in_flight_fence;
let frame_bytes = self.frame_uniforms.to_ne_bytes();
+ let reflection_bytes = self
+ .active_reflection_plane()
+ .map(|height| {
+ let mut frame = self.frame_uniforms;
+ frame.clip_from_world =
+ fparkan_render::reflect_clip_from_world_about_z(frame.clip_from_world, height)
+ .ok_or(VulkanSmokeRendererError::InvalidStaticCamera {
+ context: "non-finite reflected clip transform",
+ })?;
+ frame.camera_position[2] = 2.0 * height - frame.camera_position[2];
+ if !frame.is_finite() {
+ return Err(VulkanSmokeRendererError::InvalidStaticCamera {
+ context: "non-finite reflected camera position",
+ });
+ }
+ let mut bytes = frame.to_ne_bytes();
+ let length = bytes.len();
+ bytes[length - 4..].copy_from_slice(&height.to_ne_bytes());
+ Ok(bytes)
+ })
+ .transpose()?;
{
let explicit_point_lights = self.point_lights_explicit;
let legacy_point_light = self.frame_uniforms.point_light;
@@ -1541,9 +1601,18 @@ impl VulkanSmokeRenderer {
device,
&resources.frame_uniform_buffer,
resources.frame_uniform_stride,
- self.current_frame,
+ self.current_frame * 2,
&frame_bytes,
)?;
+ if let Some(bytes) = &reflection_bytes {
+ write_frame_uniform(
+ device,
+ &resources.frame_uniform_buffer,
+ resources.frame_uniform_stride,
+ self.current_frame * 2 + 1,
+ bytes,
+ )?;
+ }
write_point_light_buffer(
device,
&resources.point_light_buffer,
@@ -1686,6 +1755,9 @@ impl VulkanSmokeRenderer {
self.textures_ref()?,
&self.texture_slot_indices,
&self.draw_range_base_pipeline_states,
+ self.draw_ranges
+ .iter()
+ .any(|range| range.terrain_reflection),
self.depth_request,
reuse_command_pool,
)?,
@@ -1741,6 +1813,7 @@ impl VulkanSmokeRenderer {
self.rebuild_draw_order();
self.draw_order_dirty = false;
}
+ let reflection_plane = self.active_reflection_plane();
let device = self.device_ref()?;
let swapchain = self.swapchain_ref()?;
let resources = self.resources_ref()?;
@@ -1787,185 +1860,269 @@ impl VulkanSmokeRenderer {
height: swapchain.report.plan.extent.1,
},
};
- let render_pass_info = vk::RenderPassBeginInfo::default()
- .render_pass(resources.render_pass)
- .framebuffer(resources.framebuffers[image_index])
- .render_area(render_area)
- .clear_values(&clear_values);
- // SAFETY: All commands target live frame resources owned by this renderer.
- unsafe {
- device.device().cmd_begin_render_pass(
- command_buffer,
- &render_pass_info,
- vk::SubpassContents::INLINE,
- );
- let vertex_buffers = [self.vertex_buffer_ref()?.buffer];
- let offsets = [0_u64];
- device
- .device()
- .cmd_bind_vertex_buffers(command_buffer, 0, &vertex_buffers, &offsets);
- device.device().cmd_bind_index_buffer(
- command_buffer,
- self.index_buffer_ref()?.buffer,
- 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 {
- context: "stable draw order range handle",
- },
- )?;
- let texture_index = *self.draw_texture_indices.get(range_index).ok_or(
- VulkanSmokeRendererError::InvariantViolation {
- context: "stable draw texture range handle",
- },
- )?;
- let pipeline = resources.pipelines.get(&range.pipeline_key()).ok_or(
- VulkanSmokeRendererError::InvariantViolation {
- context: "static draw pipeline variant",
- },
- )?;
- device.device().cmd_bind_pipeline(
+ for reflected in [true, false] {
+ if reflected && reflection_plane.is_none() {
+ continue;
+ }
+ let framebuffer = if reflected {
+ resources.reflection.framebuffer
+ } else {
+ resources.framebuffers[image_index]
+ };
+ let render_pass_info = vk::RenderPassBeginInfo::default()
+ .render_pass(resources.render_pass)
+ .framebuffer(framebuffer)
+ .render_area(render_area)
+ .clear_values(&clear_values);
+ // SAFETY: All commands target live frame resources owned by this renderer.
+ unsafe {
+ device.device().cmd_begin_render_pass(
command_buffer,
- vk::PipelineBindPoint::GRAPHICS,
- *pipeline,
+ &render_pass_info,
+ vk::SubpassContents::INLINE,
);
- let frame_offset = u32::try_from(
- resources
- .frame_uniform_stride
- .saturating_mul(u64::try_from(self.current_frame).unwrap_or(u64::MAX)),
- )
- .unwrap_or(u32::MAX);
- let material_offset = u32::try_from(
- resources
- .material_specular_stride
- .saturating_mul(u64::try_from(texture_index).unwrap_or(u64::MAX)),
- )
- .unwrap_or(u32::MAX);
- let point_light_offset = u32::try_from(
- resources
- .point_light_stride
- .saturating_mul(u64::try_from(self.current_frame).unwrap_or(u64::MAX)),
- )
- .unwrap_or(u32::MAX);
- let descriptor_set = resources.descriptor_sets.get(texture_index).ok_or(
- VulkanSmokeRendererError::InvariantViolation {
- context: "static material descriptor set",
- },
- )?;
- device.device().cmd_bind_descriptor_sets(
+ let vertex_buffers = [self.vertex_buffer_ref()?.buffer];
+ let offsets = [0_u64];
+ device.device().cmd_bind_vertex_buffers(
command_buffer,
- vk::PipelineBindPoint::GRAPHICS,
- resources.pipeline_layout,
0,
- std::slice::from_ref(descriptor_set),
- &[frame_offset, material_offset, point_light_offset],
+ &vertex_buffers,
+ &offsets,
);
- let alpha_cutoff = range.alpha_test_cutoff().to_ne_bytes();
- device.device().cmd_push_constants(
+ device.device().cmd_bind_index_buffer(
command_buffer,
- resources.pipeline_layout,
- vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT,
+ self.index_buffer_ref()?.buffer,
0,
- &alpha_cutoff,
- );
- let material_alpha = self.material_alphas.get(texture_index).ok_or(
- VulkanSmokeRendererError::InvariantViolation {
- context: "static material alpha",
- },
- )?;
- 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,
- vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT,
- 4,
- &material_alphas,
- );
- let material_lighting = self.material_lighting.get(texture_index).ok_or(
- VulkanSmokeRendererError::InvariantViolation {
- context: "static material lighting",
- },
- )?;
- let material_lighting = material_lighting
- .iter()
- .flatten()
- .flat_map(|value| value.to_ne_bytes())
- .collect::<Vec<_>>();
- device.device().cmd_push_constants(
- command_buffer,
- resources.pipeline_layout,
- vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT,
- 12,
- &material_lighting,
- );
- let material_uv_transforms = self.material_uv_transforms.get(texture_index).ok_or(
- VulkanSmokeRendererError::InvariantViolation {
- context: "static material UV transforms",
- },
- )?;
- let material_uv_transforms = material_uv_transforms
- .iter()
- .flatten()
- .flat_map(|value| value.to_ne_bytes())
- .collect::<Vec<_>>();
- device.device().cmd_push_constants(
- command_buffer,
- resources.pipeline_layout,
- vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT,
- 60,
- &material_uv_transforms,
+ vk::IndexType::UINT32,
);
- let material_mode = self.material_unlit.get(texture_index).ok_or(
- VulkanSmokeRendererError::InvariantViolation {
- context: "static material unlit flag",
- },
- )?;
- let material_mode = if range.terrain_base_only {
- ((*material_mode).round() as i32 | 16) as f32
- } 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(
+ device.device().cmd_bind_descriptor_sets(
command_buffer,
+ vk::PipelineBindPoint::GRAPHICS,
resources.pipeline_layout,
- vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT,
- 124,
- &material_mode.concat(),
- );
- device.device().cmd_draw_indexed(
- command_buffer,
- range.index_count,
1,
- range.first_index,
- 0,
- 0,
+ &[resources.reflection.sets
+ [usize::from(!reflected && reflection_plane.is_some())]],
+ &[],
);
+ let mut viewport_depth = None;
+ for &range_index in &self.draw_order {
+ let range = self.draw_ranges.get(range_index).ok_or(
+ VulkanSmokeRendererError::InvariantViolation {
+ context: "stable draw order range handle",
+ },
+ )?;
+ let texture_index = *self.draw_texture_indices.get(range_index).ok_or(
+ VulkanSmokeRendererError::InvariantViolation {
+ context: "stable draw texture range handle",
+ },
+ )?;
+ let mode = self.material_unlit[texture_index].round() as i32;
+ if reflected && (range.terrain_reflection || mode & 8 != 0) {
+ continue;
+ }
+ let state = if reflected {
+ super::reflection::reflected_pipeline_state(range.pipeline_state)
+ } else {
+ range.pipeline_state
+ };
+ let pipeline = resources.pipelines.get(&state.into()).ok_or(
+ VulkanSmokeRendererError::InvariantViolation {
+ context: "static draw pipeline variant",
+ },
+ )?;
+ device.device().cmd_bind_pipeline(
+ command_buffer,
+ vk::PipelineBindPoint::GRAPHICS,
+ *pipeline,
+ );
+ let frame_offset = u32::try_from(
+ resources.frame_uniform_stride.saturating_mul(
+ u64::try_from(self.current_frame * 2 + usize::from(reflected))
+ .unwrap_or(u64::MAX),
+ ),
+ )
+ .unwrap_or(u32::MAX);
+ let material_offset = u32::try_from(
+ resources
+ .material_specular_stride
+ .saturating_mul(u64::try_from(texture_index).unwrap_or(u64::MAX)),
+ )
+ .unwrap_or(u32::MAX);
+ let point_light_offset = u32::try_from(
+ resources
+ .point_light_stride
+ .saturating_mul(u64::try_from(self.current_frame).unwrap_or(u64::MAX)),
+ )
+ .unwrap_or(u32::MAX);
+ let descriptor_set = resources.descriptor_sets.get(texture_index).ok_or(
+ VulkanSmokeRendererError::InvariantViolation {
+ context: "static material descriptor set",
+ },
+ )?;
+ device.device().cmd_bind_descriptor_sets(
+ command_buffer,
+ vk::PipelineBindPoint::GRAPHICS,
+ resources.pipeline_layout,
+ 0,
+ std::slice::from_ref(descriptor_set),
+ &[frame_offset, material_offset, point_light_offset],
+ );
+ let alpha_cutoff = range.alpha_test_cutoff().to_ne_bytes();
+ device.device().cmd_push_constants(
+ command_buffer,
+ resources.pipeline_layout,
+ vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT,
+ 0,
+ &alpha_cutoff,
+ );
+ let material_alpha = self.material_alphas.get(texture_index).ok_or(
+ VulkanSmokeRendererError::InvariantViolation {
+ context: "static material alpha",
+ },
+ )?;
+ 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,
+ vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT,
+ 4,
+ &material_alphas,
+ );
+ let material_lighting = self.material_lighting.get(texture_index).ok_or(
+ VulkanSmokeRendererError::InvariantViolation {
+ context: "static material lighting",
+ },
+ )?;
+ let material_lighting = material_lighting
+ .iter()
+ .flatten()
+ .flat_map(|value| value.to_ne_bytes())
+ .collect::<Vec<_>>();
+ device.device().cmd_push_constants(
+ command_buffer,
+ resources.pipeline_layout,
+ vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT,
+ 12,
+ &material_lighting,
+ );
+ let material_uv_transforms = self
+ .material_uv_transforms
+ .get(texture_index)
+ .ok_or(VulkanSmokeRendererError::InvariantViolation {
+ context: "static material UV transforms",
+ })?;
+ let material_uv_transforms = material_uv_transforms
+ .iter()
+ .flatten()
+ .flat_map(|value| value.to_ne_bytes())
+ .collect::<Vec<_>>();
+ device.device().cmd_push_constants(
+ command_buffer,
+ resources.pipeline_layout,
+ vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT,
+ 60,
+ &material_uv_transforms,
+ );
+ let material_mode = self.material_unlit.get(texture_index).ok_or(
+ VulkanSmokeRendererError::InvariantViolation {
+ context: "static material unlit flag",
+ },
+ )?;
+ let material_mode = if range.terrain_base_only {
+ ((*material_mode).round() as i32 | 16) as f32
+ } else {
+ *material_mode
+ };
+ let material_mode = (material_mode.round() as i32
+ | if reflected { 32 } else { 0 }
+ | if !reflected
+ && reflection_plane.is_some()
+ && self.reflection_planes[range_index] == reflection_plane
+ {
+ 64
+ } else {
+ 0
+ }) as f32;
+ 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,
+ resources.pipeline_layout,
+ vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT,
+ 124,
+ &material_mode.concat(),
+ );
+ device.device().cmd_draw_indexed(
+ command_buffer,
+ range.index_count,
+ 1,
+ range.first_index,
+ 0,
+ 0,
+ );
+ }
+ device.device().cmd_end_render_pass(command_buffer);
+ if reflected {
+ let image = resources.reflection.color.as_ref().ok_or(
+ VulkanSmokeRendererError::InvariantViolation {
+ context: "reflection target",
+ },
+ )?;
+ let barrier = vk::ImageMemoryBarrier::default()
+ .old_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL)
+ .new_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
+ .src_access_mask(vk::AccessFlags::COLOR_ATTACHMENT_WRITE)
+ .dst_access_mask(vk::AccessFlags::SHADER_READ)
+ .image(image.image)
+ .subresource_range(super::color_subresource_range(1));
+ device.device().cmd_pipeline_barrier(
+ command_buffer,
+ vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT,
+ vk::PipelineStageFlags::FRAGMENT_SHADER,
+ vk::DependencyFlags::empty(),
+ &[],
+ &[],
+ &[barrier],
+ );
+ }
}
- device.device().cmd_end_render_pass(command_buffer);
+ }
+ // SAFETY: The main pass has completed its writes to this acquired swapchain image.
+ unsafe {
+ let barrier = vk::ImageMemoryBarrier::default()
+ .old_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL)
+ .new_layout(vk::ImageLayout::PRESENT_SRC_KHR)
+ .src_access_mask(vk::AccessFlags::COLOR_ATTACHMENT_WRITE)
+ .image(resources.images[image_index])
+ .subresource_range(super::color_subresource_range(1));
+ device.device().cmd_pipeline_barrier(
+ command_buffer,
+ vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT,
+ vk::PipelineStageFlags::BOTTOM_OF_PIPE,
+ vk::DependencyFlags::empty(),
+ &[],
+ &[],
+ &[barrier],
+ );
if self.readback_enabled {
if let (Some(image), Some(readback)) = (
resources.images.get(image_index),
@@ -2028,7 +2185,7 @@ impl VulkanSmokeRenderer {
}
}
- // SAFETY: The render pass owns the attachment layout transitions for this clear-and-present path.
+ // SAFETY: Every render pass and attachment transition has been recorded in this buffer.
unsafe { device.device().end_command_buffer(command_buffer) }.map_err(|error| {
VulkanSmokeRendererError::VulkanOperation {
context: "vkEndCommandBuffer",
@@ -2720,6 +2877,7 @@ mod tests {
batch_flags: 0x108,
pipeline_state: base_state,
terrain_base_only: false,
+ terrain_reflection: false,
alpha_test_reference: 17,
},
VulkanStaticDrawRange {
@@ -2730,6 +2888,7 @@ mod tests {
batch_flags: 0,
pipeline_state: LegacyPipelineState::default(),
terrain_base_only: false,
+ terrain_reflection: false,
alpha_test_reference: 0,
},
];
@@ -2886,6 +3045,7 @@ mod tests {
batch_flags: 0,
pipeline_state: LegacyPipelineState::default(),
terrain_base_only: false,
+ terrain_reflection: false,
alpha_test_reference: 0,
}];
let mut capacities = [6];
@@ -3072,6 +3232,7 @@ mod tests {
batch_flags: 0,
pipeline_state: fparkan_render::LegacyPipelineState::default(),
terrain_base_only: false,
+ terrain_reflection: false,
alpha_test_reference: 0,
})
.collect::<Vec<_>>();
@@ -3096,6 +3257,7 @@ mod tests {
batch_flags: 0,
pipeline_state: fparkan_render::LegacyPipelineState::default(),
terrain_base_only: false,
+ terrain_reflection: false,
alpha_test_reference: 0,
},
super::super::VulkanStaticDrawRange {
@@ -3106,6 +3268,7 @@ mod tests {
batch_flags: 0,
pipeline_state: fparkan_render::LegacyPipelineState::default(),
terrain_base_only: false,
+ terrain_reflection: false,
alpha_test_reference: 0,
},
super::super::VulkanStaticDrawRange {
@@ -3116,6 +3279,7 @@ mod tests {
batch_flags: 0,
pipeline_state: fparkan_render::LegacyPipelineState::default(),
terrain_base_only: false,
+ terrain_reflection: false,
alpha_test_reference: 0,
},
];
@@ -3140,6 +3304,7 @@ mod tests {
batch_flags: 0,
pipeline_state: fparkan_render::LegacyPipelineState::default(),
terrain_base_only: false,
+ terrain_reflection: false,
alpha_test_reference: 0,
})
.collect::<Vec<_>>();
@@ -3233,6 +3398,7 @@ mod tests {
batch_flags: 0,
pipeline_state: LegacyPipelineState::default(),
terrain_base_only: false,
+ terrain_reflection: false,
alpha_test_reference: 0,
})
.collect::<Vec<_>>();
@@ -3291,6 +3457,7 @@ mod tests {
batch_flags: 0,
pipeline_state: LegacyPipelineState::default(),
terrain_base_only: false,
+ terrain_reflection: false,
alpha_test_reference: 0,
})
.collect::<Vec<_>>();