/
d.vision
/
division_engine_cpp
Обзор
Документация
Войти
/
d.vision
/
division_engine_cpp
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/canvas/text_drawer.cpp
343 строки
11 KB
den163
Added SDF font support
05 дек 2024, 01:01
05 дек 2024, 01:01
a1df803
Код
Авторство
О чём код?
#include "division_engine/canvas/text_drawer.hpp" #include "canvas/components/render_batch.hpp" #include "canvas/components/text_bounds.hpp" #include "core/alpha_blend.hpp" #include "core/context.hpp" #include "core/render_pass_instance_builder.hpp" #include "core/vertex_buffer_data.hpp" #include "division_engine/rect.hpp" #include "division_engine_core/font.h" #include <division_engine_core/types/id.h> #include <division_engine_core/types/render_pass_descriptor.h> #include <division_engine_core/types/vertex_buffer.h> #include <flecs.h> #include <algorithm> #include <filesystem> #include <ranges> #include <string> #include <string_view> namespace division_engine::canvas { const auto RASTERIZED_FONT_SIZE = 128.f; const auto INSTANCE_CAPACITY = 1024; const auto SCREEN_SIZE_UNIFORM_LOCATION = 1; const auto TEXTURE_LOCATION = 0; using namespace components; TextDrawer::TextDrawer(State& state, const std::filesystem::path& font_path) : _font_texture(FontTexture { state.context, font_path, static_cast<size_t>(RASTERIZED_FONT_SIZE), }) , _ctx(state.context) , _screen_size_uniform(DivisionIdWithBinding { .id = state.screen_size_uniform_id, .shader_location = SCREEN_SIZE_UNIFORM_LOCATION, }) , _shader_id(_ctx.create_bundled_shader( std::filesystem::path { "resources" } / "shaders" / "canvas" / "font" )) , _vertex_buffer_id(_ctx.create_vertex_buffer<TextCharVertex, TextCharInstance>( DivisionVertexBufferSize { .vertex_count = RECT_VERTICES.size(), .index_count = RECT_INDICES.size(), .instance_count = INSTANCE_CAPACITY, }, DIVISION_TOPOLOGY_TRIANGLES )) , _render_pass_descriptor_id( _ctx.render_pass_descriptor_builder() .shader(_shader_id) .vertex_buffer(_vertex_buffer_id) .enable_aplha_blending( core::AlphaBlend { DIVISION_ALPHA_BLEND_SRC_ALPHA, DIVISION_ALPHA_BLEND_ONE_MINUS_SRC_ALPHA, }, DIVISION_ALPHA_BLEND_OP_ADD ) .build() ) { auto vb_data = _ctx.borrow_vertex_buffer_data<TextCharVertex, TextCharInstance>(_vertex_buffer_id); std::ranges::copy(RECT_VERTICES, vb_data.per_vertex_data().data()); std::ranges::copy(RECT_INDICES, vb_data.index_data().data()); _texture_bindings.push_back(DivisionIdWithBinding { .id = _font_texture.texture_id(), .shader_location = TEXTURE_LOCATION, }); _query = state.world .query_builder<const RenderBounds, const RenderableText, const RenderOrder, TextBounds>( ) .order_by<RenderOrder>([](auto, const auto* x, auto, const auto* y) { return x->compare(*y); }) .term<RenderBatch>() .up(flecs::IsA) .build(); } TextDrawer::~TextDrawer() { _ctx.delete_vertex_buffer(_vertex_buffer_id); _ctx.delete_shader(_shader_id); } static inline core::VertexBufferData<TextDrawer::TextCharVertex, TextDrawer::TextCharInstance> borrow_vertex_buffer_data(core::Context& ctx, DivisionId vertex_buffer_id) { return ctx.borrow_vertex_buffer_data<TextDrawer::TextCharVertex, TextDrawer::TextCharInstance>( vertex_buffer_id ); } void TextDrawer::fill_render_queue(State& state) { using core::RenderPassInstanceBuilder; size_t overall_instance_count = 0; auto vb_instance_size = borrow_vertex_buffer_data(_ctx, _vertex_buffer_id).per_instance_data().size(); if (_query.count() == 0) { return; } _query.iter( [&](flecs::iter& it, const RenderBounds* bounds_ptr, const RenderableText* renderable_ptr, const RenderOrder* render_order_ptr, TextBounds* text_bounds_ptr) { size_t instance_per_pass_count = 0; for (const auto i : it) { const auto& bounds = bounds_ptr[i]; const auto& renderable = renderable_ptr[i]; const auto& text_str = renderable.text; for (auto ch : text_str) { _font_texture.reserve_character(ch); } const auto needed_capacity = overall_instance_count + text_str.size(); if (vb_instance_size < needed_capacity) { _ctx.resize_vertex_buffer( _vertex_buffer_id, DivisionVertexBufferSize { .vertex_count = RECT_VERTICES.size(), .index_count = RECT_INDICES.size(), .instance_count = static_cast<uint32_t>(needed_capacity), } ); vb_instance_size = needed_capacity; } auto vb_data = borrow_vertex_buffer_data(_ctx, _vertex_buffer_id); auto instances = vb_data.per_instance_data(); auto subinstances = instances.last(instances.size() - overall_instance_count); const auto& text_info = add_renderable_to_vertex_buffer(subinstances, bounds, renderable); instance_per_pass_count = text_info.rendered_character_count; text_bounds_ptr[i] = TextBounds { text_info.bounding_box }; } if (instance_per_pass_count == 0) { return; } const auto pass = RenderPassInstanceBuilder { _render_pass_descriptor_id } .instances(instance_per_pass_count, overall_instance_count) .vertices(RECT_VERTICES.size()) .indices(RECT_INDICES.size()) .fragment_textures({ &_texture_bindings[0], 1 }) .uniform_fragment_buffers({ &_screen_size_uniform, 1 }) .uniform_vertex_buffers({ &_screen_size_uniform, 1 }) .build(); state.render_queue.enqueue_pass(pass, render_order_ptr[0]); overall_instance_count += instance_per_pass_count; } ); _font_texture.upload_texture(); } TextDrawer::TextInfo TextDrawer::add_renderable_to_vertex_buffer( std::span<TextCharInstance> instances, const RenderBounds& bounds, const RenderableText& renderable ) { const auto& text_str = renderable.text; const auto font_scale = renderable.font_size / RASTERIZED_FONT_SIZE; const auto space_index = _font_texture.reserve_character(' '); const auto space_glyph = _font_texture.glyph_at(space_index); const auto space_advance_x = static_cast<float>(space_glyph.advance_x) * font_scale; const auto& bounds_rect = bounds.value; const float line_height = static_cast<float>(_font_texture.line_height()) * font_scale; glm::vec2 pen_pos { bounds_rect.left(), bounds_rect.top() - line_height }; size_t rendered_char_count = 0; auto bounding_box = Rect::from_bottom_left(bounds_rect.top_left(), glm::vec2 { 0 }); size_t i = 0; while (i < text_str.size()) { const auto ch = text_str[i]; switch (ch) { case ' ': { pen_pos.x += space_advance_x; i += 1; continue; } case '\n': { pen_pos = { bounds_rect.left(), pen_pos.y - line_height }; i += 1; continue; } } if (pen_pos.y < bounds_rect.bottom()) { break; } using str_diff_type = std::string::difference_type; const auto word_start_it = text_str.begin() + static_cast<str_diff_type>(i); const auto word = get_next_word( std::string_view { word_start_it, text_str.end(), }, font_scale ); if (pen_pos.x + word.width > bounds_rect.right()) { pen_pos = { bounds_rect.left(), pen_pos.y - line_height }; } if ((word.width > bounds_rect.size().x) | (pen_pos.y < bounds_rect.bottom())) { break; } auto word_instances = instances.subspan(rendered_char_count, word.character_count); const auto word_end_it = text_str.begin() + static_cast<str_diff_type>(i + word.character_count); add_word_to_vertex_buffer( { word_start_it, word_end_it }, pen_pos, renderable.color, font_scale, word_instances, bounding_box ); pen_pos.x += word.width; rendered_char_count += word.character_count; i += word.character_count; } return TextInfo { bounding_box, rendered_char_count }; } TextDrawer::WordInfo TextDrawer::get_next_word(const std::string_view& text, float font_scale) const { WordInfo word { 0, 0 }; for (int i = 0; i < text.size(); i++) { const auto ch = text[i]; if (ch == ' ' | ch == '\n') { break; } const auto glyph_index = _font_texture.glyph_index(ch); const auto& glyph = _font_texture.glyph_at(glyph_index); word.width += static_cast<float>(glyph.advance_x) * font_scale; word.character_count += 1; } return word; } void TextDrawer::add_word_to_vertex_buffer( const std::string_view& word, const glm::vec2& position, const glm::vec4& color, float font_scale, std::span<TextCharInstance> instances, Rect& bounding_box ) { auto word_pos = position; for (int i = 0; i < word.size(); i++) { const auto ch = word[i]; const auto char_idx = _font_texture.glyph_index(ch); const auto& glyph = _font_texture.glyph_at(char_idx); if (glyph.width <= 0) { continue; } const auto& tex_rect = _font_texture.glyph_sdf_texture_rect(char_idx); const auto scaled_advance = static_cast<float>(glyph.advance_x) * font_scale; const auto scaled_size = glm::vec2 { glyph.width, glyph.height } * font_scale; const auto offset = glm::vec2 { glyph.bitmap_left, static_cast<float>(glyph.bitmap_top) - static_cast<float>(glyph.height), } * font_scale; const auto instance_position = word_pos + offset; instances[i] = TextCharInstance { .color = color, .texel_coord = tex_rect.top_left(), .size = scaled_size, .position = instance_position, .glyph_in_tex_size = tex_rect.size(), .tex_size = _font_texture.texture_size() }; word_pos.x += scaled_advance; const auto& word_bounds = Rect::from_bottom_left_top_right(instance_position, instance_position + scaled_size); bounding_box = bounding_box.include(word_bounds); } } }