#![deny(unsafe_op_in_unsafe_fn)]
use objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2_metal::{
MTLClearColor, MTLCommandBuffer as _, MTLDevice as _, MTLLoadAction, MTLPixelFormat,
MTLRenderCommandEncoder as _, MTLRenderPassDescriptor, MTLRenderPipelineDescriptor,
MTLRenderPipelineState, MTLStoreAction, MTLTexture, MTLTextureUsage, MTLVertexDescriptor,
MTLVertexFormat, MTLVertexStepFunction,
};
use crate::gfx::mesh_payload::Vertex;
use crate::metal::context::MtlContext;
use crate::metal::descriptors::{TextureDesc, VertexAttr, VertexLayout, vertex_descriptor};
use crate::metal::encode::RenderEncode;
use crate::metal::scoped_encoder::ScopedEncoder;
use crate::metal::slang_shaders;
use concinnity_core::render::uniforms::GBufferView;
pub(crate) struct GBufferState {
pub targets: Option<GBufferTargets>,
pub bindless_pipeline: Option<Retained<ProtocolObject<dyn MTLRenderPipelineState>>>,
pub history_pipeline:
Option<Retained<ProtocolObject<dyn objc2_metal::MTLComputePipelineState>>>,
}
pub(crate) struct GBufferTargets {
pub depth: Retained<ProtocolObject<dyn MTLTexture>>,
}
pub(crate) fn create_gbuffer_targets(
device: &ProtocolObject<dyn objc2_metal::MTLDevice>,
width: u32,
height: u32,
) -> Result<GBufferTargets, String> {
let desc = TextureDesc {
format: MTLPixelFormat::Depth32Float,
width: width.max(1) as usize,
height: height.max(1) as usize,
usage: MTLTextureUsage(MTLTextureUsage::ShaderRead.0 | MTLTextureUsage::RenderTarget.0),
..Default::default()
}
.build();
let depth = device
.newTextureWithDescriptor(&desc)
.ok_or("failed to create G-buffer depth texture")?;
Ok(GBufferTargets { depth })
}
pub(crate) fn gbuffer_bindless_vertex_descriptor() -> Retained<MTLVertexDescriptor> {
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: 3,
format: MTLVertexFormat::Float3,
offset: 36,
buffer_index: 1,
}, VertexAttr {
index: 5,
format: MTLVertexFormat::Float3,
offset: 0,
buffer_index: 2,
}, ],
&[
VertexLayout {
buffer_index: 1,
stride: std::mem::size_of::<Vertex>(),
step: MTLVertexStepFunction::PerVertex,
},
VertexLayout {
buffer_index: 2,
stride: std::mem::size_of::<Vertex>(),
step: MTLVertexStepFunction::PerVertex,
},
],
)
}
pub(crate) fn build_gbuffer_bindless_pipeline(
device: &ProtocolObject<dyn objc2_metal::MTLDevice>,
hot_reload: bool,
) -> Result<Retained<ProtocolObject<dyn MTLRenderPipelineState>>, String> {
let vert_fn = slang_shaders::entry_function(
device,
&slang_shaders::GBUFFER_PREPASS_VERT_BINDLESS,
hot_reload,
)?;
let frag_fn = slang_shaders::entry_function(
device,
&slang_shaders::GBUFFER_PREPASS_FRAG_BINDLESS,
hot_reload,
)?;
let vert_desc = gbuffer_bindless_vertex_descriptor();
let desc = MTLRenderPipelineDescriptor::new();
desc.setVertexDescriptor(Some(&vert_desc));
desc.setVertexFunction(Some(&vert_fn));
desc.setFragmentFunction(Some(&frag_fn));
desc.setRasterSampleCount(1);
unsafe {
let ca0 = desc.colorAttachments().objectAtIndexedSubscript(0);
ca0.setPixelFormat(MTLPixelFormat::RGBA16Float);
ca0.setBlendingEnabled(false);
let ca1 = desc.colorAttachments().objectAtIndexedSubscript(1);
ca1.setPixelFormat(MTLPixelFormat::R8Unorm);
ca1.setBlendingEnabled(false);
let ca2 = desc.colorAttachments().objectAtIndexedSubscript(2);
ca2.setPixelFormat(MTLPixelFormat::RG16Float);
ca2.setBlendingEnabled(false);
}
desc.setDepthAttachmentPixelFormat(MTLPixelFormat::Depth32Float);
desc.setSupportIndirectCommandBuffers(true);
device
.newRenderPipelineStateWithDescriptor_error(&desc)
.map_err(|e| format!("failed to create G-buffer bindless pipeline: {:?}", e))
}
#[derive(Clone, Copy)]
pub(in crate::metal) struct GbufferGpuBuffers<'a> {
pub object_buffer: Option<&'a Retained<ProtocolObject<dyn objc2_metal::MTLBuffer>>>,
pub prev_model_buffer: Option<&'a Retained<ProtocolObject<dyn objc2_metal::MTLBuffer>>>,
pub draw_args_buffer: Option<&'a Retained<ProtocolObject<dyn objc2_metal::MTLBuffer>>>,
pub history_targets: &'a [Retained<ProtocolObject<dyn objc2_metal::MTLBuffer>>],
pub deformed_current: Option<&'a Retained<ProtocolObject<dyn objc2_metal::MTLBuffer>>>,
pub deformed_prev: Option<&'a Retained<ProtocolObject<dyn objc2_metal::MTLBuffer>>>,
}
impl MtlContext {
pub(in crate::metal) fn encode_gbuffer_prepass(
&self,
cmd_buf: &ProtocolObject<dyn objc2_metal::MTLCommandBuffer>,
view: &GBufferView,
gpu: GbufferGpuBuffers,
velocity_active: bool,
) -> Result<u32, String> {
let Some(targets) = &self.gbuffer.targets else {
return Ok(0);
};
let (normal_depth, roughness, velocity) = match (
self.gbuffer_normal_depth(),
self.gbuffer_roughness(),
self.gbuffer_velocity(),
) {
(Some(n), Some(r), Some(v)) => (n, r, v),
_ => {
return Err(
"G-buffer pre-pass: the transient pool is missing a colour channel; \
its build gate disagrees with the pre-pass's"
.to_string(),
);
}
};
let desc = MTLRenderPassDescriptor::new();
unsafe {
let ca0 = desc.colorAttachments().objectAtIndexedSubscript(0);
ca0.setTexture(Some(normal_depth));
ca0.setLoadAction(MTLLoadAction::Clear);
ca0.setStoreAction(MTLStoreAction::Store);
ca0.setClearColor(MTLClearColor {
red: 0.0,
green: 0.0,
blue: 0.0,
alpha: 0.0,
});
let ca1 = desc.colorAttachments().objectAtIndexedSubscript(1);
ca1.setTexture(Some(roughness));
ca1.setLoadAction(MTLLoadAction::Clear);
ca1.setStoreAction(MTLStoreAction::Store);
ca1.setClearColor(MTLClearColor {
red: 1.0,
green: 0.0,
blue: 0.0,
alpha: 0.0,
});
let ca2 = desc.colorAttachments().objectAtIndexedSubscript(2);
ca2.setTexture(Some(velocity));
ca2.setLoadAction(MTLLoadAction::Clear);
ca2.setStoreAction(MTLStoreAction::Store);
ca2.setClearColor(MTLClearColor {
red: 0.0,
green: 0.0,
blue: 0.0,
alpha: 0.0,
});
let da = desc.depthAttachment();
da.setTexture(Some(targets.depth.as_ref()));
da.setLoadAction(MTLLoadAction::Clear);
da.setClearDepth(1.0);
da.setStoreAction(MTLStoreAction::Store);
}
if let Some(t) = &self.diagnostics.pass_timing {
t.attach_render(&desc, crate::metal::pass_timing::PassId::GBufferPrepass);
}
let snapshot = gpu.object_buffer.cloned();
let history_targets = gpu.history_targets;
let draw_calls = {
let enc = ScopedEncoder::new(
cmd_buf
.renderCommandEncoderWithDescriptor(&desc)
.ok_or("failed to get G-buffer pre-pass encoder")?,
"g-buffer prepass",
);
self.encode_gbuffer_prepass_gpu_driven(&enc, view, gpu, velocity_active)
};
if let Some(objects) = snapshot.as_ref() {
self.encode_model_history(cmd_buf, objects, history_targets, self.cull_count())?;
}
Ok(draw_calls)
}
fn encode_gbuffer_prepass_gpu_driven(
&self,
enc: &ProtocolObject<dyn objc2_metal::MTLRenderCommandEncoder>,
view: &GBufferView,
gpu: GbufferGpuBuffers,
velocity_active: bool,
) -> u32 {
use objc2_metal::{MTLRenderStages, MTLResourceUsage};
use std::sync::atomic::Ordering;
let GbufferGpuBuffers {
object_buffer,
prev_model_buffer,
draw_args_buffer,
history_targets: _,
deformed_current,
deformed_prev,
} = gpu;
let (Some(pipeline), Some(object_buffer), Some(prev_models), Some(draw_args)) = (
self.gbuffer.bindless_pipeline.as_ref(),
object_buffer,
prev_model_buffer,
draw_args_buffer,
) else {
return 0;
};
if self.cull.icbs.is_empty() {
return 0;
}
enc.set_pipeline(pipeline);
enc.set_depth_stencil(&self.depth_state);
enc.set_vertex_value(view, 0);
enc.set_vertex_buffer(object_buffer, 0, 9);
enc.set_vertex_buffer(prev_models, 0, 10);
enc.set_vertex_buffer(draw_args, 0, 11);
enc.set_vertex_buffer(&self.vertex_buffer, 0, 1);
enc.set_vertex_buffer(&self.vertex_buffer, 0, 2);
let counts = self.draw_record_counts();
let mut draw_calls = 0u32;
if let Some(prefix) = counts.prefix(0) {
enc.useResource_usage_stages(
ProtocolObject::from_ref(&*self.index_buffer),
MTLResourceUsage::Read,
MTLRenderStages::Vertex,
);
let range = crate::metal::context::ns_range(prefix);
for (b, icb) in self.cull.icbs.iter().enumerate() {
if !self.world_shader_resident(b) {
continue;
}
unsafe {
enc.executeCommandsInBuffer_withRange(icb, range);
}
draw_calls += 1;
}
}
if let (Some(deformed), Some(tail)) = (deformed_current, counts.skinned_tail(0)) {
let prev = if velocity_active
&& self.frames_in_flight >= 2
&& self.skinned.deformed_primed.load(Ordering::Relaxed)
{
deformed_prev.unwrap_or(deformed)
} else {
deformed
};
enc.set_vertex_buffer(deformed, 0, 1);
enc.set_vertex_buffer(prev, 0, 2);
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(
&self.cull.icbs[0],
crate::metal::context::ns_range(tail),
);
}
draw_calls += 1;
self.skinned.deformed_primed.store(true, Ordering::Relaxed);
}
draw_calls
}
}