diff --git a/webrender/res/cs_text_run.vs.glsl b/webrender/res/cs_text_run.vs.glsl index c6a56ed970..a0bdd47b64 100644 --- a/webrender/res/cs_text_run.vs.glsl +++ b/webrender/res/cs_text_run.vs.glsl @@ -13,6 +13,17 @@ void main(void) { int glyph_index = prim.user_data0; int resource_address = prim.user_data1; + int text_shadow_address = prim.user_data2; + + // Fetch the parent text-shadow for this primitive. This allows the code + // below to normalize the glyph offsets relative to the original text + // shadow rect, which is the union of all elements that make up this + // text shadow. This allows the text shadow to be rendered at an + // arbitrary location in a render target (provided by the render + // task render_target_origin field). + PrimitiveGeometry shadow_geom = fetch_primitive_geometry(text_shadow_address); + TextShadow shadow = fetch_text_shadow(text_shadow_address + VECS_PER_PRIM_HEADER); + Glyph glyph = fetch_glyph(prim.specific_prim_address, glyph_index); GlyphResource res = fetch_glyph_resource(resource_address); @@ -22,7 +33,7 @@ void main(void) { 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.render_target_origin + - uDevicePixelRatio * (text.offset + local_pos); + uDevicePixelRatio * (local_pos + shadow.offset - shadow_geom.local_rect.p0); vec4 local_rect = vec4(origin, size); vec2 texture_size = vec2(textureSize(sColor0, 0)); @@ -34,7 +45,7 @@ void main(void) { aPosition.xy); vUv = mix(st0, st1, aPosition.xy); - vColor = text.color; + vColor = shadow.color; gl_Position = uTransform * vec4(pos, 0.0, 1.0); } diff --git a/webrender/res/prim_shared.glsl b/webrender/res/prim_shared.glsl index 36e971a717..364bac7971 100644 --- a/webrender/res/prim_shared.glsl +++ b/webrender/res/prim_shared.glsl @@ -494,6 +494,17 @@ struct Primitive { float z; }; +struct PrimitiveGeometry { + RectWithSize local_rect; + RectWithSize local_clip_rect; +}; + +PrimitiveGeometry fetch_primitive_geometry(int address) { + vec4 geom[2] = fetch_from_resource_cache_2(address); + return PrimitiveGeometry(RectWithSize(geom[0].xy, geom[0].zw), + RectWithSize(geom[1].xy, geom[1].zw)); +} + Primitive load_primitive() { PrimitiveInstance pi = fetch_prim_instance(); @@ -503,9 +514,9 @@ Primitive load_primitive() { prim.clip_area = fetch_clip_area(pi.clip_task_index); prim.task = fetch_alpha_batch_task(pi.render_task_index); - vec4 geom[2] = fetch_from_resource_cache_2(pi.prim_address); - prim.local_rect = RectWithSize(geom[0].xy, geom[0].zw); - prim.local_clip_rect = RectWithSize(geom[1].xy, geom[1].zw); + PrimitiveGeometry geom = fetch_primitive_geometry(pi.prim_address); + prim.local_rect = geom.local_rect; + prim.local_clip_rect = geom.local_clip_rect; prim.specific_prim_address = pi.specific_prim_address; prim.user_data0 = pi.user_data0; @@ -761,6 +772,17 @@ Rectangle fetch_rectangle(int address) { return Rectangle(data); } +struct TextShadow { + vec4 color; + vec2 offset; + float blur_radius; +}; + +TextShadow fetch_text_shadow(int address) { + vec4 data[2] = fetch_from_resource_cache_2(address); + return TextShadow(data[0], data[1].xy, data[1].z); +} + struct TextRun { vec4 color; vec2 offset; diff --git a/webrender/res/ps_text_run.vs.glsl b/webrender/res/ps_text_run.vs.glsl index 956fc1f4e9..e7c6e81f87 100644 --- a/webrender/res/ps_text_run.vs.glsl +++ b/webrender/res/ps_text_run.vs.glsl @@ -3,15 +3,33 @@ * 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 RENDER_MODE_MONO 0 +#define RENDER_MODE_ALPHA 1 +#define RENDER_MODE_SUBPIXEL 2 + void main(void) { Primitive prim = load_primitive(); TextRun text = fetch_text_run(prim.specific_prim_address); int glyph_index = prim.user_data0; + int render_mode = prim.user_data1; int resource_address = prim.user_data2; + Glyph glyph = fetch_glyph(prim.specific_prim_address, glyph_index); GlyphResource res = fetch_glyph_resource(resource_address); + switch (render_mode) { + case RENDER_MODE_ALPHA: + break; + case RENDER_MODE_MONO: + break; + case RENDER_MODE_SUBPIXEL: + // In subpixel mode, the subpixel offset has already been + // accounted for while rasterizing the glyph. + glyph.offset = trunc(glyph.offset); + break; + } + vec2 local_pos = glyph.offset + text.offset + vec2(res.offset.x, -res.offset.y) / uDevicePixelRatio; diff --git a/webrender/src/frame_builder.rs b/webrender/src/frame_builder.rs index bc857d6c17..7500470e2e 100644 --- a/webrender/src/frame_builder.rs +++ b/webrender/src/frame_builder.rs @@ -14,9 +14,9 @@ use gpu_cache::GpuCache; use internal_types::HardwareCompositeOp; use mask_cache::{ClipMode, ClipRegion, ClipSource, MaskCacheInfo}; use plane_split::{BspSplitter, Polygon, Splitter}; -use prim_store::{GradientPrimitiveCpu, ImagePrimitiveCpu}; +use prim_store::{GradientPrimitiveCpu, ImagePrimitiveCpu, PrimitiveKind}; use prim_store::{ImagePrimitiveKind, PrimitiveContainer, PrimitiveIndex}; -use prim_store::{PrimitiveStore, RadialGradientPrimitiveCpu, TextDecoration, TextRun}; +use prim_store::{PrimitiveStore, RadialGradientPrimitiveCpu, TextRunMode}; use prim_store::{RectanglePrimitive, TextRunPrimitiveCpu, TextShadowPrimitiveCpu}; use prim_store::{BoxShadowPrimitiveCpu, TexelRect, YuvImagePrimitiveCpu}; use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters}; @@ -106,185 +106,6 @@ pub struct FrameBuilderConfig { pub cache_expiry_frames: u32, } -// A normal text run that was added while adding a text-shadow. -// This is a text run added inside a push/pop text-shadow -// context where the alpha of the text run is > 0. -// We need to store them in a pending list so that we -// can add them after adding the popped text shadows, -// to maintain correct paint order. -struct PendingTextRun { - prim: TextRunPrimitiveCpu, - local_rect: LayerRect, - clip_and_scroll: ClipAndScrollInfo, - local_clip: LocalClip, -} - -struct PendingTextDecoration { - prim: RectanglePrimitive, - local_rect: LayerRect, - clip_and_scroll: ClipAndScrollInfo, - local_clip: LocalClip, -} - -// A pending text shadow. Contains the details of the -// shadow itself, plus a list of text runs that -// make up this shadow. -struct PendingTextShadow { - shadow: TextShadow, - clip_and_scroll: ClipAndScrollInfo, - local_rect: LayerRect, - local_clip: LocalClip, - runs: Vec, - decorations: Vec, -} - -impl PendingTextShadow { - fn push_text(&mut self, - run: &TextRun, - local_rect: &LayerRect) { - let mut run = run.clone(); - - if self.shadow.blur_radius == 0.0 { - // This text can be pushed through a fast path. Set the offset - // of the run to the shadow offset, to move the normal text - // run to the correct location. - run.offset = self.shadow.offset; - } else { - // Blur filter needs to render without subpixel AA. - if run.render_mode == FontRenderMode::Subpixel { - run.render_mode = FontRenderMode::Alpha; - } - - // The blur shaders expect the source items to be placed in the - // center of the target (so offset by the blur radius). Since - // we are rendering glyphs into a target, we need to subtract the - // origin of their local bounding rect, to give a normalized - // glyph position. - let offset = LayerVector2D::new(self.shadow.blur_radius - local_rect.origin.x, - self.shadow.blur_radius - local_rect.origin.y); - run.offset = offset; - } - - // The combined local rect of the text-shadow primitive, is - // the union of all text runs, expanded by the blur radius - // for each run. - let shadow_rect = local_rect.inflate(self.shadow.blur_radius, - self.shadow.blur_radius); - self.local_rect = self.local_rect.union(&shadow_rect); - - self.runs.push(run); - } - - fn push_decoration(&mut self, - local_rect: &LayerRect) { - self.local_rect = self.local_rect.union(local_rect); - - self.decorations.push(TextDecoration { - local_rect: *local_rect, - prim: RectanglePrimitive { - color: self.shadow.color, - }, - }); - } -} - -// Maintains state when processing a text-shadow -// stack, via push_text_shadow() and pop_text_shadow(). -struct TextShadowBuilder { - pending_shadows: Vec, - pending_texts: Vec, - pending_decorations: Vec, -} - -impl TextShadowBuilder { - fn new() -> TextShadowBuilder { - TextShadowBuilder { - pending_shadows: Vec::new(), - pending_texts: Vec::new(), - pending_decorations: Vec::new(), - } - } - - fn recycle(self) -> TextShadowBuilder { - TextShadowBuilder { - pending_shadows: recycle_vec(self.pending_shadows), - pending_texts: recycle_vec(self.pending_texts), - pending_decorations: recycle_vec(self.pending_decorations), - } - } - - fn has_shadows(&self) -> bool { - !self.pending_shadows.is_empty() - } - - // Add a new shadow to the list. - fn push_shadow(&mut self, - shadow: TextShadow, - clip_and_scroll: ClipAndScrollInfo, - local_clip: LocalClip) { - self.pending_shadows.push(PendingTextShadow { - runs: Vec::new(), - decorations: Vec::new(), - shadow, - local_rect: LayerRect::zero(), - clip_and_scroll, - local_clip, - }); - } - - // Add a text run inside a push/pop text shadow stack. - fn push_text(&mut self, - prim: TextRunPrimitiveCpu, - local_rect: LayerRect, - local_clip: LocalClip, - clip_and_scroll: ClipAndScrollInfo) { - debug_assert!(self.has_shadows()); - - // Add the text run to each shadow in the list. - // TODO(gw): In the future we may be able to optimize - // this to share glyph resources better. - for pending_shadow in &mut self.pending_shadows { - pending_shadow.push_text(&prim.run, &local_rect); - } - - // If the color of the text run is not transparent, - // then also render it as a normal visual text run. - // Store in pending list so that it gets drawn - // *after* any text shadows are flushed. - if prim.color.a > 0.0 { - self.pending_texts.push(PendingTextRun { - prim, - local_rect, - clip_and_scroll, - local_clip, - }); - } - } - - fn push_decoration(&mut self, - local_rect: &LayerRect, - color: &ColorF, - local_clip: &LocalClip, - clip_and_scroll: ClipAndScrollInfo) { - debug_assert!(self.has_shadows()); - - for pending_shadow in &mut self.pending_shadows { - pending_shadow.push_decoration(local_rect); - } - - if color.a > 0.0 { - self.pending_decorations.push(PendingTextDecoration { - prim: RectanglePrimitive { - color: *color, - }, - local_rect: *local_rect, - clip_and_scroll, - local_clip: *local_clip, - }); - } - } -} - pub struct FrameBuilder { screen_size: DeviceUintSize, background_color: Option, @@ -295,7 +116,9 @@ pub struct FrameBuilder { stacking_context_store: Vec, clip_scroll_group_store: Vec, packed_layers: Vec, - text_shadow_builder: TextShadowBuilder, + + // A stack of the current text-shadow primitives. + shadow_prim_stack: Vec, scrollbar_prims: Vec, @@ -324,7 +147,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_shadow_builder: prev.text_shadow_builder.recycle(), + shadow_prim_stack: recycle_vec(prev.shadow_prim_stack), 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), @@ -341,7 +164,7 @@ impl FrameBuilder { clip_scroll_group_store: Vec::new(), cmds: Vec::new(), packed_layers: Vec::new(), - text_shadow_builder: TextShadowBuilder::new(), + shadow_prim_stack: Vec::new(), scrollbar_prims: Vec::new(), reference_frame_stack: Vec::new(), stacking_context_stack: Vec::new(), @@ -367,13 +190,15 @@ impl FrameBuilder { stacking_context.clip_scroll_groups.push(group_index); } - pub fn add_primitive(&mut self, - clip_and_scroll: ClipAndScrollInfo, - rect: &LayerRect, - local_clip: &LocalClip, - extra_clips: &[ClipSource], - container: PrimitiveContainer) - -> PrimitiveIndex { + /// 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. + fn create_primitive(&mut self, + clip_and_scroll: ClipAndScrollInfo, + rect: &LayerRect, + local_clip: &LocalClip, + extra_clips: &[ClipSource], + container: PrimitiveContainer) -> PrimitiveIndex { let stacking_context_index = *self.stacking_context_stack.last().unwrap(); self.create_clip_scroll_group_if_necessary(stacking_context_index, clip_and_scroll); @@ -395,19 +220,44 @@ impl FrameBuilder { clip_info, container); + prim_index + } + + /// Add an already created primitive to the draw lists. + pub fn add_primitive_to_draw_list(&mut self, + prim_index: PrimitiveIndex, + clip_and_scroll: ClipAndScrollInfo) { match self.cmds.last_mut().unwrap() { - &mut PrimitiveRunCmd::PrimitiveRun(_run_prim_index, ref mut count, run_clip_and_scroll) - if run_clip_and_scroll == clip_and_scroll => { - debug_assert!(_run_prim_index.0 + *count == prim_index.0); + &mut PrimitiveRunCmd::PrimitiveRun(run_prim_index, ref mut count, run_clip_and_scroll) => { + if run_clip_and_scroll == clip_and_scroll && + run_prim_index.0 + *count == prim_index.0 { *count += 1; - return prim_index; + return; + } } - &mut PrimitiveRunCmd::PrimitiveRun(..) | &mut PrimitiveRunCmd::PushStackingContext(..) | &mut PrimitiveRunCmd::PopStackingContext => {} } self.cmds.push(PrimitiveRunCmd::PrimitiveRun(prim_index, 1, 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: ClipAndScrollInfo, + rect: &LayerRect, + local_clip: &LocalClip, + extra_clips: &[ClipSource], + container: PrimitiveContainer) -> PrimitiveIndex { + let prim_index = self.create_primitive(clip_and_scroll, + rect, + local_clip, + extra_clips, + container); + + self.add_primitive_to_draw_list(prim_index, clip_and_scroll); + prim_index } @@ -469,7 +319,7 @@ impl FrameBuilder { pub fn pop_stacking_context(&mut self) { self.cmds.push(PrimitiveRunCmd::PopStackingContext); self.stacking_context_stack.pop(); - assert!(!self.text_shadow_builder.has_shadows(), + assert!(self.shadow_prim_stack.is_empty(), "Found unpopped text shadows when popping stacking context!"); } @@ -580,85 +430,37 @@ impl FrameBuilder { shadow: TextShadow, clip_and_scroll: ClipAndScrollInfo, local_clip: &LocalClip) { - self.text_shadow_builder.push_shadow(shadow, - clip_and_scroll, - local_clip.clone()); + let prim = TextShadowPrimitiveCpu { + shadow, + primitives: Vec::new(), + }; + + // Create an empty text-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 text-shadow context. + let prim_index = self.add_primitive(clip_and_scroll, + &LayerRect::zero(), + local_clip, + &[], + PrimitiveContainer::TextShadow(prim)); + + self.shadow_prim_stack.push(prim_index); } pub fn pop_text_shadow(&mut self) { - let text_shadow = self.text_shadow_builder - .pending_shadows - .pop() - .expect("Too many PopTextShadows?"); - if !text_shadow.runs.is_empty() { - // Offset the position we will draw the cached primitive result - // (which is blurred) into the main target by the text-shadow offset. - let local_rect = text_shadow.local_rect - .translate(&text_shadow.shadow.offset); - - // Select a fast path if the blur radius is zero. - if text_shadow.shadow.blur_radius == 0.0 { - // In the case of zero blur, just add each run as a normal text - // primitive. - for run in text_shadow.runs { - let prim = TextRunPrimitiveCpu { - run: run, - color: text_shadow.shadow.color, - }; - - self.add_primitive(text_shadow.clip_and_scroll, - &local_rect, - &text_shadow.local_clip, - &[], - PrimitiveContainer::TextRun(prim)); - } - } else { - let prim_cpu = TextShadowPrimitiveCpu { - runs: text_shadow.runs, - shadow: text_shadow.shadow, - }; - - // Add a text shadow that contains all the text runs added for - // this shadow. - self.add_primitive(text_shadow.clip_and_scroll, - &local_rect, - &text_shadow.local_clip, - &[], - PrimitiveContainer::TextShadow(prim_cpu)); - } - - for decoration in text_shadow.decorations { - self.add_primitive(text_shadow.clip_and_scroll, - &decoration.local_rect.translate(&text_shadow.shadow.offset), - &text_shadow.local_clip, - &[], - PrimitiveContainer::Rectangle(decoration.prim)); - } - } - - // Once all shadows have been added for this stack, and we have - // emptied the stack, add any normal / visual text runs that - // were added. This ensures they paint after their associated - // shadow primitives. - if !self.text_shadow_builder.has_shadows() { - let pending_texts = mem::replace(&mut self.text_shadow_builder.pending_texts, Vec::new()); - for prim in pending_texts { - self.add_primitive(prim.clip_and_scroll, - &prim.local_rect, - &prim.local_clip, - &[], - PrimitiveContainer::TextRun(prim.prim)); - } - - let pending_decorations = mem::replace(&mut self.text_shadow_builder.pending_decorations, Vec::new()); - for prim in pending_decorations { - self.add_primitive(prim.clip_and_scroll, - &prim.local_rect, - &prim.local_clip, - &[], - PrimitiveContainer::Rectangle(prim.prim)); - } - } + let prim_index = self.shadow_prim_stack + .pop() + .expect("invalid shadow push/pop count"); + + // By now, the local rect of the text shadow has been calculated. It + // is calculated as the items in the shadow are added. It's now + // safe to offset the local rect by the offset of the shadow, which + // is then used when blitting the shadow to the final location. + let metadata = &mut self.prim_store.cpu_metadata[prim_index.0]; + let prim = &self.prim_store.cpu_text_shadows[metadata.cpu_prim_index.0]; + + metadata.local_rect = metadata.local_rect.translate(&prim.shadow.offset); } pub fn add_solid_rectangle(&mut self, @@ -675,12 +477,25 @@ impl FrameBuilder { // text decoration support is added (via the // Line display item) this can be removed, so that // rectangles don't participate in text shadows. - if self.text_shadow_builder.has_shadows() { - self.text_shadow_builder.push_decoration(rect, - color, - local_clip, - clip_and_scroll); - } else if color.a > 0.0 { + let mut trivial_shadows = Vec::new(); + for shadow_prim_index in &self.shadow_prim_stack { + let shadow_metadata = &self.prim_store.cpu_metadata[shadow_prim_index.0]; + let shadow_prim = &self.prim_store.cpu_text_shadows[shadow_metadata.cpu_prim_index.0]; + if shadow_prim.shadow.blur_radius == 0.0 { + trivial_shadows.push(shadow_prim.shadow); + } + } + for shadow in trivial_shadows { + self.add_primitive(clip_and_scroll, + &rect.translate(&shadow.offset), + local_clip, + &[], + PrimitiveContainer::Rectangle(RectanglePrimitive { + color: shadow.color, + })); + } + + if color.a > 0.0 { let prim = RectanglePrimitive { color: *color, }; @@ -1024,13 +839,13 @@ impl FrameBuilder { // 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; + let mut normal_render_mode = self.config.default_font_render_mode; // There are some conditions under which we can't use // subpixel text rendering, even if enabled. - if render_mode == FontRenderMode::Subpixel { + if normal_render_mode == FontRenderMode::Subpixel { if color.a != 1.0 { - render_mode = FontRenderMode::Alpha; + normal_render_mode = FontRenderMode::Alpha; } // text on a stacking context that has filters @@ -1041,36 +856,90 @@ impl FrameBuilder { if let Some(sc_index) = self.stacking_context_stack.last() { let stacking_context = &self.stacking_context_store[sc_index.0]; if stacking_context.composite_ops.count() > 0 { - render_mode = FontRenderMode::Alpha; + normal_render_mode = FontRenderMode::Alpha; } } } + // Shadows never use subpixel AA, but need to respect the alpha/mono flag + // for reftests. + let shadow_render_mode = match self.config.default_font_render_mode { + FontRenderMode::Subpixel | FontRenderMode::Alpha => FontRenderMode::Alpha, + FontRenderMode::Mono => FontRenderMode::Mono, + }; + let prim = TextRunPrimitiveCpu { - run: TextRun { - font_key, - logical_font_size: size, - glyph_range, - glyph_count, - glyph_instances: Vec::new(), - glyph_options, - render_mode: render_mode, - offset: LayerVector2D::zero(), - }, + font_key, + logical_font_size: size, + glyph_range, + glyph_count, + glyph_instances: Vec::new(), + glyph_options, + normal_render_mode, + shadow_render_mode, + offset: LayerVector2D::zero(), color: *color, }; - if self.text_shadow_builder.has_shadows() { - self.text_shadow_builder.push_text(prim, - rect, - local_clip.clone(), - clip_and_scroll); - } else if color.a > 0.0 { + // 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_text_shadow_prims = Vec::new(); + for shadow_prim_index in &self.shadow_prim_stack { + let shadow_metadata = &self.prim_store.cpu_metadata[shadow_prim_index.0]; + let shadow_prim = &self.prim_store.cpu_text_shadows[shadow_metadata.cpu_prim_index.0]; + if shadow_prim.shadow.blur_radius == 0.0 { + let mut text_prim = prim.clone(); + text_prim.color = shadow_prim.shadow.color; + text_prim.offset = shadow_prim.shadow.offset; + fast_text_shadow_prims.push(text_prim); + } + } + for text_prim in fast_text_shadow_prims { self.add_primitive(clip_and_scroll, - &rect, + &rect.translate(&text_prim.offset), local_clip, &[], - PrimitiveContainer::TextRun(prim)); + PrimitiveContainer::TextRun(text_prim)); + } + + // 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(clip_and_scroll, + &rect, + local_clip, + &[], + PrimitiveContainer::TextRun(prim)); + + // Only add a visual element if it can contribute to the scene. + if color.a > 0.0 { + self.add_primitive_to_draw_list(prim_index, clip_and_scroll); + } + + // 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 text-shadow primitive itself has been added to the draw cmd + // list *before* the visual element, during push_text_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::TextShadow); + let shadow_prim = &mut self.prim_store.cpu_text_shadows[shadow_metadata.cpu_prim_index.0]; + + // Only run real blurs here (fast path zero blurs are handled above). + if shadow_prim.shadow.blur_radius > 0.0 { + let shadow_rect = rect.inflate(shadow_prim.shadow.blur_radius, + shadow_prim.shadow.blur_radius); + shadow_metadata.local_rect = shadow_metadata.local_rect.union(&shadow_rect); + shadow_prim.primitives.push(prim_index); + } } } @@ -2081,7 +1950,8 @@ impl<'a> LayerRectCalculationAndCullingPass<'a> { self.gpu_cache, &packed_layer.transform, self.device_pixel_ratio, - display_list); + display_list, + TextRunMode::Normal); stacking_context.screen_bounds = stacking_context.screen_bounds.union(&prim_screen_rect); stacking_context.isolated_items_bounds = stacking_context.isolated_items_bounds.union(&prim_local_rect); diff --git a/webrender/src/prim_store.rs b/webrender/src/prim_store.rs index 938888069d..ce4e66b609 100644 --- a/webrender/src/prim_store.rs +++ b/webrender/src/prim_store.rs @@ -480,19 +480,6 @@ impl RadialGradientPrimitiveCpu { } } -#[derive(Debug, Clone)] -pub struct TextRun { - pub font_key: FontKey, - pub offset: LayerVector2D, - pub logical_font_size: Au, - pub glyph_range: ItemRange, - pub glyph_count: usize, - // TODO(gw): Maybe make this an Arc for sharing with resource cache - pub glyph_instances: Vec, - pub glyph_options: Option, - pub render_mode: FontRenderMode, -} - #[derive(Debug, Clone)] pub struct TextDecoration { pub local_rect: LayerRect, @@ -501,22 +488,37 @@ pub struct TextDecoration { #[derive(Debug, Clone)] pub struct TextShadowPrimitiveCpu { - pub runs: Vec, pub shadow: TextShadow, + pub primitives: Vec, } #[derive(Debug, Clone)] pub struct TextRunPrimitiveCpu { - pub run: TextRun, + pub font_key: FontKey, + pub offset: LayerVector2D, + pub logical_font_size: Au, + pub glyph_range: ItemRange, + pub glyph_count: usize, + // TODO(gw): Maybe make this an Arc for sharing with resource cache + pub glyph_instances: Vec, + pub glyph_options: Option, + pub normal_render_mode: FontRenderMode, + pub shadow_render_mode: FontRenderMode, pub color: ColorF, } -impl TextRun { +#[derive(Debug, Copy, Clone)] +pub enum TextRunMode { + Normal, + Shadow, +} + +impl TextRunPrimitiveCpu { fn prepare_for_render(&mut self, - color: ColorF, resource_cache: &mut ResourceCache, device_pixel_ratio: f32, - display_list: &BuiltDisplayList) { + display_list: &BuiltDisplayList, + run_mode: TextRunMode) { // Cache the glyph positions, if not in the cache already. // TODO(gw): In the future, remove `glyph_instances` // completely, and just reference the glyphs @@ -532,19 +534,22 @@ impl TextRun { } let font_size_dp = self.logical_font_size.scale_by(device_pixel_ratio); + let render_mode = match run_mode { + TextRunMode::Normal => self.normal_render_mode, + TextRunMode::Shadow => self.shadow_render_mode, + }; resource_cache.request_glyphs(self.font_key, font_size_dp, - color, + self.color, &self.glyph_instances, - self.render_mode, + render_mode, self.glyph_options); } fn write_gpu_blocks(&self, - color: ColorF, request: &mut GpuDataRequest) { - request.push(color); + request.push(self.color); request.push([self.offset.x, self.offset.y, 0.0, 0.0]); // Two glyphs are packed per GPU block. @@ -554,40 +559,14 @@ impl TextRun { // GPU block. let first_glyph = glyph_chunk.first().unwrap(); let second_glyph = glyph_chunk.last().unwrap(); - let data = match self.render_mode { - FontRenderMode::Mono | - FontRenderMode::Alpha => [ - first_glyph.point.x, - first_glyph.point.y, - second_glyph.point.x, - second_glyph.point.y, - ], - // The sub-pixel offset has already been taken into account - // by the glyph rasterizer, thus the truncating here. - FontRenderMode::Subpixel => [ - first_glyph.point.x.trunc(), - first_glyph.point.y.trunc(), - second_glyph.point.x.trunc(), - second_glyph.point.y.trunc(), - ], - }; - request.push(data); + request.push([first_glyph.point.x, + first_glyph.point.y, + second_glyph.point.x, + second_glyph.point.y]); } } } -impl TextRunPrimitiveCpu { - fn prepare_for_render(&mut self, - resource_cache: &mut ResourceCache, - device_pixel_ratio: f32, - display_list: &BuiltDisplayList) { - self.run.prepare_for_render(self.color, - resource_cache, - device_pixel_ratio, - display_list); - } -} - #[derive(Debug, Clone)] #[repr(C)] struct GlyphPrimitive { @@ -1058,8 +1037,30 @@ impl PrimitiveStore { gpu_cache: &mut GpuCache, layer_transform: &LayerToWorldTransform, device_pixel_ratio: f32, - display_list: &BuiltDisplayList) + display_list: &BuiltDisplayList, + text_run_mode: TextRunMode) -> &mut PrimitiveMetadata { + let (prim_kind, cpu_prim_index) = { + let metadata = &self.cpu_metadata[prim_index.0]; + (metadata.prim_kind, metadata.cpu_prim_index) + }; + + // Recurse into any sub primitives and prepare them for rendering first. + // TODO(gw): This code is a bit hacky to work around the borrow checker. + // Specifically, the clone() below on the primitive list for + // text shadow primitives. Consider restructuring this code to + // avoid borrow checker issues. + if prim_kind == PrimitiveKind::TextShadow { + for sub_prim_index in self.cpu_text_shadows[cpu_prim_index.0].primitives.clone() { + self.prepare_prim_for_render(sub_prim_index, + resource_cache, + gpu_cache, + layer_transform, + device_pixel_ratio, + display_list, + TextRunMode::Shadow); + } + } let metadata = &mut self.cpu_metadata[prim_index.0]; @@ -1087,7 +1088,7 @@ impl PrimitiveStore { // in device space. The shader adds a 1-pixel border around // the patch, in order to prevent bilinear filter artifacts as // the patch is clamped / mirrored across the box shadow rect. - let box_shadow_cpu = &self.cpu_box_shadows[metadata.cpu_prim_index.0]; + let box_shadow_cpu = &self.cpu_box_shadows[cpu_prim_index.0]; let edge_size = box_shadow_cpu.edge_size.ceil() * device_pixel_ratio; let edge_size = edge_size as i32 + 2; // Account for bilinear filtering let cache_size = DeviceIntSize::new(edge_size, edge_size); @@ -1095,18 +1096,7 @@ impl PrimitiveStore { metadata.render_task.as_mut().unwrap().location = location; } PrimitiveKind::TextShadow => { - let shadow = &mut self.cpu_text_shadows[metadata.cpu_prim_index.0]; - for text in &mut shadow.runs { - // The color used to request glyphs for shadow rendering - // doesn't actually matter (since we're not using subpixel - // rendering in this case). Passing a constant color here - // is a bit more efficient, since we don't need to rasterize - // glyphs multiple times for shadows of different colors. - text.prepare_for_render(ColorF::new(0.0, 0.0, 0.0, 1.0), - resource_cache, - device_pixel_ratio, - display_list); - } + let shadow = &mut self.cpu_text_shadows[cpu_prim_index.0]; // This is a text-shadow element. Create a render task that will // render the text run to a target, and then apply a gaussian @@ -1124,13 +1114,14 @@ impl PrimitiveStore { prim_index)); } PrimitiveKind::TextRun => { - let text = &mut self.cpu_text_runs[metadata.cpu_prim_index.0]; + let text = &mut self.cpu_text_runs[cpu_prim_index.0]; text.prepare_for_render(resource_cache, device_pixel_ratio, - display_list); + display_list, + text_run_mode); } PrimitiveKind::Image => { - let image_cpu = &mut self.cpu_images[metadata.cpu_prim_index.0]; + let image_cpu = &mut self.cpu_images[cpu_prim_index.0]; match image_cpu.kind { ImagePrimitiveKind::Image(image_key, image_rendering, tile_offset, tile_spacing) => { @@ -1149,7 +1140,7 @@ impl PrimitiveStore { } } PrimitiveKind::YuvImage => { - let image_cpu = &mut self.cpu_yuv_images[metadata.cpu_prim_index.0]; + let image_cpu = &mut self.cpu_yuv_images[cpu_prim_index.0]; let channel_num = image_cpu.format.get_plane_num(); debug_assert!(channel_num <= 3); @@ -1169,49 +1160,51 @@ impl PrimitiveStore { match metadata.prim_kind { PrimitiveKind::Rectangle => { - let rect = &self.cpu_rectangles[metadata.cpu_prim_index.0]; + let rect = &self.cpu_rectangles[cpu_prim_index.0]; rect.write_gpu_blocks(request); } PrimitiveKind::Border => { - let border = &self.cpu_borders[metadata.cpu_prim_index.0]; + let border = &self.cpu_borders[cpu_prim_index.0]; border.write_gpu_blocks(request); } PrimitiveKind::BoxShadow => { - let box_shadow = &self.cpu_box_shadows[metadata.cpu_prim_index.0]; + let box_shadow = &self.cpu_box_shadows[cpu_prim_index.0]; box_shadow.write_gpu_blocks(request); } PrimitiveKind::Image => { - let image = &self.cpu_images[metadata.cpu_prim_index.0]; + let image = &self.cpu_images[cpu_prim_index.0]; image.write_gpu_blocks(request); } PrimitiveKind::YuvImage => { - let yuv_image = &self.cpu_yuv_images[metadata.cpu_prim_index.0]; + let yuv_image = &self.cpu_yuv_images[cpu_prim_index.0]; yuv_image.write_gpu_blocks(request); } PrimitiveKind::AlignedGradient => { - let gradient = &self.cpu_gradients[metadata.cpu_prim_index.0]; + let gradient = &self.cpu_gradients[cpu_prim_index.0]; metadata.opacity = gradient.build_gpu_blocks_for_aligned(display_list, request); } PrimitiveKind::AngleGradient => { - let gradient = &self.cpu_gradients[metadata.cpu_prim_index.0]; + let gradient = &self.cpu_gradients[cpu_prim_index.0]; gradient.build_gpu_blocks_for_angle_radial(display_list, request); } PrimitiveKind::RadialGradient => { - let gradient = &self.cpu_radial_gradients[metadata.cpu_prim_index.0]; + let gradient = &self.cpu_radial_gradients[cpu_prim_index.0]; gradient.build_gpu_blocks_for_angle_radial(display_list, request); } PrimitiveKind::TextRun => { - let text = &self.cpu_text_runs[metadata.cpu_prim_index.0]; - text.run.write_gpu_blocks(text.color, &mut request); + let text = &self.cpu_text_runs[cpu_prim_index.0]; + text.write_gpu_blocks(&mut request); } PrimitiveKind::TextShadow => { - let prim = &self.cpu_text_shadows[metadata.cpu_prim_index.0]; - for text in &prim.runs { - text.write_gpu_blocks(prim.shadow.color, &mut request); - } + let prim = &self.cpu_text_shadows[cpu_prim_index.0]; + request.push(prim.shadow.color); + request.push([prim.shadow.offset.x, + prim.shadow.offset.y, + prim.shadow.blur_radius, + 0.0]); } } } diff --git a/webrender/src/resource_cache.rs b/webrender/src/resource_cache.rs index bdf8a5c9e5..770b0aca78 100644 --- a/webrender/src/resource_cache.rs +++ b/webrender/src/resource_cache.rs @@ -28,6 +28,7 @@ use rayon::ThreadPool; use glyph_rasterizer::{GlyphRasterizer, GlyphCache, GlyphRequest}; const DEFAULT_TILE_SIZE: TileSize = 512; +const BLACK: ColorF = ColorF { r: 0.0, b: 0.0, g: 0.0, a: 1.0 }; // These coordinates are always in texels. // They are converted to normalized ST @@ -450,12 +451,19 @@ impl ResourceCache { pub fn request_glyphs(&mut self, key: FontKey, size: Au, - color: ColorF, + mut color: ColorF, glyph_instances: &[GlyphInstance], render_mode: FontRenderMode, glyph_options: Option) { debug_assert_eq!(self.state, State::AddResources); + // In alpha/mono mode, the color of the font is irrelevant. + // Forcing it to black in those cases saves rasterizing glyphs + // of different colors when not needed. + if render_mode != FontRenderMode::Subpixel { + color = BLACK; + } + self.glyph_rasterizer.request_glyphs( &mut self.cached_glyphs, self.current_frame_id, @@ -476,11 +484,17 @@ impl ResourceCache { pub fn get_glyphs(&self, font_key: FontKey, size: Au, - color: ColorF, + mut color: ColorF, glyph_instances: &[GlyphInstance], render_mode: FontRenderMode, glyph_options: Option, mut f: F) -> SourceTexture where F: FnMut(usize, &GpuCacheHandle) { + // Color when retrieving glyphs must match that of the request, + // otherwise the hash keys won't match. + if render_mode != FontRenderMode::Subpixel { + color = BLACK; + } + debug_assert_eq!(self.state, State::QueryResources); let mut glyph_request = GlyphRequest::new( font_key, diff --git a/webrender/src/tiling.rs b/webrender/src/tiling.rs index ed15b50081..c32f856e38 100644 --- a/webrender/src/tiling.rs +++ b/webrender/src/tiling.rs @@ -50,7 +50,7 @@ impl AlphaBatchHelpers for PrimitiveStore { match metadata.prim_kind { PrimitiveKind::TextRun => { let text_run_cpu = &self.cpu_text_runs[metadata.cpu_prim_index.0]; - match text_run_cpu.run.render_mode { + match text_run_cpu.normal_render_mode { FontRenderMode::Subpixel => BlendMode::Subpixel(text_run_cpu.color), FontRenderMode::Alpha | FontRenderMode::Mono => BlendMode::Alpha, } @@ -478,19 +478,21 @@ impl AlphaRenderItem { } PrimitiveKind::TextRun => { let text_cpu = &ctx.prim_store.cpu_text_runs[prim_metadata.cpu_prim_index.0]; - let font_size_dp = text_cpu.run.logical_font_size.scale_by(ctx.device_pixel_ratio); + let font_size_dp = text_cpu.logical_font_size.scale_by(ctx.device_pixel_ratio); // TODO(gw): avoid / recycle this allocation in the future. let mut instances = Vec::new(); - let texture_id = ctx.resource_cache.get_glyphs(text_cpu.run.font_key, + let texture_id = ctx.resource_cache.get_glyphs(text_cpu.font_key, font_size_dp, text_cpu.color, - &text_cpu.run.glyph_instances, - text_cpu.run.render_mode, - text_cpu.run.glyph_options, |index, handle| { + &text_cpu.glyph_instances, + text_cpu.normal_render_mode, + text_cpu.glyph_options, |index, handle| { let uv_address = handle.as_int(gpu_cache); - instances.push(base_instance.build(index as i32, 0, uv_address)); + instances.push(base_instance.build(index as i32, + text_cpu.normal_render_mode as i32, + uv_address)); }); if texture_id != SourceTexture::Invalid { @@ -1017,7 +1019,7 @@ impl RenderTarget for ColorRenderTarget { RenderTaskKind::CachePrimitive(prim_index) => { let prim_metadata = ctx.prim_store.get_metadata(prim_index); - let mut prim_address = prim_metadata.gpu_location.as_int(gpu_cache); + let prim_address = prim_metadata.gpu_location.as_int(gpu_cache); match prim_metadata.prim_kind { PrimitiveKind::BoxShadow => { @@ -1036,51 +1038,53 @@ impl RenderTarget for ColorRenderTarget { let task_index = render_tasks.get_task_index(&task.id, pass_index); - for text in &prim.runs { - let instance = SimplePrimitiveInstance::new(prim_address, - task_index, - RenderTaskIndex(0), - PackedLayerIndex(0), - 0); // z is disabled for rendering cache 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, - ColorF::new(0.0, 0.0, 0.0, 1.0), - &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, - uv_address, - 0)); - }); - - if texture_id != SourceTexture::Invalid { - let textures = BatchTextures { - colors: [texture_id, SourceTexture::Invalid, SourceTexture::Invalid], - }; - - self.text_run_cache_prims.extend_from_slice(&instances); - instances.clear(); - - // - // Work out the GPU address of the next primitive in the GPU cache - // for this text run. The GPU block layout for a text run is: - // - // Color of text run [1 block] - // Local offset for this text run [1 block] - // Packed glyph offsets [ (glyph count + 1) / 2 ] - // - // Two glyphs are packed per GPU block. - // - prim_address += 2 + (text.glyph_instances.len() as i32 + 1) / 2; - - 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 sub_prim_index in &prim.primitives { + let sub_metadata = ctx.prim_store.get_metadata(*sub_prim_index); + match sub_metadata.prim_kind { + PrimitiveKind::TextRun => { + // Add instances that reference the text run GPU location. Also supply + // the parent text-shadow prim address as a user data field, allowing + // the shader to fetch the text-shadow parameters. + let sub_prim_address = sub_metadata.gpu_location.as_int(gpu_cache); + let text = &ctx.prim_store.cpu_text_runs[sub_metadata.cpu_prim_index.0]; + + let instance = SimplePrimitiveInstance::new(sub_prim_address, + task_index, + RenderTaskIndex(0), + PackedLayerIndex(0), + 0); // z is disabled for rendering cache 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.shadow_render_mode, + text.glyph_options, |index, handle| { + let uv_address = handle.as_int(gpu_cache); + instances.push(instance.build(index as i32, + uv_address, + prim_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); + 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; + } + } + _ => { + unreachable!("Unexpected sub primitive type"); + } } } } diff --git a/webrender_api/src/font.rs b/webrender_api/src/font.rs index 572a5133a8..782929024c 100644 --- a/webrender_api/src/font.rs +++ b/webrender_api/src/font.rs @@ -71,9 +71,10 @@ pub enum FontTemplate { Native(NativeFontHandle), } +#[repr(C)] #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Ord, PartialOrd)] pub enum FontRenderMode { - Mono, + Mono = 0, Alpha, Subpixel, }