Bläddra i källkod

LibWeb+Ladybird: Add option to enable the `AffineCommandExecutorCPU`

This adds a `--experimental-cpu-transforms` option to Ladybird and
WebContent (which defaults to false/off).

When enabled the AffineCommandExecutorCPU will be used to handle
painting transformed stacking contexts (i.e. stacking contexts where
the transform is something other than a simple translation). The regular
command executor will still handle the non-transformed cases.

This is hidden under a flag as the `AffineCommandExecutorCPU` is very
incomplete now. It missing support for clipping, text, and other basic
commands. Once most common commands have been implemented this flag
will be removed.
MacDue 1 år sedan
förälder
incheckning
9c711bc868

+ 2 - 0
Ladybird/HelperProcess.cpp

@@ -116,6 +116,8 @@ ErrorOr<NonnullRefPtr<WebView::WebContentClient>> launch_web_content_process(
         arguments.append("--use-lagom-networking"sv);
     if (web_content_options.enable_gpu_painting == Ladybird::EnableGPUPainting::Yes)
         arguments.append("--use-gpu-painting"sv);
+    if (web_content_options.enable_experimental_cpu_transforms == Ladybird::EnableExperimentalCPUTransforms::Yes)
+        arguments.append("--experimental-cpu-transforms"sv);
     if (web_content_options.wait_for_debugger == Ladybird::WaitForDebugger::Yes)
         arguments.append("--wait-for-debugger"sv);
     if (web_content_options.log_all_js_exceptions == Ladybird::LogAllJSExceptions::Yes)

+ 3 - 0
Ladybird/Qt/main.cpp

@@ -100,6 +100,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
     bool enable_qt_networking = false;
     bool expose_internals_object = false;
     bool use_gpu_painting = false;
+    bool use_experimental_cpu_transform_support = false;
     bool debug_web_content = false;
     bool log_all_js_exceptions = false;
     bool enable_idl_tracing = false;
@@ -113,6 +114,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
     args_parser.add_option(disable_sql_database, "Disable SQL database", "disable-sql-database");
     args_parser.add_option(enable_qt_networking, "Enable Qt as the backend networking service", "enable-qt-networking");
     args_parser.add_option(use_gpu_painting, "Enable GPU painting", "enable-gpu-painting");
+    args_parser.add_option(use_experimental_cpu_transform_support, "Enable experimental CPU transform support", "experimental-cpu-transforms");
     args_parser.add_option(debug_web_content, "Wait for debugger to attach to WebContent", "debug-web-content");
     args_parser.add_option(certificates, "Path to a certificate file", "certificate", 'C', "certificate");
     args_parser.add_option(log_all_js_exceptions, "Log all JavaScript exceptions", "log-all-js-exceptions");
@@ -177,6 +179,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
         .executable_path = MUST(String::from_byte_string(MUST(Core::System::current_executable_path()))),
         .enable_callgrind_profiling = enable_callgrind_profiling ? Ladybird::EnableCallgrindProfiling::Yes : Ladybird::EnableCallgrindProfiling::No,
         .enable_gpu_painting = use_gpu_painting ? Ladybird::EnableGPUPainting::Yes : Ladybird::EnableGPUPainting::No,
+        .enable_experimental_cpu_transforms = use_experimental_cpu_transform_support ? Ladybird::EnableExperimentalCPUTransforms::Yes : Ladybird::EnableExperimentalCPUTransforms::No,
         .use_lagom_networking = enable_qt_networking ? Ladybird::UseLagomNetworking::No : Ladybird::UseLagomNetworking::Yes,
         .wait_for_debugger = debug_web_content ? Ladybird::WaitForDebugger::Yes : Ladybird::WaitForDebugger::No,
         .log_all_js_exceptions = log_all_js_exceptions ? Ladybird::LogAllJSExceptions::Yes : Ladybird::LogAllJSExceptions::No,

+ 6 - 0
Ladybird/Types.h

@@ -20,6 +20,11 @@ enum class EnableGPUPainting {
     Yes
 };
 
+enum class EnableExperimentalCPUTransforms {
+    No,
+    Yes
+};
+
 enum class IsLayoutTestMode {
     No,
     Yes
@@ -55,6 +60,7 @@ struct WebContentOptions {
     String executable_path;
     EnableCallgrindProfiling enable_callgrind_profiling { EnableCallgrindProfiling::No };
     EnableGPUPainting enable_gpu_painting { EnableGPUPainting::No };
+    EnableExperimentalCPUTransforms enable_experimental_cpu_transforms { EnableExperimentalCPUTransforms::No };
     IsLayoutTestMode is_layout_test_mode { IsLayoutTestMode::No };
     UseLagomNetworking use_lagom_networking { UseLagomNetworking::Yes };
     WaitForDebugger wait_for_debugger { WaitForDebugger::No };

+ 6 - 0
Ladybird/WebContent/main.cpp

@@ -97,6 +97,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
     bool expose_internals_object = false;
     bool use_lagom_networking = false;
     bool use_gpu_painting = false;
+    bool use_experimental_cpu_transform_support = false;
     bool wait_for_debugger = false;
     bool log_all_js_exceptions = false;
     bool enable_idl_tracing = false;
@@ -109,6 +110,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
     args_parser.add_option(expose_internals_object, "Expose internals object", "expose-internals-object");
     args_parser.add_option(use_lagom_networking, "Enable Lagom servers for networking", "use-lagom-networking");
     args_parser.add_option(use_gpu_painting, "Enable GPU painting", "use-gpu-painting");
+    args_parser.add_option(use_experimental_cpu_transform_support, "Enable experimental CPU transform support", "experimental-cpu-transforms");
     args_parser.add_option(wait_for_debugger, "Wait for debugger", "wait-for-debugger");
     args_parser.add_option(mach_server_name, "Mach server name", "mach-server-name", 0, "mach_server_name");
     args_parser.add_option(log_all_js_exceptions, "Log all JavaScript exceptions", "log-all-js-exceptions");
@@ -130,6 +132,10 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
         WebContent::PageClient::set_use_gpu_painter();
     }
 
+    if (use_experimental_cpu_transform_support) {
+        WebContent::PageClient::set_use_experimental_cpu_transform_support();
+    }
+
 #if defined(AK_OS_MACOS)
     if (!mach_server_name.is_empty()) {
         Core::Platform::register_with_mach_server(mach_server_name);

+ 1 - 0
Userland/Libraries/LibWeb/CMakeLists.txt

@@ -524,6 +524,7 @@ set(SOURCES
     Page/EventHandler.cpp
     Page/InputEvent.cpp
     Page/Page.cpp
+    Painting/AffineCommandExecutorCPU.cpp
     Painting/AudioPaintable.cpp
     Painting/BackgroundPainting.cpp
     Painting/BorderRadiiData.cpp

+ 17 - 5
Userland/Libraries/LibWeb/Painting/CommandExecutorCPU.cpp

@@ -15,8 +15,9 @@
 
 namespace Web::Painting {
 
-CommandExecutorCPU::CommandExecutorCPU(Gfx::Bitmap& bitmap)
+CommandExecutorCPU::CommandExecutorCPU(Gfx::Bitmap& bitmap, bool enable_affine_command_executor)
     : m_target_bitmap(bitmap)
+    , m_enable_affine_command_executor(enable_affine_command_executor)
 {
     stacking_contexts.append({ .painter = AK::make<Gfx::Painter>(bitmap),
         .opacity = 1.0f,
@@ -127,6 +128,21 @@ CommandResult CommandExecutorCPU::clear_clip_rect(ClearClipRect const&)
 
 CommandResult CommandExecutorCPU::push_stacking_context(PushStackingContext const& command)
 {
+    // FIXME: This extracts the affine 2D part of the full transformation matrix.
+    // Use the whole matrix when we get better transformation support in LibGfx or use LibGL for drawing the bitmap
+    auto affine_transform = Gfx::extract_2d_affine_transform(command.transform.matrix);
+
+    if (m_enable_affine_command_executor && command.opacity == 1.0f && !affine_transform.is_identity_or_translation()) {
+        auto offset = command.is_fixed_position ? Gfx::IntPoint {} : painter().translation();
+        auto full_transform = Gfx::AffineTransform {}
+                                  .set_translation((command.post_transform_translation + offset).to_type<float>())
+                                  .translate(command.transform.origin)
+                                  .multiply(affine_transform)
+                                  .translate(-command.transform.origin);
+        m_affine_command_executor = AffineCommandExecutorCPU(m_target_bitmap, full_transform, painter().clip_rect());
+        return CommandResult::ContinueWithNestedExecutor;
+    }
+
     painter().save();
     if (command.is_fixed_position)
         painter().translate(-painter().translation());
@@ -148,10 +164,6 @@ CommandResult CommandExecutorCPU::push_stacking_context(PushStackingContext cons
         return CommandResult::Continue;
     }
 
-    // FIXME: This extracts the affine 2D part of the full transformation matrix.
-    // Use the whole matrix when we get better transformation support in LibGfx or use LibGL for drawing the bitmap
-    auto affine_transform = Gfx::extract_2d_affine_transform(command.transform.matrix);
-
     if (command.opacity == 1.0f && affine_transform.is_identity_or_translation()) {
         // OPTIMIZATION: This is a simple translation use previous stacking context's painter.
         painter().translate(affine_transform.translation().to_rounded<int>() + command.post_transform_translation);

+ 10 - 1
Userland/Libraries/LibWeb/Painting/CommandExecutorCPU.h

@@ -7,6 +7,7 @@
 #pragma once
 
 #include <AK/MaybeOwned.h>
+#include <LibWeb/Painting/AffineCommandExecutorCPU.h>
 #include <LibWeb/Painting/RecordingPainter.h>
 
 namespace Web::Painting {
@@ -53,10 +54,17 @@ public:
     bool needs_update_immutable_bitmap_texture_cache() const override { return false; }
     void update_immutable_bitmap_texture_cache(HashMap<u32, Gfx::ImmutableBitmap const*>&) override {};
 
-    CommandExecutorCPU(Gfx::Bitmap& bitmap);
+    CommandExecutorCPU(Gfx::Bitmap& bitmap, bool enable_affine_command_executor = false);
+
+    CommandExecutor& nested_executor() override
+    {
+        return *m_affine_command_executor;
+    }
 
 private:
     Gfx::Bitmap& m_target_bitmap;
+    bool m_enable_affine_command_executor { false };
+
     Vector<RefPtr<BorderRadiusCornerClipper>> m_corner_clippers_stack;
 
     struct StackingContext {
@@ -71,6 +79,7 @@ private:
     [[nodiscard]] Gfx::Painter& painter() { return *stacking_contexts.last().painter; }
 
     Vector<StackingContext> stacking_contexts;
+    Optional<AffineCommandExecutorCPU> m_affine_command_executor;
 };
 
 }

+ 2 - 2
Userland/Libraries/LibWeb/Painting/PaintableBox.cpp

@@ -309,7 +309,7 @@ void PaintableBox::paint(PaintContext& context, PaintPhase phase) const
             border_radius_data.inflate(outline_data->top.width + outline_offset_y, outline_data->right.width + outline_offset_x, outline_data->bottom.width + outline_offset_y, outline_data->left.width + outline_offset_x);
             borders_rect.inflate(outline_data->top.width + outline_offset_y, outline_data->right.width + outline_offset_x, outline_data->bottom.width + outline_offset_y, outline_data->left.width + outline_offset_x);
 
-            context.recording_painter().paint_borders(context.rounded_device_rect(borders_rect), border_radius_data.as_corners(context), outline_data->to_device_pixels(context));
+            paint_all_borders(context.recording_painter(), context.rounded_device_rect(borders_rect), border_radius_data.as_corners(context), outline_data->to_device_pixels(context));
         }
     }
 
@@ -390,7 +390,7 @@ void PaintableBox::paint_border(PaintContext& context) const
         .bottom = box_model().border.bottom == 0 ? CSS::BorderData() : computed_values().border_bottom(),
         .left = box_model().border.left == 0 ? CSS::BorderData() : computed_values().border_left(),
     };
-    context.recording_painter().paint_borders(context.rounded_device_rect(absolute_border_box_rect()), normalized_border_radii_data().as_corners(context), borders_data.to_device_pixels(context));
+    paint_all_borders(context.recording_painter(), context.rounded_device_rect(absolute_border_box_rect()), normalized_border_radii_data().as_corners(context), borders_data.to_device_pixels(context));
 }
 
 void PaintableBox::paint_backdrop_filter(PaintContext& context) const

+ 7 - 1
Userland/Services/WebContent/PageClient.cpp

@@ -37,6 +37,7 @@
 namespace WebContent {
 
 static bool s_use_gpu_painter = false;
+static bool s_use_experimental_cpu_transform_support = false;
 
 JS_DEFINE_ALLOCATOR(PageClient);
 
@@ -45,6 +46,11 @@ void PageClient::set_use_gpu_painter()
     s_use_gpu_painter = true;
 }
 
+void PageClient::set_use_experimental_cpu_transform_support()
+{
+    s_use_experimental_cpu_transform_support = true;
+}
+
 JS::NonnullGCPtr<PageClient> PageClient::create(JS::VM& vm, PageHost& page_host, u64 id)
 {
     return vm.heap().allocate_without_realm<PageClient>(page_host, id);
@@ -222,7 +228,7 @@ void PageClient::paint(Web::DevicePixelRect const& content_rect, Gfx::Bitmap& ta
         }
 #endif
     } else {
-        Web::Painting::CommandExecutorCPU painting_command_executor(target);
+        Web::Painting::CommandExecutorCPU painting_command_executor(target, s_use_experimental_cpu_transform_support);
         painting_commands.execute(painting_command_executor);
     }
 }

+ 1 - 0
Userland/Services/WebContent/PageClient.h

@@ -30,6 +30,7 @@ public:
     static JS::NonnullGCPtr<PageClient> create(JS::VM& vm, PageHost& page_host, u64 id);
 
     static void set_use_gpu_painter();
+    static void set_use_experimental_cpu_transform_support();
 
     virtual void schedule_repaint() override;
     virtual bool is_ready_to_paint() const override;