Replace layers with render targets, which can be used as patterns.

This allows us to efficiently handle a lot of workloads that require multiple
HTML canvases, without CPU readback. For example, you can render paths to a
render target, then turn that render target into a repeating pattern that you
fill other paths with. Or you can render paths to a render target and then
composite that render target as a whole with a blend mode.

This introduces a new `DrawRenderTarget` render command that blits a render
target without any paths involved. This is basically just a hack that works
around the fact that our tiled renderer doesn't yet support effects that widen
the ink region (i.e. blurs). It can be removed once we have that support.
This commit is contained in:
Patrick Walton 2020-02-21 21:42:15 -08:00
parent 3245796445
commit 67d12adb6c
8 changed files with 426 additions and 174 deletions

View File

@ -20,10 +20,19 @@ use image::RgbaImage;
/// A raster image pattern.
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct Pattern {
pub image: Image,
pub source: PatternSource,
pub repeat: Repeat,
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub enum PatternSource {
Image(Image),
RenderTarget(RenderTargetId),
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct RenderTargetId(pub u32);
/// RGBA, non-premultiplied.
// FIXME(pcwalton): Hash the pixel contents so that we don't have to compare every pixel!
// TODO(pcwalton): Should the pixels be premultiplied?
@ -43,8 +52,8 @@ bitflags! {
impl Pattern {
#[inline]
pub fn new(image: Image, repeat: Repeat) -> Pattern {
Pattern { image, repeat }
pub fn new(source: PatternSource, repeat: Repeat) -> Pattern {
Pattern { source, repeat }
}
}
@ -90,6 +99,31 @@ impl Image {
}
}
impl PatternSource {
#[inline]
pub fn is_opaque(&self) -> bool {
match *self {
PatternSource::Image(ref image) => image.is_opaque(),
PatternSource::RenderTarget(_) => {
// TODO(pcwalton): Maybe do something smarter here?
false
}
}
}
#[inline]
pub fn set_opacity(&mut self, alpha: f32) {
match *self {
PatternSource::Image(ref mut image) => image.set_opacity(alpha),
PatternSource::RenderTarget(_) => {
// TODO(pcwalton): We'll probably have to introduce and use an Opacity filter for
// this.
unimplemented!()
}
}
}
}
impl Debug for Image {
#[inline]
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {

View File

@ -35,7 +35,7 @@ use pathfinder_renderer::concurrent::scene_proxy::{RenderCommandStream, ScenePro
use pathfinder_renderer::gpu::options::{DestFramebuffer, RendererOptions};
use pathfinder_renderer::gpu::renderer::{RenderStats, RenderTime, Renderer};
use pathfinder_renderer::options::{BuildOptions, RenderTransform};
use pathfinder_renderer::scene::Scene;
use pathfinder_renderer::scene::{RenderTarget, Scene};
use pathfinder_svg::BuiltSVG;
use pathfinder_ui::{MousePosition, UIEvent};
use std::fs::File;
@ -138,10 +138,14 @@ impl<W> DemoApp<W> where W: Window {
let effects = build_effects(&ui_model);
let (mut built_svg, svg_tree) = load_scene(resources, &options.input_path, effects);
let viewport = window.viewport(options.mode.view(0));
let (mut built_svg, svg_tree) = load_scene(resources,
&options.input_path,
viewport.size(),
effects);
let message = get_svg_building_message(&built_svg);
let viewport = window.viewport(options.mode.view(0));
let dest_framebuffer = DestFramebuffer::Default {
viewport,
window_size: window_size.device_size(),
@ -428,9 +432,11 @@ impl<W> DemoApp<W> where W: Window {
}
Event::OpenSVG(ref svg_path) => {
let viewport = self.window.viewport(self.ui_model.mode.view(0));
let effects = build_effects(&self.ui_model);
let (mut built_svg, svg_tree) = load_scene(self.window.resource_loader(),
svg_path,
viewport.size(),
effects);
self.ui_model.message = get_svg_building_message(&built_svg);
@ -586,9 +592,9 @@ impl<W> DemoApp<W> where W: Window {
UIAction::None => {}
UIAction::ModelChanged => self.dirty = true,
UIAction::EffectsChanged => {
let effects = build_effects(&self.ui_model);
let mut built_svg = build_svg_tree(&self.svg_tree, effects);
let viewport_size = self.window.viewport(self.ui_model.mode.view(0)).size();
let effects = build_effects(&self.ui_model);
let mut built_svg = build_svg_tree(&self.svg_tree, viewport_size, effects);
self.scene_metadata =
SceneMetadata::new_clipping_view_box(&mut built_svg.scene, viewport_size);
self.scene_proxy.replace_scene(built_svg.scene);
@ -743,7 +749,10 @@ pub enum UIVisibility {
All,
}
fn load_scene(resource_loader: &dyn ResourceLoader, input_path: &SVGPath, effects: Option<Effects>)
fn load_scene(resource_loader: &dyn ResourceLoader,
input_path: &SVGPath,
viewport_size: Vector2I,
effects: Option<Effects>)
-> (BuiltSVG, Tree) {
let mut data;
match *input_path {
@ -756,22 +765,32 @@ fn load_scene(resource_loader: &dyn ResourceLoader, input_path: &SVGPath, effect
};
let tree = Tree::from_data(&data, &UsvgOptions::default()).expect("Failed to parse the SVG!");
let built_svg = build_svg_tree(&tree, effects);
let built_svg = build_svg_tree(&tree, viewport_size, effects);
(built_svg, tree)
}
fn build_svg_tree(tree: &Tree, effects: Option<Effects>) -> BuiltSVG {
fn build_svg_tree(tree: &Tree, viewport_size: Vector2I, effects: Option<Effects>) -> BuiltSVG {
let mut scene = Scene::new();
if let Some(effects) = effects {
scene.push_layer(effects);
let render_target_id = match effects {
None => None,
Some(effects) => {
let scale = match effects.filter {
Filter::Text { defringing_kernel: Some(_), .. } => Vector2I::new(3, 1),
_ => Vector2I::splat(1),
};
let name = "Text".to_owned();
let render_target = RenderTarget::new(viewport_size.scale_xy(scale), name);
Some(scene.push_render_target(render_target))
}
};
let mut built_svg = BuiltSVG::from_tree_and_scene(&tree, scene);
if effects.is_some() {
built_svg.scene.pop_layer();
if let (Some(render_target_id), Some(effects)) = (render_target_id, effects) {
built_svg.scene.pop_render_target();
built_svg.scene.draw_render_target(render_target_id, effects);
}
built_svg
}

View File

@ -10,6 +10,8 @@
//! A simple quadtree-based texture allocator.
use crate::gpu_data::PaintPageId;
use pathfinder_content::pattern::RenderTargetId;
use pathfinder_geometry::rect::RectI;
use pathfinder_geometry::vector::{Vector2F, Vector2I};
@ -27,6 +29,8 @@ pub enum TexturePageAllocator {
Atlas(TextureAtlasAllocator),
// A single image.
Image { size: Vector2I },
// A render target.
RenderTarget { size: Vector2I, id: RenderTargetId },
}
#[derive(Debug)]
@ -37,7 +41,7 @@ pub struct TextureAtlasAllocator {
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct TextureLocation {
pub page: u32,
pub page: PaintPageId,
pub rect: RectI,
}
@ -55,45 +59,58 @@ impl TextureAllocator {
TextureAllocator { pages: vec![] }
}
#[inline]
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 };
return self.allocate_image(requested_size);
}
// Try to add to each atlas.
for (page_index, page) in self.pages.iter_mut().enumerate() {
match *page {
TexturePageAllocator::Image { .. } => {}
TexturePageAllocator::Image { .. } |
TexturePageAllocator::RenderTarget { .. } => {}
TexturePageAllocator::Atlas(ref mut allocator) => {
if let Some(rect) = allocator.allocate(requested_size) {
return TextureLocation { page: page_index as u32, rect };
return TextureLocation { page: PaintPageId(page_index as u32), rect };
}
}
}
}
// Add a new atlas.
let page = self.pages.len() as u32;
let page = PaintPageId(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] {
fn allocate_image(&mut self, requested_size: Vector2I) -> TextureLocation {
let page = PaintPageId(self.pages.len() as u32);
let rect = RectI::new(Vector2I::default(), requested_size);
self.pages.push(TexturePageAllocator::Image { size: rect.size() });
TextureLocation { page, rect }
}
pub fn allocate_render_target(&mut self, requested_size: Vector2I, id: RenderTargetId)
-> TextureLocation {
let page = PaintPageId(self.pages.len() as u32);
let rect = RectI::new(Vector2I::default(), requested_size);
self.pages.push(TexturePageAllocator::RenderTarget { size: rect.size(), id });
TextureLocation { page, rect }
}
pub fn page_size(&self, page_index: PaintPageId) -> Vector2I {
match self.pages[page_index.0 as usize] {
TexturePageAllocator::Atlas(ref atlas) => Vector2I::splat(atlas.size as i32),
TexturePageAllocator::Image { size } => size,
TexturePageAllocator::Image { size } |
TexturePageAllocator::RenderTarget { size, .. } => size,
}
}
pub fn page_scale(&self, page_index: u32) -> Vector2F {
pub fn page_scale(&self, page_index: PaintPageId) -> Vector2F {
Vector2F::splat(1.0) / self.page_size(page_index).to_f32()
}
@ -101,6 +118,14 @@ impl TextureAllocator {
pub fn page_count(&self) -> u32 {
self.pages.len() as u32
}
#[inline]
pub fn page_render_target_id(&self, page_index: PaintPageId) -> Option<RenderTargetId> {
match self.pages[page_index.0 as usize] {
TexturePageAllocator::RenderTarget { id, .. } => Some(id),
TexturePageAllocator::Atlas(_) | TexturePageAllocator::Image { .. } => None,
}
}
}
impl TextureAtlasAllocator {

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, SolidTileBatch, TileObjectPrimitive};
use crate::gpu_data::{PaintPageId, RenderCommand, SolidTileBatch, TileObjectPrimitive};
use crate::options::{PreparedBuildOptions, RenderCommandListener};
use crate::paint::{PaintInfo, PaintMetadata};
use crate::scene::{DisplayItem, Scene};
@ -22,6 +22,7 @@ use crate::tiles::{self, TILE_HEIGHT, TILE_WIDTH, Tiler, TilingPathInfo};
use crate::z_buffer::ZBuffer;
use pathfinder_content::effects::{BlendMode, Effects};
use pathfinder_content::fill::FillRule;
use pathfinder_content::pattern::RenderTargetId;
use pathfinder_geometry::line_segment::{LineSegment2F, LineSegmentU4, LineSegmentU8};
use pathfinder_geometry::vector::{Vector2F, Vector2I};
use pathfinder_geometry::rect::{RectF, RectI};
@ -52,7 +53,7 @@ pub(crate) struct ObjectBuilder {
struct BuiltDrawPath {
path: BuiltPath,
blend_mode: BlendMode,
paint_page: u32,
paint_page: PaintPageId,
}
#[derive(Debug)]
@ -213,10 +214,11 @@ impl<'a> SceneBuilder<'a> {
let mut layer_z_buffers_stack = vec![first_z_buffer];
for display_item in &self.scene.display_list {
// Just pass through `PushLayer` and `PopLayer` commands.
// Pass all commands except `DrawPaths` through.
let (start_draw_path_index, end_draw_path_index) = match *display_item {
DisplayItem::PushLayer { effects } => {
culled_tiles.display_list.push(CulledDisplayItem::PushLayer { effects });
DisplayItem::PushRenderTarget(render_target_id) => {
culled_tiles.display_list
.push(CulledDisplayItem::PushRenderTarget(render_target_id));
let z_buffer = remaining_layer_z_buffers.pop().unwrap();
let solid_tiles = z_buffer.build_solid_tiles(&self.scene.paths,
@ -227,11 +229,18 @@ impl<'a> SceneBuilder<'a> {
layer_z_buffers_stack.push(z_buffer);
continue;
}
DisplayItem::PopLayer => {
culled_tiles.display_list.push(CulledDisplayItem::PopLayer);
DisplayItem::PopRenderTarget => {
culled_tiles.display_list.push(CulledDisplayItem::PopRenderTarget);
layer_z_buffers_stack.pop();
continue;
}
DisplayItem::DrawRenderTarget { render_target, effects } => {
culled_tiles.display_list.push(CulledDisplayItem::DrawRenderTarget {
render_target,
effects,
});
continue;
}
DisplayItem::DrawPaths { start_index, end_index } => (start_index, end_index),
};
@ -291,11 +300,11 @@ impl<'a> SceneBuilder<'a> {
// Create Z-buffers.
for display_item in &self.scene.display_list {
match *display_item {
DisplayItem::PushLayer { .. } => {
DisplayItem::PushRenderTarget { .. } => {
z_buffer_index_stack.push(z_buffers.len());
z_buffers.push(ZBuffer::new(effective_view_box));
}
DisplayItem::PopLayer => {
DisplayItem::PopRenderTarget => {
z_buffer_index_stack.pop();
}
DisplayItem::DrawPaths { start_index, end_index } => {
@ -306,6 +315,9 @@ impl<'a> SceneBuilder<'a> {
z_buffer.update(&built_draw_path.path.solid_tiles, path_index as u32);
}
}
DisplayItem::DrawRenderTarget { .. } => {
// FIXME(pcwalton): Not great that this doesn't participate in Z-buffering!
}
}
}
debug_assert_eq!(z_buffer_index_stack.len(), 1);
@ -339,10 +351,15 @@ impl<'a> SceneBuilder<'a> {
blend_mode,
})
}
CulledDisplayItem::PushLayer { effects } => {
self.listener.send(RenderCommand::PushLayer { effects })
CulledDisplayItem::DrawRenderTarget { render_target, effects } => {
self.listener.send(RenderCommand::DrawRenderTarget { render_target, effects })
}
CulledDisplayItem::PushRenderTarget(render_target_id) => {
self.listener.send(RenderCommand::PushRenderTarget(render_target_id))
}
CulledDisplayItem::PopRenderTarget => {
self.listener.send(RenderCommand::PopRenderTarget)
}
CulledDisplayItem::PopLayer => self.listener.send(RenderCommand::PopLayer),
}
}
}
@ -389,9 +406,10 @@ struct CulledTiles {
enum CulledDisplayItem {
DrawSolidTiles(SolidTileBatch),
DrawAlphaTiles { tiles: Vec<AlphaTile>, paint_page: u32, blend_mode: BlendMode },
PushLayer { effects: Effects },
PopLayer,
DrawAlphaTiles { tiles: Vec<AlphaTile>, paint_page: PaintPageId, blend_mode: BlendMode },
DrawRenderTarget { render_target: RenderTargetId, effects: Effects },
PushRenderTarget(RenderTargetId),
PopRenderTarget,
}
#[derive(Clone, Copy, Debug, Default)]

View File

@ -16,12 +16,13 @@ use crate::gpu::shaders::{FilterTextVertexArray, MAX_FILLS_PER_BATCH, MaskTilePr
use crate::gpu::shaders::{MaskTileVertexArray, ReprojectionProgram, ReprojectionVertexArray};
use crate::gpu::shaders::{SolidTileProgram, SolidTileVertexArray};
use crate::gpu::shaders::{StencilProgram, StencilVertexArray};
use crate::gpu_data::{AlphaTile, FillBatchPrimitive, MaskTile, PaintData};
use crate::gpu_data::{RenderCommand, SolidTileVertex};
use crate::gpu_data::{AlphaTile, FillBatchPrimitive, MaskTile, PaintData, PaintPageContents};
use crate::gpu_data::{PaintPageId, RenderCommand, SolidTileVertex};
use crate::tiles::{TILE_HEIGHT, TILE_WIDTH};
use pathfinder_color::{self as color, ColorF};
use pathfinder_content::effects::{BlendMode, CompositeOp, DefringingKernel, Effects, Filter};
use pathfinder_content::fill::FillRule;
use pathfinder_content::pattern::RenderTargetId;
use pathfinder_geometry::vector::{Vector2I, Vector4F};
use pathfinder_geometry::rect::RectI;
use pathfinder_geometry::transform3d::Transform4F;
@ -77,8 +78,9 @@ where
fill_vertex_array: FillVertexArray<D>,
fill_framebuffer: D::Framebuffer,
mask_framebuffer: D::Framebuffer,
paint_textures: Vec<D::Texture>,
layer_framebuffer_stack: Vec<LayerFramebufferInfo<D>>,
paint_textures: Vec<PaintTexture<D>>,
render_targets: Vec<RenderTargetInfo<D>>,
render_target_stack: Vec<RenderTargetId>,
// This is a dummy texture consisting solely of a single `rgba(0, 0, 0, 255)` texel. It serves
// as the paint texture when drawing alpha tiles with the Clear blend mode. If this weren't
@ -247,7 +249,8 @@ where
fill_framebuffer,
mask_framebuffer,
paint_textures: vec![],
layer_framebuffer_stack: vec![],
render_targets: vec![],
render_target_stack: vec![],
clear_paint_texture,
filter_basic_program,
@ -283,7 +286,6 @@ where
}
pub fn render_command(&mut self, command: &RenderCommand) {
//println!("{:?}", command);
match *command {
RenderCommand::Start { bounding_quad, path_count } => {
if self.use_depth {
@ -302,8 +304,13 @@ where
self.upload_mask_tiles(mask_tiles, fill_rule);
self.draw_mask_tiles(count as u32, fill_rule);
}
RenderCommand::PushLayer { effects } => self.push_layer(effects),
RenderCommand::PopLayer => self.pop_layer(),
RenderCommand::PushRenderTarget(render_target_id) => {
self.push_render_target(render_target_id)
}
RenderCommand::PopRenderTarget => self.pop_render_target(),
RenderCommand::DrawRenderTarget { render_target, effects } => {
self.draw_entire_render_target(render_target, effects)
}
RenderCommand::DrawSolidTiles(ref batch) => {
let count = batch.vertices.len() / 4;
self.stats.solid_tile_count += count;
@ -404,24 +411,47 @@ where
}
fn upload_paint_data(&mut self, paint_data: &PaintData) {
// Clear out old paint textures.
for paint_texture in self.paint_textures.drain(..) {
match paint_texture {
PaintTexture::Texture(paint_texture) => {
self.texture_cache.release_texture(paint_texture);
}
PaintTexture::RenderTarget(_) => {}
}
}
// Clear out old render targets.
for render_target in self.render_targets.drain(..) {
let texture = self.device.destroy_framebuffer(render_target.framebuffer);
self.texture_cache.release_texture(texture);
}
// Build up new paint textures and render targets.
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);
match paint_page_data.contents {
PaintPageContents::RenderTarget(render_target_id) => {
let framebuffer = self.device.create_framebuffer(paint_texture);
self.render_targets.push(RenderTargetInfo {
framebuffer,
must_preserve_contents: false
});
self.paint_textures.push(PaintTexture::RenderTarget(render_target_id));
}
PaintPageContents::Texels(ref paint_texels) => {
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);
self.paint_textures.push(PaintTexture::Texture(paint_texture));
}
}
}
}
@ -611,7 +641,10 @@ where
self.framebuffer_flags.insert(FramebufferFlags::MUST_PRESERVE_MASK_FRAMEBUFFER_CONTENTS);
}
fn draw_alpha_tiles(&mut self, tile_count: u32, paint_page: u32, blend_mode: BlendMode) {
fn draw_alpha_tiles(&mut self,
tile_count: u32,
paint_page: PaintPageId,
blend_mode: BlendMode) {
let clear_color = self.clear_color_for_draw_operation();
let mut textures = vec![self.device.framebuffer_texture(&self.mask_framebuffer)];
@ -632,7 +665,7 @@ where
// transparent black paint color doesn't zero out the mask.
&self.clear_paint_texture
}
_ => &self.paint_textures[paint_page as usize],
_ => self.paint_texture(paint_page),
};
textures.push(paint_texture);
@ -663,7 +696,7 @@ where
self.preserve_draw_framebuffer();
}
fn draw_solid_tiles(&mut self, tile_count: u32, paint_page: u32) {
fn draw_solid_tiles(&mut self, tile_count: u32, paint_page: PaintPageId) {
let clear_color = self.clear_color_for_draw_operation();
let mut textures = vec![];
@ -674,7 +707,7 @@ where
UniformData::Vec2(F32x2::new(TILE_WIDTH as f32, TILE_HEIGHT as f32))),
];
let paint_texture = &self.paint_textures[paint_page as usize];
let paint_texture = self.paint_texture(paint_page);
textures.push(paint_texture);
uniforms.push((&self.solid_tile_program.paint_texture_uniform,
UniformData::TextureUnit(0)));
@ -778,9 +811,10 @@ where
}
pub fn draw_render_target(&self) -> RenderTarget<D> {
match self.layer_framebuffer_stack.last() {
Some(ref layer_framebuffer_info) => {
RenderTarget::Framebuffer(&layer_framebuffer_info.framebuffer)
match self.render_target_stack.last() {
Some(&render_target_id) => {
let framebuffer = &self.render_targets[render_target_id.0 as usize].framebuffer;
RenderTarget::Framebuffer(framebuffer)
}
None => {
match self.dest_framebuffer {
@ -793,38 +827,22 @@ where
}
}
fn push_layer(&mut self, effects: Effects) {
let main_framebuffer_size = self.main_viewport().size();
let framebuffer_size = match effects.filter {
Filter::Text { defringing_kernel: Some(_), .. } => {
main_framebuffer_size.scale_xy(Vector2I::new(3, 1))
}
_ => main_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,
must_preserve_contents: false,
});
fn push_render_target(&mut self, render_target_id: RenderTargetId) {
self.render_target_stack.push(render_target_id);
}
fn pop_layer(&mut self) {
let layer_framebuffer_info = self.layer_framebuffer_stack
.pop()
.expect("Where's the layer?");
fn pop_render_target(&mut self) {
self.render_target_stack.pop().expect("Render target stack underflow!");
}
match layer_framebuffer_info.effects.filter {
// FIXME(pcwalton): This is inefficient and should eventually go away.
fn draw_entire_render_target(&mut self, render_target_id: RenderTargetId, effects: Effects) {
match effects.filter {
Filter::Composite(composite_op) => {
self.composite_layer(&layer_framebuffer_info, composite_op)
self.composite_render_target(render_target_id, composite_op)
}
Filter::Text { fg_color, bg_color, defringing_kernel, gamma_correction } => {
self.draw_text_layer(&layer_framebuffer_info,
self.draw_text_render_target(render_target_id,
fg_color,
bg_color,
defringing_kernel,
@ -833,16 +851,13 @@ where
}
self.preserve_draw_framebuffer();
let texture = self.device.destroy_framebuffer(layer_framebuffer_info.framebuffer);
self.texture_cache.release_texture(texture);
}
fn composite_layer(&self,
layer_framebuffer_info: &LayerFramebufferInfo<D>,
fn composite_render_target(&self,
render_target_id: RenderTargetId,
composite_op: CompositeOp) {
let clear_color = self.clear_color_for_draw_operation();
let source_framebuffer = &layer_framebuffer_info.framebuffer;
let source_framebuffer = &self.render_targets[render_target_id.0 as usize].framebuffer;
let source_texture = self.device.framebuffer_texture(source_framebuffer);
let source_texture_size = self.device.texture_size(source_texture);
let main_viewport = self.main_viewport();
@ -875,14 +890,14 @@ where
});
}
fn draw_text_layer(&self,
layer_framebuffer_info: &LayerFramebufferInfo<D>,
fn draw_text_render_target(&self,
render_target_id: RenderTargetId,
fg_color: ColorF,
bg_color: ColorF,
defringing_kernel: Option<DefringingKernel>,
gamma_correction: bool) {
let clear_color = self.clear_color_for_draw_operation();
let source_framebuffer = &layer_framebuffer_info.framebuffer;
let source_framebuffer = &self.render_targets[render_target_id.0 as usize].framebuffer;
let source_texture = self.device.framebuffer_texture(source_framebuffer);
let source_texture_size = self.device.texture_size(source_texture);
let main_viewport = self.main_viewport();
@ -940,8 +955,10 @@ where
}
fn clear_color_for_draw_operation(&self) -> Option<ColorF> {
let must_preserve_contents = match self.layer_framebuffer_stack.last() {
Some(ref layer_framebuffer_info) => layer_framebuffer_info.must_preserve_contents,
let must_preserve_contents = match self.render_target_stack.last() {
Some(render_target_id) => {
self.render_targets[render_target_id.0 as usize].must_preserve_contents
}
None => {
self.framebuffer_flags
.contains(FramebufferFlags::MUST_PRESERVE_DEST_FRAMEBUFFER_CONTENTS)
@ -950,7 +967,7 @@ where
if must_preserve_contents {
None
} else if self.layer_framebuffer_stack.is_empty() {
} else if self.render_target_stack.is_empty() {
self.options.background_color
} else {
Some(ColorF::default())
@ -958,9 +975,9 @@ where
}
fn preserve_draw_framebuffer(&mut self) {
match self.layer_framebuffer_stack.last_mut() {
Some(ref mut layer_framebuffer_info) => {
layer_framebuffer_info.must_preserve_contents = true;
match self.render_target_stack.last() {
Some(render_target_id) => {
self.render_targets[render_target_id.0 as usize].must_preserve_contents = true;
}
None => {
self.framebuffer_flags
@ -970,9 +987,10 @@ where
}
pub fn draw_viewport(&self) -> RectI {
match self.layer_framebuffer_stack.last() {
Some(ref layer_framebuffer_info) => {
let texture = self.device.framebuffer_texture(&layer_framebuffer_info.framebuffer);
match self.render_target_stack.last() {
Some(render_target_id) => {
let framebuffer = &self.render_targets[render_target_id.0 as usize].framebuffer;
let texture = self.device.framebuffer_texture(framebuffer);
RectI::new(Vector2I::default(), self.device.texture_size(texture))
}
None => self.main_viewport(),
@ -996,6 +1014,16 @@ where
Vector2I::new(MASK_FRAMEBUFFER_WIDTH, MASK_FRAMEBUFFER_HEIGHT))
}
fn paint_texture(&self, paint_page: PaintPageId) -> &D::Texture {
match self.paint_textures[paint_page.0 as usize] {
PaintTexture::Texture(ref texture) => texture,
PaintTexture::RenderTarget(render_target_id) => {
let framebuffer = &self.render_targets[render_target_id.0 as usize].framebuffer;
self.device.framebuffer_texture(framebuffer)
}
}
}
fn allocate_timer_query(&mut self) -> D::TimerQuery {
match self.free_timer_queries.pop() {
Some(query) => query,
@ -1124,9 +1152,13 @@ impl<D> TextureCache<D> where D: Device {
}
}
struct LayerFramebufferInfo<D> where D: Device {
enum PaintTexture<D> where D: Device {
Texture(D::Texture),
RenderTarget(RenderTargetId),
}
struct RenderTargetInfo<D> where D: Device {
framebuffer: D::Framebuffer,
effects: Effects,
must_preserve_contents: bool,
}

View File

@ -14,21 +14,49 @@ use crate::options::BoundingQuad;
use pathfinder_color::ColorU;
use pathfinder_content::effects::{BlendMode, Effects};
use pathfinder_content::fill::FillRule;
use pathfinder_content::pattern::RenderTargetId;
use pathfinder_geometry::line_segment::{LineSegmentU4, LineSegmentU8};
use pathfinder_geometry::vector::Vector2I;
use std::fmt::{Debug, Formatter, Result as DebugResult};
use std::time::Duration;
pub enum RenderCommand {
// Starts rendering a frame.
Start { path_count: usize, bounding_quad: BoundingQuad },
// Uploads paint data for use with subsequent rendering commands to the GPU.
AddPaintData(PaintData),
// Adds fills to the queue.
AddFills(Vec<FillBatchPrimitive>),
// Flushes the queue of fills.
FlushFills,
// Render fills to a set of mask tiles.
RenderMaskTiles { tiles: Vec<MaskTile>, fill_rule: FillRule },
PushLayer { effects: Effects },
PopLayer,
DrawAlphaTiles { tiles: Vec<AlphaTile>, paint_page: u32, blend_mode: BlendMode },
// Pushes a render target onto the stack. Draw commands go to the render target on top of the
// stack.
PushRenderTarget(RenderTargetId),
// Pops a render target from the stack.
PopRenderTarget,
// Draws a batch of alpha tiles to the render target on top of the stack.
DrawAlphaTiles { tiles: Vec<AlphaTile>, paint_page: PaintPageId, blend_mode: BlendMode },
// Draws a batch of solid tiles to the render target on top of the stack.
DrawSolidTiles(SolidTileBatch),
// Draws an entire render target to the render target on top of the stack.
//
// FIXME(pcwalton): This draws the entire render target, so it's inefficient. We should get rid
// of this command and transition all uses to `DrawAlphaTiles`/`DrawSolidTiles`. The reason it
// exists is that we don't have logic to create tiles for blur bounding regions yet.
DrawRenderTarget { render_target: RenderTargetId, effects: Effects },
// Presents a rendered frame.
Finish { build_time: Duration },
}
@ -37,16 +65,25 @@ pub struct PaintData {
pub pages: Vec<PaintPageData>,
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct PaintPageId(pub u32);
#[derive(Clone, Debug)]
pub struct PaintPageData {
pub size: Vector2I,
pub texels: Vec<ColorU>,
pub contents: PaintPageContents,
}
#[derive(Clone, Debug)]
pub enum PaintPageContents {
Texels(Vec<ColorU>),
RenderTarget(RenderTargetId),
}
#[derive(Clone, Debug)]
pub struct SolidTileBatch {
pub vertices: Vec<SolidTileVertex>,
pub paint_page: u32,
pub paint_page: PaintPageId,
}
#[derive(Clone, Copy, Debug)]
@ -139,18 +176,23 @@ impl Debug for RenderCommand {
RenderCommand::RenderMaskTiles { ref tiles, fill_rule } => {
write!(formatter, "RenderMaskTiles(x{}, {:?})", tiles.len(), fill_rule)
}
RenderCommand::PushLayer { .. } => write!(formatter, "PushLayer"),
RenderCommand::PopLayer => write!(formatter, "PopLayer"),
RenderCommand::PushRenderTarget(render_target_id) => {
write!(formatter, "PushRenderTarget({:?})", render_target_id)
}
RenderCommand::PopRenderTarget => write!(formatter, "PopRenderTarget"),
RenderCommand::DrawRenderTarget { render_target, .. } => {
write!(formatter, "DrawRenderTarget({:?})", render_target)
}
RenderCommand::DrawAlphaTiles { ref tiles, paint_page, blend_mode } => {
write!(formatter,
"DrawAlphaTiles(x{}, {}, {:?})",
"DrawAlphaTiles(x{}, {:?}, {:?})",
tiles.len(),
paint_page,
blend_mode)
}
RenderCommand::DrawSolidTiles(ref batch) => {
write!(formatter,
"DrawSolidTiles(x{}, {})",
"DrawSolidTiles(x{}, {:?})",
batch.vertices.len(),
batch.paint_page)
}

View File

@ -9,12 +9,13 @@
// except according to those terms.
use crate::allocator::{TextureAllocator, TextureLocation};
use crate::gpu_data::{PaintData, PaintPageData};
use crate::gpu_data::{PaintData, PaintPageContents, PaintPageData, PaintPageId};
use crate::scene::RenderTarget;
use crate::tiles::{TILE_HEIGHT, TILE_WIDTH};
use hashbrown::HashMap;
use pathfinder_color::ColorU;
use pathfinder_content::gradient::{Gradient, GradientGeometry};
use pathfinder_content::pattern::Pattern;
use pathfinder_content::pattern::{Image, Pattern, PatternSource, RenderTargetId};
use pathfinder_geometry::rect::{RectF, RectI};
use pathfinder_geometry::transform2d::{Matrix2x2F, Transform2F};
use pathfinder_geometry::util;
@ -33,6 +34,7 @@ const MAX_SOLID_COLORS_PER_TILE: u32 = SOLID_COLOR_TILE_LENGTH * SOLID_COLOR_TIL
#[derive(Clone)]
pub struct Palette {
pub(crate) paints: Vec<Paint>,
pub(crate) render_targets: Vec<RenderTarget>,
cache: HashMap<Paint, PaintId>,
}
@ -65,7 +67,7 @@ impl Debug for Paint {
impl Palette {
#[inline]
pub fn new() -> Palette {
Palette { paints: vec![], cache: HashMap::new() }
Palette { paints: vec![], render_targets: vec![], cache: HashMap::new() }
}
}
@ -86,7 +88,7 @@ impl Paint {
Paint::Gradient(ref gradient) => {
gradient.stops().iter().all(|stop| stop.color.is_opaque())
}
Paint::Pattern(ref pattern) => pattern.image.is_opaque(),
Paint::Pattern(ref pattern) => pattern.source.is_opaque(),
}
}
@ -119,7 +121,7 @@ impl Paint {
match *self {
Paint::Color(ref mut color) => color.a = (color.a as f32 * alpha).round() as u8,
Paint::Gradient(ref mut gradient) => gradient.set_opacity(alpha),
Paint::Pattern(ref mut pattern) => pattern.image.set_opacity(alpha),
Paint::Pattern(ref mut pattern) => pattern.source.set_opacity(alpha),
}
}
@ -172,8 +174,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 location of the texture.
pub tex_page: PaintPageId,
/// The rectangle within the texture atlas.
pub tex_rect: RectI,
/// The transform to apply to screen coordinates to translate them into UVs.
@ -195,10 +197,24 @@ impl Palette {
paint_id
}
pub fn push_render_target(&mut self, render_target: RenderTarget) -> RenderTargetId {
let id = RenderTargetId(self.render_targets.len() as u32);
self.render_targets.push(render_target);
id
}
pub fn build_paint_info(&self, view_box_size: Vector2I) -> PaintInfo {
let mut allocator = TextureAllocator::new();
let mut metadata = vec![];
// Assign render target locations.
let mut render_target_locations = vec![];
for (render_target_index, render_target) in self.render_targets.iter().enumerate() {
let render_target_id = RenderTargetId(render_target_index as u32);
render_target_locations.push(allocator.allocate_render_target(render_target.size(),
render_target_id));
}
// Assign paint locations.
let mut solid_color_tile_builder = SolidColorTileBuilder::new();
for paint in &self.paints {
@ -212,7 +228,14 @@ impl Palette {
allocator.allocate(Vector2I::splat(GRADIENT_TILE_LENGTH as i32))
}
Paint::Pattern(ref pattern) => {
allocator.allocate(pattern.image.size())
match pattern.source {
PatternSource::RenderTarget(render_target_id) => {
render_target_locations[render_target_id.0 as usize]
}
PatternSource::Image(ref image) => {
allocator.allocate(image.size())
}
}
}
};
@ -251,19 +274,32 @@ impl Palette {
// TODO(pcwalton): This is slow. Do more on GPU.
let mut paint_data = PaintData { pages: vec![] };
for page_index in 0..allocator.page_count() {
let page_index = PaintPageId(page_index);
let page_size = allocator.page_size(page_index);
if let Some(render_target_id) = allocator.page_render_target_id(page_index) {
paint_data.pages.push(PaintPageData {
size: page_size,
contents: PaintPageContents::RenderTarget(render_target_id),
});
continue;
}
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 });
paint_data.pages.push(PaintPageData {
size: page_size,
contents: PaintPageContents::Texels(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 paint_page_data = &mut paint_data.pages[tex_page.0 as usize];
let page_size = paint_page_data.size;
let page_scale = allocator.page_scale(tex_page);
match paint_page_data.contents {
PaintPageContents::Texels(ref mut texels) => {
match paint {
Paint::Color(color) => {
put_pixel(metadata.tex_rect.origin(), *color, texels, page_size);
@ -277,10 +313,18 @@ impl Palette {
page_scale);
}
Paint::Pattern(ref pattern) => {
self.render_pattern(pattern, metadata.tex_rect, texels, page_size);
match pattern.source {
PatternSource::RenderTarget(_) => {}
PatternSource::Image(ref image) => {
self.render_image(image, metadata.tex_rect, texels, page_size);
}
}
}
}
}
PaintPageContents::RenderTarget(_) => {}
}
}
return PaintInfo { data: paint_data, metadata };
}
@ -340,12 +384,12 @@ impl Palette {
}
}
fn render_pattern(&self,
pattern: &Pattern,
fn render_image(&self,
image: &Image,
tex_rect: RectI,
texels: &mut [ColorU],
tex_size: Vector2I) {
let image_size = pattern.image.size();
let image_size = 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, tex_size);
@ -353,7 +397,7 @@ impl Palette {
let dest_end_index = dest_start_index + image_size.x() as usize;
let src_end_index = src_start_index + image_size.x() as usize;
texels[dest_start_index..dest_end_index].copy_from_slice(
&pattern.image.pixels()[src_start_index..src_end_index]);
&image.pixels()[src_start_index..src_end_index]);
}
}
}

View File

@ -17,10 +17,11 @@ use crate::options::{PreparedRenderTransform, RenderCommandListener};
use crate::paint::{Paint, PaintId, PaintInfo, Palette};
use pathfinder_content::effects::{BlendMode, Effects};
use pathfinder_content::fill::FillRule;
use pathfinder_geometry::vector::Vector2F;
use pathfinder_content::outline::Outline;
use pathfinder_content::pattern::RenderTargetId;
use pathfinder_geometry::vector::{Vector2F, Vector2I};
use pathfinder_geometry::rect::RectF;
use pathfinder_geometry::transform2d::Transform2F;
use pathfinder_content::outline::Outline;
#[derive(Clone)]
pub struct Scene {
@ -70,12 +71,18 @@ impl Scene {
clip_path_id
}
pub fn push_layer(&mut self, effects: Effects) {
self.display_list.push(DisplayItem::PushLayer { effects });
pub fn push_render_target(&mut self, render_target: RenderTarget) -> RenderTargetId {
let render_target_id = self.palette.push_render_target(render_target);
self.display_list.push(DisplayItem::PushRenderTarget(render_target_id));
render_target_id
}
pub fn pop_layer(&mut self) {
self.display_list.push(DisplayItem::PopLayer);
pub fn pop_render_target(&mut self) {
self.display_list.push(DisplayItem::PopRenderTarget);
}
pub fn draw_render_target(&mut self, render_target: RenderTargetId, effects: Effects) {
self.display_list.push(DisplayItem::DrawRenderTarget { render_target, effects });
}
#[inline]
@ -232,11 +239,30 @@ pub struct ClipPath {
#[derive(Clone, Copy, Debug)]
pub struct ClipPathId(pub u32);
#[derive(Clone, Debug)]
pub struct RenderTarget {
size: Vector2I,
name: String,
}
/// Drawing commands.
#[derive(Clone, Debug)]
pub enum DisplayItem {
/// Draws paths to the render target on top of the stack.
DrawPaths { start_index: u32, end_index: u32 },
PushLayer { effects: Effects },
PopLayer,
/// Draws an entire render target to the render target on top of the stack.
///
/// FIXME(pcwalton): This draws the entire render target, so it's inefficient. We should get
/// rid of this command and transition all uses to `DrawPaths`. The reason it exists is that we
/// don't have logic to create tiles for blur bounding regions yet.
DrawRenderTarget { render_target: RenderTargetId, effects: Effects },
/// Pushes a render target onto the top of the stack.
PushRenderTarget(RenderTargetId),
/// Pops a render target from the stack.
PopRenderTarget,
}
impl DrawPath {
@ -293,3 +319,15 @@ impl ClipPath {
self.fill_rule
}
}
impl RenderTarget {
#[inline]
pub fn new(size: Vector2I, name: String) -> RenderTarget {
RenderTarget { size, name }
}
#[inline]
pub fn size(&self) -> Vector2I {
self.size
}
}