use concinnity_core::components::WaterSurface;
use concinnity_core::geometry::water_grid::build_water_grid;
use concinnity_core::gfx::mesh_payload::Vertex;
use concinnity_core::render::error::{RenderError, RenderResult};
use concinnity_core::render::uniforms::WaterParams;
use windows::Win32::Graphics::Direct3D12::*;
use super::allocator::DeviceAllocator;
use crate::directx::builtin_shaders;
use crate::directx::builtin_shaders::CompileProgram;
use crate::directx::context::dump_on_err;
use crate::directx::transparent::{
RecordUpload, TransparentProducer, TransparentRecord, create_transparent_pso,
};
pub(in crate::directx) fn compile_water_shaders(
msaa_samples: u32,
hot_reload: bool,
) -> RenderResult<(Vec<u8>, Vec<u8>)> {
let vs = builtin_shaders::WATER_VERT.compile(hot_reload)?;
let ps = builtin_shaders::WATER_FRAG
.at(msaa_samples > 1)
.compile(hot_reload)?;
Ok((vs, ps))
}
pub(in crate::directx) fn rebuild_water_pso(
device: &ID3D12Device,
root_sig: &ID3D12RootSignature,
msaa_samples: u32,
hot_reload: bool,
info_queue: Option<&ID3D12InfoQueue>,
) -> RenderResult<ID3D12PipelineState> {
let (vs, ps) = compile_water_shaders(msaa_samples, hot_reload)?;
dump_on_err(
info_queue,
create_transparent_pso(device, root_sig, &vs, &ps),
)
}
struct WaterRtShaders {
vs: Vec<u8>,
flat_ps: Vec<u8>,
textured_ps: Vec<u8>,
}
fn compile_water_rt_shaders(msaa_samples: u32, hot_reload: bool) -> RenderResult<WaterRtShaders> {
let msaa = msaa_samples > 1;
Ok(WaterRtShaders {
vs: builtin_shaders::WATER_VERT.compile(hot_reload)?,
flat_ps: builtin_shaders::WATER_FRAG_RT
.at(msaa)
.compile(hot_reload)?,
textured_ps: builtin_shaders::WATER_FRAG_RT_TEXTURED
.at(msaa)
.compile(hot_reload)?,
})
}
#[derive(Clone, Copy)]
pub(in crate::directx) struct WaterBuild<'a> {
pub alloc: &'a DeviceAllocator,
pub root_sig: &'a ID3D12RootSignature,
pub rt_root_sig: Option<&'a ID3D12RootSignature>,
pub msaa_samples: u32,
pub hot_reload: bool,
pub info_queue: Option<&'a ID3D12InfoQueue>,
}
pub(in crate::directx) fn build_water_producer(
build: WaterBuild,
surfaces: &[WaterSurface],
planar_slots: &[Option<usize>],
) -> RenderResult<TransparentProducer> {
let WaterBuild {
alloc,
root_sig,
rt_root_sig,
msaa_samples,
hot_reload,
info_queue,
} = build;
let device = alloc.device();
let (vs, ps) = compile_water_shaders(msaa_samples, hot_reload)?;
let pso = dump_on_err(
info_queue,
create_transparent_pso(device, root_sig, &vs, &ps),
)?;
let (flat_rt_pso, textured_rt_pso) = match rt_root_sig {
Some(sig) => {
match build_water_rt_pipelines(device, sig, msaa_samples, hot_reload, info_queue) {
Ok(pair) => (Some(pair.0), Some(pair.1)),
Err(e) => {
tracing::warn!(
"water RT reflection pipeline build failed ({e}); \
using the probe/planar water path"
);
(None, None)
}
}
}
None => (None, None),
};
let mut records = Vec::with_capacity(surfaces.len());
for (i, surface) in surfaces.iter().enumerate() {
let planar_slot = planar_slots.get(i).copied().flatten();
let (verts, idxs) =
build_water_grid(surface.extent[0], surface.extent[1], surface.subdivisions)
.map_err(RenderError::Other)?;
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 = WaterParams::from_surface(surface, planar_slot.is_some());
records.push(TransparentRecord::upload(
alloc,
RecordUpload {
vertices: &packed,
indices: &idxs,
params: bytemuck::bytes_of(¶ms),
visible: surface.visible,
center: surface.center,
planar_slot,
},
)?);
}
Ok(TransparentProducer {
pso,
flat_rt_pso,
textured_rt_pso,
reflection_flat_pso: None,
reflection_textured_pso: None,
records,
})
}
fn build_water_rt_pipelines(
device: &ID3D12Device,
rt_root_sig: &ID3D12RootSignature,
msaa_samples: u32,
hot_reload: bool,
info_queue: Option<&ID3D12InfoQueue>,
) -> RenderResult<(ID3D12PipelineState, ID3D12PipelineState)> {
let shaders = compile_water_rt_shaders(msaa_samples, hot_reload)?;
let flat = dump_on_err(
info_queue,
create_transparent_pso(device, rt_root_sig, &shaders.vs, &shaders.flat_ps),
)?;
let textured = dump_on_err(
info_queue,
create_transparent_pso(device, rt_root_sig, &shaders.vs, &shaders.textured_ps),
)?;
Ok((flat, textured))
}
#[cfg(test)]
mod tests {
#[test]
fn water_shaders_compile() {
concinnity_shader::require_dxc!();
for msaa in [1u32, 4] {
super::compile_water_shaders(msaa, false)
.unwrap_or_else(|e| panic!("water shaders (msaa={msaa}) must compile: {e}"));
}
}
#[test]
fn water_rt_shaders_compile() {
concinnity_shader::require_dxc!();
for msaa in [1u32, 4] {
super::compile_water_rt_shaders(msaa, false)
.unwrap_or_else(|e| panic!("water_rt shaders (msaa={msaa}) must compile: {e}"));
}
}
}