#![deny(unsafe_op_in_unsafe_fn)]
use objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2_metal::{
MTLBuffer, MTLCommandBuffer as _, MTLCommandEncoder as _, MTLLoadAction,
MTLRenderCommandEncoder as _, MTLRenderPassDescriptor, MTLStoreAction,
};
use crate::gfx::render_types::{NUM_SHADOW_CASCADES, ShadowPassPush};
use crate::gfx::shadow_bias;
use crate::metal::context::MtlContext;
use crate::metal::encode::RenderEncode;
use crate::metal::scoped_encoder::ScopedEncoder;
impl MtlContext {
pub(in crate::metal) fn next_shadow_cascade_mask(&mut self) -> u32 {
self.shadow
.scheduler
.next_mask(self.shadow.update, self.shadow.cascades)
}
pub(in crate::metal) fn encode_shadow_pass(
&self,
cmd_buf: &ProtocolObject<dyn objc2_metal::MTLCommandBuffer>,
object_buffer: Option<&Retained<ProtocolObject<dyn MTLBuffer>>>,
deformed_skinned: Option<&Retained<ProtocolObject<dyn MTLBuffer>>>,
raymarch_view: Option<&crate::metal::raymarch::RaymarchView>,
) -> Result<u32, String> {
if self.shadow.pipeline_state.is_none() {
return Ok(0);
}
let mut total_draws: u32 = 0;
let all = (1u32 << NUM_SHADOW_CASCADES) - 1;
let mask = if self.shadow.render_mask == 0 {
all
} else {
self.shadow.render_mask
};
let rendered: Vec<usize> = (0..NUM_SHADOW_CASCADES)
.filter(|i| mask & (1u32 << i) != 0)
.collect();
let first_rendered = rendered.first().copied();
let last_rendered = rendered.last().copied();
for &cascade_idx in &rendered {
let shadow_pass_desc = MTLRenderPassDescriptor::new();
let depth_attach = shadow_pass_desc.depthAttachment();
depth_attach.setTexture(Some(self.shadow.map.as_ref()));
depth_attach.setSlice(cascade_idx);
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::Shadow;
let is_first = Some(cascade_idx) == first_rendered;
let is_last = Some(cascade_idx) == last_rendered;
if is_first && is_last {
t.attach_render(&shadow_pass_desc, id);
} else if is_first {
t.attach_render_first(&shadow_pass_desc, id);
} else if is_last {
t.attach_render_last(&shadow_pass_desc, id);
}
}
let shadow_enc = ScopedEncoder::new(
cmd_buf
.renderCommandEncoderWithDescriptor(&shadow_pass_desc)
.ok_or("failed to get shadow render encoder")?,
"shadow cascade",
);
if let Some(object_buffer) = object_buffer {
let push = ShadowPassPush {
cascade_idx: cascade_idx as u32,
_pad: [0; 3],
};
total_draws += self.encode_shadow_cascade_indirect(
&shadow_enc,
&push,
cascade_idx,
object_buffer,
deformed_skinned,
);
}
}
if let Some(view) = raymarch_view {
total_draws += self.encode_sdf_shadow_casters(cmd_buf, view)?;
}
Ok(total_draws)
}
fn encode_shadow_cascade_indirect(
&self,
enc: &ProtocolObject<dyn objc2_metal::MTLRenderCommandEncoder>,
push: &ShadowPassPush,
cascade_idx: usize,
object_buffer: &Retained<ProtocolObject<dyn MTLBuffer>>,
deformed_skinned: Option<&Retained<ProtocolObject<dyn MTLBuffer>>>,
) -> u32 {
use objc2_metal::{MTLRenderStages, MTLResourceUsage};
let (Some(pipeline), Some(icb)) = (
self.cull.shadow_bindless_pipeline.as_ref(),
self.cull.shadow_icb.as_ref(),
) else {
return 0;
};
enc.pushDebugGroup(&objc2_foundation::NSString::from_str(
"shadow cascade indirect",
));
enc.set_pipeline(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(&self.shadow.uniforms, 0);
enc.set_vertex_value(push, 7);
enc.set_vertex_buffer(object_buffer, 0, 9);
enc.set_vertex_buffer(&self.vertex_buffer, 0, 1);
let counts = self.draw_record_counts();
let cascade_off = cascade_idx * counts.total;
let mut draw_calls = 0u32;
if let Some(prefix) = counts.prefix(cascade_off) {
enc.useResource_usage_stages(
ProtocolObject::from_ref(&*self.index_buffer),
MTLResourceUsage::Read,
MTLRenderStages::Vertex,
);
unsafe {
enc.executeCommandsInBuffer_withRange(
icb.as_ref(),
crate::metal::context::ns_range(prefix),
);
}
draw_calls += 1;
}
if let (Some(deformed), Some(tail)) = (deformed_skinned, counts.skinned_tail(cascade_off)) {
enc.set_vertex_buffer(deformed, 0, 1);
if let Some(skinned_ib) = self.skinned.index_buffer.as_ref() {
enc.useResource_usage_stages(
ProtocolObject::from_ref(&**skinned_ib),
MTLResourceUsage::Read,
MTLRenderStages::Vertex,
);
}
unsafe {
enc.executeCommandsInBuffer_withRange(
icb.as_ref(),
crate::metal::context::ns_range(tail),
);
}
draw_calls += 1;
}
enc.popDebugGroup();
draw_calls
}
}