#![deny(unsafe_op_in_unsafe_fn)]
use concinnity_core::gfx::render_types::{NUM_SHADOW_CASCADES, ShadowPassPush, ShadowUniforms};
use concinnity_core::render::error::{RenderError, RenderResult};
use concinnity_core::render::shadow_bias;
use objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2_foundation::ns_string;
use objc2_metal::{
MTLBuffer, MTLCommandBuffer as _, MTLCommandEncoder as _, MTLLoadAction,
MTLRenderCommandEncoder as _, MTLRenderPassDescriptor, MTLStoreAction,
};
use crate::metal::context::MtlContext;
use crate::metal::cull::ShadowViewIcb;
use crate::metal::depth::CLEAR_DEPTH;
use crate::metal::encode::RenderEncode;
use crate::metal::scoped_encoder::ScopedEncoder;
pub(in crate::metal) struct ShadowView<'a> {
pub uniforms: &'a ShadowUniforms,
pub vp_index: u32,
pub set: &'a ShadowViewIcb,
pub region: usize,
}
impl MtlContext {
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>,
) -> RenderResult<u32> {
if !self.shadow.enabled {
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(CLEAR_DEPTH);
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_else(|| {
RenderError::Other("failed to get shadow render encoder".to_string())
})?,
ns_string!("shadow cascade"),
);
if let Some(object_buffer) = object_buffer {
total_draws += self.draw_shadow_view(
&shadow_enc,
ShadowView {
uniforms: &self.shadow.uniforms,
vp_index: cascade_idx as u32,
set: &self.cull.shadow_views,
region: 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)
}
pub(in crate::metal) fn draw_shadow_view(
&self,
enc: &ProtocolObject<dyn objc2_metal::MTLRenderCommandEncoder>,
view: ShadowView<'_>,
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(),
view.set.icb.as_ref(),
) else {
return 0;
};
enc.pushDebugGroup(&objc2_foundation::NSString::from_str(
"shadow view indirect",
));
enc.set_pipeline(pipeline);
enc.set_depth_stencil(&self.targets.depth_state);
enc.setDepthBias_slopeScale_clamp(
shadow_bias::RASTER_CONSTANT,
shadow_bias::RASTER_SLOPE,
shadow_bias::RASTER_CLAMP,
);
enc.set_vertex_value(view.uniforms, 0);
enc.set_vertex_value(
&ShadowPassPush {
cascade_idx: view.vp_index,
_pad: [0; 3],
},
7,
);
enc.set_vertex_buffer(object_buffer, 0, 9);
enc.set_vertex_buffer(&self.scene.vertex_buffer, 0, 1);
let counts = self.draw_record_counts();
let view_off = view.region * counts.total;
let mut draw_calls = 0u32;
if let Some(prefix) = counts.prefix(view_off) {
enc.useResource_usage_stages(
ProtocolObject::from_ref(&*self.scene.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(view_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
}
}