LibWeb: Store gradient color stops separately

Gradient display-list commands currently carry a Vector<Gfx::ColorStop>,
which keeps command payloads tied to an owning container. Split resolved
gradient stops into parallel color and position arrays so the display
list can later store those arrays as contiguous inline data.

This keeps the painting behavior the same while removing another nested
object shape that does not map cleanly to the flat command-buffer format
This commit is contained in:
Aliaksandr Kalenik 2026-05-08 16:08:09 +02:00 committed by Alexander Kalenik
parent 36a366e2e9
commit ce6ef14587
3 changed files with 71 additions and 48 deletions

View file

@ -364,29 +364,23 @@ static SkGradientShader::Interpolation to_skia_interpolation(CSS::ColorInterpola
void DisplayListPlayerSkia::paint_linear_gradient(PaintLinearGradient const& command)
{
auto const& linear_gradient_data = command.linear_gradient_data;
auto const& color_stop_list = linear_gradient_data.color_stops.list;
auto const& repeat_length = linear_gradient_data.color_stops.repeat_length;
VERIFY(!color_stop_list.is_empty());
auto const& color_stop_colors = linear_gradient_data.color_stops.colors;
auto const& color_stop_positions = linear_gradient_data.color_stops.positions;
VERIFY(!color_stop_colors.is_empty());
VERIFY(color_stop_colors.size() == color_stop_positions.size());
Vector<SkColor4f> colors;
Vector<SkScalar> positions;
auto const first_position = repeat_length.has_value() ? color_stop_list.first().position : 0.f;
for (size_t stop_index = 0; stop_index < color_stop_list.size(); stop_index++) {
auto const& stop = color_stop_list[stop_index];
if (stop_index > 0 && stop == color_stop_list[stop_index - 1])
continue;
colors.append(to_skia_color4f(stop.color));
positions.append((stop.position - first_position) / repeat_length.value_or(1));
}
colors.ensure_capacity(color_stop_colors.size());
for (auto color : color_stop_colors)
colors.unchecked_append(to_skia_color4f(color));
auto rect = command.gradient_rect.to_type<float>();
auto length = calculate_gradient_length<float>(rect.size(), linear_gradient_data.gradient_angle);
// Starting and ending points before rotation (0deg / "to top")
auto rect_center = rect.center();
auto start = rect_center.translated(0, (.5f - first_position) * length);
auto end = start.translated(0, repeat_length.value_or(1) * -length);
auto start = rect_center.translated(0, (.5f - linear_gradient_data.first_stop_position) * length);
auto end = start.translated(0, linear_gradient_data.repeat_length * -length);
Array const points { to_skia_point(start), to_skia_point(end) };
SkMatrix matrix;
@ -394,7 +388,7 @@ void DisplayListPlayerSkia::paint_linear_gradient(PaintLinearGradient const& com
auto color_space = SkColorSpace::MakeSRGB();
auto interpolation = to_skia_interpolation(linear_gradient_data.interpolation_method);
auto shader = SkGradientShader::MakeLinear(points.data(), colors.data(), color_space, positions.data(), positions.size(), SkTileMode::kRepeat, interpolation, &matrix);
auto shader = SkGradientShader::MakeLinear(points.data(), colors.data(), color_space, color_stop_positions.data(), color_stop_positions.size(), SkTileMode::kRepeat, interpolation, &matrix);
SkPaint paint;
paint.setDither(true);
@ -711,18 +705,15 @@ void DisplayListPlayerSkia::draw_rect(DrawRect const& command)
void DisplayListPlayerSkia::paint_radial_gradient(PaintRadialGradient const& command)
{
auto const& radial_gradient_data = command.radial_gradient_data;
auto const& color_stop_list = radial_gradient_data.color_stops.list;
VERIFY(!color_stop_list.is_empty());
auto const& color_stop_colors = radial_gradient_data.color_stops.colors;
auto const& color_stop_positions = radial_gradient_data.color_stops.positions;
VERIFY(!color_stop_colors.is_empty());
VERIFY(color_stop_colors.size() == color_stop_positions.size());
Vector<SkColor4f> colors;
Vector<SkScalar> positions;
for (size_t stop_index = 0; stop_index < color_stop_list.size(); stop_index++) {
auto const& stop = color_stop_list[stop_index];
if (stop_index > 0 && stop == color_stop_list[stop_index - 1])
continue;
colors.append(to_skia_color4f(stop.color));
positions.append(stop.position);
}
colors.ensure_capacity(color_stop_colors.size());
for (auto color : color_stop_colors)
colors.unchecked_append(to_skia_color4f(color));
auto const& rect = command.rect;
auto center = to_skia_point(command.center.translated(command.rect.location()));
@ -739,7 +730,7 @@ void DisplayListPlayerSkia::paint_radial_gradient(PaintRadialGradient const& com
auto color_space = SkColorSpace::MakeSRGB();
auto interpolation = to_skia_interpolation(radial_gradient_data.interpolation_method);
auto shader = SkGradientShader::MakeRadial(center, size.height(), colors.data(), color_space, positions.data(), positions.size(), tile_mode, interpolation, &matrix);
auto shader = SkGradientShader::MakeRadial(center, size.height(), colors.data(), color_space, color_stop_positions.data(), color_stop_positions.size(), tile_mode, interpolation, &matrix);
SkPaint paint;
paint.setDither(true);
@ -751,18 +742,15 @@ void DisplayListPlayerSkia::paint_radial_gradient(PaintRadialGradient const& com
void DisplayListPlayerSkia::paint_conic_gradient(PaintConicGradient const& command)
{
auto const& conic_gradient_data = command.conic_gradient_data;
auto const& color_stop_list = conic_gradient_data.color_stops.list;
VERIFY(!color_stop_list.is_empty());
auto const& color_stop_colors = conic_gradient_data.color_stops.colors;
auto const& color_stop_positions = conic_gradient_data.color_stops.positions;
VERIFY(!color_stop_colors.is_empty());
VERIFY(color_stop_colors.size() == color_stop_positions.size());
Vector<SkColor4f> colors;
Vector<SkScalar> positions;
for (size_t stop_index = 0; stop_index < color_stop_list.size(); stop_index++) {
auto const& stop = color_stop_list[stop_index];
if (stop_index > 0 && stop == color_stop_list[stop_index - 1])
continue;
colors.append(to_skia_color4f(stop.color));
positions.append(stop.position);
}
colors.ensure_capacity(color_stop_colors.size());
for (auto color : color_stop_colors)
colors.unchecked_append(to_skia_color4f(color));
auto const& rect = command.rect;
auto center = command.position.translated(rect.location()).to_type<float>();
@ -771,7 +759,7 @@ void DisplayListPlayerSkia::paint_conic_gradient(PaintConicGradient const& comma
matrix.setRotate(-90 + conic_gradient_data.start_angle, center.x(), center.y());
auto color_space = SkColorSpace::MakeSRGB();
auto interpolation = to_skia_interpolation(conic_gradient_data.interpolation_method);
auto shader = SkGradientShader::MakeSweep(center.x(), center.y(), colors.data(), color_space, positions.data(), positions.size(), SkTileMode::kRepeat, 0, 360, interpolation, &matrix);
auto shader = SkGradientShader::MakeSweep(center.x(), center.y(), colors.data(), color_space, color_stop_positions.data(), color_stop_positions.size(), SkTileMode::kRepeat, 0, 360, interpolation, &matrix);
SkPaint paint;
paint.setDither(true);

View file

@ -6,24 +6,24 @@
#pragma once
#include <AK/Span.h>
#include <AK/Optional.h>
#include <AK/Vector.h>
#include <LibGfx/Gradients.h>
#include <LibGfx/Color.h>
#include <LibWeb/CSS/StyleValues/ColorInterpolationMethodStyleValue.h>
#include <LibWeb/Forward.h>
namespace Web::Painting {
using ColorStopList = Vector<Gfx::ColorStop, 4>;
struct ColorStopData {
ColorStopList list;
Optional<float> repeat_length;
Vector<Color, 4> colors;
Vector<float, 4> positions;
bool repeating { false };
};
struct LinearGradientData {
float gradient_angle;
float first_stop_position { 0 };
float repeat_length { 1 };
ColorStopData color_stops;
CSS::ColorInterpolationMethodStyleValue::ColorInterpolationMethod interpolation_method;
};

View file

@ -21,6 +21,30 @@
namespace Web::Painting {
using ColorStopList = Vector<Gfx::ColorStop, 4>;
struct ResolvedColorStopData {
ColorStopList list;
Optional<float> repeat_length;
bool repeating { false };
};
static ColorStopData to_color_stop_data(ColorStopList const& color_stop_list, bool repeating, auto to_position)
{
ColorStopData data;
data.colors.ensure_capacity(color_stop_list.size());
data.positions.ensure_capacity(color_stop_list.size());
for (auto const& color_stop : color_stop_list) {
auto position = to_position(color_stop.position);
if (!data.colors.is_empty() && data.colors.last() == color_stop.color && data.positions.last() == position)
continue;
data.colors.unchecked_append(color_stop.color);
data.positions.unchecked_append(position);
}
data.repeating = repeating;
return data;
}
static ColorStopList replace_transition_hints_with_normal_color_stops(ColorStopList const& color_stop_list)
{
ColorStopList stops_with_replaced_transition_hints;
@ -125,7 +149,7 @@ static ColorStopList expand_color_stops_for_painting(ColorStopList const& color_
return replace_transition_hints_with_normal_color_stops(expanded);
}
static ColorStopData resolve_color_stop_positions(Layout::NodeWithStyle const& node, Vector<CSS::ColorStopListElement> const& color_stop_list, auto resolve_position_to_float, bool repeating)
static ResolvedColorStopData resolve_color_stop_positions(Layout::NodeWithStyle const& node, Vector<CSS::ColorStopListElement> const& color_stop_list, auto resolve_position_to_float, bool repeating)
{
VERIFY(!color_stop_list.is_empty());
ColorStopList resolved_color_stops;
@ -234,7 +258,12 @@ LinearGradientData resolve_linear_gradient_data(Layout::NodeWithStyle const& nod
// Replace transition hints for painting; keep repeat_length for Skia's native tiling
resolved_color_stops.list = replace_transition_hints_with_normal_color_stops(resolved_color_stops.list);
return { gradient_angle, resolved_color_stops, linear_gradient.interpolation_method() };
auto repeat_length = resolved_color_stops.repeat_length.value_or(1);
auto first_stop_position = resolved_color_stops.repeat_length.has_value() ? resolved_color_stops.list.first().position : 0.f;
auto color_stop_data = to_color_stop_data(resolved_color_stops.list, resolved_color_stops.repeating, [&](float position) {
return (position - first_stop_position) / repeat_length;
});
return { gradient_angle, first_stop_position, repeat_length, move(color_stop_data), linear_gradient.interpolation_method() };
}
ConicGradientData resolve_conic_gradient_data(Layout::NodeWithStyle const& node, CSS::ConicGradientStyleValue const& conic_gradient)
@ -250,7 +279,10 @@ ConicGradientData resolve_conic_gradient_data(Layout::NodeWithStyle const& node,
resolved_color_stops.list = expand_color_stops_for_painting(resolved_color_stops.list, resolved_color_stops.repeat_length);
resolved_color_stops.repeat_length = {};
return { conic_gradient.angle_degrees(), resolved_color_stops, conic_gradient.interpolation_method() };
auto color_stop_data = to_color_stop_data(resolved_color_stops.list, resolved_color_stops.repeating, [](float position) {
return position;
});
return { conic_gradient.angle_degrees(), move(color_stop_data), conic_gradient.interpolation_method() };
}
RadialGradientData resolve_radial_gradient_data(Layout::NodeWithStyle const& node, CSSPixelSize gradient_size, CSS::RadialGradientStyleValue const& radial_gradient)
@ -266,7 +298,10 @@ RadialGradientData resolve_radial_gradient_data(Layout::NodeWithStyle const& nod
resolved_color_stops.list = expand_color_stops_for_painting(resolved_color_stops.list, resolved_color_stops.repeat_length);
resolved_color_stops.repeat_length = {};
return { resolved_color_stops, radial_gradient.interpolation_method() };
auto color_stop_data = to_color_stop_data(resolved_color_stops.list, resolved_color_stops.repeating, [](float position) {
return position;
});
return { move(color_stop_data), radial_gradient.interpolation_method() };
}
}