mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-10-08 05:11:32 +00:00
* Previously the vertex picking was always being done in NDC space, but now for perspective projections we unproject into world space and raycast there for more reliable results with unsual projections.
2871 lines
94 KiB
C++
2871 lines
94 KiB
C++
/******************************************************************************
|
|
* The MIT License (MIT)
|
|
*
|
|
* Copyright (c) 2019-2021 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 "driver/shaders/spirv/glslang_compile.h"
|
|
#include "maths/camera.h"
|
|
#include "maths/formatpacking.h"
|
|
#include "maths/matrix.h"
|
|
#include "strings/string_utils.h"
|
|
#include "gl_driver.h"
|
|
#include "gl_replay.h"
|
|
#include "gl_resources.h"
|
|
|
|
#define OPENGL 1
|
|
#include "data/glsl/glsl_ubos_cpp.h"
|
|
|
|
void GetGLSLVersions(ShaderType &shaderType, int &glslVersion, int &glslBaseVer, int &glslCSVer)
|
|
{
|
|
if(IsGLES)
|
|
{
|
|
shaderType = ShaderType::GLSLES;
|
|
glslVersion = glslBaseVer = glslCSVer = 300;
|
|
|
|
if(GLCoreVersion >= 31)
|
|
glslVersion = glslBaseVer = glslCSVer = 310;
|
|
|
|
if(GLCoreVersion >= 32)
|
|
glslVersion = glslBaseVer = glslCSVer = 320;
|
|
}
|
|
else
|
|
{
|
|
shaderType = ShaderType::GLSL;
|
|
glslVersion = glslBaseVer = 150;
|
|
glslCSVer = 420;
|
|
}
|
|
}
|
|
|
|
GLuint CreateShader(GLenum shaderType, const rdcstr &src)
|
|
{
|
|
GLuint ret = GL.glCreateShader(shaderType);
|
|
|
|
const char *csrc = src.c_str();
|
|
GL.glShaderSource(ret, 1, &csrc, NULL);
|
|
|
|
GL.glCompileShader(ret);
|
|
|
|
char buffer[1024] = {};
|
|
GLint status = 0;
|
|
GL.glGetShaderiv(ret, eGL_COMPILE_STATUS, &status);
|
|
if(status == 0)
|
|
{
|
|
GL.glGetShaderInfoLog(ret, 1024, NULL, buffer);
|
|
RDCERR("%s compile error: %s", ToStr(shaderType).c_str(), buffer);
|
|
return 0;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
GLuint CreateSPIRVShader(GLenum shaderType, const rdcstr &src)
|
|
{
|
|
if(!HasExt[ARB_gl_spirv])
|
|
{
|
|
RDCERR("Compiling SPIR-V shader without ARB_gl_spirv - should be checked above!");
|
|
return 0;
|
|
}
|
|
|
|
rdcspv::CompilationSettings settings(rdcspv::InputLanguage::OpenGLGLSL,
|
|
rdcspv::ShaderStage(ShaderIdx(shaderType)));
|
|
|
|
rdcarray<uint32_t> spirv;
|
|
rdcstr s = rdcspv::Compile(settings, {src}, spirv);
|
|
|
|
if(spirv.empty())
|
|
{
|
|
RDCERR("Couldn't compile shader to SPIR-V: %s", s.c_str());
|
|
return 0;
|
|
}
|
|
|
|
GLuint ret = GL.glCreateShader(shaderType);
|
|
|
|
GL.glShaderBinary(1, &ret, eGL_SHADER_BINARY_FORMAT_SPIR_V, spirv.data(),
|
|
(GLsizei)spirv.size() * 4);
|
|
|
|
GL.glSpecializeShader(ret, "main", 0, NULL, NULL);
|
|
|
|
char buffer[1024] = {};
|
|
GLint status = 0;
|
|
GL.glGetShaderiv(ret, eGL_COMPILE_STATUS, &status);
|
|
if(status == 0)
|
|
{
|
|
GL.glGetShaderInfoLog(ret, 1024, NULL, buffer);
|
|
RDCERR("%s compile error: %s", ToStr(shaderType).c_str(), buffer);
|
|
return 0;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
GLuint CreateCShaderProgram(const rdcstr &src)
|
|
{
|
|
GLuint cs = CreateShader(eGL_COMPUTE_SHADER, src);
|
|
if(cs == 0)
|
|
return 0;
|
|
|
|
GLuint ret = GL.glCreateProgram();
|
|
|
|
GL.glAttachShader(ret, cs);
|
|
|
|
GL.glLinkProgram(ret);
|
|
|
|
char buffer[1024] = {};
|
|
GLint status = 0;
|
|
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 CreateShaderProgram(GLuint vs, GLuint fs, GLuint gs)
|
|
{
|
|
GLuint ret = GL.glCreateProgram();
|
|
|
|
GL.glAttachShader(ret, vs);
|
|
GL.glAttachShader(ret, fs);
|
|
if(gs)
|
|
GL.glAttachShader(ret, gs);
|
|
|
|
GL.glLinkProgram(ret);
|
|
|
|
char buffer[1024] = {};
|
|
GLint status = 0;
|
|
GL.glGetProgramiv(ret, eGL_LINK_STATUS, &status);
|
|
if(status == 0)
|
|
{
|
|
GL.glGetProgramInfoLog(ret, 1024, NULL, buffer);
|
|
RDCERR("Shader error: %s", buffer);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
GLuint CreateShaderProgram(const rdcstr &vsSrc, const rdcstr &fsSrc, const rdcstr &gsSrc)
|
|
{
|
|
GLuint vs = 0;
|
|
GLuint fs = 0;
|
|
GLuint gs = 0;
|
|
|
|
if(vsSrc.empty())
|
|
{
|
|
RDCERR("Must have vertex shader - no separable programs supported.");
|
|
return 0;
|
|
}
|
|
|
|
if(fsSrc.empty())
|
|
{
|
|
RDCERR("Must have fragment shader - no separable programs supported.");
|
|
return 0;
|
|
}
|
|
|
|
vs = CreateShader(eGL_VERTEX_SHADER, vsSrc);
|
|
if(vs == 0)
|
|
return 0;
|
|
|
|
fs = CreateShader(eGL_FRAGMENT_SHADER, fsSrc);
|
|
if(fs == 0)
|
|
return 0;
|
|
|
|
if(!gsSrc.empty())
|
|
{
|
|
gs = CreateShader(eGL_GEOMETRY_SHADER, gsSrc);
|
|
if(gs == 0)
|
|
return 0;
|
|
}
|
|
|
|
GLuint ret = CreateShaderProgram(vs, fs, gs);
|
|
|
|
GL.glDetachShader(ret, vs);
|
|
GL.glDetachShader(ret, fs);
|
|
if(gs)
|
|
GL.glDetachShader(ret, gs);
|
|
|
|
GL.glDeleteShader(vs);
|
|
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;
|
|
}
|
|
}
|
|
|
|
GLuint GLReplay::CreateMeshProgram(GLuint vs, GLuint fs, GLuint gs)
|
|
{
|
|
GLuint program = CreateShaderProgram(vs, fs, gs);
|
|
|
|
// set attrib locations
|
|
GL.glBindAttribLocation(program, 0, "position");
|
|
GL.glBindAttribLocation(program, 1, "IN_secondary");
|
|
|
|
// relink
|
|
GL.glLinkProgram(program);
|
|
|
|
// check that the relink succeeded
|
|
char buffer[1024] = {};
|
|
GLint status = 0;
|
|
GL.glGetProgramiv(program, eGL_LINK_STATUS, &status);
|
|
if(status == 0)
|
|
{
|
|
GL.glGetProgramInfoLog(program, 1024, NULL, buffer);
|
|
RDCERR("Link error: %s", buffer);
|
|
}
|
|
|
|
// detach the shaders
|
|
GL.glDetachShader(program, vs);
|
|
GL.glDetachShader(program, fs);
|
|
if(gs)
|
|
GL.glDetachShader(program, gs);
|
|
|
|
// bind the UBO
|
|
BindUBO(program, "MeshUBOData", 0);
|
|
|
|
return program;
|
|
}
|
|
|
|
void GLReplay::ConfigureTexDisplayProgramBindings(GLuint program)
|
|
{
|
|
GLint location = -1;
|
|
|
|
GL.glUseProgram(program);
|
|
|
|
// since we split the shader up by type, not all texture slots are always available. Also on GLES
|
|
// some texture slots might be missing due to lack of extensions. So we need to check for a location
|
|
// of -1
|
|
#define SET_TEX_BINDING(name, bind) \
|
|
location = GL.glGetUniformLocation(program, name); \
|
|
if(location >= 0) \
|
|
GL.glUniform1i(location, bind);
|
|
|
|
SET_TEX_BINDING("texUInt1D", 1);
|
|
SET_TEX_BINDING("texUInt2D", 2);
|
|
SET_TEX_BINDING("texUInt3D", 3);
|
|
SET_TEX_BINDING("texUInt1DArray", 5);
|
|
SET_TEX_BINDING("texUInt2DArray", 6);
|
|
SET_TEX_BINDING("texUInt2DRect", 8);
|
|
SET_TEX_BINDING("texUIntBuffer", 9);
|
|
SET_TEX_BINDING("texUInt2DMS", 10);
|
|
SET_TEX_BINDING("texUInt2DMSArray", 11);
|
|
|
|
SET_TEX_BINDING("texSInt1D", 1);
|
|
SET_TEX_BINDING("texSInt2D", 2);
|
|
SET_TEX_BINDING("texSInt3D", 3);
|
|
SET_TEX_BINDING("texSInt1DArray", 5);
|
|
SET_TEX_BINDING("texSInt2DArray", 6);
|
|
SET_TEX_BINDING("texSInt2DRect", 8);
|
|
SET_TEX_BINDING("texSIntBuffer", 9);
|
|
SET_TEX_BINDING("texSInt2DMS", 10);
|
|
SET_TEX_BINDING("texSInt2DMSArray", 11);
|
|
|
|
SET_TEX_BINDING("tex1D", 1);
|
|
SET_TEX_BINDING("tex2D", 2);
|
|
SET_TEX_BINDING("tex3D", 3);
|
|
SET_TEX_BINDING("texCube", 4);
|
|
SET_TEX_BINDING("tex1DArray", 5);
|
|
SET_TEX_BINDING("tex2DArray", 6);
|
|
SET_TEX_BINDING("texCubeArray", 7);
|
|
SET_TEX_BINDING("tex2DRect", 8);
|
|
SET_TEX_BINDING("texBuffer", 9);
|
|
SET_TEX_BINDING("tex2DMS", 10);
|
|
SET_TEX_BINDING("tex2DMSArray", 11);
|
|
|
|
#undef SET_TEX_BINDING
|
|
}
|
|
|
|
void GLReplay::BindUBO(GLuint program, const char *name, GLuint binding)
|
|
{
|
|
GL.glUniformBlockBinding(program, GL.glGetUniformBlockIndex(program, name), binding);
|
|
}
|
|
|
|
void GLReplay::InitDebugData()
|
|
{
|
|
if(m_pDriver == NULL)
|
|
return;
|
|
|
|
// don't reflect any shaders or programs we make
|
|
m_pDriver->PushInternalShader();
|
|
|
|
m_HighlightCache.driver = m_pDriver->GetReplay();
|
|
|
|
RenderDoc::Inst().SetProgress(LoadProgress::DebugManagerInit, 0.0f);
|
|
|
|
{
|
|
WindowingData window = {WindowingSystem::Headless};
|
|
uint64_t id = MakeOutputWindow(window, true);
|
|
|
|
m_DebugCtx = NULL;
|
|
|
|
if(id == 0)
|
|
return;
|
|
|
|
m_DebugID = id;
|
|
m_DebugCtx = &m_OutputWindows[id];
|
|
|
|
MakeCurrentReplayContext(m_DebugCtx);
|
|
|
|
m_pDriver->RegisterDebugCallback();
|
|
}
|
|
|
|
WrappedOpenGL &drv = *m_pDriver;
|
|
|
|
DebugData.outWidth = 0.0f;
|
|
DebugData.outHeight = 0.0f;
|
|
|
|
rdcstr vs;
|
|
rdcstr fs;
|
|
rdcstr gs;
|
|
rdcstr cs;
|
|
|
|
ShaderType shaderType;
|
|
int glslVersion;
|
|
int glslBaseVer;
|
|
int glslCSVer; // compute shader
|
|
|
|
GetGLSLVersions(shaderType, glslVersion, glslBaseVer, glslCSVer);
|
|
|
|
rdcstr texSampleDefines;
|
|
|
|
if(IsGLES)
|
|
{
|
|
if(HasExt[OES_texture_cube_map_array] || HasExt[EXT_texture_cube_map_array] || GLCoreVersion >= 32)
|
|
texSampleDefines += "#define TEXSAMPLE_CUBE_ARRAY 1\n";
|
|
|
|
if(HasExt[OES_texture_cube_map_array])
|
|
texSampleDefines += "#extension GL_OES_texture_cube_map_array : require\n";
|
|
|
|
if(HasExt[EXT_texture_cube_map_array])
|
|
texSampleDefines += "#extension GL_EXT_texture_cube_map_array : require\n";
|
|
|
|
if(HasExt[EXT_texture_buffer])
|
|
texSampleDefines +=
|
|
"#define TEXSAMPLE_BUFFER 1\n"
|
|
"#extension GL_EXT_texture_buffer : require\n";
|
|
|
|
texSampleDefines += "#define HAS_BIT_CONVERSION 1\n";
|
|
}
|
|
else
|
|
{
|
|
if(HasExt[ARB_texture_cube_map_array])
|
|
texSampleDefines +=
|
|
"#define TEXSAMPLE_CUBE_ARRAY 1\n"
|
|
"#extension GL_ARB_texture_cube_map_array : require\n";
|
|
|
|
if(HasExt[ARB_texture_multisample])
|
|
texSampleDefines +=
|
|
"#define TEXSAMPLE_MULTISAMPLE 1\n"
|
|
"#extension GL_ARB_texture_multisample : require\n";
|
|
|
|
if(HasExt[ARB_gpu_shader5])
|
|
texSampleDefines +=
|
|
"#define HAS_BIT_CONVERSION 1\n"
|
|
"#extension GL_ARB_gpu_shader5 : require\n";
|
|
else if(HasExt[ARB_shader_bit_encoding])
|
|
texSampleDefines +=
|
|
"#define HAS_BIT_CONVERSION 1\n"
|
|
"#extension GL_ARB_shader_bit_encoding : require\n";
|
|
}
|
|
|
|
vs = GenerateGLSLShader(GetEmbeddedResource(glsl_blit_vert), shaderType, glslBaseVer);
|
|
|
|
DebugData.fixedcolFragShaderSPIRV = DebugData.quadoverdrawFragShaderSPIRV = 0;
|
|
|
|
// pre-compile SPIR-V shaders up front since this is more expensive
|
|
if(HasExt[ARB_gl_spirv])
|
|
{
|
|
// SPIR-V shaders are always generated as desktop GL 430, for ease
|
|
rdcstr source =
|
|
GenerateGLSLShader(GetEmbeddedResource(glsl_fixedcol_frag), ShaderType::GLSPIRV, 430);
|
|
DebugData.fixedcolFragShaderSPIRV = CreateSPIRVShader(eGL_FRAGMENT_SHADER, source);
|
|
|
|
if(HasExt[ARB_gpu_shader5] && HasExt[ARB_shader_image_load_store])
|
|
{
|
|
rdcstr defines = "";
|
|
|
|
if(!HasExt[ARB_derivative_control])
|
|
{
|
|
defines += "#define dFdxFine dFdx\n\n";
|
|
defines += "#define dFdyFine dFdy\n\n";
|
|
}
|
|
|
|
source = GenerateGLSLShader(GetEmbeddedResource(glsl_quadwrite_frag), ShaderType::GLSPIRV,
|
|
430, defines);
|
|
DebugData.quadoverdrawFragShaderSPIRV = CreateSPIRVShader(eGL_FRAGMENT_SHADER, source);
|
|
}
|
|
}
|
|
|
|
// used to combine with custom shaders.
|
|
DebugData.texDisplayVertexShader = CreateShader(eGL_VERTEX_SHADER, vs);
|
|
|
|
for(int i = 0; i < 3; i++)
|
|
{
|
|
rdcstr defines = rdcstr("#define SHADER_BASETYPE ") + ToStr(i) + "\n";
|
|
|
|
fs = GenerateGLSLShader(GetEmbeddedResource(glsl_texdisplay_frag), shaderType, glslBaseVer,
|
|
defines + texSampleDefines);
|
|
|
|
DebugData.texDisplayProg[i] = CreateShaderProgram(vs, fs);
|
|
|
|
BindUBO(DebugData.texDisplayProg[i], "TexDisplayUBOData", 0);
|
|
BindUBO(DebugData.texDisplayProg[i], "HeatmapData", 1);
|
|
ConfigureTexDisplayProgramBindings(DebugData.texDisplayProg[i]);
|
|
|
|
fs = GenerateGLSLShader(GetEmbeddedResource(glsl_texremap_frag), shaderType, glslBaseVer,
|
|
defines + texSampleDefines);
|
|
|
|
DebugData.texRemapProg[i] = CreateShaderProgram(vs, fs);
|
|
|
|
BindUBO(DebugData.texRemapProg[i], "TexDisplayUBOData", 0);
|
|
ConfigureTexDisplayProgramBindings(DebugData.texRemapProg[i]);
|
|
}
|
|
|
|
RenderDoc::Inst().SetProgress(LoadProgress::DebugManagerInit, 0.2f);
|
|
|
|
if(GLCoreVersion >= 43 && !IsGLES)
|
|
{
|
|
GLint numsl = 0;
|
|
drv.glGetIntegerv(eGL_NUM_SHADING_LANGUAGE_VERSIONS, &numsl);
|
|
|
|
for(GLint i = 0; i < numsl; i++)
|
|
{
|
|
const char *sl = (const char *)drv.glGetStringi(eGL_SHADING_LANGUAGE_VERSION, (GLuint)i);
|
|
|
|
CheckGLSLVersion(sl, glslVersion);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const char *sl = (const char *)drv.glGetString(eGL_SHADING_LANGUAGE_VERSION);
|
|
|
|
CheckGLSLVersion(sl, glslVersion);
|
|
}
|
|
|
|
DebugData.glslVersion = glslVersion;
|
|
|
|
RDCLOG("GLSL version %d", glslVersion);
|
|
|
|
vs = GenerateGLSLShader(GetEmbeddedResource(glsl_blit_vert), shaderType, glslBaseVer);
|
|
|
|
DebugData.fixedcolFragShader = DebugData.quadoverdrawFragShader = 0;
|
|
DebugData.quadoverdrawResolveProg = 0;
|
|
|
|
if(IsGLES)
|
|
{
|
|
// quad overdraw not supported on GLES.
|
|
// 1.
|
|
// dFdx doesn't support uints - potentially workaroundable with float casts, but highly
|
|
// doubtful GLES compilers will do that properly without exploding.
|
|
// 2.
|
|
// quad overdraw write shader must be linked with user shaders in program, which requires
|
|
// matching ESSL version and features required for it aren't exposed as extensions to older
|
|
// versions but only in core versions.
|
|
}
|
|
else if(HasExt[ARB_shader_image_load_store] && HasExt[ARB_gpu_shader5])
|
|
{
|
|
fs = GenerateGLSLShader(GetEmbeddedResource(glsl_quadresolve_frag), shaderType, glslBaseVer);
|
|
|
|
DebugData.quadoverdrawResolveProg = CreateShaderProgram(vs, fs);
|
|
|
|
GL.glUseProgram(DebugData.quadoverdrawResolveProg);
|
|
|
|
GL.glUniform1i(GL.glGetUniformLocation(DebugData.quadoverdrawResolveProg, "overdrawImage"), 0);
|
|
}
|
|
else
|
|
{
|
|
RDCWARN(
|
|
"GL_ARB_shader_image_load_store/GL_ARB_gpu_shader5 not supported, disabling quad overdraw "
|
|
"feature.");
|
|
m_pDriver->AddDebugMessage(MessageCategory::Portability, MessageSeverity::Medium,
|
|
MessageSource::RuntimeWarning,
|
|
"GL_ARB_shader_image_load_store/GL_ARB_gpu_shader5 not supported, "
|
|
"disabling quad overdraw feature.");
|
|
}
|
|
|
|
fs = GenerateGLSLShader(GetEmbeddedResource(glsl_checkerboard_frag), shaderType, glslBaseVer);
|
|
DebugData.checkerProg = CreateShaderProgram(vs, fs);
|
|
|
|
BindUBO(DebugData.checkerProg, "CheckerboardUBOData", 0);
|
|
|
|
for(size_t intIdx = 0; intIdx < 3; intIdx++)
|
|
{
|
|
for(size_t numViews = 0; numViews < ARRAY_COUNT(DebugData.discardProg[intIdx]); numViews++)
|
|
{
|
|
rdcstr defines;
|
|
|
|
defines += rdcstr("#define SHADER_BASETYPE ") + ToStr(intIdx) + "\n";
|
|
|
|
if(numViews > 0 && IsGLES && HasExt[OVR_multiview])
|
|
defines = StringFormat::Fmt("#define NUM_VIEWS %zu\n", numViews + 1);
|
|
|
|
rdcstr blitvs =
|
|
GenerateGLSLShader(GetEmbeddedResource(glsl_blit_vert), shaderType, glslBaseVer, defines);
|
|
|
|
fs = GenerateGLSLShader(GetEmbeddedResource(glsl_discard_frag), shaderType, glslBaseVer,
|
|
defines);
|
|
DebugData.discardProg[intIdx][numViews] = CreateShaderProgram(blitvs, fs);
|
|
|
|
BindUBO(DebugData.discardProg[intIdx][numViews], "DiscardUBOData", 0);
|
|
|
|
if(!IsGLES)
|
|
{
|
|
rdcstr name = "col0";
|
|
for(GLuint i = 0; i < 8; i++)
|
|
{
|
|
name[3] = char('0' + i);
|
|
GL.glBindFragDataLocation(DebugData.discardProg[intIdx][numViews], i, name.c_str());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
ResourceFormat fmt;
|
|
fmt.type = ResourceFormatType::Regular;
|
|
fmt.compType = CompType::Float;
|
|
fmt.compByteWidth = 4;
|
|
fmt.compCount = 1;
|
|
bytebuf pattern = GetDiscardPattern(DiscardType::InvalidateCall, fmt, 1, true);
|
|
fmt.compType = CompType::UInt;
|
|
pattern.append(GetDiscardPattern(DiscardType::InvalidateCall, fmt, 1, true));
|
|
drv.glGenBuffers(1, &DebugData.discardPatternBuffer);
|
|
drv.glBindBuffer(eGL_UNIFORM_BUFFER, DebugData.discardPatternBuffer);
|
|
drv.glNamedBufferDataEXT(DebugData.discardPatternBuffer, pattern.size(), pattern.data(),
|
|
eGL_STATIC_DRAW);
|
|
}
|
|
|
|
if(HasExt[ARB_geometry_shader4])
|
|
{
|
|
vs = GenerateGLSLShader(GetEmbeddedResource(glsl_mesh_vert), shaderType, glslBaseVer);
|
|
fs = GenerateGLSLShader(GetEmbeddedResource(glsl_trisize_frag), shaderType, glslBaseVer);
|
|
gs = GenerateGLSLShader(GetEmbeddedResource(glsl_trisize_geom), shaderType, glslBaseVer);
|
|
|
|
// create the shaders
|
|
GLuint vsShad = CreateShader(eGL_VERTEX_SHADER, vs);
|
|
GLuint trifsShad = CreateShader(eGL_FRAGMENT_SHADER, fs);
|
|
GLuint gsShad = CreateShader(eGL_GEOMETRY_SHADER, gs);
|
|
|
|
DebugData.trisizeProg = CreateMeshProgram(vsShad, trifsShad, gsShad);
|
|
|
|
// bind trisize-unique viewport size UBO
|
|
BindUBO(DebugData.trisizeProg, "ViewportSizeUBO", 2);
|
|
|
|
GL.glDeleteShader(trifsShad);
|
|
GL.glDeleteShader(gsShad);
|
|
|
|
// we have two fragment shaders, one that reads from the vs outputs and one that reads from the
|
|
// gs outputs
|
|
rdcstr vsfs = GenerateGLSLShader(GetEmbeddedResource(glsl_mesh_frag), shaderType, glslBaseVer,
|
|
"#define SECONDARY_NAME vsout_secondary\n"
|
|
"#define NORM_NAME vsout_norm\n");
|
|
rdcstr gsfs = GenerateGLSLShader(GetEmbeddedResource(glsl_mesh_frag), shaderType, glslBaseVer,
|
|
"#define SECONDARY_NAME gsout_secondary\n"
|
|
"#define NORM_NAME gsout_norm\n");
|
|
gs = GenerateGLSLShader(GetEmbeddedResource(glsl_mesh_geom), shaderType, glslBaseVer);
|
|
|
|
// recreate the shaders
|
|
GLuint vsfsShad = CreateShader(eGL_FRAGMENT_SHADER, vsfs);
|
|
GLuint gsfsShad = CreateShader(eGL_FRAGMENT_SHADER, gsfs);
|
|
gsShad = CreateShader(eGL_GEOMETRY_SHADER, gs);
|
|
|
|
DebugData.meshProg[0] = CreateMeshProgram(vsShad, vsfsShad);
|
|
DebugData.meshgsProg[0] = CreateMeshProgram(vsShad, gsfsShad, gsShad);
|
|
|
|
if(HasExt[ARB_gpu_shader_fp64] && HasExt[ARB_vertex_attrib_64bit])
|
|
{
|
|
rdcstr extensions =
|
|
"#extension GL_ARB_gpu_shader_fp64 : require\n"
|
|
"#extension GL_ARB_vertex_attrib_64bit : require\n";
|
|
|
|
// position only dvec4
|
|
vs = GenerateGLSLShader(GetEmbeddedResource(glsl_mesh_vert), shaderType, glslBaseVer,
|
|
extensions + "#define POSITION_TYPE dvec4\n");
|
|
|
|
// delete old shader and recreate with new source
|
|
GL.glDeleteShader(vsShad);
|
|
vsShad = CreateShader(eGL_VERTEX_SHADER, vs);
|
|
|
|
DebugData.meshProg[1] = CreateMeshProgram(vsShad, vsfsShad);
|
|
DebugData.meshgsProg[1] = CreateMeshProgram(vsShad, gsfsShad, gsShad);
|
|
|
|
// secondary only dvec4
|
|
vs = GenerateGLSLShader(GetEmbeddedResource(glsl_mesh_vert), shaderType, glslBaseVer,
|
|
extensions + "#define SECONDARY_TYPE dvec4\n");
|
|
|
|
// delete old shader and recreate with new source
|
|
GL.glDeleteShader(vsShad);
|
|
vsShad = CreateShader(eGL_VERTEX_SHADER, vs);
|
|
|
|
DebugData.meshProg[2] = CreateMeshProgram(vsShad, vsfsShad);
|
|
DebugData.meshgsProg[2] = CreateMeshProgram(vsShad, gsfsShad, gsShad);
|
|
|
|
// both dvec4
|
|
vs = GenerateGLSLShader(GetEmbeddedResource(glsl_mesh_vert), shaderType, glslBaseVer,
|
|
extensions +
|
|
"#define POSITION_TYPE dvec4\n"
|
|
"#define SECONDARY_TYPE dvec4\n");
|
|
|
|
// delete old shader and recreate with new source
|
|
GL.glDeleteShader(vsShad);
|
|
vsShad = CreateShader(eGL_VERTEX_SHADER, vs);
|
|
|
|
DebugData.meshProg[3] = CreateMeshProgram(vsShad, vsfsShad);
|
|
DebugData.meshgsProg[3] = CreateMeshProgram(vsShad, gsfsShad, gsShad);
|
|
}
|
|
else
|
|
{
|
|
// we don't warn about the lack of double support, assuming that if the driver doesn't support
|
|
// it then it's highly unlikely that the capture uses it.
|
|
DebugData.meshProg[1] = DebugData.meshProg[2] = DebugData.meshProg[3] = 0;
|
|
DebugData.meshgsProg[1] = DebugData.meshgsProg[2] = DebugData.meshgsProg[3] = 0;
|
|
}
|
|
|
|
GL.glDeleteShader(vsShad);
|
|
GL.glDeleteShader(vsfsShad);
|
|
GL.glDeleteShader(gsfsShad);
|
|
GL.glDeleteShader(gsShad);
|
|
}
|
|
else
|
|
{
|
|
vs = GenerateGLSLShader(GetEmbeddedResource(glsl_mesh_vert), shaderType, glslBaseVer);
|
|
|
|
// without a geometry shader, the fragment shader always reads from vs outputs
|
|
fs = GenerateGLSLShader(GetEmbeddedResource(glsl_mesh_frag), shaderType, glslBaseVer,
|
|
"#define SECONDARY_NAME vsout_secondary\n"
|
|
"#define NORM_NAME vsout_norm\n");
|
|
|
|
// create the shaders
|
|
GLuint vsShad = CreateShader(eGL_VERTEX_SHADER, vs);
|
|
GLuint fsShad = CreateShader(eGL_FRAGMENT_SHADER, fs);
|
|
|
|
DebugData.meshProg[0] = CreateMeshProgram(vsShad, fsShad);
|
|
RDCEraseEl(DebugData.meshgsProg);
|
|
DebugData.trisizeProg = 0;
|
|
|
|
const char *warning_msg =
|
|
"GL_ARB_geometry_shader4/GL_EXT_geometry_shader not supported, disabling triangle size and "
|
|
"lit solid shading feature.";
|
|
RDCWARN(warning_msg);
|
|
m_pDriver->AddDebugMessage(MessageCategory::Portability, MessageSeverity::Medium,
|
|
MessageSource::RuntimeWarning, warning_msg);
|
|
|
|
if(HasExt[ARB_gpu_shader_fp64] && HasExt[ARB_vertex_attrib_64bit])
|
|
{
|
|
rdcstr extensions =
|
|
"#extension GL_ARB_gpu_shader_fp64 : require\n"
|
|
"#extension GL_ARB_vertex_attrib_64bit : require\n";
|
|
|
|
// position only dvec4
|
|
vs = GenerateGLSLShader(GetEmbeddedResource(glsl_mesh_vert), shaderType, glslBaseVer,
|
|
extensions + "#define POSITION_TYPE dvec4");
|
|
|
|
// delete old shader and recreate with new source
|
|
GL.glDeleteShader(vsShad);
|
|
vsShad = CreateShader(eGL_VERTEX_SHADER, vs);
|
|
|
|
DebugData.meshProg[1] = CreateMeshProgram(vsShad, fsShad);
|
|
|
|
// secondary only dvec4
|
|
vs = GenerateGLSLShader(GetEmbeddedResource(glsl_mesh_vert), shaderType, glslBaseVer,
|
|
extensions + "#define SECONDARY_TYPE dvec4");
|
|
|
|
// delete old shader and recreate with new source
|
|
GL.glDeleteShader(vsShad);
|
|
vsShad = CreateShader(eGL_VERTEX_SHADER, vs);
|
|
|
|
DebugData.meshProg[2] = CreateMeshProgram(vsShad, fsShad);
|
|
|
|
// both dvec4
|
|
vs = GenerateGLSLShader(GetEmbeddedResource(glsl_mesh_vert), shaderType, glslBaseVer,
|
|
extensions +
|
|
"#define POSITION_TYPE dvec4\n"
|
|
"#define SECONDARY_TYPE dvec4");
|
|
|
|
// delete old shader and recreate with new source
|
|
GL.glDeleteShader(vsShad);
|
|
vsShad = CreateShader(eGL_VERTEX_SHADER, vs);
|
|
|
|
DebugData.meshProg[3] = CreateMeshProgram(vsShad, fsShad);
|
|
}
|
|
else
|
|
{
|
|
// we don't warn about the lack of double support, assuming that if the driver doesn't support
|
|
// it then it's highly unlikely that the capture uses it.
|
|
DebugData.meshProg[1] = DebugData.meshProg[2] = DebugData.meshProg[3] = 0;
|
|
}
|
|
|
|
GL.glDeleteShader(vsShad);
|
|
GL.glDeleteShader(fsShad);
|
|
}
|
|
|
|
RenderDoc::Inst().SetProgress(LoadProgress::DebugManagerInit, 0.4f);
|
|
|
|
drv.glGenBuffers(ARRAY_COUNT(DebugData.UBOs), DebugData.UBOs);
|
|
for(size_t i = 0; i < ARRAY_COUNT(DebugData.UBOs); i++)
|
|
{
|
|
drv.glBindBuffer(eGL_UNIFORM_BUFFER, DebugData.UBOs[i]);
|
|
drv.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");
|
|
}
|
|
|
|
DebugData.overlayTexWidth = DebugData.overlayTexHeight = DebugData.overlayTexSamples = 0;
|
|
DebugData.overlayTex = DebugData.overlayFBO = 0;
|
|
|
|
DebugData.overlayProg = 0;
|
|
|
|
drv.glGenFramebuffers(1, &DebugData.customFBO);
|
|
drv.glBindFramebuffer(eGL_FRAMEBUFFER, DebugData.customFBO);
|
|
DebugData.customTex = 0;
|
|
|
|
drv.glGenFramebuffers(1, &DebugData.pickPixelFBO);
|
|
drv.glBindFramebuffer(eGL_FRAMEBUFFER, DebugData.pickPixelFBO);
|
|
|
|
if(HasExt[ARB_texture_buffer_object])
|
|
{
|
|
drv.glGenBuffers(1, &DebugData.dummyTexBufferStore);
|
|
drv.glBindBuffer(eGL_TEXTURE_BUFFER, DebugData.dummyTexBufferStore);
|
|
drv.glNamedBufferDataEXT(DebugData.dummyTexBufferStore, 32, NULL, eGL_STATIC_DRAW);
|
|
drv.glBindBuffer(eGL_TEXTURE_BUFFER, 0);
|
|
|
|
drv.glGenTextures(1, &DebugData.dummyTexBuffer);
|
|
drv.glBindTexture(eGL_TEXTURE_BUFFER, DebugData.dummyTexBuffer);
|
|
drv.glTextureBufferEXT(DebugData.dummyTexBuffer, eGL_TEXTURE_BUFFER, eGL_RGBA32F,
|
|
DebugData.dummyTexBufferStore);
|
|
drv.glBindTexture(eGL_TEXTURE_BUFFER, 0);
|
|
}
|
|
else
|
|
{
|
|
DebugData.dummyTexBuffer = DebugData.dummyTexBufferStore = 0;
|
|
}
|
|
|
|
drv.glGenTextures(1, &DebugData.pickPixelTex);
|
|
drv.glBindTexture(eGL_TEXTURE_2D, DebugData.pickPixelTex);
|
|
|
|
drv.glTextureImage2DEXT(DebugData.pickPixelTex, eGL_TEXTURE_2D, 0, eGL_RGBA32F, 1, 1, 0, eGL_RGBA,
|
|
eGL_FLOAT, NULL);
|
|
drv.glTextureParameteriEXT(DebugData.pickPixelTex, eGL_TEXTURE_2D, eGL_TEXTURE_MAX_LEVEL, 0);
|
|
drv.glTextureParameteriEXT(DebugData.pickPixelTex, eGL_TEXTURE_2D, eGL_TEXTURE_MIN_FILTER,
|
|
eGL_NEAREST);
|
|
drv.glTextureParameteriEXT(DebugData.pickPixelTex, eGL_TEXTURE_2D, eGL_TEXTURE_MAG_FILTER,
|
|
eGL_NEAREST);
|
|
drv.glTextureParameteriEXT(DebugData.pickPixelTex, eGL_TEXTURE_2D, eGL_TEXTURE_WRAP_S,
|
|
eGL_CLAMP_TO_EDGE);
|
|
drv.glTextureParameteriEXT(DebugData.pickPixelTex, eGL_TEXTURE_2D, eGL_TEXTURE_WRAP_T,
|
|
eGL_CLAMP_TO_EDGE);
|
|
drv.glFramebufferTexture2D(eGL_FRAMEBUFFER, eGL_COLOR_ATTACHMENT0, eGL_TEXTURE_2D,
|
|
DebugData.pickPixelTex, 0);
|
|
|
|
drv.glGenVertexArrays(1, &DebugData.emptyVAO);
|
|
drv.glBindVertexArray(DebugData.emptyVAO);
|
|
|
|
RenderDoc::Inst().SetProgress(LoadProgress::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");
|
|
|
|
if(HasExt[ARB_compute_shader] && HasExt[ARB_shader_storage_buffer_object] &&
|
|
HasExt[ARB_shading_language_420pack])
|
|
{
|
|
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;
|
|
|
|
{
|
|
rdcstr defines;
|
|
defines += rdcstr("#define SHADER_RESTYPE ") + ToStr(t) + "\n";
|
|
defines += rdcstr("#define SHADER_BASETYPE ") + ToStr(i) + "\n";
|
|
defines += texSampleDefines;
|
|
|
|
cs = GenerateGLSLShader(GetEmbeddedResource(glsl_minmaxtile_comp), shaderType,
|
|
glslCSVer, defines);
|
|
|
|
DebugData.minmaxTileProgram[idx] = CreateCShaderProgram(cs);
|
|
|
|
BindUBO(DebugData.minmaxTileProgram[idx], "HistogramUBOData", 2);
|
|
ConfigureTexDisplayProgramBindings(DebugData.minmaxTileProgram[idx]);
|
|
}
|
|
|
|
{
|
|
rdcstr defines;
|
|
defines += rdcstr("#define SHADER_RESTYPE ") + ToStr(t) + "\n";
|
|
defines += rdcstr("#define SHADER_BASETYPE ") + ToStr(i) + "\n";
|
|
defines += texSampleDefines;
|
|
|
|
cs = GenerateGLSLShader(GetEmbeddedResource(glsl_histogram_comp), shaderType, glslCSVer,
|
|
defines);
|
|
|
|
DebugData.histogramProgram[idx] = CreateCShaderProgram(cs);
|
|
|
|
BindUBO(DebugData.histogramProgram[idx], "HistogramUBOData", 2);
|
|
ConfigureTexDisplayProgramBindings(DebugData.histogramProgram[idx]);
|
|
}
|
|
|
|
if(t == 1)
|
|
{
|
|
rdcstr defines;
|
|
defines += rdcstr("#define SHADER_RESTYPE ") + ToStr(t) + "\n";
|
|
defines += rdcstr("#define SHADER_BASETYPE ") + ToStr(i) + "\n";
|
|
|
|
cs = GenerateGLSLShader(GetEmbeddedResource(glsl_minmaxresult_comp), shaderType,
|
|
glslCSVer, defines);
|
|
|
|
DebugData.minmaxResultProgram[i] = CreateCShaderProgram(cs);
|
|
|
|
BindUBO(DebugData.minmaxResultProgram[i], "HistogramUBOData", 2);
|
|
ConfigureTexDisplayProgramBindings(DebugData.minmaxResultProgram[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RDCWARN(
|
|
"GL_ARB_compute_shader or ARB_shader_storage_buffer_object not supported, disabling "
|
|
"min/max and histogram features.");
|
|
m_pDriver->AddDebugMessage(MessageCategory::Portability, MessageSeverity::Medium,
|
|
MessageSource::RuntimeWarning,
|
|
"GL_ARB_compute_shader or ARB_shader_storage_buffer_object not "
|
|
"supported, disabling min/max and histogram features.");
|
|
}
|
|
|
|
drv.glGenBuffers(1, &DebugData.minmaxTileResult);
|
|
drv.glGenBuffers(1, &DebugData.minmaxResult);
|
|
drv.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;
|
|
|
|
drv.glNamedBufferDataEXT(DebugData.minmaxTileResult, byteSize, NULL, eGL_DYNAMIC_DRAW);
|
|
drv.glNamedBufferDataEXT(DebugData.minmaxResult, sizeof(Vec4f) * 2, NULL, eGL_DYNAMIC_READ);
|
|
drv.glNamedBufferDataEXT(DebugData.histogramBuf, sizeof(uint32_t) * HGRAM_NUM_BUCKETS, NULL,
|
|
eGL_DYNAMIC_READ);
|
|
}
|
|
|
|
if(HasExt[ARB_compute_shader] && HasExt[ARB_shading_language_420pack])
|
|
{
|
|
cs = GenerateGLSLShader(GetEmbeddedResource(glsl_mesh_comp), shaderType, glslCSVer);
|
|
DebugData.meshPickProgram = CreateCShaderProgram(cs);
|
|
|
|
BindUBO(DebugData.meshPickProgram, "MeshPickUBOData", 0);
|
|
}
|
|
else
|
|
{
|
|
DebugData.meshPickProgram = 0;
|
|
RDCWARN("GL_ARB_compute_shader not supported, disabling mesh picking.");
|
|
m_pDriver->AddDebugMessage(MessageCategory::Portability, MessageSeverity::Medium,
|
|
MessageSource::RuntimeWarning,
|
|
"GL_ARB_compute_shader not supported, disabling mesh picking.");
|
|
}
|
|
|
|
RenderDoc::Inst().SetProgress(LoadProgress::DebugManagerInit, 0.8f);
|
|
|
|
DebugData.pickResultBuf = 0;
|
|
|
|
if(DebugData.meshPickProgram)
|
|
{
|
|
drv.glGenBuffers(1, &DebugData.pickResultBuf);
|
|
drv.glBindBuffer(eGL_SHADER_STORAGE_BUFFER, DebugData.pickResultBuf);
|
|
drv.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;
|
|
}
|
|
|
|
drv.glGenVertexArrays(1, &DebugData.meshVAO);
|
|
drv.glBindVertexArray(DebugData.meshVAO);
|
|
|
|
drv.glGenBuffers(1, &DebugData.axisFrustumBuffer);
|
|
drv.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,
|
|
};
|
|
|
|
drv.glNamedBufferDataEXT(DebugData.axisFrustumBuffer, sizeof(axisFrustum), axisFrustum,
|
|
eGL_STATIC_DRAW);
|
|
|
|
drv.glGenVertexArrays(1, &DebugData.axisVAO);
|
|
drv.glBindVertexArray(DebugData.axisVAO);
|
|
drv.glVertexAttribPointer(0, 3, eGL_FLOAT, GL_FALSE, sizeof(Vec3f), NULL);
|
|
drv.glEnableVertexAttribArray(0);
|
|
|
|
drv.glGenVertexArrays(1, &DebugData.frustumVAO);
|
|
drv.glBindVertexArray(DebugData.frustumVAO);
|
|
drv.glVertexAttribPointer(0, 3, eGL_FLOAT, GL_FALSE, sizeof(Vec3f),
|
|
(const void *)(sizeof(Vec3f) * 6));
|
|
drv.glEnableVertexAttribArray(0);
|
|
|
|
drv.glGenVertexArrays(1, &DebugData.triHighlightVAO);
|
|
drv.glBindVertexArray(DebugData.triHighlightVAO);
|
|
|
|
drv.glGenBuffers(1, &DebugData.triHighlightBuffer);
|
|
drv.glBindBuffer(eGL_ARRAY_BUFFER, DebugData.triHighlightBuffer);
|
|
|
|
drv.glNamedBufferDataEXT(DebugData.triHighlightBuffer, sizeof(Vec4f) * 24, NULL, eGL_DYNAMIC_DRAW);
|
|
|
|
drv.glVertexAttribPointer(0, 4, eGL_FLOAT, GL_FALSE, sizeof(Vec4f), NULL);
|
|
drv.glEnableVertexAttribArray(0);
|
|
|
|
MakeCurrentReplayContext(&m_ReplayCtx);
|
|
|
|
// try to identify the GPU we're running on.
|
|
{
|
|
RDCEraseEl(m_DriverInfo);
|
|
|
|
const char *vendor = (const char *)drv.glGetString(eGL_VENDOR);
|
|
const char *renderer = (const char *)drv.glGetString(eGL_RENDERER);
|
|
const char *version = (const char *)drv.glGetString(eGL_VERSION);
|
|
|
|
// we're just doing substring searches, so combine both for ease.
|
|
rdcstr combined = (vendor ? vendor : "");
|
|
combined += " ";
|
|
combined += (renderer ? renderer : "");
|
|
|
|
// make lowercase, for case-insensitive matching, and add preceding/trailing space for easier
|
|
// 'word' matching
|
|
combined = " " + strlower(combined) + " ";
|
|
|
|
RDCDEBUG("Identifying vendor from '%s'", combined.c_str());
|
|
|
|
struct pattern
|
|
{
|
|
const char *search;
|
|
GPUVendor vendor;
|
|
} patterns[] = {
|
|
{" arm ", GPUVendor::ARM},
|
|
{" mali ", GPUVendor::ARM},
|
|
{" mali-", GPUVendor::ARM},
|
|
{" amd ", GPUVendor::AMD},
|
|
{"advanced micro devices", GPUVendor::AMD},
|
|
{"ati technologies", GPUVendor::AMD},
|
|
{"radeon", GPUVendor::AMD},
|
|
{"broadcom", GPUVendor::Broadcom},
|
|
{"imagination", GPUVendor::Imagination},
|
|
{"powervr", GPUVendor::Imagination},
|
|
{"intel", GPUVendor::Intel},
|
|
{"geforce", GPUVendor::nVidia},
|
|
{"quadro", GPUVendor::nVidia},
|
|
{"nouveau", GPUVendor::nVidia},
|
|
{"nvidia", GPUVendor::nVidia},
|
|
{"adreno", GPUVendor::Qualcomm},
|
|
{"qualcomm", GPUVendor::Qualcomm},
|
|
{"vivante", GPUVendor::Verisilicon},
|
|
{"llvmpipe", GPUVendor::Software},
|
|
{"softpipe", GPUVendor::Software},
|
|
{"bluestacks", GPUVendor::Software},
|
|
};
|
|
|
|
for(const pattern &p : patterns)
|
|
{
|
|
if(combined.contains(p.search))
|
|
{
|
|
if(m_DriverInfo.vendor == GPUVendor::Unknown)
|
|
{
|
|
m_DriverInfo.vendor = p.vendor;
|
|
}
|
|
else
|
|
{
|
|
// either we already found this with another pattern, or we've identified two patterns and
|
|
// it's ambiguous. Keep the first one we found, arbitrarily, but print a warning.
|
|
if(m_DriverInfo.vendor != p.vendor)
|
|
{
|
|
RDCWARN("Already identified '%s' as %s, but now identified as %s", combined.c_str(),
|
|
ToStr(m_DriverInfo.vendor).c_str(), ToStr(p.vendor).c_str());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
RDCDEBUG("Identified GPU vendor '%s'", ToStr(m_DriverInfo.vendor).c_str());
|
|
|
|
rdcstr versionString = version;
|
|
|
|
versionString += " / ";
|
|
versionString += renderer ? renderer : "";
|
|
versionString += " / ";
|
|
versionString += vendor ? vendor : "";
|
|
|
|
versionString.resize(RDCMIN(versionString.size(), ARRAY_COUNT(m_DriverInfo.version) - 1));
|
|
memcpy(m_DriverInfo.version, versionString.c_str(), versionString.size());
|
|
}
|
|
|
|
// 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.
|
|
|
|
if(HasExt[ARB_transform_feedback2])
|
|
drv.glGenTransformFeedbacks(1, &DebugData.feedbackObj);
|
|
drv.glGenBuffers(1, &DebugData.feedbackBuffer);
|
|
DebugData.feedbackQueries.push_back(0);
|
|
drv.glGenQueries(1, &DebugData.feedbackQueries[0]);
|
|
|
|
if(HasExt[ARB_transform_feedback2])
|
|
drv.glBindTransformFeedback(eGL_TRANSFORM_FEEDBACK, DebugData.feedbackObj);
|
|
drv.glBindBuffer(eGL_TRANSFORM_FEEDBACK_BUFFER, DebugData.feedbackBuffer);
|
|
drv.glNamedBufferDataEXT(DebugData.feedbackBuffer, (GLsizeiptr)DebugData.feedbackBufferSize, NULL,
|
|
eGL_DYNAMIC_READ);
|
|
drv.glBindBufferBase(eGL_TRANSFORM_FEEDBACK_BUFFER, 0, DebugData.feedbackBuffer);
|
|
if(HasExt[ARB_transform_feedback2])
|
|
drv.glBindTransformFeedback(eGL_TRANSFORM_FEEDBACK, 0);
|
|
|
|
RenderDoc::Inst().SetProgress(LoadProgress::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(MessageCategory::Portability, MessageSeverity::Medium,
|
|
MessageSource::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(
|
|
MessageCategory::Portability, MessageSeverity::Medium, MessageSource::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] ||
|
|
!HasExt[ARB_shading_language_420pack])
|
|
{
|
|
RDCWARN(
|
|
"Don't have shader image load/store or compute shaders, functionality will be degraded.");
|
|
m_Degraded = true;
|
|
}
|
|
|
|
#if ENABLED(RDOC_APPLE)
|
|
// temporary hack - just never consider apple degraded, since there's never going to be an
|
|
// improvement so there's no point warning users.
|
|
m_Degraded = false;
|
|
#endif
|
|
|
|
m_pDriver->PopInternalShader();
|
|
}
|
|
|
|
void GLReplay::DeleteDebugData()
|
|
{
|
|
WrappedOpenGL &drv = *m_pDriver;
|
|
|
|
MakeCurrentReplayContext(&m_ReplayCtx);
|
|
|
|
if(HasExt[KHR_debug])
|
|
{
|
|
GL.glDebugMessageCallback(NULL, NULL);
|
|
}
|
|
|
|
if(DebugData.overlayProg)
|
|
drv.glDeleteProgram(DebugData.overlayProg);
|
|
|
|
for(size_t b = 0; b < 3; b++)
|
|
for(size_t i = 0; i < ARRAY_COUNT(DebugData.discardProg[b]); i++)
|
|
if(DebugData.discardProg[b][i])
|
|
drv.glDeleteProgram(DebugData.discardProg[b][i]);
|
|
|
|
if(HasExt[ARB_transform_feedback2])
|
|
drv.glDeleteTransformFeedbacks(1, &DebugData.feedbackObj);
|
|
drv.glDeleteBuffers(1, &DebugData.feedbackBuffer);
|
|
drv.glDeleteQueries((GLsizei)DebugData.feedbackQueries.size(), DebugData.feedbackQueries.data());
|
|
|
|
MakeCurrentReplayContext(m_DebugCtx);
|
|
|
|
if(HasExt[KHR_debug])
|
|
{
|
|
GL.glDebugMessageCallback(NULL, NULL);
|
|
}
|
|
|
|
ClearPostVSCache();
|
|
|
|
drv.glDeleteFramebuffers(1, &DebugData.overlayFBO);
|
|
drv.glDeleteTextures(1, &DebugData.overlayTex);
|
|
|
|
if(DebugData.quadoverdrawFragShader)
|
|
drv.glDeleteShader(DebugData.quadoverdrawFragShader);
|
|
if(DebugData.quadoverdrawFragShaderSPIRV)
|
|
drv.glDeleteShader(DebugData.quadoverdrawFragShaderSPIRV);
|
|
if(DebugData.quadoverdrawResolveProg)
|
|
drv.glDeleteProgram(DebugData.quadoverdrawResolveProg);
|
|
|
|
if(DebugData.texDisplayVertexShader)
|
|
drv.glDeleteShader(DebugData.texDisplayVertexShader);
|
|
for(int i = 0; i < 3; i++)
|
|
{
|
|
if(DebugData.texDisplayProg[i])
|
|
drv.glDeleteProgram(DebugData.texDisplayProg[i]);
|
|
if(DebugData.texRemapProg[i])
|
|
drv.glDeleteProgram(DebugData.texRemapProg[i]);
|
|
}
|
|
|
|
if(DebugData.checkerProg)
|
|
drv.glDeleteProgram(DebugData.checkerProg);
|
|
if(DebugData.fixedcolFragShader)
|
|
drv.glDeleteShader(DebugData.fixedcolFragShader);
|
|
if(DebugData.fixedcolFragShaderSPIRV)
|
|
drv.glDeleteShader(DebugData.fixedcolFragShaderSPIRV);
|
|
|
|
for(size_t i = 0; i < ARRAY_COUNT(DebugData.meshProg); i++)
|
|
{
|
|
if(DebugData.meshProg[i])
|
|
drv.glDeleteProgram(DebugData.meshProg[i]);
|
|
if(DebugData.meshgsProg[i])
|
|
drv.glDeleteProgram(DebugData.meshgsProg[i]);
|
|
}
|
|
if(DebugData.trisizeProg)
|
|
drv.glDeleteProgram(DebugData.trisizeProg);
|
|
|
|
drv.glDeleteBuffers(ARRAY_COUNT(DebugData.UBOs), DebugData.UBOs);
|
|
drv.glDeleteFramebuffers(1, &DebugData.pickPixelFBO);
|
|
drv.glDeleteTextures(1, &DebugData.pickPixelTex);
|
|
|
|
drv.glDeleteTextures(1, &DebugData.dummyTexBuffer);
|
|
drv.glDeleteBuffers(1, &DebugData.dummyTexBufferStore);
|
|
|
|
drv.glDeleteFramebuffers(1, &DebugData.customFBO);
|
|
drv.glDeleteTextures(1, &DebugData.customTex);
|
|
|
|
drv.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;
|
|
|
|
if(DebugData.minmaxTileProgram[idx])
|
|
drv.glDeleteProgram(DebugData.minmaxTileProgram[idx]);
|
|
if(DebugData.histogramProgram[idx])
|
|
drv.glDeleteProgram(DebugData.histogramProgram[idx]);
|
|
|
|
if(t == 1)
|
|
{
|
|
if(DebugData.minmaxResultProgram[i])
|
|
drv.glDeleteProgram(DebugData.minmaxResultProgram[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(DebugData.meshPickProgram)
|
|
drv.glDeleteProgram(DebugData.meshPickProgram);
|
|
drv.glDeleteBuffers(1, &DebugData.pickIBBuf);
|
|
drv.glDeleteBuffers(1, &DebugData.pickVBBuf);
|
|
drv.glDeleteBuffers(1, &DebugData.pickResultBuf);
|
|
|
|
drv.glDeleteBuffers(1, &DebugData.minmaxTileResult);
|
|
drv.glDeleteBuffers(1, &DebugData.minmaxResult);
|
|
drv.glDeleteBuffers(1, &DebugData.histogramBuf);
|
|
|
|
drv.glDeleteVertexArrays(1, &DebugData.meshVAO);
|
|
drv.glDeleteVertexArrays(1, &DebugData.axisVAO);
|
|
drv.glDeleteVertexArrays(1, &DebugData.frustumVAO);
|
|
drv.glDeleteVertexArrays(1, &DebugData.triHighlightVAO);
|
|
|
|
drv.glDeleteBuffers(1, &DebugData.axisFrustumBuffer);
|
|
drv.glDeleteBuffers(1, &DebugData.triHighlightBuffer);
|
|
}
|
|
|
|
GLReplay::TextureSamplerState GLReplay::SetSamplerParams(GLenum target, GLuint texname,
|
|
TextureSamplerMode mode)
|
|
{
|
|
TextureSamplerState ret;
|
|
|
|
if(target == eGL_TEXTURE_BUFFER || target == eGL_TEXTURE_2D_MULTISAMPLE ||
|
|
target == eGL_TEXTURE_2D_MULTISAMPLE_ARRAY)
|
|
return ret;
|
|
|
|
// fetch previous texture sampler settings
|
|
GL.glGetTextureParameterivEXT(texname, target, eGL_TEXTURE_MIN_FILTER, (GLint *)&ret.minFilter);
|
|
GL.glGetTextureParameterivEXT(texname, target, eGL_TEXTURE_MAG_FILTER, (GLint *)&ret.magFilter);
|
|
GL.glGetTextureParameterivEXT(texname, target, eGL_TEXTURE_WRAP_S, (GLint *)&ret.wrapS);
|
|
GL.glGetTextureParameterivEXT(texname, target, eGL_TEXTURE_WRAP_T, (GLint *)&ret.wrapT);
|
|
GL.glGetTextureParameterivEXT(texname, target, eGL_TEXTURE_WRAP_R, (GLint *)&ret.wrapR);
|
|
GL.glGetTextureParameterivEXT(texname, target, eGL_TEXTURE_COMPARE_MODE, (GLint *)&ret.compareMode);
|
|
|
|
// disable depth comparison
|
|
GLenum compareMode = eGL_NONE;
|
|
GL.glTextureParameterivEXT(texname, target, eGL_TEXTURE_COMPARE_MODE, (GLint *)&compareMode);
|
|
|
|
// always want to clamp
|
|
GLenum param = eGL_CLAMP_TO_EDGE;
|
|
GL.glTextureParameterivEXT(texname, target, eGL_TEXTURE_WRAP_S, (GLint *)¶m);
|
|
GL.glTextureParameterivEXT(texname, target, eGL_TEXTURE_WRAP_T, (GLint *)¶m);
|
|
GL.glTextureParameterivEXT(texname, target, eGL_TEXTURE_WRAP_R, (GLint *)¶m);
|
|
|
|
// depending on the mode, set min/mag filter
|
|
GLenum minFilter = eGL_NEAREST;
|
|
GLenum magFilter = eGL_NEAREST;
|
|
|
|
switch(mode)
|
|
{
|
|
case TextureSamplerMode::Point:
|
|
minFilter = eGL_NEAREST_MIPMAP_NEAREST;
|
|
magFilter = eGL_NEAREST;
|
|
break;
|
|
case TextureSamplerMode::PointNoMip:
|
|
minFilter = eGL_NEAREST;
|
|
magFilter = eGL_NEAREST;
|
|
break;
|
|
case TextureSamplerMode::Linear:
|
|
minFilter = eGL_LINEAR;
|
|
magFilter = eGL_LINEAR;
|
|
break;
|
|
}
|
|
|
|
GL.glTextureParameterivEXT(texname, target, eGL_TEXTURE_MIN_FILTER, (GLint *)&minFilter);
|
|
GL.glTextureParameterivEXT(texname, target, eGL_TEXTURE_MAG_FILTER, (GLint *)&magFilter);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void GLReplay::RestoreSamplerParams(GLenum target, GLuint texname, TextureSamplerState state)
|
|
{
|
|
if(target == eGL_TEXTURE_BUFFER || target == eGL_TEXTURE_2D_MULTISAMPLE ||
|
|
target == eGL_TEXTURE_2D_MULTISAMPLE_ARRAY)
|
|
return;
|
|
|
|
GL.glTextureParameterivEXT(texname, target, eGL_TEXTURE_WRAP_S, (GLint *)&state.wrapS);
|
|
GL.glTextureParameterivEXT(texname, target, eGL_TEXTURE_WRAP_T, (GLint *)&state.wrapT);
|
|
GL.glTextureParameterivEXT(texname, target, eGL_TEXTURE_WRAP_R, (GLint *)&state.wrapR);
|
|
GL.glTextureParameterivEXT(texname, target, eGL_TEXTURE_MIN_FILTER, (GLint *)&state.minFilter);
|
|
GL.glTextureParameterivEXT(texname, target, eGL_TEXTURE_MAG_FILTER, (GLint *)&state.magFilter);
|
|
GL.glTextureParameterivEXT(texname, target, eGL_TEXTURE_COMPARE_MODE, (GLint *)&state.compareMode);
|
|
}
|
|
|
|
void GLReplay::FillWithDiscardPattern(DiscardType type, GLuint framebuffer, GLsizei numAttachments,
|
|
const GLenum *attachments, GLint x, GLint y, GLsizei width,
|
|
GLsizei height)
|
|
{
|
|
RDCASSERT(type == DiscardType::InvalidateCall);
|
|
|
|
WrappedOpenGL &drv = *m_pDriver;
|
|
|
|
GLMarkerRegion region("FillWithDiscardPattern FBO");
|
|
|
|
GLRenderState rs;
|
|
rs.FetchState(&drv);
|
|
|
|
// we use the original framebuffer, and mask off any attachments that shouldn't be discarded
|
|
drv.glBindFramebuffer(eGL_DRAW_FRAMEBUFFER, framebuffer);
|
|
|
|
int numviews = 1;
|
|
|
|
if(IsGLES && HasExt[OVR_multiview])
|
|
{
|
|
GL.glGetNamedFramebufferAttachmentParameterivEXT(
|
|
framebuffer, eGL_COLOR_ATTACHMENT0, eGL_FRAMEBUFFER_ATTACHMENT_TEXTURE_NUM_VIEWS_OVR,
|
|
&numviews);
|
|
}
|
|
|
|
drv.glBindBufferBase(eGL_UNIFORM_BUFFER, 0, DebugData.discardPatternBuffer);
|
|
|
|
drv.glDisable(eGL_BLEND);
|
|
drv.glDisable(eGL_CULL_FACE);
|
|
drv.glEnable(eGL_DEPTH_TEST);
|
|
drv.glEnable(eGL_STENCIL_TEST);
|
|
drv.glDepthFunc(eGL_ALWAYS);
|
|
drv.glStencilFunc(eGL_ALWAYS, 0, 0xff);
|
|
if(!IsGLES)
|
|
drv.glPolygonMode(eGL_FRONT_AND_BACK, eGL_FILL);
|
|
|
|
// scissor to the rect
|
|
drv.glEnable(eGL_SCISSOR_TEST);
|
|
drv.glScissor(x, y, width, height);
|
|
|
|
GLint maxview[2] = {2048, 2048};
|
|
drv.glGetIntegerv(eGL_MAX_VIEWPORT_DIMS, maxview);
|
|
drv.glViewport(0, 0, maxview[0], maxview[1]);
|
|
|
|
// disable all masks at first
|
|
drv.glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
|
|
drv.glDepthMask(GL_FALSE);
|
|
drv.glStencilMask(0);
|
|
|
|
int intIdx = -1;
|
|
|
|
// enable masks specified by attachments
|
|
bool stencil = false;
|
|
for(GLsizei i = 0; i < numAttachments; i++)
|
|
{
|
|
if(attachments[i] == eGL_DEPTH_STENCIL_ATTACHMENT)
|
|
{
|
|
drv.glDepthMask(GL_TRUE);
|
|
drv.glStencilMask(0xff);
|
|
stencil = true;
|
|
}
|
|
else if(attachments[i] == eGL_DEPTH_ATTACHMENT)
|
|
{
|
|
drv.glDepthMask(GL_TRUE);
|
|
}
|
|
else if(attachments[i] == eGL_STENCIL_ATTACHMENT)
|
|
{
|
|
drv.glStencilMask(0xff);
|
|
stencil = true;
|
|
}
|
|
else
|
|
{
|
|
drv.glColorMaski(attachments[i] - eGL_COLOR_ATTACHMENT0, GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
|
|
|
if(intIdx == -1)
|
|
{
|
|
GLuint att;
|
|
drv.glGetFramebufferAttachmentParameteriv(eGL_DRAW_FRAMEBUFFER, attachments[i],
|
|
eGL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME,
|
|
(GLint *)&att);
|
|
GLenum attType;
|
|
drv.glGetFramebufferAttachmentParameteriv(eGL_DRAW_FRAMEBUFFER, attachments[i],
|
|
eGL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE,
|
|
(GLint *)&attType);
|
|
|
|
ResourceId id = drv.GetResourceManager()->GetResID(attType == eGL_RENDERBUFFER
|
|
? RenderbufferRes(drv.GetCtx(), att)
|
|
: TextureRes(drv.GetCtx(), att));
|
|
|
|
GLenum fmt = m_pDriver->m_Textures[id].internalFormat;
|
|
|
|
if(IsUIntFormat(fmt))
|
|
intIdx = 1;
|
|
else if(IsSIntFormat(fmt))
|
|
intIdx = 2;
|
|
else
|
|
intIdx = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(intIdx == -1)
|
|
intIdx = 0;
|
|
|
|
GLuint prog = DebugData.discardProg[intIdx][RDCCLAMP(numviews, 1, 4) - 1];
|
|
|
|
drv.glUseProgram(prog);
|
|
|
|
GLint loc = drv.glGetUniformLocation(prog, "flags");
|
|
|
|
uint32_t flags = 0U;
|
|
|
|
if(height == 1)
|
|
flags |= 0x10U;
|
|
|
|
if(stencil)
|
|
{
|
|
drv.glStencilOp(eGL_REPLACE, eGL_REPLACE, eGL_REPLACE);
|
|
|
|
drv.glStencilFunc(eGL_ALWAYS, 0, 0xff);
|
|
drv.glProgramUniform1ui(prog, loc, flags | 1U);
|
|
drv.glDrawArrays(eGL_TRIANGLE_STRIP, 0, 4);
|
|
|
|
drv.glStencilFunc(eGL_ALWAYS, 0xff, 0xff);
|
|
drv.glProgramUniform1ui(prog, loc, flags | 2U);
|
|
drv.glDrawArrays(eGL_TRIANGLE_STRIP, 0, 4);
|
|
}
|
|
else
|
|
{
|
|
drv.glProgramUniform1ui(prog, loc, flags);
|
|
|
|
// if we're not writing stencil we can just do one draw
|
|
drv.glDrawArrays(eGL_TRIANGLE_STRIP, 0, 4);
|
|
}
|
|
|
|
rs.ApplyState(&drv);
|
|
}
|
|
|
|
void GLReplay::FillWithDiscardPattern(DiscardType type, ResourceId id, GLuint mip, GLint xoffset,
|
|
GLint yoffset, GLint zoffset, GLsizei width, GLsizei height,
|
|
GLsizei depth)
|
|
{
|
|
RDCASSERT(type == DiscardType::InvalidateCall);
|
|
|
|
WrappedOpenGL &drv = *m_pDriver;
|
|
|
|
GLMarkerRegion region("FillWithDiscardPattern Texture");
|
|
|
|
auto &texDetails = drv.m_Textures[id];
|
|
|
|
GLenum fmt = texDetails.internalFormat;
|
|
|
|
bytebuf &pattern = m_DiscardPatterns[fmt];
|
|
|
|
GLenum target = texDetails.curType;
|
|
|
|
if(pattern.empty())
|
|
pattern = GetDiscardPattern(type, MakeResourceFormat(target, fmt), 1, true);
|
|
|
|
bool compressed = IsCompressedFormat(fmt);
|
|
|
|
if(target == eGL_TEXTURE_CUBE_MAP)
|
|
depth = RDCMIN(depth, 6);
|
|
else
|
|
depth = RDCMIN(texDetails.depth, depth);
|
|
height = RDCMIN(texDetails.height, height);
|
|
width = RDCMIN(texDetails.width, width);
|
|
|
|
GLint dim = texDetails.dimension;
|
|
|
|
GLint oldRowLength = 0;
|
|
GL.glGetIntegerv(eGL_UNPACK_ROW_LENGTH, &oldRowLength);
|
|
|
|
// ensure even if we're uploading a sub-rect that the row length is the same.
|
|
GL.glPixelStorei(eGL_UNPACK_ROW_LENGTH, DiscardPatternWidth);
|
|
|
|
GLuint tex = texDetails.resource.name;
|
|
|
|
GLenum format = eGL_NONE;
|
|
GLenum datatype = eGL_NONE;
|
|
|
|
if(!compressed)
|
|
{
|
|
format = GetBaseFormat(fmt);
|
|
datatype = GetDataType(fmt);
|
|
}
|
|
|
|
for(GLsizei zc = 0; zc < depth; zc++)
|
|
{
|
|
GLsizei z = zoffset + zc;
|
|
|
|
if(texDetails.curType == eGL_TEXTURE_CUBE_MAP)
|
|
{
|
|
GLenum targets[] = {
|
|
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,
|
|
};
|
|
|
|
target = targets[z % 6];
|
|
}
|
|
|
|
for(GLsizei yc = 0; yc < height; yc += DiscardPatternHeight)
|
|
{
|
|
GLsizei y = yoffset + yc;
|
|
|
|
for(GLsizei xc = 0; xc < width; xc += DiscardPatternWidth)
|
|
{
|
|
GLsizei x = xoffset + xc;
|
|
|
|
GLsizei subwidth = RDCMIN((GLsizei)DiscardPatternWidth, texDetails.width - x);
|
|
GLsizei subheight = RDCMIN((GLsizei)DiscardPatternHeight, texDetails.height - y);
|
|
|
|
if(compressed)
|
|
{
|
|
GLsizei dataSize = (GLsizei)GetCompressedByteSize(subwidth, subheight, 1, fmt);
|
|
|
|
if(dim == 1)
|
|
GL.glCompressedTextureSubImage1DEXT(tex, target, mip, x, subwidth, fmt, dataSize,
|
|
pattern.data());
|
|
else if(dim == 2)
|
|
GL.glCompressedTextureSubImage2DEXT(tex, target, mip, x, y, subwidth, subheight, fmt,
|
|
dataSize, pattern.data());
|
|
else if(dim == 3)
|
|
GL.glCompressedTextureSubImage3DEXT(tex, target, mip, x, y, z, subwidth, subheight, 1,
|
|
fmt, dataSize, pattern.data());
|
|
}
|
|
else
|
|
{
|
|
if(dim == 1)
|
|
GL.glTextureSubImage1DEXT(tex, target, mip, x, subwidth, format, datatype,
|
|
pattern.data());
|
|
else if(dim == 2)
|
|
GL.glTextureSubImage2DEXT(tex, target, mip, x, y, subwidth, subheight, format, datatype,
|
|
pattern.data());
|
|
else if(dim == 3)
|
|
GL.glTextureSubImage3DEXT(tex, target, mip, x, y, z, subwidth, subheight, 1, format,
|
|
datatype, pattern.data());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
GL.glPixelStorei(eGL_UNPACK_ROW_LENGTH, oldRowLength);
|
|
}
|
|
|
|
void GLReplay::PickPixel(ResourceId texture, uint32_t x, uint32_t y, const Subresource &sub,
|
|
CompType typeCast, float pixel[4])
|
|
{
|
|
WrappedOpenGL &drv = *m_pDriver;
|
|
|
|
MakeCurrentReplayContext(m_DebugCtx);
|
|
|
|
drv.glBindFramebuffer(eGL_FRAMEBUFFER, DebugData.pickPixelFBO);
|
|
drv.glBindFramebuffer(eGL_READ_FRAMEBUFFER, DebugData.pickPixelFBO);
|
|
|
|
pixel[0] = pixel[1] = pixel[2] = pixel[3] = 0.0f;
|
|
drv.glClearBufferfv(eGL_COLOR, 0, pixel);
|
|
|
|
DebugData.outWidth = DebugData.outHeight = 1.0f;
|
|
drv.glViewport(0, 0, 1, 1);
|
|
|
|
TextureDisplay texDisplay;
|
|
|
|
texDisplay.red = texDisplay.green = texDisplay.blue = texDisplay.alpha = true;
|
|
texDisplay.flipY = false;
|
|
texDisplay.hdrMultiplier = -1.0f;
|
|
texDisplay.linearDisplayAsGamma = true;
|
|
texDisplay.subresource = sub;
|
|
texDisplay.customShaderId = ResourceId();
|
|
texDisplay.rangeMin = 0.0f;
|
|
texDisplay.rangeMax = 1.0f;
|
|
texDisplay.scale = 1.0f;
|
|
texDisplay.resourceId = texture;
|
|
texDisplay.typeCast = typeCast;
|
|
texDisplay.rawOutput = true;
|
|
texDisplay.xOffset = -float(x << sub.mip);
|
|
texDisplay.yOffset = -float(y << sub.mip);
|
|
|
|
RenderTextureInternal(texDisplay, eTexDisplay_MipShift);
|
|
|
|
drv.glReadPixels(0, 0, 1, 1, eGL_RGBA, eGL_FLOAT, (void *)pixel);
|
|
|
|
if(!HasExt[ARB_gpu_shader5])
|
|
{
|
|
auto &texDetails = m_pDriver->m_Textures[texDisplay.resourceId];
|
|
|
|
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_DEPTH_STENCIL ||
|
|
texDetails.internalFormat == eGL_STENCIL_INDEX8)
|
|
{
|
|
texDisplay.red = texDisplay.blue = texDisplay.alpha = false;
|
|
|
|
RenderTextureInternal(texDisplay, eTexDisplay_MipShift);
|
|
|
|
uint32_t stencilpixel[4];
|
|
drv.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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool GLReplay::GetMinMax(ResourceId texid, const Subresource &sub, CompType typeCast, float *minval,
|
|
float *maxval)
|
|
{
|
|
auto &texDetails = m_pDriver->m_Textures[texid];
|
|
|
|
if(!IsCompressedFormat(texDetails.internalFormat) &&
|
|
GetBaseFormat(texDetails.internalFormat) == eGL_DEPTH_STENCIL)
|
|
{
|
|
// for depth/stencil we need to run the code twice - once to fetch depth and once to fetch
|
|
// stencil - since we can't process float depth and int stencil at the same time
|
|
Vec4f depth[2] = {
|
|
{0.0f, 0.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 1.0f, 1.0f},
|
|
};
|
|
Vec4u stencil[2] = {{0, 0, 0, 0}, {1, 1, 1, 1}};
|
|
|
|
bool success = GetMinMax(texid, sub, typeCast, false, &depth[0].x, &depth[1].x);
|
|
|
|
if(!success)
|
|
return false;
|
|
|
|
success = GetMinMax(texid, sub, typeCast, true, (float *)&stencil[0].x, (float *)&stencil[1].x);
|
|
|
|
if(!success)
|
|
return false;
|
|
|
|
// copy across into green channel, casting up to float, dividing by the range for this texture
|
|
float rangeScale = 1.0f;
|
|
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"); DELIBERATE_FALLTHROUGH();
|
|
case eGL_DEPTH24_STENCIL8:
|
|
case eGL_DEPTH32F_STENCIL8:
|
|
case eGL_DEPTH_STENCIL:
|
|
case eGL_STENCIL_INDEX8: rangeScale = 255.0f; break;
|
|
case eGL_STENCIL_INDEX16: rangeScale = 65535.0f; break;
|
|
}
|
|
|
|
depth[0].y = float(stencil[0].x) / rangeScale;
|
|
depth[1].y = float(stencil[1].x) / rangeScale;
|
|
|
|
memcpy(minval, &depth[0], sizeof(depth[0]));
|
|
memcpy(maxval, &depth[1], sizeof(depth[1]));
|
|
|
|
return true;
|
|
}
|
|
|
|
return GetMinMax(texid, sub, typeCast, false, minval, maxval);
|
|
}
|
|
|
|
bool GLReplay::GetMinMax(ResourceId texid, const Subresource &sub, CompType typeCast, bool stencil,
|
|
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] || !HasExt[ARB_shading_language_420pack])
|
|
return false;
|
|
|
|
auto &texDetails = m_pDriver->m_Textures[texid];
|
|
|
|
TextureDescription details = GetTexture(texid);
|
|
|
|
int texSlot = 0;
|
|
int intIdx = 0;
|
|
|
|
bool renderbuffer = false;
|
|
|
|
switch(texDetails.curType)
|
|
{
|
|
case eGL_RENDERBUFFER:
|
|
texSlot = texDetails.samples > 1 ? RESTYPE_TEX2DMS : RESTYPE_TEX2D;
|
|
renderbuffer = true;
|
|
break;
|
|
case eGL_TEXTURE_1D: texSlot = RESTYPE_TEX1D; break;
|
|
default: RDCWARN("Unexpected texture type"); DELIBERATE_FALLTHROUGH();
|
|
case eGL_TEXTURE_2D: texSlot = RESTYPE_TEX2D; break;
|
|
case eGL_TEXTURE_2D_MULTISAMPLE: texSlot = RESTYPE_TEX2DMS; break;
|
|
case eGL_TEXTURE_2D_MULTISAMPLE_ARRAY: texSlot = RESTYPE_TEX2DMSARRAY; 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);
|
|
|
|
GLMarkerRegion blitRegion("Renderbuffer Blit");
|
|
|
|
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]);
|
|
|
|
SafeBlitFramebuffer(
|
|
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 = texDetails.samples > 1 ? eGL_TEXTURE_2D_MULTISAMPLE : eGL_TEXTURE_2D;
|
|
}
|
|
|
|
MakeCurrentReplayContext(m_DebugCtx);
|
|
|
|
GLMarkerRegion region("GetMinMax");
|
|
|
|
RDCGLenum dsTexMode = eGL_NONE;
|
|
if(IsDepthStencilFormat(texDetails.internalFormat))
|
|
{
|
|
if(stencil)
|
|
{
|
|
dsTexMode = eGL_STENCIL_INDEX;
|
|
intIdx = 1;
|
|
}
|
|
else
|
|
{
|
|
dsTexMode = eGL_DEPTH_COMPONENT;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(details.format.compType == CompType::UInt)
|
|
intIdx = 1;
|
|
if(details.format.compType == CompType::SInt)
|
|
intIdx = 2;
|
|
}
|
|
|
|
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 >> sub.mip, 1U);
|
|
cdata->HistogramTextureResolution.y = (float)RDCMAX(details.height >> sub.mip, 1U);
|
|
cdata->HistogramTextureResolution.z = (float)RDCMAX(details.depth >> sub.mip, 1U);
|
|
if(texDetails.curType == eGL_TEXTURE_3D)
|
|
cdata->HistogramSlice =
|
|
(float)RDCCLAMP(sub.slice, 0U, uint32_t(details.depth >> sub.mip) - 1) + 0.001f;
|
|
else
|
|
cdata->HistogramSlice = (float)RDCCLAMP(sub.slice, 0U, details.arraysize - 1) + 0.001f;
|
|
cdata->HistogramMip = (int)sub.mip;
|
|
cdata->HistogramNumSamples = texDetails.samples;
|
|
cdata->HistogramSample = (int)RDCCLAMP(sub.sample, 0U, details.msSamp - 1);
|
|
if(sub.sample == ~0U)
|
|
cdata->HistogramSample = -int(details.msSamp);
|
|
cdata->HistogramMin = 0.0f;
|
|
cdata->HistogramMax = 1.0f;
|
|
cdata->HistogramChannels = 0xf;
|
|
|
|
cdata->HistogramYUVDownsampleRate = {};
|
|
cdata->HistogramYUVAChannels = {};
|
|
|
|
int progIdx = texSlot;
|
|
|
|
if(intIdx == 1)
|
|
progIdx |= TEXDISPLAY_UINT_TEX;
|
|
if(intIdx == 2)
|
|
progIdx |= TEXDISPLAY_SINT_TEX;
|
|
|
|
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);
|
|
|
|
TextureSamplerMode mode = TextureSamplerMode::Point;
|
|
|
|
if(texSlot == RESTYPE_TEXRECT || texSlot == RESTYPE_TEXBUFFER)
|
|
mode = TextureSamplerMode::PointNoMip;
|
|
|
|
TextureSamplerState prevSampState = SetSamplerParams(target, texname, mode);
|
|
|
|
GLint origDSTexMode = eGL_DEPTH_COMPONENT;
|
|
if(dsTexMode != eGL_NONE && HasExt[ARB_stencil_texturing])
|
|
{
|
|
GL.glGetTextureParameterivEXT(texname, target, eGL_DEPTH_STENCIL_TEXTURE_MODE, &origDSTexMode);
|
|
GL.glTextureParameteriEXT(texname, target, eGL_DEPTH_STENCIL_TEXTURE_MODE, dsTexMode);
|
|
}
|
|
|
|
GLint baseLevel[4] = {-1};
|
|
GLint maxlevel[4] = {-1};
|
|
GLint forcedparam[4] = {};
|
|
|
|
bool levelsTex = (target != eGL_TEXTURE_BUFFER && target != eGL_TEXTURE_2D_MULTISAMPLE &&
|
|
target != eGL_TEXTURE_2D_MULTISAMPLE_ARRAY);
|
|
|
|
if(levelsTex)
|
|
{
|
|
GL.glGetTextureParameterivEXT(texname, target, eGL_TEXTURE_BASE_LEVEL, baseLevel);
|
|
GL.glGetTextureParameterivEXT(texname, target, eGL_TEXTURE_MAX_LEVEL, maxlevel);
|
|
}
|
|
else
|
|
{
|
|
baseLevel[0] = maxlevel[0] = -1;
|
|
}
|
|
|
|
// ensure texture is mipmap complete and we can view all mips (if the range has been reduced) by
|
|
// forcing TEXTURE_MAX_LEVEL to cover all valid mips.
|
|
if(levelsTex && texid != DebugData.CustomShaderTexID)
|
|
{
|
|
forcedparam[0] = 0;
|
|
GL.glTextureParameterivEXT(texname, target, eGL_TEXTURE_BASE_LEVEL, forcedparam);
|
|
forcedparam[0] = GLint(details.mips - 1);
|
|
GL.glTextureParameterivEXT(texname, target, eGL_TEXTURE_MAX_LEVEL, forcedparam);
|
|
}
|
|
else
|
|
{
|
|
maxlevel[0] = -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(baseLevel[0] >= 0)
|
|
GL.glTextureParameterivEXT(texname, target, eGL_TEXTURE_BASE_LEVEL, baseLevel);
|
|
|
|
if(maxlevel[0] >= 0)
|
|
GL.glTextureParameterivEXT(texname, target, eGL_TEXTURE_MAX_LEVEL, maxlevel);
|
|
|
|
RestoreSamplerParams(target, texname, prevSampState);
|
|
|
|
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;
|
|
|
|
if(dsTexMode != eGL_NONE && HasExt[ARB_stencil_texturing])
|
|
GL.glTextureParameteriEXT(texname, target, eGL_DEPTH_STENCIL_TEXTURE_MODE, origDSTexMode);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool GLReplay::GetHistogram(ResourceId texid, const Subresource &sub, CompType typeCast,
|
|
float minval, float maxval, const rdcfixedarray<bool, 4> &channels_,
|
|
rdcarray<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] || !HasExt[ARB_shading_language_420pack])
|
|
return false;
|
|
|
|
// take a local copy so we can modify it
|
|
rdcfixedarray<bool, 4> channels = channels_;
|
|
|
|
auto &texDetails = m_pDriver->m_Textures[texid];
|
|
|
|
TextureDescription details = GetTexture(texid);
|
|
|
|
int texSlot = 0;
|
|
int intIdx = 0;
|
|
|
|
bool renderbuffer = false;
|
|
|
|
switch(texDetails.curType)
|
|
{
|
|
case eGL_RENDERBUFFER:
|
|
texSlot = texDetails.samples > 1 ? RESTYPE_TEX2DMS : RESTYPE_TEX2D;
|
|
renderbuffer = true;
|
|
break;
|
|
case eGL_TEXTURE_1D: texSlot = RESTYPE_TEX1D; break;
|
|
default: RDCWARN("Unexpected texture type"); DELIBERATE_FALLTHROUGH();
|
|
case eGL_TEXTURE_2D: texSlot = RESTYPE_TEX2D; break;
|
|
case eGL_TEXTURE_2D_MULTISAMPLE: texSlot = RESTYPE_TEX2DMS; break;
|
|
case eGL_TEXTURE_2D_MULTISAMPLE_ARRAY: texSlot = RESTYPE_TEX2DMSARRAY; 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);
|
|
|
|
GLMarkerRegion blitRegion("Renderbuffer Blit");
|
|
|
|
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]);
|
|
|
|
SafeBlitFramebuffer(
|
|
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 = texDetails.samples > 1 ? eGL_TEXTURE_2D_MULTISAMPLE : eGL_TEXTURE_2D;
|
|
}
|
|
|
|
MakeCurrentReplayContext(m_DebugCtx);
|
|
|
|
GLMarkerRegion region("GetHistogram");
|
|
|
|
RDCGLenum dsTexMode = eGL_NONE;
|
|
if(IsDepthStencilFormat(texDetails.internalFormat))
|
|
{
|
|
// stencil-only, make sure we display it as such
|
|
if(texDetails.internalFormat == eGL_STENCIL_INDEX8)
|
|
{
|
|
channels[0] = false;
|
|
channels[1] = true;
|
|
channels[2] = false;
|
|
channels[3] = false;
|
|
}
|
|
|
|
// depth-only, make sure we display it as such
|
|
if(GetBaseFormat(texDetails.internalFormat) == eGL_DEPTH_COMPONENT)
|
|
{
|
|
channels[0] = true;
|
|
channels[1] = false;
|
|
channels[2] = false;
|
|
channels[3] = false;
|
|
}
|
|
|
|
if(!channels[0] && channels[1])
|
|
{
|
|
dsTexMode = eGL_STENCIL_INDEX;
|
|
|
|
// Stencil texture sampling is not normalized in OpenGL
|
|
intIdx = 1;
|
|
float rangeScale = 1.0f;
|
|
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");
|
|
DELIBERATE_FALLTHROUGH();
|
|
case eGL_DEPTH24_STENCIL8:
|
|
case eGL_DEPTH32F_STENCIL8:
|
|
case eGL_DEPTH_STENCIL:
|
|
case eGL_STENCIL_INDEX8: rangeScale = 255.0f; break;
|
|
case eGL_STENCIL_INDEX16: rangeScale = 65535.0f; break;
|
|
}
|
|
minval *= rangeScale;
|
|
maxval *= rangeScale;
|
|
}
|
|
else
|
|
{
|
|
dsTexMode = eGL_DEPTH_COMPONENT;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(details.format.compType == CompType::UInt)
|
|
intIdx = 1;
|
|
if(details.format.compType == CompType::SInt)
|
|
intIdx = 2;
|
|
}
|
|
|
|
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 >> sub.mip, 1U);
|
|
cdata->HistogramTextureResolution.y = (float)RDCMAX(details.height >> sub.mip, 1U);
|
|
cdata->HistogramTextureResolution.z = (float)RDCMAX(details.depth >> sub.mip, 1U);
|
|
if(texDetails.curType == eGL_TEXTURE_3D)
|
|
cdata->HistogramSlice =
|
|
(float)RDCCLAMP(sub.slice, 0U, uint32_t(details.depth >> sub.mip) - 1) + 0.001f;
|
|
else
|
|
cdata->HistogramSlice = (float)RDCCLAMP(sub.slice, 0U, details.arraysize - 1) + 0.001f;
|
|
cdata->HistogramMip = sub.mip;
|
|
cdata->HistogramNumSamples = texDetails.samples;
|
|
cdata->HistogramSample = (int)RDCCLAMP(sub.sample, 0U, details.msSamp - 1);
|
|
if(sub.sample == ~0U)
|
|
cdata->HistogramSample = -int(details.msSamp);
|
|
cdata->HistogramMin = minval;
|
|
|
|
cdata->HistogramYUVDownsampleRate = {};
|
|
cdata->HistogramYUVAChannels = {};
|
|
|
|
// 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(dsTexMode == eGL_NONE)
|
|
{
|
|
if(channels[0])
|
|
cdata->HistogramChannels |= 0x1;
|
|
if(channels[1])
|
|
cdata->HistogramChannels |= 0x2;
|
|
if(channels[2])
|
|
cdata->HistogramChannels |= 0x4;
|
|
if(channels[3])
|
|
cdata->HistogramChannels |= 0x8;
|
|
}
|
|
else
|
|
{
|
|
// Both depth and stencil texture mode use the red channel
|
|
cdata->HistogramChannels |= 0x1;
|
|
}
|
|
cdata->HistogramFlags = 0;
|
|
|
|
int progIdx = texSlot;
|
|
|
|
if(intIdx == 1)
|
|
progIdx |= TEXDISPLAY_UINT_TEX;
|
|
if(intIdx == 2)
|
|
progIdx |= TEXDISPLAY_SINT_TEX;
|
|
|
|
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);
|
|
|
|
TextureSamplerMode mode = TextureSamplerMode::Point;
|
|
|
|
if(texSlot == RESTYPE_TEXRECT || texSlot == RESTYPE_TEXBUFFER)
|
|
mode = TextureSamplerMode::PointNoMip;
|
|
|
|
TextureSamplerState prevSampState = SetSamplerParams(target, texname, mode);
|
|
|
|
GLint origDSTexMode = eGL_DEPTH_COMPONENT;
|
|
if(dsTexMode != eGL_NONE && HasExt[ARB_stencil_texturing])
|
|
{
|
|
GL.glGetTextureParameterivEXT(texname, target, eGL_DEPTH_STENCIL_TEXTURE_MODE, &origDSTexMode);
|
|
GL.glTextureParameteriEXT(texname, target, eGL_DEPTH_STENCIL_TEXTURE_MODE, dsTexMode);
|
|
}
|
|
|
|
GLint baseLevel[4] = {-1};
|
|
GLint maxlevel[4] = {-1};
|
|
GLint forcedparam[4] = {};
|
|
|
|
bool levelsTex = (target != eGL_TEXTURE_BUFFER && target != eGL_TEXTURE_2D_MULTISAMPLE &&
|
|
target != eGL_TEXTURE_2D_MULTISAMPLE_ARRAY);
|
|
|
|
if(levelsTex)
|
|
{
|
|
GL.glGetTextureParameterivEXT(texname, target, eGL_TEXTURE_BASE_LEVEL, baseLevel);
|
|
GL.glGetTextureParameterivEXT(texname, target, eGL_TEXTURE_MAX_LEVEL, maxlevel);
|
|
}
|
|
else
|
|
{
|
|
baseLevel[0] = maxlevel[0] = -1;
|
|
}
|
|
|
|
// ensure texture is mipmap complete and we can view all mips (if the range has been reduced) by
|
|
// forcing TEXTURE_MAX_LEVEL to cover all valid mips.
|
|
if(levelsTex && texid != DebugData.CustomShaderTexID)
|
|
{
|
|
forcedparam[0] = 0;
|
|
GL.glTextureParameterivEXT(texname, target, eGL_TEXTURE_BASE_LEVEL, forcedparam);
|
|
forcedparam[0] = GLint(details.mips - 1);
|
|
GL.glTextureParameterivEXT(texname, target, eGL_TEXTURE_MAX_LEVEL, forcedparam);
|
|
}
|
|
else
|
|
{
|
|
maxlevel[0] = -1;
|
|
}
|
|
|
|
GL.glBindBufferBase(eGL_SHADER_STORAGE_BUFFER, 0, DebugData.histogramBuf);
|
|
|
|
GLuint zero = 0;
|
|
GL.glClearBufferData(eGL_SHADER_STORAGE_BUFFER, eGL_R32UI, eGL_RED_INTEGER, 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);
|
|
|
|
GL.glBindBuffer(eGL_COPY_READ_BUFFER, DebugData.histogramBuf);
|
|
GL.glGetBufferSubData(eGL_COPY_READ_BUFFER, 0, sizeof(uint32_t) * HGRAM_NUM_BUCKETS, &histogram[0]);
|
|
|
|
if(baseLevel[0] >= 0)
|
|
GL.glTextureParameterivEXT(texname, target, eGL_TEXTURE_BASE_LEVEL, baseLevel);
|
|
|
|
if(maxlevel[0] >= 0)
|
|
GL.glTextureParameterivEXT(texname, target, eGL_TEXTURE_MAX_LEVEL, maxlevel);
|
|
|
|
RestoreSamplerParams(target, texname, prevSampState);
|
|
|
|
if(dsTexMode != eGL_NONE && HasExt[ARB_stencil_texturing])
|
|
GL.glTextureParameteriEXT(texname, target, eGL_DEPTH_STENCIL_TEXTURE_MODE, origDSTexMode);
|
|
|
|
return true;
|
|
}
|
|
|
|
uint32_t GLReplay::PickVertex(uint32_t eventId, int32_t width, int32_t height,
|
|
const MeshDisplay &cfg, uint32_t x, uint32_t y)
|
|
{
|
|
WrappedOpenGL &drv = *m_pDriver;
|
|
|
|
if(!HasExt[ARB_compute_shader] || !HasExt[ARB_shading_language_420pack])
|
|
return ~0U;
|
|
|
|
MakeCurrentReplayContext(m_DebugCtx);
|
|
|
|
drv.glUseProgram(DebugData.meshPickProgram);
|
|
|
|
Matrix4f projMat = Matrix4f::Perspective(90.0f, 0.1f, 100000.0f, float(width) / float(height));
|
|
|
|
Matrix4f camMat = cfg.cam ? ((Camera *)cfg.cam)->GetMatrix() : Matrix4f::Identity();
|
|
Matrix4f pickMVP = projMat.Mul(camMat);
|
|
|
|
bool reverseProjection = false;
|
|
Matrix4f guessProj;
|
|
Matrix4f guessProjInverse;
|
|
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.
|
|
if(cfg.position.farPlane != FLT_MAX)
|
|
{
|
|
guessProj =
|
|
Matrix4f::Perspective(cfg.fov, cfg.position.nearPlane, cfg.position.farPlane, cfg.aspect);
|
|
}
|
|
else
|
|
{
|
|
reverseProjection = true;
|
|
guessProj = Matrix4f::ReversePerspective(cfg.fov, cfg.position.nearPlane, cfg.aspect);
|
|
}
|
|
|
|
if(cfg.ortho)
|
|
guessProj = Matrix4f::Orthographic(cfg.position.nearPlane, cfg.position.farPlane);
|
|
|
|
if(cfg.position.flipY)
|
|
guessProj[5] *= -1.0f;
|
|
|
|
guessProjInverse = guessProj.Inverse();
|
|
}
|
|
|
|
Vec3f rayPos;
|
|
Vec3f rayDir;
|
|
// convert mouse pos to world space ray
|
|
{
|
|
float pickX = ((float)x) / ((float)width);
|
|
float pickXCanonical = RDCLERP(-1.0f, 1.0f, pickX);
|
|
|
|
float pickY = ((float)y) / ((float)height);
|
|
// flip the Y axis by default for Y-up
|
|
float pickYCanonical = RDCLERP(1.0f, -1.0f, pickY);
|
|
|
|
if(cfg.position.flipY && !cfg.ortho)
|
|
pickYCanonical = -pickYCanonical;
|
|
|
|
// x/y is inside the window. Since we're not using the window projection we need to correct
|
|
// for the aspect ratio here.
|
|
if(cfg.position.unproject && !cfg.ortho)
|
|
pickXCanonical *= (float(width) / float(height)) / cfg.aspect;
|
|
|
|
// set up the NDC near/far pos
|
|
Vec3f nearPosNDC = Vec3f(pickXCanonical, pickYCanonical, 0);
|
|
Vec3f farPosNDC = Vec3f(pickXCanonical, pickYCanonical, 1);
|
|
|
|
if(cfg.position.unproject && cfg.ortho)
|
|
{
|
|
// orthographic projections we raycast in NDC space
|
|
Matrix4f inversePickMVP = pickMVP.Inverse();
|
|
|
|
// transform from the desired NDC co-ordinates into camera space
|
|
Vec3f nearPosCamera = inversePickMVP.Transform(nearPosNDC, 1);
|
|
Vec3f farPosCamera = inversePickMVP.Transform(farPosNDC, 1);
|
|
|
|
Vec3f testDir = (farPosCamera - nearPosCamera);
|
|
testDir.Normalise();
|
|
|
|
Matrix4f pickMVPguessProjInverse = guessProj.Mul(inversePickMVP);
|
|
|
|
Vec3f nearPosProj = pickMVPguessProjInverse.Transform(nearPosNDC, 1);
|
|
Vec3f farPosProj = pickMVPguessProjInverse.Transform(farPosNDC, 1);
|
|
|
|
rayDir = (farPosProj - nearPosProj);
|
|
rayDir.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(testDir.z < 0)
|
|
{
|
|
rayDir = -rayDir;
|
|
}
|
|
rayPos = nearPosProj;
|
|
}
|
|
else if(cfg.position.unproject)
|
|
{
|
|
// projected data we pick in world-space to avoid problems with handling unusual transforms
|
|
|
|
if(reverseProjection)
|
|
{
|
|
farPosNDC.z = 1e-6f;
|
|
nearPosNDC.z = 1e+6f;
|
|
}
|
|
|
|
// invert the guessed projection matrix to get the near/far pos in camera space
|
|
Vec3f nearPosCamera = guessProjInverse.Transform(nearPosNDC, 1.0f);
|
|
Vec3f farPosCamera = guessProjInverse.Transform(farPosNDC, 1.0f);
|
|
|
|
// normalise and generate the ray
|
|
rayDir = (farPosCamera - nearPosCamera);
|
|
rayDir.Normalise();
|
|
|
|
farPosCamera = nearPosCamera + rayDir;
|
|
|
|
// invert the camera transform to transform the ray as camera-relative into world space
|
|
Matrix4f inverseCamera = camMat.Inverse();
|
|
|
|
Vec3f nearPosWorld = inverseCamera.Transform(nearPosCamera, 1);
|
|
Vec3f farPosWorld = inverseCamera.Transform(farPosCamera, 1);
|
|
|
|
// again normalise our final ray
|
|
rayDir = (farPosWorld - nearPosWorld);
|
|
rayDir.Normalise();
|
|
|
|
rayPos = nearPosWorld;
|
|
}
|
|
else
|
|
{
|
|
Matrix4f inversePickMVP = pickMVP.Inverse();
|
|
|
|
// transform from the desired NDC co-ordinates into model space
|
|
Vec3f nearPosCamera = inversePickMVP.Transform(nearPosNDC, 1);
|
|
Vec3f farPosCamera = inversePickMVP.Transform(farPosNDC, 1);
|
|
|
|
rayDir = (farPosCamera - nearPosCamera);
|
|
rayDir.Normalise();
|
|
rayPos = nearPosCamera;
|
|
}
|
|
}
|
|
|
|
GLuint ib = 0;
|
|
|
|
uint32_t minIndex = 0;
|
|
uint32_t maxIndex = cfg.position.numIndices;
|
|
|
|
uint32_t idxclamp = 0;
|
|
if(cfg.position.baseVertex < 0)
|
|
idxclamp = uint32_t(-cfg.position.baseVertex);
|
|
|
|
if(cfg.position.indexByteStride && cfg.position.indexResourceId != ResourceId())
|
|
ib = m_pDriver->GetResourceManager()->GetCurrentResource(cfg.position.indexResourceId).name;
|
|
|
|
const bool fandecode =
|
|
(cfg.position.topology == Topology::TriangleFan && cfg.position.allowRestart);
|
|
uint32_t numIndices = cfg.position.numIndices;
|
|
|
|
// 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. We also apply baseVertex here
|
|
|
|
if(ib)
|
|
{
|
|
rdcarray<uint32_t> idxtmp;
|
|
|
|
// if it's a triangle fan that allows restart, we'll have to unpack it.
|
|
// Allocate enough space for the list on the GPU, and enough temporary space to upcast into
|
|
// first
|
|
if(fandecode)
|
|
{
|
|
idxtmp.resize(numIndices);
|
|
|
|
numIndices *= 3;
|
|
}
|
|
|
|
// resize up on demand
|
|
if(DebugData.pickIBBuf == 0 || DebugData.pickIBSize < numIndices * sizeof(uint32_t))
|
|
{
|
|
drv.glDeleteBuffers(1, &DebugData.pickIBBuf);
|
|
|
|
drv.glGenBuffers(1, &DebugData.pickIBBuf);
|
|
drv.glBindBuffer(eGL_SHADER_STORAGE_BUFFER, DebugData.pickIBBuf);
|
|
drv.glNamedBufferDataEXT(DebugData.pickIBBuf, numIndices * sizeof(uint32_t), NULL,
|
|
eGL_STREAM_DRAW);
|
|
|
|
DebugData.pickIBSize = numIndices * sizeof(uint32_t);
|
|
}
|
|
|
|
byte *idxs = new byte[numIndices * cfg.position.indexByteStride];
|
|
memset(idxs, 0, numIndices * cfg.position.indexByteStride);
|
|
|
|
rdcarray<uint32_t> outidxs;
|
|
outidxs.resize(numIndices);
|
|
|
|
drv.glBindBuffer(eGL_COPY_READ_BUFFER, ib);
|
|
|
|
GLuint bufsize = 0;
|
|
drv.glGetBufferParameteriv(eGL_COPY_READ_BUFFER, eGL_BUFFER_SIZE, (GLint *)&bufsize);
|
|
|
|
drv.glGetBufferSubData(eGL_COPY_READ_BUFFER, (GLintptr)cfg.position.indexByteOffset,
|
|
RDCMIN(uint32_t(bufsize) - uint32_t(cfg.position.indexByteOffset),
|
|
cfg.position.numIndices * cfg.position.indexByteStride),
|
|
idxs);
|
|
|
|
uint8_t *idxs8 = (uint8_t *)idxs;
|
|
uint16_t *idxs16 = (uint16_t *)idxs;
|
|
uint32_t *idxs32 = (uint32_t *)idxs;
|
|
|
|
size_t idxcount = 0;
|
|
|
|
if(cfg.position.indexByteStride == 1)
|
|
{
|
|
for(uint32_t i = 0; i < cfg.position.numIndices; i++)
|
|
{
|
|
uint32_t idx = idxs8[i];
|
|
|
|
if(idx < idxclamp)
|
|
idx = 0;
|
|
else if(cfg.position.baseVertex < 0)
|
|
idx -= idxclamp;
|
|
else if(cfg.position.baseVertex > 0)
|
|
idx += cfg.position.baseVertex;
|
|
|
|
if(i == 0)
|
|
{
|
|
minIndex = maxIndex = idx;
|
|
}
|
|
else
|
|
{
|
|
minIndex = RDCMIN(idx, minIndex);
|
|
maxIndex = RDCMAX(idx, maxIndex);
|
|
}
|
|
|
|
outidxs[i] = idx;
|
|
idxcount++;
|
|
}
|
|
}
|
|
else if(cfg.position.indexByteStride == 2)
|
|
{
|
|
for(uint32_t i = 0; i < cfg.position.numIndices; i++)
|
|
{
|
|
uint32_t idx = idxs16[i];
|
|
|
|
if(idx < idxclamp)
|
|
idx = 0;
|
|
else if(cfg.position.baseVertex < 0)
|
|
idx -= idxclamp;
|
|
else if(cfg.position.baseVertex > 0)
|
|
idx += cfg.position.baseVertex;
|
|
|
|
if(i == 0)
|
|
{
|
|
minIndex = maxIndex = idx;
|
|
}
|
|
else
|
|
{
|
|
minIndex = RDCMIN(idx, minIndex);
|
|
maxIndex = RDCMAX(idx, maxIndex);
|
|
}
|
|
|
|
outidxs[i] = idx;
|
|
idxcount++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for(uint32_t i = 0; i < cfg.position.numIndices; i++)
|
|
{
|
|
uint32_t idx = idxs32[i];
|
|
|
|
if(idx < idxclamp)
|
|
idx = 0;
|
|
else if(cfg.position.baseVertex < 0)
|
|
idx -= idxclamp;
|
|
else if(cfg.position.baseVertex > 0)
|
|
idx += cfg.position.baseVertex;
|
|
|
|
if(i == 0)
|
|
{
|
|
minIndex = maxIndex = idx;
|
|
}
|
|
else
|
|
{
|
|
minIndex = RDCMIN(idx, minIndex);
|
|
maxIndex = RDCMAX(idx, maxIndex);
|
|
}
|
|
|
|
outidxs[i] = idx;
|
|
idxcount++;
|
|
}
|
|
}
|
|
|
|
// if it's a triangle fan that allows restart, unpack it
|
|
if(cfg.position.topology == Topology::TriangleFan && cfg.position.allowRestart)
|
|
{
|
|
// resize to how many indices were actually read
|
|
outidxs.resize(idxcount);
|
|
|
|
// patch the index buffer
|
|
PatchTriangleFanRestartIndexBufer(outidxs, cfg.position.restartIndex);
|
|
|
|
for(uint32_t &idx : idxtmp)
|
|
{
|
|
if(idx == cfg.position.restartIndex)
|
|
idx = 0;
|
|
}
|
|
|
|
numIndices = (uint32_t)outidxs.size();
|
|
}
|
|
|
|
drv.glBindBuffer(eGL_SHADER_STORAGE_BUFFER, DebugData.pickIBBuf);
|
|
drv.glBufferSubData(eGL_SHADER_STORAGE_BUFFER, 0, numIndices * sizeof(uint32_t), outidxs.data());
|
|
}
|
|
|
|
// unpack and linearise the data
|
|
{
|
|
bytebuf oldData;
|
|
GetBufferData(cfg.position.vertexResourceId, cfg.position.vertexByteOffset, 0, oldData);
|
|
|
|
// clamp maxIndex to upper bound in case we got invalid indices or primitive restart indices
|
|
maxIndex = RDCMIN(maxIndex, uint32_t(oldData.size() / RDCMAX(1U, cfg.position.vertexByteStride)));
|
|
|
|
if(DebugData.pickVBBuf == 0 || DebugData.pickVBSize < (maxIndex + 1) * sizeof(Vec4f))
|
|
{
|
|
drv.glDeleteBuffers(1, &DebugData.pickVBBuf);
|
|
|
|
drv.glGenBuffers(1, &DebugData.pickVBBuf);
|
|
drv.glBindBuffer(eGL_SHADER_STORAGE_BUFFER, DebugData.pickVBBuf);
|
|
drv.glNamedBufferDataEXT(DebugData.pickVBBuf, (maxIndex + 1) * sizeof(Vec4f), NULL,
|
|
eGL_DYNAMIC_DRAW);
|
|
|
|
DebugData.pickVBSize = (maxIndex + 1) * sizeof(Vec4f);
|
|
}
|
|
|
|
rdcarray<FloatVector> vbData;
|
|
vbData.resize(maxIndex + 1);
|
|
|
|
byte *data = &oldData[0];
|
|
byte *dataEnd = data + oldData.size();
|
|
|
|
bool valid;
|
|
|
|
// the index buffer may refer to vertices past the start of the vertex buffer, so we can't just
|
|
// conver the first N vertices we'll need.
|
|
// Instead we grab min and max above, and convert every vertex in that range. This might
|
|
// slightly over-estimate but not as bad as 0-max or the whole buffer.
|
|
for(uint32_t idx = minIndex; idx <= maxIndex; idx++)
|
|
vbData[idx] = HighlightCache::InterpretVertex(data, idx, cfg.position.vertexByteStride,
|
|
cfg.position.format, dataEnd, valid);
|
|
|
|
drv.glBindBuffer(eGL_SHADER_STORAGE_BUFFER, DebugData.pickVBBuf);
|
|
drv.glBufferSubData(eGL_SHADER_STORAGE_BUFFER, 0, (maxIndex + 1) * sizeof(Vec4f), vbData.data());
|
|
}
|
|
|
|
drv.glBindBufferBase(eGL_UNIFORM_BUFFER, 0, DebugData.UBOs[0]);
|
|
MeshPickUBOData *cdata =
|
|
(MeshPickUBOData *)drv.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.indexByteStride ? 1U : 0U;
|
|
cdata->numVerts = numIndices;
|
|
bool isTriangleMesh = true;
|
|
switch(cfg.position.topology)
|
|
{
|
|
case Topology::TriangleList:
|
|
{
|
|
cdata->meshMode = MESH_TRIANGLE_LIST;
|
|
break;
|
|
}
|
|
case Topology::TriangleStrip:
|
|
{
|
|
cdata->meshMode = MESH_TRIANGLE_STRIP;
|
|
break;
|
|
}
|
|
case Topology::TriangleFan:
|
|
{
|
|
if(fandecode)
|
|
cdata->meshMode = MESH_TRIANGLE_LIST;
|
|
else
|
|
cdata->meshMode = MESH_TRIANGLE_FAN;
|
|
break;
|
|
}
|
|
case Topology::TriangleList_Adj:
|
|
{
|
|
cdata->meshMode = MESH_TRIANGLE_LIST_ADJ;
|
|
break;
|
|
}
|
|
case Topology::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->flipY = cfg.position.flipY;
|
|
cdata->ortho = cfg.ortho;
|
|
cdata->coords = Vec2f((float)x, (float)y);
|
|
cdata->viewport = Vec2f((float)width, (float)height);
|
|
|
|
if(cfg.position.unproject && isTriangleMesh)
|
|
{
|
|
// projected triangle meshes we transform the vertices into world space, and ray-cast against
|
|
// that
|
|
//
|
|
// NOTE: for ortho, this matrix is not used and we just do the perspective W division on model
|
|
// vertices. The ray is cast in NDC
|
|
if(cfg.ortho)
|
|
cdata->transformMat = Matrix4f::Identity();
|
|
else
|
|
cdata->transformMat = guessProjInverse;
|
|
}
|
|
else if(cfg.position.unproject)
|
|
{
|
|
// projected non-triangles are just point clouds, so we transform the vertices into world space
|
|
// then project them back onto the output and compare that against the picking 2D co-ordinates
|
|
cdata->transformMat = pickMVP.Mul(guessProjInverse);
|
|
}
|
|
else
|
|
{
|
|
// plain meshes of either type, we just transform from model space to the output, and raycast or
|
|
// co-ordinate check
|
|
cdata->transformMat = pickMVP;
|
|
}
|
|
|
|
drv.glUnmapBuffer(eGL_UNIFORM_BUFFER);
|
|
|
|
uint32_t reset[4] = {};
|
|
drv.glBindBufferBase(eGL_SHADER_STORAGE_BUFFER, 0, DebugData.pickResultBuf);
|
|
drv.glBufferSubData(eGL_SHADER_STORAGE_BUFFER, 0, sizeof(uint32_t) * 4, &reset);
|
|
|
|
drv.glBindBufferBase(eGL_SHADER_STORAGE_BUFFER, 1, DebugData.pickVBBuf);
|
|
drv.glBindBufferRange(eGL_SHADER_STORAGE_BUFFER, 2, DebugData.pickIBBuf, (GLintptr)0,
|
|
(GLsizeiptr)(sizeof(uint32_t) * cfg.position.numIndices));
|
|
drv.glBindBufferBase(eGL_SHADER_STORAGE_BUFFER, 3, DebugData.pickResultBuf);
|
|
|
|
drv.glDispatchCompute(GLuint((cfg.position.numIndices) / 128 + 1), 1, 1);
|
|
drv.glMemoryBarrier(GL_ATOMIC_COUNTER_BARRIER_BIT | GL_SHADER_STORAGE_BARRIER_BIT);
|
|
|
|
uint32_t numResults = 0;
|
|
|
|
drv.glBindBuffer(eGL_COPY_READ_BUFFER, DebugData.pickResultBuf);
|
|
drv.glGetBufferSubData(eGL_COPY_READ_BUFFER, 0, sizeof(uint32_t), &numResults);
|
|
|
|
uint32_t ret = ~0U;
|
|
|
|
if(numResults > 0)
|
|
{
|
|
if(isTriangleMesh)
|
|
{
|
|
struct PickResult
|
|
{
|
|
uint32_t vertid;
|
|
vec3 intersectionPoint;
|
|
};
|
|
|
|
byte *mapped = (byte *)drv.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;
|
|
}
|
|
}
|
|
|
|
drv.glUnmapNamedBufferEXT(DebugData.pickResultBuf);
|
|
|
|
ret = closest->vertid;
|
|
}
|
|
else
|
|
{
|
|
struct PickResult
|
|
{
|
|
uint32_t vertid;
|
|
uint32_t idx;
|
|
float len;
|
|
float depth;
|
|
};
|
|
|
|
byte *mapped = (byte *)drv.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;
|
|
}
|
|
|
|
drv.glUnmapNamedBufferEXT(DebugData.pickResultBuf);
|
|
|
|
ret = closest->vertid;
|
|
}
|
|
}
|
|
|
|
if(fandecode)
|
|
{
|
|
// undo the triangle list expansion
|
|
if(ret > 2)
|
|
ret = (ret + 3) / 3 + 1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void GLReplay::RenderCheckerboard(FloatVector dark, FloatVector light)
|
|
{
|
|
MakeCurrentReplayContext(m_DebugCtx);
|
|
|
|
WrappedOpenGL &drv = *m_pDriver;
|
|
|
|
drv.glUseProgram(DebugData.checkerProg);
|
|
|
|
drv.glDisable(eGL_DEPTH_TEST);
|
|
|
|
if(HasExt[EXT_framebuffer_sRGB])
|
|
drv.glEnable(eGL_FRAMEBUFFER_SRGB);
|
|
|
|
drv.glBindBufferBase(eGL_UNIFORM_BUFFER, 0, DebugData.UBOs[0]);
|
|
|
|
CheckerboardUBOData *ubo =
|
|
(CheckerboardUBOData *)drv.glMapBufferRange(eGL_UNIFORM_BUFFER, 0, sizeof(CheckerboardUBOData),
|
|
GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
|
|
|
|
ubo->BorderWidth = 0.0f;
|
|
ubo->RectPosition = Vec2f();
|
|
ubo->RectSize = Vec2f();
|
|
ubo->CheckerSquareDimension = 64.0f;
|
|
ubo->InnerColor = Vec4f();
|
|
|
|
ubo->PrimaryColor = ConvertSRGBToLinear(dark);
|
|
ubo->SecondaryColor = ConvertSRGBToLinear(light);
|
|
|
|
drv.glUnmapBuffer(eGL_UNIFORM_BUFFER);
|
|
|
|
drv.glBindVertexArray(DebugData.emptyVAO);
|
|
drv.glDrawArrays(eGL_TRIANGLE_STRIP, 0, 4);
|
|
}
|
|
|
|
void GLReplay::RenderHighlightBox(float w, float h, float scale)
|
|
{
|
|
MakeCurrentReplayContext(m_DebugCtx);
|
|
|
|
WrappedOpenGL &drv = *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
|
|
drv.glEnable(eGL_SCISSOR_TEST);
|
|
drv.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
|
|
for(size_t i = 0; i < ARRAY_COUNT(scissors); i++)
|
|
{
|
|
drv.glScissor(scissors[i].x, scissors[i].y, scissors[i].w, scissors[i].h);
|
|
drv.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
|
|
drv.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
|
for(size_t i = 0; i < ARRAY_COUNT(scissors); i++)
|
|
{
|
|
drv.glScissor(scissors[i].x, scissors[i].y, scissors[i].w, scissors[i].h);
|
|
drv.glClear(eGL_COLOR_BUFFER_BIT);
|
|
}
|
|
|
|
drv.glDisable(eGL_SCISSOR_TEST);
|
|
}
|