From 32ea11d45c67b32d70c7c93cd8f9916ccf1786a3 Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Sun, 5 Nov 2023 00:25:12 +0100 Subject: [PATCH] LibAccelGfx: Introduce glyph run painting support Text painting operates in two steps: 1. Preparation of a texture that contains all the glyphs required for text painting, along with metadata that describes the locations of those glyphs within texture beforehand. 2. Blitting glyphs from the prepared texture onto corresponding glyph quads. Users of LibAccelGfx will need to call `prepare_glyphs_texture()`, passing a set of all unique (font, code_paint) pairs, before painting any text. --- Userland/Libraries/LibAccelGfx/Painter.cpp | 173 +++++++++++++++++++-- Userland/Libraries/LibAccelGfx/Painter.h | 34 ++++ 2 files changed, 196 insertions(+), 11 deletions(-) diff --git a/Userland/Libraries/LibAccelGfx/Painter.cpp b/Userland/Libraries/LibAccelGfx/Painter.cpp index 7d2a6d2672..af98d9f090 100644 --- a/Userland/Libraries/LibAccelGfx/Painter.cpp +++ b/Userland/Libraries/LibAccelGfx/Painter.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2023, Andreas Kling + * Copyright (c) 2023, Aliaksandr Kalenik * * SPDX-License-Identifier: BSD-2-Clause */ @@ -8,9 +9,11 @@ #include "Painter.h" #include "Canvas.h" +#include #include #include #include +#include namespace AccelGfx { @@ -59,20 +62,20 @@ void main() { char const* blit_vertex_shader_source = R"( attribute vec4 aVertexPosition; -attribute vec2 aTextureCoord; varying vec2 vTextureCoord; void main() { - gl_Position = aVertexPosition; - vTextureCoord = aTextureCoord; + gl_Position = vec4(aVertexPosition.xy, 0.0, 1.0); + vTextureCoord = aVertexPosition.zw; } )"; char const* blit_fragment_shader_source = R"( precision mediump float; +uniform vec4 uColor; varying vec2 vTextureCoord; uniform sampler2D uSampler; void main() { - gl_FragColor = texture2D(uSampler, vTextureCoord); + gl_FragColor = texture2D(uSampler, vTextureCoord) * uColor; } )"; @@ -88,6 +91,8 @@ Painter::Painter(Context& context) , m_blit_program(Program::create(blit_vertex_shader_source, blit_fragment_shader_source)) { m_state_stack.empend(State()); + + glGenTextures(1, &m_glyphs_texture); } Painter::~Painter() @@ -181,7 +186,7 @@ void Painter::draw_scaled_bitmap(Gfx::FloatRect const& dst_rect, Gfx::Bitmap con // FIXME: We should reuse textures across repaints if possible. glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bitmap.width(), bitmap.height(), 0, GL_BGRA, GL_UNSIGNED_BYTE, bitmap.scanline(0)); + glTexImage2D(GL_TEXTURE_2D, 0, GL_BGRA, bitmap.width(), bitmap.height(), 0, GL_BGRA, GL_UNSIGNED_BYTE, bitmap.scanline(0)); GLenum scaling_mode_gl = to_gl_scaling_mode(scaling_mode); @@ -190,16 +195,30 @@ void Painter::draw_scaled_bitmap(Gfx::FloatRect const& dst_rect, Gfx::Bitmap con glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, scaling_mode_gl); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, scaling_mode_gl); - auto vertices = rect_to_vertices(to_clip_space(transform().map(dst_rect))); - auto texture_coordinates = rect_to_vertices(to_texture_space(src_rect, bitmap.size())); + auto dst_rect_in_clip_space = to_clip_space(transform().map(dst_rect)); + auto src_rect_in_texture_space = to_texture_space(src_rect, bitmap.size()); + + Vector vertices; + vertices.ensure_capacity(16); + + auto add_vertex = [&](auto const& p, auto const& s) { + vertices.append(p.x()); + vertices.append(p.y()); + vertices.append(s.x()); + vertices.append(s.y()); + }; + + add_vertex(dst_rect_in_clip_space.top_left(), src_rect_in_texture_space.top_left()); + add_vertex(dst_rect_in_clip_space.bottom_left(), src_rect_in_texture_space.bottom_left()); + add_vertex(dst_rect_in_clip_space.bottom_right(), src_rect_in_texture_space.bottom_right()); + add_vertex(dst_rect_in_clip_space.top_right(), src_rect_in_texture_space.top_right()); GLuint vertex_position_attribute = m_blit_program.get_attribute_location("aVertexPosition"); - glVertexAttribPointer(vertex_position_attribute, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), vertices.data()); + glVertexAttribPointer(vertex_position_attribute, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), vertices.data()); glEnableVertexAttribArray(vertex_position_attribute); - GLuint texture_coord_attribute = m_blit_program.get_attribute_location("aTextureCoord"); - glVertexAttribPointer(texture_coord_attribute, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), texture_coordinates.data()); - glEnableVertexAttribArray(texture_coord_attribute); + GLuint color_uniform = m_blit_program.get_uniform_location("uColor"); + glUniform4f(color_uniform, 1, 1, 1, 1); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); @@ -208,6 +227,138 @@ void Painter::draw_scaled_bitmap(Gfx::FloatRect const& dst_rect, Gfx::Bitmap con glDeleteTextures(1, &texture); } +void Painter::prepare_glyph_texture(HashMap> const& unique_glyphs) +{ + HashMap> glyph_bitmaps; + for (auto const& [font, code_points] : unique_glyphs) { + for (auto const& code_point : code_points) { + auto glyph = font->glyph(code_point); + auto atlas_key = GlyphsTextureKey { font, code_point }; + glyph_bitmaps.set(atlas_key, *glyph.bitmap()); + } + } + + if (glyph_bitmaps.is_empty()) + return; + + Vector 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; + 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; + row_height = 0; + } + m_glyphs_texture_map.set(glyphs_texture_key, { current_x, current_y, bitmap->width(), bitmap->height() }); + current_x += bitmap->width(); + 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(); + + glBindTexture(GL_TEXTURE_2D, m_glyphs_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_BGRA, glyphs_texture_bitmap->width(), glyphs_texture_bitmap->height(), 0, GL_BGRA, GL_UNSIGNED_BYTE, glyphs_texture_bitmap->scanline(0)); +} + +void Painter::draw_glyph_run(Vector const& glyph_run, Color const& color) +{ + Vector vertices; + + for (auto& glyph_or_emoji : glyph_run) { + if (glyph_or_emoji.has()) { + auto& glyph = glyph_or_emoji.get(); + + 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 }); + VERIFY(maybe_texture_rect.has_value()); + + auto texture_rect = to_texture_space(maybe_texture_rect.value().to_type(), m_glyphs_texture_size); + + auto glyph_position = point + Gfx::FloatPoint(font->glyph_left_bearing(code_point), 0); + auto glyph_size = maybe_texture_rect->size().to_type(); + auto rect_in_clip_space = to_clip_space({ glyph_position, glyph_size }); + + // p0 --- p1 + // | \ | + // | \ | + // | \ | + // p2 --- p3 + + auto p0 = rect_in_clip_space.top_left(); + auto p1 = rect_in_clip_space.top_right(); + auto p2 = rect_in_clip_space.bottom_left(); + auto p3 = rect_in_clip_space.bottom_right(); + + auto s0 = texture_rect.top_left(); + auto s1 = texture_rect.top_right(); + auto s2 = texture_rect.bottom_left(); + auto s3 = texture_rect.bottom_right(); + + auto add_triangle = [&](auto& p1, auto& p2, auto& p3, auto& s1, auto& s2, auto& s3) { + vertices.append(p1.x()); + vertices.append(p1.y()); + vertices.append(s1.x()); + vertices.append(s1.y()); + vertices.append(p2.x()); + vertices.append(p2.y()); + vertices.append(s2.x()); + vertices.append(s2.y()); + vertices.append(p3.x()); + vertices.append(p3.y()); + vertices.append(s3.x()); + vertices.append(s3.y()); + }; + + add_triangle(p0, p1, p3, s0, s1, s3); + add_triangle(p0, p3, p2, s0, s3, s2); + } + } + + auto [red, green, blue, alpha] = gfx_color_to_opengl_color(color); + + m_blit_program.use(); + + glBindTexture(GL_TEXTURE_2D, m_glyphs_texture); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + + GLuint position_attribute = m_blit_program.get_attribute_location("aVertexPosition"); + GLuint color_uniform = m_blit_program.get_uniform_location("uColor"); + + glUniform4f(color_uniform, red, green, blue, alpha); + + glVertexAttribPointer(position_attribute, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), vertices.data()); + glEnableVertexAttribArray(position_attribute); + + glDrawArrays(GL_TRIANGLES, 0, vertices.size() / 4); +} + void Painter::save() { m_state_stack.append(state()); diff --git a/Userland/Libraries/LibAccelGfx/Painter.h b/Userland/Libraries/LibAccelGfx/Painter.h index 2be0a966d0..f1ecdf2f64 100644 --- a/Userland/Libraries/LibAccelGfx/Painter.h +++ b/Userland/Libraries/LibAccelGfx/Painter.h @@ -1,18 +1,22 @@ /* * Copyright (c) 2023, Andreas Kling + * Copyright (c) 2023, Aliaksandr Kalenik * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once +#include #include #include #include #include #include #include +#include #include +#include namespace AccelGfx { @@ -45,6 +49,20 @@ public: void draw_scaled_bitmap(Gfx::FloatRect const& dst_rect, Gfx::Bitmap const&, Gfx::FloatRect const& src_rect, ScalingMode = ScalingMode::NearestNeighbor); void draw_scaled_bitmap(Gfx::IntRect const& dst_rect, Gfx::Bitmap const&, Gfx::IntRect const& src_rect, ScalingMode = ScalingMode::NearestNeighbor); + void prepare_glyph_texture(HashMap> 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 const& glyph_run, Color const& color); + void set_canvas(Canvas& canvas) { m_canvas = canvas; } void flush(); @@ -65,6 +83,22 @@ private: Program m_rectangle_program; Program m_blit_program; + + HashMap m_glyphs_texture_map; + Gfx::IntSize m_glyphs_texture_size; + GLuint m_glyphs_texture; +}; + +} + +namespace AK { + +template<> +struct Traits : public GenericTraits { + static unsigned hash(AccelGfx::Painter::GlyphsTextureKey const& key) + { + return pair_int_hash(ptr_hash(key.font), key.code_point); + } }; }