#![deny(unsafe_op_in_unsafe_fn)]
use objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2_metal::{
MTLDevice as _, MTLLoadAction, MTLPixelFormat, MTLRenderPipelineState, MTLTexture,
MTLTextureUsage,
};
use crate::gfx::ssr::SsrSettings;
use crate::metal::context::MtlContext;
use crate::metal::descriptors::TextureDesc;
use crate::metal::encode::RenderEncode;
use crate::metal::post::fullscreen::{
FullscreenBlend, FullscreenPass, PassTimer, build_slang_fullscreen_pipeline,
set_fragment_sampler_range,
};
use crate::metal::slang_shaders::{REFLECTION_BLUR, REFLECTION_COMPOSITE, SSR_RESOLVE};
pub(crate) struct SsrState {
pub settings: Option<SsrSettings>,
pub targets: Option<SsrTargets>,
pub resolve_pipeline: Option<Retained<ProtocolObject<dyn MTLRenderPipelineState>>>,
pub composite_pipeline: Option<Retained<ProtocolObject<dyn MTLRenderPipelineState>>>,
pub blur_pipeline: Option<Retained<ProtocolObject<dyn MTLRenderPipelineState>>>,
pub blur_scale: u32,
}
pub(crate) fn build_ssr_pipeline(
device: &ProtocolObject<dyn objc2_metal::MTLDevice>,
hot_reload: bool,
) -> Result<Retained<ProtocolObject<dyn MTLRenderPipelineState>>, String> {
build_slang_fullscreen_pipeline(
device,
&SSR_RESOLVE,
MTLPixelFormat::RGBA16Float,
FullscreenBlend::Replace,
hot_reload,
)
}
pub(crate) fn build_reflection_composite_pipeline(
device: &ProtocolObject<dyn objc2_metal::MTLDevice>,
hot_reload: bool,
) -> Result<Retained<ProtocolObject<dyn MTLRenderPipelineState>>, String> {
build_slang_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,
) -> Result<Retained<ProtocolObject<dyn MTLRenderPipelineState>>, String> {
build_slang_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,
blur_scale: u32,
) -> Result<SsrTargets, String> {
let blur_scale = blur_scale.max(1);
let make_at = |w: usize, h: usize| -> Option<Retained<ProtocolObject<dyn MTLTexture>>> {
let desc = TextureDesc {
format: MTLPixelFormat::RGBA16Float,
width: w,
height: h,
usage: MTLTextureUsage(MTLTextureUsage::ShaderRead.0 | MTLTextureUsage::RenderTarget.0),
..Default::default()
}
.build();
device.newTextureWithDescriptor(&desc)
};
let w = width.max(1) as usize;
let h = height.max(1) as usize;
let bw = (width / blur_scale).max(1) as usize;
let bh = (height / blur_scale).max(1) as usize;
let reflection = make_at(w, h).ok_or("failed to create reflection texture")?;
let output = make_at(w, h).ok_or("failed to create SSR output texture")?;
let blur = make_at(bw, bh).ok_or("failed to create 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: &crate::gfx::render_types::SsrParams,
) -> Result<u32, String> {
let (targets, resolve_ps, gb_normal_depth, gb_roughness) = match (
&self.ssr.targets,
&self.ssr.resolve_pipeline,
self.gbuffer_normal_depth(),
self.gbuffer_roughness(),
) {
(Some(t), Some(b), Some(n), Some(r)) => (t, b, n, r),
_ => return Ok(0),
};
self.fullscreen_pass(
cmd_buf,
FullscreenPass {
target: targets.reflection.as_ref(),
load: MTLLoadAction::DontCare,
timer: PassTimer::Whole(crate::metal::pass_timing::PassId::SsrResolve),
pipeline: resolve_ps,
label: "SSR resolve",
},
|enc| {
enc.set_fragment_texture(self.hdr_targets.hdr_resolve.as_ref(), 0);
enc.set_fragment_texture(gb_normal_depth, 1);
enc.set_fragment_texture(gb_roughness, 2);
enc.set_fragment_texture(self.env_map.prefilter.as_ref(), 3);
for i in 0..concinnity_core::render::uniforms::MAX_PROBES {
enc.set_fragment_texture(self.probe_cube_or_sky(i), 4 + i);
}
set_fragment_sampler_range(enc, &self.post_sampler, 0, 3);
set_fragment_sampler_range(
enc,
self.cube_sampler.as_ref(),
3,
1 + concinnity_core::render::uniforms::MAX_PROBES,
);
enc.set_fragment_value(ssr_params, 0);
enc.set_fragment_value(&self.probe.set, 1);
},
)?;
self.encode_reflection_composite(cmd_buf)?;
Ok(0)
}
pub(in crate::metal) fn encode_reflection_composite(
&self,
cmd_buf: &ProtocolObject<dyn objc2_metal::MTLCommandBuffer>,
) -> Result<(), String> {
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.post_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.hdr_targets.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.post_sampler, 0, 5);
},
)?;
Ok(())
}
}