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.
Author: https://github.com/kalenikaliaksandr Commit: https://github.com/LadybirdBrowser/ladybird/commit/1bd0871ed88 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/845
22
Meta/CMake/skia.cmake
Normal 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()
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 267 KiB After Width: | Height: | Size: 268 KiB |
Before Width: | Height: | Size: 115 KiB After Width: | Height: | Size: 107 KiB |
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 656 KiB After Width: | Height: | Size: 674 KiB |
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 78 KiB |
@ -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/")
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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 };
|
||||
|
21
Userland/Libraries/LibGfx/Font/ScaledFontSkia.cpp
Normal 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 };
|
||||
}
|
||||
|
||||
}
|
@ -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>
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
45
Userland/Libraries/LibGfx/Font/TypefaceSkia.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
|
@ -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)
|
||||
|