use ash::vk;
use crate::vulkan::owned::{OwnedPipeline, VkDevice};
use super::builtins;
use crate::vulkan::slang_builtins::SlangCompile;
pub(in crate::vulkan) const OBJECT_COMMON_GLSL: &str = include_str!("shaders/object_common.glsl");
pub(super) fn is_spirv(bytes: &[u8]) -> bool {
bytes.len() >= 4 && u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) == 0x07230203
}
pub(in crate::vulkan) fn shader_source(
hot_reload: bool,
name: &str,
embedded: &'static str,
) -> std::borrow::Cow<'static, str> {
if hot_reload {
let path = format!("{}/src/vulkan/shaders/{}", env!("CARGO_MANIFEST_DIR"), name);
match std::fs::read_to_string(&path) {
Ok(s) => return std::borrow::Cow::Owned(s),
Err(e) => {
tracing::debug!(
"hot-reload: falling back to embedded source for {} ({})",
name,
e
);
}
}
}
std::borrow::Cow::Borrowed(embedded)
}
pub(super) fn compile_bindless_shaders(
hot_reload: bool,
pool_size: usize,
probe_cube_count: u32,
) -> Result<(Vec<u8>, Vec<u8>), String> {
let ctx = builtins::Ctx {
hot_reload,
msaa: false,
pool_size,
probe_count: probe_cube_count as usize,
};
let vert = super::slang_builtins::MAIN_BINDLESS_VERT.compile(&ctx)?;
let frag = super::slang_builtins::MAIN_BINDLESS_FRAG.compile(&ctx)?;
Ok((vert, frag))
}
pub(super) const CULL_PUSH_CONSTANT_BYTES: u32 = 120;
pub(super) fn compile_cull_shader(hot_reload: bool) -> Result<Vec<u8>, String> {
builtins::CULL.compile(&builtins::Ctx::plain(hot_reload))
}
pub(super) fn compile_cull_shader_phase2(hot_reload: bool) -> Result<Vec<u8>, String> {
builtins::CULL_PHASE2.compile(&builtins::Ctx::plain(hot_reload))
}
pub(super) fn compile_shadow_cull_shader(hot_reload: bool) -> Result<Vec<u8>, String> {
builtins::CULL_SHADOW.compile(&builtins::Ctx::plain(hot_reload))
}
pub(super) fn compile_shadow_bindless_vs(hot_reload: bool) -> Result<Vec<u8>, String> {
super::slang_builtins::SHADOW_BINDLESS_VERT.compile(&builtins::Ctx::plain(hot_reload))
}
pub(in crate::vulkan) fn inject_define(src: &str, define: &str) -> String {
if let Some(pos) = src.find('\n') {
let (head, tail) = src.split_at(pos + 1);
format!("{head}{define}{tail}")
} else {
format!("{define}{src}")
}
}
pub(super) fn create_cull_pipeline(
device: &VkDevice,
layout: vk::PipelineLayout,
spv: &[u8],
) -> Result<OwnedPipeline, String> {
let module = spv_module(device, spv)?;
let entry = std::ffi::CString::new("main").unwrap();
let stage = vk::PipelineShaderStageCreateInfo::default()
.stage(vk::ShaderStageFlags::COMPUTE)
.module(module.handle())
.name(&entry);
let info = vk::ComputePipelineCreateInfo::default()
.stage(stage)
.layout(layout);
let pipeline = crate::vulkan::pipeline_cache::create_compute_pipeline(device, &info)
.map_err(|e| format!("create cull pipeline: {e}"))?;
Ok(pipeline)
}
thread_local! {
static SHADERC: std::cell::OnceCell<shaderc::Compiler> = const { std::cell::OnceCell::new() };
}
fn with_compiler<R>(f: impl FnOnce(&shaderc::Compiler) -> Result<R, String>) -> Result<R, String> {
SHADERC.with(|cell| {
if cell.get().is_none() {
let compiler =
shaderc::Compiler::new().map_err(|e| format!("shaderc init failed: {e}"))?;
let _ = cell.set(compiler);
}
f(cell.get().expect("shaderc compiler present"))
})
}
pub(in crate::vulkan) fn glsl_cache_key<'a>(
source: &'a str,
kind: shaderc::ShaderKind,
) -> crate::shader_cache::Key<'a> {
crate::shader_cache::Key {
compiler: "shaderc",
source,
entry: "main",
target: "vulkan1.0",
options: kind as u64,
}
}
pub(in crate::vulkan) fn glsl_rt_cache_key<'a>(
source: &'a str,
kind: shaderc::ShaderKind,
) -> crate::shader_cache::Key<'a> {
crate::shader_cache::Key {
compiler: "shaderc",
source,
entry: "main",
target: "vulkan1.2/spv1.4",
options: kind as u64,
}
}
pub(in crate::vulkan) fn compile_glsl(
source: &str,
kind: shaderc::ShaderKind,
label: &str,
) -> Result<Vec<u8>, String> {
let key = glsl_cache_key(source, kind);
crate::shader_cache::cached(&key, label, || compile_glsl_uncached(source, kind, label))
}
fn compile_glsl_uncached(
source: &str,
kind: shaderc::ShaderKind,
label: &str,
) -> Result<Vec<u8>, String> {
with_compiler(|compiler| {
let mut opts =
shaderc::CompileOptions::new().map_err(|e| format!("shaderc options failed: {e}"))?;
opts.set_target_env(
shaderc::TargetEnv::Vulkan,
shaderc::EnvVersion::Vulkan1_0 as u32,
);
opts.set_optimization_level(shaderc::OptimizationLevel::Performance);
let artifact = compiler
.compile_into_spirv(source, kind, label, "main", Some(&opts))
.map_err(|e| format!("compile {label}: {e}"))?;
Ok(artifact.as_binary_u8().to_vec())
})
}
pub(in crate::vulkan) fn compile_glsl_rt(
source: &str,
kind: shaderc::ShaderKind,
label: &str,
) -> Result<Vec<u8>, String> {
let key = glsl_rt_cache_key(source, kind);
crate::shader_cache::cached(&key, label, || {
compile_glsl_rt_uncached(source, kind, label)
})
}
fn compile_glsl_rt_uncached(
source: &str,
kind: shaderc::ShaderKind,
label: &str,
) -> Result<Vec<u8>, String> {
with_compiler(|compiler| {
let mut opts =
shaderc::CompileOptions::new().map_err(|e| format!("shaderc options failed: {e}"))?;
opts.set_target_env(
shaderc::TargetEnv::Vulkan,
shaderc::EnvVersion::Vulkan1_2 as u32,
);
opts.set_target_spirv(shaderc::SpirvVersion::V1_4);
opts.set_optimization_level(shaderc::OptimizationLevel::Performance);
let artifact = compiler
.compile_into_spirv(source, kind, label, "main", Some(&opts))
.map_err(|e| format!("compile {label}: {e}"))?;
Ok(artifact.as_binary_u8().to_vec())
})
}
pub(in crate::vulkan) struct SpvModule<'d> {
device: &'d VkDevice,
module: vk::ShaderModule,
}
impl SpvModule<'_> {
pub(in crate::vulkan) fn handle(&self) -> vk::ShaderModule {
self.module
}
}
impl Drop for SpvModule<'_> {
fn drop(&mut self) {
unsafe { self.device.destroy_shader_module(self.module, None) };
}
}
fn spirv_words(spv: &[u8]) -> Result<Vec<u32>, String> {
if !spv.len().is_multiple_of(4) {
return Err(format!(
"SPIR-V length {} is not a whole number of words",
spv.len()
));
}
Ok(spv
.chunks_exact(4)
.map(|w| u32::from_ne_bytes([w[0], w[1], w[2], w[3]]))
.collect())
}
pub(in crate::vulkan) fn spv_module<'d>(
device: &'d VkDevice,
spv: &[u8],
) -> Result<SpvModule<'d>, String> {
let code = spirv_words(spv).map_err(|e| format!("shader module: {e}"))?;
let info = vk::ShaderModuleCreateInfo::default().code(&code);
let module = unsafe { device.create_shader_module(&info, None) }
.map_err(|e| format!("shader module: {e}"))?;
Ok(SpvModule { device, module })
}
pub(super) fn resolve_main_shaders(
hot_reload: bool,
vert_bytes: &[u8],
frag_bytes: &[u8],
) -> Result<(Vec<u8>, Vec<u8>), String> {
let vert = if is_spirv(vert_bytes) {
vert_bytes.to_vec()
} else {
builtins::MAIN_VERT.compile(&builtins::Ctx::plain(hot_reload))?
};
let frag = if is_spirv(frag_bytes) {
frag_bytes.to_vec()
} else {
builtins::MAIN_FRAG.compile(&builtins::Ctx::plain(hot_reload))?
};
Ok((vert, frag))
}
pub(super) fn resolve_instanced_shader(
hot_reload: bool,
vert_instanced_bytes: &[u8],
need_instanced: bool,
) -> Result<Option<Vec<u8>>, String> {
if !need_instanced && !is_spirv(vert_instanced_bytes) {
return Ok(None);
}
let spv = if is_spirv(vert_instanced_bytes) {
vert_instanced_bytes.to_vec()
} else {
builtins::MAIN_VERT_INSTANCED.compile(&builtins::Ctx::plain(hot_reload))?
};
Ok(Some(spv))
}
type SkinnedShaderSpirv = (Vec<u8>, Vec<u8>, Vec<u8>);
pub(super) fn compile_skinned_shaders(
hot_reload: bool,
frag_bytes: &[u8],
) -> Result<SkinnedShaderSpirv, String> {
let ctx = builtins::Ctx::plain(hot_reload);
let main_vs = builtins::SKINNED_VERT.compile(&ctx)?;
let shadow_vs = super::slang_builtins::SKINNED_SHADOW_VERT.compile(&ctx)?;
let frag = if is_spirv(frag_bytes) {
frag_bytes.to_vec()
} else {
builtins::MAIN_FRAG.compile(&ctx)?
};
Ok((main_vs, shadow_vs, frag))
}
pub(super) fn resolve_shadow_shader(
hot_reload: bool,
shadow_bytes: &[u8],
) -> Result<Option<Vec<u8>>, String> {
let spv = if is_spirv(shadow_bytes) {
shadow_bytes.to_vec()
} else {
super::slang_builtins::SHADOW_VERT.compile(&builtins::Ctx::plain(hot_reload))?
};
Ok(Some(spv))
}
pub(super) fn compile_text_shaders(hot_reload: bool) -> Result<(Vec<u8>, Vec<u8>), String> {
let ctx = builtins::Ctx::plain(hot_reload);
let vert = super::slang_builtins::TEXT_VERT.compile(&ctx)?;
let frag = super::slang_builtins::TEXT_FRAG.compile(&ctx)?;
Ok((vert, frag))
}
pub(super) fn compile_composite_shaders(hot_reload: bool) -> Result<(Vec<u8>, Vec<u8>), String> {
let ctx = builtins::Ctx::plain(hot_reload);
let vert = super::slang_builtins::FULLSCREEN_VERT.compile(&ctx)?;
let frag = super::slang_builtins::COMPOSITE_FRAG.compile(&ctx)?;
Ok((vert, frag))
}
fn main_vertex_input() -> (
[vk::VertexInputBindingDescription; 1],
[vk::VertexInputAttributeDescription; 5],
) {
let binding = vk::VertexInputBindingDescription::default()
.binding(0)
.stride(56)
.input_rate(vk::VertexInputRate::VERTEX);
let attrs = [
vk::VertexInputAttributeDescription::default()
.binding(0)
.location(0)
.format(vk::Format::R32G32B32_SFLOAT)
.offset(0),
vk::VertexInputAttributeDescription::default()
.binding(0)
.location(1)
.format(vk::Format::R32G32B32_SFLOAT)
.offset(12),
vk::VertexInputAttributeDescription::default()
.binding(0)
.location(2)
.format(vk::Format::R32G32B32_SFLOAT)
.offset(24),
vk::VertexInputAttributeDescription::default()
.binding(0)
.location(3)
.format(vk::Format::R32G32B32_SFLOAT)
.offset(36),
vk::VertexInputAttributeDescription::default()
.binding(0)
.location(4)
.format(vk::Format::R32G32_SFLOAT)
.offset(48),
];
([binding], attrs)
}
fn skinned_vertex_input() -> (
[vk::VertexInputBindingDescription; 1],
[vk::VertexInputAttributeDescription; 7],
) {
let binding = vk::VertexInputBindingDescription::default()
.binding(0)
.stride(80)
.input_rate(vk::VertexInputRate::VERTEX);
let attrs = [
vk::VertexInputAttributeDescription::default()
.binding(0)
.location(0)
.format(vk::Format::R32G32B32_SFLOAT)
.offset(0),
vk::VertexInputAttributeDescription::default()
.binding(0)
.location(1)
.format(vk::Format::R32G32B32_SFLOAT)
.offset(12),
vk::VertexInputAttributeDescription::default()
.binding(0)
.location(2)
.format(vk::Format::R32G32B32_SFLOAT)
.offset(24),
vk::VertexInputAttributeDescription::default()
.binding(0)
.location(3)
.format(vk::Format::R32G32B32_SFLOAT)
.offset(36),
vk::VertexInputAttributeDescription::default()
.binding(0)
.location(4)
.format(vk::Format::R32G32_SFLOAT)
.offset(48),
vk::VertexInputAttributeDescription::default()
.binding(0)
.location(5)
.format(vk::Format::R16G16B16A16_UINT)
.offset(56),
vk::VertexInputAttributeDescription::default()
.binding(0)
.location(6)
.format(vk::Format::R32G32B32A32_SFLOAT)
.offset(64),
];
([binding], attrs)
}
fn skinned_shadow_vertex_input() -> (
[vk::VertexInputBindingDescription; 1],
[vk::VertexInputAttributeDescription; 3],
) {
let binding = vk::VertexInputBindingDescription::default()
.binding(0)
.stride(80)
.input_rate(vk::VertexInputRate::VERTEX);
let attrs = [
vk::VertexInputAttributeDescription::default()
.binding(0)
.location(0)
.format(vk::Format::R32G32B32_SFLOAT)
.offset(0),
vk::VertexInputAttributeDescription::default()
.binding(0)
.location(5)
.format(vk::Format::R16G16B16A16_UINT)
.offset(56),
vk::VertexInputAttributeDescription::default()
.binding(0)
.location(6)
.format(vk::Format::R32G32B32A32_SFLOAT)
.offset(64),
];
([binding], attrs)
}
fn text_vertex_input() -> (
[vk::VertexInputBindingDescription; 1],
[vk::VertexInputAttributeDescription; 4],
) {
let binding = vk::VertexInputBindingDescription::default()
.binding(0)
.stride(32)
.input_rate(vk::VertexInputRate::VERTEX);
let attrs = [
vk::VertexInputAttributeDescription::default()
.binding(0)
.location(0)
.format(vk::Format::R32G32_SFLOAT)
.offset(0),
vk::VertexInputAttributeDescription::default()
.binding(0)
.location(1)
.format(vk::Format::R32G32_SFLOAT)
.offset(8),
vk::VertexInputAttributeDescription::default()
.binding(0)
.location(2)
.format(vk::Format::R32G32B32_SFLOAT)
.offset(16),
vk::VertexInputAttributeDescription::default()
.binding(0)
.location(3)
.format(vk::Format::R32_SFLOAT)
.offset(28),
];
([binding], attrs)
}
pub(super) struct MeshPipelineTargets<'a> {
pub render_pass: vk::RenderPass,
pub layout: vk::PipelineLayout,
pub vert_spv: &'a [u8],
pub frag_spv: &'a [u8],
}
#[derive(Copy, Clone)]
pub(super) struct BucketPipelineTargets {
pub render_pass: vk::RenderPass,
pub layout: vk::PipelineLayout,
pub msaa_samples: vk::SampleCountFlags,
pub swapchain_format: vk::Format,
}
pub(super) fn build_bucket_pipeline(
device: &VkDevice,
targets: BucketPipelineTargets,
bucket: usize,
shader: crate::gfx::backend_init::ShaderBytes<'_>,
engine_default: &(Vec<u8>, Vec<u8>),
) -> Result<OwnedPipeline, String> {
let use_default = shader.vert.is_empty();
let (vert_spv, frag_spv) = if use_default {
(engine_default.0.as_slice(), engine_default.1.as_slice())
} else {
(shader.vert, shader.frag)
};
if vert_spv.is_empty() || frag_spv.is_empty() {
return Err(format!("shader bucket {bucket} carries no SPIR-V stages"));
}
create_main_pipeline(
device,
MeshPipelineTargets {
render_pass: targets.render_pass,
layout: targets.layout,
vert_spv,
frag_spv,
},
targets.msaa_samples,
targets.swapchain_format,
)
.map_err(|e| format!("shader bucket {bucket}: {e}"))
}
pub(super) fn build_world_pipeline_table(
device: &VkDevice,
targets: BucketPipelineTargets,
bucket_shaders: &[crate::gfx::backend_init::ShaderBytes<'_>],
engine_default: &(Vec<u8>, Vec<u8>),
) -> Result<Vec<Option<OwnedPipeline>>, String> {
let mut table = Vec::with_capacity(bucket_shaders.len());
for (i, shader) in bucket_shaders.iter().enumerate() {
if shader.deferred {
table.push(None);
continue;
}
table.push(Some(build_bucket_pipeline(
device,
targets,
i + 1,
*shader,
engine_default,
)?));
}
Ok(table)
}
pub(super) fn create_main_pipeline(
device: &VkDevice,
targets: MeshPipelineTargets<'_>,
msaa: vk::SampleCountFlags,
surface_format: vk::Format,
) -> Result<OwnedPipeline, String> {
create_main_pipeline_filled(device, targets, msaa, surface_format, vk::PolygonMode::FILL)
}
pub(super) fn create_main_pipeline_wireframe(
device: &VkDevice,
targets: MeshPipelineTargets<'_>,
msaa: vk::SampleCountFlags,
surface_format: vk::Format,
) -> Result<OwnedPipeline, String> {
create_main_pipeline_filled(device, targets, msaa, surface_format, vk::PolygonMode::LINE)
}
fn create_main_pipeline_filled(
device: &VkDevice,
targets: MeshPipelineTargets<'_>,
msaa: vk::SampleCountFlags,
_surface_format: vk::Format,
polygon_mode: vk::PolygonMode,
) -> Result<OwnedPipeline, String> {
let MeshPipelineTargets {
render_pass,
layout,
vert_spv,
frag_spv,
} = targets;
let vert_mod = spv_module(device, vert_spv)?;
let frag_mod = spv_module(device, frag_spv)?;
let entry = std::ffi::CString::new("main").unwrap();
let stages = [
vk::PipelineShaderStageCreateInfo::default()
.stage(vk::ShaderStageFlags::VERTEX)
.module(vert_mod.handle())
.name(&entry),
vk::PipelineShaderStageCreateInfo::default()
.stage(vk::ShaderStageFlags::FRAGMENT)
.module(frag_mod.handle())
.name(&entry),
];
let (bindings, attrs) = main_vertex_input();
let vert_input = vk::PipelineVertexInputStateCreateInfo::default()
.vertex_binding_descriptions(&bindings)
.vertex_attribute_descriptions(&attrs);
let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::default()
.topology(vk::PrimitiveTopology::TRIANGLE_LIST)
.primitive_restart_enable(false);
let viewport_state = vk::PipelineViewportStateCreateInfo::default()
.viewport_count(1)
.scissor_count(1);
let raster = vk::PipelineRasterizationStateCreateInfo::default()
.depth_clamp_enable(false)
.rasterizer_discard_enable(false)
.polygon_mode(polygon_mode)
.line_width(1.0)
.cull_mode(vk::CullModeFlags::NONE)
.front_face(vk::FrontFace::COUNTER_CLOCKWISE)
.depth_bias_enable(false);
let multisample = vk::PipelineMultisampleStateCreateInfo::default()
.sample_shading_enable(false)
.rasterization_samples(msaa);
let depth_stencil = vk::PipelineDepthStencilStateCreateInfo::default()
.depth_test_enable(true)
.depth_write_enable(true)
.depth_compare_op(vk::CompareOp::LESS)
.depth_bounds_test_enable(false)
.stencil_test_enable(false);
let color_blend_attach = vk::PipelineColorBlendAttachmentState::default()
.color_write_mask(vk::ColorComponentFlags::RGBA)
.blend_enable(false);
let color_blend = vk::PipelineColorBlendStateCreateInfo::default()
.logic_op_enable(false)
.attachments(std::slice::from_ref(&color_blend_attach));
let dynamic_states = [vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR];
let dynamic = vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&dynamic_states);
let pipeline_info = vk::GraphicsPipelineCreateInfo::default()
.stages(&stages)
.vertex_input_state(&vert_input)
.input_assembly_state(&input_assembly)
.viewport_state(&viewport_state)
.rasterization_state(&raster)
.multisample_state(&multisample)
.depth_stencil_state(&depth_stencil)
.color_blend_state(&color_blend)
.dynamic_state(&dynamic)
.layout(layout)
.render_pass(render_pass)
.subpass(0);
let pipeline = crate::vulkan::pipeline_cache::create_graphics_pipeline(device, &pipeline_info)
.map_err(|e| format!("create main pipeline: {e}"))?;
Ok(pipeline)
}
pub(super) fn create_instanced_pipeline(
device: &VkDevice,
targets: MeshPipelineTargets<'_>,
msaa: vk::SampleCountFlags,
surface_format: vk::Format,
) -> Result<OwnedPipeline, String> {
create_main_pipeline(device, targets, msaa, surface_format)
}
pub(super) fn create_shadow_pipeline(
device: &VkDevice,
render_pass: vk::RenderPass,
layout: vk::PipelineLayout,
vert_spv: &[u8],
) -> Result<OwnedPipeline, String> {
let vert_mod = spv_module(device, vert_spv)?;
let entry = std::ffi::CString::new("main").unwrap();
let stages = [vk::PipelineShaderStageCreateInfo::default()
.stage(vk::ShaderStageFlags::VERTEX)
.module(vert_mod.handle())
.name(&entry)];
let (bindings, attrs) = main_vertex_input();
let vert_input = vk::PipelineVertexInputStateCreateInfo::default()
.vertex_binding_descriptions(&bindings)
.vertex_attribute_descriptions(&attrs[..1]);
let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::default()
.topology(vk::PrimitiveTopology::TRIANGLE_LIST)
.primitive_restart_enable(false);
let viewport_state = vk::PipelineViewportStateCreateInfo::default()
.viewport_count(1)
.scissor_count(1);
let raster = vk::PipelineRasterizationStateCreateInfo::default()
.depth_clamp_enable(false)
.rasterizer_discard_enable(false)
.polygon_mode(vk::PolygonMode::FILL)
.line_width(1.0)
.cull_mode(vk::CullModeFlags::NONE)
.front_face(vk::FrontFace::COUNTER_CLOCKWISE)
.depth_bias_enable(true)
.depth_bias_constant_factor(0.005)
.depth_bias_slope_factor(1.0)
.depth_bias_clamp(0.0);
let multisample = vk::PipelineMultisampleStateCreateInfo::default()
.sample_shading_enable(false)
.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)
.depth_bounds_test_enable(false)
.stencil_test_enable(false);
let dynamic_states = [vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR];
let dynamic = vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&dynamic_states);
let pipeline_info = vk::GraphicsPipelineCreateInfo::default()
.stages(&stages)
.vertex_input_state(&vert_input)
.input_assembly_state(&input_assembly)
.viewport_state(&viewport_state)
.rasterization_state(&raster)
.multisample_state(&multisample)
.depth_stencil_state(&depth_stencil)
.dynamic_state(&dynamic)
.layout(layout)
.render_pass(render_pass)
.subpass(0);
let pipeline = crate::vulkan::pipeline_cache::create_graphics_pipeline(device, &pipeline_info)
.map_err(|e| format!("create shadow pipeline: {e}"))?;
Ok(pipeline)
}
pub(super) fn create_skinned_pipeline(
device: &VkDevice,
targets: MeshPipelineTargets<'_>,
msaa: vk::SampleCountFlags,
) -> Result<OwnedPipeline, String> {
create_skinned_pipeline_filled(device, targets, msaa, vk::PolygonMode::FILL)
}
pub(super) fn create_skinned_pipeline_wireframe(
device: &VkDevice,
targets: MeshPipelineTargets<'_>,
msaa: vk::SampleCountFlags,
) -> Result<OwnedPipeline, String> {
create_skinned_pipeline_filled(device, targets, msaa, vk::PolygonMode::LINE)
}
fn create_skinned_pipeline_filled(
device: &VkDevice,
targets: MeshPipelineTargets<'_>,
msaa: vk::SampleCountFlags,
polygon_mode: vk::PolygonMode,
) -> Result<OwnedPipeline, String> {
let MeshPipelineTargets {
render_pass,
layout,
vert_spv,
frag_spv,
} = targets;
let vert_mod = spv_module(device, vert_spv)?;
let frag_mod = spv_module(device, frag_spv)?;
let entry = std::ffi::CString::new("main").unwrap();
let stages = [
vk::PipelineShaderStageCreateInfo::default()
.stage(vk::ShaderStageFlags::VERTEX)
.module(vert_mod.handle())
.name(&entry),
vk::PipelineShaderStageCreateInfo::default()
.stage(vk::ShaderStageFlags::FRAGMENT)
.module(frag_mod.handle())
.name(&entry),
];
let (bindings, attrs) = skinned_vertex_input();
let vert_input = vk::PipelineVertexInputStateCreateInfo::default()
.vertex_binding_descriptions(&bindings)
.vertex_attribute_descriptions(&attrs);
let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::default()
.topology(vk::PrimitiveTopology::TRIANGLE_LIST)
.primitive_restart_enable(false);
let viewport_state = vk::PipelineViewportStateCreateInfo::default()
.viewport_count(1)
.scissor_count(1);
let raster = vk::PipelineRasterizationStateCreateInfo::default()
.depth_clamp_enable(false)
.rasterizer_discard_enable(false)
.polygon_mode(polygon_mode)
.line_width(1.0)
.cull_mode(vk::CullModeFlags::NONE)
.front_face(vk::FrontFace::COUNTER_CLOCKWISE)
.depth_bias_enable(false);
let multisample = vk::PipelineMultisampleStateCreateInfo::default()
.sample_shading_enable(false)
.rasterization_samples(msaa);
let depth_stencil = vk::PipelineDepthStencilStateCreateInfo::default()
.depth_test_enable(true)
.depth_write_enable(true)
.depth_compare_op(vk::CompareOp::LESS)
.depth_bounds_test_enable(false)
.stencil_test_enable(false);
let color_blend_attach = vk::PipelineColorBlendAttachmentState::default()
.color_write_mask(vk::ColorComponentFlags::RGBA)
.blend_enable(false);
let color_blend = vk::PipelineColorBlendStateCreateInfo::default()
.logic_op_enable(false)
.attachments(std::slice::from_ref(&color_blend_attach));
let dynamic_states = [vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR];
let dynamic = vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&dynamic_states);
let pipeline_info = vk::GraphicsPipelineCreateInfo::default()
.stages(&stages)
.vertex_input_state(&vert_input)
.input_assembly_state(&input_assembly)
.viewport_state(&viewport_state)
.rasterization_state(&raster)
.multisample_state(&multisample)
.depth_stencil_state(&depth_stencil)
.color_blend_state(&color_blend)
.dynamic_state(&dynamic)
.layout(layout)
.render_pass(render_pass)
.subpass(0);
let pipeline = crate::vulkan::pipeline_cache::create_graphics_pipeline(device, &pipeline_info)
.map_err(|e| format!("create skinned pipeline: {e}"))?;
Ok(pipeline)
}
pub(super) fn create_skinned_shadow_pipeline(
device: &VkDevice,
render_pass: vk::RenderPass,
layout: vk::PipelineLayout,
vert_spv: &[u8],
) -> Result<OwnedPipeline, String> {
let vert_mod = spv_module(device, vert_spv)?;
let entry = std::ffi::CString::new("main").unwrap();
let stages = [vk::PipelineShaderStageCreateInfo::default()
.stage(vk::ShaderStageFlags::VERTEX)
.module(vert_mod.handle())
.name(&entry)];
let (bindings, attrs) = skinned_shadow_vertex_input();
let vert_input = vk::PipelineVertexInputStateCreateInfo::default()
.vertex_binding_descriptions(&bindings)
.vertex_attribute_descriptions(&attrs);
let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::default()
.topology(vk::PrimitiveTopology::TRIANGLE_LIST)
.primitive_restart_enable(false);
let viewport_state = vk::PipelineViewportStateCreateInfo::default()
.viewport_count(1)
.scissor_count(1);
let raster = vk::PipelineRasterizationStateCreateInfo::default()
.depth_clamp_enable(false)
.rasterizer_discard_enable(false)
.polygon_mode(vk::PolygonMode::FILL)
.line_width(1.0)
.cull_mode(vk::CullModeFlags::NONE)
.front_face(vk::FrontFace::COUNTER_CLOCKWISE)
.depth_bias_enable(true)
.depth_bias_constant_factor(0.005)
.depth_bias_slope_factor(1.0)
.depth_bias_clamp(0.0);
let multisample = vk::PipelineMultisampleStateCreateInfo::default()
.sample_shading_enable(false)
.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)
.depth_bounds_test_enable(false)
.stencil_test_enable(false);
let dynamic_states = [vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR];
let dynamic = vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&dynamic_states);
let pipeline_info = vk::GraphicsPipelineCreateInfo::default()
.stages(&stages)
.vertex_input_state(&vert_input)
.input_assembly_state(&input_assembly)
.viewport_state(&viewport_state)
.rasterization_state(&raster)
.multisample_state(&multisample)
.depth_stencil_state(&depth_stencil)
.dynamic_state(&dynamic)
.layout(layout)
.render_pass(render_pass)
.subpass(0);
let pipeline = crate::vulkan::pipeline_cache::create_graphics_pipeline(device, &pipeline_info)
.map_err(|e| format!("create skinned shadow pipeline: {e}"))?;
Ok(pipeline)
}
pub(super) fn create_text_pipeline(
device: &VkDevice,
render_pass: vk::RenderPass,
layout: vk::PipelineLayout,
vert_spv: &[u8],
frag_spv: &[u8],
msaa: vk::SampleCountFlags,
) -> Result<OwnedPipeline, String> {
let vert_mod = spv_module(device, vert_spv)?;
let frag_mod = spv_module(device, frag_spv)?;
let entry = std::ffi::CString::new("main").unwrap();
let stages = [
vk::PipelineShaderStageCreateInfo::default()
.stage(vk::ShaderStageFlags::VERTEX)
.module(vert_mod.handle())
.name(&entry),
vk::PipelineShaderStageCreateInfo::default()
.stage(vk::ShaderStageFlags::FRAGMENT)
.module(frag_mod.handle())
.name(&entry),
];
let (bindings, attrs) = text_vertex_input();
let vert_input = vk::PipelineVertexInputStateCreateInfo::default()
.vertex_binding_descriptions(&bindings)
.vertex_attribute_descriptions(&attrs);
let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::default()
.topology(vk::PrimitiveTopology::TRIANGLE_LIST)
.primitive_restart_enable(false);
let viewport_state = vk::PipelineViewportStateCreateInfo::default()
.viewport_count(1)
.scissor_count(1);
let raster = vk::PipelineRasterizationStateCreateInfo::default()
.depth_clamp_enable(false)
.rasterizer_discard_enable(false)
.polygon_mode(vk::PolygonMode::FILL)
.line_width(1.0)
.cull_mode(vk::CullModeFlags::NONE)
.front_face(vk::FrontFace::COUNTER_CLOCKWISE)
.depth_bias_enable(false);
let multisample = vk::PipelineMultisampleStateCreateInfo::default()
.sample_shading_enable(false)
.rasterization_samples(msaa);
let depth_stencil = vk::PipelineDepthStencilStateCreateInfo::default()
.depth_test_enable(false)
.depth_write_enable(false)
.depth_compare_op(vk::CompareOp::ALWAYS);
let blend_attach = vk::PipelineColorBlendAttachmentState::default()
.color_write_mask(vk::ColorComponentFlags::RGBA)
.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::SRC_ALPHA)
.dst_alpha_blend_factor(vk::BlendFactor::ONE_MINUS_SRC_ALPHA)
.alpha_blend_op(vk::BlendOp::ADD);
let color_blend = vk::PipelineColorBlendStateCreateInfo::default()
.logic_op_enable(false)
.attachments(std::slice::from_ref(&blend_attach));
let dynamic_states = [vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR];
let dynamic = vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&dynamic_states);
let pipeline_info = vk::GraphicsPipelineCreateInfo::default()
.stages(&stages)
.vertex_input_state(&vert_input)
.input_assembly_state(&input_assembly)
.viewport_state(&viewport_state)
.rasterization_state(&raster)
.multisample_state(&multisample)
.depth_stencil_state(&depth_stencil)
.color_blend_state(&color_blend)
.dynamic_state(&dynamic)
.layout(layout)
.render_pass(render_pass)
.subpass(0);
let pipeline = crate::vulkan::pipeline_cache::create_graphics_pipeline(device, &pipeline_info)
.map_err(|e| format!("create text pipeline: {e}"))?;
Ok(pipeline)
}
pub(super) fn create_composite_pipeline(
device: &VkDevice,
render_pass: vk::RenderPass,
layout: vk::PipelineLayout,
vert_spv: &[u8],
frag_spv: &[u8],
) -> Result<OwnedPipeline, String> {
let vert_mod = spv_module(device, vert_spv)?;
let frag_mod = spv_module(device, frag_spv)?;
let entry = std::ffi::CString::new("main").unwrap();
let stages = [
vk::PipelineShaderStageCreateInfo::default()
.stage(vk::ShaderStageFlags::VERTEX)
.module(vert_mod.handle())
.name(&entry),
vk::PipelineShaderStageCreateInfo::default()
.stage(vk::ShaderStageFlags::FRAGMENT)
.module(frag_mod.handle())
.name(&entry),
];
let vert_input = vk::PipelineVertexInputStateCreateInfo::default();
let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::default()
.topology(vk::PrimitiveTopology::TRIANGLE_LIST)
.primitive_restart_enable(false);
let viewport_state = vk::PipelineViewportStateCreateInfo::default()
.viewport_count(1)
.scissor_count(1);
let raster = vk::PipelineRasterizationStateCreateInfo::default()
.depth_clamp_enable(false)
.rasterizer_discard_enable(false)
.polygon_mode(vk::PolygonMode::FILL)
.line_width(1.0)
.cull_mode(vk::CullModeFlags::NONE)
.front_face(vk::FrontFace::COUNTER_CLOCKWISE)
.depth_bias_enable(false);
let multisample = vk::PipelineMultisampleStateCreateInfo::default()
.sample_shading_enable(false)
.rasterization_samples(vk::SampleCountFlags::TYPE_1);
let depth_stencil = vk::PipelineDepthStencilStateCreateInfo::default()
.depth_test_enable(false)
.depth_write_enable(false)
.depth_compare_op(vk::CompareOp::ALWAYS);
let color_blend_attach = vk::PipelineColorBlendAttachmentState::default()
.color_write_mask(vk::ColorComponentFlags::RGBA)
.blend_enable(false);
let color_blend = vk::PipelineColorBlendStateCreateInfo::default()
.logic_op_enable(false)
.attachments(std::slice::from_ref(&color_blend_attach));
let dynamic_states = [vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR];
let dynamic = vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&dynamic_states);
let pipeline_info = vk::GraphicsPipelineCreateInfo::default()
.stages(&stages)
.vertex_input_state(&vert_input)
.input_assembly_state(&input_assembly)
.viewport_state(&viewport_state)
.rasterization_state(&raster)
.multisample_state(&multisample)
.depth_stencil_state(&depth_stencil)
.color_blend_state(&color_blend)
.dynamic_state(&dynamic)
.layout(layout)
.render_pass(render_pass)
.subpass(0);
let pipeline = crate::vulkan::pipeline_cache::create_graphics_pipeline(device, &pipeline_info)
.map_err(|e| format!("create composite pipeline: {e}"))?;
Ok(pipeline)
}
#[cfg(test)]
mod tests {
use super::{
SlangCompile, builtins, compile_bindless_shaders, compile_cull_shader,
compile_cull_shader_phase2, compile_shadow_bindless_vs, compile_shadow_cull_shader,
compile_skinned_shaders, is_spirv, resolve_instanced_shader, resolve_main_shaders,
spirv_words,
};
#[test]
fn spirv_words_reads_whole_words() {
let bytes = [0x03, 0x02, 0x23, 0x07, 0x00, 0x01, 0x00, 0x00];
let words = spirv_words(&bytes).expect("a two-word blob converts");
assert_eq!(
words,
vec![
u32::from_ne_bytes([0x03, 0x02, 0x23, 0x07]),
u32::from_ne_bytes([0x00, 0x01, 0x00, 0x00]),
]
);
assert_eq!(spirv_words(&[]).expect("empty converts"), Vec::<u32>::new());
}
#[test]
fn spirv_words_rejects_a_partial_word() {
for len in [1usize, 2, 3, 5, 7] {
let bytes = vec![0xFFu8; len];
assert!(
spirv_words(&bytes).is_err(),
"length {len} is not a whole number of words"
);
}
}
#[test]
fn cull_shaders_compile_both_phases() {
let phase1 = compile_cull_shader(false).expect("phase-1 cull compiles");
let phase2 = compile_cull_shader_phase2(false).expect("phase-2 cull compiles");
let shadow = compile_shadow_cull_shader(false).expect("shadow cull compiles");
assert!(is_spirv(&phase1), "phase-1 cull is valid SPIR-V");
assert!(is_spirv(&phase2), "phase-2 cull is valid SPIR-V");
assert!(is_spirv(&shadow), "shadow cull is valid SPIR-V");
assert_ne!(phase1, phase2);
assert_ne!(phase1, shadow);
}
#[test]
fn shadow_bindless_vs_compiles() {
if !crate::slangc_gate::slangc_available() {
return;
}
let vs = compile_shadow_bindless_vs(false).expect("shadow bindless VS compiles");
assert!(is_spirv(&vs), "shadow bindless VS is valid SPIR-V");
}
#[test]
fn bindless_shaders_compile() {
if !crate::slangc_gate::slangc_available() {
return;
}
for probes in [1, 7, concinnity_render::uniforms::MAX_PROBES as u32] {
let (vs, fs) =
compile_bindless_shaders(false, 4, probes).expect("bindless shaders compile");
assert!(is_spirv(&vs), "bindless vertex is valid SPIR-V");
assert!(is_spirv(&fs), "bindless fragment is valid SPIR-V");
}
let frag_src = crate::vulkan::slang_builtins::MAIN_BINDLESS_FRAG.source(&builtins::Ctx {
hot_reload: false,
msaa: false,
pool_size: 4,
probe_count: 4,
});
assert!(frag_src.contains("#define POOL_SIZE 4"));
assert!(frag_src.contains("#define MAX_PROBES 4"));
}
#[test]
fn world_shader_resolution_passes_spirv_and_falls_back_to_glsl() {
if !crate::slangc_gate::slangc_available() {
return;
}
let ctx = builtins::Ctx::plain(false);
let vert_spv = builtins::MAIN_VERT.compile(&ctx).unwrap();
let frag_spv = builtins::MAIN_FRAG.compile(&ctx).unwrap();
let (v, f) = resolve_main_shaders(false, &vert_spv, &frag_spv).unwrap();
assert_eq!(v, vert_spv, "SPIR-V vertex bytes pass through unchanged");
assert_eq!(f, frag_spv, "SPIR-V fragment bytes pass through unchanged");
let (v2, f2) = resolve_main_shaders(false, b"not spirv", b"still not spirv").unwrap();
assert!(is_spirv(&v2), "GLSL fallback vertex compiles to SPIR-V");
assert!(is_spirv(&f2), "GLSL fallback fragment compiles to SPIR-V");
let inst = resolve_instanced_shader(false, b"not spirv", true)
.unwrap()
.expect("forced instanced resolve yields Some");
assert!(is_spirv(&inst), "instanced fallback compiles to SPIR-V");
let (skinned_vs, skinned_shadow_vs, skinned_frag) =
compile_skinned_shaders(false, &frag_spv).unwrap();
assert!(is_spirv(&skinned_vs), "skinned VS compiles to SPIR-V");
assert!(is_spirv(&skinned_shadow_vs), "skinned shadow VS compiles");
assert_eq!(skinned_frag, frag_spv, "skinned fragment passes through");
}
}