908 lines
36 KiB
Rust
908 lines
36 KiB
Rust
/* Any copyright is dedicated to the Public Domain.
|
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
|
|
extern crate clap;
|
|
extern crate compute_shader;
|
|
extern crate euclid;
|
|
extern crate gl;
|
|
extern crate glfw;
|
|
extern crate image;
|
|
extern crate memmap;
|
|
extern crate pathfinder;
|
|
|
|
use clap::{App, Arg};
|
|
use compute_shader::buffer;
|
|
use compute_shader::image::{ExternalImage, Format, Image};
|
|
use compute_shader::instance::{Instance, ShadingLanguage};
|
|
use euclid::{Point2D, Rect, Size2D};
|
|
use gl::types::{GLchar, GLint, GLsizei, GLsizeiptr, GLuint, GLvoid};
|
|
use glfw::{Action, Context, Key, OpenGlProfileHint, SwapInterval, WindowEvent};
|
|
use glfw::{WindowHint, WindowMode};
|
|
use memmap::{Mmap, Protection};
|
|
use pathfinder::atlas::{Atlas, AtlasBuilder};
|
|
use pathfinder::charmap::{CodepointRanges, GlyphMapping};
|
|
use pathfinder::coverage::CoverageBuffer;
|
|
use pathfinder::otf::Font;
|
|
use pathfinder::outline::{OutlineBuilder, Outlines};
|
|
use pathfinder::rasterizer::{DrawAtlasProfilingEvents, Rasterizer, RasterizerOptions};
|
|
use pathfinder::shaper;
|
|
use std::char;
|
|
use std::fs::File;
|
|
use std::io::Read;
|
|
use std::mem;
|
|
use std::os::raw::c_void;
|
|
use std::path::Path;
|
|
|
|
const ATLAS_SIZE: u32 = 2048;
|
|
const WIDTH: u32 = 640;
|
|
const HEIGHT: u32 = 480;
|
|
const SCROLL_SPEED: f64 = 6.0;
|
|
|
|
const INITIAL_POINT_SIZE: f32 = 24.0;
|
|
const MIN_POINT_SIZE: f32 = 6.0;
|
|
const MAX_POINT_SIZE: f32 = 256.0;
|
|
|
|
const FPS_DISPLAY_POINT_SIZE: f32 = 24.0;
|
|
const FPS_PADDING: i32 = 6;
|
|
|
|
static FPS_BACKGROUND_COLOR: [f32; 4] = [0.0, 0.0, 0.0, 0.7];
|
|
static FPS_FOREGROUND_COLOR: [f32; 4] = [1.0, 1.0, 1.0, 1.0];
|
|
static TEXT_COLOR: [f32; 4] = [0.0, 0.0, 0.0, 1.0];
|
|
|
|
static ATLAS_DUMP_FILENAME: &'static str = "lorem-ipsum-atlas.png";
|
|
|
|
fn main() {
|
|
let index_arg = Arg::with_name("index").short("i")
|
|
.long("index")
|
|
.help("Select an index within a font collection")
|
|
.takes_value(true);
|
|
let font_arg = Arg::with_name("FONT-FILE").help("Select the font file (`.ttf`, `.otf`, etc.)")
|
|
.required(true)
|
|
.index(1);
|
|
let text_arg = Arg::with_name("TEXT-FILE").help("Select a file containing text to display")
|
|
.index(2);
|
|
let matches = App::new("lorem-ipsum").arg(index_arg).arg(font_arg).arg(text_arg).get_matches();
|
|
|
|
let mut glfw = glfw::init(glfw::LOG_ERRORS).unwrap();
|
|
glfw.window_hint(WindowHint::ContextVersion(3, 3));
|
|
glfw.window_hint(WindowHint::OpenGlForwardCompat(true));
|
|
glfw.window_hint(WindowHint::OpenGlProfile(OpenGlProfileHint::Core));
|
|
let context = glfw.create_window(WIDTH, HEIGHT, "lorem-ipsum", WindowMode::Windowed);
|
|
|
|
let (mut window, events) = context.expect("Couldn't create a window!");
|
|
window.make_current();
|
|
window.set_key_polling(true);
|
|
window.set_scroll_polling(true);
|
|
window.set_size_polling(true);
|
|
window.set_framebuffer_size_polling(true);
|
|
glfw.set_swap_interval(SwapInterval::Sync(1));
|
|
|
|
gl::load_with(|symbol| window.get_proc_address(symbol) as *const c_void);
|
|
|
|
let (width, height) = window.get_framebuffer_size();
|
|
let mut device_pixel_size = Size2D::new(width as u32, height as u32);
|
|
|
|
let mut text = "".to_string();
|
|
match matches.value_of("TEXT-FILE") {
|
|
Some(path) => drop(File::open(path).unwrap().read_to_string(&mut text).unwrap()),
|
|
None => text.push_str(TEXT),
|
|
}
|
|
text = text.replace(&['\n', '\r', '\t'][..], " ");
|
|
|
|
// Make sure the characters include `[A-Za-z0-9 ./,]`, for the FPS display.
|
|
let mut chars: Vec<char> = text.chars().collect();
|
|
chars.extend(" ./,:()".chars());
|
|
chars.extend(('A' as u32..('Z' as u32 + 1)).flat_map(char::from_u32));
|
|
chars.extend(('a' as u32..('z' as u32 + 1)).flat_map(char::from_u32));
|
|
chars.extend(('0' as u32..('9' as u32 + 1)).flat_map(char::from_u32));
|
|
chars.sort();
|
|
let codepoint_ranges = CodepointRanges::from_sorted_chars(&chars);
|
|
|
|
let font_index = match matches.value_of("index") {
|
|
Some(index) => index.parse().unwrap(),
|
|
None => 0,
|
|
};
|
|
|
|
let file = Mmap::open_path(matches.value_of("FONT-FILE").unwrap(), Protection::Read).unwrap();
|
|
let (font, glyph_mapping);
|
|
unsafe {
|
|
font = Font::from_collection_index(file.as_slice(), font_index).unwrap();
|
|
glyph_mapping = font.glyph_mapping_for_codepoint_ranges(&codepoint_ranges.ranges).unwrap();
|
|
}
|
|
|
|
// Do some basic line breaking.
|
|
let mut glyph_positions = vec![];
|
|
let paragraph_width = (device_pixel_size.width as f32 * font.units_per_em() as f32 /
|
|
INITIAL_POINT_SIZE) as u32;
|
|
let space_advance = font.metrics_for_glyph(glyph_mapping.glyph_for(' ' as u32).unwrap())
|
|
.unwrap()
|
|
.advance_width as u32;
|
|
let line_spacing = (font.ascender() as i32 - font.descender() as i32 + font.line_gap() as i32)
|
|
as u32;
|
|
|
|
let (mut current_x, mut current_y) = (0, line_spacing);
|
|
for word in text.split_whitespace() {
|
|
let shaped_glyph_positions = shaper::shape_text(&font, &glyph_mapping, word);
|
|
let total_advance: u32 = shaped_glyph_positions.iter().map(|p| p.advance as u32).sum();
|
|
if current_x + total_advance > paragraph_width {
|
|
current_x = 0;
|
|
current_y += line_spacing;
|
|
}
|
|
|
|
for glyph_position in &shaped_glyph_positions {
|
|
glyph_positions.push(GlyphPos {
|
|
x: current_x,
|
|
y: current_y,
|
|
glyph_id: glyph_position.glyph_id,
|
|
});
|
|
current_x += glyph_position.advance as u32;
|
|
}
|
|
|
|
current_x += space_advance
|
|
}
|
|
|
|
let renderer = Renderer::new();
|
|
let mut point_size = INITIAL_POINT_SIZE;
|
|
let mut translation = Point2D::new(0, 0);
|
|
let mut dirty = true;
|
|
|
|
let mut outline_builder = OutlineBuilder::new();
|
|
let mut glyph_indices = vec![];
|
|
let mut glyph_count = 0;
|
|
for (_, glyph_id) in glyph_mapping.iter() {
|
|
let glyph_index = outline_builder.add_glyph(&font, glyph_id).unwrap();
|
|
|
|
while glyph_id as usize >= glyph_indices.len() {
|
|
glyph_indices.push(0)
|
|
}
|
|
glyph_indices[glyph_id as usize] = glyph_index;
|
|
|
|
glyph_count += 1
|
|
}
|
|
|
|
let outlines = outline_builder.create_buffers().unwrap();
|
|
let fps_atlas = renderer.create_fps_atlas(&font, &outlines, glyph_count);
|
|
|
|
while !window.should_close() {
|
|
if dirty {
|
|
let events = renderer.redraw(point_size,
|
|
&font,
|
|
&outlines,
|
|
&glyph_indices,
|
|
glyph_count,
|
|
&glyph_positions,
|
|
&device_pixel_size,
|
|
&translation);
|
|
|
|
let mut draw_time = 0u64;
|
|
unsafe {
|
|
gl::Flush();
|
|
gl::GetQueryObjectui64v(events.draw, gl::QUERY_RESULT, &mut draw_time);
|
|
}
|
|
|
|
let draw_time = draw_time as f64;
|
|
let accum_time = events.accum.time_elapsed().unwrap() as f64;
|
|
let timing = renderer.get_timing_in_ms();
|
|
|
|
renderer.draw_fps(&font,
|
|
&fps_atlas,
|
|
&outlines,
|
|
&device_pixel_size,
|
|
&glyph_indices,
|
|
&glyph_mapping,
|
|
draw_time,
|
|
accum_time,
|
|
timing,
|
|
glyph_count);
|
|
|
|
window.swap_buffers();
|
|
|
|
dirty = false
|
|
}
|
|
|
|
|
|
glfw.wait_events();
|
|
for (_, event) in glfw::flush_messages(&events) {
|
|
match event {
|
|
WindowEvent::Key(Key::Escape, _, Action::Press, _) => {
|
|
window.set_should_close(true)
|
|
}
|
|
WindowEvent::Key(Key::S, _, Action::Press, _) => {
|
|
renderer.take_screenshot();
|
|
println!("wrote screenshot to: {}", ATLAS_DUMP_FILENAME);
|
|
}
|
|
WindowEvent::Scroll(x, y) => {
|
|
if window.get_key(Key::LeftAlt) == Action::Press ||
|
|
window.get_key(Key::RightAlt) == Action::Press {
|
|
let old_point_size = point_size;
|
|
point_size = old_point_size + y as f32;
|
|
|
|
if point_size < MIN_POINT_SIZE {
|
|
point_size = MIN_POINT_SIZE
|
|
} else if point_size > MAX_POINT_SIZE {
|
|
point_size = MAX_POINT_SIZE
|
|
}
|
|
|
|
let mut center = Point2D::new(
|
|
translation.x as f32 - device_pixel_size.width as f32 * 0.5,
|
|
translation.y as f32 - device_pixel_size.height as f32 * 0.5);
|
|
center.x = center.x * point_size / old_point_size;
|
|
center.y = center.y * point_size / old_point_size;
|
|
|
|
translation.x = (center.x + device_pixel_size.width as f32 * 0.5).round()
|
|
as i32;
|
|
translation.y = (center.y + device_pixel_size.height as f32 * 0.5).round()
|
|
as i32;
|
|
} else {
|
|
translation.x += (x * SCROLL_SPEED).round() as i32;
|
|
translation.y += (y * SCROLL_SPEED).round() as i32;
|
|
}
|
|
|
|
dirty = true
|
|
}
|
|
WindowEvent::Size(_, _) | WindowEvent::FramebufferSize(_, _) => {
|
|
let (width, height) = window.get_framebuffer_size();
|
|
device_pixel_size = Size2D::new(width as u32, height as u32);
|
|
dirty = true
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct Renderer {
|
|
rasterizer: Rasterizer,
|
|
|
|
composite_program: GLuint,
|
|
composite_atlas_uniform: GLint,
|
|
composite_transform_uniform: GLint,
|
|
composite_translation_uniform: GLint,
|
|
composite_color_uniform: GLint,
|
|
|
|
main_composite_vertex_array: CompositeVertexArray,
|
|
fps_composite_vertex_array: CompositeVertexArray,
|
|
|
|
solid_color_program: GLuint,
|
|
solid_color_color_uniform: GLint,
|
|
|
|
solid_color_vertex_array: GLuint,
|
|
solid_color_vertex_buffer: GLuint,
|
|
solid_color_index_buffer: GLuint,
|
|
|
|
atlas_size: Size2D<u32>,
|
|
|
|
main_coverage_buffer: CoverageBuffer,
|
|
fps_coverage_buffer: CoverageBuffer,
|
|
main_compute_image: Image,
|
|
main_gl_texture: GLuint,
|
|
fps_compute_image: Image,
|
|
fps_gl_texture: GLuint,
|
|
|
|
query: GLuint,
|
|
|
|
shading_language: ShadingLanguage,
|
|
}
|
|
|
|
impl Renderer {
|
|
fn new() -> Renderer {
|
|
let instance = Instance::new().unwrap();
|
|
let device = instance.open_device().unwrap();
|
|
let queue = device.create_queue().unwrap();
|
|
|
|
let rasterizer_options = RasterizerOptions::from_env().unwrap();
|
|
let rasterizer = Rasterizer::new(&instance, device, queue, rasterizer_options).unwrap();
|
|
|
|
let (composite_program, composite_position_attribute, composite_tex_coord_attribute);
|
|
let (composite_atlas_uniform, composite_transform_uniform);
|
|
let (composite_translation_uniform, composite_color_uniform);
|
|
let (main_composite_vertex_array, fps_composite_vertex_array);
|
|
let (solid_color_program, solid_color_position_attribute, solid_color_color_uniform);
|
|
let (mut solid_color_vertex_buffer, mut solid_color_index_buffer) = (0, 0);
|
|
let mut solid_color_vertex_array = 0;
|
|
unsafe {
|
|
composite_program = create_program(COMPOSITE_VERTEX_SHADER, COMPOSITE_FRAGMENT_SHADER);
|
|
composite_position_attribute =
|
|
gl::GetAttribLocation(composite_program, "aPosition\0".as_ptr() as *const GLchar);
|
|
composite_tex_coord_attribute =
|
|
gl::GetAttribLocation(composite_program, "aTexCoord\0".as_ptr() as *const GLchar);
|
|
composite_atlas_uniform =
|
|
gl::GetUniformLocation(composite_program, "uAtlas\0".as_ptr() as *const GLchar);
|
|
composite_transform_uniform =
|
|
gl::GetUniformLocation(composite_program,
|
|
"uTransform\0".as_ptr() as *const GLchar);
|
|
composite_translation_uniform =
|
|
gl::GetUniformLocation(composite_program,
|
|
"uTranslation\0".as_ptr() as *const GLchar);
|
|
composite_color_uniform =
|
|
gl::GetUniformLocation(composite_program, "uColor\0".as_ptr() as *const GLchar);
|
|
|
|
solid_color_program = create_program(SOLID_COLOR_VERTEX_SHADER,
|
|
SOLID_COLOR_FRAGMENT_SHADER);
|
|
solid_color_position_attribute =
|
|
gl::GetAttribLocation(solid_color_program,
|
|
"aPosition\0".as_ptr() as *const GLchar);
|
|
solid_color_color_uniform =
|
|
gl::GetUniformLocation(solid_color_program, "uColor\0".as_ptr() as *const GLchar);
|
|
|
|
gl::UseProgram(composite_program);
|
|
|
|
main_composite_vertex_array = CompositeVertexArray::new();
|
|
fps_composite_vertex_array = CompositeVertexArray::new();
|
|
for vertex_array in &[&main_composite_vertex_array, &fps_composite_vertex_array] {
|
|
gl::BindVertexArray(vertex_array.vertex_array);
|
|
|
|
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, vertex_array.index_buffer);
|
|
gl::BindBuffer(gl::ARRAY_BUFFER, vertex_array.vertex_buffer);
|
|
|
|
gl::VertexAttribPointer(composite_position_attribute as GLuint,
|
|
2,
|
|
gl::INT,
|
|
gl::FALSE,
|
|
mem::size_of::<Vertex>() as GLsizei,
|
|
0 as *const GLvoid);
|
|
gl::VertexAttribPointer(composite_tex_coord_attribute as GLuint,
|
|
2,
|
|
gl::UNSIGNED_INT,
|
|
gl::FALSE,
|
|
mem::size_of::<Vertex>() as GLsizei,
|
|
(mem::size_of::<f32>() * 2) as *const GLvoid);
|
|
gl::EnableVertexAttribArray(composite_position_attribute as GLuint);
|
|
gl::EnableVertexAttribArray(composite_tex_coord_attribute as GLuint);
|
|
}
|
|
|
|
gl::UseProgram(solid_color_program);
|
|
|
|
gl::GenVertexArrays(1, &mut solid_color_vertex_array);
|
|
gl::BindVertexArray(solid_color_vertex_array);
|
|
|
|
gl::GenBuffers(1, &mut solid_color_vertex_buffer);
|
|
gl::GenBuffers(1, &mut solid_color_index_buffer);
|
|
|
|
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, solid_color_index_buffer);
|
|
gl::BindBuffer(gl::ARRAY_BUFFER, solid_color_vertex_buffer);
|
|
|
|
gl::VertexAttribPointer(solid_color_position_attribute as GLuint,
|
|
2,
|
|
gl::FLOAT,
|
|
gl::FALSE,
|
|
mem::size_of::<i32>() as GLsizei * 2,
|
|
0 as *const GLvoid);
|
|
gl::EnableVertexAttribArray(solid_color_position_attribute as GLuint);
|
|
|
|
gl::BufferData(gl::ELEMENT_ARRAY_BUFFER,
|
|
(RECT_INDICES.len() * mem::size_of::<u32>()) as GLsizeiptr,
|
|
RECT_INDICES.as_ptr() as *const GLvoid,
|
|
gl::STATIC_DRAW);
|
|
}
|
|
|
|
// FIXME(pcwalton)
|
|
let atlas_size = Size2D::new(ATLAS_SIZE, ATLAS_SIZE);
|
|
|
|
let main_coverage_buffer = CoverageBuffer::new(rasterizer.device(), &atlas_size).unwrap();
|
|
let fps_coverage_buffer = CoverageBuffer::new(rasterizer.device(), &atlas_size).unwrap();
|
|
|
|
let (main_compute_image, main_gl_texture) = create_image(&rasterizer, &atlas_size);
|
|
let (fps_compute_image, fps_gl_texture) = create_image(&rasterizer, &atlas_size);
|
|
|
|
let mut query = 0;
|
|
unsafe {
|
|
gl::GenQueries(1, &mut query);
|
|
}
|
|
|
|
let shading_language = instance.shading_language();
|
|
|
|
Renderer {
|
|
rasterizer: rasterizer,
|
|
|
|
composite_program: composite_program,
|
|
composite_atlas_uniform: composite_atlas_uniform,
|
|
composite_transform_uniform: composite_transform_uniform,
|
|
composite_translation_uniform: composite_translation_uniform,
|
|
composite_color_uniform: composite_color_uniform,
|
|
|
|
main_composite_vertex_array: main_composite_vertex_array,
|
|
fps_composite_vertex_array: fps_composite_vertex_array,
|
|
|
|
solid_color_program: solid_color_program,
|
|
solid_color_color_uniform: solid_color_color_uniform,
|
|
|
|
solid_color_vertex_array: solid_color_vertex_array,
|
|
solid_color_vertex_buffer: solid_color_vertex_buffer,
|
|
solid_color_index_buffer: solid_color_index_buffer,
|
|
|
|
atlas_size: atlas_size,
|
|
|
|
main_coverage_buffer: main_coverage_buffer,
|
|
fps_coverage_buffer: fps_coverage_buffer,
|
|
main_compute_image: main_compute_image,
|
|
main_gl_texture: main_gl_texture,
|
|
fps_compute_image: fps_compute_image,
|
|
fps_gl_texture: fps_gl_texture,
|
|
|
|
query: query,
|
|
|
|
shading_language: shading_language,
|
|
}
|
|
}
|
|
|
|
fn redraw(&self,
|
|
point_size: f32,
|
|
font: &Font,
|
|
outlines: &Outlines,
|
|
glyph_indices: &[u16],
|
|
glyph_count: usize,
|
|
glyph_positions: &[GlyphPos],
|
|
device_pixel_size: &Size2D<u32>,
|
|
translation: &Point2D<i32>)
|
|
-> DrawAtlasProfilingEvents {
|
|
let shelf_height = font.shelf_height(point_size);
|
|
let mut atlas_builder = AtlasBuilder::new(ATLAS_SIZE, shelf_height);
|
|
for glyph_index in 0..(glyph_count as u16) {
|
|
atlas_builder.pack_glyph(&outlines, glyph_index, point_size).unwrap()
|
|
}
|
|
|
|
let atlas = atlas_builder.create_atlas().unwrap();
|
|
|
|
let rect = Rect::new(Point2D::new(0, 0), self.atlas_size);
|
|
|
|
let events = self.rasterizer.draw_atlas(&self.main_compute_image,
|
|
&rect,
|
|
&atlas,
|
|
outlines,
|
|
&self.main_coverage_buffer).unwrap();
|
|
self.rasterizer.queue().flush().unwrap();
|
|
|
|
unsafe {
|
|
if self.shading_language == ShadingLanguage::Glsl {
|
|
gl::MemoryBarrier(gl::SHADER_IMAGE_ACCESS_BARRIER_BIT |
|
|
gl::TEXTURE_FETCH_BARRIER_BIT);
|
|
}
|
|
|
|
gl::Viewport(0,
|
|
0,
|
|
device_pixel_size.width as GLint,
|
|
device_pixel_size.height as GLint);
|
|
gl::ClearColor(1.0, 1.0, 1.0, 1.0);
|
|
gl::Clear(gl::COLOR_BUFFER_BIT);
|
|
}
|
|
|
|
self.draw_glyphs(&font,
|
|
&atlas,
|
|
outlines,
|
|
&self.main_composite_vertex_array,
|
|
glyph_indices,
|
|
glyph_positions,
|
|
device_pixel_size,
|
|
translation,
|
|
self.main_gl_texture,
|
|
point_size,
|
|
&TEXT_COLOR);
|
|
|
|
events
|
|
}
|
|
|
|
fn get_timing_in_ms(&self) -> f64 {
|
|
unsafe {
|
|
let mut result = 0;
|
|
gl::GetQueryObjectui64v(self.query, gl::QUERY_RESULT, &mut result);
|
|
(result as f64) / (1_000_000.0)
|
|
}
|
|
}
|
|
|
|
fn draw_glyphs(&self,
|
|
font: &Font,
|
|
atlas: &Atlas,
|
|
outlines: &Outlines,
|
|
vertex_array: &CompositeVertexArray,
|
|
glyph_indices: &[u16],
|
|
glyph_positions: &[GlyphPos],
|
|
device_pixel_size: &Size2D<u32>,
|
|
translation: &Point2D<i32>,
|
|
texture: GLuint,
|
|
point_size: f32,
|
|
color: &[f32]) {
|
|
unsafe {
|
|
gl::UseProgram(self.composite_program);
|
|
gl::BindVertexArray(vertex_array.vertex_array);
|
|
gl::BindBuffer(gl::ARRAY_BUFFER, vertex_array.vertex_buffer);
|
|
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, vertex_array.index_buffer);
|
|
|
|
let vertex_count = self.upload_quads_for_text(font,
|
|
atlas,
|
|
outlines,
|
|
glyph_indices,
|
|
glyph_positions,
|
|
point_size);
|
|
|
|
gl::ActiveTexture(gl::TEXTURE0);
|
|
gl::BindTexture(gl::TEXTURE_RECTANGLE, texture);
|
|
gl::Uniform1i(self.composite_atlas_uniform, 0);
|
|
|
|
let matrix = [
|
|
2.0 / device_pixel_size.width as f32, 0.0,
|
|
0.0, -2.0 / device_pixel_size.height as f32,
|
|
];
|
|
gl::UniformMatrix2fv(self.composite_transform_uniform, 1, gl::FALSE, matrix.as_ptr());
|
|
|
|
gl::Uniform2f(self.composite_translation_uniform,
|
|
-1.0 + 2.0 * translation.x as f32 / device_pixel_size.width as f32,
|
|
1.0 - 2.0 * translation.y as f32 / device_pixel_size.height as f32);
|
|
|
|
gl::Uniform4fv(self.composite_color_uniform, 1, color.as_ptr());
|
|
|
|
gl::Enable(gl::BLEND);
|
|
gl::BlendEquation(gl::FUNC_ADD);
|
|
gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
|
|
|
|
gl::BeginQuery(gl::TIME_ELAPSED, self.query);
|
|
|
|
gl::DrawElements(gl::TRIANGLES,
|
|
vertex_count as GLsizei,
|
|
gl::UNSIGNED_SHORT,
|
|
0 as *const GLvoid);
|
|
|
|
gl::EndQuery(gl::TIME_ELAPSED);
|
|
}
|
|
}
|
|
|
|
fn upload_quads_for_text(&self,
|
|
font: &Font,
|
|
atlas: &Atlas,
|
|
outlines: &Outlines,
|
|
glyph_indices: &[u16],
|
|
glyph_positions: &[GlyphPos],
|
|
point_size: f32)
|
|
-> usize {
|
|
let pixels_per_unit = point_size as f32 / font.units_per_em() as f32;
|
|
|
|
let (mut vertices, mut indices) = (vec![], vec![]);
|
|
for position in glyph_positions {
|
|
let glyph_index = glyph_indices[position.glyph_id as usize];
|
|
let glyph_rect_i = outlines.glyph_pixel_bounds(glyph_index, point_size);
|
|
|
|
let uv_tl: Point2D<u32> = atlas.atlas_origin(glyph_index).floor().cast().unwrap();
|
|
let uv_br = uv_tl + glyph_rect_i.size().cast().unwrap();
|
|
|
|
let bearing_pos = (position.x as f32 * pixels_per_unit).round() as i32;
|
|
let baseline_pos = (position.y as f32 * pixels_per_unit).round() as i32;
|
|
|
|
let left_pos = bearing_pos + glyph_rect_i.left;
|
|
let top_pos = baseline_pos - glyph_rect_i.top;
|
|
let right_pos = bearing_pos + glyph_rect_i.right;
|
|
let bottom_pos = baseline_pos - glyph_rect_i.bottom;
|
|
|
|
let first_index = vertices.len() as u16;
|
|
|
|
vertices.push(Vertex::new(left_pos, top_pos, uv_tl.x, uv_tl.y));
|
|
vertices.push(Vertex::new(right_pos, top_pos, uv_br.x, uv_tl.y));
|
|
vertices.push(Vertex::new(right_pos, bottom_pos, uv_br.x, uv_br.y));
|
|
vertices.push(Vertex::new(left_pos, bottom_pos, uv_tl.x, uv_br.y));
|
|
|
|
indices.extend(RECT_INDICES.iter().map(|index| first_index + index));
|
|
}
|
|
|
|
unsafe {
|
|
gl::BufferData(gl::ARRAY_BUFFER,
|
|
(vertices.len() * mem::size_of::<Vertex>()) as GLsizeiptr,
|
|
vertices.as_ptr() as *const GLvoid,
|
|
gl::STATIC_DRAW);
|
|
gl::BufferData(gl::ELEMENT_ARRAY_BUFFER,
|
|
(indices.len() * mem::size_of::<u16>()) as GLsizeiptr,
|
|
indices.as_ptr() as *const GLvoid,
|
|
gl::STATIC_DRAW);
|
|
}
|
|
|
|
indices.len()
|
|
}
|
|
|
|
fn create_fps_atlas(&self, font: &Font, outlines: &Outlines, glyph_count: usize) -> Atlas {
|
|
let shelf_height = font.shelf_height(FPS_DISPLAY_POINT_SIZE);
|
|
let mut atlas_builder = AtlasBuilder::new(ATLAS_SIZE, shelf_height);
|
|
for glyph_index in 0..(glyph_count as u16) {
|
|
atlas_builder.pack_glyph(&outlines, glyph_index, FPS_DISPLAY_POINT_SIZE).unwrap()
|
|
}
|
|
|
|
let atlas = atlas_builder.create_atlas().unwrap();
|
|
|
|
self.rasterizer.draw_atlas(&self.fps_compute_image,
|
|
&Rect::new(Point2D::new(0, 0), self.atlas_size),
|
|
&atlas,
|
|
outlines,
|
|
&self.fps_coverage_buffer).unwrap();
|
|
|
|
atlas
|
|
}
|
|
|
|
fn draw_fps(&self,
|
|
font: &Font,
|
|
atlas: &Atlas,
|
|
outlines: &Outlines,
|
|
device_pixel_size: &Size2D<u32>,
|
|
glyph_indices: &[u16],
|
|
glyph_mapping: &GlyphMapping,
|
|
draw_time: f64,
|
|
accum_time: f64,
|
|
composite_time: f64,
|
|
glyph_count: usize) {
|
|
// Draw the background color.
|
|
unsafe {
|
|
gl::BindVertexArray(self.solid_color_vertex_array);
|
|
gl::UseProgram(self.solid_color_program);
|
|
gl::BindBuffer(gl::ARRAY_BUFFER, self.solid_color_vertex_buffer);
|
|
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, self.solid_color_index_buffer);
|
|
|
|
let tl = Point2D::new(
|
|
-1.0,
|
|
-1.0 + (FPS_DISPLAY_POINT_SIZE + FPS_PADDING as f32 * 2.0) /
|
|
(device_pixel_size.height as f32) * 2.0);
|
|
let br = Point2D::new(1.0, -1.0);
|
|
|
|
let vertices = [(tl.x, tl.y), (br.x, tl.y), (br.x, br.y), (tl.x, br.y)];
|
|
gl::BufferData(gl::ARRAY_BUFFER,
|
|
(vertices.len() * mem::size_of::<(f32, f32)>()) as GLsizeiptr,
|
|
vertices.as_ptr() as *const GLvoid,
|
|
gl::DYNAMIC_DRAW);
|
|
|
|
gl::Uniform4fv(self.solid_color_color_uniform, 1, FPS_BACKGROUND_COLOR.as_ptr());
|
|
|
|
gl::Enable(gl::BLEND);
|
|
gl::BlendEquation(gl::FUNC_ADD);
|
|
gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
|
|
|
|
gl::DrawElements(gl::TRIANGLES, 6, gl::UNSIGNED_SHORT, 0 as *const GLvoid);
|
|
}
|
|
|
|
let fps_text = format!("draw: {:.3}ms ({:.3}us/glyph), \
|
|
accum: {:.3}ms ({:.3}us/glyph), \
|
|
composite: {:.3}ms ({:.3}us/glyph)",
|
|
draw_time / 1_000_000.0,
|
|
draw_time / (1000.0 * glyph_count as f64),
|
|
accum_time / 1_000_000.0,
|
|
accum_time / (1000.0 * glyph_count as f64),
|
|
composite_time,
|
|
(composite_time * 1000.0) / (glyph_count as f64));
|
|
|
|
let mut fps_glyphs = vec![];
|
|
let mut current_x = 0;
|
|
for glyph_pos in &shaper::shape_text(&font, &glyph_mapping, &fps_text) {
|
|
fps_glyphs.push(GlyphPos {
|
|
x: current_x,
|
|
y: 0,
|
|
glyph_id: glyph_pos.glyph_id,
|
|
});
|
|
current_x += glyph_pos.advance as u32;
|
|
}
|
|
|
|
self.draw_glyphs(font,
|
|
atlas,
|
|
outlines,
|
|
&self.fps_composite_vertex_array,
|
|
glyph_indices,
|
|
&fps_glyphs,
|
|
device_pixel_size,
|
|
&Point2D::new(FPS_PADDING, device_pixel_size.height as i32 - FPS_PADDING),
|
|
self.fps_gl_texture,
|
|
FPS_DISPLAY_POINT_SIZE,
|
|
&FPS_FOREGROUND_COLOR);
|
|
}
|
|
|
|
fn take_screenshot(&self) {
|
|
unsafe {
|
|
let mut fbo = 0;
|
|
gl::GenFramebuffers(1, &mut fbo);
|
|
gl::BindFramebuffer(gl::FRAMEBUFFER, fbo);
|
|
gl::FramebufferTexture2D(gl::FRAMEBUFFER,
|
|
gl::COLOR_ATTACHMENT0,
|
|
gl::TEXTURE_RECTANGLE,
|
|
self.main_gl_texture,
|
|
0);
|
|
|
|
let length = 4 * self.atlas_size.width as usize * self.atlas_size.height as usize;
|
|
let mut pixels: Vec<u8> = vec![0; length];
|
|
gl::ReadPixels(0, 0,
|
|
self.atlas_size.width as GLint, self.atlas_size.height as GLint,
|
|
gl::RGBA,
|
|
gl::UNSIGNED_BYTE,
|
|
pixels.as_mut_ptr() as *mut c_void);
|
|
|
|
gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
|
|
gl::DeleteFramebuffers(1, &mut fbo);
|
|
|
|
image::save_buffer(&Path::new(ATLAS_DUMP_FILENAME),
|
|
&pixels,
|
|
self.atlas_size.width,
|
|
self.atlas_size.height,
|
|
image::RGBA(8)).unwrap();
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
#[repr(C)]
|
|
struct Vertex {
|
|
x: i32,
|
|
y: i32,
|
|
u: u32,
|
|
v: u32,
|
|
}
|
|
|
|
impl Vertex {
|
|
fn new(x: i32, y: i32, u: u32, v: u32) -> Vertex {
|
|
Vertex {
|
|
x: x,
|
|
y: y,
|
|
u: u,
|
|
v: v,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
struct GlyphPos {
|
|
x: u32,
|
|
y: u32,
|
|
glyph_id: u16,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct CompositeVertexArray {
|
|
vertex_array: GLuint,
|
|
vertex_buffer: GLuint,
|
|
index_buffer: GLuint,
|
|
}
|
|
|
|
impl CompositeVertexArray {
|
|
fn new() -> CompositeVertexArray {
|
|
let (mut vertex_array, mut vertex_buffer, mut index_buffer) = (0, 0, 0);
|
|
|
|
unsafe {
|
|
gl::GenVertexArrays(1, &mut vertex_array);
|
|
gl::GenBuffers(1, &mut vertex_buffer);
|
|
gl::GenBuffers(1, &mut index_buffer);
|
|
}
|
|
|
|
CompositeVertexArray {
|
|
vertex_array: vertex_array,
|
|
vertex_buffer: vertex_buffer,
|
|
index_buffer: index_buffer,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn create_program(vertex_shader_source: &str, fragment_shader_source: &str) -> GLuint {
|
|
unsafe {
|
|
let vertex_shader = gl::CreateShader(gl::VERTEX_SHADER);
|
|
let fragment_shader = gl::CreateShader(gl::FRAGMENT_SHADER);
|
|
gl::ShaderSource(vertex_shader,
|
|
1,
|
|
&(vertex_shader_source.as_ptr() as *const u8 as *const GLchar),
|
|
&(vertex_shader_source.len() as GLint));
|
|
gl::ShaderSource(fragment_shader,
|
|
1,
|
|
&(fragment_shader_source.as_ptr() as *const u8 as *const GLchar),
|
|
&(fragment_shader_source.len() as GLint));
|
|
gl::CompileShader(vertex_shader);
|
|
gl::CompileShader(fragment_shader);
|
|
|
|
let program = gl::CreateProgram();
|
|
gl::AttachShader(program, vertex_shader);
|
|
gl::AttachShader(program, fragment_shader);
|
|
gl::LinkProgram(program);
|
|
program
|
|
}
|
|
}
|
|
|
|
fn create_image(rasterizer: &Rasterizer, atlas_size: &Size2D<u32>) -> (Image, GLuint) {
|
|
let compute_image = rasterizer.device().create_image(Format::R8,
|
|
buffer::Protection::ReadWrite,
|
|
&atlas_size).unwrap();
|
|
|
|
let mut gl_texture = 0;
|
|
unsafe {
|
|
gl::GenTextures(1, &mut gl_texture);
|
|
compute_image.bind_to(&ExternalImage::GlTexture(gl_texture)).unwrap();
|
|
|
|
gl::BindTexture(gl::TEXTURE_RECTANGLE, gl_texture);
|
|
gl::TexParameteri(gl::TEXTURE_RECTANGLE, gl::TEXTURE_MIN_FILTER, gl::LINEAR as GLint);
|
|
gl::TexParameteri(gl::TEXTURE_RECTANGLE, gl::TEXTURE_MAG_FILTER, gl::LINEAR as GLint);
|
|
gl::TexParameteri(gl::TEXTURE_RECTANGLE, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as GLint);
|
|
gl::TexParameteri(gl::TEXTURE_RECTANGLE, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as GLint);
|
|
}
|
|
|
|
(compute_image, gl_texture)
|
|
}
|
|
|
|
static COMPOSITE_VERTEX_SHADER: &'static str = "\
|
|
#version 330
|
|
|
|
uniform mat2 uTransform;
|
|
uniform vec2 uTranslation;
|
|
|
|
in vec2 aPosition;
|
|
in vec2 aTexCoord;
|
|
|
|
out vec2 vTexCoord;
|
|
|
|
void main() {
|
|
vTexCoord = aTexCoord;
|
|
gl_Position = vec4(uTransform * aPosition + uTranslation, 0.0f, 1.0f);
|
|
}
|
|
";
|
|
|
|
static COMPOSITE_FRAGMENT_SHADER: &'static str = "\
|
|
#version 330
|
|
|
|
uniform sampler2DRect uAtlas;
|
|
uniform vec4 uColor;
|
|
|
|
in vec2 vTexCoord;
|
|
|
|
out vec4 oFragColor;
|
|
|
|
void main() {
|
|
float value = texture(uAtlas, vTexCoord).r;
|
|
oFragColor = vec4(uColor.rgb, uColor.a * value);
|
|
}
|
|
";
|
|
|
|
static SOLID_COLOR_VERTEX_SHADER: &'static str = "\
|
|
#version 330
|
|
|
|
in vec2 aPosition;
|
|
|
|
void main() {
|
|
gl_Position = vec4(aPosition, 0.0f, 1.0f);
|
|
}
|
|
";
|
|
|
|
static SOLID_COLOR_FRAGMENT_SHADER: &'static str = "\
|
|
#version 330
|
|
|
|
uniform vec4 uColor;
|
|
|
|
out vec4 oFragColor;
|
|
|
|
void main() {
|
|
oFragColor = uColor;
|
|
}
|
|
";
|
|
|
|
static RECT_INDICES: [u16; 6] = [0, 1, 3, 1, 2, 3];
|
|
|
|
static TEXT: &'static str = "\
|
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur scelerisque pellentesque risus \
|
|
quis vehicula. Ut sollicitudin aliquet diam, vel lobortis orci porta in. Sed eu nisi egestas odio \
|
|
tincidunt cursus eget ut lorem. Fusce lacinia ex nec lectus rutrum mollis. Donec in ultrices \
|
|
purus. Integer id suscipit magna. Suspendisse congue pulvinar neque id ultrices. Curabitur nec \
|
|
tellus et est pellentesque posuere. Duis ut metus euismod, feugiat arcu vitae, posuere libero. \
|
|
Curabitur nunc urna, rhoncus vitae scelerisque quis, viverra et odio. Suspendisse accumsan \
|
|
pretium mi, nec fringilla metus condimentum id. Duis dignissim quam eu felis lobortis, eget \
|
|
dignissim lectus fermentum. Nunc et massa id orci pellentesque rutrum. Nam imperdiet quam vel \
|
|
ligula efficitur ultricies vel eu tellus. Maecenas luctus risus a erat euismod ultricies. \
|
|
Pellentesque neque mauris, laoreet vitae finibus quis, molestie ut velit. Donec laoreet justo \
|
|
risus. In id mi sed odio placerat interdum ut vitae erat. Fusce quis mollis mauris, sit amet \
|
|
efficitur libero. In efficitur tortor nulla, sollicitudin sodales mi tempor in. In egestas \
|
|
ultrices fermentum. Quisque mattis egestas nulla. Interdum et malesuada fames ac ante ipsum \
|
|
primis in faucibus. Etiam in tempus sapien, in dignissim arcu. Quisque diam nulla, rhoncus et \
|
|
tempor nec, facilisis porta purus. Nulla ut eros laoreet, placerat dolor ut, interdum orci. Sed \
|
|
posuere eleifend mollis. Integer at nunc ex. Vestibulum aliquet risus quis lacinia convallis. \
|
|
Fusce et metus viverra, varius nulla in, rutrum justo. Interdum et malesuada fames ac ante ipsum \
|
|
primis in faucibus. Praesent non est vel lectus suscipit malesuada id ut nisl. Aenean sem ipsum, \
|
|
tincidunt non orci non, varius consectetur purus. Aenean sed mollis turpis, sit amet vestibulum \
|
|
risus. Nunc ut hendrerit urna, sit amet lacinia arcu. Curabitur laoreet a enim et eleifend. Etiam \
|
|
consectetur pharetra massa, sed elementum quam molestie nec. Integer eu justo lectus. Vestibulum \
|
|
sed vulputate sapien. Curabitur pretium luctus orci et interdum. Quisque ligula nisi, varius id \
|
|
sodales id, volutpat et lorem. Pellentesque ex urna, malesuada at ex non, elementum ultricies \
|
|
nulla. Nunc sodales, turpis at maximus bibendum, neque lorem laoreet felis, eget convallis sem \
|
|
mauris ac quam. Mauris non pretium nulla. Nam semper pulvinar convallis. Suspendisse ultricies \
|
|
odio vitae tortor congue, rutrum finibus nisl malesuada. Interdum et malesuada fames ac ante \
|
|
ipsum primis in faucibus. Vestibulum aliquam et lacus sit amet lobortis. In sed ligula quis urna \
|
|
accumsan vehicula sit amet id magna. Cras mollis orci vitae turpis porta, sed gravida nunc \
|
|
aliquam. Phasellus nec facilisis nunc. Suspendisse volutpat leo felis, in iaculis nisi dignissim \
|
|
et. Phasellus at urna purus. Nullam vitae metus ante. Praesent porttitor libero quis velit \
|
|
fermentum rhoncus. Cras vitae rhoncus nulla. In efficitur risus sapien, sed viverra neque \
|
|
scelerisque at. Morbi fringilla odio massa. Donec tincidunt magna diam, eget congue leo tristique \
|
|
eget. Cras et sapien nulla.";
|
|
|