From 4318bcf447ecb97b867af13dfcba1b72de107049 Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Tue, 24 Oct 2023 18:48:27 +0200 Subject: [PATCH] LibWeb: Record painting commands in coordinates of stacking context By storing painting command coordinates relative to the nearest stacking context we can get rid of the Translate command. Additionally, this allows us to easily check if the bounding rectangles of the commands cover or intersect within a stacking context. This should be useful if we decide to optimize by avoiding the execution of commands that will be overpainted by the results of subsequent commands. --- .../Painting/BorderRadiusCornerClipper.cpp | 2 +- .../LibWeb/Painting/PaintableBox.cpp | 4 +- .../LibWeb/Painting/RecordingPainter.cpp | 115 +++++++++--------- .../LibWeb/Painting/RecordingPainter.h | 19 +-- .../LibWeb/Painting/ShadowPainting.cpp | 2 +- .../LibWeb/Painting/StackingContext.cpp | 2 +- 6 files changed, 76 insertions(+), 68 deletions(-) diff --git a/Userland/Libraries/LibWeb/Painting/BorderRadiusCornerClipper.cpp b/Userland/Libraries/LibWeb/Painting/BorderRadiusCornerClipper.cpp index 88fc088654..af3cb14839 100644 --- a/Userland/Libraries/LibWeb/Painting/BorderRadiusCornerClipper.cpp +++ b/Userland/Libraries/LibWeb/Painting/BorderRadiusCornerClipper.cpp @@ -113,7 +113,7 @@ ScopedCornerRadiusClip::ScopedCornerRadiusClip(PaintContext& context, DevicePixe .bottom_right = border_radii.bottom_right.as_corner(context), .bottom_left = border_radii.bottom_left.as_corner(context) }; - auto clipper = BorderRadiusCornerClipper::create(corner_radii, border_rect, border_radii, corner_clip); + auto clipper = BorderRadiusCornerClipper::create(corner_radii, context.painter().state().translation.map(border_rect.to_type()).to_type(), border_radii, corner_clip); if (!clipper.is_error()) { m_corner_clipper = clipper.release_value(); m_context.painter().sample_under_corners(*m_corner_clipper); diff --git a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp index 01c3c98994..4087a79a0d 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp +++ b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp @@ -484,7 +484,7 @@ void PaintableBox::apply_clip_overflow_rect(PaintContext& context, PaintPhase ph .bottom_left = border_radii_data.bottom_left.as_corner(context) }; if (border_radii_data.has_any_radius()) { - auto corner_clipper = BorderRadiusCornerClipper::create(corner_radii, context.rounded_device_rect(*clip_rect), border_radii_data, CornerClip::Outside); + auto corner_clipper = BorderRadiusCornerClipper::create(corner_radii, context.painter().state().translation.map(context.rounded_device_rect(*clip_rect).to_type()).to_type(), border_radii_data, CornerClip::Outside); if (corner_clipper.is_error()) { dbgln("Failed to create overflow border-radius corner clipper: {}", corner_clipper.error()); return; @@ -689,7 +689,7 @@ void PaintableWithLines::paint(PaintContext& context, PaintPhase phase) const .bottom_left = border_radii.bottom_left.as_corner(context) }; if (border_radii.has_any_radius()) { - auto clipper = BorderRadiusCornerClipper::create(corner_radii, clip_box, border_radii); + auto clipper = BorderRadiusCornerClipper::create(corner_radii, context.painter().state().translation.map(clip_box.to_type()).to_type(), border_radii); if (!clipper.is_error()) { corner_clipper = clipper.release_value(); context.painter().sample_under_corners(*corner_clipper); diff --git a/Userland/Libraries/LibWeb/Painting/RecordingPainter.cpp b/Userland/Libraries/LibWeb/Painting/RecordingPainter.cpp index 3c69258a24..8b47473de3 100644 --- a/Userland/Libraries/LibWeb/Painting/RecordingPainter.cpp +++ b/Userland/Libraries/LibWeb/Painting/RecordingPainter.cpp @@ -123,13 +123,6 @@ CommandResult DrawScaledBitmap::execute(CommandExecutionState& state) const return CommandResult::Continue; } -CommandResult Translate::execute(CommandExecutionState& state) const -{ - auto& painter = state.painter(); - painter.translate(translation_delta); - return CommandResult::Continue; -} - CommandResult SaveState::execute(CommandExecutionState& state) const { auto& painter = state.painter(); @@ -168,9 +161,6 @@ CommandResult SetFont::execute(CommandExecutionState& state) const CommandResult PushStackingContext::execute(CommandExecutionState& state) const { auto& painter = state.painter(); - if (has_fixed_position) { - painter.translate(-painter.translation()); - } if (semitransparent_or_has_non_identity_transform) { auto destination_rect = transformed_destination_rect.to_rounded(); @@ -485,57 +475,61 @@ void RecordingPainter::fill_rect(Gfx::IntRect const& rect, Color color) void RecordingPainter::fill_path(FillPathUsingColorParams params) { - auto path_bounding_rect = params.path.bounding_box().translated(params.translation.value_or({})).to_type(); + 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 { .path_bounding_rect = path_bounding_rect, .path = params.path, .color = params.color, .winding_rule = params.winding_rule, - .aa_translation = params.translation, + .aa_translation = aa_translation, }); } void RecordingPainter::fill_path(FillPathUsingPaintStyleParams params) { - auto path_bounding_rect = params.path.bounding_box().translated(params.translation.value_or({})).to_type(); + 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 { .path_bounding_rect = path_bounding_rect, .path = params.path, .paint_style = params.paint_style, .winding_rule = params.winding_rule, .opacity = params.opacity, - .aa_translation = params.translation, + .aa_translation = aa_translation, }); } void RecordingPainter::stroke_path(StrokePathUsingColorParams params) { - auto path_bounding_rect = params.path.bounding_box().translated(params.translation.value_or({})).to_type(); + 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(StrokePathUsingColor { .path_bounding_rect = path_bounding_rect, .path = params.path, .color = params.color, .thickness = params.thickness, - .aa_translation = params.translation, + .aa_translation = aa_translation, }); } void RecordingPainter::stroke_path(StrokePathUsingPaintStyleParams params) { - auto path_bounding_rect = params.path.bounding_box().translated(params.translation.value_or({})).to_type(); + 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(StrokePathUsingPaintStyle { .path_bounding_rect = path_bounding_rect, .path = params.path, .paint_style = params.paint_style, .thickness = params.thickness, - .aa_translation = params.translation, + .aa_translation = aa_translation, }); } void RecordingPainter::draw_ellipse(Gfx::IntRect const& a_rect, Color color, int thickness) { push_command(DrawEllipse { - .rect = a_rect, + .rect = state().translation.map(a_rect), .color = color, .thickness = thickness, }); @@ -544,7 +538,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(FillElipse { - .rect = a_rect, + .rect = state().translation.map(a_rect), .color = color, .blend_mode = blend_mode, }); @@ -553,7 +547,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 { - .gradient_rect = gradient_rect, + .gradient_rect = state().translation.map(gradient_rect), .linear_gradient_data = data, }); } @@ -561,7 +555,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 { - .rect = rect, + .rect = state().translation.map(rect), .conic_gradient_data = data, .position = position }); } @@ -569,7 +563,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 { - .rect = rect, + .rect = state().translation.map(rect), .radial_gradient_data = data, .center = center, .size = size }); @@ -578,7 +572,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 { - .rect = rect, + .rect = state().translation.map(rect), .color = color, .rough = rough }); } @@ -586,7 +580,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, float opacity, Gfx::Painter::ScalingMode scaling_mode) { push_command(DrawScaledBitmap { - .dst_rect = dst_rect, + .dst_rect = state().translation.map(dst_rect), .bitmap = bitmap, .src_rect = src_rect, .opacity = opacity, @@ -598,8 +592,8 @@ void RecordingPainter::draw_line(Gfx::IntPoint from, Gfx::IntPoint to, Color col { push_command(DrawLine { .color = color, - .from = from, - .to = to, + .from = state().translation.map(from), + .to = state().translation.map(to), .thickness = thickness, .style = style, .alternate_color = alternate_color, @@ -609,7 +603,7 @@ void RecordingPainter::draw_line(Gfx::IntPoint from, Gfx::IntPoint to, Color col void RecordingPainter::draw_text(Gfx::IntRect const& rect, StringView raw_text, Gfx::TextAlignment alignment, Color color, Gfx::TextElision elision, Gfx::TextWrapping wrapping) { push_command(DrawText { - .rect = rect, + .rect = state().translation.map(rect), .raw_text = String::from_utf8(raw_text).release_value_but_fixme_should_propagate_errors(), .alignment = alignment, .color = color, @@ -621,7 +615,7 @@ void RecordingPainter::draw_text(Gfx::IntRect const& rect, StringView raw_text, void RecordingPainter::draw_text(Gfx::IntRect const& rect, StringView raw_text, Gfx::Font const& font, Gfx::TextAlignment alignment, Color color, Gfx::TextElision elision, Gfx::TextWrapping wrapping) { push_command(DrawText { - .rect = rect, + .rect = state().translation.map(rect), .raw_text = String::from_utf8(raw_text).release_value_but_fixme_should_propagate_errors(), .alignment = alignment, .color = color, @@ -634,7 +628,7 @@ void RecordingPainter::draw_text(Gfx::IntRect const& rect, StringView raw_text, void RecordingPainter::draw_signed_distance_field(Gfx::IntRect const& dst_rect, Color color, Gfx::GrayscaleBitmap const& sdf, float smoothing) { push_command(DrawSignedDistanceField { - .rect = dst_rect, + .rect = state().translation.map(dst_rect), .color = color, .sdf = sdf, .smoothing = smoothing, @@ -645,18 +639,16 @@ void RecordingPainter::draw_text_run(Gfx::IntPoint baseline_start, Utf8View stri { push_command(DrawTextRun { .color = color, - .baseline_start = baseline_start, + .baseline_start = state().translation.map(baseline_start), .string = String::from_utf8(string.as_string()).release_value_but_fixme_should_propagate_errors(), .font = font, - .rect = rect, + .rect = state().translation.map(rect), }); } void RecordingPainter::add_clip_rect(Gfx::IntRect const& rect) { - push_command(AddClipRect { - .rect = rect, - }); + push_command(AddClipRect { .rect = state().translation.map(rect) }); } void RecordingPainter::clear_clip_rect() @@ -666,16 +658,12 @@ void RecordingPainter::clear_clip_rect() void RecordingPainter::translate(int dx, int dy) { - push_command(Translate { - .translation_delta = Gfx::IntPoint { dx, dy }, - }); + m_state_stack.last().translation.translate(dx, dy); } void RecordingPainter::translate(Gfx::IntPoint delta) { - push_command(Translate { - .translation_delta = delta, - }); + m_state_stack.last().translation.translate(delta.to_type()); } void RecordingPainter::set_font(Gfx::Font const& font) @@ -685,11 +673,14 @@ void RecordingPainter::set_font(Gfx::Font const& font) void RecordingPainter::save() { + m_state_stack.append(m_state_stack.last()); push_command(SaveState {}); } void RecordingPainter::restore() { + VERIFY(m_state_stack.size() > 1); + m_state_stack.take_last(); push_command(RestoreState {}); } @@ -699,10 +690,18 @@ void RecordingPainter::push_stacking_context(PushStackingContextParams params) .semitransparent_or_has_non_identity_transform = params.semitransparent_or_has_non_identity_transform, .has_fixed_position = params.has_fixed_position, .opacity = params.opacity, - .source_rect = params.source_rect, - .transformed_destination_rect = params.transformed_destination_rect, - .painter_location = params.painter_location, + .source_rect = state().translation.map(params.source_rect), + .transformed_destination_rect = state().translation.map(params.transformed_destination_rect), + .painter_location = state().translation.map(params.painter_location), }); + + if (params.has_fixed_position) { + state().translation.set_translation(0, 0); + } + + if (params.semitransparent_or_has_non_identity_transform) { + m_state_stack.append(State()); + } } void RecordingPainter::pop_stacking_context(PopStackingContextParams params) @@ -711,13 +710,17 @@ void RecordingPainter::pop_stacking_context(PopStackingContextParams params) .semitransparent_or_has_non_identity_transform = params.semitransparent_or_has_non_identity_transform, .scaling_mode = params.scaling_mode, }); + + if (params.semitransparent_or_has_non_identity_transform) { + m_state_stack.take_last(); + } } void RecordingPainter::paint_progressbar(Gfx::IntRect frame_rect, Gfx::IntRect progress_rect, Palette palette, int min, int max, int value, StringView text) { push_command(PaintProgressbar { - .frame_rect = frame_rect, - .progress_rect = progress_rect, + .frame_rect = state().translation.map(frame_rect), + .progress_rect = state().translation.map(progress_rect), .palette = palette, .min = min, .max = max, @@ -728,13 +731,13 @@ void RecordingPainter::paint_progressbar(Gfx::IntRect frame_rect, Gfx::IntRect p void RecordingPainter::paint_frame(Gfx::IntRect rect, Palette palette, Gfx::FrameStyle style) { - push_command(PaintFrame { rect, palette, style }); + push_command(PaintFrame { state().translation.map(rect), palette, style }); } void RecordingPainter::apply_backdrop_filter(Gfx::IntRect const& backdrop_region, BorderRadiiData const& border_radii_data, CSS::ResolvedBackdropFilter const& backdrop_filter) { push_command(ApplyBackdropFilter { - .backdrop_region = backdrop_region, + .backdrop_region = state().translation.map(backdrop_region), .border_radii_data = border_radii_data, .backdrop_filter = backdrop_filter, }); @@ -758,19 +761,19 @@ void RecordingPainter::paint_text_shadow(int blur_radius, Gfx::IntRect bounding_ { push_command(PaintTextShadow { .blur_radius = blur_radius, - .shadow_bounding_rect = bounding_rect, - .text_rect = text_rect, + .shadow_bounding_rect = state().translation.map(bounding_rect), + .text_rect = state().translation.map(text_rect), .text = String::from_utf8(text.as_string()).release_value_but_fixme_should_propagate_errors(), .font = font, .color = color, .fragment_baseline = fragment_baseline, - .draw_location = draw_location }); + .draw_location = state().translation.map(draw_location) }); } void RecordingPainter::fill_rect_with_rounded_corners(Gfx::IntRect const& rect, Color color, Gfx::AntiAliasingPainter::CornerRadius top_left_radius, Gfx::AntiAliasingPainter::CornerRadius top_right_radius, Gfx::AntiAliasingPainter::CornerRadius bottom_right_radius, Gfx::AntiAliasingPainter::CornerRadius bottom_left_radius) { push_command(FillRectWithRoundedCorners { - .rect = rect, + .rect = state().translation.map(rect), .color = color, .top_left_radius = top_left_radius, .top_right_radius = top_right_radius, @@ -795,13 +798,13 @@ void RecordingPainter::fill_rect_with_rounded_corners(Gfx::IntRect const& a_rect void RecordingPainter::push_stacking_context_with_mask(Gfx::IntRect paint_rect) { - push_command(PushStackingContextWithMask { .paint_rect = paint_rect }); + push_command(PushStackingContextWithMask { .paint_rect = state().translation.map(paint_rect) }); } void RecordingPainter::pop_stacking_context_with_mask(RefPtr mask_bitmap, Gfx::Bitmap::MaskKind mask_kind, Gfx::IntRect paint_rect, float opacity) { push_command(PopStackingContextWithMask { - .paint_rect = paint_rect, + .paint_rect = state().translation.map(paint_rect), .mask_bitmap = mask_bitmap, .mask_kind = mask_kind, .opacity = opacity }); @@ -810,8 +813,8 @@ void RecordingPainter::pop_stacking_context_with_mask(RefPtr mask_b void RecordingPainter::draw_triangle_wave(Gfx::IntPoint a_p1, Gfx::IntPoint a_p2, Color color, int amplitude, int thickness = 1) { push_command(DrawTriangleWave { - .p1 = a_p1, - .p2 = a_p2, + .p1 = state().translation.map(a_p1), + .p2 = state().translation.map(a_p2), .color = color, .amplitude = amplitude, .thickness = thickness }); diff --git a/Userland/Libraries/LibWeb/Painting/RecordingPainter.h b/Userland/Libraries/LibWeb/Painting/RecordingPainter.h index b0fd6f6233..6e0149c9fa 100644 --- a/Userland/Libraries/LibWeb/Painting/RecordingPainter.h +++ b/Userland/Libraries/LibWeb/Painting/RecordingPainter.h @@ -83,12 +83,6 @@ struct DrawScaledBitmap { [[nodiscard]] CommandResult execute(CommandExecutionState&) const; }; -struct Translate { - Gfx::IntPoint translation_delta; - - [[nodiscard]] CommandResult execute(CommandExecutionState&) const; -}; - struct SaveState { [[nodiscard]] CommandResult execute(CommandExecutionState&) const; }; @@ -367,7 +361,6 @@ using PaintingCommand = Variant< DrawText, FillRect, DrawScaledBitmap, - Translate, SaveState, RestoreState, AddClipRect, @@ -510,6 +503,17 @@ public: void execute(Gfx::Bitmap&); + RecordingPainter() + { + m_state_stack.append(State()); + } + + struct State { + Gfx::AffineTransform translation; + }; + State& state() { return m_state_stack.last(); } + State const& state() const { return m_state_stack.last(); } + private: void push_command(PaintingCommand command) { @@ -517,6 +521,7 @@ private: } Vector m_painting_commands; + Vector m_state_stack; }; class RecordingPainterStateSaver { diff --git a/Userland/Libraries/LibWeb/Painting/ShadowPainting.cpp b/Userland/Libraries/LibWeb/Painting/ShadowPainting.cpp index 3d11f8ded7..cfc7b1b6aa 100644 --- a/Userland/Libraries/LibWeb/Painting/ShadowPainting.cpp +++ b/Userland/Libraries/LibWeb/Painting/ShadowPainting.cpp @@ -564,7 +564,7 @@ void paint_box_shadow(PaintContext& context, .offset_y = offset_y, .blur_radius = blur_radius, .spread_distance = spread_distance, - .device_content_rect = device_content_rect, + .device_content_rect = context.painter().state().translation.map(device_content_rect.to_type()).to_type(), }; if (box_shadow_data.placement == ShadowPlacement::Inner) { diff --git a/Userland/Libraries/LibWeb/Painting/StackingContext.cpp b/Userland/Libraries/LibWeb/Painting/StackingContext.cpp index 166db4f1f6..f5642eb33a 100644 --- a/Userland/Libraries/LibWeb/Painting/StackingContext.cpp +++ b/Userland/Libraries/LibWeb/Painting/StackingContext.cpp @@ -343,8 +343,8 @@ void StackingContext::paint(PaintContext& context) const paint_internal(context); context.painter().pop_stacking_context(pop_stacking_context_params); } else { - context.painter().push_stacking_context(push_stacking_context_params); context.painter().translate(affine_transform.translation().to_rounded()); + context.painter().push_stacking_context(push_stacking_context_params); paint_internal(context); context.painter().pop_stacking_context(pop_stacking_context_params); }