renderer.rs

  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}