Initial experimental support for subpixel antialiasing

This commit is contained in:
Patrick Walton 2017-02-22 16:31:44 -08:00
parent 5e9b8c9423
commit b86133a4ef
11 changed files with 425 additions and 112 deletions

View File

@ -19,9 +19,9 @@ use euclid::{Point2D, Rect, Size2D};
use gl::types::GLuint;
use glfw::{Context, OpenGlProfileHint, WindowHint, WindowMode};
use memmap::{Mmap, Protection};
use pathfinder::atlas::AtlasBuilder;
use pathfinder::atlas::{AtlasBuilder, AtlasOptions, GlyphRasterizationOptions};
use pathfinder::charmap::CodepointRange;
use pathfinder::coverage::CoverageBuffer;
use pathfinder::coverage::{CoverageBuffer, CoverageBufferOptions};
use pathfinder::font::Font;
use pathfinder::outline::OutlineBuilder;
use pathfinder::rasterizer::{Rasterizer, RasterizerOptions};
@ -88,11 +88,18 @@ fn main() {
}
outlines = outline_builder.create_buffers().unwrap();
let mut atlas_builder = AtlasBuilder::new(device_pixel_width as GLuint,
shelf_height);
let mut atlas_builder = AtlasBuilder::new(&AtlasOptions {
available_width: device_pixel_width as GLuint,
shelf_height: shelf_height,
..AtlasOptions::default()
});
for glyph_index in 0..(glyph_count as u16) {
atlas_builder.pack_glyph(&outlines, glyph_index, point_size as f32, 0.0)
.unwrap();
atlas_builder.pack_glyph(&outlines,
glyph_index,
&GlyphRasterizationOptions {
point_size: point_size as f32,
..GlyphRasterizationOptions::default()
}).unwrap();
}
atlas = atlas_builder.create_atlas().unwrap();
}
@ -110,7 +117,11 @@ fn main() {
println!("cpu,{}", time_per_glyph);
let atlas_size = Size2D::new(ATLAS_SIZE, ATLAS_SIZE);
let coverage_buffer = CoverageBuffer::new(rasterizer.device(), &atlas_size).unwrap();
let coverage_buffer = CoverageBuffer::new(rasterizer.device(),
&CoverageBufferOptions {
size: atlas_size,
..CoverageBufferOptions::default()
}).unwrap();
let image = rasterizer.device()
.create_image(Format::R8, buffer::Protection::WriteOnly, &atlas_size)

View File

@ -18,9 +18,9 @@ use euclid::{Point2D, Rect, Size2D};
use gl::types::{GLint, GLuint};
use glfw::{Action, Context, Key, OpenGlProfileHint, WindowEvent, WindowHint, WindowMode};
use memmap::{Mmap, Protection};
use pathfinder::atlas::AtlasBuilder;
use pathfinder::atlas::{AtlasBuilder, AtlasOptions, GlyphRasterizationOptions};
use pathfinder::charmap::CodepointRange;
use pathfinder::coverage::CoverageBuffer;
use pathfinder::coverage::{CoverageBuffer, CoverageBufferOptions};
use pathfinder::font::Font;
use pathfinder::outline::OutlineBuilder;
use pathfinder::rasterizer::{Rasterizer, RasterizerOptions};
@ -39,12 +39,17 @@ fn main() {
.long("index")
.help("Select an index within a font collection")
.takes_value(true);
let subpixel_antialiasing_arg =
Arg::with_name("subpixel-aa").short("s")
.long("subpixel-aa")
.help("Enable subpixel antialiasing");
let font_arg = Arg::with_name("FONT-FILE").help("Select the font file (`.ttf`, `.otf`, etc.)")
.required(true)
.index(1);
let point_size_arg = Arg::with_name("POINT-SIZE").help("Select the point size")
.index(2);
let matches = App::new("generate-atlas").arg(index_arg)
.arg(subpixel_antialiasing_arg)
.arg(font_arg)
.arg(point_size_arg)
.get_matches();
@ -84,6 +89,8 @@ fn main() {
None => 0,
};
let subpixel_aa = matches.is_present("subpixel-aa");
let (outlines, atlas);
unsafe {
let font = Font::from_collection_index(file.as_slice(), font_index, &mut buffer).unwrap();
@ -100,15 +107,29 @@ fn main() {
}
outlines = outline_builder.create_buffers().unwrap();
let mut atlas_builder = AtlasBuilder::new(device_pixel_width as GLuint, shelf_height);
let mut atlas_builder = AtlasBuilder::new(&AtlasOptions {
available_width: device_pixel_width as u32,
shelf_height: shelf_height,
subpixel_antialiasing: subpixel_aa,
});
for glyph_index in 0..glyph_count {
atlas_builder.pack_glyph(&outlines, glyph_index, point_size, 0.0).unwrap();
atlas_builder.pack_glyph(&outlines,
glyph_index,
&GlyphRasterizationOptions {
point_size: point_size,
..GlyphRasterizationOptions::default()
}).unwrap();
}
atlas = atlas_builder.create_atlas().unwrap();
}
let atlas_size = Size2D::new(device_pixel_width as GLuint, device_pixel_height as GLuint);
let coverage_buffer = CoverageBuffer::new(rasterizer.device(), &atlas_size).unwrap();
let coverage_buffer = CoverageBuffer::new(rasterizer.device(),
&CoverageBufferOptions {
size: atlas_size,
subpixel_antialiasing: subpixel_aa,
..CoverageBufferOptions::default()
}).unwrap();
let image = rasterizer.device()
.create_image(Format::RGBA8, buffer::Protection::ReadWrite, &atlas_size)

View File

@ -19,9 +19,9 @@ 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::AtlasBuilder;
use pathfinder::atlas::{AtlasBuilder, AtlasOptions, GlyphRasterizationOptions};
use pathfinder::charmap::CodepointRanges;
use pathfinder::coverage::CoverageBuffer;
use pathfinder::coverage::{CoverageBuffer, CoverageBufferOptions};
use pathfinder::error::RasterError;
use pathfinder::font::Font;
use pathfinder::rasterizer::{DrawAtlasProfilingEvents, Rasterizer, RasterizerOptions};
@ -52,9 +52,10 @@ const FPS_PADDING: i32 = 6;
static SHADER_PATH: &'static str = "resources/shaders/";
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 FPS_BACKGROUND_COLOR: [f32; 3] = [0.3, 0.3, 0.3];
static FPS_FOREGROUND_COLOR: [f32; 3] = [1.0, 1.0, 1.0];
static BACKGROUND_COLOR: [f32; 3] = [1.0, 1.0, 1.0];
static TEXT_COLOR: [f32; 3] = [0.0, 0.0, 0.0];
static ATLAS_DUMP_FILENAME: &'static str = "lorem-ipsum-atlas.png";
@ -63,12 +64,20 @@ fn main() {
.long("index")
.help("Select an index within a font collection")
.takes_value(true);
let subpixel_antialiasing_arg =
Arg::with_name("subpixel-aa").short("s")
.long("subpixel-aa")
.help("Enable subpixel antialiasing");
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 matches = App::new("lorem-ipsum").arg(index_arg)
.arg(subpixel_antialiasing_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));
@ -101,6 +110,8 @@ fn main() {
None => 0,
};
let subpixel_aa = matches.is_present("subpixel-aa");
let file = Mmap::open_path(matches.value_of("FONT-FILE").unwrap(), Protection::Read).unwrap();
let mut buffer = vec![];
let font = unsafe {
@ -112,7 +123,7 @@ fn main() {
let mut typesetter = Typesetter::new(page_width, &font, units_per_em);
typesetter.add_text(&font, font.units_per_em() as f32, &text);
let renderer = Renderer::new();
let renderer = Renderer::new(subpixel_aa);
let mut point_size = INITIAL_POINT_SIZE;
let mut translation = Point2D::new(0, 0);
let mut dirty = true;
@ -229,7 +240,8 @@ struct Renderer {
composite_atlas_uniform: GLint,
composite_transform_uniform: GLint,
composite_translation_uniform: GLint,
composite_color_uniform: GLint,
composite_foreground_color_uniform: GLint,
composite_background_color_uniform: GLint,
main_composite_vertex_array: CompositeVertexArray,
fps_composite_vertex_array: CompositeVertexArray,
@ -253,10 +265,12 @@ struct Renderer {
query: GLuint,
shading_language: ShadingLanguage,
subpixel_aa: bool,
}
impl Renderer {
fn new() -> Renderer {
fn new(subpixel_aa: bool) -> Renderer {
let instance = Instance::new().unwrap();
let device = instance.open_device().unwrap();
let queue = device.create_queue().unwrap();
@ -270,7 +284,8 @@ impl Renderer {
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 (composite_translation_uniform, composite_foreground_color_uniform);
let composite_background_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);
@ -289,8 +304,12 @@ impl Renderer {
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);
composite_foreground_color_uniform =
gl::GetUniformLocation(composite_program,
"uForegroundColor\0".as_ptr() as *const GLchar);
composite_background_color_uniform =
gl::GetUniformLocation(composite_program,
"uBackgroundColor\0".as_ptr() as *const GLchar);
solid_color_program = create_program(SOLID_COLOR_VERTEX_SHADER,
SOLID_COLOR_FRAGMENT_SHADER);
@ -353,9 +372,16 @@ impl Renderer {
// FIXME(pcwalton)
let atlas_size = Size2D::new(ATLAS_SIZE, ATLAS_SIZE);
let coverage_buffer_options = CoverageBufferOptions {
size: atlas_size,
subpixel_antialiasing: subpixel_aa,
..CoverageBufferOptions::default()
};
let main_coverage_buffer = CoverageBuffer::new(rasterizer.device(), &atlas_size).unwrap();
let fps_coverage_buffer = CoverageBuffer::new(rasterizer.device(), &atlas_size).unwrap();
let main_coverage_buffer = CoverageBuffer::new(rasterizer.device(),
&coverage_buffer_options).unwrap();
let fps_coverage_buffer = CoverageBuffer::new(rasterizer.device(),
&coverage_buffer_options).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);
@ -374,7 +400,8 @@ impl Renderer {
composite_atlas_uniform: composite_atlas_uniform,
composite_transform_uniform: composite_transform_uniform,
composite_translation_uniform: composite_translation_uniform,
composite_color_uniform: composite_color_uniform,
composite_foreground_color_uniform: composite_foreground_color_uniform,
composite_background_color_uniform: composite_background_color_uniform,
main_composite_vertex_array: main_composite_vertex_array,
fps_composite_vertex_array: fps_composite_vertex_array,
@ -398,6 +425,8 @@ impl Renderer {
query: query,
shading_language: shading_language,
subpixel_aa: subpixel_aa,
}
}
@ -410,7 +439,14 @@ impl Renderer {
translation: &Point2D<i32>)
-> RedrawResult {
let shelf_height = font.shelf_height(point_size);
let mut atlas_builder = AtlasBuilder::new(ATLAS_SIZE, shelf_height);
let atlas_options = AtlasOptions {
available_width: ATLAS_SIZE,
shelf_height: shelf_height,
subpixel_antialiasing: self.subpixel_aa,
..AtlasOptions::default()
};
let mut atlas_builder = AtlasBuilder::new(&atlas_options);
let (positioned_glyphs, cached_glyphs) = self.determine_visible_glyphs(&mut atlas_builder,
font,
@ -445,7 +481,7 @@ impl Renderer {
0,
device_pixel_size.width as GLint,
device_pixel_size.height as GLint);
gl::ClearColor(1.0, 1.0, 1.0, 1.0);
gl::ClearColor(BACKGROUND_COLOR[0], BACKGROUND_COLOR[1], BACKGROUND_COLOR[2], 1.0);
gl::Clear(gl::COLOR_BUFFER_BIT);
}
@ -458,7 +494,8 @@ impl Renderer {
translation,
self.main_gl_texture,
point_size,
&TEXT_COLOR)
&TEXT_COLOR,
&BACKGROUND_COLOR)
}
RedrawResult {
@ -497,8 +534,11 @@ impl Renderer {
let subpixel_offset = (subpixel as f32) / (SUBPIXEL_GRANULARITY as f32);
let origin = atlas_builder.pack_glyph(&glyph_store.outlines,
glyph_index,
point_size,
subpixel_offset).unwrap();
&GlyphRasterizationOptions {
point_size: point_size,
horizontal_offset: subpixel_offset,
..GlyphRasterizationOptions::default()
}).unwrap();
CachedGlyph {
x: origin.x,
y: origin.y,
@ -527,7 +567,8 @@ impl Renderer {
translation: &Point2D<i32>,
texture: GLuint,
point_size: f32,
color: &[f32]) {
foreground_color: &[f32],
background_color: &[f32]) {
unsafe {
gl::UseProgram(self.composite_program);
gl::BindVertexArray(vertex_array.vertex_array);
@ -553,7 +594,8 @@ impl Renderer {
-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::Uniform3fv(self.composite_foreground_color_uniform, 1, foreground_color.as_ptr());
gl::Uniform3fv(self.composite_background_color_uniform, 1, background_color.as_ptr());
gl::Enable(gl::BLEND);
gl::BlendEquation(gl::FUNC_ADD);
@ -651,7 +693,7 @@ impl Renderer {
vertices.as_ptr() as *const GLvoid,
gl::DYNAMIC_DRAW);
gl::Uniform4fv(self.solid_color_color_uniform, 1, FPS_BACKGROUND_COLOR.as_ptr());
gl::Uniform3fv(self.solid_color_color_uniform, 1, FPS_BACKGROUND_COLOR.as_ptr());
gl::Enable(gl::BLEND);
gl::BlendEquation(gl::FUNC_ADD);
@ -675,16 +717,28 @@ impl Renderer {
fps_typesetter.add_text(&font, font.units_per_em() as f32, &fps_text);
let shelf_height = font.shelf_height(FPS_DISPLAY_POINT_SIZE);
let mut fps_atlas_builder = AtlasBuilder::new(ATLAS_SIZE, shelf_height);
let atlas_options = AtlasOptions {
available_width: ATLAS_SIZE,
shelf_height: shelf_height,
subpixel_antialiasing: self.subpixel_aa,
..AtlasOptions::default()
};
let mut fps_atlas_builder = AtlasBuilder::new(&atlas_options);
let mut fps_glyphs = vec![];
for &fps_glyph_index in &fps_glyph_store.all_glyph_indices {
for subpixel in 0..SUBPIXEL_GRANULARITY_COUNT {
let subpixel_increment = SUBPIXEL_GRANULARITY * subpixel as f32;
let options = GlyphRasterizationOptions {
point_size: FPS_DISPLAY_POINT_SIZE,
horizontal_offset: subpixel_increment,
..GlyphRasterizationOptions::default()
};
let origin = fps_atlas_builder.pack_glyph(&fps_glyph_store.outlines,
fps_glyph_index,
FPS_DISPLAY_POINT_SIZE,
subpixel_increment).unwrap();
&options).unwrap();
fps_glyphs.push(CachedGlyph {
x: origin.x,
y: origin.y,
@ -728,7 +782,8 @@ impl Renderer {
&Point2D::new(fps_left, fps_top),
self.fps_gl_texture,
FPS_DISPLAY_POINT_SIZE,
&FPS_FOREGROUND_COLOR);
&FPS_FOREGROUND_COLOR,
&FPS_BACKGROUND_COLOR);
}
fn take_screenshot(&self) {
@ -839,7 +894,7 @@ fn create_program(vertex_shader_source: &str, fragment_shader_source: &str) -> G
}
fn create_image(rasterizer: &Rasterizer, atlas_size: &Size2D<u32>) -> (Image, GLuint) {
let compute_image = rasterizer.device().create_image(Format::R8,
let compute_image = rasterizer.device().create_image(Format::RGBA8,
buffer::Protection::ReadWrite,
&atlas_size).unwrap();
@ -886,15 +941,17 @@ static COMPOSITE_FRAGMENT_SHADER: &'static str = "\
#version 330
uniform sampler2DRect uAtlas;
uniform vec4 uColor;
uniform vec3 uForegroundColor;
uniform vec3 uBackgroundColor;
in vec2 vTexCoord;
out vec4 oFragColor;
void main() {
float value = texture(uAtlas, vTexCoord).r;
oFragColor = vec4(uColor.rgb, uColor.a * value);
vec3 value = texture(uAtlas, vTexCoord).rgb;
vec3 color = mix(uBackgroundColor, uForegroundColor, value);
oFragColor = vec4(color, 1.0f);
}
";
@ -911,12 +968,12 @@ void main() {
static SOLID_COLOR_FRAGMENT_SHADER: &'static str = "\
#version 330
uniform vec4 uColor;
uniform vec3 uColor;
out vec4 oFragColor;
void main() {
oFragColor = uColor;
oFragColor = vec4(uColor, 1.0f);
}
";

View File

@ -0,0 +1,26 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// Common functions for all accumulation operations.
// Determines the boundaries of the column we'll be traversing.
void get_location(__private uint *lColumn,
__private uint *lFirstRow,
__private uint *lLastRow,
uint4 kAtlasRect,
uint kAtlasShelfHeight) {
uint atlasWidth = kAtlasRect.z - kAtlasRect.x, atlasHeight = kAtlasRect.w - kAtlasRect.y;
uint shelfIndex = get_global_id(0) / atlasWidth;
*lColumn = get_global_id(0) % atlasWidth;
*lFirstRow = min(shelfIndex * kAtlasShelfHeight, atlasHeight);
*lLastRow = min((shelfIndex + 1) * kAtlasShelfHeight, atlasHeight);
}

View File

@ -0,0 +1,14 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// Placeholder file.
//
// TODO(pcwalton): Fill this in.

View File

@ -8,7 +8,7 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// Computes total coverage and writes into the output atlas.
// Computes total coverage and writes into the output atlas for grayscale antialiasing.
//
// This proceeds top to bottom for better data locality. For details on the algorithm, see [1].
//
@ -16,15 +16,13 @@
const sampler_t SAMPLER = CLK_NORMALIZED_COORDS_FALSE | CLK_ADDRESS_NONE | CLK_FILTER_NEAREST;
__kernel void accum(__write_only image2d_t gImage,
__read_only image2d_t gCoverage,
uint4 kAtlasRect,
uint kAtlasShelfHeight) {
__kernel void accum_gray(__write_only image2d_t gImage,
__read_only image2d_t gCoverage,
uint4 kAtlasRect,
uint kAtlasShelfHeight) {
// Determine the boundaries of the column we'll be traversing.
uint atlasWidth = kAtlasRect.z - kAtlasRect.x, atlasHeight = kAtlasRect.w - kAtlasRect.y;
uint column = get_global_id(0) % atlasWidth, shelfIndex = get_global_id(0) / atlasWidth;
uint firstRow = min(shelfIndex * kAtlasShelfHeight, atlasHeight);
uint lastRow = min((shelfIndex + 1) * kAtlasShelfHeight, atlasHeight);
uint column = 0, firstRow = 0, lastRow = 0;
get_location(&column, &firstRow, &lastRow, kAtlasRect, kAtlasShelfHeight);
// Sweep down the column, accumulating coverage as we go.
float coverage = 0.0f;

View File

@ -0,0 +1,40 @@
// Copyright 2017 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// Computes total coverage and writes into the output atlas for subpixel antialiasing.
//
// This proceeds top to bottom for better data locality. For details on the algorithm, see [1].
//
// [1]: https://medium.com/@raphlinus/inside-the-fastest-font-renderer-in-the-world-75ae5270c445
const sampler_t SAMPLER = CLK_NORMALIZED_COORDS_FALSE | CLK_ADDRESS_NONE | CLK_FILTER_NEAREST;
__kernel void accum_subpixel(__write_only image2d_t gImage,
__read_only image2d_t gCoverage,
uint4 kAtlasRect,
uint kAtlasShelfHeight) {
// Determine the boundaries of the column we'll be traversing.
uint column = 0, firstRow = 0, lastRow = 0;
get_location(&column, &firstRow, &lastRow, kAtlasRect, kAtlasShelfHeight);
// Sweep down the column, accumulating coverage as we go.
float3 coverage = (float3)(0.0f, 0.0f, 0.0f);
for (uint row = firstRow; row < lastRow; row++) {
int coverageColumn = (int)(column * 3);
coverage.r += read_imagef(gCoverage, SAMPLER, (int2)(coverageColumn, (int)row)).r;
coverage.g += read_imagef(gCoverage, SAMPLER, (int2)(coverageColumn + 1, (int)row)).r;
coverage.b += read_imagef(gCoverage, SAMPLER, (int2)(coverageColumn + 2, (int)row)).r;
int2 coord = (int2)((int)column, (int)row) + (int2)kAtlasRect.xy;
float3 aa = fabs(coverage);
write_imagef(gImage, coord, (float4)(aa, 1.0f));
}
}

View File

@ -28,6 +28,9 @@ struct GlyphDescriptor {
// The size of the atlas in pixels.
uniform uvec2 uAtlasSize;
// Whether subpixel antialiasing is in use.
uniform bool uSubpixelAA;
layout(std140) uniform ubGlyphDescriptors {
GlyphDescriptor uGlyphs[MAX_GLYPHS];
};
@ -55,6 +58,8 @@ void main() {
float pointSize = IMAGE_DESCRIPTOR_POINT_SIZE(image);
vec2 glyphPxPos = glyphPos * pointSize / GLYPH_DESCRIPTOR_UNITS_PER_EM(glyph);
vec2 atlasPos = glyphPxPos + IMAGE_DESCRIPTOR_ATLAS_POS(image);
if (uSubpixelAA)
atlasPos.x *= 3.0f;
gl_Position = vec4(atlasPos, 0.0f, 1.0f);
}

View File

@ -33,10 +33,13 @@ use std::u16;
pub struct AtlasBuilder {
rect_packer: RectPacker,
batch_builders: Vec<BatchBuilder>,
options: AtlasOptions,
}
impl AtlasBuilder {
/// Constructs a new atlas builder with the given width in pixels and shelf height.
/// Constructs a new atlas builder with the given options.
///
/// At least `available_width` and `shelf_height` must be set in the options.
///
/// The width can be any value at least as large as all glyphs in the font. It is recommended
/// to keep it fairly large in order to make efficient use of the space: 1024 or 2048 is a good
@ -45,11 +48,14 @@ impl AtlasBuilder {
/// The shelf height should be the maximum of all minimum shelf heights for all fonts you wish
/// to render into the atlas. You can retrive the minimum shelf height for a font with the
/// `Font::shelf_height()` method.
///
/// Remember to set the `subpixel_antialiasing` field to true if you're using subpixel AA.
#[inline]
pub fn new(available_width: u32, shelf_height: u32) -> AtlasBuilder {
pub fn new(options: &AtlasOptions) -> AtlasBuilder {
AtlasBuilder {
rect_packer: RectPacker::new(available_width, shelf_height),
rect_packer: RectPacker::new(options.available_width, options.shelf_height),
batch_builders: vec![],
options: *options,
}
}
@ -64,12 +70,11 @@ impl AtlasBuilder {
pub fn pack_glyph(&mut self,
outlines: &Outlines,
glyph_index: u16,
point_size: f32,
horizontal_offset: f32)
options: &GlyphRasterizationOptions)
-> Result<Point2D<f32>, ()> {
let mut subpixel_bounds = outlines.glyph_subpixel_bounds(glyph_index, point_size);
subpixel_bounds.left += horizontal_offset;
subpixel_bounds.right += horizontal_offset;
let mut subpixel_bounds = outlines.glyph_subpixel_bounds(glyph_index, options.point_size);
subpixel_bounds.left += options.horizontal_offset;
subpixel_bounds.right += options.horizontal_offset;
let pixel_bounds = subpixel_bounds.round_out();
let atlas_origin = try!(self.rect_packer.pack(&pixel_bounds.size().cast().unwrap()));
@ -78,8 +83,7 @@ impl AtlasBuilder {
if let Ok(atlas_origin) = batch_builder.add_glyph(outlines,
&atlas_origin,
glyph_index,
point_size,
horizontal_offset) {
options) {
return Ok(atlas_origin)
}
}
@ -88,8 +92,7 @@ impl AtlasBuilder {
let atlas_origin = try!(batch_builder.add_glyph(outlines,
&atlas_origin,
glyph_index,
point_size,
horizontal_offset));
options));
self.batch_builders.push(batch_builder);
Ok(atlas_origin)
}
@ -105,6 +108,7 @@ impl AtlasBuilder {
batches: batches,
shelf_height: self.rect_packer.shelf_height(),
shelf_columns: self.rect_packer.shelf_columns(),
options: self.options,
})
}
}
@ -126,14 +130,14 @@ impl BatchBuilder {
outlines: &Outlines,
atlas_origin: &Point2D<u32>,
glyph_index: u16,
point_size: f32,
horizontal_offset: f32)
options: &GlyphRasterizationOptions)
-> Result<Point2D<f32>, ()> {
// Check to see if we're already rendering this glyph.
let image_index = glyph_index as usize;
if let Some(image_descriptor) = self.image_descriptors.get(image_index) {
if image_descriptor.point_size == point_size &&
self.image_metadata[image_index].horizontal_offset == horizontal_offset {
if image_descriptor.point_size == options.point_size &&
self.image_metadata[image_index]
.horizontal_offset == options.horizontal_offset {
// Glyph is already present.
return Ok(Point2D::new(image_descriptor.atlas_x, image_descriptor.atlas_y))
} else {
@ -142,7 +146,7 @@ impl BatchBuilder {
}
}
let subpixel_bounds = outlines.glyph_subpixel_bounds(glyph_index, point_size);
let subpixel_bounds = outlines.glyph_subpixel_bounds(glyph_index, options.point_size);
let glyph_id = outlines.glyph_id(glyph_index);
while self.image_descriptors.len() < image_index + 1 {
@ -151,7 +155,7 @@ impl BatchBuilder {
}
let units_per_em = outlines.glyph_units_per_em(glyph_index) as f32;
let horizontal_px_offset = horizontal_offset / units_per_em * point_size;
let horizontal_px_offset = options.horizontal_offset / units_per_em * options.point_size;
let atlas_origin = Point2D::new(
atlas_origin.x as f32 + subpixel_bounds.left.fract() + horizontal_px_offset,
@ -159,14 +163,14 @@ impl BatchBuilder {
self.image_descriptors[image_index] = ImageDescriptor {
atlas_x: atlas_origin.x,
atlas_y: atlas_origin.y,
point_size: point_size,
point_size: options.point_size,
pad: 0.0,
};
self.image_metadata[image_index] = ImageMetadata {
glyph_index: glyph_index as u32,
glyph_id: glyph_id,
horizontal_offset: horizontal_offset,
horizontal_offset: options.horizontal_offset,
start_index: outlines.descriptor(glyph_index).unwrap().start_index(),
end_index: match outlines.descriptor(glyph_index + 1) {
Some(descriptor) => descriptor.start_index() as u32,
@ -227,6 +231,7 @@ pub struct Atlas {
batches: Vec<Batch>,
shelf_height: u32,
shelf_columns: u32,
options: AtlasOptions,
}
impl Atlas {
@ -237,6 +242,12 @@ impl Atlas {
}
}
/// Returns the width of this atlas in pixels.
#[inline]
pub fn width(&self) -> u32 {
self.options.available_width
}
/// Returns the height of each shelf.
#[inline]
pub fn shelf_height(&self) -> u32 {
@ -249,6 +260,12 @@ impl Atlas {
self.shelf_columns
}
#[doc(hidden)]
#[inline]
pub fn uses_subpixel_antialiasing(&self) -> bool {
self.options.subpixel_antialiasing
}
/// Returns true if this atlas has no glyphs of nonzero size in it.
#[inline]
pub fn is_empty(&self) -> bool {
@ -285,6 +302,38 @@ impl Batch {
}
}
/// Customizable attributes of the atlas.
#[derive(Clone, Copy, Debug, Default)]
pub struct AtlasOptions {
/// The width of the atlas.
///
/// This width can be any value at least as large as all glyphs in the font. It is recommended
/// to keep it fairly large in order to make efficient use of the space: 1024 or 2048 is a good
/// choice on modern GPUs.
pub available_width: u32,
/// The height of each shelf in the atlas.
///
/// The shelf height should be the maximum of all minimum shelf heights for all fonts you wish
/// to render into the atlas. You can retrive the minimum shelf height for a font with the
/// `Font::shelf_height()` method.
pub shelf_height: u32,
/// Whether subpixel antialiasing should be used.
pub subpixel_antialiasing: bool,
}
/// Options that control how a glyph is rasterized.
#[derive(Clone, Copy, Debug, Default)]
pub struct GlyphRasterizationOptions {
/// The point size.
pub point_size: f32,
/// How much space we should leave on the left side of the glyph, in pixels.
///
/// This is useful for rendering at a subpixel offset.
pub horizontal_offset: f32,
}
// Information about each image that we send to the GPU.
#[repr(C)]
#[doc(hidden)]

View File

@ -18,6 +18,8 @@ use euclid::size::Size2D;
use gl::types::{GLint, GLuint};
use gl;
const DEFAULT_COVERAGE_BUFFER_SIZE: u32 = 1024;
/// An intermediate surface on the GPU used during the rasterization process.
///
/// You can reuse this surface from draw operation to draw operation. It only needs to be at least
@ -30,11 +32,15 @@ pub struct CoverageBuffer {
}
impl CoverageBuffer {
/// Creates a new coverage buffer of the given size.
///
/// The size must be at least as large as every atlas you will render with it.
pub fn new(device: &Device, size: &Size2D<u32>) -> Result<CoverageBuffer, InitError> {
let image = try!(device.create_image(Format::R32F, Protection::ReadWrite, size)
/// Creates a new coverage buffer with the given options.
pub fn new(device: &Device, options: &CoverageBufferOptions)
-> Result<CoverageBuffer, InitError> {
let mut size = options.size;
if options.subpixel_antialiasing {
size.width *= 3
}
let image = try!(device.create_image(Format::R32F, Protection::ReadWrite, &size)
.map_err(InitError::ComputeError));
let mut framebuffer = 0;
@ -100,3 +106,28 @@ impl Drop for CoverageBuffer {
}
}
/// Options that control the format of the coverage buffer.
#[derive(Clone, Copy, Debug)]
pub struct CoverageBufferOptions {
/// The size of the coverage buffer.
///
/// The size must be at least as large as every atlas you will render with the buffer.
pub size: Size2D<u32>,
/// Whether this coverage buffer is intended for subpixel antialiasing.
///
/// If this buffer is intended for subpixel AA, all atlas rendered with it must use subpixel
/// AA, and vice versa.
pub subpixel_antialiasing: bool,
}
impl Default for CoverageBufferOptions {
#[inline]
fn default() -> CoverageBufferOptions {
CoverageBufferOptions {
size: Size2D::new(DEFAULT_COVERAGE_BUFFER_SIZE, DEFAULT_COVERAGE_BUFFER_SIZE),
subpixel_antialiasing: false,
}
}
}

View File

@ -33,8 +33,14 @@ use std::ptr;
static COMPUTE_PREAMBLE_FILENAME: &'static str = "preamble.cs.glsl";
static ACCUM_CL_SHADER_FILENAME: &'static str = "accum.cl";
static ACCUM_COMPUTE_SHADER_FILENAME: &'static str = "accum.cs.glsl";
static ACCUM_COMMON_CL_SHADER_FILENAME: &'static str = "accum_common.cl";
static ACCUM_COMMON_COMPUTE_SHADER_FILENAME: &'static str = "accum_common.cs.glsl";
static ACCUM_GRAY_CL_SHADER_FILENAME: &'static str = "accum_gray.cl";
static ACCUM_SUBPIXEL_CL_SHADER_FILENAME: &'static str = "accum_subpixel.cl";
static ACCUM_GRAY_COMPUTE_SHADER_FILENAME: &'static str = "accum.cs.glsl";
static ACCUM_SUBPIXEL_COMPUTE_SHADER_FILENAME: &'static str = "accum.cs.glsl";
static DRAW_VERTEX_SHADER_FILENAME: &'static str = "draw.vs.glsl";
static DRAW_TESS_CONTROL_SHADER_FILENAME: &'static str = "draw.tcs.glsl";
@ -48,12 +54,14 @@ pub struct Rasterizer {
queue: Queue,
shading_language: ShadingLanguage,
draw_program: GLuint,
accum_program_r8: Program,
accum_program_rgba8: Program,
accum_program_gray_r8: Program,
accum_program_gray_rgba8: Program,
accum_program_subpixel_rgba8: Program,
draw_vertex_array: GLuint,
draw_position_attribute: GLint,
draw_glyph_index_attribute: GLint,
draw_atlas_size_uniform: GLint,
draw_subpixel_aa_uniform: GLint,
draw_glyph_descriptors_uniform: GLuint,
draw_image_descriptors_uniform: GLuint,
draw_query: GLuint,
@ -89,7 +97,7 @@ impl Rasterizer {
-> Result<Rasterizer, InitError> {
let (draw_program, draw_position_attribute, draw_glyph_index_attribute);
let (draw_glyph_descriptors_uniform, draw_image_descriptors_uniform);
let draw_atlas_size_uniform;
let (draw_atlas_size_uniform, draw_subpixel_aa_uniform);
let (mut draw_vertex_array, mut draw_query) = (0, 0);
unsafe {
draw_program = gl::CreateProgram();
@ -141,6 +149,8 @@ impl Rasterizer {
draw_atlas_size_uniform =
gl::GetUniformLocation(draw_program, b"uAtlasSize\0".as_ptr() as *const GLchar);
draw_subpixel_aa_uniform =
gl::GetUniformLocation(draw_program, b"uSubpixelAA\0".as_ptr() as *const GLchar);
draw_glyph_descriptors_uniform =
gl::GetUniformBlockIndex(draw_program,
b"ubGlyphDescriptors\0".as_ptr() as *const GLchar);
@ -152,15 +162,36 @@ impl Rasterizer {
}
// FIXME(pcwalton): Don't panic if this fails to compile; just return an error.
let (accum_filename_common, accum_filename_gray, accum_filename_subpixel);
let shading_language = instance.shading_language();
let accum_filename = match shading_language {
ShadingLanguage::Cl => ACCUM_CL_SHADER_FILENAME,
ShadingLanguage::Glsl => ACCUM_COMPUTE_SHADER_FILENAME,
};
match shading_language {
ShadingLanguage::Cl => {
accum_filename_common = ACCUM_COMMON_CL_SHADER_FILENAME;
accum_filename_gray = ACCUM_GRAY_CL_SHADER_FILENAME;
accum_filename_subpixel = ACCUM_SUBPIXEL_CL_SHADER_FILENAME;
}
ShadingLanguage::Glsl => {
accum_filename_common = ACCUM_COMMON_COMPUTE_SHADER_FILENAME;
accum_filename_gray = ACCUM_GRAY_COMPUTE_SHADER_FILENAME;
accum_filename_subpixel = ACCUM_SUBPIXEL_COMPUTE_SHADER_FILENAME;
}
}
let mut accum_path = options.shader_path.to_owned();
accum_path.push(accum_filename);
let mut accum_file = match File::open(&accum_path) {
let mut accum_path_common = options.shader_path.to_owned();
let mut accum_path_gray = accum_path_common.clone();
let mut accum_path_subpixel = accum_path_common.clone();
accum_path_common.push(accum_filename_common);
accum_path_gray.push(accum_filename_gray);
accum_path_subpixel.push(accum_filename_subpixel);
let mut accum_file_common = match File::open(&accum_path_common) {
Err(error) => return Err(InitError::ShaderUnreadable(error)),
Ok(file) => file,
};
let mut accum_file_gray = match File::open(&accum_path_gray) {
Err(error) => return Err(InitError::ShaderUnreadable(error)),
Ok(file) => file,
};
let mut accum_file_subpixel = match File::open(&accum_path_subpixel) {
Err(error) => return Err(InitError::ShaderUnreadable(error)),
Ok(file) => file,
};
@ -183,34 +214,52 @@ impl Rasterizer {
}
}
let mut accum_source = String::new();
if accum_file.read_to_string(&mut accum_source).is_err() {
let mut accum_source_common = String::new();
let mut accum_source_gray = String::new();
let mut accum_source_subpixel = String::new();
if accum_file_common.read_to_string(&mut accum_source_common).is_err() {
return Err(InitError::CompileFailed("Compute shader", "Invalid UTF-8".to_string()))
}
if accum_file_gray.read_to_string(&mut accum_source_gray).is_err() {
return Err(InitError::CompileFailed("Compute shader", "Invalid UTF-8".to_string()))
}
if accum_file_subpixel.read_to_string(&mut accum_source_subpixel).is_err() {
return Err(InitError::CompileFailed("Compute shader", "Invalid UTF-8".to_string()))
}
let accum_source_r8 = format!("{}\n#define IMAGE_FORMAT r8\n{}",
compute_preamble_source,
accum_source);
let accum_source_rgba8 = format!("{}\n#define IMAGE_FORMAT rgba8\n{}",
compute_preamble_source,
accum_source);
let accum_source_gray_r8 = format!("{}\n#define IMAGE_FORMAT r8\n{}\n{}",
compute_preamble_source,
accum_source_common,
accum_source_gray);
let accum_source_gray_rgba8 = format!("{}\n#define IMAGE_FORMAT rgba8\n{}\n{}",
compute_preamble_source,
accum_source_common,
accum_source_gray);
let accum_source_subpixel_rgba8 = format!("{}\n#define IMAGE_FORMAT rgba8\n{}\n{}",
compute_preamble_source,
accum_source_common,
accum_source_subpixel);
let accum_program_r8 = try!(device.create_program(&accum_source_r8)
.map_err(InitError::ComputeError));
let accum_program_rgba8 = try!(device.create_program(&accum_source_rgba8)
.map_err(InitError::ComputeError));
let accum_program_gray_r8 = try!(device.create_program(&accum_source_gray_r8)
.map_err(InitError::ComputeError));
let accum_program_gray_rgba8 = try!(device.create_program(&accum_source_gray_rgba8)
.map_err(InitError::ComputeError));
let accum_program_subpixel_rgba8 = try!(device.create_program(&accum_source_subpixel_rgba8)
.map_err(InitError::ComputeError));
Ok(Rasterizer {
device: device,
queue: queue,
shading_language: shading_language,
draw_program: draw_program,
accum_program_r8: accum_program_r8,
accum_program_rgba8: accum_program_rgba8,
accum_program_gray_r8: accum_program_gray_r8,
accum_program_gray_rgba8: accum_program_gray_rgba8,
accum_program_subpixel_rgba8: accum_program_subpixel_rgba8,
draw_vertex_array: draw_vertex_array,
draw_position_attribute: draw_position_attribute,
draw_glyph_index_attribute: draw_glyph_index_attribute,
draw_atlas_size_uniform: draw_atlas_size_uniform,
draw_subpixel_aa_uniform: draw_subpixel_aa_uniform,
draw_glyph_descriptors_uniform: draw_glyph_descriptors_uniform,
draw_image_descriptors_uniform: draw_image_descriptors_uniform,
draw_query: draw_query,
@ -230,6 +279,8 @@ impl Rasterizer {
///
/// * `coverage_buffer` is a coverage buffer to use (see `CoverageBuffer`). This can be reused
/// from call to call. It must be at least as large as the atlas.
///
/// Note that, if the atlas is empty, `RasterError::NoGlyphsToDraw` is returned.
pub fn draw_atlas(&self,
image: &Image,
rect: &Rect<u32>,
@ -247,7 +298,14 @@ impl Rasterizer {
// Save the old viewport so we can restore it later.
let mut old_viewport: [GLint; 4] = [0; 4];
gl::GetIntegerv(gl::VIEWPORT, old_viewport.as_mut_ptr());
gl::Viewport(0, 0, rect.size.width as GLint, rect.size.height as GLint);
// Set up our new viewport.
let viewport_width = if atlas.uses_subpixel_antialiasing() {
rect.size.width * 3
} else {
rect.size.width
};
gl::Viewport(0, 0, viewport_width as GLint, rect.size.height as GLint);
// TODO(pcwalton): Scissor to the image rect to clear faster?
gl::ClearColor(0.0, 0.0, 0.0, 1.0);
@ -279,7 +337,9 @@ impl Rasterizer {
gl::UniformBlockBinding(self.draw_program, self.draw_glyph_descriptors_uniform, 1);
gl::UniformBlockBinding(self.draw_program, self.draw_image_descriptors_uniform, 2);
gl::Uniform2ui(self.draw_atlas_size_uniform, rect.size.width, rect.size.height);
gl::Uniform2ui(self.draw_atlas_size_uniform, viewport_width, rect.size.height);
gl::Uniform1i(self.draw_subpixel_aa_uniform,
atlas.uses_subpixel_antialiasing() as GLint);
gl::PatchParameteri(gl::PATCH_VERTICES, 4);
@ -331,11 +391,12 @@ impl Rasterizer {
(3, Uniform::U32(atlas.shelf_height())),
];
let accum_program = match image.format() {
Ok(Format::R8) => &self.accum_program_r8,
Ok(Format::RGBA8) => &self.accum_program_rgba8,
Ok(_) => return Err(RasterError::UnsupportedImageFormat),
Err(err) => return Err(RasterError::ComputeError(err)),
let accum_program = match (image.format(), atlas.uses_subpixel_antialiasing()) {
(Ok(Format::R8), false) => &self.accum_program_gray_r8,
(Ok(Format::RGBA8), false) => &self.accum_program_gray_rgba8,
(Ok(Format::RGBA8), true) => &self.accum_program_subpixel_rgba8,
(Ok(_), _) => return Err(RasterError::UnsupportedImageFormat),
(Err(err), _) => return Err(RasterError::ComputeError(err)),
};
let accum_event = try!(self.queue.submit_compute(accum_program,