Files
ladybird/Userland/Libraries/LibWeb/Painting/DisplayListPlayerGPU.cpp
Aliaksandr Kalenik 7181c3f2ea Everywhere: Limit layout text fragments to use one font for all glyphs
The ChunkIterator now limits a chunk to using only one font (before, it
was possible to have a chunk with >1 font, when `unicode-range` CSS
property is used).

This change allows us to reduce some complexity in the text shaping and
painting code and makes us compatible with the APIs in Skia and
HarfBuzz.
2024-06-30 19:23:24 +02:00

398 lines
17 KiB
C++

/*
* Copyright (c) 2023, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibAccelGfx/GlyphAtlas.h>
#include <LibWeb/Painting/BorderRadiusCornerClipper.h>
#include <LibWeb/Painting/DisplayListPlayerGPU.h>
namespace Web::Painting {
DisplayListPlayerGPU::DisplayListPlayerGPU(AccelGfx::Context& context, Gfx::Bitmap& bitmap)
: m_target_bitmap(bitmap)
, m_context(context)
{
m_context.activate();
auto canvas = AccelGfx::Canvas::create(bitmap.size());
auto painter = AccelGfx::Painter::create(m_context, canvas);
m_stacking_contexts.append({ .canvas = canvas,
.painter = move(painter),
.opacity = 1.0f,
.destination = {},
.transform = {} });
}
DisplayListPlayerGPU::~DisplayListPlayerGPU()
{
m_context.activate();
VERIFY(m_stacking_contexts.size() == 1);
painter().flush(m_target_bitmap);
}
CommandResult DisplayListPlayerGPU::draw_glyph_run(DrawGlyphRun const& command)
{
Vector<Gfx::DrawGlyphOrEmoji> transformed_glyph_run;
auto const& glyphs = command.glyph_run->glyphs();
transformed_glyph_run.ensure_capacity(glyphs.size());
auto const& font = command.glyph_run->font();
auto scaled_font = font.with_size(font.point_size() * static_cast<float>(command.scale));
for (auto& glyph : glyphs) {
auto transformed_glyph = glyph;
transformed_glyph.visit([&](auto& glyph) {
glyph.position = glyph.position.scaled(command.scale).translated(command.translation);
});
transformed_glyph_run.append(transformed_glyph);
}
painter().draw_glyph_run(transformed_glyph_run, scaled_font, command.color);
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::fill_rect(FillRect const& command)
{
// FIXME: Support clip paths
painter().fill_rect(command.rect, command.color);
return CommandResult::Continue;
}
static AccelGfx::Painter::ScalingMode to_accelgfx_scaling_mode(Gfx::ScalingMode scaling_mode)
{
switch (scaling_mode) {
case Gfx::ScalingMode::NearestNeighbor:
case Gfx::ScalingMode::BoxSampling:
case Gfx::ScalingMode::SmoothPixels:
case Gfx::ScalingMode::None:
return AccelGfx::Painter::ScalingMode::NearestNeighbor;
case Gfx::ScalingMode::BilinearBlend:
return AccelGfx::Painter::ScalingMode::Bilinear;
default:
VERIFY_NOT_REACHED();
}
}
CommandResult DisplayListPlayerGPU::draw_scaled_bitmap(DrawScaledBitmap const& command)
{
painter().draw_scaled_bitmap(command.dst_rect, command.bitmap, command.src_rect, to_accelgfx_scaling_mode(command.scaling_mode));
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::draw_scaled_immutable_bitmap(DrawScaledImmutableBitmap const& command)
{
// TODO: Support clip paths
painter().draw_scaled_immutable_bitmap(command.dst_rect, command.bitmap, command.src_rect, to_accelgfx_scaling_mode(command.scaling_mode));
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::save(Save const&)
{
painter().save();
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::restore(Restore const&)
{
painter().restore();
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::add_clip_rect(AddClipRect const& command)
{
painter().set_clip_rect(command.rect);
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::push_stacking_context(PushStackingContext const& command)
{
if (command.source_paintable_rect.is_empty())
return CommandResult::SkipStackingContext;
m_stacking_contexts.last().stacking_context_depth++;
painter().save();
if (command.is_fixed_position) {
auto const& translation = painter().transform().translation();
painter().translate(-translation);
}
auto stacking_context_transform = Gfx::extract_2d_affine_transform(command.transform.matrix);
Gfx::AffineTransform inverse_origin_translation;
inverse_origin_translation.translate(-command.transform.origin);
Gfx::AffineTransform origin_translation;
origin_translation.translate(command.transform.origin);
Gfx::AffineTransform final_transform = origin_translation;
final_transform.multiply(stacking_context_transform);
final_transform.multiply(inverse_origin_translation);
if (command.opacity < 1 || !stacking_context_transform.is_identity_or_translation()) {
// If, due to layout mistakes, we encounter an excessively large rectangle here, it must be skipped to prevent
// framebuffer allocation failure.
if (command.source_paintable_rect.width() > 10000 || command.source_paintable_rect.height() > 10000) {
dbgln("FIXME: Skipping stacking context with excessively large paintable rect: {}", command.source_paintable_rect);
return CommandResult::SkipStackingContext;
}
auto canvas = AccelGfx::Canvas::create(command.source_paintable_rect.size());
auto painter = AccelGfx::Painter::create(m_context, canvas);
painter->translate(-command.source_paintable_rect.location().to_type<float>());
painter->clear(Color::Transparent);
m_stacking_contexts.append({ .canvas = canvas,
.painter = move(painter),
.opacity = command.opacity,
.destination = command.source_paintable_rect,
.transform = final_transform });
} else {
painter().translate(stacking_context_transform.translation() + command.post_transform_translation.to_type<float>());
m_stacking_contexts.append({ .canvas = {},
.painter = MaybeOwned(painter()),
.opacity = command.opacity,
.destination = {},
.transform = final_transform });
}
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::pop_stacking_context(PopStackingContext const&)
{
auto stacking_context = m_stacking_contexts.take_last();
VERIFY(stacking_context.stacking_context_depth == 0);
if (stacking_context.painter.is_owned()) {
painter().blit_canvas(stacking_context.destination, *stacking_context.canvas, stacking_context.opacity, stacking_context.transform);
}
painter().restore();
m_stacking_contexts.last().stacking_context_depth--;
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::paint_linear_gradient(PaintLinearGradient const& command)
{
// FIXME: Support clip paths
auto const& linear_gradient_data = command.linear_gradient_data;
painter().fill_rect_with_linear_gradient(command.gradient_rect, linear_gradient_data.color_stops.list, linear_gradient_data.gradient_angle, linear_gradient_data.color_stops.repeat_length);
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::paint_outer_box_shadow(PaintOuterBoxShadow const&)
{
// FIXME
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::paint_inner_box_shadow(PaintInnerBoxShadow const&)
{
// FIXME
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::paint_text_shadow(PaintTextShadow const& command)
{
auto text_shadow_canvas = AccelGfx::Canvas::create(command.shadow_bounding_rect.size());
auto text_shadow_painter = AccelGfx::Painter::create(m_context, text_shadow_canvas);
text_shadow_painter->clear(command.color.with_alpha(0));
Gfx::FloatRect const shadow_location { command.draw_location, command.shadow_bounding_rect.size() };
Gfx::IntPoint const baseline_start(command.text_rect.x(), command.text_rect.y());
text_shadow_painter->translate(baseline_start.to_type<float>());
text_shadow_painter->draw_glyph_run(command.glyph_run->glyphs(), command.glyph_run->font(), command.color);
if (command.blur_radius == 0) {
painter().blit_canvas(shadow_location, *text_shadow_canvas);
return CommandResult::Continue;
}
auto horizontal_blur_canvas = AccelGfx::Canvas::create(command.shadow_bounding_rect.size());
auto horizontal_blur_painter = AccelGfx::Painter::create(m_context, horizontal_blur_canvas);
horizontal_blur_painter->clear(command.color.with_alpha(0));
horizontal_blur_painter->blit_blurred_canvas(command.shadow_bounding_rect.to_type<float>(), *text_shadow_canvas, command.blur_radius, AccelGfx::Painter::BlurDirection::Horizontal);
painter().blit_blurred_canvas(shadow_location, *horizontal_blur_canvas, command.blur_radius, AccelGfx::Painter::BlurDirection::Vertical);
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::fill_rect_with_rounded_corners(FillRectWithRoundedCorners const& command)
{
// FIXME: Support clip paths
painter().fill_rect_with_rounded_corners(
command.rect, command.color,
{ static_cast<float>(command.top_left_radius.horizontal_radius), static_cast<float>(command.top_left_radius.vertical_radius) },
{ static_cast<float>(command.top_right_radius.horizontal_radius), static_cast<float>(command.top_right_radius.vertical_radius) },
{ static_cast<float>(command.bottom_left_radius.horizontal_radius), static_cast<float>(command.bottom_left_radius.vertical_radius) },
{ static_cast<float>(command.bottom_right_radius.horizontal_radius), static_cast<float>(command.bottom_right_radius.vertical_radius) });
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::fill_path_using_color(FillPathUsingColor const&)
{
// FIXME
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::fill_path_using_paint_style(FillPathUsingPaintStyle const&)
{
// FIXME
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::stroke_path_using_color(StrokePathUsingColor const&)
{
// FIXME
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::stroke_path_using_paint_style(StrokePathUsingPaintStyle const&)
{
// FIXME
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::draw_ellipse(DrawEllipse const&)
{
// FIXME
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::fill_ellipse(FillEllipse const& command)
{
auto horizontal_radius = static_cast<float>(command.rect.width() / 2);
auto vertical_radius = static_cast<float>(command.rect.height() / 2);
painter().fill_rect_with_rounded_corners(
command.rect, command.color,
{ horizontal_radius, vertical_radius },
{ horizontal_radius, vertical_radius },
{ horizontal_radius, vertical_radius },
{ horizontal_radius, vertical_radius });
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::draw_line(DrawLine const& command)
{
// FIXME: Pass line style and alternate color once AccelGfx::Painter supports it
painter().draw_line(command.from, command.to, command.thickness, command.color);
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::apply_backdrop_filter(ApplyBackdropFilter const&)
{
// FIXME
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::draw_rect(DrawRect const&)
{
// FIXME
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::paint_radial_gradient(PaintRadialGradient const&)
{
// FIXME
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::paint_conic_gradient(PaintConicGradient const&)
{
// FIXME
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::draw_triangle_wave(DrawTriangleWave const&)
{
// FIXME
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::sample_under_corners(SampleUnderCorners const& command)
{
m_corner_clippers.resize(command.id + 1);
m_corner_clippers[command.id] = make<BorderRadiusCornerClipper>();
auto& corner_clipper = *m_corner_clippers[command.id];
auto const& top_left = command.corner_radii.top_left;
auto const& top_right = command.corner_radii.top_right;
auto const& bottom_right = command.corner_radii.bottom_right;
auto const& bottom_left = command.corner_radii.bottom_left;
auto sampling_config = calculate_border_radius_sampling_config(command.corner_radii, command.border_rect);
auto const& page_locations = sampling_config.page_locations;
auto const& bitmap_locations = sampling_config.bitmap_locations;
auto top_left_corner_size = Gfx::IntSize { top_left.horizontal_radius, top_left.vertical_radius };
auto top_right_corner_size = Gfx::IntSize { top_right.horizontal_radius, top_right.vertical_radius };
auto bottom_right_corner_size = Gfx::IntSize { bottom_right.horizontal_radius, bottom_right.vertical_radius };
auto bottom_left_corner_size = Gfx::IntSize { bottom_left.horizontal_radius, bottom_left.vertical_radius };
corner_clipper.page_top_left_rect = { page_locations.top_left, top_left_corner_size };
corner_clipper.page_top_right_rect = { page_locations.top_right, top_right_corner_size };
corner_clipper.page_bottom_right_rect = { page_locations.bottom_right, bottom_right_corner_size };
corner_clipper.page_bottom_left_rect = { page_locations.bottom_left, bottom_left_corner_size };
corner_clipper.sample_canvas_top_left_rect = { bitmap_locations.top_left, top_left_corner_size };
corner_clipper.sample_canvas_top_right_rect = { bitmap_locations.top_right, top_right_corner_size };
corner_clipper.sample_canvas_bottom_right_rect = { bitmap_locations.bottom_right, bottom_right_corner_size };
corner_clipper.sample_canvas_bottom_left_rect = { bitmap_locations.bottom_left, bottom_left_corner_size };
corner_clipper.corners_sample_canvas = AccelGfx::Canvas::create(sampling_config.corners_bitmap_size);
auto corner_painter = AccelGfx::Painter::create(m_context, *corner_clipper.corners_sample_canvas);
corner_painter->clear(Color::White);
corner_painter->fill_rect_with_rounded_corners(
Gfx::IntRect { { 0, 0 }, sampling_config.corners_bitmap_size },
Color::Transparent,
{ static_cast<float>(top_left.horizontal_radius), static_cast<float>(top_left.vertical_radius) },
{ static_cast<float>(top_right.horizontal_radius), static_cast<float>(top_right.vertical_radius) },
{ static_cast<float>(bottom_right.horizontal_radius), static_cast<float>(bottom_right.vertical_radius) },
{ static_cast<float>(bottom_left.horizontal_radius), static_cast<float>(bottom_left.vertical_radius) },
AccelGfx::Painter::BlendingMode::AlphaOverride);
auto const& target_canvas = painter().canvas();
if (!corner_clipper.sample_canvas_top_left_rect.is_empty())
corner_painter->blit_canvas(corner_clipper.sample_canvas_top_left_rect, target_canvas, painter().transform().map(corner_clipper.page_top_left_rect), 1.0f, {}, AccelGfx::Painter::BlendingMode::AlphaPreserve);
if (!corner_clipper.sample_canvas_top_right_rect.is_empty())
corner_painter->blit_canvas(corner_clipper.sample_canvas_top_right_rect, target_canvas, painter().transform().map(corner_clipper.page_top_right_rect), 1.0f, {}, AccelGfx::Painter::BlendingMode::AlphaPreserve);
if (!corner_clipper.sample_canvas_bottom_right_rect.is_empty())
corner_painter->blit_canvas(corner_clipper.sample_canvas_bottom_right_rect, target_canvas, painter().transform().map(corner_clipper.page_bottom_right_rect), 1.0f, {}, AccelGfx::Painter::BlendingMode::AlphaPreserve);
if (!corner_clipper.sample_canvas_bottom_left_rect.is_empty())
corner_painter->blit_canvas(corner_clipper.sample_canvas_bottom_left_rect, target_canvas, painter().transform().map(corner_clipper.page_bottom_left_rect), 1.0f, {}, AccelGfx::Painter::BlendingMode::AlphaPreserve);
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::blit_corner_clipping(BlitCornerClipping const& command)
{
auto const& corner_clipper = *m_corner_clippers[command.id];
auto const& corner_sample_canvas = *corner_clipper.corners_sample_canvas;
if (!corner_clipper.sample_canvas_top_left_rect.is_empty())
painter().blit_canvas(corner_clipper.page_top_left_rect, corner_sample_canvas, corner_clipper.sample_canvas_top_left_rect);
if (!corner_clipper.sample_canvas_top_right_rect.is_empty())
painter().blit_canvas(corner_clipper.page_top_right_rect, corner_sample_canvas, corner_clipper.sample_canvas_top_right_rect);
if (!corner_clipper.sample_canvas_bottom_right_rect.is_empty())
painter().blit_canvas(corner_clipper.page_bottom_right_rect, corner_sample_canvas, corner_clipper.sample_canvas_bottom_right_rect);
if (!corner_clipper.sample_canvas_bottom_left_rect.is_empty())
painter().blit_canvas(corner_clipper.page_bottom_left_rect, corner_sample_canvas, corner_clipper.sample_canvas_bottom_left_rect);
m_corner_clippers[command.id].clear();
return CommandResult::Continue;
}
bool DisplayListPlayerGPU::would_be_fully_clipped_by_painter(Gfx::IntRect rect) const
{
auto translation = painter().transform().translation().to_type<int>();
return !painter().clip_rect().intersects(rect.translated(translation));
}
void DisplayListPlayerGPU::prepare_glyph_texture(HashMap<Gfx::Font const*, HashTable<u32>> const& unique_glyphs)
{
AccelGfx::GlyphAtlas::the().update(unique_glyphs);
}
void DisplayListPlayerGPU::prepare_to_execute([[maybe_unused]] size_t corner_clip_max_depth)
{
m_context.activate();
}
void DisplayListPlayerGPU::update_immutable_bitmap_texture_cache(HashMap<u32, Gfx::ImmutableBitmap const*>& immutable_bitmaps)
{
painter().update_immutable_bitmap_texture_cache(immutable_bitmaps);
}
}