--- a/gfx/webrender/Cargo.toml
+++ b/gfx/webrender/Cargo.toml
@@ -13,28 +13,28 @@ profiler = ["thread_profiler/thread_prof
debugger = ["ws", "serde_json", "serde", "image", "base64"]
capture = ["webrender_api/serialize", "ron", "serde"]
replay = ["webrender_api/deserialize", "ron", "serde"]
[dependencies]
app_units = "0.6"
bincode = "0.9"
byteorder = "1.0"
-euclid = "0.16"
+euclid = "0.17"
fxhash = "0.2.1"
gleam = "0.4.20"
lazy_static = "1"
log = "0.4"
num-traits = "0.1.32"
time = "0.1"
rayon = "1"
webrender_api = {path = "../webrender_api"}
bitflags = "1.0"
thread_profiler = "0.1.1"
-plane-split = "0.7"
+plane-split = "0.8"
png = { optional = true, version = "0.11" }
smallvec = "0.6"
ws = { optional = true, version = "0.7.3" }
serde_json = { optional = true, version = "1.0" }
serde = { optional = true, version = "1.0", features = ["serde_derive"] }
image = { optional = true, version = "0.17" }
base64 = { optional = true, version = "0.3.0" }
ron = { optional = true, version = "0.1.7" }
--- a/gfx/webrender/res/brush_blend.glsl
+++ b/gfx/webrender/res/brush_blend.glsl
@@ -8,16 +8,17 @@
#include shared,prim_shared,brush
varying vec3 vUv;
flat varying float vAmount;
flat varying int vOp;
flat varying mat4 vColorMat;
flat varying vec4 vColorOffset;
+flat varying vec4 vUvClipBounds;
#ifdef WR_VERTEX_SHADER
void brush_vs(
VertexInfo vi,
int prim_address,
RectWithSize local_rect,
ivec3 user_data,
@@ -25,16 +26,20 @@ void brush_vs(
) {
PictureTask src_task = fetch_picture_task(user_data.x);
vec2 texture_size = vec2(textureSize(sColor0, 0).xy);
vec2 uv = vi.snapped_device_pos +
src_task.common_data.task_rect.p0 -
src_task.content_origin;
vUv = vec3(uv / texture_size, src_task.common_data.texture_layer_index);
+ vec2 uv0 = src_task.common_data.task_rect.p0;
+ vec2 uv1 = uv0 + src_task.common_data.task_rect.size;
+ vUvClipBounds = vec4(uv0, uv1) / texture_size.xyxy;
+
vOp = user_data.y;
float lumR = 0.2126;
float lumG = 0.7152;
float lumB = 0.0722;
float oneMinusLumR = 1.0 - lumR;
float oneMinusLumG = 1.0 - lumG;
float oneMinusLumB = 1.0 - lumB;
@@ -137,14 +142,18 @@ vec4 brush_fs() {
break;
case 8:
color = Opacity(Cs, vAmount);
break;
default:
color = vColorMat * Cs + vColorOffset;
}
+ // Fail-safe to ensure that we don't sample outside the rendered
+ // portion of a blend source.
+ color.a *= point_inside_rect(vUv.xy, vUvClipBounds.xy, vUvClipBounds.zw);
+
// Pre-multiply the alpha into the output value.
color.rgb *= color.a;
return color;
}
#endif
--- a/gfx/webrender/res/brush_image.glsl
+++ b/gfx/webrender/res/brush_image.glsl
@@ -1,24 +1,29 @@
/* 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/. */
-#define VECS_PER_SPECIFIC_BRUSH 0
+#define VECS_PER_SPECIFIC_BRUSH 1
#include shared,prim_shared,brush
#ifdef WR_FEATURE_ALPHA_PASS
varying vec2 vLocalPos;
#endif
varying vec3 vUv;
flat varying vec4 vUvBounds;
+flat varying vec4 vColor;
+flat varying vec2 vSelect;
+#ifdef WR_VERTEX_SHADER
-#ifdef WR_VERTEX_SHADER
+#define IMAGE_SOURCE_COLOR 0
+#define IMAGE_SOURCE_ALPHA 1
+#define IMAGE_SOURCE_MASK_FROM_COLOR 2
void brush_vs(
VertexInfo vi,
int prim_address,
RectWithSize local_rect,
ivec3 user_data,
PictureTask pic_task
) {
@@ -30,39 +35,54 @@ void brush_vs(
vec2 texture_size = vec2(textureSize(sColor0, 0));
#endif
ImageResource res = fetch_image_resource(user_data.x);
vec2 uv0 = res.uv_rect.p0;
vec2 uv1 = res.uv_rect.p1;
vUv.z = res.layer;
+ vColor = res.color;
vec2 f = (vi.local_pos - local_rect.p0) / local_rect.size;
vUv.xy = mix(uv0, uv1, f);
vUv.xy /= texture_size;
// Handle case where the UV coords are inverted (e.g. from an
// external image).
vUvBounds = vec4(
min(uv0, uv1) + vec2(0.5),
max(uv0, uv1) - vec2(0.5)
) / texture_size.xyxy;
+ switch (user_data.y) {
+ case IMAGE_SOURCE_COLOR:
+ vSelect = vec2(0.0, 0.0);
+ break;
+ case IMAGE_SOURCE_ALPHA:
+ vSelect = vec2(0.0, 1.0);
+ break;
+ case IMAGE_SOURCE_MASK_FROM_COLOR:
+ vSelect = vec2(1.0, 1.0);
+ break;
+ }
+
#ifdef WR_FEATURE_ALPHA_PASS
vLocalPos = vi.local_pos;
#endif
}
#endif
#ifdef WR_FRAGMENT_SHADER
vec4 brush_fs() {
vec2 uv = clamp(vUv.xy, vUvBounds.xy, vUvBounds.zw);
- vec4 color = TEX_SAMPLE(sColor0, vec3(uv, vUv.z));
+ vec4 texel = TEX_SAMPLE(sColor0, vec3(uv, vUv.z));
+ vec4 mask = mix(texel.rrrr, texel.aaaa, vSelect.x);
+ vec4 color = mix(texel, vColor * mask, vSelect.y);
#ifdef WR_FEATURE_ALPHA_PASS
color *= init_transform_fs(vLocalPos);
#endif
return color;
}
#endif
deleted file mode 100644
--- a/gfx/webrender/res/brush_mask_corner.glsl
+++ /dev/null
@@ -1,64 +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/. */
-
-#define VECS_PER_SPECIFIC_BRUSH 1
-
-#include shared,prim_shared,ellipse,brush
-
-flat varying float vClipMode;
-flat varying vec4 vClipCenter_Radius;
-flat varying vec4 vLocalRect;
-varying vec2 vLocalPos;
-
-#ifdef WR_VERTEX_SHADER
-
-struct BrushMaskCornerPrimitive {
- vec2 radius;
- float clip_mode;
-};
-
-BrushMaskCornerPrimitive fetch_primitive(int address) {
- vec4 data = fetch_from_resource_cache_1(address);
- return BrushMaskCornerPrimitive(data.xy, data.z);
-}
-
-void brush_vs(
- VertexInfo vi,
- int prim_address,
- RectWithSize local_rect,
- ivec3 user_data,
- PictureTask pic_task
-) {
- // Load the specific primitive.
- BrushMaskCornerPrimitive prim = fetch_primitive(prim_address);
-
- // Write clip parameters
- vClipMode = prim.clip_mode;
- vClipCenter_Radius = vec4(local_rect.p0 + prim.radius, prim.radius);
-
- vLocalRect = vec4(local_rect.p0, local_rect.p0 + local_rect.size);
- vLocalPos = vi.local_pos;
-}
-#endif
-
-#ifdef WR_FRAGMENT_SHADER
-vec4 brush_fs() {
- float d = 1.0;
- // NOTE: The AA range must be computed outside the if statement,
- // since otherwise the results can be undefined if the
- // input function is not continuous. I have observed this
- // as flickering behaviour on Intel GPUs.
- float aa_range = compute_aa_range(vLocalPos);
- // Check if in valid clip region.
- if (vLocalPos.x < vClipCenter_Radius.x && vLocalPos.y < vClipCenter_Radius.y) {
- // Apply ellipse clip on corner.
- d = distance_to_ellipse(vLocalPos - vClipCenter_Radius.xy,
- vClipCenter_Radius.zw,
- aa_range);
- d = distance_aa(aa_range, d);
- }
-
- return vec4(mix(d, 1.0 - d, vClipMode));
-}
-#endif
--- a/gfx/webrender/res/brush_picture.glsl
+++ b/gfx/webrender/res/brush_picture.glsl
@@ -10,24 +10,20 @@
varying vec2 vLocalPos;
#endif
varying vec3 vUv;
flat varying int vImageKind;
flat varying vec4 vUvBounds;
flat varying vec4 vUvBounds_NoClamp;
flat varying vec4 vParams;
-
-#if defined WR_FEATURE_ALPHA_TARGET || defined WR_FEATURE_COLOR_TARGET_ALPHA_MASK
flat varying vec4 vColor;
-#endif
#define BRUSH_PICTURE_SIMPLE 0
#define BRUSH_PICTURE_NINEPATCH 1
-#define BRUSH_PICTURE_MIRROR 2
#ifdef WR_VERTEX_SHADER
struct Picture {
vec4 color;
};
Picture fetch_picture(int address) {
@@ -39,43 +35,24 @@ void brush_vs(
VertexInfo vi,
int prim_address,
RectWithSize local_rect,
ivec3 user_data,
PictureTask pic_task
) {
vImageKind = user_data.y;
- // TODO(gw): There's quite a bit of code duplication here,
- // depending on which variation of brush image
- // this is being used for. This is because only
- // box-shadow pictures are currently supported
- // as texture cacheable items. Once we port the
- // drop-shadows and text-shadows to be cacheable,
- // most of this code can be merged together.
-#if defined WR_FEATURE_COLOR_TARGET || defined WR_FEATURE_COLOR_TARGET_ALPHA_MASK
- BlurTask blur_task = fetch_blur_task(user_data.x);
- vUv.z = blur_task.common_data.texture_layer_index;
- vec2 texture_size = vec2(textureSize(sColor0, 0).xy);
-#if defined WR_FEATURE_COLOR_TARGET_ALPHA_MASK
- vColor = blur_task.color;
-#endif
- vec2 uv0 = blur_task.common_data.task_rect.p0;
- vec2 src_size = blur_task.common_data.task_rect.size * blur_task.scale_factor;
- vec2 uv1 = uv0 + blur_task.common_data.task_rect.size;
-#else
Picture pic = fetch_picture(prim_address);
ImageResource res = fetch_image_resource(user_data.x);
vec2 texture_size = vec2(textureSize(sColor1, 0).xy);
vColor = pic.color;
vec2 uv0 = res.uv_rect.p0;
vec2 uv1 = res.uv_rect.p1;
vec2 src_size = (uv1 - uv0) * res.user_data.x;
vUv.z = res.layer;
-#endif
// TODO(gw): In the future we'll probably draw these as segments
// with the brush shader. When that occurs, we can
// modify the UVs for each segment in the VS, and the
// FS can become a simple shader that doesn't need
// to adjust the UVs.
switch (vImageKind) {
@@ -87,22 +64,16 @@ void brush_vs(
}
case BRUSH_PICTURE_NINEPATCH: {
vec2 local_src_size = src_size / uDevicePixelRatio;
vUv.xy = (vi.local_pos - local_rect.p0) / local_src_size;
vParams.xy = vec2(0.5);
vParams.zw = (local_rect.size / local_src_size - 0.5);
break;
}
- case BRUSH_PICTURE_MIRROR: {
- vec2 local_src_size = src_size / uDevicePixelRatio;
- vUv.xy = (vi.local_pos - local_rect.p0) / local_src_size;
- vParams.xy = 0.5 * local_rect.size / local_src_size;
- break;
- }
default:
vUv.xy = vec2(0.0);
vParams = vec4(0.0);
}
vUvBounds = vec4(uv0 + vec2(0.5), uv1 - vec2(0.5)) / texture_size.xyxy;
vUvBounds_NoClamp = vec4(uv0, uv1) / texture_size.xyxy;
@@ -123,40 +94,21 @@ vec4 brush_fs() {
}
case BRUSH_PICTURE_NINEPATCH: {
uv = clamp(vUv.xy, vec2(0.0), vParams.xy);
uv += max(vec2(0.0), vUv.xy - vParams.zw);
uv = mix(vUvBounds_NoClamp.xy, vUvBounds_NoClamp.zw, uv);
uv = clamp(uv, vUvBounds.xy, vUvBounds.zw);
break;
}
- case BRUSH_PICTURE_MIRROR: {
- // Mirror and stretch the box shadow corner over the entire
- // primitives.
- uv = vParams.xy - abs(vUv.xy - vParams.xy);
-
- // Ensure that we don't fetch texels outside the box
- // shadow corner. This can happen, for example, when
- // drawing the outer parts of an inset box shadow.
- uv = clamp(uv, vec2(0.0), vec2(1.0));
- uv = mix(vUvBounds_NoClamp.xy, vUvBounds_NoClamp.zw, uv);
- uv = clamp(uv, vUvBounds.xy, vUvBounds.zw);
- break;
- }
default:
uv = vec2(0.0);
}
-#if defined WR_FEATURE_COLOR_TARGET
- vec4 color = texture(sColor0, vec3(uv, vUv.z));
-#elif defined WR_FEATURE_COLOR_TARGET_ALPHA_MASK
- vec4 color = vColor * texture(sColor0, vec3(uv, vUv.z)).a;
-#else
vec4 color = vColor * texture(sColor1, vec3(uv, vUv.z)).r;
-#endif
#ifdef WR_FEATURE_ALPHA_PASS
color *= init_transform_fs(vLocalPos);
#endif
return color;
}
#endif
--- a/gfx/webrender/res/cs_blur.glsl
+++ b/gfx/webrender/res/cs_blur.glsl
@@ -16,16 +16,36 @@ flat varying int vBlurRadius;
#define DIR_HORIZONTAL 0
#define DIR_VERTICAL 1
in int aBlurRenderTaskAddress;
in int aBlurSourceTaskAddress;
in int aBlurDirection;
+struct BlurTask {
+ RenderTaskCommonData common_data;
+ float blur_radius;
+ float scale_factor;
+ vec4 color;
+};
+
+BlurTask fetch_blur_task(int address) {
+ RenderTaskData task_data = fetch_render_task_data(address);
+
+ BlurTask task = BlurTask(
+ task_data.common_data,
+ task_data.data1.x,
+ task_data.data1.y,
+ task_data.data2
+ );
+
+ return task;
+}
+
void main(void) {
BlurTask blur_task = fetch_blur_task(aBlurRenderTaskAddress);
RenderTaskCommonData src_task = fetch_render_task_common_data(aBlurSourceTaskAddress);
RectWithSize src_rect = src_task.task_rect;
RectWithSize target_rect = blur_task.common_data.task_rect;
#if defined WR_FEATURE_COLOR_TARGET
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -111,16 +111,24 @@ vec4[3] fetch_from_resource_cache_3(int
ivec2 uv = get_resource_cache_uv(address);
return vec4[3](
TEXEL_FETCH(sResourceCache, uv, 0, ivec2(0, 0)),
TEXEL_FETCH(sResourceCache, uv, 0, ivec2(1, 0)),
TEXEL_FETCH(sResourceCache, uv, 0, ivec2(2, 0))
);
}
+vec4[3] fetch_from_resource_cache_3_direct(ivec2 address) {
+ return vec4[3](
+ TEXEL_FETCH(sResourceCache, address, 0, ivec2(0, 0)),
+ TEXEL_FETCH(sResourceCache, address, 0, ivec2(1, 0)),
+ TEXEL_FETCH(sResourceCache, address, 0, ivec2(2, 0))
+ );
+}
+
vec4[4] fetch_from_resource_cache_4_direct(ivec2 address) {
return vec4[4](
TEXEL_FETCH(sResourceCache, address, 0, ivec2(0, 0)),
TEXEL_FETCH(sResourceCache, address, 0, ivec2(1, 0)),
TEXEL_FETCH(sResourceCache, address, 0, ivec2(2, 0)),
TEXEL_FETCH(sResourceCache, address, 0, ivec2(3, 0))
);
}
@@ -256,36 +264,16 @@ PictureTask fetch_picture_task(int addre
task_data.data1.xy,
task_data.data1.z,
task_data.data2
);
return task;
}
-struct BlurTask {
- RenderTaskCommonData common_data;
- float blur_radius;
- float scale_factor;
- vec4 color;
-};
-
-BlurTask fetch_blur_task(int address) {
- RenderTaskData task_data = fetch_render_task_data(address);
-
- BlurTask task = BlurTask(
- task_data.common_data,
- task_data.data1.x,
- task_data.data1.y,
- task_data.data2
- );
-
- return task;
-}
-
struct ClipArea {
RenderTaskCommonData common_data;
vec2 screen_origin;
};
ClipArea fetch_clip_area(int index) {
ClipArea area;
@@ -683,29 +671,30 @@ GlyphResource fetch_glyph_resource(int a
vec4 data[2] = fetch_from_resource_cache_2(address);
return GlyphResource(data[0], data[1].x, data[1].yz, data[1].w);
}
struct ImageResource {
RectWithEndpoint uv_rect;
float layer;
vec3 user_data;
+ vec4 color;
};
ImageResource fetch_image_resource(int address) {
//Note: number of blocks has to match `renderer::BLOCKS_PER_UV_RECT`
- vec4 data[2] = fetch_from_resource_cache_2(address);
+ vec4 data[3] = fetch_from_resource_cache_3(address);
RectWithEndpoint uv_rect = RectWithEndpoint(data[0].xy, data[0].zw);
- return ImageResource(uv_rect, data[1].x, data[1].yzw);
+ return ImageResource(uv_rect, data[1].x, data[1].yzw, data[2]);
}
ImageResource fetch_image_resource_direct(ivec2 address) {
- vec4 data[2] = fetch_from_resource_cache_2_direct(address);
+ vec4 data[3] = fetch_from_resource_cache_3_direct(address);
RectWithEndpoint uv_rect = RectWithEndpoint(data[0].xy, data[0].zw);
- return ImageResource(uv_rect, data[1].x, data[1].yzw);
+ return ImageResource(uv_rect, data[1].x, data[1].yzw, data[2]);
}
struct TextRun {
vec4 color;
vec4 bg_color;
vec2 offset;
};
--- a/gfx/webrender/res/rect.glsl
+++ b/gfx/webrender/res/rect.glsl
@@ -41,8 +41,13 @@ RectWithSize intersect_rects(RectWithSiz
return result;
}
bool rect_inside_rect(RectWithSize little, RectWithSize big) {
return all(lessThanEqual(vec4(big.p0, little.p0 + little.size),
vec4(little.p0, big.p0 + big.size)));
}
+
+float point_inside_rect(vec2 p, vec2 p0, vec2 p1) {
+ vec2 s = step(p0, p) - step(p1, p);
+ return s.x * s.y;
+}
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.rs
@@ -7,30 +7,30 @@ use api::{DeviceUintRect, DeviceUintPoin
use api::{DeviceIntPoint, LayerPoint, SubpixelDirection, YuvColorSpace, YuvFormat};
use api::{LayerToWorldTransform, WorldPixel};
use border::{BorderCornerInstance, BorderCornerSide, BorderEdgeKind};
use clip::{ClipSource, ClipStore, ClipWorkItem};
use clip_scroll_tree::{CoordinateSystemId};
use euclid::{TypedTransform3D, vec3};
use glyph_rasterizer::GlyphFormat;
use gpu_cache::{GpuCache, GpuCacheAddress};
-use gpu_types::{BrushFlags, BrushImageKind, BrushInstance, ClipChainRectIndex};
+use gpu_types::{BrushFlags, BrushInstance, ClipChainRectIndex};
use gpu_types::{ClipMaskInstance, ClipScrollNodeIndex};
use gpu_types::{CompositePrimitiveInstance, PrimitiveInstance, SimplePrimitiveInstance};
use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
use picture::{ContentOrigin, PictureCompositeMode, PictureKind, PicturePrimitive, PictureSurface};
use plane_split::{BspSplitter, Polygon, Splitter};
use prim_store::{CachedGradient, ImageSource, PrimitiveIndex, PrimitiveKind, PrimitiveMetadata, PrimitiveStore};
use prim_store::{BrushPrimitive, BrushKind, DeferredResolve, EdgeAaSegmentMask, PrimitiveRun};
use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskKind, RenderTaskTree};
use renderer::{BlendMode, ImageBufferKind};
use renderer::BLOCKS_PER_UV_RECT;
use resource_cache::{CacheItem, GlyphFetchResult, ImageRequest, ResourceCache};
use std::{usize, f32, i32};
-use tiling::{RenderTargetContext, RenderTargetKind};
+use tiling::{RenderTargetContext};
use util::{MatrixHelpers, TransformedRectKind};
// Special sentinel value recognized by the shader. It is considered to be
// a dummy task that doesn't mask out anything.
const OPAQUE_TASK_ADDRESS: RenderTaskAddress = RenderTaskAddress(0x7fff);
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
#[cfg_attr(feature = "capture", derive(Serialize))]
@@ -41,35 +41,26 @@ pub enum TransformBatchKind {
BorderCorner,
BorderEdge,
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub enum BrushImageSourceKind {
- Alpha,
- Color,
- ColorAlphaMask,
-}
-
-impl BrushImageSourceKind {
- pub fn from_render_target_kind(render_target_kind: RenderTargetKind) -> BrushImageSourceKind {
- match render_target_kind {
- RenderTargetKind::Color => BrushImageSourceKind::Color,
- RenderTargetKind::Alpha => BrushImageSourceKind::Alpha,
- }
- }
+ Color = 0,
+ //Alpha = 1, // Unused for now, but left here as shaders need to match.
+ ColorAlphaMask = 2,
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub enum BrushBatchKind {
- Picture(BrushImageSourceKind),
+ Picture,
Solid,
Line,
Image(ImageBufferKind),
Blend,
MixBlend {
task_id: RenderTaskId,
source_id: RenderTaskId,
backdrop_id: RenderTaskId,
@@ -305,22 +296,29 @@ impl BatchList {
BatchList {
alpha_batch_list: AlphaBatchList::new(),
opaque_batch_list: OpaqueBatchList::new(batch_area_threshold),
combined_bounding_rect: DeviceIntRect::zero(),
}
}
+ fn add_bounding_rect(
+ &mut self,
+ task_relative_bounding_rect: &DeviceIntRect,
+ ) {
+ self.combined_bounding_rect = self.combined_bounding_rect.union(task_relative_bounding_rect);
+ }
+
pub fn get_suitable_batch(
&mut self,
key: BatchKey,
task_relative_bounding_rect: &DeviceIntRect,
) -> &mut Vec<PrimitiveInstance> {
- self.combined_bounding_rect = self.combined_bounding_rect.union(task_relative_bounding_rect);
+ self.add_bounding_rect(task_relative_bounding_rect);
match key.blend_mode {
BlendMode::None => {
self.opaque_batch_list
.get_suitable_batch(key, task_relative_bounding_rect)
}
BlendMode::Alpha |
BlendMode::PremultipliedAlpha |
@@ -481,17 +479,17 @@ impl AlphaBatchBuilder {
};
// Even though most of the time a splitter isn't used or needed,
// they are cheap to construct so we will always pass one down.
let mut splitter = BspSplitter::new();
// Add each run in this picture to the batch.
for run in &pic.runs {
- let scroll_node = &ctx.clip_scroll_tree.nodes[&run.clip_and_scroll.scroll_node_id];
+ let scroll_node = &ctx.clip_scroll_tree.nodes[run.clip_and_scroll.scroll_node_id.0];
let scroll_id = scroll_node.node_data_index;
self.add_run_to_batch(
run,
scroll_id,
ctx,
gpu_cache,
render_tasks,
task_id,
@@ -899,19 +897,17 @@ impl AlphaBatchBuilder {
Some(PictureSurface::TextureCache(ref cache_item)) => {
match picture.kind {
PictureKind::TextShadow { .. } |
PictureKind::Image { .. } => {
panic!("BUG: only supported as render tasks for now");
}
PictureKind::BoxShadow { image_kind, .. } => {
let textures = BatchTextures::color(cache_item.texture_id);
- let kind = BrushBatchKind::Picture(
- BrushImageSourceKind::from_render_target_kind(picture.target_kind()),
- );
+ let kind = BrushBatchKind::Picture;
self.add_brush_to_batch(
&picture.brush,
prim_metadata,
kind,
specified_blend_mode,
non_segmented_blend_mode,
textures,
@@ -931,60 +927,63 @@ impl AlphaBatchBuilder {
}
Some(PictureSurface::RenderTask(cache_task_id)) => {
let cache_task_address = render_tasks.get_task_address(cache_task_id);
let textures = BatchTextures::render_target_cache();
match picture.kind {
PictureKind::TextShadow { .. } => {
let kind = BatchKind::Brush(
- BrushBatchKind::Picture(
- BrushImageSourceKind::from_render_target_kind(picture.target_kind())),
+ BrushBatchKind::Image(ImageBufferKind::Texture2DArray)
);
let key = BatchKey::new(kind, non_segmented_blend_mode, textures);
let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
+ let uv_rect_address = render_tasks[cache_task_id]
+ .get_texture_handle()
+ .as_int(gpu_cache);
+
let 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: EdgeAaSegmentMask::empty(),
brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
user_data: [
- cache_task_address.0 as i32,
- BrushImageKind::Simple as i32,
+ uv_rect_address,
+ BrushImageSourceKind::Color as i32,
0,
],
};
batch.push(PrimitiveInstance::from(instance));
}
PictureKind::BoxShadow { .. } => {
panic!("BUG: should be handled as a texture cache surface");
}
PictureKind::Image {
composite_mode,
secondary_render_task_id,
is_in_3d_context,
- reference_frame_id,
+ reference_frame_index,
real_local_rect,
ref extra_gpu_data_handle,
..
} => {
// If this picture is participating in a 3D rendering context,
// then don't add it to any batches here. Instead, create a polygon
// for it and add it to the current plane splitter.
if is_in_3d_context {
// Push into parent plane splitter.
let real_xf = &ctx.clip_scroll_tree
- .nodes[&reference_frame_id]
+ .nodes[reference_frame_index.0]
.world_content_transform
.into();
let polygon = make_polygon(
real_local_rect,
&real_xf,
prim_index.0,
);
@@ -1023,33 +1022,37 @@ impl AlphaBatchBuilder {
item_bounding_rect.size.width,
item_bounding_rect.size.height,
);
batch.push(PrimitiveInstance::from(instance));
}
FilterOp::DropShadow(offset, _, _) => {
let kind = BatchKind::Brush(
- BrushBatchKind::Picture(BrushImageSourceKind::ColorAlphaMask),
+ BrushBatchKind::Image(ImageBufferKind::Texture2DArray),
);
let key = BatchKey::new(kind, non_segmented_blend_mode, textures);
+ let uv_rect_address = render_tasks[cache_task_id]
+ .get_texture_handle()
+ .as_int(gpu_cache);
+
let 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: EdgeAaSegmentMask::empty(),
brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
user_data: [
- cache_task_address.0 as i32,
- BrushImageKind::Simple as i32,
+ uv_rect_address,
+ BrushImageSourceKind::ColorAlphaMask as i32,
0,
],
};
{
let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
batch.push(PrimitiveInstance::from(instance));
}
@@ -1241,16 +1244,18 @@ impl AlphaBatchBuilder {
clip_task_address,
z,
segment_index: 0,
edge_flags: EdgeAaSegmentMask::all(),
brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
user_data,
};
+ self.batch_list.add_bounding_rect(task_relative_bounding_rect);
+
match brush.segment_desc {
Some(ref segment_desc) => {
let alpha_batch_key = BatchKey {
blend_mode: alpha_blend_mode,
kind: BatchKind::Brush(batch_kind),
textures,
};
@@ -1334,17 +1339,21 @@ impl BrushPrimitive {
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), 0, 0],
+ [
+ cache_item.uv_rect_handle.as_int(gpu_cache),
+ BrushImageSourceKind::Color as i32,
+ 0,
+ ],
))
}
}
BrushKind::Picture => {
panic!("bug: get_batch_key is handled at higher level for pictures");
}
BrushKind::Solid { .. } => {
Some((
--- a/gfx/webrender/src/border.rs
+++ b/gfx/webrender/src/border.rs
@@ -1,17 +1,17 @@
/* 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/. */
use api::{BorderRadius, BorderSide, BorderStyle, BorderWidths, ColorF, LayerPoint};
use api::{LayerPrimitiveInfo, LayerRect, LayerSize, NormalBorder, RepeatMode, TexelRect};
use clip::ClipSource;
use ellipse::Ellipse;
-use frame_builder::FrameBuilder;
+use display_list_flattener::DisplayListFlattener;
use gpu_cache::GpuDataRequest;
use prim_store::{BorderPrimitiveCpu, BrushClipMaskKind, BrushSegment, BrushSegmentDescriptor};
use prim_store::{EdgeAaSegmentMask, PrimitiveContainer, ScrollNodeAndClipChain};
use util::{lerp, pack_as_float};
#[repr(u8)]
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum BorderCornerInstance {
@@ -274,17 +274,17 @@ pub fn ensure_no_corner_overlap(
bottom_left_radius.width *= ratio;
bottom_left_radius.height *= ratio;
bottom_right_radius.width *= ratio;
bottom_right_radius.height *= ratio;
}
}
-impl FrameBuilder {
+impl<'a> DisplayListFlattener<'a> {
fn add_normal_border_primitive(
&mut self,
info: &LayerPrimitiveInfo,
border: &NormalBorder,
widths: &BorderWidths,
clip_and_scroll: ScrollNodeAndClipChain,
corner_instances: [BorderCornerInstance; 4],
edges: [BorderEdgeKind; 4],
--- a/gfx/webrender/src/box_shadow.rs
+++ b/gfx/webrender/src/box_shadow.rs
@@ -2,36 +2,31 @@
* 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::{BorderRadius, BoxShadowClipMode, ClipMode, ColorF, ComplexClipRegion, LayerPoint};
use api::{LayerPrimitiveInfo, LayerRect, LayerSize, LayerVector2D, LayoutSize, LocalClip};
use api::PipelineId;
use app_units::Au;
use clip::ClipSource;
-use frame_builder::FrameBuilder;
+use display_list_flattener::DisplayListFlattener;
use gpu_types::BrushImageKind;
-use prim_store::{BrushKind, BrushMaskKind, BrushPrimitive, PrimitiveContainer};
+use prim_store::{BrushKind, BrushPrimitive, PrimitiveContainer};
use prim_store::ScrollNodeAndClipChain;
use picture::PicturePrimitive;
+use render_task::MAX_BLUR_STD_DEVIATION;
use util::RectHelpers;
-use render_task::MAX_BLUR_STD_DEVIATION;
// The blur shader samples BLUR_SAMPLE_SCALE * blur_radius surrounding texels.
pub const BLUR_SAMPLE_SCALE: f32 = 3.0;
// Maximum blur radius.
// Taken from https://searchfox.org/mozilla-central/rev/c633ffa4c4611f202ca11270dcddb7b29edddff8/layout/painting/nsCSSRendering.cpp#4412
pub const MAX_BLUR_RADIUS : f32 = 300.;
-// The amount of padding added to the border corner drawn in the box shadow
-// mask. This ensures that we get a few pixels past the corner that can be
-// blurred without being affected by the border radius.
-pub const MASK_CORNER_PADDING: f32 = 4.0;
-
#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct BoxShadowCacheKey {
pub width: Au,
pub height: Au,
pub blur_radius: Au,
pub spread_radius: Au,
@@ -43,17 +38,17 @@ pub struct BoxShadowCacheKey {
pub br_top_right_h: Au,
pub br_bottom_left_w: Au,
pub br_bottom_left_h: Au,
pub br_bottom_right_w: Au,
pub br_bottom_right_h: Au,
pub clip_mode: BoxShadowClipMode,
}
-impl FrameBuilder {
+impl<'a> DisplayListFlattener<'a> {
pub fn add_box_shadow(
&mut self,
pipeline_id: PipelineId,
clip_and_scroll: ScrollNodeAndClipChain,
prim_info: &LayerPrimitiveInfo,
box_offset: &LayerVector2D,
color: &ColorF,
mut blur_radius: f32,
@@ -166,78 +161,60 @@ impl FrameBuilder {
clip_mode,
};
match clip_mode {
BoxShadowClipMode::Outset => {
let mut width;
let mut height;
let brush_prim;
- let corner_size = shadow_radius.is_uniform_size();
- let mut image_kind;
+ let mut image_kind = BrushImageKind::NinePatch;
if !shadow_rect.is_well_formed_and_nonempty() {
return;
}
- // If the outset box shadow has a uniform corner side, we can
- // just blur the top left corner, and stretch / mirror that
- // across the primitive.
- if let Some(corner_size) = corner_size {
- image_kind = BrushImageKind::Mirror;
- width = MASK_CORNER_PADDING + corner_size.width.max(BLUR_SAMPLE_SCALE * blur_radius);
- height = MASK_CORNER_PADDING + corner_size.height.max(BLUR_SAMPLE_SCALE * blur_radius);
+ // Create a minimal size primitive mask to blur. In this
+ // case, we ensure the size of each corner is the same,
+ // to simplify the shader logic that stretches the blurred
+ // result across the primitive.
+ let max_width = shadow_radius.top_left.width
+ .max(shadow_radius.bottom_left.width)
+ .max(shadow_radius.top_right.width)
+ .max(shadow_radius.bottom_right.width);
+ let max_height = shadow_radius.top_left.height
+ .max(shadow_radius.bottom_left.height)
+ .max(shadow_radius.top_right.height)
+ .max(shadow_radius.bottom_right.height);
+
+ width = 2.0 * max_width + BLUR_SAMPLE_SCALE * blur_radius;
+ height = 2.0 * max_height + BLUR_SAMPLE_SCALE * blur_radius;
- brush_prim = BrushPrimitive::new(
- BrushKind::Mask {
- clip_mode: brush_clip_mode,
- kind: BrushMaskKind::Corner(corner_size),
- },
- None,
- );
- } else {
- // Create a minimal size primitive mask to blur. In this
- // case, we ensure the size of each corner is the same,
- // to simplify the shader logic that stretches the blurred
- // result across the primitive.
- image_kind = BrushImageKind::NinePatch;
- let max_width = shadow_radius.top_left.width
- .max(shadow_radius.bottom_left.width)
- .max(shadow_radius.top_right.width)
- .max(shadow_radius.bottom_right.width);
- let max_height = shadow_radius.top_left.height
- .max(shadow_radius.bottom_left.height)
- .max(shadow_radius.top_right.height)
- .max(shadow_radius.bottom_right.height);
+ // If the width or height ends up being bigger than the original
+ // primitive shadow rect, just blur the entire rect and draw that
+ // as a simple blit.
+ if width > prim_info.rect.size.width || height > prim_info.rect.size.height {
+ image_kind = BrushImageKind::Simple;
+ width = prim_info.rect.size.width + spread_amount * 2.0;
+ height = prim_info.rect.size.height + spread_amount * 2.0;
+ }
- width = 2.0 * max_width + BLUR_SAMPLE_SCALE * blur_radius;
- height = 2.0 * max_height + BLUR_SAMPLE_SCALE * blur_radius;
-
- // If the width or height ends up being bigger than the original
- // primitive shadow rect, just blur the entire rect and draw that
- // as a simple blit.
- if width > prim_info.rect.size.width || height > prim_info.rect.size.height {
- image_kind = BrushImageKind::Simple;
- width = prim_info.rect.size.width + spread_amount * 2.0;
- height = prim_info.rect.size.height + spread_amount * 2.0;
- }
+ let clip_rect = LayerRect::new(
+ LayerPoint::zero(),
+ LayerSize::new(width, height)
+ );
- let clip_rect = LayerRect::new(
- LayerPoint::zero(),
- LayerSize::new(width, height)
- );
-
- brush_prim = BrushPrimitive::new(
- BrushKind::Mask {
- clip_mode: brush_clip_mode,
- kind: BrushMaskKind::RoundedRect(clip_rect, shadow_radius),
- },
- None,
- );
- };
+ brush_prim = BrushPrimitive::new(
+ BrushKind::Mask {
+ clip_mode: brush_clip_mode,
+ rect: clip_rect,
+ radii: shadow_radius,
+ },
+ None,
+ );
// Construct a mask primitive to add to the picture.
let brush_rect = LayerRect::new(LayerPoint::zero(),
LayerSize::new(width, height));
let brush_info = LayerPrimitiveInfo::new(brush_rect);
let brush_prim_index = self.create_primitive(
&brush_info,
Vec::new(),
@@ -302,17 +279,18 @@ impl FrameBuilder {
adjusted_blur_std_deviation *= 0.5;
inflate_size *= 2.0;
}
let brush_rect = brush_rect.inflate(inflate_size + box_offset.x.abs(), inflate_size + box_offset.y.abs());
let brush_prim = BrushPrimitive::new(
BrushKind::Mask {
clip_mode: brush_clip_mode,
- kind: BrushMaskKind::RoundedRect(clip_rect, shadow_radius),
+ rect: clip_rect,
+ radii: shadow_radius,
},
None,
);
let brush_info = LayerPrimitiveInfo::new(brush_rect);
let brush_prim_index = self.create_primitive(
&brush_info,
Vec::new(),
PrimitiveContainer::Brush(brush_prim),
--- a/gfx/webrender/src/clip.rs
+++ b/gfx/webrender/src/clip.rs
@@ -9,17 +9,17 @@ use clip_scroll_tree::{ClipChainIndex, C
use ellipse::Ellipse;
use freelist::{FreeList, FreeListHandle, WeakFreeListHandle};
use gpu_cache::{GpuCache, GpuCacheHandle, ToGpuBlocks};
use gpu_types::ClipScrollNodeIndex;
use prim_store::{ClipData, ImageMaskData};
use resource_cache::{ImageRequest, ResourceCache};
use util::{LayerToWorldFastTransform, MaxRect, calculate_screen_bounding_rect};
use util::extract_inner_rect_safe;
-use std::rc::Rc;
+use std::sync::Arc;
pub type ClipStore = FreeList<ClipSources>;
pub type ClipSourcesHandle = FreeListHandle<ClipSources>;
pub type ClipSourcesWeakHandle = WeakFreeListHandle<ClipSources>;
#[derive(Clone, Debug)]
pub struct ClipRegion {
pub main: LayerRect,
@@ -348,17 +348,17 @@ pub fn rounded_rectangle_contains_point(
if bottom_left_center.x > point.x && bottom_left_center.y < point.y &&
!Ellipse::new(radii.bottom_left).contains(*point - bottom_left_center.to_vector()) {
return false;
}
true
}
-pub type ClipChainNodeRef = Option<Rc<ClipChainNode>>;
+pub type ClipChainNodeRef = Option<Arc<ClipChainNode>>;
#[derive(Debug, Clone)]
pub struct ClipChainNode {
pub work_item: ClipWorkItem,
pub local_clip_rect: LayerRect,
pub screen_outer_rect: DeviceIntRect,
pub screen_inner_rect: DeviceIntRect,
pub prev: ClipChainNodeRef,
@@ -377,39 +377,25 @@ impl ClipChain {
ClipChain {
parent_index: None,
combined_inner_screen_rect: *screen_rect,
combined_outer_screen_rect: *screen_rect,
nodes: None,
}
}
- pub fn new_with_added_node(
- &self,
- work_item: ClipWorkItem,
- local_clip_rect: LayerRect,
- screen_outer_rect: DeviceIntRect,
- screen_inner_rect: DeviceIntRect,
- ) -> ClipChain {
+ pub fn new_with_added_node(&self, new_node: &ClipChainNode) -> ClipChain {
// If the new node's inner rectangle completely surrounds our outer rectangle,
// we can discard the new node entirely since it isn't going to affect anything.
- if screen_inner_rect.contains_rect(&self.combined_outer_screen_rect) {
+ if new_node.screen_inner_rect.contains_rect(&self.combined_outer_screen_rect) {
return self.clone();
}
- let new_node = ClipChainNode {
- work_item,
- local_clip_rect,
- screen_outer_rect,
- screen_inner_rect,
- prev: None,
- };
-
let mut new_chain = self.clone();
- new_chain.add_node(new_node);
+ new_chain.add_node(new_node.clone());
new_chain
}
pub fn add_node(&mut self, mut new_node: ClipChainNode) {
new_node.prev = self.nodes.clone();
// If this clip's outer rectangle is completely enclosed by the clip
// chain's inner rectangle, then the only clip that matters from this point
@@ -420,26 +406,26 @@ impl ClipChain {
self.combined_outer_screen_rect =
self.combined_outer_screen_rect.intersection(&new_node.screen_outer_rect)
.unwrap_or_else(DeviceIntRect::zero);
self.combined_inner_screen_rect =
self.combined_inner_screen_rect.intersection(&new_node.screen_inner_rect)
.unwrap_or_else(DeviceIntRect::zero);
- self.nodes = Some(Rc::new(new_node));
+ self.nodes = Some(Arc::new(new_node));
}
}
pub struct ClipChainNodeIter {
pub current: ClipChainNodeRef,
}
impl Iterator for ClipChainNodeIter {
- type Item = Rc<ClipChainNode>;
+ type Item = Arc<ClipChainNode>;
fn next(&mut self) -> ClipChainNodeRef {
let previous = self.current.clone();
self.current = match self.current {
Some(ref item) => item.prev.clone(),
None => return None,
};
previous
--- a/gfx/webrender/src/clip_scroll_node.rs
+++ b/gfx/webrender/src/clip_scroll_node.rs
@@ -1,22 +1,23 @@
/* 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/. */
-use api::{ClipId, DevicePixelScale, ExternalScrollId, LayerPixel, LayerPoint, LayerRect};
-use api::{LayerSize, LayerVector2D, LayoutTransform, LayoutVector2D, PipelineId, PropertyBinding};
+use api::{DevicePixelScale, ExternalScrollId, LayerPixel, LayerPoint, LayerRect, LayerSize};
+use api::{LayerVector2D, LayoutTransform, LayoutVector2D, PipelineId, PropertyBinding};
use api::{ScrollClamping, ScrollEventPhase, ScrollLocation, ScrollSensitivity, StickyOffsetBounds};
use api::WorldPoint;
-use clip::{ClipChain, ClipSourcesHandle, ClipStore, ClipWorkItem};
-use clip_scroll_tree::{ClipChainIndex, CoordinateSystemId, TransformUpdateState};
+use clip::{ClipChain, ClipChainNode, ClipSourcesHandle, ClipStore, ClipWorkItem};
+use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, CoordinateSystemId};
+use clip_scroll_tree::TransformUpdateState;
use euclid::SideOffsets2D;
use geometry::ray_intersects_rect;
use gpu_cache::GpuCache;
-use gpu_types::{ClipScrollNodeIndex, ClipScrollNodeData};
+use gpu_types::{ClipScrollNodeIndex as GPUClipScrollNodeIndex, ClipScrollNodeData};
use resource_cache::ResourceCache;
use scene::SceneProperties;
use spring::{DAMPING, STIFFNESS, Spring};
use util::{LayerToWorldFastTransform, LayerFastTransform, LayoutFastTransform};
use util::{TransformedRectKind};
#[cfg(target_os = "macos")]
const CAN_OVERSCROLL: bool = true;
@@ -53,28 +54,38 @@ impl StickyFrameInfo {
#[derive(Debug)]
pub enum NodeType {
/// A reference frame establishes a new coordinate space in the tree.
ReferenceFrame(ReferenceFrameInfo),
/// Other nodes just do clipping, but no transformation.
Clip {
handle: ClipSourcesHandle,
- clip_chain_index: ClipChainIndex
+ clip_chain_index: ClipChainIndex,
+
+ /// A copy of the ClipChainNode this node would produce. We need to keep a copy,
+ /// because the ClipChain may not contain our node if is optimized out, but API
+ /// defined ClipChains will still need to access it.
+ clip_chain_node: Option<ClipChainNode>,
},
/// Transforms it's content, but doesn't clip it. Can also be adjusted
/// by scroll events or setting scroll offsets.
ScrollFrame(ScrollFrameInfo),
/// A special kind of node that adjusts its position based on the position
/// of its parent node and a given set of sticky positioning offset bounds.
/// Sticky positioned is described in the CSS Positioned Layout Module Level 3 here:
/// https://www.w3.org/TR/css-position-3/#sticky-pos
StickyFrame(StickyFrameInfo),
+
+ /// An empty node, used to pad the ClipScrollTree's array of nodes so that
+ /// we can immediately use each assigned ClipScrollNodeIndex. After display
+ /// list flattening this node type should never be used.
+ Empty,
}
impl NodeType {
fn is_reference_frame(&self) -> bool {
match *self {
NodeType::ReferenceFrame(_) => true,
_ => false,
}
@@ -96,20 +107,20 @@ pub struct ClipScrollNode {
/// World transform for content transformed by this node.
pub world_content_transform: LayerToWorldFastTransform,
/// Pipeline that this layer belongs to
pub pipeline_id: PipelineId,
/// Parent layer. If this is None, we are the root node.
- pub parent: Option<ClipId>,
+ pub parent: Option<ClipScrollNodeIndex>,
/// Child layers
- pub children: Vec<ClipId>,
+ pub children: Vec<ClipScrollNodeIndex>,
/// The type of this node and any data associated with that node type.
pub node_type: NodeType,
/// True if this node is transformed by an invertible transform. If not, display items
/// transformed by this node will not be displayed and display items not transformed by this
/// node will not be clipped by clips that are transformed by this node.
pub invertible: bool,
@@ -119,94 +130,98 @@ pub struct ClipScrollNode {
/// The transformation from the coordinate system which established our compatible coordinate
/// system (same coordinate system id) and us. This can change via scroll offsets and via new
/// reference frame transforms.
pub coordinate_system_relative_transform: LayerFastTransform,
/// A linear ID / index of this clip-scroll node. Used as a reference to
/// pass to shaders, to allow them to fetch a given clip-scroll node.
- pub node_data_index: ClipScrollNodeIndex,
+ pub node_data_index: GPUClipScrollNodeIndex,
}
impl ClipScrollNode {
pub fn new(
pipeline_id: PipelineId,
- parent_id: Option<ClipId>,
+ parent_index: Option<ClipScrollNodeIndex>,
rect: &LayerRect,
node_type: NodeType
) -> Self {
ClipScrollNode {
local_viewport_rect: *rect,
world_viewport_transform: LayerToWorldFastTransform::identity(),
world_content_transform: LayerToWorldFastTransform::identity(),
- parent: parent_id,
+ parent: parent_index,
children: Vec::new(),
pipeline_id,
node_type: node_type,
invertible: true,
coordinate_system_id: CoordinateSystemId(0),
coordinate_system_relative_transform: LayerFastTransform::identity(),
- node_data_index: ClipScrollNodeIndex(0),
+ node_data_index: GPUClipScrollNodeIndex(0),
}
}
+ pub fn empty() -> ClipScrollNode {
+ ClipScrollNode::new(PipelineId::dummy(), None, &LayerRect::zero(), NodeType::Empty)
+ }
+
pub fn new_scroll_frame(
pipeline_id: PipelineId,
- parent_id: ClipId,
+ parent_index: ClipScrollNodeIndex,
external_id: Option<ExternalScrollId>,
frame_rect: &LayerRect,
content_size: &LayerSize,
scroll_sensitivity: ScrollSensitivity,
) -> Self {
let node_type = NodeType::ScrollFrame(ScrollFrameInfo::new(
scroll_sensitivity,
LayerSize::new(
(content_size.width - frame_rect.size.width).max(0.0),
(content_size.height - frame_rect.size.height).max(0.0)
),
external_id,
));
- Self::new(pipeline_id, Some(parent_id), frame_rect, node_type)
+ Self::new(pipeline_id, Some(parent_index), frame_rect, node_type)
}
pub fn new_reference_frame(
- parent_id: Option<ClipId>,
+ parent_index: Option<ClipScrollNodeIndex>,
frame_rect: &LayerRect,
source_transform: Option<PropertyBinding<LayoutTransform>>,
source_perspective: Option<LayoutTransform>,
origin_in_parent_reference_frame: LayerVector2D,
pipeline_id: PipelineId,
) -> Self {
let identity = LayoutTransform::identity();
let source_perspective = source_perspective.map_or_else(
LayoutFastTransform::identity, |perspective| perspective.into());
let info = ReferenceFrameInfo {
resolved_transform: LayerFastTransform::identity(),
source_transform: source_transform.unwrap_or(PropertyBinding::Value(identity)),
source_perspective: source_perspective,
origin_in_parent_reference_frame,
invertible: true,
};
- Self::new(pipeline_id, parent_id, frame_rect, NodeType::ReferenceFrame(info))
+ Self::new(pipeline_id, parent_index, frame_rect, NodeType::ReferenceFrame(info))
}
pub fn new_sticky_frame(
- parent_id: ClipId,
+ parent_index: ClipScrollNodeIndex,
frame_rect: LayerRect,
sticky_frame_info: StickyFrameInfo,
pipeline_id: PipelineId,
) -> Self {
let node_type = NodeType::StickyFrame(sticky_frame_info);
- Self::new(pipeline_id, Some(parent_id), &frame_rect, node_type)
+ Self::new(pipeline_id, Some(parent_index), &frame_rect, node_type)
}
- pub fn add_child(&mut self, child: ClipId) {
+ pub fn add_child(&mut self, child: ClipScrollNodeIndex) {
self.children.push(child);
}
pub fn apply_old_scrolling_state(&mut self, old_scrolling_state: &ScrollFrameInfo) {
match self.node_type {
NodeType::ScrollFrame(ref mut scrolling) => {
let scroll_sensitivity = scrolling.scroll_sensitivity;
let scrollable_size = scrolling.scrollable_size;
@@ -331,18 +346,19 @@ impl ClipScrollNode {
&mut self,
state: &mut TransformUpdateState,
device_pixel_scale: DevicePixelScale,
clip_store: &mut ClipStore,
resource_cache: &mut ResourceCache,
gpu_cache: &mut GpuCache,
clip_chains: &mut Vec<ClipChain>,
) {
- let (clip_sources_handle, clip_chain_index) = match self.node_type {
- NodeType::Clip { ref handle, clip_chain_index } => (handle, clip_chain_index),
+ let (clip_sources_handle, clip_chain_index, stored_clip_chain_node) = match self.node_type {
+ NodeType::Clip { ref handle, clip_chain_index, ref mut clip_chain_node } =>
+ (handle, clip_chain_index, clip_chain_node),
_ => {
self.invertible = true;
return;
}
};
let clip_sources = clip_store.get_mut(clip_sources_handle);
clip_sources.update(gpu_cache, resource_cache);
@@ -352,29 +368,33 @@ impl ClipScrollNode {
// All clipping ClipScrollNodes should have outer rectangles, because they never
// use the BorderCorner clip type and they always have at last one non-ClipOut
// Rectangle ClipSource.
let screen_outer_rect = screen_outer_rect.expect("Clipping node didn't have outer rect.");
let local_outer_rect = clip_sources.local_outer_rect.expect(
"Clipping node didn't have outer rect."
);
- let work_item = ClipWorkItem {
- scroll_node_data_index: self.node_data_index,
- clip_sources: clip_sources_handle.weak(),
- coordinate_system_id: state.current_coordinate_system_id,
+ let new_node = ClipChainNode {
+ work_item: ClipWorkItem {
+ scroll_node_data_index: self.node_data_index,
+ clip_sources: clip_sources_handle.weak(),
+ coordinate_system_id: state.current_coordinate_system_id,
+ },
+ local_clip_rect:
+ self.coordinate_system_relative_transform.transform_rect(&local_outer_rect),
+ screen_outer_rect,
+ screen_inner_rect,
+ prev: None,
};
- let mut clip_chain = clip_chains[state.parent_clip_chain_index.0].new_with_added_node(
- work_item,
- self.coordinate_system_relative_transform.transform_rect(&local_outer_rect),
- screen_outer_rect,
- screen_inner_rect,
- );
+ let mut clip_chain =
+ clip_chains[state.parent_clip_chain_index.0].new_with_added_node(&new_node);
+ *stored_clip_chain_node = Some(new_node);
clip_chain.parent_index = Some(state.parent_clip_chain_index);
clip_chains[clip_chain_index.0] = clip_chain;
state.parent_clip_chain_index = clip_chain_index;
}
pub fn update_transform(
&mut self,
state: &mut TransformUpdateState,
@@ -616,16 +636,17 @@ impl ClipScrollNode {
}
NodeType::StickyFrame(ref info) => {
// We don't translate the combined rect by the sticky offset, because sticky
// offsets actually adjust the node position itself, whereas scroll offsets
// only apply to contents inside the node.
state.parent_accumulated_scroll_offset =
info.current_offset + state.parent_accumulated_scroll_offset;
}
+ NodeType::Empty => unreachable!("Empty node remaining in ClipScrollTree."),
}
}
pub fn scrollable_size(&self) -> LayerSize {
match self.node_type {
NodeType:: ScrollFrame(state) => state.scrollable_size,
_ => LayerSize::zero(),
}
--- a/gfx/webrender/src/clip_scroll_tree.rs
+++ b/gfx/webrender/src/clip_scroll_tree.rs
@@ -1,19 +1,19 @@
/* 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/. */
-use api::{ClipId, DeviceIntRect, DevicePixelScale, ExternalScrollId, LayerPoint, LayerRect};
-use api::{LayerVector2D, PipelineId, ScrollClamping, ScrollEventPhase, ScrollLocation};
-use api::{ScrollNodeState, WorldPoint};
+use api::{DeviceIntRect, DevicePixelScale, ExternalScrollId, LayerPoint, LayerRect, LayerVector2D};
+use api::{PipelineId, ScrollClamping, ScrollEventPhase, ScrollLocation, ScrollNodeState};
+use api::WorldPoint;
use clip::{ClipChain, ClipSourcesHandle, ClipStore};
use clip_scroll_node::{ClipScrollNode, NodeType, ScrollFrameInfo, StickyFrameInfo};
use gpu_cache::GpuCache;
-use gpu_types::{ClipScrollNodeIndex, ClipScrollNodeData};
+use gpu_types::{ClipScrollNodeIndex as GPUClipScrollNodeIndex, ClipScrollNodeData};
use internal_types::{FastHashMap, FastHashSet};
use print_tree::{PrintTree, PrintTreePrinter};
use resource_cache::ResourceCache;
use scene::SceneProperties;
use util::{LayerFastTransform, LayerToWorldFastTransform};
pub type ScrollStates = FastHashMap<ExternalScrollId, ScrollFrameInfo>;
@@ -21,16 +21,22 @@ pub type ScrollStates = FastHashMap<Exte
/// coordinate system has an id and those ids will be shared when the coordinates
/// system are the same or are in the same axis-aligned space. This allows
/// for optimizing mask generation.
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct CoordinateSystemId(pub u32);
+#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
+pub struct ClipScrollNodeIndex(pub usize);
+
+const ROOT_REFERENCE_FRAME_INDEX: ClipScrollNodeIndex = ClipScrollNodeIndex(0);
+const TOPMOST_SCROLL_NODE_INDEX: ClipScrollNodeIndex = ClipScrollNodeIndex(1);
+
impl CoordinateSystemId {
pub fn root() -> Self {
CoordinateSystemId(0)
}
pub fn next(&self) -> Self {
let CoordinateSystemId(id) = *self;
CoordinateSystemId(id + 1)
@@ -39,53 +45,45 @@ impl CoordinateSystemId {
pub fn advance(&mut self) {
self.0 += 1;
}
}
pub struct ClipChainDescriptor {
pub index: ClipChainIndex,
pub parent: Option<ClipChainIndex>,
- pub clips: Vec<ClipId>,
+ pub clips: Vec<ClipScrollNodeIndex>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ClipChainIndex(pub usize);
pub struct ClipScrollTree {
- pub nodes: FastHashMap<ClipId, ClipScrollNode>,
+ pub nodes: Vec<ClipScrollNode>,
/// A Vec of all descriptors that describe ClipChains in the order in which they are
/// encountered during display list flattening. ClipChains are expected to never be
/// the children of ClipChains later in the list.
pub clip_chains_descriptors: Vec<ClipChainDescriptor>,
/// A vector of all ClipChains in this ClipScrollTree including those from
/// ClipChainDescriptors and also those defined by the clipping node hierarchy.
pub clip_chains: Vec<ClipChain>,
pub pending_scroll_offsets: FastHashMap<ExternalScrollId, (LayerPoint, ScrollClamping)>,
/// The ClipId of the currently scrolling node. Used to allow the same
/// node to scroll even if a touch operation leaves the boundaries of that node.
- pub currently_scrolling_node_id: Option<ClipId>,
+ pub currently_scrolling_node_index: Option<ClipScrollNodeIndex>,
/// The current frame id, used for giving a unique id to all new dynamically
/// added frames and clips. The ClipScrollTree increments this by one every
/// time a new dynamic frame is created.
current_new_node_item: u64,
- /// The root reference frame, which is the true root of the ClipScrollTree. Initially
- /// this ID is not valid, which is indicated by ```node``` being empty.
- pub root_reference_frame_id: ClipId,
-
- /// The root scroll node which is the first child of the root reference frame.
- /// Initially this ID is not valid, which is indicated by ```nodes``` being empty.
- pub topmost_scrolling_node_id: ClipId,
-
/// A set of pipelines which should be discarded the next time this
/// tree is drained.
pub pipelines_to_discard: FastHashSet<PipelineId>,
}
#[derive(Clone)]
pub struct TransformUpdateState {
pub parent_reference_frame_transform: LayerToWorldFastTransform,
@@ -108,106 +106,103 @@ pub struct TransformUpdateState {
/// True if this node is transformed by an invertible transform. If not, display items
/// transformed by this node will not be displayed and display items not transformed by this
/// node will not be clipped by clips that are transformed by this node.
pub invertible: bool,
}
impl ClipScrollTree {
pub fn new() -> Self {
- let dummy_pipeline = PipelineId::dummy();
ClipScrollTree {
- nodes: FastHashMap::default(),
+ nodes: Vec::new(),
clip_chains_descriptors: Vec::new(),
clip_chains: vec![ClipChain::empty(&DeviceIntRect::zero())],
pending_scroll_offsets: FastHashMap::default(),
- currently_scrolling_node_id: None,
- root_reference_frame_id: ClipId::root_reference_frame(dummy_pipeline),
- topmost_scrolling_node_id: ClipId::root_scroll_node(dummy_pipeline),
+ currently_scrolling_node_index: None,
current_new_node_item: 1,
pipelines_to_discard: FastHashSet::default(),
}
}
- pub fn root_reference_frame_id(&self) -> ClipId {
- // TODO(mrobinson): We should eventually make this impossible to misuse.
- debug_assert!(!self.nodes.is_empty());
- debug_assert!(self.nodes.contains_key(&self.root_reference_frame_id));
- self.root_reference_frame_id
- }
-
- pub fn topmost_scrolling_node_id(&self) -> ClipId {
+ /// The root reference frame, which is the true root of the ClipScrollTree. Initially
+ /// this ID is not valid, which is indicated by ```nodes``` being empty.
+ pub fn root_reference_frame_index(&self) -> ClipScrollNodeIndex {
// TODO(mrobinson): We should eventually make this impossible to misuse.
debug_assert!(!self.nodes.is_empty());
- debug_assert!(self.nodes.contains_key(&self.topmost_scrolling_node_id));
- self.topmost_scrolling_node_id
+ ROOT_REFERENCE_FRAME_INDEX
}
- pub fn collect_nodes_bouncing_back(&self) -> FastHashSet<ClipId> {
+ /// The root scroll node which is the first child of the root reference frame.
+ /// Initially this ID is not valid, which is indicated by ```nodes``` being empty.
+ pub fn topmost_scroll_node_index(&self) -> ClipScrollNodeIndex {
+ // TODO(mrobinson): We should eventually make this impossible to misuse.
+ debug_assert!(self.nodes.len() >= 1);
+ TOPMOST_SCROLL_NODE_INDEX
+ }
+
+ pub fn collect_nodes_bouncing_back(&self) -> FastHashSet<ClipScrollNodeIndex> {
let mut nodes_bouncing_back = FastHashSet::default();
- for (clip_id, node) in self.nodes.iter() {
+ for (index, node) in self.nodes.iter().enumerate() {
if let NodeType::ScrollFrame(ref scrolling) = node.node_type {
if scrolling.bouncing_back {
- nodes_bouncing_back.insert(*clip_id);
+ nodes_bouncing_back.insert(ClipScrollNodeIndex(index));
}
}
}
nodes_bouncing_back
}
fn find_scrolling_node_at_point_in_node(
&self,
cursor: &WorldPoint,
- clip_id: ClipId,
- ) -> Option<ClipId> {
- self.nodes.get(&clip_id).and_then(|node| {
- for child_layer_id in node.children.iter().rev() {
- if let Some(layer_id) =
- self.find_scrolling_node_at_point_in_node(cursor, *child_layer_id)
- {
- return Some(layer_id);
- }
+ index: ClipScrollNodeIndex,
+ ) -> Option<ClipScrollNodeIndex> {
+ let node = &self.nodes[index.0];
+ for child_index in node.children.iter().rev() {
+ let found_index = self.find_scrolling_node_at_point_in_node(cursor, *child_index);
+ if found_index.is_some() {
+ return found_index;
}
+ }
- match node.node_type {
- NodeType::ScrollFrame(state) if state.sensitive_to_input_events() => {}
- _ => return None,
- }
+ match node.node_type {
+ NodeType::ScrollFrame(state) if state.sensitive_to_input_events() => {}
+ _ => return None,
+ }
- if node.ray_intersects_node(cursor) {
- Some(clip_id)
- } else {
- None
- }
- })
+ if node.ray_intersects_node(cursor) {
+ Some(index)
+ } else {
+ None
+ }
}
- pub fn find_scrolling_node_at_point(&self, cursor: &WorldPoint) -> ClipId {
- self.find_scrolling_node_at_point_in_node(cursor, self.root_reference_frame_id())
- .unwrap_or(self.topmost_scrolling_node_id())
+ pub fn find_scrolling_node_at_point(&self, cursor: &WorldPoint) -> ClipScrollNodeIndex {
+ self.find_scrolling_node_at_point_in_node(cursor, self.root_reference_frame_index())
+ .unwrap_or(self.topmost_scroll_node_index())
}
pub fn get_scroll_node_state(&self) -> Vec<ScrollNodeState> {
let mut result = vec![];
- for node in self.nodes.values() {
+ for node in &self.nodes {
if let NodeType::ScrollFrame(info) = node.node_type {
if let Some(id) = info.external_id {
result.push(ScrollNodeState { id, scroll_offset: info.offset })
}
}
}
result
}
pub fn drain(&mut self) -> ScrollStates {
self.current_new_node_item = 1;
let mut scroll_states = FastHashMap::default();
- for (node_id, old_node) in &mut self.nodes.drain() {
- if self.pipelines_to_discard.contains(&node_id.pipeline_id()) {
+ for old_node in &mut self.nodes.drain(..) {
+ if self.pipelines_to_discard.contains(&old_node.pipeline_id) {
continue;
}
match old_node.node_type {
NodeType::ScrollFrame(info) if info.external_id.is_some() => {
scroll_states.insert(info.external_id.unwrap(), info);
}
_ => {}
@@ -221,17 +216,17 @@ impl ClipScrollTree {
}
pub fn scroll_node(
&mut self,
origin: LayerPoint,
id: ExternalScrollId,
clamp: ScrollClamping
) -> bool {
- for node in &mut self.nodes.values_mut() {
+ for node in &mut self.nodes {
if node.matches_external_id(id) {
return node.set_scroll_origin(&origin, clamp);
}
}
self.pending_scroll_offsets.insert(id, (origin, clamp));
false
}
@@ -241,47 +236,48 @@ impl ClipScrollTree {
scroll_location: ScrollLocation,
cursor: WorldPoint,
phase: ScrollEventPhase,
) -> bool {
if self.nodes.is_empty() {
return false;
}
- let clip_id = match (
+ let node_index = match (
phase,
self.find_scrolling_node_at_point(&cursor),
- self.currently_scrolling_node_id,
+ self.currently_scrolling_node_index,
) {
- (ScrollEventPhase::Start, scroll_node_at_point_id, _) => {
- self.currently_scrolling_node_id = Some(scroll_node_at_point_id);
- scroll_node_at_point_id
+ (ScrollEventPhase::Start, scroll_node_at_point_index, _) => {
+ self.currently_scrolling_node_index = Some(scroll_node_at_point_index);
+ scroll_node_at_point_index
}
- (_, scroll_node_at_point_id, Some(cached_clip_id)) => {
- let clip_id = match self.nodes.get(&cached_clip_id) {
- Some(_) => cached_clip_id,
+ (_, scroll_node_at_point_index, Some(cached_node_index)) => {
+ let node_index = match self.nodes.get(cached_node_index.0) {
+ Some(_) => cached_node_index,
None => {
- self.currently_scrolling_node_id = Some(scroll_node_at_point_id);
- scroll_node_at_point_id
+ self.currently_scrolling_node_index = Some(scroll_node_at_point_index);
+ scroll_node_at_point_index
}
};
- clip_id
+ node_index
}
(_, _, None) => return false,
};
- let topmost_scrolling_node_id = self.topmost_scrolling_node_id();
- let non_root_overscroll = if clip_id != topmost_scrolling_node_id {
- self.nodes.get(&clip_id).unwrap().is_overscrolling()
+ let topmost_scroll_node_index = self.topmost_scroll_node_index();
+ let non_root_overscroll = if node_index != topmost_scroll_node_index {
+ self.nodes[node_index.0].is_overscrolling()
} else {
false
};
let mut switch_node = false;
- if let Some(node) = self.nodes.get_mut(&clip_id) {
+ {
+ let node = &mut self.nodes[node_index.0];
if let NodeType::ScrollFrame(ref mut scrolling) = node.node_type {
match phase {
ScrollEventPhase::Start => {
// if this is a new gesture, we do not switch node,
// however we do save the state of non_root_overscroll,
// for use in the subsequent Move phase.
scrolling.should_handoff_scroll = non_root_overscroll;
}
@@ -293,26 +289,23 @@ impl ClipScrollTree {
ScrollEventPhase::End => {
// clean-up when gesture ends.
scrolling.should_handoff_scroll = false;
}
}
}
}
- let clip_id = if switch_node {
- topmost_scrolling_node_id
+ let node_index = if switch_node {
+ topmost_scroll_node_index
} else {
- clip_id
+ node_index
};
- self.nodes
- .get_mut(&clip_id)
- .unwrap()
- .scroll(scroll_location, phase)
+ self.nodes[node_index.0].scroll(scroll_location, phase)
}
pub fn update_tree(
&mut self,
screen_rect: &DeviceIntRect,
device_pixel_scale: DevicePixelScale,
clip_store: &mut ClipStore,
resource_cache: &mut ResourceCache,
@@ -322,66 +315,66 @@ impl ClipScrollTree {
scene_properties: &SceneProperties,
) {
if self.nodes.is_empty() {
return;
}
self.clip_chains[0] = ClipChain::empty(screen_rect);
- let root_reference_frame_id = self.root_reference_frame_id();
+ let root_reference_frame_index = self.root_reference_frame_index();
let mut state = TransformUpdateState {
parent_reference_frame_transform: LayerVector2D::new(pan.x, pan.y).into(),
parent_accumulated_scroll_offset: LayerVector2D::zero(),
nearest_scrolling_ancestor_offset: LayerVector2D::zero(),
nearest_scrolling_ancestor_viewport: LayerRect::zero(),
parent_clip_chain_index: ClipChainIndex(0),
current_coordinate_system_id: CoordinateSystemId::root(),
coordinate_system_relative_transform: LayerFastTransform::identity(),
invertible: true,
};
let mut next_coordinate_system_id = state.current_coordinate_system_id.next();
self.update_node(
- root_reference_frame_id,
+ root_reference_frame_index,
&mut state,
&mut next_coordinate_system_id,
device_pixel_scale,
clip_store,
resource_cache,
gpu_cache,
node_data,
scene_properties,
);
self.build_clip_chains(screen_rect);
}
fn update_node(
&mut self,
- layer_id: ClipId,
+ node_index: ClipScrollNodeIndex,
state: &mut TransformUpdateState,
next_coordinate_system_id: &mut CoordinateSystemId,
device_pixel_scale: DevicePixelScale,
clip_store: &mut ClipStore,
resource_cache: &mut ResourceCache,
gpu_cache: &mut GpuCache,
gpu_node_data: &mut Vec<ClipScrollNodeData>,
scene_properties: &SceneProperties,
) {
// TODO(gw): This is an ugly borrow check workaround to clone these.
// Restructure this to avoid the clones!
let mut state = state.clone();
let node_children = {
- let node = match self.nodes.get_mut(&layer_id) {
+ let node = match self.nodes.get_mut(node_index.0) {
Some(node) => node,
None => return,
};
// We set this early so that we can use it to populate the ClipChain.
- node.node_data_index = ClipScrollNodeIndex(gpu_node_data.len() as u32);
+ node.node_data_index = GPUClipScrollNodeIndex(gpu_node_data.len() as u32);
node.update(
&mut state,
next_coordinate_system_id,
device_pixel_scale,
clip_store,
resource_cache,
gpu_cache,
@@ -394,19 +387,19 @@ impl ClipScrollTree {
if node.children.is_empty() {
return;
}
node.prepare_state_for_children(&mut state);
node.children.clone()
};
- for child_node_id in node_children {
+ for child_node_index in node_children {
self.update_node(
- child_node_id,
+ child_node_index,
&mut state,
next_coordinate_system_id,
device_pixel_scale,
clip_store,
resource_cache,
gpu_cache,
gpu_node_data,
scene_properties,
@@ -421,193 +414,201 @@ impl ClipScrollTree {
// parent's node, if necessary.
let mut chain = match descriptor.parent {
Some(index) => self.clip_chains[index.0].clone(),
None => ClipChain::empty(screen_rect),
};
// Now we walk through each ClipScrollNode in the vector of clip nodes and
// extract their ClipChain nodes to construct the final list.
- for clip_id in &descriptor.clips {
- let node_clip_chain_index = match self.nodes[&clip_id].node_type {
- NodeType::Clip { clip_chain_index, .. } => clip_chain_index,
- _ => {
- warn!("Tried to create a clip chain with non-clipping node.");
- continue;
+ for clip_index in &descriptor.clips {
+ match self.nodes[clip_index.0].node_type {
+ NodeType::Clip { clip_chain_node: Some(ref node), .. } => {
+ chain.add_node(node.clone());
}
+ NodeType::Clip { .. } => warn!("Found uninitialized clipping ClipScrollNode."),
+ _ => warn!("Tried to create a clip chain with non-clipping node."),
};
-
- if let Some(ref nodes) = self.clip_chains[node_clip_chain_index.0].nodes {
- chain.add_node((**nodes).clone());
- }
}
chain.parent_index = descriptor.parent;
self.clip_chains[descriptor.index.0] = chain;
}
}
pub fn tick_scrolling_bounce_animations(&mut self) {
- for (_, node) in &mut self.nodes {
+ for node in &mut self.nodes {
node.tick_scrolling_bounce_animation()
}
}
pub fn finalize_and_apply_pending_scroll_offsets(&mut self, old_states: ScrollStates) {
- for node in self.nodes.values_mut() {
+ for node in &mut self.nodes {
let external_id = match node.node_type {
NodeType::ScrollFrame(ScrollFrameInfo { external_id: Some(id), ..} ) => id,
_ => continue,
};
if let Some(scrolling_state) = old_states.get(&external_id) {
node.apply_old_scrolling_state(scrolling_state);
}
-
if let Some((offset, clamping)) = self.pending_scroll_offsets.remove(&external_id) {
node.set_scroll_origin(&offset, clamping);
}
}
}
pub fn add_clip_node(
&mut self,
- id: ClipId,
- parent_id: ClipId,
+ index: ClipScrollNodeIndex,
+ parent_index: ClipScrollNodeIndex,
handle: ClipSourcesHandle,
clip_rect: LayerRect,
+ pipeline_id: PipelineId,
) -> ClipChainIndex {
let clip_chain_index = self.allocate_clip_chain();
- let node_type = NodeType::Clip { handle, clip_chain_index };
- let node = ClipScrollNode::new(id.pipeline_id(), Some(parent_id), &clip_rect, node_type);
- self.add_node(node, id);
+ let node_type = NodeType::Clip { handle, clip_chain_index, clip_chain_node: None };
+ let node = ClipScrollNode::new(pipeline_id, Some(parent_index), &clip_rect, node_type);
+ self.add_node(node, index);
clip_chain_index
}
pub fn add_sticky_frame(
&mut self,
- id: ClipId,
- parent_id: ClipId,
+ index: ClipScrollNodeIndex,
+ parent_index: ClipScrollNodeIndex,
frame_rect: LayerRect,
sticky_frame_info: StickyFrameInfo,
+ pipeline_id: PipelineId,
) {
let node = ClipScrollNode::new_sticky_frame(
- parent_id,
+ parent_index,
frame_rect,
sticky_frame_info,
- id.pipeline_id(),
+ pipeline_id,
);
- self.add_node(node, id);
+ self.add_node(node, index);
}
pub fn add_clip_chain_descriptor(
&mut self,
parent: Option<ClipChainIndex>,
- clips: Vec<ClipId>
+ clips: Vec<ClipScrollNodeIndex>
) -> ClipChainIndex {
let index = self.allocate_clip_chain();
self.clip_chains_descriptors.push(ClipChainDescriptor { index, parent, clips });
index
}
- pub fn add_node(&mut self, node: ClipScrollNode, id: ClipId) {
+ pub fn add_node(&mut self, node: ClipScrollNode, index: ClipScrollNodeIndex) {
// When the parent node is None this means we are adding the root.
- match node.parent {
- Some(parent_id) => self.nodes.get_mut(&parent_id).unwrap().add_child(id),
- None => self.root_reference_frame_id = id,
+ if let Some(parent_index) = node.parent {
+ self.nodes[parent_index.0].add_child(index);
+ }
+
+ if index.0 == self.nodes.len() {
+ self.nodes.push(node);
+ return;
}
- debug_assert!(!self.nodes.contains_key(&id));
- self.nodes.insert(id, node);
+
+ if let Some(empty_node) = self.nodes.get_mut(index.0) {
+ *empty_node = node;
+ return
+ }
+
+ let length_to_reserve = index.0 + 1 - self.nodes.len();
+ self.nodes.reserve_exact(length_to_reserve);
+
+ // We would like to use `Vec::resize` here, but the Clone trait is not supported
+ // for ClipScrollNodes. We can fix this either by splitting the clip nodes out into
+ // their own tree or when support is added for something like `Vec::resize_default`.
+ let length_to_extend = self.nodes.len() .. index.0;
+ self.nodes.extend(length_to_extend.map(|_| ClipScrollNode::empty()));
+
+ self.nodes.push(node);
}
pub fn discard_frame_state_for_pipeline(&mut self, pipeline_id: PipelineId) {
self.pipelines_to_discard.insert(pipeline_id);
- match self.currently_scrolling_node_id {
- Some(id) if id.pipeline_id() == pipeline_id => self.currently_scrolling_node_id = None,
- _ => {}
+ if let Some(index) = self.currently_scrolling_node_index {
+ if self.nodes[index.0].pipeline_id == pipeline_id {
+ self.currently_scrolling_node_index = None;
+ }
}
}
- fn print_node<T: PrintTreePrinter>(&self, id: &ClipId, pt: &mut T, clip_store: &ClipStore) {
- let node = self.nodes.get(id).unwrap();
-
+ fn print_node<T: PrintTreePrinter>(
+ &self,
+ index: ClipScrollNodeIndex,
+ pt: &mut T,
+ clip_store: &ClipStore
+ ) {
+ let node = &self.nodes[index.0];
match node.node_type {
NodeType::Clip { ref handle, .. } => {
pt.new_level("Clip".to_owned());
- pt.add_item(format!("id: {:?}", id));
+ pt.add_item(format!("index: {:?}", index));
let clips = clip_store.get(&handle).clips();
pt.new_level(format!("Clip Sources [{}]", clips.len()));
for source in clips {
pt.add_item(format!("{:?}", source));
}
pt.end_level();
}
NodeType::ReferenceFrame(ref info) => {
pt.new_level(format!("ReferenceFrame {:?}", info.resolved_transform));
- pt.add_item(format!("id: {:?}", id));
+ pt.add_item(format!("index: {:?}", index));
}
NodeType::ScrollFrame(scrolling_info) => {
pt.new_level(format!("ScrollFrame"));
- pt.add_item(format!("id: {:?}", id));
+ pt.add_item(format!("index: {:?}", index));
pt.add_item(format!("scrollable_size: {:?}", scrolling_info.scrollable_size));
pt.add_item(format!("scroll.offset: {:?}", scrolling_info.offset));
}
NodeType::StickyFrame(ref sticky_frame_info) => {
pt.new_level(format!("StickyFrame"));
- pt.add_item(format!("id: {:?}", id));
+ pt.add_item(format!("index: {:?}", index));
pt.add_item(format!("sticky info: {:?}", sticky_frame_info));
}
+ NodeType::Empty => unreachable!("Empty node remaining in ClipScrollTree."),
}
- pt.add_item(format!(
- "local_viewport_rect: {:?}",
- node.local_viewport_rect
- ));
- pt.add_item(format!(
- "world_viewport_transform: {:?}",
- node.world_viewport_transform
- ));
- pt.add_item(format!(
- "world_content_transform: {:?}",
- node.world_content_transform
- ));
- pt.add_item(format!(
- "coordinate_system_id: {:?}",
- node.coordinate_system_id
- ));
+ pt.add_item(format!("local_viewport_rect: {:?}", node.local_viewport_rect));
+ pt.add_item(format!("world_viewport_transform: {:?}", node.world_viewport_transform));
+ pt.add_item(format!("world_content_transform: {:?}", node.world_content_transform));
+ pt.add_item(format!("coordinate_system_id: {:?}", node.coordinate_system_id));
- for child_id in &node.children {
- self.print_node(child_id, pt, clip_store);
+ for child_index in &node.children {
+ self.print_node(*child_index, pt, clip_store);
}
pt.end_level();
}
#[allow(dead_code)]
pub fn print(&self, clip_store: &ClipStore) {
if !self.nodes.is_empty() {
let mut pt = PrintTree::new("clip_scroll tree");
self.print_with(clip_store, &mut pt);
}
}
pub fn print_with<T: PrintTreePrinter>(&self, clip_store: &ClipStore, pt: &mut T) {
if !self.nodes.is_empty() {
- self.print_node(&self.root_reference_frame_id, pt, clip_store);
+ self.print_node(self.root_reference_frame_index(), pt, clip_store);
}
}
pub fn allocate_clip_chain(&mut self) -> ClipChainIndex {
debug_assert!(!self.clip_chains.is_empty());
let new_clip_chain =self.clip_chains[0].clone();
self.clip_chains.push(new_clip_chain);
ClipChainIndex(self.clip_chains.len() - 1)
}
pub fn get_clip_chain(&self, index: ClipChainIndex) -> &ClipChain {
&self.clip_chains[index.0]
}
-
}
--- a/gfx/webrender/src/device.rs
+++ b/gfx/webrender/src/device.rs
@@ -1004,25 +1004,31 @@ impl Device {
}
self.gl.delete_textures(&[old_texture_id]);
self.bind_read_target(None);
}
pub fn init_texture(
&mut self,
texture: &mut Texture,
- width: u32,
- height: u32,
+ mut width: u32,
+ mut height: u32,
filter: TextureFilter,
render_target: Option<RenderTargetInfo>,
layer_count: i32,
pixels: Option<&[u8]>,
) {
debug_assert!(self.inside_frame);
+ if width > self.max_texture_size || height > self.max_texture_size {
+ error!("Attempting to allocate a texture of size {}x{} above the limit, trimming", width, height);
+ width = width.min(self.max_texture_size);
+ height = height.min(self.max_texture_size);
+ }
+
let is_resized = texture.width != width || texture.height != height;
texture.width = width;
texture.height = height;
texture.filter = filter;
texture.layer_count = layer_count;
texture.render_target = render_target;
texture.last_frame_used = self.frame_id;
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/src/display_list_flattener.rs
@@ -0,0 +1,2744 @@
+
+/* 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/. */
+
+use api::{AlphaType, BorderDetails, BorderDisplayItem, BuiltDisplayListIter};
+use api::{ClipAndScrollInfo, ClipId, ColorF, ComplexClipRegion, DeviceIntPoint, DeviceIntRect};
+use api::{DeviceIntSize, DevicePixelScale, DeviceUintRect, DeviceUintSize};
+use api::{DisplayItemRef, Epoch, ExtendMode, ExternalScrollId, FilterOp};
+use api::{FontInstanceKey, FontRenderMode, GlyphInstance, GlyphOptions, GradientStop};
+use api::{IframeDisplayItem, ImageDisplayItem, ImageKey, ImageRendering, ItemRange, LayerPoint};
+use api::{LayerPrimitiveInfo, LayerRect, LayerSize, LayerVector2D, LayoutSize, LayoutTransform};
+use api::{LayoutVector2D, LineOrientation, LineStyle, LocalClip, PipelineId};
+use api::{PropertyBinding, RepeatMode, ScrollFrameDisplayItem, ScrollPolicy, ScrollSensitivity};
+use api::{Shadow, SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, TexelRect};
+use api::{TileOffset, TransformStyle, YuvColorSpace, YuvData};
+use app_units::Au;
+use border::ImageBorderSegment;
+use clip::{ClipRegion, ClipSource, ClipSources, ClipStore};
+use clip_scroll_node::{ClipScrollNode, NodeType, StickyFrameInfo};
+use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, ClipScrollTree};
+use euclid::{SideOffsets2D, rect, vec2};
+use frame_builder::{FrameBuilder, FrameBuilderConfig};
+use glyph_rasterizer::FontInstance;
+use hit_test::{HitTestingItem, HitTestingRun};
+use internal_types::{FastHashMap, FastHashSet};
+use picture::{PictureCompositeMode, PictureKind, PicturePrimitive};
+use prim_store::{BrushKind, BrushPrimitive, BrushSegmentDescriptor, CachedGradient};
+use prim_store::{CachedGradientIndex, ImageCacheKey, ImagePrimitiveCpu, ImageSource};
+use prim_store::{PrimitiveContainer, PrimitiveIndex, PrimitiveKind, PrimitiveStore};
+use prim_store::{ScrollNodeAndClipChain, TextRunPrimitiveCpu};
+use render_backend::{DocumentView};
+use resource_cache::{FontInstanceMap, ImageRequest, TiledImageMap};
+use scene::{Scene, ScenePipeline, StackingContextHelpers};
+use scene_builder::{BuiltScene, SceneRequest};
+use std::{f32, mem, usize};
+use tiling::{CompositeOps, ScrollbarPrimitive};
+use util::{MaxRect, RectHelpers, recycle_vec};
+
+static DEFAULT_SCROLLBAR_COLOR: ColorF = ColorF {
+ r: 0.3,
+ g: 0.3,
+ b: 0.3,
+ a: 0.6,
+};
+
+/// A data structure that keeps track of mapping between API ClipIds and the indices used
+/// internally in the ClipScrollTree to avoid having to do HashMap lookups. ClipIdToIndexMapper is
+/// responsible for mapping both ClipId to ClipChainIndex and ClipId to ClipScrollNodeIndex. We
+/// also include two small LRU caches. Currently the caches are small (1 entry), but in the future
+/// we could use uluru here to do something more involved.
+#[derive(Default)]
+pub struct ClipIdToIndexMapper {
+ /// A map which converts a ClipId for a clipping node or an API-defined ClipChain into
+ /// a ClipChainIndex, which is the index used internally in the ClipScrollTree to
+ /// identify ClipChains.
+ clip_chain_map: FastHashMap<ClipId, ClipChainIndex>,
+
+ /// The last mapped ClipChainIndex, used to avoid having to do lots of consecutive
+ /// HashMap lookups.
+ cached_clip_chain_index: Option<(ClipId, ClipChainIndex)>,
+
+ /// The offset in the ClipScrollTree's array of ClipScrollNodes for a particular pipeline.
+ /// This is used to convert a ClipId into a ClipScrollNodeIndex.
+ pipeline_offsets: FastHashMap<PipelineId, usize>,
+
+ /// The last mapped pipeline offset for this mapper. This is used to avoid having to
+ /// consult `pipeline_offsets` repeatedly when flattening the display list.
+ cached_pipeline_offset: Option<(PipelineId, usize)>,
+
+ /// The next available pipeline offset for ClipScrollNodeIndex. When we encounter a pipeline
+ /// we will use this value and increment it by the total number of ClipScrollNodes in the
+ /// pipeline's display list.
+ next_available_offset: usize,
+}
+
+impl ClipIdToIndexMapper {
+ pub fn add_clip_chain(&mut self, id: ClipId, index: ClipChainIndex) {
+ debug_assert!(!self.clip_chain_map.contains_key(&id));
+ self.clip_chain_map.insert(id, index);
+ }
+
+ pub fn map_to_parent_clip_chain(&mut self, id: ClipId, parent_id: &ClipId) {
+ let parent_chain_index = self.get_clip_chain_index(parent_id);
+ self.add_clip_chain(id, parent_chain_index);
+ }
+
+ pub fn get_clip_chain_index(&mut self, id: &ClipId) -> ClipChainIndex {
+ match self.cached_clip_chain_index {
+ Some((cached_id, cached_clip_chain_index)) if cached_id == *id =>
+ return cached_clip_chain_index,
+ _ => {}
+ }
+
+ self.clip_chain_map[id]
+ }
+
+ pub fn get_clip_chain_index_and_cache_result(&mut self, id: &ClipId) -> ClipChainIndex {
+ let index = self.get_clip_chain_index(id);
+ self.cached_clip_chain_index = Some((*id, index));
+ index
+ }
+
+ pub fn map_clip_and_scroll(&mut self, info: &ClipAndScrollInfo) -> ScrollNodeAndClipChain {
+ ScrollNodeAndClipChain::new(
+ self.get_node_index(info.scroll_node_id),
+ self.get_clip_chain_index_and_cache_result(&info.clip_node_id())
+ )
+ }
+
+ pub fn simple_scroll_and_clip_chain(&mut self, id: &ClipId) -> ScrollNodeAndClipChain {
+ self.map_clip_and_scroll(&ClipAndScrollInfo::simple(*id))
+ }
+
+ pub fn initialize_for_pipeline(&mut self, pipeline: &ScenePipeline) {
+ debug_assert!(!self.pipeline_offsets.contains_key(&pipeline.pipeline_id));
+ self.pipeline_offsets.insert(pipeline.pipeline_id, self.next_available_offset);
+ self.next_available_offset += pipeline.display_list.total_clip_ids();
+ }
+
+ pub fn get_node_index(&mut self, id: ClipId) -> ClipScrollNodeIndex {
+ let (index, pipeline_id) = match id {
+ ClipId::Clip(index, pipeline_id) => (index, pipeline_id),
+ ClipId::ClipChain(_) => panic!("Tried to use ClipChain as scroll node."),
+ };
+
+ let pipeline_offset = match self.cached_pipeline_offset {
+ Some((last_used_id, offset)) if last_used_id == pipeline_id => offset,
+ _ => {
+ let offset = self.pipeline_offsets[&pipeline_id];
+ self.cached_pipeline_offset = Some((pipeline_id, offset));
+ offset
+ }
+ };
+
+ ClipScrollNodeIndex(pipeline_offset + index)
+ }
+}
+
+/// A structure that converts a serialized display list into a form that WebRender
+/// can use to later build a frame. This structure produces a FrameBuilder. Public
+/// members are typically those that are destructured into the FrameBuilder.
+pub struct DisplayListFlattener<'a> {
+ /// The scene that we are currently flattening.
+ scene: &'a Scene,
+
+ /// The ClipScrollTree that we are currently building during flattening.
+ clip_scroll_tree: &'a mut ClipScrollTree,
+
+ /// 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)>,
+
+ /// A set of pipelines that the caller has requested be made available as
+ /// output textures.
+ output_pipelines: &'a FastHashSet<PipelineId>,
+
+ /// A list of replacements to make in order to properly handle fixed position
+ /// content as well as stacking contexts that create reference frames.
+ replacements: Vec<(ClipId, ClipId)>,
+
+ /// The data structure that converting between ClipId and the various index
+ /// types that the ClipScrollTree uses.
+ id_to_index_mapper: ClipIdToIndexMapper,
+
+ /// A stack of the current shadow primitives. The sub-Vec stores
+ /// a buffer of fast-path primitives to be appended on pop.
+ shadow_prim_stack: Vec<(PrimitiveIndex, Vec<(PrimitiveIndex, ScrollNodeAndClipChain)>)>,
+
+ /// A buffer of "real" content when doing fast-path shadows. This is appended
+ /// when the shadow stack is empty.
+ pending_shadow_contents: Vec<(PrimitiveIndex, ScrollNodeAndClipChain, LayerPrimitiveInfo)>,
+
+ /// A stack of scroll nodes used during display list processing to properly
+ /// parent new scroll nodes.
+ reference_frame_stack: Vec<(ClipId, ClipScrollNodeIndex)>,
+
+ /// A stack of stacking context properties.
+ sc_stack: Vec<FlattenedStackingContext>,
+
+ /// A stack of the current pictures.
+ picture_stack: Vec<PrimitiveIndex>,
+
+ /// A list of scrollbar primitives.
+ pub scrollbar_prims: Vec<ScrollbarPrimitive>,
+
+ /// The store of primitives.
+ pub prim_store: PrimitiveStore,
+
+ /// Information about all primitives involved in hit testing.
+ pub hit_testing_runs: Vec<HitTestingRun>,
+
+ /// The store which holds all complex clipping information.
+ pub clip_store: ClipStore,
+
+ /// The configuration to use for the FrameBuilder. We consult this in
+ /// order to determine the default font.
+ pub config: FrameBuilderConfig,
+
+ /// The gradients collecting during display list flattening.
+ pub cached_gradients: Vec<CachedGradient>,
+}
+
+impl<'a> DisplayListFlattener<'a> {
+ pub fn create_frame_builder(
+ old_builder: FrameBuilder,
+ scene: &Scene,
+ clip_scroll_tree: &mut ClipScrollTree,
+ font_instances: FontInstanceMap,
+ tiled_image_map: TiledImageMap,
+ view: &DocumentView,
+ output_pipelines: &FastHashSet<PipelineId>,
+ frame_builder_config: &FrameBuilderConfig,
+ pipeline_epochs: &mut FastHashMap<PipelineId, Epoch>,
+ ) -> FrameBuilder {
+ // We checked that the root pipeline is available on the render backend.
+ let root_pipeline_id = scene.root_pipeline_id.unwrap();
+ let root_pipeline = scene.pipelines.get(&root_pipeline_id).unwrap();
+
+ let root_epoch = scene.pipeline_epochs[&root_pipeline_id];
+ pipeline_epochs.insert(root_pipeline_id, root_epoch);
+
+ let background_color = root_pipeline
+ .background_color
+ .and_then(|color| if color.a > 0.0 { Some(color) } else { None });
+
+ let mut flattener = DisplayListFlattener {
+ scene,
+ clip_scroll_tree,
+ font_instances,
+ tiled_image_map,
+ config: *frame_builder_config,
+ pipeline_epochs: Vec::new(),
+ replacements: Vec::new(),
+ output_pipelines,
+ id_to_index_mapper: ClipIdToIndexMapper::default(),
+ hit_testing_runs: recycle_vec(old_builder.hit_testing_runs),
+ shadow_prim_stack: Vec::new(),
+ cached_gradients: recycle_vec(old_builder.cached_gradients),
+ pending_shadow_contents: Vec::new(),
+ scrollbar_prims: recycle_vec(old_builder.scrollbar_prims),
+ reference_frame_stack: Vec::new(),
+ picture_stack: Vec::new(),
+ sc_stack: Vec::new(),
+ prim_store: old_builder.prim_store.recycle(),
+ clip_store: old_builder.clip_store.recycle(),
+ };
+
+ flattener.id_to_index_mapper.initialize_for_pipeline(root_pipeline);
+ flattener.push_root(
+ root_pipeline_id,
+ &root_pipeline.viewport_size,
+ &root_pipeline.content_size,
+ );
+ flattener.setup_viewport_offset(view.inner_rect, view.accumulated_scale_factor());
+ flattener.flatten_root(root_pipeline, &root_pipeline.viewport_size);
+
+ debug_assert!(flattener.picture_stack.is_empty());
+ pipeline_epochs.extend(flattener.pipeline_epochs.drain(..));
+
+ FrameBuilder::with_display_list_flattener(
+ view.inner_rect,
+ background_color,
+ view.window_size,
+ flattener
+ )
+ }
+
+ /// Since WebRender still handles fixed position and reference frame content internally
+ /// we need to apply this table of id replacements only to the id that affects the
+ /// position of a node. We can eventually remove this when clients start handling
+ /// reference frames themselves. This method applies these replacements.
+ fn apply_scroll_frame_id_replacement(&self, index: ClipId) -> ClipId {
+ match self.replacements.last() {
+ Some(&(to_replace, replacement)) if to_replace == index => replacement,
+ _ => index,
+ }
+ }
+
+ fn get_complex_clips(
+ &self,
+ pipeline_id: PipelineId,
+ complex_clips: ItemRange<ComplexClipRegion>,
+ ) -> Vec<ComplexClipRegion> {
+ if complex_clips.is_empty() {
+ return vec![];
+ }
+
+ self.scene
+ .pipelines
+ .get(&pipeline_id)
+ .expect("No display list?")
+ .display_list
+ .get(complex_clips)
+ .collect()
+ }
+
+ fn get_clip_chain_items(
+ &self,
+ pipeline_id: PipelineId,
+ items: ItemRange<ClipId>,
+ ) -> Vec<ClipId> {
+ if items.is_empty() {
+ return vec![];
+ }
+
+ self.scene
+ .pipelines
+ .get(&pipeline_id)
+ .expect("No display list?")
+ .display_list
+ .get(items)
+ .collect()
+ }
+
+ fn flatten_root(&mut self, pipeline: &'a ScenePipeline, frame_size: &LayoutSize) {
+ let pipeline_id = pipeline.pipeline_id;
+ let reference_frame_info = self.id_to_index_mapper.simple_scroll_and_clip_chain(
+ &ClipId::root_reference_frame(pipeline_id)
+ );
+ let scroll_frame_info = self.id_to_index_mapper.simple_scroll_and_clip_chain(
+ &ClipId::root_scroll_node(pipeline_id)
+ );
+
+ self.push_stacking_context(
+ pipeline_id,
+ CompositeOps::default(),
+ TransformStyle::Flat,
+ true,
+ true,
+ scroll_frame_info,
+ );
+
+ // For the root pipeline, there's no need to add a full screen rectangle
+ // here, as it's handled by the framebuffer clear.
+ if self.scene.root_pipeline_id != Some(pipeline_id) {
+ if let Some(pipeline) = self.scene.pipelines.get(&pipeline_id) {
+ if let Some(bg_color) = pipeline.background_color {
+ let root_bounds = LayerRect::new(LayerPoint::zero(), *frame_size);
+ let info = LayerPrimitiveInfo::new(root_bounds);
+ self.add_solid_rectangle(
+ reference_frame_info,
+ &info,
+ bg_color,
+ None,
+ );
+ }
+ }
+ }
+
+ self.flatten_items(&mut pipeline.display_list.iter(), pipeline_id, LayerVector2D::zero());
+
+ if self.config.enable_scrollbars {
+ let scrollbar_rect = LayerRect::new(LayerPoint::zero(), LayerSize::new(10.0, 70.0));
+ let container_rect = LayerRect::new(LayerPoint::zero(), *frame_size);
+ self.add_scroll_bar(
+ reference_frame_info,
+ &LayerPrimitiveInfo::new(scrollbar_rect),
+ DEFAULT_SCROLLBAR_COLOR,
+ ScrollbarInfo(scroll_frame_info.scroll_node_id, container_rect),
+ );
+ }
+
+ self.pop_stacking_context();
+ }
+
+ fn flatten_items(
+ &mut self,
+ traversal: &mut BuiltDisplayListIter<'a>,
+ pipeline_id: PipelineId,
+ reference_frame_relative_offset: LayerVector2D,
+ ) {
+ loop {
+ let subtraversal = {
+ let item = match traversal.next() {
+ Some(item) => item,
+ None => break,
+ };
+
+ if SpecificDisplayItem::PopStackingContext == *item.item() {
+ return;
+ }
+
+ self.flatten_item(
+ item,
+ pipeline_id,
+ reference_frame_relative_offset,
+ )
+ };
+
+ // If flatten_item created a sub-traversal, we need `traversal` to have the
+ // same state as the completed subtraversal, so we reinitialize it here.
+ if let Some(subtraversal) = subtraversal {
+ *traversal = subtraversal;
+ }
+ }
+ }
+
+ fn flatten_sticky_frame(
+ &mut self,
+ item: &DisplayItemRef,
+ info: &StickyFrameDisplayItem,
+ clip_and_scroll: &ScrollNodeAndClipChain,
+ parent_id: &ClipId,
+ reference_frame_relative_offset: &LayerVector2D,
+ ) {
+ let frame_rect = item.rect().translate(&reference_frame_relative_offset);
+ let sticky_frame_info = StickyFrameInfo::new(
+ info.margins,
+ info.vertical_offset_bounds,
+ info.horizontal_offset_bounds,
+ info.previously_applied_offset,
+ );
+
+ let index = self.id_to_index_mapper.get_node_index(info.id);
+ self.clip_scroll_tree.add_sticky_frame(
+ index,
+ clip_and_scroll.scroll_node_id, /* parent id */
+ frame_rect,
+ sticky_frame_info,
+ info.id.pipeline_id(),
+ );
+ self.id_to_index_mapper.map_to_parent_clip_chain(info.id, &parent_id);
+ }
+
+ fn flatten_scroll_frame(
+ &mut self,
+ item: &DisplayItemRef,
+ info: &ScrollFrameDisplayItem,
+ pipeline_id: PipelineId,
+ clip_and_scroll_ids: &ClipAndScrollInfo,
+ reference_frame_relative_offset: &LayerVector2D,
+ ) {
+ let complex_clips = self.get_complex_clips(pipeline_id, item.complex_clip().0);
+ let clip_region = ClipRegion::create_for_clip_node(
+ *item.local_clip().clip_rect(),
+ complex_clips,
+ info.image_mask,
+ &reference_frame_relative_offset,
+ );
+ // Just use clip rectangle as the frame rect for this scroll frame.
+ // This is useful when calculating scroll extents for the
+ // ClipScrollNode::scroll(..) API as well as for properly setting sticky
+ // positioning offsets.
+ let frame_rect = item.local_clip()
+ .clip_rect()
+ .translate(&reference_frame_relative_offset);
+ let content_rect = item.rect().translate(&reference_frame_relative_offset);
+
+ debug_assert!(info.clip_id != info.scroll_frame_id);
+
+ self.add_clip_node(info.clip_id, clip_and_scroll_ids.scroll_node_id, clip_region);
+
+ self.add_scroll_frame(
+ info.scroll_frame_id,
+ info.clip_id,
+ info.external_id,
+ pipeline_id,
+ &frame_rect,
+ &content_rect.size,
+ info.scroll_sensitivity,
+ );
+ }
+
+ fn flatten_stacking_context(
+ &mut self,
+ traversal: &mut BuiltDisplayListIter<'a>,
+ pipeline_id: PipelineId,
+ unreplaced_scroll_id: ClipId,
+ mut scroll_node_id: ClipId,
+ mut reference_frame_relative_offset: LayerVector2D,
+ bounds: &LayerRect,
+ stacking_context: &StackingContext,
+ filters: ItemRange<FilterOp>,
+ is_backface_visible: bool,
+ ) {
+ // Avoid doing unnecessary work for empty stacking contexts.
+ if traversal.current_stacking_context_empty() {
+ traversal.skip_current_stacking_context();
+ return;
+ }
+
+ let composition_operations = {
+ // TODO(optimization?): self.traversal.display_list()
+ let display_list = &self
+ .scene
+ .pipelines
+ .get(&pipeline_id)
+ .expect("No display list?!")
+ .display_list;
+ CompositeOps::new(
+ stacking_context.filter_ops_for_compositing(display_list, filters),
+ stacking_context.mix_blend_mode_for_compositing(),
+ )
+ };
+
+ if stacking_context.scroll_policy == ScrollPolicy::Fixed {
+ scroll_node_id = self.current_reference_frame_id();
+ self.replacements.push((unreplaced_scroll_id, scroll_node_id));
+ }
+
+ reference_frame_relative_offset += bounds.origin.to_vector();
+
+ // If we have a transformation or a perspective, we should have been assigned a new
+ // reference frame id. This means this stacking context establishes a new reference frame.
+ // Descendant fixed position content will be positioned relative to us.
+ if let Some(reference_frame_id) = stacking_context.reference_frame_id {
+ debug_assert!(
+ stacking_context.transform.is_some() ||
+ stacking_context.perspective.is_some()
+ );
+
+ let reference_frame_bounds = LayerRect::new(LayerPoint::zero(), bounds.size);
+ self.push_reference_frame(
+ reference_frame_id,
+ Some(scroll_node_id),
+ pipeline_id,
+ &reference_frame_bounds,
+ stacking_context.transform,
+ stacking_context.perspective,
+ reference_frame_relative_offset,
+ );
+ self.replacements.push((unreplaced_scroll_id, reference_frame_id));
+ reference_frame_relative_offset = LayerVector2D::zero();
+ }
+
+ // We apply the replacements one more time in case we need to set it to a replacement
+ // that we just pushed above.
+ let sc_scroll_node = self.apply_scroll_frame_id_replacement(unreplaced_scroll_id);
+ let stacking_context_clip_and_scroll =
+ self.id_to_index_mapper.simple_scroll_and_clip_chain(&sc_scroll_node);
+ self.push_stacking_context(
+ pipeline_id,
+ composition_operations,
+ stacking_context.transform_style,
+ is_backface_visible,
+ false,
+ stacking_context_clip_and_scroll,
+ );
+
+ self.flatten_items(
+ traversal,
+ pipeline_id,
+ reference_frame_relative_offset,
+ );
+
+ if stacking_context.scroll_policy == ScrollPolicy::Fixed {
+ self.replacements.pop();
+ }
+
+ if stacking_context.reference_frame_id.is_some() {
+ self.replacements.pop();
+ self.pop_reference_frame();
+ }
+
+ self.pop_stacking_context();
+ }
+
+ fn flatten_iframe(
+ &mut self,
+ item: &DisplayItemRef,
+ info: &IframeDisplayItem,
+ clip_and_scroll_ids: &ClipAndScrollInfo,
+ reference_frame_relative_offset: &LayerVector2D,
+ ) {
+ let iframe_pipeline_id = info.pipeline_id;
+ let pipeline = match self.scene.pipelines.get(&iframe_pipeline_id) {
+ Some(pipeline) => pipeline,
+ None => return,
+ };
+
+ self.id_to_index_mapper.initialize_for_pipeline(pipeline);
+
+ self.add_clip_node(
+ info.clip_id,
+ clip_and_scroll_ids.scroll_node_id,
+ ClipRegion::create_for_clip_node_with_local_clip(
+ &item.local_clip(),
+ &reference_frame_relative_offset
+ ),
+ );
+
+ let epoch = self.scene.pipeline_epochs[&iframe_pipeline_id];
+ self.pipeline_epochs.push((iframe_pipeline_id, epoch));
+
+ let bounds = item.rect();
+ let iframe_rect = LayerRect::new(LayerPoint::zero(), bounds.size);
+ let origin = *reference_frame_relative_offset + bounds.origin.to_vector();
+ self.push_reference_frame(
+ ClipId::root_reference_frame(iframe_pipeline_id),
+ Some(info.clip_id),
+ iframe_pipeline_id,
+ &iframe_rect,
+ None,
+ None,
+ origin,
+ );
+
+ self.add_scroll_frame(
+ ClipId::root_scroll_node(iframe_pipeline_id),
+ ClipId::root_reference_frame(iframe_pipeline_id),
+ Some(ExternalScrollId(0, iframe_pipeline_id)),
+ iframe_pipeline_id,
+ &iframe_rect,
+ &pipeline.content_size,
+ ScrollSensitivity::ScriptAndInputEvents,
+ );
+
+ self.flatten_root(pipeline, &iframe_rect.size);
+
+ self.pop_reference_frame();
+ }
+
+ fn flatten_item<'b>(
+ &'b mut self,
+ item: DisplayItemRef<'a, 'b>,
+ pipeline_id: PipelineId,
+ reference_frame_relative_offset: LayerVector2D,
+ ) -> Option<BuiltDisplayListIter<'a>> {
+ let mut clip_and_scroll_ids = item.clip_and_scroll();
+ let unreplaced_scroll_id = clip_and_scroll_ids.scroll_node_id;
+ clip_and_scroll_ids.scroll_node_id =
+ self.apply_scroll_frame_id_replacement(clip_and_scroll_ids.scroll_node_id);
+ let clip_and_scroll = self.id_to_index_mapper.map_clip_and_scroll(&clip_and_scroll_ids);
+
+ 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.
+ self.decompose_image(
+ clip_and_scroll,
+ &prim_info,
+ info,
+ tiling.image_size,
+ tiling.tile_size as u32,
+ );
+ }
+ 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,
+ );
+ }
+ }
+ }
+ SpecificDisplayItem::YuvImage(ref info) => {
+ self.add_yuv_image(
+ clip_and_scroll,
+ &prim_info,
+ info.yuv_data,
+ info.color_space,
+ info.image_rendering,
+ );
+ }
+ SpecificDisplayItem::Text(ref text_info) => {
+ self.add_text(
+ clip_and_scroll,
+ reference_frame_relative_offset,
+ &prim_info,
+ &text_info.font_key,
+ &text_info.color,
+ item.glyphs(),
+ item.display_list().get(item.glyphs()).count(),
+ text_info.glyph_options,
+ );
+ }
+ SpecificDisplayItem::Rectangle(ref info) => {
+ self.add_solid_rectangle(
+ clip_and_scroll,
+ &prim_info,
+ info.color,
+ None,
+ );
+ }
+ SpecificDisplayItem::ClearRectangle => {
+ self.add_clear_rectangle(
+ clip_and_scroll,
+ &prim_info,
+ );
+ }
+ SpecificDisplayItem::Line(ref info) => {
+ self.add_line(
+ clip_and_scroll,
+ &prim_info,
+ info.wavy_line_thickness,
+ info.orientation,
+ &info.color,
+ info.style,
+ );
+ }
+ SpecificDisplayItem::Gradient(ref info) => {
+ self.add_gradient(
+ clip_and_scroll,
+ &prim_info,
+ info.gradient.start_point,
+ info.gradient.end_point,
+ item.gradient_stops(),
+ item.display_list().get(item.gradient_stops()).count(),
+ info.gradient.extend_mode,
+ info.tile_size,
+ info.tile_spacing,
+ );
+ }
+ SpecificDisplayItem::RadialGradient(ref info) => {
+ self.add_radial_gradient(
+ clip_and_scroll,
+ &prim_info,
+ info.gradient.start_center,
+ info.gradient.start_radius,
+ info.gradient.end_center,
+ info.gradient.end_radius,
+ info.gradient.ratio_xy,
+ item.gradient_stops(),
+ info.gradient.extend_mode,
+ info.tile_size,
+ info.tile_spacing,
+ );
+ }
+ SpecificDisplayItem::BoxShadow(ref box_shadow_info) => {
+ let bounds = box_shadow_info
+ .box_bounds
+ .translate(&reference_frame_relative_offset);
+ let mut prim_info = prim_info.clone();
+ prim_info.rect = bounds;
+ self.add_box_shadow(
+ pipeline_id,
+ clip_and_scroll,
+ &prim_info,
+ &box_shadow_info.offset,
+ &box_shadow_info.color,
+ box_shadow_info.blur_radius,
+ box_shadow_info.spread_radius,
+ box_shadow_info.border_radius,
+ box_shadow_info.clip_mode,
+ );
+ }
+ SpecificDisplayItem::Border(ref info) => {
+ self.add_border(
+ clip_and_scroll,
+ &prim_info,
+ info,
+ item.gradient_stops(),
+ item.display_list().get(item.gradient_stops()).count(),
+ );
+ }
+ SpecificDisplayItem::PushStackingContext(ref info) => {
+ let mut subtraversal = item.sub_iter();
+ self.flatten_stacking_context(
+ &mut subtraversal,
+ pipeline_id,
+ unreplaced_scroll_id,
+ clip_and_scroll_ids.scroll_node_id,
+ reference_frame_relative_offset,
+ &item.rect(),
+ &info.stacking_context,
+ item.filters(),
+ prim_info.is_backface_visible,
+ );
+ return Some(subtraversal);
+ }
+ SpecificDisplayItem::Iframe(ref info) => {
+ self.flatten_iframe(
+ &item,
+ info,
+ &clip_and_scroll_ids,
+ &reference_frame_relative_offset
+ );
+ }
+ SpecificDisplayItem::Clip(ref info) => {
+ let complex_clips = self.get_complex_clips(pipeline_id, item.complex_clip().0);
+ let clip_region = ClipRegion::create_for_clip_node(
+ *item.local_clip().clip_rect(),
+ complex_clips,
+ info.image_mask,
+ &reference_frame_relative_offset,
+ );
+ self.add_clip_node(info.id, clip_and_scroll_ids.scroll_node_id, clip_region);
+ }
+ SpecificDisplayItem::ClipChain(ref info) => {
+ let items = self.get_clip_chain_items(pipeline_id, item.clip_chain_items())
+ .iter()
+ .map(|id| self.id_to_index_mapper.get_node_index(*id))
+ .collect();
+ let parent = info.parent.map(|id|
+ self.id_to_index_mapper.get_clip_chain_index(&ClipId::ClipChain(id))
+ );
+ let clip_chain_index =
+ self.clip_scroll_tree.add_clip_chain_descriptor(parent, items);
+ self.id_to_index_mapper.add_clip_chain(ClipId::ClipChain(info.id), clip_chain_index);
+ },
+ SpecificDisplayItem::ScrollFrame(ref info) => {
+ self.flatten_scroll_frame(
+ &item,
+ info,
+ pipeline_id,
+ &clip_and_scroll_ids,
+ &reference_frame_relative_offset
+ );
+ }
+ SpecificDisplayItem::StickyFrame(ref info) => {
+ self.flatten_sticky_frame(
+ &item,
+ info,
+ &clip_and_scroll,
+ &clip_and_scroll_ids.scroll_node_id,
+ &reference_frame_relative_offset
+ );
+ }
+
+ // Do nothing; these are dummy items for the display list parser
+ SpecificDisplayItem::SetGradientStops => {}
+
+ SpecificDisplayItem::PopStackingContext => {
+ unreachable!("Should have returned in parent method.")
+ }
+ SpecificDisplayItem::PushShadow(shadow) => {
+ let mut prim_info = prim_info.clone();
+ prim_info.rect = LayerRect::zero();
+ self
+ .push_shadow(shadow, clip_and_scroll, &prim_info);
+ }
+ SpecificDisplayItem::PopAllShadows => {
+ self.pop_all_shadows();
+ }
+ }
+ None
+ }
+
+ /// Decomposes an image display item 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,
+ /// decompositing 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_image_row handle image repetitions while decompose_tiled_image
+ /// takes care of the decomposition required by the internal tiling of the image.
+ fn decompose_image(
+ &mut self,
+ clip_and_scroll: ScrollNodeAndClipChain,
+ prim_info: &LayerPrimitiveInfo,
+ info: &ImageDisplayItem,
+ image_size: DeviceUintSize,
+ tile_size: u32,
+ ) {
+ let no_vertical_tiling = image_size.height <= tile_size;
+ let no_vertical_spacing = info.tile_spacing.height == 0.0;
+ let item_rect = prim_info.rect;
+ if no_vertical_tiling && no_vertical_spacing {
+ self.decompose_image_row(
+ clip_and_scroll,
+ prim_info,
+ info,
+ image_size,
+ tile_size,
+ );
+ return;
+ }
+
+ // Decompose each vertical repetition into rows.
+ let layout_stride = info.stretch_size.height + info.tile_spacing.height;
+ let num_repetitions = (item_rect.size.height / layout_stride).ceil() as u32;
+ for i in 0 .. num_repetitions {
+ if let Some(row_rect) = rect(
+ item_rect.origin.x,
+ item_rect.origin.y + (i as f32) * layout_stride,
+ item_rect.size.width,
+ info.stretch_size.height,
+ ).intersection(&item_rect)
+ {
+ let mut prim_info = prim_info.clone();
+ prim_info.rect = row_rect;
+ self.decompose_image_row(
+ clip_and_scroll,
+ &prim_info,
+ info,
+ image_size,
+ tile_size,
+ );
+ }
+ }
+ }
+
+ fn decompose_image_row(
+ &mut self,
+ clip_and_scroll: ScrollNodeAndClipChain,
+ prim_info: &LayerPrimitiveInfo,
+ info: &ImageDisplayItem,
+ image_size: DeviceUintSize,
+ tile_size: u32,
+ ) {
+ let no_horizontal_tiling = image_size.width <= tile_size;
+ let no_horizontal_spacing = info.tile_spacing.width == 0.0;
+ if no_horizontal_tiling && no_horizontal_spacing {
+ self.decompose_tiled_image(
+ clip_and_scroll,
+ prim_info,
+ info,
+ image_size,
+ tile_size,
+ );
+ return;
+ }
+
+ // Decompose each horizontal repetition.
+ let item_rect = prim_info.rect;
+ let layout_stride = info.stretch_size.width + info.tile_spacing.width;
+ let num_repetitions = (item_rect.size.width / layout_stride).ceil() as u32;
+ for i in 0 .. num_repetitions {
+ if let Some(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)
+ {
+ let mut prim_info = prim_info.clone();
+ prim_info.rect = decomposed_rect;
+ self.decompose_tiled_image(
+ clip_and_scroll,
+ &prim_info,
+ info,
+ image_size,
+ tile_size,
+ );
+ }
+ }
+ }
+
+ fn decompose_tiled_image(
+ &mut self,
+ clip_and_scroll: ScrollNodeAndClipChain,
+ prim_info: &LayerPrimitiveInfo,
+ info: &ImageDisplayItem,
+ image_size: DeviceUintSize,
+ tile_size: u32,
+ ) {
+ // The image resource is tiled. We have to generate an image primitive
+ // for each tile.
+ // We need to do this because the image is broken up into smaller tiles in the texture
+ // cache and the image shader is not able to work with this type of sparse representation.
+
+ // The tiling logic works as follows:
+ //
+ // ###################-+ -+
+ // # | | |//# | | image size
+ // # | | |//# | |
+ // #----+----+----+--#-+ | -+
+ // # | | |//# | | | regular tile size
+ // # | | |//# | | |
+ // #----+----+----+--#-+ | -+-+
+ // #////|////|////|//# | | | "leftover" height
+ // ################### | -+ ---+
+ // #----+----+----+----+
+ //
+ // In the ascii diagram above, a large image is plit 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
+ // 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 item_rect = prim_info.rect;
+ let needs_repeat_x = info.stretch_size.width < item_rect.size.width;
+ let needs_repeat_y = info.stretch_size.height < item_rect.size.height;
+
+ let tiled_in_x = image_size.width > tile_size;
+ let tiled_in_y = image_size.height > tile_size;
+
+ // 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 tile_size_f32 = tile_size 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 = (image_size.width / tile_size) as u16;
+ let num_tiles_y = (image_size.height / tile_size) as u16;
+
+ // Ratio between (image space) tile size and image size.
+ let img_dw = tile_size_f32 / (image_size.width as f32);
+ let img_dh = tile_size_f32 / (image_size.height as f32);
+
+ // Strected 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,
+ );
+
+ // 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(image_size.width % tile_size, image_size.height % tile_size);
+
+ for ty in 0 .. num_tiles_y {
+ for tx in 0 .. num_tiles_x {
+ self.add_tile_primitive(
+ clip_and_scroll,
+ prim_info,
+ info,
+ TileOffset::new(tx, ty),
+ stretched_tile_size,
+ 1.0,
+ 1.0,
+ shader_repeat_x,
+ shader_repeat_y,
+ );
+ }
+ if leftover.width != 0 {
+ // Tiles on the right edge that are smaller than the tile size.
+ self.add_tile_primitive(
+ clip_and_scroll,
+ prim_info,
+ info,
+ TileOffset::new(num_tiles_x, ty),
+ stretched_tile_size,
+ (leftover.width as f32) / tile_size_f32,
+ 1.0,
+ shader_repeat_x,
+ shader_repeat_y,
+ );
+ }
+ }
+
+ if leftover.height != 0 {
+ for tx in 0 .. num_tiles_x {
+ // Tiles on the bottom edge that are smaller than the tile size.
+ self.add_tile_primitive(
+ clip_and_scroll,
+ prim_info,
+ info,
+ TileOffset::new(tx, num_tiles_y),
+ stretched_tile_size,
+ 1.0,
+ (leftover.height as f32) / tile_size_f32,
+ shader_repeat_x,
+ shader_repeat_y,
+ );
+ }
+
+ if leftover.width != 0 {
+ // Finally, the bottom-right tile with a "leftover" size.
+ self.add_tile_primitive(
+ clip_and_scroll,
+ prim_info,
+ info,
+ TileOffset::new(num_tiles_x, num_tiles_y),
+ stretched_tile_size,
+ (leftover.width as f32) / tile_size_f32,
+ (leftover.height as f32) / tile_size_f32,
+ shader_repeat_x,
+ shader_repeat_y,
+ );
+ }
+ }
+ }
+
+ fn add_tile_primitive(
+ &mut self,
+ clip_and_scroll: ScrollNodeAndClipChain,
+ prim_info: &LayerPrimitiveInfo,
+ info: &ImageDisplayItem,
+ tile_offset: TileOffset,
+ stretched_tile_size: LayerSize,
+ tile_ratio_width: f32,
+ tile_ratio_height: f32,
+ shader_repeat_x: bool,
+ shader_repeat_y: bool,
+ ) {
+ // If the 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 repetion (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 stretched_size = LayerSize::new(
+ stretched_tile_size.width * tile_ratio_width,
+ stretched_tile_size.height * tile_ratio_height,
+ );
+
+ let mut prim_rect = LayerRect::new(
+ prim_info.rect.origin +
+ LayerVector2D::new(
+ tile_offset.x as f32 * stretched_tile_size.width,
+ tile_offset.y as f32 * stretched_tile_size.height,
+ ),
+ stretched_size,
+ );
+
+ if shader_repeat_x {
+ assert_eq!(tile_offset.x, 0);
+ prim_rect.size.width = prim_info.rect.size.width;
+ }
+
+ if shader_repeat_y {
+ assert_eq!(tile_offset.y, 0);
+ prim_rect.size.height = prim_info.rect.size.height;
+ }
+
+ // Fix up the primitive's rect if it overflows the original item rect.
+ if let Some(prim_rect) = prim_rect.intersection(&prim_info.rect) {
+ let mut prim_info = prim_info.clone();
+ prim_info.rect = prim_rect;
+ self.add_image(
+ clip_and_scroll,
+ &prim_info,
+ stretched_size,
+ info.tile_spacing,
+ None,
+ info.image_key,
+ info.image_rendering,
+ info.alpha_type,
+ Some(tile_offset),
+ );
+ }
+ }
+
+ /// Create a primitive and add it to the prim store. This method doesn't
+ /// add the primitive to the draw list, so can be used for creating
+ /// sub-primitives.
+ pub fn create_primitive(
+ &mut self,
+ info: &LayerPrimitiveInfo,
+ mut clip_sources: Vec<ClipSource>,
+ container: PrimitiveContainer,
+ ) -> PrimitiveIndex {
+ if let &LocalClip::RoundedRect(main, region) = &info.local_clip {
+ clip_sources.push(ClipSource::Rectangle(main));
+
+ clip_sources.push(ClipSource::new_rounded_rect(
+ region.rect,
+ region.radii,
+ region.mode,
+ ));
+ }
+
+ let stacking_context = self.sc_stack.last().expect("bug: no stacking context!");
+
+ let clip_sources = self.clip_store.insert(ClipSources::new(clip_sources));
+ let prim_index = self.prim_store.add_primitive(
+ &info.rect,
+ &info.local_clip.clip_rect(),
+ info.is_backface_visible && stacking_context.is_backface_visible,
+ clip_sources,
+ info.tag,
+ container,
+ );
+
+ prim_index
+ }
+
+ pub fn add_primitive_to_hit_testing_list(
+ &mut self,
+ info: &LayerPrimitiveInfo,
+ clip_and_scroll: ScrollNodeAndClipChain
+ ) {
+ let tag = match info.tag {
+ Some(tag) => tag,
+ None => return,
+ };
+
+ let new_item = HitTestingItem::new(tag, info);
+ match self.hit_testing_runs.last_mut() {
+ Some(&mut HitTestingRun(ref mut items, prev_clip_and_scroll))
+ if prev_clip_and_scroll == clip_and_scroll => {
+ items.push(new_item);
+ return;
+ }
+ _ => {}
+ }
+
+ self.hit_testing_runs.push(HitTestingRun(vec![new_item], clip_and_scroll));
+ }
+
+ /// Add an already created primitive to the draw lists.
+ pub fn add_primitive_to_draw_list(
+ &mut self,
+ prim_index: PrimitiveIndex,
+ clip_and_scroll: ScrollNodeAndClipChain,
+ ) {
+ // Add primitive to the top-most Picture on the stack.
+ // TODO(gw): Let's consider removing the extra indirection
+ // needed to get a specific primitive index...
+ let pic_prim_index = self.picture_stack.last().unwrap();
+ let metadata = &self.prim_store.cpu_metadata[pic_prim_index.0];
+ let pic = &mut self.prim_store.cpu_pictures[metadata.cpu_prim_index.0];
+ pic.add_primitive(
+ prim_index,
+ clip_and_scroll
+ );
+ }
+
+ /// Convenience interface that creates a primitive entry and adds it
+ /// to the draw list.
+ pub fn add_primitive(
+ &mut self,
+ clip_and_scroll: ScrollNodeAndClipChain,
+ info: &LayerPrimitiveInfo,
+ clip_sources: Vec<ClipSource>,
+ container: PrimitiveContainer,
+ ) -> PrimitiveIndex {
+ self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
+ let prim_index = self.create_primitive(info, clip_sources, container);
+
+ self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
+ prim_index
+ }
+
+ pub fn push_stacking_context(
+ &mut self,
+ pipeline_id: PipelineId,
+ composite_ops: CompositeOps,
+ transform_style: TransformStyle,
+ is_backface_visible: bool,
+ is_pipeline_root: bool,
+ clip_and_scroll: ScrollNodeAndClipChain,
+ ) {
+ // Construct the necessary set of Picture primitives
+ // to draw this stacking context.
+ let current_reference_frame_index = self.current_reference_frame_index();
+
+ // An arbitrary large clip rect. For now, we don't
+ // specify a clip specific to the stacking context.
+ // However, now that they are represented as Picture
+ // primitives, we can apply any kind of clip mask
+ // to them, as for a normal primitive. This is needed
+ // to correctly handle some CSS cases (see #1957).
+ let max_clip = LayerRect::max_rect();
+
+ // If there is no root picture, create one for the main framebuffer.
+ if self.sc_stack.is_empty() {
+ // Should be no pictures at all if the stack is empty...
+ debug_assert!(self.prim_store.cpu_pictures.is_empty());
+ debug_assert_eq!(transform_style, TransformStyle::Flat);
+
+ // This picture stores primitive runs for items on the
+ // main framebuffer.
+ let pic = PicturePrimitive::new_image(
+ None,
+ false,
+ pipeline_id,
+ current_reference_frame_index,
+ None,
+ );
+
+ // No clip sources needed for the main framebuffer.
+ let clip_sources = self.clip_store.insert(ClipSources::new(Vec::new()));
+
+ // Add root picture primitive. The provided layer rect
+ // is zero, because we don't yet know the size of the
+ // picture. Instead, this is calculated recursively
+ // when we cull primitives.
+ let prim_index = self.prim_store.add_primitive(
+ &LayerRect::zero(),
+ &max_clip,
+ true,
+ clip_sources,
+ None,
+ PrimitiveContainer::Picture(pic),
+ );
+
+ self.picture_stack.push(prim_index);
+ } else if composite_ops.mix_blend_mode.is_some() && self.sc_stack.len() > 2 {
+ // If we have a mix-blend-mode, and we aren't the primary framebuffer,
+ // the stacking context needs to be isolated to blend correctly as per
+ // the CSS spec.
+ // TODO(gw): The way we detect not being the primary framebuffer (len > 2)
+ // is hacky and depends on how we create a root stacking context
+ // during flattening.
+ let current_pic_prim_index = self.picture_stack.last().unwrap();
+ let pic_cpu_prim_index = self.prim_store.cpu_metadata[current_pic_prim_index.0].cpu_prim_index;
+ let parent_pic = &mut self.prim_store.cpu_pictures[pic_cpu_prim_index.0];
+
+ match parent_pic.kind {
+ PictureKind::Image { ref mut composite_mode, .. } => {
+ // If not already isolated for some other reason,
+ // make this picture as isolated.
+ if composite_mode.is_none() {
+ *composite_mode = Some(PictureCompositeMode::Blit);
+ }
+ }
+ PictureKind::TextShadow { .. } |
+ PictureKind::BoxShadow { .. } => {
+ panic!("bug: text/box pictures invalid here");
+ }
+ }
+ }
+
+ // Get the transform-style of the parent stacking context,
+ // which determines if we *might* need to draw this on
+ // an intermediate surface for plane splitting purposes.
+ let parent_transform_style = match self.sc_stack.last() {
+ Some(sc) => sc.transform_style,
+ None => TransformStyle::Flat,
+ };
+
+ // If this is preserve-3d *or* the parent is, then this stacking
+ // context is participating in the 3d rendering context. In that
+ // case, hoist the picture up to the 3d rendering context
+ // container, so that it's rendered as a sibling with other
+ // elements in this context.
+ let participating_in_3d_context =
+ composite_ops.count() == 0 &&
+ (parent_transform_style == TransformStyle::Preserve3D ||
+ transform_style == TransformStyle::Preserve3D);
+
+ // If this is participating in a 3d context *and* the
+ // parent was not a 3d context, then this must be the
+ // element that establishes a new 3d context.
+ let establishes_3d_context =
+ participating_in_3d_context &&
+ parent_transform_style == TransformStyle::Flat;
+
+ let rendering_context_3d_prim_index = if establishes_3d_context {
+ // If establishing a 3d context, we need to add a picture
+ // that will be the container for all the planes and any
+ // un-transformed content.
+ let container = PicturePrimitive::new_image(
+ None,
+ false,
+ pipeline_id,
+ current_reference_frame_index,
+ None,
+ );
+
+ let clip_sources = self.clip_store.insert(ClipSources::new(Vec::new()));
+
+ let prim_index = self.prim_store.add_primitive(
+ &LayerRect::zero(),
+ &max_clip,
+ is_backface_visible,
+ clip_sources,
+ None,
+ PrimitiveContainer::Picture(container),
+ );
+
+ let parent_pic_prim_index = *self.picture_stack.last().unwrap();
+ let pic_prim_index = self.prim_store.cpu_metadata[parent_pic_prim_index.0].cpu_prim_index;
+ let pic = &mut self.prim_store.cpu_pictures[pic_prim_index.0];
+ pic.add_primitive(
+ prim_index,
+ clip_and_scroll,
+ );
+
+ self.picture_stack.push(prim_index);
+
+ Some(prim_index)
+ } else {
+ None
+ };
+
+ let mut parent_pic_prim_index = if !establishes_3d_context && participating_in_3d_context {
+ // If we're in a 3D context, we will parent the picture
+ // to the first stacking context we find that is a
+ // 3D rendering context container. This follows the spec
+ // by hoisting these items out into the same 3D context
+ // for plane splitting.
+ self.sc_stack
+ .iter()
+ .rev()
+ .find(|sc| sc.rendering_context_3d_prim_index.is_some())
+ .map(|sc| sc.rendering_context_3d_prim_index.unwrap())
+ .unwrap()
+ } else {
+ *self.picture_stack.last().unwrap()
+ };
+
+ // For each filter, create a new image with that composite mode.
+ for filter in composite_ops.filters.iter().rev() {
+ let src_prim = PicturePrimitive::new_image(
+ Some(PictureCompositeMode::Filter(*filter)),
+ false,
+ pipeline_id,
+ current_reference_frame_index,
+ None,
+ );
+ let src_clip_sources = self.clip_store.insert(ClipSources::new(Vec::new()));
+
+ let src_prim_index = self.prim_store.add_primitive(
+ &LayerRect::zero(),
+ &max_clip,
+ is_backface_visible,
+ src_clip_sources,
+ None,
+ PrimitiveContainer::Picture(src_prim),
+ );
+
+ let pic_prim_index = self.prim_store.cpu_metadata[parent_pic_prim_index.0].cpu_prim_index;
+ parent_pic_prim_index = src_prim_index;
+ let pic = &mut self.prim_store.cpu_pictures[pic_prim_index.0];
+ pic.add_primitive(
+ src_prim_index,
+ clip_and_scroll,
+ );
+
+ self.picture_stack.push(src_prim_index);
+ }
+
+ // Same for mix-blend-mode.
+ if let Some(mix_blend_mode) = composite_ops.mix_blend_mode {
+ let src_prim = PicturePrimitive::new_image(
+ Some(PictureCompositeMode::MixBlend(mix_blend_mode)),
+ false,
+ pipeline_id,
+ current_reference_frame_index,
+ None,
+ );
+ let src_clip_sources = self.clip_store.insert(ClipSources::new(Vec::new()));
+
+ let src_prim_index = self.prim_store.add_primitive(
+ &LayerRect::zero(),
+ &max_clip,
+ is_backface_visible,
+ src_clip_sources,
+ None,
+ PrimitiveContainer::Picture(src_prim),
+ );
+
+ let pic_prim_index = self.prim_store.cpu_metadata[parent_pic_prim_index.0].cpu_prim_index;
+ parent_pic_prim_index = src_prim_index;
+ let pic = &mut self.prim_store.cpu_pictures[pic_prim_index.0];
+ pic.add_primitive(
+ src_prim_index,
+ clip_and_scroll,
+ );
+
+ self.picture_stack.push(src_prim_index);
+ }
+
+ // By default, this picture will be collapsed into
+ // the owning target.
+ let mut composite_mode = None;
+ let mut frame_output_pipeline_id = None;
+
+ // If this stacking context if the root of a pipeline, and the caller
+ // has requested it as an output frame, create a render task to isolate it.
+ if is_pipeline_root && self.output_pipelines.contains(&pipeline_id) {
+ composite_mode = Some(PictureCompositeMode::Blit);
+ frame_output_pipeline_id = Some(pipeline_id);
+ }
+
+ if participating_in_3d_context {
+ // TODO(gw): For now, as soon as this picture is in
+ // a 3D context, we draw it to an intermediate
+ // surface and apply plane splitting. However,
+ // there is a large optimization opportunity here.
+ // During culling, we can check if there is actually
+ // perspective present, and skip the plane splitting
+ // completely when that is not the case.
+ composite_mode = Some(PictureCompositeMode::Blit);
+ }
+
+ // Add picture for this actual stacking context contents to render into.
+ let sc_prim = PicturePrimitive::new_image(
+ composite_mode,
+ participating_in_3d_context,
+ pipeline_id,
+ current_reference_frame_index,
+ frame_output_pipeline_id,
+ );
+
+ let sc_clip_sources = self.clip_store.insert(ClipSources::new(Vec::new()));
+ let sc_prim_index = self.prim_store.add_primitive(
+ &LayerRect::zero(),
+ &max_clip,
+ is_backface_visible,
+ sc_clip_sources,
+ None,
+ PrimitiveContainer::Picture(sc_prim),
+ );
+
+ let pic_prim_index = self.prim_store.cpu_metadata[parent_pic_prim_index.0].cpu_prim_index;
+ let sc_pic = &mut self.prim_store.cpu_pictures[pic_prim_index.0];
+ sc_pic.add_primitive(
+ sc_prim_index,
+ clip_and_scroll,
+ );
+
+ // Add this as the top-most picture for primitives to be added to.
+ self.picture_stack.push(sc_prim_index);
+
+ // TODO(gw): This is super conservative. We can expand on this a lot
+ // once all the picture code is in place and landed.
+ let allow_subpixel_aa = composite_ops.count() == 0 &&
+ transform_style == TransformStyle::Flat;
+
+ // Push the SC onto the stack, so we know how to handle things in
+ // pop_stacking_context.
+ let sc = FlattenedStackingContext {
+ composite_ops,
+ is_backface_visible,
+ pipeline_id,
+ allow_subpixel_aa,
+ transform_style,
+ rendering_context_3d_prim_index,
+ };
+
+ self.sc_stack.push(sc);
+ }
+
+ pub fn pop_stacking_context(&mut self) {
+ let sc = self.sc_stack.pop().unwrap();
+
+ // Always pop at least the main picture for this stacking context.
+ let mut pop_count = 1;
+
+ // Remove the picture for any filter/mix-blend-mode effects.
+ pop_count += sc.composite_ops.count();
+
+ // Remove the 3d context container if created
+ if sc.rendering_context_3d_prim_index.is_some() {
+ pop_count += 1;
+ }
+
+ for _ in 0 .. pop_count {
+ self.picture_stack.pop().expect("bug: mismatched picture stack");
+ }
+
+ // By the time the stacking context stack is empty, we should
+ // also have cleared the picture stack.
+ if self.sc_stack.is_empty() {
+ self.picture_stack.pop().expect("bug: picture stack invalid");
+ debug_assert!(self.picture_stack.is_empty());
+ }
+
+ assert!(
+ self.shadow_prim_stack.is_empty(),
+ "Found unpopped text shadows when popping stacking context!"
+ );
+ }
+
+ pub fn push_reference_frame(
+ &mut self,
+ reference_frame_id: ClipId,
+ parent_id: Option<ClipId>,
+ pipeline_id: PipelineId,
+ rect: &LayerRect,
+ source_transform: Option<PropertyBinding<LayoutTransform>>,
+ source_perspective: Option<LayoutTransform>,
+ origin_in_parent_reference_frame: LayerVector2D,
+ ) -> ClipScrollNodeIndex {
+ let index = self.id_to_index_mapper.get_node_index(reference_frame_id);
+ let node = ClipScrollNode::new_reference_frame(
+ parent_id.map(|id| self.id_to_index_mapper.get_node_index(id)),
+ rect,
+ source_transform,
+ source_perspective,
+ origin_in_parent_reference_frame,
+ pipeline_id,
+ );
+ self.clip_scroll_tree.add_node(node, index);
+ self.reference_frame_stack.push((reference_frame_id, index));
+
+ match parent_id {
+ Some(ref parent_id) =>
+ self.id_to_index_mapper.map_to_parent_clip_chain(reference_frame_id, parent_id),
+ _ => self.id_to_index_mapper.add_clip_chain(reference_frame_id, ClipChainIndex(0)),
+ }
+ index
+ }
+
+ pub fn current_reference_frame_index(&self) -> ClipScrollNodeIndex {
+ self.reference_frame_stack.last().unwrap().1
+ }
+
+ pub fn current_reference_frame_id(&self) -> ClipId{
+ self.reference_frame_stack.last().unwrap().0
+ }
+
+ pub fn setup_viewport_offset(
+ &mut self,
+ inner_rect: DeviceUintRect,
+ device_pixel_scale: DevicePixelScale,
+ ) {
+ let viewport_offset = (inner_rect.origin.to_vector().to_f32() / device_pixel_scale).round();
+ let root_id = self.clip_scroll_tree.root_reference_frame_index();
+ let root_node = &mut self.clip_scroll_tree.nodes[root_id.0];
+ if let NodeType::ReferenceFrame(ref mut info) = root_node.node_type {
+ info.resolved_transform =
+ LayerVector2D::new(viewport_offset.x, viewport_offset.y).into();
+ }
+ }
+
+ pub fn push_root(
+ &mut self,
+ pipeline_id: PipelineId,
+ viewport_size: &LayerSize,
+ content_size: &LayerSize,
+ ) {
+ let viewport_rect = LayerRect::new(LayerPoint::zero(), *viewport_size);
+
+ self.push_reference_frame(
+ ClipId::root_reference_frame(pipeline_id),
+ None,
+ pipeline_id,
+ &viewport_rect,
+ None,
+ None,
+ LayerVector2D::zero(),
+ );
+
+ self.add_scroll_frame(
+ ClipId::root_scroll_node(pipeline_id),
+ ClipId::root_reference_frame(pipeline_id),
+ Some(ExternalScrollId(0, pipeline_id)),
+ pipeline_id,
+ &viewport_rect,
+ content_size,
+ ScrollSensitivity::ScriptAndInputEvents,
+ );
+ }
+
+ pub fn add_clip_node(
+ &mut self,
+ new_node_id: ClipId,
+ parent_id: ClipId,
+ clip_region: ClipRegion,
+ ) -> ClipScrollNodeIndex {
+ let clip_rect = clip_region.main;
+ let clip_sources = ClipSources::from(clip_region);
+
+ debug_assert!(clip_sources.has_clips());
+ let handle = self.clip_store.insert(clip_sources);
+
+ let node_index = self.id_to_index_mapper.get_node_index(new_node_id);
+ let clip_chain_index = self.clip_scroll_tree.add_clip_node(
+ node_index,
+ self.id_to_index_mapper.get_node_index(parent_id),
+ handle,
+ clip_rect,
+ new_node_id.pipeline_id(),
+ );
+ self.id_to_index_mapper.add_clip_chain(new_node_id, clip_chain_index);
+ node_index
+ }
+
+ pub fn add_scroll_frame(
+ &mut self,
+ new_node_id: ClipId,
+ parent_id: ClipId,
+ external_id: Option<ExternalScrollId>,
+ pipeline_id: PipelineId,
+ frame_rect: &LayerRect,
+ content_size: &LayerSize,
+ scroll_sensitivity: ScrollSensitivity,
+ ) -> ClipScrollNodeIndex {
+ let node_index = self.id_to_index_mapper.get_node_index(new_node_id);
+ let node = ClipScrollNode::new_scroll_frame(
+ pipeline_id,
+ self.id_to_index_mapper.get_node_index(parent_id),
+ external_id,
+ frame_rect,
+ content_size,
+ scroll_sensitivity,
+ );
+
+ self.clip_scroll_tree.add_node(node, node_index);
+ self.id_to_index_mapper.map_to_parent_clip_chain(new_node_id, &parent_id);
+ node_index
+ }
+
+ pub fn pop_reference_frame(&mut self) {
+ self.reference_frame_stack.pop();
+ }
+
+ pub fn push_shadow(
+ &mut self,
+ shadow: Shadow,
+ clip_and_scroll: ScrollNodeAndClipChain,
+ info: &LayerPrimitiveInfo,
+ ) {
+ let pipeline_id = self.sc_stack.last().unwrap().pipeline_id;
+ let prim = PicturePrimitive::new_text_shadow(shadow, pipeline_id);
+
+ // Create an empty shadow primitive. Insert it into
+ // the draw lists immediately so that it will be drawn
+ // before any visual text elements that are added as
+ // part of this shadow context.
+ let prim_index = self.create_primitive(
+ info,
+ Vec::new(),
+ PrimitiveContainer::Picture(prim),
+ );
+
+ let pending = vec![(prim_index, clip_and_scroll)];
+ self.shadow_prim_stack.push((prim_index, pending));
+ }
+
+ pub fn pop_all_shadows(&mut self) {
+ assert!(self.shadow_prim_stack.len() > 0, "popped shadows, but none were present");
+
+ // Borrowcheck dance
+ let mut shadows = mem::replace(&mut self.shadow_prim_stack, Vec::new());
+ for (_, pending_primitives) in shadows.drain(..) {
+ // Push any fast-path shadows now
+ for (prim_index, clip_and_scroll) in pending_primitives {
+ self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
+ }
+ }
+
+ let mut pending_primitives = mem::replace(&mut self.pending_shadow_contents, Vec::new());
+ for (prim_index, clip_and_scroll, info) in pending_primitives.drain(..) {
+ self.add_primitive_to_hit_testing_list(&info, clip_and_scroll);
+ self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
+ }
+
+ mem::replace(&mut self.pending_shadow_contents, pending_primitives);
+ mem::replace(&mut self.shadow_prim_stack, shadows);
+ }
+
+ pub fn add_solid_rectangle(
+ &mut self,
+ clip_and_scroll: ScrollNodeAndClipChain,
+ info: &LayerPrimitiveInfo,
+ color: ColorF,
+ segments: Option<BrushSegmentDescriptor>,
+ ) {
+ if color.a == 0.0 {
+ // Don't add transparent rectangles to the draw list, but do consider them for hit
+ // testing. This allows specifying invisible hit testing areas.
+ self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
+ return;
+ }
+
+ let prim = BrushPrimitive::new(
+ BrushKind::Solid {
+ color,
+ },
+ segments,
+ );
+
+ self.add_primitive(
+ clip_and_scroll,
+ info,
+ Vec::new(),
+ PrimitiveContainer::Brush(prim),
+ );
+ }
+
+ pub fn add_clear_rectangle(
+ &mut self,
+ clip_and_scroll: ScrollNodeAndClipChain,
+ info: &LayerPrimitiveInfo,
+ ) {
+ let prim = BrushPrimitive::new(
+ BrushKind::Clear,
+ None,
+ );
+
+ self.add_primitive(
+ clip_and_scroll,
+ info,
+ Vec::new(),
+ PrimitiveContainer::Brush(prim),
+ );
+ }
+
+ pub fn add_scroll_bar(
+ &mut self,
+ clip_and_scroll: ScrollNodeAndClipChain,
+ info: &LayerPrimitiveInfo,
+ color: ColorF,
+ scrollbar_info: ScrollbarInfo,
+ ) {
+ if color.a == 0.0 {
+ return;
+ }
+
+ let prim = BrushPrimitive::new(
+ BrushKind::Solid {
+ color,
+ },
+ None,
+ );
+
+ let prim_index = self.add_primitive(
+ clip_and_scroll,
+ info,
+ Vec::new(),
+ PrimitiveContainer::Brush(prim),
+ );
+
+ self.scrollbar_prims.push(ScrollbarPrimitive {
+ prim_index,
+ scroll_frame_index: scrollbar_info.0,
+ frame_rect: scrollbar_info.1,
+ });
+ }
+
+ pub fn add_line(
+ &mut self,
+ clip_and_scroll: ScrollNodeAndClipChain,
+ info: &LayerPrimitiveInfo,
+ wavy_line_thickness: f32,
+ orientation: LineOrientation,
+ line_color: &ColorF,
+ style: LineStyle,
+ ) {
+ let line = BrushPrimitive::new(
+ BrushKind::Line {
+ wavy_line_thickness,
+ color: line_color.premultiplied(),
+ style,
+ orientation,
+ },
+ None,
+ );
+
+ let mut fast_shadow_prims = Vec::new();
+ for (idx, &(shadow_prim_index, _)) in self.shadow_prim_stack.iter().enumerate() {
+ let shadow_metadata = &self.prim_store.cpu_metadata[shadow_prim_index.0];
+ let picture = &self.prim_store.cpu_pictures[shadow_metadata.cpu_prim_index.0];
+ match picture.kind {
+ PictureKind::TextShadow { offset, color, blur_radius, .. } if blur_radius == 0.0 => {
+ fast_shadow_prims.push((idx, offset, color));
+ }
+ _ => {}
+ }
+ }
+
+ for (idx, shadow_offset, shadow_color) in fast_shadow_prims {
+ let line = BrushPrimitive::new(
+ BrushKind::Line {
+ wavy_line_thickness,
+ color: shadow_color.premultiplied(),
+ style,
+ orientation,
+ },
+ None,
+ );
+ let mut info = info.clone();
+ info.rect = info.rect.translate(&shadow_offset);
+ info.local_clip =
+ LocalClip::from(info.local_clip.clip_rect().translate(&shadow_offset));
+ let prim_index = self.create_primitive(
+ &info,
+ Vec::new(),
+ PrimitiveContainer::Brush(line),
+ );
+ self.shadow_prim_stack[idx].1.push((prim_index, clip_and_scroll));
+ }
+
+ let prim_index = self.create_primitive(
+ &info,
+ Vec::new(),
+ PrimitiveContainer::Brush(line),
+ );
+
+ if line_color.a > 0.0 {
+ if self.shadow_prim_stack.is_empty() {
+ self.add_primitive_to_hit_testing_list(&info, clip_and_scroll);
+ self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
+ } else {
+ self.pending_shadow_contents.push((prim_index, clip_and_scroll, *info));
+ }
+ }
+
+ for &(shadow_prim_index, _) in &self.shadow_prim_stack {
+ let shadow_metadata = &mut self.prim_store.cpu_metadata[shadow_prim_index.0];
+ debug_assert_eq!(shadow_metadata.prim_kind, PrimitiveKind::Picture);
+ let picture =
+ &mut self.prim_store.cpu_pictures[shadow_metadata.cpu_prim_index.0];
+
+ match picture.kind {
+ // Only run real blurs here (fast path zero blurs are handled above).
+ PictureKind::TextShadow { blur_radius, .. } if blur_radius > 0.0 => {
+ picture.add_primitive(
+ prim_index,
+ clip_and_scroll,
+ );
+ }
+ _ => {}
+ }
+ }
+ }
+
+ pub fn add_border(
+ &mut self,
+ clip_and_scroll: ScrollNodeAndClipChain,
+ info: &LayerPrimitiveInfo,
+ border_item: &BorderDisplayItem,
+ gradient_stops: ItemRange<GradientStop>,
+ gradient_stops_count: usize,
+ ) {
+ let rect = info.rect;
+ let create_segments = |outset: SideOffsets2D<f32>| {
+ // Calculate the modified rect as specific by border-image-outset
+ let origin = LayerPoint::new(rect.origin.x - outset.left, rect.origin.y - outset.top);
+ let size = LayerSize::new(
+ rect.size.width + outset.left + outset.right,
+ rect.size.height + outset.top + outset.bottom,
+ );
+ let rect = LayerRect::new(origin, size);
+
+ let tl_outer = LayerPoint::new(rect.origin.x, rect.origin.y);
+ let tl_inner = tl_outer + vec2(border_item.widths.left, border_item.widths.top);
+
+ let tr_outer = LayerPoint::new(rect.origin.x + rect.size.width, rect.origin.y);
+ let tr_inner = tr_outer + vec2(-border_item.widths.right, border_item.widths.top);
+
+ let bl_outer = LayerPoint::new(rect.origin.x, rect.origin.y + rect.size.height);
+ let bl_inner = bl_outer + vec2(border_item.widths.left, -border_item.widths.bottom);
+
+ let br_outer = LayerPoint::new(
+ rect.origin.x + rect.size.width,
+ rect.origin.y + rect.size.height,
+ );
+ let br_inner = br_outer - vec2(border_item.widths.right, border_item.widths.bottom);
+
+ // Build the list of gradient segments
+ vec![
+ // Top left
+ LayerRect::from_floats(tl_outer.x, tl_outer.y, tl_inner.x, tl_inner.y),
+ // Top right
+ LayerRect::from_floats(tr_inner.x, tr_outer.y, tr_outer.x, tr_inner.y),
+ // Bottom right
+ LayerRect::from_floats(br_inner.x, br_inner.y, br_outer.x, br_outer.y),
+ // Bottom left
+ LayerRect::from_floats(bl_outer.x, bl_inner.y, bl_inner.x, bl_outer.y),
+ // Top
+ LayerRect::from_floats(tl_inner.x, tl_outer.y, tr_inner.x, tl_inner.y),
+ // Bottom
+ LayerRect::from_floats(bl_inner.x, bl_inner.y, br_inner.x, bl_outer.y),
+ // Left
+ LayerRect::from_floats(tl_outer.x, tl_inner.y, tl_inner.x, bl_inner.y),
+ // Right
+ LayerRect::from_floats(tr_inner.x, tr_inner.y, br_outer.x, br_inner.y),
+ ]
+ };
+
+ match border_item.details {
+ BorderDetails::Image(ref border) => {
+ // Calculate the modified rect as specific by border-image-outset
+ let origin = LayerPoint::new(
+ rect.origin.x - border.outset.left,
+ rect.origin.y - border.outset.top,
+ );
+ let size = LayerSize::new(
+ rect.size.width + border.outset.left + border.outset.right,
+ rect.size.height + border.outset.top + border.outset.bottom,
+ );
+ let rect = LayerRect::new(origin, size);
+
+ // Calculate the local texel coords of the slices.
+ let px0 = 0.0;
+ let px1 = border.patch.slice.left as f32;
+ let px2 = border.patch.width as f32 - border.patch.slice.right as f32;
+ let px3 = border.patch.width as f32;
+
+ let py0 = 0.0;
+ let py1 = border.patch.slice.top as f32;
+ let py2 = border.patch.height as f32 - border.patch.slice.bottom as f32;
+ let py3 = border.patch.height as f32;
+
+ let tl_outer = LayerPoint::new(rect.origin.x, rect.origin.y);
+ let tl_inner = tl_outer + vec2(border_item.widths.left, border_item.widths.top);
+
+ let tr_outer = LayerPoint::new(rect.origin.x + rect.size.width, rect.origin.y);
+ let tr_inner = tr_outer + vec2(-border_item.widths.right, border_item.widths.top);
+
+ let bl_outer = LayerPoint::new(rect.origin.x, rect.origin.y + rect.size.height);
+ let bl_inner = bl_outer + vec2(border_item.widths.left, -border_item.widths.bottom);
+
+ let br_outer = LayerPoint::new(
+ rect.origin.x + rect.size.width,
+ rect.origin.y + rect.size.height,
+ );
+ let br_inner = br_outer - vec2(border_item.widths.right, border_item.widths.bottom);
+
+ fn add_segment(
+ segments: &mut Vec<ImageBorderSegment>,
+ rect: LayerRect,
+ uv_rect: TexelRect,
+ repeat_horizontal: RepeatMode,
+ repeat_vertical: RepeatMode) {
+ if uv_rect.uv1.x > uv_rect.uv0.x &&
+ uv_rect.uv1.y > uv_rect.uv0.y {
+ segments.push(ImageBorderSegment::new(
+ rect,
+ uv_rect,
+ repeat_horizontal,
+ repeat_vertical,
+ ));
+ }
+ }
+
+ // Build the list of image segments
+ let mut segments = vec![];
+
+ // Top left
+ add_segment(
+ &mut segments,
+ LayerRect::from_floats(tl_outer.x, tl_outer.y, tl_inner.x, tl_inner.y),
+ TexelRect::new(px0, py0, px1, py1),
+ RepeatMode::Stretch,
+ RepeatMode::Stretch
+ );
+ // Top right
+ add_segment(
+ &mut segments,
+ LayerRect::from_floats(tr_inner.x, tr_outer.y, tr_outer.x, tr_inner.y),
+ TexelRect::new(px2, py0, px3, py1),
+ RepeatMode::Stretch,
+ RepeatMode::Stretch
+ );
+ // Bottom right
+ add_segment(
+ &mut segments,
+ LayerRect::from_floats(br_inner.x, br_inner.y, br_outer.x, br_outer.y),
+ TexelRect::new(px2, py2, px3, py3),
+ RepeatMode::Stretch,
+ RepeatMode::Stretch
+ );
+ // Bottom left
+ add_segment(
+ &mut segments,
+ LayerRect::from_floats(bl_outer.x, bl_inner.y, bl_inner.x, bl_outer.y),
+ TexelRect::new(px0, py2, px1, py3),
+ RepeatMode::Stretch,
+ RepeatMode::Stretch
+ );
+
+ // Center
+ if border.fill {
+ add_segment(
+ &mut segments,
+ LayerRect::from_floats(tl_inner.x, tl_inner.y, tr_inner.x, bl_inner.y),
+ TexelRect::new(px1, py1, px2, py2),
+ border.repeat_horizontal,
+ border.repeat_vertical
+ );
+ }
+
+ // Add edge segments.
+
+ // Top
+ add_segment(
+ &mut segments,
+ LayerRect::from_floats(tl_inner.x, tl_outer.y, tr_inner.x, tl_inner.y),
+ TexelRect::new(px1, py0, px2, py1),
+ border.repeat_horizontal,
+ RepeatMode::Stretch,
+ );
+ // Bottom
+ add_segment(
+ &mut segments,
+ LayerRect::from_floats(bl_inner.x, bl_inner.y, br_inner.x, bl_outer.y),
+ TexelRect::new(px1, py2, px2, py3),
+ border.repeat_horizontal,
+ RepeatMode::Stretch,
+ );
+ // Left
+ add_segment(
+ &mut segments,
+ LayerRect::from_floats(tl_outer.x, tl_inner.y, tl_inner.x, bl_inner.y),
+ TexelRect::new(px0, py1, px1, py2),
+ RepeatMode::Stretch,
+ border.repeat_vertical,
+ );
+ // Right
+ add_segment(
+ &mut segments,
+ LayerRect::from_floats(tr_inner.x, tr_inner.y, br_outer.x, br_inner.y),
+ TexelRect::new(px2, py1, px3, py2),
+ RepeatMode::Stretch,
+ border.repeat_vertical,
+ );
+
+ for segment in segments {
+ let mut info = info.clone();
+ info.rect = segment.geom_rect;
+ self.add_image(
+ clip_and_scroll,
+ &info,
+ segment.stretch_size,
+ segment.tile_spacing,
+ Some(segment.sub_rect),
+ border.image_key,
+ ImageRendering::Auto,
+ AlphaType::PremultipliedAlpha,
+ None,
+ );
+ }
+ }
+ BorderDetails::Normal(ref border) => {
+ self.add_normal_border(info, border, &border_item.widths, clip_and_scroll);
+ }
+ BorderDetails::Gradient(ref border) => for segment in create_segments(border.outset) {
+ let segment_rel = segment.origin - rect.origin;
+ let mut info = info.clone();
+ info.rect = segment;
+
+ self.add_gradient(
+ clip_and_scroll,
+ &info,
+ border.gradient.start_point - segment_rel,
+ border.gradient.end_point - segment_rel,
+ gradient_stops,
+ gradient_stops_count,
+ border.gradient.extend_mode,
+ segment.size,
+ LayerSize::zero(),
+ );
+ },
+ BorderDetails::RadialGradient(ref border) => {
+ for segment in create_segments(border.outset) {
+ let segment_rel = segment.origin - rect.origin;
+ let mut info = info.clone();
+ info.rect = segment;
+
+ self.add_radial_gradient(
+ clip_and_scroll,
+ &info,
+ border.gradient.start_center - segment_rel,
+ border.gradient.start_radius,
+ border.gradient.end_center - segment_rel,
+ border.gradient.end_radius,
+ border.gradient.ratio_xy,
+ gradient_stops,
+ border.gradient.extend_mode,
+ segment.size,
+ LayerSize::zero(),
+ );
+ }
+ }
+ }
+ }
+
+ fn add_gradient_impl(
+ &mut self,
+ clip_and_scroll: ScrollNodeAndClipChain,
+ info: &LayerPrimitiveInfo,
+ start_point: LayerPoint,
+ end_point: LayerPoint,
+ stops: ItemRange<GradientStop>,
+ stops_count: usize,
+ extend_mode: ExtendMode,
+ gradient_index: CachedGradientIndex,
+ ) {
+ // 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,
+ // a reference orientation for the gradient line must be chosen, somewhat arbitrarily, so
+ // just designate the reference orientation as start < end. Aligned gradient rendering
+ // manages to produce the same result regardless of orientation, so don't worry about
+ // reversing in that case.
+ let reverse_stops = start_point.x > end_point.x ||
+ (start_point.x == end_point.x && start_point.y > end_point.y);
+
+ // To get reftests exactly matching with reverse start/end
+ // points, it's necessary to reverse the gradient
+ // line in some cases.
+ let (sp, ep) = if reverse_stops {
+ (end_point, start_point)
+ } else {
+ (start_point, end_point)
+ };
+
+ let prim = BrushPrimitive::new(
+ BrushKind::LinearGradient {
+ stops_range: stops,
+ stops_count,
+ extend_mode,
+ reverse_stops,
+ start_point: sp,
+ end_point: ep,
+ gradient_index,
+ },
+ None,
+ );
+
+ let prim = PrimitiveContainer::Brush(prim);
+
+ self.add_primitive(clip_and_scroll, info, Vec::new(), prim);
+ }
+
+ pub fn add_gradient(
+ &mut self,
+ clip_and_scroll: ScrollNodeAndClipChain,
+ info: &LayerPrimitiveInfo,
+ start_point: LayerPoint,
+ end_point: LayerPoint,
+ stops: ItemRange<GradientStop>,
+ stops_count: usize,
+ extend_mode: ExtendMode,
+ tile_size: LayerSize,
+ 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,
+ );
+
+ if prim_infos.is_empty() {
+ self.add_gradient_impl(
+ clip_and_scroll,
+ info,
+ start_point,
+ end_point,
+ stops,
+ stops_count,
+ extend_mode,
+ gradient_index,
+ );
+ } 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,
+ );
+ }
+ }
+ }
+
+ fn add_radial_gradient_impl(
+ &mut self,
+ clip_and_scroll: ScrollNodeAndClipChain,
+ info: &LayerPrimitiveInfo,
+ start_center: LayerPoint,
+ start_radius: f32,
+ end_center: LayerPoint,
+ end_radius: f32,
+ ratio_xy: f32,
+ stops: ItemRange<GradientStop>,
+ extend_mode: ExtendMode,
+ gradient_index: CachedGradientIndex,
+ ) {
+ let prim = BrushPrimitive::new(
+ BrushKind::RadialGradient {
+ stops_range: stops,
+ extend_mode,
+ start_center,
+ end_center,
+ start_radius,
+ end_radius,
+ ratio_xy,
+ gradient_index,
+ },
+ None,
+ );
+
+ self.add_primitive(
+ clip_and_scroll,
+ info,
+ Vec::new(),
+ PrimitiveContainer::Brush(prim),
+ );
+ }
+
+ pub fn add_radial_gradient(
+ &mut self,
+ clip_and_scroll: ScrollNodeAndClipChain,
+ info: &LayerPrimitiveInfo,
+ start_center: LayerPoint,
+ start_radius: f32,
+ end_center: LayerPoint,
+ end_radius: f32,
+ ratio_xy: f32,
+ stops: ItemRange<GradientStop>,
+ extend_mode: ExtendMode,
+ tile_size: LayerSize,
+ 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,
+ );
+
+ if prim_infos.is_empty() {
+ self.add_radial_gradient_impl(
+ clip_and_scroll,
+ info,
+ start_center,
+ start_radius,
+ end_center,
+ end_radius,
+ ratio_xy,
+ stops,
+ extend_mode,
+ gradient_index,
+ );
+ } else {
+ for prim_info in prim_infos {
+ self.add_radial_gradient_impl(
+ clip_and_scroll,
+ &prim_info,
+ start_center,
+ start_radius,
+ end_center,
+ end_radius,
+ ratio_xy,
+ stops,
+ extend_mode,
+ gradient_index,
+ );
+ }
+ }
+ }
+
+ pub fn add_text(
+ &mut self,
+ clip_and_scroll: ScrollNodeAndClipChain,
+ run_offset: LayoutVector2D,
+ info: &LayerPrimitiveInfo,
+ font_instance_key: &FontInstanceKey,
+ text_color: &ColorF,
+ glyph_range: ItemRange<GlyphInstance>,
+ glyph_count: usize,
+ glyph_options: Option<GlyphOptions>,
+ ) {
+ let prim = {
+ let instance_map = self.font_instances.read().unwrap();
+ let font_instance = match instance_map.get(&font_instance_key) {
+ Some(instance) => instance,
+ None => {
+ warn!("Unknown font instance key");
+ debug!("key={:?}", font_instance_key);
+ return;
+ }
+ };
+
+ // Trivial early out checks
+ if font_instance.size.0 <= 0 {
+ return;
+ }
+
+ // Sanity check - anything with glyphs bigger than this
+ // is probably going to consume too much memory to render
+ // efficiently anyway. This is specifically to work around
+ // the font_advance.html reftest, which creates a very large
+ // font as a crash test - the rendering is also ignored
+ // by the azure renderer.
+ if font_instance.size >= Au::from_px(4096) {
+ return;
+ }
+
+ // TODO(gw): Use a proper algorithm to select
+ // whether this item should be rendered with
+ // subpixel AA!
+ let mut render_mode = self.config
+ .default_font_render_mode
+ .limit_by(font_instance.render_mode);
+ let mut flags = font_instance.flags;
+ if let Some(options) = glyph_options {
+ render_mode = render_mode.limit_by(options.render_mode);
+ flags |= options.flags;
+ }
+
+ // There are some conditions under which we can't use
+ // subpixel text rendering, even if enabled.
+ if render_mode == FontRenderMode::Subpixel {
+ // text on a picture that has filters
+ // (e.g. opacity) can't use sub-pixel.
+ // TODO(gw): It's possible we can relax this in
+ // the future, if we modify the way
+ // we handle subpixel blending.
+ if let Some(ref stacking_context) = self.sc_stack.last() {
+ if !stacking_context.allow_subpixel_aa {
+ render_mode = FontRenderMode::Alpha;
+ }
+ }
+ }
+
+ let prim_font = FontInstance::new(
+ font_instance.font_key,
+ font_instance.size,
+ *text_color,
+ font_instance.bg_color,
+ render_mode,
+ font_instance.subpx_dir,
+ flags,
+ font_instance.platform_options,
+ font_instance.variations.clone(),
+ );
+ TextRunPrimitiveCpu {
+ font: prim_font,
+ glyph_range,
+ glyph_count,
+ glyph_gpu_blocks: Vec::new(),
+ glyph_keys: Vec::new(),
+ offset: run_offset,
+ shadow: false,
+ }
+ };
+
+ // Text shadows that have a blur radius of 0 need to be rendered as normal
+ // text elements to get pixel perfect results for reftests. It's also a big
+ // performance win to avoid blurs and render target allocations where
+ // possible. For any text shadows that have zero blur, create a normal text
+ // primitive with the shadow's color and offset. These need to be added
+ // *before* the visual text primitive in order to get the correct paint
+ // order. Store them in a Vec first to work around borrowck issues.
+ // TODO(gw): Refactor to avoid having to store them in a Vec first.
+ let mut fast_shadow_prims = Vec::new();
+ for (idx, &(shadow_prim_index, _)) in self.shadow_prim_stack.iter().enumerate() {
+ let shadow_metadata = &self.prim_store.cpu_metadata[shadow_prim_index.0];
+ let picture_prim = &self.prim_store.cpu_pictures[shadow_metadata.cpu_prim_index.0];
+ match picture_prim.kind {
+ PictureKind::TextShadow { offset, color, blur_radius, .. } if blur_radius == 0.0 => {
+ let mut text_prim = prim.clone();
+ text_prim.font.color = color.into();
+ text_prim.shadow = true;
+ text_prim.offset += offset;
+ fast_shadow_prims.push((idx, text_prim));
+ }
+ _ => {}
+ }
+ }
+
+ for (idx, text_prim) in fast_shadow_prims {
+ let rect = info.rect;
+ let mut info = info.clone();
+ info.rect = rect.translate(&text_prim.offset);
+ info.local_clip =
+ LocalClip::from(info.local_clip.clip_rect().translate(&text_prim.offset));
+ let prim_index = self.create_primitive(
+ &info,
+ Vec::new(),
+ PrimitiveContainer::TextRun(text_prim),
+ );
+ self.shadow_prim_stack[idx].1.push((prim_index, clip_and_scroll));
+ }
+
+ // Create (and add to primitive store) the primitive that will be
+ // used for both the visual element and also the shadow(s).
+ let prim_index = self.create_primitive(
+ info,
+ Vec::new(),
+ PrimitiveContainer::TextRun(prim),
+ );
+
+ // Only add a visual element if it can contribute to the scene.
+ if text_color.a > 0.0 {
+ if self.shadow_prim_stack.is_empty() {
+ self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
+ self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
+ } else {
+ self.pending_shadow_contents.push((prim_index, clip_and_scroll, *info));
+ }
+ }
+
+ // Now add this primitive index to all the currently active text shadow
+ // primitives. Although we're adding the indices *after* the visual
+ // primitive here, they will still draw before the visual text, since
+ // the shadow primitive itself has been added to the draw cmd
+ // list *before* the visual element, during push_shadow. We need
+ // the primitive index of the visual element here before we can add
+ // the indices as sub-primitives to the shadow primitives.
+ for &(shadow_prim_index, _) in &self.shadow_prim_stack {
+ let shadow_metadata = &mut self.prim_store.cpu_metadata[shadow_prim_index.0];
+ debug_assert_eq!(shadow_metadata.prim_kind, PrimitiveKind::Picture);
+ let picture =
+ &mut self.prim_store.cpu_pictures[shadow_metadata.cpu_prim_index.0];
+
+ match picture.kind {
+ // Only run real blurs here (fast path zero blurs are handled above).
+ PictureKind::TextShadow { blur_radius, .. } if blur_radius > 0.0 => {
+ picture.add_primitive(
+ prim_index,
+ clip_and_scroll,
+ );
+ }
+ _ => {}
+ }
+ }
+ }
+
+ pub fn add_image(
+ &mut self,
+ clip_and_scroll: ScrollNodeAndClipChain,
+ info: &LayerPrimitiveInfo,
+ stretch_size: LayerSize,
+ mut tile_spacing: LayerSize,
+ sub_rect: Option<TexelRect>,
+ image_key: ImageKey,
+ image_rendering: ImageRendering,
+ alpha_type: AlphaType,
+ tile_offset: Option<TileOffset>,
+ ) {
+ // 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,
+ };
+
+ // 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 &&
+ sub_rect.is_none() &&
+ tile_offset.is_none() {
+ let prim = BrushPrimitive::new(
+ BrushKind::Image {
+ request,
+ current_epoch: Epoch::invalid(),
+ alpha_type,
+ },
+ None,
+ );
+
+ self.add_primitive(
+ clip_and_scroll,
+ info,
+ Vec::new(),
+ PrimitiveContainer::Brush(prim),
+ );
+ } else {
+ let prim_cpu = ImagePrimitiveCpu {
+ tile_spacing,
+ alpha_type,
+ stretch_size,
+ current_epoch: Epoch::invalid(),
+ source: ImageSource::Default,
+ key: ImageCacheKey {
+ request,
+ texel_rect: sub_rect.map(|texel_rect| {
+ DeviceIntRect::new(
+ DeviceIntPoint::new(
+ texel_rect.uv0.x as i32,
+ texel_rect.uv0.y as i32,
+ ),
+ DeviceIntSize::new(
+ (texel_rect.uv1.x - texel_rect.uv0.x) as i32,
+ (texel_rect.uv1.y - texel_rect.uv0.y) as i32,
+ ),
+ )
+ }),
+ },
+ };
+
+ self.add_primitive(
+ clip_and_scroll,
+ info,
+ Vec::new(),
+ PrimitiveContainer::Image(prim_cpu),
+ );
+ }
+ }
+
+ pub fn add_yuv_image(
+ &mut self,
+ clip_and_scroll: ScrollNodeAndClipChain,
+ info: &LayerPrimitiveInfo,
+ yuv_data: YuvData,
+ color_space: YuvColorSpace,
+ image_rendering: ImageRendering,
+ ) {
+ let format = yuv_data.get_format();
+ let yuv_key = match yuv_data {
+ YuvData::NV12(plane_0, plane_1) => [plane_0, plane_1, ImageKey::DUMMY],
+ YuvData::PlanarYCbCr(plane_0, plane_1, plane_2) => [plane_0, plane_1, plane_2],
+ YuvData::InterleavedYCbCr(plane_0) => [plane_0, ImageKey::DUMMY, ImageKey::DUMMY],
+ };
+
+ let prim = BrushPrimitive::new(
+ BrushKind::YuvImage {
+ yuv_key,
+ format,
+ color_space,
+ image_rendering,
+ },
+ None,
+ );
+
+ self.add_primitive(
+ clip_and_scroll,
+ info,
+ Vec::new(),
+ PrimitiveContainer::Brush(prim),
+ );
+ }
+}
+
+pub fn build_scene(config: &FrameBuilderConfig, request: SceneRequest) -> BuiltScene {
+ // TODO: mutably pass the scene and update its own pipeline epoch map instead of
+ // creating a new one here.
+ let mut pipeline_epoch_map = FastHashMap::default();
+ let mut clip_scroll_tree = ClipScrollTree::new();
+
+ let frame_builder = DisplayListFlattener::create_frame_builder(
+ FrameBuilder::empty(), // WIP, we're not really recycling anything here, clean this up.
+ &request.scene,
+ &mut clip_scroll_tree,
+ request.font_instances,
+ request.tiled_image_map,
+ &request.view,
+ &request.output_pipelines,
+ config,
+ &mut pipeline_epoch_map
+ );
+
+ let mut scene = request.scene;
+ scene.pipeline_epochs = pipeline_epoch_map;
+
+ BuiltScene {
+ scene,
+ frame_builder,
+ clip_scroll_tree,
+ removed_pipelines: request.removed_pipelines,
+ }
+}
+
+trait PrimitiveInfoTiler {
+ fn decompose(
+ &self,
+ tile_size: LayerSize,
+ tile_spacing: LayerSize,
+ max_prims: usize,
+ ) -> Vec<LayerPrimitiveInfo>;
+}
+
+impl PrimitiveInfoTiler for LayerPrimitiveInfo {
+ fn decompose(
+ &self,
+ tile_size: LayerSize,
+ tile_spacing: LayerSize,
+ max_prims: usize,
+ ) -> Vec<LayerPrimitiveInfo> {
+ let mut prims = Vec::new();
+ let tile_repeat = tile_size + tile_spacing;
+
+ if tile_repeat.width <= 0.0 ||
+ tile_repeat.height <= 0.0 {
+ return prims;
+ }
+
+ if tile_repeat.width < self.rect.size.width ||
+ tile_repeat.height < self.rect.size.height {
+ let local_clip = self.local_clip.clip_by(&self.rect);
+ let rect_p0 = self.rect.origin;
+ let rect_p1 = self.rect.bottom_right();
+
+ let mut y0 = rect_p0.y;
+ while y0 < rect_p1.y {
+ let mut x0 = rect_p0.x;
+
+ while x0 < rect_p1.x {
+ prims.push(LayerPrimitiveInfo {
+ rect: LayerRect::new(
+ LayerPoint::new(x0, y0),
+ tile_size,
+ ),
+ local_clip,
+ is_backface_visible: self.is_backface_visible,
+ tag: self.tag,
+ });
+
+ // Mostly a safety against a crazy number of primitives
+ // being generated. If we exceed that amount, just bail
+ // out and only draw the maximum amount.
+ if prims.len() > max_prims {
+ warn!("too many prims found due to repeat/tile. dropping extra prims!");
+ return prims;
+ }
+
+ x0 += tile_repeat.width;
+ }
+
+ y0 += tile_repeat.height;
+ }
+ }
+
+ prims
+ }
+}
+
+/// Properties of a stacking context that are maintained
+/// during creation of the scene. These structures are
+/// not persisted after the initial scene build.
+struct FlattenedStackingContext {
+ /// Pipeline this stacking context belongs to.
+ pipeline_id: PipelineId,
+
+ /// Filters / mix-blend-mode effects
+ composite_ops: CompositeOps,
+
+ /// If true, visible when backface is visible.
+ is_backface_visible: bool,
+
+ /// Allow subpixel AA for text runs on this stacking context.
+ /// This is a temporary hack while we don't support subpixel AA
+ /// on transparent stacking contexts.
+ allow_subpixel_aa: bool,
+
+ /// CSS transform-style property.
+ transform_style: TransformStyle,
+
+ /// If Some(..), this stacking context establishes a new
+ /// 3d rendering context, and the value is the primitive
+ // index of the 3d context container.
+ rendering_context_3d_prim_index: Option<PrimitiveIndex>,
+}
+
+#[derive(Debug)]
+pub struct ScrollbarInfo(pub ClipScrollNodeIndex, pub LayerRect);
deleted file mode 100644
--- a/gfx/webrender/src/frame.rs
+++ /dev/null
@@ -1,1210 +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/. */
-
-use api::{BuiltDisplayListIter, ClipId, ColorF, ComplexClipRegion, DevicePixelScale};
-use api::{DeviceUintRect, DeviceUintSize, DisplayItemRef, DocumentLayer, Epoch, ExternalScrollId};
-use api::{FilterOp, IframeDisplayItem, ImageDisplayItem, ItemRange, LayerPoint};
-use api::{LayerPrimitiveInfo, LayerRect, LayerSize, LayerVector2D, LayoutSize, PipelineId};
-use api::{ScrollClamping, ScrollEventPhase, ScrollFrameDisplayItem, ScrollLocation};
-use api::{ScrollNodeState, ScrollPolicy, ScrollSensitivity, SpecificDisplayItem, StackingContext};
-use api::{TileOffset, TransformStyle, WorldPoint};
-use clip::ClipRegion;
-use clip_scroll_node::StickyFrameInfo;
-use clip_scroll_tree::{ClipChainIndex, ClipScrollTree, ScrollStates};
-use euclid::rect;
-use frame_builder::{FrameBuilder, FrameBuilderConfig, ScrollbarInfo};
-use gpu_cache::GpuCache;
-use hit_test::HitTester;
-use internal_types::{FastHashMap, FastHashSet, RenderedDocument};
-use prim_store::ScrollNodeAndClipChain;
-use profiler::{GpuCacheProfileCounters, TextureCacheProfileCounters};
-use resource_cache::{FontInstanceMap,ResourceCache, TiledImageMap};
-use scene::{Scene, StackingContextHelpers, ScenePipeline, SceneProperties};
-use tiling::{CompositeOps, Frame};
-use renderer::PipelineInfo;
-
-#[derive(Copy, Clone, PartialEq, PartialOrd, Debug, Eq, Ord)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct FrameId(pub u32);
-
-static DEFAULT_SCROLLBAR_COLOR: ColorF = ColorF {
- r: 0.3,
- g: 0.3,
- b: 0.3,
- a: 0.6,
-};
-
-/// A data structure that keeps track of mapping between API clip ids and the indices
-/// used internally in the ClipScrollTree to avoid having to do HashMap lookups. This
-/// also includes a small LRU cache. Currently the cache is small (1 entry), but in the
-/// future we could use uluru here to do something more involved.
-pub struct ClipIdToIndexMapper {
- map: FastHashMap<ClipId, ClipChainIndex>,
- cached_index: Option<(ClipId, ClipChainIndex)>,
-}
-
-impl ClipIdToIndexMapper {
- fn new() -> ClipIdToIndexMapper {
- ClipIdToIndexMapper {
- map: FastHashMap::default(),
- cached_index: None,
- }
- }
-
- pub fn add(&mut self, id: ClipId, index: ClipChainIndex) {
- debug_assert!(!self.map.contains_key(&id));
- self.map.insert(id, index);
- }
-
- pub fn map_to_parent_clip_chain(&mut self, id: ClipId, parent_id: &ClipId) {
- let parent_chain_index = self.map_clip_id(parent_id);
- self.add(id, parent_chain_index);
- }
-
- pub fn map_clip_id(&mut self, id: &ClipId) -> ClipChainIndex {
- match self.cached_index {
- Some((cached_id, cached_index)) if cached_id == *id => return cached_index,
- _ => {}
- }
-
- self.map[id]
- }
-
- pub fn map_clip_id_and_cache_result(&mut self, id: &ClipId) -> ClipChainIndex {
- let index = self.map_clip_id(id);
- self.cached_index = Some((*id, index));
- index
- }
-
- pub fn simple_scroll_and_clip_chain(&mut self, id: &ClipId) -> ScrollNodeAndClipChain {
- ScrollNodeAndClipChain::new(*id, self.map_clip_id(&id))
- }
-}
-
-struct FlattenContext<'a> {
- scene: &'a Scene,
- builder: FrameBuilder,
- clip_scroll_tree: &'a mut ClipScrollTree,
- font_instances: FontInstanceMap,
- tiled_image_map: TiledImageMap,
- pipeline_epochs: Vec<(PipelineId, Epoch)>,
- replacements: Vec<(ClipId, ClipId)>,
- output_pipelines: &'a FastHashSet<PipelineId>,
- id_to_index_mapper: ClipIdToIndexMapper,
-}
-
-impl<'a> FlattenContext<'a> {
- /// Since WebRender still handles fixed position and reference frame content internally
- /// we need to apply this table of id replacements only to the id that affects the
- /// position of a node. We can eventually remove this when clients start handling
- /// reference frames themselves. This method applies these replacements.
- fn apply_scroll_frame_id_replacement(&self, id: ClipId) -> ClipId {
- match self.replacements.last() {
- Some(&(to_replace, replacement)) if to_replace == id => replacement,
- _ => id,
- }
- }
-
- fn get_complex_clips(
- &self,
- pipeline_id: PipelineId,
- complex_clips: ItemRange<ComplexClipRegion>,
- ) -> Vec<ComplexClipRegion> {
- if complex_clips.is_empty() {
- return vec![];
- }
-
- self.scene
- .pipelines
- .get(&pipeline_id)
- .expect("No display list?")
- .display_list
- .get(complex_clips)
- .collect()
- }
-
- fn get_clip_chain_items(
- &self,
- pipeline_id: PipelineId,
- items: ItemRange<ClipId>,
- ) -> Vec<ClipId> {
- if items.is_empty() {
- return vec![];
- }
-
- self.scene
- .pipelines
- .get(&pipeline_id)
- .expect("No display list?")
- .display_list
- .get(items)
- .collect()
- }
-
- fn flatten_root(
- &mut self,
- traversal: &mut BuiltDisplayListIter<'a>,
- pipeline_id: PipelineId,
- frame_size: &LayoutSize,
- ) {
- let root_reference_frame_id = ClipId::root_reference_frame(pipeline_id);
- let root_scroll_frame_id = ClipId::root_scroll_node(pipeline_id);
-
- let root_clip_chain_index =
- self.id_to_index_mapper.map_clip_id_and_cache_result(&root_reference_frame_id);
- let root_reference_frame_clip_and_scroll = ScrollNodeAndClipChain::new(
- root_reference_frame_id,
- root_clip_chain_index,
- );
-
- self.builder.push_stacking_context(
- pipeline_id,
- CompositeOps::default(),
- TransformStyle::Flat,
- true,
- true,
- ScrollNodeAndClipChain::new(
- ClipId::root_scroll_node(pipeline_id),
- root_clip_chain_index,
- ),
- self.output_pipelines,
- );
-
- // For the root pipeline, there's no need to add a full screen rectangle
- // here, as it's handled by the framebuffer clear.
- if self.scene.root_pipeline_id != Some(pipeline_id) {
- if let Some(pipeline) = self.scene.pipelines.get(&pipeline_id) {
- if let Some(bg_color) = pipeline.background_color {
- let root_bounds = LayerRect::new(LayerPoint::zero(), *frame_size);
- let info = LayerPrimitiveInfo::new(root_bounds);
- self.builder.add_solid_rectangle(
- root_reference_frame_clip_and_scroll,
- &info,
- bg_color,
- None,
- );
- }
- }
- }
-
-
- self.flatten_items(
- traversal,
- pipeline_id,
- LayerVector2D::zero(),
- );
-
- if self.builder.config.enable_scrollbars {
- let scrollbar_rect = LayerRect::new(LayerPoint::zero(), LayerSize::new(10.0, 70.0));
- let container_rect = LayerRect::new(LayerPoint::zero(), *frame_size);
- self.builder.add_scroll_bar(
- root_reference_frame_clip_and_scroll,
- &LayerPrimitiveInfo::new(scrollbar_rect),
- DEFAULT_SCROLLBAR_COLOR,
- ScrollbarInfo(root_scroll_frame_id, container_rect),
- );
- }
-
- self.builder.pop_stacking_context();
- }
-
- fn flatten_items(
- &mut self,
- traversal: &mut BuiltDisplayListIter<'a>,
- pipeline_id: PipelineId,
- reference_frame_relative_offset: LayerVector2D,
- ) {
- loop {
- let subtraversal = {
- let item = match traversal.next() {
- Some(item) => item,
- None => break,
- };
-
- if SpecificDisplayItem::PopStackingContext == *item.item() {
- return;
- }
-
- self.flatten_item(
- item,
- pipeline_id,
- reference_frame_relative_offset,
- )
- };
-
- // If flatten_item created a sub-traversal, we need `traversal` to have the
- // same state as the completed subtraversal, so we reinitialize it here.
- if let Some(subtraversal) = subtraversal {
- *traversal = subtraversal;
- }
- }
- }
-
- fn flatten_clip(&mut self, parent_id: &ClipId, new_clip_id: &ClipId, clip_region: ClipRegion) {
- self.builder.add_clip_node(
- *new_clip_id,
- *parent_id,
- clip_region,
- self.clip_scroll_tree,
- &mut self.id_to_index_mapper,
- );
- }
-
- fn flatten_scroll_frame(
- &mut self,
- item: &DisplayItemRef,
- info: &ScrollFrameDisplayItem,
- pipeline_id: PipelineId,
- clip_and_scroll: &ScrollNodeAndClipChain,
- reference_frame_relative_offset: &LayerVector2D,
- ) {
- let complex_clips = self.get_complex_clips(pipeline_id, item.complex_clip().0);
- let clip_region = ClipRegion::create_for_clip_node(
- *item.local_clip().clip_rect(),
- complex_clips,
- info.image_mask,
- &reference_frame_relative_offset,
- );
- // Just use clip rectangle as the frame rect for this scroll frame.
- // This is useful when calculating scroll extents for the
- // ClipScrollNode::scroll(..) API as well as for properly setting sticky
- // positioning offsets.
- let frame_rect = item.local_clip()
- .clip_rect()
- .translate(&reference_frame_relative_offset);
- let content_rect = item.rect().translate(&reference_frame_relative_offset);
-
- debug_assert!(info.clip_id != info.scroll_frame_id);
-
- self.builder.add_clip_node(
- info.clip_id,
- clip_and_scroll.scroll_node_id,
- clip_region,
- self.clip_scroll_tree,
- &mut self.id_to_index_mapper,
- );
-
- self.builder.add_scroll_frame(
- info.scroll_frame_id,
- info.clip_id,
- info.external_id,
- pipeline_id,
- &frame_rect,
- &content_rect.size,
- info.scroll_sensitivity,
- self.clip_scroll_tree,
- &mut self.id_to_index_mapper,
- );
- }
-
- fn flatten_stacking_context(
- &mut self,
- traversal: &mut BuiltDisplayListIter<'a>,
- pipeline_id: PipelineId,
- unreplaced_scroll_id: ClipId,
- clip_and_scroll: ScrollNodeAndClipChain,
- mut reference_frame_relative_offset: LayerVector2D,
- bounds: &LayerRect,
- stacking_context: &StackingContext,
- filters: ItemRange<FilterOp>,
- is_backface_visible: bool,
- ) {
- // Avoid doing unnecessary work for empty stacking contexts.
- if traversal.current_stacking_context_empty() {
- traversal.skip_current_stacking_context();
- return;
- }
-
- let composition_operations = {
- // TODO(optimization?): self.traversal.display_list()
- let display_list = &self
- .scene
- .pipelines
- .get(&pipeline_id)
- .expect("No display list?!")
- .display_list;
- CompositeOps::new(
- stacking_context.filter_ops_for_compositing(display_list, filters),
- stacking_context.mix_blend_mode_for_compositing(),
- )
- };
-
- if stacking_context.scroll_policy == ScrollPolicy::Fixed {
- self.replacements.push((
- unreplaced_scroll_id,
- self.builder.current_reference_frame_id(),
- ));
- }
-
- reference_frame_relative_offset += bounds.origin.to_vector();
-
- // If we have a transformation or a perspective, we should have been assigned a new
- // reference frame id. This means this stacking context establishes a new reference frame.
- // Descendant fixed position content will be positioned relative to us.
- if let Some(reference_frame_id) = stacking_context.reference_frame_id {
- debug_assert!(
- stacking_context.transform.is_some() ||
- stacking_context.perspective.is_some()
- );
-
- let reference_frame_bounds = LayerRect::new(LayerPoint::zero(), bounds.size);
- self.builder.push_reference_frame(
- reference_frame_id,
- Some(clip_and_scroll.scroll_node_id),
- pipeline_id,
- &reference_frame_bounds,
- stacking_context.transform,
- stacking_context.perspective,
- reference_frame_relative_offset,
- self.clip_scroll_tree,
- &mut self.id_to_index_mapper,
- );
- self.replacements.push((unreplaced_scroll_id, reference_frame_id));
- reference_frame_relative_offset = LayerVector2D::zero();
- }
-
- // We apply the replacements one more time in case we need to set it to a replacement
- // that we just pushed above.
- let new_scroll_node = self.apply_scroll_frame_id_replacement(unreplaced_scroll_id);
- let stacking_context_clip_and_scroll =
- self.id_to_index_mapper.simple_scroll_and_clip_chain(&new_scroll_node);
- self.builder.push_stacking_context(
- pipeline_id,
- composition_operations,
- stacking_context.transform_style,
- is_backface_visible,
- false,
- stacking_context_clip_and_scroll,
- self.output_pipelines,
- );
-
- self.flatten_items(
- traversal,
- pipeline_id,
- reference_frame_relative_offset,
- );
-
- if stacking_context.scroll_policy == ScrollPolicy::Fixed {
- self.replacements.pop();
- }
-
- if stacking_context.reference_frame_id.is_some() {
- self.replacements.pop();
- self.builder.pop_reference_frame();
- }
-
- self.builder.pop_stacking_context();
- }
-
- fn flatten_iframe(
- &mut self,
- item: &DisplayItemRef,
- info: &IframeDisplayItem,
- clip_and_scroll: &ScrollNodeAndClipChain,
- reference_frame_relative_offset: &LayerVector2D,
- ) {
- let iframe_pipeline_id = info.pipeline_id;
- let pipeline = match self.scene.pipelines.get(&iframe_pipeline_id) {
- Some(pipeline) => pipeline,
- None => return,
- };
-
- self.builder.add_clip_node(
- info.clip_id,
- clip_and_scroll.scroll_node_id,
- ClipRegion::create_for_clip_node_with_local_clip(
- &item.local_clip(),
- &reference_frame_relative_offset
- ),
- self.clip_scroll_tree,
- &mut self.id_to_index_mapper,
- );
-
- self.pipeline_epochs.push((iframe_pipeline_id, pipeline.epoch));
-
- let bounds = item.rect();
- let iframe_rect = LayerRect::new(LayerPoint::zero(), bounds.size);
- let origin = *reference_frame_relative_offset + bounds.origin.to_vector();
- self.builder.push_reference_frame(
- ClipId::root_reference_frame(iframe_pipeline_id),
- Some(info.clip_id),
- iframe_pipeline_id,
- &iframe_rect,
- None,
- None,
- origin,
- self.clip_scroll_tree,
- &mut self.id_to_index_mapper,
- );
-
- self.builder.add_scroll_frame(
- ClipId::root_scroll_node(iframe_pipeline_id),
- ClipId::root_reference_frame(iframe_pipeline_id),
- Some(ExternalScrollId(0, iframe_pipeline_id)),
- iframe_pipeline_id,
- &iframe_rect,
- &pipeline.content_size,
- ScrollSensitivity::ScriptAndInputEvents,
- self.clip_scroll_tree,
- &mut self.id_to_index_mapper,
- );
-
- self.flatten_root(&mut pipeline.display_list.iter(), iframe_pipeline_id, &iframe_rect.size);
-
- self.builder.pop_reference_frame();
- }
-
- fn flatten_item<'b>(
- &'b mut self,
- item: DisplayItemRef<'a, 'b>,
- pipeline_id: PipelineId,
- reference_frame_relative_offset: LayerVector2D,
- ) -> Option<BuiltDisplayListIter<'a>> {
- let clip_and_scroll = item.clip_and_scroll();
- let mut clip_and_scroll = ScrollNodeAndClipChain::new(
- clip_and_scroll.scroll_node_id,
- self.id_to_index_mapper.map_clip_id_and_cache_result(&clip_and_scroll.clip_node_id()),
- );
-
- let unreplaced_scroll_id = clip_and_scroll.scroll_node_id;
- clip_and_scroll.scroll_node_id =
- self.apply_scroll_frame_id_replacement(clip_and_scroll.scroll_node_id);
-
- 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.
- self.decompose_image(
- clip_and_scroll,
- &prim_info,
- info,
- tiling.image_size,
- tiling.tile_size as u32,
- );
- }
- None => {
- self.builder.add_image(
- clip_and_scroll,
- &prim_info,
- info.stretch_size,
- info.tile_spacing,
- None,
- info.image_key,
- info.image_rendering,
- info.alpha_type,
- None,
- );
- }
- }
- }
- SpecificDisplayItem::YuvImage(ref info) => {
- self.builder.add_yuv_image(
- clip_and_scroll,
- &prim_info,
- info.yuv_data,
- info.color_space,
- info.image_rendering,
- );
- }
- SpecificDisplayItem::Text(ref text_info) => {
- let instance_map = self.font_instances
- .read()
- .unwrap();
- match instance_map.get(&text_info.font_key) {
- Some(instance) => {
- self.builder.add_text(
- clip_and_scroll,
- reference_frame_relative_offset,
- &prim_info,
- instance,
- &text_info.color,
- item.glyphs(),
- item.display_list().get(item.glyphs()).count(),
- text_info.glyph_options,
- );
- }
- None => {
- warn!("Unknown font instance key");
- debug!("key={:?}", text_info.font_key);
- }
- }
- }
- SpecificDisplayItem::Rectangle(ref info) => {
- self.builder.add_solid_rectangle(
- clip_and_scroll,
- &prim_info,
- info.color,
- None,
- );
- }
- SpecificDisplayItem::ClearRectangle => {
- self.builder.add_clear_rectangle(
- clip_and_scroll,
- &prim_info,
- );
- }
- SpecificDisplayItem::Line(ref info) => {
- self.builder.add_line(
- clip_and_scroll,
- &prim_info,
- info.wavy_line_thickness,
- info.orientation,
- &info.color,
- info.style,
- );
- }
- SpecificDisplayItem::Gradient(ref info) => {
- self.builder.add_gradient(
- clip_and_scroll,
- &prim_info,
- info.gradient.start_point,
- info.gradient.end_point,
- item.gradient_stops(),
- item.display_list().get(item.gradient_stops()).count(),
- info.gradient.extend_mode,
- info.tile_size,
- info.tile_spacing,
- );
- }
- SpecificDisplayItem::RadialGradient(ref info) => {
- self.builder.add_radial_gradient(
- clip_and_scroll,
- &prim_info,
- info.gradient.start_center,
- info.gradient.start_radius,
- info.gradient.end_center,
- info.gradient.end_radius,
- info.gradient.ratio_xy,
- item.gradient_stops(),
- info.gradient.extend_mode,
- info.tile_size,
- info.tile_spacing,
- );
- }
- SpecificDisplayItem::BoxShadow(ref box_shadow_info) => {
- let bounds = box_shadow_info
- .box_bounds
- .translate(&reference_frame_relative_offset);
- let mut prim_info = prim_info.clone();
- prim_info.rect = bounds;
- self.builder.add_box_shadow(
- pipeline_id,
- clip_and_scroll,
- &prim_info,
- &box_shadow_info.offset,
- &box_shadow_info.color,
- box_shadow_info.blur_radius,
- box_shadow_info.spread_radius,
- box_shadow_info.border_radius,
- box_shadow_info.clip_mode,
- );
- }
- SpecificDisplayItem::Border(ref info) => {
- self.builder.add_border(
- clip_and_scroll,
- &prim_info,
- info,
- item.gradient_stops(),
- item.display_list().get(item.gradient_stops()).count(),
- );
- }
- SpecificDisplayItem::PushStackingContext(ref info) => {
- let mut subtraversal = item.sub_iter();
- self.flatten_stacking_context(
- &mut subtraversal,
- pipeline_id,
- unreplaced_scroll_id,
- clip_and_scroll,
- reference_frame_relative_offset,
- &item.rect(),
- &info.stacking_context,
- item.filters(),
- prim_info.is_backface_visible,
- );
- return Some(subtraversal);
- }
- SpecificDisplayItem::Iframe(ref info) => {
- self.flatten_iframe(
- &item,
- info,
- &clip_and_scroll,
- &reference_frame_relative_offset
- );
- }
- SpecificDisplayItem::Clip(ref info) => {
- let complex_clips = self.get_complex_clips(pipeline_id, item.complex_clip().0);
- let clip_region = ClipRegion::create_for_clip_node(
- *item.local_clip().clip_rect(),
- complex_clips,
- info.image_mask,
- &reference_frame_relative_offset,
- );
- self.flatten_clip(&clip_and_scroll.scroll_node_id, &info.id, clip_region);
- }
- SpecificDisplayItem::ClipChain(ref info) => {
- let items = self.get_clip_chain_items(pipeline_id, item.clip_chain_items());
- let parent = info.parent.map(|id|
- self.id_to_index_mapper.map_clip_id(&ClipId::ClipChain(id))
- );
- let clip_chain_index =
- self.clip_scroll_tree.add_clip_chain_descriptor(parent, items);
- self.id_to_index_mapper.add(ClipId::ClipChain(info.id), clip_chain_index);
- },
- SpecificDisplayItem::ScrollFrame(ref info) => {
- self.flatten_scroll_frame(
- &item,
- info,
- pipeline_id,
- &clip_and_scroll,
- &reference_frame_relative_offset
- );
- }
- SpecificDisplayItem::StickyFrame(ref info) => {
- let frame_rect = item.rect().translate(&reference_frame_relative_offset);
- let sticky_frame_info = StickyFrameInfo::new(
- info.margins,
- info.vertical_offset_bounds,
- info.horizontal_offset_bounds,
- info.previously_applied_offset,
- );
- let parent_id = clip_and_scroll.scroll_node_id;
- self.clip_scroll_tree.add_sticky_frame(
- info.id,
- parent_id,
- frame_rect,
- sticky_frame_info
- );
- self.id_to_index_mapper.map_to_parent_clip_chain(info.id, &parent_id);
- }
-
- // Do nothing; these are dummy items for the display list parser
- SpecificDisplayItem::SetGradientStops => {}
-
- SpecificDisplayItem::PopStackingContext => {
- unreachable!("Should have returned in parent method.")
- }
- SpecificDisplayItem::PushShadow(shadow) => {
- let mut prim_info = prim_info.clone();
- prim_info.rect = LayerRect::zero();
- self.builder
- .push_shadow(shadow, clip_and_scroll, &prim_info);
- }
- SpecificDisplayItem::PopAllShadows => {
- self.builder.pop_all_shadows();
- }
- }
- None
- }
-
- /// Decomposes an image display item 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,
- /// decompositing 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_image_row handle image repetitions while decompose_tiled_image
- /// takes care of the decomposition required by the internal tiling of the image.
- fn decompose_image(
- &mut self,
- clip_and_scroll: ScrollNodeAndClipChain,
- prim_info: &LayerPrimitiveInfo,
- info: &ImageDisplayItem,
- image_size: DeviceUintSize,
- tile_size: u32,
- ) {
- let no_vertical_tiling = image_size.height <= tile_size;
- let no_vertical_spacing = info.tile_spacing.height == 0.0;
- let item_rect = prim_info.rect;
- if no_vertical_tiling && no_vertical_spacing {
- self.decompose_image_row(
- clip_and_scroll,
- prim_info,
- info,
- image_size,
- tile_size,
- );
- return;
- }
-
- // Decompose each vertical repetition into rows.
- let layout_stride = info.stretch_size.height + info.tile_spacing.height;
- let num_repetitions = (item_rect.size.height / layout_stride).ceil() as u32;
- for i in 0 .. num_repetitions {
- if let Some(row_rect) = rect(
- item_rect.origin.x,
- item_rect.origin.y + (i as f32) * layout_stride,
- item_rect.size.width,
- info.stretch_size.height,
- ).intersection(&item_rect)
- {
- let mut prim_info = prim_info.clone();
- prim_info.rect = row_rect;
- self.decompose_image_row(
- clip_and_scroll,
- &prim_info,
- info,
- image_size,
- tile_size,
- );
- }
- }
- }
-
- fn decompose_image_row(
- &mut self,
- clip_and_scroll: ScrollNodeAndClipChain,
- prim_info: &LayerPrimitiveInfo,
- info: &ImageDisplayItem,
- image_size: DeviceUintSize,
- tile_size: u32,
- ) {
- let no_horizontal_tiling = image_size.width <= tile_size;
- let no_horizontal_spacing = info.tile_spacing.width == 0.0;
- if no_horizontal_tiling && no_horizontal_spacing {
- self.decompose_tiled_image(
- clip_and_scroll,
- prim_info,
- info,
- image_size,
- tile_size,
- );
- return;
- }
-
- // Decompose each horizontal repetition.
- let item_rect = prim_info.rect;
- let layout_stride = info.stretch_size.width + info.tile_spacing.width;
- let num_repetitions = (item_rect.size.width / layout_stride).ceil() as u32;
- for i in 0 .. num_repetitions {
- if let Some(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)
- {
- let mut prim_info = prim_info.clone();
- prim_info.rect = decomposed_rect;
- self.decompose_tiled_image(
- clip_and_scroll,
- &prim_info,
- info,
- image_size,
- tile_size,
- );
- }
- }
- }
-
- fn decompose_tiled_image(
- &mut self,
- clip_and_scroll: ScrollNodeAndClipChain,
- prim_info: &LayerPrimitiveInfo,
- info: &ImageDisplayItem,
- image_size: DeviceUintSize,
- tile_size: u32,
- ) {
- // The image resource is tiled. We have to generate an image primitive
- // for each tile.
- // We need to do this because the image is broken up into smaller tiles in the texture
- // cache and the image shader is not able to work with this type of sparse representation.
-
- // The tiling logic works as follows:
- //
- // ###################-+ -+
- // # | | |//# | | image size
- // # | | |//# | |
- // #----+----+----+--#-+ | -+
- // # | | |//# | | | regular tile size
- // # | | |//# | | |
- // #----+----+----+--#-+ | -+-+
- // #////|////|////|//# | | | "leftover" height
- // ################### | -+ ---+
- // #----+----+----+----+
- //
- // In the ascii diagram above, a large image is plit 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
- // 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 item_rect = prim_info.rect;
- let needs_repeat_x = info.stretch_size.width < item_rect.size.width;
- let needs_repeat_y = info.stretch_size.height < item_rect.size.height;
-
- let tiled_in_x = image_size.width > tile_size;
- let tiled_in_y = image_size.height > tile_size;
-
- // 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 tile_size_f32 = tile_size 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 = (image_size.width / tile_size) as u16;
- let num_tiles_y = (image_size.height / tile_size) as u16;
-
- // Ratio between (image space) tile size and image size.
- let img_dw = tile_size_f32 / (image_size.width as f32);
- let img_dh = tile_size_f32 / (image_size.height as f32);
-
- // Strected 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,
- );
-
- // 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(image_size.width % tile_size, image_size.height % tile_size);
-
- for ty in 0 .. num_tiles_y {
- for tx in 0 .. num_tiles_x {
- self.add_tile_primitive(
- clip_and_scroll,
- prim_info,
- info,
- TileOffset::new(tx, ty),
- stretched_tile_size,
- 1.0,
- 1.0,
- shader_repeat_x,
- shader_repeat_y,
- );
- }
- if leftover.width != 0 {
- // Tiles on the right edge that are smaller than the tile size.
- self.add_tile_primitive(
- clip_and_scroll,
- prim_info,
- info,
- TileOffset::new(num_tiles_x, ty),
- stretched_tile_size,
- (leftover.width as f32) / tile_size_f32,
- 1.0,
- shader_repeat_x,
- shader_repeat_y,
- );
- }
- }
-
- if leftover.height != 0 {
- for tx in 0 .. num_tiles_x {
- // Tiles on the bottom edge that are smaller than the tile size.
- self.add_tile_primitive(
- clip_and_scroll,
- prim_info,
- info,
- TileOffset::new(tx, num_tiles_y),
- stretched_tile_size,
- 1.0,
- (leftover.height as f32) / tile_size_f32,
- shader_repeat_x,
- shader_repeat_y,
- );
- }
-
- if leftover.width != 0 {
- // Finally, the bottom-right tile with a "leftover" size.
- self.add_tile_primitive(
- clip_and_scroll,
- prim_info,
- info,
- TileOffset::new(num_tiles_x, num_tiles_y),
- stretched_tile_size,
- (leftover.width as f32) / tile_size_f32,
- (leftover.height as f32) / tile_size_f32,
- shader_repeat_x,
- shader_repeat_y,
- );
- }
- }
- }
-
- fn add_tile_primitive(
- &mut self,
- clip_and_scroll: ScrollNodeAndClipChain,
- prim_info: &LayerPrimitiveInfo,
- info: &ImageDisplayItem,
- tile_offset: TileOffset,
- stretched_tile_size: LayerSize,
- tile_ratio_width: f32,
- tile_ratio_height: f32,
- shader_repeat_x: bool,
- shader_repeat_y: bool,
- ) {
- // If the 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 repetion (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 stretched_size = LayerSize::new(
- stretched_tile_size.width * tile_ratio_width,
- stretched_tile_size.height * tile_ratio_height,
- );
-
- let mut prim_rect = LayerRect::new(
- prim_info.rect.origin +
- LayerVector2D::new(
- tile_offset.x as f32 * stretched_tile_size.width,
- tile_offset.y as f32 * stretched_tile_size.height,
- ),
- stretched_size,
- );
-
- if shader_repeat_x {
- assert_eq!(tile_offset.x, 0);
- prim_rect.size.width = prim_info.rect.size.width;
- }
-
- if shader_repeat_y {
- assert_eq!(tile_offset.y, 0);
- prim_rect.size.height = prim_info.rect.size.height;
- }
-
- // Fix up the primitive's rect if it overflows the original item rect.
- if let Some(prim_rect) = prim_rect.intersection(&prim_info.rect) {
- let mut prim_info = prim_info.clone();
- prim_info.rect = prim_rect;
- self.builder.add_image(
- clip_and_scroll,
- &prim_info,
- stretched_size,
- info.tile_spacing,
- None,
- info.image_key,
- info.image_rendering,
- info.alpha_type,
- Some(tile_offset),
- );
- }
- }
-}
-
-/// Frame context contains the information required to update
-/// (e.g. scroll) a renderer frame builder (`FrameBuilder`).
-pub struct FrameContext {
- window_size: DeviceUintSize,
- clip_scroll_tree: ClipScrollTree,
- pipeline_epoch_map: FastHashMap<PipelineId, Epoch>,
- id: FrameId,
- pub frame_builder_config: FrameBuilderConfig,
-}
-
-impl FrameContext {
- pub fn new(config: FrameBuilderConfig) -> Self {
- FrameContext {
- window_size: DeviceUintSize::zero(),
- pipeline_epoch_map: FastHashMap::default(),
- clip_scroll_tree: ClipScrollTree::new(),
- id: FrameId(0),
- frame_builder_config: config,
- }
- }
-
- pub fn reset(&mut self) -> ScrollStates {
- self.pipeline_epoch_map.clear();
-
- // Advance to the next frame.
- self.id.0 += 1;
-
- self.clip_scroll_tree.drain()
- }
-
- #[cfg(feature = "debugger")]
- pub fn get_clip_scroll_tree(&self) -> &ClipScrollTree {
- &self.clip_scroll_tree
- }
-
- pub fn get_scroll_node_state(&self) -> Vec<ScrollNodeState> {
- self.clip_scroll_tree.get_scroll_node_state()
- }
-
- /// Returns true if the node actually changed position or false otherwise.
- pub fn scroll_node(
- &mut self,
- origin: LayerPoint,
- id: ExternalScrollId,
- clamp: ScrollClamping
- ) -> bool {
- self.clip_scroll_tree.scroll_node(origin, id, clamp)
- }
-
- /// Returns true if any nodes actually changed position or false otherwise.
- pub fn scroll(
- &mut self,
- scroll_location: ScrollLocation,
- cursor: WorldPoint,
- phase: ScrollEventPhase,
- ) -> bool {
- self.clip_scroll_tree.scroll(scroll_location, cursor, phase)
- }
-
- pub fn tick_scrolling_bounce_animations(&mut self) {
- self.clip_scroll_tree.tick_scrolling_bounce_animations();
- }
-
- pub fn discard_frame_state_for_pipeline(&mut self, pipeline_id: PipelineId) {
- self.clip_scroll_tree
- .discard_frame_state_for_pipeline(pipeline_id);
- }
-
- pub fn create_frame_builder(
- &mut self,
- old_builder: FrameBuilder,
- scene: &Scene,
- resource_cache: &mut ResourceCache,
- window_size: DeviceUintSize,
- inner_rect: DeviceUintRect,
- device_pixel_scale: DevicePixelScale,
- output_pipelines: &FastHashSet<PipelineId>,
- ) -> FrameBuilder {
- let root_pipeline_id = match scene.root_pipeline_id {
- Some(root_pipeline_id) => root_pipeline_id,
- None => return old_builder,
- };
-
- let root_pipeline = match scene.pipelines.get(&root_pipeline_id) {
- Some(root_pipeline) => root_pipeline,
- None => return old_builder,
- };
-
- if window_size.width == 0 || window_size.height == 0 {
- error!("ERROR: Invalid window dimensions! Please call api.set_window_size()");
- }
- self.window_size = window_size;
-
- let old_scrolling_states = self.reset();
-
- self.pipeline_epoch_map
- .insert(root_pipeline_id, root_pipeline.epoch);
-
- let background_color = root_pipeline
- .background_color
- .and_then(|color| if color.a > 0.0 { Some(color) } else { None });
-
- let frame_builder = {
- let mut roller = FlattenContext {
- scene,
- builder: old_builder.recycle(
- inner_rect,
- background_color,
- self.frame_builder_config,
- ),
- clip_scroll_tree: &mut self.clip_scroll_tree,
- font_instances: resource_cache.get_font_instances(),
- tiled_image_map: resource_cache.get_tiled_image_map(),
- pipeline_epochs: Vec::new(),
- replacements: Vec::new(),
- output_pipelines,
- id_to_index_mapper: ClipIdToIndexMapper::new(),
- };
-
- roller.builder.push_root(
- root_pipeline_id,
- &root_pipeline.viewport_size,
- &root_pipeline.content_size,
- roller.clip_scroll_tree,
- &mut roller.id_to_index_mapper,
- );
-
- roller.builder.setup_viewport_offset(
- inner_rect,
- device_pixel_scale,
- roller.clip_scroll_tree,
- );
-
- roller.flatten_root(
- &mut root_pipeline.display_list.iter(),
- root_pipeline_id,
- &root_pipeline.viewport_size,
- );
-
- debug_assert!(roller.builder.picture_stack.is_empty());
-
- self.pipeline_epoch_map.extend(roller.pipeline_epochs.drain(..));
- roller.builder
- };
-
- self.clip_scroll_tree
- .finalize_and_apply_pending_scroll_offsets(old_scrolling_states);
-
- frame_builder
- }
-
- pub fn update_epoch(&mut self, pipeline_id: PipelineId, epoch: Epoch) {
- self.pipeline_epoch_map.insert(pipeline_id, epoch);
- }
-
- pub fn make_rendered_document(&mut self, frame: Frame, removed_pipelines: Vec<PipelineId>) -> RenderedDocument {
- let nodes_bouncing_back = self.clip_scroll_tree.collect_nodes_bouncing_back();
- RenderedDocument::new(
- PipelineInfo {
- epochs: self.pipeline_epoch_map.clone(),
- removed_pipelines,
- },
- nodes_bouncing_back,
- frame
- )
- }
-
- //TODO: this can probably be simplified if `build()` is called directly by RB.
- // The only things it needs from the frame context is the CST and frame ID.
- pub fn build_rendered_document(
- &mut self,
- frame_builder: &mut FrameBuilder,
- resource_cache: &mut ResourceCache,
- gpu_cache: &mut GpuCache,
- pipelines: &FastHashMap<PipelineId, ScenePipeline>,
- device_pixel_scale: DevicePixelScale,
- layer: DocumentLayer,
- pan: WorldPoint,
- texture_cache_profile: &mut TextureCacheProfileCounters,
- gpu_cache_profile: &mut GpuCacheProfileCounters,
- scene_properties: &SceneProperties,
- removed_pipelines: Vec<PipelineId>,
- ) -> (HitTester, RenderedDocument) {
- let frame = frame_builder.build(
- resource_cache,
- gpu_cache,
- self.id,
- &mut self.clip_scroll_tree,
- pipelines,
- self.window_size,
- device_pixel_scale,
- layer,
- pan,
- texture_cache_profile,
- gpu_cache_profile,
- scene_properties,
- );
-
- let hit_tester = frame_builder.create_hit_tester(&self.clip_scroll_tree);
-
- (hit_tester, self.make_rendered_document(frame, removed_pipelines))
- }
-}
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -1,142 +1,84 @@
/* 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/. */
-use api::{AlphaType, BorderDetails, BorderDisplayItem, BuiltDisplayList, ClipId, ColorF};
-use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixelScale, DeviceUintPoint};
-use api::{DeviceUintRect, DeviceUintSize, DocumentLayer, Epoch, ExtendMode, ExternalScrollId};
-use api::{FontRenderMode, GlyphInstance, GlyphOptions, GradientStop, ImageKey, ImageRendering};
-use api::{ItemRange, LayerPoint, LayerPrimitiveInfo, LayerRect, LayerSize, LayerVector2D};
-use api::{LayoutTransform, LayoutVector2D, LineOrientation, LineStyle, LocalClip, PipelineId};
-use api::{PremultipliedColorF, PropertyBinding, RepeatMode, ScrollSensitivity, Shadow, TexelRect};
-use api::{TileOffset, TransformStyle, WorldPoint, YuvColorSpace, YuvData};
-use app_units::Au;
-use border::ImageBorderSegment;
-use clip::{ClipChain, ClipRegion, ClipSource, ClipSources, ClipStore};
-use clip_scroll_node::{ClipScrollNode, NodeType};
-use clip_scroll_tree::{ClipScrollTree, ClipChainIndex};
-use euclid::{SideOffsets2D, vec2};
-use frame::{FrameId, ClipIdToIndexMapper};
-use glyph_rasterizer::FontInstance;
+use api::{BuiltDisplayList, ColorF, DeviceIntPoint, DeviceIntRect, DevicePixelScale};
+use api::{DeviceUintPoint, DeviceUintRect, DeviceUintSize, DocumentLayer, FontRenderMode};
+use api::{LayerRect, LayerSize, PipelineId, PremultipliedColorF, WorldPoint};
+use clip::{ClipChain, ClipStore};
+use clip_scroll_node::{ClipScrollNode};
+use clip_scroll_tree::{ClipScrollNodeIndex, ClipScrollTree};
+use display_list_flattener::{DisplayListFlattener};
use gpu_cache::GpuCache;
use gpu_types::{ClipChainRectIndex, ClipScrollNodeData, PictureType};
-use hit_test::{HitTester, HitTestingItem, HitTestingRun};
-use internal_types::{FastHashMap, FastHashSet};
-use picture::{ContentOrigin, PictureCompositeMode, PictureKind, PicturePrimitive, PictureSurface};
-use prim_store::{BrushKind, BrushPrimitive, BrushSegmentDescriptor, CachedGradient, CachedGradientIndex};
-use prim_store::{ImageCacheKey, ImagePrimitiveCpu, ImageSource, PrimitiveContainer};
-use prim_store::{PrimitiveIndex, PrimitiveKind, PrimitiveRun, PrimitiveStore};
-use prim_store::{ScrollNodeAndClipChain, TextRunPrimitiveCpu};
+use hit_test::{HitTester, HitTestingRun};
+use internal_types::{FastHashMap};
+use picture::{ContentOrigin, PictureSurface};
+use prim_store::{CachedGradient, PrimitiveIndex, PrimitiveRun, PrimitiveStore};
use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
+use render_backend::FrameId;
use render_task::{ClearMode, RenderTask, RenderTaskId, RenderTaskLocation, RenderTaskTree};
-use resource_cache::{ImageRequest, ResourceCache};
+use resource_cache::{ResourceCache};
use scene::{ScenePipeline, SceneProperties};
-use std::{mem, usize, f32};
-use tiling::{CompositeOps, Frame, RenderPass, RenderTargetKind};
-use tiling::{RenderPassKind, RenderTargetContext, ScrollbarPrimitive};
-use util::{self, MaxRect, RectHelpers, WorldToLayerFastTransform, recycle_vec};
-
-#[derive(Debug)]
-pub struct ScrollbarInfo(pub ClipId, pub LayerRect);
-
-/// Properties of a stacking context that are maintained
-/// during creation of the scene. These structures are
-/// not persisted after the initial scene build.
-struct StackingContext {
- /// Pipeline this stacking context belongs to.
- pipeline_id: PipelineId,
-
- /// Filters / mix-blend-mode effects
- composite_ops: CompositeOps,
-
- /// If true, visible when backface is visible.
- is_backface_visible: bool,
-
- /// Allow subpixel AA for text runs on this stacking context.
- /// This is a temporary hack while we don't support subpixel AA
- /// on transparent stacking contexts.
- allow_subpixel_aa: bool,
-
- /// CSS transform-style property.
- transform_style: TransformStyle,
-
- /// If Some(..), this stacking context establishes a new
- /// 3d rendering context, and the value is the primitive
- // index of the 3d context container.
- rendering_context_3d_prim_index: Option<PrimitiveIndex>,
-}
+use std::{mem, f32};
+use std::sync::Arc;
+use tiling::{Frame, RenderPass, RenderPassKind, RenderTargetContext, RenderTargetKind};
+use tiling::ScrollbarPrimitive;
+use util::{self, MaxRect, WorldToLayerFastTransform};
#[derive(Clone, Copy)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct FrameBuilderConfig {
pub enable_scrollbars: bool,
pub default_font_render_mode: FontRenderMode,
pub debug: bool,
pub dual_source_blending_is_supported: bool,
pub dual_source_blending_is_enabled: bool,
}
/// A builder structure for `tiling::Frame`
pub struct FrameBuilder {
screen_rect: DeviceUintRect,
background_color: Option<ColorF>,
- prim_store: PrimitiveStore,
+ window_size: DeviceUintSize,
+ pub prim_store: PrimitiveStore,
pub clip_store: ClipStore,
- hit_testing_runs: Vec<HitTestingRun>,
+ pub hit_testing_runs: Vec<HitTestingRun>,
pub config: FrameBuilderConfig,
pub cached_gradients: Vec<CachedGradient>,
-
- // A stack of the current shadow primitives.
- // The sub-Vec stores a buffer of fast-path primitives to be appended on pop.
- shadow_prim_stack: Vec<(PrimitiveIndex, Vec<(PrimitiveIndex, ScrollNodeAndClipChain)>)>,
- // If we're doing any fast-path shadows, we buffer the "real"
- // content here, to be appended when the shadow stack is empty.
- pending_shadow_contents: Vec<(PrimitiveIndex, ScrollNodeAndClipChain, LayerPrimitiveInfo)>,
-
- scrollbar_prims: Vec<ScrollbarPrimitive>,
-
- /// A stack of scroll nodes used during display list processing to properly
- /// parent new scroll nodes.
- reference_frame_stack: Vec<ClipId>,
-
- /// A stack of the current pictures, used during scene building.
- pub picture_stack: Vec<PrimitiveIndex>,
-
- /// A temporary stack of stacking context properties, used only
- /// during scene building.
- sc_stack: Vec<StackingContext>,
+ pub scrollbar_prims: Vec<ScrollbarPrimitive>,
}
-pub struct FrameContext<'a> {
+pub struct FrameBuildingContext<'a> {
pub device_pixel_scale: DevicePixelScale,
pub scene_properties: &'a SceneProperties,
- pub pipelines: &'a FastHashMap<PipelineId, ScenePipeline>,
+ pub pipelines: &'a FastHashMap<PipelineId, Arc<ScenePipeline>>,
pub screen_rect: DeviceIntRect,
pub clip_scroll_tree: &'a ClipScrollTree,
pub node_data: &'a [ClipScrollNodeData],
}
-pub struct FrameState<'a> {
+pub struct FrameBuildingState<'a> {
pub render_tasks: &'a mut RenderTaskTree,
pub profile_counters: &'a mut FrameProfileCounters,
pub clip_store: &'a mut ClipStore,
pub local_clip_rects: &'a mut Vec<LayerRect>,
pub resource_cache: &'a mut ResourceCache,
pub gpu_cache: &'a mut GpuCache,
pub cached_gradients: &'a mut [CachedGradient],
}
pub struct PictureContext<'a> {
pub pipeline_id: PipelineId,
pub perform_culling: bool,
pub prim_runs: Vec<PrimitiveRun>,
- pub original_reference_frame_id: Option<ClipId>,
+ pub original_reference_frame_index: Option<ClipScrollNodeIndex>,
pub display_list: &'a BuiltDisplayList,
pub draw_text_transformed: bool,
pub inv_world_transform: Option<WorldToLayerFastTransform>,
}
pub struct PictureState {
pub tasks: Vec<RenderTaskId>,
}
@@ -168,1585 +110,106 @@ impl<'a> PrimitiveRunContext<'a> {
}
}
}
impl FrameBuilder {
pub fn empty() -> Self {
FrameBuilder {
hit_testing_runs: Vec::new(),
- shadow_prim_stack: Vec::new(),
cached_gradients: Vec::new(),
- pending_shadow_contents: Vec::new(),
scrollbar_prims: Vec::new(),
- reference_frame_stack: Vec::new(),
- picture_stack: Vec::new(),
- sc_stack: Vec::new(),
prim_store: PrimitiveStore::new(),
clip_store: ClipStore::new(),
screen_rect: DeviceUintRect::zero(),
+ window_size: DeviceUintSize::zero(),
background_color: None,
config: FrameBuilderConfig {
enable_scrollbars: false,
default_font_render_mode: FontRenderMode::Mono,
debug: false,
dual_source_blending_is_enabled: true,
dual_source_blending_is_supported: false,
},
}
}
- pub fn recycle(
- self,
+ pub fn with_display_list_flattener(
screen_rect: DeviceUintRect,
background_color: Option<ColorF>,
- config: FrameBuilderConfig,
+ window_size: DeviceUintSize,
+ flattener: DisplayListFlattener,
) -> Self {
FrameBuilder {
- hit_testing_runs: recycle_vec(self.hit_testing_runs),
- shadow_prim_stack: recycle_vec(self.shadow_prim_stack),
- cached_gradients: recycle_vec(self.cached_gradients),
- pending_shadow_contents: recycle_vec(self.pending_shadow_contents),
- scrollbar_prims: recycle_vec(self.scrollbar_prims),
- reference_frame_stack: recycle_vec(self.reference_frame_stack),
- picture_stack: recycle_vec(self.picture_stack),
- sc_stack: recycle_vec(self.sc_stack),
- prim_store: self.prim_store.recycle(),
- clip_store: self.clip_store.recycle(),
+ hit_testing_runs: flattener.hit_testing_runs,
+ cached_gradients: flattener.cached_gradients,
+ scrollbar_prims: flattener.scrollbar_prims,
+ prim_store: flattener.prim_store,
+ clip_store: flattener.clip_store,
screen_rect,
background_color,
- config,
- }
- }
-
- /// Create a primitive and add it to the prim store. This method doesn't
- /// add the primitive to the draw list, so can be used for creating
- /// sub-primitives.
- pub fn create_primitive(
- &mut self,
- info: &LayerPrimitiveInfo,
- mut clip_sources: Vec<ClipSource>,
- container: PrimitiveContainer,
- ) -> PrimitiveIndex {
- if let &LocalClip::RoundedRect(main, region) = &info.local_clip {
- clip_sources.push(ClipSource::Rectangle(main));
-
- clip_sources.push(ClipSource::new_rounded_rect(
- region.rect,
- region.radii,
- region.mode,
- ));
- }
-
- let stacking_context = self.sc_stack.last().expect("bug: no stacking context!");
-
- let clip_sources = self.clip_store.insert(ClipSources::new(clip_sources));
- let prim_index = self.prim_store.add_primitive(
- &info.rect,
- &info.local_clip.clip_rect(),
- info.is_backface_visible && stacking_context.is_backface_visible,
- clip_sources,
- info.tag,
- container,
- );
-
- prim_index
- }
-
- pub fn add_primitive_to_hit_testing_list(
- &mut self,
- info: &LayerPrimitiveInfo,
- clip_and_scroll: ScrollNodeAndClipChain
- ) {
- let tag = match info.tag {
- Some(tag) => tag,
- None => return,
- };
-
- let new_item = HitTestingItem::new(tag, info);
- match self.hit_testing_runs.last_mut() {
- Some(&mut HitTestingRun(ref mut items, prev_clip_and_scroll))
- if prev_clip_and_scroll == clip_and_scroll => {
- items.push(new_item);
- return;
- }
- _ => {}
- }
-
- self.hit_testing_runs.push(HitTestingRun(vec![new_item], clip_and_scroll));
- }
-
- /// Add an already created primitive to the draw lists.
- pub fn add_primitive_to_draw_list(
- &mut self,
- prim_index: PrimitiveIndex,
- clip_and_scroll: ScrollNodeAndClipChain,
- ) {
- // Add primitive to the top-most Picture on the stack.
- // TODO(gw): Let's consider removing the extra indirection
- // needed to get a specific primitive index...
- let pic_prim_index = self.picture_stack.last().unwrap();
- let metadata = &self.prim_store.cpu_metadata[pic_prim_index.0];
- let pic = &mut self.prim_store.cpu_pictures[metadata.cpu_prim_index.0];
- pic.add_primitive(
- prim_index,
- clip_and_scroll
- );
- }
-
- /// Convenience interface that creates a primitive entry and adds it
- /// to the draw list.
- pub fn add_primitive(
- &mut self,
- clip_and_scroll: ScrollNodeAndClipChain,
- info: &LayerPrimitiveInfo,
- clip_sources: Vec<ClipSource>,
- container: PrimitiveContainer,
- ) -> PrimitiveIndex {
- self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
- let prim_index = self.create_primitive(info, clip_sources, container);
-
- self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
- prim_index
- }
-
- pub fn push_stacking_context(
- &mut self,
- pipeline_id: PipelineId,
- composite_ops: CompositeOps,
- transform_style: TransformStyle,
- is_backface_visible: bool,
- is_pipeline_root: bool,
- clip_and_scroll: ScrollNodeAndClipChain,
- output_pipelines: &FastHashSet<PipelineId>,
- ) {
- // Construct the necessary set of Picture primitives
- // to draw this stacking context.
- let current_reference_frame_id = self.current_reference_frame_id();
-
- // An arbitrary large clip rect. For now, we don't
- // specify a clip specific to the stacking context.
- // However, now that they are represented as Picture
- // primitives, we can apply any kind of clip mask
- // to them, as for a normal primitive. This is needed
- // to correctly handle some CSS cases (see #1957).
- let max_clip = LayerRect::max_rect();
-
- // If there is no root picture, create one for the main framebuffer.
- if self.sc_stack.is_empty() {
- // Should be no pictures at all if the stack is empty...
- debug_assert!(self.prim_store.cpu_pictures.is_empty());
- debug_assert_eq!(transform_style, TransformStyle::Flat);
-
- // This picture stores primitive runs for items on the
- // main framebuffer.
- let pic = PicturePrimitive::new_image(
- None,
- false,
- pipeline_id,
- current_reference_frame_id,
- None,
- );
-
- // No clip sources needed for the main framebuffer.
- let clip_sources = self.clip_store.insert(ClipSources::new(Vec::new()));
-
- // Add root picture primitive. The provided layer rect
- // is zero, because we don't yet know the size of the
- // picture. Instead, this is calculated recursively
- // when we cull primitives.
- let prim_index = self.prim_store.add_primitive(
- &LayerRect::zero(),
- &max_clip,
- true,
- clip_sources,
- None,
- PrimitiveContainer::Picture(pic),
- );
-
- self.picture_stack.push(prim_index);
- } else if composite_ops.mix_blend_mode.is_some() && self.sc_stack.len() > 2 {
- // If we have a mix-blend-mode, and we aren't the primary framebuffer,
- // the stacking context needs to be isolated to blend correctly as per
- // the CSS spec.
- // TODO(gw): The way we detect not being the primary framebuffer (len > 2)
- // is hacky and depends on how we create a root stacking context
- // during flattening.
- let current_pic_prim_index = self.picture_stack.last().unwrap();
- let pic_cpu_prim_index = self.prim_store.cpu_metadata[current_pic_prim_index.0].cpu_prim_index;
- let parent_pic = &mut self.prim_store.cpu_pictures[pic_cpu_prim_index.0];
-
- match parent_pic.kind {
- PictureKind::Image { ref mut composite_mode, .. } => {
- // If not already isolated for some other reason,
- // make this picture as isolated.
- if composite_mode.is_none() {
- *composite_mode = Some(PictureCompositeMode::Blit);
- }
- }
- PictureKind::TextShadow { .. } |
- PictureKind::BoxShadow { .. } => {
- panic!("bug: text/box pictures invalid here");
- }
- }
- }
-
- // Get the transform-style of the parent stacking context,
- // which determines if we *might* need to draw this on
- // an intermediate surface for plane splitting purposes.
- let parent_transform_style = match self.sc_stack.last() {
- Some(sc) => sc.transform_style,
- None => TransformStyle::Flat,
- };
-
- // If this is preserve-3d *or* the parent is, then this stacking
- // context is participating in the 3d rendering context. In that
- // case, hoist the picture up to the 3d rendering context
- // container, so that it's rendered as a sibling with other
- // elements in this context.
- let participating_in_3d_context =
- composite_ops.count() == 0 &&
- (parent_transform_style == TransformStyle::Preserve3D ||
- transform_style == TransformStyle::Preserve3D);
-
- // If this is participating in a 3d context *and* the
- // parent was not a 3d context, then this must be the
- // element that establishes a new 3d context.
- let establishes_3d_context =
- participating_in_3d_context &&
- parent_transform_style == TransformStyle::Flat;
-
- let rendering_context_3d_prim_index = if establishes_3d_context {
- // If establishing a 3d context, we need to add a picture
- // that will be the container for all the planes and any
- // un-transformed content.
- let container = PicturePrimitive::new_image(
- None,
- false,
- pipeline_id,
- current_reference_frame_id,
- None,
- );
-
- let clip_sources = self.clip_store.insert(ClipSources::new(Vec::new()));
-
- let prim_index = self.prim_store.add_primitive(
- &LayerRect::zero(),
- &max_clip,
- is_backface_visible,
- clip_sources,
- None,
- PrimitiveContainer::Picture(container),
- );
-
- let parent_pic_prim_index = *self.picture_stack.last().unwrap();
- let pic_prim_index = self.prim_store.cpu_metadata[parent_pic_prim_index.0].cpu_prim_index;
- let pic = &mut self.prim_store.cpu_pictures[pic_prim_index.0];
- pic.add_primitive(
- prim_index,
- clip_and_scroll,
- );
-
- self.picture_stack.push(prim_index);
-
- Some(prim_index)
- } else {
- None
- };
-
- let mut parent_pic_prim_index = if !establishes_3d_context && participating_in_3d_context {
- // If we're in a 3D context, we will parent the picture
- // to the first stacking context we find that is a
- // 3D rendering context container. This follows the spec
- // by hoisting these items out into the same 3D context
- // for plane splitting.
- self.sc_stack
- .iter()
- .rev()
- .find(|sc| sc.rendering_context_3d_prim_index.is_some())
- .map(|sc| sc.rendering_context_3d_prim_index.unwrap())
- .unwrap()
- } else {
- *self.picture_stack.last().unwrap()
- };
-
- // For each filter, create a new image with that composite mode.
- for filter in composite_ops.filters.iter().rev() {
- let src_prim = PicturePrimitive::new_image(
- Some(PictureCompositeMode::Filter(*filter)),
- false,
- pipeline_id,
- current_reference_frame_id,
- None,
- );
- let src_clip_sources = self.clip_store.insert(ClipSources::new(Vec::new()));
-
- let src_prim_index = self.prim_store.add_primitive(
- &LayerRect::zero(),
- &max_clip,
- is_backface_visible,
- src_clip_sources,
- None,
- PrimitiveContainer::Picture(src_prim),
- );
-
- let pic_prim_index = self.prim_store.cpu_metadata[parent_pic_prim_index.0].cpu_prim_index;
- parent_pic_prim_index = src_prim_index;
- let pic = &mut self.prim_store.cpu_pictures[pic_prim_index.0];
- pic.add_primitive(
- src_prim_index,
- clip_and_scroll,
- );
-
- self.picture_stack.push(src_prim_index);
- }
-
- // Same for mix-blend-mode.
- if let Some(mix_blend_mode) = composite_ops.mix_blend_mode {
- let src_prim = PicturePrimitive::new_image(
- Some(PictureCompositeMode::MixBlend(mix_blend_mode)),
- false,
- pipeline_id,
- current_reference_frame_id,
- None,
- );
- let src_clip_sources = self.clip_store.insert(ClipSources::new(Vec::new()));
-
- let src_prim_index = self.prim_store.add_primitive(
- &LayerRect::zero(),
- &max_clip,
- is_backface_visible,
- src_clip_sources,
- None,
- PrimitiveContainer::Picture(src_prim),
- );
-
- let pic_prim_index = self.prim_store.cpu_metadata[parent_pic_prim_index.0].cpu_prim_index;
- parent_pic_prim_index = src_prim_index;
- let pic = &mut self.prim_store.cpu_pictures[pic_prim_index.0];
- pic.add_primitive(
- src_prim_index,
- clip_and_scroll,
- );
-
- self.picture_stack.push(src_prim_index);
- }
-
- // By default, this picture will be collapsed into
- // the owning target.
- let mut composite_mode = None;
- let mut frame_output_pipeline_id = None;
-
- // If this stacking context if the root of a pipeline, and the caller
- // has requested it as an output frame, create a render task to isolate it.
- if is_pipeline_root && output_pipelines.contains(&pipeline_id) {
- composite_mode = Some(PictureCompositeMode::Blit);
- frame_output_pipeline_id = Some(pipeline_id);
- }
-
- if participating_in_3d_context {
- // TODO(gw): For now, as soon as this picture is in
- // a 3D context, we draw it to an intermediate
- // surface and apply plane splitting. However,
- // there is a large optimization opportunity here.
- // During culling, we can check if there is actually
- // perspective present, and skip the plane splitting
- // completely when that is not the case.
- composite_mode = Some(PictureCompositeMode::Blit);
- }
-
- // Add picture for this actual stacking context contents to render into.
- let sc_prim = PicturePrimitive::new_image(
- composite_mode,
- participating_in_3d_context,
- pipeline_id,
- current_reference_frame_id,
- frame_output_pipeline_id,
- );
-
- let sc_clip_sources = self.clip_store.insert(ClipSources::new(Vec::new()));
- let sc_prim_index = self.prim_store.add_primitive(
- &LayerRect::zero(),
- &max_clip,
- is_backface_visible,
- sc_clip_sources,
- None,
- PrimitiveContainer::Picture(sc_prim),
- );
-
- let pic_prim_index = self.prim_store.cpu_metadata[parent_pic_prim_index.0].cpu_prim_index;
- let sc_pic = &mut self.prim_store.cpu_pictures[pic_prim_index.0];
- sc_pic.add_primitive(
- sc_prim_index,
- clip_and_scroll,
- );
-
- // Add this as the top-most picture for primitives to be added to.
- self.picture_stack.push(sc_prim_index);
-
- // TODO(gw): This is super conservative. We can expand on this a lot
- // once all the picture code is in place and landed.
- let allow_subpixel_aa = composite_ops.count() == 0 &&
- transform_style == TransformStyle::Flat;
-
- // Push the SC onto the stack, so we know how to handle things in
- // pop_stacking_context.
- let sc = StackingContext {
- composite_ops,
- is_backface_visible,
- pipeline_id,
- allow_subpixel_aa,
- transform_style,
- rendering_context_3d_prim_index,
- };
-
- self.sc_stack.push(sc);
- }
-
- pub fn pop_stacking_context(&mut self) {
- let sc = self.sc_stack.pop().unwrap();
-
- // Always pop at least the main picture for this stacking context.
- let mut pop_count = 1;
-
- // Remove the picture for any filter/mix-blend-mode effects.
- pop_count += sc.composite_ops.count();
-
- // Remove the 3d context container if created
- if sc.rendering_context_3d_prim_index.is_some() {
- pop_count += 1;
- }
-
- for _ in 0 .. pop_count {
- self.picture_stack.pop().expect("bug: mismatched picture stack");
- }
-
- // By the time the stacking context stack is empty, we should
- // also have cleared the picture stack.
- if self.sc_stack.is_empty() {
- self.picture_stack.pop().expect("bug: picture stack invalid");
- debug_assert!(self.picture_stack.is_empty());
- }
-
- assert!(
- self.shadow_prim_stack.is_empty(),
- "Found unpopped text shadows when popping stacking context!"
- );
- }
-
- pub fn push_reference_frame(
- &mut self,
- reference_frame_id: ClipId,
- parent_id: Option<ClipId>,
- pipeline_id: PipelineId,
- rect: &LayerRect,
- source_transform: Option<PropertyBinding<LayoutTransform>>,
- source_perspective: Option<LayoutTransform>,
- origin_in_parent_reference_frame: LayerVector2D,
- clip_scroll_tree: &mut ClipScrollTree,
- id_to_index_mapper: &mut ClipIdToIndexMapper,
- ) {
- let node = ClipScrollNode::new_reference_frame(
- parent_id,
- rect,
- source_transform,
- source_perspective,
- origin_in_parent_reference_frame,
- pipeline_id,
- );
- clip_scroll_tree.add_node(node, reference_frame_id);
- self.reference_frame_stack.push(reference_frame_id);
-
- match parent_id {
- Some(ref parent_id) =>
- id_to_index_mapper.map_to_parent_clip_chain(reference_frame_id, parent_id),
- _ => id_to_index_mapper.add(reference_frame_id, ClipChainIndex(0)),
- }
- }
-
- pub fn current_reference_frame_id(&self) -> ClipId {
- *self.reference_frame_stack.last().unwrap()
- }
-
- pub fn setup_viewport_offset(
- &mut self,
- inner_rect: DeviceUintRect,
- device_pixel_scale: DevicePixelScale,
- clip_scroll_tree: &mut ClipScrollTree,
- ) {
- let viewport_offset = (inner_rect.origin.to_vector().to_f32() / device_pixel_scale).round();
- let root_id = clip_scroll_tree.root_reference_frame_id();
- if let Some(root_node) = clip_scroll_tree.nodes.get_mut(&root_id) {
- if let NodeType::ReferenceFrame(ref mut info) = root_node.node_type {
- info.resolved_transform =
- LayerVector2D::new(viewport_offset.x, viewport_offset.y).into();
- }
- }
- }
-
- pub fn push_root(
- &mut self,
- pipeline_id: PipelineId,
- viewport_size: &LayerSize,
- content_size: &LayerSize,
- clip_scroll_tree: &mut ClipScrollTree,
- id_to_index_mapper: &mut ClipIdToIndexMapper,
- ) -> ClipId {
- let viewport_rect = LayerRect::new(LayerPoint::zero(), *viewport_size);
- self.push_reference_frame(
- ClipId::root_reference_frame(pipeline_id),
- None,
- pipeline_id,
- &viewport_rect,
- None,
- None,
- LayerVector2D::zero(),
- clip_scroll_tree,
- id_to_index_mapper,
- );
-
- let topmost_scrolling_node_id = ClipId::root_scroll_node(pipeline_id);
- clip_scroll_tree.topmost_scrolling_node_id = topmost_scrolling_node_id;
-
- self.add_scroll_frame(
- topmost_scrolling_node_id,
- clip_scroll_tree.root_reference_frame_id,
- Some(ExternalScrollId(0, pipeline_id)),
- pipeline_id,
- &viewport_rect,
- content_size,
- ScrollSensitivity::ScriptAndInputEvents,
- clip_scroll_tree,
- id_to_index_mapper,
- );
-
- topmost_scrolling_node_id
- }
-
- pub fn add_clip_node(
- &mut self,
- new_node_id: ClipId,
- parent_id: ClipId,
- clip_region: ClipRegion,
- clip_scroll_tree: &mut ClipScrollTree,
- id_to_index_mapper: &mut ClipIdToIndexMapper,
- ) {
- let clip_rect = clip_region.main;
- let clip_sources = ClipSources::from(clip_region);
-
- debug_assert!(clip_sources.has_clips());
- let handle = self.clip_store.insert(clip_sources);
-
- let clip_chain_index = clip_scroll_tree.add_clip_node(
- new_node_id,
- parent_id,
- handle,
- clip_rect
- );
- id_to_index_mapper.add(new_node_id, clip_chain_index);
- }
-
- pub fn add_scroll_frame(
- &mut self,
- new_node_id: ClipId,
- parent_id: ClipId,
- external_id: Option<ExternalScrollId>,
- pipeline_id: PipelineId,
- frame_rect: &LayerRect,
- content_size: &LayerSize,
- scroll_sensitivity: ScrollSensitivity,
- clip_scroll_tree: &mut ClipScrollTree,
- id_to_index_mapper: &mut ClipIdToIndexMapper,
- ) {
- let node = ClipScrollNode::new_scroll_frame(
- pipeline_id,
- parent_id,
- external_id,
- frame_rect,
- content_size,
- scroll_sensitivity,
- );
-
- clip_scroll_tree.add_node(node, new_node_id);
- id_to_index_mapper.map_to_parent_clip_chain(new_node_id, &parent_id);
- }
-
- pub fn pop_reference_frame(&mut self) {
- self.reference_frame_stack.pop();
- }
-
- pub fn push_shadow(
- &mut self,
- shadow: Shadow,
- clip_and_scroll: ScrollNodeAndClipChain,
- info: &LayerPrimitiveInfo,
- ) {
- let pipeline_id = self.sc_stack.last().unwrap().pipeline_id;
- let prim = PicturePrimitive::new_text_shadow(shadow, pipeline_id);
-
- // Create an empty shadow primitive. Insert it into
- // the draw lists immediately so that it will be drawn
- // before any visual text elements that are added as
- // part of this shadow context.
- let prim_index = self.create_primitive(
- info,
- Vec::new(),
- PrimitiveContainer::Picture(prim),
- );
-
- let pending = vec![(prim_index, clip_and_scroll)];
- self.shadow_prim_stack.push((prim_index, pending));
- }
-
- pub fn pop_all_shadows(&mut self) {
- assert!(self.shadow_prim_stack.len() > 0, "popped shadows, but none were present");
-
- // Borrowcheck dance
- let mut shadows = mem::replace(&mut self.shadow_prim_stack, Vec::new());
- for (_, pending_primitives) in shadows.drain(..) {
- // Push any fast-path shadows now
- for (prim_index, clip_and_scroll) in pending_primitives {
- self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
- }
- }
-
- let mut pending_primitives = mem::replace(&mut self.pending_shadow_contents, Vec::new());
- for (prim_index, clip_and_scroll, info) in pending_primitives.drain(..) {
- self.add_primitive_to_hit_testing_list(&info, clip_and_scroll);
- self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
- }
-
- mem::replace(&mut self.pending_shadow_contents, pending_primitives);
- mem::replace(&mut self.shadow_prim_stack, shadows);
- }
-
- pub fn add_solid_rectangle(
- &mut self,
- clip_and_scroll: ScrollNodeAndClipChain,
- info: &LayerPrimitiveInfo,
- color: ColorF,
- segments: Option<BrushSegmentDescriptor>,
- ) {
- if color.a == 0.0 {
- // Don't add transparent rectangles to the draw list, but do consider them for hit
- // testing. This allows specifying invisible hit testing areas.
- self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
- return;
- }
-
- let prim = BrushPrimitive::new(
- BrushKind::Solid {
- color,
- },
- segments,
- );
-
- self.add_primitive(
- clip_and_scroll,
- info,
- Vec::new(),
- PrimitiveContainer::Brush(prim),
- );
- }
-
- pub fn add_clear_rectangle(
- &mut self,
- clip_and_scroll: ScrollNodeAndClipChain,
- info: &LayerPrimitiveInfo,
- ) {
- let prim = BrushPrimitive::new(
- BrushKind::Clear,
- None,
- );
-
- self.add_primitive(
- clip_and_scroll,
- info,
- Vec::new(),
- PrimitiveContainer::Brush(prim),
- );
- }
-
- pub fn add_scroll_bar(
- &mut self,
- clip_and_scroll: ScrollNodeAndClipChain,
- info: &LayerPrimitiveInfo,
- color: ColorF,
- scrollbar_info: ScrollbarInfo,
- ) {
- if color.a == 0.0 {
- return;
- }
-
- let prim = BrushPrimitive::new(
- BrushKind::Solid {
- color,
- },
- None,
- );
-
- let prim_index = self.add_primitive(
- clip_and_scroll,
- info,
- Vec::new(),
- PrimitiveContainer::Brush(prim),
- );
-
- self.scrollbar_prims.push(ScrollbarPrimitive {
- prim_index,
- clip_id: scrollbar_info.0,
- frame_rect: scrollbar_info.1,
- });
- }
-
- pub fn add_line(
- &mut self,
- clip_and_scroll: ScrollNodeAndClipChain,
- info: &LayerPrimitiveInfo,
- wavy_line_thickness: f32,
- orientation: LineOrientation,
- line_color: &ColorF,
- style: LineStyle,
- ) {
- let line = BrushPrimitive::new(
- BrushKind::Line {
- wavy_line_thickness,
- color: line_color.premultiplied(),
- style,
- orientation,
- },
- None,
- );
-
- let mut fast_shadow_prims = Vec::new();
- for (idx, &(shadow_prim_index, _)) in self.shadow_prim_stack.iter().enumerate() {
- let shadow_metadata = &self.prim_store.cpu_metadata[shadow_prim_index.0];
- let picture = &self.prim_store.cpu_pictures[shadow_metadata.cpu_prim_index.0];
- match picture.kind {
- PictureKind::TextShadow { offset, color, blur_radius, .. } if blur_radius == 0.0 => {
- fast_shadow_prims.push((idx, offset, color));
- }
- _ => {}
- }
- }
-
- for (idx, shadow_offset, shadow_color) in fast_shadow_prims {
- let line = BrushPrimitive::new(
- BrushKind::Line {
- wavy_line_thickness,
- color: shadow_color.premultiplied(),
- style,
- orientation,
- },
- None,
- );
- let mut info = info.clone();
- info.rect = info.rect.translate(&shadow_offset);
- info.local_clip =
- LocalClip::from(info.local_clip.clip_rect().translate(&shadow_offset));
- let prim_index = self.create_primitive(
- &info,
- Vec::new(),
- PrimitiveContainer::Brush(line),
- );
- self.shadow_prim_stack[idx].1.push((prim_index, clip_and_scroll));
+ window_size,
+ config: flattener.config,
}
-
- let prim_index = self.create_primitive(
- &info,
- Vec::new(),
- PrimitiveContainer::Brush(line),
- );
-
- if line_color.a > 0.0 {
- if self.shadow_prim_stack.is_empty() {
- self.add_primitive_to_hit_testing_list(&info, clip_and_scroll);
- self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
- } else {
- self.pending_shadow_contents.push((prim_index, clip_and_scroll, *info));
- }
- }
-
- for &(shadow_prim_index, _) in &self.shadow_prim_stack {
- let shadow_metadata = &mut self.prim_store.cpu_metadata[shadow_prim_index.0];
- debug_assert_eq!(shadow_metadata.prim_kind, PrimitiveKind::Picture);
- let picture =
- &mut self.prim_store.cpu_pictures[shadow_metadata.cpu_prim_index.0];
-
- match picture.kind {
- // Only run real blurs here (fast path zero blurs are handled above).
- PictureKind::TextShadow { blur_radius, .. } if blur_radius > 0.0 => {
- picture.add_primitive(
- prim_index,
- clip_and_scroll,
- );
- }
- _ => {}
- }
- }
- }
-
- pub fn add_border(
- &mut self,
- clip_and_scroll: ScrollNodeAndClipChain,
- info: &LayerPrimitiveInfo,
- border_item: &BorderDisplayItem,
- gradient_stops: ItemRange<GradientStop>,
- gradient_stops_count: usize,
- ) {
- let rect = info.rect;
- let create_segments = |outset: SideOffsets2D<f32>| {
- // Calculate the modified rect as specific by border-image-outset
- let origin = LayerPoint::new(rect.origin.x - outset.left, rect.origin.y - outset.top);
- let size = LayerSize::new(
- rect.size.width + outset.left + outset.right,
- rect.size.height + outset.top + outset.bottom,
- );
- let rect = LayerRect::new(origin, size);
-
- let tl_outer = LayerPoint::new(rect.origin.x, rect.origin.y);
- let tl_inner = tl_outer + vec2(border_item.widths.left, border_item.widths.top);
-
- let tr_outer = LayerPoint::new(rect.origin.x + rect.size.width, rect.origin.y);
- let tr_inner = tr_outer + vec2(-border_item.widths.right, border_item.widths.top);
-
- let bl_outer = LayerPoint::new(rect.origin.x, rect.origin.y + rect.size.height);
- let bl_inner = bl_outer + vec2(border_item.widths.left, -border_item.widths.bottom);
-
- let br_outer = LayerPoint::new(
- rect.origin.x + rect.size.width,
- rect.origin.y + rect.size.height,
- );
- let br_inner = br_outer - vec2(border_item.widths.right, border_item.widths.bottom);
-
- // Build the list of gradient segments
- vec![
- // Top left
- LayerRect::from_floats(tl_outer.x, tl_outer.y, tl_inner.x, tl_inner.y),
- // Top right
- LayerRect::from_floats(tr_inner.x, tr_outer.y, tr_outer.x, tr_inner.y),
- // Bottom right
- LayerRect::from_floats(br_inner.x, br_inner.y, br_outer.x, br_outer.y),
- // Bottom left
- LayerRect::from_floats(bl_outer.x, bl_inner.y, bl_inner.x, bl_outer.y),
- // Top
- LayerRect::from_floats(tl_inner.x, tl_outer.y, tr_inner.x, tl_inner.y),
- // Bottom
- LayerRect::from_floats(bl_inner.x, bl_inner.y, br_inner.x, bl_outer.y),
- // Left
- LayerRect::from_floats(tl_outer.x, tl_inner.y, tl_inner.x, bl_inner.y),
- // Right
- LayerRect::from_floats(tr_inner.x, tr_inner.y, br_outer.x, br_inner.y),
- ]
- };
-
- match border_item.details {
- BorderDetails::Image(ref border) => {
- // Calculate the modified rect as specific by border-image-outset
- let origin = LayerPoint::new(
- rect.origin.x - border.outset.left,
- rect.origin.y - border.outset.top,
- );
- let size = LayerSize::new(
- rect.size.width + border.outset.left + border.outset.right,
- rect.size.height + border.outset.top + border.outset.bottom,
- );
- let rect = LayerRect::new(origin, size);
-
- // Calculate the local texel coords of the slices.
- let px0 = 0.0;
- let px1 = border.patch.slice.left as f32;
- let px2 = border.patch.width as f32 - border.patch.slice.right as f32;
- let px3 = border.patch.width as f32;
-
- let py0 = 0.0;
- let py1 = border.patch.slice.top as f32;
- let py2 = border.patch.height as f32 - border.patch.slice.bottom as f32;
- let py3 = border.patch.height as f32;
-
- let tl_outer = LayerPoint::new(rect.origin.x, rect.origin.y);
- let tl_inner = tl_outer + vec2(border_item.widths.left, border_item.widths.top);
-
- let tr_outer = LayerPoint::new(rect.origin.x + rect.size.width, rect.origin.y);
- let tr_inner = tr_outer + vec2(-border_item.widths.right, border_item.widths.top);
-
- let bl_outer = LayerPoint::new(rect.origin.x, rect.origin.y + rect.size.height);
- let bl_inner = bl_outer + vec2(border_item.widths.left, -border_item.widths.bottom);
-
- let br_outer = LayerPoint::new(
- rect.origin.x + rect.size.width,
- rect.origin.y + rect.size.height,
- );
- let br_inner = br_outer - vec2(border_item.widths.right, border_item.widths.bottom);
-
- fn add_segment(
- segments: &mut Vec<ImageBorderSegment>,
- rect: LayerRect,
- uv_rect: TexelRect,
- repeat_horizontal: RepeatMode,
- repeat_vertical: RepeatMode) {
- if uv_rect.uv1.x > uv_rect.uv0.x &&
- uv_rect.uv1.y > uv_rect.uv0.y {
- segments.push(ImageBorderSegment::new(
- rect,
- uv_rect,
- repeat_horizontal,
- repeat_vertical,
- ));
- }
- }
-
- // Build the list of image segments
- let mut segments = vec![];
-
- // Top left
- add_segment(
- &mut segments,
- LayerRect::from_floats(tl_outer.x, tl_outer.y, tl_inner.x, tl_inner.y),
- TexelRect::new(px0, py0, px1, py1),
- RepeatMode::Stretch,
- RepeatMode::Stretch
- );
- // Top right
- add_segment(
- &mut segments,
- LayerRect::from_floats(tr_inner.x, tr_outer.y, tr_outer.x, tr_inner.y),
- TexelRect::new(px2, py0, px3, py1),
- RepeatMode::Stretch,
- RepeatMode::Stretch
- );
- // Bottom right
- add_segment(
- &mut segments,
- LayerRect::from_floats(br_inner.x, br_inner.y, br_outer.x, br_outer.y),
- TexelRect::new(px2, py2, px3, py3),
- RepeatMode::Stretch,
- RepeatMode::Stretch
- );
- // Bottom left
- add_segment(
- &mut segments,
- LayerRect::from_floats(bl_outer.x, bl_inner.y, bl_inner.x, bl_outer.y),
- TexelRect::new(px0, py2, px1, py3),
- RepeatMode::Stretch,
- RepeatMode::Stretch
- );
-
- // Center
- if border.fill {
- add_segment(
- &mut segments,
- LayerRect::from_floats(tl_inner.x, tl_inner.y, tr_inner.x, bl_inner.y),
- TexelRect::new(px1, py1, px2, py2),
- border.repeat_horizontal,
- border.repeat_vertical
- );
- }
-
- // Add edge segments.
-
- // Top
- add_segment(
- &mut segments,
- LayerRect::from_floats(tl_inner.x, tl_outer.y, tr_inner.x, tl_inner.y),
- TexelRect::new(px1, py0, px2, py1),
- border.repeat_horizontal,
- RepeatMode::Stretch,
- );
- // Bottom
- add_segment(
- &mut segments,
- LayerRect::from_floats(bl_inner.x, bl_inner.y, br_inner.x, bl_outer.y),
- TexelRect::new(px1, py2, px2, py3),
- border.repeat_horizontal,
- RepeatMode::Stretch,
- );
- // Left
- add_segment(
- &mut segments,
- LayerRect::from_floats(tl_outer.x, tl_inner.y, tl_inner.x, bl_inner.y),
- TexelRect::new(px0, py1, px1, py2),
- RepeatMode::Stretch,
- border.repeat_vertical,
- );
- // Right
- add_segment(
- &mut segments,
- LayerRect::from_floats(tr_inner.x, tr_inner.y, br_outer.x, br_inner.y),
- TexelRect::new(px2, py1, px3, py2),
- RepeatMode::Stretch,
- border.repeat_vertical,
- );
-
- for segment in segments {
- let mut info = info.clone();
- info.rect = segment.geom_rect;
- self.add_image(
- clip_and_scroll,
- &info,
- segment.stretch_size,
- segment.tile_spacing,
- Some(segment.sub_rect),
- border.image_key,
- ImageRendering::Auto,
- AlphaType::PremultipliedAlpha,
- None,
- );
- }
- }
- BorderDetails::Normal(ref border) => {
- self.add_normal_border(info, border, &border_item.widths, clip_and_scroll);
- }
- BorderDetails::Gradient(ref border) => for segment in create_segments(border.outset) {
- let segment_rel = segment.origin - rect.origin;
- let mut info = info.clone();
- info.rect = segment;
-
- self.add_gradient(
- clip_and_scroll,
- &info,
- border.gradient.start_point - segment_rel,
- border.gradient.end_point - segment_rel,
- gradient_stops,
- gradient_stops_count,
- border.gradient.extend_mode,
- segment.size,
- LayerSize::zero(),
- );
- },
- BorderDetails::RadialGradient(ref border) => {
- for segment in create_segments(border.outset) {
- let segment_rel = segment.origin - rect.origin;
- let mut info = info.clone();
- info.rect = segment;
-
- self.add_radial_gradient(
- clip_and_scroll,
- &info,
- border.gradient.start_center - segment_rel,
- border.gradient.start_radius,
- border.gradient.end_center - segment_rel,
- border.gradient.end_radius,
- border.gradient.ratio_xy,
- gradient_stops,
- border.gradient.extend_mode,
- segment.size,
- LayerSize::zero(),
- );
- }
- }
- }
- }
-
- fn add_gradient_impl(
- &mut self,
- clip_and_scroll: ScrollNodeAndClipChain,
- info: &LayerPrimitiveInfo,
- start_point: LayerPoint,
- end_point: LayerPoint,
- stops: ItemRange<GradientStop>,
- stops_count: usize,
- extend_mode: ExtendMode,
- gradient_index: CachedGradientIndex,
- ) {
- // 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,
- // a reference orientation for the gradient line must be chosen, somewhat arbitrarily, so
- // just designate the reference orientation as start < end. Aligned gradient rendering
- // manages to produce the same result regardless of orientation, so don't worry about
- // reversing in that case.
- let reverse_stops = start_point.x > end_point.x ||
- (start_point.x == end_point.x && start_point.y > end_point.y);
-
- // To get reftests exactly matching with reverse start/end
- // points, it's necessary to reverse the gradient
- // line in some cases.
- let (sp, ep) = if reverse_stops {
- (end_point, start_point)
- } else {
- (start_point, end_point)
- };
-
- let prim = BrushPrimitive::new(
- BrushKind::LinearGradient {
- stops_range: stops,
- stops_count,
- extend_mode,
- reverse_stops,
- start_point: sp,
- end_point: ep,
- gradient_index,
- },
- None,
- );
-
- let prim = PrimitiveContainer::Brush(prim);
-
- self.add_primitive(clip_and_scroll, info, Vec::new(), prim);
- }
-
- pub fn add_gradient(
- &mut self,
- clip_and_scroll: ScrollNodeAndClipChain,
- info: &LayerPrimitiveInfo,
- start_point: LayerPoint,
- end_point: LayerPoint,
- stops: ItemRange<GradientStop>,
- stops_count: usize,
- extend_mode: ExtendMode,
- tile_size: LayerSize,
- 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,
- );
-
- if prim_infos.is_empty() {
- self.add_gradient_impl(
- clip_and_scroll,
- info,
- start_point,
- end_point,
- stops,
- stops_count,
- extend_mode,
- gradient_index,
- );
- } 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,
- );
- }
- }
- }
-
- fn add_radial_gradient_impl(
- &mut self,
- clip_and_scroll: ScrollNodeAndClipChain,
- info: &LayerPrimitiveInfo,
- start_center: LayerPoint,
- start_radius: f32,
- end_center: LayerPoint,
- end_radius: f32,
- ratio_xy: f32,
- stops: ItemRange<GradientStop>,
- extend_mode: ExtendMode,
- gradient_index: CachedGradientIndex,
- ) {
- let prim = BrushPrimitive::new(
- BrushKind::RadialGradient {
- stops_range: stops,
- extend_mode,
- start_center,
- end_center,
- start_radius,
- end_radius,
- ratio_xy,
- gradient_index,
- },
- None,
- );
-
- self.add_primitive(
- clip_and_scroll,
- info,
- Vec::new(),
- PrimitiveContainer::Brush(prim),
- );
- }
-
- pub fn add_radial_gradient(
- &mut self,
- clip_and_scroll: ScrollNodeAndClipChain,
- info: &LayerPrimitiveInfo,
- start_center: LayerPoint,
- start_radius: f32,
- end_center: LayerPoint,
- end_radius: f32,
- ratio_xy: f32,
- stops: ItemRange<GradientStop>,
- extend_mode: ExtendMode,
- tile_size: LayerSize,
- 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,
- );
-
- if prim_infos.is_empty() {
- self.add_radial_gradient_impl(
- clip_and_scroll,
- info,
- start_center,
- start_radius,
- end_center,
- end_radius,
- ratio_xy,
- stops,
- extend_mode,
- gradient_index,
- );
- } else {
- for prim_info in prim_infos {
- self.add_radial_gradient_impl(
- clip_and_scroll,
- &prim_info,
- start_center,
- start_radius,
- end_center,
- end_radius,
- ratio_xy,
- stops,
- extend_mode,
- gradient_index,
- );
- }
- }
- }
-
- pub fn add_text(
- &mut self,
- clip_and_scroll: ScrollNodeAndClipChain,
- run_offset: LayoutVector2D,
- info: &LayerPrimitiveInfo,
- font: &FontInstance,
- text_color: &ColorF,
- glyph_range: ItemRange<GlyphInstance>,
- glyph_count: usize,
- glyph_options: Option<GlyphOptions>,
- ) {
- // Trivial early out checks
- if font.size.0 <= 0 {
- return;
- }
-
- // Sanity check - anything with glyphs bigger than this
- // is probably going to consume too much memory to render
- // efficiently anyway. This is specifically to work around
- // the font_advance.html reftest, which creates a very large
- // font as a crash test - the rendering is also ignored
- // by the azure renderer.
- if font.size >= Au::from_px(4096) {
- return;
- }
-
- // TODO(gw): Use a proper algorithm to select
- // whether this item should be rendered with
- // subpixel AA!
- let mut render_mode = self.config
- .default_font_render_mode
- .limit_by(font.render_mode);
- let mut flags = font.flags;
- if let Some(options) = glyph_options {
- render_mode = render_mode.limit_by(options.render_mode);
- flags |= options.flags;
- }
-
- // There are some conditions under which we can't use
- // subpixel text rendering, even if enabled.
- if render_mode == FontRenderMode::Subpixel {
- // text on a picture that has filters
- // (e.g. opacity) can't use sub-pixel.
- // TODO(gw): It's possible we can relax this in
- // the future, if we modify the way
- // we handle subpixel blending.
- if let Some(ref stacking_context) = self.sc_stack.last() {
- if !stacking_context.allow_subpixel_aa {
- render_mode = FontRenderMode::Alpha;
- }
- }
- }
-
- let prim_font = FontInstance::new(
- font.font_key,
- font.size,
- *text_color,
- font.bg_color,
- render_mode,
- font.subpx_dir,
- flags,
- font.platform_options,
- font.variations.clone(),
- );
- let prim = TextRunPrimitiveCpu {
- font: prim_font,
- glyph_range,
- glyph_count,
- glyph_gpu_blocks: Vec::new(),
- glyph_keys: Vec::new(),
- offset: run_offset,
- shadow: false,
- };
-
- // Text shadows that have a blur radius of 0 need to be rendered as normal
- // text elements to get pixel perfect results for reftests. It's also a big
- // performance win to avoid blurs and render target allocations where
- // possible. For any text shadows that have zero blur, create a normal text
- // primitive with the shadow's color and offset. These need to be added
- // *before* the visual text primitive in order to get the correct paint
- // order. Store them in a Vec first to work around borrowck issues.
- // TODO(gw): Refactor to avoid having to store them in a Vec first.
- let mut fast_shadow_prims = Vec::new();
- for (idx, &(shadow_prim_index, _)) in self.shadow_prim_stack.iter().enumerate() {
- let shadow_metadata = &self.prim_store.cpu_metadata[shadow_prim_index.0];
- let picture_prim = &self.prim_store.cpu_pictures[shadow_metadata.cpu_prim_index.0];
- match picture_prim.kind {
- PictureKind::TextShadow { offset, color, blur_radius, .. } if blur_radius == 0.0 => {
- let mut text_prim = prim.clone();
- text_prim.font.color = color.into();
- text_prim.shadow = true;
- text_prim.offset += offset;
- fast_shadow_prims.push((idx, text_prim));
- }
- _ => {}
- }
- }
-
- for (idx, text_prim) in fast_shadow_prims {
- let rect = info.rect;
- let mut info = info.clone();
- info.rect = rect.translate(&text_prim.offset);
- info.local_clip =
- LocalClip::from(info.local_clip.clip_rect().translate(&text_prim.offset));
- let prim_index = self.create_primitive(
- &info,
- Vec::new(),
- PrimitiveContainer::TextRun(text_prim),
- );
- self.shadow_prim_stack[idx].1.push((prim_index, clip_and_scroll));
- }
-
- // Create (and add to primitive store) the primitive that will be
- // used for both the visual element and also the shadow(s).
- let prim_index = self.create_primitive(
- info,
- Vec::new(),
- PrimitiveContainer::TextRun(prim),
- );
-
- // Only add a visual element if it can contribute to the scene.
- if text_color.a > 0.0 {
- if self.shadow_prim_stack.is_empty() {
- self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
- self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
- } else {
- self.pending_shadow_contents.push((prim_index, clip_and_scroll, *info));
- }
- }
-
- // Now add this primitive index to all the currently active text shadow
- // primitives. Although we're adding the indices *after* the visual
- // primitive here, they will still draw before the visual text, since
- // the shadow primitive itself has been added to the draw cmd
- // list *before* the visual element, during push_shadow. We need
- // the primitive index of the visual element here before we can add
- // the indices as sub-primitives to the shadow primitives.
- for &(shadow_prim_index, _) in &self.shadow_prim_stack {
- let shadow_metadata = &mut self.prim_store.cpu_metadata[shadow_prim_index.0];
- debug_assert_eq!(shadow_metadata.prim_kind, PrimitiveKind::Picture);
- let picture =
- &mut self.prim_store.cpu_pictures[shadow_metadata.cpu_prim_index.0];
-
- match picture.kind {
- // Only run real blurs here (fast path zero blurs are handled above).
- PictureKind::TextShadow { blur_radius, .. } if blur_radius > 0.0 => {
- picture.add_primitive(
- prim_index,
- clip_and_scroll,
- );
- }
- _ => {}
- }
- }
- }
-
- pub fn add_image(
- &mut self,
- clip_and_scroll: ScrollNodeAndClipChain,
- info: &LayerPrimitiveInfo,
- stretch_size: LayerSize,
- mut tile_spacing: LayerSize,
- sub_rect: Option<TexelRect>,
- image_key: ImageKey,
- image_rendering: ImageRendering,
- alpha_type: AlphaType,
- tile_offset: Option<TileOffset>,
- ) {
- // 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,
- };
-
- // 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 &&
- sub_rect.is_none() &&
- tile_offset.is_none() {
- let prim = BrushPrimitive::new(
- BrushKind::Image {
- request,
- current_epoch: Epoch::invalid(),
- alpha_type,
- },
- None,
- );
-
- self.add_primitive(
- clip_and_scroll,
- info,
- Vec::new(),
- PrimitiveContainer::Brush(prim),
- );
- } else {
- let prim_cpu = ImagePrimitiveCpu {
- tile_spacing,
- alpha_type,
- stretch_size,
- current_epoch: Epoch::invalid(),
- source: ImageSource::Default,
- key: ImageCacheKey {
- request,
- texel_rect: sub_rect.map(|texel_rect| {
- DeviceIntRect::new(
- DeviceIntPoint::new(
- texel_rect.uv0.x as i32,
- texel_rect.uv0.y as i32,
- ),
- DeviceIntSize::new(
- (texel_rect.uv1.x - texel_rect.uv0.x) as i32,
- (texel_rect.uv1.y - texel_rect.uv0.y) as i32,
- ),
- )
- }),
- },
- };
-
- self.add_primitive(
- clip_and_scroll,
- info,
- Vec::new(),
- PrimitiveContainer::Image(prim_cpu),
- );
- }
- }
-
- pub fn add_yuv_image(
- &mut self,
- clip_and_scroll: ScrollNodeAndClipChain,
- info: &LayerPrimitiveInfo,
- yuv_data: YuvData,
- color_space: YuvColorSpace,
- image_rendering: ImageRendering,
- ) {
- let format = yuv_data.get_format();
- let yuv_key = match yuv_data {
- YuvData::NV12(plane_0, plane_1) => [plane_0, plane_1, ImageKey::DUMMY],
- YuvData::PlanarYCbCr(plane_0, plane_1, plane_2) => [plane_0, plane_1, plane_2],
- YuvData::InterleavedYCbCr(plane_0) => [plane_0, ImageKey::DUMMY, ImageKey::DUMMY],
- };
-
- let prim = BrushPrimitive::new(
- BrushKind::YuvImage {
- yuv_key,
- format,
- color_space,
- image_rendering,
- },
- None,
- );
-
- self.add_primitive(
- clip_and_scroll,
- info,
- Vec::new(),
- PrimitiveContainer::Brush(prim),
- );
}
/// Compute the contribution (bounding rectangles, and resources) of layers and their
/// primitives in screen space.
fn build_layer_screen_rects_and_cull_layers(
&mut self,
clip_scroll_tree: &ClipScrollTree,
- pipelines: &FastHashMap<PipelineId, ScenePipeline>,
+ pipelines: &FastHashMap<PipelineId, Arc<ScenePipeline>>,
resource_cache: &mut ResourceCache,
gpu_cache: &mut GpuCache,
render_tasks: &mut RenderTaskTree,
profile_counters: &mut FrameProfileCounters,
device_pixel_scale: DevicePixelScale,
scene_properties: &SceneProperties,
local_clip_rects: &mut Vec<LayerRect>,
node_data: &[ClipScrollNodeData],
) -> Option<RenderTaskId> {
profile_scope!("cull");
if self.prim_store.cpu_pictures.is_empty() {
return None
}
// The root picture is always the first one added.
- let root_clip_scroll_node = &clip_scroll_tree.nodes[&clip_scroll_tree.root_reference_frame_id()];
+ let root_clip_scroll_node =
+ &clip_scroll_tree.nodes[clip_scroll_tree.root_reference_frame_index().0];
let display_list = &pipelines
.get(&root_clip_scroll_node.pipeline_id)
.expect("No display list?")
.display_list;
- let frame_context = FrameContext {
+ let frame_context = FrameBuildingContext {
device_pixel_scale,
scene_properties,
pipelines,
screen_rect: self.screen_rect.to_i32(),
clip_scroll_tree,
node_data,
};
- let mut frame_state = FrameState {
+ let mut frame_state = FrameBuildingState {
render_tasks,
profile_counters,
clip_store: &mut self.clip_store,
local_clip_rects,
resource_cache,
gpu_cache,
cached_gradients: &mut self.cached_gradients,
};
let pic_context = PictureContext {
pipeline_id: root_clip_scroll_node.pipeline_id,
perform_culling: true,
prim_runs: mem::replace(&mut self.prim_store.cpu_pictures[0].runs, Vec::new()),
- original_reference_frame_id: None,
+ original_reference_frame_index: None,
display_list,
draw_text_transformed: true,
inv_world_transform: None,
};
let mut pic_state = PictureState::new();
self.prim_store.reset_prim_visibility();
@@ -1776,17 +239,17 @@ impl FrameBuilder {
Some(render_task_id)
}
fn update_scroll_bars(&mut self, clip_scroll_tree: &ClipScrollTree, gpu_cache: &mut GpuCache) {
static SCROLLBAR_PADDING: f32 = 8.0;
for scrollbar_prim in &self.scrollbar_prims {
let metadata = &mut self.prim_store.cpu_metadata[scrollbar_prim.prim_index.0];
- let scroll_frame = &clip_scroll_tree.nodes[&scrollbar_prim.clip_id];
+ let scroll_frame = &clip_scroll_tree.nodes[scrollbar_prim.scroll_frame_index.0];
// Invalidate what's in the cache so it will get rebuilt.
gpu_cache.invalidate(&metadata.gpu_location);
let scrollable_distance = scroll_frame.scrollable_size().height;
if scrollable_distance <= 0.0 {
metadata.local_clip_rect.size = LayerSize::zero();
continue;
@@ -1806,28 +269,27 @@ impl FrameBuilder {
}
pub fn build(
&mut self,
resource_cache: &mut ResourceCache,
gpu_cache: &mut GpuCache,
frame_id: FrameId,
clip_scroll_tree: &mut ClipScrollTree,
- pipelines: &FastHashMap<PipelineId, ScenePipeline>,
- window_size: DeviceUintSize,
+ pipelines: &FastHashMap<PipelineId, Arc<ScenePipeline>>,
device_pixel_scale: DevicePixelScale,
layer: DocumentLayer,
pan: WorldPoint,
texture_cache_profile: &mut TextureCacheProfileCounters,
gpu_cache_profile: &mut GpuCacheProfileCounters,
scene_properties: &SceneProperties,
) -> Frame {
profile_scope!("build");
debug_assert!(
- DeviceUintRect::new(DeviceUintPoint::zero(), window_size)
+ DeviceUintRect::new(DeviceUintPoint::zero(), self.window_size)
.contains_rect(&self.screen_rect)
);
let mut profile_counters = FrameProfileCounters::new();
profile_counters
.total_primitives
.set(self.prim_store.prim_count());
@@ -1921,17 +383,17 @@ impl FrameBuilder {
let gpu_cache_frame_id = gpu_cache.end_frame(gpu_cache_profile);
render_tasks.build();
resource_cache.end_frame();
Frame {
- window_size,
+ window_size: self.window_size,
inner_rect: self.screen_rect,
device_pixel_ratio: device_pixel_scale.0,
background_color: self.background_color,
layer,
profile_counters,
passes,
node_data,
clip_chain_local_clip_rects,
@@ -1947,71 +409,8 @@ impl FrameBuilder {
HitTester::new(
&self.hit_testing_runs,
&clip_scroll_tree,
&self.clip_store
)
}
}
-trait PrimitiveInfoTiler {
- fn decompose(
- &self,
- tile_size: LayerSize,
- tile_spacing: LayerSize,
- max_prims: usize,
- ) -> Vec<LayerPrimitiveInfo>;
-}
-
-impl PrimitiveInfoTiler for LayerPrimitiveInfo {
- fn decompose(
- &self,
- tile_size: LayerSize,
- tile_spacing: LayerSize,
- max_prims: usize,
- ) -> Vec<LayerPrimitiveInfo> {
- let mut prims = Vec::new();
- let tile_repeat = tile_size + tile_spacing;
-
- if tile_repeat.width <= 0.0 ||
- tile_repeat.height <= 0.0 {
- return prims;
- }
-
- if tile_repeat.width < self.rect.size.width ||
- tile_repeat.height < self.rect.size.height {
- let local_clip = self.local_clip.clip_by(&self.rect);
- let rect_p0 = self.rect.origin;
- let rect_p1 = self.rect.bottom_right();
-
- let mut y0 = rect_p0.y;
- while y0 < rect_p1.y {
- let mut x0 = rect_p0.x;
-
- while x0 < rect_p1.x {
- prims.push(LayerPrimitiveInfo {
- rect: LayerRect::new(
- LayerPoint::new(x0, y0),
- tile_size,
- ),
- local_clip,
- is_backface_visible: self.is_backface_visible,
- tag: self.tag,
- });
-
- // Mostly a safety against a crazy number of primitives
- // being generated. If we exceed that amount, just bail
- // out and only draw the maximum amount.
- if prims.len() > max_prims {
- warn!("too many prims found due to repeat/tile. dropping extra prims!");
- return prims;
- }
-
- x0 += tile_repeat.width;
- }
-
- y0 += tile_repeat.height;
- }
- }
-
- prims
- }
-}
--- a/gfx/webrender/src/glyph_rasterizer.rs
+++ b/gfx/webrender/src/glyph_rasterizer.rs
@@ -397,17 +397,17 @@ impl GlyphRasterizer {
// select glyphs that have not been requested yet.
for key in glyph_keys {
match glyph_key_cache.entry(key.clone()) {
Entry::Occupied(mut entry) => {
if let Ok(Some(ref mut glyph_info)) = *entry.get_mut() {
if texture_cache.request(&mut glyph_info.texture_cache_handle, gpu_cache) {
// This case gets hit when we have already rasterized
- // the glyph and stored it in CPU memory, the the glyph
+ // the glyph and stored it in CPU memory, but the glyph
// has been evicted from the texture cache. In which case
// we need to re-upload it to the GPU.
texture_cache.update(
&mut glyph_info.texture_cache_handle,
ImageDescriptor {
width: glyph_info.size.width,
height: glyph_info.size.height,
stride: None,
--- a/gfx/webrender/src/gpu_types.rs
+++ b/gfx/webrender/src/gpu_types.rs
@@ -1,14 +1,14 @@
/* 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/. */
-use api::{LayerToWorldTransform};
-use gpu_cache::GpuCacheAddress;
+use api::{DevicePoint, LayerToWorldTransform, PremultipliedColorF};
+use gpu_cache::{GpuCacheAddress, GpuDataRequest};
use prim_store::EdgeAaSegmentMask;
use render_task::RenderTaskAddress;
// Contains type that must exactly match the same structures declared in GLSL.
#[repr(i32)]
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "capture", derive(Serialize))]
@@ -198,17 +198,16 @@ impl From<BrushInstance> for PrimitiveIn
// Defines how a brush image is stretched onto the primitive.
// In the future, we may draw with segments for each portion
// of the primitive, in which case this will be redundant.
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub enum BrushImageKind {
Simple = 0, // A normal rect
NinePatch = 1, // A nine-patch image (stretch inside segments)
- Mirror = 2, // A top left corner only (mirror across x/y axes)
}
#[derive(Copy, Debug, Clone, PartialEq)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
#[repr(C)]
pub struct ClipScrollNodeIndex(pub u32);
@@ -240,8 +239,38 @@ pub struct ClipChainRectIndex(pub usize)
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
#[repr(C)]
pub enum PictureType {
Image = 1,
TextShadow = 2,
BoxShadow = 3,
}
+
+#[derive(Debug, Copy, Clone)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[repr(C)]
+pub struct ImageSource {
+ pub p0: DevicePoint,
+ pub p1: DevicePoint,
+ pub texture_layer: f32,
+ pub user_data: [f32; 3],
+ pub color: PremultipliedColorF,
+}
+
+impl ImageSource {
+ pub fn write_gpu_blocks(&self, request: &mut GpuDataRequest) {
+ request.push([
+ self.p0.x,
+ self.p0.y,
+ self.p1.x,
+ self.p1.y,
+ ]);
+ request.push([
+ self.texture_layer,
+ self.user_data[0],
+ self.user_data[1],
+ self.user_data[2],
+ ]);
+ request.push(self.color);
+ }
+}
\ No newline at end of file
--- a/gfx/webrender/src/hit_test.rs
+++ b/gfx/webrender/src/hit_test.rs
@@ -1,25 +1,28 @@
/* 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/. */
-use api::{BorderRadius, ClipId, ClipMode, HitTestFlags, HitTestItem, HitTestResult, ItemTag};
-use api::{LayerPoint, LayerPrimitiveInfo, LayerRect, LocalClip, PipelineId, WorldPoint};
+use api::{BorderRadius, ClipMode, HitTestFlags, HitTestItem, HitTestResult, ItemTag, LayerPoint};
+use api::{LayerPrimitiveInfo, LayerRect, LocalClip, PipelineId, WorldPoint};
use clip::{ClipSource, ClipStore, Contains, rounded_rectangle_contains_point};
use clip_scroll_node::{ClipScrollNode, NodeType};
-use clip_scroll_tree::{ClipChainIndex, ClipScrollTree};
+use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, ClipScrollTree};
use internal_types::FastHashMap;
use prim_store::ScrollNodeAndClipChain;
use util::LayerToWorldFastTransform;
/// A copy of important clip scroll node data to use during hit testing. This a copy of
/// data from the ClipScrollTree that will persist as a new frame is under construction,
/// allowing hit tests consistent with the currently rendered frame.
pub struct HitTestClipScrollNode {
+ /// The pipeline id of this node.
+ pipeline_id: PipelineId,
+
/// A particular point must be inside all of these regions to be considered clipped in
/// for the purposes of a hit test.
regions: Vec<HitTestRegion>,
/// World transform for content transformed by this node.
world_content_transform: LayerToWorldFastTransform,
/// World viewport transform for content transformed by this node.
@@ -32,17 +35,17 @@ pub struct HitTestClipScrollNode {
/// A description of a clip chain in the HitTester. This is used to describe
/// hierarchical clip scroll nodes as well as ClipChains, so that they can be
/// handled the same way during hit testing. Once we represent all ClipChains
/// using ClipChainDescriptors, we can get rid of this and just use the
/// ClipChainDescriptor here.
#[derive(Clone)]
struct HitTestClipChainDescriptor {
parent: Option<ClipChainIndex>,
- clips: Vec<ClipId>,
+ clips: Vec<ClipScrollNodeIndex>,
}
impl HitTestClipChainDescriptor {
fn empty() -> HitTestClipChainDescriptor {
HitTestClipChainDescriptor {
parent: None,
clips: Vec::new(),
}
@@ -83,30 +86,32 @@ impl HitTestRegion {
&HitTestRegion::RoundedRectangle(rect, radii, ClipMode::ClipOut) =>
!rounded_rectangle_contains_point(point, &rect, &radii),
}
}
}
pub struct HitTester {
runs: Vec<HitTestingRun>,
- nodes: FastHashMap<ClipId, HitTestClipScrollNode>,
+ nodes: Vec<HitTestClipScrollNode>,
clip_chains: Vec<HitTestClipChainDescriptor>,
+ pipeline_root_nodes: FastHashMap<PipelineId, ClipScrollNodeIndex>,
}
impl HitTester {
pub fn new(
runs: &Vec<HitTestingRun>,
clip_scroll_tree: &ClipScrollTree,
clip_store: &ClipStore
) -> HitTester {
let mut hit_tester = HitTester {
runs: runs.clone(),
- nodes: FastHashMap::default(),
+ nodes: Vec::new(),
clip_chains: Vec::new(),
+ pipeline_root_nodes: FastHashMap::default(),
};
hit_tester.read_clip_scroll_tree(clip_scroll_tree, clip_store);
hit_tester
}
fn read_clip_scroll_tree(
&mut self,
clip_scroll_tree: &ClipScrollTree,
@@ -114,29 +119,36 @@ impl HitTester {
) {
self.nodes.clear();
self.clip_chains.clear();
self.clip_chains.resize(
clip_scroll_tree.clip_chains.len(),
HitTestClipChainDescriptor::empty()
);
- for (id, node) in &clip_scroll_tree.nodes {
- self.nodes.insert(*id, HitTestClipScrollNode {
+ for (index, node) in clip_scroll_tree.nodes.iter().enumerate() {
+ let index = ClipScrollNodeIndex(index);
+
+ // If we haven't already seen a node for this pipeline, record this one as the root
+ // node.
+ self.pipeline_root_nodes.entry(node.pipeline_id).or_insert(index);
+
+ self.nodes.push(HitTestClipScrollNode {
+ pipeline_id: node.pipeline_id,
regions: get_regions_for_clip_scroll_node(node, clip_store),
world_content_transform: node.world_content_transform,
world_viewport_transform: node.world_viewport_transform,
node_origin: node.local_viewport_rect.origin,
});
if let NodeType::Clip { clip_chain_index, .. } = node.node_type {
let clip_chain = self.clip_chains.get_mut(clip_chain_index.0).unwrap();
clip_chain.parent =
clip_scroll_tree.get_clip_chain(clip_chain_index).parent_index;
- clip_chain.clips = vec![*id];
+ clip_chain.clips = vec![index];
}
}
for descriptor in &clip_scroll_tree.clip_chains_descriptors {
let clip_chain = self.clip_chains.get_mut(descriptor.index.0).unwrap();
clip_chain.parent = clip_scroll_tree.get_clip_chain(descriptor.index).parent_index;
clip_chain.clips = descriptor.clips.clone();
}
@@ -158,68 +170,68 @@ impl HitTester {
Some(parent) => self.is_point_clipped_in_for_clip_chain(point, parent, test),
};
if !parent_clipped_in {
test.set_in_clip_chain_cache(clip_chain_index, false);
return false;
}
- for clip_node in &descriptor.clips {
- if !self.is_point_clipped_in_for_node(point, clip_node, test) {
+ for clip_node_index in &descriptor.clips {
+ if !self.is_point_clipped_in_for_node(point, *clip_node_index, test) {
test.set_in_clip_chain_cache(clip_chain_index, false);
return false;
}
}
test.set_in_clip_chain_cache(clip_chain_index, true);
true
}
fn is_point_clipped_in_for_node(
&self,
point: WorldPoint,
- node_id: &ClipId,
+ node_index: ClipScrollNodeIndex,
test: &mut HitTest
) -> bool {
- if let Some(point) = test.node_cache.get(node_id) {
+ if let Some(point) = test.node_cache.get(&node_index) {
return point.is_some();
}
- let node = self.nodes.get(node_id).unwrap();
+ let node = &self.nodes[node_index.0];
let transform = node.world_viewport_transform;
let transformed_point = match transform.inverse() {
Some(inverted) => inverted.transform_point2d(&point),
None => {
- test.node_cache.insert(*node_id, None);
+ test.node_cache.insert(node_index, None);
return false;
}
};
let point_in_layer = transformed_point - node.node_origin.to_vector();
for region in &node.regions {
if !region.contains(&transformed_point) {
- test.node_cache.insert(*node_id, None);
+ test.node_cache.insert(node_index, None);
return false;
}
}
- test.node_cache.insert(*node_id, Some(point_in_layer));
+ test.node_cache.insert(node_index, Some(point_in_layer));
true
}
pub fn hit_test(&self, mut test: HitTest) -> HitTestResult {
let point = test.get_absolute_point(self);
let mut result = HitTestResult::default();
for &HitTestingRun(ref items, ref clip_and_scroll) in self.runs.iter().rev() {
let scroll_node_id = clip_and_scroll.scroll_node_id;
- let scroll_node = &self.nodes[&scroll_node_id];
- let pipeline_id = scroll_node_id.pipeline_id();
- match (test.pipeline_id, clip_and_scroll.scroll_node_id.pipeline_id()) {
+ let scroll_node = &self.nodes[scroll_node_id.0];
+ let pipeline_id = scroll_node.pipeline_id;
+ match (test.pipeline_id, pipeline_id) {
(Some(id), node_id) if node_id != id => continue,
_ => {},
}
let transform = scroll_node.world_content_transform;
let point_in_layer = match transform.inverse() {
Some(inverted) => inverted.transform_point2d(&point),
None => continue,
@@ -238,21 +250,21 @@ impl HitTester {
break;
}
// We need to trigger a lookup against the root reference frame here, because
// items that are clipped by clip chains won't test against that part of the
// hierarchy. If we don't have a valid point for this test, we are likely
// in a situation where the reference frame has an univertible transform, but the
// item's clip does not.
- let root_reference_frame = ClipId::root_reference_frame(pipeline_id);
- if !self.is_point_clipped_in_for_node(point, &root_reference_frame, &mut test) {
+ let root_node_index = self.pipeline_root_nodes[&pipeline_id];
+ if !self.is_point_clipped_in_for_node(point, root_node_index, &mut test) {
continue;
}
- let point_in_viewport = match test.node_cache[&root_reference_frame] {
+ let point_in_viewport = match test.node_cache[&root_node_index] {
Some(point) => point,
None => continue,
};
result.items.push(HitTestItem {
pipeline: pipeline_id,
tag: item.tag,
point_in_viewport,
@@ -262,16 +274,20 @@ impl HitTester {
return result;
}
}
}
result.items.dedup();
result
}
+
+ pub fn get_pipeline_root(&self, pipeline_id: PipelineId) -> &HitTestClipScrollNode {
+ &self.nodes[self.pipeline_root_nodes[&pipeline_id].0]
+ }
}
fn get_regions_for_clip_scroll_node(
node: &ClipScrollNode,
clip_store: &ClipStore
) -> Vec<HitTestRegion> {
let clips = match node.node_type {
NodeType::Clip{ ref handle, .. } => clip_store.get(handle).clips(),
@@ -289,17 +305,17 @@ fn get_regions_for_clip_scroll_node(
}
}).collect()
}
pub struct HitTest {
pipeline_id: Option<PipelineId>,
point: WorldPoint,
flags: HitTestFlags,
- node_cache: FastHashMap<ClipId, Option<LayerPoint>>,
+ node_cache: FastHashMap<ClipScrollNodeIndex, Option<LayerPoint>>,
clip_chain_cache: Vec<Option<bool>>,
}
impl HitTest {
pub fn new(
pipeline_id: Option<PipelineId>,
point: WorldPoint,
flags: HitTestFlags,
@@ -329,13 +345,13 @@ impl HitTest {
}
pub fn get_absolute_point(&self, hit_tester: &HitTester) -> WorldPoint {
if !self.flags.contains(HitTestFlags::POINT_RELATIVE_TO_PIPELINE_VIEWPORT) {
return self.point;
}
let point = &LayerPoint::new(self.point.x, self.point.y);
- self.pipeline_id.and_then(|id| hit_tester.nodes.get(&ClipId::root_reference_frame(id)))
- .map(|node| node.world_viewport_transform.transform_point2d(&point))
- .unwrap_or_else(|| WorldPoint::new(self.point.x, self.point.y))
+ self.pipeline_id.map(|id|
+ hit_tester.get_pipeline_root(id).world_viewport_transform.transform_point2d(&point)
+ ).unwrap_or_else(|| WorldPoint::new(self.point.x, self.point.y))
}
}
--- a/gfx/webrender/src/internal_types.rs
+++ b/gfx/webrender/src/internal_types.rs
@@ -1,16 +1,15 @@
/* 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/. */
-use api::{ClipId, DeviceUintRect, DocumentId};
-use api::{ExternalImageData, ExternalImageId};
+use api::{DebugCommand, DeviceUintRect, DocumentId, ExternalImageData, ExternalImageId};
use api::ImageFormat;
-use api::DebugCommand;
+use clip_scroll_tree::ClipScrollNodeIndex;
use device::TextureFilter;
use renderer::PipelineInfo;
use gpu_cache::GpuCacheUpdateList;
use fxhash::FxHasher;
use profiler::BackendProfileCounters;
use std::{usize, i32};
use std::collections::{HashMap, HashSet};
use std::f32;
@@ -135,25 +134,25 @@ impl TextureUpdateList {
pub struct RenderedDocument {
/// The pipeline info contains:
/// - The last rendered epoch for each pipeline present in the frame.
/// This information is used to know if a certain transformation on the layout has
/// been rendered, which is necessary for reftests.
/// - Pipelines that were removed from the scene.
pub pipeline_info: PipelineInfo,
/// The layers that are currently affected by the over-scrolling animation.
- pub layers_bouncing_back: FastHashSet<ClipId>,
+ pub layers_bouncing_back: FastHashSet<ClipScrollNodeIndex>,
pub frame: tiling::Frame,
}
impl RenderedDocument {
pub fn new(
pipeline_info: PipelineInfo,
- layers_bouncing_back: FastHashSet<ClipId>,
+ layers_bouncing_back: FastHashSet<ClipScrollNodeIndex>,
frame: tiling::Frame,
) -> Self {
RenderedDocument {
pipeline_info,
layers_bouncing_back,
frame,
}
}
--- a/gfx/webrender/src/lib.rs
+++ b/gfx/webrender/src/lib.rs
@@ -61,18 +61,18 @@ mod clip;
mod clip_scroll_node;
mod clip_scroll_tree;
mod debug_colors;
mod debug_font_data;
mod debug_render;
#[cfg(feature = "debugger")]
mod debug_server;
mod device;
+mod display_list_flattener;
mod ellipse;
-mod frame;
mod frame_builder;
mod freelist;
#[cfg(any(target_os = "macos", target_os = "windows"))]
mod gamma_lut;
mod geometry;
mod glyph_cache;
mod glyph_rasterizer;
mod gpu_cache;
@@ -85,16 +85,17 @@ mod print_tree;
mod profiler;
mod query;
mod record;
mod render_backend;
mod render_task;
mod renderer;
mod resource_cache;
mod scene;
+mod scene_builder;
mod segment;
mod spring;
mod texture_allocator;
mod texture_cache;
mod tiling;
mod util;
mod shader_source {
--- a/gfx/webrender/src/picture.rs
+++ b/gfx/webrender/src/picture.rs
@@ -1,17 +1,18 @@
/* 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/. */
-use api::{BoxShadowClipMode, ClipId, ColorF, DeviceIntPoint, DeviceIntRect, FilterOp, LayerPoint};
+use api::{BoxShadowClipMode, ColorF, DeviceIntPoint, DeviceIntRect, FilterOp, LayerPoint};
use api::{LayerRect, LayerToWorldScale, LayerVector2D, MixBlendMode, PipelineId};
use api::{PremultipliedColorF, Shadow};
use box_shadow::{BLUR_SAMPLE_SCALE, BoxShadowCacheKey};
-use frame_builder::{FrameContext, FrameState, PictureState};
+use clip_scroll_tree::ClipScrollNodeIndex;
+use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState};
use gpu_cache::{GpuCacheHandle, GpuDataRequest};
use gpu_types::{BrushImageKind, PictureType};
use prim_store::{BrushKind, BrushPrimitive, PrimitiveIndex, PrimitiveRun, PrimitiveRunLocalRect};
use prim_store::ScrollNodeAndClipChain;
use render_task::{ClearMode, RenderTask, RenderTaskCacheKey};
use render_task::{RenderTaskCacheKeyKind, RenderTaskId, RenderTaskLocation};
use resource_cache::CacheItem;
use scene::{FilterOpHelpers, SceneProperties};
@@ -81,17 +82,17 @@ pub enum PictureKind {
is_in_3d_context: bool,
// If requested as a frame output (for rendering
// pages to a texture), this is the pipeline this
// picture is the root of.
frame_output_pipeline_id: Option<PipelineId>,
// The original reference frame ID for this picture.
// It is only different if this is part of a 3D
// rendering context.
- reference_frame_id: ClipId,
+ reference_frame_index: ClipScrollNodeIndex,
real_local_rect: LayerRect,
// An optional cache handle for storing extra data
// in the GPU cache, depending on the type of
// picture.
extra_gpu_data_handle: GpuCacheHandle,
},
}
@@ -203,28 +204,28 @@ impl PicturePrimitive {
),
}
}
pub fn new_image(
composite_mode: Option<PictureCompositeMode>,
is_in_3d_context: bool,
pipeline_id: PipelineId,
- reference_frame_id: ClipId,
+ reference_frame_index: ClipScrollNodeIndex,
frame_output_pipeline_id: Option<PipelineId>,
) -> Self {
PicturePrimitive {
runs: Vec::new(),
surface: None,
kind: PictureKind::Image {
secondary_render_task_id: None,
composite_mode,
is_in_3d_context,
frame_output_pipeline_id,
- reference_frame_id,
+ reference_frame_index,
real_local_rect: LayerRect::zero(),
extra_gpu_data_handle: GpuCacheHandle::new(),
},
pipeline_id,
cull_children: true,
brush: BrushPrimitive::new(
BrushKind::Picture,
None,
@@ -282,41 +283,27 @@ impl PicturePrimitive {
*content_rect = local_content_rect.inflate(
blur_offset,
blur_offset,
);
content_rect.translate(&offset)
}
- PictureKind::BoxShadow { blur_radius, clip_mode, image_kind, ref mut content_rect, .. } => {
+ PictureKind::BoxShadow { blur_radius, clip_mode, ref mut content_rect, .. } => {
// We need to inflate the content rect if outset.
*content_rect = match clip_mode {
BoxShadowClipMode::Outset => {
- match image_kind {
- BrushImageKind::Mirror => {
- let half_offset = 0.5 * blur_radius * BLUR_SAMPLE_SCALE;
- // If the radii are uniform, we can render just the top
- // left corner and mirror it across the primitive. In
- // this case, shift the content rect to leave room
- // for the blur to take effect.
- local_content_rect
- .translate(&-LayerVector2D::new(half_offset, half_offset))
- .inflate(half_offset, half_offset)
- }
- BrushImageKind::NinePatch | BrushImageKind::Simple => {
- let full_offset = blur_radius * BLUR_SAMPLE_SCALE;
- // For a non-uniform radii, we need to expand
- // the content rect on all sides for the blur.
- local_content_rect.inflate(
- full_offset,
- full_offset,
- )
- }
- }
+ let full_offset = blur_radius * BLUR_SAMPLE_SCALE;
+ // For a non-uniform radii, we need to expand
+ // the content rect on all sides for the blur.
+ local_content_rect.inflate(
+ full_offset,
+ full_offset,
+ )
}
BoxShadowClipMode::Inset => {
local_content_rect
}
};
prim_local_rect
}
@@ -325,18 +312,18 @@ impl PicturePrimitive {
pub fn prepare_for_render(
&mut self,
prim_index: PrimitiveIndex,
prim_screen_rect: &DeviceIntRect,
prim_local_rect: &LayerRect,
pic_state_for_children: PictureState,
pic_state: &mut PictureState,
- frame_context: &FrameContext,
- frame_state: &mut FrameState,
+ frame_context: &FrameBuildingContext,
+ frame_state: &mut FrameBuildingState,
) {
let content_scale = LayerToWorldScale::new(1.0) * frame_context.device_pixel_scale;
match self.kind {
PictureKind::Image {
ref mut secondary_render_task_id,
ref mut extra_gpu_data_handle,
composite_mode,
@@ -637,17 +624,9 @@ impl PicturePrimitive {
}
}
}
PictureKind::BoxShadow { color, .. } => {
request.push(color.premultiplied());
}
}
}
-
- pub fn target_kind(&self) -> RenderTargetKind {
- match self.kind {
- PictureKind::TextShadow { .. } => RenderTargetKind::Color,
- PictureKind::BoxShadow { .. } => RenderTargetKind::Alpha,
- PictureKind::Image { .. } => RenderTargetKind::Color,
- }
- }
}
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -1,49 +1,53 @@
/* 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/. */
-use api::{AlphaType, BorderRadius, BuiltDisplayList, ClipId, ClipMode, ColorF, ComplexClipRegion};
+use api::{AlphaType, BorderRadius, BuiltDisplayList, ClipMode, ColorF, ComplexClipRegion};
use api::{DeviceIntRect, DeviceIntSize, DevicePixelScale, Epoch, ExtendMode, FontRenderMode};
use api::{GlyphInstance, GlyphKey, GradientStop, ImageKey, ImageRendering, ItemRange, ItemTag};
use api::{LayerPoint, LayerRect, LayerSize, LayerToWorldTransform, LayerVector2D, LineOrientation};
use api::{LineStyle, PremultipliedColorF, YuvColorSpace, YuvFormat};
use border::{BorderCornerInstance, BorderEdgeKind};
-use clip_scroll_tree::{ClipChainIndex, CoordinateSystemId};
+use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, CoordinateSystemId};
use clip_scroll_node::ClipScrollNode;
use clip::{ClipChain, ClipChainNode, ClipChainNodeIter, ClipChainNodeRef, ClipSource};
use clip::{ClipSourcesHandle, ClipWorkItem};
-use frame_builder::{FrameContext, FrameState, PictureContext, PictureState, PrimitiveRunContext};
+use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext, PictureState};
+use frame_builder::PrimitiveRunContext;
use glyph_rasterizer::{FontInstance, FontTransform};
use gpu_cache::{GpuBlockData, GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest,
ToGpuBlocks};
use gpu_types::{ClipChainRectIndex};
use picture::{PictureKind, PicturePrimitive};
use render_task::{BlitSource, RenderTask, RenderTaskCacheKey, RenderTaskCacheKeyKind};
use render_task::RenderTaskId;
use renderer::{MAX_VERTEX_TEXTURE_WIDTH};
use resource_cache::{CacheItem, ImageProperties, ImageRequest, ResourceCache};
use segment::SegmentBuilder;
use std::{mem, usize};
-use std::rc::Rc;
+use std::sync::Arc;
use util::{MatrixHelpers, WorldToLayerFastTransform, calculate_screen_bounding_rect};
use util::{pack_as_float, recycle_vec};
const MIN_BRUSH_SPLIT_AREA: f32 = 256.0 * 256.0;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ScrollNodeAndClipChain {
- pub scroll_node_id: ClipId,
+ pub scroll_node_id: ClipScrollNodeIndex,
pub clip_chain_index: ClipChainIndex,
}
impl ScrollNodeAndClipChain {
- pub fn new(scroll_node_id: ClipId, clip_chain_index: ClipChainIndex) -> ScrollNodeAndClipChain {
+ pub fn new(
+ scroll_node_id: ClipScrollNodeIndex,
+ clip_chain_index: ClipChainIndex
+ ) -> ScrollNodeAndClipChain {
ScrollNodeAndClipChain { scroll_node_id, clip_chain_index }
}
}
#[derive(Debug)]
pub struct PrimitiveRun {
pub base_prim_index: PrimitiveIndex,
pub count: usize,
@@ -180,27 +184,21 @@ pub struct PrimitiveMetadata {
pub screen_rect: Option<ScreenRect>,
/// A tag used to identify this primitive outside of WebRender. This is
/// used for returning useful data during hit testing.
pub tag: Option<ItemTag>,
}
#[derive(Debug)]
-pub enum BrushMaskKind {
- //Rect, // TODO(gw): Optimization opportunity for masks with 0 border radii.
- Corner(LayerSize),
- RoundedRect(LayerRect, BorderRadius),
-}
-
-#[derive(Debug)]
pub enum BrushKind {
Mask {
clip_mode: ClipMode,
- kind: BrushMaskKind,
+ rect: LayerRect,
+ radii: BorderRadius,
},
Solid {
color: ColorF,
},
Clear,
Line {
color: PremultipliedColorF,
wavy_line_thickness: f32,
@@ -325,35 +323,29 @@ impl BrushPrimitive {
segment_desc,
}
}
fn write_gpu_blocks(&self, request: &mut GpuDataRequest) {
// has to match VECS_PER_SPECIFIC_BRUSH
match self.kind {
BrushKind::Picture |
- BrushKind::Image { .. } |
BrushKind::YuvImage { .. } => {
}
+ BrushKind::Image { .. } => {
+ request.push([0.0; 4]);
+ }
BrushKind::Solid { color } => {
request.push(color.premultiplied());
}
BrushKind::Clear => {
// Opaque black with operator dest out
request.push(PremultipliedColorF::BLACK);
}
- BrushKind::Mask { clip_mode, kind: BrushMaskKind::Corner(radius) } => {
- request.push([
- radius.width,
- radius.height,
- clip_mode as u32 as f32,
- 0.0,
- ]);
- }
- BrushKind::Mask { clip_mode, kind: BrushMaskKind::RoundedRect(rect, radii) } => {
+ BrushKind::Mask { clip_mode, rect, radii } => {
request.push([
clip_mode as u32 as f32,
0.0,
0.0,
0.0
]);
request.push(rect);
request.push([
@@ -1083,18 +1075,18 @@ impl PrimitiveStore {
fn prepare_prim_for_render_inner(
&mut self,
prim_index: PrimitiveIndex,
prim_run_context: &PrimitiveRunContext,
pic_state_for_children: PictureState,
pic_context: &PictureContext,
pic_state: &mut PictureState,
- frame_context: &FrameContext,
- frame_state: &mut FrameState,
+ frame_context: &FrameBuildingContext,
+ frame_state: &mut FrameBuildingState,
) {
let metadata = &mut self.cpu_metadata[prim_index.0];
match metadata.prim_kind {
PrimitiveKind::Border => {}
PrimitiveKind::Picture => {
self.cpu_pictures[metadata.cpu_prim_index.0]
.prepare_for_render(
prim_index,
@@ -1362,18 +1354,18 @@ impl PrimitiveStore {
}
fn write_brush_segment_description(
brush: &mut BrushPrimitive,
metadata: &PrimitiveMetadata,
prim_run_context: &PrimitiveRunContext,
clips: &Vec<ClipWorkItem>,
has_clips_from_other_coordinate_systems: bool,
- frame_context: &FrameContext,
- frame_state: &mut FrameState,
+ frame_context: &FrameBuildingContext,
+ frame_state: &mut FrameBuildingState,
) {
match brush.segment_desc {
Some(ref segment_desc) => {
// If we already have a segment descriptor, only run through the
// clips list if we haven't already determined the mask kind.
if segment_desc.clip_mask_kind != BrushClipMaskKind::Unknown {
return;
}
@@ -1484,18 +1476,18 @@ impl PrimitiveStore {
fn update_clip_task_for_brush(
&mut self,
prim_run_context: &PrimitiveRunContext,
prim_index: PrimitiveIndex,
clips: &Vec<ClipWorkItem>,
combined_outer_rect: &DeviceIntRect,
has_clips_from_other_coordinate_systems: bool,
pic_state: &mut PictureState,
- frame_context: &FrameContext,
- frame_state: &mut FrameState,
+ frame_context: &FrameBuildingContext,
+ frame_state: &mut FrameBuildingState,
) -> bool {
let metadata = &self.cpu_metadata[prim_index.0];
let brush = match metadata.prim_kind {
PrimitiveKind::Brush => {
&mut self.cpu_brushes[metadata.cpu_prim_index.0]
}
PrimitiveKind::Picture => {
&mut self.cpu_pictures[metadata.cpu_prim_index.0].brush
@@ -1552,18 +1544,18 @@ impl PrimitiveStore {
}
fn update_clip_task(
&mut self,
prim_index: PrimitiveIndex,
prim_run_context: &PrimitiveRunContext,
prim_screen_rect: &DeviceIntRect,
pic_state: &mut PictureState,
- frame_context: &FrameContext,
- frame_state: &mut FrameState,
+ frame_context: &FrameBuildingContext,
+ frame_state: &mut FrameBuildingState,
) -> bool {
self.cpu_metadata[prim_index.0].clip_task_id = None;
let prim_screen_rect = match prim_screen_rect.intersection(&frame_context.screen_rect) {
Some(rect) => rect,
None => {
self.cpu_metadata[prim_index.0].screen_rect = None;
return false;
@@ -1586,17 +1578,17 @@ impl PrimitiveStore {
);
let (screen_inner_rect, screen_outer_rect) =
prim_clips.get_screen_bounds(transform, frame_context.device_pixel_scale);
if let Some(outer) = screen_outer_rect {
combined_outer_rect = combined_outer_rect.and_then(|r| r.intersection(&outer));
}
- Some(Rc::new(ClipChainNode {
+ Some(Arc::new(ClipChainNode {
work_item: ClipWorkItem {
scroll_node_data_index: prim_run_context.scroll_node.node_data_index,
clip_sources: metadata.clip_sources.weak(),
coordinate_system_id: prim_coordinate_system_id,
},
// The local_clip_rect a property of ClipChain nodes that are ClipScrollNodes.
// It's used to calculate a local clipping rectangle before we reach this
// point, so we can set it to zero here. It should be unused from this point
@@ -1683,18 +1675,18 @@ impl PrimitiveStore {
}
pub fn prepare_prim_for_render(
&mut self,
prim_index: PrimitiveIndex,
prim_run_context: &PrimitiveRunContext,
pic_context: &PictureContext,
pic_state: &mut PictureState,
- frame_context: &FrameContext,
- frame_state: &mut FrameState,
+ frame_context: &FrameBuildingContext,
+ frame_state: &mut FrameBuildingState,
) -> Option<LayerRect> {
let mut may_need_clip_mask = true;
let mut pic_state_for_children = PictureState::new();
// Do some basic checks first, that can early out
// without even knowing the local rect.
let (prim_kind, cpu_prim_index) = {
let metadata = &self.cpu_metadata[prim_index.0];
@@ -1716,20 +1708,20 @@ impl PrimitiveStore {
if let PrimitiveKind::Picture = prim_kind {
let pic_context_for_children = {
let pic = &mut self.cpu_pictures[cpu_prim_index.0];
if !pic.resolve_scene_properties(frame_context.scene_properties) {
return None;
}
- let (draw_text_transformed, original_reference_frame_id) = match pic.kind {
- PictureKind::Image { reference_frame_id, .. } => {
- may_need_clip_mask = false;
- (true, Some(reference_frame_id))
+ let (draw_text_transformed, original_reference_frame_index) = match pic.kind {
+ PictureKind::Image { reference_frame_index, composite_mode, .. } => {
+ may_need_clip_mask = composite_mode.is_some();
+ (true, Some(reference_frame_index))
}
PictureKind::BoxShadow { .. } |
PictureKind::TextShadow { .. } => {
(false, None)
}
};
let display_list = &frame_context
@@ -1742,17 +1734,17 @@ impl PrimitiveStore {
.scroll_node
.world_content_transform
.inverse();
PictureContext {
pipeline_id: pic.pipeline_id,
perform_culling: pic.cull_children,
prim_runs: mem::replace(&mut pic.runs, Vec::new()),
- original_reference_frame_id,
+ original_reference_frame_index,
display_list,
draw_text_transformed,
inv_world_transform,
}
};
let result = self.prepare_prim_runs(
&pic_context_for_children,
@@ -1842,31 +1834,31 @@ impl PrimitiveStore {
md.screen_rect = None;
}
}
pub fn prepare_prim_runs(
&mut self,
pic_context: &PictureContext,
pic_state: &mut PictureState,
- frame_context: &FrameContext,
- frame_state: &mut FrameState,
+ frame_context: &FrameBuildingContext,
+ frame_state: &mut FrameBuildingState,
) -> PrimitiveRunLocalRect {
let mut result = PrimitiveRunLocalRect {
local_rect_in_actual_parent_space: LayerRect::zero(),
local_rect_in_original_parent_space: LayerRect::zero(),
};
for run in &pic_context.prim_runs {
// TODO(gw): Perhaps we can restructure this to not need to create
// a new primitive context for every run (if the hash
// lookups ever show up in a profile).
let scroll_node = &frame_context
.clip_scroll_tree
- .nodes[&run.clip_and_scroll.scroll_node_id];
+ .nodes[run.clip_and_scroll.scroll_node_id.0];
let clip_chain = frame_context
.clip_scroll_tree
.get_clip_chain(run.clip_and_scroll.clip_chain_index);
if pic_context.perform_culling {
if !scroll_node.invertible {
debug!("{:?} {:?}: position not invertible", run.base_prim_index, pic_context.pipeline_id);
continue;
@@ -1879,21 +1871,21 @@ impl PrimitiveStore {
}
let parent_relative_transform = pic_context
.inv_world_transform
.map(|inv_parent| {
inv_parent.pre_mul(&scroll_node.world_content_transform)
});
- let original_relative_transform = pic_context.original_reference_frame_id
- .and_then(|original_reference_frame_id| {
+ let original_relative_transform = pic_context.original_reference_frame_index
+ .and_then(|original_reference_frame_index| {
let parent = frame_context
.clip_scroll_tree
- .nodes[&original_reference_frame_id]
+ .nodes[original_reference_frame_index.0]
.world_content_transform;
parent.inverse()
.map(|inv_parent| {
inv_parent.pre_mul(&scroll_node.world_content_transform)
})
});
let clip_chain_rect = match pic_context.perform_culling {
--- a/gfx/webrender/src/record.rs
+++ b/gfx/webrender/src/record.rs
@@ -1,13 +1,13 @@
/* 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/. */
-use api::{ApiMsg, DocumentMsg};
+use api::{ApiMsg, FrameMsg};
use bincode::{serialize, Infinite};
use byteorder::{LittleEndian, WriteBytesExt};
use std::any::TypeId;
use std::fmt::Debug;
use std::fs::File;
use std::io::Write;
use std::mem;
use std::path::PathBuf;
@@ -62,20 +62,21 @@ impl ApiRecordingReceiver for BinaryReco
}
pub fn should_record_msg(msg: &ApiMsg) -> bool {
match *msg {
ApiMsg::UpdateResources(..) |
ApiMsg::AddDocument { .. } |
ApiMsg::DeleteDocument(..) => true,
ApiMsg::UpdateDocument(_, ref msgs) => {
- for msg in msgs {
+ for msg in &msgs.frame_ops {
match *msg {
- DocumentMsg::GetScrollNodeState(..) |
- DocumentMsg::HitTest(..) => {}
+ FrameMsg::GetScrollNodeState(..) |
+ FrameMsg::HitTest(..) => {}
_ => { return true; }
}
}
+
false
}
_ => false,
}
}
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -1,179 +1,363 @@
/* 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/. */
use api::{ApiMsg, BuiltDisplayList, ClearCache, DebugCommand};
#[cfg(feature = "debugger")]
use api::{BuiltDisplayListIter, SpecificDisplayItem};
use api::{DeviceIntPoint, DevicePixelScale, DeviceUintPoint, DeviceUintRect, DeviceUintSize};
-use api::{DocumentId, DocumentLayer, DocumentMsg, HitTestResult, IdNamespace, PipelineId};
-use api::RenderNotifier;
-use api::channel::{MsgReceiver, PayloadReceiver, PayloadReceiverHelperMethods};
-use api::channel::{PayloadSender, PayloadSenderHelperMethods};
+use api::{DocumentId, DocumentLayer, ExternalScrollId, FrameMsg, HitTestResult};
+use api::{IdNamespace, LayerPoint, PipelineId, RenderNotifier, SceneMsg, ScrollClamping};
+use api::{ScrollEventPhase, ScrollLocation, ScrollNodeState, TransactionMsg, WorldPoint};
+use api::channel::{MsgReceiver, Payload};
#[cfg(feature = "capture")]
use api::CaptureBits;
#[cfg(feature = "replay")]
use api::CapturedDocument;
+use clip_scroll_tree::ClipScrollTree;
#[cfg(feature = "debugger")]
use debug_server;
-use frame::FrameContext;
+use display_list_flattener::DisplayListFlattener;
use frame_builder::{FrameBuilder, FrameBuilderConfig};
use gpu_cache::GpuCache;
use hit_test::{HitTest, HitTester};
use internal_types::{DebugOutput, FastHashMap, FastHashSet, RenderedDocument, ResultMsg};
use profiler::{BackendProfileCounters, IpcProfileCounters, ResourceProfileCounters};
use record::ApiRecordingReceiver;
+use renderer::PipelineInfo;
use resource_cache::ResourceCache;
#[cfg(feature = "replay")]
use resource_cache::PlainCacheOwn;
#[cfg(any(feature = "capture", feature = "replay"))]
use resource_cache::PlainResources;
-use scene::Scene;
+use scene::{Scene, SceneProperties};
+use scene_builder::*;
#[cfg(feature = "serialize")]
use serde::{Serialize, Deserialize};
#[cfg(feature = "debugger")]
use serde_json;
#[cfg(any(feature = "capture", feature = "replay"))]
use std::path::PathBuf;
use std::sync::atomic::{ATOMIC_USIZE_INIT, AtomicUsize, Ordering};
-use std::sync::mpsc::Sender;
use std::mem::replace;
+use std::sync::mpsc::{Sender, Receiver};
use std::u32;
+use tiling::Frame;
use time::precise_time_ns;
-#[cfg_attr(feature = "capture", derive(Clone, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
-struct DocumentView {
- window_size: DeviceUintSize,
- inner_rect: DeviceUintRect,
- layer: DocumentLayer,
- pan: DeviceIntPoint,
- device_pixel_ratio: f32,
- page_zoom_factor: f32,
- pinch_zoom_factor: f32,
+#[derive(Clone)]
+pub struct DocumentView {
+ pub window_size: DeviceUintSize,
+ pub inner_rect: DeviceUintRect,
+ pub layer: DocumentLayer,
+ pub pan: DeviceIntPoint,
+ pub device_pixel_ratio: f32,
+ pub page_zoom_factor: f32,
+ pub pinch_zoom_factor: f32,
}
impl DocumentView {
- fn accumulated_scale_factor(&self) -> DevicePixelScale {
+ pub fn accumulated_scale_factor(&self) -> DevicePixelScale {
DevicePixelScale::new(
self.device_pixel_ratio *
self.page_zoom_factor *
self.pinch_zoom_factor
)
}
}
-struct Document {
+struct SceneData {
scene: Scene,
+ removed_pipelines: Vec<PipelineId>,
+}
+
+#[derive(Copy, Clone, PartialEq, PartialOrd, Debug, Eq, Ord)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct FrameId(pub u32);
+
+struct Document {
+ // The latest built scene, usable to build frames.
+ // received from the scene builder thread.
+ current: SceneData,
+ // The scene with the latest transactions applied, not necessarily built yet.
+ // what we will send to the scene builder.
+ pending: SceneData,
+
view: DocumentView,
- frame_ctx: FrameContext,
+
+ /// The ClipScrollTree for this document which tracks both ClipScrollNodes and ClipChains.
+ /// This is stored here so that we are able to preserve scrolling positions between
+ /// rendered frames.
+ clip_scroll_tree: ClipScrollTree,
+
+ /// The id of the current frame.
+ frame_id: FrameId,
+
+ /// A configuration object for the FrameBuilder that we produce.
+ frame_builder_config: FrameBuilderConfig,
+
// the `Option` here is only to deal with borrow checker
frame_builder: Option<FrameBuilder>,
// A set of pipelines that the caller has requested be
// made available as output textures.
output_pipelines: FastHashSet<PipelineId>,
- // The pipeline removal notifications that will be sent in the next frame.
- // Because of async scene building, removed pipelines should not land here
- // as soon as the render backend receives a DocumentMsg::RemovePipeline.
- // Instead, the notification should be added to this list when the first
- // scene that does not contain the pipeline becomes current.
- removed_pipelines: Vec<PipelineId>,
// A helper switch to prevent any frames rendering triggered by scrolling
// messages between `SetDisplayList` and `GenerateFrame`.
// If we allow them, then a reftest that scrolls a few layers before generating
// the first frame would produce inconsistent rendering results, because
// scroll events are not necessarily received in deterministic order.
render_on_scroll: Option<bool>,
// A helper flag to prevent any hit-tests from happening between calls
// to build_scene and rendering the document. In between these two calls,
// hit-tests produce inconsistent results because the clip_scroll_tree
// is out of sync with the display list.
render_on_hittest: bool,
/// A data structure to allow hit testing against rendered frames. This is updated
/// every time we produce a fully rendered frame.
hit_tester: Option<HitTester>,
+
+ /// Properties that are resolved during frame building and can be changed at any time
+ /// without requiring the scene to be re-built.
+ dynamic_properties: SceneProperties,
}
impl Document {
pub fn new(
- config: FrameBuilderConfig,
+ frame_builder_config: FrameBuilderConfig,
window_size: DeviceUintSize,
layer: DocumentLayer,
enable_render_on_scroll: bool,
default_device_pixel_ratio: f32,
) -> Self {
let render_on_scroll = if enable_render_on_scroll {
Some(false)
} else {
None
};
Document {
- scene: Scene::new(),
- removed_pipelines: Vec::new(),
+ current: SceneData {
+ scene: Scene::new(),
+ removed_pipelines: Vec::new(),
+ },
+ pending: SceneData {
+ scene: Scene::new(),
+ removed_pipelines: Vec::new(),
+ },
view: DocumentView {
window_size,
inner_rect: DeviceUintRect::new(DeviceUintPoint::zero(), window_size),
layer,
pan: DeviceIntPoint::zero(),
page_zoom_factor: 1.0,
pinch_zoom_factor: 1.0,
device_pixel_ratio: default_device_pixel_ratio,
},
- frame_ctx: FrameContext::new(config),
- frame_builder: Some(FrameBuilder::empty()),
+ clip_scroll_tree: ClipScrollTree::new(),
+ frame_id: FrameId(0),
+ frame_builder_config,
+ frame_builder: None,
output_pipelines: FastHashSet::default(),
render_on_scroll,
render_on_hittest: false,
hit_tester: None,
+ dynamic_properties: SceneProperties::new(),
}
}
+ fn can_render(&self) -> bool { self.frame_builder.is_some() }
+
+ // TODO: We will probably get rid of this soon and always forward to the scene building thread.
fn build_scene(&mut self, resource_cache: &mut ResourceCache) {
- // this code is why we have `Option`, which is never `None`
- let frame_builder = self.frame_ctx.create_frame_builder(
- self.frame_builder.take().unwrap(),
- &self.scene,
- resource_cache,
- self.view.window_size,
- self.view.inner_rect,
- self.view.accumulated_scale_factor(),
- &self.output_pipelines,
- );
- self.removed_pipelines.extend(self.scene.removed_pipelines.drain(..));
+ let frame_builder = self.create_frame_builder(resource_cache);
+ if !self.current.removed_pipelines.is_empty() {
+ warn!("Built the scene several times without rendering it.");
+ }
+ self.current.removed_pipelines.extend(self.pending.removed_pipelines.drain(..));
self.frame_builder = Some(frame_builder);
+ self.current.scene = self.pending.scene.clone();
+ }
+
+ fn forward_transaction_to_scene_builder(
+ &mut self,
+ transaction_msg: TransactionMsg,
+ document_ops: &DocumentOps,
+ document_id: DocumentId,
+ resource_cache: &ResourceCache,
+ scene_tx: &Sender<SceneBuilderRequest>,
+ ) {
+ // Do as much of the error handling as possible here before dispatching to
+ // the scene builder thread.
+ let build_scene: bool = document_ops.build
+ && self.pending.scene.root_pipeline_id.map(
+ |id| { self.pending.scene.pipelines.contains_key(&id) }
+ ).unwrap_or(false);
+
+ let scene_request = if build_scene {
+ if self.view.window_size.width == 0 || self.view.window_size.height == 0 {
+ error!("ERROR: Invalid window dimensions! Please call api.set_window_size()");
+ }
+
+ Some(SceneRequest {
+ scene: self.pending.scene.clone(),
+ 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 {
+ None
+ };
+
+ scene_tx.send(SceneBuilderRequest::Transaction {
+ scene: scene_request,
+ resource_updates: transaction_msg.resource_updates,
+ frame_ops: transaction_msg.frame_ops,
+ render: transaction_msg.generate_frame,
+ document_id,
+ }).unwrap();
}
fn render(
&mut self,
resource_cache: &mut ResourceCache,
gpu_cache: &mut GpuCache,
resource_profile: &mut ResourceProfileCounters,
) -> RenderedDocument {
let accumulated_scale_factor = self.view.accumulated_scale_factor();
let pan = self.view.pan.to_f32() / accumulated_scale_factor;
- let (hit_tester, rendered_document) = self.frame_ctx.build_rendered_document(
- self.frame_builder.as_mut().unwrap(),
- resource_cache,
- gpu_cache,
- &self.scene.pipelines,
- accumulated_scale_factor,
- self.view.layer,
- pan,
- &mut resource_profile.texture_cache,
- &mut resource_profile.gpu_cache,
- &self.scene.properties,
- replace(&mut self.removed_pipelines, Vec::new()),
+ let removed_pipelines = replace(&mut self.current.removed_pipelines, Vec::new());
+
+ let frame = {
+ let frame_builder = self.frame_builder.as_mut().unwrap();
+ let frame = frame_builder.build(
+ resource_cache,
+ gpu_cache,
+ self.frame_id,
+ &mut self.clip_scroll_tree,
+ &self.current.scene.pipelines,
+ accumulated_scale_factor,
+ self.view.layer,
+ pan,
+ &mut resource_profile.texture_cache,
+ &mut resource_profile.gpu_cache,
+ &self.dynamic_properties,
+ );
+ self.hit_tester = Some(frame_builder.create_hit_tester(&self.clip_scroll_tree));
+ frame
+ };
+
+ self.make_rendered_document(frame, removed_pipelines)
+ }
+
+ pub fn make_rendered_document(&mut self, frame: Frame, removed_pipelines: Vec<PipelineId>) -> RenderedDocument {
+ let nodes_bouncing_back = self.clip_scroll_tree.collect_nodes_bouncing_back();
+ RenderedDocument::new(
+ PipelineInfo {
+ epochs: self.current.scene.pipeline_epochs.clone(),
+ removed_pipelines,
+ },
+ nodes_bouncing_back,
+ frame
+ )
+ }
+
+ pub fn discard_frame_state_for_pipeline(&mut self, pipeline_id: PipelineId) {
+ self.clip_scroll_tree
+ .discard_frame_state_for_pipeline(pipeline_id);
+ }
+
+ /// Returns true if any nodes actually changed position or false otherwise.
+ pub fn scroll(
+ &mut self,
+ scroll_location: ScrollLocation,
+ cursor: WorldPoint,
+ phase: ScrollEventPhase,
+ ) -> bool {
+ self.clip_scroll_tree.scroll(scroll_location, cursor, phase)
+ }
+
+ /// Returns true if the node actually changed position or false otherwise.
+ pub fn scroll_node(
+ &mut self,
+ origin: LayerPoint,
+ id: ExternalScrollId,
+ clamp: ScrollClamping
+ ) -> bool {
+ self.clip_scroll_tree.scroll_node(origin, id, clamp)
+ }
+
+ pub fn tick_scrolling_bounce_animations(&mut self) {
+ self.clip_scroll_tree.tick_scrolling_bounce_animations();
+ }
+
+ pub fn get_scroll_node_state(&self) -> Vec<ScrollNodeState> {
+ self.clip_scroll_tree.get_scroll_node_state()
+ }
+
+ pub fn new_async_scene_ready(&mut self, mut built_scene: BuiltScene) {
+ self.current.scene = built_scene.scene;
+
+ self.frame_builder = Some(built_scene.frame_builder);
+ self.current.removed_pipelines.extend(built_scene.removed_pipelines.drain(..));
+
+ let old_scrolling_states = self.clip_scroll_tree.drain();
+ self.clip_scroll_tree = built_scene.clip_scroll_tree;
+ self.clip_scroll_tree.finalize_and_apply_pending_scroll_offsets(old_scrolling_states);
+
+ // Advance to the next frame.
+ self.frame_id.0 += 1;
+ }
+
+ // When changing this, please make the same modification to build_scene,
+ // which will soon replace this method completely.
+ pub fn create_frame_builder(&mut self, resource_cache: &mut ResourceCache) -> FrameBuilder {
+ if self.view.window_size.width == 0 || self.view.window_size.height == 0 {
+ error!("ERROR: Invalid window dimensions! Please call api.set_window_size()");
+ }
+
+ let old_builder = self.frame_builder.take().unwrap_or_else(FrameBuilder::empty);
+ let root_pipeline_id = match self.pending.scene.root_pipeline_id {
+ Some(root_pipeline_id) => root_pipeline_id,
+ None => return old_builder,
+ };
+
+ if !self.pending.scene.pipelines.contains_key(&root_pipeline_id) {
+ return old_builder;
+ }
+
+ // The DisplayListFlattener will re-create the up-to-date current scene's pipeline epoch
+ // map and clip scroll tree from the information in the pending scene.
+ self.current.scene.pipeline_epochs.clear();
+ let old_scrolling_states = self.clip_scroll_tree.drain();
+
+ let frame_builder = DisplayListFlattener::create_frame_builder(
+ old_builder,
+ &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,
+ &mut self.current.scene.pipeline_epochs,
);
- self.hit_tester = Some(hit_tester);
+ self.clip_scroll_tree.finalize_and_apply_pending_scroll_offsets(old_scrolling_states);
- rendered_document
+ // Advance to the next frame.
+ self.frame_id.0 += 1;
+
+ frame_builder
}
}
struct DocumentOps {
scroll: bool,
build: bool,
render: bool,
composite: bool,
@@ -219,19 +403,23 @@ struct PlainRenderBackend {
}
/// The render backend is responsible for transforming high level display lists into
/// GPU-friendly work which is then submitted to the renderer in the form of a frame::Frame.
///
/// The render backend operates on its own thread.
pub struct RenderBackend {
api_rx: MsgReceiver<ApiMsg>,
- payload_rx: PayloadReceiver,
- payload_tx: PayloadSender,
+ payload_rx: Receiver<Payload>,
result_tx: Sender<ResultMsg>,
+ scene_tx: Sender<SceneBuilderRequest>,
+ scene_rx: Receiver<SceneBuilderResult>,
+
+ payload_buffer: Vec<Payload>,
+
default_device_pixel_ratio: f32,
gpu_cache: GpuCache,
resource_cache: ResourceCache,
frame_config: FrameBuilderConfig,
documents: FastHashMap<DocumentId, Document>,
@@ -239,124 +427,125 @@ pub struct RenderBackend {
recorder: Option<Box<ApiRecordingReceiver>>,
enable_render_on_scroll: bool,
}
impl RenderBackend {
pub fn new(
api_rx: MsgReceiver<ApiMsg>,
- payload_rx: PayloadReceiver,
- payload_tx: PayloadSender,
+ payload_rx: Receiver<Payload>,
result_tx: Sender<ResultMsg>,
+ scene_tx: Sender<SceneBuilderRequest>,
+ scene_rx: Receiver<SceneBuilderResult>,
default_device_pixel_ratio: f32,
resource_cache: ResourceCache,
notifier: Box<RenderNotifier>,
frame_config: FrameBuilderConfig,
recorder: Option<Box<ApiRecordingReceiver>>,
enable_render_on_scroll: bool,
) -> RenderBackend {
// The namespace_id should start from 1.
NEXT_NAMESPACE_ID.fetch_add(1, Ordering::Relaxed);
RenderBackend {
api_rx,
payload_rx,
- payload_tx,
result_tx,
+ scene_tx,
+ scene_rx,
+ payload_buffer: Vec::new(),
default_device_pixel_ratio,
resource_cache,
gpu_cache: GpuCache::new(),
frame_config,
documents: FastHashMap::default(),
notifier,
recorder,
enable_render_on_scroll,
}
}
- fn process_document(
+ fn process_scene_msg(
&mut self,
document_id: DocumentId,
- message: DocumentMsg,
+ message: SceneMsg,
frame_counter: u32,
ipc_profile_counters: &mut IpcProfileCounters,
- resource_profile_counters: &mut ResourceProfileCounters,
) -> DocumentOps {
let doc = self.documents.get_mut(&document_id).expect("No document?");
match message {
- //TODO: move view-related messages in a separate enum?
- DocumentMsg::SetPageZoom(factor) => {
+ SceneMsg::UpdateEpoch(pipeline_id, epoch) => {
+ doc.pending.scene.update_epoch(pipeline_id, epoch);
+
+ DocumentOps::nop()
+ }
+ SceneMsg::SetPageZoom(factor) => {
doc.view.page_zoom_factor = factor.get();
DocumentOps::nop()
}
- DocumentMsg::EnableFrameOutput(pipeline_id, enable) => {
- if enable {
- doc.output_pipelines.insert(pipeline_id);
- } else {
- doc.output_pipelines.remove(&pipeline_id);
- }
- DocumentOps::nop()
- }
- DocumentMsg::SetPinchZoom(factor) => {
+ SceneMsg::SetPinchZoom(factor) => {
doc.view.pinch_zoom_factor = factor.get();
DocumentOps::nop()
}
- DocumentMsg::SetPan(pan) => {
- doc.view.pan = pan;
- DocumentOps::nop()
- }
- DocumentMsg::SetWindowParameters {
+ SceneMsg::SetWindowParameters {
window_size,
inner_rect,
device_pixel_ratio,
} => {
doc.view.window_size = window_size;
doc.view.inner_rect = inner_rect;
doc.view.device_pixel_ratio = device_pixel_ratio;
DocumentOps::nop()
}
- DocumentMsg::SetDisplayList {
+ SceneMsg::SetDisplayList {
epoch,
pipeline_id,
background,
viewport_size,
content_size,
list_descriptor,
preserve_frame_state,
} => {
profile_scope!("SetDisplayList");
- let mut data;
- while {
- data = self.payload_rx.recv_payload().unwrap();
- data.epoch != epoch || data.pipeline_id != pipeline_id
- } {
- self.payload_tx.send_payload(data).unwrap()
- }
+ let data = if let Some(idx) = self.payload_buffer.iter().position(|data|
+ data.epoch == epoch && data.pipeline_id == pipeline_id
+ ) {
+ self.payload_buffer.swap_remove(idx)
+ } else {
+ loop {
+ let data = self.payload_rx.recv().unwrap();
+ if data.epoch == epoch && data.pipeline_id == pipeline_id {
+ break data;
+ } else {
+ self.payload_buffer.push(data);
+ }
+ }
+ };
if let Some(ref mut r) = self.recorder {
r.write_payload(frame_counter, &data.to_data());
}
let built_display_list =
BuiltDisplayList::from_data(data.display_list_data, list_descriptor);
if !preserve_frame_state {
- doc.frame_ctx.discard_frame_state_for_pipeline(pipeline_id);
+ doc.discard_frame_state_for_pipeline(pipeline_id);
}
let display_list_len = built_display_list.data().len();
let (builder_start_time, builder_finish_time, send_start_time) =
built_display_list.times();
let display_list_received_time = precise_time_ns();
{
- doc.scene.set_display_list(
+ doc.pending.scene.set_display_list(
pipeline_id,
epoch,
built_display_list,
background,
viewport_size,
content_size,
);
}
@@ -376,337 +565,422 @@ impl RenderBackend {
send_start_time,
display_list_received_time,
display_list_consumed_time,
display_list_len,
);
DocumentOps::build()
}
- DocumentMsg::UpdateResources(updates) => {
- profile_scope!("UpdateResources");
-
- self.resource_cache.update_resources(
- updates,
- resource_profile_counters
- );
-
- DocumentOps::nop()
- }
- DocumentMsg::UpdateEpoch(pipeline_id, epoch) => {
- doc.scene.update_epoch(pipeline_id, epoch);
- doc.frame_ctx.update_epoch(pipeline_id, epoch);
- DocumentOps::nop()
- }
- DocumentMsg::SetRootPipeline(pipeline_id) => {
+ SceneMsg::SetRootPipeline(pipeline_id) => {
profile_scope!("SetRootPipeline");
- doc.scene.set_root_pipeline_id(pipeline_id);
- if doc.scene.pipelines.get(&pipeline_id).is_some() {
+ doc.pending.scene.set_root_pipeline_id(pipeline_id);
+ if doc.pending.scene.pipelines.get(&pipeline_id).is_some() {
DocumentOps::build()
} else {
DocumentOps::nop()
}
}
- DocumentMsg::RemovePipeline(pipeline_id) => {
+ SceneMsg::RemovePipeline(pipeline_id) => {
profile_scope!("RemovePipeline");
- doc.scene.remove_pipeline(pipeline_id);
+ doc.pending.scene.remove_pipeline(pipeline_id);
+ doc.pending.removed_pipelines.push(pipeline_id);
DocumentOps::nop()
}
- DocumentMsg::Scroll(delta, cursor, move_phase) => {
+ }
+ }
+
+ fn process_frame_msg(
+ &mut self,
+ document_id: DocumentId,
+ message: FrameMsg,
+ ) -> DocumentOps {
+ let doc = self.documents.get_mut(&document_id).expect("No document?");
+
+ match message {
+ FrameMsg::UpdateEpoch(pipeline_id, epoch) => {
+ doc.current.scene.update_epoch(pipeline_id, epoch);
+
+ DocumentOps::nop()
+ }
+ FrameMsg::EnableFrameOutput(pipeline_id, enable) => {
+ if enable {
+ doc.output_pipelines.insert(pipeline_id);
+ } else {
+ doc.output_pipelines.remove(&pipeline_id);
+ }
+ DocumentOps::nop()
+ }
+ FrameMsg::Scroll(delta, cursor, move_phase) => {
profile_scope!("Scroll");
- let should_render = doc.frame_ctx.scroll(delta, cursor, move_phase)
+ let should_render = doc.scroll(delta, cursor, move_phase)
&& doc.render_on_scroll == Some(true);
DocumentOps {
scroll: true,
render: should_render,
composite: should_render,
..DocumentOps::nop()
}
}
- DocumentMsg::HitTest(pipeline_id, point, flags, tx) => {
+ FrameMsg::HitTest(pipeline_id, point, flags, tx) => {
let result = match doc.hit_tester {
Some(ref hit_tester) => {
hit_tester.hit_test(HitTest::new(pipeline_id, point, flags))
}
None => HitTestResult { items: Vec::new() },
};
tx.send(result).unwrap();
DocumentOps::nop()
}
- DocumentMsg::ScrollNodeWithId(origin, id, clamp) => {
+ FrameMsg::SetPan(pan) => {
+ doc.view.pan = pan;
+ DocumentOps::nop()
+ }
+ FrameMsg::ScrollNodeWithId(origin, id, clamp) => {
profile_scope!("ScrollNodeWithScrollId");
- let should_render = doc.frame_ctx.scroll_node(origin, id, clamp)
+ let should_render = doc.scroll_node(origin, id, clamp)
&& doc.render_on_scroll == Some(true);
DocumentOps {
scroll: true,
render: should_render,
composite: should_render,
..DocumentOps::nop()
}
}
- DocumentMsg::TickScrollingBounce => {
+ FrameMsg::TickScrollingBounce => {
profile_scope!("TickScrollingBounce");
- doc.frame_ctx.tick_scrolling_bounce_animations();
+ doc.tick_scrolling_bounce_animations();
let should_render = doc.render_on_scroll == Some(true);
DocumentOps {
scroll: true,
render: should_render,
composite: should_render,
..DocumentOps::nop()
}
}
- DocumentMsg::GetScrollNodeState(tx) => {
+ FrameMsg::GetScrollNodeState(tx) => {
profile_scope!("GetScrollNodeState");
- tx.send(doc.frame_ctx.get_scroll_node_state()).unwrap();
+ tx.send(doc.get_scroll_node_state()).unwrap();
DocumentOps::nop()
}
- DocumentMsg::UpdateDynamicProperties(property_bindings) => {
- // Ideally, when there are property bindings present,
- // we won't need to rebuild the entire frame here.
- // However, to avoid conflicts with the ongoing work to
- // refactor how scroll roots + transforms work, this
- // just rebuilds the frame if there are animated property
- // bindings present for now.
- // TODO(gw): Once the scrolling / reference frame changes
- // are completed, optimize the internals of
- // animated properties to not require a full
- // rebuild of the frame!
- doc.scene.properties.set_properties(property_bindings);
+ FrameMsg::UpdateDynamicProperties(property_bindings) => {
+ doc.dynamic_properties.set_properties(property_bindings);
DocumentOps::build()
}
- DocumentMsg::GenerateFrame => {
- let mut op = DocumentOps::nop();
-
- if let Some(ref mut ros) = doc.render_on_scroll {
- *ros = true;
- }
-
- if doc.scene.root_pipeline_id.is_some() {
- op.render = true;
- op.composite = true;
- }
-
- op
- }
}
}
fn next_namespace_id(&self) -> IdNamespace {
IdNamespace(NEXT_NAMESPACE_ID.fetch_add(1, Ordering::Relaxed) as u32)
}
pub fn run(&mut self, mut profile_counters: BackendProfileCounters) {
let mut frame_counter: u32 = 0;
loop {
profile_scope!("handle_msg");
- let msg = match self.api_rx.recv() {
+ while let Ok(msg) = self.scene_rx.try_recv() {
+ match msg {
+ SceneBuilderResult::Transaction {
+ document_id,
+ mut built_scene,
+ resource_updates,
+ frame_ops,
+ render,
+ } => {
+ if let Some(doc) = self.documents.get_mut(&document_id) {
+ if let Some(mut built_scene) = built_scene.take() {
+ doc.new_async_scene_ready(built_scene);
+ doc.render_on_hittest = true;
+ }
+ } else {
+ // The document was removed while we were building it, skip it.
+ // TODO: we might want to just ensure that removed documents are
+ // always forwarded to the scene builder thread to avoid this case.
+ continue;
+ }
+
+ let transaction_msg = TransactionMsg {
+ scene_ops: Vec::new(),
+ frame_ops,
+ resource_updates,
+ generate_frame: render,
+ use_scene_builder_thread: false,
+ };
+
+ if !transaction_msg.is_empty() {
+ self.update_document(
+ document_id,
+ transaction_msg,
+ &mut frame_counter,
+ &mut profile_counters
+ );
+ }
+ }
+ }
+ }
+
+ let keep_going = match self.api_rx.recv() {
Ok(msg) => {
if let Some(ref mut r) = self.recorder {
r.write_msg(frame_counter, &msg);
}
- msg
+ self.process_api_msg(msg, &mut profile_counters, &mut frame_counter)
}
- Err(..) => {
- self.notifier.shut_down();
- break;
- }
+ Err(..) => { false }
};
- match msg {
- ApiMsg::UpdateResources(updates) => {
- self.resource_cache
- .update_resources(updates, &mut profile_counters.resources);
- }
- ApiMsg::GetGlyphDimensions(instance_key, glyph_keys, tx) => {
- let mut glyph_dimensions = Vec::with_capacity(glyph_keys.len());
- if let Some(font) = self.resource_cache.get_font_instance(instance_key) {
- for glyph_key in &glyph_keys {
- let glyph_dim = self.resource_cache.get_glyph_dimensions(&font, glyph_key);
- glyph_dimensions.push(glyph_dim);
- }
- }
- tx.send(glyph_dimensions).unwrap();
- }
- ApiMsg::GetGlyphIndices(font_key, text, tx) => {
- let mut glyph_indices = Vec::new();
- for ch in text.chars() {
- let index = self.resource_cache.get_glyph_index(font_key, ch);
- glyph_indices.push(index);
- }
- tx.send(glyph_indices).unwrap();
- }
- ApiMsg::CloneApi(sender) => {
- sender.send(self.next_namespace_id()).unwrap();
- }
- ApiMsg::AddDocument(document_id, initial_size, layer) => {
- let document = Document::new(
- self.frame_config.clone(),
- initial_size,
- layer,
- self.enable_render_on_scroll,
- self.default_device_pixel_ratio,
- );
- self.documents.insert(document_id, document);
- }
- ApiMsg::DeleteDocument(document_id) => {
- self.documents.remove(&document_id);
- }
- ApiMsg::ExternalEvent(evt) => {
- self.notifier.external_event(evt);
- }
- ApiMsg::ClearNamespace(namespace_id) => {
- self.resource_cache.clear_namespace(namespace_id);
- let document_ids = self.documents
- .keys()
- .filter(|did| did.0 == namespace_id)
- .cloned()
- .collect::<Vec<_>>();
- for document in document_ids {
- self.documents.remove(&document);
+ if !keep_going {
+ let _ = self.scene_tx.send(SceneBuilderRequest::Stop);
+ self.notifier.shut_down();
+ break;
+ }
+ }
+ }
+
+ fn process_api_msg(
+ &mut self,
+ msg: ApiMsg,
+ profile_counters: &mut BackendProfileCounters,
+ frame_counter: &mut u32,
+ ) -> bool {
+ match msg {
+ ApiMsg::WakeUp => {}
+ ApiMsg::UpdateResources(updates) => {
+ self.resource_cache
+ .update_resources(updates, &mut profile_counters.resources);
+ }
+ ApiMsg::GetGlyphDimensions(instance_key, glyph_keys, tx) => {
+ let mut glyph_dimensions = Vec::with_capacity(glyph_keys.len());
+ if let Some(font) = self.resource_cache.get_font_instance(instance_key) {
+ for glyph_key in &glyph_keys {
+ let glyph_dim = self.resource_cache.get_glyph_dimensions(&font, glyph_key);
+ glyph_dimensions.push(glyph_dim);
}
}
- ApiMsg::MemoryPressure => {
- // This is drastic. It will basically flush everything out of the cache,
- // and the next frame will have to rebuild all of its resources.
- // We may want to look into something less extreme, but on the other hand this
- // should only be used in situations where are running low enough on memory
- // that we risk crashing if we don't do something about it.
- // The advantage of clearing the cache completely is that it gets rid of any
- // remaining fragmentation that could have persisted if we kept around the most
- // recently used resources.
- self.resource_cache.clear(ClearCache::all());
-
- let pending_update = self.resource_cache.pending_updates();
- let msg = ResultMsg::UpdateResources {
- updates: pending_update,
- cancel_rendering: true,
- };
- self.result_tx.send(msg).unwrap();
- self.notifier.wake_up();
+ tx.send(glyph_dimensions).unwrap();
+ }
+ ApiMsg::GetGlyphIndices(font_key, text, tx) => {
+ let mut glyph_indices = Vec::new();
+ for ch in text.chars() {
+ let index = self.resource_cache.get_glyph_index(font_key, ch);
+ glyph_indices.push(index);
}
- ApiMsg::DebugCommand(option) => {
- let msg = match option {
- DebugCommand::EnableDualSourceBlending(enable) => {
- // Set in the config used for any future documents
- // that are created.
- self.frame_config
- .dual_source_blending_is_enabled = enable;
-
- // Set for any existing documents.
- for (_, doc) in &mut self.documents {
- doc.frame_ctx
- .frame_builder_config
- .dual_source_blending_is_enabled = enable;
- }
-
- // We don't want to forward this message to the renderer.
- continue;
- }
- DebugCommand::FetchDocuments => {
- let json = self.get_docs_for_debugger();
- ResultMsg::DebugOutput(DebugOutput::FetchDocuments(json))
- }
- DebugCommand::FetchClipScrollTree => {
- let json = self.get_clip_scroll_tree_for_debugger();
- ResultMsg::DebugOutput(DebugOutput::FetchClipScrollTree(json))
- }
- #[cfg(feature = "capture")]
- DebugCommand::SaveCapture(root, bits) => {
- let output = self.save_capture(root, bits, &mut profile_counters);
- ResultMsg::DebugOutput(output)
- },
- #[cfg(feature = "replay")]
- DebugCommand::LoadCapture(root, tx) => {
- NEXT_NAMESPACE_ID.fetch_add(1, Ordering::Relaxed);
- frame_counter += 1;
-
- self.load_capture(&root, &mut profile_counters);
-
- for (id, doc) in &self.documents {
- let captured = CapturedDocument {
- document_id: *id,
- root_pipeline_id: doc.scene.root_pipeline_id,
- window_size: doc.view.window_size,
- };
- tx.send(captured).unwrap();
- }
- // Note: we can't pass `LoadCapture` here since it needs to arrive
- // before the `PublishDocument` messages sent by `load_capture`.
- continue
- }
- DebugCommand::ClearCaches(mask) => {
- self.resource_cache.clear(mask);
- continue
- }
- _ => ResultMsg::DebugCommand(option),
- };
- self.result_tx.send(msg).unwrap();
- self.notifier.wake_up();
- }
- ApiMsg::ShutDown => {
- self.notifier.shut_down();
- break;
- }
- ApiMsg::UpdateDocument(document_id, doc_msgs) => {
- self.update_document(
- document_id,
- doc_msgs,
- &mut frame_counter,
- &mut profile_counters
- )
+ tx.send(glyph_indices).unwrap();
+ }
+ ApiMsg::CloneApi(sender) => {
+ sender.send(self.next_namespace_id()).unwrap();
+ }
+ ApiMsg::AddDocument(document_id, initial_size, layer) => {
+ let document = Document::new(
+ self.frame_config.clone(),
+ initial_size,
+ layer,
+ self.enable_render_on_scroll,
+ self.default_device_pixel_ratio,
+ );
+ self.documents.insert(document_id, document);
+ }
+ ApiMsg::DeleteDocument(document_id) => {
+ self.documents.remove(&document_id);
+ }
+ ApiMsg::ExternalEvent(evt) => {
+ self.notifier.external_event(evt);
+ }
+ ApiMsg::ClearNamespace(namespace_id) => {
+ self.resource_cache.clear_namespace(namespace_id);
+ let document_ids = self.documents
+ .keys()
+ .filter(|did| did.0 == namespace_id)
+ .cloned()
+ .collect::<Vec<_>>();
+ for document in document_ids {
+ self.documents.remove(&document);
}
}
+ ApiMsg::MemoryPressure => {
+ // This is drastic. It will basically flush everything out of the cache,
+ // and the next frame will have to rebuild all of its resources.
+ // We may want to look into something less extreme, but on the other hand this
+ // should only be used in situations where are running low enough on memory
+ // that we risk crashing if we don't do something about it.
+ // The advantage of clearing the cache completely is that it gets rid of any
+ // remaining fragmentation that could have persisted if we kept around the most
+ // recently used resources.
+ self.resource_cache.clear(ClearCache::all());
+
+ let pending_update = self.resource_cache.pending_updates();
+ let msg = ResultMsg::UpdateResources {
+ updates: pending_update,
+ cancel_rendering: true,
+ };
+ self.result_tx.send(msg).unwrap();
+ self.notifier.wake_up();
+ }
+ ApiMsg::DebugCommand(option) => {
+ let msg = match option {
+ DebugCommand::EnableDualSourceBlending(enable) => {
+ // Set in the config used for any future documents
+ // that are created.
+ self.frame_config
+ .dual_source_blending_is_enabled = enable;
+
+ // Set for any existing documents.
+ for (_, doc) in &mut self.documents {
+ doc.frame_builder_config .dual_source_blending_is_enabled = enable;
+ }
+
+ // We don't want to forward this message to the renderer.
+ return true;
+ }
+ DebugCommand::FetchDocuments => {
+ let json = self.get_docs_for_debugger();
+ ResultMsg::DebugOutput(DebugOutput::FetchDocuments(json))
+ }
+ DebugCommand::FetchClipScrollTree => {
+ let json = self.get_clip_scroll_tree_for_debugger();
+ ResultMsg::DebugOutput(DebugOutput::FetchClipScrollTree(json))
+ }
+ #[cfg(feature = "capture")]
+ DebugCommand::SaveCapture(root, bits) => {
+ let output = self.save_capture(root, bits, profile_counters);
+ ResultMsg::DebugOutput(output)
+ },
+ #[cfg(feature = "replay")]
+ DebugCommand::LoadCapture(root, tx) => {
+ NEXT_NAMESPACE_ID.fetch_add(1, Ordering::Relaxed);
+ *frame_counter += 1;
+
+ self.load_capture(&root, profile_counters);
+
+ for (id, doc) in &self.documents {
+ let captured = CapturedDocument {
+ document_id: *id,
+ root_pipeline_id: doc.current.scene.root_pipeline_id,
+ window_size: doc.view.window_size,
+ };
+ tx.send(captured).unwrap();
+ }
+ // Note: we can't pass `LoadCapture` here since it needs to arrive
+ // before the `PublishDocument` messages sent by `load_capture`.
+ return true;
+ }
+ DebugCommand::ClearCaches(mask) => {
+ self.resource_cache.clear(mask);
+ return true;
+ }
+ _ => ResultMsg::DebugCommand(option),
+ };
+ self.result_tx.send(msg).unwrap();
+ self.notifier.wake_up();
+ }
+ ApiMsg::ShutDown => {
+ return false;
+ }
+ ApiMsg::UpdateDocument(document_id, doc_msgs) => {
+ self.update_document(
+ document_id,
+ doc_msgs,
+ frame_counter,
+ profile_counters
+ )
+ }
}
+
+ true
}
fn update_document(
&mut self,
document_id: DocumentId,
- doc_msgs: Vec<DocumentMsg>,
+ mut transaction_msg: TransactionMsg,
frame_counter: &mut u32,
profile_counters: &mut BackendProfileCounters,
) {
let mut op = DocumentOps::nop();
- for doc_msg in doc_msgs {
+
+ for scene_msg in transaction_msg.scene_ops.drain(..) {
let _timer = profile_counters.total_time.timer();
op.combine(
- self.process_document(
+ self.process_scene_msg(
document_id,
- doc_msg,
+ scene_msg,
*frame_counter,
&mut profile_counters.ipc,
- &mut profile_counters.resources,
)
);
}
- debug_assert!(op.render || !op.composite);
+ if transaction_msg.use_scene_builder_thread && !transaction_msg.is_empty() {
+ let doc = self.documents.get_mut(&document_id).unwrap();
+ doc.forward_transaction_to_scene_builder(
+ transaction_msg,
+ &op,
+ document_id,
+ &self.resource_cache,
+ &self.scene_tx,
+ );
+
+ return;
+ }
+
+ self.resource_cache.update_resources(
+ transaction_msg.resource_updates,
+ &mut profile_counters.resources,
+ );
+
+ if op.build {
+ let doc = self.documents.get_mut(&document_id).unwrap();
+ let _timer = profile_counters.total_time.timer();
+ profile_scope!("build scene");
+
+ doc.build_scene(&mut self.resource_cache);
+ doc.render_on_hittest = true;
+ }
+
+ for frame_msg in transaction_msg.frame_ops {
+ let _timer = profile_counters.total_time.timer();
+ op.combine(self.process_frame_msg(document_id, frame_msg));
+ }
let doc = self.documents.get_mut(&document_id).unwrap();
- if op.build {
- let _timer = profile_counters.total_time.timer();
- profile_scope!("build scene");
- doc.build_scene(&mut self.resource_cache);
- doc.render_on_hittest = true;
+ if !doc.can_render() {
+ // TODO: this happens if we are building the first scene asynchronously and
+ // scroll at the same time. we should keep track of the fact that we skipped
+ // composition here and do it as soon as we receive the scene.
+ op.render = false;
+ op.composite = false;
}
+ if transaction_msg.generate_frame {
+ if let Some(ref mut ros) = doc.render_on_scroll {
+ *ros = true;
+ }
+
+ if doc.current.scene.root_pipeline_id.is_some() {
+ op.render = true;
+ op.composite = true;
+ }
+ }
+
+ debug_assert!(op.render || !op.composite);
+
if op.render {
profile_scope!("generate frame");
*frame_counter += 1;
// borrow ck hack for profile_counters
let (pending_update, rendered_document) = {
let _timer = profile_counters.total_time.timer();
@@ -791,17 +1065,17 @@ impl RenderBackend {
#[cfg(feature = "debugger")]
fn get_docs_for_debugger(&self) -> String {
let mut docs = debug_server::DocumentList::new();
for (_, doc) in &self.documents {
let mut debug_doc = debug_server::TreeNode::new("document");
- for (_, pipeline) in &doc.scene.pipelines {
+ for (_, pipeline) in &doc.current.scene.pipelines {
let mut debug_dl = debug_server::TreeNode::new("display-list");
self.traverse_items(&mut pipeline.display_list.iter(), &mut debug_dl);
debug_doc.add_child(debug_dl);
}
docs.add(debug_doc);
}
@@ -819,19 +1093,17 @@ impl RenderBackend {
for (_, doc) in &self.documents {
let debug_node = debug_server::TreeNode::new("document clip-scroll tree");
let mut builder = debug_server::TreeNodeBuilder::new(debug_node);
// TODO(gw): Restructure the storage of clip-scroll tree, clip store
// etc so this isn't so untidy.
let clip_store = &doc.frame_builder.as_ref().unwrap().clip_store;
- doc.frame_ctx
- .get_clip_scroll_tree()
- .print_with(clip_store, &mut builder);
+ doc.clip_scroll_tree.print_with(clip_store, &mut builder);
debug_root.add(builder.build());
}
serde_json::to_string(&debug_root).unwrap()
}
}
@@ -882,17 +1154,17 @@ impl RenderBackend {
debug!("capture: saving {:?}", root);
let (resources, deferred) = self.resource_cache.save_capture(&root);
let config = CaptureConfig::new(root, bits);
for (&id, doc) in &mut self.documents {
debug!("\tdocument {:?}", id);
if config.bits.contains(CaptureBits::SCENE) {
let file_name = format!("scene-{}-{}", (id.0).0, id.1);
- config.serialize(&doc.scene, file_name);
+ config.serialize(&doc.current.scene, file_name);
}
if config.bits.contains(CaptureBits::FRAME) {
let rendered_document = doc.render(
&mut self.resource_cache,
&mut self.gpu_cache,
&mut profile_counters.resources,
);
//TODO: write down full `RenderedDocument`?
@@ -941,17 +1213,16 @@ impl RenderBackend {
#[cfg(feature = "replay")]
fn load_capture(
&mut self,
root: &PathBuf,
profile_counters: &mut BackendProfileCounters,
) {
use capture::CaptureConfig;
- use tiling::Frame;
debug!("capture: loading {:?}", root);
let backend = CaptureConfig::deserialize::<PlainRenderBackend, _>(root, "backend")
.expect("Unable to open backend.ron");
let caches_maybe = CaptureConfig::deserialize::<PlainCacheOwn, _>(root, "resource_cache");
// Note: it would be great to have `RenderBackend` to be split
// rather explicitly on what's used before and after scene building
@@ -975,32 +1246,41 @@ impl RenderBackend {
for (id, view) in backend.documents {
debug!("\tdocument {:?}", id);
let scene_name = format!("scene-{}-{}", (id.0).0, id.1);
let scene = CaptureConfig::deserialize::<Scene, _>(root, &scene_name)
.expect(&format!("Unable to open {}.ron", scene_name));
let mut doc = Document {
- scene,
+ current: SceneData {
+ scene: scene.clone(),
+ removed_pipelines: Vec::new(),
+ },
+ pending: SceneData {
+ scene,
+ removed_pipelines: Vec::new(),
+ },
view,
- frame_ctx: FrameContext::new(self.frame_config.clone()),
+ clip_scroll_tree: ClipScrollTree::new(),
+ frame_id: FrameId(0),
+ frame_builder_config: self.frame_config.clone(),
frame_builder: Some(FrameBuilder::empty()),
output_pipelines: FastHashSet::default(),
render_on_scroll: None,
render_on_hittest: false,
- removed_pipelines: Vec::new(),
+ dynamic_properties: SceneProperties::new(),
hit_tester: None,
};
let frame_name = format!("frame-{}-{}", (id.0).0, id.1);
let render_doc = match CaptureConfig::deserialize::<Frame, _>(root, frame_name) {
Some(frame) => {
info!("\tloaded a built frame with {} passes", frame.passes.len());
- doc.frame_ctx.make_rendered_document(frame, Vec::new())
+ doc.make_rendered_document(frame, Vec::new())
}
None => {
doc.build_scene(&mut self.resource_cache);
doc.render(
&mut self.resource_cache,
&mut self.gpu_cache,
&mut profile_counters.resources,
)
@@ -1019,8 +1299,9 @@ impl RenderBackend {
self.result_tx.send(msg_publish).unwrap();
profile_counters.reset();
self.notifier.new_document_ready(id, false, true);
self.documents.insert(id, doc);
}
}
}
+
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -3,18 +3,18 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, ImageDescriptor, ImageFormat};
use api::PremultipliedColorF;
use box_shadow::BoxShadowCacheKey;
use clip::ClipWorkItem;
use clip_scroll_tree::CoordinateSystemId;
use device::TextureFilter;
-use gpu_cache::GpuCache;
-use gpu_types::PictureType;
+use gpu_cache::{GpuCache, GpuCacheHandle};
+use gpu_types::{ImageSource, PictureType};
use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
use picture::ContentOrigin;
use prim_store::{PrimitiveIndex, ImageCacheKey};
#[cfg(feature = "debugger")]
use print_tree::{PrintTreePrinter};
use resource_cache::CacheItem;
use std::{cmp, ops, usize, f32, i32};
use texture_cache::{TextureCache, TextureCacheHandle};
@@ -158,26 +158,28 @@ pub struct CacheMaskTask {
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct PictureTask {
pub prim_index: PrimitiveIndex,
pub target_kind: RenderTargetKind,
pub content_origin: ContentOrigin,
pub color: PremultipliedColorF,
pub pic_type: PictureType,
+ pub uv_rect_handle: GpuCacheHandle,
}
#[derive(Debug)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct BlurTask {
pub blur_std_deviation: f32,
pub target_kind: RenderTargetKind,
pub color: PremultipliedColorF,
pub scale_factor: f32,
+ pub uv_rect_handle: GpuCacheHandle,
}
impl BlurTask {
#[cfg(feature = "debugger")]
fn print_with<T: PrintTreePrinter>(&self, pt: &mut T) {
pt.add_item(format!("std deviation: {}", self.blur_std_deviation));
pt.add_item(format!("target: {:?}", self.target_kind));
pt.add_item(format!("scale: {}", self.scale_factor));
@@ -262,16 +264,17 @@ impl RenderTask {
children,
location,
kind: RenderTaskKind::Picture(PictureTask {
prim_index,
target_kind,
content_origin,
color,
pic_type,
+ uv_rect_handle: GpuCacheHandle::new(),
}),
clear_mode,
saved_index: None,
}
}
pub fn new_readback(screen_rect: DeviceIntRect) -> Self {
RenderTask {
@@ -379,31 +382,33 @@ impl RenderTask {
let blur_task_v = RenderTask {
children: vec![downscaling_src_task_id],
location: RenderTaskLocation::Dynamic(None, adjusted_blur_target_size),
kind: RenderTaskKind::VerticalBlur(BlurTask {
blur_std_deviation: adjusted_blur_std_deviation,
target_kind,
color,
scale_factor,
+ uv_rect_handle: GpuCacheHandle::new(),
}),
clear_mode,
saved_index: None,
};
let blur_task_v_id = render_tasks.add(blur_task_v);
let blur_task_h = RenderTask {
children: vec![blur_task_v_id],
location: RenderTaskLocation::Dynamic(None, adjusted_blur_target_size),
kind: RenderTaskKind::HorizontalBlur(BlurTask {
blur_std_deviation: adjusted_blur_std_deviation,
target_kind,
color,
scale_factor,
+ uv_rect_handle: GpuCacheHandle::new(),
}),
clear_mode,
saved_index: None,
};
(blur_task_h, scale_factor)
}
@@ -502,16 +507,34 @@ impl RenderTask {
data2[0],
data2[1],
data2[2],
data2[3],
]
}
}
+ pub fn get_texture_handle(&self) -> &GpuCacheHandle {
+ match self.kind {
+ RenderTaskKind::Picture(ref info) => {
+ &info.uv_rect_handle
+ }
+ RenderTaskKind::VerticalBlur(ref info) |
+ RenderTaskKind::HorizontalBlur(ref info) => {
+ &info.uv_rect_handle
+ }
+ RenderTaskKind::Readback(..) |
+ RenderTaskKind::Scaling(..) |
+ RenderTaskKind::Blit(..) |
+ RenderTaskKind::CacheMask(..) => {
+ panic!("texture handle not supported for this task kind");
+ }
+ }
+ }
+
pub fn get_dynamic_size(&self) -> DeviceIntSize {
match self.location {
RenderTaskLocation::Fixed(..) => DeviceIntSize::zero(),
RenderTaskLocation::Dynamic(_, size) => size,
RenderTaskLocation::TextureCache(_, _, rect) => rect.size,
}
}
@@ -586,16 +609,50 @@ impl RenderTask {
RenderTaskKind::Readback(..) |
RenderTaskKind::HorizontalBlur(..) |
RenderTaskKind::Scaling(..) |
RenderTaskKind::Blit(..) => false,
RenderTaskKind::CacheMask(..) => true,
}
}
+ pub fn prepare_for_render(
+ &mut self,
+ gpu_cache: &mut GpuCache,
+ ) {
+ let (target_rect, target_index) = self.get_target_rect();
+
+ let (cache_handle, color) = match self.kind {
+ RenderTaskKind::HorizontalBlur(ref mut info) |
+ RenderTaskKind::VerticalBlur(ref mut info) => {
+ (&mut info.uv_rect_handle, info.color)
+ }
+ RenderTaskKind::Picture(ref mut info) => {
+ (&mut info.uv_rect_handle, info.color)
+ }
+ RenderTaskKind::Readback(..) |
+ RenderTaskKind::Scaling(..) |
+ RenderTaskKind::Blit(..) |
+ RenderTaskKind::CacheMask(..) => {
+ return;
+ }
+ };
+
+ if let Some(mut request) = gpu_cache.request(cache_handle) {
+ let image_source = ImageSource {
+ p0: target_rect.origin.to_f32(),
+ p1: target_rect.bottom_right().to_f32(),
+ color,
+ texture_layer: target_index.0 as f32,
+ user_data: [0.0; 3],
+ };
+ image_source.write_gpu_blocks(&mut request);
+ }
+ }
+
#[cfg(feature = "debugger")]
pub fn print_with<T: PrintTreePrinter>(&self, pt: &mut T, tree: &RenderTaskTree) -> bool {
match self.kind {
RenderTaskKind::Picture(ref task) => {
pt.new_level(format!("Picture of {:?}", task.prim_index));
pt.add_item(format!("kind: {:?}", task.target_kind));
}
RenderTaskKind::CacheMask(ref task) => {
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -14,18 +14,19 @@ use api::{DeviceUintPoint, DeviceUintRec
use api::{ExternalImageType, FontRenderMode, ImageFormat, PipelineId};
use api::{RenderApiSender, RenderNotifier, TexelRect, TextureTarget, YuvColorSpace, YuvFormat};
use api::{YUV_COLOR_SPACES, YUV_FORMATS, channel};
#[cfg(not(feature = "debugger"))]
use api::ApiMsg;
use api::DebugCommand;
#[cfg(not(feature = "debugger"))]
use api::channel::MsgSender;
+use api::channel::PayloadReceiverHelperMethods;
use batch::{BatchKey, BatchKind, BatchTextures, BrushBatchKind};
-use batch::{BrushImageSourceKind, TransformBatchKind};
+use batch::{TransformBatchKind};
#[cfg(any(feature = "capture", feature = "replay"))]
use capture::{CaptureConfig, ExternalCaptureImage, PlainExternalImage};
use debug_colors;
use debug_render::DebugRenderer;
#[cfg(feature = "debugger")]
use debug_server::{self, DebugServer};
use device::{DepthFunction, Device, FrameId, Program, UploadMethod, Texture,
VertexDescriptor, PBO};
@@ -46,16 +47,17 @@ use internal_types::{RenderTargetInfo, S
use picture::ContentOrigin;
use prim_store::DeferredResolve;
use profiler::{BackendProfileCounters, FrameProfileCounters, Profiler};
use profiler::{GpuProfileTag, RendererProfileCounters, RendererProfileTimers};
use query::{GpuProfiler, GpuTimer};
use rayon::{ThreadPool, ThreadPoolBuilder};
use record::ApiRecordingReceiver;
use render_backend::RenderBackend;
+use scene_builder::SceneBuilder;
use render_task::{RenderTaskKind, RenderTaskTree};
use resource_cache::ResourceCache;
#[cfg(feature = "debugger")]
use serde_json;
use std;
use std::cmp;
use std::collections::VecDeque;
use std::collections::hash_map::Entry;
@@ -214,17 +216,17 @@ impl TransformBatchKind {
impl BatchKind {
#[cfg(feature = "debugger")]
fn debug_name(&self) -> &'static str {
match *self {
BatchKind::HardwareComposite => "HardwareComposite",
BatchKind::SplitComposite => "SplitComposite",
BatchKind::Brush(kind) => {
match kind {
- BrushBatchKind::Picture(..) => "Brush (Picture)",
+ BrushBatchKind::Picture => "Brush (Picture)",
BrushBatchKind::Solid => "Brush (Solid)",
BrushBatchKind::Line => "Brush (Line)",
BrushBatchKind::Image(..) => "Brush (Image)",
BrushBatchKind::Blend => "Brush (Blend)",
BrushBatchKind::MixBlend { .. } => "Brush (Composite)",
BrushBatchKind::YuvImage(..) => "Brush (YuvImage)",
BrushBatchKind::RadialGradient => "Brush (RadialGradient)",
BrushBatchKind::LinearGradient => "Brush (LinearGradient)",
@@ -235,17 +237,17 @@ impl BatchKind {
}
fn gpu_sampler_tag(&self) -> GpuProfileTag {
match *self {
BatchKind::HardwareComposite => GPU_TAG_PRIM_HW_COMPOSITE,
BatchKind::SplitComposite => GPU_TAG_PRIM_SPLIT_COMPOSITE,
BatchKind::Brush(kind) => {
match kind {
- BrushBatchKind::Picture(..) => GPU_TAG_BRUSH_PICTURE,
+ BrushBatchKind::Picture => GPU_TAG_BRUSH_PICTURE,
BrushBatchKind::Solid => GPU_TAG_BRUSH_SOLID,
BrushBatchKind::Line => GPU_TAG_BRUSH_LINE,
BrushBatchKind::Image(..) => GPU_TAG_BRUSH_IMAGE,
BrushBatchKind::Blend => GPU_TAG_BRUSH_BLEND,
BrushBatchKind::MixBlend { .. } => GPU_TAG_BRUSH_MIXBLEND,
BrushBatchKind::YuvImage(..) => GPU_TAG_BRUSH_YUV_IMAGE,
BrushBatchKind::RadialGradient => GPU_TAG_BRUSH_RADIAL_GRADIENT,
BrushBatchKind::LinearGradient => GPU_TAG_BRUSH_LINEAR_GRADIENT,
@@ -1595,21 +1597,18 @@ pub struct Renderer {
// These are "cache shaders". These shaders are used to
// draw intermediate results to cache targets. The results
// of these shaders are then used by the primitive shaders.
cs_text_run: LazilyCompiledShader,
cs_blur_a8: LazilyCompiledShader,
cs_blur_rgba8: LazilyCompiledShader,
// Brush shaders
- brush_mask_corner: LazilyCompiledShader,
brush_mask_rounded_rect: LazilyCompiledShader,
- brush_picture_rgba8: BrushShader,
- brush_picture_rgba8_alpha_mask: BrushShader,
- brush_picture_a8: BrushShader,
+ brush_picture: BrushShader,
brush_solid: BrushShader,
brush_line: BrushShader,
brush_image: Vec<Option<BrushShader>>,
brush_blend: BrushShader,
brush_mix_blend: BrushShader,
brush_yuv_image: Vec<Option<BrushShader>>,
brush_radial_gradient: BrushShader,
brush_linear_gradient: BrushShader,
@@ -1655,16 +1654,17 @@ pub struct Renderer {
clip_vao: VAO,
node_data_texture: VertexDataTexture,
local_clip_rects_texture: VertexDataTexture,
render_task_texture: VertexDataTexture,
gpu_cache_texture: CacheTexture,
gpu_cache_frame_id: FrameId,
+ gpu_cache_overflow: bool,
pipeline_info: PipelineInfo,
// Manages and resolves source textures IDs to real texture IDs.
texture_resolver: SourceTextureResolver,
// A PBO used to do asynchronous texture cache uploads.
texture_cache_upload_pbo: PBO,
@@ -1796,24 +1796,16 @@ impl Renderer {
let cs_text_run = try!{
LazilyCompiledShader::new(ShaderKind::Cache(VertexArrayKind::Primitive),
"cs_text_run",
&[],
&mut device,
options.precache_shaders)
};
- let brush_mask_corner = try!{
- LazilyCompiledShader::new(ShaderKind::Brush,
- "brush_mask_corner",
- &[],
- &mut device,
- options.precache_shaders)
- };
-
let brush_mask_rounded_rect = try!{
LazilyCompiledShader::new(ShaderKind::Brush,
"brush_mask_rounded_rect",
&[],
&mut device,
options.precache_shaders)
};
@@ -1840,34 +1832,20 @@ impl Renderer {
let brush_mix_blend = try!{
BrushShader::new("brush_mix_blend",
&mut device,
&[],
options.precache_shaders)
};
- let brush_picture_a8 = try!{
+ let brush_picture = try!{
BrushShader::new("brush_picture",
&mut device,
- &["ALPHA_TARGET"],
- options.precache_shaders)
- };
-
- let brush_picture_rgba8 = try!{
- BrushShader::new("brush_picture",
- &mut device,
- &["COLOR_TARGET"],
- options.precache_shaders)
- };
-
- let brush_picture_rgba8_alpha_mask = try!{
- BrushShader::new("brush_picture",
- &mut device,
- &["COLOR_TARGET_ALPHA_MASK"],
+ &[],
options.precache_shaders)
};
let brush_radial_gradient = try!{
BrushShader::new("brush_radial_gradient",
&mut device,
if options.enable_dithering {
&dithering_feature
@@ -2193,17 +2171,17 @@ impl Renderer {
dual_source_blending_is_enabled: true,
dual_source_blending_is_supported: ext_dual_source_blending,
};
let device_pixel_ratio = options.device_pixel_ratio;
// First set the flags to default and later call set_debug_flags to ensure any
// potential transition when enabling a flag is run.
let debug_flags = DebugFlags::default();
- let payload_tx_for_backend = payload_tx.clone();
+ let payload_rx_for_backend = payload_rx.to_mpsc_receiver();
let recorder = options.recorder;
let thread_listener = Arc::new(options.thread_listener);
let thread_listener_for_rayon_start = thread_listener.clone();
let thread_listener_for_rayon_end = thread_listener.clone();
let workers = options
.workers
.take()
.unwrap_or_else(|| {
@@ -2222,43 +2200,64 @@ impl Renderer {
})
.build();
Arc::new(worker.unwrap())
});
let enable_render_on_scroll = options.enable_render_on_scroll;
let blob_image_renderer = options.blob_image_renderer.take();
let thread_listener_for_render_backend = thread_listener.clone();
- let thread_name = format!("WRRenderBackend#{}", options.renderer_id.unwrap_or(0));
+ let thread_listener_for_scene_builder = thread_listener.clone();
+ let rb_thread_name = format!("WRRenderBackend#{}", options.renderer_id.unwrap_or(0));
+ let scene_thread_name = format!("WRSceneBuilder#{}", options.renderer_id.unwrap_or(0));
let resource_cache = ResourceCache::new(
texture_cache,
workers,
blob_image_renderer,
)?;
+
+ let (scene_builder, scene_tx, scene_rx) = SceneBuilder::new(config, api_tx.clone());
+ try! {
+ thread::Builder::new().name(scene_thread_name.clone()).spawn(move || {
+ register_thread_with_profiler(scene_thread_name.clone());
+ if let Some(ref thread_listener) = *thread_listener_for_scene_builder {
+ thread_listener.thread_started(&scene_thread_name);
+ }
+
+ let mut scene_builder = scene_builder;
+ scene_builder.run();
+
+ if let Some(ref thread_listener) = *thread_listener_for_scene_builder {
+ thread_listener.thread_stopped(&scene_thread_name);
+ }
+ })
+ };
+
try!{
- thread::Builder::new().name(thread_name.clone()).spawn(move || {
- register_thread_with_profiler(thread_name.clone());
+ thread::Builder::new().name(rb_thread_name.clone()).spawn(move || {
+ register_thread_with_profiler(rb_thread_name.clone());
if let Some(ref thread_listener) = *thread_listener_for_render_backend {
- thread_listener.thread_started(&thread_name);
+ thread_listener.thread_started(&rb_thread_name);
}
let mut backend = RenderBackend::new(
api_rx,
- payload_rx,
- payload_tx_for_backend,
+ payload_rx_for_backend,
result_tx,
+ scene_tx,
+ scene_rx,
device_pixel_ratio,
resource_cache,
backend_notifier,
config,
recorder,
enable_render_on_scroll,
);
backend.run(backend_profile_counters);
if let Some(ref thread_listener) = *thread_listener_for_render_backend {
- thread_listener.thread_stopped(&thread_name);
+ thread_listener.thread_stopped(&rb_thread_name);
}
})
};
let gpu_profile = GpuProfiler::new(Rc::clone(device.rc_gl()));
#[cfg(feature = "capture")]
let read_fbo = device.create_fbo_for_external_texture(0);
@@ -2268,21 +2267,18 @@ impl Renderer {
device,
active_documents: Vec::new(),
pending_texture_updates: Vec::new(),
pending_gpu_cache_updates: Vec::new(),
pending_shader_updates: Vec::new(),
cs_text_run,
cs_blur_a8,
cs_blur_rgba8,
- brush_mask_corner,
brush_mask_rounded_rect,
- brush_picture_rgba8,
- brush_picture_rgba8_alpha_mask,
- brush_picture_a8,
+ brush_picture,
brush_solid,
brush_line,
brush_image,
brush_blend,
brush_mix_blend,
brush_yuv_image,
brush_radial_gradient,
brush_linear_gradient,
@@ -2317,16 +2313,17 @@ impl Renderer {
dither_matrix_texture,
external_image_handler: None,
output_image_handler: None,
output_targets: FastHashMap::default(),
cpu_profiles: VecDeque::new(),
gpu_profiles: VecDeque::new(),
gpu_cache_texture,
gpu_cache_frame_id: FrameId::new(0),
+ gpu_cache_overflow: false,
texture_cache_upload_pbo,
texture_resolver,
renderer_errors: Vec::new(),
#[cfg(feature = "capture")]
read_fbo,
#[cfg(feature = "replay")]
owned_external_images: FastHashMap::default(),
};
@@ -2430,17 +2427,17 @@ impl Renderer {
updates,
cancel_rendering,
} => {
self.pending_texture_updates.push(updates);
self.device.begin_frame();
self.update_texture_cache();
self.device.end_frame();
// If we receive a `PublishDocument` message followed by this one
- // within the same update we need ot cancel the frame because we
+ // within the same update we need to cancel the frame because we
// might have deleted the resources in use in the frame due to a
// memory pressure event.
if cancel_rendering {
self.active_documents.clear();
}
}
ResultMsg::RefreshShader(path) => {
self.pending_shader_updates.push(path);
@@ -2529,21 +2526,16 @@ impl Renderer {
);
debug_target.add(
debug_server::BatchKind::Clip,
"Rectangles",
target.clip_batcher.rectangles.len(),
);
debug_target.add(
debug_server::BatchKind::Cache,
- "Rectangle Brush (Corner)",
- target.brush_mask_corners.len(),
- );
- debug_target.add(
- debug_server::BatchKind::Cache,
"Rectangle Brush (Rounded Rect)",
target.brush_mask_rounded_rects.len(),
);
for (_, items) in target.clip_batcher.images.iter() {
debug_target.add(debug_server::BatchKind::Clip, "Image mask", items.len());
}
debug_target
@@ -2970,16 +2962,21 @@ impl Renderer {
let (updated_blocks, max_requested_height) = self
.pending_gpu_cache_updates
.iter()
.fold((0, gpu_cache_height), |(count, height), list| {
(count + list.blocks.len(), cmp::max(height, list.height))
});
+ if max_requested_height > self.max_texture_size && !self.gpu_cache_overflow {
+ self.gpu_cache_overflow = true;
+ self.renderer_errors.push(RendererError::MaxTextureSize);
+ }
+
//Note: if we decide to switch to scatter-style GPU cache update
// permanently, we can have this code nicer with `BufferUploader` kind
// of helper, similarly to how `TextureUploader` API is used.
self.gpu_cache_texture.prepare_for_updates(
&mut self.device,
updated_blocks,
max_requested_height,
);
@@ -3200,23 +3197,18 @@ impl Renderer {
.bind(
&mut self.device,
key.blend_mode,
projection,
0,
&mut self.renderer_errors,
);
}
- BrushBatchKind::Picture(target_kind) => {
- let shader = match target_kind {
- BrushImageSourceKind::Alpha => &mut self.brush_picture_a8,
- BrushImageSourceKind::Color => &mut self.brush_picture_rgba8,
- BrushImageSourceKind::ColorAlphaMask => &mut self.brush_picture_rgba8_alpha_mask,
- };
- shader.bind(
+ BrushBatchKind::Picture => {
+ self.brush_picture.bind(
&mut self.device,
key.blend_mode,
projection,
0,
&mut self.renderer_errors,
);
}
BrushBatchKind::Line => {
@@ -3980,30 +3972,16 @@ impl Renderer {
&BatchTextures::no_texture(),
stats,
);
}
}
self.handle_scaling(render_tasks, &target.scalings, SourceTexture::CacheA8);
- if !target.brush_mask_corners.is_empty() {
- self.device.set_blend(false);
-
- let _timer = self.gpu_profile.start_timer(GPU_TAG_BRUSH_MASK);
- self.brush_mask_corner
- .bind(&mut self.device, projection, 0, &mut self.renderer_errors);
- self.draw_instanced_batch(
- &target.brush_mask_corners,
- VertexArrayKind::Primitive,
- &BatchTextures::no_texture(),
- stats,
- );
- }
-
if !target.brush_mask_rounded_rects.is_empty() {
self.device.set_blend(false);
let _timer = self.gpu_profile.start_timer(GPU_TAG_BRUSH_MASK);
self.brush_mask_rounded_rect
.bind(&mut self.device, projection, 0, &mut self.renderer_errors);
self.draw_instanced_batch(
&target.brush_mask_rounded_rects,
@@ -4701,20 +4679,17 @@ impl Renderer {
self.device.delete_vao(self.prim_vao);
self.device.delete_vao(self.clip_vao);
self.device.delete_vao(self.blur_vao);
self.debug.deinit(&mut self.device);
self.cs_text_run.deinit(&mut self.device);
self.cs_blur_a8.deinit(&mut self.device);
self.cs_blur_rgba8.deinit(&mut self.device);
self.brush_mask_rounded_rect.deinit(&mut self.device);
- self.brush_mask_corner.deinit(&mut self.device);
- self.brush_picture_rgba8.deinit(&mut self.device);
- self.brush_picture_rgba8_alpha_mask.deinit(&mut self.device);
- self.brush_picture_a8.deinit(&mut self.device);
+ self.brush_picture.deinit(&mut self.device);
self.brush_solid.deinit(&mut self.device);
self.brush_line.deinit(&mut self.device);
self.brush_blend.deinit(&mut self.device);
self.brush_mix_blend.deinit(&mut self.device);
self.brush_radial_gradient.deinit(&mut self.device);
self.brush_linear_gradient.deinit(&mut self.device);
self.cs_clip_rectangle.deinit(&mut self.device);
self.cs_clip_image.deinit(&mut self.device);
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -14,27 +14,27 @@ use api::{TileOffset, TileSize};
use app_units::Au;
#[cfg(feature = "capture")]
use capture::ExternalCaptureImage;
#[cfg(feature = "replay")]
use capture::PlainExternalImage;
#[cfg(any(feature = "replay", feature = "png"))]
use capture::CaptureConfig;
use device::TextureFilter;
-use frame::FrameId;
use glyph_cache::GlyphCache;
#[cfg(feature = "capture")]
use glyph_cache::{PlainGlyphCacheRef, PlainCachedGlyphInfo};
#[cfg(feature = "replay")]
use glyph_cache::{CachedGlyphInfo, PlainGlyphCacheOwn};
use glyph_rasterizer::{FontInstance, GlyphFormat, GlyphRasterizer, GlyphRequest};
use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
use internal_types::{FastHashMap, FastHashSet, ResourceCacheError, SourceTexture, TextureUpdateList};
use profiler::{ResourceProfileCounters, TextureCacheProfileCounters};
use rayon::ThreadPool;
+use render_backend::FrameId;
use render_task::{RenderTaskCache, RenderTaskCacheKey, RenderTaskId, RenderTaskTree};
use std::collections::hash_map::Entry::{self, Occupied, Vacant};
use std::cmp;
use std::fmt::Debug;
use std::hash::Hash;
use std::mem;
#[cfg(any(feature = "capture", feature = "replay"))]
use std::path::PathBuf;
--- a/gfx/webrender/src/scene.rs
+++ b/gfx/webrender/src/scene.rs
@@ -1,22 +1,24 @@
/* 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/. */
use api::{BuiltDisplayList, ColorF, DynamicProperties, Epoch, LayerSize, LayoutSize};
use api::{FilterOp, LayoutTransform, PipelineId, PropertyBinding, PropertyBindingId};
use api::{ItemRange, MixBlendMode, StackingContext};
use internal_types::FastHashMap;
+use std::sync::Arc;
/// Stores a map of the animated property bindings for the current display list. These
/// can be used to animate the transform and/or opacity of a display list without
/// re-submitting the display list itself.
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Clone)]
pub struct SceneProperties {
transform_properties: FastHashMap<PropertyBindingId, LayoutTransform>,
float_properties: FastHashMap<PropertyBindingId, f32>,
}
impl SceneProperties {
pub fn new() -> Self {
SceneProperties {
@@ -81,42 +83,41 @@ impl SceneProperties {
}
}
}
}
/// A representation of the layout within the display port for a given document or iframe.
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Clone)]
pub struct ScenePipeline {
pub pipeline_id: PipelineId,
- pub epoch: Epoch,
pub viewport_size: LayerSize,
pub content_size: LayoutSize,
pub background_color: Option<ColorF>,
pub display_list: BuiltDisplayList,
}
/// A complete representation of the layout bundling visible pipelines together.
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Clone)]
pub struct Scene {
pub root_pipeline_id: Option<PipelineId>,
- pub pipelines: FastHashMap<PipelineId, ScenePipeline>,
- pub removed_pipelines: Vec<PipelineId>,
- pub properties: SceneProperties,
+ pub pipelines: FastHashMap<PipelineId, Arc<ScenePipeline>>,
+ pub pipeline_epochs: FastHashMap<PipelineId, Epoch>,
}
impl Scene {
pub fn new() -> Self {
Scene {
root_pipeline_id: None,
pipelines: FastHashMap::default(),
- removed_pipelines: Vec::new(),
- properties: SceneProperties::new(),
+ pipeline_epochs: FastHashMap::default(),
}
}
pub fn set_root_pipeline_id(&mut self, pipeline_id: PipelineId) {
self.root_pipeline_id = Some(pipeline_id);
}
pub fn set_display_list(
@@ -125,38 +126,35 @@ impl Scene {
epoch: Epoch,
display_list: BuiltDisplayList,
background_color: Option<ColorF>,
viewport_size: LayerSize,
content_size: LayoutSize,
) {
let new_pipeline = ScenePipeline {
pipeline_id,
- epoch,
viewport_size,
content_size,
background_color,
display_list,
};
- self.pipelines.insert(pipeline_id, new_pipeline);
+ self.pipelines.insert(pipeline_id, Arc::new(new_pipeline));
+ self.pipeline_epochs.insert(pipeline_id, epoch);
}
pub fn remove_pipeline(&mut self, pipeline_id: PipelineId) {
if self.root_pipeline_id == Some(pipeline_id) {
self.root_pipeline_id = None;
}
self.pipelines.remove(&pipeline_id);
- self.removed_pipelines.push(pipeline_id);
}
pub fn update_epoch(&mut self, pipeline_id: PipelineId, epoch: Epoch) {
- if let Some(pipeline) = self.pipelines.get_mut(&pipeline_id) {
- pipeline.epoch = epoch;
- }
+ self.pipeline_epochs.insert(pipeline_id, epoch);
}
}
/// An arbitrary number which we assume opacity is invisible below.
pub const OPACITY_EPSILON: f32 = 0.001;
pub trait FilterOpHelpers {
fn is_visible(&self) -> bool;
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/src/scene_builder.rs
@@ -0,0 +1,127 @@
+/* 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/. */
+
+use api::{DocumentId, PipelineId, ApiMsg, FrameMsg, ResourceUpdates};
+use api::channel::MsgSender;
+use display_list_flattener::build_scene;
+use frame_builder::{FrameBuilderConfig, FrameBuilder};
+use clip_scroll_tree::ClipScrollTree;
+use internal_types::FastHashSet;
+use resource_cache::{FontInstanceMap, TiledImageMap};
+use render_backend::DocumentView;
+use scene::Scene;
+use std::sync::mpsc::{channel, Receiver, Sender};
+
+// Message from render backend to scene builder.
+pub enum SceneBuilderRequest {
+ Transaction {
+ document_id: DocumentId,
+ scene: Option<SceneRequest>,
+ resource_updates: ResourceUpdates,
+ frame_ops: Vec<FrameMsg>,
+ render: bool,
+ },
+ Stop
+}
+
+// Message from scene builder to render backend.
+pub enum SceneBuilderResult {
+ Transaction {
+ document_id: DocumentId,
+ built_scene: Option<BuiltScene>,
+ resource_updates: ResourceUpdates,
+ frame_ops: Vec<FrameMsg>,
+ render: bool,
+ },
+}
+
+/// Contains the the render backend data needed to build a scene.
+pub struct SceneRequest {
+ pub scene: Scene,
+ pub view: DocumentView,
+ pub font_instances: FontInstanceMap,
+ pub tiled_image_map: TiledImageMap,
+ pub output_pipelines: FastHashSet<PipelineId>,
+ pub removed_pipelines: Vec<PipelineId>,
+}
+
+pub struct BuiltScene {
+ pub scene: Scene,
+ pub frame_builder: FrameBuilder,
+ pub clip_scroll_tree: ClipScrollTree,
+ pub removed_pipelines: Vec<PipelineId>,
+}
+
+pub struct SceneBuilder {
+ rx: Receiver<SceneBuilderRequest>,
+ tx: Sender<SceneBuilderResult>,
+ api_tx: MsgSender<ApiMsg>,
+ config: FrameBuilderConfig,
+}
+
+impl SceneBuilder {
+ pub fn new(
+ config: FrameBuilderConfig,
+ api_tx: MsgSender<ApiMsg>
+ ) -> (Self, Sender<SceneBuilderRequest>, Receiver<SceneBuilderResult>) {
+ let (in_tx, in_rx) = channel();
+ let (out_tx, out_rx) = channel();
+ (
+ SceneBuilder {
+ rx: in_rx,
+ tx: out_tx,
+ api_tx,
+ config,
+ },
+ in_tx,
+ out_rx,
+ )
+ }
+
+ pub fn run(&mut self) {
+ loop {
+ match self.rx.recv() {
+ Ok(msg) => {
+ if !self.process_message(msg) {
+ return;
+ }
+ }
+ Err(_) => {
+ return;
+ }
+ }
+ }
+ }
+
+ fn process_message(&mut self, msg: SceneBuilderRequest) -> bool {
+ match msg {
+ SceneBuilderRequest::Transaction {
+ document_id,
+ scene,
+ resource_updates,
+ frame_ops,
+ render,
+ } => {
+ let built_scene = scene.map(|request|{
+ build_scene(&self.config, request)
+ });
+
+ // TODO: pre-rasterization.
+
+ self.tx.send(SceneBuilderResult::Transaction {
+ document_id,
+ built_scene,
+ resource_updates,
+ frame_ops,
+ render,
+ }).unwrap();
+
+ let _ = self.api_tx.send(ApiMsg::WakeUp);
+ }
+ SceneBuilderRequest::Stop => { return false; }
+ }
+
+ true
+ }
+}
--- a/gfx/webrender/src/texture_cache.rs
+++ b/gfx/webrender/src/texture_cache.rs
@@ -1,22 +1,23 @@
/* 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/. */
-use api::{DeviceUintPoint, DeviceUintRect, DeviceUintSize};
-use api::{ExternalImageType, ImageData, ImageFormat};
+use api::{ColorF, DeviceUintPoint, DeviceUintRect, DeviceUintSize};
+use api::{ExternalImageType, ImageData, ImageFormat, PremultipliedColorF};
use api::ImageDescriptor;
use device::TextureFilter;
-use frame::FrameId;
use freelist::{FreeList, FreeListHandle, UpsertResult, WeakFreeListHandle};
use gpu_cache::{GpuCache, GpuCacheHandle};
+use gpu_types::ImageSource;
use internal_types::{CacheTextureId, FastHashMap, TextureUpdateList, TextureUpdateSource};
use internal_types::{RenderTargetInfo, SourceTexture, TextureUpdate, TextureUpdateOp};
use profiler::{ResourceProfileCounter, TextureCacheProfileCounters};
+use render_backend::FrameId;
use resource_cache::CacheItem;
use std::cmp;
use std::mem;
// The fixed number of layers for the shared texture cache.
// There is one array texture per image format, allocated lazily.
const TEXTURE_ARRAY_LAYERS_LINEAR: usize = 4;
const TEXTURE_ARRAY_LAYERS_NEAREST: usize = 1;
@@ -97,16 +98,18 @@ struct CacheEntry {
last_access: FrameId,
// Handle to the resource rect in the GPU cache.
uv_rect_handle: GpuCacheHandle,
// Image format of the item.
format: ImageFormat,
filter: TextureFilter,
// The actual device texture ID this is part of.
texture_id: CacheTextureId,
+ // Color to modulate this cache item by.
+ color: PremultipliedColorF,
}
impl CacheEntry {
// Create a new entry for a standalone texture.
fn new_standalone(
texture_id: CacheTextureId,
size: DeviceUintSize,
format: ImageFormat,
@@ -118,16 +121,17 @@ impl CacheEntry {
size,
user_data,
last_access,
kind: EntryKind::Standalone,
texture_id,
format,
filter,
uv_rect_handle: GpuCacheHandle::new(),
+ color: ColorF::new(1.0, 1.0, 1.0, 1.0).premultiplied(),
}
}
// Update the GPU cache for this texture cache entry.
// This ensures that the UV rect, and texture layer index
// are up to date in the GPU cache for vertex shaders
// to fetch from.
fn update_gpu_cache(&mut self, gpu_cache: &mut GpuCache) {
@@ -135,23 +139,24 @@ impl CacheEntry {
let (origin, layer_index) = match self.kind {
EntryKind::Standalone { .. } => (DeviceUintPoint::zero(), 0.0),
EntryKind::Cache {
origin,
layer_index,
..
} => (origin, layer_index as f32),
};
- request.push([
- origin.x as f32,
- origin.y as f32,
- (origin.x + self.size.width) as f32,
- (origin.y + self.size.height) as f32,
- ]);
- request.push([layer_index, self.user_data[0], self.user_data[1], self.user_data[2]]);
+ let image_source = ImageSource {
+ p0: origin.to_f32(),
+ p1: (origin + self.size).to_f32(),
+ color: self.color,
+ texture_layer: layer_index,
+ user_data: self.user_data,
+ };
+ image_source.write_gpu_blocks(&mut request);
}
}
}
type WeakCacheEntryHandle = WeakFreeListHandle<CacheEntry>;
// A texture cache handle is a weak reference to a cache entry.
// If the handle has not been inserted into the cache yet, the
@@ -259,17 +264,17 @@ impl TextureCache {
// Request an item in the texture cache. All images that will
// be used on a frame *must* have request() called on their
// handle, to update the last used timestamp and ensure
// that resources are not flushed from the cache too early.
//
// Returns true if the image needs to be uploaded to the
// texture cache (either never uploaded, or has been
// evicted on a previous frame).
- pub fn request(&mut self, handle: &mut TextureCacheHandle, gpu_cache: &mut GpuCache) -> bool {
+ pub fn request(&mut self, handle: &TextureCacheHandle, gpu_cache: &mut GpuCache) -> bool {
match handle.entry {
Some(ref handle) => {
match self.entries.get_opt_mut(handle) {
// If an image is requested that is already in the cache,
// refresh the GPU cache data associated with this item.
Some(entry) => {
entry.last_access = self.frame_id;
entry.update_gpu_cache(gpu_cache);
@@ -1063,16 +1068,17 @@ impl TextureArray {
size: DeviceUintSize::new(width, height),
user_data,
last_access: frame_id,
kind,
uv_rect_handle: GpuCacheHandle::new(),
format: self.format,
filter: self.filter,
texture_id: self.texture_id.unwrap(),
+ color: ColorF::new(1.0, 1.0, 1.0, 1.0).premultiplied(),
}
})
}
}
impl TextureUpdate {
// Constructs a TextureUpdate operation to be passed to the
// rendering thread in order to do an upload to the right
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -1,40 +1,39 @@
/* 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/. */
-use api::{ClipId, ColorF, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
-use api::{DevicePixelScale, DeviceUintPoint, DeviceUintRect, DeviceUintSize};
-use api::{DocumentLayer, FilterOp, ImageFormat};
-use api::{LayerRect, MixBlendMode, PipelineId};
+use api::{ColorF, DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixelScale, DeviceUintPoint};
+use api::{DeviceUintRect, DeviceUintSize, DocumentLayer, FilterOp, ImageFormat, LayerRect};
+use api::{MixBlendMode, PipelineId};
use batch::{AlphaBatchBuilder, AlphaBatchContainer, ClipBatcher, resolve_image};
use clip::{ClipStore};
-use clip_scroll_tree::{ClipScrollTree};
+use clip_scroll_tree::{ClipScrollTree, ClipScrollNodeIndex};
use device::{FrameId, Texture};
use gpu_cache::{GpuCache};
use gpu_types::{BlurDirection, BlurInstance, BrushFlags, BrushInstance, ClipChainRectIndex};
-use gpu_types::{ClipScrollNodeData, ClipScrollNodeIndex};
+use gpu_types::{ClipScrollNodeData, ClipScrollNodeIndex as GPUClipScrollNodeIndex};
use gpu_types::{PrimitiveInstance};
use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
use picture::{PictureKind};
use prim_store::{CachedGradient, PrimitiveIndex, PrimitiveKind, PrimitiveStore};
-use prim_store::{BrushMaskKind, BrushKind, DeferredResolve, EdgeAaSegmentMask};
+use prim_store::{BrushKind, DeferredResolve, EdgeAaSegmentMask};
use profiler::FrameProfileCounters;
use render_task::{BlitSource, RenderTaskAddress, RenderTaskId, RenderTaskKind};
use render_task::{BlurTask, ClearMode, RenderTaskLocation, RenderTaskTree};
use resource_cache::ResourceCache;
use std::{cmp, usize, f32, i32};
use texture_allocator::GuillotineAllocator;
const MIN_TARGET_SIZE: u32 = 2048;
#[derive(Debug)]
pub struct ScrollbarPrimitive {
- pub clip_id: ClipId,
+ pub scroll_frame_index: ClipScrollNodeIndex,
pub prim_index: PrimitiveIndex,
pub frame_rect: LayerRect,
}
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct RenderTargetIndex(pub usize);
@@ -476,17 +475,16 @@ impl RenderTarget for ColorRenderTarget
})
}
}
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct AlphaRenderTarget {
pub clip_batcher: ClipBatcher,
- pub brush_mask_corners: Vec<PrimitiveInstance>,
pub brush_mask_rounded_rects: Vec<PrimitiveInstance>,
// List of blur operations to apply for this render target.
pub vertical_blurs: Vec<BlurInstance>,
pub horizontal_blurs: Vec<BlurInstance>,
pub scalings: Vec<ScalingInfo>,
pub zero_clears: Vec<RenderTaskId>,
allocator: TextureAllocator,
}
@@ -497,17 +495,16 @@ impl RenderTarget for AlphaRenderTarget
}
fn new(
size: Option<DeviceUintSize>,
_: DeviceIntSize,
) -> Self {
AlphaRenderTarget {
clip_batcher: ClipBatcher::new(),
- brush_mask_corners: Vec::new(),
brush_mask_rounded_rects: Vec::new(),
vertical_blurs: Vec::new(),
horizontal_blurs: Vec::new(),
scalings: Vec::new(),
zero_clears: Vec::new(),
allocator: TextureAllocator::new(size.expect("bug: alpha targets need size")),
}
}
@@ -579,17 +576,17 @@ impl RenderTarget for AlphaRenderTarget
picture_address: task_index,
prim_address: sub_prim_address,
// TODO(gw): In the future, when brush
// primitives on picture backed
// tasks support clip masks and
// transform primitives, these
// will need to be filled out!
clip_chain_rect_index: ClipChainRectIndex(0),
- scroll_id: ClipScrollNodeIndex(0),
+ scroll_id: GPUClipScrollNodeIndex(0),
clip_task_address: RenderTaskAddress(0),
z: 0,
segment_index: 0,
brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
edge_flags: EdgeAaSegmentMask::empty(),
user_data: [0; 3],
};
let brush = &ctx.prim_store.cpu_brushes[sub_metadata.cpu_prim_index.0];
@@ -599,21 +596,18 @@ impl RenderTarget for AlphaRenderTarget
BrushKind::Picture |
BrushKind::Line { .. } |
BrushKind::YuvImage { .. } |
BrushKind::RadialGradient { .. } |
BrushKind::LinearGradient { .. } |
BrushKind::Image { .. } => {
unreachable!("bug: unexpected brush here");
}
- BrushKind::Mask { ref kind, .. } => {
- match *kind {
- BrushMaskKind::Corner(..) => &mut self.brush_mask_corners,
- BrushMaskKind::RoundedRect(..) => &mut self.brush_mask_rounded_rects,
- }
+ BrushKind::Mask { .. } => {
+ &mut self.brush_mask_rounded_rects
}
};
batch.push(PrimitiveInstance::from(instance));
}
_ => {
unreachable!("Unexpected sub primitive type");
}
}
@@ -863,16 +857,20 @@ impl RenderPass {
if let Some(index) = task.saved_index {
assert_eq!(index, SavedTargetIndex::PENDING);
task.saved_index = match target_kind {
RenderTargetKind::Color => saved_color,
RenderTargetKind::Alpha => saved_alpha,
};
}
+ // Give the render task an opportunity to add any
+ // information to the GPU cache, if appropriate.
+ task.prepare_for_render(gpu_cache);
+
(target_kind, texture_target)
};
match texture_target {
Some(texture_target) => {
let texture = texture_cache
.entry(texture_target)
.or_insert(
@@ -928,17 +926,17 @@ impl CompositeOps {
}
}
pub fn count(&self) -> usize {
self.filters.len() + if self.mix_blend_mode.is_some() { 1 } else { 0 }
}
}
-/// A rendering-oriented representation of frame::Frame built by the render backend
+/// A rendering-oriented representation of the frame built by the render backend
/// and presented to the renderer.
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct Frame {
//TODO: share the fields with DocumentView struct
pub window_size: DeviceUintSize,
pub inner_rect: DeviceUintRect,
pub background_color: Option<ColorF>,
--- a/gfx/webrender/src/util.rs
+++ b/gfx/webrender/src/util.rs
@@ -92,19 +92,22 @@ impl<Src, Dst> MatrixHelpers<Src, Dst> f
if self.preserves_2d_axis_alignment() {
TransformedRectKind::AxisAligned
} else {
TransformedRectKind::Complex
}
}
fn is_simple_translation(&self) -> bool {
- if self.m11 != 1. || self.m22 != 1. || self.m33 != 1. {
+ if (self.m11 - 1.0).abs() > NEARLY_ZERO ||
+ (self.m22 - 1.0).abs() > NEARLY_ZERO ||
+ (self.m33 - 1.0).abs() > NEARLY_ZERO {
return false;
}
+
self.m12.abs() < NEARLY_ZERO && self.m13.abs() < NEARLY_ZERO &&
self.m14.abs() < NEARLY_ZERO && self.m21.abs() < NEARLY_ZERO &&
self.m23.abs() < NEARLY_ZERO && self.m24.abs() < NEARLY_ZERO &&
self.m31.abs() < NEARLY_ZERO && self.m32.abs() < NEARLY_ZERO &&
self.m34.abs() < NEARLY_ZERO
}
fn is_simple_2d_translation(&self) -> bool {
--- a/gfx/webrender/tests/angle_shader_validation.rs
+++ b/gfx/webrender/tests/angle_shader_validation.rs
@@ -80,17 +80,17 @@ const SHADERS: &[Shader] = &[
features: &[],
},
Shader {
name: "brush_solid",
features: &[],
},
Shader {
name: "brush_picture",
- features: &["COLOR_TARGET", "ALPHA_TARGET"],
+ features: &[],
},
Shader {
name: "brush_blend",
features: &[],
},
Shader {
name: "brush_composite",
features: &[],
--- a/gfx/webrender_api/Cargo.toml
+++ b/gfx/webrender_api/Cargo.toml
@@ -11,17 +11,17 @@ ipc = ["ipc-channel"]
serialize = []
deserialize = []
[dependencies]
app_units = "0.6"
bitflags = "1.0"
bincode = "0.9"
byteorder = "1.2.1"
-euclid = "0.16"
+euclid = { version = "0.17", features = ["serde"] }
ipc-channel = {version = "0.9", optional = true}
serde = { version = "=1.0.27", features = ["rc"] }
serde_derive = { version = "=1.0.27", features = ["deserialize_in_place"] }
time = "0.1"
[target.'cfg(target_os = "macos")'.dependencies]
core-foundation = "0.5"
core-graphics = "0.13"
--- a/gfx/webrender_api/src/api.rs
+++ b/gfx/webrender_api/src/api.rs
@@ -126,57 +126,97 @@ impl ResourceUpdates {
/// A Transaction is a group of commands to apply atomically to a document.
///
/// This mechanism ensures that:
/// - no other message can be interleaved between two commands that need to be applied together.
/// - no redundant work is performed if two commands in the same transaction cause the scene or
/// the frame to be rebuilt.
pub struct Transaction {
- ops: Vec<DocumentMsg>,
+ // Operations affecting the scene (applied before scene building).
+ scene_ops: Vec<SceneMsg>,
+ // Operations affecting the generation of frames (applied after scene building).
+ frame_ops: Vec<FrameMsg>,
+
+ // Additional display list data.
payloads: Vec<Payload>,
+
+ // Resource updates are applied after scene building.
+ resource_updates: ResourceUpdates,
+
+ // If true the transaction is piped through the scene building thread, if false
+ // it will be applied directly on the render backend.
+ use_scene_builder_thread: bool,
+
+ generate_frame: bool,
}
impl Transaction {
pub fn new() -> Self {
Transaction {
- ops: Vec::new(),
+ scene_ops: Vec::new(),
+ frame_ops: Vec::new(),
+ resource_updates: ResourceUpdates::new(),
payloads: Vec::new(),
+ use_scene_builder_thread: false, // TODO: make this true by default.
+ generate_frame: false,
}
}
+ // TODO: better name?
+ pub fn skip_scene_builder(&mut self) {
+ self.use_scene_builder_thread = false;
+ }
+
+ // TODO: this is temporary, using the scene builder thread is the default for
+ // most transactions, and opt-in for specific cases like scrolling and async video.
+ pub fn use_scene_builder_thread(&mut self) {
+ self.use_scene_builder_thread = true;
+ }
+
pub fn is_empty(&self) -> bool {
- self.ops.is_empty()
+ !self.generate_frame &&
+ self.scene_ops.is_empty() &&
+ self.frame_ops.is_empty() &&
+ self.resource_updates.updates.is_empty()
}
pub fn update_epoch(&mut self, pipeline_id: PipelineId, epoch: Epoch) {
- self.ops.push(DocumentMsg::UpdateEpoch(pipeline_id, epoch));
+ // We track epochs before and after scene building.
+ // This one will be applied to the pending scene right away:
+ self.scene_ops.push(SceneMsg::UpdateEpoch(pipeline_id, epoch));
+ // And this one will be applied to the currently built scene at the end
+ // of the transaction (potentially long after the scene_ops one).
+ self.frame_ops.push(FrameMsg::UpdateEpoch(pipeline_id, epoch));
+ // We could avoid the duplication here by storing the epoch updates in a
+ // separate array and let the render backend schedule the updates at the
+ // proper times, but it wouldn't make things simpler.
}
/// Sets the root pipeline.
///
/// # Examples
///
/// ```
/// # use webrender_api::{DeviceUintSize, PipelineId, RenderApiSender, Transaction};
/// # fn example() {
/// let pipeline_id = PipelineId(0, 0);
/// let mut txn = Transaction::new();
/// txn.set_root_pipeline(pipeline_id);
/// # }
/// ```
pub fn set_root_pipeline(&mut self, pipeline_id: PipelineId) {
- self.ops.push(DocumentMsg::SetRootPipeline(pipeline_id));
+ self.scene_ops.push(SceneMsg::SetRootPipeline(pipeline_id));
}
/// Removes data associated with a pipeline from the internal data structures.
/// If the specified `pipeline_id` is for the root pipeline, the root pipeline
/// is reset back to `None`.
pub fn remove_pipeline(&mut self, pipeline_id: PipelineId) {
- self.ops.push(DocumentMsg::RemovePipeline(pipeline_id));
+ self.scene_ops.push(SceneMsg::RemovePipeline(pipeline_id));
}
/// Supplies a new frame to WebRender.
///
/// Non-blocking, it notifies a worker process which processes the display list.
/// When it's done and a RenderNotifier has been set in `webrender::Renderer`,
/// [new_frame_ready()][notifier] gets called.
///
@@ -200,42 +240,42 @@ impl Transaction {
&mut self,
epoch: Epoch,
background: Option<ColorF>,
viewport_size: LayoutSize,
(pipeline_id, content_size, display_list): (PipelineId, LayoutSize, BuiltDisplayList),
preserve_frame_state: bool,
) {
let (display_list_data, list_descriptor) = display_list.into_data();
- self.ops.push(
- DocumentMsg::SetDisplayList {
+ self.scene_ops.push(
+ SceneMsg::SetDisplayList {
epoch,
pipeline_id,
background,
viewport_size,
content_size,
list_descriptor,
preserve_frame_state,
}
);
self.payloads.push(Payload { epoch, pipeline_id, display_list_data });
}
pub fn update_resources(&mut self, resources: ResourceUpdates) {
- self.ops.push(DocumentMsg::UpdateResources(resources));
+ self.resource_updates.merge(resources);
}
pub fn set_window_parameters(
&mut self,
window_size: DeviceUintSize,
inner_rect: DeviceUintRect,
device_pixel_ratio: f32,
) {
- self.ops.push(
- DocumentMsg::SetWindowParameters {
+ self.scene_ops.push(
+ SceneMsg::SetWindowParameters {
window_size,
inner_rect,
device_pixel_ratio,
},
);
}
/// Scrolls the scrolling layer under the `cursor`
@@ -243,62 +283,114 @@ impl Transaction {
/// WebRender looks for the layer closest to the user
/// which has `ScrollPolicy::Scrollable` set.
pub fn scroll(
&mut self,
scroll_location: ScrollLocation,
cursor: WorldPoint,
phase: ScrollEventPhase,
) {
- self.ops.push(DocumentMsg::Scroll(scroll_location, cursor, phase));
+ self.frame_ops.push(FrameMsg::Scroll(scroll_location, cursor, phase));
}
pub fn scroll_node_with_id(
&mut self,
origin: LayoutPoint,
id: ExternalScrollId,
clamp: ScrollClamping,
) {
- self.ops.push(DocumentMsg::ScrollNodeWithId(origin, id, clamp));
+ self.frame_ops.push(FrameMsg::ScrollNodeWithId(origin, id, clamp));
}
pub fn set_page_zoom(&mut self, page_zoom: ZoomFactor) {
- self.ops.push(DocumentMsg::SetPageZoom(page_zoom));
+ self.scene_ops.push(SceneMsg::SetPageZoom(page_zoom));
}
pub fn set_pinch_zoom(&mut self, pinch_zoom: ZoomFactor) {
- self.ops.push(DocumentMsg::SetPinchZoom(pinch_zoom));
+ self.scene_ops.push(SceneMsg::SetPinchZoom(pinch_zoom));
}
pub fn set_pan(&mut self, pan: DeviceIntPoint) {
- self.ops.push(DocumentMsg::SetPan(pan));
+ self.frame_ops.push(FrameMsg::SetPan(pan));
}
pub fn tick_scrolling_bounce_animations(&mut self) {
- self.ops.push(DocumentMsg::TickScrollingBounce);
+ self.frame_ops.push(FrameMsg::TickScrollingBounce);
}
/// Generate a new frame.
pub fn generate_frame(&mut self) {
- self.ops.push(DocumentMsg::GenerateFrame);
+ self.generate_frame = true;
}
/// Supply a list of animated property bindings that should be used to resolve
/// bindings in the current display list.
pub fn update_dynamic_properties(&mut self, properties: DynamicProperties) {
- self.ops.push(DocumentMsg::UpdateDynamicProperties(properties));
+ self.frame_ops.push(FrameMsg::UpdateDynamicProperties(properties));
}
/// Enable copying of the output of this pipeline id to
/// an external texture for callers to consume.
pub fn enable_frame_output(&mut self, pipeline_id: PipelineId, enable: bool) {
- self.ops.push(DocumentMsg::EnableFrameOutput(pipeline_id, enable));
+ self.frame_ops.push(FrameMsg::EnableFrameOutput(pipeline_id, enable));
+ }
+
+ fn finalize(self) -> (TransactionMsg, Vec<Payload>) {
+ (
+ TransactionMsg {
+ scene_ops: self.scene_ops,
+ frame_ops: self.frame_ops,
+ resource_updates: self.resource_updates,
+ use_scene_builder_thread: self.use_scene_builder_thread,
+ generate_frame: self.generate_frame,
+ },
+ self.payloads,
+ )
}
}
+/// Represents a transaction in the format sent through the channel.
+#[derive(Clone, Deserialize, Serialize)]
+pub struct TransactionMsg {
+ pub scene_ops: Vec<SceneMsg>,
+ pub frame_ops: Vec<FrameMsg>,
+ pub resource_updates: ResourceUpdates,
+ pub generate_frame: bool,
+ pub use_scene_builder_thread: bool,
+}
+
+impl TransactionMsg {
+ pub fn is_empty(&self) -> bool {
+ !self.generate_frame &&
+ self.scene_ops.is_empty() &&
+ self.frame_ops.is_empty() &&
+ self.resource_updates.updates.is_empty()
+ }
+
+ // TODO: We only need this for a few RenderApi methods which we should remove.
+ fn frame_message(msg: FrameMsg) -> Self {
+ TransactionMsg {
+ scene_ops: Vec::new(),
+ frame_ops: vec![msg],
+ resource_updates: ResourceUpdates::new(),
+ generate_frame: false,
+ use_scene_builder_thread: false,
+ }
+ }
+
+ fn scene_message(msg: SceneMsg) -> Self {
+ TransactionMsg {
+ scene_ops: vec![msg],
+ frame_ops: Vec::new(),
+ resource_updates: ResourceUpdates::new(),
+ generate_frame: false,
+ use_scene_builder_thread: false,
+ }
+ }
+}
#[derive(Clone, Deserialize, Serialize)]
pub struct AddImage {
pub key: ImageKey,
pub descriptor: ImageDescriptor,
pub data: ImageData,
pub tiling: Option<TileSize>,
}
@@ -353,69 +445,80 @@ pub struct AddFontInstance {
pub key: FontInstanceKey,
pub font_key: FontKey,
pub glyph_size: Au,
pub options: Option<FontInstanceOptions>,
pub platform_options: Option<FontInstancePlatformOptions>,
pub variations: Vec<FontVariation>,
}
+// Frame messages affect building the scene.
#[derive(Clone, Deserialize, Serialize)]
-pub enum DocumentMsg {
- HitTest(Option<PipelineId>, WorldPoint, HitTestFlags, MsgSender<HitTestResult>),
+pub enum SceneMsg {
+ UpdateEpoch(PipelineId, Epoch),
+ SetPageZoom(ZoomFactor),
+ SetPinchZoom(ZoomFactor),
+ SetRootPipeline(PipelineId),
+ RemovePipeline(PipelineId),
SetDisplayList {
list_descriptor: BuiltDisplayListDescriptor,
epoch: Epoch,
pipeline_id: PipelineId,
background: Option<ColorF>,
viewport_size: LayoutSize,
content_size: LayoutSize,
preserve_frame_state: bool,
},
- UpdateResources(ResourceUpdates),
- UpdateEpoch(PipelineId, Epoch),
- SetPageZoom(ZoomFactor),
- SetPinchZoom(ZoomFactor),
- SetPan(DeviceIntPoint),
- SetRootPipeline(PipelineId),
- RemovePipeline(PipelineId),
- EnableFrameOutput(PipelineId, bool),
SetWindowParameters {
window_size: DeviceUintSize,
inner_rect: DeviceUintRect,
device_pixel_ratio: f32,
},
+}
+
+// Frame messages affect frame generation (applied after building the scene).
+#[derive(Clone, Deserialize, Serialize)]
+pub enum FrameMsg {
+ UpdateEpoch(PipelineId, Epoch),
+ HitTest(Option<PipelineId>, WorldPoint, HitTestFlags, MsgSender<HitTestResult>),
+ SetPan(DeviceIntPoint),
+ EnableFrameOutput(PipelineId, bool),
Scroll(ScrollLocation, WorldPoint, ScrollEventPhase),
ScrollNodeWithId(LayoutPoint, ExternalScrollId, ScrollClamping),
TickScrollingBounce,
GetScrollNodeState(MsgSender<Vec<ScrollNodeState>>),
- GenerateFrame,
UpdateDynamicProperties(DynamicProperties),
}
-impl fmt::Debug for DocumentMsg {
+impl fmt::Debug for SceneMsg {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(match *self {
- DocumentMsg::SetDisplayList { .. } => "DocumentMsg::SetDisplayList",
- DocumentMsg::HitTest(..) => "DocumentMsg::HitTest",
- DocumentMsg::SetPageZoom(..) => "DocumentMsg::SetPageZoom",
- DocumentMsg::SetPinchZoom(..) => "DocumentMsg::SetPinchZoom",
- DocumentMsg::SetPan(..) => "DocumentMsg::SetPan",
- DocumentMsg::SetRootPipeline(..) => "DocumentMsg::SetRootPipeline",
- DocumentMsg::RemovePipeline(..) => "DocumentMsg::RemovePipeline",
- DocumentMsg::SetWindowParameters { .. } => "DocumentMsg::SetWindowParameters",
- DocumentMsg::Scroll(..) => "DocumentMsg::Scroll",
- DocumentMsg::ScrollNodeWithId(..) => "DocumentMsg::ScrollNodeWithId",
- DocumentMsg::TickScrollingBounce => "DocumentMsg::TickScrollingBounce",
- DocumentMsg::GetScrollNodeState(..) => "DocumentMsg::GetScrollNodeState",
- DocumentMsg::GenerateFrame => "DocumentMsg::GenerateFrame",
- DocumentMsg::EnableFrameOutput(..) => "DocumentMsg::EnableFrameOutput",
- DocumentMsg::UpdateResources(..) => "DocumentMsg::UpdateResources",
- DocumentMsg::UpdateEpoch(..) => "DocumentMsg::UpdateEpoch",
- DocumentMsg::UpdateDynamicProperties(..) => "DocumentMsg::UpdateDynamicProperties",
+ SceneMsg::UpdateEpoch(..) => "SceneMsg::UpdateEpoch",
+ SceneMsg::SetDisplayList { .. } => "SceneMsg::SetDisplayList",
+ SceneMsg::SetPageZoom(..) => "SceneMsg::SetPageZoom",
+ SceneMsg::SetPinchZoom(..) => "SceneMsg::SetPinchZoom",
+ SceneMsg::RemovePipeline(..) => "SceneMsg::RemovePipeline",
+ SceneMsg::SetWindowParameters { .. } => "SceneMsg::SetWindowParameters",
+ SceneMsg::SetRootPipeline(..) => "SceneMsg::SetRootPipeline",
+ })
+ }
+}
+
+impl fmt::Debug for FrameMsg {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.write_str(match *self {
+ FrameMsg::UpdateEpoch(..) => "FrameMsg::UpdateEpoch",
+ FrameMsg::HitTest(..) => "FrameMsg::HitTest",
+ FrameMsg::SetPan(..) => "FrameMsg::SetPan",
+ FrameMsg::Scroll(..) => "FrameMsg::Scroll",
+ FrameMsg::ScrollNodeWithId(..) => "FrameMsg::ScrollNodeWithId",
+ FrameMsg::TickScrollingBounce => "FrameMsg::TickScrollingBounce",
+ FrameMsg::GetScrollNodeState(..) => "FrameMsg::GetScrollNodeState",
+ FrameMsg::EnableFrameOutput(..) => "FrameMsg::EnableFrameOutput",
+ FrameMsg::UpdateDynamicProperties(..) => "FrameMsg::UpdateDynamicProperties",
})
}
}
bitflags!{
/// Bit flags for WR stages to store in a capture.
// Note: capturing `FRAME` without `SCENE` is not currently supported.
#[derive(Deserialize, Serialize)]
@@ -491,29 +594,32 @@ pub enum ApiMsg {
),
/// Gets the glyph indices from a string
GetGlyphIndices(FontKey, String, MsgSender<Vec<Option<u32>>>),
/// Adds a new document namespace.
CloneApi(MsgSender<IdNamespace>),
/// Adds a new document with given initial size.
AddDocument(DocumentId, DeviceUintSize, DocumentLayer),
/// A message targeted at a particular document.
- UpdateDocument(DocumentId, Vec<DocumentMsg>),
+ UpdateDocument(DocumentId, TransactionMsg),
/// Deletes an existing document.
DeleteDocument(DocumentId),
/// An opaque handle that must be passed to the render notifier. It is used by Gecko
/// to forward gecko-specific messages to the render thread preserving the ordering
/// within the other messages.
ExternalEvent(ExternalEvent),
/// Removes all resources associated with a namespace.
ClearNamespace(IdNamespace),
/// Flush from the caches anything that isn't necessary, to free some memory.
MemoryPressure,
/// Change debugging options.
DebugCommand(DebugCommand),
+ /// Wakes the render backend's event loop up. Needed when an event is communicated
+ /// through another channel.
+ WakeUp,
ShutDown,
}
impl fmt::Debug for ApiMsg {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(match *self {
ApiMsg::UpdateResources(..) => "ApiMsg::UpdateResources",
ApiMsg::GetGlyphDimensions(..) => "ApiMsg::GetGlyphDimensions",
@@ -522,16 +628,17 @@ impl fmt::Debug for ApiMsg {
ApiMsg::AddDocument(..) => "ApiMsg::AddDocument",
ApiMsg::UpdateDocument(..) => "ApiMsg::UpdateDocument",
ApiMsg::DeleteDocument(..) => "ApiMsg::DeleteDocument",
ApiMsg::ExternalEvent(..) => "ApiMsg::ExternalEvent",
ApiMsg::ClearNamespace(..) => "ApiMsg::ClearNamespace",
ApiMsg::MemoryPressure => "ApiMsg::MemoryPressure",
ApiMsg::DebugCommand(..) => "ApiMsg::DebugCommand",
ApiMsg::ShutDown => "ApiMsg::ShutDown",
+ ApiMsg::WakeUp => "ApiMsg::WakeUp",
})
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub struct Epoch(pub u32);
@@ -612,20 +719,32 @@ impl RenderApiSender {
}
/// Creates a new resource API object with a dedicated namespace.
pub fn create_api(&self) -> RenderApi {
let (sync_tx, sync_rx) =
channel::msg_channel().expect("Failed to create channel");
let msg = ApiMsg::CloneApi(sync_tx);
self.api_sender.send(msg).expect("Failed to send CloneApi message");
+ let namespace_id = match sync_rx.recv() {
+ Ok(id) => id,
+ Err(e) => {
+ // This is used to discover the underlying cause of https://github.com/servo/servo/issues/13480.
+ let webrender_is_alive = self.api_sender.send(ApiMsg::WakeUp);
+ if webrender_is_alive.is_err() {
+ panic!("Webrender was shut down before processing CloneApi: {}", e);
+ } else {
+ panic!("CloneApi message response was dropped while Webrender was still alive: {}", e);
+ }
+ }
+ };
RenderApi {
api_sender: self.api_sender.clone(),
payload_sender: self.payload_sender.clone(),
- namespace_id: sync_rx.recv().expect("Failed to receive API response"),
+ namespace_id: namespace_id,
next_id: Cell::new(ResourceId(0)),
}
}
}
pub struct RenderApi {
api_sender: MsgSender<ApiMsg>,
payload_sender: PayloadSender,
@@ -751,74 +870,80 @@ impl RenderApi {
#[doc(hidden)]
pub fn send_payload(&self, data: &[u8]) {
self.payload_sender
.send_payload(Payload::from_data(data))
.unwrap();
}
/// A helper method to send document messages.
- fn send(&self, document_id: DocumentId, msg: DocumentMsg) {
+ fn send_scene_msg(&self, document_id: DocumentId, msg: SceneMsg) {
// This assertion fails on Servo use-cases, because it creates different
// `RenderApi` instances for layout and compositor.
//assert_eq!(document_id.0, self.namespace_id);
self.api_sender
- .send(ApiMsg::UpdateDocument(document_id, vec![msg]))
+ .send(ApiMsg::UpdateDocument(document_id, TransactionMsg::scene_message(msg)))
.unwrap()
}
- // TODO(nical) - decide what to do with the methods that are duplicated in Transaction.
- // I think that we should remove them from RenderApi but we could also leave them here if
- // it makes things easier for servo.
- // They are all equivalent to creating a transaction with a single command.
+ /// A helper method to send document messages.
+ fn send_frame_msg(&self, document_id: DocumentId, msg: FrameMsg) {
+ // This assertion fails on Servo use-cases, because it creates different
+ // `RenderApi` instances for layout and compositor.
+ //assert_eq!(document_id.0, self.namespace_id);
+ self.api_sender
+ .send(ApiMsg::UpdateDocument(document_id, TransactionMsg::frame_message(msg)))
+ .unwrap()
+ }
pub fn send_transaction(&self, document_id: DocumentId, transaction: Transaction) {
- for payload in transaction.payloads {
+ let (msg, payloads) = transaction.finalize();
+ for payload in payloads {
self.payload_sender.send_payload(payload).unwrap();
}
- self.api_sender.send(ApiMsg::UpdateDocument(document_id, transaction.ops)).unwrap();
+ self.api_sender.send(ApiMsg::UpdateDocument(document_id, msg)).unwrap();
}
/// Does a hit test on display items in the specified document, at the given
/// point. If a pipeline_id is specified, it is used to further restrict the
/// hit results so that only items inside that pipeline are matched. If the
/// HitTestFlags argument contains the FIND_ALL flag, then the vector of hit
/// results will contain all display items that match, ordered from front
/// to back.
pub fn hit_test(&self,
document_id: DocumentId,
pipeline_id: Option<PipelineId>,
point: WorldPoint,
flags: HitTestFlags)
-> HitTestResult {
let (tx, rx) = channel::msg_channel().unwrap();
- self.send(document_id, DocumentMsg::HitTest(pipeline_id, point, flags, tx));
+
+ self.send_frame_msg(
+ document_id,
+ FrameMsg::HitTest(pipeline_id, point, flags, tx)
+ );
rx.recv().unwrap()
}
pub fn set_window_parameters(
&self,
document_id: DocumentId,
window_size: DeviceUintSize,
inner_rect: DeviceUintRect,
device_pixel_ratio: f32,
) {
- self.send(
+ self.send_scene_msg(
document_id,
- DocumentMsg::SetWindowParameters {
- window_size,
- inner_rect,
- device_pixel_ratio,
- },
+ SceneMsg::SetWindowParameters { window_size, inner_rect, device_pixel_ratio, },
);
}
pub fn get_scroll_node_state(&self, document_id: DocumentId) -> Vec<ScrollNodeState> {
let (tx, rx) = channel::msg_channel().unwrap();
- self.send(document_id, DocumentMsg::GetScrollNodeState(tx));
+ self.send_frame_msg(document_id, FrameMsg::GetScrollNodeState(tx));
rx.recv().unwrap()
}
/// Save a capture of the current frame state for debugging.
pub fn save_capture(&self, path: PathBuf, bits: CaptureBits) {
let msg = ApiMsg::DebugCommand(DebugCommand::SaveCapture(path, bits));
self.send_message(msg);
}
--- a/gfx/webrender_api/src/channel.rs
+++ b/gfx/webrender_api/src/channel.rs
@@ -1,16 +1,17 @@
/* 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/. */
use api::{Epoch, PipelineId};
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use std::io::{Cursor, Read};
use std::mem;
+use std::sync::mpsc::Receiver;
#[derive(Clone)]
pub struct Payload {
/// An epoch used to get the proper payload for a pipeline id frame request.
///
/// TODO(emilio): Is this still relevant? We send the messages for the same
/// pipeline in order, so we shouldn't need it. Seems like this was only
/// wallpapering (in most cases) the underlying problem in #991.
@@ -69,15 +70,19 @@ impl Payload {
/// A helper to handle the interface difference between `IpcBytesSender`
/// and `Sender<Vec<u8>>`.
pub trait PayloadSenderHelperMethods {
fn send_payload(&self, data: Payload) -> Result<(), Error>;
}
pub trait PayloadReceiverHelperMethods {
fn recv_payload(&self) -> Result<Payload, Error>;
+
+ // For an MPSC receiver, this is the identity function,
+ // for an IPC receiver, it routes to a new mpsc receiver
+ fn to_mpsc_receiver(self) -> Receiver<Payload>;
}
#[cfg(not(feature = "ipc"))]
include!("channel_mpsc.rs");
#[cfg(feature = "ipc")]
include!("channel_ipc.rs");
--- a/gfx/webrender_api/src/channel_ipc.rs
+++ b/gfx/webrender_api/src/channel_ipc.rs
@@ -1,15 +1,17 @@
/* 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/. */
use ipc_channel::ipc::{self, IpcBytesReceiver, IpcBytesSender, IpcReceiver, IpcSender};
use serde::{Deserialize, Serialize};
use std::io::{Error, ErrorKind};
+use std::sync::mpsc;
+use std::thread;
use std::{error, io};
///
/// Handles the channel implementation when IPC is enabled.
///
pub type MsgSender<T> = IpcSender<T>;
@@ -25,16 +27,30 @@ impl PayloadSenderHelperMethods for Payl
}
}
impl PayloadReceiverHelperMethods for PayloadReceiver {
fn recv_payload(&self) -> Result<Payload, Error> {
self.recv().map(|data| Payload::from_data(&data) )
.map_err(|e| io::Error::new(ErrorKind::Other, error::Error::description(&e)))
}
+
+ fn to_mpsc_receiver(self) -> Receiver<Payload> {
+ // It would be nice to use the IPC router for this,
+ // but that requires an IpcReceiver, not an IpcBytesReceiver.
+ let (tx, rx) = mpsc::channel();
+ thread::spawn(move || {
+ while let Ok(payload) = self.recv_payload() {
+ if tx.send(payload).is_err() {
+ break;
+ }
+ }
+ });
+ rx
+ }
}
pub fn msg_channel<T: Serialize + for<'de> Deserialize<'de>>() -> Result<(MsgSender<T>, MsgReceiver<T>), Error> {
ipc::channel()
}
pub fn payload_channel() -> Result<(PayloadSender, PayloadReceiver), Error> {
ipc::bytes_channel()
--- a/gfx/webrender_api/src/channel_mpsc.rs
+++ b/gfx/webrender_api/src/channel_mpsc.rs
@@ -20,16 +20,20 @@ impl PayloadSenderHelperMethods for Payl
self.send(payload)
}
}
impl PayloadReceiverHelperMethods for PayloadReceiver {
fn recv_payload(&self) -> Result<Payload, Error> {
self.recv()
}
+
+ fn to_mpsc_receiver(self) -> Receiver<Payload> {
+ self.rx
+ }
}
pub struct MsgReceiver<T> {
rx: mpsc::Receiver<T>,
}
impl<T> MsgReceiver<T> {
pub fn recv(&self) -> Result<T, Error> {
--- a/gfx/webrender_api/src/display_item.rs
+++ b/gfx/webrender_api/src/display_item.rs
@@ -764,22 +764,22 @@ impl ComplexClipRegion {
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct ClipChainId(pub u64, pub PipelineId);
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub enum ClipId {
- Clip(u64, PipelineId),
+ Clip(usize, PipelineId),
ClipChain(ClipChainId),
}
-const ROOT_REFERENCE_FRAME_CLIP_ID: u64 = 0;
-const ROOT_SCROLL_NODE_CLIP_ID: u64 = 1;
+const ROOT_REFERENCE_FRAME_CLIP_ID: usize = 0;
+const ROOT_SCROLL_NODE_CLIP_ID: usize = 1;
impl ClipId {
pub fn root_scroll_node(pipeline_id: PipelineId) -> ClipId {
ClipId::Clip(ROOT_SCROLL_NODE_CLIP_ID, pipeline_id)
}
pub fn root_reference_frame(pipeline_id: PipelineId) -> ClipId {
ClipId::Clip(ROOT_REFERENCE_FRAME_CLIP_ID, pipeline_id)
--- a/gfx/webrender_api/src/display_list.rs
+++ b/gfx/webrender_api/src/display_list.rs
@@ -24,18 +24,18 @@ use {PropertyBinding, PushStackingContex
use {RectangleDisplayItem, ScrollFrameDisplayItem, ScrollPolicy, ScrollSensitivity, Shadow};
use {SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, StickyOffsetBounds};
use {TextDisplayItem, TransformStyle, YuvColorSpace, YuvData, YuvImageDisplayItem};
// We don't want to push a long text-run. If a text-run is too long, split it into several parts.
// This needs to be set to (renderer::MAX_VERTEX_TEXTURE_WIDTH - VECS_PER_PRIM_HEADER - VECS_PER_TEXT_RUN) * 2
pub const MAX_TEXT_RUN_LENGTH: usize = 2038;
-// We start at 2 , because the root reference is always 0 and the root scroll node is always 1.
-const FIRST_CLIP_ID: u64 = 2;
+// We start at 2, because the root reference is always 0 and the root scroll node is always 1.
+const FIRST_CLIP_ID: usize = 2;
#[repr(C)]
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct ItemRange<T> {
start: usize,
length: usize,
_boo: PhantomData<T>,
}
@@ -73,16 +73,18 @@ pub struct BuiltDisplayList {
#[derive(Copy, Clone, Default, Deserialize, Serialize)]
pub struct BuiltDisplayListDescriptor {
/// The first IPC time stamp: before any work has been done
builder_start_time: u64,
/// The second IPC time stamp: after serialization
builder_finish_time: u64,
/// The third IPC time stamp: just before sending
send_start_time: u64,
+ /// The amount of clips ids assigned while building this display list.
+ total_clip_ids: usize,
}
pub struct BuiltDisplayListIter<'a> {
list: &'a BuiltDisplayList,
data: &'a [u8],
cur_item: DisplayItem,
cur_stops: ItemRange<GradientStop>,
cur_glyphs: ItemRange<GlyphInstance>,
@@ -138,16 +140,20 @@ impl BuiltDisplayList {
pub fn times(&self) -> (u64, u64, u64) {
(
self.descriptor.builder_start_time,
self.descriptor.builder_finish_time,
self.descriptor.send_start_time,
)
}
+ pub fn total_clip_ids(&self) -> usize {
+ self.descriptor.total_clip_ids
+ }
+
pub fn iter(&self) -> BuiltDisplayListIter {
BuiltDisplayListIter::new(self)
}
pub fn get<'de, T: Deserialize<'de>>(&self, range: ItemRange<T>) -> AuxIter<T> {
AuxIter::new(&self.data[range.start .. range.start + range.length])
}
}
@@ -494,48 +500,60 @@ impl<'de> Deserialize<'de> for BuiltDisp
use display_item::CompletelySpecificDisplayItem::*;
use display_item::{CompletelySpecificDisplayItem, GenericDisplayItem};
let list = Vec::<GenericDisplayItem<CompletelySpecificDisplayItem>>
::deserialize(deserializer)?;
let mut data = Vec::new();
let mut temp = Vec::new();
+ let mut total_clip_ids = FIRST_CLIP_ID;
for complete in list {
let item = DisplayItem {
item: match complete.item {
Clip(specific_item, complex_clips) => {
+ total_clip_ids += 1;
DisplayListBuilder::push_iter_impl(&mut temp, complex_clips);
SpecificDisplayItem::Clip(specific_item)
},
ClipChain(specific_item, clip_chain_ids) => {
DisplayListBuilder::push_iter_impl(&mut temp, clip_chain_ids);
SpecificDisplayItem::ClipChain(specific_item)
}
ScrollFrame(specific_item, complex_clips) => {
+ total_clip_ids += 2;
DisplayListBuilder::push_iter_impl(&mut temp, complex_clips);
SpecificDisplayItem::ScrollFrame(specific_item)
},
- StickyFrame(specific_item) => SpecificDisplayItem::StickyFrame(specific_item),
+ StickyFrame(specific_item) => {
+ total_clip_ids += 1;
+ SpecificDisplayItem::StickyFrame(specific_item)
+ }
Rectangle(specific_item) => SpecificDisplayItem::Rectangle(specific_item),
ClearRectangle => SpecificDisplayItem::ClearRectangle,
Line(specific_item) => SpecificDisplayItem::Line(specific_item),
Text(specific_item, glyphs) => {
DisplayListBuilder::push_iter_impl(&mut temp, glyphs);
SpecificDisplayItem::Text(specific_item)
},
Image(specific_item) => SpecificDisplayItem::Image(specific_item),
YuvImage(specific_item) => SpecificDisplayItem::YuvImage(specific_item),
Border(specific_item) => SpecificDisplayItem::Border(specific_item),
BoxShadow(specific_item) => SpecificDisplayItem::BoxShadow(specific_item),
Gradient(specific_item) => SpecificDisplayItem::Gradient(specific_item),
RadialGradient(specific_item) =>
SpecificDisplayItem::RadialGradient(specific_item),
- Iframe(specific_item) => SpecificDisplayItem::Iframe(specific_item),
+ Iframe(specific_item) => {
+ total_clip_ids += 1;
+ SpecificDisplayItem::Iframe(specific_item)
+ }
PushStackingContext(specific_item, filters) => {
+ if specific_item.stacking_context.reference_frame_id.is_some() {
+ total_clip_ids += 1;
+ }
DisplayListBuilder::push_iter_impl(&mut temp, filters);
SpecificDisplayItem::PushStackingContext(specific_item)
},
PopStackingContext => SpecificDisplayItem::PopStackingContext,
SetGradientStops(stops) => {
DisplayListBuilder::push_iter_impl(&mut temp, stops);
SpecificDisplayItem::SetGradientStops
},
@@ -551,16 +569,17 @@ impl<'de> Deserialize<'de> for BuiltDisp
}
Ok(BuiltDisplayList {
data,
descriptor: BuiltDisplayListDescriptor {
builder_start_time: 0,
builder_finish_time: 1,
send_start_time: 0,
+ total_clip_ids,
},
})
}
}
// This is a replacement for bincode::serialize_into(&vec)
// The default implementation Write for Vec will basically
// call extend_from_slice(). Serde ends up calling that for every
@@ -775,26 +794,26 @@ impl<'a, 'b> Read for UnsafeReader<'a, '
Ok(())
}
}
#[derive(Clone, Debug)]
pub struct SaveState {
dl_len: usize,
clip_stack_len: usize,
- next_clip_id: u64,
+ next_clip_id: usize,
next_clip_chain_id: u64,
}
#[derive(Clone)]
pub struct DisplayListBuilder {
pub data: Vec<u8>,
pub pipeline_id: PipelineId,
clip_stack: Vec<ClipAndScrollInfo>,
- next_clip_id: u64,
+ next_clip_id: usize,
next_clip_chain_id: u64,
builder_start_time: u64,
/// The size of the content of this display list. This is used to allow scrolling
/// outside the bounds of the display list items themselves.
content_size: LayoutSize,
save_state: Option<SaveState>,
}
@@ -1536,14 +1555,15 @@ impl DisplayListBuilder {
(
self.pipeline_id,
self.content_size,
BuiltDisplayList {
descriptor: BuiltDisplayListDescriptor {
builder_start_time: self.builder_start_time,
builder_finish_time: end_time,
send_start_time: 0,
+ total_clip_ids: self.next_clip_id,
},
data: self.data,
},
)
}
}
--- a/gfx/webrender_bindings/Cargo.toml
+++ b/gfx/webrender_bindings/Cargo.toml
@@ -2,17 +2,17 @@
name = "webrender_bindings"
version = "0.1.0"
authors = ["The Mozilla Project Developers"]
license = "MPL-2.0"
[dependencies]
rayon = "1"
thread_profiler = "0.1.1"
-euclid = "0.16"
+euclid = { version = "0.17", features = ["serde"] }
app_units = "0.6"
gleam = "0.4.20"
log = "0.4"
[dependencies.webrender]
path = "../webrender"
version = "0.57.0"
default-features = false
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-8a19316a733a484bf9bafb8257e3008b1418bfe4
+22b831c02479eea31821f49a0fac7dd699083557
--- a/gfx/wrench/Cargo.toml
+++ b/gfx/wrench/Cargo.toml
@@ -5,17 +5,17 @@ authors = ["Vladimir Vukicevic <vladimir
build = "build.rs"
license = "MPL-2.0"
[dependencies]
base64 = "0.3"
bincode = "0.9"
byteorder = "1.0"
env_logger = { version = "0.5", optional = true }
-euclid = "0.16"
+euclid = "0.17"
gleam = "0.4"
glutin = "0.12"
app_units = "0.6"
image = "0.17"
clap = { version = "2", features = ["yaml"] }
lazy_static = "1"
log = "0.4"
yaml-rust = { git = "https://github.com/vvuk/yaml-rust", features = ["preserve_order"] }
--- a/gfx/wrench/src/binary_frame_reader.rs
+++ b/gfx/wrench/src/binary_frame_reader.rs
@@ -6,17 +6,17 @@ use bincode::deserialize;
use byteorder::{LittleEndian, ReadBytesExt};
use clap;
use std::any::TypeId;
use std::fs::File;
use std::io::{Read, Seek, SeekFrom};
use std::path::{Path, PathBuf};
use std::{mem, process};
use webrender::WEBRENDER_RECORDING_HEADER;
-use webrender::api::{ApiMsg, DocumentMsg};
+use webrender::api::{ApiMsg, SceneMsg};
use wrench::{Wrench, WrenchThing};
#[derive(Clone)]
enum Item {
Message(ApiMsg),
Data(Vec<u8>),
}
@@ -135,27 +135,27 @@ impl WrenchThing for BinaryFrameReader {
let msg = deserialize(&buffer).unwrap();
let mut store_message = true;
// In order to detect the first valid frame, we
// need to find:
// (a) SetRootPipeline
// (b) SetDisplayList
// (c) GenerateFrame that occurs *after* (a) and (b)
match msg {
- ApiMsg::UpdateDocument(_, ref doc_msgs) => {
- for doc_msg in doc_msgs {
+ ApiMsg::UpdateDocument(_, ref txn) => {
+ if txn.generate_frame {
+ found_frame_marker = true;
+ }
+ for doc_msg in &txn.scene_ops {
match *doc_msg {
- DocumentMsg::GenerateFrame => {
- found_frame_marker = true;
- }
- DocumentMsg::SetDisplayList { .. } => {
+ SceneMsg::SetDisplayList { .. } => {
found_frame_marker = false;
found_display_list = true;
}
- DocumentMsg::SetRootPipeline(..) => {
+ SceneMsg::SetRootPipeline(..) => {
found_frame_marker = false;
found_pipeline = true;
}
_ => {}
}
}
}
// Wrench depends on the document always existing
--- a/gfx/wrench/src/json_frame_writer.rs
+++ b/gfx/wrench/src/json_frame_writer.rs
@@ -273,23 +273,21 @@ impl fmt::Debug for JsonFrameWriter {
}
}
impl webrender::ApiRecordingReceiver for JsonFrameWriter {
fn write_msg(&mut self, _: u32, msg: &ApiMsg) {
match *msg {
ApiMsg::UpdateResources(ref updates) => self.update_resources(updates),
- ApiMsg::UpdateDocument(_, ref doc_msgs) => {
- for doc_msg in doc_msgs {
+ ApiMsg::UpdateDocument(_, ref txn) => {
+ self.update_resources(&txn.resource_updates);
+ for doc_msg in &txn.scene_ops {
match *doc_msg {
- DocumentMsg::UpdateResources(ref resources) => {
- self.update_resources(resources);
- }
- DocumentMsg::SetDisplayList {
+ SceneMsg::SetDisplayList {
ref epoch,
ref pipeline_id,
ref background,
ref viewport_size,
ref list_descriptor,
..
} => {
self.begin_write_display_list(
--- a/gfx/wrench/src/main.rs
+++ b/gfx/wrench/src/main.rs
@@ -63,16 +63,17 @@ use png::save_flipped;
use rawtest::RawtestHarness;
use reftest::{ReftestHarness, ReftestOptions};
#[cfg(feature = "headless")]
use std::ffi::CString;
#[cfg(feature = "headless")]
use std::mem;
use std::os::raw::c_void;
use std::path::{Path, PathBuf};
+use std::process;
use std::ptr;
use std::rc::Rc;
use std::sync::mpsc::{channel, Sender, Receiver};
use webrender::DebugFlags;
use webrender::api::*;
use wrench::{Wrench, WrenchThing};
use yaml_frame_reader::YamlFrameReader;
@@ -407,26 +408,28 @@ fn main() {
_ => panic!("Unknown surface argument value")
};
let reader = YamlFrameReader::new_from_args(subargs);
png::png(&mut wrench, surface, &mut window, reader, rx.unwrap());
wrench.renderer.deinit();
return;
} else if let Some(subargs) = args.subcommand_matches("reftest") {
let dim = window.get_inner_size();
- let harness = ReftestHarness::new(&mut wrench, &mut window, rx.unwrap());
let base_manifest = Path::new("reftests/reftest.list");
let specific_reftest = subargs.value_of("REFTEST").map(|x| Path::new(x));
let mut reftest_options = ReftestOptions::default();
if let Some(allow_max_diff) = subargs.value_of("fuzz_tolerance") {
reftest_options.allow_max_difference = allow_max_diff.parse().unwrap_or(1);
reftest_options.allow_num_differences = dim.width as usize * dim.height as usize;
}
- harness.run(base_manifest, specific_reftest, &reftest_options);
- return;
+ let num_failures = ReftestHarness::new(&mut wrench, &mut window, rx.unwrap())
+ .run(base_manifest, specific_reftest, &reftest_options);
+ wrench.renderer.deinit();
+ // exit with an error code to fail on CI
+ process::exit(num_failures as _);
} else if let Some(_) = args.subcommand_matches("rawtest") {
{
let harness = RawtestHarness::new(&mut wrench, &mut window, rx.unwrap());
harness.run();
}
wrench.renderer.deinit();
return;
} else if let Some(subargs) = args.subcommand_matches("perf") {
@@ -460,17 +463,19 @@ fn main() {
let mut cpu_profile_index = 0;
let dim = window.get_inner_size();
wrench.update(dim);
thing.do_frame(&mut wrench);
let mut body = |wrench: &mut Wrench, global_event: glutin::Event| {
if let Some(window_title) = wrench.take_title() {
- window.set_title(&window_title);
+ if !cfg!(windows) { //TODO: calling `set_title` from inside the `run_forever` loop is illegal...
+ window.set_title(&window_title);
+ }
}
let mut do_frame = false;
let mut do_render = false;
match global_event {
glutin::Event::Awakened => {
do_render = true;
--- a/gfx/wrench/src/reftest.rs
+++ b/gfx/wrench/src/reftest.rs
@@ -294,17 +294,17 @@ pub struct ReftestHarness<'a> {
window: &'a mut WindowWrapper,
rx: Receiver<()>,
}
impl<'a> ReftestHarness<'a> {
pub fn new(wrench: &'a mut Wrench, window: &'a mut WindowWrapper, rx: Receiver<()>) -> Self {
ReftestHarness { wrench, window, rx }
}
- pub fn run(mut self, base_manifest: &Path, reftests: Option<&Path>, options: &ReftestOptions) {
+ pub fn run(mut self, base_manifest: &Path, reftests: Option<&Path>, options: &ReftestOptions) -> usize {
let manifest = ReftestManifest::new(base_manifest, options);
let reftests = manifest.find(reftests.unwrap_or(&PathBuf::new()));
let mut total_passing = 0;
let mut failing = Vec::new();
for t in reftests {
if self.run_reftest(t) {
@@ -323,18 +323,17 @@ impl<'a> ReftestHarness<'a> {
if !failing.is_empty() {
println!("\nReftests with unexpected results:");
for reftest in &failing {
println!("\t{}", reftest);
}
}
- // panic here so that we fail CI
- assert!(failing.is_empty());
+ failing.len()
}
fn run_reftest(&mut self, t: &Reftest) -> bool {
println!("REFTEST {}", t);
self.wrench.set_page_zoom(ZoomFactor::new(t.zoom_factor));
if t.disable_dual_source_blending {
--- a/gfx/wrench/src/ron_frame_writer.rs
+++ b/gfx/wrench/src/ron_frame_writer.rs
@@ -151,23 +151,21 @@ impl fmt::Debug for RonFrameWriter {
write!(f, "RonFrameWriter")
}
}
impl webrender::ApiRecordingReceiver for RonFrameWriter {
fn write_msg(&mut self, _: u32, msg: &ApiMsg) {
match *msg {
ApiMsg::UpdateResources(ref updates) => self.update_resources(updates),
- ApiMsg::UpdateDocument(_, ref doc_msgs) => {
- for doc_msg in doc_msgs {
+ ApiMsg::UpdateDocument(_, ref txn) => {
+ self.update_resources(&txn.resource_updates);
+ for doc_msg in &txn.scene_ops {
match *doc_msg {
- DocumentMsg::UpdateResources(ref resources) => {
- self.update_resources(resources);
- }
- DocumentMsg::SetDisplayList {
+ SceneMsg::SetDisplayList {
ref epoch,
ref pipeline_id,
ref background,
ref viewport_size,
ref list_descriptor,
..
} => {
self.begin_write_display_list(
--- a/gfx/wrench/src/yaml_frame_writer.rs
+++ b/gfx/wrench/src/yaml_frame_writer.rs
@@ -1119,46 +1119,49 @@ impl YamlFrameWriter {
}
impl webrender::ApiRecordingReceiver for YamlFrameWriterReceiver {
fn write_msg(&mut self, _: u32, msg: &ApiMsg) {
match *msg {
ApiMsg::UpdateResources(ref updates) => {
self.frame_writer.update_resources(updates);
}
- ApiMsg::UpdateDocument(_, ref doc_msgs) => {
- for doc_msg in doc_msgs {
+ ApiMsg::UpdateDocument(_, ref txn) => {
+ self.frame_writer.update_resources(&txn.resource_updates);
+ for doc_msg in &txn.scene_ops {
match *doc_msg {
- DocumentMsg::UpdateResources(ref resources) => {
- self.frame_writer.update_resources(resources);
- }
- DocumentMsg::SetDisplayList {
+ SceneMsg::SetDisplayList {
ref epoch,
ref pipeline_id,
ref background,
ref viewport_size,
ref list_descriptor,
..
} => {
self.frame_writer.begin_write_display_list(
&mut self.scene,
epoch,
pipeline_id,
background,
viewport_size,
list_descriptor,
);
}
- DocumentMsg::SetRootPipeline(ref pipeline_id) => {
+ SceneMsg::SetRootPipeline(ref pipeline_id) => {
self.scene.set_root_pipeline_id(pipeline_id.clone());
}
- DocumentMsg::RemovePipeline(ref pipeline_id) => {
+ SceneMsg::RemovePipeline(ref pipeline_id) => {
self.scene.remove_pipeline(pipeline_id);
}
- DocumentMsg::UpdateDynamicProperties(ref properties) => {
+ _ => {}
+ }
+ }
+ for doc_msg in &txn.frame_ops {
+ match *doc_msg {
+ FrameMsg::UpdateDynamicProperties(ref properties) => {
self.scene.properties.set_properties(properties);
}
_ => {}
}
}
}
_ => {}
}