|
@@ -1,5 +1,6 @@
|
|
/*
|
|
/*
|
|
* Copyright (c) 2023, Andreas Kling <kling@serenityos.org>
|
|
* Copyright (c) 2023, Andreas Kling <kling@serenityos.org>
|
|
|
|
+ * Copyright (c) 2023, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
|
|
*
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
*/
|
|
@@ -8,9 +9,11 @@
|
|
|
|
|
|
#include "Painter.h"
|
|
#include "Painter.h"
|
|
#include "Canvas.h"
|
|
#include "Canvas.h"
|
|
|
|
+#include <AK/QuickSort.h>
|
|
#include <GL/gl.h>
|
|
#include <GL/gl.h>
|
|
#include <GL/glext.h>
|
|
#include <GL/glext.h>
|
|
#include <LibGfx/Color.h>
|
|
#include <LibGfx/Color.h>
|
|
|
|
+#include <LibGfx/Painter.h>
|
|
|
|
|
|
namespace AccelGfx {
|
|
namespace AccelGfx {
|
|
|
|
|
|
@@ -59,20 +62,20 @@ void main() {
|
|
|
|
|
|
char const* blit_vertex_shader_source = R"(
|
|
char const* blit_vertex_shader_source = R"(
|
|
attribute vec4 aVertexPosition;
|
|
attribute vec4 aVertexPosition;
|
|
-attribute vec2 aTextureCoord;
|
|
|
|
varying vec2 vTextureCoord;
|
|
varying vec2 vTextureCoord;
|
|
void main() {
|
|
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"(
|
|
char const* blit_fragment_shader_source = R"(
|
|
precision mediump float;
|
|
precision mediump float;
|
|
|
|
+uniform vec4 uColor;
|
|
varying vec2 vTextureCoord;
|
|
varying vec2 vTextureCoord;
|
|
uniform sampler2D uSampler;
|
|
uniform sampler2D uSampler;
|
|
void main() {
|
|
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_blit_program(Program::create(blit_vertex_shader_source, blit_fragment_shader_source))
|
|
{
|
|
{
|
|
m_state_stack.empend(State());
|
|
m_state_stack.empend(State());
|
|
|
|
+
|
|
|
|
+ glGenTextures(1, &m_glyphs_texture);
|
|
}
|
|
}
|
|
|
|
|
|
Painter::~Painter()
|
|
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.
|
|
// FIXME: We should reuse textures across repaints if possible.
|
|
glGenTextures(1, &texture);
|
|
glGenTextures(1, &texture);
|
|
glBindTexture(GL_TEXTURE_2D, 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);
|
|
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_MIN_FILTER, scaling_mode_gl);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_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");
|
|
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);
|
|
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);
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
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);
|
|
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()
|
|
void Painter::save()
|
|
{
|
|
{
|
|
m_state_stack.append(state());
|
|
m_state_stack.append(state());
|