#![deny(unsafe_op_in_unsafe_fn)]
use super::error::allocation_failed;
use concinnity_core::gfx::frustum::Frustum;
use concinnity_core::render::error::RenderResult;
use concinnity_core::render::planar_reflection::{self, PlanarReflectors};
use concinnity_core::transform::mat4_inverse;
use concinnity_core::transform::mat4_mul;
use objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2_metal::{
MTLBuffer, MTLDevice, MTLPixelFormat, MTLTexture, MTLTextureType, MTLTextureUsage,
};
use super::context::MtlContext;
use super::cull::MirrorCull;
use super::descriptors::TextureDesc;
use super::draw::main::{ClusterGrid, FacePass, FaceTargets, GpuFrameBuffers, MainPassCamera};
use super::light_cull::build_cluster_light_buffer;
use super::pass_timing::{PassId, PassTimer};
const PLANAR_CLIP_BIAS: f32 = 0.02;
const PLANAR_CROP_MARGIN: u32 = 2;
pub(in crate::metal) const MAX_PLANAR_PLANES: usize = planar_reflection::MAX_PLANAR_PLANES;
pub(in crate::metal) struct PlanarReflectionTargets {
pub(in crate::metal) msaa_color: Option<Retained<ProtocolObject<dyn MTLTexture>>>,
pub(in crate::metal) depth: Retained<ProtocolObject<dyn MTLTexture>>,
pub(in crate::metal) resolve: Retained<ProtocolObject<dyn MTLTexture>>,
}
pub(in crate::metal) struct PlanarReflectionSet {
pub(in crate::metal) targets: Vec<PlanarReflectionTargets>,
clusters: Vec<Retained<ProtocolObject<dyn MTLBuffer>>>,
pub(in crate::metal) layout: PlanarReflectors,
width: u32,
height: u32,
sample_count: u32,
}
fn create_planar_targets(
device: &ProtocolObject<dyn MTLDevice>,
width: u32,
height: u32,
sample_count: u32,
) -> RenderResult<PlanarReflectionTargets> {
let multisampled = sample_count > 1;
let color = if multisampled {
let desc = TextureDesc {
kind: MTLTextureType::Type2DMultisample,
format: MTLPixelFormat::RGBA16Float,
width: width as usize,
height: height as usize,
sample_count: sample_count as usize,
usage: MTLTextureUsage::RenderTarget,
..Default::default()
}
.build();
Some(
device
.newTextureWithDescriptor(&desc)
.ok_or_else(|| allocation_failed("planar MSAA color target"))?,
)
} else {
None
};
let depth = {
let desc = TextureDesc {
kind: if multisampled {
MTLTextureType::Type2DMultisample
} else {
MTLTextureType::Type2D
},
format: MTLPixelFormat::Depth32Float,
width: width as usize,
height: height as usize,
sample_count: sample_count as usize,
usage: MTLTextureUsage::RenderTarget,
..Default::default()
}
.build();
device
.newTextureWithDescriptor(&desc)
.ok_or_else(|| allocation_failed("planar depth target"))?
};
let resolve = {
let desc = TextureDesc {
format: MTLPixelFormat::RGBA16Float,
width: width as usize,
height: height as usize,
usage: MTLTextureUsage(MTLTextureUsage::ShaderRead.0 | MTLTextureUsage::RenderTarget.0),
..Default::default()
}
.build();
device
.newTextureWithDescriptor(&desc)
.ok_or_else(|| allocation_failed("planar resolve target"))?
};
Ok(PlanarReflectionTargets {
msaa_color: color,
depth,
resolve,
})
}
impl PlanarReflectionSet {
pub(in crate::metal) fn new(
device: &ProtocolObject<dyn MTLDevice>,
layout: PlanarReflectors,
(render_w, render_h): (u32, u32),
sample_count: u32,
) -> RenderResult<Self> {
let (width, height) = layout.target_size(render_w, render_h);
let targets = create_targets(device, layout.planes().len(), (width, height), sample_count)?;
let clusters = (0..layout.planes().len())
.map(|_| build_cluster_light_buffer(device))
.collect::<RenderResult<_>>()?;
Ok(Self {
targets,
clusters,
layout,
width,
height,
sample_count,
})
}
pub(in crate::metal) fn resize(
&mut self,
device: &ProtocolObject<dyn MTLDevice>,
(render_w, render_h): (u32, u32),
) -> RenderResult<()> {
let (width, height) = self.layout.target_size(render_w, render_h);
self.targets = create_targets(
device,
self.layout.planes().len(),
(width, height),
self.sample_count,
)?;
self.width = width;
self.height = height;
Ok(())
}
}
fn create_targets(
device: &ProtocolObject<dyn MTLDevice>,
count: usize,
(width, height): (u32, u32),
sample_count: u32,
) -> RenderResult<Vec<PlanarReflectionTargets>> {
(0..count)
.map(|_| create_planar_targets(device, width, height, sample_count))
.collect()
}
impl MtlContext {
pub(in crate::metal) fn planar_mirrors_needed(&self) -> bool {
planar_reflection::planar_pass_needed(
self.planar_reflection
.as_ref()
.is_some_and(|s| !s.targets.is_empty()),
self.water_planar_slot_live(),
self.rt_transparent_active(),
)
}
pub(in crate::metal) fn encode_planar_reflections(
&self,
cmd_buf: &ProtocolObject<dyn objc2_metal::MTLCommandBuffer>,
params: &super::graph_exec::GraphFrameParams,
) -> RenderResult<()> {
let Some(set) = self.planar_reflection.as_ref() else {
return Ok(());
};
let crops =
params
.planar
.crops(set.targets.len(), set.width, set.height, PLANAR_CROP_MARGIN);
let crops = crops.as_slice();
let proj = mat4_mul(params.vp, mat4_inverse(self.state.view.matrix));
for (i, &(slot, crop)) in crops.iter().enumerate() {
let plane = set.layout.planes()[slot];
let oriented = planar_reflection::orient_plane_toward(plane, params.cam_pos);
let m = planar_reflection::planar_matrices(
self.state.view.matrix,
proj,
params.cam_pos,
oriented,
PLANAR_CLIP_BIAS,
);
let mirror_frustum = Frustum::from_camera(
crop.crop_view_projection(m.view_proj, set.width, set.height),
self.state.view.view_distance,
);
let icb_override = match (params.object_buffer, params.draw_args_buffer) {
(Some(object_buffer), Some(draw_args_buffer)) => {
self.encode_mirror_cull(
cmd_buf,
MirrorCull {
object_buffer,
draw_args_buffer,
frustum: &mirror_frustum,
eye: m.eye,
slot,
timer: PassTimer::None,
},
)?;
self.cull.mirror_slots.get(slot).map(|s| s.icb.as_ref())
}
_ => None,
};
let cluster_params = self.cluster_params.with_camera(&m.cluster_camera(
self.cluster_params.z_near,
self.scene.cluster_reach.range(
m.eye,
self.cluster_params.z_near,
self.probe.book.records(),
self.state.view.view_distance,
),
set.width,
set.height,
));
let clusters = (cluster_params.use_clusters != 0).then(|| ClusterGrid {
params: &cluster_params,
lists: &set.clusters[slot],
});
if let Some(grid) = clusters {
self.encode_light_cull(cmd_buf, grid, None)?;
}
let targets = &set.targets[slot];
self.encode_main_into_face(
cmd_buf,
FaceTargets {
color_msaa: targets.msaa_color.as_deref(),
depth: &targets.depth,
resolve: &targets.resolve,
resolve_slice: 0,
},
MainPassCamera {
elapsed: params.elapsed,
vp: m.view_proj,
view: m.view,
cam_pos: m.eye,
},
GpuFrameBuffers {
object_buffer: params.object_buffer,
material_params: params.material_params,
bindless_tex_args: params.bindless_tex_args,
deformed_skinned: params.deformed_skinned,
counts: self.draw_record_counts(),
},
FacePass {
icb_override,
scissor: Some(crop),
clusters,
timer: PassTimer::span(PassId::PlanarReflection, i, crops.len()),
},
)?;
}
Ok(())
}
}