radial.rs (21894B)
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 //! Radial gradients 6 //! 7 //! Specification: https://drafts.csswg.org/css-images-4/#radial-gradients 8 //! 9 //! Radial gradients are rendered via cached render tasks and composited with the image brush. 10 11 use euclid::{vec2, size2}; 12 use api::{ColorF, ColorU, 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::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, InternablePrimitive, VECS_PER_SEGMENT}; 22 use crate::prim_store::{PrimitiveInstanceKind, PrimitiveOpacity}; 23 use crate::prim_store::{PrimKeyCommonData, PrimTemplateCommonData, PrimitiveStore}; 24 use crate::prim_store::{NinePatchDescriptor, PointKey, SizeKey, FloatKey}; 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::{ 32 stops_and_min_alpha, GradientStopKey, GradientGpuBlockBuilder, 33 apply_gradient_local_clip, gpu_gradient_stops_blocks, 34 write_gpu_gradient_stops_tree, 35 }; 36 37 /// Hashable radial gradient parameters, for use during prim interning. 38 #[cfg_attr(feature = "capture", derive(Serialize))] 39 #[cfg_attr(feature = "replay", derive(Deserialize))] 40 #[derive(Debug, Clone, MallocSizeOf, PartialEq)] 41 pub struct RadialGradientParams { 42 pub start_radius: f32, 43 pub end_radius: f32, 44 pub ratio_xy: f32, 45 } 46 47 impl Eq for RadialGradientParams {} 48 49 impl hash::Hash for RadialGradientParams { 50 fn hash<H: hash::Hasher>(&self, state: &mut H) { 51 self.start_radius.to_bits().hash(state); 52 self.end_radius.to_bits().hash(state); 53 self.ratio_xy.to_bits().hash(state); 54 } 55 } 56 57 /// Identifying key for a radial gradient. 58 #[cfg_attr(feature = "capture", derive(Serialize))] 59 #[cfg_attr(feature = "replay", derive(Deserialize))] 60 #[derive(Debug, Clone, Eq, PartialEq, Hash, MallocSizeOf)] 61 pub struct RadialGradientKey { 62 pub common: PrimKeyCommonData, 63 pub extend_mode: ExtendMode, 64 pub center: PointKey, 65 pub params: RadialGradientParams, 66 pub stretch_size: SizeKey, 67 pub stops: Vec<GradientStopKey>, 68 pub tile_spacing: SizeKey, 69 pub nine_patch: Option<Box<NinePatchDescriptor>>, 70 } 71 72 impl RadialGradientKey { 73 pub fn new( 74 info: &LayoutPrimitiveInfo, 75 radial_grad: RadialGradient, 76 ) -> Self { 77 RadialGradientKey { 78 common: info.into(), 79 extend_mode: radial_grad.extend_mode, 80 center: radial_grad.center, 81 params: radial_grad.params, 82 stretch_size: radial_grad.stretch_size, 83 stops: radial_grad.stops, 84 tile_spacing: radial_grad.tile_spacing, 85 nine_patch: radial_grad.nine_patch, 86 } 87 } 88 } 89 90 impl InternDebug for RadialGradientKey {} 91 92 #[cfg_attr(feature = "capture", derive(Serialize))] 93 #[cfg_attr(feature = "replay", derive(Deserialize))] 94 #[derive(MallocSizeOf)] 95 #[derive(Debug)] 96 pub struct RadialGradientTemplate { 97 pub common: PrimTemplateCommonData, 98 pub extend_mode: ExtendMode, 99 pub params: RadialGradientParams, 100 pub center: DevicePoint, 101 pub task_size: DeviceIntSize, 102 pub scale: DeviceVector2D, 103 pub stretch_size: LayoutSize, 104 pub tile_spacing: LayoutSize, 105 pub brush_segments: Vec<BrushSegment>, 106 pub stops_opacity: PrimitiveOpacity, 107 pub stops: Vec<GradientStop>, 108 pub src_color: Option<RenderTaskId>, 109 } 110 111 impl PatternBuilder for RadialGradientTemplate { 112 fn build( 113 &self, 114 _sub_rect: Option<DeviceRect>, 115 ctx: &PatternBuilderContext, 116 state: &mut PatternBuilderState, 117 ) -> Pattern { 118 // The scaling parameter is used to compensate for when we reduce the size 119 // of the render task for cached gradients. Here we aren't applying any. 120 let no_scale = DeviceVector2D::one(); 121 122 if ctx.fb_config.precise_radial_gradients { 123 radial_gradient_pattern( 124 self.center, 125 no_scale, 126 &self.params, 127 self.extend_mode, 128 &self.stops, 129 ctx.fb_config.is_software, 130 state.frame_gpu_data, 131 ) 132 } else { 133 radial_gradient_pattern_with_table( 134 self.center, 135 no_scale, 136 &self.params, 137 self.extend_mode, 138 &self.stops, 139 state.frame_gpu_data, 140 ) 141 } 142 } 143 144 fn get_base_color( 145 &self, 146 _ctx: &PatternBuilderContext, 147 ) -> ColorF { 148 ColorF::WHITE 149 } 150 151 fn use_shared_pattern( 152 &self, 153 ) -> bool { 154 true 155 } 156 } 157 158 impl Deref for RadialGradientTemplate { 159 type Target = PrimTemplateCommonData; 160 fn deref(&self) -> &Self::Target { 161 &self.common 162 } 163 } 164 165 impl DerefMut for RadialGradientTemplate { 166 fn deref_mut(&mut self) -> &mut Self::Target { 167 &mut self.common 168 } 169 } 170 171 impl From<RadialGradientKey> for RadialGradientTemplate { 172 fn from(item: RadialGradientKey) -> Self { 173 let common = PrimTemplateCommonData::with_key_common(item.common); 174 let mut brush_segments = Vec::new(); 175 176 if let Some(ref nine_patch) = item.nine_patch { 177 brush_segments = nine_patch.create_segments(common.prim_rect.size()); 178 } 179 180 let (stops, min_alpha) = stops_and_min_alpha(&item.stops); 181 182 // Save opacity of the stops for use in 183 // selecting which pass this gradient 184 // should be drawn in. 185 let stops_opacity = PrimitiveOpacity::from_alpha(min_alpha); 186 187 let mut stretch_size: LayoutSize = item.stretch_size.into(); 188 stretch_size.width = stretch_size.width.min(common.prim_rect.width()); 189 stretch_size.height = stretch_size.height.min(common.prim_rect.height()); 190 191 // Avoid rendering enormous gradients. Radial gradients are mostly made of soft transitions, 192 // so it is unlikely that rendering at a higher resolution that 1024 would produce noticeable 193 // differences, especially with 8 bits per channel. 194 const MAX_SIZE: f32 = 1024.0; 195 let mut task_size: DeviceSize = stretch_size.cast_unit(); 196 let mut scale = vec2(1.0, 1.0); 197 if task_size.width > MAX_SIZE { 198 scale.x = task_size.width/ MAX_SIZE; 199 task_size.width = MAX_SIZE; 200 } 201 if task_size.height > MAX_SIZE { 202 scale.y = task_size.height /MAX_SIZE; 203 task_size.height = MAX_SIZE; 204 } 205 206 RadialGradientTemplate { 207 common, 208 center: DevicePoint::new(item.center.x, item.center.y), 209 extend_mode: item.extend_mode, 210 params: item.params, 211 stretch_size, 212 task_size: task_size.ceil().to_i32(), 213 scale, 214 tile_spacing: item.tile_spacing.into(), 215 brush_segments, 216 stops_opacity, 217 stops, 218 src_color: None, 219 } 220 } 221 } 222 223 impl RadialGradientTemplate { 224 /// Update the GPU cache for a given primitive template. This may be called multiple 225 /// times per frame, by each primitive reference that refers to this interned 226 /// template. The initial request call to the GPU cache ensures that work is only 227 /// done if the cache entry is invalid (due to first use or eviction). 228 pub fn update( 229 &mut self, 230 frame_state: &mut FrameBuildingState, 231 ) { 232 let mut writer = frame_state.frame_gpu_data.f32.write_blocks(3 + self.brush_segments.len() * VECS_PER_SEGMENT); 233 234 // write_prim_gpu_blocks 235 writer.push(&ImageBrushPrimitiveData { 236 color: PremultipliedColorF::WHITE, 237 background_color: PremultipliedColorF::WHITE, 238 stretch_size: self.stretch_size, 239 }); 240 241 // write_segment_gpu_blocks 242 for segment in &self.brush_segments { 243 segment.write_gpu_blocks(&mut writer); 244 } 245 self.common.gpu_buffer_address = writer.finish(); 246 247 let task_size = self.task_size; 248 let cache_key = RadialGradientCacheKey { 249 size: task_size, 250 center: PointKey { x: self.center.x, y: self.center.y }, 251 scale: PointKey { x: self.scale.x, y: self.scale.y }, 252 start_radius: FloatKey(self.params.start_radius), 253 end_radius: FloatKey(self.params.end_radius), 254 ratio_xy: FloatKey(self.params.ratio_xy), 255 extend_mode: self.extend_mode, 256 stops: self.stops.iter().map(|stop| (*stop).into()).collect(), 257 }; 258 259 let task_id = frame_state.resource_cache.request_render_task( 260 Some(RenderTaskCacheKey { 261 size: task_size, 262 kind: RenderTaskCacheKeyKind::RadialGradient(cache_key), 263 }), 264 false, 265 RenderTaskParent::Surface, 266 &mut frame_state.frame_gpu_data.f32, 267 frame_state.rg_builder, 268 &mut frame_state.surface_builder, 269 &mut |rg_builder, gpu_buffer_builder| { 270 let stops = GradientGpuBlockBuilder::build( 271 false, 272 gpu_buffer_builder, 273 &self.stops, 274 ); 275 276 rg_builder.add().init(RenderTask::new_dynamic( 277 task_size, 278 RenderTaskKind::RadialGradient(RadialGradientTask { 279 extend_mode: self.extend_mode, 280 center: self.center, 281 scale: self.scale, 282 params: self.params.clone(), 283 stops, 284 }), 285 )) 286 } 287 ); 288 289 self.src_color = Some(task_id); 290 291 // Tile spacing is always handled by decomposing into separate draw calls so the 292 // primitive opacity is equivalent to stops opacity. This might change to being 293 // set to non-opaque in the presence of tile spacing if/when tile spacing is handled 294 // in the same way as with the image primitive. 295 self.opacity = self.stops_opacity; 296 } 297 } 298 299 pub type RadialGradientDataHandle = InternHandle<RadialGradient>; 300 301 #[derive(Debug, MallocSizeOf)] 302 #[cfg_attr(feature = "capture", derive(Serialize))] 303 #[cfg_attr(feature = "replay", derive(Deserialize))] 304 pub struct RadialGradient { 305 pub extend_mode: ExtendMode, 306 pub center: PointKey, 307 pub params: RadialGradientParams, 308 pub stretch_size: SizeKey, 309 pub stops: Vec<GradientStopKey>, 310 pub tile_spacing: SizeKey, 311 pub nine_patch: Option<Box<NinePatchDescriptor>>, 312 } 313 314 impl Internable for RadialGradient { 315 type Key = RadialGradientKey; 316 type StoreData = RadialGradientTemplate; 317 type InternData = (); 318 const PROFILE_COUNTER: usize = crate::profiler::INTERNED_RADIAL_GRADIENTS; 319 } 320 321 impl InternablePrimitive for RadialGradient { 322 fn into_key( 323 self, 324 info: &LayoutPrimitiveInfo, 325 ) -> RadialGradientKey { 326 RadialGradientKey::new(info, self) 327 } 328 329 fn make_instance_kind( 330 _key: RadialGradientKey, 331 data_handle: RadialGradientDataHandle, 332 _prim_store: &mut PrimitiveStore, 333 ) -> PrimitiveInstanceKind { 334 PrimitiveInstanceKind::RadialGradient { 335 data_handle, 336 visible_tiles_range: GradientTileRange::empty(), 337 use_legacy_path: true, 338 } 339 } 340 } 341 342 impl IsVisible for RadialGradient { 343 fn is_visible(&self) -> bool { 344 true 345 } 346 } 347 348 #[derive(Debug)] 349 #[cfg_attr(feature = "capture", derive(Serialize))] 350 #[cfg_attr(feature = "replay", derive(Deserialize))] 351 pub struct RadialGradientTask { 352 pub extend_mode: ExtendMode, 353 pub center: DevicePoint, 354 pub scale: DeviceVector2D, 355 pub params: RadialGradientParams, 356 pub stops: GpuBufferAddress, 357 } 358 359 impl RadialGradientTask { 360 pub fn to_instance(&self, target_rect: &DeviceIntRect) -> RadialGradientInstance { 361 RadialGradientInstance { 362 task_rect: target_rect.to_f32(), 363 center: self.center, 364 scale: self.scale, 365 start_radius: self.params.start_radius, 366 end_radius: self.params.end_radius, 367 ratio_xy: self.params.ratio_xy, 368 extend_mode: self.extend_mode as i32, 369 gradient_stops_address: self.stops.as_int(), 370 } 371 } 372 } 373 374 /// The per-instance shader input of a radial gradient render task. 375 /// 376 /// Must match the RADIAL_GRADIENT instance description in renderer/vertex.rs. 377 #[cfg_attr(feature = "capture", derive(Serialize))] 378 #[cfg_attr(feature = "replay", derive(Deserialize))] 379 #[repr(C)] 380 #[derive(Clone, Debug)] 381 pub struct RadialGradientInstance { 382 pub task_rect: DeviceRect, 383 pub center: DevicePoint, 384 pub scale: DeviceVector2D, 385 pub start_radius: f32, 386 pub end_radius: f32, 387 pub ratio_xy: f32, 388 pub extend_mode: i32, 389 pub gradient_stops_address: i32, 390 } 391 392 #[derive(Clone, Debug, Hash, PartialEq, Eq)] 393 #[cfg_attr(feature = "capture", derive(Serialize))] 394 #[cfg_attr(feature = "replay", derive(Deserialize))] 395 pub struct RadialGradientCacheKey { 396 pub size: DeviceIntSize, 397 pub center: PointKey, 398 pub scale: PointKey, 399 pub start_radius: FloatKey, 400 pub end_radius: FloatKey, 401 pub ratio_xy: FloatKey, 402 pub extend_mode: ExtendMode, 403 pub stops: Vec<GradientStopKey>, 404 } 405 406 /// Avoid invoking the radial gradient shader on large areas where the color is 407 /// constant. 408 /// 409 /// If the extend mode is set to clamp, the "interesting" part 410 /// of the gradient is only in the bounds of the gradient's ellipse, and the rest 411 /// is the color of the last gradient stop. 412 /// 413 /// Sometimes we run into radial gradient with a small radius compared to the 414 /// primitive bounds, which means a large area of the primitive is a constant color 415 /// This function tries to detect that, potentially shrink the gradient primitive to only 416 /// the useful part and if needed insert solid color primitives around the gradient where 417 /// parts of it have been removed. 418 pub fn optimize_radial_gradient( 419 prim_rect: &mut LayoutRect, 420 stretch_size: &mut LayoutSize, 421 center: &mut LayoutPoint, 422 tile_spacing: &mut LayoutSize, 423 clip_rect: &LayoutRect, 424 radius: LayoutSize, 425 end_offset: f32, 426 extend_mode: ExtendMode, 427 stops: &[GradientStopKey], 428 solid_parts: &mut dyn FnMut(&LayoutRect, ColorU), 429 ) { 430 let offset = apply_gradient_local_clip( 431 prim_rect, 432 stretch_size, 433 tile_spacing, 434 clip_rect 435 ); 436 437 *center += offset; 438 439 if extend_mode != ExtendMode::Clamp || stops.is_empty() { 440 return; 441 } 442 443 // Bounding box of the "interesting" part of the gradient. 444 let min = prim_rect.min + center.to_vector() - radius.to_vector() * end_offset; 445 let max = prim_rect.min + center.to_vector() + radius.to_vector() * end_offset; 446 447 // The (non-repeated) gradient primitive rect. 448 let gradient_rect = LayoutRect::from_origin_and_size( 449 prim_rect.min, 450 *stretch_size, 451 ); 452 453 // How much internal margin between the primitive bounds and the gradient's 454 // bounding rect (areas that are a constant color). 455 let mut l = (min.x - gradient_rect.min.x).max(0.0).floor(); 456 let mut t = (min.y - gradient_rect.min.y).max(0.0).floor(); 457 let mut r = (gradient_rect.max.x - max.x).max(0.0).floor(); 458 let mut b = (gradient_rect.max.y - max.y).max(0.0).floor(); 459 460 let is_tiled = prim_rect.width() > stretch_size.width + tile_spacing.width 461 || prim_rect.height() > stretch_size.height + tile_spacing.height; 462 463 let bg_color = stops.last().unwrap().color; 464 465 if bg_color.a != 0 && is_tiled { 466 // If the primitive has repetitions, it's not enough to insert solid rects around it, 467 // so bail out. 468 return; 469 } 470 471 // If the background is fully transparent, shrinking the primitive bounds as much as possible 472 // is always a win. If the background is not transparent, we have to insert solid rectangles 473 // around the shrunk parts. 474 // If the background is transparent and the primitive is tiled, the optimization may introduce 475 // tile spacing which forces the tiling to be manually decomposed. 476 // Either way, don't bother optimizing unless it saves a significant amount of pixels. 477 if bg_color.a != 0 || (is_tiled && tile_spacing.is_empty()) { 478 let threshold = 128.0; 479 if l < threshold { l = 0.0 } 480 if t < threshold { t = 0.0 } 481 if r < threshold { r = 0.0 } 482 if b < threshold { b = 0.0 } 483 } 484 485 if l + t + r + b == 0.0 { 486 // No adjustment to make; 487 return; 488 } 489 490 // Insert solid rectangles around the gradient, in the places where the primitive will be 491 // shrunk. 492 if bg_color.a != 0 { 493 if l != 0.0 && t != 0.0 { 494 let solid_rect = LayoutRect::from_origin_and_size( 495 gradient_rect.min, 496 size2(l, t), 497 ); 498 solid_parts(&solid_rect, bg_color); 499 } 500 501 if l != 0.0 && b != 0.0 { 502 let solid_rect = LayoutRect::from_origin_and_size( 503 gradient_rect.bottom_left() - vec2(0.0, b), 504 size2(l, b), 505 ); 506 solid_parts(&solid_rect, bg_color); 507 } 508 509 if t != 0.0 && r != 0.0 { 510 let solid_rect = LayoutRect::from_origin_and_size( 511 gradient_rect.top_right() - vec2(r, 0.0), 512 size2(r, t), 513 ); 514 solid_parts(&solid_rect, bg_color); 515 } 516 517 if r != 0.0 && b != 0.0 { 518 let solid_rect = LayoutRect::from_origin_and_size( 519 gradient_rect.bottom_right() - vec2(r, b), 520 size2(r, b), 521 ); 522 solid_parts(&solid_rect, bg_color); 523 } 524 525 if l != 0.0 { 526 let solid_rect = LayoutRect::from_origin_and_size( 527 gradient_rect.min + vec2(0.0, t), 528 size2(l, gradient_rect.height() - t - b), 529 ); 530 solid_parts(&solid_rect, bg_color); 531 } 532 533 if r != 0.0 { 534 let solid_rect = LayoutRect::from_origin_and_size( 535 gradient_rect.top_right() + vec2(-r, t), 536 size2(r, gradient_rect.height() - t - b), 537 ); 538 solid_parts(&solid_rect, bg_color); 539 } 540 541 if t != 0.0 { 542 let solid_rect = LayoutRect::from_origin_and_size( 543 gradient_rect.min + vec2(l, 0.0), 544 size2(gradient_rect.width() - l - r, t), 545 ); 546 solid_parts(&solid_rect, bg_color); 547 } 548 549 if b != 0.0 { 550 let solid_rect = LayoutRect::from_origin_and_size( 551 gradient_rect.bottom_left() + vec2(l, -b), 552 size2(gradient_rect.width() - l - r, b), 553 ); 554 solid_parts(&solid_rect, bg_color); 555 } 556 } 557 558 // Shrink the gradient primitive. 559 560 prim_rect.min.x += l; 561 prim_rect.min.y += t; 562 563 stretch_size.width -= l + r; 564 stretch_size.height -= b + t; 565 566 center.x -= l; 567 center.y -= t; 568 569 tile_spacing.width += l + r; 570 tile_spacing.height += t + b; 571 } 572 573 pub fn radial_gradient_pattern_with_table( 574 center: DevicePoint, 575 scale: DeviceVector2D, 576 params: &RadialGradientParams, 577 extend_mode: ExtendMode, 578 stops: &[GradientStop], 579 gpu_buffer_builder: &mut GpuBufferBuilder 580 ) -> Pattern { 581 let mut writer = gpu_buffer_builder.f32.write_blocks(2); 582 writer.push_one([ 583 center.x, 584 center.y, 585 scale.x, 586 scale.y, 587 ]); 588 writer.push_one([ 589 params.start_radius, 590 params.end_radius, 591 params.ratio_xy, 592 if extend_mode == ExtendMode::Repeat { 1.0 } else { 0.0 } 593 ]); 594 let gradient_address = writer.finish(); 595 596 let stops_address = GradientGpuBlockBuilder::build( 597 false, 598 &mut gpu_buffer_builder.f32, 599 &stops, 600 ); 601 602 let is_opaque = stops.iter().all(|stop| stop.color.a >= 1.0); 603 604 Pattern { 605 kind: PatternKind::RadialGradient, 606 shader_input: PatternShaderInput( 607 gradient_address.as_int(), 608 stops_address.as_int(), 609 ), 610 texture_input: PatternTextureInput::default(), 611 base_color: ColorF::WHITE, 612 is_opaque, 613 } 614 } 615 616 pub fn radial_gradient_pattern( 617 center: DevicePoint, 618 scale: DeviceVector2D, 619 params: &RadialGradientParams, 620 extend_mode: ExtendMode, 621 stops: &[GradientStop], 622 _is_software: bool, 623 gpu_buffer_builder: &mut GpuBufferBuilder 624 ) -> Pattern { 625 let num_blocks = 2 + gpu_gradient_stops_blocks(stops.len()); 626 let mut writer = gpu_buffer_builder.f32.write_blocks(num_blocks); 627 writer.push_one([ 628 center.x, 629 center.y, 630 scale.x, 631 scale.y, 632 ]); 633 writer.push_one([ 634 params.start_radius, 635 params.end_radius, 636 params.ratio_xy, 637 0.0, 638 ]); 639 640 let is_opaque = write_gpu_gradient_stops_tree(stops, GradientKind::Radial, extend_mode, &mut writer); 641 642 let gradient_address = writer.finish(); 643 644 Pattern { 645 kind: PatternKind::Gradient, 646 shader_input: PatternShaderInput( 647 gradient_address.as_int(), 648 0, 649 ), 650 texture_input: PatternTextureInput::default(), 651 base_color: ColorF::WHITE, 652 is_opaque, 653 } 654 }