#![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::ssao::SsaoSettings;
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::SlangLib;
pub(crate) struct SsaoState {
pub settings: Option<SsaoSettings>,
pub targets: Option<SsaoTargets>,
pub kernel_pipeline: Option<Retained<ProtocolObject<dyn MTLRenderPipelineState>>>,
pub blur_pipeline: Option<Retained<ProtocolObject<dyn MTLRenderPipelineState>>>,
pub white: crate::metal::allocator::PooledTexture,
}
pub(crate) const SSAO_OCCLUSION_FORMAT: MTLPixelFormat = MTLPixelFormat::R8Unorm;
pub(crate) fn build_ssao_pipeline(
device: &ProtocolObject<dyn objc2_metal::MTLDevice>,
fragment: &SlangLib,
hot_reload: bool,
) -> Result<Retained<ProtocolObject<dyn MTLRenderPipelineState>>, String> {
build_slang_fullscreen_pipeline(
device,
fragment,
SSAO_OCCLUSION_FORMAT,
FullscreenBlend::Replace,
hot_reload,
)
}
pub(crate) struct SsaoTargets {
pub ao_raw: Retained<ProtocolObject<dyn MTLTexture>>,
}
pub(crate) fn create_ssao_targets(
device: &ProtocolObject<dyn objc2_metal::MTLDevice>,
width: u32,
height: u32,
) -> Result<SsaoTargets, String> {
let w = width.max(1) as usize;
let h = height.max(1) as usize;
let sampled = MTLTextureUsage(MTLTextureUsage::ShaderRead.0 | MTLTextureUsage::RenderTarget.0);
let make = |label: &str| -> Result<Retained<ProtocolObject<dyn MTLTexture>>, String> {
let desc = TextureDesc {
format: SSAO_OCCLUSION_FORMAT,
width: w,
height: h,
usage: sampled,
..Default::default()
}
.build();
device
.newTextureWithDescriptor(&desc)
.ok_or_else(|| format!("failed to create SSAO {} texture", label))
};
let ao_raw = make("raw occlusion")?;
Ok(SsaoTargets { ao_raw })
}
impl MtlContext {
pub(in crate::metal) fn encode_ssao(
&self,
cmd_buf: &ProtocolObject<dyn objc2_metal::MTLCommandBuffer>,
ssao_params: &crate::gfx::render_types::SsaoParams,
) -> Result<u32, String> {
let (targets, kernel_ps, blur_ps, gbuffer) = match (
&self.ssao.targets,
&self.ssao.kernel_pipeline,
&self.ssao.blur_pipeline,
self.gbuffer_normal_depth(),
) {
(Some(t), Some(b), Some(c), Some(g)) => (t, b, c, g),
_ => return Ok(0),
};
let ao_output = self
.transient_pool
.texture_for("ao_output")
.ok_or("ao_output missing from transient pool")?;
self.fullscreen_pass(
cmd_buf,
FullscreenPass {
target: targets.ao_raw.as_ref(),
load: MTLLoadAction::DontCare,
timer: PassTimer::Whole(crate::metal::pass_timing::PassId::SsaoKernel),
pipeline: kernel_ps,
label: "SSAO kernel",
},
|enc| {
enc.set_fragment_texture(gbuffer, 0);
set_fragment_sampler_range(enc, &self.post_sampler, 0, 1);
enc.set_fragment_value(ssao_params, 0);
},
)?;
self.fullscreen_pass(
cmd_buf,
FullscreenPass {
target: ao_output,
load: MTLLoadAction::DontCare,
timer: PassTimer::Whole(crate::metal::pass_timing::PassId::SsaoBlur),
pipeline: blur_ps,
label: "SSAO blur",
},
|enc| {
enc.set_fragment_texture(targets.ao_raw.as_ref(), 0);
enc.set_fragment_texture(gbuffer, 1);
set_fragment_sampler_range(enc, &self.post_sampler, 0, 2);
},
)?;
Ok(0)
}
}