LibAccelGfx+LibWeb: Move glyph atlas into a singleton class

This is going to allow us reuse glyph texture across painters and page
repaints.
This commit is contained in:
Aliaksandr Kalenik 2023-11-26 15:22:06 +01:00 committed by Andreas Kling
parent 4b23046a22
commit 28723d8be1
Notes: sideshowbarker 2024-07-17 01:12:07 +09:00
6 changed files with 157 additions and 105 deletions

View File

@ -3,6 +3,7 @@ include(accelerated_graphics)
if (HAS_ACCELERATED_GRAPHICS)
set(SOURCES
GL.cpp
GlyphAtlas.cpp
Context.cpp
Painter.cpp
Program.cpp

View File

@ -0,0 +1,81 @@
/*
* Copyright (c) 2023, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/QuickSort.h>
#include <LibAccelGfx/GlyphAtlas.h>
#include <LibGfx/Painter.h>
namespace AccelGfx {
GlyphAtlas& GlyphAtlas::the()
{
static OwnPtr<GlyphAtlas> s_the;
if (!s_the)
s_the = make<GlyphAtlas>();
return *s_the;
}
void GlyphAtlas::update(HashMap<Gfx::Font const*, HashTable<u32>> const& unique_glyphs)
{
HashMap<GlyphsTextureKey, NonnullRefPtr<Gfx::Bitmap>> glyph_bitmaps;
for (auto const& [font, code_points] : unique_glyphs) {
for (auto const& code_point : code_points) {
auto glyph = font->glyph(code_point);
if (glyph.bitmap()) {
auto atlas_key = GlyphsTextureKey { font, code_point };
glyph_bitmaps.set(atlas_key, *glyph.bitmap());
}
}
}
if (glyph_bitmaps.is_empty())
return;
Vector<GlyphsTextureKey> glyphs_sorted_by_height;
glyphs_sorted_by_height.ensure_capacity(glyph_bitmaps.size());
for (auto const& [atlas_key, bitmap] : glyph_bitmaps) {
glyphs_sorted_by_height.append(atlas_key);
}
quick_sort(glyphs_sorted_by_height, [&](auto const& a, auto const& b) {
auto const& bitmap_a = *glyph_bitmaps.get(a);
auto const& bitmap_b = *glyph_bitmaps.get(b);
return bitmap_a->height() > bitmap_b->height();
});
int current_x = 0;
int current_y = 0;
int row_height = 0;
int const texture_width = 512;
int const padding = 1;
for (auto const& glyphs_texture_key : glyphs_sorted_by_height) {
auto const& bitmap = *glyph_bitmaps.get(glyphs_texture_key);
if (current_x + bitmap->width() > texture_width) {
current_x = 0;
current_y += row_height + padding;
row_height = 0;
}
m_glyphs_texture_map.set(glyphs_texture_key, { current_x, current_y, bitmap->width(), bitmap->height() });
current_x += bitmap->width() + padding;
row_height = max(row_height, bitmap->height());
}
auto glyphs_texture_bitmap = MUST(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, { texture_width, current_y + row_height }));
auto glyphs_texture_painter = Gfx::Painter(*glyphs_texture_bitmap);
for (auto const& [glyphs_texture_key, glyph_bitmap] : glyph_bitmaps) {
auto rect = m_glyphs_texture_map.get(glyphs_texture_key).value();
glyphs_texture_painter.blit({ rect.x(), rect.y() }, glyph_bitmap, glyph_bitmap->rect());
}
GL::upload_texture_data(m_texture, *glyphs_texture_bitmap);
}
Optional<Gfx::IntRect> GlyphAtlas::get_glyph_rect(Gfx::Font const* font, u32 code_point) const
{
auto atlas_key = GlyphsTextureKey { font, code_point };
return m_glyphs_texture_map.get(atlas_key);
}
}

View File

@ -0,0 +1,64 @@
/*
* Copyright (c) 2023, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/HashMap.h>
#include <AK/Noncopyable.h>
#include <LibAccelGfx/GL.h>
#include <LibGfx/Font/Font.h>
namespace AccelGfx {
class GlyphAtlas {
AK_MAKE_NONCOPYABLE(GlyphAtlas);
public:
GlyphAtlas()
: m_texture(GL::create_texture())
{
}
~GlyphAtlas()
{
GL::delete_texture(m_texture);
}
static GlyphAtlas& the();
struct GlyphsTextureKey {
Gfx::Font const* font;
u32 code_point;
bool operator==(GlyphsTextureKey const& other) const
{
return font == other.font && code_point == other.code_point;
}
};
void update(HashMap<Gfx::Font const*, HashTable<u32>> const& unique_glyphs);
Optional<Gfx::IntRect> get_glyph_rect(Gfx::Font const*, u32 code_point) const;
GL::Texture const& texture() const { return m_texture; }
private:
GL::Texture m_texture;
HashMap<GlyphsTextureKey, Gfx::IntRect> m_glyphs_texture_map;
};
}
namespace AK {
template<>
struct Traits<AccelGfx::GlyphAtlas::GlyphsTextureKey> : public DefaultTraits<AccelGfx::GlyphAtlas::GlyphsTextureKey> {
static unsigned hash(AccelGfx::GlyphAtlas::GlyphsTextureKey const& key)
{
return pair_int_hash(ptr_hash(key.font), key.code_point);
}
};
}

View File

@ -5,10 +5,8 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/QuickSort.h>
#include <LibAccelGfx/GL.h>
#include <LibAccelGfx/Painter.h>
#include <LibGfx/Color.h>
#include <LibGfx/ImmutableBitmap.h>
#include <LibGfx/Painter.h>
@ -145,23 +143,12 @@ OwnPtr<Painter> Painter::create()
return make<Painter>(context);
}
OwnPtr<Painter> Painter::create_with_glyphs_texture_from_painter(Painter const& painter)
{
auto& context = Context::the();
auto glyphs_texture_painter = make<Painter>(context);
glyphs_texture_painter->m_glyphs_texture = painter.m_glyphs_texture;
glyphs_texture_painter->m_glyphs_texture_size = painter.m_glyphs_texture_size;
glyphs_texture_painter->m_glyphs_texture_map = painter.m_glyphs_texture_map;
return glyphs_texture_painter;
}
Painter::Painter(Context& context)
: m_context(context)
, m_rectangle_program(Program::create(Program::Name::RectangleProgram, vertex_shader_source, solid_color_fragment_shader_source))
, m_rounded_rectangle_program(Program::create(Program::Name::RoundedRectangleProgram, vertex_shader_source, rect_with_rounded_corners_fragment_shader_source))
, m_blit_program(Program::create(Program::Name::BlitProgram, blit_vertex_shader_source, blit_fragment_shader_source))
, m_linear_gradient_program(Program::create(Program::Name::LinearGradientProgram, linear_gradient_vertex_shader_source, linear_gradient_fragment_shader_source))
, m_glyphs_texture(GL::create_texture())
{
m_state_stack.empend(State());
}
@ -365,62 +352,6 @@ void Painter::draw_scaled_bitmap(Gfx::FloatRect const& dst_rect, Gfx::Bitmap con
GL::delete_texture(texture);
}
void Painter::prepare_glyph_texture(HashMap<Gfx::Font const*, HashTable<u32>> const& unique_glyphs)
{
HashMap<GlyphsTextureKey, NonnullRefPtr<Gfx::Bitmap>> glyph_bitmaps;
for (auto const& [font, code_points] : unique_glyphs) {
for (auto const& code_point : code_points) {
auto glyph = font->glyph(code_point);
if (glyph.bitmap()) {
auto atlas_key = GlyphsTextureKey { font, code_point };
glyph_bitmaps.set(atlas_key, *glyph.bitmap());
}
}
}
if (glyph_bitmaps.is_empty())
return;
Vector<GlyphsTextureKey> glyphs_sorted_by_height;
glyphs_sorted_by_height.ensure_capacity(glyph_bitmaps.size());
for (auto const& [atlas_key, bitmap] : glyph_bitmaps) {
glyphs_sorted_by_height.append(atlas_key);
}
quick_sort(glyphs_sorted_by_height, [&](auto const& a, auto const& b) {
auto const& bitmap_a = *glyph_bitmaps.get(a);
auto const& bitmap_b = *glyph_bitmaps.get(b);
return bitmap_a->height() > bitmap_b->height();
});
int current_x = 0;
int current_y = 0;
int row_height = 0;
int texture_width = 512;
int padding = 1;
for (auto const& glyphs_texture_key : glyphs_sorted_by_height) {
auto const& bitmap = *glyph_bitmaps.get(glyphs_texture_key);
if (current_x + bitmap->width() > texture_width) {
current_x = 0;
current_y += row_height + padding;
row_height = 0;
}
m_glyphs_texture_map.set(glyphs_texture_key, { current_x, current_y, bitmap->width(), bitmap->height() });
current_x += bitmap->width() + padding;
row_height = max(row_height, bitmap->height());
}
auto glyphs_texture_bitmap = MUST(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, { texture_width, current_y + row_height }));
auto glyphs_texure_painter = Gfx::Painter(*glyphs_texture_bitmap);
for (auto const& [glyphs_texture_key, glyph_bitmap] : glyph_bitmaps) {
auto rect = m_glyphs_texture_map.get(glyphs_texture_key).value();
glyphs_texure_painter.blit({ rect.x(), rect.y() }, glyph_bitmap, glyph_bitmap->rect());
}
m_glyphs_texture_size = glyphs_texture_bitmap->size();
GL::upload_texture_data(m_glyphs_texture, *glyphs_texture_bitmap);
}
void Painter::draw_glyph_run(Vector<Gfx::DrawGlyphOrEmoji> const& glyph_run, Color const& color)
{
bind_target_canvas();
@ -428,20 +359,22 @@ void Painter::draw_glyph_run(Vector<Gfx::DrawGlyphOrEmoji> const& glyph_run, Col
Vector<GLfloat> vertices;
vertices.ensure_capacity(glyph_run.size() * 24);
for (auto& glyph_or_emoji : glyph_run) {
auto const& glyph_atlas = GlyphAtlas::the();
for (auto const& glyph_or_emoji : glyph_run) {
if (glyph_or_emoji.has<Gfx::DrawGlyph>()) {
auto& glyph = glyph_or_emoji.get<Gfx::DrawGlyph>();
auto const& glyph = glyph_or_emoji.get<Gfx::DrawGlyph>();
auto const* font = glyph.font;
auto code_point = glyph.code_point;
auto point = glyph.position;
auto maybe_texture_rect = m_glyphs_texture_map.get(GlyphsTextureKey { font, code_point });
auto maybe_texture_rect = glyph_atlas.get_glyph_rect(font, code_point);
if (!maybe_texture_rect.has_value()) {
continue;
}
auto texture_rect = to_texture_space(maybe_texture_rect.value().to_type<float>(), m_glyphs_texture_size);
auto texture_rect = to_texture_space(maybe_texture_rect.value().to_type<float>(), *glyph_atlas.texture().size);
auto glyph_position = point + Gfx::FloatPoint(font->glyph_left_bearing(code_point), 0);
auto glyph_size = maybe_texture_rect->size().to_type<float>();
@ -497,7 +430,7 @@ void Painter::draw_glyph_run(Vector<Gfx::DrawGlyphOrEmoji> const& glyph_run, Col
m_blit_program.use();
GL::bind_texture(m_glyphs_texture);
GL::bind_texture(glyph_atlas.texture());
GL::set_texture_scale_mode(GL::ScalingMode::Nearest);
auto position_attribute = m_blit_program.get_attribute_location("aVertexPosition");

View File

@ -14,6 +14,7 @@
#include <LibAccelGfx/Context.h>
#include <LibAccelGfx/Forward.h>
#include <LibAccelGfx/GL.h>
#include <LibAccelGfx/GlyphAtlas.h>
#include <LibAccelGfx/Program.h>
#include <LibGfx/AffineTransform.h>
#include <LibGfx/Font/Font.h>
@ -29,7 +30,6 @@ class Painter {
public:
static OwnPtr<Painter> create();
static OwnPtr<Painter> create_with_glyphs_texture_from_painter(Painter const& painter);
Painter(Context&);
~Painter();
@ -60,18 +60,6 @@ public:
void draw_scaled_immutable_bitmap(Gfx::IntRect const& dst_rect, Gfx::ImmutableBitmap const&, Gfx::IntRect const& src_rect, ScalingMode = ScalingMode::NearestNeighbor);
void draw_scaled_immutable_bitmap(Gfx::FloatRect const& dst_rect, Gfx::ImmutableBitmap const&, Gfx::FloatRect const& src_rect, ScalingMode = ScalingMode::NearestNeighbor);
void prepare_glyph_texture(HashMap<Gfx::Font const*, HashTable<u32>> const& unique_glyphs);
struct GlyphsTextureKey {
Gfx::Font const* font;
u32 code_point;
bool operator==(GlyphsTextureKey const& other) const
{
return font == other.font && code_point == other.code_point;
}
};
void draw_glyph_run(Vector<Gfx::DrawGlyphOrEmoji> const& glyph_run, Color const& color);
void set_clip_rect(Gfx::IntRect);
@ -118,22 +106,6 @@ private:
Program m_rounded_rectangle_program;
Program m_blit_program;
Program m_linear_gradient_program;
HashMap<GlyphsTextureKey, Gfx::IntRect> m_glyphs_texture_map;
Gfx::IntSize m_glyphs_texture_size;
GL::Texture m_glyphs_texture;
};
}
namespace AK {
template<>
struct Traits<AccelGfx::Painter::GlyphsTextureKey> : public DefaultTraits<AccelGfx::Painter::GlyphsTextureKey> {
static unsigned hash(AccelGfx::Painter::GlyphsTextureKey const& key)
{
return pair_int_hash(ptr_hash(key.font), key.code_point);
}
};
}

View File

@ -4,6 +4,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibAccelGfx/GlyphAtlas.h>
#include <LibWeb/Painting/PaintingCommandExecutorGPU.h>
namespace Web::Painting {
@ -92,7 +93,7 @@ CommandResult PaintingCommandExecutorGPU::set_font(Gfx::Font const&)
CommandResult PaintingCommandExecutorGPU::push_stacking_context(float opacity, bool, Gfx::IntRect const& source_paintable_rect, Gfx::IntPoint post_transform_translation, CSS::ImageRendering, StackingContextTransform, Optional<StackingContextMask>)
{
if (opacity < 1) {
auto painter = AccelGfx::Painter::create_with_glyphs_texture_from_painter(this->painter());
auto painter = AccelGfx::Painter::create();
auto canvas = AccelGfx::Canvas::create(source_paintable_rect.size());
painter->set_target_canvas(canvas);
painter->translate(-source_paintable_rect.location().to_type<float>());
@ -313,7 +314,7 @@ bool PaintingCommandExecutorGPU::would_be_fully_clipped_by_painter(Gfx::IntRect)
void PaintingCommandExecutorGPU::prepare_glyph_texture(HashMap<Gfx::Font const*, HashTable<u32>> const& unique_glyphs)
{
painter().prepare_glyph_texture(unique_glyphs);
AccelGfx::GlyphAtlas::the().update(unique_glyphs);
}
void PaintingCommandExecutorGPU::update_immutable_bitmap_texture_cache(HashMap<u32, Gfx::ImmutableBitmap const*>& immutable_bitmaps)