[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