#![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::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::TAA_FRAG;
use concinnity_core::render::uniforms::TaaParams;
pub(crate) struct TaaState {
pub enabled: bool,
pub pipeline_state: Option<Retained<ProtocolObject<dyn MTLRenderPipelineState>>>,
pub targets: Vec<Retained<ProtocolObject<dyn MTLTexture>>>,
pub dst: usize,
pub history_valid: bool,
pub frame: u32,
}
pub(crate) fn build_taa_pipeline(
device: &ProtocolObject<dyn objc2_metal::MTLDevice>,
hot_reload: bool,
) -> Result<Retained<ProtocolObject<dyn MTLRenderPipelineState>>, String> {
build_slang_fullscreen_pipeline(
device,
&TAA_FRAG,
MTLPixelFormat::RGBA16Float,
FullscreenBlend::Replace,
hot_reload,
)
}
pub(crate) fn create_taa_targets(
device: &ProtocolObject<dyn objc2_metal::MTLDevice>,
width: u32,
height: u32,
) -> Result<[Retained<ProtocolObject<dyn MTLTexture>>; 2], String> {
let w = width.max(1) as usize;
let h = height.max(1) as usize;
let make = || -> Result<Retained<ProtocolObject<dyn MTLTexture>>, String> {
let desc = TextureDesc {
format: MTLPixelFormat::RGBA16Float,
width: w,
height: h,
usage: MTLTextureUsage(MTLTextureUsage::ShaderRead.0 | MTLTextureUsage::RenderTarget.0),
..Default::default()
}
.build();
device
.newTextureWithDescriptor(&desc)
.ok_or("failed to create TAA target texture".to_string())
};
Ok([make()?, make()?])
}
impl MtlContext {
pub(in crate::metal) fn encode_taa(
&self,
cmd_buf: &ProtocolObject<dyn objc2_metal::MTLCommandBuffer>,
taa_uniforms: &TaaParams,
scene_input: &ProtocolObject<dyn objc2_metal::MTLTexture>,
) -> Result<u32, String> {
let pipeline = self
.taa
.pipeline_state
.as_ref()
.ok_or("TAA enabled but pipeline missing")?;
let velocity = self
.gbuffer_velocity()
.ok_or("TAA enabled but the pooled G-buffer velocity is missing")?;
let history = &self.taa.targets[1 - self.taa.dst];
let dst = &self.taa.targets[self.taa.dst];
self.fullscreen_pass(
cmd_buf,
FullscreenPass {
target: dst.as_ref(),
load: MTLLoadAction::DontCare,
timer: PassTimer::Whole(crate::metal::pass_timing::PassId::TaaResolve),
pipeline,
label: "TAA resolve",
},
|enc| {
enc.set_fragment_texture(scene_input, 0);
enc.set_fragment_texture(velocity, 1);
enc.set_fragment_texture(history.as_ref(), 2);
enc.set_fragment_value(taa_uniforms, 0);
set_fragment_sampler_range(enc, &self.post_sampler, 0, 3);
},
)?;
Ok(0)
}
}