use super::pipeline::{
OBJECT_COMMON_GLSL, compile_glsl, compile_glsl_rt, inject_define, shader_source,
};
pub(crate) fn world_pool_size(texture_count: usize) -> usize {
texture_count.max(1) + crate::gfx::render_types::FALLBACK_TEXTURE_COUNT
}
pub(crate) fn bindless_pool_size(texture_count: usize, ceiling_fits: bool) -> usize {
if ceiling_fits {
concinnity_core::render::uniforms::BINDLESS_POOL_SIZE
} else {
world_pool_size(texture_count)
}
}
pub(crate) struct Assembly {
pub define: Option<&'static str>,
pub pool_size: bool,
pub object_data: bool,
}
const PLAIN: Assembly = Assembly {
define: None,
pool_size: false,
object_data: false,
};
pub(crate) struct Ctx {
pub hot_reload: bool,
pub msaa: bool,
pub pool_size: usize,
pub probe_count: usize,
}
impl Ctx {
pub(crate) fn plain(hot_reload: bool) -> Self {
Self {
hot_reload,
msaa: false,
pool_size: 0,
probe_count: 0,
}
}
}
pub(crate) struct GlslProgram {
pub file: &'static str,
pub embedded: &'static str,
pub kind: shaderc::ShaderKind,
pub label: &'static str,
pub rt: bool,
pub assembly: Assembly,
}
impl GlslProgram {
pub(crate) fn source(&self, ctx: &Ctx) -> String {
let mut src = shader_source(ctx.hot_reload, self.file, self.embedded).into_owned();
if let Some(define) = self.assembly.define {
src = inject_define(&src, define);
}
if self.assembly.pool_size {
src = src.replace("{POOL_SIZE}", &ctx.pool_size.to_string());
}
if self.assembly.object_data {
let object_common =
shader_source(ctx.hot_reload, "object_common.glsl", OBJECT_COMMON_GLSL);
src = src.replace("{OBJECT_DATA}", &object_common);
}
src
}
pub(crate) fn compile(&self, ctx: &Ctx) -> Result<Vec<u8>, String> {
let source = self.source(ctx);
if self.rt {
compile_glsl_rt(&source, self.kind, self.label)
} else {
compile_glsl(&source, self.kind, self.label)
}
}
}
pub(crate) fn precompile(out_dir: &std::path::Path, report: &mut crate::precompile::Report) {
let pool_size = concinnity_core::render::uniforms::BINDLESS_POOL_SIZE;
for program in ALL {
let ctx = Ctx {
hot_reload: false,
msaa: false,
pool_size,
probe_count: concinnity_core::render::uniforms::MAX_PROBES,
};
let source = program.source(&ctx);
let key = if program.rt {
super::pipeline::glsl_rt_cache_key(&source, program.kind)
} else {
super::pipeline::glsl_cache_key(&source, program.kind)
};
let compile = || {
if program.rt {
compile_glsl_rt(&source, program.kind, program.label)
} else {
compile_glsl(&source, program.kind, program.label)
}
};
report.record(
program.label,
crate::shader_cache::ensure_in(out_dir, &key, compile),
);
}
for program in super::slang_builtins::ALL {
let msaa_variants: &[bool] = if program.msaa {
&[false, true]
} else {
&[false]
};
for &msaa in msaa_variants {
let ctx = Ctx {
hot_reload: false,
msaa,
pool_size,
probe_count: concinnity_core::render::uniforms::MAX_PROBES,
};
let source = program.source(&ctx);
let key = program.cache_key(&source);
report.record(
program.label,
crate::shader_cache::ensure_in(out_dir, &key, || {
super::slang_builtins::compile_uncached(program, &source)
}),
);
}
}
}
const fn glsl(
file: &'static str,
embedded: &'static str,
kind: shaderc::ShaderKind,
label: &'static str,
) -> GlslProgram {
GlslProgram {
file,
embedded,
kind,
label,
rt: false,
assembly: PLAIN,
}
}
use crate::vulkan::slang_builtins::SlangCompile;
use shaderc::ShaderKind::{Compute, Fragment, Vertex};
const CULL_COMPUTE_GLSL: &str = include_str!("shaders/cull.comp");
pub(super) static MAIN_VERT: GlslProgram = glsl(
"main.vert",
include_str!("shaders/main.vert"),
Vertex,
"vert.glsl",
);
pub(super) static MAIN_FRAG: GlslProgram = glsl(
"main.frag",
include_str!("shaders/main.frag"),
Fragment,
"frag.glsl",
);
pub(super) static MAIN_VERT_INSTANCED: GlslProgram = glsl(
"instanced.vert",
include_str!("shaders/instanced.vert"),
Vertex,
"vert_instanced.glsl",
);
pub(super) static SKINNED_VERT: GlslProgram = glsl(
"skinned.vert",
include_str!("shaders/skinned.vert"),
Vertex,
"skinned_vert.glsl",
);
pub(super) static CULL: GlslProgram = GlslProgram {
assembly: Assembly {
object_data: true,
..PLAIN
},
..glsl("cull.comp", CULL_COMPUTE_GLSL, Compute, "cull_compute.glsl")
};
pub(super) static CULL_PHASE2: GlslProgram = GlslProgram {
assembly: Assembly {
define: Some("#define CULL_PHASE2 1\n"),
object_data: true,
..PLAIN
},
..glsl(
"cull.comp",
CULL_COMPUTE_GLSL,
Compute,
"cull_compute_phase2.glsl",
)
};
pub(super) static CULL_SHADOW: GlslProgram = GlslProgram {
assembly: Assembly {
define: Some("#define SHADOW_CULL 1\n"),
object_data: true,
..PLAIN
},
..glsl(
"cull.comp",
CULL_COMPUTE_GLSL,
Compute,
"cull_compute_shadow.glsl",
)
};
pub(super) static RAYMARCH_PROXY_VERT: GlslProgram = glsl(
"raymarch_proxy.vert",
include_str!("shaders/raymarch_proxy.vert"),
Vertex,
"raymarch_proxy.vert",
);
pub(super) static RAYMARCH_SHADOW_PROXY_VERT: GlslProgram = glsl(
"raymarch_shadow_proxy.vert",
include_str!("shaders/raymarch_shadow_proxy.vert"),
Vertex,
"raymarch_shadow_proxy.vert",
);
pub(crate) static ALL: &[&GlslProgram] = &[
&MAIN_VERT,
&MAIN_FRAG,
&MAIN_VERT_INSTANCED,
&SKINNED_VERT,
&CULL,
&CULL_PHASE2,
&CULL_SHADOW,
&RAYMARCH_PROXY_VERT,
&RAYMARCH_SHADOW_PROXY_VERT,
];
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pool_size_counts_fallbacks() {
assert_eq!(world_pool_size(0), 3);
assert_eq!(world_pool_size(1), 3);
assert_eq!(world_pool_size(7), 9);
}
#[test]
fn world_pool_size_matches_the_uploaded_image_counts() {
for texture_count in [0usize, 1, 7, 64] {
let gpu_textures = texture_count.max(1);
assert_eq!(
world_pool_size(texture_count),
gpu_textures + crate::gfx::render_types::FALLBACK_TEXTURE_COUNT
);
assert!(world_pool_size(texture_count) > texture_count);
}
}
#[test]
fn the_ceiling_makes_the_pool_length_independent_of_the_world() {
let ceiling = concinnity_core::render::uniforms::BINDLESS_POOL_SIZE;
for texture_count in [0usize, 1, 7, 64] {
assert_eq!(bindless_pool_size(texture_count, true), ceiling);
assert_eq!(
bindless_pool_size(texture_count, false),
world_pool_size(texture_count)
);
}
}
#[test]
fn a_world_that_fits_the_ceiling_leaves_room_to_pad() {
let ceiling = concinnity_core::render::uniforms::BINDLESS_POOL_SIZE;
assert!(world_pool_size(ceiling - 3) <= ceiling);
assert!(world_pool_size(ceiling) > ceiling);
}
#[test]
fn pool_programs_size_their_texture_array_from_the_context() {
for pool_size in [2usize, 8, 65] {
let ctx = Ctx {
hot_reload: false,
msaa: false,
pool_size,
probe_count: 4,
};
for p in ALL.iter().filter(|p| p.assembly.pool_size) {
let src = p.source(&ctx);
assert!(
src.contains(&format!("tex_pool[{pool_size}]")),
"{} did not size its texture pool at {pool_size}",
p.label
);
}
}
}
#[test]
fn table_has_no_duplicate_programs() {
let mut seen = std::collections::HashSet::new();
for p in ALL {
let ctx = Ctx {
hot_reload: false,
msaa: false,
pool_size: 4,
probe_count: 4,
};
assert!(
seen.insert((p.source(&ctx), p.kind as u32, p.rt)),
"duplicate program: {}",
p.label
);
}
}
#[test]
fn defines_inject_after_the_version_directive() {
let ctx = Ctx {
hot_reload: false,
msaa: false,
pool_size: 3,
probe_count: 3,
};
let src = CULL_PHASE2.source(&ctx);
let mut lines = src.lines();
assert!(lines.next().unwrap().starts_with("#version"));
assert_eq!(lines.next().unwrap(), "#define CULL_PHASE2 1");
}
#[test]
fn substitutions_resolve_every_marker() {
let ctx = Ctx {
hot_reload: false,
msaa: false,
pool_size: 5,
probe_count: 5,
};
for p in ALL {
let src = p.source(&ctx);
for marker in [
"{PROBE_COMMON}",
"{MAX_PROBES}",
"{PROBE_DESC_SET}",
"{POOL_SIZE}",
"{OBJECT_DATA}",
] {
assert!(!src.contains(marker), "{} left {}", p.label, marker);
}
}
}
#[test]
fn object_data_programs_splice_the_shared_record() {
let ctx = Ctx {
hot_reload: false,
msaa: false,
pool_size: 4,
probe_count: 4,
};
let mut spliced = 0usize;
for p in ALL {
let declares = p.source(&ctx).contains("struct GpuObjectData");
assert_eq!(
declares, p.assembly.object_data,
"{}: declares GpuObjectData = {declares}, object_data = {}",
p.label, p.assembly.object_data
);
spliced += usize::from(declares);
}
assert_eq!(spliced, 3, "object-data program count changed");
}
}