tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }