From 1ec4e5898e4c357c87afe328114a9db0e28addbf Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Mon, 25 Jan 2016 15:29:14 -0800 Subject: [PATCH] Free resources from the texture cache as appropriate. For now, this just unconditionally frees unused resources from the previous frame after building a new frame. We may want to throttle this back at some point. Also, the cache used is potentially inefficient and can be easily improved. We don't free texture pages at the GL level yet. --- src/batch_builder.rs | 204 ++++++++++++++++++++++++------------------ src/frame.rs | 28 ++++-- src/node_compiler.rs | 21 +++-- src/resource_cache.rs | 141 ++++++++++++++++++++++++----- src/texture_cache.rs | 22 ++++- 5 files changed, 290 insertions(+), 126 deletions(-) diff --git a/src/batch_builder.rs b/src/batch_builder.rs index e26720b55c..fa34c7957c 100644 --- a/src/batch_builder.rs +++ b/src/batch_builder.rs @@ -3,6 +3,7 @@ use batch::{BatchBuilder, TileParams}; use device::TextureId; use euclid::{Rect, Point2D, Size2D}; use fnv::FnvHasher; +use frame::FrameId; use internal_types::{AxisDirection, BasicRotationAngle, BorderRadiusRasterOp, BoxShadowRasterOp}; use internal_types::{GlyphKey, PackedVertexColorMode, RasterItem, RectColors, RectPolygon}; use internal_types::{RectSide, RectUv, DevicePixel}; @@ -82,7 +83,8 @@ impl<'a> BatchBuilder<'a> { uv_rect: &RectUv, colors: &[ColorF; 4], tile_params: Option, - resource_cache: &ResourceCache) { + resource_cache: &ResourceCache, + frame_id: FrameId) { if pos_rect.size.width == 0.0 || pos_rect.size.height == 0.0 { return } @@ -174,15 +176,21 @@ impl<'a> BatchBuilder<'a> { let (mask_texture_id, muv_rect) = match mask_info { Some(clip_rect) => { - let mask_image = resource_cache.get_raster(&RasterItem::BorderRadius(BorderRadiusRasterOp { - outer_radius_x: DevicePixel::new(clip_rect.size.width, self.device_pixel_ratio), - outer_radius_y: DevicePixel::new(clip_rect.size.height, self.device_pixel_ratio), - inner_radius_x: DevicePixel::zero(), - inner_radius_y: DevicePixel::zero(), - inverted: false, - index: None, - image_format: ImageFormat::A8, - })); + let mask_image = resource_cache.get_raster( + &RasterItem::BorderRadius(BorderRadiusRasterOp { + outer_radius_x: + DevicePixel::new(clip_rect.size.width, + self.device_pixel_ratio), + outer_radius_y: + DevicePixel::new(clip_rect.size.height, + self.device_pixel_ratio), + inner_radius_x: DevicePixel::zero(), + inner_radius_y: DevicePixel::zero(), + inverted: false, + index: None, + image_format: ImageFormat::A8, + }), + frame_id); let mut x0_f = (x0 - clip_rect.origin.x) / clip_rect.size.width; let mut x1_f = (x1 - clip_rect.origin.x) / clip_rect.size.width; @@ -282,21 +290,24 @@ impl<'a> BatchBuilder<'a> { #[inline] pub fn add_color_rectangle(&mut self, rect: &Rect, + color: &ColorF, resource_cache: &ResourceCache, - color: &ColorF) { + frame_id: FrameId) { let white_image = resource_cache.get_dummy_color_image(); self.add_complex_clipped_rectangle(white_image.texture_id, rect, &white_image.uv_rect, &[*color, *color, *color, *color], None, - resource_cache); + resource_cache, + frame_id); } pub fn add_webgl_rectangle(&mut self, rect: &Rect, resource_cache: &ResourceCache, - webgl_context_id: &WebGLContextId) { + webgl_context_id: &WebGLContextId, + frame_id: FrameId) { let texture_id = resource_cache.get_webgl_texture(webgl_context_id); let color = ColorF::new(1.0, 1.0, 1.0, 1.0); @@ -312,7 +323,8 @@ impl<'a> BatchBuilder<'a> { &uv, &[color, color, color, color], None, - resource_cache); + resource_cache, + frame_id); } pub fn add_image(&mut self, @@ -320,10 +332,11 @@ impl<'a> BatchBuilder<'a> { stretch_size: &Size2D, image_key: ImageKey, image_rendering: ImageRendering, - resource_cache: &ResourceCache) { + resource_cache: &ResourceCache, + frame_id: FrameId) { // Should be caught higher up debug_assert!(stretch_size.width > 0.0 && stretch_size.height > 0.0); - let image_info = resource_cache.get_image(image_key, image_rendering); + let image_info = resource_cache.get_image(image_key, image_rendering, frame_id); let u1 = rect.size.width / stretch_size.width; let v1 = rect.size.height / stretch_size.height; @@ -351,7 +364,8 @@ impl<'a> BatchBuilder<'a> { &uv, &[color, color, color, color], Some(tile_params), - resource_cache); + resource_cache, + frame_id); } pub fn add_text(&mut self, @@ -362,6 +376,7 @@ impl<'a> BatchBuilder<'a> { color: &ColorF, glyphs: &[GlyphInstance], resource_cache: &ResourceCache, + frame_id: FrameId, device_pixel_ratio: f32) { let dummy_mask_image = resource_cache.get_dummy_mask_image(); @@ -378,7 +393,7 @@ impl<'a> BatchBuilder<'a> { for glyph in glyphs { glyph_key.index = glyph.index; - let image_info = resource_cache.get_glyph(&glyph_key); + let image_info = resource_cache.get_glyph(&glyph_key, frame_id); if let Some(image_info) = image_info { let mut x = (glyph.x * device_pixel_ratio + image_info.user_data.x0 as f32).round() / device_pixel_ratio; let mut y = (glyph.y * device_pixel_ratio - image_info.user_data.y0 as f32).round() / device_pixel_ratio; @@ -423,7 +438,8 @@ impl<'a> BatchBuilder<'a> { rect: &Rect, direction: AxisDirection, stops: &[GradientStop], - resource_cache: &ResourceCache) { + resource_cache: &ResourceCache, + frame_id: FrameId) { let white_image = resource_cache.get_dummy_color_image(); for i in 0..(stops.len() - 1) { @@ -462,7 +478,8 @@ impl<'a> BatchBuilder<'a> { &white_image.uv_rect, &piece_colors, None, - resource_cache); + resource_cache, + frame_id); } } @@ -470,7 +487,8 @@ impl<'a> BatchBuilder<'a> { start_point: &Point2D, end_point: &Point2D, stops: &[GradientStop], - resource_cache: &ResourceCache) { + resource_cache: &ResourceCache, + frame_id: FrameId) { // Fast paths for axis-aligned gradients: // // FIXME(pcwalton): Determine the start and end points properly! @@ -480,7 +498,8 @@ impl<'a> BatchBuilder<'a> { self.add_axis_aligned_gradient_with_stops(&rect, AxisDirection::Vertical, stops, - resource_cache); + resource_cache, + frame_id); return } if start_point.y == end_point.y { @@ -489,7 +508,8 @@ impl<'a> BatchBuilder<'a> { self.add_axis_aligned_gradient_with_stops(&rect, AxisDirection::Horizontal, stops, - resource_cache); + resource_cache, + frame_id); return } @@ -562,14 +582,13 @@ impl<'a> BatchBuilder<'a> { spread_radius: f32, border_radius: f32, clip_mode: BoxShadowClipMode, - resource_cache: &ResourceCache) { + resource_cache: &ResourceCache, + frame_id: FrameId) { let rect = compute_box_shadow_rect(box_bounds, box_offset, spread_radius); // Fast path. if blur_radius == 0.0 && spread_radius == 0.0 && clip_mode == BoxShadowClipMode::None { - self.add_color_rectangle(&rect, - resource_cache, - color); + self.add_color_rectangle(&rect, color, resource_cache, frame_id); return; } @@ -581,7 +600,8 @@ impl<'a> BatchBuilder<'a> { spread_radius, border_radius, clip_mode, - resource_cache); + resource_cache, + frame_id); // Draw the sides. self.add_box_shadow_sides(box_bounds, @@ -591,14 +611,13 @@ impl<'a> BatchBuilder<'a> { spread_radius, border_radius, clip_mode, - resource_cache); + resource_cache, + frame_id); match clip_mode { BoxShadowClipMode::None => { // Fill the center area. - self.add_color_rectangle(box_bounds, - resource_cache, - color); + self.add_color_rectangle(box_bounds, color, resource_cache, frame_id); } BoxShadowClipMode::Outset => { // Fill the center area. @@ -614,9 +633,7 @@ impl<'a> BatchBuilder<'a> { // the case! let old_clip_out_rect = self.set_clip_out_rect(Some(*box_bounds)); - self.add_color_rectangle(¢er_rect, - resource_cache, - color); + self.add_color_rectangle(¢er_rect, color, resource_cache, frame_id); self.set_clip_out_rect(old_clip_out_rect); } @@ -629,7 +646,8 @@ impl<'a> BatchBuilder<'a> { blur_radius, spread_radius, border_radius, - resource_cache); + resource_cache, + frame_id); } } } @@ -642,7 +660,8 @@ impl<'a> BatchBuilder<'a> { spread_radius: f32, border_radius: f32, clip_mode: BoxShadowClipMode, - resource_cache: &ResourceCache) { + resource_cache: &ResourceCache, + frame_id: FrameId) { // Draw the corners. // // +--+------------------+--+ @@ -678,6 +697,7 @@ impl<'a> BatchBuilder<'a> { border_radius, clip_mode, resource_cache, + frame_id, BasicRotationAngle::Upright); self.add_box_shadow_corner(&Point2D::new(metrics.tr_outer.x - metrics.edge_size, metrics.tr_outer.y), @@ -691,6 +711,7 @@ impl<'a> BatchBuilder<'a> { border_radius, clip_mode, resource_cache, + frame_id, BasicRotationAngle::Clockwise90); self.add_box_shadow_corner(&Point2D::new(metrics.br_outer.x - metrics.edge_size, metrics.br_outer.y - metrics.edge_size), @@ -703,6 +724,7 @@ impl<'a> BatchBuilder<'a> { border_radius, clip_mode, resource_cache, + frame_id, BasicRotationAngle::Clockwise180); self.add_box_shadow_corner(&Point2D::new(metrics.bl_outer.x, metrics.bl_outer.y - metrics.edge_size), @@ -716,6 +738,7 @@ impl<'a> BatchBuilder<'a> { border_radius, clip_mode, resource_cache, + frame_id, BasicRotationAngle::Clockwise270); self.undo_clip_state(clip_state); @@ -729,7 +752,8 @@ impl<'a> BatchBuilder<'a> { spread_radius: f32, border_radius: f32, clip_mode: BoxShadowClipMode, - resource_cache: &ResourceCache) { + resource_cache: &ResourceCache, + frame_id: FrameId) { let rect = compute_box_shadow_rect(box_bounds, box_offset, spread_radius); let metrics = BoxShadowMetrics::new(&rect, border_radius, blur_radius); @@ -772,6 +796,7 @@ impl<'a> BatchBuilder<'a> { border_radius, clip_mode, resource_cache, + frame_id, BasicRotationAngle::Clockwise90); self.add_box_shadow_edge(&right_rect.origin, &right_rect.bottom_right(), @@ -781,6 +806,7 @@ impl<'a> BatchBuilder<'a> { border_radius, clip_mode, resource_cache, + frame_id, BasicRotationAngle::Clockwise180); self.add_box_shadow_edge(&bottom_rect.origin, &bottom_rect.bottom_right(), @@ -790,6 +816,7 @@ impl<'a> BatchBuilder<'a> { border_radius, clip_mode, resource_cache, + frame_id, BasicRotationAngle::Clockwise270); self.add_box_shadow_edge(&left_rect.origin, &left_rect.bottom_right(), @@ -799,6 +826,7 @@ impl<'a> BatchBuilder<'a> { border_radius, clip_mode, resource_cache, + frame_id, BasicRotationAngle::Upright); self.undo_clip_state(clip_state); @@ -811,7 +839,8 @@ impl<'a> BatchBuilder<'a> { blur_radius: f32, spread_radius: f32, border_radius: f32, - resource_cache: &ResourceCache) { + resource_cache: &ResourceCache, + frame_id: FrameId) { let rect = compute_box_shadow_rect(box_bounds, box_offset, spread_radius); let metrics = BoxShadowMetrics::new(&rect, border_radius, blur_radius); @@ -839,29 +868,33 @@ impl<'a> BatchBuilder<'a> { self.add_color_rectangle(&Rect::new(box_bounds.origin, Size2D::new(box_bounds.size.width, metrics.tl_outer.y - box_bounds.origin.y)), + color, resource_cache, - color); + frame_id); // B: self.add_color_rectangle(&Rect::new(metrics.tr_outer, Size2D::new(box_bounds.max_x() - metrics.tr_outer.x, metrics.br_outer.y - metrics.tr_outer.y)), + color, resource_cache, - color); + frame_id); // C: self.add_color_rectangle(&Rect::new(Point2D::new(box_bounds.origin.x, metrics.bl_outer.y), Size2D::new(box_bounds.size.width, box_bounds.max_y() - metrics.br_outer.y)), + color, resource_cache, - color); + frame_id); // D: self.add_color_rectangle(&Rect::new(Point2D::new(box_bounds.origin.x, metrics.tl_outer.y), Size2D::new(metrics.tl_outer.x - box_bounds.origin.x, metrics.bl_outer.y - metrics.tl_outer.y)), + color, resource_cache, - color); + frame_id); self.undo_clip_state(clip_state); } @@ -905,7 +938,8 @@ impl<'a> BatchBuilder<'a> { side: RectSide, color: &ColorF, border_style: BorderStyle, - resource_cache: &ResourceCache) { + resource_cache: &ResourceCache, + frame_id: FrameId) { if color.a <= 0.0 { return } @@ -941,9 +975,7 @@ impl<'a> BatchBuilder<'a> { } }; - self.add_color_rectangle(&dash_rect, - resource_cache, - color); + self.add_color_rectangle(&dash_rect, color, resource_cache, frame_id); origin += step + step; } @@ -982,7 +1014,7 @@ impl<'a> BatchBuilder<'a> { ImageFormat::RGBA8).expect( "Didn't find border radius mask for dashed border!"); let raster_item = RasterItem::BorderRadius(raster_op); - let color_image = resource_cache.get_raster(&raster_item); + let color_image = resource_cache.get_raster(&raster_item, frame_id); // Top left: self.add_simple_rectangle(color_image.texture_id, @@ -1048,12 +1080,8 @@ impl<'a> BatchBuilder<'a> { Size2D::new(rect.size.width / 3.0, rect.size.height))) } }; - self.add_color_rectangle(&outer_rect, - resource_cache, - color); - self.add_color_rectangle(&inner_rect, - resource_cache, - color); + self.add_color_rectangle(&outer_rect, color, resource_cache, frame_id); + self.add_color_rectangle(&inner_rect, color, resource_cache, frame_id); } BorderStyle::Groove | BorderStyle::Ridge => { let (tl_rect, br_rect) = match side { @@ -1073,17 +1101,11 @@ impl<'a> BatchBuilder<'a> { } }; let (tl_color, br_color) = groove_ridge_border_colors(color, border_style); - self.add_color_rectangle(&tl_rect, - resource_cache, - &tl_color); - self.add_color_rectangle(&br_rect, - resource_cache, - &br_color); + self.add_color_rectangle(&tl_rect, &tl_color, resource_cache, frame_id); + self.add_color_rectangle(&br_rect, &br_color, resource_cache, frame_id); } _ => { - self.add_color_rectangle(rect, - resource_cache, - color); + self.add_color_rectangle(rect, color, resource_cache, frame_id); } } } @@ -1125,6 +1147,7 @@ impl<'a> BatchBuilder<'a> { outer_radius: &Size2D, inner_radius: &Size2D, resource_cache: &ResourceCache, + frame_id: FrameId, rotation_angle: BasicRotationAngle, device_pixel_ratio: f32) { if color0.a <= 0.0 && color1.a <= 0.0 { @@ -1162,6 +1185,7 @@ impl<'a> BatchBuilder<'a> { outer_radius, inner_radius, resource_cache, + frame_id, rotation_angle, device_pixel_ratio); self.add_solid_border_corner(&inner_corner_rect, @@ -1171,19 +1195,16 @@ impl<'a> BatchBuilder<'a> { outer_radius, inner_radius, resource_cache, + frame_id, rotation_angle, device_pixel_ratio); // Draw the solid parts: if util::rect_is_well_formed_and_nonempty(&color0_rect) { - self.add_color_rectangle(&color0_rect, - resource_cache, - &color0_outer) + self.add_color_rectangle(&color0_rect, &color0_outer, resource_cache, frame_id) } if util::rect_is_well_formed_and_nonempty(&color1_rect) { - self.add_color_rectangle(&color1_rect, - resource_cache, - &color1_outer) + self.add_color_rectangle(&color1_rect, &color1_outer, resource_cache, frame_id) } } BorderStyle::Double => { @@ -1259,12 +1280,11 @@ impl<'a> BatchBuilder<'a> { outer_radius, &Size2D::new(0.0, 0.0), resource_cache, + frame_id, rotation_angle, device_pixel_ratio); - self.add_color_rectangle(&outer_side_rect_1, - resource_cache, - &color0); + self.add_color_rectangle(&outer_side_rect_1, &color0, resource_cache, frame_id); self.add_solid_border_corner(&inner_corner_rect, radius_extent, @@ -1273,12 +1293,11 @@ impl<'a> BatchBuilder<'a> { &Size2D::new(0.0, 0.0), inner_radius, resource_cache, + frame_id, rotation_angle, device_pixel_ratio); - self.add_color_rectangle(&outer_side_rect_0, - resource_cache, - &color1); + self.add_color_rectangle(&outer_side_rect_0, &color1, resource_cache, frame_id); } _ => { self.add_solid_border_corner(corner_bounds, @@ -1288,6 +1307,7 @@ impl<'a> BatchBuilder<'a> { outer_radius, inner_radius, resource_cache, + frame_id, rotation_angle, device_pixel_ratio) } @@ -1302,6 +1322,7 @@ impl<'a> BatchBuilder<'a> { outer_radius: &Size2D, inner_radius: &Size2D, resource_cache: &ResourceCache, + frame_id: FrameId, rotation_angle: BasicRotationAngle, _device_pixel_ratio: f32) { // TODO: Check for zero width/height borders! @@ -1334,7 +1355,7 @@ impl<'a> BatchBuilder<'a> { None,//Some(rect_index), ImageFormat::A8) { Some(raster_item) => { - resource_cache.get_raster(&RasterItem::BorderRadius(raster_item)) + resource_cache.get_raster(&RasterItem::BorderRadius(raster_item), frame_id) } None => dummy_mask_image, }; @@ -1371,14 +1392,10 @@ impl<'a> BatchBuilder<'a> { // Draw the two solid rects. if util::rect_is_well_formed_and_nonempty(&color0_rect) { - self.add_color_rectangle(&color0_rect, - resource_cache, - color0) + self.add_color_rectangle(&color0_rect, color0, resource_cache, frame_id) } if util::rect_is_well_formed_and_nonempty(&color1_rect) { - self.add_color_rectangle(&color1_rect, - resource_cache, - color1) + self.add_color_rectangle(&color1_rect, color1, resource_cache, frame_id) } } @@ -1485,6 +1502,7 @@ impl<'a> BatchBuilder<'a> { rect: &Rect, info: &BorderDisplayItem, resource_cache: &ResourceCache, + frame_id: FrameId, device_pixel_ratio: f32) { // TODO: If any border segment is alpha, place all in alpha pass. // Is it ever worth batching at a per-segment level? @@ -1522,7 +1540,8 @@ impl<'a> BatchBuilder<'a> { RectSide::Left, &left_color, info.left.style, - resource_cache); + resource_cache, + frame_id); self.add_border_edge(&Rect::new(Point2D::new(tl_inner.x, tl_outer.y), Size2D::new(tr_inner.x - tl_inner.x, @@ -1530,14 +1549,16 @@ impl<'a> BatchBuilder<'a> { RectSide::Top, &top_color, info.top.style, - resource_cache); + resource_cache, + frame_id); self.add_border_edge(&Rect::new(Point2D::new(br_outer.x - right.width, tr_inner.y), Size2D::new(right.width, br_inner.y - tr_inner.y)), RectSide::Right, &right_color, info.right.style, - resource_cache); + resource_cache, + frame_id); self.add_border_edge(&Rect::new(Point2D::new(bl_inner.x, bl_outer.y - bottom.width), Size2D::new(br_inner.x - bl_inner.x, @@ -1545,7 +1566,8 @@ impl<'a> BatchBuilder<'a> { RectSide::Bottom, &bottom_color, info.bottom.style, - resource_cache); + resource_cache, + frame_id); // Corners self.add_border_corner(info.left.style, @@ -1559,6 +1581,7 @@ impl<'a> BatchBuilder<'a> { &radius.top_left, &info.top_left_inner_radius(), resource_cache, + frame_id, BasicRotationAngle::Upright, device_pixel_ratio); @@ -1573,6 +1596,7 @@ impl<'a> BatchBuilder<'a> { &radius.top_right, &info.top_right_inner_radius(), resource_cache, + frame_id, BasicRotationAngle::Clockwise90, device_pixel_ratio); @@ -1587,6 +1611,7 @@ impl<'a> BatchBuilder<'a> { &radius.bottom_right, &info.bottom_right_inner_radius(), resource_cache, + frame_id, BasicRotationAngle::Clockwise180, device_pixel_ratio); @@ -1601,6 +1626,7 @@ impl<'a> BatchBuilder<'a> { &radius.bottom_left, &info.bottom_left_inner_radius(), resource_cache, + frame_id, BasicRotationAngle::Clockwise270, device_pixel_ratio); } @@ -1617,6 +1643,7 @@ impl<'a> BatchBuilder<'a> { border_radius: f32, clip_mode: BoxShadowClipMode, resource_cache: &ResourceCache, + frame_id: FrameId, rotation_angle: BasicRotationAngle) { let corner_area_rect = Rect::new(*corner_area_top_left, @@ -1636,7 +1663,7 @@ impl<'a> BatchBuilder<'a> { inverted) { Some(raster_item) => { let raster_item = RasterItem::BoxShadow(raster_item); - resource_cache.get_raster(&raster_item) + resource_cache.get_raster(&raster_item, frame_id) } None => resource_cache.get_dummy_color_image(), }; @@ -1661,6 +1688,7 @@ impl<'a> BatchBuilder<'a> { border_radius: f32, clip_mode: BoxShadowClipMode, resource_cache: &ResourceCache, + frame_id: FrameId, rotation_angle: BasicRotationAngle) { if top_left.x >= bottom_right.x || top_left.y >= bottom_right.y { return @@ -1677,7 +1705,7 @@ impl<'a> BatchBuilder<'a> { inverted) { Some(raster_item) => { let raster_item = RasterItem::BoxShadow(raster_item); - resource_cache.get_raster(&raster_item) + resource_cache.get_raster(&raster_item, frame_id) } None => resource_cache.get_dummy_color_image(), }; diff --git a/src/frame.rs b/src/frame.rs index dbe96b0400..31d5d1ea5a 100644 --- a/src/frame.rs +++ b/src/frame.rs @@ -25,6 +25,9 @@ use util; use webrender_traits::{PipelineId, Epoch, ScrollPolicy, ScrollLayerId, StackingContext}; use webrender_traits::{FilterOp, ImageFormat, MixBlendMode, StackingLevel}; +#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)] +pub struct FrameId(pub u32); + pub struct DrawListGroup { pub id: DrawListGroupId, @@ -331,6 +334,7 @@ pub struct Frame { next_draw_list_group_id: DrawListGroupId, draw_list_groups: HashMap>, root_scroll_layer_id: Option, + id: FrameId, } enum SceneItemKind<'a> { @@ -538,6 +542,7 @@ impl Frame { next_draw_list_group_id: DrawListGroupId(0), draw_list_groups: HashMap::with_hash_state(Default::default()), root_scroll_layer_id: None, + id: FrameId(0), } } @@ -559,6 +564,9 @@ impl Frame { old_layer_offsets.insert(layer_id, old_layer.scroll_offset); } + // Advance to the next frame. + self.id.0 += 1; + old_layer_offsets } @@ -1005,13 +1013,10 @@ impl Frame { self.update_texture_cache_and_build_raster_jobs(resource_cache); // Rasterize needed glyphs on worker threads - self.raster_glyphs(thread_pool, - resource_cache); + self.raster_glyphs(thread_pool, resource_cache); // Compile nodes that have become visible - self.compile_visible_nodes(thread_pool, - resource_cache, - device_pixel_ratio); + self.compile_visible_nodes(thread_pool, resource_cache, device_pixel_ratio); // Update the batch cache from newly compiled nodes self.update_batch_cache(); @@ -1022,6 +1027,8 @@ impl Frame { // Collect the visible batches into a frame let frame = self.collect_and_sort_visible_batches(resource_cache, device_pixel_ratio); + resource_cache.expire_old_resources(self.id); + frame } @@ -1079,21 +1086,22 @@ impl Frame { resource_cache: &mut ResourceCache) { let _pf = util::ProfileScope::new(" update_texture_cache_and_build_raster_jobs"); + let frame_id = self.id; for (_, layer) in &self.layers { for node in &layer.aabb_tree.nodes { if node.is_visible { let resource_list = node.resource_list.as_ref().unwrap(); - resource_cache.add_resource_list(resource_list); + resource_cache.add_resource_list(resource_list, frame_id); } } } } pub fn raster_glyphs(&mut self, - thread_pool: &mut scoped_threadpool::Pool, - resource_cache: &mut ResourceCache) { + thread_pool: &mut scoped_threadpool::Pool, + resource_cache: &mut ResourceCache) { let _pf = util::ProfileScope::new(" raster_glyphs"); - resource_cache.raster_pending_glyphs(thread_pool); + resource_cache.raster_pending_glyphs(thread_pool, self.id); } pub fn compile_visible_nodes(&mut self, @@ -1105,6 +1113,7 @@ impl Frame { let layers = &mut self.layers; let stacking_context_info = &self.stacking_context_info; let draw_list_groups = &self.draw_list_groups; + let frame_id = self.id; thread_pool.scoped(|scope| { for (_, layer) in layers { @@ -1113,6 +1122,7 @@ impl Frame { if node.is_visible && node.compiled_node.is_none() { scope.execute(move || { node.compile(resource_cache, + frame_id, device_pixel_ratio, stacking_context_info, draw_list_groups); diff --git a/src/node_compiler.rs b/src/node_compiler.rs index 338a715582..5995b40875 100644 --- a/src/node_compiler.rs +++ b/src/node_compiler.rs @@ -1,7 +1,7 @@ use aabbtree::AABBTreeNode; use batch::{BatchBuilder, VertexBuffer}; use fnv::FnvHasher; -use frame::DrawListGroup; +use frame::{DrawListGroup, FrameId}; use internal_types::{DrawListItemIndex, CompiledNode, StackingContextInfo}; use internal_types::{BatchList, StackingContextIndex}; use internal_types::{DrawListGroupId}; @@ -13,6 +13,7 @@ use webrender_traits::SpecificDisplayItem; pub trait NodeCompiler { fn compile(&mut self, resource_cache: &ResourceCache, + frame_id: FrameId, device_pixel_ratio: f32, stacking_context_info: &Vec, draw_list_groups: &HashMap>); @@ -21,6 +22,7 @@ pub trait NodeCompiler { impl NodeCompiler for AABBTreeNode { fn compile(&mut self, resource_cache: &ResourceCache, + frame_id: FrameId, device_pixel_ratio: f32, stacking_context_info: &Vec, draw_list_groups: &HashMap>) { @@ -67,14 +69,16 @@ impl NodeCompiler for AABBTreeNode { SpecificDisplayItem::WebGL(ref info) => { builder.add_webgl_rectangle(&display_item.rect, resource_cache, - &info.context_id); + &info.context_id, + frame_id); } SpecificDisplayItem::Image(ref info) => { builder.add_image(&display_item.rect, &info.stretch_size, info.image_key, info.image_rendering, - resource_cache); + resource_cache, + frame_id); } SpecificDisplayItem::Text(ref info) => { builder.add_text(&display_item.rect, @@ -84,18 +88,21 @@ impl NodeCompiler for AABBTreeNode { &info.color, &info.glyphs, resource_cache, + frame_id, device_pixel_ratio); } SpecificDisplayItem::Rectangle(ref info) => { builder.add_color_rectangle(&display_item.rect, + &info.color, resource_cache, - &info.color); + frame_id); } SpecificDisplayItem::Gradient(ref info) => { builder.add_gradient(&info.start_point, &info.end_point, &info.stops, - resource_cache); + resource_cache, + frame_id); } SpecificDisplayItem::BoxShadow(ref info) => { builder.add_box_shadow(&info.box_bounds, @@ -105,12 +112,14 @@ impl NodeCompiler for AABBTreeNode { info.spread_radius, info.border_radius, info.clip_mode, - resource_cache); + resource_cache, + frame_id); } SpecificDisplayItem::Border(ref info) => { builder.add_border(&display_item.rect, info, resource_cache, + frame_id, device_pixel_ratio); } } diff --git a/src/resource_cache.rs b/src/resource_cache.rs index 13cf229d15..d901072686 100644 --- a/src/resource_cache.rs +++ b/src/resource_cache.rs @@ -2,6 +2,7 @@ use app_units::Au; use device::{TextureFilter, TextureId}; use euclid::Size2D; use fnv::FnvHasher; +use frame::FrameId; use freelist::FreeList; use internal_types::{FontTemplate, GlyphKey, RasterItem}; use internal_types::{TextureUpdateList, DrawListId, DrawList}; @@ -11,8 +12,10 @@ use resource_list::ResourceList; use scoped_threadpool; use std::cell::RefCell; use std::collections::HashMap; -use std::collections::hash_map::Entry::{Occupied, Vacant}; +use std::collections::hash_map::Entry::{self, Occupied, Vacant}; use std::collections::hash_state::DefaultState; +use std::fmt::Debug; +use std::hash::Hash; use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT}; use std::sync::atomic::Ordering::SeqCst; use std::thread; @@ -44,10 +47,73 @@ struct CachedImageInfo { epoch: Epoch, } +pub struct ResourceClassCache { + resources: HashMap>, + last_access_times: HashMap>, +} + +impl ResourceClassCache where K: Clone + Hash + Eq + Debug, V: Resource { + fn new() -> ResourceClassCache { + ResourceClassCache { + resources: HashMap::with_hash_state(Default::default()), + last_access_times: HashMap::with_hash_state(Default::default()), + } + } + + fn contains_key(&self, key: &K) -> bool { + self.resources.contains_key(key) + } + + fn get(&self, key: &K, frame: FrameId) -> &V { + // This assert catches cases in which we accidentally request a resource that we forgot to + // mark as needed this frame. + debug_assert!(frame == *self.last_access_times + .get(key) + .expect("Didn't find the access time for a cached resource \ + with that ID!")); + self.resources.get(key).expect("Didn't find a cached resource with that ID!") + } + + fn insert(&mut self, key: K, value: V, frame: FrameId) { + self.last_access_times.insert(key.clone(), frame); + self.resources.insert(key, value); + } + + fn entry(&mut self, key: K, frame: FrameId) -> Entry { + self.last_access_times.insert(key.clone(), frame); + self.resources.entry(key) + } + + fn mark_as_needed(&mut self, key: &K, frame: FrameId) { + self.last_access_times.insert((*key).clone(), frame); + } + + fn expire_old_resources(&mut self, texture_cache: &mut TextureCache, frame_id: FrameId) { + let mut resources_to_destroy = vec![]; + for (key, this_frame_id) in self.last_access_times.iter() { + if *this_frame_id < frame_id { + resources_to_destroy.push((*key).clone()) + } + } + for key in resources_to_destroy { + let resource = + self.resources + .remove(&key) + .expect("Resource was in `last_access_times` but not in `resources`!"); + self.last_access_times.remove(&key); + if let Some(texture_cache_item_id) = resource.texture_cache_item_id() { + texture_cache.free(texture_cache_item_id) + } + } + } +} + pub struct ResourceCache { - cached_glyphs: HashMap, DefaultState>, - cached_rasters: HashMap>, - cached_images: HashMap<(ImageKey, ImageRendering), CachedImageInfo, DefaultState>, + cached_glyphs: ResourceClassCache>, + cached_rasters: ResourceClassCache, + cached_images: ResourceClassCache<(ImageKey, ImageRendering), CachedImageInfo>, + + // TODO(pcwalton): Figure out the lifecycle of these. webgl_textures: HashMap>, draw_lists: FreeList, @@ -87,9 +153,9 @@ impl ResourceCache { }); ResourceCache { - cached_glyphs: HashMap::with_hash_state(Default::default()), - cached_rasters: HashMap::with_hash_state(Default::default()), - cached_images: HashMap::with_hash_state(Default::default()), + cached_glyphs: ResourceClassCache::new(), + cached_rasters: ResourceClassCache::new(), + cached_images: ResourceClassCache::new(), webgl_textures: HashMap::with_hash_state(Default::default()), draw_lists: FreeList::new(), font_templates: HashMap::with_hash_state(Default::default()), @@ -156,7 +222,7 @@ impl ResourceCache { self.texture_cache.add_raw_update(texture_id, size); } - pub fn add_resource_list(&mut self, resource_list: &ResourceList) { + pub fn add_resource_list(&mut self, resource_list: &ResourceList, frame_id: FrameId) { // Update texture cache with any GPU generated procedural items. resource_list.for_each_raster(|raster_item| { if !self.cached_rasters.contains_key(raster_item) { @@ -164,8 +230,9 @@ impl ResourceCache { self.texture_cache.insert_raster_op(image_id, raster_item, self.device_pixel_ratio); - self.cached_rasters.insert(raster_item.clone(), image_id); + self.cached_rasters.insert(raster_item.clone(), image_id, frame_id); } + self.cached_rasters.mark_as_needed(raster_item, frame_id); }); // Update texture cache with any images that aren't yet uploaded to GPU. @@ -173,7 +240,7 @@ impl ResourceCache { let cached_images = &mut self.cached_images; let image_template = &self.image_templates[&image_key]; - match cached_images.entry((image_key, image_rendering)) { + match cached_images.entry((image_key, image_rendering), frame_id) { Occupied(entry) => { if entry.get().epoch != image_template.epoch { let image_id = entry.get().texture_cache_id; @@ -227,11 +294,13 @@ impl ResourceCache { result: None, }); } + self.cached_glyphs.mark_as_needed(glyph_key, frame_id); }); } pub fn raster_pending_glyphs(&mut self, - thread_pool: &mut scoped_threadpool::Pool) { + thread_pool: &mut scoped_threadpool::Pool, + frame_id: FrameId) { // Run raster jobs in parallel run_raster_jobs(thread_pool, &mut self.pending_raster_jobs, @@ -276,7 +345,7 @@ impl ResourceCache { } else { None }; - self.cached_glyphs.insert(job.glyph_key, image_id); + self.cached_glyphs.insert(job.glyph_key, image_id, frame_id); } } @@ -323,34 +392,41 @@ impl ResourceCache { } #[inline] - pub fn get_glyph(&self, glyph_key: &GlyphKey) -> Option<&TextureCacheItem> { - let image_id = self.cached_glyphs[glyph_key]; + pub fn get_glyph(&self, glyph_key: &GlyphKey, frame_id: FrameId) -> Option<&TextureCacheItem> { + let image_id = self.cached_glyphs.get(glyph_key, frame_id); image_id.map(|image_id| self.texture_cache.get(image_id)) } #[inline] pub fn get_image(&self, image_key: ImageKey, - image_rendering: ImageRendering) -> &TextureCacheItem { - let image_info = &self.cached_images[&(image_key, image_rendering)]; + image_rendering: ImageRendering, + frame_id: FrameId) + -> &TextureCacheItem { + let image_info = &self.cached_images.get(&(image_key, image_rendering), frame_id); self.texture_cache.get(image_info.texture_cache_id) } #[inline] - pub fn get_raster(&self, raster_item: &RasterItem) -> &TextureCacheItem { - let image_id = self.cached_rasters[raster_item]; - self.texture_cache.get(image_id) + pub fn get_raster(&self, raster_item: &RasterItem, frame_id: FrameId) -> &TextureCacheItem { + let image_id = self.cached_rasters.get(raster_item, frame_id); + self.texture_cache.get(*image_id) } #[inline] - pub fn get_webgl_texture(&self, - context_id: &WebGLContextId) -> TextureId { + pub fn get_webgl_texture(&self, context_id: &WebGLContextId) -> TextureId { self.webgl_textures.get(context_id).unwrap().clone() } pub fn device_pixel_ratio(&self) -> f32 { self.device_pixel_ratio } + + pub fn expire_old_resources(&mut self, frame_id: FrameId) { + self.cached_glyphs.expire_old_resources(&mut self.texture_cache, frame_id); + self.cached_rasters.expire_old_resources(&mut self.texture_cache, frame_id); + self.cached_images.expire_old_resources(&mut self.texture_cache, frame_id); + } } fn run_raster_jobs(thread_pool: &mut scoped_threadpool::Pool, @@ -388,3 +464,26 @@ fn run_raster_jobs(thread_pool: &mut scoped_threadpool::Pool, } }); } + +pub trait Resource { + fn texture_cache_item_id(&self) -> Option; +} + +impl Resource for TextureCacheItemId { + fn texture_cache_item_id(&self) -> Option { + Some(*self) + } +} + +impl Resource for Option { + fn texture_cache_item_id(&self) -> Option { + *self + } +} + +impl Resource for CachedImageInfo { + fn texture_cache_item_id(&self) -> Option { + Some(self.texture_cache_id) + } +} + diff --git a/src/texture_cache.rs b/src/texture_cache.rs index 18e5365a6f..4221192915 100644 --- a/src/texture_cache.rs +++ b/src/texture_cache.rs @@ -297,7 +297,6 @@ impl TexturePage { self.dirty = false; } -/* fn free(&mut self, rect: &Rect) { debug_assert!(self.allocations > 0); self.allocations -= 1; @@ -308,7 +307,7 @@ impl TexturePage { self.free_list.push(rect); self.dirty = true - }*/ + } } // TODO(gw): This is used to store data specific to glyphs. @@ -513,6 +512,18 @@ impl TextureCacheArena { //render_target_pages: Vec::new(), } } + + fn texture_page_for_id(&mut self, id: TextureId) -> &mut TexturePage { + for page in self.pages_a8.iter_mut().chain(self.pages_rgb8.iter_mut()) + .chain(self.pages_rgba8.iter_mut()) + .chain(self.alternate_pages_a8.iter_mut()) + .chain(self.alternate_pages_rgba8.iter_mut()) { + if page.texture_id == id { + return page + } + } + panic!("No texture page for ID {:?}", id) + } } pub struct TextureCache { @@ -1066,6 +1077,13 @@ impl TextureCache { self.items.get(id) } + pub fn free(&mut self, id: TextureCacheItemId) { + { + let item = self.items.get(id); + self.arena.texture_page_for_id(item.texture_id).free(&item.allocated_rect); + } + self.items.free(id) + } } fn texture_create_op(texture_size: u32, format: ImageFormat, mode: RenderTargetMode)