1use std::{ffi::c_void, mem};
2
3use self::shaders::ToUchar4;
4
5use super::window::RenderContext;
6use crate::{color::ColorU, scene::Layer, Scene};
7use anyhow::{anyhow, Result};
8use metal::{MTLResourceOptions, NSRange};
9use shaders::ToFloat2 as _;
10
11const SHADERS_METALLIB: &'static [u8] =
12 include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
13const INSTANCE_BUFFER_SIZE: u64 = 1024 * 1024;
14
15pub struct Renderer {
16 quad_pipeline_state: metal::RenderPipelineState,
17 quad_vertices: metal::Buffer,
18 instances: metal::Buffer,
19}
20
21impl Renderer {
22 pub fn new(device: &metal::DeviceRef, pixel_format: metal::MTLPixelFormat) -> Result<Self> {
23 let library = device
24 .new_library_with_data(SHADERS_METALLIB)
25 .map_err(|message| anyhow!("error building metal library: {}", message))?;
26
27 let quad_vertices = [
28 (0., 0.).to_float2(),
29 (1., 0.).to_float2(),
30 (0., 1.).to_float2(),
31 (0., 1.).to_float2(),
32 (1., 0.).to_float2(),
33 (1., 1.).to_float2(),
34 ];
35 let quad_vertices = device.new_buffer_with_data(
36 quad_vertices.as_ptr() as *const c_void,
37 (quad_vertices.len() * mem::size_of::<shaders::vector_float2>()) as u64,
38 MTLResourceOptions::StorageModeManaged,
39 );
40 let instances =
41 device.new_buffer(INSTANCE_BUFFER_SIZE, MTLResourceOptions::StorageModeManaged);
42
43 Ok(Self {
44 quad_pipeline_state: build_pipeline_state(
45 device,
46 &library,
47 "quad",
48 "quad_vertex",
49 "quad_fragment",
50 pixel_format,
51 )?,
52 quad_vertices,
53 instances,
54 })
55 }
56
57 pub fn render(&mut self, scene: &Scene, ctx: &RenderContext) {
58 ctx.command_encoder.set_viewport(metal::MTLViewport {
59 originX: 0.0,
60 originY: 0.0,
61 width: ctx.drawable_size.x() as f64,
62 height: ctx.drawable_size.y() as f64,
63 znear: 0.0,
64 zfar: 1.0,
65 });
66
67 for layer in scene.layers() {
68 self.render_quads(scene, layer, ctx);
69 }
70 }
71
72 fn render_quads(&mut self, scene: &Scene, layer: &Layer, ctx: &RenderContext) {
73 ctx.command_encoder
74 .set_render_pipeline_state(&self.quad_pipeline_state);
75 ctx.command_encoder.set_vertex_buffer(
76 shaders::GPUIQuadInputIndex_GPUIQuadInputIndexVertices as u64,
77 Some(&self.quad_vertices),
78 0,
79 );
80 ctx.command_encoder.set_vertex_buffer(
81 shaders::GPUIQuadInputIndex_GPUIQuadInputIndexQuads as u64,
82 Some(&self.instances),
83 0,
84 );
85 ctx.command_encoder.set_vertex_bytes(
86 shaders::GPUIQuadInputIndex_GPUIQuadInputIndexUniforms as u64,
87 mem::size_of::<shaders::GPUIQuadUniforms>() as u64,
88 [shaders::GPUIQuadUniforms {
89 viewport_size: ctx.drawable_size.to_float2(),
90 }]
91 .as_ptr() as *const c_void,
92 );
93
94 let batch_size = self.instances.length() as usize / mem::size_of::<shaders::GPUIQuad>();
95
96 let buffer_contents = self.instances.contents() as *mut shaders::GPUIQuad;
97 for quad_batch in layer.quads().chunks(batch_size) {
98 for (ix, quad) in quad_batch.iter().enumerate() {
99 let bounds = quad.bounds * scene.scale_factor();
100 let shader_quad = shaders::GPUIQuad {
101 origin: bounds.origin().to_float2(),
102 size: bounds.size().to_float2(),
103 background_color: quad
104 .background
105 .unwrap_or(ColorU::transparent_black())
106 .to_uchar4(),
107 corner_radius: quad.corner_radius * scene.scale_factor(),
108 };
109 unsafe {
110 *(buffer_contents.offset(ix as isize)) = shader_quad;
111 }
112 }
113 self.instances.did_modify_range(NSRange {
114 location: 0,
115 length: (quad_batch.len() * mem::size_of::<shaders::GPUIQuad>()) as u64,
116 });
117
118 ctx.command_encoder.draw_primitives_instanced(
119 metal::MTLPrimitiveType::Triangle,
120 0,
121 6,
122 quad_batch.len() as u64,
123 );
124 }
125 }
126}
127
128fn build_pipeline_state(
129 device: &metal::DeviceRef,
130 library: &metal::LibraryRef,
131 label: &str,
132 vertex_fn_name: &str,
133 fragment_fn_name: &str,
134 pixel_format: metal::MTLPixelFormat,
135) -> Result<metal::RenderPipelineState> {
136 let vertex_fn = library
137 .get_function(vertex_fn_name, None)
138 .map_err(|message| anyhow!("error locating vertex function: {}", message))?;
139 let fragment_fn = library
140 .get_function(fragment_fn_name, None)
141 .map_err(|message| anyhow!("error locating fragment function: {}", message))?;
142
143 let descriptor = metal::RenderPipelineDescriptor::new();
144 descriptor.set_label(label);
145 descriptor.set_vertex_function(Some(vertex_fn.as_ref()));
146 descriptor.set_fragment_function(Some(fragment_fn.as_ref()));
147 let color_attachment = descriptor.color_attachments().object_at(0).unwrap();
148 color_attachment.set_pixel_format(pixel_format);
149 color_attachment.set_blending_enabled(true);
150 color_attachment.set_rgb_blend_operation(metal::MTLBlendOperation::Add);
151 color_attachment.set_alpha_blend_operation(metal::MTLBlendOperation::Add);
152 color_attachment.set_source_rgb_blend_factor(metal::MTLBlendFactor::SourceAlpha);
153 color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::SourceAlpha);
154 color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
155 color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
156
157 device
158 .new_render_pipeline_state(&descriptor)
159 .map_err(|message| anyhow!("could not create render pipeline state: {}", message))
160}
161
162mod shaders {
163 #![allow(non_upper_case_globals)]
164 #![allow(non_camel_case_types)]
165 #![allow(non_snake_case)]
166
167 use crate::{color::ColorU, geometry::vector::Vector2F};
168 use std::mem;
169
170 include!(concat!(env!("OUT_DIR"), "/shaders.rs"));
171
172 pub trait ToFloat2 {
173 fn to_float2(&self) -> vector_float2;
174 }
175
176 pub trait ToUchar4 {
177 fn to_uchar4(&self) -> vector_uchar4;
178 }
179
180 impl ToFloat2 for (f32, f32) {
181 fn to_float2(&self) -> vector_float2 {
182 unsafe {
183 let mut output = mem::transmute::<_, u32>(self.1.to_bits()) as vector_float2;
184 output <<= 32;
185 output |= mem::transmute::<_, u32>(self.0.to_bits()) as vector_float2;
186 output
187 }
188 }
189 }
190
191 impl ToFloat2 for Vector2F {
192 fn to_float2(&self) -> vector_float2 {
193 unsafe {
194 let mut output = mem::transmute::<_, u32>(self.y().to_bits()) as vector_float2;
195 output <<= 32;
196 output |= mem::transmute::<_, u32>(self.x().to_bits()) as vector_float2;
197 output
198 }
199 }
200 }
201
202 impl ToUchar4 for ColorU {
203 fn to_uchar4(&self) -> vector_uchar4 {
204 let mut vec = self.a as vector_uchar4;
205 vec <<= 8;
206 vec |= self.b as vector_uchar4;
207 vec <<= 8;
208 vec |= self.g as vector_uchar4;
209 vec <<= 8;
210 vec |= self.r as vector_uchar4;
211 vec
212 }
213 }
214}