#![deny(unsafe_op_in_unsafe_fn)]
use objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2_metal::{
MTLBuffer, MTLCommandBuffer as _, MTLLoadAction, MTLRenderPassDescriptor, MTLStoreAction,
};
use crate::gfx::render_types::{ShadowPassPush, ShadowUniforms, SpotShadowData};
use crate::metal::context::MtlContext;
use crate::metal::scoped_encoder::ScopedEncoder;
use super::shadow::ShadowPassBinding;
impl MtlContext {
pub(in crate::metal) fn next_spot_shadow_mask(&mut self) -> u32 {
let every_frame = matches!(
self.shadow.update,
crate::components::ShadowUpdate::EveryFrame
);
self.spot_shadow
.scheduler
.next_mask(every_frame, self.spot_shadow.count as usize)
}
pub(in crate::metal) fn encode_spot_shadow_pass(
&self,
cmd_buf: &ProtocolObject<dyn objc2_metal::MTLCommandBuffer>,
skinned_joint_bufs: &[Retained<ProtocolObject<dyn MTLBuffer>>],
cam_pos: [f32; 3],
) -> Result<u32, String> {
let Some(shadow_pipeline) = self.shadow.pipeline_state.clone() else {
return Ok(0);
};
if self.spot_shadow.count == 0 {
return Ok(0);
}
let all = if self.spot_shadow.count >= 32 {
u32::MAX
} else {
(1_u32 << self.spot_shadow.count) - 1
};
let mask = if self.spot_shadow.render_mask == 0 {
all
} else {
self.spot_shadow.render_mask
};
let rendered: Vec<u32> = (0..self.spot_shadow.count)
.filter(|i| mask & (1u32 << i) != 0)
.collect();
let first_rendered = rendered.first().copied();
let last_rendered = rendered.last().copied();
let mut total_draws: u32 = 0;
for &slice in &rendered {
let pass_desc = MTLRenderPassDescriptor::new();
let depth_attach = pass_desc.depthAttachment();
depth_attach.setTexture(Some(self.spot_shadow.map.as_ref()));
depth_attach.setSlice(slice as usize);
depth_attach.setLoadAction(MTLLoadAction::Clear);
depth_attach.setStoreAction(MTLStoreAction::Store);
depth_attach.setClearDepth(1.0);
if let Some(t) = &self.diagnostics.pass_timing {
let id = super::super::pass_timing::PassId::SpotShadow;
let is_first = Some(slice) == first_rendered;
let is_last = Some(slice) == last_rendered;
if is_first && is_last {
t.attach_render(&pass_desc, id);
} else if is_first {
t.attach_render_first(&pass_desc, id);
} else if is_last {
t.attach_render_last(&pass_desc, id);
}
}
let enc = ScopedEncoder::new(
cmd_buf
.renderCommandEncoderWithDescriptor(&pass_desc)
.ok_or("failed to get spot shadow render encoder")?,
"spot shadow slice",
);
let uniforms = self.spot_slice_uniforms(slice);
let bind = ShadowPassBinding {
pipeline: &shadow_pipeline,
uniforms: &uniforms,
push: ShadowPassPush {
cascade_idx: 0,
_pad: [0; 3],
},
slope_bias: 1.0,
};
total_draws += self.encode_shadow_static_into(&enc, &bind, cam_pos);
total_draws += self.encode_shadow_instanced_into(&enc, &bind, cam_pos);
total_draws +=
self.encode_shadow_skinned_into(&enc, &bind, cam_pos, skinned_joint_bufs);
}
Ok(total_draws)
}
fn spot_slice_uniforms(&self, slice: u32) -> ShadowUniforms {
let data = self.spot_shadow_data(slice);
let mut uniforms = crate::gfx::csm::empty_shadow_uniforms();
uniforms.light_vps[0] = data.light_vp;
uniforms.active_cascades = 1;
uniforms
}
fn spot_shadow_data(&self, slice: u32) -> SpotShadowData {
debug_assert!(slice < self.spot_shadow.count);
unsafe {
let base = self.spot_shadow.buffer.contents().as_ptr() as *const SpotShadowData;
*base.add(slice as usize)
}
}
}