Add support for multiple paint texture pages.

This avoids arbitrary limits on the number of images, gradients, etc. you can
have.
This commit is contained in:
Patrick Walton 2020-02-21 14:46:10 -08:00
parent 0b102ec97d
commit 3245796445
10 changed files with 287 additions and 185 deletions

View File

@ -533,12 +533,12 @@ fn main() {
let mut event_pump = sdl_context.event_pump().unwrap();
let mut mouse_position = Vector2F::default();
let start_time = Instant::now();
let font_context = CanvasFontContext::from_system_source();
// Enter the main loop.
loop {
// Make a canvas.
let mut canvas = CanvasRenderingContext2D::new(CanvasFontContext::from_system_source(),
window_size.to_f32());
let mut canvas = CanvasRenderingContext2D::new(font_context.clone(), window_size.to_f32());
// Render the demo.
let time = (Instant::now() - start_time).as_secs_f32();

View File

@ -449,6 +449,17 @@ impl Device for GLDevice {
&framebuffer.texture
}
#[inline]
fn destroy_framebuffer(&self, framebuffer: Self::Framebuffer) -> Self::Texture {
let texture = GLTexture {
gl_texture: framebuffer.texture.gl_texture,
size: framebuffer.texture.size,
format: framebuffer.texture.format,
};
mem::forget(framebuffer);
texture
}
#[inline]
fn texture_format(&self, texture: &Self::Texture) -> TextureFormat {
texture.format

View File

@ -70,6 +70,7 @@ pub trait Device: Sized {
mode: BufferUploadMode,
);
fn framebuffer_texture<'f>(&self, framebuffer: &'f Self::Framebuffer) -> &'f Self::Texture;
fn destroy_framebuffer(&self, framebuffer: Self::Framebuffer) -> Self::Texture;
fn texture_format(&self, texture: &Self::Texture) -> TextureFormat;
fn texture_size(&self, texture: &Self::Texture) -> Vector2I;
fn upload_to_texture(&self, texture: &Self::Texture, rect: RectI, data: TextureDataRef);

View File

@ -433,10 +433,16 @@ impl Device for MetalDevice {
}
}
#[inline]
fn framebuffer_texture<'f>(&self, framebuffer: &'f MetalFramebuffer) -> &'f MetalTexture {
&framebuffer.0
}
#[inline]
fn destroy_framebuffer(&self, framebuffer: MetalFramebuffer) -> MetalTexture {
framebuffer.0
}
fn texture_format(&self, texture: &MetalTexture) -> TextureFormat {
match texture.texture.pixel_format() {
MTLPixelFormat::R8Unorm => TextureFormat::R8,

View File

@ -11,19 +11,33 @@
//! A simple quadtree-based texture allocator.
use pathfinder_geometry::rect::RectI;
use pathfinder_geometry::vector::Vector2I;
use std::mem;
use pathfinder_geometry::vector::{Vector2F, Vector2I};
const MAX_TEXTURE_LENGTH: u32 = 4096;
const ATLAS_TEXTURE_LENGTH: u32 = 1024;
#[derive(Debug)]
pub struct TextureAllocator {
pages: Vec<TexturePageAllocator>,
}
// TODO(pcwalton): Add layers, perhaps?
#[derive(Debug)]
pub enum TexturePageAllocator {
// An atlas allocated with our quadtree allocator.
Atlas(TextureAtlasAllocator),
// A single image.
Image { size: Vector2I },
}
#[derive(Debug)]
pub struct TextureAtlasAllocator {
root: TreeNode,
size: u32,
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct TextureLocation {
pub page: u32,
pub rect: RectI,
}
@ -37,80 +51,97 @@ enum TreeNode {
impl TextureAllocator {
#[inline]
pub fn new(size: u32) -> TextureAllocator {
// Make sure that the size is a power of two.
debug_assert_eq!(size & (size - 1), 0);
TextureAllocator { root: TreeNode::EmptyLeaf, size }
pub fn new() -> TextureAllocator {
TextureAllocator { pages: vec![] }
}
#[inline]
pub fn allocate(&mut self, requested_size: Vector2I) -> Option<TextureLocation> {
let requested_length =
(requested_size.x().max(requested_size.y()) as u32).next_power_of_two();
loop {
if let Some(location) = self.root.allocate(Vector2I::default(),
self.size,
requested_length) {
return Some(location);
}
if !self.grow() {
return None;
pub fn allocate(&mut self, requested_size: Vector2I) -> TextureLocation {
// If too big, the image gets its own page.
if requested_size.x() > ATLAS_TEXTURE_LENGTH as i32 ||
requested_size.y() > ATLAS_TEXTURE_LENGTH as i32 {
let page = self.pages.len() as u32;
let rect = RectI::new(Vector2I::default(), requested_size);
self.pages.push(TexturePageAllocator::Image { size: rect.size() });
return TextureLocation { page, rect };
}
// Try to add to each atlas.
for (page_index, page) in self.pages.iter_mut().enumerate() {
match *page {
TexturePageAllocator::Image { .. } => {}
TexturePageAllocator::Atlas(ref mut allocator) => {
if let Some(rect) = allocator.allocate(requested_size) {
return TextureLocation { page: page_index as u32, rect };
}
}
}
}
// Add a new atlas.
let page = self.pages.len() as u32;
let mut allocator = TextureAtlasAllocator::new();
let rect = allocator.allocate(requested_size).expect("Allocation failed!");
self.pages.push(TexturePageAllocator::Atlas(allocator));
TextureLocation { page, rect }
}
pub fn page_size(&self, page_index: u32) -> Vector2I {
match self.pages[page_index as usize] {
TexturePageAllocator::Atlas(ref atlas) => Vector2I::splat(atlas.size as i32),
TexturePageAllocator::Image { size } => size,
}
}
pub fn page_scale(&self, page_index: u32) -> Vector2F {
Vector2F::splat(1.0) / self.page_size(page_index).to_f32()
}
#[inline]
#[allow(dead_code)]
pub fn free(&mut self, location: TextureLocation) {
let requested_length = location.rect.width() as u32;
self.root.free(Vector2I::default(), self.size, location.rect.origin(), requested_length)
pub fn page_count(&self) -> u32 {
self.pages.len() as u32
}
}
impl TextureAtlasAllocator {
#[inline]
fn new() -> TextureAtlasAllocator {
TextureAtlasAllocator::with_length(ATLAS_TEXTURE_LENGTH)
}
#[inline]
fn with_length(length: u32) -> TextureAtlasAllocator {
TextureAtlasAllocator { root: TreeNode::EmptyLeaf, size: length }
}
#[inline]
fn allocate(&mut self, requested_size: Vector2I) -> Option<RectI> {
let requested_length =
(requested_size.x().max(requested_size.y()) as u32).next_power_of_two();
self.root.allocate(Vector2I::default(), self.size, requested_length)
}
#[inline]
#[allow(dead_code)]
pub fn is_empty(&self) -> bool {
fn free(&mut self, rect: RectI) {
let requested_length = rect.width() as u32;
self.root.free(Vector2I::default(), self.size, rect.origin(), requested_length)
}
#[inline]
#[allow(dead_code)]
fn is_empty(&self) -> bool {
match self.root {
TreeNode::EmptyLeaf => true,
_ => false,
}
}
// TODO(pcwalton): Make this more flexible.
pub fn grow(&mut self) -> bool {
if self.size >= MAX_TEXTURE_LENGTH {
return false;
}
let old_root = mem::replace(&mut self.root, TreeNode::EmptyLeaf);
self.size *= 2;
// NB: Don't change the order of the children, or else texture coordinates of
// already-allocated objects will become invalid.
self.root = TreeNode::Parent([
Box::new(old_root),
Box::new(TreeNode::EmptyLeaf),
Box::new(TreeNode::EmptyLeaf),
Box::new(TreeNode::EmptyLeaf),
]);
true
}
#[inline]
pub fn size(&self) -> u32 {
self.size
}
#[inline]
pub fn scale(&self) -> f32 {
1.0 / self.size as f32
}
}
impl TreeNode {
// Invariant: `requested_size` must be a power of two.
fn allocate(&mut self, this_origin: Vector2I, this_size: u32, requested_size: u32)
-> Option<TextureLocation> {
-> Option<RectI> {
if let TreeNode::FullLeaf = *self {
// No room here.
return None;
@ -125,9 +156,7 @@ impl TreeNode {
// Do we have a perfect fit?
if this_size == requested_size {
*self = TreeNode::FullLeaf;
return Some(TextureLocation {
rect: RectI::new(this_origin, Vector2I::splat(this_size as i32)),
});
return Some(RectI::new(this_origin, Vector2I::splat(this_size as i32)));
}
// Split.
@ -240,7 +269,7 @@ mod test {
use quickcheck;
use std::u32;
use super::TextureAllocator;
use super::TextureAtlasAllocator;
#[test]
fn test_allocation_and_freeing() {
@ -255,7 +284,7 @@ mod test {
*height = (*height).min(length).max(1);
}
let mut allocator = TextureAllocator::new(length);
let mut allocator = TextureAtlasAllocator::with_length(length);
let mut locations = vec![];
for &(width, height) in &sizes {
let size = Vector2I::new(width as i32, height as i32);

View File

@ -13,7 +13,7 @@
use crate::concurrent::executor::Executor;
use crate::gpu::renderer::MASK_TILES_ACROSS;
use crate::gpu_data::{AlphaTile, AlphaTileVertex, FillBatchPrimitive, MaskTile, MaskTileVertex};
use crate::gpu_data::{RenderCommand, SolidTileVertex, TileObjectPrimitive};
use crate::gpu_data::{RenderCommand, SolidTileBatch, TileObjectPrimitive};
use crate::options::{PreparedBuildOptions, RenderCommandListener};
use crate::paint::{PaintInfo, PaintMetadata};
use crate::scene::{DisplayItem, Scene};
@ -52,6 +52,7 @@ pub(crate) struct ObjectBuilder {
struct BuiltDrawPath {
path: BuiltPath,
blend_mode: BlendMode,
paint_page: u32,
}
#[derive(Debug)]
@ -157,7 +158,7 @@ impl<'a> SceneBuilder<'a> {
let path_object = &scene.paths[path_index];
let outline = scene.apply_render_options(path_object.outline(), built_options);
let paint_id = path_object.paint();
let paint_metadata = &paint_metadata[paint_id.0 as usize];
let built_clip_path =
path_object.clip_path().map(|clip_path_id| &built_clip_paths[clip_path_id.0 as usize]);
@ -167,7 +168,7 @@ impl<'a> SceneBuilder<'a> {
view_box,
path_index as u16,
TilingPathInfo::Draw {
paint_metadata: &paint_metadata[paint_id.0 as usize],
paint_metadata,
blend_mode: path_object.blend_mode(),
built_clip_path,
});
@ -179,6 +180,7 @@ impl<'a> SceneBuilder<'a> {
BuiltDrawPath {
path: tiler.object_builder.built_path,
blend_mode: path_object.blend_mode(),
paint_page: paint_metadata.tex_page,
}
}
@ -204,9 +206,8 @@ impl<'a> SceneBuilder<'a> {
let first_z_buffer = remaining_layer_z_buffers.pop().unwrap();
let first_solid_tiles = first_z_buffer.build_solid_tiles(&self.scene.paths,
paint_metadata);
if !first_solid_tiles.is_empty() {
culled_tiles.display_list
.push(CulledDisplayItem::DrawSolidTiles(first_solid_tiles));
for batch in first_solid_tiles.batches {
culled_tiles.display_list.push(CulledDisplayItem::DrawSolidTiles(batch));
}
let mut layer_z_buffers_stack = vec![first_z_buffer];
@ -220,9 +221,8 @@ impl<'a> SceneBuilder<'a> {
let z_buffer = remaining_layer_z_buffers.pop().unwrap();
let solid_tiles = z_buffer.build_solid_tiles(&self.scene.paths,
paint_metadata);
if !solid_tiles.is_empty() {
culled_tiles.display_list
.push(CulledDisplayItem::DrawSolidTiles(solid_tiles));
for batch in solid_tiles.batches {
culled_tiles.display_list.push(CulledDisplayItem::DrawSolidTiles(batch));
}
layer_z_buffers_stack.push(z_buffer);
continue;
@ -240,18 +240,21 @@ impl<'a> SceneBuilder<'a> {
culled_tiles.push_mask_tiles(&built_draw_path.path);
// Create a new `DrawAlphaTiles` display item if we don't have one or if we have to
// break a batch due to blend mode differences.
// break a batch due to blend mode or paint page.
//
// TODO(pcwalton): If we really wanted to, we could use tile maps to avoid batch
// breaks in some cases…
match culled_tiles.display_list.last() {
Some(&CulledDisplayItem::DrawAlphaTiles {
tiles: _,
paint_page,
blend_mode
}) if blend_mode == built_draw_path.blend_mode => {}
}) if paint_page == built_draw_path.paint_page &&
blend_mode == built_draw_path.blend_mode => {}
_ => {
culled_tiles.display_list.push(CulledDisplayItem::DrawAlphaTiles {
tiles: vec![],
paint_page: built_draw_path.paint_page,
blend_mode: built_draw_path.blend_mode,
})
}
@ -326,11 +329,15 @@ impl<'a> SceneBuilder<'a> {
for display_item in culled_tiles.display_list {
match display_item {
CulledDisplayItem::DrawSolidTiles(tiles) => {
self.listener.send(RenderCommand::DrawSolidTiles(tiles))
CulledDisplayItem::DrawSolidTiles(batch) => {
self.listener.send(RenderCommand::DrawSolidTiles(batch))
}
CulledDisplayItem::DrawAlphaTiles { tiles, blend_mode } => {
self.listener.send(RenderCommand::DrawAlphaTiles { tiles, blend_mode })
CulledDisplayItem::DrawAlphaTiles { tiles, paint_page, blend_mode } => {
self.listener.send(RenderCommand::DrawAlphaTiles {
tiles,
paint_page,
blend_mode,
})
}
CulledDisplayItem::PushLayer { effects } => {
self.listener.send(RenderCommand::PushLayer { effects })
@ -381,8 +388,8 @@ struct CulledTiles {
}
enum CulledDisplayItem {
DrawSolidTiles(Vec<SolidTileVertex>),
DrawAlphaTiles { tiles: Vec<AlphaTile>, blend_mode: BlendMode },
DrawSolidTiles(SolidTileBatch),
DrawAlphaTiles { tiles: Vec<AlphaTile>, paint_page: u32, blend_mode: BlendMode },
PushLayer { effects: Effects },
PopLayer,
}

View File

@ -44,7 +44,7 @@ static QUAD_VERTEX_INDICES: [u32; 6] = [0, 1, 3, 1, 2, 3];
pub(crate) const MASK_TILES_ACROSS: u32 = 256;
pub(crate) const MASK_TILES_DOWN: u32 = 256;
const FRAMEBUFFER_CACHE_SIZE: usize = 8;
const TEXTURE_CACHE_SIZE: usize = 8;
// FIXME(pcwalton): Shrink this again!
const MASK_FRAMEBUFFER_WIDTH: i32 = TILE_WIDTH as i32 * MASK_TILES_ACROSS as i32;
@ -77,7 +77,7 @@ where
fill_vertex_array: FillVertexArray<D>,
fill_framebuffer: D::Framebuffer,
mask_framebuffer: D::Framebuffer,
paint_texture: Option<D::Texture>,
paint_textures: Vec<D::Texture>,
layer_framebuffer_stack: Vec<LayerFramebufferInfo<D>>,
// This is a dummy texture consisting solely of a single `rgba(0, 0, 0, 255)` texel. It serves
@ -103,7 +103,7 @@ where
// Rendering state
framebuffer_flags: FramebufferFlags,
buffered_fills: Vec<FillBatchPrimitive>,
framebuffer_cache: FramebufferCache<D>,
texture_cache: TextureCache<D>,
// Debug
pub stats: RenderStats,
@ -246,7 +246,7 @@ where
fill_vertex_array,
fill_framebuffer,
mask_framebuffer,
paint_texture: None,
paint_textures: vec![],
layer_framebuffer_stack: vec![],
clear_paint_texture,
@ -270,7 +270,7 @@ where
framebuffer_flags: FramebufferFlags::empty(),
buffered_fills: vec![],
framebuffer_cache: FramebufferCache::new(),
texture_cache: TextureCache::new(),
use_depth: false,
}
@ -283,6 +283,7 @@ where
}
pub fn render_command(&mut self, command: &RenderCommand) {
//println!("{:?}", command);
match *command {
RenderCommand::Start { bounding_quad, path_count } => {
if self.use_depth {
@ -303,17 +304,17 @@ where
}
RenderCommand::PushLayer { effects } => self.push_layer(effects),
RenderCommand::PopLayer => self.pop_layer(),
RenderCommand::DrawSolidTiles(ref solid_tile_vertices) => {
let count = solid_tile_vertices.len() / 4;
RenderCommand::DrawSolidTiles(ref batch) => {
let count = batch.vertices.len() / 4;
self.stats.solid_tile_count += count;
self.upload_solid_tiles(solid_tile_vertices);
self.draw_solid_tiles(count as u32);
self.upload_solid_tiles(&batch.vertices);
self.draw_solid_tiles(count as u32, batch.paint_page);
}
RenderCommand::DrawAlphaTiles { tiles: ref alpha_tiles, blend_mode } => {
RenderCommand::DrawAlphaTiles { tiles: ref alpha_tiles, paint_page, blend_mode } => {
let count = alpha_tiles.len();
self.stats.alpha_tile_count += count;
self.upload_alpha_tiles(alpha_tiles);
self.draw_alpha_tiles(count as u32, blend_mode);
self.draw_alpha_tiles(count as u32, paint_page, blend_mode);
}
RenderCommand::Finish { .. } => {}
}
@ -403,21 +404,25 @@ where
}
fn upload_paint_data(&mut self, paint_data: &PaintData) {
let paint_size = paint_data.size;
let paint_texels = &paint_data.texels;
match self.paint_texture {
Some(ref paint_texture) if self.device.texture_size(paint_texture) == paint_size => {}
_ => {
let texture = self.device.create_texture(TextureFormat::RGBA8, paint_size);
self.paint_texture = Some(texture)
}
for paint_texture in self.paint_textures.drain(..) {
self.texture_cache.release_texture(paint_texture);
}
let texels = color::color_slice_to_u8_slice(paint_texels);
self.device.upload_to_texture(self.paint_texture.as_ref().unwrap(),
RectI::new(Vector2I::default(), paint_size),
TextureDataRef::U8(texels));
for paint_page_data in &paint_data.pages {
let paint_size = paint_page_data.size;
let paint_texels = &paint_page_data.texels;
let paint_texture = self.texture_cache.create_texture(&mut self.device,
TextureFormat::RGBA8,
paint_size);
let texels = color::color_slice_to_u8_slice(paint_texels);
self.device.upload_to_texture(&paint_texture,
RectI::new(Vector2I::default(), paint_size),
TextureDataRef::U8(texels));
self.paint_textures.push(paint_texture);
}
}
fn upload_mask_tiles(&mut self, mask_tiles: &[MaskTile], fill_rule: FillRule) {
@ -606,7 +611,7 @@ where
self.framebuffer_flags.insert(FramebufferFlags::MUST_PRESERVE_MASK_FRAMEBUFFER_CONTENTS);
}
fn draw_alpha_tiles(&mut self, tile_count: u32, blend_mode: BlendMode) {
fn draw_alpha_tiles(&mut self, tile_count: u32, paint_page: u32, blend_mode: BlendMode) {
let clear_color = self.clear_color_for_draw_operation();
let mut textures = vec![self.device.framebuffer_texture(&self.mask_framebuffer)];
@ -627,7 +632,7 @@ where
// transparent black paint color doesn't zero out the mask.
&self.clear_paint_texture
}
_ => self.paint_texture.as_ref().unwrap(),
_ => &self.paint_textures[paint_page as usize],
};
textures.push(paint_texture);
@ -658,7 +663,7 @@ where
self.preserve_draw_framebuffer();
}
fn draw_solid_tiles(&mut self, tile_count: u32) {
fn draw_solid_tiles(&mut self, tile_count: u32, paint_page: u32) {
let clear_color = self.clear_color_for_draw_operation();
let mut textures = vec![];
@ -669,7 +674,7 @@ where
UniformData::Vec2(F32x2::new(TILE_WIDTH as f32, TILE_HEIGHT as f32))),
];
let paint_texture = self.paint_texture.as_ref().unwrap();
let paint_texture = &self.paint_textures[paint_page as usize];
textures.push(paint_texture);
uniforms.push((&self.solid_tile_program.paint_texture_uniform,
UniformData::TextureUnit(0)));
@ -797,9 +802,11 @@ where
_ => main_framebuffer_size,
};
let framebuffer = self.framebuffer_cache.create_framebuffer(&mut self.device,
TextureFormat::RGBA8,
framebuffer_size);
let texture = self.texture_cache.create_texture(&mut self.device,
TextureFormat::RGBA8,
framebuffer_size);
let framebuffer = self.device.create_framebuffer(texture);
self.layer_framebuffer_stack.push(LayerFramebufferInfo {
framebuffer,
effects,
@ -827,7 +834,8 @@ where
self.preserve_draw_framebuffer();
self.framebuffer_cache.release_framebuffer(layer_framebuffer_info.framebuffer);
let texture = self.device.destroy_framebuffer(layer_framebuffer_info.framebuffer);
self.texture_cache.release_texture(texture);
}
fn composite_layer(&self,
@ -1086,37 +1094,33 @@ bitflags! {
}
}
struct FramebufferCache<D> where D: Device {
framebuffers: Vec<D::Framebuffer>,
struct TextureCache<D> where D: Device {
textures: Vec<D::Texture>,
}
impl<D> FramebufferCache<D> where D: Device {
fn new() -> FramebufferCache<D> {
FramebufferCache { framebuffers: vec![] }
impl<D> TextureCache<D> where D: Device {
fn new() -> TextureCache<D> {
TextureCache { textures: vec![] }
}
fn create_framebuffer(&mut self, device: &mut D, format: TextureFormat, size: Vector2I)
-> D::Framebuffer {
for index in 0..self.framebuffers.len() {
{
let texture = device.framebuffer_texture(&self.framebuffers[index]);
if device.texture_size(texture) != size ||
device.texture_format(texture) != format {
continue;
}
fn create_texture(&mut self, device: &mut D, format: TextureFormat, size: Vector2I)
-> D::Texture {
for index in 0..self.textures.len() {
if device.texture_size(&self.textures[index]) != size ||
device.texture_format(&self.textures[index]) != format {
continue;
}
return self.framebuffers.remove(index);
return self.textures.remove(index);
}
let texture = device.create_texture(format, size);
device.create_framebuffer(texture)
device.create_texture(format, size)
}
fn release_framebuffer(&mut self, framebuffer: D::Framebuffer) {
if self.framebuffers.len() == FRAMEBUFFER_CACHE_SIZE {
self.framebuffers.pop();
fn release_texture(&mut self, texture: D::Texture) {
if self.textures.len() == TEXTURE_CACHE_SIZE {
self.textures.pop();
}
self.framebuffers.insert(0, framebuffer);
self.textures.insert(0, texture);
}
}

View File

@ -27,17 +27,28 @@ pub enum RenderCommand {
RenderMaskTiles { tiles: Vec<MaskTile>, fill_rule: FillRule },
PushLayer { effects: Effects },
PopLayer,
DrawAlphaTiles { tiles: Vec<AlphaTile>, blend_mode: BlendMode },
DrawSolidTiles(Vec<SolidTileVertex>),
DrawAlphaTiles { tiles: Vec<AlphaTile>, paint_page: u32, blend_mode: BlendMode },
DrawSolidTiles(SolidTileBatch),
Finish { build_time: Duration },
}
#[derive(Clone, Debug)]
pub struct PaintData {
pub pages: Vec<PaintPageData>,
}
#[derive(Clone, Debug)]
pub struct PaintPageData {
pub size: Vector2I,
pub texels: Vec<ColorU>,
}
#[derive(Clone, Debug)]
pub struct SolidTileBatch {
pub vertices: Vec<SolidTileVertex>,
pub paint_page: u32,
}
#[derive(Clone, Copy, Debug)]
pub struct FillObjectPrimitive {
pub px: LineSegmentU4,
@ -121,7 +132,7 @@ impl Debug for RenderCommand {
match *self {
RenderCommand::Start { .. } => write!(formatter, "Start"),
RenderCommand::AddPaintData(ref paint_data) => {
write!(formatter, "AddPaintData({}x{})", paint_data.size.x(), paint_data.size.y())
write!(formatter, "AddPaintData(x{})", paint_data.pages.len())
}
RenderCommand::AddFills(ref fills) => write!(formatter, "AddFills(x{})", fills.len()),
RenderCommand::FlushFills => write!(formatter, "FlushFills"),
@ -130,11 +141,18 @@ impl Debug for RenderCommand {
}
RenderCommand::PushLayer { .. } => write!(formatter, "PushLayer"),
RenderCommand::PopLayer => write!(formatter, "PopLayer"),
RenderCommand::DrawAlphaTiles { ref tiles, blend_mode } => {
write!(formatter, "DrawAlphaTiles(x{}, {:?})", tiles.len(), blend_mode)
RenderCommand::DrawAlphaTiles { ref tiles, paint_page, blend_mode } => {
write!(formatter,
"DrawAlphaTiles(x{}, {}, {:?})",
tiles.len(),
paint_page,
blend_mode)
}
RenderCommand::DrawSolidTiles(ref tiles) => {
write!(formatter, "DrawSolidTiles(x{})", tiles.len())
RenderCommand::DrawSolidTiles(ref batch) => {
write!(formatter,
"DrawSolidTiles(x{}, {})",
batch.vertices.len(),
batch.paint_page)
}
RenderCommand::Finish { .. } => write!(formatter, "Finish"),
}

View File

@ -9,7 +9,7 @@
// except according to those terms.
use crate::allocator::{TextureAllocator, TextureLocation};
use crate::gpu_data::PaintData;
use crate::gpu_data::{PaintData, PaintPageData};
use crate::tiles::{TILE_HEIGHT, TILE_WIDTH};
use hashbrown::HashMap;
use pathfinder_color::ColorU;
@ -22,8 +22,6 @@ use pathfinder_geometry::vector::{Vector2F, Vector2I};
use pathfinder_simd::default::F32x4;
use std::fmt::{self, Debug, Formatter};
const INITIAL_PAINT_TEXTURE_LENGTH: u32 = 1024;
// The size of a gradient tile.
//
// TODO(pcwalton): Choose this size dynamically!
@ -174,6 +172,8 @@ pub struct PaintInfo {
// TODO(pcwalton): Add clamp/repeat options.
#[derive(Debug)]
pub struct PaintMetadata {
/// The index of the texture page.
pub tex_page: u32,
/// The rectangle within the texture atlas.
pub tex_rect: RectI,
/// The transform to apply to screen coordinates to translate them into UVs.
@ -196,7 +196,7 @@ impl Palette {
}
pub fn build_paint_info(&self, view_box_size: Vector2I) -> PaintInfo {
let mut allocator = TextureAllocator::new(INITIAL_PAINT_TEXTURE_LENGTH);
let mut allocator = TextureAllocator::new();
let mut metadata = vec![];
// Assign paint locations.
@ -210,15 +210,14 @@ impl Palette {
// 2. Choose an optimal size for the gradient that minimizes memory usage while
// retaining quality.
allocator.allocate(Vector2I::splat(GRADIENT_TILE_LENGTH as i32))
.expect("Failed to allocate space for the gradient!")
}
Paint::Pattern(ref pattern) => {
allocator.allocate(pattern.image.size())
.expect("Failed to allocate space for the image!")
}
};
metadata.push(PaintMetadata {
tex_page: tex_location.page,
tex_rect: tex_location.rect,
tex_transform: Transform2F::default(),
is_opaque: paint.is_opaque(),
@ -226,25 +225,23 @@ impl Palette {
}
// Calculate texture transforms.
let texture_length = allocator.size();
let texture_scale = allocator.scale();
for (paint, metadata) in self.paints.iter().zip(metadata.iter_mut()) {
let texture_scale = allocator.page_scale(metadata.tex_page);
metadata.tex_transform = match paint {
Paint::Color(_) => {
let vector = rect_to_inset_uv(metadata.tex_rect, texture_length).origin();
let vector = rect_to_inset_uv(metadata.tex_rect, texture_scale).origin();
Transform2F { matrix: Matrix2x2F(F32x4::default()), vector }
}
Paint::Gradient(_) => {
let texture_origin_uv = rect_to_uv(metadata.tex_rect, texture_length).origin();
let gradient_tile_scale = GRADIENT_TILE_LENGTH as f32 * texture_scale;
let texture_origin_uv = rect_to_uv(metadata.tex_rect, texture_scale).origin();
let gradient_tile_scale = texture_scale.scale(GRADIENT_TILE_LENGTH as f32);
Transform2F::from_translation(texture_origin_uv) *
Transform2F::from_scale(Vector2F::splat(gradient_tile_scale) /
view_box_size.to_f32())
Transform2F::from_scale(gradient_tile_scale / view_box_size.to_f32())
}
Paint::Pattern(_) => {
let texture_origin_uv = rect_to_uv(metadata.tex_rect, texture_length).origin();
let texture_origin_uv = rect_to_uv(metadata.tex_rect, texture_scale).origin();
Transform2F::from_translation(texture_origin_uv) *
Transform2F::from_uniform_scale(texture_scale)
Transform2F::from_scale(texture_scale)
}
}
}
@ -252,28 +249,40 @@ impl Palette {
// Render the actual texels.
//
// TODO(pcwalton): This is slow. Do more on GPU.
let texture_area = texture_length as usize * texture_length as usize;
let mut texels = vec![ColorU::default(); texture_area];
let mut paint_data = PaintData { pages: vec![] };
for page_index in 0..allocator.page_count() {
let page_size = allocator.page_size(page_index);
let page_area = page_size.x() as usize * page_size.y() as usize;
let texels = vec![ColorU::default(); page_area];
paint_data.pages.push(PaintPageData { size: page_size, texels });
}
for (paint, metadata) in self.paints.iter().zip(metadata.iter()) {
let tex_page = metadata.tex_page;
let PaintPageData {
size: page_size,
ref mut texels,
} = paint_data.pages[tex_page as usize];
let page_scale = allocator.page_scale(tex_page);
match paint {
Paint::Color(color) => {
put_pixel(metadata.tex_rect.origin(), *color, &mut texels, texture_length);
put_pixel(metadata.tex_rect.origin(), *color, texels, page_size);
}
Paint::Gradient(ref gradient) => {
self.render_gradient(gradient,
metadata.tex_rect,
&metadata.tex_transform,
&mut texels,
texture_length);
texels,
page_size,
page_scale);
}
Paint::Pattern(ref pattern) => {
self.render_pattern(pattern, metadata.tex_rect, &mut texels, texture_length);
self.render_pattern(pattern, metadata.tex_rect, texels, page_size);
}
}
}
let size = Vector2I::splat(texture_length as i32);
return PaintInfo { data: PaintData { size, texels }, metadata };
return PaintInfo { data: paint_data, metadata };
}
// TODO(pcwalton): This is slow. Do on GPU instead.
@ -282,7 +291,8 @@ impl Palette {
tex_rect: RectI,
tex_transform: &Transform2F,
texels: &mut [ColorU],
texture_length: u32) {
tex_size: Vector2I,
tex_scale: Vector2F) {
match *gradient.geometry() {
GradientGeometry::Linear(gradient_line) => {
// FIXME(pcwalton): Paint transparent if gradient line has zero size, per spec.
@ -294,13 +304,12 @@ impl Palette {
for y in 0..(GRADIENT_TILE_LENGTH as i32) {
for x in 0..(GRADIENT_TILE_LENGTH as i32) {
let point = tex_rect.origin() + Vector2I::new(x, y);
let vector = point.to_f32().scale(1.0 / texture_length as f32) -
gradient_line.from();
let vector = point.to_f32().scale_xy(tex_scale) - gradient_line.from();
let mut t = gradient_line.vector().projection_coefficient(vector);
t = util::clamp(t, 0.0, 1.0);
put_pixel(point, gradient.sample(t), texels, texture_length);
put_pixel(point, gradient.sample(t), texels, tex_size);
}
}
}
@ -319,13 +328,12 @@ impl Palette {
for y in 0..(GRADIENT_TILE_LENGTH as i32) {
for x in 0..(GRADIENT_TILE_LENGTH as i32) {
let point = tex_rect.origin() + Vector2I::new(x, y);
let vector = tex_transform_inv *
point.to_f32().scale(1.0 / texture_length as f32);
let vector = tex_transform_inv * point.to_f32().scale_xy(tex_scale);
let t = util::clamp((vector - center).length(), start_radius, end_radius) /
(end_radius - start_radius);
put_pixel(point, gradient.sample(t), texels, texture_length);
put_pixel(point, gradient.sample(t), texels, tex_size);
}
}
}
@ -336,11 +344,11 @@ impl Palette {
pattern: &Pattern,
tex_rect: RectI,
texels: &mut [ColorU],
texture_length: u32) {
tex_size: Vector2I) {
let image_size = pattern.image.size();
for y in 0..image_size.y() {
let dest_origin = tex_rect.origin() + Vector2I::new(0, y);
let dest_start_index = paint_texel_index(dest_origin, texture_length);
let dest_start_index = paint_texel_index(dest_origin, tex_size);
let src_start_index = y as usize * image_size.x() as usize;
let dest_end_index = dest_start_index + image_size.x() as usize;
let src_end_index = src_start_index + image_size.x() as usize;
@ -359,20 +367,20 @@ impl PaintMetadata {
}
}
fn paint_texel_index(position: Vector2I, texture_length: u32) -> usize {
position.y() as usize * texture_length as usize + position.x() as usize
fn paint_texel_index(position: Vector2I, tex_size: Vector2I) -> usize {
position.y() as usize * tex_size.x() as usize + position.x() as usize
}
fn put_pixel(position: Vector2I, color: ColorU, texels: &mut [ColorU], texture_length: u32) {
texels[paint_texel_index(position, texture_length)] = color
fn put_pixel(position: Vector2I, color: ColorU, texels: &mut [ColorU], tex_size: Vector2I) {
texels[paint_texel_index(position, tex_size)] = color
}
fn rect_to_uv(rect: RectI, texture_length: u32) -> RectF {
rect.to_f32().scale(1.0 / texture_length as f32)
fn rect_to_uv(rect: RectI, texture_scale: Vector2F) -> RectF {
rect.to_f32().scale_xy(texture_scale)
}
fn rect_to_inset_uv(rect: RectI, texture_length: u32) -> RectF {
rect_to_uv(rect, texture_length).contract(Vector2F::splat(0.5 / texture_length as f32))
fn rect_to_inset_uv(rect: RectI, texture_scale: Vector2F) -> RectF {
rect_to_uv(rect, texture_scale).contract(texture_scale.scale(0.5))
}
// Solid color allocation
@ -393,8 +401,7 @@ impl SolidColorTileBuilder {
if self.0.is_none() {
// TODO(pcwalton): Handle allocation failure gracefully!
self.0 = Some(SolidColorTileBuilderData {
tile_location: allocator.allocate(Vector2I::splat(SOLID_COLOR_TILE_LENGTH as i32))
.expect("Failed to allocate a solid color tile!"),
tile_location: allocator.allocate(Vector2I::splat(SOLID_COLOR_TILE_LENGTH as i32)),
next_index: 0,
});
}
@ -405,6 +412,7 @@ impl SolidColorTileBuilder {
let subtile_origin = Vector2I::new((data.next_index % SOLID_COLOR_TILE_LENGTH) as i32,
(data.next_index / SOLID_COLOR_TILE_LENGTH) as i32);
location = TextureLocation {
page: data.tile_location.page,
rect: RectI::new(data.tile_location.rect.origin() + subtile_origin,
Vector2I::splat(1)),
};

View File

@ -11,7 +11,7 @@
//! Software occlusion culling.
use crate::builder::SolidTile;
use crate::gpu_data::SolidTileVertex;
use crate::gpu_data::{SolidTileBatch, SolidTileVertex};
use crate::paint::PaintMetadata;
use crate::scene::DrawPath;
use crate::tile_map::DenseTileMap;
@ -23,6 +23,10 @@ pub(crate) struct ZBuffer {
buffer: DenseTileMap<u32>,
}
pub(crate) struct SolidTiles {
pub(crate) batches: Vec<SolidTileBatch>,
}
impl ZBuffer {
pub(crate) fn new(view_box: RectF) -> ZBuffer {
let tile_rect = tiles::round_rect_out_to_tile_bounds(view_box);
@ -45,8 +49,9 @@ impl ZBuffer {
}
pub(crate) fn build_solid_tiles(&self, paths: &[DrawPath], paint_metadata: &[PaintMetadata])
-> Vec<SolidTileVertex> {
let mut solid_tiles = vec![];
-> SolidTiles {
let mut solid_tiles = SolidTiles { batches: vec![] };
for tile_index in 0..self.buffer.data.len() {
let depth = self.buffer.data[tile_index];
if depth == 0 {
@ -62,7 +67,20 @@ impl ZBuffer {
let tile_position = tile_coords + self.buffer.rect.origin();
let object_index = object_index as u16;
solid_tiles.extend_from_slice(&[
// Create a batch if necessary.
match solid_tiles.batches.last() {
Some(ref batch) if batch.paint_page == paint_metadata.tex_page => {}
_ => {
// Batch break.
solid_tiles.batches.push(SolidTileBatch {
paint_page: paint_metadata.tex_page,
vertices: vec![],
});
}
}
let batch = solid_tiles.batches.last_mut().unwrap();
batch.vertices.extend_from_slice(&[
SolidTileVertex::new(tile_position, object_index, paint_metadata),
SolidTileVertex::new(tile_position + Vector2I::new(1, 0),
object_index,