#![deny(unsafe_op_in_unsafe_fn)]
use objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2_metal::{
MTLBlendFactor, MTLBuffer, MTLDevice, MTLPixelFormat, MTLRenderPipelineDescriptor,
MTLRenderPipelineState, MTLResourceOptions, MTLVertexFormat, MTLVertexStepFunction,
};
use crate::components::GlassPanel;
use crate::geometry::glass_quad::build_glass_quad;
use crate::gfx::mesh_payload::Vertex;
use super::context::MtlContext;
use super::descriptors::{VertexAttr, VertexLayout, vertex_descriptor};
use super::slang_shaders;
use super::transparent::{TransparentDraw, bytes_of};
use concinnity_render::uniforms::{GlassMeshParams, GlassParams, TransparentView};
const GLASS_MESH_REFRACTION: f32 = 0.02;
const GLASS_MESH_FRESNEL_POWER: f32 = 1.0;
pub(in crate::metal) struct GlassPanelRecord {
pub(in crate::metal) vertex_buffer: Retained<ProtocolObject<dyn MTLBuffer>>,
pub(in crate::metal) index_buffer: Retained<ProtocolObject<dyn MTLBuffer>>,
pub(in crate::metal) index_count: u32,
pub(in crate::metal) params: GlassParams,
pub(in crate::metal) visible: bool,
pub(in crate::metal) centre: [f32; 3],
pub(in crate::metal) planar_slot: Option<usize>,
}
fn glass_params_from(panel: &GlassPanel) -> GlassParams {
let n = panel.normal; GlassParams {
centre: [panel.centre[0], panel.centre[1], panel.centre[2], 0.0],
normal: [n[0], n[1], n[2], 0.0],
tint: [panel.tint[0], panel.tint[1], panel.tint[2], 0.0],
opacity: panel.opacity,
refraction_strength: panel.refraction_strength,
fresnel_power: panel.fresnel_power,
planar: 0.0,
}
}
pub(in crate::metal) fn build_glass_panel_record(
device: &ProtocolObject<dyn MTLDevice>,
panel: &GlassPanel,
) -> Result<GlassPanelRecord, String> {
let (verts, idxs) = build_glass_quad(panel.centre, panel.normal, panel.half_size);
let mut packed: Vec<Vertex> = Vec::with_capacity(verts.len());
for (pos, normal, color, uv) in verts {
packed.push(Vertex {
pos,
normal,
tangent: [1.0, 0.0, 0.0],
color,
uv,
});
}
let vb_bytes = packed.len() * std::mem::size_of::<Vertex>();
let ib_bytes = idxs.len() * std::mem::size_of::<u16>();
let vb = unsafe {
let ptr = std::ptr::NonNull::new(packed.as_ptr() as *mut _)
.ok_or("glass vertex buffer: source pointer is null")?;
device
.newBufferWithBytes_length_options(ptr, vb_bytes, MTLResourceOptions::StorageModeShared)
.ok_or("failed to allocate glass vertex buffer")?
};
let ib = unsafe {
let ptr = std::ptr::NonNull::new(idxs.as_ptr() as *mut _)
.ok_or("glass index buffer: source pointer is null")?;
device
.newBufferWithBytes_length_options(ptr, ib_bytes, MTLResourceOptions::StorageModeShared)
.ok_or("failed to allocate glass index buffer")?
};
Ok(GlassPanelRecord {
vertex_buffer: vb,
index_buffer: ib,
index_count: idxs.len() as u32,
params: glass_params_from(panel),
visible: panel.visible,
centre: panel.centre,
planar_slot: None,
})
}
pub(super) fn build_glass_pipeline(
device: &ProtocolObject<dyn MTLDevice>,
hot_reload: bool,
) -> Result<Retained<ProtocolObject<dyn MTLRenderPipelineState>>, String> {
build_glass_pipeline_slang(device, hot_reload, &slang_shaders::GLASS_FRAG)
}
pub(super) fn build_glass_pipeline_rt(
device: &ProtocolObject<dyn MTLDevice>,
hot_reload: bool,
) -> Result<Retained<ProtocolObject<dyn MTLRenderPipelineState>>, String> {
build_glass_pipeline_slang(device, hot_reload, &slang_shaders::GLASS_FRAG_RT)
}
pub(super) fn build_glass_mesh_pipeline_rt(
device: &ProtocolObject<dyn MTLDevice>,
hot_reload: bool,
) -> Result<Retained<ProtocolObject<dyn MTLRenderPipelineState>>, String> {
build_glass_mesh_pipeline_slang(device, hot_reload, &slang_shaders::GLASS_MESH_FRAG_RT)
}
pub(super) fn build_glass_mesh_pipeline_rt_textured(
device: &ProtocolObject<dyn MTLDevice>,
hot_reload: bool,
) -> Result<Retained<ProtocolObject<dyn MTLRenderPipelineState>>, String> {
build_glass_mesh_pipeline_slang(
device,
hot_reload,
&slang_shaders::GLASS_MESH_FRAG_RT_TEXTURED,
)
}
pub(super) fn build_glass_pipeline_rt_textured(
device: &ProtocolObject<dyn MTLDevice>,
hot_reload: bool,
) -> Result<Retained<ProtocolObject<dyn MTLRenderPipelineState>>, String> {
build_glass_pipeline_slang(device, hot_reload, &slang_shaders::GLASS_FRAG_RT_TEXTURED)
}
fn build_glass_pipeline_slang(
device: &ProtocolObject<dyn MTLDevice>,
hot_reload: bool,
fragment: &slang_shaders::SlangLib,
) -> Result<Retained<ProtocolObject<dyn MTLRenderPipelineState>>, String> {
let vert_fn = slang_shaders::entry_function(device, &slang_shaders::GLASS_VERT, hot_reload)?;
let frag_fn = slang_shaders::entry_function(device, fragment, hot_reload)?;
build_transparent_pipeline_stages(device, &vert_fn, &frag_fn)
}
fn build_glass_mesh_pipeline_slang(
device: &ProtocolObject<dyn MTLDevice>,
hot_reload: bool,
fragment: &slang_shaders::SlangLib,
) -> Result<Retained<ProtocolObject<dyn MTLRenderPipelineState>>, String> {
let vert_fn =
slang_shaders::entry_function(device, &slang_shaders::GLASS_MESH_VERT, hot_reload)?;
let frag_fn = slang_shaders::entry_function(device, fragment, hot_reload)?;
build_transparent_pipeline_stages(device, &vert_fn, &frag_fn)
}
pub(in crate::metal) fn build_transparent_pipeline_stages(
device: &ProtocolObject<dyn MTLDevice>,
vert_fn: &ProtocolObject<dyn objc2_metal::MTLFunction>,
frag_fn: &ProtocolObject<dyn objc2_metal::MTLFunction>,
) -> Result<Retained<ProtocolObject<dyn MTLRenderPipelineState>>, String> {
let vert_desc = vertex_descriptor(
&[
VertexAttr {
index: 0,
format: MTLVertexFormat::Float3,
offset: 0,
buffer_index: 1,
},
VertexAttr {
index: 1,
format: MTLVertexFormat::Float3,
offset: 12,
buffer_index: 1,
},
VertexAttr {
index: 2,
format: MTLVertexFormat::Float3,
offset: 24,
buffer_index: 1,
},
VertexAttr {
index: 3,
format: MTLVertexFormat::Float3,
offset: 36,
buffer_index: 1,
},
VertexAttr {
index: 4,
format: MTLVertexFormat::Float2,
offset: 48,
buffer_index: 1,
},
],
&[VertexLayout {
buffer_index: 1,
stride: std::mem::size_of::<Vertex>(),
step: MTLVertexStepFunction::PerVertex,
}],
);
let desc = MTLRenderPipelineDescriptor::new();
desc.setVertexDescriptor(Some(&vert_desc));
desc.setVertexFunction(Some(vert_fn));
desc.setFragmentFunction(Some(frag_fn));
desc.setRasterSampleCount(1);
unsafe {
let ca = desc.colorAttachments().objectAtIndexedSubscript(0);
ca.setPixelFormat(MTLPixelFormat::RGBA16Float);
ca.setBlendingEnabled(true);
ca.setSourceRGBBlendFactor(MTLBlendFactor::SourceAlpha);
ca.setDestinationRGBBlendFactor(MTLBlendFactor::OneMinusSourceAlpha);
ca.setSourceAlphaBlendFactor(MTLBlendFactor::SourceAlpha);
ca.setDestinationAlphaBlendFactor(MTLBlendFactor::OneMinusSourceAlpha);
}
device
.newRenderPipelineStateWithDescriptor_error(&desc)
.map_err(|e| format!("failed to create transparent pipeline state: {:?}", e))
}
impl MtlContext {
pub(in crate::metal) fn collect_glass_transparent_draws(
&self,
view: &TransparentView,
bindless: bool,
planar_live: bool,
out: &mut Vec<TransparentDraw>,
) {
let rt_on = self.rt.accel.is_some();
let pipeline = match (
rt_on && bindless,
&self.glass.pipeline_rt_textured,
rt_on,
&self.glass.pipeline_rt,
) {
(true, Some(p), _, _) => p,
(_, _, true, Some(p)) => p,
_ => match &self.glass.pipeline {
Some(p) => p,
None => return,
},
};
let cam = view.camera_pos;
let planar_set = self.planar_reflection.as_ref();
for panel in &self.glass.panels {
if !panel.visible {
continue;
}
let mut params = panel.params;
let mut fragment_textures = vec![
(0, self.hdr_targets.transparent_scene_copy.clone()),
(1, self.hdr_targets.depth_resolve.clone()),
];
if planar_live
&& let Some(targets) = panel
.planar_slot
.and_then(|s| planar_set.and_then(|set| set.targets.get(s)))
{
params.planar = 1.0;
fragment_textures.push((11, targets.resolve.clone()));
}
let c = panel.centre;
let sort_distance =
((c[0] - cam[0]).powi(2) + (c[1] - cam[1]).powi(2) + (c[2] - cam[2]).powi(2))
.sqrt();
out.push(TransparentDraw {
pipeline: pipeline.clone(),
vertex_buffer: panel.vertex_buffer.clone(),
index_buffer: panel.index_buffer.clone(),
index_count: panel.index_count,
index_type: objc2_metal::MTLIndexType::UInt16,
index_offset_bytes: 0,
base_vertex: 0,
params: bytes_of(¶ms),
fragment_textures,
fragment_samplers: vec![(0, self.post_sampler.clone())],
sort_distance,
});
}
}
pub(in crate::metal) fn seethrough_meshes_enabled(&self) -> bool {
!self.glass.seethrough_mesh_indices.is_empty() && self.glass.mesh_pipeline_rt.is_some()
}
pub(in crate::metal) fn mesh_glass_active(&self) -> bool {
self.seethrough_meshes_enabled() && self.rt.accel.is_some()
}
pub(in crate::metal) fn mesh_glass_visible(&self) -> bool {
self.mesh_glass_active()
&& self.glass.seethrough_mesh_indices.iter().any(|&i| {
self.draw
.objects
.get(i)
.is_some_and(|o| o.visible && o.resident)
})
}
pub(in crate::metal) fn collect_mesh_transparent_draws(
&self,
view: &TransparentView,
bindless: bool,
out: &mut Vec<TransparentDraw>,
) {
if !self.mesh_glass_active() {
return;
}
let pipeline = match (bindless, &self.glass.mesh_pipeline_rt_textured) {
(true, Some(p)) => p,
_ => match &self.glass.mesh_pipeline_rt {
Some(p) => p,
None => return,
},
};
let prefilter_mip_count = self.env_map.prefilter_mip_count as f32;
let cam = view.camera_pos;
for &idx in &self.glass.seethrough_mesh_indices {
let Some(obj) = self.draw.objects.get(idx) else {
continue;
};
if !obj.visible || !obj.resident {
continue;
}
let centre = [
0.5 * (obj.bb_min[0] + obj.bb_max[0]),
0.5 * (obj.bb_min[1] + obj.bb_max[1]),
0.5 * (obj.bb_min[2] + obj.bb_max[2]),
];
let d = ((centre[0] - cam[0]).powi(2)
+ (centre[1] - cam[1]).powi(2)
+ (centre[2] - cam[2]).powi(2))
.sqrt();
let (index_offset, index_count) = obj.active_lod(d);
let t = obj.material.tint;
let params = GlassMeshParams {
model: obj.model,
tint: [t[0], t[1], t[2], 0.0],
opacity: obj.material.opacity,
refraction_strength: GLASS_MESH_REFRACTION,
fresnel_power: GLASS_MESH_FRESNEL_POWER,
prefilter_mip_count,
};
out.push(TransparentDraw {
pipeline: pipeline.clone(),
vertex_buffer: self.vertex_buffer.retained(),
index_buffer: self.index_buffer.retained(),
index_count: index_count as u32,
index_type: objc2_metal::MTLIndexType::UInt32,
index_offset_bytes: index_offset * std::mem::size_of::<u32>(),
base_vertex: obj.base_vertex,
params: bytes_of(¶ms),
fragment_textures: vec![
(0, self.hdr_targets.transparent_scene_copy.clone()),
(1, self.hdr_targets.depth_resolve.clone()),
],
fragment_samplers: vec![(0, self.post_sampler.clone())],
sort_distance: d,
});
}
}
}