use api::{ColorF, ColorU, ExtendMode, GradientStop, PremultipliedColorF};
use api::units::{LayoutRect, LayoutSize, LayoutVector2D};
use crate::renderer::{GpuBufferAddress, GpuBufferBuilderF, GpuBufferWriterF};
use std::hash;
mod linear;
mod radial;
mod conic;
pub use linear::MAX_CACHED_SIZE as LINEAR_MAX_CACHED_SIZE;
pub use linear::*;
pub use radial::*;
pub use conic::*;
#[repr(u8)]
#[derive(Copy, Clone, Debug)]
pub enum GradientKind {
Linear = 0,
Radial = 1,
Conic = 2,
}
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
#[derive(Debug, Copy, Clone, MallocSizeOf, PartialEq)]
pub struct GradientStopKey {
pub offset: f32,
pub color: ColorU,
}
impl GradientStopKey {
pub fn empty() -> Self {
GradientStopKey {
offset: 0.0,
color: ColorU::new(0, 0, 0, 0),
}
}
}
impl Into<GradientStopKey> for GradientStop {
fn into(self) -> GradientStopKey {
GradientStopKey {
offset: self.offset,
color: self.color.into(),
}
}
}
fn stops_and_min_alpha(stop_keys: &[GradientStopKey]) -> (Vec<GradientStop>, f32) {
let mut min_alpha: f32 = 1.0;
let stops = stop_keys.iter().map(|stop_key| {
let color: ColorF = stop_key.color.into();
min_alpha = min_alpha.min(color.a);
GradientStop {
offset: stop_key.offset,
color,
}
}).collect();
(stops, min_alpha)
}
fn write_gpu_gradient_stops_header_and_colors(
stops: &[GradientStop],
kind: GradientKind,
extend_mode: ExtendMode,
writer: &mut GpuBufferWriterF,
) -> bool {
writer.push_one([
(kind as u8) as f32,
stops.len() as f32,
if extend_mode == ExtendMode::Repeat { 1.0 } else { 0.0 },
0.0
]);
let mut is_opaque = true;
for stop in stops {
writer.push_one(stop.color.premultiplied());
is_opaque &= stop.color.a == 1.0;
}
is_opaque
}
fn write_gpu_gradient_stops_linear(
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
);
for chunk in stops.chunks(4) {
let mut block = [0.0; 4];
let mut i = 0;
for stop in chunk {
block[i] = stop.offset;
i += 1;
}
writer.push_one(block);
}
is_opaque
}
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;
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;
}
let num_blocks_for_last_level = num_blocks_for_level.min(num_stops / 5 + 1);
num_blocks_for_level = 1;
for level in 0..num_levels {
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, tree_traversal: bool) -> usize {
let header_blocks = 1;
let color_blocks = num_stops;
let mut offset_blocks = (num_stops + 3) / 4;
if tree_traversal {
let mut num_blocks_for_level = 1;
offset_blocks = 1;
while offset_blocks * 4 < num_stops {
num_blocks_for_level *= 5;
offset_blocks += num_blocks_for_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
}
impl Eq for GradientStopKey {}
impl hash::Hash for GradientStopKey {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.offset.to_bits().hash(state);
self.color.hash(state);
}
}
pub const GRADIENT_DATA_FIRST_STOP: usize = 0;
pub const GRADIENT_DATA_LAST_STOP: usize = GRADIENT_DATA_SIZE - 1;
pub const GRADIENT_DATA_TABLE_BEGIN: usize = GRADIENT_DATA_FIRST_STOP + 1;
pub const GRADIENT_DATA_TABLE_END: usize = GRADIENT_DATA_LAST_STOP;
pub const GRADIENT_DATA_TABLE_SIZE: usize = 128;
pub const GRADIENT_DATA_SIZE: usize = GRADIENT_DATA_TABLE_SIZE + 2;
#[derive(Debug, Copy, Clone)]
#[repr(C)]
struct GradientDataEntry {
start_color: PremultipliedColorF,
end_step: PremultipliedColorF,
}
impl GradientDataEntry {
fn white() -> Self {
Self {
start_color: PremultipliedColorF::WHITE,
end_step: PremultipliedColorF::TRANSPARENT,
}
}
}
pub struct GradientGpuBlockBuilder {}
impl GradientGpuBlockBuilder {
fn fill_colors(
start_idx: usize,
end_idx: usize,
start_color: &PremultipliedColorF,
end_color: &PremultipliedColorF,
entries: &mut [GradientDataEntry; GRADIENT_DATA_SIZE],
prev_step: &PremultipliedColorF,
) -> PremultipliedColorF {
let inv_steps = 1.0 / (end_idx - start_idx) as f32;
let mut step = PremultipliedColorF {
r: (end_color.r - start_color.r) * inv_steps,
g: (end_color.g - start_color.g) * inv_steps,
b: (end_color.b - start_color.b) * inv_steps,
a: (end_color.a - start_color.a) * inv_steps,
};
if step == *prev_step {
step.a = f32::from_bits(if step.a == 0.0 { 1 } else { step.a.to_bits() + 1 });
}
let mut cur_color = *start_color;
for index in start_idx .. end_idx {
let entry = &mut entries[index];
entry.start_color = cur_color;
cur_color.r += step.r;
cur_color.g += step.g;
cur_color.b += step.b;
cur_color.a += step.a;
entry.end_step = step;
}
step
}
#[inline]
fn get_index(offset: f32) -> usize {
(offset.max(0.0).min(1.0) * GRADIENT_DATA_TABLE_SIZE as f32 +
GRADIENT_DATA_TABLE_BEGIN as f32)
.round() as usize
}
pub fn build(
reverse_stops: bool,
gpu_buffer_builder: &mut GpuBufferBuilderF,
src_stops: &[GradientStop],
) -> GpuBufferAddress {
let mut src_stops = src_stops.into_iter();
let mut cur_color = match src_stops.next() {
Some(stop) => {
debug_assert_eq!(stop.offset, 0.0);
stop.color.premultiplied()
}
None => {
error!("Zero gradient stops found!");
PremultipliedColorF::BLACK
}
};
let mut entries = [GradientDataEntry::white(); GRADIENT_DATA_SIZE];
let mut prev_step = cur_color;
if reverse_stops {
prev_step = GradientGpuBlockBuilder::fill_colors(
GRADIENT_DATA_LAST_STOP,
GRADIENT_DATA_LAST_STOP + 1,
&cur_color,
&cur_color,
&mut entries,
&prev_step,
);
let mut cur_idx = GRADIENT_DATA_TABLE_END;
for next in src_stops {
let next_color = next.color.premultiplied();
let next_idx = Self::get_index(1.0 - next.offset);
if next_idx < cur_idx {
prev_step = GradientGpuBlockBuilder::fill_colors(
next_idx,
cur_idx,
&next_color,
&cur_color,
&mut entries,
&prev_step,
);
cur_idx = next_idx;
}
cur_color = next_color;
}
if cur_idx != GRADIENT_DATA_TABLE_BEGIN {
error!("Gradient stops abruptly at {}, auto-completing to white", cur_idx);
}
GradientGpuBlockBuilder::fill_colors(
GRADIENT_DATA_FIRST_STOP,
GRADIENT_DATA_FIRST_STOP + 1,
&cur_color,
&cur_color,
&mut entries,
&prev_step,
);
} else {
prev_step = GradientGpuBlockBuilder::fill_colors(
GRADIENT_DATA_FIRST_STOP,
GRADIENT_DATA_FIRST_STOP + 1,
&cur_color,
&cur_color,
&mut entries,
&prev_step,
);
let mut cur_idx = GRADIENT_DATA_TABLE_BEGIN;
for next in src_stops {
let next_color = next.color.premultiplied();
let next_idx = Self::get_index(next.offset);
if next_idx > cur_idx {
prev_step = GradientGpuBlockBuilder::fill_colors(
cur_idx,
next_idx,
&cur_color,
&next_color,
&mut entries,
&prev_step,
);
cur_idx = next_idx;
}
cur_color = next_color;
}
if cur_idx != GRADIENT_DATA_TABLE_END {
error!("Gradient stops abruptly at {}, auto-completing to white", cur_idx);
}
GradientGpuBlockBuilder::fill_colors(
GRADIENT_DATA_LAST_STOP,
GRADIENT_DATA_LAST_STOP + 1,
&cur_color,
&cur_color,
&mut entries,
&prev_step,
);
}
let mut writer = gpu_buffer_builder.write_blocks(2 * entries.len());
for entry in entries {
writer.push_one(entry.start_color);
writer.push_one(entry.end_step);
}
writer.finish()
}
}
pub fn apply_gradient_local_clip(
prim_rect: &mut LayoutRect,
stretch_size: &LayoutSize,
tile_spacing: &LayoutSize,
clip_rect: &LayoutRect,
) -> LayoutVector2D {
let w = prim_rect.max.x.min(clip_rect.max.x) - prim_rect.min.x;
let h = prim_rect.max.y.min(clip_rect.max.y) - prim_rect.min.y;
let is_tiled_x = w > stretch_size.width + tile_spacing.width;
let is_tiled_y = h > stretch_size.height + tile_spacing.height;
let mut offset = LayoutVector2D::new(0.0, 0.0);
if !is_tiled_x {
let diff = (clip_rect.min.x - prim_rect.min.x).min(prim_rect.width());
if diff > 0.0 {
prim_rect.min.x += diff;
offset.x = -diff;
}
let diff = prim_rect.max.x - clip_rect.max.x;
if diff > 0.0 {
prim_rect.max.x -= diff;
}
}
if !is_tiled_y {
let diff = (clip_rect.min.y - prim_rect.min.y).min(prim_rect.height());
if diff > 0.0 {
prim_rect.min.y += diff;
offset.y = -diff;
}
let diff = prim_rect.max.y - clip_rect.max.y;
if diff > 0.0 {
prim_rect.max.y -= diff;
}
}
offset
}
#[test]
#[cfg(target_pointer_width = "64")]
fn test_struct_sizes() {
use std::mem;
assert_eq!(mem::size_of::<LinearGradient>(), 72, "LinearGradient size changed");
assert_eq!(mem::size_of::<LinearGradientTemplate>(), 144, "LinearGradientTemplate size changed");
assert_eq!(mem::size_of::<LinearGradientKey>(), 96, "LinearGradientKey size changed");
assert_eq!(mem::size_of::<RadialGradient>(), 72, "RadialGradient size changed");
assert_eq!(mem::size_of::<RadialGradientTemplate>(), 144, "RadialGradientTemplate size changed");
assert_eq!(mem::size_of::<RadialGradientKey>(), 96, "RadialGradientKey size changed");
assert_eq!(mem::size_of::<ConicGradient>(), 72, "ConicGradient size changed");
assert_eq!(mem::size_of::<ConicGradientTemplate>(), 144, "ConicGradientTemplate size changed");
assert_eq!(mem::size_of::<ConicGradientKey>(), 96, "ConicGradientKey size changed");
}