use ash::vk;
use concinnity_core::components::SdfVolume;
use concinnity_core::components::sdf_programs::SdfPrograms;
use concinnity_core::gfx::mesh_payload::Vertex;
use concinnity_core::gfx::render_types::{LightUniforms, ShadowUniforms};
use concinnity_core::platform::Platform;
use concinnity_core::render::backend_init::SdfVolumeSource;
use concinnity_core::render::error::{RenderError, RenderResult};
use concinnity_core::render::shader_programs::raymarch::{Family, ProxyFaces, VolumeFlags};
use concinnity_core::transform::mat4_inverse;
use super::allocator::{DeviceAllocator, PooledBuffer};
use super::context::{HDR_FORMAT, VkContext};
use super::descriptor_layout::{Binding, PoolSizes};
use super::pipeline::GraphicsStages;
use super::pipeline_desc::{Blend, Depth, GraphicsPipelineDesc, Raster};
use super::record::cmd_push_constants;
use super::render_pass::{create_main_render_pass_two_pass, main_framebuffer_dependency};
use super::resources::{alloc_descriptor_sets, create_descriptor_set_layout};
use super::set_writes::SetWrites;
use super::texture::{
GpuImage, ImageSpec, LayoutTransition, SubresourceRange, create_image, create_image_view,
one_shot_submit, transition_image_layout_range,
};
use crate::shader::raymarch_source::{face_artifacts, family_artifacts};
use crate::vulkan::owned::{
OwnedDescriptorPool, OwnedPipeline, OwnedPipelineLayout, OwnedRenderPass, OwnedSetLayout,
VkDevice,
};
use crate::vulkan::post::gbuffer::GbufferPrepassView;
const CUBE_INDEX_COUNT: u32 = 36;
pub(in crate::vulkan) use concinnity_core::render::uniforms::{
RaymarchView, RaymarchVolumeUniforms,
};
use concinnity_core::render::uniforms::{GBufferView, PassCamera, RaymarchShadowCascade};
mod swap;
pub(in crate::vulkan) fn volume_uniforms_from(v: &SdfVolume) -> RaymarchVolumeUniforms {
RaymarchVolumeUniforms {
center: v.center,
_pad0: 0.0,
extent: v.extent,
_pad1: 0.0,
cone_ratio: v.cone_ratio(),
max_distance: v.max_distance,
max_steps: v.max_steps as i32,
receive_shadows: if v.receive_shadows { 1 } else { 0 },
params: v.params,
}
}
fn copy_scene_snapshot(
device: &ash::Device,
cmd: vk::CommandBuffer,
hdr_resolve: vk::Image,
snapshot: vk::Image,
extent: vk::Extent2D,
) {
let color_aspect = vk::ImageSubresourceRange {
aspect_mask: vk::ImageAspectFlags::COLOR,
base_mip_level: 0,
level_count: 1,
base_array_layer: 0,
layer_count: 1,
};
let open = |image: vk::Image, new: vk::ImageLayout, dst: vk::AccessFlags| {
vk::ImageMemoryBarrier::default()
.src_access_mask(vk::AccessFlags::SHADER_READ)
.dst_access_mask(dst)
.old_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
.new_layout(new)
.src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.image(image)
.subresource_range(color_aspect)
};
let layers = vk::ImageSubresourceLayers {
aspect_mask: vk::ImageAspectFlags::COLOR,
mip_level: 0,
base_array_layer: 0,
layer_count: 1,
};
let region = vk::ImageCopy::default()
.src_subresource(layers)
.dst_subresource(layers)
.extent(vk::Extent3D {
width: extent.width,
height: extent.height,
depth: 1,
});
unsafe {
device.cmd_pipeline_barrier(
cmd,
vk::PipelineStageFlags::COMPUTE_SHADER | vk::PipelineStageFlags::FRAGMENT_SHADER,
vk::PipelineStageFlags::TRANSFER,
vk::DependencyFlags::empty(),
&[],
&[],
&[
open(
hdr_resolve,
vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
vk::AccessFlags::TRANSFER_READ,
),
open(
snapshot,
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
vk::AccessFlags::TRANSFER_WRITE,
),
],
);
device.cmd_copy_image(
cmd,
hdr_resolve,
vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
snapshot,
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
std::slice::from_ref(®ion),
);
}
}
struct RaymarchVolumeRecord {
label: String,
flags: VolumeFlags,
pipeline: OwnedPipeline,
front_pipeline: Option<OwnedPipeline>,
shadow_pipeline: Option<OwnedPipeline>,
prepass_pipelines: Option<FacePipelines>,
center: [f32; 3],
extent: [f32; 3],
_volume_ubo: PooledBuffer,
volume_set: vk::DescriptorSet,
visible: bool,
refractive: bool,
}
impl RaymarchVolumeRecord {
fn faces(&self, view: &RaymarchView) -> ProxyFaces {
if self.flags.volumetric {
ProxyFaces::Back
} else {
ProxyFaces::for_box(view, self.center, self.extent)
}
}
fn draw_pipeline(&self, view: &RaymarchView) -> vk::Pipeline {
match (self.faces(view), &self.front_pipeline) {
(ProxyFaces::Front, Some(front)) => front.handle(),
_ => self.pipeline.handle(),
}
}
}
pub(in crate::vulkan) struct RaymarchResources {
render_pass: OwnedRenderPass,
pub(in crate::vulkan) main_store_color_pass: Option<OwnedRenderPass>,
pipeline_layout: OwnedPipelineLayout,
_view_set_layout: OwnedSetLayout,
_volume_set_layout: OwnedSetLayout,
_descriptor_pool: OwnedDescriptorPool,
view_ubos: Vec<PooledBuffer>,
view_sets: Vec<vk::DescriptorSet>,
cube_vb: PooledBuffer,
cube_ib: PooledBuffer,
snapshot: GpuImage,
shadow_pipeline_layout: OwnedPipelineLayout,
_shadow_view_set_layout: OwnedSetLayout,
shadow_view_ubos: Vec<PooledBuffer>,
shadow_view_sets: Vec<vk::DescriptorSet>,
prepass_render_pass: OwnedRenderPass,
prepass_view_ubos: Vec<PooledBuffer>,
prepass_view_sets: Vec<vk::DescriptorSet>,
msaa: bool,
volumes: Vec<RaymarchVolumeRecord>,
}
fn cube_vertex(pos: [f32; 3]) -> Vertex {
Vertex {
pos,
normal: [0.0; 3],
tangent: [0.0; 3],
color: [0.0; 3],
uv: [0.0; 2],
}
}
type CubeBuffers = (PooledBuffer, PooledBuffer);
fn build_cube_buffers(alloc: &DeviceAllocator) -> RenderResult<CubeBuffers> {
#[rustfmt::skip]
let corners: [Vertex; 8] = [
cube_vertex([-1.0, -1.0, -1.0]),
cube_vertex([ 1.0, -1.0, -1.0]),
cube_vertex([ 1.0, 1.0, -1.0]),
cube_vertex([-1.0, 1.0, -1.0]),
cube_vertex([-1.0, -1.0, 1.0]),
cube_vertex([ 1.0, -1.0, 1.0]),
cube_vertex([ 1.0, 1.0, 1.0]),
cube_vertex([-1.0, 1.0, 1.0]),
];
#[rustfmt::skip]
let indices: [u16; 36] = [
0, 2, 1, 0, 3, 2, 4, 5, 6, 4, 6, 7, 0, 4, 7, 0, 7, 3, 1, 2, 6, 1, 6, 5, 0, 1, 5, 0, 5, 4, 3, 7, 6, 3, 6, 2, ];
let vb_bytes = std::mem::size_of_val(&corners) as u64;
let ib_bytes = std::mem::size_of_val(&indices) as u64;
let host = vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT;
let vb = alloc.create_buffer(vb_bytes, vk::BufferUsageFlags::VERTEX_BUFFER, host)?;
let ib = alloc.create_buffer(ib_bytes, vk::BufferUsageFlags::INDEX_BUFFER, host)?;
vb.write_slice(0, &corners);
ib.write_slice(0, &indices);
Ok((vb, ib))
}
fn create_raymarch_render_pass_single(
device: &VkDevice,
format: vk::Format,
) -> RenderResult<OwnedRenderPass> {
let attachments = [
vk::AttachmentDescription::default()
.format(format)
.samples(vk::SampleCountFlags::TYPE_1)
.load_op(vk::AttachmentLoadOp::LOAD)
.store_op(vk::AttachmentStoreOp::STORE)
.stencil_load_op(vk::AttachmentLoadOp::DONT_CARE)
.stencil_store_op(vk::AttachmentStoreOp::DONT_CARE)
.initial_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
.final_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL),
vk::AttachmentDescription::default()
.format(vk::Format::D32_SFLOAT)
.samples(vk::SampleCountFlags::TYPE_1)
.load_op(vk::AttachmentLoadOp::LOAD)
.store_op(vk::AttachmentStoreOp::STORE)
.stencil_load_op(vk::AttachmentLoadOp::DONT_CARE)
.stencil_store_op(vk::AttachmentStoreOp::DONT_CARE)
.initial_layout(vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL)
.final_layout(vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL),
];
let color_ref = vk::AttachmentReference::default()
.attachment(0)
.layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL);
let depth_ref = vk::AttachmentReference::default()
.attachment(1)
.layout(vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
let subpass = vk::SubpassDescription::default()
.pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS)
.color_attachments(std::slice::from_ref(&color_ref))
.depth_stencil_attachment(&depth_ref);
let dependency = main_framebuffer_dependency();
let info = vk::RenderPassCreateInfo::default()
.attachments(&attachments)
.subpasses(std::slice::from_ref(&subpass))
.dependencies(std::slice::from_ref(&dependency));
device
.create_render_pass(&info)
.map_err(|e| super::error::map_vk_result(e, "raymarch render pass"))
}
fn view_set_bindings() -> [Binding; 10] {
use vk::DescriptorType as T;
let frag = vk::ShaderStageFlags::FRAGMENT;
let vert_frag = vk::ShaderStageFlags::VERTEX | frag;
[
(0, T::UNIFORM_BUFFER, vert_frag), (1, T::UNIFORM_BUFFER, frag), (2, T::UNIFORM_BUFFER, frag), (3, T::SAMPLED_IMAGE, frag), (4, T::SAMPLED_IMAGE, frag), (5, T::SAMPLED_IMAGE, frag), (6, T::SAMPLED_IMAGE, frag), (8, T::SAMPLER, frag), (9, T::SAMPLER, frag), (10, T::SAMPLER, frag), ]
}
fn volume_set_bindings() -> [Binding; 1] {
[(
0,
vk::DescriptorType::UNIFORM_BUFFER,
vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT,
)]
}
#[derive(Clone, Copy)]
struct ViewRings {
shadow: bool,
prepass: bool,
}
impl ViewRings {
fn of(volumes: &[SdfVolumeSource]) -> Self {
Self {
shadow: volumes.iter().any(|s| VolumeFlags::of(&s.volume).casts()),
prepass: volumes.iter().any(|s| !s.volume.volumetric),
}
}
}
fn create_descriptor_pool(
device: &VkDevice,
frames: usize,
volumes: usize,
rings: ViewRings,
) -> RenderResult<OwnedDescriptorPool> {
let f = frames as u32;
let v = volumes as u32;
let shadow_sets = f * (u32::from(rings.shadow) + u32::from(rings.prepass));
let sizes = PoolSizes::default()
.sets(&view_set_bindings(), f)
.sets(&volume_set_bindings(), v)
.sets(&shadow_view_set_bindings(), shadow_sets)
.build();
let info = vk::DescriptorPoolCreateInfo::default()
.max_sets(f + v + shadow_sets)
.pool_sizes(&sizes);
device
.create_descriptor_pool(&info)
.map_err(|e| super::error::map_vk_result(e, "raymarch descriptor pool"))
}
fn shadow_view_set_bindings() -> [Binding; 3] {
let frag = vk::ShaderStageFlags::FRAGMENT;
let vert_frag = vk::ShaderStageFlags::VERTEX | frag;
let ubo = vk::DescriptorType::UNIFORM_BUFFER;
[
(0, ubo, vert_frag), (1, ubo, frag), (2, ubo, vert_frag), ]
}
fn write_shadow_view_set(
device: &VkDevice,
set: vk::DescriptorSet,
view_ubo: vk::Buffer,
light_ubo: vk::Buffer,
shadow_ubo: vk::Buffer,
) {
SetWrites::new(set)
.uniform_buffer(0, view_ubo, size_of::<RaymarchView>() as u64)
.uniform_buffer(1, light_ubo, size_of::<LightUniforms>() as u64)
.uniform_buffer(2, shadow_ubo, size_of::<ShadowUniforms>() as u64)
.apply(device);
}
#[derive(Clone, Copy)]
struct RaymarchViewSetBuffers {
view_ubo: vk::Buffer,
light_ubo: vk::Buffer,
shadow_ubo: vk::Buffer,
}
#[derive(Clone, Copy)]
struct RaymarchViewSetTextures {
shadow_map_view: vk::ImageView,
shadow_sampler: vk::Sampler,
irradiance_view: vk::ImageView,
prefilter_view: vk::ImageView,
cube_sampler: vk::Sampler,
snapshot_view: vk::ImageView,
scene_sampler: vk::Sampler,
}
fn write_view_set(
device: &VkDevice,
set: vk::DescriptorSet,
buffers: RaymarchViewSetBuffers,
textures: RaymarchViewSetTextures,
) {
let RaymarchViewSetBuffers {
view_ubo,
light_ubo,
shadow_ubo,
} = buffers;
let RaymarchViewSetTextures {
shadow_map_view,
shadow_sampler,
irradiance_view,
prefilter_view,
cube_sampler,
snapshot_view,
scene_sampler,
} = textures;
SetWrites::new(set)
.uniform_buffer(0, view_ubo, size_of::<RaymarchView>() as u64)
.uniform_buffer(1, light_ubo, size_of::<LightUniforms>() as u64)
.uniform_buffer(2, shadow_ubo, size_of::<ShadowUniforms>() as u64)
.sampled_image(3, shadow_map_view)
.sampled_image(4, irradiance_view)
.sampled_image(5, prefilter_view)
.sampled_image(6, snapshot_view)
.sampler(8, shadow_sampler)
.sampler(9, cube_sampler)
.sampler(10, scene_sampler)
.apply(device);
}
fn write_volume_set(device: &VkDevice, set: vk::DescriptorSet, volume_ubo: vk::Buffer) {
SetWrites::new(set)
.uniform_buffer(0, volume_ubo, size_of::<RaymarchVolumeUniforms>() as u64)
.apply(device);
}
const CUBE_VERTEX_BINDINGS: [vk::VertexInputBindingDescription; 1] =
[vk::VertexInputBindingDescription {
binding: 0,
stride: size_of::<Vertex>() as u32,
input_rate: vk::VertexInputRate::VERTEX,
}];
const CUBE_VERTEX_ATTRIBUTES: [vk::VertexInputAttributeDescription; 1] =
[vk::VertexInputAttributeDescription {
location: 0,
binding: 0,
format: vk::Format::R32G32B32_SFLOAT,
offset: 0,
}];
const fn cube_raster(faces: ProxyFaces) -> Raster {
Raster {
cull: match faces {
ProxyFaces::Front => vk::CullModeFlags::BACK,
ProxyFaces::Back => vk::CullModeFlags::FRONT,
},
front_face: vk::FrontFace::COUNTER_CLOCKWISE,
polygon_mode: vk::PolygonMode::FILL,
bias: None,
}
}
fn cube_proxy<'a>(
(vert_spv, frag_spv): (&'a [u8], &'a [u8]),
faces: ProxyFaces,
layout: vk::PipelineLayout,
render_pass: vk::RenderPass,
color_targets: &'a [Blend],
) -> GraphicsPipelineDesc<'a> {
GraphicsPipelineDesc {
raster: cube_raster(faces),
vertex_bindings: &CUBE_VERTEX_BINDINGS,
vertex_attributes: &CUBE_VERTEX_ATTRIBUTES,
..GraphicsPipelineDesc::fullscreen(vert_spv, frag_spv, layout, render_pass, color_targets)
}
}
fn create_pipeline(
device: &VkDevice,
t: &VolumePipelineTargets,
spv: (&[u8], &[u8]),
faces: ProxyFaces,
) -> RenderResult<OwnedPipeline> {
GraphicsPipelineDesc {
depth: Depth::write_inclusive(),
samples: t.msaa_samples,
..cube_proxy(spv, faces, t.layout, t.render_pass, &[Blend::Opaque])
}
.build(device, "raymarch")
}
fn create_volumetric_pipeline(
device: &VkDevice,
render_pass: vk::RenderPass,
layout: vk::PipelineLayout,
msaa_samples: vk::SampleCountFlags,
vert_spv: &[u8],
frag_spv: &[u8],
) -> RenderResult<OwnedPipeline> {
let modules = GraphicsStages::new(device, vert_spv, frag_spv)?;
let stages = modules.infos();
let vertex_input = vk::PipelineVertexInputStateCreateInfo::default()
.vertex_binding_descriptions(&CUBE_VERTEX_BINDINGS)
.vertex_attribute_descriptions(&CUBE_VERTEX_ATTRIBUTES);
let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::default()
.topology(vk::PrimitiveTopology::TRIANGLE_LIST);
let viewport_state = vk::PipelineViewportStateCreateInfo::default()
.viewport_count(1)
.scissor_count(1);
let raster = vk::PipelineRasterizationStateCreateInfo::default()
.polygon_mode(vk::PolygonMode::FILL)
.cull_mode(vk::CullModeFlags::FRONT)
.front_face(vk::FrontFace::COUNTER_CLOCKWISE)
.line_width(1.0);
let multisample =
vk::PipelineMultisampleStateCreateInfo::default().rasterization_samples(msaa_samples);
let depth_stencil = Depth::read_only().raw();
let blend_attachment = vk::PipelineColorBlendAttachmentState::default()
.blend_enable(true)
.src_color_blend_factor(vk::BlendFactor::SRC_ALPHA)
.dst_color_blend_factor(vk::BlendFactor::ONE_MINUS_SRC_ALPHA)
.color_blend_op(vk::BlendOp::ADD)
.src_alpha_blend_factor(vk::BlendFactor::ONE)
.dst_alpha_blend_factor(vk::BlendFactor::ONE_MINUS_SRC_ALPHA)
.alpha_blend_op(vk::BlendOp::ADD)
.color_write_mask(vk::ColorComponentFlags::RGBA);
let blend_attachments = [blend_attachment];
let blend_state = vk::PipelineColorBlendStateCreateInfo::default()
.logic_op_enable(false)
.attachments(&blend_attachments);
let dynamic_states = [vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR];
let dynamic = vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&dynamic_states);
let info = vk::GraphicsPipelineCreateInfo::default()
.stages(&stages)
.vertex_input_state(&vertex_input)
.input_assembly_state(&input_assembly)
.viewport_state(&viewport_state)
.rasterization_state(&raster)
.multisample_state(&multisample)
.depth_stencil_state(&depth_stencil)
.color_blend_state(&blend_state)
.dynamic_state(&dynamic)
.layout(layout)
.render_pass(render_pass);
let pipeline = crate::vulkan::pipeline_cache::create_graphics_pipeline(device, &info)
.map_err(|e| super::error::map_vk_result(e, "create raymarch volumetric pipeline"))?;
Ok(pipeline)
}
fn create_shadow_pipeline(
device: &VkDevice,
shadow_render_pass: vk::RenderPass,
layout: vk::PipelineLayout,
vert_spv: &[u8],
frag_spv: &[u8],
) -> RenderResult<OwnedPipeline> {
GraphicsPipelineDesc {
depth: Depth::write(),
..cube_proxy(
(vert_spv, frag_spv),
ProxyFaces::Back,
layout,
shadow_render_pass,
&[],
)
}
.build(device, "raymarch shadow")
}
fn create_prepass_pipeline(
device: &VkDevice,
t: &VolumePipelineTargets,
spv: (&[u8], &[u8]),
faces: ProxyFaces,
) -> RenderResult<OwnedPipeline> {
GraphicsPipelineDesc {
depth: Depth::write_inclusive(),
..cube_proxy(
spv,
faces,
t.shadow_layout,
t.prepass_render_pass,
&crate::vulkan::post::gbuffer::PREPASS_TARGETS,
)
}
.build(device, "raymarch prepass")
}
fn create_snapshot(
alloc: &DeviceAllocator,
device: &VkDevice,
command_pool: vk::CommandPool,
queue: vk::Queue,
width: u32,
height: u32,
) -> RenderResult<GpuImage> {
let pooled = create_image(
alloc,
&ImageSpec {
width: width.max(1),
height: height.max(1),
format: HDR_FORMAT,
tiling: vk::ImageTiling::OPTIMAL,
usage: vk::ImageUsageFlags::SAMPLED | vk::ImageUsageFlags::TRANSFER_DST,
mem_props: vk::MemoryPropertyFlags::DEVICE_LOCAL,
samples: vk::SampleCountFlags::TYPE_1,
},
)?;
let image = pooled.image();
one_shot_submit(device, command_pool, queue, |cmd| {
transition_image_layout_range(
device,
cmd,
image,
LayoutTransition {
old_layout: vk::ImageLayout::UNDEFINED,
new_layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
aspect: vk::ImageAspectFlags::COLOR,
},
SubresourceRange {
base_layer: 0,
layer_count: 1,
base_mip: 0,
mip_count: 1,
},
);
})?;
let view = create_image_view(device, image, HDR_FORMAT, vk::ImageAspectFlags::COLOR)?;
Ok(GpuImage::from_pooled(pooled, view))
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(in crate::vulkan) struct VolumePipelineTargets {
render_pass: vk::RenderPass,
layout: vk::PipelineLayout,
shadow_render_pass: vk::RenderPass,
shadow_layout: vk::PipelineLayout,
prepass_render_pass: vk::RenderPass,
msaa_samples: vk::SampleCountFlags,
hot_reload: bool,
}
pub(in crate::vulkan) struct FacePipelines {
front: OwnedPipeline,
back: OwnedPipeline,
}
impl FacePipelines {
fn get(&self, faces: ProxyFaces) -> vk::Pipeline {
match faces {
ProxyFaces::Front => self.front.handle(),
ProxyFaces::Back => self.back.handle(),
}
}
}
pub(in crate::vulkan) struct VolumePipelines {
pipeline: OwnedPipeline,
front_pipeline: Option<OwnedPipeline>,
shadow_pipeline: Option<OwnedPipeline>,
prepass_pipelines: Option<FacePipelines>,
}
pub(in crate::vulkan) fn build_volume_pipelines(
device: &VkDevice,
t: &VolumePipelineTargets,
programs: &SdfPrograms,
flags: VolumeFlags,
label: &str,
) -> RenderResult<VolumePipelines> {
let artifacts = |family, faces| {
face_artifacts(
programs,
family,
faces,
Platform::Vulkan,
t.hot_reload,
label,
)
};
let surface = |faces| {
let (vert, frag) = artifacts(Family::Surface, faces)?;
create_pipeline(device, t, (&vert, &frag), faces)
};
let (pipeline, front_pipeline) = if flags.volumetric {
let (vert, frag) = family_artifacts(
programs,
Family::Volumetric,
Platform::Vulkan,
t.hot_reload,
label,
)?;
let medium = create_volumetric_pipeline(
device,
t.render_pass,
t.layout,
t.msaa_samples,
&vert,
&frag,
)?;
(medium, None)
} else {
(
surface(ProxyFaces::Back)?,
Some(surface(ProxyFaces::Front)?),
)
};
let shadow_pipeline = if flags.casts() {
let (sh_vert, sh_frag) = family_artifacts(
programs,
Family::Shadow,
Platform::Vulkan,
t.hot_reload,
label,
)?;
Some(create_shadow_pipeline(
device,
t.shadow_render_pass,
t.shadow_layout,
&sh_vert,
&sh_frag,
)?)
} else {
None
};
let prepass = |faces| {
let (vert, frag) = artifacts(Family::Prepass, faces)?;
create_prepass_pipeline(device, t, (&vert, &frag), faces)
};
let prepass_pipelines = if flags.volumetric {
None
} else {
Some(FacePipelines {
front: prepass(ProxyFaces::Front)?,
back: prepass(ProxyFaces::Back)?,
})
};
Ok(VolumePipelines {
pipeline,
front_pipeline,
shadow_pipeline,
prepass_pipelines,
})
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct RaymarchDeviceContext<'a> {
pub(in crate::vulkan) alloc: &'a DeviceAllocator,
pub(in crate::vulkan) device: &'a VkDevice,
pub(in crate::vulkan) command_pool: vk::CommandPool,
pub(in crate::vulkan) queue: vk::Queue,
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct RaymarchTargetConfig {
pub(in crate::vulkan) frames: usize,
pub(in crate::vulkan) msaa_samples: vk::SampleCountFlags,
pub(in crate::vulkan) width: u32,
pub(in crate::vulkan) height: u32,
}
#[derive(Clone, Copy)]
pub(in crate::vulkan) struct RaymarchSharedBindings<'a> {
pub(in crate::vulkan) shadow_map_view: vk::ImageView,
pub(in crate::vulkan) shadow_sampler: vk::Sampler,
pub(in crate::vulkan) irradiance_view: vk::ImageView,
pub(in crate::vulkan) prefilter_view: vk::ImageView,
pub(in crate::vulkan) cube_sampler: vk::Sampler,
pub(in crate::vulkan) linear_sampler: vk::Sampler,
pub(in crate::vulkan) light_ubos: &'a [PooledBuffer],
pub(in crate::vulkan) shadow_ubos: &'a [PooledBuffer],
pub(in crate::vulkan) shadow_render_pass: vk::RenderPass,
}
impl RaymarchResources {
pub(in crate::vulkan) fn volume_targets(
&self,
shadow_render_pass: vk::RenderPass,
msaa_samples: vk::SampleCountFlags,
hot_reload: bool,
) -> VolumePipelineTargets {
VolumePipelineTargets {
render_pass: self.render_pass.handle(),
layout: self.pipeline_layout.handle(),
shadow_render_pass,
shadow_layout: self.shadow_pipeline_layout.handle(),
prepass_render_pass: self.prepass_render_pass.handle(),
msaa_samples,
hot_reload,
}
}
pub(in crate::vulkan) fn try_new(
ctx: RaymarchDeviceContext,
target: RaymarchTargetConfig,
bindings: RaymarchSharedBindings,
sdf_volumes: &[SdfVolumeSource],
hot_reload: bool,
) -> RenderResult<Option<Self>> {
let RaymarchDeviceContext {
alloc,
device,
command_pool,
queue,
} = ctx;
let RaymarchTargetConfig {
frames,
msaa_samples,
width,
height,
} = target;
let RaymarchSharedBindings {
shadow_map_view,
shadow_sampler,
irradiance_view,
prefilter_view,
cube_sampler,
linear_sampler,
light_ubos,
shadow_ubos,
shadow_render_pass,
} = bindings;
if sdf_volumes.is_empty() {
return Ok(None);
}
let msaa = msaa_samples != vk::SampleCountFlags::TYPE_1;
let (render_pass, main_store_color_pass) = if msaa {
(
create_main_render_pass_two_pass(device, HDR_FORMAT, msaa_samples, true)?,
Some(create_main_render_pass_two_pass(
device,
HDR_FORMAT,
msaa_samples,
false,
)?),
)
} else {
(
create_raymarch_render_pass_single(device, HDR_FORMAT)?,
None,
)
};
let view_set_layout = create_descriptor_set_layout(device, &view_set_bindings())?;
let volume_set_layout = create_descriptor_set_layout(device, &volume_set_bindings())?;
let set_layouts = [view_set_layout.handle(), volume_set_layout.handle()];
let pipeline_layout = {
let info = vk::PipelineLayoutCreateInfo::default().set_layouts(&set_layouts);
device
.create_pipeline_layout(&info)
.map_err(|e| super::error::map_vk_result(e, "raymarch pipeline layout"))?
};
let (cube_vb, cube_ib) = build_cube_buffers(alloc)?;
let snapshot = create_snapshot(alloc, device, command_pool, queue, width, height)?;
let view_size = std::mem::size_of::<RaymarchView>() as u64;
let mut view_ubos = Vec::with_capacity(frames);
for _ in 0..frames {
view_ubos.push(alloc.create_buffer(
view_size,
vk::BufferUsageFlags::UNIFORM_BUFFER,
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
)?);
}
let rings = ViewRings::of(sdf_volumes);
let descriptor_pool = create_descriptor_pool(device, frames, sdf_volumes.len(), rings)?;
let view_layouts: Vec<_> = (0..frames).map(|_| view_set_layout.handle()).collect();
let view_sets = alloc_descriptor_sets(device, descriptor_pool.handle(), &view_layouts)?;
for (i, &set) in view_sets.iter().enumerate() {
write_view_set(
device,
set,
RaymarchViewSetBuffers {
view_ubo: view_ubos[i].buffer(),
light_ubo: light_ubos[i].buffer(),
shadow_ubo: shadow_ubos[i].buffer(),
},
RaymarchViewSetTextures {
shadow_map_view,
shadow_sampler,
irradiance_view,
prefilter_view,
cube_sampler,
snapshot_view: snapshot.view,
scene_sampler: linear_sampler,
},
);
}
let mut shadow_pipeline_layout = OwnedPipelineLayout::null();
let mut shadow_view_set_layout = OwnedSetLayout::null();
let mut shadow_view_ubos: Vec<PooledBuffer> = Vec::new();
let mut shadow_view_sets: Vec<vk::DescriptorSet> = Vec::new();
let mut prepass_render_pass = OwnedRenderPass::null();
let mut prepass_view_ubos: Vec<PooledBuffer> = Vec::new();
let mut prepass_view_sets: Vec<vk::DescriptorSet> = Vec::new();
if rings.shadow || rings.prepass {
shadow_view_set_layout =
create_descriptor_set_layout(device, &shadow_view_set_bindings())?;
let set_layouts = [shadow_view_set_layout.handle(), volume_set_layout.handle()];
let push = vk::PushConstantRange::default()
.stage_flags(vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT)
.offset(0)
.size(std::mem::size_of::<RaymarchShadowCascade>() as u32);
let info = vk::PipelineLayoutCreateInfo::default()
.set_layouts(&set_layouts)
.push_constant_ranges(std::slice::from_ref(&push));
shadow_pipeline_layout = device
.create_pipeline_layout(&info)
.map_err(|e| super::error::map_vk_result(e, "raymarch shadow pipeline layout"))?;
}
let three_ubo_ring =
|ubos: &mut Vec<PooledBuffer>| -> RenderResult<Vec<vk::DescriptorSet>> {
for _ in 0..frames {
ubos.push(alloc.create_buffer(
view_size,
vk::BufferUsageFlags::UNIFORM_BUFFER,
vk::MemoryPropertyFlags::HOST_VISIBLE
| vk::MemoryPropertyFlags::HOST_COHERENT,
)?);
}
let layouts: Vec<_> = (0..frames)
.map(|_| shadow_view_set_layout.handle())
.collect();
let sets = alloc_descriptor_sets(device, descriptor_pool.handle(), &layouts)?;
for (i, &set) in sets.iter().enumerate() {
write_shadow_view_set(
device,
set,
ubos[i].buffer(),
light_ubos[i].buffer(),
shadow_ubos[i].buffer(),
);
}
Ok(sets)
};
if rings.shadow {
shadow_view_sets = three_ubo_ring(&mut shadow_view_ubos)?;
}
if rings.prepass {
prepass_view_sets = three_ubo_ring(&mut prepass_view_ubos)?;
prepass_render_pass = crate::vulkan::post::gbuffer::create_prepass_render_pass(device)?;
}
let mut volumes: Vec<RaymarchVolumeRecord> = Vec::with_capacity(sdf_volumes.len());
for SdfVolumeSource {
volume: vol,
fragment_source: payload,
label,
} in sdf_volumes
{
let programs = crate::shader::raymarch_source::decode(payload, label)
.map_err(RenderError::Other)?;
let flags = VolumeFlags::of(vol);
let VolumePipelines {
pipeline,
front_pipeline,
shadow_pipeline,
prepass_pipelines,
} = build_volume_pipelines(
device,
&VolumePipelineTargets {
render_pass: render_pass.handle(),
layout: pipeline_layout.handle(),
shadow_render_pass,
shadow_layout: shadow_pipeline_layout.handle(),
prepass_render_pass: prepass_render_pass.handle(),
msaa_samples,
hot_reload,
},
&programs,
flags,
label,
)?;
let uniforms = volume_uniforms_from(vol);
let volume_ubo = alloc.create_buffer(
std::mem::size_of::<RaymarchVolumeUniforms>() as u64,
vk::BufferUsageFlags::UNIFORM_BUFFER,
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
)?;
volume_ubo.write_val(0, &uniforms);
let volume_set = alloc_descriptor_sets(
device,
descriptor_pool.handle(),
&[volume_set_layout.handle()],
)?[0];
write_volume_set(device, volume_set, volume_ubo.buffer());
volumes.push(RaymarchVolumeRecord {
label: label.clone(),
flags,
pipeline,
front_pipeline,
shadow_pipeline,
prepass_pipelines,
center: vol.center,
extent: vol.extent,
_volume_ubo: volume_ubo,
volume_set,
visible: vol.visible,
refractive: crate::shader::raymarch_source::taps_scene(&programs),
});
}
Ok(Some(Self {
render_pass,
main_store_color_pass,
pipeline_layout,
_view_set_layout: view_set_layout,
_volume_set_layout: volume_set_layout,
_descriptor_pool: descriptor_pool,
view_ubos,
view_sets,
cube_vb,
cube_ib,
snapshot,
shadow_pipeline_layout,
_shadow_view_set_layout: shadow_view_set_layout,
shadow_view_ubos,
shadow_view_sets,
prepass_render_pass,
prepass_view_ubos,
prepass_view_sets,
msaa,
volumes,
}))
}
pub(in crate::vulkan) fn any_visible(&self) -> bool {
self.volumes.iter().any(|v| v.visible)
}
fn any_refractive_visible(&self) -> bool {
self.volumes.iter().any(|v| v.visible && v.refractive)
}
fn any_shadow_casters(&self) -> bool {
self.volumes
.iter()
.any(|v| v.visible && v.shadow_pipeline.is_some())
}
pub(in crate::vulkan) fn rebuild(
&mut self,
ctx: RaymarchDeviceContext,
width: u32,
height: u32,
) -> RenderResult<()> {
let RaymarchDeviceContext {
alloc,
device,
command_pool,
queue,
} = ctx;
let old = std::mem::replace(
&mut self.snapshot,
create_snapshot(alloc, device, command_pool, queue, width, height)?,
);
drop(old);
for &set in &self.view_sets {
SetWrites::new(set)
.sampled_image(6, self.snapshot.view)
.apply(device);
}
Ok(())
}
pub(in crate::vulkan) fn rewire_ibl_cubes(
&self,
device: &VkDevice,
irradiance_view: vk::ImageView,
prefilter_view: vk::ImageView,
) {
for &set in &self.view_sets {
SetWrites::new(set)
.sampled_image(4, irradiance_view)
.sampled_image(5, prefilter_view)
.apply(device);
}
}
pub(in crate::vulkan) fn destroy(&mut self, _device: &VkDevice) {
self.volumes.clear();
self.view_ubos.clear();
self.shadow_view_ubos.clear();
self.prepass_view_ubos.clear();
self.snapshot = GpuImage::null();
self.cube_vb = PooledBuffer::null();
self.cube_ib = PooledBuffer::null();
}
}
impl VkContext {
pub(in crate::vulkan) fn build_raymarch_view(
&self,
vp: [[f32; 4]; 4],
cam_pos: [f32; 3],
time: f32,
) -> RaymarchView {
RaymarchView::new(&self.pass_camera(vp, cam_pos, time))
}
pub(in crate::vulkan) fn pass_camera(
&self,
vp: [[f32; 4]; 4],
cam_pos: [f32; 3],
time: f32,
) -> PassCamera {
PassCamera {
vp,
inv_vp: mat4_inverse(vp),
cam_pos,
viewport: [
self.targets.render_extent.width as f32,
self.targets.render_extent.height as f32,
],
time,
prefilter_mip_count: self.scene.prefilter_mip_count as f32,
sky_rot: self.state.view.sky_rot,
}
}
pub(in crate::vulkan) fn upload_raymarch_shadow_view(&self, frame_idx: usize, elapsed: f32) {
let Some(rm) = self.raymarch.as_ref() else {
return;
};
if !rm.any_shadow_casters() {
return;
}
let Some(ubo) = rm.shadow_view_ubos.get(frame_idx) else {
return;
};
let view = RaymarchView {
vp: [[0.0; 4]; 4],
inv_vp: [[0.0; 4]; 4],
cam_pos: [0.0; 4],
viewport: [0.0, 0.0],
time: elapsed,
prefilter_mip_count: 0.0,
sky_rot: concinnity_core::sky::SkyOrientation::IDENTITY_ROWS,
cur_vp: [[0.0; 4]; 4],
prev_vp: [[0.0; 4]; 4],
view_mat: [[0.0; 4]; 4],
};
ubo.write_val(0, &view);
}
pub(in crate::vulkan) fn encode_sdf_shadow_cascade(
&self,
cmd: vk::CommandBuffer,
frame_idx: usize,
cascade_idx: usize,
) {
let Some(rm) = self.raymarch.as_ref() else {
return;
};
if !rm.any_shadow_casters() || rm.shadow_view_sets.is_empty() {
return;
}
let device = &self.hw.device;
let push = RaymarchShadowCascade {
cascade_idx: cascade_idx as u32,
_pad: [0; 3],
};
unsafe {
device.cmd_bind_vertex_buffers(cmd, 0, &[rm.cube_vb.buffer()], &[0]);
device.cmd_bind_index_buffer(cmd, rm.cube_ib.buffer(), 0, vk::IndexType::UINT16);
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::GRAPHICS,
rm.shadow_pipeline_layout.handle(),
0,
std::slice::from_ref(&rm.shadow_view_sets[frame_idx]),
&[],
);
cmd_push_constants(
device,
cmd,
rm.shadow_pipeline_layout.handle(),
vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT,
&push,
);
for vol in &rm.volumes {
let Some(shadow_pipeline) = vol.shadow_pipeline.as_ref() else {
continue;
};
if !vol.visible {
continue;
}
device.cmd_bind_pipeline(
cmd,
vk::PipelineBindPoint::GRAPHICS,
shadow_pipeline.handle(),
);
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::GRAPHICS,
rm.shadow_pipeline_layout.handle(),
1,
std::slice::from_ref(&vol.volume_set),
&[],
);
device.cmd_draw_indexed(cmd, CUBE_INDEX_COUNT, 1, 0, 0, 0);
self.inc_draw_calls(1);
}
}
}
pub(in crate::vulkan) fn encode_raymarch_prepass(
&self,
cmd: vk::CommandBuffer,
frame_idx: usize,
prepass: &GbufferPrepassView,
gbuffer: &GBufferView,
) {
let Some(rm) = self.raymarch.as_ref() else {
return;
};
let (Some(ubo), Some(&set)) = (
rm.prepass_view_ubos.get(frame_idx),
rm.prepass_view_sets.get(frame_idx),
) else {
return;
};
let surfaces = || {
rm.volumes
.iter()
.filter(|v| v.visible)
.filter_map(|v| v.prepass_pipelines.as_ref().map(|p| (v, p)))
};
if surfaces().next().is_none() {
return;
}
let camera = self.pass_camera(prepass.jittered_vp, prepass.cam_pos, prepass.elapsed);
let view = RaymarchView::for_gbuffer(&camera, gbuffer);
ubo.write_val(0, &view);
let draws = surfaces().map(|(v, p)| (p.get(v.faces(&view)), v.volume_set));
let device = &self.hw.device;
let layout = rm.shadow_pipeline_layout.handle();
let push = RaymarchShadowCascade {
cascade_idx: 0,
_pad: [0; 3],
};
unsafe {
device.cmd_bind_vertex_buffers(cmd, 0, &[rm.cube_vb.buffer()], &[0]);
device.cmd_bind_index_buffer(cmd, rm.cube_ib.buffer(), 0, vk::IndexType::UINT16);
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::GRAPHICS,
layout,
0,
std::slice::from_ref(&set),
&[],
);
cmd_push_constants(
device,
cmd,
layout,
vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT,
&push,
);
for (pipeline, volume_set) in draws {
device.cmd_bind_pipeline(cmd, vk::PipelineBindPoint::GRAPHICS, pipeline);
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::GRAPHICS,
layout,
1,
std::slice::from_ref(&volume_set),
&[],
);
device.cmd_draw_indexed(cmd, CUBE_INDEX_COUNT, 1, 0, 0, 0);
self.inc_draw_calls(1);
}
}
}
pub(in crate::vulkan) fn encode_raymarch(
&self,
cmd: vk::CommandBuffer,
frame_idx: usize,
view: &RaymarchView,
) -> RenderResult<()> {
let Some(rm) = self.raymarch.as_ref() else {
return Ok(());
};
if !rm.any_visible() {
return Ok(());
}
let device = &self.hw.device;
let extent = self.targets.render_extent;
let hdr_resolve = self
.targets
.hdr_resolve_images
.get(frame_idx)
.ok_or_else(|| RenderError::Other("raymarch: hdr_resolve index OOB".to_string()))?
.image;
let snapshot = rm.snapshot.image;
let refract = rm.any_refractive_visible();
rm.view_ubos
.get(frame_idx)
.ok_or_else(|| RenderError::Other("raymarch: view_ubos index OOB".to_string()))?
.write_val(0, view);
let color_aspect = vk::ImageSubresourceRange {
aspect_mask: vk::ImageAspectFlags::COLOR,
base_mip_level: 0,
level_count: 1,
base_array_layer: 0,
layer_count: 1,
};
let barrier = |image: vk::Image,
old: vk::ImageLayout,
new: vk::ImageLayout,
src: vk::AccessFlags,
dst: vk::AccessFlags| {
vk::ImageMemoryBarrier::default()
.src_access_mask(src)
.dst_access_mask(dst)
.old_layout(old)
.new_layout(new)
.src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.image(image)
.subresource_range(color_aspect)
};
if refract {
copy_scene_snapshot(device, cmd, hdr_resolve, snapshot, extent);
}
let snapshot_to_read = barrier(
snapshot,
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
vk::AccessFlags::TRANSFER_WRITE,
vk::AccessFlags::SHADER_READ,
);
let load_barrier = vk::MemoryBarrier::default()
.src_access_mask(
vk::AccessFlags::COLOR_ATTACHMENT_WRITE
| vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE
| vk::AccessFlags::TRANSFER_READ,
)
.dst_access_mask(
vk::AccessFlags::COLOR_ATTACHMENT_READ
| vk::AccessFlags::COLOR_ATTACHMENT_WRITE
| vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_READ
| vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE,
);
let hdr_to_read = barrier(
hdr_resolve,
vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
vk::AccessFlags::TRANSFER_READ,
vk::AccessFlags::COLOR_ATTACHMENT_READ,
);
let image_barriers = [snapshot_to_read, hdr_to_read];
let image_barriers = match (refract, rm.msaa) {
(false, _) => &image_barriers[..0],
(true, true) => &image_barriers[..1],
(true, false) => &image_barriers[..],
};
unsafe {
device.cmd_pipeline_barrier(
cmd,
vk::PipelineStageFlags::TRANSFER
| vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT
| vk::PipelineStageFlags::LATE_FRAGMENT_TESTS,
vk::PipelineStageFlags::FRAGMENT_SHADER
| vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT
| vk::PipelineStageFlags::EARLY_FRAGMENT_TESTS,
vk::DependencyFlags::empty(),
std::slice::from_ref(&load_barrier),
&[],
image_barriers,
);
}
let rp_begin = vk::RenderPassBeginInfo::default()
.render_pass(rm.render_pass.handle())
.framebuffer(self.targets.framebuffers[frame_idx].handle())
.render_area(vk::Rect2D::default().extent(extent));
let vp = vk::Viewport {
x: 0.0,
y: extent.height as f32,
width: extent.width as f32,
height: -(extent.height as f32),
min_depth: 0.0,
max_depth: 1.0,
};
let scissor = vk::Rect2D::default().extent(extent);
unsafe {
device.cmd_begin_render_pass(cmd, &rp_begin, vk::SubpassContents::INLINE);
device.cmd_set_viewport(cmd, 0, std::slice::from_ref(&vp));
device.cmd_set_scissor(cmd, 0, std::slice::from_ref(&scissor));
device.cmd_bind_vertex_buffers(cmd, 0, &[rm.cube_vb.buffer()], &[0]);
device.cmd_bind_index_buffer(cmd, rm.cube_ib.buffer(), 0, vk::IndexType::UINT16);
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::GRAPHICS,
rm.pipeline_layout.handle(),
0,
std::slice::from_ref(&rm.view_sets[frame_idx]),
&[],
);
for vol in &rm.volumes {
if !vol.visible {
continue;
}
device.cmd_bind_pipeline(
cmd,
vk::PipelineBindPoint::GRAPHICS,
vol.draw_pipeline(view),
);
device.cmd_bind_descriptor_sets(
cmd,
vk::PipelineBindPoint::GRAPHICS,
rm.pipeline_layout.handle(),
1,
std::slice::from_ref(&vol.volume_set),
&[],
);
device.cmd_draw_indexed(cmd, CUBE_INDEX_COUNT, 1, 0, 0, 0);
self.inc_draw_calls(1);
}
device.cmd_end_render_pass(cmd);
}
Ok(())
}
}