line_dec.rs (8927B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 use api::{ 6 ColorF, ColorU, RasterSpace, 7 LineOrientation, LineStyle, PremultipliedColorF, Shadow, 8 }; 9 use api::units::*; 10 use crate::gpu_types::ImageBrushPrimitiveData; 11 use crate::renderer::GpuBufferWriterF; 12 use crate::scene_building::{CreateShadow, IsVisible}; 13 use crate::frame_builder::FrameBuildingState; 14 use crate::intern; 15 use crate::internal_types::LayoutPrimitiveInfo; 16 use crate::prim_store::{ 17 PrimKey, PrimTemplate, PrimTemplateCommonData, 18 InternablePrimitive, PrimitiveStore, 19 }; 20 use crate::prim_store::PrimitiveInstanceKind; 21 22 /// Maximum resolution in device pixels at which line decorations are rasterized. 23 pub const MAX_LINE_DECORATION_RESOLUTION: u32 = 4096; 24 25 #[derive(Clone, Debug, Hash, MallocSizeOf, PartialEq, Eq)] 26 #[cfg_attr(feature = "capture", derive(Serialize))] 27 #[cfg_attr(feature = "replay", derive(Deserialize))] 28 pub struct LineDecorationCacheKey { 29 pub style: LineStyle, 30 pub orientation: LineOrientation, 31 pub wavy_line_thickness: Au, 32 pub size: LayoutSizeAu, 33 } 34 35 /// Identifying key for a line decoration. 36 #[derive(Clone, Debug, Hash, MallocSizeOf, PartialEq, Eq)] 37 #[cfg_attr(feature = "capture", derive(Serialize))] 38 #[cfg_attr(feature = "replay", derive(Deserialize))] 39 pub struct LineDecoration { 40 // If the cache_key is Some(..) it is a line decoration 41 // that relies on a render task (e.g. wavy). If the 42 // cache key is None, it uses a fast path to draw the 43 // line decoration as a solid rect. 44 pub cache_key: Option<LineDecorationCacheKey>, 45 pub color: ColorU, 46 } 47 48 pub type LineDecorationKey = PrimKey<LineDecoration>; 49 50 impl LineDecorationKey { 51 pub fn new( 52 info: &LayoutPrimitiveInfo, 53 line_dec: LineDecoration, 54 ) -> Self { 55 LineDecorationKey { 56 common: info.into(), 57 kind: line_dec, 58 } 59 } 60 } 61 62 impl intern::InternDebug for LineDecorationKey {} 63 64 #[cfg_attr(feature = "capture", derive(Serialize))] 65 #[cfg_attr(feature = "replay", derive(Deserialize))] 66 #[derive(MallocSizeOf)] 67 pub struct LineDecorationData { 68 pub cache_key: Option<LineDecorationCacheKey>, 69 pub color: ColorF, 70 } 71 72 impl LineDecorationData { 73 /// Update the GPU cache for a given primitive template. This may be called multiple 74 /// times per frame, by each primitive reference that refers to this interned 75 /// template. The initial request call to the GPU cache ensures that work is only 76 /// done if the cache entry is invalid (due to first use or eviction). 77 pub fn update( 78 &mut self, 79 common: &mut PrimTemplateCommonData, 80 frame_state: &mut FrameBuildingState, 81 ) { 82 let mut writer = frame_state.frame_gpu_data.f32.write_blocks(3); 83 self.write_prim_gpu_blocks(&mut writer); 84 common.gpu_buffer_address = writer.finish(); 85 } 86 87 fn write_prim_gpu_blocks( 88 &self, 89 writer: &mut GpuBufferWriterF 90 ) { 91 match self.cache_key.as_ref() { 92 Some(cache_key) => { 93 writer.push(&ImageBrushPrimitiveData { 94 color: self.color.premultiplied(), 95 background_color: PremultipliedColorF::WHITE, 96 stretch_size: LayoutSize::new( 97 cache_key.size.width.to_f32_px(), 98 cache_key.size.height.to_f32_px(), 99 ), 100 }); 101 } 102 None => { 103 writer.push_one(self.color.premultiplied()); 104 } 105 } 106 } 107 } 108 109 pub type LineDecorationTemplate = PrimTemplate<LineDecorationData>; 110 111 impl From<LineDecorationKey> for LineDecorationTemplate { 112 fn from(line_dec: LineDecorationKey) -> Self { 113 let common = PrimTemplateCommonData::with_key_common(line_dec.common); 114 LineDecorationTemplate { 115 common, 116 kind: LineDecorationData { 117 cache_key: line_dec.kind.cache_key, 118 color: line_dec.kind.color.into(), 119 } 120 } 121 } 122 } 123 124 pub type LineDecorationDataHandle = intern::Handle<LineDecoration>; 125 126 impl intern::Internable for LineDecoration { 127 type Key = LineDecorationKey; 128 type StoreData = LineDecorationTemplate; 129 type InternData = (); 130 const PROFILE_COUNTER: usize = crate::profiler::INTERNED_LINE_DECORATIONS; 131 } 132 133 impl InternablePrimitive for LineDecoration { 134 fn into_key( 135 self, 136 info: &LayoutPrimitiveInfo, 137 ) -> LineDecorationKey { 138 LineDecorationKey::new( 139 info, 140 self, 141 ) 142 } 143 144 fn make_instance_kind( 145 _key: LineDecorationKey, 146 data_handle: LineDecorationDataHandle, 147 _: &mut PrimitiveStore, 148 ) -> PrimitiveInstanceKind { 149 PrimitiveInstanceKind::LineDecoration { 150 data_handle, 151 render_task: None, 152 } 153 } 154 } 155 156 impl CreateShadow for LineDecoration { 157 fn create_shadow( 158 &self, 159 shadow: &Shadow, 160 _: bool, 161 _: RasterSpace, 162 ) -> Self { 163 LineDecoration { 164 color: shadow.color.into(), 165 cache_key: self.cache_key.clone(), 166 } 167 } 168 } 169 170 impl IsVisible for LineDecoration { 171 fn is_visible(&self) -> bool { 172 self.color.a > 0 173 } 174 } 175 176 /// Choose the decoration mask tile size for a given line. 177 /// 178 /// Given a line with overall size `rect_size` and the given `orientation`, 179 /// return the dimensions of a single mask tile for the decoration pattern 180 /// described by `style` and `wavy_line_thickness`. 181 /// 182 /// If `style` is `Solid`, no mask tile is necessary; return `None`. The other 183 /// styles each have their own characteristic periods of repetition, so for each 184 /// one, this function returns a `LayoutSize` with the right aspect ratio and 185 /// whose specific size is convenient for the `cs_line_decoration.glsl` fragment 186 /// shader to work with. The shader uses a local coordinate space in which the 187 /// tile fills a rectangle with one corner at the origin, and with the size this 188 /// function returns. 189 /// 190 /// The returned size is not necessarily in pixels; device scaling and other 191 /// concerns can still affect the actual task size. 192 /// 193 /// Regardless of whether `orientation` is `Vertical` or `Horizontal`, the 194 /// `width` and `height` of the returned size are always horizontal and 195 /// vertical, respectively. 196 pub fn get_line_decoration_size( 197 rect_size: &LayoutSize, 198 orientation: LineOrientation, 199 style: LineStyle, 200 wavy_line_thickness: f32, 201 ) -> Option<LayoutSize> { 202 let h = match orientation { 203 LineOrientation::Horizontal => rect_size.height, 204 LineOrientation::Vertical => rect_size.width, 205 }; 206 207 // TODO(gw): The formulae below are based on the existing gecko and line 208 // shader code. They give reasonable results for most inputs, 209 // but could definitely do with a detailed pass to get better 210 // quality on a wider range of inputs! 211 // See nsCSSRendering::PaintDecorationLine in Gecko. 212 213 let (parallel, perpendicular) = match style { 214 LineStyle::Solid => { 215 return None; 216 } 217 LineStyle::Dashed => { 218 let dash_length = (3.0 * h).min(64.0).max(1.0); 219 220 (2.0 * dash_length, 4.0) 221 } 222 LineStyle::Dotted => { 223 let diameter = h.min(64.0).max(1.0); 224 let period = 2.0 * diameter; 225 226 (period, diameter) 227 } 228 LineStyle::Wavy => { 229 let line_thickness = wavy_line_thickness.max(1.0); 230 let slope_length = h - line_thickness; 231 let flat_length = ((line_thickness - 1.0) * 2.0).max(1.0); 232 let approx_period = 2.0 * (slope_length + flat_length); 233 234 (approx_period, h) 235 } 236 }; 237 238 Some(match orientation { 239 LineOrientation::Horizontal => LayoutSize::new(parallel, perpendicular), 240 LineOrientation::Vertical => LayoutSize::new(perpendicular, parallel), 241 }) 242 } 243 244 #[test] 245 #[cfg(target_pointer_width = "64")] 246 fn test_struct_sizes() { 247 use std::mem; 248 // The sizes of these structures are critical for performance on a number of 249 // talos stress tests. If you get a failure here on CI, there's two possibilities: 250 // (a) You made a structure smaller than it currently is. Great work! Update the 251 // test expectations and move on. 252 // (b) You made a structure larger. This is not necessarily a problem, but should only 253 // be done with care, and after checking if talos performance regresses badly. 254 assert_eq!(mem::size_of::<LineDecoration>(), 20, "LineDecoration size changed"); 255 assert_eq!(mem::size_of::<LineDecorationTemplate>(), 56, "LineDecorationTemplate size changed"); 256 assert_eq!(mem::size_of::<LineDecorationKey>(), 40, "LineDecorationKey size changed"); 257 }