#![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::ssgi::SsgiSettings;
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::{SSGI_COMPOSITE, SSGI_GATHER, SlangLib};
pub(crate) struct SsgiState {
pub settings: Option<SsgiSettings>,
pub targets: Option<SsgiTargets>,
pub gather_pipeline: Option<Retained<ProtocolObject<dyn MTLRenderPipelineState>>>,
pub composite_pipeline: Option<Retained<ProtocolObject<dyn MTLRenderPipelineState>>>,
}
fn build_ssgi_pipeline(
device: &ProtocolObject<dyn objc2_metal::MTLDevice>,
fragment: &SlangLib,
additive: bool,
hot_reload: bool,
) -> Result<Retained<ProtocolObject<dyn MTLRenderPipelineState>>, String> {
let blend = if additive {
FullscreenBlend::Additive
} else {
FullscreenBlend::Replace
};
build_slang_fullscreen_pipeline(
device,
fragment,
MTLPixelFormat::RGBA16Float,
blend,
hot_reload,
)
}
pub(crate) fn build_ssgi_gather_pipeline(
device: &ProtocolObject<dyn objc2_metal::MTLDevice>,
hot_reload: bool,
) -> Result<Retained<ProtocolObject<dyn MTLRenderPipelineState>>, String> {
build_ssgi_pipeline(device, &SSGI_GATHER, false, hot_reload)
}
pub(crate) fn build_ssgi_composite_pipeline(
device: &ProtocolObject<dyn objc2_metal::MTLDevice>,
hot_reload: bool,
) -> Result<Retained<ProtocolObject<dyn MTLRenderPipelineState>>, String> {
build_ssgi_pipeline(device, &SSGI_COMPOSITE, true, hot_reload)
}
pub(crate) struct SsgiTargets {
pub gi: Retained<ProtocolObject<dyn MTLTexture>>,
}
pub(crate) fn create_ssgi_targets(
device: &ProtocolObject<dyn objc2_metal::MTLDevice>,
width: u32,
height: u32,
) -> Result<SsgiTargets, String> {
let w = width.max(1) as usize;
let h = height.max(1) as usize;
let usage = MTLTextureUsage(MTLTextureUsage::ShaderRead.0 | MTLTextureUsage::RenderTarget.0);
let desc = TextureDesc {
format: MTLPixelFormat::RGBA16Float,
width: w,
height: h,
usage,
..Default::default()
}
.build();
let gi = device
.newTextureWithDescriptor(&desc)
.ok_or("failed to create SSGI gi texture")?;
Ok(SsgiTargets { gi })
}
impl MtlContext {
pub(in crate::metal) fn encode_ssgi(
&self,
cmd_buf: &ProtocolObject<dyn objc2_metal::MTLCommandBuffer>,
ssgi_params: &crate::gfx::render_types::SsgiParams,
) -> Result<u32, String> {
let (targets, gather_ps, composite_ps, gbuffer) = match (
&self.ssgi.targets,
&self.ssgi.gather_pipeline,
&self.ssgi.composite_pipeline,
self.gbuffer_normal_depth(),
) {
(Some(t), Some(g), Some(c), Some(gb)) => (t, g, c, gb),
_ => return Ok(0),
};
self.fullscreen_pass(
cmd_buf,
FullscreenPass {
target: targets.gi.as_ref(),
load: MTLLoadAction::DontCare,
timer: PassTimer::First(crate::metal::pass_timing::PassId::Ssgi),
pipeline: gather_ps,
label: "SSGI gather",
},
|enc| {
enc.set_fragment_texture(self.hdr_targets.hdr_resolve.as_ref(), 0);
enc.set_fragment_texture(gbuffer, 1);
set_fragment_sampler_range(enc, &self.post_sampler, 0, 2);
enc.set_fragment_value(ssgi_params, 0);
},
)?;
self.fullscreen_pass(
cmd_buf,
FullscreenPass {
target: self.hdr_targets.hdr_resolve.as_ref(),
load: MTLLoadAction::Load,
timer: PassTimer::Last(crate::metal::pass_timing::PassId::Ssgi),
pipeline: composite_ps,
label: "SSGI composite",
},
|enc| {
enc.set_fragment_texture(targets.gi.as_ref(), 0);
enc.set_fragment_texture(gbuffer, 1);
set_fragment_sampler_range(enc, &self.post_sampler, 0, 2);
enc.set_fragment_value(ssgi_params, 0);
},
)?;
Ok(0)
}
}