[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
|
||||
in vec2 TexCoords;
|
||||
in vec2 TexCoords;
|
||||
in vec3 ourColor;
|
||||
out vec4 color;
|
||||
|
||||
uniform sampler2D text;
|
||||
uniform vec3 textColor;
|
||||
|
||||
void main() {
|
||||
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
|
||||
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 vec3 ourColor;
|
||||
|
||||
uniform mat4 projection;
|
||||
|
||||
void main() {
|
||||
gl_Position = projection * vec4(vertex.xy, 0.0, 1.0);
|
||||
TexCoords = vertex.zw;
|
||||
gl_Position = projection * vec4(aPos, 0.0, 1.0);
|
||||
TexCoords = aTexCoords;
|
||||
ourColor = aColor;
|
||||
}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "gfx/types.h"
|
||||
|
||||
|
||||
@ -17,24 +17,37 @@ TextRenderer::TextRenderer(unsigned int screen_width, unsigned int screen_height
|
||||
_txt_shader->use();
|
||||
_txt_shader->set_mat4("projection", _projecton);
|
||||
|
||||
/* Configure VAO/VBO for texture coords. */
|
||||
/* Configure VAO/VBO for batch rendering. */
|
||||
glGenVertexArrays(1, &_vao);
|
||||
glGenBuffers(1, &_vbo);
|
||||
glBindVertexArray(_vao);
|
||||
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);
|
||||
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);
|
||||
glBindVertexArray(0);
|
||||
|
||||
/* Pre-allocate vector capacity. */
|
||||
_vertices.reserve(MAX_VERTICES);
|
||||
}
|
||||
|
||||
TextRenderer::~TextRenderer(void) {
|
||||
delete _txt_shader;
|
||||
glDeleteTextures(1, &_atlas_texture_id);
|
||||
}
|
||||
|
||||
void TextRenderer::load_font(const char* font_path, unsigned int font_size) {
|
||||
_chars.clear();
|
||||
FT_Library ft;
|
||||
if(FT_Init_FreeType(&ft)) {
|
||||
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);
|
||||
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++) {
|
||||
if(FT_Load_Char(face, c, FT_LOAD_RENDER)) {
|
||||
printf("Failed to load Glyph for char %c\n", c);
|
||||
continue;
|
||||
}
|
||||
|
||||
unsigned int texture;
|
||||
glGenTextures(1, &texture);
|
||||
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_width += face->glyph->bitmap.width;
|
||||
max_height = std::max(max_height, face->glyph->bitmap.rows);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
FT_Done_Face(face);
|
||||
FT_Done_FreeType(ft);
|
||||
}
|
||||
|
||||
void TextRenderer::render_text(const char* text, float x, float y, float scale,
|
||||
const Color& color) {
|
||||
void TextRenderer::begin(void) {
|
||||
_vertices.clear();
|
||||
}
|
||||
|
||||
void TextRenderer::flush(void) {
|
||||
if(_vertices.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_txt_shader->use();
|
||||
_txt_shader->set_vec3("textColor", color.r, color.g, color.b);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, _atlas_texture_id);
|
||||
glBindVertexArray(_vao);
|
||||
|
||||
for(const char* p = text; *p; p++) {
|
||||
character ch = _chars[*p];
|
||||
glBindBuffer(GL_ARRAY_BUFFER, _vbo);
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, _vertices.size() * sizeof(TextVertex), _vertices.data());
|
||||
|
||||
float xpos = x + ch.bearing[0] * scale;
|
||||
float ypos = y - (ch.size[1] - ch.bearing[1]) * scale;
|
||||
float w = ch.size[0] * scale;
|
||||
float h = ch.size[1] * scale;
|
||||
glDrawArrays(GL_TRIANGLES, 0, _vertices.size());
|
||||
|
||||
float vertices[6][4] = {
|
||||
{ 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;
|
||||
}
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
glBindVertexArray(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 width = 0.0f;
|
||||
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;
|
||||
}
|
||||
|
||||
@ -1,33 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <ft2build.h>
|
||||
#include <vector>
|
||||
#include FT_FREETYPE_H
|
||||
#include <map>
|
||||
|
||||
#include "shader.h"
|
||||
#include "types.h"
|
||||
|
||||
struct TextVertex {
|
||||
float x, y, s, t, r, g, b;
|
||||
};
|
||||
|
||||
/* State of a single charactrer glyph. */
|
||||
struct character {
|
||||
unsigned int texture_id;
|
||||
int size[2];
|
||||
int bearing[2];
|
||||
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 {
|
||||
public:
|
||||
TextRenderer(unsigned int screen_width, unsigned int screen_height);
|
||||
~TextRenderer(void);
|
||||
|
||||
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);
|
||||
|
||||
void begin(void);
|
||||
void flush(void);
|
||||
|
||||
|
||||
private:
|
||||
Shader* _txt_shader;
|
||||
unsigned int _vao, _vbo;
|
||||
std::map<char, character> _chars;
|
||||
unsigned int _atlas_texture_id;
|
||||
character _chars[128];
|
||||
std::vector<TextVertex> _vertices;
|
||||
float _projecton[16];
|
||||
};
|
||||
|
||||
@ -7,7 +7,6 @@
|
||||
|
||||
#include "terminal.h"
|
||||
#include "client_network.h"
|
||||
#include "gfx/txt_renderer.h"
|
||||
#include "gfx/types.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 padding = 5.0f;
|
||||
|
||||
context.ui_renderer->begin_text();
|
||||
|
||||
/* Enable scissor test to clip rendering to the window content area. */
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
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. */
|
||||
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) {
|
||||
/* Pass 1: Background. */
|
||||
context.ui_renderer->begin_text();
|
||||
_render_wallpaper(context.ui_renderer);
|
||||
if(_launcher_is_open) {
|
||||
_launcher->render(context.ui_renderer);
|
||||
}
|
||||
_taskbar->render(context.ui_renderer, _windows, _focused_window);
|
||||
/* Render non-focused windows first. */
|
||||
context.ui_renderer->flush_text();
|
||||
|
||||
/* Pass 2: Windows (back to front). */
|
||||
for(const auto& win : _windows) {
|
||||
if(win.get() != _focused_window && !win->is_minimized()) {
|
||||
win.get()->render(context);
|
||||
}
|
||||
}
|
||||
/* Render focused window last so it's on top. */
|
||||
if(_focused_window && !_focused_window->is_minimized()) {
|
||||
_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) {
|
||||
|
||||
@ -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_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);
|
||||
_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) {
|
||||
|
||||
@ -95,7 +95,7 @@ Screen MainMenu::update(void) {
|
||||
}
|
||||
|
||||
void MainMenu::render(UIRenderer* ui_renderer) {
|
||||
_render_background(ui_renderer->get_text_renderer());
|
||||
_render_background(ui_renderer);
|
||||
|
||||
/* Button colours. */
|
||||
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. */
|
||||
for(const auto& line : _background_text) {
|
||||
txt_renderer->render_text(line.text.c_str(), line.x, line.y, 1.0f,
|
||||
background_text_color);
|
||||
ui_renderer->render_text(line.text.c_str(), line.x, line.y, background_text_color);
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ public:
|
||||
|
||||
private:
|
||||
void _update_background(void);
|
||||
void _render_background(TextRenderer* txdt_renderer);
|
||||
void _render_background(UIRenderer* ui_renderer);
|
||||
|
||||
/* For animated background. */
|
||||
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 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);
|
||||
|
||||
@ -84,16 +83,23 @@ void MenuBar::render(UIRenderer* ui_renderer, int x, int y, int width) {
|
||||
int menu_width = 60;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
private:
|
||||
|
||||
@ -25,7 +25,17 @@ void UIRenderer::render_text(const char* text, int x, int y, const Color& color)
|
||||
if(!_txt_renderer) return;
|
||||
/* Convert the screen-space baseline y-coord to GL-space baseline y-coord. */
|
||||
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) {
|
||||
|
||||
@ -17,6 +17,9 @@ public:
|
||||
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 begin_text(void);
|
||||
void flush_text(void);
|
||||
|
||||
/* Expose underlying text renderer for things like width calculation. */
|
||||
TextRenderer* get_text_renderer(void);
|
||||
|
||||
|
||||
@ -79,6 +79,8 @@ bool UIWindow::is_point_inside(int x, int y) {
|
||||
void UIWindow::render(const RenderContext& context) {
|
||||
int title_bar_height = 30;
|
||||
|
||||
context.ui_renderer->begin_text();
|
||||
|
||||
/* Define colours. */
|
||||
const Color frame_color = { 0.2f, 0.2f, 0.25f };
|
||||
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,
|
||||
corner_y, corner_x, corner_y, resize_handle_color);
|
||||
|
||||
context.ui_renderer->flush_text();
|
||||
|
||||
if(_content) {
|
||||
int content_screen_y = _y + title_bar_height;
|
||||
int content_height = _height - title_bar_height;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user