LibWeb+LibGfx: Use Skia for text rasterization

The main incentive is much better performance. We could have gone a bit
further in optimizing the Skia painter to blit glyphs produced by LibGfx
more efficiently from the glyph atlas, but eventually, we also want Skia
to improve correctness.

This change does not completely replace LibGfx in text handling. It's
still used at all stages, including layout, up until display list
replaying.
This commit is contained in:
Aliaksandr Kalenik 2024-07-25 21:59:30 +03:00 committed by Andreas Kling
parent b7f0241ab5
commit 1bd0871ed8
Notes: github-actions[bot] 2024-07-27 06:19:45 +00:00
21 changed files with 154 additions and 56 deletions

22
Meta/CMake/skia.cmake Normal file
View file

@ -0,0 +1,22 @@
find_package(unofficial-skia CONFIG)
if(unofficial-skia_FOUND)
set(SKIA_LIBRARIES unofficial::skia::skia)
else()
find_package(PkgConfig)
# Get skia version from vcpkg.json
file(READ ${LADYBIRD_SOURCE_DIR}/vcpkg.json VCPKG_DOT_JSON)
string(JSON VCPKG_OVERRIDES_LENGTH LENGTH ${VCPKG_DOT_JSON} overrides)
MATH(EXPR VCPKG_OVERRIDES_END_RANGE "${VCPKG_OVERRIDES_LENGTH}-1")
foreach(IDX RANGE ${VCPKG_OVERRIDES_END_RANGE})
string(JSON VCPKG_OVERRIDE_NAME GET ${VCPKG_DOT_JSON} overrides ${IDX} name)
if(VCPKG_OVERRIDE_NAME STREQUAL "skia")
string(JSON SKIA_REQUIRED_VERSION GET ${VCPKG_DOT_JSON} overrides ${IDX} version)
string(REGEX MATCH "[0-9]+" SKIA_REQUIRED_VERSION ${SKIA_REQUIRED_VERSION})
endif()
endforeach()
pkg_check_modules(SKIA skia=${SKIA_REQUIRED_VERSION} REQUIRED)
target_include_directories(LibWeb PRIVATE ${SKIA_INCLUDE_DIRS})
target_link_directories(LibWeb PRIVATE ${SKIA_LIBRARY_DIRS})
endif()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 267 KiB

After

Width:  |  Height:  |  Size: 268 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 656 KiB

After

Width:  |  Height:  |  Size: 674 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 78 KiB

View file

@ -1,3 +1,5 @@
include(skia)
set(SOURCES
AffineTransform.cpp
AntiAliasingPainter.cpp
@ -17,7 +19,9 @@ set(SOURCES
Font/OpenType/Tables.cpp
Font/OpenType/Typeface.cpp
Font/ScaledFont.cpp
Font/ScaledFontSkia.cpp
Font/Typeface.cpp
Font/TypefaceSkia.cpp
Font/WOFF/Loader.cpp
Font/WOFF2/Loader.cpp
GradientPainting.cpp
@ -65,7 +69,8 @@ set(SOURCES
)
serenity_lib(LibGfx gfx)
target_link_libraries(LibGfx PRIVATE LibCompress LibCore LibCrypto LibFileSystem LibRIFF LibTextCodec LibIPC LibUnicode LibURL)
target_link_libraries(LibGfx PRIVATE LibCompress LibCore LibCrypto LibFileSystem LibRIFF LibTextCodec LibIPC LibUnicode LibURL ${SKIA_LIBRARIES})
set(generated_sources TIFFMetadata.h TIFFTagHandler.cpp)
list(TRANSFORM generated_sources PREPEND "ImageFormats/")

View file

@ -98,6 +98,8 @@ enum FontWidth {
UltraExpanded = 9
};
class Typeface;
class Font : public RefCounted<Font> {
public:
virtual ~Font() {};
@ -143,6 +145,8 @@ public:
virtual bool has_color_bitmaps() const = 0;
virtual Typeface const& typeface() const = 0;
private:
mutable RefPtr<Gfx::Font const> m_bold_variant;
};

View file

@ -352,7 +352,9 @@ ErrorOr<NonnullRefPtr<Typeface>> Typeface::try_load_from_offset(ReadonlyBytes bu
move(prep),
move(cblc),
move(cbdt),
move(gpos)));
move(gpos),
buffer.slice(offset),
options.index));
}
Gfx::ScaledFontMetrics Typeface::metrics([[maybe_unused]] float x_scale, float y_scale) const

View file

@ -88,6 +88,10 @@ public:
static constexpr Tag HeaderTag_CFFOutlines = Tag { "OTTO" };
static constexpr Tag HeaderTag_FontCollection = Tag { "ttcf" };
protected:
virtual ReadonlyBytes buffer() const override { return m_buffer; }
virtual unsigned ttc_index() const override { return m_ttc_index; }
private:
struct AscenderAndDescender {
i16 ascender;
@ -126,8 +130,12 @@ private:
Optional<Prep> prep,
Optional<CBLC> cblc,
Optional<CBDT> cbdt,
Optional<GPOS> gpos)
: m_head(move(head))
Optional<GPOS> gpos,
ReadonlyBytes buffer,
unsigned ttc_index)
: m_buffer(buffer)
, m_ttc_index(ttc_index)
, m_head(move(head))
, m_name(move(name))
, m_hhea(move(hhea))
, m_maxp(move(maxp))
@ -146,6 +154,8 @@ private:
}
OwnPtr<Gfx::FontData> m_font_data;
ReadonlyBytes m_buffer;
unsigned m_ttc_index { 0 };
// These are stateful wrappers around non-owning slices
Head m_head;

View file

@ -12,6 +12,8 @@
#include <LibGfx/Font/Font.h>
#include <LibGfx/Font/Typeface.h>
class SkFont;
namespace Gfx {
struct GlyphIndexWithSubpixelOffset {
@ -57,6 +59,10 @@ public:
virtual bool has_color_bitmaps() const override { return m_typeface->has_color_bitmaps(); }
virtual Typeface const& typeface() const override { return m_typeface; }
SkFont skia_font(float scale) const;
private:
NonnullRefPtr<Typeface> m_typeface;
float m_x_scale { 0.0f };

View file

@ -0,0 +1,21 @@
/*
* Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#define AK_DONT_REPLACE_STD
#include <core/SkFont.h>
#include <LibGfx/Font/ScaledFont.h>
namespace Gfx {
SkFont ScaledFont::skia_font(float scale) const
{
auto const& typeface = this->typeface().skia_typeface();
return SkFont { sk_ref_sp(typeface.ptr()), pixel_size() * scale };
}
}

View file

@ -4,6 +4,10 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#define AK_DONT_REPLACE_STD
#include <core/SkTypeface.h>
#include <LibGfx/Font/ScaledFont.h>
#include <LibGfx/Font/Typeface.h>

View file

@ -17,6 +17,8 @@
#define POINTS_PER_INCH 72.0f
#define DEFAULT_DPI 96
class SkTypeface;
namespace Gfx {
class ScaledFont;
@ -63,11 +65,17 @@ public:
[[nodiscard]] NonnullRefPtr<ScaledFont> scaled_font(float point_size) const;
RefPtr<SkTypeface> const& skia_typeface() const;
protected:
Typeface();
virtual ReadonlyBytes buffer() const = 0;
virtual unsigned ttc_index() const = 0;
private:
mutable HashMap<float, NonnullRefPtr<ScaledFont>> m_scaled_fonts;
mutable RefPtr<SkTypeface> m_skia_typeface;
};
}

View file

@ -0,0 +1,45 @@
/*
* Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#define AK_DONT_REPLACE_STD
#include <LibGfx/Font/Typeface.h>
#include <core/SkData.h>
#include <core/SkFontMgr.h>
#include <core/SkTypeface.h>
#ifdef AK_OS_MACOS
# include <ports/SkFontMgr_mac_ct.h>
#else
# include <ports/SkFontMgr_fontconfig.h>
#endif
namespace Gfx {
static sk_sp<SkFontMgr> s_font_manager;
RefPtr<SkTypeface> const& Typeface::skia_typeface() const
{
if (!s_font_manager) {
#ifdef AK_OS_MACOS
s_font_manager = SkFontMgr_New_CoreText(nullptr);
#else
s_font_manager = SkFontMgr_New_FontConfig(nullptr);
#endif
}
if (!m_skia_typeface) {
auto data = SkData::MakeWithoutCopy(buffer().data(), buffer().size());
auto skia_typeface = s_font_manager->makeFromData(data, ttc_index());
if (!skia_typeface)
VERIFY_NOT_REACHED();
m_skia_typeface = *skia_typeface;
}
return m_skia_typeface;
}
}

View file

@ -1,5 +1,6 @@
include(libweb_generators)
include(vulkan)
include(skia)
set(SOURCES
Animations/Animatable.cpp
@ -767,29 +768,6 @@ set(GENERATED_SOURCES
serenity_lib(LibWeb web)
find_package(unofficial-skia CONFIG)
if(unofficial-skia_FOUND)
set(SKIA_LIBRARIES unofficial::skia::skia)
else()
find_package(PkgConfig)
# Get skia version from vcpkg.json
file(READ ${LADYBIRD_SOURCE_DIR}/vcpkg.json VCPKG_DOT_JSON)
string(JSON VCPKG_OVERRIDES_LENGTH LENGTH ${VCPKG_DOT_JSON} overrides)
MATH(EXPR VCPKG_OVERRIDES_END_RANGE "${VCPKG_OVERRIDES_LENGTH}-1")
foreach(IDX RANGE ${VCPKG_OVERRIDES_END_RANGE})
string(JSON VCPKG_OVERRIDE_NAME GET ${VCPKG_DOT_JSON} overrides ${IDX} name)
if(VCPKG_OVERRIDE_NAME STREQUAL "skia")
string(JSON SKIA_REQUIRED_VERSION GET ${VCPKG_DOT_JSON} overrides ${IDX} version)
string(REGEX MATCH "[0-9]+" SKIA_REQUIRED_VERSION ${SKIA_REQUIRED_VERSION})
endif()
endforeach()
pkg_check_modules(SKIA skia=${SKIA_REQUIRED_VERSION} REQUIRED)
target_include_directories(LibWeb PRIVATE ${SKIA_INCLUDE_DIRS})
target_link_directories(LibWeb PRIVATE ${SKIA_LIBRARY_DIRS})
endif()
target_link_libraries(LibWeb PRIVATE LibCore LibCrypto LibJS LibHTTP LibGfx LibIPC LibRegex LibSyntax LibTextCodec LibUnicode LibAudio LibMedia LibWasm LibXML LibIDL LibURL LibTLS ${SKIA_LIBRARIES})
generate_js_bindings(LibWeb)

View file

@ -10,6 +10,8 @@
#include <core/SkBlurTypes.h>
#include <core/SkCanvas.h>
#include <core/SkColorFilter.h>
#include <core/SkFont.h>
#include <core/SkFontMgr.h>
#include <core/SkMaskFilter.h>
#include <core/SkPath.h>
#include <core/SkPathBuilder.h>
@ -23,6 +25,7 @@
#include <gpu/ganesh/SkSurfaceGanesh.h>
#include <pathops/SkPathOps.h>
#include <LibGfx/Font/ScaledFont.h>
#include <LibWeb/CSS/ComputedValues.h>
#include <LibWeb/Painting/DisplayListPlayerSkia.h>
#include <LibWeb/Painting/ShadowPainting.h>
@ -387,46 +390,36 @@ DisplayListPlayerSkia::SkiaSurface& DisplayListPlayerSkia::surface() const
return static_cast<SkiaSurface&>(*m_surface);
}
static HashMap<Gfx::Bitmap*, sk_sp<SkImage>> s_glyph_cache;
void DisplayListPlayerSkia::draw_glyph_run(DrawGlyphRun const& command)
{
auto& canvas = surface().canvas();
SkPaint paint;
paint.setColorFilter(SkColorFilters::Blend(to_skia_color(command.color), SkBlendMode::kSrcIn));
auto const& glyphs = command.glyph_run->glyphs();
auto const& font = command.glyph_run->font();
auto scaled_font = font.with_size(font.point_size() * static_cast<float>(command.scale));
for (auto const& glyph_or_emoji : glyphs) {
auto const& gfx_font = static_cast<Gfx::ScaledFont const&>(command.glyph_run->font());
auto const& gfx_typeface = gfx_font.typeface();
auto sk_font = gfx_font.skia_font(command.scale);
auto glyph_count = command.glyph_run->glyphs().size();
Vector<SkGlyphID> glyphs;
glyphs.ensure_capacity(glyph_count);
Vector<SkPoint> positions;
positions.ensure_capacity(glyph_count);
auto font_ascent = gfx_font.pixel_metrics().ascent;
for (auto const& glyph_or_emoji : command.glyph_run->glyphs()) {
auto transformed_glyph = glyph_or_emoji;
transformed_glyph.visit([&](auto& glyph) {
glyph.position = glyph.position.scaled(command.scale).translated(command.translation);
glyph.position.set_y(glyph.position.y() + font_ascent);
glyph.position = glyph.position.scaled(command.scale);
});
if (transformed_glyph.has<Gfx::DrawGlyph>()) {
auto& glyph = transformed_glyph.get<Gfx::DrawGlyph>();
auto const& point = glyph.position;
auto const& code_point = glyph.code_point;
auto top_left = point + Gfx::FloatPoint(scaled_font->glyph_left_bearing(code_point), 0);
auto glyph_position = Gfx::GlyphRasterPosition::get_nearest_fit_for(top_left);
auto maybe_font_glyph = scaled_font->glyph(code_point, glyph_position.subpixel_offset);
if (!maybe_font_glyph.has_value())
continue;
if (!maybe_font_glyph->is_color_bitmap()) {
auto const& blit_position = glyph_position.blit_position;
sk_sp<SkImage> image;
if (auto maybe_image = s_glyph_cache.get(maybe_font_glyph->bitmap()); maybe_image.has_value()) {
image = maybe_image.value();
} else {
auto sk_bitmap = to_skia_bitmap(*maybe_font_glyph->bitmap());
image = SkImages::RasterFromBitmap(sk_bitmap);
s_glyph_cache.set(maybe_font_glyph->bitmap(), image);
}
canvas.drawImage(image, blit_position.x(), blit_position.y(), SkSamplingOptions(), &paint);
} else {
TODO();
}
glyphs.append(gfx_typeface.glyph_id_for_code_point(code_point));
positions.append(to_skia_point(point));
}
}
SkPaint paint;
paint.setColor(to_skia_color(command.color));
surface().canvas().drawGlyphs(glyphs.size(), glyphs.data(), positions.data(), to_skia_point(command.translation), sk_font, paint);
}
void DisplayListPlayerSkia::fill_rect(FillRect const& command)