Files
renderdoc/renderdoc/driver/gl/gl_overlay.cpp
T

2163 lines
78 KiB
C++

/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2019-2023 Baldur Karlsson
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
#include <float.h>
#include <algorithm>
#include "common/common.h"
#include "data/glsl_shaders.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"
bool GLReplay::CreateFragmentShaderReplacementProgram(GLuint program, GLuint replacementProgram,
GLuint pipeline, GLuint fragShader,
GLuint fragShaderSPIRV)
{
WrappedOpenGL &drv = *m_pDriver;
ContextPair &ctx = drv.GetCtx();
// these are the shaders to attach, and the programs to copy details from
GLuint shaders[4] = {0};
GLuint programs[4] = {0};
// temporary programs created as needed if the original program was created with
// glCreateShaderProgramv and we don't have a shader to attach
GLuint tmpShaders[4] = {0};
// the reflection for the vertex shader, used to copy vertex bindings
ShaderReflection *vsRefl = NULL;
bool HasSPIRVShaders = false;
bool HasGLSLShaders = false;
if(program == 0)
{
if(pipeline == 0)
{
return false;
}
else
{
ResourceId id = m_pDriver->GetResourceManager()->GetResID(ProgramPipeRes(ctx, pipeline));
const WrappedOpenGL::PipelineData &pipeDetails = m_pDriver->m_Pipelines[id];
// fetch the corresponding shaders and programs for each stage
for(size_t i = 0; i < 4; i++)
{
if(pipeDetails.stageShaders[i] != ResourceId())
{
const WrappedOpenGL::ShaderData &shadDetails =
m_pDriver->m_Shaders[pipeDetails.stageShaders[i]];
if(shadDetails.reflection->encoding == ShaderEncoding::OpenGLSPIRV)
HasSPIRVShaders = true;
else
HasGLSLShaders = true;
programs[i] =
m_pDriver->GetResourceManager()->GetCurrentResource(pipeDetails.stagePrograms[i]).name;
shaders[i] =
m_pDriver->GetResourceManager()->GetCurrentResource(pipeDetails.stageShaders[i]).name;
if(pipeDetails.stagePrograms[i] == pipeDetails.stageShaders[i])
{
const WrappedOpenGL::ProgramData &progDetails =
m_pDriver->m_Programs[pipeDetails.stagePrograms[i]];
if(progDetails.shaderProgramUnlinkable)
{
rdcarray<const char *> sources;
sources.reserve(shadDetails.sources.size());
for(const rdcstr &s : shadDetails.sources)
sources.push_back(s.c_str());
shaders[i] = tmpShaders[i] = drv.glCreateShader(ShaderEnum(i));
drv.glShaderSource(tmpShaders[i], (GLsizei)sources.size(), sources.data(), NULL);
drv.glCompileShader(tmpShaders[i]);
GLint status = 0;
drv.glGetShaderiv(tmpShaders[i], eGL_COMPILE_STATUS, &status);
if(status == 0)
{
char buffer[1024] = {};
drv.glGetShaderInfoLog(tmpShaders[i], 1024, NULL, buffer);
RDCERR("Trying to recreate replacement program, couldn't compile shader:\n%s",
buffer);
}
}
}
if(i == 0)
vsRefl = GetShader(ResourceId(), pipeDetails.stageShaders[i], ShaderEntryPoint());
}
}
}
}
else
{
const WrappedOpenGL::ProgramData &progDetails =
m_pDriver->m_Programs[m_pDriver->GetResourceManager()->GetResID(ProgramRes(ctx, program))];
// fetch any and all non-fragment shader shaders
for(size_t i = 0; i < 4; i++)
{
if(progDetails.stageShaders[i] != ResourceId())
{
programs[i] = program;
shaders[i] =
m_pDriver->GetResourceManager()->GetCurrentResource(progDetails.stageShaders[i]).name;
const WrappedOpenGL::ShaderData &shadDetails =
m_pDriver->m_Shaders[progDetails.stageShaders[i]];
if(shadDetails.reflection->encoding == ShaderEncoding::OpenGLSPIRV)
HasSPIRVShaders = true;
else
HasGLSLShaders = true;
if(i == 0)
vsRefl = GetShader(ResourceId(), progDetails.stageShaders[0], ShaderEntryPoint());
}
}
}
if(HasGLSLShaders && HasSPIRVShaders)
RDCERR("Unsupported - mixed GLSL and SPIR-V shaders in pipeline");
// attach the shaders
for(size_t i = 0; i < 4; i++)
if(shaders[i])
drv.glAttachShader(replacementProgram, shaders[i]);
if(HasSPIRVShaders)
{
RDCASSERT(fragShaderSPIRV);
drv.glAttachShader(replacementProgram, fragShaderSPIRV);
}
else
{
drv.glAttachShader(replacementProgram, fragShader);
}
// copy the vertex attribs over from the source program
if(vsRefl && programs[0] && !HasSPIRVShaders)
CopyProgramAttribBindings(programs[0], replacementProgram, vsRefl);
// link the overlay program
drv.glLinkProgram(replacementProgram);
// detach the shaders
for(size_t i = 0; i < 4; i++)
if(shaders[i])
drv.glDetachShader(replacementProgram, shaders[i]);
if(HasSPIRVShaders)
drv.glDetachShader(replacementProgram, fragShaderSPIRV);
else
drv.glDetachShader(replacementProgram, fragShader);
// delete any temporaries
for(size_t i = 0; i < 4; i++)
if(tmpShaders[i])
drv.glDeleteShader(tmpShaders[i]);
// check that the link succeeded
char buffer[1024] = {};
GLint status = 0;
drv.glGetProgramiv(replacementProgram, eGL_LINK_STATUS, &status);
if(status == 0)
{
drv.glGetProgramInfoLog(replacementProgram, 1024, NULL, buffer);
RDCERR("Error linking overlay program: %s", buffer);
return false;
}
// copy the uniform values over from the source program. This is redundant but harmless if the
// same program is bound to multiple stages. It's just inefficient
{
PerStageReflections dstStages;
m_pDriver->FillReflectionArray(ProgramRes(ctx, replacementProgram), dstStages);
for(size_t i = 0; i < 4; i++)
{
if(programs[i])
{
PerStageReflections stages;
m_pDriver->FillReflectionArray(ProgramRes(ctx, programs[i]), stages);
CopyProgramUniforms(stages, programs[i], dstStages, replacementProgram);
}
}
}
return HasSPIRVShaders;
}
RenderOutputSubresource GLReplay::GetRenderOutputSubresource(ResourceId id)
{
MakeCurrentReplayContext(&m_ReplayCtx);
WrappedOpenGL &drv = *m_pDriver;
ContextPair &ctx = drv.GetCtx();
RenderOutputSubresource ret(~0U, ~0U, 0);
GLint numCols = 8;
drv.glGetIntegerv(eGL_MAX_COLOR_ATTACHMENTS, &numCols);
WrappedOpenGL::TextureData &details = m_pDriver->m_Textures[id];
GLuint curDrawFBO = 0;
drv.glGetIntegerv(eGL_DRAW_FRAMEBUFFER_BINDING, (GLint *)&curDrawFBO);
GLuint name = 0;
GLenum type = eGL_TEXTURE;
for(GLint i = 0; i < numCols + 2; i++)
{
GLenum att = GLenum(eGL_COLOR_ATTACHMENT0 + i);
if(i == numCols)
att = eGL_DEPTH_ATTACHMENT;
else if(i == numCols + 1)
att = eGL_STENCIL_ATTACHMENT;
drv.glGetFramebufferAttachmentParameteriv(
eGL_DRAW_FRAMEBUFFER, att, eGL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, (GLint *)&name);
drv.glGetFramebufferAttachmentParameteriv(
eGL_DRAW_FRAMEBUFFER, att, eGL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, (GLint *)&type);
GLResource res;
if(type == eGL_RENDERBUFFER)
res = RenderbufferRes(ctx, name);
else
res = TextureRes(ctx, name);
if(res == details.resource)
{
ret.numSlices = 1;
if(type == eGL_TEXTURE)
{
GetFramebufferMipAndLayer(curDrawFBO, att, (GLint *)&ret.mip, (GLint *)&ret.slice);
// desktop GL allows layered attachments which attach all slices from 0 to N
if(!IsGLES)
{
GLint layered = 0;
GL.glGetNamedFramebufferAttachmentParameterivEXT(
curDrawFBO, att, eGL_FRAMEBUFFER_ATTACHMENT_LAYERED, &layered);
if(layered)
ret.numSlices = details.depth;
}
else
{
// on GLES there's an OVR extension that allows attaching multiple layers
if(HasExt[OVR_multiview])
{
GLint numViews = 0;
GL.glGetNamedFramebufferAttachmentParameterivEXT(
curDrawFBO, att, eGL_FRAMEBUFFER_ATTACHMENT_TEXTURE_NUM_VIEWS_OVR, &numViews);
if(numViews > 1)
ret.numSlices = numViews;
}
}
}
else
{
ret.mip = 0;
ret.slice = 0;
}
}
}
return ret;
}
void GLReplay::BindFramebufferTexture(RenderOutputSubresource &sub, GLenum texBindingEnum,
GLint numSamples)
{
WrappedOpenGL &drv = *m_pDriver;
if(sub.numSlices > 1)
{
if(IsGLES)
{
if(HasExt[OVR_multiview])
{
if(texBindingEnum == eGL_TEXTURE_2D_MULTISAMPLE_ARRAY)
{
drv.glFramebufferTextureMultisampleMultiviewOVR(eGL_FRAMEBUFFER, eGL_COLOR_ATTACHMENT0,
DebugData.overlayTex, sub.mip, numSamples,
sub.slice, sub.numSlices);
}
else
{
drv.glFramebufferTextureMultiviewOVR(eGL_FRAMEBUFFER, eGL_COLOR_ATTACHMENT0,
DebugData.overlayTex, sub.mip, sub.slice,
sub.numSlices);
}
}
else
{
RDCERR("Multiple slices bound without OVR_multiview");
// without OVR_multiview we can't bind the whole array, so just bind slice 0
drv.glFramebufferTextureLayer(eGL_FRAMEBUFFER, eGL_COLOR_ATTACHMENT0, DebugData.overlayTex,
sub.mip, sub.slice);
}
}
else
{
drv.glFramebufferTexture(eGL_FRAMEBUFFER, eGL_COLOR_ATTACHMENT0, DebugData.overlayTex, sub.mip);
}
}
else
{
if(texBindingEnum == eGL_TEXTURE_2D_MULTISAMPLE_ARRAY || texBindingEnum == eGL_TEXTURE_2D_ARRAY)
{
drv.glFramebufferTextureLayer(eGL_FRAMEBUFFER, eGL_COLOR_ATTACHMENT0, DebugData.overlayTex,
sub.mip, sub.slice);
}
else
{
drv.glFramebufferTexture2D(eGL_FRAMEBUFFER, eGL_COLOR_ATTACHMENT0, texBindingEnum,
DebugData.overlayTex, sub.mip);
}
}
}
ResourceId GLReplay::RenderOverlay(ResourceId texid, FloatVector clearCol, DebugOverlay overlay,
uint32_t eventId, const rdcarray<uint32_t> &passEvents)
{
WrappedOpenGL &drv = *m_pDriver;
MakeCurrentReplayContext(&m_ReplayCtx);
RenderOutputSubresource sub = GetRenderOutputSubresource(texid);
if(sub.slice == ~0U)
{
RDCERR("Rendering overlay for %s couldn't find output to get subresource.", ToStr(texid).c_str());
sub = RenderOutputSubresource(0, 0, 1);
}
GLMarkerRegion renderoverlay(StringFormat::Fmt("RenderOverlay %d", overlay));
ContextPair &ctx = drv.GetCtx();
GLRenderState rs;
rs.FetchState(&drv);
if(rs.Program.name == 0 && rs.Pipeline.name == 0)
return ResourceId();
GL.glBindBuffer(eGL_PIXEL_UNPACK_BUFFER, 0);
// use our overlay program that we'll fill up with all the right
// shaders, then replace the fragment shader with our own.
drv.glBindProgramPipeline(0);
if(DebugData.fixedcolFragShader)
drv.glDeleteShader(DebugData.fixedcolFragShader);
if(DebugData.quadoverdrawFragShader)
drv.glDeleteShader(DebugData.quadoverdrawFragShader);
DebugData.fixedcolFragShader = DebugData.quadoverdrawFragShader = 0;
ShaderType shaderType;
int glslVer;
if(IsGLES)
{
shaderType = ShaderType::GLSLES;
// default to 100 just in case something is broken
glslVer = 100;
// GLES requires that versions *precisely* match, so here we must figure out what version the
// existing shader was using (we pick the vertex shader for simplicity since by definition it
// must match any others) and recompile our shaders with that version. We've ensured they are
// compatible with anything after the minimum version
ResourceId vs;
if(rs.Program.name)
vs =
m_pDriver->m_Programs[m_pDriver->GetResourceManager()->GetResID(rs.Program)].stageShaders[0];
else
vs = m_pDriver->m_Pipelines[m_pDriver->GetResourceManager()->GetResID(rs.Pipeline)]
.stageShaders[0];
if(vs != ResourceId())
{
glslVer = m_pDriver->m_Shaders[vs].version;
if(glslVer == 0)
glslVer = 100;
}
}
else
{
// Desktop GL is vastly better, it can link any program so we just default to 150
glslVer = 150;
shaderType = ShaderType::GLSL;
}
// this is always compatible.
{
rdcstr source = GenerateGLSLShader(GetEmbeddedResource(glsl_fixedcol_frag), shaderType, glslVer);
DebugData.fixedcolFragShader = CreateShader(eGL_FRAGMENT_SHADER, source);
}
// this is not supported on GLES
if(shaderType == ShaderType::GLSL)
{
rdcstr defines = "";
if(!HasExt[ARB_derivative_control])
{
// dFdx fine functions not available before GLSL 450. Use normal dFdx, which might be coarse,
// so won't show quad overdraw properly
defines += "#define dFdxFine dFdx\n\n";
defines += "#define dFdyFine dFdy\n\n";
RDCWARN("Quad overdraw requires GLSL 4.50 for dFd(xy)fine, using possibly coarse dFd(xy).");
}
DebugData.quadoverdrawFragShader = 0;
// needs these extensions
if(HasExt[ARB_gpu_shader5] && HasExt[ARB_shader_image_load_store])
{
rdcstr source =
GenerateGLSLShader(GetEmbeddedResource(glsl_quadwrite_frag), shaderType, glslVer, defines);
DebugData.quadoverdrawFragShader = CreateShader(eGL_FRAGMENT_SHADER, source);
// we expect if the SPIR-V extension is present then we've compiled this variant.
if(HasExt[ARB_gl_spirv])
{
RDCASSERT(DebugData.quadoverdrawFragShaderSPIRV);
}
}
}
else
{
if(overlay == DebugOverlay::QuadOverdrawDraw || overlay == DebugOverlay::QuadOverdrawPass)
RDCWARN("Quad overdraw not supported on GLES", glslVer);
}
// delete the old program if it exists
if(DebugData.overlayProg != 0)
drv.glDeleteProgram(DebugData.overlayProg);
DebugData.overlayProg = drv.glCreateProgram();
// we bind the separable program created for each shader, and copy
// uniforms and attrib bindings from the 'real' programs, wherever
// they are.
bool spirvOverlay = CreateFragmentShaderReplacementProgram(
rs.Program.name, DebugData.overlayProg, rs.Pipeline.name, DebugData.fixedcolFragShader,
DebugData.fixedcolFragShaderSPIRV);
drv.glUseProgram(DebugData.overlayProg);
GLint overlayFixedColLocation = 0;
// on SPIR-V overlays we don't query the location, it's baked into the shader
if(spirvOverlay)
overlayFixedColLocation = 99;
else
overlayFixedColLocation =
drv.glGetUniformLocation(DebugData.overlayProg, "RENDERDOC_Fixed_Color");
auto &texDetails = m_pDriver->m_Textures[texid];
GLenum texBindingEnum = eGL_TEXTURE_2D;
GLenum texQueryEnum = eGL_TEXTURE_BINDING_2D;
GLint texSlices = texDetails.depth;
if(TextureTarget(texDetails.curType) == eGL_TEXTURE_3D)
texSlices = 1;
if(texDetails.samples > 1)
{
texBindingEnum = eGL_TEXTURE_2D_MULTISAMPLE;
texQueryEnum = eGL_TEXTURE_BINDING_2D_MULTISAMPLE;
if(texSlices > 1)
{
texBindingEnum = eGL_TEXTURE_2D_MULTISAMPLE_ARRAY;
texQueryEnum = eGL_TEXTURE_BINDING_2D_MULTISAMPLE_ARRAY;
}
}
if(texSlices > 1)
{
texBindingEnum = eGL_TEXTURE_2D_ARRAY;
texQueryEnum = eGL_TEXTURE_BINDING_2D_ARRAY;
}
GLint texMips = GetNumMips(texDetails.curType, texDetails.resource.name, texDetails.width,
texDetails.height, texDetails.depth);
// resize (or create) the overlay texture and FBO if necessary
if(DebugData.overlayTexWidth != texDetails.width ||
DebugData.overlayTexHeight != texDetails.height ||
DebugData.overlayTexSamples != texDetails.samples || DebugData.overlayTexMips != texMips ||
DebugData.overlayTexSlices != texSlices)
{
if(DebugData.overlayFBO)
{
drv.glDeleteFramebuffers(1, &DebugData.overlayFBO);
drv.glDeleteTextures(1, &DebugData.overlayTex);
}
drv.glGenFramebuffers(1, &DebugData.overlayFBO);
drv.glBindFramebuffer(eGL_FRAMEBUFFER, DebugData.overlayFBO);
GLuint curTex = 0;
drv.glGetIntegerv(texQueryEnum, (GLint *)&curTex);
drv.glGenTextures(1, &DebugData.overlayTex);
drv.glBindTexture(texBindingEnum, DebugData.overlayTex);
DebugData.overlayTexWidth = texDetails.width;
DebugData.overlayTexHeight = texDetails.height;
DebugData.overlayTexSamples = texDetails.samples;
DebugData.overlayTexMips = texMips;
DebugData.overlayTexSlices = texSlices;
if(DebugData.overlayTexSamples > 1)
{
if(DebugData.overlayTexSlices > 1)
{
drv.glTextureStorage3DMultisampleEXT(DebugData.overlayTex, texBindingEnum,
texDetails.samples, eGL_RGBA16F, texDetails.width,
texDetails.height, texSlices, true);
}
else
{
drv.glTextureStorage2DMultisampleEXT(DebugData.overlayTex, texBindingEnum, texDetails.samples,
eGL_RGBA16F, texDetails.width, texDetails.height, true);
}
}
else
{
GLint internalFormat = eGL_RGBA16F;
GLenum format = eGL_RGBA;
GLenum type = eGL_FLOAT;
if(IsGLES && !HasExt[EXT_color_buffer_float])
{
internalFormat = eGL_RGBA8;
type = eGL_UNSIGNED_BYTE;
}
if(texSlices > 1)
{
drv.glTextureImage3DEXT(DebugData.overlayTex, texBindingEnum, 0, internalFormat,
texDetails.width, texDetails.height, texSlices, 0, format, type,
NULL);
for(GLint i = 1; i < texMips; i++)
drv.glTextureImage3DEXT(DebugData.overlayTex, texBindingEnum, i, internalFormat,
RDCMAX(1, texDetails.width >> i), RDCMAX(1, texDetails.height >> i),
texSlices, 0, format, type, NULL);
}
else
{
drv.glTextureImage2DEXT(DebugData.overlayTex, texBindingEnum, 0, internalFormat,
texDetails.width, texDetails.height, 0, format, type, NULL);
for(GLint i = 1; i < texMips; i++)
drv.glTextureImage2DEXT(DebugData.overlayTex, texBindingEnum, i, internalFormat,
RDCMAX(1, texDetails.width >> i),
RDCMAX(1, texDetails.height >> i), 0, format, type, NULL);
}
drv.glTextureParameteriEXT(DebugData.overlayTex, texBindingEnum, eGL_TEXTURE_MAX_LEVEL,
texMips - 1);
drv.glTextureParameteriEXT(DebugData.overlayTex, texBindingEnum, eGL_TEXTURE_MIN_FILTER,
eGL_NEAREST);
drv.glTextureParameteriEXT(DebugData.overlayTex, texBindingEnum, eGL_TEXTURE_MAG_FILTER,
eGL_NEAREST);
drv.glTextureParameteriEXT(DebugData.overlayTex, texBindingEnum, eGL_TEXTURE_WRAP_S,
eGL_CLAMP_TO_EDGE);
drv.glTextureParameteriEXT(DebugData.overlayTex, texBindingEnum, eGL_TEXTURE_WRAP_T,
eGL_CLAMP_TO_EDGE);
}
// clear all mips first
drv.glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
drv.glDisable(eGL_BLEND);
drv.glDepthMask(GL_FALSE);
drv.glDisable(eGL_CULL_FACE);
drv.glDisable(eGL_DEPTH_TEST);
drv.glDisable(eGL_STENCIL_TEST);
drv.glStencilMask(0);
drv.glBindTexture(texBindingEnum, curTex);
}
GLint outWidth = RDCMAX(1, texDetails.width >> sub.mip);
GLint outHeight = RDCMAX(1, texDetails.height >> sub.mip);
drv.glBindFramebuffer(eGL_FRAMEBUFFER, DebugData.overlayFBO);
drv.glDisable(eGL_SCISSOR_TEST);
// clear the overlay texture to black
{
GLfloat black[4] = {};
if(texSlices > 1)
{
for(GLint s = 0; s < texSlices; s++)
{
for(GLint m = 0; m < texMips; m++)
{
drv.glFramebufferTextureLayer(eGL_FRAMEBUFFER, eGL_COLOR_ATTACHMENT0,
DebugData.overlayTex, m, s);
drv.glClearBufferfv(eGL_COLOR, 0, black);
}
}
}
else
{
for(GLint m = 0; m < texMips; m++)
{
drv.glFramebufferTexture2D(eGL_FRAMEBUFFER, eGL_COLOR_ATTACHMENT0, texBindingEnum,
DebugData.overlayTex, m);
drv.glClearBufferfv(eGL_COLOR, 0, black);
}
}
}
// bind the desired mip/slice/slices
BindFramebufferTexture(sub, texBindingEnum, texDetails.samples);
// disable several tests/allow rendering - some overlays will override
// these states but commonly we don't want to inherit these states from
// the program's state.
drv.glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
drv.glDisable(eGL_BLEND);
drv.glDepthMask(GL_FALSE);
drv.glDisable(eGL_CULL_FACE);
drv.glDisable(eGL_DEPTH_TEST);
drv.glDisable(eGL_STENCIL_TEST);
drv.glStencilMask(0);
if(!IsGLES)
{
drv.glPolygonMode(eGL_FRONT_AND_BACK, eGL_FILL);
drv.glEnable(eGL_DEPTH_CLAMP);
}
if(HasExt[ARB_texture_multisample_no_array] || HasExt[ARB_texture_multisample])
drv.glDisable(eGL_SAMPLE_MASK);
if(HasExt[ARB_viewport_array])
{
for(size_t s = 0; s < ARRAY_COUNT(rs.Scissors); s++)
{
if(rs.Scissors[s].enabled)
drv.glEnablei(eGL_SCISSOR_TEST, (GLuint)s);
else
drv.glDisablei(eGL_SCISSOR_TEST, (GLuint)s);
}
}
else
{
if(rs.Scissors[0].enabled)
drv.glEnable(eGL_SCISSOR_TEST);
else
drv.glDisable(eGL_SCISSOR_TEST);
}
if(overlay == DebugOverlay::NaN || overlay == DebugOverlay::Clipping)
{
// just need the basic texture
float black[] = {0.0f, 0.0f, 0.0f, 0.0f};
drv.glClearBufferfv(eGL_COLOR, 0, black);
}
else if(overlay == DebugOverlay::Drawcall)
{
if(HasExt[ARB_viewport_array])
drv.glDisablei(eGL_SCISSOR_TEST, 0);
else
drv.glDisable(eGL_SCISSOR_TEST);
float black[] = {0.0f, 0.0f, 0.0f, 0.5f};
drv.glClearBufferfv(eGL_COLOR, 0, black);
float colVal[] = {0.8f, 0.1f, 0.8f, 1.0f};
drv.glProgramUniform4fv(DebugData.overlayProg, overlayFixedColLocation, 1, colVal);
ReplayLog(eventId, eReplay_OnlyDraw);
}
else if(overlay == DebugOverlay::Wireframe)
{
float wireCol[] = {200.0f / 255.0f, 255.0f / 255.0f, 0.0f / 255.0f, 0.0f};
drv.glClearBufferfv(eGL_COLOR, 0, wireCol);
wireCol[3] = 1.0f;
drv.glProgramUniform4fv(DebugData.overlayProg, overlayFixedColLocation, 1, wireCol);
if(!IsGLES)
{
// desktop GL is simple
drv.glPolygonMode(eGL_FRONT_AND_BACK, eGL_LINE);
ReplayLog(eventId, eReplay_OnlyDraw);
}
else
{
// GLES is hard. We need to readback the index buffer, convert it to a line list, then draw
// with that. We can at least use a client-side pointer for the index buffer.
GLint idxbuf = 0;
drv.glGetIntegerv(eGL_ELEMENT_ARRAY_BUFFER_BINDING, &idxbuf);
const ActionDescription *action = m_pDriver->GetAction(eventId);
const GLDrawParams &drawParams = m_pDriver->GetDrawParameters(eventId);
rdcarray<uint32_t> patchedIndices;
// readback the index buffer data
if(idxbuf)
{
rdcarray<byte> idxs;
uint32_t offset = action->indexOffset * drawParams.indexWidth;
uint32_t length = 1;
drv.glGetNamedBufferParameterivEXT(idxbuf, eGL_BUFFER_SIZE, (GLint *)&length);
idxs.resize(length);
drv.glGetBufferSubData(
eGL_ELEMENT_ARRAY_BUFFER, offset,
RDCMIN(GLsizeiptr(length - offset),
GLsizeiptr(action->numIndices) * GLsizeiptr(drawParams.indexWidth)),
&idxs[0]);
// unbind the real index buffer
drv.glBindBuffer(eGL_ELEMENT_ARRAY_BUFFER, 0);
uint32_t expectedSize = action->numIndices * drawParams.indexWidth;
if(idxs.size() < expectedSize)
{
RDCERR("Index buffer is as large as expected");
idxs.resize(expectedSize);
}
PatchLineStripIndexBuffer(
action, drawParams.topo,
drawParams.indexWidth == 1 ? (uint8_t *)idxs.data() : (uint8_t *)NULL,
drawParams.indexWidth == 2 ? (uint16_t *)idxs.data() : (uint16_t *)NULL,
drawParams.indexWidth == 4 ? (uint32_t *)idxs.data() : (uint32_t *)NULL, patchedIndices);
}
else
{
// generate 'index' list
rdcarray<uint32_t> idxs;
idxs.resize(action->numIndices);
for(uint32_t i = 0; i < action->numIndices; i++)
idxs[i] = i;
PatchLineStripIndexBuffer(action, drawParams.topo, NULL, NULL, idxs.data(), patchedIndices);
}
GLboolean primRestart = drv.glIsEnabled(eGL_PRIMITIVE_RESTART_FIXED_INDEX);
drv.glEnable(eGL_PRIMITIVE_RESTART_FIXED_INDEX);
if(action->flags & ActionFlags::Instanced)
{
if(HasExt[ARB_base_instance])
{
drv.glDrawElementsInstancedBaseVertexBaseInstance(
eGL_LINE_STRIP, (GLsizei)patchedIndices.size(), eGL_UNSIGNED_INT,
patchedIndices.data(), action->numInstances, 0, action->instanceOffset);
}
else
{
drv.glDrawElementsInstancedBaseVertex(eGL_LINE_STRIP, (GLsizei)patchedIndices.size(),
eGL_UNSIGNED_INT, patchedIndices.data(),
action->numInstances, 0);
}
}
else
{
drv.glDrawElementsBaseVertex(eGL_LINE_STRIP, (GLsizei)patchedIndices.size(),
eGL_UNSIGNED_INT, patchedIndices.data(), 0);
}
if(!primRestart)
drv.glDisable(eGL_PRIMITIVE_RESTART_FIXED_INDEX);
drv.glBindBuffer(eGL_ELEMENT_ARRAY_BUFFER, idxbuf);
}
}
else if(overlay == DebugOverlay::ViewportScissor)
{
float col[] = {0.0f, 0.0f, 0.0f, 0.0f};
drv.glClearBufferfv(eGL_COLOR, 0, col);
if(HasExt[ARB_viewport_array])
drv.glDisablei(eGL_SCISSOR_TEST, 0);
else
drv.glDisable(eGL_SCISSOR_TEST);
col[0] = 1.0f;
col[1] = 0.0f;
col[3] = 1.0f;
drv.glProgramUniform4fv(DebugData.overlayProg, overlayFixedColLocation, 1, col);
ReplayLog(eventId, eReplay_OnlyDraw);
if(HasExt[ARB_viewport_array])
{
if(rs.Scissors[0].enabled)
drv.glEnablei(eGL_SCISSOR_TEST, 0);
else
drv.glDisablei(eGL_SCISSOR_TEST, 0);
}
else
{
if(rs.Scissors[0].enabled)
drv.glEnable(eGL_SCISSOR_TEST);
else
drv.glDisable(eGL_SCISSOR_TEST);
}
col[0] = 0.0f;
col[1] = 1.0f;
drv.glProgramUniform4fv(DebugData.overlayProg, overlayFixedColLocation, 1, col);
ReplayLog(eventId, eReplay_OnlyDraw);
// don't need to use the existing program at all!
drv.glUseProgram(DebugData.checkerProg);
drv.glBindProgramPipeline(0);
if(HasExt[ARB_viewport_array])
{
drv.glDisablei(eGL_SCISSOR_TEST, 0);
drv.glViewportIndexedf(0, rs.Viewports[0].x, rs.Viewports[0].y, rs.Viewports[0].width,
rs.Viewports[0].height);
}
else
{
drv.glDisable(eGL_SCISSOR_TEST);
drv.glViewport((GLint)rs.Viewports[0].x, (GLint)rs.Viewports[0].y,
(GLsizei)rs.Viewports[0].width, (GLsizei)rs.Viewports[0].height);
}
if(HasExt[ARB_draw_buffers_blend])
{
drv.glEnablei(eGL_BLEND, 0);
drv.glBlendFuncSeparatei(0, eGL_SRC_ALPHA, eGL_ONE_MINUS_SRC_ALPHA, eGL_SRC_ALPHA,
eGL_ONE_MINUS_SRC_ALPHA);
drv.glBlendEquationSeparatei(0, eGL_FUNC_ADD, eGL_FUNC_ADD);
}
else
{
drv.glBlendFuncSeparate(eGL_SRC_ALPHA, eGL_ONE_MINUS_SRC_ALPHA, eGL_SRC_ALPHA,
eGL_ONE_MINUS_SRC_ALPHA);
drv.glBlendEquationSeparate(eGL_FUNC_ADD, eGL_FUNC_ADD);
drv.glEnable(eGL_BLEND);
}
drv.glBlendColor(1.0f, 1.0f, 1.0f, 1.0f);
drv.glBindBufferBase(eGL_UNIFORM_BUFFER, 0, DebugData.UBOs[0]);
CheckerboardUBOData *cdata = (CheckerboardUBOData *)drv.glMapBufferRange(
eGL_UNIFORM_BUFFER, 0, sizeof(CheckerboardUBOData),
GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
cdata->BorderWidth = 3;
cdata->CheckerSquareDimension = 16.0f;
// set primary/secondary to the same to 'disable' checkerboard
cdata->PrimaryColor = cdata->SecondaryColor = Vec4f(0.1f, 0.1f, 0.1f, 1.0f);
cdata->InnerColor = Vec4f(0.2f, 0.2f, 0.9f, 0.4f);
// set viewport rect
cdata->RectPosition = Vec2f(rs.Viewports[0].x, rs.Viewports[0].y);
cdata->RectSize = Vec2f(rs.Viewports[0].width, rs.Viewports[0].height);
drv.glUnmapBuffer(eGL_UNIFORM_BUFFER);
drv.glDrawArrays(eGL_TRIANGLE_STRIP, 0, 4);
if(rs.Scissors[0].enabled)
{
Vec4f scissor((float)rs.Scissors[0].x, (float)rs.Scissors[0].y, (float)rs.Scissors[0].width,
(float)rs.Scissors[0].height);
if(HasExt[ARB_viewport_array])
drv.glViewportIndexedf(0, scissor.x, scissor.y, scissor.z, scissor.w);
else
drv.glViewport(rs.Scissors[0].x, rs.Scissors[0].y, rs.Scissors[0].width,
rs.Scissors[0].height);
cdata = (CheckerboardUBOData *)drv.glMapBufferRange(
eGL_UNIFORM_BUFFER, 0, sizeof(CheckerboardUBOData),
GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
cdata->BorderWidth = 3;
cdata->CheckerSquareDimension = 16.0f;
// black/white checkered border
cdata->PrimaryColor = Vec4f(1.0f, 1.0f, 1.0f, 1.0f);
cdata->SecondaryColor = Vec4f(0.0f, 0.0f, 0.0f, 1.0f);
// nothing at all inside
cdata->InnerColor = Vec4f(0.0f, 0.0f, 0.0f, 0.0f);
cdata->RectPosition = Vec2f(scissor.x, scissor.y);
cdata->RectSize = Vec2f(scissor.z, scissor.w);
drv.glUnmapBuffer(eGL_UNIFORM_BUFFER);
drv.glDrawArrays(eGL_TRIANGLE_STRIP, 0, 4);
}
}
else if(overlay == DebugOverlay::Depth || overlay == DebugOverlay::Stencil)
{
if(HasExt[ARB_viewport_array])
drv.glDisablei(eGL_SCISSOR_TEST, 0);
else
drv.glDisable(eGL_SCISSOR_TEST);
float backCol[] = {0.0f, 0.0f, 0.0f, 0.0f};
drv.glClearBufferfv(eGL_COLOR, 0, backCol);
if(HasExt[ARB_viewport_array])
{
if(rs.Scissors[0].enabled)
drv.glEnablei(eGL_SCISSOR_TEST, 0);
else
drv.glDisablei(eGL_SCISSOR_TEST, 0);
}
else
{
if(rs.Scissors[0].enabled)
drv.glEnable(eGL_SCISSOR_TEST);
else
drv.glDisable(eGL_SCISSOR_TEST);
}
float red[] = {1.0f, 0.0f, 0.0f, 1.0f};
drv.glProgramUniform4fv(DebugData.overlayProg, overlayFixedColLocation, 1, red);
ReplayLog(eventId, eReplay_OnlyDraw);
GLuint curDepth = 0, curStencil = 0;
drv.glGetNamedFramebufferAttachmentParameterivEXT(rs.DrawFBO.name, eGL_DEPTH_ATTACHMENT,
eGL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME,
(GLint *)&curDepth);
drv.glGetNamedFramebufferAttachmentParameterivEXT(rs.DrawFBO.name, eGL_STENCIL_ATTACHMENT,
eGL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME,
(GLint *)&curStencil);
GLenum copyBindingEnum = texBindingEnum;
GLenum copyQueryEnum = texQueryEnum;
GLuint depthCopy = 0, stencilCopy = 0;
// create matching depth for existing FBO
if(curDepth != 0)
{
GLint type = 0;
drv.glGetNamedFramebufferAttachmentParameterivEXT(
rs.DrawFBO.name, eGL_DEPTH_ATTACHMENT, eGL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &type);
GLenum fmt;
if(type != eGL_RENDERBUFFER)
{
ResourceId id = m_pDriver->GetResourceManager()->GetResID(TextureRes(ctx, curDepth));
fmt = m_pDriver->m_Textures[id].internalFormat;
}
else
{
ResourceId id = m_pDriver->GetResourceManager()->GetResID(RenderbufferRes(ctx, curDepth));
fmt = m_pDriver->m_Textures[id].internalFormat;
GLint depth = 0;
GLint stencil = 0;
GL.glGetNamedRenderbufferParameterivEXT(curDepth, eGL_RENDERBUFFER_DEPTH_SIZE, &depth);
GL.glGetNamedRenderbufferParameterivEXT(curDepth, eGL_RENDERBUFFER_STENCIL_SIZE, &stencil);
if(depth == 16 && stencil == 0)
fmt = eGL_DEPTH_COMPONENT16;
else if(depth == 24 && stencil == 0)
fmt = eGL_DEPTH_COMPONENT24;
else if(depth == 24 && stencil == 8)
fmt = eGL_DEPTH24_STENCIL8;
else if(depth == 32 && stencil == 0)
fmt = eGL_DEPTH_COMPONENT32F;
else if(depth == 32 && stencil == 8)
fmt = eGL_DEPTH32F_STENCIL8;
else if(depth == 0 && stencil == 8)
fmt = eGL_STENCIL_INDEX8;
}
if(copyBindingEnum == eGL_TEXTURE_CUBE_MAP)
{
copyBindingEnum = eGL_TEXTURE_2D;
copyQueryEnum = eGL_TEXTURE_BINDING_2D;
}
GLuint curTex = 0;
drv.glGetIntegerv(copyQueryEnum, (GLint *)&curTex);
drv.glGenTextures(1, &depthCopy);
drv.glBindTexture(copyBindingEnum, depthCopy);
if(DebugData.overlayTexSlices > 1)
{
if(DebugData.overlayTexSamples > 1)
{
drv.glTextureStorage3DMultisampleEXT(
depthCopy, copyBindingEnum, DebugData.overlayTexSamples, fmt, DebugData.overlayTexWidth,
DebugData.overlayTexHeight, DebugData.overlayTexSlices, true);
}
else
{
drv.glTextureImage3DEXT(depthCopy, copyBindingEnum, 0, fmt, DebugData.overlayTexWidth,
DebugData.overlayTexHeight, DebugData.overlayTexSlices, 0,
GetBaseFormat(fmt), GetDataType(fmt), NULL);
for(GLint i = 1; i < texMips; i++)
drv.glTextureImage3DEXT(
depthCopy, copyBindingEnum, i, fmt, RDCMAX(1, DebugData.overlayTexWidth >> i),
RDCMAX(1, DebugData.overlayTexHeight >> i), DebugData.overlayTexSlices, 0,
GetBaseFormat(fmt), GetDataType(fmt), NULL);
drv.glTextureParameteriEXT(depthCopy, copyBindingEnum, eGL_TEXTURE_MAX_LEVEL, 0);
drv.glTextureParameteriEXT(depthCopy, copyBindingEnum, eGL_TEXTURE_MIN_FILTER, eGL_NEAREST);
drv.glTextureParameteriEXT(depthCopy, copyBindingEnum, eGL_TEXTURE_MAG_FILTER, eGL_NEAREST);
drv.glTextureParameteriEXT(depthCopy, copyBindingEnum, eGL_TEXTURE_WRAP_S,
eGL_CLAMP_TO_EDGE);
drv.glTextureParameteriEXT(depthCopy, copyBindingEnum, eGL_TEXTURE_WRAP_T,
eGL_CLAMP_TO_EDGE);
}
}
else
{
if(DebugData.overlayTexSamples > 1)
{
drv.glTextureStorage2DMultisampleEXT(
depthCopy, copyBindingEnum, DebugData.overlayTexSamples, fmt,
DebugData.overlayTexWidth, DebugData.overlayTexHeight, true);
}
else
{
drv.glTextureImage2DEXT(depthCopy, copyBindingEnum, 0, fmt, DebugData.overlayTexWidth,
DebugData.overlayTexHeight, 0, GetBaseFormat(fmt),
GetDataType(fmt), NULL);
for(GLint i = 1; i < texMips; i++)
drv.glTextureImage2DEXT(depthCopy, copyBindingEnum, i, fmt,
RDCMAX(1, DebugData.overlayTexWidth >> i),
RDCMAX(1, DebugData.overlayTexHeight >> i), 0,
GetBaseFormat(fmt), GetDataType(fmt), NULL);
drv.glTextureParameteriEXT(depthCopy, copyBindingEnum, eGL_TEXTURE_MAX_LEVEL, 0);
drv.glTextureParameteriEXT(depthCopy, copyBindingEnum, eGL_TEXTURE_MIN_FILTER, eGL_NEAREST);
drv.glTextureParameteriEXT(depthCopy, copyBindingEnum, eGL_TEXTURE_MAG_FILTER, eGL_NEAREST);
drv.glTextureParameteriEXT(depthCopy, copyBindingEnum, eGL_TEXTURE_WRAP_S,
eGL_CLAMP_TO_EDGE);
drv.glTextureParameteriEXT(depthCopy, copyBindingEnum, eGL_TEXTURE_WRAP_T,
eGL_CLAMP_TO_EDGE);
}
}
drv.glBindTexture(copyBindingEnum, curTex);
}
// create matching separate stencil if relevant
if(curStencil != curDepth && curStencil != 0)
{
GLint type = 0;
drv.glGetNamedFramebufferAttachmentParameterivEXT(
rs.DrawFBO.name, eGL_STENCIL_ATTACHMENT, eGL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &type);
GLenum fmt;
if(type != eGL_RENDERBUFFER)
{
ResourceId id = m_pDriver->GetResourceManager()->GetResID(TextureRes(ctx, curDepth));
fmt = m_pDriver->m_Textures[id].internalFormat;
}
else
{
ResourceId id = m_pDriver->GetResourceManager()->GetResID(RenderbufferRes(ctx, curDepth));
fmt = m_pDriver->m_Textures[id].internalFormat;
GLint depth = 0;
GLint stencil = 0;
GL.glGetNamedRenderbufferParameterivEXT(curDepth, eGL_RENDERBUFFER_DEPTH_SIZE, &depth);
GL.glGetNamedRenderbufferParameterivEXT(curDepth, eGL_RENDERBUFFER_STENCIL_SIZE, &stencil);
if(depth == 16 && stencil == 0)
fmt = eGL_DEPTH_COMPONENT16;
else if(depth == 24 && stencil == 0)
fmt = eGL_DEPTH_COMPONENT24;
else if(depth == 24 && stencil == 8)
fmt = eGL_DEPTH24_STENCIL8;
else if(depth == 32 && stencil == 0)
fmt = eGL_DEPTH_COMPONENT32F;
else if(depth == 32 && stencil == 8)
fmt = eGL_DEPTH32F_STENCIL8;
else if(depth == 0 && stencil == 8)
fmt = eGL_STENCIL_INDEX8;
}
GLuint curTex = 0;
drv.glGetIntegerv(copyQueryEnum, (GLint *)&curTex);
drv.glGenTextures(1, &stencilCopy);
drv.glBindTexture(copyBindingEnum, stencilCopy);
if(DebugData.overlayTexSlices > 1)
{
if(DebugData.overlayTexSamples > 1)
{
drv.glTextureStorage3DMultisampleEXT(stencilCopy, copyBindingEnum,
DebugData.overlayTexSamples, fmt,
DebugData.overlayTexWidth, DebugData.overlayTexHeight,
DebugData.overlayTexSlices, true);
}
else
{
drv.glTextureImage3DEXT(stencilCopy, copyBindingEnum, 0, fmt, DebugData.overlayTexWidth,
DebugData.overlayTexHeight, DebugData.overlayTexSlices, 0,
GetBaseFormat(fmt), GetDataType(fmt), NULL);
for(GLint i = 1; i < texMips; i++)
drv.glTextureImage3DEXT(
stencilCopy, copyBindingEnum, i, fmt, RDCMAX(1, DebugData.overlayTexWidth >> i),
RDCMAX(1, DebugData.overlayTexHeight >> i), DebugData.overlayTexSlices, 0,
GetBaseFormat(fmt), GetDataType(fmt), NULL);
drv.glTextureParameteriEXT(stencilCopy, copyBindingEnum, eGL_TEXTURE_MAX_LEVEL, 0);
drv.glTextureParameteriEXT(stencilCopy, copyBindingEnum, eGL_TEXTURE_MIN_FILTER,
eGL_NEAREST);
drv.glTextureParameteriEXT(stencilCopy, copyBindingEnum, eGL_TEXTURE_MAG_FILTER,
eGL_NEAREST);
drv.glTextureParameteriEXT(stencilCopy, copyBindingEnum, eGL_TEXTURE_WRAP_S,
eGL_CLAMP_TO_EDGE);
drv.glTextureParameteriEXT(stencilCopy, copyBindingEnum, eGL_TEXTURE_WRAP_T,
eGL_CLAMP_TO_EDGE);
}
}
else
{
if(DebugData.overlayTexSamples > 1)
{
drv.glTextureStorage2DMultisampleEXT(
stencilCopy, copyBindingEnum, DebugData.overlayTexSamples, fmt,
DebugData.overlayTexWidth, DebugData.overlayTexHeight, true);
}
else
{
drv.glTextureImage2DEXT(stencilCopy, copyBindingEnum, 0, fmt, DebugData.overlayTexWidth,
DebugData.overlayTexHeight, 0, GetBaseFormat(fmt),
GetDataType(fmt), NULL);
for(GLint i = 1; i < texMips; i++)
drv.glTextureImage2DEXT(stencilCopy, copyBindingEnum, i, fmt,
RDCMAX(1, DebugData.overlayTexWidth >> i),
RDCMAX(1, DebugData.overlayTexHeight >> i), 0,
GetBaseFormat(fmt), GetDataType(fmt), NULL);
drv.glTextureParameteriEXT(stencilCopy, copyBindingEnum, eGL_TEXTURE_MAX_LEVEL, 0);
drv.glTextureParameteriEXT(stencilCopy, copyBindingEnum, eGL_TEXTURE_MIN_FILTER,
eGL_NEAREST);
drv.glTextureParameteriEXT(stencilCopy, copyBindingEnum, eGL_TEXTURE_MAG_FILTER,
eGL_NEAREST);
drv.glTextureParameteriEXT(stencilCopy, copyBindingEnum, eGL_TEXTURE_WRAP_S,
eGL_CLAMP_TO_EDGE);
drv.glTextureParameteriEXT(stencilCopy, copyBindingEnum, eGL_TEXTURE_WRAP_T,
eGL_CLAMP_TO_EDGE);
}
}
drv.glBindTexture(copyBindingEnum, curTex);
}
// bind depth/stencil to overlay FBO (currently bound to DRAW_FRAMEBUFFER)
if(curDepth != 0 && curDepth == curStencil)
{
drv.glFramebufferTexture2D(eGL_DRAW_FRAMEBUFFER, eGL_DEPTH_STENCIL_ATTACHMENT,
copyBindingEnum, depthCopy, sub.mip);
}
else if(curDepth != 0)
{
drv.glFramebufferTexture2D(eGL_DRAW_FRAMEBUFFER, eGL_DEPTH_ATTACHMENT, copyBindingEnum,
depthCopy, sub.mip);
}
else if(curStencil != 0)
{
drv.glFramebufferTexture2D(eGL_DRAW_FRAMEBUFFER, eGL_STENCIL_ATTACHMENT, copyBindingEnum,
stencilCopy, sub.mip);
}
// bind the 'real' fbo to the read framebuffer, so we can blit from it
drv.glBindFramebuffer(eGL_READ_FRAMEBUFFER, rs.DrawFBO.name);
float green[] = {0.0f, 1.0f, 0.0f, 1.0f};
drv.glProgramUniform4fv(DebugData.overlayProg, overlayFixedColLocation, 1, green);
if(overlay == DebugOverlay::Depth)
{
if(rs.Enabled[GLRenderState::eEnabled_DepthTest])
drv.glEnable(eGL_DEPTH_TEST);
else
drv.glDisable(eGL_DEPTH_TEST);
if(rs.DepthWriteMask)
drv.glDepthMask(GL_TRUE);
else
drv.glDepthMask(GL_FALSE);
}
else
{
if(rs.Enabled[GLRenderState::eEnabled_StencilTest])
drv.glEnable(eGL_STENCIL_TEST);
else
drv.glDisable(eGL_STENCIL_TEST);
drv.glStencilMaskSeparate(eGL_FRONT, rs.StencilFront.writemask);
drv.glStencilMaskSeparate(eGL_BACK, rs.StencilBack.writemask);
}
if(rs.Enabled[GLRenderState::eEnabled_CullFace])
drv.glEnable(eGL_CULL_FACE);
if(!IsGLES && !rs.Enabled[GLRenderState::eEnabled_DepthClamp])
drv.glDisable(eGL_DEPTH_CLAMP);
// get latest depth/stencil from read FBO (existing FBO) into draw FBO (overlay FBO)
SafeBlitFramebuffer(0, 0, outWidth, outHeight, 0, 0, outWidth, outHeight,
GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT, eGL_NEAREST);
ReplayLog(eventId, eReplay_OnlyDraw);
// unset depth/stencil textures from overlay FBO and delete temp depth/stencil
if(curDepth != 0 && curDepth == curStencil)
drv.glFramebufferTexture2D(eGL_DRAW_FRAMEBUFFER, eGL_DEPTH_STENCIL_ATTACHMENT, eGL_TEXTURE_2D,
0, 0);
else if(curDepth != 0)
drv.glFramebufferTexture2D(eGL_DRAW_FRAMEBUFFER, eGL_DEPTH_ATTACHMENT, eGL_TEXTURE_2D, 0, 0);
else if(curStencil != 0)
drv.glFramebufferTexture2D(eGL_DRAW_FRAMEBUFFER, eGL_STENCIL_ATTACHMENT, eGL_TEXTURE_2D, 0, 0);
if(depthCopy != 0)
drv.glDeleteTextures(1, &depthCopy);
if(stencilCopy != 0)
drv.glDeleteTextures(1, &stencilCopy);
}
else if(overlay == DebugOverlay::BackfaceCull)
{
if(HasExt[ARB_viewport_array])
drv.glDisablei(eGL_SCISSOR_TEST, 0);
else
drv.glDisable(eGL_SCISSOR_TEST);
float col[] = {0.0f, 0.0f, 0.0f, 0.0f};
drv.glClearBufferfv(eGL_COLOR, 0, col);
if(HasExt[ARB_viewport_array])
{
if(rs.Scissors[0].enabled)
drv.glEnablei(eGL_SCISSOR_TEST, 0);
else
drv.glDisablei(eGL_SCISSOR_TEST, 0);
}
else
{
if(rs.Scissors[0].enabled)
drv.glEnable(eGL_SCISSOR_TEST);
else
drv.glDisable(eGL_SCISSOR_TEST);
}
col[0] = 1.0f;
col[1] = 0.0f;
col[3] = 1.0f;
drv.glProgramUniform4fv(DebugData.overlayProg, overlayFixedColLocation, 1, col);
ReplayLog(eventId, eReplay_OnlyDraw);
// only enable cull face if it was enabled originally (otherwise
// we just render green over the exact same area, so it shows up "passing")
if(rs.Enabled[GLRenderState::eEnabled_CullFace])
drv.glEnable(eGL_CULL_FACE);
col[0] = 0.0f;
col[1] = 1.0f;
drv.glProgramUniform4fv(DebugData.overlayProg, overlayFixedColLocation, 1, col);
ReplayLog(eventId, eReplay_OnlyDraw);
}
else if(overlay == DebugOverlay::ClearBeforeDraw || overlay == DebugOverlay::ClearBeforePass)
{
if(HasExt[ARB_viewport_array])
drv.glDisablei(eGL_SCISSOR_TEST, 0);
else
drv.glDisable(eGL_SCISSOR_TEST);
float col[] = {0.0f, 0.0f, 0.0f, 0.0f};
drv.glClearBufferfv(eGL_COLOR, 0, col);
if(HasExt[ARB_viewport_array])
{
for(size_t s = 0; s < ARRAY_COUNT(rs.Scissors); s++)
{
if(rs.Scissors[s].enabled)
drv.glEnablei(eGL_SCISSOR_TEST, (GLuint)s);
else
drv.glDisablei(eGL_SCISSOR_TEST, (GLuint)s);
}
}
else
{
if(rs.Scissors[0].enabled)
drv.glEnable(eGL_SCISSOR_TEST);
else
drv.glDisable(eGL_SCISSOR_TEST);
}
rdcarray<uint32_t> events = passEvents;
if(overlay == DebugOverlay::ClearBeforeDraw)
events.clear();
events.push_back(eventId);
if(!events.empty())
{
if(overlay == DebugOverlay::ClearBeforePass)
{
m_pDriver->ReplayLog(0, events[0], eReplay_WithoutDraw);
}
else
{
// if we don't replay the real state, restore what we've changed
rs.ApplyState(&drv);
}
GLboolean scissor = HasExt[ARB_viewport_array] ? GL.glIsEnabledi(eGL_SCISSOR_TEST, 0)
: GL.glIsEnabled(eGL_SCISSOR_TEST);
if(HasExt[ARB_viewport_array])
drv.glDisablei(eGL_SCISSOR_TEST, 0);
else
drv.glDisable(eGL_SCISSOR_TEST);
for(int i = 0; i < 8; i++)
drv.glClearBufferfv(eGL_COLOR, i, &clearCol.x);
if(HasExt[ARB_viewport_array])
{
if(scissor == GL_TRUE)
drv.glEnablei(eGL_SCISSOR_TEST, 0);
else
drv.glDisablei(eGL_SCISSOR_TEST, 0);
}
else
{
if(scissor == GL_TRUE)
drv.glEnable(eGL_SCISSOR_TEST);
else
drv.glDisable(eGL_SCISSOR_TEST);
}
// Try to clear depth as well, to help debug shadow rendering
if(IsDepthStencilFormat(texDetails.internalFormat))
{
// If the depth func is equal or not equal, don't clear at all since the output would be
// altered in an way that would cause replay to produce mostly incorrect results.
// Similarly, skip if the depth func is always, as we'd have a 50% chance of guessing the
// wrong clear value.
if(rs.DepthFunc != eGL_EQUAL && rs.DepthFunc != eGL_NOTEQUAL && rs.DepthFunc != eGL_ALWAYS)
{
// Don't use the render state's depth clear value, as this overlay should show as much
// information as possible. Instead, clear to the value that would cause the most depth
// writes to happen.
bool depthFuncLess = rs.DepthFunc == eGL_LESS || rs.DepthFunc == eGL_LEQUAL;
float depthClear = depthFuncLess ? 1.0f : 0.0f;
drv.glClearBufferfi(eGL_DEPTH_STENCIL, 0, depthClear, 0);
}
}
for(size_t i = 0; i < events.size(); i++)
{
m_pDriver->ReplayLog(events[i], events[i], eReplay_OnlyDraw);
if(overlay == DebugOverlay::ClearBeforePass && i + 1 < events.size())
m_pDriver->ReplayLog(events[i] + 1, events[i + 1], eReplay_WithoutDraw);
}
}
}
else if(overlay == DebugOverlay::TriangleSizeDraw || overlay == DebugOverlay::TriangleSizePass)
{
SCOPED_TIMER("Triangle Size");
if(HasExt[ARB_viewport_array])
drv.glDisablei(eGL_SCISSOR_TEST, 0);
else
drv.glDisable(eGL_SCISSOR_TEST);
float black[] = {0.0f, 0.0f, 0.0f, 0.0f};
drv.glClearBufferfv(eGL_COLOR, 0, black);
if(HasExt[ARB_viewport_array])
{
for(size_t s = 0; s < ARRAY_COUNT(rs.Scissors); s++)
{
if(rs.Scissors[s].enabled)
drv.glEnablei(eGL_SCISSOR_TEST, (GLuint)s);
else
drv.glDisablei(eGL_SCISSOR_TEST, (GLuint)s);
}
}
else
{
if(rs.Scissors[0].enabled)
drv.glEnable(eGL_SCISSOR_TEST);
else
drv.glDisable(eGL_SCISSOR_TEST);
}
MeshUBOData uboParams = {};
uboParams.homogenousInput = 1;
uboParams.invProj = Matrix4f::Identity();
uboParams.mvp = Matrix4f::Identity();
drv.glBindBuffer(eGL_COPY_WRITE_BUFFER, DebugData.UBOs[0]);
MeshUBOData *uboptr =
(MeshUBOData *)drv.glMapBufferRange(eGL_COPY_WRITE_BUFFER, 0, sizeof(MeshUBOData),
GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
*uboptr = uboParams;
drv.glUnmapBuffer(eGL_COPY_WRITE_BUFFER);
drv.glBindBuffer(eGL_COPY_WRITE_BUFFER, DebugData.UBOs[2]);
Vec4f *v = (Vec4f *)drv.glMapBufferRange(eGL_COPY_WRITE_BUFFER, 0, sizeof(Vec4f),
GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
*v = Vec4f(rs.Viewports[0].width, rs.Viewports[0].height);
drv.glUnmapBuffer(eGL_COPY_WRITE_BUFFER);
rdcarray<uint32_t> events = passEvents;
if(overlay == DebugOverlay::TriangleSizeDraw)
events.clear();
events.push_back(eventId);
if(!events.empty() && DebugData.trisizeProg)
{
if(overlay == DebugOverlay::TriangleSizePass)
ReplayLog(events[0], eReplay_WithoutDraw);
else
rs.ApplyState(m_pDriver);
// this all happens on the replay context so we need a temp FBO/VAO
GLuint overlayFBO = 0, tempVAO = 0;
drv.glGenFramebuffers(1, &overlayFBO);
drv.glGenVertexArrays(1, &tempVAO);
for(size_t i = 0; i < events.size(); i++)
{
GLboolean blending = GL_FALSE;
GLint depthwritemask = 1;
GLint stencilfmask = 0xff, stencilbmask = 0xff;
GLuint drawFBO = 0, prevVAO = 0;
struct UBO
{
GLuint buf;
GLint64 offs;
GLint64 size;
} ubos[3];
// save the state we're going to mess with
{
drv.glGetIntegerv(eGL_DEPTH_WRITEMASK, &depthwritemask);
drv.glGetIntegerv(eGL_STENCIL_WRITEMASK, &stencilfmask);
drv.glGetIntegerv(eGL_STENCIL_BACK_WRITEMASK, &stencilbmask);
drv.glGetIntegerv(eGL_DRAW_FRAMEBUFFER_BINDING, (GLint *)&drawFBO);
drv.glGetIntegerv(eGL_VERTEX_ARRAY_BINDING, (GLint *)&prevVAO);
blending = drv.glIsEnabled(eGL_BLEND);
for(uint32_t u = 0; u < 3; u++)
{
drv.glGetIntegeri_v(eGL_UNIFORM_BUFFER_BINDING, u, (GLint *)&ubos[u].buf);
drv.glGetInteger64i_v(eGL_UNIFORM_BUFFER_START, u, (GLint64 *)&ubos[u].offs);
drv.glGetInteger64i_v(eGL_UNIFORM_BUFFER_SIZE, u, (GLint64 *)&ubos[u].size);
}
}
// disable depth and stencil writes
drv.glDepthMask(GL_FALSE);
drv.glStencilMask(GL_FALSE);
// disable blending
drv.glDisable(eGL_BLEND);
// bind our UBOs
drv.glBindBufferBase(eGL_UNIFORM_BUFFER, 0, DebugData.UBOs[0]);
drv.glBindBufferBase(eGL_UNIFORM_BUFFER, 2, DebugData.UBOs[2]);
GLenum att = eGL_DEPTH_ATTACHMENT;
GLuint depthObj = 0, stencilObj = 0;
GLint type = 0, level = 0, layered = 0, layer = 0;
// do we have a stencil object?
drv.glGetNamedFramebufferAttachmentParameterivEXT(drawFBO, eGL_STENCIL_ATTACHMENT,
eGL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME,
(GLint *)&stencilObj);
// fetch the details of the 'real' depth attachment
drv.glGetNamedFramebufferAttachmentParameterivEXT(
drawFBO, att, eGL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, (GLint *)&depthObj);
drv.glGetNamedFramebufferAttachmentParameterivEXT(
drawFBO, att, eGL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &type);
if(depthObj && stencilObj)
{
att = eGL_DEPTH_STENCIL_ATTACHMENT;
}
else if(depthObj == 0 && stencilObj)
{
att = eGL_STENCIL_ATTACHMENT;
depthObj = stencilObj;
}
if(depthObj && type != eGL_RENDERBUFFER)
{
drv.glGetNamedFramebufferAttachmentParameterivEXT(
drawFBO, att, eGL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, &level);
drv.glGetNamedFramebufferAttachmentParameterivEXT(
drawFBO, att, eGL_FRAMEBUFFER_ATTACHMENT_LAYERED, &layered);
layered = (layered != 0);
layer = 0;
if(layered == 0)
drv.glGetNamedFramebufferAttachmentParameterivEXT(
drawFBO, att, eGL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER, &layer);
ResourceId id = m_pDriver->GetResourceManager()->GetResID(TextureRes(ctx, depthObj));
WrappedOpenGL::TextureData &details = m_pDriver->m_Textures[id];
if(details.curType == eGL_TEXTURE_CUBE_MAP)
{
GLenum face;
drv.glGetNamedFramebufferAttachmentParameterivEXT(
drawFBO, att, eGL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE, (GLint *)&face);
layer = CubeTargetIndex(face);
}
}
// bind our FBO
drv.glBindFramebuffer(eGL_DRAW_FRAMEBUFFER, overlayFBO);
if(texBindingEnum == eGL_TEXTURE_2D_ARRAY ||
texBindingEnum == eGL_TEXTURE_2D_MULTISAMPLE_ARRAY)
drv.glFramebufferTextureLayer(eGL_FRAMEBUFFER, eGL_COLOR_ATTACHMENT0,
DebugData.overlayTex, sub.mip, sub.slice);
else
drv.glFramebufferTexture2D(eGL_FRAMEBUFFER, eGL_COLOR_ATTACHMENT0, texBindingEnum,
DebugData.overlayTex, sub.mip);
// now apply the depth texture binding
if(depthObj)
{
if(type == eGL_RENDERBUFFER)
{
drv.glNamedFramebufferRenderbufferEXT(overlayFBO, att, eGL_RENDERBUFFER, depthObj);
}
else
{
if(!layered)
{
// we use old-style non-DSA for this because binding cubemap faces with EXT_dsa
// is completely messed up and broken
// if obj is a cubemap use face-specific targets
ResourceId id = m_pDriver->GetResourceManager()->GetResID(TextureRes(ctx, depthObj));
WrappedOpenGL::TextureData &details = m_pDriver->m_Textures[id];
if(details.curType == eGL_TEXTURE_CUBE_MAP)
{
GLenum faces[] = {
eGL_TEXTURE_CUBE_MAP_POSITIVE_X, eGL_TEXTURE_CUBE_MAP_NEGATIVE_X,
eGL_TEXTURE_CUBE_MAP_POSITIVE_Y, eGL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
eGL_TEXTURE_CUBE_MAP_POSITIVE_Z, eGL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
};
if(layer < 6)
{
drv.glFramebufferTexture2D(eGL_DRAW_FRAMEBUFFER, att, faces[layer], depthObj,
level);
}
else
{
RDCWARN(
"Invalid layer %u used to bind cubemap to framebuffer. Binding POSITIVE_X");
drv.glFramebufferTexture2D(eGL_DRAW_FRAMEBUFFER, att, faces[0], depthObj, level);
}
}
else if(details.curType == eGL_TEXTURE_CUBE_MAP_ARRAY ||
details.curType == eGL_TEXTURE_1D_ARRAY ||
details.curType == eGL_TEXTURE_2D_ARRAY)
{
drv.glFramebufferTextureLayer(eGL_DRAW_FRAMEBUFFER, att, depthObj, level, layer);
}
else
{
RDCASSERT(layer == 0);
drv.glNamedFramebufferTextureEXT(overlayFBO, att, depthObj, level);
}
}
else
{
drv.glNamedFramebufferTextureEXT(overlayFBO, att, depthObj, level);
}
}
}
GLuint prog = 0, pipe = 0;
drv.glGetIntegerv(eGL_CURRENT_PROGRAM, (GLint *)&prog);
drv.glGetIntegerv(eGL_PROGRAM_PIPELINE_BINDING, (GLint *)&pipe);
drv.glUseProgram(DebugData.trisizeProg);
drv.glBindProgramPipeline(0);
const ActionDescription *action = m_pDriver->GetAction(events[i]);
for(uint32_t inst = 0; action && inst < RDCMAX(1U, action->numInstances); inst++)
{
MeshFormat postvs = GetPostVSBuffers(events[i], inst, 0, MeshDataStage::GSOut);
if(postvs.vertexResourceId == ResourceId())
postvs = GetPostVSBuffers(events[i], inst, 0, MeshDataStage::VSOut);
if(postvs.vertexResourceId != ResourceId())
{
GLenum topo = MakeGLPrimitiveTopology(postvs.topology);
drv.glBindVertexArray(tempVAO);
{
if(postvs.format.Special())
{
if(postvs.format.type == ResourceFormatType::R10G10B10A2)
{
if(postvs.format.compType == CompType::UInt)
drv.glVertexAttribIFormat(0, 4, eGL_UNSIGNED_INT_2_10_10_10_REV, 0);
if(postvs.format.compType == CompType::SInt)
drv.glVertexAttribIFormat(0, 4, eGL_INT_2_10_10_10_REV, 0);
}
else if(postvs.format.type == ResourceFormatType::R11G11B10)
{
drv.glVertexAttribFormat(0, 4, eGL_UNSIGNED_INT_10F_11F_11F_REV, GL_FALSE, 0);
}
else
{
RDCWARN("Unsupported vertex attribute format: %x", postvs.format.type);
}
}
else if(postvs.format.compType == CompType::Float ||
postvs.format.compType == CompType::UNorm ||
postvs.format.compType == CompType::SNorm)
{
if(postvs.format.compByteWidth == 8)
{
drv.glVertexAttribLFormat(0, postvs.format.compCount, eGL_DOUBLE, 0);
}
else
{
GLenum fmttype = eGL_UNSIGNED_INT;
if(postvs.format.compByteWidth == 4)
{
if(postvs.format.compType == CompType::Float)
fmttype = eGL_FLOAT;
else if(postvs.format.compType == CompType::UNorm)
fmttype = eGL_UNSIGNED_INT;
else if(postvs.format.compType == CompType::SNorm)
fmttype = eGL_INT;
}
else if(postvs.format.compByteWidth == 2)
{
if(postvs.format.compType == CompType::Float)
fmttype = eGL_HALF_FLOAT;
else if(postvs.format.compType == CompType::UNorm)
fmttype = eGL_UNSIGNED_SHORT;
else if(postvs.format.compType == CompType::SNorm)
fmttype = eGL_SHORT;
}
else if(postvs.format.compByteWidth == 1)
{
if(postvs.format.compType == CompType::UNorm)
fmttype = eGL_UNSIGNED_BYTE;
else if(postvs.format.compType == CompType::SNorm)
fmttype = eGL_BYTE;
}
drv.glVertexAttribFormat(0, postvs.format.compCount, fmttype,
postvs.format.compType != CompType::Float, 0);
}
}
else if(postvs.format.compType == CompType::UInt ||
postvs.format.compType == CompType::SInt)
{
GLenum fmttype = eGL_UNSIGNED_INT;
if(postvs.format.compByteWidth == 4)
{
if(postvs.format.compType == CompType::UInt)
fmttype = eGL_UNSIGNED_INT;
else if(postvs.format.compType == CompType::SInt)
fmttype = eGL_INT;
}
else if(postvs.format.compByteWidth == 2)
{
if(postvs.format.compType == CompType::UInt)
fmttype = eGL_UNSIGNED_SHORT;
else if(postvs.format.compType == CompType::SInt)
fmttype = eGL_SHORT;
}
else if(postvs.format.compByteWidth == 1)
{
if(postvs.format.compType == CompType::UInt)
fmttype = eGL_UNSIGNED_BYTE;
else if(postvs.format.compType == CompType::SInt)
fmttype = eGL_BYTE;
}
drv.glVertexAttribIFormat(0, postvs.format.compCount, fmttype, 0);
}
GLuint vb =
m_pDriver->GetResourceManager()->GetCurrentResource(postvs.vertexResourceId).name;
drv.glBindVertexBuffer(0, vb, (GLintptr)postvs.vertexByteOffset,
postvs.vertexByteStride);
}
drv.glEnableVertexAttribArray(0);
drv.glDisableVertexAttribArray(1);
if(postvs.indexByteStride)
{
GLenum idxtype = eGL_UNSIGNED_BYTE;
if(postvs.indexByteStride == 2)
idxtype = eGL_UNSIGNED_SHORT;
else if(postvs.indexByteStride == 4)
idxtype = eGL_UNSIGNED_INT;
GLuint ib =
m_pDriver->GetResourceManager()->GetCurrentResource(postvs.indexResourceId).name;
drv.glBindBuffer(eGL_ELEMENT_ARRAY_BUFFER, ib);
drv.glDrawElementsBaseVertex(topo, postvs.numIndices, idxtype,
(const void *)uintptr_t(postvs.indexByteOffset),
postvs.baseVertex);
}
else
{
drv.glDrawArrays(topo, 0, postvs.numIndices);
}
}
}
// pop the state that we messed with
{
drv.glBindProgramPipeline(pipe);
drv.glUseProgram(prog);
if(blending)
drv.glEnable(eGL_BLEND);
else
drv.glDisable(eGL_BLEND);
// restore the previous FBO/VAO
drv.glBindFramebuffer(eGL_DRAW_FRAMEBUFFER, drawFBO);
drv.glBindVertexArray(prevVAO);
for(uint32_t u = 0; u < 3; u++)
{
if(ubos[u].buf == 0 || (ubos[u].offs == 0 && ubos[u].size == 0))
drv.glBindBufferBase(eGL_UNIFORM_BUFFER, u, ubos[u].buf);
else
drv.glBindBufferRange(eGL_UNIFORM_BUFFER, u, ubos[u].buf, (GLintptr)ubos[u].offs,
(GLsizeiptr)ubos[u].size);
}
drv.glDepthMask(depthwritemask ? GL_TRUE : GL_FALSE);
drv.glStencilMaskSeparate(eGL_FRONT, (GLuint)stencilfmask);
drv.glStencilMaskSeparate(eGL_BACK, (GLuint)stencilbmask);
}
if(overlay == DebugOverlay::TriangleSizePass)
{
m_pDriver->ReplayLog(0, events[i], eReplay_OnlyDraw);
if(i + 1 < events.size())
m_pDriver->ReplayLog(events[i] + 1, events[i + 1], eReplay_WithoutDraw);
}
}
drv.glDeleteFramebuffers(1, &overlayFBO);
drv.glDeleteVertexArrays(1, &tempVAO);
if(overlay == DebugOverlay::TriangleSizePass)
ReplayLog(eventId, eReplay_WithoutDraw);
}
}
else if(overlay == DebugOverlay::QuadOverdrawDraw || overlay == DebugOverlay::QuadOverdrawPass)
{
if(DebugData.quadoverdrawFragShader)
{
SCOPED_TIMER("Quad Overdraw");
if(HasExt[ARB_viewport_array])
drv.glDisablei(eGL_SCISSOR_TEST, 0);
else
drv.glDisable(eGL_SCISSOR_TEST);
float black[] = {0.0f, 0.0f, 0.0f, 0.0f};
drv.glClearBufferfv(eGL_COLOR, 0, black);
rdcarray<uint32_t> events = passEvents;
if(overlay == DebugOverlay::QuadOverdrawDraw)
events.clear();
events.push_back(eventId);
if(!events.empty())
{
GLuint replacefbo = 0;
GLuint overridedepth = 0;
GLuint quadtexs[2] = {0};
drv.glGenFramebuffers(1, &replacefbo);
drv.glBindFramebuffer(eGL_FRAMEBUFFER, replacefbo);
drv.glGenTextures(2, quadtexs);
// image for quad usage
drv.glBindTexture(eGL_TEXTURE_2D_ARRAY, quadtexs[1]);
drv.glTextureImage3DEXT(quadtexs[1], eGL_TEXTURE_2D_ARRAY, 0, eGL_R32UI,
RDCMAX(1, outWidth >> 1), RDCMAX(1, outHeight >> 1), 4, 0,
eGL_RED_INTEGER, eGL_UNSIGNED_INT, NULL);
drv.glTextureParameteriEXT(quadtexs[1], eGL_TEXTURE_2D_ARRAY, eGL_TEXTURE_MAX_LEVEL, 0);
// temporarily attach to FBO to clear it
GLint zero[4] = {0};
drv.glFramebufferTextureLayer(eGL_FRAMEBUFFER, eGL_COLOR_ATTACHMENT0, quadtexs[1], 0, 0);
drv.glClearBufferiv(eGL_COLOR, 0, zero);
drv.glFramebufferTextureLayer(eGL_FRAMEBUFFER, eGL_COLOR_ATTACHMENT0, quadtexs[1], 0, 1);
drv.glClearBufferiv(eGL_COLOR, 0, zero);
drv.glFramebufferTextureLayer(eGL_FRAMEBUFFER, eGL_COLOR_ATTACHMENT0, quadtexs[1], 0, 2);
drv.glClearBufferiv(eGL_COLOR, 0, zero);
drv.glFramebufferTextureLayer(eGL_FRAMEBUFFER, eGL_COLOR_ATTACHMENT0, quadtexs[1], 0, 3);
drv.glClearBufferiv(eGL_COLOR, 0, zero);
drv.glBindTexture(eGL_TEXTURE_2D, quadtexs[0]);
drv.glTextureImage2DEXT(quadtexs[0], eGL_TEXTURE_2D, 0, eGL_RGBA8, outWidth, outHeight, 0,
eGL_RGBA, eGL_UNSIGNED_BYTE, NULL);
drv.glTextureParameteriEXT(quadtexs[0], eGL_TEXTURE_2D, eGL_TEXTURE_MAX_LEVEL, 0);
drv.glTextureParameteriEXT(quadtexs[0], eGL_TEXTURE_2D, eGL_TEXTURE_MIN_FILTER, eGL_NEAREST);
drv.glTextureParameteriEXT(quadtexs[0], eGL_TEXTURE_2D, eGL_TEXTURE_MAG_FILTER, eGL_NEAREST);
drv.glTextureParameteriEXT(quadtexs[0], eGL_TEXTURE_2D, eGL_TEXTURE_WRAP_S,
eGL_CLAMP_TO_EDGE);
drv.glTextureParameteriEXT(quadtexs[0], eGL_TEXTURE_2D, eGL_TEXTURE_WRAP_T,
eGL_CLAMP_TO_EDGE);
drv.glFramebufferTexture2D(eGL_FRAMEBUFFER, eGL_COLOR_ATTACHMENT0, eGL_TEXTURE_2D,
quadtexs[0], 0);
GLuint curdrawfbo = 0, curreadfbo = 0;
GLuint curDepth = 0, depthType = 0;
if(overlay == DebugOverlay::QuadOverdrawPass)
ReplayLog(events[0], eReplay_WithoutDraw);
else
rs.ApplyState(m_pDriver);
drv.glGetIntegerv(eGL_DRAW_FRAMEBUFFER_BINDING, (GLint *)&curdrawfbo);
drv.glGetIntegerv(eGL_READ_FRAMEBUFFER_BINDING, (GLint *)&curreadfbo);
// TODO handle non-2D depth/stencil attachments and fetch slice or cubemap face
GLint mip = 0;
drv.glGetNamedFramebufferAttachmentParameterivEXT(curdrawfbo, eGL_DEPTH_ATTACHMENT,
eGL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME,
(GLint *)&curDepth);
drv.glGetNamedFramebufferAttachmentParameterivEXT(curdrawfbo, eGL_DEPTH_ATTACHMENT,
eGL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE,
(GLint *)&depthType);
GLenum fmt = eGL_DEPTH32F_STENCIL8;
GLenum depthEnum = eGL_TEXTURE_2D;
uint32_t depthSlices = 1, depthSamples = 1;
if(curDepth)
{
if(depthType == eGL_TEXTURE)
{
ResourceId id = m_pDriver->GetResourceManager()->GetResID(TextureRes(ctx, curDepth));
WrappedOpenGL::TextureData &depthdetails = m_pDriver->m_Textures[id];
depthSamples = depthdetails.samples;
depthSlices = depthdetails.depth;
if(depthdetails.samples > 1)
depthEnum = depthdetails.depth > 1 ? eGL_TEXTURE_2D_MULTISAMPLE_ARRAY
: eGL_TEXTURE_2D_MULTISAMPLE;
else
depthEnum = depthdetails.depth > 1 ? eGL_TEXTURE_2D_ARRAY : eGL_TEXTURE_2D;
if(depthEnum == eGL_TEXTURE_2D_MULTISAMPLE ||
depthEnum == eGL_TEXTURE_2D_MULTISAMPLE_ARRAY)
mip = 0;
else
drv.glGetNamedFramebufferAttachmentParameterivEXT(
curdrawfbo, eGL_DEPTH_ATTACHMENT, eGL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, &mip);
drv.glGetTextureLevelParameterivEXT(curDepth, depthEnum, mip,
eGL_TEXTURE_INTERNAL_FORMAT, (GLint *)&fmt);
}
else
{
drv.glGetNamedRenderbufferParameterivEXT(curDepth, eGL_RENDERBUFFER_INTERNAL_FORMAT,
(GLint *)&fmt);
}
}
GLenum dsAttach = eGL_DEPTH_STENCIL_ATTACHMENT;
if(GetBaseFormat(fmt) == eGL_DEPTH_COMPONENT)
dsAttach = eGL_DEPTH_ATTACHMENT;
drv.glBindFramebuffer(eGL_FRAMEBUFFER, replacefbo);
drv.glFramebufferTexture2D(eGL_FRAMEBUFFER, dsAttach, depthEnum, curDepth, 0);
if(depthEnum == eGL_TEXTURE_2D_MULTISAMPLE || depthEnum == eGL_TEXTURE_2D_MULTISAMPLE_ARRAY)
{
drv.CopyTex2DMSToArray(overridedepth, curDepth, outWidth, outHeight, depthSlices,
depthSamples, fmt);
drv.glTextureParameteriEXT(overridedepth, eGL_TEXTURE_2D_ARRAY, eGL_TEXTURE_MAX_LEVEL, 0);
drv.glTextureParameteriEXT(overridedepth, eGL_TEXTURE_2D_ARRAY, eGL_TEXTURE_MIN_FILTER,
eGL_NEAREST);
drv.glTextureParameteriEXT(overridedepth, eGL_TEXTURE_2D_ARRAY, eGL_TEXTURE_MAG_FILTER,
eGL_NEAREST);
drv.glTextureParameteriEXT(overridedepth, eGL_TEXTURE_2D_ARRAY, eGL_TEXTURE_WRAP_S,
eGL_CLAMP_TO_EDGE);
drv.glTextureParameteriEXT(overridedepth, eGL_TEXTURE_2D_ARRAY, eGL_TEXTURE_WRAP_T,
eGL_CLAMP_TO_EDGE);
drv.glFramebufferTextureLayer(eGL_FRAMEBUFFER, dsAttach, overridedepth, 0, 0);
}
drv.glBindFramebuffer(eGL_DRAW_FRAMEBUFFER, curdrawfbo);
drv.glBindFramebuffer(eGL_READ_FRAMEBUFFER, curreadfbo);
for(size_t i = 0; i < events.size(); i++)
{
GLint depthwritemask = 1;
GLint stencilfmask = 0xff, stencilbmask = 0xff;
struct
{
GLuint name;
GLuint level;
GLboolean layered;
GLuint layer;
GLenum access;
GLenum format;
} curimage0 = {0};
// save the state we're going to mess with
{
drv.glGetIntegerv(eGL_DEPTH_WRITEMASK, &depthwritemask);
drv.glGetIntegerv(eGL_STENCIL_WRITEMASK, &stencilfmask);
drv.glGetIntegerv(eGL_STENCIL_BACK_WRITEMASK, &stencilbmask);
drv.glGetIntegerv(eGL_DRAW_FRAMEBUFFER_BINDING, (GLint *)&curdrawfbo);
drv.glGetIntegerv(eGL_READ_FRAMEBUFFER_BINDING, (GLint *)&curreadfbo);
drv.glGetIntegeri_v(eGL_IMAGE_BINDING_NAME, 0, (GLint *)&curimage0.name);
drv.glGetIntegeri_v(eGL_IMAGE_BINDING_LEVEL, 0, (GLint *)&curimage0.level);
drv.glGetIntegeri_v(eGL_IMAGE_BINDING_ACCESS, 0, (GLint *)&curimage0.access);
drv.glGetIntegeri_v(eGL_IMAGE_BINDING_FORMAT, 0, (GLint *)&curimage0.format);
drv.glGetBooleani_v(eGL_IMAGE_BINDING_LAYERED, 0, &curimage0.layered);
if(curimage0.layered)
drv.glGetIntegeri_v(eGL_IMAGE_BINDING_LAYER, 0, (GLint *)&curimage0.layer);
}
// disable depth and stencil writes
drv.glDepthMask(GL_FALSE);
drv.glStencilMask(GL_FALSE);
// bind our FBO
drv.glBindFramebuffer(eGL_DRAW_FRAMEBUFFER, replacefbo);
// bind image
drv.glBindImageTexture(0, quadtexs[1], 0, GL_TRUE, 0, eGL_READ_WRITE, eGL_R32UI);
GLuint prog = 0, pipe = 0;
drv.glGetIntegerv(eGL_CURRENT_PROGRAM, (GLint *)&prog);
drv.glGetIntegerv(eGL_PROGRAM_PIPELINE_BINDING, (GLint *)&pipe);
// delete the old program if it exists
if(DebugData.overlayProg != 0)
drv.glDeleteProgram(DebugData.overlayProg);
DebugData.overlayProg = drv.glCreateProgram();
// replace fragment shader. This is exactly what we did
// at the start of this function for the single-event case, but now we have
// to do it for every event
spirvOverlay = CreateFragmentShaderReplacementProgram(
prog, DebugData.overlayProg, pipe, DebugData.quadoverdrawFragShader,
DebugData.quadoverdrawFragShaderSPIRV);
drv.glUseProgram(DebugData.overlayProg);
drv.glBindProgramPipeline(0);
if(!spirvOverlay)
{
GLint loc = drv.glGetUniformLocation(DebugData.overlayProg, "overdrawImage");
if(loc != -1)
drv.glUniform1i(loc, 0);
else
RDCERR("Couldn't get location of overdrawImage");
}
if(overlay == DebugOverlay::QuadOverdrawPass && overridedepth)
drv.CopyTex2DMSToArray(overridedepth, curDepth, outWidth, outHeight, depthSlices,
depthSamples, fmt);
m_pDriver->ReplayLog(0, events[i], eReplay_OnlyDraw);
// pop the state that we messed with
{
drv.glBindProgramPipeline(pipe);
drv.glUseProgram(prog);
if(curimage0.name)
drv.glBindImageTexture(0, curimage0.name, curimage0.level,
curimage0.layered ? GL_TRUE : GL_FALSE, curimage0.layer,
curimage0.access, curimage0.format);
else
drv.glBindImageTexture(0, 0, 0, GL_FALSE, 0, eGL_READ_ONLY, eGL_R32UI);
drv.glBindFramebuffer(eGL_DRAW_FRAMEBUFFER, curdrawfbo);
drv.glBindFramebuffer(eGL_READ_FRAMEBUFFER, curreadfbo);
drv.glDepthMask(depthwritemask ? GL_TRUE : GL_FALSE);
drv.glStencilMaskSeparate(eGL_FRONT, (GLuint)stencilfmask);
drv.glStencilMaskSeparate(eGL_BACK, (GLuint)stencilbmask);
}
if(overlay == DebugOverlay::QuadOverdrawPass)
{
m_pDriver->ReplayLog(0, events[i], eReplay_OnlyDraw);
if(i + 1 < events.size())
m_pDriver->ReplayLog(events[i] + 1, events[i + 1], eReplay_WithoutDraw);
}
}
// resolve pass
{
drv.glUseProgram(DebugData.quadoverdrawResolveProg);
drv.glBindProgramPipeline(0);
// modify our fbo to attach the overlay texture instead
drv.glBindFramebuffer(eGL_FRAMEBUFFER, replacefbo);
if(texBindingEnum == eGL_TEXTURE_2D_ARRAY ||
texBindingEnum == eGL_TEXTURE_2D_MULTISAMPLE_ARRAY)
{
drv.glFramebufferTextureLayer(eGL_FRAMEBUFFER, eGL_COLOR_ATTACHMENT0,
DebugData.overlayTex, sub.mip, sub.slice);
drv.glFramebufferTextureLayer(eGL_FRAMEBUFFER, eGL_DEPTH_STENCIL_ATTACHMENT, 0, 0, 0);
}
else
{
drv.glFramebufferTexture2D(eGL_FRAMEBUFFER, eGL_COLOR_ATTACHMENT0, texBindingEnum,
DebugData.overlayTex, sub.mip);
drv.glFramebufferTexture2D(eGL_FRAMEBUFFER, eGL_DEPTH_STENCIL_ATTACHMENT,
texBindingEnum, 0, 0);
}
drv.glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
drv.glDisable(eGL_BLEND);
drv.glDisable(eGL_SCISSOR_TEST);
drv.glDepthMask(GL_FALSE);
drv.glDisable(eGL_CULL_FACE);
if(!IsGLES)
drv.glPolygonMode(eGL_FRONT_AND_BACK, eGL_FILL);
drv.glDisable(eGL_DEPTH_TEST);
drv.glDisable(eGL_STENCIL_TEST);
drv.glStencilMask(0);
drv.glViewport(0, 0, outWidth, outHeight);
drv.glBindImageTexture(0, quadtexs[1], 0, GL_TRUE, 0, eGL_READ_WRITE, eGL_R32UI);
GLuint emptyVAO = 0;
drv.glGenVertexArrays(1, &emptyVAO);
drv.glBindVertexArray(emptyVAO);
drv.glDrawArrays(eGL_TRIANGLE_STRIP, 0, 4);
drv.glBindVertexArray(0);
drv.glDeleteVertexArrays(1, &emptyVAO);
}
drv.glDeleteFramebuffers(1, &replacefbo);
drv.glDeleteTextures(2, quadtexs);
drv.glDeleteTextures(1, &overridedepth);
if(overlay == DebugOverlay::QuadOverdrawPass)
ReplayLog(eventId, eReplay_WithoutDraw);
}
}
}
else
{
RDCERR(
"Unexpected/unimplemented overlay type - should implement a placeholder overlay for all "
"types");
}
rs.ApplyState(m_pDriver);
DebugData.overlayTexId =
m_pDriver->GetResourceManager()->GetResID(TextureRes(ctx, DebugData.overlayTex));
return DebugData.overlayTexId;
}