Source code

Revision control

Copy as Markdown

Other Tools

/* 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::{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,
}
}
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,
}
}
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,
}
}
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
}