From d97598b4cde28dfeb3fc974a1ccf752af4e7558e Mon Sep 17 00:00:00 2001 From: Alexis Beingessner Date: Wed, 5 Jul 2017 16:33:30 -0400 Subject: [PATCH 1/3] Add PushTextShadow and PopTextShadow DisplayItems --- webrender/src/frame.rs | 7 ++++++- webrender/src/frame_builder.rs | 20 +++++++++++++++++--- webrender_api/src/display_item.rs | 10 +++++++++- webrender_api/src/display_list.rs | 12 +++++++++--- 4 files changed, 41 insertions(+), 8 deletions(-) diff --git a/webrender/src/frame.rs b/webrender/src/frame.rs index bd450642bd..635bfd50c6 100644 --- a/webrender/src/frame.rs +++ b/webrender/src/frame.rs @@ -583,7 +583,6 @@ impl Frame { item.local_clip(), text_info.font_key, text_info.size, - text_info.blur_radius, &text_info.color, item.glyphs(), item.display_list().get(item.glyphs()).count(), @@ -718,6 +717,12 @@ impl Frame { SpecificDisplayItem::PopStackingContext => unreachable!("Should have returned in parent method."), + SpecificDisplayItem::PushTextShadow(shadow) => { + context.builder.push_text_shadow(shadow); + } + SpecificDisplayItem::PopTextShadow => { + context.builder.pop_text_shadow(); + } } None } diff --git a/webrender/src/frame_builder.rs b/webrender/src/frame_builder.rs index db5947328e..05f1a2c28f 100644 --- a/webrender/src/frame_builder.rs +++ b/webrender/src/frame_builder.rs @@ -6,8 +6,8 @@ use api::{BorderDetails, BorderDisplayItem, BoxShadowClipMode, ClipAndScrollInfo use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceUintRect, DeviceUintSize}; use api::{ExtendMode, FontKey, FontRenderMode, GlyphInstance, GlyphOptions, GradientStop}; use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect, LayerSize}; -use api::{LayerToScrollTransform, LayerVector2D, LocalClip, PipelineId, RepeatMode, TileOffset}; -use api::{TransformStyle, WebGLContextId, WorldPixel, YuvColorSpace, YuvData}; +use api::{LayerToScrollTransform, LayerVector2D, LocalClip, PipelineId, RepeatMode, TextShadow}; +use api::{TileOffset, TransformStyle, WebGLContextId, WorldPixel, YuvColorSpace, YuvData}; use app_units::Au; use frame::FrameId; use gpu_cache::GpuCache; @@ -116,6 +116,7 @@ pub struct FrameBuilder { stacking_context_store: Vec, clip_scroll_group_store: Vec, packed_layers: Vec, + text_shadows: Vec, scrollbar_prims: Vec, @@ -144,6 +145,7 @@ impl FrameBuilder { clip_scroll_group_store: recycle_vec(prev.clip_scroll_group_store), cmds: recycle_vec(prev.cmds), packed_layers: recycle_vec(prev.packed_layers), + text_shadows: recycle_vec(prev.text_shadows), scrollbar_prims: recycle_vec(prev.scrollbar_prims), reference_frame_stack: recycle_vec(prev.reference_frame_stack), stacking_context_stack: recycle_vec(prev.stacking_context_stack), @@ -160,6 +162,7 @@ impl FrameBuilder { clip_scroll_group_store: Vec::new(), cmds: Vec::new(), packed_layers: Vec::new(), + text_shadows: Vec::new(), scrollbar_prims: Vec::new(), reference_frame_stack: Vec::new(), stacking_context_stack: Vec::new(), @@ -287,6 +290,8 @@ impl FrameBuilder { pub fn pop_stacking_context(&mut self) { self.cmds.push(PrimitiveRunCmd::PopStackingContext); self.stacking_context_stack.pop(); + assert!(self.text_shadows.is_empty(), + "Found unpopped text shadows when popping stacking context!"); } pub fn push_reference_frame(&mut self, @@ -392,6 +397,14 @@ impl FrameBuilder { self.reference_frame_stack.pop(); } + pub fn push_text_shadow(&mut self, shadow: TextShadow) { + self.text_shadows.push(shadow); + } + + pub fn pop_text_shadow(&mut self) { + self.text_shadows.pop().expect("Too many PopTextShadows?"); + } + pub fn add_solid_rectangle(&mut self, clip_and_scroll: ClipAndScrollInfo, rect: &LayerRect, @@ -732,7 +745,6 @@ impl FrameBuilder { local_clip: &LocalClip, font_key: FontKey, size: Au, - blur_radius: f32, color: &ColorF, glyph_range: ItemRange, glyph_count: usize, @@ -745,6 +757,8 @@ impl FrameBuilder { return } + let mut blur_radius = 0.0; // TODO: remove this in favour of PushTextShadow handling + // Expand the rectangle of the text run by the blur radius. let rect = rect.inflate(blur_radius, blur_radius); diff --git a/webrender_api/src/display_item.rs b/webrender_api/src/display_item.rs index dc1ded5710..3ef8dede8d 100644 --- a/webrender_api/src/display_item.rs +++ b/webrender_api/src/display_item.rs @@ -65,6 +65,8 @@ pub enum SpecificDisplayItem { SetGradientStops, PushNestedDisplayList, PopNestedDisplayList, + PushTextShadow(TextShadow), + PopTextShadow, } #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)] @@ -84,7 +86,6 @@ pub struct TextDisplayItem { pub font_key: FontKey, pub size: Au, pub color: ColorF, - pub blur_radius: f32, pub glyph_options: Option, } // IMPLICIT: glyphs: Vec @@ -224,6 +225,13 @@ pub struct BoxShadowDisplayItem { pub clip_mode: BoxShadowClipMode, } +#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)] +pub struct TextShadow { + pub offset: LayoutVector2D, + pub color: ColorF, + pub blur_radius: f32, +} + #[repr(u32)] #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Ord, PartialOrd)] pub enum ExtendMode { diff --git a/webrender_api/src/display_list.rs b/webrender_api/src/display_list.rs index 26c4a0321c..5f919f512e 100644 --- a/webrender_api/src/display_list.rs +++ b/webrender_api/src/display_list.rs @@ -14,7 +14,7 @@ use {GradientStop, IframeDisplayItem, ImageDisplayItem, ImageKey, ImageMask, Ima use {LayoutPoint, LayoutRect, LayoutSize, LayoutTransform, LayoutVector2D, LocalClip}; use {MixBlendMode, PipelineId, PropertyBinding, PushStackingContextDisplayItem, RadialGradient}; use {RadialGradientDisplayItem, RectangleDisplayItem, ScrollPolicy, SpecificDisplayItem}; -use {StackingContext, TextDisplayItem, TransformStyle, WebGLContextId, WebGLDisplayItem}; +use {StackingContext, TextDisplayItem, TextShadow, TransformStyle, WebGLContextId, WebGLDisplayItem}; use {YuvColorSpace, YuvData, YuvImageDisplayItem}; use std::marker::PhantomData; @@ -543,7 +543,6 @@ impl DisplayListBuilder { font_key: FontKey, color: ColorF, size: Au, - blur_radius: f32, glyph_options: Option) { // Sanity check - anything with glyphs bigger than this // is probably going to consume too much memory to render @@ -556,7 +555,6 @@ impl DisplayListBuilder { color, font_key, size, - blur_radius, glyph_options, }); @@ -914,6 +912,14 @@ impl DisplayListBuilder { self.push_new_empty_item(SpecificDisplayItem::PopNestedDisplayList); } + pub fn push_text_shadow(&mut self, shadow: TextShadow) { + self.push_new_empty_item(SpecificDisplayItem::PushTextShadow(shadow)); + } + + pub fn pop_text_shadow(&mut self) { + self.push_new_empty_item(SpecificDisplayItem::PopTextShadow); + } + pub fn finalize(self) -> (PipelineId, LayoutSize, BuiltDisplayList) { let end_time = precise_time_ns(); From 089818fa41dc8f5267c8618a783665cf9aa67e01 Mon Sep 17 00:00:00 2001 From: Alexis Beingessner Date: Wed, 5 Jul 2017 16:33:50 -0400 Subject: [PATCH 2/3] update wrench and tests to reflect new shadow mechanism --- webrender/examples/basic.rs | 1 - wrench/reftests/text/reftest.list | 2 ++ wrench/reftests/text/shadow-complex.yaml | 45 ++++++++++++++++++++++++ wrench/reftests/text/shadow-many.yaml | 31 ++++++++++++++++ wrench/reftests/text/shadow-single.yaml | 10 ++++-- wrench/reftests/text/shadow.yaml | 10 ++++-- wrench/src/yaml_frame_reader.rs | 21 +++++++++-- wrench/src/yaml_frame_writer.rs | 9 +++++ 8 files changed, 122 insertions(+), 7 deletions(-) create mode 100644 wrench/reftests/text/shadow-complex.yaml create mode 100644 wrench/reftests/text/shadow-many.yaml diff --git a/webrender/examples/basic.rs b/webrender/examples/basic.rs index a65f95d7f8..88c093c2a9 100644 --- a/webrender/examples/basic.rs +++ b/webrender/examples/basic.rs @@ -290,7 +290,6 @@ fn body(api: &RenderApi, font_key, ColorF::new(1.0, 1.0, 0.0, 1.0), Au::from_px(32), - 0.0, None); } diff --git a/wrench/reftests/text/reftest.list b/wrench/reftests/text/reftest.list index 891c2710d8..e01fae0bbd 100644 --- a/wrench/reftests/text/reftest.list +++ b/wrench/reftests/text/reftest.list @@ -2,4 +2,6 @@ != negative-pos.yaml blank.yaml != shadow.yaml text.yaml != shadow-single.yaml blank.yaml +!= shadow-many.yaml shadow.yaml +!= shadow-complex.yaml shadow-many.yaml != non-opaque.yaml non-opaque-notref.yaml diff --git a/wrench/reftests/text/shadow-complex.yaml b/wrench/reftests/text/shadow-complex.yaml new file mode 100644 index 0000000000..795656d20d --- /dev/null +++ b/wrench/reftests/text/shadow-complex.yaml @@ -0,0 +1,45 @@ +--- # The same as shadow-many.yaml, except the shadows only apply to parts of the text +root: + items: + - + type: "text-shadow" + blur-radius: 5 + offset: [0, 0] + color: black + - + type: "text-shadow" + blur-radius: 0 + offset: [2, 3] + color: red + - + bounds: [14, 18, 205, 35] + glyphs: [55, 75, 76, 86] + offsets: [16, 43, 35.533333, 43, 51.533333, 43, 60.4, 43] + size: 18 + color: [0, 0, 0, 0] # actual text is transparent + font: "VeraBd.ttf" + - + type: "pop-text-shadow" + - + type: "text-shadow" + blur-radius: 3 + offset: [-2, 3.5] + color: blue + - + bounds: [14, 18, 205, 35] + glyphs: [3, 76, 86, 3, 87, 75] + offsets: [72.833336, 43, 80.833336, 43, 89.7, 43, 102.13333, 43, 110.13333, 43, 119, 43] + size: 18 + color: [0, 0, 0, 0] # actual text is transparent + font: "VeraBd.ttf" + - + type: "pop-text-shadow" + - + bounds: [14, 18, 205, 35] + glyphs: [72, 3, 69, 72, 86, 87] + offsets: [135, 43, 149.2, 43, 157.2, 43, 173.2, 43, 187.4, 43, 196.26666, 43] + size: 18 + color: [0, 0, 0, 0] # actual text is transparent + font: "VeraBd.ttf" + - + type: "pop-text-shadow" diff --git a/wrench/reftests/text/shadow-many.yaml b/wrench/reftests/text/shadow-many.yaml new file mode 100644 index 0000000000..e795f7ff39 --- /dev/null +++ b/wrench/reftests/text/shadow-many.yaml @@ -0,0 +1,31 @@ +--- # the same as shadow.yaml, except there are many shadows with different offsets and colors +root: + items: + - + type: "text-shadow" + blur-radius: 5 + offset: [0, 0] + color: black + - + type: "text-shadow" + blur-radius: 0 + offset: [2, 3] + color: red + - + type: "text-shadow" + blur-radius: 3 + offset: [-2, 3.5] + color: blue + - + bounds: [14, 18, 205, 35] + glyphs: [55, 75, 76, 86, 3, 76, 86, 3, 87, 75, 72, 3, 69, 72, 86, 87] + offsets: [16, 43, 35.533333, 43, 51.533333, 43, 60.4, 43, 72.833336, 43, 80.833336, 43, 89.7, 43, 102.13333, 43, 110.13333, 43, 119, 43, 135, 43, 149.2, 43, 157.2, 43, 173.2, 43, 187.4, 43, 196.26666, 43] + size: 18 + color: [0, 0, 0, 0] # actual text is transparent + font: "VeraBd.ttf" + - + type: "pop-text-shadow" + - + type: "pop-text-shadow" + - + type: "pop-text-shadow" diff --git a/wrench/reftests/text/shadow-single.yaml b/wrench/reftests/text/shadow-single.yaml index 4f8d6dd26a..466db980f1 100644 --- a/wrench/reftests/text/shadow-single.yaml +++ b/wrench/reftests/text/shadow-single.yaml @@ -1,11 +1,17 @@ --- root: items: + - + type: "text-shadow" + blur-radius: 5 + offset: [0, 0] + color: black - bounds: [14, 18, 205, 35] glyphs: [55] offsets: [16, 43] size: 18 - color: black - blur-radius: 5 + color: [0, 0, 0, 0] # actual text is transparent font: "VeraBd.ttf" + - + type: "pop-text-shadow" diff --git a/wrench/reftests/text/shadow.yaml b/wrench/reftests/text/shadow.yaml index 001821829c..905fd1e09c 100644 --- a/wrench/reftests/text/shadow.yaml +++ b/wrench/reftests/text/shadow.yaml @@ -1,11 +1,17 @@ --- root: items: + - + type: "text-shadow" + blur-radius: 5 + offset: [0, 0] + color: black - bounds: [14, 18, 205, 35] glyphs: [55, 75, 76, 86, 3, 76, 86, 3, 87, 75, 72, 3, 69, 72, 86, 87] offsets: [16, 43, 35.533333, 43, 51.533333, 43, 60.4, 43, 72.833336, 43, 80.833336, 43, 89.7, 43, 102.13333, 43, 110.13333, 43, 119, 43, 135, 43, 149.2, 43, 157.2, 43, 173.2, 43, 187.4, 43, 196.26666, 43] size: 18 - color: black - blur-radius: 5 + color: [0, 0, 0, 0] # actual text is transparent font: "VeraBd.ttf" + - + type: "pop-text-shadow" diff --git a/wrench/src/yaml_frame_reader.rs b/wrench/src/yaml_frame_reader.rs index 8b2ff69c47..052dbf870a 100644 --- a/wrench/src/yaml_frame_reader.rs +++ b/wrench/src/yaml_frame_reader.rs @@ -429,7 +429,9 @@ impl YamlFrameReader { fn handle_text(&mut self, wrench: &mut Wrench, item: &Yaml, local_clip: LocalClip) { let size = item["size"].as_pt_to_au().unwrap_or(Au::from_f32_px(16.0)); let color = item["color"].as_colorf().unwrap_or(*BLACK_COLOR); - let blur_radius = item["blur-radius"].as_force_f32().unwrap_or(0.0); + + assert!(item["blur-radius"].is_badvalue(), + "text no longer has a blur radius, use PushTextShadow and PopTextShadow"); let (font_key, native_key) = if !item["family"].is_badvalue() { wrench.font_key_from_yaml_table(item) @@ -498,7 +500,6 @@ impl YamlFrameReader { font_key, color, size, - blur_radius, None); } @@ -571,6 +572,8 @@ impl YamlFrameReader { "box-shadow" => self.handle_box_shadow(item, local_clip), "iframe" => self.handle_iframe(item), "stacking-context" => self.add_stacking_context_from_yaml(wrench, item, false), + "text-shadow" => self.handle_push_text_shadow(item), + "pop-text-shadow" => self.handle_pop_text_shadow(), _ => println!("Skipping unknown item type: {:?}", item), } @@ -606,6 +609,20 @@ impl YamlFrameReader { self.builder().pop_clip_id(); } + pub fn handle_push_text_shadow(&mut self, yaml: &Yaml) { + let blur_radius = yaml["blur-radius"].as_f32().unwrap_or(0.0); + let offset = yaml["blur-radius"].as_vector().unwrap_or(LayoutVector2D::zero()); + let color = yaml["color"].as_colorf().unwrap_or(*BLACK_COLOR); + + self.builder().push_text_shadow(TextShadow { + blur_radius, offset, color + }); + } + + pub fn handle_pop_text_shadow(&mut self) { + self.builder().pop_text_shadow(); + } + pub fn handle_clip(&mut self, wrench: &mut Wrench, yaml: &Yaml) { let clip_rect = yaml["bounds"].as_rect().expect("clip must have a bounds"); let id = yaml["id"].as_i64().map(|id| ClipId::new(id as u64, self.builder().pipeline_id)); diff --git a/wrench/src/yaml_frame_writer.rs b/wrench/src/yaml_frame_writer.rs index 7b2f1f9656..29742572ad 100644 --- a/wrench/src/yaml_frame_writer.rs +++ b/wrench/src/yaml_frame_writer.rs @@ -791,6 +791,15 @@ impl YamlFrameWriter { PopNestedDisplayList => clip_id_mapper.pop_nested_display_list_ids(), PopStackingContext => return, SetGradientStops => { panic!("dummy item yielded?") }, + PushTextShadow(shadow) => { + str_node(&mut v, "type", "text-shadow"); + vector_node(&mut v, "offset", &shadow.offset); + color_node(&mut v, "color", shadow.color); + f32_node(&mut v, "blur-radius", shadow.blur_radius); + } + PopTextShadow => { + str_node(&mut v, "type", "pop-text-shadow"); + }, } if !v.is_empty() { list.push(Yaml::Hash(v)); From 02ca881c4cfded96d2968616484642fb77d303d2 Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Wed, 12 Jul 2017 12:47:57 +1000 Subject: [PATCH 3/3] Update the backend to work with the modified push/pop text shadow interface. This allows multiple text runs to be combined in to one shadow, and fixes a few existing bugs encountered along the way. It doesn't (yet) support other items in the text shadow, but the structures are in place to make this easy to add. --- webrender/res/cs_blur.fs.glsl | 9 ++ webrender/res/cs_text_run.vs.glsl | 2 +- webrender/src/frame.rs | 4 +- webrender/src/frame_builder.rs | 98 ++++++++++++---- webrender/src/prim_store.rs | 141 +++++++++++++++-------- webrender/src/tiling.rs | 100 ++++++++-------- webrender_api/src/display_list.rs | 9 +- wrench/reftests/text/shadow-complex.yaml | 3 + wrench/reftests/text/shadow-many.yaml | 3 + wrench/reftests/text/shadow-single.yaml | 1 + wrench/reftests/text/shadow.yaml | 1 + wrench/src/yaml_frame_reader.rs | 12 +- 12 files changed, 251 insertions(+), 132 deletions(-) diff --git a/webrender/res/cs_blur.fs.glsl b/webrender/res/cs_blur.fs.glsl index 19dcb15ca4..e26f34233d 100644 --- a/webrender/res/cs_blur.fs.glsl +++ b/webrender/res/cs_blur.fs.glsl @@ -16,6 +16,15 @@ float gauss(float x, float sigma) { void main(void) { vec4 cache_sample = texture(sCacheRGBA8, vUv); + + // TODO(gw): The gauss function gets NaNs when blur radius + // is zero. In the future, detect this earlier + // and skip the blur passes completely. + if (vBlurRadius == 0) { + oFragColor = cache_sample; + return; + } + vec4 color = vec4(cache_sample.rgb, 1.0) * (cache_sample.a * gauss(0.0, vSigma)); for (int i=1 ; i < vBlurRadius ; ++i) { diff --git a/webrender/res/cs_text_run.vs.glsl b/webrender/res/cs_text_run.vs.glsl index 694a5eb031..18a5a5a46c 100644 --- a/webrender/res/cs_text_run.vs.glsl +++ b/webrender/res/cs_text_run.vs.glsl @@ -21,7 +21,7 @@ void main(void) { // the glyph offset, relative to its primitive bounding rect. vec2 size = res.uv_rect.zw - res.uv_rect.xy; vec2 local_pos = glyph.offset + vec2(res.offset.x, -res.offset.y) / uDevicePixelRatio; - vec2 origin = prim.task.screen_space_origin + uDevicePixelRatio * (local_pos - prim.local_rect.p0); + vec2 origin = prim.task.render_target_origin + uDevicePixelRatio * (local_pos - prim.local_rect.p0); vec4 local_rect = vec4(origin, size); vec2 texture_size = vec2(textureSize(sColor0, 0)); diff --git a/webrender/src/frame.rs b/webrender/src/frame.rs index 635bfd50c6..d166099994 100644 --- a/webrender/src/frame.rs +++ b/webrender/src/frame.rs @@ -718,7 +718,9 @@ impl Frame { SpecificDisplayItem::PopStackingContext => unreachable!("Should have returned in parent method."), SpecificDisplayItem::PushTextShadow(shadow) => { - context.builder.push_text_shadow(shadow); + context.builder.push_text_shadow(shadow, + clip_and_scroll, + item.local_clip()); } SpecificDisplayItem::PopTextShadow => { context.builder.pop_text_shadow(); diff --git a/webrender/src/frame_builder.rs b/webrender/src/frame_builder.rs index 05f1a2c28f..7f3f4af2fe 100644 --- a/webrender/src/frame_builder.rs +++ b/webrender/src/frame_builder.rs @@ -17,7 +17,7 @@ use plane_split::{BspSplitter, Polygon, Splitter}; use prim_store::{GradientPrimitiveCpu, ImagePrimitiveCpu}; use prim_store::{ImagePrimitiveKind, PrimitiveContainer, PrimitiveIndex}; use prim_store::{PrimitiveStore, RadialGradientPrimitiveCpu}; -use prim_store::{RectanglePrimitive, TextRunPrimitiveCpu}; +use prim_store::{RectanglePrimitive, TextRunPrimitiveCpu, TextShadowPrimitiveCpu}; use prim_store::{BoxShadowPrimitiveCpu, TexelRect, YuvImagePrimitiveCpu}; use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters}; use render_task::{AlphaRenderItem, ClipWorkItem, MaskCacheKey, RenderTask, RenderTaskIndex}; @@ -106,6 +106,37 @@ pub struct FrameBuilderConfig { pub cache_expiry_frames: u32, } +struct PendingTextShadow { + shadow: TextShadow, + text_primitives: Vec, + clip_and_scroll: ClipAndScrollInfo, + local_rect: LayerRect, + local_clip: LocalClip, +} + +impl PendingTextShadow { + fn new(shadow: TextShadow, + clip_and_scroll: ClipAndScrollInfo, + local_clip: &LocalClip) -> PendingTextShadow { + PendingTextShadow { + shadow: shadow, + text_primitives: Vec::new(), + clip_and_scroll: clip_and_scroll, + local_clip: local_clip.clone(), + local_rect: LayerRect::zero(), + } + } + + fn push(&mut self, + local_rect: LayerRect, + primitive: &TextRunPrimitiveCpu) { + self.text_primitives.push(primitive.clone()); + let shadow_rect = local_rect.inflate(self.shadow.blur_radius, + self.shadow.blur_radius); + self.local_rect = self.local_rect.union(&shadow_rect); + } +} + pub struct FrameBuilder { screen_size: DeviceUintSize, background_color: Option, @@ -116,7 +147,7 @@ pub struct FrameBuilder { stacking_context_store: Vec, clip_scroll_group_store: Vec, packed_layers: Vec, - text_shadows: Vec, + pending_text_shadows: Vec, scrollbar_prims: Vec, @@ -145,7 +176,7 @@ impl FrameBuilder { clip_scroll_group_store: recycle_vec(prev.clip_scroll_group_store), cmds: recycle_vec(prev.cmds), packed_layers: recycle_vec(prev.packed_layers), - text_shadows: recycle_vec(prev.text_shadows), + pending_text_shadows: recycle_vec(prev.pending_text_shadows), scrollbar_prims: recycle_vec(prev.scrollbar_prims), reference_frame_stack: recycle_vec(prev.reference_frame_stack), stacking_context_stack: recycle_vec(prev.stacking_context_stack), @@ -162,7 +193,7 @@ impl FrameBuilder { clip_scroll_group_store: Vec::new(), cmds: Vec::new(), packed_layers: Vec::new(), - text_shadows: Vec::new(), + pending_text_shadows: Vec::new(), scrollbar_prims: Vec::new(), reference_frame_stack: Vec::new(), stacking_context_stack: Vec::new(), @@ -290,7 +321,7 @@ impl FrameBuilder { pub fn pop_stacking_context(&mut self) { self.cmds.push(PrimitiveRunCmd::PopStackingContext); self.stacking_context_stack.pop(); - assert!(self.text_shadows.is_empty(), + assert!(self.pending_text_shadows.is_empty(), "Found unpopped text shadows when popping stacking context!"); } @@ -397,12 +428,35 @@ impl FrameBuilder { self.reference_frame_stack.pop(); } - pub fn push_text_shadow(&mut self, shadow: TextShadow) { - self.text_shadows.push(shadow); + pub fn push_text_shadow(&mut self, + shadow: TextShadow, + clip_and_scroll: ClipAndScrollInfo, + local_clip: &LocalClip) { + let text_shadow = PendingTextShadow::new(shadow, + clip_and_scroll, + local_clip); + self.pending_text_shadows.push(text_shadow); } pub fn pop_text_shadow(&mut self) { - self.text_shadows.pop().expect("Too many PopTextShadows?"); + let mut text_shadow = self.pending_text_shadows + .pop() + .expect("Too many PopTextShadows?"); + if !text_shadow.text_primitives.is_empty() { + let prim_cpu = TextShadowPrimitiveCpu { + text_primitives: text_shadow.text_primitives, + shadow: text_shadow.shadow, + }; + + text_shadow.local_rect = text_shadow.local_rect + .translate(&text_shadow.shadow.offset); + + self.add_primitive(text_shadow.clip_and_scroll, + &text_shadow.local_rect, + &text_shadow.local_clip, + &[], + PrimitiveContainer::TextShadow(prim_cpu)); + } } pub fn add_solid_rectangle(&mut self, @@ -749,7 +803,9 @@ impl FrameBuilder { glyph_range: ItemRange, glyph_count: usize, glyph_options: Option) { - if color.a == 0.0 { + let is_text_shadow = !self.pending_text_shadows.is_empty(); + + if color.a == 0.0 && !is_text_shadow { return } @@ -757,11 +813,6 @@ impl FrameBuilder { return } - let mut blur_radius = 0.0; // TODO: remove this in favour of PushTextShadow handling - - // Expand the rectangle of the text run by the blur radius. - let rect = rect.inflate(blur_radius, blur_radius); - // TODO(gw): Use a proper algorithm to select // whether this item should be rendered with // subpixel AA! @@ -771,7 +822,7 @@ impl FrameBuilder { // subpixel text rendering, even if enabled. if render_mode == FontRenderMode::Subpixel { // text-blur shadow needs to force alpha AA. - if blur_radius != 0.0 { + if is_text_shadow { render_mode = FontRenderMode::Alpha; } @@ -795,7 +846,6 @@ impl FrameBuilder { let prim_cpu = TextRunPrimitiveCpu { font_key, logical_font_size: size, - blur_radius, glyph_range, glyph_count, glyph_instances: Vec::new(), @@ -804,11 +854,17 @@ impl FrameBuilder { glyph_options, }; - self.add_primitive(clip_and_scroll, - &rect, - local_clip, - &[], - PrimitiveContainer::TextRun(prim_cpu)); + if is_text_shadow { + for shadow in &mut self.pending_text_shadows { + shadow.push(rect, &prim_cpu); + } + } else { + self.add_primitive(clip_and_scroll, + &rect, + local_clip, + &[], + PrimitiveContainer::TextRun(prim_cpu)); + } } pub fn fill_box_shadow_rect(&mut self, diff --git a/webrender/src/prim_store.rs b/webrender/src/prim_store.rs index 64e2f764a8..acbe38a57e 100644 --- a/webrender/src/prim_store.rs +++ b/webrender/src/prim_store.rs @@ -4,7 +4,7 @@ use api::{BuiltDisplayList, ColorF, ComplexClipRegion, DeviceIntRect, DeviceIntSize, DevicePoint}; use api::{ExtendMode, FontKey, FontRenderMode, GlyphInstance, GlyphOptions, GradientStop}; -use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect, LayerSize}; +use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect, LayerSize, TextShadow}; use api::{LayerToWorldTransform, TileOffset, WebGLContextId, YuvColorSpace, YuvFormat}; use api::device_length; use app_units::Au; @@ -115,6 +115,7 @@ pub enum PrimitiveKind { AngleGradient, RadialGradient, BoxShadow, + TextShadow, } #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] @@ -479,11 +480,16 @@ impl RadialGradientPrimitiveCpu { } } +#[derive(Debug, Clone)] +pub struct TextShadowPrimitiveCpu { + pub text_primitives: Vec, + pub shadow: TextShadow, +} + #[derive(Debug, Clone)] pub struct TextRunPrimitiveCpu { pub font_key: FontKey, pub logical_font_size: Au, - pub blur_radius: f32, pub glyph_range: ItemRange, pub glyph_count: usize, // TODO(gw): Maybe make this an Arc for sharing with resource cache @@ -493,10 +499,36 @@ pub struct TextRunPrimitiveCpu { pub glyph_options: Option, } -impl ToGpuBlocks for TextRunPrimitiveCpu { - fn write_gpu_blocks(&self, mut request: GpuDataRequest) { - request.push(self.color); +impl TextRunPrimitiveCpu { + fn prepare_for_render(&mut self, + resource_cache: &mut ResourceCache, + device_pixel_ratio: f32, + display_list: &BuiltDisplayList) { + // Cache the glyph positions, if not in the cache already. + // TODO(gw): In the future, remove `glyph_instances` + // completely, and just reference the glyphs + // directly from the displaty list. + if self.glyph_instances.is_empty() { + let src_glyphs = display_list.get(self.glyph_range); + for src in src_glyphs { + self.glyph_instances.push(GlyphInstance { + index: src.index, + point: src.point, + }); + } + } + let font_size_dp = self.logical_font_size.scale_by(device_pixel_ratio); + + resource_cache.request_glyphs(self.font_key, + font_size_dp, + self.color, + &self.glyph_instances, + self.render_mode, + self.glyph_options); + } + + fn write_gpu_blocks(&self, request: &mut GpuDataRequest) { // Two glyphs are packed per GPU block. for glyph_chunk in self.glyph_instances.chunks(2) { // In the case of an odd number of glyphs, the @@ -679,6 +711,7 @@ pub enum PrimitiveContainer { AngleGradient(GradientPrimitiveCpu), RadialGradient(RadialGradientPrimitiveCpu), BoxShadow(BoxShadowPrimitiveCpu), + TextShadow(TextShadowPrimitiveCpu), } pub struct PrimitiveStore { @@ -686,6 +719,7 @@ pub struct PrimitiveStore { pub cpu_bounding_rects: Vec>, pub cpu_rectangles: Vec, pub cpu_text_runs: Vec, + pub cpu_text_shadows: Vec, pub cpu_images: Vec, pub cpu_yuv_images: Vec, pub cpu_gradients: Vec, @@ -702,6 +736,7 @@ impl PrimitiveStore { cpu_rectangles: Vec::new(), cpu_bounding_rects: Vec::new(), cpu_text_runs: Vec::new(), + cpu_text_shadows: Vec::new(), cpu_images: Vec::new(), cpu_yuv_images: Vec::new(), cpu_gradients: Vec::new(), @@ -717,6 +752,7 @@ impl PrimitiveStore { cpu_rectangles: recycle_vec(self.cpu_rectangles), cpu_bounding_rects: recycle_vec(self.cpu_bounding_rects), cpu_text_runs: recycle_vec(self.cpu_text_runs), + cpu_text_shadows: recycle_vec(self.cpu_text_shadows), cpu_images: recycle_vec(self.cpu_images), cpu_yuv_images: recycle_vec(self.cpu_yuv_images), cpu_gradients: recycle_vec(self.cpu_gradients), @@ -771,6 +807,23 @@ impl PrimitiveStore { self.cpu_text_runs.push(text_cpu); metadata } + PrimitiveContainer::TextShadow(text_shadow) => { + let metadata = PrimitiveMetadata { + opacity: PrimitiveOpacity::translucent(), + clips, + clip_cache_info: clip_info, + prim_kind: PrimitiveKind::TextShadow, + cpu_prim_index: SpecificPrimitiveIndex(self.cpu_text_shadows.len()), + gpu_location: GpuCacheHandle::new(), + render_task: None, + clip_task: None, + local_rect: *local_rect, + local_clip_rect: *local_clip_rect, + }; + + self.cpu_text_shadows.push(text_shadow); + metadata + } PrimitiveContainer::Image(image_cpu) => { let metadata = PrimitiveMetadata { opacity: PrimitiveOpacity::translucent(), @@ -997,50 +1050,34 @@ impl PrimitiveStore { let location = RenderTaskLocation::Dynamic(None, cache_size); metadata.render_task.as_mut().unwrap().location = location; } - PrimitiveKind::TextRun => { - let text = &mut self.cpu_text_runs[metadata.cpu_prim_index.0]; - - let font_size_dp = text.logical_font_size.scale_by(device_pixel_ratio); - let src_glyphs = display_list.get(text.glyph_range); - - // Cache the glyph positions, if not in the cache already. - // TODO(gw): In the future, remove `glyph_instances` - // completely, and just reference the glyphs - // directly from the displaty list. - if text.glyph_instances.is_empty() { - for src in src_glyphs { - text.glyph_instances.push(GlyphInstance { - index: src.index, - point: src.point, - }); - } + PrimitiveKind::TextShadow => { + let shadow = &mut self.cpu_text_shadows[metadata.cpu_prim_index.0]; + for text in &mut shadow.text_primitives { + text.prepare_for_render(resource_cache, + device_pixel_ratio, + display_list); } - metadata.render_task = if text.blur_radius == 0.0 { - None - } else { - // This is a text-shadow element. Create a render task that will - // render the text run to a target, and then apply a gaussian - // blur to that text run in order to build the actual primitive - // which will be blitted to the framebuffer. - let cache_width = (metadata.local_rect.size.width * device_pixel_ratio).ceil() as i32; - let cache_height = (metadata.local_rect.size.height * device_pixel_ratio).ceil() as i32; - let cache_size = DeviceIntSize::new(cache_width, cache_height); - let cache_key = PrimitiveCacheKey::TextShadow(prim_index); - let blur_radius = device_length(text.blur_radius, - device_pixel_ratio); - Some(RenderTask::new_blur(cache_key, - cache_size, - blur_radius, - prim_index)) - }; - - resource_cache.request_glyphs(text.font_key, - font_size_dp, - text.color, - &text.glyph_instances, - text.render_mode, - text.glyph_options); + // This is a text-shadow element. Create a render task that will + // render the text run to a target, and then apply a gaussian + // blur to that text run in order to build the actual primitive + // which will be blitted to the framebuffer. + let cache_width = (metadata.local_rect.size.width * device_pixel_ratio).ceil() as i32; + let cache_height = (metadata.local_rect.size.height * device_pixel_ratio).ceil() as i32; + let cache_size = DeviceIntSize::new(cache_width, cache_height); + let cache_key = PrimitiveCacheKey::TextShadow(prim_index); + let blur_radius = device_length(shadow.shadow.blur_radius, + device_pixel_ratio); + metadata.render_task = Some(RenderTask::new_blur(cache_key, + cache_size, + blur_radius, + prim_index)); + } + PrimitiveKind::TextRun => { + let text = &mut self.cpu_text_runs[metadata.cpu_prim_index.0]; + text.prepare_for_render(resource_cache, + device_pixel_ratio, + display_list); } PrimitiveKind::Image => { let image_cpu = &mut self.cpu_images[metadata.cpu_prim_index.0]; @@ -1118,7 +1155,15 @@ impl PrimitiveStore { } PrimitiveKind::TextRun => { let text = &self.cpu_text_runs[metadata.cpu_prim_index.0]; - text.write_gpu_blocks(request); + request.push(text.color); + text.write_gpu_blocks(&mut request); + } + PrimitiveKind::TextShadow => { + let prim = &self.cpu_text_shadows[metadata.cpu_prim_index.0]; + request.push(prim.shadow.color); + for text in &prim.text_primitives { + text.write_gpu_blocks(&mut request); + } } } } diff --git a/webrender/src/tiling.rs b/webrender/src/tiling.rs index d78bb21837..eca3fd255c 100644 --- a/webrender/src/tiling.rs +++ b/webrender/src/tiling.rs @@ -50,14 +50,9 @@ impl AlphaBatchHelpers for PrimitiveStore { match metadata.prim_kind { PrimitiveKind::TextRun => { let text_run_cpu = &self.cpu_text_runs[metadata.cpu_prim_index.0]; - if text_run_cpu.blur_radius == 0.0 { - match text_run_cpu.render_mode { - FontRenderMode::Subpixel => BlendMode::Subpixel(text_run_cpu.color), - FontRenderMode::Alpha | FontRenderMode::Mono => BlendMode::Alpha, - } - } else { - // Text runs drawn to blur never get drawn with subpixel AA. - BlendMode::Alpha + match text_run_cpu.render_mode { + FontRenderMode::Subpixel => BlendMode::Subpixel(text_run_cpu.color), + FontRenderMode::Alpha | FontRenderMode::Mono => BlendMode::Alpha, } } PrimitiveKind::Image | @@ -483,26 +478,8 @@ impl AlphaRenderItem { } PrimitiveKind::TextRun => { let text_cpu = &ctx.prim_store.cpu_text_runs[prim_metadata.cpu_prim_index.0]; - let batch_kind = if text_cpu.blur_radius == 0.0 { - AlphaBatchKind::TextRun - } else { - // Select a generic primitive shader that can blit the - // results of the cached text blur to the framebuffer, - // applying tile clipping etc. - AlphaBatchKind::CacheImage - }; - let font_size_dp = text_cpu.logical_font_size.scale_by(ctx.device_pixel_ratio); - let cache_task_index = match prim_metadata.render_task { - Some(ref task) => { - let cache_task_id = task.id; - render_tasks.get_task_index(&cache_task_id, - child_pass_index).0 as i32 - } - None => 0, - }; - // TODO(gw): avoid / recycle this allocation in the future. let mut instances = Vec::new(); @@ -513,7 +490,7 @@ impl AlphaRenderItem { text_cpu.render_mode, text_cpu.glyph_options, |index, handle| { let uv_address = handle.as_int(gpu_cache); - instances.push(base_instance.build(index as i32, cache_task_index, uv_address)); + instances.push(base_instance.build(index as i32, 0, uv_address)); }); if texture_id != SourceTexture::Invalid { @@ -521,12 +498,20 @@ impl AlphaRenderItem { colors: [texture_id, SourceTexture::Invalid, SourceTexture::Invalid], }; - let key = AlphaBatchKey::new(batch_kind, flags, blend_mode, textures); + let key = AlphaBatchKey::new(AlphaBatchKind::TextRun, flags, blend_mode, textures); let batch = batch_list.get_suitable_batch(&key, item_bounding_rect); batch.add_instances(&instances); } } + PrimitiveKind::TextShadow => { + let cache_task_id = prim_metadata.render_task.as_ref().expect("no render task!").id; + let cache_task_index = render_tasks.get_task_index(&cache_task_id, + child_pass_index); + let key = AlphaBatchKey::new(AlphaBatchKind::CacheImage, flags, blend_mode, no_textures); + let batch = batch_list.get_suitable_batch(&key, item_bounding_rect); + batch.add_instance(base_instance.build(0, cache_task_index.0 as i32, 0)); + } PrimitiveKind::AlignedGradient => { let gradient_cpu = &ctx.prim_store.cpu_gradients[prim_metadata.cpu_prim_index.0]; let key = AlphaBatchKey::new(AlphaBatchKind::AlignedGradient, flags, blend_mode, no_textures); @@ -1043,43 +1028,48 @@ impl RenderTarget for ColorRenderTarget { 0); // z is disabled for rendering cache primitives self.box_shadow_cache_prims.push(instance.build(0, 0, 0)); } - PrimitiveKind::TextRun => { - let text = &ctx.prim_store.cpu_text_runs[prim_metadata.cpu_prim_index.0]; - // We only cache text runs with a text-shadow (for now). - debug_assert!(text.blur_radius != 0.0); + PrimitiveKind::TextShadow => { + let prim = &ctx.prim_store.cpu_text_shadows[prim_metadata.cpu_prim_index.0]; // todo(gw): avoid / recycle this allocation... let mut instances = Vec::new(); + let mut base_index = 0; - let font_size_dp = text.logical_font_size.scale_by(ctx.device_pixel_ratio); + let task_index = render_tasks.get_task_index(&task.id, pass_index); let instance = SimplePrimitiveInstance::new(prim_address, - render_tasks.get_task_index(&task.id, pass_index), + task_index, RenderTaskIndex(0), PackedLayerIndex(0), 0); // z is disabled for rendering cache primitives - let texture_id = ctx.resource_cache.get_glyphs(text.font_key, - font_size_dp, - text.color, - &text.glyph_instances, - text.render_mode, - text.glyph_options, |index, handle| { - let uv_address = handle.as_int(gpu_cache); - instances.push(instance.build(index as i32, 0, uv_address)); - }); - - if texture_id != SourceTexture::Invalid { - let textures = BatchTextures { - colors: [texture_id, SourceTexture::Invalid, SourceTexture::Invalid], - }; - - self.text_run_cache_prims.extend_from_slice(&instances); - - debug_assert!(textures.colors[0] != SourceTexture::Invalid); - debug_assert!(self.text_run_textures.colors[0] == SourceTexture::Invalid || - self.text_run_textures.colors[0] == textures.colors[0]); - self.text_run_textures = textures; + for text in &prim.text_primitives { + let font_size_dp = text.logical_font_size.scale_by(ctx.device_pixel_ratio); + + let texture_id = ctx.resource_cache.get_glyphs(text.font_key, + font_size_dp, + text.color, + &text.glyph_instances, + text.render_mode, + text.glyph_options, |index, handle| { + let uv_address = handle.as_int(gpu_cache); + instances.push(instance.build(base_index + index as i32, 0, uv_address)); + }); + + if texture_id != SourceTexture::Invalid { + let textures = BatchTextures { + colors: [texture_id, SourceTexture::Invalid, SourceTexture::Invalid], + }; + + self.text_run_cache_prims.extend_from_slice(&instances); + base_index += text.glyph_instances.len() as i32; + instances.clear(); + + debug_assert!(textures.colors[0] != SourceTexture::Invalid); + debug_assert!(self.text_run_textures.colors[0] == SourceTexture::Invalid || + self.text_run_textures.colors[0] == textures.colors[0]); + self.text_run_textures = textures; + } } } _ => { diff --git a/webrender_api/src/display_list.rs b/webrender_api/src/display_list.rs index 5f919f512e..b70dd71e20 100644 --- a/webrender_api/src/display_list.rs +++ b/webrender_api/src/display_list.rs @@ -912,8 +912,13 @@ impl DisplayListBuilder { self.push_new_empty_item(SpecificDisplayItem::PopNestedDisplayList); } - pub fn push_text_shadow(&mut self, shadow: TextShadow) { - self.push_new_empty_item(SpecificDisplayItem::PushTextShadow(shadow)); + pub fn push_text_shadow(&mut self, + rect: LayoutRect, + local_clip: Option, + shadow: TextShadow) { + self.push_item(SpecificDisplayItem::PushTextShadow(shadow), + rect, + local_clip); } pub fn pop_text_shadow(&mut self) { diff --git a/wrench/reftests/text/shadow-complex.yaml b/wrench/reftests/text/shadow-complex.yaml index 795656d20d..99a3df24e5 100644 --- a/wrench/reftests/text/shadow-complex.yaml +++ b/wrench/reftests/text/shadow-complex.yaml @@ -3,11 +3,13 @@ root: items: - type: "text-shadow" + bounds: [14, 18, 205, 35] blur-radius: 5 offset: [0, 0] color: black - type: "text-shadow" + bounds: [14, 18, 205, 35] blur-radius: 0 offset: [2, 3] color: red @@ -22,6 +24,7 @@ root: type: "pop-text-shadow" - type: "text-shadow" + bounds: [14, 18, 205, 35] blur-radius: 3 offset: [-2, 3.5] color: blue diff --git a/wrench/reftests/text/shadow-many.yaml b/wrench/reftests/text/shadow-many.yaml index e795f7ff39..6c715cd97d 100644 --- a/wrench/reftests/text/shadow-many.yaml +++ b/wrench/reftests/text/shadow-many.yaml @@ -3,16 +3,19 @@ root: items: - type: "text-shadow" + bounds: [14, 18, 205, 35] blur-radius: 5 offset: [0, 0] color: black - type: "text-shadow" + bounds: [14, 18, 205, 35] blur-radius: 0 offset: [2, 3] color: red - type: "text-shadow" + bounds: [14, 18, 205, 35] blur-radius: 3 offset: [-2, 3.5] color: blue diff --git a/wrench/reftests/text/shadow-single.yaml b/wrench/reftests/text/shadow-single.yaml index 466db980f1..dd1126a207 100644 --- a/wrench/reftests/text/shadow-single.yaml +++ b/wrench/reftests/text/shadow-single.yaml @@ -4,6 +4,7 @@ root: - type: "text-shadow" blur-radius: 5 + bounds: [14, 18, 205, 35] offset: [0, 0] color: black - diff --git a/wrench/reftests/text/shadow.yaml b/wrench/reftests/text/shadow.yaml index 905fd1e09c..8d4f48f261 100644 --- a/wrench/reftests/text/shadow.yaml +++ b/wrench/reftests/text/shadow.yaml @@ -3,6 +3,7 @@ root: items: - type: "text-shadow" + bounds: [14, 18, 205, 35] blur-radius: 5 offset: [0, 0] color: black diff --git a/wrench/src/yaml_frame_reader.rs b/wrench/src/yaml_frame_reader.rs index 052dbf870a..990898fe61 100644 --- a/wrench/src/yaml_frame_reader.rs +++ b/wrench/src/yaml_frame_reader.rs @@ -610,13 +610,17 @@ impl YamlFrameReader { } pub fn handle_push_text_shadow(&mut self, yaml: &Yaml) { + let rect = yaml["bounds"].as_rect() + .expect("Text shadows require bounds"); let blur_radius = yaml["blur-radius"].as_f32().unwrap_or(0.0); - let offset = yaml["blur-radius"].as_vector().unwrap_or(LayoutVector2D::zero()); + let offset = yaml["offset"].as_vector().unwrap_or(LayoutVector2D::zero()); let color = yaml["color"].as_colorf().unwrap_or(*BLACK_COLOR); - self.builder().push_text_shadow(TextShadow { - blur_radius, offset, color - }); + self.builder().push_text_shadow(rect, + None, + TextShadow { + blur_radius, offset, color + }); } pub fn handle_pop_text_shadow(&mut self) {