diff --git a/renderdoc/driver/d3d11/d3d11_debug.cpp b/renderdoc/driver/d3d11/d3d11_debug.cpp index a8b81eca6..6c3fb55ff 100644 --- a/renderdoc/driver/d3d11/d3d11_debug.cpp +++ b/renderdoc/driver/d3d11/d3d11_debug.cpp @@ -3806,19 +3806,6 @@ void D3D11DebugManager::RenderCheckerboard() } } -void D3D11DebugManager::ClearPostVSCache() -{ - for(auto it = m_PostVSData.begin(); it != m_PostVSData.end(); ++it) - { - SAFE_RELEASE(it->second.vsout.buf); - SAFE_RELEASE(it->second.vsout.idxBuf); - SAFE_RELEASE(it->second.gsout.buf); - SAFE_RELEASE(it->second.gsout.idxBuf); - } - - m_PostVSData.clear(); -} - void D3D11DebugManager::RenderForPredicate() { // just somehow draw a quad that renders some pixels to fill the predicate with TRUE @@ -3831,960 +3818,6 @@ void D3D11DebugManager::RenderForPredicate() m_WrappedContext->Draw(3, 0); } -MeshFormat D3D11DebugManager::GetPostVSBuffers(uint32_t eventId, uint32_t instID, MeshDataStage stage) -{ - D3D11PostVSData postvs; - RDCEraseEl(postvs); - - if(m_PostVSData.find(eventId) != m_PostVSData.end()) - postvs = m_PostVSData[eventId]; - - const D3D11PostVSData::StageData &s = postvs.GetStage(stage); - - MeshFormat ret; - - if(s.useIndices && s.idxBuf) - ret.indexResourceId = ((WrappedID3D11Buffer *)s.idxBuf)->GetResourceID(); - else - ret.indexResourceId = ResourceId(); - ret.indexByteOffset = 0; - ret.indexByteStride = s.idxFmt == DXGI_FORMAT_R16_UINT ? 2 : 4; - ret.baseVertex = 0; - - if(s.buf) - ret.vertexResourceId = ((WrappedID3D11Buffer *)s.buf)->GetResourceID(); - 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 = MakePrimitiveTopology(s.topo); - ret.numIndices = s.numVerts; - - ret.unproject = s.hasPosOut; - ret.nearPlane = s.nearPlane; - ret.farPlane = s.farPlane; - - if(instID < s.instData.size()) - { - D3D11PostVSData::InstData inst = s.instData[instID]; - - ret.vertexByteOffset = inst.bufOffset; - ret.numIndices = inst.numVerts; - } - - return ret; -} - -void D3D11DebugManager::InitPostVSBuffers(uint32_t eventId) -{ - if(m_PostVSData.find(eventId) != m_PostVSData.end()) - return; - - D3D11RenderStateTracker tracker(m_WrappedContext); - - ID3D11VertexShader *vs = NULL; - m_pImmediateContext->VSGetShader(&vs, NULL, NULL); - - ID3D11GeometryShader *gs = NULL; - m_pImmediateContext->GSGetShader(&gs, NULL, NULL); - - ID3D11HullShader *hs = NULL; - m_pImmediateContext->HSGetShader(&hs, NULL, NULL); - - ID3D11DomainShader *ds = NULL; - m_pImmediateContext->DSGetShader(&ds, NULL, NULL); - - if(vs) - vs->Release(); - if(gs) - gs->Release(); - if(hs) - hs->Release(); - if(ds) - ds->Release(); - - if(!vs) - return; - - D3D11_PRIMITIVE_TOPOLOGY topo; - m_pImmediateContext->IAGetPrimitiveTopology(&topo); - - WrappedID3D11Shader *wrappedVS = (WrappedID3D11Shader *)vs; - - if(!wrappedVS) - { - RDCERR("Couldn't find wrapped vertex shader!"); - return; - } - - const DrawcallDescription *drawcall = m_WrappedDevice->GetDrawcall(eventId); - - if(drawcall->numIndices == 0) - return; - - DXBC::DXBCFile *dxbcVS = wrappedVS->GetDXBC(); - - RDCASSERT(dxbcVS); - - DXBC::DXBCFile *dxbcGS = NULL; - - if(gs) - { - WrappedID3D11Shader *wrappedGS = - (WrappedID3D11Shader *)gs; - - if(!wrappedGS) - { - RDCERR("Couldn't find wrapped geometry shader!"); - return; - } - - dxbcGS = wrappedGS->GetDXBC(); - - RDCASSERT(dxbcGS); - } - - DXBC::DXBCFile *dxbcDS = NULL; - - if(ds) - { - WrappedID3D11Shader *wrappedDS = - (WrappedID3D11Shader *)ds; - - if(!wrappedDS) - { - RDCERR("Couldn't find wrapped domain shader!"); - return; - } - - dxbcDS = wrappedDS->GetDXBC(); - - RDCASSERT(dxbcDS); - } - - vector sodecls; - - UINT stride = 0; - int posidx = -1; - int numPosComponents = 0; - - ID3D11GeometryShader *streamoutGS = NULL; - - if(!dxbcVS->m_OutputSig.empty()) - { - for(size_t i = 0; i < dxbcVS->m_OutputSig.size(); i++) - { - SigParameter &sign = dxbcVS->m_OutputSig[i]; - - D3D11_SO_DECLARATION_ENTRY decl; - - decl.Stream = 0; - decl.OutputSlot = 0; - - decl.SemanticName = sign.semanticName.c_str(); - decl.SemanticIndex = sign.semanticIndex; - decl.StartComponent = 0; - decl.ComponentCount = sign.compCount & 0xff; - - if(sign.systemValue == ShaderBuiltin::Position) - { - posidx = (int)sodecls.size(); - numPosComponents = decl.ComponentCount = 4; - } - - stride += decl.ComponentCount * sizeof(float); - sodecls.push_back(decl); - } - - // shift position attribute up to first, keeping order otherwise - // the same - if(posidx > 0) - { - D3D11_SO_DECLARATION_ENTRY pos = sodecls[posidx]; - sodecls.erase(sodecls.begin() + posidx); - sodecls.insert(sodecls.begin(), pos); - } - - HRESULT hr = m_pDevice->CreateGeometryShaderWithStreamOutput( - (void *)&dxbcVS->m_ShaderBlob[0], dxbcVS->m_ShaderBlob.size(), &sodecls[0], - (UINT)sodecls.size(), &stride, 1, D3D11_SO_NO_RASTERIZED_STREAM, NULL, &streamoutGS); - - if(FAILED(hr)) - { - RDCERR("Failed to create Geometry Shader + SO HRESULT: %s", ToStr(hr).c_str()); - return; - } - - m_pImmediateContext->GSSetShader(streamoutGS, NULL, 0); - m_pImmediateContext->HSSetShader(NULL, NULL, 0); - m_pImmediateContext->DSSetShader(NULL, NULL, 0); - - SAFE_RELEASE(streamoutGS); - - UINT offset = 0; - ID3D11Buffer *idxBuf = NULL; - DXGI_FORMAT idxFmt = DXGI_FORMAT_UNKNOWN; - UINT idxOffs = 0; - - m_pImmediateContext->IAGetIndexBuffer(&idxBuf, &idxFmt, &idxOffs); - - ID3D11Buffer *origBuf = idxBuf; - - if(!(drawcall->flags & DrawFlags::UseIBuffer)) - { - m_pImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_POINTLIST); - - SAFE_RELEASE(idxBuf); - - uint32_t outputSize = stride * drawcall->numIndices; - if(drawcall->flags & DrawFlags::Instanced) - outputSize *= drawcall->numInstances; - - if(m_SOBufferSize < outputSize) - { - int oldSize = m_SOBufferSize; - while(m_SOBufferSize < outputSize) - m_SOBufferSize *= 2; - RDCWARN("Resizing stream-out buffer from %d to %d", oldSize, m_SOBufferSize); - CreateSOBuffers(); - } - - m_pImmediateContext->SOSetTargets(1, &m_SOBuffer, &offset); - - m_pImmediateContext->Begin(m_SOStatsQueries[0]); - - if(drawcall->flags & DrawFlags::Instanced) - m_pImmediateContext->DrawInstanced(drawcall->numIndices, drawcall->numInstances, - drawcall->vertexOffset, drawcall->instanceOffset); - else - m_pImmediateContext->Draw(drawcall->numIndices, drawcall->vertexOffset); - - m_pImmediateContext->End(m_SOStatsQueries[0]); - } - else // drawcall is indexed - { - bool index16 = (idxFmt == DXGI_FORMAT_R16_UINT); - UINT bytesize = index16 ? 2 : 4; - - bytebuf idxdata; - GetBufferData(idxBuf, idxOffs + drawcall->indexOffset * bytesize, - drawcall->numIndices * bytesize, idxdata); - - SAFE_RELEASE(idxBuf); - - vector indices; - - 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(index16 ? idxdata.size() / 2 : idxdata.size() / 4), drawcall->numIndices); - - uint32_t idxclamp = 0; - if(drawcall->baseVertex < 0) - idxclamp = uint32_t(-drawcall->baseVertex); - - // grab all unique vertex indices referenced - for(uint32_t i = 0; i < numIndices; i++) - { - uint32_t i32 = index16 ? uint32_t(idx16[i]) : idx32[i]; - - // apply baseVertex but clamp to 0 (don't allow index to become negative) - if(i32 < idxclamp) - i32 = 0; - else if(drawcall->baseVertex < 0) - i32 -= idxclamp; - else if(drawcall->baseVertex > 0) - i32 += drawcall->baseVertex; - - 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 indexRemap; - for(size_t i = 0; i < indices.size(); i++) - { - // by definition, this index will only appear once in indices[] - indexRemap[indices[i]] = i; - } - - D3D11_BUFFER_DESC desc = {UINT(sizeof(uint32_t) * indices.size()), - D3D11_USAGE_IMMUTABLE, - D3D11_BIND_INDEX_BUFFER, - 0, - 0, - 0}; - D3D11_SUBRESOURCE_DATA initData = {&indices[0], desc.ByteWidth, desc.ByteWidth}; - - if(!indices.empty()) - m_pDevice->CreateBuffer(&desc, &initData, &idxBuf); - else - idxBuf = NULL; - - m_pImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_POINTLIST); - m_pImmediateContext->IASetIndexBuffer(idxBuf, DXGI_FORMAT_R32_UINT, 0); - SAFE_RELEASE(idxBuf); - - uint32_t outputSize = stride * (uint32_t)indices.size(); - if(drawcall->flags & DrawFlags::Instanced) - outputSize *= drawcall->numInstances; - - if(m_SOBufferSize < outputSize) - { - int oldSize = m_SOBufferSize; - while(m_SOBufferSize < outputSize) - m_SOBufferSize *= 2; - RDCWARN("Resizing stream-out buffer from %d to %d", oldSize, m_SOBufferSize); - CreateSOBuffers(); - } - - m_pImmediateContext->SOSetTargets(1, &m_SOBuffer, &offset); - - m_pImmediateContext->Begin(m_SOStatsQueries[0]); - - if(drawcall->flags & DrawFlags::Instanced) - m_pImmediateContext->DrawIndexedInstanced((UINT)indices.size(), drawcall->numInstances, 0, - 0, drawcall->instanceOffset); - else - m_pImmediateContext->DrawIndexed((UINT)indices.size(), 0, 0); - - m_pImmediateContext->End(m_SOStatsQueries[0]); - - // rebase existing index buffer to point to the right elements in our stream-out'd - // vertex buffer - for(uint32_t i = 0; i < numIndices; i++) - { - uint32_t i32 = index16 ? uint32_t(idx16[i]) : idx32[i]; - - // preserve primitive restart indices - if(i32 == (index16 ? 0xffff : 0xffffffff)) - continue; - - // apply baseVertex but clamp to 0 (don't allow index to become negative) - if(i32 < idxclamp) - i32 = 0; - else if(drawcall->baseVertex < 0) - i32 -= idxclamp; - else if(drawcall->baseVertex > 0) - i32 += drawcall->baseVertex; - - if(index16) - idx16[i] = uint16_t(indexRemap[i32]); - else - idx32[i] = uint32_t(indexRemap[i32]); - } - - desc.ByteWidth = (UINT)idxdata.size(); - initData.pSysMem = &idxdata[0]; - initData.SysMemPitch = initData.SysMemSlicePitch = desc.ByteWidth; - - if(desc.ByteWidth > 0) - m_pDevice->CreateBuffer(&desc, &initData, &idxBuf); - else - idxBuf = NULL; - } - - m_pImmediateContext->IASetPrimitiveTopology(topo); - m_pImmediateContext->IASetIndexBuffer(origBuf, idxFmt, idxOffs); - - m_pImmediateContext->GSSetShader(NULL, NULL, 0); - m_pImmediateContext->SOSetTargets(0, NULL, NULL); - - D3D11_QUERY_DATA_SO_STATISTICS numPrims; - - m_pImmediateContext->CopyResource(m_SOStagingBuffer, m_SOBuffer); - - do - { - hr = m_pImmediateContext->GetData(m_SOStatsQueries[0], &numPrims, - sizeof(D3D11_QUERY_DATA_SO_STATISTICS), 0); - } while(hr == S_FALSE); - - if(numPrims.NumPrimitivesWritten == 0) - { - m_PostVSData[eventId] = D3D11PostVSData(); - SAFE_RELEASE(idxBuf); - return; - } - - D3D11_MAPPED_SUBRESOURCE mapped; - hr = m_pImmediateContext->Map(m_SOStagingBuffer, 0, D3D11_MAP_READ, 0, &mapped); - - if(FAILED(hr)) - { - RDCERR("Failed to map sobuffer HRESULT: %s", ToStr(hr).c_str()); - SAFE_RELEASE(idxBuf); - return; - } - - D3D11_BUFFER_DESC bufferDesc = {stride * (uint32_t)numPrims.NumPrimitivesWritten, - D3D11_USAGE_IMMUTABLE, - D3D11_BIND_VERTEX_BUFFER, - 0, - 0, - 0}; - - ID3D11Buffer *vsoutBuffer = NULL; - - // we need to map this data into memory for read anyway, might as well make this VB - // immutable while we're at it. - D3D11_SUBRESOURCE_DATA initialData; - initialData.pSysMem = mapped.pData; - initialData.SysMemPitch = bufferDesc.ByteWidth; - initialData.SysMemSlicePitch = bufferDesc.ByteWidth; - - hr = m_pDevice->CreateBuffer(&bufferDesc, &initialData, &vsoutBuffer); - - if(FAILED(hr)) - { - RDCERR("Failed to create postvs pos buffer HRESULT: %s", ToStr(hr).c_str()); - - m_pImmediateContext->Unmap(m_SOStagingBuffer, 0); - SAFE_RELEASE(idxBuf); - return; - } - - byte *byteData = (byte *)mapped.pData; - - float nearp = 0.1f; - float farp = 100.0f; - - Vec4f *pos0 = (Vec4f *)byteData; - - bool found = false; - - for(UINT64 i = 1; numPosComponents == 4 && i < numPrims.NumPrimitivesWritten; 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; - } - - m_pImmediateContext->Unmap(m_SOStagingBuffer, 0); - - m_PostVSData[eventId].vsin.topo = topo; - 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 = - bufferDesc.ByteWidth / RDCMAX(1U, drawcall->numInstances); - - m_PostVSData[eventId].vsout.idxBuf = NULL; - if(m_PostVSData[eventId].vsout.useIndices && idxBuf) - { - m_PostVSData[eventId].vsout.idxBuf = idxBuf; - m_PostVSData[eventId].vsout.idxFmt = idxFmt; - } - - m_PostVSData[eventId].vsout.hasPosOut = posidx >= 0; - - m_PostVSData[eventId].vsout.topo = topo; - } - else - { - // empty vertex output signature - m_PostVSData[eventId].vsin.topo = topo; - m_PostVSData[eventId].vsout.buf = NULL; - m_PostVSData[eventId].vsout.instStride = 0; - m_PostVSData[eventId].vsout.vertStride = 0; - m_PostVSData[eventId].vsout.nearPlane = 0.0f; - m_PostVSData[eventId].vsout.farPlane = 0.0f; - m_PostVSData[eventId].vsout.useIndices = false; - m_PostVSData[eventId].vsout.hasPosOut = false; - m_PostVSData[eventId].vsout.idxBuf = NULL; - - m_PostVSData[eventId].vsout.topo = topo; - } - - if(dxbcGS || dxbcDS) - { - stride = 0; - posidx = -1; - numPosComponents = 0; - - DXBC::DXBCFile *lastShader = dxbcGS; - if(dxbcDS) - lastShader = dxbcDS; - - sodecls.clear(); - for(size_t i = 0; i < lastShader->m_OutputSig.size(); i++) - { - SigParameter &sign = lastShader->m_OutputSig[i]; - - D3D11_SO_DECLARATION_ENTRY decl; - - // for now, skip streams that aren't stream 0 - if(sign.stream != 0) - continue; - - decl.Stream = 0; - decl.OutputSlot = 0; - - decl.SemanticName = sign.semanticName.c_str(); - decl.SemanticIndex = sign.semanticIndex; - decl.StartComponent = 0; - decl.ComponentCount = sign.compCount & 0xff; - - if(sign.systemValue == ShaderBuiltin::Position) - { - posidx = (int)sodecls.size(); - numPosComponents = decl.ComponentCount = 4; - } - - stride += decl.ComponentCount * sizeof(float); - sodecls.push_back(decl); - } - - // shift position attribute up to first, keeping order otherwise - // the same - if(posidx > 0) - { - D3D11_SO_DECLARATION_ENTRY pos = sodecls[posidx]; - sodecls.erase(sodecls.begin() + posidx); - sodecls.insert(sodecls.begin(), pos); - } - - streamoutGS = NULL; - - HRESULT hr = m_pDevice->CreateGeometryShaderWithStreamOutput( - (void *)&lastShader->m_ShaderBlob[0], lastShader->m_ShaderBlob.size(), &sodecls[0], - (UINT)sodecls.size(), &stride, 1, D3D11_SO_NO_RASTERIZED_STREAM, NULL, &streamoutGS); - - if(FAILED(hr)) - { - RDCERR("Failed to create Geometry Shader + SO HRESULT: %s", ToStr(hr).c_str()); - return; - } - - m_pImmediateContext->GSSetShader(streamoutGS, NULL, 0); - m_pImmediateContext->HSSetShader(hs, NULL, 0); - m_pImmediateContext->DSSetShader(ds, NULL, 0); - - SAFE_RELEASE(streamoutGS); - - UINT offset = 0; - - D3D11_QUERY_DATA_SO_STATISTICS numPrims = {0}; - - // do the whole draw, and if our output buffer isn't large enough then loop around. - while(true) - { - m_pImmediateContext->Begin(m_SOStatsQueries[0]); - - m_pImmediateContext->SOSetTargets(1, &m_SOBuffer, &offset); - - if(drawcall->flags & DrawFlags::Instanced) - { - if(drawcall->flags & DrawFlags::UseIBuffer) - { - m_pImmediateContext->DrawIndexedInstanced(drawcall->numIndices, drawcall->numInstances, - drawcall->indexOffset, drawcall->baseVertex, - drawcall->instanceOffset); - } - else - { - m_pImmediateContext->DrawInstanced(drawcall->numIndices, drawcall->numInstances, - drawcall->vertexOffset, drawcall->instanceOffset); - } - } - else - { - // trying to stream out a stream-out-auto based drawcall would be bad! - // instead just draw the number of verts we pre-calculated - if(drawcall->flags & DrawFlags::Auto) - { - m_pImmediateContext->Draw(drawcall->numIndices, 0); - } - else - { - if(drawcall->flags & DrawFlags::UseIBuffer) - { - m_pImmediateContext->DrawIndexed(drawcall->numIndices, drawcall->indexOffset, - drawcall->baseVertex); - } - else - { - m_pImmediateContext->Draw(drawcall->numIndices, drawcall->vertexOffset); - } - } - } - - m_pImmediateContext->End(m_SOStatsQueries[0]); - - do - { - hr = m_pImmediateContext->GetData(m_SOStatsQueries[0], &numPrims, - sizeof(D3D11_QUERY_DATA_SO_STATISTICS), 0); - } while(hr == S_FALSE); - - if(m_SOBufferSize < stride * (uint32_t)numPrims.PrimitivesStorageNeeded * 3) - { - int oldSize = m_SOBufferSize; - while(m_SOBufferSize < stride * (uint32_t)numPrims.PrimitivesStorageNeeded * 3) - m_SOBufferSize *= 2; - RDCWARN("Resizing stream-out buffer from %d to %d", oldSize, m_SOBufferSize); - CreateSOBuffers(); - continue; - } - - break; - } - - // 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 && drawcall->numInstances > 1) - { - // ensure we have enough queries - while(m_SOStatsQueries.size() < drawcall->numInstances) - { - D3D11_QUERY_DESC qdesc; - qdesc.MiscFlags = 0; - qdesc.Query = D3D11_QUERY_SO_STATISTICS; - - ID3D11Query *q = NULL; - hr = m_pDevice->CreateQuery(&qdesc, &q); - if(FAILED(hr)) - RDCERR("Failed to create m_SOStatsQuery HRESULT: %s", ToStr(hr).c_str()); - - m_SOStatsQueries.push_back(q); - } - - // 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++) - { - if(drawcall->flags & DrawFlags::UseIBuffer) - { - m_pImmediateContext->SOSetTargets(1, &m_SOBuffer, &offset); - m_pImmediateContext->Begin(m_SOStatsQueries[inst - 1]); - m_pImmediateContext->DrawIndexedInstanced(drawcall->numIndices, inst, drawcall->indexOffset, - drawcall->baseVertex, drawcall->instanceOffset); - m_pImmediateContext->End(m_SOStatsQueries[inst - 1]); - } - else - { - m_pImmediateContext->SOSetTargets(1, &m_SOBuffer, &offset); - m_pImmediateContext->Begin(m_SOStatsQueries[inst - 1]); - m_pImmediateContext->DrawInstanced(drawcall->numIndices, inst, drawcall->vertexOffset, - drawcall->instanceOffset); - m_pImmediateContext->End(m_SOStatsQueries[inst - 1]); - } - } - } - - m_pImmediateContext->GSSetShader(NULL, NULL, 0); - m_pImmediateContext->SOSetTargets(0, NULL, NULL); - - m_pImmediateContext->CopyResource(m_SOStagingBuffer, m_SOBuffer); - - std::vector instData; - - if((drawcall->flags & DrawFlags::Instanced) && drawcall->numInstances > 1) - { - uint64_t prevVertCount = 0; - - for(uint32_t inst = 0; inst < drawcall->numInstances; inst++) - { - do - { - hr = m_pImmediateContext->GetData(m_SOStatsQueries[inst], &numPrims, - sizeof(D3D11_QUERY_DATA_SO_STATISTICS), 0); - } while(hr == S_FALSE); - - uint64_t vertCount = 3 * numPrims.NumPrimitivesWritten; - - D3D11PostVSData::InstData d; - d.numVerts = uint32_t(vertCount - prevVertCount); - d.bufOffset = uint32_t(stride * prevVertCount); - prevVertCount = vertCount; - - instData.push_back(d); - } - } - else - { - do - { - hr = m_pImmediateContext->GetData(m_SOStatsQueries[0], &numPrims, - sizeof(D3D11_QUERY_DATA_SO_STATISTICS), 0); - } while(hr == S_FALSE); - } - - if(numPrims.NumPrimitivesWritten == 0) - { - return; - } - - D3D11_MAPPED_SUBRESOURCE mapped; - hr = m_pImmediateContext->Map(m_SOStagingBuffer, 0, D3D11_MAP_READ, 0, &mapped); - - if(FAILED(hr)) - { - RDCERR("Failed to map sobuffer HRESULT: %s", ToStr(hr).c_str()); - return; - } - - D3D11_BUFFER_DESC bufferDesc = {stride * (uint32_t)numPrims.NumPrimitivesWritten * 3, - D3D11_USAGE_IMMUTABLE, - D3D11_BIND_VERTEX_BUFFER, - 0, - 0, - 0}; - - if(bufferDesc.ByteWidth >= m_SOBufferSize) - { - RDCERR("Generated output data too large: %08x", bufferDesc.ByteWidth); - - m_pImmediateContext->Unmap(m_SOStagingBuffer, 0); - return; - } - - ID3D11Buffer *gsoutBuffer = NULL; - - // we need to map this data into memory for read anyway, might as well make this VB - // immutable while we're at it. - D3D11_SUBRESOURCE_DATA initialData; - initialData.pSysMem = mapped.pData; - initialData.SysMemPitch = bufferDesc.ByteWidth; - initialData.SysMemSlicePitch = bufferDesc.ByteWidth; - - hr = m_pDevice->CreateBuffer(&bufferDesc, &initialData, &gsoutBuffer); - - if(FAILED(hr)) - { - RDCERR("Failed to create postvs pos buffer HRESULT: %s", ToStr(hr).c_str()); - - m_pImmediateContext->Unmap(m_SOStagingBuffer, 0); - return; - } - - byte *byteData = (byte *)mapped.pData; - - float nearp = 0.1f; - float farp = 100.0f; - - Vec4f *pos0 = (Vec4f *)byteData; - - bool found = false; - - for(UINT64 i = 1; numPosComponents == 4 && i < numPrims.NumPrimitivesWritten; 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; - } - - m_pImmediateContext->Unmap(m_SOStagingBuffer, 0); - - m_PostVSData[eventId].gsout.buf = gsoutBuffer; - m_PostVSData[eventId].gsout.instStride = 0; - if(drawcall->flags & DrawFlags::Instanced) - m_PostVSData[eventId].gsout.instStride = - bufferDesc.ByteWidth / RDCMAX(1U, drawcall->numInstances); - 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 = NULL; - - topo = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST; - - if(lastShader == dxbcGS) - { - for(size_t i = 0; i < dxbcGS->GetNumDeclarations(); i++) - { - const DXBC::ASMDecl &decl = dxbcGS->GetDeclaration(i); - - if(decl.declaration == DXBC::OPCODE_DCL_GS_OUTPUT_PRIMITIVE_TOPOLOGY) - { - topo = decl.outTopology; - break; - } - } - } - else if(lastShader == dxbcDS) - { - for(size_t i = 0; i < dxbcDS->GetNumDeclarations(); i++) - { - const DXBC::ASMDecl &decl = dxbcDS->GetDeclaration(i); - - if(decl.declaration == DXBC::OPCODE_DCL_TESS_DOMAIN) - { - if(decl.domain == DXBC::DOMAIN_ISOLINE) - topo = D3D11_PRIMITIVE_TOPOLOGY_LINELIST; - else - topo = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST; - break; - } - } - } - - m_PostVSData[eventId].gsout.topo = topo; - - // streamout expands strips unfortunately - if(topo == D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP) - m_PostVSData[eventId].gsout.topo = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST; - else if(topo == D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP) - m_PostVSData[eventId].gsout.topo = D3D11_PRIMITIVE_TOPOLOGY_LINELIST; - else if(topo == D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP_ADJ) - m_PostVSData[eventId].gsout.topo = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ; - else if(topo == D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP_ADJ) - m_PostVSData[eventId].gsout.topo = D3D11_PRIMITIVE_TOPOLOGY_LINELIST_ADJ; - - switch(m_PostVSData[eventId].gsout.topo) - { - case D3D11_PRIMITIVE_TOPOLOGY_POINTLIST: - m_PostVSData[eventId].gsout.numVerts = (uint32_t)numPrims.NumPrimitivesWritten; - break; - case D3D11_PRIMITIVE_TOPOLOGY_LINELIST: - case D3D11_PRIMITIVE_TOPOLOGY_LINELIST_ADJ: - m_PostVSData[eventId].gsout.numVerts = (uint32_t)numPrims.NumPrimitivesWritten * 2; - break; - default: - case D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST: - case D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ: - m_PostVSData[eventId].gsout.numVerts = (uint32_t)numPrims.NumPrimitivesWritten * 3; - break; - } - - if(drawcall->flags & DrawFlags::Instanced) - m_PostVSData[eventId].gsout.numVerts /= RDCMAX(1U, drawcall->numInstances); - - m_PostVSData[eventId].gsout.instData = instData; - } -} - void D3D11DebugManager::RenderMesh(uint32_t eventId, const vector &secondaryDraws, const MeshDisplay &cfg) { diff --git a/renderdoc/driver/d3d11/d3d11_postvs.cpp b/renderdoc/driver/d3d11/d3d11_postvs.cpp new file mode 100644 index 000000000..80694c755 --- /dev/null +++ b/renderdoc/driver/d3d11/d3d11_postvs.cpp @@ -0,0 +1,999 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2018 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#include "data/resource.h" +#include "driver/d3d11/d3d11_resources.h" +#include "driver/shaders/dxbc/dxbc_debug.h" +#include "strings/string_utils.h" +#include "d3d11_context.h" +#include "d3d11_debug.h" +#include "d3d11_manager.h" +#include "d3d11_renderstate.h" + +void D3D11DebugManager::ClearPostVSCache() +{ + for(auto it = m_PostVSData.begin(); it != m_PostVSData.end(); ++it) + { + SAFE_RELEASE(it->second.vsout.buf); + SAFE_RELEASE(it->second.vsout.idxBuf); + SAFE_RELEASE(it->second.gsout.buf); + SAFE_RELEASE(it->second.gsout.idxBuf); + } + + m_PostVSData.clear(); +} + +MeshFormat D3D11DebugManager::GetPostVSBuffers(uint32_t eventId, uint32_t instID, MeshDataStage stage) +{ + D3D11PostVSData postvs; + RDCEraseEl(postvs); + + if(m_PostVSData.find(eventId) != m_PostVSData.end()) + postvs = m_PostVSData[eventId]; + + const D3D11PostVSData::StageData &s = postvs.GetStage(stage); + + MeshFormat ret; + + if(s.useIndices && s.idxBuf) + ret.indexResourceId = ((WrappedID3D11Buffer *)s.idxBuf)->GetResourceID(); + else + ret.indexResourceId = ResourceId(); + ret.indexByteOffset = 0; + ret.indexByteStride = s.idxFmt == DXGI_FORMAT_R16_UINT ? 2 : 4; + ret.baseVertex = 0; + + if(s.buf) + ret.vertexResourceId = ((WrappedID3D11Buffer *)s.buf)->GetResourceID(); + 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 = MakePrimitiveTopology(s.topo); + ret.numIndices = s.numVerts; + + ret.unproject = s.hasPosOut; + ret.nearPlane = s.nearPlane; + ret.farPlane = s.farPlane; + + if(instID < s.instData.size()) + { + D3D11PostVSData::InstData inst = s.instData[instID]; + + ret.vertexByteOffset = inst.bufOffset; + ret.numIndices = inst.numVerts; + } + + return ret; +} + +void D3D11DebugManager::InitPostVSBuffers(uint32_t eventId) +{ + if(m_PostVSData.find(eventId) != m_PostVSData.end()) + return; + + D3D11RenderStateTracker tracker(m_WrappedContext); + + ID3D11VertexShader *vs = NULL; + m_pImmediateContext->VSGetShader(&vs, NULL, NULL); + + ID3D11GeometryShader *gs = NULL; + m_pImmediateContext->GSGetShader(&gs, NULL, NULL); + + ID3D11HullShader *hs = NULL; + m_pImmediateContext->HSGetShader(&hs, NULL, NULL); + + ID3D11DomainShader *ds = NULL; + m_pImmediateContext->DSGetShader(&ds, NULL, NULL); + + if(vs) + vs->Release(); + if(gs) + gs->Release(); + if(hs) + hs->Release(); + if(ds) + ds->Release(); + + if(!vs) + return; + + D3D11_PRIMITIVE_TOPOLOGY topo; + m_pImmediateContext->IAGetPrimitiveTopology(&topo); + + WrappedID3D11Shader *wrappedVS = (WrappedID3D11Shader *)vs; + + if(!wrappedVS) + { + RDCERR("Couldn't find wrapped vertex shader!"); + return; + } + + const DrawcallDescription *drawcall = m_WrappedDevice->GetDrawcall(eventId); + + if(drawcall->numIndices == 0) + return; + + DXBC::DXBCFile *dxbcVS = wrappedVS->GetDXBC(); + + RDCASSERT(dxbcVS); + + DXBC::DXBCFile *dxbcGS = NULL; + + if(gs) + { + WrappedID3D11Shader *wrappedGS = + (WrappedID3D11Shader *)gs; + + if(!wrappedGS) + { + RDCERR("Couldn't find wrapped geometry shader!"); + return; + } + + dxbcGS = wrappedGS->GetDXBC(); + + RDCASSERT(dxbcGS); + } + + DXBC::DXBCFile *dxbcDS = NULL; + + if(ds) + { + WrappedID3D11Shader *wrappedDS = + (WrappedID3D11Shader *)ds; + + if(!wrappedDS) + { + RDCERR("Couldn't find wrapped domain shader!"); + return; + } + + dxbcDS = wrappedDS->GetDXBC(); + + RDCASSERT(dxbcDS); + } + + vector sodecls; + + UINT stride = 0; + int posidx = -1; + int numPosComponents = 0; + + ID3D11GeometryShader *streamoutGS = NULL; + + if(!dxbcVS->m_OutputSig.empty()) + { + for(size_t i = 0; i < dxbcVS->m_OutputSig.size(); i++) + { + SigParameter &sign = dxbcVS->m_OutputSig[i]; + + D3D11_SO_DECLARATION_ENTRY decl; + + decl.Stream = 0; + decl.OutputSlot = 0; + + decl.SemanticName = sign.semanticName.c_str(); + decl.SemanticIndex = sign.semanticIndex; + decl.StartComponent = 0; + decl.ComponentCount = sign.compCount & 0xff; + + if(sign.systemValue == ShaderBuiltin::Position) + { + posidx = (int)sodecls.size(); + numPosComponents = decl.ComponentCount = 4; + } + + stride += decl.ComponentCount * sizeof(float); + sodecls.push_back(decl); + } + + // shift position attribute up to first, keeping order otherwise + // the same + if(posidx > 0) + { + D3D11_SO_DECLARATION_ENTRY pos = sodecls[posidx]; + sodecls.erase(sodecls.begin() + posidx); + sodecls.insert(sodecls.begin(), pos); + } + + HRESULT hr = m_pDevice->CreateGeometryShaderWithStreamOutput( + (void *)&dxbcVS->m_ShaderBlob[0], dxbcVS->m_ShaderBlob.size(), &sodecls[0], + (UINT)sodecls.size(), &stride, 1, D3D11_SO_NO_RASTERIZED_STREAM, NULL, &streamoutGS); + + if(FAILED(hr)) + { + RDCERR("Failed to create Geometry Shader + SO HRESULT: %s", ToStr(hr).c_str()); + return; + } + + m_pImmediateContext->GSSetShader(streamoutGS, NULL, 0); + m_pImmediateContext->HSSetShader(NULL, NULL, 0); + m_pImmediateContext->DSSetShader(NULL, NULL, 0); + + SAFE_RELEASE(streamoutGS); + + UINT offset = 0; + ID3D11Buffer *idxBuf = NULL; + DXGI_FORMAT idxFmt = DXGI_FORMAT_UNKNOWN; + UINT idxOffs = 0; + + m_pImmediateContext->IAGetIndexBuffer(&idxBuf, &idxFmt, &idxOffs); + + ID3D11Buffer *origBuf = idxBuf; + + if(!(drawcall->flags & DrawFlags::UseIBuffer)) + { + m_pImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_POINTLIST); + + SAFE_RELEASE(idxBuf); + + uint32_t outputSize = stride * drawcall->numIndices; + if(drawcall->flags & DrawFlags::Instanced) + outputSize *= drawcall->numInstances; + + if(m_SOBufferSize < outputSize) + { + int oldSize = m_SOBufferSize; + while(m_SOBufferSize < outputSize) + m_SOBufferSize *= 2; + RDCWARN("Resizing stream-out buffer from %d to %d", oldSize, m_SOBufferSize); + CreateSOBuffers(); + } + + m_pImmediateContext->SOSetTargets(1, &m_SOBuffer, &offset); + + m_pImmediateContext->Begin(m_SOStatsQueries[0]); + + if(drawcall->flags & DrawFlags::Instanced) + m_pImmediateContext->DrawInstanced(drawcall->numIndices, drawcall->numInstances, + drawcall->vertexOffset, drawcall->instanceOffset); + else + m_pImmediateContext->Draw(drawcall->numIndices, drawcall->vertexOffset); + + m_pImmediateContext->End(m_SOStatsQueries[0]); + } + else // drawcall is indexed + { + bool index16 = (idxFmt == DXGI_FORMAT_R16_UINT); + UINT bytesize = index16 ? 2 : 4; + + bytebuf idxdata; + GetBufferData(idxBuf, idxOffs + drawcall->indexOffset * bytesize, + drawcall->numIndices * bytesize, idxdata); + + SAFE_RELEASE(idxBuf); + + vector indices; + + 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(index16 ? idxdata.size() / 2 : idxdata.size() / 4), drawcall->numIndices); + + uint32_t idxclamp = 0; + if(drawcall->baseVertex < 0) + idxclamp = uint32_t(-drawcall->baseVertex); + + // grab all unique vertex indices referenced + for(uint32_t i = 0; i < numIndices; i++) + { + uint32_t i32 = index16 ? uint32_t(idx16[i]) : idx32[i]; + + // apply baseVertex but clamp to 0 (don't allow index to become negative) + if(i32 < idxclamp) + i32 = 0; + else if(drawcall->baseVertex < 0) + i32 -= idxclamp; + else if(drawcall->baseVertex > 0) + i32 += drawcall->baseVertex; + + 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 indexRemap; + for(size_t i = 0; i < indices.size(); i++) + { + // by definition, this index will only appear once in indices[] + indexRemap[indices[i]] = i; + } + + D3D11_BUFFER_DESC desc = {UINT(sizeof(uint32_t) * indices.size()), + D3D11_USAGE_IMMUTABLE, + D3D11_BIND_INDEX_BUFFER, + 0, + 0, + 0}; + D3D11_SUBRESOURCE_DATA initData = {&indices[0], desc.ByteWidth, desc.ByteWidth}; + + if(!indices.empty()) + m_pDevice->CreateBuffer(&desc, &initData, &idxBuf); + else + idxBuf = NULL; + + m_pImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_POINTLIST); + m_pImmediateContext->IASetIndexBuffer(idxBuf, DXGI_FORMAT_R32_UINT, 0); + SAFE_RELEASE(idxBuf); + + uint32_t outputSize = stride * (uint32_t)indices.size(); + if(drawcall->flags & DrawFlags::Instanced) + outputSize *= drawcall->numInstances; + + if(m_SOBufferSize < outputSize) + { + int oldSize = m_SOBufferSize; + while(m_SOBufferSize < outputSize) + m_SOBufferSize *= 2; + RDCWARN("Resizing stream-out buffer from %d to %d", oldSize, m_SOBufferSize); + CreateSOBuffers(); + } + + m_pImmediateContext->SOSetTargets(1, &m_SOBuffer, &offset); + + m_pImmediateContext->Begin(m_SOStatsQueries[0]); + + if(drawcall->flags & DrawFlags::Instanced) + m_pImmediateContext->DrawIndexedInstanced((UINT)indices.size(), drawcall->numInstances, 0, + 0, drawcall->instanceOffset); + else + m_pImmediateContext->DrawIndexed((UINT)indices.size(), 0, 0); + + m_pImmediateContext->End(m_SOStatsQueries[0]); + + // rebase existing index buffer to point to the right elements in our stream-out'd + // vertex buffer + for(uint32_t i = 0; i < numIndices; i++) + { + uint32_t i32 = index16 ? uint32_t(idx16[i]) : idx32[i]; + + // preserve primitive restart indices + if(i32 == (index16 ? 0xffff : 0xffffffff)) + continue; + + // apply baseVertex but clamp to 0 (don't allow index to become negative) + if(i32 < idxclamp) + i32 = 0; + else if(drawcall->baseVertex < 0) + i32 -= idxclamp; + else if(drawcall->baseVertex > 0) + i32 += drawcall->baseVertex; + + if(index16) + idx16[i] = uint16_t(indexRemap[i32]); + else + idx32[i] = uint32_t(indexRemap[i32]); + } + + desc.ByteWidth = (UINT)idxdata.size(); + initData.pSysMem = &idxdata[0]; + initData.SysMemPitch = initData.SysMemSlicePitch = desc.ByteWidth; + + if(desc.ByteWidth > 0) + m_pDevice->CreateBuffer(&desc, &initData, &idxBuf); + else + idxBuf = NULL; + } + + m_pImmediateContext->IASetPrimitiveTopology(topo); + m_pImmediateContext->IASetIndexBuffer(origBuf, idxFmt, idxOffs); + + m_pImmediateContext->GSSetShader(NULL, NULL, 0); + m_pImmediateContext->SOSetTargets(0, NULL, NULL); + + D3D11_QUERY_DATA_SO_STATISTICS numPrims; + + m_pImmediateContext->CopyResource(m_SOStagingBuffer, m_SOBuffer); + + do + { + hr = m_pImmediateContext->GetData(m_SOStatsQueries[0], &numPrims, + sizeof(D3D11_QUERY_DATA_SO_STATISTICS), 0); + } while(hr == S_FALSE); + + if(numPrims.NumPrimitivesWritten == 0) + { + m_PostVSData[eventId] = D3D11PostVSData(); + SAFE_RELEASE(idxBuf); + return; + } + + D3D11_MAPPED_SUBRESOURCE mapped; + hr = m_pImmediateContext->Map(m_SOStagingBuffer, 0, D3D11_MAP_READ, 0, &mapped); + + if(FAILED(hr)) + { + RDCERR("Failed to map sobuffer HRESULT: %s", ToStr(hr).c_str()); + SAFE_RELEASE(idxBuf); + return; + } + + D3D11_BUFFER_DESC bufferDesc = {stride * (uint32_t)numPrims.NumPrimitivesWritten, + D3D11_USAGE_IMMUTABLE, + D3D11_BIND_VERTEX_BUFFER, + 0, + 0, + 0}; + + ID3D11Buffer *vsoutBuffer = NULL; + + // we need to map this data into memory for read anyway, might as well make this VB + // immutable while we're at it. + D3D11_SUBRESOURCE_DATA initialData; + initialData.pSysMem = mapped.pData; + initialData.SysMemPitch = bufferDesc.ByteWidth; + initialData.SysMemSlicePitch = bufferDesc.ByteWidth; + + hr = m_pDevice->CreateBuffer(&bufferDesc, &initialData, &vsoutBuffer); + + if(FAILED(hr)) + { + RDCERR("Failed to create postvs pos buffer HRESULT: %s", ToStr(hr).c_str()); + + m_pImmediateContext->Unmap(m_SOStagingBuffer, 0); + SAFE_RELEASE(idxBuf); + return; + } + + byte *byteData = (byte *)mapped.pData; + + float nearp = 0.1f; + float farp = 100.0f; + + Vec4f *pos0 = (Vec4f *)byteData; + + bool found = false; + + for(UINT64 i = 1; numPosComponents == 4 && i < numPrims.NumPrimitivesWritten; 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; + } + + m_pImmediateContext->Unmap(m_SOStagingBuffer, 0); + + m_PostVSData[eventId].vsin.topo = topo; + 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 = + bufferDesc.ByteWidth / RDCMAX(1U, drawcall->numInstances); + + m_PostVSData[eventId].vsout.idxBuf = NULL; + if(m_PostVSData[eventId].vsout.useIndices && idxBuf) + { + m_PostVSData[eventId].vsout.idxBuf = idxBuf; + m_PostVSData[eventId].vsout.idxFmt = idxFmt; + } + + m_PostVSData[eventId].vsout.hasPosOut = posidx >= 0; + + m_PostVSData[eventId].vsout.topo = topo; + } + else + { + // empty vertex output signature + m_PostVSData[eventId].vsin.topo = topo; + m_PostVSData[eventId].vsout.buf = NULL; + m_PostVSData[eventId].vsout.instStride = 0; + m_PostVSData[eventId].vsout.vertStride = 0; + m_PostVSData[eventId].vsout.nearPlane = 0.0f; + m_PostVSData[eventId].vsout.farPlane = 0.0f; + m_PostVSData[eventId].vsout.useIndices = false; + m_PostVSData[eventId].vsout.hasPosOut = false; + m_PostVSData[eventId].vsout.idxBuf = NULL; + + m_PostVSData[eventId].vsout.topo = topo; + } + + if(dxbcGS || dxbcDS) + { + stride = 0; + posidx = -1; + numPosComponents = 0; + + DXBC::DXBCFile *lastShader = dxbcGS; + if(dxbcDS) + lastShader = dxbcDS; + + sodecls.clear(); + for(size_t i = 0; i < lastShader->m_OutputSig.size(); i++) + { + SigParameter &sign = lastShader->m_OutputSig[i]; + + D3D11_SO_DECLARATION_ENTRY decl; + + // for now, skip streams that aren't stream 0 + if(sign.stream != 0) + continue; + + decl.Stream = 0; + decl.OutputSlot = 0; + + decl.SemanticName = sign.semanticName.c_str(); + decl.SemanticIndex = sign.semanticIndex; + decl.StartComponent = 0; + decl.ComponentCount = sign.compCount & 0xff; + + if(sign.systemValue == ShaderBuiltin::Position) + { + posidx = (int)sodecls.size(); + numPosComponents = decl.ComponentCount = 4; + } + + stride += decl.ComponentCount * sizeof(float); + sodecls.push_back(decl); + } + + // shift position attribute up to first, keeping order otherwise + // the same + if(posidx > 0) + { + D3D11_SO_DECLARATION_ENTRY pos = sodecls[posidx]; + sodecls.erase(sodecls.begin() + posidx); + sodecls.insert(sodecls.begin(), pos); + } + + streamoutGS = NULL; + + HRESULT hr = m_pDevice->CreateGeometryShaderWithStreamOutput( + (void *)&lastShader->m_ShaderBlob[0], lastShader->m_ShaderBlob.size(), &sodecls[0], + (UINT)sodecls.size(), &stride, 1, D3D11_SO_NO_RASTERIZED_STREAM, NULL, &streamoutGS); + + if(FAILED(hr)) + { + RDCERR("Failed to create Geometry Shader + SO HRESULT: %s", ToStr(hr).c_str()); + return; + } + + m_pImmediateContext->GSSetShader(streamoutGS, NULL, 0); + m_pImmediateContext->HSSetShader(hs, NULL, 0); + m_pImmediateContext->DSSetShader(ds, NULL, 0); + + SAFE_RELEASE(streamoutGS); + + UINT offset = 0; + + D3D11_QUERY_DATA_SO_STATISTICS numPrims = {0}; + + // do the whole draw, and if our output buffer isn't large enough then loop around. + while(true) + { + m_pImmediateContext->Begin(m_SOStatsQueries[0]); + + m_pImmediateContext->SOSetTargets(1, &m_SOBuffer, &offset); + + if(drawcall->flags & DrawFlags::Instanced) + { + if(drawcall->flags & DrawFlags::UseIBuffer) + { + m_pImmediateContext->DrawIndexedInstanced(drawcall->numIndices, drawcall->numInstances, + drawcall->indexOffset, drawcall->baseVertex, + drawcall->instanceOffset); + } + else + { + m_pImmediateContext->DrawInstanced(drawcall->numIndices, drawcall->numInstances, + drawcall->vertexOffset, drawcall->instanceOffset); + } + } + else + { + // trying to stream out a stream-out-auto based drawcall would be bad! + // instead just draw the number of verts we pre-calculated + if(drawcall->flags & DrawFlags::Auto) + { + m_pImmediateContext->Draw(drawcall->numIndices, 0); + } + else + { + if(drawcall->flags & DrawFlags::UseIBuffer) + { + m_pImmediateContext->DrawIndexed(drawcall->numIndices, drawcall->indexOffset, + drawcall->baseVertex); + } + else + { + m_pImmediateContext->Draw(drawcall->numIndices, drawcall->vertexOffset); + } + } + } + + m_pImmediateContext->End(m_SOStatsQueries[0]); + + do + { + hr = m_pImmediateContext->GetData(m_SOStatsQueries[0], &numPrims, + sizeof(D3D11_QUERY_DATA_SO_STATISTICS), 0); + } while(hr == S_FALSE); + + if(m_SOBufferSize < stride * (uint32_t)numPrims.PrimitivesStorageNeeded * 3) + { + int oldSize = m_SOBufferSize; + while(m_SOBufferSize < stride * (uint32_t)numPrims.PrimitivesStorageNeeded * 3) + m_SOBufferSize *= 2; + RDCWARN("Resizing stream-out buffer from %d to %d", oldSize, m_SOBufferSize); + CreateSOBuffers(); + continue; + } + + break; + } + + // 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 && drawcall->numInstances > 1) + { + // ensure we have enough queries + while(m_SOStatsQueries.size() < drawcall->numInstances) + { + D3D11_QUERY_DESC qdesc; + qdesc.MiscFlags = 0; + qdesc.Query = D3D11_QUERY_SO_STATISTICS; + + ID3D11Query *q = NULL; + hr = m_pDevice->CreateQuery(&qdesc, &q); + if(FAILED(hr)) + RDCERR("Failed to create m_SOStatsQuery HRESULT: %s", ToStr(hr).c_str()); + + m_SOStatsQueries.push_back(q); + } + + // 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++) + { + if(drawcall->flags & DrawFlags::UseIBuffer) + { + m_pImmediateContext->SOSetTargets(1, &m_SOBuffer, &offset); + m_pImmediateContext->Begin(m_SOStatsQueries[inst - 1]); + m_pImmediateContext->DrawIndexedInstanced(drawcall->numIndices, inst, drawcall->indexOffset, + drawcall->baseVertex, drawcall->instanceOffset); + m_pImmediateContext->End(m_SOStatsQueries[inst - 1]); + } + else + { + m_pImmediateContext->SOSetTargets(1, &m_SOBuffer, &offset); + m_pImmediateContext->Begin(m_SOStatsQueries[inst - 1]); + m_pImmediateContext->DrawInstanced(drawcall->numIndices, inst, drawcall->vertexOffset, + drawcall->instanceOffset); + m_pImmediateContext->End(m_SOStatsQueries[inst - 1]); + } + } + } + + m_pImmediateContext->GSSetShader(NULL, NULL, 0); + m_pImmediateContext->SOSetTargets(0, NULL, NULL); + + m_pImmediateContext->CopyResource(m_SOStagingBuffer, m_SOBuffer); + + std::vector instData; + + if((drawcall->flags & DrawFlags::Instanced) && drawcall->numInstances > 1) + { + uint64_t prevVertCount = 0; + + for(uint32_t inst = 0; inst < drawcall->numInstances; inst++) + { + do + { + hr = m_pImmediateContext->GetData(m_SOStatsQueries[inst], &numPrims, + sizeof(D3D11_QUERY_DATA_SO_STATISTICS), 0); + } while(hr == S_FALSE); + + uint64_t vertCount = 3 * numPrims.NumPrimitivesWritten; + + D3D11PostVSData::InstData d; + d.numVerts = uint32_t(vertCount - prevVertCount); + d.bufOffset = uint32_t(stride * prevVertCount); + prevVertCount = vertCount; + + instData.push_back(d); + } + } + else + { + do + { + hr = m_pImmediateContext->GetData(m_SOStatsQueries[0], &numPrims, + sizeof(D3D11_QUERY_DATA_SO_STATISTICS), 0); + } while(hr == S_FALSE); + } + + if(numPrims.NumPrimitivesWritten == 0) + { + return; + } + + D3D11_MAPPED_SUBRESOURCE mapped; + hr = m_pImmediateContext->Map(m_SOStagingBuffer, 0, D3D11_MAP_READ, 0, &mapped); + + if(FAILED(hr)) + { + RDCERR("Failed to map sobuffer HRESULT: %s", ToStr(hr).c_str()); + return; + } + + D3D11_BUFFER_DESC bufferDesc = {stride * (uint32_t)numPrims.NumPrimitivesWritten * 3, + D3D11_USAGE_IMMUTABLE, + D3D11_BIND_VERTEX_BUFFER, + 0, + 0, + 0}; + + if(bufferDesc.ByteWidth >= m_SOBufferSize) + { + RDCERR("Generated output data too large: %08x", bufferDesc.ByteWidth); + + m_pImmediateContext->Unmap(m_SOStagingBuffer, 0); + return; + } + + ID3D11Buffer *gsoutBuffer = NULL; + + // we need to map this data into memory for read anyway, might as well make this VB + // immutable while we're at it. + D3D11_SUBRESOURCE_DATA initialData; + initialData.pSysMem = mapped.pData; + initialData.SysMemPitch = bufferDesc.ByteWidth; + initialData.SysMemSlicePitch = bufferDesc.ByteWidth; + + hr = m_pDevice->CreateBuffer(&bufferDesc, &initialData, &gsoutBuffer); + + if(FAILED(hr)) + { + RDCERR("Failed to create postvs pos buffer HRESULT: %s", ToStr(hr).c_str()); + + m_pImmediateContext->Unmap(m_SOStagingBuffer, 0); + return; + } + + byte *byteData = (byte *)mapped.pData; + + float nearp = 0.1f; + float farp = 100.0f; + + Vec4f *pos0 = (Vec4f *)byteData; + + bool found = false; + + for(UINT64 i = 1; numPosComponents == 4 && i < numPrims.NumPrimitivesWritten; 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; + } + + m_pImmediateContext->Unmap(m_SOStagingBuffer, 0); + + m_PostVSData[eventId].gsout.buf = gsoutBuffer; + m_PostVSData[eventId].gsout.instStride = 0; + if(drawcall->flags & DrawFlags::Instanced) + m_PostVSData[eventId].gsout.instStride = + bufferDesc.ByteWidth / RDCMAX(1U, drawcall->numInstances); + 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 = NULL; + + topo = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST; + + if(lastShader == dxbcGS) + { + for(size_t i = 0; i < dxbcGS->GetNumDeclarations(); i++) + { + const DXBC::ASMDecl &decl = dxbcGS->GetDeclaration(i); + + if(decl.declaration == DXBC::OPCODE_DCL_GS_OUTPUT_PRIMITIVE_TOPOLOGY) + { + topo = decl.outTopology; + break; + } + } + } + else if(lastShader == dxbcDS) + { + for(size_t i = 0; i < dxbcDS->GetNumDeclarations(); i++) + { + const DXBC::ASMDecl &decl = dxbcDS->GetDeclaration(i); + + if(decl.declaration == DXBC::OPCODE_DCL_TESS_DOMAIN) + { + if(decl.domain == DXBC::DOMAIN_ISOLINE) + topo = D3D11_PRIMITIVE_TOPOLOGY_LINELIST; + else + topo = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST; + break; + } + } + } + + m_PostVSData[eventId].gsout.topo = topo; + + // streamout expands strips unfortunately + if(topo == D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP) + m_PostVSData[eventId].gsout.topo = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST; + else if(topo == D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP) + m_PostVSData[eventId].gsout.topo = D3D11_PRIMITIVE_TOPOLOGY_LINELIST; + else if(topo == D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP_ADJ) + m_PostVSData[eventId].gsout.topo = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ; + else if(topo == D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP_ADJ) + m_PostVSData[eventId].gsout.topo = D3D11_PRIMITIVE_TOPOLOGY_LINELIST_ADJ; + + switch(m_PostVSData[eventId].gsout.topo) + { + case D3D11_PRIMITIVE_TOPOLOGY_POINTLIST: + m_PostVSData[eventId].gsout.numVerts = (uint32_t)numPrims.NumPrimitivesWritten; + break; + case D3D11_PRIMITIVE_TOPOLOGY_LINELIST: + case D3D11_PRIMITIVE_TOPOLOGY_LINELIST_ADJ: + m_PostVSData[eventId].gsout.numVerts = (uint32_t)numPrims.NumPrimitivesWritten * 2; + break; + default: + case D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST: + case D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ: + m_PostVSData[eventId].gsout.numVerts = (uint32_t)numPrims.NumPrimitivesWritten * 3; + break; + } + + if(drawcall->flags & DrawFlags::Instanced) + m_PostVSData[eventId].gsout.numVerts /= RDCMAX(1U, drawcall->numInstances); + + m_PostVSData[eventId].gsout.instData = instData; + } +} diff --git a/renderdoc/driver/d3d11/renderdoc_d3d11.vcxproj b/renderdoc/driver/d3d11/renderdoc_d3d11.vcxproj index 5d807cfe0..f49ba906d 100644 --- a/renderdoc/driver/d3d11/renderdoc_d3d11.vcxproj +++ b/renderdoc/driver/d3d11/renderdoc_d3d11.vcxproj @@ -100,6 +100,7 @@ + diff --git a/renderdoc/driver/d3d11/renderdoc_d3d11.vcxproj.filters b/renderdoc/driver/d3d11/renderdoc_d3d11.vcxproj.filters index 1a4bbb568..578cf3b0d 100644 --- a/renderdoc/driver/d3d11/renderdoc_d3d11.vcxproj.filters +++ b/renderdoc/driver/d3d11/renderdoc_d3d11.vcxproj.filters @@ -93,6 +93,9 @@ Util + + Replay + diff --git a/renderdoc/driver/d3d12/d3d12_debug.cpp b/renderdoc/driver/d3d12/d3d12_debug.cpp index 6381bd0de..7ca2fd323 100644 --- a/renderdoc/driver/d3d12/d3d12_debug.cpp +++ b/renderdoc/driver/d3d12/d3d12_debug.cpp @@ -3789,1235 +3789,6 @@ void D3D12DebugManager::GetTextureData(ResourceId tex, uint32_t arrayIdx, uint32 SAFE_RELEASE(tmpTexture); } -void D3D12DebugManager::ClearPostVSCache() -{ - for(auto it = m_PostVSData.begin(); it != m_PostVSData.end(); ++it) - { - SAFE_RELEASE(it->second.vsout.buf); - SAFE_RELEASE(it->second.vsout.idxBuf); - SAFE_RELEASE(it->second.gsout.buf); - SAFE_RELEASE(it->second.gsout.idxBuf); - } - - m_PostVSData.clear(); -} - -void D3D12DebugManager::InitPostVSBuffers(uint32_t eventId) -{ - // go through any aliasing - if(m_PostVSAlias.find(eventId) != m_PostVSAlias.end()) - eventId = m_PostVSAlias[eventId]; - - if(m_PostVSData.find(eventId) != m_PostVSData.end()) - return; - - D3D12CommandData *cmd = m_WrappedDevice->GetQueue()->GetCommandData(); - const D3D12RenderState &rs = cmd->m_RenderState; - - if(rs.pipe == ResourceId()) - return; - - WrappedID3D12PipelineState *origPSO = - m_WrappedDevice->GetResourceManager()->GetCurrentAs(rs.pipe); - - if(!origPSO->IsGraphics()) - return; - - D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = origPSO->GetGraphicsDesc(); - - if(psoDesc.VS.BytecodeLength == 0) - return; - - WrappedID3D12Shader *vs = origPSO->VS(); - - D3D_PRIMITIVE_TOPOLOGY topo = rs.topo; - - const DrawcallDescription *drawcall = m_WrappedDevice->GetDrawcall(eventId); - - if(drawcall->numIndices == 0) - return; - - DXBC::DXBCFile *dxbcVS = vs->GetDXBC(); - - RDCASSERT(dxbcVS); - - DXBC::DXBCFile *dxbcGS = NULL; - - WrappedID3D12Shader *gs = origPSO->GS(); - - if(gs) - { - dxbcGS = gs->GetDXBC(); - - RDCASSERT(dxbcGS); - } - - DXBC::DXBCFile *dxbcDS = NULL; - - WrappedID3D12Shader *ds = origPSO->DS(); - - if(ds) - { - dxbcDS = ds->GetDXBC(); - - RDCASSERT(dxbcDS); - } - - ID3D12RootSignature *soSig = NULL; - - HRESULT hr = S_OK; - - { - WrappedID3D12RootSignature *sig = - m_WrappedDevice->GetResourceManager()->GetCurrentAs( - rs.graphics.rootsig); - - D3D12RootSignature rootsig = sig->sig; - - // create a root signature that allows stream out, if necessary - if((rootsig.Flags & D3D12_ROOT_SIGNATURE_FLAG_ALLOW_STREAM_OUTPUT) == 0) - { - rootsig.Flags |= D3D12_ROOT_SIGNATURE_FLAG_ALLOW_STREAM_OUTPUT; - - ID3DBlob *blob = MakeRootSig(rootsig); - - hr = m_WrappedDevice->CreateRootSignature(0, blob->GetBufferPointer(), blob->GetBufferSize(), - __uuidof(ID3D12RootSignature), (void **)&soSig); - if(FAILED(hr)) - { - RDCERR("Couldn't enable stream-out in root signature: HRESULT: %s", ToStr(hr).c_str()); - return; - } - - SAFE_RELEASE(blob); - } - } - - vector sodecls; - - UINT stride = 0; - int posidx = -1; - int numPosComponents = 0; - - if(!dxbcVS->m_OutputSig.empty()) - { - for(const SigParameter &sign : dxbcVS->m_OutputSig) - { - D3D12_SO_DECLARATION_ENTRY decl; - - decl.Stream = 0; - decl.OutputSlot = 0; - - decl.SemanticName = sign.semanticName.c_str(); - decl.SemanticIndex = sign.semanticIndex; - decl.StartComponent = 0; - decl.ComponentCount = sign.compCount & 0xff; - - if(sign.systemValue == ShaderBuiltin::Position) - { - posidx = (int)sodecls.size(); - numPosComponents = decl.ComponentCount = 4; - } - - stride += decl.ComponentCount * sizeof(float); - sodecls.push_back(decl); - } - - if(stride == 0) - { - RDCERR("Didn't get valid stride! Setting to 4 bytes"); - stride = 4; - } - - // shift position attribute up to first, keeping order otherwise - // the same - if(posidx > 0) - { - D3D12_SO_DECLARATION_ENTRY pos = sodecls[posidx]; - sodecls.erase(sodecls.begin() + posidx); - sodecls.insert(sodecls.begin(), pos); - } - - // set up stream output entries and buffers - psoDesc.StreamOutput.NumEntries = (UINT)sodecls.size(); - psoDesc.StreamOutput.pSODeclaration = &sodecls[0]; - psoDesc.StreamOutput.NumStrides = 1; - psoDesc.StreamOutput.pBufferStrides = &stride; - psoDesc.StreamOutput.RasterizedStream = D3D12_SO_NO_RASTERIZED_STREAM; - - // disable all other shader stages - psoDesc.HS.BytecodeLength = 0; - psoDesc.HS.pShaderBytecode = NULL; - psoDesc.DS.BytecodeLength = 0; - psoDesc.DS.pShaderBytecode = NULL; - psoDesc.GS.BytecodeLength = 0; - psoDesc.GS.pShaderBytecode = NULL; - psoDesc.PS.BytecodeLength = 0; - psoDesc.PS.pShaderBytecode = NULL; - - // disable any rasterization/use of output targets - psoDesc.DepthStencilState.DepthEnable = FALSE; - psoDesc.DepthStencilState.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ZERO; - psoDesc.DepthStencilState.StencilEnable = FALSE; - - if(soSig) - psoDesc.pRootSignature = soSig; - - // render as points - psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_POINT; - - // disable outputs - psoDesc.NumRenderTargets = 0; - RDCEraseEl(psoDesc.RTVFormats); - psoDesc.DSVFormat = DXGI_FORMAT_UNKNOWN; - - ID3D12PipelineState *pipe = NULL; - hr = m_WrappedDevice->CreateGraphicsPipelineState(&psoDesc, __uuidof(ID3D12PipelineState), - (void **)&pipe); - if(FAILED(hr)) - { - RDCERR("Couldn't create patched graphics pipeline: HRESULT: %s", ToStr(hr).c_str()); - SAFE_RELEASE(soSig); - return; - } - - ID3D12Resource *idxBuf = NULL; - - bool recreate = false; - uint64_t outputSize = uint64_t(drawcall->numIndices) * drawcall->numInstances * stride; - - if(m_SOBufferSize < outputSize) - { - uint64_t oldSize = m_SOBufferSize; - while(m_SOBufferSize < outputSize) - m_SOBufferSize *= 2; - RDCWARN("Resizing stream-out buffer from %llu to %llu for output data", oldSize, - m_SOBufferSize); - recreate = true; - } - - if(!(drawcall->flags & DrawFlags::UseIBuffer)) - { - if(recreate) - { - m_WrappedDevice->GPUSync(); - - CreateSOBuffers(); - } - - m_DebugList->Reset(m_DebugAlloc, NULL); - - rs.ApplyState(m_DebugList); - - m_DebugList->SetPipelineState(pipe); - - if(soSig) - { - m_DebugList->SetGraphicsRootSignature(soSig); - rs.ApplyGraphicsRootElements(m_DebugList); - } - - D3D12_STREAM_OUTPUT_BUFFER_VIEW view; - view.BufferFilledSizeLocation = m_SOBuffer->GetGPUVirtualAddress(); - view.BufferLocation = m_SOBuffer->GetGPUVirtualAddress() + 64; - view.SizeInBytes = m_SOBufferSize; - m_DebugList->SOSetTargets(0, 1, &view); - - m_DebugList->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_POINTLIST); - m_DebugList->DrawInstanced(drawcall->numIndices, drawcall->numInstances, - drawcall->vertexOffset, drawcall->instanceOffset); - } - else // drawcall is indexed - { - bytebuf idxdata; - GetBufferData(rs.ibuffer.buf, rs.ibuffer.offs + drawcall->indexOffset * rs.ibuffer.bytewidth, - RDCMIN(drawcall->numIndices * rs.ibuffer.bytewidth, rs.ibuffer.size), idxdata); - - vector indices; - - 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() / rs.ibuffer.bytewidth), drawcall->numIndices); - - uint32_t idxclamp = 0; - if(drawcall->baseVertex < 0) - idxclamp = uint32_t(-drawcall->baseVertex); - - // grab all unique vertex indices referenced - for(uint32_t i = 0; i < numIndices; i++) - { - uint32_t i32 = rs.ibuffer.bytewidth == 2 ? uint32_t(idx16[i]) : idx32[i]; - - // apply baseVertex but clamp to 0 (don't allow index to become negative) - if(i32 < idxclamp) - i32 = 0; - else if(drawcall->baseVertex < 0) - i32 -= idxclamp; - else if(drawcall->baseVertex > 0) - i32 += drawcall->baseVertex; - - 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 indexRemap; - for(size_t i = 0; i < indices.size(); i++) - { - // by definition, this index will only appear once in indices[] - indexRemap[indices[i]] = i; - } - - if(m_SOBufferSize / sizeof(Vec4f) < indices.size() * sizeof(uint32_t)) - { - uint64_t oldSize = m_SOBufferSize; - while(m_SOBufferSize / sizeof(Vec4f) < indices.size() * sizeof(uint32_t)) - m_SOBufferSize *= 2; - RDCWARN("Resizing stream-out buffer from %llu to %llu for indices", oldSize, m_SOBufferSize); - recreate = true; - } - - if(recreate) - { - m_WrappedDevice->GPUSync(); - - CreateSOBuffers(); - } - - FillBuffer(m_SOPatchedIndexBuffer, 0, &indices[0], indices.size() * sizeof(uint32_t)); - - D3D12_INDEX_BUFFER_VIEW patchedIB; - - patchedIB.BufferLocation = m_SOPatchedIndexBuffer->GetGPUVirtualAddress(); - patchedIB.Format = DXGI_FORMAT_R32_UINT; - patchedIB.SizeInBytes = UINT(indices.size() * sizeof(uint32_t)); - - m_DebugList->Reset(m_DebugAlloc, NULL); - - rs.ApplyState(m_DebugList); - - m_DebugList->SetPipelineState(pipe); - - m_DebugList->IASetIndexBuffer(&patchedIB); - - if(soSig) - { - m_DebugList->SetGraphicsRootSignature(soSig); - rs.ApplyGraphicsRootElements(m_DebugList); - } - - D3D12_STREAM_OUTPUT_BUFFER_VIEW view; - view.BufferFilledSizeLocation = m_SOBuffer->GetGPUVirtualAddress(); - view.BufferLocation = m_SOBuffer->GetGPUVirtualAddress() + 64; - view.SizeInBytes = m_SOBufferSize; - m_DebugList->SOSetTargets(0, 1, &view); - - m_DebugList->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_POINTLIST); - - m_DebugList->DrawIndexedInstanced((UINT)indices.size(), drawcall->numInstances, 0, 0, - drawcall->instanceOffset); - - uint32_t stripCutValue = 0; - if(psoDesc.IBStripCutValue == D3D12_INDEX_BUFFER_STRIP_CUT_VALUE_0xFFFF) - stripCutValue = 0xffff; - else if(psoDesc.IBStripCutValue == D3D12_INDEX_BUFFER_STRIP_CUT_VALUE_0xFFFFFFFF) - stripCutValue = 0xffffffff; - - // rebase existing index buffer to point to the right elements in our stream-out'd - // vertex buffer - for(uint32_t i = 0; i < numIndices; i++) - { - uint32_t i32 = rs.ibuffer.bytewidth == 2 ? uint32_t(idx16[i]) : idx32[i]; - - // preserve primitive restart indices - if(stripCutValue && i32 == stripCutValue) - continue; - - // apply baseVertex but clamp to 0 (don't allow index to become negative) - if(i32 < idxclamp) - i32 = 0; - else if(drawcall->baseVertex < 0) - i32 -= idxclamp; - else if(drawcall->baseVertex > 0) - i32 += drawcall->baseVertex; - - if(rs.ibuffer.bytewidth == 2) - idx16[i] = uint16_t(indexRemap[i32]); - else - idx32[i] = uint32_t(indexRemap[i32]); - } - - idxBuf = NULL; - - if(!idxdata.empty()) - { - D3D12_RESOURCE_DESC idxBufDesc; - idxBufDesc.Alignment = 0; - idxBufDesc.DepthOrArraySize = 1; - idxBufDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; - idxBufDesc.Flags = D3D12_RESOURCE_FLAG_NONE; - idxBufDesc.Format = DXGI_FORMAT_UNKNOWN; - idxBufDesc.Height = 1; - idxBufDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; - idxBufDesc.MipLevels = 1; - idxBufDesc.SampleDesc.Count = 1; - idxBufDesc.SampleDesc.Quality = 0; - idxBufDesc.Width = idxdata.size(); - - D3D12_HEAP_PROPERTIES heapProps; - heapProps.Type = D3D12_HEAP_TYPE_UPLOAD; - heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; - heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; - heapProps.CreationNodeMask = 1; - heapProps.VisibleNodeMask = 1; - - hr = m_WrappedDevice->CreateCommittedResource(&heapProps, D3D12_HEAP_FLAG_NONE, &idxBufDesc, - D3D12_RESOURCE_STATE_GENERIC_READ, NULL, - __uuidof(ID3D12Resource), (void **)&idxBuf); - RDCASSERTEQUAL(hr, S_OK); - - SetObjName(idxBuf, StringFormat::Fmt("PostVS idxBuf for %u", eventId)); - - FillBuffer(idxBuf, 0, &idxdata[0], idxdata.size()); - } - } - - D3D12_RESOURCE_BARRIER sobarr = {}; - sobarr.Transition.pResource = m_SOBuffer; - sobarr.Transition.StateBefore = D3D12_RESOURCE_STATE_STREAM_OUT; - sobarr.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_SOURCE; - - m_DebugList->ResourceBarrier(1, &sobarr); - - m_DebugList->CopyResource(m_SOStagingBuffer, m_SOBuffer); - - // we're done with this after the copy, so we can discard it and reset - // the counter for the next stream-out - sobarr.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_SOURCE; - sobarr.Transition.StateAfter = D3D12_RESOURCE_STATE_UNORDERED_ACCESS; - m_DebugList->DiscardResource(m_SOBuffer, NULL); - m_DebugList->ResourceBarrier(1, &sobarr); - - UINT zeroes[4] = {0, 0, 0, 0}; - m_DebugList->ClearUnorderedAccessViewUint( - GetGPUHandle(STREAM_OUT_UAV), GetUAVClearHandle(STREAM_OUT_UAV), m_SOBuffer, zeroes, 0, NULL); - - m_DebugList->Close(); - - ID3D12CommandList *l = m_DebugList; - m_WrappedDevice->GetQueue()->ExecuteCommandLists(1, &l); - m_WrappedDevice->GPUSync(); - m_DebugAlloc->Reset(); - - SAFE_RELEASE(pipe); - - byte *byteData = NULL; - D3D12_RANGE range = {0, (SIZE_T)m_SOBufferSize}; - hr = m_SOStagingBuffer->Map(0, &range, (void **)&byteData); - if(FAILED(hr)) - { - RDCERR("Failed to map sobuffer HRESULT: %s", ToStr(hr).c_str()); - SAFE_RELEASE(idxBuf); - SAFE_RELEASE(soSig); - return; - } - - range.End = 0; - - uint64_t numBytesWritten = *(uint64_t *)byteData; - - if(numBytesWritten == 0) - { - m_PostVSData[eventId] = D3D12PostVSData(); - SAFE_RELEASE(idxBuf); - SAFE_RELEASE(soSig); - return; - } - - // skip past the counter - byteData += 64; - - uint64_t numPrims = numBytesWritten / stride; - - ID3D12Resource *vsoutBuffer = NULL; - - { - D3D12_RESOURCE_DESC vertBufDesc; - vertBufDesc.Alignment = 0; - vertBufDesc.DepthOrArraySize = 1; - vertBufDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; - vertBufDesc.Flags = D3D12_RESOURCE_FLAG_NONE; - vertBufDesc.Format = DXGI_FORMAT_UNKNOWN; - vertBufDesc.Height = 1; - vertBufDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; - vertBufDesc.MipLevels = 1; - vertBufDesc.SampleDesc.Count = 1; - vertBufDesc.SampleDesc.Quality = 0; - vertBufDesc.Width = numBytesWritten; - - D3D12_HEAP_PROPERTIES heapProps; - heapProps.Type = D3D12_HEAP_TYPE_UPLOAD; - heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; - heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; - heapProps.CreationNodeMask = 1; - heapProps.VisibleNodeMask = 1; - - hr = m_WrappedDevice->CreateCommittedResource(&heapProps, D3D12_HEAP_FLAG_NONE, &vertBufDesc, - D3D12_RESOURCE_STATE_GENERIC_READ, NULL, - __uuidof(ID3D12Resource), (void **)&vsoutBuffer); - RDCASSERTEQUAL(hr, S_OK); - - if(vsoutBuffer) - { - SetObjName(vsoutBuffer, StringFormat::Fmt("PostVS vsoutBuffer for %u", eventId)); - FillBuffer(vsoutBuffer, 0, byteData, (size_t)numBytesWritten); - } - } - - float nearp = 0.1f; - float farp = 100.0f; - - Vec4f *pos0 = (Vec4f *)byteData; - - bool found = false; - - for(uint64_t i = 1; numPosComponents == 4 && i < numPrims; 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; - } - - m_SOStagingBuffer->Unmap(0, &range); - - m_PostVSData[eventId].vsin.topo = topo; - 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 = - uint32_t(numBytesWritten / RDCMAX(1U, drawcall->numInstances)); - - m_PostVSData[eventId].vsout.idxBuf = NULL; - if(m_PostVSData[eventId].vsout.useIndices && idxBuf) - { - m_PostVSData[eventId].vsout.idxBuf = idxBuf; - m_PostVSData[eventId].vsout.idxFmt = - rs.ibuffer.bytewidth == 2 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT; - } - - m_PostVSData[eventId].vsout.hasPosOut = posidx >= 0; - - m_PostVSData[eventId].vsout.topo = topo; - } - else - { - // empty vertex output signature - m_PostVSData[eventId].vsin.topo = topo; - m_PostVSData[eventId].vsout.buf = NULL; - m_PostVSData[eventId].vsout.instStride = 0; - m_PostVSData[eventId].vsout.vertStride = 0; - m_PostVSData[eventId].vsout.nearPlane = 0.0f; - m_PostVSData[eventId].vsout.farPlane = 0.0f; - m_PostVSData[eventId].vsout.useIndices = false; - m_PostVSData[eventId].vsout.hasPosOut = false; - m_PostVSData[eventId].vsout.idxBuf = NULL; - - m_PostVSData[eventId].vsout.topo = topo; - } - - if(dxbcGS || dxbcDS) - { - stride = 0; - posidx = -1; - numPosComponents = 0; - - DXBC::DXBCFile *lastShader = dxbcGS; - if(dxbcDS) - lastShader = dxbcDS; - - sodecls.clear(); - for(const SigParameter &sign : lastShader->m_OutputSig) - { - D3D12_SO_DECLARATION_ENTRY decl; - - // for now, skip streams that aren't stream 0 - if(sign.stream != 0) - continue; - - decl.Stream = 0; - decl.OutputSlot = 0; - - decl.SemanticName = sign.semanticName.c_str(); - decl.SemanticIndex = sign.semanticIndex; - decl.StartComponent = 0; - decl.ComponentCount = sign.compCount & 0xff; - - if(sign.systemValue == ShaderBuiltin::Position) - { - posidx = (int)sodecls.size(); - numPosComponents = decl.ComponentCount = 4; - } - - stride += decl.ComponentCount * sizeof(float); - sodecls.push_back(decl); - } - - // shift position attribute up to first, keeping order otherwise - // the same - if(posidx > 0) - { - D3D12_SO_DECLARATION_ENTRY pos = sodecls[posidx]; - sodecls.erase(sodecls.begin() + posidx); - sodecls.insert(sodecls.begin(), pos); - } - - // enable the other shader stages again - if(origPSO->DS()) - psoDesc.DS = origPSO->DS()->GetDesc(); - if(origPSO->HS()) - psoDesc.HS = origPSO->HS()->GetDesc(); - if(origPSO->GS()) - psoDesc.GS = origPSO->GS()->GetDesc(); - - // configure new SO declarations - psoDesc.StreamOutput.NumEntries = (UINT)sodecls.size(); - psoDesc.StreamOutput.pSODeclaration = &sodecls[0]; - psoDesc.StreamOutput.NumStrides = 1; - psoDesc.StreamOutput.pBufferStrides = &stride; - - // we're using the same topology this time - psoDesc.PrimitiveTopologyType = origPSO->graphics->PrimitiveTopologyType; - - ID3D12PipelineState *pipe = NULL; - hr = m_WrappedDevice->CreateGraphicsPipelineState(&psoDesc, __uuidof(ID3D12PipelineState), - (void **)&pipe); - if(FAILED(hr)) - { - RDCERR("Couldn't create patched graphics pipeline: HRESULT: %s", ToStr(hr).c_str()); - SAFE_RELEASE(soSig); - return; - } - - D3D12_STREAM_OUTPUT_BUFFER_VIEW view; - - view.BufferFilledSizeLocation = m_SOBuffer->GetGPUVirtualAddress(); - view.BufferLocation = m_SOBuffer->GetGPUVirtualAddress() + 64; - view.SizeInBytes = m_SOBufferSize; - // draws with multiple instances 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->numInstances > 1) - { - m_DebugList->Reset(m_DebugAlloc, NULL); - - rs.ApplyState(m_DebugList); - - m_DebugList->SetPipelineState(pipe); - - if(soSig) - { - m_DebugList->SetGraphicsRootSignature(soSig); - rs.ApplyGraphicsRootElements(m_DebugList); - } - - view.BufferFilledSizeLocation = m_SOBuffer->GetGPUVirtualAddress(); - view.BufferLocation = m_SOBuffer->GetGPUVirtualAddress() + 64; - view.SizeInBytes = m_SOBufferSize; - - // do a dummy draw to make sure we have enough space in the output buffer - m_DebugList->SOSetTargets(0, 1, &view); - - m_DebugList->BeginQuery(m_SOQueryHeap, D3D12_QUERY_TYPE_SO_STATISTICS_STREAM0, 0); - - // because the result is expanded we don't have to remap index buffers or anything - if(drawcall->flags & DrawFlags::UseIBuffer) - { - m_DebugList->DrawIndexedInstanced(drawcall->numIndices, drawcall->numInstances, - drawcall->indexOffset, drawcall->baseVertex, - drawcall->instanceOffset); - } - else - { - m_DebugList->DrawInstanced(drawcall->numIndices, drawcall->numInstances, - drawcall->vertexOffset, drawcall->instanceOffset); - } - - m_DebugList->EndQuery(m_SOQueryHeap, D3D12_QUERY_TYPE_SO_STATISTICS_STREAM0, 0); - - m_DebugList->ResolveQueryData(m_SOQueryHeap, D3D12_QUERY_TYPE_SO_STATISTICS_STREAM0, 0, 1, - m_SOStagingBuffer, 0); - - m_DebugList->Close(); - - ID3D12CommandList *l = m_DebugList; - m_WrappedDevice->GetQueue()->ExecuteCommandLists(1, &l); - m_WrappedDevice->GPUSync(); - - // check that things are OK, and resize up if needed - D3D12_RANGE range; - range.Begin = 0; - range.End = (SIZE_T)sizeof(D3D12_QUERY_DATA_SO_STATISTICS); - - D3D12_QUERY_DATA_SO_STATISTICS *data; - hr = m_SOStagingBuffer->Map(0, &range, (void **)&data); - - D3D12_QUERY_DATA_SO_STATISTICS result = *data; - - range.End = 0; - m_SOStagingBuffer->Unmap(0, &range); - - if(m_SOBufferSize < data->PrimitivesStorageNeeded * 3 * stride) - { - uint64_t oldSize = m_SOBufferSize; - while(m_SOBufferSize < data->PrimitivesStorageNeeded * 3 * stride) - m_SOBufferSize *= 2; - RDCWARN("Resizing stream-out buffer from %llu to %llu for output", oldSize, m_SOBufferSize); - CreateSOBuffers(); - } - - view.BufferFilledSizeLocation = m_SOBuffer->GetGPUVirtualAddress(); - view.BufferLocation = m_SOBuffer->GetGPUVirtualAddress() + 64; - view.SizeInBytes = m_SOBufferSize; - - m_DebugAlloc->Reset(); - - // now do the actual stream out - m_DebugList->Reset(m_DebugAlloc, NULL); - - // first need to reset the counter byte values which may have either been written to above, or - // are newly created - { - D3D12_RESOURCE_BARRIER sobarr = {}; - sobarr.Transition.pResource = m_SOBuffer; - sobarr.Transition.StateBefore = D3D12_RESOURCE_STATE_STREAM_OUT; - sobarr.Transition.StateAfter = D3D12_RESOURCE_STATE_UNORDERED_ACCESS; - - m_DebugList->ResourceBarrier(1, &sobarr); - - D3D12_UNORDERED_ACCESS_VIEW_DESC counterDesc = {}; - counterDesc.ViewDimension = D3D12_UAV_DIMENSION_BUFFER; - counterDesc.Format = DXGI_FORMAT_R32_UINT; - counterDesc.Buffer.FirstElement = 0; - counterDesc.Buffer.NumElements = 4; - - UINT zeroes[4] = {0, 0, 0, 0}; - m_DebugList->ClearUnorderedAccessViewUint(GetGPUHandle(STREAM_OUT_UAV), - GetUAVClearHandle(STREAM_OUT_UAV), m_SOBuffer, - zeroes, 0, NULL); - - std::swap(sobarr.Transition.StateBefore, sobarr.Transition.StateAfter); - m_DebugList->ResourceBarrier(1, &sobarr); - } - - rs.ApplyState(m_DebugList); - - m_DebugList->SetPipelineState(pipe); - - if(soSig) - { - m_DebugList->SetGraphicsRootSignature(soSig); - rs.ApplyGraphicsRootElements(m_DebugList); - } - - // reserve space for enough 'buffer filled size' locations - view.BufferLocation = m_SOBuffer->GetGPUVirtualAddress() + - AlignUp(uint64_t(drawcall->numInstances * sizeof(UINT64)), 64ULL); - - // 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++) - { - if(drawcall->flags & DrawFlags::UseIBuffer) - { - view.BufferFilledSizeLocation = - m_SOBuffer->GetGPUVirtualAddress() + (inst - 1) * sizeof(UINT64); - m_DebugList->SOSetTargets(0, 1, &view); - m_DebugList->DrawIndexedInstanced(drawcall->numIndices, inst, drawcall->indexOffset, - drawcall->baseVertex, drawcall->instanceOffset); - } - else - { - view.BufferFilledSizeLocation = - m_SOBuffer->GetGPUVirtualAddress() + (inst - 1) * sizeof(UINT64); - m_DebugList->SOSetTargets(0, 1, &view); - m_DebugList->DrawInstanced(drawcall->numIndices, inst, drawcall->vertexOffset, - drawcall->instanceOffset); - } - } - - m_DebugList->Close(); - - l = m_DebugList; - m_WrappedDevice->GetQueue()->ExecuteCommandLists(1, &l); - m_WrappedDevice->GPUSync(); - - // the last draw will have written the actual data we want into the buffer - } - else - { - // this only loops if we find from a query that we need to resize up - while(true) - { - m_DebugList->Reset(m_DebugAlloc, NULL); - - rs.ApplyState(m_DebugList); - - m_DebugList->SetPipelineState(pipe); - - if(soSig) - { - m_DebugList->SetGraphicsRootSignature(soSig); - rs.ApplyGraphicsRootElements(m_DebugList); - } - - view.BufferFilledSizeLocation = m_SOBuffer->GetGPUVirtualAddress(); - view.BufferLocation = m_SOBuffer->GetGPUVirtualAddress() + 64; - view.SizeInBytes = m_SOBufferSize; - - m_DebugList->SOSetTargets(0, 1, &view); - - m_DebugList->BeginQuery(m_SOQueryHeap, D3D12_QUERY_TYPE_SO_STATISTICS_STREAM0, 0); - - // because the result is expanded we don't have to remap index buffers or anything - if(drawcall->flags & DrawFlags::UseIBuffer) - { - m_DebugList->DrawIndexedInstanced(drawcall->numIndices, drawcall->numInstances, - drawcall->indexOffset, drawcall->baseVertex, - drawcall->instanceOffset); - } - else - { - m_DebugList->DrawInstanced(drawcall->numIndices, drawcall->numInstances, - drawcall->vertexOffset, drawcall->instanceOffset); - } - - m_DebugList->EndQuery(m_SOQueryHeap, D3D12_QUERY_TYPE_SO_STATISTICS_STREAM0, 0); - - m_DebugList->ResolveQueryData(m_SOQueryHeap, D3D12_QUERY_TYPE_SO_STATISTICS_STREAM0, 0, 1, - m_SOStagingBuffer, 0); - - m_DebugList->Close(); - - ID3D12CommandList *l = m_DebugList; - m_WrappedDevice->GetQueue()->ExecuteCommandLists(1, &l); - m_WrappedDevice->GPUSync(); - - // check that things are OK, and resize up if needed - D3D12_RANGE range; - range.Begin = 0; - range.End = (SIZE_T)sizeof(D3D12_QUERY_DATA_SO_STATISTICS); - - D3D12_QUERY_DATA_SO_STATISTICS *data; - hr = m_SOStagingBuffer->Map(0, &range, (void **)&data); - - if(m_SOBufferSize < data->PrimitivesStorageNeeded * 3 * stride) - { - uint64_t oldSize = m_SOBufferSize; - while(m_SOBufferSize < data->PrimitivesStorageNeeded * 3 * stride) - m_SOBufferSize *= 2; - RDCWARN("Resizing stream-out buffer from %llu to %llu for output", oldSize, m_SOBufferSize); - CreateSOBuffers(); - - continue; - } - - range.End = 0; - m_SOStagingBuffer->Unmap(0, &range); - - m_DebugAlloc->Reset(); - - break; - } - } - - m_DebugList->Reset(m_DebugAlloc, NULL); - - D3D12_RESOURCE_BARRIER sobarr = {}; - sobarr.Transition.pResource = m_SOBuffer; - sobarr.Transition.StateBefore = D3D12_RESOURCE_STATE_STREAM_OUT; - sobarr.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_SOURCE; - - m_DebugList->ResourceBarrier(1, &sobarr); - - m_DebugList->CopyResource(m_SOStagingBuffer, m_SOBuffer); - - // we're done with this after the copy, so we can discard it and reset - // the counter for the next stream-out - sobarr.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_SOURCE; - sobarr.Transition.StateAfter = D3D12_RESOURCE_STATE_UNORDERED_ACCESS; - m_DebugList->DiscardResource(m_SOBuffer, NULL); - m_DebugList->ResourceBarrier(1, &sobarr); - - D3D12_UNORDERED_ACCESS_VIEW_DESC counterDesc = {}; - counterDesc.ViewDimension = D3D12_UAV_DIMENSION_BUFFER; - counterDesc.Format = DXGI_FORMAT_R32_UINT; - counterDesc.Buffer.FirstElement = 0; - counterDesc.Buffer.NumElements = 4; - - UINT zeroes[4] = {0, 0, 0, 0}; - m_DebugList->ClearUnorderedAccessViewUint( - GetGPUHandle(STREAM_OUT_UAV), GetUAVClearHandle(STREAM_OUT_UAV), m_SOBuffer, zeroes, 0, NULL); - - m_DebugList->Close(); - - ID3D12CommandList *l = m_DebugList; - m_WrappedDevice->GetQueue()->ExecuteCommandLists(1, &l); - m_WrappedDevice->GPUSync(); - m_DebugAlloc->Reset(); - - SAFE_RELEASE(pipe); - - byte *byteData = NULL; - D3D12_RANGE range = {0, (SIZE_T)m_SOBufferSize}; - hr = m_SOStagingBuffer->Map(0, &range, (void **)&byteData); - if(FAILED(hr)) - { - RDCERR("Failed to map sobuffer HRESULT: %s", ToStr(hr).c_str()); - SAFE_RELEASE(soSig); - return; - } - - range.End = 0; - - uint64_t *counters = (uint64_t *)byteData; - - uint64_t numBytesWritten = 0; - std::vector instData; - if(drawcall->numInstances > 1) - { - uint64_t prevByteCount = 0; - - for(uint32_t inst = 0; inst < drawcall->numInstances; inst++) - { - uint64_t byteCount = counters[inst]; - - D3D12PostVSData::InstData d; - d.numVerts = uint32_t((byteCount - prevByteCount) / stride); - d.bufOffset = prevByteCount; - prevByteCount = byteCount; - - instData.push_back(d); - } - - numBytesWritten = prevByteCount; - } - else - { - numBytesWritten = counters[0]; - } - - if(numBytesWritten == 0) - { - SAFE_RELEASE(soSig); - return; - } - - // skip past the counter(s) - byteData += (view.BufferLocation - m_SOBuffer->GetGPUVirtualAddress()); - - uint64_t numVerts = numBytesWritten / stride; - - ID3D12Resource *gsoutBuffer = NULL; - - { - D3D12_RESOURCE_DESC vertBufDesc; - vertBufDesc.Alignment = 0; - vertBufDesc.DepthOrArraySize = 1; - vertBufDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; - vertBufDesc.Flags = D3D12_RESOURCE_FLAG_NONE; - vertBufDesc.Format = DXGI_FORMAT_UNKNOWN; - vertBufDesc.Height = 1; - vertBufDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; - vertBufDesc.MipLevels = 1; - vertBufDesc.SampleDesc.Count = 1; - vertBufDesc.SampleDesc.Quality = 0; - vertBufDesc.Width = numBytesWritten; - - D3D12_HEAP_PROPERTIES heapProps; - heapProps.Type = D3D12_HEAP_TYPE_UPLOAD; - heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; - heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; - heapProps.CreationNodeMask = 1; - heapProps.VisibleNodeMask = 1; - - hr = m_WrappedDevice->CreateCommittedResource(&heapProps, D3D12_HEAP_FLAG_NONE, &vertBufDesc, - D3D12_RESOURCE_STATE_GENERIC_READ, NULL, - __uuidof(ID3D12Resource), (void **)&gsoutBuffer); - RDCASSERTEQUAL(hr, S_OK); - - if(gsoutBuffer) - { - SetObjName(gsoutBuffer, StringFormat::Fmt("PostVS gsoutBuffer for %u", eventId)); - FillBuffer(gsoutBuffer, 0, byteData, (size_t)numBytesWritten); - } - } - - float nearp = 0.1f; - float farp = 100.0f; - - Vec4f *pos0 = (Vec4f *)byteData; - - bool found = false; - - for(UINT64 i = 1; numPosComponents == 4 && i < 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; - } - - m_SOStagingBuffer->Unmap(0, &range); - - m_PostVSData[eventId].gsout.buf = gsoutBuffer; - m_PostVSData[eventId].gsout.instStride = 0; - if(drawcall->flags & DrawFlags::Instanced) - m_PostVSData[eventId].gsout.instStride = - uint32_t(numBytesWritten / RDCMAX(1U, drawcall->numInstances)); - 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 = NULL; - - topo = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST; - - if(lastShader == dxbcGS) - { - for(size_t i = 0; i < dxbcGS->GetNumDeclarations(); i++) - { - const DXBC::ASMDecl &decl = dxbcGS->GetDeclaration(i); - - if(decl.declaration == DXBC::OPCODE_DCL_GS_OUTPUT_PRIMITIVE_TOPOLOGY) - { - topo = decl.outTopology; - break; - } - } - } - else if(lastShader == dxbcDS) - { - for(size_t i = 0; i < dxbcDS->GetNumDeclarations(); i++) - { - const DXBC::ASMDecl &decl = dxbcDS->GetDeclaration(i); - - if(decl.declaration == DXBC::OPCODE_DCL_TESS_DOMAIN) - { - if(decl.domain == DXBC::DOMAIN_ISOLINE) - topo = D3D_PRIMITIVE_TOPOLOGY_LINELIST; - else - topo = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST; - break; - } - } - } - - m_PostVSData[eventId].gsout.topo = topo; - - // streamout expands strips unfortunately - if(topo == D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP) - m_PostVSData[eventId].gsout.topo = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST; - else if(topo == D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP) - m_PostVSData[eventId].gsout.topo = D3D11_PRIMITIVE_TOPOLOGY_LINELIST; - else if(topo == D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP_ADJ) - m_PostVSData[eventId].gsout.topo = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ; - else if(topo == D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP_ADJ) - m_PostVSData[eventId].gsout.topo = D3D11_PRIMITIVE_TOPOLOGY_LINELIST_ADJ; - - m_PostVSData[eventId].gsout.numVerts = (uint32_t)numVerts; - - if(drawcall->flags & DrawFlags::Instanced) - m_PostVSData[eventId].gsout.numVerts /= RDCMAX(1U, drawcall->numInstances); - - m_PostVSData[eventId].gsout.instData = instData; - } - - SAFE_RELEASE(soSig); -} - -MeshFormat D3D12DebugManager::GetPostVSBuffers(uint32_t eventId, uint32_t instID, MeshDataStage stage) -{ - // go through any aliasing - if(m_PostVSAlias.find(eventId) != m_PostVSAlias.end()) - eventId = m_PostVSAlias[eventId]; - - D3D12PostVSData postvs; - RDCEraseEl(postvs); - - if(m_PostVSData.find(eventId) != m_PostVSData.end()) - postvs = m_PostVSData[eventId]; - - const D3D12PostVSData::StageData &s = postvs.GetStage(stage); - - MeshFormat ret; - - if(s.useIndices && s.idxBuf != NULL) - { - ret.indexResourceId = GetResID(s.idxBuf); - ret.indexByteStride = s.idxFmt == DXGI_FORMAT_R16_UINT ? 2 : 4; - } - else - { - ret.indexResourceId = ResourceId(); - ret.indexByteStride = 0; - } - ret.indexByteOffset = 0; - ret.baseVertex = 0; - - if(s.buf != NULL) - ret.vertexResourceId = GetResID(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 = MakePrimitiveTopology(s.topo); - ret.numIndices = s.numVerts; - - ret.unproject = s.hasPosOut; - ret.nearPlane = s.nearPlane; - ret.farPlane = s.farPlane; - - if(instID < s.instData.size()) - { - D3D12PostVSData::InstData inst = s.instData[instID]; - - ret.vertexByteOffset = inst.bufOffset; - ret.numIndices = inst.numVerts; - } - - return ret; -} - void D3D12DebugManager::RenderHighlightBox(float w, float h, float scale) { OutputWindow &outw = m_OutputWindows[m_CurrentOutputWindow]; diff --git a/renderdoc/driver/d3d12/d3d12_postvs.cpp b/renderdoc/driver/d3d12/d3d12_postvs.cpp new file mode 100644 index 000000000..91e094aa9 --- /dev/null +++ b/renderdoc/driver/d3d12/d3d12_postvs.cpp @@ -0,0 +1,1259 @@ +/****************************************************************************** + * 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 "driver/dxgi/dxgi_common.h" +#include "strings/string_utils.h" +#include "d3d12_command_list.h" +#include "d3d12_command_queue.h" +#include "d3d12_debug.h" +#include "d3d12_device.h" + +void D3D12DebugManager::ClearPostVSCache() +{ + for(auto it = m_PostVSData.begin(); it != m_PostVSData.end(); ++it) + { + SAFE_RELEASE(it->second.vsout.buf); + SAFE_RELEASE(it->second.vsout.idxBuf); + SAFE_RELEASE(it->second.gsout.buf); + SAFE_RELEASE(it->second.gsout.idxBuf); + } + + m_PostVSData.clear(); +} + +void D3D12DebugManager::InitPostVSBuffers(uint32_t eventId) +{ + // go through any aliasing + if(m_PostVSAlias.find(eventId) != m_PostVSAlias.end()) + eventId = m_PostVSAlias[eventId]; + + if(m_PostVSData.find(eventId) != m_PostVSData.end()) + return; + + D3D12CommandData *cmd = m_WrappedDevice->GetQueue()->GetCommandData(); + const D3D12RenderState &rs = cmd->m_RenderState; + + if(rs.pipe == ResourceId()) + return; + + WrappedID3D12PipelineState *origPSO = + m_WrappedDevice->GetResourceManager()->GetCurrentAs(rs.pipe); + + if(!origPSO->IsGraphics()) + return; + + D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = origPSO->GetGraphicsDesc(); + + if(psoDesc.VS.BytecodeLength == 0) + return; + + WrappedID3D12Shader *vs = origPSO->VS(); + + D3D_PRIMITIVE_TOPOLOGY topo = rs.topo; + + const DrawcallDescription *drawcall = m_WrappedDevice->GetDrawcall(eventId); + + if(drawcall->numIndices == 0) + return; + + DXBC::DXBCFile *dxbcVS = vs->GetDXBC(); + + RDCASSERT(dxbcVS); + + DXBC::DXBCFile *dxbcGS = NULL; + + WrappedID3D12Shader *gs = origPSO->GS(); + + if(gs) + { + dxbcGS = gs->GetDXBC(); + + RDCASSERT(dxbcGS); + } + + DXBC::DXBCFile *dxbcDS = NULL; + + WrappedID3D12Shader *ds = origPSO->DS(); + + if(ds) + { + dxbcDS = ds->GetDXBC(); + + RDCASSERT(dxbcDS); + } + + ID3D12RootSignature *soSig = NULL; + + HRESULT hr = S_OK; + + { + WrappedID3D12RootSignature *sig = + m_WrappedDevice->GetResourceManager()->GetCurrentAs( + rs.graphics.rootsig); + + D3D12RootSignature rootsig = sig->sig; + + // create a root signature that allows stream out, if necessary + if((rootsig.Flags & D3D12_ROOT_SIGNATURE_FLAG_ALLOW_STREAM_OUTPUT) == 0) + { + rootsig.Flags |= D3D12_ROOT_SIGNATURE_FLAG_ALLOW_STREAM_OUTPUT; + + ID3DBlob *blob = MakeRootSig(rootsig); + + hr = m_WrappedDevice->CreateRootSignature(0, blob->GetBufferPointer(), blob->GetBufferSize(), + __uuidof(ID3D12RootSignature), (void **)&soSig); + if(FAILED(hr)) + { + RDCERR("Couldn't enable stream-out in root signature: HRESULT: %s", ToStr(hr).c_str()); + return; + } + + SAFE_RELEASE(blob); + } + } + + vector sodecls; + + UINT stride = 0; + int posidx = -1; + int numPosComponents = 0; + + if(!dxbcVS->m_OutputSig.empty()) + { + for(const SigParameter &sign : dxbcVS->m_OutputSig) + { + D3D12_SO_DECLARATION_ENTRY decl; + + decl.Stream = 0; + decl.OutputSlot = 0; + + decl.SemanticName = sign.semanticName.c_str(); + decl.SemanticIndex = sign.semanticIndex; + decl.StartComponent = 0; + decl.ComponentCount = sign.compCount & 0xff; + + if(sign.systemValue == ShaderBuiltin::Position) + { + posidx = (int)sodecls.size(); + numPosComponents = decl.ComponentCount = 4; + } + + stride += decl.ComponentCount * sizeof(float); + sodecls.push_back(decl); + } + + if(stride == 0) + { + RDCERR("Didn't get valid stride! Setting to 4 bytes"); + stride = 4; + } + + // shift position attribute up to first, keeping order otherwise + // the same + if(posidx > 0) + { + D3D12_SO_DECLARATION_ENTRY pos = sodecls[posidx]; + sodecls.erase(sodecls.begin() + posidx); + sodecls.insert(sodecls.begin(), pos); + } + + // set up stream output entries and buffers + psoDesc.StreamOutput.NumEntries = (UINT)sodecls.size(); + psoDesc.StreamOutput.pSODeclaration = &sodecls[0]; + psoDesc.StreamOutput.NumStrides = 1; + psoDesc.StreamOutput.pBufferStrides = &stride; + psoDesc.StreamOutput.RasterizedStream = D3D12_SO_NO_RASTERIZED_STREAM; + + // disable all other shader stages + psoDesc.HS.BytecodeLength = 0; + psoDesc.HS.pShaderBytecode = NULL; + psoDesc.DS.BytecodeLength = 0; + psoDesc.DS.pShaderBytecode = NULL; + psoDesc.GS.BytecodeLength = 0; + psoDesc.GS.pShaderBytecode = NULL; + psoDesc.PS.BytecodeLength = 0; + psoDesc.PS.pShaderBytecode = NULL; + + // disable any rasterization/use of output targets + psoDesc.DepthStencilState.DepthEnable = FALSE; + psoDesc.DepthStencilState.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ZERO; + psoDesc.DepthStencilState.StencilEnable = FALSE; + + if(soSig) + psoDesc.pRootSignature = soSig; + + // render as points + psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_POINT; + + // disable outputs + psoDesc.NumRenderTargets = 0; + RDCEraseEl(psoDesc.RTVFormats); + psoDesc.DSVFormat = DXGI_FORMAT_UNKNOWN; + + ID3D12PipelineState *pipe = NULL; + hr = m_WrappedDevice->CreateGraphicsPipelineState(&psoDesc, __uuidof(ID3D12PipelineState), + (void **)&pipe); + if(FAILED(hr)) + { + RDCERR("Couldn't create patched graphics pipeline: HRESULT: %s", ToStr(hr).c_str()); + SAFE_RELEASE(soSig); + return; + } + + ID3D12Resource *idxBuf = NULL; + + bool recreate = false; + uint64_t outputSize = uint64_t(drawcall->numIndices) * drawcall->numInstances * stride; + + if(m_SOBufferSize < outputSize) + { + uint64_t oldSize = m_SOBufferSize; + while(m_SOBufferSize < outputSize) + m_SOBufferSize *= 2; + RDCWARN("Resizing stream-out buffer from %llu to %llu for output data", oldSize, + m_SOBufferSize); + recreate = true; + } + + if(!(drawcall->flags & DrawFlags::UseIBuffer)) + { + if(recreate) + { + m_WrappedDevice->GPUSync(); + + CreateSOBuffers(); + } + + m_DebugList->Reset(m_DebugAlloc, NULL); + + rs.ApplyState(m_DebugList); + + m_DebugList->SetPipelineState(pipe); + + if(soSig) + { + m_DebugList->SetGraphicsRootSignature(soSig); + rs.ApplyGraphicsRootElements(m_DebugList); + } + + D3D12_STREAM_OUTPUT_BUFFER_VIEW view; + view.BufferFilledSizeLocation = m_SOBuffer->GetGPUVirtualAddress(); + view.BufferLocation = m_SOBuffer->GetGPUVirtualAddress() + 64; + view.SizeInBytes = m_SOBufferSize; + m_DebugList->SOSetTargets(0, 1, &view); + + m_DebugList->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_POINTLIST); + m_DebugList->DrawInstanced(drawcall->numIndices, drawcall->numInstances, + drawcall->vertexOffset, drawcall->instanceOffset); + } + else // drawcall is indexed + { + bytebuf idxdata; + GetBufferData(rs.ibuffer.buf, rs.ibuffer.offs + drawcall->indexOffset * rs.ibuffer.bytewidth, + RDCMIN(drawcall->numIndices * rs.ibuffer.bytewidth, rs.ibuffer.size), idxdata); + + vector indices; + + 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() / rs.ibuffer.bytewidth), drawcall->numIndices); + + uint32_t idxclamp = 0; + if(drawcall->baseVertex < 0) + idxclamp = uint32_t(-drawcall->baseVertex); + + // grab all unique vertex indices referenced + for(uint32_t i = 0; i < numIndices; i++) + { + uint32_t i32 = rs.ibuffer.bytewidth == 2 ? uint32_t(idx16[i]) : idx32[i]; + + // apply baseVertex but clamp to 0 (don't allow index to become negative) + if(i32 < idxclamp) + i32 = 0; + else if(drawcall->baseVertex < 0) + i32 -= idxclamp; + else if(drawcall->baseVertex > 0) + i32 += drawcall->baseVertex; + + 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 indexRemap; + for(size_t i = 0; i < indices.size(); i++) + { + // by definition, this index will only appear once in indices[] + indexRemap[indices[i]] = i; + } + + if(m_SOBufferSize / sizeof(Vec4f) < indices.size() * sizeof(uint32_t)) + { + uint64_t oldSize = m_SOBufferSize; + while(m_SOBufferSize / sizeof(Vec4f) < indices.size() * sizeof(uint32_t)) + m_SOBufferSize *= 2; + RDCWARN("Resizing stream-out buffer from %llu to %llu for indices", oldSize, m_SOBufferSize); + recreate = true; + } + + if(recreate) + { + m_WrappedDevice->GPUSync(); + + CreateSOBuffers(); + } + + FillBuffer(m_SOPatchedIndexBuffer, 0, &indices[0], indices.size() * sizeof(uint32_t)); + + D3D12_INDEX_BUFFER_VIEW patchedIB; + + patchedIB.BufferLocation = m_SOPatchedIndexBuffer->GetGPUVirtualAddress(); + patchedIB.Format = DXGI_FORMAT_R32_UINT; + patchedIB.SizeInBytes = UINT(indices.size() * sizeof(uint32_t)); + + m_DebugList->Reset(m_DebugAlloc, NULL); + + rs.ApplyState(m_DebugList); + + m_DebugList->SetPipelineState(pipe); + + m_DebugList->IASetIndexBuffer(&patchedIB); + + if(soSig) + { + m_DebugList->SetGraphicsRootSignature(soSig); + rs.ApplyGraphicsRootElements(m_DebugList); + } + + D3D12_STREAM_OUTPUT_BUFFER_VIEW view; + view.BufferFilledSizeLocation = m_SOBuffer->GetGPUVirtualAddress(); + view.BufferLocation = m_SOBuffer->GetGPUVirtualAddress() + 64; + view.SizeInBytes = m_SOBufferSize; + m_DebugList->SOSetTargets(0, 1, &view); + + m_DebugList->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_POINTLIST); + + m_DebugList->DrawIndexedInstanced((UINT)indices.size(), drawcall->numInstances, 0, 0, + drawcall->instanceOffset); + + uint32_t stripCutValue = 0; + if(psoDesc.IBStripCutValue == D3D12_INDEX_BUFFER_STRIP_CUT_VALUE_0xFFFF) + stripCutValue = 0xffff; + else if(psoDesc.IBStripCutValue == D3D12_INDEX_BUFFER_STRIP_CUT_VALUE_0xFFFFFFFF) + stripCutValue = 0xffffffff; + + // rebase existing index buffer to point to the right elements in our stream-out'd + // vertex buffer + for(uint32_t i = 0; i < numIndices; i++) + { + uint32_t i32 = rs.ibuffer.bytewidth == 2 ? uint32_t(idx16[i]) : idx32[i]; + + // preserve primitive restart indices + if(stripCutValue && i32 == stripCutValue) + continue; + + // apply baseVertex but clamp to 0 (don't allow index to become negative) + if(i32 < idxclamp) + i32 = 0; + else if(drawcall->baseVertex < 0) + i32 -= idxclamp; + else if(drawcall->baseVertex > 0) + i32 += drawcall->baseVertex; + + if(rs.ibuffer.bytewidth == 2) + idx16[i] = uint16_t(indexRemap[i32]); + else + idx32[i] = uint32_t(indexRemap[i32]); + } + + idxBuf = NULL; + + if(!idxdata.empty()) + { + D3D12_RESOURCE_DESC idxBufDesc; + idxBufDesc.Alignment = 0; + idxBufDesc.DepthOrArraySize = 1; + idxBufDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + idxBufDesc.Flags = D3D12_RESOURCE_FLAG_NONE; + idxBufDesc.Format = DXGI_FORMAT_UNKNOWN; + idxBufDesc.Height = 1; + idxBufDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + idxBufDesc.MipLevels = 1; + idxBufDesc.SampleDesc.Count = 1; + idxBufDesc.SampleDesc.Quality = 0; + idxBufDesc.Width = idxdata.size(); + + D3D12_HEAP_PROPERTIES heapProps; + heapProps.Type = D3D12_HEAP_TYPE_UPLOAD; + heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; + heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; + heapProps.CreationNodeMask = 1; + heapProps.VisibleNodeMask = 1; + + hr = m_WrappedDevice->CreateCommittedResource(&heapProps, D3D12_HEAP_FLAG_NONE, &idxBufDesc, + D3D12_RESOURCE_STATE_GENERIC_READ, NULL, + __uuidof(ID3D12Resource), (void **)&idxBuf); + RDCASSERTEQUAL(hr, S_OK); + + SetObjName(idxBuf, StringFormat::Fmt("PostVS idxBuf for %u", eventId)); + + FillBuffer(idxBuf, 0, &idxdata[0], idxdata.size()); + } + } + + D3D12_RESOURCE_BARRIER sobarr = {}; + sobarr.Transition.pResource = m_SOBuffer; + sobarr.Transition.StateBefore = D3D12_RESOURCE_STATE_STREAM_OUT; + sobarr.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_SOURCE; + + m_DebugList->ResourceBarrier(1, &sobarr); + + m_DebugList->CopyResource(m_SOStagingBuffer, m_SOBuffer); + + // we're done with this after the copy, so we can discard it and reset + // the counter for the next stream-out + sobarr.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_SOURCE; + sobarr.Transition.StateAfter = D3D12_RESOURCE_STATE_UNORDERED_ACCESS; + m_DebugList->DiscardResource(m_SOBuffer, NULL); + m_DebugList->ResourceBarrier(1, &sobarr); + + UINT zeroes[4] = {0, 0, 0, 0}; + m_DebugList->ClearUnorderedAccessViewUint( + GetGPUHandle(STREAM_OUT_UAV), GetUAVClearHandle(STREAM_OUT_UAV), m_SOBuffer, zeroes, 0, NULL); + + m_DebugList->Close(); + + ID3D12CommandList *l = m_DebugList; + m_WrappedDevice->GetQueue()->ExecuteCommandLists(1, &l); + m_WrappedDevice->GPUSync(); + m_DebugAlloc->Reset(); + + SAFE_RELEASE(pipe); + + byte *byteData = NULL; + D3D12_RANGE range = {0, (SIZE_T)m_SOBufferSize}; + hr = m_SOStagingBuffer->Map(0, &range, (void **)&byteData); + if(FAILED(hr)) + { + RDCERR("Failed to map sobuffer HRESULT: %s", ToStr(hr).c_str()); + SAFE_RELEASE(idxBuf); + SAFE_RELEASE(soSig); + return; + } + + range.End = 0; + + uint64_t numBytesWritten = *(uint64_t *)byteData; + + if(numBytesWritten == 0) + { + m_PostVSData[eventId] = D3D12PostVSData(); + SAFE_RELEASE(idxBuf); + SAFE_RELEASE(soSig); + return; + } + + // skip past the counter + byteData += 64; + + uint64_t numPrims = numBytesWritten / stride; + + ID3D12Resource *vsoutBuffer = NULL; + + { + D3D12_RESOURCE_DESC vertBufDesc; + vertBufDesc.Alignment = 0; + vertBufDesc.DepthOrArraySize = 1; + vertBufDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + vertBufDesc.Flags = D3D12_RESOURCE_FLAG_NONE; + vertBufDesc.Format = DXGI_FORMAT_UNKNOWN; + vertBufDesc.Height = 1; + vertBufDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + vertBufDesc.MipLevels = 1; + vertBufDesc.SampleDesc.Count = 1; + vertBufDesc.SampleDesc.Quality = 0; + vertBufDesc.Width = numBytesWritten; + + D3D12_HEAP_PROPERTIES heapProps; + heapProps.Type = D3D12_HEAP_TYPE_UPLOAD; + heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; + heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; + heapProps.CreationNodeMask = 1; + heapProps.VisibleNodeMask = 1; + + hr = m_WrappedDevice->CreateCommittedResource(&heapProps, D3D12_HEAP_FLAG_NONE, &vertBufDesc, + D3D12_RESOURCE_STATE_GENERIC_READ, NULL, + __uuidof(ID3D12Resource), (void **)&vsoutBuffer); + RDCASSERTEQUAL(hr, S_OK); + + if(vsoutBuffer) + { + SetObjName(vsoutBuffer, StringFormat::Fmt("PostVS vsoutBuffer for %u", eventId)); + FillBuffer(vsoutBuffer, 0, byteData, (size_t)numBytesWritten); + } + } + + float nearp = 0.1f; + float farp = 100.0f; + + Vec4f *pos0 = (Vec4f *)byteData; + + bool found = false; + + for(uint64_t i = 1; numPosComponents == 4 && i < numPrims; 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; + } + + m_SOStagingBuffer->Unmap(0, &range); + + m_PostVSData[eventId].vsin.topo = topo; + 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 = + uint32_t(numBytesWritten / RDCMAX(1U, drawcall->numInstances)); + + m_PostVSData[eventId].vsout.idxBuf = NULL; + if(m_PostVSData[eventId].vsout.useIndices && idxBuf) + { + m_PostVSData[eventId].vsout.idxBuf = idxBuf; + m_PostVSData[eventId].vsout.idxFmt = + rs.ibuffer.bytewidth == 2 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT; + } + + m_PostVSData[eventId].vsout.hasPosOut = posidx >= 0; + + m_PostVSData[eventId].vsout.topo = topo; + } + else + { + // empty vertex output signature + m_PostVSData[eventId].vsin.topo = topo; + m_PostVSData[eventId].vsout.buf = NULL; + m_PostVSData[eventId].vsout.instStride = 0; + m_PostVSData[eventId].vsout.vertStride = 0; + m_PostVSData[eventId].vsout.nearPlane = 0.0f; + m_PostVSData[eventId].vsout.farPlane = 0.0f; + m_PostVSData[eventId].vsout.useIndices = false; + m_PostVSData[eventId].vsout.hasPosOut = false; + m_PostVSData[eventId].vsout.idxBuf = NULL; + + m_PostVSData[eventId].vsout.topo = topo; + } + + if(dxbcGS || dxbcDS) + { + stride = 0; + posidx = -1; + numPosComponents = 0; + + DXBC::DXBCFile *lastShader = dxbcGS; + if(dxbcDS) + lastShader = dxbcDS; + + sodecls.clear(); + for(const SigParameter &sign : lastShader->m_OutputSig) + { + D3D12_SO_DECLARATION_ENTRY decl; + + // for now, skip streams that aren't stream 0 + if(sign.stream != 0) + continue; + + decl.Stream = 0; + decl.OutputSlot = 0; + + decl.SemanticName = sign.semanticName.c_str(); + decl.SemanticIndex = sign.semanticIndex; + decl.StartComponent = 0; + decl.ComponentCount = sign.compCount & 0xff; + + if(sign.systemValue == ShaderBuiltin::Position) + { + posidx = (int)sodecls.size(); + numPosComponents = decl.ComponentCount = 4; + } + + stride += decl.ComponentCount * sizeof(float); + sodecls.push_back(decl); + } + + // shift position attribute up to first, keeping order otherwise + // the same + if(posidx > 0) + { + D3D12_SO_DECLARATION_ENTRY pos = sodecls[posidx]; + sodecls.erase(sodecls.begin() + posidx); + sodecls.insert(sodecls.begin(), pos); + } + + // enable the other shader stages again + if(origPSO->DS()) + psoDesc.DS = origPSO->DS()->GetDesc(); + if(origPSO->HS()) + psoDesc.HS = origPSO->HS()->GetDesc(); + if(origPSO->GS()) + psoDesc.GS = origPSO->GS()->GetDesc(); + + // configure new SO declarations + psoDesc.StreamOutput.NumEntries = (UINT)sodecls.size(); + psoDesc.StreamOutput.pSODeclaration = &sodecls[0]; + psoDesc.StreamOutput.NumStrides = 1; + psoDesc.StreamOutput.pBufferStrides = &stride; + + // we're using the same topology this time + psoDesc.PrimitiveTopologyType = origPSO->graphics->PrimitiveTopologyType; + + ID3D12PipelineState *pipe = NULL; + hr = m_WrappedDevice->CreateGraphicsPipelineState(&psoDesc, __uuidof(ID3D12PipelineState), + (void **)&pipe); + if(FAILED(hr)) + { + RDCERR("Couldn't create patched graphics pipeline: HRESULT: %s", ToStr(hr).c_str()); + SAFE_RELEASE(soSig); + return; + } + + D3D12_STREAM_OUTPUT_BUFFER_VIEW view; + + view.BufferFilledSizeLocation = m_SOBuffer->GetGPUVirtualAddress(); + view.BufferLocation = m_SOBuffer->GetGPUVirtualAddress() + 64; + view.SizeInBytes = m_SOBufferSize; + // draws with multiple instances 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->numInstances > 1) + { + m_DebugList->Reset(m_DebugAlloc, NULL); + + rs.ApplyState(m_DebugList); + + m_DebugList->SetPipelineState(pipe); + + if(soSig) + { + m_DebugList->SetGraphicsRootSignature(soSig); + rs.ApplyGraphicsRootElements(m_DebugList); + } + + view.BufferFilledSizeLocation = m_SOBuffer->GetGPUVirtualAddress(); + view.BufferLocation = m_SOBuffer->GetGPUVirtualAddress() + 64; + view.SizeInBytes = m_SOBufferSize; + + // do a dummy draw to make sure we have enough space in the output buffer + m_DebugList->SOSetTargets(0, 1, &view); + + m_DebugList->BeginQuery(m_SOQueryHeap, D3D12_QUERY_TYPE_SO_STATISTICS_STREAM0, 0); + + // because the result is expanded we don't have to remap index buffers or anything + if(drawcall->flags & DrawFlags::UseIBuffer) + { + m_DebugList->DrawIndexedInstanced(drawcall->numIndices, drawcall->numInstances, + drawcall->indexOffset, drawcall->baseVertex, + drawcall->instanceOffset); + } + else + { + m_DebugList->DrawInstanced(drawcall->numIndices, drawcall->numInstances, + drawcall->vertexOffset, drawcall->instanceOffset); + } + + m_DebugList->EndQuery(m_SOQueryHeap, D3D12_QUERY_TYPE_SO_STATISTICS_STREAM0, 0); + + m_DebugList->ResolveQueryData(m_SOQueryHeap, D3D12_QUERY_TYPE_SO_STATISTICS_STREAM0, 0, 1, + m_SOStagingBuffer, 0); + + m_DebugList->Close(); + + ID3D12CommandList *l = m_DebugList; + m_WrappedDevice->GetQueue()->ExecuteCommandLists(1, &l); + m_WrappedDevice->GPUSync(); + + // check that things are OK, and resize up if needed + D3D12_RANGE range; + range.Begin = 0; + range.End = (SIZE_T)sizeof(D3D12_QUERY_DATA_SO_STATISTICS); + + D3D12_QUERY_DATA_SO_STATISTICS *data; + hr = m_SOStagingBuffer->Map(0, &range, (void **)&data); + + D3D12_QUERY_DATA_SO_STATISTICS result = *data; + + range.End = 0; + m_SOStagingBuffer->Unmap(0, &range); + + if(m_SOBufferSize < data->PrimitivesStorageNeeded * 3 * stride) + { + uint64_t oldSize = m_SOBufferSize; + while(m_SOBufferSize < data->PrimitivesStorageNeeded * 3 * stride) + m_SOBufferSize *= 2; + RDCWARN("Resizing stream-out buffer from %llu to %llu for output", oldSize, m_SOBufferSize); + CreateSOBuffers(); + } + + view.BufferFilledSizeLocation = m_SOBuffer->GetGPUVirtualAddress(); + view.BufferLocation = m_SOBuffer->GetGPUVirtualAddress() + 64; + view.SizeInBytes = m_SOBufferSize; + + m_DebugAlloc->Reset(); + + // now do the actual stream out + m_DebugList->Reset(m_DebugAlloc, NULL); + + // first need to reset the counter byte values which may have either been written to above, or + // are newly created + { + D3D12_RESOURCE_BARRIER sobarr = {}; + sobarr.Transition.pResource = m_SOBuffer; + sobarr.Transition.StateBefore = D3D12_RESOURCE_STATE_STREAM_OUT; + sobarr.Transition.StateAfter = D3D12_RESOURCE_STATE_UNORDERED_ACCESS; + + m_DebugList->ResourceBarrier(1, &sobarr); + + D3D12_UNORDERED_ACCESS_VIEW_DESC counterDesc = {}; + counterDesc.ViewDimension = D3D12_UAV_DIMENSION_BUFFER; + counterDesc.Format = DXGI_FORMAT_R32_UINT; + counterDesc.Buffer.FirstElement = 0; + counterDesc.Buffer.NumElements = 4; + + UINT zeroes[4] = {0, 0, 0, 0}; + m_DebugList->ClearUnorderedAccessViewUint(GetGPUHandle(STREAM_OUT_UAV), + GetUAVClearHandle(STREAM_OUT_UAV), m_SOBuffer, + zeroes, 0, NULL); + + std::swap(sobarr.Transition.StateBefore, sobarr.Transition.StateAfter); + m_DebugList->ResourceBarrier(1, &sobarr); + } + + rs.ApplyState(m_DebugList); + + m_DebugList->SetPipelineState(pipe); + + if(soSig) + { + m_DebugList->SetGraphicsRootSignature(soSig); + rs.ApplyGraphicsRootElements(m_DebugList); + } + + // reserve space for enough 'buffer filled size' locations + view.BufferLocation = m_SOBuffer->GetGPUVirtualAddress() + + AlignUp(uint64_t(drawcall->numInstances * sizeof(UINT64)), 64ULL); + + // 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++) + { + if(drawcall->flags & DrawFlags::UseIBuffer) + { + view.BufferFilledSizeLocation = + m_SOBuffer->GetGPUVirtualAddress() + (inst - 1) * sizeof(UINT64); + m_DebugList->SOSetTargets(0, 1, &view); + m_DebugList->DrawIndexedInstanced(drawcall->numIndices, inst, drawcall->indexOffset, + drawcall->baseVertex, drawcall->instanceOffset); + } + else + { + view.BufferFilledSizeLocation = + m_SOBuffer->GetGPUVirtualAddress() + (inst - 1) * sizeof(UINT64); + m_DebugList->SOSetTargets(0, 1, &view); + m_DebugList->DrawInstanced(drawcall->numIndices, inst, drawcall->vertexOffset, + drawcall->instanceOffset); + } + } + + m_DebugList->Close(); + + l = m_DebugList; + m_WrappedDevice->GetQueue()->ExecuteCommandLists(1, &l); + m_WrappedDevice->GPUSync(); + + // the last draw will have written the actual data we want into the buffer + } + else + { + // this only loops if we find from a query that we need to resize up + while(true) + { + m_DebugList->Reset(m_DebugAlloc, NULL); + + rs.ApplyState(m_DebugList); + + m_DebugList->SetPipelineState(pipe); + + if(soSig) + { + m_DebugList->SetGraphicsRootSignature(soSig); + rs.ApplyGraphicsRootElements(m_DebugList); + } + + view.BufferFilledSizeLocation = m_SOBuffer->GetGPUVirtualAddress(); + view.BufferLocation = m_SOBuffer->GetGPUVirtualAddress() + 64; + view.SizeInBytes = m_SOBufferSize; + + m_DebugList->SOSetTargets(0, 1, &view); + + m_DebugList->BeginQuery(m_SOQueryHeap, D3D12_QUERY_TYPE_SO_STATISTICS_STREAM0, 0); + + // because the result is expanded we don't have to remap index buffers or anything + if(drawcall->flags & DrawFlags::UseIBuffer) + { + m_DebugList->DrawIndexedInstanced(drawcall->numIndices, drawcall->numInstances, + drawcall->indexOffset, drawcall->baseVertex, + drawcall->instanceOffset); + } + else + { + m_DebugList->DrawInstanced(drawcall->numIndices, drawcall->numInstances, + drawcall->vertexOffset, drawcall->instanceOffset); + } + + m_DebugList->EndQuery(m_SOQueryHeap, D3D12_QUERY_TYPE_SO_STATISTICS_STREAM0, 0); + + m_DebugList->ResolveQueryData(m_SOQueryHeap, D3D12_QUERY_TYPE_SO_STATISTICS_STREAM0, 0, 1, + m_SOStagingBuffer, 0); + + m_DebugList->Close(); + + ID3D12CommandList *l = m_DebugList; + m_WrappedDevice->GetQueue()->ExecuteCommandLists(1, &l); + m_WrappedDevice->GPUSync(); + + // check that things are OK, and resize up if needed + D3D12_RANGE range; + range.Begin = 0; + range.End = (SIZE_T)sizeof(D3D12_QUERY_DATA_SO_STATISTICS); + + D3D12_QUERY_DATA_SO_STATISTICS *data; + hr = m_SOStagingBuffer->Map(0, &range, (void **)&data); + + if(m_SOBufferSize < data->PrimitivesStorageNeeded * 3 * stride) + { + uint64_t oldSize = m_SOBufferSize; + while(m_SOBufferSize < data->PrimitivesStorageNeeded * 3 * stride) + m_SOBufferSize *= 2; + RDCWARN("Resizing stream-out buffer from %llu to %llu for output", oldSize, m_SOBufferSize); + CreateSOBuffers(); + + continue; + } + + range.End = 0; + m_SOStagingBuffer->Unmap(0, &range); + + m_DebugAlloc->Reset(); + + break; + } + } + + m_DebugList->Reset(m_DebugAlloc, NULL); + + D3D12_RESOURCE_BARRIER sobarr = {}; + sobarr.Transition.pResource = m_SOBuffer; + sobarr.Transition.StateBefore = D3D12_RESOURCE_STATE_STREAM_OUT; + sobarr.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_SOURCE; + + m_DebugList->ResourceBarrier(1, &sobarr); + + m_DebugList->CopyResource(m_SOStagingBuffer, m_SOBuffer); + + // we're done with this after the copy, so we can discard it and reset + // the counter for the next stream-out + sobarr.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_SOURCE; + sobarr.Transition.StateAfter = D3D12_RESOURCE_STATE_UNORDERED_ACCESS; + m_DebugList->DiscardResource(m_SOBuffer, NULL); + m_DebugList->ResourceBarrier(1, &sobarr); + + D3D12_UNORDERED_ACCESS_VIEW_DESC counterDesc = {}; + counterDesc.ViewDimension = D3D12_UAV_DIMENSION_BUFFER; + counterDesc.Format = DXGI_FORMAT_R32_UINT; + counterDesc.Buffer.FirstElement = 0; + counterDesc.Buffer.NumElements = 4; + + UINT zeroes[4] = {0, 0, 0, 0}; + m_DebugList->ClearUnorderedAccessViewUint( + GetGPUHandle(STREAM_OUT_UAV), GetUAVClearHandle(STREAM_OUT_UAV), m_SOBuffer, zeroes, 0, NULL); + + m_DebugList->Close(); + + ID3D12CommandList *l = m_DebugList; + m_WrappedDevice->GetQueue()->ExecuteCommandLists(1, &l); + m_WrappedDevice->GPUSync(); + m_DebugAlloc->Reset(); + + SAFE_RELEASE(pipe); + + byte *byteData = NULL; + D3D12_RANGE range = {0, (SIZE_T)m_SOBufferSize}; + hr = m_SOStagingBuffer->Map(0, &range, (void **)&byteData); + if(FAILED(hr)) + { + RDCERR("Failed to map sobuffer HRESULT: %s", ToStr(hr).c_str()); + SAFE_RELEASE(soSig); + return; + } + + range.End = 0; + + uint64_t *counters = (uint64_t *)byteData; + + uint64_t numBytesWritten = 0; + std::vector instData; + if(drawcall->numInstances > 1) + { + uint64_t prevByteCount = 0; + + for(uint32_t inst = 0; inst < drawcall->numInstances; inst++) + { + uint64_t byteCount = counters[inst]; + + D3D12PostVSData::InstData d; + d.numVerts = uint32_t((byteCount - prevByteCount) / stride); + d.bufOffset = prevByteCount; + prevByteCount = byteCount; + + instData.push_back(d); + } + + numBytesWritten = prevByteCount; + } + else + { + numBytesWritten = counters[0]; + } + + if(numBytesWritten == 0) + { + SAFE_RELEASE(soSig); + return; + } + + // skip past the counter(s) + byteData += (view.BufferLocation - m_SOBuffer->GetGPUVirtualAddress()); + + uint64_t numVerts = numBytesWritten / stride; + + ID3D12Resource *gsoutBuffer = NULL; + + { + D3D12_RESOURCE_DESC vertBufDesc; + vertBufDesc.Alignment = 0; + vertBufDesc.DepthOrArraySize = 1; + vertBufDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + vertBufDesc.Flags = D3D12_RESOURCE_FLAG_NONE; + vertBufDesc.Format = DXGI_FORMAT_UNKNOWN; + vertBufDesc.Height = 1; + vertBufDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + vertBufDesc.MipLevels = 1; + vertBufDesc.SampleDesc.Count = 1; + vertBufDesc.SampleDesc.Quality = 0; + vertBufDesc.Width = numBytesWritten; + + D3D12_HEAP_PROPERTIES heapProps; + heapProps.Type = D3D12_HEAP_TYPE_UPLOAD; + heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; + heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; + heapProps.CreationNodeMask = 1; + heapProps.VisibleNodeMask = 1; + + hr = m_WrappedDevice->CreateCommittedResource(&heapProps, D3D12_HEAP_FLAG_NONE, &vertBufDesc, + D3D12_RESOURCE_STATE_GENERIC_READ, NULL, + __uuidof(ID3D12Resource), (void **)&gsoutBuffer); + RDCASSERTEQUAL(hr, S_OK); + + if(gsoutBuffer) + { + SetObjName(gsoutBuffer, StringFormat::Fmt("PostVS gsoutBuffer for %u", eventId)); + FillBuffer(gsoutBuffer, 0, byteData, (size_t)numBytesWritten); + } + } + + float nearp = 0.1f; + float farp = 100.0f; + + Vec4f *pos0 = (Vec4f *)byteData; + + bool found = false; + + for(UINT64 i = 1; numPosComponents == 4 && i < 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; + } + + m_SOStagingBuffer->Unmap(0, &range); + + m_PostVSData[eventId].gsout.buf = gsoutBuffer; + m_PostVSData[eventId].gsout.instStride = 0; + if(drawcall->flags & DrawFlags::Instanced) + m_PostVSData[eventId].gsout.instStride = + uint32_t(numBytesWritten / RDCMAX(1U, drawcall->numInstances)); + 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 = NULL; + + topo = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST; + + if(lastShader == dxbcGS) + { + for(size_t i = 0; i < dxbcGS->GetNumDeclarations(); i++) + { + const DXBC::ASMDecl &decl = dxbcGS->GetDeclaration(i); + + if(decl.declaration == DXBC::OPCODE_DCL_GS_OUTPUT_PRIMITIVE_TOPOLOGY) + { + topo = decl.outTopology; + break; + } + } + } + else if(lastShader == dxbcDS) + { + for(size_t i = 0; i < dxbcDS->GetNumDeclarations(); i++) + { + const DXBC::ASMDecl &decl = dxbcDS->GetDeclaration(i); + + if(decl.declaration == DXBC::OPCODE_DCL_TESS_DOMAIN) + { + if(decl.domain == DXBC::DOMAIN_ISOLINE) + topo = D3D_PRIMITIVE_TOPOLOGY_LINELIST; + else + topo = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST; + break; + } + } + } + + m_PostVSData[eventId].gsout.topo = topo; + + // streamout expands strips unfortunately + if(topo == D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP) + m_PostVSData[eventId].gsout.topo = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST; + else if(topo == D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP) + m_PostVSData[eventId].gsout.topo = D3D11_PRIMITIVE_TOPOLOGY_LINELIST; + else if(topo == D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP_ADJ) + m_PostVSData[eventId].gsout.topo = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ; + else if(topo == D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP_ADJ) + m_PostVSData[eventId].gsout.topo = D3D11_PRIMITIVE_TOPOLOGY_LINELIST_ADJ; + + m_PostVSData[eventId].gsout.numVerts = (uint32_t)numVerts; + + if(drawcall->flags & DrawFlags::Instanced) + m_PostVSData[eventId].gsout.numVerts /= RDCMAX(1U, drawcall->numInstances); + + m_PostVSData[eventId].gsout.instData = instData; + } + + SAFE_RELEASE(soSig); +} + +MeshFormat D3D12DebugManager::GetPostVSBuffers(uint32_t eventId, uint32_t instID, MeshDataStage stage) +{ + // go through any aliasing + if(m_PostVSAlias.find(eventId) != m_PostVSAlias.end()) + eventId = m_PostVSAlias[eventId]; + + D3D12PostVSData postvs; + RDCEraseEl(postvs); + + if(m_PostVSData.find(eventId) != m_PostVSData.end()) + postvs = m_PostVSData[eventId]; + + const D3D12PostVSData::StageData &s = postvs.GetStage(stage); + + MeshFormat ret; + + if(s.useIndices && s.idxBuf != NULL) + { + ret.indexResourceId = GetResID(s.idxBuf); + ret.indexByteStride = s.idxFmt == DXGI_FORMAT_R16_UINT ? 2 : 4; + } + else + { + ret.indexResourceId = ResourceId(); + ret.indexByteStride = 0; + } + ret.indexByteOffset = 0; + ret.baseVertex = 0; + + if(s.buf != NULL) + ret.vertexResourceId = GetResID(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 = MakePrimitiveTopology(s.topo); + ret.numIndices = s.numVerts; + + ret.unproject = s.hasPosOut; + ret.nearPlane = s.nearPlane; + ret.farPlane = s.farPlane; + + if(instID < s.instData.size()) + { + D3D12PostVSData::InstData inst = s.instData[instID]; + + ret.vertexByteOffset = inst.bufOffset; + ret.numIndices = inst.numVerts; + } + + return ret; +} diff --git a/renderdoc/driver/d3d12/renderdoc_d3d12.vcxproj b/renderdoc/driver/d3d12/renderdoc_d3d12.vcxproj index 4cec487e4..cbe7ef330 100644 --- a/renderdoc/driver/d3d12/renderdoc_d3d12.vcxproj +++ b/renderdoc/driver/d3d12/renderdoc_d3d12.vcxproj @@ -109,6 +109,7 @@ + diff --git a/renderdoc/driver/d3d12/renderdoc_d3d12.vcxproj.filters b/renderdoc/driver/d3d12/renderdoc_d3d12.vcxproj.filters index d6abff1d1..e132073dd 100644 --- a/renderdoc/driver/d3d12/renderdoc_d3d12.vcxproj.filters +++ b/renderdoc/driver/d3d12/renderdoc_d3d12.vcxproj.filters @@ -116,5 +116,8 @@ Util + + Replay + \ No newline at end of file diff --git a/renderdoc/driver/gl/CMakeLists.txt b/renderdoc/driver/gl/CMakeLists.txt index d9c8a3308..72fe4cb06 100644 --- a/renderdoc/driver/gl/CMakeLists.txt +++ b/renderdoc/driver/gl/CMakeLists.txt @@ -3,6 +3,7 @@ set(sources gl_common.h gl_counters.cpp gl_debug.cpp + gl_postvs.cpp gl_driver.cpp gl_driver.h gl_enum.h diff --git a/renderdoc/driver/gl/gl_debug.cpp b/renderdoc/driver/gl/gl_debug.cpp index cff5a8959..9fcda23bd 100644 --- a/renderdoc/driver/gl/gl_debug.cpp +++ b/renderdoc/driver/gl/gl_debug.cpp @@ -3336,1439 +3336,6 @@ ResourceId GLReplay::RenderOverlay(ResourceId texid, CompType typeHint, DebugOve return m_pDriver->GetResourceManager()->GetID(TextureRes(ctx, DebugData.overlayTex)); } -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], ""); - vsProg = m_pDriver->m_Shaders[pipeDetails.stageShaders[0]].prog; - vsProgSrc = rm->GetCurrentResource(pipeDetails.stagePrograms[0]).name; - } - if(pipeDetails.stageShaders[1] != ResourceId()) - { - tcsProg = m_pDriver->m_Shaders[pipeDetails.stageShaders[1]].prog; - tcsProgSrc = rm->GetCurrentResource(pipeDetails.stagePrograms[1]).name; - } - if(pipeDetails.stageShaders[2] != ResourceId()) - { - tesRefl = GetShader(pipeDetails.stageShaders[2], ""); - tesProg = m_pDriver->m_Shaders[pipeDetails.stageShaders[2]].prog; - tesProgSrc = rm->GetCurrentResource(pipeDetails.stagePrograms[2]).name; - } - if(pipeDetails.stageShaders[3] != ResourceId()) - { - gsRefl = GetShader(pipeDetails.stageShaders[3], ""); - gsProg = m_pDriver->m_Shaders[pipeDetails.stageShaders[3]].prog; - gsProgSrc = rm->GetCurrentResource(pipeDetails.stagePrograms[3]).name; - } - } - } - else - { - auto &progDetails = m_pDriver->m_Programs[rm->GetID(rs.Program)]; - - if(progDetails.stageShaders[0] != ResourceId()) - { - vsRefl = GetShader(progDetails.stageShaders[0], ""); - vsProg = m_pDriver->m_Shaders[progDetails.stageShaders[0]].prog; - } - if(progDetails.stageShaders[1] != ResourceId()) - { - tcsProg = m_pDriver->m_Shaders[progDetails.stageShaders[1]].prog; - } - if(progDetails.stageShaders[2] != ResourceId()) - { - tesRefl = GetShader(progDetails.stageShaders[2], ""); - tesProg = m_pDriver->m_Shaders[progDetails.stageShaders[2]].prog; - } - if(progDetails.stageShaders[3] != ResourceId()) - { - gsRefl = GetShader(progDetails.stageShaders[3], ""); - gsProg = m_pDriver->m_Shaders[progDetails.stageShaders[3]].prog; - } - - vsProgSrc = tcsProgSrc = tesProgSrc = gsProgSrc = rs.Program.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 matrixVaryings; // matrices need some fixup - vector 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 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 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 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 &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; -} - void GLReplay::RenderMesh(uint32_t eventId, const vector &secondaryDraws, const MeshDisplay &cfg) { diff --git a/renderdoc/driver/gl/gl_postvs.cpp b/renderdoc/driver/gl/gl_postvs.cpp new file mode 100644 index 000000000..11a311f99 --- /dev/null +++ b/renderdoc/driver/gl/gl_postvs.cpp @@ -0,0 +1,1464 @@ +/****************************************************************************** + * 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 +#include +#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], ""); + vsProg = m_pDriver->m_Shaders[pipeDetails.stageShaders[0]].prog; + vsProgSrc = rm->GetCurrentResource(pipeDetails.stagePrograms[0]).name; + } + if(pipeDetails.stageShaders[1] != ResourceId()) + { + tcsProg = m_pDriver->m_Shaders[pipeDetails.stageShaders[1]].prog; + tcsProgSrc = rm->GetCurrentResource(pipeDetails.stagePrograms[1]).name; + } + if(pipeDetails.stageShaders[2] != ResourceId()) + { + tesRefl = GetShader(pipeDetails.stageShaders[2], ""); + tesProg = m_pDriver->m_Shaders[pipeDetails.stageShaders[2]].prog; + tesProgSrc = rm->GetCurrentResource(pipeDetails.stagePrograms[2]).name; + } + if(pipeDetails.stageShaders[3] != ResourceId()) + { + gsRefl = GetShader(pipeDetails.stageShaders[3], ""); + gsProg = m_pDriver->m_Shaders[pipeDetails.stageShaders[3]].prog; + gsProgSrc = rm->GetCurrentResource(pipeDetails.stagePrograms[3]).name; + } + } + } + else + { + auto &progDetails = m_pDriver->m_Programs[rm->GetID(rs.Program)]; + + if(progDetails.stageShaders[0] != ResourceId()) + { + vsRefl = GetShader(progDetails.stageShaders[0], ""); + vsProg = m_pDriver->m_Shaders[progDetails.stageShaders[0]].prog; + } + if(progDetails.stageShaders[1] != ResourceId()) + { + tcsProg = m_pDriver->m_Shaders[progDetails.stageShaders[1]].prog; + } + if(progDetails.stageShaders[2] != ResourceId()) + { + tesRefl = GetShader(progDetails.stageShaders[2], ""); + tesProg = m_pDriver->m_Shaders[progDetails.stageShaders[2]].prog; + } + if(progDetails.stageShaders[3] != ResourceId()) + { + gsRefl = GetShader(progDetails.stageShaders[3], ""); + gsProg = m_pDriver->m_Shaders[progDetails.stageShaders[3]].prog; + } + + vsProgSrc = tcsProgSrc = tesProgSrc = gsProgSrc = rs.Program.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 matrixVaryings; // matrices need some fixup + vector 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 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 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 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 &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; +} diff --git a/renderdoc/driver/gl/renderdoc_gl.vcxproj b/renderdoc/driver/gl/renderdoc_gl.vcxproj index 51b0ba3f2..f7659f4e9 100644 --- a/renderdoc/driver/gl/renderdoc_gl.vcxproj +++ b/renderdoc/driver/gl/renderdoc_gl.vcxproj @@ -150,6 +150,7 @@ + diff --git a/renderdoc/driver/gl/renderdoc_gl.vcxproj.filters b/renderdoc/driver/gl/renderdoc_gl.vcxproj.filters index 22a68cc0c..1e6a1c6e7 100644 --- a/renderdoc/driver/gl/renderdoc_gl.vcxproj.filters +++ b/renderdoc/driver/gl/renderdoc_gl.vcxproj.filters @@ -227,5 +227,8 @@ GLSL + + Replay + \ No newline at end of file diff --git a/renderdoc/driver/vulkan/CMakeLists.txt b/renderdoc/driver/vulkan/CMakeLists.txt index 9abd25632..e14964600 100644 --- a/renderdoc/driver/vulkan/CMakeLists.txt +++ b/renderdoc/driver/vulkan/CMakeLists.txt @@ -4,8 +4,9 @@ set(sources vk_core.cpp vk_core.h vk_counters.cpp - vk_debug.cpp vk_debug.h + vk_debug.cpp + vk_postvs.cpp vk_dispatchtables.cpp vk_dispatchtables.h vk_hookset_defs.h diff --git a/renderdoc/driver/vulkan/renderdoc_vulkan.vcxproj b/renderdoc/driver/vulkan/renderdoc_vulkan.vcxproj index 858994159..e999db6be 100644 --- a/renderdoc/driver/vulkan/renderdoc_vulkan.vcxproj +++ b/renderdoc/driver/vulkan/renderdoc_vulkan.vcxproj @@ -104,6 +104,7 @@ true + diff --git a/renderdoc/driver/vulkan/renderdoc_vulkan.vcxproj.filters b/renderdoc/driver/vulkan/renderdoc_vulkan.vcxproj.filters index 382f3369f..d7ec429a7 100644 --- a/renderdoc/driver/vulkan/renderdoc_vulkan.vcxproj.filters +++ b/renderdoc/driver/vulkan/renderdoc_vulkan.vcxproj.filters @@ -106,6 +106,9 @@ Core + + Replay + diff --git a/renderdoc/driver/vulkan/vk_debug.cpp b/renderdoc/driver/vulkan/vk_debug.cpp index 0af6ecfb8..2d6d4869b 100644 --- a/renderdoc/driver/vulkan/vk_debug.cpp +++ b/renderdoc/driver/vulkan/vk_debug.cpp @@ -7251,1632 +7251,3 @@ MeshDisplayPipelines VulkanDebugManager::CacheMeshDisplayPipelines(const MeshFor return cache; } - -inline uint32_t MakeSPIRVOp(spv::Op op, uint32_t WordCount) -{ - return (uint32_t(op) & spv::OpCodeMask) | (WordCount << spv::WordCountShift); -} - -static void AddOutputDumping(const ShaderReflection &refl, const SPIRVPatchData &patchData, - const char *entryName, uint32_t &descSet, uint32_t vertexIndexOffset, - uint32_t instanceIndexOffset, uint32_t numVerts, - vector &modSpirv, uint32_t &bufStride) -{ - uint32_t *spirv = &modSpirv[0]; - size_t spirvLength = modSpirv.size(); - - int numOutputs = refl.outputSignature.count(); - - RDCASSERT(numOutputs > 0); - - // save the id bound. We use this whenever we need to allocate ourselves - // a new ID - uint32_t idBound = spirv[3]; - - // we do multiple passes through the SPIR-V to simplify logic, rather than - // trying to do as few passes as possible. - - // first try to find a few IDs of things we know we'll probably need: - // * gl_VertexID, gl_InstanceID (identified by a DecorationBuiltIn) - // * Int32 type, signed and unsigned - // * Float types, half, float and double - // * Input Pointer to Int32 (for declaring gl_VertexID) - // * UInt32 constants from 0 up to however many outputs we have - // * The entry point we're after - // - // At the same time we find the highest descriptor set used and add a - // new descriptor set binding on the end for our output buffer. This is - // much easier than trying to add a new bind to an existing descriptor - // set (which would cascade into a new descriptor set layout, new pipeline - // layout, etc etc!). However, this might push us over the limit on number - // of descriptor sets. - // - // we also note the index where decorations end, and the index where - // functions start, for if we need to add new decorations or new - // types/constants/global variables - uint32_t vertidxID = 0; - uint32_t instidxID = 0; - uint32_t sint32ID = 0; - uint32_t sint32PtrInID = 0; - uint32_t uint32ID = 0; - uint32_t halfID = 0; - uint32_t floatID = 0; - uint32_t doubleID = 0; - uint32_t entryID = 0; - - struct outputIDs - { - uint32_t constID; // constant ID for the index of this output - uint32_t basetypeID; // the type ID for this output. Must be present already by definition! - uint32_t uniformPtrID; // Uniform Pointer ID for this output. Used to write the output data - uint32_t outputPtrID; // Output Pointer ID for this output. Used to read the output data - }; - outputIDs outs[100] = {}; - - RDCASSERT(numOutputs < 100); - - size_t entryInterfaceOffset = 0; - size_t entryWordCountOffset = 0; - uint16_t entryWordCount = 0; - size_t decorateOffset = 0; - size_t typeVarOffset = 0; - - descSet = 0; - - size_t it = 5; - while(it < spirvLength) - { - uint16_t WordCount = spirv[it] >> spv::WordCountShift; - spv::Op opcode = spv::Op(spirv[it] & spv::OpCodeMask); - - // we will use the descriptor set immediately after the last set statically used by the shader. - // This means we don't have to worry about if the descriptor set layout declares more sets which - // might be invalid and un-bindable, we just trample over the next set that's unused - if(opcode == spv::OpDecorate && spirv[it + 2] == spv::DecorationDescriptorSet) - descSet = RDCMAX(descSet, spirv[it + 3] + 1); - - if(opcode == spv::OpDecorate && spirv[it + 2] == spv::DecorationBuiltIn && - spirv[it + 3] == spv::BuiltInVertexIndex) - vertidxID = spirv[it + 1]; - - if(opcode == spv::OpDecorate && spirv[it + 2] == spv::DecorationBuiltIn && - spirv[it + 3] == spv::BuiltInInstanceIndex) - instidxID = spirv[it + 1]; - - if(opcode == spv::OpTypeInt && spirv[it + 2] == 32 && spirv[it + 3] == 1) - sint32ID = spirv[it + 1]; - - if(opcode == spv::OpTypeInt && spirv[it + 2] == 32 && spirv[it + 3] == 0) - uint32ID = spirv[it + 1]; - - if(opcode == spv::OpTypeFloat && spirv[it + 2] == 16) - halfID = spirv[it + 1]; - - if(opcode == spv::OpTypeFloat && spirv[it + 2] == 32) - floatID = spirv[it + 1]; - - if(opcode == spv::OpTypeFloat && spirv[it + 2] == 64) - doubleID = spirv[it + 1]; - - if(opcode == spv::OpTypePointer && spirv[it + 2] == spv::StorageClassInput && - spirv[it + 3] == sint32ID) - sint32PtrInID = spirv[it + 1]; - - for(int i = 0; i < numOutputs; i++) - { - if(opcode == spv::OpConstant && spirv[it + 1] == uint32ID && spirv[it + 3] == (uint32_t)i) - { - if(outs[i].constID != 0) - RDCWARN("identical constant declared with two different IDs %u %u!", spirv[it + 2], - outs[i].constID); // not sure if this is valid or not - outs[i].constID = spirv[it + 2]; - } - - if(outs[i].basetypeID == 0) - { - if(refl.outputSignature[i].compCount > 1 && opcode == spv::OpTypeVector) - { - uint32_t baseID = 0; - - if(refl.outputSignature[i].compType == CompType::UInt) - baseID = uint32ID; - else if(refl.outputSignature[i].compType == CompType::SInt) - baseID = sint32ID; - else if(refl.outputSignature[i].compType == CompType::Float) - baseID = floatID; - else if(refl.outputSignature[i].compType == CompType::Double) - baseID = doubleID; - else - RDCERR("Unexpected component type for output signature element"); - - // if we have the base type, see if this is the right sized vector of that type - if(baseID != 0 && spirv[it + 2] == baseID && - spirv[it + 3] == refl.outputSignature[i].compCount) - outs[i].basetypeID = spirv[it + 1]; - } - - // handle non-vectors - if(refl.outputSignature[i].compCount == 1) - { - if(refl.outputSignature[i].compType == CompType::UInt) - outs[i].basetypeID = uint32ID; - else if(refl.outputSignature[i].compType == CompType::SInt) - outs[i].basetypeID = sint32ID; - else if(refl.outputSignature[i].compType == CompType::Float) - outs[i].basetypeID = floatID; - else if(refl.outputSignature[i].compType == CompType::Double) - outs[i].basetypeID = doubleID; - } - } - - // if we've found the base type, try and identify pointers to that type - if(outs[i].basetypeID != 0 && opcode == spv::OpTypePointer && - spirv[it + 2] == spv::StorageClassUniform && spirv[it + 3] == outs[i].basetypeID) - { - outs[i].uniformPtrID = spirv[it + 1]; - } - - if(outs[i].basetypeID != 0 && opcode == spv::OpTypePointer && - spirv[it + 2] == spv::StorageClassOutput && spirv[it + 3] == outs[i].basetypeID) - { - outs[i].outputPtrID = spirv[it + 1]; - } - } - - if(opcode == spv::OpEntryPoint) - { - const char *name = (const char *)&spirv[it + 3]; - - if(!strcmp(name, entryName)) - { - if(entryID != 0) - RDCERR("Same entry point declared twice! %s", entryName); - entryID = spirv[it + 2]; - } - - // need to update the WordCount when we add IDs, so store this - entryWordCountOffset = it; - entryWordCount = WordCount; - - // where to insert new interface IDs if we add them - entryInterfaceOffset = it + WordCount; - } - - // when we reach the types, decorations are over - if(decorateOffset == 0 && opcode >= spv::OpTypeVoid && opcode <= spv::OpTypeForwardPointer) - decorateOffset = it; - - // stop when we reach the functions, types are over - if(opcode == spv::OpFunction) - { - typeVarOffset = it; - break; - } - - it += WordCount; - } - - RDCASSERT(entryID != 0); - - for(int i = 0; i < numOutputs; i++) - { - // must have at least found the base type, or something has gone seriously wrong - RDCASSERT(outs[i].basetypeID != 0); - } - - // if needed add new ID for sint32 type - if(sint32ID == 0) - { - sint32ID = idBound++; - - uint32_t typeOp[] = { - MakeSPIRVOp(spv::OpTypeInt, 4), sint32ID, - 32U, // 32-bit - 1U, // signed - }; - - // insert at the end of the types/variables section - modSpirv.insert(modSpirv.begin() + typeVarOffset, typeOp, typeOp + ARRAY_COUNT(typeOp)); - - // update offsets to account for inserted op - typeVarOffset += ARRAY_COUNT(typeOp); - } - - // if needed, new ID for input ptr type - if(sint32PtrInID == 0 && (vertidxID == 0 || instidxID == 0)) - { - sint32PtrInID = idBound; - idBound++; - - uint32_t typeOp[] = { - MakeSPIRVOp(spv::OpTypePointer, 4), sint32PtrInID, spv::StorageClassInput, sint32ID, - }; - - // insert at the end of the types/variables section - modSpirv.insert(modSpirv.begin() + typeVarOffset, typeOp, typeOp + ARRAY_COUNT(typeOp)); - - // update offsets to account for inserted op - typeVarOffset += ARRAY_COUNT(typeOp); - } - - if(vertidxID == 0) - { - // need to declare our own "in int gl_VertexID;" - - // new ID for vertex index - vertidxID = idBound; - idBound++; - - uint32_t varOp[] = { - MakeSPIRVOp(spv::OpVariable, 4), - sint32PtrInID, // type - vertidxID, // variable id - spv::StorageClassInput, - }; - - // insert at the end of the types/variables section - modSpirv.insert(modSpirv.begin() + typeVarOffset, varOp, varOp + ARRAY_COUNT(varOp)); - - // update offsets to account for inserted op - typeVarOffset += ARRAY_COUNT(varOp); - - uint32_t decorateOp[] = { - MakeSPIRVOp(spv::OpDecorate, 4), vertidxID, spv::DecorationBuiltIn, spv::BuiltInVertexIndex, - }; - - // insert at the end of the decorations before the types - modSpirv.insert(modSpirv.begin() + decorateOffset, decorateOp, - decorateOp + ARRAY_COUNT(decorateOp)); - - // update offsets to account for inserted op - typeVarOffset += ARRAY_COUNT(decorateOp); - decorateOffset += ARRAY_COUNT(decorateOp); - - modSpirv[entryWordCountOffset] = MakeSPIRVOp(spv::OpEntryPoint, ++entryWordCount); - - // need to add this input to the declared interface on OpEntryPoint - modSpirv.insert(modSpirv.begin() + entryInterfaceOffset, vertidxID); - - // update offsets to account for inserted ID - entryInterfaceOffset++; - typeVarOffset++; - decorateOffset++; - } - - if(instidxID == 0) - { - // need to declare our own "in int gl_InstanceID;" - - // new ID for vertex index - instidxID = idBound; - idBound++; - - uint32_t varOp[] = { - MakeSPIRVOp(spv::OpVariable, 4), - sint32PtrInID, // type - instidxID, // variable id - spv::StorageClassInput, - }; - - // insert at the end of the types/variables section - modSpirv.insert(modSpirv.begin() + typeVarOffset, varOp, varOp + ARRAY_COUNT(varOp)); - - // update offsets to account for inserted op - typeVarOffset += ARRAY_COUNT(varOp); - - uint32_t decorateOp[] = { - MakeSPIRVOp(spv::OpDecorate, 4), instidxID, spv::DecorationBuiltIn, spv::BuiltInInstanceIndex, - }; - - // insert at the end of the decorations before the types - modSpirv.insert(modSpirv.begin() + decorateOffset, decorateOp, - decorateOp + ARRAY_COUNT(decorateOp)); - - // update offsets to account for inserted op - typeVarOffset += ARRAY_COUNT(decorateOp); - decorateOffset += ARRAY_COUNT(decorateOp); - - modSpirv[entryWordCountOffset] = MakeSPIRVOp(spv::OpEntryPoint, ++entryWordCount); - - // need to add this input to the declared interface on OpEntryPoint - modSpirv.insert(modSpirv.begin() + entryInterfaceOffset, instidxID); - - // update offsets to account for inserted ID - entryInterfaceOffset++; - typeVarOffset++; - decorateOffset++; - } - - // if needed add new ID for uint32 type - if(uint32ID == 0) - { - uint32ID = idBound++; - - uint32_t typeOp[] = { - MakeSPIRVOp(spv::OpTypeInt, 4), uint32ID, - 32U, // 32-bit - 0U, // unsigned - }; - - // insert at the end of the types/variables section - modSpirv.insert(modSpirv.begin() + typeVarOffset, typeOp, typeOp + ARRAY_COUNT(typeOp)); - - // update offsets to account for inserted op - typeVarOffset += ARRAY_COUNT(typeOp); - } - - // add any constants we're missing - for(int i = 0; i < numOutputs; i++) - { - if(outs[i].constID == 0) - { - outs[i].constID = idBound++; - - uint32_t constantOp[] = { - MakeSPIRVOp(spv::OpConstant, 4), uint32ID, outs[i].constID, (uint32_t)i, - }; - - // insert at the end of the types/variables/constants section - modSpirv.insert(modSpirv.begin() + typeVarOffset, constantOp, - constantOp + ARRAY_COUNT(constantOp)); - - // update offsets to account for inserted op - typeVarOffset += ARRAY_COUNT(constantOp); - } - } - - // add any uniform pointer types we're missing. Note that it's quite likely - // output types will overlap (think - 5 outputs, 3 of which are float4/vec4) - // so any time we create a new uniform pointer type, we update all subsequent - // outputs to refer to it. - for(int i = 0; i < numOutputs; i++) - { - if(outs[i].uniformPtrID == 0) - { - outs[i].uniformPtrID = idBound++; - - uint32_t typeOp[] = { - MakeSPIRVOp(spv::OpTypePointer, 4), outs[i].uniformPtrID, spv::StorageClassUniform, - outs[i].basetypeID, - }; - - // insert at the end of the types/variables/constants section - modSpirv.insert(modSpirv.begin() + typeVarOffset, typeOp, typeOp + ARRAY_COUNT(typeOp)); - - // update offsets to account for inserted op - typeVarOffset += ARRAY_COUNT(typeOp); - - // update subsequent outputs of identical type - for(int j = i + 1; j < numOutputs; j++) - { - if(outs[i].basetypeID == outs[j].basetypeID) - { - RDCASSERT(outs[j].uniformPtrID == 0); - outs[j].uniformPtrID = outs[i].uniformPtrID; - } - } - } - - // matrices would have been written through an output pointer of matrix type, but we're reading - // them vector-by-vector so we may need to declare an output pointer of the corresponding - // vector type. - // Otherwise, we expect to re-use the original SPIR-V's output pointer. - if(outs[i].outputPtrID == 0) - { - if(!patchData.outputs[i].isMatrix) - { - RDCERR("No output pointer ID found for non-matrix output %d: %s (%u %u)", i, - refl.outputSignature[i].varName.c_str(), refl.outputSignature[i].compType, - refl.outputSignature[i].compCount); - } - - outs[i].outputPtrID = idBound++; - - uint32_t typeOp[] = { - MakeSPIRVOp(spv::OpTypePointer, 4), outs[i].outputPtrID, spv::StorageClassOutput, - outs[i].basetypeID, - }; - - // insert at the end of the types/variables/constants section - modSpirv.insert(modSpirv.begin() + typeVarOffset, typeOp, typeOp + ARRAY_COUNT(typeOp)); - - // update offsets to account for inserted op - typeVarOffset += ARRAY_COUNT(typeOp); - - // update subsequent outputs of identical type - for(int j = i + 1; j < numOutputs; j++) - { - if(outs[i].basetypeID == outs[j].basetypeID) - { - RDCASSERT(outs[j].outputPtrID == 0); - outs[j].outputPtrID = outs[i].outputPtrID; - } - } - } - } - - uint32_t outBufferVarID = 0; - uint32_t numVertsConstID = 0; - uint32_t vertexIndexOffsetConstID = 0; - uint32_t instanceIndexOffsetConstID = 0; - - // now add the structure type etc for our output buffer - { - uint32_t vertStructID = idBound++; - - uint32_t vertStructOp[2 + 100] = { - MakeSPIRVOp(spv::OpTypeStruct, 2 + numOutputs), vertStructID, - }; - - for(int o = 0; o < numOutputs; o++) - vertStructOp[2 + o] = outs[o].basetypeID; - - // insert at the end of the types/variables section - modSpirv.insert(modSpirv.begin() + typeVarOffset, vertStructOp, vertStructOp + 2 + numOutputs); - - // update offsets to account for inserted op - typeVarOffset += 2 + numOutputs; - - uint32_t runtimeArrayID = idBound++; - - uint32_t runtimeArrayOp[] = { - MakeSPIRVOp(spv::OpTypeRuntimeArray, 3), runtimeArrayID, vertStructID, - }; - - // insert at the end of the types/variables section - modSpirv.insert(modSpirv.begin() + typeVarOffset, runtimeArrayOp, - runtimeArrayOp + ARRAY_COUNT(runtimeArrayOp)); - - // update offsets to account for inserted op - typeVarOffset += ARRAY_COUNT(runtimeArrayOp); - - // add a constant for the number of verts, the 'instance stride' of the array - numVertsConstID = idBound++; - - uint32_t instanceStrideConstOp[] = { - MakeSPIRVOp(spv::OpConstant, 4), sint32ID, numVertsConstID, numVerts, - }; - - // insert at the end of the types/variables section - modSpirv.insert(modSpirv.begin() + typeVarOffset, instanceStrideConstOp, - instanceStrideConstOp + ARRAY_COUNT(instanceStrideConstOp)); - - // update offsets to account for inserted op - typeVarOffset += ARRAY_COUNT(instanceStrideConstOp); - - // add a constant for the value that VertexIndex starts at, so we can get a 0-based vertex index - vertexIndexOffsetConstID = idBound++; - - uint32_t vertexIndexOffsetConstOp[] = { - MakeSPIRVOp(spv::OpConstant, 4), sint32ID, vertexIndexOffsetConstID, vertexIndexOffset, - }; - - // insert at the end of the types/variables section - modSpirv.insert(modSpirv.begin() + typeVarOffset, vertexIndexOffsetConstOp, - vertexIndexOffsetConstOp + ARRAY_COUNT(vertexIndexOffsetConstOp)); - - // update offsets to account for inserted op - typeVarOffset += ARRAY_COUNT(vertexIndexOffsetConstOp); - - // add a constant for the value that InstanceIndex starts at, so we can get a 0-based instance - // index - instanceIndexOffsetConstID = idBound++; - - uint32_t instanceIndexOffsetConstOp[] = { - MakeSPIRVOp(spv::OpConstant, 4), sint32ID, instanceIndexOffsetConstID, instanceIndexOffset, - }; - - // insert at the end of the types/variables section - modSpirv.insert(modSpirv.begin() + typeVarOffset, instanceIndexOffsetConstOp, - instanceIndexOffsetConstOp + ARRAY_COUNT(instanceIndexOffsetConstOp)); - - // update offsets to account for inserted op - typeVarOffset += ARRAY_COUNT(instanceIndexOffsetConstOp); - - uint32_t outputStructID = idBound++; - - uint32_t outputStructOp[] = { - MakeSPIRVOp(spv::OpTypeStruct, 3), outputStructID, runtimeArrayID, - }; - - // insert at the end of the types/variables section - modSpirv.insert(modSpirv.begin() + typeVarOffset, outputStructOp, - outputStructOp + ARRAY_COUNT(outputStructOp)); - - // update offsets to account for inserted op - typeVarOffset += ARRAY_COUNT(outputStructOp); - - uint32_t outputStructPtrID = idBound++; - - uint32_t outputStructPtrOp[] = { - MakeSPIRVOp(spv::OpTypePointer, 4), outputStructPtrID, spv::StorageClassUniform, - outputStructID, - }; - - // insert at the end of the types/variables section - modSpirv.insert(modSpirv.begin() + typeVarOffset, outputStructPtrOp, - outputStructPtrOp + ARRAY_COUNT(outputStructPtrOp)); - - // update offsets to account for inserted op - typeVarOffset += ARRAY_COUNT(outputStructPtrOp); - - outBufferVarID = idBound++; - - uint32_t outputVarOp[] = { - MakeSPIRVOp(spv::OpVariable, 4), outputStructPtrID, outBufferVarID, spv::StorageClassUniform, - }; - - // insert at the end of the types/variables section - modSpirv.insert(modSpirv.begin() + typeVarOffset, outputVarOp, - outputVarOp + ARRAY_COUNT(outputVarOp)); - - // update offsets to account for inserted op - typeVarOffset += ARRAY_COUNT(outputVarOp); - - // need to add decorations as appropriate - vector decorations; - - // reserve room for 1 member decorate per output, plus - // other fixed decorations - decorations.reserve(5 * numOutputs + 20); - - uint32_t memberOffset = 0; - for(int o = 0; o < numOutputs; o++) - { - uint32_t elemSize = 0; - if(refl.outputSignature[o].compType == CompType::Double) - elemSize = 8; - else if(refl.outputSignature[o].compType == CompType::SInt || - refl.outputSignature[o].compType == CompType::UInt || - refl.outputSignature[o].compType == CompType::Float) - elemSize = 4; - else - RDCERR("Unexpected component type for output signature element"); - - uint32_t numComps = refl.outputSignature[o].compCount; - - // ensure member is std430 packed (vec4 alignment for vec3/vec4) - if(numComps == 2) - memberOffset = AlignUp(memberOffset, 2U * elemSize); - else if(numComps > 2) - memberOffset = AlignUp(memberOffset, 4U * elemSize); - - decorations.push_back(MakeSPIRVOp(spv::OpMemberDecorate, 5)); - decorations.push_back(vertStructID); - decorations.push_back((uint32_t)o); - decorations.push_back(spv::DecorationOffset); - decorations.push_back(memberOffset); - - memberOffset += elemSize * refl.outputSignature[o].compCount; - } - - // align to 16 bytes (vec4) since we will almost certainly have - // a vec4 in the struct somewhere, and even in std430 alignment, - // the base struct alignment is still the largest base alignment - // of any member - memberOffset = AlignUp16(memberOffset); - - // the array is the only element in the output struct, so - // it's at offset 0 - decorations.push_back(MakeSPIRVOp(spv::OpMemberDecorate, 5)); - decorations.push_back(outputStructID); - decorations.push_back(0); - decorations.push_back(spv::DecorationOffset); - decorations.push_back(0); - - // set array stride - decorations.push_back(MakeSPIRVOp(spv::OpDecorate, 4)); - decorations.push_back(runtimeArrayID); - decorations.push_back(spv::DecorationArrayStride); - decorations.push_back(memberOffset); - - bufStride = memberOffset; - - // set object type - decorations.push_back(MakeSPIRVOp(spv::OpDecorate, 3)); - decorations.push_back(outputStructID); - decorations.push_back(spv::DecorationBufferBlock); - - // set binding - decorations.push_back(MakeSPIRVOp(spv::OpDecorate, 4)); - decorations.push_back(outBufferVarID); - decorations.push_back(spv::DecorationDescriptorSet); - decorations.push_back(descSet); - - decorations.push_back(MakeSPIRVOp(spv::OpDecorate, 4)); - decorations.push_back(outBufferVarID); - decorations.push_back(spv::DecorationBinding); - decorations.push_back(0); - - // insert at the end of the types/variables section - modSpirv.insert(modSpirv.begin() + decorateOffset, decorations.begin(), decorations.end()); - - // update offsets to account for inserted op - typeVarOffset += decorations.size(); - decorateOffset += decorations.size(); - } - - vector dumpCode; - - { - // bit of a conservative resize. Each output if in a struct could have - // AccessChain on source = 4 uint32s - // Load source = 4 uint32s - // AccessChain on dest = 7 uint32s - // Store dest = 3 uint32s - // - // loading the indices, and multiplying to get the destination array - // slot is constant on top of that - dumpCode.reserve(numOutputs * (4 + 4 + 7 + 3) + 4 + 4 + 5 + 5); - - uint32_t loadedVtxID = idBound++; - dumpCode.push_back(MakeSPIRVOp(spv::OpLoad, 4)); - dumpCode.push_back(sint32ID); - dumpCode.push_back(loadedVtxID); - dumpCode.push_back(vertidxID); - - uint32_t loadedInstID = idBound++; - dumpCode.push_back(MakeSPIRVOp(spv::OpLoad, 4)); - dumpCode.push_back(sint32ID); - dumpCode.push_back(loadedInstID); - dumpCode.push_back(instidxID); - - uint32_t rebasedInstID = idBound++; - dumpCode.push_back(MakeSPIRVOp(spv::OpISub, 5)); - dumpCode.push_back(sint32ID); - dumpCode.push_back(rebasedInstID); // rebasedInst = - dumpCode.push_back(loadedInstID); // gl_InstanceIndex - - dumpCode.push_back(instanceIndexOffsetConstID); // instanceIndexOffset - - uint32_t startVertID = idBound++; - dumpCode.push_back(MakeSPIRVOp(spv::OpIMul, 5)); - dumpCode.push_back(sint32ID); - dumpCode.push_back(startVertID); // startVert = - dumpCode.push_back(rebasedInstID); // rebasedInst * - dumpCode.push_back(numVertsConstID); // numVerts - - uint32_t rebasedVertID = idBound++; - dumpCode.push_back(MakeSPIRVOp(spv::OpISub, 5)); - dumpCode.push_back(sint32ID); - dumpCode.push_back(rebasedVertID); // rebasedVert = - dumpCode.push_back(loadedVtxID); // gl_VertexIndex - - dumpCode.push_back(vertexIndexOffsetConstID); // vertexIndexOffset - - uint32_t arraySlotID = idBound++; - dumpCode.push_back(MakeSPIRVOp(spv::OpIAdd, 5)); - dumpCode.push_back(sint32ID); - dumpCode.push_back(arraySlotID); // arraySlot = - dumpCode.push_back(startVertID); // startVert + - dumpCode.push_back(rebasedVertID); // rebasedVert - - for(int o = 0; o < numOutputs; o++) - { - uint32_t loaded = 0; - - // not a structure member or array child, can load directly - if(patchData.outputs[o].accessChain.empty()) - { - loaded = idBound++; - - dumpCode.push_back(MakeSPIRVOp(spv::OpLoad, 4)); - dumpCode.push_back(outs[o].basetypeID); - dumpCode.push_back(loaded); - dumpCode.push_back(patchData.outputs[o].ID); - } - else - { - uint32_t readPtr = idBound++; - loaded = idBound++; - - // structure member, need to access chain first - dumpCode.push_back( - MakeSPIRVOp(spv::OpAccessChain, 4 + (uint32_t)patchData.outputs[o].accessChain.size())); - dumpCode.push_back(outs[o].outputPtrID); - dumpCode.push_back(readPtr); // readPtr = - dumpCode.push_back(patchData.outputs[o].ID); // outStructWhatever - - for(uint32_t idx : patchData.outputs[o].accessChain) - dumpCode.push_back(outs[idx].constID); - - dumpCode.push_back(MakeSPIRVOp(spv::OpLoad, 4)); - dumpCode.push_back(outs[o].basetypeID); - dumpCode.push_back(loaded); - dumpCode.push_back(readPtr); - } - - // access chain the destination - uint32_t writePtr = idBound++; - dumpCode.push_back(MakeSPIRVOp(spv::OpAccessChain, 7)); - dumpCode.push_back(outs[o].uniformPtrID); - dumpCode.push_back(writePtr); - dumpCode.push_back(outBufferVarID); // outBuffer - dumpCode.push_back(outs[0].constID); // .verts - dumpCode.push_back(arraySlotID); // [arraySlot] - dumpCode.push_back(outs[o].constID); // .out_... - - dumpCode.push_back(MakeSPIRVOp(spv::OpStore, 3)); - dumpCode.push_back(writePtr); - dumpCode.push_back(loaded); - } - } - - // update these values, since vector will have resized and/or reallocated above - spirv = &modSpirv[0]; - spirvLength = modSpirv.size(); - - bool infunc = false; - - it = 5; - while(it < spirvLength) - { - uint16_t WordCount = spirv[it] >> spv::WordCountShift; - spv::Op opcode = spv::Op(spirv[it] & spv::OpCodeMask); - - // find the start of the entry point - if(opcode == spv::OpFunction && spirv[it + 2] == entryID) - infunc = true; - - // insert the dumpCode before any spv::OpReturn. - // we should not have any spv::OpReturnValue since this is - // the entry point. Neither should we have OpKill etc. - if(infunc && opcode == spv::OpReturn) - { - modSpirv.insert(modSpirv.begin() + it, dumpCode.begin(), dumpCode.end()); - - it += dumpCode.size(); - - // update these values, since vector will have resized and/or reallocated above - spirv = &modSpirv[0]; - spirvLength = modSpirv.size(); - } - - // done patching entry point - if(opcode == spv::OpFunctionEnd && infunc) - break; - - it += WordCount; - } - - // patch up the new id bound - spirv[3] = idBound; -} - -void VulkanDebugManager::ClearPostVSCache() -{ - VkDevice dev = m_Device; - - for(auto it = m_PostVSData.begin(); it != m_PostVSData.end(); ++it) - { - m_pDriver->vkDestroyBuffer(dev, it->second.vsout.buf, NULL); - m_pDriver->vkDestroyBuffer(dev, it->second.vsout.idxBuf, NULL); - m_pDriver->vkFreeMemory(dev, it->second.vsout.bufmem, NULL); - m_pDriver->vkFreeMemory(dev, it->second.vsout.idxBufMem, NULL); - } - - m_PostVSData.clear(); -} - -void VulkanDebugManager::InitPostVSBuffers(uint32_t eventId) -{ - // go through any aliasing - if(m_PostVSAlias.find(eventId) != m_PostVSAlias.end()) - eventId = m_PostVSAlias[eventId]; - - if(m_PostVSData.find(eventId) != m_PostVSData.end()) - return; - - if(!m_pDriver->GetDeviceFeatures().vertexPipelineStoresAndAtomics) - return; - - const VulkanRenderState &state = m_pDriver->m_RenderState; - VulkanCreationInfo &creationInfo = m_pDriver->m_CreationInfo; - - if(state.graphics.pipeline == ResourceId() || state.renderPass == ResourceId()) - return; - - const VulkanCreationInfo::Pipeline &pipeInfo = creationInfo.m_Pipeline[state.graphics.pipeline]; - - if(pipeInfo.shaders[0].module == ResourceId()) - return; - - const VulkanCreationInfo::ShaderModule &moduleInfo = - creationInfo.m_ShaderModule[pipeInfo.shaders[0].module]; - - ShaderReflection *refl = pipeInfo.shaders[0].refl; - - // no outputs from this shader? unexpected but theoretically possible (dummy VS before - // tessellation maybe). Just fill out an empty data set - if(refl->outputSignature.empty()) - { - // empty vertex output signature - m_PostVSData[eventId].vsin.topo = pipeInfo.topology; - m_PostVSData[eventId].vsout.buf = VK_NULL_HANDLE; - m_PostVSData[eventId].vsout.instStride = 0; - m_PostVSData[eventId].vsout.vertStride = 0; - m_PostVSData[eventId].vsout.nearPlane = 0.0f; - m_PostVSData[eventId].vsout.farPlane = 0.0f; - m_PostVSData[eventId].vsout.useIndices = false; - m_PostVSData[eventId].vsout.hasPosOut = false; - m_PostVSData[eventId].vsout.idxBuf = VK_NULL_HANDLE; - - m_PostVSData[eventId].vsout.topo = pipeInfo.topology; - - return; - } - - const DrawcallDescription *drawcall = m_pDriver->GetDrawcall(eventId); - - if(drawcall == NULL || drawcall->numIndices == 0 || drawcall->numInstances == 0) - return; - - // the SPIR-V patching will determine the next descriptor set to use, after all sets statically - // used by the shader. This gets around the problem where the shader only uses 0 and 1, but the - // layout declares 0-4, and 2,3,4 are invalid at bind time and we are unable to bind our new set - // 5. Instead we'll notice that only 0 and 1 are used and just use 2 ourselves (although it was in - // the original set layout, we know it's statically unused by the shader so we can safely steal - // it). - uint32_t descSet = 0; - - // we go through the driver for all these creations since they need to be properly - // registered in order to be put in the partial replay state - VkResult vkr = VK_SUCCESS; - VkDevice dev = m_Device; - - VkPipelineLayout pipeLayout; - - VkGraphicsPipelineCreateInfo pipeCreateInfo; - - // get pipeline create info - MakeGraphicsPipelineInfo(pipeCreateInfo, state.graphics.pipeline); - - // set primitive topology to point list - VkPipelineInputAssemblyStateCreateInfo *ia = - (VkPipelineInputAssemblyStateCreateInfo *)pipeCreateInfo.pInputAssemblyState; - - VkPrimitiveTopology topo = ia->topology; - - ia->topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST; - - // remove all stages but the vertex shader, we just want to run it and write the data, - // we don't want to tessellate/geometry shade, nor rasterize (which we disable below) - uint32_t vertIdx = pipeCreateInfo.stageCount; - - for(uint32_t i = 0; i < pipeCreateInfo.stageCount; i++) - { - if(pipeCreateInfo.pStages[i].stage & VK_SHADER_STAGE_VERTEX_BIT) - { - vertIdx = i; - break; - } - } - - RDCASSERT(vertIdx < pipeCreateInfo.stageCount); - - if(vertIdx != 0) - (VkPipelineShaderStageCreateInfo &)pipeCreateInfo.pStages[0] = pipeCreateInfo.pStages[vertIdx]; - - pipeCreateInfo.stageCount = 1; - - // enable rasterizer discard - VkPipelineRasterizationStateCreateInfo *rs = - (VkPipelineRasterizationStateCreateInfo *)pipeCreateInfo.pRasterizationState; - rs->rasterizerDiscardEnable = true; - - VkBuffer meshBuffer = VK_NULL_HANDLE, readbackBuffer = VK_NULL_HANDLE; - VkDeviceMemory meshMem = VK_NULL_HANDLE, readbackMem = VK_NULL_HANDLE; - - VkBuffer idxBuf = VK_NULL_HANDLE, uniqIdxBuf = VK_NULL_HANDLE; - VkDeviceMemory idxBufMem = VK_NULL_HANDLE, uniqIdxBufMem = VK_NULL_HANDLE; - - uint32_t numVerts = drawcall->numIndices; - VkDeviceSize bufSize = 0; - - vector indices; - uint32_t idxsize = state.ibuffer.bytewidth; - bool index16 = (idxsize == 2); - uint32_t numIndices = numVerts; - bytebuf idxdata; - uint16_t *idx16 = NULL; - uint32_t *idx32 = NULL; - - uint32_t minIndex = 0, maxIndex = 0; - - uint32_t vertexIndexOffset = 0; - - if(drawcall->flags & DrawFlags::UseIBuffer) - { - // fetch ibuffer - GetBufferData(state.ibuffer.buf, state.ibuffer.offs + drawcall->indexOffset * idxsize, - uint64_t(drawcall->numIndices) * idxsize, idxdata); - - // figure out what the maximum index could be, so we can clamp our index buffer to something - // sane - uint32_t maxIdx = 0; - - // if there are no active bindings assume the vertex shader is generating its own data - // and don't clamp the indices - if(pipeCreateInfo.pVertexInputState->vertexBindingDescriptionCount == 0) - maxIdx = ~0U; - - for(uint32_t b = 0; b < pipeCreateInfo.pVertexInputState->vertexBindingDescriptionCount; b++) - { - const VkVertexInputBindingDescription &input = - pipeCreateInfo.pVertexInputState->pVertexBindingDescriptions[b]; - // only vertex inputs (not instance inputs) count - if(input.inputRate == VK_VERTEX_INPUT_RATE_VERTEX) - { - if(b >= state.vbuffers.size()) - continue; - - ResourceId buf = state.vbuffers[b].buf; - VkDeviceSize offs = state.vbuffers[b].offs; - - VkDeviceSize bufsize = creationInfo.m_Buffer[buf].size; - - // the maximum valid index on this particular input is the one that reaches - // the end of the buffer. The maximum valid index at all is the one that reads - // off the end of ALL buffers (so we max it with any other maxindex value - // calculated). - if(input.stride > 0) - maxIdx = RDCMAX(maxIdx, uint32_t((bufsize - offs) / input.stride)); - } - } - - // in case the vertex buffers were set but had invalid stride (0), max with the number - // of vertices too. This is fine since the max here is just a conservative limit - maxIdx = RDCMAX(maxIdx, drawcall->numIndices); - - // do ibuffer rebasing/remapping - - idx16 = (uint16_t *)&idxdata[0]; - idx32 = (uint32_t *)&idxdata[0]; - - // only read as many indices as were available in the buffer - numIndices = - RDCMIN(uint32_t(index16 ? idxdata.size() / 2 : idxdata.size() / 4), drawcall->numIndices); - - // grab all unique vertex indices referenced - for(uint32_t i = 0; i < numIndices; i++) - { - uint32_t i32 = index16 ? uint32_t(idx16[i]) : idx32[i]; - - // we clamp to maxIdx here, to avoid any invalid indices like 0xffffffff - // from filtering through. Worst case we index to the end of the vertex - // buffers which is generally much more reasonable - i32 = RDCMIN(maxIdx, i32); - - 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); - - minIndex = indices[0]; - maxIndex = indices[indices.size() - 1]; - - vertexIndexOffset = minIndex + drawcall->baseVertex; - - // set numVerts - numVerts = maxIndex - minIndex + 1; - - // create buffer with unique 0-based indices - VkBufferCreateInfo bufInfo = { - VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, - NULL, - 0, - indices.size() * sizeof(uint32_t), - VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, - }; - - vkr = m_pDriver->vkCreateBuffer(dev, &bufInfo, NULL, &uniqIdxBuf); - RDCASSERTEQUAL(vkr, VK_SUCCESS); - - VkMemoryRequirements mrq = {0}; - m_pDriver->vkGetBufferMemoryRequirements(dev, uniqIdxBuf, &mrq); - - VkMemoryAllocateInfo allocInfo = { - VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, NULL, mrq.size, - m_pDriver->GetUploadMemoryIndex(mrq.memoryTypeBits), - }; - - vkr = m_pDriver->vkAllocateMemory(dev, &allocInfo, NULL, &uniqIdxBufMem); - RDCASSERTEQUAL(vkr, VK_SUCCESS); - - vkr = m_pDriver->vkBindBufferMemory(dev, uniqIdxBuf, uniqIdxBufMem, 0); - RDCASSERTEQUAL(vkr, VK_SUCCESS); - - byte *idxData = NULL; - vkr = m_pDriver->vkMapMemory(m_Device, uniqIdxBufMem, 0, VK_WHOLE_SIZE, 0, (void **)&idxData); - RDCASSERTEQUAL(vkr, VK_SUCCESS); - - memcpy(idxData, &indices[0], indices.size() * sizeof(uint32_t)); - - m_pDriver->vkUnmapMemory(m_Device, uniqIdxBufMem); - - bufInfo.size = numIndices * idxsize; - - vkr = m_pDriver->vkCreateBuffer(dev, &bufInfo, NULL, &idxBuf); - RDCASSERTEQUAL(vkr, VK_SUCCESS); - - m_pDriver->vkGetBufferMemoryRequirements(dev, idxBuf, &mrq); - - allocInfo.allocationSize = mrq.size; - allocInfo.memoryTypeIndex = m_pDriver->GetUploadMemoryIndex(mrq.memoryTypeBits); - - vkr = m_pDriver->vkAllocateMemory(dev, &allocInfo, NULL, &idxBufMem); - RDCASSERTEQUAL(vkr, VK_SUCCESS); - - vkr = m_pDriver->vkBindBufferMemory(dev, idxBuf, idxBufMem, 0); - RDCASSERTEQUAL(vkr, VK_SUCCESS); - } - else - { - // firstVertex - vertexIndexOffset = drawcall->vertexOffset; - } - - uint32_t bufStride = 0; - vector modSpirv = moduleInfo.spirv.spirv; - - AddOutputDumping(*refl, *pipeInfo.shaders[0].patchData, pipeInfo.shaders[0].entryPoint.c_str(), - descSet, vertexIndexOffset, drawcall->instanceOffset, numVerts, modSpirv, - bufStride); - - { - VkDescriptorSetLayout *descSetLayouts; - - // descSet will be the index of our new descriptor set - descSetLayouts = new VkDescriptorSetLayout[descSet + 1]; - - for(uint32_t i = 0; i < descSet; i++) - descSetLayouts[i] = GetResourceManager()->GetCurrentHandle( - creationInfo.m_PipelineLayout[pipeInfo.layout].descSetLayouts[i]); - - // this layout just says it has one storage buffer - descSetLayouts[descSet] = m_MeshFetchDescSetLayout; - - const vector &push = - creationInfo.m_PipelineLayout[pipeInfo.layout].pushRanges; - - VkPipelineLayoutCreateInfo pipeLayoutInfo = { - VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, - NULL, - 0, - descSet + 1, - descSetLayouts, - (uint32_t)push.size(), - push.empty() ? NULL : &push[0], - }; - - // create pipeline layout with same descriptor set layouts, plus our mesh output set - vkr = m_pDriver->vkCreatePipelineLayout(dev, &pipeLayoutInfo, NULL, &pipeLayout); - RDCASSERTEQUAL(vkr, VK_SUCCESS); - - SAFE_DELETE_ARRAY(descSetLayouts); - - // repoint pipeline layout - pipeCreateInfo.layout = pipeLayout; - } - - // create vertex shader with modified code - VkShaderModuleCreateInfo moduleCreateInfo = { - VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, NULL, 0, - modSpirv.size() * sizeof(uint32_t), &modSpirv[0], - }; - - VkShaderModule module; - vkr = m_pDriver->vkCreateShaderModule(dev, &moduleCreateInfo, NULL, &module); - RDCASSERTEQUAL(vkr, VK_SUCCESS); - - // change vertex shader to use our modified code - for(uint32_t i = 0; i < pipeCreateInfo.stageCount; i++) - { - VkPipelineShaderStageCreateInfo &sh = - (VkPipelineShaderStageCreateInfo &)pipeCreateInfo.pStages[i]; - if(sh.stage == VK_SHADER_STAGE_VERTEX_BIT) - { - sh.module = module; - // entry point name remains the same - break; - } - } - - // create new pipeline - VkPipeline pipe; - vkr = m_pDriver->vkCreateGraphicsPipelines(m_Device, VK_NULL_HANDLE, 1, &pipeCreateInfo, NULL, - &pipe); - RDCASSERTEQUAL(vkr, VK_SUCCESS); - - // make copy of state to draw from - VulkanRenderState modifiedstate = state; - - // bind created pipeline to partial replay state - modifiedstate.graphics.pipeline = GetResID(pipe); - - // push back extra descriptor set to partial replay state - // note that we examined the used pipeline layout above and inserted our descriptor set - // after any the application used. So there might be more bound, but we want to ensure to - // bind to the slot we're using - modifiedstate.graphics.descSets.resize(descSet + 1); - modifiedstate.graphics.descSets[descSet].descSet = GetResID(m_MeshFetchDescSet); - - if(!(drawcall->flags & DrawFlags::UseIBuffer)) - { - // create buffer of sufficient size (num indices * bufStride) - VkBufferCreateInfo bufInfo = { - VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, - NULL, - 0, - drawcall->numIndices * drawcall->numInstances * bufStride, - 0, - }; - - bufSize = bufInfo.size; - - bufInfo.usage |= VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - bufInfo.usage |= VK_BUFFER_USAGE_TRANSFER_DST_BIT; - bufInfo.usage |= VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; - bufInfo.usage |= VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; - - vkr = m_pDriver->vkCreateBuffer(dev, &bufInfo, NULL, &meshBuffer); - RDCASSERTEQUAL(vkr, VK_SUCCESS); - - bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; - - vkr = m_pDriver->vkCreateBuffer(dev, &bufInfo, NULL, &readbackBuffer); - RDCASSERTEQUAL(vkr, VK_SUCCESS); - - VkMemoryRequirements mrq = {0}; - m_pDriver->vkGetBufferMemoryRequirements(dev, meshBuffer, &mrq); - - VkMemoryAllocateInfo allocInfo = { - VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, NULL, mrq.size, - m_pDriver->GetGPULocalMemoryIndex(mrq.memoryTypeBits), - }; - - vkr = m_pDriver->vkAllocateMemory(dev, &allocInfo, NULL, &meshMem); - RDCASSERTEQUAL(vkr, VK_SUCCESS); - - vkr = m_pDriver->vkBindBufferMemory(dev, meshBuffer, meshMem, 0); - RDCASSERTEQUAL(vkr, VK_SUCCESS); - - m_pDriver->vkGetBufferMemoryRequirements(dev, readbackBuffer, &mrq); - - allocInfo.memoryTypeIndex = m_pDriver->GetReadbackMemoryIndex(mrq.memoryTypeBits); - - vkr = m_pDriver->vkAllocateMemory(dev, &allocInfo, NULL, &readbackMem); - RDCASSERTEQUAL(vkr, VK_SUCCESS); - - vkr = m_pDriver->vkBindBufferMemory(dev, readbackBuffer, readbackMem, 0); - RDCASSERTEQUAL(vkr, VK_SUCCESS); - - // vkUpdateDescriptorSet desc set to point to buffer - VkDescriptorBufferInfo fetchdesc = {0}; - fetchdesc.buffer = meshBuffer; - fetchdesc.offset = 0; - fetchdesc.range = bufInfo.size; - - VkWriteDescriptorSet write = { - VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, m_MeshFetchDescSet, 0, 0, 1, - VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, NULL, &fetchdesc, NULL}; - m_pDriver->vkUpdateDescriptorSets(dev, 1, &write, 0, NULL); - - VkCommandBuffer cmd = m_pDriver->GetNextCmd(); - - VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, NULL, - VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT}; - - vkr = ObjDisp(dev)->BeginCommandBuffer(Unwrap(cmd), &beginInfo); - RDCASSERTEQUAL(vkr, VK_SUCCESS); - - // do single draw - modifiedstate.BeginRenderPassAndApplyState(cmd, VulkanRenderState::BindGraphics); - ObjDisp(cmd)->CmdDraw(Unwrap(cmd), drawcall->numIndices, drawcall->numInstances, - drawcall->vertexOffset, drawcall->instanceOffset); - modifiedstate.EndRenderPass(cmd); - - VkBufferMemoryBarrier meshbufbarrier = { - VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, - NULL, - VK_ACCESS_SHADER_WRITE_BIT, - VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT, - VK_QUEUE_FAMILY_IGNORED, - VK_QUEUE_FAMILY_IGNORED, - Unwrap(meshBuffer), - 0, - bufInfo.size, - }; - - // wait for writing to finish - DoPipelineBarrier(cmd, 1, &meshbufbarrier); - - VkBufferCopy bufcopy = { - 0, 0, bufInfo.size, - }; - - // copy to readback buffer - ObjDisp(dev)->CmdCopyBuffer(Unwrap(cmd), Unwrap(meshBuffer), Unwrap(readbackBuffer), 1, &bufcopy); - - meshbufbarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - meshbufbarrier.dstAccessMask = VK_ACCESS_HOST_READ_BIT; - meshbufbarrier.buffer = Unwrap(readbackBuffer); - - // wait for copy to finish - DoPipelineBarrier(cmd, 1, &meshbufbarrier); - - vkr = ObjDisp(dev)->EndCommandBuffer(Unwrap(cmd)); - RDCASSERTEQUAL(vkr, VK_SUCCESS); - - // submit & flush so that we don't have to keep pipeline around for a while - m_pDriver->SubmitCmds(); - m_pDriver->FlushQ(); - } - else - { - // create buffer of sufficient size - // this can't just be bufStride * num unique indices per instance, as we don't - // have a compact 0-based index to index into the buffer. We must use - // index-minIndex which is 0-based but potentially sparse, so this buffer may - // be more or less wasteful - VkBufferCreateInfo bufInfo = { - VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, NULL, 0, - numVerts * drawcall->numInstances * bufStride, 0, - }; - - bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - bufInfo.usage |= VK_BUFFER_USAGE_TRANSFER_DST_BIT; - bufInfo.usage |= VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; - bufInfo.usage |= VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; - - vkr = m_pDriver->vkCreateBuffer(dev, &bufInfo, NULL, &meshBuffer); - RDCASSERTEQUAL(vkr, VK_SUCCESS); - - bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; - - vkr = m_pDriver->vkCreateBuffer(dev, &bufInfo, NULL, &readbackBuffer); - RDCASSERTEQUAL(vkr, VK_SUCCESS); - - VkMemoryRequirements mrq = {0}; - m_pDriver->vkGetBufferMemoryRequirements(dev, meshBuffer, &mrq); - - VkMemoryAllocateInfo allocInfo = { - VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, NULL, mrq.size, - m_pDriver->GetGPULocalMemoryIndex(mrq.memoryTypeBits), - }; - - vkr = m_pDriver->vkAllocateMemory(dev, &allocInfo, NULL, &meshMem); - RDCASSERTEQUAL(vkr, VK_SUCCESS); - - vkr = m_pDriver->vkBindBufferMemory(dev, meshBuffer, meshMem, 0); - RDCASSERTEQUAL(vkr, VK_SUCCESS); - - m_pDriver->vkGetBufferMemoryRequirements(dev, readbackBuffer, &mrq); - - allocInfo.memoryTypeIndex = m_pDriver->GetReadbackMemoryIndex(mrq.memoryTypeBits); - - vkr = m_pDriver->vkAllocateMemory(dev, &allocInfo, NULL, &readbackMem); - RDCASSERTEQUAL(vkr, VK_SUCCESS); - - vkr = m_pDriver->vkBindBufferMemory(dev, readbackBuffer, readbackMem, 0); - RDCASSERTEQUAL(vkr, VK_SUCCESS); - - VkBufferMemoryBarrier meshbufbarrier = { - VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, - NULL, - VK_ACCESS_HOST_WRITE_BIT, - VK_ACCESS_INDEX_READ_BIT, - VK_QUEUE_FAMILY_IGNORED, - VK_QUEUE_FAMILY_IGNORED, - Unwrap(uniqIdxBuf), - 0, - indices.size() * sizeof(uint32_t), - }; - - VkCommandBuffer cmd = m_pDriver->GetNextCmd(); - - VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, NULL, - VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT}; - - vkr = ObjDisp(dev)->BeginCommandBuffer(Unwrap(cmd), &beginInfo); - RDCASSERTEQUAL(vkr, VK_SUCCESS); - - // wait for upload to finish - DoPipelineBarrier(cmd, 1, &meshbufbarrier); - - // fill destination buffer with 0s to ensure unwritten vertices have sane data - ObjDisp(dev)->CmdFillBuffer(Unwrap(cmd), Unwrap(meshBuffer), 0, bufInfo.size, 0); - - // wait to finish - meshbufbarrier.buffer = Unwrap(meshBuffer); - meshbufbarrier.size = bufInfo.size; - DoPipelineBarrier(cmd, 1, &meshbufbarrier); - - // set bufSize - bufSize = numVerts * drawcall->numInstances * bufStride; - - // bind unique'd ibuffer - modifiedstate.ibuffer.bytewidth = 4; - modifiedstate.ibuffer.offs = 0; - modifiedstate.ibuffer.buf = GetResID(uniqIdxBuf); - - // vkUpdateDescriptorSet desc set to point to buffer - VkDescriptorBufferInfo fetchdesc = {0}; - fetchdesc.buffer = meshBuffer; - fetchdesc.offset = 0; - fetchdesc.range = bufInfo.size; - - VkWriteDescriptorSet write = { - VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, m_MeshFetchDescSet, 0, 0, 1, - VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, NULL, &fetchdesc, NULL}; - m_pDriver->vkUpdateDescriptorSets(dev, 1, &write, 0, NULL); - - // do single draw - modifiedstate.BeginRenderPassAndApplyState(cmd, VulkanRenderState::BindGraphics); - ObjDisp(cmd)->CmdDrawIndexed(Unwrap(cmd), (uint32_t)indices.size(), drawcall->numInstances, 0, - drawcall->baseVertex, drawcall->instanceOffset); - modifiedstate.EndRenderPass(cmd); - - // rebase existing index buffer to point to the right elements in our stream-out'd - // vertex buffer - - // An index buffer could be something like: 500, 520, 518, 553, 554, 556 - // 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 index 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 in the indices as above which must remain as - // we don't have a 0-based dense 'vertex id' to base our SSBO indexing off, only index value. - - bool stripRestart = pipeCreateInfo.pInputAssemblyState->primitiveRestartEnable == VK_TRUE && - IsStrip(drawcall->topology); - - if(index16) - { - for(uint32_t i = 0; i < numIndices; i++) - { - if(stripRestart && idx16[i] == 0xffff) - continue; - - idx16[i] = idx16[i] - uint16_t(minIndex); - } - } - else - { - for(uint32_t i = 0; i < numIndices; i++) - { - if(stripRestart && idx32[i] == 0xffffffff) - continue; - - idx32[i] -= minIndex; - } - } - - // upload rebased memory - byte *idxData = NULL; - vkr = m_pDriver->vkMapMemory(m_Device, idxBufMem, 0, VK_WHOLE_SIZE, 0, (void **)&idxData); - RDCASSERTEQUAL(vkr, VK_SUCCESS); - - memcpy(idxData, idx32, numIndices * idxsize); - - m_pDriver->vkUnmapMemory(m_Device, idxBufMem); - - meshbufbarrier.buffer = Unwrap(idxBuf); - meshbufbarrier.size = numIndices * idxsize; - - // wait for upload to finish - DoPipelineBarrier(cmd, 1, &meshbufbarrier); - - // wait for mesh output writing to finish - meshbufbarrier.buffer = Unwrap(meshBuffer); - meshbufbarrier.size = bufSize; - meshbufbarrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; - meshbufbarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; - - DoPipelineBarrier(cmd, 1, &meshbufbarrier); - - VkBufferCopy bufcopy = { - 0, 0, bufInfo.size, - }; - - // copy to readback buffer - ObjDisp(dev)->CmdCopyBuffer(Unwrap(cmd), Unwrap(meshBuffer), Unwrap(readbackBuffer), 1, &bufcopy); - - meshbufbarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - meshbufbarrier.dstAccessMask = VK_ACCESS_HOST_READ_BIT; - meshbufbarrier.buffer = Unwrap(readbackBuffer); - - // wait for copy to finish - DoPipelineBarrier(cmd, 1, &meshbufbarrier); - - vkr = ObjDisp(dev)->EndCommandBuffer(Unwrap(cmd)); - RDCASSERTEQUAL(vkr, VK_SUCCESS); - - // submit & flush so that we don't have to keep pipeline around for a while - m_pDriver->SubmitCmds(); - m_pDriver->FlushQ(); - } - - // readback mesh data - byte *byteData = NULL; - vkr = m_pDriver->vkMapMemory(m_Device, readbackMem, 0, VK_WHOLE_SIZE, 0, (void **)&byteData); - - // do near/far calculations - - float nearp = 0.1f; - float farp = 100.0f; - - Vec4f *pos0 = (Vec4f *)byteData; - - bool found = false; - - // expect position at the start of the buffer, as system values are sorted first - // and position is the first value - - for(uint32_t i = 1; - refl->outputSignature[0].systemValue == ShaderBuiltin::Position && i < 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 * bufStride); - - // skip invalid vertices (w=0) - if(pos->w != 0.0f && 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; - - if(-c / m <= 0.000001f) - 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; - } - - m_pDriver->vkUnmapMemory(m_Device, readbackMem); - - // clean up temporary memories - m_pDriver->vkDestroyBuffer(m_Device, readbackBuffer, NULL); - m_pDriver->vkFreeMemory(m_Device, readbackMem, NULL); - - if(uniqIdxBuf != VK_NULL_HANDLE) - { - m_pDriver->vkDestroyBuffer(m_Device, uniqIdxBuf, NULL); - m_pDriver->vkFreeMemory(m_Device, uniqIdxBufMem, NULL); - } - - // fill out m_PostVSData - m_PostVSData[eventId].vsin.topo = topo; - m_PostVSData[eventId].vsout.topo = topo; - m_PostVSData[eventId].vsout.buf = meshBuffer; - m_PostVSData[eventId].vsout.bufmem = meshMem; - - m_PostVSData[eventId].vsout.vertStride = bufStride; - 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 = uint32_t(bufSize / drawcall->numInstances); - - m_PostVSData[eventId].vsout.idxBuf = VK_NULL_HANDLE; - if(m_PostVSData[eventId].vsout.useIndices && idxBuf != VK_NULL_HANDLE) - { - m_PostVSData[eventId].vsout.idxBuf = idxBuf; - m_PostVSData[eventId].vsout.idxBufMem = idxBufMem; - m_PostVSData[eventId].vsout.idxFmt = - state.ibuffer.bytewidth == 2 ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32; - } - - m_PostVSData[eventId].vsout.hasPosOut = - refl->outputSignature[0].systemValue == ShaderBuiltin::Position; - - // delete pipeline layout - m_pDriver->vkDestroyPipelineLayout(dev, pipeLayout, NULL); - - // delete pipeline - m_pDriver->vkDestroyPipeline(dev, pipe, NULL); - - // delete shader/shader module - m_pDriver->vkDestroyShaderModule(dev, module, NULL); -} - -MeshFormat VulkanDebugManager::GetPostVSBuffers(uint32_t eventId, uint32_t instID, MeshDataStage stage) -{ - // go through any aliasing - if(m_PostVSAlias.find(eventId) != m_PostVSAlias.end()) - eventId = m_PostVSAlias[eventId]; - - VulkanPostVSData postvs; - RDCEraseEl(postvs); - - if(m_PostVSData.find(eventId) != m_PostVSData.end()) - postvs = m_PostVSData[eventId]; - - VulkanPostVSData::StageData s = postvs.GetStage(stage); - - MeshFormat ret; - - if(s.useIndices && s.idxBuf != VK_NULL_HANDLE) - { - ret.indexResourceId = GetResID(s.idxBuf); - ret.indexByteStride = s.idxFmt == VK_INDEX_TYPE_UINT16 ? 2 : 4; - } - else - { - ret.indexResourceId = ResourceId(); - ret.indexByteStride = 0; - } - ret.indexByteOffset = 0; - ret.baseVertex = 0; - - if(s.buf != VK_NULL_HANDLE) - ret.vertexResourceId = GetResID(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 = MakePrimitiveTopology(s.topo, 1); - ret.numIndices = s.numVerts; - - ret.unproject = s.hasPosOut; - ret.nearPlane = s.nearPlane; - ret.farPlane = s.farPlane; - - return ret; -} diff --git a/renderdoc/driver/vulkan/vk_postvs.cpp b/renderdoc/driver/vulkan/vk_postvs.cpp new file mode 100644 index 000000000..463a60591 --- /dev/null +++ b/renderdoc/driver/vulkan/vk_postvs.cpp @@ -0,0 +1,1658 @@ +/****************************************************************************** + * 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 +#include "3rdparty/glslang/SPIRV/spirv.hpp" +#include "driver/shaders/spirv/spirv_common.h" +#include "vk_core.h" +#include "vk_debug.h" + +inline uint32_t MakeSPIRVOp(spv::Op op, uint32_t WordCount) +{ + return (uint32_t(op) & spv::OpCodeMask) | (WordCount << spv::WordCountShift); +} + +static void AddOutputDumping(const ShaderReflection &refl, const SPIRVPatchData &patchData, + const char *entryName, uint32_t &descSet, uint32_t vertexIndexOffset, + uint32_t instanceIndexOffset, uint32_t numVerts, + vector &modSpirv, uint32_t &bufStride) +{ + uint32_t *spirv = &modSpirv[0]; + size_t spirvLength = modSpirv.size(); + + int numOutputs = refl.outputSignature.count(); + + RDCASSERT(numOutputs > 0); + + // save the id bound. We use this whenever we need to allocate ourselves + // a new ID + uint32_t idBound = spirv[3]; + + // we do multiple passes through the SPIR-V to simplify logic, rather than + // trying to do as few passes as possible. + + // first try to find a few IDs of things we know we'll probably need: + // * gl_VertexID, gl_InstanceID (identified by a DecorationBuiltIn) + // * Int32 type, signed and unsigned + // * Float types, half, float and double + // * Input Pointer to Int32 (for declaring gl_VertexID) + // * UInt32 constants from 0 up to however many outputs we have + // * The entry point we're after + // + // At the same time we find the highest descriptor set used and add a + // new descriptor set binding on the end for our output buffer. This is + // much easier than trying to add a new bind to an existing descriptor + // set (which would cascade into a new descriptor set layout, new pipeline + // layout, etc etc!). However, this might push us over the limit on number + // of descriptor sets. + // + // we also note the index where decorations end, and the index where + // functions start, for if we need to add new decorations or new + // types/constants/global variables + uint32_t vertidxID = 0; + uint32_t instidxID = 0; + uint32_t sint32ID = 0; + uint32_t sint32PtrInID = 0; + uint32_t uint32ID = 0; + uint32_t halfID = 0; + uint32_t floatID = 0; + uint32_t doubleID = 0; + uint32_t entryID = 0; + + struct outputIDs + { + uint32_t constID; // constant ID for the index of this output + uint32_t basetypeID; // the type ID for this output. Must be present already by definition! + uint32_t uniformPtrID; // Uniform Pointer ID for this output. Used to write the output data + uint32_t outputPtrID; // Output Pointer ID for this output. Used to read the output data + }; + outputIDs outs[100] = {}; + + RDCASSERT(numOutputs < 100); + + size_t entryInterfaceOffset = 0; + size_t entryWordCountOffset = 0; + uint16_t entryWordCount = 0; + size_t decorateOffset = 0; + size_t typeVarOffset = 0; + + descSet = 0; + + size_t it = 5; + while(it < spirvLength) + { + uint16_t WordCount = spirv[it] >> spv::WordCountShift; + spv::Op opcode = spv::Op(spirv[it] & spv::OpCodeMask); + + // we will use the descriptor set immediately after the last set statically used by the shader. + // This means we don't have to worry about if the descriptor set layout declares more sets which + // might be invalid and un-bindable, we just trample over the next set that's unused + if(opcode == spv::OpDecorate && spirv[it + 2] == spv::DecorationDescriptorSet) + descSet = RDCMAX(descSet, spirv[it + 3] + 1); + + if(opcode == spv::OpDecorate && spirv[it + 2] == spv::DecorationBuiltIn && + spirv[it + 3] == spv::BuiltInVertexIndex) + vertidxID = spirv[it + 1]; + + if(opcode == spv::OpDecorate && spirv[it + 2] == spv::DecorationBuiltIn && + spirv[it + 3] == spv::BuiltInInstanceIndex) + instidxID = spirv[it + 1]; + + if(opcode == spv::OpTypeInt && spirv[it + 2] == 32 && spirv[it + 3] == 1) + sint32ID = spirv[it + 1]; + + if(opcode == spv::OpTypeInt && spirv[it + 2] == 32 && spirv[it + 3] == 0) + uint32ID = spirv[it + 1]; + + if(opcode == spv::OpTypeFloat && spirv[it + 2] == 16) + halfID = spirv[it + 1]; + + if(opcode == spv::OpTypeFloat && spirv[it + 2] == 32) + floatID = spirv[it + 1]; + + if(opcode == spv::OpTypeFloat && spirv[it + 2] == 64) + doubleID = spirv[it + 1]; + + if(opcode == spv::OpTypePointer && spirv[it + 2] == spv::StorageClassInput && + spirv[it + 3] == sint32ID) + sint32PtrInID = spirv[it + 1]; + + for(int i = 0; i < numOutputs; i++) + { + if(opcode == spv::OpConstant && spirv[it + 1] == uint32ID && spirv[it + 3] == (uint32_t)i) + { + if(outs[i].constID != 0) + RDCWARN("identical constant declared with two different IDs %u %u!", spirv[it + 2], + outs[i].constID); // not sure if this is valid or not + outs[i].constID = spirv[it + 2]; + } + + if(outs[i].basetypeID == 0) + { + if(refl.outputSignature[i].compCount > 1 && opcode == spv::OpTypeVector) + { + uint32_t baseID = 0; + + if(refl.outputSignature[i].compType == CompType::UInt) + baseID = uint32ID; + else if(refl.outputSignature[i].compType == CompType::SInt) + baseID = sint32ID; + else if(refl.outputSignature[i].compType == CompType::Float) + baseID = floatID; + else if(refl.outputSignature[i].compType == CompType::Double) + baseID = doubleID; + else + RDCERR("Unexpected component type for output signature element"); + + // if we have the base type, see if this is the right sized vector of that type + if(baseID != 0 && spirv[it + 2] == baseID && + spirv[it + 3] == refl.outputSignature[i].compCount) + outs[i].basetypeID = spirv[it + 1]; + } + + // handle non-vectors + if(refl.outputSignature[i].compCount == 1) + { + if(refl.outputSignature[i].compType == CompType::UInt) + outs[i].basetypeID = uint32ID; + else if(refl.outputSignature[i].compType == CompType::SInt) + outs[i].basetypeID = sint32ID; + else if(refl.outputSignature[i].compType == CompType::Float) + outs[i].basetypeID = floatID; + else if(refl.outputSignature[i].compType == CompType::Double) + outs[i].basetypeID = doubleID; + } + } + + // if we've found the base type, try and identify pointers to that type + if(outs[i].basetypeID != 0 && opcode == spv::OpTypePointer && + spirv[it + 2] == spv::StorageClassUniform && spirv[it + 3] == outs[i].basetypeID) + { + outs[i].uniformPtrID = spirv[it + 1]; + } + + if(outs[i].basetypeID != 0 && opcode == spv::OpTypePointer && + spirv[it + 2] == spv::StorageClassOutput && spirv[it + 3] == outs[i].basetypeID) + { + outs[i].outputPtrID = spirv[it + 1]; + } + } + + if(opcode == spv::OpEntryPoint) + { + const char *name = (const char *)&spirv[it + 3]; + + if(!strcmp(name, entryName)) + { + if(entryID != 0) + RDCERR("Same entry point declared twice! %s", entryName); + entryID = spirv[it + 2]; + } + + // need to update the WordCount when we add IDs, so store this + entryWordCountOffset = it; + entryWordCount = WordCount; + + // where to insert new interface IDs if we add them + entryInterfaceOffset = it + WordCount; + } + + // when we reach the types, decorations are over + if(decorateOffset == 0 && opcode >= spv::OpTypeVoid && opcode <= spv::OpTypeForwardPointer) + decorateOffset = it; + + // stop when we reach the functions, types are over + if(opcode == spv::OpFunction) + { + typeVarOffset = it; + break; + } + + it += WordCount; + } + + RDCASSERT(entryID != 0); + + for(int i = 0; i < numOutputs; i++) + { + // must have at least found the base type, or something has gone seriously wrong + RDCASSERT(outs[i].basetypeID != 0); + } + + // if needed add new ID for sint32 type + if(sint32ID == 0) + { + sint32ID = idBound++; + + uint32_t typeOp[] = { + MakeSPIRVOp(spv::OpTypeInt, 4), sint32ID, + 32U, // 32-bit + 1U, // signed + }; + + // insert at the end of the types/variables section + modSpirv.insert(modSpirv.begin() + typeVarOffset, typeOp, typeOp + ARRAY_COUNT(typeOp)); + + // update offsets to account for inserted op + typeVarOffset += ARRAY_COUNT(typeOp); + } + + // if needed, new ID for input ptr type + if(sint32PtrInID == 0 && (vertidxID == 0 || instidxID == 0)) + { + sint32PtrInID = idBound; + idBound++; + + uint32_t typeOp[] = { + MakeSPIRVOp(spv::OpTypePointer, 4), sint32PtrInID, spv::StorageClassInput, sint32ID, + }; + + // insert at the end of the types/variables section + modSpirv.insert(modSpirv.begin() + typeVarOffset, typeOp, typeOp + ARRAY_COUNT(typeOp)); + + // update offsets to account for inserted op + typeVarOffset += ARRAY_COUNT(typeOp); + } + + if(vertidxID == 0) + { + // need to declare our own "in int gl_VertexID;" + + // new ID for vertex index + vertidxID = idBound; + idBound++; + + uint32_t varOp[] = { + MakeSPIRVOp(spv::OpVariable, 4), + sint32PtrInID, // type + vertidxID, // variable id + spv::StorageClassInput, + }; + + // insert at the end of the types/variables section + modSpirv.insert(modSpirv.begin() + typeVarOffset, varOp, varOp + ARRAY_COUNT(varOp)); + + // update offsets to account for inserted op + typeVarOffset += ARRAY_COUNT(varOp); + + uint32_t decorateOp[] = { + MakeSPIRVOp(spv::OpDecorate, 4), vertidxID, spv::DecorationBuiltIn, spv::BuiltInVertexIndex, + }; + + // insert at the end of the decorations before the types + modSpirv.insert(modSpirv.begin() + decorateOffset, decorateOp, + decorateOp + ARRAY_COUNT(decorateOp)); + + // update offsets to account for inserted op + typeVarOffset += ARRAY_COUNT(decorateOp); + decorateOffset += ARRAY_COUNT(decorateOp); + + modSpirv[entryWordCountOffset] = MakeSPIRVOp(spv::OpEntryPoint, ++entryWordCount); + + // need to add this input to the declared interface on OpEntryPoint + modSpirv.insert(modSpirv.begin() + entryInterfaceOffset, vertidxID); + + // update offsets to account for inserted ID + entryInterfaceOffset++; + typeVarOffset++; + decorateOffset++; + } + + if(instidxID == 0) + { + // need to declare our own "in int gl_InstanceID;" + + // new ID for vertex index + instidxID = idBound; + idBound++; + + uint32_t varOp[] = { + MakeSPIRVOp(spv::OpVariable, 4), + sint32PtrInID, // type + instidxID, // variable id + spv::StorageClassInput, + }; + + // insert at the end of the types/variables section + modSpirv.insert(modSpirv.begin() + typeVarOffset, varOp, varOp + ARRAY_COUNT(varOp)); + + // update offsets to account for inserted op + typeVarOffset += ARRAY_COUNT(varOp); + + uint32_t decorateOp[] = { + MakeSPIRVOp(spv::OpDecorate, 4), instidxID, spv::DecorationBuiltIn, spv::BuiltInInstanceIndex, + }; + + // insert at the end of the decorations before the types + modSpirv.insert(modSpirv.begin() + decorateOffset, decorateOp, + decorateOp + ARRAY_COUNT(decorateOp)); + + // update offsets to account for inserted op + typeVarOffset += ARRAY_COUNT(decorateOp); + decorateOffset += ARRAY_COUNT(decorateOp); + + modSpirv[entryWordCountOffset] = MakeSPIRVOp(spv::OpEntryPoint, ++entryWordCount); + + // need to add this input to the declared interface on OpEntryPoint + modSpirv.insert(modSpirv.begin() + entryInterfaceOffset, instidxID); + + // update offsets to account for inserted ID + entryInterfaceOffset++; + typeVarOffset++; + decorateOffset++; + } + + // if needed add new ID for uint32 type + if(uint32ID == 0) + { + uint32ID = idBound++; + + uint32_t typeOp[] = { + MakeSPIRVOp(spv::OpTypeInt, 4), uint32ID, + 32U, // 32-bit + 0U, // unsigned + }; + + // insert at the end of the types/variables section + modSpirv.insert(modSpirv.begin() + typeVarOffset, typeOp, typeOp + ARRAY_COUNT(typeOp)); + + // update offsets to account for inserted op + typeVarOffset += ARRAY_COUNT(typeOp); + } + + // add any constants we're missing + for(int i = 0; i < numOutputs; i++) + { + if(outs[i].constID == 0) + { + outs[i].constID = idBound++; + + uint32_t constantOp[] = { + MakeSPIRVOp(spv::OpConstant, 4), uint32ID, outs[i].constID, (uint32_t)i, + }; + + // insert at the end of the types/variables/constants section + modSpirv.insert(modSpirv.begin() + typeVarOffset, constantOp, + constantOp + ARRAY_COUNT(constantOp)); + + // update offsets to account for inserted op + typeVarOffset += ARRAY_COUNT(constantOp); + } + } + + // add any uniform pointer types we're missing. Note that it's quite likely + // output types will overlap (think - 5 outputs, 3 of which are float4/vec4) + // so any time we create a new uniform pointer type, we update all subsequent + // outputs to refer to it. + for(int i = 0; i < numOutputs; i++) + { + if(outs[i].uniformPtrID == 0) + { + outs[i].uniformPtrID = idBound++; + + uint32_t typeOp[] = { + MakeSPIRVOp(spv::OpTypePointer, 4), outs[i].uniformPtrID, spv::StorageClassUniform, + outs[i].basetypeID, + }; + + // insert at the end of the types/variables/constants section + modSpirv.insert(modSpirv.begin() + typeVarOffset, typeOp, typeOp + ARRAY_COUNT(typeOp)); + + // update offsets to account for inserted op + typeVarOffset += ARRAY_COUNT(typeOp); + + // update subsequent outputs of identical type + for(int j = i + 1; j < numOutputs; j++) + { + if(outs[i].basetypeID == outs[j].basetypeID) + { + RDCASSERT(outs[j].uniformPtrID == 0); + outs[j].uniformPtrID = outs[i].uniformPtrID; + } + } + } + + // matrices would have been written through an output pointer of matrix type, but we're reading + // them vector-by-vector so we may need to declare an output pointer of the corresponding + // vector type. + // Otherwise, we expect to re-use the original SPIR-V's output pointer. + if(outs[i].outputPtrID == 0) + { + if(!patchData.outputs[i].isMatrix) + { + RDCERR("No output pointer ID found for non-matrix output %d: %s (%u %u)", i, + refl.outputSignature[i].varName.c_str(), refl.outputSignature[i].compType, + refl.outputSignature[i].compCount); + } + + outs[i].outputPtrID = idBound++; + + uint32_t typeOp[] = { + MakeSPIRVOp(spv::OpTypePointer, 4), outs[i].outputPtrID, spv::StorageClassOutput, + outs[i].basetypeID, + }; + + // insert at the end of the types/variables/constants section + modSpirv.insert(modSpirv.begin() + typeVarOffset, typeOp, typeOp + ARRAY_COUNT(typeOp)); + + // update offsets to account for inserted op + typeVarOffset += ARRAY_COUNT(typeOp); + + // update subsequent outputs of identical type + for(int j = i + 1; j < numOutputs; j++) + { + if(outs[i].basetypeID == outs[j].basetypeID) + { + RDCASSERT(outs[j].outputPtrID == 0); + outs[j].outputPtrID = outs[i].outputPtrID; + } + } + } + } + + uint32_t outBufferVarID = 0; + uint32_t numVertsConstID = 0; + uint32_t vertexIndexOffsetConstID = 0; + uint32_t instanceIndexOffsetConstID = 0; + + // now add the structure type etc for our output buffer + { + uint32_t vertStructID = idBound++; + + uint32_t vertStructOp[2 + 100] = { + MakeSPIRVOp(spv::OpTypeStruct, 2 + numOutputs), vertStructID, + }; + + for(int o = 0; o < numOutputs; o++) + vertStructOp[2 + o] = outs[o].basetypeID; + + // insert at the end of the types/variables section + modSpirv.insert(modSpirv.begin() + typeVarOffset, vertStructOp, vertStructOp + 2 + numOutputs); + + // update offsets to account for inserted op + typeVarOffset += 2 + numOutputs; + + uint32_t runtimeArrayID = idBound++; + + uint32_t runtimeArrayOp[] = { + MakeSPIRVOp(spv::OpTypeRuntimeArray, 3), runtimeArrayID, vertStructID, + }; + + // insert at the end of the types/variables section + modSpirv.insert(modSpirv.begin() + typeVarOffset, runtimeArrayOp, + runtimeArrayOp + ARRAY_COUNT(runtimeArrayOp)); + + // update offsets to account for inserted op + typeVarOffset += ARRAY_COUNT(runtimeArrayOp); + + // add a constant for the number of verts, the 'instance stride' of the array + numVertsConstID = idBound++; + + uint32_t instanceStrideConstOp[] = { + MakeSPIRVOp(spv::OpConstant, 4), sint32ID, numVertsConstID, numVerts, + }; + + // insert at the end of the types/variables section + modSpirv.insert(modSpirv.begin() + typeVarOffset, instanceStrideConstOp, + instanceStrideConstOp + ARRAY_COUNT(instanceStrideConstOp)); + + // update offsets to account for inserted op + typeVarOffset += ARRAY_COUNT(instanceStrideConstOp); + + // add a constant for the value that VertexIndex starts at, so we can get a 0-based vertex index + vertexIndexOffsetConstID = idBound++; + + uint32_t vertexIndexOffsetConstOp[] = { + MakeSPIRVOp(spv::OpConstant, 4), sint32ID, vertexIndexOffsetConstID, vertexIndexOffset, + }; + + // insert at the end of the types/variables section + modSpirv.insert(modSpirv.begin() + typeVarOffset, vertexIndexOffsetConstOp, + vertexIndexOffsetConstOp + ARRAY_COUNT(vertexIndexOffsetConstOp)); + + // update offsets to account for inserted op + typeVarOffset += ARRAY_COUNT(vertexIndexOffsetConstOp); + + // add a constant for the value that InstanceIndex starts at, so we can get a 0-based instance + // index + instanceIndexOffsetConstID = idBound++; + + uint32_t instanceIndexOffsetConstOp[] = { + MakeSPIRVOp(spv::OpConstant, 4), sint32ID, instanceIndexOffsetConstID, instanceIndexOffset, + }; + + // insert at the end of the types/variables section + modSpirv.insert(modSpirv.begin() + typeVarOffset, instanceIndexOffsetConstOp, + instanceIndexOffsetConstOp + ARRAY_COUNT(instanceIndexOffsetConstOp)); + + // update offsets to account for inserted op + typeVarOffset += ARRAY_COUNT(instanceIndexOffsetConstOp); + + uint32_t outputStructID = idBound++; + + uint32_t outputStructOp[] = { + MakeSPIRVOp(spv::OpTypeStruct, 3), outputStructID, runtimeArrayID, + }; + + // insert at the end of the types/variables section + modSpirv.insert(modSpirv.begin() + typeVarOffset, outputStructOp, + outputStructOp + ARRAY_COUNT(outputStructOp)); + + // update offsets to account for inserted op + typeVarOffset += ARRAY_COUNT(outputStructOp); + + uint32_t outputStructPtrID = idBound++; + + uint32_t outputStructPtrOp[] = { + MakeSPIRVOp(spv::OpTypePointer, 4), outputStructPtrID, spv::StorageClassUniform, + outputStructID, + }; + + // insert at the end of the types/variables section + modSpirv.insert(modSpirv.begin() + typeVarOffset, outputStructPtrOp, + outputStructPtrOp + ARRAY_COUNT(outputStructPtrOp)); + + // update offsets to account for inserted op + typeVarOffset += ARRAY_COUNT(outputStructPtrOp); + + outBufferVarID = idBound++; + + uint32_t outputVarOp[] = { + MakeSPIRVOp(spv::OpVariable, 4), outputStructPtrID, outBufferVarID, spv::StorageClassUniform, + }; + + // insert at the end of the types/variables section + modSpirv.insert(modSpirv.begin() + typeVarOffset, outputVarOp, + outputVarOp + ARRAY_COUNT(outputVarOp)); + + // update offsets to account for inserted op + typeVarOffset += ARRAY_COUNT(outputVarOp); + + // need to add decorations as appropriate + vector decorations; + + // reserve room for 1 member decorate per output, plus + // other fixed decorations + decorations.reserve(5 * numOutputs + 20); + + uint32_t memberOffset = 0; + for(int o = 0; o < numOutputs; o++) + { + uint32_t elemSize = 0; + if(refl.outputSignature[o].compType == CompType::Double) + elemSize = 8; + else if(refl.outputSignature[o].compType == CompType::SInt || + refl.outputSignature[o].compType == CompType::UInt || + refl.outputSignature[o].compType == CompType::Float) + elemSize = 4; + else + RDCERR("Unexpected component type for output signature element"); + + uint32_t numComps = refl.outputSignature[o].compCount; + + // ensure member is std430 packed (vec4 alignment for vec3/vec4) + if(numComps == 2) + memberOffset = AlignUp(memberOffset, 2U * elemSize); + else if(numComps > 2) + memberOffset = AlignUp(memberOffset, 4U * elemSize); + + decorations.push_back(MakeSPIRVOp(spv::OpMemberDecorate, 5)); + decorations.push_back(vertStructID); + decorations.push_back((uint32_t)o); + decorations.push_back(spv::DecorationOffset); + decorations.push_back(memberOffset); + + memberOffset += elemSize * refl.outputSignature[o].compCount; + } + + // align to 16 bytes (vec4) since we will almost certainly have + // a vec4 in the struct somewhere, and even in std430 alignment, + // the base struct alignment is still the largest base alignment + // of any member + memberOffset = AlignUp16(memberOffset); + + // the array is the only element in the output struct, so + // it's at offset 0 + decorations.push_back(MakeSPIRVOp(spv::OpMemberDecorate, 5)); + decorations.push_back(outputStructID); + decorations.push_back(0); + decorations.push_back(spv::DecorationOffset); + decorations.push_back(0); + + // set array stride + decorations.push_back(MakeSPIRVOp(spv::OpDecorate, 4)); + decorations.push_back(runtimeArrayID); + decorations.push_back(spv::DecorationArrayStride); + decorations.push_back(memberOffset); + + bufStride = memberOffset; + + // set object type + decorations.push_back(MakeSPIRVOp(spv::OpDecorate, 3)); + decorations.push_back(outputStructID); + decorations.push_back(spv::DecorationBufferBlock); + + // set binding + decorations.push_back(MakeSPIRVOp(spv::OpDecorate, 4)); + decorations.push_back(outBufferVarID); + decorations.push_back(spv::DecorationDescriptorSet); + decorations.push_back(descSet); + + decorations.push_back(MakeSPIRVOp(spv::OpDecorate, 4)); + decorations.push_back(outBufferVarID); + decorations.push_back(spv::DecorationBinding); + decorations.push_back(0); + + // insert at the end of the types/variables section + modSpirv.insert(modSpirv.begin() + decorateOffset, decorations.begin(), decorations.end()); + + // update offsets to account for inserted op + typeVarOffset += decorations.size(); + decorateOffset += decorations.size(); + } + + vector dumpCode; + + { + // bit of a conservative resize. Each output if in a struct could have + // AccessChain on source = 4 uint32s + // Load source = 4 uint32s + // AccessChain on dest = 7 uint32s + // Store dest = 3 uint32s + // + // loading the indices, and multiplying to get the destination array + // slot is constant on top of that + dumpCode.reserve(numOutputs * (4 + 4 + 7 + 3) + 4 + 4 + 5 + 5); + + uint32_t loadedVtxID = idBound++; + dumpCode.push_back(MakeSPIRVOp(spv::OpLoad, 4)); + dumpCode.push_back(sint32ID); + dumpCode.push_back(loadedVtxID); + dumpCode.push_back(vertidxID); + + uint32_t loadedInstID = idBound++; + dumpCode.push_back(MakeSPIRVOp(spv::OpLoad, 4)); + dumpCode.push_back(sint32ID); + dumpCode.push_back(loadedInstID); + dumpCode.push_back(instidxID); + + uint32_t rebasedInstID = idBound++; + dumpCode.push_back(MakeSPIRVOp(spv::OpISub, 5)); + dumpCode.push_back(sint32ID); + dumpCode.push_back(rebasedInstID); // rebasedInst = + dumpCode.push_back(loadedInstID); // gl_InstanceIndex - + dumpCode.push_back(instanceIndexOffsetConstID); // instanceIndexOffset + + uint32_t startVertID = idBound++; + dumpCode.push_back(MakeSPIRVOp(spv::OpIMul, 5)); + dumpCode.push_back(sint32ID); + dumpCode.push_back(startVertID); // startVert = + dumpCode.push_back(rebasedInstID); // rebasedInst * + dumpCode.push_back(numVertsConstID); // numVerts + + uint32_t rebasedVertID = idBound++; + dumpCode.push_back(MakeSPIRVOp(spv::OpISub, 5)); + dumpCode.push_back(sint32ID); + dumpCode.push_back(rebasedVertID); // rebasedVert = + dumpCode.push_back(loadedVtxID); // gl_VertexIndex - + dumpCode.push_back(vertexIndexOffsetConstID); // vertexIndexOffset + + uint32_t arraySlotID = idBound++; + dumpCode.push_back(MakeSPIRVOp(spv::OpIAdd, 5)); + dumpCode.push_back(sint32ID); + dumpCode.push_back(arraySlotID); // arraySlot = + dumpCode.push_back(startVertID); // startVert + + dumpCode.push_back(rebasedVertID); // rebasedVert + + for(int o = 0; o < numOutputs; o++) + { + uint32_t loaded = 0; + + // not a structure member or array child, can load directly + if(patchData.outputs[o].accessChain.empty()) + { + loaded = idBound++; + + dumpCode.push_back(MakeSPIRVOp(spv::OpLoad, 4)); + dumpCode.push_back(outs[o].basetypeID); + dumpCode.push_back(loaded); + dumpCode.push_back(patchData.outputs[o].ID); + } + else + { + uint32_t readPtr = idBound++; + loaded = idBound++; + + // structure member, need to access chain first + dumpCode.push_back( + MakeSPIRVOp(spv::OpAccessChain, 4 + (uint32_t)patchData.outputs[o].accessChain.size())); + dumpCode.push_back(outs[o].outputPtrID); + dumpCode.push_back(readPtr); // readPtr = + dumpCode.push_back(patchData.outputs[o].ID); // outStructWhatever + + for(uint32_t idx : patchData.outputs[o].accessChain) + dumpCode.push_back(outs[idx].constID); + + dumpCode.push_back(MakeSPIRVOp(spv::OpLoad, 4)); + dumpCode.push_back(outs[o].basetypeID); + dumpCode.push_back(loaded); + dumpCode.push_back(readPtr); + } + + // access chain the destination + uint32_t writePtr = idBound++; + dumpCode.push_back(MakeSPIRVOp(spv::OpAccessChain, 7)); + dumpCode.push_back(outs[o].uniformPtrID); + dumpCode.push_back(writePtr); + dumpCode.push_back(outBufferVarID); // outBuffer + dumpCode.push_back(outs[0].constID); // .verts + dumpCode.push_back(arraySlotID); // [arraySlot] + dumpCode.push_back(outs[o].constID); // .out_... + + dumpCode.push_back(MakeSPIRVOp(spv::OpStore, 3)); + dumpCode.push_back(writePtr); + dumpCode.push_back(loaded); + } + } + + // update these values, since vector will have resized and/or reallocated above + spirv = &modSpirv[0]; + spirvLength = modSpirv.size(); + + bool infunc = false; + + it = 5; + while(it < spirvLength) + { + uint16_t WordCount = spirv[it] >> spv::WordCountShift; + spv::Op opcode = spv::Op(spirv[it] & spv::OpCodeMask); + + // find the start of the entry point + if(opcode == spv::OpFunction && spirv[it + 2] == entryID) + infunc = true; + + // insert the dumpCode before any spv::OpReturn. + // we should not have any spv::OpReturnValue since this is + // the entry point. Neither should we have OpKill etc. + if(infunc && opcode == spv::OpReturn) + { + modSpirv.insert(modSpirv.begin() + it, dumpCode.begin(), dumpCode.end()); + + it += dumpCode.size(); + + // update these values, since vector will have resized and/or reallocated above + spirv = &modSpirv[0]; + spirvLength = modSpirv.size(); + } + + // done patching entry point + if(opcode == spv::OpFunctionEnd && infunc) + break; + + it += WordCount; + } + + // patch up the new id bound + spirv[3] = idBound; +} + +void VulkanDebugManager::ClearPostVSCache() +{ + VkDevice dev = m_Device; + + for(auto it = m_PostVSData.begin(); it != m_PostVSData.end(); ++it) + { + m_pDriver->vkDestroyBuffer(dev, it->second.vsout.buf, NULL); + m_pDriver->vkDestroyBuffer(dev, it->second.vsout.idxBuf, NULL); + m_pDriver->vkFreeMemory(dev, it->second.vsout.bufmem, NULL); + m_pDriver->vkFreeMemory(dev, it->second.vsout.idxBufMem, NULL); + } + + m_PostVSData.clear(); +} + +void VulkanDebugManager::InitPostVSBuffers(uint32_t eventId) +{ + // go through any aliasing + if(m_PostVSAlias.find(eventId) != m_PostVSAlias.end()) + eventId = m_PostVSAlias[eventId]; + + if(m_PostVSData.find(eventId) != m_PostVSData.end()) + return; + + if(!m_pDriver->GetDeviceFeatures().vertexPipelineStoresAndAtomics) + return; + + const VulkanRenderState &state = m_pDriver->m_RenderState; + VulkanCreationInfo &creationInfo = m_pDriver->m_CreationInfo; + + if(state.graphics.pipeline == ResourceId() || state.renderPass == ResourceId()) + return; + + const VulkanCreationInfo::Pipeline &pipeInfo = creationInfo.m_Pipeline[state.graphics.pipeline]; + + if(pipeInfo.shaders[0].module == ResourceId()) + return; + + const VulkanCreationInfo::ShaderModule &moduleInfo = + creationInfo.m_ShaderModule[pipeInfo.shaders[0].module]; + + ShaderReflection *refl = pipeInfo.shaders[0].refl; + + // no outputs from this shader? unexpected but theoretically possible (dummy VS before + // tessellation maybe). Just fill out an empty data set + if(refl->outputSignature.empty()) + { + // empty vertex output signature + m_PostVSData[eventId].vsin.topo = pipeInfo.topology; + m_PostVSData[eventId].vsout.buf = VK_NULL_HANDLE; + m_PostVSData[eventId].vsout.instStride = 0; + m_PostVSData[eventId].vsout.vertStride = 0; + m_PostVSData[eventId].vsout.nearPlane = 0.0f; + m_PostVSData[eventId].vsout.farPlane = 0.0f; + m_PostVSData[eventId].vsout.useIndices = false; + m_PostVSData[eventId].vsout.hasPosOut = false; + m_PostVSData[eventId].vsout.idxBuf = VK_NULL_HANDLE; + + m_PostVSData[eventId].vsout.topo = pipeInfo.topology; + + return; + } + + const DrawcallDescription *drawcall = m_pDriver->GetDrawcall(eventId); + + if(drawcall == NULL || drawcall->numIndices == 0 || drawcall->numInstances == 0) + return; + + // the SPIR-V patching will determine the next descriptor set to use, after all sets statically + // used by the shader. This gets around the problem where the shader only uses 0 and 1, but the + // layout declares 0-4, and 2,3,4 are invalid at bind time and we are unable to bind our new set + // 5. Instead we'll notice that only 0 and 1 are used and just use 2 ourselves (although it was in + // the original set layout, we know it's statically unused by the shader so we can safely steal + // it). + uint32_t descSet = 0; + + // we go through the driver for all these creations since they need to be properly + // registered in order to be put in the partial replay state + VkResult vkr = VK_SUCCESS; + VkDevice dev = m_Device; + + VkPipelineLayout pipeLayout; + + VkGraphicsPipelineCreateInfo pipeCreateInfo; + + // get pipeline create info + MakeGraphicsPipelineInfo(pipeCreateInfo, state.graphics.pipeline); + + // set primitive topology to point list + VkPipelineInputAssemblyStateCreateInfo *ia = + (VkPipelineInputAssemblyStateCreateInfo *)pipeCreateInfo.pInputAssemblyState; + + VkPrimitiveTopology topo = ia->topology; + + ia->topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST; + + // remove all stages but the vertex shader, we just want to run it and write the data, + // we don't want to tessellate/geometry shade, nor rasterize (which we disable below) + uint32_t vertIdx = pipeCreateInfo.stageCount; + + for(uint32_t i = 0; i < pipeCreateInfo.stageCount; i++) + { + if(pipeCreateInfo.pStages[i].stage & VK_SHADER_STAGE_VERTEX_BIT) + { + vertIdx = i; + break; + } + } + + RDCASSERT(vertIdx < pipeCreateInfo.stageCount); + + if(vertIdx != 0) + (VkPipelineShaderStageCreateInfo &)pipeCreateInfo.pStages[0] = pipeCreateInfo.pStages[vertIdx]; + + pipeCreateInfo.stageCount = 1; + + // enable rasterizer discard + VkPipelineRasterizationStateCreateInfo *rs = + (VkPipelineRasterizationStateCreateInfo *)pipeCreateInfo.pRasterizationState; + rs->rasterizerDiscardEnable = true; + + VkBuffer meshBuffer = VK_NULL_HANDLE, readbackBuffer = VK_NULL_HANDLE; + VkDeviceMemory meshMem = VK_NULL_HANDLE, readbackMem = VK_NULL_HANDLE; + + VkBuffer idxBuf = VK_NULL_HANDLE, uniqIdxBuf = VK_NULL_HANDLE; + VkDeviceMemory idxBufMem = VK_NULL_HANDLE, uniqIdxBufMem = VK_NULL_HANDLE; + + uint32_t numVerts = drawcall->numIndices; + VkDeviceSize bufSize = 0; + + vector indices; + uint32_t idxsize = state.ibuffer.bytewidth; + bool index16 = (idxsize == 2); + uint32_t numIndices = numVerts; + bytebuf idxdata; + uint16_t *idx16 = NULL; + uint32_t *idx32 = NULL; + + uint32_t minIndex = 0, maxIndex = 0; + + uint32_t vertexIndexOffset = 0; + + if(drawcall->flags & DrawFlags::UseIBuffer) + { + // fetch ibuffer + GetBufferData(state.ibuffer.buf, state.ibuffer.offs + drawcall->indexOffset * idxsize, + uint64_t(drawcall->numIndices) * idxsize, idxdata); + + // figure out what the maximum index could be, so we can clamp our index buffer to something + // sane + uint32_t maxIdx = 0; + + // if there are no active bindings assume the vertex shader is generating its own data + // and don't clamp the indices + if(pipeCreateInfo.pVertexInputState->vertexBindingDescriptionCount == 0) + maxIdx = ~0U; + + for(uint32_t b = 0; b < pipeCreateInfo.pVertexInputState->vertexBindingDescriptionCount; b++) + { + const VkVertexInputBindingDescription &input = + pipeCreateInfo.pVertexInputState->pVertexBindingDescriptions[b]; + // only vertex inputs (not instance inputs) count + if(input.inputRate == VK_VERTEX_INPUT_RATE_VERTEX) + { + if(b >= state.vbuffers.size()) + continue; + + ResourceId buf = state.vbuffers[b].buf; + VkDeviceSize offs = state.vbuffers[b].offs; + + VkDeviceSize bufsize = creationInfo.m_Buffer[buf].size; + + // the maximum valid index on this particular input is the one that reaches + // the end of the buffer. The maximum valid index at all is the one that reads + // off the end of ALL buffers (so we max it with any other maxindex value + // calculated). + if(input.stride > 0) + maxIdx = RDCMAX(maxIdx, uint32_t((bufsize - offs) / input.stride)); + } + } + + // in case the vertex buffers were set but had invalid stride (0), max with the number + // of vertices too. This is fine since the max here is just a conservative limit + maxIdx = RDCMAX(maxIdx, drawcall->numIndices); + + // do ibuffer rebasing/remapping + + idx16 = (uint16_t *)&idxdata[0]; + idx32 = (uint32_t *)&idxdata[0]; + + // only read as many indices as were available in the buffer + numIndices = + RDCMIN(uint32_t(index16 ? idxdata.size() / 2 : idxdata.size() / 4), drawcall->numIndices); + + // grab all unique vertex indices referenced + for(uint32_t i = 0; i < numIndices; i++) + { + uint32_t i32 = index16 ? uint32_t(idx16[i]) : idx32[i]; + + // we clamp to maxIdx here, to avoid any invalid indices like 0xffffffff + // from filtering through. Worst case we index to the end of the vertex + // buffers which is generally much more reasonable + i32 = RDCMIN(maxIdx, i32); + + 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); + + minIndex = indices[0]; + maxIndex = indices[indices.size() - 1]; + + vertexIndexOffset = minIndex + drawcall->baseVertex; + + // set numVerts + numVerts = maxIndex - minIndex + 1; + + // create buffer with unique 0-based indices + VkBufferCreateInfo bufInfo = { + VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + NULL, + 0, + indices.size() * sizeof(uint32_t), + VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + }; + + vkr = m_pDriver->vkCreateBuffer(dev, &bufInfo, NULL, &uniqIdxBuf); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + + VkMemoryRequirements mrq = {0}; + m_pDriver->vkGetBufferMemoryRequirements(dev, uniqIdxBuf, &mrq); + + VkMemoryAllocateInfo allocInfo = { + VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, NULL, mrq.size, + m_pDriver->GetUploadMemoryIndex(mrq.memoryTypeBits), + }; + + vkr = m_pDriver->vkAllocateMemory(dev, &allocInfo, NULL, &uniqIdxBufMem); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + + vkr = m_pDriver->vkBindBufferMemory(dev, uniqIdxBuf, uniqIdxBufMem, 0); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + + byte *idxData = NULL; + vkr = m_pDriver->vkMapMemory(m_Device, uniqIdxBufMem, 0, VK_WHOLE_SIZE, 0, (void **)&idxData); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + + memcpy(idxData, &indices[0], indices.size() * sizeof(uint32_t)); + + m_pDriver->vkUnmapMemory(m_Device, uniqIdxBufMem); + + bufInfo.size = numIndices * idxsize; + + vkr = m_pDriver->vkCreateBuffer(dev, &bufInfo, NULL, &idxBuf); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + + m_pDriver->vkGetBufferMemoryRequirements(dev, idxBuf, &mrq); + + allocInfo.allocationSize = mrq.size; + allocInfo.memoryTypeIndex = m_pDriver->GetUploadMemoryIndex(mrq.memoryTypeBits); + + vkr = m_pDriver->vkAllocateMemory(dev, &allocInfo, NULL, &idxBufMem); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + + vkr = m_pDriver->vkBindBufferMemory(dev, idxBuf, idxBufMem, 0); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + } + else + { + // firstVertex + vertexIndexOffset = drawcall->vertexOffset; + } + + uint32_t bufStride = 0; + vector modSpirv = moduleInfo.spirv.spirv; + + AddOutputDumping(*refl, *pipeInfo.shaders[0].patchData, pipeInfo.shaders[0].entryPoint.c_str(), + descSet, vertexIndexOffset, drawcall->instanceOffset, numVerts, modSpirv, + bufStride); + + { + VkDescriptorSetLayout *descSetLayouts; + + // descSet will be the index of our new descriptor set + descSetLayouts = new VkDescriptorSetLayout[descSet + 1]; + + for(uint32_t i = 0; i < descSet; i++) + descSetLayouts[i] = GetResourceManager()->GetCurrentHandle( + creationInfo.m_PipelineLayout[pipeInfo.layout].descSetLayouts[i]); + + // this layout just says it has one storage buffer + descSetLayouts[descSet] = m_MeshFetchDescSetLayout; + + const vector &push = + creationInfo.m_PipelineLayout[pipeInfo.layout].pushRanges; + + VkPipelineLayoutCreateInfo pipeLayoutInfo = { + VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + NULL, + 0, + descSet + 1, + descSetLayouts, + (uint32_t)push.size(), + push.empty() ? NULL : &push[0], + }; + + // create pipeline layout with same descriptor set layouts, plus our mesh output set + vkr = m_pDriver->vkCreatePipelineLayout(dev, &pipeLayoutInfo, NULL, &pipeLayout); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + + SAFE_DELETE_ARRAY(descSetLayouts); + + // repoint pipeline layout + pipeCreateInfo.layout = pipeLayout; + } + + // create vertex shader with modified code + VkShaderModuleCreateInfo moduleCreateInfo = { + VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, NULL, 0, + modSpirv.size() * sizeof(uint32_t), &modSpirv[0], + }; + + VkShaderModule module; + vkr = m_pDriver->vkCreateShaderModule(dev, &moduleCreateInfo, NULL, &module); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + + // change vertex shader to use our modified code + for(uint32_t i = 0; i < pipeCreateInfo.stageCount; i++) + { + VkPipelineShaderStageCreateInfo &sh = + (VkPipelineShaderStageCreateInfo &)pipeCreateInfo.pStages[i]; + if(sh.stage == VK_SHADER_STAGE_VERTEX_BIT) + { + sh.module = module; + // entry point name remains the same + break; + } + } + + // create new pipeline + VkPipeline pipe; + vkr = m_pDriver->vkCreateGraphicsPipelines(m_Device, VK_NULL_HANDLE, 1, &pipeCreateInfo, NULL, + &pipe); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + + // make copy of state to draw from + VulkanRenderState modifiedstate = state; + + // bind created pipeline to partial replay state + modifiedstate.graphics.pipeline = GetResID(pipe); + + // push back extra descriptor set to partial replay state + // note that we examined the used pipeline layout above and inserted our descriptor set + // after any the application used. So there might be more bound, but we want to ensure to + // bind to the slot we're using + modifiedstate.graphics.descSets.resize(descSet + 1); + modifiedstate.graphics.descSets[descSet].descSet = GetResID(m_MeshFetchDescSet); + + if(!(drawcall->flags & DrawFlags::UseIBuffer)) + { + // create buffer of sufficient size (num indices * bufStride) + VkBufferCreateInfo bufInfo = { + VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + NULL, + 0, + drawcall->numIndices * drawcall->numInstances * bufStride, + 0, + }; + + bufSize = bufInfo.size; + + bufInfo.usage |= VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + bufInfo.usage |= VK_BUFFER_USAGE_TRANSFER_DST_BIT; + bufInfo.usage |= VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; + bufInfo.usage |= VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + + vkr = m_pDriver->vkCreateBuffer(dev, &bufInfo, NULL, &meshBuffer); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + + bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + + vkr = m_pDriver->vkCreateBuffer(dev, &bufInfo, NULL, &readbackBuffer); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + + VkMemoryRequirements mrq = {0}; + m_pDriver->vkGetBufferMemoryRequirements(dev, meshBuffer, &mrq); + + VkMemoryAllocateInfo allocInfo = { + VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, NULL, mrq.size, + m_pDriver->GetGPULocalMemoryIndex(mrq.memoryTypeBits), + }; + + vkr = m_pDriver->vkAllocateMemory(dev, &allocInfo, NULL, &meshMem); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + + vkr = m_pDriver->vkBindBufferMemory(dev, meshBuffer, meshMem, 0); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + + m_pDriver->vkGetBufferMemoryRequirements(dev, readbackBuffer, &mrq); + + allocInfo.memoryTypeIndex = m_pDriver->GetReadbackMemoryIndex(mrq.memoryTypeBits); + + vkr = m_pDriver->vkAllocateMemory(dev, &allocInfo, NULL, &readbackMem); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + + vkr = m_pDriver->vkBindBufferMemory(dev, readbackBuffer, readbackMem, 0); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + + // vkUpdateDescriptorSet desc set to point to buffer + VkDescriptorBufferInfo fetchdesc = {0}; + fetchdesc.buffer = meshBuffer; + fetchdesc.offset = 0; + fetchdesc.range = bufInfo.size; + + VkWriteDescriptorSet write = { + VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, m_MeshFetchDescSet, 0, 0, 1, + VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, NULL, &fetchdesc, NULL}; + m_pDriver->vkUpdateDescriptorSets(dev, 1, &write, 0, NULL); + + VkCommandBuffer cmd = m_pDriver->GetNextCmd(); + + VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, NULL, + VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT}; + + vkr = ObjDisp(dev)->BeginCommandBuffer(Unwrap(cmd), &beginInfo); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + + // do single draw + modifiedstate.BeginRenderPassAndApplyState(cmd, VulkanRenderState::BindGraphics); + ObjDisp(cmd)->CmdDraw(Unwrap(cmd), drawcall->numIndices, drawcall->numInstances, + drawcall->vertexOffset, drawcall->instanceOffset); + modifiedstate.EndRenderPass(cmd); + + VkBufferMemoryBarrier meshbufbarrier = { + VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, + NULL, + VK_ACCESS_SHADER_WRITE_BIT, + VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT, + VK_QUEUE_FAMILY_IGNORED, + VK_QUEUE_FAMILY_IGNORED, + Unwrap(meshBuffer), + 0, + bufInfo.size, + }; + + // wait for writing to finish + DoPipelineBarrier(cmd, 1, &meshbufbarrier); + + VkBufferCopy bufcopy = { + 0, 0, bufInfo.size, + }; + + // copy to readback buffer + ObjDisp(dev)->CmdCopyBuffer(Unwrap(cmd), Unwrap(meshBuffer), Unwrap(readbackBuffer), 1, &bufcopy); + + meshbufbarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + meshbufbarrier.dstAccessMask = VK_ACCESS_HOST_READ_BIT; + meshbufbarrier.buffer = Unwrap(readbackBuffer); + + // wait for copy to finish + DoPipelineBarrier(cmd, 1, &meshbufbarrier); + + vkr = ObjDisp(dev)->EndCommandBuffer(Unwrap(cmd)); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + + // submit & flush so that we don't have to keep pipeline around for a while + m_pDriver->SubmitCmds(); + m_pDriver->FlushQ(); + } + else + { + // create buffer of sufficient size + // this can't just be bufStride * num unique indices per instance, as we don't + // have a compact 0-based index to index into the buffer. We must use + // index-minIndex which is 0-based but potentially sparse, so this buffer may + // be more or less wasteful + VkBufferCreateInfo bufInfo = { + VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, NULL, 0, + numVerts * drawcall->numInstances * bufStride, 0, + }; + + bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + bufInfo.usage |= VK_BUFFER_USAGE_TRANSFER_DST_BIT; + bufInfo.usage |= VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; + bufInfo.usage |= VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + + vkr = m_pDriver->vkCreateBuffer(dev, &bufInfo, NULL, &meshBuffer); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + + bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + + vkr = m_pDriver->vkCreateBuffer(dev, &bufInfo, NULL, &readbackBuffer); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + + VkMemoryRequirements mrq = {0}; + m_pDriver->vkGetBufferMemoryRequirements(dev, meshBuffer, &mrq); + + VkMemoryAllocateInfo allocInfo = { + VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, NULL, mrq.size, + m_pDriver->GetGPULocalMemoryIndex(mrq.memoryTypeBits), + }; + + vkr = m_pDriver->vkAllocateMemory(dev, &allocInfo, NULL, &meshMem); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + + vkr = m_pDriver->vkBindBufferMemory(dev, meshBuffer, meshMem, 0); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + + m_pDriver->vkGetBufferMemoryRequirements(dev, readbackBuffer, &mrq); + + allocInfo.memoryTypeIndex = m_pDriver->GetReadbackMemoryIndex(mrq.memoryTypeBits); + + vkr = m_pDriver->vkAllocateMemory(dev, &allocInfo, NULL, &readbackMem); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + + vkr = m_pDriver->vkBindBufferMemory(dev, readbackBuffer, readbackMem, 0); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + + VkBufferMemoryBarrier meshbufbarrier = { + VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, + NULL, + VK_ACCESS_HOST_WRITE_BIT, + VK_ACCESS_INDEX_READ_BIT, + VK_QUEUE_FAMILY_IGNORED, + VK_QUEUE_FAMILY_IGNORED, + Unwrap(uniqIdxBuf), + 0, + indices.size() * sizeof(uint32_t), + }; + + VkCommandBuffer cmd = m_pDriver->GetNextCmd(); + + VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, NULL, + VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT}; + + vkr = ObjDisp(dev)->BeginCommandBuffer(Unwrap(cmd), &beginInfo); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + + // wait for upload to finish + DoPipelineBarrier(cmd, 1, &meshbufbarrier); + + // fill destination buffer with 0s to ensure unwritten vertices have sane data + ObjDisp(dev)->CmdFillBuffer(Unwrap(cmd), Unwrap(meshBuffer), 0, bufInfo.size, 0); + + // wait to finish + meshbufbarrier.buffer = Unwrap(meshBuffer); + meshbufbarrier.size = bufInfo.size; + DoPipelineBarrier(cmd, 1, &meshbufbarrier); + + // set bufSize + bufSize = numVerts * drawcall->numInstances * bufStride; + + // bind unique'd ibuffer + modifiedstate.ibuffer.bytewidth = 4; + modifiedstate.ibuffer.offs = 0; + modifiedstate.ibuffer.buf = GetResID(uniqIdxBuf); + + // vkUpdateDescriptorSet desc set to point to buffer + VkDescriptorBufferInfo fetchdesc = {0}; + fetchdesc.buffer = meshBuffer; + fetchdesc.offset = 0; + fetchdesc.range = bufInfo.size; + + VkWriteDescriptorSet write = { + VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, m_MeshFetchDescSet, 0, 0, 1, + VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, NULL, &fetchdesc, NULL}; + m_pDriver->vkUpdateDescriptorSets(dev, 1, &write, 0, NULL); + + // do single draw + modifiedstate.BeginRenderPassAndApplyState(cmd, VulkanRenderState::BindGraphics); + ObjDisp(cmd)->CmdDrawIndexed(Unwrap(cmd), (uint32_t)indices.size(), drawcall->numInstances, 0, + drawcall->baseVertex, drawcall->instanceOffset); + modifiedstate.EndRenderPass(cmd); + + // rebase existing index buffer to point to the right elements in our stream-out'd + // vertex buffer + + // An index buffer could be something like: 500, 520, 518, 553, 554, 556 + // 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 index 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 in the indices as above which must remain as + // we don't have a 0-based dense 'vertex id' to base our SSBO indexing off, only index value. + + bool stripRestart = pipeCreateInfo.pInputAssemblyState->primitiveRestartEnable == VK_TRUE && + IsStrip(drawcall->topology); + + if(index16) + { + for(uint32_t i = 0; i < numIndices; i++) + { + if(stripRestart && idx16[i] == 0xffff) + continue; + + idx16[i] = idx16[i] - uint16_t(minIndex); + } + } + else + { + for(uint32_t i = 0; i < numIndices; i++) + { + if(stripRestart && idx32[i] == 0xffffffff) + continue; + + idx32[i] -= minIndex; + } + } + + // upload rebased memory + byte *idxData = NULL; + vkr = m_pDriver->vkMapMemory(m_Device, idxBufMem, 0, VK_WHOLE_SIZE, 0, (void **)&idxData); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + + memcpy(idxData, idx32, numIndices * idxsize); + + m_pDriver->vkUnmapMemory(m_Device, idxBufMem); + + meshbufbarrier.buffer = Unwrap(idxBuf); + meshbufbarrier.size = numIndices * idxsize; + + // wait for upload to finish + DoPipelineBarrier(cmd, 1, &meshbufbarrier); + + // wait for mesh output writing to finish + meshbufbarrier.buffer = Unwrap(meshBuffer); + meshbufbarrier.size = bufSize; + meshbufbarrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; + meshbufbarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + + DoPipelineBarrier(cmd, 1, &meshbufbarrier); + + VkBufferCopy bufcopy = { + 0, 0, bufInfo.size, + }; + + // copy to readback buffer + ObjDisp(dev)->CmdCopyBuffer(Unwrap(cmd), Unwrap(meshBuffer), Unwrap(readbackBuffer), 1, &bufcopy); + + meshbufbarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + meshbufbarrier.dstAccessMask = VK_ACCESS_HOST_READ_BIT; + meshbufbarrier.buffer = Unwrap(readbackBuffer); + + // wait for copy to finish + DoPipelineBarrier(cmd, 1, &meshbufbarrier); + + vkr = ObjDisp(dev)->EndCommandBuffer(Unwrap(cmd)); + RDCASSERTEQUAL(vkr, VK_SUCCESS); + + // submit & flush so that we don't have to keep pipeline around for a while + m_pDriver->SubmitCmds(); + m_pDriver->FlushQ(); + } + + // readback mesh data + byte *byteData = NULL; + vkr = m_pDriver->vkMapMemory(m_Device, readbackMem, 0, VK_WHOLE_SIZE, 0, (void **)&byteData); + + // do near/far calculations + + float nearp = 0.1f; + float farp = 100.0f; + + Vec4f *pos0 = (Vec4f *)byteData; + + bool found = false; + + // expect position at the start of the buffer, as system values are sorted first + // and position is the first value + + for(uint32_t i = 1; + refl->outputSignature[0].systemValue == ShaderBuiltin::Position && i < 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 * bufStride); + + // skip invalid vertices (w=0) + if(pos->w != 0.0f && 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; + + if(-c / m <= 0.000001f) + 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; + } + + m_pDriver->vkUnmapMemory(m_Device, readbackMem); + + // clean up temporary memories + m_pDriver->vkDestroyBuffer(m_Device, readbackBuffer, NULL); + m_pDriver->vkFreeMemory(m_Device, readbackMem, NULL); + + if(uniqIdxBuf != VK_NULL_HANDLE) + { + m_pDriver->vkDestroyBuffer(m_Device, uniqIdxBuf, NULL); + m_pDriver->vkFreeMemory(m_Device, uniqIdxBufMem, NULL); + } + + // fill out m_PostVSData + m_PostVSData[eventId].vsin.topo = topo; + m_PostVSData[eventId].vsout.topo = topo; + m_PostVSData[eventId].vsout.buf = meshBuffer; + m_PostVSData[eventId].vsout.bufmem = meshMem; + + m_PostVSData[eventId].vsout.vertStride = bufStride; + 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 = uint32_t(bufSize / drawcall->numInstances); + + m_PostVSData[eventId].vsout.idxBuf = VK_NULL_HANDLE; + if(m_PostVSData[eventId].vsout.useIndices && idxBuf != VK_NULL_HANDLE) + { + m_PostVSData[eventId].vsout.idxBuf = idxBuf; + m_PostVSData[eventId].vsout.idxBufMem = idxBufMem; + m_PostVSData[eventId].vsout.idxFmt = + state.ibuffer.bytewidth == 2 ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32; + } + + m_PostVSData[eventId].vsout.hasPosOut = + refl->outputSignature[0].systemValue == ShaderBuiltin::Position; + + // delete pipeline layout + m_pDriver->vkDestroyPipelineLayout(dev, pipeLayout, NULL); + + // delete pipeline + m_pDriver->vkDestroyPipeline(dev, pipe, NULL); + + // delete shader/shader module + m_pDriver->vkDestroyShaderModule(dev, module, NULL); +} + +MeshFormat VulkanDebugManager::GetPostVSBuffers(uint32_t eventId, uint32_t instID, MeshDataStage stage) +{ + // go through any aliasing + if(m_PostVSAlias.find(eventId) != m_PostVSAlias.end()) + eventId = m_PostVSAlias[eventId]; + + VulkanPostVSData postvs; + RDCEraseEl(postvs); + + if(m_PostVSData.find(eventId) != m_PostVSData.end()) + postvs = m_PostVSData[eventId]; + + VulkanPostVSData::StageData s = postvs.GetStage(stage); + + MeshFormat ret; + + if(s.useIndices && s.idxBuf != VK_NULL_HANDLE) + { + ret.indexResourceId = GetResID(s.idxBuf); + ret.indexByteStride = s.idxFmt == VK_INDEX_TYPE_UINT16 ? 2 : 4; + } + else + { + ret.indexResourceId = ResourceId(); + ret.indexByteStride = 0; + } + ret.indexByteOffset = 0; + ret.baseVertex = 0; + + if(s.buf != VK_NULL_HANDLE) + ret.vertexResourceId = GetResID(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 = MakePrimitiveTopology(s.topo, 1); + ret.numIndices = s.numVerts; + + ret.unproject = s.hasPosOut; + ret.nearPlane = s.nearPlane; + ret.farPlane = s.farPlane; + + return ret; +}