From ef0bbf2dc2d5b7a85b960c8227bd748961254032 Mon Sep 17 00:00:00 2001 From: Aaron Klotz Date: Tue, 7 Apr 2020 04:01:59 +0000 Subject: [PATCH 1/4] Bug 1627354: Part 5 - Update wrench to build using Android 29; r=kats Depends on D69634 Differential Revision: https://phabricator.services.mozilla.com/D69648 [ghsync] From https://hg.mozilla.org/mozilla-central/rev/c98499d204fb8df645daf3e2841d0b3955141e90 --- wrench/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wrench/Cargo.toml b/wrench/Cargo.toml index 933c14b17f..d64c8d5e6f 100644 --- a/wrench/Cargo.toml +++ b/wrench/Cargo.toml @@ -57,7 +57,7 @@ font-loader = "0.7" [package.metadata.android] package_name = "org.mozilla.wrench" label = "Wrench" -android_version = 28 +android_version = 29 target_sdk_version = 18 min_sdk_version = 18 fullscreen = true From e1b91f00187e3ad283b37051110f29d60cebcf90 Mon Sep 17 00:00:00 2001 From: Dzmitry Malyshau Date: Tue, 7 Apr 2020 04:02:07 +0000 Subject: [PATCH 2/4] Bug 1626827 - Force WR picture tasks to fit into max target size r=Bert this is an attempt to handle tasks outside of the device bounds, that belong to surfaces not establishing raster roots. I suspect that the scaling we are now setting up in adjust_scale_for_max_surface_size doesn't work properly, since the function was assumed to only affect the raster-rooted surfaces. But it does fix the crash we have. Differential Revision: https://phabricator.services.mozilla.com/D69654 [ghsync] From https://hg.mozilla.org/mozilla-central/rev/745c38db6468c26d2ee4c0cdfc5f9ec0b6733db9 --- webrender/src/frame_builder.rs | 1 + webrender/src/picture.rs | 57 ++++++++++++++++++++-------------- webrender/src/renderer.rs | 1 + webrender/src/scene.rs | 1 + 4 files changed, 36 insertions(+), 24 deletions(-) diff --git a/webrender/src/frame_builder.rs b/webrender/src/frame_builder.rs index 2942ba2855..88f838442f 100644 --- a/webrender/src/frame_builder.rs +++ b/webrender/src/frame_builder.rs @@ -67,6 +67,7 @@ pub struct FrameBuilderConfig { pub compositor_kind: CompositorKind, pub tile_size_override: Option, pub max_depth_ids: i32, + pub max_target_size: i32, } /// A set of common / global resources that are retained between diff --git a/webrender/src/picture.rs b/webrender/src/picture.rs index 73ce7c1a2c..d8faf40c95 100644 --- a/webrender/src/picture.rs +++ b/webrender/src/picture.rs @@ -4630,6 +4630,7 @@ impl PicturePrimitive { /// font size, etc. need to be scaled accordingly. fn adjust_scale_for_max_surface_size( raster_config: &RasterConfig, + max_target_size: i32, pic_rect: PictureRect, map_pic_to_raster: &SpaceMapper, map_raster_to_world: &SpaceMapper, @@ -4638,13 +4639,15 @@ impl PicturePrimitive { device_rect: &mut DeviceIntRect, unclipped: &mut DeviceRect) -> Option { - if raster_config.establishes_raster_root && - (device_rect.size.width > (MAX_SURFACE_SIZE as i32) || - device_rect.size.height > (MAX_SURFACE_SIZE as i32)) - { + let limit = if raster_config.establishes_raster_root { + MAX_SURFACE_SIZE as i32 + } else { + max_target_size + }; + if device_rect.size.width > limit || device_rect.size.height > limit { // round_out will grow by 1 integer pixel if origin is on a // fractional position, so keep that margin for error with -1: - let scale = (MAX_SURFACE_SIZE as f32 - 1.0) / + let scale = (limit as f32 - 1.0) / (i32::max(device_rect.size.width, device_rect.size.height) as f32); *device_pixel_scale = *device_pixel_scale * Scale::new(scale); let new_device_rect = device_rect.to_f32() * Scale::new(scale); @@ -4717,10 +4720,11 @@ impl PicturePrimitive { ); if let Some(scale) = adjust_scale_for_max_surface_size( - raster_config, pic_rect, &map_pic_to_raster, &map_raster_to_world, - clipped_prim_bounding_rect, - &mut device_pixel_scale, &mut device_rect, &mut unclipped) - { + raster_config, frame_context.fb_config.max_target_size, + pic_rect, &map_pic_to_raster, &map_raster_to_world, + clipped_prim_bounding_rect, + &mut device_pixel_scale, &mut device_rect, &mut unclipped, + ) { blur_std_deviation = blur_std_deviation * scale; original_size = (original_size.to_f32() * scale).try_cast::().unwrap(); raster_config.root_scaling_factor = scale; @@ -4791,10 +4795,11 @@ impl PicturePrimitive { ); if let Some(scale) = adjust_scale_for_max_surface_size( - raster_config, pic_rect, &map_pic_to_raster, &map_raster_to_world, + raster_config, frame_context.fb_config.max_target_size, + pic_rect, &map_pic_to_raster, &map_raster_to_world, clipped_prim_bounding_rect, - &mut device_pixel_scale, &mut device_rect, &mut unclipped) - { + &mut device_pixel_scale, &mut device_rect, &mut unclipped, + ) { // std_dev adjusts automatically from using device_pixel_scale raster_config.root_scaling_factor = scale; } @@ -4889,10 +4894,11 @@ impl PicturePrimitive { PictureCompositeMode::Filter(..) => { if let Some(scale) = adjust_scale_for_max_surface_size( - raster_config, pic_rect, &map_pic_to_raster, &map_raster_to_world, + raster_config, frame_context.fb_config.max_target_size, + pic_rect, &map_pic_to_raster, &map_raster_to_world, clipped_prim_bounding_rect, - &mut device_pixel_scale, &mut clipped, &mut unclipped) - { + &mut device_pixel_scale, &mut clipped, &mut unclipped, + ) { raster_config.root_scaling_factor = scale; } @@ -4922,10 +4928,11 @@ impl PicturePrimitive { } PictureCompositeMode::ComponentTransferFilter(..) => { if let Some(scale) = adjust_scale_for_max_surface_size( - raster_config, pic_rect, &map_pic_to_raster, &map_raster_to_world, + raster_config, frame_context.fb_config.max_target_size, + pic_rect, &map_pic_to_raster, &map_raster_to_world, clipped_prim_bounding_rect, - &mut device_pixel_scale, &mut clipped, &mut unclipped) - { + &mut device_pixel_scale, &mut clipped, &mut unclipped, + ) { raster_config.root_scaling_factor = scale; } @@ -5228,10 +5235,11 @@ impl PicturePrimitive { PictureCompositeMode::MixBlend(..) | PictureCompositeMode::Blit(_) => { if let Some(scale) = adjust_scale_for_max_surface_size( - raster_config, pic_rect, &map_pic_to_raster, &map_raster_to_world, + raster_config, frame_context.fb_config.max_target_size, + pic_rect, &map_pic_to_raster, &map_raster_to_world, clipped_prim_bounding_rect, - &mut device_pixel_scale, &mut clipped, &mut unclipped) - { + &mut device_pixel_scale, &mut clipped, &mut unclipped, + ) { raster_config.root_scaling_factor = scale; } @@ -5262,10 +5270,11 @@ impl PicturePrimitive { PictureCompositeMode::SvgFilter(ref primitives, ref filter_datas) => { if let Some(scale) = adjust_scale_for_max_surface_size( - raster_config, pic_rect, &map_pic_to_raster, &map_raster_to_world, + raster_config, frame_context.fb_config.max_target_size, + pic_rect, &map_pic_to_raster, &map_raster_to_world, clipped_prim_bounding_rect, - &mut device_pixel_scale, &mut clipped, &mut unclipped) - { + &mut device_pixel_scale, &mut clipped, &mut unclipped, + ) { raster_config.root_scaling_factor = scale; } diff --git a/webrender/src/renderer.rs b/webrender/src/renderer.rs index fe17540e59..49becbd30d 100644 --- a/webrender/src/renderer.rs +++ b/webrender/src/renderer.rs @@ -2276,6 +2276,7 @@ impl Renderer { compositor_kind, tile_size_override: None, max_depth_ids: device.max_depth_ids(), + max_target_size: max_texture_size, }; info!("WR {:?}", config); diff --git a/webrender/src/scene.rs b/webrender/src/scene.rs index 7498f9dae5..3caf68ab60 100644 --- a/webrender/src/scene.rs +++ b/webrender/src/scene.rs @@ -309,6 +309,7 @@ impl BuiltScene { compositor_kind: CompositorKind::default(), tile_size_override: None, max_depth_ids: 0, + max_target_size: 0, }, } } From 0cc830cf9bd0f0e3513a8ea096d744b3b9ad4ec3 Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Tue, 7 Apr 2020 04:02:16 +0000 Subject: [PATCH 3/4] Bug 1627588 - Fix picture cache tiles being evicted too eagerly. r=nical The picture cache code retains a set of tiles that are currently off-screen but might be needed again soon, depending on how the page is scrolled. However, off-screen tiles were being skipped during draw processing, which meant that the texture cache request method was not being called on these tiles. This would often result in the texture cache eagerly evicting these seemingly unused surface tiles. This patch re-arranges the occlusion and visibility processing code for tiles, so that if a tile has been retained in the picture cache grid, the texture surface is always requested, even if that tile is currently off-screen. This prevents the texture cache from evicting tiles that we want to retain for now. Differential Revision: https://phabricator.services.mozilla.com/D69760 [ghsync] From https://hg.mozilla.org/mozilla-central/rev/c39512f89aedf7c0745137da9c8c09a54f9ab2e8 --- webrender/src/picture.rs | 71 +++++++++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 26 deletions(-) diff --git a/webrender/src/picture.rs b/webrender/src/picture.rs index d8faf40c95..9dc072f4b7 100644 --- a/webrender/src/picture.rs +++ b/webrender/src/picture.rs @@ -4972,37 +4972,56 @@ impl PicturePrimitive { let device_clip_rect = (world_clip_rect * frame_context.global_device_pixel_scale).round(); for tile in tile_cache.tiles.values_mut() { - if !tile.is_visible { - continue; - } - // Get the world space rect that this tile will actually occupy on screem - let device_draw_rect = match device_clip_rect.intersection(&tile.device_valid_rect) { - Some(rect) => rect, - None => { - tile.is_visible = false; - continue; - } - }; + // Only check for occlusion on visible tiles that are fixed position. + if tile.is_visible && tile_cache.spatial_node_index == ROOT_SPATIAL_NODE_INDEX { + // Get the world space rect that this tile will actually occupy on screem + let device_draw_rect = device_clip_rect.intersection(&tile.device_valid_rect); + + // If that draw rect is occluded by some set of tiles in front of it, + // then mark it as not visible and skip drawing. When it's not occluded + // it will fail this test, and get rasterized by the render task setup + // code below. + match device_draw_rect { + Some(device_draw_rect) => { + if frame_state.composite_state.is_tile_occluded(tile.z_id, device_draw_rect) { + // If this tile has an allocated native surface, free it, since it's completely + // occluded. We will need to re-allocate this surface if it becomes visible, + // but that's likely to be rare (e.g. when there is no content display list + // for a frame or two during a tab switch). + let surface = tile.surface.as_mut().expect("no tile surface set!"); + + if let TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { id, .. }, .. } = surface { + if let Some(id) = id.take() { + frame_state.resource_cache.destroy_compositor_tile(id); + } + } - // If that draw rect is occluded by some set of tiles in front of it, - // then mark it as not visible and skip drawing. When it's not occluded - // it will fail this test, and get rasterized by the render task setup - // code below. - if frame_state.composite_state.is_tile_occluded(tile.z_id, device_draw_rect) { - // If this tile has an allocated native surface, free it, since it's completely - // occluded. We will need to re-allocate this surface if it becomes visible, - // but that's likely to be rare (e.g. when there is no content display list - // for a frame or two during a tab switch). - let surface = tile.surface.as_mut().expect("no tile surface set!"); - - if let TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { id, .. }, .. } = surface { - if let Some(id) = id.take() { - frame_state.resource_cache.destroy_compositor_tile(id); + tile.is_visible = false; + continue; + } + } + None => { + tile.is_visible = false; } } + } + + // If we get here, we want to ensure that the surface remains valid in the texture + // cache, _even if_ it's not visible due to clipping or being scrolled off-screen. + // This ensures that we retain valid tiles that are off-screen, but still in the + // display port of this tile cache instance. + if let Some(TileSurface::Texture { descriptor, .. }) = tile.surface.as_ref() { + if let SurfaceTextureDescriptor::TextureCache { ref handle, .. } = descriptor { + frame_state.resource_cache.texture_cache.request( + handle, + frame_state.gpu_cache, + ); + } + } - tile.is_visible = false; + // If the tile has been found to be off-screen / clipped, skip any further processing. + if !tile.is_visible { continue; } From 82bb47036ebb794d2ea64a100cf6b2dfd1214000 Mon Sep 17 00:00:00 2001 From: Bert Peers Date: Tue, 7 Apr 2020 04:02:25 +0000 Subject: [PATCH 4/4] Bug 1624468 - Add a fast path for more gradient types in WR r=gw Differential Revision: https://phabricator.services.mozilla.com/D68945 [ghsync] From https://hg.mozilla.org/mozilla-central/rev/376011a4881e966bf359cda3d10658191525af13 --- webrender/src/batch.rs | 98 ++++++----- webrender/src/prim_store/gradient.rs | 22 +-- webrender/src/prim_store/mod.rs | 152 ++++++++++++++---- .../gradient/gradient_cache_5stops.yaml | 13 ++ .../gradient/gradient_cache_5stops_ref.yaml | 18 +++ .../gradient_cache_5stops_vertical.yaml | 13 ++ .../gradient_cache_5stops_vertical_ref.yaml | 18 +++ .../gradient/gradient_cache_clamp.yaml | 20 +++ .../gradient/gradient_cache_clamp_ref.yaml | 30 ++++ .../gradient/gradient_cache_hardstop.yaml | 19 +++ .../gradient_cache_hardstop_clip.yaml | 21 +++ .../gradient_cache_hardstop_clip_ref.yaml | 28 ++++ .../gradient/gradient_cache_hardstop_ref.yaml | 24 +++ wrench/reftests/gradient/reftest.list | 11 +- 14 files changed, 399 insertions(+), 88 deletions(-) create mode 100644 wrench/reftests/gradient/gradient_cache_5stops.yaml create mode 100644 wrench/reftests/gradient/gradient_cache_5stops_ref.yaml create mode 100644 wrench/reftests/gradient/gradient_cache_5stops_vertical.yaml create mode 100644 wrench/reftests/gradient/gradient_cache_5stops_vertical_ref.yaml create mode 100644 wrench/reftests/gradient/gradient_cache_clamp.yaml create mode 100644 wrench/reftests/gradient/gradient_cache_clamp_ref.yaml create mode 100644 wrench/reftests/gradient/gradient_cache_hardstop.yaml create mode 100644 wrench/reftests/gradient/gradient_cache_hardstop_clip.yaml create mode 100644 wrench/reftests/gradient/gradient_cache_hardstop_clip_ref.yaml create mode 100644 wrench/reftests/gradient/gradient_cache_hardstop_ref.yaml diff --git a/webrender/src/batch.rs b/webrender/src/batch.rs index cfa766029e..54635b1e45 100644 --- a/webrender/src/batch.rs +++ b/webrender/src/batch.rs @@ -2268,53 +2268,69 @@ impl BatchBuilder { BlendMode::None }; - if let Some(ref cache_handle) = gradient.cache_handle { - let rt_cache_entry = ctx.resource_cache - .get_cached_render_task(cache_handle); - let cache_item = ctx.resource_cache - .get_texture_cache_item(&rt_cache_entry.handle); + if !gradient.cache_segments.is_empty() { - if cache_item.texture_id == TextureSource::Invalid { - return; - } + for segment in &gradient.cache_segments { + let ref cache_handle = segment.handle; + let rt_cache_entry = ctx.resource_cache + .get_cached_render_task(cache_handle); + let cache_item = ctx.resource_cache + .get_texture_cache_item(&rt_cache_entry.handle); - let textures = BatchTextures::color(cache_item.texture_id); - let batch_kind = BrushBatchKind::Image(get_buffer_kind(cache_item.texture_id)); - let prim_user_data = ImageBrushData { - color_mode: ShaderColorMode::Image, - alpha_type: AlphaType::PremultipliedAlpha, - raster_space: RasterizationSpace::Local, - opacity: 1.0, - }.encode(); + if cache_item.texture_id == TextureSource::Invalid { + return; + } - let specific_resource_address = cache_item.uv_rect_handle.as_int(gpu_cache); - prim_header.specific_prim_address = gpu_cache.get_address(&ctx.globals.default_image_handle); + let textures = BatchTextures::color(cache_item.texture_id); + let batch_kind = BrushBatchKind::Image(get_buffer_kind(cache_item.texture_id)); + let prim_user_data = ImageBrushData { + color_mode: ShaderColorMode::Image, + alpha_type: AlphaType::PremultipliedAlpha, + raster_space: RasterizationSpace::Local, + opacity: 1.0, + }.encode(); + + let specific_resource_address = cache_item.uv_rect_handle.as_int(gpu_cache); + prim_header.specific_prim_address = gpu_cache.get_address(&ctx.globals.default_image_handle); + + let segment_local_clip_rect = prim_header.local_clip_rect.intersection(&segment.local_rect); + if segment_local_clip_rect.is_none() { + continue; + } - let prim_header_index = prim_headers.push( - &prim_header, - z_id, - prim_user_data, - ); + let segment_prim_header = PrimitiveHeader { + local_rect: segment.local_rect, + local_clip_rect: segment_local_clip_rect.unwrap(), + specific_prim_address: prim_header.specific_prim_address, + transform_id: prim_header.transform_id, + }; - let batch_key = BatchKey { - blend_mode: non_segmented_blend_mode, - kind: BatchKind::Brush(batch_kind), - textures, - }; + let prim_header_index = prim_headers.push( + &segment_prim_header, + z_id, + prim_user_data, + ); - self.add_brush_instance_to_batches( - batch_key, - batch_features, - bounding_rect, - z_id, - INVALID_SEGMENT_INDEX, - EdgeAaSegmentMask::all(), - clip_task_address.unwrap(), - BrushFlags::PERSPECTIVE_INTERPOLATION, - prim_header_index, - specific_resource_address, - prim_vis_mask, - ); + let batch_key = BatchKey { + blend_mode: non_segmented_blend_mode, + kind: BatchKind::Brush(batch_kind), + textures, + }; + + self.add_brush_instance_to_batches( + batch_key, + batch_features, + bounding_rect, + z_id, + INVALID_SEGMENT_INDEX, + EdgeAaSegmentMask::all(), + clip_task_address.unwrap(), + BrushFlags::PERSPECTIVE_INTERPOLATION, + prim_header_index, + specific_resource_address, + prim_vis_mask, + ); + } } else if gradient.visible_tiles_range.is_empty() { let batch_params = BrushBatchParameters::shared( BrushBatchKind::LinearGradient, diff --git a/webrender/src/prim_store/gradient.rs b/webrender/src/prim_store/gradient.rs index b8d59c8030..9e2c27be55 100644 --- a/webrender/src/prim_store/gradient.rs +++ b/webrender/src/prim_store/gradient.rs @@ -13,11 +13,10 @@ use crate::frame_builder::FrameBuildingState; use crate::gpu_cache::{GpuCacheHandle, GpuDataRequest}; use crate::intern::{Internable, InternDebug, Handle as InternHandle}; use crate::internal_types::LayoutPrimitiveInfo; -use crate::prim_store::{BrushSegment, GradientTileRange, VectorKey}; +use crate::prim_store::{BrushSegment, CachedGradientSegment, GradientTileRange, VectorKey}; use crate::prim_store::{PrimitiveInstanceKind, PrimitiveOpacity, PrimitiveSceneData}; use crate::prim_store::{PrimKeyCommonData, PrimTemplateCommonData, PrimitiveStore}; use crate::prim_store::{NinePatchDescriptor, PointKey, SizeKey, InternablePrimitive}; -use crate::render_task_cache::RenderTaskCacheEntryHandle; use std::{hash, ops::{Deref, DerefMut}}; use crate::util::pack_as_float; @@ -152,7 +151,7 @@ impl From for LinearGradientTemplate { // gradient in a smaller task, and drawing as an image. // TODO(gw): Aim to reduce the constraints on fast path gradients in future, // although this catches the vast majority of gradients on real pages. - let mut supports_caching = + let supports_caching = // No repeating support in fast path item.extend_mode == ExtendMode::Clamp && // Gradient must cover entire primitive @@ -161,28 +160,15 @@ impl From for LinearGradientTemplate { // Must be a vertical or horizontal gradient (item.start_point.x.approx_eq(&item.end_point.x) || item.start_point.y.approx_eq(&item.end_point.y)) && - // Fast path supports a limited number of stops - item.stops.len() <= GRADIENT_FP_STOPS && // Fast path not supported on segmented (border-image) gradients. item.nine_patch.is_none(); - let mut prev_offset = None; // Convert the stops to more convenient representation // for the current gradient builder. let stops: Vec = item.stops.iter().map(|stop| { let color: ColorF = stop.color.into(); min_alpha = min_alpha.min(color.a); - // The fast path doesn't support hard color stops, yet. - // Since the length of the gradient is a fixed size (512 device pixels), if there - // is a hard stop you will see bilinear interpolation with this method, instead - // of an abrupt color change. - if prev_offset == Some(stop.offset) { - supports_caching = false; - } - - prev_offset = Some(stop.offset); - GradientStop { offset: stop.offset, color, @@ -318,7 +304,7 @@ impl InternablePrimitive for LinearGradient { _reference_frame_relative_offset: LayoutVector2D, ) -> PrimitiveInstanceKind { let gradient_index = prim_store.linear_gradients.push(LinearGradientPrimitive { - cache_handle: None, + cache_segments: Vec::new(), visible_tiles_range: GradientTileRange::empty(), }); @@ -338,7 +324,7 @@ impl IsVisible for LinearGradient { #[derive(Debug)] #[cfg_attr(feature = "capture", derive(Serialize))] pub struct LinearGradientPrimitive { - pub cache_handle: Option, + pub cache_segments: Vec, pub visible_tiles_range: GradientTileRange, } diff --git a/webrender/src/prim_store/mod.rs b/webrender/src/prim_store/mod.rs index c9c16df149..82d17aa441 100644 --- a/webrender/src/prim_store/mod.rs +++ b/webrender/src/prim_store/mod.rs @@ -970,6 +970,13 @@ pub struct VisibleGradientTile { pub local_clip_rect: LayoutRect, } +#[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +pub struct CachedGradientSegment { + pub handle: RenderTaskCacheEntryHandle, + pub local_rect: LayoutRect, +} + /// Information about how to cache a border segment, /// along with the current render task cache entry. #[cfg_attr(feature = "capture", derive(Serialize))] @@ -3284,7 +3291,7 @@ impl PrimitiveStore { }; // Build the cache key, including information about the stops. - let mut stops = [GradientStopKey::empty(); GRADIENT_FP_STOPS]; + let mut stops = vec![GradientStopKey::empty(); prim_data.stops.len()]; // Reverse the stops as required, same as the gradient builder does // for the slow path. @@ -3302,35 +3309,124 @@ impl PrimitiveStore { } } - let cache_key = GradientCacheKey { - orientation, - start_stop_point: VectorKey { - x: start_point, - y: end_point, - }, - stops, - }; + // To support clamping, we need to make sure that quads are emitted for the + // segments before and after the 0.0...1.0 range of offsets. The loop below + // can handle that by duplicating the first and last point if necessary: + if start_point < 0.0 { + stops.insert(0, GradientStopKey { + offset: start_point, + color : stops[0].color + }); + } - // Request the render task each frame. - gradient.cache_handle = Some(frame_state.resource_cache.request_render_task( - RenderTaskCacheKey { - size, - kind: RenderTaskCacheKeyKind::Gradient(cache_key), - }, - frame_state.gpu_cache, - frame_state.render_tasks, - None, - prim_data.stops_opacity.is_opaque, - |render_tasks| { - render_tasks.add().init(RenderTask::new_gradient( - size, - stops, - orientation, - start_point, - end_point, - )) + if end_point > 1.0 { + stops.push( GradientStopKey { + offset: end_point, + color : stops[stops.len()-1].color + }); + } + + gradient.cache_segments.clear(); + + let mut first_stop = 0; + // look for an inclusive range of stops [first_stop, last_stop]. + // once first_stop points at (or past) the last stop, we're done. + while first_stop < stops.len()-1 { + + // if the entire segment starts at an offset that's past the primitive's + // end_point, we're done. + if stops[first_stop].offset > end_point { + break; } - )); + + // accumulate stops until we have GRADIENT_FP_STOPS of them, or we hit + // a hard stop: + let mut last_stop = first_stop; + let mut hard_stop = false; // did we stop on a hard stop? + while last_stop < stops.len()-1 && + last_stop - first_stop + 1 < GRADIENT_FP_STOPS + { + if stops[last_stop+1].offset == stops[last_stop].offset { + hard_stop = true; + break; + } + + last_stop = last_stop + 1; + } + + let num_stops = last_stop - first_stop + 1; + + // repeated hard stops at the same offset, skip + if num_stops == 0 { + first_stop = last_stop + 1; + continue; + } + + // if the last stop offset is before start_point, the segment's not visible: + if stops[last_stop].offset < start_point { + first_stop = if hard_stop { last_stop+1 } else { last_stop }; + continue; + } + + let segment_start_point = start_point.max(stops[first_stop].offset); + let segment_end_point = end_point .min(stops[last_stop ].offset); + + let mut segment_stops = [GradientStopKey::empty(); GRADIENT_FP_STOPS]; + for i in 0..num_stops { + segment_stops[i] = stops[first_stop + i]; + } + + let cache_key = GradientCacheKey { + orientation, + start_stop_point: VectorKey { + x: segment_start_point, + y: segment_end_point, + }, + stops: segment_stops, + }; + + let mut prim_origin = prim_instance.prim_origin; + let mut prim_size = prim_data.common.prim_size; + + let inv_length = 1.0 / ( end_point - start_point ); + if orientation == LineOrientation::Horizontal { + prim_origin.x += ( segment_start_point - start_point ) * inv_length * prim_size.width; + prim_size.width *= ( segment_end_point - segment_start_point ) * inv_length; + } else { + prim_origin.y += ( segment_start_point - start_point ) * inv_length * prim_size.height; + prim_size.height *= ( segment_end_point - segment_start_point ) * inv_length; + } + + let local_rect = LayoutRect::new( prim_origin, prim_size ); + + // Request the render task each frame. + gradient.cache_segments.push( + CachedGradientSegment { + handle: frame_state.resource_cache.request_render_task( + RenderTaskCacheKey { + size, + kind: RenderTaskCacheKeyKind::Gradient(cache_key), + }, + frame_state.gpu_cache, + frame_state.render_tasks, + None, + prim_data.stops_opacity.is_opaque, + |render_tasks| { + render_tasks.add().init(RenderTask::new_gradient( + size, + segment_stops, + orientation, + segment_start_point, + segment_end_point, + )) + }), + local_rect: local_rect, + } + ); + + // if ending on a hardstop, skip past it for the start of the next run: + first_stop = if hard_stop { last_stop + 1 } else { last_stop }; + } } if prim_data.tile_spacing != LayoutSize::zero() { diff --git a/wrench/reftests/gradient/gradient_cache_5stops.yaml b/wrench/reftests/gradient/gradient_cache_5stops.yaml new file mode 100644 index 0000000000..d448723002 --- /dev/null +++ b/wrench/reftests/gradient/gradient_cache_5stops.yaml @@ -0,0 +1,13 @@ +--- +root: + items: + - type: gradient + bounds: 0 0 960 540 + start: 0 0 + end: 960 0 + stops: [0.0, red, + 0.25, green, + 0.5, blue, + 0.75, [40,40,40,1], + 1.0, [100,200,50,1]] + diff --git a/wrench/reftests/gradient/gradient_cache_5stops_ref.yaml b/wrench/reftests/gradient/gradient_cache_5stops_ref.yaml new file mode 100644 index 0000000000..34b6b0e01c --- /dev/null +++ b/wrench/reftests/gradient/gradient_cache_5stops_ref.yaml @@ -0,0 +1,18 @@ +--- +root: + items: + - type: gradient + bounds: 0 0 480 540 + start: 0 0 + end: 480 0 + stops: [0.0, red, + 0.5, green, + 1.0, blue] + - type: gradient + bounds: 480 0 480 540 + start: 0 0 + end: 480 0 + stops: [ 0.0, blue, + 0.5, [40,40,40,1], + 1.0, [100,200,50,1]] + diff --git a/wrench/reftests/gradient/gradient_cache_5stops_vertical.yaml b/wrench/reftests/gradient/gradient_cache_5stops_vertical.yaml new file mode 100644 index 0000000000..dd2c8b7c9d --- /dev/null +++ b/wrench/reftests/gradient/gradient_cache_5stops_vertical.yaml @@ -0,0 +1,13 @@ +--- +root: + items: + - type: gradient + bounds: 0 0 960 540 + start: 0 0 + end: 0 540 + stops: [0.0, red, + 0.25, green, + 0.5, blue, + 0.75, [40,40,40,1], + 1.0, [100,200,50,1]] + diff --git a/wrench/reftests/gradient/gradient_cache_5stops_vertical_ref.yaml b/wrench/reftests/gradient/gradient_cache_5stops_vertical_ref.yaml new file mode 100644 index 0000000000..704b5be2f6 --- /dev/null +++ b/wrench/reftests/gradient/gradient_cache_5stops_vertical_ref.yaml @@ -0,0 +1,18 @@ +--- +root: + items: + - type: gradient + bounds: 0 0 960 270 + start: 0 0 + end: 0 270 + stops: [0.0, red, + 0.5, green, + 1.0, blue] + - type: gradient + bounds: 0 270 960 270 + start: 0 0 + end: 0 270 + stops: [ 0.0, blue, + 0.5, [40,40,40,1], + 1.0, [100,200,50,1]] + diff --git a/wrench/reftests/gradient/gradient_cache_clamp.yaml b/wrench/reftests/gradient/gradient_cache_clamp.yaml new file mode 100644 index 0000000000..1c55a269a1 --- /dev/null +++ b/wrench/reftests/gradient/gradient_cache_clamp.yaml @@ -0,0 +1,20 @@ +--- +root: + items: + - type: gradient + bounds: 0 0 400 200 + start: 0 100 + end: 100 100 + stops: [0.0, blue, 1.0, blue, 1.0, red] + - type: gradient + bounds: 0 300 400 200 + start: 100 100 + end: 200 100 + stops: [0.0, blue, 1.0, blue, 1.0, red] + - type: gradient + bounds: 0 600 200 400 + start: 0 100 + end: 0 300 + stops: [ + 0.0, blue, + 1.0, red] diff --git a/wrench/reftests/gradient/gradient_cache_clamp_ref.yaml b/wrench/reftests/gradient/gradient_cache_clamp_ref.yaml new file mode 100644 index 0000000000..4631192cd8 --- /dev/null +++ b/wrench/reftests/gradient/gradient_cache_clamp_ref.yaml @@ -0,0 +1,30 @@ +--- +root: + items: + - type: gradient + bounds: 0 0 400 200 + start: 0 100 + end: 400 100 + stops: [ + 0.0, blue, + 0.25, blue, + 0.25, red, + 1.0, red] + - type: gradient + bounds: 0 300 400 200 + start: 0 100 + end: 400 100 + stops: [ + 0.0, blue, + 0.5, blue, + 0.5, red, + 1.0, red] + - type: gradient + bounds: 0 600 200 400 + start: 0 0 + end: 0 400 + stops: [ + 0.0, blue, + 0.25, blue, + 0.75, red, + 1.0, red] diff --git a/wrench/reftests/gradient/gradient_cache_hardstop.yaml b/wrench/reftests/gradient/gradient_cache_hardstop.yaml new file mode 100644 index 0000000000..53c908fb22 --- /dev/null +++ b/wrench/reftests/gradient/gradient_cache_hardstop.yaml @@ -0,0 +1,19 @@ +--- +root: + items: + - type: gradient + bounds: 0 0 960 540 + start: 0 0 + end: 960 0 + stops: [0.0, red, + 0.125, yellow, + 0.25, red, + 0.25, green, + 0.375, yellow, + 0.5, green, + 0.5, blue, + 0.625, yellow, + 0.75, blue, + 0.75, white, + 1.0, [100,200,50,1]] + diff --git a/wrench/reftests/gradient/gradient_cache_hardstop_clip.yaml b/wrench/reftests/gradient/gradient_cache_hardstop_clip.yaml new file mode 100644 index 0000000000..3e7a2e946f --- /dev/null +++ b/wrench/reftests/gradient/gradient_cache_hardstop_clip.yaml @@ -0,0 +1,21 @@ +--- +root: + items: + - type: gradient + bounds: 0 0 960 540 + start: 0 0 + end: 960 0 + stops: [0.0, red, + 0.125, yellow, + 0.25, red, + 0.25, green, + 0.375, yellow, + 0.5, green, + 0.5, blue, + 0.625, yellow, + 0.75, blue, + 0.75, white, + 1.0, [100,200,50,1]] + complex-clip: + rect: [100, 100, 760, 340] + radius: [32, 32] diff --git a/wrench/reftests/gradient/gradient_cache_hardstop_clip_ref.yaml b/wrench/reftests/gradient/gradient_cache_hardstop_clip_ref.yaml new file mode 100644 index 0000000000..2b27c5649c --- /dev/null +++ b/wrench/reftests/gradient/gradient_cache_hardstop_clip_ref.yaml @@ -0,0 +1,28 @@ +--- +root: + items: + - type: gradient + bounds: 0 0 480 540 + start: 0 0 + end: 480 0 + stops: [0.0, red, + 0.25, yellow, + 0.5, red, + 0.5, green, + 0.75, yellow, + 1.0, green] + complex-clip: + rect: [100, 100, 760, 340] + radius: [32, 32] + - type: gradient + bounds: 480 0 480 540 + start: 0 0 + end: 480 0 + stops: [0.0, blue, + 0.25, yellow, + 0.5, blue, + 0.5, white, + 1.0, [100,200,50,1]] + complex-clip: + rect: [100, 100, 760, 340] + radius: [32, 32] diff --git a/wrench/reftests/gradient/gradient_cache_hardstop_ref.yaml b/wrench/reftests/gradient/gradient_cache_hardstop_ref.yaml new file mode 100644 index 0000000000..e4b3928046 --- /dev/null +++ b/wrench/reftests/gradient/gradient_cache_hardstop_ref.yaml @@ -0,0 +1,24 @@ +--- +root: + items: + - type: gradient + bounds: 0 0 480 540 + start: 0 0 + end: 480 0 + stops: [0.0, red, + 0.25, yellow, + 0.5, red, + 0.5, green, + 0.75, yellow, + 1.0, green] + - type: gradient + bounds: 480 0 480 540 + start: 0 0 + end: 480 0 + stops: [0.0, blue, + 0.25, yellow, + 0.5, blue, + 0.5, white, + 1.0, [100,200,50,1]] + + diff --git a/wrench/reftests/gradient/reftest.list b/wrench/reftests/gradient/reftest.list index ac0b6454b4..c3b3eee9cd 100644 --- a/wrench/reftests/gradient/reftest.list +++ b/wrench/reftests/gradient/reftest.list @@ -16,7 +16,7 @@ platform(linux,mac) fuzzy(1,35000) == linear-stops.yaml linear-stops-ref.png == linear-clamp-1b.yaml linear-clamp-1-ref.yaml == linear-clamp-2.yaml linear-clamp-2-ref.yaml -== linear-hard-stop.yaml linear-hard-stop-ref.png +fuzzy-range(<=1,*4800) == linear-hard-stop.yaml linear-hard-stop-ref.png # dithering requires us to fuzz here fuzzy(1,20000) == linear.yaml linear-ref.yaml @@ -85,3 +85,12 @@ fuzzy(255,166) == conic-angle.yaml conic-angle.png fuzzy(1,1) == conic-angle-wraparound.yaml conic-angle.yaml fuzzy(1,1) == conic-angle-wraparound-negative.yaml conic-angle.yaml fuzzy(1,115) == conic-color-wheel.yaml conic-color-wheel.png + +# gradient caching tests +# replaces a computed gradient by a sampled texture, so a lot of off-by-one +# variation from interpolation, which is fine: +fuzzy-range(<=1,*195000) == gradient_cache_5stops.yaml gradient_cache_5stops_ref.yaml +fuzzy-range(<=1,*169000) == gradient_cache_5stops_vertical.yaml gradient_cache_5stops_vertical_ref.yaml +== gradient_cache_hardstop.yaml gradient_cache_hardstop_ref.yaml +== gradient_cache_hardstop_clip.yaml gradient_cache_hardstop_clip_ref.yaml +== gradient_cache_clamp.yaml gradient_cache_clamp_ref.yaml