webrender 0.69.0

A GPU accelerated 2D renderer for web content
Documentation
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use api::units::*;
use api::{ColorF, ExtendMode, GradientStop};
use crate::pattern::{Pattern, PatternKind, PatternShaderInput, PatternTextureInput};
use crate::renderer::{BlendMode, GpuBufferBuilder, GpuBufferWriterF};

#[repr(u8)]
#[derive(Copy, Clone, Debug)]
pub enum GradientKind {
    Linear = 0,
    Radial = 1,
    Conic = 2,
}

pub fn linear_gradient_pattern(
    start: LayoutPoint,
    end: LayoutPoint,
    extend_mode: ExtendMode,
    stops: &[GradientStop],
    _is_software: bool,
    gpu_buffer_builder: &mut GpuBufferBuilder
) -> Pattern {
    let num_blocks = 2 + gpu_gradient_stops_blocks(stops.len());
    let mut writer = gpu_buffer_builder.f32.write_blocks(num_blocks);
    writer.push_one([
        start.x,
        start.y,
        end.x,
        end.y,
    ]);
    writer.push_one([
        0.0,
        0.0,
        0.0,
        0.0,
    ]);

    let is_opaque = write_gpu_gradient_stops_tree(stops, GradientKind::Linear, extend_mode, &mut writer);

    let gradient_address = writer.finish();

    Pattern {
        kind: PatternKind::Gradient,
        shader_input: PatternShaderInput(
            gradient_address.as_int(),
            0,
        ),
        texture_input: PatternTextureInput::default(),
        base_color: ColorF::WHITE,
        is_opaque,
        blend_mode: BlendMode::PremultipliedAlpha,
    }
}

pub fn radial_gradient_pattern(
    center: LayoutPoint,
    scale: DeviceVector2D,
    start_radius: f32,
    end_radius: f32,
    ratio_xy: f32,
    extend_mode: ExtendMode,
    stops: &[GradientStop],
    _is_software: bool,
    gpu_buffer_builder: &mut GpuBufferBuilder
) -> Pattern {
    let num_blocks = 2 + gpu_gradient_stops_blocks(stops.len());
    let mut writer = gpu_buffer_builder.f32.write_blocks(num_blocks);
    writer.push_one([
        center.x,
        center.y,
        scale.x,
        scale.y,
    ]);
    writer.push_one([
        start_radius,
        end_radius,
        ratio_xy,
        0.0,
    ]);

    let is_opaque = write_gpu_gradient_stops_tree(stops, GradientKind::Radial, extend_mode, &mut writer);

    let gradient_address = writer.finish();

    Pattern {
        kind: PatternKind::Gradient,
        shader_input: PatternShaderInput(
            gradient_address.as_int(),
            0,
        ),
        texture_input: PatternTextureInput::default(),
        base_color: ColorF::WHITE,
        is_opaque,
        blend_mode: BlendMode::PremultipliedAlpha,
    }
}

pub fn conic_gradient_pattern(
    center: LayoutPoint,
    scale: DeviceVector2D,
    angle: f32, // in radians
    start_offset: f32,
    end_offset: f32,
    extend_mode: ExtendMode,
    stops: &[GradientStop],
    gpu_buffer_builder: &mut GpuBufferBuilder
) -> Pattern {
    let num_blocks = 2 + gpu_gradient_stops_blocks(stops.len());
    let mut writer = gpu_buffer_builder.f32.write_blocks(num_blocks);
    writer.push_one([
        center.x,
        center.y,
        scale.x,
        scale.y,
    ]);
    writer.push_one([
        start_offset,
        end_offset,
        angle,
        0.0,
    ]);
    let is_opaque = write_gpu_gradient_stops_tree(stops, GradientKind::Conic, extend_mode, &mut writer);
    let gradient_address = writer.finish();

    Pattern {
        kind: PatternKind::Gradient,
        shader_input: PatternShaderInput(
            gradient_address.as_int(),
            0,
        ),
        texture_input: PatternTextureInput::default(),
        base_color: ColorF::WHITE,
        is_opaque,
        blend_mode: BlendMode::PremultipliedAlpha,
    }
}


fn write_gpu_gradient_stops_header_and_colors(
    stops: &[GradientStop],
    kind: GradientKind,
    extend_mode: ExtendMode,
    writer: &mut GpuBufferWriterF,
) -> bool {
    // Write the header.
    writer.push_one([
        (kind as u8) as f32,
        stops.len() as f32,
        if extend_mode == ExtendMode::Repeat { 1.0 } else { 0.0 },
        0.0
    ]);

    // Write the stop colors.
    let mut is_opaque = true;
    for stop in stops {
        writer.push_one(stop.color.premultiplied());
        is_opaque &= stop.color.a == 1.0;
    }

    is_opaque
}

// Push stop offsets in rearranged order so that the search can be carried
// out as an implicit tree traversal.
//
// The structure of the tree is:
//  - Each level is plit into 5 partitions.
//  - The root level has one node (4 offsets -> 5 partitions).
//  - Each level has 5 more nodes than the previous one.
//  - Levels are pushed one by one starting from the root
//
// ```ascii
// level : indices
// ------:---------
//   0   :                                                               24     ...
//   1   :          4         9            14             19             |      ...
//   2   :  0,1,2,3,|,5,6,7,8,|10,11,12,13,| ,15,16,17,18,| ,20,21,22,23,| ,25, ...
// ```
//
// In the example above:
// - The first (root) contains a single block containing the stop offsets from
//   indices [24, 49, 74, 99].
// - The second level contains blocks of offsets from indices [4, 9, 14, 19],
//   [29, 34, 39, 44], etc.
// - The third (leaf) level contains blocks from indices [0,1,2,3], [5,6,7,8],
//   [15, 16, 17, 18], etc.
//
// Placeholder offsets (1.0) are used when a level has more capacity than the
// input number of stops.
//
// Conceptually, blocks [0,1,2,3] and [5,6,7,8] are the first two children of
// the node [4,9,14,19], separated by the offset from index 4.
// Links are not explicitly represented via pointers or indices. Instead the
// position in the buffer is sufficient to represent the level and index of the
// stop (at the expense of having to store extra padding to round up each tree
// level to its power-of-5-aligned size).
//
// This scheme is meant to make the traversal efficient loading offsets in
// blocks of 4. The shader can converge to the leaf in very few loads.
pub fn write_gpu_gradient_stops_tree(
    stops: &[GradientStop],
    kind: GradientKind,
    extend_mode: ExtendMode,
    writer: &mut GpuBufferWriterF,
) -> bool {
    let is_opaque = write_gpu_gradient_stops_header_and_colors(
        stops,
        kind,
        extend_mode,
        writer
    );

    let num_stops = stops.len();
    let mut num_levels = 1;
    let mut index_stride = 5;
    let mut next_index_stride = 1;
    // Number of 4-offsets blocks for the current level.
    // The root has 1, then each level has 5 more than the previous one.
    let mut num_blocks_for_level = 1;
    let mut offset_blocks = 1;
    while offset_blocks * 4 < num_stops {
        num_blocks_for_level *= 5;
        offset_blocks += num_blocks_for_level;

        num_levels += 1;
        index_stride *= 5;
        next_index_stride *= 5;
    }

    // Fix offset_blocks up to account for the fact that we don't
    // store the entirety of the last level;
    let num_blocks_for_last_level = num_blocks_for_level.min(num_stops / 5 + 1);

    // Reset num_blocks_for_level for the traversal.
    num_blocks_for_level = 1;

    // Go over each level, starting from the root.
    for level in 0..num_levels {
        // This scheme rounds up the number of offsets to store for each
        // level to the next power of 5, which can represent a lot of wasted
        // space, especially for the last levels. We need each level to start
        // at a specific power-of-5-aligned offset so we can't get around the
        // wasted space for all levels except the last one (which has the most
        // waste).
        let is_last_level = level == num_levels - 1;
        let num_blocks = if is_last_level {
            num_blocks_for_last_level
        } else {
            num_blocks_for_level
        };

        for block_idx in 0..num_blocks {
            let mut block = [1.0; 4];
            for i in 0..4 {
                let linear_idx = block_idx * index_stride
                    + i * next_index_stride
                    + next_index_stride - 1;

                if linear_idx < num_stops {
                    block[i] = stops[linear_idx].offset;
                }
            }
            writer.push_one(block);
        }

        index_stride = next_index_stride;
        next_index_stride /= 5;
        num_blocks_for_level *= 5;
    }

    return is_opaque;
}

fn gpu_gradient_stops_blocks(num_stops: usize) -> usize {
    let header_blocks = 1;
    let color_blocks = num_stops;

    // If this is changed, matching changes should be made to the
    // equivalent code in write_gpu_gradient_stops_tree.
    let mut num_blocks_for_level = 1;
    let mut offset_blocks = 1;
    while offset_blocks * 4 < num_stops {
        num_blocks_for_level *= 5;
        offset_blocks += num_blocks_for_level;
    }

    // Fix the capacity up to account for the fact that we don't
    // store the entirety of the last level;
    let num_blocks_for_last_level = num_blocks_for_level.min(num_stops / 5 + 1);
    offset_blocks -= num_blocks_for_level;
    offset_blocks += num_blocks_for_last_level;

    header_blocks + color_blocks + offset_blocks
}