Skip to content

Commit b1a72d6

Browse files
MacDuelinusg
authored andcommitted
LibGfx: Speed up fill_path() with per scanline clipping & fast fills
This improves fill_path() performance by adding an API to the painter that allows painting an entire scanline rather than just a pixel. With this paths can be clipped a scanline at a time rather than each pixel, removing a fair amount of checks. Along with optimized clipping, this can now use a fast_u32_fill() to paint all but the subpixels of a scanline if a solid color with no alpha channel is used (which is quite common in SVGs). This reduces scrolling around on svg.html from 21% in set_pixel() and 19% in fill_path() to just 7.8% in fill_path (with set_pixel() eliminated). Now fill_path() is far from the slowest code when scrolling the page.
1 parent be958a1 commit b1a72d6

File tree

4 files changed

+81
-23
lines changed

4 files changed

+81
-23
lines changed

Userland/Libraries/LibGfx/AntiAliasingPainter.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,8 +213,7 @@ void AntiAliasingPainter::draw_line(FloatPoint actual_from, FloatPoint actual_to
213213

214214
void AntiAliasingPainter::fill_path(Path const& path, Color color, Painter::WindingRule rule)
215215
{
216-
Detail::fill_path<Detail::FillPathMode::AllowFloatingPoints>(
217-
m_underlying_painter, path, [=](IntPoint) { return color; }, rule, m_transform.translation());
216+
Detail::fill_path<Detail::FillPathMode::AllowFloatingPoints>(m_underlying_painter, path, color, rule, m_transform.translation());
218217
}
219218

220219
void AntiAliasingPainter::fill_path(Path const& path, PaintStyle const& paint_style, Painter::WindingRule rule)

Userland/Libraries/LibGfx/FillPathImplementation.h

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,13 @@ enum class FillPathMode {
4141
AllowFloatingPoints,
4242
};
4343

44-
template<FillPathMode fill_path_mode, typename ColorFunction>
45-
void fill_path(Painter& painter, Path const& path, ColorFunction color_function, Gfx::Painter::WindingRule winding_rule, Optional<FloatPoint> offset = {})
44+
template<FillPathMode fill_path_mode, typename ColorOrFunction>
45+
void fill_path(Painter& painter, Path const& path, ColorOrFunction color, Gfx::Painter::WindingRule winding_rule, Optional<FloatPoint> offset = {})
4646
{
4747
using GridCoordinateType = Conditional<fill_path_mode == FillPathMode::PlaceOnIntGrid, int, float>;
4848
using PointType = Point<GridCoordinateType>;
4949

50-
auto draw_scanline = [&](int y, float x1, float x2) {
50+
auto draw_scanline = [&](int y, GridCoordinateType x1, GridCoordinateType x2) {
5151
const auto draw_offset = offset.value_or({ 0, 0 });
5252
const auto draw_origin = (path.bounding_box().top_left() + draw_offset).to_type<int>();
5353
// FIMXE: Offset is added here to handle floating point translations in the AA painter,
@@ -57,23 +57,12 @@ void fill_path(Painter& painter, Path const& path, ColorFunction color_function,
5757
x2 += draw_offset.x();
5858
if (x1 > x2)
5959
swap(x1, x2);
60-
auto set_pixel = [&](int x, int y, Color color) {
61-
painter.set_pixel(x, y, color, true);
62-
};
63-
if constexpr (fill_path_mode == FillPathMode::AllowFloatingPoints) {
64-
int int_x1 = ceilf(x1);
65-
int int_x2 = floorf(x2);
66-
float left_subpixel = int_x1 - x1;
67-
float right_subpixel = x2 - int_x2;
68-
auto left_color = color_function(IntPoint(int_x1 - 1, y) - draw_origin);
69-
auto right_color = color_function(IntPoint(int_x2, y) - draw_origin);
70-
set_pixel(int_x1 - 1, y, left_color.with_alpha(left_color.alpha() * left_subpixel));
71-
set_pixel(int_x2, y, right_color.with_alpha(right_color.alpha() * right_subpixel));
72-
for (int x = int_x1; x < int_x2; x++)
73-
set_pixel(x, y, color_function(IntPoint(x, y) - draw_origin));
60+
if constexpr (IsSameIgnoringCV<ColorOrFunction, Color>) {
61+
painter.draw_scanline_for_fill_path(y, x1, x2 + 1, color);
7462
} else {
75-
for (int x = x1; x < int(x2); x++)
76-
set_pixel(x, y, color_function(IntPoint(x, y) - draw_origin));
63+
painter.draw_scanline_for_fill_path(y, x1, x2 + 1, [&](int offset) {
64+
return color(IntPoint(x1 + offset, y) - draw_origin);
65+
});
7766
}
7867
};
7968

Userland/Libraries/LibGfx/Painter.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2393,8 +2393,7 @@ void Painter::stroke_path(Path const& path, Color color, int thickness)
23932393
void Painter::fill_path(Path const& path, Color color, WindingRule winding_rule)
23942394
{
23952395
VERIFY(scale() == 1); // FIXME: Add scaling support.
2396-
Detail::fill_path<Detail::FillPathMode::PlaceOnIntGrid>(
2397-
*this, path, [=](IntPoint) { return color; }, winding_rule);
2396+
Detail::fill_path<Detail::FillPathMode::PlaceOnIntGrid>(*this, path, color, winding_rule);
23982397
}
23992398

24002399
void Painter::fill_path(Path const& path, PaintStyle const& paint_style, Painter::WindingRule rule)

Userland/Libraries/LibGfx/Painter.h

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#pragma once
88

99
#include <AK/Forward.h>
10+
#include <AK/Memory.h>
1011
#include <AK/NonnullRefPtr.h>
1112
#include <AK/Utf8View.h>
1213
#include <AK/Vector.h>
@@ -180,6 +181,76 @@ class Painter {
180181

181182
int scale() const { return state().scale; }
182183

184+
template<typename T, typename TColorOrFunction>
185+
ALWAYS_INLINE void draw_scanline_for_fill_path(int y, T x_start, T x_end, TColorOrFunction color)
186+
{
187+
// Note: This is really an internal function for FillPathImplementation.h to use.
188+
// This allows fill path to clip more of the pixels and reduce the number of clipping checks
189+
// to the number of scanlines (and allows for a fast fill).
190+
191+
// Fill path should scale the scanlines before calling this.
192+
VERIFY(scale() == 1);
193+
194+
constexpr bool is_floating_point = IsSameIgnoringCV<T, int>;
195+
constexpr bool has_constant_color = IsSameIgnoringCV<TColorOrFunction, Color>;
196+
197+
int x1 = 0;
198+
int x2 = 0;
199+
u8 left_subpixel_alpha = 0;
200+
u8 right_subpixel_alpha = 0;
201+
if constexpr (is_floating_point) {
202+
x1 = ceilf(x_start);
203+
x2 = floorf(x_end);
204+
left_subpixel_alpha = (x1 - x_start) * 255;
205+
right_subpixel_alpha = (x_end - x2) * 255;
206+
x1 -= left_subpixel_alpha > 0;
207+
x2 += right_subpixel_alpha > 0;
208+
} else {
209+
x1 = x_start;
210+
x2 = x_end;
211+
}
212+
213+
IntRect scanline(x1, y, x2 - x1, 1);
214+
scanline = scanline.translated(translation());
215+
auto clipped = scanline.intersected(clip_rect());
216+
if (clipped.is_empty())
217+
return;
218+
219+
auto get_color = [&](int offset) {
220+
if constexpr (has_constant_color) {
221+
return color;
222+
} else {
223+
return color(offset);
224+
}
225+
};
226+
227+
if constexpr (is_floating_point) {
228+
// Paint left and right subpixels (then remove them from the scanline).
229+
auto get_color_with_alpha = [&](int offset, u8 alpha) {
230+
auto color_at_offset = get_color(offset);
231+
u8 color_alpha = (alpha * color_at_offset.alpha()) / 255;
232+
return color_at_offset.with_alpha(color_alpha);
233+
};
234+
if (clipped.left() == scanline.left() && left_subpixel_alpha)
235+
set_physical_pixel(clipped.top_left(), get_color_with_alpha(0, left_subpixel_alpha), true);
236+
if (clipped.right() == scanline.right() && right_subpixel_alpha)
237+
set_physical_pixel(clipped.top_right(), get_color_with_alpha(scanline.width(), right_subpixel_alpha), true);
238+
clipped.shrink(0, right_subpixel_alpha > 0, 0, left_subpixel_alpha > 0);
239+
}
240+
241+
if constexpr (has_constant_color) {
242+
if (color.alpha() == 255) {
243+
// Speedy path: Constant color and no alpha blending.
244+
fast_u32_fill(m_target->scanline(clipped.y()) + clipped.x(), color.value(), clipped.width());
245+
return;
246+
}
247+
}
248+
249+
for (int x = clipped.x(); x <= clipped.right(); x++) {
250+
set_physical_pixel({ x, clipped.y() }, get_color(x - scanline.x()), true);
251+
}
252+
}
253+
183254
protected:
184255
friend GradientLine;
185256

0 commit comments

Comments
 (0)