#![deny(unsafe_op_in_unsafe_fn)]
use objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2_metal::{
MTLCommandBuffer as _, MTLComputeCommandEncoder as _, MTLComputePipelineState, MTLDevice as _,
MTLLibrary as _, MTLResourceOptions, MTLSize,
};
use crate::gfx::render_types::{CLUSTER_COUNT, CLUSTER_LIGHT_LIST_STRIDE, ClusterParams};
use super::context::MtlContext;
use super::encode::ComputeEncode;
use super::pipeline::ns_str;
use super::scoped_encoder::ScopedEncoder;
pub(crate) struct LightCullState {
pub pipeline: Option<Retained<ProtocolObject<dyn MTLComputePipelineState>>>,
pub cluster_buffer: Retained<ProtocolObject<dyn objc2_metal::MTLBuffer>>,
}
impl MtlContext {
pub(in crate::metal) fn encode_light_cull(
&self,
cmd_buf: &ProtocolObject<dyn objc2_metal::MTLCommandBuffer>,
cluster_params: &ClusterParams,
) -> Result<u32, String> {
let pipeline = match &self.light_cull.pipeline {
Some(p) => p,
None => return Ok(0),
};
let desc = objc2_metal::MTLComputePassDescriptor::computePassDescriptor();
if let Some(t) = &self.diagnostics.pass_timing {
t.attach_compute(&desc, super::pass_timing::PassId::LightCull);
}
let enc = ScopedEncoder::new(
cmd_buf
.computeCommandEncoderWithDescriptor(&desc)
.ok_or("failed to get light-cull compute encoder")?,
"clustered light cull",
);
enc.set_pipeline(pipeline);
enc.set_value(cluster_params, 0);
enc.set_buffer(&self.local_light_buffer, 0, 1);
enc.set_buffer(&self.light_cull.cluster_buffer, 0, 2);
let tg = MTLSize {
width: 64,
height: 1,
depth: 1,
};
let grid = MTLSize {
width: CLUSTER_COUNT as usize,
height: 1,
depth: 1,
};
enc.dispatchThreads_threadsPerThreadgroup(grid, tg);
Ok(0)
}
}
pub(super) fn build_light_cull_pipeline(
device: &ProtocolObject<dyn objc2_metal::MTLDevice>,
hot_reload: bool,
) -> Result<Retained<ProtocolObject<dyn MTLComputePipelineState>>, String> {
let library = super::slang_shaders::LIGHT_CULL.library(device, hot_reload)?;
let func = library
.newFunctionWithName(&ns_str("light_cull_kernel"))
.ok_or("light_cull_kernel not found")?;
device
.newComputePipelineStateWithFunction_error(&func)
.map_err(|e| format!("failed to create light cull pipeline: {:?}", e))
}
pub(super) fn build_cluster_light_buffer(
device: &ProtocolObject<dyn objc2_metal::MTLDevice>,
) -> Result<Retained<ProtocolObject<dyn objc2_metal::MTLBuffer>>, String> {
let len = (CLUSTER_COUNT * CLUSTER_LIGHT_LIST_STRIDE) as usize * std::mem::size_of::<u32>();
device
.newBufferWithLength_options(len, MTLResourceOptions::StorageModePrivate)
.ok_or_else(|| "failed to allocate cluster light buffer".to_string())
}