concinnity-device 0.18.64

GPU backends (Metal, Vulkan, DirectX) behind a device facade for Concinnity
Documentation
// src/directx/builtins.rs
//
// The declarative table of every built-in HLSL program the DirectX backend
// compiles at runtime. Each program is declared exactly once: its source file,
// entry point, target profile, compiler, and whether its body takes the shared
// object_common injection. Renderer init and hot-reload compile through
// `HlslProgram::compile`, and the export-time precompile iterates `ALL` to
// populate a bundle's shader cache from the very same declarations, so the two
// can never drift.
//
// What is left here is the per-draw main pass, its skinned and instanced
// vertex siblings, the cull kernel and the RT skinned refit: everything else
// ships from `src/shaders/*.slang` (`slang_builtins`), which compiles to DXIL
// through slangc rather than FXC. Also not declared here: the SdfVolume
// raymarch pipelines (raymarch.rs), whose fragment source embeds
// world-authored shader text and therefore cannot be enumerated ahead of a
// world. Both compile at init through the same cache.

use std::borrow::Cow;

use super::pipeline::shader_source;

// The shared bindless per-object record, substituted into every pass that
// strides the per-frame object StructuredBuffer at its `{OBJECT_DATA}` marker.
// Sole HLSL declaration of `GpuObjectData`: the main pass, G-buffer prepass,
// shadow pass and cull kernel all read the same buffer, so a per-shader copy is
// a silent layout-drift hazard.
const OBJECT_COMMON_HLSL: &str = include_str!("shaders/object_common.hlsl");

// Every program left on this table is FXC (`D3DCompile`, shader model 5.1).
// The SM 6 programs all became single-source `.slang`, which slangc emits as
// DXIL directly, so the DXC path this table used to carry has no consumer.
pub(crate) struct HlslProgram {
    // File name under `src/directx/shaders/` for the `cn debug` disk-first resolve.
    pub file: &'static str,
    pub embedded: &'static str,
    pub entry: &'static str,
    pub target: &'static str,
    // Substitute `{OBJECT_DATA}` with the shared `GpuObjectData` declaration.
    pub object_data: bool,
}

impl HlslProgram {
    fn body(&self, hot_reload: bool) -> Cow<'static, str> {
        shader_source(hot_reload, self.file, self.embedded)
    }

    // Assemble the exact source text this program compiles.
    pub(crate) fn source(&self, hot_reload: bool) -> String {
        let mut src = self.body(hot_reload).into_owned();
        if self.object_data {
            let object_common = shader_source(hot_reload, "object_common.hlsl", OBJECT_COMMON_HLSL);
            src = src.replace("{OBJECT_DATA}", &object_common);
        }
        src
    }

    pub(crate) fn compile(&self, hot_reload: bool) -> Result<Vec<u8>, String> {
        let source = self.source(hot_reload);
        super::pipeline::compile_hlsl(&source, self.entry, self.target)
    }
}

// Compile every declared program into `out_dir`, reusing local cache artifacts
// where present. A failure is reported rather than failing the export.
pub(crate) fn precompile(out_dir: &std::path::Path, report: &mut crate::precompile::Report) {
    for program in ALL {
        let source = program.source(false);
        let key = super::pipeline::fxc_cache_key(&source, program.entry, program.target);
        let compile = || super::pipeline::compile_hlsl(&source, program.entry, program.target);
        report.record(
            &format!("{} {}", program.entry, program.target),
            crate::shader_cache::ensure_in(out_dir, &key, compile),
        );
    }
}

// Embedded sources shared by several programs.
const MAIN_VERT_HLSL: &str = include_str!("shaders/main_vert.hlsl");
const MAIN_FRAG_HLSL: &str = include_str!("shaders/main_frag.hlsl");
const CULL_HLSL: &str = include_str!("shaders/cull.hlsl");

// Declaration shorthand: FXC, single `main` entry, no object_data splice.
const fn fxc_main(file: &'static str, embedded: &'static str, target: &'static str) -> HlslProgram {
    HlslProgram {
        file,
        embedded,
        entry: "main",
        target,
        object_data: false,
    }
}

// The main geometry pass. Both vertex entry points share main_vert.hlsl.
pub(super) static MAIN_VERT: HlslProgram = HlslProgram {
    file: "main_vert.hlsl",
    embedded: MAIN_VERT_HLSL,
    entry: "vertex_main",
    target: "vs_5_1",
    object_data: false,
};
pub(super) static MAIN_VERT_INSTANCED: HlslProgram = HlslProgram {
    file: "main_vert.hlsl",
    embedded: MAIN_VERT_HLSL,
    entry: "vertex_main_instanced",
    target: "vs_5_1",
    object_data: false,
};
pub(super) static MAIN_FRAG: HlslProgram = fxc_main("main_frag.hlsl", MAIN_FRAG_HLSL, "ps_5_1");
pub(super) static SKINNED_VERT: HlslProgram = fxc_main(
    "skinned_vert.hlsl",
    include_str!("shaders/skinned_vert.hlsl"),
    "vs_5_1",
);

const fn fxc_cull(entry: &'static str) -> HlslProgram {
    HlslProgram {
        entry,
        object_data: true,
        ..fxc_main("cull.hlsl", CULL_HLSL, "cs_5_1")
    }
}

pub(super) static CULL: HlslProgram = fxc_cull("main");
pub(super) static CULL_PHASE2: HlslProgram = fxc_cull("main_phase2");
pub(super) static CULL_SHADOW: HlslProgram = fxc_cull("main_shadow");

// Every declared program, iterated by the export-time precompile.
pub(crate) static ALL: &[&HlslProgram] = &[
    &MAIN_VERT,
    &MAIN_FRAG,
    &MAIN_VERT_INSTANCED,
    &SKINNED_VERT,
    &CULL,
    &CULL_PHASE2,
    &CULL_SHADOW,
];

#[cfg(test)]
mod tests {
    use super::*;

    // Two programs collide when they would compile identical source text to
    // the same entry + target; the table must not declare the same slot twice.
    #[test]
    fn table_has_no_duplicate_programs() {
        let mut seen = std::collections::HashSet::new();
        for p in ALL {
            assert!(
                seen.insert((p.source(false), p.entry, p.target)),
                "duplicate program: {} {}",
                p.entry,
                p.target
            );
        }
    }

    // Every program's assembled source must embed its body, so a shader edit is
    // always visible to the cache key. A body carrying the `{OBJECT_DATA}`
    // marker is split by the substitution, so both halves are checked.
    #[test]
    fn every_program_source_contains_its_embedded_body() {
        for p in ALL {
            let src = p.source(false);
            for part in p.embedded.split("{OBJECT_DATA}") {
                assert!(src.contains(part), "{} {} lost its body", p.entry, p.target);
            }
        }
    }

    // Every program that strides the per-frame object StructuredBuffer gets the
    // shared record spliced in, and no other program carries a stray
    // declaration: the whole point of the fragment is that `GpuObjectData`
    // exists exactly once.
    #[test]
    fn object_data_programs_splice_the_shared_record() {
        let mut spliced = 0usize;
        for p in ALL {
            let src = p.source(false);
            let declares = src.contains("struct GpuObjectData");
            assert_eq!(
                declares, p.object_data,
                "{} {}: declares GpuObjectData = {declares}, object_data = {}",
                p.entry, p.target, p.object_data
            );
            assert!(
                !src.contains("{OBJECT_DATA}"),
                "{} {} left {{OBJECT_DATA}}",
                p.entry,
                p.target
            );
            spliced += usize::from(declares);
        }
        assert_eq!(spliced, 3, "object-data program count changed");
    }
}