From 6f670b6cd9c7d5e08f067bff616ffecaf37b02fa Mon Sep 17 00:00:00 2001 From: rexy712 Date: Wed, 26 Jan 2022 16:43:31 -0800 Subject: [PATCH] Rendering some amount of text bby --- assets/hinted-RosaSans-Regular.ttf | Bin 0 -> 91576 bytes include/engine/flat_object.hpp | 62 ++++++++++ include/engine/font.hpp | 121 +++++++++++++----- include/engine/text.hpp | 72 +++++++++++ include/graphics/resource_manager.hpp | 13 ++ include/graphics/shader_program.hpp | 4 +- include/graphics/texture.hpp | 21 ++-- include/ttt/font_renderer.hpp | 133 +------------------- include/ttt/font_shader.hpp | 44 +++++-- include/ttt/pause_state.hpp | 5 + include/ttt/screen_renderer.hpp | 111 ----------------- src/engine/flat_object.cpp | 74 +++++++++++ src/engine/font.cpp | 170 ++++++++++++-------------- src/engine/text.cpp | 106 ++++++++++++++++ src/graphics/resource_manager.cpp | 10 ++ src/graphics/shader_program.cpp | 4 +- src/graphics/texture.cpp | 34 +++--- src/ttt/font_renderer.cpp | 26 +--- src/ttt/pause_state.cpp | 15 ++- 19 files changed, 597 insertions(+), 428 deletions(-) create mode 100644 assets/hinted-RosaSans-Regular.ttf create mode 100644 include/engine/flat_object.hpp create mode 100644 include/engine/text.hpp create mode 100644 src/engine/flat_object.cpp create mode 100644 src/engine/text.cpp diff --git a/assets/hinted-RosaSans-Regular.ttf b/assets/hinted-RosaSans-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..7435805781beb15c702216fb41b8539b7c46489d GIT binary patch literal 91576 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 literal 0 HcmV?d00001 diff --git a/include/engine/flat_object.hpp b/include/engine/flat_object.hpp new file mode 100644 index 0000000..1192e5e --- /dev/null +++ b/include/engine/flat_object.hpp @@ -0,0 +1,62 @@ +/** + This file is a part of our_dick + Copyright (C) 2022 rexy712 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +#ifndef OUR_DICK_ENGINE_FLAT_OBJECT_HPP +#define OUR_DICK_ENGINE_FLAT_OBJECT_HPP + +#include "math/vec.hpp" + +namespace egn{ + + namespace flat{ + + class object_base + { + protected: + math::vec2f m_position; + math::vec2f m_scale; + float m_orientation; + + public: + object_base(void) = default; + object_base(const math::vec2f& position, float orientation = 0); + object_base(const object_base&) = default; + object_base(object_base&&) = default; + ~object_base(void) = default; + + object_base& operator=(const object_base&) = default; + object_base& operator=(object_base&&) = default; + + void translate(const math::vec2f& distance); + void rotate(float distance); + void scale(const math::vec2f& distance); + void look_at(const math::vec2f& target); + + void set_position(const math::vec2f& pos); + void set_orientation(float orient); + void set_scale(const math::vec2f& scale); + + const math::vec2f& position(void)const; + float orientation(void)const; + const math::vec2f& scale(void)const; + }; + } + +} + +#endif diff --git a/include/engine/font.hpp b/include/engine/font.hpp index b8dd357..278ed8d 100644 --- a/include/engine/font.hpp +++ b/include/engine/font.hpp @@ -24,6 +24,8 @@ #include #include #include //pair +#include +#include //min, max #include "graphics/texture.hpp" #include "config.hpp" @@ -31,7 +33,6 @@ namespace egn{ struct font_character{ - GLuint atlas_texture; math::vec2 atlas_offset; math::vec2 size; math::vec2 bearing; @@ -42,35 +43,34 @@ namespace egn{ float aspect_ratio(void)const; }; - class font + class font_atlas : public gfx::texture { + public: + using map_type = std::map; + private: - std::optional m_atlas; - std::map m_atlas_data; - size_t m_requested_width; - size_t m_requested_height; + map_type m_atlas_data; + size_t m_requested_width, m_requested_height; public: - font(void) = default; - font(gfx::texture&& tex, std::map&& metadata, size_t width, size_t height); - ~font(void) = default; + font_atlas(gfx::texture&& t, map_type&& map, size_t w, size_t h); + font_atlas(const font_atlas&) = default; + font_atlas(font_atlas&&) = default; + ~font_atlas(void) = default; + + font_atlas& operator=(const font_atlas&) = default; + font_atlas& operator=(font_atlas&&) = default; + + map_type& metadata(void); + const map_type& metadata(void)const; + map_type&& release_metadata(void); font_character& operator[](int character); - const font_character& operator[](int character)const; - gfx::texture release_atlas(void); - - gfx::texture& atlas(void); - const gfx::texture& atlas(void)const; - - std::map release_metadata(void); - - bool valid(void)const; - - math::vec2 size(void)const; + math::vec2 glyph_size(void)const; }; - class font_generator + class font { private: static inline bool s_initialized = false; @@ -78,25 +78,84 @@ namespace egn{ private: FT_Face m_face = nullptr; + int m_depth = 3; public: - font_generator(const char* file); - ~font_generator(void); + font(const char* file, int depth = 3); + ~font(void); + + template + font_atlas generate_atlas(size_t glyph_w, size_t glyph_h, Ts&&... ranges); - //params: - // start_codepoint: unicode character at which the atlas starts - // count: number of characters to make in the atlas - // glyph_width: requested pixel width of glyph - // glyph_height: requested pixel height of glyph - // atlas_width: requested pixel width of atlas - // atlas_height: requested pixel height of atlas - font generate_atlas(int start_codepoint, int count, size_t glyph_width, size_t glyph_height); std::optional generate_glyph(char character, int width, int height); + private: + using atlas_meta = std::tuple; + template + void generate_atlas_piece_(gfx::texture& atlas, font_atlas::map_type& metadata, math::vec2& target_pos, const atlas_meta& info, unsigned char* data_buffer, const std::pair& range, Ts&&... ranges); + void generate_atlas_piece_impl_(gfx::texture& atlas, font_atlas::map_type& metadata, math::vec2& target_pos, const atlas_meta& info, unsigned char* data_buffer, const std::pair& range); + template + atlas_meta get_atlas_size_ranges_(Ts&&... ranges); + template + void get_atlas_size_ranges_recurse_(size_t& avg_w, size_t& avg_h, size_t& mgw, size_t& mgh, size_t& count, const std::pair& range, Ts&&... ranges); + void get_atlas_size_ranges_impl_(size_t& avg_w, size_t& avg_h, size_t& mgw, size_t& mgh, size_t& count, const std::pair& range); + private: static bool initialize_freetype_(void); }; + + + + template + void font::generate_atlas_piece_(gfx::texture& atlas, font_atlas::map_type& metadata, math::vec2& target_pos, const atlas_meta& info, unsigned char* data_buffer, const std::pair& range, Ts&&... ranges){ + generate_atlas_piece_impl_(atlas, metadata, target_pos, info, data_buffer, range); + + if constexpr(sizeof...(ranges) > 0){ + generate_atlas_piece_(atlas, metadata, target_pos, info, data_buffer, std::forward(ranges)...); + } + } + template + font_atlas font::generate_atlas(size_t glyph_w, size_t glyph_h, Ts&&... ranges){ + FT_Set_Pixel_Sizes(m_face, glyph_w, glyph_h); + auto [atlas_width, atlas_height, max_glyph_width, max_glyph_height] = get_atlas_size_ranges_(std::forward(ranges)...); + const auto format = (m_depth == 3) ? GL_RGB : GL_RED; + + + gfx::texture atlas(format, atlas_width, atlas_height, GL_UNSIGNED_BYTE, false); + font_atlas::map_type atlas_metadata; + math::vec2 target_position = {0, 0}; + std::unique_ptr dest_data(new unsigned char[max_glyph_height * max_glyph_width * m_depth]); + + generate_atlas_piece_(atlas, atlas_metadata, target_position, std::tuple{atlas_width, atlas_height, max_glyph_width, max_glyph_height}, dest_data.get(), std::forward(ranges)...); + return {std::move(atlas), std::move(atlas_metadata), glyph_w, glyph_h}; + } + template + auto font::get_atlas_size_ranges_(Ts&&... ranges) -> atlas_meta{ + size_t avg_width = 0; + size_t avg_height = 0; + size_t max_glyph_width = 0; + size_t max_glyph_height = 0; + size_t count = 0; + + get_atlas_size_ranges_recurse_(avg_width, avg_height, max_glyph_width, max_glyph_height, count, std::forward(ranges)...); + + avg_width /= count; + avg_height /= count; + const size_t est_width = avg_width * (std::min(size_t{20}, count) + 1); + const size_t est_height = (max_glyph_height * (std::round(count / 20) + 1)); + + return {est_width / m_depth, est_height, max_glyph_width / m_depth, max_glyph_height}; + + } + template + void font::get_atlas_size_ranges_recurse_(size_t& avg_w, size_t& avg_h, size_t& mgw, size_t& mgh, size_t& count, const std::pair& range, Ts&&... ranges){ + get_atlas_size_ranges_impl_(avg_w, avg_h, mgw, mgh, count, range); + if constexpr(sizeof...(ranges) > 0){ + get_atlas_size_ranges_recurse_(avg_w, avg_h, mgw, mgh, count, std::forward(ranges)...); + } + } + } #endif diff --git a/include/engine/text.hpp b/include/engine/text.hpp new file mode 100644 index 0000000..06d1454 --- /dev/null +++ b/include/engine/text.hpp @@ -0,0 +1,72 @@ +/** + This file is a part of our_dick + Copyright (C) 2022 rexy712 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +#ifndef OUR_DICK_ENGINE_TEXT_HPP +#define OUR_DICK_ENGINE_TEXT_HPP + +#include "graphics/vbo.hpp" +#include "graphics/vao.hpp" +#include "graphics/texture.hpp" +#include "graphics/shader_program.hpp" +#include "font.hpp" +#include "math/vec.hpp" +#include "flat_object.hpp" + +#include + +namespace egn{ + + class text_gfx + { + private: + static constexpr int s_size_per_char = sizeof(GLfloat) * 12; + private: + gfx::weak_texture m_atlas; + egn::font_atlas::map_type m_font; + gfx::vbo m_vbo; + gfx::vao m_vao; + size_t m_original_height; + float m_scale; + + public: + text_gfx(const egn::font_atlas& font, int target_px, size_t initsize = 0); + text_gfx(const egn::font_atlas& font, int target_px, const char* t); + + void set_text(const char* string); + + void render(gfx::shader_program& sh, const math::vec2f& pos); + }; + + + class text : public flat::object_base + { + private: + text_gfx m_gfx; + std::string m_text; + + public: + text(const egn::font_atlas& font, const char* string, int target_px, float target_x, float target_y); + + void set_text(const char* string); + + void render(gfx::shader_program& sh); + }; + +} + +#endif diff --git a/include/graphics/resource_manager.hpp b/include/graphics/resource_manager.hpp index 78804ef..8537847 100644 --- a/include/graphics/resource_manager.hpp +++ b/include/graphics/resource_manager.hpp @@ -27,6 +27,8 @@ #include "graphics/texture.hpp" #include "graphics/shader_program.hpp" +#include "engine/font.hpp" + namespace gfx{ namespace detail{ @@ -65,6 +67,7 @@ namespace gfx{ { private: detail::resource_manager m_textures; + detail::resource_manager m_fonts; detail::resource_manager m_texture_arrays; detail::resource_manager m_shaders; @@ -90,6 +93,12 @@ namespace gfx{ gfx::texture* get_texture(const char* file); bool erase_texture(const char* key); + bool has_font(const char* key)const; + template + auto emplace_font(const char* key, Args&&... args); + egn::font_atlas* get_font(const char* file); + bool erase_font(const char* key); + bool has_shader(const char* key)const; template auto emplace_shader(const char* key, Args&&... args); @@ -133,6 +142,10 @@ namespace gfx{ return m_textures.emplace_value(key, std::forward(args)...); } template + auto resource_manager::emplace_font(const char* key, Args&&... args){ + return m_fonts.emplace_value(key, std::forward(args)...); + } + template auto resource_manager::emplace_shader(const char* key, Args&&... args){ return m_shaders.emplace_value(key, std::forward(args)...); } diff --git a/include/graphics/shader_program.hpp b/include/graphics/shader_program.hpp index 65d83f6..be055bc 100644 --- a/include/graphics/shader_program.hpp +++ b/include/graphics/shader_program.hpp @@ -210,8 +210,8 @@ namespace gfx{ void set_uniform(GLuint loc, const texture&, GLuint tex_unit = 0); void set_uniform(const char* name, const texture_array& t, GLuint tex_unit = 0); void set_uniform(GLuint loc, const texture_array& t, GLuint tex_unit = 0); - void set_uniform(const char* name, const weak_texture_handle&, GLuint tex_unit = 0); - void set_uniform(GLuint loc, const weak_texture_handle&, GLuint tex_unit = 0); + void set_uniform(const char* name, const weak_texture&, GLuint tex_unit = 0); + void set_uniform(GLuint loc, const weak_texture&, GLuint tex_unit = 0); //Float vectors void set_uniform(const char* name, const vec2&); diff --git a/include/graphics/texture.hpp b/include/graphics/texture.hpp index 6832822..9686168 100644 --- a/include/graphics/texture.hpp +++ b/include/graphics/texture.hpp @@ -27,25 +27,26 @@ namespace gfx{ class texture; - class weak_texture_handle + class weak_texture { private: - const texture* m_texture; + GLuint m_texture; public: - weak_texture_handle(const texture& tex); - weak_texture_handle(const weak_texture_handle&) = default; - weak_texture_handle(weak_texture_handle&&) = default; - ~weak_texture_handle(void) = default; + weak_texture(GLuint tex); + weak_texture(const texture& tex); + weak_texture(const weak_texture&) = default; + weak_texture(weak_texture&&) = default; + ~weak_texture(void) = default; - weak_texture_handle& operator=(const weak_texture_handle&) = default; - weak_texture_handle& operator=(weak_texture_handle&&) = default; + weak_texture& operator=(const weak_texture&) = default; + weak_texture& operator=(weak_texture&&) = default; GLuint raw(void)const; GLsizei get_width(void)const; GLsizei get_height(void)const; - void bind(void)const; + void bind(GLuint target = GL_TEXTURE_2D)const; void bind_unit(GLuint tunit)const; }; @@ -152,7 +153,7 @@ namespace gfx{ //bind to given texture unit and load into given program uniform location void bind_unit(GLuint tunit)const; - weak_texture_handle create_handle(void)const; + weak_texture create_handle(void)const; private: bool create_texture_storage_(void); diff --git a/include/ttt/font_renderer.hpp b/include/ttt/font_renderer.hpp index be7b817..83e241c 100644 --- a/include/ttt/font_renderer.hpp +++ b/include/ttt/font_renderer.hpp @@ -28,145 +28,18 @@ #include "scene.hpp" #include "engine/font.hpp" - -static constexpr float pixels_to_screen_space(int dim, int window_dim){ - const float ratio = 2.0f / window_dim; - return dim * ratio; -} -static constexpr float screen_space_to_pixels(int dim, int window_dim){ - const float ratio = window_dim / 2.0f; - return dim * ratio; -} -static constexpr float font_scale_ratio(int height, int target_height){ - return ((float)target_height) / height; -} -static constexpr float font_aspect_ratio(const egn::font_character& ch){ - return ((float)ch.size.x()) / ch.size.y(); -} - - -class text -{ -private: - static constexpr int s_size_per_char = sizeof(GLfloat) * 13; -private: - egn::font m_font; - std::string m_text; - gfx::vbo m_vbo; - gfx::vao m_vao; - - float m_scale; - -public: - text(egn::font& font, const char* string, int target_px, float target_x, float target_y, int screen_width, int screen_height): - m_font(font), - m_text(string), - m_vbo(s_size_per_char * strlen(string), gfx::vbo::usage::DYNAMIC_DRAW) - { - constexpr float tmp_color[] = {1,1,1,1}; - - m_scale = font_scale_ratio(m_font.size().y(), target_px); - - math::vec3f target_pos = {target_x, target_y, -1}; - size_t offset = 0; - for(int i = 0;m_text[i] != 0;++i){ - const auto ch = m_font[m_text[i]]; - const math::vec2 letter_size = ch.size * m_scale; - const math::vec2f letter_screen_size = {pixels_to_screen_space(letter_size.x(), screen_width), pixels_to_screen_space(letter_size.y(), screen_height)}; - const math::vec3f position = {target_pos.x() + pixels_to_screen_space(ch.bearing.x() * m_scale, screen_width), - target_pos.y() - (letter_screen_size.y() - pixels_to_screen_space(ch.bearing.y() * m_scale, screen_height)), - target_pos.z()}; - - - m_vbo.buffer(ch.texture_coords[1], sizeof(GLfloat) * 2, offset); - offset += sizeof(GLfloat) * 2; - m_vbo.buffer(ch.texture_coords[2], sizeof(GLfloat) * 2, offset); - offset += sizeof(GLfloat) * 2; - m_vbo.buffer(tmp_color, sizeof(GLfloat) * 4, offset); - offset += sizeof(GLfloat) * 4; - m_vbo.buffer(position, sizeof(GLfloat) * 3, offset); - offset += sizeof(GLfloat) * 3; - m_vbo.buffer(letter_screen_size, sizeof(GLfloat) * 2, offset); - offset += sizeof(GLfloat) * 2; - - target_pos.x() += pixels_to_screen_space(m_scale * ch.advance.x(), screen_width); - } - - //attach a vbo to the vao and assign attribute specs - m_vao.bind_buffer(m_vbo, 0, 0, sizeof(GLfloat) * 13); - - auto attrib = m_vao.get_attribute(0); - attrib.set_float_array(4, 0); - attrib.associate_with(0); - attrib.enable(); - - attrib = m_vao.get_attribute(1); - attrib.set_float_array(4, sizeof(GLfloat) * 4); - attrib.associate_with(0); - attrib.enable(); - - attrib = m_vao.get_attribute(2); - attrib.set_float_array(3, sizeof(GLfloat) * 8); - attrib.associate_with(0); - attrib.enable(); - - attrib = m_vao.get_attribute(3); - attrib.set_float_array(2, sizeof(GLfloat) * 11); - attrib.associate_with(0); - attrib.enable(); - - } - void set_position(float x, float y, int screen_width, int screen_height){ - size_t offset = sizeof(GLfloat) * 8; - math::vec3f target_pos = {x, y, -1}; - for(size_t i = 0;m_text[i] != 0;++i){ - const auto ch = m_font[m_text[i]]; - const math::vec2i letter_size = ch.size * m_scale; - const math::vec2f letter_screen_size = {pixels_to_screen_space(letter_size.x(), screen_width), pixels_to_screen_space(letter_size.y(), screen_height)}; - const math::vec3f position = {target_pos.x() + pixels_to_screen_space(ch.bearing.x() * m_scale, screen_width), - target_pos.y() - (letter_screen_size.y() - pixels_to_screen_space(ch.bearing.y() * m_scale, screen_height)), - target_pos.z()}; - - m_vbo.buffer(position, sizeof(GLfloat) * 3, offset); - offset += sizeof(GLfloat) * 3 + sizeof(GLfloat) * 8; - target_pos.x() += pixels_to_screen_space(m_scale * ch.advance.x(), screen_width); - } - } - - void render(gfx::shader_program& sh){ - m_vao.bind(); - sh.set_uniform("texture1", m_font.atlas()); - glDrawArrays(GL_POINTS, 0, m_text.length()); - } -}; +#include "engine/text.hpp" class font_renderer { -private: - struct vertex{ - math::vec2 pos; - math::vec2 tex_coord; - }; - static constexpr vertex s_vertices[] = { - {{-1.0f, 1.0f}, {0.0f, 1.0f}}, - {{-1.0f, -1.0f}, {0.0f, 0.0f}}, - {{ 1.0f, -1.0f}, {1.0f, 0.0f}}, - {{ 1.0f, -1.0f}, {1.0f, 0.0f}}, - {{ 1.0f, 1.0f}, {1.0f, 1.0f}}, - {{-1.0f, 1.0f}, {0.0f, 1.0f}} - }; private: gfx::shader_program* m_font_shader; - gfx::vbo m_vbo; - gfx::vao m_vao; - gfx::texture* m_texture; - std::unique_ptr m_text; int m_screen_width, m_screen_height; public: - font_renderer(gfx::resource_manager& res, int width, int height, gfx::texture& base_tex); + font_renderer(gfx::resource_manager& res, int width, int height); - void render(scene&); + void render(egn::text&); void resize_viewport(int width, int height); }; diff --git a/include/ttt/font_shader.hpp b/include/ttt/font_shader.hpp index cfa52ca..de686a7 100644 --- a/include/ttt/font_shader.hpp +++ b/include/ttt/font_shader.hpp @@ -25,20 +25,20 @@ namespace font_shader{ #version 430 core layout (location = 0) in vec4 tex_coords; layout (location = 1) in vec4 color; - layout (location = 2) in vec3 position; + layout (location = 2) in vec2 offset; layout (location = 3) in vec2 size; out VS_OUT{ vec4 tex_coords; vec4 color; - vec3 position; + vec2 offset; vec2 size; }vs_out; void main(){ vs_out.tex_coords = tex_coords; vs_out.color = color; - vs_out.position = position; + vs_out.offset = offset; vs_out.size = size; } )glsl"; @@ -51,7 +51,7 @@ static constexpr char geometry_shader_text[] = in VS_OUT{ vec4 tex_coords; vec4 color; - vec3 position; + vec2 offset; vec2 size; }gs_in[]; @@ -59,27 +59,49 @@ static constexpr char geometry_shader_text[] = vec2 tex_coords; flat vec4 color; }gs_out; + + uniform vec2 out_size; + uniform vec2 start_pos; + + //map from [0, max_out] to [0, 2] + float size_to_screen_space(float x, float max_out){ + const float ratio = 2.0 / max_out; + return x * ratio; + } + //map from [0, max_out] to [-1, 1] + float pos_to_screen_space(float x, float max_out){ + return size_to_screen_space(x, max_out) - 1.0; + } + void main(){ //texture coordinates need done botleft->topleft->botright->topright //whereas the position goes topleft->botleft->topright->botright //because freetype generates bitmaps with inverse y axis to opengl - gl_Position = vec4(gs_in[0].position.x, gs_in[0].position.y + gs_in[0].size.y, gs_in[0].position.z, 1.0); + vec2 start_screen_pos = vec2(pos_to_screen_space(start_pos.x, out_size.x), pos_to_screen_space(start_pos.y, out_size.y)); + + const vec3 position = vec3(start_screen_pos.x + size_to_screen_space(gs_in[0].offset.x, out_size.x), + start_screen_pos.y + size_to_screen_space(gs_in[0].offset.y, out_size.y), + -1); + const vec2 size = vec2(size_to_screen_space(gs_in[0].size.x, out_size.x), + size_to_screen_space(gs_in[0].size.y, out_size.y)); + + gl_Position = vec4(position.x, position.y + size.y, position.z, 1.0); gs_out.tex_coords = gs_in[0].tex_coords.xy; gs_out.color = gs_in[0].color; EmitVertex(); - gl_Position = vec4(gs_in[0].position, 1.0); + gl_Position = vec4(position, 1.0); gs_out.tex_coords = gs_in[0].tex_coords.xw; gs_out.color = gs_in[0].color; EmitVertex(); - gl_Position = vec4(gs_in[0].position.x + gs_in[0].size.x, gs_in[0].position.y + gs_in[0].size.y, gs_in[0].position.z, 1.0); + gl_Position = vec4(position.x + size.x, position.y + size.y, position.z, 1.0); gs_out.tex_coords = gs_in[0].tex_coords.zy; gs_out.color = gs_in[0].color; EmitVertex(); - gl_Position = vec4(gs_in[0].position.x + gs_in[0].size.x, gs_in[0].position.y, gs_in[0].position.z, 1.0); + gl_Position = vec4(position.x + size.x, position.y, position.z, 1.0); gs_out.tex_coords = gs_in[0].tex_coords.zw; gs_out.color = gs_in[0].color; EmitVertex(); @@ -101,9 +123,9 @@ static constexpr char fragment_shader_text[] = void main(){ vec4 s = texture(texture1, fs_in.tex_coords); - float alpha = (s.r + s.g + s.b) / 3; - - frag_color = mix(s, fs_in.color, alpha); + float alpha = (s.r + s.g + s.b) / 3.0; + vec4 alpha_texture = vec4(s.rgb, alpha); + frag_color = alpha_texture * fs_in.color; //NOT the dot product in glsl } )glsl"; } diff --git a/include/ttt/pause_state.hpp b/include/ttt/pause_state.hpp index 1fd027a..4a46a34 100644 --- a/include/ttt/pause_state.hpp +++ b/include/ttt/pause_state.hpp @@ -23,12 +23,17 @@ #include "engine/input.hpp" #include "graphics/texture.hpp" #include "screen_renderer.hpp" +#include "font_renderer.hpp" + +#include "engine/text.hpp" class pause_state : public egn::game_state_iface { private: egn::game_state_iface* m_under_state; screen_renderer m_screen_renderer; + font_renderer m_font_renderer; + egn::text m_text; public: pause_state(egn::game& owner, egn::game_state_iface* under); diff --git a/include/ttt/screen_renderer.hpp b/include/ttt/screen_renderer.hpp index 374fff9..ed14402 100644 --- a/include/ttt/screen_renderer.hpp +++ b/include/ttt/screen_renderer.hpp @@ -29,117 +29,6 @@ #include "engine/font.hpp" -static constexpr float pixels_to_screen_space(int dim, int window_dim){ - const float ratio = 2.0f / window_dim; - return dim * ratio; -} -static constexpr float screen_space_to_pixels(int dim, int window_dim){ - const float ratio = window_dim / 2.0f; - return dim * ratio; -} -static constexpr float font_scale_ratio(int height, int target_height){ - return ((float)target_height) / height; -} -static constexpr float font_aspect_ratio(const egn::font_character& ch){ - return ((float)ch.size.x()) / ch.size.y(); -} - - -class text -{ -private: - static constexpr int s_size_per_char = sizeof(GLfloat) * 13; -private: - egn::font m_font; - std::string m_text; - gfx::vbo m_vbo; - gfx::vao m_vao; - - float m_scale; - -public: - text(egn::font& font, const char* string, int target_px, float target_x, float target_y, int screen_width, int screen_height): - m_font(font), - m_text(string), - m_vbo(s_size_per_char * strlen(string), gfx::vbo::usage::DYNAMIC_DRAW) - { - constexpr float tmp_color[] = {1,1,1,1}; - - m_scale = font_scale_ratio(m_font.size().y(), target_px); - - math::vec3f target_pos = {target_x, target_y, -1}; - size_t offset = 0; - for(int i = 0;m_text[i] != 0;++i){ - const auto ch = m_font[m_text[i]]; - const math::vec2 letter_size = ch.size * m_scale; - const math::vec2f letter_screen_size = {pixels_to_screen_space(letter_size.x(), screen_width), pixels_to_screen_space(letter_size.y(), screen_height)}; - const math::vec3f position = {target_pos.x() + pixels_to_screen_space(ch.bearing.x() * m_scale, screen_width), - target_pos.y() - (letter_screen_size.y() - pixels_to_screen_space(ch.bearing.y() * m_scale, screen_height)), - target_pos.z()}; - - - m_vbo.buffer(ch.texture_coords[1], sizeof(GLfloat) * 2, offset); - offset += sizeof(GLfloat) * 2; - m_vbo.buffer(ch.texture_coords[2], sizeof(GLfloat) * 2, offset); - offset += sizeof(GLfloat) * 2; - m_vbo.buffer(tmp_color, sizeof(GLfloat) * 4, offset); - offset += sizeof(GLfloat) * 4; - m_vbo.buffer(position, sizeof(GLfloat) * 3, offset); - offset += sizeof(GLfloat) * 3; - m_vbo.buffer(letter_screen_size, sizeof(GLfloat) * 2, offset); - offset += sizeof(GLfloat) * 2; - - target_pos.x() += pixels_to_screen_space(m_scale * ch.advance.x(), screen_width); - } - - //attach a vbo to the vao and assign attribute specs - m_vao.bind_buffer(m_vbo, 0, 0, sizeof(GLfloat) * 13); - - auto attrib = m_vao.get_attribute(0); - attrib.set_float_array(4, 0); - attrib.associate_with(0); - attrib.enable(); - - attrib = m_vao.get_attribute(1); - attrib.set_float_array(4, sizeof(GLfloat) * 4); - attrib.associate_with(0); - attrib.enable(); - - attrib = m_vao.get_attribute(2); - attrib.set_float_array(3, sizeof(GLfloat) * 8); - attrib.associate_with(0); - attrib.enable(); - - attrib = m_vao.get_attribute(3); - attrib.set_float_array(2, sizeof(GLfloat) * 11); - attrib.associate_with(0); - attrib.enable(); - - } - void set_position(float x, float y, int screen_width, int screen_height){ - size_t offset = sizeof(GLfloat) * 8; - math::vec3f target_pos = {x, y, -1}; - for(size_t i = 0;m_text[i] != 0;++i){ - const auto ch = m_font[m_text[i]]; - const math::vec2i letter_size = ch.size * m_scale; - const math::vec2f letter_screen_size = {pixels_to_screen_space(letter_size.x(), screen_width), pixels_to_screen_space(letter_size.y(), screen_height)}; - const math::vec3f position = {target_pos.x() + pixels_to_screen_space(ch.bearing.x() * m_scale, screen_width), - target_pos.y() - (letter_screen_size.y() - pixels_to_screen_space(ch.bearing.y() * m_scale, screen_height)), - target_pos.z()}; - - m_vbo.buffer(position, sizeof(GLfloat) * 3, offset); - offset += sizeof(GLfloat) * 3 + sizeof(GLfloat) * 8; - target_pos.x() += pixels_to_screen_space(m_scale * ch.advance.x(), screen_width); - } - } - - void render(gfx::shader_program& sh){ - m_vao.bind(); - sh.set_uniform("texture1", m_font.atlas()); - glDrawArrays(GL_POINTS, 0, m_text.length()); - } -}; - class screen_renderer { private: diff --git a/src/engine/flat_object.cpp b/src/engine/flat_object.cpp new file mode 100644 index 0000000..9cf3855 --- /dev/null +++ b/src/engine/flat_object.cpp @@ -0,0 +1,74 @@ +/** + This file is a part of our_dick + Copyright (C) 2022 rexy712 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +#include "engine/flat_object.hpp" +#include "math/math_common.hpp" +#include //signbit, atan + +namespace egn::flat{ + + object_base::object_base(const math::vec2f& position, float orientation): + m_position(position), + m_orientation(orientation){} + + void object_base::translate(const math::vec2f& distance){ + m_position += distance; + } + void object_base::rotate(float distance){ + m_orientation += distance; + } + void object_base::scale(const math::vec2f& distance){ + m_scale += distance; + } + void object_base::look_at(const math::vec2f& target){ + const math::vec2f rel_target = target - m_position; + const float angle = std::atan(rel_target.y() / rel_target.x()); + const int x_sign = std::signbit(rel_target.x()); + const int y_sign = std::signbit(rel_target.y()); + const int quadrant = (x_sign ^ y_sign) + (2 * y_sign); + + //quadrants: + // | + // 1 | 0 + //----------------- + // 2 | 3 + // | + m_orientation = angle + (90.0_deg * quadrant); + } + + void object_base::set_position(const math::vec2f& pos){ + m_position = pos; + } + void object_base::set_orientation(float orient){ + m_orientation = orient; + } + void object_base::set_scale(const math::vec2f& scale){ + m_scale = scale; + } + + const math::vec2f& object_base::position(void)const{ + return m_position; + } + float object_base::orientation(void)const{ + return m_orientation; + } + const math::vec2f& object_base::scale(void)const{ + return m_scale; + } + +} diff --git a/src/engine/font.cpp b/src/engine/font.cpp index ca17aa2..8fe1d9d 100644 --- a/src/engine/font.cpp +++ b/src/engine/font.cpp @@ -28,129 +28,111 @@ namespace egn{ return ((float)size.x()) / size.y(); } + font_atlas::font_atlas(gfx::texture&& t, map_type&& map, size_t w, size_t h): + texture(std::move(t)), + m_atlas_data(std::move(map)), + m_requested_width(w), + m_requested_height(h){} - font::font(gfx::texture&& tex, std::map&& metadata, size_t width, size_t height): - m_atlas(std::move(tex)), - m_atlas_data(std::move(metadata)), - m_requested_width(width), - m_requested_height(height){} - font_character& font::operator[](int character){ + auto font_atlas::metadata(void) -> map_type&{ + return m_atlas_data; + } + auto font_atlas::metadata(void)const -> const map_type&{ + return m_atlas_data; + } + auto font_atlas::release_metadata(void) -> map_type&&{ + return std::move(m_atlas_data); + } + + font_character& font_atlas::operator[](int character){ return m_atlas_data[character]; } - const font_character& font::operator[](int character)const{ - return m_atlas_data.at(character); - } - gfx::texture font::release_atlas(void){ - return gfx::texture(std::move(*m_atlas)); - } - gfx::texture& font::atlas(void){ - return *m_atlas; - } - const gfx::texture& font::atlas(void)const{ - return *m_atlas; - } - std::map font::release_metadata(void){ - return std::map(std::move(m_atlas_data)); - } - bool font::valid(void)const{ - return m_atlas.has_value(); - } - math::vec2 font::size(void)const{ + math::vec2 font_atlas::glyph_size(void)const{ return {m_requested_width, m_requested_height}; } - font_generator::font_generator(const char* file){ + + font::font(const char* file, int depth): + m_depth(depth) + { initialize_freetype_(); - if(FT_New_Face(s_ft, file, 0, &m_face)){ - debug_print_error("Failed to initialize font '%s'\n", file); + if(int err = FT_New_Face(s_ft, file, 0, &m_face);err){ + debug_print_error("Failed to initialize font '%s'. Error %d\n", file, err); } } - font_generator::~font_generator(void){ + font::~font(void){ FT_Done_Face(m_face); } - struct atlas_info{ - size_t width, height; - size_t max_glyph_width, max_glyph_height; - }; - atlas_info get_atlas_size_(FT_Face face, int start_codepoint, int count){ - size_t max_height = 0; - size_t max_width = 0; - size_t avg_width = 0; - size_t avg_height = 0; - for(int i = 0;i < count;++i){ - if(FT_Load_Char(face, i + start_codepoint, FT_LOAD_RENDER | FT_LOAD_TARGET_LCD)){ - debug_print_error("Failed to load character '%c' from font '%s'\n", i, face->family_name); + void font::get_atlas_size_ranges_impl_(size_t& avg_w, size_t& avg_h, size_t& mgw, size_t& mgh, size_t& count, const std::pair& range){ + const int freetype_load_flags = FT_LOAD_RENDER | ((m_depth == 3) ? FT_LOAD_TARGET_LCD : 0); + const size_t start = range.first; + const size_t range_cnt = range.second; + for(size_t i = 0;i < range_cnt;++i){ + if(int err = FT_Load_Char(m_face, i + start, freetype_load_flags);err){ + debug_print_error("Failed to load character '%c' from font '%s'\n", (char)i, m_face->family_name); continue; } - max_width = std::max(max_width, size_t{face->glyph->bitmap.width}); - max_height = std::max(max_height, size_t{face->glyph->bitmap.rows}); - avg_width += face->glyph->bitmap.width; - avg_height += face->glyph->bitmap.rows; + mgw = std::max(mgw, size_t{m_face->glyph->bitmap.width}); + mgh = std::max(mgh, size_t{m_face->glyph->bitmap.rows}); + avg_w += m_face->glyph->bitmap.width; + avg_h += m_face->glyph->bitmap.rows; + ++count; } - avg_width /= count; - avg_height /= count; - size_t est_width = avg_width * (std::min(20, count) + 1); - size_t est_height = (max_height * (std::round(count / 20) + 1)); - - - return {est_width / 3, est_height, max_width / 3, max_height}; } - font font_generator::generate_atlas(int start_codepoint, int count, size_t glyph_width, size_t glyph_height){ - FT_Set_Pixel_Sizes(m_face, glyph_width, glyph_height); + font_character calculate_character(FT_Face face, size_t atlas_w, size_t atlas_h, size_t atlas_x, size_t atlas_y){ + const math::vec2 dest_size = {face->glyph->metrics.width >> 6, face->glyph->metrics.height >> 6}; + const float ratio_w = 1.0f / atlas_w; + const float ratio_h = 1.0f / atlas_h; + const float left = ratio_w * atlas_x; + const float right = ratio_w * (atlas_x + dest_size.x()); + const float bottom = ratio_h * atlas_y; + const float top = ratio_h * (atlas_y + dest_size.y()); - atlas_info ai = get_atlas_size_(m_face, start_codepoint, count); + return font_character {{atlas_x, atlas_y}, + dest_size, + {face->glyph->metrics.horiBearingX >> 6, face->glyph->metrics.horiBearingY >> 6}, + {face->glyph->metrics.horiAdvance >> 6, 0}, //convert from 1/64th pixels to pixels + {{left, top}, {left, bottom}, {right, top}, {right, bottom}}}; + } - gfx::texture atlas(GL_RGB, ai.width, ai.height, GL_UNSIGNED_BYTE, false); - std::map atlas_metadata; - math::vec2 atlas_pos = {0, 0}; - - std::unique_ptr dest_data(new unsigned char[ai.max_glyph_height * ai.max_glyph_width * 3]); + void pack_font_data_(unsigned char* dest, const unsigned char* src, size_t out_width, size_t out_height, int pitch){ + for(size_t j = 0;j < out_height;++j){ + memcpy(dest, src, out_width); + dest += out_width; + src += pitch; + } + } + void font::generate_atlas_piece_impl_(gfx::texture& atlas, font_atlas::map_type& metadata, math::vec2& target_pos, const atlas_meta& info, unsigned char* data_buffer, const std::pair& range){ + const auto& [atlas_width, atlas_height, max_glyph_width, max_glyph_height] = info; + const auto format = (m_depth == 3) ? GL_RGB : GL_RED; + const int freetype_load_flags = FT_LOAD_RENDER | ((m_depth == 3) ? FT_LOAD_TARGET_LCD : 0); + int start_codepoint = range.first; + int count = range.second; for(int i = start_codepoint;i < start_codepoint + count;++i){ - if(FT_Load_Char(m_face, i, FT_LOAD_RENDER | FT_LOAD_TARGET_LCD)){ + if(FT_Load_Char(m_face, i, freetype_load_flags)){ debug_print_error("Failed to load character '%c', from font '%s'\n", i, m_face->family_name); continue; } auto& bitmap = m_face->glyph->bitmap; - const math::vec2 dest_size = {bitmap.width / 3, bitmap.rows}; - - const float ratio_w = 1.0f / ai.width; - const float ratio_h = 1.0f / ai.height; - const float left = ratio_w * atlas_pos.x(); - const float right = ratio_w * (atlas_pos.x() + dest_size.x()); - const float bottom = ratio_h * atlas_pos.y(); - const float top = ratio_h * (atlas_pos.y() + dest_size.y()); - - font_character character{atlas.raw(), - atlas_pos, - dest_size, - {m_face->glyph->bitmap_left, m_face->glyph->bitmap_top}, - {m_face->glyph->advance.x >> 6, m_face->glyph->advance.y >> 6}, //convert from 1/64th pixels to pixels - {{left, top}, {left, bottom}, {right, top}, {right, bottom}}}; - atlas_metadata.emplace(i, character); - if(atlas_pos.x() + dest_size.x() > ai.width){ - atlas_pos.x() = 0; - atlas_pos.y() += ai.max_glyph_height; + const math::vec2 out_size = {bitmap.width / m_depth, bitmap.rows}; + if(target_pos.x() + out_size.x() > atlas_width){ + target_pos.x() = 0; + target_pos.y() += max_glyph_height; } - unsigned char* dest = dest_data.get(); - unsigned char* src = bitmap.buffer; - for(int j = 0;j < bitmap.rows;++j){ - memcpy(dest, src, bitmap.width); - dest += bitmap.width; - src += bitmap.pitch; - } - atlas.set_subimage(dest_data.get(), GL_RGB, GL_UNSIGNED_BYTE, atlas_pos.x(), atlas_pos.y(), dest_size.x(), dest_size.y()); - atlas_pos.x() += dest_size.x(); + font_character character = calculate_character(m_face, atlas_width, atlas_height, target_pos.x(), target_pos.y()); + metadata.emplace(i, character); + + pack_font_data_(data_buffer, bitmap.buffer, bitmap.width, bitmap.rows, bitmap.pitch); + atlas.set_subimage(data_buffer, format, GL_UNSIGNED_BYTE, target_pos.x(), target_pos.y(), out_size.x(), out_size.y()); + target_pos.x() += out_size.x(); } - atlas.generate_mipmap(); - atlas.set_wrap_mode(gfx::texture::wrap::CLAMP_EDGE); - return {std::move(atlas), std::move(atlas_metadata), glyph_width, glyph_height}; } - std::optional font_generator::generate_glyph(char character, int width, int height){ + std::optional font::generate_glyph(char character, int width, int height){ FT_Set_Pixel_Sizes(m_face, width, height); if(FT_Load_Char(m_face, character, FT_LOAD_RENDER | FT_LOAD_TARGET_LCD)){ debug_print_error("Failed to load character '%c', from font '%s'\n", character, m_face->family_name); @@ -162,7 +144,7 @@ namespace egn{ - bool font_generator::initialize_freetype_(void){ + bool font::initialize_freetype_(void){ if(!s_initialized){ if(FT_Init_FreeType(&s_ft)){ return false; diff --git a/src/engine/text.cpp b/src/engine/text.cpp new file mode 100644 index 0000000..3103d61 --- /dev/null +++ b/src/engine/text.cpp @@ -0,0 +1,106 @@ +/** + This file is a part of our_dick + Copyright (C) 2022 rexy712 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +#include "engine/text.hpp" + +namespace egn{ + + text_gfx::text_gfx(const egn::font_atlas& font, int target_px, size_t initsize): + m_atlas(font), + m_font(font.metadata()), + m_vbo(initsize * s_size_per_char, gfx::vbo::usage::DYNAMIC_DRAW), + m_original_height(font.glyph_size().y()), + m_scale(((float)target_px) / m_original_height){} + text_gfx::text_gfx(const egn::font_atlas& font, int target_px, const char* string): + text_gfx(font, target_px, strlen(string)) + { + set_text(string); + + //attach a vbo to the vao and assign attribute specs + m_vao.bind_buffer(m_vbo, 0, 0, s_size_per_char); + + auto attrib = m_vao.get_attribute(0); + attrib.set_float_array(4, 0); + attrib.associate_with(0); + attrib.enable(); + + attrib = m_vao.get_attribute(1); + attrib.set_float_array(4, sizeof(GLfloat) * 4); + attrib.associate_with(0); + attrib.enable(); + + attrib = m_vao.get_attribute(2); + attrib.set_float_array(2, sizeof(GLfloat) * 8); + attrib.associate_with(0); + attrib.enable(); + + attrib = m_vao.get_attribute(3); + attrib.set_float_array(2, sizeof(GLfloat) * 10); + attrib.associate_with(0); + attrib.enable(); + } + void text_gfx::set_text(const char* string){ + m_vbo.clear(); + constexpr float tmp_color[] = {1,1,1,1}; + + math::vec2f pos_offset = {0, 0}; + + for(int i = 0;string[i] != 0;++i){ + if(string[i] == '\n'){ + pos_offset.x() = 0; + pos_offset.y() -= m_original_height * m_scale; + continue; + } + const auto ch = m_font[string[i]]; + const math::vec2 letter_size = ch.size * m_scale; + const math::vec2f char_offset = {(ch.bearing.x() * m_scale) + pos_offset.x(), + ((ch.bearing.y() * m_scale) - letter_size.y()) + pos_offset.y()}; + + m_vbo.buffer(ch.texture_coords[1], sizeof(GLfloat) * 2); + m_vbo.buffer(ch.texture_coords[2], sizeof(GLfloat) * 2); + m_vbo.buffer(tmp_color, sizeof(GLfloat) * 4); + m_vbo.buffer(char_offset, sizeof(GLfloat) * 2); + m_vbo.buffer(letter_size, sizeof(GLfloat) * 2); + + pos_offset.x() += (m_scale * ch.advance.x()); + } + } + void text_gfx::render(gfx::shader_program& sh, const math::vec2f& pos){ + m_vao.bind(); + sh.set_uniform("texture1", m_atlas); + sh.set_uniform("start_pos", pos); + glDrawArrays(GL_POINTS, 0, m_vbo.get_size() / s_size_per_char); + } + + + + text::text(const egn::font_atlas& font, const char* string, int target_px, float target_x, float target_y): + flat::object_base(math::vec2f{target_x, target_y}), + m_gfx(font, target_px, string), + m_text(string){} + + void text::set_text(const char* string){ + m_text = string; + m_gfx.set_text(string); + } + + void text::render(gfx::shader_program& sh){ + m_gfx.render(sh, m_position); + } + +} diff --git a/src/graphics/resource_manager.cpp b/src/graphics/resource_manager.cpp index 976fe94..6453664 100644 --- a/src/graphics/resource_manager.cpp +++ b/src/graphics/resource_manager.cpp @@ -40,6 +40,16 @@ namespace gfx{ return m_textures.erase_value(key); } + bool resource_manager::has_font(const char* key)const{ + return m_fonts.has_value(key); + } + egn::font_atlas* resource_manager::get_font(const char* file){ + return m_fonts.get_value(file); + } + bool resource_manager::erase_font(const char* key){ + return m_fonts.erase_value(key); + } + bool resource_manager::has_shader(const char* key)const{ return m_shaders.has_value(key); } diff --git a/src/graphics/shader_program.cpp b/src/graphics/shader_program.cpp index 9a57c5d..4773bf4 100644 --- a/src/graphics/shader_program.cpp +++ b/src/graphics/shader_program.cpp @@ -501,10 +501,10 @@ namespace gfx{ t.bind_unit(tex_unit); glProgramUniform1i(m_shader_id, loc, tex_unit); } - void shader_program::set_uniform(const char* name, const weak_texture_handle& t, GLuint tex_unit){ + void shader_program::set_uniform(const char* name, const weak_texture& t, GLuint tex_unit){ set_uniform(get_uniform_loc(name), t, tex_unit); } - void shader_program::set_uniform(GLuint loc, const weak_texture_handle& t, GLuint tex_unit){ + void shader_program::set_uniform(GLuint loc, const weak_texture& t, GLuint tex_unit){ t.bind_unit(tex_unit); glProgramUniform1i(m_shader_id, loc, tex_unit); } diff --git a/src/graphics/texture.cpp b/src/graphics/texture.cpp index f1606e6..a87fa24 100644 --- a/src/graphics/texture.cpp +++ b/src/graphics/texture.cpp @@ -301,8 +301,8 @@ namespace gfx{ void texture::bind_unit(GLuint tunit)const{ glBindTextureUnit(tunit, m_tex_id); } - weak_texture_handle texture::create_handle(void)const{ - return weak_texture_handle(*this); + weak_texture texture::create_handle(void)const{ + return weak_texture(*this); } bool texture::create_texture_storage_(void){ @@ -526,24 +526,30 @@ namespace gfx{ - weak_texture_handle::weak_texture_handle(const texture& tex): - m_texture(&tex){} + weak_texture::weak_texture(GLuint tex): + m_texture(tex){} + weak_texture::weak_texture(const texture& tex): + m_texture(tex.raw()){} - GLuint weak_texture_handle::raw(void)const{ - return m_texture->raw(); + GLuint weak_texture::raw(void)const{ + return m_texture; } - GLsizei weak_texture_handle::get_width(void)const{ - return m_texture->get_width(); + GLsizei weak_texture::get_width(void)const{ + GLint retval; + glGetTextureLevelParameteriv(m_texture, 0, GL_TEXTURE_WIDTH, &retval); + return retval; } - GLsizei weak_texture_handle::get_height(void)const{ - return m_texture->get_height(); + GLsizei weak_texture::get_height(void)const{ + GLint retval; + glGetTextureLevelParameteriv(m_texture, 0, GL_TEXTURE_HEIGHT, &retval); + return retval; } - void weak_texture_handle::bind(void)const{ - m_texture->bind(); + void weak_texture::bind(GLuint target)const{ + glBindTexture(target, m_texture); } - void weak_texture_handle::bind_unit(GLuint tunit)const{ - m_texture->bind_unit(tunit); + void weak_texture::bind_unit(GLuint tunit)const{ + glBindTextureUnit(tunit, m_texture); } } diff --git a/src/ttt/font_renderer.cpp b/src/ttt/font_renderer.cpp index 562ccd1..2b3c577 100644 --- a/src/ttt/font_renderer.cpp +++ b/src/ttt/font_renderer.cpp @@ -24,31 +24,21 @@ #include "engine/font.hpp" -font_renderer::font_renderer(gfx::resource_manager& res, int width, int height, gfx::texture& base_tex): - m_vbo(s_vertices, sizeof(s_vertices), gfx::vbo::usage::DYNAMIC_DRAW), - m_texture(&base_tex), - m_text(nullptr), +font_renderer::font_renderer(gfx::resource_manager& res, int width, int height): m_screen_width(width), m_screen_height(height) { m_font_shader = res.emplace_shader("font_shader", util::make_deferred(font_shader::vertex_shader_text, gfx::shader::type::VERTEX), - util::make_deferred(font_shader::geometry_shader_text, gfx::shader::type::GEOMETRY), - util::make_deferred(font_shader::fragment_shader_text, gfx::shader::type::FRAGMENT)).first; + util::make_deferred(font_shader::geometry_shader_text, gfx::shader::type::GEOMETRY), + util::make_deferred(font_shader::fragment_shader_text, gfx::shader::type::FRAGMENT)).first; if(m_font_shader->has_link_error()){ debug_print_error("%s\n", m_font_shader->get_error().c_str()); } - constexpr int target_pixel = 256; - - egn::font_generator f("assets/Woodwarrior-Regular.otf"); - auto font = f.generate_atlas('a', 26, 0, target_pixel); - - m_text.reset(new text(font, "pause", target_pixel, -0.8, -0.8, m_screen_width, m_screen_height)); - resize_viewport(width, height); } -void font_renderer::render(scene&){ +void font_renderer::render(egn::text& t){ //Clear the global state to that which this renderer prefers glBindFramebuffer(GL_FRAMEBUFFER, 0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); @@ -56,14 +46,10 @@ void font_renderer::render(scene&){ glViewport(0, 0, m_screen_width, m_screen_height); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - //Apply our shader and vao m_font_shader->use(); -// m_font_shader->set_uniform("texture1", *m_texture, 0); -// m_vao.bind(); - m_text->render(*m_font_shader); + m_font_shader->set_uniform("out_size", math::vec2f{m_screen_width, m_screen_height}); - //Draw - //glDrawArrays(GL_POINTS, 0, 5); + t.render(*m_font_shader); } void font_renderer::resize_viewport(int width, int height){ diff --git a/src/ttt/pause_state.cpp b/src/ttt/pause_state.cpp index e12e511..d1be43e 100644 --- a/src/ttt/pause_state.cpp +++ b/src/ttt/pause_state.cpp @@ -24,13 +24,21 @@ #include "config.hpp" +static egn::font_atlas generate_font_atlas_from_file(const char* file, int target_height){ + egn::font f(file); + return f.generate_atlas(0, target_height, std::pair{' ', ('~' - ' ')}); +} + pause_state::pause_state(egn::game& owner, egn::game_state_iface* under): game_state_iface(&owner), m_under_state(under), - m_screen_renderer(owner.gfx_resource_manager(), owner.get_width(), owner.get_height(), *owner.gfx_resource_manager().emplace_texture("pause_screen", util::make_deferred("assets/images/pause.png", true)).first){} + m_screen_renderer(owner.gfx_resource_manager(), owner.get_width(), owner.get_height(), *owner.gfx_resource_manager().emplace_texture("pause_screen", util::make_deferred("assets/images/pause.png", true)).first), + m_font_renderer(owner.gfx_resource_manager(), owner.get_width(), owner.get_height()), + m_text(*owner.gfx_resource_manager().emplace_font("hinted-RosaSans-Regular-atlas-50px", util::deferred_function(generate_font_atlas_from_file, "assets/hinted-RosaSans-Regular.ttf", 50)).first, "Pause Menu", 50, 100, 100){} void pause_state::enter(){ m_screen_renderer.resize_viewport(m_owner->get_width(), m_owner->get_height()); + m_font_renderer.resize_viewport(m_owner->get_width(), m_owner->get_height()); } void pause_state::leave(){} void pause_state::handle_input(const egn::input_event& event){ @@ -45,11 +53,12 @@ void pause_state::handle_input(const egn::input_event& event){ }else if(event.ev_type == egn::input_event::type::RESIZE){ m_under_state->handle_input(event); m_screen_renderer.resize_viewport(event.x, event.y); + m_font_renderer.resize_viewport(event.x, event.y); } } void pause_state::update(double){} void pause_state::render(){ - scene tmp; m_under_state->render(); - m_screen_renderer.render(tmp); + //m_screen_renderer.render(tmp); + m_font_renderer.render(m_text); }