mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 20:27:45 +00:00
LibWeb: Don't encode painting limitations in RecordingPainter API
The current set of stacking context commands do not encode the information needed to correctly paint the stacking context, instead, they're based on the limitations of the current CPU renderer. Stacking contexts should be able to be transformed by an arbitrary 3D transformation matrix, not just scaled from a source to a destination rect. The `_with_mask()` stacking context also should not be separate from the regular stacking context. ```c++ push_stacking_context( bool semitransparent_or_has_non_identity_transform, float opacity, Gfx::FloatRect const& source_rect, Gfx::FloatRect const& transformed_destination_rect, Gfx::IntPoint const& painter_location); pop_stacking_context( bool semitransparent_or_has_non_identity_transform, Gfx::Painter::ScalingMode scaling_mode); push_stacking_context_with_mask( Gfx::IntRect const& paint_rect); pop_stacking_context_with_mask( Gfx::IntRect const& paint_rect, RefPtr<Gfx::Bitmap> const& mask_bitmap, Gfx::Bitmap::MaskKind mask_kind, float opacity); ``` This patch replaces this APIs with just: ```c++ push_stacking_context( float opacity, bool is_fixed_position, Gfx::IntRect const& source_paintable_rect, Gfx::IntPoint post_transform_translation, CSS::ImageRendering image_rendering, StackingContextTransform transform, Optional<StackingContextMask> mask); pop_stacking_context() ``` And moves the implementation details into the executor, this should allow future backends to implement stacking contexts without these limitations.
This commit is contained in:
parent
848b0d9c81
commit
4e04f81626
7 changed files with 189 additions and 253 deletions
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include <LibGfx/Filters/StackBlurFilter.h>
|
||||
#include <LibGfx/StylePainter.h>
|
||||
#include <LibWeb/CSS/ComputedValues.h>
|
||||
#include <LibWeb/Painting/BorderRadiusCornerClipper.h>
|
||||
#include <LibWeb/Painting/FilterPainting.h>
|
||||
#include <LibWeb/Painting/PaintingCommandExecutorCPU.h>
|
||||
|
@ -17,7 +18,10 @@ namespace Web::Painting {
|
|||
PaintingCommandExecutorCPU::PaintingCommandExecutorCPU(Gfx::Bitmap& bitmap)
|
||||
: m_target_bitmap(bitmap)
|
||||
{
|
||||
stacking_contexts.append({ Gfx::Painter(bitmap), {}, 1.0f });
|
||||
stacking_contexts.append({ .painter = AK::make<Gfx::Painter>(bitmap),
|
||||
.opacity = 1.0f,
|
||||
.destination = {},
|
||||
.scaling_mode = {} });
|
||||
}
|
||||
|
||||
CommandResult PaintingCommandExecutorCPU::draw_glyph_run(Vector<Gfx::DrawGlyphOrEmoji> const& glyph_run, Color const& color)
|
||||
|
@ -70,8 +74,7 @@ CommandResult PaintingCommandExecutorCPU::set_clip_rect(Gfx::IntRect const& rect
|
|||
|
||||
CommandResult PaintingCommandExecutorCPU::clear_clip_rect()
|
||||
{
|
||||
auto& painter = this->painter();
|
||||
painter.clear_clip_rect();
|
||||
painter().clear_clip_rect();
|
||||
return CommandResult::Continue;
|
||||
}
|
||||
|
||||
|
@ -82,107 +85,112 @@ CommandResult PaintingCommandExecutorCPU::set_font(Gfx::Font const& font)
|
|||
return CommandResult::Continue;
|
||||
}
|
||||
|
||||
CommandResult PaintingCommandExecutorCPU::push_stacking_context(bool semitransparent_or_has_non_identity_transform, float opacity, Gfx::FloatRect const& source_rect, Gfx::FloatRect const& transformed_destination_rect, Gfx::IntPoint const& painter_location)
|
||||
CommandResult PaintingCommandExecutorCPU::push_stacking_context(
|
||||
float opacity, bool is_fixed_position, Gfx::IntRect const& source_paintable_rect, Gfx::IntPoint post_transform_translation,
|
||||
CSS::ImageRendering image_rendering, StackingContextTransform transform, Optional<StackingContextMask> mask)
|
||||
{
|
||||
auto& painter = this->painter();
|
||||
if (semitransparent_or_has_non_identity_transform) {
|
||||
auto destination_rect = transformed_destination_rect.to_rounded<int>();
|
||||
|
||||
// FIXME: We should find a way to scale the paintable, rather than paint into a separate bitmap,
|
||||
// then scale it. This snippet now copies the background at the destination, then scales it down/up
|
||||
// to the size of the source (which could add some artefacts, though just scaling the bitmap already does that).
|
||||
// We need to copy the background at the destination because a bunch of our rendering effects now rely on
|
||||
// being able to sample the painter (see border radii, shadows, filters, etc).
|
||||
Gfx::FloatPoint destination_clipped_fixup {};
|
||||
auto try_get_scaled_destination_bitmap = [&]() -> ErrorOr<NonnullRefPtr<Gfx::Bitmap>> {
|
||||
Gfx::IntRect actual_destination_rect;
|
||||
auto bitmap = TRY(painter.get_region_bitmap(destination_rect, Gfx::BitmapFormat::BGRA8888, actual_destination_rect));
|
||||
// get_region_bitmap() may clip to a smaller region if the requested rect goes outside the painter, so we need to account for that.
|
||||
destination_clipped_fixup = Gfx::FloatPoint { destination_rect.location() - actual_destination_rect.location() };
|
||||
destination_rect = actual_destination_rect;
|
||||
if (source_rect.size() != transformed_destination_rect.size()) {
|
||||
auto sx = static_cast<float>(source_rect.width()) / transformed_destination_rect.width();
|
||||
auto sy = static_cast<float>(source_rect.height()) / transformed_destination_rect.height();
|
||||
bitmap = TRY(bitmap->scaled(sx, sy));
|
||||
destination_clipped_fixup.scale_by(sx, sy);
|
||||
}
|
||||
return bitmap;
|
||||
};
|
||||
|
||||
auto bitmap_or_error = try_get_scaled_destination_bitmap();
|
||||
if (bitmap_or_error.is_error()) {
|
||||
// NOTE: If the creation of the bitmap fails, we need to skip all painting commands that belong to this stacking context.
|
||||
// We don't interrupt the execution of painting commands because get_region_bitmap() returns an error if the requested
|
||||
// region is outside of the viewport (mmap fails to allocate a zero-size region), which means we can safely proceed
|
||||
// with execution of commands outside of this stacking context.
|
||||
// FIXME: Change the get_region_bitmap() API to return ErrorOr<Optional<Bitmap>> and exit the execution of commands here
|
||||
// if we run out of memory.
|
||||
return CommandResult::SkipStackingContext;
|
||||
}
|
||||
auto bitmap = bitmap_or_error.release_value_but_fixme_should_propagate_errors();
|
||||
|
||||
Gfx::Painter stacking_context_painter(bitmap);
|
||||
|
||||
stacking_context_painter.translate(painter_location + destination_clipped_fixup.to_type<int>());
|
||||
painter().save();
|
||||
if (is_fixed_position)
|
||||
painter().translate(-painter().translation());
|
||||
|
||||
if (mask.has_value()) {
|
||||
// TODO: Support masks and other stacking context features at the same time.
|
||||
// Note: Currently only SVG masking is implemented (which does not use CSS transforms anyway).
|
||||
auto bitmap_or_error = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, mask->mask_bitmap->size());
|
||||
if (bitmap_or_error.is_error())
|
||||
return CommandResult::Continue;
|
||||
auto bitmap = bitmap_or_error.release_value();
|
||||
stacking_contexts.append(StackingContext {
|
||||
.painter = stacking_context_painter,
|
||||
.destination = destination_rect,
|
||||
.opacity = opacity,
|
||||
});
|
||||
} else {
|
||||
painter.save();
|
||||
.painter = AK::make<Gfx::Painter>(bitmap),
|
||||
.opacity = 1,
|
||||
.destination = source_paintable_rect.translated(post_transform_translation),
|
||||
.scaling_mode = Gfx::Painter::ScalingMode::None,
|
||||
.mask = mask });
|
||||
painter().translate(-source_paintable_rect.location());
|
||||
return CommandResult::Continue;
|
||||
}
|
||||
|
||||
// FIXME: This extracts the affine 2D part of the full transformation matrix.
|
||||
// Use the whole matrix when we get better transformation support in LibGfx or use LibGL for drawing the bitmap
|
||||
auto affine_transform = Gfx::extract_2d_affine_transform(transform.matrix);
|
||||
|
||||
if (opacity == 1.0f && affine_transform.is_identity_or_translation()) {
|
||||
// OPTIMIZATION: This is a simple translation use previous stacking context's painter.
|
||||
painter().translate(affine_transform.translation().to_rounded<int>() + post_transform_translation);
|
||||
stacking_contexts.append(StackingContext {
|
||||
.painter = MaybeOwned(painter()),
|
||||
.opacity = 1,
|
||||
.destination = {},
|
||||
.scaling_mode = {} });
|
||||
return CommandResult::Continue;
|
||||
}
|
||||
|
||||
auto& current_painter = this->painter();
|
||||
auto source_rect = source_paintable_rect.to_type<float>().translated(-transform.origin);
|
||||
auto transformed_destination_rect = affine_transform.map(source_rect).translated(transform.origin);
|
||||
auto destination_rect = transformed_destination_rect.to_rounded<int>();
|
||||
|
||||
// FIXME: We should find a way to scale the paintable, rather than paint into a separate bitmap,
|
||||
// then scale it. This snippet now copies the background at the destination, then scales it down/up
|
||||
// to the size of the source (which could add some artefacts, though just scaling the bitmap already does that).
|
||||
// We need to copy the background at the destination because a bunch of our rendering effects now rely on
|
||||
// being able to sample the painter (see border radii, shadows, filters, etc).
|
||||
Gfx::FloatPoint destination_clipped_fixup {};
|
||||
auto try_get_scaled_destination_bitmap = [&]() -> ErrorOr<NonnullRefPtr<Gfx::Bitmap>> {
|
||||
Gfx::IntRect actual_destination_rect;
|
||||
auto bitmap = TRY(current_painter.get_region_bitmap(destination_rect, Gfx::BitmapFormat::BGRA8888, actual_destination_rect));
|
||||
// get_region_bitmap() may clip to a smaller region if the requested rect goes outside the painter, so we need to account for that.
|
||||
destination_clipped_fixup = Gfx::FloatPoint { destination_rect.location() - actual_destination_rect.location() };
|
||||
destination_rect = actual_destination_rect;
|
||||
if (source_rect.size() != transformed_destination_rect.size()) {
|
||||
auto sx = static_cast<float>(source_rect.width()) / transformed_destination_rect.width();
|
||||
auto sy = static_cast<float>(source_rect.height()) / transformed_destination_rect.height();
|
||||
bitmap = TRY(bitmap->scaled(sx, sy));
|
||||
destination_clipped_fixup.scale_by(sx, sy);
|
||||
}
|
||||
return bitmap;
|
||||
};
|
||||
|
||||
auto bitmap_or_error = try_get_scaled_destination_bitmap();
|
||||
if (bitmap_or_error.is_error()) {
|
||||
// NOTE: If the creation of the bitmap fails, we need to skip all painting commands that belong to this stacking context.
|
||||
// We don't interrupt the execution of painting commands because get_region_bitmap() returns an error if the requested
|
||||
// region is outside of the viewport (mmap fails to allocate a zero-size region), which means we can safely proceed
|
||||
// with execution of commands outside of this stacking context.
|
||||
// FIXME: Change the get_region_bitmap() API to return ErrorOr<Optional<Bitmap>> and exit the execution of commands here
|
||||
// if we run out of memory.
|
||||
return CommandResult::SkipStackingContext;
|
||||
}
|
||||
|
||||
auto bitmap = bitmap_or_error.release_value();
|
||||
stacking_contexts.append(StackingContext {
|
||||
.painter = AK::make<Gfx::Painter>(bitmap),
|
||||
.opacity = opacity,
|
||||
.destination = destination_rect.translated(post_transform_translation),
|
||||
.scaling_mode = CSS::to_gfx_scaling_mode(image_rendering, destination_rect, destination_rect) });
|
||||
painter().translate(-source_paintable_rect.location() + destination_clipped_fixup.to_type<int>());
|
||||
|
||||
return CommandResult::Continue;
|
||||
}
|
||||
|
||||
CommandResult PaintingCommandExecutorCPU::pop_stacking_context(bool semitransparent_or_has_non_identity_transform, Gfx::Painter::ScalingMode scaling_mode)
|
||||
CommandResult PaintingCommandExecutorCPU::pop_stacking_context()
|
||||
{
|
||||
if (semitransparent_or_has_non_identity_transform) {
|
||||
auto stacking_context = stacking_contexts.take_last();
|
||||
auto bitmap = stacking_context.painter.target();
|
||||
ScopeGuard restore_painter = [&] {
|
||||
painter().restore();
|
||||
};
|
||||
auto stacking_context = stacking_contexts.take_last();
|
||||
// Stacking contexts that don't own their painter are simple translations, and don't need to blit anything back.
|
||||
if (stacking_context.painter.is_owned()) {
|
||||
auto bitmap = stacking_context.painter->target();
|
||||
if (stacking_context.mask.has_value())
|
||||
bitmap->apply_mask(*stacking_context.mask->mask_bitmap, stacking_context.mask->mask_kind);
|
||||
auto destination_rect = stacking_context.destination;
|
||||
|
||||
if (destination_rect.size() == bitmap->size()) {
|
||||
painter().blit(destination_rect.location(), *bitmap, bitmap->rect(), stacking_context.opacity);
|
||||
} else {
|
||||
painter().draw_scaled_bitmap(destination_rect, *bitmap, bitmap->rect(), stacking_context.opacity, scaling_mode);
|
||||
painter().draw_scaled_bitmap(destination_rect, *bitmap, bitmap->rect(), stacking_context.opacity, stacking_context.scaling_mode);
|
||||
}
|
||||
} else {
|
||||
painter().restore();
|
||||
}
|
||||
|
||||
return CommandResult::Continue;
|
||||
}
|
||||
|
||||
CommandResult PaintingCommandExecutorCPU::push_stacking_context_with_mask(Gfx::IntRect const& paint_rect)
|
||||
{
|
||||
auto bitmap_or_error = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, paint_rect.size());
|
||||
if (bitmap_or_error.is_error())
|
||||
return CommandResult::Continue;
|
||||
auto bitmap = bitmap_or_error.release_value();
|
||||
|
||||
Gfx::Painter stacking_context_painter(bitmap);
|
||||
|
||||
stacking_context_painter.translate(-paint_rect.location());
|
||||
|
||||
stacking_contexts.append(StackingContext {
|
||||
.painter = stacking_context_painter,
|
||||
.destination = {},
|
||||
.opacity = 1,
|
||||
});
|
||||
|
||||
return CommandResult::Continue;
|
||||
}
|
||||
|
||||
CommandResult PaintingCommandExecutorCPU::pop_stacking_context_with_mask(Gfx::IntRect const& paint_rect, RefPtr<Gfx::Bitmap> const& mask_bitmap, Gfx::Bitmap::MaskKind mask_kind, float opacity)
|
||||
{
|
||||
auto stacking_context = stacking_contexts.take_last();
|
||||
auto bitmap = stacking_context.painter.target();
|
||||
if (mask_bitmap)
|
||||
bitmap->apply_mask(*mask_bitmap, mask_kind);
|
||||
painter().blit(paint_rect.location(), *bitmap, bitmap->rect(), opacity);
|
||||
return CommandResult::Continue;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue