#![deny(unsafe_op_in_unsafe_fn)]
use crate::metal::error::allocation_failed;
use concinnity_core::gfx::render_types;
use concinnity_core::render::error::RenderResult;
use concinnity_core::render::post::ssr::settings::SsrSettings;
use concinnity_core::render::post::ssr::{SsrInputs, SsrPass};
use objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2_metal::{
MTLDevice as _, MTLLoadAction, MTLPixelFormat, MTLRenderPipelineState, MTLTexture,
MTLTextureUsage,
};
use crate::metal::builtin_shaders::{REFLECTION_BLUR, REFLECTION_COMPOSITE};
use crate::metal::context::MtlContext;
use crate::metal::descriptors::TextureDesc;
use crate::metal::encode::RenderEncode;
use crate::metal::post::fullscreen::{
FullscreenBlend, FullscreenPass, PassTimer, build_fullscreen_pipeline,
set_fragment_sampler_range,
};
use crate::metal::post::post_device::MtlPostPipeline;
pub(crate) type MtlSsrPass = SsrPass<MtlPostPipeline>;
pub(crate) struct SsrState {
pub settings: Option<SsrSettings>,
pub targets: Option<SsrTargets>,
pub resolve: Option<MtlSsrPass>,
pub composite_pipeline: Option<Retained<ProtocolObject<dyn MTLRenderPipelineState>>>,
pub blur_pipeline: Option<Retained<ProtocolObject<dyn MTLRenderPipelineState>>>,
pub scales: ReflectionScales,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct ReflectionScales {
pub trace: u32,
pub blur: u32,
}
pub(crate) fn build_reflection_composite_pipeline(
device: &ProtocolObject<dyn objc2_metal::MTLDevice>,
hot_reload: bool,
) -> RenderResult<Retained<ProtocolObject<dyn MTLRenderPipelineState>>> {
build_fullscreen_pipeline(
device,
&REFLECTION_COMPOSITE,
MTLPixelFormat::RGBA16Float,
FullscreenBlend::Replace,
hot_reload,
)
}
pub(crate) fn build_reflection_blur_pipeline(
device: &ProtocolObject<dyn objc2_metal::MTLDevice>,
hot_reload: bool,
) -> RenderResult<Retained<ProtocolObject<dyn MTLRenderPipelineState>>> {
build_fullscreen_pipeline(
device,
&REFLECTION_BLUR,
MTLPixelFormat::RGBA16Float,
FullscreenBlend::Replace,
hot_reload,
)
}
pub(crate) struct SsrTargets {
pub reflection: Retained<ProtocolObject<dyn MTLTexture>>,
pub output: Retained<ProtocolObject<dyn MTLTexture>>,
pub blur: Retained<ProtocolObject<dyn MTLTexture>>,
}
pub(crate) fn create_ssr_targets(
device: &ProtocolObject<dyn objc2_metal::MTLDevice>,
width: u32,
height: u32,
scales: ReflectionScales,
) -> RenderResult<SsrTargets> {
let make_at = |w: u32, h: u32| -> Option<Retained<ProtocolObject<dyn MTLTexture>>> {
let desc = TextureDesc {
format: MTLPixelFormat::RGBA16Float,
width: w as usize,
height: h as usize,
usage: MTLTextureUsage(MTLTextureUsage::ShaderRead.0 | MTLTextureUsage::RenderTarget.0),
..Default::default()
}
.build();
device.newTextureWithDescriptor(&desc)
};
let reduced = |scale: u32| {
let s = scale.max(1);
((width / s).max(1), (height / s).max(1))
};
let (rw, rh) = reduced(scales.trace);
let (bw, bh) = reduced(scales.blur);
let reflection = make_at(rw, rh).ok_or_else(|| allocation_failed("reflection texture"))?;
let output = make_at(width.max(1), height.max(1))
.ok_or_else(|| allocation_failed("SSR output texture"))?;
let blur = make_at(bw, bh).ok_or_else(|| allocation_failed("reflection blur texture"))?;
Ok(SsrTargets {
reflection,
output,
blur,
})
}
impl MtlContext {
pub(in crate::metal) fn encode_ssr_resolve(
&self,
cmd_buf: &ProtocolObject<dyn objc2_metal::MTLCommandBuffer>,
ssr_params: &render_types::SsrParams,
) -> RenderResult<u32> {
let (Some(targets), Some(resolve), Some(normal_depth), Some(roughness)) = (
&self.ssr.targets,
&self.ssr.resolve,
self.gbuffer_normal_depth(),
self.gbuffer_roughness(),
) else {
return Ok(0);
};
resolve.encode(
&self.post_device(),
cmd_buf,
SsrInputs {
target: targets.reflection.as_ref(),
scene: self.targets.hdr.hdr_resolve.as_ref(),
normal_depth,
roughness,
prefilter: self.scene.env_map.prefilter.as_ref(),
},
ssr_params,
)?;
self.encode_reflection_composite(cmd_buf)?;
Ok(0)
}
pub(in crate::metal) fn encode_reflection_composite(
&self,
cmd_buf: &ProtocolObject<dyn objc2_metal::MTLCommandBuffer>,
) -> RenderResult<()> {
let (targets, composite_ps, blur_ps, gb_normal_depth, gb_roughness) = match (
&self.ssr.targets,
&self.ssr.composite_pipeline,
&self.ssr.blur_pipeline,
self.gbuffer_normal_depth(),
self.gbuffer_roughness(),
) {
(Some(t), Some(cp), Some(bp), Some(n), Some(r)) => (t, cp, bp, n, r),
_ => return Ok(()),
};
self.fullscreen_pass(
cmd_buf,
FullscreenPass {
target: targets.blur.as_ref(),
load: MTLLoadAction::DontCare,
timer: PassTimer::First(crate::metal::pass_timing::PassId::ReflectionComposite),
pipeline: blur_ps,
label: "reflection blur",
},
|enc| {
enc.set_fragment_texture(targets.reflection.as_ref(), 0);
enc.set_fragment_texture(gb_roughness, 1);
set_fragment_sampler_range(enc, &self.composite.sampler, 0, 2);
},
)?;
self.fullscreen_pass(
cmd_buf,
FullscreenPass {
target: targets.output.as_ref(),
load: MTLLoadAction::DontCare,
timer: PassTimer::Last(crate::metal::pass_timing::PassId::ReflectionComposite),
pipeline: composite_ps,
label: "reflection composite",
},
|enc| {
enc.set_fragment_texture(targets.reflection.as_ref(), 0);
enc.set_fragment_texture(self.targets.hdr.hdr_resolve.as_ref(), 1);
enc.set_fragment_texture(gb_normal_depth, 2);
enc.set_fragment_texture(gb_roughness, 3);
enc.set_fragment_texture(targets.blur.as_ref(), 4);
set_fragment_sampler_range(enc, &self.composite.sampler, 0, 5);
},
)?;
Ok(())
}
}