[Refactor] Implement batched text rendering.
Overhauls text rendering to improve performance. Renderer was really ineffient, sending a separate GPU draw call for every character rendered. This created a bottleneck, especially with text-heavy UI stuff like the wallpaper. - The 'TextRenderer' now generates a single texture atlas for all font glyhs on load so all characters share a single texture. - 'begin()' / 'flush()' was added to the 'TextRenderer'. Calls to 'render_text' now buffer vertex data (position, texture coords, colour) instead of drawing immediately. A single 'flush()' call at the end of a pass draws all buffered text in one draw call. - A simple batching introduced some shitty visual layering bugs. to solve this, a staged flushing architecture has been implemented. Each logical UI component is now responsible for managing its own rendering passes, beginning and flushing text batches as needed to ensure correct back-to-front layering.
This commit is contained in:
parent
9d2a2f4195
commit
c3316b3da1
@ -1,11 +1,11 @@
|
|||||||
#version 330 core
|
#version 330 core
|
||||||
in vec2 TexCoords;
|
in vec2 TexCoords;
|
||||||
|
in vec3 ourColor;
|
||||||
out vec4 color;
|
out vec4 color;
|
||||||
|
|
||||||
uniform sampler2D text;
|
uniform sampler2D text;
|
||||||
uniform vec3 textColor;
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec4 sampled = vec4(1.0, 1.0, 1.0, texture(text, TexCoords).r);
|
vec4 sampled = vec4(1.0, 1.0, 1.0, texture(text, TexCoords).r);
|
||||||
color = vec4(textColor, 1.0) * sampled;
|
color = vec4(ourColor, 1.0) * sampled;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,15 @@
|
|||||||
#version 330 core
|
#version 330 core
|
||||||
layout(location = 0) in vec4 vertex;
|
layout (location = 0) in vec2 aPos;
|
||||||
|
layout (location = 1) in vec2 aTexCoords;
|
||||||
|
layout (location = 2) in vec3 aColor;
|
||||||
|
|
||||||
out vec2 TexCoords;
|
out vec2 TexCoords;
|
||||||
|
out vec3 ourColor;
|
||||||
|
|
||||||
uniform mat4 projection;
|
uniform mat4 projection;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
gl_Position = projection * vec4(vertex.xy, 0.0, 1.0);
|
gl_Position = projection * vec4(aPos, 0.0, 1.0);
|
||||||
TexCoords = vertex.zw;
|
TexCoords = aTexCoords;
|
||||||
|
ourColor = aColor;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "gfx/types.h"
|
#include "gfx/types.h"
|
||||||
|
|
||||||
|
|||||||
@ -17,24 +17,37 @@ TextRenderer::TextRenderer(unsigned int screen_width, unsigned int screen_height
|
|||||||
_txt_shader->use();
|
_txt_shader->use();
|
||||||
_txt_shader->set_mat4("projection", _projecton);
|
_txt_shader->set_mat4("projection", _projecton);
|
||||||
|
|
||||||
/* Configure VAO/VBO for texture coords. */
|
/* Configure VAO/VBO for batch rendering. */
|
||||||
glGenVertexArrays(1, &_vao);
|
glGenVertexArrays(1, &_vao);
|
||||||
glGenBuffers(1, &_vbo);
|
glGenBuffers(1, &_vbo);
|
||||||
glBindVertexArray(_vao);
|
glBindVertexArray(_vao);
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, _vbo);
|
glBindBuffer(GL_ARRAY_BUFFER, _vbo);
|
||||||
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6 * 4, NULL, GL_DYNAMIC_DRAW);
|
/* Pre-allocate buffer memory. */
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, sizeof(TextVertex) * MAX_VERTICES, nullptr, GL_DYNAMIC_DRAW);
|
||||||
|
|
||||||
|
/* Position attribute. */
|
||||||
glEnableVertexAttribArray(0);
|
glEnableVertexAttribArray(0);
|
||||||
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);
|
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(TextVertex), (void*)0);
|
||||||
|
/* Texture coord attribute. */
|
||||||
|
glEnableVertexAttribArray(1);
|
||||||
|
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(TextVertex), (void*)offsetof(TextVertex, s));
|
||||||
|
/* Colour attribute. */
|
||||||
|
glEnableVertexAttribArray(2);
|
||||||
|
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(TextVertex), (void*)offsetof(TextVertex, r));
|
||||||
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||||
glBindVertexArray(0);
|
glBindVertexArray(0);
|
||||||
|
|
||||||
|
/* Pre-allocate vector capacity. */
|
||||||
|
_vertices.reserve(MAX_VERTICES);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextRenderer::~TextRenderer(void) {
|
TextRenderer::~TextRenderer(void) {
|
||||||
delete _txt_shader;
|
delete _txt_shader;
|
||||||
|
glDeleteTextures(1, &_atlas_texture_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextRenderer::load_font(const char* font_path, unsigned int font_size) {
|
void TextRenderer::load_font(const char* font_path, unsigned int font_size) {
|
||||||
_chars.clear();
|
|
||||||
FT_Library ft;
|
FT_Library ft;
|
||||||
if(FT_Init_FreeType(&ft)) {
|
if(FT_Init_FreeType(&ft)) {
|
||||||
printf("Could not init FreeType Library\n");
|
printf("Could not init FreeType Library\n");
|
||||||
@ -50,77 +63,120 @@ void TextRenderer::load_font(const char* font_path, unsigned int font_size) {
|
|||||||
FT_Set_Pixel_Sizes(face, 0, font_size);
|
FT_Set_Pixel_Sizes(face, 0, font_size);
|
||||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||||
|
|
||||||
|
/* Calculate atlas dimensions. */
|
||||||
|
unsigned int atlas_width = 0;
|
||||||
|
unsigned int atlas_height = 0;
|
||||||
|
unsigned int max_height = 0;
|
||||||
|
|
||||||
for(unsigned char c = 0; c < 128; c++) {
|
for(unsigned char c = 0; c < 128; c++) {
|
||||||
if(FT_Load_Char(face, c, FT_LOAD_RENDER)) {
|
if(FT_Load_Char(face, c, FT_LOAD_RENDER)) {
|
||||||
printf("Failed to load Glyph for char %c\n", c);
|
printf("Failed to load Glyph for char %c\n", c);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int texture;
|
atlas_width += face->glyph->bitmap.width;
|
||||||
glGenTextures(1, &texture);
|
max_height = std::max(max_height, face->glyph->bitmap.rows);
|
||||||
glBindTexture(GL_TEXTURE_2D, texture);
|
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, face->glyph->bitmap.width,
|
|
||||||
face->glyph->bitmap.rows, 0, GL_RED, GL_UNSIGNED_BYTE,
|
|
||||||
face->glyph->bitmap.buffer);
|
|
||||||
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
||||||
|
|
||||||
character ch = {
|
|
||||||
texture,
|
|
||||||
{ (int)face->glyph->bitmap.width, (int)face->glyph->bitmap.rows },
|
|
||||||
{ face->glyph->bitmap_left, face->glyph->bitmap_top },
|
|
||||||
(unsigned int)face->glyph->advance.x
|
|
||||||
};
|
|
||||||
_chars.insert(std::pair<char, character>(c, ch));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
atlas_height = max_height;
|
||||||
|
|
||||||
|
/* Create the atlas texture. */
|
||||||
|
glGenTextures(1, &_atlas_texture_id);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, _atlas_texture_id);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, atlas_width, atlas_height, 0, GL_RED,
|
||||||
|
GL_UNSIGNED_BYTE, 0);
|
||||||
|
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
|
|
||||||
|
/* Fill the atlas texture with data. */
|
||||||
|
int x_offset = 0;
|
||||||
|
for(unsigned char c = 0; c < 128; c++) {
|
||||||
|
if(FT_Load_Char(face, c, FT_LOAD_RENDER)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Copy glyph bitmap to atlas. */
|
||||||
|
glTexSubImage2D(
|
||||||
|
GL_TEXTURE_2D, 0, x_offset, 0,
|
||||||
|
face->glyph->bitmap.width, face->glyph->bitmap.rows,
|
||||||
|
GL_RED, GL_UNSIGNED_BYTE, face->glyph->bitmap.buffer
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Store character metrics and texture coords. */
|
||||||
|
_chars[c] = {
|
||||||
|
{(int)face->glyph->bitmap.width, (int)face->glyph->bitmap.rows },
|
||||||
|
{face->glyph->bitmap_left, face->glyph->bitmap_top},
|
||||||
|
(unsigned int)face->glyph->advance.x,
|
||||||
|
(float)x_offset / atlas_width,
|
||||||
|
0.0f,
|
||||||
|
(float)face->glyph->bitmap.width / atlas_width,
|
||||||
|
(float)face->glyph->bitmap.rows / atlas_height
|
||||||
|
};
|
||||||
|
x_offset += face->glyph->bitmap.width;
|
||||||
|
}
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
|
||||||
FT_Done_Face(face);
|
FT_Done_Face(face);
|
||||||
FT_Done_FreeType(ft);
|
FT_Done_FreeType(ft);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextRenderer::render_text(const char* text, float x, float y, float scale,
|
void TextRenderer::begin(void) {
|
||||||
const Color& color) {
|
_vertices.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextRenderer::flush(void) {
|
||||||
|
if(_vertices.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_txt_shader->use();
|
_txt_shader->use();
|
||||||
_txt_shader->set_vec3("textColor", color.r, color.g, color.b);
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, _atlas_texture_id);
|
||||||
glBindVertexArray(_vao);
|
glBindVertexArray(_vao);
|
||||||
|
|
||||||
for(const char* p = text; *p; p++) {
|
glBindBuffer(GL_ARRAY_BUFFER, _vbo);
|
||||||
character ch = _chars[*p];
|
glBufferSubData(GL_ARRAY_BUFFER, 0, _vertices.size() * sizeof(TextVertex), _vertices.data());
|
||||||
|
|
||||||
float xpos = x + ch.bearing[0] * scale;
|
glDrawArrays(GL_TRIANGLES, 0, _vertices.size());
|
||||||
float ypos = y - (ch.size[1] - ch.bearing[1]) * scale;
|
|
||||||
float w = ch.size[0] * scale;
|
|
||||||
float h = ch.size[1] * scale;
|
|
||||||
|
|
||||||
float vertices[6][4] = {
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||||
{ xpos, ypos + h, 0.0f, 0.0f },
|
|
||||||
{ xpos, ypos, 0.0f, 1.0f },
|
|
||||||
{ xpos + w, ypos, 1.0f, 1.0f },
|
|
||||||
{ xpos, ypos + h, 0.0f, 0.0f },
|
|
||||||
{ xpos + w, ypos, 1.0f, 1.0f },
|
|
||||||
{ xpos + w, ypos + h, 1.0f, 0.0f }
|
|
||||||
};
|
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, ch.texture_id);
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, _vbo);
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
||||||
glDrawArrays(GL_TRIANGLES, 0, 6);
|
|
||||||
x += (ch.advance >> 6) * scale;
|
|
||||||
}
|
|
||||||
glBindVertexArray(0);
|
glBindVertexArray(0);
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TextRenderer::render_text(const char* text, float x, float y, const Color& color) {
|
||||||
|
for(const char* p = text; *p; p++) {
|
||||||
|
unsigned char c = *p;
|
||||||
|
character ch = _chars[c];
|
||||||
|
|
||||||
|
float xpos = x + ch.bearing[0];
|
||||||
|
float ypos = y - (ch.size[1] - ch.bearing[1]);
|
||||||
|
float w = ch.size[0];
|
||||||
|
float h = ch.size[1];
|
||||||
|
|
||||||
|
_vertices.push_back({xpos, ypos+h, ch.tx, ch.ty, color.r, color.g, color.b});
|
||||||
|
_vertices.push_back({xpos, ypos, ch.tx, ch.ty+ch.th, color.r, color.g, color.b});
|
||||||
|
_vertices.push_back({xpos+w, ypos, ch.tx+ch.tw, ch.ty+ch.th, color.r, color.g, color.b});
|
||||||
|
|
||||||
|
_vertices.push_back({xpos, ypos+h, ch.tx, ch.ty, color.r, color.g, color.b});
|
||||||
|
_vertices.push_back({xpos+w, ypos, ch.tx+ch.tw, ch.ty+ch.th, color.r, color.g, color.b});
|
||||||
|
_vertices.push_back({xpos+w, ypos+h, ch.tx+ch.tw, ch.ty, color.r, color.g, color.b});
|
||||||
|
|
||||||
|
x += (ch.advance >> 6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
float TextRenderer::get_text_width(const char* text, float scale) {
|
float TextRenderer::get_text_width(const char* text, float scale) {
|
||||||
float width = 0.0f;
|
float width = 0.0f;
|
||||||
for(const char* p = text; *p; p++) {
|
for(const char* p = text; *p; p++) {
|
||||||
width += (_chars[*p].advance >> 6) * scale;
|
unsigned char c = *p;
|
||||||
|
if(c < 128) {
|
||||||
|
width += (_chars[c].advance >> 6) * scale;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return width;
|
return width;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,33 +1,47 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <ft2build.h>
|
#include <ft2build.h>
|
||||||
|
#include <vector>
|
||||||
#include FT_FREETYPE_H
|
#include FT_FREETYPE_H
|
||||||
#include <map>
|
|
||||||
|
|
||||||
#include "shader.h"
|
#include "shader.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
|
struct TextVertex {
|
||||||
|
float x, y, s, t, r, g, b;
|
||||||
|
};
|
||||||
|
|
||||||
/* State of a single charactrer glyph. */
|
/* State of a single charactrer glyph. */
|
||||||
struct character {
|
struct character {
|
||||||
unsigned int texture_id;
|
|
||||||
int size[2];
|
int size[2];
|
||||||
int bearing[2];
|
int bearing[2];
|
||||||
unsigned int advance;
|
unsigned int advance;
|
||||||
|
float tx, ty; /* x and y offset of glyph in texture atlas. */
|
||||||
|
float tw, th; /* width and height of glyph in texture atlas. */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const int MAX_GLYPHS_PER_BATCH = 10000;
|
||||||
|
const int VERTICES_PER_GLYPH = 6;
|
||||||
|
const int MAX_VERTICES = MAX_GLYPHS_PER_BATCH * VERTICES_PER_GLYPH;
|
||||||
|
|
||||||
class TextRenderer {
|
class TextRenderer {
|
||||||
public:
|
public:
|
||||||
TextRenderer(unsigned int screen_width, unsigned int screen_height);
|
TextRenderer(unsigned int screen_width, unsigned int screen_height);
|
||||||
~TextRenderer(void);
|
~TextRenderer(void);
|
||||||
|
|
||||||
void load_font(const char* font_path, unsigned int font_size);
|
void load_font(const char* font_path, unsigned int font_size);
|
||||||
void render_text(const char* text, float x, float y, float scale, const Color& color);
|
void render_text(const char* text, float x, float y, const Color& color);
|
||||||
float get_text_width(const char* text, float scale);
|
float get_text_width(const char* text, float scale);
|
||||||
|
|
||||||
|
void begin(void);
|
||||||
|
void flush(void);
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Shader* _txt_shader;
|
Shader* _txt_shader;
|
||||||
unsigned int _vao, _vbo;
|
unsigned int _vao, _vbo;
|
||||||
std::map<char, character> _chars;
|
unsigned int _atlas_texture_id;
|
||||||
|
character _chars[128];
|
||||||
|
std::vector<TextVertex> _vertices;
|
||||||
float _projecton[16];
|
float _projecton[16];
|
||||||
};
|
};
|
||||||
|
|||||||
@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
#include "terminal.h"
|
#include "terminal.h"
|
||||||
#include "client_network.h"
|
#include "client_network.h"
|
||||||
#include "gfx/txt_renderer.h"
|
|
||||||
#include "gfx/types.h"
|
#include "gfx/types.h"
|
||||||
#include "ui/window_action.h"
|
#include "ui/window_action.h"
|
||||||
|
|
||||||
@ -118,6 +117,8 @@ void Terminal::render(const RenderContext& context, int x, int y_screen, int y_g
|
|||||||
float line_height = 20.0f;
|
float line_height = 20.0f;
|
||||||
float padding = 5.0f;
|
float padding = 5.0f;
|
||||||
|
|
||||||
|
context.ui_renderer->begin_text();
|
||||||
|
|
||||||
/* Enable scissor test to clip rendering to the window content area. */
|
/* Enable scissor test to clip rendering to the window content area. */
|
||||||
glEnable(GL_SCISSOR_TEST);
|
glEnable(GL_SCISSOR_TEST);
|
||||||
glScissor(x, y_gl, width, height);
|
glScissor(x, y_gl, width, height);
|
||||||
@ -144,4 +145,6 @@ void Terminal::render(const RenderContext& context, int x, int y_screen, int y_g
|
|||||||
|
|
||||||
/* Disable scissor test. */
|
/* Disable scissor test. */
|
||||||
glDisable(GL_SCISSOR_TEST);
|
glDisable(GL_SCISSOR_TEST);
|
||||||
|
|
||||||
|
context.ui_renderer->flush_text();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -196,21 +196,28 @@ UIWindow* Desktop::get_focused_window(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Desktop::render(const RenderContext& context) {
|
void Desktop::render(const RenderContext& context) {
|
||||||
|
/* Pass 1: Background. */
|
||||||
|
context.ui_renderer->begin_text();
|
||||||
_render_wallpaper(context.ui_renderer);
|
_render_wallpaper(context.ui_renderer);
|
||||||
if(_launcher_is_open) {
|
context.ui_renderer->flush_text();
|
||||||
_launcher->render(context.ui_renderer);
|
|
||||||
}
|
/* Pass 2: Windows (back to front). */
|
||||||
_taskbar->render(context.ui_renderer, _windows, _focused_window);
|
|
||||||
/* Render non-focused windows first. */
|
|
||||||
for(const auto& win : _windows) {
|
for(const auto& win : _windows) {
|
||||||
if(win.get() != _focused_window && !win->is_minimized()) {
|
if(win.get() != _focused_window && !win->is_minimized()) {
|
||||||
win.get()->render(context);
|
win.get()->render(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Render focused window last so it's on top. */
|
|
||||||
if(_focused_window && !_focused_window->is_minimized()) {
|
if(_focused_window && !_focused_window->is_minimized()) {
|
||||||
_focused_window->render(context);
|
_focused_window->render(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Pass 3: Top-level static UI (taskbar, Launcher, etc.). */
|
||||||
|
context.ui_renderer->begin_text();
|
||||||
|
if(_launcher_is_open) {
|
||||||
|
_launcher->render(context.ui_renderer);
|
||||||
|
}
|
||||||
|
_taskbar->render(context.ui_renderer, _windows, _focused_window);
|
||||||
|
context.ui_renderer->flush_text();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Desktop::_render_wallpaper(UIRenderer* ui_renderer) {
|
void Desktop::_render_wallpaper(UIRenderer* ui_renderer) {
|
||||||
|
|||||||
@ -52,8 +52,20 @@ void Editor::render(const RenderContext& context, int x, int y_screen, int y_gl,
|
|||||||
int content_y = y_screen + menu_bar_height;
|
int content_y = y_screen + menu_bar_height;
|
||||||
int content_height = height - menu_bar_height;
|
int content_height = height - menu_bar_height;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Render editor in two passes to ensure the dropdown
|
||||||
|
* menu appears on top of the main text view.
|
||||||
|
*/
|
||||||
|
/* Pass 1: Main Content. */
|
||||||
|
context.ui_renderer->begin_text();
|
||||||
|
_menu_bar->render_bar(context.ui_renderer, x, y_screen, width);
|
||||||
_view->render(context.ui_renderer, x, content_y, width, content_height, context.show_cursor);
|
_view->render(context.ui_renderer, x, content_y, width, content_height, context.show_cursor);
|
||||||
_menu_bar->render(context.ui_renderer, x, y_screen, width);
|
context.ui_renderer->flush_text();
|
||||||
|
|
||||||
|
/* Pass 2: Dropdown Menu. */
|
||||||
|
context.ui_renderer->begin_text();
|
||||||
|
_menu_bar->render_dropdown(context.ui_renderer, x, y_screen, width);
|
||||||
|
context.ui_renderer->flush_text();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Editor::scroll(int amount, int content_height) {
|
void Editor::scroll(int amount, int content_height) {
|
||||||
|
|||||||
@ -95,7 +95,7 @@ Screen MainMenu::update(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MainMenu::render(UIRenderer* ui_renderer) {
|
void MainMenu::render(UIRenderer* ui_renderer) {
|
||||||
_render_background(ui_renderer->get_text_renderer());
|
_render_background(ui_renderer);
|
||||||
|
|
||||||
/* Button colours. */
|
/* Button colours. */
|
||||||
const Color button_color = { 0.1f, 0.15f, 0.2f };
|
const Color button_color = { 0.1f, 0.15f, 0.2f };
|
||||||
@ -130,10 +130,9 @@ void MainMenu::_update_background(void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainMenu::_render_background(TextRenderer* txt_renderer) {
|
void MainMenu::_render_background(UIRenderer* ui_renderer) {
|
||||||
const Color background_text_color = { 0.0f, 0.35f, 0.15f }; /* Dark green. */
|
const Color background_text_color = { 0.0f, 0.35f, 0.15f }; /* Dark green. */
|
||||||
for(const auto& line : _background_text) {
|
for(const auto& line : _background_text) {
|
||||||
txt_renderer->render_text(line.text.c_str(), line.x, line.y, 1.0f,
|
ui_renderer->render_text(line.text.c_str(), line.x, line.y, background_text_color);
|
||||||
background_text_color);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,7 +30,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void _update_background(void);
|
void _update_background(void);
|
||||||
void _render_background(TextRenderer* txdt_renderer);
|
void _render_background(UIRenderer* ui_renderer);
|
||||||
|
|
||||||
/* For animated background. */
|
/* For animated background. */
|
||||||
struct ScrollingText {
|
struct ScrollingText {
|
||||||
|
|||||||
@ -72,10 +72,9 @@ void MenuBar::handle_event(SDL_Event* event, int window_x, int window_y) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MenuBar::render(UIRenderer* ui_renderer, int x, int y, int width) {
|
void MenuBar::render_bar(UIRenderer* ui_renderer, int x, int y, int width) {
|
||||||
const Color bg_color = {0.15f, 0.17f, 0.19f};
|
const Color bg_color = {0.15f, 0.17f, 0.19f};
|
||||||
const Color text_color = {0.9f, 0.9f, 0.9f};
|
const Color text_color = {0.9f, 0.9f, 0.9f};
|
||||||
const Color hover_color = {0.3f, 0.32f, 0.34f};
|
|
||||||
|
|
||||||
ui_renderer->draw_rect(x, y, width, _height, bg_color);
|
ui_renderer->draw_rect(x, y, width, _height, bg_color);
|
||||||
|
|
||||||
@ -84,16 +83,23 @@ void MenuBar::render(UIRenderer* ui_renderer, int x, int y, int width) {
|
|||||||
int menu_width = 60;
|
int menu_width = 60;
|
||||||
ui_renderer->render_text(_menus[i].label.c_str(), menu_x+10, y+20, text_color);
|
ui_renderer->render_text(_menus[i].label.c_str(), menu_x+10, y+20, text_color);
|
||||||
|
|
||||||
if(_open_menu_index == (int)i) {
|
|
||||||
int item_y = y + _height; /* Draw items below the bar. */
|
|
||||||
int item_height = 30;
|
|
||||||
int dropdown_width = 150;
|
|
||||||
for(const auto& item : _menus[i].items) {
|
|
||||||
ui_renderer->draw_rect(menu_x, item_y, dropdown_width, item_height, bg_color);
|
|
||||||
ui_renderer->render_text(item.label.c_str(), menu_x+10, item_y+20, text_color);
|
|
||||||
item_y += item_height;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
menu_x += menu_width;
|
menu_x += menu_width;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MenuBar::render_dropdown(UIRenderer* ui_renderer, int x, int y, int width ) {
|
||||||
|
if(_open_menu_index == -1) return;
|
||||||
|
|
||||||
|
const Color bg_color = { 0.15f, 0.17f, 0.19f };
|
||||||
|
const Color text_color = { 0.9f, 0.9f, 0.9f };
|
||||||
|
|
||||||
|
int menu_x = x + (_open_menu_index*60);
|
||||||
|
int item_y = y + _height;
|
||||||
|
int item_height = 30;
|
||||||
|
int dropdown_width = 150;
|
||||||
|
for(const auto& item : _menus[_open_menu_index].items) {
|
||||||
|
ui_renderer->draw_rect(menu_x, item_y, dropdown_width, item_height, bg_color);
|
||||||
|
ui_renderer->render_text(item.label.c_str(), menu_x+10, item_y+20, text_color);
|
||||||
|
item_y += item_height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -28,7 +28,8 @@ public:
|
|||||||
std::function<void()> action);
|
std::function<void()> action);
|
||||||
|
|
||||||
void handle_event(SDL_Event* event, int window_x, int window_y);
|
void handle_event(SDL_Event* event, int window_x, int window_y);
|
||||||
void render(UIRenderer* ui_renderer, int x, int y, int width);
|
void render_bar(UIRenderer* ui_renderer, int x, int y, int width);
|
||||||
|
void render_dropdown(UIRenderer* ui_renderer, int x, int y, int width);
|
||||||
|
|
||||||
int get_height(void) const;
|
int get_height(void) const;
|
||||||
private:
|
private:
|
||||||
|
|||||||
@ -25,7 +25,17 @@ void UIRenderer::render_text(const char* text, int x, int y, const Color& color)
|
|||||||
if(!_txt_renderer) return;
|
if(!_txt_renderer) return;
|
||||||
/* Convert the screen-space baseline y-coord to GL-space baseline y-coord. */
|
/* Convert the screen-space baseline y-coord to GL-space baseline y-coord. */
|
||||||
int y_gl = _screen_height - y;
|
int y_gl = _screen_height - y;
|
||||||
_txt_renderer->render_text(text, x, y_gl, 1.0f, color);
|
_txt_renderer->render_text(text, x, y_gl, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UIRenderer::begin_text(void) {
|
||||||
|
if(!_txt_renderer) return;
|
||||||
|
_txt_renderer->begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UIRenderer::flush_text(void) {
|
||||||
|
if(!_txt_renderer) return;
|
||||||
|
_txt_renderer->flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
TextRenderer* UIRenderer::get_text_renderer(void) {
|
TextRenderer* UIRenderer::get_text_renderer(void) {
|
||||||
|
|||||||
@ -17,6 +17,9 @@ public:
|
|||||||
void draw_triangle(int x1, int y1, int x2, int y2, int x3, int y3, const Color& color);
|
void draw_triangle(int x1, int y1, int x2, int y2, int x3, int y3, const Color& color);
|
||||||
void render_text(const char* text, int x, int y, const Color& color);
|
void render_text(const char* text, int x, int y, const Color& color);
|
||||||
|
|
||||||
|
void begin_text(void);
|
||||||
|
void flush_text(void);
|
||||||
|
|
||||||
/* Expose underlying text renderer for things like width calculation. */
|
/* Expose underlying text renderer for things like width calculation. */
|
||||||
TextRenderer* get_text_renderer(void);
|
TextRenderer* get_text_renderer(void);
|
||||||
|
|
||||||
|
|||||||
@ -79,6 +79,8 @@ bool UIWindow::is_point_inside(int x, int y) {
|
|||||||
void UIWindow::render(const RenderContext& context) {
|
void UIWindow::render(const RenderContext& context) {
|
||||||
int title_bar_height = 30;
|
int title_bar_height = 30;
|
||||||
|
|
||||||
|
context.ui_renderer->begin_text();
|
||||||
|
|
||||||
/* Define colours. */
|
/* Define colours. */
|
||||||
const Color frame_color = { 0.2f, 0.2f, 0.25f };
|
const Color frame_color = { 0.2f, 0.2f, 0.25f };
|
||||||
const Color title_bar_color = { 0.15f, 0.15f, 0.2f };
|
const Color title_bar_color = { 0.15f, 0.15f, 0.2f };
|
||||||
@ -115,6 +117,8 @@ void UIWindow::render(const RenderContext& context) {
|
|||||||
context.ui_renderer->draw_triangle(corner_x, corner_y - 10, corner_x - 10,
|
context.ui_renderer->draw_triangle(corner_x, corner_y - 10, corner_x - 10,
|
||||||
corner_y, corner_x, corner_y, resize_handle_color);
|
corner_y, corner_x, corner_y, resize_handle_color);
|
||||||
|
|
||||||
|
context.ui_renderer->flush_text();
|
||||||
|
|
||||||
if(_content) {
|
if(_content) {
|
||||||
int content_screen_y = _y + title_bar_height;
|
int content_screen_y = _y + title_bar_height;
|
||||||
int content_height = _height - title_bar_height;
|
int content_height = _height - title_bar_height;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user