#![deny(unsafe_op_in_unsafe_fn)]
use objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2_metal::{
MTLBuffer, MTLCommandBuffer, MTLComputeCommandEncoder as _, MTLComputePipelineState,
MTLDevice as _, MTLLibrary as _, MTLSize,
};
use concinnity_core::render::uniforms::ModelHistoryParams;
use super::context::MtlContext;
use super::encode::ComputeEncode;
use super::pipeline::ns_str;
use super::scoped_encoder::ScopedEncoder;
const THREADGROUP: usize = 64;
impl MtlContext {
pub(in crate::metal) fn encode_model_history(
&self,
cmd_buf: &ProtocolObject<dyn MTLCommandBuffer>,
object_buffer: &Retained<ProtocolObject<dyn MTLBuffer>>,
targets: &[Retained<ProtocolObject<dyn MTLBuffer>>],
record_count: usize,
) -> Result<(), String> {
let Some(pipeline) = &self.gbuffer.history_pipeline else {
return Ok(());
};
if record_count == 0 || targets.is_empty() {
return Ok(());
}
let params = ModelHistoryParams {
record_count: record_count as u32,
_pad: [0; 3],
};
let desc = objc2_metal::MTLComputePassDescriptor::computePassDescriptor();
if let Some(t) = &self.diagnostics.pass_timing {
t.attach_compute(&desc, super::pass_timing::PassId::GBufferPrepass);
}
let enc = ScopedEncoder::new(
cmd_buf
.computeCommandEncoderWithDescriptor(&desc)
.ok_or("failed to get model-history compute encoder")?,
"model history",
);
enc.set_pipeline(pipeline);
enc.set_value(¶ms, 0);
enc.set_buffer(object_buffer, 0, 1);
let grid = MTLSize {
width: record_count,
height: 1,
depth: 1,
};
let tg = MTLSize {
width: THREADGROUP,
height: 1,
depth: 1,
};
for target in targets {
enc.set_buffer(target, 0, 2);
enc.dispatchThreads_threadsPerThreadgroup(grid, tg);
}
Ok(())
}
}
pub(super) fn build_model_history_pipeline(
device: &ProtocolObject<dyn objc2_metal::MTLDevice>,
hot_reload: bool,
) -> Result<Retained<ProtocolObject<dyn MTLComputePipelineState>>, String> {
let library = super::slang_shaders::MODEL_HISTORY.library(device, hot_reload)?;
let func = library
.newFunctionWithName(&ns_str("model_history_kernel"))
.ok_or("model_history_kernel not found")?;
device
.newComputePipelineStateWithFunction_error(&func)
.map_err(|e| format!("failed to create model history pipeline: {:?}", e))
}