Cache images from frame to frame.

Images are cached for one frame; if they are not used the next frame, they're
freed.
This commit is contained in:
Patrick Walton 2020-07-14 13:52:00 -07:00
parent f62f85354f
commit bb89f52d39
6 changed files with 361 additions and 135 deletions

View File

@ -53,6 +53,10 @@ pub struct Image {
is_opaque: bool,
}
/// Unique identifier for an image.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct ImageHash(pub u64);
bitflags! {
pub struct PatternFlags: u8 {
const REPEAT_X = 0x01;
@ -185,6 +189,13 @@ impl Image {
pub fn is_opaque(&self) -> bool {
self.is_opaque
}
#[inline]
pub fn get_hash(&self) -> ImageHash {
let mut hasher = DefaultHasher::new();
self.hash(&mut hasher);
ImageHash(hasher.finish())
}
}
impl PatternSource {

View File

@ -1541,6 +1541,9 @@ fn main() {
let mut cpu_graph = PerfGraph::new(GraphStyle::MS, "CPU Time");
let mut gpu_graph = PerfGraph::new(GraphStyle::MS, "GPU Time");
// Initialize scene proxy.
let mut scene = SceneProxy::new(renderer.mode().level, RayonExecutor);
// Enter the main loop.
let mut exit = false;
while !exit {
@ -1569,9 +1572,7 @@ fn main() {
// Render the canvas to screen.
let canvas = context.into_canvas();
let mut scene = SceneProxy::from_scene(canvas.into_scene(),
renderer.mode().level,
RayonExecutor);
scene.replace_scene(canvas.into_scene());
scene.build_and_render(&mut renderer, BuildOptions::default());
// Present the rendered canvas via `surfman`.

View File

@ -18,7 +18,7 @@ const ATLAS_TEXTURE_LENGTH: u32 = 1024;
#[derive(Clone, Debug)]
pub struct TextureAllocator {
pages: Vec<TexturePage>,
pages: Vec<Option<TexturePage>>,
}
#[derive(Clone, Debug)]
@ -71,22 +71,34 @@ impl TextureAllocator {
}
// Try to add to each atlas.
let mut first_free_page_index = self.pages.len();
for (page_index, page) in self.pages.iter_mut().enumerate() {
match page.allocator {
TexturePageAllocator::Image { .. } => {}
TexturePageAllocator::Atlas(ref mut allocator) => {
if let Some(rect) = allocator.allocate(requested_size) {
return TextureLocation { page: TexturePageId(page_index as u32), rect };
match *page {
Some(ref mut page) => {
match page.allocator {
TexturePageAllocator::Image { .. } => {}
TexturePageAllocator::Atlas(ref mut allocator) => {
if let Some(rect) = allocator.allocate(requested_size) {
return TextureLocation {
page: TexturePageId(page_index as u32),
rect
};
}
}
}
}
None => first_free_page_index = first_free_page_index.min(page_index),
}
}
// Add a new atlas.
let page = TexturePageId(self.pages.len() as u32);
let page = self.get_first_free_page_id();
let mut allocator = TextureAtlasAllocator::new();
let rect = allocator.allocate(requested_size).expect("Allocation failed!");
self.pages.push(TexturePage {
while (page.0 as usize) >= self.pages.len() {
self.pages.push(None);
}
self.pages[page.0 as usize] = Some(TexturePage {
is_new: true,
allocator: TexturePageAllocator::Atlas(allocator),
});
@ -94,17 +106,52 @@ impl TextureAllocator {
}
pub fn allocate_image(&mut self, requested_size: Vector2I) -> TextureLocation {
let page = TexturePageId(self.pages.len() as u32);
let page = self.get_first_free_page_id();
let rect = RectI::new(Vector2I::default(), requested_size);
self.pages.push(TexturePage {
while (page.0 as usize) >= self.pages.len() {
self.pages.push(None);
}
self.pages[page.0 as usize] = Some(TexturePage {
is_new: true,
allocator: TexturePageAllocator::Image { size: rect.size() },
});
TextureLocation { page, rect }
}
fn get_first_free_page_id(&self) -> TexturePageId {
for (page_index, page) in self.pages.iter().enumerate() {
if page.is_none() {
return TexturePageId(page_index as u32);
}
}
TexturePageId(self.pages.len() as u32)
}
pub fn free(&mut self, location: TextureLocation) {
//println!("free({:?})", location);
match self.pages[location.page.0 as usize]
.as_mut()
.expect("Texture page is not allocated!")
.allocator {
TexturePageAllocator::Image { size } => {
debug_assert_eq!(location.rect, RectI::new(Vector2I::default(), size));
}
TexturePageAllocator::Atlas(ref mut atlas_allocator) => {
atlas_allocator.free(location.rect);
if !atlas_allocator.is_empty() {
// Keep the page around.
return;
}
}
}
// If we got here, free the page.
// TODO(pcwalton): Actually tell the renderer to free this page!
self.pages[location.page.0 as usize] = None;
}
pub fn page_size(&self, page_id: TexturePageId) -> Vector2I {
match self.pages[page_id.0 as usize].allocator {
match self.pages[page_id.0 as usize].as_ref().expect("No such texture page!").allocator {
TexturePageAllocator::Atlas(ref atlas) => Vector2I::splat(atlas.size as i32),
TexturePageAllocator::Image { size, .. } => size,
}
@ -115,16 +162,23 @@ impl TextureAllocator {
}
pub fn page_is_new(&self, page_id: TexturePageId) -> bool {
self.pages[page_id.0 as usize].is_new
self.pages[page_id.0 as usize].as_ref().expect("No such texture page!").is_new
}
pub fn mark_page_as_allocated(&mut self, page_id: TexturePageId) {
self.pages[page_id.0 as usize].is_new = false;
pub fn mark_all_pages_as_allocated(&mut self) {
for page in &mut self.pages {
if let Some(ref mut page) = *page {
page.is_new = false;
}
}
}
#[inline]
pub fn page_count(&self) -> u32 {
self.pages.len() as u32
pub fn page_ids(&self) -> TexturePageIter {
let mut first_index = 0;
while first_index < self.pages.len() && self.pages[first_index].is_none() {
first_index += 1;
}
TexturePageIter { allocator: self, next_index: first_index }
}
}
@ -147,7 +201,6 @@ impl TextureAtlasAllocator {
}
#[inline]
#[allow(dead_code)]
fn free(&mut self, rect: RectI) {
let requested_length = rect.width() as u32;
self.root.free(Vector2I::default(), self.size, rect.origin(), requested_length)
@ -285,6 +338,30 @@ impl TreeNode {
}
}
pub struct TexturePageIter<'a> {
allocator: &'a TextureAllocator,
next_index: usize,
}
impl<'a> Iterator for TexturePageIter<'a> {
type Item = TexturePageId;
fn next(&mut self) -> Option<TexturePageId> {
let next_id = if self.next_index >= self.allocator.pages.len() {
None
} else {
Some(TexturePageId(self.next_index as u32))
};
loop {
self.next_index += 1;
if self.next_index >= self.allocator.pages.len() ||
self.allocator.pages[self.next_index as usize].is_some() {
break;
}
}
next_id
}
}
#[cfg(test)]
mod test {
use pathfinder_geometry::vector::vec2i;

View File

@ -175,7 +175,7 @@ impl<'a, 'b, 'c, 'd> SceneBuilder<'a, 'b, 'c, 'd> {
render_commands,
paint_metadata,
render_target_metadata: _,
} = self.scene.build_paint_info(render_transform);
} = self.scene.build_paint_info(&mut self.sink.paint_texture_manager, render_transform);
for render_command in render_commands {
self.sink.listener.send(render_command);
}

View File

@ -14,11 +14,11 @@ use crate::allocator::{AllocationMode, TextureAllocator};
use crate::gpu_data::{ColorCombineMode, RenderCommand, TextureLocation, TextureMetadataEntry};
use crate::gpu_data::{TexturePageDescriptor, TexturePageId, TileBatchTexture};
use crate::scene::{RenderTarget, SceneId};
use hashbrown::HashMap;
use hashbrown::{HashMap, HashSet};
use pathfinder_color::ColorU;
use pathfinder_content::effects::{BlendMode, Filter, PatternFilter};
use pathfinder_content::gradient::{Gradient, GradientGeometry, GradientWrap};
use pathfinder_content::pattern::{Pattern, PatternSource};
use pathfinder_content::pattern::{ImageHash, Pattern, PatternSource};
use pathfinder_content::render_target::RenderTargetId;
use pathfinder_geometry::line_segment::LineSegment2F;
use pathfinder_geometry::rect::{RectF, RectI};
@ -35,19 +35,19 @@ use std::sync::Arc;
// TODO(pcwalton): Choose this size dynamically!
const GRADIENT_TILE_LENGTH: u32 = 256;
// Stores all paints in a scene.
#[derive(Clone)]
pub(crate) struct Palette {
pub(crate) paints: Vec<Paint>,
render_targets: Vec<RenderTargetData>,
render_targets: Vec<RenderTarget>,
cache: HashMap<Paint, PaintId>,
allocator: TextureAllocator,
scene_id: SceneId,
}
#[derive(Clone)]
struct RenderTargetData {
render_target: RenderTarget,
metadata: RenderTargetMetadata,
// Caches texture images from scene to scene.
pub(crate) struct PaintTextureManager {
allocator: TextureAllocator,
cached_images: HashMap<ImageHash, TextureLocation>,
}
/// Defines how a path is to be filled: with a solid color, gradient, or pattern.
@ -109,7 +109,6 @@ impl Palette {
paints: vec![],
render_targets: vec![],
cache: HashMap::new(),
allocator: TextureAllocator::new(),
scene_id,
}
}
@ -392,98 +391,172 @@ impl Palette {
pub(crate) fn push_render_target(&mut self, render_target: RenderTarget) -> RenderTargetId {
let id = self.render_targets.len() as u32;
let metadata = RenderTargetMetadata {
location: self.allocator.allocate_image(render_target.size()),
};
self.render_targets.push(RenderTargetData { render_target, metadata });
self.render_targets.push(render_target);
RenderTargetId { scene: self.scene_id.0, render_target: id }
}
pub(crate) fn build_paint_info(&mut self, render_transform: Transform2F) -> PaintInfo {
let mut paint_metadata = vec![];
pub(crate) fn build_paint_info(&mut self,
texture_manager: &mut PaintTextureManager,
render_transform: Transform2F)
-> PaintInfo {
// Assign render target locations.
let mut transient_paint_locations = vec![];
let render_target_metadata =
self.assign_render_target_locations(texture_manager, &mut transient_paint_locations);
// Assign paint locations.
let PaintLocationsInfo {
mut paint_metadata,
gradient_tile_builder,
image_texel_info,
used_image_hashes,
} = self.assign_paint_locations(&render_target_metadata,
texture_manager,
&mut transient_paint_locations);
// Calculate texture transforms.
self.calculate_texture_transforms(&mut paint_metadata, texture_manager, render_transform);
// Create texture metadata.
let texture_metadata = self.create_texture_metadata(&paint_metadata);
let mut render_commands = vec![RenderCommand::UploadTextureMetadata(texture_metadata)];
// Allocate textures.
self.allocate_textures(&mut render_commands, texture_manager);
// Create render commands.
self.create_render_commands(&mut render_commands,
&render_target_metadata,
gradient_tile_builder,
image_texel_info);
// Free transient locations and unused images, now that they're no longer needed.
self.free_transient_locations(texture_manager, transient_paint_locations);
self.free_unused_images(texture_manager, used_image_hashes);
PaintInfo { render_commands, paint_metadata, render_target_metadata }
}
fn assign_render_target_locations(&self,
texture_manager: &mut PaintTextureManager,
transient_paint_locations: &mut Vec<TextureLocation>)
-> Vec<RenderTargetMetadata> {
let mut render_target_metadata = vec![];
for render_target in &self.render_targets {
let location = texture_manager.allocator.allocate_image(render_target.size());
render_target_metadata.push(RenderTargetMetadata { location });
transient_paint_locations.push(location);
}
render_target_metadata
}
fn assign_paint_locations(&self,
render_target_metadata: &[RenderTargetMetadata],
texture_manager: &mut PaintTextureManager,
transient_paint_locations: &mut Vec<TextureLocation>)
-> PaintLocationsInfo {
let mut paint_metadata = vec![];
let mut gradient_tile_builder = GradientTileBuilder::new();
let mut image_texel_info = vec![];
let mut used_image_hashes = HashSet::new();
for paint in &self.paints {
let allocator = &mut self.allocator;
let render_targets = &self.render_targets;
let color_texture_metadata = paint.overlay.as_ref().map(|overlay| {
match overlay.contents {
PaintContents::Gradient(ref gradient) => {
let mut sampling_flags = TextureSamplingFlags::empty();
match gradient.wrap {
GradientWrap::Repeat => {
let allocator = &mut texture_manager.allocator;
let color_texture_metadata = match paint.overlay {
None => None,
Some(ref overlay) => {
match overlay.contents {
PaintContents::Gradient(ref gradient) => {
let mut sampling_flags = TextureSamplingFlags::empty();
match gradient.wrap {
GradientWrap::Repeat => {
sampling_flags.insert(TextureSamplingFlags::REPEAT_U);
}
GradientWrap::Clamp => {}
}
// FIXME(pcwalton): The gradient size might not be big enough. Detect
// this.
let location =
gradient_tile_builder.allocate(allocator,
transient_paint_locations,
gradient);
Some(PaintColorTextureMetadata {
location,
page_scale: allocator.page_scale(location.page),
sampling_flags,
filter: match gradient.geometry {
GradientGeometry::Linear(_) => PaintFilter::None,
GradientGeometry::Radial { line, radii, .. } => {
PaintFilter::RadialGradient { line, radii }
}
},
transform: Transform2F::default(),
composite_op: overlay.composite_op(),
})
}
PaintContents::Pattern(ref pattern) => {
let location;
match *pattern.source() {
PatternSource::RenderTarget { id: render_target_id, .. } => {
let index = render_target_id.render_target as usize;
location = render_target_metadata[index].location;
}
PatternSource::Image(ref image) => {
// TODO(pcwalton): We should be able to use tile cleverness to
// repeat inside the atlas in some cases.
let image_hash = image.get_hash();
//println!("image hash: {:?}", image_hash);
match texture_manager.cached_images.get(&image_hash) {
Some(cached_location) => {
//println!("... cache hit: {:?}", cached_location);
location = *cached_location;
used_image_hashes.insert(image_hash);
}
None => {
//println!("... cache MISS");
let allocation_mode = AllocationMode::OwnPage;
location = allocator.allocate(image.size(),
allocation_mode);
texture_manager.cached_images.insert(image_hash,
location);
}
}
image_texel_info.push(ImageTexelInfo {
location,
texels: (*image.pixels()).clone(),
});
}
}
let mut sampling_flags = TextureSamplingFlags::empty();
if pattern.repeat_x() {
sampling_flags.insert(TextureSamplingFlags::REPEAT_U);
}
GradientWrap::Clamp => {}
}
// FIXME(pcwalton): The gradient size might not be big enough. Detect this.
let location = gradient_tile_builder.allocate(allocator, gradient);
PaintColorTextureMetadata {
location,
page_scale: allocator.page_scale(location.page),
sampling_flags,
filter: match gradient.geometry {
GradientGeometry::Linear(_) => PaintFilter::None,
GradientGeometry::Radial { line, radii, .. } => {
PaintFilter::RadialGradient { line, radii }
}
},
transform: Transform2F::default(),
composite_op: overlay.composite_op(),
}
}
PaintContents::Pattern(ref pattern) => {
let location;
match *pattern.source() {
PatternSource::RenderTarget { id: render_target_id, .. } => {
let index = render_target_id.render_target as usize;
location = render_targets[index].metadata.location;
if pattern.repeat_y() {
sampling_flags.insert(TextureSamplingFlags::REPEAT_V);
}
PatternSource::Image(ref image) => {
// TODO(pcwalton): We should be able to use tile cleverness to
// repeat inside the atlas in some cases.
let allocation_mode = AllocationMode::OwnPage;
location = allocator.allocate(image.size(), allocation_mode);
image_texel_info.push(ImageTexelInfo {
location,
texels: (*image.pixels()).clone(),
});
if !pattern.smoothing_enabled() {
sampling_flags.insert(TextureSamplingFlags::NEAREST_MIN |
TextureSamplingFlags::NEAREST_MAG);
}
}
let mut sampling_flags = TextureSamplingFlags::empty();
if pattern.repeat_x() {
sampling_flags.insert(TextureSamplingFlags::REPEAT_U);
}
if pattern.repeat_y() {
sampling_flags.insert(TextureSamplingFlags::REPEAT_V);
}
if !pattern.smoothing_enabled() {
sampling_flags.insert(TextureSamplingFlags::NEAREST_MIN |
TextureSamplingFlags::NEAREST_MAG);
}
let filter = match pattern.filter() {
None => PaintFilter::None,
Some(pattern_filter) => PaintFilter::PatternFilter(pattern_filter),
};
let filter = match pattern.filter() {
None => PaintFilter::None,
Some(pattern_filter) => PaintFilter::PatternFilter(pattern_filter),
};
PaintColorTextureMetadata {
location,
page_scale: allocator.page_scale(location.page),
sampling_flags,
filter,
transform: Transform2F::default(),
composite_op: overlay.composite_op(),
Some(PaintColorTextureMetadata {
location,
page_scale: allocator.page_scale(location.page),
sampling_flags,
filter,
transform: Transform2F::default(),
composite_op: overlay.composite_op(),
})
}
}
}
});
};
paint_metadata.push(PaintMetadata {
color_texture_metadata,
@ -494,14 +567,26 @@ impl Palette {
});
}
// Calculate texture transforms.
PaintLocationsInfo {
paint_metadata,
gradient_tile_builder,
image_texel_info,
used_image_hashes,
}
}
fn calculate_texture_transforms(&self,
paint_metadata: &mut [PaintMetadata],
texture_manager: &mut PaintTextureManager,
render_transform: Transform2F) {
for (paint, metadata) in self.paints.iter().zip(paint_metadata.iter_mut()) {
let mut color_texture_metadata = match metadata.color_texture_metadata {
None => continue,
Some(ref mut color_texture_metadata) => color_texture_metadata,
};
let texture_scale = self.allocator.page_scale(color_texture_metadata.location.page);
let texture_scale = texture_manager.allocator
.page_scale(color_texture_metadata.location.page);
let texture_rect = color_texture_metadata.location.rect;
color_texture_metadata.transform = match paint.overlay
.as_ref()
@ -544,9 +629,11 @@ impl Palette {
};
color_texture_metadata.transform *= render_transform;
}
}
// Create texture metadata.
let texture_metadata = paint_metadata.iter().map(|paint_metadata| {
fn create_texture_metadata(&self, paint_metadata: &[PaintMetadata])
-> Vec<TextureMetadataEntry> {
paint_metadata.iter().map(|paint_metadata| {
TextureMetadataEntry {
color_0_transform: match paint_metadata.color_texture_metadata {
None => Transform2F::default(),
@ -561,29 +648,28 @@ impl Palette {
filter: paint_metadata.filter(),
blend_mode: paint_metadata.blend_mode,
}
}).collect();
let mut render_commands = vec![RenderCommand::UploadTextureMetadata(texture_metadata)];
}).collect()
}
// Allocate textures.
let mut texture_page_descriptors = vec![];
for page_index in 0..self.allocator.page_count() {
let page_id = TexturePageId(page_index);
let page_size = self.allocator.page_size(page_id);
fn allocate_textures(&self,
render_commands: &mut Vec<RenderCommand>,
texture_manager: &mut PaintTextureManager) {
for page_id in texture_manager.allocator.page_ids() {
let page_size = texture_manager.allocator.page_size(page_id);
let descriptor = TexturePageDescriptor { size: page_size };
texture_page_descriptors.push(descriptor);
if self.allocator.page_is_new(page_id) {
if texture_manager.allocator.page_is_new(page_id) {
render_commands.push(RenderCommand::AllocateTexturePage { page_id, descriptor });
self.allocator.mark_page_as_allocated(page_id);
}
}
texture_manager.allocator.mark_all_pages_as_allocated();
}
// Gather up render target metadata.
let render_target_metadata: Vec<_> = self.render_targets.iter().map(|render_target_data| {
render_target_data.metadata
}).collect();
// Create render commands.
fn create_render_commands(&self,
render_commands: &mut Vec<RenderCommand>,
render_target_metadata: &[RenderTargetMetadata],
gradient_tile_builder: GradientTileBuilder,
image_texel_info: Vec<ImageTexelInfo>) {
for (index, metadata) in render_target_metadata.iter().enumerate() {
let id = RenderTargetId { scene: self.scene_id.0, render_target: index as u32 };
render_commands.push(RenderCommand::DeclareRenderTarget {
@ -591,15 +677,36 @@ impl Palette {
location: metadata.location,
});
}
gradient_tile_builder.create_render_commands(&mut render_commands);
gradient_tile_builder.create_render_commands(render_commands);
for image_texel_info in image_texel_info {
render_commands.push(RenderCommand::UploadTexelData {
texels: image_texel_info.texels,
location: image_texel_info.location,
});
}
}
PaintInfo { render_commands, paint_metadata, render_target_metadata }
fn free_transient_locations(&self,
texture_manager: &mut PaintTextureManager,
transient_paint_locations: Vec<TextureLocation>) {
for location in transient_paint_locations {
texture_manager.allocator.free(location);
}
}
// Frees images that are cached but not used this frame.
fn free_unused_images(&self,
texture_manager: &mut PaintTextureManager,
used_image_hashes: HashSet<ImageHash>) {
let cached_images = &mut texture_manager.cached_images;
let allocator = &mut texture_manager.allocator;
cached_images.retain(|image_hash, location| {
let keep = used_image_hashes.contains(image_hash);
if !keep {
allocator.free(*location);
}
keep
});
}
pub(crate) fn append_palette(&mut self, palette: Palette) -> MergedPaletteInfo {
@ -612,7 +719,7 @@ impl Palette {
scene: palette.scene_id.0,
render_target: old_render_target_index as u32,
};
let new_render_target_id = self.push_render_target(render_target.render_target);
let new_render_target_id = self.push_render_target(render_target);
render_target_mapping.insert(old_render_target_id, new_render_target_id);
}
@ -650,6 +757,15 @@ impl Palette {
}
}
impl PaintTextureManager {
pub(crate) fn new() -> PaintTextureManager {
PaintTextureManager {
allocator: TextureAllocator::new(),
cached_images: HashMap::new(),
}
}
}
pub(crate) struct MergedPaletteInfo {
pub(crate) render_target_mapping: HashMap<RenderTargetId, RenderTargetId>,
pub(crate) paint_mapping: HashMap<PaintId, PaintId>,
@ -702,15 +818,20 @@ impl GradientTileBuilder {
GradientTileBuilder { tiles: vec![] }
}
fn allocate(&mut self, allocator: &mut TextureAllocator, gradient: &Gradient)
fn allocate(&mut self,
allocator: &mut TextureAllocator,
transient_paint_locations: &mut Vec<TextureLocation>,
gradient: &Gradient)
-> TextureLocation {
if self.tiles.is_empty() ||
self.tiles.last().unwrap().next_index == GRADIENT_TILE_LENGTH {
let size = Vector2I::splat(GRADIENT_TILE_LENGTH as i32);
let area = size.x() as usize * size.y() as usize;
let page_location = allocator.allocate(size, AllocationMode::OwnPage);
transient_paint_locations.push(page_location);
self.tiles.push(GradientTile {
texels: vec![ColorU::black(); area],
page: allocator.allocate(size, AllocationMode::OwnPage).page,
page: page_location.page,
next_index: 0,
})
}
@ -749,6 +870,13 @@ impl GradientTileBuilder {
}
}
struct PaintLocationsInfo {
paint_metadata: Vec<PaintMetadata>,
gradient_tile_builder: GradientTileBuilder,
image_texel_info: Vec<ImageTexelInfo>,
used_image_hashes: HashSet<ImageHash>,
}
struct ImageTexelInfo {
location: TextureLocation,
texels: Arc<Vec<ColorU>>,

View File

@ -17,7 +17,7 @@ use crate::gpu::renderer::Renderer;
use crate::gpu_data::RenderCommand;
use crate::options::{BuildOptions, PreparedBuildOptions};
use crate::options::{PreparedRenderTransform, RenderCommandListener};
use crate::paint::{MergedPaletteInfo, Paint, PaintId, PaintInfo, Palette};
use crate::paint::{MergedPaletteInfo, Paint, PaintId, PaintInfo, PaintTextureManager, Palette};
use pathfinder_content::effects::BlendMode;
use pathfinder_content::fill::FillRule;
use pathfinder_content::outline::Outline;
@ -178,8 +178,11 @@ impl Scene {
}
#[inline]
pub(crate) fn build_paint_info(&mut self, render_transform: Transform2F) -> PaintInfo {
self.palette.build_paint_info(render_transform)
pub(crate) fn build_paint_info(&mut self,
texture_manager: &mut PaintTextureManager,
render_transform: Transform2F)
-> PaintInfo {
self.palette.build_paint_info(texture_manager, render_transform)
}
/// Defines a new paint, which specifies how paths are to be filled or stroked. Returns a paint
@ -382,6 +385,7 @@ pub struct SceneSink<'a> {
pub(crate) listener: RenderCommandListener<'a>,
pub(crate) renderer_level: RendererLevel,
pub(crate) last_scene: Option<LastSceneInfo>,
pub(crate) paint_texture_manager: PaintTextureManager,
}
pub(crate) struct LastSceneInfo {
@ -423,7 +427,12 @@ impl<'a> SceneSink<'a> {
#[inline]
pub fn new(listener: RenderCommandListener<'a>, renderer_level: RendererLevel)
-> SceneSink<'a> {
SceneSink { listener, renderer_level, last_scene: None }
SceneSink {
listener,
renderer_level,
last_scene: None,
paint_texture_manager: PaintTextureManager::new(),
}
}
}