Browse Source

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.
MacDue 2 years ago
parent
commit
b1a72d66f6

+ 1 - 2
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)
 void AntiAliasingPainter::fill_path(Path const& path, Color color, Painter::WindingRule rule)
 {
 {
-    Detail::fill_path<Detail::FillPathMode::AllowFloatingPoints>(
-        m_underlying_painter, path, [=](IntPoint) { return color; }, rule, m_transform.translation());
+    Detail::fill_path<Detail::FillPathMode::AllowFloatingPoints>(m_underlying_painter, path, color, rule, m_transform.translation());
 }
 }
 
 
 void AntiAliasingPainter::fill_path(Path const& path, PaintStyle const& paint_style, Painter::WindingRule rule)
 void AntiAliasingPainter::fill_path(Path const& path, PaintStyle const& paint_style, Painter::WindingRule rule)

+ 8 - 19
Userland/Libraries/LibGfx/FillPathImplementation.h

@@ -41,13 +41,13 @@ enum class FillPathMode {
     AllowFloatingPoints,
     AllowFloatingPoints,
 };
 };
 
 
-template<FillPathMode fill_path_mode, typename ColorFunction>
-void fill_path(Painter& painter, Path const& path, ColorFunction color_function, Gfx::Painter::WindingRule winding_rule, Optional<FloatPoint> offset = {})
+template<FillPathMode fill_path_mode, typename ColorOrFunction>
+void fill_path(Painter& painter, Path const& path, ColorOrFunction color, Gfx::Painter::WindingRule winding_rule, Optional<FloatPoint> offset = {})
 {
 {
     using GridCoordinateType = Conditional<fill_path_mode == FillPathMode::PlaceOnIntGrid, int, float>;
     using GridCoordinateType = Conditional<fill_path_mode == FillPathMode::PlaceOnIntGrid, int, float>;
     using PointType = Point<GridCoordinateType>;
     using PointType = Point<GridCoordinateType>;
 
 
-    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_offset = offset.value_or({ 0, 0 });
         const auto draw_origin = (path.bounding_box().top_left() + draw_offset).to_type<int>();
         const auto draw_origin = (path.bounding_box().top_left() + draw_offset).to_type<int>();
         // FIMXE: Offset is added here to handle floating point translations in the AA painter,
         // 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();
         x2 += draw_offset.x();
         if (x1 > x2)
         if (x1 > x2)
             swap(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<ColorOrFunction, Color>) {
+            painter.draw_scanline_for_fill_path(y, x1, x2 + 1, color);
         } else {
         } 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);
+            });
         }
         }
     };
     };
 
 

+ 1 - 2
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)
 void Painter::fill_path(Path const& path, Color color, WindingRule winding_rule)
 {
 {
     VERIFY(scale() == 1); // FIXME: Add scaling support.
     VERIFY(scale() == 1); // FIXME: Add scaling support.
-    Detail::fill_path<Detail::FillPathMode::PlaceOnIntGrid>(
-        *this, path, [=](IntPoint) { return color; }, winding_rule);
+    Detail::fill_path<Detail::FillPathMode::PlaceOnIntGrid>(*this, path, color, winding_rule);
 }
 }
 
 
 void Painter::fill_path(Path const& path, PaintStyle const& paint_style, Painter::WindingRule rule)
 void Painter::fill_path(Path const& path, PaintStyle const& paint_style, Painter::WindingRule rule)

+ 71 - 0
Userland/Libraries/LibGfx/Painter.h

@@ -7,6 +7,7 @@
 #pragma once
 #pragma once
 
 
 #include <AK/Forward.h>
 #include <AK/Forward.h>
+#include <AK/Memory.h>
 #include <AK/NonnullRefPtr.h>
 #include <AK/NonnullRefPtr.h>
 #include <AK/Utf8View.h>
 #include <AK/Utf8View.h>
 #include <AK/Vector.h>
 #include <AK/Vector.h>
@@ -180,6 +181,76 @@ public:
 
 
     int scale() const { return state().scale; }
     int scale() const { return state().scale; }
 
 
+    template<typename T, typename TColorOrFunction>
+    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<T, int>;
+        constexpr bool has_constant_color = IsSameIgnoringCV<TColorOrFunction, Color>;
+
+        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:
 protected:
     friend GradientLine;
     friend GradientLine;