From b1a72d66f6a897e252cdae4843bf32eabcdcf3d4 Mon Sep 17 00:00:00 2001 From: MacDue Date: Wed, 8 Mar 2023 20:49:00 +0100 Subject: [PATCH] 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. --- .../Libraries/LibGfx/AntiAliasingPainter.cpp | 3 +- .../Libraries/LibGfx/FillPathImplementation.h | 27 +++---- Userland/Libraries/LibGfx/Painter.cpp | 3 +- Userland/Libraries/LibGfx/Painter.h | 71 +++++++++++++++++++ 4 files changed, 81 insertions(+), 23 deletions(-) diff --git a/Userland/Libraries/LibGfx/AntiAliasingPainter.cpp b/Userland/Libraries/LibGfx/AntiAliasingPainter.cpp index 05d3104c9a5..96465a35f08 100644 --- a/Userland/Libraries/LibGfx/AntiAliasingPainter.cpp +++ b/Userland/Libraries/LibGfx/AntiAliasingPainter.cpp @@ -213,8 +213,7 @@ void AntiAliasingPainter::draw_line(FloatPoint actual_from, FloatPoint actual_to void AntiAliasingPainter::fill_path(Path const& path, Color color, Painter::WindingRule rule) { - Detail::fill_path( - m_underlying_painter, path, [=](IntPoint) { return color; }, rule, m_transform.translation()); + Detail::fill_path(m_underlying_painter, path, color, rule, m_transform.translation()); } void AntiAliasingPainter::fill_path(Path const& path, PaintStyle const& paint_style, Painter::WindingRule rule) diff --git a/Userland/Libraries/LibGfx/FillPathImplementation.h b/Userland/Libraries/LibGfx/FillPathImplementation.h index aa82f25e32a..8b139f3bcad 100644 --- a/Userland/Libraries/LibGfx/FillPathImplementation.h +++ b/Userland/Libraries/LibGfx/FillPathImplementation.h @@ -41,13 +41,13 @@ enum class FillPathMode { AllowFloatingPoints, }; -template -void fill_path(Painter& painter, Path const& path, ColorFunction color_function, Gfx::Painter::WindingRule winding_rule, Optional offset = {}) +template +void fill_path(Painter& painter, Path const& path, ColorOrFunction color, Gfx::Painter::WindingRule winding_rule, Optional offset = {}) { using GridCoordinateType = Conditional; using PointType = Point; - auto draw_scanline = [&](int y, float x1, float x2) { + auto draw_scanline = [&](int y, GridCoordinateType x1, GridCoordinateType x2) { const auto draw_offset = offset.value_or({ 0, 0 }); const auto draw_origin = (path.bounding_box().top_left() + draw_offset).to_type(); // 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, x2 += draw_offset.x(); if (x1 > x2) swap(x1, x2); - auto set_pixel = [&](int x, int y, Color color) { - painter.set_pixel(x, y, color, true); - }; - if constexpr (fill_path_mode == FillPathMode::AllowFloatingPoints) { - int int_x1 = ceilf(x1); - int int_x2 = floorf(x2); - float left_subpixel = int_x1 - x1; - float right_subpixel = x2 - int_x2; - auto left_color = color_function(IntPoint(int_x1 - 1, y) - draw_origin); - auto right_color = color_function(IntPoint(int_x2, y) - draw_origin); - set_pixel(int_x1 - 1, y, left_color.with_alpha(left_color.alpha() * left_subpixel)); - set_pixel(int_x2, y, right_color.with_alpha(right_color.alpha() * right_subpixel)); - for (int x = int_x1; x < int_x2; x++) - set_pixel(x, y, color_function(IntPoint(x, y) - draw_origin)); + if constexpr (IsSameIgnoringCV) { + painter.draw_scanline_for_fill_path(y, x1, x2 + 1, color); } else { - for (int x = x1; x < int(x2); x++) - set_pixel(x, y, color_function(IntPoint(x, y) - draw_origin)); + painter.draw_scanline_for_fill_path(y, x1, x2 + 1, [&](int offset) { + return color(IntPoint(x1 + offset, y) - draw_origin); + }); } }; diff --git a/Userland/Libraries/LibGfx/Painter.cpp b/Userland/Libraries/LibGfx/Painter.cpp index c9c63ddef51..5fd5c2f53dc 100644 --- a/Userland/Libraries/LibGfx/Painter.cpp +++ b/Userland/Libraries/LibGfx/Painter.cpp @@ -2393,8 +2393,7 @@ void Painter::stroke_path(Path const& path, Color color, int thickness) void Painter::fill_path(Path const& path, Color color, WindingRule winding_rule) { VERIFY(scale() == 1); // FIXME: Add scaling support. - Detail::fill_path( - *this, path, [=](IntPoint) { return color; }, winding_rule); + Detail::fill_path(*this, path, color, winding_rule); } void Painter::fill_path(Path const& path, PaintStyle const& paint_style, Painter::WindingRule rule) diff --git a/Userland/Libraries/LibGfx/Painter.h b/Userland/Libraries/LibGfx/Painter.h index 9e5efec0438..369acfa947e 100644 --- a/Userland/Libraries/LibGfx/Painter.h +++ b/Userland/Libraries/LibGfx/Painter.h @@ -7,6 +7,7 @@ #pragma once #include +#include #include #include #include @@ -180,6 +181,76 @@ public: int scale() const { return state().scale; } + template + ALWAYS_INLINE void draw_scanline_for_fill_path(int y, T x_start, T x_end, TColorOrFunction color) + { + // Note: This is really an internal function for FillPathImplementation.h to use. + // This allows fill path to clip more of the pixels and reduce the number of clipping checks + // to the number of scanlines (and allows for a fast fill). + + // Fill path should scale the scanlines before calling this. + VERIFY(scale() == 1); + + constexpr bool is_floating_point = IsSameIgnoringCV; + constexpr bool has_constant_color = IsSameIgnoringCV; + + int x1 = 0; + int x2 = 0; + u8 left_subpixel_alpha = 0; + u8 right_subpixel_alpha = 0; + if constexpr (is_floating_point) { + x1 = ceilf(x_start); + x2 = floorf(x_end); + left_subpixel_alpha = (x1 - x_start) * 255; + right_subpixel_alpha = (x_end - x2) * 255; + x1 -= left_subpixel_alpha > 0; + x2 += right_subpixel_alpha > 0; + } else { + x1 = x_start; + x2 = x_end; + } + + IntRect scanline(x1, y, x2 - x1, 1); + scanline = scanline.translated(translation()); + auto clipped = scanline.intersected(clip_rect()); + if (clipped.is_empty()) + return; + + auto get_color = [&](int offset) { + if constexpr (has_constant_color) { + return color; + } else { + return color(offset); + } + }; + + if constexpr (is_floating_point) { + // Paint left and right subpixels (then remove them from the scanline). + auto get_color_with_alpha = [&](int offset, u8 alpha) { + auto color_at_offset = get_color(offset); + u8 color_alpha = (alpha * color_at_offset.alpha()) / 255; + return color_at_offset.with_alpha(color_alpha); + }; + if (clipped.left() == scanline.left() && left_subpixel_alpha) + set_physical_pixel(clipped.top_left(), get_color_with_alpha(0, left_subpixel_alpha), true); + if (clipped.right() == scanline.right() && right_subpixel_alpha) + set_physical_pixel(clipped.top_right(), get_color_with_alpha(scanline.width(), right_subpixel_alpha), true); + clipped.shrink(0, right_subpixel_alpha > 0, 0, left_subpixel_alpha > 0); + } + + if constexpr (has_constant_color) { + if (color.alpha() == 255) { + // Speedy path: Constant color and no alpha blending. + fast_u32_fill(m_target->scanline(clipped.y()) + clipped.x(), color.value(), clipped.width()); + return; + } + } + + for (int x = clipped.x(); x <= clipped.right(); x++) { + set_physical_pixel({ x, clipped.y() }, get_color(x - scanline.x()), true); + } + } + protected: friend GradientLine;