diff --git a/libs/gfx/include/psemek/gfx/painter.hpp b/libs/gfx/include/psemek/gfx/painter.hpp index 3b0406d7..6b004fe1 100644 --- a/libs/gfx/include/psemek/gfx/painter.hpp +++ b/libs/gfx/include/psemek/gfx/painter.hpp @@ -16,6 +16,34 @@ namespace psemek::gfx { using color = color_rgba; + enum class font + { + font_9x12, + }; + + enum class x_align + { + left, + center, + right, + }; + + enum class y_align + { + top, + center, + bottom, + }; + + struct text_options + { + font f = font::font_9x12; + float scale = 1.f; + x_align x = x_align::center; + y_align y = y_align::center; + color c = {255, 255, 255, 255}; + }; + painter(); ~painter(); @@ -25,6 +53,9 @@ namespace psemek::gfx void circle(geom::point const & center, float radius, color const & c); void line(geom::point const & p0, geom::point const & p1, float width, color const & c, bool smooth = true); + geom::vector text_size(std::string_view str, font f = font::font_9x12, float scale = 1.f); + void text(geom::point const & p, std::string_view str, text_options const & opts); + // Should be called on each frame void render(geom::matrix const & transform); diff --git a/libs/gfx/source/painter.cpp b/libs/gfx/source/painter.cpp index 87019d5f..60d9c0a6 100644 --- a/libs/gfx/source/painter.cpp +++ b/libs/gfx/source/painter.cpp @@ -1,7 +1,10 @@ #include #include #include +#include +#include #include +#include static const char vertex_source[] = R"(#version 330 @@ -33,6 +36,42 @@ void main() } )"; +static const char text_vertex_source[] = +R"(#version 330 + +uniform mat4 u_transform; + +layout (location = 0) in vec4 in_position; +layout (location = 1) in vec4 in_color; +layout (location = 2) in vec2 in_texcoord; + +out vec4 color; +out vec2 texcoord; + +void main() +{ + gl_Position = u_transform * in_position; + color = in_color; + texcoord = in_texcoord; +} +)"; + +static const char text_fragment_source[] = +R"(#version 330 + +uniform sampler2D u_texture; + +in vec4 color; +in vec2 texcoord; + +out vec4 out_color; + +void main() +{ + out_color = color * texture(u_texture, texcoord); +} +)"; + namespace psemek::gfx { @@ -44,15 +83,39 @@ namespace psemek::gfx color_rgba color; }; + struct text_vertex + { + geom::point position; + color_rgba color; + geom::point texcoord; + }; + gfx::program program{vertex_source, fragment_source}; + gfx::program text_program{text_vertex_source, text_fragment_source}; gfx::indexed_mesh mesh; + gfx::indexed_mesh text_mesh; std::vector vertices; std::vector indices; + std::vector text_vertices; + std::vector text_indices; + + gfx::texture_2d font_texture; + impl() { mesh.setup, gfx::normalized>(); + text_mesh.setup, gfx::normalized, gfx::normalized>>(); + + util::memory_istream font_data(resource::font_9x12); + font_texture.load(gfx::read_pbm(font_data)); + + font_texture.bind(); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_SWIZZLE_G, gl::RED); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_SWIZZLE_B, gl::RED); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_SWIZZLE_A, gl::RED); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST); } }; @@ -140,15 +203,114 @@ namespace psemek::gfx } } + geom::vector painter::text_size(std::string_view str, font f, float scale) + { + // TODO: multiline text + geom::vector s; + switch (f) + { + case font::font_9x12: + s = {9.f, 12.f}; + break; + default: + throw std::runtime_error("Unknown font"); + } + + s[0] *= str.size() * scale; + s[1] *= scale; + return s; + } + + void painter::text(geom::point const & p, std::string_view str, text_options const & opts) + { + auto const size = text_size(str, opts.f, opts.scale); + + auto pen = p; + + switch (opts.x) + { + case x_align::left: + break; + case x_align::center: + pen[0] -= size[0] / 2.f; + break; + case x_align::right: + pen[0] -= size[0]; + break; + default: + throw std::runtime_error("Unknown x alignment"); + } + + switch (opts.y) + { + case y_align::top: + break; + case y_align::center: + pen[1] -= size[1] / 2.f; + break; + case y_align::bottom: + pen[1] -= size[1]; + break; + default: + throw std::runtime_error("Unknown y alignment"); + } + + geom::vector const sx = {9.f * opts.scale, 0.f}; + geom::vector const sy = {0.f, 12.f * opts.scale}; + + auto to_texcoord = [](int tx, int ty) + { + return geom::point{ (tx * 65535) / 16, (ty * 65535) / 6 }; + }; + + for (char c : str) + { + if (c < 32 || c > 128) c = '?'; + + int ty = (c - 32) / 16; + int tx = (c - 32) % 16; + + std::uint32_t const base = impl().text_vertices.size(); + + geom::point position; + color_rgba color; + geom::point texcoord; + + impl().text_vertices.push_back({pen, opts.c, to_texcoord(tx, ty)}); + impl().text_vertices.push_back({pen + sx, opts.c, to_texcoord(tx + 1, ty)}); + impl().text_vertices.push_back({pen + sy, opts.c, to_texcoord(tx, ty + 1)}); + impl().text_vertices.push_back({pen + sx + sy, opts.c, to_texcoord(tx + 1, ty + 1)}); + + impl().text_indices.push_back(base + 0); + impl().text_indices.push_back(base + 1); + impl().text_indices.push_back(base + 3); + impl().text_indices.push_back(base + 0); + impl().text_indices.push_back(base + 3); + impl().text_indices.push_back(base + 2); + + pen += sx; + } + } + void painter::render(geom::matrix const & transform) { impl().mesh.load(impl().vertices, impl().indices, gl::STREAM_DRAW); impl().vertices.clear(); impl().indices.clear(); + impl().text_mesh.load(impl().text_vertices, impl().text_indices, gl::STREAM_DRAW); + impl().text_vertices.clear(); + impl().text_indices.clear(); + impl().program.bind(); impl().program["u_transform"] = transform; impl().mesh.draw(gl::TRIANGLES); + + impl().text_program.bind(); + impl().text_program["u_transform"] = transform; + impl().text_program["u_texture"] = 0; + impl().font_texture.bind(); + impl().text_mesh.draw(gl::TRIANGLES); } }