#![deny(unsafe_op_in_unsafe_fn)]
use objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2_metal::{
MTLBlendFactor, MTLCommandBuffer as _, MTLCommandEncoder as _, MTLDevice as _, MTLLibrary as _,
MTLLoadAction, MTLPixelFormat, MTLPrimitiveType, MTLRenderCommandEncoder as _,
MTLRenderPassDescriptor, MTLRenderPipelineDescriptor, MTLRenderPipelineState, MTLStoreAction,
MTLTexture,
};
use crate::metal::context::MtlContext;
use crate::metal::encode::RenderEncode;
use crate::metal::pass_timing::PassId;
use crate::metal::pipeline::ns_str;
use crate::metal::slang_shaders::{FULLSCREEN_VERT, SlangLib};
#[derive(Clone, Copy)]
pub(crate) enum FullscreenBlend {
Replace,
Additive,
PremultipliedOver,
}
pub(crate) struct FullscreenStages<'a> {
pub vertex_library: &'a ProtocolObject<dyn objc2_metal::MTLLibrary>,
pub vertex_name: &'a str,
pub fragment_library: &'a ProtocolObject<dyn objc2_metal::MTLLibrary>,
pub fragment_name: &'a str,
}
pub(crate) fn build_fullscreen_pipeline_split(
device: &ProtocolObject<dyn objc2_metal::MTLDevice>,
stages: FullscreenStages,
format: MTLPixelFormat,
blend: FullscreenBlend,
) -> Result<Retained<ProtocolObject<dyn MTLRenderPipelineState>>, String> {
let FullscreenStages {
vertex_library,
vertex_name,
fragment_library,
fragment_name,
} = stages;
let vert_fn = vertex_library
.newFunctionWithName(&ns_str(vertex_name))
.ok_or_else(|| format!("{} not found", vertex_name))?;
let frag_fn = fragment_library
.newFunctionWithName(&ns_str(fragment_name))
.ok_or_else(|| format!("{} not found", fragment_name))?;
let desc = MTLRenderPipelineDescriptor::new();
desc.setVertexFunction(Some(&vert_fn));
desc.setFragmentFunction(Some(&frag_fn));
desc.setRasterSampleCount(1);
unsafe {
let ca = desc.colorAttachments().objectAtIndexedSubscript(0);
ca.setPixelFormat(format);
match blend {
FullscreenBlend::Replace => ca.setBlendingEnabled(false),
FullscreenBlend::Additive => {
ca.setBlendingEnabled(true);
ca.setSourceRGBBlendFactor(MTLBlendFactor::One);
ca.setDestinationRGBBlendFactor(MTLBlendFactor::One);
ca.setSourceAlphaBlendFactor(MTLBlendFactor::One);
ca.setDestinationAlphaBlendFactor(MTLBlendFactor::One);
}
FullscreenBlend::PremultipliedOver => {
ca.setBlendingEnabled(true);
ca.setSourceRGBBlendFactor(MTLBlendFactor::One);
ca.setDestinationRGBBlendFactor(MTLBlendFactor::OneMinusSourceAlpha);
ca.setSourceAlphaBlendFactor(MTLBlendFactor::One);
ca.setDestinationAlphaBlendFactor(MTLBlendFactor::OneMinusSourceAlpha);
}
}
}
device
.newRenderPipelineStateWithDescriptor_error(&desc)
.map_err(|e| format!("failed to create {} pipeline: {:?}", fragment_name, e))
}
pub(in crate::metal) fn build_slang_fullscreen_pipeline(
device: &ProtocolObject<dyn objc2_metal::MTLDevice>,
fragment: &SlangLib,
format: MTLPixelFormat,
blend: FullscreenBlend,
hot_reload: bool,
) -> Result<Retained<ProtocolObject<dyn MTLRenderPipelineState>>, String> {
let vert = FULLSCREEN_VERT.library(device, hot_reload)?;
let frag = fragment.library(device, hot_reload)?;
build_fullscreen_pipeline_split(
device,
FullscreenStages {
vertex_library: &vert,
vertex_name: "fullscreen_vertex",
fragment_library: &frag,
fragment_name: fragment.entries[0],
},
format,
blend,
)
}
pub(in crate::metal) fn set_fragment_sampler_range(
enc: &ProtocolObject<dyn objc2_metal::MTLRenderCommandEncoder>,
sampler: &ProtocolObject<dyn objc2_metal::MTLSamplerState>,
first: usize,
count: usize,
) {
for i in first..first + count {
enc.set_fragment_sampler(sampler, i);
}
}
#[derive(Clone, Copy)]
pub(crate) enum PassTimer {
None,
Whole(PassId),
First(PassId),
Last(PassId),
}
pub(in crate::metal) struct FullscreenPass<'a> {
pub target: &'a ProtocolObject<dyn MTLTexture>,
pub load: MTLLoadAction,
pub timer: PassTimer,
pub pipeline: &'a ProtocolObject<dyn MTLRenderPipelineState>,
pub label: &'a str,
}
impl MtlContext {
pub(in crate::metal) fn fullscreen_pass(
&self,
cmd_buf: &ProtocolObject<dyn objc2_metal::MTLCommandBuffer>,
pass: FullscreenPass,
bind: impl FnOnce(&ProtocolObject<dyn objc2_metal::MTLRenderCommandEncoder>),
) -> Result<(), String> {
let FullscreenPass {
target,
load,
timer,
pipeline,
label,
} = pass;
let desc = MTLRenderPassDescriptor::new();
unsafe {
let ca = desc.colorAttachments().objectAtIndexedSubscript(0);
ca.setTexture(Some(target));
ca.setLoadAction(load);
ca.setStoreAction(MTLStoreAction::Store);
}
if let Some(t) = &self.diagnostics.pass_timing {
match timer {
PassTimer::None => {}
PassTimer::Whole(id) => t.attach_render(&desc, id),
PassTimer::First(id) => t.attach_render_first(&desc, id),
PassTimer::Last(id) => t.attach_render_last(&desc, id),
}
}
let enc = cmd_buf
.renderCommandEncoderWithDescriptor(&desc)
.ok_or_else(|| format!("failed to get {} encoder", label))?;
enc.set_pipeline(pipeline);
bind(&enc);
unsafe {
enc.drawPrimitives_vertexStart_vertexCount(MTLPrimitiveType::Triangle, 0, 3);
}
enc.endEncoding();
Ok(())
}
}