Files
renderdoc/renderdoc/driver/gl/gl_rendertext.cpp
T
baldurk 73e1f7e770 Refactor GL hookset to only a single shared hookset
* 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.
2018-07-06 22:44:18 +01:00

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);
}
}