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, 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 {
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
}
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;
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) -> usize {
let header_blocks = 1;
let color_blocks = num_stops;
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;
}
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
}