use ash::vk;
use concinnity_core::components::GlassPanel;
use concinnity_core::geometry::glass_quad::build_glass_quad;
use concinnity_core::gfx::mesh_payload::Vertex;
use concinnity_core::render::error::RenderResult;
use concinnity_core::render::uniforms::GlassParams;
use super::allocator::DeviceAllocator;
use crate::vulkan::builtin_shaders::CompileProgram;
use crate::vulkan::transparent::{
GlassMeshProducer, ProducerCtx, RecordUpload, TracedGlassPipelines, TransparentProducer,
TransparentRecord, TransparentVertexInput, create_glass_reflection_pipeline,
create_transparent_pipeline,
};
fn compile_glass_shaders(hot_reload: bool, msaa: bool) -> RenderResult<(Vec<u8>, Vec<u8>)> {
let vert = super::builtin_shaders::GLASS_VERT.compile(hot_reload)?;
let frag = super::builtin_shaders::GLASS_FRAG
.at(msaa)
.compile(hot_reload)?;
Ok((vert, frag))
}
struct GlassRtShaders {
vs: Vec<u8>,
flat_fs: Vec<u8>,
textured_fs: Option<Vec<u8>>,
reflection_flat_fs: Vec<u8>,
reflection_textured_fs: Option<Vec<u8>>,
}
fn compile_glass_rt_shaders(
hot_reload: bool,
msaa: bool,
pool_size: usize,
) -> RenderResult<GlassRtShaders> {
let vs = super::builtin_shaders::GLASS_VERT.compile(hot_reload)?;
let flat_fs = super::builtin_shaders::GLASS_FRAG_RT
.at(msaa)
.compile(hot_reload)?;
let reflection_flat_fs = super::builtin_shaders::GLASS_REFLECTION_FRAG
.at(msaa)
.compile(hot_reload)?;
let (textured_fs, reflection_textured_fs) = if pool_size > 0 {
(
Some(
super::builtin_shaders::GLASS_FRAG_RT_TEXTURED
.at(msaa)
.compile(hot_reload)?,
),
Some(
super::builtin_shaders::GLASS_REFLECTION_FRAG_TEXTURED
.at(msaa)
.compile(hot_reload)?,
),
)
} else {
(None, None)
};
Ok(GlassRtShaders {
vs,
flat_fs,
textured_fs,
reflection_flat_fs,
reflection_textured_fs,
})
}
fn build_panel_record(
alloc: &DeviceAllocator,
ctx: &ProducerCtx,
panel: &GlassPanel,
planar_slot: Option<usize>,
) -> RenderResult<TransparentRecord> {
let (verts, idxs) = build_glass_quad(panel.center, panel.normal, panel.half_size);
let packed: Vec<Vertex> = verts
.into_iter()
.map(|(pos, normal, color, uv)| Vertex {
pos,
normal,
tangent: [1.0, 0.0, 0.0],
color,
uv,
})
.collect();
let params = GlassParams::from_panel(panel, planar_slot.is_some());
TransparentRecord::upload(
alloc,
ctx.record_descriptors(planar_slot),
RecordUpload {
vertices: &packed,
indices: &idxs,
params: bytemuck::bytes_of(¶ms),
visible: panel.visible,
center: panel.center,
planar_slot,
},
)
}
pub(in crate::vulkan) fn build_glass_producer(
ctx: ProducerCtx,
panels: &[GlassPanel],
planar_slots: &[Option<usize>],
) -> RenderResult<TransparentProducer> {
let (vert_spv, frag_spv) = compile_glass_shaders(ctx.hot_reload, ctx.msaa)?;
let pipeline = create_transparent_pipeline(
ctx.device,
ctx.render_pass,
ctx.layout,
&vert_spv,
&frag_spv,
TransparentVertexInput::Position,
)?;
let traced = match ctx.rt_layout_flat {
Some(flat_layout) => match build_glass_rt_pipelines(&ctx, flat_layout) {
Ok(p) => Some(p),
Err(e) => {
tracing::warn!(
"glass RT pipelines failed to build ({e}); using the probe / planar glass path"
);
None
}
},
None => None,
};
let (flat_rt_pso, textured_rt_pso, reflection_flat_pso, reflection_textured_pso) = match traced
{
Some(p) => (
Some(p.shade_flat),
p.shade_textured,
Some(p.reflection_flat),
p.reflection_textured,
),
None => (None, None, None, None),
};
let mut records = Vec::with_capacity(panels.len());
for (i, panel) in panels.iter().enumerate() {
let planar_slot = planar_slots.get(i).copied().flatten();
records.push(build_panel_record(ctx.alloc, &ctx, panel, planar_slot)?);
}
Ok(TransparentProducer {
pipeline,
flat_rt_pso,
textured_rt_pso,
reflection_flat_pso,
reflection_textured_pso,
records,
})
}
fn build_glass_rt_pipelines(
ctx: &ProducerCtx,
flat_layout: vk::PipelineLayout,
) -> RenderResult<TracedGlassPipelines> {
let shaders = compile_glass_rt_shaders(ctx.hot_reload, ctx.msaa, ctx.bindless_pool_size)?;
build_traced_pipelines(ctx, flat_layout, &shaders, TransparentVertexInput::Position)
}
fn build_traced_pipelines(
ctx: &ProducerCtx,
flat_layout: vk::PipelineLayout,
shaders: &GlassRtShaders,
vertex_input: TransparentVertexInput,
) -> RenderResult<TracedGlassPipelines> {
let shade = |layout, fs: &[u8]| {
create_transparent_pipeline(
ctx.device,
ctx.render_pass,
layout,
&shaders.vs,
fs,
vertex_input,
)
};
let reflection = |layout, fs: &[u8]| {
create_glass_reflection_pipeline(
ctx.device,
ctx.reflection_render_pass,
layout,
&shaders.vs,
fs,
vertex_input,
)
};
let (shade_textured, reflection_textured) = match (
ctx.rt_layout_textured,
&shaders.textured_fs,
&shaders.reflection_textured_fs,
) {
(Some(layout), Some(fs), Some(rfs)) => {
(Some(shade(layout, fs)?), Some(reflection(layout, rfs)?))
}
_ => (None, None),
};
Ok(TracedGlassPipelines {
shade_flat: shade(flat_layout, &shaders.flat_fs)?,
shade_textured,
reflection_flat: reflection(flat_layout, &shaders.reflection_flat_fs)?,
reflection_textured,
})
}
fn compile_glass_mesh_shaders(
hot_reload: bool,
msaa: bool,
pool_size: usize,
) -> RenderResult<GlassRtShaders> {
let vs = super::builtin_shaders::GLASS_MESH_VERT.compile(hot_reload)?;
let flat_fs = super::builtin_shaders::GLASS_MESH_FRAG_RT
.at(msaa)
.compile(hot_reload)?;
let reflection_flat_fs = super::builtin_shaders::GLASS_MESH_REFLECTION_FRAG
.at(msaa)
.compile(hot_reload)?;
let (textured_fs, reflection_textured_fs) = if pool_size > 0 {
(
Some(
super::builtin_shaders::GLASS_MESH_FRAG_RT_TEXTURED
.at(msaa)
.compile(hot_reload)?,
),
Some(
super::builtin_shaders::GLASS_MESH_REFLECTION_FRAG_TEXTURED
.at(msaa)
.compile(hot_reload)?,
),
)
} else {
(None, None)
};
Ok(GlassRtShaders {
vs,
flat_fs,
textured_fs,
reflection_flat_fs,
reflection_textured_fs,
})
}
pub(in crate::vulkan) fn build_glass_mesh_producer(
ctx: ProducerCtx,
flat_layout: vk::PipelineLayout,
object_indices: &[usize],
) -> RenderResult<GlassMeshProducer> {
let shaders = compile_glass_mesh_shaders(ctx.hot_reload, ctx.msaa, ctx.bindless_pool_size)?;
let pipelines = build_traced_pipelines(
&ctx,
flat_layout,
&shaders,
TransparentVertexInput::PositionAndNormal,
)?;
GlassMeshProducer::new(&ctx, pipelines, object_indices.to_vec())
}
#[cfg(test)]
mod tests {
#[test]
fn glass_mesh_shaders_compile() {
concinnity_shader::require_dxc!();
for msaa in [false, true] {
super::compile_glass_mesh_shaders(false, msaa, 16)
.unwrap_or_else(|e| panic!("glass_mesh shaders (msaa={msaa}) must compile: {e}"));
}
}
#[test]
fn glass_shaders_compile() {
concinnity_shader::require_dxc!();
super::compile_glass_shaders(false, true).expect("glass compiles (msaa)");
super::compile_glass_shaders(false, false).expect("glass compiles (no msaa)");
}
#[test]
fn glass_rt_shaders_compile() {
concinnity_shader::require_dxc!();
for &msaa in &[true, false] {
let shaders =
super::compile_glass_rt_shaders(false, msaa, 4).expect("glass rt shaders compile");
assert!(crate::vulkan::pipeline::is_spirv(&shaders.vs));
assert!(crate::vulkan::pipeline::is_spirv(&shaders.flat_fs));
assert!(
shaders.textured_fs.is_some(),
"pool_size>0 builds the textured variant"
);
}
let flat_only =
super::compile_glass_rt_shaders(false, false, 0).expect("glass rt flat compiles");
assert!(flat_only.textured_fs.is_none());
}
}