use concinnity_core::components::ShaderPrograms;
use concinnity_core::components::sdf_programs::SdfPrograms;
use concinnity_core::render::backend::{PipelineBuilder, PreparedPipelines};
use concinnity_core::render::error::RenderResult;
use concinnity_core::render::shader_programs::raymarch::VolumeFlags;
use objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2_metal::MTLDevice;
use super::bucket_pipelines::{BucketBuild, BucketPipelines, build_bucket_pipelines};
use super::init::pipelines::make_vertex_descriptor;
use super::raymarch::{VolumePipelines, build_volume_pipelines};
pub(super) struct MtlPipelineBuilder {
pub device: Retained<ProtocolObject<dyn MTLDevice>>,
pub targets: BucketBuild,
}
pub(super) struct PreparedWorldShader {
pub targets: BucketBuild,
pub pipelines: BucketPipelines,
}
pub(super) struct PreparedVolume {
pub hot_reload: bool,
pub flags: VolumeFlags,
pub pipelines: VolumePipelines,
}
impl PipelineBuilder for MtlPipelineBuilder {
fn world_shader(
&self,
bucket: u32,
programs: &ShaderPrograms,
) -> RenderResult<PreparedPipelines> {
let pipelines = objc2::rc::autoreleasepool(|_| {
build_bucket_pipelines(
&self.device,
&make_vertex_descriptor(),
bucket as usize,
Some(programs),
self.targets,
)
})?;
Ok(PreparedPipelines::new(PreparedWorldShader {
targets: self.targets,
pipelines,
}))
}
fn sdf_volume(
&self,
programs: &SdfPrograms,
flags: VolumeFlags,
label: &str,
) -> RenderResult<PreparedPipelines> {
let hot_reload = self.targets.hot_reload;
let pipelines = objc2::rc::autoreleasepool(|_| {
build_volume_pipelines(&self.device, programs, flags, hot_reload, label)
})?;
Ok(PreparedPipelines::new(PreparedVolume {
hot_reload,
flags,
pipelines,
}))
}
}
pub(super) fn world_shader_for(
prepared: Option<PreparedPipelines>,
targets: BucketBuild,
) -> Option<BucketPipelines> {
prepared?
.downcast::<PreparedWorldShader>()
.filter(|p| p.targets == targets)
.map(|p| p.pipelines)
}
pub(super) fn volume_for(
prepared: Option<PreparedPipelines>,
flags: VolumeFlags,
hot_reload: bool,
) -> Option<VolumePipelines> {
prepared?
.downcast::<PreparedVolume>()
.filter(|p| p.flags == flags && p.hot_reload == hot_reload)
.map(|p| p.pipelines)
}
#[cfg(test)]
mod tests {
use super::*;
use objc2_metal::{MTLCreateSystemDefaultDevice, MTLPixelFormat, MTLRenderPipelineState};
const TARGETS: BucketBuild = BucketBuild {
sample_count: 4,
hot_reload: true,
prepass: true,
template_generation: 1,
};
const SURFACE: VolumeFlags = VolumeFlags {
volumetric: false,
cast_shadows: false,
};
fn pipeline() -> Option<Retained<ProtocolObject<dyn MTLRenderPipelineState>>> {
let device = MTLCreateSystemDefaultDevice()?;
Some(
super::super::pipeline::build_text_pipeline(&device, MTLPixelFormat::BGRA8Unorm, false)
.unwrap(),
)
}
fn world(
targets: BucketBuild,
pipeline: &Retained<ProtocolObject<dyn MTLRenderPipelineState>>,
) -> Option<PreparedPipelines> {
Some(PreparedPipelines::new(PreparedWorldShader {
targets,
pipelines: BucketPipelines {
main: pipeline.clone(),
prepass: Some(pipeline.clone()),
programs: None,
},
}))
}
fn volume(
flags: VolumeFlags,
hot_reload: bool,
pipeline: &Retained<ProtocolObject<dyn MTLRenderPipelineState>>,
) -> Option<PreparedPipelines> {
Some(PreparedPipelines::new(PreparedVolume {
hot_reload,
flags,
pipelines: VolumePipelines {
pipeline: pipeline.clone(),
front_pipeline: None,
shadow_pipeline: None,
prepass_pipelines: None,
},
}))
}
#[test]
fn a_world_shader_pipeline_is_taken_only_for_the_targets_it_was_built_for() {
let Some(pso) = pipeline() else { return };
assert!(world_shader_for(world(TARGETS, &pso), TARGETS).is_some());
let single_sample = BucketBuild {
sample_count: 1,
..TARGETS
};
assert!(world_shader_for(world(TARGETS, &pso), single_sample).is_none());
let embedded = BucketBuild {
hot_reload: false,
..TARGETS
};
assert!(world_shader_for(world(TARGETS, &pso), embedded).is_none());
let no_gbuffer = BucketBuild {
prepass: false,
..TARGETS
};
assert!(world_shader_for(world(TARGETS, &pso), no_gbuffer).is_none());
let reloaded = BucketBuild {
template_generation: 2,
..TARGETS
};
assert!(world_shader_for(world(TARGETS, &pso), reloaded).is_none());
assert!(world_shader_for(volume(SURFACE, true, &pso), TARGETS).is_none());
assert!(world_shader_for(None, TARGETS).is_none());
}
#[test]
fn volume_pipelines_are_taken_only_for_the_flags_they_were_built_for() {
let Some(pso) = pipeline() else { return };
assert!(volume_for(volume(SURFACE, true, &pso), SURFACE, true).is_some());
let caster = VolumeFlags {
cast_shadows: true,
..SURFACE
};
assert!(volume_for(volume(SURFACE, true, &pso), caster, true).is_none());
assert!(volume_for(volume(SURFACE, true, &pso), SURFACE, false).is_none());
assert!(volume_for(world(TARGETS, &pso), SURFACE, true).is_none());
}
}