#![deny(unsafe_op_in_unsafe_fn)]
use objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2_metal::{
MTLBuffer, MTLCommandBuffer as _, MTLCommandEncoder as _, MTLIndexType, MTLLoadAction,
MTLPrimitiveType, MTLRenderCommandEncoder as _, MTLRenderPassDescriptor, MTLStoreAction,
};
use crate::gfx::render_types::{ShadowPassPush, ShadowUniforms, SpotShadowData};
use crate::gfx::shadow_bias;
use crate::metal::context::MtlContext;
use crate::metal::encode::RenderEncode;
use crate::metal::scoped_encoder::ScopedEncoder;
use crate::metal::uniforms::ModelUniforms;
const SPOT_SLICE_IDX: u32 = 0;
struct SpotSliceBinding<'a> {
pipeline: &'a ProtocolObject<dyn objc2_metal::MTLRenderPipelineState>,
uniforms: &'a ShadowUniforms,
}
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 = SpotSliceBinding {
pipeline: &shadow_pipeline,
uniforms: &uniforms,
};
total_draws += self.encode_spot_casters(&enc, &bind, cam_pos, skinned_joint_bufs);
}
Ok(total_draws)
}
fn encode_spot_casters(
&self,
enc: &ProtocolObject<dyn objc2_metal::MTLRenderCommandEncoder>,
bind: &SpotSliceBinding,
cam_pos: [f32; 3],
skinned_joint_bufs: &[Retained<ProtocolObject<dyn MTLBuffer>>],
) -> u32 {
self.encode_spot_static_into(enc, bind, cam_pos)
+ self.encode_spot_instanced_into(enc, bind, cam_pos)
+ self.encode_spot_skinned_into(enc, bind, cam_pos, skinned_joint_bufs)
}
fn bind_spot_slice(
&self,
enc: &ProtocolObject<dyn objc2_metal::MTLRenderCommandEncoder>,
bind: &SpotSliceBinding,
) {
enc.set_pipeline(bind.pipeline);
enc.set_depth_stencil(&self.depth_state);
enc.setDepthBias_slopeScale_clamp(
shadow_bias::RASTER_CONSTANT,
shadow_bias::RASTER_SLOPE,
shadow_bias::RASTER_CLAMP,
);
enc.set_vertex_value(bind.uniforms, 0);
enc.set_vertex_value(
&ShadowPassPush {
cascade_idx: SPOT_SLICE_IDX,
_pad: [0; 3],
},
7,
);
enc.set_vertex_buffer(&self.vertex_buffer, 0, 1);
}
fn encode_spot_static_into(
&self,
enc: &ProtocolObject<dyn objc2_metal::MTLRenderCommandEncoder>,
bind: &SpotSliceBinding,
cam_pos: [f32; 3],
) -> u32 {
enc.pushDebugGroup(&objc2_foundation::NSString::from_str("spot shadow static"));
self.bind_spot_slice(enc, bind);
let mut draw_calls: u32 = 0;
for obj in &self.draw.objects {
if !obj.visible || !obj.resident {
continue;
}
let model_uniforms = ModelUniforms { model: obj.model };
enc.set_vertex_value(&model_uniforms, 2);
let d = crate::gfx::lod::camera_distance(obj, cam_pos);
let (index_offset, index_count) = obj.active_lod(d);
let index_byte_offset = index_offset * std::mem::size_of::<u32>();
unsafe {
enc.drawIndexedPrimitives_indexCount_indexType_indexBuffer_indexBufferOffset_instanceCount_baseVertex_baseInstance(
MTLPrimitiveType::Triangle,
index_count,
MTLIndexType::UInt32,
&self.index_buffer,
index_byte_offset,
1,
obj.base_vertex as isize,
0,
);
}
draw_calls += 1;
}
enc.popDebugGroup();
draw_calls
}
fn encode_spot_instanced_into(
&self,
enc: &ProtocolObject<dyn objc2_metal::MTLRenderCommandEncoder>,
bind: &SpotSliceBinding,
cam_pos: [f32; 3],
) -> u32 {
if self.instanced.clusters.is_empty() {
return 0;
}
enc.pushDebugGroup(&objc2_foundation::NSString::from_str(
"spot shadow instanced",
));
self.bind_spot_slice(enc, bind);
let mut draw_calls: u32 = 0;
for cluster in &self.instanced.clusters {
cluster.for_each_lod_bucket(cam_pos, |index_offset, index_count, instances| {
let index_byte_offset = index_offset * std::mem::size_of::<u32>();
for &model in instances {
let model_uniforms = ModelUniforms { model };
enc.set_vertex_value(&model_uniforms, 2);
unsafe {
enc.drawIndexedPrimitives_indexCount_indexType_indexBuffer_indexBufferOffset(
MTLPrimitiveType::Triangle,
index_count,
MTLIndexType::UInt32,
&self.index_buffer,
index_byte_offset,
);
}
draw_calls += 1;
}
});
}
enc.popDebugGroup();
draw_calls
}
fn encode_spot_skinned_into(
&self,
enc: &ProtocolObject<dyn objc2_metal::MTLRenderCommandEncoder>,
bind: &SpotSliceBinding,
cam_pos: [f32; 3],
skinned_joint_bufs: &[Retained<ProtocolObject<dyn MTLBuffer>>],
) -> u32 {
let mut draw_calls: u32 = 0;
let (Some(skinned_shadow_ps), Some(svb), Some(sib)) = (
&self.skinned.shadow_pipeline_state,
&self.skinned.vertex_buffer,
&self.skinned.index_buffer,
) else {
return draw_calls;
};
if self.skinned.draw_objects.is_empty() {
return draw_calls;
}
enc.pushDebugGroup(&objc2_foundation::NSString::from_str("spot shadow skinned"));
self.bind_spot_slice(
enc,
&SpotSliceBinding {
pipeline: skinned_shadow_ps,
uniforms: bind.uniforms,
},
);
enc.set_vertex_buffer(svb, 0, 1);
for (i, obj) in self.skinned.draw_objects.iter().enumerate() {
if !obj.visible {
continue;
}
let model_uniforms = ModelUniforms { model: obj.model };
let d = crate::gfx::lod::skinned_camera_distance(obj, cam_pos);
let (index_offset, index_count) = obj.active_lod(d);
let index_byte_offset = index_offset * std::mem::size_of::<u32>();
enc.set_vertex_value(&model_uniforms, 2);
enc.set_vertex_buffer(&skinned_joint_bufs[i], 0, 8);
unsafe {
enc.drawIndexedPrimitives_indexCount_indexType_indexBuffer_indexBufferOffset(
MTLPrimitiveType::Triangle,
index_count,
MTLIndexType::UInt32,
sib,
index_byte_offset,
);
}
draw_calls += 1;
}
enc.popDebugGroup();
draw_calls
}
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[SPOT_SLICE_IDX as usize] = 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)
}
}
}