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); + } }; }