LibWeb: Store visual context nodes in arena-based tree
Replace per-node heap-allocated AtomicRefCounted AccumulatedVisualContext objects with a single contiguous Vector inside AccumulatedVisualContextTree. All nodes for a frame are now stored in one allocation, using type-safe VisualContextIndex instead of RefPtr pointers. This reduces allocation churn, improves cache locality, and opens the door for future snapshotting of visual context state — similar to how scroll offsets are snapshotted today.
This commit is contained in:
parent
834675aea3
commit
9d2ebe90ed
20 changed files with 258 additions and 207 deletions
|
|
@ -101,7 +101,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();
|
||||
auto display_list = Painting::DisplayList::create(Painting::AccumulatedVisualContextTree::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() };
|
||||
|
||||
|
|
|
|||
|
|
@ -7344,7 +7344,10 @@ 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();
|
||||
update_paint_and_hit_testing_properties_if_needed();
|
||||
VERIFY(paintable());
|
||||
|
||||
auto display_list = Painting::DisplayList::create(*paintable()->visual_context_tree());
|
||||
Painting::DisplayListRecorder display_list_recorder(display_list);
|
||||
|
||||
// https://drafts.csswg.org/css-color-adjust-1/#color-scheme-effect
|
||||
|
|
@ -7380,17 +7383,12 @@ RefPtr<Painting::DisplayList> Document::record_display_list(HTML::PaintConfig co
|
|||
display_list_recorder.fill_rect(bitmap_rect, CSS::SystemColor::canvas(color_scheme));
|
||||
|
||||
display_list_recorder.fill_rect(bitmap_rect, background_color());
|
||||
if (!paintable()) {
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
Web::DisplayListRecordingContext context(display_list_recorder, page().palette(), page().client().device_pixels_per_css_pixel(), page().chrome_metrics());
|
||||
context.set_device_viewport_rect(viewport_rect);
|
||||
context.set_should_show_line_box_borders(config.should_show_line_box_borders);
|
||||
context.set_should_paint_overlay(config.paint_overlay);
|
||||
|
||||
update_paint_and_hit_testing_properties_if_needed();
|
||||
|
||||
auto& viewport_paintable = *paintable();
|
||||
|
||||
viewport_paintable.paint_all_phases(context);
|
||||
|
|
@ -7642,41 +7640,45 @@ String Document::dump_display_list()
|
|||
|
||||
HashMap<size_t, Painting::PaintableBox const*> context_id_to_paintable;
|
||||
viewport_paintable->for_each_in_inclusive_subtree_of_type<Painting::PaintableBox>([&](auto const& paintable_box) {
|
||||
if (auto context = paintable_box.accumulated_visual_context())
|
||||
(void)context_id_to_paintable.try_set(context->id(), &paintable_box);
|
||||
auto visual_context_index = paintable_box.accumulated_visual_context_index();
|
||||
if (visual_context_index.value())
|
||||
(void)context_id_to_paintable.try_set(visual_context_index.value(), &paintable_box);
|
||||
return TraversalDecision::Continue;
|
||||
});
|
||||
|
||||
HashTable<Painting::AccumulatedVisualContext const*> visited;
|
||||
HashMap<Painting::AccumulatedVisualContext const*, Vector<Painting::AccumulatedVisualContext const*>> children;
|
||||
Vector<Painting::AccumulatedVisualContext const*> root_contexts;
|
||||
StringBuilder builder;
|
||||
builder.append("AccumulatedVisualContext Tree:\n"sv);
|
||||
|
||||
auto const& visual_context_tree = display_list->visual_context_tree();
|
||||
HashTable<size_t> visited;
|
||||
HashMap<size_t, Vector<size_t>> children;
|
||||
Vector<size_t> root_contexts;
|
||||
|
||||
for (auto const& item : display_list->commands()) {
|
||||
if (!item.context)
|
||||
if (!item.context_index.value())
|
||||
continue;
|
||||
for (auto const* node = item.context.ptr(); node && !visited.contains(node); node = node->parent().ptr()) {
|
||||
visited.set(node);
|
||||
if (auto const* parent = node->parent().ptr())
|
||||
children.ensure(parent).append(node);
|
||||
else if (!root_contexts.contains_slow(node))
|
||||
root_contexts.append(node);
|
||||
for (size_t node_index = item.context_index.value(); node_index && !visited.contains(node_index); node_index = visual_context_tree.node_at(Painting::VisualContextIndex(node_index)).parent_index.value()) {
|
||||
visited.set(node_index);
|
||||
auto parent = visual_context_tree.node_at(Painting::VisualContextIndex(node_index)).parent_index.value();
|
||||
if (parent)
|
||||
children.ensure(parent).append(node_index);
|
||||
else if (!root_contexts.contains_slow(node_index))
|
||||
root_contexts.append(node_index);
|
||||
}
|
||||
}
|
||||
|
||||
StringBuilder builder;
|
||||
builder.append("AccumulatedVisualContext Tree:\n"sv);
|
||||
Function<void(Painting::AccumulatedVisualContext const*, size_t)> dump_context = [&](auto const* node, size_t indent) {
|
||||
Function<void(size_t, size_t)> dump_context = [&](size_t node_index, size_t indent) {
|
||||
builder.append_repeated(' ', indent * 2);
|
||||
builder.appendff("[{}] ", node->id());
|
||||
node->dump(builder);
|
||||
if (auto it = context_id_to_paintable.find(node->id()); it != context_id_to_paintable.end())
|
||||
builder.appendff("[{}] ", node_index);
|
||||
visual_context_tree.dump(Painting::VisualContextIndex(node_index), builder);
|
||||
if (auto it = context_id_to_paintable.find(node_index); it != context_id_to_paintable.end())
|
||||
builder.appendff(" ({})", it->value->debug_description());
|
||||
builder.append('\n');
|
||||
for (auto const* child : children.get(node).value_or({}))
|
||||
dump_context(child, indent + 1);
|
||||
for (auto child_node_index : children.get(node_index).value_or({}))
|
||||
dump_context(child_node_index, indent + 1);
|
||||
};
|
||||
|
||||
for (auto const* root : root_contexts)
|
||||
for (auto root : root_contexts)
|
||||
dump_context(root, 1);
|
||||
|
||||
builder.append("\nDisplayList:\n"sv);
|
||||
|
|
@ -7696,7 +7698,7 @@ String Document::dump_display_list()
|
|||
|
||||
builder.append_repeated(' ', indent * 2);
|
||||
item.command.visit([&](auto const& command) {
|
||||
builder.appendff("{}@{}", command.command_name, item.context ? item.context->id() : 0);
|
||||
builder.appendff("{}@{}", command.command_name, item.context_index.value());
|
||||
command.dump(builder);
|
||||
});
|
||||
builder.append('\n');
|
||||
|
|
|
|||
|
|
@ -2587,7 +2587,7 @@ CSSPixelPoint Navigable::to_top_level_position(CSSPixelPoint a_position)
|
|||
return {};
|
||||
|
||||
if (auto const* paintable_box = as_if<Painting::PaintableBox>(*paintable);
|
||||
paintable_box && paintable_box->accumulated_visual_context()) {
|
||||
paintable_box && paintable_box->accumulated_visual_context_index().value()) {
|
||||
auto point = paintable_box->absolute_position();
|
||||
point.translate_by(position);
|
||||
position = paintable_box->transform_rect_to_viewport({ point, { 0, 0 } }).location();
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ static Optional<CSSPixelRect> scrollport_rect_in_viewport(Painting::PaintableBox
|
|||
if (paintable_box.is_viewport_paintable())
|
||||
return scrollport;
|
||||
|
||||
if (!paintable_box.accumulated_visual_context())
|
||||
if (!paintable_box.accumulated_visual_context_index().value())
|
||||
return {};
|
||||
return paintable_box.transform_rect_to_viewport(scrollport);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,28 +12,76 @@
|
|||
|
||||
namespace Web::Painting {
|
||||
|
||||
NonnullRefPtr<AccumulatedVisualContext> AccumulatedVisualContext::create(size_t id, VisualContextData data, RefPtr<AccumulatedVisualContext const> parent)
|
||||
{
|
||||
return adopt_ref(*new AccumulatedVisualContext(id, move(data), move(parent)));
|
||||
}
|
||||
|
||||
bool ClipData::contains(DevicePixelPoint point) const
|
||||
{
|
||||
return corner_radii.contains(point.to_type<int>(), rect.to_type<int>());
|
||||
}
|
||||
|
||||
Optional<Gfx::FloatPoint> AccumulatedVisualContext::transform_point_for_hit_test(Gfx::FloatPoint screen_point, ScrollStateSnapshot const& scroll_state) const
|
||||
NonnullRefPtr<AccumulatedVisualContextTree> AccumulatedVisualContextTree::create()
|
||||
{
|
||||
Vector<AccumulatedVisualContext const*, 8> chain;
|
||||
chain.ensure_capacity(m_depth);
|
||||
for (auto const* node = this; node; node = node->parent().ptr())
|
||||
chain.append(node);
|
||||
auto visual_context_tree = adopt_ref(*new AccumulatedVisualContextTree());
|
||||
// Sentinel at index 0 (null context). Data type doesn't matter; it's never accessed.
|
||||
visual_context_tree->m_nodes.append({ ScrollData { 0, false }, {}, 0, false });
|
||||
return visual_context_tree;
|
||||
}
|
||||
|
||||
VisualContextIndex AccumulatedVisualContextTree::append(VisualContextData data, VisualContextIndex parent_index)
|
||||
{
|
||||
size_t depth = parent_index.value() ? m_nodes[parent_index.value()].depth + 1 : 1;
|
||||
|
||||
bool empty_clip = false;
|
||||
if (parent_index.value() && m_nodes[parent_index.value()].has_empty_effective_clip) {
|
||||
empty_clip = true;
|
||||
} else if (data.has<ClipData>()) {
|
||||
empty_clip = data.get<ClipData>().rect.is_empty();
|
||||
} else if (data.has<ClipPathData>()) {
|
||||
empty_clip = data.get<ClipPathData>().path.bounding_box().is_empty();
|
||||
}
|
||||
|
||||
auto index = VisualContextIndex(m_nodes.size());
|
||||
m_nodes.append({ move(data), parent_index, depth, empty_clip });
|
||||
return index;
|
||||
}
|
||||
|
||||
VisualContextIndex AccumulatedVisualContextTree::find_common_ancestor(VisualContextIndex a, VisualContextIndex b) const
|
||||
{
|
||||
if (!a.value() || !b.value())
|
||||
return {};
|
||||
size_t a_index = a.value();
|
||||
size_t b_index = b.value();
|
||||
while (m_nodes[a_index].depth > m_nodes[b_index].depth)
|
||||
a_index = m_nodes[a_index].parent_index.value();
|
||||
while (m_nodes[b_index].depth > m_nodes[a_index].depth)
|
||||
b_index = m_nodes[b_index].parent_index.value();
|
||||
while (a_index != b_index) {
|
||||
a_index = m_nodes[a_index].parent_index.value();
|
||||
b_index = m_nodes[b_index].parent_index.value();
|
||||
}
|
||||
return VisualContextIndex(a_index);
|
||||
}
|
||||
|
||||
Vector<size_t, 8> AccumulatedVisualContextTree::build_ancestor_chain(VisualContextIndex index) const
|
||||
{
|
||||
auto const& node = m_nodes[index.value()];
|
||||
Vector<size_t, 8> chain;
|
||||
chain.ensure_capacity(node.depth);
|
||||
for (size_t i = index.value(); i; i = m_nodes[i].parent_index.value())
|
||||
chain.append(i);
|
||||
return chain;
|
||||
}
|
||||
|
||||
Optional<Gfx::FloatPoint> AccumulatedVisualContextTree::transform_point_for_hit_test(VisualContextIndex index, Gfx::FloatPoint screen_point, ScrollStateSnapshot const& scroll_state) const
|
||||
{
|
||||
if (!index.value())
|
||||
return screen_point;
|
||||
|
||||
auto chain = build_ancestor_chain(index);
|
||||
|
||||
auto point = screen_point;
|
||||
for (size_t i = chain.size(); i > 0; --i) {
|
||||
auto const* node = chain[i - 1];
|
||||
auto const& node = m_nodes[chain[i - 1]];
|
||||
|
||||
auto result = node->data().visit(
|
||||
auto result = node.data.visit(
|
||||
[&](PerspectiveData const& perspective) -> Optional<Gfx::FloatPoint> {
|
||||
auto affine = Gfx::extract_2d_affine_transform(perspective.matrix);
|
||||
auto inverse = affine.inverse();
|
||||
|
|
@ -85,18 +133,18 @@ Optional<Gfx::FloatPoint> AccumulatedVisualContext::transform_point_for_hit_test
|
|||
return point;
|
||||
}
|
||||
|
||||
Gfx::FloatPoint AccumulatedVisualContext::inverse_transform_point(Gfx::FloatPoint screen_point) const
|
||||
Gfx::FloatPoint AccumulatedVisualContextTree::inverse_transform_point(VisualContextIndex index, Gfx::FloatPoint screen_point) const
|
||||
{
|
||||
Vector<AccumulatedVisualContext const*, 8> chain;
|
||||
chain.ensure_capacity(m_depth);
|
||||
for (auto const* node = this; node; node = node->parent().ptr())
|
||||
chain.append(node);
|
||||
if (!index.value())
|
||||
return screen_point;
|
||||
|
||||
auto chain = build_ancestor_chain(index);
|
||||
|
||||
auto point = screen_point;
|
||||
for (size_t i = chain.size(); i > 0; --i) {
|
||||
auto const* node = chain[i - 1];
|
||||
auto const& node = m_nodes[chain[i - 1]];
|
||||
|
||||
node->data().visit(
|
||||
node.data.visit(
|
||||
[&](PerspectiveData const& perspective) {
|
||||
auto affine = Gfx::extract_2d_affine_transform(perspective.matrix);
|
||||
auto inverse = affine.inverse();
|
||||
|
|
@ -118,11 +166,15 @@ Gfx::FloatPoint AccumulatedVisualContext::inverse_transform_point(Gfx::FloatPoin
|
|||
return point;
|
||||
}
|
||||
|
||||
Gfx::FloatRect AccumulatedVisualContext::transform_rect_to_viewport(Gfx::FloatRect const& source_rect, ScrollStateSnapshot const& scroll_state) const
|
||||
Gfx::FloatRect AccumulatedVisualContextTree::transform_rect_to_viewport(VisualContextIndex index, Gfx::FloatRect const& source_rect, ScrollStateSnapshot const& scroll_state) const
|
||||
{
|
||||
if (!index.value())
|
||||
return source_rect;
|
||||
|
||||
auto rect = source_rect;
|
||||
for (auto const* node = this; node; node = node->parent().ptr()) {
|
||||
node->data().visit(
|
||||
for (size_t i = index.value(); i; i = m_nodes[i].parent_index.value()) {
|
||||
auto const& node = m_nodes[i];
|
||||
node.data.visit(
|
||||
[&](TransformData const& transform) {
|
||||
auto affine = Gfx::extract_2d_affine_transform(transform.matrix);
|
||||
rect.translate_by(-transform.origin);
|
||||
|
|
@ -144,9 +196,13 @@ Gfx::FloatRect AccumulatedVisualContext::transform_rect_to_viewport(Gfx::FloatRe
|
|||
return rect;
|
||||
}
|
||||
|
||||
void AccumulatedVisualContext::dump(StringBuilder& builder) const
|
||||
void AccumulatedVisualContextTree::dump(VisualContextIndex index, StringBuilder& builder) const
|
||||
{
|
||||
m_data.visit(
|
||||
if (!index.value())
|
||||
return;
|
||||
|
||||
auto const& node = m_nodes[index.value()];
|
||||
node.data.visit(
|
||||
[&](PerspectiveData const&) {
|
||||
builder.append("perspective"sv);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@
|
|||
#pragma once
|
||||
|
||||
#include <AK/AtomicRefCounted.h>
|
||||
#include <AK/DistinctNumeric.h>
|
||||
#include <AK/Variant.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibGfx/CompositingAndBlendingOperator.h>
|
||||
#include <LibGfx/Filter.h>
|
||||
#include <LibGfx/Matrix4x4.h>
|
||||
|
|
@ -22,6 +24,8 @@ namespace Web::Painting {
|
|||
|
||||
class ScrollStateSnapshot;
|
||||
|
||||
AK_TYPEDEF_DISTINCT_ORDERED_ID(size_t, VisualContextIndex);
|
||||
|
||||
struct ScrollData {
|
||||
size_t scroll_frame_id;
|
||||
bool is_sticky;
|
||||
|
|
@ -70,46 +74,36 @@ struct EffectsData {
|
|||
|
||||
using VisualContextData = Variant<ScrollData, ClipData, TransformData, PerspectiveData, ClipPathData, EffectsData>;
|
||||
|
||||
class AccumulatedVisualContext : public AtomicRefCounted<AccumulatedVisualContext> {
|
||||
struct AccumulatedVisualContextNode {
|
||||
VisualContextData data;
|
||||
VisualContextIndex parent_index {};
|
||||
size_t depth { 0 };
|
||||
bool has_empty_effective_clip { false };
|
||||
};
|
||||
|
||||
class AccumulatedVisualContextTree : public AtomicRefCounted<AccumulatedVisualContextTree> {
|
||||
public:
|
||||
static NonnullRefPtr<AccumulatedVisualContext> create(size_t id, VisualContextData data, RefPtr<AccumulatedVisualContext const> parent);
|
||||
static NonnullRefPtr<AccumulatedVisualContextTree> create();
|
||||
|
||||
VisualContextData const& data() const { return m_data; }
|
||||
RefPtr<AccumulatedVisualContext const> parent() const { return m_parent; }
|
||||
VisualContextIndex append(VisualContextData data, VisualContextIndex parent_index);
|
||||
|
||||
bool is_effect() const { return m_data.has<EffectsData>(); }
|
||||
bool has_empty_effective_clip() const { return m_has_empty_effective_clip; }
|
||||
AccumulatedVisualContextNode const& node_at(VisualContextIndex index) const { return m_nodes[index.value()]; }
|
||||
|
||||
size_t depth() const { return m_depth; }
|
||||
size_t id() const { return m_id; }
|
||||
VisualContextIndex find_common_ancestor(VisualContextIndex a, VisualContextIndex b) const;
|
||||
Optional<Gfx::FloatPoint> transform_point_for_hit_test(VisualContextIndex, Gfx::FloatPoint, ScrollStateSnapshot const&) const;
|
||||
Gfx::FloatPoint inverse_transform_point(VisualContextIndex, Gfx::FloatPoint) const;
|
||||
Gfx::FloatRect transform_rect_to_viewport(VisualContextIndex, Gfx::FloatRect const&, ScrollStateSnapshot const&) const;
|
||||
void dump(VisualContextIndex, StringBuilder&) const;
|
||||
|
||||
void dump(StringBuilder&) const;
|
||||
|
||||
Optional<Gfx::FloatPoint> transform_point_for_hit_test(Gfx::FloatPoint, ScrollStateSnapshot const&) const;
|
||||
Gfx::FloatPoint inverse_transform_point(Gfx::FloatPoint) const;
|
||||
Gfx::FloatRect transform_rect_to_viewport(Gfx::FloatRect const&, ScrollStateSnapshot const&) const;
|
||||
bool is_effect(VisualContextIndex i) const { return m_nodes[i.value()].data.has<EffectsData>(); }
|
||||
bool has_empty_effective_clip(VisualContextIndex i) const { return m_nodes[i.value()].has_empty_effective_clip; }
|
||||
|
||||
private:
|
||||
AccumulatedVisualContext(size_t id, VisualContextData data, RefPtr<AccumulatedVisualContext const> parent)
|
||||
: m_data(move(data))
|
||||
, m_parent(move(parent))
|
||||
, m_depth(m_parent ? m_parent->depth() + 1 : 1)
|
||||
, m_id(id)
|
||||
{
|
||||
if (m_parent && m_parent->has_empty_effective_clip()) {
|
||||
m_has_empty_effective_clip = true;
|
||||
} else if (m_data.has<ClipData>()) {
|
||||
m_has_empty_effective_clip = m_data.get<ClipData>().rect.is_empty();
|
||||
} else if (m_data.has<ClipPathData>()) {
|
||||
m_has_empty_effective_clip = m_data.get<ClipPathData>().path.bounding_box().is_empty();
|
||||
}
|
||||
}
|
||||
AccumulatedVisualContextTree() = default;
|
||||
|
||||
VisualContextData m_data;
|
||||
RefPtr<AccumulatedVisualContext const> m_parent;
|
||||
size_t m_depth;
|
||||
size_t m_id;
|
||||
bool m_has_empty_effective_clip { false };
|
||||
Vector<size_t, 8> build_ancestor_chain(VisualContextIndex index) const;
|
||||
|
||||
Vector<AccumulatedVisualContextNode> m_nodes;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
auto text_clip_paths = DisplayList::create(AccumulatedVisualContextTree::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>());
|
||||
|
|
|
|||
|
|
@ -10,11 +10,11 @@
|
|||
|
||||
namespace Web::Painting {
|
||||
|
||||
bool DisplayList::append(DisplayListCommand&& command, RefPtr<AccumulatedVisualContext const> context)
|
||||
bool DisplayList::append(DisplayListCommand&& command, VisualContextIndex context_index)
|
||||
{
|
||||
if (context && context->has_empty_effective_clip())
|
||||
if (context_index.value() && m_visual_context_tree->has_empty_effective_clip(context_index))
|
||||
return false;
|
||||
m_commands.append({ move(context), move(command) });
|
||||
m_commands.append({ context_index, move(command) });
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -64,38 +64,22 @@ void DisplayListPlayer::execute_display_list_into_surface(DisplayList& display_l
|
|||
execute_impl(display_list, scroll_state_snapshot);
|
||||
}
|
||||
|
||||
static RefPtr<AccumulatedVisualContext const> find_common_ancestor(RefPtr<AccumulatedVisualContext const> a, RefPtr<AccumulatedVisualContext const> b)
|
||||
{
|
||||
if (!a || !b)
|
||||
return {};
|
||||
|
||||
while (a->depth() > b->depth())
|
||||
a = a->parent();
|
||||
while (b->depth() > a->depth())
|
||||
b = b->parent();
|
||||
|
||||
while (a != b) {
|
||||
a = a->parent();
|
||||
b = b->parent();
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
void DisplayListPlayer::execute_impl(DisplayList& display_list, ScrollStateSnapshot const& scroll_state)
|
||||
{
|
||||
auto const& commands = display_list.commands();
|
||||
auto const& visual_context_tree = display_list.visual_context_tree();
|
||||
|
||||
VERIFY(m_surface);
|
||||
|
||||
auto for_each_node_from_common_ancestor_to_target = [](this auto const& self, RefPtr<AccumulatedVisualContext const> common_ancestor, RefPtr<AccumulatedVisualContext const> node, auto&& callback) -> void {
|
||||
if (!node || node == common_ancestor)
|
||||
auto for_each_node_from_common_ancestor_to_target = [&](this auto const& self, VisualContextIndex common_ancestor_index, VisualContextIndex target_index, auto&& callback) -> void {
|
||||
if (!target_index.value() || target_index == common_ancestor_index)
|
||||
return;
|
||||
self(common_ancestor, node->parent(), callback);
|
||||
callback(*node);
|
||||
self(common_ancestor_index, visual_context_tree.node_at(target_index).parent_index, callback);
|
||||
callback(visual_context_tree.node_at(target_index));
|
||||
};
|
||||
|
||||
auto apply_accumulated_visual_context = [&](AccumulatedVisualContext const& node) {
|
||||
node.data().visit(
|
||||
auto apply_accumulated_visual_context = [&](AccumulatedVisualContextNode const& node) {
|
||||
node.data.visit(
|
||||
[&](EffectsData const& effects) {
|
||||
apply_effects({ .opacity = effects.opacity, .compositing_and_blending_operator = effects.blend_mode, .filter = effects.gfx_filter });
|
||||
},
|
||||
|
|
@ -126,31 +110,31 @@ void DisplayListPlayer::execute_impl(DisplayList& display_list, ScrollStateSnaps
|
|||
});
|
||||
};
|
||||
|
||||
RefPtr<AccumulatedVisualContext const> applied_context;
|
||||
VisualContextIndex applied_context_index;
|
||||
size_t applied_depth = 0;
|
||||
|
||||
auto switch_to_context = [&](RefPtr<AccumulatedVisualContext const> const& target_context) {
|
||||
if (applied_context == target_context)
|
||||
auto switch_to_context = [&](VisualContextIndex target_index) {
|
||||
if (applied_context_index == target_index)
|
||||
return;
|
||||
|
||||
auto common_ancestor = find_common_ancestor(applied_context, target_context);
|
||||
auto common_ancestor_depth = common_ancestor ? common_ancestor->depth() : 0;
|
||||
auto common_ancestor_index = visual_context_tree.find_common_ancestor(applied_context_index, target_index);
|
||||
size_t common_ancestor_depth = common_ancestor_index.value() ? visual_context_tree.node_at(common_ancestor_index).depth : 0;
|
||||
|
||||
while (applied_depth > common_ancestor_depth) {
|
||||
restore({});
|
||||
applied_depth--;
|
||||
}
|
||||
|
||||
for_each_node_from_common_ancestor_to_target(common_ancestor, target_context, [&](AccumulatedVisualContext const& node) {
|
||||
for_each_node_from_common_ancestor_to_target(common_ancestor_index, target_index, [&](AccumulatedVisualContextNode const& node) {
|
||||
apply_accumulated_visual_context(node);
|
||||
applied_depth++;
|
||||
});
|
||||
|
||||
applied_context = target_context;
|
||||
applied_context_index = target_index;
|
||||
};
|
||||
|
||||
for (size_t command_index = 0; command_index < commands.size(); command_index++) {
|
||||
auto const& [context, command] = commands[command_index];
|
||||
auto const& [context_index, command] = commands[command_index];
|
||||
|
||||
auto bounding_rect = command_bounding_rectangle(command);
|
||||
|
||||
|
|
@ -161,13 +145,13 @@ void DisplayListPlayer::execute_impl(DisplayList& display_list, ScrollStateSnaps
|
|||
// This avoids expensive saveLayer/restore cycles for off-screen elements with effects like blur.
|
||||
// NOTE: We must not do this for consecutive commands with the same context, as that would incorrectly restore
|
||||
// and re-apply the effect layer, breaking blend mode compositing.
|
||||
if (context && applied_context != context && context->is_effect() && bounding_rect.has_value()) {
|
||||
switch_to_context(context->parent());
|
||||
if (context_index.value() && applied_context_index != context_index && visual_context_tree.is_effect(context_index) && bounding_rect.has_value()) {
|
||||
switch_to_context(visual_context_tree.node_at(context_index).parent_index);
|
||||
if (bounding_rect->is_empty() || would_be_fully_clipped_by_painter(*bounding_rect))
|
||||
continue;
|
||||
}
|
||||
|
||||
switch_to_context(context);
|
||||
switch_to_context(context_index);
|
||||
|
||||
if (command.has<PaintScrollBar>()) {
|
||||
auto translated_command = command;
|
||||
|
|
|
|||
|
|
@ -73,24 +73,30 @@ private:
|
|||
|
||||
class DisplayList : public AtomicRefCounted<DisplayList> {
|
||||
public:
|
||||
static NonnullRefPtr<DisplayList> create()
|
||||
static NonnullRefPtr<DisplayList> create(NonnullRefPtr<AccumulatedVisualContextTree const> visual_context_tree)
|
||||
{
|
||||
return adopt_ref(*new DisplayList());
|
||||
return adopt_ref(*new DisplayList(move(visual_context_tree)));
|
||||
}
|
||||
|
||||
bool append(DisplayListCommand&& command, RefPtr<AccumulatedVisualContext const> context);
|
||||
bool append(DisplayListCommand&& command, VisualContextIndex context_index);
|
||||
|
||||
struct CommandListItem {
|
||||
RefPtr<AccumulatedVisualContext const> context;
|
||||
VisualContextIndex context_index {};
|
||||
DisplayListCommand command;
|
||||
};
|
||||
|
||||
AccumulatedVisualContextTree const& visual_context_tree() const { return *m_visual_context_tree; }
|
||||
|
||||
auto& commands(Badge<DisplayListRecorder>) { return m_commands; }
|
||||
auto const& commands() const { return m_commands; }
|
||||
|
||||
private:
|
||||
DisplayList() = default;
|
||||
explicit DisplayList(NonnullRefPtr<AccumulatedVisualContextTree const> visual_context_tree)
|
||||
: m_visual_context_tree(move(visual_context_tree))
|
||||
{
|
||||
}
|
||||
|
||||
NonnullRefPtr<AccumulatedVisualContextTree const> const m_visual_context_tree;
|
||||
AK::SegmentedVector<CommandListItem, 512> m_commands;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -60,17 +60,17 @@ consteval static int command_nesting_level_change(T const& command)
|
|||
return 0;
|
||||
}
|
||||
|
||||
#define APPEND(...) \
|
||||
do { \
|
||||
auto command = __VA_ARGS__; \
|
||||
m_save_nesting_level += command_nesting_level_change(command); \
|
||||
if (m_is_capturing) { \
|
||||
auto command_copy = command; \
|
||||
if (m_display_list.append(move(command), m_accumulated_visual_context)) \
|
||||
m_captured_commands.append(move(command_copy)); \
|
||||
} else { \
|
||||
m_display_list.append(move(command), m_accumulated_visual_context); \
|
||||
} \
|
||||
#define APPEND(...) \
|
||||
do { \
|
||||
auto command = __VA_ARGS__; \
|
||||
m_save_nesting_level += command_nesting_level_change(command); \
|
||||
if (m_is_capturing) { \
|
||||
auto command_copy = command; \
|
||||
if (m_display_list.append(move(command), m_accumulated_visual_context_index)) \
|
||||
m_captured_commands.append(move(command_copy)); \
|
||||
} else { \
|
||||
m_display_list.append(move(command), m_accumulated_visual_context_index); \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
void DisplayListRecorder::replay_cached_commands(ReadonlySpan<DisplayListCommand> commands)
|
||||
|
|
@ -82,7 +82,7 @@ void DisplayListRecorder::replay_cached_commands(ReadonlySpan<DisplayListCommand
|
|||
return command.nesting_level_change;
|
||||
return 0;
|
||||
});
|
||||
m_display_list.append(move(command_copy), m_accumulated_visual_context);
|
||||
m_display_list.append(move(command_copy), m_accumulated_visual_context_index);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -87,8 +87,8 @@ public:
|
|||
|
||||
void translate(Gfx::IntPoint delta);
|
||||
|
||||
void set_accumulated_visual_context(RefPtr<AccumulatedVisualContext const> state) { m_accumulated_visual_context = move(state); }
|
||||
RefPtr<AccumulatedVisualContext const> accumulated_visual_context() const { return m_accumulated_visual_context; }
|
||||
void set_accumulated_visual_context(VisualContextIndex index) { m_accumulated_visual_context_index = index; }
|
||||
VisualContextIndex accumulated_visual_context() const { return m_accumulated_visual_context_index; }
|
||||
|
||||
void replay_cached_commands(ReadonlySpan<DisplayListCommand> commands);
|
||||
|
||||
|
|
@ -149,7 +149,7 @@ public:
|
|||
private:
|
||||
void end_capture();
|
||||
|
||||
RefPtr<AccumulatedVisualContext const> m_accumulated_visual_context;
|
||||
VisualContextIndex m_accumulated_visual_context_index {};
|
||||
Vector<size_t> m_push_sc_index_stack;
|
||||
DisplayList& m_display_list;
|
||||
bool m_is_capturing { false };
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
#include <LibWeb/Painting/PaintableBox.h>
|
||||
#include <LibWeb/Painting/PaintableWithLines.h>
|
||||
#include <LibWeb/Painting/StackingContext.h>
|
||||
#include <LibWeb/Painting/ViewportPaintable.h>
|
||||
|
||||
namespace Web::Painting {
|
||||
|
||||
|
|
@ -169,27 +170,30 @@ void Paintable::paint_inspector_overlay(DisplayListRecordingContext& context) co
|
|||
paintable_box = first_ancestor_of_type<PaintableBox>();
|
||||
|
||||
if (paintable_box) {
|
||||
Vector<RefPtr<AccumulatedVisualContext const>> relevant_contexts;
|
||||
for (auto visual_context = paintable_box->accumulated_visual_context(); visual_context != nullptr; visual_context = visual_context->parent()) {
|
||||
auto should_keep_entry = visual_context->data().visit(
|
||||
[](ScrollData const&) -> bool { return true; },
|
||||
[](ClipData const&) -> bool { return false; },
|
||||
[](TransformData const&) -> bool { return true; },
|
||||
[](PerspectiveData const&) -> bool { return true; },
|
||||
[](ClipPathData const&) -> bool { return false; },
|
||||
[](EffectsData const&) -> bool { return false; });
|
||||
auto* visual_context_tree = const_cast<ViewportPaintable*>(document().paintable())->visual_context_tree();
|
||||
auto visual_context_index = paintable_box->accumulated_visual_context_index();
|
||||
|
||||
if (should_keep_entry)
|
||||
relevant_contexts.append(visual_context);
|
||||
if (visual_context_tree && visual_context_index.value()) {
|
||||
Vector<VisualContextIndex> relevant_indices;
|
||||
for (auto i = visual_context_index; i.value(); i = visual_context_tree->node_at(i).parent_index) {
|
||||
auto should_keep = visual_context_tree->node_at(i).data.visit(
|
||||
[](ScrollData const&) { return true; },
|
||||
[](ClipData const&) { return false; },
|
||||
[](TransformData const&) { return true; },
|
||||
[](PerspectiveData const&) { return true; },
|
||||
[](ClipPathData const&) { return false; },
|
||||
[](EffectsData const&) { return false; });
|
||||
if (should_keep)
|
||||
relevant_indices.append(i);
|
||||
}
|
||||
|
||||
VisualContextIndex overlay_visual_context_index {};
|
||||
for (auto const& source_visual_context_index : relevant_indices.in_reverse())
|
||||
overlay_visual_context_index = visual_context_tree->append(visual_context_tree->node_at(source_visual_context_index).data, overlay_visual_context_index);
|
||||
|
||||
if (overlay_visual_context_index.value())
|
||||
display_list_recorder.set_accumulated_visual_context(overlay_visual_context_index);
|
||||
}
|
||||
|
||||
auto visual_context_id = 1;
|
||||
RefPtr<AccumulatedVisualContext> copied_visual_context;
|
||||
for (auto const& original_visual_context : relevant_contexts.in_reverse())
|
||||
copied_visual_context = AccumulatedVisualContext::create(visual_context_id++, original_visual_context->data(), copied_visual_context);
|
||||
|
||||
if (copied_visual_context)
|
||||
display_list_recorder.set_accumulated_visual_context(copied_visual_context);
|
||||
}
|
||||
|
||||
paint_inspector_overlay_internal(context);
|
||||
|
|
|
|||
|
|
@ -166,8 +166,8 @@ void PaintableBox::reset_for_relayout()
|
|||
|
||||
m_enclosing_scroll_frame = nullptr;
|
||||
m_own_scroll_frame = nullptr;
|
||||
m_accumulated_visual_context = nullptr;
|
||||
m_accumulated_visual_context_for_descendants = nullptr;
|
||||
m_accumulated_visual_context_index = {};
|
||||
m_accumulated_visual_context_for_descendants_index = {};
|
||||
|
||||
m_used_values_for_grid_template_columns = nullptr;
|
||||
m_used_values_for_grid_template_rows = nullptr;
|
||||
|
|
@ -890,11 +890,12 @@ Optional<int> PaintableBox::scroll_frame_id() const
|
|||
|
||||
Optional<CSSPixelPoint> PaintableBox::transform_point_to_local(CSSPixelPoint screen_position) const
|
||||
{
|
||||
if (!accumulated_visual_context())
|
||||
if (!m_accumulated_visual_context_index.value())
|
||||
return 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);
|
||||
auto const& visual_context_tree = *document().paintable()->visual_context_tree();
|
||||
auto result = visual_context_tree.transform_point_for_hit_test(m_accumulated_visual_context_index, screen_position.to_type<float>() * pixel_ratio, scroll_state);
|
||||
if (!result.has_value())
|
||||
return {};
|
||||
return (*result / pixel_ratio).to_type<CSSPixels>();
|
||||
|
|
@ -902,11 +903,12 @@ Optional<CSSPixelPoint> PaintableBox::transform_point_to_local(CSSPixelPoint scr
|
|||
|
||||
Optional<CSSPixelPoint> PaintableBox::transform_point_to_local_for_descendants(CSSPixelPoint screen_position) const
|
||||
{
|
||||
if (!accumulated_visual_context_for_descendants())
|
||||
if (!m_accumulated_visual_context_for_descendants_index.value())
|
||||
return 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_for_descendants()->transform_point_for_hit_test(screen_position.to_type<float>() * pixel_ratio, scroll_state);
|
||||
auto const& visual_context_tree = *document().paintable()->visual_context_tree();
|
||||
auto result = visual_context_tree.transform_point_for_hit_test(m_accumulated_visual_context_for_descendants_index, screen_position.to_type<float>() * pixel_ratio, scroll_state);
|
||||
if (!result.has_value())
|
||||
return {};
|
||||
return (*result / pixel_ratio).to_type<CSSPixels>();
|
||||
|
|
@ -914,20 +916,22 @@ Optional<CSSPixelPoint> PaintableBox::transform_point_to_local_for_descendants(C
|
|||
|
||||
CSSPixelRect PaintableBox::transform_rect_to_viewport(CSSPixelRect const& rect) const
|
||||
{
|
||||
if (!accumulated_visual_context())
|
||||
if (!m_accumulated_visual_context_index.value())
|
||||
return 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(rect.to_type<float>() * pixel_ratio, scroll_state);
|
||||
auto const& visual_context_tree = *document().paintable()->visual_context_tree();
|
||||
auto result = visual_context_tree.transform_rect_to_viewport(m_accumulated_visual_context_index, rect.to_type<float>() * pixel_ratio, scroll_state);
|
||||
return (result * (1.f / pixel_ratio)).to_type<CSSPixels>();
|
||||
}
|
||||
|
||||
CSSPixelPoint PaintableBox::inverse_transform_point(CSSPixelPoint screen_position) const
|
||||
{
|
||||
if (!accumulated_visual_context())
|
||||
if (!m_accumulated_visual_context_index.value())
|
||||
return screen_position;
|
||||
auto pixel_ratio = static_cast<float>(document().page().client().device_pixels_per_css_pixel());
|
||||
auto result = accumulated_visual_context()->inverse_transform_point(screen_position.to_type<float>() * pixel_ratio);
|
||||
auto const& visual_context_tree = *document().paintable()->visual_context_tree();
|
||||
auto result = visual_context_tree.inverse_transform_point(m_accumulated_visual_context_index, screen_position.to_type<float>() * pixel_ratio);
|
||||
return (result / pixel_ratio).to_type<CSSPixels>();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -230,10 +230,10 @@ public:
|
|||
void set_enclosing_scroll_frame(RefPtr<ScrollFrame const> const& scroll_frame) { m_enclosing_scroll_frame = scroll_frame; }
|
||||
void set_own_scroll_frame(RefPtr<ScrollFrame> const& scroll_frame) { m_own_scroll_frame = scroll_frame; }
|
||||
|
||||
void set_accumulated_visual_context(auto state) { m_accumulated_visual_context = move(state); }
|
||||
[[nodiscard]] auto accumulated_visual_context() const { return m_accumulated_visual_context; }
|
||||
void set_accumulated_visual_context_for_descendants(auto state) { m_accumulated_visual_context_for_descendants = move(state); }
|
||||
[[nodiscard]] auto accumulated_visual_context_for_descendants() const { return m_accumulated_visual_context_for_descendants; }
|
||||
void set_accumulated_visual_context(VisualContextIndex index) { m_accumulated_visual_context_index = index; }
|
||||
[[nodiscard]] VisualContextIndex accumulated_visual_context_index() const { return m_accumulated_visual_context_index; }
|
||||
void set_accumulated_visual_context_for_descendants(VisualContextIndex index) { m_accumulated_visual_context_for_descendants_index = index; }
|
||||
[[nodiscard]] VisualContextIndex accumulated_visual_context_for_descendants_index() const { return m_accumulated_visual_context_for_descendants_index; }
|
||||
|
||||
Optional<CSSPixelPoint> transform_point_to_local(CSSPixelPoint screen_position) const;
|
||||
Optional<CSSPixelPoint> transform_point_to_local_for_descendants(CSSPixelPoint screen_position) const;
|
||||
|
|
@ -329,8 +329,8 @@ private:
|
|||
|
||||
RefPtr<ScrollFrame const> m_enclosing_scroll_frame;
|
||||
RefPtr<ScrollFrame const> m_own_scroll_frame;
|
||||
RefPtr<AccumulatedVisualContext const> m_accumulated_visual_context;
|
||||
RefPtr<AccumulatedVisualContext const> m_accumulated_visual_context_for_descendants;
|
||||
VisualContextIndex m_accumulated_visual_context_index {};
|
||||
VisualContextIndex m_accumulated_visual_context_for_descendants_index {};
|
||||
|
||||
Optional<BordersDataWithElementKind> m_override_borders_data;
|
||||
Optional<TableCellCoordinates> m_table_cell_coordinates;
|
||||
|
|
|
|||
|
|
@ -77,7 +77,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();
|
||||
auto display_list = DisplayList::create(AccumulatedVisualContextTree::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);
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ SVGSVGPaintable::SVGSVGPaintable(Layout::SVGSVGBox const& layout_box)
|
|||
|
||||
void SVGSVGPaintable::paint_svg_box(DisplayListRecordingContext& context, PaintableBox const& svg_box, PaintPhase phase)
|
||||
{
|
||||
context.display_list_recorder().set_accumulated_visual_context(svg_box.accumulated_visual_context());
|
||||
context.display_list_recorder().set_accumulated_visual_context(svg_box.accumulated_visual_context_index());
|
||||
|
||||
// For elements with SVG filters, emit a transparent FillRect to trigger filter application.
|
||||
// This ensures content-generating filters (feFlood, feImage) work even with empty source.
|
||||
|
|
|
|||
|
|
@ -37,9 +37,9 @@ static void paint_node(Paintable const& paintable, DisplayListRecordingContext&
|
|||
// Text fragments in a PaintableWithLines are content of the block container.
|
||||
// They need the descendants' visual context, not the element's own visual context.
|
||||
if (is<PaintableWithLines>(paintable) && phase == PaintPhase::Foreground)
|
||||
context.display_list_recorder().set_accumulated_visual_context(paintable_box->accumulated_visual_context_for_descendants());
|
||||
context.display_list_recorder().set_accumulated_visual_context(paintable_box->accumulated_visual_context_for_descendants_index());
|
||||
else
|
||||
context.display_list_recorder().set_accumulated_visual_context(paintable_box->accumulated_visual_context());
|
||||
context.display_list_recorder().set_accumulated_visual_context(paintable_box->accumulated_visual_context_index());
|
||||
}
|
||||
|
||||
bool const skip_cache = !paintable_box || context.should_show_line_box_borders();
|
||||
|
|
@ -323,8 +323,8 @@ void StackingContext::paint(DisplayListRecordingContext& context) const
|
|||
mask_image->resolve_for_size(paintable_box().layout_node_with_style_and_box_metrics(), paintable_box().absolute_padding_box_rect().size());
|
||||
}
|
||||
|
||||
auto effective_state = paintable_box().accumulated_visual_context();
|
||||
context.display_list_recorder().set_accumulated_visual_context(effective_state);
|
||||
auto effective_context_index = paintable_box().accumulated_visual_context_index();
|
||||
context.display_list_recorder().set_accumulated_visual_context(effective_context_index);
|
||||
|
||||
// For elements with SVG filters, emit a transparent FillRect to trigger filter application.
|
||||
// This ensures content-generating filters (feFlood, feImage) work even with empty source.
|
||||
|
|
@ -337,7 +337,7 @@ void StackingContext::paint(DisplayListRecordingContext& context) const
|
|||
Vector<DisplayListRecorder::MaskInfo> masks;
|
||||
|
||||
if (mask_image) {
|
||||
auto mask_display_list = DisplayList::create();
|
||||
auto mask_display_list = DisplayList::create(AccumulatedVisualContextTree::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());
|
||||
|
|
|
|||
|
|
@ -45,7 +45,8 @@ void ViewportPaintable::reset_for_relayout()
|
|||
m_scroll_state_snapshot = {};
|
||||
m_needs_to_refresh_scroll_state = true;
|
||||
m_paintable_boxes_with_auto_content_visibility.clear();
|
||||
m_next_accumulated_visual_context_id = 1;
|
||||
m_visual_context_tree = nullptr;
|
||||
m_visual_viewport_context_index = {};
|
||||
}
|
||||
|
||||
void ViewportPaintable::build_stacking_context_tree_if_needed()
|
||||
|
|
@ -303,14 +304,14 @@ static Optional<ClipData> compute_clip_data(PaintableBox const& paintable_box, C
|
|||
|
||||
void ViewportPaintable::assign_accumulated_visual_contexts()
|
||||
{
|
||||
m_next_accumulated_visual_context_id = 1;
|
||||
m_visual_context_tree = AccumulatedVisualContextTree::create();
|
||||
|
||||
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 append_node = [&](VisualContextIndex parent_index, VisualContextData data) -> VisualContextIndex {
|
||||
return m_visual_context_tree->append(move(data), parent_index);
|
||||
};
|
||||
|
||||
auto make_effects_data = [&](PaintableBox const& box) -> Optional<EffectsData> {
|
||||
|
|
@ -327,17 +328,17 @@ void ViewportPaintable::assign_accumulated_visual_contexts()
|
|||
};
|
||||
|
||||
// Create visual viewport transform as root (if not identity)
|
||||
m_visual_viewport_context = nullptr;
|
||||
m_visual_viewport_context_index = {};
|
||||
auto transform = document().visual_viewport()->transform();
|
||||
if (!transform.is_identity()) {
|
||||
auto matrix = scale_matrix_for_device_pixels(transform.to_matrix(), scale);
|
||||
m_visual_viewport_context = append_node(nullptr, TransformData { matrix, { 0.f, 0.f } });
|
||||
m_visual_viewport_context_index = append_node({}, TransformData { matrix, { 0.f, 0.f } });
|
||||
}
|
||||
|
||||
RefPtr<AccumulatedVisualContext const> viewport_state_for_descendants = m_visual_viewport_context;
|
||||
VisualContextIndex viewport_state_for_descendants = m_visual_viewport_context_index;
|
||||
if (own_scroll_frame())
|
||||
viewport_state_for_descendants = append_node(m_visual_viewport_context, ScrollData { own_scroll_frame()->id(), false });
|
||||
set_accumulated_visual_context(nullptr);
|
||||
viewport_state_for_descendants = append_node(m_visual_viewport_context_index, ScrollData { own_scroll_frame()->id(), false });
|
||||
set_accumulated_visual_context({});
|
||||
set_accumulated_visual_context_for_descendants(viewport_state_for_descendants);
|
||||
|
||||
for_each_in_subtree_of_type<PaintableBox>([&](auto& paintable_box) {
|
||||
|
|
@ -352,14 +353,14 @@ void ViewportPaintable::assign_accumulated_visual_contexts()
|
|||
else
|
||||
paintable_box.set_filter({});
|
||||
|
||||
RefPtr<AccumulatedVisualContext const> inherited_state;
|
||||
VisualContextIndex inherited_state;
|
||||
|
||||
if (paintable_box.is_fixed_position()) {
|
||||
inherited_state = m_visual_viewport_context;
|
||||
inherited_state = m_visual_viewport_context_index;
|
||||
} else if (paintable_box.is_absolutely_positioned()) {
|
||||
// For position: absolute, use containing block's state to correctly escape scroll containers.
|
||||
auto* containing = paintable_box.containing_block();
|
||||
inherited_state = containing->accumulated_visual_context_for_descendants();
|
||||
inherited_state = containing->accumulated_visual_context_for_descendants_index();
|
||||
|
||||
// Abspos elements escape scroll containers and overflow clips of non-positioned
|
||||
// ancestors, but cannot escape stacking contexts created by intermediate effects
|
||||
|
|
@ -381,11 +382,11 @@ void ViewportPaintable::assign_accumulated_visual_contexts()
|
|||
// For position: relative/static, use visual parent's state directly.
|
||||
// This avoids duplicate transform/perspective allocations that would occur with
|
||||
// the containing block + intermediate walk approach.
|
||||
inherited_state = visual_parent->accumulated_visual_context_for_descendants();
|
||||
inherited_state = visual_parent->accumulated_visual_context_for_descendants_index();
|
||||
}
|
||||
|
||||
// Build this element's own state from inherited state.
|
||||
RefPtr<AccumulatedVisualContext const> own_state = inherited_state;
|
||||
VisualContextIndex own_state = inherited_state;
|
||||
|
||||
if (paintable_box.is_sticky_position()) {
|
||||
// For sticky elements, use enclosing_scroll_frame which holds the sticky frame.
|
||||
|
|
@ -430,7 +431,7 @@ void ViewportPaintable::assign_accumulated_visual_contexts()
|
|||
paintable_box.set_accumulated_visual_context(own_state);
|
||||
|
||||
// Build state for descendants: own state + perspective + clip + scroll.
|
||||
RefPtr<AccumulatedVisualContext const> state_for_descendants = own_state;
|
||||
VisualContextIndex state_for_descendants = own_state;
|
||||
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -44,7 +44,8 @@ public:
|
|||
void set_paintable_boxes_with_auto_content_visibility(Vector<GC::Ref<PaintableBox>> paintable_boxes) { m_paintable_boxes_with_auto_content_visibility = move(paintable_boxes); }
|
||||
ReadonlySpan<GC::Ref<PaintableBox>> paintable_boxes_with_auto_content_visibility() const { return m_paintable_boxes_with_auto_content_visibility; }
|
||||
|
||||
size_t allocate_accumulated_visual_context_id() { return m_next_accumulated_visual_context_id++; }
|
||||
AccumulatedVisualContextTree const* visual_context_tree() const { return m_visual_context_tree.ptr(); }
|
||||
AccumulatedVisualContextTree* visual_context_tree() { return m_visual_context_tree.ptr(); }
|
||||
|
||||
private:
|
||||
virtual bool is_viewport_paintable() const override { return true; }
|
||||
|
|
@ -61,9 +62,8 @@ private:
|
|||
|
||||
Vector<GC::Ref<PaintableBox>> m_paintable_boxes_with_auto_content_visibility;
|
||||
|
||||
size_t m_next_accumulated_visual_context_id { 1 };
|
||||
|
||||
RefPtr<AccumulatedVisualContext const> m_visual_viewport_context;
|
||||
RefPtr<AccumulatedVisualContextTree> m_visual_context_tree;
|
||||
VisualContextIndex m_visual_viewport_context_index {};
|
||||
};
|
||||
|
||||
template<>
|
||||
|
|
|
|||
|
|
@ -289,7 +289,7 @@ Optional<Painting::PaintStyle> SVGPatternElement::to_gfx_paint_style(SVGPaintCon
|
|||
auto svg_offset = recording_context.rounded_device_point(svg_element_rect.location()).to_type<int>().to_type<float>();
|
||||
tile_rect.translate_by(svg_offset);
|
||||
|
||||
auto display_list = Painting::DisplayList::create();
|
||||
auto display_list = Painting::DisplayList::create(Painting::AccumulatedVisualContextTree::create());
|
||||
Painting::DisplayListRecorder display_list_recorder(*display_list);
|
||||
auto content_origin = paint_context.paint_transform.map(Gfx::FloatPoint { 0, 0 }) + svg_offset;
|
||||
display_list_recorder.translate(-Gfx::IntPoint(content_origin.to_type<int>()));
|
||||
|
|
|
|||
Loading…
Reference in a new issue