/* * Copyright (c) 2012-2026 Daniele Bartolini et al. * SPDX-License-Identifier: MIT */ #include "core/math/color4.inl" #include "core/math/constants.h" #include "core/math/matrix4x4.inl" #include "core/math/vector2.inl" #include "core/math/vector3.inl" #include "core/memory/memory.inl" #include "core/strings/string.inl" #include "core/strings/string_id.inl" #include "core/strings/utf8.h" #include "resource/font_resource.inl" #include "resource/material_resource.h" #include "resource/resource_manager.h" #include "world/gui.h" #include "world/material_manager.h" #include "world/shader_manager.h" #include #include namespace crown { static inline u32 depth_u32(f32 depth) { return u32(depth * 100.0f * 1000.0f); } GuiBuffer::GuiBuffer(ShaderManager &sm) : _shader_manager(&sm) , _num_vertices(0) , _num_indices(0) , _vertex_buffer() , _index_buffer() { } void *GuiBuffer::vertex_buffer_end() { return _vertex_buffer.data + _num_vertices*24; } void *GuiBuffer::index_buffer_end() { return _index_buffer.data + _num_indices*2; } void GuiBuffer::create() { _pos_tex_col.begin(); _pos_tex_col.add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float); _pos_tex_col.add(bgfx::Attrib::TexCoord0, 2, bgfx::AttribType::Float, true); _pos_tex_col.add(bgfx::Attrib::Color0, 4, bgfx::AttribType::Uint8, true); _pos_tex_col.end(); } void GuiBuffer::reset() { _num_vertices = 0; _num_indices = 0; bgfx::allocTransientVertexBuffer(&_vertex_buffer, 4096, _pos_tex_col); bgfx::allocTransientIndexBuffer(&_index_buffer, 6144); } void GuiBuffer::submit(u32 num_vertices, u32 num_indices, const Matrix4x4 &world, ShaderData &shader, u8 view, u32 depth) { bgfx::setVertexBuffer(0, &_vertex_buffer, _num_vertices, num_vertices); bgfx::setIndexBuffer(&_index_buffer, _num_indices, num_indices); bgfx::setTransform(to_float_ptr(world)); bgfx::setState(shader.state); bgfx::submit(view, shader.program, depth); _num_vertices += num_vertices; _num_indices += num_indices; } void GuiBuffer::submit_with_material(u32 num_vertices, u32 num_indices, const Matrix4x4 &world, u8 view, u32 depth, Material *material) { bgfx::setVertexBuffer(0, &_vertex_buffer, _num_vertices, num_vertices); bgfx::setIndexBuffer(&_index_buffer, _num_indices, num_indices); bgfx::setTransform(to_float_ptr(world)); material->bind(view, depth); _num_vertices += num_vertices; _num_indices += num_indices; } Gui::Gui(GuiBuffer &gb , ResourceManager &rm , ShaderManager &sm , MaterialManager &mm , ShaderData *shader , u8 view ) : _marker(DEBUG_GUI_MARKER) , _buffer(&gb) , _resource_manager(&rm) , _shader_manager(&sm) , _material_manager(&mm) , _world(MATRIX4X4_IDENTITY) , _gui_shader(shader) , _view(view) { _node.next = NULL; _node.prev = NULL; } Gui::~Gui() { _marker = 0; } void Gui::move(const Vector2 &pos) { set_translation(_world, { pos.x, pos.y, 0 }); } void Gui::triangle_3d(const Matrix4x4 &local_pose, const Vector3 &a, const Vector3 &b, const Vector3 &c, const Color4 &color, f32 depth) { VertexData *vd = (VertexData *)_buffer->vertex_buffer_end(); vd[0].pos.x = a.x; vd[0].pos.y = a.y; vd[0].pos.z = a.z; vd[0].uv.x = 0.0f; vd[0].uv.y = 0.0f; vd[0].col = to_abgr(color); vd[1].pos.x = b.x; vd[1].pos.y = b.y; vd[1].pos.z = b.z; vd[1].uv.x = 1.0f; vd[1].uv.y = 0.0f; vd[1].col = to_abgr(color); vd[2].pos.x = c.x; vd[2].pos.y = c.y; vd[2].pos.z = c.z; vd[2].uv.x = 1.0f; vd[2].uv.y = 1.0f; vd[2].col = to_abgr(color); u16 *inds = (u16 *)_buffer->index_buffer_end(); inds[0] = 0; inds[1] = 1; inds[2] = 2; _buffer->submit(3, 3, local_pose*_world, *_gui_shader, _view, depth_u32(depth)); } void Gui::triangle(const Vector2 &a, const Vector2 &b, const Vector2 &c, const Color4 &color, f32 depth) { triangle_3d(MATRIX4X4_IDENTITY , { a.x, a.y, 0.0f } , { b.x, b.y, 0.0f } , { c.x, c.y, 0.0f } , color , depth ); } void Gui::rect_3d(const Matrix4x4 &local_pose, const Vector3 &pos, const Vector2 &size, const Color4 &color, f32 depth) { VertexData *vd = (VertexData *)_buffer->vertex_buffer_end(); vd[0].pos.x = pos.x; vd[0].pos.y = pos.y; vd[0].pos.z = pos.z; vd[0].uv.x = 0.0f; vd[0].uv.y = 1.0f; vd[0].col = to_abgr(color); vd[1].pos.x = pos.x + size.x; vd[1].pos.y = pos.y; vd[1].pos.z = pos.z; vd[1].uv.x = 1.0f; vd[1].uv.y = 1.0f; vd[1].col = to_abgr(color); vd[2].pos.x = pos.x + size.x; vd[2].pos.y = pos.y + size.y; vd[2].pos.z = pos.z; vd[2].uv.x = 1.0f; vd[2].uv.y = 0.0f; vd[2].col = to_abgr(color); vd[3].pos.x = pos.x; vd[3].pos.y = pos.y + size.y; vd[3].pos.z = pos.z; vd[3].uv.x = 0.0f; vd[3].uv.y = 0.0f; vd[3].col = to_abgr(color); u16 *inds = (u16 *)_buffer->index_buffer_end(); inds[0] = 0; inds[1] = 1; inds[2] = 2; inds[3] = 0; inds[4] = 2; inds[5] = 3; _buffer->submit(4, 6, local_pose*_world, *_gui_shader, _view, depth_u32(depth)); } void Gui::rect(const Vector3 &pos, const Vector2 &size, const Color4 &color) { rect_3d(MATRIX4X4_IDENTITY , { pos.x, pos.y, 0.0f } , size , color , pos.z ); } void Gui::image_3d_uv(const Matrix4x4 &local_pose, const Vector3 &pos, const Vector2 &size, const Vector2 &uv0, const Vector2 &uv1, StringId64 material, const Color4 &color, f32 depth) { VertexData *vd = (VertexData *)_buffer->vertex_buffer_end(); vd[0].pos.x = pos.x; vd[0].pos.y = pos.y; vd[0].pos.z = pos.z; vd[0].uv.x = uv0.x; vd[0].uv.y = uv1.y; vd[0].col = to_abgr(color); vd[1].pos.x = pos.x + size.x; vd[1].pos.y = pos.y; vd[1].pos.z = pos.z; vd[1].uv.x = uv1.x; vd[1].uv.y = uv1.y; vd[1].col = to_abgr(color); vd[2].pos.x = pos.x + size.x; vd[2].pos.y = pos.y + size.y; vd[2].pos.z = pos.z; vd[2].uv.x = uv1.x; vd[2].uv.y = uv0.y; vd[2].col = to_abgr(color); vd[3].pos.x = pos.x; vd[3].pos.y = pos.y + size.y; vd[3].pos.z = pos.z; vd[3].uv.x = uv0.x; vd[3].uv.y = uv0.y; vd[3].col = to_abgr(color); u16 *inds = (u16 *)_buffer->index_buffer_end(); inds[0] = 0; inds[1] = 1; inds[2] = 2; inds[3] = 0; inds[4] = 2; inds[5] = 3; const MaterialResource *mr = (MaterialResource *)_resource_manager->get(RESOURCE_TYPE_MATERIAL, material); _material_manager->create_material(mr); _buffer->submit_with_material(4 , 6 , local_pose*_world , _view , depth_u32(depth) , _material_manager->get(mr) ); } void Gui::image_uv(const Vector3 &pos, const Vector2 &size, const Vector2 &uv0, const Vector2 &uv1, StringId64 material, const Color4 &color) { image_3d_uv(MATRIX4X4_IDENTITY , { pos.x, pos.y, 0.0f } , size , uv0 , uv1 , material , color , pos.z ); } void Gui::image_3d(const Matrix4x4 &local_pose, const Vector3 &pos, const Vector2 &size, StringId64 material, const Color4 &color, f32 depth) { image_3d_uv(local_pose , pos , size , VECTOR2_ZERO , VECTOR2_ONE , material , color , depth ); } void Gui::image(const Vector3 &pos, const Vector2 &size, StringId64 material, const Color4 &color) { image_3d(MATRIX4X4_IDENTITY , { pos.x, pos.y, 0.0f } , size , material , color , pos.z ); } void Gui::text_3d(const Matrix4x4 &local_pose, const Vector3 &pos, u32 font_size, const char *str, StringId64 font, StringId64 material, const Color4 &color, f32 depth) { const MaterialResource *mr = (MaterialResource *)_resource_manager->get(RESOURCE_TYPE_MATERIAL, material); _material_manager->create_material(mr); const FontResource *fr = (FontResource *)_resource_manager->get(RESOURCE_TYPE_FONT, font); const f32 scale = (f32)font_size / (f32)fr->font_size; VertexData *vd = (VertexData *)_buffer->vertex_buffer_end(); u16 *id = (u16 *)_buffer->index_buffer_end(); u32 num_vertices = 0; u32 num_indices = 0; u32 cp; u32 state = 0; f32 pen_x = 0.0f; f32 pen_y = 0.0f; const GlyphData deffault_glyph = {}; for (const u8 *ch = (u8 *)str; *ch; ++ch) { if (utf8::decode(&state, &cp, *ch) != UTF8_ACCEPT) continue; if (cp == '\n') { pen_x = 0.0f; pen_y -= scale*fr->font_size; continue; } else if (cp == '\t') { pen_x += scale*font_size*4; continue; } const GlyphData *glyph = font_resource::glyph(fr, cp, &deffault_glyph); const f32 baseline = glyph->height - glyph->y_offset; const f32 x_offset = fsign(pen_x) * scale*glyph->x_offset; // Glyph position coords. const f32 x0 = pen_x + pos.x + x_offset; const f32 y0 = pen_y + pos.y - scale*baseline; const f32 x1 = x0 + scale*glyph->width; const f32 y1 = y0 + scale*glyph->height; pen_x += scale*glyph->x_advance; // Glyph atlas coords. const f32 u0 = glyph->x / fr->texture_size; const f32 v1 = glyph->y / fr->texture_size; // Upper-left char corner const f32 u1 = glyph->width / fr->texture_size + u0; const f32 v0 = glyph->height / fr->texture_size + v1; // Bottom-left char corner const u32 abgr = to_abgr(color); // Fill vertex buffer. vd[0].pos.x = x0; vd[0].pos.y = y0; vd[0].pos.z = pos.z; vd[0].uv.x = u0; vd[0].uv.y = v0; vd[0].col = abgr; vd[1].pos.x = x1; vd[1].pos.y = y0; vd[1].pos.z = pos.z; vd[1].uv.x = u1; vd[1].uv.y = v0; vd[1].col = abgr; vd[2].pos.x = x1; vd[2].pos.y = y1; vd[2].pos.z = pos.z; vd[2].uv.x = u1; vd[2].uv.y = v1; vd[2].col = abgr; vd[3].pos.x = x0; vd[3].pos.y = y1; vd[3].pos.z = pos.z; vd[3].uv.x = u0; vd[3].uv.y = v1; vd[3].col = abgr; // Fill index buffer. id[0] = num_vertices + 0; id[1] = num_vertices + 1; id[2] = num_vertices + 2; id[3] = num_vertices + 0; id[4] = num_vertices + 2; id[5] = num_vertices + 3; vd += 4; id += 6; num_vertices += 4; num_indices += 6; } _buffer->submit_with_material(num_vertices , num_indices , local_pose*_world , _view , depth_u32(depth) , _material_manager->get(mr) ); } void Gui::text(const Vector3 &pos, u32 font_size, const char *str, StringId64 font, StringId64 material, const Color4 &color) { const MaterialResource *mr = (MaterialResource *)_resource_manager->get(RESOURCE_TYPE_MATERIAL, material); _material_manager->create_material(mr); const FontResource *fr = (FontResource *)_resource_manager->get(RESOURCE_TYPE_FONT, font); const f32 scale = (f32)font_size / (f32)fr->font_size; VertexData *vd = (VertexData *)_buffer->vertex_buffer_end(); u16 *id = (u16 *)_buffer->index_buffer_end(); u32 num_vertices = 0; u32 num_indices = 0; u32 cp; u32 state = 0; f32 pen_x = 0.0f; f32 pen_y = 0.0f; const GlyphData deffault_glyph = {}; for (const u8 *ch = (u8 *)str; *ch; ++ch) { if (utf8::decode(&state, &cp, *ch) != UTF8_ACCEPT) continue; if (cp == '\n') { pen_x = 0.0f; pen_y -= scale*fr->font_size; continue; } else if (cp == '\t') { pen_x += scale*font_size*4; continue; } const GlyphData *glyph = font_resource::glyph(fr, cp, &deffault_glyph); const f32 baseline = glyph->height - glyph->y_offset; const f32 x_offset = fsign(pen_x) * scale*glyph->x_offset; // Glyph position coords. const f32 x0 = pen_x + pos.x + x_offset; const f32 y0 = pen_y + pos.y - scale*baseline; const f32 x1 = fround(x0 + scale*glyph->width); const f32 y1 = fround(y0 + scale*glyph->height); pen_x += scale*glyph->x_advance; // Glyph atlas coords. const f32 u0 = glyph->x / fr->texture_size; const f32 v1 = glyph->y / fr->texture_size; // Upper-left char corner const f32 u1 = glyph->width / fr->texture_size + u0; const f32 v0 = glyph->height / fr->texture_size + v1; // Bottom-left char corner const u32 abgr = to_abgr(color); // Fill vertex buffer. vd[0].pos.x = x0; vd[0].pos.y = y0; vd[0].pos.z = 0.0f; vd[0].uv.x = u0; vd[0].uv.y = v0; vd[0].col = abgr; vd[1].pos.x = x1; vd[1].pos.y = y0; vd[1].pos.z = 0.0f; vd[1].uv.x = u1; vd[1].uv.y = v0; vd[1].col = abgr; vd[2].pos.x = x1; vd[2].pos.y = y1; vd[2].pos.z = 0.0f; vd[2].uv.x = u1; vd[2].uv.y = v1; vd[2].col = abgr; vd[3].pos.x = x0; vd[3].pos.y = y1; vd[3].pos.z = 0.0f; vd[3].uv.x = u0; vd[3].uv.y = v1; vd[3].col = abgr; // Fill index buffer. id[0] = num_vertices + 0; id[1] = num_vertices + 1; id[2] = num_vertices + 2; id[3] = num_vertices + 0; id[4] = num_vertices + 2; id[5] = num_vertices + 3; vd += 4; id += 6; num_vertices += 4; num_indices += 6; } _buffer->submit_with_material(num_vertices , num_indices , _world , _view , depth_u32(pos.z) , _material_manager->get(mr) ); } Vector2 Gui::text_extents(const u32 font_size, const char *str, StringId64 font) { const FontResource *fr = (FontResource *)_resource_manager->get(RESOURCE_TYPE_FONT, font); const f32 scale = (f32)font_size / (f32)fr->font_size; u32 cp; u32 state = 0; const GlyphData deffault_glyph = {}; f32 pen_x = 0.0f; f32 pen_y = 0.0f; Vector2 box_min = { FLT_MAX, FLT_MAX }; Vector2 box_max = { -FLT_MAX, -FLT_MAX }; for (const u8 *ch = (u8 *)str; *ch; ++ch) { if (utf8::decode(&state, &cp, *ch) != UTF8_ACCEPT) continue; if (cp == '\n') { pen_x = 0.0f; pen_y -= scale*fr->font_size; continue; } else if (cp == '\t') { pen_x += scale*font_size*4; continue; } const GlyphData *glyph = font_resource::glyph(fr, cp, &deffault_glyph); const f32 baseline = glyph->height - glyph->y_offset; const f32 x_offset = fsign(pen_x) * scale*glyph->x_offset; // Glyph position coords. const f32 x0 = pen_x + x_offset; const f32 y0 = pen_y - scale*baseline; const f32 x1 = fround(x0 + scale*glyph->width); const f32 y1 = fround(y0 + scale*glyph->height); box_min = min(box_min, { x0, y0 }); box_max = max(box_max, { x1, y1 }); pen_x += scale*glyph->x_advance; } // Avoid returning funny extents empty or malformed strings. box_min = min(box_min, VECTOR2_ZERO); box_max = max(box_max, VECTOR2_ZERO); return box_max - box_min; } Material *Gui::material(ResourceId material_resource) { const MaterialResource *mr = (MaterialResource *)_resource_manager->get(RESOURCE_TYPE_MATERIAL, material_resource); return _material_manager->get(mr); } namespace gui { Gui *create_screen_gui(Allocator &allocator , GuiBuffer &buffer , ResourceManager &resource_manager , ShaderManager &shader_manager , MaterialManager &material_manager , ShaderData *shader ) { Gui *gui = CE_NEW(allocator, Gui)(buffer , resource_manager , shader_manager , material_manager , shader , View::SCREEN_GUI ); return gui; } Gui *create_world_gui(Allocator &allocator , GuiBuffer &buffer , ResourceManager &resource_manager , ShaderManager &shader_manager , MaterialManager &material_manager , ShaderData *shader ) { Gui *gui = CE_NEW(allocator, Gui)(buffer , resource_manager , shader_manager , material_manager , shader , View::WORLD_GUI ); return gui; } } // namespace gui } // namespace crown