mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-07-29 02:41:08 +00:00
* Previously we were relying on the pipeline info create to initialise the reflection for an entry point, now we do it on demand wherever it is needed.
1465 lines
49 KiB
C++
1465 lines
49 KiB
C++
/******************************************************************************
|
|
* The MIT License (MIT)
|
|
*
|
|
* Copyright (c) 2018 Baldur Karlsson
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
******************************************************************************/
|
|
|
|
#include <float.h>
|
|
#include <algorithm>
|
|
#include "common/common.h"
|
|
#include "strings/string_utils.h"
|
|
#include "gl_driver.h"
|
|
#include "gl_replay.h"
|
|
#include "gl_resources.h"
|
|
|
|
void GLReplay::ClearPostVSCache()
|
|
{
|
|
WrappedOpenGL &gl = *m_pDriver;
|
|
|
|
for(auto it = m_PostVSData.begin(); it != m_PostVSData.end(); ++it)
|
|
{
|
|
gl.glDeleteBuffers(1, &it->second.vsout.buf);
|
|
gl.glDeleteBuffers(1, &it->second.vsout.idxBuf);
|
|
gl.glDeleteBuffers(1, &it->second.gsout.buf);
|
|
gl.glDeleteBuffers(1, &it->second.gsout.idxBuf);
|
|
}
|
|
|
|
m_PostVSData.clear();
|
|
}
|
|
|
|
void GLReplay::InitPostVSBuffers(uint32_t eventId)
|
|
{
|
|
if(m_PostVSData.find(eventId) != m_PostVSData.end())
|
|
return;
|
|
|
|
MakeCurrentReplayContext(&m_ReplayCtx);
|
|
|
|
WrappedOpenGL &gl = *m_pDriver;
|
|
if(gl.m_ActiveFeedback)
|
|
{
|
|
gl.glEndTransformFeedback();
|
|
gl.m_WasActiveFeedback = true;
|
|
}
|
|
|
|
GLResourceManager *rm = m_pDriver->GetResourceManager();
|
|
|
|
GLRenderState rs(&gl.GetHookset());
|
|
rs.FetchState(&gl);
|
|
GLuint elArrayBuffer = 0;
|
|
if(rs.VAO.name)
|
|
gl.glGetIntegerv(eGL_ELEMENT_ARRAY_BUFFER_BINDING, (GLint *)&elArrayBuffer);
|
|
|
|
// reflection structures
|
|
ShaderReflection *vsRefl = NULL;
|
|
ShaderReflection *tesRefl = NULL;
|
|
ShaderReflection *gsRefl = NULL;
|
|
|
|
// non-program used separable programs of each shader.
|
|
// we'll add our feedback varings to these programs, relink,
|
|
// and combine into a pipeline for use.
|
|
GLuint vsProg = 0;
|
|
GLuint tcsProg = 0;
|
|
GLuint tesProg = 0;
|
|
GLuint gsProg = 0;
|
|
|
|
// these are the 'real' programs with uniform values that we need
|
|
// to copy over to our separable programs.
|
|
GLuint vsProgSrc = 0;
|
|
GLuint tcsProgSrc = 0;
|
|
GLuint tesProgSrc = 0;
|
|
GLuint gsProgSrc = 0;
|
|
|
|
if(rs.Program.name == 0)
|
|
{
|
|
if(rs.Pipeline.name == 0)
|
|
{
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
ResourceId id = rm->GetID(rs.Pipeline);
|
|
auto &pipeDetails = m_pDriver->m_Pipelines[id];
|
|
|
|
if(pipeDetails.stageShaders[0] != ResourceId())
|
|
{
|
|
vsRefl = GetShader(pipeDetails.stageShaders[0], ShaderEntryPoint());
|
|
vsProg = m_pDriver->m_Shaders[pipeDetails.stageShaders[0]].prog;
|
|
vsProgSrc = rm->GetCurrentResource(pipeDetails.stagePrograms[0]).name;
|
|
}
|
|
if(pipeDetails.stageShaders[1] != ResourceId())
|
|
{
|
|
tcsProg = m_pDriver->m_Shaders[pipeDetails.stageShaders[1]].prog;
|
|
tcsProgSrc = rm->GetCurrentResource(pipeDetails.stagePrograms[1]).name;
|
|
}
|
|
if(pipeDetails.stageShaders[2] != ResourceId())
|
|
{
|
|
tesRefl = GetShader(pipeDetails.stageShaders[2], ShaderEntryPoint());
|
|
tesProg = m_pDriver->m_Shaders[pipeDetails.stageShaders[2]].prog;
|
|
tesProgSrc = rm->GetCurrentResource(pipeDetails.stagePrograms[2]).name;
|
|
}
|
|
if(pipeDetails.stageShaders[3] != ResourceId())
|
|
{
|
|
gsRefl = GetShader(pipeDetails.stageShaders[3], ShaderEntryPoint());
|
|
gsProg = m_pDriver->m_Shaders[pipeDetails.stageShaders[3]].prog;
|
|
gsProgSrc = rm->GetCurrentResource(pipeDetails.stagePrograms[3]).name;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
auto &progDetails = m_pDriver->m_Programs[rm->GetID(rs.Program)];
|
|
|
|
if(progDetails.stageShaders[0] != ResourceId())
|
|
{
|
|
vsRefl = GetShader(progDetails.stageShaders[0], ShaderEntryPoint());
|
|
vsProg = m_pDriver->m_Shaders[progDetails.stageShaders[0]].prog;
|
|
}
|
|
if(progDetails.stageShaders[1] != ResourceId())
|
|
{
|
|
tcsProg = m_pDriver->m_Shaders[progDetails.stageShaders[1]].prog;
|
|
}
|
|
if(progDetails.stageShaders[2] != ResourceId())
|
|
{
|
|
tesRefl = GetShader(progDetails.stageShaders[2], ShaderEntryPoint());
|
|
tesProg = m_pDriver->m_Shaders[progDetails.stageShaders[2]].prog;
|
|
}
|
|
if(progDetails.stageShaders[3] != ResourceId())
|
|
{
|
|
gsRefl = GetShader(progDetails.stageShaders[3], ShaderEntryPoint());
|
|
gsProg = m_pDriver->m_Shaders[progDetails.stageShaders[3]].prog;
|
|
}
|
|
|
|
vsProgSrc = tcsProgSrc = tesProgSrc = gsProgSrc = rs.Program.name;
|
|
}
|
|
|
|
if(vsRefl == NULL)
|
|
{
|
|
// no vertex shader bound (no vertex processing - compute only program
|
|
// or no program bound, for a clear etc)
|
|
m_PostVSData[eventId] = GLPostVSData();
|
|
return;
|
|
}
|
|
|
|
const DrawcallDescription *drawcall = m_pDriver->GetDrawcall(eventId);
|
|
|
|
if(drawcall->numIndices == 0)
|
|
{
|
|
// draw is 0 length, nothing to do
|
|
m_PostVSData[eventId] = GLPostVSData();
|
|
return;
|
|
}
|
|
|
|
list<string> matrixVaryings; // matrices need some fixup
|
|
vector<const char *> varyings;
|
|
|
|
// we don't want to do any work, so just discard before rasterizing
|
|
gl.glEnable(eGL_RASTERIZER_DISCARD);
|
|
|
|
CopyProgramAttribBindings(gl.GetHookset(), vsProgSrc, vsProg, vsRefl);
|
|
|
|
varyings.clear();
|
|
|
|
uint32_t stride = 0;
|
|
int32_t posidx = -1;
|
|
|
|
for(const SigParameter &sig : vsRefl->outputSignature)
|
|
{
|
|
const char *name = sig.varName.c_str();
|
|
size_t len = sig.varName.size();
|
|
|
|
bool include = true;
|
|
|
|
// for matrices with names including :row1, :row2 etc we only include :row0
|
|
// as a varying (but increment the stride for all rows to account for the space)
|
|
// and modify the name to remove the :row0 part
|
|
const char *colon = strchr(name, ':');
|
|
if(colon)
|
|
{
|
|
if(name[len - 1] != '0')
|
|
{
|
|
include = false;
|
|
}
|
|
else
|
|
{
|
|
matrixVaryings.push_back(string(name, colon));
|
|
name = matrixVaryings.back().c_str();
|
|
}
|
|
}
|
|
|
|
if(include)
|
|
varyings.push_back(name);
|
|
|
|
if(sig.systemValue == ShaderBuiltin::Position)
|
|
posidx = int32_t(varyings.size()) - 1;
|
|
|
|
stride += sizeof(float) * sig.compCount;
|
|
}
|
|
|
|
// shift position attribute up to first, keeping order otherwise
|
|
// the same
|
|
if(posidx > 0)
|
|
{
|
|
const char *pos = varyings[posidx];
|
|
varyings.erase(varyings.begin() + posidx);
|
|
varyings.insert(varyings.begin(), pos);
|
|
}
|
|
|
|
// this is REALLY ugly, but I've seen problems with varying specification, so we try and
|
|
// do some fixup by removing prefixes from the results we got from PROGRAM_OUTPUT.
|
|
//
|
|
// the problem I've seen is:
|
|
//
|
|
// struct vertex
|
|
// {
|
|
// vec4 Color;
|
|
// };
|
|
//
|
|
// layout(location = 0) out vertex Out;
|
|
//
|
|
// (from g_truc gl-410-primitive-tessellation-2). On AMD the varyings are what you might expect
|
|
// (from
|
|
// the PROGRAM_OUTPUT interface names reflected out): "Out.Color", "gl_Position"
|
|
// however nvidia complains unless you use "Color", "gl_Position". This holds even if you add
|
|
// other
|
|
// variables to the vertex struct.
|
|
//
|
|
// strangely another sample that in-lines the output block like so:
|
|
//
|
|
// out block
|
|
// {
|
|
// vec2 Texcoord;
|
|
// } Out;
|
|
//
|
|
// uses "block.Texcoord" (reflected name from PROGRAM_OUTPUT and accepted by varyings string on
|
|
// both
|
|
// vendors). This is inconsistent as it's type.member not structname.member as move.
|
|
//
|
|
// The spec is very vague on exactly what these names should be, so I can't say which is correct
|
|
// out of these three possibilities.
|
|
//
|
|
// So our 'fix' is to loop while we have problems linking with the varyings (since we know
|
|
// otherwise
|
|
// linking should succeed, as we only get here with a successfully linked separable program - if
|
|
// it fails
|
|
// to link, it's assigned 0 earlier) and remove any prefixes from variables seen in the link error
|
|
// string.
|
|
// The error string is something like:
|
|
// "error: Varying (named Out.Color) specified but not present in the program object."
|
|
//
|
|
// Yeh. Ugly. Not guaranteed to work at all, but hopefully the common case will just be a single
|
|
// block
|
|
// without any nesting so this might work.
|
|
// At least we don't have to reallocate strings all over, since the memory is
|
|
// already owned elsewhere, we just need to modify pointers to trim prefixes. Bright side?
|
|
|
|
GLint status = 0;
|
|
bool finished = false;
|
|
for(;;)
|
|
{
|
|
// specify current varyings & relink
|
|
gl.glTransformFeedbackVaryings(vsProg, (GLsizei)varyings.size(), &varyings[0],
|
|
eGL_INTERLEAVED_ATTRIBS);
|
|
gl.glLinkProgram(vsProg);
|
|
|
|
gl.glGetProgramiv(vsProg, eGL_LINK_STATUS, &status);
|
|
|
|
// all good! Hopefully we'll mostly hit this
|
|
if(status == 1)
|
|
break;
|
|
|
|
// if finished is true, this was our last attempt - there are no
|
|
// more fixups possible
|
|
if(finished)
|
|
break;
|
|
|
|
char buffer[1025] = {0};
|
|
gl.glGetProgramInfoLog(vsProg, 1024, NULL, buffer);
|
|
|
|
// assume we're finished and can't retry any more after this.
|
|
// if we find a potential 'fixup' we'll set this back to false
|
|
finished = true;
|
|
|
|
// see if any of our current varyings are present in the buffer string
|
|
for(size_t i = 0; i < varyings.size(); i++)
|
|
{
|
|
if(strstr(buffer, varyings[i]))
|
|
{
|
|
const char *prefix_removed = strchr(varyings[i], '.');
|
|
|
|
// does it contain a prefix?
|
|
if(prefix_removed)
|
|
{
|
|
prefix_removed++; // now this is our string without the prefix
|
|
|
|
// first check this won't cause a duplicate - if it does, we have to try something else
|
|
bool duplicate = false;
|
|
for(size_t j = 0; j < varyings.size(); j++)
|
|
{
|
|
if(!strcmp(varyings[j], prefix_removed))
|
|
{
|
|
duplicate = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!duplicate)
|
|
{
|
|
// we'll attempt this fixup
|
|
RDCWARN("Attempting XFB varying fixup, subst '%s' for '%s'", varyings[i], prefix_removed);
|
|
varyings[i] = prefix_removed;
|
|
finished = false;
|
|
|
|
// don't try more than one at once (just in case)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(status == 0)
|
|
{
|
|
char buffer[1025] = {0};
|
|
gl.glGetProgramInfoLog(vsProg, 1024, NULL, buffer);
|
|
RDCERR("Failed to fix-up. Link error making xfb vs program: %s", buffer);
|
|
m_PostVSData[eventId] = GLPostVSData();
|
|
return;
|
|
}
|
|
|
|
// make a pipeline to contain just the vertex shader
|
|
GLuint vsFeedbackPipe = 0;
|
|
gl.glGenProgramPipelines(1, &vsFeedbackPipe);
|
|
|
|
// bind the separable vertex program to it
|
|
gl.glUseProgramStages(vsFeedbackPipe, eGL_VERTEX_SHADER_BIT, vsProg);
|
|
|
|
// copy across any uniform values, bindings etc from the real program containing
|
|
// the vertex stage
|
|
CopyProgramUniforms(gl.GetHookset(), vsProgSrc, vsProg);
|
|
|
|
// bind our program and do the feedback draw
|
|
gl.glUseProgram(0);
|
|
gl.glBindProgramPipeline(vsFeedbackPipe);
|
|
|
|
gl.glBindTransformFeedback(eGL_TRANSFORM_FEEDBACK, DebugData.feedbackObj);
|
|
|
|
GLuint idxBuf = 0;
|
|
|
|
if(!(drawcall->flags & DrawFlags::UseIBuffer))
|
|
{
|
|
uint32_t outputSize = drawcall->numIndices * stride;
|
|
|
|
if(drawcall->flags & DrawFlags::Instanced)
|
|
outputSize *= drawcall->numInstances;
|
|
|
|
// resize up the buffer if needed for the vertex output data
|
|
if(DebugData.feedbackBufferSize < outputSize)
|
|
{
|
|
uint32_t oldSize = DebugData.feedbackBufferSize;
|
|
while(DebugData.feedbackBufferSize < outputSize)
|
|
DebugData.feedbackBufferSize *= 2;
|
|
RDCWARN("Resizing xfb buffer from %u to %u for output", oldSize, DebugData.feedbackBufferSize);
|
|
gl.glNamedBufferDataEXT(DebugData.feedbackBuffer, DebugData.feedbackBufferSize, NULL,
|
|
eGL_DYNAMIC_READ);
|
|
}
|
|
|
|
// need to rebind this here because of an AMD bug that seems to ignore the buffer
|
|
// bindings in the feedback object - or at least it errors if the default feedback
|
|
// object has no buffers bound. Fortunately the state is still object-local so
|
|
// we don't have to restore the buffer binding on the default feedback object.
|
|
gl.glBindBufferBase(eGL_TRANSFORM_FEEDBACK_BUFFER, 0, DebugData.feedbackBuffer);
|
|
|
|
gl.glBeginQuery(eGL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, DebugData.feedbackQueries[0]);
|
|
gl.glBeginTransformFeedback(eGL_POINTS);
|
|
|
|
if(drawcall->flags & DrawFlags::Instanced)
|
|
{
|
|
if(HasExt[ARB_base_instance])
|
|
{
|
|
gl.glDrawArraysInstancedBaseInstance(eGL_POINTS, drawcall->vertexOffset, drawcall->numIndices,
|
|
drawcall->numInstances, drawcall->instanceOffset);
|
|
}
|
|
else
|
|
{
|
|
gl.glDrawArraysInstanced(eGL_POINTS, drawcall->vertexOffset, drawcall->numIndices,
|
|
drawcall->numInstances);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gl.glDrawArrays(eGL_POINTS, drawcall->vertexOffset, drawcall->numIndices);
|
|
}
|
|
}
|
|
else // drawcall is indexed
|
|
{
|
|
ResourceId idxId = rm->GetID(BufferRes(NULL, elArrayBuffer));
|
|
|
|
bytebuf idxdata;
|
|
GetBufferData(idxId, drawcall->indexOffset * drawcall->indexByteWidth,
|
|
drawcall->numIndices * drawcall->indexByteWidth, idxdata);
|
|
|
|
vector<uint32_t> indices;
|
|
|
|
uint8_t *idx8 = (uint8_t *)&idxdata[0];
|
|
uint16_t *idx16 = (uint16_t *)&idxdata[0];
|
|
uint32_t *idx32 = (uint32_t *)&idxdata[0];
|
|
|
|
// only read as many indices as were available in the buffer
|
|
uint32_t numIndices =
|
|
RDCMIN(uint32_t(idxdata.size() / drawcall->indexByteWidth), drawcall->numIndices);
|
|
|
|
// grab all unique vertex indices referenced
|
|
for(uint32_t i = 0; i < numIndices; i++)
|
|
{
|
|
uint32_t i32 = 0;
|
|
if(drawcall->indexByteWidth == 1)
|
|
i32 = uint32_t(idx8[i]);
|
|
else if(drawcall->indexByteWidth == 2)
|
|
i32 = uint32_t(idx16[i]);
|
|
else if(drawcall->indexByteWidth == 4)
|
|
i32 = idx32[i];
|
|
|
|
auto it = std::lower_bound(indices.begin(), indices.end(), i32);
|
|
|
|
if(it != indices.end() && *it == i32)
|
|
continue;
|
|
|
|
indices.insert(it, i32);
|
|
}
|
|
|
|
// if we read out of bounds, we'll also have a 0 index being referenced
|
|
// (as 0 is read). Don't insert 0 if we already have 0 though
|
|
if(numIndices < drawcall->numIndices && (indices.empty() || indices[0] != 0))
|
|
indices.insert(indices.begin(), 0);
|
|
|
|
// An index buffer could be something like: 500, 501, 502, 501, 503, 502
|
|
// in which case we can't use the existing index buffer without filling 499 slots of vertex
|
|
// data with padding. Instead we rebase the indices based on the smallest vertex so it becomes
|
|
// 0, 1, 2, 1, 3, 2 and then that matches our stream-out'd buffer.
|
|
//
|
|
// Note that there could also be gaps, like: 500, 501, 502, 510, 511, 512
|
|
// which would become 0, 1, 2, 3, 4, 5 and so the old index buffer would no longer be valid.
|
|
// We just stream-out a tightly packed list of unique indices, and then remap the index buffer
|
|
// so that what did point to 500 points to 0 (accounting for rebasing), and what did point
|
|
// to 510 now points to 3 (accounting for the unique sort).
|
|
|
|
// we use a map here since the indices may be sparse. Especially considering if an index
|
|
// is 'invalid' like 0xcccccccc then we don't want an array of 3.4 billion entries.
|
|
map<uint32_t, size_t> indexRemap;
|
|
for(size_t i = 0; i < indices.size(); i++)
|
|
{
|
|
// by definition, this index will only appear once in indices[]
|
|
indexRemap[indices[i]] = i;
|
|
}
|
|
|
|
// generate a temporary index buffer with our 'unique index set' indices,
|
|
// so we can transform feedback each referenced vertex once
|
|
GLuint indexSetBuffer = 0;
|
|
gl.glGenBuffers(1, &indexSetBuffer);
|
|
gl.glBindBuffer(eGL_ELEMENT_ARRAY_BUFFER, indexSetBuffer);
|
|
gl.glNamedBufferDataEXT(indexSetBuffer, sizeof(uint32_t) * indices.size(), &indices[0],
|
|
eGL_STATIC_DRAW);
|
|
|
|
uint32_t outputSize = (uint32_t)indices.size() * stride;
|
|
|
|
if(drawcall->flags & DrawFlags::Instanced)
|
|
outputSize *= drawcall->numInstances;
|
|
|
|
// resize up the buffer if needed for the vertex output data
|
|
if(DebugData.feedbackBufferSize < outputSize)
|
|
{
|
|
uint32_t oldSize = DebugData.feedbackBufferSize;
|
|
while(DebugData.feedbackBufferSize < outputSize)
|
|
DebugData.feedbackBufferSize *= 2;
|
|
RDCWARN("Resizing xfb buffer from %u to %u for output", oldSize, DebugData.feedbackBufferSize);
|
|
gl.glNamedBufferDataEXT(DebugData.feedbackBuffer, DebugData.feedbackBufferSize, NULL,
|
|
eGL_DYNAMIC_READ);
|
|
}
|
|
|
|
// need to rebind this here because of an AMD bug that seems to ignore the buffer
|
|
// bindings in the feedback object - or at least it errors if the default feedback
|
|
// object has no buffers bound. Fortunately the state is still object-local so
|
|
// we don't have to restore the buffer binding on the default feedback object.
|
|
gl.glBindBufferBase(eGL_TRANSFORM_FEEDBACK_BUFFER, 0, DebugData.feedbackBuffer);
|
|
|
|
gl.glBeginQuery(eGL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, DebugData.feedbackQueries[0]);
|
|
gl.glBeginTransformFeedback(eGL_POINTS);
|
|
|
|
if(drawcall->flags & DrawFlags::Instanced)
|
|
{
|
|
if(HasExt[ARB_base_instance])
|
|
{
|
|
gl.glDrawElementsInstancedBaseVertexBaseInstance(
|
|
eGL_POINTS, (GLsizei)indices.size(), eGL_UNSIGNED_INT, NULL, drawcall->numInstances,
|
|
drawcall->baseVertex, drawcall->instanceOffset);
|
|
}
|
|
else
|
|
{
|
|
gl.glDrawElementsInstancedBaseVertex(eGL_POINTS, (GLsizei)indices.size(), eGL_UNSIGNED_INT,
|
|
NULL, drawcall->numInstances, drawcall->baseVertex);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gl.glDrawElementsBaseVertex(eGL_POINTS, (GLsizei)indices.size(), eGL_UNSIGNED_INT, NULL,
|
|
drawcall->baseVertex);
|
|
}
|
|
|
|
// delete the buffer, we don't need it anymore
|
|
gl.glBindBuffer(eGL_ELEMENT_ARRAY_BUFFER, elArrayBuffer);
|
|
gl.glDeleteBuffers(1, &indexSetBuffer);
|
|
|
|
uint32_t stripRestartValue32 = 0;
|
|
|
|
if(IsStrip(drawcall->topology) && rs.Enabled[GLRenderState::eEnabled_PrimitiveRestart])
|
|
{
|
|
stripRestartValue32 = rs.Enabled[GLRenderState::eEnabled_PrimitiveRestartFixedIndex]
|
|
? ~0U
|
|
: rs.PrimitiveRestartIndex;
|
|
}
|
|
|
|
// rebase existing index buffer to point from 0 onwards (which will index into our
|
|
// stream-out'd vertex buffer)
|
|
if(drawcall->indexByteWidth == 1)
|
|
{
|
|
uint8_t stripRestartValue = stripRestartValue32 & 0xff;
|
|
|
|
for(uint32_t i = 0; i < numIndices; i++)
|
|
{
|
|
// preserve primitive restart indices
|
|
if(stripRestartValue && idx8[i] == stripRestartValue)
|
|
continue;
|
|
|
|
idx8[i] = uint8_t(indexRemap[idx8[i]]);
|
|
}
|
|
}
|
|
else if(drawcall->indexByteWidth == 2)
|
|
{
|
|
uint16_t stripRestartValue = stripRestartValue32 & 0xffff;
|
|
|
|
for(uint32_t i = 0; i < numIndices; i++)
|
|
{
|
|
// preserve primitive restart indices
|
|
if(stripRestartValue && idx16[i] == stripRestartValue)
|
|
continue;
|
|
|
|
idx16[i] = uint16_t(indexRemap[idx16[i]]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
uint32_t stripRestartValue = stripRestartValue32;
|
|
|
|
for(uint32_t i = 0; i < numIndices; i++)
|
|
{
|
|
// preserve primitive restart indices
|
|
if(stripRestartValue && idx32[i] == stripRestartValue)
|
|
continue;
|
|
|
|
idx32[i] = uint32_t(indexRemap[idx32[i]]);
|
|
}
|
|
}
|
|
|
|
// make the index buffer that can be used to render this postvs data - the original
|
|
// indices, repointed (since we transform feedback to the start of our feedback
|
|
// buffer and only tightly packed unique indices).
|
|
if(!idxdata.empty())
|
|
{
|
|
gl.glGenBuffers(1, &idxBuf);
|
|
gl.glBindBuffer(eGL_ELEMENT_ARRAY_BUFFER, idxBuf);
|
|
gl.glNamedBufferDataEXT(idxBuf, (GLsizeiptr)idxdata.size(), &idxdata[0], eGL_STATIC_DRAW);
|
|
}
|
|
|
|
// restore previous element array buffer binding
|
|
gl.glBindBuffer(eGL_ELEMENT_ARRAY_BUFFER, elArrayBuffer);
|
|
}
|
|
|
|
gl.glEndTransformFeedback();
|
|
gl.glEndQuery(eGL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
|
|
|
|
bool error = false;
|
|
|
|
// this should be the same as the draw size
|
|
GLuint primsWritten = 0;
|
|
gl.glGetQueryObjectuiv(DebugData.feedbackQueries[0], eGL_QUERY_RESULT, &primsWritten);
|
|
|
|
if(primsWritten == 0)
|
|
{
|
|
// we bailed out much earlier if this was a draw of 0 verts
|
|
RDCERR("No primitives written - but we must have had some number of vertices in the draw");
|
|
error = true;
|
|
}
|
|
|
|
// get buffer data from buffer attached to feedback object
|
|
float *data = (float *)gl.glMapNamedBufferEXT(DebugData.feedbackBuffer, eGL_READ_ONLY);
|
|
|
|
if(data == NULL)
|
|
{
|
|
gl.glUnmapNamedBufferEXT(DebugData.feedbackBuffer);
|
|
RDCERR("Couldn't map feedback buffer!");
|
|
error = true;
|
|
}
|
|
|
|
if(error)
|
|
{
|
|
// delete temporary pipelines we made
|
|
gl.glDeleteProgramPipelines(1, &vsFeedbackPipe);
|
|
|
|
// restore replay state we trashed
|
|
gl.glUseProgram(rs.Program.name);
|
|
gl.glBindProgramPipeline(rs.Pipeline.name);
|
|
|
|
gl.glBindBuffer(eGL_ARRAY_BUFFER, rs.BufferBindings[GLRenderState::eBufIdx_Array].name);
|
|
gl.glBindBuffer(eGL_ELEMENT_ARRAY_BUFFER, elArrayBuffer);
|
|
|
|
gl.glBindTransformFeedback(eGL_TRANSFORM_FEEDBACK, rs.FeedbackObj.name);
|
|
|
|
if(!rs.Enabled[GLRenderState::eEnabled_RasterizerDiscard])
|
|
gl.glDisable(eGL_RASTERIZER_DISCARD);
|
|
else
|
|
gl.glEnable(eGL_RASTERIZER_DISCARD);
|
|
|
|
m_PostVSData[eventId] = GLPostVSData();
|
|
return;
|
|
}
|
|
|
|
// create a buffer with this data, for future use (typed to ARRAY_BUFFER so we
|
|
// can render from it to display previews).
|
|
GLuint vsoutBuffer = 0;
|
|
gl.glGenBuffers(1, &vsoutBuffer);
|
|
gl.glBindBuffer(eGL_ARRAY_BUFFER, vsoutBuffer);
|
|
gl.glNamedBufferDataEXT(vsoutBuffer, stride * primsWritten, data, eGL_STATIC_DRAW);
|
|
|
|
byte *byteData = (byte *)data;
|
|
|
|
float nearp = 0.1f;
|
|
float farp = 100.0f;
|
|
|
|
Vec4f *pos0 = (Vec4f *)byteData;
|
|
|
|
bool found = false;
|
|
|
|
for(GLuint i = 1; posidx != -1 && i < primsWritten; i++)
|
|
{
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
// derive near/far, assuming a standard perspective matrix
|
|
//
|
|
// the transformation from from pre-projection {Z,W} to post-projection {Z,W}
|
|
// is linear. So we can say Zpost = Zpre*m + c . Here we assume Wpre = 1
|
|
// and we know Wpost = Zpre from the perspective matrix.
|
|
// we can then see from the perspective matrix that
|
|
// m = F/(F-N)
|
|
// c = -(F*N)/(F-N)
|
|
//
|
|
// with re-arranging and substitution, we then get:
|
|
// N = -c/m
|
|
// F = c/(1-m)
|
|
//
|
|
// so if we can derive m and c then we can determine N and F. We can do this with
|
|
// two points, and we pick them reasonably distinct on z to reduce floating-point
|
|
// error
|
|
|
|
Vec4f *pos = (Vec4f *)(byteData + i * stride);
|
|
|
|
if(fabs(pos->w - pos0->w) > 0.01f && fabs(pos->z - pos0->z) > 0.01f)
|
|
{
|
|
Vec2f A(pos0->w, pos0->z);
|
|
Vec2f B(pos->w, pos->z);
|
|
|
|
float m = (B.y - A.y) / (B.x - A.x);
|
|
float c = B.y - B.x * m;
|
|
|
|
if(m == 1.0f)
|
|
continue;
|
|
|
|
nearp = -c / m;
|
|
farp = c / (1 - m);
|
|
|
|
found = true;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
// if we didn't find anything, all z's and w's were identical.
|
|
// If the z is positive and w greater for the first element then
|
|
// we detect this projection as reversed z with infinite far plane
|
|
if(!found && pos0->z > 0.0f && pos0->w > pos0->z)
|
|
{
|
|
nearp = pos0->z;
|
|
farp = FLT_MAX;
|
|
}
|
|
|
|
gl.glUnmapNamedBufferEXT(DebugData.feedbackBuffer);
|
|
|
|
// store everything out to the PostVS data cache
|
|
m_PostVSData[eventId].vsin.topo = drawcall->topology;
|
|
m_PostVSData[eventId].vsout.buf = vsoutBuffer;
|
|
m_PostVSData[eventId].vsout.vertStride = stride;
|
|
m_PostVSData[eventId].vsout.nearPlane = nearp;
|
|
m_PostVSData[eventId].vsout.farPlane = farp;
|
|
|
|
m_PostVSData[eventId].vsout.useIndices = bool(drawcall->flags & DrawFlags::UseIBuffer);
|
|
m_PostVSData[eventId].vsout.numVerts = drawcall->numIndices;
|
|
|
|
m_PostVSData[eventId].vsout.instStride = 0;
|
|
if(drawcall->flags & DrawFlags::Instanced)
|
|
m_PostVSData[eventId].vsout.instStride =
|
|
(stride * primsWritten) / RDCMAX(1U, drawcall->numInstances);
|
|
|
|
m_PostVSData[eventId].vsout.idxBuf = 0;
|
|
m_PostVSData[eventId].vsout.idxByteWidth = drawcall->indexByteWidth;
|
|
if(m_PostVSData[eventId].vsout.useIndices && idxBuf)
|
|
{
|
|
m_PostVSData[eventId].vsout.idxBuf = idxBuf;
|
|
}
|
|
|
|
m_PostVSData[eventId].vsout.hasPosOut = posidx >= 0;
|
|
|
|
m_PostVSData[eventId].vsout.topo = drawcall->topology;
|
|
|
|
// set vsProg back to no varyings, for future use
|
|
gl.glTransformFeedbackVaryings(vsProg, 0, NULL, eGL_INTERLEAVED_ATTRIBS);
|
|
gl.glLinkProgram(vsProg);
|
|
|
|
GLuint lastFeedbackPipe = 0;
|
|
|
|
if(tesProg || gsProg)
|
|
{
|
|
GLuint lastProg = gsProg;
|
|
ShaderReflection *lastRefl = gsRefl;
|
|
|
|
if(lastProg == 0)
|
|
{
|
|
lastProg = tesProg;
|
|
lastRefl = tesRefl;
|
|
}
|
|
|
|
RDCASSERT(lastProg && lastRefl);
|
|
|
|
varyings.clear();
|
|
|
|
stride = 0;
|
|
posidx = -1;
|
|
|
|
for(const SigParameter &sig : lastRefl->outputSignature)
|
|
{
|
|
const char *name = sig.varName.c_str();
|
|
size_t len = sig.varName.size();
|
|
|
|
bool include = true;
|
|
|
|
// for matrices with names including :row1, :row2 etc we only include :row0
|
|
// as a varying (but increment the stride for all rows to account for the space)
|
|
// and modify the name to remove the :row0 part
|
|
const char *colon = strchr(name, ':');
|
|
if(colon)
|
|
{
|
|
if(name[len - 1] != '0')
|
|
{
|
|
include = false;
|
|
}
|
|
else
|
|
{
|
|
matrixVaryings.push_back(std::string(name, colon));
|
|
name = matrixVaryings.back().c_str();
|
|
}
|
|
}
|
|
|
|
if(include)
|
|
varyings.push_back(name);
|
|
|
|
if(sig.systemValue == ShaderBuiltin::Position)
|
|
posidx = int32_t(varyings.size()) - 1;
|
|
|
|
stride += sizeof(float) * sig.compCount;
|
|
}
|
|
|
|
// shift position attribute up to first, keeping order otherwise
|
|
// the same
|
|
if(posidx > 0)
|
|
{
|
|
const char *pos = varyings[posidx];
|
|
varyings.erase(varyings.begin() + posidx);
|
|
varyings.insert(varyings.begin(), pos);
|
|
}
|
|
|
|
// see above for the justification/explanation of this monstrosity.
|
|
|
|
status = 0;
|
|
finished = false;
|
|
for(;;)
|
|
{
|
|
// specify current varyings & relink
|
|
gl.glTransformFeedbackVaryings(lastProg, (GLsizei)varyings.size(), &varyings[0],
|
|
eGL_INTERLEAVED_ATTRIBS);
|
|
gl.glLinkProgram(lastProg);
|
|
|
|
gl.glGetProgramiv(lastProg, eGL_LINK_STATUS, &status);
|
|
|
|
// all good! Hopefully we'll mostly hit this
|
|
if(status == 1)
|
|
break;
|
|
|
|
// if finished is true, this was our last attempt - there are no
|
|
// more fixups possible
|
|
if(finished)
|
|
break;
|
|
|
|
char buffer[1025] = {0};
|
|
gl.glGetProgramInfoLog(lastProg, 1024, NULL, buffer);
|
|
|
|
// assume we're finished and can't retry any more after this.
|
|
// if we find a potential 'fixup' we'll set this back to false
|
|
finished = true;
|
|
|
|
// see if any of our current varyings are present in the buffer string
|
|
for(size_t i = 0; i < varyings.size(); i++)
|
|
{
|
|
if(strstr(buffer, varyings[i]))
|
|
{
|
|
const char *prefix_removed = strchr(varyings[i], '.');
|
|
|
|
// does it contain a prefix?
|
|
if(prefix_removed)
|
|
{
|
|
prefix_removed++; // now this is our string without the prefix
|
|
|
|
// first check this won't cause a duplicate - if it does, we have to try something else
|
|
bool duplicate = false;
|
|
for(size_t j = 0; j < varyings.size(); j++)
|
|
{
|
|
if(!strcmp(varyings[j], prefix_removed))
|
|
{
|
|
duplicate = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!duplicate)
|
|
{
|
|
// we'll attempt this fixup
|
|
RDCWARN("Attempting XFB varying fixup, subst '%s' for '%s'", varyings[i],
|
|
prefix_removed);
|
|
varyings[i] = prefix_removed;
|
|
finished = false;
|
|
|
|
// don't try more than one at once (just in case)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(status == 0)
|
|
{
|
|
char buffer[1025] = {0};
|
|
gl.glGetProgramInfoLog(lastProg, 1024, NULL, buffer);
|
|
RDCERR("Failed to fix-up. Link error making xfb last program: %s", buffer);
|
|
}
|
|
else
|
|
{
|
|
// make a pipeline to contain all the vertex processing shaders
|
|
gl.glGenProgramPipelines(1, &lastFeedbackPipe);
|
|
|
|
// bind the separable vertex program to it
|
|
gl.glUseProgramStages(lastFeedbackPipe, eGL_VERTEX_SHADER_BIT, vsProg);
|
|
|
|
// copy across any uniform values, bindings etc from the real program containing
|
|
// the vertex stage
|
|
CopyProgramUniforms(gl.GetHookset(), vsProgSrc, vsProg);
|
|
|
|
// if tessellation is enabled, bind & copy uniforms. Note, control shader is optional
|
|
// independent of eval shader (default values are used for the tessellation levels).
|
|
if(tcsProg)
|
|
{
|
|
gl.glUseProgramStages(lastFeedbackPipe, eGL_TESS_CONTROL_SHADER_BIT, tcsProg);
|
|
CopyProgramUniforms(gl.GetHookset(), tcsProgSrc, tcsProg);
|
|
}
|
|
if(tesProg)
|
|
{
|
|
gl.glUseProgramStages(lastFeedbackPipe, eGL_TESS_EVALUATION_SHADER_BIT, tesProg);
|
|
CopyProgramUniforms(gl.GetHookset(), tesProgSrc, tesProg);
|
|
}
|
|
|
|
// if we have a geometry shader, bind & copy uniforms
|
|
if(gsProg)
|
|
{
|
|
gl.glUseProgramStages(lastFeedbackPipe, eGL_GEOMETRY_SHADER_BIT, gsProg);
|
|
CopyProgramUniforms(gl.GetHookset(), gsProgSrc, gsProg);
|
|
}
|
|
|
|
// bind our program and do the feedback draw
|
|
gl.glUseProgram(0);
|
|
gl.glBindProgramPipeline(lastFeedbackPipe);
|
|
|
|
gl.glBindTransformFeedback(eGL_TRANSFORM_FEEDBACK, DebugData.feedbackObj);
|
|
|
|
// need to rebind this here because of an AMD bug that seems to ignore the buffer
|
|
// bindings in the feedback object - or at least it errors if the default feedback
|
|
// object has no buffers bound. Fortunately the state is still object-local so
|
|
// we don't have to restore the buffer binding on the default feedback object.
|
|
gl.glBindBufferBase(eGL_TRANSFORM_FEEDBACK_BUFFER, 0, DebugData.feedbackBuffer);
|
|
|
|
idxBuf = 0;
|
|
|
|
GLenum shaderOutMode = eGL_TRIANGLES;
|
|
GLenum lastOutTopo = eGL_TRIANGLES;
|
|
|
|
uint32_t maxOutputSize = stride;
|
|
|
|
if(drawcall->flags & DrawFlags::Instanced)
|
|
maxOutputSize *= drawcall->numInstances;
|
|
|
|
uint32_t numInputPrimitives = drawcall->numIndices;
|
|
GLenum drawtopo = MakeGLPrimitiveTopology(drawcall->topology);
|
|
|
|
switch(drawcall->topology)
|
|
{
|
|
case Topology::Unknown:
|
|
case Topology::PointList: break;
|
|
case Topology::LineList: numInputPrimitives /= 2; break;
|
|
case Topology::LineStrip: numInputPrimitives -= 1; break;
|
|
case Topology::LineLoop: break;
|
|
case Topology::TriangleList: numInputPrimitives /= 3; break;
|
|
case Topology::TriangleStrip:
|
|
case Topology::TriangleFan: numInputPrimitives -= 2; break;
|
|
case Topology::LineList_Adj: numInputPrimitives /= 4; break;
|
|
case Topology::LineStrip_Adj: numInputPrimitives -= 3; break;
|
|
case Topology::TriangleList_Adj: numInputPrimitives /= 6; break;
|
|
case Topology::TriangleStrip_Adj: numInputPrimitives -= 5; break;
|
|
case Topology::PatchList_1CPs:
|
|
case Topology::PatchList_2CPs:
|
|
case Topology::PatchList_3CPs:
|
|
case Topology::PatchList_4CPs:
|
|
case Topology::PatchList_5CPs:
|
|
case Topology::PatchList_6CPs:
|
|
case Topology::PatchList_7CPs:
|
|
case Topology::PatchList_8CPs:
|
|
case Topology::PatchList_9CPs:
|
|
case Topology::PatchList_10CPs:
|
|
case Topology::PatchList_11CPs:
|
|
case Topology::PatchList_12CPs:
|
|
case Topology::PatchList_13CPs:
|
|
case Topology::PatchList_14CPs:
|
|
case Topology::PatchList_15CPs:
|
|
case Topology::PatchList_16CPs:
|
|
case Topology::PatchList_17CPs:
|
|
case Topology::PatchList_18CPs:
|
|
case Topology::PatchList_19CPs:
|
|
case Topology::PatchList_20CPs:
|
|
case Topology::PatchList_21CPs:
|
|
case Topology::PatchList_22CPs:
|
|
case Topology::PatchList_23CPs:
|
|
case Topology::PatchList_24CPs:
|
|
case Topology::PatchList_25CPs:
|
|
case Topology::PatchList_26CPs:
|
|
case Topology::PatchList_27CPs:
|
|
case Topology::PatchList_28CPs:
|
|
case Topology::PatchList_29CPs:
|
|
case Topology::PatchList_30CPs:
|
|
case Topology::PatchList_31CPs:
|
|
case Topology::PatchList_32CPs:
|
|
numInputPrimitives /= PatchList_Count(drawcall->topology);
|
|
break;
|
|
}
|
|
|
|
if(lastProg == gsProg)
|
|
{
|
|
gl.glGetProgramiv(gsProg, eGL_GEOMETRY_OUTPUT_TYPE, (GLint *)&shaderOutMode);
|
|
|
|
GLint maxVerts = 1;
|
|
|
|
gl.glGetProgramiv(gsProg, eGL_GEOMETRY_VERTICES_OUT, (GLint *)&maxVerts);
|
|
|
|
if(shaderOutMode == eGL_TRIANGLE_STRIP)
|
|
{
|
|
lastOutTopo = eGL_TRIANGLES;
|
|
maxVerts = RDCMAX(3, maxVerts);
|
|
}
|
|
else if(shaderOutMode == eGL_LINE_STRIP)
|
|
{
|
|
lastOutTopo = eGL_LINES;
|
|
maxVerts = RDCMAX(2, maxVerts);
|
|
}
|
|
else if(shaderOutMode == eGL_POINTS)
|
|
{
|
|
lastOutTopo = eGL_POINTS;
|
|
maxVerts = RDCMAX(1, maxVerts);
|
|
}
|
|
|
|
maxOutputSize *= maxVerts * numInputPrimitives;
|
|
}
|
|
else if(lastProg == tesProg)
|
|
{
|
|
gl.glGetProgramiv(tesProg, eGL_TESS_GEN_MODE, (GLint *)&shaderOutMode);
|
|
|
|
uint32_t outputPrimitiveVerts = 1;
|
|
|
|
if(shaderOutMode == eGL_QUADS)
|
|
{
|
|
lastOutTopo = eGL_TRIANGLES;
|
|
outputPrimitiveVerts = 3;
|
|
}
|
|
else if(shaderOutMode == eGL_ISOLINES)
|
|
{
|
|
lastOutTopo = eGL_LINES;
|
|
outputPrimitiveVerts = 2;
|
|
}
|
|
else if(shaderOutMode == eGL_TRIANGLES)
|
|
{
|
|
lastOutTopo = eGL_TRIANGLES;
|
|
outputPrimitiveVerts = 3;
|
|
}
|
|
|
|
// assume an average maximum tessellation level of 32
|
|
maxOutputSize *= 32 * outputPrimitiveVerts * numInputPrimitives;
|
|
}
|
|
|
|
// resize up the buffer if needed for the vertex output data
|
|
if(DebugData.feedbackBufferSize < maxOutputSize)
|
|
{
|
|
uint32_t oldSize = DebugData.feedbackBufferSize;
|
|
while(DebugData.feedbackBufferSize < maxOutputSize)
|
|
DebugData.feedbackBufferSize *= 2;
|
|
RDCWARN("Conservatively resizing xfb buffer from %u to %u for output", oldSize,
|
|
DebugData.feedbackBufferSize);
|
|
gl.glNamedBufferDataEXT(DebugData.feedbackBuffer, DebugData.feedbackBufferSize, NULL,
|
|
eGL_DYNAMIC_READ);
|
|
}
|
|
|
|
GLenum idxType = eGL_UNSIGNED_BYTE;
|
|
if(drawcall->indexByteWidth == 2)
|
|
idxType = eGL_UNSIGNED_SHORT;
|
|
else if(drawcall->indexByteWidth == 4)
|
|
idxType = eGL_UNSIGNED_INT;
|
|
|
|
// instanced draws must be replayed one at a time so we can record the number of primitives
|
|
// from
|
|
// each drawcall, as due to expansion this can vary per-instance.
|
|
if(drawcall->flags & DrawFlags::Instanced)
|
|
{
|
|
// if there is only one instance it's a trivial case and we don't need to bother with the
|
|
// expensive path
|
|
if(drawcall->numInstances > 1)
|
|
{
|
|
// ensure we have enough queries
|
|
uint32_t curSize = (uint32_t)DebugData.feedbackQueries.size();
|
|
if(curSize < drawcall->numInstances)
|
|
{
|
|
DebugData.feedbackQueries.resize(drawcall->numInstances);
|
|
gl.glGenQueries(drawcall->numInstances - curSize,
|
|
DebugData.feedbackQueries.data() + curSize);
|
|
}
|
|
|
|
// do incremental draws to get the output size. We have to do this O(N^2) style because
|
|
// there's no way to replay only a single instance. We have to replay 1, 2, 3, ... N
|
|
// instances and count the total number of verts each time, then we can see from the
|
|
// difference how much each instance wrote.
|
|
for(uint32_t inst = 1; inst <= drawcall->numInstances; inst++)
|
|
{
|
|
gl.glBindBufferBase(eGL_TRANSFORM_FEEDBACK_BUFFER, 0, DebugData.feedbackBuffer);
|
|
gl.glBeginQuery(eGL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN,
|
|
DebugData.feedbackQueries[inst - 1]);
|
|
gl.glBeginTransformFeedback(lastOutTopo);
|
|
|
|
if(!(drawcall->flags & DrawFlags::UseIBuffer))
|
|
{
|
|
if(HasExt[ARB_base_instance])
|
|
{
|
|
gl.glDrawArraysInstancedBaseInstance(drawtopo, drawcall->vertexOffset,
|
|
drawcall->numIndices, inst,
|
|
drawcall->instanceOffset);
|
|
}
|
|
else
|
|
{
|
|
gl.glDrawArraysInstanced(drawtopo, drawcall->vertexOffset, drawcall->numIndices,
|
|
inst);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(HasExt[ARB_base_instance])
|
|
{
|
|
gl.glDrawElementsInstancedBaseVertexBaseInstance(
|
|
drawtopo, drawcall->numIndices, idxType,
|
|
(const void *)uintptr_t(drawcall->indexOffset * drawcall->indexByteWidth), inst,
|
|
drawcall->baseVertex, drawcall->instanceOffset);
|
|
}
|
|
else
|
|
{
|
|
gl.glDrawElementsInstancedBaseVertex(
|
|
drawtopo, drawcall->numIndices, idxType,
|
|
(const void *)uintptr_t(drawcall->indexOffset * drawcall->indexByteWidth), inst,
|
|
drawcall->baseVertex);
|
|
}
|
|
}
|
|
|
|
gl.glEndTransformFeedback();
|
|
gl.glEndQuery(eGL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gl.glBeginQuery(eGL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, DebugData.feedbackQueries[0]);
|
|
gl.glBeginTransformFeedback(lastOutTopo);
|
|
|
|
if(!(drawcall->flags & DrawFlags::UseIBuffer))
|
|
{
|
|
if(HasExt[ARB_base_instance])
|
|
{
|
|
gl.glDrawArraysInstancedBaseInstance(drawtopo, drawcall->vertexOffset,
|
|
drawcall->numIndices, drawcall->numInstances,
|
|
drawcall->instanceOffset);
|
|
}
|
|
else
|
|
{
|
|
gl.glDrawArraysInstanced(drawtopo, drawcall->vertexOffset, drawcall->numIndices,
|
|
drawcall->numInstances);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(HasExt[ARB_base_instance])
|
|
{
|
|
gl.glDrawElementsInstancedBaseVertexBaseInstance(
|
|
drawtopo, drawcall->numIndices, idxType,
|
|
(const void *)uintptr_t(drawcall->indexOffset * drawcall->indexByteWidth),
|
|
drawcall->numInstances, drawcall->baseVertex, drawcall->instanceOffset);
|
|
}
|
|
else
|
|
{
|
|
gl.glDrawElementsInstancedBaseVertex(
|
|
drawtopo, drawcall->numIndices, idxType,
|
|
(const void *)uintptr_t(drawcall->indexOffset * drawcall->indexByteWidth),
|
|
drawcall->numInstances, drawcall->baseVertex);
|
|
}
|
|
}
|
|
|
|
gl.glEndTransformFeedback();
|
|
gl.glEndQuery(eGL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gl.glBeginQuery(eGL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, DebugData.feedbackQueries[0]);
|
|
gl.glBeginTransformFeedback(lastOutTopo);
|
|
|
|
if(!(drawcall->flags & DrawFlags::UseIBuffer))
|
|
{
|
|
gl.glDrawArrays(drawtopo, drawcall->vertexOffset, drawcall->numIndices);
|
|
}
|
|
else
|
|
{
|
|
gl.glDrawElementsBaseVertex(
|
|
drawtopo, drawcall->numIndices, idxType,
|
|
(const void *)uintptr_t(drawcall->indexOffset * drawcall->indexByteWidth),
|
|
drawcall->baseVertex);
|
|
}
|
|
|
|
gl.glEndTransformFeedback();
|
|
gl.glEndQuery(eGL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
|
|
}
|
|
|
|
std::vector<GLPostVSData::InstData> instData;
|
|
|
|
if((drawcall->flags & DrawFlags::Instanced) && drawcall->numInstances > 1)
|
|
{
|
|
uint64_t prevVertCount = 0;
|
|
|
|
for(uint32_t inst = 0; inst < drawcall->numInstances; inst++)
|
|
{
|
|
gl.glGetQueryObjectuiv(DebugData.feedbackQueries[inst], eGL_QUERY_RESULT, &primsWritten);
|
|
|
|
uint32_t vertCount = 3 * primsWritten;
|
|
|
|
GLPostVSData::InstData d;
|
|
d.numVerts = uint32_t(vertCount - prevVertCount);
|
|
d.bufOffset = uint32_t(stride * prevVertCount);
|
|
prevVertCount = vertCount;
|
|
|
|
instData.push_back(d);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
primsWritten = 0;
|
|
gl.glGetQueryObjectuiv(DebugData.feedbackQueries[0], eGL_QUERY_RESULT, &primsWritten);
|
|
}
|
|
|
|
error = false;
|
|
|
|
if(primsWritten == 0)
|
|
{
|
|
RDCWARN("No primitives written by last vertex processing stage");
|
|
error = true;
|
|
}
|
|
|
|
// get buffer data from buffer attached to feedback object
|
|
data = (float *)gl.glMapNamedBufferEXT(DebugData.feedbackBuffer, eGL_READ_ONLY);
|
|
|
|
if(data == NULL)
|
|
{
|
|
gl.glUnmapNamedBufferEXT(DebugData.feedbackBuffer);
|
|
RDCERR("Couldn't map feedback buffer!");
|
|
error = true;
|
|
}
|
|
|
|
if(error)
|
|
{
|
|
// delete temporary pipelines we made
|
|
gl.glDeleteProgramPipelines(1, &vsFeedbackPipe);
|
|
if(lastFeedbackPipe)
|
|
gl.glDeleteProgramPipelines(1, &lastFeedbackPipe);
|
|
|
|
// restore replay state we trashed
|
|
gl.glUseProgram(rs.Program.name);
|
|
gl.glBindProgramPipeline(rs.Pipeline.name);
|
|
|
|
gl.glBindBuffer(eGL_ARRAY_BUFFER, rs.BufferBindings[GLRenderState::eBufIdx_Array].name);
|
|
gl.glBindBuffer(eGL_ELEMENT_ARRAY_BUFFER, elArrayBuffer);
|
|
|
|
gl.glBindTransformFeedback(eGL_TRANSFORM_FEEDBACK, rs.FeedbackObj.name);
|
|
|
|
if(!rs.Enabled[GLRenderState::eEnabled_RasterizerDiscard])
|
|
gl.glDisable(eGL_RASTERIZER_DISCARD);
|
|
else
|
|
gl.glEnable(eGL_RASTERIZER_DISCARD);
|
|
|
|
return;
|
|
}
|
|
|
|
if(lastProg == tesProg)
|
|
{
|
|
// primitive counter is the number of primitives, not vertices
|
|
if(shaderOutMode == eGL_TRIANGLES ||
|
|
shaderOutMode == eGL_QUADS) // query for quads returns # triangles
|
|
m_PostVSData[eventId].gsout.numVerts = primsWritten * 3;
|
|
else if(shaderOutMode == eGL_ISOLINES)
|
|
m_PostVSData[eventId].gsout.numVerts = primsWritten * 2;
|
|
}
|
|
else if(lastProg == gsProg)
|
|
{
|
|
// primitive counter is the number of primitives, not vertices
|
|
if(shaderOutMode == eGL_POINTS)
|
|
m_PostVSData[eventId].gsout.numVerts = primsWritten;
|
|
else if(shaderOutMode == eGL_LINE_STRIP)
|
|
m_PostVSData[eventId].gsout.numVerts = primsWritten * 2;
|
|
else if(shaderOutMode == eGL_TRIANGLE_STRIP)
|
|
m_PostVSData[eventId].gsout.numVerts = primsWritten * 3;
|
|
}
|
|
|
|
// create a buffer with this data, for future use (typed to ARRAY_BUFFER so we
|
|
// can render from it to display previews).
|
|
GLuint lastoutBuffer = 0;
|
|
gl.glGenBuffers(1, &lastoutBuffer);
|
|
gl.glBindBuffer(eGL_ARRAY_BUFFER, lastoutBuffer);
|
|
gl.glNamedBufferDataEXT(lastoutBuffer, stride * m_PostVSData[eventId].gsout.numVerts, data,
|
|
eGL_STATIC_DRAW);
|
|
|
|
byteData = (byte *)data;
|
|
|
|
nearp = 0.1f;
|
|
farp = 100.0f;
|
|
|
|
pos0 = (Vec4f *)byteData;
|
|
|
|
found = false;
|
|
|
|
for(uint32_t i = 1; posidx != -1 && i < m_PostVSData[eventId].gsout.numVerts; i++)
|
|
{
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
// derive near/far, assuming a standard perspective matrix
|
|
//
|
|
// the transformation from from pre-projection {Z,W} to post-projection {Z,W}
|
|
// is linear. So we can say Zpost = Zpre*m + c . Here we assume Wpre = 1
|
|
// and we know Wpost = Zpre from the perspective matrix.
|
|
// we can then see from the perspective matrix that
|
|
// m = F/(F-N)
|
|
// c = -(F*N)/(F-N)
|
|
//
|
|
// with re-arranging and substitution, we then get:
|
|
// N = -c/m
|
|
// F = c/(1-m)
|
|
//
|
|
// so if we can derive m and c then we can determine N and F. We can do this with
|
|
// two points, and we pick them reasonably distinct on z to reduce floating-point
|
|
// error
|
|
|
|
Vec4f *pos = (Vec4f *)(byteData + i * stride);
|
|
|
|
if(fabs(pos->w - pos0->w) > 0.01f && fabs(pos->z - pos0->z) > 0.01f)
|
|
{
|
|
Vec2f A(pos0->w, pos0->z);
|
|
Vec2f B(pos->w, pos->z);
|
|
|
|
float m = (B.y - A.y) / (B.x - A.x);
|
|
float c = B.y - B.x * m;
|
|
|
|
if(m == 1.0f)
|
|
continue;
|
|
|
|
nearp = -c / m;
|
|
farp = c / (1 - m);
|
|
|
|
found = true;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
// if we didn't find anything, all z's and w's were identical.
|
|
// If the z is positive and w greater for the first element then
|
|
// we detect this projection as reversed z with infinite far plane
|
|
if(!found && pos0->z > 0.0f && pos0->w > pos0->z)
|
|
{
|
|
nearp = pos0->z;
|
|
farp = FLT_MAX;
|
|
}
|
|
|
|
gl.glUnmapNamedBufferEXT(DebugData.feedbackBuffer);
|
|
|
|
// store everything out to the PostVS data cache
|
|
m_PostVSData[eventId].gsout.buf = lastoutBuffer;
|
|
m_PostVSData[eventId].gsout.instStride = 0;
|
|
if(drawcall->flags & DrawFlags::Instanced)
|
|
{
|
|
m_PostVSData[eventId].gsout.numVerts /= RDCMAX(1U, drawcall->numInstances);
|
|
m_PostVSData[eventId].gsout.instStride = stride * m_PostVSData[eventId].gsout.numVerts;
|
|
}
|
|
m_PostVSData[eventId].gsout.vertStride = stride;
|
|
m_PostVSData[eventId].gsout.nearPlane = nearp;
|
|
m_PostVSData[eventId].gsout.farPlane = farp;
|
|
|
|
m_PostVSData[eventId].gsout.useIndices = false;
|
|
|
|
m_PostVSData[eventId].gsout.hasPosOut = posidx >= 0;
|
|
|
|
m_PostVSData[eventId].gsout.idxBuf = 0;
|
|
m_PostVSData[eventId].gsout.idxByteWidth = 0;
|
|
|
|
m_PostVSData[eventId].gsout.topo = MakePrimitiveTopology(gl.GetHookset(), lastOutTopo);
|
|
|
|
m_PostVSData[eventId].gsout.instData = instData;
|
|
}
|
|
|
|
// set lastProg back to no varyings, for future use
|
|
gl.glTransformFeedbackVaryings(lastProg, 0, NULL, eGL_INTERLEAVED_ATTRIBS);
|
|
gl.glLinkProgram(lastProg);
|
|
}
|
|
|
|
// delete temporary pipelines we made
|
|
gl.glDeleteProgramPipelines(1, &vsFeedbackPipe);
|
|
if(lastFeedbackPipe)
|
|
gl.glDeleteProgramPipelines(1, &lastFeedbackPipe);
|
|
|
|
// restore replay state we trashed
|
|
gl.glUseProgram(rs.Program.name);
|
|
gl.glBindProgramPipeline(rs.Pipeline.name);
|
|
|
|
gl.glBindBuffer(eGL_ARRAY_BUFFER, rs.BufferBindings[GLRenderState::eBufIdx_Array].name);
|
|
gl.glBindBuffer(eGL_ELEMENT_ARRAY_BUFFER, elArrayBuffer);
|
|
|
|
gl.glBindTransformFeedback(eGL_TRANSFORM_FEEDBACK, rs.FeedbackObj.name);
|
|
|
|
if(!rs.Enabled[GLRenderState::eEnabled_RasterizerDiscard])
|
|
gl.glDisable(eGL_RASTERIZER_DISCARD);
|
|
else
|
|
gl.glEnable(eGL_RASTERIZER_DISCARD);
|
|
}
|
|
|
|
void GLReplay::InitPostVSBuffers(const vector<uint32_t> &passEvents)
|
|
{
|
|
uint32_t prev = 0;
|
|
|
|
// since we can always replay between drawcalls, just loop through all the events
|
|
// doing partial replays and calling InitPostVSBuffers for each
|
|
for(size_t i = 0; i < passEvents.size(); i++)
|
|
{
|
|
if(prev != passEvents[i])
|
|
{
|
|
m_pDriver->ReplayLog(prev, passEvents[i], eReplay_WithoutDraw);
|
|
|
|
prev = passEvents[i];
|
|
}
|
|
|
|
const DrawcallDescription *d = m_pDriver->GetDrawcall(passEvents[i]);
|
|
|
|
if(d)
|
|
InitPostVSBuffers(passEvents[i]);
|
|
}
|
|
}
|
|
|
|
MeshFormat GLReplay::GetPostVSBuffers(uint32_t eventId, uint32_t instID, MeshDataStage stage)
|
|
{
|
|
GLPostVSData postvs;
|
|
RDCEraseEl(postvs);
|
|
|
|
if(m_PostVSData.find(eventId) != m_PostVSData.end())
|
|
postvs = m_PostVSData[eventId];
|
|
|
|
const GLPostVSData::StageData &s = postvs.GetStage(stage);
|
|
|
|
MeshFormat ret;
|
|
|
|
if(s.useIndices && s.idxBuf)
|
|
ret.indexResourceId = m_pDriver->GetResourceManager()->GetID(BufferRes(NULL, s.idxBuf));
|
|
else
|
|
ret.indexResourceId = ResourceId();
|
|
ret.indexByteOffset = 0;
|
|
ret.indexByteStride = s.idxByteWidth;
|
|
ret.baseVertex = 0;
|
|
|
|
if(s.buf)
|
|
ret.vertexResourceId = m_pDriver->GetResourceManager()->GetID(BufferRes(NULL, s.buf));
|
|
else
|
|
ret.vertexResourceId = ResourceId();
|
|
|
|
ret.vertexByteOffset = s.instStride * instID;
|
|
ret.vertexByteStride = s.vertStride;
|
|
|
|
ret.format.compCount = 4;
|
|
ret.format.compByteWidth = 4;
|
|
ret.format.compType = CompType::Float;
|
|
ret.format.type = ResourceFormatType::Regular;
|
|
ret.format.bgraOrder = false;
|
|
|
|
ret.showAlpha = false;
|
|
|
|
ret.topology = s.topo;
|
|
ret.numIndices = s.numVerts;
|
|
|
|
ret.unproject = s.hasPosOut;
|
|
ret.nearPlane = s.nearPlane;
|
|
ret.farPlane = s.farPlane;
|
|
|
|
if(instID < s.instData.size())
|
|
{
|
|
GLPostVSData::InstData inst = s.instData[instID];
|
|
|
|
ret.vertexByteOffset = inst.bufOffset;
|
|
ret.numIndices = inst.numVerts;
|
|
}
|
|
|
|
return ret;
|
|
}
|