mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 14:38:11 +00:00
LibWeb: Apply scroll boxes offsets after painting commands recording
With this change, instead of applying scroll offsets during the recording of the painting command list, we do the following: 1. Collect all boxes with scrollable overflow into a PaintContext, each with an id and the total amount of scrolling offset accumulated from ancestor scrollable boxes. 2. During the recording phase assign a corresponding scroll_frame_id to each command that paints content within a scrollable box. 3. Before executing the recorded commands, translate each command that has a scroll_frame_id by the accumulated scroll offset. This approach has following advantages: - Implementing nested scrollables becomes much simpler, as the recording phase only requires the correct assignment of the nearest scrollable's scroll_frame_id, while the accumulated offset from ancestors is applied subsequently. - The recording of painting commands is not tied to a specific offset within scrollable boxes, which means in the future, it will be possible to update the scrolling offset and repaint without the need to re-record painting commands.
This commit is contained in:
parent
d3025668a4
commit
ac6b3c989d
12 changed files with 307 additions and 24 deletions
|
@ -0,0 +1,28 @@
|
||||||
|
<style>
|
||||||
|
#container {
|
||||||
|
width: 300px;
|
||||||
|
height: 500px;
|
||||||
|
overflow: auto;
|
||||||
|
position: relative;
|
||||||
|
border: 2px solid black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
background-color: lightblue;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box {
|
||||||
|
background-color: magenta;
|
||||||
|
width: 100px;
|
||||||
|
height: 50px;
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
right: 50px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div id="container">
|
||||||
|
<div class="content">
|
||||||
|
<div class="box"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,46 @@
|
||||||
|
<link rel="match" href="reference/scrollable-box-with-nested-stacking-context-ref.html" />
|
||||||
|
<style>
|
||||||
|
#scrollable-box {
|
||||||
|
width: 300px;
|
||||||
|
height: 500px;
|
||||||
|
overflow: auto;
|
||||||
|
position: relative;
|
||||||
|
border: 2px solid black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
height: 1000px;
|
||||||
|
background-color: lightblue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.abspos {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0px;
|
||||||
|
right: 0px;
|
||||||
|
width: 150px;
|
||||||
|
height: 50px;
|
||||||
|
background-color: coral;
|
||||||
|
z-index: 1000;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box {
|
||||||
|
background-color: magenta;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
position: relative;
|
||||||
|
top: 0px;
|
||||||
|
left: 0px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div id="scrollable-box">
|
||||||
|
<div class="content">
|
||||||
|
Lots of scrollable content here!
|
||||||
|
<div class="box">box</div>
|
||||||
|
</div>
|
||||||
|
<div class="abspos"><div class="box">box</div></div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
const scrollContainer = document.getElementById("scrollable-box");
|
||||||
|
scrollContainer.scrollTop = 500;
|
||||||
|
</script>
|
|
@ -80,12 +80,22 @@ struct DrawGlyph {
|
||||||
FloatPoint position;
|
FloatPoint position;
|
||||||
u32 code_point;
|
u32 code_point;
|
||||||
NonnullRefPtr<Font const> font;
|
NonnullRefPtr<Font const> font;
|
||||||
|
|
||||||
|
void translate_by(FloatPoint const& delta)
|
||||||
|
{
|
||||||
|
position.translate_by(delta);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DrawEmoji {
|
struct DrawEmoji {
|
||||||
FloatPoint position;
|
FloatPoint position;
|
||||||
Gfx::Bitmap const* emoji;
|
Gfx::Bitmap const* emoji;
|
||||||
NonnullRefPtr<Font const> font;
|
NonnullRefPtr<Font const> font;
|
||||||
|
|
||||||
|
void translate_by(FloatPoint const& delta)
|
||||||
|
{
|
||||||
|
position.translate_by(delta);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
using DrawGlyphOrEmoji = Variant<DrawGlyph, DrawEmoji>;
|
using DrawGlyphOrEmoji = Variant<DrawGlyph, DrawEmoji>;
|
||||||
|
|
|
@ -72,11 +72,14 @@ public:
|
||||||
|
|
||||||
double device_pixels_per_css_pixel() const { return m_device_pixels_per_css_pixel; }
|
double device_pixels_per_css_pixel() const { return m_device_pixels_per_css_pixel; }
|
||||||
|
|
||||||
CSSPixelPoint scroll_offset() const { return m_scroll_offset; }
|
|
||||||
void translate_scroll_offset_by(CSSPixelPoint offset) { m_scroll_offset.translate_by(offset); }
|
|
||||||
|
|
||||||
u32 allocate_corner_clipper_id() { return m_next_corner_clipper_id++; }
|
u32 allocate_corner_clipper_id() { return m_next_corner_clipper_id++; }
|
||||||
|
|
||||||
|
struct ScrollFrame {
|
||||||
|
i32 id { -1 };
|
||||||
|
CSSPixelPoint offset;
|
||||||
|
};
|
||||||
|
HashMap<Painting::PaintableBox const*, ScrollFrame>& scroll_frames() { return m_scroll_frames; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Painting::RecordingPainter& m_recording_painter;
|
Painting::RecordingPainter& m_recording_painter;
|
||||||
Palette m_palette;
|
Palette m_palette;
|
||||||
|
@ -85,9 +88,9 @@ private:
|
||||||
bool m_should_show_line_box_borders { false };
|
bool m_should_show_line_box_borders { false };
|
||||||
bool m_should_paint_overlay { true };
|
bool m_should_paint_overlay { true };
|
||||||
bool m_focus { false };
|
bool m_focus { false };
|
||||||
CSSPixelPoint m_scroll_offset;
|
|
||||||
Gfx::AffineTransform m_svg_transform;
|
Gfx::AffineTransform m_svg_transform;
|
||||||
u32 m_next_corner_clipper_id { 0 };
|
u32 m_next_corner_clipper_id { 0 };
|
||||||
|
HashMap<Painting::PaintableBox const*, ScrollFrame> m_scroll_frames;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -410,16 +410,17 @@ Optional<CSSPixelRect> PaintableBox::calculate_overflow_clipped_rect() const
|
||||||
|
|
||||||
void PaintableBox::apply_scroll_offset(PaintContext& context, PaintPhase) const
|
void PaintableBox::apply_scroll_offset(PaintContext& context, PaintPhase) const
|
||||||
{
|
{
|
||||||
auto scroll_offset = -this->scroll_offset();
|
if (context.scroll_frames().contains(this)) {
|
||||||
context.translate_scroll_offset_by(scroll_offset);
|
context.recording_painter().save();
|
||||||
context.recording_painter().translate({ context.enclosing_device_pixels(scroll_offset.x()), context.enclosing_device_pixels(scroll_offset.y()) });
|
context.recording_painter().set_scroll_frame_id(context.scroll_frames().get(this)->id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PaintableBox::reset_scroll_offset(PaintContext& context, PaintPhase) const
|
void PaintableBox::reset_scroll_offset(PaintContext& context, PaintPhase) const
|
||||||
{
|
{
|
||||||
auto scroll_offset = this->scroll_offset();
|
if (context.scroll_frames().contains(this)) {
|
||||||
context.translate_scroll_offset_by(scroll_offset);
|
context.recording_painter().restore();
|
||||||
context.recording_painter().translate({ context.enclosing_device_pixels(scroll_offset.x()), context.enclosing_device_pixels(scroll_offset.y()) });
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PaintableBox::apply_clip_overflow_rect(PaintContext& context, PaintPhase phase) const
|
void PaintableBox::apply_clip_overflow_rect(PaintContext& context, PaintPhase phase) const
|
||||||
|
@ -445,10 +446,7 @@ void PaintableBox::apply_clip_overflow_rect(PaintContext& context, PaintPhase ph
|
||||||
|
|
||||||
if (!m_clipping_overflow) {
|
if (!m_clipping_overflow) {
|
||||||
context.recording_painter().save();
|
context.recording_painter().save();
|
||||||
auto scroll_offset = context.scroll_offset();
|
|
||||||
context.recording_painter().translate({ -context.enclosing_device_pixels(scroll_offset.x()), -context.enclosing_device_pixels(scroll_offset.y()) });
|
|
||||||
context.recording_painter().add_clip_rect(context.enclosing_device_rect(*clip_rect).to_type<int>());
|
context.recording_painter().add_clip_rect(context.enclosing_device_rect(*clip_rect).to_type<int>());
|
||||||
context.recording_painter().translate({ context.enclosing_device_pixels(scroll_offset.x()), context.enclosing_device_pixels(scroll_offset.y()) });
|
|
||||||
m_clipping_overflow = true;
|
m_clipping_overflow = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -826,10 +824,12 @@ Optional<HitTestResult> PaintableWithLines::hit_test(CSSPixelPoint position, Hit
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
PaintableBox const* PaintableBox::nearest_scrollable_ancestor() const
|
PaintableBox const* PaintableBox::nearest_scrollable_ancestor_within_stacking_context() const
|
||||||
{
|
{
|
||||||
auto* ancestor = parent();
|
auto* ancestor = parent();
|
||||||
while (ancestor) {
|
while (ancestor) {
|
||||||
|
if (ancestor->stacking_context_rooted_here())
|
||||||
|
return nullptr;
|
||||||
if (ancestor->is_paintable_box() && static_cast<PaintableBox const*>(ancestor)->has_scrollable_overflow())
|
if (ancestor->is_paintable_box() && static_cast<PaintableBox const*>(ancestor)->has_scrollable_overflow())
|
||||||
return static_cast<PaintableBox const*>(ancestor);
|
return static_cast<PaintableBox const*>(ancestor);
|
||||||
ancestor = ancestor->parent();
|
ancestor = ancestor->parent();
|
||||||
|
|
|
@ -194,7 +194,7 @@ public:
|
||||||
void set_box_shadow_data(Vector<ShadowData> box_shadow_data) { m_box_shadow_data = move(box_shadow_data); }
|
void set_box_shadow_data(Vector<ShadowData> box_shadow_data) { m_box_shadow_data = move(box_shadow_data); }
|
||||||
Vector<ShadowData> const& box_shadow_data() const { return m_box_shadow_data; }
|
Vector<ShadowData> const& box_shadow_data() const { return m_box_shadow_data; }
|
||||||
|
|
||||||
PaintableBox const* nearest_scrollable_ancestor() const;
|
PaintableBox const* nearest_scrollable_ancestor_within_stacking_context() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
explicit PaintableBox(Layout::Box const&);
|
explicit PaintableBox(Layout::Box const&);
|
||||||
|
|
|
@ -9,11 +9,31 @@
|
||||||
|
|
||||||
namespace Web::Painting {
|
namespace Web::Painting {
|
||||||
|
|
||||||
|
void DrawGlyphRun::translate_by(Gfx::IntPoint const& offset)
|
||||||
|
{
|
||||||
|
for (auto& glyph : glyph_run) {
|
||||||
|
glyph.visit([&](auto& glyph) {
|
||||||
|
glyph.translate_by(offset.to_type<float>());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
rect.translate_by(offset);
|
||||||
|
}
|
||||||
|
|
||||||
Gfx::IntRect PaintOuterBoxShadow::bounding_rect() const
|
Gfx::IntRect PaintOuterBoxShadow::bounding_rect() const
|
||||||
{
|
{
|
||||||
return get_outer_box_shadow_bounding_rect(outer_box_shadow_params);
|
return get_outer_box_shadow_bounding_rect(outer_box_shadow_params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PaintOuterBoxShadow::translate_by(Gfx::IntPoint const& offset)
|
||||||
|
{
|
||||||
|
outer_box_shadow_params.device_content_rect.translate_by(offset.to_type<DevicePixels>());
|
||||||
|
}
|
||||||
|
|
||||||
|
void PaintInnerBoxShadow::translate_by(Gfx::IntPoint const& offset)
|
||||||
|
{
|
||||||
|
outer_box_shadow_params.device_content_rect.translate_by(offset.to_type<DevicePixels>());
|
||||||
|
}
|
||||||
|
|
||||||
Gfx::IntRect SampleUnderCorners::bounding_rect() const
|
Gfx::IntRect SampleUnderCorners::bounding_rect() const
|
||||||
{
|
{
|
||||||
return border_rect;
|
return border_rect;
|
||||||
|
@ -303,8 +323,8 @@ void RecordingPainter::push_stacking_context(PushStackingContextParams params)
|
||||||
|
|
||||||
void RecordingPainter::pop_stacking_context()
|
void RecordingPainter::pop_stacking_context()
|
||||||
{
|
{
|
||||||
push_command(PopStackingContext {});
|
|
||||||
m_state_stack.take_last();
|
m_state_stack.take_last();
|
||||||
|
push_command(PopStackingContext {});
|
||||||
}
|
}
|
||||||
|
|
||||||
void RecordingPainter::paint_frame(Gfx::IntRect rect, Palette palette, Gfx::FrameStyle style)
|
void RecordingPainter::paint_frame(Gfx::IntRect rect, Palette palette, Gfx::FrameStyle style)
|
||||||
|
@ -405,11 +425,27 @@ static Optional<Gfx::IntRect> command_bounding_rectangle(PaintingCommand const&
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RecordingPainter::apply_scroll_offsets(Vector<Gfx::IntPoint> const& offsets_by_frame_id)
|
||||||
|
{
|
||||||
|
for (auto& command_with_scroll_id : m_painting_commands) {
|
||||||
|
if (command_with_scroll_id.scroll_frame_id.has_value()) {
|
||||||
|
auto const& scroll_frame_id = command_with_scroll_id.scroll_frame_id.value();
|
||||||
|
auto const& scroll_offset = offsets_by_frame_id[scroll_frame_id];
|
||||||
|
command_with_scroll_id.command.visit(
|
||||||
|
[&](auto& command) {
|
||||||
|
if constexpr (requires { command.translate_by(scroll_offset); })
|
||||||
|
command.translate_by(scroll_offset);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void RecordingPainter::execute(PaintingCommandExecutor& executor)
|
void RecordingPainter::execute(PaintingCommandExecutor& executor)
|
||||||
{
|
{
|
||||||
if (executor.needs_prepare_glyphs_texture()) {
|
if (executor.needs_prepare_glyphs_texture()) {
|
||||||
HashMap<Gfx::Font const*, HashTable<u32>> unique_glyphs;
|
HashMap<Gfx::Font const*, HashTable<u32>> unique_glyphs;
|
||||||
for (auto& command : m_painting_commands) {
|
for (auto& command_with_scroll_id : m_painting_commands) {
|
||||||
|
auto& command = command_with_scroll_id.command;
|
||||||
if (command.has<DrawGlyphRun>()) {
|
if (command.has<DrawGlyphRun>()) {
|
||||||
for (auto const& glyph_or_emoji : command.get<DrawGlyphRun>().glyph_run) {
|
for (auto const& glyph_or_emoji : command.get<DrawGlyphRun>().glyph_run) {
|
||||||
if (glyph_or_emoji.has<Gfx::DrawGlyph>()) {
|
if (glyph_or_emoji.has<Gfx::DrawGlyph>()) {
|
||||||
|
@ -424,7 +460,8 @@ void RecordingPainter::execute(PaintingCommandExecutor& executor)
|
||||||
|
|
||||||
if (executor.needs_update_immutable_bitmap_texture_cache()) {
|
if (executor.needs_update_immutable_bitmap_texture_cache()) {
|
||||||
HashMap<u32, Gfx::ImmutableBitmap const*> immutable_bitmaps;
|
HashMap<u32, Gfx::ImmutableBitmap const*> immutable_bitmaps;
|
||||||
for (auto const& command : m_painting_commands) {
|
for (auto const& command_with_scroll_id : m_painting_commands) {
|
||||||
|
auto& command = command_with_scroll_id.command;
|
||||||
if (command.has<DrawScaledImmutableBitmap>()) {
|
if (command.has<DrawScaledImmutableBitmap>()) {
|
||||||
auto const& immutable_bitmap = command.get<DrawScaledImmutableBitmap>().bitmap;
|
auto const& immutable_bitmap = command.get<DrawScaledImmutableBitmap>().bitmap;
|
||||||
immutable_bitmaps.set(immutable_bitmap->id(), immutable_bitmap.ptr());
|
immutable_bitmaps.set(immutable_bitmap->id(), immutable_bitmap.ptr());
|
||||||
|
@ -436,7 +473,8 @@ void RecordingPainter::execute(PaintingCommandExecutor& executor)
|
||||||
HashTable<u32> skipped_sample_corner_commands;
|
HashTable<u32> skipped_sample_corner_commands;
|
||||||
size_t next_command_index = 0;
|
size_t next_command_index = 0;
|
||||||
while (next_command_index < m_painting_commands.size()) {
|
while (next_command_index < m_painting_commands.size()) {
|
||||||
auto& command = m_painting_commands[next_command_index++];
|
auto& command_with_scroll_id = m_painting_commands[next_command_index++];
|
||||||
|
auto& command = command_with_scroll_id.command;
|
||||||
auto bounding_rect = command_bounding_rectangle(command);
|
auto bounding_rect = command_bounding_rectangle(command);
|
||||||
if (bounding_rect.has_value() && (bounding_rect->is_empty() || executor.would_be_fully_clipped_by_painter(*bounding_rect))) {
|
if (bounding_rect.has_value() && (bounding_rect->is_empty() || executor.would_be_fully_clipped_by_painter(*bounding_rect))) {
|
||||||
if (command.has<SampleUnderCorners>()) {
|
if (command.has<SampleUnderCorners>()) {
|
||||||
|
@ -555,9 +593,9 @@ void RecordingPainter::execute(PaintingCommandExecutor& executor)
|
||||||
if (result == CommandResult::SkipStackingContext) {
|
if (result == CommandResult::SkipStackingContext) {
|
||||||
auto stacking_context_nesting_level = 1;
|
auto stacking_context_nesting_level = 1;
|
||||||
while (next_command_index < m_painting_commands.size()) {
|
while (next_command_index < m_painting_commands.size()) {
|
||||||
if (m_painting_commands[next_command_index].has<PushStackingContext>()) {
|
if (m_painting_commands[next_command_index].command.has<PushStackingContext>()) {
|
||||||
stacking_context_nesting_level++;
|
stacking_context_nesting_level++;
|
||||||
} else if (m_painting_commands[next_command_index].has<PopStackingContext>()) {
|
} else if (m_painting_commands[next_command_index].command.has<PopStackingContext>()) {
|
||||||
stacking_context_nesting_level--;
|
stacking_context_nesting_level--;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,8 @@ struct DrawGlyphRun {
|
||||||
Gfx::IntRect rect;
|
Gfx::IntRect rect;
|
||||||
|
|
||||||
[[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; }
|
[[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; }
|
||||||
|
|
||||||
|
void translate_by(Gfx::IntPoint const& offset);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DrawText {
|
struct DrawText {
|
||||||
|
@ -59,6 +61,7 @@ struct DrawText {
|
||||||
Optional<NonnullRefPtr<Gfx::Font>> font {};
|
Optional<NonnullRefPtr<Gfx::Font>> font {};
|
||||||
|
|
||||||
[[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; }
|
[[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; }
|
||||||
|
void translate_by(Gfx::IntPoint const& offset) { rect.translate_by(offset); }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FillRect {
|
struct FillRect {
|
||||||
|
@ -66,6 +69,7 @@ struct FillRect {
|
||||||
Color color;
|
Color color;
|
||||||
|
|
||||||
[[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; }
|
[[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; }
|
||||||
|
void translate_by(Gfx::IntPoint const& offset) { rect.translate_by(offset); }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DrawScaledBitmap {
|
struct DrawScaledBitmap {
|
||||||
|
@ -75,6 +79,7 @@ struct DrawScaledBitmap {
|
||||||
Gfx::Painter::ScalingMode scaling_mode;
|
Gfx::Painter::ScalingMode scaling_mode;
|
||||||
|
|
||||||
[[nodiscard]] Gfx::IntRect bounding_rect() const { return dst_rect; }
|
[[nodiscard]] Gfx::IntRect bounding_rect() const { return dst_rect; }
|
||||||
|
void translate_by(Gfx::IntPoint const& offset) { dst_rect.translate_by(offset); }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DrawScaledImmutableBitmap {
|
struct DrawScaledImmutableBitmap {
|
||||||
|
@ -84,6 +89,7 @@ struct DrawScaledImmutableBitmap {
|
||||||
Gfx::Painter::ScalingMode scaling_mode;
|
Gfx::Painter::ScalingMode scaling_mode;
|
||||||
|
|
||||||
[[nodiscard]] Gfx::IntRect bounding_rect() const { return dst_rect; }
|
[[nodiscard]] Gfx::IntRect bounding_rect() const { return dst_rect; }
|
||||||
|
void translate_by(Gfx::IntPoint const& offset) { dst_rect.translate_by(offset); }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SetClipRect {
|
struct SetClipRect {
|
||||||
|
@ -116,6 +122,11 @@ struct PushStackingContext {
|
||||||
CSS::ImageRendering image_rendering;
|
CSS::ImageRendering image_rendering;
|
||||||
StackingContextTransform transform;
|
StackingContextTransform transform;
|
||||||
Optional<StackingContextMask> mask = {};
|
Optional<StackingContextMask> mask = {};
|
||||||
|
|
||||||
|
void translate_by(Gfx::IntPoint const& offset)
|
||||||
|
{
|
||||||
|
post_transform_translation.translate_by(offset);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PopStackingContext { };
|
struct PopStackingContext { };
|
||||||
|
@ -125,16 +136,24 @@ struct PaintLinearGradient {
|
||||||
LinearGradientData linear_gradient_data;
|
LinearGradientData linear_gradient_data;
|
||||||
|
|
||||||
[[nodiscard]] Gfx::IntRect bounding_rect() const { return gradient_rect; }
|
[[nodiscard]] Gfx::IntRect bounding_rect() const { return gradient_rect; }
|
||||||
|
|
||||||
|
void translate_by(Gfx::IntPoint const& offset)
|
||||||
|
{
|
||||||
|
gradient_rect.translate_by(offset);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PaintOuterBoxShadow {
|
struct PaintOuterBoxShadow {
|
||||||
PaintOuterBoxShadowParams outer_box_shadow_params;
|
PaintOuterBoxShadowParams outer_box_shadow_params;
|
||||||
|
|
||||||
[[nodiscard]] Gfx::IntRect bounding_rect() const;
|
[[nodiscard]] Gfx::IntRect bounding_rect() const;
|
||||||
|
void translate_by(Gfx::IntPoint const& offset);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PaintInnerBoxShadow {
|
struct PaintInnerBoxShadow {
|
||||||
PaintOuterBoxShadowParams outer_box_shadow_params;
|
PaintOuterBoxShadowParams outer_box_shadow_params;
|
||||||
|
|
||||||
|
void translate_by(Gfx::IntPoint const& offset);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PaintTextShadow {
|
struct PaintTextShadow {
|
||||||
|
@ -147,6 +166,7 @@ struct PaintTextShadow {
|
||||||
Gfx::IntPoint draw_location;
|
Gfx::IntPoint draw_location;
|
||||||
|
|
||||||
[[nodiscard]] Gfx::IntRect bounding_rect() const { return { draw_location, shadow_bounding_rect.size() }; }
|
[[nodiscard]] Gfx::IntRect bounding_rect() const { return { draw_location, shadow_bounding_rect.size() }; }
|
||||||
|
void translate_by(Gfx::IntPoint const& offset) { draw_location.translate_by(offset); }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FillRectWithRoundedCorners {
|
struct FillRectWithRoundedCorners {
|
||||||
|
@ -158,6 +178,7 @@ struct FillRectWithRoundedCorners {
|
||||||
Gfx::AntiAliasingPainter::CornerRadius bottom_right_radius;
|
Gfx::AntiAliasingPainter::CornerRadius bottom_right_radius;
|
||||||
|
|
||||||
[[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; }
|
[[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; }
|
||||||
|
void translate_by(Gfx::IntPoint const& offset) { rect.translate_by(offset); }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FillPathUsingColor {
|
struct FillPathUsingColor {
|
||||||
|
@ -168,6 +189,12 @@ struct FillPathUsingColor {
|
||||||
Gfx::FloatPoint aa_translation;
|
Gfx::FloatPoint aa_translation;
|
||||||
|
|
||||||
[[nodiscard]] Gfx::IntRect bounding_rect() const { return path_bounding_rect; }
|
[[nodiscard]] Gfx::IntRect bounding_rect() const { return path_bounding_rect; }
|
||||||
|
|
||||||
|
void translate_by(Gfx::IntPoint const& offset)
|
||||||
|
{
|
||||||
|
path_bounding_rect.translate_by(offset);
|
||||||
|
aa_translation.translate_by(offset.to_type<float>());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FillPathUsingPaintStyle {
|
struct FillPathUsingPaintStyle {
|
||||||
|
@ -179,6 +206,12 @@ struct FillPathUsingPaintStyle {
|
||||||
Gfx::FloatPoint aa_translation;
|
Gfx::FloatPoint aa_translation;
|
||||||
|
|
||||||
[[nodiscard]] Gfx::IntRect bounding_rect() const { return path_bounding_rect; }
|
[[nodiscard]] Gfx::IntRect bounding_rect() const { return path_bounding_rect; }
|
||||||
|
|
||||||
|
void translate_by(Gfx::IntPoint const& offset)
|
||||||
|
{
|
||||||
|
path_bounding_rect.translate_by(offset);
|
||||||
|
aa_translation.translate_by(offset.to_type<float>());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct StrokePathUsingColor {
|
struct StrokePathUsingColor {
|
||||||
|
@ -189,6 +222,12 @@ struct StrokePathUsingColor {
|
||||||
Gfx::FloatPoint aa_translation;
|
Gfx::FloatPoint aa_translation;
|
||||||
|
|
||||||
[[nodiscard]] Gfx::IntRect bounding_rect() const { return path_bounding_rect; }
|
[[nodiscard]] Gfx::IntRect bounding_rect() const { return path_bounding_rect; }
|
||||||
|
|
||||||
|
void translate_by(Gfx::IntPoint const& offset)
|
||||||
|
{
|
||||||
|
path_bounding_rect.translate_by(offset);
|
||||||
|
aa_translation.translate_by(offset.to_type<float>());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct StrokePathUsingPaintStyle {
|
struct StrokePathUsingPaintStyle {
|
||||||
|
@ -200,6 +239,12 @@ struct StrokePathUsingPaintStyle {
|
||||||
Gfx::FloatPoint aa_translation;
|
Gfx::FloatPoint aa_translation;
|
||||||
|
|
||||||
[[nodiscard]] Gfx::IntRect bounding_rect() const { return path_bounding_rect; }
|
[[nodiscard]] Gfx::IntRect bounding_rect() const { return path_bounding_rect; }
|
||||||
|
|
||||||
|
void translate_by(Gfx::IntPoint const& offset)
|
||||||
|
{
|
||||||
|
path_bounding_rect.translate_by(offset);
|
||||||
|
aa_translation.translate_by(offset.to_type<float>());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DrawEllipse {
|
struct DrawEllipse {
|
||||||
|
@ -208,6 +253,11 @@ struct DrawEllipse {
|
||||||
int thickness;
|
int thickness;
|
||||||
|
|
||||||
[[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; }
|
[[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; }
|
||||||
|
|
||||||
|
void translate_by(Gfx::IntPoint const& offset)
|
||||||
|
{
|
||||||
|
rect.translate_by(offset);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FillEllipse {
|
struct FillEllipse {
|
||||||
|
@ -216,6 +266,11 @@ struct FillEllipse {
|
||||||
Gfx::AntiAliasingPainter::BlendMode blend_mode;
|
Gfx::AntiAliasingPainter::BlendMode blend_mode;
|
||||||
|
|
||||||
[[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; }
|
[[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; }
|
||||||
|
|
||||||
|
void translate_by(Gfx::IntPoint const& offset)
|
||||||
|
{
|
||||||
|
rect.translate_by(offset);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DrawLine {
|
struct DrawLine {
|
||||||
|
@ -225,6 +280,12 @@ struct DrawLine {
|
||||||
int thickness;
|
int thickness;
|
||||||
Gfx::Painter::LineStyle style;
|
Gfx::Painter::LineStyle style;
|
||||||
Color alternate_color;
|
Color alternate_color;
|
||||||
|
|
||||||
|
void translate_by(Gfx::IntPoint const& offset)
|
||||||
|
{
|
||||||
|
from.translate_by(offset);
|
||||||
|
to.translate_by(offset);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DrawSignedDistanceField {
|
struct DrawSignedDistanceField {
|
||||||
|
@ -234,6 +295,11 @@ struct DrawSignedDistanceField {
|
||||||
float smoothing;
|
float smoothing;
|
||||||
|
|
||||||
[[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; }
|
[[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; }
|
||||||
|
|
||||||
|
void translate_by(Gfx::IntPoint const& offset)
|
||||||
|
{
|
||||||
|
rect.translate_by(offset);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PaintFrame {
|
struct PaintFrame {
|
||||||
|
@ -242,6 +308,8 @@ struct PaintFrame {
|
||||||
Gfx::FrameStyle style;
|
Gfx::FrameStyle style;
|
||||||
|
|
||||||
[[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; }
|
[[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; }
|
||||||
|
|
||||||
|
void translate_by(Gfx::IntPoint const& offset) { rect.translate_by(offset); }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ApplyBackdropFilter {
|
struct ApplyBackdropFilter {
|
||||||
|
@ -250,6 +318,11 @@ struct ApplyBackdropFilter {
|
||||||
CSS::ResolvedBackdropFilter backdrop_filter;
|
CSS::ResolvedBackdropFilter backdrop_filter;
|
||||||
|
|
||||||
[[nodiscard]] Gfx::IntRect bounding_rect() const { return backdrop_region; }
|
[[nodiscard]] Gfx::IntRect bounding_rect() const { return backdrop_region; }
|
||||||
|
|
||||||
|
void translate_by(Gfx::IntPoint const& offset)
|
||||||
|
{
|
||||||
|
backdrop_region.translate_by(offset);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DrawRect {
|
struct DrawRect {
|
||||||
|
@ -258,6 +331,8 @@ struct DrawRect {
|
||||||
bool rough;
|
bool rough;
|
||||||
|
|
||||||
[[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; }
|
[[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; }
|
||||||
|
|
||||||
|
void translate_by(Gfx::IntPoint const& offset) { rect.translate_by(offset); }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PaintRadialGradient {
|
struct PaintRadialGradient {
|
||||||
|
@ -267,6 +342,8 @@ struct PaintRadialGradient {
|
||||||
Gfx::IntSize size;
|
Gfx::IntSize size;
|
||||||
|
|
||||||
[[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; }
|
[[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; }
|
||||||
|
|
||||||
|
void translate_by(Gfx::IntPoint const& offset) { rect.translate_by(offset); }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PaintConicGradient {
|
struct PaintConicGradient {
|
||||||
|
@ -275,6 +352,8 @@ struct PaintConicGradient {
|
||||||
Gfx::IntPoint position;
|
Gfx::IntPoint position;
|
||||||
|
|
||||||
[[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; }
|
[[nodiscard]] Gfx::IntRect bounding_rect() const { return rect; }
|
||||||
|
|
||||||
|
void translate_by(Gfx::IntPoint const& offset) { rect.translate_by(offset); }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DrawTriangleWave {
|
struct DrawTriangleWave {
|
||||||
|
@ -283,6 +362,12 @@ struct DrawTriangleWave {
|
||||||
Color color;
|
Color color;
|
||||||
int amplitude;
|
int amplitude;
|
||||||
int thickness;
|
int thickness;
|
||||||
|
|
||||||
|
void translate_by(Gfx::IntPoint const& offset)
|
||||||
|
{
|
||||||
|
p1.translate_by(offset);
|
||||||
|
p2.translate_by(offset);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SampleUnderCorners {
|
struct SampleUnderCorners {
|
||||||
|
@ -292,6 +377,11 @@ struct SampleUnderCorners {
|
||||||
CornerClip corner_clip;
|
CornerClip corner_clip;
|
||||||
|
|
||||||
[[nodiscard]] Gfx::IntRect bounding_rect() const;
|
[[nodiscard]] Gfx::IntRect bounding_rect() const;
|
||||||
|
|
||||||
|
void translate_by(Gfx::IntPoint const& offset)
|
||||||
|
{
|
||||||
|
border_rect.translate_by(offset);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct BlitCornerClipping {
|
struct BlitCornerClipping {
|
||||||
|
@ -299,6 +389,11 @@ struct BlitCornerClipping {
|
||||||
Gfx::IntRect border_rect;
|
Gfx::IntRect border_rect;
|
||||||
|
|
||||||
[[nodiscard]] Gfx::IntRect bounding_rect() const;
|
[[nodiscard]] Gfx::IntRect bounding_rect() const;
|
||||||
|
|
||||||
|
void translate_by(Gfx::IntPoint const& offset)
|
||||||
|
{
|
||||||
|
border_rect.translate_by(offset);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PaintBorders {
|
struct PaintBorders {
|
||||||
|
@ -307,6 +402,11 @@ struct PaintBorders {
|
||||||
BordersDataDevicePixels borders_data;
|
BordersDataDevicePixels borders_data;
|
||||||
|
|
||||||
[[nodiscard]] Gfx::IntRect bounding_rect() const { return border_rect.to_type<int>(); }
|
[[nodiscard]] Gfx::IntRect bounding_rect() const { return border_rect.to_type<int>(); }
|
||||||
|
|
||||||
|
void translate_by(Gfx::IntPoint const& offset)
|
||||||
|
{
|
||||||
|
border_rect.translate_by(offset.to_type<DevicePixels>());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
using PaintingCommand = Variant<
|
using PaintingCommand = Variant<
|
||||||
|
@ -457,6 +557,11 @@ public:
|
||||||
|
|
||||||
void set_font(Gfx::Font const& font);
|
void set_font(Gfx::Font const& font);
|
||||||
|
|
||||||
|
void set_scroll_frame_id(i32 id)
|
||||||
|
{
|
||||||
|
state().scroll_frame_id = id;
|
||||||
|
}
|
||||||
|
|
||||||
void save();
|
void save();
|
||||||
void restore();
|
void restore();
|
||||||
|
|
||||||
|
@ -497,20 +602,28 @@ public:
|
||||||
m_state_stack.append(State());
|
m_state_stack.append(State());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void apply_scroll_offsets(Vector<Gfx::IntPoint> const& offsets_by_frame_id);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct State {
|
struct State {
|
||||||
Gfx::AffineTransform translation;
|
Gfx::AffineTransform translation;
|
||||||
Optional<Gfx::IntRect> clip_rect;
|
Optional<Gfx::IntRect> clip_rect;
|
||||||
|
Optional<i32> scroll_frame_id;
|
||||||
};
|
};
|
||||||
State& state() { return m_state_stack.last(); }
|
State& state() { return m_state_stack.last(); }
|
||||||
State const& state() const { return m_state_stack.last(); }
|
State const& state() const { return m_state_stack.last(); }
|
||||||
|
|
||||||
void push_command(PaintingCommand command)
|
void push_command(PaintingCommand command)
|
||||||
{
|
{
|
||||||
m_painting_commands.append(command);
|
m_painting_commands.append({ state().scroll_frame_id, command });
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector<PaintingCommand> m_painting_commands;
|
struct PaintingCommandWithScrollFrame {
|
||||||
|
Optional<i32> scroll_frame_id;
|
||||||
|
PaintingCommand command;
|
||||||
|
};
|
||||||
|
|
||||||
|
Vector<PaintingCommandWithScrollFrame> m_painting_commands;
|
||||||
Vector<State> m_state_stack;
|
Vector<State> m_state_stack;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -184,8 +184,16 @@ void StackingContext::paint_child(PaintContext& context, StackingContext const&
|
||||||
if (parent_paintable)
|
if (parent_paintable)
|
||||||
parent_paintable->before_children_paint(context, PaintPhase::Foreground);
|
parent_paintable->before_children_paint(context, PaintPhase::Foreground);
|
||||||
|
|
||||||
|
PaintableBox const* nearest_scrollable_ancestor = child.paintable_box().nearest_scrollable_ancestor_within_stacking_context();
|
||||||
|
|
||||||
|
if (nearest_scrollable_ancestor)
|
||||||
|
nearest_scrollable_ancestor->apply_scroll_offset(context, PaintPhase::Foreground);
|
||||||
|
|
||||||
child.paint(context);
|
child.paint(context);
|
||||||
|
|
||||||
|
if (nearest_scrollable_ancestor)
|
||||||
|
nearest_scrollable_ancestor->reset_scroll_offset(context, PaintPhase::Foreground);
|
||||||
|
|
||||||
if (parent_paintable)
|
if (parent_paintable)
|
||||||
parent_paintable->after_children_paint(context, PaintPhase::Foreground);
|
parent_paintable->after_children_paint(context, PaintPhase::Foreground);
|
||||||
}
|
}
|
||||||
|
@ -229,7 +237,7 @@ void StackingContext::paint_internal(PaintContext& context) const
|
||||||
// Apply scroll offset of nearest scrollable ancestor before painting the positioned descendant.
|
// Apply scroll offset of nearest scrollable ancestor before painting the positioned descendant.
|
||||||
PaintableBox const* nearest_scrollable_ancestor = nullptr;
|
PaintableBox const* nearest_scrollable_ancestor = nullptr;
|
||||||
if (paintable.is_paintable_box())
|
if (paintable.is_paintable_box())
|
||||||
nearest_scrollable_ancestor = static_cast<PaintableBox const&>(paintable).nearest_scrollable_ancestor();
|
nearest_scrollable_ancestor = static_cast<PaintableBox const&>(paintable).nearest_scrollable_ancestor_within_stacking_context();
|
||||||
if (nearest_scrollable_ancestor)
|
if (nearest_scrollable_ancestor)
|
||||||
nearest_scrollable_ancestor->apply_scroll_offset(context, PaintPhase::Foreground);
|
nearest_scrollable_ancestor->apply_scroll_offset(context, PaintPhase::Foreground);
|
||||||
|
|
||||||
|
@ -265,6 +273,12 @@ void StackingContext::paint_internal(PaintContext& context) const
|
||||||
for (auto* child : m_children) {
|
for (auto* child : m_children) {
|
||||||
if (!child->paintable_box().is_positioned())
|
if (!child->paintable_box().is_positioned())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
PaintableBox const* nearest_scrollable_ancestor = child->paintable_box().nearest_scrollable_ancestor_within_stacking_context();
|
||||||
|
|
||||||
|
if (nearest_scrollable_ancestor)
|
||||||
|
nearest_scrollable_ancestor->apply_scroll_offset(context, PaintPhase::Foreground);
|
||||||
|
|
||||||
auto containing_block = child->paintable_box().containing_block();
|
auto containing_block = child->paintable_box().containing_block();
|
||||||
auto const* containing_block_paintable = containing_block ? containing_block->paintable() : nullptr;
|
auto const* containing_block_paintable = containing_block ? containing_block->paintable() : nullptr;
|
||||||
if (containing_block_paintable)
|
if (containing_block_paintable)
|
||||||
|
@ -273,6 +287,9 @@ void StackingContext::paint_internal(PaintContext& context) const
|
||||||
paint_child(context, *child);
|
paint_child(context, *child);
|
||||||
if (containing_block_paintable)
|
if (containing_block_paintable)
|
||||||
containing_block_paintable->clear_clip_overflow_rect(context, PaintPhase::Foreground);
|
containing_block_paintable->clear_clip_overflow_rect(context, PaintPhase::Foreground);
|
||||||
|
|
||||||
|
if (nearest_scrollable_ancestor)
|
||||||
|
nearest_scrollable_ancestor->reset_scroll_offset(context, PaintPhase::Foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
paint_node(paintable_box(), context, PaintPhase::Outline);
|
paint_node(paintable_box(), context, PaintPhase::Outline);
|
||||||
|
|
|
@ -57,4 +57,22 @@ void ViewportPaintable::paint_all_phases(PaintContext& context)
|
||||||
stacking_context()->paint(context);
|
stacking_context()->paint(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ViewportPaintable::collect_scroll_frames(PaintContext& context) const
|
||||||
|
{
|
||||||
|
i32 next_id = 0;
|
||||||
|
for_each_in_subtree_of_type<PaintableBox>([&](auto const& paintable_box) {
|
||||||
|
if (paintable_box.has_scrollable_overflow()) {
|
||||||
|
auto offset = paintable_box.scroll_offset();
|
||||||
|
auto ancestor = paintable_box.parent();
|
||||||
|
while (ancestor) {
|
||||||
|
if (ancestor->is_paintable_box() && static_cast<PaintableBox const*>(ancestor)->has_scrollable_overflow())
|
||||||
|
offset.translate_by(static_cast<PaintableBox const*>(ancestor)->scroll_offset());
|
||||||
|
ancestor = ancestor->parent();
|
||||||
|
}
|
||||||
|
context.scroll_frames().set(&paintable_box, { .id = next_id++, .offset = -offset });
|
||||||
|
}
|
||||||
|
return TraversalDecision::Continue;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,8 @@ public:
|
||||||
void paint_all_phases(PaintContext&);
|
void paint_all_phases(PaintContext&);
|
||||||
void build_stacking_context_tree_if_needed();
|
void build_stacking_context_tree_if_needed();
|
||||||
|
|
||||||
|
void collect_scroll_frames(PaintContext&) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void build_stacking_context_tree();
|
void build_stacking_context_tree();
|
||||||
|
|
||||||
|
|
|
@ -188,8 +188,16 @@ void PageClient::paint(Web::DevicePixelRect const& content_rect, Gfx::Bitmap& ta
|
||||||
context.set_should_paint_overlay(paint_options.paint_overlay == Web::PaintOptions::PaintOverlay::Yes);
|
context.set_should_paint_overlay(paint_options.paint_overlay == Web::PaintOptions::PaintOverlay::Yes);
|
||||||
context.set_device_viewport_rect(content_rect);
|
context.set_device_viewport_rect(content_rect);
|
||||||
context.set_has_focus(m_has_focus);
|
context.set_has_focus(m_has_focus);
|
||||||
|
|
||||||
|
document->paintable()->collect_scroll_frames(context);
|
||||||
document->paintable()->paint_all_phases(context);
|
document->paintable()->paint_all_phases(context);
|
||||||
|
|
||||||
|
Vector<Gfx::IntPoint> scroll_offsets_by_frame_id;
|
||||||
|
scroll_offsets_by_frame_id.resize(context.scroll_frames().size());
|
||||||
|
for (auto [_, scrollable_frame] : context.scroll_frames())
|
||||||
|
scroll_offsets_by_frame_id[scrollable_frame.id] = context.rounded_device_point(scrollable_frame.offset).to_type<int>();
|
||||||
|
recording_painter.apply_scroll_offsets(scroll_offsets_by_frame_id);
|
||||||
|
|
||||||
if (s_use_gpu_painter) {
|
if (s_use_gpu_painter) {
|
||||||
#ifdef HAS_ACCELERATED_GRAPHICS
|
#ifdef HAS_ACCELERATED_GRAPHICS
|
||||||
Web::Painting::PaintingCommandExecutorGPU painting_command_executor(*m_accelerated_graphics_context, target);
|
Web::Painting::PaintingCommandExecutorGPU painting_command_executor(*m_accelerated_graphics_context, target);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue