use crate::error::{FilterChainError, Result}; use icrate::Foundation::NSString; use icrate::Metal::{ MTLBlendFactorOneMinusSourceAlpha, MTLBlendFactorSourceAlpha, MTLCommandBuffer, MTLCommandEncoder, MTLDevice, MTLFunction, MTLLibrary, MTLLoadActionDontCare, MTLPixelFormat, MTLPrimitiveTopologyClassTriangle, MTLRenderCommandEncoder, MTLRenderPassDescriptor, MTLRenderPipelineColorAttachmentDescriptor, MTLRenderPipelineDescriptor, MTLRenderPipelineState, MTLScissorRect, MTLStoreActionStore, MTLTexture, MTLVertexAttributeDescriptor, MTLVertexBufferLayoutDescriptor, MTLVertexDescriptor, MTLVertexFormatFloat2, MTLVertexStepFunctionPerVertex, MTLViewport, }; use librashader_reflect::back::msl::{CrossMslContext, NagaMslContext}; use librashader_reflect::back::ShaderCompilerOutput; use librashader_reflect::reflect::ShaderReflection; use librashader_runtime::render_target::RenderTarget; use objc2::rc::Id; use objc2::runtime::ProtocolObject; pub struct MetalGraphicsPipeline { pub layout: PipelineLayoutObjects, render_pipeline: Id>, } pub struct PipelineLayoutObjects { vertex_lib: Id>, fragment_lib: Id>, vertex_entry: Id>, fragment_entry: Id>, device: Id>, } trait MslEntryPoint { fn entry_point() -> NSString; } impl MslEntryPoint for CrossMslContext { fn entry_point() -> Id { NSString::from_str("main0") } } impl MslEntryPoint for NagaMslContext { fn entry_point() -> Id { NSString::from_str("main_") } } impl PipelineLayoutObjects { pub fn new( shader_assembly: &ShaderCompilerOutput, device: Id>, ) -> Result { let entry = T::entry_point(); let vertex = NSString::from_str(&shader_assembly.vertex); let vertex = device.newLibraryWithSource_options_error(&vertex, None)?; let vertex_entry = vertex .newFunctionWithName(&entry) .ok_or(FilterChainError::ShaderWrongEntryName)?; let fragment = NSString::from_str(&shader_assembly.fragment); let fragment = device.newLibraryWithSource_options_error(&fragment, None)?; let fragment_entry = fragment .newFunctionWithName(&entry) .ok_or(FilterChainError::ShaderWrongEntryName)?; Ok(Self { vertex_lib: vertex, fragment_lib: fragment, vertex_entry, fragment_entry, device, }) } unsafe fn create_vertex_descriptor() -> Id { let descriptor = MTLVertexDescriptor::new(); let attributes = descriptor.attributes(); let layouts = descriptor.layouts(); let binding = MTLVertexBufferLayoutDescriptor::new(); let vertex_0 = MTLVertexAttributeDescriptor::new(); let vertex_1 = MTLVertexAttributeDescriptor::new(); // hopefully metal fills in vertices otherwise we'll need to use the vec4 stuff. vertex_0.setFormat(MTLVertexFormatFloat2); vertex_0.setBufferIndex(4); vertex_0.setOffset(0); vertex_1.setFormat(MTLVertexFormatFloat2); vertex_1.setBufferIndex(4); vertex_1.setOffset(2 * std::mem::size_of::()); attributes.setObject_atIndexedSubscript(Some(&vertex_0), 0); attributes.setObject_atIndexedSubscript(Some(&vertex_1), 1); binding.setStepFunction(MTLVertexStepFunctionPerVertex); binding.setStride(4 * std::mem::size_of::()); layouts.setObject_atIndexedSubscript(Some(&binding), 0); descriptor } unsafe fn create_color_attachments( format: MTLPixelFormat, ) -> Id { let ca = MTLRenderPipelineColorAttachmentDescriptor::new(); ca.setPixelFormat(format); ca.setBlendingEnabled(false); ca.setSourceAlphaBlendFactor(MTLBlendFactorSourceAlpha); ca.setSourceRGBBlendFactor(MTLBlendFactorSourceAlpha); ca.setDestinationAlphaBlendFactor(MTLBlendFactorOneMinusSourceAlpha); ca.setDetinationRGBBlendFactor(MTLBlendFactorOneMinusSourceAlpha); ca } pub fn create_pipeline( &self, format: MTLPixelFormat, ) -> Result>> { let descriptor = MTLRenderPipelineDescriptor::new(); unsafe { let vertex = Self::create_vertex_descriptor(); let ca = Self::create_color_attachments(format); descriptor.setInputPrimitiveTopology(MTLPrimitiveTopologyClassTriangle); descriptor.setVertexDescriptor(Some(&vertex)); descriptor .colorAttachments() .setObject_atIndexedSubscript(Some(&ca), 0); descriptor.setRasterSampleCount(1); descriptor.setVertexFunction(Some(&self.vertex_entry)); descriptor.setFragmentFunction(Some(&self.fragment_entry)); } Ok(self .device .newRenderPipelineStateWithDescriptor_error(descriptor.as_ref())?) } } impl MetalGraphicsPipeline { pub fn new( device: Id>, shader_assembly: &ShaderCompilerOutput, render_pass_format: MTLPixelFormat, ) -> Result { let layout = PipelineLayoutObjects::new(shader_assembly, device)?; Ok(Self { layout, render_pipeline: layout.create_pipeline(render_pass_format)?, }) } pub fn recompile(&mut self, format: MTLPixelFormat) -> Result<()> { let render_pipeline = self.layout.create_pipeline(format)?; self.render_pipeline = render_pipeline; Ok(()) } pub fn begin_rendering<'pass>( &self, output: RenderTarget<&'pass ProtocolObject>, buffer: Id>, ) -> Result>> { unsafe { let descriptor = MTLRenderPassDescriptor::new(); let ca = descriptor.colorAttachments().objectAtIndexedSubscript(0); ca.setLoadAction(MTLLoadActionDontCare); ca.setStoreAction(MTLStoreActionStore); ca.setTexture(Some(output.output)); let rpass = buffer .renderCommandEncoderWithDescriptor(&descriptor) .ok_or(FilterChainError::FailedToCreateRenderPass)?; rpass.setScissorRect(MTLScissorRect { x: output.x as usize, y: output.y as usize, width: output.output.width(), height: output.output.height(), }); rpass.setViewport(MTLViewport { originX: output.x as f64, originY: output.y as f64, width: output.output.width() as f64, height: output.output.height() as f64, znear: 0.0, zfar: 1.0, }); rpass.setRenderPipelineState(&self.render_pipeline); Ok(rpass) } } }