#![deny(unsafe_op_in_unsafe_fn)]
use concinnity_core::gfx::render_types;
use concinnity_core::render::error::{RenderError, RenderResult};
use objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2_foundation::ns_string;
use objc2_metal::{
MTLCommandBuffer as _, MTLLoadAction, MTLPixelFormat, MTLPrimitiveType,
MTLRenderCommandEncoder as _, MTLRenderPassDescriptor, MTLRenderPipelineState, MTLStoreAction,
};
use crate::metal::builtin_shaders::ShaderProgram;
use crate::metal::context::MtlContext;
use crate::metal::encode::RenderEncode;
use crate::metal::post::fullscreen::{
FullscreenBlend, build_fullscreen_pipeline, set_fragment_sampler_range,
};
use crate::metal::scoped_encoder::ScopedEncoder;
const RT_POOL_SAMPLER_INDEX: usize = 5;
pub(crate) fn build_rt_reflection_pipeline(
device: &ProtocolObject<dyn objc2_metal::MTLDevice>,
fragment: &ShaderProgram,
hot_reload: bool,
) -> RenderResult<Retained<ProtocolObject<dyn MTLRenderPipelineState>>> {
build_fullscreen_pipeline(
device,
fragment,
MTLPixelFormat::RGBA16Float,
FullscreenBlend::Replace,
hot_reload,
)
}
impl MtlContext {
pub(in crate::metal) fn encode_rt_reflections(
&self,
cmd_buf: &ProtocolObject<dyn objc2_metal::MTLCommandBuffer>,
rt_params: &render_types::RtParams,
bindless_tex_args: Option<&Retained<ProtocolObject<dyn objc2_metal::MTLBuffer>>>,
) -> RenderResult<u32> {
let (reflection, accel, gb_normal_depth, gb_roughness) = match (
&self.ssr.reflection,
&self.rt.accel,
self.gbuffer_normal_depth(),
self.gbuffer_roughness(),
) {
(Some(t), Some(a), Some(n), Some(r)) => (t, a, n, r),
_ => return Ok(0),
};
let textured = bindless_tex_args.is_some() && self.rt.pipelines.resolve_textured.is_some();
let pipeline = match (
textured,
&self.rt.pipelines.resolve_textured,
&self.rt.pipelines.resolve,
) {
(true, Some(p), _) => p,
(_, _, Some(p)) => p,
_ => return Ok(0),
};
let desc = MTLRenderPassDescriptor::new();
unsafe {
let ca = desc.colorAttachments().objectAtIndexedSubscript(0);
ca.setTexture(Some(reflection.as_ref()));
ca.setLoadAction(MTLLoadAction::DontCare);
ca.setStoreAction(MTLStoreAction::Store);
}
if let Some(t) = &self.diagnostics.pass_timing {
t.attach_render(&desc, crate::metal::pass_timing::PassId::RtReflections);
}
let enc = ScopedEncoder::new(
cmd_buf
.renderCommandEncoderWithDescriptor(&desc)
.ok_or_else(|| RenderError::Other("failed to get RT reflections encoder".into()))?,
ns_string!("rt reflections"),
);
enc.set_pipeline(pipeline);
enc.set_fragment_texture(self.targets.hdr.hdr_resolve.as_ref(), 0);
enc.set_fragment_texture(gb_normal_depth, 1);
enc.set_fragment_texture(gb_roughness, 2);
enc.set_fragment_texture(self.scene.env_map.prefilter.as_ref(), 3);
self.probe_bindings().bind(
&enc,
crate::metal::probe_set::ProbeSlots {
set: 8,
records: 11,
cubes: Some(4),
cluster: Some((9, 10)),
},
);
set_fragment_sampler_range(&enc, &self.composite.sampler, 0, 3);
set_fragment_sampler_range(&enc, self.scene.cube_sampler.as_ref(), 3, 2);
set_fragment_sampler_range(&enc, self.scene.sampler.as_ref(), RT_POOL_SAMPLER_INDEX, 1);
enc.set_fragment_value(rt_params, 0);
enc.set_fragment_buffer(self.scene.vertex_buffer.as_ref(), 0, 1);
enc.set_fragment_buffer(self.scene.index_buffer.as_ref(), 0, 2);
enc.set_fragment_buffer(accel.geom_table.as_ref(), 0, 3);
enc.set_fragment_acceleration_structure(accel.tlas.as_ref(), 4);
enc.set_fragment_buffer(accel.deformed_verts.as_ref(), 0, 5);
enc.set_fragment_buffer(accel.skinned_indices.as_ref(), 0, 6);
if textured && let Some(tex_args) = bindless_tex_args {
enc.set_fragment_buffer(
tex_args.as_ref(),
crate::metal::bindless_args::BINDLESS_POOL_OFFSET,
crate::metal::context::BINDLESS_TEXTURE_ARG_BUFFER_INDEX,
);
self.use_bindless_textures(&enc);
}
crate::metal::raytrace::use_blas_resident_fragment(&enc, accel.traced_blas());
unsafe {
enc.drawPrimitives_vertexStart_vertexCount(MTLPrimitiveType::Triangle, 0, 3);
}
drop(enc);
self.encode_reflection_composite(cmd_buf)?;
Ok(0)
}
}