conic.rs (17691B)
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 //! Conic gradients 6 //! 7 //! Specification: https://drafts.csswg.org/css-images-4/#conic-gradients 8 //! 9 //! Conic gradients are rendered via cached render tasks and composited with the image brush. 10 11 use euclid::vec2; 12 use api::{ColorF, ExtendMode, GradientStop, PremultipliedColorF}; 13 use api::units::*; 14 use crate::gpu_types::ImageBrushPrimitiveData; 15 use crate::pattern::{Pattern, PatternBuilder, PatternBuilderContext, PatternBuilderState, PatternKind, PatternShaderInput, PatternTextureInput}; 16 use crate::prim_store::gradient::{gpu_gradient_stops_blocks, write_gpu_gradient_stops_tree, GradientKind}; 17 use crate::scene_building::IsVisible; 18 use crate::frame_builder::FrameBuildingState; 19 use crate::intern::{Internable, InternDebug, Handle as InternHandle}; 20 use crate::internal_types::LayoutPrimitiveInfo; 21 use crate::prim_store::{BrushSegment, GradientTileRange, VECS_PER_SEGMENT}; 22 use crate::prim_store::{PrimitiveInstanceKind, PrimitiveOpacity, FloatKey}; 23 use crate::prim_store::{PrimKeyCommonData, PrimTemplateCommonData, PrimitiveStore}; 24 use crate::prim_store::{NinePatchDescriptor, PointKey, SizeKey, InternablePrimitive}; 25 use crate::render_task::{RenderTask, RenderTaskKind}; 26 use crate::render_task_graph::RenderTaskId; 27 use crate::render_task_cache::{RenderTaskCacheKeyKind, RenderTaskCacheKey, RenderTaskParent}; 28 use crate::renderer::{GpuBufferAddress, GpuBufferBuilder}; 29 30 use std::{hash, ops::{Deref, DerefMut}}; 31 use super::{stops_and_min_alpha, GradientStopKey, GradientGpuBlockBuilder}; 32 33 /// Hashable conic gradient parameters, for use during prim interning. 34 #[cfg_attr(feature = "capture", derive(Serialize))] 35 #[cfg_attr(feature = "replay", derive(Deserialize))] 36 #[derive(Debug, Clone, MallocSizeOf, PartialEq)] 37 pub struct ConicGradientParams { 38 pub angle: f32, // in radians 39 pub start_offset: f32, 40 pub end_offset: f32, 41 } 42 43 impl Eq for ConicGradientParams {} 44 45 impl hash::Hash for ConicGradientParams { 46 fn hash<H: hash::Hasher>(&self, state: &mut H) { 47 self.angle.to_bits().hash(state); 48 self.start_offset.to_bits().hash(state); 49 self.end_offset.to_bits().hash(state); 50 } 51 } 52 53 /// Identifying key for a line decoration. 54 #[cfg_attr(feature = "capture", derive(Serialize))] 55 #[cfg_attr(feature = "replay", derive(Deserialize))] 56 #[derive(Debug, Clone, Eq, PartialEq, Hash, MallocSizeOf)] 57 pub struct ConicGradientKey { 58 pub common: PrimKeyCommonData, 59 pub extend_mode: ExtendMode, 60 pub center: PointKey, 61 pub params: ConicGradientParams, 62 pub stretch_size: SizeKey, 63 pub stops: Vec<GradientStopKey>, 64 pub tile_spacing: SizeKey, 65 pub nine_patch: Option<Box<NinePatchDescriptor>>, 66 } 67 68 impl ConicGradientKey { 69 pub fn new( 70 info: &LayoutPrimitiveInfo, 71 conic_grad: ConicGradient, 72 ) -> Self { 73 ConicGradientKey { 74 common: info.into(), 75 extend_mode: conic_grad.extend_mode, 76 center: conic_grad.center, 77 params: conic_grad.params, 78 stretch_size: conic_grad.stretch_size, 79 stops: conic_grad.stops, 80 tile_spacing: conic_grad.tile_spacing, 81 nine_patch: conic_grad.nine_patch, 82 } 83 } 84 } 85 86 impl InternDebug for ConicGradientKey {} 87 88 #[cfg_attr(feature = "capture", derive(Serialize))] 89 #[cfg_attr(feature = "replay", derive(Deserialize))] 90 #[derive(MallocSizeOf)] 91 pub struct ConicGradientTemplate { 92 pub common: PrimTemplateCommonData, 93 pub extend_mode: ExtendMode, 94 pub center: DevicePoint, 95 pub params: ConicGradientParams, 96 pub task_size: DeviceIntSize, 97 pub scale: DeviceVector2D, 98 pub stretch_size: LayoutSize, 99 pub tile_spacing: LayoutSize, 100 pub brush_segments: Vec<BrushSegment>, 101 pub stops_opacity: PrimitiveOpacity, 102 pub stops: Vec<GradientStop>, 103 pub src_color: Option<RenderTaskId>, 104 } 105 106 impl PatternBuilder for ConicGradientTemplate { 107 fn build( 108 &self, 109 _sub_rect: Option<DeviceRect>, 110 ctx: &PatternBuilderContext, 111 state: &mut PatternBuilderState, 112 ) -> Pattern { 113 // The scaling parameter is used to compensate for when we reduce the size 114 // of the render task for cached gradients. Here we aren't applying any. 115 let no_scale = DeviceVector2D::one(); 116 117 if ctx.fb_config.precise_conic_gradients { 118 conic_gradient_pattern( 119 self.center, 120 no_scale, 121 &self.params, 122 self.extend_mode, 123 &self.stops, 124 state.frame_gpu_data, 125 ) 126 } else { 127 conic_gradient_pattern_with_table( 128 self.center, 129 no_scale, 130 &self.params, 131 self.extend_mode, 132 &self.stops, 133 state.frame_gpu_data, 134 ) 135 } 136 } 137 138 fn get_base_color( 139 &self, 140 _ctx: &PatternBuilderContext, 141 ) -> ColorF { 142 ColorF::WHITE 143 } 144 145 fn use_shared_pattern( 146 &self, 147 ) -> bool { 148 true 149 } 150 } 151 152 impl Deref for ConicGradientTemplate { 153 type Target = PrimTemplateCommonData; 154 fn deref(&self) -> &Self::Target { 155 &self.common 156 } 157 } 158 159 impl DerefMut for ConicGradientTemplate { 160 fn deref_mut(&mut self) -> &mut Self::Target { 161 &mut self.common 162 } 163 } 164 165 impl From<ConicGradientKey> for ConicGradientTemplate { 166 fn from(item: ConicGradientKey) -> Self { 167 let common = PrimTemplateCommonData::with_key_common(item.common); 168 let mut brush_segments = Vec::new(); 169 170 if let Some(ref nine_patch) = item.nine_patch { 171 brush_segments = nine_patch.create_segments(common.prim_rect.size()); 172 } 173 174 let (stops, min_alpha) = stops_and_min_alpha(&item.stops); 175 176 // Save opacity of the stops for use in 177 // selecting which pass this gradient 178 // should be drawn in. 179 let stops_opacity = PrimitiveOpacity::from_alpha(min_alpha); 180 181 let mut stretch_size: LayoutSize = item.stretch_size.into(); 182 stretch_size.width = stretch_size.width.min(common.prim_rect.width()); 183 stretch_size.height = stretch_size.height.min(common.prim_rect.height()); 184 185 fn approx_eq(a: f32, b: f32) -> bool { (a - b).abs() < 0.01 } 186 187 // Attempt to detect some of the common configurations with hard gradient stops. Allow 188 // those a higher maximum resolution to avoid the worst cases of aliasing artifacts with 189 // large conic gradients. A better solution would be to go back to rendering very large 190 // conic gradients via a brush shader instead of caching all of them (unclear whether 191 // it is important enough to warrant the better solution). 192 let mut has_hard_stops = false; 193 let mut prev_stop = None; 194 let offset_range = item.params.end_offset - item.params.start_offset; 195 for stop in &stops { 196 if offset_range <= 0.0 { 197 break; 198 } 199 if let Some(prev_offset) = prev_stop { 200 // Check whether two consecutive stops are very close (hard stops). 201 if stop.offset < prev_offset + 0.005 / offset_range { 202 // a is the angle of the stop normalized into 0-1 space and repeating in the 0-0.25 range. 203 // If close to 0.0 or 0.25 it means the stop is vertical or horizontal. For those, the lower 204 // resolution isn't a big issue. 205 let a = item.params.angle / (2.0 * std::f32::consts::PI) 206 + item.params.start_offset 207 + stop.offset / offset_range; 208 let a = a.rem_euclid(0.25); 209 210 if !approx_eq(a, 0.0) && !approx_eq(a, 0.25) { 211 has_hard_stops = true; 212 break; 213 } 214 } 215 } 216 prev_stop = Some(stop.offset); 217 } 218 219 let max_size = if has_hard_stops { 220 2048.0 221 } else { 222 1024.0 223 }; 224 225 // Avoid rendering enormous gradients. Radial gradients are mostly made of soft transitions, 226 // so it is unlikely that rendering at a higher resolution that 1024 would produce noticeable 227 // differences, especially with 8 bits per channel. 228 let mut task_size: DeviceSize = stretch_size.cast_unit(); 229 let mut scale = vec2(1.0, 1.0); 230 if task_size.width > max_size { 231 scale.x = task_size.width / max_size; 232 task_size.width = max_size; 233 } 234 if task_size.height > max_size { 235 scale.y = task_size.height / max_size; 236 task_size.height = max_size; 237 } 238 239 ConicGradientTemplate { 240 common, 241 center: DevicePoint::new(item.center.x, item.center.y), 242 extend_mode: item.extend_mode, 243 params: item.params, 244 stretch_size, 245 task_size: task_size.ceil().to_i32(), 246 scale, 247 tile_spacing: item.tile_spacing.into(), 248 brush_segments, 249 stops_opacity, 250 stops, 251 src_color: None, 252 } 253 } 254 } 255 256 impl ConicGradientTemplate { 257 /// Update the GPU cache for a given primitive template. This may be called multiple 258 /// times per frame, by each primitive reference that refers to this interned 259 /// template. The initial request call to the GPU cache ensures that work is only 260 /// done if the cache entry is invalid (due to first use or eviction). 261 pub fn update( 262 &mut self, 263 frame_state: &mut FrameBuildingState, 264 ) { 265 let mut writer = frame_state.frame_gpu_data.f32.write_blocks(3 + self.brush_segments.len() * VECS_PER_SEGMENT); 266 // write_prim_gpu_blocks 267 writer.push(&ImageBrushPrimitiveData { 268 color: PremultipliedColorF::WHITE, 269 background_color: PremultipliedColorF::WHITE, 270 stretch_size: self.stretch_size, 271 }); 272 // write_segment_gpu_blocks 273 for segment in &self.brush_segments { 274 segment.write_gpu_blocks(&mut writer); 275 } 276 self.common.gpu_buffer_address = writer.finish(); 277 278 let cache_key = ConicGradientCacheKey { 279 size: self.task_size, 280 center: PointKey { x: self.center.x, y: self.center.y }, 281 scale: PointKey { x: self.scale.x, y: self.scale.y }, 282 start_offset: FloatKey(self.params.start_offset), 283 end_offset: FloatKey(self.params.end_offset), 284 angle: FloatKey(self.params.angle), 285 extend_mode: self.extend_mode, 286 stops: self.stops.iter().map(|stop| (*stop).into()).collect(), 287 }; 288 289 let task_id = frame_state.resource_cache.request_render_task( 290 Some(RenderTaskCacheKey { 291 size: self.task_size, 292 kind: RenderTaskCacheKeyKind::ConicGradient(cache_key), 293 }), 294 false, 295 RenderTaskParent::Surface, 296 &mut frame_state.frame_gpu_data.f32, 297 frame_state.rg_builder, 298 &mut frame_state.surface_builder, 299 &mut |rg_builder, gpu_buffer_builder| { 300 let stops = GradientGpuBlockBuilder::build( 301 false, 302 gpu_buffer_builder, 303 &self.stops, 304 ); 305 306 rg_builder.add().init(RenderTask::new_dynamic( 307 self.task_size, 308 RenderTaskKind::ConicGradient(ConicGradientTask { 309 extend_mode: self.extend_mode, 310 scale: self.scale, 311 center: self.center, 312 params: self.params.clone(), 313 stops, 314 }), 315 )) 316 } 317 ); 318 319 self.src_color = Some(task_id); 320 321 // Tile spacing is always handled by decomposing into separate draw calls so the 322 // primitive opacity is equivalent to stops opacity. This might change to being 323 // set to non-opaque in the presence of tile spacing if/when tile spacing is handled 324 // in the same way as with the image primitive. 325 self.opacity = self.stops_opacity; 326 } 327 } 328 329 pub type ConicGradientDataHandle = InternHandle<ConicGradient>; 330 331 #[derive(Debug, MallocSizeOf)] 332 #[cfg_attr(feature = "capture", derive(Serialize))] 333 #[cfg_attr(feature = "replay", derive(Deserialize))] 334 pub struct ConicGradient { 335 pub extend_mode: ExtendMode, 336 pub center: PointKey, 337 pub params: ConicGradientParams, 338 pub stretch_size: SizeKey, 339 pub stops: Vec<GradientStopKey>, 340 pub tile_spacing: SizeKey, 341 pub nine_patch: Option<Box<NinePatchDescriptor>>, 342 } 343 344 impl Internable for ConicGradient { 345 type Key = ConicGradientKey; 346 type StoreData = ConicGradientTemplate; 347 type InternData = (); 348 const PROFILE_COUNTER: usize = crate::profiler::INTERNED_CONIC_GRADIENTS; 349 } 350 351 impl InternablePrimitive for ConicGradient { 352 fn into_key( 353 self, 354 info: &LayoutPrimitiveInfo, 355 ) -> ConicGradientKey { 356 ConicGradientKey::new(info, self) 357 } 358 359 fn make_instance_kind( 360 _key: ConicGradientKey, 361 data_handle: ConicGradientDataHandle, 362 _prim_store: &mut PrimitiveStore, 363 ) -> PrimitiveInstanceKind { 364 PrimitiveInstanceKind::ConicGradient { 365 data_handle, 366 visible_tiles_range: GradientTileRange::empty(), 367 use_legacy_path: true, 368 } 369 } 370 } 371 372 impl IsVisible for ConicGradient { 373 fn is_visible(&self) -> bool { 374 true 375 } 376 } 377 378 #[derive(Debug)] 379 #[cfg_attr(feature = "capture", derive(Serialize))] 380 #[cfg_attr(feature = "replay", derive(Deserialize))] 381 pub struct ConicGradientTask { 382 pub extend_mode: ExtendMode, 383 pub center: DevicePoint, 384 pub scale: DeviceVector2D, 385 pub params: ConicGradientParams, 386 pub stops: GpuBufferAddress, 387 } 388 389 impl ConicGradientTask { 390 pub fn to_instance(&self, target_rect: &DeviceIntRect) -> ConicGradientInstance { 391 ConicGradientInstance { 392 task_rect: target_rect.to_f32(), 393 center: self.center, 394 scale: self.scale, 395 start_offset: self.params.start_offset, 396 end_offset: self.params.end_offset, 397 angle: self.params.angle, 398 extend_mode: self.extend_mode as i32, 399 gradient_stops_address: self.stops.as_int(), 400 } 401 } 402 } 403 404 /// The per-instance shader input of a radial gradient render task. 405 /// 406 /// Must match the RADIAL_GRADIENT instance description in renderer/vertex.rs. 407 #[cfg_attr(feature = "capture", derive(Serialize))] 408 #[cfg_attr(feature = "replay", derive(Deserialize))] 409 #[repr(C)] 410 #[derive(Clone, Debug)] 411 pub struct ConicGradientInstance { 412 pub task_rect: DeviceRect, 413 pub center: DevicePoint, 414 pub scale: DeviceVector2D, 415 pub start_offset: f32, 416 pub end_offset: f32, 417 pub angle: f32, 418 pub extend_mode: i32, 419 pub gradient_stops_address: i32, 420 } 421 422 #[derive(Clone, Debug, Hash, PartialEq, Eq)] 423 #[cfg_attr(feature = "capture", derive(Serialize))] 424 #[cfg_attr(feature = "replay", derive(Deserialize))] 425 pub struct ConicGradientCacheKey { 426 pub size: DeviceIntSize, 427 pub center: PointKey, 428 pub scale: PointKey, 429 pub start_offset: FloatKey, 430 pub end_offset: FloatKey, 431 pub angle: FloatKey, 432 pub extend_mode: ExtendMode, 433 pub stops: Vec<GradientStopKey>, 434 } 435 436 pub fn conic_gradient_pattern_with_table( 437 center: DevicePoint, 438 scale: DeviceVector2D, 439 params: &ConicGradientParams, 440 extend_mode: ExtendMode, 441 stops: &[GradientStop], 442 gpu_buffer_builder: &mut GpuBufferBuilder 443 ) -> Pattern { 444 let mut writer = gpu_buffer_builder.f32.write_blocks(2); 445 writer.push_one([ 446 center.x, 447 center.y, 448 scale.x, 449 scale.y, 450 ]); 451 writer.push_one([ 452 params.start_offset, 453 params.end_offset, 454 params.angle, 455 if extend_mode == ExtendMode::Repeat { 1.0 } else { 0.0 } 456 ]); 457 let gradient_address = writer.finish(); 458 459 let stops_address = GradientGpuBlockBuilder::build( 460 false, 461 &mut gpu_buffer_builder.f32, 462 &stops, 463 ); 464 465 let is_opaque = stops.iter().all(|stop| stop.color.a >= 1.0); 466 467 Pattern { 468 kind: PatternKind::ConicGradient, 469 shader_input: PatternShaderInput( 470 gradient_address.as_int(), 471 stops_address.as_int(), 472 ), 473 texture_input: PatternTextureInput::default(), 474 base_color: ColorF::WHITE, 475 is_opaque, 476 } 477 } 478 479 pub fn conic_gradient_pattern( 480 center: DevicePoint, 481 scale: DeviceVector2D, 482 params: &ConicGradientParams, 483 extend_mode: ExtendMode, 484 stops: &[GradientStop], 485 gpu_buffer_builder: &mut GpuBufferBuilder 486 ) -> Pattern { 487 let num_blocks = 2 + gpu_gradient_stops_blocks(stops.len()); 488 let mut writer = gpu_buffer_builder.f32.write_blocks(num_blocks); 489 writer.push_one([ 490 center.x, 491 center.y, 492 scale.x, 493 scale.y, 494 ]); 495 writer.push_one([ 496 params.start_offset, 497 params.end_offset, 498 params.angle, 499 0.0, 500 ]); 501 let is_opaque = write_gpu_gradient_stops_tree(stops, GradientKind::Conic, extend_mode, &mut writer); 502 let gradient_address = writer.finish(); 503 504 Pattern { 505 kind: PatternKind::Gradient, 506 shader_input: PatternShaderInput( 507 gradient_address.as_int(), 508 0, 509 ), 510 texture_input: PatternTextureInput::default(), 511 base_color: ColorF::WHITE, 512 is_opaque, 513 } 514 }