use cgmath::Matrix4; use glow::{Context as GLContext, HasContext}; use kurbo::{BezPath, PathEl, Point, Rect}; use lyon::path::PathEvent; use lyon::tessellation::geometry_builder::{BuffersBuilder, VertexBuffers}; use lyon::tessellation::{FillAttributes, FillOptions, FillTessellator}; use sixtyfps_corelib::graphics::{Color, FillStyle, Frame as GraphicsFrame, GraphicsBackend}; use std::marker; use std::mem; extern crate alloc; use alloc::rc::Rc; #[derive(Copy, Clone)] struct Vertex { _pos: [f32; 2], } enum GLRenderingPrimitive { FillPath { vertices: GLVertexBuffer, indices: GLIndexBuffer, style: FillStyle, }, Texture { vertices: GLVertexBuffer, texture_vertices: GLVertexBuffer, texture: GLTexture, }, } #[derive(Clone)] struct Shader { program: ::Program, } impl Shader { fn new(gl: &GLContext, vertex_shader_source: &str, fragment_shader_source: &str) -> Shader { let program = unsafe { gl.create_program().expect("Cannot create program") }; let shader_sources = [ (glow::VERTEX_SHADER, vertex_shader_source), (glow::FRAGMENT_SHADER, fragment_shader_source), ]; let mut shaders = Vec::with_capacity(shader_sources.len()); for (shader_type, shader_source) in shader_sources.iter() { unsafe { let shader = gl.create_shader(*shader_type).expect("Cannot create shader"); gl.shader_source(shader, &shader_source); gl.compile_shader(shader); if !gl.get_shader_compile_status(shader) { panic!(gl.get_shader_info_log(shader)); } gl.attach_shader(program, shader); shaders.push(shader); } } unsafe { gl.link_program(program); if !gl.get_program_link_status(program) { panic!(gl.get_program_info_log(program)); } for shader in shaders { gl.detach_shader(program, shader); gl.delete_shader(shader); } } Shader { program } } fn use_program(&self, gl: &glow::Context) { unsafe { gl.use_program(Some(self.program)); } } fn drop(&mut self, gl: &GLContext) { unsafe { gl.delete_program(self.program); } } } struct GLVertexBuffer { buffer_id: ::Buffer, _vertex_marker: marker::PhantomData, } impl GLVertexBuffer { fn new(gl: &glow::Context, data: &[VertexType]) -> Self { let buffer_id = unsafe { gl.create_buffer().expect("vertex buffer") }; unsafe { gl.bind_buffer(glow::ARRAY_BUFFER, Some(buffer_id)); let byte_len = mem::size_of_val(&data[0]) * data.len() / mem::size_of::(); let byte_slice = std::slice::from_raw_parts(data.as_ptr() as *const u8, byte_len); gl.buffer_data_u8_slice(glow::ARRAY_BUFFER, byte_slice, glow::STATIC_DRAW); } Self { buffer_id, _vertex_marker: marker::PhantomData } } fn bind(&self, gl: &glow::Context, attribute_location: u32) { unsafe { gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.buffer_id)); // TODO: generalize size/data_type gl.vertex_attrib_pointer_f32( attribute_location, (mem::size_of::() / mem::size_of::()) as i32, glow::FLOAT, false, 0, 0, ); gl.enable_vertex_attrib_array(attribute_location); } } // TODO #3: make sure we release GL resources /* fn drop(&mut self, gl: &glow::Context) { unsafe { gl.delete_buffer(self.buffer_id); } } */ } struct GLIndexBuffer { buffer_id: ::Buffer, len: i32, _vertex_marker: marker::PhantomData, } impl GLIndexBuffer { fn new(gl: &glow::Context, data: &[IndexType]) -> Self { let buffer_id = unsafe { gl.create_buffer().expect("vertex buffer") }; unsafe { gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(buffer_id)); let byte_len = mem::size_of_val(&data[0]) * data.len() / mem::size_of::(); let byte_slice = std::slice::from_raw_parts(data.as_ptr() as *const u8, byte_len); gl.buffer_data_u8_slice(glow::ELEMENT_ARRAY_BUFFER, byte_slice, glow::STATIC_DRAW); } Self { buffer_id, len: data.len() as i32, _vertex_marker: marker::PhantomData } } fn bind(&self, gl: &glow::Context) { unsafe { gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.buffer_id)); } } // TODO #3: make sure we release GL resources /* fn drop(&mut self, gl: &glow::Context) { unsafe { gl.delete_buffer(self.buffer_id); } } */ } struct GLTexture { texture_id: ::Texture, } impl GLTexture { fn new(gl: &glow::Context, image: image::ImageBuffer, Vec>) -> Self { let texture_id = unsafe { gl.create_texture().unwrap() }; unsafe { gl.bind_texture(glow::TEXTURE_2D, Some(texture_id)); gl.tex_parameter_i32( glow::TEXTURE_2D, glow::TEXTURE_WRAP_S, glow::CLAMP_TO_EDGE as i32, ); gl.tex_parameter_i32( glow::TEXTURE_2D, glow::TEXTURE_WRAP_T, glow::CLAMP_TO_EDGE as i32, ); gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MIN_FILTER, glow::LINEAR as i32); gl.tex_image_2d( glow::TEXTURE_2D, 0, glow::RGBA as i32, image.width() as i32, image.height() as i32, 0, glow::RGBA, glow::UNSIGNED_BYTE, Some(&image.into_raw()), ) } Self { texture_id } } fn bind_to_location( &self, gl: &glow::Context, texture_location: ::UniformLocation, ) { unsafe { gl.active_texture(glow::TEXTURE0); gl.bind_texture(glow::TEXTURE_2D, Some(self.texture_id)); gl.uniform_1_i32(Some(texture_location), 0); } } // TODO #3: make sure we release GL resources /* fn drop(&mut self, gl: &glow::Context) { unsafe { gl.delete_texture(self.texture_id); } } */ } pub struct GLRenderer { context: Rc, path_program: Shader, image_program: Shader, fill_tesselator: FillTessellator, } pub struct GLFrame { context: Rc, path_program: Shader, image_program: Shader, root_matrix: cgmath::Matrix4, } impl GLRenderer { pub fn new(context: glow::Context) -> GLRenderer { unsafe { let vertex_array = context.create_vertex_array().expect("Cannot create vertex array"); context.bind_vertex_array(Some(vertex_array)); } const PATH_VERTEX_SHADER: &str = r#"#version 100 attribute vec2 pos; uniform vec4 vertcolor; uniform mat4 matrix; varying lowp vec4 fragcolor; void main() { gl_Position = matrix * vec4(pos, 0.0, 1); fragcolor = vertcolor; }"#; const PATH_FRAGMENT_SHADER: &str = r#"#version 100 precision mediump float; varying lowp vec4 fragcolor; void main() { gl_FragColor = fragcolor; }"#; let path_program = Shader::new(&context, PATH_VERTEX_SHADER, PATH_FRAGMENT_SHADER); const IMAGE_VERTEX_SHADER: &str = r#"#version 100 attribute vec2 pos; attribute vec2 tex_pos; uniform mat4 matrix; varying highp vec2 frag_tex_pos; void main() { gl_Position = matrix * vec4(pos, 0.0, 1); frag_tex_pos = tex_pos; }"#; const IMAGE_FRAGMENT_SHADER: &str = r#"#version 100 varying highp vec2 frag_tex_pos; uniform sampler2D tex; void main() { gl_FragColor = texture2D(tex, frag_tex_pos); }"#; let image_program = Shader::new(&context, IMAGE_VERTEX_SHADER, IMAGE_FRAGMENT_SHADER); GLRenderer { context: Rc::new(context), path_program, image_program, fill_tesselator: FillTessellator::new(), } } } pub struct OpaqueRenderingPrimitive(GLRenderingPrimitive); impl GraphicsBackend for GLRenderer { type RenderingPrimitive = OpaqueRenderingPrimitive; type Frame = GLFrame; fn create_path_fill_primitive( &mut self, path: &BezPath, style: FillStyle, ) -> Self::RenderingPrimitive { let mut geometry: VertexBuffers = VertexBuffers::new(); let fill_opts = FillOptions::default(); self.fill_tesselator .tessellate( PathConverter::new(path), &fill_opts, &mut BuffersBuilder::new( &mut geometry, |pos: lyon::math::Point, _: FillAttributes| Vertex { _pos: [pos.x as f32, pos.y as f32], }, ), ) .unwrap(); let vertices = GLVertexBuffer::new(&self.context, &geometry.vertices); let indices = GLIndexBuffer::new(&self.context, &geometry.indices); OpaqueRenderingPrimitive(GLRenderingPrimitive::FillPath { vertices, indices, style }) } fn create_image_primitive( &mut self, source_rect: impl Into, dest_rect: impl Into, image: image::ImageBuffer, Vec>, ) -> Self::RenderingPrimitive { let rect = dest_rect.into(); let src_rect = source_rect.into(); let image_width = image.width() as f32; let image_height = image.height() as f32; let src_left = (src_rect.x0 as f32) / image_width; let src_top = (src_rect.y0 as f32) / image_height; let src_right = (src_rect.x1 as f32) / image_width; let src_bottom = (src_rect.y1 as f32) / image_height; let vertex1 = Vertex { _pos: [rect.x0 as f32, rect.y0 as f32] }; let tex_vertex1 = Vertex { _pos: [src_left, src_top] }; let vertex2 = Vertex { _pos: [rect.x1 as f32, rect.y0 as f32] }; let tex_vertex2 = Vertex { _pos: [src_right, src_top] }; let vertex3 = Vertex { _pos: [rect.x1 as f32, rect.y1 as f32] }; let tex_vertex3 = Vertex { _pos: [src_right, src_bottom] }; let vertex4 = Vertex { _pos: [rect.x0 as f32, rect.y1 as f32] }; let tex_vertex4 = Vertex { _pos: [src_left, src_bottom] }; let vertices = GLVertexBuffer::new( &self.context, &vec![vertex1, vertex2, vertex3, vertex1, vertex3, vertex4], ); let texture_vertices = GLVertexBuffer::new( &self.context, &vec![tex_vertex1, tex_vertex2, tex_vertex3, tex_vertex1, tex_vertex3, tex_vertex4], ); let texture = GLTexture::new(&self.context, image); OpaqueRenderingPrimitive(GLRenderingPrimitive::Texture { vertices, texture_vertices, texture, }) } fn new_frame(&mut self, width: u32, height: u32, clear_color: &Color) -> GLFrame { unsafe { self.context.viewport(0, 0, width as i32, height as i32); self.context.enable(glow::BLEND); self.context.blend_func(glow::ONE, glow::ONE_MINUS_SRC_ALPHA); } let (r, g, b, a) = clear_color.as_rgba_f32(); unsafe { self.context.clear_color(r, g, b, a); self.context.clear(glow::COLOR_BUFFER_BIT); }; GLFrame { context: self.context.clone(), path_program: self.path_program.clone(), image_program: self.image_program.clone(), root_matrix: cgmath::ortho(0.0, width as f32, height as f32, 0.0, -1., 1.0), } } } impl GraphicsFrame for GLFrame { type RenderingPrimitive = OpaqueRenderingPrimitive; fn render_primitive(&mut self, primitive: &OpaqueRenderingPrimitive, transform: &Matrix4) { let matrix = self.root_matrix * transform; let gl_matrix: [f32; 16] = [ matrix.x[0], matrix.x[1], matrix.x[2], matrix.x[3], matrix.y[0], matrix.y[1], matrix.y[2], matrix.y[3], matrix.z[0], matrix.z[1], matrix.z[2], matrix.z[3], matrix.w[0], matrix.w[1], matrix.w[2], matrix.w[3], ]; match &primitive.0 { GLRenderingPrimitive::FillPath { vertices, indices, style } => { self.path_program.use_program(&self.context); let matrix_location = unsafe { self.context.get_uniform_location(self.path_program.program, "matrix") }; unsafe { self.context.uniform_matrix_4_f32_slice(matrix_location, false, &gl_matrix) }; let (r, g, b, a) = match style { FillStyle::SolidColor(color) => color.as_rgba_f32(), }; let color_location = unsafe { self.context.get_uniform_location(self.path_program.program, "vertcolor") }; unsafe { self.context.uniform_4_f32(color_location, r, g, b, a) }; let vertex_attribute_location = unsafe { self.context.get_attrib_location(self.path_program.program, "pos").unwrap() }; vertices.bind(&self.context, vertex_attribute_location); indices.bind(&self.context); unsafe { self.context.draw_elements( glow::TRIANGLE_STRIP, indices.len, glow::UNSIGNED_SHORT, 0, ); } } GLRenderingPrimitive::Texture { vertices, texture_vertices, texture } => { self.image_program.use_program(&self.context); let matrix_location = unsafe { self.context.get_uniform_location(self.image_program.program, "matrix") }; unsafe { self.context.uniform_matrix_4_f32_slice(matrix_location, false, &gl_matrix) }; let texture_location = unsafe { self.context.get_uniform_location(self.image_program.program, "tex").unwrap() }; texture.bind_to_location(&self.context, texture_location); let vertex_attribute_location = unsafe { self.context.get_attrib_location(self.image_program.program, "pos").unwrap() }; vertices.bind(&self.context, vertex_attribute_location); let vertex_texture_attribute_location = unsafe { self.context.get_attrib_location(self.image_program.program, "tex_pos").unwrap() }; texture_vertices.bind(&self.context, vertex_texture_attribute_location); unsafe { self.context.draw_arrays(glow::TRIANGLES, 0, 6); } } } } fn submit(self) {} } struct PathConverter<'a> { first_point: Option, current_point: Option, shape_iter: Box + 'a>, deferred_begin: Option, needs_closure: bool, } impl<'a> PathConverter<'a> { fn new(path: &'a BezPath) -> Self { PathConverter { first_point: None, current_point: None, shape_iter: Box::new(path.iter()), deferred_begin: None, needs_closure: false, } } } impl<'a> Iterator for PathConverter<'a> { type Item = PathEvent; fn next(&mut self) -> Option { if self.deferred_begin.is_some() { return self.deferred_begin.take(); } let path_el = self.shape_iter.next(); match path_el { Some(PathEl::MoveTo(p)) => { let first = self.first_point; let last = self.current_point; self.current_point = Some(point_to_lyon_point(&p)); let event = Some(PathEvent::Begin { at: self.current_point.unwrap() }); if self.needs_closure { self.first_point = self.current_point; self.needs_closure = false; self.deferred_begin = event; Some(PathEvent::End { first: first.unwrap(), last: last.unwrap(), close: true }) } else { if self.first_point.is_none() { self.first_point = self.current_point; } event } } Some(PathEl::LineTo(p)) => { self.needs_closure = true; let from = self.current_point.unwrap(); let to = point_to_lyon_point(&p); self.current_point = Some(to); Some(PathEvent::Line { from, to }) } Some(PathEl::QuadTo(ctrl, to)) => { self.needs_closure = true; let to = point_to_lyon_point(&to); let from = self.current_point.replace(to).unwrap(); Some(PathEvent::Quadratic { from, ctrl: point_to_lyon_point(&ctrl), to }) } Some(PathEl::CurveTo(ctrl1, ctrl2, to)) => { self.needs_closure = true; let to = point_to_lyon_point(&to); let from = self.current_point.replace(to).unwrap(); Some(PathEvent::Cubic { from, ctrl1: point_to_lyon_point(&ctrl1), ctrl2: point_to_lyon_point(&ctrl2), to, }) } Some(PathEl::ClosePath) => { self.needs_closure = false; let last = self.current_point.take().unwrap(); let first = self.first_point.take().unwrap(); Some(PathEvent::End { first, last, close: true }) } None => { if self.needs_closure { self.needs_closure = false; let last = self.current_point.take().unwrap(); let first = self.first_point.take().unwrap(); Some(PathEvent::End { first, last, close: true }) } else { None } } } } } fn point_to_lyon_point(p: &Point) -> lyon::path::math::Point { lyon::path::math::Point::new(p.x as f32, p.y as f32) } impl Drop for GLRenderer { fn drop(&mut self) { self.path_program.drop(&self.context); self.image_program.drop(&self.context); } } #[cfg(test)] mod tests { #[test] fn it_works() { assert_eq!(2 + 2, 4); } }