#![deny(unsafe_op_in_unsafe_fn)]
use concinnity_core::gfx::render_types::ClusterParams;
use concinnity_core::render::error::RenderResult;
use concinnity_core::render::reflection_probe::PrefilterPlan;
use concinnity_core::render::uniforms::{ProbeSet, ProbeUniforms, grown_probe_capacity};
use objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2_metal::{
MTLBuffer, MTLDevice, MTLRenderCommandEncoder, MTLTexture, MTLTextureType, MTLTextureUsage,
};
use super::context::MtlContext;
use super::descriptors::TextureDesc;
use super::encode::RenderEncode;
use super::error::allocation_failed;
use super::probe_prefilter::PROBE_CUBE_FORMAT;
pub(in crate::metal) struct ProbeCubeArray {
texture: Retained<ProtocolObject<dyn MTLTexture>>,
capacity: usize,
}
impl ProbeCubeArray {
pub(in crate::metal) fn placeholder(
device: &ProtocolObject<dyn MTLDevice>,
) -> RenderResult<ProbeCubeArray> {
Ok(ProbeCubeArray {
texture: create(device, 1, 1, 1)?,
capacity: 0,
})
}
pub(in crate::metal) fn new(
device: &ProtocolObject<dyn MTLDevice>,
plan: &PrefilterPlan,
capacity: usize,
) -> RenderResult<ProbeCubeArray> {
Ok(ProbeCubeArray {
texture: create(
device,
plan.face_size() as usize,
plan.mips() as usize,
capacity,
)?,
capacity,
})
}
pub(in crate::metal) fn capacity(&self) -> usize {
self.capacity
}
pub(in crate::metal) fn texture(&self) -> &ProtocolObject<dyn MTLTexture> {
&self.texture
}
}
fn create(
device: &ProtocolObject<dyn MTLDevice>,
face_size: usize,
mips: usize,
cubes: usize,
) -> RenderResult<Retained<ProtocolObject<dyn MTLTexture>>> {
let desc = TextureDesc {
kind: MTLTextureType::TypeCubeArray,
format: PROBE_CUBE_FORMAT,
width: face_size,
height: face_size,
mip_count: mips,
array_length: cubes.max(1),
usage: MTLTextureUsage(MTLTextureUsage::ShaderRead.0 | MTLTextureUsage::ShaderWrite.0),
..Default::default()
}
.build();
device
.newTextureWithDescriptor(&desc)
.ok_or_else(|| allocation_failed("probe cube array"))
}
#[derive(Clone, Copy)]
pub(in crate::metal) struct ProbeSlots {
pub set: usize,
pub records: usize,
pub cubes: Option<usize>,
pub cluster: Option<(usize, usize)>,
}
#[derive(Clone, Copy)]
pub(in crate::metal) struct ProbeBindings<'a> {
pub set: ProbeSet,
pub records: Option<&'a ProtocolObject<dyn MTLBuffer>>,
pub cubes: &'a ProtocolObject<dyn MTLTexture>,
pub cluster: ClusterParams,
pub cluster_list: &'a ProtocolObject<dyn MTLBuffer>,
}
impl ProbeBindings<'_> {
pub(in crate::metal) fn bind(
&self,
enc: &ProtocolObject<dyn MTLRenderCommandEncoder>,
slots: ProbeSlots,
) {
enc.set_fragment_value(&self.set, slots.set);
if let Some(records) = self.records {
enc.set_fragment_buffer(records, 0, slots.records);
}
if let Some(cubes) = slots.cubes {
enc.set_fragment_texture(self.cubes, cubes);
}
if let Some((params, list)) = slots.cluster {
enc.set_fragment_value(&self.cluster, params);
enc.set_fragment_buffer(self.cluster_list, 0, list);
}
}
}
impl MtlContext {
pub(in crate::metal) fn reserve_probe_cubes(
&mut self,
plan: &PrefilterPlan,
placements: usize,
) -> RenderResult<()> {
let Some(capacity) = grown_probe_capacity(self.probe.cubes.capacity(), placements, 1)
else {
return Ok(());
};
let grown = ProbeCubeArray::new(&self.hw.device, plan, capacity)?;
let old = core::mem::replace(&mut self.probe.cubes, grown);
self.probe.retire_pool.push(
self.frame_ring_index,
super::probe::RetiredBake::CubeArray(old),
);
self.arg_buffers.texture_epoch += 1;
tracing::debug!("reflection probes: cube array grown to {capacity}");
Ok(())
}
pub(in crate::metal) fn probe_set(&self) -> ProbeSet {
self.probe.book.header()
}
pub(in crate::metal) fn build_probe_records(
&mut self,
ring_slot: usize,
) -> RenderResult<Retained<ProtocolObject<dyn MTLBuffer>>> {
let empty = [<ProbeUniforms as bytemuck::Zeroable>::zeroed()];
let records: &[ProbeUniforms] = if self.probe.book.count() == 0 {
&empty
} else {
self.probe.book.records()
};
self.rings
.probe_records
.write(&self.hw.device, ring_slot, bytemuck::cast_slice(records))
}
pub(in crate::metal) fn probe_bindings(&self) -> ProbeBindings<'_> {
ProbeBindings {
set: self.probe_set(),
records: self.probe.records_buf.as_deref(),
cubes: self.probe.cubes.texture(),
cluster: self.cluster_params,
cluster_list: &self.light_cull.cluster_buffer,
}
}
}