use concinnity_core::render::backend_init;
use concinnity_core::render::error::{RenderError, RenderResult};
use concinnity_core::render::shadow_bias;
use windows::Win32::Graphics::Direct3D12::*;
use windows::Win32::Graphics::Dxgi::Common::*;
use crate::directx::builtin_shaders;
use crate::directx::builtin_shaders::CompileProgram;
use crate::directx::context::dump_on_err;
use crate::directx::pipeline::main_input_layout;
use crate::directx::pso::{Blend, Depth, DepthBias, GraphicsPso, Raster};
use crate::directx::root_sig::{Range, RootSig, Visibility};
use crate::directx::texture::HDR_FORMAT;
pub(in crate::directx) fn world_entry(
world: &concinnity_core::components::ShaderPrograms,
entry: &str,
hot_reload: bool,
) -> RenderResult<Vec<u8>> {
let req = crate::shader::surface_source::Request {
platform: concinnity_core::platform::Platform::DirectX,
hot_reload,
};
crate::shader::surface_source::artifact(world, entry, &req, crate::shader::compile::cooked)
.map(|c| c.into_owned())
}
pub(in crate::directx) fn compile_main_bindless_shaders(
hot_reload: bool,
) -> RenderResult<BindlessMainShaders> {
Ok(BindlessMainShaders {
vs: builtin_shaders::MAIN_BINDLESS_VERT.compile(hot_reload)?,
ps: builtin_shaders::MAIN_BINDLESS_FRAG.compile(hot_reload)?,
prepass_vs: builtin_shaders::MAIN_PREPASS_VERT.compile(hot_reload)?,
prepass_ps: builtin_shaders::MAIN_PREPASS_FRAG.compile(hot_reload)?,
})
}
pub(in crate::directx) fn compile_shadow_bindless_vs(hot_reload: bool) -> RenderResult<Vec<u8>> {
builtin_shaders::SHADOW_VERT_BINDLESS.compile(hot_reload)
}
pub(super) fn create_main_bindless_root_signature(
device: &ID3D12Device,
) -> RenderResult<ID3D12RootSignature> {
use Visibility::{All, Pixel};
RootSig::new()
.constant_dwords(0, 1, All)
.cbv(1, All)
.cbv(2, Pixel)
.cbv(3, Pixel)
.table(&[Range::srv(0, 1), Range::srv(5, 2)], Pixel)
.table(&[Range::bindless_srv(1)], Pixel)
.sampler_table(0, 1, Pixel)
.sampler_table(1, 2, Pixel)
.srv(3, All)
.srv_table(4, 1, Pixel)
.srv_table(7, 1, Pixel)
.cbv(4, Pixel)
.srv(1, Pixel)
.cbv(5, Pixel)
.srv(2, Pixel)
.srv(15, Pixel)
.srv_table(16, 1, Pixel)
.srv(17, Pixel)
.srv_table(18, 2, Pixel)
.srv(8, Pixel)
.srv(20, All)
.input_layout()
.build(device, "main bindless root sig")
}
pub(in crate::directx) fn create_shadow_bindless_root_signature(
device: &ID3D12Device,
) -> RenderResult<ID3D12RootSignature> {
RootSig::new()
.constant_dwords(0, 1, Visibility::Vertex)
.cbv(1, Visibility::Vertex)
.constant_dwords(2, 1, Visibility::Vertex)
.srv(0, Visibility::Vertex)
.input_layout()
.build(device, "shadow bindless root sig")
}
pub(in crate::directx) fn create_main_pso(
device: &ID3D12Device,
root_sig: &ID3D12RootSignature,
vs: &[u8],
ps: &[u8],
rtv_format: DXGI_FORMAT,
sample_count: u32,
) -> RenderResult<ID3D12PipelineState> {
create_main_pso_filled(device, root_sig, vs, ps, rtv_format, sample_count, false)
}
pub(in crate::directx) fn create_main_pso_wireframe(
device: &ID3D12Device,
root_sig: &ID3D12RootSignature,
vs: &[u8],
ps: &[u8],
rtv_format: DXGI_FORMAT,
sample_count: u32,
) -> RenderResult<ID3D12PipelineState> {
create_main_pso_filled(device, root_sig, vs, ps, rtv_format, sample_count, true)
}
fn create_main_pso_filled(
device: &ID3D12Device,
root_sig: &ID3D12RootSignature,
vs: &[u8],
ps: &[u8],
rtv_format: DXGI_FORMAT,
sample_count: u32,
wireframe: bool,
) -> RenderResult<ID3D12PipelineState> {
let layout = main_input_layout();
GraphicsPso::new(root_sig, vs, ps)
.input_layout(&layout)
.target(rtv_format, Blend::Opaque)
.depth(DXGI_FORMAT_D32_FLOAT, Depth::write())
.samples(sample_count)
.raster(Raster {
wireframe,
..Raster::default()
})
.build(device, "main")
}
pub(in crate::directx) fn create_shadow_pso(
device: &ID3D12Device,
root_sig: &ID3D12RootSignature,
vs: &[u8],
) -> RenderResult<ID3D12PipelineState> {
let layout = main_input_layout();
GraphicsPso::new(root_sig, vs, &[])
.input_layout(&layout)
.depth(DXGI_FORMAT_D32_FLOAT, Depth::write())
.raster(Raster {
bias: DepthBias {
constant: shadow_bias::RASTER_CONSTANT as i32,
clamp: shadow_bias::RASTER_CLAMP,
slope: shadow_bias::RASTER_SLOPE,
},
..Raster::default()
})
.build(device, "shadow")
}
pub(in crate::directx) struct BindlessMainShaders {
pub vs: Vec<u8>,
pub ps: Vec<u8>,
pub prepass_vs: Vec<u8>,
pub prepass_ps: Vec<u8>,
}
pub(in crate::directx) struct BucketPsos {
pub main: ID3D12PipelineState,
pub prepass: Option<ID3D12PipelineState>,
pub programs: Option<concinnity_core::components::ShaderPrograms>,
}
#[derive(Clone, PartialEq)]
pub(in crate::directx) struct BucketRootSigs {
pub main: ID3D12RootSignature,
pub prepass: Option<ID3D12RootSignature>,
}
pub(in crate::directx) fn build_bucket_pipeline(
device: &ID3D12Device,
info_queue: Option<&ID3D12InfoQueue>,
targets: BucketPipelineTargets<'_>,
bucket: usize,
shader: backend_init::WorldShader<'_>,
) -> RenderResult<BucketPsos> {
match shader.programs {
Some(programs) => {
build_world_shader_pso(device, info_queue, targets.world(), bucket, programs)
}
None => {
let engine = targets.engine_default;
create_bucket_psos(
device,
info_queue,
targets.world(),
bucket,
BucketStages {
vs: &engine.vs,
ps: &engine.ps,
prepass_vs: &engine.prepass_vs,
prepass_ps: &engine.prepass_ps,
},
)
}
}
}
pub(in crate::directx) fn build_world_shader_pso(
device: &ID3D12Device,
info_queue: Option<&ID3D12InfoQueue>,
targets: WorldPsoTargets<'_>,
bucket: usize,
programs: &concinnity_core::components::ShaderPrograms,
) -> RenderResult<BucketPsos> {
use concinnity_core::render::shader_programs::surface;
let entry =
|program: surface::Program| world_entry(programs, program.entry, targets.hot_reload);
let vs = entry(surface::MAIN_VERTEX)?;
let ps = entry(surface::MAIN_FRAGMENT)?;
let (prepass_vs, prepass_ps) = match targets.root_sigs.prepass {
Some(_) => world_prepass_stages(programs, targets.hot_reload, bucket),
None => (Vec::new(), Vec::new()),
};
let mut psos = create_bucket_psos(
device,
info_queue,
targets,
bucket,
BucketStages {
vs: &vs,
ps: &ps,
prepass_vs: &prepass_vs,
prepass_ps: &prepass_ps,
},
)?;
psos.programs = Some(programs.clone());
Ok(psos)
}
fn world_prepass_stages(
programs: &concinnity_core::components::ShaderPrograms,
hot_reload: bool,
bucket: usize,
) -> (Vec<u8>, Vec<u8>) {
use concinnity_core::render::shader_programs::surface;
let stages = world_entry(programs, surface::PREPASS_VERTEX.entry, hot_reload).and_then(|vs| {
world_entry(programs, surface::PREPASS_FRAGMENT.entry, hot_reload).map(|ps| (vs, ps))
});
stages.unwrap_or_else(|e| {
tracing::warn!("shader bucket {bucket}'s G-buffer pre-pass did not build: {e}");
(Vec::new(), Vec::new())
})
}
pub(in crate::directx) fn build_bucket_prepass(
device: &ID3D12Device,
info_queue: Option<&ID3D12InfoQueue>,
root_sig: &ID3D12RootSignature,
bucket: usize,
stages: (&[u8], &[u8]),
) -> Option<ID3D12PipelineState> {
let (vs, ps) = stages;
if vs.is_empty() || ps.is_empty() {
return None;
}
let pso = dump_on_err(
info_queue,
crate::directx::post::gbuffer::create_prepass_pso(device, root_sig, vs, ps),
);
pso.map_err(|e| tracing::warn!("shader bucket {bucket}'s G-buffer pre-pass did not build: {e}"))
.ok()
}
pub(in crate::directx) fn build_prepass_for(
device: &ID3D12Device,
info_queue: Option<&ID3D12InfoQueue>,
root_sig: &ID3D12RootSignature,
bucket: usize,
source: PrepassSource<'_>,
) -> Option<ID3D12PipelineState> {
let (vs, ps) = match source {
PrepassSource::World(programs, hot_reload) => {
world_prepass_stages(programs, hot_reload, bucket)
}
PrepassSource::Engine(engine) => (engine.prepass_vs.clone(), engine.prepass_ps.clone()),
};
build_bucket_prepass(device, info_queue, root_sig, bucket, (&vs, &ps))
}
pub(in crate::directx) enum PrepassSource<'a> {
World(&'a concinnity_core::components::ShaderPrograms, bool),
Engine(&'a BindlessMainShaders),
}
struct BucketStages<'a> {
vs: &'a [u8],
ps: &'a [u8],
prepass_vs: &'a [u8],
prepass_ps: &'a [u8],
}
fn create_bucket_psos(
device: &ID3D12Device,
info_queue: Option<&ID3D12InfoQueue>,
targets: WorldPsoTargets<'_>,
bucket: usize,
stages: BucketStages<'_>,
) -> RenderResult<BucketPsos> {
let BucketStages {
vs,
ps,
prepass_vs,
prepass_ps,
} = stages;
if vs.is_empty() || ps.is_empty() {
return Err(RenderError::ShaderCompile(format!(
"shader bucket {bucket} carries no vertex/fragment bytecode"
)));
}
let main = dump_on_err(
info_queue,
create_main_pso(
device,
&targets.root_sigs.main,
vs,
ps,
HDR_FORMAT,
targets.msaa_samples,
),
)
.map_err(|e| e.context(format!("shader bucket {bucket}")))?;
let prepass = targets.root_sigs.prepass.as_ref().and_then(|root_sig| {
build_bucket_prepass(
device,
info_queue,
root_sig,
bucket,
(prepass_vs, prepass_ps),
)
});
Ok(BucketPsos {
main,
prepass,
programs: None,
})
}
#[derive(Clone, Copy)]
pub(in crate::directx) struct BucketPipelineTargets<'a> {
pub root_sigs: &'a BucketRootSigs,
pub msaa_samples: u32,
pub engine_default: &'a BindlessMainShaders,
pub hot_reload: bool,
}
impl<'a> BucketPipelineTargets<'a> {
fn world(self) -> WorldPsoTargets<'a> {
WorldPsoTargets {
root_sigs: self.root_sigs,
msaa_samples: self.msaa_samples,
hot_reload: self.hot_reload,
}
}
}
#[derive(Clone, Copy)]
pub(in crate::directx) struct WorldPsoTargets<'a> {
pub root_sigs: &'a BucketRootSigs,
pub msaa_samples: u32,
pub hot_reload: bool,
}
pub(super) fn build_world_pipeline_table(
device: &ID3D12Device,
info_queue: Option<&ID3D12InfoQueue>,
targets: BucketPipelineTargets<'_>,
bucket_shaders: &[backend_init::WorldShader<'_>],
) -> RenderResult<Vec<Option<BucketPsos>>> {
let mut table = Vec::with_capacity(bucket_shaders.len());
for (i, shader) in bucket_shaders.iter().enumerate() {
let bucket = i + 1;
if shader.deferred {
table.push(None);
continue;
}
table.push(Some(build_bucket_pipeline(
device, info_queue, targets, bucket, *shader,
)?));
}
Ok(table)
}
#[cfg(test)]
mod tests {
#[test]
fn bindless_main_shaders_compile() {
concinnity_shader::require_dxc!();
super::compile_main_bindless_shaders(false).expect("bindless main shaders must compile");
}
}