linear.rs (27546B)
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 //! Linear gradients 6 //! 7 //! Specification: https://drafts.csswg.org/css-images-4/#linear-gradients 8 //! 9 //! Linear gradients are rendered via cached render tasks and composited with the image brush. 10 11 use euclid::approxeq::ApproxEq; 12 use euclid::{point2, vec2, size2}; 13 use api::{ExtendMode, GradientStop, LineOrientation, PremultipliedColorF, ColorF, ColorU}; 14 use api::units::*; 15 use crate::gpu_types::{ImageBrushPrimitiveData, LinearGradientBrushData}; 16 use crate::pattern::{Pattern, PatternBuilder, PatternBuilderContext, PatternBuilderState, PatternKind, PatternShaderInput, PatternTextureInput}; 17 use crate::prim_store::gradient::{gpu_gradient_stops_blocks, write_gpu_gradient_stops_tree, GradientKind}; 18 use crate::scene_building::IsVisible; 19 use crate::frame_builder::FrameBuildingState; 20 use crate::intern::{Internable, InternDebug, Handle as InternHandle}; 21 use crate::internal_types::LayoutPrimitiveInfo; 22 use crate::image_tiling::simplify_repeated_primitive; 23 use crate::prim_store::{BrushSegment, GradientTileRange, VECS_PER_SEGMENT}; 24 use crate::prim_store::{PrimitiveInstanceKind, PrimitiveOpacity}; 25 use crate::prim_store::{PrimKeyCommonData, PrimTemplateCommonData, PrimitiveStore}; 26 use crate::prim_store::{NinePatchDescriptor, PointKey, SizeKey, InternablePrimitive}; 27 use crate::render_task::{RenderTask, RenderTaskKind}; 28 use crate::render_task_graph::RenderTaskId; 29 use crate::render_task_cache::{RenderTaskCacheKeyKind, RenderTaskCacheKey, RenderTaskParent}; 30 use crate::renderer::{GpuBufferAddress, GpuBufferBuilder}; 31 use crate::segment::EdgeAaSegmentMask; 32 use super::{stops_and_min_alpha, GradientStopKey, GradientGpuBlockBuilder, apply_gradient_local_clip}; 33 use std::ops::{Deref, DerefMut}; 34 use std::mem::swap; 35 36 pub const MAX_CACHED_SIZE: f32 = 1024.0; 37 38 /// Identifying key for a linear gradient. 39 #[cfg_attr(feature = "capture", derive(Serialize))] 40 #[cfg_attr(feature = "replay", derive(Deserialize))] 41 #[derive(Debug, Clone, Eq, PartialEq, Hash, MallocSizeOf)] 42 pub struct LinearGradientKey { 43 pub common: PrimKeyCommonData, 44 pub extend_mode: ExtendMode, 45 pub start_point: PointKey, 46 pub end_point: PointKey, 47 pub stretch_size: SizeKey, 48 pub tile_spacing: SizeKey, 49 pub stops: Vec<GradientStopKey>, 50 pub reverse_stops: bool, 51 pub cached: bool, 52 pub nine_patch: Option<Box<NinePatchDescriptor>>, 53 pub edge_aa_mask: EdgeAaSegmentMask, 54 pub enable_dithering: bool, 55 } 56 57 impl LinearGradientKey { 58 pub fn new( 59 info: &LayoutPrimitiveInfo, 60 linear_grad: LinearGradient, 61 ) -> Self { 62 LinearGradientKey { 63 common: info.into(), 64 extend_mode: linear_grad.extend_mode, 65 start_point: linear_grad.start_point, 66 end_point: linear_grad.end_point, 67 stretch_size: linear_grad.stretch_size, 68 tile_spacing: linear_grad.tile_spacing, 69 stops: linear_grad.stops, 70 reverse_stops: linear_grad.reverse_stops, 71 cached: linear_grad.cached, 72 nine_patch: linear_grad.nine_patch, 73 edge_aa_mask: linear_grad.edge_aa_mask, 74 enable_dithering: linear_grad.enable_dithering, 75 } 76 } 77 } 78 79 impl InternDebug for LinearGradientKey {} 80 81 #[cfg_attr(feature = "capture", derive(Serialize))] 82 #[cfg_attr(feature = "replay", derive(Deserialize))] 83 #[derive(Debug, MallocSizeOf)] 84 pub struct LinearGradientTemplate { 85 pub common: PrimTemplateCommonData, 86 pub extend_mode: ExtendMode, 87 pub start_point: LayoutPoint, 88 pub end_point: LayoutPoint, 89 pub task_size: DeviceIntSize, 90 pub scale: DeviceVector2D, 91 pub stretch_size: LayoutSize, 92 pub tile_spacing: LayoutSize, 93 pub stops_opacity: PrimitiveOpacity, 94 pub stops: Vec<GradientStop>, 95 pub brush_segments: Vec<BrushSegment>, 96 pub reverse_stops: bool, 97 pub is_fast_path: bool, 98 pub cached: bool, 99 pub src_color: Option<RenderTaskId>, 100 } 101 102 impl PatternBuilder for LinearGradientTemplate { 103 fn build( 104 &self, 105 _sub_rect: Option<DeviceRect>, 106 ctx: &PatternBuilderContext, 107 state: &mut PatternBuilderState, 108 ) -> Pattern { 109 let (start, end) = if self.reverse_stops { 110 (self.end_point, self.start_point) 111 } else { 112 (self.start_point, self.end_point) 113 }; 114 115 linear_gradient_pattern( 116 start, 117 end, 118 self.extend_mode, 119 &self.stops, 120 ctx.fb_config.is_software, 121 state.frame_gpu_data, 122 ) 123 } 124 125 fn get_base_color( 126 &self, 127 _ctx: &PatternBuilderContext, 128 ) -> ColorF { 129 ColorF::WHITE 130 } 131 132 fn use_shared_pattern( 133 &self, 134 ) -> bool { 135 true 136 } 137 } 138 139 impl Deref for LinearGradientTemplate { 140 type Target = PrimTemplateCommonData; 141 fn deref(&self) -> &Self::Target { 142 &self.common 143 } 144 } 145 146 impl DerefMut for LinearGradientTemplate { 147 fn deref_mut(&mut self) -> &mut Self::Target { 148 &mut self.common 149 } 150 } 151 152 /// Perform a few optimizations to the gradient that are relevant to scene building. 153 /// 154 /// Returns true if the gradient was decomposed into fast-path primitives, indicating 155 /// that we shouldn't emit a regular gradient primitive after this returns. 156 pub fn optimize_linear_gradient( 157 prim_rect: &mut LayoutRect, 158 tile_size: &mut LayoutSize, 159 mut tile_spacing: LayoutSize, 160 clip_rect: &LayoutRect, 161 start: &mut LayoutPoint, 162 end: &mut LayoutPoint, 163 extend_mode: ExtendMode, 164 stops: &mut [GradientStopKey], 165 enable_dithering: bool, 166 // Callback called for each fast-path segment (rect, start end, stops). 167 callback: &mut dyn FnMut(&LayoutRect, LayoutPoint, LayoutPoint, &[GradientStopKey], EdgeAaSegmentMask) 168 ) -> bool { 169 // First sanitize the gradient parameters. See if we can remove repetitions, 170 // tighten the primitive bounds, etc. 171 172 simplify_repeated_primitive(&tile_size, &mut tile_spacing, prim_rect); 173 174 let vertical = start.x.approx_eq(&end.x); 175 let horizontal = start.y.approx_eq(&end.y); 176 177 let mut horizontally_tiled = prim_rect.width() > tile_size.width; 178 let mut vertically_tiled = prim_rect.height() > tile_size.height; 179 180 // Check whether the tiling is equivalent to stretching on either axis. 181 // Stretching the gradient is more efficient than repeating it. 182 if vertically_tiled && horizontal && tile_spacing.height == 0.0 { 183 tile_size.height = prim_rect.height(); 184 vertically_tiled = false; 185 } 186 187 if horizontally_tiled && vertical && tile_spacing.width == 0.0 { 188 tile_size.width = prim_rect.width(); 189 horizontally_tiled = false; 190 } 191 192 let offset = apply_gradient_local_clip( 193 prim_rect, 194 &tile_size, 195 &tile_spacing, 196 &clip_rect 197 ); 198 199 // The size of gradient render tasks depends on the tile_size. No need to generate 200 // large stretch sizes that will be clipped to the bounds of the primitive. 201 tile_size.width = tile_size.width.min(prim_rect.width()); 202 tile_size.height = tile_size.height.min(prim_rect.height()); 203 204 *start += offset; 205 *end += offset; 206 207 // Next, in the case of axis-aligned gradients, see if it is worth 208 // decomposing the gradient into multiple gradients with only two 209 // gradient stops per segment to get a faster shader. 210 211 if extend_mode != ExtendMode::Clamp || stops.is_empty() { 212 return false; 213 } 214 215 if !vertical && !horizontal { 216 return false; 217 } 218 219 if vertical && horizontal { 220 return false; 221 } 222 223 if !tile_spacing.is_empty() || vertically_tiled || horizontally_tiled { 224 return false; 225 } 226 227 // If the gradient is small, no need to bother with decomposing it. 228 if !enable_dithering && 229 ((horizontal && tile_size.width < 256.0) 230 || (vertical && tile_size.height < 256.0)) { 231 return false; 232 } 233 234 // Flip x and y if need be so that we only deal with the horizontal case. 235 236 // From now on don't return false. We are going modifying the caller's 237 // variables and not bother to restore them. If the control flow changes, 238 // Make sure to to restore &mut parameters to sensible values before 239 // returning false. 240 241 let adjust_rect = &mut |rect: &mut LayoutRect| { 242 if vertical { 243 swap(&mut rect.min.x, &mut rect.min.y); 244 swap(&mut rect.max.x, &mut rect.max.y); 245 } 246 }; 247 248 let adjust_size = &mut |size: &mut LayoutSize| { 249 if vertical { swap(&mut size.width, &mut size.height); } 250 }; 251 252 let adjust_point = &mut |p: &mut LayoutPoint| { 253 if vertical { swap(&mut p.x, &mut p.y); } 254 }; 255 256 let clip_rect = match clip_rect.intersection(prim_rect) { 257 Some(clip) => clip, 258 None => { 259 return false; 260 } 261 }; 262 263 adjust_rect(prim_rect); 264 adjust_point(start); 265 adjust_point(end); 266 adjust_size(tile_size); 267 268 let length = (end.x - start.x).abs(); 269 270 // Decompose the gradient into simple segments. This lets us: 271 // - separate opaque from semi-transparent segments, 272 // - compress long segments into small render tasks, 273 // - make sure hard stops stay so even if the primitive is large. 274 275 let reverse_stops = start.x > end.x; 276 277 // Handle reverse stops so we can assume stops are arranged in increasing x. 278 if reverse_stops { 279 stops.reverse(); 280 swap(start, end); 281 } 282 283 // Use fake gradient stop to emulate the potential constant color sections 284 // before and after the gradient endpoints. 285 let mut prev = *stops.first().unwrap(); 286 let mut last = *stops.last().unwrap(); 287 288 // Set the offsets of the fake stops to position them at the edges of the primitive. 289 prev.offset = -start.x / length; 290 last.offset = (tile_size.width - start.x) / length; 291 if reverse_stops { 292 prev.offset = 1.0 - prev.offset; 293 last.offset = 1.0 - last.offset; 294 } 295 296 let (side_edges, first_edge, last_edge) = if vertical { 297 ( 298 EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::RIGHT, 299 EdgeAaSegmentMask::TOP, 300 EdgeAaSegmentMask::BOTTOM 301 ) 302 } else { 303 ( 304 EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::BOTTOM, 305 EdgeAaSegmentMask::LEFT, 306 EdgeAaSegmentMask::RIGHT 307 ) 308 }; 309 310 let mut is_first = true; 311 let last_offset = last.offset; 312 for stop in stops.iter().chain((&[last]).iter()) { 313 let prev_stop = prev; 314 prev = *stop; 315 316 if prev_stop.color.a == 0 && stop.color.a == 0 { 317 continue; 318 } 319 320 321 let prev_offset = if reverse_stops { 1.0 - prev_stop.offset } else { prev_stop.offset }; 322 let offset = if reverse_stops { 1.0 - stop.offset } else { stop.offset }; 323 324 // In layout space, relative to the primitive. 325 let segment_start = start.x + prev_offset * length; 326 let segment_end = start.x + offset * length; 327 let segment_length = segment_end - segment_start; 328 329 if segment_length <= 0.0 { 330 continue; 331 } 332 333 let mut segment_rect = *prim_rect; 334 segment_rect.min.x += segment_start; 335 segment_rect.max.x = segment_rect.min.x + segment_length; 336 337 let mut start = point2(0.0, 0.0); 338 let mut end = point2(segment_length, 0.0); 339 340 adjust_point(&mut start); 341 adjust_point(&mut end); 342 adjust_rect(&mut segment_rect); 343 344 let origin_before_clip = segment_rect.min; 345 segment_rect = match segment_rect.intersection(&clip_rect) { 346 Some(rect) => rect, 347 None => { 348 continue; 349 } 350 }; 351 let offset = segment_rect.min - origin_before_clip; 352 353 // Account for the clipping since start and end are relative to the origin. 354 start -= offset; 355 end -= offset; 356 357 let mut edge_flags = side_edges; 358 if is_first { 359 edge_flags |= first_edge; 360 is_first = false; 361 } 362 if stop.offset == last_offset { 363 edge_flags |= last_edge; 364 } 365 366 callback( 367 &segment_rect, 368 start, 369 end, 370 &[ 371 GradientStopKey { offset: 0.0, .. prev_stop }, 372 GradientStopKey { offset: 1.0, .. *stop }, 373 ], 374 edge_flags, 375 ); 376 } 377 378 true 379 } 380 381 impl From<LinearGradientKey> for LinearGradientTemplate { 382 fn from(item: LinearGradientKey) -> Self { 383 384 let mut common = PrimTemplateCommonData::with_key_common(item.common); 385 common.edge_aa_mask = item.edge_aa_mask; 386 387 let (mut stops, min_alpha) = stops_and_min_alpha(&item.stops); 388 389 let mut brush_segments = Vec::new(); 390 391 if let Some(ref nine_patch) = item.nine_patch { 392 brush_segments = nine_patch.create_segments(common.prim_rect.size()); 393 } 394 395 // Save opacity of the stops for use in 396 // selecting which pass this gradient 397 // should be drawn in. 398 let stops_opacity = PrimitiveOpacity::from_alpha(min_alpha); 399 400 let start_point = LayoutPoint::new(item.start_point.x, item.start_point.y); 401 let end_point = LayoutPoint::new(item.end_point.x, item.end_point.y); 402 let tile_spacing: LayoutSize = item.tile_spacing.into(); 403 let stretch_size: LayoutSize = item.stretch_size.into(); 404 let mut task_size: DeviceSize = stretch_size.cast_unit(); 405 406 let horizontal = !item.enable_dithering && 407 start_point.y.approx_eq(&end_point.y); 408 let vertical = !item.enable_dithering && 409 start_point.x.approx_eq(&end_point.x); 410 411 if horizontal { 412 // Completely horizontal, we can stretch the gradient vertically. 413 task_size.height = 1.0; 414 } 415 416 if vertical { 417 // Completely vertical, we can stretch the gradient horizontally. 418 task_size.width = 1.0; 419 } 420 421 // See if we can render the gradient using a special fast-path shader. 422 // The fast path path only works with two gradient stops. 423 let mut is_fast_path = false; 424 if item.cached && stops.len() == 2 && brush_segments.is_empty() { 425 if horizontal 426 && stretch_size.width >= common.prim_rect.width() 427 && start_point.x.approx_eq(&0.0) 428 && end_point.x.approx_eq(&stretch_size.width) { 429 is_fast_path = true; 430 task_size.width = task_size.width.min(256.0); 431 } 432 if vertical 433 && stretch_size.height >= common.prim_rect.height() 434 && start_point.y.approx_eq(&0.0) 435 && end_point.y.approx_eq(&stretch_size.height) { 436 is_fast_path = true; 437 task_size.height = task_size.height.min(256.0); 438 } 439 440 if stops[0].color == stops[1].color { 441 is_fast_path = true; 442 task_size = size2(1.0, 1.0); 443 } 444 445 if is_fast_path && item.reverse_stops { 446 // The fast path doesn't use the gradient gpu blocks builder so handle 447 // reversed stops here. 448 stops.swap(0, 1); 449 } 450 } 451 452 // Avoid rendering enormous gradients. Linear gradients are mostly made of soft transitions, 453 // so it is unlikely that rendering at a higher resolution than 1024 would produce noticeable 454 // differences, especially with 8 bits per channel. 455 456 let mut scale = vec2(1.0, 1.0); 457 458 if task_size.width > MAX_CACHED_SIZE { 459 scale.x = task_size.width / MAX_CACHED_SIZE; 460 task_size.width = MAX_CACHED_SIZE; 461 } 462 463 if task_size.height > MAX_CACHED_SIZE { 464 scale.y = task_size.height / MAX_CACHED_SIZE; 465 task_size.height = MAX_CACHED_SIZE; 466 } 467 468 LinearGradientTemplate { 469 common, 470 extend_mode: item.extend_mode, 471 start_point, 472 end_point, 473 task_size: task_size.ceil().to_i32(), 474 scale, 475 stretch_size, 476 tile_spacing, 477 stops_opacity, 478 stops, 479 brush_segments, 480 reverse_stops: item.reverse_stops, 481 is_fast_path, 482 cached: item.cached, 483 src_color: None, 484 } 485 } 486 } 487 488 impl LinearGradientTemplate { 489 /// Update the GPU cache for a given primitive template. This may be called multiple 490 /// times per frame, by each primitive reference that refers to this interned 491 /// template. The initial request call to the GPU cache ensures that work is only 492 /// done if the cache entry is invalid (due to first use or eviction). 493 pub fn update( 494 &mut self, 495 frame_state: &mut FrameBuildingState, 496 ) { 497 let mut writer = frame_state.frame_gpu_data.f32.write_blocks(3 + self.brush_segments.len() * VECS_PER_SEGMENT); 498 499 // Write_prim_gpu_blocks 500 if self.cached { 501 writer.push(&ImageBrushPrimitiveData { 502 color: PremultipliedColorF::WHITE, 503 background_color: PremultipliedColorF::WHITE, 504 stretch_size: self.stretch_size, 505 }); 506 } else { 507 // We are using the gradient brush. 508 writer.push(&LinearGradientBrushData { 509 start: self.start_point, 510 end: self.end_point, 511 extend_mode: self.extend_mode, 512 stretch_size: self.stretch_size, 513 }); 514 } 515 516 // write_segment_gpu_blocks 517 for segment in &self.brush_segments { 518 segment.write_gpu_blocks(&mut writer); 519 } 520 521 self.common.gpu_buffer_address = writer.finish(); 522 523 // Tile spacing is always handled by decomposing into separate draw calls so the 524 // primitive opacity is equivalent to stops opacity. This might change to being 525 // set to non-opaque in the presence of tile spacing if/when tile spacing is handled 526 // in the same way as with the image primitive. 527 self.opacity = self.stops_opacity; 528 529 if !self.cached { 530 return; 531 } 532 533 let task_id = if self.is_fast_path { 534 let orientation = if self.task_size.width > self.task_size.height { 535 LineOrientation::Horizontal 536 } else { 537 LineOrientation::Vertical 538 }; 539 540 let gradient = FastLinearGradientTask { 541 color0: self.stops[0].color.into(), 542 color1: self.stops[1].color.into(), 543 orientation, 544 }; 545 546 frame_state.resource_cache.request_render_task( 547 Some(RenderTaskCacheKey { 548 size: self.task_size, 549 kind: RenderTaskCacheKeyKind::FastLinearGradient(gradient), 550 }), 551 false, 552 RenderTaskParent::Surface, 553 &mut frame_state.frame_gpu_data.f32, 554 frame_state.rg_builder, 555 &mut frame_state.surface_builder, 556 &mut |rg_builder, _| { 557 rg_builder.add().init(RenderTask::new_dynamic( 558 self.task_size, 559 RenderTaskKind::FastLinearGradient(gradient), 560 )) 561 } 562 ) 563 } else { 564 let cache_key = LinearGradientCacheKey { 565 size: self.task_size, 566 start: PointKey { x: self.start_point.x, y: self.start_point.y }, 567 end: PointKey { x: self.end_point.x, y: self.end_point.y }, 568 scale: PointKey { x: self.scale.x, y: self.scale.y }, 569 extend_mode: self.extend_mode, 570 stops: self.stops.iter().map(|stop| (*stop).into()).collect(), 571 reversed_stops: self.reverse_stops, 572 }; 573 574 frame_state.resource_cache.request_render_task( 575 Some(RenderTaskCacheKey { 576 size: self.task_size, 577 kind: RenderTaskCacheKeyKind::LinearGradient(cache_key), 578 }), 579 false, 580 RenderTaskParent::Surface, 581 &mut frame_state.frame_gpu_data.f32, 582 frame_state.rg_builder, 583 &mut frame_state.surface_builder, 584 &mut |rg_builder, gpu_buffer_builder| { 585 let stops = Some(GradientGpuBlockBuilder::build( 586 self.reverse_stops, 587 gpu_buffer_builder, 588 &self.stops, 589 )); 590 591 rg_builder.add().init(RenderTask::new_dynamic( 592 self.task_size, 593 RenderTaskKind::LinearGradient(LinearGradientTask { 594 // Cached brush gradients are rasteried with 1 layout 595 // pixel = 1 device pixel (regardless of potential 596 // scaling factors). 597 start: self.start_point.cast_unit(), 598 end: self.end_point.cast_unit(), 599 scale: self.scale, 600 extend_mode: self.extend_mode, 601 stops: stops.unwrap(), 602 }), 603 )) 604 } 605 ) 606 }; 607 608 self.src_color = Some(task_id); 609 } 610 } 611 612 pub type LinearGradientDataHandle = InternHandle<LinearGradient>; 613 614 #[derive(Debug, MallocSizeOf)] 615 #[cfg_attr(feature = "capture", derive(Serialize))] 616 #[cfg_attr(feature = "replay", derive(Deserialize))] 617 pub struct LinearGradient { 618 pub extend_mode: ExtendMode, 619 pub start_point: PointKey, 620 pub end_point: PointKey, 621 pub stretch_size: SizeKey, 622 pub tile_spacing: SizeKey, 623 pub stops: Vec<GradientStopKey>, 624 pub reverse_stops: bool, 625 pub nine_patch: Option<Box<NinePatchDescriptor>>, 626 pub cached: bool, 627 pub edge_aa_mask: EdgeAaSegmentMask, 628 pub enable_dithering: bool, 629 } 630 631 impl Internable for LinearGradient { 632 type Key = LinearGradientKey; 633 type StoreData = LinearGradientTemplate; 634 type InternData = (); 635 const PROFILE_COUNTER: usize = crate::profiler::INTERNED_LINEAR_GRADIENTS; 636 } 637 638 impl InternablePrimitive for LinearGradient { 639 fn into_key( 640 self, 641 info: &LayoutPrimitiveInfo, 642 ) -> LinearGradientKey { 643 LinearGradientKey::new(info, self) 644 } 645 646 fn make_instance_kind( 647 key: LinearGradientKey, 648 data_handle: LinearGradientDataHandle, 649 _prim_store: &mut PrimitiveStore, 650 ) -> PrimitiveInstanceKind { 651 if key.cached { 652 PrimitiveInstanceKind::CachedLinearGradient { 653 data_handle, 654 visible_tiles_range: GradientTileRange::empty(), 655 } 656 } else { 657 PrimitiveInstanceKind::LinearGradient { 658 data_handle, 659 visible_tiles_range: GradientTileRange::empty(), 660 use_legacy_path: true, 661 } 662 } 663 } 664 } 665 666 impl IsVisible for LinearGradient { 667 fn is_visible(&self) -> bool { 668 true 669 } 670 } 671 672 #[derive(Debug)] 673 #[cfg_attr(feature = "capture", derive(Serialize))] 674 pub struct LinearGradientPrimitive { 675 pub cache_segments: Vec<CachedGradientSegment>, 676 pub visible_tiles_range: GradientTileRange, 677 } 678 679 #[derive(Debug)] 680 #[cfg_attr(feature = "capture", derive(Serialize))] 681 pub struct CachedGradientSegment { 682 pub render_task: RenderTaskId, 683 pub local_rect: LayoutRect, 684 } 685 686 687 #[derive(Copy, Clone, Debug, Hash, MallocSizeOf, PartialEq, Eq)] 688 #[cfg_attr(feature = "capture", derive(Serialize))] 689 #[cfg_attr(feature = "replay", derive(Deserialize))] 690 pub struct FastLinearGradientTask { 691 pub color0: ColorU, 692 pub color1: ColorU, 693 pub orientation: LineOrientation, 694 } 695 696 impl FastLinearGradientTask { 697 pub fn to_instance(&self, target_rect: &DeviceIntRect) -> FastLinearGradientInstance { 698 FastLinearGradientInstance { 699 task_rect: target_rect.to_f32(), 700 color0: ColorF::from(self.color0).premultiplied(), 701 color1: ColorF::from(self.color1).premultiplied(), 702 axis_select: match self.orientation { 703 LineOrientation::Horizontal => 0.0, 704 LineOrientation::Vertical => 1.0, 705 }, 706 } 707 } 708 } 709 710 pub type FastLinearGradientCacheKey = FastLinearGradientTask; 711 712 /// The per-instance shader input of a fast-path linear gradient render task. 713 /// 714 /// Must match the FAST_LINEAR_GRADIENT instance description in renderer/vertex.rs. 715 #[cfg_attr(feature = "capture", derive(Serialize))] 716 #[cfg_attr(feature = "replay", derive(Deserialize))] 717 #[repr(C)] 718 #[derive(Clone, Debug)] 719 pub struct FastLinearGradientInstance { 720 pub task_rect: DeviceRect, 721 pub color0: PremultipliedColorF, 722 pub color1: PremultipliedColorF, 723 pub axis_select: f32, 724 } 725 726 #[derive(Debug)] 727 #[cfg_attr(feature = "capture", derive(Serialize))] 728 #[cfg_attr(feature = "replay", derive(Deserialize))] 729 pub struct LinearGradientTask { 730 pub start: DevicePoint, 731 pub end: DevicePoint, 732 pub scale: DeviceVector2D, 733 pub extend_mode: ExtendMode, 734 pub stops: GpuBufferAddress, 735 } 736 737 impl LinearGradientTask { 738 pub fn to_instance(&self, target_rect: &DeviceIntRect) -> LinearGradientInstance { 739 LinearGradientInstance { 740 task_rect: target_rect.to_f32(), 741 start: self.start, 742 end: self.end, 743 scale: self.scale, 744 extend_mode: self.extend_mode as i32, 745 gradient_stops_address: self.stops.as_int(), 746 } 747 } 748 } 749 750 /// The per-instance shader input of a linear gradient render task. 751 /// 752 /// Must match the LINEAR_GRADIENT instance description in renderer/vertex.rs. 753 #[cfg_attr(feature = "capture", derive(Serialize))] 754 #[cfg_attr(feature = "replay", derive(Deserialize))] 755 #[repr(C)] 756 #[derive(Clone, Debug)] 757 pub struct LinearGradientInstance { 758 pub task_rect: DeviceRect, 759 pub start: DevicePoint, 760 pub end: DevicePoint, 761 pub scale: DeviceVector2D, 762 pub extend_mode: i32, 763 pub gradient_stops_address: i32, 764 } 765 766 #[derive(Clone, Debug, Hash, PartialEq, Eq)] 767 #[cfg_attr(feature = "capture", derive(Serialize))] 768 #[cfg_attr(feature = "replay", derive(Deserialize))] 769 pub struct LinearGradientCacheKey { 770 pub size: DeviceIntSize, 771 pub start: PointKey, 772 pub end: PointKey, 773 pub scale: PointKey, 774 pub extend_mode: ExtendMode, 775 pub stops: Vec<GradientStopKey>, 776 pub reversed_stops: bool, 777 } 778 779 pub fn linear_gradient_pattern( 780 start: LayoutPoint, 781 end: LayoutPoint, 782 extend_mode: ExtendMode, 783 stops: &[GradientStop], 784 _is_software: bool, 785 gpu_buffer_builder: &mut GpuBufferBuilder 786 ) -> Pattern { 787 let num_blocks = 2 + gpu_gradient_stops_blocks(stops.len()); 788 let mut writer = gpu_buffer_builder.f32.write_blocks(num_blocks); 789 writer.push_one([ 790 start.x, 791 start.y, 792 end.x, 793 end.y, 794 ]); 795 writer.push_one([ 796 0.0, 797 0.0, 798 0.0, 799 0.0, 800 ]); 801 802 let is_opaque = write_gpu_gradient_stops_tree(stops, GradientKind::Linear, extend_mode, &mut writer); 803 804 let gradient_address = writer.finish(); 805 806 Pattern { 807 kind: PatternKind::Gradient, 808 shader_input: PatternShaderInput( 809 gradient_address.as_int(), 810 0, 811 ), 812 texture_input: PatternTextureInput::default(), 813 base_color: ColorF::WHITE, 814 is_opaque, 815 } 816 }