diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index 550a96e146..df5e091bab 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -492,6 +492,8 @@ set(SOURCES Painting/BordersData.cpp Painting/ButtonPaintable.cpp Painting/CanvasPaintable.cpp + Painting/Command.cpp + Painting/CommandList.cpp Painting/CheckBoxPaintable.cpp Painting/GradientPainting.cpp Painting/FilterPainting.cpp diff --git a/Userland/Libraries/LibWeb/HTML/Navigable.cpp b/Userland/Libraries/LibWeb/HTML/Navigable.cpp index e82cb824da..1860354d61 100644 --- a/Userland/Libraries/LibWeb/HTML/Navigable.cpp +++ b/Userland/Libraries/LibWeb/HTML/Navigable.cpp @@ -2127,7 +2127,7 @@ void Navigable::paint(Painting::RecordingPainter& recording_painter, PaintConfig auto scroll_offset = context.rounded_device_point(scrollable_frame->offset).to_type(); scroll_offsets_by_frame_id[scrollable_frame->id] = scroll_offset; } - recording_painter.apply_scroll_offsets(scroll_offsets_by_frame_id); + recording_painter.commands_list().apply_scroll_offsets(scroll_offsets_by_frame_id); } } diff --git a/Userland/Libraries/LibWeb/Painting/Command.cpp b/Userland/Libraries/LibWeb/Painting/Command.cpp new file mode 100644 index 0000000000..731514100e --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/Command.cpp @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace Web::Painting { + +void DrawGlyphRun::translate_by(Gfx::IntPoint const& offset) +{ + for (auto& glyph : glyph_run) { + glyph.visit([&](auto& glyph) { + glyph.translate_by(offset.to_type()); + }); + } + rect.translate_by(offset); +} + +Gfx::IntRect PaintOuterBoxShadow::bounding_rect() const +{ + return get_outer_box_shadow_bounding_rect(outer_box_shadow_params); +} + +void PaintOuterBoxShadow::translate_by(Gfx::IntPoint const& offset) +{ + outer_box_shadow_params.device_content_rect.translate_by(offset.to_type()); +} + +void PaintInnerBoxShadow::translate_by(Gfx::IntPoint const& offset) +{ + outer_box_shadow_params.device_content_rect.translate_by(offset.to_type()); +} + +} diff --git a/Userland/Libraries/LibWeb/Painting/Command.h b/Userland/Libraries/LibWeb/Painting/Command.h new file mode 100644 index 0000000000..f75d00bdc9 --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/Command.h @@ -0,0 +1,431 @@ +/* + * Copyright (c) 2024, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Web::Painting { + +struct DrawGlyphRun { + Vector glyph_run; + Color color; + Gfx::IntRect rect; + + [[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; } + + void translate_by(Gfx::IntPoint const& offset); +}; + +struct DrawText { + Gfx::IntRect rect; + String raw_text; + Gfx::TextAlignment alignment; + Color color; + Gfx::TextElision elision; + Gfx::TextWrapping wrapping; + Optional> font {}; + + [[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; } + void translate_by(Gfx::IntPoint const& offset) { rect.translate_by(offset); } +}; + +struct FillRect { + Gfx::IntRect rect; + Color color; + + [[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; } + void translate_by(Gfx::IntPoint const& offset) { rect.translate_by(offset); } +}; + +struct DrawScaledBitmap { + Gfx::IntRect dst_rect; + NonnullRefPtr bitmap; + Gfx::IntRect src_rect; + Gfx::Painter::ScalingMode scaling_mode; + + [[nodiscard]] Gfx::IntRect bounding_rect() const { return dst_rect; } + void translate_by(Gfx::IntPoint const& offset) { dst_rect.translate_by(offset); } +}; + +struct DrawScaledImmutableBitmap { + Gfx::IntRect dst_rect; + NonnullRefPtr bitmap; + Gfx::IntRect src_rect; + Gfx::Painter::ScalingMode scaling_mode; + + [[nodiscard]] Gfx::IntRect bounding_rect() const { return dst_rect; } + void translate_by(Gfx::IntPoint const& offset) { dst_rect.translate_by(offset); } +}; + +struct SetClipRect { + Gfx::IntRect rect; +}; + +struct ClearClipRect { }; + +struct StackingContextTransform { + Gfx::FloatPoint origin; + Gfx::FloatMatrix4x4 matrix; +}; + +struct StackingContextMask { + NonnullRefPtr mask_bitmap; + Gfx::Bitmap::MaskKind mask_kind; +}; + +struct PushStackingContext { + float opacity; + bool is_fixed_position; + // The bounding box of the source paintable (pre-transform). + Gfx::IntRect source_paintable_rect; + // A translation to be applied after the stacking context has been transformed. + Gfx::IntPoint post_transform_translation; + CSS::ImageRendering image_rendering; + StackingContextTransform transform; + Optional mask = {}; + + void translate_by(Gfx::IntPoint const& offset) + { + source_paintable_rect.translate_by(offset); + } +}; + +struct PopStackingContext { }; + +struct PaintLinearGradient { + Gfx::IntRect gradient_rect; + LinearGradientData linear_gradient_data; + + [[nodiscard]] Gfx::IntRect bounding_rect() const { return gradient_rect; } + + void translate_by(Gfx::IntPoint const& offset) + { + gradient_rect.translate_by(offset); + } +}; + +struct PaintOuterBoxShadow { + PaintOuterBoxShadowParams outer_box_shadow_params; + + [[nodiscard]] Gfx::IntRect bounding_rect() const; + void translate_by(Gfx::IntPoint const& offset); +}; + +struct PaintInnerBoxShadow { + PaintOuterBoxShadowParams outer_box_shadow_params; + + void translate_by(Gfx::IntPoint const& offset); +}; + +struct PaintTextShadow { + int blur_radius; + Gfx::IntRect shadow_bounding_rect; + Gfx::IntRect text_rect; + Vector glyph_run; + Color color; + int fragment_baseline; + Gfx::IntPoint draw_location; + + [[nodiscard]] Gfx::IntRect bounding_rect() const { return { draw_location, shadow_bounding_rect.size() }; } + void translate_by(Gfx::IntPoint const& offset) { draw_location.translate_by(offset); } +}; + +struct FillRectWithRoundedCorners { + Gfx::IntRect rect; + Color color; + Gfx::AntiAliasingPainter::CornerRadius top_left_radius; + Gfx::AntiAliasingPainter::CornerRadius top_right_radius; + Gfx::AntiAliasingPainter::CornerRadius bottom_left_radius; + Gfx::AntiAliasingPainter::CornerRadius bottom_right_radius; + + [[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; } + void translate_by(Gfx::IntPoint const& offset) { rect.translate_by(offset); } +}; + +struct FillPathUsingColor { + Gfx::IntRect path_bounding_rect; + Gfx::Path path; + Color color; + Gfx::Painter::WindingRule winding_rule; + Gfx::FloatPoint aa_translation; + + [[nodiscard]] Gfx::IntRect bounding_rect() const { return path_bounding_rect; } + + void translate_by(Gfx::IntPoint const& offset) + { + path_bounding_rect.translate_by(offset); + aa_translation.translate_by(offset.to_type()); + } +}; + +struct FillPathUsingPaintStyle { + Gfx::IntRect path_bounding_rect; + Gfx::Path path; + NonnullRefPtr paint_style; + Gfx::Painter::WindingRule winding_rule; + float opacity; + Gfx::FloatPoint aa_translation; + + [[nodiscard]] Gfx::IntRect bounding_rect() const { return path_bounding_rect; } + + void translate_by(Gfx::IntPoint const& offset) + { + path_bounding_rect.translate_by(offset); + aa_translation.translate_by(offset.to_type()); + } +}; + +struct StrokePathUsingColor { + Gfx::IntRect path_bounding_rect; + Gfx::Path path; + Color color; + float thickness; + Gfx::FloatPoint aa_translation; + + [[nodiscard]] Gfx::IntRect bounding_rect() const { return path_bounding_rect; } + + void translate_by(Gfx::IntPoint const& offset) + { + path_bounding_rect.translate_by(offset); + aa_translation.translate_by(offset.to_type()); + } +}; + +struct StrokePathUsingPaintStyle { + Gfx::IntRect path_bounding_rect; + Gfx::Path path; + NonnullRefPtr paint_style; + float thickness; + float opacity = 1.0f; + Gfx::FloatPoint aa_translation; + + [[nodiscard]] Gfx::IntRect bounding_rect() const { return path_bounding_rect; } + + void translate_by(Gfx::IntPoint const& offset) + { + path_bounding_rect.translate_by(offset); + aa_translation.translate_by(offset.to_type()); + } +}; + +struct DrawEllipse { + Gfx::IntRect rect; + Color color; + int thickness; + + [[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; } + + void translate_by(Gfx::IntPoint const& offset) + { + rect.translate_by(offset); + } +}; + +struct FillEllipse { + Gfx::IntRect rect; + Color color; + Gfx::AntiAliasingPainter::BlendMode blend_mode; + + [[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; } + + void translate_by(Gfx::IntPoint const& offset) + { + rect.translate_by(offset); + } +}; + +struct DrawLine { + Color color; + Gfx::IntPoint from; + Gfx::IntPoint to; + int thickness; + Gfx::Painter::LineStyle style; + Color alternate_color; + + void translate_by(Gfx::IntPoint const& offset) + { + from.translate_by(offset); + to.translate_by(offset); + } +}; + +struct DrawSignedDistanceField { + Gfx::IntRect rect; + Color color; + Gfx::GrayscaleBitmap sdf; + float smoothing; + + [[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; } + + void translate_by(Gfx::IntPoint const& offset) + { + rect.translate_by(offset); + } +}; + +struct PaintFrame { + Gfx::IntRect rect; + Palette palette; + Gfx::FrameStyle style; + + [[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; } + + void translate_by(Gfx::IntPoint const& offset) { rect.translate_by(offset); } +}; + +struct ApplyBackdropFilter { + Gfx::IntRect backdrop_region; + BorderRadiiData border_radii_data; + CSS::ResolvedBackdropFilter backdrop_filter; + + [[nodiscard]] Gfx::IntRect bounding_rect() const { return backdrop_region; } + + void translate_by(Gfx::IntPoint const& offset) + { + backdrop_region.translate_by(offset); + } +}; + +struct DrawRect { + Gfx::IntRect rect; + Color color; + bool rough; + + [[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; } + + void translate_by(Gfx::IntPoint const& offset) { rect.translate_by(offset); } +}; + +struct PaintRadialGradient { + Gfx::IntRect rect; + RadialGradientData radial_gradient_data; + Gfx::IntPoint center; + Gfx::IntSize size; + + [[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; } + + void translate_by(Gfx::IntPoint const& offset) { rect.translate_by(offset); } +}; + +struct PaintConicGradient { + Gfx::IntRect rect; + ConicGradientData conic_gradient_data; + Gfx::IntPoint position; + + [[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; } + + void translate_by(Gfx::IntPoint const& offset) { rect.translate_by(offset); } +}; + +struct DrawTriangleWave { + Gfx::IntPoint p1; + Gfx::IntPoint p2; + Color color; + int amplitude; + int thickness; + + void translate_by(Gfx::IntPoint const& offset) + { + p1.translate_by(offset); + p2.translate_by(offset); + } +}; + +struct SampleUnderCorners { + u32 id; + CornerRadii corner_radii; + Gfx::IntRect border_rect; + CornerClip corner_clip; + + [[nodiscard]] Gfx::IntRect bounding_rect() const { return border_rect; } + + void translate_by(Gfx::IntPoint const& offset) { border_rect.translate_by(offset); } +}; + +struct BlitCornerClipping { + u32 id; + Gfx::IntRect border_rect; + + [[nodiscard]] Gfx::IntRect bounding_rect() const { return border_rect; } + + void translate_by(Gfx::IntPoint const& offset) { border_rect.translate_by(offset); } +}; + +struct PaintBorders { + DevicePixelRect border_rect; + CornerRadii corner_radii; + BordersDataDevicePixels borders_data; + + [[nodiscard]] Gfx::IntRect bounding_rect() const { return border_rect.to_type(); } + + void translate_by(Gfx::IntPoint const& offset) + { + border_rect.translate_by(offset.to_type()); + } +}; + +using Command = Variant< + DrawGlyphRun, + DrawText, + FillRect, + DrawScaledBitmap, + DrawScaledImmutableBitmap, + SetClipRect, + ClearClipRect, + PushStackingContext, + PopStackingContext, + PaintLinearGradient, + PaintRadialGradient, + PaintConicGradient, + PaintOuterBoxShadow, + PaintInnerBoxShadow, + PaintTextShadow, + FillRectWithRoundedCorners, + FillPathUsingColor, + FillPathUsingPaintStyle, + StrokePathUsingColor, + StrokePathUsingPaintStyle, + DrawEllipse, + FillEllipse, + DrawLine, + DrawSignedDistanceField, + PaintFrame, + ApplyBackdropFilter, + DrawRect, + DrawTriangleWave, + SampleUnderCorners, + BlitCornerClipping, + PaintBorders>; + +} diff --git a/Userland/Libraries/LibWeb/Painting/CommandList.cpp b/Userland/Libraries/LibWeb/Painting/CommandList.cpp new file mode 100644 index 0000000000..76d69798cc --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/CommandList.cpp @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2024, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +namespace Web::Painting { + +void CommandList::append(Command&& command, Optional scroll_frame_id) +{ + m_commands.append({ scroll_frame_id, move(command) }); +} + +static Optional command_bounding_rectangle(Command const& command) +{ + return command.visit( + [&](auto const& command) -> Optional { + if constexpr (requires { command.bounding_rect(); }) + return command.bounding_rect(); + else + return {}; + }); +} + +void CommandList::apply_scroll_offsets(Vector const& offsets_by_frame_id) +{ + for (auto& command_with_scroll_id : m_commands) { + if (command_with_scroll_id.scroll_frame_id.has_value()) { + auto const& scroll_frame_id = command_with_scroll_id.scroll_frame_id.value(); + auto const& scroll_offset = offsets_by_frame_id[scroll_frame_id]; + command_with_scroll_id.command.visit( + [&](auto& command) { + if constexpr (requires { command.translate_by(scroll_offset); }) + command.translate_by(scroll_offset); + }); + } + } +} + +void CommandList::execute(PaintingCommandExecutor& executor) +{ + executor.prepare_to_execute(); + + if (executor.needs_prepare_glyphs_texture()) { + HashMap> unique_glyphs; + for (auto& command_with_scroll_id : m_commands) { + auto& command = command_with_scroll_id.command; + if (command.has()) { + for (auto const& glyph_or_emoji : command.get().glyph_run) { + if (glyph_or_emoji.has()) { + auto const& glyph = glyph_or_emoji.get(); + unique_glyphs.ensure(glyph.font, [] { return HashTable {}; }).set(glyph.code_point); + } + } + } + } + executor.prepare_glyph_texture(unique_glyphs); + } + + if (executor.needs_update_immutable_bitmap_texture_cache()) { + HashMap immutable_bitmaps; + for (auto const& command_with_scroll_id : m_commands) { + auto& command = command_with_scroll_id.command; + if (command.has()) { + auto const& immutable_bitmap = command.get().bitmap; + immutable_bitmaps.set(immutable_bitmap->id(), immutable_bitmap.ptr()); + } + } + executor.update_immutable_bitmap_texture_cache(immutable_bitmaps); + } + + HashTable skipped_sample_corner_commands; + size_t next_command_index = 0; + while (next_command_index < m_commands.size()) { + auto& command_with_scroll_id = m_commands[next_command_index++]; + auto& command = command_with_scroll_id.command; + auto bounding_rect = command_bounding_rectangle(command); + if (bounding_rect.has_value() && (bounding_rect->is_empty() || executor.would_be_fully_clipped_by_painter(*bounding_rect))) { + if (command.has()) { + auto const& sample_under_corners = command.get(); + skipped_sample_corner_commands.set(sample_under_corners.id); + } + continue; + } + + auto result = command.visit( + [&](DrawGlyphRun const& command) { + return executor.draw_glyph_run(command.glyph_run, command.color); + }, + [&](DrawText const& command) { + return executor.draw_text(command.rect, command.raw_text, command.alignment, command.color, + command.elision, command.wrapping, command.font); + }, + [&](FillRect const& command) { + return executor.fill_rect(command.rect, command.color); + }, + [&](DrawScaledBitmap const& command) { + return executor.draw_scaled_bitmap(command.dst_rect, command.bitmap, command.src_rect, + command.scaling_mode); + }, + [&](DrawScaledImmutableBitmap const& command) { + return executor.draw_scaled_immutable_bitmap(command.dst_rect, command.bitmap, command.src_rect, + command.scaling_mode); + }, + [&](SetClipRect const& command) { + return executor.set_clip_rect(command.rect); + }, + [&](ClearClipRect const&) { + return executor.clear_clip_rect(); + }, + [&](PushStackingContext const& command) { + return executor.push_stacking_context(command.opacity, command.is_fixed_position, + command.source_paintable_rect, + command.post_transform_translation, + command.image_rendering, command.transform, command.mask); + }, + [&](PopStackingContext const&) { + return executor.pop_stacking_context(); + }, + [&](PaintLinearGradient const& command) { + return executor.paint_linear_gradient(command.gradient_rect, command.linear_gradient_data); + }, + [&](PaintRadialGradient const& command) { + return executor.paint_radial_gradient(command.rect, command.radial_gradient_data, + command.center, command.size); + }, + [&](PaintConicGradient const& command) { + return executor.paint_conic_gradient(command.rect, command.conic_gradient_data, + command.position); + }, + [&](PaintOuterBoxShadow const& command) { + return executor.paint_outer_box_shadow(command.outer_box_shadow_params); + }, + [&](PaintInnerBoxShadow const& command) { + return executor.paint_inner_box_shadow(command.outer_box_shadow_params); + }, + [&](PaintTextShadow const& command) { + return executor.paint_text_shadow(command.blur_radius, command.shadow_bounding_rect, + command.text_rect, command.glyph_run, command.color, + command.fragment_baseline, command.draw_location); + }, + [&](FillRectWithRoundedCorners const& command) { + return executor.fill_rect_with_rounded_corners(command.rect, command.color, + command.top_left_radius, + command.top_right_radius, + command.bottom_left_radius, + command.bottom_right_radius); + }, + [&](FillPathUsingColor const& command) { + return executor.fill_path_using_color(command.path, command.color, command.winding_rule, + command.aa_translation); + }, + [&](FillPathUsingPaintStyle const& command) { + return executor.fill_path_using_paint_style(command.path, command.paint_style, + command.winding_rule, command.opacity, + command.aa_translation); + }, + [&](StrokePathUsingColor const& command) { + return executor.stroke_path_using_color(command.path, command.color, command.thickness, + command.aa_translation); + }, + [&](StrokePathUsingPaintStyle const& command) { + return executor.stroke_path_using_paint_style(command.path, command.paint_style, + command.thickness, command.opacity, + command.aa_translation); + }, + [&](DrawEllipse const& command) { + return executor.draw_ellipse(command.rect, command.color, command.thickness); + }, + [&](FillEllipse const& command) { + return executor.fill_ellipse(command.rect, command.color, command.blend_mode); + }, + [&](DrawLine const& command) { + return executor.draw_line(command.color, command.from, command.to, command.thickness, + command.style, command.alternate_color); + }, + [&](DrawSignedDistanceField const& command) { + return executor.draw_signed_distance_field(command.rect, command.color, command.sdf, + command.smoothing); + }, + [&](PaintFrame const& command) { + return executor.paint_frame(command.rect, command.palette, command.style); + }, + [&](ApplyBackdropFilter const& command) { + return executor.apply_backdrop_filter(command.backdrop_region, command.backdrop_filter); + }, + [&](DrawRect const& command) { + return executor.draw_rect(command.rect, command.color, command.rough); + }, + [&](DrawTriangleWave const& command) { + return executor.draw_triangle_wave(command.p1, command.p2, command.color, command.amplitude, + command.thickness); + }, + [&](SampleUnderCorners const& command) { + return executor.sample_under_corners(command.id, command.corner_radii, command.border_rect, + command.corner_clip); + }, + [&](BlitCornerClipping const& command) { + if (skipped_sample_corner_commands.contains(command.id)) { + // FIXME: If a sampling command falls outside the viewport and is not executed, the associated blit + // should also be skipped if it is within the viewport. In a properly generated list of + // painting commands, sample and blit commands should have matching rectangles, preventing + // this discrepancy. + dbgln("Skipping blit_corner_clipping command because the sample_under_corners command was skipped."); + return CommandResult::Continue; + } + return executor.blit_corner_clipping(command.id); + }, + [&](PaintBorders const& command) { + return executor.paint_borders(command.border_rect, command.corner_radii, command.borders_data); + }); + + if (result == CommandResult::SkipStackingContext) { + auto stacking_context_nesting_level = 1; + while (next_command_index < m_commands.size()) { + if (m_commands[next_command_index].command.has()) { + stacking_context_nesting_level++; + } else if (m_commands[next_command_index].command.has()) { + stacking_context_nesting_level--; + } + + next_command_index++; + + if (stacking_context_nesting_level == 0) + break; + } + } + } +} + +} diff --git a/Userland/Libraries/LibWeb/Painting/CommandList.h b/Userland/Libraries/LibWeb/Painting/CommandList.h new file mode 100644 index 0000000000..722093ac5f --- /dev/null +++ b/Userland/Libraries/LibWeb/Painting/CommandList.h @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2024, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Web::Painting { + +enum class CommandResult { + Continue, + SkipStackingContext, +}; + +class PaintingCommandExecutor { +public: + virtual ~PaintingCommandExecutor() = default; + + virtual CommandResult draw_glyph_run(Vector const& glyph_run, Color const&) = 0; + virtual CommandResult draw_text(Gfx::IntRect const&, String const&, Gfx::TextAlignment alignment, Color const&, Gfx::TextElision, Gfx::TextWrapping, Optional> const&) = 0; + virtual CommandResult fill_rect(Gfx::IntRect const&, Color const&) = 0; + virtual CommandResult draw_scaled_bitmap(Gfx::IntRect const& dst_rect, Gfx::Bitmap const& bitmap, Gfx::IntRect const& src_rect, Gfx::Painter::ScalingMode scaling_mode) = 0; + virtual CommandResult draw_scaled_immutable_bitmap(Gfx::IntRect const& dst_rect, Gfx::ImmutableBitmap const&, Gfx::IntRect const& src_rect, Gfx::Painter::ScalingMode scaling_mode) = 0; + virtual CommandResult set_clip_rect(Gfx::IntRect const& rect) = 0; + virtual CommandResult clear_clip_rect() = 0; + virtual CommandResult push_stacking_context(float opacity, bool is_fixed_position, Gfx::IntRect const& source_paintable_rect, Gfx::IntPoint post_transform_translation, CSS::ImageRendering image_rendering, StackingContextTransform transform, Optional mask) = 0; + virtual CommandResult pop_stacking_context() = 0; + virtual CommandResult paint_linear_gradient(Gfx::IntRect const&, LinearGradientData const&) = 0; + virtual CommandResult paint_radial_gradient(Gfx::IntRect const& rect, RadialGradientData const&, Gfx::IntPoint const& center, Gfx::IntSize const& size) = 0; + virtual CommandResult paint_conic_gradient(Gfx::IntRect const& rect, ConicGradientData const&, Gfx::IntPoint const& position) = 0; + virtual CommandResult paint_outer_box_shadow(PaintOuterBoxShadowParams const&) = 0; + virtual CommandResult paint_inner_box_shadow(PaintOuterBoxShadowParams const&) = 0; + virtual CommandResult paint_text_shadow(int blur_radius, Gfx::IntRect const& shadow_bounding_rect, Gfx::IntRect const& text_rect, Span, Color const&, int fragment_baseline, Gfx::IntPoint const& draw_location) = 0; + virtual CommandResult fill_rect_with_rounded_corners(Gfx::IntRect const& rect, Color const& color, + Gfx::AntiAliasingPainter::CornerRadius const& top_left_radius, + Gfx::AntiAliasingPainter::CornerRadius const& top_right_radius, + Gfx::AntiAliasingPainter::CornerRadius const& bottom_left_radius, + Gfx::AntiAliasingPainter::CornerRadius const& bottom_right_radius) + = 0; + virtual CommandResult fill_path_using_color(Gfx::Path const&, Color const& color, Gfx::Painter::WindingRule, Gfx::FloatPoint const& aa_translation) = 0; + virtual CommandResult fill_path_using_paint_style(Gfx::Path const&, Gfx::PaintStyle const& paint_style, Gfx::Painter::WindingRule winding_rule, float opacity, Gfx::FloatPoint const& aa_translation) = 0; + virtual CommandResult stroke_path_using_color(Gfx::Path const&, Color const& color, float thickness, Gfx::FloatPoint const& aa_translation) = 0; + virtual CommandResult stroke_path_using_paint_style(Gfx::Path const&, Gfx::PaintStyle const& paint_style, float thickness, float opacity, Gfx::FloatPoint const& aa_translation) = 0; + virtual CommandResult draw_ellipse(Gfx::IntRect const&, Color const&, int thickness) = 0; + virtual CommandResult fill_ellipse(Gfx::IntRect const&, Color const&, Gfx::AntiAliasingPainter::BlendMode blend_mode) = 0; + virtual CommandResult draw_line(Color const& color, Gfx::IntPoint const& from, Gfx::IntPoint const& to, int thickness, Gfx::Painter::LineStyle, Color const& alternate_color) = 0; + virtual CommandResult draw_signed_distance_field(Gfx::IntRect const& rect, Color const&, Gfx::GrayscaleBitmap const&, float smoothing) = 0; + virtual CommandResult paint_frame(Gfx::IntRect const& rect, Palette const&, Gfx::FrameStyle) = 0; + virtual CommandResult apply_backdrop_filter(Gfx::IntRect const& backdrop_region, Web::CSS::ResolvedBackdropFilter const& backdrop_filter) = 0; + virtual CommandResult draw_rect(Gfx::IntRect const& rect, Color const&, bool rough) = 0; + virtual CommandResult draw_triangle_wave(Gfx::IntPoint const& p1, Gfx::IntPoint const& p2, Color const& color, int amplitude, int thickness) = 0; + virtual CommandResult sample_under_corners(u32 id, CornerRadii const&, Gfx::IntRect const&, CornerClip) = 0; + virtual CommandResult blit_corner_clipping(u32 id) = 0; + virtual CommandResult paint_borders(DevicePixelRect const& border_rect, CornerRadii const& corner_radii, BordersDataDevicePixels const& borders_data) = 0; + virtual bool would_be_fully_clipped_by_painter(Gfx::IntRect) const = 0; + virtual bool needs_prepare_glyphs_texture() const { return false; } + virtual void prepare_glyph_texture(HashMap> const& unique_glyphs) = 0; + virtual void prepare_to_execute() { } + virtual bool needs_update_immutable_bitmap_texture_cache() const = 0; + virtual void update_immutable_bitmap_texture_cache(HashMap&) = 0; +}; + +class CommandList { +public: + void append(Command&& command, Optional scroll_frame_id); + + void apply_scroll_offsets(Vector const& offsets_by_frame_id); + void execute(PaintingCommandExecutor&); + +private: + struct CommandWithScrollFrame { + Optional scroll_frame_id; + Command command; + }; + + AK::SegmentedVector m_commands; +}; + +} diff --git a/Userland/Libraries/LibWeb/Painting/RecordingPainter.cpp b/Userland/Libraries/LibWeb/Painting/RecordingPainter.cpp index 5e4624580c..3faa2a4a38 100644 --- a/Userland/Libraries/LibWeb/Painting/RecordingPainter.cpp +++ b/Userland/Libraries/LibWeb/Painting/RecordingPainter.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Aliaksandr Kalenik + * Copyright (c) 2023-2024, Aliaksandr Kalenik * * SPDX-License-Identifier: BSD-2-Clause */ @@ -9,44 +9,20 @@ namespace Web::Painting { -void DrawGlyphRun::translate_by(Gfx::IntPoint const& offset) +RecordingPainter::RecordingPainter(CommandList& command_list) + : m_command_list(command_list) { - for (auto& glyph : glyph_run) { - glyph.visit([&](auto& glyph) { - glyph.translate_by(offset.to_type()); - }); - } - rect.translate_by(offset); + m_state_stack.append(State()); } -Gfx::IntRect PaintOuterBoxShadow::bounding_rect() const +void RecordingPainter::append(Command&& command) { - return get_outer_box_shadow_bounding_rect(outer_box_shadow_params); -} - -void PaintOuterBoxShadow::translate_by(Gfx::IntPoint const& offset) -{ - outer_box_shadow_params.device_content_rect.translate_by(offset.to_type()); -} - -void PaintInnerBoxShadow::translate_by(Gfx::IntPoint const& offset) -{ - outer_box_shadow_params.device_content_rect.translate_by(offset.to_type()); -} - -Gfx::IntRect SampleUnderCorners::bounding_rect() const -{ - return border_rect; -} - -Gfx::IntRect BlitCornerClipping::bounding_rect() const -{ - return border_rect; + m_command_list.append(move(command), state().scroll_frame_id); } void RecordingPainter::sample_under_corners(u32 id, CornerRadii corner_radii, Gfx::IntRect border_rect, CornerClip corner_clip) { - push_command(SampleUnderCorners { + append(SampleUnderCorners { id, corner_radii, border_rect = state().translation.map(border_rect), @@ -55,12 +31,12 @@ void RecordingPainter::sample_under_corners(u32 id, CornerRadii corner_radii, Gf void RecordingPainter::blit_corner_clipping(u32 id, Gfx::IntRect border_rect) { - push_command(BlitCornerClipping { id, border_rect = state().translation.map(border_rect) }); + append(BlitCornerClipping { id, border_rect = state().translation.map(border_rect) }); } void RecordingPainter::fill_rect(Gfx::IntRect const& rect, Color color) { - push_command(FillRect { + append(FillRect { .rect = state().translation.map(rect), .color = color, }); @@ -70,7 +46,7 @@ void RecordingPainter::fill_path(FillPathUsingColorParams params) { auto aa_translation = state().translation.map(params.translation.value_or(Gfx::FloatPoint {})); auto path_bounding_rect = params.path.bounding_box().translated(aa_translation).to_type(); - push_command(FillPathUsingColor { + append(FillPathUsingColor { .path_bounding_rect = path_bounding_rect, .path = params.path, .color = params.color, @@ -83,7 +59,7 @@ void RecordingPainter::fill_path(FillPathUsingPaintStyleParams params) { auto aa_translation = state().translation.map(params.translation.value_or(Gfx::FloatPoint {})); auto path_bounding_rect = params.path.bounding_box().translated(aa_translation).to_type(); - push_command(FillPathUsingPaintStyle { + append(FillPathUsingPaintStyle { .path_bounding_rect = path_bounding_rect, .path = params.path, .paint_style = params.paint_style, @@ -99,7 +75,7 @@ void RecordingPainter::stroke_path(StrokePathUsingColorParams params) auto path_bounding_rect = params.path.bounding_box().translated(aa_translation).to_type(); // Increase path bounding box by `thickness` to account for stroke. path_bounding_rect.inflate(params.thickness, params.thickness); - push_command(StrokePathUsingColor { + append(StrokePathUsingColor { .path_bounding_rect = path_bounding_rect, .path = params.path, .color = params.color, @@ -114,7 +90,7 @@ void RecordingPainter::stroke_path(StrokePathUsingPaintStyleParams params) auto path_bounding_rect = params.path.bounding_box().translated(aa_translation).to_type(); // Increase path bounding box by `thickness` to account for stroke. path_bounding_rect.inflate(params.thickness, params.thickness); - push_command(StrokePathUsingPaintStyle { + append(StrokePathUsingPaintStyle { .path_bounding_rect = path_bounding_rect, .path = params.path, .paint_style = params.paint_style, @@ -126,7 +102,7 @@ void RecordingPainter::stroke_path(StrokePathUsingPaintStyleParams params) void RecordingPainter::draw_ellipse(Gfx::IntRect const& a_rect, Color color, int thickness) { - push_command(DrawEllipse { + append(DrawEllipse { .rect = state().translation.map(a_rect), .color = color, .thickness = thickness, @@ -135,7 +111,7 @@ void RecordingPainter::draw_ellipse(Gfx::IntRect const& a_rect, Color color, int void RecordingPainter::fill_ellipse(Gfx::IntRect const& a_rect, Color color, Gfx::AntiAliasingPainter::BlendMode blend_mode) { - push_command(FillEllipse { + append(FillEllipse { .rect = state().translation.map(a_rect), .color = color, .blend_mode = blend_mode, @@ -144,7 +120,7 @@ void RecordingPainter::fill_ellipse(Gfx::IntRect const& a_rect, Color color, Gfx void RecordingPainter::fill_rect_with_linear_gradient(Gfx::IntRect const& gradient_rect, LinearGradientData const& data) { - push_command(PaintLinearGradient { + append(PaintLinearGradient { .gradient_rect = state().translation.map(gradient_rect), .linear_gradient_data = data, }); @@ -152,7 +128,7 @@ void RecordingPainter::fill_rect_with_linear_gradient(Gfx::IntRect const& gradie void RecordingPainter::fill_rect_with_conic_gradient(Gfx::IntRect const& rect, ConicGradientData const& data, Gfx::IntPoint const& position) { - push_command(PaintConicGradient { + append(PaintConicGradient { .rect = state().translation.map(rect), .conic_gradient_data = data, .position = position }); @@ -160,7 +136,7 @@ void RecordingPainter::fill_rect_with_conic_gradient(Gfx::IntRect const& rect, C void RecordingPainter::fill_rect_with_radial_gradient(Gfx::IntRect const& rect, RadialGradientData const& data, Gfx::IntPoint center, Gfx::IntSize size) { - push_command(PaintRadialGradient { + append(PaintRadialGradient { .rect = state().translation.map(rect), .radial_gradient_data = data, .center = center, @@ -169,7 +145,7 @@ void RecordingPainter::fill_rect_with_radial_gradient(Gfx::IntRect const& rect, void RecordingPainter::draw_rect(Gfx::IntRect const& rect, Color color, bool rough) { - push_command(DrawRect { + append(DrawRect { .rect = state().translation.map(rect), .color = color, .rough = rough }); @@ -177,7 +153,7 @@ void RecordingPainter::draw_rect(Gfx::IntRect const& rect, Color color, bool rou void RecordingPainter::draw_scaled_bitmap(Gfx::IntRect const& dst_rect, Gfx::Bitmap const& bitmap, Gfx::IntRect const& src_rect, Gfx::Painter::ScalingMode scaling_mode) { - push_command(DrawScaledBitmap { + append(DrawScaledBitmap { .dst_rect = state().translation.map(dst_rect), .bitmap = bitmap, .src_rect = src_rect, @@ -187,7 +163,7 @@ void RecordingPainter::draw_scaled_bitmap(Gfx::IntRect const& dst_rect, Gfx::Bit void RecordingPainter::draw_scaled_immutable_bitmap(Gfx::IntRect const& dst_rect, Gfx::ImmutableBitmap const& bitmap, Gfx::IntRect const& src_rect, Gfx::Painter::ScalingMode scaling_mode) { - push_command(DrawScaledImmutableBitmap { + append(DrawScaledImmutableBitmap { .dst_rect = state().translation.map(dst_rect), .bitmap = bitmap, .src_rect = src_rect, @@ -197,7 +173,7 @@ void RecordingPainter::draw_scaled_immutable_bitmap(Gfx::IntRect const& dst_rect void RecordingPainter::draw_line(Gfx::IntPoint from, Gfx::IntPoint to, Color color, int thickness, Gfx::Painter::LineStyle style, Color alternate_color) { - push_command(DrawLine { + append(DrawLine { .color = color, .from = state().translation.map(from), .to = state().translation.map(to), @@ -209,7 +185,7 @@ void RecordingPainter::draw_line(Gfx::IntPoint from, Gfx::IntPoint to, Color col void RecordingPainter::draw_text(Gfx::IntRect const& rect, String raw_text, Gfx::Font const& font, Gfx::TextAlignment alignment, Color color, Gfx::TextElision elision, Gfx::TextWrapping wrapping) { - push_command(DrawText { + append(DrawText { .rect = state().translation.map(rect), .raw_text = move(raw_text), .alignment = alignment, @@ -222,7 +198,7 @@ void RecordingPainter::draw_text(Gfx::IntRect const& rect, String raw_text, Gfx: void RecordingPainter::draw_signed_distance_field(Gfx::IntRect const& dst_rect, Color color, Gfx::GrayscaleBitmap const& sdf, float smoothing) { - push_command(DrawSignedDistanceField { + append(DrawSignedDistanceField { .rect = state().translation.map(dst_rect), .color = color, .sdf = sdf, @@ -239,7 +215,7 @@ void RecordingPainter::draw_text_run(Gfx::IntPoint baseline_start, Span()).to_type(); - push_command(PaintOuterBoxShadow { + append(PaintOuterBoxShadow { .outer_box_shadow_params = params, }); } void RecordingPainter::paint_inner_box_shadow_params(PaintOuterBoxShadowParams params) { - push_command(PaintInnerBoxShadow { + append(PaintInnerBoxShadow { .outer_box_shadow_params = params, }); } void RecordingPainter::paint_text_shadow(int blur_radius, Gfx::IntRect bounding_rect, Gfx::IntRect text_rect, Span glyph_run, Color color, int fragment_baseline, Gfx::IntPoint draw_location) { - push_command(PaintTextShadow { + append(PaintTextShadow { .blur_radius = blur_radius, .shadow_bounding_rect = bounding_rect, .text_rect = text_rect, @@ -362,7 +338,7 @@ void RecordingPainter::fill_rect_with_rounded_corners(Gfx::IntRect const& rect, return; } - push_command(FillRectWithRoundedCorners { + append(FillRectWithRoundedCorners { .rect = state().translation.map(rect), .color = color, .top_left_radius = top_left_radius, @@ -388,7 +364,7 @@ void RecordingPainter::fill_rect_with_rounded_corners(Gfx::IntRect const& a_rect void RecordingPainter::draw_triangle_wave(Gfx::IntPoint a_p1, Gfx::IntPoint a_p2, Color color, int amplitude, int thickness = 1) { - push_command(DrawTriangleWave { + append(DrawTriangleWave { .p1 = state().translation.map(a_p1), .p2 = state().translation.map(a_p2), .color = color, @@ -400,200 +376,7 @@ void RecordingPainter::paint_borders(DevicePixelRect const& border_rect, CornerR { if (borders_data.top.width == 0 && borders_data.right.width == 0 && borders_data.bottom.width == 0 && borders_data.left.width == 0) return; - push_command(PaintBorders { border_rect, corner_radii, borders_data }); -} - -static Optional command_bounding_rectangle(PaintingCommand const& command) -{ - return command.visit( - [&](auto const& command) -> Optional { - if constexpr (requires { command.bounding_rect(); }) - return command.bounding_rect(); - else - return {}; - }); -} - -void RecordingPainter::apply_scroll_offsets(Vector const& offsets_by_frame_id) -{ - for (auto& command_with_scroll_id : m_painting_commands) { - if (command_with_scroll_id.scroll_frame_id.has_value()) { - auto const& scroll_frame_id = command_with_scroll_id.scroll_frame_id.value(); - auto const& scroll_offset = offsets_by_frame_id[scroll_frame_id]; - command_with_scroll_id.command.visit( - [&](auto& command) { - if constexpr (requires { command.translate_by(scroll_offset); }) - command.translate_by(scroll_offset); - }); - } - } -} - -void RecordingPainter::execute(PaintingCommandExecutor& executor) -{ - executor.prepare_to_execute(); - - if (executor.needs_prepare_glyphs_texture()) { - HashMap> unique_glyphs; - for (auto& command_with_scroll_id : m_painting_commands) { - auto& command = command_with_scroll_id.command; - if (command.has()) { - for (auto const& glyph_or_emoji : command.get().glyph_run) { - if (glyph_or_emoji.has()) { - auto const& glyph = glyph_or_emoji.get(); - unique_glyphs.ensure(glyph.font, [] { return HashTable {}; }).set(glyph.code_point); - } - } - } - } - executor.prepare_glyph_texture(unique_glyphs); - } - - if (executor.needs_update_immutable_bitmap_texture_cache()) { - HashMap immutable_bitmaps; - for (auto const& command_with_scroll_id : m_painting_commands) { - auto& command = command_with_scroll_id.command; - if (command.has()) { - auto const& immutable_bitmap = command.get().bitmap; - immutable_bitmaps.set(immutable_bitmap->id(), immutable_bitmap.ptr()); - } - } - executor.update_immutable_bitmap_texture_cache(immutable_bitmaps); - } - - HashTable skipped_sample_corner_commands; - size_t next_command_index = 0; - while (next_command_index < m_painting_commands.size()) { - auto& command_with_scroll_id = m_painting_commands[next_command_index++]; - auto& command = command_with_scroll_id.command; - auto bounding_rect = command_bounding_rectangle(command); - if (bounding_rect.has_value() && (bounding_rect->is_empty() || executor.would_be_fully_clipped_by_painter(*bounding_rect))) { - if (command.has()) { - auto const& sample_under_corners = command.get(); - skipped_sample_corner_commands.set(sample_under_corners.id); - } - continue; - } - - auto result = command.visit( - [&](DrawGlyphRun const& command) { - return executor.draw_glyph_run(command.glyph_run, command.color); - }, - [&](DrawText const& command) { - return executor.draw_text(command.rect, command.raw_text, command.alignment, command.color, command.elision, command.wrapping, command.font); - }, - [&](FillRect const& command) { - return executor.fill_rect(command.rect, command.color); - }, - [&](DrawScaledBitmap const& command) { - return executor.draw_scaled_bitmap(command.dst_rect, command.bitmap, command.src_rect, command.scaling_mode); - }, - [&](DrawScaledImmutableBitmap const& command) { - return executor.draw_scaled_immutable_bitmap(command.dst_rect, command.bitmap, command.src_rect, command.scaling_mode); - }, - [&](SetClipRect const& command) { - return executor.set_clip_rect(command.rect); - }, - [&](ClearClipRect const&) { - return executor.clear_clip_rect(); - }, - [&](PushStackingContext const& command) { - return executor.push_stacking_context(command.opacity, command.is_fixed_position, command.source_paintable_rect, command.post_transform_translation, command.image_rendering, command.transform, command.mask); - }, - [&](PopStackingContext const&) { - return executor.pop_stacking_context(); - }, - [&](PaintLinearGradient const& command) { - return executor.paint_linear_gradient(command.gradient_rect, command.linear_gradient_data); - }, - [&](PaintRadialGradient const& command) { - return executor.paint_radial_gradient(command.rect, command.radial_gradient_data, command.center, command.size); - }, - [&](PaintConicGradient const& command) { - return executor.paint_conic_gradient(command.rect, command.conic_gradient_data, command.position); - }, - [&](PaintOuterBoxShadow const& command) { - return executor.paint_outer_box_shadow(command.outer_box_shadow_params); - }, - [&](PaintInnerBoxShadow const& command) { - return executor.paint_inner_box_shadow(command.outer_box_shadow_params); - }, - [&](PaintTextShadow const& command) { - return executor.paint_text_shadow(command.blur_radius, command.shadow_bounding_rect, command.text_rect, command.glyph_run, command.color, command.fragment_baseline, command.draw_location); - }, - [&](FillRectWithRoundedCorners const& command) { - return executor.fill_rect_with_rounded_corners(command.rect, command.color, command.top_left_radius, command.top_right_radius, command.bottom_left_radius, command.bottom_right_radius); - }, - [&](FillPathUsingColor const& command) { - return executor.fill_path_using_color(command.path, command.color, command.winding_rule, command.aa_translation); - }, - [&](FillPathUsingPaintStyle const& command) { - return executor.fill_path_using_paint_style(command.path, command.paint_style, command.winding_rule, command.opacity, command.aa_translation); - }, - [&](StrokePathUsingColor const& command) { - return executor.stroke_path_using_color(command.path, command.color, command.thickness, command.aa_translation); - }, - [&](StrokePathUsingPaintStyle const& command) { - return executor.stroke_path_using_paint_style(command.path, command.paint_style, command.thickness, command.opacity, command.aa_translation); - }, - [&](DrawEllipse const& command) { - return executor.draw_ellipse(command.rect, command.color, command.thickness); - }, - [&](FillEllipse const& command) { - return executor.fill_ellipse(command.rect, command.color, command.blend_mode); - }, - [&](DrawLine const& command) { - return executor.draw_line(command.color, command.from, command.to, command.thickness, command.style, command.alternate_color); - }, - [&](DrawSignedDistanceField const& command) { - return executor.draw_signed_distance_field(command.rect, command.color, command.sdf, command.smoothing); - }, - [&](PaintFrame const& command) { - return executor.paint_frame(command.rect, command.palette, command.style); - }, - [&](ApplyBackdropFilter const& command) { - return executor.apply_backdrop_filter(command.backdrop_region, command.backdrop_filter); - }, - [&](DrawRect const& command) { - return executor.draw_rect(command.rect, command.color, command.rough); - }, - [&](DrawTriangleWave const& command) { - return executor.draw_triangle_wave(command.p1, command.p2, command.color, command.amplitude, command.thickness); - }, - [&](SampleUnderCorners const& command) { - return executor.sample_under_corners(command.id, command.corner_radii, command.border_rect, command.corner_clip); - }, - [&](BlitCornerClipping const& command) { - if (skipped_sample_corner_commands.contains(command.id)) { - // FIXME: If a sampling command falls outside the viewport and is not executed, the associated blit - // should also be skipped if it is within the viewport. In a properly generated list of - // painting commands, sample and blit commands should have matching rectangles, preventing - // this discrepancy. - dbgln("Skipping blit_corner_clipping command because the sample_under_corners command was skipped."); - return CommandResult::Continue; - } - return executor.blit_corner_clipping(command.id); - }, - [&](PaintBorders const& command) { - return executor.paint_borders(command.border_rect, command.corner_radii, command.borders_data); - }); - - if (result == CommandResult::SkipStackingContext) { - auto stacking_context_nesting_level = 1; - while (next_command_index < m_painting_commands.size()) { - if (m_painting_commands[next_command_index].command.has()) { - stacking_context_nesting_level++; - } else if (m_painting_commands[next_command_index].command.has()) { - stacking_context_nesting_level--; - } - - next_command_index++; - - if (stacking_context_nesting_level == 0) - break; - } - } - } + append(PaintBorders { border_rect, corner_radii, borders_data }); } } diff --git a/Userland/Libraries/LibWeb/Painting/RecordingPainter.h b/Userland/Libraries/LibWeb/Painting/RecordingPainter.h index d6dd682046..a320b5be57 100644 --- a/Userland/Libraries/LibWeb/Painting/RecordingPainter.h +++ b/Userland/Libraries/LibWeb/Painting/RecordingPainter.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Aliaksandr Kalenik + * Copyright (c) 2023-2024, Aliaksandr Kalenik * * SPDX-License-Identifier: BSD-2-Clause */ @@ -32,461 +32,17 @@ #include #include #include +#include +#include #include #include namespace Web::Painting { -enum class CommandResult { - Continue, - SkipStackingContext, -}; - -struct DrawGlyphRun { - Vector glyph_run; - Color color; - Gfx::IntRect rect; - - [[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; } - - void translate_by(Gfx::IntPoint const& offset); -}; - -struct DrawText { - Gfx::IntRect rect; - String raw_text; - Gfx::TextAlignment alignment; - Color color; - Gfx::TextElision elision; - Gfx::TextWrapping wrapping; - Optional> font {}; - - [[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; } - void translate_by(Gfx::IntPoint const& offset) { rect.translate_by(offset); } -}; - -struct FillRect { - Gfx::IntRect rect; - Color color; - - [[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; } - void translate_by(Gfx::IntPoint const& offset) { rect.translate_by(offset); } -}; - -struct DrawScaledBitmap { - Gfx::IntRect dst_rect; - NonnullRefPtr bitmap; - Gfx::IntRect src_rect; - Gfx::Painter::ScalingMode scaling_mode; - - [[nodiscard]] Gfx::IntRect bounding_rect() const { return dst_rect; } - void translate_by(Gfx::IntPoint const& offset) { dst_rect.translate_by(offset); } -}; - -struct DrawScaledImmutableBitmap { - Gfx::IntRect dst_rect; - NonnullRefPtr bitmap; - Gfx::IntRect src_rect; - Gfx::Painter::ScalingMode scaling_mode; - - [[nodiscard]] Gfx::IntRect bounding_rect() const { return dst_rect; } - void translate_by(Gfx::IntPoint const& offset) { dst_rect.translate_by(offset); } -}; - -struct SetClipRect { - Gfx::IntRect rect; -}; - -struct ClearClipRect { }; - -struct StackingContextTransform { - Gfx::FloatPoint origin; - Gfx::FloatMatrix4x4 matrix; -}; - -struct StackingContextMask { - NonnullRefPtr mask_bitmap; - Gfx::Bitmap::MaskKind mask_kind; -}; - -struct PushStackingContext { - float opacity; - bool is_fixed_position; - // The bounding box of the source paintable (pre-transform). - Gfx::IntRect source_paintable_rect; - // A translation to be applied after the stacking context has been transformed. - Gfx::IntPoint post_transform_translation; - CSS::ImageRendering image_rendering; - StackingContextTransform transform; - Optional mask = {}; - - void translate_by(Gfx::IntPoint const& offset) - { - source_paintable_rect.translate_by(offset); - } -}; - -struct PopStackingContext { }; - -struct PaintLinearGradient { - Gfx::IntRect gradient_rect; - LinearGradientData linear_gradient_data; - - [[nodiscard]] Gfx::IntRect bounding_rect() const { return gradient_rect; } - - void translate_by(Gfx::IntPoint const& offset) - { - gradient_rect.translate_by(offset); - } -}; - -struct PaintOuterBoxShadow { - PaintOuterBoxShadowParams outer_box_shadow_params; - - [[nodiscard]] Gfx::IntRect bounding_rect() const; - void translate_by(Gfx::IntPoint const& offset); -}; - -struct PaintInnerBoxShadow { - PaintOuterBoxShadowParams outer_box_shadow_params; - - void translate_by(Gfx::IntPoint const& offset); -}; - -struct PaintTextShadow { - int blur_radius; - Gfx::IntRect shadow_bounding_rect; - Gfx::IntRect text_rect; - Vector glyph_run; - Color color; - int fragment_baseline; - Gfx::IntPoint draw_location; - - [[nodiscard]] Gfx::IntRect bounding_rect() const { return { draw_location, shadow_bounding_rect.size() }; } - void translate_by(Gfx::IntPoint const& offset) { draw_location.translate_by(offset); } -}; - -struct FillRectWithRoundedCorners { - Gfx::IntRect rect; - Color color; - Gfx::AntiAliasingPainter::CornerRadius top_left_radius; - Gfx::AntiAliasingPainter::CornerRadius top_right_radius; - Gfx::AntiAliasingPainter::CornerRadius bottom_left_radius; - Gfx::AntiAliasingPainter::CornerRadius bottom_right_radius; - - [[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; } - void translate_by(Gfx::IntPoint const& offset) { rect.translate_by(offset); } -}; - -struct FillPathUsingColor { - Gfx::IntRect path_bounding_rect; - Gfx::Path path; - Color color; - Gfx::Painter::WindingRule winding_rule; - Gfx::FloatPoint aa_translation; - - [[nodiscard]] Gfx::IntRect bounding_rect() const { return path_bounding_rect; } - - void translate_by(Gfx::IntPoint const& offset) - { - path_bounding_rect.translate_by(offset); - aa_translation.translate_by(offset.to_type()); - } -}; - -struct FillPathUsingPaintStyle { - Gfx::IntRect path_bounding_rect; - Gfx::Path path; - NonnullRefPtr paint_style; - Gfx::Painter::WindingRule winding_rule; - float opacity; - Gfx::FloatPoint aa_translation; - - [[nodiscard]] Gfx::IntRect bounding_rect() const { return path_bounding_rect; } - - void translate_by(Gfx::IntPoint const& offset) - { - path_bounding_rect.translate_by(offset); - aa_translation.translate_by(offset.to_type()); - } -}; - -struct StrokePathUsingColor { - Gfx::IntRect path_bounding_rect; - Gfx::Path path; - Color color; - float thickness; - Gfx::FloatPoint aa_translation; - - [[nodiscard]] Gfx::IntRect bounding_rect() const { return path_bounding_rect; } - - void translate_by(Gfx::IntPoint const& offset) - { - path_bounding_rect.translate_by(offset); - aa_translation.translate_by(offset.to_type()); - } -}; - -struct StrokePathUsingPaintStyle { - Gfx::IntRect path_bounding_rect; - Gfx::Path path; - NonnullRefPtr paint_style; - float thickness; - float opacity = 1.0f; - Gfx::FloatPoint aa_translation; - - [[nodiscard]] Gfx::IntRect bounding_rect() const { return path_bounding_rect; } - - void translate_by(Gfx::IntPoint const& offset) - { - path_bounding_rect.translate_by(offset); - aa_translation.translate_by(offset.to_type()); - } -}; - -struct DrawEllipse { - Gfx::IntRect rect; - Color color; - int thickness; - - [[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; } - - void translate_by(Gfx::IntPoint const& offset) - { - rect.translate_by(offset); - } -}; - -struct FillEllipse { - Gfx::IntRect rect; - Color color; - Gfx::AntiAliasingPainter::BlendMode blend_mode; - - [[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; } - - void translate_by(Gfx::IntPoint const& offset) - { - rect.translate_by(offset); - } -}; - -struct DrawLine { - Color color; - Gfx::IntPoint from; - Gfx::IntPoint to; - int thickness; - Gfx::Painter::LineStyle style; - Color alternate_color; - - void translate_by(Gfx::IntPoint const& offset) - { - from.translate_by(offset); - to.translate_by(offset); - } -}; - -struct DrawSignedDistanceField { - Gfx::IntRect rect; - Color color; - Gfx::GrayscaleBitmap sdf; - float smoothing; - - [[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; } - - void translate_by(Gfx::IntPoint const& offset) - { - rect.translate_by(offset); - } -}; - -struct PaintFrame { - Gfx::IntRect rect; - Palette palette; - Gfx::FrameStyle style; - - [[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; } - - void translate_by(Gfx::IntPoint const& offset) { rect.translate_by(offset); } -}; - -struct ApplyBackdropFilter { - Gfx::IntRect backdrop_region; - BorderRadiiData border_radii_data; - CSS::ResolvedBackdropFilter backdrop_filter; - - [[nodiscard]] Gfx::IntRect bounding_rect() const { return backdrop_region; } - - void translate_by(Gfx::IntPoint const& offset) - { - backdrop_region.translate_by(offset); - } -}; - -struct DrawRect { - Gfx::IntRect rect; - Color color; - bool rough; - - [[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; } - - void translate_by(Gfx::IntPoint const& offset) { rect.translate_by(offset); } -}; - -struct PaintRadialGradient { - Gfx::IntRect rect; - RadialGradientData radial_gradient_data; - Gfx::IntPoint center; - Gfx::IntSize size; - - [[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; } - - void translate_by(Gfx::IntPoint const& offset) { rect.translate_by(offset); } -}; - -struct PaintConicGradient { - Gfx::IntRect rect; - ConicGradientData conic_gradient_data; - Gfx::IntPoint position; - - [[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; } - - void translate_by(Gfx::IntPoint const& offset) { rect.translate_by(offset); } -}; - -struct DrawTriangleWave { - Gfx::IntPoint p1; - Gfx::IntPoint p2; - Color color; - int amplitude; - int thickness; - - void translate_by(Gfx::IntPoint const& offset) - { - p1.translate_by(offset); - p2.translate_by(offset); - } -}; - -struct SampleUnderCorners { - u32 id; - CornerRadii corner_radii; - Gfx::IntRect border_rect; - CornerClip corner_clip; - - [[nodiscard]] Gfx::IntRect bounding_rect() const; - - void translate_by(Gfx::IntPoint const& offset) - { - border_rect.translate_by(offset); - } -}; - -struct BlitCornerClipping { - u32 id; - Gfx::IntRect border_rect; - - [[nodiscard]] Gfx::IntRect bounding_rect() const; - - void translate_by(Gfx::IntPoint const& offset) - { - border_rect.translate_by(offset); - } -}; - -struct PaintBorders { - DevicePixelRect border_rect; - CornerRadii corner_radii; - BordersDataDevicePixels borders_data; - - [[nodiscard]] Gfx::IntRect bounding_rect() const { return border_rect.to_type(); } - - void translate_by(Gfx::IntPoint const& offset) - { - border_rect.translate_by(offset.to_type()); - } -}; - -using PaintingCommand = Variant< - DrawGlyphRun, - DrawText, - FillRect, - DrawScaledBitmap, - DrawScaledImmutableBitmap, - SetClipRect, - ClearClipRect, - PushStackingContext, - PopStackingContext, - PaintLinearGradient, - PaintRadialGradient, - PaintConicGradient, - PaintOuterBoxShadow, - PaintInnerBoxShadow, - PaintTextShadow, - FillRectWithRoundedCorners, - FillPathUsingColor, - FillPathUsingPaintStyle, - StrokePathUsingColor, - StrokePathUsingPaintStyle, - DrawEllipse, - FillEllipse, - DrawLine, - DrawSignedDistanceField, - PaintFrame, - ApplyBackdropFilter, - DrawRect, - DrawTriangleWave, - SampleUnderCorners, - BlitCornerClipping, - PaintBorders>; - -class PaintingCommandExecutor { -public: - virtual ~PaintingCommandExecutor() = default; - - virtual CommandResult draw_glyph_run(Vector const& glyph_run, Color const&) = 0; - virtual CommandResult draw_text(Gfx::IntRect const&, String const&, Gfx::TextAlignment alignment, Color const&, Gfx::TextElision, Gfx::TextWrapping, Optional> const&) = 0; - virtual CommandResult fill_rect(Gfx::IntRect const&, Color const&) = 0; - virtual CommandResult draw_scaled_bitmap(Gfx::IntRect const& dst_rect, Gfx::Bitmap const& bitmap, Gfx::IntRect const& src_rect, Gfx::Painter::ScalingMode scaling_mode) = 0; - virtual CommandResult draw_scaled_immutable_bitmap(Gfx::IntRect const& dst_rect, Gfx::ImmutableBitmap const&, Gfx::IntRect const& src_rect, Gfx::Painter::ScalingMode scaling_mode) = 0; - virtual CommandResult set_clip_rect(Gfx::IntRect const& rect) = 0; - virtual CommandResult clear_clip_rect() = 0; - virtual CommandResult push_stacking_context(float opacity, bool is_fixed_position, Gfx::IntRect const& source_paintable_rect, Gfx::IntPoint post_transform_translation, CSS::ImageRendering image_rendering, StackingContextTransform transform, Optional mask) = 0; - virtual CommandResult pop_stacking_context() = 0; - virtual CommandResult paint_linear_gradient(Gfx::IntRect const&, LinearGradientData const&) = 0; - virtual CommandResult paint_radial_gradient(Gfx::IntRect const& rect, RadialGradientData const&, Gfx::IntPoint const& center, Gfx::IntSize const& size) = 0; - virtual CommandResult paint_conic_gradient(Gfx::IntRect const& rect, ConicGradientData const&, Gfx::IntPoint const& position) = 0; - virtual CommandResult paint_outer_box_shadow(PaintOuterBoxShadowParams const&) = 0; - virtual CommandResult paint_inner_box_shadow(PaintOuterBoxShadowParams const&) = 0; - virtual CommandResult paint_text_shadow(int blur_radius, Gfx::IntRect const& shadow_bounding_rect, Gfx::IntRect const& text_rect, Span, Color const&, int fragment_baseline, Gfx::IntPoint const& draw_location) = 0; - virtual CommandResult fill_rect_with_rounded_corners(Gfx::IntRect const& rect, Color const& color, Gfx::AntiAliasingPainter::CornerRadius const& top_left_radius, Gfx::AntiAliasingPainter::CornerRadius const& top_right_radius, Gfx::AntiAliasingPainter::CornerRadius const& bottom_left_radius, Gfx::AntiAliasingPainter::CornerRadius const& bottom_right_radius) = 0; - virtual CommandResult fill_path_using_color(Gfx::Path const&, Color const& color, Gfx::Painter::WindingRule, Gfx::FloatPoint const& aa_translation) = 0; - virtual CommandResult fill_path_using_paint_style(Gfx::Path const&, Gfx::PaintStyle const& paint_style, Gfx::Painter::WindingRule winding_rule, float opacity, Gfx::FloatPoint const& aa_translation) = 0; - virtual CommandResult stroke_path_using_color(Gfx::Path const&, Color const& color, float thickness, Gfx::FloatPoint const& aa_translation) = 0; - virtual CommandResult stroke_path_using_paint_style(Gfx::Path const&, Gfx::PaintStyle const& paint_style, float thickness, float opacity, Gfx::FloatPoint const& aa_translation) = 0; - virtual CommandResult draw_ellipse(Gfx::IntRect const&, Color const&, int thickness) = 0; - virtual CommandResult fill_ellipse(Gfx::IntRect const&, Color const&, Gfx::AntiAliasingPainter::BlendMode blend_mode) = 0; - virtual CommandResult draw_line(Color const& color, Gfx::IntPoint const& from, Gfx::IntPoint const& to, int thickness, Gfx::Painter::LineStyle, Color const& alternate_color) = 0; - virtual CommandResult draw_signed_distance_field(Gfx::IntRect const& rect, Color const&, Gfx::GrayscaleBitmap const&, float smoothing) = 0; - virtual CommandResult paint_frame(Gfx::IntRect const& rect, Palette const&, Gfx::FrameStyle) = 0; - virtual CommandResult apply_backdrop_filter(Gfx::IntRect const& backdrop_region, Web::CSS::ResolvedBackdropFilter const& backdrop_filter) = 0; - virtual CommandResult draw_rect(Gfx::IntRect const& rect, Color const&, bool rough) = 0; - virtual CommandResult draw_triangle_wave(Gfx::IntPoint const& p1, Gfx::IntPoint const& p2, Color const& color, int amplitude, int thickness) = 0; - virtual CommandResult sample_under_corners(u32 id, CornerRadii const&, Gfx::IntRect const&, CornerClip) = 0; - virtual CommandResult blit_corner_clipping(u32 id) = 0; - virtual CommandResult paint_borders(DevicePixelRect const& border_rect, CornerRadii const& corner_radii, BordersDataDevicePixels const& borders_data) = 0; - - virtual bool would_be_fully_clipped_by_painter(Gfx::IntRect) const = 0; - - virtual bool needs_prepare_glyphs_texture() const { return false; } - virtual void prepare_glyph_texture(HashMap> const& unique_glyphs) = 0; - - virtual void prepare_to_execute() { } - - virtual bool needs_update_immutable_bitmap_texture_cache() const = 0; - virtual void update_immutable_bitmap_texture_cache(HashMap&) = 0; -}; - class RecordingPainter { + AK_MAKE_NONCOPYABLE(RecordingPainter); + AK_MAKE_NONMOVABLE(RecordingPainter); + public: void fill_rect(Gfx::IntRect const& rect, Color color); @@ -589,14 +145,11 @@ public: void paint_borders(DevicePixelRect const& border_rect, CornerRadii const& corner_radii, BordersDataDevicePixels const& borders_data); - void execute(PaintingCommandExecutor&); + RecordingPainter(CommandList& commands_list); - RecordingPainter() - { - m_state_stack.append(State()); - } + CommandList& commands_list() { return m_command_list; } - void apply_scroll_offsets(Vector const& offsets_by_frame_id); + void append(Command&& command); private: struct State { @@ -607,18 +160,8 @@ private: State& state() { return m_state_stack.last(); } State const& state() const { return m_state_stack.last(); } - void push_command(PaintingCommand command) - { - m_painting_commands.append({ state().scroll_frame_id, move(command) }); - } - - struct PaintingCommandWithScrollFrame { - Optional scroll_frame_id; - PaintingCommand command; - }; - - AK::SegmentedVector m_painting_commands; Vector m_state_stack; + CommandList& m_command_list; }; class RecordingPainterStateSaver { diff --git a/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.cpp b/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.cpp index fe6d920181..4efc892c1a 100644 --- a/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.cpp @@ -92,13 +92,14 @@ RefPtr SVGGraphicsPaintable::calculate_mask(PaintContext& context, return {}; mask_bitmap = mask_bitmap_or_error.release_value(); { - RecordingPainter painter; - painter.translate(-mask_rect.location().to_type()); - auto paint_context = context.clone(painter); + CommandList painting_commands; + RecordingPainter recording_painter(painting_commands); + recording_painter.translate(-mask_rect.location().to_type()); + auto paint_context = context.clone(recording_painter); paint_context.set_svg_transform(graphics_element.get_transform()); StackingContext::paint_node_as_stacking_context(mask_paintable, paint_context); PaintingCommandExecutorCPU executor { *mask_bitmap }; - painter.execute(executor); + painting_commands.execute(executor); } } return mask_bitmap; diff --git a/Userland/Libraries/LibWeb/SVG/SVGDecodedImageData.cpp b/Userland/Libraries/LibWeb/SVG/SVGDecodedImageData.cpp index 5639abe8b2..a001abfcfe 100644 --- a/Userland/Libraries/LibWeb/SVG/SVGDecodedImageData.cpp +++ b/Userland/Libraries/LibWeb/SVG/SVGDecodedImageData.cpp @@ -129,13 +129,14 @@ RefPtr SVGDecodedImageData::render(Gfx::IntSize size) const m_document->navigable()->set_viewport_rect({ 0, 0, size.width(), size.height() }); m_document->update_layout(); - Painting::RecordingPainter recording_painter; + Painting::CommandList painting_commands; + Painting::RecordingPainter recording_painter(painting_commands); PaintContext context(recording_painter, m_page_client->palette(), m_page_client->device_pixels_per_css_pixel()); m_document->paintable()->paint_all_phases(context); Painting::PaintingCommandExecutorCPU executor { *bitmap }; - recording_painter.execute(executor); + painting_commands.execute(executor); return bitmap; } diff --git a/Userland/Services/WebContent/PageClient.cpp b/Userland/Services/WebContent/PageClient.cpp index 5128809574..048eab85ae 100644 --- a/Userland/Services/WebContent/PageClient.cpp +++ b/Userland/Services/WebContent/PageClient.cpp @@ -189,7 +189,8 @@ Web::Layout::Viewport* PageClient::layout_root() void PageClient::paint(Web::DevicePixelRect const& content_rect, Gfx::Bitmap& target, Web::PaintOptions paint_options) { - Web::Painting::RecordingPainter recording_painter; + Web::Painting::CommandList painting_commands; + Web::Painting::RecordingPainter recording_painter(painting_commands); Gfx::IntRect bitmap_rect { {}, content_rect.size().to_type() }; recording_painter.fill_rect(bitmap_rect, Web::CSS::SystemColor::canvas()); @@ -203,7 +204,7 @@ void PageClient::paint(Web::DevicePixelRect const& content_rect, Gfx::Bitmap& ta if (s_use_gpu_painter) { #ifdef HAS_ACCELERATED_GRAPHICS Web::Painting::PaintingCommandExecutorGPU painting_command_executor(*m_accelerated_graphics_context, target); - recording_painter.execute(painting_command_executor); + painting_commands.execute(painting_command_executor); #else static bool has_warned_about_configuration = false; @@ -214,7 +215,7 @@ void PageClient::paint(Web::DevicePixelRect const& content_rect, Gfx::Bitmap& ta #endif } else { Web::Painting::PaintingCommandExecutorCPU painting_command_executor(target); - recording_painter.execute(painting_command_executor); + painting_commands.execute(painting_command_executor); } }