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 82161f9e7f..3c421b0b98 100644 --- a/webrender/src/batch.rs +++ b/webrender/src/batch.rs @@ -37,7 +37,6 @@ const OPAQUE_TASK_ADDRESS: RenderTaskAddress = RenderTaskAddress(0x7fff); #[cfg_attr(feature = "replay", derive(Deserialize))] pub enum TransformBatchKind { TextRun(GlyphFormat), - Image(ImageBufferKind), BorderCorner, BorderEdge, } @@ -613,7 +612,10 @@ impl AlphaBatchBuilder { screen_rect.unclipped.size, ); - let prim_cache_address = gpu_cache.get_address(&prim_metadata.gpu_location); + // Tiled images have a cache handle per visible tile and an invalid handle in the + // primitive metadata. + let prim_cache_address = gpu_cache.try_get_address(&prim_metadata.gpu_location); + let no_textures = BatchTextures::no_texture(); let clip_task_address = prim_metadata .clip_task_id @@ -980,6 +982,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, @@ -1070,50 +1098,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]; @@ -1191,6 +1175,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, @@ -1286,6 +1309,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), + (BrushImageSourceKind::Color as i32) << 16 | + RasterizationSpace::Local as i32, + 0, + ], + )) + } +} + impl BrushPrimitive { pub fn get_picture_index(&self) -> PictureIndex { match self.kind { @@ -1479,13 +1533,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/device.rs b/webrender/src/device.rs index 8bfcde406e..a49bf76ef7 100644 --- a/webrender/src/device.rs +++ b/webrender/src/device.rs @@ -1250,8 +1250,8 @@ impl Device { src_rect.origin.y + src_rect.size.height, dest_rect.origin.x, dest_rect.origin.y, - dest_rect.origin.x + dest_rect.size.width, - dest_rect.origin.y + dest_rect.size.height, + dest_rect.origin.x + src_rect.size.width, + dest_rect.origin.y + src_rect.size.height, gl::COLOR_BUFFER_BIT, gl::LINEAR, ); diff --git a/webrender/src/display_list_flattener.rs b/webrender/src/display_list_flattener.rs index 324a3d17f7..6a2bef5e0c 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, LayerPoint, La use api::{LayerRect, LayerSize, LayerVector2D, LayoutRect, LayoutSize, LayoutTransform}; use api::{LayoutVector2D, 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::{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(), @@ -633,49 +628,16 @@ impl<'a> DisplayListFlattener<'a> { let prim_info = item.get_layer_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( @@ -1791,7 +1753,6 @@ impl<'a> DisplayListFlattener<'a> { border.image_key, ImageRendering::Auto, AlphaType::PremultipliedAlpha, - None, ); } } @@ -1848,6 +1809,7 @@ impl<'a> DisplayListFlattener<'a> { stops_count: usize, extend_mode: ExtendMode, gradient_index: CachedGradientIndex, + stretch_size: LayerSize, ) { // Try to ensure that if the gradient is specified in reverse, then so long as the stops // are also supplied in reverse that the rendered result will be equivalent. To do this, @@ -1876,6 +1838,7 @@ impl<'a> DisplayListFlattener<'a> { start_point: sp, end_point: ep, gradient_index, + stretch_size, }, None, ); @@ -1894,43 +1857,56 @@ impl<'a> DisplayListFlattener<'a> { stops: ItemRange, stops_count: usize, extend_mode: ExtendMode, - tile_size: LayerSize, - tile_spacing: LayerSize, + stretch_size: LayerSize, + mut tile_spacing: LayerSize, ) { let gradient_index = CachedGradientIndex(self.cached_gradients.len()); self.cached_gradients.push(CachedGradient::new()); - let prim_infos = info.decompose( - tile_size, - tile_spacing, - 64 * 64, - ); + let mut prim_rect = info.rect; + simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect); + let info = LayerPrimitiveInfo { + rect: prim_rect, + .. *info + }; - if prim_infos.is_empty() { - self.add_gradient_impl( - clip_and_scroll, - info, - start_point, - end_point, - stops, - stops_count, - extend_mode, - gradient_index, + if tile_spacing != LayerSize::zero() { + let prim_infos = info.decompose( + stretch_size, + tile_spacing, + 64 * 64, ); - } else { - for prim_info in prim_infos { - self.add_gradient_impl( - clip_and_scroll, - &prim_info, - start_point, - end_point, - stops, - stops_count, - extend_mode, - gradient_index, - ); + + if !prim_infos.is_empty() { + for prim_info in prim_infos { + self.add_gradient_impl( + clip_and_scroll, + &prim_info, + start_point, + end_point, + stops, + stops_count, + extend_mode, + gradient_index, + prim_info.rect.size, + ); + } + + return; } } + + self.add_gradient_impl( + clip_and_scroll, + &info, + start_point, + end_point, + stops, + stops_count, + extend_mode, + gradient_index, + stretch_size, + ); } fn add_radial_gradient_impl( @@ -1944,6 +1920,7 @@ impl<'a> DisplayListFlattener<'a> { stops: ItemRange, extend_mode: ExtendMode, gradient_index: CachedGradientIndex, + stretch_size: LayerSize, ) { let prim = BrushPrimitive::new( BrushKind::RadialGradient { @@ -1954,6 +1931,7 @@ impl<'a> DisplayListFlattener<'a> { end_radius, ratio_xy, gradient_index, + stretch_size, }, None, ); @@ -1976,45 +1954,58 @@ impl<'a> DisplayListFlattener<'a> { ratio_xy: f32, stops: ItemRange, extend_mode: ExtendMode, - tile_size: LayerSize, - tile_spacing: LayerSize, + stretch_size: LayerSize, + mut tile_spacing: LayerSize, ) { let gradient_index = CachedGradientIndex(self.cached_gradients.len()); self.cached_gradients.push(CachedGradient::new()); - let prim_infos = info.decompose( - tile_size, - tile_spacing, - 64 * 64, - ); + let mut prim_rect = info.rect; + simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect); + let info = LayerPrimitiveInfo { + rect: prim_rect, + .. *info + }; - if prim_infos.is_empty() { - self.add_radial_gradient_impl( - clip_and_scroll, - info, - center, - start_radius, - end_radius, - ratio_xy, - stops, - extend_mode, - gradient_index, + if tile_spacing != LayerSize::zero() { + let prim_infos = info.decompose( + stretch_size, + tile_spacing, + 64 * 64, ); - } else { - for prim_info in prim_infos { - self.add_radial_gradient_impl( - clip_and_scroll, - &prim_info, - center, - start_radius, - end_radius, - ratio_xy, - stops, - extend_mode, - gradient_index, - ); + + if !prim_infos.is_empty() { + for prim_info in prim_infos { + self.add_radial_gradient_impl( + clip_and_scroll, + &prim_info, + center, + start_radius, + end_radius, + ratio_xy, + stops, + extend_mode, + gradient_index, + stretch_size, + ); + } + + return; } } + + self.add_radial_gradient_impl( + clip_and_scroll, + &info, + center, + start_radius, + end_radius, + ratio_xy, + stops, + extend_mode, + gradient_index, + stretch_size, + ); } pub fn add_text( @@ -2119,20 +2110,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 = LayerSize::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 = LayerPrimitiveInfo { + rect: prim_rect, + .. *info }; let sub_rect = sub_rect.map(|texel_rect| { @@ -2148,50 +2131,30 @@ impl<'a> DisplayListFlattener<'a> { ) }); - // See if conditions are met to run through the new - // image brush shader, which supports segments. - if tile_spacing == LayerSize::zero() && - stretch_size == info.rect.size && - 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, + 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(), + }, + 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( @@ -2238,7 +2201,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/gpu_cache.rs b/webrender/src/gpu_cache.rs index 2e145e8a5c..af17cf1118 100644 --- a/webrender/src/gpu_cache.rs +++ b/webrender/src/gpu_cache.rs @@ -654,4 +654,14 @@ impl GpuCache { debug_assert_eq!(block.last_access_time, self.frame_id); block.address } + + /// Similar to get_address except that it accepts invalid cache handles in which case it + /// returns an invalid cache address. + pub fn try_get_address(&self, id: &GpuCacheHandle) -> GpuCacheAddress { + if id.location.is_none() { + return GpuCacheAddress::invalid(); + } + + self.get_address(id) + } } diff --git a/webrender/src/image.rs b/webrender/src/image.rs index 6126814140..6f8b491b03 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, LayerRect, LayerSize, LayerVector2D, DeviceUintSize}; -use euclid::rect; +use api::{TileOffset, LayerRect, LayerSize, LayerPoint, 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: &LayerSize, + tile_spacing: &mut LayerSize, + prim_rect: &mut LayerRect, +) { + let stride = *stretch_size + *tile_spacing; -pub struct DecomposedTile { - pub rect: LayerRect, - pub stretch_size: LayerSize, - 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.width >= 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: LayerRect, - /// The space between each repeated pattern in layout space. - pub tile_spacing: LayerSize, - /// The size in layout space of each repetition of the image. - pub stretch_size: LayerSize, - - /// 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: &LayerRect, + visible_rect: &LayerRect, + stride: &LayerSize, + callback: &mut FnMut(&LayerPoint, 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 = LayerPoint::new(x0, y0); -fn decompose_row(item_rect: &LayerRect, 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: &LayerRect, - info: &TiledImageInfo, - callback: &mut FnMut(&DecomposedTile), +pub fn for_each_tile( + prim_rect: &LayerRect, + visible_rect: &LayerRect, + device_image_size: &DeviceUintSize, + device_tile_size: u32, + callback: &mut FnMut(&LayerRect, 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 = LayerSize::new( - img_dw * info.stretch_size.width, - img_dh * info.stretch_size.height, + // size of regular tiles in layout space. + let layer_tile_size = LayerSize::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 = LayerSize::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: &LayerRect, - stretched_tile_size: LayerSize, - 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 = LayerSize::new( - stretched_tile_size.width * tile_ratio_width, - stretched_tile_size.height * tile_ratio_height, - ); + let mut segment_rect = LayerRect { + origin: LayerPoint::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 = LayerRect::new( - item_rect.origin + LayerVector2D::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 14a5350383..89502d3605 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::{DeviceIntRect, DeviceIntSize, DeviceUintSize, DevicePixelScale, Epoch, ExtendMode, FontRenderMode}; use api::{FilterOp, GlyphInstance, GlyphKey, GradientStop, ImageKey, ImageRendering, ItemRange, ItemTag}; use api::{LayerPoint, LayerRect, LayerSize, LayerToWorldTransform, LayerVector2D}; -use api::{PipelineId, PremultipliedColorF, Shadow, YuvColorSpace, YuvFormat}; +use api::{PipelineId, PremultipliedColorF, Shadow, YuvColorSpace, YuvFormat, TileOffset}; 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}; use render_task::{BlitSource, RenderTask, RenderTaskCacheKey}; use render_task::{RenderTaskCacheKeyKind, RenderTaskId, RenderTaskCacheEntryHandle}; @@ -143,7 +144,6 @@ pub struct PictureIndex(pub usize); #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum PrimitiveKind { TextRun, - Image, Border, Brush, } @@ -193,6 +193,13 @@ pub struct PrimitiveMetadata { pub tag: Option, } +#[derive(Debug)] +pub struct VisibleImageTile { + pub tile_offset: TileOffset, + pub handle: GpuCacheHandle, + pub edge_flags: EdgeAaSegmentMask, +} + #[derive(Debug)] pub enum BrushKind { Solid { @@ -210,6 +217,7 @@ pub enum BrushKind { tile_spacing: LayerSize, source: ImageSource, sub_rect: Option, + visible_tiles: Vec, }, YuvImage { yuv_key: [ImageKey; 3], @@ -225,6 +233,7 @@ pub enum BrushKind { start_radius: f32, end_radius: f32, ratio_xy: f32, + stretch_size: LayerSize, }, LinearGradient { gradient_index: CachedGradientIndex, @@ -234,6 +243,7 @@ pub enum BrushKind { reverse_stops: bool, start_point: LayerPoint, end_point: LayerPoint, + stretch_size: LayerSize, } } @@ -902,7 +912,6 @@ impl ClipData { #[derive(Debug)] pub enum PrimitiveContainer { TextRun(TextRunPrimitiveCpu), - Image(ImagePrimitiveCpu), Border(BorderPrimitiveCpu), Brush(BrushPrimitive), } @@ -935,7 +944,6 @@ impl PrimitiveContainer { } } } - PrimitiveContainer::Image(..) | PrimitiveContainer::Border(..) => { true } @@ -987,7 +995,6 @@ impl PrimitiveContainer { } } } - PrimitiveContainer::Image(..) | PrimitiveContainer::Border(..) => { panic!("bug: other primitive containers not expected here"); } @@ -1119,17 +1126,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(), @@ -1166,6 +1162,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]; match metadata.prim_kind { PrimitiveKind::Border => {} @@ -1180,122 +1177,20 @@ 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, .. } => { + BrushKind::Image { + request, + sub_rect, + stretch_size, + ref mut tile_spacing, + ref mut current_epoch, + ref mut source, + ref mut visible_tiles, + .. + } => { let image_properties = frame_state .resource_cache .get_image_properties(request.key); @@ -1309,9 +1204,22 @@ impl PrimitiveStore { metadata.opacity.is_opaque = image_properties.descriptor.is_opaque; } + if *tile_spacing != LayerSize::zero() && image_properties.tiling.is_none() { + *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!(image_properties.tiling.is_none()); *source = ImageSource::Cache { // Size in device-pixels we need to allocate in render task cache. size: rect.size, @@ -1326,7 +1234,21 @@ 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_x = (tile_spacing.width * size.width as f32 / + stretch_size.width) as i32; + let padding_y = (tile_spacing.height * size.height as f32 / + stretch_size.height) as i32; + + if padding_x > 0 { + metadata.opacity.is_opaque = false; + size.width += padding_x; + } + if padding_y > 0 { + metadata.opacity.is_opaque = false; + size.height += padding_y; + } + let image_cache_key = ImageCacheKey { request, texel_rect: sub_rect, @@ -1335,7 +1257,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, @@ -1351,7 +1273,7 @@ impl PrimitiveStore { // a normal transient render task surface. This will // copy only the sub-rect, if specified. let cache_to_target_task = RenderTask::new_blit( - size, + *size, BlitSource::Image { key: image_cache_key }, ); let cache_to_target_task_id = render_tasks.add(cache_to_target_task); @@ -1360,7 +1282,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, }, @@ -1382,14 +1304,78 @@ impl PrimitiveStore { } } - if request_source_image { + if let Some(tile_size) = image_properties.tiling { + is_tiled_image = true; + + 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_conservatrive_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 = LayerRect { + 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.write_segment(*tile_rect, [1.0, 1.0, 0.0, 0.0]); + } + + visible_tiles.push(VisibleImageTile { + tile_offset, + handle, + edge_flags: tile_flags & edge_flags, + }); + } + ); + } + ); + } 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(); @@ -1448,6 +1434,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 @@ -1459,10 +1450,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); @@ -1470,15 +1457,32 @@ impl PrimitiveStore { PrimitiveKind::Brush => { let brush = &self.cpu_brushes[metadata.cpu_prim_index.0]; brush.write_gpu_blocks(&mut request); + + let repeat = match brush.kind { + BrushKind::Image { stretch_size, .. } | + BrushKind::LinearGradient { stretch_size, .. } | + BrushKind::RadialGradient { stretch_size, .. } => { + [ + metadata.local_rect.size.width / stretch_size.width, + metadata.local_rect.size.height / stretch_size.height, + 0.0, + 0.0, + ] + } + _ => { + [1.0, 1.0, 0.0, 0.0] + } + }; + match brush.segment_desc { Some(ref segment_desc) => { for segment in &segment_desc.segments { // has to match VECS_PER_SEGMENT - request.write_segment(segment.local_rect); + request.write_segment(segment.local_rect, repeat); } } None => { - request.write_segment(metadata.local_rect); + request.write_segment(metadata.local_rect, repeat); } } } @@ -2155,6 +2159,41 @@ impl PrimitiveStore { } } +fn compute_conservatrive_visible_rect( + prim_run_context: &PrimitiveRunContext, + frame_context: &FrameBuildingContext, + local_clip_rect: &LayerRect, +) -> LayerRect { + 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(LayerRect::zero()); + } + + *local_clip_rect +} + +fn edge_flags_for_tile_spacing(tile_spacing: &LayerSize) -> EdgeAaSegmentMask { + // If the number of gpu blocks for the request changes we can't reuse + // the same gpu location. + 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; @@ -2246,13 +2285,9 @@ impl<'a> GpuDataRequest<'a> { fn write_segment( &mut self, local_rect: LayerRect, + extra_params: [f32; 4], ) { self.push(local_rect); - self.push([ - 1.0, - 1.0, - 0.0, - 0.0 - ]); + self.push(extra_params); } } diff --git a/webrender/src/render_backend.rs b/webrender/src/render_backend.rs index 41dd893eeb..004e6b0fa8 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/renderer.rs b/webrender/src/renderer.rs index fa1eb3913c..75454c14c6 100644 --- a/webrender/src/renderer.rs +++ b/webrender/src/renderer.rs @@ -137,10 +137,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, @@ -184,12 +180,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", } @@ -198,7 +188,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, } @@ -2693,7 +2682,8 @@ impl Renderer { source_rect } }; - debug_assert_eq!(source_rect.size, blit.target_rect.size); + debug_assert!(source_rect.size.width <= blit.target_rect.size.width); + debug_assert!(source_rect.size.height <= blit.target_rect.size.height); self.device.blit_render_target( source_rect, blit.target_rect, diff --git a/webrender/src/resource_cache.rs b/webrender/src/resource_cache.rs index ba469e0282..e8139ad690 100644 --- a/webrender/src/resource_cache.rs +++ b/webrender/src/resource_cache.rs @@ -109,8 +109,6 @@ pub struct ImageTiling { pub tile_size: TileSize, } -pub type TiledImageMap = FastHashMap; - #[derive(Default)] struct ImageTemplates { images: FastHashMap, @@ -237,6 +235,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 { @@ -878,28 +886,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 1c4f3f0196..6e1a637bbe 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; @@ -53,7 +53,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 c9b0ed5522..f1d2bf064c 100644 --- a/webrender/src/shade.rs +++ b/webrender/src/shade.rs @@ -461,7 +461,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, @@ -596,11 +595,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() { @@ -609,12 +606,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, @@ -711,7 +702,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, @@ -769,11 +759,6 @@ impl Shaders { TransformBatchKind::TextRun(..) => { unreachable!("bug: text batches are special cased"); } - 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 } @@ -806,11 +791,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/wrench/reftests/image/tile-with-spacing.yaml b/wrench/reftests/image/tile-with-spacing.yaml index 40bc5802d4..0a4493d7f6 100644 --- a/wrench/reftests/image/tile-with-spacing.yaml +++ b/wrench/reftests/image/tile-with-spacing.yaml @@ -5,7 +5,7 @@ root: # applied so that when an image is split into several primitives, so that the latter # all get snapped the same way rather than independently. #- image: xy-gradient(300, 300) - - image: solid-color(255, 0, 0, 255, 300, 300) + - image: xy-gradient(800, 800) bounds: 0 0 800 800 stretch-size: 200 200 tile-spacing: 10 10 diff --git a/wrench/reftests/transforms/local-clip.png b/wrench/reftests/transforms/local-clip.png index 88fcc870f9..92e25ec5e5 100644 Binary files a/wrench/reftests/transforms/local-clip.png and b/wrench/reftests/transforms/local-clip.png differ