mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-07-29 19:01:05 +00:00
5479 lines
185 KiB
C++
5479 lines
185 KiB
C++
/******************************************************************************
|
|
* The MIT License (MIT)
|
|
*
|
|
* Copyright (c) 2015-2017 Baldur Karlsson
|
|
* Copyright (c) 2014 Crytek
|
|
*
|
|
* 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 <float.h>
|
|
#include <algorithm>
|
|
#include "common/common.h"
|
|
#include "data/glsl_shaders.h"
|
|
#include "maths/camera.h"
|
|
#include "maths/formatpacking.h"
|
|
#include "maths/matrix.h"
|
|
#include "serialise/string_utils.h"
|
|
#include "gl_driver.h"
|
|
#include "gl_replay.h"
|
|
#include "gl_resources.h"
|
|
|
|
#define OPENGL 1
|
|
#include "data/glsl/debuguniforms.h"
|
|
|
|
GLuint GLReplay::CreateCShaderProgram(const vector<string> &csSources)
|
|
{
|
|
if(m_pDriver == NULL)
|
|
return 0;
|
|
|
|
MakeCurrentReplayContext(m_DebugCtx);
|
|
|
|
WrappedOpenGL &gl = *m_pDriver;
|
|
|
|
GLuint cs = gl.glCreateShader(eGL_COMPUTE_SHADER);
|
|
|
|
vector<const char *> srcs;
|
|
srcs.reserve(csSources.size());
|
|
for(size_t i = 0; i < csSources.size(); i++)
|
|
srcs.push_back(csSources[i].c_str());
|
|
|
|
gl.glShaderSource(cs, (GLsizei)srcs.size(), &srcs[0], NULL);
|
|
|
|
gl.glCompileShader(cs);
|
|
|
|
char buffer[1024];
|
|
GLint status = 0;
|
|
|
|
gl.glGetShaderiv(cs, eGL_COMPILE_STATUS, &status);
|
|
if(status == 0)
|
|
{
|
|
gl.glGetShaderInfoLog(cs, 1024, NULL, buffer);
|
|
RDCERR("Shader error: %s", buffer);
|
|
}
|
|
|
|
GLuint ret = gl.glCreateProgram();
|
|
|
|
gl.glAttachShader(ret, cs);
|
|
|
|
gl.glLinkProgram(ret);
|
|
|
|
gl.glGetProgramiv(ret, eGL_LINK_STATUS, &status);
|
|
if(status == 0)
|
|
{
|
|
gl.glGetProgramInfoLog(ret, 1024, NULL, buffer);
|
|
RDCERR("Link error: %s", buffer);
|
|
}
|
|
|
|
gl.glDetachShader(ret, cs);
|
|
|
|
gl.glDeleteShader(cs);
|
|
|
|
return ret;
|
|
}
|
|
|
|
GLuint GLReplay::CreateShaderProgram(const vector<string> &vs, const vector<string> &fs)
|
|
{
|
|
vector<string> empty;
|
|
return CreateShaderProgram(vs, fs, empty);
|
|
}
|
|
|
|
GLuint GLReplay::CreateShaderProgram(const vector<string> &vsSources,
|
|
const vector<string> &fsSources, const vector<string> &gsSources)
|
|
{
|
|
if(m_pDriver == NULL)
|
|
return 0;
|
|
|
|
MakeCurrentReplayContext(m_DebugCtx);
|
|
|
|
WrappedOpenGL &gl = *m_pDriver;
|
|
|
|
GLuint vs = 0;
|
|
GLuint fs = 0;
|
|
GLuint gs = 0;
|
|
|
|
char buffer[1024];
|
|
GLint status = 0;
|
|
|
|
if(!vsSources.empty())
|
|
{
|
|
vs = gl.glCreateShader(eGL_VERTEX_SHADER);
|
|
|
|
vector<const char *> srcs;
|
|
srcs.reserve(vsSources.size());
|
|
for(size_t i = 0; i < vsSources.size(); i++)
|
|
srcs.push_back(vsSources[i].c_str());
|
|
|
|
gl.glShaderSource(vs, (GLsizei)srcs.size(), &srcs[0], NULL);
|
|
|
|
gl.glCompileShader(vs);
|
|
|
|
gl.glGetShaderiv(vs, eGL_COMPILE_STATUS, &status);
|
|
if(status == 0)
|
|
{
|
|
gl.glGetShaderInfoLog(vs, 1024, NULL, buffer);
|
|
RDCERR("Shader error: %s", buffer);
|
|
}
|
|
}
|
|
|
|
if(!fsSources.empty())
|
|
{
|
|
fs = gl.glCreateShader(eGL_FRAGMENT_SHADER);
|
|
|
|
vector<const char *> srcs;
|
|
srcs.reserve(fsSources.size());
|
|
for(size_t i = 0; i < fsSources.size(); i++)
|
|
srcs.push_back(fsSources[i].c_str());
|
|
|
|
gl.glShaderSource(fs, (GLsizei)srcs.size(), &srcs[0], NULL);
|
|
|
|
gl.glCompileShader(fs);
|
|
|
|
gl.glGetShaderiv(fs, eGL_COMPILE_STATUS, &status);
|
|
if(status == 0)
|
|
{
|
|
gl.glGetShaderInfoLog(fs, 1024, NULL, buffer);
|
|
RDCERR("Shader error: %s", buffer);
|
|
}
|
|
}
|
|
|
|
if(!gsSources.empty())
|
|
{
|
|
gs = gl.glCreateShader(eGL_GEOMETRY_SHADER);
|
|
|
|
vector<const char *> srcs;
|
|
srcs.reserve(gsSources.size());
|
|
for(size_t i = 0; i < gsSources.size(); i++)
|
|
srcs.push_back(gsSources[i].c_str());
|
|
|
|
gl.glShaderSource(gs, (GLsizei)srcs.size(), &srcs[0], NULL);
|
|
|
|
gl.glCompileShader(gs);
|
|
|
|
gl.glGetShaderiv(gs, eGL_COMPILE_STATUS, &status);
|
|
if(status == 0)
|
|
{
|
|
gl.glGetShaderInfoLog(gs, 1024, NULL, buffer);
|
|
RDCERR("Shader error: %s", buffer);
|
|
}
|
|
}
|
|
|
|
GLuint ret = gl.glCreateProgram();
|
|
|
|
if(vs)
|
|
gl.glAttachShader(ret, vs);
|
|
if(fs)
|
|
gl.glAttachShader(ret, fs);
|
|
if(gs)
|
|
gl.glAttachShader(ret, gs);
|
|
|
|
gl.glProgramParameteri(ret, eGL_PROGRAM_SEPARABLE, GL_TRUE);
|
|
|
|
gl.glLinkProgram(ret);
|
|
|
|
gl.glGetProgramiv(ret, eGL_LINK_STATUS, &status);
|
|
if(status == 0)
|
|
{
|
|
gl.glGetProgramInfoLog(ret, 1024, NULL, buffer);
|
|
RDCERR("Shader error: %s", buffer);
|
|
}
|
|
|
|
if(vs)
|
|
gl.glDetachShader(ret, vs);
|
|
if(fs)
|
|
gl.glDetachShader(ret, fs);
|
|
if(gs)
|
|
gl.glDetachShader(ret, gs);
|
|
|
|
if(vs)
|
|
gl.glDeleteShader(vs);
|
|
if(fs)
|
|
gl.glDeleteShader(fs);
|
|
if(gs)
|
|
gl.glDeleteShader(gs);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void GLReplay::CheckGLSLVersion(const char *sl, int &glslVersion)
|
|
{
|
|
// GL_SHADING_LANGUAGE_VERSION for OpenGL ES:
|
|
// "OpenGL ES GLSL ES N.M vendor-specific information"
|
|
static const char *const GLSL_ES_STR = "OpenGL ES GLSL ES";
|
|
if(strncmp(sl, GLSL_ES_STR, 17) == 0)
|
|
sl += 18;
|
|
|
|
if(sl[0] >= '0' && sl[0] <= '9' && sl[1] == '.' && sl[2] >= '0' && sl[2] <= '9')
|
|
{
|
|
int major = int(sl[0] - '0');
|
|
int minor = int(sl[2] - '0');
|
|
int ver = major * 100 + minor * 10;
|
|
|
|
if(ver > glslVersion)
|
|
glslVersion = ver;
|
|
}
|
|
|
|
if(sl[0] >= '0' && sl[0] <= '9' && sl[1] >= '0' && sl[1] <= '9' && sl[2] == '0')
|
|
{
|
|
int major = int(sl[0] - '0');
|
|
int minor = int(sl[1] - '0');
|
|
int ver = major * 100 + minor * 10;
|
|
|
|
if(ver > glslVersion)
|
|
glslVersion = ver;
|
|
}
|
|
}
|
|
|
|
void GLReplay::InitDebugData()
|
|
{
|
|
if(m_pDriver == NULL)
|
|
return;
|
|
|
|
RenderDoc::Inst().SetProgress(DebugManagerInit, 0.0f);
|
|
|
|
{
|
|
uint64_t id = MakeOutputWindow(eWindowingSystem_Unknown, NULL, true);
|
|
|
|
m_DebugID = id;
|
|
m_DebugCtx = &m_OutputWindows[id];
|
|
|
|
MakeCurrentReplayContext(m_DebugCtx);
|
|
}
|
|
|
|
WrappedOpenGL &gl = *m_pDriver;
|
|
|
|
DebugData.outWidth = 0.0f;
|
|
DebugData.outHeight = 0.0f;
|
|
|
|
vector<string> empty;
|
|
|
|
vector<string> vs;
|
|
vector<string> fs;
|
|
vector<string> gs;
|
|
vector<string> cs;
|
|
|
|
int glslVersion;
|
|
int glslBaseVer;
|
|
int glslCSVer; // compute shader
|
|
ShaderType shaderType;
|
|
|
|
if(IsGLES)
|
|
{
|
|
glslVersion = glslBaseVer = glslCSVer = 310;
|
|
shaderType = eShaderGLSLES;
|
|
}
|
|
else
|
|
{
|
|
glslVersion = glslBaseVer = 150;
|
|
glslCSVer = 420;
|
|
shaderType = eShaderGLSL;
|
|
}
|
|
|
|
// TODO In case of GLES some currently unused shaders, which are guarded by HasExt[..] checks,
|
|
// still contain compile errors (e.g. array2ms.comp, ms2array.comp, quad*, etc.).
|
|
bool glesShadersAreComplete = !IsGLES;
|
|
|
|
GenerateGLSLShader(vs, shaderType, "", GetEmbeddedResource(glsl_blit_vert), glslBaseVer);
|
|
|
|
DebugData.texDisplayVSProg = CreateShaderProgram(vs, empty);
|
|
|
|
for(int i = 0; i < 3; i++)
|
|
{
|
|
string defines = string("#define UINT_TEX ") + (i == 1 ? "1" : "0") + "\n";
|
|
defines += string("#define SINT_TEX ") + (i == 2 ? "1" : "0") + "\n";
|
|
|
|
GenerateGLSLShader(fs, shaderType, defines, GetEmbeddedResource(glsl_texdisplay_frag),
|
|
glslBaseVer);
|
|
|
|
DebugData.texDisplayProg[i] = CreateShaderProgram(empty, fs);
|
|
}
|
|
|
|
RenderDoc::Inst().SetProgress(DebugManagerInit, 0.2f);
|
|
|
|
if(GLCoreVersion >= 43 && !IsGLES)
|
|
{
|
|
GLint numsl = 0;
|
|
gl.glGetIntegerv(eGL_NUM_SHADING_LANGUAGE_VERSIONS, &numsl);
|
|
|
|
for(GLint i = 0; i < numsl; i++)
|
|
{
|
|
const char *sl = (const char *)gl.glGetStringi(eGL_SHADING_LANGUAGE_VERSION, (GLuint)i);
|
|
|
|
CheckGLSLVersion(sl, glslVersion);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const char *sl = (const char *)gl.glGetString(eGL_SHADING_LANGUAGE_VERSION);
|
|
|
|
CheckGLSLVersion(sl, glslVersion);
|
|
}
|
|
|
|
DebugData.glslVersion = glslVersion;
|
|
|
|
RDCLOG("GLSL version %d", glslVersion);
|
|
|
|
GenerateGLSLShader(vs, shaderType, "", GetEmbeddedResource(glsl_blit_vert), glslBaseVer);
|
|
|
|
if(glesShadersAreComplete && HasExt[ARB_shader_image_load_store] && HasExt[ARB_gpu_shader5])
|
|
{
|
|
string defines = "";
|
|
|
|
if(glslVersion < 450)
|
|
{
|
|
// dFdx fine functions not available before GLSL 450. Use normal dFdx, which might be coarse,
|
|
// so won't show quad overdraw properly
|
|
defines += "#define dFdxFine dFdx\n\n";
|
|
defines += "#define dFdyFine dFdy\n\n";
|
|
|
|
RDCWARN("Quad overdraw requires GLSL 4.50 for dFd(xy)fine, using possibly coarse dFd(xy).");
|
|
m_pDriver->AddDebugMessage(
|
|
eDbgCategory_Portability, eDbgSeverity_Medium, eDbgSource_RuntimeWarning,
|
|
"Quad overdraw requires GLSL 4.50 for dFd(xy)fine, using possibly coarse dFd(xy).");
|
|
}
|
|
|
|
GenerateGLSLShader(fs, shaderType, defines, GetEmbeddedResource(glsl_quadwrite_frag),
|
|
RDCMIN(450, glslVersion));
|
|
|
|
DebugData.quadoverdrawFSProg = CreateShaderProgram(empty, fs);
|
|
|
|
GenerateGLSLShader(fs, shaderType, "", GetEmbeddedResource(glsl_quadresolve_frag), glslBaseVer);
|
|
|
|
DebugData.quadoverdrawResolveProg = CreateShaderProgram(vs, fs);
|
|
}
|
|
else
|
|
{
|
|
RDCWARN(
|
|
"GL_ARB_shader_image_load_store/GL_ARB_gpu_shader5 not supported, disabling quad overdraw "
|
|
"feature.");
|
|
m_pDriver->AddDebugMessage(eDbgCategory_Portability, eDbgSeverity_Medium,
|
|
eDbgSource_RuntimeWarning,
|
|
"GL_ARB_shader_image_load_store/GL_ARB_gpu_shader5 not supported, "
|
|
"disabling quad overdraw feature.");
|
|
DebugData.quadoverdrawFSProg = 0;
|
|
DebugData.quadoverdrawResolveProg = 0;
|
|
}
|
|
|
|
GenerateGLSLShader(fs, shaderType, "", GetEmbeddedResource(glsl_checkerboard_frag), glslBaseVer);
|
|
DebugData.checkerProg = CreateShaderProgram(vs, fs);
|
|
|
|
GenerateGLSLShader(fs, shaderType, "", GetEmbeddedResource(glsl_fixedcol_frag), glslBaseVer);
|
|
|
|
DebugData.fixedcolFSProg = CreateShaderProgram(empty, fs);
|
|
|
|
GenerateGLSLShader(vs, shaderType, "", GetEmbeddedResource(glsl_mesh_vert), glslBaseVer);
|
|
GenerateGLSLShader(fs, shaderType, "", GetEmbeddedResource(glsl_mesh_frag), glslBaseVer);
|
|
GenerateGLSLShader(gs, shaderType, "", GetEmbeddedResource(glsl_mesh_geom), glslBaseVer);
|
|
|
|
DebugData.meshProg = CreateShaderProgram(vs, fs);
|
|
DebugData.meshgsProg = CreateShaderProgram(vs, fs, gs);
|
|
|
|
GenerateGLSLShader(fs, shaderType, "", GetEmbeddedResource(glsl_trisize_frag), glslBaseVer);
|
|
GenerateGLSLShader(gs, shaderType, "", GetEmbeddedResource(glsl_trisize_geom), glslBaseVer);
|
|
|
|
DebugData.trisizeProg = CreateShaderProgram(vs, fs, gs);
|
|
|
|
gl.glGenProgramPipelines(1, &DebugData.texDisplayPipe);
|
|
|
|
RenderDoc::Inst().SetProgress(DebugManagerInit, 0.4f);
|
|
|
|
gl.glGenSamplers(1, &DebugData.linearSampler);
|
|
gl.glSamplerParameteri(DebugData.linearSampler, eGL_TEXTURE_MIN_FILTER, eGL_LINEAR);
|
|
gl.glSamplerParameteri(DebugData.linearSampler, eGL_TEXTURE_MAG_FILTER, eGL_LINEAR);
|
|
gl.glSamplerParameteri(DebugData.linearSampler, eGL_TEXTURE_WRAP_S, eGL_CLAMP_TO_EDGE);
|
|
gl.glSamplerParameteri(DebugData.linearSampler, eGL_TEXTURE_WRAP_T, eGL_CLAMP_TO_EDGE);
|
|
|
|
gl.glGenSamplers(1, &DebugData.pointSampler);
|
|
gl.glSamplerParameteri(DebugData.pointSampler, eGL_TEXTURE_MIN_FILTER, eGL_NEAREST_MIPMAP_NEAREST);
|
|
gl.glSamplerParameteri(DebugData.pointSampler, eGL_TEXTURE_MAG_FILTER, eGL_NEAREST);
|
|
gl.glSamplerParameteri(DebugData.pointSampler, eGL_TEXTURE_WRAP_S, eGL_CLAMP_TO_EDGE);
|
|
gl.glSamplerParameteri(DebugData.pointSampler, eGL_TEXTURE_WRAP_T, eGL_CLAMP_TO_EDGE);
|
|
|
|
gl.glGenSamplers(1, &DebugData.pointNoMipSampler);
|
|
gl.glSamplerParameteri(DebugData.pointNoMipSampler, eGL_TEXTURE_MIN_FILTER, eGL_NEAREST);
|
|
gl.glSamplerParameteri(DebugData.pointNoMipSampler, eGL_TEXTURE_MAG_FILTER, eGL_NEAREST);
|
|
gl.glSamplerParameteri(DebugData.pointNoMipSampler, eGL_TEXTURE_WRAP_S, eGL_CLAMP_TO_EDGE);
|
|
gl.glSamplerParameteri(DebugData.pointNoMipSampler, eGL_TEXTURE_WRAP_T, eGL_CLAMP_TO_EDGE);
|
|
|
|
gl.glGenBuffers(ARRAY_COUNT(DebugData.UBOs), DebugData.UBOs);
|
|
for(size_t i = 0; i < ARRAY_COUNT(DebugData.UBOs); i++)
|
|
{
|
|
gl.glBindBuffer(eGL_UNIFORM_BUFFER, DebugData.UBOs[i]);
|
|
gl.glNamedBufferDataEXT(DebugData.UBOs[i], 2048, NULL, eGL_DYNAMIC_DRAW);
|
|
RDCCOMPILE_ASSERT(sizeof(TexDisplayUBOData) <= 2048, "UBO too small");
|
|
RDCCOMPILE_ASSERT(sizeof(FontUBOData) <= 2048, "UBO too small");
|
|
RDCCOMPILE_ASSERT(sizeof(HistogramUBOData) <= 2048, "UBO too small");
|
|
RDCCOMPILE_ASSERT(sizeof(overdrawRamp) <= 2048, "UBO too small");
|
|
}
|
|
|
|
DebugData.overlayTexWidth = DebugData.overlayTexHeight = DebugData.overlayTexSamples = 0;
|
|
DebugData.overlayTex = DebugData.overlayFBO = 0;
|
|
|
|
gl.glGenFramebuffers(1, &DebugData.customFBO);
|
|
gl.glBindFramebuffer(eGL_FRAMEBUFFER, DebugData.customFBO);
|
|
DebugData.customTex = 0;
|
|
|
|
gl.glGenFramebuffers(1, &DebugData.pickPixelFBO);
|
|
gl.glBindFramebuffer(eGL_FRAMEBUFFER, DebugData.pickPixelFBO);
|
|
|
|
gl.glGenTextures(1, &DebugData.pickPixelTex);
|
|
gl.glBindTexture(eGL_TEXTURE_2D, DebugData.pickPixelTex);
|
|
|
|
gl.glTextureImage2DEXT(DebugData.pickPixelTex, eGL_TEXTURE_2D, 0, eGL_RGBA32F, 1, 1, 0, eGL_RGBA,
|
|
eGL_FLOAT, NULL);
|
|
gl.glTexParameteri(eGL_TEXTURE_2D, eGL_TEXTURE_MAX_LEVEL, 0);
|
|
gl.glTexParameteri(eGL_TEXTURE_2D, eGL_TEXTURE_MIN_FILTER, eGL_NEAREST);
|
|
gl.glTexParameteri(eGL_TEXTURE_2D, eGL_TEXTURE_MAG_FILTER, eGL_NEAREST);
|
|
gl.glTexParameteri(eGL_TEXTURE_2D, eGL_TEXTURE_WRAP_S, eGL_CLAMP_TO_EDGE);
|
|
gl.glTexParameteri(eGL_TEXTURE_2D, eGL_TEXTURE_WRAP_T, eGL_CLAMP_TO_EDGE);
|
|
gl.glFramebufferTexture(eGL_FRAMEBUFFER, eGL_COLOR_ATTACHMENT0, DebugData.pickPixelTex, 0);
|
|
|
|
gl.glGenVertexArrays(1, &DebugData.emptyVAO);
|
|
gl.glBindVertexArray(DebugData.emptyVAO);
|
|
|
|
RenderDoc::Inst().SetProgress(DebugManagerInit, 0.6f);
|
|
|
|
// histogram/minmax data
|
|
{
|
|
RDCEraseEl(DebugData.minmaxTileProgram);
|
|
RDCEraseEl(DebugData.histogramProgram);
|
|
RDCEraseEl(DebugData.minmaxResultProgram);
|
|
|
|
RDCCOMPILE_ASSERT(
|
|
ARRAY_COUNT(DebugData.minmaxTileProgram) >= (TEXDISPLAY_SINT_TEX | TEXDISPLAY_TYPEMASK) + 1,
|
|
"not enough programs");
|
|
|
|
string extensions =
|
|
"#extension GL_ARB_compute_shader : require\n"
|
|
"#extension GL_ARB_shader_storage_buffer_object : require\n";
|
|
|
|
for(int t = 1; glesShadersAreComplete && HasExt[ARB_compute_shader] && t <= RESTYPE_TEXTYPEMAX;
|
|
t++)
|
|
{
|
|
// float, uint, sint
|
|
for(int i = 0; i < 3; i++)
|
|
{
|
|
int idx = t;
|
|
if(i == 1)
|
|
idx |= TEXDISPLAY_UINT_TEX;
|
|
if(i == 2)
|
|
idx |= TEXDISPLAY_SINT_TEX;
|
|
|
|
{
|
|
string defines = extensions;
|
|
defines += string("#define SHADER_RESTYPE ") + ToStr::Get(t) + "\n";
|
|
defines += string("#define UINT_TEX ") + (i == 1 ? "1" : "0") + "\n";
|
|
defines += string("#define SINT_TEX ") + (i == 2 ? "1" : "0") + "\n";
|
|
|
|
GenerateGLSLShader(cs, shaderType, defines, GetEmbeddedResource(glsl_minmaxtile_comp),
|
|
glslCSVer);
|
|
|
|
DebugData.minmaxTileProgram[idx] = CreateCShaderProgram(cs);
|
|
}
|
|
|
|
{
|
|
string defines = extensions;
|
|
defines += string("#define SHADER_RESTYPE ") + ToStr::Get(t) + "\n";
|
|
defines += string("#define UINT_TEX ") + (i == 1 ? "1" : "0") + "\n";
|
|
defines += string("#define SINT_TEX ") + (i == 2 ? "1" : "0") + "\n";
|
|
|
|
GenerateGLSLShader(cs, shaderType, defines, GetEmbeddedResource(glsl_histogram_comp),
|
|
glslCSVer);
|
|
|
|
DebugData.histogramProgram[idx] = CreateCShaderProgram(cs);
|
|
}
|
|
|
|
if(t == 1)
|
|
{
|
|
string defines = extensions;
|
|
defines += string("#define SHADER_RESTYPE ") + ToStr::Get(t) + "\n";
|
|
defines += string("#define UINT_TEX ") + (i == 1 ? "1" : "0") + "\n";
|
|
defines += string("#define SINT_TEX ") + (i == 2 ? "1" : "0") + "\n";
|
|
|
|
GenerateGLSLShader(cs, shaderType, defines, GetEmbeddedResource(glsl_minmaxresult_comp),
|
|
glslCSVer);
|
|
|
|
DebugData.minmaxResultProgram[i] = CreateCShaderProgram(cs);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!HasExt[ARB_compute_shader])
|
|
{
|
|
RDCWARN("GL_ARB_compute_shader not supported, disabling min/max and histogram features.");
|
|
m_pDriver->AddDebugMessage(
|
|
eDbgCategory_Portability, eDbgSeverity_Medium, eDbgSource_RuntimeWarning,
|
|
"GL_ARB_compute_shader not supported, disabling min/max and histogram features.");
|
|
}
|
|
|
|
gl.glGenBuffers(1, &DebugData.minmaxTileResult);
|
|
gl.glGenBuffers(1, &DebugData.minmaxResult);
|
|
gl.glGenBuffers(1, &DebugData.histogramBuf);
|
|
|
|
const uint32_t maxTexDim = 16384;
|
|
const uint32_t blockPixSize = HGRAM_PIXELS_PER_TILE * HGRAM_TILES_PER_BLOCK;
|
|
const uint32_t maxBlocksNeeded = (maxTexDim * maxTexDim) / (blockPixSize * blockPixSize);
|
|
|
|
const size_t byteSize =
|
|
2 * sizeof(Vec4f) * HGRAM_TILES_PER_BLOCK * HGRAM_TILES_PER_BLOCK * maxBlocksNeeded;
|
|
|
|
gl.glNamedBufferDataEXT(DebugData.minmaxTileResult, byteSize, NULL, eGL_DYNAMIC_DRAW);
|
|
gl.glNamedBufferDataEXT(DebugData.minmaxResult, sizeof(Vec4f) * 2, NULL, eGL_DYNAMIC_READ);
|
|
gl.glNamedBufferDataEXT(DebugData.histogramBuf, sizeof(uint32_t) * 4 * HGRAM_NUM_BUCKETS, NULL,
|
|
eGL_DYNAMIC_READ);
|
|
}
|
|
|
|
if(glesShadersAreComplete && HasExt[ARB_compute_shader])
|
|
{
|
|
GenerateGLSLShader(cs, shaderType, "", GetEmbeddedResource(glsl_ms2array_comp), glslCSVer);
|
|
DebugData.MS2Array = CreateCShaderProgram(cs);
|
|
|
|
GenerateGLSLShader(cs, shaderType, "", GetEmbeddedResource(glsl_array2ms_comp), glslCSVer);
|
|
DebugData.Array2MS = CreateCShaderProgram(cs);
|
|
}
|
|
else
|
|
{
|
|
DebugData.MS2Array = 0;
|
|
DebugData.Array2MS = 0;
|
|
RDCWARN("GL_ARB_compute_shader not supported, disabling 2DMS save/load.");
|
|
m_pDriver->AddDebugMessage(eDbgCategory_Portability, eDbgSeverity_Medium,
|
|
eDbgSource_RuntimeWarning,
|
|
"GL_ARB_compute_shader not supported, disabling 2DMS save/load.");
|
|
}
|
|
|
|
if(glesShadersAreComplete && HasExt[ARB_compute_shader])
|
|
{
|
|
string defines =
|
|
"#extension GL_ARB_compute_shader : require\n"
|
|
"#extension GL_ARB_shader_storage_buffer_object : require";
|
|
GenerateGLSLShader(cs, shaderType, defines, GetEmbeddedResource(glsl_mesh_comp), glslCSVer);
|
|
DebugData.meshPickProgram = CreateCShaderProgram(cs);
|
|
}
|
|
else
|
|
{
|
|
DebugData.meshPickProgram = 0;
|
|
RDCWARN("GL_ARB_compute_shader not supported, disabling mesh picking.");
|
|
m_pDriver->AddDebugMessage(eDbgCategory_Portability, eDbgSeverity_Medium,
|
|
eDbgSource_RuntimeWarning,
|
|
"GL_ARB_compute_shader not supported, disabling mesh picking.");
|
|
}
|
|
|
|
RenderDoc::Inst().SetProgress(DebugManagerInit, 0.8f);
|
|
|
|
DebugData.pickResultBuf = 0;
|
|
|
|
if(DebugData.meshPickProgram)
|
|
{
|
|
gl.glGenBuffers(1, &DebugData.pickResultBuf);
|
|
gl.glBindBuffer(eGL_SHADER_STORAGE_BUFFER, DebugData.pickResultBuf);
|
|
gl.glNamedBufferDataEXT(DebugData.pickResultBuf,
|
|
sizeof(Vec4f) * DebugRenderData::maxMeshPicks + sizeof(uint32_t) * 4,
|
|
NULL, eGL_DYNAMIC_READ);
|
|
|
|
// sized/created on demand
|
|
DebugData.pickVBBuf = DebugData.pickIBBuf = 0;
|
|
DebugData.pickVBSize = DebugData.pickIBSize = 0;
|
|
}
|
|
|
|
gl.glGenVertexArrays(1, &DebugData.meshVAO);
|
|
gl.glBindVertexArray(DebugData.meshVAO);
|
|
|
|
gl.glGenBuffers(1, &DebugData.axisFrustumBuffer);
|
|
gl.glBindBuffer(eGL_ARRAY_BUFFER, DebugData.axisFrustumBuffer);
|
|
|
|
Vec3f TLN = Vec3f(-1.0f, 1.0f, 0.0f); // TopLeftNear, etc...
|
|
Vec3f TRN = Vec3f(1.0f, 1.0f, 0.0f);
|
|
Vec3f BLN = Vec3f(-1.0f, -1.0f, 0.0f);
|
|
Vec3f BRN = Vec3f(1.0f, -1.0f, 0.0f);
|
|
|
|
Vec3f TLF = Vec3f(-1.0f, 1.0f, 1.0f);
|
|
Vec3f TRF = Vec3f(1.0f, 1.0f, 1.0f);
|
|
Vec3f BLF = Vec3f(-1.0f, -1.0f, 1.0f);
|
|
Vec3f BRF = Vec3f(1.0f, -1.0f, 1.0f);
|
|
|
|
Vec3f axisFrustum[] = {
|
|
// axis marker vertices
|
|
Vec3f(0.0f, 0.0f, 0.0f), Vec3f(1.0f, 0.0f, 0.0f), Vec3f(0.0f, 0.0f, 0.0f),
|
|
Vec3f(0.0f, 1.0f, 0.0f), Vec3f(0.0f, 0.0f, 0.0f), Vec3f(0.0f, 0.0f, 1.0f),
|
|
|
|
// frustum vertices
|
|
TLN, TRN, TRN, BRN, BRN, BLN, BLN, TLN,
|
|
|
|
TLN, TLF, TRN, TRF, BLN, BLF, BRN, BRF,
|
|
|
|
TLF, TRF, TRF, BRF, BRF, BLF, BLF, TLF,
|
|
};
|
|
|
|
gl.glNamedBufferDataEXT(DebugData.axisFrustumBuffer, sizeof(axisFrustum), axisFrustum,
|
|
eGL_STATIC_DRAW);
|
|
|
|
gl.glGenVertexArrays(1, &DebugData.axisVAO);
|
|
gl.glBindVertexArray(DebugData.axisVAO);
|
|
gl.glVertexAttribPointer(0, 3, eGL_FLOAT, GL_FALSE, sizeof(Vec3f), NULL);
|
|
gl.glEnableVertexAttribArray(0);
|
|
|
|
gl.glGenVertexArrays(1, &DebugData.frustumVAO);
|
|
gl.glBindVertexArray(DebugData.frustumVAO);
|
|
gl.glVertexAttribPointer(0, 3, eGL_FLOAT, GL_FALSE, sizeof(Vec3f),
|
|
(const void *)(sizeof(Vec3f) * 6));
|
|
gl.glEnableVertexAttribArray(0);
|
|
|
|
gl.glGenVertexArrays(1, &DebugData.triHighlightVAO);
|
|
gl.glBindVertexArray(DebugData.triHighlightVAO);
|
|
|
|
gl.glGenBuffers(1, &DebugData.triHighlightBuffer);
|
|
gl.glBindBuffer(eGL_ARRAY_BUFFER, DebugData.triHighlightBuffer);
|
|
|
|
gl.glNamedBufferDataEXT(DebugData.triHighlightBuffer, sizeof(Vec4f) * 24, NULL, eGL_DYNAMIC_DRAW);
|
|
|
|
gl.glVertexAttribPointer(0, 4, eGL_FLOAT, GL_FALSE, sizeof(Vec4f), NULL);
|
|
gl.glEnableVertexAttribArray(0);
|
|
|
|
GenerateGLSLShader(vs, shaderType, "", GetEmbeddedResource(glsl_blit_vert), glslBaseVer);
|
|
GenerateGLSLShader(fs, shaderType, "", GetEmbeddedResource(glsl_outline_frag), glslBaseVer);
|
|
|
|
DebugData.outlineQuadProg = CreateShaderProgram(vs, fs);
|
|
|
|
MakeCurrentReplayContext(&m_ReplayCtx);
|
|
|
|
// these below need to be made on the replay context, as they are context-specific (not shared)
|
|
// and will be used on the replay context.
|
|
gl.glGenProgramPipelines(1, &DebugData.overlayPipe);
|
|
|
|
gl.glGenTransformFeedbacks(1, &DebugData.feedbackObj);
|
|
gl.glGenBuffers(1, &DebugData.feedbackBuffer);
|
|
DebugData.feedbackQueries.push_back(0);
|
|
gl.glGenQueries(1, &DebugData.feedbackQueries[0]);
|
|
|
|
gl.glBindTransformFeedback(eGL_TRANSFORM_FEEDBACK, DebugData.feedbackObj);
|
|
gl.glBindBuffer(eGL_TRANSFORM_FEEDBACK_BUFFER, DebugData.feedbackBuffer);
|
|
gl.glNamedBufferDataEXT(DebugData.feedbackBuffer, 32 * 1024 * 1024, NULL, eGL_DYNAMIC_READ);
|
|
gl.glBindBufferBase(eGL_TRANSFORM_FEEDBACK_BUFFER, 0, DebugData.feedbackBuffer);
|
|
gl.glBindTransformFeedback(eGL_TRANSFORM_FEEDBACK, 0);
|
|
|
|
RenderDoc::Inst().SetProgress(DebugManagerInit, 1.0f);
|
|
|
|
if(!HasExt[ARB_gpu_shader5])
|
|
{
|
|
RDCWARN(
|
|
"ARB_gpu_shader5 not supported, pixel picking and saving of integer textures may be "
|
|
"inaccurate.");
|
|
m_pDriver->AddDebugMessage(eDbgCategory_Portability, eDbgSeverity_Medium,
|
|
eDbgSource_RuntimeWarning,
|
|
"ARB_gpu_shader5 not supported, pixel picking and saving of integer "
|
|
"textures may be inaccurate.");
|
|
|
|
m_Degraded = true;
|
|
}
|
|
|
|
if(!HasExt[ARB_stencil_texturing])
|
|
{
|
|
RDCWARN("ARB_stencil_texturing not supported, stencil values will not be displayed or picked.");
|
|
m_pDriver->AddDebugMessage(
|
|
eDbgCategory_Portability, eDbgSeverity_Medium, eDbgSource_RuntimeWarning,
|
|
"ARB_stencil_texturing not supported, stencil values will not be displayed or picked.");
|
|
|
|
m_Degraded = true;
|
|
}
|
|
|
|
if(!HasExt[ARB_shader_image_load_store] || !HasExt[ARB_compute_shader])
|
|
{
|
|
m_Degraded = true;
|
|
}
|
|
}
|
|
|
|
void GLReplay::DeleteDebugData()
|
|
{
|
|
WrappedOpenGL &gl = *m_pDriver;
|
|
|
|
MakeCurrentReplayContext(&m_ReplayCtx);
|
|
|
|
gl.glDeleteProgramPipelines(1, &DebugData.overlayPipe);
|
|
|
|
gl.glDeleteTransformFeedbacks(1, &DebugData.feedbackObj);
|
|
gl.glDeleteBuffers(1, &DebugData.feedbackBuffer);
|
|
gl.glDeleteQueries((GLsizei)DebugData.feedbackQueries.size(), DebugData.feedbackQueries.data());
|
|
|
|
MakeCurrentReplayContext(m_DebugCtx);
|
|
|
|
for(auto it = m_PostVSData.begin(); it != m_PostVSData.end(); ++it)
|
|
{
|
|
gl.glDeleteBuffers(1, &it->second.vsout.buf);
|
|
gl.glDeleteBuffers(1, &it->second.vsout.idxBuf);
|
|
gl.glDeleteBuffers(1, &it->second.gsout.buf);
|
|
gl.glDeleteBuffers(1, &it->second.gsout.idxBuf);
|
|
}
|
|
|
|
m_PostVSData.clear();
|
|
|
|
gl.glDeleteFramebuffers(1, &DebugData.overlayFBO);
|
|
gl.glDeleteTextures(1, &DebugData.overlayTex);
|
|
|
|
gl.glDeleteProgram(DebugData.quadoverdrawFSProg);
|
|
gl.glDeleteProgram(DebugData.quadoverdrawResolveProg);
|
|
|
|
gl.glDeleteProgram(DebugData.texDisplayVSProg);
|
|
for(int i = 0; i < 3; i++)
|
|
gl.glDeleteProgram(DebugData.texDisplayProg[i]);
|
|
|
|
gl.glDeleteProgramPipelines(1, &DebugData.texDisplayPipe);
|
|
|
|
gl.glDeleteProgram(DebugData.checkerProg);
|
|
gl.glDeleteProgram(DebugData.fixedcolFSProg);
|
|
gl.glDeleteProgram(DebugData.meshProg);
|
|
gl.glDeleteProgram(DebugData.meshgsProg);
|
|
gl.glDeleteProgram(DebugData.trisizeProg);
|
|
|
|
gl.glDeleteSamplers(1, &DebugData.linearSampler);
|
|
gl.glDeleteSamplers(1, &DebugData.pointSampler);
|
|
gl.glDeleteSamplers(1, &DebugData.pointNoMipSampler);
|
|
gl.glDeleteBuffers(ARRAY_COUNT(DebugData.UBOs), DebugData.UBOs);
|
|
gl.glDeleteFramebuffers(1, &DebugData.pickPixelFBO);
|
|
gl.glDeleteTextures(1, &DebugData.pickPixelTex);
|
|
|
|
gl.glDeleteBuffers(1, &DebugData.genericUBO);
|
|
|
|
gl.glDeleteFramebuffers(1, &DebugData.customFBO);
|
|
gl.glDeleteTextures(1, &DebugData.customTex);
|
|
|
|
gl.glDeleteVertexArrays(1, &DebugData.emptyVAO);
|
|
|
|
for(int t = 1; t <= RESTYPE_TEXTYPEMAX; t++)
|
|
{
|
|
// float, uint, sint
|
|
for(int i = 0; i < 3; i++)
|
|
{
|
|
int idx = t;
|
|
if(i == 1)
|
|
idx |= TEXDISPLAY_UINT_TEX;
|
|
if(i == 2)
|
|
idx |= TEXDISPLAY_SINT_TEX;
|
|
|
|
gl.glDeleteProgram(DebugData.minmaxTileProgram[idx]);
|
|
gl.glDeleteProgram(DebugData.histogramProgram[idx]);
|
|
|
|
gl.glDeleteProgram(DebugData.minmaxResultProgram[i]);
|
|
DebugData.minmaxResultProgram[i] = 0;
|
|
}
|
|
}
|
|
|
|
gl.glDeleteProgram(DebugData.meshPickProgram);
|
|
gl.glDeleteBuffers(1, &DebugData.pickIBBuf);
|
|
gl.glDeleteBuffers(1, &DebugData.pickVBBuf);
|
|
gl.glDeleteBuffers(1, &DebugData.pickResultBuf);
|
|
|
|
gl.glDeleteProgram(DebugData.Array2MS);
|
|
gl.glDeleteProgram(DebugData.MS2Array);
|
|
|
|
gl.glDeleteBuffers(1, &DebugData.minmaxTileResult);
|
|
gl.glDeleteBuffers(1, &DebugData.minmaxResult);
|
|
gl.glDeleteBuffers(1, &DebugData.histogramBuf);
|
|
|
|
gl.glDeleteVertexArrays(1, &DebugData.meshVAO);
|
|
gl.glDeleteVertexArrays(1, &DebugData.axisVAO);
|
|
gl.glDeleteVertexArrays(1, &DebugData.frustumVAO);
|
|
gl.glDeleteVertexArrays(1, &DebugData.triHighlightVAO);
|
|
|
|
gl.glDeleteBuffers(1, &DebugData.axisFrustumBuffer);
|
|
gl.glDeleteBuffers(1, &DebugData.triHighlightBuffer);
|
|
|
|
gl.glDeleteProgram(DebugData.outlineQuadProg);
|
|
}
|
|
|
|
bool GLReplay::GetMinMax(ResourceId texid, uint32_t sliceFace, uint32_t mip, uint32_t sample,
|
|
FormatComponentType typeHint, float *minval, float *maxval)
|
|
{
|
|
if(texid == ResourceId() || m_pDriver->m_Textures.find(texid) == m_pDriver->m_Textures.end())
|
|
return false;
|
|
|
|
if(!HasExt[ARB_compute_shader])
|
|
return false;
|
|
|
|
auto &texDetails = m_pDriver->m_Textures[texid];
|
|
|
|
FetchTexture details = GetTexture(texid);
|
|
|
|
const GLHookSet &gl = m_pDriver->GetHookset();
|
|
|
|
int texSlot = 0;
|
|
int intIdx = 0;
|
|
|
|
bool renderbuffer = false;
|
|
|
|
switch(texDetails.curType)
|
|
{
|
|
case eGL_RENDERBUFFER:
|
|
texSlot = RESTYPE_TEX2D;
|
|
renderbuffer = true;
|
|
break;
|
|
case eGL_TEXTURE_1D: texSlot = RESTYPE_TEX1D; break;
|
|
default:
|
|
RDCWARN("Unexpected texture type");
|
|
// fall through
|
|
case eGL_TEXTURE_2D: texSlot = RESTYPE_TEX2D; break;
|
|
case eGL_TEXTURE_2D_MULTISAMPLE: texSlot = RESTYPE_TEX2DMS; break;
|
|
case eGL_TEXTURE_RECTANGLE: texSlot = RESTYPE_TEXRECT; break;
|
|
case eGL_TEXTURE_BUFFER: texSlot = RESTYPE_TEXBUFFER; break;
|
|
case eGL_TEXTURE_3D: texSlot = RESTYPE_TEX3D; break;
|
|
case eGL_TEXTURE_CUBE_MAP: texSlot = RESTYPE_TEXCUBE; break;
|
|
case eGL_TEXTURE_1D_ARRAY: texSlot = RESTYPE_TEX1DARRAY; break;
|
|
case eGL_TEXTURE_2D_ARRAY: texSlot = RESTYPE_TEX2DARRAY; break;
|
|
case eGL_TEXTURE_CUBE_MAP_ARRAY: texSlot = RESTYPE_TEXCUBEARRAY; break;
|
|
}
|
|
|
|
GLenum target = texDetails.curType;
|
|
GLuint texname = texDetails.resource.name;
|
|
|
|
// do blit from renderbuffer to texture, then sample from texture
|
|
if(renderbuffer)
|
|
{
|
|
// need replay context active to do blit (as FBOs aren't shared)
|
|
MakeCurrentReplayContext(&m_ReplayCtx);
|
|
|
|
GLuint curDrawFBO = 0;
|
|
GLuint curReadFBO = 0;
|
|
gl.glGetIntegerv(eGL_DRAW_FRAMEBUFFER_BINDING, (GLint *)&curDrawFBO);
|
|
gl.glGetIntegerv(eGL_READ_FRAMEBUFFER_BINDING, (GLint *)&curReadFBO);
|
|
|
|
gl.glBindFramebuffer(eGL_DRAW_FRAMEBUFFER, texDetails.renderbufferFBOs[1]);
|
|
gl.glBindFramebuffer(eGL_READ_FRAMEBUFFER, texDetails.renderbufferFBOs[0]);
|
|
|
|
gl.glBlitFramebuffer(
|
|
0, 0, texDetails.width, texDetails.height, 0, 0, texDetails.width, texDetails.height,
|
|
GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT, eGL_NEAREST);
|
|
|
|
gl.glBindFramebuffer(eGL_DRAW_FRAMEBUFFER, curDrawFBO);
|
|
gl.glBindFramebuffer(eGL_READ_FRAMEBUFFER, curReadFBO);
|
|
|
|
texname = texDetails.renderbufferReadTex;
|
|
target = eGL_TEXTURE_2D;
|
|
}
|
|
|
|
MakeCurrentReplayContext(m_DebugCtx);
|
|
|
|
gl.glBindBufferBase(eGL_UNIFORM_BUFFER, 2, DebugData.UBOs[0]);
|
|
HistogramUBOData *cdata =
|
|
(HistogramUBOData *)gl.glMapBufferRange(eGL_UNIFORM_BUFFER, 0, sizeof(HistogramUBOData),
|
|
GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
|
|
|
|
cdata->HistogramTextureResolution.x = (float)RDCMAX(details.width >> mip, 1U);
|
|
cdata->HistogramTextureResolution.y = (float)RDCMAX(details.height >> mip, 1U);
|
|
cdata->HistogramTextureResolution.z = (float)RDCMAX(details.depth >> mip, 1U);
|
|
if(texDetails.curType != eGL_TEXTURE_3D)
|
|
cdata->HistogramSlice = (float)sliceFace + 0.001f;
|
|
else
|
|
cdata->HistogramSlice = (float)(sliceFace >> mip);
|
|
cdata->HistogramMip = (int)mip;
|
|
cdata->HistogramNumSamples = texDetails.samples;
|
|
cdata->HistogramSample = (int)RDCCLAMP(sample, 0U, details.msSamp - 1);
|
|
if(sample == ~0U)
|
|
cdata->HistogramSample = -int(details.msSamp);
|
|
cdata->HistogramMin = 0.0f;
|
|
cdata->HistogramMax = 1.0f;
|
|
cdata->HistogramChannels = 0xf;
|
|
|
|
int progIdx = texSlot;
|
|
|
|
if(details.format.compType == eCompType_UInt)
|
|
{
|
|
progIdx |= TEXDISPLAY_UINT_TEX;
|
|
intIdx = 1;
|
|
}
|
|
if(details.format.compType == eCompType_SInt)
|
|
{
|
|
progIdx |= TEXDISPLAY_SINT_TEX;
|
|
intIdx = 2;
|
|
}
|
|
|
|
int blocksX = (int)ceil(cdata->HistogramTextureResolution.x /
|
|
float(HGRAM_PIXELS_PER_TILE * HGRAM_TILES_PER_BLOCK));
|
|
int blocksY = (int)ceil(cdata->HistogramTextureResolution.y /
|
|
float(HGRAM_PIXELS_PER_TILE * HGRAM_TILES_PER_BLOCK));
|
|
|
|
gl.glUnmapBuffer(eGL_UNIFORM_BUFFER);
|
|
|
|
gl.glActiveTexture((RDCGLenum)(eGL_TEXTURE0 + texSlot));
|
|
gl.glBindTexture(target, texname);
|
|
if(texSlot == RESTYPE_TEXRECT || texSlot == RESTYPE_TEXBUFFER)
|
|
gl.glBindSampler(texSlot, DebugData.pointNoMipSampler);
|
|
else
|
|
gl.glBindSampler(texSlot, DebugData.pointSampler);
|
|
|
|
int maxlevel = -1;
|
|
|
|
int clampmaxlevel = details.mips - 1;
|
|
|
|
gl.glGetTextureParameterivEXT(texname, target, eGL_TEXTURE_MAX_LEVEL, (GLint *)&maxlevel);
|
|
|
|
// need to ensure texture is mipmap complete by clamping TEXTURE_MAX_LEVEL.
|
|
if(clampmaxlevel != maxlevel)
|
|
{
|
|
gl.glTextureParameterivEXT(texname, target, eGL_TEXTURE_MAX_LEVEL, (GLint *)&clampmaxlevel);
|
|
}
|
|
else
|
|
{
|
|
maxlevel = -1;
|
|
}
|
|
|
|
gl.glBindBufferBase(eGL_SHADER_STORAGE_BUFFER, 0, DebugData.minmaxTileResult);
|
|
|
|
gl.glUseProgram(DebugData.minmaxTileProgram[progIdx]);
|
|
gl.glDispatchCompute(blocksX, blocksY, 1);
|
|
|
|
gl.glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
|
|
|
gl.glBindBufferBase(eGL_SHADER_STORAGE_BUFFER, 0, DebugData.minmaxResult);
|
|
gl.glBindBufferBase(eGL_SHADER_STORAGE_BUFFER, 1, DebugData.minmaxTileResult);
|
|
|
|
gl.glUseProgram(DebugData.minmaxResultProgram[intIdx]);
|
|
gl.glDispatchCompute(1, 1, 1);
|
|
|
|
gl.glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
|
|
|
Vec4f minmax[2];
|
|
gl.glBindBuffer(eGL_COPY_READ_BUFFER, DebugData.minmaxResult);
|
|
gl.glGetBufferSubData(eGL_COPY_READ_BUFFER, 0, sizeof(minmax), minmax);
|
|
|
|
if(maxlevel >= 0)
|
|
gl.glTextureParameterivEXT(texname, target, eGL_TEXTURE_MAX_LEVEL, (GLint *)&maxlevel);
|
|
|
|
minval[0] = minmax[0].x;
|
|
minval[1] = minmax[0].y;
|
|
minval[2] = minmax[0].z;
|
|
minval[3] = minmax[0].w;
|
|
|
|
maxval[0] = minmax[1].x;
|
|
maxval[1] = minmax[1].y;
|
|
maxval[2] = minmax[1].z;
|
|
maxval[3] = minmax[1].w;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool GLReplay::GetHistogram(ResourceId texid, uint32_t sliceFace, uint32_t mip, uint32_t sample,
|
|
FormatComponentType typeHint, float minval, float maxval,
|
|
bool channels[4], vector<uint32_t> &histogram)
|
|
{
|
|
if(minval >= maxval || texid == ResourceId())
|
|
return false;
|
|
|
|
if(m_pDriver->m_Textures.find(texid) == m_pDriver->m_Textures.end())
|
|
return false;
|
|
|
|
if(!HasExt[ARB_compute_shader])
|
|
return false;
|
|
|
|
auto &texDetails = m_pDriver->m_Textures[texid];
|
|
|
|
FetchTexture details = GetTexture(texid);
|
|
|
|
const GLHookSet &gl = m_pDriver->GetHookset();
|
|
|
|
int texSlot = 0;
|
|
int intIdx = 0;
|
|
|
|
bool renderbuffer = false;
|
|
|
|
switch(texDetails.curType)
|
|
{
|
|
case eGL_RENDERBUFFER:
|
|
texSlot = RESTYPE_TEX2D;
|
|
renderbuffer = true;
|
|
break;
|
|
case eGL_TEXTURE_1D: texSlot = RESTYPE_TEX1D; break;
|
|
default:
|
|
RDCWARN("Unexpected texture type");
|
|
// fall through
|
|
case eGL_TEXTURE_2D: texSlot = RESTYPE_TEX2D; break;
|
|
case eGL_TEXTURE_2D_MULTISAMPLE: texSlot = RESTYPE_TEX2DMS; break;
|
|
case eGL_TEXTURE_RECTANGLE: texSlot = RESTYPE_TEXRECT; break;
|
|
case eGL_TEXTURE_BUFFER: texSlot = RESTYPE_TEXBUFFER; break;
|
|
case eGL_TEXTURE_3D: texSlot = RESTYPE_TEX3D; break;
|
|
case eGL_TEXTURE_CUBE_MAP: texSlot = RESTYPE_TEXCUBE; break;
|
|
case eGL_TEXTURE_1D_ARRAY: texSlot = RESTYPE_TEX1DARRAY; break;
|
|
case eGL_TEXTURE_2D_ARRAY: texSlot = RESTYPE_TEX2DARRAY; break;
|
|
case eGL_TEXTURE_CUBE_MAP_ARRAY: texSlot = RESTYPE_TEXCUBEARRAY; break;
|
|
}
|
|
|
|
GLenum target = texDetails.curType;
|
|
GLuint texname = texDetails.resource.name;
|
|
|
|
// do blit from renderbuffer to texture, then sample from texture
|
|
if(renderbuffer)
|
|
{
|
|
// need replay context active to do blit (as FBOs aren't shared)
|
|
MakeCurrentReplayContext(&m_ReplayCtx);
|
|
|
|
GLuint curDrawFBO = 0;
|
|
GLuint curReadFBO = 0;
|
|
gl.glGetIntegerv(eGL_DRAW_FRAMEBUFFER_BINDING, (GLint *)&curDrawFBO);
|
|
gl.glGetIntegerv(eGL_READ_FRAMEBUFFER_BINDING, (GLint *)&curReadFBO);
|
|
|
|
gl.glBindFramebuffer(eGL_DRAW_FRAMEBUFFER, texDetails.renderbufferFBOs[1]);
|
|
gl.glBindFramebuffer(eGL_READ_FRAMEBUFFER, texDetails.renderbufferFBOs[0]);
|
|
|
|
gl.glBlitFramebuffer(
|
|
0, 0, texDetails.width, texDetails.height, 0, 0, texDetails.width, texDetails.height,
|
|
GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT, eGL_NEAREST);
|
|
|
|
gl.glBindFramebuffer(eGL_DRAW_FRAMEBUFFER, curDrawFBO);
|
|
gl.glBindFramebuffer(eGL_READ_FRAMEBUFFER, curReadFBO);
|
|
|
|
texname = texDetails.renderbufferReadTex;
|
|
target = eGL_TEXTURE_2D;
|
|
}
|
|
|
|
MakeCurrentReplayContext(m_DebugCtx);
|
|
|
|
gl.glBindBufferBase(eGL_UNIFORM_BUFFER, 2, DebugData.UBOs[0]);
|
|
HistogramUBOData *cdata =
|
|
(HistogramUBOData *)gl.glMapBufferRange(eGL_UNIFORM_BUFFER, 0, sizeof(HistogramUBOData),
|
|
GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
|
|
|
|
cdata->HistogramTextureResolution.x = (float)RDCMAX(details.width >> mip, 1U);
|
|
cdata->HistogramTextureResolution.y = (float)RDCMAX(details.height >> mip, 1U);
|
|
cdata->HistogramTextureResolution.z = (float)RDCMAX(details.depth >> mip, 1U);
|
|
if(texDetails.curType != eGL_TEXTURE_3D)
|
|
cdata->HistogramSlice = (float)sliceFace + 0.001f;
|
|
else
|
|
cdata->HistogramSlice = (float)(sliceFace >> mip);
|
|
cdata->HistogramMip = mip;
|
|
cdata->HistogramNumSamples = texDetails.samples;
|
|
cdata->HistogramSample = (int)RDCCLAMP(sample, 0U, details.msSamp - 1);
|
|
if(sample == ~0U)
|
|
cdata->HistogramSample = -int(details.msSamp);
|
|
cdata->HistogramMin = minval;
|
|
|
|
// The calculation in the shader normalises each value between min and max, then multiplies by the
|
|
// number of buckets.
|
|
// But any value equal to HistogramMax must go into NUM_BUCKETS-1, so add a small delta.
|
|
cdata->HistogramMax = maxval + maxval * 1e-6f;
|
|
|
|
cdata->HistogramChannels = 0;
|
|
if(channels[0])
|
|
cdata->HistogramChannels |= 0x1;
|
|
if(channels[1])
|
|
cdata->HistogramChannels |= 0x2;
|
|
if(channels[2])
|
|
cdata->HistogramChannels |= 0x4;
|
|
if(channels[3])
|
|
cdata->HistogramChannels |= 0x8;
|
|
cdata->HistogramFlags = 0;
|
|
|
|
int progIdx = texSlot;
|
|
|
|
if(details.format.compType == eCompType_UInt)
|
|
{
|
|
progIdx |= TEXDISPLAY_UINT_TEX;
|
|
intIdx = 1;
|
|
}
|
|
if(details.format.compType == eCompType_SInt)
|
|
{
|
|
progIdx |= TEXDISPLAY_SINT_TEX;
|
|
intIdx = 2;
|
|
}
|
|
|
|
int blocksX = (int)ceil(cdata->HistogramTextureResolution.x /
|
|
float(HGRAM_PIXELS_PER_TILE * HGRAM_TILES_PER_BLOCK));
|
|
int blocksY = (int)ceil(cdata->HistogramTextureResolution.y /
|
|
float(HGRAM_PIXELS_PER_TILE * HGRAM_TILES_PER_BLOCK));
|
|
|
|
gl.glUnmapBuffer(eGL_UNIFORM_BUFFER);
|
|
|
|
gl.glActiveTexture((RDCGLenum)(eGL_TEXTURE0 + texSlot));
|
|
gl.glBindTexture(target, texname);
|
|
if(texSlot == RESTYPE_TEXRECT || texSlot == RESTYPE_TEXBUFFER)
|
|
gl.glBindSampler(texSlot, DebugData.pointNoMipSampler);
|
|
else
|
|
gl.glBindSampler(texSlot, DebugData.pointSampler);
|
|
|
|
int maxlevel = -1;
|
|
|
|
int clampmaxlevel = details.mips - 1;
|
|
|
|
gl.glGetTextureParameterivEXT(texname, target, eGL_TEXTURE_MAX_LEVEL, (GLint *)&maxlevel);
|
|
|
|
// need to ensure texture is mipmap complete by clamping TEXTURE_MAX_LEVEL.
|
|
if(clampmaxlevel != maxlevel)
|
|
{
|
|
gl.glTextureParameterivEXT(texname, target, eGL_TEXTURE_MAX_LEVEL, (GLint *)&clampmaxlevel);
|
|
}
|
|
else
|
|
{
|
|
maxlevel = -1;
|
|
}
|
|
|
|
gl.glBindBufferBase(eGL_SHADER_STORAGE_BUFFER, 0, DebugData.histogramBuf);
|
|
|
|
GLuint zero = 0;
|
|
gl.glClearBufferData(eGL_SHADER_STORAGE_BUFFER, eGL_R32UI, eGL_RED, eGL_UNSIGNED_INT, &zero);
|
|
|
|
gl.glUseProgram(DebugData.histogramProgram[progIdx]);
|
|
gl.glDispatchCompute(blocksX, blocksY, 1);
|
|
|
|
gl.glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
|
|
|
histogram.clear();
|
|
histogram.resize(HGRAM_NUM_BUCKETS * 4);
|
|
|
|
gl.glBindBuffer(eGL_COPY_READ_BUFFER, DebugData.histogramBuf);
|
|
gl.glGetBufferSubData(eGL_COPY_READ_BUFFER, 0, sizeof(uint32_t) * 4 * HGRAM_NUM_BUCKETS,
|
|
&histogram[0]);
|
|
|
|
// compress down from uvec4, then resize down
|
|
for(size_t i = 1; i < HGRAM_NUM_BUCKETS; i++)
|
|
histogram[i] = histogram[i * 4];
|
|
|
|
histogram.resize(HGRAM_NUM_BUCKETS);
|
|
|
|
if(maxlevel >= 0)
|
|
gl.glTextureParameterivEXT(texname, target, eGL_TEXTURE_MAX_LEVEL, (GLint *)&maxlevel);
|
|
|
|
return true;
|
|
}
|
|
|
|
uint32_t GLReplay::PickVertex(uint32_t eventID, const MeshDisplay &cfg, uint32_t x, uint32_t y)
|
|
{
|
|
WrappedOpenGL &gl = *m_pDriver;
|
|
|
|
if(!HasExt[ARB_compute_shader])
|
|
return ~0U;
|
|
|
|
MakeCurrentReplayContext(m_DebugCtx);
|
|
|
|
gl.glUseProgram(DebugData.meshPickProgram);
|
|
|
|
Matrix4f projMat =
|
|
Matrix4f::Perspective(90.0f, 0.1f, 100000.0f, DebugData.outWidth / DebugData.outHeight);
|
|
|
|
Matrix4f camMat = cfg.cam ? cfg.cam->GetMatrix() : Matrix4f::Identity();
|
|
Matrix4f pickMVP = projMat.Mul(camMat);
|
|
|
|
ResourceFormat resFmt;
|
|
resFmt.compByteWidth = cfg.position.compByteWidth;
|
|
resFmt.compCount = cfg.position.compCount;
|
|
resFmt.compType = cfg.position.compType;
|
|
resFmt.special = false;
|
|
if(cfg.position.specialFormat != eSpecial_Unknown)
|
|
{
|
|
resFmt.special = true;
|
|
resFmt.specialFormat = cfg.position.specialFormat;
|
|
}
|
|
|
|
Matrix4f pickMVPProj;
|
|
if(cfg.position.unproject)
|
|
{
|
|
// the derivation of the projection matrix might not be right (hell, it could be an
|
|
// orthographic projection). But it'll be close enough likely.
|
|
Matrix4f guessProj =
|
|
cfg.position.farPlane != FLT_MAX
|
|
? Matrix4f::Perspective(cfg.fov, cfg.position.nearPlane, cfg.position.farPlane, cfg.aspect)
|
|
: Matrix4f::ReversePerspective(cfg.fov, cfg.position.nearPlane, cfg.aspect);
|
|
|
|
if(cfg.ortho)
|
|
guessProj = Matrix4f::Orthographic(cfg.position.nearPlane, cfg.position.farPlane);
|
|
|
|
pickMVPProj = projMat.Mul(camMat.Mul(guessProj.Inverse()));
|
|
;
|
|
}
|
|
|
|
vec3 rayPos;
|
|
vec3 rayDir;
|
|
// convert mouse pos to world space ray
|
|
{
|
|
Matrix4f inversePickMVP = pickMVP.Inverse();
|
|
|
|
float pickX = ((float)x) / ((float)DebugData.outWidth);
|
|
float pickXCanonical = RDCLERP(-1.0f, 1.0f, pickX);
|
|
|
|
float pickY = ((float)y) / ((float)DebugData.outHeight);
|
|
// flip the Y axis
|
|
float pickYCanonical = RDCLERP(1.0f, -1.0f, pickY);
|
|
|
|
vec3 cameraToWorldNearPosition =
|
|
inversePickMVP.Transform(Vec3f(pickXCanonical, pickYCanonical, -1), 1);
|
|
|
|
vec3 cameraToWorldFarPosition =
|
|
inversePickMVP.Transform(Vec3f(pickXCanonical, pickYCanonical, 1), 1);
|
|
|
|
vec3 testDir = (cameraToWorldFarPosition - cameraToWorldNearPosition);
|
|
testDir.Normalise();
|
|
|
|
/* Calculate the ray direction first in the regular way (above), so we can use the
|
|
the output for testing if the ray we are picking is negative or not. This is similar
|
|
to checking against the forward direction of the camera, but more robust
|
|
*/
|
|
if(cfg.position.unproject)
|
|
{
|
|
Matrix4f inversePickMVPGuess = pickMVPProj.Inverse();
|
|
|
|
vec3 nearPosProj = inversePickMVPGuess.Transform(Vec3f(pickXCanonical, pickYCanonical, -1), 1);
|
|
|
|
vec3 farPosProj = inversePickMVPGuess.Transform(Vec3f(pickXCanonical, pickYCanonical, 1), 1);
|
|
|
|
rayDir = (farPosProj - nearPosProj);
|
|
rayDir.Normalise();
|
|
|
|
if(testDir.z < 0)
|
|
{
|
|
rayDir = -rayDir;
|
|
}
|
|
rayPos = nearPosProj;
|
|
}
|
|
else
|
|
{
|
|
rayDir = testDir;
|
|
rayPos = cameraToWorldNearPosition;
|
|
}
|
|
}
|
|
|
|
gl.glBindBufferBase(eGL_UNIFORM_BUFFER, 0, DebugData.UBOs[0]);
|
|
MeshPickUBOData *cdata =
|
|
(MeshPickUBOData *)gl.glMapBufferRange(eGL_UNIFORM_BUFFER, 0, sizeof(MeshPickUBOData),
|
|
GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
|
|
|
|
cdata->rayPos = rayPos;
|
|
cdata->rayDir = rayDir;
|
|
cdata->use_indices = cfg.position.idxByteWidth ? 1U : 0U;
|
|
cdata->numVerts = cfg.position.numVerts;
|
|
bool isTriangleMesh = true;
|
|
switch(cfg.position.topo)
|
|
{
|
|
case eTopology_TriangleList:
|
|
{
|
|
cdata->meshMode = MESH_TRIANGLE_LIST;
|
|
break;
|
|
};
|
|
case eTopology_TriangleStrip:
|
|
{
|
|
cdata->meshMode = MESH_TRIANGLE_STRIP;
|
|
break;
|
|
};
|
|
case eTopology_TriangleFan:
|
|
{
|
|
cdata->meshMode = MESH_TRIANGLE_FAN;
|
|
break;
|
|
};
|
|
case eTopology_TriangleList_Adj:
|
|
{
|
|
cdata->meshMode = MESH_TRIANGLE_LIST_ADJ;
|
|
break;
|
|
};
|
|
case eTopology_TriangleStrip_Adj:
|
|
{
|
|
cdata->meshMode = MESH_TRIANGLE_STRIP_ADJ;
|
|
break;
|
|
};
|
|
default: // points, lines, patchlists, unknown
|
|
{
|
|
cdata->meshMode = MESH_OTHER;
|
|
isTriangleMesh = false;
|
|
};
|
|
}
|
|
|
|
// line/point data
|
|
cdata->unproject = cfg.position.unproject;
|
|
cdata->mvp = cfg.position.unproject ? pickMVPProj : pickMVP;
|
|
cdata->coords = Vec2f((float)x, (float)y);
|
|
cdata->viewport = Vec2f(DebugData.outWidth, DebugData.outHeight);
|
|
|
|
gl.glUnmapBuffer(eGL_UNIFORM_BUFFER);
|
|
|
|
GLuint ib = 0;
|
|
|
|
if(cfg.position.idxByteWidth && cfg.position.idxbuf != ResourceId())
|
|
ib = m_pDriver->GetResourceManager()->GetCurrentResource(cfg.position.idxbuf).name;
|
|
|
|
// We copy into our own buffers to promote to the target type (uint32) that the
|
|
// shader expects. Most IBs will be 16-bit indices, most VBs will not be float4.
|
|
|
|
if(ib)
|
|
{
|
|
// resize up on demand
|
|
if(DebugData.pickIBBuf == 0 || DebugData.pickIBSize < cfg.position.numVerts * sizeof(uint32_t))
|
|
{
|
|
gl.glDeleteBuffers(1, &DebugData.pickIBBuf);
|
|
|
|
gl.glGenBuffers(1, &DebugData.pickIBBuf);
|
|
gl.glBindBuffer(eGL_SHADER_STORAGE_BUFFER, DebugData.pickIBBuf);
|
|
gl.glNamedBufferDataEXT(DebugData.pickIBBuf, cfg.position.numVerts * sizeof(uint32_t), NULL,
|
|
eGL_STREAM_DRAW);
|
|
|
|
DebugData.pickIBSize = cfg.position.numVerts * sizeof(uint32_t);
|
|
}
|
|
|
|
byte *idxs = new byte[cfg.position.numVerts * cfg.position.idxByteWidth];
|
|
memset(idxs, 0, cfg.position.numVerts * cfg.position.idxByteWidth);
|
|
uint32_t *outidxs = NULL;
|
|
|
|
if(cfg.position.idxByteWidth < 4)
|
|
outidxs = new uint32_t[cfg.position.numVerts];
|
|
|
|
gl.glBindBuffer(eGL_COPY_READ_BUFFER, ib);
|
|
|
|
GLint bufsize = 0;
|
|
gl.glGetBufferParameteriv(eGL_COPY_READ_BUFFER, eGL_BUFFER_SIZE, &bufsize);
|
|
|
|
gl.glGetBufferSubData(eGL_COPY_READ_BUFFER, (GLintptr)cfg.position.idxoffs,
|
|
RDCMIN(uint32_t(bufsize) - uint32_t(cfg.position.idxoffs),
|
|
cfg.position.numVerts * cfg.position.idxByteWidth),
|
|
idxs);
|
|
|
|
uint16_t *idxs16 = (uint16_t *)idxs;
|
|
|
|
if(cfg.position.idxByteWidth == 1)
|
|
{
|
|
for(uint32_t i = 0; i < cfg.position.numVerts; i++)
|
|
outidxs[i] = idxs[i];
|
|
|
|
gl.glBindBuffer(eGL_SHADER_STORAGE_BUFFER, DebugData.pickIBBuf);
|
|
gl.glBufferSubData(eGL_SHADER_STORAGE_BUFFER, 0, cfg.position.numVerts * sizeof(uint32_t),
|
|
outidxs);
|
|
}
|
|
else if(cfg.position.idxByteWidth == 2)
|
|
{
|
|
for(uint32_t i = 0; i < cfg.position.numVerts; i++)
|
|
outidxs[i] = idxs16[i];
|
|
|
|
gl.glBindBuffer(eGL_SHADER_STORAGE_BUFFER, DebugData.pickIBBuf);
|
|
gl.glBufferSubData(eGL_SHADER_STORAGE_BUFFER, 0, cfg.position.numVerts * sizeof(uint32_t),
|
|
outidxs);
|
|
}
|
|
else
|
|
{
|
|
gl.glBindBuffer(eGL_SHADER_STORAGE_BUFFER, DebugData.pickIBBuf);
|
|
gl.glBufferSubData(eGL_SHADER_STORAGE_BUFFER, 0, cfg.position.numVerts * sizeof(uint32_t),
|
|
idxs);
|
|
}
|
|
|
|
SAFE_DELETE_ARRAY(outidxs);
|
|
}
|
|
|
|
if(DebugData.pickVBBuf == 0 || DebugData.pickVBSize < cfg.position.numVerts * sizeof(Vec4f))
|
|
{
|
|
gl.glDeleteBuffers(1, &DebugData.pickVBBuf);
|
|
|
|
gl.glGenBuffers(1, &DebugData.pickVBBuf);
|
|
gl.glBindBuffer(eGL_SHADER_STORAGE_BUFFER, DebugData.pickVBBuf);
|
|
gl.glNamedBufferDataEXT(DebugData.pickVBBuf, cfg.position.numVerts * sizeof(Vec4f), NULL,
|
|
eGL_DYNAMIC_DRAW);
|
|
|
|
DebugData.pickVBSize = cfg.position.numVerts * sizeof(Vec4f);
|
|
}
|
|
|
|
// unpack and linearise the data
|
|
{
|
|
FloatVector *vbData = new FloatVector[cfg.position.numVerts];
|
|
|
|
vector<byte> oldData;
|
|
GetBufferData(cfg.position.buf, cfg.position.offset, 0, oldData);
|
|
|
|
byte *data = &oldData[0];
|
|
byte *dataEnd = data + oldData.size();
|
|
|
|
bool valid;
|
|
|
|
uint32_t idxclamp = 0;
|
|
if(cfg.position.baseVertex < 0)
|
|
idxclamp = uint32_t(-cfg.position.baseVertex);
|
|
|
|
for(uint32_t i = 0; i < cfg.position.numVerts; i++)
|
|
{
|
|
uint32_t idx = i;
|
|
|
|
// apply baseVertex but clamp to 0 (don't allow index to become negative)
|
|
if(idx < idxclamp)
|
|
idx = 0;
|
|
else if(cfg.position.baseVertex < 0)
|
|
idx -= idxclamp;
|
|
else if(cfg.position.baseVertex > 0)
|
|
idx += cfg.position.baseVertex;
|
|
|
|
vbData[i] = InterpretVertex(data, idx, cfg, dataEnd, false, valid);
|
|
}
|
|
|
|
gl.glBindBuffer(eGL_SHADER_STORAGE_BUFFER, DebugData.pickVBBuf);
|
|
gl.glBufferSubData(eGL_SHADER_STORAGE_BUFFER, 0, cfg.position.numVerts * sizeof(Vec4f), vbData);
|
|
|
|
delete[] vbData;
|
|
}
|
|
|
|
uint32_t reset[4] = {};
|
|
gl.glBindBufferBase(eGL_SHADER_STORAGE_BUFFER, 0, DebugData.pickResultBuf);
|
|
gl.glBufferSubData(eGL_SHADER_STORAGE_BUFFER, 0, sizeof(uint32_t) * 4, &reset);
|
|
|
|
gl.glBindBufferBase(eGL_SHADER_STORAGE_BUFFER, 1, DebugData.pickVBBuf);
|
|
gl.glBindBufferRange(
|
|
eGL_SHADER_STORAGE_BUFFER, 2, DebugData.pickIBBuf, (GLintptr)cfg.position.idxoffs,
|
|
(GLsizeiptr)(cfg.position.idxoffs + sizeof(uint32_t) * cfg.position.numVerts));
|
|
gl.glBindBufferBase(eGL_SHADER_STORAGE_BUFFER, 3, DebugData.pickResultBuf);
|
|
|
|
gl.glDispatchCompute(GLuint((cfg.position.numVerts) / 128 + 1), 1, 1);
|
|
gl.glMemoryBarrier(GL_ATOMIC_COUNTER_BARRIER_BIT | GL_SHADER_STORAGE_BARRIER_BIT);
|
|
|
|
uint32_t numResults = 0;
|
|
|
|
gl.glBindBuffer(eGL_COPY_READ_BUFFER, DebugData.pickResultBuf);
|
|
gl.glGetBufferSubData(eGL_COPY_READ_BUFFER, 0, sizeof(uint32_t), &numResults);
|
|
|
|
if(numResults > 0)
|
|
{
|
|
if(isTriangleMesh)
|
|
{
|
|
struct PickResult
|
|
{
|
|
uint32_t vertid;
|
|
vec3 intersectionPoint;
|
|
};
|
|
|
|
byte *mapped = (byte *)gl.glMapNamedBufferEXT(DebugData.pickResultBuf, eGL_READ_ONLY);
|
|
|
|
mapped += sizeof(uint32_t) * 4;
|
|
|
|
PickResult *pickResults = (PickResult *)mapped;
|
|
|
|
PickResult *closest = pickResults;
|
|
// distance from raycast hit to nearest worldspace position of the mouse
|
|
float closestPickDistance = (closest->intersectionPoint - rayPos).Length();
|
|
|
|
// min with size of results buffer to protect against overflows
|
|
for(uint32_t i = 1; i < RDCMIN((uint32_t)DebugRenderData::maxMeshPicks, numResults); i++)
|
|
{
|
|
float pickDistance = (pickResults[i].intersectionPoint - rayPos).Length();
|
|
if(pickDistance < closestPickDistance)
|
|
{
|
|
closest = pickResults + i;
|
|
}
|
|
}
|
|
|
|
gl.glUnmapNamedBufferEXT(DebugData.pickResultBuf);
|
|
|
|
return closest->vertid;
|
|
}
|
|
else
|
|
{
|
|
struct PickResult
|
|
{
|
|
uint32_t vertid;
|
|
uint32_t idx;
|
|
float len;
|
|
float depth;
|
|
};
|
|
|
|
byte *mapped = (byte *)gl.glMapNamedBufferEXT(DebugData.pickResultBuf, eGL_READ_ONLY);
|
|
|
|
mapped += sizeof(uint32_t) * 4;
|
|
|
|
PickResult *pickResults = (PickResult *)mapped;
|
|
|
|
PickResult *closest = pickResults;
|
|
|
|
// min with size of results buffer to protect against overflows
|
|
for(uint32_t i = 1; i < RDCMIN((uint32_t)DebugRenderData::maxMeshPicks, numResults); i++)
|
|
{
|
|
// We need to keep the picking order consistent in the face
|
|
// of random buffer appends, when multiple vertices have the
|
|
// identical position (e.g. if UVs or normals are different).
|
|
//
|
|
// We could do something to try and disambiguate, but it's
|
|
// never going to be intuitive, it's just going to flicker
|
|
// confusingly.
|
|
if(pickResults[i].len < closest->len ||
|
|
(pickResults[i].len == closest->len && pickResults[i].depth < closest->depth) ||
|
|
(pickResults[i].len == closest->len && pickResults[i].depth == closest->depth &&
|
|
pickResults[i].vertid < closest->vertid))
|
|
closest = pickResults + i;
|
|
}
|
|
|
|
gl.glUnmapNamedBufferEXT(DebugData.pickResultBuf);
|
|
|
|
return closest->vertid;
|
|
}
|
|
}
|
|
|
|
return ~0U;
|
|
}
|
|
|
|
void GLReplay::PickPixel(ResourceId texture, uint32_t x, uint32_t y, uint32_t sliceFace,
|
|
uint32_t mip, uint32_t sample, FormatComponentType typeHint, float pixel[4])
|
|
{
|
|
WrappedOpenGL &gl = *m_pDriver;
|
|
|
|
MakeCurrentReplayContext(m_DebugCtx);
|
|
|
|
gl.glBindFramebuffer(eGL_FRAMEBUFFER, DebugData.pickPixelFBO);
|
|
gl.glBindFramebuffer(eGL_READ_FRAMEBUFFER, DebugData.pickPixelFBO);
|
|
|
|
pixel[0] = pixel[1] = pixel[2] = pixel[3] = 0.0f;
|
|
gl.glClearBufferfv(eGL_COLOR, 0, pixel);
|
|
|
|
DebugData.outWidth = DebugData.outHeight = 1.0f;
|
|
gl.glViewport(0, 0, 1, 1);
|
|
|
|
TextureDisplay texDisplay;
|
|
|
|
texDisplay.Red = texDisplay.Green = texDisplay.Blue = texDisplay.Alpha = true;
|
|
texDisplay.FlipY = false;
|
|
texDisplay.HDRMul = -1.0f;
|
|
texDisplay.linearDisplayAsGamma = true;
|
|
texDisplay.mip = mip;
|
|
texDisplay.sampleIdx = sample;
|
|
texDisplay.CustomShader = ResourceId();
|
|
texDisplay.sliceFace = sliceFace;
|
|
texDisplay.rangemin = 0.0f;
|
|
texDisplay.rangemax = 1.0f;
|
|
texDisplay.scale = 1.0f;
|
|
texDisplay.texid = texture;
|
|
texDisplay.typeHint = typeHint;
|
|
texDisplay.rawoutput = true;
|
|
texDisplay.offx = -float(x);
|
|
texDisplay.offy = -float(y);
|
|
|
|
RenderTextureInternal(texDisplay, eTexDisplay_MipShift);
|
|
|
|
gl.glReadPixels(0, 0, 1, 1, eGL_RGBA, eGL_FLOAT, (void *)pixel);
|
|
|
|
if(!HasExt[ARB_gpu_shader5])
|
|
{
|
|
auto &texDetails = m_pDriver->m_Textures[texDisplay.texid];
|
|
|
|
if(IsSIntFormat(texDetails.internalFormat))
|
|
{
|
|
int32_t casted[4] = {
|
|
(int32_t)pixel[0], (int32_t)pixel[1], (int32_t)pixel[2], (int32_t)pixel[3],
|
|
};
|
|
|
|
memcpy(pixel, casted, sizeof(casted));
|
|
}
|
|
else if(IsUIntFormat(texDetails.internalFormat))
|
|
{
|
|
uint32_t casted[4] = {
|
|
(uint32_t)pixel[0], (uint32_t)pixel[1], (uint32_t)pixel[2], (uint32_t)pixel[3],
|
|
};
|
|
|
|
memcpy(pixel, casted, sizeof(casted));
|
|
}
|
|
}
|
|
|
|
{
|
|
auto &texDetails = m_pDriver->m_Textures[texture];
|
|
|
|
// need to read stencil separately as GL can't read both depth and stencil
|
|
// at the same time.
|
|
if(texDetails.internalFormat == eGL_DEPTH24_STENCIL8 ||
|
|
texDetails.internalFormat == eGL_DEPTH32F_STENCIL8 ||
|
|
texDetails.internalFormat == eGL_STENCIL_INDEX8)
|
|
{
|
|
texDisplay.Red = texDisplay.Blue = texDisplay.Alpha = false;
|
|
|
|
RenderTextureInternal(texDisplay, eTexDisplay_MipShift);
|
|
|
|
uint32_t stencilpixel[4];
|
|
gl.glReadPixels(0, 0, 1, 1, eGL_RGBA, eGL_FLOAT, (void *)stencilpixel);
|
|
|
|
if(!HasExt[ARB_gpu_shader5])
|
|
{
|
|
// bits weren't aliased, so re-cast back to uint.
|
|
float fpix[4];
|
|
memcpy(fpix, stencilpixel, sizeof(fpix));
|
|
|
|
stencilpixel[0] = (uint32_t)fpix[0];
|
|
stencilpixel[1] = (uint32_t)fpix[1];
|
|
}
|
|
|
|
// not sure whether [0] or [1] will return stencil values, so use
|
|
// max of two because other channel should be 0
|
|
pixel[1] = float(RDCMAX(stencilpixel[0], stencilpixel[1])) / 255.0f;
|
|
|
|
// the first depth read will have read stencil instead.
|
|
// NULL it out so the UI sees only stencil
|
|
if(texDetails.internalFormat == eGL_STENCIL_INDEX8)
|
|
{
|
|
pixel[1] = float(RDCMAX(stencilpixel[0], stencilpixel[1])) / 255.0f;
|
|
pixel[0] = 0.0f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void GLReplay::CopyTex2DMSToArray(GLuint destArray, GLuint srcMS, GLint width, GLint height,
|
|
GLint arraySize, GLint samples, GLenum intFormat)
|
|
{
|
|
WrappedOpenGL &gl = *m_pDriver;
|
|
|
|
if(!HasExt[ARB_compute_shader])
|
|
return;
|
|
|
|
if(!HasExt[ARB_texture_view])
|
|
{
|
|
RDCWARN("Can't copy multisampled texture to array for serialisation without ARB_texture_view.");
|
|
return;
|
|
}
|
|
|
|
GLRenderState rs(&gl.GetHookset(), NULL, READING);
|
|
rs.FetchState(m_pDriver->GetCtx(), m_pDriver);
|
|
|
|
GLenum viewClass;
|
|
gl.glGetInternalformativ(eGL_TEXTURE_2D_ARRAY, intFormat, eGL_VIEW_COMPATIBILITY_CLASS,
|
|
sizeof(GLenum), (GLint *)&viewClass);
|
|
|
|
GLenum fmt = eGL_R32UI;
|
|
if(viewClass == eGL_VIEW_CLASS_8_BITS)
|
|
fmt = eGL_R8UI;
|
|
else if(viewClass == eGL_VIEW_CLASS_16_BITS)
|
|
fmt = eGL_R16UI;
|
|
else if(viewClass == eGL_VIEW_CLASS_24_BITS)
|
|
fmt = eGL_RGB8UI;
|
|
else if(viewClass == eGL_VIEW_CLASS_32_BITS)
|
|
fmt = eGL_RGBA8UI;
|
|
else if(viewClass == eGL_VIEW_CLASS_48_BITS)
|
|
fmt = eGL_RGB16UI;
|
|
else if(viewClass == eGL_VIEW_CLASS_64_BITS)
|
|
fmt = eGL_RG32UI;
|
|
else if(viewClass == eGL_VIEW_CLASS_96_BITS)
|
|
fmt = eGL_RGB32UI;
|
|
else if(viewClass == eGL_VIEW_CLASS_128_BITS)
|
|
fmt = eGL_RGBA32UI;
|
|
|
|
GLuint texs[2];
|
|
gl.glGenTextures(2, texs);
|
|
gl.glTextureView(texs[0], eGL_TEXTURE_2D_ARRAY, destArray, fmt, 0, 1, 0, arraySize * samples);
|
|
gl.glTextureView(texs[1], eGL_TEXTURE_2D_MULTISAMPLE_ARRAY, srcMS, fmt, 0, 1, 0, arraySize);
|
|
|
|
gl.glBindImageTexture(2, texs[0], 0, GL_TRUE, 0, eGL_WRITE_ONLY, fmt);
|
|
gl.glActiveTexture(eGL_TEXTURE0);
|
|
gl.glBindTexture(eGL_TEXTURE_2D_MULTISAMPLE_ARRAY, texs[1]);
|
|
gl.glBindSampler(0, DebugData.pointNoMipSampler);
|
|
gl.glTexParameteri(eGL_TEXTURE_2D_MULTISAMPLE_ARRAY, eGL_TEXTURE_BASE_LEVEL, 0);
|
|
gl.glTexParameteri(eGL_TEXTURE_2D_MULTISAMPLE_ARRAY, eGL_TEXTURE_MAX_LEVEL, 0);
|
|
|
|
gl.glUseProgram(DebugData.MS2Array);
|
|
|
|
GLint loc = gl.glGetUniformLocation(DebugData.MS2Array, "mscopy");
|
|
if(loc >= 0)
|
|
{
|
|
gl.glProgramUniform4ui(DebugData.MS2Array, loc, samples, 0, 0, 0);
|
|
|
|
gl.glDispatchCompute((GLuint)width, (GLuint)height, GLuint(arraySize * samples));
|
|
}
|
|
gl.glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
|
|
|
|
gl.glDeleteTextures(2, texs);
|
|
|
|
rs.ApplyState(m_pDriver->GetCtx(), m_pDriver);
|
|
}
|
|
|
|
bool GLReplay::RenderTexture(TextureDisplay cfg)
|
|
{
|
|
return RenderTextureInternal(cfg, eTexDisplay_BlendAlpha | eTexDisplay_MipShift);
|
|
}
|
|
|
|
bool GLReplay::RenderTextureInternal(TextureDisplay cfg, int flags)
|
|
{
|
|
const bool blendAlpha = (flags & eTexDisplay_BlendAlpha) != 0;
|
|
const bool mipShift = (flags & eTexDisplay_MipShift) != 0;
|
|
|
|
WrappedOpenGL &gl = *m_pDriver;
|
|
|
|
auto &texDetails = m_pDriver->m_Textures[cfg.texid];
|
|
|
|
if(texDetails.internalFormat == eGL_NONE)
|
|
return false;
|
|
|
|
bool renderbuffer = false;
|
|
|
|
int intIdx = 0;
|
|
|
|
int resType;
|
|
switch(texDetails.curType)
|
|
{
|
|
case eGL_RENDERBUFFER:
|
|
resType = RESTYPE_TEX2D;
|
|
if(texDetails.samples > 1)
|
|
resType = RESTYPE_TEX2DMS;
|
|
renderbuffer = true;
|
|
break;
|
|
case eGL_TEXTURE_1D: resType = RESTYPE_TEX1D; break;
|
|
default:
|
|
RDCWARN("Unexpected texture type");
|
|
// fall through
|
|
case eGL_TEXTURE_2D: resType = RESTYPE_TEX2D; break;
|
|
case eGL_TEXTURE_2D_MULTISAMPLE: resType = RESTYPE_TEX2DMS; break;
|
|
case eGL_TEXTURE_RECTANGLE: resType = RESTYPE_TEXRECT; break;
|
|
case eGL_TEXTURE_BUFFER: resType = RESTYPE_TEXBUFFER; break;
|
|
case eGL_TEXTURE_3D: resType = RESTYPE_TEX3D; break;
|
|
case eGL_TEXTURE_CUBE_MAP: resType = RESTYPE_TEXCUBE; break;
|
|
case eGL_TEXTURE_1D_ARRAY: resType = RESTYPE_TEX1DARRAY; break;
|
|
case eGL_TEXTURE_2D_ARRAY: resType = RESTYPE_TEX2DARRAY; break;
|
|
case eGL_TEXTURE_CUBE_MAP_ARRAY: resType = RESTYPE_TEXCUBEARRAY; break;
|
|
}
|
|
|
|
GLuint texname = texDetails.resource.name;
|
|
GLenum target = texDetails.curType;
|
|
|
|
// do blit from renderbuffer to texture, then sample from texture
|
|
if(renderbuffer)
|
|
{
|
|
// need replay context active to do blit (as FBOs aren't shared)
|
|
MakeCurrentReplayContext(&m_ReplayCtx);
|
|
|
|
GLuint curDrawFBO = 0;
|
|
GLuint curReadFBO = 0;
|
|
gl.glGetIntegerv(eGL_DRAW_FRAMEBUFFER_BINDING, (GLint *)&curDrawFBO);
|
|
gl.glGetIntegerv(eGL_READ_FRAMEBUFFER_BINDING, (GLint *)&curReadFBO);
|
|
|
|
gl.glBindFramebuffer(eGL_DRAW_FRAMEBUFFER, texDetails.renderbufferFBOs[1]);
|
|
gl.glBindFramebuffer(eGL_READ_FRAMEBUFFER, texDetails.renderbufferFBOs[0]);
|
|
|
|
gl.glBlitFramebuffer(
|
|
0, 0, texDetails.width, texDetails.height, 0, 0, texDetails.width, texDetails.height,
|
|
GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT, eGL_NEAREST);
|
|
|
|
gl.glBindFramebuffer(eGL_DRAW_FRAMEBUFFER, curDrawFBO);
|
|
gl.glBindFramebuffer(eGL_READ_FRAMEBUFFER, curReadFBO);
|
|
|
|
texname = texDetails.renderbufferReadTex;
|
|
if(resType == RESTYPE_TEX2D)
|
|
target = eGL_TEXTURE_2D;
|
|
else
|
|
target = eGL_TEXTURE_2D_MULTISAMPLE;
|
|
}
|
|
|
|
MakeCurrentReplayContext(m_DebugCtx);
|
|
|
|
RDCGLenum dsTexMode = eGL_NONE;
|
|
if(IsDepthStencilFormat(texDetails.internalFormat))
|
|
{
|
|
// stencil-only, make sure we display it as such
|
|
if(texDetails.internalFormat == eGL_STENCIL_INDEX8)
|
|
{
|
|
cfg.Red = false;
|
|
cfg.Green = true;
|
|
cfg.Blue = false;
|
|
cfg.Alpha = false;
|
|
}
|
|
|
|
// depth-only, make sure we display it as such
|
|
if(GetBaseFormat(texDetails.internalFormat) == eGL_DEPTH_COMPONENT)
|
|
{
|
|
cfg.Red = true;
|
|
cfg.Green = false;
|
|
cfg.Blue = false;
|
|
cfg.Alpha = false;
|
|
}
|
|
|
|
if(!cfg.Red && cfg.Green)
|
|
{
|
|
dsTexMode = eGL_STENCIL_INDEX;
|
|
|
|
// Stencil texture sampling is not normalized in OpenGL
|
|
intIdx = 1;
|
|
float rangeScale;
|
|
switch(texDetails.internalFormat)
|
|
{
|
|
case eGL_STENCIL_INDEX1: rangeScale = 1.0f; break;
|
|
case eGL_STENCIL_INDEX4: rangeScale = 16.0f; break;
|
|
default:
|
|
RDCWARN("Unexpected raw format for stencil visualization");
|
|
// fall through
|
|
case eGL_DEPTH24_STENCIL8:
|
|
case eGL_DEPTH32F_STENCIL8:
|
|
case eGL_STENCIL_INDEX8: rangeScale = 256.0f; break;
|
|
case eGL_STENCIL_INDEX16: rangeScale = 65536.0f; break;
|
|
}
|
|
cfg.rangemin *= rangeScale;
|
|
cfg.rangemax *= rangeScale;
|
|
}
|
|
else
|
|
dsTexMode = eGL_DEPTH_COMPONENT;
|
|
}
|
|
else
|
|
{
|
|
if(IsUIntFormat(texDetails.internalFormat))
|
|
intIdx = 1;
|
|
if(IsSIntFormat(texDetails.internalFormat))
|
|
intIdx = 2;
|
|
}
|
|
|
|
gl.glUseProgram(0);
|
|
gl.glUseProgramStages(DebugData.texDisplayPipe, eGL_VERTEX_SHADER_BIT, DebugData.texDisplayVSProg);
|
|
gl.glUseProgramStages(DebugData.texDisplayPipe, eGL_FRAGMENT_SHADER_BIT,
|
|
DebugData.texDisplayProg[intIdx]);
|
|
|
|
if(cfg.CustomShader != ResourceId() && gl.GetResourceManager()->HasCurrentResource(cfg.CustomShader))
|
|
{
|
|
GLuint customProg = gl.GetResourceManager()->GetCurrentResource(cfg.CustomShader).name;
|
|
gl.glUseProgramStages(DebugData.texDisplayPipe, eGL_FRAGMENT_SHADER_BIT, customProg);
|
|
|
|
GLint loc = -1;
|
|
|
|
loc = gl.glGetUniformLocation(customProg, "RENDERDOC_TexDim");
|
|
if(loc >= 0)
|
|
gl.glProgramUniform4ui(customProg, loc, texDetails.width, texDetails.height, texDetails.depth,
|
|
m_CachedTextures[cfg.texid].mips);
|
|
|
|
loc = gl.glGetUniformLocation(customProg, "RENDERDOC_SelectedMip");
|
|
if(loc >= 0)
|
|
gl.glProgramUniform1ui(customProg, loc, cfg.mip);
|
|
|
|
loc = gl.glGetUniformLocation(customProg, "RENDERDOC_SelectedSliceFace");
|
|
if(loc >= 0)
|
|
gl.glProgramUniform1ui(customProg, loc, cfg.sliceFace);
|
|
|
|
loc = gl.glGetUniformLocation(customProg, "RENDERDOC_SelectedSample");
|
|
if(loc >= 0)
|
|
{
|
|
if(cfg.sampleIdx == ~0U)
|
|
gl.glProgramUniform1i(customProg, loc, -texDetails.samples);
|
|
else
|
|
gl.glProgramUniform1i(customProg, loc,
|
|
(int)RDCCLAMP(cfg.sampleIdx, 0U, (uint32_t)texDetails.samples - 1));
|
|
}
|
|
|
|
loc = gl.glGetUniformLocation(customProg, "RENDERDOC_TextureType");
|
|
if(loc >= 0)
|
|
gl.glProgramUniform1ui(customProg, loc, resType);
|
|
}
|
|
gl.glBindProgramPipeline(DebugData.texDisplayPipe);
|
|
|
|
gl.glActiveTexture((RDCGLenum)(eGL_TEXTURE0 + resType));
|
|
gl.glBindTexture(target, texname);
|
|
|
|
GLint origDSTexMode = eGL_DEPTH_COMPONENT;
|
|
if(dsTexMode != eGL_NONE && HasExt[ARB_stencil_texturing])
|
|
{
|
|
gl.glGetTexParameteriv(target, eGL_DEPTH_STENCIL_TEXTURE_MODE, &origDSTexMode);
|
|
gl.glTexParameteri(target, eGL_DEPTH_STENCIL_TEXTURE_MODE, dsTexMode);
|
|
}
|
|
|
|
// defined as arrays mostly for Coverity code analysis to stay calm about passing
|
|
// them to the *TexParameter* functions
|
|
GLint maxlevel[4] = {-1};
|
|
GLint clampmaxlevel[4] = {};
|
|
|
|
if(cfg.texid != DebugData.CustomShaderTexID)
|
|
clampmaxlevel[0] = GLint(m_CachedTextures[cfg.texid].mips - 1);
|
|
|
|
gl.glGetTextureParameterivEXT(texname, target, eGL_TEXTURE_MAX_LEVEL, maxlevel);
|
|
|
|
// need to ensure texture is mipmap complete by clamping TEXTURE_MAX_LEVEL.
|
|
if(clampmaxlevel[0] != maxlevel[0] && cfg.texid != DebugData.CustomShaderTexID)
|
|
{
|
|
gl.glTextureParameterivEXT(texname, target, eGL_TEXTURE_MAX_LEVEL, clampmaxlevel);
|
|
}
|
|
else
|
|
{
|
|
maxlevel[0] = -1;
|
|
}
|
|
|
|
if(cfg.mip == 0 && cfg.scale < 1.0f && dsTexMode == eGL_NONE && resType != RESTYPE_TEXBUFFER &&
|
|
resType != RESTYPE_TEXRECT)
|
|
{
|
|
gl.glBindSampler(resType, DebugData.linearSampler);
|
|
}
|
|
else
|
|
{
|
|
if(resType == RESTYPE_TEXRECT || resType == RESTYPE_TEX2DMS || resType == RESTYPE_TEXBUFFER)
|
|
gl.glBindSampler(resType, DebugData.pointNoMipSampler);
|
|
else
|
|
gl.glBindSampler(resType, DebugData.pointSampler);
|
|
}
|
|
|
|
GLint tex_x = texDetails.width, tex_y = texDetails.height, tex_z = texDetails.depth;
|
|
|
|
gl.glBindBufferBase(eGL_UNIFORM_BUFFER, 0, DebugData.UBOs[0]);
|
|
|
|
TexDisplayUBOData *ubo =
|
|
(TexDisplayUBOData *)gl.glMapBufferRange(eGL_UNIFORM_BUFFER, 0, sizeof(TexDisplayUBOData),
|
|
GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
|
|
|
|
float x = cfg.offx;
|
|
float y = cfg.offy;
|
|
|
|
ubo->Position.x = x;
|
|
ubo->Position.y = y;
|
|
ubo->Scale = cfg.scale;
|
|
|
|
if(cfg.scale <= 0.0f)
|
|
{
|
|
float xscale = DebugData.outWidth / float(tex_x);
|
|
float yscale = DebugData.outHeight / float(tex_y);
|
|
|
|
ubo->Scale = RDCMIN(xscale, yscale);
|
|
|
|
if(yscale > xscale)
|
|
{
|
|
ubo->Position.x = 0;
|
|
ubo->Position.y = (DebugData.outHeight - (tex_y * ubo->Scale)) * 0.5f;
|
|
}
|
|
else
|
|
{
|
|
ubo->Position.y = 0;
|
|
ubo->Position.x = (DebugData.outWidth - (tex_x * ubo->Scale)) * 0.5f;
|
|
}
|
|
}
|
|
|
|
ubo->HDRMul = cfg.HDRMul;
|
|
|
|
ubo->FlipY = cfg.FlipY ? 1 : 0;
|
|
|
|
if(cfg.rangemax <= cfg.rangemin)
|
|
cfg.rangemax += 0.00001f;
|
|
|
|
if(dsTexMode == eGL_NONE)
|
|
{
|
|
ubo->Channels.x = cfg.Red ? 1.0f : 0.0f;
|
|
ubo->Channels.y = cfg.Green ? 1.0f : 0.0f;
|
|
ubo->Channels.z = cfg.Blue ? 1.0f : 0.0f;
|
|
ubo->Channels.w = cfg.Alpha ? 1.0f : 0.0f;
|
|
}
|
|
else
|
|
{
|
|
// Both depth and stencil texture mode use the red channel
|
|
ubo->Channels.x = 1.0f;
|
|
ubo->Channels.y = 0.0f;
|
|
ubo->Channels.z = 0.0f;
|
|
ubo->Channels.w = 0.0f;
|
|
}
|
|
|
|
ubo->RangeMinimum = cfg.rangemin;
|
|
ubo->InverseRangeSize = 1.0f / (cfg.rangemax - cfg.rangemin);
|
|
|
|
ubo->MipLevel = (int)cfg.mip;
|
|
if(texDetails.curType != eGL_TEXTURE_3D)
|
|
ubo->Slice = (float)cfg.sliceFace + 0.001f;
|
|
else
|
|
ubo->Slice = (float)(cfg.sliceFace >> cfg.mip);
|
|
|
|
ubo->OutputDisplayFormat = resType;
|
|
|
|
if(cfg.overlay == eTexOverlay_NaN)
|
|
ubo->OutputDisplayFormat |= TEXDISPLAY_NANS;
|
|
|
|
if(cfg.overlay == eTexOverlay_Clipping)
|
|
ubo->OutputDisplayFormat |= TEXDISPLAY_CLIPPING;
|
|
|
|
if(!IsSRGBFormat(texDetails.internalFormat) && cfg.linearDisplayAsGamma)
|
|
ubo->OutputDisplayFormat |= TEXDISPLAY_GAMMA_CURVE;
|
|
|
|
ubo->RawOutput = cfg.rawoutput ? 1 : 0;
|
|
|
|
ubo->TextureResolutionPS.x = float(RDCMAX(1, tex_x >> cfg.mip));
|
|
ubo->TextureResolutionPS.y = float(RDCMAX(1, tex_y >> cfg.mip));
|
|
ubo->TextureResolutionPS.z = float(RDCMAX(1, tex_z >> cfg.mip));
|
|
|
|
if(mipShift)
|
|
ubo->MipShift = float(1 << cfg.mip);
|
|
else
|
|
ubo->MipShift = 1.0f;
|
|
|
|
ubo->OutputRes.x = DebugData.outWidth;
|
|
ubo->OutputRes.y = DebugData.outHeight;
|
|
|
|
ubo->SampleIdx = (int)RDCCLAMP(cfg.sampleIdx, 0U, (uint32_t)texDetails.samples - 1);
|
|
|
|
// hacky resolve
|
|
if(cfg.sampleIdx == ~0U)
|
|
ubo->SampleIdx = -texDetails.samples;
|
|
|
|
gl.glUnmapBuffer(eGL_UNIFORM_BUFFER);
|
|
|
|
if(cfg.rawoutput || !blendAlpha)
|
|
{
|
|
gl.glDisable(eGL_BLEND);
|
|
}
|
|
else
|
|
{
|
|
gl.glEnable(eGL_BLEND);
|
|
gl.glBlendFunc(eGL_SRC_ALPHA, eGL_ONE_MINUS_SRC_ALPHA);
|
|
}
|
|
|
|
gl.glDisable(eGL_DEPTH_TEST);
|
|
|
|
gl.glEnable(eGL_FRAMEBUFFER_SRGB);
|
|
|
|
gl.glBindVertexArray(DebugData.emptyVAO);
|
|
gl.glDrawArrays(eGL_TRIANGLE_STRIP, 0, 4);
|
|
|
|
if(maxlevel[0] >= 0)
|
|
gl.glTextureParameterivEXT(texname, target, eGL_TEXTURE_MAX_LEVEL, maxlevel);
|
|
|
|
gl.glBindSampler(0, 0);
|
|
|
|
if(dsTexMode != eGL_NONE && HasExt[ARB_stencil_texturing])
|
|
gl.glTexParameteri(target, eGL_DEPTH_STENCIL_TEXTURE_MODE, origDSTexMode);
|
|
|
|
return true;
|
|
}
|
|
|
|
void GLReplay::RenderCheckerboard(Vec3f light, Vec3f dark)
|
|
{
|
|
MakeCurrentReplayContext(m_DebugCtx);
|
|
|
|
WrappedOpenGL &gl = *m_pDriver;
|
|
|
|
gl.glUseProgram(DebugData.checkerProg);
|
|
|
|
gl.glDisable(eGL_DEPTH_TEST);
|
|
|
|
gl.glEnable(eGL_FRAMEBUFFER_SRGB);
|
|
|
|
gl.glBindBufferBase(eGL_UNIFORM_BUFFER, 0, DebugData.UBOs[0]);
|
|
|
|
Vec4f *ubo = (Vec4f *)gl.glMapBufferRange(eGL_UNIFORM_BUFFER, 0, sizeof(Vec4f) * 2,
|
|
GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
|
|
|
|
ubo[0] = Vec4f(light.x, light.y, light.z, 1.0f);
|
|
ubo[1] = Vec4f(dark.x, dark.y, dark.z, 1.0f);
|
|
|
|
gl.glUnmapBuffer(eGL_UNIFORM_BUFFER);
|
|
|
|
gl.glBindVertexArray(DebugData.emptyVAO);
|
|
gl.glDrawArrays(eGL_TRIANGLE_STRIP, 0, 4);
|
|
}
|
|
|
|
void GLReplay::RenderHighlightBox(float w, float h, float scale)
|
|
{
|
|
MakeCurrentReplayContext(m_DebugCtx);
|
|
|
|
WrappedOpenGL &gl = *m_pDriver;
|
|
|
|
GLint sz = GLint(scale);
|
|
|
|
struct rect
|
|
{
|
|
GLint x, y;
|
|
GLint w, h;
|
|
};
|
|
|
|
rect tl = {GLint(w / 2.0f + 0.5f), GLint(h / 2.0f + 0.5f), 1, 1};
|
|
|
|
rect scissors[4] = {
|
|
{tl.x, tl.y - (GLint)sz - 1, 1, sz + 1},
|
|
{tl.x + (GLint)sz, tl.y - (GLint)sz - 1, 1, sz + 2},
|
|
{tl.x, tl.y, sz, 1},
|
|
{tl.x, tl.y - (GLint)sz - 1, sz, 1},
|
|
};
|
|
|
|
// inner
|
|
gl.glEnable(eGL_SCISSOR_TEST);
|
|
gl.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
|
|
for(size_t i = 0; i < ARRAY_COUNT(scissors); i++)
|
|
{
|
|
gl.glScissor(scissors[i].x, scissors[i].y, scissors[i].w, scissors[i].h);
|
|
gl.glClear(eGL_COLOR_BUFFER_BIT);
|
|
}
|
|
|
|
scissors[0].x--;
|
|
scissors[1].x++;
|
|
scissors[2].x--;
|
|
scissors[3].x--;
|
|
|
|
scissors[0].y--;
|
|
scissors[1].y--;
|
|
scissors[2].y++;
|
|
scissors[3].y--;
|
|
|
|
scissors[0].h += 2;
|
|
scissors[1].h += 2;
|
|
scissors[2].w += 2;
|
|
scissors[3].w += 2;
|
|
|
|
// outer
|
|
gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
|
for(size_t i = 0; i < ARRAY_COUNT(scissors); i++)
|
|
{
|
|
gl.glScissor(scissors[i].x, scissors[i].y, scissors[i].w, scissors[i].h);
|
|
gl.glClear(eGL_COLOR_BUFFER_BIT);
|
|
}
|
|
|
|
gl.glDisable(eGL_SCISSOR_TEST);
|
|
}
|
|
|
|
void GLReplay::SetupOverlayPipeline(GLuint Program, GLuint Pipeline, GLuint fragProgram)
|
|
{
|
|
WrappedOpenGL &gl = *m_pDriver;
|
|
|
|
void *ctx = m_ReplayCtx.ctx;
|
|
|
|
if(Program == 0)
|
|
{
|
|
if(Pipeline == 0)
|
|
{
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
ResourceId id = m_pDriver->GetResourceManager()->GetID(ProgramPipeRes(ctx, Pipeline));
|
|
auto &pipeDetails = m_pDriver->m_Pipelines[id];
|
|
|
|
for(size_t i = 0; i < 4; i++)
|
|
{
|
|
if(pipeDetails.stageShaders[i] != ResourceId())
|
|
{
|
|
GLuint progsrc =
|
|
m_pDriver->GetResourceManager()->GetCurrentResource(pipeDetails.stagePrograms[i]).name;
|
|
GLuint progdst = m_pDriver->m_Shaders[pipeDetails.stageShaders[i]].prog;
|
|
|
|
gl.glUseProgramStages(DebugData.overlayPipe, ShaderBit(i), progdst);
|
|
|
|
CopyProgramUniforms(gl.GetHookset(), progsrc, progdst);
|
|
|
|
if(i == 0)
|
|
{
|
|
CopyProgramAttribBindings(gl.GetHookset(), progsrc, progdst,
|
|
GetShader(pipeDetails.stageShaders[i], ""));
|
|
|
|
gl.glLinkProgram(progdst);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
auto &progDetails =
|
|
m_pDriver->m_Programs[m_pDriver->GetResourceManager()->GetID(ProgramRes(ctx, Program))];
|
|
|
|
for(size_t i = 0; i < 4; i++)
|
|
{
|
|
if(progDetails.stageShaders[i] != ResourceId())
|
|
{
|
|
GLuint progdst = m_pDriver->m_Shaders[progDetails.stageShaders[i]].prog;
|
|
|
|
gl.glUseProgramStages(DebugData.overlayPipe, ShaderBit(i), progdst);
|
|
|
|
// we have to link the program first, as this trashes all uniform values
|
|
if(i == 0)
|
|
{
|
|
CopyProgramAttribBindings(gl.GetHookset(), Program, progdst,
|
|
GetShader(progDetails.stageShaders[i], ""));
|
|
|
|
gl.glLinkProgram(progdst);
|
|
}
|
|
|
|
CopyProgramUniforms(gl.GetHookset(), Program, progdst);
|
|
}
|
|
}
|
|
}
|
|
|
|
// use the generic FS program by default, can be overridden for specific overlays if needed
|
|
gl.glUseProgramStages(DebugData.overlayPipe, eGL_FRAGMENT_SHADER_BIT, fragProgram);
|
|
}
|
|
|
|
ResourceId GLReplay::RenderOverlay(ResourceId texid, FormatComponentType typeHint,
|
|
TextureDisplayOverlay overlay, uint32_t eventID,
|
|
const vector<uint32_t> &passEvents)
|
|
{
|
|
WrappedOpenGL &gl = *m_pDriver;
|
|
|
|
MakeCurrentReplayContext(&m_ReplayCtx);
|
|
|
|
GLMarkerRegion renderoverlay(StringFormat::Fmt("RenderOverlay %d", overlay));
|
|
|
|
void *ctx = m_ReplayCtx.ctx;
|
|
|
|
GLRenderState rs(&gl.GetHookset(), NULL, READING);
|
|
rs.FetchState(ctx, &gl);
|
|
|
|
// use our overlay pipeline that we'll fill up with all the right
|
|
// shaders, then replace the fragment shader with our own.
|
|
gl.glUseProgram(0);
|
|
gl.glBindProgramPipeline(DebugData.overlayPipe);
|
|
|
|
// we bind the separable program created for each shader, and copy
|
|
// uniforms and attrib bindings from the 'real' programs, wherever
|
|
// they are.
|
|
SetupOverlayPipeline(rs.Program, rs.Pipeline, DebugData.fixedcolFSProg);
|
|
|
|
auto &texDetails = m_pDriver->m_Textures[texid];
|
|
|
|
GLenum texBindingEnum = eGL_TEXTURE_2D;
|
|
GLenum texQueryEnum = eGL_TEXTURE_BINDING_2D;
|
|
|
|
if(texDetails.samples > 1)
|
|
{
|
|
texBindingEnum = eGL_TEXTURE_2D_MULTISAMPLE;
|
|
texQueryEnum = eGL_TEXTURE_BINDING_2D_MULTISAMPLE;
|
|
}
|
|
|
|
// resize (or create) the overlay texture and FBO if necessary
|
|
if(DebugData.overlayTexWidth != texDetails.width ||
|
|
DebugData.overlayTexHeight != texDetails.height ||
|
|
DebugData.overlayTexSamples != texDetails.samples)
|
|
{
|
|
if(DebugData.overlayFBO)
|
|
{
|
|
gl.glDeleteFramebuffers(1, &DebugData.overlayFBO);
|
|
gl.glDeleteTextures(1, &DebugData.overlayTex);
|
|
}
|
|
|
|
gl.glGenFramebuffers(1, &DebugData.overlayFBO);
|
|
gl.glBindFramebuffer(eGL_FRAMEBUFFER, DebugData.overlayFBO);
|
|
|
|
GLuint curTex = 0;
|
|
gl.glGetIntegerv(texQueryEnum, (GLint *)&curTex);
|
|
|
|
gl.glGenTextures(1, &DebugData.overlayTex);
|
|
gl.glBindTexture(texBindingEnum, DebugData.overlayTex);
|
|
|
|
DebugData.overlayTexWidth = texDetails.width;
|
|
DebugData.overlayTexHeight = texDetails.height;
|
|
DebugData.overlayTexSamples = texDetails.samples;
|
|
|
|
if(DebugData.overlayTexSamples > 1)
|
|
{
|
|
gl.glTextureStorage2DMultisampleEXT(DebugData.overlayTex, texBindingEnum, texDetails.samples,
|
|
eGL_RGBA16, texDetails.width, texDetails.height, true);
|
|
}
|
|
else
|
|
{
|
|
gl.glTextureImage2DEXT(DebugData.overlayTex, texBindingEnum, 0, eGL_RGBA16, texDetails.width,
|
|
texDetails.height, 0, eGL_RGBA, eGL_FLOAT, NULL);
|
|
gl.glTexParameteri(texBindingEnum, eGL_TEXTURE_MAX_LEVEL, 0);
|
|
gl.glTexParameteri(texBindingEnum, eGL_TEXTURE_MIN_FILTER, eGL_NEAREST);
|
|
gl.glTexParameteri(texBindingEnum, eGL_TEXTURE_MAG_FILTER, eGL_NEAREST);
|
|
gl.glTexParameteri(texBindingEnum, eGL_TEXTURE_WRAP_S, eGL_CLAMP_TO_EDGE);
|
|
gl.glTexParameteri(texBindingEnum, eGL_TEXTURE_WRAP_T, eGL_CLAMP_TO_EDGE);
|
|
}
|
|
gl.glFramebufferTexture(eGL_FRAMEBUFFER, eGL_COLOR_ATTACHMENT0, DebugData.overlayTex, 0);
|
|
|
|
gl.glBindTexture(texBindingEnum, curTex);
|
|
}
|
|
|
|
gl.glBindFramebuffer(eGL_FRAMEBUFFER, DebugData.overlayFBO);
|
|
|
|
// disable several tests/allow rendering - some overlays will override
|
|
// these states but commonly we don't want to inherit these states from
|
|
// the program's state.
|
|
gl.glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
|
gl.glDisable(eGL_BLEND);
|
|
gl.glDisable(eGL_SCISSOR_TEST);
|
|
gl.glDepthMask(GL_FALSE);
|
|
gl.glDisable(eGL_CULL_FACE);
|
|
gl.glPolygonMode(eGL_FRONT_AND_BACK, eGL_FILL);
|
|
gl.glDisable(eGL_DEPTH_TEST);
|
|
gl.glDisable(eGL_STENCIL_TEST);
|
|
gl.glStencilMask(0);
|
|
|
|
if(overlay == eTexOverlay_NaN || overlay == eTexOverlay_Clipping)
|
|
{
|
|
// just need the basic texture
|
|
float black[] = {0.0f, 0.0f, 0.0f, 0.0f};
|
|
gl.glClearBufferfv(eGL_COLOR, 0, black);
|
|
}
|
|
else if(overlay == eTexOverlay_Drawcall)
|
|
{
|
|
float black[] = {0.0f, 0.0f, 0.0f, 0.5f};
|
|
gl.glClearBufferfv(eGL_COLOR, 0, black);
|
|
|
|
GLint colLoc = gl.glGetUniformLocation(DebugData.fixedcolFSProg, "RENDERDOC_Fixed_Color");
|
|
float colVal[] = {0.8f, 0.1f, 0.8f, 1.0f};
|
|
gl.glProgramUniform4fv(DebugData.fixedcolFSProg, colLoc, 1, colVal);
|
|
|
|
ReplayLog(eventID, eReplay_OnlyDraw);
|
|
}
|
|
else if(overlay == eTexOverlay_Wireframe)
|
|
{
|
|
float wireCol[] = {200.0f / 255.0f, 255.0f / 255.0f, 0.0f / 255.0f, 0.0f};
|
|
gl.glClearBufferfv(eGL_COLOR, 0, wireCol);
|
|
|
|
GLint colLoc = gl.glGetUniformLocation(DebugData.fixedcolFSProg, "RENDERDOC_Fixed_Color");
|
|
wireCol[3] = 1.0f;
|
|
gl.glProgramUniform4fv(DebugData.fixedcolFSProg, colLoc, 1, wireCol);
|
|
|
|
gl.glPolygonMode(eGL_FRONT_AND_BACK, eGL_LINE);
|
|
|
|
ReplayLog(eventID, eReplay_OnlyDraw);
|
|
}
|
|
else if(overlay == eTexOverlay_ViewportScissor)
|
|
{
|
|
float col[] = {0.0f, 0.0f, 0.0f, 0.0f};
|
|
gl.glClearBufferfv(eGL_COLOR, 0, col);
|
|
|
|
// don't need to use the existing program at all!
|
|
gl.glUseProgram(DebugData.outlineQuadProg);
|
|
gl.glBindProgramPipeline(0);
|
|
|
|
gl.glDisablei(eGL_SCISSOR_TEST, 0);
|
|
|
|
if(HasExt[ARB_viewport_array])
|
|
gl.glViewportIndexedf(0, rs.Viewports[0].x, rs.Viewports[0].y, rs.Viewports[0].width,
|
|
rs.Viewports[0].height);
|
|
else
|
|
gl.glViewport((GLint)rs.Viewports[0].x, (GLint)rs.Viewports[0].y,
|
|
(GLsizei)rs.Viewports[0].width, (GLsizei)rs.Viewports[0].height);
|
|
|
|
gl.glBindBufferBase(eGL_UNIFORM_BUFFER, 0, DebugData.UBOs[0]);
|
|
OutlineUBOData *cdata =
|
|
(OutlineUBOData *)gl.glMapBufferRange(eGL_UNIFORM_BUFFER, 0, sizeof(OutlineUBOData),
|
|
GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
|
|
|
|
cdata->Inner_Color = Vec4f(0.2f, 0.2f, 0.9f, 0.7f);
|
|
cdata->Border_Color = Vec4f(0.1f, 0.1f, 0.1f, 1.0f);
|
|
cdata->ViewRect =
|
|
Vec4f(rs.Viewports[0].x, rs.Viewports[0].y, rs.Viewports[0].width, rs.Viewports[0].height);
|
|
cdata->Scissor = 0;
|
|
|
|
gl.glUnmapBuffer(eGL_UNIFORM_BUFFER);
|
|
|
|
gl.glDrawArrays(eGL_TRIANGLE_STRIP, 0, 4);
|
|
|
|
if(rs.Scissors[0].enabled)
|
|
{
|
|
Vec4f scissor((float)rs.Scissors[0].x, (float)rs.Scissors[0].y, (float)rs.Scissors[0].width,
|
|
(float)rs.Scissors[0].height);
|
|
|
|
if(HasExt[ARB_viewport_array])
|
|
gl.glViewportIndexedf(0, scissor.x, scissor.y, scissor.z, scissor.w);
|
|
else
|
|
gl.glViewport(rs.Scissors[0].x, rs.Scissors[0].y, rs.Scissors[0].width,
|
|
rs.Scissors[0].height);
|
|
|
|
cdata = (OutlineUBOData *)gl.glMapBufferRange(eGL_UNIFORM_BUFFER, 0, sizeof(OutlineUBOData),
|
|
GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
|
|
|
|
cdata->Inner_Color = Vec4f(0.2f, 0.2f, 0.9f, 0.7f);
|
|
cdata->Border_Color = Vec4f(0.1f, 0.1f, 0.1f, 1.0f);
|
|
cdata->ViewRect = scissor;
|
|
cdata->Scissor = 1;
|
|
|
|
gl.glUnmapBuffer(eGL_UNIFORM_BUFFER);
|
|
|
|
gl.glDrawArrays(eGL_TRIANGLE_STRIP, 0, 4);
|
|
}
|
|
}
|
|
else if(overlay == eTexOverlay_Depth || overlay == eTexOverlay_Stencil)
|
|
{
|
|
float black[] = {0.0f, 0.0f, 0.0f, 0.0f};
|
|
gl.glClearBufferfv(eGL_COLOR, 0, black);
|
|
|
|
GLint colLoc = gl.glGetUniformLocation(DebugData.fixedcolFSProg, "RENDERDOC_Fixed_Color");
|
|
float red[] = {1.0f, 0.0f, 0.0f, 1.0f};
|
|
gl.glProgramUniform4fv(DebugData.fixedcolFSProg, colLoc, 1, red);
|
|
|
|
ReplayLog(eventID, eReplay_OnlyDraw);
|
|
|
|
GLuint curDepth = 0, curStencil = 0;
|
|
|
|
gl.glGetNamedFramebufferAttachmentParameterivEXT(
|
|
rs.DrawFBO, eGL_DEPTH_ATTACHMENT, eGL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, (GLint *)&curDepth);
|
|
gl.glGetNamedFramebufferAttachmentParameterivEXT(rs.DrawFBO, eGL_STENCIL_ATTACHMENT,
|
|
eGL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME,
|
|
(GLint *)&curStencil);
|
|
|
|
GLenum copyBindingEnum = texBindingEnum;
|
|
GLenum copyQueryEnum = texQueryEnum;
|
|
|
|
GLuint depthCopy = 0, stencilCopy = 0;
|
|
|
|
GLint mip = 0;
|
|
GLint layer = 0;
|
|
|
|
// create matching depth for existing FBO
|
|
if(curDepth != 0)
|
|
{
|
|
GLint type = 0;
|
|
gl.glGetNamedFramebufferAttachmentParameterivEXT(
|
|
rs.DrawFBO, eGL_DEPTH_ATTACHMENT, eGL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &type);
|
|
|
|
GLenum fmt;
|
|
|
|
if(type != eGL_RENDERBUFFER)
|
|
{
|
|
ResourceId id = m_pDriver->GetResourceManager()->GetID(TextureRes(ctx, curDepth));
|
|
WrappedOpenGL::TextureData &details = m_pDriver->m_Textures[id];
|
|
|
|
fmt = details.internalFormat;
|
|
|
|
gl.glGetNamedFramebufferAttachmentParameterivEXT(
|
|
rs.DrawFBO, eGL_DEPTH_ATTACHMENT, eGL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, &mip);
|
|
|
|
if(details.curType == eGL_TEXTURE_CUBE_MAP)
|
|
{
|
|
GLenum face;
|
|
gl.glGetNamedFramebufferAttachmentParameterivEXT(
|
|
rs.DrawFBO, eGL_DEPTH_ATTACHMENT, eGL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE,
|
|
(GLint *)&face);
|
|
|
|
layer = CubeTargetIndex(face);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ResourceId id = m_pDriver->GetResourceManager()->GetID(RenderbufferRes(ctx, curDepth));
|
|
WrappedOpenGL::TextureData &details = m_pDriver->m_Textures[id];
|
|
|
|
fmt = details.internalFormat;
|
|
}
|
|
|
|
if(copyBindingEnum == eGL_TEXTURE_CUBE_MAP)
|
|
{
|
|
copyBindingEnum = eGL_TEXTURE_2D;
|
|
copyQueryEnum = eGL_TEXTURE_BINDING_2D;
|
|
}
|
|
|
|
GLuint curTex = 0;
|
|
gl.glGetIntegerv(copyQueryEnum, (GLint *)&curTex);
|
|
|
|
gl.glGenTextures(1, &depthCopy);
|
|
gl.glBindTexture(copyBindingEnum, depthCopy);
|
|
if(DebugData.overlayTexSamples > 1)
|
|
{
|
|
gl.glTextureStorage2DMultisampleEXT(depthCopy, copyBindingEnum, DebugData.overlayTexSamples,
|
|
fmt, DebugData.overlayTexWidth,
|
|
DebugData.overlayTexHeight, true);
|
|
}
|
|
else
|
|
{
|
|
gl.glTextureImage2DEXT(depthCopy, copyBindingEnum, 0, fmt, DebugData.overlayTexWidth,
|
|
DebugData.overlayTexHeight, 0, GetBaseFormat(fmt), GetDataType(fmt),
|
|
NULL);
|
|
gl.glTexParameteri(copyBindingEnum, eGL_TEXTURE_MAX_LEVEL, 0);
|
|
gl.glTexParameteri(copyBindingEnum, eGL_TEXTURE_MIN_FILTER, eGL_NEAREST);
|
|
gl.glTexParameteri(copyBindingEnum, eGL_TEXTURE_MAG_FILTER, eGL_NEAREST);
|
|
gl.glTexParameteri(copyBindingEnum, eGL_TEXTURE_WRAP_S, eGL_CLAMP_TO_EDGE);
|
|
gl.glTexParameteri(copyBindingEnum, eGL_TEXTURE_WRAP_T, eGL_CLAMP_TO_EDGE);
|
|
}
|
|
|
|
gl.glBindTexture(copyBindingEnum, curTex);
|
|
}
|
|
|
|
// create matching separate stencil if relevant
|
|
if(curStencil != curDepth && curStencil != 0)
|
|
{
|
|
GLint type = 0;
|
|
gl.glGetNamedFramebufferAttachmentParameterivEXT(
|
|
rs.DrawFBO, eGL_STENCIL_ATTACHMENT, eGL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &type);
|
|
|
|
GLenum fmt;
|
|
|
|
if(type != eGL_RENDERBUFFER)
|
|
{
|
|
ResourceId id = m_pDriver->GetResourceManager()->GetID(TextureRes(ctx, curDepth));
|
|
WrappedOpenGL::TextureData &details = m_pDriver->m_Textures[id];
|
|
|
|
fmt = details.internalFormat;
|
|
|
|
if(details.curType == eGL_TEXTURE_CUBE_MAP)
|
|
{
|
|
GLenum face;
|
|
gl.glGetNamedFramebufferAttachmentParameterivEXT(
|
|
rs.DrawFBO, eGL_DEPTH_ATTACHMENT, eGL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE,
|
|
(GLint *)&face);
|
|
|
|
layer = CubeTargetIndex(face);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ResourceId id = m_pDriver->GetResourceManager()->GetID(RenderbufferRes(ctx, curDepth));
|
|
WrappedOpenGL::TextureData &details = m_pDriver->m_Textures[id];
|
|
|
|
fmt = details.internalFormat;
|
|
}
|
|
|
|
GLuint curTex = 0;
|
|
gl.glGetIntegerv(copyQueryEnum, (GLint *)&curTex);
|
|
|
|
gl.glGenTextures(1, &stencilCopy);
|
|
gl.glBindTexture(copyBindingEnum, stencilCopy);
|
|
if(DebugData.overlayTexSamples > 1)
|
|
{
|
|
gl.glTextureStorage2DMultisampleEXT(
|
|
stencilCopy, copyBindingEnum, DebugData.overlayTexSamples, fmt,
|
|
DebugData.overlayTexWidth, DebugData.overlayTexHeight, true);
|
|
}
|
|
else
|
|
{
|
|
gl.glTextureImage2DEXT(stencilCopy, copyBindingEnum, 0, fmt, DebugData.overlayTexWidth,
|
|
DebugData.overlayTexHeight, 0, GetBaseFormat(fmt), GetDataType(fmt),
|
|
NULL);
|
|
gl.glTexParameteri(copyBindingEnum, eGL_TEXTURE_MAX_LEVEL, 0);
|
|
gl.glTexParameteri(copyBindingEnum, eGL_TEXTURE_MIN_FILTER, eGL_NEAREST);
|
|
gl.glTexParameteri(copyBindingEnum, eGL_TEXTURE_MAG_FILTER, eGL_NEAREST);
|
|
gl.glTexParameteri(copyBindingEnum, eGL_TEXTURE_WRAP_S, eGL_CLAMP_TO_EDGE);
|
|
gl.glTexParameteri(copyBindingEnum, eGL_TEXTURE_WRAP_T, eGL_CLAMP_TO_EDGE);
|
|
}
|
|
|
|
gl.glBindTexture(copyBindingEnum, curTex);
|
|
}
|
|
|
|
// bind depth/stencil to overlay FBO (currently bound to DRAW_FRAMEBUFFER)
|
|
if(curDepth != 0 && curDepth == curStencil)
|
|
{
|
|
if(layer == 0)
|
|
gl.glFramebufferTexture(eGL_DRAW_FRAMEBUFFER, eGL_DEPTH_STENCIL_ATTACHMENT, depthCopy, mip);
|
|
else
|
|
gl.glFramebufferTextureLayer(eGL_DRAW_FRAMEBUFFER, eGL_DEPTH_STENCIL_ATTACHMENT, depthCopy,
|
|
mip, layer);
|
|
}
|
|
else if(curDepth != 0)
|
|
{
|
|
if(layer == 0)
|
|
gl.glFramebufferTexture(eGL_DRAW_FRAMEBUFFER, eGL_DEPTH_ATTACHMENT, depthCopy, mip);
|
|
else
|
|
gl.glFramebufferTextureLayer(eGL_DRAW_FRAMEBUFFER, eGL_DEPTH_ATTACHMENT, depthCopy, mip,
|
|
layer);
|
|
}
|
|
else if(curStencil != 0)
|
|
{
|
|
if(layer == 0)
|
|
gl.glFramebufferTexture(eGL_DRAW_FRAMEBUFFER, eGL_STENCIL_ATTACHMENT, stencilCopy, mip);
|
|
else
|
|
gl.glFramebufferTextureLayer(eGL_DRAW_FRAMEBUFFER, eGL_STENCIL_ATTACHMENT, stencilCopy, mip,
|
|
layer);
|
|
}
|
|
|
|
// bind the 'real' fbo to the read framebuffer, so we can blit from it
|
|
gl.glBindFramebuffer(eGL_READ_FRAMEBUFFER, rs.DrawFBO);
|
|
|
|
float green[] = {0.0f, 1.0f, 0.0f, 1.0f};
|
|
gl.glProgramUniform4fv(DebugData.fixedcolFSProg, colLoc, 1, green);
|
|
|
|
if(overlay == eTexOverlay_Depth)
|
|
{
|
|
if(rs.Enabled[GLRenderState::eEnabled_DepthTest])
|
|
gl.glEnable(eGL_DEPTH_TEST);
|
|
else
|
|
gl.glDisable(eGL_DEPTH_TEST);
|
|
|
|
if(rs.DepthWriteMask)
|
|
gl.glDepthMask(GL_TRUE);
|
|
else
|
|
gl.glDepthMask(GL_FALSE);
|
|
}
|
|
else
|
|
{
|
|
if(rs.Enabled[GLRenderState::eEnabled_StencilTest])
|
|
gl.glEnable(eGL_STENCIL_TEST);
|
|
else
|
|
gl.glDisable(eGL_STENCIL_TEST);
|
|
|
|
gl.glStencilMaskSeparate(eGL_FRONT, rs.StencilFront.writemask);
|
|
gl.glStencilMaskSeparate(eGL_BACK, rs.StencilBack.writemask);
|
|
}
|
|
|
|
// get latest depth/stencil from read FBO (existing FBO) into draw FBO (overlay FBO)
|
|
gl.glBlitFramebuffer(0, 0, DebugData.overlayTexWidth, DebugData.overlayTexHeight, 0, 0,
|
|
DebugData.overlayTexWidth, DebugData.overlayTexHeight,
|
|
GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT, eGL_NEAREST);
|
|
|
|
ReplayLog(eventID, eReplay_OnlyDraw);
|
|
|
|
// unset depth/stencil textures from overlay FBO and delete temp depth/stencil
|
|
if(curDepth != 0 && curDepth == curStencil)
|
|
gl.glFramebufferTexture(eGL_DRAW_FRAMEBUFFER, eGL_DEPTH_STENCIL_ATTACHMENT, 0, 0);
|
|
else if(curDepth != 0)
|
|
gl.glFramebufferTexture(eGL_DRAW_FRAMEBUFFER, eGL_DEPTH_ATTACHMENT, 0, 0);
|
|
else if(curStencil != 0)
|
|
gl.glFramebufferTexture(eGL_DRAW_FRAMEBUFFER, eGL_STENCIL_ATTACHMENT, 0, 0);
|
|
if(depthCopy != 0)
|
|
gl.glDeleteTextures(1, &depthCopy);
|
|
if(stencilCopy != 0)
|
|
gl.glDeleteTextures(1, &stencilCopy);
|
|
}
|
|
else if(overlay == eTexOverlay_BackfaceCull)
|
|
{
|
|
float col[] = {0.0f, 0.0f, 0.0f, 0.0f};
|
|
gl.glClearBufferfv(eGL_COLOR, 0, col);
|
|
|
|
col[0] = 1.0f;
|
|
col[3] = 1.0f;
|
|
|
|
GLint colLoc = gl.glGetUniformLocation(DebugData.fixedcolFSProg, "RENDERDOC_Fixed_Color");
|
|
gl.glProgramUniform4fv(DebugData.fixedcolFSProg, colLoc, 1, col);
|
|
|
|
ReplayLog(eventID, eReplay_OnlyDraw);
|
|
|
|
// only enable cull face if it was enabled originally (otherwise
|
|
// we just render green over the exact same area, so it shows up "passing")
|
|
if(rs.Enabled[GLRenderState::eEnabled_CullFace])
|
|
gl.glEnable(eGL_CULL_FACE);
|
|
|
|
col[0] = 0.0f;
|
|
col[1] = 1.0f;
|
|
|
|
gl.glProgramUniform4fv(DebugData.fixedcolFSProg, colLoc, 1, col);
|
|
|
|
ReplayLog(eventID, eReplay_OnlyDraw);
|
|
}
|
|
else if(overlay == eTexOverlay_ClearBeforeDraw || overlay == eTexOverlay_ClearBeforePass)
|
|
{
|
|
float col[] = {0.0f, 0.0f, 0.0f, 0.0f};
|
|
gl.glClearBufferfv(eGL_COLOR, 0, col);
|
|
|
|
vector<uint32_t> events = passEvents;
|
|
|
|
if(overlay == eTexOverlay_ClearBeforeDraw)
|
|
events.clear();
|
|
|
|
events.push_back(eventID);
|
|
|
|
if(!events.empty())
|
|
{
|
|
if(overlay == eTexOverlay_ClearBeforePass)
|
|
{
|
|
m_pDriver->ReplayLog(0, events[0], eReplay_WithoutDraw);
|
|
}
|
|
else
|
|
{
|
|
// if we don't replay the real state, restore what we've changed
|
|
rs.ApplyState(ctx, &gl);
|
|
}
|
|
|
|
float black[] = {0.0f, 0.0f, 0.0f, 0.0f};
|
|
for(int i = 0; i < 8; i++)
|
|
gl.glClearBufferfv(eGL_COLOR, i, black);
|
|
|
|
for(size_t i = 0; i < events.size(); i++)
|
|
{
|
|
m_pDriver->ReplayLog(events[i], events[i], eReplay_OnlyDraw);
|
|
|
|
if(overlay == eTexOverlay_ClearBeforePass && i + 1 < events.size())
|
|
m_pDriver->ReplayLog(events[i], events[i + 1], eReplay_WithoutDraw);
|
|
}
|
|
}
|
|
}
|
|
else if(overlay == eTexOverlay_TriangleSizeDraw || overlay == eTexOverlay_TriangleSizePass)
|
|
{
|
|
SCOPED_TIMER("Triangle Size");
|
|
|
|
float black[] = {0.0f, 0.0f, 0.0f, 0.0f};
|
|
gl.glClearBufferfv(eGL_COLOR, 0, black);
|
|
|
|
MeshUBOData uboParams = {};
|
|
uboParams.homogenousInput = 1;
|
|
uboParams.invProj = Matrix4f::Identity();
|
|
uboParams.mvp = Matrix4f::Identity();
|
|
|
|
gl.glBindBuffer(eGL_COPY_WRITE_BUFFER, DebugData.UBOs[0]);
|
|
|
|
MeshUBOData *uboptr =
|
|
(MeshUBOData *)gl.glMapBufferRange(eGL_COPY_WRITE_BUFFER, 0, sizeof(MeshUBOData),
|
|
GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
|
|
*uboptr = uboParams;
|
|
gl.glUnmapBuffer(eGL_COPY_WRITE_BUFFER);
|
|
|
|
gl.glBindBuffer(eGL_COPY_WRITE_BUFFER, DebugData.UBOs[1]);
|
|
Vec4f *v = (Vec4f *)gl.glMapBufferRange(eGL_COPY_WRITE_BUFFER, 0, sizeof(overdrawRamp),
|
|
GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
|
|
memcpy(v, overdrawRamp, sizeof(overdrawRamp));
|
|
gl.glUnmapBuffer(eGL_COPY_WRITE_BUFFER);
|
|
|
|
gl.glBindBuffer(eGL_COPY_WRITE_BUFFER, DebugData.UBOs[2]);
|
|
v = (Vec4f *)gl.glMapBufferRange(eGL_COPY_WRITE_BUFFER, 0, sizeof(Vec4f),
|
|
GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
|
|
*v = Vec4f((float)texDetails.width, (float)texDetails.height);
|
|
gl.glUnmapBuffer(eGL_COPY_WRITE_BUFFER);
|
|
|
|
vector<uint32_t> events = passEvents;
|
|
|
|
if(overlay == eTexOverlay_TriangleSizeDraw)
|
|
events.clear();
|
|
|
|
events.push_back(eventID);
|
|
|
|
if(!events.empty())
|
|
{
|
|
if(overlay == eTexOverlay_TriangleSizePass)
|
|
ReplayLog(events[0], eReplay_WithoutDraw);
|
|
else
|
|
rs.ApplyState(m_pDriver->GetCtx(), m_pDriver);
|
|
|
|
// this all happens on the replay context so we need a temp FBO/VAO
|
|
GLuint overlayFBO = 0, tempVAO = 0;
|
|
gl.glGenFramebuffers(1, &overlayFBO);
|
|
gl.glGenVertexArrays(1, &tempVAO);
|
|
|
|
for(size_t i = 0; i < events.size(); i++)
|
|
{
|
|
GLboolean blending = GL_FALSE;
|
|
GLint depthwritemask = 1;
|
|
GLint stencilfmask = 0xff, stencilbmask = 0xff;
|
|
GLuint drawFBO = 0, prevVAO = 0;
|
|
struct UBO
|
|
{
|
|
GLuint buf;
|
|
GLint64 offs;
|
|
GLint64 size;
|
|
} ubos[3];
|
|
|
|
// save the state we're going to mess with
|
|
{
|
|
gl.glGetIntegerv(eGL_DEPTH_WRITEMASK, &depthwritemask);
|
|
gl.glGetIntegerv(eGL_STENCIL_WRITEMASK, &stencilfmask);
|
|
gl.glGetIntegerv(eGL_STENCIL_BACK_WRITEMASK, &stencilbmask);
|
|
|
|
gl.glGetIntegerv(eGL_DRAW_FRAMEBUFFER_BINDING, (GLint *)&drawFBO);
|
|
gl.glGetIntegerv(eGL_VERTEX_ARRAY_BINDING, (GLint *)&prevVAO);
|
|
|
|
blending = gl.glIsEnabled(eGL_BLEND);
|
|
|
|
for(uint32_t u = 0; u < 3; u++)
|
|
{
|
|
gl.glGetIntegeri_v(eGL_UNIFORM_BUFFER_BINDING, u, (GLint *)&ubos[u].buf);
|
|
gl.glGetInteger64i_v(eGL_UNIFORM_BUFFER_START, u, (GLint64 *)&ubos[u].offs);
|
|
gl.glGetInteger64i_v(eGL_UNIFORM_BUFFER_SIZE, u, (GLint64 *)&ubos[u].size);
|
|
}
|
|
}
|
|
|
|
// disable depth and stencil writes
|
|
gl.glDepthMask(GL_FALSE);
|
|
gl.glStencilMask(GL_FALSE);
|
|
|
|
// disable blending
|
|
gl.glDisable(eGL_BLEND);
|
|
|
|
// bind our UBOs
|
|
gl.glBindBufferBase(eGL_UNIFORM_BUFFER, 0, DebugData.UBOs[0]);
|
|
gl.glBindBufferBase(eGL_UNIFORM_BUFFER, 1, DebugData.UBOs[1]);
|
|
gl.glBindBufferBase(eGL_UNIFORM_BUFFER, 2, DebugData.UBOs[2]);
|
|
|
|
const GLenum att = eGL_DEPTH_ATTACHMENT;
|
|
GLuint depthObj = 0;
|
|
GLint type = 0, level = 0, layered = 0, layer = 0;
|
|
|
|
// fetch the details of the 'real' depth attachment
|
|
gl.glGetNamedFramebufferAttachmentParameterivEXT(
|
|
drawFBO, att, eGL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, (GLint *)&depthObj);
|
|
gl.glGetNamedFramebufferAttachmentParameterivEXT(
|
|
drawFBO, att, eGL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &type);
|
|
|
|
if(depthObj)
|
|
{
|
|
gl.glGetNamedFramebufferAttachmentParameterivEXT(
|
|
drawFBO, att, eGL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, &level);
|
|
gl.glGetNamedFramebufferAttachmentParameterivEXT(
|
|
drawFBO, att, eGL_FRAMEBUFFER_ATTACHMENT_LAYERED, &layered);
|
|
|
|
layered = (layered != 0);
|
|
|
|
layer = 0;
|
|
if(layered == 0)
|
|
gl.glGetNamedFramebufferAttachmentParameterivEXT(
|
|
drawFBO, att, eGL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER, &layer);
|
|
|
|
if(type != eGL_RENDERBUFFER)
|
|
{
|
|
ResourceId id = m_pDriver->GetResourceManager()->GetID(TextureRes(ctx, depthObj));
|
|
WrappedOpenGL::TextureData &details = m_pDriver->m_Textures[id];
|
|
|
|
if(details.curType == eGL_TEXTURE_CUBE_MAP)
|
|
{
|
|
GLenum face;
|
|
gl.glGetNamedFramebufferAttachmentParameterivEXT(
|
|
drawFBO, att, eGL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE, (GLint *)&face);
|
|
|
|
layer = CubeTargetIndex(face);
|
|
}
|
|
}
|
|
}
|
|
|
|
// bind our FBO
|
|
gl.glBindFramebuffer(eGL_DRAW_FRAMEBUFFER, overlayFBO);
|
|
gl.glFramebufferTexture(eGL_FRAMEBUFFER, eGL_COLOR_ATTACHMENT0, DebugData.overlayTex, 0);
|
|
|
|
// now apply the depth texture binding
|
|
if(depthObj)
|
|
{
|
|
if(type == eGL_RENDERBUFFER)
|
|
{
|
|
gl.glNamedFramebufferRenderbufferEXT(overlayFBO, att, eGL_RENDERBUFFER, depthObj);
|
|
}
|
|
else
|
|
{
|
|
if(!layered)
|
|
{
|
|
// we use old-style non-DSA for this because binding cubemap faces with EXT_dsa
|
|
// is completely messed up and broken
|
|
|
|
// if obj is a cubemap use face-specific targets
|
|
ResourceId id = m_pDriver->GetResourceManager()->GetID(TextureRes(ctx, depthObj));
|
|
WrappedOpenGL::TextureData &details = m_pDriver->m_Textures[id];
|
|
|
|
if(details.curType == eGL_TEXTURE_CUBE_MAP)
|
|
{
|
|
GLenum faces[] = {
|
|
eGL_TEXTURE_CUBE_MAP_POSITIVE_X, eGL_TEXTURE_CUBE_MAP_NEGATIVE_X,
|
|
eGL_TEXTURE_CUBE_MAP_POSITIVE_Y, eGL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
|
|
eGL_TEXTURE_CUBE_MAP_POSITIVE_Z, eGL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
|
|
};
|
|
|
|
if(layer < 6)
|
|
{
|
|
gl.glFramebufferTexture2D(eGL_DRAW_FRAMEBUFFER, att, faces[layer], depthObj, level);
|
|
}
|
|
else
|
|
{
|
|
RDCWARN(
|
|
"Invalid layer %u used to bind cubemap to framebuffer. Binding POSITIVE_X");
|
|
gl.glFramebufferTexture2D(eGL_DRAW_FRAMEBUFFER, att, faces[0], depthObj, level);
|
|
}
|
|
}
|
|
else if(details.curType == eGL_TEXTURE_CUBE_MAP_ARRAY ||
|
|
details.curType == eGL_TEXTURE_1D_ARRAY ||
|
|
details.curType == eGL_TEXTURE_2D_ARRAY)
|
|
{
|
|
gl.glFramebufferTextureLayer(eGL_DRAW_FRAMEBUFFER, att, depthObj, level, layer);
|
|
}
|
|
else
|
|
{
|
|
RDCASSERT(layer == 0);
|
|
gl.glNamedFramebufferTextureEXT(overlayFBO, att, depthObj, level);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gl.glNamedFramebufferTextureEXT(overlayFBO, att, depthObj, level);
|
|
}
|
|
}
|
|
}
|
|
|
|
GLuint prog = 0, pipe = 0;
|
|
gl.glGetIntegerv(eGL_CURRENT_PROGRAM, (GLint *)&prog);
|
|
gl.glGetIntegerv(eGL_PROGRAM_PIPELINE_BINDING, (GLint *)&pipe);
|
|
|
|
gl.glUseProgram(DebugData.trisizeProg);
|
|
gl.glBindProgramPipeline(0);
|
|
|
|
const FetchDrawcall *draw = m_pDriver->GetDrawcall(events[i]);
|
|
|
|
for(uint32_t inst = 0; draw && inst < RDCMAX(1U, draw->numInstances); inst++)
|
|
{
|
|
MeshFormat fmt = GetPostVSBuffers(events[i], inst, eMeshDataStage_GSOut);
|
|
if(fmt.buf == ResourceId())
|
|
fmt = GetPostVSBuffers(events[i], inst, eMeshDataStage_VSOut);
|
|
|
|
if(fmt.buf != ResourceId())
|
|
{
|
|
GLenum topo = MakeGLPrimitiveTopology(fmt.topo);
|
|
|
|
gl.glBindVertexArray(tempVAO);
|
|
|
|
{
|
|
if(fmt.specialFormat != eSpecial_Unknown)
|
|
{
|
|
if(fmt.specialFormat == eSpecial_R10G10B10A2)
|
|
{
|
|
if(fmt.compType == eCompType_UInt)
|
|
gl.glVertexAttribIFormat(0, 4, eGL_UNSIGNED_INT_2_10_10_10_REV, 0);
|
|
if(fmt.compType == eCompType_SInt)
|
|
gl.glVertexAttribIFormat(0, 4, eGL_INT_2_10_10_10_REV, 0);
|
|
}
|
|
else if(fmt.specialFormat == eSpecial_R11G11B10)
|
|
{
|
|
gl.glVertexAttribFormat(0, 4, eGL_UNSIGNED_INT_10F_11F_11F_REV, GL_FALSE, 0);
|
|
}
|
|
else
|
|
{
|
|
RDCWARN("Unsupported special vertex attribute format: %x", fmt.specialFormat);
|
|
}
|
|
}
|
|
else if(fmt.compType == eCompType_Float || fmt.compType == eCompType_UNorm ||
|
|
fmt.compType == eCompType_SNorm)
|
|
{
|
|
GLenum fmttype = eGL_UNSIGNED_INT;
|
|
|
|
if(fmt.compByteWidth == 4)
|
|
{
|
|
if(fmt.compType == eCompType_Float)
|
|
fmttype = eGL_FLOAT;
|
|
else if(fmt.compType == eCompType_UNorm)
|
|
fmttype = eGL_UNSIGNED_INT;
|
|
else if(fmt.compType == eCompType_SNorm)
|
|
fmttype = eGL_INT;
|
|
}
|
|
else if(fmt.compByteWidth == 2)
|
|
{
|
|
if(fmt.compType == eCompType_Float)
|
|
fmttype = eGL_HALF_FLOAT;
|
|
else if(fmt.compType == eCompType_UNorm)
|
|
fmttype = eGL_UNSIGNED_SHORT;
|
|
else if(fmt.compType == eCompType_SNorm)
|
|
fmttype = eGL_SHORT;
|
|
}
|
|
else if(fmt.compByteWidth == 1)
|
|
{
|
|
if(fmt.compType == eCompType_UNorm)
|
|
fmttype = eGL_UNSIGNED_BYTE;
|
|
else if(fmt.compType == eCompType_SNorm)
|
|
fmttype = eGL_BYTE;
|
|
}
|
|
|
|
gl.glVertexAttribFormat(0, fmt.compCount, fmttype, fmt.compType != eCompType_Float,
|
|
0);
|
|
}
|
|
else if(fmt.compType == eCompType_UInt || fmt.compType == eCompType_SInt)
|
|
{
|
|
GLenum fmttype = eGL_UNSIGNED_INT;
|
|
|
|
if(fmt.compByteWidth == 4)
|
|
{
|
|
if(fmt.compType == eCompType_UInt)
|
|
fmttype = eGL_UNSIGNED_INT;
|
|
else if(fmt.compType == eCompType_SInt)
|
|
fmttype = eGL_INT;
|
|
}
|
|
else if(fmt.compByteWidth == 2)
|
|
{
|
|
if(fmt.compType == eCompType_UInt)
|
|
fmttype = eGL_UNSIGNED_SHORT;
|
|
else if(fmt.compType == eCompType_SInt)
|
|
fmttype = eGL_SHORT;
|
|
}
|
|
else if(fmt.compByteWidth == 1)
|
|
{
|
|
if(fmt.compType == eCompType_UInt)
|
|
fmttype = eGL_UNSIGNED_BYTE;
|
|
else if(fmt.compType == eCompType_SInt)
|
|
fmttype = eGL_BYTE;
|
|
}
|
|
|
|
gl.glVertexAttribIFormat(0, fmt.compCount, fmttype, 0);
|
|
}
|
|
else if(fmt.compType == eCompType_Double)
|
|
{
|
|
gl.glVertexAttribLFormat(0, fmt.compCount, eGL_DOUBLE, 0);
|
|
}
|
|
|
|
GLuint vb = m_pDriver->GetResourceManager()->GetCurrentResource(fmt.buf).name;
|
|
gl.glBindVertexBuffer(0, vb, (GLintptr)fmt.offset, fmt.stride);
|
|
}
|
|
|
|
gl.glEnableVertexAttribArray(0);
|
|
gl.glDisableVertexAttribArray(1);
|
|
|
|
if(fmt.idxbuf != ResourceId())
|
|
{
|
|
GLenum idxtype = eGL_UNSIGNED_BYTE;
|
|
if(fmt.idxByteWidth == 2)
|
|
idxtype = eGL_UNSIGNED_SHORT;
|
|
else if(fmt.idxByteWidth == 4)
|
|
idxtype = eGL_UNSIGNED_INT;
|
|
|
|
GLuint ib = m_pDriver->GetResourceManager()->GetCurrentResource(fmt.idxbuf).name;
|
|
gl.glBindBuffer(eGL_ELEMENT_ARRAY_BUFFER, ib);
|
|
gl.glDrawElementsBaseVertex(topo, fmt.numVerts, idxtype,
|
|
(const void *)uintptr_t(fmt.idxoffs), fmt.baseVertex);
|
|
}
|
|
else
|
|
{
|
|
gl.glDrawArrays(topo, 0, fmt.numVerts);
|
|
}
|
|
}
|
|
}
|
|
|
|
// pop the state that we messed with
|
|
{
|
|
gl.glBindProgramPipeline(pipe);
|
|
gl.glUseProgram(prog);
|
|
|
|
if(blending)
|
|
gl.glEnable(eGL_BLEND);
|
|
else
|
|
gl.glDisable(eGL_BLEND);
|
|
|
|
// restore the previous FBO/VAO
|
|
gl.glBindFramebuffer(eGL_DRAW_FRAMEBUFFER, drawFBO);
|
|
gl.glBindVertexArray(prevVAO);
|
|
|
|
for(uint32_t u = 0; u < 3; u++)
|
|
{
|
|
if(ubos[u].buf == 0 || (ubos[u].offs == 0 && ubos[u].size == 0))
|
|
gl.glBindBufferBase(eGL_UNIFORM_BUFFER, u, ubos[u].buf);
|
|
else
|
|
gl.glBindBufferRange(eGL_UNIFORM_BUFFER, u, ubos[u].buf, (GLintptr)ubos[u].offs,
|
|
(GLsizeiptr)ubos[u].size);
|
|
}
|
|
|
|
gl.glDepthMask(depthwritemask ? GL_TRUE : GL_FALSE);
|
|
gl.glStencilMaskSeparate(eGL_FRONT, (GLuint)stencilfmask);
|
|
gl.glStencilMaskSeparate(eGL_BACK, (GLuint)stencilbmask);
|
|
}
|
|
|
|
if(overlay == eTexOverlay_TriangleSizePass)
|
|
{
|
|
m_pDriver->ReplayLog(0, events[i], eReplay_OnlyDraw);
|
|
|
|
if(i + 1 < events.size())
|
|
m_pDriver->ReplayLog(events[i], events[i + 1], eReplay_WithoutDraw);
|
|
}
|
|
}
|
|
|
|
gl.glDeleteFramebuffers(1, &overlayFBO);
|
|
gl.glDeleteVertexArrays(1, &tempVAO);
|
|
|
|
if(overlay == eTexOverlay_TriangleSizePass)
|
|
ReplayLog(eventID, eReplay_WithoutDraw);
|
|
}
|
|
}
|
|
else if(overlay == eTexOverlay_QuadOverdrawDraw || overlay == eTexOverlay_QuadOverdrawPass)
|
|
{
|
|
if(DebugData.quadoverdrawFSProg)
|
|
{
|
|
SCOPED_TIMER("Quad Overdraw");
|
|
|
|
float black[] = {0.0f, 0.0f, 0.0f, 0.0f};
|
|
gl.glClearBufferfv(eGL_COLOR, 0, black);
|
|
|
|
vector<uint32_t> events = passEvents;
|
|
|
|
if(overlay == eTexOverlay_QuadOverdrawDraw)
|
|
events.clear();
|
|
|
|
events.push_back(eventID);
|
|
|
|
if(!events.empty())
|
|
{
|
|
GLuint replacefbo = 0;
|
|
GLuint quadtexs[3] = {0};
|
|
gl.glGenFramebuffers(1, &replacefbo);
|
|
gl.glBindFramebuffer(eGL_FRAMEBUFFER, replacefbo);
|
|
|
|
gl.glGenTextures(3, quadtexs);
|
|
|
|
// image for quad usage
|
|
gl.glBindTexture(eGL_TEXTURE_2D_ARRAY, quadtexs[2]);
|
|
gl.glTextureImage3DEXT(quadtexs[2], eGL_TEXTURE_2D_ARRAY, 0, eGL_R32UI,
|
|
RDCMAX(1, texDetails.width >> 1), RDCMAX(1, texDetails.height >> 1),
|
|
4, 0, eGL_RED_INTEGER, eGL_UNSIGNED_INT, NULL);
|
|
gl.glTexParameteri(eGL_TEXTURE_2D_ARRAY, eGL_TEXTURE_MAX_LEVEL, 0);
|
|
|
|
// temporarily attach to FBO to clear it
|
|
GLint zero[4] = {0};
|
|
gl.glFramebufferTextureLayer(eGL_FRAMEBUFFER, eGL_COLOR_ATTACHMENT0, quadtexs[2], 0, 0);
|
|
gl.glClearBufferiv(eGL_COLOR, 0, zero);
|
|
gl.glFramebufferTextureLayer(eGL_FRAMEBUFFER, eGL_COLOR_ATTACHMENT0, quadtexs[2], 0, 1);
|
|
gl.glClearBufferiv(eGL_COLOR, 0, zero);
|
|
gl.glFramebufferTextureLayer(eGL_FRAMEBUFFER, eGL_COLOR_ATTACHMENT0, quadtexs[2], 0, 2);
|
|
gl.glClearBufferiv(eGL_COLOR, 0, zero);
|
|
gl.glFramebufferTextureLayer(eGL_FRAMEBUFFER, eGL_COLOR_ATTACHMENT0, quadtexs[2], 0, 3);
|
|
gl.glClearBufferiv(eGL_COLOR, 0, zero);
|
|
|
|
gl.glBindTexture(eGL_TEXTURE_2D, quadtexs[0]);
|
|
gl.glTextureImage2DEXT(quadtexs[0], eGL_TEXTURE_2D, 0, eGL_RGBA8, texDetails.width,
|
|
texDetails.height, 0, eGL_RGBA, eGL_UNSIGNED_BYTE, NULL);
|
|
gl.glTexParameteri(eGL_TEXTURE_2D, eGL_TEXTURE_MAX_LEVEL, 0);
|
|
gl.glTexParameteri(eGL_TEXTURE_2D, eGL_TEXTURE_MIN_FILTER, eGL_NEAREST);
|
|
gl.glTexParameteri(eGL_TEXTURE_2D, eGL_TEXTURE_MAG_FILTER, eGL_NEAREST);
|
|
gl.glTexParameteri(eGL_TEXTURE_2D, eGL_TEXTURE_WRAP_S, eGL_CLAMP_TO_EDGE);
|
|
gl.glTexParameteri(eGL_TEXTURE_2D, eGL_TEXTURE_WRAP_T, eGL_CLAMP_TO_EDGE);
|
|
gl.glFramebufferTexture(eGL_FRAMEBUFFER, eGL_COLOR_ATTACHMENT0, quadtexs[0], 0);
|
|
|
|
GLuint curDepth = 0, depthType = 0;
|
|
|
|
// TODO handle non-2D depth/stencil attachments and fetch slice or cubemap face
|
|
GLint mip = 0;
|
|
|
|
gl.glGetNamedFramebufferAttachmentParameterivEXT(rs.DrawFBO, eGL_DEPTH_ATTACHMENT,
|
|
eGL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME,
|
|
(GLint *)&curDepth);
|
|
gl.glGetNamedFramebufferAttachmentParameterivEXT(rs.DrawFBO, eGL_DEPTH_ATTACHMENT,
|
|
eGL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE,
|
|
(GLint *)&depthType);
|
|
gl.glGetNamedFramebufferAttachmentParameterivEXT(
|
|
rs.DrawFBO, eGL_DEPTH_ATTACHMENT, eGL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, &mip);
|
|
|
|
GLenum fmt = eGL_DEPTH32F_STENCIL8;
|
|
|
|
if(depthType == eGL_TEXTURE)
|
|
{
|
|
gl.glGetTextureLevelParameterivEXT(curDepth, texBindingEnum, mip,
|
|
eGL_TEXTURE_INTERNAL_FORMAT, (GLint *)&fmt);
|
|
}
|
|
else
|
|
{
|
|
gl.glGetNamedRenderbufferParameterivEXT(curDepth, eGL_RENDERBUFFER_INTERNAL_FORMAT,
|
|
(GLint *)&fmt);
|
|
}
|
|
|
|
gl.glBindTexture(eGL_TEXTURE_2D, quadtexs[1]);
|
|
gl.glTextureImage2DEXT(quadtexs[1], eGL_TEXTURE_2D, 0, fmt, texDetails.width,
|
|
texDetails.height, 0, GetBaseFormat(fmt), GetDataType(fmt), NULL);
|
|
gl.glTexParameteri(eGL_TEXTURE_2D, eGL_TEXTURE_MAX_LEVEL, 0);
|
|
gl.glTexParameteri(eGL_TEXTURE_2D, eGL_TEXTURE_MIN_FILTER, eGL_NEAREST);
|
|
gl.glTexParameteri(eGL_TEXTURE_2D, eGL_TEXTURE_MAG_FILTER, eGL_NEAREST);
|
|
gl.glTexParameteri(eGL_TEXTURE_2D, eGL_TEXTURE_WRAP_S, eGL_CLAMP_TO_EDGE);
|
|
gl.glTexParameteri(eGL_TEXTURE_2D, eGL_TEXTURE_WRAP_T, eGL_CLAMP_TO_EDGE);
|
|
gl.glFramebufferTexture(eGL_FRAMEBUFFER, eGL_DEPTH_STENCIL_ATTACHMENT, quadtexs[1], 0);
|
|
|
|
if(overlay == eTexOverlay_QuadOverdrawPass)
|
|
ReplayLog(events[0], eReplay_WithoutDraw);
|
|
else
|
|
rs.ApplyState(m_pDriver->GetCtx(), m_pDriver);
|
|
|
|
for(size_t i = 0; i < events.size(); i++)
|
|
{
|
|
GLint depthwritemask = 1;
|
|
GLint stencilfmask = 0xff, stencilbmask = 0xff;
|
|
GLuint curdrawfbo = 0, curreadfbo = 0;
|
|
struct
|
|
{
|
|
GLuint name;
|
|
GLuint level;
|
|
GLboolean layered;
|
|
GLuint layer;
|
|
GLenum access;
|
|
GLenum format;
|
|
} curimage0 = {0};
|
|
|
|
// save the state we're going to mess with
|
|
{
|
|
gl.glGetIntegerv(eGL_DEPTH_WRITEMASK, &depthwritemask);
|
|
gl.glGetIntegerv(eGL_STENCIL_WRITEMASK, &stencilfmask);
|
|
gl.glGetIntegerv(eGL_STENCIL_BACK_WRITEMASK, &stencilbmask);
|
|
|
|
gl.glGetIntegerv(eGL_DRAW_FRAMEBUFFER_BINDING, (GLint *)&curdrawfbo);
|
|
gl.glGetIntegerv(eGL_READ_FRAMEBUFFER_BINDING, (GLint *)&curreadfbo);
|
|
|
|
gl.glGetIntegeri_v(eGL_IMAGE_BINDING_NAME, 0, (GLint *)&curimage0.name);
|
|
gl.glGetIntegeri_v(eGL_IMAGE_BINDING_LEVEL, 0, (GLint *)&curimage0.level);
|
|
gl.glGetIntegeri_v(eGL_IMAGE_BINDING_ACCESS, 0, (GLint *)&curimage0.access);
|
|
gl.glGetIntegeri_v(eGL_IMAGE_BINDING_FORMAT, 0, (GLint *)&curimage0.format);
|
|
gl.glGetBooleani_v(eGL_IMAGE_BINDING_LAYERED, 0, &curimage0.layered);
|
|
if(curimage0.layered)
|
|
gl.glGetIntegeri_v(eGL_IMAGE_BINDING_LAYER, 0, (GLint *)&curimage0.layer);
|
|
}
|
|
|
|
// disable depth and stencil writes
|
|
gl.glDepthMask(GL_FALSE);
|
|
gl.glStencilMask(GL_FALSE);
|
|
|
|
// bind our FBO
|
|
gl.glBindFramebuffer(eGL_DRAW_FRAMEBUFFER, replacefbo);
|
|
// bind image
|
|
gl.glBindImageTexture(0, quadtexs[2], 0, GL_TRUE, 0, eGL_READ_WRITE, eGL_R32UI);
|
|
|
|
GLuint prog = 0, pipe = 0;
|
|
gl.glGetIntegerv(eGL_CURRENT_PROGRAM, (GLint *)&prog);
|
|
gl.glGetIntegerv(eGL_PROGRAM_PIPELINE_BINDING, (GLint *)&pipe);
|
|
|
|
// replace fragment shader. This is exactly what we did
|
|
// at the start of this function for the single-event case, but now we have
|
|
// to do it for every event
|
|
SetupOverlayPipeline(prog, pipe, DebugData.quadoverdrawFSProg);
|
|
gl.glUseProgram(0);
|
|
gl.glBindProgramPipeline(DebugData.overlayPipe);
|
|
|
|
gl.glBindFramebuffer(eGL_READ_FRAMEBUFFER, curdrawfbo);
|
|
gl.glBlitFramebuffer(0, 0, texDetails.width, texDetails.height, 0, 0, texDetails.width,
|
|
texDetails.height, GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT,
|
|
eGL_NEAREST);
|
|
|
|
m_pDriver->ReplayLog(0, events[i], eReplay_OnlyDraw);
|
|
|
|
// pop the state that we messed with
|
|
{
|
|
gl.glBindProgramPipeline(pipe);
|
|
gl.glUseProgram(prog);
|
|
|
|
if(curimage0.name)
|
|
gl.glBindImageTexture(0, curimage0.name, curimage0.level,
|
|
curimage0.layered ? GL_TRUE : GL_FALSE, curimage0.layer,
|
|
curimage0.access, curimage0.format);
|
|
else
|
|
gl.glBindImageTexture(0, 0, 0, GL_FALSE, 0, eGL_READ_ONLY, eGL_R32UI);
|
|
|
|
gl.glBindFramebuffer(eGL_DRAW_FRAMEBUFFER, curdrawfbo);
|
|
gl.glBindFramebuffer(eGL_READ_FRAMEBUFFER, curreadfbo);
|
|
|
|
gl.glDepthMask(depthwritemask ? GL_TRUE : GL_FALSE);
|
|
gl.glStencilMaskSeparate(eGL_FRONT, (GLuint)stencilfmask);
|
|
gl.glStencilMaskSeparate(eGL_BACK, (GLuint)stencilbmask);
|
|
}
|
|
|
|
if(overlay == eTexOverlay_QuadOverdrawPass)
|
|
{
|
|
m_pDriver->ReplayLog(0, events[i], eReplay_OnlyDraw);
|
|
|
|
if(i + 1 < events.size())
|
|
m_pDriver->ReplayLog(events[i], events[i + 1], eReplay_WithoutDraw);
|
|
}
|
|
}
|
|
|
|
// resolve pass
|
|
{
|
|
gl.glUseProgram(DebugData.quadoverdrawResolveProg);
|
|
gl.glBindProgramPipeline(0);
|
|
|
|
gl.glBindBufferBase(eGL_UNIFORM_BUFFER, 1, DebugData.UBOs[0]);
|
|
|
|
Vec4f *v = (Vec4f *)gl.glMapBufferRange(eGL_UNIFORM_BUFFER, 0, sizeof(overdrawRamp),
|
|
GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
|
|
memcpy(v, overdrawRamp, sizeof(overdrawRamp));
|
|
gl.glUnmapBuffer(eGL_UNIFORM_BUFFER);
|
|
|
|
// modify our fbo to attach the overlay texture instead
|
|
gl.glBindFramebuffer(eGL_FRAMEBUFFER, replacefbo);
|
|
gl.glFramebufferTexture(eGL_FRAMEBUFFER, eGL_COLOR_ATTACHMENT0, DebugData.overlayTex, 0);
|
|
gl.glFramebufferTexture(eGL_FRAMEBUFFER, eGL_DEPTH_STENCIL_ATTACHMENT, 0, 0);
|
|
|
|
gl.glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
|
gl.glDisable(eGL_BLEND);
|
|
gl.glDisable(eGL_SCISSOR_TEST);
|
|
gl.glDepthMask(GL_FALSE);
|
|
gl.glDisable(eGL_CULL_FACE);
|
|
gl.glPolygonMode(eGL_FRONT_AND_BACK, eGL_FILL);
|
|
gl.glDisable(eGL_DEPTH_TEST);
|
|
gl.glDisable(eGL_STENCIL_TEST);
|
|
gl.glStencilMask(0);
|
|
gl.glViewport(0, 0, texDetails.width, texDetails.height);
|
|
|
|
gl.glBindImageTexture(0, quadtexs[2], 0, GL_FALSE, 0, eGL_READ_WRITE, eGL_R32UI);
|
|
|
|
GLuint emptyVAO = 0;
|
|
gl.glGenVertexArrays(1, &emptyVAO);
|
|
gl.glBindVertexArray(emptyVAO);
|
|
gl.glDrawArrays(eGL_TRIANGLE_STRIP, 0, 4);
|
|
gl.glBindVertexArray(0);
|
|
gl.glDeleteVertexArrays(1, &emptyVAO);
|
|
|
|
gl.glFramebufferTexture(eGL_FRAMEBUFFER, eGL_COLOR_ATTACHMENT0, quadtexs[0], 0);
|
|
}
|
|
|
|
gl.glDeleteFramebuffers(1, &replacefbo);
|
|
gl.glDeleteTextures(3, quadtexs);
|
|
|
|
if(overlay == eTexOverlay_QuadOverdrawPass)
|
|
ReplayLog(eventID, eReplay_WithoutDraw);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RDCERR(
|
|
"Unexpected/unimplemented overlay type - should implement a placeholder overlay for all "
|
|
"types");
|
|
}
|
|
|
|
rs.ApplyState(m_pDriver->GetCtx(), m_pDriver);
|
|
|
|
return m_pDriver->GetResourceManager()->GetID(TextureRes(ctx, DebugData.overlayTex));
|
|
}
|
|
|
|
void GLReplay::InitPostVSBuffers(uint32_t eventID)
|
|
{
|
|
if(m_PostVSData.find(eventID) != m_PostVSData.end())
|
|
return;
|
|
|
|
MakeCurrentReplayContext(&m_ReplayCtx);
|
|
|
|
void *ctx = m_ReplayCtx.ctx;
|
|
|
|
WrappedOpenGL &gl = *m_pDriver;
|
|
GLResourceManager *rm = m_pDriver->GetResourceManager();
|
|
|
|
GLRenderState rs(&gl.GetHookset(), NULL, READING);
|
|
rs.FetchState(ctx, &gl);
|
|
GLuint elArrayBuffer = 0;
|
|
if(rs.VAO)
|
|
gl.glGetIntegerv(eGL_ELEMENT_ARRAY_BUFFER_BINDING, (GLint *)&elArrayBuffer);
|
|
|
|
// reflection structures
|
|
ShaderReflection *vsRefl = NULL;
|
|
ShaderReflection *tesRefl = NULL;
|
|
ShaderReflection *gsRefl = NULL;
|
|
|
|
// non-program used separable programs of each shader.
|
|
// we'll add our feedback varings to these programs, relink,
|
|
// and combine into a pipeline for use.
|
|
GLuint vsProg = 0;
|
|
GLuint tcsProg = 0;
|
|
GLuint tesProg = 0;
|
|
GLuint gsProg = 0;
|
|
|
|
// these are the 'real' programs with uniform values that we need
|
|
// to copy over to our separable programs.
|
|
GLuint vsProgSrc = 0;
|
|
GLuint tcsProgSrc = 0;
|
|
GLuint tesProgSrc = 0;
|
|
GLuint gsProgSrc = 0;
|
|
|
|
if(rs.Program == 0)
|
|
{
|
|
if(rs.Pipeline == 0)
|
|
{
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
ResourceId id = rm->GetID(ProgramPipeRes(ctx, rs.Pipeline));
|
|
auto &pipeDetails = m_pDriver->m_Pipelines[id];
|
|
|
|
if(pipeDetails.stageShaders[0] != ResourceId())
|
|
{
|
|
vsRefl = GetShader(pipeDetails.stageShaders[0], "");
|
|
vsProg = m_pDriver->m_Shaders[pipeDetails.stageShaders[0]].prog;
|
|
vsProgSrc = rm->GetCurrentResource(pipeDetails.stagePrograms[0]).name;
|
|
}
|
|
if(pipeDetails.stageShaders[1] != ResourceId())
|
|
{
|
|
tcsProg = m_pDriver->m_Shaders[pipeDetails.stageShaders[1]].prog;
|
|
tcsProgSrc = rm->GetCurrentResource(pipeDetails.stagePrograms[1]).name;
|
|
}
|
|
if(pipeDetails.stageShaders[2] != ResourceId())
|
|
{
|
|
tesRefl = GetShader(pipeDetails.stageShaders[2], "");
|
|
tesProg = m_pDriver->m_Shaders[pipeDetails.stageShaders[2]].prog;
|
|
tesProgSrc = rm->GetCurrentResource(pipeDetails.stagePrograms[2]).name;
|
|
}
|
|
if(pipeDetails.stageShaders[3] != ResourceId())
|
|
{
|
|
gsRefl = GetShader(pipeDetails.stageShaders[3], "");
|
|
gsProg = m_pDriver->m_Shaders[pipeDetails.stageShaders[3]].prog;
|
|
gsProgSrc = rm->GetCurrentResource(pipeDetails.stagePrograms[3]).name;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
auto &progDetails = m_pDriver->m_Programs[rm->GetID(ProgramRes(ctx, rs.Program))];
|
|
|
|
if(progDetails.stageShaders[0] != ResourceId())
|
|
{
|
|
vsRefl = GetShader(progDetails.stageShaders[0], "");
|
|
vsProg = m_pDriver->m_Shaders[progDetails.stageShaders[0]].prog;
|
|
}
|
|
if(progDetails.stageShaders[1] != ResourceId())
|
|
{
|
|
tcsProg = m_pDriver->m_Shaders[progDetails.stageShaders[1]].prog;
|
|
}
|
|
if(progDetails.stageShaders[2] != ResourceId())
|
|
{
|
|
tesRefl = GetShader(progDetails.stageShaders[2], "");
|
|
tesProg = m_pDriver->m_Shaders[progDetails.stageShaders[2]].prog;
|
|
}
|
|
if(progDetails.stageShaders[3] != ResourceId())
|
|
{
|
|
gsRefl = GetShader(progDetails.stageShaders[3], "");
|
|
gsProg = m_pDriver->m_Shaders[progDetails.stageShaders[3]].prog;
|
|
}
|
|
|
|
vsProgSrc = tcsProgSrc = tesProgSrc = gsProgSrc = rs.Program;
|
|
}
|
|
|
|
if(vsRefl == NULL)
|
|
{
|
|
// no vertex shader bound (no vertex processing - compute only program
|
|
// or no program bound, for a clear etc)
|
|
m_PostVSData[eventID] = GLPostVSData();
|
|
return;
|
|
}
|
|
|
|
const FetchDrawcall *drawcall = m_pDriver->GetDrawcall(eventID);
|
|
|
|
if(drawcall->numIndices == 0)
|
|
{
|
|
// draw is 0 length, nothing to do
|
|
m_PostVSData[eventID] = GLPostVSData();
|
|
return;
|
|
}
|
|
|
|
list<string> matrixVaryings; // matrices need some fixup
|
|
vector<const char *> varyings;
|
|
|
|
// we don't want to do any work, so just discard before rasterizing
|
|
gl.glEnable(eGL_RASTERIZER_DISCARD);
|
|
|
|
CopyProgramAttribBindings(gl.GetHookset(), vsProgSrc, vsProg, vsRefl);
|
|
|
|
varyings.clear();
|
|
|
|
uint32_t stride = 0;
|
|
int32_t posidx = -1;
|
|
|
|
for(int32_t i = 0; i < vsRefl->OutputSig.count; i++)
|
|
{
|
|
const char *name = vsRefl->OutputSig[i].varName.elems;
|
|
int32_t len = vsRefl->OutputSig[i].varName.count;
|
|
|
|
bool include = true;
|
|
|
|
// for matrices with names including :row1, :row2 etc we only include :row0
|
|
// as a varying (but increment the stride for all rows to account for the space)
|
|
// and modify the name to remove the :row0 part
|
|
const char *colon = strchr(name, ':');
|
|
if(colon)
|
|
{
|
|
if(name[len - 1] != '0')
|
|
{
|
|
include = false;
|
|
}
|
|
else
|
|
{
|
|
matrixVaryings.push_back(string(name, colon));
|
|
name = matrixVaryings.back().c_str();
|
|
}
|
|
}
|
|
|
|
if(include)
|
|
varyings.push_back(name);
|
|
|
|
if(vsRefl->OutputSig[i].systemValue == eAttr_Position)
|
|
posidx = int32_t(varyings.size()) - 1;
|
|
|
|
stride += sizeof(float) * vsRefl->OutputSig[i].compCount;
|
|
}
|
|
|
|
// shift position attribute up to first, keeping order otherwise
|
|
// the same
|
|
if(posidx > 0)
|
|
{
|
|
const char *pos = varyings[posidx];
|
|
varyings.erase(varyings.begin() + posidx);
|
|
varyings.insert(varyings.begin(), pos);
|
|
}
|
|
|
|
// this is REALLY ugly, but I've seen problems with varying specification, so we try and
|
|
// do some fixup by removing prefixes from the results we got from PROGRAM_OUTPUT.
|
|
//
|
|
// the problem I've seen is:
|
|
//
|
|
// struct vertex
|
|
// {
|
|
// vec4 Color;
|
|
// };
|
|
//
|
|
// layout(location = 0) out vertex Out;
|
|
//
|
|
// (from g_truc gl-410-primitive-tessellation-2). On AMD the varyings are what you might expect
|
|
// (from
|
|
// the PROGRAM_OUTPUT interface names reflected out): "Out.Color", "gl_Position"
|
|
// however nvidia complains unless you use "Color", "gl_Position". This holds even if you add
|
|
// other
|
|
// variables to the vertex struct.
|
|
//
|
|
// strangely another sample that in-lines the output block like so:
|
|
//
|
|
// out block
|
|
// {
|
|
// vec2 Texcoord;
|
|
// } Out;
|
|
//
|
|
// uses "block.Texcoord" (reflected name from PROGRAM_OUTPUT and accepted by varyings string on
|
|
// both
|
|
// vendors). This is inconsistent as it's type.member not structname.member as move.
|
|
//
|
|
// The spec is very vague on exactly what these names should be, so I can't say which is correct
|
|
// out of these three possibilities.
|
|
//
|
|
// So our 'fix' is to loop while we have problems linking with the varyings (since we know
|
|
// otherwise
|
|
// linking should succeed, as we only get here with a successfully linked separable program - if
|
|
// it fails
|
|
// to link, it's assigned 0 earlier) and remove any prefixes from variables seen in the link error
|
|
// string.
|
|
// The error string is something like:
|
|
// "error: Varying (named Out.Color) specified but not present in the program object."
|
|
//
|
|
// Yeh. Ugly. Not guaranteed to work at all, but hopefully the common case will just be a single
|
|
// block
|
|
// without any nesting so this might work.
|
|
// At least we don't have to reallocate strings all over, since the memory is
|
|
// already owned elsewhere, we just need to modify pointers to trim prefixes. Bright side?
|
|
|
|
GLint status = 0;
|
|
bool finished = false;
|
|
for(;;)
|
|
{
|
|
// specify current varyings & relink
|
|
gl.glTransformFeedbackVaryings(vsProg, (GLsizei)varyings.size(), &varyings[0],
|
|
eGL_INTERLEAVED_ATTRIBS);
|
|
gl.glLinkProgram(vsProg);
|
|
|
|
gl.glGetProgramiv(vsProg, eGL_LINK_STATUS, &status);
|
|
|
|
// all good! Hopefully we'll mostly hit this
|
|
if(status == 1)
|
|
break;
|
|
|
|
// if finished is true, this was our last attempt - there are no
|
|
// more fixups possible
|
|
if(finished)
|
|
break;
|
|
|
|
char buffer[1025] = {0};
|
|
gl.glGetProgramInfoLog(vsProg, 1024, NULL, buffer);
|
|
|
|
// assume we're finished and can't retry any more after this.
|
|
// if we find a potential 'fixup' we'll set this back to false
|
|
finished = true;
|
|
|
|
// see if any of our current varyings are present in the buffer string
|
|
for(size_t i = 0; i < varyings.size(); i++)
|
|
{
|
|
if(strstr(buffer, varyings[i]))
|
|
{
|
|
const char *prefix_removed = strchr(varyings[i], '.');
|
|
|
|
// does it contain a prefix?
|
|
if(prefix_removed)
|
|
{
|
|
prefix_removed++; // now this is our string without the prefix
|
|
|
|
// first check this won't cause a duplicate - if it does, we have to try something else
|
|
bool duplicate = false;
|
|
for(size_t j = 0; j < varyings.size(); j++)
|
|
{
|
|
if(!strcmp(varyings[j], prefix_removed))
|
|
{
|
|
duplicate = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!duplicate)
|
|
{
|
|
// we'll attempt this fixup
|
|
RDCWARN("Attempting XFB varying fixup, subst '%s' for '%s'", varyings[i], prefix_removed);
|
|
varyings[i] = prefix_removed;
|
|
finished = false;
|
|
|
|
// don't try more than one at once (just in case)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(status == 0)
|
|
{
|
|
char buffer[1025] = {0};
|
|
gl.glGetProgramInfoLog(vsProg, 1024, NULL, buffer);
|
|
RDCERR("Failed to fix-up. Link error making xfb vs program: %s", buffer);
|
|
m_PostVSData[eventID] = GLPostVSData();
|
|
return;
|
|
}
|
|
|
|
// make a pipeline to contain just the vertex shader
|
|
GLuint vsFeedbackPipe = 0;
|
|
gl.glGenProgramPipelines(1, &vsFeedbackPipe);
|
|
|
|
// bind the separable vertex program to it
|
|
gl.glUseProgramStages(vsFeedbackPipe, eGL_VERTEX_SHADER_BIT, vsProg);
|
|
|
|
// copy across any uniform values, bindings etc from the real program containing
|
|
// the vertex stage
|
|
CopyProgramUniforms(gl.GetHookset(), vsProgSrc, vsProg);
|
|
|
|
// bind our program and do the feedback draw
|
|
gl.glUseProgram(0);
|
|
gl.glBindProgramPipeline(vsFeedbackPipe);
|
|
|
|
gl.glBindTransformFeedback(eGL_TRANSFORM_FEEDBACK, DebugData.feedbackObj);
|
|
|
|
// need to rebind this here because of an AMD bug that seems to ignore the buffer
|
|
// bindings in the feedback object - or at least it errors if the default feedback
|
|
// object has no buffers bound. Fortunately the state is still object-local so
|
|
// we don't have to restore the buffer binding on the default feedback object.
|
|
gl.glBindBufferBase(eGL_TRANSFORM_FEEDBACK_BUFFER, 0, DebugData.feedbackBuffer);
|
|
|
|
GLuint idxBuf = 0;
|
|
|
|
gl.glBeginQuery(eGL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, DebugData.feedbackQueries[0]);
|
|
gl.glBeginTransformFeedback(eGL_POINTS);
|
|
|
|
if((drawcall->flags & eDraw_UseIBuffer) == 0)
|
|
{
|
|
if(drawcall->flags & eDraw_Instanced)
|
|
{
|
|
if(HasExt[ARB_base_instance])
|
|
{
|
|
gl.glDrawArraysInstancedBaseInstance(eGL_POINTS, drawcall->vertexOffset, drawcall->numIndices,
|
|
drawcall->numInstances, drawcall->instanceOffset);
|
|
}
|
|
else
|
|
{
|
|
gl.glDrawArraysInstanced(eGL_POINTS, drawcall->vertexOffset, drawcall->numIndices,
|
|
drawcall->numInstances);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gl.glDrawArrays(eGL_POINTS, drawcall->vertexOffset, drawcall->numIndices);
|
|
}
|
|
}
|
|
else // drawcall is indexed
|
|
{
|
|
ResourceId idxId = rm->GetID(BufferRes(NULL, elArrayBuffer));
|
|
|
|
vector<byte> idxdata;
|
|
GetBufferData(idxId, drawcall->indexOffset * drawcall->indexByteWidth,
|
|
drawcall->numIndices * drawcall->indexByteWidth, idxdata);
|
|
|
|
vector<uint32_t> indices;
|
|
|
|
uint8_t *idx8 = (uint8_t *)&idxdata[0];
|
|
uint16_t *idx16 = (uint16_t *)&idxdata[0];
|
|
uint32_t *idx32 = (uint32_t *)&idxdata[0];
|
|
|
|
// only read as many indices as were available in the buffer
|
|
uint32_t numIndices =
|
|
RDCMIN(uint32_t(idxdata.size() / drawcall->indexByteWidth), drawcall->numIndices);
|
|
|
|
// grab all unique vertex indices referenced
|
|
for(uint32_t i = 0; i < numIndices; i++)
|
|
{
|
|
uint32_t i32 = 0;
|
|
if(drawcall->indexByteWidth == 1)
|
|
i32 = uint32_t(idx8[i]);
|
|
else if(drawcall->indexByteWidth == 2)
|
|
i32 = uint32_t(idx16[i]);
|
|
else if(drawcall->indexByteWidth == 4)
|
|
i32 = idx32[i];
|
|
|
|
auto it = std::lower_bound(indices.begin(), indices.end(), i32);
|
|
|
|
if(it != indices.end() && *it == i32)
|
|
continue;
|
|
|
|
indices.insert(it, i32);
|
|
}
|
|
|
|
// if we read out of bounds, we'll also have a 0 index being referenced
|
|
// (as 0 is read). Don't insert 0 if we already have 0 though
|
|
if(numIndices < drawcall->numIndices && (indices.empty() || indices[0] != 0))
|
|
indices.insert(indices.begin(), 0);
|
|
|
|
// An index buffer could be something like: 500, 501, 502, 501, 503, 502
|
|
// in which case we can't use the existing index buffer without filling 499 slots of vertex
|
|
// data with padding. Instead we rebase the indices based on the smallest vertex so it becomes
|
|
// 0, 1, 2, 1, 3, 2 and then that matches our stream-out'd buffer.
|
|
//
|
|
// Note that there could also be gaps, like: 500, 501, 502, 510, 511, 512
|
|
// which would become 0, 1, 2, 3, 4, 5 and so the old index buffer would no longer be valid.
|
|
// We just stream-out a tightly packed list of unique indices, and then remap the index buffer
|
|
// so that what did point to 500 points to 0 (accounting for rebasing), and what did point
|
|
// to 510 now points to 3 (accounting for the unique sort).
|
|
|
|
// we use a map here since the indices may be sparse. Especially considering if an index
|
|
// is 'invalid' like 0xcccccccc then we don't want an array of 3.4 billion entries.
|
|
map<uint32_t, size_t> indexRemap;
|
|
for(size_t i = 0; i < indices.size(); i++)
|
|
{
|
|
// by definition, this index will only appear once in indices[]
|
|
indexRemap[indices[i]] = i;
|
|
}
|
|
|
|
// generate a temporary index buffer with our 'unique index set' indices,
|
|
// so we can transform feedback each referenced vertex once
|
|
GLuint indexSetBuffer = 0;
|
|
gl.glGenBuffers(1, &indexSetBuffer);
|
|
gl.glBindBuffer(eGL_ELEMENT_ARRAY_BUFFER, indexSetBuffer);
|
|
gl.glNamedBufferDataEXT(indexSetBuffer, sizeof(uint32_t) * indices.size(), &indices[0],
|
|
eGL_STATIC_DRAW);
|
|
|
|
if(drawcall->flags & eDraw_Instanced)
|
|
{
|
|
if(HasExt[ARB_base_instance])
|
|
{
|
|
gl.glDrawElementsInstancedBaseVertexBaseInstance(
|
|
eGL_POINTS, (GLsizei)indices.size(), eGL_UNSIGNED_INT, NULL, drawcall->numInstances,
|
|
drawcall->baseVertex, drawcall->instanceOffset);
|
|
}
|
|
else
|
|
{
|
|
gl.glDrawElementsInstancedBaseVertex(eGL_POINTS, (GLsizei)indices.size(), eGL_UNSIGNED_INT,
|
|
NULL, drawcall->numInstances, drawcall->baseVertex);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gl.glDrawElementsBaseVertex(eGL_POINTS, (GLsizei)indices.size(), eGL_UNSIGNED_INT, NULL,
|
|
drawcall->baseVertex);
|
|
}
|
|
|
|
// delete the buffer, we don't need it anymore
|
|
gl.glBindBuffer(eGL_ELEMENT_ARRAY_BUFFER, elArrayBuffer);
|
|
gl.glDeleteBuffers(1, &indexSetBuffer);
|
|
|
|
// rebase existing index buffer to point from 0 onwards (which will index into our
|
|
// stream-out'd vertex buffer)
|
|
if(drawcall->indexByteWidth == 1)
|
|
{
|
|
for(uint32_t i = 0; i < numIndices; i++)
|
|
idx8[i] = uint8_t(indexRemap[idx8[i]]);
|
|
}
|
|
else if(drawcall->indexByteWidth == 2)
|
|
{
|
|
for(uint32_t i = 0; i < numIndices; i++)
|
|
idx16[i] = uint16_t(indexRemap[idx16[i]]);
|
|
}
|
|
else
|
|
{
|
|
for(uint32_t i = 0; i < numIndices; i++)
|
|
idx32[i] = uint32_t(indexRemap[idx32[i]]);
|
|
}
|
|
|
|
// make the index buffer that can be used to render this postvs data - the original
|
|
// indices, repointed (since we transform feedback to the start of our feedback
|
|
// buffer and only tightly packed unique indices).
|
|
if(!idxdata.empty())
|
|
{
|
|
gl.glGenBuffers(1, &idxBuf);
|
|
gl.glBindBuffer(eGL_ELEMENT_ARRAY_BUFFER, idxBuf);
|
|
gl.glNamedBufferDataEXT(idxBuf, (GLsizeiptr)idxdata.size(), &idxdata[0], eGL_STATIC_DRAW);
|
|
}
|
|
|
|
// restore previous element array buffer binding
|
|
gl.glBindBuffer(eGL_ELEMENT_ARRAY_BUFFER, elArrayBuffer);
|
|
}
|
|
|
|
gl.glEndTransformFeedback();
|
|
gl.glEndQuery(eGL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
|
|
|
|
bool error = false;
|
|
|
|
// this should be the same as the draw size
|
|
GLuint primsWritten = 0;
|
|
gl.glGetQueryObjectuiv(DebugData.feedbackQueries[0], eGL_QUERY_RESULT, &primsWritten);
|
|
|
|
if(primsWritten == 0)
|
|
{
|
|
// we bailed out much earlier if this was a draw of 0 verts
|
|
RDCERR("No primitives written - but we must have had some number of vertices in the draw");
|
|
error = true;
|
|
}
|
|
|
|
// get buffer data from buffer attached to feedback object
|
|
float *data = (float *)gl.glMapNamedBufferEXT(DebugData.feedbackBuffer, eGL_READ_ONLY);
|
|
|
|
if(data == NULL)
|
|
{
|
|
gl.glUnmapNamedBufferEXT(DebugData.feedbackBuffer);
|
|
RDCERR("Couldn't map feedback buffer!");
|
|
error = true;
|
|
}
|
|
|
|
if(error)
|
|
{
|
|
// delete temporary pipelines we made
|
|
gl.glDeleteProgramPipelines(1, &vsFeedbackPipe);
|
|
|
|
// restore replay state we trashed
|
|
gl.glUseProgram(rs.Program);
|
|
gl.glBindProgramPipeline(rs.Pipeline);
|
|
|
|
gl.glBindBuffer(eGL_ARRAY_BUFFER, rs.BufferBindings[GLRenderState::eBufIdx_Array]);
|
|
gl.glBindBuffer(eGL_ELEMENT_ARRAY_BUFFER, elArrayBuffer);
|
|
|
|
gl.glBindTransformFeedback(eGL_TRANSFORM_FEEDBACK, rs.FeedbackObj);
|
|
|
|
if(!rs.Enabled[GLRenderState::eEnabled_RasterizerDiscard])
|
|
gl.glDisable(eGL_RASTERIZER_DISCARD);
|
|
else
|
|
gl.glEnable(eGL_RASTERIZER_DISCARD);
|
|
|
|
m_PostVSData[eventID] = GLPostVSData();
|
|
return;
|
|
}
|
|
|
|
// create a buffer with this data, for future use (typed to ARRAY_BUFFER so we
|
|
// can render from it to display previews).
|
|
GLuint vsoutBuffer = 0;
|
|
gl.glGenBuffers(1, &vsoutBuffer);
|
|
gl.glBindBuffer(eGL_ARRAY_BUFFER, vsoutBuffer);
|
|
gl.glNamedBufferDataEXT(vsoutBuffer, stride * primsWritten, data, eGL_STATIC_DRAW);
|
|
|
|
byte *byteData = (byte *)data;
|
|
|
|
float nearp = 0.1f;
|
|
float farp = 100.0f;
|
|
|
|
Vec4f *pos0 = (Vec4f *)byteData;
|
|
|
|
bool found = false;
|
|
|
|
for(GLuint i = 1; posidx != -1 && i < primsWritten; i++)
|
|
{
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
// derive near/far, assuming a standard perspective matrix
|
|
//
|
|
// the transformation from from pre-projection {Z,W} to post-projection {Z,W}
|
|
// is linear. So we can say Zpost = Zpre*m + c . Here we assume Wpre = 1
|
|
// and we know Wpost = Zpre from the perspective matrix.
|
|
// we can then see from the perspective matrix that
|
|
// m = F/(F-N)
|
|
// c = -(F*N)/(F-N)
|
|
//
|
|
// with re-arranging and substitution, we then get:
|
|
// N = -c/m
|
|
// F = c/(1-m)
|
|
//
|
|
// so if we can derive m and c then we can determine N and F. We can do this with
|
|
// two points, and we pick them reasonably distinct on z to reduce floating-point
|
|
// error
|
|
|
|
Vec4f *pos = (Vec4f *)(byteData + i * stride);
|
|
|
|
if(fabs(pos->w - pos0->w) > 0.01f && fabs(pos->z - pos0->z) > 0.01f)
|
|
{
|
|
Vec2f A(pos0->w, pos0->z);
|
|
Vec2f B(pos->w, pos->z);
|
|
|
|
float m = (B.y - A.y) / (B.x - A.x);
|
|
float c = B.y - B.x * m;
|
|
|
|
if(m == 1.0f)
|
|
continue;
|
|
|
|
nearp = -c / m;
|
|
farp = c / (1 - m);
|
|
|
|
found = true;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
// if we didn't find anything, all z's and w's were identical.
|
|
// If the z is positive and w greater for the first element then
|
|
// we detect this projection as reversed z with infinite far plane
|
|
if(!found && pos0->z > 0.0f && pos0->w > pos0->z)
|
|
{
|
|
nearp = pos0->z;
|
|
farp = FLT_MAX;
|
|
}
|
|
|
|
gl.glUnmapNamedBufferEXT(DebugData.feedbackBuffer);
|
|
|
|
// store everything out to the PostVS data cache
|
|
m_PostVSData[eventID].vsin.topo = drawcall->topology;
|
|
m_PostVSData[eventID].vsout.buf = vsoutBuffer;
|
|
m_PostVSData[eventID].vsout.vertStride = stride;
|
|
m_PostVSData[eventID].vsout.nearPlane = nearp;
|
|
m_PostVSData[eventID].vsout.farPlane = farp;
|
|
|
|
m_PostVSData[eventID].vsout.useIndices = (drawcall->flags & eDraw_UseIBuffer) > 0;
|
|
m_PostVSData[eventID].vsout.numVerts = drawcall->numIndices;
|
|
|
|
m_PostVSData[eventID].vsout.instStride = 0;
|
|
if(drawcall->flags & eDraw_Instanced)
|
|
m_PostVSData[eventID].vsout.instStride =
|
|
(stride * primsWritten) / RDCMAX(1U, drawcall->numInstances);
|
|
|
|
m_PostVSData[eventID].vsout.idxBuf = 0;
|
|
m_PostVSData[eventID].vsout.idxByteWidth = drawcall->indexByteWidth;
|
|
if(m_PostVSData[eventID].vsout.useIndices && idxBuf)
|
|
{
|
|
m_PostVSData[eventID].vsout.idxBuf = idxBuf;
|
|
}
|
|
|
|
m_PostVSData[eventID].vsout.hasPosOut = posidx >= 0;
|
|
|
|
m_PostVSData[eventID].vsout.topo = drawcall->topology;
|
|
|
|
// set vsProg back to no varyings, for future use
|
|
gl.glTransformFeedbackVaryings(vsProg, 0, NULL, eGL_INTERLEAVED_ATTRIBS);
|
|
gl.glLinkProgram(vsProg);
|
|
|
|
GLuint lastFeedbackPipe = 0;
|
|
|
|
if(tesProg || gsProg)
|
|
{
|
|
GLuint lastProg = gsProg;
|
|
ShaderReflection *lastRefl = gsRefl;
|
|
|
|
if(lastProg == 0)
|
|
{
|
|
lastProg = tesProg;
|
|
lastRefl = tesRefl;
|
|
}
|
|
|
|
RDCASSERT(lastProg && lastRefl);
|
|
|
|
varyings.clear();
|
|
|
|
stride = 0;
|
|
posidx = -1;
|
|
|
|
for(int32_t i = 0; i < lastRefl->OutputSig.count; i++)
|
|
{
|
|
const char *name = lastRefl->OutputSig[i].varName.elems;
|
|
int32_t len = lastRefl->OutputSig[i].varName.count;
|
|
|
|
bool include = true;
|
|
|
|
// for matrices with names including :row1, :row2 etc we only include :row0
|
|
// as a varying (but increment the stride for all rows to account for the space)
|
|
// and modify the name to remove the :row0 part
|
|
const char *colon = strchr(name, ':');
|
|
if(colon)
|
|
{
|
|
if(name[len - 1] != '0')
|
|
{
|
|
include = false;
|
|
}
|
|
else
|
|
{
|
|
matrixVaryings.push_back(string(name, colon));
|
|
name = matrixVaryings.back().c_str();
|
|
}
|
|
}
|
|
|
|
if(include)
|
|
varyings.push_back(name);
|
|
|
|
if(lastRefl->OutputSig[i].systemValue == eAttr_Position)
|
|
posidx = int32_t(varyings.size()) - 1;
|
|
|
|
stride += sizeof(float) * lastRefl->OutputSig[i].compCount;
|
|
}
|
|
|
|
// shift position attribute up to first, keeping order otherwise
|
|
// the same
|
|
if(posidx > 0)
|
|
{
|
|
const char *pos = varyings[posidx];
|
|
varyings.erase(varyings.begin() + posidx);
|
|
varyings.insert(varyings.begin(), pos);
|
|
}
|
|
|
|
// see above for the justification/explanation of this monstrosity.
|
|
|
|
status = 0;
|
|
finished = false;
|
|
for(;;)
|
|
{
|
|
// specify current varyings & relink
|
|
gl.glTransformFeedbackVaryings(lastProg, (GLsizei)varyings.size(), &varyings[0],
|
|
eGL_INTERLEAVED_ATTRIBS);
|
|
gl.glLinkProgram(lastProg);
|
|
|
|
gl.glGetProgramiv(lastProg, eGL_LINK_STATUS, &status);
|
|
|
|
// all good! Hopefully we'll mostly hit this
|
|
if(status == 1)
|
|
break;
|
|
|
|
// if finished is true, this was our last attempt - there are no
|
|
// more fixups possible
|
|
if(finished)
|
|
break;
|
|
|
|
char buffer[1025] = {0};
|
|
gl.glGetProgramInfoLog(lastProg, 1024, NULL, buffer);
|
|
|
|
// assume we're finished and can't retry any more after this.
|
|
// if we find a potential 'fixup' we'll set this back to false
|
|
finished = true;
|
|
|
|
// see if any of our current varyings are present in the buffer string
|
|
for(size_t i = 0; i < varyings.size(); i++)
|
|
{
|
|
if(strstr(buffer, varyings[i]))
|
|
{
|
|
const char *prefix_removed = strchr(varyings[i], '.');
|
|
|
|
// does it contain a prefix?
|
|
if(prefix_removed)
|
|
{
|
|
prefix_removed++; // now this is our string without the prefix
|
|
|
|
// first check this won't cause a duplicate - if it does, we have to try something else
|
|
bool duplicate = false;
|
|
for(size_t j = 0; j < varyings.size(); j++)
|
|
{
|
|
if(!strcmp(varyings[j], prefix_removed))
|
|
{
|
|
duplicate = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!duplicate)
|
|
{
|
|
// we'll attempt this fixup
|
|
RDCWARN("Attempting XFB varying fixup, subst '%s' for '%s'", varyings[i],
|
|
prefix_removed);
|
|
varyings[i] = prefix_removed;
|
|
finished = false;
|
|
|
|
// don't try more than one at once (just in case)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(status == 0)
|
|
{
|
|
char buffer[1025] = {0};
|
|
gl.glGetProgramInfoLog(lastProg, 1024, NULL, buffer);
|
|
RDCERR("Failed to fix-up. Link error making xfb last program: %s", buffer);
|
|
}
|
|
else
|
|
{
|
|
// make a pipeline to contain all the vertex processing shaders
|
|
gl.glGenProgramPipelines(1, &lastFeedbackPipe);
|
|
|
|
// bind the separable vertex program to it
|
|
gl.glUseProgramStages(lastFeedbackPipe, eGL_VERTEX_SHADER_BIT, vsProg);
|
|
|
|
// copy across any uniform values, bindings etc from the real program containing
|
|
// the vertex stage
|
|
CopyProgramUniforms(gl.GetHookset(), vsProgSrc, vsProg);
|
|
|
|
// if tessellation is enabled, bind & copy uniforms. Note, control shader is optional
|
|
// independent of eval shader (default values are used for the tessellation levels).
|
|
if(tcsProg)
|
|
{
|
|
gl.glUseProgramStages(lastFeedbackPipe, eGL_TESS_CONTROL_SHADER_BIT, tcsProg);
|
|
CopyProgramUniforms(gl.GetHookset(), tcsProgSrc, tcsProg);
|
|
}
|
|
if(tesProg)
|
|
{
|
|
gl.glUseProgramStages(lastFeedbackPipe, eGL_TESS_EVALUATION_SHADER_BIT, tesProg);
|
|
CopyProgramUniforms(gl.GetHookset(), tesProgSrc, tesProg);
|
|
}
|
|
|
|
// if we have a geometry shader, bind & copy uniforms
|
|
if(gsProg)
|
|
{
|
|
gl.glUseProgramStages(lastFeedbackPipe, eGL_GEOMETRY_SHADER_BIT, gsProg);
|
|
CopyProgramUniforms(gl.GetHookset(), gsProgSrc, gsProg);
|
|
}
|
|
|
|
// bind our program and do the feedback draw
|
|
gl.glUseProgram(0);
|
|
gl.glBindProgramPipeline(lastFeedbackPipe);
|
|
|
|
gl.glBindTransformFeedback(eGL_TRANSFORM_FEEDBACK, DebugData.feedbackObj);
|
|
|
|
// need to rebind this here because of an AMD bug that seems to ignore the buffer
|
|
// bindings in the feedback object - or at least it errors if the default feedback
|
|
// object has no buffers bound. Fortunately the state is still object-local so
|
|
// we don't have to restore the buffer binding on the default feedback object.
|
|
gl.glBindBufferBase(eGL_TRANSFORM_FEEDBACK_BUFFER, 0, DebugData.feedbackBuffer);
|
|
|
|
idxBuf = 0;
|
|
|
|
GLenum shaderOutMode = eGL_TRIANGLES;
|
|
GLenum lastOutTopo = eGL_TRIANGLES;
|
|
|
|
if(lastProg == gsProg)
|
|
{
|
|
gl.glGetProgramiv(gsProg, eGL_GEOMETRY_OUTPUT_TYPE, (GLint *)&shaderOutMode);
|
|
if(shaderOutMode == eGL_TRIANGLE_STRIP)
|
|
lastOutTopo = eGL_TRIANGLES;
|
|
else if(shaderOutMode == eGL_LINE_STRIP)
|
|
lastOutTopo = eGL_LINES;
|
|
else if(shaderOutMode == eGL_POINTS)
|
|
lastOutTopo = eGL_POINTS;
|
|
}
|
|
else if(lastProg == tesProg)
|
|
{
|
|
gl.glGetProgramiv(tesProg, eGL_TESS_GEN_MODE, (GLint *)&shaderOutMode);
|
|
if(shaderOutMode == eGL_QUADS)
|
|
lastOutTopo = eGL_TRIANGLES;
|
|
else if(shaderOutMode == eGL_ISOLINES)
|
|
lastOutTopo = eGL_LINES;
|
|
else if(shaderOutMode == eGL_TRIANGLES)
|
|
lastOutTopo = eGL_TRIANGLES;
|
|
}
|
|
|
|
GLenum drawtopo = MakeGLPrimitiveTopology(drawcall->topology);
|
|
|
|
GLenum idxType = eGL_UNSIGNED_BYTE;
|
|
if(drawcall->indexByteWidth == 2)
|
|
idxType = eGL_UNSIGNED_SHORT;
|
|
else if(drawcall->indexByteWidth == 4)
|
|
idxType = eGL_UNSIGNED_INT;
|
|
|
|
// instanced draws must be replayed one at a time so we can record the number of primitives
|
|
// from
|
|
// each drawcall, as due to expansion this can vary per-instance.
|
|
if(drawcall->flags & eDraw_Instanced)
|
|
{
|
|
// if there is only one instance it's a trivial case and we don't need to bother with the
|
|
// expensive path
|
|
if(drawcall->numInstances > 1)
|
|
{
|
|
// ensure we have enough queries
|
|
uint32_t curSize = (uint32_t)DebugData.feedbackQueries.size();
|
|
if(curSize < drawcall->numInstances)
|
|
{
|
|
DebugData.feedbackQueries.resize(drawcall->numInstances);
|
|
gl.glGenQueries(drawcall->numInstances - curSize,
|
|
DebugData.feedbackQueries.data() + curSize);
|
|
}
|
|
|
|
// do incremental draws to get the output size. We have to do this O(N^2) style because
|
|
// there's no way to replay only a single instance. We have to replay 1, 2, 3, ... N
|
|
// instances and count the total number of verts each time, then we can see from the
|
|
// difference how much each instance wrote.
|
|
for(uint32_t inst = 1; inst <= drawcall->numInstances; inst++)
|
|
{
|
|
gl.glBindBufferBase(eGL_TRANSFORM_FEEDBACK_BUFFER, 0, DebugData.feedbackBuffer);
|
|
gl.glBeginQuery(eGL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN,
|
|
DebugData.feedbackQueries[inst - 1]);
|
|
gl.glBeginTransformFeedback(lastOutTopo);
|
|
|
|
if((drawcall->flags & eDraw_UseIBuffer) == 0)
|
|
{
|
|
if(HasExt[ARB_base_instance])
|
|
{
|
|
gl.glDrawArraysInstancedBaseInstance(drawtopo, drawcall->vertexOffset,
|
|
drawcall->numIndices, inst,
|
|
drawcall->instanceOffset);
|
|
}
|
|
else
|
|
{
|
|
gl.glDrawArraysInstanced(drawtopo, drawcall->vertexOffset, drawcall->numIndices,
|
|
inst);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(HasExt[ARB_base_instance])
|
|
{
|
|
gl.glDrawElementsInstancedBaseVertexBaseInstance(
|
|
drawtopo, drawcall->numIndices, idxType,
|
|
(const void *)uintptr_t(drawcall->indexOffset * drawcall->indexByteWidth), inst,
|
|
drawcall->baseVertex, drawcall->instanceOffset);
|
|
}
|
|
else
|
|
{
|
|
gl.glDrawElementsInstancedBaseVertex(
|
|
drawtopo, drawcall->numIndices, idxType,
|
|
(const void *)uintptr_t(drawcall->indexOffset * drawcall->indexByteWidth), inst,
|
|
drawcall->baseVertex);
|
|
}
|
|
}
|
|
|
|
gl.glEndTransformFeedback();
|
|
gl.glEndQuery(eGL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gl.glBeginQuery(eGL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, DebugData.feedbackQueries[0]);
|
|
gl.glBeginTransformFeedback(lastOutTopo);
|
|
|
|
if((drawcall->flags & eDraw_UseIBuffer) == 0)
|
|
{
|
|
if(HasExt[ARB_base_instance])
|
|
{
|
|
gl.glDrawArraysInstancedBaseInstance(drawtopo, drawcall->vertexOffset,
|
|
drawcall->numIndices, drawcall->numInstances,
|
|
drawcall->instanceOffset);
|
|
}
|
|
else
|
|
{
|
|
gl.glDrawArraysInstanced(drawtopo, drawcall->vertexOffset, drawcall->numIndices,
|
|
drawcall->numInstances);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(HasExt[ARB_base_instance])
|
|
{
|
|
gl.glDrawElementsInstancedBaseVertexBaseInstance(
|
|
drawtopo, drawcall->numIndices, idxType,
|
|
(const void *)uintptr_t(drawcall->indexOffset * drawcall->indexByteWidth),
|
|
drawcall->numInstances, drawcall->baseVertex, drawcall->instanceOffset);
|
|
}
|
|
else
|
|
{
|
|
gl.glDrawElementsInstancedBaseVertex(
|
|
drawtopo, drawcall->numIndices, idxType,
|
|
(const void *)uintptr_t(drawcall->indexOffset * drawcall->indexByteWidth),
|
|
drawcall->numInstances, drawcall->baseVertex);
|
|
}
|
|
}
|
|
|
|
gl.glEndTransformFeedback();
|
|
gl.glEndQuery(eGL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gl.glBeginQuery(eGL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, DebugData.feedbackQueries[0]);
|
|
gl.glBeginTransformFeedback(lastOutTopo);
|
|
|
|
if((drawcall->flags & eDraw_UseIBuffer) == 0)
|
|
{
|
|
gl.glDrawArrays(drawtopo, drawcall->vertexOffset, drawcall->numIndices);
|
|
}
|
|
else
|
|
{
|
|
gl.glDrawElementsBaseVertex(
|
|
drawtopo, drawcall->numIndices, idxType,
|
|
(const void *)uintptr_t(drawcall->indexOffset * drawcall->indexByteWidth),
|
|
drawcall->baseVertex);
|
|
}
|
|
|
|
gl.glEndTransformFeedback();
|
|
gl.glEndQuery(eGL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
|
|
}
|
|
|
|
std::vector<GLPostVSData::InstData> instData;
|
|
|
|
if((drawcall->flags & eDraw_Instanced) && drawcall->numInstances > 1)
|
|
{
|
|
uint64_t prevVertCount = 0;
|
|
|
|
for(uint32_t inst = 0; inst < drawcall->numInstances; inst++)
|
|
{
|
|
gl.glGetQueryObjectuiv(DebugData.feedbackQueries[inst], eGL_QUERY_RESULT, &primsWritten);
|
|
|
|
uint32_t vertCount = 3 * primsWritten;
|
|
|
|
GLPostVSData::InstData d;
|
|
d.numVerts = uint32_t(vertCount - prevVertCount);
|
|
d.bufOffset = uint32_t(stride * prevVertCount);
|
|
prevVertCount = vertCount;
|
|
|
|
instData.push_back(d);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
primsWritten = 0;
|
|
gl.glGetQueryObjectuiv(DebugData.feedbackQueries[0], eGL_QUERY_RESULT, &primsWritten);
|
|
}
|
|
|
|
error = false;
|
|
|
|
if(primsWritten == 0)
|
|
{
|
|
RDCWARN("No primitives written by last vertex processing stage");
|
|
error = true;
|
|
}
|
|
|
|
// get buffer data from buffer attached to feedback object
|
|
data = (float *)gl.glMapNamedBufferEXT(DebugData.feedbackBuffer, eGL_READ_ONLY);
|
|
|
|
if(data == NULL)
|
|
{
|
|
gl.glUnmapNamedBufferEXT(DebugData.feedbackBuffer);
|
|
RDCERR("Couldn't map feedback buffer!");
|
|
error = true;
|
|
}
|
|
|
|
if(error)
|
|
{
|
|
// delete temporary pipelines we made
|
|
gl.glDeleteProgramPipelines(1, &vsFeedbackPipe);
|
|
if(lastFeedbackPipe)
|
|
gl.glDeleteProgramPipelines(1, &lastFeedbackPipe);
|
|
|
|
// restore replay state we trashed
|
|
gl.glUseProgram(rs.Program);
|
|
gl.glBindProgramPipeline(rs.Pipeline);
|
|
|
|
gl.glBindBuffer(eGL_ARRAY_BUFFER, rs.BufferBindings[GLRenderState::eBufIdx_Array]);
|
|
gl.glBindBuffer(eGL_ELEMENT_ARRAY_BUFFER, elArrayBuffer);
|
|
|
|
gl.glBindTransformFeedback(eGL_TRANSFORM_FEEDBACK, rs.FeedbackObj);
|
|
|
|
if(!rs.Enabled[GLRenderState::eEnabled_RasterizerDiscard])
|
|
gl.glDisable(eGL_RASTERIZER_DISCARD);
|
|
else
|
|
gl.glEnable(eGL_RASTERIZER_DISCARD);
|
|
|
|
return;
|
|
}
|
|
|
|
if(lastProg == tesProg)
|
|
{
|
|
// primitive counter is the number of primitives, not vertices
|
|
if(shaderOutMode == eGL_TRIANGLES ||
|
|
shaderOutMode == eGL_QUADS) // query for quads returns # triangles
|
|
m_PostVSData[eventID].gsout.numVerts = primsWritten * 3;
|
|
else if(shaderOutMode == eGL_ISOLINES)
|
|
m_PostVSData[eventID].gsout.numVerts = primsWritten * 2;
|
|
}
|
|
else if(lastProg == gsProg)
|
|
{
|
|
// primitive counter is the number of primitives, not vertices
|
|
if(shaderOutMode == eGL_POINTS)
|
|
m_PostVSData[eventID].gsout.numVerts = primsWritten;
|
|
else if(shaderOutMode == eGL_LINE_STRIP)
|
|
m_PostVSData[eventID].gsout.numVerts = primsWritten * 2;
|
|
else if(shaderOutMode == eGL_TRIANGLE_STRIP)
|
|
m_PostVSData[eventID].gsout.numVerts = primsWritten * 3;
|
|
}
|
|
|
|
// create a buffer with this data, for future use (typed to ARRAY_BUFFER so we
|
|
// can render from it to display previews).
|
|
GLuint lastoutBuffer = 0;
|
|
gl.glGenBuffers(1, &lastoutBuffer);
|
|
gl.glBindBuffer(eGL_ARRAY_BUFFER, lastoutBuffer);
|
|
gl.glNamedBufferDataEXT(lastoutBuffer, stride * m_PostVSData[eventID].gsout.numVerts, data,
|
|
eGL_STATIC_DRAW);
|
|
|
|
byteData = (byte *)data;
|
|
|
|
nearp = 0.1f;
|
|
farp = 100.0f;
|
|
|
|
pos0 = (Vec4f *)byteData;
|
|
|
|
found = false;
|
|
|
|
for(uint32_t i = 1; posidx != -1 && i < m_PostVSData[eventID].gsout.numVerts; i++)
|
|
{
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
// derive near/far, assuming a standard perspective matrix
|
|
//
|
|
// the transformation from from pre-projection {Z,W} to post-projection {Z,W}
|
|
// is linear. So we can say Zpost = Zpre*m + c . Here we assume Wpre = 1
|
|
// and we know Wpost = Zpre from the perspective matrix.
|
|
// we can then see from the perspective matrix that
|
|
// m = F/(F-N)
|
|
// c = -(F*N)/(F-N)
|
|
//
|
|
// with re-arranging and substitution, we then get:
|
|
// N = -c/m
|
|
// F = c/(1-m)
|
|
//
|
|
// so if we can derive m and c then we can determine N and F. We can do this with
|
|
// two points, and we pick them reasonably distinct on z to reduce floating-point
|
|
// error
|
|
|
|
Vec4f *pos = (Vec4f *)(byteData + i * stride);
|
|
|
|
if(fabs(pos->w - pos0->w) > 0.01f && fabs(pos->z - pos0->z) > 0.01f)
|
|
{
|
|
Vec2f A(pos0->w, pos0->z);
|
|
Vec2f B(pos->w, pos->z);
|
|
|
|
float m = (B.y - A.y) / (B.x - A.x);
|
|
float c = B.y - B.x * m;
|
|
|
|
if(m == 1.0f)
|
|
continue;
|
|
|
|
nearp = -c / m;
|
|
farp = c / (1 - m);
|
|
|
|
found = true;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
// if we didn't find anything, all z's and w's were identical.
|
|
// If the z is positive and w greater for the first element then
|
|
// we detect this projection as reversed z with infinite far plane
|
|
if(!found && pos0->z > 0.0f && pos0->w > pos0->z)
|
|
{
|
|
nearp = pos0->z;
|
|
farp = FLT_MAX;
|
|
}
|
|
|
|
gl.glUnmapNamedBufferEXT(DebugData.feedbackBuffer);
|
|
|
|
// store everything out to the PostVS data cache
|
|
m_PostVSData[eventID].gsout.buf = lastoutBuffer;
|
|
m_PostVSData[eventID].gsout.instStride = 0;
|
|
if(drawcall->flags & eDraw_Instanced)
|
|
{
|
|
m_PostVSData[eventID].gsout.numVerts /= RDCMAX(1U, drawcall->numInstances);
|
|
m_PostVSData[eventID].gsout.instStride = stride * m_PostVSData[eventID].gsout.numVerts;
|
|
}
|
|
m_PostVSData[eventID].gsout.vertStride = stride;
|
|
m_PostVSData[eventID].gsout.nearPlane = nearp;
|
|
m_PostVSData[eventID].gsout.farPlane = farp;
|
|
|
|
m_PostVSData[eventID].gsout.useIndices = false;
|
|
|
|
m_PostVSData[eventID].gsout.hasPosOut = posidx >= 0;
|
|
|
|
m_PostVSData[eventID].gsout.idxBuf = 0;
|
|
m_PostVSData[eventID].gsout.idxByteWidth = 0;
|
|
|
|
m_PostVSData[eventID].gsout.topo = MakePrimitiveTopology(gl.GetHookset(), lastOutTopo);
|
|
|
|
m_PostVSData[eventID].gsout.instData = instData;
|
|
}
|
|
|
|
// set lastProg back to no varyings, for future use
|
|
gl.glTransformFeedbackVaryings(lastProg, 0, NULL, eGL_INTERLEAVED_ATTRIBS);
|
|
gl.glLinkProgram(lastProg);
|
|
}
|
|
|
|
// delete temporary pipelines we made
|
|
gl.glDeleteProgramPipelines(1, &vsFeedbackPipe);
|
|
if(lastFeedbackPipe)
|
|
gl.glDeleteProgramPipelines(1, &lastFeedbackPipe);
|
|
|
|
// restore replay state we trashed
|
|
gl.glUseProgram(rs.Program);
|
|
gl.glBindProgramPipeline(rs.Pipeline);
|
|
|
|
gl.glBindBuffer(eGL_ARRAY_BUFFER, rs.BufferBindings[GLRenderState::eBufIdx_Array]);
|
|
gl.glBindBuffer(eGL_ELEMENT_ARRAY_BUFFER, elArrayBuffer);
|
|
|
|
gl.glBindTransformFeedback(eGL_TRANSFORM_FEEDBACK, rs.FeedbackObj);
|
|
|
|
if(!rs.Enabled[GLRenderState::eEnabled_RasterizerDiscard])
|
|
gl.glDisable(eGL_RASTERIZER_DISCARD);
|
|
else
|
|
gl.glEnable(eGL_RASTERIZER_DISCARD);
|
|
}
|
|
|
|
void GLReplay::InitPostVSBuffers(const vector<uint32_t> &passEvents)
|
|
{
|
|
uint32_t prev = 0;
|
|
|
|
// since we can always replay between drawcalls, just loop through all the events
|
|
// doing partial replays and calling InitPostVSBuffers for each
|
|
for(size_t i = 0; i < passEvents.size(); i++)
|
|
{
|
|
if(prev != passEvents[i])
|
|
{
|
|
m_pDriver->ReplayLog(prev, passEvents[i], eReplay_WithoutDraw);
|
|
|
|
prev = passEvents[i];
|
|
}
|
|
|
|
const FetchDrawcall *d = m_pDriver->GetDrawcall(passEvents[i]);
|
|
|
|
if(d)
|
|
InitPostVSBuffers(passEvents[i]);
|
|
}
|
|
}
|
|
|
|
MeshFormat GLReplay::GetPostVSBuffers(uint32_t eventID, uint32_t instID, MeshDataStage stage)
|
|
{
|
|
GLPostVSData postvs;
|
|
RDCEraseEl(postvs);
|
|
|
|
if(m_PostVSData.find(eventID) != m_PostVSData.end())
|
|
postvs = m_PostVSData[eventID];
|
|
|
|
const GLPostVSData::StageData &s = postvs.GetStage(stage);
|
|
|
|
MeshFormat ret;
|
|
|
|
if(s.useIndices && s.idxBuf)
|
|
ret.idxbuf = m_pDriver->GetResourceManager()->GetID(BufferRes(NULL, s.idxBuf));
|
|
else
|
|
ret.idxbuf = ResourceId();
|
|
ret.idxoffs = 0;
|
|
ret.idxByteWidth = s.idxByteWidth;
|
|
ret.baseVertex = 0;
|
|
|
|
if(s.buf)
|
|
ret.buf = m_pDriver->GetResourceManager()->GetID(BufferRes(NULL, s.buf));
|
|
else
|
|
ret.buf = ResourceId();
|
|
|
|
ret.offset = s.instStride * instID;
|
|
ret.stride = s.vertStride;
|
|
|
|
ret.compCount = 4;
|
|
ret.compByteWidth = 4;
|
|
ret.compType = eCompType_Float;
|
|
ret.specialFormat = eSpecial_Unknown;
|
|
|
|
ret.showAlpha = false;
|
|
ret.bgraOrder = false;
|
|
|
|
ret.topo = s.topo;
|
|
ret.numVerts = s.numVerts;
|
|
|
|
ret.unproject = s.hasPosOut;
|
|
ret.nearPlane = s.nearPlane;
|
|
ret.farPlane = s.farPlane;
|
|
|
|
if(instID < s.instData.size())
|
|
{
|
|
GLPostVSData::InstData inst = s.instData[instID];
|
|
|
|
ret.offset = inst.bufOffset;
|
|
ret.numVerts = inst.numVerts;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
FloatVector GLReplay::InterpretVertex(byte *data, uint32_t vert, const MeshDisplay &cfg, byte *end,
|
|
bool useidx, bool &valid)
|
|
{
|
|
FloatVector ret(0.0f, 0.0f, 0.0f, 1.0f);
|
|
|
|
if(useidx && m_HighlightCache.useidx)
|
|
{
|
|
if(vert >= (uint32_t)m_HighlightCache.indices.size())
|
|
{
|
|
valid = false;
|
|
return ret;
|
|
}
|
|
|
|
vert = m_HighlightCache.indices[vert];
|
|
}
|
|
|
|
data += vert * cfg.position.stride;
|
|
|
|
float *out = &ret.x;
|
|
|
|
ResourceFormat fmt;
|
|
fmt.compByteWidth = cfg.position.compByteWidth;
|
|
fmt.compCount = cfg.position.compCount;
|
|
fmt.compType = cfg.position.compType;
|
|
|
|
if(cfg.position.specialFormat == eSpecial_R10G10B10A2)
|
|
{
|
|
if(data + 4 >= end)
|
|
{
|
|
valid = false;
|
|
return ret;
|
|
}
|
|
|
|
Vec4f v = ConvertFromR10G10B10A2(*(uint32_t *)data);
|
|
ret.x = v.x;
|
|
ret.y = v.y;
|
|
ret.z = v.z;
|
|
ret.w = v.w;
|
|
return ret;
|
|
}
|
|
else if(cfg.position.specialFormat == eSpecial_R11G11B10)
|
|
{
|
|
if(data + 4 >= end)
|
|
{
|
|
valid = false;
|
|
return ret;
|
|
}
|
|
|
|
Vec3f v = ConvertFromR11G11B10(*(uint32_t *)data);
|
|
ret.x = v.x;
|
|
ret.y = v.y;
|
|
ret.z = v.z;
|
|
return ret;
|
|
}
|
|
|
|
if(data + cfg.position.compCount * cfg.position.compByteWidth > end)
|
|
{
|
|
valid = false;
|
|
return ret;
|
|
}
|
|
|
|
for(uint32_t i = 0; i < cfg.position.compCount; i++)
|
|
{
|
|
*out = ConvertComponent(fmt, data);
|
|
|
|
data += cfg.position.compByteWidth;
|
|
out++;
|
|
}
|
|
|
|
if(cfg.position.bgraOrder)
|
|
{
|
|
FloatVector reversed;
|
|
reversed.x = ret.z;
|
|
reversed.y = ret.y;
|
|
reversed.z = ret.x;
|
|
reversed.w = ret.w;
|
|
return reversed;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void GLReplay::RenderMesh(uint32_t eventID, const vector<MeshFormat> &secondaryDraws,
|
|
const MeshDisplay &cfg)
|
|
{
|
|
WrappedOpenGL &gl = *m_pDriver;
|
|
|
|
if(cfg.position.buf == ResourceId())
|
|
return;
|
|
|
|
MakeCurrentReplayContext(m_DebugCtx);
|
|
|
|
Matrix4f projMat =
|
|
Matrix4f::Perspective(90.0f, 0.1f, 100000.0f, DebugData.outWidth / DebugData.outHeight);
|
|
|
|
Matrix4f camMat = cfg.cam ? cfg.cam->GetMatrix() : Matrix4f::Identity();
|
|
|
|
Matrix4f ModelViewProj = projMat.Mul(camMat);
|
|
Matrix4f guessProjInv;
|
|
|
|
gl.glBindVertexArray(DebugData.meshVAO);
|
|
|
|
const MeshFormat *fmts[2] = {&cfg.position, &cfg.second};
|
|
|
|
GLenum topo = MakeGLPrimitiveTopology(cfg.position.topo);
|
|
|
|
GLuint prog = DebugData.meshProg;
|
|
|
|
MeshUBOData uboParams = {};
|
|
MeshUBOData *uboptr = NULL;
|
|
|
|
gl.glBindBufferBase(eGL_UNIFORM_BUFFER, 0, DebugData.UBOs[0]);
|
|
|
|
gl.glUseProgram(prog);
|
|
|
|
gl.glEnable(eGL_FRAMEBUFFER_SRGB);
|
|
|
|
if(cfg.position.unproject)
|
|
{
|
|
// the derivation of the projection matrix might not be right (hell, it could be an
|
|
// orthographic projection). But it'll be close enough likely.
|
|
Matrix4f guessProj =
|
|
cfg.position.farPlane != FLT_MAX
|
|
? Matrix4f::Perspective(cfg.fov, cfg.position.nearPlane, cfg.position.farPlane, cfg.aspect)
|
|
: Matrix4f::ReversePerspective(cfg.fov, cfg.position.nearPlane, cfg.aspect);
|
|
|
|
if(cfg.ortho)
|
|
{
|
|
guessProj = Matrix4f::Orthographic(cfg.position.nearPlane, cfg.position.farPlane);
|
|
}
|
|
|
|
guessProjInv = guessProj.Inverse();
|
|
|
|
ModelViewProj = projMat.Mul(camMat.Mul(guessProjInv));
|
|
}
|
|
|
|
uboParams.mvp = ModelViewProj;
|
|
uboParams.homogenousInput = cfg.position.unproject;
|
|
uboParams.pointSpriteSize = Vec2f(0.0f, 0.0f);
|
|
|
|
if(!secondaryDraws.empty())
|
|
{
|
|
uboParams.displayFormat = MESHDISPLAY_SOLID;
|
|
|
|
gl.glPolygonMode(eGL_FRONT_AND_BACK, eGL_LINE);
|
|
|
|
// secondary draws have to come from gl_Position which is float4
|
|
gl.glVertexAttribFormat(0, 4, eGL_FLOAT, GL_FALSE, 0);
|
|
gl.glEnableVertexAttribArray(0);
|
|
gl.glDisableVertexAttribArray(1);
|
|
|
|
for(size_t i = 0; i < secondaryDraws.size(); i++)
|
|
{
|
|
const MeshFormat &fmt = secondaryDraws[i];
|
|
|
|
if(fmt.buf != ResourceId())
|
|
{
|
|
uboParams.color =
|
|
Vec4f(fmt.meshColour.x, fmt.meshColour.y, fmt.meshColour.z, fmt.meshColour.w);
|
|
|
|
uboptr = (MeshUBOData *)gl.glMapBufferRange(eGL_UNIFORM_BUFFER, 0, sizeof(MeshUBOData),
|
|
GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
|
|
*uboptr = uboParams;
|
|
gl.glUnmapBuffer(eGL_UNIFORM_BUFFER);
|
|
|
|
GLuint vb = m_pDriver->GetResourceManager()->GetCurrentResource(fmt.buf).name;
|
|
gl.glBindVertexBuffer(0, vb, (GLintptr)fmt.offset, fmt.stride);
|
|
|
|
GLenum secondarytopo = MakeGLPrimitiveTopology(fmt.topo);
|
|
|
|
if(fmt.idxbuf != ResourceId())
|
|
{
|
|
GLuint ib = m_pDriver->GetResourceManager()->GetCurrentResource(fmt.idxbuf).name;
|
|
gl.glBindBuffer(eGL_ELEMENT_ARRAY_BUFFER, ib);
|
|
|
|
GLenum idxtype = eGL_UNSIGNED_BYTE;
|
|
if(fmt.idxByteWidth == 2)
|
|
idxtype = eGL_UNSIGNED_SHORT;
|
|
else if(fmt.idxByteWidth == 4)
|
|
idxtype = eGL_UNSIGNED_INT;
|
|
|
|
gl.glDrawElementsBaseVertex(secondarytopo, fmt.numVerts, idxtype,
|
|
(const void *)uintptr_t(fmt.idxoffs), fmt.baseVertex);
|
|
}
|
|
else
|
|
{
|
|
gl.glDrawArrays(secondarytopo, 0, fmt.numVerts);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for(uint32_t i = 0; i < 2; i++)
|
|
{
|
|
if(fmts[i]->buf == ResourceId())
|
|
continue;
|
|
|
|
if(fmts[i]->specialFormat != eSpecial_Unknown)
|
|
{
|
|
if(fmts[i]->specialFormat == eSpecial_R10G10B10A2)
|
|
{
|
|
if(fmts[i]->compType == eCompType_UInt)
|
|
gl.glVertexAttribIFormat(i, 4, eGL_UNSIGNED_INT_2_10_10_10_REV, 0);
|
|
if(fmts[i]->compType == eCompType_SInt)
|
|
gl.glVertexAttribIFormat(i, 4, eGL_INT_2_10_10_10_REV, 0);
|
|
}
|
|
else if(fmts[i]->specialFormat == eSpecial_R11G11B10)
|
|
{
|
|
gl.glVertexAttribFormat(i, 4, eGL_UNSIGNED_INT_10F_11F_11F_REV, GL_FALSE, 0);
|
|
}
|
|
else
|
|
{
|
|
RDCWARN("Unsupported special vertex attribute format: %x", fmts[i]->specialFormat);
|
|
}
|
|
}
|
|
else if(fmts[i]->compType == eCompType_Float || fmts[i]->compType == eCompType_UNorm ||
|
|
fmts[i]->compType == eCompType_SNorm)
|
|
{
|
|
GLenum fmttype = eGL_UNSIGNED_INT;
|
|
|
|
if(fmts[i]->compByteWidth == 4)
|
|
{
|
|
if(fmts[i]->compType == eCompType_Float)
|
|
fmttype = eGL_FLOAT;
|
|
else if(fmts[i]->compType == eCompType_UNorm)
|
|
fmttype = eGL_UNSIGNED_INT;
|
|
else if(fmts[i]->compType == eCompType_SNorm)
|
|
fmttype = eGL_INT;
|
|
}
|
|
else if(fmts[i]->compByteWidth == 2)
|
|
{
|
|
if(fmts[i]->compType == eCompType_Float)
|
|
fmttype = eGL_HALF_FLOAT;
|
|
else if(fmts[i]->compType == eCompType_UNorm)
|
|
fmttype = eGL_UNSIGNED_SHORT;
|
|
else if(fmts[i]->compType == eCompType_SNorm)
|
|
fmttype = eGL_SHORT;
|
|
}
|
|
else if(fmts[i]->compByteWidth == 1)
|
|
{
|
|
if(fmts[i]->compType == eCompType_UNorm)
|
|
fmttype = eGL_UNSIGNED_BYTE;
|
|
else if(fmts[i]->compType == eCompType_SNorm)
|
|
fmttype = eGL_BYTE;
|
|
}
|
|
|
|
gl.glVertexAttribFormat(i, fmts[i]->compCount, fmttype, fmts[i]->compType != eCompType_Float,
|
|
0);
|
|
}
|
|
else if(fmts[i]->compType == eCompType_UInt || fmts[i]->compType == eCompType_SInt)
|
|
{
|
|
GLenum fmttype = eGL_UNSIGNED_INT;
|
|
|
|
if(fmts[i]->compByteWidth == 4)
|
|
{
|
|
if(fmts[i]->compType == eCompType_UInt)
|
|
fmttype = eGL_UNSIGNED_INT;
|
|
else if(fmts[i]->compType == eCompType_SInt)
|
|
fmttype = eGL_INT;
|
|
}
|
|
else if(fmts[i]->compByteWidth == 2)
|
|
{
|
|
if(fmts[i]->compType == eCompType_UInt)
|
|
fmttype = eGL_UNSIGNED_SHORT;
|
|
else if(fmts[i]->compType == eCompType_SInt)
|
|
fmttype = eGL_SHORT;
|
|
}
|
|
else if(fmts[i]->compByteWidth == 1)
|
|
{
|
|
if(fmts[i]->compType == eCompType_UInt)
|
|
fmttype = eGL_UNSIGNED_BYTE;
|
|
else if(fmts[i]->compType == eCompType_SInt)
|
|
fmttype = eGL_BYTE;
|
|
}
|
|
|
|
gl.glVertexAttribIFormat(i, fmts[i]->compCount, fmttype, 0);
|
|
}
|
|
else if(fmts[i]->compType == eCompType_Double)
|
|
{
|
|
gl.glVertexAttribLFormat(i, fmts[i]->compCount, eGL_DOUBLE, 0);
|
|
}
|
|
|
|
GLuint vb = m_pDriver->GetResourceManager()->GetCurrentResource(fmts[i]->buf).name;
|
|
gl.glBindVertexBuffer(i, vb, (GLintptr)fmts[i]->offset, fmts[i]->stride);
|
|
}
|
|
|
|
// enable position attribute
|
|
gl.glEnableVertexAttribArray(0);
|
|
gl.glDisableVertexAttribArray(1);
|
|
|
|
gl.glEnable(eGL_DEPTH_TEST);
|
|
|
|
// solid render
|
|
if(cfg.solidShadeMode != eShade_None && topo != eGL_PATCHES)
|
|
{
|
|
gl.glDepthFunc(eGL_LESS);
|
|
|
|
GLuint solidProg = prog;
|
|
|
|
if(cfg.solidShadeMode == eShade_Lit)
|
|
{
|
|
// pick program with GS for per-face lighting
|
|
solidProg = DebugData.meshgsProg;
|
|
|
|
ClearGLErrors(gl.GetHookset());
|
|
gl.glUseProgram(solidProg);
|
|
GLenum err = gl.glGetError();
|
|
|
|
err = eGL_NONE;
|
|
}
|
|
|
|
MeshUBOData *soliddata = (MeshUBOData *)gl.glMapBufferRange(
|
|
eGL_UNIFORM_BUFFER, 0, sizeof(MeshUBOData), GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
|
|
|
|
soliddata->mvp = ModelViewProj;
|
|
soliddata->pointSpriteSize = Vec2f(0.0f, 0.0f);
|
|
soliddata->homogenousInput = cfg.position.unproject;
|
|
|
|
soliddata->color = Vec4f(0.8f, 0.8f, 0.0f, 1.0f);
|
|
|
|
uint32_t OutputDisplayFormat = (uint32_t)cfg.solidShadeMode;
|
|
if(cfg.solidShadeMode == eShade_Secondary && cfg.second.showAlpha)
|
|
OutputDisplayFormat = MESHDISPLAY_SECONDARY_ALPHA;
|
|
soliddata->displayFormat = OutputDisplayFormat;
|
|
|
|
if(cfg.solidShadeMode == eShade_Lit)
|
|
soliddata->invProj = projMat.Inverse();
|
|
|
|
gl.glUnmapBuffer(eGL_UNIFORM_BUFFER);
|
|
|
|
if(cfg.second.buf != ResourceId())
|
|
gl.glEnableVertexAttribArray(1);
|
|
|
|
gl.glPolygonMode(eGL_FRONT_AND_BACK, eGL_FILL);
|
|
|
|
if(cfg.position.idxByteWidth)
|
|
{
|
|
GLenum idxtype = eGL_UNSIGNED_BYTE;
|
|
if(cfg.position.idxByteWidth == 2)
|
|
idxtype = eGL_UNSIGNED_SHORT;
|
|
else if(cfg.position.idxByteWidth == 4)
|
|
idxtype = eGL_UNSIGNED_INT;
|
|
|
|
if(cfg.position.idxbuf != ResourceId())
|
|
{
|
|
GLuint ib = m_pDriver->GetResourceManager()->GetCurrentResource(cfg.position.idxbuf).name;
|
|
gl.glBindBuffer(eGL_ELEMENT_ARRAY_BUFFER, ib);
|
|
}
|
|
gl.glDrawElementsBaseVertex(topo, cfg.position.numVerts, idxtype,
|
|
(const void *)uintptr_t(cfg.position.idxoffs),
|
|
cfg.position.baseVertex);
|
|
}
|
|
else
|
|
{
|
|
gl.glDrawArrays(topo, 0, cfg.position.numVerts);
|
|
}
|
|
|
|
gl.glDisableVertexAttribArray(1);
|
|
|
|
gl.glUseProgram(prog);
|
|
}
|
|
|
|
gl.glDepthFunc(eGL_ALWAYS);
|
|
|
|
// wireframe render
|
|
if(cfg.solidShadeMode == eShade_None || cfg.wireframeDraw || topo == eGL_PATCHES)
|
|
{
|
|
uboParams.color = Vec4f(cfg.position.meshColour.x, cfg.position.meshColour.y,
|
|
cfg.position.meshColour.z, cfg.position.meshColour.w);
|
|
|
|
uboParams.displayFormat = MESHDISPLAY_SOLID;
|
|
|
|
gl.glPolygonMode(eGL_FRONT_AND_BACK, eGL_LINE);
|
|
|
|
uboptr = (MeshUBOData *)gl.glMapBufferRange(eGL_UNIFORM_BUFFER, 0, sizeof(MeshUBOData),
|
|
GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
|
|
*uboptr = uboParams;
|
|
gl.glUnmapBuffer(eGL_UNIFORM_BUFFER);
|
|
|
|
if(cfg.position.idxByteWidth)
|
|
{
|
|
GLenum idxtype = eGL_UNSIGNED_BYTE;
|
|
if(cfg.position.idxByteWidth == 2)
|
|
idxtype = eGL_UNSIGNED_SHORT;
|
|
else if(cfg.position.idxByteWidth == 4)
|
|
idxtype = eGL_UNSIGNED_INT;
|
|
|
|
if(cfg.position.idxbuf != ResourceId())
|
|
{
|
|
GLuint ib = m_pDriver->GetResourceManager()->GetCurrentResource(cfg.position.idxbuf).name;
|
|
gl.glBindBuffer(eGL_ELEMENT_ARRAY_BUFFER, ib);
|
|
|
|
gl.glDrawElementsBaseVertex(topo != eGL_PATCHES ? topo : eGL_POINTS, cfg.position.numVerts,
|
|
idxtype, (const void *)uintptr_t(cfg.position.idxoffs),
|
|
cfg.position.baseVertex);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gl.glDrawArrays(topo != eGL_PATCHES ? topo : eGL_POINTS, 0, cfg.position.numVerts);
|
|
}
|
|
}
|
|
|
|
if(cfg.showBBox)
|
|
{
|
|
Vec4f a = Vec4f(cfg.minBounds.x, cfg.minBounds.y, cfg.minBounds.z, cfg.minBounds.w);
|
|
Vec4f b = Vec4f(cfg.maxBounds.x, cfg.maxBounds.y, cfg.maxBounds.z, cfg.maxBounds.w);
|
|
|
|
Vec4f TLN = Vec4f(a.x, b.y, a.z, 1.0f); // TopLeftNear, etc...
|
|
Vec4f TRN = Vec4f(b.x, b.y, a.z, 1.0f);
|
|
Vec4f BLN = Vec4f(a.x, a.y, a.z, 1.0f);
|
|
Vec4f BRN = Vec4f(b.x, a.y, a.z, 1.0f);
|
|
|
|
Vec4f TLF = Vec4f(a.x, b.y, b.z, 1.0f);
|
|
Vec4f TRF = Vec4f(b.x, b.y, b.z, 1.0f);
|
|
Vec4f BLF = Vec4f(a.x, a.y, b.z, 1.0f);
|
|
Vec4f BRF = Vec4f(b.x, a.y, b.z, 1.0f);
|
|
|
|
// 12 frustum lines => 24 verts
|
|
Vec4f bbox[24] = {
|
|
TLN, TRN, TRN, BRN, BRN, BLN, BLN, TLN,
|
|
|
|
TLN, TLF, TRN, TRF, BLN, BLF, BRN, BRF,
|
|
|
|
TLF, TRF, TRF, BRF, BRF, BLF, BLF, TLF,
|
|
};
|
|
|
|
gl.glBindBuffer(eGL_ARRAY_BUFFER, DebugData.triHighlightBuffer);
|
|
gl.glBufferSubData(eGL_ARRAY_BUFFER, 0, sizeof(bbox), &bbox[0]);
|
|
|
|
gl.glBindVertexArray(DebugData.triHighlightVAO);
|
|
|
|
uboParams.color = Vec4f(0.2f, 0.2f, 1.0f, 1.0f);
|
|
|
|
Matrix4f mvpMat = projMat.Mul(camMat);
|
|
|
|
uboParams.mvp = mvpMat;
|
|
|
|
uboptr = (MeshUBOData *)gl.glMapBufferRange(eGL_UNIFORM_BUFFER, 0, sizeof(MeshUBOData),
|
|
GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
|
|
*uboptr = uboParams;
|
|
gl.glUnmapBuffer(eGL_UNIFORM_BUFFER);
|
|
|
|
// we want this to clip
|
|
gl.glDepthFunc(eGL_LESS);
|
|
|
|
gl.glDrawArrays(eGL_LINES, 0, 24);
|
|
|
|
gl.glDepthFunc(eGL_ALWAYS);
|
|
}
|
|
|
|
// draw axis helpers
|
|
if(!cfg.position.unproject)
|
|
{
|
|
gl.glBindVertexArray(DebugData.axisVAO);
|
|
|
|
uboParams.color = Vec4f(1.0f, 0.0f, 0.0f, 1.0f);
|
|
uboptr = (MeshUBOData *)gl.glMapBufferRange(eGL_UNIFORM_BUFFER, 0, sizeof(MeshUBOData),
|
|
GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
|
|
*uboptr = uboParams;
|
|
gl.glUnmapBuffer(eGL_UNIFORM_BUFFER);
|
|
|
|
gl.glDrawArrays(eGL_LINES, 0, 2);
|
|
|
|
uboParams.color = Vec4f(0.0f, 1.0f, 0.0f, 1.0f);
|
|
uboptr = (MeshUBOData *)gl.glMapBufferRange(eGL_UNIFORM_BUFFER, 0, sizeof(MeshUBOData),
|
|
GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
|
|
*uboptr = uboParams;
|
|
gl.glUnmapBuffer(eGL_UNIFORM_BUFFER);
|
|
gl.glDrawArrays(eGL_LINES, 2, 2);
|
|
|
|
uboParams.color = Vec4f(0.0f, 0.0f, 1.0f, 1.0f);
|
|
uboptr = (MeshUBOData *)gl.glMapBufferRange(eGL_UNIFORM_BUFFER, 0, sizeof(MeshUBOData),
|
|
GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
|
|
*uboptr = uboParams;
|
|
gl.glUnmapBuffer(eGL_UNIFORM_BUFFER);
|
|
gl.glDrawArrays(eGL_LINES, 4, 2);
|
|
}
|
|
|
|
// 'fake' helper frustum
|
|
if(cfg.position.unproject)
|
|
{
|
|
gl.glBindVertexArray(DebugData.frustumVAO);
|
|
|
|
uboParams.color = Vec4f(1.0f, 1.0f, 1.0f, 1.0f);
|
|
uboParams.mvp = ModelViewProj;
|
|
|
|
uboptr = (MeshUBOData *)gl.glMapBufferRange(eGL_UNIFORM_BUFFER, 0, sizeof(MeshUBOData),
|
|
GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
|
|
*uboptr = uboParams;
|
|
gl.glUnmapBuffer(eGL_UNIFORM_BUFFER);
|
|
|
|
gl.glDrawArrays(eGL_LINES, 0, 24);
|
|
}
|
|
|
|
gl.glPolygonMode(eGL_FRONT_AND_BACK, eGL_FILL);
|
|
|
|
// show highlighted vertex
|
|
if(cfg.highlightVert != ~0U)
|
|
{
|
|
MeshDataStage stage = cfg.type;
|
|
|
|
if(m_HighlightCache.EID != eventID || stage != m_HighlightCache.stage ||
|
|
cfg.position.buf != m_HighlightCache.buf || cfg.position.offset != m_HighlightCache.offs)
|
|
{
|
|
m_HighlightCache.EID = eventID;
|
|
m_HighlightCache.buf = cfg.position.buf;
|
|
m_HighlightCache.offs = cfg.position.offset;
|
|
m_HighlightCache.stage = stage;
|
|
|
|
uint32_t bytesize = cfg.position.idxByteWidth;
|
|
|
|
GetBufferData(cfg.position.buf, 0, 0, m_HighlightCache.data);
|
|
|
|
if(cfg.position.idxByteWidth == 0 || stage == eMeshDataStage_GSOut)
|
|
{
|
|
m_HighlightCache.indices.clear();
|
|
m_HighlightCache.useidx = false;
|
|
}
|
|
else
|
|
{
|
|
m_HighlightCache.useidx = true;
|
|
|
|
vector<byte> idxdata;
|
|
if(cfg.position.idxbuf != ResourceId())
|
|
GetBufferData(cfg.position.idxbuf, cfg.position.idxoffs, cfg.position.numVerts * bytesize,
|
|
idxdata);
|
|
|
|
uint8_t *idx8 = (uint8_t *)&idxdata[0];
|
|
uint16_t *idx16 = (uint16_t *)&idxdata[0];
|
|
uint32_t *idx32 = (uint32_t *)&idxdata[0];
|
|
|
|
uint32_t numIndices = RDCMIN(cfg.position.numVerts, uint32_t(idxdata.size() / bytesize));
|
|
|
|
m_HighlightCache.indices.resize(numIndices);
|
|
|
|
if(bytesize == 1)
|
|
{
|
|
for(uint32_t i = 0; i < numIndices; i++)
|
|
m_HighlightCache.indices[i] = uint32_t(idx8[i]);
|
|
}
|
|
else if(bytesize == 2)
|
|
{
|
|
for(uint32_t i = 0; i < numIndices; i++)
|
|
m_HighlightCache.indices[i] = uint32_t(idx16[i]);
|
|
}
|
|
else if(bytesize == 4)
|
|
{
|
|
for(uint32_t i = 0; i < numIndices; i++)
|
|
m_HighlightCache.indices[i] = idx32[i];
|
|
}
|
|
|
|
uint32_t sub = uint32_t(-cfg.position.baseVertex);
|
|
uint32_t add = uint32_t(cfg.position.baseVertex);
|
|
|
|
for(uint32_t i = 0; cfg.position.baseVertex != 0 && i < numIndices; i++)
|
|
{
|
|
if(cfg.position.baseVertex < 0)
|
|
{
|
|
if(m_HighlightCache.indices[i] < sub)
|
|
m_HighlightCache.indices[i] = 0;
|
|
else
|
|
m_HighlightCache.indices[i] -= sub;
|
|
}
|
|
else
|
|
m_HighlightCache.indices[i] += add;
|
|
}
|
|
}
|
|
}
|
|
|
|
GLenum meshtopo = topo;
|
|
|
|
uint32_t idx = cfg.highlightVert;
|
|
|
|
byte *data = &m_HighlightCache.data[0]; // buffer start
|
|
byte *dataEnd = data + m_HighlightCache.data.size();
|
|
|
|
data += cfg.position.offset; // to start of position data
|
|
|
|
///////////////////////////////////////////////////////////////
|
|
// vectors to be set from buffers, depending on topology
|
|
|
|
bool valid = true;
|
|
|
|
// this vert (blue dot, required)
|
|
FloatVector activeVertex;
|
|
|
|
// primitive this vert is a part of (red prim, optional)
|
|
vector<FloatVector> activePrim;
|
|
|
|
// for patch lists, to show other verts in patch (green dots, optional)
|
|
// for non-patch lists, we use the activePrim and adjacentPrimVertices
|
|
// to show what other verts are related
|
|
vector<FloatVector> inactiveVertices;
|
|
|
|
// adjacency (line or tri, strips or lists) (green prims, optional)
|
|
// will be N*M long, N adjacent prims of M verts each. M = primSize below
|
|
vector<FloatVector> adjacentPrimVertices;
|
|
|
|
GLenum primTopo = eGL_TRIANGLES;
|
|
uint32_t primSize = 3; // number of verts per primitive
|
|
|
|
if(meshtopo == eGL_LINES || meshtopo == eGL_LINES_ADJACENCY || meshtopo == eGL_LINE_STRIP ||
|
|
meshtopo == eGL_LINE_STRIP_ADJACENCY)
|
|
{
|
|
primSize = 2;
|
|
primTopo = eGL_LINES;
|
|
}
|
|
|
|
activeVertex = InterpretVertex(data, idx, cfg, dataEnd, true, valid);
|
|
|
|
// see Section 10.1 of the OpenGL 4.5 spec for
|
|
// how primitive topologies are laid out
|
|
if(meshtopo == eGL_LINES)
|
|
{
|
|
uint32_t v = uint32_t(idx / 2) * 2; // find first vert in primitive
|
|
|
|
activePrim.push_back(InterpretVertex(data, v + 0, cfg, dataEnd, true, valid));
|
|
activePrim.push_back(InterpretVertex(data, v + 1, cfg, dataEnd, true, valid));
|
|
}
|
|
else if(meshtopo == eGL_TRIANGLES)
|
|
{
|
|
uint32_t v = uint32_t(idx / 3) * 3; // find first vert in primitive
|
|
|
|
activePrim.push_back(InterpretVertex(data, v + 0, cfg, dataEnd, true, valid));
|
|
activePrim.push_back(InterpretVertex(data, v + 1, cfg, dataEnd, true, valid));
|
|
activePrim.push_back(InterpretVertex(data, v + 2, cfg, dataEnd, true, valid));
|
|
}
|
|
else if(meshtopo == eGL_LINES_ADJACENCY)
|
|
{
|
|
uint32_t v = uint32_t(idx / 4) * 4; // find first vert in primitive
|
|
|
|
FloatVector vs[] = {
|
|
InterpretVertex(data, v + 0, cfg, dataEnd, true, valid),
|
|
InterpretVertex(data, v + 1, cfg, dataEnd, true, valid),
|
|
InterpretVertex(data, v + 2, cfg, dataEnd, true, valid),
|
|
InterpretVertex(data, v + 3, cfg, dataEnd, true, valid),
|
|
};
|
|
|
|
adjacentPrimVertices.push_back(vs[0]);
|
|
adjacentPrimVertices.push_back(vs[1]);
|
|
|
|
adjacentPrimVertices.push_back(vs[2]);
|
|
adjacentPrimVertices.push_back(vs[3]);
|
|
|
|
activePrim.push_back(vs[1]);
|
|
activePrim.push_back(vs[2]);
|
|
}
|
|
else if(meshtopo == eGL_TRIANGLES_ADJACENCY)
|
|
{
|
|
uint32_t v = uint32_t(idx / 6) * 6; // find first vert in primitive
|
|
|
|
FloatVector vs[] = {
|
|
InterpretVertex(data, v + 0, cfg, dataEnd, true, valid),
|
|
InterpretVertex(data, v + 1, cfg, dataEnd, true, valid),
|
|
InterpretVertex(data, v + 2, cfg, dataEnd, true, valid),
|
|
InterpretVertex(data, v + 3, cfg, dataEnd, true, valid),
|
|
InterpretVertex(data, v + 4, cfg, dataEnd, true, valid),
|
|
InterpretVertex(data, v + 5, cfg, dataEnd, true, valid),
|
|
};
|
|
|
|
adjacentPrimVertices.push_back(vs[0]);
|
|
adjacentPrimVertices.push_back(vs[1]);
|
|
adjacentPrimVertices.push_back(vs[2]);
|
|
|
|
adjacentPrimVertices.push_back(vs[2]);
|
|
adjacentPrimVertices.push_back(vs[3]);
|
|
adjacentPrimVertices.push_back(vs[4]);
|
|
|
|
adjacentPrimVertices.push_back(vs[4]);
|
|
adjacentPrimVertices.push_back(vs[5]);
|
|
adjacentPrimVertices.push_back(vs[0]);
|
|
|
|
activePrim.push_back(vs[0]);
|
|
activePrim.push_back(vs[2]);
|
|
activePrim.push_back(vs[4]);
|
|
}
|
|
else if(meshtopo == eGL_LINE_STRIP)
|
|
{
|
|
// find first vert in primitive. In strips a vert isn't
|
|
// in only one primitive, so we pick the first primitive
|
|
// it's in. This means the first N points are in the first
|
|
// primitive, and thereafter each point is in the next primitive
|
|
uint32_t v = RDCMAX(idx, 1U) - 1;
|
|
|
|
activePrim.push_back(InterpretVertex(data, v + 0, cfg, dataEnd, true, valid));
|
|
activePrim.push_back(InterpretVertex(data, v + 1, cfg, dataEnd, true, valid));
|
|
}
|
|
else if(meshtopo == eGL_TRIANGLE_STRIP)
|
|
{
|
|
// find first vert in primitive. In strips a vert isn't
|
|
// in only one primitive, so we pick the first primitive
|
|
// it's in. This means the first N points are in the first
|
|
// primitive, and thereafter each point is in the next primitive
|
|
uint32_t v = RDCMAX(idx, 2U) - 2;
|
|
|
|
activePrim.push_back(InterpretVertex(data, v + 0, cfg, dataEnd, true, valid));
|
|
activePrim.push_back(InterpretVertex(data, v + 1, cfg, dataEnd, true, valid));
|
|
activePrim.push_back(InterpretVertex(data, v + 2, cfg, dataEnd, true, valid));
|
|
}
|
|
else if(meshtopo == eGL_LINE_STRIP_ADJACENCY)
|
|
{
|
|
// find first vert in primitive. In strips a vert isn't
|
|
// in only one primitive, so we pick the first primitive
|
|
// it's in. This means the first N points are in the first
|
|
// primitive, and thereafter each point is in the next primitive
|
|
uint32_t v = RDCMAX(idx, 3U) - 3;
|
|
|
|
FloatVector vs[] = {
|
|
InterpretVertex(data, v + 0, cfg, dataEnd, true, valid),
|
|
InterpretVertex(data, v + 1, cfg, dataEnd, true, valid),
|
|
InterpretVertex(data, v + 2, cfg, dataEnd, true, valid),
|
|
InterpretVertex(data, v + 3, cfg, dataEnd, true, valid),
|
|
};
|
|
|
|
adjacentPrimVertices.push_back(vs[0]);
|
|
adjacentPrimVertices.push_back(vs[1]);
|
|
|
|
adjacentPrimVertices.push_back(vs[2]);
|
|
adjacentPrimVertices.push_back(vs[3]);
|
|
|
|
activePrim.push_back(vs[1]);
|
|
activePrim.push_back(vs[2]);
|
|
}
|
|
else if(meshtopo == eGL_TRIANGLE_STRIP_ADJACENCY)
|
|
{
|
|
// Triangle strip with adjacency is the most complex topology, as
|
|
// we need to handle the ends separately where the pattern breaks.
|
|
|
|
uint32_t numidx = cfg.position.numVerts;
|
|
|
|
if(numidx < 6)
|
|
{
|
|
// not enough indices provided, bail to make sure logic below doesn't
|
|
// need to have tons of edge case detection
|
|
valid = false;
|
|
}
|
|
else if(idx <= 4 || numidx <= 7)
|
|
{
|
|
FloatVector vs[] = {
|
|
InterpretVertex(data, 0, cfg, dataEnd, true, valid),
|
|
InterpretVertex(data, 1, cfg, dataEnd, true, valid),
|
|
InterpretVertex(data, 2, cfg, dataEnd, true, valid),
|
|
InterpretVertex(data, 3, cfg, dataEnd, true, valid),
|
|
InterpretVertex(data, 4, cfg, dataEnd, true, valid),
|
|
|
|
// note this one isn't used as it's adjacency for the next triangle
|
|
InterpretVertex(data, 5, cfg, dataEnd, true, valid),
|
|
|
|
// min() with number of indices in case this is a tiny strip
|
|
// that is basically just a list
|
|
InterpretVertex(data, RDCMIN(6U, numidx - 1), cfg, dataEnd, true, valid),
|
|
};
|
|
|
|
// these are the triangles on the far left of the MSDN diagram above
|
|
adjacentPrimVertices.push_back(vs[0]);
|
|
adjacentPrimVertices.push_back(vs[1]);
|
|
adjacentPrimVertices.push_back(vs[2]);
|
|
|
|
adjacentPrimVertices.push_back(vs[4]);
|
|
adjacentPrimVertices.push_back(vs[3]);
|
|
adjacentPrimVertices.push_back(vs[0]);
|
|
|
|
adjacentPrimVertices.push_back(vs[4]);
|
|
adjacentPrimVertices.push_back(vs[2]);
|
|
adjacentPrimVertices.push_back(vs[6]);
|
|
|
|
activePrim.push_back(vs[0]);
|
|
activePrim.push_back(vs[2]);
|
|
activePrim.push_back(vs[4]);
|
|
}
|
|
else if(idx > numidx - 4)
|
|
{
|
|
// in diagram, numidx == 14
|
|
|
|
FloatVector vs[] = {
|
|
/*[0]=*/InterpretVertex(data, numidx - 8, cfg, dataEnd, true, valid), // 6 in diagram
|
|
|
|
// as above, unused since this is adjacency for 2-previous triangle
|
|
/*[1]=*/InterpretVertex(data, numidx - 7, cfg, dataEnd, true, valid), // 7 in diagram
|
|
/*[2]=*/InterpretVertex(data, numidx - 6, cfg, dataEnd, true, valid), // 8 in diagram
|
|
|
|
// as above, unused since this is adjacency for previous triangle
|
|
/*[3]=*/InterpretVertex(data, numidx - 5, cfg, dataEnd, true, valid), // 9 in diagram
|
|
/*[4]=*/InterpretVertex(data, numidx - 4, cfg, dataEnd, true,
|
|
valid), // 10 in diagram
|
|
/*[5]=*/InterpretVertex(data, numidx - 3, cfg, dataEnd, true,
|
|
valid), // 11 in diagram
|
|
/*[6]=*/InterpretVertex(data, numidx - 2, cfg, dataEnd, true,
|
|
valid), // 12 in diagram
|
|
/*[7]=*/InterpretVertex(data, numidx - 1, cfg, dataEnd, true,
|
|
valid), // 13 in diagram
|
|
};
|
|
|
|
// these are the triangles on the far right of the MSDN diagram above
|
|
adjacentPrimVertices.push_back(vs[2]); // 8 in diagram
|
|
adjacentPrimVertices.push_back(vs[0]); // 6 in diagram
|
|
adjacentPrimVertices.push_back(vs[4]); // 10 in diagram
|
|
|
|
adjacentPrimVertices.push_back(vs[4]); // 10 in diagram
|
|
adjacentPrimVertices.push_back(vs[7]); // 13 in diagram
|
|
adjacentPrimVertices.push_back(vs[6]); // 12 in diagram
|
|
|
|
adjacentPrimVertices.push_back(vs[6]); // 12 in diagram
|
|
adjacentPrimVertices.push_back(vs[5]); // 11 in diagram
|
|
adjacentPrimVertices.push_back(vs[2]); // 8 in diagram
|
|
|
|
activePrim.push_back(vs[2]); // 8 in diagram
|
|
activePrim.push_back(vs[4]); // 10 in diagram
|
|
activePrim.push_back(vs[6]); // 12 in diagram
|
|
}
|
|
else
|
|
{
|
|
// we're in the middle somewhere. Each primitive has two vertices for it
|
|
// so our step rate is 2. The first 'middle' primitive starts at indices 5&6
|
|
// and uses indices all the way back to 0
|
|
uint32_t v = RDCMAX(((idx + 1) / 2) * 2, 6U) - 6;
|
|
|
|
// these correspond to the indices in the MSDN diagram, with {2,4,6} as the
|
|
// main triangle
|
|
FloatVector vs[] = {
|
|
InterpretVertex(data, v + 0, cfg, dataEnd, true, valid),
|
|
|
|
// this one is adjacency for 2-previous triangle
|
|
InterpretVertex(data, v + 1, cfg, dataEnd, true, valid),
|
|
InterpretVertex(data, v + 2, cfg, dataEnd, true, valid),
|
|
|
|
// this one is adjacency for previous triangle
|
|
InterpretVertex(data, v + 3, cfg, dataEnd, true, valid),
|
|
InterpretVertex(data, v + 4, cfg, dataEnd, true, valid),
|
|
InterpretVertex(data, v + 5, cfg, dataEnd, true, valid),
|
|
InterpretVertex(data, v + 6, cfg, dataEnd, true, valid),
|
|
InterpretVertex(data, v + 7, cfg, dataEnd, true, valid),
|
|
InterpretVertex(data, v + 8, cfg, dataEnd, true, valid),
|
|
};
|
|
|
|
// these are the triangles around {2,4,6} in the MSDN diagram above
|
|
adjacentPrimVertices.push_back(vs[0]);
|
|
adjacentPrimVertices.push_back(vs[2]);
|
|
adjacentPrimVertices.push_back(vs[4]);
|
|
|
|
adjacentPrimVertices.push_back(vs[2]);
|
|
adjacentPrimVertices.push_back(vs[5]);
|
|
adjacentPrimVertices.push_back(vs[6]);
|
|
|
|
adjacentPrimVertices.push_back(vs[6]);
|
|
adjacentPrimVertices.push_back(vs[8]);
|
|
adjacentPrimVertices.push_back(vs[4]);
|
|
|
|
activePrim.push_back(vs[2]);
|
|
activePrim.push_back(vs[4]);
|
|
activePrim.push_back(vs[6]);
|
|
}
|
|
}
|
|
else if(meshtopo == eGL_PATCHES)
|
|
{
|
|
uint32_t dim = (cfg.position.topo - eTopology_PatchList_1CPs + 1);
|
|
|
|
uint32_t v0 = uint32_t(idx / dim) * dim;
|
|
|
|
for(uint32_t v = v0; v < v0 + dim; v++)
|
|
{
|
|
if(v != idx && valid)
|
|
inactiveVertices.push_back(InterpretVertex(data, v, cfg, dataEnd, true, valid));
|
|
}
|
|
}
|
|
else // if(meshtopo == eGL_POINTS) point list, or unknown/unhandled type
|
|
{
|
|
// no adjacency, inactive verts or active primitive
|
|
}
|
|
|
|
if(valid)
|
|
{
|
|
////////////////////////////////////////////////////////////////
|
|
// prepare rendering (for both vertices & primitives)
|
|
|
|
// if data is from post transform, it will be in clipspace
|
|
if(cfg.position.unproject)
|
|
ModelViewProj = projMat.Mul(camMat.Mul(guessProjInv));
|
|
else
|
|
ModelViewProj = projMat.Mul(camMat);
|
|
|
|
uboParams.homogenousInput = cfg.position.unproject;
|
|
|
|
uboParams.mvp = ModelViewProj;
|
|
|
|
gl.glBindVertexArray(DebugData.triHighlightVAO);
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// render primitives
|
|
|
|
// Draw active primitive (red)
|
|
uboParams.color = Vec4f(1.0f, 0.0f, 0.0f, 1.0f);
|
|
|
|
if(activePrim.size() >= primSize)
|
|
{
|
|
uboptr = (MeshUBOData *)gl.glMapBufferRange(eGL_UNIFORM_BUFFER, 0, sizeof(MeshUBOData),
|
|
GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
|
|
*uboptr = uboParams;
|
|
gl.glUnmapBuffer(eGL_UNIFORM_BUFFER);
|
|
|
|
gl.glBindBuffer(eGL_ARRAY_BUFFER, DebugData.triHighlightBuffer);
|
|
gl.glBufferSubData(eGL_ARRAY_BUFFER, 0, sizeof(Vec4f) * primSize, &activePrim[0]);
|
|
|
|
gl.glDrawArrays(primTopo, 0, primSize);
|
|
}
|
|
|
|
// Draw adjacent primitives (green)
|
|
uboParams.color = Vec4f(0.0f, 1.0f, 0.0f, 1.0f);
|
|
|
|
if(adjacentPrimVertices.size() >= primSize && (adjacentPrimVertices.size() % primSize) == 0)
|
|
{
|
|
uboptr = (MeshUBOData *)gl.glMapBufferRange(eGL_UNIFORM_BUFFER, 0, sizeof(MeshUBOData),
|
|
GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
|
|
*uboptr = uboParams;
|
|
gl.glUnmapBuffer(eGL_UNIFORM_BUFFER);
|
|
|
|
gl.glBindBuffer(eGL_ARRAY_BUFFER, DebugData.triHighlightBuffer);
|
|
gl.glBufferSubData(eGL_ARRAY_BUFFER, 0, sizeof(Vec4f) * adjacentPrimVertices.size(),
|
|
&adjacentPrimVertices[0]);
|
|
|
|
gl.glDrawArrays(primTopo, 0, (GLsizei)adjacentPrimVertices.size());
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// prepare to render dots
|
|
float scale = 800.0f / float(DebugData.outHeight);
|
|
float asp = float(DebugData.outWidth) / float(DebugData.outHeight);
|
|
|
|
uboParams.pointSpriteSize = Vec2f(scale / asp, scale);
|
|
|
|
// Draw active vertex (blue)
|
|
uboParams.color = Vec4f(0.0f, 0.0f, 1.0f, 1.0f);
|
|
|
|
uboptr = (MeshUBOData *)gl.glMapBufferRange(eGL_UNIFORM_BUFFER, 0, sizeof(MeshUBOData),
|
|
GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
|
|
*uboptr = uboParams;
|
|
gl.glUnmapBuffer(eGL_UNIFORM_BUFFER);
|
|
|
|
FloatVector vertSprite[4] = {
|
|
activeVertex, activeVertex, activeVertex, activeVertex,
|
|
};
|
|
|
|
gl.glBindBuffer(eGL_ARRAY_BUFFER, DebugData.triHighlightBuffer);
|
|
gl.glBufferSubData(eGL_ARRAY_BUFFER, 0, sizeof(vertSprite), &vertSprite[0]);
|
|
|
|
gl.glDrawArrays(eGL_TRIANGLE_STRIP, 0, 4);
|
|
|
|
// Draw inactive vertices (green)
|
|
uboParams.color = Vec4f(0.0f, 1.0f, 0.0f, 1.0f);
|
|
|
|
uboptr = (MeshUBOData *)gl.glMapBufferRange(eGL_UNIFORM_BUFFER, 0, sizeof(MeshUBOData),
|
|
GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
|
|
*uboptr = uboParams;
|
|
gl.glUnmapBuffer(eGL_UNIFORM_BUFFER);
|
|
|
|
for(size_t i = 0; i < inactiveVertices.size(); i++)
|
|
{
|
|
vertSprite[0] = vertSprite[1] = vertSprite[2] = vertSprite[3] = inactiveVertices[i];
|
|
|
|
gl.glBufferSubData(eGL_ARRAY_BUFFER, 0, sizeof(vertSprite), &vertSprite[0]);
|
|
|
|
gl.glDrawArrays(eGL_TRIANGLE_STRIP, 0, 4);
|
|
}
|
|
}
|
|
}
|
|
}
|