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
commit 35392d4d28
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

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 267 KiB

After

Width:  |  Height:  |  Size: 268 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 107 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 656 KiB

After

Width:  |  Height:  |  Size: 674 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Before After
Before After

View file

@ -1,3 +1,5 @@
include(skia)
set(SOURCES set(SOURCES
AffineTransform.cpp AffineTransform.cpp
AntiAliasingPainter.cpp AntiAliasingPainter.cpp
@ -17,7 +19,9 @@ set(SOURCES
Font/OpenType/Tables.cpp Font/OpenType/Tables.cpp
Font/OpenType/Typeface.cpp Font/OpenType/Typeface.cpp
Font/ScaledFont.cpp Font/ScaledFont.cpp
Font/ScaledFontSkia.cpp
Font/Typeface.cpp Font/Typeface.cpp
Font/TypefaceSkia.cpp
Font/WOFF/Loader.cpp Font/WOFF/Loader.cpp
Font/WOFF2/Loader.cpp Font/WOFF2/Loader.cpp
GradientPainting.cpp GradientPainting.cpp
@ -65,7 +69,8 @@ set(SOURCES
) )
serenity_lib(LibGfx gfx) 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) set(generated_sources TIFFMetadata.h TIFFTagHandler.cpp)
list(TRANSFORM generated_sources PREPEND "ImageFormats/") list(TRANSFORM generated_sources PREPEND "ImageFormats/")

View file

@ -98,6 +98,8 @@ enum FontWidth {
UltraExpanded = 9 UltraExpanded = 9
}; };
class Typeface;
class Font : public RefCounted<Font> { class Font : public RefCounted<Font> {
public: public:
virtual ~Font() {}; virtual ~Font() {};
@ -143,6 +145,8 @@ public:
virtual bool has_color_bitmaps() const = 0; virtual bool has_color_bitmaps() const = 0;
virtual Typeface const& typeface() const = 0;
private: private:
mutable RefPtr<Gfx::Font const> m_bold_variant; 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(prep),
move(cblc), move(cblc),
move(cbdt), move(cbdt),
move(gpos))); move(gpos),
buffer.slice(offset),
options.index));
} }
Gfx::ScaledFontMetrics Typeface::metrics([[maybe_unused]] float x_scale, float y_scale) const 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_CFFOutlines = Tag { "OTTO" };
static constexpr Tag HeaderTag_FontCollection = Tag { "ttcf" }; 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: private:
struct AscenderAndDescender { struct AscenderAndDescender {
i16 ascender; i16 ascender;
@ -126,8 +130,12 @@ private:
Optional<Prep> prep, Optional<Prep> prep,
Optional<CBLC> cblc, Optional<CBLC> cblc,
Optional<CBDT> cbdt, Optional<CBDT> cbdt,
Optional<GPOS> gpos) Optional<GPOS> gpos,
: m_head(move(head)) ReadonlyBytes buffer,
unsigned ttc_index)
: m_buffer(buffer)
, m_ttc_index(ttc_index)
, m_head(move(head))
, m_name(move(name)) , m_name(move(name))
, m_hhea(move(hhea)) , m_hhea(move(hhea))
, m_maxp(move(maxp)) , m_maxp(move(maxp))
@ -146,6 +154,8 @@ private:
} }
OwnPtr<Gfx::FontData> m_font_data; OwnPtr<Gfx::FontData> m_font_data;
ReadonlyBytes m_buffer;
unsigned m_ttc_index { 0 };
// These are stateful wrappers around non-owning slices // These are stateful wrappers around non-owning slices
Head m_head; Head m_head;

View file

@ -12,6 +12,8 @@
#include <LibGfx/Font/Font.h> #include <LibGfx/Font/Font.h>
#include <LibGfx/Font/Typeface.h> #include <LibGfx/Font/Typeface.h>
class SkFont;
namespace Gfx { namespace Gfx {
struct GlyphIndexWithSubpixelOffset { struct GlyphIndexWithSubpixelOffset {
@ -57,6 +59,10 @@ public:
virtual bool has_color_bitmaps() const override { return m_typeface->has_color_bitmaps(); } 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: private:
NonnullRefPtr<Typeface> m_typeface; NonnullRefPtr<Typeface> m_typeface;
float m_x_scale { 0.0f }; 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 * SPDX-License-Identifier: BSD-2-Clause
*/ */
#define AK_DONT_REPLACE_STD
#include <core/SkTypeface.h>
#include <LibGfx/Font/ScaledFont.h> #include <LibGfx/Font/ScaledFont.h>
#include <LibGfx/Font/Typeface.h> #include <LibGfx/Font/Typeface.h>

View file

@ -17,6 +17,8 @@
#define POINTS_PER_INCH 72.0f #define POINTS_PER_INCH 72.0f
#define DEFAULT_DPI 96 #define DEFAULT_DPI 96
class SkTypeface;
namespace Gfx { namespace Gfx {
class ScaledFont; class ScaledFont;
@ -63,11 +65,17 @@ public:
[[nodiscard]] NonnullRefPtr<ScaledFont> scaled_font(float point_size) const; [[nodiscard]] NonnullRefPtr<ScaledFont> scaled_font(float point_size) const;
RefPtr<SkTypeface> const& skia_typeface() const;
protected: protected:
Typeface(); Typeface();
virtual ReadonlyBytes buffer() const = 0;
virtual unsigned ttc_index() const = 0;
private: private:
mutable HashMap<float, NonnullRefPtr<ScaledFont>> m_scaled_fonts; 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(libweb_generators)
include(vulkan) include(vulkan)
include(skia)
set(SOURCES set(SOURCES
Animations/Animatable.cpp Animations/Animatable.cpp
@ -767,29 +768,6 @@ set(GENERATED_SOURCES
serenity_lib(LibWeb web) 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}) 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) generate_js_bindings(LibWeb)

View file

@ -10,6 +10,8 @@
#include <core/SkBlurTypes.h> #include <core/SkBlurTypes.h>
#include <core/SkCanvas.h> #include <core/SkCanvas.h>
#include <core/SkColorFilter.h> #include <core/SkColorFilter.h>
#include <core/SkFont.h>
#include <core/SkFontMgr.h>
#include <core/SkMaskFilter.h> #include <core/SkMaskFilter.h>
#include <core/SkPath.h> #include <core/SkPath.h>
#include <core/SkPathBuilder.h> #include <core/SkPathBuilder.h>
@ -23,6 +25,7 @@
#include <gpu/ganesh/SkSurfaceGanesh.h> #include <gpu/ganesh/SkSurfaceGanesh.h>
#include <pathops/SkPathOps.h> #include <pathops/SkPathOps.h>
#include <LibGfx/Font/ScaledFont.h>
#include <LibWeb/CSS/ComputedValues.h> #include <LibWeb/CSS/ComputedValues.h>
#include <LibWeb/Painting/DisplayListPlayerSkia.h> #include <LibWeb/Painting/DisplayListPlayerSkia.h>
#include <LibWeb/Painting/ShadowPainting.h> #include <LibWeb/Painting/ShadowPainting.h>
@ -387,46 +390,36 @@ DisplayListPlayerSkia::SkiaSurface& DisplayListPlayerSkia::surface() const
return static_cast<SkiaSurface&>(*m_surface); return static_cast<SkiaSurface&>(*m_surface);
} }
static HashMap<Gfx::Bitmap*, sk_sp<SkImage>> s_glyph_cache;
void DisplayListPlayerSkia::draw_glyph_run(DrawGlyphRun const& command) void DisplayListPlayerSkia::draw_glyph_run(DrawGlyphRun const& command)
{ {
auto& canvas = surface().canvas(); auto const& gfx_font = static_cast<Gfx::ScaledFont const&>(command.glyph_run->font());
SkPaint paint; auto const& gfx_typeface = gfx_font.typeface();
paint.setColorFilter(SkColorFilters::Blend(to_skia_color(command.color), SkBlendMode::kSrcIn)); auto sk_font = gfx_font.skia_font(command.scale);
auto const& glyphs = command.glyph_run->glyphs();
auto const& font = command.glyph_run->font(); auto glyph_count = command.glyph_run->glyphs().size();
auto scaled_font = font.with_size(font.point_size() * static_cast<float>(command.scale)); Vector<SkGlyphID> glyphs;
for (auto const& glyph_or_emoji : 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; auto transformed_glyph = glyph_or_emoji;
transformed_glyph.visit([&](auto& glyph) { 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>()) { if (transformed_glyph.has<Gfx::DrawGlyph>()) {
auto& glyph = transformed_glyph.get<Gfx::DrawGlyph>(); auto& glyph = transformed_glyph.get<Gfx::DrawGlyph>();
auto const& point = glyph.position; auto const& point = glyph.position;
auto const& code_point = glyph.code_point; auto const& code_point = glyph.code_point;
auto top_left = point + Gfx::FloatPoint(scaled_font->glyph_left_bearing(code_point), 0); glyphs.append(gfx_typeface.glyph_id_for_code_point(code_point));
auto glyph_position = Gfx::GlyphRasterPosition::get_nearest_fit_for(top_left); positions.append(to_skia_point(point));
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();
}
} }
} }
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) void DisplayListPlayerSkia::fill_rect(FillRect const& command)