Let style changes that only rebuild compatible accumulated visual contexts avoid marking the display list dirty. This lets transform and nonzero opacity updates send visual context tree updates without recording a new display list. Keep repainting changes that affect display-list contents or can change visual context tree compatibility, including zero-crossing opacity, transform invertibility crossings, background-attachment, clipping, mix-blend-mode, and perspective. Schedule accumulated visual context updates for animations independently of repaint so animated transform/effect updates keep reaching the document. Cover compatible visual context reuse, incompatible tree shapes, and the display-list invalidation cases with focused LibWeb tests.
84 lines
3.1 KiB
C++
84 lines
3.1 KiB
C++
/*
|
|
* Copyright (c) 2026-present, the Ladybird developers.
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <LibTest/TestCase.h>
|
|
#include <LibWeb/Painting/AccumulatedVisualContext.h>
|
|
|
|
using namespace Web::Painting;
|
|
|
|
static TransformData make_transform(float translation)
|
|
{
|
|
auto matrix = Gfx::FloatMatrix4x4::identity();
|
|
matrix[0, 3] = translation;
|
|
matrix[1, 3] = translation;
|
|
return { matrix, { translation, translation } };
|
|
}
|
|
|
|
TEST_CASE(compatible_trees_can_reuse_versions)
|
|
{
|
|
auto tree = AccumulatedVisualContextTree::create(make_transform(1));
|
|
tree.append(make_transform(2), VISUAL_VIEWPORT_NODE_INDEX);
|
|
|
|
auto updated_tree = AccumulatedVisualContextTree::create(make_transform(3));
|
|
updated_tree.append(make_transform(4), VISUAL_VIEWPORT_NODE_INDEX);
|
|
|
|
EXPECT_NE(tree.version(), updated_tree.version());
|
|
EXPECT(updated_tree.is_compatible_with(tree));
|
|
|
|
updated_tree.reuse_version_from(tree);
|
|
EXPECT_EQ(updated_tree.version(), tree.version());
|
|
}
|
|
|
|
TEST_CASE(compatibility_requires_same_shape)
|
|
{
|
|
auto tree = AccumulatedVisualContextTree::create();
|
|
tree.append(make_transform(1), VISUAL_VIEWPORT_NODE_INDEX);
|
|
|
|
auto shorter_tree = AccumulatedVisualContextTree::create();
|
|
EXPECT(!shorter_tree.is_compatible_with(tree));
|
|
|
|
auto different_type_tree = AccumulatedVisualContextTree::create();
|
|
different_type_tree.append(EffectsData {}, VISUAL_VIEWPORT_NODE_INDEX);
|
|
EXPECT(!different_type_tree.is_compatible_with(tree));
|
|
|
|
auto different_parent_tree = AccumulatedVisualContextTree::create();
|
|
auto parent = different_parent_tree.append(make_transform(1), VISUAL_VIEWPORT_NODE_INDEX);
|
|
different_parent_tree.append(make_transform(2), parent);
|
|
|
|
auto same_node_count_tree = AccumulatedVisualContextTree::create();
|
|
same_node_count_tree.append(make_transform(1), VISUAL_VIEWPORT_NODE_INDEX);
|
|
same_node_count_tree.append(make_transform(2), VISUAL_VIEWPORT_NODE_INDEX);
|
|
|
|
EXPECT(!different_parent_tree.is_compatible_with(same_node_count_tree));
|
|
}
|
|
|
|
TEST_CASE(compatibility_requires_same_empty_effective_clip)
|
|
{
|
|
auto empty_clip_tree = AccumulatedVisualContextTree::create();
|
|
empty_clip_tree.append(ClipData { Web::DevicePixelRect {}, {} }, VISUAL_VIEWPORT_NODE_INDEX);
|
|
|
|
auto non_empty_clip_tree = AccumulatedVisualContextTree::create();
|
|
non_empty_clip_tree.append(ClipData { Web::DevicePixelRect { 0, 0, 1, 1 }, {} }, VISUAL_VIEWPORT_NODE_INDEX);
|
|
|
|
EXPECT(!empty_clip_tree.is_compatible_with(non_empty_clip_tree));
|
|
}
|
|
|
|
TEST_CASE(compatibility_requires_same_visual_context_types)
|
|
{
|
|
auto clip_tree = AccumulatedVisualContextTree::create();
|
|
clip_tree.append(ClipData { Web::DevicePixelRect { 0, 0, 1, 1 }, {} }, VISUAL_VIEWPORT_NODE_INDEX);
|
|
|
|
Gfx::Path path;
|
|
path.move_to({ 0, 0 });
|
|
path.line_to({ 1, 0 });
|
|
path.line_to({ 1, 1 });
|
|
path.close();
|
|
|
|
auto clip_path_tree = AccumulatedVisualContextTree::create();
|
|
clip_path_tree.append(ClipPathData { path, Web::DevicePixelRect { 0, 0, 1, 1 }, Gfx::WindingRule::Nonzero }, VISUAL_VIEWPORT_NODE_INDEX);
|
|
|
|
EXPECT(!clip_tree.is_compatible_with(clip_path_tree));
|
|
}
|