LibWeb: Use device-pixel coordinates in display list and AVC

Stop converting between CSS and device pixels as part of rendering - the
display list should be as simple as possible, so convert to DevicePixels
once when constructing the display list.
This commit is contained in:
Jelle Raaijmakers 2026-02-25 17:09:59 +01:00 committed by Alexander Kalenik
parent 8d099b9cec
commit 90a211bf47
Notes: github-actions[bot] 2026-02-26 06:44:15 +00:00
23 changed files with 255 additions and 211 deletions

View file

@ -100,7 +100,7 @@ Optional<Gfx::ImageCursor> CursorStyleValue::make_image_cursor(Layout::NodeWithS
painter->clear_rect(bitmap.rect().to_type<float>(), Color::Transparent);
// Paint the cursor into a bitmap.
auto display_list = Painting::DisplayList::create(document.page().client().device_pixels_per_css_pixel());
auto display_list = Painting::DisplayList::create();
Painting::DisplayListRecorder display_list_recorder(display_list);
DisplayListRecordingContext paint_context { display_list_recorder, document.page().palette(), document.page().client().device_pixels_per_css_pixel(), document.page().chrome_metrics() };

View file

@ -7279,7 +7279,7 @@ RefPtr<Painting::DisplayList> Document::record_display_list(HTML::PaintConfig co
if (m_cached_display_list && m_cached_display_list_paint_config == config)
return m_cached_display_list;
auto display_list = Painting::DisplayList::create(page().client().device_pixels_per_css_pixel());
auto display_list = Painting::DisplayList::create();
Painting::DisplayListRecorder display_list_recorder(display_list);
// https://drafts.csswg.org/css-color-adjust-1/#color-scheme-effect

View file

@ -1434,10 +1434,10 @@ Vector<CSSPixelRect> Element::get_client_rects() const
auto absolute_rect = paintable_box->absolute_border_box_rect();
if (auto const& accumulated_visual_context = paintable_box->accumulated_visual_context()) {
auto const& viewport_paintable = *document().paintable();
auto const& scroll_state = viewport_paintable.scroll_state_snapshot();
auto transformed_rect = accumulated_visual_context->transform_rect_to_viewport(absolute_rect, scroll_state);
rects.append(transformed_rect);
auto pixel_ratio = static_cast<float>(document().page().client().device_pixels_per_css_pixel());
auto const& scroll_state = document().paintable()->scroll_state_snapshot();
auto result = accumulated_visual_context->transform_rect_to_viewport(absolute_rect.to_type<float>() * pixel_ratio, scroll_state.device_offsets());
rects.append((result * (1.f / pixel_ratio)).to_type<CSSPixels>());
} else {
rects.append(absolute_rect);
}

View file

@ -11,6 +11,7 @@
#include <LibWeb/HTML/Navigable.h>
#include <LibWeb/Page/AutoScrollHandler.h>
#include <LibWeb/Page/EventHandler.h>
#include <LibWeb/Page/Page.h>
#include <LibWeb/Painting/PaintableBox.h>
#include <LibWeb/Painting/ViewportPaintable.h>
@ -49,8 +50,10 @@ static Optional<CSSPixelRect> scrollport_rect_in_viewport(Painting::PaintableBox
auto const* viewport_paintable = paintable_box.document().paintable();
if (!viewport_paintable)
return {};
auto pixel_ratio = static_cast<float>(paintable_box.document().page().client().device_pixels_per_css_pixel());
auto const& scroll_state = viewport_paintable->scroll_state_snapshot();
return accumulated_visual_context->transform_rect_to_viewport(scrollport, scroll_state);
auto result = accumulated_visual_context->transform_rect_to_viewport(scrollport.to_type<float>() * pixel_ratio, scroll_state.device_offsets());
return (result * (1.f / pixel_ratio)).to_type<CSSPixels>();
}
// Returns scroll speed in CSS pixels per second for each axis, based on how far the mouse is past the auto scroll edge.

View file

@ -206,8 +206,9 @@ static CSSPixelPoint compute_mouse_event_offset(CSSPixelPoint position, Painting
visual_context = containing_block->accumulated_visual_context();
}
if (visual_context) {
auto transformed = visual_context->inverse_transform_point(position);
precision_offset = { transformed.x().to_double(), transformed.y().to_double() };
auto pixel_ratio = static_cast<float>(paintable.document().page().client().device_pixels_per_css_pixel());
auto result = visual_context->inverse_transform_point(position.to_type<float>() * pixel_ratio);
precision_offset = result / pixel_ratio;
}
// relative to the origin of the padding edge of the target node

View file

@ -16,12 +16,12 @@ NonnullRefPtr<AccumulatedVisualContext> AccumulatedVisualContext::create(size_t
return adopt_ref(*new AccumulatedVisualContext(id, move(data), move(parent)));
}
bool ClipData::contains(CSSPixelPoint point) const
bool ClipData::contains(DevicePixelPoint point) const
{
return corner_radii.contains(point, rect);
return corner_radii.contains(point.to_type<int>(), rect.to_type<int>());
}
Optional<CSSPixelPoint> AccumulatedVisualContext::transform_point_for_hit_test(CSSPixelPoint screen_point, ScrollStateSnapshot const& scroll_state) const
Optional<Gfx::FloatPoint> AccumulatedVisualContext::transform_point_for_hit_test(Gfx::FloatPoint screen_point, ReadonlySpan<Gfx::FloatPoint> scroll_offsets) const
{
Vector<AccumulatedVisualContext const*, 8> chain;
chain.ensure_capacity(m_depth);
@ -33,47 +33,47 @@ Optional<CSSPixelPoint> AccumulatedVisualContext::transform_point_for_hit_test(C
auto const* node = chain[i - 1];
auto result = node->data().visit(
[&](PerspectiveData const& perspective) -> Optional<CSSPixelPoint> {
[&](PerspectiveData const& perspective) -> Optional<Gfx::FloatPoint> {
auto affine = Gfx::extract_2d_affine_transform(perspective.matrix);
auto inverse = affine.inverse();
if (!inverse.has_value())
return {};
point = inverse->map(point.to_type<float>()).to_type<CSSPixels>();
point = inverse->map(point);
return point;
},
[&](ScrollData const& scroll) -> Optional<CSSPixelPoint> {
auto offset = scroll_state.own_offset_for_frame_with_id(scroll.scroll_frame_id);
point.translate_by(-offset);
[&](ScrollData const& scroll) -> Optional<Gfx::FloatPoint> {
if (scroll.scroll_frame_id < scroll_offsets.size())
point.translate_by(-scroll_offsets[scroll.scroll_frame_id]);
return point;
},
[&](TransformData const& transform) -> Optional<CSSPixelPoint> {
[&](TransformData const& transform) -> Optional<Gfx::FloatPoint> {
auto affine = Gfx::extract_2d_affine_transform(transform.matrix);
auto inverse = affine.inverse();
if (!inverse.has_value())
return {};
auto offset_point = point - transform.origin;
auto transformed = inverse->map(offset_point.to_type<float>()).to_type<CSSPixels>();
auto transformed = inverse->map(offset_point);
point = transformed + transform.origin;
return point;
},
[&](ClipData const& clip) -> Optional<CSSPixelPoint> {
// NOTE: The clip rect is stored in absolute (layout) coordinates. After inverse-transforming, `point`
// is also in layout coordinates, so we compare them directly without mapping back to screen space.
if (!clip.contains(point))
[&](ClipData const& clip) -> Optional<Gfx::FloatPoint> {
// NOTE: The clip rect is in absolute device-pixel coordinates. After inverse-transforming, `point`
// is also in device-pixel coordinates, so we compare them directly.
if (!clip.contains(point.to_type<int>().to_type<DevicePixels>()))
return {};
return point;
},
[&](ClipPathData const& clip_path) -> Optional<CSSPixelPoint> {
// NOTE: The clip path is stored in absolute (layout) coordinates. After inverse-transforming, `point`
// is also in layout coordinates, so we compare them directly without mapping back to screen space.
if (!clip_path.bounding_rect.contains(point))
[&](ClipPathData const& clip_path) -> Optional<Gfx::FloatPoint> {
// NOTE: The clip path is in absolute device-pixel coordinates. After inverse-transforming, `point`
// is also in device-pixel coordinates, so we compare them directly.
if (!clip_path.bounding_rect.contains(point.to_type<int>().to_type<DevicePixels>()))
return {};
if (!clip_path.path.contains(point.to_type<float>(), clip_path.fill_rule))
if (!clip_path.path.contains(point, clip_path.fill_rule))
return {};
return point;
},
[&](EffectsData const&) -> Optional<CSSPixelPoint> {
[&](EffectsData const&) -> Optional<Gfx::FloatPoint> {
// Effects don't affect coordinate transforms
return point;
});
@ -85,7 +85,7 @@ Optional<CSSPixelPoint> AccumulatedVisualContext::transform_point_for_hit_test(C
return point;
}
CSSPixelPoint AccumulatedVisualContext::inverse_transform_point(CSSPixelPoint screen_point) const
Gfx::FloatPoint AccumulatedVisualContext::inverse_transform_point(Gfx::FloatPoint screen_point) const
{
Vector<AccumulatedVisualContext const*, 8> chain;
chain.ensure_capacity(m_depth);
@ -101,14 +101,14 @@ CSSPixelPoint AccumulatedVisualContext::inverse_transform_point(CSSPixelPoint sc
auto affine = Gfx::extract_2d_affine_transform(perspective.matrix);
auto inverse = affine.inverse();
if (inverse.has_value())
point = inverse->map(point.to_type<float>()).to_type<CSSPixels>();
point = inverse->map(point);
},
[&](TransformData const& transform) {
auto affine = Gfx::extract_2d_affine_transform(transform.matrix);
auto inverse = affine.inverse();
if (inverse.has_value()) {
auto offset_point = point - transform.origin;
auto transformed = inverse->map(offset_point.to_type<float>()).to_type<CSSPixels>();
auto transformed = inverse->map(offset_point);
point = transformed + transform.origin;
}
},
@ -118,32 +118,31 @@ CSSPixelPoint AccumulatedVisualContext::inverse_transform_point(CSSPixelPoint sc
return point;
}
CSSPixelRect AccumulatedVisualContext::transform_rect_to_viewport(CSSPixelRect const& source_rect, ScrollStateSnapshot const& scroll_state) const
Gfx::FloatRect AccumulatedVisualContext::transform_rect_to_viewport(Gfx::FloatRect const& source_rect, ReadonlySpan<Gfx::FloatPoint> scroll_offsets) const
{
auto rect = source_rect.to_type<float>();
auto rect = source_rect;
for (auto const* node = this; node; node = node->parent().ptr()) {
node->data().visit(
[&](TransformData const& transform) {
auto affine = Gfx::extract_2d_affine_transform(transform.matrix);
auto origin = transform.origin.to_type<float>();
rect.translate_by(-origin);
rect.translate_by(-transform.origin);
rect = affine.map(rect);
rect.translate_by(origin);
rect.translate_by(transform.origin);
},
[&](PerspectiveData const& perspective) {
auto affine = Gfx::extract_2d_affine_transform(perspective.matrix);
rect = affine.map(rect);
},
[&](ScrollData const& scroll) {
auto offset = scroll_state.own_offset_for_frame_with_id(scroll.scroll_frame_id);
rect.translate_by(offset.to_type<float>());
if (scroll.scroll_frame_id < scroll_offsets.size())
rect.translate_by(scroll_offsets[scroll.scroll_frame_id]);
},
[&](ClipData const&) { /* clips don't affect rect coordinates */ },
[&](ClipPathData const&) { /* clip paths don't affect rect coordinates */ },
[&](EffectsData const&) { /* effects don't affect rect coordinates */ });
}
return rect.to_type<CSSPixels>();
return rect;
}
void AccumulatedVisualContext::dump(StringBuilder& builder) const
@ -160,11 +159,11 @@ void AccumulatedVisualContext::dump(StringBuilder& builder) const
[&](TransformData const& transform) {
auto const& matrix = transform.matrix.elements();
auto const& origin = transform.origin;
builder.appendff("transform=[{},{},{},{},{},{}] origin=({},{})", matrix[0][0], matrix[0][1], matrix[1][0], matrix[1][1], matrix[0][3], matrix[1][3], origin.x().to_float(), origin.y().to_float());
builder.appendff("transform=[{},{},{},{},{},{}] origin=({},{})", matrix[0][0], matrix[0][1], matrix[1][0], matrix[1][1], matrix[0][3], matrix[1][3], origin.x(), origin.y());
},
[&](ClipData const& clip) {
auto const& rect = clip.rect;
builder.appendff("clip=[{},{} {}x{}]", rect.x().to_float(), rect.y().to_float(), rect.width().to_float(), rect.height().to_float());
builder.appendff("clip=[{},{} {}x{}]", rect.x(), rect.y(), rect.width(), rect.height());
if (clip.corner_radii.has_any_radius()) {
auto const& corner_radii = clip.corner_radii;
@ -173,7 +172,7 @@ void AccumulatedVisualContext::dump(StringBuilder& builder) const
},
[&](ClipPathData const& clip_path) {
auto const& rect = clip_path.bounding_rect;
builder.appendff("clip_path=[bounds: {},{} {}x{}, path: {}]", rect.x().to_float(), rect.y().to_float(), rect.width().to_float(), rect.height().to_float(), clip_path.path.to_svg_string());
builder.appendff("clip_path=[bounds: {},{} {}x{}, path: {}]", rect.x(), rect.y(), rect.width(), rect.height(), clip_path.path.to_svg_string());
},
[&](EffectsData const& effects) {
builder.append("effects=["sv);
@ -188,10 +187,10 @@ void AccumulatedVisualContext::dump(StringBuilder& builder) const
builder.appendff("blend_mode={}", static_cast<int>(effects.blend_mode));
has_content = true;
}
if (effects.filter.has_filters()) {
if (effects.gfx_filter.has_value()) {
if (has_content)
builder.append(' ');
effects.filter.dump(builder);
builder.append("filter"sv);
has_content = true;
}
builder.append("]"sv);

View file

@ -9,47 +9,38 @@
#include <AK/AtomicRefCounted.h>
#include <AK/Variant.h>
#include <LibGfx/CompositingAndBlendingOperator.h>
#include <LibGfx/Filter.h>
#include <LibGfx/Matrix4x4.h>
#include <LibGfx/Path.h>
#include <LibGfx/Point.h>
#include <LibGfx/Rect.h>
#include <LibGfx/WindingRule.h>
#include <LibWeb/Painting/BorderRadiiData.h>
#include <LibWeb/Painting/ResolvedCSSFilter.h>
#include <LibWeb/Painting/ScrollState.h>
#include <LibWeb/PixelUnits.h>
namespace Web::Painting {
struct ClipRect {
CSSPixelRect rect;
BorderRadiiData corner_radii;
};
struct ScrollData {
size_t scroll_frame_id;
bool is_sticky;
};
struct ClipData {
CSSPixelRect rect;
BorderRadiiData corner_radii;
DevicePixelRect rect;
CornerRadii corner_radii;
explicit ClipData(ClipRect const& clip_rect)
: rect(clip_rect.rect)
, corner_radii(clip_rect.corner_radii)
{
}
ClipData(CSSPixelRect r, BorderRadiiData radii)
ClipData(DevicePixelRect r, CornerRadii radii)
: rect(r)
, corner_radii(radii)
{
}
bool contains(CSSPixelPoint point) const;
bool contains(DevicePixelPoint point) const;
};
struct TransformData {
Gfx::FloatMatrix4x4 matrix;
CSSPixelPoint origin;
Gfx::FloatPoint origin;
};
struct PerspectiveData {
@ -58,20 +49,20 @@ struct PerspectiveData {
struct ClipPathData {
Gfx::Path path;
CSSPixelRect bounding_rect;
DevicePixelRect bounding_rect;
Gfx::WindingRule fill_rule;
};
struct EffectsData {
float opacity { 1.0f };
Gfx::CompositingAndBlendingOperator blend_mode { Gfx::CompositingAndBlendingOperator::Normal };
ResolvedCSSFilter filter;
Optional<Gfx::Filter> gfx_filter;
bool needs_layer() const
{
return opacity < 1.0f
|| blend_mode != Gfx::CompositingAndBlendingOperator::Normal
|| filter.has_filters();
|| gfx_filter.has_value();
}
};
@ -92,9 +83,9 @@ public:
void dump(StringBuilder&) const;
Optional<CSSPixelPoint> transform_point_for_hit_test(CSSPixelPoint screen_point, ScrollStateSnapshot const& scroll_state) const;
CSSPixelPoint inverse_transform_point(CSSPixelPoint point) const;
CSSPixelRect transform_rect_to_viewport(CSSPixelRect const&, ScrollStateSnapshot const&) const;
Optional<Gfx::FloatPoint> transform_point_for_hit_test(Gfx::FloatPoint, ReadonlySpan<Gfx::FloatPoint> scroll_offsets) const;
Gfx::FloatPoint inverse_transform_point(Gfx::FloatPoint) const;
Gfx::FloatRect transform_rect_to_viewport(Gfx::FloatRect const&, ReadonlySpan<Gfx::FloatPoint> scroll_offsets) const;
private:
AccumulatedVisualContext(size_t id, VisualContextData data, RefPtr<AccumulatedVisualContext const> parent)

View file

@ -21,7 +21,7 @@ namespace Web::Painting {
static RefPtr<DisplayList> compute_text_clip_paths(DisplayListRecordingContext& context, Paintable const& paintable, CSSPixelPoint containing_block_location)
{
auto text_clip_paths = DisplayList::create(context.device_pixels_per_css_pixel());
auto text_clip_paths = DisplayList::create();
DisplayListRecorder display_list_recorder(*text_clip_paths);
// Remove containing block offset, so executing the display list will produce mask at (0, 0)
display_list_recorder.translate(-context.floored_device_point(containing_block_location).to_type<int>());

View file

@ -7,6 +7,8 @@
#pragma once
#include <LibGfx/Point.h>
#include <LibGfx/Rect.h>
#include <LibWeb/CSS/ComputedValues.h>
#include <LibWeb/Export.h>
@ -52,20 +54,8 @@ struct CornerRadii {
{
return top_left || top_right || bottom_right || bottom_left;
}
};
struct BorderRadiiData {
BorderRadiusData top_left;
BorderRadiusData top_right;
BorderRadiusData bottom_right;
BorderRadiusData bottom_left;
inline bool has_any_radius() const
{
return top_left || top_right || bottom_right || bottom_left;
}
bool contains(CSSPixelPoint point, CSSPixelRect const& rect) const
bool contains(Gfx::IntPoint point, Gfx::IntRect const& rect) const
{
if (!rect.contains(point))
return false;
@ -76,10 +66,10 @@ struct BorderRadiiData {
auto const px = point.x();
auto const py = point.y();
auto outside_ellipse = [&](BorderRadiusData const& r, CSSPixels cx, CSSPixels cy) {
auto dx = (px - cx).to_float() / r.horizontal_radius.to_float();
auto dy = (py - cy).to_float() / r.vertical_radius.to_float();
return dx * dx + dy * dy > 1.0f;
auto outside_ellipse = [&](CornerRadius const& r, int cx, int cy) {
auto dx = static_cast<float>(px - cx) / r.horizontal_radius;
auto dy = static_cast<float>(py - cy) / r.vertical_radius;
return dx * dx + dy * dy > 1.f;
};
if (top_left) {
@ -112,6 +102,33 @@ struct BorderRadiiData {
return true;
}
};
struct BorderRadiiData {
BorderRadiusData top_left;
BorderRadiusData top_right;
BorderRadiusData bottom_right;
BorderRadiusData bottom_left;
inline bool has_any_radius() const
{
return top_left || top_right || bottom_right || bottom_left;
}
bool contains(CSSPixelPoint point, CSSPixelRect const& rect) const
{
if (!rect.contains(point))
return false;
if (!has_any_radius())
return true;
auto to_corner = [](BorderRadiusData const& r) -> CornerRadius {
return { static_cast<int>(r.horizontal_radius.to_float()), static_cast<int>(r.vertical_radius.to_float()) };
};
CornerRadii corners { to_corner(top_left), to_corner(top_right), to_corner(bottom_right), to_corner(bottom_left) };
return corners.contains(point.to_type<int>(), rect.to_type<int>());
}
inline void shrink(CSSPixels top, CSSPixels right, CSSPixels bottom, CSSPixels left)
{

View file

@ -6,9 +6,7 @@
#include <AK/TemporaryChange.h>
#include <LibGfx/PaintingSurface.h>
#include <LibWeb/Painting/DevicePixelConverter.h>
#include <LibWeb/Painting/DisplayList.h>
#include <LibWeb/Painting/ResolvedCSSFilter.h>
namespace Web::Painting {
@ -75,27 +73,9 @@ static RefPtr<AccumulatedVisualContext const> find_common_ancestor(RefPtr<Accumu
return a;
}
// Converts a CSS-pixel-space 4x4 matrix to device-pixel-space.
// - Translation column (column 3, rows 0-2) is scaled up by DPR
// - Perspective row (row 3, columns 0-2) is scaled down by DPR
// - All other elements are unaffected (the scale factors cancel out)
static FloatMatrix4x4 scale_matrix_for_device_pixels(FloatMatrix4x4 matrix, float scale)
{
matrix[0, 3] *= scale;
matrix[1, 3] *= scale;
matrix[2, 3] *= scale;
matrix[3, 0] /= scale;
matrix[3, 1] /= scale;
matrix[3, 2] /= scale;
return matrix;
}
void DisplayListPlayer::execute_impl(DisplayList& display_list, ScrollStateSnapshot const& scroll_state)
{
auto const& commands = display_list.commands();
auto device_pixels_per_css_pixel = display_list.device_pixels_per_css_pixel();
DevicePixelConverter device_pixel_converter { device_pixels_per_css_pixel };
VERIFY(m_surface);
@ -109,43 +89,35 @@ void DisplayListPlayer::execute_impl(DisplayList& display_list, ScrollStateSnaps
auto apply_accumulated_visual_context = [&](AccumulatedVisualContext const& node) {
node.data().visit(
[&](EffectsData const& effects) {
Optional<Gfx::Filter> gfx_filter;
if (effects.filter.has_filters())
gfx_filter = to_gfx_filter(effects.filter, device_pixels_per_css_pixel);
apply_effects({ .opacity = effects.opacity, .compositing_and_blending_operator = effects.blend_mode, .filter = gfx_filter });
apply_effects({ .opacity = effects.opacity, .compositing_and_blending_operator = effects.blend_mode, .filter = effects.gfx_filter });
},
[&](PerspectiveData const& perspective) {
save({});
auto matrix = scale_matrix_for_device_pixels(perspective.matrix, static_cast<float>(device_pixels_per_css_pixel));
apply_transform({ 0, 0 }, matrix);
apply_transform({ 0, 0 }, perspective.matrix);
},
[&](ScrollData const& scroll) {
save({});
auto own_offset = scroll_state.own_offset_for_frame_with_id(scroll.scroll_frame_id);
if (!own_offset.is_zero()) {
auto scroll_offset = own_offset.to_type<double>().scaled(device_pixels_per_css_pixel).to_type<int>();
translate({ .delta = scroll_offset });
auto const& offsets = scroll_state.device_offsets();
if (scroll.scroll_frame_id < offsets.size()) {
auto const& offset = offsets[scroll.scroll_frame_id];
if (!offset.is_zero())
translate({ .delta = offset.to_type<int>() });
}
},
[&](TransformData const& transform) {
save({});
auto origin = transform.origin.to_type<double>().scaled(device_pixels_per_css_pixel).to_type<float>();
auto matrix = scale_matrix_for_device_pixels(transform.matrix, static_cast<float>(device_pixels_per_css_pixel));
apply_transform(origin, matrix);
apply_transform(transform.origin, transform.matrix);
},
[&](ClipData const& clip) {
save({});
auto device_rect = device_pixel_converter.rounded_device_rect(clip.rect).to_type<int>();
auto corner_radii = clip.corner_radii.as_corners(device_pixel_converter);
if (corner_radii.has_any_radius())
add_rounded_rect_clip({ .corner_radii = corner_radii, .border_rect = device_rect, .corner_clip = CornerClip::Outside });
if (clip.corner_radii.has_any_radius())
add_rounded_rect_clip({ .corner_radii = clip.corner_radii, .border_rect = clip.rect.to_type<int>(), .corner_clip = CornerClip::Outside });
else
add_clip_rect({ .rect = device_rect });
add_clip_rect({ .rect = clip.rect.to_type<int>() });
},
[&](ClipPathData const& clip_path) {
save({});
auto transformed_path = clip_path.path.copy_transformed(Gfx::AffineTransform {}.set_scale(static_cast<float>(device_pixels_per_css_pixel), static_cast<float>(device_pixels_per_css_pixel)));
add_clip_path(transformed_path);
add_clip_path(clip_path.path);
});
};
@ -195,13 +167,13 @@ void DisplayListPlayer::execute_impl(DisplayList& display_list, ScrollStateSnaps
if (command.has<PaintScrollBar>()) {
auto translated_command = command;
auto& paint_scroll_bar = translated_command.get<PaintScrollBar>();
auto scroll_offset = scroll_state.own_offset_for_frame_with_id(paint_scroll_bar.scroll_frame_id);
if (paint_scroll_bar.vertical) {
auto offset = scroll_offset.y() * paint_scroll_bar.scroll_size;
paint_scroll_bar.thumb_rect.translate_by(0, -offset.to_int() * device_pixels_per_css_pixel);
} else {
auto offset = scroll_offset.x() * paint_scroll_bar.scroll_size;
paint_scroll_bar.thumb_rect.translate_by(-offset.to_int() * device_pixels_per_css_pixel, 0);
auto const& offsets = scroll_state.device_offsets();
if (paint_scroll_bar.scroll_frame_id < static_cast<int>(offsets.size())) {
auto const& device_offset = offsets[paint_scroll_bar.scroll_frame_id];
if (paint_scroll_bar.vertical)
paint_scroll_bar.thumb_rect.translate_by(0, static_cast<int>(-device_offset.y() * paint_scroll_bar.scroll_size));
else
paint_scroll_bar.thumb_rect.translate_by(static_cast<int>(-device_offset.x() * paint_scroll_bar.scroll_size), 0);
}
paint_scrollbar(paint_scroll_bar);
continue;

View file

@ -72,9 +72,9 @@ private:
class DisplayList : public AtomicRefCounted<DisplayList> {
public:
static NonnullRefPtr<DisplayList> create(double device_pixels_per_css_pixel)
static NonnullRefPtr<DisplayList> create()
{
return adopt_ref(*new DisplayList(device_pixels_per_css_pixel));
return adopt_ref(*new DisplayList());
}
void append(DisplayListCommand&& command, RefPtr<AccumulatedVisualContext const> context);
@ -86,16 +86,11 @@ public:
auto& commands(Badge<DisplayListRecorder>) { return m_commands; }
auto const& commands() const { return m_commands; }
double device_pixels_per_css_pixel() const { return m_device_pixels_per_css_pixel; }
private:
DisplayList(double device_pixels_per_css_pixel)
: m_device_pixels_per_css_pixel(device_pixels_per_css_pixel)
{
}
DisplayList() = default;
AK::SegmentedVector<CommandListItem, 512> m_commands;
double m_device_pixels_per_css_pixel;
};
}

View file

@ -269,7 +269,7 @@ struct ApplyBackdropFilter {
static constexpr StringView command_name = "ApplyBackdropFilter"sv;
Gfx::IntRect backdrop_region;
BorderRadiiData border_radii_data;
CornerRadii corner_radii;
Optional<Gfx::Filter> backdrop_filter;
[[nodiscard]] Gfx::IntRect bounding_rect() const { return backdrop_region; }
@ -344,7 +344,7 @@ struct PaintScrollBar {
int scroll_frame_id { 0 };
Gfx::IntRect gutter_rect;
Gfx::IntRect thumb_rect;
CSSPixelFraction scroll_size;
double scroll_size;
Color thumb_color;
Color track_color;
bool vertical;

View file

@ -285,13 +285,13 @@ void DisplayListRecorder::restore()
APPEND(Restore {});
}
void DisplayListRecorder::apply_backdrop_filter(Gfx::IntRect const& backdrop_region, BorderRadiiData const& border_radii_data, Gfx::Filter const& backdrop_filter)
void DisplayListRecorder::apply_backdrop_filter(Gfx::IntRect const& backdrop_region, CornerRadii const& corner_radii, Gfx::Filter const& backdrop_filter)
{
if (backdrop_region.is_empty())
return;
APPEND(ApplyBackdropFilter {
.backdrop_region = backdrop_region,
.border_radii_data = border_radii_data,
.corner_radii = corner_radii,
.backdrop_filter = backdrop_filter,
});
}
@ -349,7 +349,7 @@ void DisplayListRecorder::fill_rect_with_rounded_corners(Gfx::IntRect const& a_r
{ bottom_left_radius, bottom_left_radius } });
}
void DisplayListRecorder::paint_scrollbar(int scroll_frame_id, Gfx::IntRect gutter_rect, Gfx::IntRect thumb_rect, CSSPixelFraction scroll_size, Color thumb_color, Color track_color, bool vertical)
void DisplayListRecorder::paint_scrollbar(int scroll_frame_id, Gfx::IntRect gutter_rect, Gfx::IntRect thumb_rect, double scroll_size, Color thumb_color, Color track_color, bool vertical)
{
APPEND(PaintScrollBar {
.scroll_frame_id = scroll_frame_id,

View file

@ -106,7 +106,7 @@ public:
void begin_masks(ReadonlySpan<MaskInfo>);
void end_masks(ReadonlySpan<MaskInfo>);
void apply_backdrop_filter(Gfx::IntRect const& backdrop_region, BorderRadiiData const& border_radii_data, Gfx::Filter const& backdrop_filter);
void apply_backdrop_filter(Gfx::IntRect const& backdrop_region, CornerRadii const& corner_radii, Gfx::Filter const& backdrop_filter);
void paint_outer_box_shadow(PaintBoxShadowParams params);
void paint_inner_box_shadow(PaintBoxShadowParams params);
@ -116,7 +116,7 @@ public:
void fill_rect_with_rounded_corners(Gfx::IntRect const& a_rect, Color color, int radius);
void fill_rect_with_rounded_corners(Gfx::IntRect const& a_rect, Color color, int top_left_radius, int top_right_radius, int bottom_right_radius, int bottom_left_radius);
void paint_scrollbar(int scroll_frame_id, Gfx::IntRect gutter_rect, Gfx::IntRect thumb_rect, CSSPixelFraction scroll_size, Color thumb_color, Color track_color, bool vertical);
void paint_scrollbar(int scroll_frame_id, Gfx::IntRect gutter_rect, Gfx::IntRect thumb_rect, double scroll_size, Color thumb_color, Color track_color, bool vertical);
void apply_effects(float opacity = 1.0f, Gfx::CompositingAndBlendingOperator = Gfx::CompositingAndBlendingOperator::Normal, Optional<Gfx::Filter> filter = {}, Optional<Gfx::MaskKind> mask_kind = {});

View file

@ -15,6 +15,7 @@
#include <LibWeb/HTML/HTMLHtmlElement.h>
#include <LibWeb/HTML/Navigable.h>
#include <LibWeb/Layout/InlineNode.h>
#include <LibWeb/Page/Page.h>
#include <LibWeb/Painting/BackgroundPainting.h>
#include <LibWeb/Painting/ChromeMetrics.h>
#include <LibWeb/Painting/DisplayListRecorder.h>
@ -532,7 +533,7 @@ Optional<PaintableBox::ScrollbarData> PaintableBox::compute_scrollbar_data(Scrol
scrollbar_data.thumb_travel_to_scroll_ratio = (usable_scrollbar_length - thumb_length) / (scrollable_overflow_length - scrollport_size);
if (scroll_state_snapshot) {
auto own_offset = scroll_state_snapshot->own_offset_for_frame_with_id(own_scroll_frame_id().value());
auto own_offset = scroll_state_snapshot->css_offset_for_frame_with_id(own_scroll_frame_id().value());
CSSPixels scroll_offset = is_horizontal ? -own_offset.x() : -own_offset.y();
CSSPixels thumb_offset = scroll_offset * scrollbar_data.thumb_travel_to_scroll_ratio;
@ -608,7 +609,7 @@ void PaintableBox::paint(DisplayListRecordingContext& context, PaintPhase phase)
own_scroll_frame_id().value(),
context.rounded_device_rect(scrollbar_data->gutter_rect).to_type<int>(),
context.rounded_device_rect(scrollbar_data->thumb_rect).to_type<int>(),
scrollbar_data->thumb_travel_to_scroll_ratio,
scrollbar_data->thumb_travel_to_scroll_ratio.to_double(),
scrollbar_colors.thumb_color,
scrollbar_colors.track_color,
direction == ScrollDirection::Vertical);
@ -720,7 +721,7 @@ void PaintableBox::paint_backdrop_filter(DisplayListRecordingContext& context) c
auto border_radii_data = normalized_border_radii_data();
ScopedCornerRadiusClip corner_clipper { context, backdrop_region, border_radii_data };
if (auto resolved_backdrop_filter = to_gfx_filter(m_backdrop_filter, context.device_pixels_per_css_pixel()); resolved_backdrop_filter.has_value())
context.display_list_recorder().apply_backdrop_filter(backdrop_region.to_type<int>(), border_radii_data, *resolved_backdrop_filter);
context.display_list_recorder().apply_backdrop_filter(backdrop_region.to_type<int>(), border_radii_data.as_corners(context.device_pixel_converter()), *resolved_backdrop_filter);
}
void PaintableBox::paint_background(DisplayListRecordingContext& context) const
@ -782,10 +783,12 @@ CSSPixelPoint PaintableBox::transform_to_local_coordinates(CSSPixelPoint screen_
if (!accumulated_visual_context())
return screen_position;
auto const& viewport_paintable = *document().paintable();
auto const& scroll_state = viewport_paintable.scroll_state_snapshot();
auto local_pos = accumulated_visual_context()->transform_point_for_hit_test(screen_position, scroll_state);
return local_pos.value_or(screen_position);
auto pixel_ratio = static_cast<float>(document().page().client().device_pixels_per_css_pixel());
auto const& scroll_state = document().paintable()->scroll_state_snapshot();
auto result = accumulated_visual_context()->transform_point_for_hit_test(screen_position.to_type<float>() * pixel_ratio, scroll_state.device_offsets());
if (!result.has_value())
return screen_position;
return (*result / pixel_ratio).to_type<CSSPixels>();
}
bool PaintableBox::has_resizer() const
@ -1038,13 +1041,16 @@ TraversalDecision PaintableBox::hit_test(CSSPixelPoint position, HitTestType typ
if (!is_visible || !visible_for_hit_testing())
return TraversalDecision::Continue;
auto const& viewport_paintable = *document().paintable();
auto const& scroll_state = viewport_paintable.scroll_state_snapshot();
auto pixel_ratio = static_cast<float>(document().page().client().device_pixels_per_css_pixel());
auto const& scroll_state = document().paintable()->scroll_state_snapshot();
Optional<CSSPixelPoint> local_position;
if (auto state = accumulated_visual_context())
local_position = state->transform_point_for_hit_test(position, scroll_state);
else
if (auto state = accumulated_visual_context()) {
auto result = state->transform_point_for_hit_test(position.to_type<float>() * pixel_ratio, scroll_state.device_offsets());
if (result.has_value())
local_position = (*result / pixel_ratio).to_type<CSSPixels>();
} else {
local_position = position;
}
if (!local_position.has_value())
return TraversalDecision::Continue;

View file

@ -15,6 +15,7 @@
#include <LibWeb/HTML/Navigable.h>
#include <LibWeb/Layout/BlockContainer.h>
#include <LibWeb/Layout/InlineNode.h>
#include <LibWeb/Page/Page.h>
#include <LibWeb/Painting/DisplayListRecorder.h>
#include <LibWeb/Painting/PaintableWithLines.h>
#include <LibWeb/Painting/ShadowPainting.h>
@ -76,6 +77,7 @@ void PaintableWithLines::paint_text_fragment_debug_highlight(DisplayListRecordin
TraversalDecision PaintableWithLines::hit_test(CSSPixelPoint position, HitTestType type, Function<TraversalDecision(HitTestResult)> const& callback) const
{
auto const is_visible = computed_values().visibility() == CSS::Visibility::Visible;
auto pixel_ratio = static_cast<float>(document().page().client().device_pixels_per_css_pixel());
auto const& scroll_state = document().paintable()->scroll_state_snapshot();
Optional<CSSPixelPoint> local_position;
@ -85,10 +87,13 @@ TraversalDecision PaintableWithLines::hit_test(CSSPixelPoint position, HitTestTy
if (exchange(acquired_local_position, true))
return;
if (auto state = accumulated_visual_context())
local_position = state->transform_point_for_hit_test(position, scroll_state);
else
if (auto state = accumulated_visual_context()) {
auto result = state->transform_point_for_hit_test(position.to_type<float>() * pixel_ratio, scroll_state.device_offsets());
if (result.has_value())
local_position = (*result / pixel_ratio).to_type<CSSPixels>();
} else {
local_position = position;
}
};
// TextCursor hit testing mode should be able to place cursor in contenteditable elements even if they are empty.
@ -137,9 +142,14 @@ TraversalDecision PaintableWithLines::hit_test(CSSPixelPoint position, HitTestTy
// Fragments are descendants of this element, so use the descendants' visual context to account for this element's
// own scroll offset during fragment hit testing.
auto avc_for_descendants = accumulated_visual_context_for_descendants();
auto local_position_for_fragments = avc_for_descendants
? avc_for_descendants->transform_point_for_hit_test(position, scroll_state)
: local_position;
Optional<CSSPixelPoint> local_position_for_fragments;
if (avc_for_descendants) {
auto result = avc_for_descendants->transform_point_for_hit_test(position.to_type<float>() * pixel_ratio, scroll_state.device_offsets());
if (result.has_value())
local_position_for_fragments = (*result / pixel_ratio).to_type<CSSPixels>();
} else {
local_position_for_fragments = local_position;
}
if (local_position_for_fragments.has_value()) {
if (hit_test_fragments(position, local_position_for_fragments.value(), type, callback) == TraversalDecision::Break)
return TraversalDecision::Break;

View file

@ -75,7 +75,7 @@ static RefPtr<DisplayList> paint_mask_or_clip_to_display_list(
bool is_clip_path)
{
auto mask_rect = context.enclosing_device_rect(area);
auto display_list = DisplayList::create(context.device_pixels_per_css_pixel());
auto display_list = DisplayList::create();
DisplayListRecorder display_list_recorder(*display_list);
display_list_recorder.translate(-mask_rect.location().to_type<int>());
auto paint_context = context.clone(display_list_recorder);

View file

@ -8,12 +8,17 @@
namespace Web::Painting {
ScrollStateSnapshot ScrollStateSnapshot::create(Vector<NonnullRefPtr<ScrollFrame>> const& scroll_frames)
ScrollStateSnapshot ScrollStateSnapshot::create(Vector<NonnullRefPtr<ScrollFrame>> const& scroll_frames, double device_pixels_per_css_pixel)
{
ScrollStateSnapshot snapshot;
snapshot.own_offsets.ensure_capacity(scroll_frames.size());
for (auto const& scroll_frame : scroll_frames)
snapshot.own_offsets.append({ scroll_frame->m_own_offset });
auto scale = static_cast<float>(device_pixels_per_css_pixel);
snapshot.m_css_offsets.ensure_capacity(scroll_frames.size());
snapshot.m_device_offsets.ensure_capacity(scroll_frames.size());
for (auto const& scroll_frame : scroll_frames) {
auto const& offset = scroll_frame->m_own_offset;
snapshot.m_css_offsets.unchecked_append(offset);
snapshot.m_device_offsets.unchecked_append(offset.to_type<float>() * scale);
}
return snapshot;
}

View file

@ -6,23 +6,27 @@
#pragma once
#include <LibGfx/Point.h>
#include <LibWeb/Painting/ScrollFrame.h>
namespace Web::Painting {
class ScrollStateSnapshot {
public:
static ScrollStateSnapshot create(Vector<NonnullRefPtr<ScrollFrame>> const& scroll_frames);
static ScrollStateSnapshot create(Vector<NonnullRefPtr<ScrollFrame>> const& scroll_frames, double device_pixels_per_css_pixel);
CSSPixelPoint own_offset_for_frame_with_id(size_t id) const
ReadonlySpan<Gfx::FloatPoint> device_offsets() const { return m_device_offsets; }
CSSPixelPoint css_offset_for_frame_with_id(size_t id) const
{
if (id >= own_offsets.size())
if (id >= m_css_offsets.size())
return {};
return own_offsets[id];
return m_css_offsets[id];
}
private:
Vector<CSSPixelPoint> own_offsets;
Vector<CSSPixelPoint> m_css_offsets;
Vector<Gfx::FloatPoint> m_device_offsets;
};
class ScrollState {
@ -69,9 +73,9 @@ public:
private:
friend class ViewportPaintable;
ScrollStateSnapshot snapshot() const
ScrollStateSnapshot snapshot(double device_pixels_per_css_pixel) const
{
return ScrollStateSnapshot::create(m_scroll_frames);
return ScrollStateSnapshot::create(m_scroll_frames, device_pixels_per_css_pixel);
}
Vector<NonnullRefPtr<ScrollFrame>> m_scroll_frames;

View file

@ -13,6 +13,7 @@
#include <LibWeb/DOM/Document.h>
#include <LibWeb/Layout/ReplacedBox.h>
#include <LibWeb/Layout/Viewport.h>
#include <LibWeb/Page/Page.h>
#include <LibWeb/Painting/DisplayList.h>
#include <LibWeb/Painting/DisplayListRecorder.h>
#include <LibWeb/Painting/PaintableBox.h>
@ -313,7 +314,7 @@ void StackingContext::paint(DisplayListRecordingContext& context) const
Vector<DisplayListRecorder::MaskInfo> masks;
if (mask_image) {
auto mask_display_list = DisplayList::create(context.device_pixels_per_css_pixel());
auto mask_display_list = DisplayList::create();
DisplayListRecorder display_list_recorder(*mask_display_list);
auto mask_painting_context = context.clone(display_list_recorder);
auto mask_rect_in_device_pixels = context.enclosing_device_rect(paintable_box().absolute_padding_box_rect());
@ -391,13 +392,16 @@ TraversalDecision StackingContext::hit_test(CSSPixelPoint position, HitTestType
// Hit test the stacking context root's own fragments if it's a PaintableWithLines.
if (is<PaintableWithLines>(paintable_box())) {
auto const& paintable_with_lines = as<PaintableWithLines>(paintable_box());
auto const& viewport_paintable = *paintable_box().document().paintable();
auto const& scroll_state = viewport_paintable.scroll_state_snapshot();
auto pixel_ratio = static_cast<float>(paintable_box().document().page().client().device_pixels_per_css_pixel());
auto const& scroll_state = paintable_box().document().paintable()->scroll_state_snapshot();
Optional<CSSPixelPoint> local_position;
if (auto state = paintable_box().accumulated_visual_context())
local_position = state->transform_point_for_hit_test(position, scroll_state);
else
if (auto state = paintable_box().accumulated_visual_context()) {
auto result = state->transform_point_for_hit_test(position.to_type<float>() * pixel_ratio, scroll_state.device_offsets());
if (result.has_value())
local_position = (*result / pixel_ratio).to_type<CSSPixels>();
} else {
local_position = position;
}
if (local_position.has_value()) {
if (paintable_with_lines.hit_test_fragments(position, local_position.value(), type, callback) == TraversalDecision::Break)
@ -439,13 +443,16 @@ TraversalDecision StackingContext::hit_test(CSSPixelPoint position, HitTestType
if (!is_visible || !paintable_box().visible_for_hit_testing())
return TraversalDecision::Continue;
auto const& viewport_paintable = *paintable_box().document().paintable();
auto const& scroll_state = viewport_paintable.scroll_state_snapshot();
auto pixel_ratio = static_cast<float>(paintable_box().document().page().client().device_pixels_per_css_pixel());
auto const& scroll_state = paintable_box().document().paintable()->scroll_state_snapshot();
Optional<CSSPixelPoint> local_position;
if (auto state = paintable_box().accumulated_visual_context())
local_position = state->transform_point_for_hit_test(position, scroll_state);
else
if (auto state = paintable_box().accumulated_visual_context()) {
auto result = state->transform_point_for_hit_test(position.to_type<float>() * pixel_ratio, scroll_state.device_offsets());
if (result.has_value())
local_position = (*result / pixel_ratio).to_type<CSSPixels>();
} else {
local_position = position;
}
if (local_position.has_value() && paintable_box().absolute_border_box_rect().contains(local_position.value())) {
if (callback({ const_cast<PaintableBox&>(paintable_box()) }) == TraversalDecision::Break)

View file

@ -11,9 +11,12 @@
#include <LibWeb/DOM/Range.h>
#include <LibWeb/Layout/TextNode.h>
#include <LibWeb/Layout/Viewport.h>
#include <LibWeb/Page/Page.h>
#include <LibWeb/Painting/AccumulatedVisualContext.h>
#include <LibWeb/Painting/Blending.h>
#include <LibWeb/Painting/DevicePixelConverter.h>
#include <LibWeb/Painting/DisplayListRecorder.h>
#include <LibWeb/Painting/ResolvedCSSFilter.h>
#include <LibWeb/Painting/ScrollFrame.h>
#include <LibWeb/Painting/StackingContext.h>
#include <LibWeb/Painting/ViewportPaintable.h>
@ -166,7 +169,22 @@ static CSSPixelRect effective_css_clip_rect(CSSPixelRect const& css_clip)
return css_clip;
}
static Optional<TransformData> compute_transform(PaintableBox const& paintable_box, CSS::ComputedValues const& computed_values)
// Converts a CSS-pixel-space 4x4 matrix to device-pixel-space.
// - Translation column (column 3, rows 0-2) is scaled up by DPR
// - Perspective row (row 3, columns 0-2) is scaled down by DPR
// - All other elements are unaffected (the scale factors cancel out)
static FloatMatrix4x4 scale_matrix_for_device_pixels(FloatMatrix4x4 matrix, float scale)
{
matrix[0, 3] *= scale;
matrix[1, 3] *= scale;
matrix[2, 3] *= scale;
matrix[3, 0] /= scale;
matrix[3, 1] /= scale;
matrix[3, 2] /= scale;
return matrix;
}
static Optional<TransformData> compute_transform(PaintableBox const& paintable_box, CSS::ComputedValues const& computed_values, double pixel_ratio)
{
if (!paintable_box.has_css_transform())
return {};
@ -182,9 +200,13 @@ static Optional<TransformData> compute_transform(PaintableBox const& paintable_b
matrix = matrix * transform->to_matrix(paintable_box).release_value();
auto const& css_transform_origin = computed_values.transform_origin();
auto reference_box = paintable_box.transform_reference_box();
auto origin_x = reference_box.left() + css_transform_origin.x.to_px(paintable_box.layout_node(), reference_box.width());
auto origin_y = reference_box.top() + css_transform_origin.y.to_px(paintable_box.layout_node(), reference_box.height());
return TransformData { matrix, { origin_x, origin_y } };
CSSPixelPoint origin {
reference_box.left() + css_transform_origin.x.to_px(paintable_box.layout_node(), reference_box.width()),
reference_box.top() + css_transform_origin.y.to_px(paintable_box.layout_node(), reference_box.height()),
};
auto scale = static_cast<float>(pixel_ratio);
auto device_origin = origin.to_type<float>() * scale;
return TransformData { scale_matrix_for_device_pixels(matrix, scale), device_origin };
}
// https://drafts.csswg.org/css-transforms-2/#perspective-matrix
@ -217,7 +239,7 @@ static Optional<Gfx::FloatMatrix4x4> compute_perspective_matrix(PaintableBox con
return perspective_matrix;
}
static Optional<ClipData> compute_clip_data(PaintableBox const& paintable_box, CSS::ComputedValues const& computed_values)
static Optional<ClipData> compute_clip_data(PaintableBox const& paintable_box, CSS::ComputedValues const& computed_values, DevicePixelConverter const& converter)
{
auto overflow_x = computed_values.overflow_x();
auto overflow_y = computed_values.overflow_y();
@ -273,7 +295,7 @@ static Optional<ClipData> compute_clip_data(PaintableBox const& paintable_box, C
// clipping region is not rounded.
// FIXME: Adjust the border radii for the overflow-clip-margin case. (see https://drafts.csswg.org/css-overflow-4/#valdef-overflow-clip-margin-length-0 )
auto radii = (overflow_x != CSS::Overflow::Visible && overflow_y != CSS::Overflow::Visible) ? paintable_box.normalized_border_radii_data(PaintableBox::ShrinkRadiiForBorders::Yes) : BorderRadiiData {};
return ClipData { clip_rect, radii };
return ClipData { converter.rounded_device_rect(clip_rect), radii.as_corners(converter) };
}
return {};
@ -283,16 +305,21 @@ void ViewportPaintable::assign_accumulated_visual_contexts()
{
m_next_accumulated_visual_context_id = 1;
auto pixel_ratio = document().page().client().device_pixels_per_css_pixel();
DevicePixelConverter converter { pixel_ratio };
auto scale = static_cast<float>(pixel_ratio);
auto append_node = [&](RefPtr<AccumulatedVisualContext const> parent, VisualContextData data) {
return AccumulatedVisualContext::create(allocate_accumulated_visual_context_id(), move(data), parent);
};
auto make_effects_data = [](PaintableBox const& box) -> Optional<EffectsData> {
auto make_effects_data = [&](PaintableBox const& box) -> Optional<EffectsData> {
auto const& computed_values = box.computed_values();
auto gfx_filter = to_gfx_filter(box.filter(), pixel_ratio);
EffectsData effects {
computed_values.opacity(),
mix_blend_mode_to_compositing_and_blending_operator(computed_values.mix_blend_mode()),
box.filter()
move(gfx_filter)
};
if (!effects.needs_layer())
return {};
@ -303,7 +330,8 @@ void ViewportPaintable::assign_accumulated_visual_contexts()
m_visual_viewport_context = nullptr;
auto transform = document().visual_viewport()->transform();
if (!transform.is_identity()) {
m_visual_viewport_context = append_node(nullptr, TransformData { transform.to_matrix(), CSSPixelPoint { 0, 0 } });
auto matrix = scale_matrix_for_device_pixels(transform.to_matrix(), scale);
m_visual_viewport_context = append_node(nullptr, TransformData { matrix, { 0.f, 0.f } });
}
RefPtr<AccumulatedVisualContext const> viewport_state_for_descendants = m_visual_viewport_context;
@ -364,15 +392,17 @@ void ViewportPaintable::assign_accumulated_visual_contexts()
if (auto effects = make_effects_data(paintable_box); effects.has_value())
own_state = append_node(own_state, effects.release_value());
if (auto transform_data = compute_transform(paintable_box, computed_values); transform_data.has_value()) {
if (auto transform_data = compute_transform(paintable_box, computed_values, pixel_ratio); transform_data.has_value()) {
paintable_box.set_has_non_invertible_css_transform(!transform_data->matrix.is_invertible());
own_state = append_node(own_state, *transform_data);
} else {
paintable_box.set_has_non_invertible_css_transform(false);
}
if (auto css_clip = paintable_box.get_clip_rect(); css_clip.has_value())
own_state = append_node(own_state, ClipData { effective_css_clip_rect(*css_clip), {} });
if (auto css_clip = paintable_box.get_clip_rect(); css_clip.has_value()) {
auto effective_rect = effective_css_clip_rect(*css_clip);
own_state = append_node(own_state, ClipData { converter.rounded_device_rect(effective_rect), {} });
}
// FIXME: Support other geometry boxes. See: https://drafts.fxtf.org/css-masking/#typedef-geometry-box
if (auto const& clip_path = computed_values.clip_path(); clip_path.has_value() && clip_path->is_basic_shape()) {
@ -385,7 +415,9 @@ void ViewportPaintable::assign_accumulated_visual_contexts()
[](CSS::Polygon const& polygon) { return polygon.fill_rule; },
[](CSS::Path const& path) { return path.fill_rule; },
[](auto const&) { return Gfx::WindingRule::Nonzero; });
own_state = append_node(own_state, ClipPathData { move(path), masking_area, fill_rule });
auto device_path = path.copy_transformed(Gfx::AffineTransform {}.set_scale(scale, scale));
auto device_bounding_rect = converter.rounded_device_rect(masking_area);
own_state = append_node(own_state, ClipPathData { move(device_path), device_bounding_rect, fill_rule });
}
paintable_box.set_accumulated_visual_context(own_state);
@ -393,10 +425,12 @@ void ViewportPaintable::assign_accumulated_visual_contexts()
// Build state for descendants: own state + perspective + clip + scroll.
RefPtr<AccumulatedVisualContext const> state_for_descendants = own_state;
if (auto perspective_matrix = compute_perspective_matrix(paintable_box, computed_values); perspective_matrix.has_value())
state_for_descendants = append_node(state_for_descendants, PerspectiveData { *perspective_matrix });
if (auto perspective_matrix = compute_perspective_matrix(paintable_box, computed_values); perspective_matrix.has_value()) {
auto scaled_matrix = scale_matrix_for_device_pixels(*perspective_matrix, scale);
state_for_descendants = append_node(state_for_descendants, PerspectiveData { scaled_matrix });
}
if (auto clip_data = compute_clip_data(paintable_box, computed_values); clip_data.has_value())
if (auto clip_data = compute_clip_data(paintable_box, computed_values, converter); clip_data.has_value())
state_for_descendants = append_node(state_for_descendants, clip_data.value());
if (paintable_box.own_scroll_frame()) {
@ -471,7 +505,7 @@ void ViewportPaintable::refresh_scroll_state()
scroll_frame->set_own_offset(-scroll_frame->paintable_box().scroll_offset());
});
m_scroll_state_snapshot = m_scroll_state.snapshot();
m_scroll_state_snapshot = m_scroll_state.snapshot(document().page().client().device_pixels_per_css_pixel());
}
static void resolve_paint_only_properties_in_subtree(Paintable& root)

View file

@ -221,7 +221,7 @@ void PageClient::set_viewport(Web::DevicePixelSize const& size, double device_pi
void PageClient::set_zoom_level(double zoom_level)
{
m_zoom_level = zoom_level;
page().top_level_traversable()->set_viewport_size(page().device_to_css_size(m_viewport_size));
page().top_level_traversable()->set_viewport_size(page().device_to_css_size(m_viewport_size), Web::InvalidateDisplayList::Yes);
}
void PageClient::set_maximum_frames_per_second(u64 maximum_frames_per_second)

View file

@ -1,9 +1,9 @@
AccumulatedVisualContext Tree:
[2] clip=[23,23 91.328125x150]
[2] clip=[23,23 91x150]
[3] scroll_frame_id=1 (PaintableWithLines(BlockContainer(anonymous)))
[4] clip=[126.328125,23 91.328125x150]
[4] clip=[126,23 92x150]
[5] scroll_frame_id=2 (PaintableWithLines(BlockContainer(anonymous)))
[6] clip=[229.65625,23 91.328125x150]
[6] clip=[230,23 91x150]
[7] scroll_frame_id=3 (PaintableWithLines(BlockContainer(anonymous)))
DisplayList: