use concinnity_core::gfx::transform::mat4_inverse;
use std::ffi::CString;
use ash::vk;
use crate::vulkan::owned::{
OwnedDescriptorPool, OwnedPipeline, OwnedPipelineLayout, OwnedRenderPass, OwnedSetLayout,
VkDevice,
};
use super::allocator::{DeviceAllocator, PooledBuffer};
use crate::components::SdfVolume;
use crate::gfx::mesh_payload::Vertex;
use crate::gfx::render_types::{LightUniforms, ShadowUniforms};
use super::context::{HDR_FORMAT, VkContext};
use super::pipeline::{compile_glsl, shader_source, spv_module};
use super::render_pass::create_main_render_pass_two_pass;
use super::texture::{
GpuImage, ImageSpec, LayoutTransition, SubresourceRange, create_image, create_image_view,
one_shot_submit, transition_image_layout_range,
};
const RAYMARCH_HELPERS_GLSL: &str = include_str!("shaders/raymarch_helpers.glsl");
const RAYMARCH_TEMPLATE_GLSL: &str = include_str!("shaders/raymarch_template.glsl");
const RAYMARCH_VOLUMETRIC_TEMPLATE_GLSL: &str =
include_str!("shaders/raymarch_volumetric_template.glsl");
const RAYMARCH_SHADOW_TEMPLATE_GLSL: &str = include_str!("shaders/raymarch_shadow_template.glsl");
const CUBE_INDEX_COUNT: u32 = 36;
pub(in crate::vulkan) use crate::vulkan::uniforms::RaymarchView;
pub(in crate::vulkan) use crate::vulkan::uniforms::RaymarchVolumeUniforms;
pub(in crate::vulkan) fn volume_uniforms_from(v: &SdfVolume) -> RaymarchVolumeUniforms {
RaymarchVolumeUniforms {
centre: v.centre,
_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,
}
}
struct RaymarchVolumeRecord {
pipeline: vk::Pipeline,
shadow_pipeline: Option<OwnedPipeline>,
_volume_ubo: PooledBuffer,
volume_set: vk::DescriptorSet,
visible: bool,
}
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,
scene_sampler: vk::Sampler,
shadow_pipeline_layout: OwnedPipelineLayout,
_shadow_view_set_layout: OwnedSetLayout,
shadow_view_ubos: Vec<PooledBuffer>,
shadow_view_sets: Vec<vk::DescriptorSet>,
msaa: bool,
volumes: Vec<RaymarchVolumeRecord>,
}
#[derive(Copy, Clone)]
#[repr(C)]
struct ShadowCascadePush {
cascade_idx: u32,
}
fn wrap_user_fragment(user_source: &str, hot_reload: bool) -> String {
let helpers = shader_source(hot_reload, "raymarch_helpers.glsl", RAYMARCH_HELPERS_GLSL);
let template = shader_source(hot_reload, "raymarch_template.glsl", RAYMARCH_TEMPLATE_GLSL);
format!(
"{helpers}\n// === user SdfVolume fragment shader ===\n{user_source}\n// === engine raymarch template ===\n{template}\n"
)
}
fn compile_raymarch_shaders(
user_source: &str,
hot_reload: bool,
) -> Result<(Vec<u8>, Vec<u8>), String> {
let vert =
super::builtins::RAYMARCH_PROXY_VERT.compile(&super::builtins::Ctx::plain(hot_reload))?;
let frag_src = wrap_user_fragment(user_source, hot_reload);
let frag = compile_glsl(
&frag_src,
shaderc::ShaderKind::Fragment,
"raymarch_fragment",
)?;
Ok((vert, frag))
}
fn wrap_user_fragment_volumetric(user_source: &str, hot_reload: bool) -> String {
let helpers = shader_source(hot_reload, "raymarch_helpers.glsl", RAYMARCH_HELPERS_GLSL);
let template = shader_source(
hot_reload,
"raymarch_volumetric_template.glsl",
RAYMARCH_VOLUMETRIC_TEMPLATE_GLSL,
);
format!(
"{helpers}\n// === user SdfVolume fragment shader (volumetric) ===\n{user_source}\n// === engine raymarch volumetric template ===\n{template}\n"
)
}
fn compile_raymarch_volumetric_shaders(
user_source: &str,
hot_reload: bool,
) -> Result<(Vec<u8>, Vec<u8>), String> {
let vert =
super::builtins::RAYMARCH_PROXY_VERT.compile(&super::builtins::Ctx::plain(hot_reload))?;
let frag_src = wrap_user_fragment_volumetric(user_source, hot_reload);
let frag = compile_glsl(
&frag_src,
shaderc::ShaderKind::Fragment,
"raymarch_volumetric_fragment",
)?;
Ok((vert, frag))
}
fn compile_raymarch_shadow_shaders(
user_source: &str,
hot_reload: bool,
) -> Result<(Vec<u8>, Vec<u8>), String> {
let vert = super::builtins::RAYMARCH_SHADOW_PROXY_VERT
.compile(&super::builtins::Ctx::plain(hot_reload))?;
let helpers = shader_source(hot_reload, "raymarch_helpers.glsl", RAYMARCH_HELPERS_GLSL);
let template = shader_source(
hot_reload,
"raymarch_shadow_template.glsl",
RAYMARCH_SHADOW_TEMPLATE_GLSL,
);
let frag_src = format!(
"{helpers}\n// === user SdfVolume fragment shader ===\n{user_source}\n// === engine raymarch shadow template ===\n{template}\n"
);
let frag = compile_glsl(
&frag_src,
shaderc::ShaderKind::Fragment,
"raymarch_shadow_fragment",
)?;
Ok((vert, frag))
}
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) -> Result<CubeBuffers, String> {
#[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,
) -> Result<OwnedRenderPass, String> {
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 = vk::SubpassDependency::default()
.src_subpass(vk::SUBPASS_EXTERNAL)
.dst_subpass(0)
.src_stage_mask(
vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT
| vk::PipelineStageFlags::EARLY_FRAGMENT_TESTS,
)
.src_access_mask(vk::AccessFlags::empty())
.dst_stage_mask(
vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT
| vk::PipelineStageFlags::EARLY_FRAGMENT_TESTS,
)
.dst_access_mask(
vk::AccessFlags::COLOR_ATTACHMENT_WRITE
| vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE,
);
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| format!("raymarch render pass: {e}"))
}
fn create_view_set_layout(device: &VkDevice) -> Result<OwnedSetLayout, String> {
let vert_frag = vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT;
let frag = vk::ShaderStageFlags::FRAGMENT;
let ubo = |b: u32, stages: vk::ShaderStageFlags| {
vk::DescriptorSetLayoutBinding::default()
.binding(b)
.descriptor_type(vk::DescriptorType::UNIFORM_BUFFER)
.descriptor_count(1)
.stage_flags(stages)
};
let tex = |b: u32| {
vk::DescriptorSetLayoutBinding::default()
.binding(b)
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
.descriptor_count(1)
.stage_flags(frag)
};
let bindings = [
ubo(0, vert_frag), ubo(1, frag), ubo(2, frag), tex(3), tex(4), tex(5), tex(6), ];
let info = vk::DescriptorSetLayoutCreateInfo::default().bindings(&bindings);
device
.create_descriptor_set_layout(&info)
.map_err(|e| format!("raymarch view set layout: {e}"))
}
fn create_volume_set_layout(device: &VkDevice) -> Result<OwnedSetLayout, String> {
let binding = vk::DescriptorSetLayoutBinding::default()
.binding(0)
.descriptor_type(vk::DescriptorType::UNIFORM_BUFFER)
.descriptor_count(1)
.stage_flags(vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT);
let info =
vk::DescriptorSetLayoutCreateInfo::default().bindings(std::slice::from_ref(&binding));
device
.create_descriptor_set_layout(&info)
.map_err(|e| format!("raymarch volume set layout: {e}"))
}
fn create_descriptor_pool(
device: &VkDevice,
frames: usize,
volumes: usize,
has_shadow: bool,
) -> Result<OwnedDescriptorPool, String> {
let f = frames as u32;
let v = volumes as u32;
let shadow_sets = if has_shadow { f } else { 0 };
let sizes = [
vk::DescriptorPoolSize {
ty: vk::DescriptorType::UNIFORM_BUFFER,
descriptor_count: 3 * f + v + 3 * shadow_sets,
},
vk::DescriptorPoolSize {
ty: vk::DescriptorType::COMBINED_IMAGE_SAMPLER,
descriptor_count: 4 * f,
},
];
let info = vk::DescriptorPoolCreateInfo::default()
.max_sets(f + v + shadow_sets)
.pool_sizes(&sizes);
device
.create_descriptor_pool(&info)
.map_err(|e| format!("raymarch descriptor pool: {e}"))
}
fn create_shadow_view_set_layout(device: &VkDevice) -> Result<OwnedSetLayout, String> {
let frag = vk::ShaderStageFlags::FRAGMENT;
let vert_frag = vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT;
let ubo = |b: u32, stages: vk::ShaderStageFlags| {
vk::DescriptorSetLayoutBinding::default()
.binding(b)
.descriptor_type(vk::DescriptorType::UNIFORM_BUFFER)
.descriptor_count(1)
.stage_flags(stages)
};
let bindings = [
ubo(0, frag), ubo(1, frag), ubo(2, vert_frag), ];
let info = vk::DescriptorSetLayoutCreateInfo::default().bindings(&bindings);
device
.create_descriptor_set_layout(&info)
.map_err(|e| format!("raymarch shadow view set layout: {e}"))
}
fn write_shadow_view_set(
device: &VkDevice,
set: vk::DescriptorSet,
view_ubo: vk::Buffer,
light_ubo: vk::Buffer,
shadow_ubo: vk::Buffer,
) {
let view_info = vk::DescriptorBufferInfo::default()
.buffer(view_ubo)
.offset(0)
.range(std::mem::size_of::<RaymarchView>() as u64);
let light_info = vk::DescriptorBufferInfo::default()
.buffer(light_ubo)
.offset(0)
.range(std::mem::size_of::<LightUniforms>() as u64);
let shadow_info = vk::DescriptorBufferInfo::default()
.buffer(shadow_ubo)
.offset(0)
.range(std::mem::size_of::<ShadowUniforms>() as u64);
let ubo = |b: u32| {
vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(b)
.descriptor_type(vk::DescriptorType::UNIFORM_BUFFER)
};
let writes = [
ubo(0).buffer_info(std::slice::from_ref(&view_info)),
ubo(1).buffer_info(std::slice::from_ref(&light_info)),
ubo(2).buffer_info(std::slice::from_ref(&shadow_info)),
];
unsafe { device.update_descriptor_sets(&writes, &[]) };
}
fn alloc_sets(
device: &VkDevice,
pool: vk::DescriptorPool,
layouts: &[vk::DescriptorSetLayout],
) -> Result<Vec<vk::DescriptorSet>, String> {
let info = vk::DescriptorSetAllocateInfo::default()
.descriptor_pool(pool)
.set_layouts(layouts);
unsafe { device.allocate_descriptor_sets(&info) }
.map_err(|e| format!("raymarch descriptor sets: {e}"))
}
#[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;
let view_info = vk::DescriptorBufferInfo::default()
.buffer(view_ubo)
.offset(0)
.range(std::mem::size_of::<RaymarchView>() as u64);
let light_info = vk::DescriptorBufferInfo::default()
.buffer(light_ubo)
.offset(0)
.range(std::mem::size_of::<LightUniforms>() as u64);
let shadow_info = vk::DescriptorBufferInfo::default()
.buffer(shadow_ubo)
.offset(0)
.range(std::mem::size_of::<ShadowUniforms>() as u64);
let img = |view: vk::ImageView, sampler: vk::Sampler| {
vk::DescriptorImageInfo::default()
.image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
.image_view(view)
.sampler(sampler)
};
let shadow_map_info = img(shadow_map_view, shadow_sampler);
let irradiance_info = img(irradiance_view, cube_sampler);
let prefilter_info = img(prefilter_view, cube_sampler);
let snapshot_info = img(snapshot_view, scene_sampler);
let ubo = |b: u32| {
vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(b)
.descriptor_type(vk::DescriptorType::UNIFORM_BUFFER)
};
let tex = |b: u32| {
vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(b)
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
};
let writes = [
ubo(0).buffer_info(std::slice::from_ref(&view_info)),
ubo(1).buffer_info(std::slice::from_ref(&light_info)),
ubo(2).buffer_info(std::slice::from_ref(&shadow_info)),
tex(3).image_info(std::slice::from_ref(&shadow_map_info)),
tex(4).image_info(std::slice::from_ref(&irradiance_info)),
tex(5).image_info(std::slice::from_ref(&prefilter_info)),
tex(6).image_info(std::slice::from_ref(&snapshot_info)),
];
unsafe { device.update_descriptor_sets(&writes, &[]) };
}
fn write_volume_set(device: &VkDevice, set: vk::DescriptorSet, volume_ubo: vk::Buffer) {
let info = vk::DescriptorBufferInfo::default()
.buffer(volume_ubo)
.offset(0)
.range(std::mem::size_of::<RaymarchVolumeUniforms>() as u64);
let write = vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(0)
.descriptor_type(vk::DescriptorType::UNIFORM_BUFFER)
.buffer_info(std::slice::from_ref(&info));
unsafe { device.update_descriptor_sets(std::slice::from_ref(&write), &[]) };
}
fn create_pipeline(
device: &VkDevice,
render_pass: vk::RenderPass,
layout: vk::PipelineLayout,
msaa_samples: vk::SampleCountFlags,
vert_spv: &[u8],
frag_spv: &[u8],
) -> Result<OwnedPipeline, String> {
let vert = spv_module(device, vert_spv)?;
let frag = spv_module(device, frag_spv)?;
let entry = CString::new("main").unwrap();
let stages = [
vk::PipelineShaderStageCreateInfo::default()
.stage(vk::ShaderStageFlags::VERTEX)
.module(vert.handle())
.name(&entry),
vk::PipelineShaderStageCreateInfo::default()
.stage(vk::ShaderStageFlags::FRAGMENT)
.module(frag.handle())
.name(&entry),
];
let binding = vk::VertexInputBindingDescription::default()
.binding(0)
.stride(std::mem::size_of::<Vertex>() as u32)
.input_rate(vk::VertexInputRate::VERTEX);
let attribute = vk::VertexInputAttributeDescription::default()
.location(0)
.binding(0)
.format(vk::Format::R32G32B32_SFLOAT)
.offset(0);
let vertex_input = vk::PipelineVertexInputStateCreateInfo::default()
.vertex_binding_descriptions(std::slice::from_ref(&binding))
.vertex_attribute_descriptions(std::slice::from_ref(&attribute));
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 = vk::PipelineDepthStencilStateCreateInfo::default()
.depth_test_enable(true)
.depth_write_enable(true)
.depth_compare_op(vk::CompareOp::LESS_OR_EQUAL);
let blend_attachment = vk::PipelineColorBlendAttachmentState::default()
.blend_enable(false)
.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| format!("create raymarch pipeline: {e}"))?;
Ok(pipeline)
}
fn create_volumetric_pipeline(
device: &VkDevice,
render_pass: vk::RenderPass,
layout: vk::PipelineLayout,
msaa_samples: vk::SampleCountFlags,
vert_spv: &[u8],
frag_spv: &[u8],
) -> Result<OwnedPipeline, String> {
let vert = spv_module(device, vert_spv)?;
let frag = spv_module(device, frag_spv)?;
let entry = CString::new("main").unwrap();
let stages = [
vk::PipelineShaderStageCreateInfo::default()
.stage(vk::ShaderStageFlags::VERTEX)
.module(vert.handle())
.name(&entry),
vk::PipelineShaderStageCreateInfo::default()
.stage(vk::ShaderStageFlags::FRAGMENT)
.module(frag.handle())
.name(&entry),
];
let binding = vk::VertexInputBindingDescription::default()
.binding(0)
.stride(std::mem::size_of::<Vertex>() as u32)
.input_rate(vk::VertexInputRate::VERTEX);
let attribute = vk::VertexInputAttributeDescription::default()
.location(0)
.binding(0)
.format(vk::Format::R32G32B32_SFLOAT)
.offset(0);
let vertex_input = vk::PipelineVertexInputStateCreateInfo::default()
.vertex_binding_descriptions(std::slice::from_ref(&binding))
.vertex_attribute_descriptions(std::slice::from_ref(&attribute));
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 = vk::PipelineDepthStencilStateCreateInfo::default()
.depth_test_enable(true)
.depth_write_enable(false)
.depth_compare_op(vk::CompareOp::LESS_OR_EQUAL);
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| format!("create raymarch volumetric pipeline: {e}"))?;
Ok(pipeline)
}
fn create_shadow_pipeline(
device: &VkDevice,
shadow_render_pass: vk::RenderPass,
layout: vk::PipelineLayout,
vert_spv: &[u8],
frag_spv: &[u8],
) -> Result<OwnedPipeline, String> {
let vert = spv_module(device, vert_spv)?;
let frag = spv_module(device, frag_spv)?;
let entry = CString::new("main").unwrap();
let stages = [
vk::PipelineShaderStageCreateInfo::default()
.stage(vk::ShaderStageFlags::VERTEX)
.module(vert.handle())
.name(&entry),
vk::PipelineShaderStageCreateInfo::default()
.stage(vk::ShaderStageFlags::FRAGMENT)
.module(frag.handle())
.name(&entry),
];
let binding = vk::VertexInputBindingDescription::default()
.binding(0)
.stride(std::mem::size_of::<Vertex>() as u32)
.input_rate(vk::VertexInputRate::VERTEX);
let attribute = vk::VertexInputAttributeDescription::default()
.location(0)
.binding(0)
.format(vk::Format::R32G32B32_SFLOAT)
.offset(0);
let vertex_input = vk::PipelineVertexInputStateCreateInfo::default()
.vertex_binding_descriptions(std::slice::from_ref(&binding))
.vertex_attribute_descriptions(std::slice::from_ref(&attribute));
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(vk::SampleCountFlags::TYPE_1);
let depth_stencil = vk::PipelineDepthStencilStateCreateInfo::default()
.depth_test_enable(true)
.depth_write_enable(true)
.depth_compare_op(vk::CompareOp::LESS);
let blend_state = vk::PipelineColorBlendStateCreateInfo::default().logic_op_enable(false);
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(shadow_render_pass);
let pipeline = crate::vulkan::pipeline_cache::create_graphics_pipeline(device, &info)
.map_err(|e| format!("create raymarch shadow pipeline: {e}"))?;
Ok(pipeline)
}
fn create_snapshot(
alloc: &DeviceAllocator,
device: &VkDevice,
command_pool: vk::CommandPool,
queue: vk::Queue,
width: u32,
height: u32,
) -> Result<GpuImage, String> {
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)]
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_ubo: vk::Buffer,
pub(in crate::vulkan) shadow_ubos: &'a [PooledBuffer],
pub(in crate::vulkan) shadow_render_pass: vk::RenderPass,
}
impl RaymarchResources {
pub(in crate::vulkan) fn try_new(
ctx: RaymarchDeviceContext,
target: RaymarchTargetConfig,
bindings: RaymarchSharedBindings,
sdf_volumes: &[(SdfVolume, Vec<u8>, String)],
hot_reload: bool,
) -> Result<Option<Self>, String> {
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_ubo,
shadow_ubos,
shadow_render_pass,
} = bindings;
let active: Vec<&(SdfVolume, Vec<u8>, String)> = sdf_volumes
.iter()
.filter(|(v, _, label)| {
if v.fragment_shader.to_ascii_lowercase().ends_with(".glsl") {
true
} else {
tracing::warn!(
"SdfVolume '{}': fragment shader '{}' is not .glsl; skipping on \
Vulkan (the rest of the world still renders)",
label,
v.fragment_shader
);
false
}
})
.collect();
if active.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_view_set_layout(device)?;
let volume_set_layout = create_volume_set_layout(device)?;
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| format!("raymarch pipeline layout: {e}"))?
};
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 has_shadow = active.iter().any(|(v, _, _)| v.cast_shadows);
let descriptor_pool = create_descriptor_pool(device, frames, active.len(), has_shadow)?;
let view_layouts: Vec<_> = (0..frames).map(|_| view_set_layout.handle()).collect();
let view_sets = alloc_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,
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();
if has_shadow {
shadow_view_set_layout = create_shadow_view_set_layout(device)?;
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::<ShadowCascadePush>() 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| format!("raymarch shadow pipeline layout: {e}"))?;
for _ in 0..frames {
shadow_view_ubos.push(alloc.create_buffer(
view_size,
vk::BufferUsageFlags::UNIFORM_BUFFER,
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
)?);
}
let shadow_layouts: Vec<_> = (0..frames)
.map(|_| shadow_view_set_layout.handle())
.collect();
shadow_view_sets = alloc_sets(device, descriptor_pool.handle(), &shadow_layouts)?;
for (i, &set) in shadow_view_sets.iter().enumerate() {
write_shadow_view_set(
device,
set,
shadow_view_ubos[i].buffer(),
light_ubo,
shadow_ubos[i].buffer(),
);
}
}
let mut volumes: Vec<RaymarchVolumeRecord> = Vec::with_capacity(active.len());
for (vol, bytes, label) in &active {
let user_source = std::str::from_utf8(bytes).map_err(|e| {
format!("SdfVolume '{label}': fragment shader payload is not valid UTF-8: {e}")
})?;
let pipeline = if vol.volumetric {
let (vert_spv, frag_spv) =
compile_raymarch_volumetric_shaders(user_source, hot_reload)
.map_err(|e| format!("SdfVolume '{label}' (volumetric): {e}"))?;
create_volumetric_pipeline(
device,
render_pass.handle(),
pipeline_layout.handle(),
msaa_samples,
&vert_spv,
&frag_spv,
)?
} else {
let (vert_spv, frag_spv) = compile_raymarch_shaders(user_source, hot_reload)
.map_err(|e| format!("SdfVolume '{label}': {e}"))?;
create_pipeline(
device,
render_pass.handle(),
pipeline_layout.handle(),
msaa_samples,
&vert_spv,
&frag_spv,
)?
};
let shadow_pipeline = if vol.cast_shadows {
let (sh_vert, sh_frag) =
compile_raymarch_shadow_shaders(user_source, hot_reload)
.map_err(|e| format!("SdfVolume '{label}' (shadow): {e}"))?;
Some(create_shadow_pipeline(
device,
shadow_render_pass,
shadow_pipeline_layout.handle(),
&sh_vert,
&sh_frag,
)?)
} else {
None
};
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_sets(
device,
descriptor_pool.handle(),
&[volume_set_layout.handle()],
)?[0];
write_volume_set(device, volume_set, volume_ubo.buffer());
volumes.push(RaymarchVolumeRecord {
pipeline: pipeline.handle(),
shadow_pipeline,
_volume_ubo: volume_ubo,
volume_set,
visible: vol.visible,
});
}
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,
scene_sampler: linear_sampler,
shadow_pipeline_layout,
_shadow_view_set_layout: shadow_view_set_layout,
shadow_view_ubos,
shadow_view_sets,
msaa,
volumes,
}))
}
pub(in crate::vulkan) fn any_visible(&self) -> bool {
self.volumes.iter().any(|v| v.visible)
}
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,
) -> Result<(), String> {
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 {
let info = vk::DescriptorImageInfo::default()
.image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
.image_view(self.snapshot.view)
.sampler(self.scene_sampler);
let write = vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(6)
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
.image_info(std::slice::from_ref(&info));
unsafe { device.update_descriptor_sets(std::slice::from_ref(&write), &[]) };
}
Ok(())
}
pub(in crate::vulkan) fn rewire_ibl_cubes(
&self,
device: &VkDevice,
irradiance_view: vk::ImageView,
prefilter_view: vk::ImageView,
cube_sampler: vk::Sampler,
) {
let irr_info = vk::DescriptorImageInfo::default()
.image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
.image_view(irradiance_view)
.sampler(cube_sampler);
let pre_info = vk::DescriptorImageInfo::default()
.image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
.image_view(prefilter_view)
.sampler(cube_sampler);
for &set in &self.view_sets {
let writes = [
vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(4)
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
.image_info(std::slice::from_ref(&irr_info)),
vk::WriteDescriptorSet::default()
.dst_set(set)
.dst_binding(5)
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
.image_info(std::slice::from_ref(&pre_info)),
];
unsafe { device.update_descriptor_sets(&writes, &[]) };
}
}
pub(in crate::vulkan) fn destroy(&mut self, _device: &VkDevice) {
self.volumes.clear();
self.view_ubos.clear();
self.shadow_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 {
vp,
inv_vp: mat4_inverse(vp),
cam_pos,
_pad0: 0.0,
viewport: [
self.render_extent.width as f32,
self.render_extent.height as f32,
],
time,
prefilter_mip_count: self.prefilter_mip_count as f32,
}
}
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; 3],
_pad0: 0.0,
viewport: [0.0, 0.0],
time: elapsed,
prefilter_mip_count: 0.0,
};
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.device;
let push = ShadowCascadePush {
cascade_idx: cascade_idx as u32,
};
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]),
&[],
);
device.cmd_push_constants(
cmd,
rm.shadow_pipeline_layout.handle(),
vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT,
0,
std::slice::from_raw_parts(
&push as *const ShadowCascadePush as *const u8,
std::mem::size_of::<ShadowCascadePush>(),
),
);
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(
&self,
cmd: vk::CommandBuffer,
frame_idx: usize,
view: &RaymarchView,
) -> Result<(), String> {
let Some(rm) = self.raymarch.as_ref() else {
return Ok(());
};
if !rm.any_visible() {
return Ok(());
}
let device = &self.device;
let extent = self.render_extent;
let hdr_resolve = self
.hdr_resolve_images
.get(frame_idx)
.ok_or("raymarch: hdr_resolve index OOB")?
.image;
let snapshot = rm.snapshot.image;
rm.view_ubos
.get(frame_idx)
.ok_or("raymarch: view_ubos index OOB")?
.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 image_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)
};
let to_src = image_barrier(
hdr_resolve,
vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
vk::AccessFlags::SHADER_READ,
vk::AccessFlags::TRANSFER_READ,
);
let to_dst = image_barrier(
snapshot,
vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
vk::AccessFlags::SHADER_READ,
vk::AccessFlags::TRANSFER_WRITE,
);
unsafe {
device.cmd_pipeline_barrier(
cmd,
vk::PipelineStageFlags::COMPUTE_SHADER | vk::PipelineStageFlags::FRAGMENT_SHADER,
vk::PipelineStageFlags::TRANSFER,
vk::DependencyFlags::empty(),
&[],
&[],
&[to_src, to_dst],
);
let region = vk::ImageCopy::default()
.src_subresource(vk::ImageSubresourceLayers {
aspect_mask: vk::ImageAspectFlags::COLOR,
mip_level: 0,
base_array_layer: 0,
layer_count: 1,
})
.dst_subresource(vk::ImageSubresourceLayers {
aspect_mask: vk::ImageAspectFlags::COLOR,
mip_level: 0,
base_array_layer: 0,
layer_count: 1,
})
.extent(vk::Extent3D {
width: extent.width,
height: extent.height,
depth: 1,
});
device.cmd_copy_image(
cmd,
hdr_resolve,
vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
snapshot,
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
std::slice::from_ref(®ion),
);
}
let snapshot_to_read = image_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 = image_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 = if rm.msaa {
&image_barriers[..1]
} else {
&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.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.pipeline);
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(())
}
}
#[cfg(test)]
mod tests {
const DEMO_SURFACE_GLSL: &str = r#"
float map(vec3 p, SdfParams params, float time) {
float speed = sdfParamValue(params, 5u);
float angle = time * speed;
float s = sin(angle);
float c = cos(angle);
vec3 rp = vec3(c * p.x + s * p.z, p.y, -s * p.x + c * p.z);
float d_sphere = sdSphere(rp, sdfParamValue(params, 6u));
float d_torus = sdTorus(rp, vec2(sdfParamValue(params, 7u), sdfParamValue(params, 8u)));
float k = max(sdfParamValue(params, 9u), 1e-3);
return opSmoothUnion(d_sphere, d_torus, k);
}
SdfSurface shade(vec3 p, vec3 normal, SdfParams params, float time, vec2 frag_uv) {
SdfSurface s;
s.albedo = vec3(sdfParamValue(params, 0u), sdfParamValue(params, 1u), sdfParamValue(params, 2u));
s.roughness = clamp(sdfParamValue(params, 3u), 0.02, 1.0);
s.metallic = clamp(sdfParamValue(params, 4u), 0.0, 1.0);
s.emissive = vec3(0.0);
s.transmitted = vec3(0.0);
return s;
}
"#;
const DEMO_VOLUMETRIC_GLSL: &str = r#"
float cloud_hash(vec3 p) {
vec3 q = fract(p * 0.1031);
q += dot(q, q.yzx + 19.19);
return fract((q.x + q.y) * q.z);
}
float cloud_noise(vec3 p) {
vec3 i = floor(p);
vec3 f = fract(p);
vec3 u = f * f * (3.0 - 2.0 * f);
float n000 = cloud_hash(i + vec3(0.0, 0.0, 0.0));
float n100 = cloud_hash(i + vec3(1.0, 0.0, 0.0));
float n010 = cloud_hash(i + vec3(0.0, 1.0, 0.0));
float n110 = cloud_hash(i + vec3(1.0, 1.0, 0.0));
float n001 = cloud_hash(i + vec3(0.0, 0.0, 1.0));
float n101 = cloud_hash(i + vec3(1.0, 0.0, 1.0));
float n011 = cloud_hash(i + vec3(0.0, 1.0, 1.0));
float n111 = cloud_hash(i + vec3(1.0, 1.0, 1.0));
float nx00 = mix(n000, n100, u.x);
float nx10 = mix(n010, n110, u.x);
float nx0 = mix(nx00, nx10, u.y);
float nx01 = mix(n001, n101, u.x);
float nx11 = mix(n011, n111, u.x);
float nx1 = mix(nx01, nx11, u.y);
return mix(nx0, nx1, u.z);
}
float cloud_fbm(vec3 p) {
float v = 0.0;
float amp = 0.5;
float freq = 1.0;
for (int i = 0; i < 4; ++i) {
v += amp * cloud_noise(p * freq);
freq *= 2.0;
amp *= 0.5;
}
return v;
}
VolumeSample sampleVolume(vec3 p, SdfParams params, float time) {
float cloud_scale = max(sdfParamValue(params, 0u), 0.01);
vec3 flow = vec3(sdfParamValue(params, 1u), sdfParamValue(params, 2u), sdfParamValue(params, 3u));
float base_density = sdfParamValue(params, 4u);
float albedo = sdfParamValue(params, 5u);
vec3 sample_pos = (p + flow * time) / cloud_scale;
float n = cloud_fbm(sample_pos);
float density = max(0.0, n - 0.45) * 2.0 * base_density;
VolumeSample vs;
vs.density = density;
vs.scattering = vec3(albedo, albedo, albedo);
vs.emission = vec3(0.0, 0.0, 0.0);
return vs;
}
"#;
#[test]
fn raymarch_shaders_compile() {
super::compile_raymarch_shaders(DEMO_SURFACE_GLSL, false)
.expect("raymarch shaders compile");
}
#[test]
fn raymarch_shadow_shaders_compile() {
super::compile_raymarch_shadow_shaders(DEMO_SURFACE_GLSL, false)
.expect("raymarch shadow shaders compile");
}
#[test]
fn raymarch_volumetric_shaders_compile() {
super::compile_raymarch_volumetric_shaders(DEMO_VOLUMETRIC_GLSL, false)
.expect("raymarch volumetric shaders compile");
}
}