#![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;
use concinnity_core::transform::mat4_inverse;
use concinnity_core::transform::mat4_mul;
use objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2_metal::{MTLDevice, MTLPixelFormat, MTLTexture, MTLTextureType, MTLTextureUsage};
use super::context::MtlContext;
use super::descriptors::TextureDesc;
const PLANAR_CLIP_BIAS: f32 = 0.02;
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>,
pub(in crate::metal) planes: Vec<[f32; 4]>,
}
pub(in crate::metal) 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,
})
}
pub(in crate::metal) fn create_planar_set(
device: &ProtocolObject<dyn MTLDevice>,
width: u32,
height: u32,
sample_count: u32,
planes: &[[f32; 4]],
) -> RenderResult<PlanarReflectionSet> {
let mut targets = Vec::with_capacity(planes.len());
for _ in planes {
targets.push(create_planar_targets(device, width, height, sample_count)?);
}
Ok(PlanarReflectionSet {
targets,
planes: planes.to_vec(),
})
}
impl MtlContext {
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 proj = mat4_mul(params.vp, mat4_inverse(self.view.matrix));
for (slot, (plane, targets)) in set.planes.iter().zip(set.targets.iter()).enumerate() {
let oriented = planar_reflection::orient_plane_toward(*plane, params.cam_pos);
let m = planar_reflection::planar_matrices(
self.view.matrix,
proj,
params.cam_pos,
oriented,
PLANAR_CLIP_BIAS,
);
let mirror_frustum = Frustum::from_view_projection(m.view_proj);
let icb_override = match (params.object_buffer, params.draw_args_buffer) {
(Some(object_buffer), Some(draw_args)) => {
self.encode_mirror_cull(
cmd_buf,
object_buffer,
draw_args,
&mirror_frustum,
m.eye,
slot,
)?;
self.cull.mirror_slots.get(slot).map(|s| s.icb.as_ref())
}
_ => None,
};
self.encode_main_into_face(
cmd_buf,
crate::metal::draw::main::FaceTargets {
color_msaa: targets.msaa_color.as_deref(),
depth: &targets.depth,
resolve: &targets.resolve,
resolve_slice: 0,
},
crate::metal::draw::main::MainPassCamera {
elapsed: params.elapsed,
vp: m.view_proj,
view: m.view,
cam_pos: m.eye,
},
crate::metal::draw::main::GpuFrameBuffers {
object_buffer: params.object_buffer,
bindless_tex_args: params.bindless_tex_args,
deformed_skinned: params.deformed_skinned,
counts: self.draw_record_counts(),
},
icb_override,
)?;
}
Ok(())
}
}