diff --git a/webrender/res/ps_image.glsl b/webrender/res/ps_image.glsl deleted file mode 100644 index 3f0c14c335..0000000000 --- a/webrender/res/ps_image.glsl +++ /dev/null @@ -1,106 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include shared,prim_shared - -// If this is in WR_FEATURE_TEXTURE_RECT mode, the rect and size use non-normalized -// texture coordinates. Otherwise, it uses normalized texture coordinates. Please -// check GL_TEXTURE_RECTANGLE. -flat varying vec2 vTextureOffset; // Offset of this image into the texture atlas. -flat varying vec2 vTextureSize; // Size of the image in the texture atlas. -flat varying vec2 vTileSpacing; // Amount of space between tiled instances of this image. -flat varying vec4 vStRect; // Rectangle of valid texture rect. -flat varying float vLayer; - -#ifdef WR_FEATURE_TRANSFORM -flat varying vec4 vLocalRect; -#endif - -varying vec2 vLocalPos; -flat varying vec2 vStretchSize; - -#ifdef WR_VERTEX_SHADER -void main(void) { - Primitive prim = load_primitive(); - Image image = fetch_image(prim.specific_prim_address); - ImageResource res = fetch_image_resource(prim.user_data0); - -#ifdef WR_FEATURE_TRANSFORM - VertexInfo vi = write_transform_vertex_primitive(prim); - vLocalPos = vi.local_pos; - vLocalRect = vec4(prim.local_rect.p0, prim.local_rect.p0 + prim.local_rect.size); -#else - VertexInfo vi = write_vertex(prim.local_rect, - prim.local_clip_rect, - prim.z, - prim.scroll_node, - prim.task, - prim.local_rect); - vLocalPos = vi.local_pos - prim.local_rect.p0; -#endif - - write_clip(vi.screen_pos, prim.clip_area); - - // If this is in WR_FEATURE_TEXTURE_RECT mode, the rect and size use - // non-normalized texture coordinates. -#ifdef WR_FEATURE_TEXTURE_RECT - vec2 texture_size_normalization_factor = vec2(1, 1); -#else - vec2 texture_size_normalization_factor = vec2(textureSize(sColor0, 0)); -#endif - - vec2 uv0 = res.uv_rect.p0; - vec2 uv1 = res.uv_rect.p1; - - // vUv will contain how many times this image has wrapped around the image size. - vec2 st0 = uv0 / texture_size_normalization_factor; - vec2 st1 = uv1 / texture_size_normalization_factor; - - vLayer = res.layer; - vTextureSize = st1 - st0; - vTextureOffset = st0; - vTileSpacing = image.stretch_size_and_tile_spacing.zw; - vStretchSize = image.stretch_size_and_tile_spacing.xy; - - // We clamp the texture coordinates to the half-pixel offset from the borders - // in order to avoid sampling outside of the texture area. - vec2 half_texel = vec2(0.5) / texture_size_normalization_factor; - vStRect = vec4(min(st0, st1) + half_texel, max(st0, st1) - half_texel); -} -#endif - -#ifdef WR_FRAGMENT_SHADER -void main(void) { -#ifdef WR_FEATURE_TRANSFORM - float alpha = init_transform_fs(vLocalPos); - - // We clamp the texture coordinate calculation here to the local rectangle boundaries, - // which makes the edge of the texture stretch instead of repeat. - vec2 upper_bound_mask = step(vLocalRect.zw, vLocalPos); - vec2 relative_pos_in_rect = clamp(vLocalPos, vLocalRect.xy, vLocalRect.zw) - vLocalRect.xy; -#else - float alpha = 1.0; - vec2 relative_pos_in_rect = vLocalPos; - vec2 upper_bound_mask = vec2(0.0); -#endif - - alpha *= do_clip(); - - // We calculate the particular tile this fragment belongs to, taking into - // account the spacing in between tiles. We only paint if our fragment does - // not fall into that spacing. - // If the pixel is at the local rectangle upper bound, we force the current - // tile upper bound in order to avoid wrapping. - vec2 position_in_tile = mix( - mod(relative_pos_in_rect, vStretchSize + vTileSpacing), - vStretchSize, - upper_bound_mask); - vec2 st = vTextureOffset + ((position_in_tile / vStretchSize) * vTextureSize); - st = clamp(st, vStRect.xy, vStRect.zw); - - alpha = alpha * float(all(bvec2(step(position_in_tile, vStretchSize)))); - - oFragColor = vec4(alpha) * TEX_SAMPLE(sColor0, vec3(st, vLayer)); -} -#endif diff --git a/webrender/src/batch.rs b/webrender/src/batch.rs index 936e47a99f..de27c5d8a3 100644 --- a/webrender/src/batch.rs +++ b/webrender/src/batch.rs @@ -40,7 +40,6 @@ const OPAQUE_TASK_ADDRESS: RenderTaskAddress = RenderTaskAddress(0x7fff); #[cfg_attr(feature = "replay", derive(Deserialize))] pub enum TransformBatchKind { TextRun(GlyphFormat), - Image(ImageBufferKind), BorderCorner, BorderEdge, } @@ -608,7 +607,26 @@ impl AlphaBatchBuilder { screen_rect.unclipped.size, ); - let prim_cache_address = gpu_cache.get_address(&prim_metadata.gpu_location); + // If the primitive is internally decomposed into multiple sub-primitives we may not + // use some of the per-primitive data typically stored in PrimitiveMetadata and get + // it from each sub-primitive instead. + let is_multiple_primitives = match prim_metadata.prim_kind { + PrimitiveKind::Brush => { + let brush = &ctx.prim_store.cpu_brushes[prim_metadata.cpu_prim_index.0]; + match brush.kind { + BrushKind::Image { ref visible_tiles, .. } => !visible_tiles.is_empty(), + _ => false, + } + } + _ => false, + }; + + let prim_cache_address = if is_multiple_primitives { + GpuCacheAddress::invalid() + } else { + gpu_cache.get_address(&prim_metadata.gpu_location) + }; + let no_textures = BatchTextures::no_texture(); let clip_task_address = prim_metadata .clip_task_id @@ -974,6 +992,32 @@ impl AlphaBatchBuilder { ); } } + BrushKind::Image { request, ref visible_tiles, .. } if !visible_tiles.is_empty() => { + for tile in visible_tiles { + if let Some((batch_kind, textures, user_data)) = get_image_tile_params( + ctx.resource_cache, + gpu_cache, + deferred_resolves, + request.with_tile(tile.tile_offset), + ) { + let prim_cache_address = gpu_cache.get_address(&tile.handle); + self.add_image_tile_to_batch( + batch_kind, + specified_blend_mode, + textures, + clip_chain_rect_index, + clip_task_address, + &task_relative_bounding_rect, + prim_cache_address, + scroll_id, + task_address, + z, + user_data, + tile.edge_flags + ); + } + } + } _ => { if let Some((batch_kind, textures, user_data)) = brush.get_batch_params( ctx.resource_cache, @@ -1064,50 +1108,6 @@ impl AlphaBatchBuilder { } } } - PrimitiveKind::Image => { - let image_cpu = &ctx.prim_store.cpu_images[prim_metadata.cpu_prim_index.0]; - - let cache_item = match image_cpu.source { - ImageSource::Default => { - resolve_image( - image_cpu.key.request, - ctx.resource_cache, - gpu_cache, - deferred_resolves, - ) - } - ImageSource::Cache { ref handle, .. } => { - let rt_handle = handle - .as_ref() - .expect("bug: render task handle not allocated"); - let rt_cache_entry = ctx - .resource_cache - .get_cached_render_task(rt_handle); - ctx.resource_cache.get_texture_cache_item(&rt_cache_entry.handle) - } - }; - - if cache_item.texture_id == SourceTexture::Invalid { - warn!("Warnings: skip a PrimitiveKind::Image"); - debug!("at {:?}.", task_relative_bounding_rect); - return; - } - - let batch_kind = TransformBatchKind::Image(get_buffer_kind(cache_item.texture_id)); - let key = BatchKey::new( - BatchKind::Transformable(transform_kind, batch_kind), - non_segmented_blend_mode, - BatchTextures { - colors: [ - cache_item.texture_id, - SourceTexture::Invalid, - SourceTexture::Invalid, - ], - }, - ); - let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect); - batch.push(base_instance.build(cache_item.uv_rect_handle.as_int(gpu_cache), 0, 0)); - } PrimitiveKind::TextRun => { let text_cpu = &ctx.prim_store.cpu_text_runs[prim_metadata.cpu_prim_index.0]; @@ -1210,6 +1210,45 @@ impl AlphaBatchBuilder { } } + fn add_image_tile_to_batch( + &mut self, + batch_kind: BrushBatchKind, + blend_mode: BlendMode, + textures: BatchTextures, + clip_chain_rect_index: ClipChainRectIndex, + clip_task_address: RenderTaskAddress, + task_relative_bounding_rect: &DeviceIntRect, + prim_cache_address: GpuCacheAddress, + scroll_id: ClipScrollNodeIndex, + task_address: RenderTaskAddress, + z: ZBufferId, + user_data: [i32; 3], + edge_flags: EdgeAaSegmentMask, + ) { + let base_instance = BrushInstance { + picture_address: task_address, + prim_address: prim_cache_address, + clip_chain_rect_index, + scroll_id, + clip_task_address, + z, + segment_index: 0, + edge_flags, + brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION, + user_data, + }; + + self.batch_list.add_bounding_rect(task_relative_bounding_rect); + + let batch_key = BatchKey { + blend_mode, + kind: BatchKind::Brush(batch_kind), + textures, + }; + let batch = self.batch_list.get_suitable_batch(batch_key, task_relative_bounding_rect); + batch.push(PrimitiveInstance::from(base_instance)); + } + fn add_brush_to_batch( &mut self, brush: &BrushPrimitive, @@ -1308,6 +1347,37 @@ impl AlphaBatchBuilder { } } +fn get_image_tile_params( + resource_cache: &ResourceCache, + gpu_cache: &mut GpuCache, + deferred_resolves: &mut Vec, + request: ImageRequest, +) -> Option<(BrushBatchKind, BatchTextures, [i32; 3])> { + + let cache_item = resolve_image( + request, + resource_cache, + gpu_cache, + deferred_resolves, + ); + + if cache_item.texture_id == SourceTexture::Invalid { + None + } else { + let textures = BatchTextures::color(cache_item.texture_id); + Some(( + BrushBatchKind::Image(get_buffer_kind(cache_item.texture_id)), + textures, + [ + cache_item.uv_rect_handle.as_int(gpu_cache), + (ShaderColorMode::ColorBitmap as i32) << 16 | + RasterizationSpace::Local as i32, + 0, + ], + )) + } +} + impl BrushPrimitive { pub fn get_picture_index(&self) -> PictureIndex { match self.kind { @@ -1501,13 +1571,6 @@ impl AlphaBatchHelpers for PrimitiveStore { } } } - PrimitiveKind::Image => { - let image_cpu = &self.cpu_images[metadata.cpu_prim_index.0]; - match image_cpu.alpha_type { - AlphaType::PremultipliedAlpha => BlendMode::PremultipliedAlpha, - AlphaType::Alpha => BlendMode::Alpha, - } - } } } } diff --git a/webrender/src/display_list_flattener.rs b/webrender/src/display_list_flattener.rs index e669cd75e6..46a2c3a8d1 100644 --- a/webrender/src/display_list_flattener.rs +++ b/webrender/src/display_list_flattener.rs @@ -11,7 +11,7 @@ use api::{IframeDisplayItem, ImageKey, ImageRendering, ItemRange, LayoutPoint, L use api::{LayoutRect, LayoutVector2D, LayoutSize, LayoutTransform}; use api::{LineOrientation, LineStyle, LocalClip, PipelineId, PropertyBinding}; use api::{RepeatMode, ScrollFrameDisplayItem, ScrollPolicy, ScrollSensitivity, Shadow}; -use api::{SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, TexelRect, TileOffset}; +use api::{SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, TexelRect}; use api::{TransformStyle, YuvColorSpace, YuvData}; use app_units::Au; use border::ImageBorderSegment; @@ -22,15 +22,15 @@ use euclid::{SideOffsets2D, vec2}; use frame_builder::{FrameBuilder, FrameBuilderConfig}; use glyph_rasterizer::FontInstance; use hit_test::{HitTestingItem, HitTestingRun}; -use image::{decompose_image, TiledImageInfo}; +use image::simplify_repeated_primitive; use internal_types::{FastHashMap, FastHashSet}; use picture::PictureCompositeMode; use prim_store::{BrushKind, BrushPrimitive, BrushSegmentDescriptor, CachedGradient}; -use prim_store::{CachedGradientIndex, ImageCacheKey, ImagePrimitiveCpu, ImageSource}; +use prim_store::{CachedGradientIndex, ImageSource}; use prim_store::{PictureIndex, PrimitiveContainer, PrimitiveIndex, PrimitiveStore}; use prim_store::{OpacityBinding, ScrollNodeAndClipChain, TextRunPrimitiveCpu}; use render_backend::{DocumentView}; -use resource_cache::{FontInstanceMap, ImageRequest, TiledImageMap}; +use resource_cache::{FontInstanceMap, ImageRequest}; use scene::{Scene, ScenePipeline, StackingContextHelpers}; use scene_builder::{BuiltScene, SceneRequest}; use std::{f32, mem, usize}; @@ -150,9 +150,6 @@ pub struct DisplayListFlattener<'a> { /// The map of all font instances. font_instances: FontInstanceMap, - /// The map of tiled images. - tiled_image_map: TiledImageMap, - /// Used to track the latest flattened epoch for each pipeline. pipeline_epochs: Vec<(PipelineId, Epoch)>, @@ -207,7 +204,6 @@ impl<'a> DisplayListFlattener<'a> { scene: &Scene, clip_scroll_tree: &mut ClipScrollTree, font_instances: FontInstanceMap, - tiled_image_map: TiledImageMap, view: &DocumentView, output_pipelines: &FastHashSet, frame_builder_config: &FrameBuilderConfig, @@ -227,7 +223,6 @@ impl<'a> DisplayListFlattener<'a> { scene, clip_scroll_tree, font_instances, - tiled_image_map, config: *frame_builder_config, pipeline_epochs: Vec::new(), replacements: Vec::new(), @@ -635,49 +630,16 @@ impl<'a> DisplayListFlattener<'a> { let prim_info = item.get_layout_primitive_info(&reference_frame_relative_offset); match *item.item() { SpecificDisplayItem::Image(ref info) => { - match self.tiled_image_map.get(&info.image_key).cloned() { - Some(tiling) => { - // The image resource is tiled. We have to generate an image primitive - // for each tile. - decompose_image( - &TiledImageInfo { - rect: prim_info.rect, - tile_spacing: info.tile_spacing, - stretch_size: info.stretch_size, - device_image_size: tiling.image_size, - device_tile_size: tiling.tile_size as u32, - }, - &mut|tile| { - let mut prim_info = prim_info.clone(); - prim_info.rect = tile.rect; - self.add_image( - clip_and_scroll, - &prim_info, - tile.stretch_size, - info.tile_spacing, - None, - info.image_key, - info.image_rendering, - info.alpha_type, - Some(tile.tile_offset), - ); - } - ); - } - None => { - self.add_image( - clip_and_scroll, - &prim_info, - info.stretch_size, - info.tile_spacing, - None, - info.image_key, - info.image_rendering, - info.alpha_type, - None, - ); - } - } + self.add_image( + clip_and_scroll, + &prim_info, + info.stretch_size, + info.tile_spacing, + None, + info.image_key, + info.image_rendering, + info.alpha_type, + ); } SpecificDisplayItem::YuvImage(ref info) => { self.add_yuv_image( @@ -1786,7 +1748,6 @@ impl<'a> DisplayListFlattener<'a> { border.image_key, ImageRendering::Auto, AlphaType::PremultipliedAlpha, - None, ); } } @@ -1892,11 +1853,18 @@ impl<'a> DisplayListFlattener<'a> { stops_count: usize, extend_mode: ExtendMode, stretch_size: LayoutSize, - tile_spacing: LayoutSize, + mut tile_spacing: LayoutSize, ) { let gradient_index = CachedGradientIndex(self.cached_gradients.len()); self.cached_gradients.push(CachedGradient::new()); + let mut prim_rect = info.rect; + simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect); + let info = LayoutPrimitiveInfo { + rect: prim_rect, + .. *info + }; + if tile_spacing != LayoutSize::zero() { let prim_infos = info.decompose( stretch_size, @@ -1925,7 +1893,7 @@ impl<'a> DisplayListFlattener<'a> { self.add_gradient_impl( clip_and_scroll, - info, + &info, start_point, end_point, stops, @@ -1982,11 +1950,18 @@ impl<'a> DisplayListFlattener<'a> { stops: ItemRange, extend_mode: ExtendMode, stretch_size: LayoutSize, - tile_spacing: LayoutSize, + mut tile_spacing: LayoutSize, ) { let gradient_index = CachedGradientIndex(self.cached_gradients.len()); self.cached_gradients.push(CachedGradient::new()); + let mut prim_rect = info.rect; + simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect); + let info = LayoutPrimitiveInfo { + rect: prim_rect, + .. *info + }; + if tile_spacing != LayoutSize::zero() { let prim_infos = info.decompose( stretch_size, @@ -2016,7 +1991,7 @@ impl<'a> DisplayListFlattener<'a> { self.add_radial_gradient_impl( clip_and_scroll, - info, + &info, center, start_radius, end_radius, @@ -2121,20 +2096,12 @@ impl<'a> DisplayListFlattener<'a> { image_key: ImageKey, image_rendering: ImageRendering, alpha_type: AlphaType, - tile_offset: Option, ) { - // If the tile spacing is the same as the rect size, - // then it is effectively zero. We use this later on - // in prim_store to detect if an image can be considered - // opaque. - if tile_spacing == info.rect.size { - tile_spacing = LayoutSize::zero(); - } - - let request = ImageRequest { - key: image_key, - rendering: image_rendering, - tile: tile_offset, + let mut prim_rect = info.rect; + simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect); + let info = LayoutPrimitiveInfo { + rect: prim_rect, + .. *info }; let sub_rect = sub_rect.map(|texel_rect| { @@ -2150,50 +2117,31 @@ impl<'a> DisplayListFlattener<'a> { ) }); - // See if conditions are met to run through the new - // image brush shader, which supports segments. - if tile_spacing == LayoutSize::zero() && - tile_offset.is_none() { - let prim = BrushPrimitive::new( - BrushKind::Image { - request, - current_epoch: Epoch::invalid(), - alpha_type, - stretch_size, - tile_spacing, - source: ImageSource::Default, - sub_rect, - opacity_binding: OpacityBinding::new(), + let prim = BrushPrimitive::new( + BrushKind::Image { + request: ImageRequest { + key: image_key, + rendering: image_rendering, + tile: None, }, - None, - ); - - self.add_primitive( - clip_and_scroll, - info, - Vec::new(), - PrimitiveContainer::Brush(prim), - ); - } else { - let prim_cpu = ImagePrimitiveCpu { - tile_spacing, + current_epoch: Epoch::invalid(), alpha_type, stretch_size, - current_epoch: Epoch::invalid(), + tile_spacing, source: ImageSource::Default, - key: ImageCacheKey { - request, - texel_rect: sub_rect, - }, - }; + sub_rect, + visible_tiles: Vec::new(), + opacity_binding: OpacityBinding::new(), + }, + None, + ); - self.add_primitive( - clip_and_scroll, - info, - Vec::new(), - PrimitiveContainer::Image(prim_cpu), - ); - } + self.add_primitive( + clip_and_scroll, + &info, + Vec::new(), + PrimitiveContainer::Brush(prim), + ); } pub fn add_yuv_image( @@ -2240,7 +2188,6 @@ pub fn build_scene(config: &FrameBuilderConfig, request: SceneRequest) -> BuiltS &request.scene, &mut clip_scroll_tree, request.font_instances, - request.tiled_image_map, &request.view, &request.output_pipelines, config, diff --git a/webrender/src/image.rs b/webrender/src/image.rs index 4ada110452..181cb566fd 100644 --- a/webrender/src/image.rs +++ b/webrender/src/image.rs @@ -2,105 +2,103 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use api::{TileOffset, LayoutRect, LayoutSize, LayoutVector2D, DeviceUintSize}; -use euclid::rect; +use api::{TileOffset, LayoutRect, LayoutSize, LayoutPoint, DeviceUintSize}; +use euclid::vec2; +use prim_store::EdgeAaSegmentMask; + +/// If repetitions are far enough apart that only one is within +/// the primitive rect, then we can simplify the parameters and +/// treat the primitive as not repeated. +/// This can let us avoid unnecessary work later to handle some +/// of the parameters. +pub fn simplify_repeated_primitive( + stretch_size: &LayoutSize, + tile_spacing: &mut LayoutSize, + prim_rect: &mut LayoutRect, +) { + let stride = *stretch_size + *tile_spacing; -pub struct DecomposedTile { - pub rect: LayoutRect, - pub stretch_size: LayoutSize, - pub tile_offset: TileOffset, + if stride.width >= prim_rect.size.width { + tile_spacing.width = 0.0; + prim_rect.size.width = f32::min(prim_rect.size.width, stretch_size.width); + } + if stride.height >= prim_rect.size.height { + tile_spacing.height = 0.0; + prim_rect.size.height = f32::min(prim_rect.size.height, stretch_size.height); + } } -pub struct TiledImageInfo { - /// The bounds of the item in layout space. - pub rect: LayoutRect, - /// The space between each repeated pattern in layout space. - pub tile_spacing: LayoutSize, - /// The size in layout space of each repetition of the image. - pub stretch_size: LayoutSize, - - /// The size the image occupies in the cache in device space. - pub device_image_size: DeviceUintSize, - /// The size of the tiles in the cache in device pixels. - pub device_tile_size: u32, -} +pub fn for_each_repetition( + prim_rect: &LayoutRect, + visible_rect: &LayoutRect, + stride: &LayoutSize, + callback: &mut FnMut(&LayoutPoint, EdgeAaSegmentMask), +) { + assert!(stride.width > 0.0); + assert!(stride.height > 0.0); -/// Decomposes an image that is repeated into an image per individual repetition. -/// We need to do this when we are unable to perform the repetition in the shader, -/// for example if the image is tiled. -/// -/// In all of the "decompose" methods below, we independently handle horizontal and vertical -/// decomposition. This lets us generate the minimum amount of primitives by, for example, -/// decomposing the repetition horizontally while repeating vertically in the shader (for -/// an image where the width is too bug but the height is not). -/// -/// decompose_image and decompose_row handle image repetitions while decompose_cache_tiles -/// takes care of the decomposition required by the internal tiling of the image in the cache. -/// -/// Note that the term tiling is overloaded: There is the tiling we get from repeating images -/// in layout space, and the tiling that we do in the texture cache (to avoid hitting texture -/// size limits). The latter is referred to as "device" tiling here to disambiguate. -pub fn decompose_image(info: &TiledImageInfo, callback: &mut FnMut(&DecomposedTile)) { - - let no_vertical_tiling = info.device_image_size.height <= info.device_tile_size; - let no_vertical_spacing = info.tile_spacing.height == 0.0; - - if no_vertical_tiling && no_vertical_spacing { - decompose_row(&info.rect, info, callback); - return; - } + let visible_rect = match prim_rect.intersection(&visible_rect) { + Some(rect) => rect, + None => return, + }; - // Decompose each vertical repetition into rows. - let layout_stride = info.stretch_size.height + info.tile_spacing.height; - let num_repetitions = (info.rect.size.height / layout_stride).ceil() as u32; + let nx = if visible_rect.origin.x > prim_rect.origin.x { + f32::floor((visible_rect.origin.x - prim_rect.origin.x) / stride.width) + } else { + 0.0 + }; - for i in 0 .. num_repetitions { - let row_rect = rect( - info.rect.origin.x, - info.rect.origin.y + (i as f32) * layout_stride, - info.rect.size.width, - info.stretch_size.height, - ).intersection(&info.rect); + let ny = if visible_rect.origin.y > prim_rect.origin.y { + f32::floor((visible_rect.origin.y - prim_rect.origin.y) / stride.height) + } else { + 0.0 + }; - if let Some(row_rect) = row_rect { - decompose_row(&row_rect, info, callback); - } - } -} + let x0 = prim_rect.origin.x + nx * stride.width; + let y0 = prim_rect.origin.y + ny * stride.height; + let mut p = LayoutPoint::new(x0, y0); -fn decompose_row(item_rect: &LayoutRect, info: &TiledImageInfo, callback: &mut FnMut(&DecomposedTile)) { + let x_most = visible_rect.max_x(); + let y_most = visible_rect.max_y(); - let no_horizontal_tiling = info.device_image_size.width <= info.device_tile_size; - let no_horizontal_spacing = info.tile_spacing.width == 0.0; + let x_count = f32::ceil((x_most - x0) / stride.width) as i32; + let y_count = f32::ceil((y_most - y0) / stride.height) as i32; - if no_horizontal_tiling && no_horizontal_spacing { - decompose_cache_tiles(item_rect, info, callback); - return; - } + for y in 0..y_count { + let mut row_flags = EdgeAaSegmentMask::empty(); + if y == 0 { + row_flags |= EdgeAaSegmentMask::TOP; + } + if y == y_count - 1 { + row_flags |= EdgeAaSegmentMask::BOTTOM; + } - // Decompose each horizontal repetition. - let layout_stride = info.stretch_size.width + info.tile_spacing.width; - let num_repetitions = (item_rect.size.width / layout_stride).ceil() as u32; + for x in 0..x_count { + let mut edge_flags = row_flags; + if x == 0 { + edge_flags |= EdgeAaSegmentMask::LEFT; + } + if x == x_count - 1 { + edge_flags |= EdgeAaSegmentMask::RIGHT; + } - for i in 0 .. num_repetitions { - let decomposed_rect = rect( - item_rect.origin.x + (i as f32) * layout_stride, - item_rect.origin.y, - info.stretch_size.width, - item_rect.size.height, - ).intersection(item_rect); + callback(&p, edge_flags); - if let Some(decomposed_rect) = decomposed_rect { - decompose_cache_tiles(&decomposed_rect, info, callback); + p.x += stride.width; } + + p.x = x0; + p.y += stride.height; } } -fn decompose_cache_tiles( - item_rect: &LayoutRect, - info: &TiledImageInfo, - callback: &mut FnMut(&DecomposedTile), +pub fn for_each_tile( + prim_rect: &LayoutRect, + visible_rect: &LayoutRect, + device_image_size: &DeviceUintSize, + device_tile_size: u32, + callback: &mut FnMut(&LayoutRect, TileOffset, EdgeAaSegmentMask), ) { // The image resource is tiled. We have to generate an image primitive // for each tile. @@ -123,158 +121,110 @@ fn decompose_cache_tiles( // In the ascii diagram above, a large image is split into tiles of almost regular size. // The tiles on the right and bottom edges (hatched in the diagram) are smaller than // the regular tiles and are handled separately in the code see leftover_width/height. - // each generated image primitive corresponds to a tile in the texture cache, with the + // each generated segment corresponds to a tile in the texture cache, with the // assumption that the smaller tiles with leftover sizes are sized to fit their own // irregular size in the texture cache. - // - // For the case where we don't tile along an axis, we can still perform the repetition in - // the shader (for this particular axis), and it is worth special-casing for this to avoid - // generating many primitives. - // This can happen with very tall and thin images used as a repeating background. - // Apparently web authors do that... - let needs_repeat_x = info.stretch_size.width < item_rect.size.width; - let needs_repeat_y = info.stretch_size.height < item_rect.size.height; + // Because we can have very large virtual images we iterate over the visible portion of + // the image in layer space intead of iterating over device tiles. - let tiled_in_x = info.device_image_size.width > info.device_tile_size; - let tiled_in_y = info.device_image_size.height > info.device_tile_size; + let visible_rect = match prim_rect.intersection(&visible_rect) { + Some(rect) => rect, + None => return, + }; - // If we don't actually tile in this dimension, repeating can be done in the shader. - let shader_repeat_x = needs_repeat_x && !tiled_in_x; - let shader_repeat_y = needs_repeat_y && !tiled_in_y; + let device_tile_size_f32 = device_tile_size as f32; - let tile_size_f32 = info.device_tile_size as f32; + // Ratio between (image space) tile size and image size . + let tile_dw = device_tile_size_f32 / (device_image_size.width as f32); + let tile_dh = device_tile_size_f32 / (device_image_size.height as f32); - // Note: this rounds down so it excludes the partially filled tiles on the right and - // bottom edges (we handle them separately below). - let num_tiles_x = (info.device_image_size.width / info.device_tile_size) as u16; - let num_tiles_y = (info.device_image_size.height / info.device_tile_size) as u16; - - // Ratio between (image space) tile size and image size. - let img_dw = tile_size_f32 / (info.device_image_size.width as f32); - let img_dh = tile_size_f32 / (info.device_image_size.height as f32); - - // Stretched size of the tile in layout space. - let stretched_tile_size = LayoutSize::new( - img_dw * info.stretch_size.width, - img_dh * info.stretch_size.height, + // size of regular tiles in layout space. + let layer_tile_size = LayoutSize::new( + tile_dw * prim_rect.size.width, + tile_dh * prim_rect.size.height, ); // The size in pixels of the tiles on the right and bottom edges, smaller // than the regular tile size if the image is not a multiple of the tile size. // Zero means the image size is a multiple of the tile size. - let leftover = DeviceUintSize::new( - info.device_image_size.width % info.device_tile_size, - info.device_image_size.height % info.device_tile_size + let leftover_device_size = DeviceUintSize::new( + device_image_size.width % device_tile_size, + device_image_size.height % device_tile_size + ); + + // The size in layer space of the tiles on the right and bottom edges. + let leftover_layer_size = LayoutSize::new( + layer_tile_size.width * leftover_device_size.width as f32 / device_tile_size_f32, + layer_tile_size.height * leftover_device_size.height as f32 / device_tile_size_f32, ); - for ty in 0 .. num_tiles_y { - for tx in 0 .. num_tiles_x { - add_device_tile( - item_rect, - stretched_tile_size, - TileOffset::new(tx, ty), - 1.0, - 1.0, - shader_repeat_x, - shader_repeat_y, - callback, - ); + // Offset of the row and column of tiles with leftover size. + let leftover_offset = TileOffset::new( + (device_image_size.width / device_tile_size) as u16, + (device_image_size.height / device_tile_size) as u16, + ); + + // Number of culled out tiles to skip before the first visible tile. + let t0 = TileOffset::new( + if visible_rect.origin.x > prim_rect.origin.x { + f32::floor((visible_rect.origin.x - prim_rect.origin.x) / layer_tile_size.width) as u16 + } else { + 0 + }, + if visible_rect.origin.y > prim_rect.origin.y { + f32::floor((visible_rect.origin.y - prim_rect.origin.y) / layer_tile_size.height) as u16 + } else { + 0 + }, + ); + + // Position of the first visible tile (top-left) in layer space. + let x0 = prim_rect.origin.x + t0.x as f32 * layer_tile_size.width; + let y0 = prim_rect.origin.y + t0.y as f32 * layer_tile_size.height; + + let x_count = f32::ceil((visible_rect.max_x() - x0) / layer_tile_size.width) as u16; + let y_count = f32::ceil((visible_rect.max_y() - y0) / layer_tile_size.height) as u16; + + for y in 0..y_count { + + let mut row_flags = EdgeAaSegmentMask::empty(); + if y == 0 { + row_flags |= EdgeAaSegmentMask::TOP; } - if leftover.width != 0 { - // Tiles on the right edge that are smaller than the tile size. - add_device_tile( - item_rect, - stretched_tile_size, - TileOffset::new(num_tiles_x, ty), - (leftover.width as f32) / tile_size_f32, - 1.0, - shader_repeat_x, - shader_repeat_y, - callback, - ); + if y == y_count - 1 { + row_flags |= EdgeAaSegmentMask::BOTTOM; } - } - if leftover.height != 0 { - for tx in 0 .. num_tiles_x { - // Tiles on the bottom edge that are smaller than the tile size. - add_device_tile( - item_rect, - stretched_tile_size, - TileOffset::new(tx, num_tiles_y), - 1.0, - (leftover.height as f32) / tile_size_f32, - shader_repeat_x, - shader_repeat_y, - callback, - ); - } + for x in 0..x_count { + let tile_offset = t0 + vec2(x, y); - if leftover.width != 0 { - // Finally, the bottom-right tile with a "leftover" size. - add_device_tile( - item_rect, - stretched_tile_size, - TileOffset::new(num_tiles_x, num_tiles_y), - (leftover.width as f32) / tile_size_f32, - (leftover.height as f32) / tile_size_f32, - shader_repeat_x, - shader_repeat_y, - callback, - ); - } - } -} -fn add_device_tile( - item_rect: &LayoutRect, - stretched_tile_size: LayoutSize, - tile_offset: TileOffset, - tile_ratio_width: f32, - tile_ratio_height: f32, - shader_repeat_x: bool, - shader_repeat_y: bool, - callback: &mut FnMut(&DecomposedTile), -) { - // If the image is tiled along a given axis, we can't have the shader compute - // the image repetition pattern. In this case we base the primitive's rectangle size - // on the stretched tile size which effectively cancels the repetition (and repetition - // has to be emulated by generating more primitives). - // If the image is not tiled along this axis, we can perform the repetition in the - // shader. In this case we use the item's size in the primitive (on that particular - // axis). - // See the shader_repeat_x/y code below. - - let stretch_size = LayoutSize::new( - stretched_tile_size.width * tile_ratio_width, - stretched_tile_size.height * tile_ratio_height, - ); + let mut segment_rect = LayoutRect { + origin: LayoutPoint::new( + x0 + tile_offset.x as f32 * layer_tile_size.width, + y0 + tile_offset.y as f32 * layer_tile_size.height, + ), + size: layer_tile_size, + }; - let mut prim_rect = LayoutRect::new( - item_rect.origin + LayoutVector2D::new( - tile_offset.x as f32 * stretched_tile_size.width, - tile_offset.y as f32 * stretched_tile_size.height, - ), - stretch_size, - ); + if tile_offset.x == leftover_offset.x { + segment_rect.size.width = leftover_layer_size.width; + } - if shader_repeat_x { - assert_eq!(tile_offset.x, 0); - prim_rect.size.width = item_rect.size.width; - } + if tile_offset.y == leftover_offset.y { + segment_rect.size.height = leftover_layer_size.height; + } - if shader_repeat_y { - assert_eq!(tile_offset.y, 0); - prim_rect.size.height = item_rect.size.height; - } + let mut edge_flags = row_flags; + if x == 0 { + edge_flags |= EdgeAaSegmentMask::LEFT; + } + if x == x_count - 1 { + edge_flags |= EdgeAaSegmentMask::RIGHT; + } - // Fix up the primitive's rect if it overflows the original item rect. - if let Some(rect) = prim_rect.intersection(item_rect) { - callback(&DecomposedTile { - tile_offset, - rect, - stretch_size, - }); + callback(&segment_rect, tile_offset, edge_flags); + } } } diff --git a/webrender/src/prim_store.rs b/webrender/src/prim_store.rs index 6213cc5184..04171ad289 100644 --- a/webrender/src/prim_store.rs +++ b/webrender/src/prim_store.rs @@ -3,10 +3,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use api::{AlphaType, BorderRadius, BoxShadowClipMode, BuiltDisplayList, ClipMode, ColorF, ComplexClipRegion}; -use api::{DeviceIntRect, DeviceIntSize, DevicePixelScale, Epoch, ExtendMode, FontRenderMode}; -use api::{FilterOp, GlyphInstance, GlyphKey, GradientStop, ImageKey, ImageRendering, ItemRange, ItemTag}; +use api::{DeviceIntRect, DeviceIntSize, DeviceUintSize, DevicePixelScale, Epoch, ExtendMode, FontRenderMode}; +use api::{FilterOp, GlyphInstance, GlyphKey, GradientStop, ImageKey, ImageRendering, ItemRange, ItemTag, TileOffset}; use api::{GlyphRasterSpace, LayoutPoint, LayoutRect, LayoutSize, LayoutToWorldTransform, LayoutVector2D}; -use api::{PipelineId, PremultipliedColorF, PropertyBinding, Shadow, YuvColorSpace, YuvFormat}; +use api::{PipelineId, PremultipliedColorF, PropertyBinding, Shadow, YuvColorSpace, YuvFormat, DeviceIntSideOffsets}; use border::{BorderCornerInstance, BorderEdgeKind}; use box_shadow::BLUR_SAMPLE_SCALE; use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, CoordinateSystemId}; @@ -19,6 +19,7 @@ use glyph_rasterizer::{FontInstance, FontTransform}; use gpu_cache::{GpuBlockData, GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest, ToGpuBlocks}; use gpu_types::{ClipChainRectIndex}; +use image::{for_each_tile, for_each_repetition}; use picture::{PictureCompositeMode, PictureId, PicturePrimitive}; #[cfg(debug_assertions)] use render_backend::FrameId; @@ -146,7 +147,6 @@ pub struct PictureIndex(pub usize); #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum PrimitiveKind { TextRun, - Image, Border, Brush, } @@ -242,6 +242,13 @@ impl OpacityBinding { } } +#[derive(Debug)] +pub struct VisibleImageTile { + pub tile_offset: TileOffset, + pub handle: GpuCacheHandle, + pub edge_flags: EdgeAaSegmentMask, +} + #[derive(Debug)] pub enum BrushKind { Solid { @@ -261,6 +268,7 @@ pub enum BrushKind { source: ImageSource, sub_rect: Option, opacity_binding: OpacityBinding, + visible_tiles: Vec, }, YuvImage { yuv_key: [ImageKey; 3], @@ -430,10 +438,15 @@ impl BrushPrimitive { } // Images are drawn as a white color, modulated by the total // opacity coming from any collapsed property bindings. - BrushKind::Image { stretch_size, ref opacity_binding, .. } => { + BrushKind::Image { stretch_size, tile_spacing, ref opacity_binding, .. } => { request.push(ColorF::new(1.0, 1.0, 1.0, opacity_binding.current).premultiplied()); request.push(PremultipliedColorF::WHITE); - request.push([stretch_size.width, stretch_size.height, 0.0, 0.0]); + request.push([ + stretch_size.width + tile_spacing.width, + stretch_size.height + tile_spacing.height, + 0.0, + 0.0, + ]); } // Solid rects also support opacity collapsing. BrushKind::Solid { color, ref opacity_binding, .. } => { @@ -1004,7 +1017,6 @@ impl ClipData { #[derive(Debug)] pub enum PrimitiveContainer { TextRun(TextRunPrimitiveCpu), - Image(ImagePrimitiveCpu), Border(BorderPrimitiveCpu), Brush(BrushPrimitive), } @@ -1037,7 +1049,6 @@ impl PrimitiveContainer { } } } - PrimitiveContainer::Image(..) | PrimitiveContainer::Border(..) => { true } @@ -1088,7 +1099,6 @@ impl PrimitiveContainer { } } } - PrimitiveContainer::Image(..) | PrimitiveContainer::Border(..) => { panic!("bug: other primitive containers not expected here"); } @@ -1222,17 +1232,6 @@ impl PrimitiveStore { self.cpu_text_runs.push(text_cpu); metadata } - PrimitiveContainer::Image(image_cpu) => { - let metadata = PrimitiveMetadata { - opacity: PrimitiveOpacity::translucent(), - prim_kind: PrimitiveKind::Image, - cpu_prim_index: SpecificPrimitiveIndex(self.cpu_images.len()), - ..base_metadata - }; - - self.cpu_images.push(image_cpu); - metadata - } PrimitiveContainer::Border(border_cpu) => { let metadata = PrimitiveMetadata { opacity: PrimitiveOpacity::translucent(), @@ -1301,7 +1300,6 @@ impl PrimitiveStore { } } PrimitiveKind::TextRun | - PrimitiveKind::Image | PrimitiveKind::Border => {} } @@ -1351,7 +1349,6 @@ impl PrimitiveStore { }; } PrimitiveKind::TextRun | - PrimitiveKind::Image | PrimitiveKind::Border => { unreachable!("bug: invalid prim type for opacity collapse"); } @@ -1384,6 +1381,7 @@ impl PrimitiveStore { frame_context: &FrameBuildingContext, frame_state: &mut FrameBuildingState, ) { + let mut is_tiled_image = false; let metadata = &mut self.cpu_metadata[prim_index.0]; #[cfg(debug_assertions)] { @@ -1404,129 +1402,30 @@ impl PrimitiveStore { frame_state, ); } - PrimitiveKind::Image => { - let image_cpu = &mut self.cpu_images[metadata.cpu_prim_index.0]; - let image_properties = frame_state - .resource_cache - .get_image_properties(image_cpu.key.request.key); - - // TODO(gw): Add image.rs and move this code out to a separate - // source file as it gets more complicated, and we - // start pre-rendering images for other reasons. - - if let Some(image_properties) = image_properties { - // See if this image has been updated since we last hit this code path. - // If so, we need to (at least) update the opacity, and also rebuild - // and render task cached portions of this image. - if image_properties.epoch != image_cpu.current_epoch { - image_cpu.current_epoch = image_properties.epoch; - - // Update the opacity. - metadata.opacity.is_opaque = image_properties.descriptor.is_opaque && - image_cpu.tile_spacing.width == 0.0 && - image_cpu.tile_spacing.height == 0.0; - - // Work out whether this image is a normal / simple type, or if - // we need to pre-render it to the render task cache. - image_cpu.source = match image_cpu.key.texel_rect { - Some(texel_rect) => { - ImageSource::Cache { - // Size in device-pixels we need to allocate in render task cache. - size: texel_rect.size, - handle: None, - } - } - None => { - // Simple image - just use a normal texture cache entry. - ImageSource::Default - } - }; - } - - // Set if we need to request the source image from the cache this frame. - let mut request_source_image = false; - - // Every frame, for cached items, we need to request the render - // task cache item. The closure will be invoked on the first - // time through, and any time the render task output has been - // evicted from the texture cache. - match image_cpu.source { - ImageSource::Cache { size, ref mut handle } => { - let key = image_cpu.key; - - // Request a pre-rendered image task. - *handle = Some(frame_state.resource_cache.request_render_task( - RenderTaskCacheKey { - size, - kind: RenderTaskCacheKeyKind::Image(key), - }, - frame_state.gpu_cache, - frame_state.render_tasks, - None, - image_properties.descriptor.is_opaque, - |render_tasks| { - // We need to render the image cache this frame, - // so will need access to the source texture. - request_source_image = true; - - // Create a task to blit from the texture cache to - // a normal transient render task surface. This will - // copy only the sub-rect, if specified. - let cache_to_target_task = RenderTask::new_blit( - size, - BlitSource::Image { - key, - }, - ); - let cache_to_target_task_id = render_tasks.add(cache_to_target_task); - - // Create a task to blit the rect from the child render - // task above back into the right spot in the persistent - // render target cache. - let target_to_cache_task = RenderTask::new_blit( - size, - BlitSource::RenderTask { - task_id: cache_to_target_task_id, - }, - ); - let target_to_cache_task_id = render_tasks.add(target_to_cache_task); - - // Hook this into the render task tree at the right spot. - pic_state.tasks.push(target_to_cache_task_id); - - // Pass the image opacity, so that the cached render task - // item inherits the same opacity properties. - target_to_cache_task_id - } - )); - } - ImageSource::Default => { - // Normal images just reference the source texture each frame. - request_source_image = true; - } - } - - // Request source image from the texture cache, if required. - if request_source_image { - frame_state.resource_cache.request_image( - image_cpu.key.request, - frame_state.gpu_cache, - ); - } - } - } PrimitiveKind::Brush => { let brush = &mut self.cpu_brushes[metadata.cpu_prim_index.0]; match brush.kind { - BrushKind::Image { request, sub_rect, ref mut current_epoch, ref mut source, ref mut opacity_binding, .. } => { + BrushKind::Image { + request, + sub_rect, + stretch_size, + ref mut tile_spacing, + ref mut current_epoch, + ref mut source, + ref mut opacity_binding, + ref mut visible_tiles, + .. + } => { let image_properties = frame_state .resource_cache .get_image_properties(request.key); + // Set if we need to request the source image from the cache this frame. if let Some(image_properties) = image_properties { *current_epoch = image_properties.epoch; + is_tiled_image = image_properties.tiling.is_some(); // If the opacity changed, invalidate the GPU cache so that // the new color for this primitive gets uploaded. @@ -1540,9 +1439,22 @@ impl PrimitiveStore { image_properties.descriptor.is_opaque && opacity_binding.current == 1.0; + if *tile_spacing != LayoutSize::zero() && !is_tiled_image { + *source = ImageSource::Cache { + // Size in device-pixels we need to allocate in render task cache. + size: DeviceIntSize::new( + image_properties.descriptor.width as i32, + image_properties.descriptor.height as i32 + ), + handle: None, + }; + } + // Work out whether this image is a normal / simple type, or if // we need to pre-render it to the render task cache. if let Some(rect) = sub_rect { + // We don't properly support this right now. + debug_assert!(!is_tiled_image); *source = ImageSource::Cache { // Size in device-pixels we need to allocate in render task cache. size: rect.size, @@ -1557,7 +1469,22 @@ impl PrimitiveStore { // time through, and any time the render task output has been // evicted from the texture cache. match *source { - ImageSource::Cache { size, ref mut handle } => { + ImageSource::Cache { ref mut size, ref mut handle } => { + let padding = DeviceIntSideOffsets::new( + 0, + (tile_spacing.width * size.width as f32 / stretch_size.width) as i32, + (tile_spacing.height * size.height as f32 / stretch_size.height) as i32, + 0, + ); + + let inner_size = *size; + size.width += padding.horizontal(); + size.height += padding.vertical(); + + if padding != DeviceIntSideOffsets::zero() { + metadata.opacity.is_opaque = false; + } + let image_cache_key = ImageCacheKey { request, texel_rect: sub_rect, @@ -1566,7 +1493,7 @@ impl PrimitiveStore { // Request a pre-rendered image task. *handle = Some(frame_state.resource_cache.request_render_task( RenderTaskCacheKey { - size, + size: *size, kind: RenderTaskCacheKeyKind::Image(image_cache_key), }, frame_state.gpu_cache, @@ -1581,8 +1508,9 @@ impl PrimitiveStore { // Create a task to blit from the texture cache to // a normal transient render task surface. This will // copy only the sub-rect, if specified. - let cache_to_target_task = RenderTask::new_blit( - size, + let cache_to_target_task = RenderTask::new_blit_with_padding( + inner_size, + &padding, BlitSource::Image { key: image_cache_key }, ); let cache_to_target_task_id = render_tasks.add(cache_to_target_task); @@ -1591,7 +1519,7 @@ impl PrimitiveStore { // task above back into the right spot in the persistent // render target cache. let target_to_cache_task = RenderTask::new_blit( - size, + *size, BlitSource::RenderTask { task_id: cache_to_target_task_id, }, @@ -1613,14 +1541,89 @@ impl PrimitiveStore { } } - if request_source_image { + if let Some(tile_size) = image_properties.tiling { + + let device_image_size = DeviceUintSize::new( + image_properties.descriptor.width, + image_properties.descriptor.height, + ); + + // Tighten the clip rect because decomposing the repeated image can + // produce primitives that are partially covering the original image + // rect and we want to clip these extra parts out. + let tight_clip_rect = metadata.local_clip_rect.intersection(&metadata.local_rect).unwrap(); + + let visible_rect = compute_conservative_visible_rect( + prim_run_context, + frame_context, + &tight_clip_rect + ); + + let base_edge_flags = edge_flags_for_tile_spacing(tile_spacing); + + let stride = stretch_size + *tile_spacing; + + visible_tiles.clear(); + + for_each_repetition( + &metadata.local_rect, + &visible_rect, + &stride, + &mut |origin, edge_flags| { + let edge_flags = base_edge_flags | edge_flags; + + let image_rect = LayoutRect { + origin: *origin, + size: stretch_size, + }; + + for_each_tile( + &image_rect, + &visible_rect, + &device_image_size, + tile_size as u32, + &mut |tile_rect, tile_offset, tile_flags| { + + frame_state.resource_cache.request_image( + request.with_tile(tile_offset), + frame_state.gpu_cache, + ); + + let mut handle = GpuCacheHandle::new(); + if let Some(mut request) = frame_state.gpu_cache.request(&mut handle) { + request.push(*tile_rect); + request.push(tight_clip_rect); + request.push(ColorF::new(1.0, 1.0, 1.0, opacity_binding.current).premultiplied()); + request.push(PremultipliedColorF::WHITE); + request.push([tile_rect.size.width, tile_rect.size.height, 0.0, 0.0]); + request.write_segment(*tile_rect); + } + + visible_tiles.push(VisibleImageTile { + tile_offset, + handle, + edge_flags: tile_flags & edge_flags, + }); + } + ); + } + ); + + if visible_tiles.is_empty() { + // At this point if we don't have tiles to show it means we could probably + // have done a better a job at culling during an earlier stage. + // Clearing the screen rect has the effect of "culling out" the primitive + // from the point of view of the batch builder, and ensures we don't hit + // assertions later on because we didn't request any image. + metadata.screen_rect = None; + } + } else if request_source_image { frame_state.resource_cache.request_image( request, frame_state.gpu_cache, ); } } - } BrushKind::YuvImage { format, yuv_key, image_rendering, .. } => { let channel_num = format.get_plane_num(); @@ -1689,6 +1692,11 @@ impl PrimitiveStore { } } + if is_tiled_image { + // we already requested each tile's gpu data. + return; + } + // Mark this GPU resource as required for this frame. if let Some(mut request) = frame_state.gpu_cache.request(&mut metadata.gpu_location) { // has to match VECS_PER_BRUSH_PRIM @@ -1700,10 +1708,6 @@ impl PrimitiveStore { let border = &self.cpu_borders[metadata.cpu_prim_index.0]; border.write_gpu_blocks(request); } - PrimitiveKind::Image => { - let image = &self.cpu_images[metadata.cpu_prim_index.0]; - image.write_gpu_blocks(request); - } PrimitiveKind::TextRun => { let text = &self.cpu_text_runs[metadata.cpu_prim_index.0]; text.write_gpu_blocks(&mut request); @@ -2403,6 +2407,39 @@ impl PrimitiveStore { } } +fn compute_conservative_visible_rect( + prim_run_context: &PrimitiveRunContext, + frame_context: &FrameBuildingContext, + local_clip_rect: &LayoutRect, +) -> LayoutRect { + let world_screen_rect = prim_run_context + .clip_chain.combined_outer_screen_rect + .to_f32() / frame_context.device_pixel_scale; + + if let Some(layer_screen_rect) = prim_run_context + .scroll_node + .world_content_transform + .unapply(&world_screen_rect) { + + return local_clip_rect.intersection(&layer_screen_rect).unwrap_or(LayoutRect::zero()); + } + + *local_clip_rect +} + +fn edge_flags_for_tile_spacing(tile_spacing: &LayoutSize) -> EdgeAaSegmentMask { + let mut flags = EdgeAaSegmentMask::empty(); + + if tile_spacing.width > 0.0 { + flags |= EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::RIGHT; + } + if tile_spacing.height > 0.0 { + flags |= EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::BOTTOM; + } + + flags +} + //Test for one clip region contains another trait InsideTest { fn might_contain(&self, clip: &T) -> bool; diff --git a/webrender/src/render_backend.rs b/webrender/src/render_backend.rs index bbdd7c34f9..a5604819ff 100644 --- a/webrender/src/render_backend.rs +++ b/webrender/src/render_backend.rs @@ -206,7 +206,6 @@ impl Document { &self.pending.scene, &mut self.clip_scroll_tree, resource_cache.get_font_instances(), - resource_cache.get_tiled_image_map(), &self.view, &self.output_pipelines, &self.frame_builder_config, @@ -251,7 +250,6 @@ impl Document { removed_pipelines: replace(&mut self.pending.removed_pipelines, Vec::new()), view: self.view.clone(), font_instances: resource_cache.get_font_instances(), - tiled_image_map: resource_cache.get_tiled_image_map(), output_pipelines: self.output_pipelines.clone(), }) } else { diff --git a/webrender/src/render_task.rs b/webrender/src/render_task.rs index da8eb19bfa..58228c5a87 100644 --- a/webrender/src/render_task.rs +++ b/webrender/src/render_task.rs @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceSize, ImageDescriptor, ImageFormat}; +use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceSize, DeviceIntSideOffsets, ImageDescriptor, ImageFormat}; #[cfg(feature = "pathfinder")] use api::FontRenderMode; use box_shadow::{BoxShadowCacheKey}; @@ -254,6 +254,7 @@ pub enum BlitSource { #[cfg_attr(feature = "replay", derive(Deserialize))] pub struct BlitTask { pub source: BlitSource, + pub padding: DeviceIntSideOffsets, } #[derive(Debug)] @@ -337,6 +338,14 @@ impl RenderTask { pub fn new_blit( size: DeviceIntSize, source: BlitSource, + ) -> Self { + RenderTask::new_blit_with_padding(size, &DeviceIntSideOffsets::zero(), source) + } + + pub fn new_blit_with_padding( + mut size: DeviceIntSize, + padding: &DeviceIntSideOffsets, + source: BlitSource, ) -> Self { let mut children = Vec::new(); @@ -349,11 +358,15 @@ impl RenderTask { children.push(task_id); } + size.width += padding.horizontal(); + size.height += padding.vertical(); + RenderTask { children, location: RenderTaskLocation::Dynamic(None, Some(size)), kind: RenderTaskKind::Blit(BlitTask { source, + padding: *padding, }), clear_mode: ClearMode::Transparent, saved_index: None, diff --git a/webrender/src/renderer.rs b/webrender/src/renderer.rs index c7066108dc..badba837f0 100644 --- a/webrender/src/renderer.rs +++ b/webrender/src/renderer.rs @@ -136,10 +136,6 @@ const GPU_TAG_SETUP_DATA: GpuProfileTag = GpuProfileTag { label: "data init", color: debug_colors::LIGHTGREY, }; -const GPU_TAG_PRIM_IMAGE: GpuProfileTag = GpuProfileTag { - label: "Image", - color: debug_colors::GREEN, -}; const GPU_TAG_PRIM_SPLIT_COMPOSITE: GpuProfileTag = GpuProfileTag { label: "SplitComposite", color: debug_colors::DARKBLUE, @@ -183,12 +179,6 @@ impl TransformBatchKind { fn debug_name(&self) -> &'static str { match *self { TransformBatchKind::TextRun(..) => "TextRun", - TransformBatchKind::Image(image_buffer_kind, ..) => match image_buffer_kind { - ImageBufferKind::Texture2D => "Image (2D)", - ImageBufferKind::TextureRect => "Image (Rect)", - ImageBufferKind::TextureExternal => "Image (External)", - ImageBufferKind::Texture2DArray => "Image (Array)", - }, TransformBatchKind::BorderCorner => "BorderCorner", TransformBatchKind::BorderEdge => "BorderEdge", } @@ -197,7 +187,6 @@ impl TransformBatchKind { fn sampler_tag(&self) -> GpuProfileTag { match *self { TransformBatchKind::TextRun(..) => GPU_TAG_PRIM_TEXT_RUN, - TransformBatchKind::Image(..) => GPU_TAG_PRIM_IMAGE, TransformBatchKind::BorderCorner => GPU_TAG_PRIM_BORDER_CORNER, TransformBatchKind::BorderEdge => GPU_TAG_PRIM_BORDER_EDGE, } diff --git a/webrender/src/resource_cache.rs b/webrender/src/resource_cache.rs index df035ea601..78d8d5aefb 100644 --- a/webrender/src/resource_cache.rs +++ b/webrender/src/resource_cache.rs @@ -110,8 +110,6 @@ pub struct ImageTiling { pub tile_size: TileSize, } -pub type TiledImageMap = FastHashMap; - #[derive(Default)] struct ImageTemplates { images: FastHashMap, @@ -238,6 +236,16 @@ pub struct ImageRequest { pub tile: Option, } +impl ImageRequest { + pub fn with_tile(&self, offset: TileOffset) -> Self { + ImageRequest { + key: self.key, + rendering: self.rendering, + tile: Some(offset), + } + } +} + impl Into for ImageRequest { fn into(self) -> BlobImageRequest { BlobImageRequest { @@ -879,28 +887,6 @@ impl ResourceCache { }) } - pub fn get_tiled_image_map(&self) -> TiledImageMap { - self.resources - .image_templates - .images - .iter() - .filter_map(|(&key, template)| { - template.tiling.map(|tile_size| { - ( - key, - ImageTiling { - image_size: DeviceUintSize::new( - template.descriptor.width, - template.descriptor.height, - ), - tile_size, - }, - ) - }) - }) - .collect() - } - pub fn begin_frame(&mut self, frame_id: FrameId) { debug_assert_eq!(self.state, State::Idle); self.state = State::AddResources; diff --git a/webrender/src/scene_builder.rs b/webrender/src/scene_builder.rs index be28a1b1ad..64cd1e9cb4 100644 --- a/webrender/src/scene_builder.rs +++ b/webrender/src/scene_builder.rs @@ -8,7 +8,7 @@ use display_list_flattener::build_scene; use frame_builder::{FrameBuilderConfig, FrameBuilder}; use clip_scroll_tree::ClipScrollTree; use internal_types::{FastHashMap, FastHashSet}; -use resource_cache::{FontInstanceMap, TiledImageMap}; +use resource_cache::FontInstanceMap; use render_backend::DocumentView; use renderer::{PipelineInfo, SceneBuilderHooks}; use scene::Scene; @@ -56,7 +56,6 @@ pub struct SceneRequest { pub scene: Scene, pub view: DocumentView, pub font_instances: FontInstanceMap, - pub tiled_image_map: TiledImageMap, pub output_pipelines: FastHashSet, pub removed_pipelines: Vec, } diff --git a/webrender/src/shade.rs b/webrender/src/shade.rs index 1b9eae7ea9..36ce26dcb9 100644 --- a/webrender/src/shade.rs +++ b/webrender/src/shade.rs @@ -492,7 +492,6 @@ pub struct Shaders { // a cache shader (e.g. blur) to the screen. pub ps_text_run: TextShader, pub ps_text_run_dual_source: TextShader, - ps_image: Vec>, ps_border_corner: PrimitiveShader, ps_border_edge: PrimitiveShader, @@ -632,11 +631,9 @@ impl Shaders { // All image configuration. let mut image_features = Vec::new(); - let mut ps_image = Vec::new(); let mut brush_image = Vec::new(); // PrimitiveShader is not clonable. Use push() to initialize the vec. for _ in 0 .. IMAGE_BUFFER_KINDS.len() { - ps_image.push(None); brush_image.push(None); } for buffer_kind in 0 .. IMAGE_BUFFER_KINDS.len() { @@ -645,12 +642,6 @@ impl Shaders { if feature_string != "" { image_features.push(feature_string); } - ps_image[buffer_kind] = Some(PrimitiveShader::new( - "ps_image", - device, - &image_features, - options.precache_shaders, - )?); brush_image[buffer_kind] = Some(BrushShader::new( "brush_image", device, @@ -749,7 +740,6 @@ impl Shaders { cs_clip_line, ps_text_run, ps_text_run_dual_source, - ps_image, ps_border_corner, ps_border_edge, ps_split_composite, @@ -815,11 +805,6 @@ impl Shaders { }; return text_shader.get(glyph_format, transform_kind); } - TransformBatchKind::Image(image_buffer_kind) => { - self.ps_image[image_buffer_kind as usize] - .as_mut() - .expect("Unsupported image shader kind") - } TransformBatchKind::BorderCorner => { &mut self.ps_border_corner } @@ -852,11 +837,6 @@ impl Shaders { shader.deinit(device); } } - for shader in self.ps_image { - if let Some(shader) = shader { - shader.deinit(device); - } - } for shader in self.brush_yuv_image { if let Some(shader) = shader { shader.deinit(device); diff --git a/webrender/src/tiling.rs b/webrender/src/tiling.rs index ebaf5721b8..a154961e33 100644 --- a/webrender/src/tiling.rs +++ b/webrender/src/tiling.rs @@ -484,7 +484,7 @@ impl RenderTarget for ColorRenderTarget { cache_item.texture_layer, source_rect, ), - target_rect, + target_rect: target_rect.inner_rect(task_info.padding) }); } BlitSource::RenderTask { .. } => { @@ -673,7 +673,7 @@ impl TextureCacheRenderTarget { // task to this target. self.blits.push(BlitJob { source: BlitJobSource::RenderTask(task_id), - target_rect: target_rect.0, + target_rect: target_rect.0.inner_rect(task_info.padding), }); } } diff --git a/webrender/tests/angle_shader_validation.rs b/webrender/tests/angle_shader_validation.rs index e214bede42..bd49dcef5b 100644 --- a/webrender/tests/angle_shader_validation.rs +++ b/webrender/tests/angle_shader_validation.rs @@ -62,10 +62,6 @@ const SHADERS: &[Shader] = &[ name: "ps_split_composite", features: PRIM_FEATURES, }, - Shader { - name: "ps_image", - features: PRIM_FEATURES, - }, Shader { name: "ps_text_run", features: PRIM_FEATURES, diff --git a/webrender_api/src/units.rs b/webrender_api/src/units.rs index 7e4916a306..a1946f4a6c 100644 --- a/webrender_api/src/units.rs +++ b/webrender_api/src/units.rs @@ -14,7 +14,7 @@ use app_units::Au; use euclid::{Length, TypedRect, TypedScale, TypedSize2D, TypedTransform3D}; -use euclid::{TypedPoint2D, TypedPoint3D, TypedVector2D, TypedVector3D}; +use euclid::{TypedPoint2D, TypedPoint3D, TypedVector2D, TypedVector3D, TypedSideOffsets2D}; /// Geometry in the coordinate system of the render target (screen or intermediate /// surface) in physical pixels. @@ -25,6 +25,7 @@ pub type DeviceIntRect = TypedRect; pub type DeviceIntPoint = TypedPoint2D; pub type DeviceIntSize = TypedSize2D; pub type DeviceIntLength = Length; +pub type DeviceIntSideOffsets = TypedSideOffsets2D; pub type DeviceUintRect = TypedRect; pub type DeviceUintPoint = TypedPoint2D;