/* LICENSE BEGIN This file is part of the SixtyFPS Project -- https://sixtyfps.io Copyright (c) 2020 Olivier Goffart Copyright (c) 2020 Simon Hausmann SPDX-License-Identifier: GPL-3.0-only This file is also available under commercial licensing terms. Please contact info@sixtyfps.io for more information. LICENSE END */ use cgmath::Matrix4; use glow::{Context as GLContext, HasContext}; use lyon::tessellation::geometry_builder::{BuffersBuilder, VertexBuffers}; use lyon::tessellation::{ FillAttributes, FillOptions, FillTessellator, StrokeAttributes, StrokeOptions, StrokeTessellator, }; use sixtyfps_corelib::eventloop::ComponentWindow; use sixtyfps_corelib::{ graphics::{ Color, Frame as GraphicsFrame, GraphicsBackend, GraphicsWindow, HighLevelRenderingPrimitive, IntRect, Point, Rect, RenderingPrimitivesBuilder, RenderingVariable, Resource, RgbaColor, Size, }, SharedArray, }; use smallvec::{smallvec, SmallVec}; use std::{cell::RefCell, collections::HashMap}; extern crate alloc; use alloc::rc::Rc; mod texture; use texture::{GLTexture, TextureAtlas}; mod shader; use shader::{GlyphShader, ImageShader, PathShader, RectShader}; mod buffers; use buffers::{GLArrayBuffer, GLIndexBuffer}; #[cfg(not(target_arch = "wasm32"))] mod glyphcache; #[cfg(not(target_arch = "wasm32"))] use glyphcache::GlyphCache; #[cfg(not(target_arch = "wasm32"))] #[derive(Default)] struct PlatformData { glyph_cache: GlyphCache, } #[derive(Copy, Clone)] pub(crate) struct Vertex { _pos: [f32; 2], } pub struct GlyphRun { pub(crate) vertices: GLArrayBuffer, pub(crate) texture_vertices: GLArrayBuffer, pub(crate) texture: Rc, pub(crate) vertex_count: i32, } enum GLRenderingPrimitive { FillPath { vertices: GLArrayBuffer, indices: GLIndexBuffer, }, Rectangle { vertices: GLArrayBuffer, indices: GLIndexBuffer, radius: f32, border_width: f32, rect_size: Size, }, Texture { vertices: GLArrayBuffer, texture_vertices: GLArrayBuffer, texture: Rc, }, #[cfg(target_arch = "wasm32")] DynamicPrimitive { primitive: Rc>>, }, GlyphRuns { glyph_runs: Vec, }, ApplyClip { vertices: Rc>, indices: Rc>, rect_size: Size, }, ReleaseClip { vertices: Rc>, indices: Rc>, rect_size: Size, }, } struct NormalRectangle { vertices: GLArrayBuffer, indices: GLIndexBuffer, } type TextureCacheKey = String; pub struct GLRenderer { context: Rc, path_shader: PathShader, image_shader: ImageShader, glyph_shader: GlyphShader, rect_shader: RectShader, #[cfg(not(target_arch = "wasm32"))] platform_data: Rc, texture_atlas: Rc>, #[cfg(target_arch = "wasm32")] window: Rc, #[cfg(target_arch = "wasm32")] event_loop_proxy: Rc>, #[cfg(not(target_arch = "wasm32"))] windowed_context: Option>, normal_rectangle: Option, texture_cache: Rc>>>, } pub struct GLRenderingPrimitivesBuilder { context: Rc, fill_tesselator: FillTessellator, stroke_tesselator: StrokeTessellator, texture_atlas: Rc>, #[cfg(not(target_arch = "wasm32"))] platform_data: Rc, #[cfg(target_arch = "wasm32")] window: Rc, #[cfg(target_arch = "wasm32")] event_loop_proxy: Rc>, #[cfg(not(target_arch = "wasm32"))] windowed_context: glutin::WindowedContext, texture_cache: Rc>>>, } pub struct GLFrame { context: Rc, path_shader: PathShader, image_shader: ImageShader, glyph_shader: GlyphShader, rect_shader: RectShader, root_matrix: cgmath::Matrix4, #[cfg(not(target_arch = "wasm32"))] windowed_context: glutin::WindowedContext, normal_rectangle: Option, current_stencil_clip_value: u8, } impl GLRenderer { pub fn new( event_loop: &winit::event_loop::EventLoop, window_builder: winit::window::WindowBuilder, #[cfg(target_arch = "wasm32")] canvas_id: &str, ) -> GLRenderer { #[cfg(not(target_arch = "wasm32"))] let (windowed_context, context) = { let windowed_context = glutin::ContextBuilder::new() .with_vsync(true) .build_windowed(window_builder, &event_loop) .unwrap(); let windowed_context = unsafe { windowed_context.make_current().unwrap() }; let gl_context = unsafe { glow::Context::from_loader_function(|s| { windowed_context.get_proc_address(s) as *const _ }) }; #[cfg(target_os = "macos")] { use cocoa::appkit::NSView; use winit::platform::macos::WindowExtMacOS; let ns_view = windowed_context.window().ns_view(); let view_id: cocoa::base::id = ns_view as *const _ as *mut _; unsafe { NSView::setLayerContentsPlacement(view_id, cocoa::appkit::NSViewLayerContentsPlacement::NSViewLayerContentsPlacementTopLeft) } } (windowed_context, gl_context) }; #[cfg(target_arch = "wasm32")] let (window, context) = { let canvas = web_sys::window() .unwrap() .document() .unwrap() .get_element_by_id(canvas_id) .unwrap() .dyn_into::() .unwrap(); use winit::platform::web::WindowBuilderExtWebSys; use winit::platform::web::WindowExtWebSys; // Try to maintain the existing size of the canvas element. A window created with winit // on the web will always have 1024x768 as size otherwise. let existing_canvas_size = winit::dpi::LogicalSize::new( canvas.client_width() as u32, canvas.client_height() as u32, ); let window = Rc::new(window_builder.with_canvas(Some(canvas)).build(&event_loop).unwrap()); { let default_size = window.inner_size().to_logical(window.scale_factor()); let new_size = winit::dpi::LogicalSize::new( if existing_canvas_size.width > 0 { existing_canvas_size.width } else { default_size.width }, if existing_canvas_size.height > 0 { existing_canvas_size.height } else { default_size.height }, ); if new_size != default_size { window.set_inner_size(new_size); } } let mut attrs = web_sys::WebGlContextAttributes::new(); attrs.stencil(true); use wasm_bindgen::JsCast; let webgl1_context = window .canvas() .get_context_with_context_options("webgl", attrs.as_ref()) .unwrap() .unwrap() .dyn_into::() .unwrap(); (window, glow::Context::from_webgl1_context(webgl1_context)) }; let vertex_array_object = unsafe { context.create_vertex_array().expect("Cannot create vertex array") }; unsafe { context.bind_vertex_array(Some(vertex_array_object)); } let context = Rc::new(context); let path_shader = PathShader::new(&context); let image_shader = ImageShader::new(&context); let glyph_shader = GlyphShader::new(&context); let rect_shader = RectShader::new(&context); #[cfg(not(target_arch = "wasm32"))] let platform_data = Rc::new(PlatformData::default()); GLRenderer { context, path_shader, image_shader, glyph_shader, rect_shader, #[cfg(not(target_arch = "wasm32"))] platform_data, texture_atlas: Rc::new(RefCell::new(TextureAtlas::new())), #[cfg(target_arch = "wasm32")] window, #[cfg(target_arch = "wasm32")] event_loop_proxy: Rc::new(event_loop.create_proxy()), #[cfg(not(target_arch = "wasm32"))] windowed_context: Some(unsafe { windowed_context.make_not_current().unwrap() }), normal_rectangle: None, texture_cache: Default::default(), } } } type GLRenderingPrimitives = SmallVec<[GLRenderingPrimitive; 1]>; pub struct OpaqueRenderingPrimitive { gl_primitives: GLRenderingPrimitives, } impl GraphicsBackend for GLRenderer { type LowLevelRenderingPrimitive = OpaqueRenderingPrimitive; type Frame = GLFrame; type RenderingPrimitivesBuilder = GLRenderingPrimitivesBuilder; fn new_rendering_primitives_builder(&mut self) -> Self::RenderingPrimitivesBuilder { #[cfg(not(target_arch = "wasm32"))] let current_windowed_context = unsafe { self.windowed_context.take().unwrap().make_current().unwrap() }; { if self.normal_rectangle.is_none() { let vertex1 = Vertex { _pos: [0., 0.] }; let vertex2 = Vertex { _pos: [0., 1.] }; let vertex3 = Vertex { _pos: [1., 1.] }; let vertex4 = Vertex { _pos: [1., 0.] }; let vertices = GLArrayBuffer::new(&self.context, &vec![vertex1, vertex2, vertex3, vertex4]); let indices = GLIndexBuffer::new(&self.context, &[0, 1, 2, 0, 2, 3]); self.normal_rectangle = Some(NormalRectangle { vertices, indices }); } } GLRenderingPrimitivesBuilder { context: self.context.clone(), fill_tesselator: FillTessellator::new(), stroke_tesselator: StrokeTessellator::new(), texture_atlas: self.texture_atlas.clone(), #[cfg(not(target_arch = "wasm32"))] platform_data: self.platform_data.clone(), #[cfg(target_arch = "wasm32")] window: self.window.clone(), #[cfg(target_arch = "wasm32")] event_loop_proxy: self.event_loop_proxy.clone(), #[cfg(not(target_arch = "wasm32"))] windowed_context: current_windowed_context, texture_cache: self.texture_cache.clone(), } } fn finish_primitives(&mut self, _builder: Self::RenderingPrimitivesBuilder) { #[cfg(not(target_arch = "wasm32"))] { self.windowed_context = Some(unsafe { _builder.windowed_context.make_not_current().unwrap() }); } } fn new_frame(&mut self, width: u32, height: u32, clear_color: &Color) -> GLFrame { #[cfg(not(target_arch = "wasm32"))] let current_windowed_context = unsafe { self.windowed_context.take().unwrap().make_current().unwrap() }; 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); self.context.enable(glow::STENCIL_TEST); self.context.stencil_func(glow::EQUAL, 0, 0xff); self.context.stencil_op(glow::KEEP, glow::KEEP, glow::KEEP); self.context.stencil_mask(0); } let col: RgbaColor = (*clear_color).into(); unsafe { self.context.stencil_mask(0xff); self.context.clear_stencil(0); self.context.clear_color(col.red, col.green, col.blue, col.alpha); self.context.clear(glow::COLOR_BUFFER_BIT | glow::STENCIL_BUFFER_BIT); self.context.stencil_mask(0); }; GLFrame { context: self.context.clone(), path_shader: self.path_shader.clone(), image_shader: self.image_shader.clone(), glyph_shader: self.glyph_shader.clone(), rect_shader: self.rect_shader.clone(), root_matrix: cgmath::ortho(0.0, width as f32, height as f32, 0.0, -1., 1.0), #[cfg(not(target_arch = "wasm32"))] windowed_context: current_windowed_context, normal_rectangle: self.normal_rectangle.take(), current_stencil_clip_value: 0, } } fn present_frame(&mut self, mut frame: Self::Frame) { #[cfg(not(target_arch = "wasm32"))] { frame.windowed_context.swap_buffers().unwrap(); self.windowed_context = Some(unsafe { frame.windowed_context.make_not_current().unwrap() }); } self.normal_rectangle = frame.normal_rectangle.take(); } fn window(&self) -> &winit::window::Window { #[cfg(not(target_arch = "wasm32"))] return self.windowed_context.as_ref().unwrap().window(); #[cfg(target_arch = "wasm32")] return &self.window; } } impl RenderingPrimitivesBuilder for GLRenderingPrimitivesBuilder { type LowLevelRenderingPrimitive = OpaqueRenderingPrimitive; fn create( &mut self, primitive: HighLevelRenderingPrimitive, ) -> Self::LowLevelRenderingPrimitive { OpaqueRenderingPrimitive { gl_primitives: match &primitive { HighLevelRenderingPrimitive::NoContents => smallvec::SmallVec::new(), HighLevelRenderingPrimitive::Rectangle { width, height, border_width, border_radius, } => { use lyon::math::Point; let rect = Rect::new(Point::default(), Size::new(*width, *height)); smallvec![self.fill_rectangle(&rect, *border_radius, *border_width)] } HighLevelRenderingPrimitive::Image { source, source_clip_rect } => { match source { #[cfg(not(target_arch = "wasm32"))] Resource::AbsoluteFilePath(path) => { let mut image_path = std::env::current_exe().unwrap(); image_path.pop(); // pop of executable name image_path.push(&*path.clone()); let atlas_allocation = self .cached_texture(image_path.to_string_lossy().to_string(), || { image::open(image_path.as_path()).unwrap().into_rgba() }); smallvec![GLRenderingPrimitivesBuilder::create_texture( &self.context, atlas_allocation, source_clip_rect )] } #[cfg(target_arch = "wasm32")] Resource::AbsoluteFilePath(path) => { let shared_primitive = Rc::new(RefCell::new(None)); let html_image = web_sys::HtmlImageElement::new().unwrap(); html_image.set_cross_origin(Some("anonymous")); html_image.set_onload(Some( &wasm_bindgen::closure::Closure::once_into_js({ let context = self.context.clone(); let atlas = self.texture_atlas.clone(); let html_image = html_image.clone(); let shared_primitive = shared_primitive.clone(); let window = self.window.clone(); let event_loop_proxy = self.event_loop_proxy.clone(); let source_clip_rect = *source_clip_rect; move || { let texture_primitive = GLRenderingPrimitivesBuilder::create_image( &context, &mut *atlas.borrow_mut(), &html_image, &source_clip_rect, ); *shared_primitive.borrow_mut() = Some(texture_primitive); // As you can paint on a HTML canvas at any point in time, request_redraw() // on a winit window only queues an additional internal event, that'll be // be dispatched as the next event. We are however not in an event loop // call, so we also need to wake up the event loop. window.request_redraw(); event_loop_proxy.send_event( sixtyfps_corelib::eventloop::CustomEvent::WakeUpAndPoll, ).ok(); } }) .into(), )); html_image.set_src(path); smallvec![GLRenderingPrimitive::DynamicPrimitive { primitive: shared_primitive }] } Resource::EmbeddedData(slice) => { let image_slice = slice.as_slice(); let image = image::load_from_memory(image_slice).unwrap().to_rgba(); let image = image::ImageBuffer::, &[u8]>::from_raw( image.width(), image.height(), &image, ) .unwrap(); smallvec![GLRenderingPrimitivesBuilder::create_image( &self.context, &mut *self.texture_atlas.borrow_mut(), image, &source_clip_rect )] } Resource::EmbeddedRgbaImage { width, height, data } => { // Safety: a slice of u32 can be transmuted to a slice of u8 let slice = unsafe { data.as_slice().align_to().1 }; let image = image::ImageBuffer::, &[u8]>::from_raw( *width, *height, slice, ) .unwrap(); smallvec![GLRenderingPrimitivesBuilder::create_image( &self.context, &mut *self.texture_atlas.borrow_mut(), image, &source_clip_rect )] } Resource::None => SmallVec::new(), } } HighLevelRenderingPrimitive::Text { text, font_family, font_size } => { smallvec![self.create_glyph_runs(text, font_family, *font_size)] } HighLevelRenderingPrimitive::Path { width, height, elements, stroke_width } => { let mut primitives = SmallVec::new(); let path_iter = elements.iter_fitted(*width, *height); primitives.extend(self.fill_path(path_iter.iter()).into_iter()); primitives .extend(self.stroke_path(path_iter.iter(), *stroke_width).into_iter()); primitives } HighLevelRenderingPrimitive::ClipRect { width, height } => { use lyon::math::Point; let rect = Rect::new(Point::default(), Size::new(*width, *height)); smallvec![match self.fill_rectangle(&rect, 0., 0.) { GLRenderingPrimitive::Rectangle { vertices, indices, radius: _, border_width: _, rect_size } => { GLRenderingPrimitive::ApplyClip{vertices: Rc::new(vertices), indices: Rc::new(indices), rect_size} } _ => panic!("internal error: unsupported clipping primitive returned by fill_rectangle") }] } }, } } } impl GLRenderingPrimitivesBuilder { fn fill_path_from_geometry( &self, geometry: &VertexBuffers, ) -> Option { if geometry.vertices.len() == 0 || geometry.indices.len() == 0 { return None; } let vertices = GLArrayBuffer::new(&self.context, &geometry.vertices); let indices = GLIndexBuffer::new(&self.context, &geometry.indices); Some(GLRenderingPrimitive::FillPath { vertices, indices }.into()) } fn fill_path( &mut self, path: impl IntoIterator, ) -> Option { let mut geometry: VertexBuffers = VertexBuffers::new(); let fill_opts = FillOptions::default(); self.fill_tesselator .tessellate( path, &fill_opts, &mut BuffersBuilder::new( &mut geometry, |pos: lyon::math::Point, _: FillAttributes| Vertex { _pos: [pos.x as f32, pos.y as f32], }, ), ) .unwrap(); self.fill_path_from_geometry(&geometry) } fn stroke_path( &mut self, path: impl IntoIterator, stroke_width: f32, ) -> Option { let mut geometry: VertexBuffers = VertexBuffers::new(); let stroke_opts = StrokeOptions::DEFAULT.with_line_width(stroke_width); self.stroke_tesselator .tessellate( path, &stroke_opts, &mut BuffersBuilder::new( &mut geometry, |pos: lyon::math::Point, _: StrokeAttributes| Vertex { _pos: [pos.x as f32, pos.y as f32], }, ), ) .unwrap(); self.fill_path_from_geometry(&geometry) } fn fill_rectangle( &mut self, rect: &Rect, radius: f32, border_width: f32, ) -> GLRenderingPrimitive { let vertex1 = Vertex { _pos: [rect.min_x(), rect.min_y()] }; let vertex2 = Vertex { _pos: [rect.min_x(), rect.max_y()] }; let vertex3 = Vertex { _pos: [rect.max_x(), rect.max_y()] }; let vertex4 = Vertex { _pos: [rect.max_x(), rect.min_y()] }; let vertices = GLArrayBuffer::new(&self.context, &vec![vertex1, vertex2, vertex3, vertex4]); let indices = GLIndexBuffer::new(&self.context, &[0, 1, 2, 0, 2, 3]); GLRenderingPrimitive::Rectangle { vertices, indices, radius, border_width, rect_size: rect.size, } .into() } fn create_image( context: &Rc, atlas: &mut TextureAtlas, image: impl texture::UploadableAtlasImage, source_rect: &IntRect, ) -> GLRenderingPrimitive { let atlas_allocation = atlas.allocate_image_in_atlas(&context, image); Self::create_texture(context, Rc::new(atlas_allocation), source_rect) } fn create_texture( context: &Rc, atlas_allocation: Rc, source_rect: &IntRect, ) -> GLRenderingPrimitive { let rect = Rect::new( Point::new(0.0, 0.0), Size::new( atlas_allocation.texture_coordinates.width() as f32, atlas_allocation.texture_coordinates.height() as f32, ), ); let vertex1 = Vertex { _pos: [rect.min_x(), rect.min_y()] }; let vertex2 = Vertex { _pos: [rect.max_x(), rect.min_y()] }; let vertex3 = Vertex { _pos: [rect.max_x(), rect.max_y()] }; let vertex4 = Vertex { _pos: [rect.min_x(), rect.max_y()] }; let vertices = GLArrayBuffer::new( &context, &vec![vertex1, vertex2, vertex3, vertex1, vertex3, vertex4], ); let texture_vertices = GLArrayBuffer::new( &context, &atlas_allocation.normalized_texture_coordinates_with_source_rect(source_rect), ); GLRenderingPrimitive::Texture { vertices, texture_vertices, texture: atlas_allocation } } fn cached_texture( &self, key: TextureCacheKey, create_fn: impl Fn() -> Img, ) -> Rc { self.texture_cache .borrow_mut() .entry(key) .or_insert_with(|| { Rc::new( self.texture_atlas .borrow_mut() .allocate_image_in_atlas(&self.context, create_fn()), ) }) .clone() } #[cfg(not(target_arch = "wasm32"))] fn create_glyph_runs( &mut self, text: &str, font_family: &str, pixel_size: f32, ) -> GLRenderingPrimitive { let cached_glyphs = self.platform_data.glyph_cache.find_font(font_family, pixel_size); let mut cached_glyphs = cached_glyphs.borrow_mut(); let mut atlas = self.texture_atlas.borrow_mut(); let glyphs_runs = cached_glyphs.render_glyphs(&self.context, &mut atlas, text); GLRenderingPrimitive::GlyphRuns { glyph_runs: glyphs_runs } } #[cfg(target_arch = "wasm32")] fn create_glyph_runs( &mut self, text: &str, font_family: &str, pixel_size: f32, ) -> GLRenderingPrimitive { let font = sixtyfps_corelib::font::FONT_CACHE.with(|fc| fc.find_font(font_family, pixel_size)); let text_canvas = font.render_text(text); let texture = Rc::new(GLTexture::new_from_canvas(&self.context, &text_canvas)); let rect = Rect::new( Point::new(0.0, 0.0), Size::new(text_canvas.width() as f32, text_canvas.height() as f32), ); let vertex1 = Vertex { _pos: [rect.min_x(), rect.min_y()] }; let vertex2 = Vertex { _pos: [rect.max_x(), rect.min_y()] }; let vertex3 = Vertex { _pos: [rect.max_x(), rect.max_y()] }; let vertex4 = Vertex { _pos: [rect.min_x(), rect.max_y()] }; let tex_vertex1 = Vertex { _pos: [0., 0.] }; let tex_vertex2 = Vertex { _pos: [1., 0.] }; let tex_vertex3 = Vertex { _pos: [1., 1.] }; let tex_vertex4 = Vertex { _pos: [0., 1.] }; let normalized_coordinates: [Vertex; 6] = [tex_vertex1, tex_vertex2, tex_vertex3, tex_vertex1, tex_vertex3, tex_vertex4]; let vertices = GLArrayBuffer::new( &self.context, &vec![vertex1, vertex2, vertex3, vertex1, vertex3, vertex4], ); let texture_vertices = GLArrayBuffer::new(&self.context, &normalized_coordinates); let vertex_count = 6; let glyph_runs = vec![GlyphRun { vertices, texture_vertices, texture, vertex_count }]; GLRenderingPrimitive::GlyphRuns { glyph_runs } } } fn to_gl_matrix(matrix: &Matrix4) -> [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], ] } impl GraphicsFrame for GLFrame { type LowLevelRenderingPrimitive = OpaqueRenderingPrimitive; fn render_primitive( &mut self, primitive: &OpaqueRenderingPrimitive, transform: &Matrix4, variables: SharedArray, ) -> Vec { let matrix = self.root_matrix * transform; let mut rendering_var = variables.iter().peekable(); let matrix = match rendering_var.peek() { Some(RenderingVariable::Translate(x_offset, y_offset)) => { rendering_var.next(); matrix * Matrix4::from_translation(cgmath::Vector3::new(*x_offset, *y_offset, 0.)) } _ => matrix, }; primitive .gl_primitives .iter() .filter_map(|gl_primitive| { self.render_one_low_level_primitive(gl_primitive, &mut rendering_var, matrix) }) .collect::>() } } impl GLFrame { fn render_one_low_level_primitive<'a>( &mut self, gl_primitive: &GLRenderingPrimitive, rendering_var: &mut std::iter::Peekable>, matrix: Matrix4, ) -> Option { match gl_primitive { GLRenderingPrimitive::FillPath { vertices, indices } => { let col: RgbaColor = (*rendering_var.next().unwrap().as_color()).into(); self.fill_path(&matrix, vertices, indices, col); None } GLRenderingPrimitive::Rectangle { vertices, indices, radius, border_width, rect_size, } => { let col: RgbaColor = (*rendering_var.next().unwrap().as_color()).into(); let border_color: RgbaColor = if *border_width > 0. { (*rendering_var.next().unwrap().as_color()).into() } else { Default::default() }; self.draw_rect( &matrix, vertices, indices, col, *radius, *border_width, border_color, *rect_size, ); None } GLRenderingPrimitive::Texture { vertices, texture_vertices, texture } => { let matrix = if let Some(scaled_width) = rendering_var.next() { matrix * Matrix4::from_nonuniform_scale( scaled_width.as_scaled_width() / texture.texture_coordinates.width() as f32, 1., 1., ) } else { matrix }; let matrix = if let Some(scaled_height) = rendering_var.next() { matrix * Matrix4::from_nonuniform_scale( 1., scaled_height.as_scaled_height() / texture.texture_coordinates.height() as f32, 1., ) } else { matrix }; self.render_texture(&matrix, vertices, texture_vertices, texture); None } GLRenderingPrimitive::GlyphRuns { glyph_runs } => { let col: RgbaColor = (*rendering_var.next().unwrap().as_color()).into(); let render_glyphs = |text_color| { for GlyphRun { vertices, texture_vertices, texture, vertex_count } in glyph_runs { self.render_glyph_run( &matrix, vertices, texture_vertices, texture, *vertex_count, text_color, ); } }; // Text selection is drawn in three phases: // 1. Draw the selection background rectangle, use regular stencil testing, write into the stencil buffer with GL_INCR // 2. Draw the glyphs, use regular stencil testing against current_stencil clip value + 1, don't write into the stencil buffer. This clips // and draws only the glyphs of the selected text. // 3. Draw the glyphs, use regular stencil testing against current stencil clip value, don't write into the stencil buffer. This clips // away the selected text and draws the non-selected part. // 4. We draw the selection background rectangle, use regular stencil testing, write into the stencil buffer with GL_DECR, use false color mask. // This "removes" the selection rectangle from the stencil buffer again. let reset_stencil = match (rendering_var.peek(), &self.normal_rectangle) { ( Some(RenderingVariable::TextSelection(x, width, height)), Some(text_cursor), ) => { rendering_var.next(); let foreground_color: RgbaColor = (*rendering_var.next().unwrap().as_color()).into(); let background_color: RgbaColor = (*rendering_var.next().unwrap().as_color()).into(); // Phase 1 let matrix = matrix * Matrix4::from_translation(cgmath::Vector3::new(*x, 0., 0.)) * Matrix4::from_nonuniform_scale(*width, *height, 1.); unsafe { self.context.stencil_mask(0xff); self.context.stencil_op(glow::KEEP, glow::KEEP, glow::INCR); } self.fill_path( &matrix, &text_cursor.vertices, &text_cursor.indices, background_color, ); unsafe { self.context.stencil_mask(0); self.context.stencil_op(glow::KEEP, glow::KEEP, glow::KEEP); } // Phase 2 unsafe { self.context.stencil_func( glow::EQUAL, (self.current_stencil_clip_value + 1) as i32, 0xff, ); } render_glyphs(foreground_color); unsafe { self.context.stencil_func( glow::EQUAL, self.current_stencil_clip_value as i32, 0xff, ); } Some(matrix) } _ => None, // no stencil to reset }; // Phase 3 render_glyphs(col); if let (Some(selection_matrix), Some(text_cursor)) = (reset_stencil, &self.normal_rectangle) { // Phase 4 unsafe { self.context.stencil_mask(0xff); self.context.stencil_op(glow::KEEP, glow::KEEP, glow::DECR); self.context.color_mask(false, false, false, false); } self.fill_path( &selection_matrix, &text_cursor.vertices, &text_cursor.indices, col, ); unsafe { self.context.stencil_mask(0); self.context.color_mask(true, true, true, true); self.context.stencil_op(glow::KEEP, glow::KEEP, glow::REPLACE); } } match (rendering_var.peek(), &self.normal_rectangle) { (Some(RenderingVariable::TextCursor(x, width, height)), Some(text_cursor)) => { let matrix = matrix * Matrix4::from_translation(cgmath::Vector3::new(*x, 0., 0.)) * Matrix4::from_nonuniform_scale(*width, *height, 1.); self.fill_path(&matrix, &text_cursor.vertices, &text_cursor.indices, col); rendering_var.next(); } _ => {} } None } GLRenderingPrimitive::ApplyClip { vertices, indices, rect_size } => { unsafe { self.context.stencil_mask(0xff); self.context.stencil_op(glow::KEEP, glow::KEEP, glow::INCR); self.context.color_mask(false, false, false, false); } self.draw_rect( &matrix, &vertices, &indices, RgbaColor { alpha: 0., red: 0., green: 0., blue: 0. }, 0., 0., RgbaColor { alpha: 0., red: 0., green: 0., blue: 0. }, *rect_size, ); unsafe { self.context.stencil_mask(0); self.context.stencil_op(glow::KEEP, glow::KEEP, glow::KEEP); self.context.color_mask(true, true, true, true); } self.current_stencil_clip_value += 1; unsafe { self.context.stencil_func( glow::EQUAL, self.current_stencil_clip_value as i32, 0xff, ); } Some(OpaqueRenderingPrimitive { gl_primitives: smallvec![GLRenderingPrimitive::ReleaseClip { vertices: vertices.clone(), indices: indices.clone(), rect_size: *rect_size, }], }) } GLRenderingPrimitive::ReleaseClip { vertices, indices, rect_size } => { unsafe { self.context.stencil_mask(0xff); self.context.stencil_op(glow::KEEP, glow::KEEP, glow::DECR); self.context.color_mask(false, false, false, false); } self.draw_rect( &matrix, &vertices, &indices, RgbaColor { alpha: 0., red: 0., green: 0., blue: 0. }, 0., 0., RgbaColor { alpha: 0., red: 0., green: 0., blue: 0. }, *rect_size, ); unsafe { self.context.stencil_mask(0); self.context.stencil_op(glow::KEEP, glow::KEEP, glow::KEEP); self.context.color_mask(true, true, true, true); } self.current_stencil_clip_value -= 1; unsafe { self.context.stencil_func( glow::EQUAL, self.current_stencil_clip_value as i32, 0xff, ); } None } #[cfg(target_arch = "wasm32")] GLRenderingPrimitive::DynamicPrimitive { primitive } => primitive .borrow() .as_ref() .map(|p| self.render_one_low_level_primitive(p, rendering_var, matrix)) .unwrap_or(None), } } fn fill_path( &self, matrix: &Matrix4, vertices: &GLArrayBuffer, indices: &GLIndexBuffer, color: RgbaColor, ) { self.path_shader.bind(&self.context, &to_gl_matrix(&matrix), color, vertices, indices); unsafe { self.context.draw_elements(glow::TRIANGLES, indices.len, glow::UNSIGNED_SHORT, 0); } self.path_shader.unbind(&self.context); } fn draw_rect( &self, matrix: &Matrix4, vertices: &GLArrayBuffer, indices: &GLIndexBuffer, color: RgbaColor, radius: f32, border_width: f32, border_color: RgbaColor, rect_size: Size, ) { // Make sure the border fits into the rectangle let radius = if radius * 2. > rect_size.width { rect_size.width / 2. } else { radius }; let radius = if radius * 2. > rect_size.height { rect_size.height / 2. } else { radius }; self.rect_shader.bind( &self.context, &to_gl_matrix(&matrix), color, &[rect_size.width / 2., rect_size.height / 2.], radius, border_width, border_color, vertices, indices, ); unsafe { self.context.draw_elements(glow::TRIANGLES, indices.len, glow::UNSIGNED_SHORT, 0); } self.rect_shader.unbind(&self.context); } fn render_texture( &self, matrix: &Matrix4, vertices: &GLArrayBuffer, texture_vertices: &GLArrayBuffer, texture: &texture::AtlasAllocation, ) { self.image_shader.bind( &self.context, &to_gl_matrix(&matrix), texture.atlas.texture.as_ref(), vertices, texture_vertices, ); unsafe { self.context.draw_arrays(glow::TRIANGLES, 0, 6); } self.image_shader.unbind(&self.context); } fn render_glyph_run( &self, matrix: &Matrix4, vertices: &GLArrayBuffer, texture_vertices: &GLArrayBuffer, texture: &texture::GLTexture, vertex_count: i32, color: RgbaColor, ) { self.glyph_shader.bind( &self.context, &to_gl_matrix(&matrix), color, texture, vertices, texture_vertices, ); unsafe { self.context.draw_arrays(glow::TRIANGLES, 0, vertex_count); } self.glyph_shader.unbind(&self.context); } } pub fn create_gl_window() -> ComponentWindow { ComponentWindow::new(GraphicsWindow::new(|event_loop, window_builder| { GLRenderer::new( &event_loop.get_winit_event_loop(), window_builder, #[cfg(target_arch = "wasm32")] "canvas", ) })) } #[cfg(target_arch = "wasm32")] pub fn create_gl_window_with_canvas_id(canvas_id: String) -> ComponentWindow { ComponentWindow::new(GraphicsWindow::new(move |event_loop, window_builder| { GLRenderer::new(&event_loop.get_winit_event_loop(), window_builder, &canvas_id) })) } #[doc(hidden)] #[cold] pub fn use_modules() { sixtyfps_corelib::use_modules(); } pub type NativeWidgets = (); pub type NativeGlobals = (); pub mod native_widgets {} pub const HAS_NATIVE_STYLE: bool = false;