Allow the paint texture to grow a bit
This commit is contained in:
parent
d97eb32566
commit
3f79927eb1
|
@ -12,6 +12,9 @@
|
||||||
|
|
||||||
use pathfinder_geometry::rect::RectI;
|
use pathfinder_geometry::rect::RectI;
|
||||||
use pathfinder_geometry::vector::Vector2I;
|
use pathfinder_geometry::vector::Vector2I;
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
|
const MAX_TEXTURE_LENGTH: u32 = 4096;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TextureAllocator {
|
pub struct TextureAllocator {
|
||||||
|
@ -44,7 +47,16 @@ impl TextureAllocator {
|
||||||
pub fn allocate(&mut self, requested_size: Vector2I) -> Option<TextureLocation> {
|
pub fn allocate(&mut self, requested_size: Vector2I) -> Option<TextureLocation> {
|
||||||
let requested_length =
|
let requested_length =
|
||||||
(requested_size.x().max(requested_size.y()) as u32).next_power_of_two();
|
(requested_size.x().max(requested_size.y()) as u32).next_power_of_two();
|
||||||
self.root.allocate(Vector2I::default(), self.size, requested_length)
|
loop {
|
||||||
|
if let Some(location) = self.root.allocate(Vector2I::default(),
|
||||||
|
self.size,
|
||||||
|
requested_length) {
|
||||||
|
return Some(location);
|
||||||
|
}
|
||||||
|
if !self.grow() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -62,6 +74,37 @@ impl TextureAllocator {
|
||||||
_ => false,
|
_ => 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 {
|
impl TreeNode {
|
||||||
|
|
|
@ -22,14 +22,12 @@ use pathfinder_geometry::vector::{Vector2F, Vector2I};
|
||||||
use pathfinder_simd::default::F32x4;
|
use pathfinder_simd::default::F32x4;
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
|
|
||||||
const PAINT_TEXTURE_LENGTH: u32 = 1024;
|
const INITIAL_PAINT_TEXTURE_LENGTH: u32 = 1024;
|
||||||
const PAINT_TEXTURE_SCALE: f32 = 1.0 / PAINT_TEXTURE_LENGTH as f32;
|
|
||||||
|
|
||||||
// The size of a gradient tile.
|
// The size of a gradient tile.
|
||||||
//
|
//
|
||||||
// TODO(pcwalton): Choose this size dynamically!
|
// TODO(pcwalton): Choose this size dynamically!
|
||||||
const GRADIENT_TILE_LENGTH: u32 = 256;
|
const GRADIENT_TILE_LENGTH: u32 = 256;
|
||||||
const GRADIENT_TILE_SCALE: f32 = GRADIENT_TILE_LENGTH as f32 * PAINT_TEXTURE_SCALE;
|
|
||||||
|
|
||||||
const SOLID_COLOR_TILE_LENGTH: u32 = 16;
|
const SOLID_COLOR_TILE_LENGTH: u32 = 16;
|
||||||
const MAX_SOLID_COLORS_PER_TILE: u32 = SOLID_COLOR_TILE_LENGTH * SOLID_COLOR_TILE_LENGTH;
|
const MAX_SOLID_COLORS_PER_TILE: u32 = SOLID_COLOR_TILE_LENGTH * SOLID_COLOR_TILE_LENGTH;
|
||||||
|
@ -154,67 +152,93 @@ impl Palette {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_paint_info(&self, view_box_size: Vector2I) -> PaintInfo {
|
pub fn build_paint_info(&self, view_box_size: Vector2I) -> PaintInfo {
|
||||||
let mut allocator = TextureAllocator::new(PAINT_TEXTURE_LENGTH);
|
let mut allocator = TextureAllocator::new(INITIAL_PAINT_TEXTURE_LENGTH);
|
||||||
let area = PAINT_TEXTURE_LENGTH as usize * PAINT_TEXTURE_LENGTH as usize;
|
let mut metadata = vec![];
|
||||||
let (mut texels, mut metadata) = (vec![ColorU::default(); area], vec![]);
|
|
||||||
|
|
||||||
|
// Assign paint locations.
|
||||||
let mut solid_color_tile_builder = SolidColorTileBuilder::new();
|
let mut solid_color_tile_builder = SolidColorTileBuilder::new();
|
||||||
|
|
||||||
for paint in &self.paints {
|
for paint in &self.paints {
|
||||||
let (tex_location, tex_transform);
|
let tex_location = match paint {
|
||||||
match paint {
|
Paint::Color(color) => solid_color_tile_builder.allocate(&mut allocator),
|
||||||
Paint::Color(color) => {
|
|
||||||
tex_location = solid_color_tile_builder.allocate(&mut allocator);
|
|
||||||
let vector = rect_to_inset_uv(tex_location.rect).origin();
|
|
||||||
tex_transform = Transform2F { matrix: Matrix2x2F(F32x4::default()), vector };
|
|
||||||
put_pixel(&mut texels, tex_location.rect.origin(), *color);
|
|
||||||
}
|
|
||||||
Paint::Gradient(ref gradient) => {
|
Paint::Gradient(ref gradient) => {
|
||||||
// TODO(pcwalton): Optimize this:
|
// TODO(pcwalton): Optimize this:
|
||||||
// 1. Use repeating/clamp on the sides.
|
// 1. Use repeating/clamp on the sides.
|
||||||
// 2. Choose an optimal size for the gradient that minimizes memory usage while
|
// 2. Choose an optimal size for the gradient that minimizes memory usage while
|
||||||
// retaining quality.
|
// retaining quality.
|
||||||
tex_location = allocator.allocate(Vector2I::splat(GRADIENT_TILE_LENGTH as i32))
|
allocator.allocate(Vector2I::splat(GRADIENT_TILE_LENGTH as i32))
|
||||||
.expect("Failed to allocate space for the gradient!");
|
.expect("Failed to allocate space for the gradient!")
|
||||||
|
|
||||||
tex_transform =
|
|
||||||
Transform2F::from_translation(rect_to_uv(tex_location.rect).origin()) *
|
|
||||||
Transform2F::from_scale(Vector2F::splat(GRADIENT_TILE_SCALE) /
|
|
||||||
view_box_size.to_f32());
|
|
||||||
|
|
||||||
self.build_paint_info_for_gradient(gradient,
|
|
||||||
tex_location,
|
|
||||||
&tex_transform,
|
|
||||||
&mut texels);
|
|
||||||
}
|
}
|
||||||
Paint::Pattern(ref pattern) => {
|
Paint::Pattern(ref pattern) => {
|
||||||
tex_location = allocator.allocate(pattern.image.size())
|
allocator.allocate(pattern.image.size())
|
||||||
.expect("Failed to allocate space for the image!");
|
.expect("Failed to allocate space for the image!")
|
||||||
tex_transform =
|
|
||||||
Transform2F::from_translation(rect_to_uv(tex_location.rect).origin()) *
|
|
||||||
Transform2F::from_uniform_scale(PAINT_TEXTURE_SCALE);
|
|
||||||
|
|
||||||
self.build_paint_info_for_pattern(pattern, tex_location, &mut texels);
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
metadata.push(PaintMetadata {
|
metadata.push(PaintMetadata {
|
||||||
tex_rect: tex_location.rect,
|
tex_rect: tex_location.rect,
|
||||||
tex_transform,
|
tex_transform: Transform2F::default(),
|
||||||
is_opaque: paint.is_opaque(),
|
is_opaque: paint.is_opaque(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let size = Vector2I::splat(PAINT_TEXTURE_LENGTH as i32);
|
// Calculate texture transforms.
|
||||||
return PaintInfo { data: PaintData { size, texels }, metadata };
|
let texture_length = allocator.size();
|
||||||
|
let texture_scale = allocator.scale();
|
||||||
|
for (paint, metadata) in self.paints.iter().zip(metadata.iter_mut()) {
|
||||||
|
metadata.tex_transform = match paint {
|
||||||
|
Paint::Color(_) => {
|
||||||
|
let vector = rect_to_inset_uv(metadata.tex_rect, texture_length).origin();
|
||||||
|
Transform2F { matrix: Matrix2x2F(F32x4::default()), vector }
|
||||||
|
}
|
||||||
|
Paint::Gradient(ref 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;
|
||||||
|
Transform2F::from_translation(texture_origin_uv) *
|
||||||
|
Transform2F::from_scale(Vector2F::splat(gradient_tile_scale) /
|
||||||
|
view_box_size.to_f32())
|
||||||
|
}
|
||||||
|
Paint::Pattern(ref pattern) => {
|
||||||
|
let texture_origin_uv = rect_to_uv(metadata.tex_rect, texture_length).origin();
|
||||||
|
Transform2F::from_translation(texture_origin_uv) *
|
||||||
|
Transform2F::from_uniform_scale(texture_scale)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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];
|
||||||
|
for (paint, metadata) in self.paints.iter().zip(metadata.iter()) {
|
||||||
|
match paint {
|
||||||
|
Paint::Color(color) => {
|
||||||
|
put_pixel(metadata.tex_rect.origin(), *color, &mut texels, texture_length);
|
||||||
|
}
|
||||||
|
Paint::Gradient(ref gradient) => {
|
||||||
|
self.render_gradient(gradient,
|
||||||
|
metadata.tex_rect,
|
||||||
|
&metadata.tex_transform,
|
||||||
|
&mut texels,
|
||||||
|
texture_length);
|
||||||
|
}
|
||||||
|
Paint::Pattern(ref pattern) => {
|
||||||
|
self.render_pattern(pattern, metadata.tex_rect, &mut texels, texture_length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let size = Vector2I::splat(texture_length as i32);
|
||||||
|
return PaintInfo { data: PaintData { size, texels }, metadata };
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_paint_info_for_gradient(&self,
|
// TODO(pcwalton): This is slow. Do on GPU instead.
|
||||||
gradient: &Gradient,
|
fn render_gradient(&self,
|
||||||
texture_location: TextureLocation,
|
gradient: &Gradient,
|
||||||
tex_transform: &Transform2F,
|
tex_rect: RectI,
|
||||||
texels: &mut [ColorU]) {
|
tex_transform: &Transform2F,
|
||||||
|
texels: &mut [ColorU],
|
||||||
|
texture_length: u32) {
|
||||||
match *gradient.geometry() {
|
match *gradient.geometry() {
|
||||||
GradientGeometry::Linear(gradient_line) => {
|
GradientGeometry::Linear(gradient_line) => {
|
||||||
// FIXME(pcwalton): Paint transparent if gradient line has zero size, per spec.
|
// FIXME(pcwalton): Paint transparent if gradient line has zero size, per spec.
|
||||||
|
@ -225,14 +249,14 @@ impl Palette {
|
||||||
// 2. Go four pixels at a time with SIMD.
|
// 2. Go four pixels at a time with SIMD.
|
||||||
for y in 0..(GRADIENT_TILE_LENGTH as i32) {
|
for y in 0..(GRADIENT_TILE_LENGTH as i32) {
|
||||||
for x in 0..(GRADIENT_TILE_LENGTH as i32) {
|
for x in 0..(GRADIENT_TILE_LENGTH as i32) {
|
||||||
let point = texture_location.rect.origin() + Vector2I::new(x, y);
|
let point = tex_rect.origin() + Vector2I::new(x, y);
|
||||||
let vector = point.to_f32().scale(1.0 / PAINT_TEXTURE_LENGTH as f32) -
|
let vector = point.to_f32().scale(1.0 / texture_length as f32) -
|
||||||
gradient_line.from();
|
gradient_line.from();
|
||||||
|
|
||||||
let mut t = gradient_line.vector().projection_coefficient(vector);
|
let mut t = gradient_line.vector().projection_coefficient(vector);
|
||||||
t = util::clamp(t, 0.0, 1.0);
|
t = util::clamp(t, 0.0, 1.0);
|
||||||
|
|
||||||
put_pixel(texels, point, gradient.sample(t));
|
put_pixel(point, gradient.sample(t), texels, texture_length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -250,28 +274,29 @@ impl Palette {
|
||||||
// 2. Go four pixels at a time with SIMD.
|
// 2. Go four pixels at a time with SIMD.
|
||||||
for y in 0..(GRADIENT_TILE_LENGTH as i32) {
|
for y in 0..(GRADIENT_TILE_LENGTH as i32) {
|
||||||
for x in 0..(GRADIENT_TILE_LENGTH as i32) {
|
for x in 0..(GRADIENT_TILE_LENGTH as i32) {
|
||||||
let point = texture_location.rect.origin() + Vector2I::new(x, y);
|
let point = tex_rect.origin() + Vector2I::new(x, y);
|
||||||
let vector = tex_transform_inv *
|
let vector = tex_transform_inv *
|
||||||
point.to_f32().scale(1.0 / PAINT_TEXTURE_LENGTH as f32);
|
point.to_f32().scale(1.0 / texture_length as f32);
|
||||||
|
|
||||||
let t = util::clamp((vector - center).length(), start_radius, end_radius) /
|
let t = util::clamp((vector - center).length(), start_radius, end_radius) /
|
||||||
(end_radius - start_radius);
|
(end_radius - start_radius);
|
||||||
|
|
||||||
put_pixel(texels, point, gradient.sample(t));
|
put_pixel(point, gradient.sample(t), texels, texture_length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_paint_info_for_pattern(&self,
|
fn render_pattern(&self,
|
||||||
pattern: &Pattern,
|
pattern: &Pattern,
|
||||||
texture_location: TextureLocation,
|
tex_rect: RectI,
|
||||||
texels: &mut [ColorU]) {
|
texels: &mut [ColorU],
|
||||||
|
texture_length: u32) {
|
||||||
let image_size = pattern.image.size();
|
let image_size = pattern.image.size();
|
||||||
for y in 0..image_size.y() {
|
for y in 0..image_size.y() {
|
||||||
let dest_origin = texture_location.rect.origin() + Vector2I::new(0, y);
|
let dest_origin = tex_rect.origin() + Vector2I::new(0, y);
|
||||||
let dest_start_index = paint_texel_index(dest_origin);
|
let dest_start_index = paint_texel_index(dest_origin, texture_length);
|
||||||
let src_start_index = y as usize * image_size.x() as usize;
|
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 dest_end_index = dest_start_index + image_size.x() as usize;
|
||||||
let src_end_index = src_start_index + image_size.x() as usize;
|
let src_end_index = src_start_index + image_size.x() as usize;
|
||||||
|
@ -290,20 +315,20 @@ impl PaintMetadata {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint_texel_index(position: Vector2I) -> usize {
|
fn paint_texel_index(position: Vector2I, texture_length: u32) -> usize {
|
||||||
position.y() as usize * PAINT_TEXTURE_LENGTH as usize + position.x() as usize
|
position.y() as usize * texture_length as usize + position.x() as usize
|
||||||
}
|
}
|
||||||
|
|
||||||
fn put_pixel(texels: &mut [ColorU], position: Vector2I, color: ColorU) {
|
fn put_pixel(position: Vector2I, color: ColorU, texels: &mut [ColorU], texture_length: u32) {
|
||||||
texels[paint_texel_index(position)] = color
|
texels[paint_texel_index(position, texture_length)] = color
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rect_to_uv(rect: RectI) -> RectF {
|
fn rect_to_uv(rect: RectI, texture_length: u32) -> RectF {
|
||||||
rect.to_f32().scale(1.0 / PAINT_TEXTURE_LENGTH as f32)
|
rect.to_f32().scale(1.0 / texture_length as f32)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rect_to_inset_uv(rect: RectI) -> RectF {
|
fn rect_to_inset_uv(rect: RectI, texture_length: u32) -> RectF {
|
||||||
rect_to_uv(rect).contract(Vector2F::splat(0.5 / PAINT_TEXTURE_LENGTH as f32))
|
rect_to_uv(rect, texture_length).contract(Vector2F::splat(0.5 / texture_length as f32))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Solid color allocation
|
// Solid color allocation
|
||||||
|
|
Loading…
Reference in New Issue