LibWeb: Add (initial) AffineCommandExecutorCPU

This CommandExecutor is intended to provide better support for painting
stacking contexts where the transform is not a simple translation. It
is not intended to replace the CPU command executor (as its methods of
painting will likely be slower for the non-transformed case), instead,
it will function as a companion executor to handle transformations.

This is only intended to properly handle 2D transformations (skews,
rotations, scaling, etc). Full support for 3D transformations would
need further changes in LibGfx.

As it stands this is (very) incomplete and experimental, but hopefully,
this can be fleshed out to the point where it supports most common
painting commands.
This commit is contained in:
MacDue 2024-05-27 19:15:32 +01:00 committed by Andreas Kling
parent 6333159108
commit b9db9013f5
2 changed files with 374 additions and 0 deletions

View file

@ -0,0 +1,286 @@
/*
* Copyright (c) 2024, MacDue <macdue@dueutil.tech>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/Painting/AffineCommandExecutorCPU.h>
namespace Web::Painting {
// This executor is hopes to handle (at least) 2D CSS transforms. All commands
// implemented here are required to support affine transformations, if that is
// not possible the implementation should say in CommandExecutorCPU. Note: The
// transform can be assumed to be non-identity or translation, so there's no
// need to add fast paths here (those will be handled in the normal executor).
static Gfx::Path rect_path(Gfx::FloatRect const& rect)
{
Gfx::Path path;
path.move_to({ rect.x(), rect.y() });
path.line_to({ rect.x() + rect.width(), rect.y() });
path.line_to({ rect.x() + rect.width(), rect.y() + rect.height() });
path.line_to({ rect.x(), rect.y() + rect.height() });
path.close();
return path;
}
AffineCommandExecutorCPU::AffineCommandExecutorCPU(Gfx::Bitmap& bitmap, Gfx::AffineTransform transform, Gfx::IntRect clip)
: m_painter(bitmap)
{
auto clip_quad = Gfx::AffineTransform {}.map_to_quad(clip.to_type<float>());
m_stacking_contexts.append(StackingContext { transform, clip_quad, clip_quad.bounding_rect() });
}
CommandResult AffineCommandExecutorCPU::draw_glyph_run(DrawGlyphRun const&)
{
// FIXME: Implement.
return CommandResult::Continue;
}
CommandResult AffineCommandExecutorCPU::draw_text(DrawText const&)
{
// FIXME: Implement.
return CommandResult::Continue;
}
CommandResult AffineCommandExecutorCPU::fill_rect(FillRect const& command)
{
// FIXME: Somehow support clip_paths?
auto path = rect_path(command.rect.to_type<float>()).copy_transformed(stacking_context().transform);
aa_painter().fill_path(path, command.color, Gfx::Painter::WindingRule::EvenOdd);
return CommandResult::Continue;
}
CommandResult AffineCommandExecutorCPU::draw_scaled_bitmap(DrawScaledBitmap const& command)
{
m_painter.draw_scaled_bitmap_with_transform(command.dst_rect, command.bitmap, command.src_rect.to_type<float>(), stacking_context().transform, 1.0f, command.scaling_mode);
return CommandResult::Continue;
}
CommandResult AffineCommandExecutorCPU::draw_scaled_immutable_bitmap(DrawScaledImmutableBitmap const& command)
{
m_painter.draw_scaled_bitmap_with_transform(command.dst_rect, command.bitmap->bitmap(), command.src_rect.to_type<float>(), stacking_context().transform, 1.0f, command.scaling_mode);
return CommandResult::Continue;
}
CommandResult AffineCommandExecutorCPU::set_clip_rect(SetClipRect const&)
{
// FIXME: Implement. The plan here is to implement https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm
// within the rasterizer (which should work as the clip quadrilateral will always be convex).
return CommandResult::Continue;
}
CommandResult AffineCommandExecutorCPU::clear_clip_rect(ClearClipRect const&)
{
// FIXME: Implement.
return CommandResult::Continue;
}
CommandResult AffineCommandExecutorCPU::push_stacking_context(PushStackingContext const& command)
{
// FIXME: Support opacity.
// FIXME: Support masks.
// Note: Image rendering is not relevant as this does not transform via a bitmap.
// Note: `position: fixed` does not apply when CSS transforms are involved.
// FIXME: Attempt to support 3D transforms... Somehow?
auto affine_transform = Gfx::extract_2d_affine_transform(command.transform.matrix);
auto new_transform = Gfx::AffineTransform {}
.set_translation(command.post_transform_translation.to_type<float>())
.translate(command.transform.origin)
.multiply(affine_transform)
.translate(-command.transform.origin);
auto const& current_stacking_context = stacking_context();
m_stacking_contexts.append(StackingContext {
.transform = Gfx::AffineTransform(current_stacking_context.transform).multiply(new_transform),
.clip = current_stacking_context.clip,
.clip_bounds = current_stacking_context.clip_bounds });
return CommandResult::Continue;
}
CommandResult AffineCommandExecutorCPU::pop_stacking_context(PopStackingContext const&)
{
m_stacking_contexts.take_last();
if (m_stacking_contexts.size() == 0)
return CommandResult::ContinueWithParentExecutor;
return CommandResult::Continue;
}
CommandResult AffineCommandExecutorCPU::paint_linear_gradient(PaintLinearGradient const&)
{
// FIXME: Implement.
return CommandResult::Continue;
}
CommandResult AffineCommandExecutorCPU::paint_outer_box_shadow(PaintOuterBoxShadow const&)
{
// FIXME: Implement.
return CommandResult::Continue;
}
CommandResult AffineCommandExecutorCPU::paint_inner_box_shadow(PaintInnerBoxShadow const&)
{
// FIXME: Implement.
return CommandResult::Continue;
}
CommandResult AffineCommandExecutorCPU::paint_text_shadow(PaintTextShadow const&)
{
// FIXME: Implement.
return CommandResult::Continue;
}
CommandResult AffineCommandExecutorCPU::fill_rect_with_rounded_corners(FillRectWithRoundedCorners const& command)
{
Gfx::Path path;
auto x = command.rect.x();
auto y = command.rect.y();
auto width = command.rect.width();
auto height = command.rect.height();
if (command.top_left_radius)
path.move_to({ x + command.top_left_radius.horizontal_radius, y });
else
path.move_to({ x, y });
if (command.top_right_radius) {
path.horizontal_line_to(x + width - command.top_right_radius.horizontal_radius);
path.elliptical_arc_to({ x + width, y + command.top_right_radius.horizontal_radius }, { command.top_right_radius.horizontal_radius, command.top_right_radius.vertical_radius }, 0, false, true);
} else {
path.horizontal_line_to(x + width);
}
if (command.bottom_right_radius) {
path.vertical_line_to(y + height - command.bottom_right_radius.vertical_radius);
path.elliptical_arc_to({ x + width - command.bottom_right_radius.horizontal_radius, y + height }, { command.bottom_right_radius.horizontal_radius, command.bottom_right_radius.vertical_radius }, 0, false, true);
} else {
path.vertical_line_to(y + height);
}
if (command.bottom_left_radius) {
path.horizontal_line_to(x + command.bottom_left_radius.horizontal_radius);
path.elliptical_arc_to({ x, y + height - command.bottom_left_radius.vertical_radius }, { command.bottom_left_radius.horizontal_radius, command.bottom_left_radius.vertical_radius }, 0, false, true);
} else {
path.horizontal_line_to(x);
}
if (command.top_left_radius) {
path.vertical_line_to(y + command.top_left_radius.vertical_radius);
path.elliptical_arc_to({ x + command.top_left_radius.horizontal_radius, y }, { command.top_left_radius.horizontal_radius, command.top_left_radius.vertical_radius }, 0, false, true);
} else {
path.vertical_line_to(y);
}
path = path.copy_transformed(stacking_context().transform);
aa_painter().fill_path(path, command.color, Gfx::Painter::WindingRule::EvenOdd);
return CommandResult::Continue;
}
CommandResult AffineCommandExecutorCPU::fill_path_using_color(FillPathUsingColor const& command)
{
auto path_transform = Gfx::AffineTransform(stacking_context().transform).multiply(Gfx::AffineTransform {}.set_translation(command.aa_translation));
aa_painter().fill_path(command.path.copy_transformed(path_transform), command.color, command.winding_rule);
return CommandResult::Continue;
}
CommandResult AffineCommandExecutorCPU::fill_path_using_paint_style(FillPathUsingPaintStyle const&)
{
// FIXME: Implement.
return CommandResult::Continue;
}
CommandResult AffineCommandExecutorCPU::stroke_path_using_color(StrokePathUsingColor const& command)
{
auto path_transform = Gfx::AffineTransform(stacking_context().transform).multiply(Gfx::AffineTransform {}.set_translation(command.aa_translation));
aa_painter().stroke_path(command.path.copy_transformed(path_transform), command.color, command.thickness);
return CommandResult::Continue;
}
CommandResult AffineCommandExecutorCPU::stroke_path_using_paint_style(StrokePathUsingPaintStyle const&)
{
// FIXME: Implement.
return CommandResult::Continue;
}
CommandResult AffineCommandExecutorCPU::draw_ellipse(DrawEllipse const&)
{
// FIXME: Implement.
return CommandResult::Continue;
}
CommandResult AffineCommandExecutorCPU::fill_ellipse(FillEllipse const&)
{
// FIXME: Implement.
return CommandResult::Continue;
}
CommandResult AffineCommandExecutorCPU::draw_line(DrawLine const& command)
{
// FIXME: Implement other line styles.
Gfx::Path path;
path.move_to(command.from.to_type<float>());
path.line_to(command.to.to_type<float>());
aa_painter().stroke_path(path, command.color, command.thickness);
return CommandResult::Continue;
}
CommandResult AffineCommandExecutorCPU::draw_signed_distance_field(DrawSignedDistanceField const&)
{
// FIXME: Implement.
return CommandResult::Continue;
}
CommandResult AffineCommandExecutorCPU::apply_backdrop_filter(ApplyBackdropFilter const&)
{
// FIXME: Implement.
return CommandResult::Continue;
}
CommandResult AffineCommandExecutorCPU::draw_rect(DrawRect const& command)
{
auto path = rect_path(command.rect.to_type<float>()).copy_transformed(stacking_context().transform);
aa_painter().stroke_path(path, command.color, 1);
return CommandResult::Continue;
}
CommandResult AffineCommandExecutorCPU::paint_radial_gradient(PaintRadialGradient const&)
{
// FIXME: Implement.
return CommandResult::Continue;
}
CommandResult AffineCommandExecutorCPU::paint_conic_gradient(PaintConicGradient const&)
{
// FIXME: Implement.
return CommandResult::Continue;
}
CommandResult AffineCommandExecutorCPU::draw_triangle_wave(DrawTriangleWave const&)
{
// FIXME: Implement.
return CommandResult::Continue;
}
CommandResult AffineCommandExecutorCPU::sample_under_corners(SampleUnderCorners const&)
{
// FIXME: Implement? -- Likely not a good approach for transforms.
return CommandResult::Continue;
}
CommandResult AffineCommandExecutorCPU::blit_corner_clipping(BlitCornerClipping const&)
{
// FIXME: Implement? -- Likely not a good approach for transforms.
return CommandResult::Continue;
}
bool AffineCommandExecutorCPU::would_be_fully_clipped_by_painter(Gfx::IntRect rect) const
{
auto const& current_stacking_context = stacking_context();
auto transformed_rect = current_stacking_context.transform.map(rect.to_type<float>());
return transformed_rect.intersected(current_stacking_context.clip_bounds).is_empty();
}
}

View file

@ -0,0 +1,88 @@
/*
* Copyright (c) 2024, MacDue <macdue@dueutil.tech>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibGfx/Quad.h>
#include <LibWeb/Painting/RecordingPainter.h>
namespace Web::Painting {
class AffineCommandExecutorCPU : public CommandExecutor {
public:
CommandResult draw_glyph_run(DrawGlyphRun const&) override;
CommandResult draw_text(DrawText const&) override;
CommandResult fill_rect(FillRect const&) override;
CommandResult draw_scaled_bitmap(DrawScaledBitmap const&) override;
CommandResult draw_scaled_immutable_bitmap(DrawScaledImmutableBitmap const&) override;
CommandResult set_clip_rect(SetClipRect const&) override;
CommandResult clear_clip_rect(ClearClipRect const&) override;
CommandResult push_stacking_context(PushStackingContext const&) override;
CommandResult pop_stacking_context(PopStackingContext const&) override;
CommandResult paint_linear_gradient(PaintLinearGradient const&) override;
CommandResult paint_outer_box_shadow(PaintOuterBoxShadow const&) override;
CommandResult paint_inner_box_shadow(PaintInnerBoxShadow const&) override;
CommandResult paint_text_shadow(PaintTextShadow const&) override;
CommandResult fill_rect_with_rounded_corners(FillRectWithRoundedCorners const&) override;
CommandResult fill_path_using_color(FillPathUsingColor const&) override;
CommandResult fill_path_using_paint_style(FillPathUsingPaintStyle const&) override;
CommandResult stroke_path_using_color(StrokePathUsingColor const&) override;
CommandResult stroke_path_using_paint_style(StrokePathUsingPaintStyle const&) override;
CommandResult draw_ellipse(DrawEllipse const&) override;
CommandResult fill_ellipse(FillEllipse const&) override;
CommandResult draw_line(DrawLine const&) override;
CommandResult draw_signed_distance_field(DrawSignedDistanceField const&) override;
CommandResult apply_backdrop_filter(ApplyBackdropFilter const&) override;
CommandResult draw_rect(DrawRect const&) override;
CommandResult paint_radial_gradient(PaintRadialGradient const&) override;
CommandResult paint_conic_gradient(PaintConicGradient const&) override;
CommandResult draw_triangle_wave(DrawTriangleWave const&) override;
CommandResult sample_under_corners(SampleUnderCorners const&) override;
CommandResult blit_corner_clipping(BlitCornerClipping const&) override;
bool would_be_fully_clipped_by_painter(Gfx::IntRect) const override;
bool needs_prepare_glyphs_texture() const override { return false; }
void prepare_glyph_texture(HashMap<Gfx::Font const*, HashTable<u32>> const&) override {};
virtual void prepare_to_execute(size_t) override { }
bool needs_update_immutable_bitmap_texture_cache() const override { return false; }
void update_immutable_bitmap_texture_cache(HashMap<u32, Gfx::ImmutableBitmap const*>&) override {};
AffineCommandExecutorCPU(Gfx::Bitmap& bitmap, Gfx::AffineTransform transform, Gfx::IntRect clip);
virtual ~AffineCommandExecutorCPU() override = default;
private:
// FIXME: Support masking.
// FIXME: Support opacity < 1.0f.
struct StackingContext {
Gfx::AffineTransform transform;
Gfx::FloatQuad clip;
Gfx::FloatRect clip_bounds;
};
Gfx::AntiAliasingPainter aa_painter()
{
return Gfx::AntiAliasingPainter(m_painter);
}
StackingContext& stacking_context()
{
return m_stacking_contexts.last();
}
StackingContext const& stacking_context() const
{
return m_stacking_contexts.last();
}
Gfx::Painter m_painter;
Vector<StackingContext> m_stacking_contexts;
};
}