mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-08-09 00:01:07 +00:00
* Rather than having a hookset created within the different bits of platform hooking code and then passed around everywhere, we just make a single global hookset object that's shared. * There's no risk of conflict because our GL object handles all possible contexts, there's no separate instancing or anything. * We also have places like the GL emulation or unsupported function hooks that do not handle two separate onward functions, and we undoubtedly have many assumptions that only one copy of RenderDoc will hook any given API. If we needed multiple hooksets within the same library a huge amount would need to change.
537 lines
16 KiB
C++
537 lines
16 KiB
C++
/******************************************************************************
|
|
* The MIT License (MIT)
|
|
*
|
|
* Copyright (c) 2018 Baldur Karlsson
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
******************************************************************************/
|
|
|
|
#include "data/glsl_shaders.h"
|
|
#include "maths/matrix.h"
|
|
#include "maths/vec.h"
|
|
#include "stb/stb_truetype.h"
|
|
#include "strings/string_utils.h"
|
|
#include "gl_driver.h"
|
|
|
|
const int firstChar = int(' ') + 1;
|
|
const int lastChar = 127;
|
|
const int numChars = lastChar - firstChar;
|
|
const float charPixelHeight = 20.0f;
|
|
|
|
stbtt_bakedchar chardata[numChars];
|
|
|
|
#define OPENGL 1
|
|
#include "data/glsl/debuguniforms.h"
|
|
|
|
void WrappedOpenGL::ContextData::CreateDebugData()
|
|
{
|
|
// to let us display the overlay on old GL contexts, use as simple a subset of functionality as
|
|
// possible to upload the texture. VAO and shaders are used optionally on modern contexts,
|
|
// otherwise we fall back to immediate mode rendering by hand
|
|
if(GL.glGetIntegerv && GL.glGenTextures && GL.glBindTexture && GL.glTexImage2D && GL.glTexParameteri)
|
|
{
|
|
string ttfstring = GetEmbeddedResource(sourcecodepro_ttf);
|
|
byte *ttfdata = (byte *)ttfstring.c_str();
|
|
|
|
byte *buf = new byte[FONT_TEX_WIDTH * FONT_TEX_HEIGHT];
|
|
|
|
stbtt_BakeFontBitmap(ttfdata, 0, charPixelHeight, buf, FONT_TEX_WIDTH, FONT_TEX_HEIGHT,
|
|
firstChar, numChars, chardata);
|
|
|
|
CharSize = charPixelHeight;
|
|
#if ENABLED(RDOC_ANDROID)
|
|
CharSize *= 2.0f;
|
|
#endif
|
|
CharAspect = chardata->xadvance / charPixelHeight;
|
|
|
|
stbtt_fontinfo f = {0};
|
|
stbtt_InitFont(&f, ttfdata, 0);
|
|
|
|
int ascent = 0;
|
|
stbtt_GetFontVMetrics(&f, &ascent, NULL, NULL);
|
|
|
|
float maxheight = float(ascent) * stbtt_ScaleForPixelHeight(&f, charPixelHeight);
|
|
|
|
{
|
|
PixelUnpackState unpack;
|
|
|
|
unpack.Fetch(false);
|
|
|
|
ResetPixelUnpackState(false, 1);
|
|
|
|
GLuint curtex = 0;
|
|
GL.glGetIntegerv(eGL_TEXTURE_BINDING_2D, (GLint *)&curtex);
|
|
|
|
GLenum texFmt = eGL_R8;
|
|
if(Legacy())
|
|
texFmt = eGL_LUMINANCE;
|
|
|
|
GL.glGenTextures(1, &GlyphTexture);
|
|
GL.glBindTexture(eGL_TEXTURE_2D, GlyphTexture);
|
|
GL.glTexImage2D(eGL_TEXTURE_2D, 0, texFmt, FONT_TEX_WIDTH, FONT_TEX_HEIGHT, 0, eGL_RED,
|
|
eGL_UNSIGNED_BYTE, buf);
|
|
GL.glTexParameteri(eGL_TEXTURE_2D, eGL_TEXTURE_MAX_LEVEL, 0);
|
|
GL.glTexParameteri(eGL_TEXTURE_2D, eGL_TEXTURE_MAG_FILTER, eGL_LINEAR);
|
|
GL.glTexParameteri(eGL_TEXTURE_2D, eGL_TEXTURE_MIN_FILTER, eGL_LINEAR);
|
|
|
|
GL.glBindTexture(eGL_TEXTURE_2D, curtex);
|
|
|
|
unpack.Apply(false);
|
|
}
|
|
|
|
delete[] buf;
|
|
|
|
Vec4f glyphData[2 * (numChars + 1)];
|
|
|
|
for(int i = 0; i < numChars; i++)
|
|
{
|
|
stbtt_bakedchar *b = chardata + i;
|
|
|
|
float x = b->xoff;
|
|
float y = b->yoff + maxheight;
|
|
|
|
glyphData[(i + 1) * 2 + 0] =
|
|
Vec4f(x / b->xadvance, y / charPixelHeight, b->xadvance / float(b->x1 - b->x0),
|
|
charPixelHeight / float(b->y1 - b->y0));
|
|
glyphData[(i + 1) * 2 + 1] = Vec4f(b->x0, b->y0, b->x1, b->y1);
|
|
}
|
|
|
|
if(Modern() && GL.glGenVertexArrays && GL.glBindVertexArray)
|
|
{
|
|
GLuint curvao = 0;
|
|
GL.glGetIntegerv(eGL_VERTEX_ARRAY_BINDING, (GLint *)&curvao);
|
|
|
|
GL.glGenVertexArrays(1, &DummyVAO);
|
|
GL.glBindVertexArray(DummyVAO);
|
|
|
|
GL.glBindVertexArray(curvao);
|
|
}
|
|
|
|
if(Modern() && GL.glGenBuffers && GL.glBufferData && GL.glBindBuffer)
|
|
{
|
|
GLuint curubo = 0;
|
|
GL.glGetIntegerv(eGL_UNIFORM_BUFFER_BINDING, (GLint *)&curubo);
|
|
|
|
GL.glGenBuffers(1, &GlyphUBO);
|
|
GL.glBindBuffer(eGL_UNIFORM_BUFFER, GlyphUBO);
|
|
GL.glBufferData(eGL_UNIFORM_BUFFER, sizeof(glyphData), glyphData, eGL_STATIC_DRAW);
|
|
|
|
GL.glGenBuffers(1, &GeneralUBO);
|
|
GL.glBindBuffer(eGL_UNIFORM_BUFFER, GeneralUBO);
|
|
GL.glBufferData(eGL_UNIFORM_BUFFER, sizeof(FontUBOData), NULL, eGL_DYNAMIC_DRAW);
|
|
|
|
GL.glGenBuffers(1, &StringUBO);
|
|
GL.glBindBuffer(eGL_UNIFORM_BUFFER, StringUBO);
|
|
GL.glBufferData(eGL_UNIFORM_BUFFER, sizeof(uint32_t) * 4 * FONT_MAX_CHARS, NULL,
|
|
eGL_DYNAMIC_DRAW);
|
|
|
|
GL.glBindBuffer(eGL_UNIFORM_BUFFER, curubo);
|
|
}
|
|
|
|
if(Modern() && GL.glCreateShader && GL.glShaderSource && GL.glCompileShader &&
|
|
GL.glGetShaderiv && GL.glGetShaderInfoLog && GL.glDeleteShader && GL.glCreateProgram &&
|
|
GL.glAttachShader && GL.glLinkProgram && GL.glGetProgramiv && GL.glGetProgramInfoLog)
|
|
{
|
|
vector<string> vs;
|
|
vector<string> fs;
|
|
|
|
ShaderType shaderType;
|
|
int glslVersion;
|
|
string fragDefines;
|
|
|
|
if(IsGLES)
|
|
{
|
|
shaderType = eShaderGLSLES;
|
|
glslVersion = 310;
|
|
fragDefines = "";
|
|
}
|
|
else
|
|
{
|
|
shaderType = eShaderGLSL;
|
|
glslVersion = 150;
|
|
fragDefines =
|
|
"#extension GL_ARB_shading_language_420pack : require\n"
|
|
"#extension GL_ARB_separate_shader_objects : require\n"
|
|
"#extension GL_ARB_explicit_attrib_location : require\n";
|
|
}
|
|
|
|
GenerateGLSLShader(vs, shaderType, "", GetEmbeddedResource(glsl_text_vert), glslVersion);
|
|
GenerateGLSLShader(fs, shaderType, fragDefines, GetEmbeddedResource(glsl_text_frag),
|
|
glslVersion);
|
|
|
|
vector<const char *> vsc;
|
|
vsc.reserve(vs.size());
|
|
vector<const char *> fsc;
|
|
fsc.reserve(fs.size());
|
|
|
|
for(size_t i = 0; i < vs.size(); i++)
|
|
vsc.push_back(vs[i].c_str());
|
|
|
|
for(size_t i = 0; i < fs.size(); i++)
|
|
fsc.push_back(fs[i].c_str());
|
|
|
|
GLuint vert = GL.glCreateShader(eGL_VERTEX_SHADER);
|
|
GLuint frag = GL.glCreateShader(eGL_FRAGMENT_SHADER);
|
|
|
|
GL.glShaderSource(vert, (GLsizei)vs.size(), &vsc[0], NULL);
|
|
GL.glShaderSource(frag, (GLsizei)fs.size(), &fsc[0], NULL);
|
|
|
|
GL.glCompileShader(vert);
|
|
GL.glCompileShader(frag);
|
|
|
|
char buffer[1024] = {0};
|
|
GLint status = 0;
|
|
|
|
GL.glGetShaderiv(vert, eGL_COMPILE_STATUS, &status);
|
|
if(status == 0)
|
|
{
|
|
GL.glGetShaderInfoLog(vert, 1024, NULL, buffer);
|
|
RDCERR("Shader error: %s", buffer);
|
|
}
|
|
|
|
GL.glGetShaderiv(frag, eGL_COMPILE_STATUS, &status);
|
|
if(status == 0)
|
|
{
|
|
GL.glGetShaderInfoLog(frag, 1024, NULL, buffer);
|
|
RDCERR("Shader error: %s", buffer);
|
|
}
|
|
|
|
Program = GL.glCreateProgram();
|
|
|
|
GL.glAttachShader(Program, vert);
|
|
GL.glAttachShader(Program, frag);
|
|
|
|
GL.glLinkProgram(Program);
|
|
|
|
GL.glGetProgramiv(Program, eGL_LINK_STATUS, &status);
|
|
if(status == 0)
|
|
{
|
|
GL.glGetProgramInfoLog(Program, 1024, NULL, buffer);
|
|
RDCERR("Link error: %s", buffer);
|
|
}
|
|
|
|
GL.glDeleteShader(vert);
|
|
GL.glDeleteShader(frag);
|
|
}
|
|
|
|
ready = true;
|
|
}
|
|
}
|
|
|
|
void WrappedOpenGL::RenderOverlayText(float x, float y, const char *fmt, ...)
|
|
{
|
|
static char tmpBuf[4096];
|
|
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
StringFormat::vsnprintf(tmpBuf, 4095, fmt, args);
|
|
tmpBuf[4095] = '\0';
|
|
va_end(args);
|
|
|
|
ContextData &ctxdata = GetCtxData();
|
|
|
|
GLPushPopState textState;
|
|
|
|
textState.Push(ctxdata.Modern());
|
|
|
|
RenderOverlayStr(x, y, tmpBuf);
|
|
|
|
textState.Pop(ctxdata.Modern());
|
|
}
|
|
|
|
void WrappedOpenGL::RenderOverlayStr(float x, float y, const char *text)
|
|
{
|
|
if(char *t = strchr((char *)text, '\n'))
|
|
{
|
|
*t = 0;
|
|
RenderOverlayStr(x, y, text);
|
|
RenderOverlayStr(x, y + 1.0f, t + 1);
|
|
*t = '\n';
|
|
return;
|
|
}
|
|
|
|
if(strlen(text) == 0)
|
|
return;
|
|
|
|
RDCASSERT(strlen(text) < (size_t)FONT_MAX_CHARS);
|
|
|
|
ContextData &ctxdata = GetCtxData();
|
|
|
|
if(!ctxdata.built || !ctxdata.ready)
|
|
return;
|
|
|
|
// if it's reasonably modern context, assume we can use buffers and UBOs
|
|
if(ctxdata.Modern())
|
|
{
|
|
GL.glBindBuffer(eGL_UNIFORM_BUFFER, ctxdata.GeneralUBO);
|
|
|
|
FontUBOData *ubo = (FontUBOData *)GL.glMapBufferRange(
|
|
eGL_UNIFORM_BUFFER, 0, sizeof(FontUBOData), GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
|
|
ubo->TextPosition.x = x;
|
|
ubo->TextPosition.y = y;
|
|
|
|
ubo->FontScreenAspect.x = 1.0f / RDCMAX(1.0f, float(m_InitParams.width));
|
|
ubo->FontScreenAspect.y = 1.0f / RDCMAX(1.0f, float(m_InitParams.height));
|
|
|
|
ubo->TextSize = ctxdata.CharSize;
|
|
ubo->FontScreenAspect.x *= ctxdata.CharAspect;
|
|
|
|
ubo->CharacterSize.x = 1.0f / float(FONT_TEX_WIDTH);
|
|
ubo->CharacterSize.y = 1.0f / float(FONT_TEX_HEIGHT);
|
|
|
|
GL.glUnmapBuffer(eGL_UNIFORM_BUFFER);
|
|
|
|
size_t len = strlen(text);
|
|
|
|
if((int)len > FONT_MAX_CHARS)
|
|
{
|
|
static bool printedWarning = false;
|
|
|
|
// this could be called once a frame, don't want to spam the log
|
|
if(!printedWarning)
|
|
{
|
|
printedWarning = true;
|
|
RDCWARN("log string '%s' is too long", text, (int)len);
|
|
}
|
|
|
|
len = FONT_MAX_CHARS;
|
|
}
|
|
|
|
GL.glBindBuffer(eGL_UNIFORM_BUFFER, ctxdata.StringUBO);
|
|
uint32_t *texs =
|
|
(uint32_t *)GL.glMapBufferRange(eGL_UNIFORM_BUFFER, 0, len * 4 * sizeof(uint32_t),
|
|
GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
|
|
|
|
if(texs)
|
|
{
|
|
for(size_t i = 0; i < len; i++)
|
|
{
|
|
texs[i * 4 + 0] = text[i] - ' ';
|
|
texs[i * 4 + 1] = text[i] - ' ';
|
|
texs[i * 4 + 2] = text[i] - ' ';
|
|
texs[i * 4 + 3] = text[i] - ' ';
|
|
}
|
|
}
|
|
else
|
|
{
|
|
static bool printedWarning = false;
|
|
|
|
// this could be called once a frame, don't want to spam the log
|
|
if(!printedWarning)
|
|
{
|
|
printedWarning = true;
|
|
RDCWARN("failed to map %d characters for '%s' (%d)", (int)len, text, ctxdata.StringUBO);
|
|
}
|
|
}
|
|
|
|
GL.glUnmapBuffer(eGL_UNIFORM_BUFFER);
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
// Make sure if you change any other state in here, that you also update the push
|
|
// and pop functions above (RenderTextState)
|
|
|
|
// set blend state
|
|
if(HasExt[ARB_draw_buffers_blend])
|
|
{
|
|
GL.glEnablei(eGL_BLEND, 0);
|
|
GL.glBlendFuncSeparatei(0, eGL_SRC_ALPHA, eGL_ONE_MINUS_SRC_ALPHA, eGL_SRC_ALPHA,
|
|
eGL_SRC_ALPHA);
|
|
GL.glBlendEquationSeparatei(0, eGL_FUNC_ADD, eGL_FUNC_ADD);
|
|
}
|
|
else
|
|
{
|
|
GL.glEnable(eGL_BLEND);
|
|
GL.glBlendFuncSeparate(eGL_SRC_ALPHA, eGL_ONE_MINUS_SRC_ALPHA, eGL_SRC_ALPHA, eGL_SRC_ALPHA);
|
|
GL.glBlendEquationSeparate(eGL_FUNC_ADD, eGL_FUNC_ADD);
|
|
}
|
|
|
|
if(HasExt[EXT_draw_buffers2] || HasExt[ARB_draw_buffers_blend])
|
|
{
|
|
GL.glColorMaski(0, GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
|
}
|
|
else
|
|
{
|
|
GL.glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
|
}
|
|
|
|
// set depth & stencil
|
|
GL.glDisable(eGL_DEPTH_TEST);
|
|
if(!IsGLES)
|
|
GL.glDisable(eGL_DEPTH_CLAMP);
|
|
GL.glDisable(eGL_STENCIL_TEST);
|
|
GL.glDisable(eGL_CULL_FACE);
|
|
GL.glDisable(eGL_RASTERIZER_DISCARD);
|
|
|
|
GL.glBindFramebuffer(eGL_DRAW_FRAMEBUFFER, 0);
|
|
|
|
// set viewport & scissor
|
|
if(HasExt[ARB_viewport_array])
|
|
{
|
|
GL.glViewportIndexedf(0, 0.0f, 0.0f, (float)m_InitParams.width, (float)m_InitParams.height);
|
|
GL.glDisablei(eGL_SCISSOR_TEST, 0);
|
|
}
|
|
else
|
|
{
|
|
GL.glViewport(0, 0, m_InitParams.width, m_InitParams.height);
|
|
GL.glDisable(eGL_SCISSOR_TEST);
|
|
}
|
|
|
|
if(!IsGLES)
|
|
GL.glPolygonMode(eGL_FRONT_AND_BACK, eGL_FILL);
|
|
|
|
if(GL.glClipControl && HasExt[ARB_clip_control])
|
|
GL.glClipControl(eGL_LOWER_LEFT, eGL_NEGATIVE_ONE_TO_ONE);
|
|
|
|
// bind UBOs
|
|
GL.glBindBufferBase(eGL_UNIFORM_BUFFER, 0, ctxdata.GeneralUBO);
|
|
GL.glBindBufferBase(eGL_UNIFORM_BUFFER, 1, ctxdata.GlyphUBO);
|
|
GL.glBindBufferBase(eGL_UNIFORM_BUFFER, 2, ctxdata.StringUBO);
|
|
|
|
// bind empty VAO just for valid rendering
|
|
GL.glBindVertexArray(ctxdata.DummyVAO);
|
|
|
|
// bind textures
|
|
GL.glActiveTexture(eGL_TEXTURE0);
|
|
GL.glBindTexture(eGL_TEXTURE_2D, ctxdata.GlyphTexture);
|
|
|
|
// bind program
|
|
GL.glUseProgram(ctxdata.Program);
|
|
|
|
// draw string
|
|
GL.glDrawArrays(eGL_TRIANGLES, 0, 6 * (GLsizei)len);
|
|
}
|
|
else
|
|
{
|
|
// if it wasn't created in modern fashion with createattribs, assume the worst and draw with
|
|
// immediate mode (since it's impossible that the context is core profile, this will always
|
|
// work)
|
|
//
|
|
// This isn't perfect since without a lot of fiddling we'd need to check if e.g. indexed
|
|
// blending should be used or not. Since we're not too worried about working in this situation,
|
|
// just doing something reasonable, we just assume roughly ~2.0 functionality
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
// Make sure if you change any other state in here, that you also update the push
|
|
// and pop functions above (RenderTextState)
|
|
|
|
// disable blending and some old-style fixed function features
|
|
GL.glDisable(eGL_BLEND);
|
|
GL.glDisable(eGL_LIGHTING);
|
|
GL.glDisable(eGL_ALPHA_TEST);
|
|
|
|
// set depth & stencil
|
|
GL.glDisable(eGL_DEPTH_TEST);
|
|
GL.glDisable(eGL_STENCIL_TEST);
|
|
GL.glDisable(eGL_CULL_FACE);
|
|
|
|
// set viewport & scissor
|
|
GL.glViewport(0, 0, (GLsizei)m_InitParams.width, (GLsizei)m_InitParams.height);
|
|
GL.glDisable(eGL_SCISSOR_TEST);
|
|
if(!IsGLES)
|
|
GL.glPolygonMode(eGL_FRONT_AND_BACK, eGL_FILL);
|
|
|
|
// bind textures
|
|
GL.glActiveTexture(eGL_TEXTURE0);
|
|
GL.glBindTexture(eGL_TEXTURE_2D, ctxdata.GlyphTexture);
|
|
GL.glEnable(eGL_TEXTURE_2D);
|
|
|
|
if(GL.glBindFramebuffer)
|
|
GL.glBindFramebuffer(eGL_DRAW_FRAMEBUFFER, 0);
|
|
|
|
// just in case, try to disable the programmable pipeline
|
|
if(GL.glUseProgram)
|
|
GL.glUseProgram(0);
|
|
if(GL.glBindProgramPipeline)
|
|
GL.glBindProgramPipeline(0);
|
|
|
|
// draw string (based on sample code from stb_truetype.h)
|
|
vector<Vec4f> vertices;
|
|
{
|
|
y += 1.0f;
|
|
y *= charPixelHeight;
|
|
|
|
#if ENABLED(RDOC_ANDROID)
|
|
y *= 2.0f;
|
|
#endif
|
|
|
|
float startx = x;
|
|
float starty = y;
|
|
|
|
float maxx = x, minx = x;
|
|
float maxy = y, miny = y - charPixelHeight;
|
|
|
|
stbtt_aligned_quad q;
|
|
|
|
const char *prepass = text;
|
|
while(*prepass)
|
|
{
|
|
char c = *prepass;
|
|
if(c >= firstChar && c <= lastChar)
|
|
{
|
|
stbtt_GetBakedQuad(chardata, FONT_TEX_WIDTH, FONT_TEX_HEIGHT, c - firstChar, &x, &y, &q, 1);
|
|
|
|
maxx = RDCMAX(maxx, RDCMAX(q.x0, q.x1));
|
|
maxy = RDCMAX(maxy, RDCMAX(q.y0, q.y1));
|
|
|
|
minx = RDCMIN(minx, RDCMIN(q.x0, q.x1));
|
|
miny = RDCMIN(miny, RDCMIN(q.y0, q.y1));
|
|
}
|
|
else
|
|
{
|
|
x += chardata[0].xadvance;
|
|
}
|
|
prepass++;
|
|
}
|
|
|
|
x = startx;
|
|
y = starty;
|
|
|
|
// draw black bar behind text
|
|
|
|
vertices.push_back(Vec4f(minx, maxy, 0.0f, 0.0f));
|
|
vertices.push_back(Vec4f(maxx, maxy, 0.0f, 0.0f));
|
|
vertices.push_back(Vec4f(maxx, miny, 0.0f, 0.0f));
|
|
vertices.push_back(Vec4f(minx, miny, 0.0f, 0.0f));
|
|
|
|
while(*text)
|
|
{
|
|
char c = *text;
|
|
if(c >= firstChar && c <= lastChar)
|
|
{
|
|
stbtt_GetBakedQuad(chardata, FONT_TEX_WIDTH, FONT_TEX_HEIGHT, c - firstChar, &x, &y, &q, 1);
|
|
|
|
vertices.push_back(Vec4f(q.x0, q.y0, q.s0, q.t0));
|
|
vertices.push_back(Vec4f(q.x1, q.y0, q.s1, q.t0));
|
|
vertices.push_back(Vec4f(q.x1, q.y1, q.s1, q.t1));
|
|
vertices.push_back(Vec4f(q.x0, q.y1, q.s0, q.t1));
|
|
|
|
maxx = RDCMAX(maxx, RDCMAX(q.x0, q.x1));
|
|
maxy = RDCMAX(maxy, RDCMAX(q.y0, q.y1));
|
|
}
|
|
else
|
|
{
|
|
x += chardata[0].xadvance;
|
|
}
|
|
++text;
|
|
}
|
|
}
|
|
m_Platform.DrawQuads((float)m_InitParams.width, (float)m_InitParams.height, vertices);
|
|
}
|
|
}
|