Skip to content

Commit

Permalink
drivers/glrend: refactor text renderering, again
Browse files Browse the repository at this point in the history
Two major changes:

1. Renders using an instanced quad with a batch size of 64 - significantly
   reduces draw calls as we're no longer doing one-per-character.
2. Switch to using a texture array instead of an atlas. Reduces memory
   consumption.
  • Loading branch information
vs49688 committed Sep 2, 2024
1 parent cfc2311 commit 0611b76
Show file tree
Hide file tree
Showing 14 changed files with 285 additions and 813 deletions.
18 changes: 17 additions & 1 deletion drivers/glrend/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,20 @@ add_custom_command(
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/embedshader.pl"
)

add_custom_command(
OUTPUT text.vert.glsl.h
COMMAND "${PERL_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/embedshader.pl" "g_TextVertexShader" "${CMAKE_CURRENT_SOURCE_DIR}/text.vert.glsl" text.vert.glsl.h
MAIN_DEPENDENCY "${CMAKE_CURRENT_SOURCE_DIR}/text.vert.glsl"
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/embedshader.pl"
)

add_custom_command(
OUTPUT text.frag.glsl.h
COMMAND "${PERL_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/embedshader.pl" "g_TextFragmentShader" "${CMAKE_CURRENT_SOURCE_DIR}/text.frag.glsl" text.frag.glsl.h
MAIN_DEPENDENCY "${CMAKE_CURRENT_SOURCE_DIR}/text.frag.glsl"
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/embedshader.pl"
)

set(GLSTATE_FILES
cache.c
state.c
Expand Down Expand Up @@ -88,7 +102,6 @@ set(GLREND_FILES

font.c
font.h
stb_rect_pack.h

template.h

Expand Down Expand Up @@ -133,6 +146,9 @@ add_library(glrend

"${CMAKE_CURRENT_BINARY_DIR}/brender.vert.glsl.h"
"${CMAKE_CURRENT_BINARY_DIR}/brender.frag.glsl.h"

"${CMAKE_CURRENT_BINARY_DIR}/text.vert.glsl.h"
"${CMAKE_CURRENT_BINARY_DIR}/text.frag.glsl.h"
)

get_target_property(target_type glrend TYPE)
Expand Down
110 changes: 74 additions & 36 deletions drivers/glrend/devpixmp.c
Original file line number Diff line number Diff line change
Expand Up @@ -595,23 +595,32 @@ br_error BR_CMETHOD_DECL(br_device_pixelmap_gl, rectangleCopyFrom)(br_device_pix
br_error BR_CMETHOD(br_device_pixelmap_gl, text)(br_device_pixelmap *self, br_point *point, br_font *font,
const char *text, br_uint_32 colour)
{
GLuint tex;

/* Quit if off top, bottom or right screen */
br_int_32 x = point->x + self->pm_origin_x;
br_int_32 y = point->y + self->pm_origin_y;
size_t len = strlen(text);
br_point pp;
br_font_gl *gl_font;
HVIDEO hVideo = &self->screen->asFront.video;
br_text_gl *text_data;

/*
* Make sure we're an offscreen pixelmap.
*/
if(self->use_type != BRT_OFFSCREEN)
return BRE_UNSUPPORTED;

if(y <= -font->glyph_y || y >= self->pm_height || x >= self->pm_width)
/*
* Quit if off top, bottom or right screen
*/
if(PixelmapPointClip(&pp, point, (br_pixelmap *)self) == BR_CLIP_REJECT)
return BRE_OK;

/* Ensure we're a valid font. */
br_font_gl *gl_font;
if(pp.y <= -font->glyph_y || pp.y >= self->pm_height || pp.x >= self->pm_width)
return BRE_OK;

/*
* Ensure we're a valid font.
*/

if(font == BrFontFixed3x5)
gl_font = &self->screen->asFront.font_fixed3x5;
else if(font == BrFontProp4x6)
Expand All @@ -621,9 +630,9 @@ br_error BR_CMETHOD(br_device_pixelmap_gl, text)(br_device_pixelmap *self, br_po
else
return BRE_FAIL;

/* Set up the render state. The font UVs match the texture, so no need to flip here. */
HVIDEO hVideo = &self->screen->asFront.video;

/*
* All valid, set up the render state.
*/
glBindFramebuffer(GL_FRAMEBUFFER, self->asBack.glFbo);
glViewport(0, 0, self->pm_width, self->pm_height);

Expand All @@ -634,25 +643,22 @@ br_error BR_CMETHOD(br_device_pixelmap_gl, text)(br_device_pixelmap *self, br_po
glDisable(GL_CULL_FACE);

glUseProgram(hVideo->textProgram.program);
glBindBufferBase(GL_UNIFORM_BUFFER, hVideo->textProgram.block_binding_font_data, gl_font->font_data);

br_vector3_f col = {
{BR_RED(colour) / 255.0f, BR_GRN(colour) / 255.0f, BR_BLU(colour) / 255.0f}
};

br_matrix4 mvp;
BrMatrix4Orthographic(&mvp, 0.0f, 1.0f, 0.0f, 1.0f, -1.0f, 1.0f);
VIDEOI_D3DtoGLProjection(&mvp);

glUniformMatrix4fv(hVideo->textProgram.uMVP, 1, GL_FALSE, (GLfloat *)&mvp);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D_ARRAY, gl_font->tex);
glUniform1i(hVideo->textProgram.uSampler, 0);

if(gl_font->tex != 0)
glBindTexture(GL_TEXTURE_2D, gl_font->tex);
else
glBindTexture(GL_TEXTURE_2D, self->screen->asFront.tex_checkerboard);
/*
* Create the per-model/text state.
*/
text_data = BrScratchAllocate(sizeof(br_text_gl));
BrMatrix4Orthographic(&text_data->mvp, 0.0f, 1.0f, 0.0f, 1.0f, -1.0f, 1.0f);
VIDEOI_D3DtoGLProjection(&text_data->mvp);
text_data->colour = (br_vector3)BR_VECTOR3(BR_RED(colour) / 255.0f, BR_GRN(colour) / 255.0f, BR_BLU(colour) / 255.0f);

glUniform1i(hVideo->textProgram.uSampler, 0);
glUniform3fv(hVideo->textProgram.uColour, 1, col.v);
glBindVertexArray(self->screen->asFront.video.textProgram.vao_glyphs);
glBindBuffer(GL_UNIFORM_BUFFER, self->screen->asFront.video.textProgram.ubo_glyphs);

br_rectangle r = {
.x = point->x,
Expand All @@ -661,22 +667,54 @@ br_error BR_CMETHOD(br_device_pixelmap_gl, text)(br_device_pixelmap *self, br_po
.w = 0,
};

for(; *text && r.w <= self->pm_width; ++text) {
br_uint_8 glyph = (br_uint_8)*text;
br_uint_16 width = (font->flags & BR_FONTF_PROPORTIONAL) ? font->width[glyph] : font->glyph_x;
r.w = width;
do {
size_t chunk = len;
if(chunk > BR_TEXT_GL_CHUNK_SIZE) {
chunk = BR_TEXT_GL_CHUNK_SIZE;
}

br_rectangle dr = r;
VIDEOI_BrRectToGL((br_pixelmap *)self, &dr);
for(size_t i = 0; i < chunk; ++i) {
br_uint_32 glyph = (br_uint_32)text[i];
br_uint_16 width = (font->flags & BR_FONTF_PROPORTIONAL) ? font->width[glyph] : font->glyph_x;
br_rectangle dr;

DeviceGLPatchQuadFont(&self->asBack.quad, (br_pixelmap *)self, &dr, gl_font, glyph);
DeviceGLDrawQuadText(&self->asBack.quad);
r.w = width;

r.x += width + 1;
r.w += width;
}
/*
* Bail early if the rest of the string is entirely offscreen.
*/
dr = r;
if(PixelmapRectangleClip(&dr, &r, (br_pixelmap *)self) == BR_CLIP_REJECT) {
chunk = i;
len = chunk;
break;
}

// clang-format off
text_data->rects[i] = (br_vector4_f)BR_VECTOR4(
(float)dr.x / (float)self->pm_width,
(float)dr.y / (float)self->pm_height,
(float)dr.w / (float)self->pm_width,
(float)dr.h / (float)self->pm_height
);
text_data->chars[i] = (br_uint_32)glyph;
// clang-format on

r.x += width + 1;
r.w += width;
}

glBufferData(GL_UNIFORM_BUFFER, sizeof(br_text_gl), text_data, GL_STATIC_DRAW);
glDrawArraysInstanced(GL_TRIANGLES, 0, 6, (GLsizei)chunk);

len -= chunk;
text += chunk;
} while(len > 0);

BrScratchFree(text_data);

glBindVertexArray(0);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glDisable(GL_BLEND);
return BRE_OK;
Expand Down
1 change: 0 additions & 1 deletion drivers/glrend/devpixmp.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ typedef struct {
typedef struct br_device_pixelmap_gl_quad {
br_device_pixelmap_gl_tri tris[4];
GLuint defaultVao;
GLuint textVao;
GLuint buffers[2];
} br_device_pixelmap_gl_quad;

Expand Down
12 changes: 6 additions & 6 deletions drivers/glrend/devpmglf.c
Original file line number Diff line number Diff line change
Expand Up @@ -204,14 +204,14 @@ br_device_pixelmap *DevicePixelmapGLAllocateFront(br_device *dev, br_output_faci
* glyph from left-to-right. All fonts have 256 possible characters.
*/

BrLogTrace("GLREND", "Building fixed 3x5 font atlas.");
(void)FontGLBuildAtlas(&self->asFront.font_fixed3x5, BrFontFixed3x5, 512, 64);
BrLogTrace("GLREND", "Building fixed 3x5 font array.");
(void)FontGLBuildArray(&self->asFront.font_fixed3x5, BrFontFixed3x5);

BrLogTrace("GLREND", "Building proportional 4x6 font atlas.");
(void)FontGLBuildAtlas(&self->asFront.font_prop4x6, BrFontProp4x6, 512, 64);
BrLogTrace("GLREND", "Building proportional 4x6 font array.");
(void)FontGLBuildArray(&self->asFront.font_prop4x6, BrFontProp4x6);

BrLogTrace("GLREND", "Building proportional 7x9 font atlas.");
(void)FontGLBuildAtlas(&self->asFront.font_prop7x9, BrFontProp7x9, 512, 64);
BrLogTrace("GLREND", "Building proportional 7x9 font array.");
(void)FontGLBuildArray(&self->asFront.font_prop7x9, BrFontProp7x9);

self->asFront.num_refs = 0;

Expand Down
5 changes: 1 addition & 4 deletions drivers/glrend/drv_ip.h
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ void StoredGLRenderGroup(br_geometry_stored *self, br_renderer *renderer, const
/*
* font.c
*/
br_error FontGLBuildAtlas(br_font_gl *gl_font, br_font *font, br_int_32 width, br_int_32 height);
br_error FontGLBuildArray(br_font_gl *gl_font, br_font *font);

/*
* quad.c
Expand All @@ -162,10 +162,7 @@ void DeviceGLFiniQuad(br_device_pixelmap_gl_quad *self);
*/
void DeviceGLPatchQuad(br_device_pixelmap_gl_quad *self, const br_pixelmap *dst, const br_rectangle *dr,
const br_pixelmap *src, const br_rectangle *sr);
void DeviceGLPatchQuadFont(br_device_pixelmap_gl_quad *self, const br_pixelmap *dst, const br_rectangle *dr,
const br_font_gl *font, br_uint_8 glyph);
void DeviceGLDrawQuad(br_device_pixelmap_gl_quad *self);
void DeviceGLDrawQuadText(br_device_pixelmap_gl_quad *self);

/*
* util.c
Expand Down
128 changes: 61 additions & 67 deletions drivers/glrend/font.c
Original file line number Diff line number Diff line change
@@ -1,86 +1,80 @@
#include "drv.h"
#include "brassert.h"

#define STB_RECT_PACK_IMPLEMENTATION
#include "stb_rect_pack.h"

/*
* Amount of padding in pixels around each glyph. Keep POT.
*/
#define GLYPH_PAD_PX 4
#define GLYPH_COUNT 256
#define GLYPH_COUNT 256

#pragma pack(push, 16)
typedef struct font_data {
/*
* NB: This is a vec4[GLYPH_COUNT/4] on the GLSL side because
* std140's array packing rules are shit.
*/
alignas(16) br_float widths[GLYPH_COUNT];
} font_data;
#pragma pack(pop)

br_error FontGLBuildArray(br_font_gl *gl_font, br_font *font)
{
GLuint tex;
GLint internal_format;
GLenum format, type;
br_pixelmap *pm;
br_int_32 max_width = font->glyph_x;
br_int_32 max_height = font->glyph_y;

typedef struct br_font_atlas_state_gl {
stbrp_rect rects[GLYPH_COUNT];
stbrp_node nodes[GLYPH_COUNT];
} br_font_atlas_state_gl;
font_data fd = {};

br_error FontGLBuildAtlas(br_font_gl *gl_font, br_font *font, br_int_32 width, br_int_32 height)
{
br_pixelmap *pm;
char c[2];
GLuint tex;
br_font_atlas_state_gl *state;
/*
* Find the max width, and calculate the UV coords.
*/
for(size_t i = 0; i < GLYPH_COUNT; ++i) {
br_int_32 width = (font->flags & BR_FONTF_PROPORTIONAL) ? font->width[i] : font->glyph_x;

gl_font->font = font;
fd.widths[i] = (float)width;

state = BrScratchAllocate(sizeof(br_font_atlas_state_gl));
if(width > max_width)
max_width = width;
}

for(int i = 0; i < BR_ASIZE(state->rects); ++i) {
stbrp_rect *rect = state->rects + i;
rect->id = i;
rect->w = ((font->flags & BR_FONTF_PROPORTIONAL) ? font->width[i] : font->glyph_x) + GLYPH_PAD_PX;
rect->h = font->glyph_y + GLYPH_PAD_PX;
for(size_t i = 0; i < GLYPH_COUNT; ++i) {
fd.widths[i] /= (float)max_width;
}

stbrp_context ctx = {};
stbrp_init_target(&ctx, width, height, state->nodes, BR_ASIZE(state->nodes));
/*
* Upload the font data.
*/
glGenBuffers(1, &gl_font->font_data);
glBindBuffer(GL_UNIFORM_BUFFER, gl_font->font_data);
glBufferData(GL_UNIFORM_BUFFER, sizeof(font_data), &fd, GL_STATIC_DRAW);
glBindBuffer(GL_UNIFORM_BUFFER, 0);

/*
* Allocate a temporary pixelmap to draw text into.
*/
if((pm = BrPixelmapAllocate(BR_PMT_RGBA_8888, max_width, max_height, NULL, BR_PMAF_NORMAL)) == NULL)
return BRE_FAIL;

/* If this fails, that's programmer error. */
(void)stbrp_pack_rects(&ctx, state->rects, BR_ASIZE(state->rects));
VIDEOI_BrPixelmapGetTypeDetails(pm->type, &internal_format, &format, &type, NULL, NULL);

if((pm = BrPixelmapAllocate(BR_PMT_RGBA_8888, width, height, NULL, BR_PMAF_NORMAL)) == NULL) {
BrScratchFree(state);
return 0;
}
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D_ARRAY, tex);
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, internal_format, max_width, max_height, GLYPH_COUNT, 0, format, type, NULL);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

pm->origin_x = pm->origin_y = 0;
BrPixelmapFill(pm, 0x00000000);

c[1] = '\0';
for(size_t i = 0; i < BR_ASIZE(state->rects); ++i) {
const stbrp_rect *rect = state->rects + i;

br_rectangle r = {
.w = rect->w - GLYPH_PAD_PX,
.h = rect->h - GLYPH_PAD_PX,
.x = rect->x + (GLYPH_PAD_PX >> 1),
.y = rect->y + (GLYPH_PAD_PX >> 1),
};

c[0] = (char)i;

BrPixelmapText(pm, r.x, r.y, 0xFFFFFFFF, font, c);

/*
* Calculate the UV coordinates.
* * Remember that the image will be upside down when converted to a
* OpenGL texture. The UV for each glyph will match this.
* * The origin of the atlas is being set to top-left@(0,0), so
* just swap y0/y1.
*/
VIDEOI_BrRectToUVs(pm, &r, &gl_font->glyph[i].x0, &gl_font->glyph[i].y1, &gl_font->glyph[i].x1,
&gl_font->glyph[i].y0);
}
for(GLsizei i = 0; i < GLYPH_COUNT; ++i) {
char c[2] = {(char)i, '\0'};

tex = VIDEO_BrPixelmapToGLTexture(pm);
BrPixelmapFree(pm);
BrPixelmapFill(pm, BR_COLOUR_RGBA(0, 0, 0, 0));
BrPixelmapText(pm, -pm->origin_x, -pm->origin_y, BR_COLOUR_RGBA(255, 255, 255, 255), font, c);

BrScratchFree(state);
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, i, max_width, max_height, 1, format, type, pm->pixels);
}

gl_font->tex = tex;
if(tex == 0)
return BRE_FAIL;
glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
BrPixelmapFree(pm);

gl_font->tex = tex;
gl_font->font = font;
return BRE_OK;
}
Loading

0 comments on commit 0611b76

Please sign in to comment.