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, font::FontRequest};
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,
rc::{Rc, Weak},
};
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<Vertex>,
pub(crate) texture_vertices: GLArrayBuffer<Vertex>,
pub(crate) texture: Rc<GLTexture>,
pub(crate) vertex_count: i32,
}
enum GLRenderingPrimitive {
FillPath {
vertices: GLArrayBuffer<Vertex>,
indices: GLIndexBuffer<u16>,
},
Rectangle {
vertices: GLArrayBuffer<Vertex>,
indices: GLIndexBuffer<u16>,
radius: f32,
border_width: f32,
rect_size: Size,
},
Texture {
vertices: GLArrayBuffer<Vertex>,
texture_vertices: GLArrayBuffer<Vertex>,
texture: Rc<texture::AtlasAllocation>,
},
#[cfg(target_arch = "wasm32")]
DynamicPrimitive {
primitive: Rc<RefCell<Option<GLRenderingPrimitive>>>,
},
GlyphRuns {
glyph_runs: Vec<GlyphRun>,
},
ApplyClip {
vertices: Rc<GLArrayBuffer<Vertex>>,
indices: Rc<GLIndexBuffer<u16>>,
rect_size: Size,
},
ReleaseClip {
vertices: Rc<GLArrayBuffer<Vertex>>,
indices: Rc<GLIndexBuffer<u16>>,
rect_size: Size,
},
}
struct NormalRectangle {
vertices: GLArrayBuffer<Vertex>,
indices: GLIndexBuffer<u16>,
}
#[derive(PartialEq, Eq, Hash, Debug)]
enum TextureCacheKey {
#[cfg(not(target_arch = "wasm32"))]
Path(String),
EmbeddedData(by_address::ByAddress<&'static [u8]>),
}
pub struct GLRenderer {
context: Rc<glow::Context>,
path_shader: PathShader,
image_shader: ImageShader,
glyph_shader: GlyphShader,
rect_shader: RectShader,
#[cfg(not(target_arch = "wasm32"))]
platform_data: Rc<PlatformData>,
texture_atlas: Rc<RefCell<TextureAtlas>>,
#[cfg(target_arch = "wasm32")]
window: Rc<winit::window::Window>,
#[cfg(target_arch = "wasm32")]
event_loop_proxy:
Rc<winit::event_loop::EventLoopProxy<sixtyfps_corelib::eventloop::CustomEvent>>,
#[cfg(not(target_arch = "wasm32"))]
windowed_context: Option<glutin::WindowedContext<glutin::NotCurrent>>,
normal_rectangle: Option<NormalRectangle>,
texture_cache: Rc<RefCell<HashMap<TextureCacheKey, Weak<texture::AtlasAllocation>>>>,
}
pub struct GLRenderingPrimitivesBuilder {
context: Rc<glow::Context>,
fill_tesselator: FillTessellator,
stroke_tesselator: StrokeTessellator,
texture_atlas: Rc<RefCell<TextureAtlas>>,
#[cfg(not(target_arch = "wasm32"))]
platform_data: Rc<PlatformData>,
#[cfg(target_arch = "wasm32")]
window: Rc<winit::window::Window>,
#[cfg(target_arch = "wasm32")]
event_loop_proxy:
Rc<winit::event_loop::EventLoopProxy<sixtyfps_corelib::eventloop::CustomEvent>>,
#[cfg(not(target_arch = "wasm32"))]
windowed_context: glutin::WindowedContext<glutin::PossiblyCurrent>,
texture_cache: Rc<RefCell<HashMap<TextureCacheKey, Weak<texture::AtlasAllocation>>>>,
}
pub struct GLFrame {
context: Rc<glow::Context>,
path_shader: PathShader,
image_shader: ImageShader,
glyph_shader: GlyphShader,
rect_shader: RectShader,
root_matrix: cgmath::Matrix4<f32>,
#[cfg(not(target_arch = "wasm32"))]
windowed_context: glutin::WindowedContext<glutin::PossiblyCurrent>,
normal_rectangle: Option<NormalRectangle>,
current_stencil_clip_value: u8,
}
impl GLRenderer {
pub fn new(
event_loop: &winit::event_loop::EventLoop<sixtyfps_corelib::eventloop::CustomEvent>,
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 event_loop_proxy = Rc::new(event_loop.create_proxy());
#[cfg(target_arch = "wasm32")]
let (window, context) = {
let canvas = web_sys::window()
.unwrap()
.document()
.unwrap()
.get_element_by_id(canvas_id)
.unwrap()
.dyn_into::<web_sys::HtmlCanvasElement>()
.unwrap();
use winit::platform::web::WindowBuilderExtWebSys;
use winit::platform::web::WindowExtWebSys;
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 resize_canvas = {
let event_loop_proxy = event_loop_proxy.clone();
let canvas = web_sys::window()
.unwrap()
.document()
.unwrap()
.get_element_by_id(canvas_id)
.unwrap()
.dyn_into::<web_sys::HtmlCanvasElement>()
.unwrap();
let window = window.clone();
move |_: web_sys::Event| {
let existing_canvas_size = winit::dpi::LogicalSize::new(
canvas.client_width() as u32,
canvas.client_height() as u32,
);
window.set_inner_size(existing_canvas_size);
window.request_redraw();
event_loop_proxy
.send_event(sixtyfps_corelib::eventloop::CustomEvent::WakeUpAndPoll)
.ok();
}
};
let resize_closure =
wasm_bindgen::closure::Closure::wrap(Box::new(resize_canvas) as Box<dyn FnMut(_)>);
web_sys::window()
.unwrap()
.add_event_listener_with_callback("resize", resize_closure.as_ref().unchecked_ref())
.unwrap();
resize_closure.forget();
{
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);
attrs.antialias(false);
use wasm_bindgen::JsCast;
let webgl1_context = window
.canvas()
.get_context_with_context_options("webgl", attrs.as_ref())
.unwrap()
.unwrap()
.dyn_into::<web_sys::WebGlRenderingContext>()
.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,
#[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) {
self.texture_cache.borrow_mut().retain(|_, cached_texture| {
cached_texture
.upgrade()
.map_or(false, |cached_texture| Rc::strong_count(&cached_texture) > 1)
});
#[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<f32> = (*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();
image_path.push(&*path.clone());
let atlas_allocation = self.cached_texture(
TextureCacheKey::Path(image_path.to_string_lossy().to_string()),
|| image::open(image_path.as_path()).unwrap().into_rgba8(),
);
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);
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 atlas_allocation = self.cached_texture(
TextureCacheKey::EmbeddedData(by_address::ByAddress(
slice.as_slice(),
)),
|| {
let image_slice = slice.as_slice();
image::load_from_memory(image_slice).unwrap().to_rgba8()
},
);
smallvec![GLRenderingPrimitivesBuilder::create_texture(
&self.context,
atlas_allocation,
source_clip_rect
)]
}
Resource::EmbeddedRgbaImage { width, height, data } => {
let slice = unsafe { data.as_slice().align_to().1 };
let image = image::ImageBuffer::<image::Rgba<u8>, &[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_request } => {
smallvec![self.create_glyph_runs(text, font_request)]
}
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<Vertex, u16>,
) -> Option<GLRenderingPrimitive> {
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<Item = lyon::path::PathEvent>,
) -> Option<GLRenderingPrimitive> {
let mut geometry: VertexBuffers<Vertex, u16> = 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<Item = lyon::path::PathEvent>,
stroke_width: f32,
) -> Option<GLRenderingPrimitive> {
let mut geometry: VertexBuffers<Vertex, u16> = 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<glow::Context>,
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<glow::Context>,
atlas_allocation: Rc<texture::AtlasAllocation>,
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<Img: texture::UploadableAtlasImage>(
&self,
key: TextureCacheKey,
create_fn: impl Fn() -> Img,
) -> Rc<texture::AtlasAllocation> {
match self.texture_cache.borrow_mut().entry(key) {
std::collections::hash_map::Entry::Occupied(mut existing_entry) => {
existing_entry.get().upgrade().unwrap_or_else(|| {
let result = Rc::new(
self.texture_atlas
.borrow_mut()
.allocate_image_in_atlas(&self.context, create_fn()),
);
existing_entry.insert(Rc::downgrade(&result));
result
})
}
std::collections::hash_map::Entry::Vacant(vacant_entry) => {
let result = Rc::new(
self.texture_atlas
.borrow_mut()
.allocate_image_in_atlas(&self.context, create_fn()),
);
vacant_entry.insert(Rc::downgrade(&result));
result
}
}
}
#[cfg(not(target_arch = "wasm32"))]
fn create_glyph_runs(
&mut self,
text: &str,
font_request: &FontRequest,
) -> GLRenderingPrimitive {
let cached_glyphs = self.platform_data.glyph_cache.find_font(font_request);
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_request: &FontRequest,
) -> GLRenderingPrimitive {
let font = sixtyfps_corelib::font::FONT_CACHE.with(|fc| fc.find_font(font_request));
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>) -> [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<f32>,
variables: SharedArray<RenderingVariable>,
) -> Vec<OpaqueRenderingPrimitive> {
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::<Vec<_>>()
}
}
impl GLFrame {
fn render_one_low_level_primitive<'a>(
&mut self,
gl_primitive: &GLRenderingPrimitive,
rendering_var: &mut std::iter::Peekable<impl Iterator<Item = &'a RenderingVariable>>,
matrix: Matrix4<f32>,
) -> Option<OpaqueRenderingPrimitive> {
match gl_primitive {
GLRenderingPrimitive::FillPath { vertices, indices } => {
let col: RgbaColor<f32> = (*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<f32> = (*rendering_var.next().unwrap().as_color()).into();
let border_color: RgbaColor<f32> = 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 texture_width = texture.texture_coordinates.width() as f32;
let texture_height = texture.texture_coordinates.height() as f32;
let target_width = rendering_var
.next()
.map(|var| var.as_scaled_width())
.unwrap_or_else(|| texture_width);
let target_height = rendering_var
.next()
.map(|var| var.as_scaled_height())
.unwrap_or_else(|| texture_height);
let matrix = matrix
* Matrix4::from_nonuniform_scale(
target_width / texture_width,
target_height / texture_height,
1.,
);
self.render_texture(&matrix, vertices, texture_vertices, texture);
None
}
GLRenderingPrimitive::GlyphRuns { glyph_runs } => {
let col: RgbaColor<f32> = (*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,
);
}
};
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<f32> =
(*rendering_var.next().unwrap().as_color()).into();
let background_color: RgbaColor<f32> =
(*rendering_var.next().unwrap().as_color()).into();
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);
}
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,
};
render_glyphs(col);
if let (Some(selection_matrix), Some(text_cursor)) =
(reset_stencil, &self.normal_rectangle)
{
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<f32>,
vertices: &GLArrayBuffer<Vertex>,
indices: &GLIndexBuffer<u16>,
color: RgbaColor<f32>,
) {
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<f32>,
vertices: &GLArrayBuffer<Vertex>,
indices: &GLIndexBuffer<u16>,
color: RgbaColor<f32>,
radius: f32,
border_width: f32,
border_color: RgbaColor<f32>,
rect_size: Size,
) {
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<f32>,
vertices: &GLArrayBuffer<Vertex>,
texture_vertices: &GLArrayBuffer<Vertex>,
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<f32>,
vertices: &GLArrayBuffer<Vertex>,
texture_vertices: &GLArrayBuffer<Vertex>,
texture: &texture::GLTexture,
vertex_count: i32,
color: RgbaColor<f32>,
) {
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;