#![deny(unsafe_op_in_unsafe_fn)]
use concinnity_core::gfx::render_types;
use concinnity_core::render::error::{RenderError, RenderResult};
use concinnity_core::render::planar_reflection::PixelRect;
use concinnity_core::render::post::reflection_path::ReflectionPath;
use concinnity_core::render::uniforms::ViewUniforms;
use objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2_foundation::ns_string;
use objc2_metal::{
MTLBuffer, MTLClearColor, MTLCommandBuffer as _, MTLCommandEncoder as _, MTLLoadAction,
MTLRenderCommandEncoder as _, MTLRenderPassDescriptor, MTLScissorRect, MTLStoreAction,
};
use crate::metal::context::{BINDLESS_TEXTURE_ARG_BUFFER_INDEX, MtlContext};
use crate::metal::depth::CLEAR_DEPTH;
use crate::metal::encode::RenderEncode;
use crate::metal::pass_timing::PassTimer;
use crate::metal::scoped_encoder::ScopedEncoder;
#[derive(Clone, Copy)]
pub(in crate::metal) struct MainPassCamera {
pub elapsed: f32,
pub vp: [[f32; 4]; 4],
pub view: [[f32; 4]; 4],
pub cam_pos: [f32; 3],
}
#[derive(Clone, Copy)]
pub(in crate::metal) struct GpuFrameBuffers<'a> {
pub object_buffer: Option<&'a Retained<ProtocolObject<dyn MTLBuffer>>>,
pub material_params: Option<&'a Retained<ProtocolObject<dyn MTLBuffer>>>,
pub bindless_tex_args: Option<&'a Retained<ProtocolObject<dyn MTLBuffer>>>,
pub deformed_skinned: Option<&'a Retained<ProtocolObject<dyn MTLBuffer>>>,
pub counts: crate::metal::context::DrawRecordCounts,
}
#[derive(Clone, Copy)]
pub(in crate::metal) struct FaceTargets<'a> {
pub color_msaa: Option<&'a ProtocolObject<dyn objc2_metal::MTLTexture>>,
pub depth: &'a ProtocolObject<dyn objc2_metal::MTLTexture>,
pub resolve: &'a ProtocolObject<dyn objc2_metal::MTLTexture>,
pub resolve_slice: usize,
}
#[derive(Clone, Copy)]
pub(in crate::metal) struct ClusterGrid<'a> {
pub params: &'a render_types::ClusterParams,
pub lists: &'a ProtocolObject<dyn MTLBuffer>,
}
#[derive(Clone, Copy)]
pub(in crate::metal) struct FacePass<'a> {
pub icb_override: Option<&'a ProtocolObject<dyn objc2_metal::MTLIndirectCommandBuffer>>,
pub scissor: Option<PixelRect>,
pub clusters: Option<ClusterGrid<'a>>,
pub timer: PassTimer,
}
impl FacePass<'_> {
pub(in crate::metal) const PROBE: FacePass<'static> = FacePass {
icb_override: None,
scissor: None,
clusters: None,
timer: PassTimer::None,
};
}
impl MtlContext {
fn reflection_resolve_active(&self) -> f32 {
if self.reflection_path().resolves() {
1.0
} else {
0.0
}
}
pub(in crate::metal) fn reflection_path(&self) -> ReflectionPath {
ReflectionPath::new(
self.ssr.settings.is_some(),
self.rt.settings.is_some(),
self.rt.accel.is_some(),
)
}
fn shade_mode(&self) -> f32 {
if self.state.view.mode == concinnity_core::gfx::view_modes::ViewMode::Unlit {
1.0
} else {
0.0
}
}
pub(in crate::metal) fn encode_main_pass(
&self,
cmd_buf: &ProtocolObject<dyn objc2_metal::MTLCommandBuffer>,
camera: MainPassCamera,
gpu: GpuFrameBuffers,
world_hidden: bool,
) -> RenderResult<u32> {
let object_buffer = gpu.object_buffer;
let store_msaa_color = self.cull.two_pass_occlusion
&& object_buffer.is_some()
&& self.cull.pipeline_phase2.is_some();
let main_pass_desc = MTLRenderPassDescriptor::new();
let [r, g, b, a] = self.state.view.clear_color;
unsafe {
let ca = main_pass_desc
.colorAttachments()
.objectAtIndexedSubscript(0);
ca.setTexture(Some(self.targets.hdr.color_attachment()));
ca.setLoadAction(MTLLoadAction::Clear);
if self.targets.hdr.multisampled() {
ca.setResolveTexture(Some(self.targets.hdr.hdr_resolve.as_ref()));
ca.setStoreAction(if store_msaa_color {
MTLStoreAction::StoreAndMultisampleResolve
} else {
MTLStoreAction::MultisampleResolve
});
} else {
ca.setStoreAction(MTLStoreAction::Store);
}
ca.setClearColor(MTLClearColor {
red: r as f64,
green: g as f64,
blue: b as f64,
alpha: a as f64,
});
let da = main_pass_desc.depthAttachment();
da.setTexture(Some(self.targets.hdr.depth_attachment()));
da.setLoadAction(MTLLoadAction::Clear);
da.setClearDepth(CLEAR_DEPTH);
if self.targets.hdr.multisampled() {
da.setResolveTexture(Some(self.targets.hdr.depth_resolve.as_ref()));
da.setDepthResolveFilter(objc2_metal::MTLMultisampleDepthResolveFilter::Sample0);
da.setStoreAction(MTLStoreAction::StoreAndMultisampleResolve);
} else {
da.setStoreAction(MTLStoreAction::Store);
}
}
if let Some(t) = &self.diagnostics.pass_timing {
t.attach_render(&main_pass_desc, super::super::pass_timing::PassId::Main);
}
let encoder = ScopedEncoder::new(
cmd_buf
.renderCommandEncoderWithDescriptor(&main_pass_desc)
.ok_or_else(|| RenderError::Other("failed to get render encoder".to_string()))?,
ns_string!("main pass"),
);
if self.state.view.mode == concinnity_core::gfx::view_modes::ViewMode::Wireframe {
encoder.setTriangleFillMode(objc2_metal::MTLTriangleFillMode::Lines);
}
let view_uniforms = self.main_view_uniforms(&camera);
if world_hidden || self.cull.main_pipeline.is_none() {
return Ok(0);
}
self.bind_clusters(&encoder, Some(self.main_cluster_grid()));
let draw_calls = self.encode_main_geometry_into(
&encoder,
&view_uniforms,
gpu,
None,
);
if self.draws_sky(self.state.view.mode) {
self.encode_sky(&encoder, &view_uniforms);
}
Ok(draw_calls)
}
pub(in crate::metal) fn encode_main_into_face(
&self,
cmd_buf: &ProtocolObject<dyn objc2_metal::MTLCommandBuffer>,
face_targets: FaceTargets,
camera: MainPassCamera,
gpu: GpuFrameBuffers,
pass: FacePass<'_>,
) -> RenderResult<u32> {
let FaceTargets {
color_msaa: face_color_msaa,
depth: face_depth,
resolve: face_resolve,
resolve_slice,
} = face_targets;
let MainPassCamera {
elapsed,
vp,
view,
cam_pos,
} = camera;
let desc = MTLRenderPassDescriptor::new();
let [r, g, b, a] = self.state.view.clear_color;
unsafe {
let ca = desc.colorAttachments().objectAtIndexedSubscript(0);
match face_color_msaa {
Some(msaa) => {
ca.setTexture(Some(msaa));
ca.setResolveTexture(Some(face_resolve));
ca.setResolveSlice(resolve_slice);
ca.setStoreAction(MTLStoreAction::MultisampleResolve);
}
None => {
ca.setTexture(Some(face_resolve));
ca.setSlice(resolve_slice);
ca.setStoreAction(MTLStoreAction::Store);
}
}
ca.setLoadAction(MTLLoadAction::Clear);
ca.setClearColor(MTLClearColor {
red: r as f64,
green: g as f64,
blue: b as f64,
alpha: a as f64,
});
let da = desc.depthAttachment();
da.setTexture(Some(face_depth));
da.setLoadAction(MTLLoadAction::Clear);
da.setClearDepth(CLEAR_DEPTH);
da.setStoreAction(MTLStoreAction::DontCare);
}
if let Some(t) = &self.diagnostics.pass_timing {
t.attach_render_timer(&desc, pass.timer);
}
let encoder = ScopedEncoder::new(
cmd_buf
.renderCommandEncoderWithDescriptor(&desc)
.ok_or_else(|| {
RenderError::Other("failed to get probe render encoder".to_string())
})?,
ns_string!("probe face"),
);
if let Some(r) = pass.scissor {
encoder.setScissorRect(MTLScissorRect {
x: r.x as usize,
y: r.y as usize,
width: r.width as usize,
height: r.height as usize,
});
}
let view_uniforms = ViewUniforms {
vp,
view,
elapsed,
reflections_enabled: 0.0,
cam_pos,
prefilter_mip_count: self.scene.env_map.prefilter_mip_count as f32,
shade_mode: 0.0,
ambient_occlusion: 0.0,
sky_rot: self.state.view.sky_rot,
};
self.bind_clusters(&encoder, pass.clusters);
let draw_calls =
self.encode_main_geometry_into(&encoder, &view_uniforms, gpu, pass.icb_override);
if self.draws_sky(concinnity_core::gfx::view_modes::ViewMode::Lit) {
self.encode_sky(&encoder, &view_uniforms);
}
Ok(draw_calls)
}
pub(in crate::metal) fn encode_main_pass_phase2(
&self,
cmd_buf: &ProtocolObject<dyn objc2_metal::MTLCommandBuffer>,
camera: MainPassCamera,
gpu: GpuFrameBuffers,
) -> RenderResult<u32> {
let GpuFrameBuffers {
object_buffer,
bindless_tex_args,
..
} = gpu;
let (Some(obj_buf), Some(tex_args), false) = (
object_buffer,
bindless_tex_args,
self.cull.icbs_2.is_empty(),
) else {
return Ok(0);
};
let main_pass_desc = MTLRenderPassDescriptor::new();
unsafe {
let ca = main_pass_desc
.colorAttachments()
.objectAtIndexedSubscript(0);
ca.setTexture(Some(self.targets.hdr.color_attachment()));
ca.setLoadAction(MTLLoadAction::Load);
let da = main_pass_desc.depthAttachment();
da.setTexture(Some(self.targets.hdr.depth_attachment()));
da.setLoadAction(MTLLoadAction::Load);
if self.targets.hdr.multisampled() {
ca.setResolveTexture(Some(self.targets.hdr.hdr_resolve.as_ref()));
ca.setStoreAction(MTLStoreAction::StoreAndMultisampleResolve);
da.setResolveTexture(Some(self.targets.hdr.depth_resolve.as_ref()));
da.setDepthResolveFilter(objc2_metal::MTLMultisampleDepthResolveFilter::Sample0);
da.setStoreAction(MTLStoreAction::StoreAndMultisampleResolve);
} else {
ca.setStoreAction(MTLStoreAction::Store);
da.setStoreAction(MTLStoreAction::Store);
}
}
if let Some(t) = &self.diagnostics.pass_timing {
t.attach_render(&main_pass_desc, super::super::pass_timing::PassId::Main2);
}
let encoder = ScopedEncoder::new(
cmd_buf
.renderCommandEncoderWithDescriptor(&main_pass_desc)
.ok_or_else(|| RenderError::Other("failed to get render encoder".to_string()))?,
ns_string!("main2 pass"),
);
if self.state.view.mode == concinnity_core::gfx::view_modes::ViewMode::Wireframe {
encoder.setTriangleFillMode(objc2_metal::MTLTriangleFillMode::Lines);
}
let view_uniforms = self.main_view_uniforms(&camera);
self.bind_main_pass_shared(&encoder, &view_uniforms);
self.bind_clusters(&encoder, Some(self.main_cluster_grid()));
let draw_calls =
self.execute_bindless_static_icb(&encoder, obj_buf, tex_args, &self.cull.icbs_2, gpu);
Ok(draw_calls)
}
fn execute_bindless_static_icb(
&self,
enc: &ProtocolObject<dyn objc2_metal::MTLRenderCommandEncoder>,
obj_buf: &Retained<ProtocolObject<dyn MTLBuffer>>,
tex_args: &Retained<ProtocolObject<dyn MTLBuffer>>,
icbs: &[Retained<ProtocolObject<dyn objc2_metal::MTLIndirectCommandBuffer>>],
gpu: GpuFrameBuffers,
) -> u32 {
use objc2_metal::{MTLRenderStages, MTLResourceUsage};
let GpuFrameBuffers {
material_params,
deformed_skinned,
counts,
..
} = gpu;
enc.set_vertex_buffer(obj_buf, 0, 9);
enc.set_fragment_buffer(obj_buf, 0, 9);
if let Some(params) = material_params {
let slot = crate::metal::material_params::MATERIAL_PARAMS_BUFFER_INDEX;
enc.set_vertex_buffer(params, 0, slot);
enc.set_fragment_buffer(params, 0, slot);
}
enc.set_fragment_buffer(tex_args, 0, BINDLESS_TEXTURE_ARG_BUFFER_INDEX);
if let Some(sampler_args) = &self.arg_buffers.bindless_sampler_args {
enc.set_fragment_buffer(
sampler_args,
0,
crate::metal::context::BINDLESS_SAMPLER_ARG_BUFFER_INDEX,
);
}
self.use_bindless_textures(enc);
let mut draw_calls = 0u32;
if let Some(prefix) = counts.prefix(0) {
enc.useResource_usage_stages(
ProtocolObject::from_ref(&*self.scene.index_buffer),
MTLResourceUsage::Read,
MTLRenderStages::Vertex,
);
let range = crate::metal::context::ns_range(prefix);
for (b, icb) in icbs.iter().enumerate() {
if b > 0 {
let Some(pipelines) = self.cull.world_pipelines.get(b) else {
continue;
};
enc.set_pipeline(&pipelines.main);
}
unsafe {
enc.executeCommandsInBuffer_withRange(icb, range);
}
draw_calls += 1;
}
if icbs.len() > 1
&& let Some(pipelines) = &self.cull.main_pipeline
{
enc.set_pipeline(&pipelines.main);
}
}
if let (Some(deformed), Some(icb0), Some(tail)) =
(deformed_skinned, icbs.first(), counts.skinned_tail(0))
{
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(icb0, crate::metal::context::ns_range(tail));
}
draw_calls += 1;
}
draw_calls
}
pub(in crate::metal) fn main_view_uniforms(&self, camera: &MainPassCamera) -> ViewUniforms {
ViewUniforms {
vp: camera.vp,
view: self.state.view.matrix,
elapsed: camera.elapsed,
reflections_enabled: self.reflection_resolve_active(),
cam_pos: camera.cam_pos,
prefilter_mip_count: self.scene.env_map.prefilter_mip_count as f32,
shade_mode: self.shade_mode(),
ambient_occlusion: 1.0,
sky_rot: self.state.view.sky_rot,
}
}
pub(in crate::metal) fn main_cluster_grid(&self) -> ClusterGrid<'_> {
ClusterGrid {
params: &self.cluster_params,
lists: &self.light_cull.cluster_buffer,
}
}
fn bind_clusters(
&self,
enc: &ProtocolObject<dyn objc2_metal::MTLRenderCommandEncoder>,
grid: Option<ClusterGrid<'_>>,
) {
let (params, lists) = match grid {
Some(grid) => (*grid.params, grid.lists),
None => (
render_types::ClusterParams {
use_clusters: 0,
..self.cluster_params
},
&*self.light_cull.cluster_buffer,
),
};
enc.set_fragment_value(¶ms, 11);
enc.set_fragment_buffer(lists, 0, 12);
}
fn bind_main_pass_shared(
&self,
enc: &ProtocolObject<dyn objc2_metal::MTLRenderCommandEncoder>,
view_uniforms: &ViewUniforms,
) -> bool {
let Some(pipelines) = &self.cull.main_pipeline else {
return false;
};
enc.set_pipeline(&pipelines.main);
enc.set_depth_stencil(&self.targets.depth_state);
enc.set_vertex_value(view_uniforms, 0);
enc.set_fragment_value(view_uniforms, 0);
enc.set_vertex_buffer(&self.scene.vertex_buffer, 0, 1);
enc.set_fragment_value(&self.light_uniforms, 4);
enc.set_fragment_buffer(&self.scene.local_light_buffer, 0, 8);
enc.set_fragment_buffer(&self.spot_shadow.buffer, 0, 13);
enc.set_fragment_buffer(&self.scene.area_light_buffer, 0, 14);
enc.set_fragment_value(&self.shadow.uniforms, 5);
self.probe_bindings().bind(
enc,
crate::metal::probe_set::ProbeSlots {
set: 6,
records: 15,
cubes: None,
cluster: None,
},
);
true
}
fn encode_main_geometry_into(
&self,
enc: &ProtocolObject<dyn objc2_metal::MTLRenderCommandEncoder>,
view_uniforms: &ViewUniforms,
gpu: GpuFrameBuffers,
icb_override: Option<&ProtocolObject<dyn objc2_metal::MTLIndirectCommandBuffer>>,
) -> u32 {
let GpuFrameBuffers {
object_buffer,
bindless_tex_args,
..
} = gpu;
enc.pushDebugGroup(&objc2_foundation::NSString::from_str("main geometry"));
if !self.bind_main_pass_shared(enc, view_uniforms) {
enc.popDebugGroup();
return 0;
}
let override_holder;
let bucket_icbs: &[Retained<_>] = match icb_override {
Some(icb) => {
override_holder = [objc2::Message::retain(icb)];
&override_holder
}
None => &self.cull.icbs,
};
let mut draw_calls: u32 = 0;
if let (Some(obj_buf), Some(tex_args), false) =
(object_buffer, bindless_tex_args, bucket_icbs.is_empty())
{
draw_calls +=
self.execute_bindless_static_icb(enc, obj_buf, tex_args, bucket_icbs, gpu);
}
enc.popDebugGroup();
draw_calls
}
}