Skip to content

Commit

Permalink
Make picture caching more performant, and fix some bugs.
Browse files Browse the repository at this point in the history
One the remaining issues with enabling picture caching is that
it was a noticeable performance regression on sites that don't
benefit from picture caching, due to the extra render targets
and blits. Instead, we now draw to the main framebuffer and
blit those results into tiles for caching, as appropriate.

There is still a bit of work to be done here (specifically,
detecting when a tile isn't opaque, and disabling the cache blit
if we decide the tile is changing too often), but this is in
general a much faster solution than before. It's also faster
on pages that *do* benefit from picture caching, removing
one extra blit step during cache creation.

This also changes how we handle the way gecko supplies different
display lists with different local coordinate systems. Now, we
cache based on the world rect of the tile, and verify the content
with the new ComparableVec type. This is much more robust and
fixes a number of existing bugzilla bugs related to picture caching.

This patch also removes much of the hashing required, which is a
performance win on the sites I tested (even with the vec comparison
that we now use instead). We do currently update prim deps each
scroll frame, which can be improved / optimized in the future.

Finally, this implementation only allocates and stores tiles for
the current viewport, which is much better for performance and
scaling of content where the picture rect is very large.
  • Loading branch information
gw3583 committed Dec 24, 2018
1 parent 8476ed5 commit 88622ce
Show file tree
Hide file tree
Showing 10 changed files with 706 additions and 872 deletions.
26 changes: 18 additions & 8 deletions examples/animation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,18 @@ impl App {
color: ColorF,
builder: &mut DisplayListBuilder,
property_key: PropertyBindingKey<LayoutTransform>,
opacity_key: Option<PropertyBindingKey<f32>>,
) {
let filters = [
FilterOp::Opacity(PropertyBinding::Binding(self.opacity_key, self.opacity), self.opacity),
];
let filters = match opacity_key {
Some(opacity_key) => {
vec![
FilterOp::Opacity(PropertyBinding::Binding(opacity_key, self.opacity), self.opacity),
]
}
None => {
vec![]
}
};

let reference_frame_id = builder.push_reference_frame(
&LayoutPrimitiveInfo::new(LayoutRect::new(bounds.origin, LayoutSize::zero())),
Expand Down Expand Up @@ -89,8 +97,8 @@ impl App {
}

impl Example for App {
const WIDTH: u32 = 1024;
const HEIGHT: u32 = 1024;
const WIDTH: u32 = 2048;
const HEIGHT: u32 = 1536;

fn render(
&mut self,
Expand All @@ -101,17 +109,19 @@ impl Example for App {
_pipeline_id: PipelineId,
_document_id: DocumentId,
) {
let opacity_key = self.opacity_key;

let bounds = (150, 150).to(250, 250);
let key0 = self.property_key0;
self.add_rounded_rect(bounds, ColorF::new(1.0, 0.0, 0.0, 0.5), builder, key0);
self.add_rounded_rect(bounds, ColorF::new(1.0, 0.0, 0.0, 0.5), builder, key0, Some(opacity_key));

let bounds = (400, 400).to(600, 600);
let key1 = self.property_key1;
self.add_rounded_rect(bounds, ColorF::new(0.0, 1.0, 0.0, 0.5), builder, key1);
self.add_rounded_rect(bounds, ColorF::new(0.0, 1.0, 0.0, 0.5), builder, key1, None);

let bounds = (200, 500).to(350, 580);
let key2 = self.property_key2;
self.add_rounded_rect(bounds, ColorF::new(0.0, 0.0, 1.0, 0.5), builder, key2);
self.add_rounded_rect(bounds, ColorF::new(0.0, 0.0, 1.0, 0.5), builder, key2, None);
}

fn on_event(&mut self, win_event: winit::WindowEvent, api: &RenderApi, document_id: DocumentId) -> bool {
Expand Down
157 changes: 88 additions & 69 deletions webrender/src/batch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -988,76 +988,95 @@ impl AlphaBatchBuilder {

let tile_cache = picture.tile_cache.as_ref().unwrap();

for y in 0 .. tile_cache.tile_rect.size.height {
for x in 0 .. tile_cache.tile_rect.size.width {
let i = y * tile_cache.tile_rect.size.width + x;
let tile = &tile_cache.tiles[i as usize];

// Check if the tile is visible.
if !tile.is_visible || !tile.in_use {
continue;
}

// Get the local rect of the tile.
let tile_rect = tile_cache.get_tile_rect(x, y);

// Construct a local clip rect that ensures we only draw pixels where
// the local bounds of the picture extend to within the edge tiles.
let local_clip_rect = prim_instance
.combined_local_clip_rect
.intersection(&picture.local_rect)
.expect("bug: invalid picture local rect");

let prim_header = PrimitiveHeader {
local_rect: tile_rect,
local_clip_rect,
task_address,
specific_prim_address: prim_cache_address,
clip_task_address,
transform_id,
};

let prim_header_index = prim_headers.push(&prim_header, z_id, [
ShaderColorMode::Image as i32 | ((AlphaType::PremultipliedAlpha as i32) << 16),
RasterizationSpace::Local as i32,
get_shader_opacity(1.0),
]);

let cache_item = ctx
.resource_cache
.get_texture_cache_item(&tile.handle);

let key = BatchKey::new(
kind,
BlendMode::None,
BatchTextures::color(cache_item.texture_id),
);

let uv_rect_address = gpu_cache
.get_address(&cache_item.uv_rect_handle)
.as_int();

let instance = BrushInstance {
prim_header_index,
clip_task_address,
segment_index: INVALID_SEGMENT_INDEX,
edge_flags: EdgeAaSegmentMask::empty(),
brush_flags: BrushFlags::empty(),
user_data: uv_rect_address,
};

// Instead of retrieving the batch once and adding each tile instance,
// use this API to get an appropriate batch for each tile, since
// the batch textures may be different. The batch list internally
// caches the current batch if the key hasn't changed.
let batch = self.batch_list.set_params_and_get_batch(
key,
bounding_rect,
z_id,
);
// If there is a dirty rect for the tile cache, recurse into the
// main picture primitive list, and draw them first.
if let Some(_) = tile_cache.dirty_region {
self.add_pic_to_batch(
picture,
task_id,
ctx,
gpu_cache,
render_tasks,
deferred_resolves,
prim_headers,
transforms,
root_spatial_node_index,
z_generator,
);
}

batch.push(PrimitiveInstanceData::from(instance));
}
// After drawing the dirty rect, now draw any of the valid tiles that
// will make up the rest of the scene.

// Generate a new z id for the tiles, that will place them *after*
// any opaque overdraw from the dirty rect above.
// TODO(gw): We should remove this hack, and also remove some
// (potential opaque) overdraw by adding support for
// setting a scissor rect for the dirty rect above.
let tile_zid = z_generator.next();

for i in &tile_cache.tiles_to_draw {
let tile = &tile_cache.tiles[*i as usize];

// Get the local rect of the tile.
let tile_rect = tile.local_rect;

// Construct a local clip rect that ensures we only draw pixels where
// the local bounds of the picture extend to within the edge tiles.
let local_clip_rect = prim_instance
.combined_local_clip_rect
.intersection(&picture.local_rect)
.expect("bug: invalid picture local rect");

let prim_header = PrimitiveHeader {
local_rect: tile_rect,
local_clip_rect,
task_address,
specific_prim_address: prim_cache_address,
clip_task_address,
transform_id,
};

let prim_header_index = prim_headers.push(&prim_header, tile_zid, [
ShaderColorMode::Image as i32 | ((AlphaType::PremultipliedAlpha as i32) << 16),
RasterizationSpace::Local as i32,
get_shader_opacity(1.0),
]);

let cache_item = ctx
.resource_cache
.get_texture_cache_item(&tile.handle);

let key = BatchKey::new(
kind,
BlendMode::None,
BatchTextures::color(cache_item.texture_id),
);

let uv_rect_address = gpu_cache
.get_address(&cache_item.uv_rect_handle)
.as_int();

let instance = BrushInstance {
prim_header_index,
clip_task_address,
segment_index: INVALID_SEGMENT_INDEX,
edge_flags: EdgeAaSegmentMask::empty(),
brush_flags: BrushFlags::empty(),
user_data: uv_rect_address,
};

// Instead of retrieving the batch once and adding each tile instance,
// use this API to get an appropriate batch for each tile, since
// the batch textures may be different. The batch list internally
// caches the current batch if the key hasn't changed.
let batch = self.batch_list.set_params_and_get_batch(
key,
bounding_rect,
tile_zid,
);

batch.push(PrimitiveInstanceData::from(instance));
}
}
PictureCompositeMode::Filter(filter) => {
Expand Down
45 changes: 44 additions & 1 deletion webrender/src/clip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use render_task::to_cache_size;
use resource_cache::{ImageRequest, ResourceCache};
use std::{cmp, u32};
use std::os::raw::c_void;
use util::{extract_inner_rect_safe, project_rect, ScaleOffset};
use util::{extract_inner_rect_safe, project_rect, ScaleOffset, MaxRect};

/*
Expand Down Expand Up @@ -1361,6 +1361,49 @@ impl ClipNodeCollector {
) {
self.clips.insert(clip_chain_id);
}

pub fn clear(
&mut self,
) {
self.clips.clear();
}

/// Build the world clip rect for this clip node collector.
// NOTE: This ignores any complex clips that may be present.
pub fn get_world_clip_rect(
&self,
clip_store: &ClipStore,
clip_data_store: &ClipDataStore,
clip_scroll_tree: &ClipScrollTree,
) -> Option<WorldRect> {
let mut clip_rect = WorldRect::max_rect();

let mut map_local_to_world = SpaceMapper::new(
ROOT_SPATIAL_NODE_INDEX,
WorldRect::zero(),
);

for clip_chain_id in &self.clips {
let node = clip_store.get_clip_chain(*clip_chain_id);
let clip_node = &clip_data_store[node.handle];

if let Some(local_rect) = clip_node.item.get_local_clip_rect(node.local_pos) {
map_local_to_world.set_target_spatial_node(
node.spatial_node_index,
clip_scroll_tree,
);

if let Some(world_rect) = map_local_to_world.map(&local_rect) {
clip_rect = match clip_rect.intersection(&world_rect) {
Some(rect) => rect,
None => return None,
};
}
}
}

Some(clip_rect)
}
}

// Add a clip node into the list of clips to be processed
Expand Down
16 changes: 7 additions & 9 deletions webrender/src/frame_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,14 +294,6 @@ impl FrameBuilder {
&mut retained_tiles,
);

// If we had any retained tiles from the last scene that were not picked
// up by the new frame, then just discard them eagerly.
// TODO(gw): Maybe it's worth keeping them around for a bit longer in
// some cases?
for (_, handle) in retained_tiles.tiles.drain() {
resource_cache.texture_cache.mark_unused(&handle);
}

let mut frame_state = FrameBuildingState {
render_tasks,
profile_counters,
Expand All @@ -325,6 +317,7 @@ impl FrameBuilder {
true,
&mut frame_state,
&frame_context,
screen_world_rect,
)
.unwrap();

Expand All @@ -350,6 +343,11 @@ impl FrameBuilder {
.surfaces[ROOT_SURFACE_INDEX.0]
.take_render_tasks();

let tile_blits = mem::replace(
&mut frame_state.surfaces[ROOT_SURFACE_INDEX.0].tile_blits,
Vec::new(),
);

let root_render_task = RenderTask::new_picture(
RenderTaskLocation::Fixed(self.screen_rect.to_i32()),
self.screen_rect.size.to_f32(),
Expand All @@ -359,7 +357,7 @@ impl FrameBuilder {
UvRectKind::Rect,
root_spatial_node_index,
None,
Vec::new(),
tile_blits,
);

let render_task_id = frame_state.render_tasks.add(root_render_task);
Expand Down
Loading

0 comments on commit 88622ce

Please sign in to comment.