mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 05:37:43 +00:00
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.
This commit is contained in:
parent
efdbd8238e
commit
32ea11d45c
2 changed files with 196 additions and 11 deletions
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2023, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -8,9 +9,11 @@
|
|||
|
||||
#include "Painter.h"
|
||||
#include "Canvas.h"
|
||||
#include <AK/QuickSort.h>
|
||||
#include <GL/gl.h>
|
||||
#include <GL/glext.h>
|
||||
#include <LibGfx/Color.h>
|
||||
#include <LibGfx/Painter.h>
|
||||
|
||||
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<GLfloat> 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<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);
|
||||
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;
|
||||
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<Gfx::DrawGlyphOrEmoji> const& glyph_run, Color const& color)
|
||||
{
|
||||
Vector<GLfloat> vertices;
|
||||
|
||||
for (auto& glyph_or_emoji : glyph_run) {
|
||||
if (glyph_or_emoji.has<Gfx::DrawGlyph>()) {
|
||||
auto& 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 });
|
||||
VERIFY(maybe_texture_rect.has_value());
|
||||
|
||||
auto texture_rect = to_texture_space(maybe_texture_rect.value().to_type<float>(), 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<float>();
|
||||
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());
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue