diff --git a/font/Font.h b/font/Font.h index 01cce1c..b188159 100644 --- a/font/Font.h +++ b/font/Font.h @@ -4,16 +4,17 @@ #include "../stdlib/Types.h" #include "../memory/BufferMemory.h" -enum TextAlignH { - TEXT_ALIGN_H_LEFT, - TEXT_ALIGN_H_CENTER, - TEXT_ALIGN_H_RIGHT, +// @todo Move this somewhere else, it doesn't belong here +enum UIAlignH { + UI_ALIGN_H_LEFT, + UI_ALIGN_H_CENTER, + UI_ALIGN_H_RIGHT, }; -enum TextAlignV { - TEXT_ALIGN_V_BOTTOM, - TEXT_ALIGN_V_CENTER, - TEXT_ALIGN_V_TOP, +enum UIAlignV { + UI_ALIGN_V_BOTTOM, + UI_ALIGN_V_CENTER, + UI_ALIGN_V_TOP, }; struct GlyphMetrics { diff --git a/gpuapi/RenderUtils.h b/gpuapi/RenderUtils.h index 7d4510d..1b2ad93 100644 --- a/gpuapi/RenderUtils.h +++ b/gpuapi/RenderUtils.h @@ -12,64 +12,187 @@ #include #include #include "../math/matrix/MatrixFloat32.h" +#include "../font/Font.h" -void make_character( - f32 *data, - f32 x, f32 y, f32 n, f32 m, char c) -{ - f32 *d = data; +inline +void vertex_rect_create( + Vertex3DTextureColorIndex* __restrict vertices, uint32* __restrict index, f32 zindex, + f32 x, f32 y, f32 width, f32 height, int32 align_h, int32 align_v, + uint32 color_index = 0, f32 tex_x1 = 0.0f, f32 tex_y1 = 0.0f, f32 tex_x2 = 0.0f, f32 tex_y2 = 0.0f +) { + if (align_h == UI_ALIGN_H_RIGHT) { + x -= width; + } else if (align_h == UI_ALIGN_H_CENTER) { + x -= width / 2; + } - // Texture atlas is 16 characters - // 1 / 16 = 0.0625 - f32 a = 0.0625; - f32 b = 0.0625 * 2; + if (align_v == UI_ALIGN_V_TOP) { + y -= height; + } else if (align_v == UI_ALIGN_V_CENTER) { + y -= height / 2; + } - // ascii offset - int32 w = c - 32; + // Degenerate triangles + // They are alternating every loop BUT since we use references they look the same in code + // WARNING: Before using we must make sure that the 0 index is defined + // The easiest way is to just define a "degenerate" starting point + vertices[*index].position.x = vertices[*index - 1].position.x; + vertices[*index].position.y = vertices[*index - 1].position.y; + vertices[*index].position.z = zindex; + vertices[*index].tex_coord.x = 0; + vertices[*index].tex_coord.y = 0; + vertices[*index].color = 0; + ++(*index); - f32 du = (w % 16) * a; - f32 dv = 1 - (w / 16) * b - b; + vertices[*index].position.x = x; + vertices[*index].position.y = y; + vertices[*index].position.z = zindex; + vertices[*index].tex_coord.x = 0; + vertices[*index].tex_coord.y = 0; + vertices[*index].color = 0; + ++(*index); - // Quad data (2 triangles) - *(d++) = x - n; *(d++) = y - m; - *(d++) = du + 0; *(d++) = dv; - *(d++) = x + n; *(d++) = y - m; - *(d++) = du + a; *(d++) = dv; - *(d++) = x + n; *(d++) = y + m; - *(d++) = du + a; *(d++) = dv + b; - *(d++) = x - n; *(d++) = y - m; - *(d++) = du + 0; *(d++) = dv; - *(d++) = x + n; *(d++) = y + m; - *(d++) = du + a; *(d++) = dv + b; - *(d++) = x - n; *(d++) = y + m; - *(d++) = du + 0; *(d++) = dv + b; + // Rectangle + vertices[*index].position.x = x; + vertices[*index].position.y = y; + vertices[*index].position.z = zindex; + vertices[*index].tex_coord.x = tex_x1; + vertices[*index].tex_coord.y = tex_y1; + vertices[*index].color = color_index; + ++(*index); + + // Depending on the orientation we either need to add or subtract height -> we use branchless code for that + vertices[*index].position.x = x; + vertices[*index].position.y = y + height; + vertices[*index].position.z = zindex; + vertices[*index].tex_coord.x = tex_x1; + vertices[*index].tex_coord.y = tex_y2; + vertices[*index].color = color_index; + ++(*index); + + vertices[*index].position.x = x + width; + vertices[*index].position.y = y; + vertices[*index].position.z = zindex; + vertices[*index].tex_coord.x = tex_x2; + vertices[*index].tex_coord.y = tex_y1; + vertices[*index].color = color_index; + ++(*index); + + vertices[*index].position.x = x + width; + vertices[*index].position.y = y + height; + vertices[*index].position.z = zindex; + vertices[*index].tex_coord.x = tex_x2; + vertices[*index].tex_coord.y = tex_y2; + vertices[*index].color = color_index; + ++(*index); } -void font_string_dimension(const char *str, v2_int32* dim, const int* width_lookup) -{ - size_t length = strlen(str); - int32 width = 0; +void text_calculate_dimensions( + f32* __restrict width, f32* __restrict height, + const Font* __restrict font, const char* text, f32 scale, int32 length +) { + f32 x = 0; + f32 y = font->line_height * scale; - for (int32 i = 0; i < length; ++i) { - if (str[i] == '\n') { - if (width > dim->x) { - dim->x = width; - } + f32 offset_x = 0; - width = 0; - ++dim->y; + // @todo remember to restrict to width/height if value > 0 -> force width to remain below certain value + + for (int i = 0; i < length; ++i) { + int32 character = utf8_get_char_at(text, i); + + if (character == '\n') { + x = OMS_MAX(x, offset_x); + y += font->line_height * scale; + + offset_x = 0; + + continue; } - width += width_lookup[str[i]]; + Glyph* glyph = NULL; + for (int j = 0; j < font->glyph_count; ++j) { + if (font->glyphs[j].codepoint == character) { + glyph = &font->glyphs[j]; + + break; + } + } + + if (!glyph) { + continue; + } + + offset_x += (glyph->metrics.width + glyph->metrics.offset_x) * scale; } - if (width > dim->x) { - dim->x = width; + *width = OMS_MAX(x, offset_x); + *height = y; +} + +void vertex_text_create( + Vertex3DTextureColorIndex* __restrict vertices, uint32* __restrict index, f32 zindex, + f32 x, f32 y, f32 width, f32 height, int32 align_h, int32 align_v, + const Font* __restrict font, const char* __restrict text, f32 size, uint32 color_index = 0 +) { + int32 length = utf8_strlen(text); + float scale = size / font->size; + + // If we do a different alignment we need to pre-calculate the width and height + if (align_h != 0 || align_v != 0) { + text_calculate_dimensions(&width, &height, font, text, scale, length); + + if (align_h == UI_ALIGN_H_RIGHT) { + x -= width; + } else if (align_h == UI_ALIGN_H_CENTER) { + x -= width / 2; + } + + if (align_v == UI_ALIGN_V_TOP) { + y -= height; + } else if (align_v == UI_ALIGN_V_CENTER) { + y -= height / 2; + } } - if (width > 0) { - ++dim->y; + f32 offset_x = x; + for (int i = 0; i < length; ++i) { + int32 character = utf8_get_char_at(text, i); + + if (character == '\n') { + y += font->line_height * scale; + offset_x = x; + + continue; + } + + Glyph* glyph = NULL; + for (int j = 0; j < font->glyph_count; ++j) { + if (font->glyphs[j].codepoint == character) { + glyph = &font->glyphs[j]; + + break; + } + } + + if (!glyph) { + continue; + } + + vertex_rect_create( + vertices, index, zindex, + offset_x, y, glyph->metrics.width * scale, glyph->metrics.height * scale, UI_ALIGN_H_LEFT, UI_ALIGN_V_BOTTOM, + color_index, glyph->coords.x1, glyph->coords.y1, glyph->coords.x2, glyph->coords.y2 + ); + + offset_x += (glyph->metrics.width + glyph->metrics.offset_x) * scale; } + + // @question How and where to cut off text out of view (here or somewhere else) + // We could just prepare the entire text here but then decide what to render later? + // @todo If width or height (usually just width) > 0 we use those values for automatic wrapping + // This way we can ensure no overflow easily + // @todo implement line alignment, currently only total alignment is considered } inline diff --git a/gpuapi/opengl/OpenglUtils.h b/gpuapi/opengl/OpenglUtils.h index 2d272cb..7254d69 100644 --- a/gpuapi/opengl/OpenglUtils.h +++ b/gpuapi/opengl/OpenglUtils.h @@ -559,77 +559,6 @@ int get_gpu_free_memory() return available; } -struct TextRender { - uint32 align; - uint32 x; - uint32 y; - f32 scale; - VertexRef vertices; - char* text; - - uint32 sampler_id; - - TextShader shader_data; -}; - -#define TEXT_ALIGN_LEFT 0 -#define TEXT_ALIGN_CENTER 1 -#define TEXT_ALIGN_RIGHT 2 - -// @performance This needs to handle batched randering, isolated rendering is WAY to inefficient -inline -void render_text_batched( - RingMemory* ring, - int32 width, int32 height, - TextRender* text_data -) { - glUseProgram(text_data->shader_data.program_id); - glUniform1i(text_data->shader_data.sampler_addr, text_data->vertices.sampler_id); - - // @performance Instead of re-creating the matrix every render call just use the id to activate it - // 2d projection - if (text_data->shader_data.matrix_id == 0) { - f32 matrix[16] = {}; - mat4_ortho_sparse_rh(matrix, 0.0f, (f32) width, 0.0f, (f32) height, -1.0f, 1.0f); - glUniformMatrix4fv(text_data->shader_data.matrix_addr, 1, GL_FALSE, matrix); - text_data->shader_data.matrix_id = 1; - // @bug this is wrong, we need to make buffer instead. We don't want to upload the matrix every time - // Isn't this buffer the same for every text? - // If yes, consider to save the orth projection matrix globally and change it whenever we change the resolution/window dimensions - } else { - // @question Do we even need to bind it if we never change it? - // We also never bind our projection matrix apart from the first time and it works. This should be the same no? - //glBindVertexArray(text_data->shader_data.matrix_id); - } - - int32 length = (int32) strlen(text_data->text); - f32 x = text_data->x - text_data->scale * text_data->align * (length - 1) / 2; - - // @performance Only create when the text got removed from memory - if (text_data->vertices.data_id == 0) { - GLfloat *data = (GLfloat *) ring_get_memory(ring, sizeof(GLfloat) * 6 * 4 * length); - - for (int32 i = 0; i < length; i++) { - make_character(data + i * 24, x, (f32) text_data->y, text_data->scale / 2, text_data->scale, text_data->text[i]); - x += text_data->scale; - } - - text_data->vertices.data_id = gpuapi_buffer_generate(sizeof(GLfloat) * 6 * 4 * length, data); - } else { - glBindBuffer(GL_ARRAY_BUFFER, text_data->vertices.data_id); - //glBufferData(GL_ARRAY_BUFFER, size, data, GL_STATIC_DRAW); Is this required? - } - - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - draw_triangles_2d(&text_data->vertices, text_data->vertices.data_id, length * 6); - glDisable(GL_BLEND); - - // @performance We should only delete the buffer, when the text becomes invisible - // @todo remember to implement a remove logic for all the buffers - //glDeleteBuffers(1, &text_data->vertices.data_id); -} - /* void render_9_patch(GLuint texture, int32 imgWidth, int32 imgHeight, diff --git a/input/Input.h b/input/Input.h index 7122ee6..21512ce 100644 --- a/input/Input.h +++ b/input/Input.h @@ -11,6 +11,7 @@ #include "../stdlib/Types.h" #include "../utils/BitUtils.h" +#include "../memory/BufferMemory.h" #include "ControllerInput.h" // How many concurrent mouse/secondary input device presses to we recognize @@ -526,6 +527,9 @@ void input_set_controller_state(Input* input, ControllerInput* controller, uint6 void input_hotkey_state(Input* input) { + uint8 old_hotkeys[MAX_KEY_PRESSES]; + memcpy(old_hotkeys, input->state.state_hotkeys, sizeof(uint8) * MAX_KEY_PRESSES); + memset(input->state.state_hotkeys, 0, sizeof(uint8) * MAX_KEY_PRESSES); int32 active_hotkeys = 0; @@ -576,15 +580,27 @@ input_hotkey_state(Input* input) return; } + // Hotkey already active + // @question Do we even need this? This shouldn't happen anyway?! + if (hotkey_is_active(&input->state, hotkeys_for_key[possible_hotkey_idx])) { + continue; + } + bool is_pressed = hotkey_keys_are_active(&input->state, mapping, hotkeys_for_key[possible_hotkey_idx]); // store active hotkey, if it is not already active - if (is_pressed && !hotkey_is_active(&input->state, hotkeys_for_key[possible_hotkey_idx])) { + if (is_pressed) { input->state.state_hotkeys[active_hotkeys] = hotkeys_for_key[possible_hotkey_idx]; ++active_hotkeys; // Run callback if defined - if (input->input_mapping1.callbacks[hotkeys_for_key[possible_hotkey_idx]] != 0) { + if (input->input_mapping1.callbacks[hotkeys_for_key[possible_hotkey_idx]] != 0 + && old_hotkeys[0] != hotkeys_for_key[possible_hotkey_idx] + && old_hotkeys[1] != hotkeys_for_key[possible_hotkey_idx] + && old_hotkeys[2] != hotkeys_for_key[possible_hotkey_idx] + && old_hotkeys[3] != hotkeys_for_key[possible_hotkey_idx] + && old_hotkeys[4] != hotkeys_for_key[possible_hotkey_idx] + ) { input->input_mapping1.callbacks[hotkeys_for_key[possible_hotkey_idx]](input->callback_data); } } diff --git a/models/settings/Settings.h b/models/settings/Settings.h index 21b3f93..7f1f1e5 100644 --- a/models/settings/Settings.h +++ b/models/settings/Settings.h @@ -289,6 +289,10 @@ struct CSettings { byte game_chat_size = 128; int32 game_chat_pos[2] = { -1, -1 }; + // @todo replace settings below with bit flag + // UI + uint64 ui_visibility_flags = 0; + // HUD bool game_show_health_bar_self = false; bool game_show_health_bar_player = false; diff --git a/models/settings/setting_types.h b/models/settings/setting_types.h index 3c66334..fb9a256 100644 --- a/models/settings/setting_types.h +++ b/models/settings/setting_types.h @@ -74,4 +74,6 @@ #define SETTING_TYPE_DISABLED 0x00 #define SETTING_TYPE_UNLIMITED 0x00 +#define SETTING_UI_VISIBILITY_FPS 1 + #endif \ No newline at end of file diff --git a/object/Vertex.h b/object/Vertex.h index 6f6bef1..0f8ff1f 100644 --- a/object/Vertex.h +++ b/object/Vertex.h @@ -18,6 +18,12 @@ struct Vertex3D { v4_f32 color; }; +struct Vertex3DTextureColor { + v3_f32 position; + v2_f32 tex_coord; + v4_f32 color; +}; + struct Vertex3DTextureColorIndex { v3_f32 position; v2_f32 tex_coord;