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.
This commit is contained in:
MacDue 2024-05-27 19:28:05 +01:00 committed by Andreas Kling
parent b9db9013f5
commit 9c711bc868
Notes: sideshowbarker 2024-07-16 20:12:13 +09:00
10 changed files with 55 additions and 9 deletions

View File

@ -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)

View File

@ -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,

View File

@ -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 };

View File

@ -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);

View File

@ -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

View File

@ -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);

View File

@ -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;
};
}

View File

@ -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

View File

@ -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);
}
}

View File

@ -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;