#![deny(unsafe_op_in_unsafe_fn)]
use concinnity_core::components;
use concinnity_core::gfx::render_types::{ShadowUniforms, SpotShadowData};
use concinnity_core::render::csm;
use concinnity_core::render::error::{RenderError, RenderResult};
use objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2_foundation::ns_string;
use objc2_metal::{
MTLBuffer, MTLCommandBuffer as _, MTLLoadAction, MTLRenderPassDescriptor, MTLStoreAction,
};
use super::shadow::ShadowView;
use crate::metal::context::MtlContext;
use crate::metal::depth::CLEAR_DEPTH;
use crate::metal::scoped_encoder::ScopedEncoder;
impl MtlContext {
pub(in crate::metal) fn next_spot_shadow_mask(&mut self) -> u32 {
let every_frame = matches!(
self.shadow.cadence.update,
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>,
object_buffer: Option<&Retained<ProtocolObject<dyn MTLBuffer>>>,
deformed_skinned: Option<&Retained<ProtocolObject<dyn MTLBuffer>>>,
) -> RenderResult<u32> {
if !self.shadow.enabled || self.spot_shadow.count == 0 {
return Ok(0);
}
let first_rendered = self.spot_shadow.refreshed_slices().next();
let last_rendered = self.spot_shadow.refreshed_slices().last();
let mut total_draws: u32 = 0;
for slice in self.spot_shadow.refreshed_slices() {
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(CLEAR_DEPTH);
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_else(|| {
RenderError::Other("failed to get spot shadow render encoder".to_string())
})?,
ns_string!("spot shadow slice"),
);
if let Some(object_buffer) = object_buffer {
let uniforms = self.spot_slice_uniforms(slice);
total_draws += self.draw_shadow_view(
&enc,
ShadowView {
uniforms: &uniforms,
vp_index: 0,
set: &self.cull.spot_views,
region: slice as usize,
},
object_buffer,
deformed_skinned,
);
}
}
Ok(total_draws)
}
fn spot_slice_uniforms(&self, slice: u32) -> ShadowUniforms {
let data = self.spot_shadow_data(slice);
let mut uniforms = 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)
}
}
}