Files
bearstonem--openmohaa-vr/code/renderergl2/tr_font.cpp
T
bearstonem d9968210aa fix(vr): correct visual glitches and reduce rendering overhead
Restore desktop HUD behavior and objective state, fix stereo text lifetimes, HUD placement and colors, sprite culling, and font shader passes. Handle swapchain timeouts and incomplete stereo frames safely, remove forced GPU waits, and correct rendering timing attribution.

Track client-game source additions in CMake. Validated desktop and Android GL1/GL2 builds plus targeted regression and sanitizer checks. Headset appearance and performance remain unverified.
2026-10-14 14:48:22 -07:00

1024 lines
29 KiB
C++

/*
===========================================================================
Copyright (C) 2024 the OpenMoHAA team
This file is part of OpenMoHAA source code.
OpenMoHAA source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
OpenMoHAA source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
// tr_font.cpp -- font rendering
#include "tr_local.h"
#define MAX_LOADED_FONTS 255
static fontheader_sgl_t s_loadedFonts_sgl[MAX_LOADED_FONTS];
static int s_numLoadedFonts_sgl = 0;
static fontheader_t s_loadedFonts[MAX_LOADED_FONTS];
static int s_numLoadedFonts = 0;
static float s_fontHeightScale = 1.0;
static float s_fontGeneralScale = 1.0;
static float s_fontZ = 0.0;
void R_ShutdownFont() {
int i;
fontheader_t *header;
fontheader_sgl_t *header_sgl;
for (i = 0; i < s_numLoadedFonts; i++)
{
header = &s_loadedFonts[i];
if (header->charTable) {
ri.Free(header->charTable);
header->charTable = NULL;
}
memset(header, 0, sizeof(*header));
}
for (i = 0; i < s_numLoadedFonts_sgl; i++)
{
header_sgl = &s_loadedFonts_sgl[i];
memset(header_sgl, 0, sizeof(*header_sgl));
}
}
void R_SetFontHeightScale(float scale)
{
s_fontHeightScale = scale;
}
void R_SetFontScale(float scale)
{
s_fontGeneralScale = scale;
}
void R_SetFontZ(float zed)
{
s_fontZ = zed;
}
static int CodeSearch(const fontheader_t* font, unsigned short uch) {
int mid;
int l, r;
r = font->charTableLength;
l = 0;
while (l < r) {
mid = (l + r) / 2;
if (font->charTable[mid].cp > uch) {
r = (l + r) / 2;
continue;
}
if (uch == font->charTable[mid].cp) {
return (l + r) / 2;
}
l = mid + 1;
}
if (uch != font->charTable[l].cp) {
return -1;
}
return l;
}
static qboolean DBCSIsLeadByte(const fontheader_t* font, unsigned short uch) {
// Byte ranges found in Wikipedia articles with relevant search strings in each case
switch (font->codePage) {
case 932:
// Shift_jis
return ((uch >= 0x81) && (uch <= 0x9F)) ||
((uch >= 0xE0) && (uch <= 0xFC));
// Lead bytes F0 to FC may be a Microsoft addition.
case 936:
// GBK
return (uch >= 0x81) && (uch <= 0xFE);
case 949:
// Korean Wansung KS C-5601-1987
return (uch >= 0x81) && (uch <= 0xFE);
case 950:
// Big5
return (uch >= 0x81) && (uch <= 0xFE);
case 1361:
// Korean Johab KS C-5601-1992
return
((uch >= 0x84) && (uch <= 0xD3)) ||
((uch >= 0xD8) && (uch <= 0xDE)) ||
((uch >= 0xE0) && (uch <= 0xF9));
}
return false;
}
fontheader_sgl_t* R_LoadFont_sgl(const char* name)
{
int i;
char* theFile;
fontheader_sgl_t* header;
char* ref;
const char* token;
qboolean error;
error = qfalse;
for (i = 0; i < s_numLoadedFonts_sgl; i++)
{
header = &s_loadedFonts_sgl[i];
if (!Q_stricmp(name, header->name)) {
return header;
}
}
if (s_numLoadedFonts >= MAX_LOADED_FONTS)
{
ri.Printf(PRINT_WARNING, "LoadFont: Too many fonts loaded! Couldn't load %s\n", name);
return NULL;
}
if (ri.FS_ReadFile(va("fonts/%s.RitualFont", name), (void**)&theFile) == -1)
{
ri.Printf(PRINT_WARNING, "LoadFont: Couldn't load font %s\n", name);
return NULL;
}
header = &s_loadedFonts_sgl[s_numLoadedFonts_sgl];
header->height = 0.0;
header->aspectRatio = 0.0;
Q_strncpyz(header->name, name, sizeof(header->name));
ref = theFile;
while (ref && !error)
{
token = COM_Parse(&ref);
if (!Q_stricmp(token, "RitFont"))
{
// ignore this token
continue;
}
if (!Q_stricmp(token, "indirections"))
{
token = COM_Parse(&ref);
if (Q_stricmp(token, "{"))
{
error = qtrue;
break;
}
for (i = 0; i < 256; i++)
{
token = COM_Parse(&ref);
if (!token[0]) {
error = qtrue;
break;
}
header->indirection[i] = atoi(token);
}
if (error) {
break;
}
token = COM_Parse(&ref);
if (Q_stricmp(token, "}"))
{
error = qtrue;
break;
}
}
else if (!Q_stricmp(token, "locations"))
{
token = COM_Parse(&ref);
if (Q_stricmp(token, "{"))
{
error = qtrue;
break;
}
for (i = 0; i < 256; i++)
{
token = COM_Parse(&ref);
if (Q_stricmp(token, "{")) {
error = qtrue;
break;
}
if (header->aspectRatio == 0.0)
{
ri.Printf(PRINT_WARNING, "WARNING: aspect decl must be before locations in font '%s'", name);
break;
}
header->locations[i].pos[0] = atof(COM_Parse(&ref)) / 256.0;
header->locations[i].pos[1] = atof(COM_Parse(&ref)) * header->aspectRatio / 256.0;
header->locations[i].size[0] = atof(COM_Parse(&ref)) / 256.0;
header->locations[i].size[1] = atof(COM_Parse(&ref)) * header->aspectRatio / 256.0;
token = COM_Parse(&ref);
if (Q_stricmp(token, "}"))
{
error = qtrue;
break;
}
}
if (error) {
break;
}
token = COM_Parse(&ref);
if (Q_stricmp(token, "}"))
{
error = qtrue;
break;
}
}
else if (!Q_stricmp(token, "height"))
{
// parse the header height
token = COM_Parse(&ref);
header->height = atof(token);
}
else if (!Q_stricmp(token, "aspect"))
{
// parse the aspect ratio
token = COM_Parse(&ref);
header->aspectRatio = atof(token);
}
else
{
// unknown token
break;
}
}
if (token[0])
{
ri.Printf(PRINT_WARNING, "WARNING: Unknown token '%s' parsing font '%s'\n", token, name);
error = qtrue;
}
R_LoadFontShader(header);
if (!header->height || !header->aspectRatio) {
// invalid height or aspect ratio
error = qtrue;
}
ri.FS_FreeFile(theFile);
if (error)
{
ri.Printf(3, "WARNING: Error parsing font %s.\n", name);
return NULL;
}
else
{
s_numLoadedFonts_sgl++;
return header;
}
return NULL;
}
fontheader_t* R_LoadFont(const char* name) {
int i;
char* theFile;
fontheader_t* header;
char* ref;
const char* token;
qboolean error;
char* pRitFontNames[32];
error = qfalse;
for (i = 0; i < s_numLoadedFonts; i++)
{
header = &s_loadedFonts[i];
if (!Q_stricmp(name, header->name)) {
return header;
}
}
if (s_numLoadedFonts >= MAX_LOADED_FONTS)
{
ri.Printf(PRINT_WARNING, "LoadFont: Too many fonts loaded! Couldn't load %s\n", name);
return NULL;
}
if (ri.FS_ReadFile(va("fonts/%s.RitualFont", name), (void**)&theFile) == -1)
{
ri.Printf(PRINT_WARNING, "LoadFont: Couldn't load font %s\n", name);
return NULL;
}
memset(pRitFontNames, 0, sizeof(pRitFontNames));
ref = theFile;
header = &s_loadedFonts[s_numLoadedFonts];
token = COM_Parse(&ref);
if (Q_stricmp(token, "RitFontList"))
{
if (Q_stricmp(token, "RitFont"))
{
ri.Printf(PRINT_WARNING, "LoadFont: Not actual font %s\n", name);
return NULL;
}
header->numPages = 0;
}
else
{
while (ref && !error)
{
token = COM_Parse(&ref);
if (!Q_stricmp(token, "CodePage"))
{
header->codePage = atoi(COM_Parse(&ref));
}
else if (!Q_stricmp(token, "Chars"))
{
header->charTableLength = atoi(COM_Parse(&ref));
header->charTable = (fontchartable_t*)ri.Malloc(header->charTableLength * sizeof(fontchartable_t));
if (!header->charTable)
{
ri.Printf(PRINT_WARNING, "LoadFont: Couldn't alloc mem %s\n", name);
error = qtrue;
break;
}
}
else if (!Q_stricmp(token, "Pages"))
{
header->numPages = atoi(COM_Parse(&ref));
}
else if (!Q_stricmp(token, "RitFontName"))
{
token = COM_Parse(&ref);
if (Q_stricmp(token, "{"))
{
ri.Printf(PRINT_WARNING, "LoadFont: Bad Format %s\n", name);
error = qtrue;
break;
}
for (i = 0; i < header->numPages; i++)
{
token = COM_Parse(&ref);
if (!token[0])
{
ri.Printf(PRINT_WARNING, "LoadFont: Bad Token %s\n", name);
error = qtrue;
break;
}
pRitFontNames[i] = (char*)ri.Malloc(strlen(token) + 1);
if (!pRitFontNames[i])
{
ri.Printf(PRINT_WARNING, "LoadFont: Couldn't alloc mem %s\n", name);
error = qtrue;
break;
}
strcpy(pRitFontNames[i], token);
}
if (error) {
break;
}
token = COM_Parse(&ref);
if (Q_stricmp(token, "}"))
{
ri.Printf(PRINT_WARNING, "LoadFont: Bad Format %s\n", name);
error = qtrue;
break;
}
}
else if (!Q_stricmp(token, "CharTable"))
{
token = COM_Parse(&ref);
if (Q_stricmp(token, "{"))
{
ri.Printf(PRINT_WARNING, "LoadFont: Bad Format %s\n", name);
error = qtrue;
break;
}
for (i = 0; i < header->charTableLength; i++) {
token = COM_Parse(&ref);
if (Q_stricmp(token, "{"))
{
ri.Printf(PRINT_WARNING, "LoadFont: Bad Token %s\n", name);
error = qtrue;
break;
}
header->charTable[i].cp = atoi(COM_Parse(&ref));
header->charTable[i].index = atoi(COM_Parse(&ref));
header->charTable[i].loc = atoi(COM_Parse(&ref));
atoi(COM_Parse(&ref));
token = COM_Parse(&ref);
if (Q_stricmp(token, "}"))
{
ri.Printf(PRINT_WARNING, "LoadFont: Bad Format %s\n", name);
error = qtrue;
break;
}
}
if (error) {
break;
}
token = COM_Parse(&ref);
if (Q_stricmp(token, "}"))
{
ri.Printf(PRINT_WARNING, "LoadFont: Bad Format %s\n", name);
error = qtrue;
break;
}
}
else
{
ri.Printf(PRINT_WARNING, "LoadFont: Bad Token %s\n", name);
error = qtrue;
break;
}
}
}
ri.FS_FreeFile(theFile);
if (!header->numPages)
{
header->charTableLength = 0;
header->charTable = NULL;
header->sgl[0] = R_LoadFont_sgl(name);
if (!header->sgl[0])
{
ri.Printf(PRINT_WARNING, "LoadFont: failed %s\n", name);
return NULL;
}
}
else
{
for (i = 0; i < header->numPages; i++)
{
header->sgl[i] = R_LoadFont_sgl(pRitFontNames[i]);
if (!header->sgl[i])
{
ri.Printf(PRINT_WARNING, "LoadFont: failed %s(%s)\n", pRitFontNames[i], name);
error = qtrue;
break;
}
}
// Free all allocated strings
for (i = 0; i < header->numPages; i++) {
ri.Free(pRitFontNames[i]);
}
if (error)
{
if (header->charTable) {
ri.Free(header->charTable);
}
header->numPages = 0;
header->charTableLength = 0;
header->charTable = NULL;
}
}
strcpy(header->name, name);
s_numLoadedFonts++;
return header;
}
void R_LoadFontShader(fontheader_sgl_t* font)
{
int i;
int save;
char filename[64];
shader_t* fontshader;
save = r_sequencenumber;
r_sequencenumber = -1;
Com_sprintf(filename, sizeof(filename), "gfx/fonts/%s", font->name);
font->shader = R_FindShader(filename, -1, qfalse);
r_sequencenumber = save;
if (!font->shader) {
ri.Error(ERR_DROP, "Could not load font shader for %s\n", filename);
}
fontshader = (shader_t*)font->shader;
if (fontshader->numUnfoggedPasses > 0)
{
for (i = 0; i < fontshader->numUnfoggedPasses; i++)
{
if (fontshader->stages[i] != NULL && fontshader->stages[i]->active)
{
fontshader->stages[i]->rgbGen = CGEN_ENTITY;
fontshader->stages[i]->alphaGen = AGEN_ENTITY;
}
}
font->trhandle = r_sequencenumber;
}
else
{
font->trhandle = r_sequencenumber;
}
}
/*
================
R_FontTraceBegin / R_FontTraceEnd
Temporary instrumentation, to settle whether the cost of the HUD is the strings
themselves. Both string paths flush the render command buffer and then drive the
back end directly, once per call - on a tiler with a framebuffer bound that can
end the render pass and force a resolve every time. Counting the calls alongside
the time spent in them says whether that is where the frame is going.
================
*/
static int s_fontMsec;
static int s_fontCalls;
static int s_fontLastReport;
static int R_FontTraceBegin( void ) {
return ri.Milliseconds();
}
static void R_FontTraceEnd( int start ) {
int now = ri.Milliseconds();
s_fontMsec += now - start;
s_fontCalls++;
if ( now - s_fontLastReport < 1000 ) {
return;
}
ri.Printf( PRINT_ALL, "VR font: %d calls, %dms a second\n", s_fontCalls, s_fontMsec );
s_fontLastReport = now;
s_fontCalls = 0;
s_fontMsec = 0;
}
void R_DrawString_sgl(fontheader_sgl_t* font, const char* text, float x, float y, int maxlen, const float *pvVirtualScreen) {
float charHeight;
float startx, starty;
int i;
float fWidthScale, fHeightScale;
i = 0;
startx = x;
starty = y;
if (pvVirtualScreen) {
if (pvVirtualScreen[0]) {
fWidthScale = pvVirtualScreen[0];
} else {
fWidthScale = (double)glConfig.vidWidth / 640.0;
}
if (pvVirtualScreen[1]) {
fHeightScale = pvVirtualScreen[1];
} else {
fHeightScale = (double)glConfig.vidHeight / 480.0;
}
}
if (!font) {
return;
}
const int fontTraceStart = R_FontTraceBegin();
R_IssuePendingRenderCommands();
if (font->trhandle != r_sequencenumber) {
font->shader = NULL;
}
if (!font->shader) {
R_LoadFontShader(font);
}
Vector4Copy(backEnd.color2D, backEnd.entity2D.e.shaderRGBA);
backEnd.currentEntity = &backEnd.entity2D;
charHeight = s_fontHeightScale * font->height * s_fontGeneralScale;
RB_BeginSurface((shader_t*)font->shader, 0, 0);
for (i = 0; text[i]; i++) {
unsigned char c;
int indirected;
letterloc_t* loc;
c = text[i];
if (maxlen != -1 && i >= maxlen) {
break;
}
switch (c)
{
case '\t':
indirected = font->indirection[32];
if (indirected == -1) {
ri.Printf(PRINT_DEVELOPER, "R_DrawString: no space-character in font!\n");
} else {
x = s_fontGeneralScale * font->locations[indirected].size[0] * 256.0 * 3.0 + x;
}
break;
case '\n':
starty = charHeight + starty;
x = startx;
y = starty;
break;
case '\r':
x = startx;
break;
default:
indirected = font->indirection[c];
if (indirected == -1)
{
ri.Printf(PRINT_DEVELOPER, "R_DrawString: no 0x%02x-character in font!\n", c);
indirected = font->indirection['?'];
if (indirected == -1) {
ri.Printf(PRINT_DEVELOPER, "R_DrawString: no '?' character in font!\n");
break;
}
// set the indirection for the next time
font->indirection[c] = indirected;
}
RB_CHECKOVERFLOW(4, 6);
loc = &font->locations[indirected];
// texture coordinates
tess.texCoords[tess.numVertexes][0] = loc->pos[0];
tess.texCoords[tess.numVertexes][1] = loc->pos[1];
tess.texCoords[tess.numVertexes + 1][0] = loc->size[0] + loc->pos[0];
tess.texCoords[tess.numVertexes + 1][1] = loc->pos[1];
tess.texCoords[tess.numVertexes + 2][0] = loc->pos[0];
tess.texCoords[tess.numVertexes + 2][1] = loc->size[1] + loc->pos[1];
tess.texCoords[tess.numVertexes + 3][0] = loc->size[0] + loc->pos[0];
tess.texCoords[tess.numVertexes + 3][1] = loc->size[1] + loc->pos[1];
// vertices position
tess.xyz[tess.numVertexes][0] = x;
tess.xyz[tess.numVertexes][1] = y;
tess.xyz[tess.numVertexes][2] = s_fontZ;
tess.xyz[tess.numVertexes + 1][0] = x + s_fontGeneralScale * loc->size[0] * 256.0;
tess.xyz[tess.numVertexes + 1][1] = y;
tess.xyz[tess.numVertexes + 1][2] = s_fontZ;
tess.xyz[tess.numVertexes + 2][0] = x;
tess.xyz[tess.numVertexes + 2][1] = y + charHeight;
tess.xyz[tess.numVertexes + 2][2] = s_fontZ;
tess.xyz[tess.numVertexes + 3][0] = x + s_fontGeneralScale * loc->size[0] * 256.0;
tess.xyz[tess.numVertexes + 3][1] = y + charHeight;
tess.xyz[tess.numVertexes + 3][2] = s_fontZ;
// indices
tess.indexes[tess.numIndexes] = tess.numVertexes;
tess.indexes[tess.numIndexes + 1] = tess.numVertexes + 1;
tess.indexes[tess.numIndexes + 2] = tess.numVertexes + 2;
tess.indexes[tess.numIndexes + 3] = tess.numVertexes + 1;
tess.indexes[tess.numIndexes + 4] = tess.numVertexes + 3;
tess.indexes[tess.numIndexes + 5] = tess.numVertexes + 2;
if (pvVirtualScreen)
{
// scale the string properly if virtual screen
tess.xyz[tess.numVertexes][0] *= fWidthScale;
tess.xyz[tess.numVertexes][1] *= fHeightScale;
tess.xyz[tess.numVertexes + 1][0] *= fWidthScale;
tess.xyz[tess.numVertexes + 1][1] *= fHeightScale;
tess.xyz[tess.numVertexes + 2][0] *= fWidthScale;
tess.xyz[tess.numVertexes + 2][1] *= fHeightScale;
tess.xyz[tess.numVertexes + 3][0] *= fWidthScale;
tess.xyz[tess.numVertexes + 3][1] *= fHeightScale;
}
x += s_fontGeneralScale * loc->size[0] * 256.0;
tess.numVertexes += 4;
tess.numIndexes += 6;
break;
}
}
RB_EndSurface();
R_FontTraceEnd( fontTraceStart );
}
void R_DrawString(fontheader_t* font, const char* text, float x, float y, int maxlen, const float *pvVirtualScreen) {
int i;
int code;
unsigned short uch;
char buffer[512];
size_t buflen;
int cursgl;
float curX, curY;
float curHeight;
if (!font->numPages) {
if (font->sgl[0]) {
R_DrawString_sgl(font->sgl[0], text, x, y, maxlen, pvVirtualScreen);
}
return;
}
if (maxlen < 0) {
maxlen = strlen(text);
}
curX = x;
curY = y;
curHeight = 0.f;
cursgl = -1;
buflen = 0;
i = 0;
while(i < maxlen) {
fontchartable_t* ct;
uch = text[i];
i++;
if (DBCSIsLeadByte(font, uch)) {
uch = (uch << 8) | text[i];
i++;
}
if (!uch) {
break;
}
if (uch == '\n' || uch == '\r') {
buffer[buflen] = 0;
R_DrawString_sgl(font->sgl[cursgl], buffer, curX, curY, maxlen, pvVirtualScreen);
curX = x;
curHeight = 0.f;
buflen = 0;
if (uch == '\n') {
curY += font->sgl[0]->height * s_fontGeneralScale * s_fontHeightScale;
}
continue;
}
code = CodeSearch(font, uch);
if (code < 0) {
continue;
}
if (cursgl == -1) {
cursgl = font->charTable[code].index;
}
ct = &font->charTable[code];
if (cursgl != ct->index || buflen >= ARRAY_LEN(buffer) - 2) {
buffer[buflen] = 0;
R_DrawString_sgl(font->sgl[cursgl], buffer, curX, curY, maxlen, pvVirtualScreen);
curX += curHeight;
curHeight = 0.f;
buflen = 0;
ct = &font->charTable[code];
cursgl = ct->index;
}
buffer[buflen++] = ct->loc;
curHeight += font->sgl[cursgl]->locations[ct->loc].size[0] * 256.0f;
}
if (buflen)
{
buffer[buflen] = 0;
R_DrawString_sgl(font->sgl[cursgl], buffer, curX, y, buflen, pvVirtualScreen);
}
}
void R_DrawFloatingString_sgl(fontheader_sgl_t* font, const char* text, const vec3_t org, const vec4_t color, float scale, int maxlen) {
shader_t* fontshader;
qhandle_t fsh;
float charWidth, charHeight;
int i;
vec3_t pos;
polyVert_t verts[4];
if (!font) {
return;
}
R_IssuePendingRenderCommands();
if (font->trhandle != r_sequencenumber) {
font->shader = NULL;
}
if (!font->shader) {
R_LoadFontShader(font);
}
i = 0;
fontshader = (shader_t*)font->shader;
fsh = 0;
for (i = 0; i < tr.numShaders; i++)
{
if (fontshader == tr.shaders[i])
{
fsh = i;
break;
}
}
i = 0;
charHeight = font->height * s_fontHeightScale * s_fontGeneralScale * scale;
VectorCopy(org, pos);
for (i = 0; text[i]; i++) {
unsigned char c;
int indirected;
letterloc_t* loc;
c = text[i];
indirected = font->indirection[c];
if (indirected == -1)
{
ri.Printf(PRINT_ALL, "R_DrawFloatingString: no 0x%02x-character in font!\n", c);
continue;
}
loc = &font->locations[indirected];
// vertices color
verts[0].modulate[0] = (int)(color[0] * 255.0);
verts[0].modulate[1] = (int)(color[1] * 255.0);
verts[0].modulate[2] = (int)(color[2] * 255.0);
verts[0].modulate[3] = (int)(color[3] * 255.0);
verts[1].modulate[0] = verts[0].modulate[0];
verts[1].modulate[1] = verts[0].modulate[1];
verts[1].modulate[2] = verts[0].modulate[2];
verts[1].modulate[3] = verts[0].modulate[3];
verts[2].modulate[0] = verts[0].modulate[0];
verts[2].modulate[1] = verts[0].modulate[1];
verts[2].modulate[2] = verts[0].modulate[2];
verts[2].modulate[3] = verts[0].modulate[3];
verts[3].modulate[0] = verts[0].modulate[0];
verts[3].modulate[1] = verts[0].modulate[1];
verts[3].modulate[2] = verts[0].modulate[2];
verts[3].modulate[3] = verts[0].modulate[3];
// texture coordinates
verts[0].st[0] = loc->pos[0];
verts[0].st[1] = loc->pos[1];
verts[1].st[0] = loc->pos[0] + font->locations[indirected].size[0];
verts[1].st[1] = loc->pos[1];
verts[2].st[0] = verts[1].st[0];
verts[2].st[1] = loc->pos[1] + loc->size[1];
verts[3].st[0] = loc->pos[0];
verts[3].st[1] = verts[2].st[1];
VectorCopy(pos, verts[3].xyz);
charWidth = font->locations[indirected].size[0] * 256.0 * s_fontGeneralScale * scale;
verts[2].xyz[0] = pos[0] + tr.refdef.viewaxis[1][0] * -charWidth;
verts[2].xyz[1] = pos[1] + tr.refdef.viewaxis[1][1] * -charWidth;
verts[2].xyz[2] = pos[2] + tr.refdef.viewaxis[1][2] * -charWidth;
verts[1].xyz[0] = verts[2].xyz[0] + charHeight * tr.refdef.viewaxis[2][0];
verts[1].xyz[1] = verts[2].xyz[1] + charHeight * tr.refdef.viewaxis[2][1];
verts[1].xyz[2] = verts[2].xyz[2] + charHeight * tr.refdef.viewaxis[2][2];
verts[0].xyz[1] = verts[1].xyz[1] + tr.refdef.viewaxis[1][1] * charWidth;
verts[0].xyz[2] = verts[1].xyz[2] + tr.refdef.viewaxis[1][2] * charWidth;
verts[0].xyz[0] = verts[1].xyz[0] + tr.refdef.viewaxis[1][0] * charWidth;
if (RE_AddPolyToScene2(fsh, 4, verts, 0)) {
++tr.refdef.numPolys;
}
pos[0] = verts[2].xyz[0];
pos[1] = verts[2].xyz[1];
pos[2] = verts[2].xyz[2];
}
}
void R_DrawFloatingString(fontheader_t* font, const char* text, const vec3_t org, const vec4_t color, float scale, int maxlen) {
return R_DrawFloatingString_sgl(font->sgl[0], text, org, color, scale, maxlen);
}
float R_GetFontHeight(const fontheader_t* font)
{
if (!font || !font->sgl[0]) {
return 0.0;
}
return font->sgl[0]->height * s_fontGeneralScale * s_fontHeightScale;
}
float R_GetFontStringWidth_sgl(const fontheader_sgl_t* font, const char* s)
{
float widths;
int i;
widths = 0.0;
if (!font) {
return 0.0;
}
for (i = 0; s[i]; i++)
{
int indirected;
char c = *s;
if (c == '\t')
{
indirected = font->indirection[32];
if (indirected != -1) {
widths += font->locations[indirected].size[0] * 3.0;
} else {
ri.Printf(PRINT_ALL, "R_GetFontStringWidth: no space-character in font!\n");
}
}
else
{
indirected = font->indirection[c];
if (indirected != -1) {
widths += font->locations[indirected].size[0];
} else {
ri.Printf(PRINT_ALL, "R_GetFontStringWidth: no 0x%02x-character in font!\n", c);
}
}
}
return widths * s_fontGeneralScale * 256.0;
}
float R_GetFontStringWidth(const fontheader_t* font, const char* s)
{
int i;
int code;
fontchartable_t* ct;
float width = 0.f;
if (!font->numPages) {
return R_GetFontStringWidth_sgl(font->sgl[0], s);
}
i = 0;
while(s[i]) {
unsigned char uch = s[i++];
if (DBCSIsLeadByte(font, uch)) {
uch = (uch << 8) | s[i++];
if (!uch) {
break;
}
}
if (uch == '\t') {
code = CodeSearch(font, ' ');
if (code >= 0) {
ct = &font->charTable[code];
width += font->sgl[ct->index]->locations[ct->loc].size[0] * 3.f;
}
}
else {
code = CodeSearch(font, uch);
if (code >= 0) {
ct = &font->charTable[code];
width += font->sgl[ct->index]->locations[ct->loc].size[0];
}
}
}
return width;
}