mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-05-05 09:30:44 +00:00
574d87e72a
* When we're splitting every draw and execute to get pre- and post-mod colours, we resume renderpasses with load RPs for obvious reasons. The inheritance info needs to match in secondary command buffers, so we force it over conditionally.
2936 lines
105 KiB
C++
2936 lines
105 KiB
C++
/******************************************************************************
|
|
* The MIT License (MIT)
|
|
*
|
|
* Copyright (c) 2019-2021 Baldur Karlsson
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
******************************************************************************/
|
|
|
|
#include <float.h>
|
|
#include <math.h>
|
|
#include <algorithm>
|
|
#include "data/glsl_shaders.h"
|
|
#include "driver/shaders/spirv/spirv_common.h"
|
|
#include "driver/shaders/spirv/spirv_gen.h"
|
|
#include "maths/formatpacking.h"
|
|
#include "maths/matrix.h"
|
|
#include "strings/string_utils.h"
|
|
#include "vk_core.h"
|
|
#include "vk_debug.h"
|
|
#include "vk_replay.h"
|
|
#include "vk_shader_cache.h"
|
|
|
|
#define VULKAN 1
|
|
#include "data/glsl/glsl_ubos_cpp.h"
|
|
|
|
struct VulkanQuadOverdrawCallback : public VulkanActionCallback
|
|
{
|
|
VulkanQuadOverdrawCallback(WrappedVulkan *vk, VkDescriptorSetLayout descSetLayout,
|
|
VkDescriptorSet descSet, const rdcarray<uint32_t> &events)
|
|
: m_pDriver(vk), m_DescSetLayout(descSetLayout), m_DescSet(descSet), m_Events(events)
|
|
{
|
|
m_pDriver->SetActionCB(this);
|
|
}
|
|
~VulkanQuadOverdrawCallback()
|
|
{
|
|
m_pDriver->SetActionCB(NULL);
|
|
|
|
VkDevice dev = m_pDriver->GetDev();
|
|
|
|
for(auto it = m_PipelineCache.begin(); it != m_PipelineCache.end(); ++it)
|
|
{
|
|
m_pDriver->vkDestroyPipeline(dev, it->second.pipe, NULL);
|
|
m_pDriver->vkDestroyPipelineLayout(dev, it->second.pipeLayout, NULL);
|
|
}
|
|
}
|
|
void PreDraw(uint32_t eid, VkCommandBuffer cmd)
|
|
{
|
|
if(!m_Events.contains(eid))
|
|
return;
|
|
|
|
// we customise the pipeline to disable framebuffer writes, but perform normal testing
|
|
// and substitute our quad calculation fragment shader that writes to a storage image
|
|
// that is bound in a new descriptor set.
|
|
|
|
VkResult vkr = VK_SUCCESS;
|
|
|
|
m_PrevState = m_pDriver->GetCmdRenderState();
|
|
VulkanRenderState &pipestate = m_pDriver->GetCmdRenderState();
|
|
|
|
// check cache first
|
|
CachedPipeline pipe = m_PipelineCache[pipestate.graphics.pipeline];
|
|
|
|
// if we don't get a hit, create a modified pipeline
|
|
if(pipe.pipe == VK_NULL_HANDLE)
|
|
{
|
|
const VulkanCreationInfo::Pipeline &p =
|
|
m_pDriver->GetDebugManager()->GetPipelineInfo(pipestate.graphics.pipeline);
|
|
|
|
VkDescriptorSetLayout *descSetLayouts;
|
|
|
|
// descSet will be the index of our new descriptor set
|
|
uint32_t descSet =
|
|
(uint32_t)m_pDriver->GetDebugManager()->GetPipelineLayoutInfo(p.layout).descSetLayouts.size();
|
|
|
|
descSetLayouts = new VkDescriptorSetLayout[descSet + 1];
|
|
|
|
for(uint32_t i = 0; i < descSet; i++)
|
|
descSetLayouts[i] = m_pDriver->GetResourceManager()->GetCurrentHandle<VkDescriptorSetLayout>(
|
|
m_pDriver->GetDebugManager()->GetPipelineLayoutInfo(p.layout).descSetLayouts[i]);
|
|
|
|
// this layout has storage image and
|
|
descSetLayouts[descSet] = m_DescSetLayout;
|
|
|
|
const rdcarray<VkPushConstantRange> &push =
|
|
m_pDriver->GetDebugManager()->GetPipelineLayoutInfo(p.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(m_pDriver->GetDev(), &pipeLayoutInfo, NULL,
|
|
&pipe.pipeLayout);
|
|
m_pDriver->CheckVkResult(vkr);
|
|
|
|
SAFE_DELETE_ARRAY(descSetLayouts);
|
|
|
|
VkGraphicsPipelineCreateInfo pipeCreateInfo;
|
|
m_pDriver->GetShaderCache()->MakeGraphicsPipelineInfo(pipeCreateInfo,
|
|
pipestate.graphics.pipeline);
|
|
|
|
// repoint pipeline layout
|
|
pipeCreateInfo.layout = pipe.pipeLayout;
|
|
|
|
// disable colour writes/blends
|
|
VkPipelineColorBlendStateCreateInfo *cb =
|
|
(VkPipelineColorBlendStateCreateInfo *)pipeCreateInfo.pColorBlendState;
|
|
for(uint32_t i = 0; i < cb->attachmentCount; i++)
|
|
{
|
|
VkPipelineColorBlendAttachmentState *att =
|
|
(VkPipelineColorBlendAttachmentState *)&cb->pAttachments[i];
|
|
att->blendEnable = false;
|
|
att->colorWriteMask = 0x0;
|
|
}
|
|
|
|
// disable depth/stencil writes but keep any tests enabled
|
|
VkPipelineDepthStencilStateCreateInfo *ds =
|
|
(VkPipelineDepthStencilStateCreateInfo *)pipeCreateInfo.pDepthStencilState;
|
|
ds->depthWriteEnable = false;
|
|
ds->front.passOp = ds->front.failOp = ds->front.depthFailOp = VK_STENCIL_OP_KEEP;
|
|
ds->back.passOp = ds->back.failOp = ds->back.depthFailOp = VK_STENCIL_OP_KEEP;
|
|
|
|
// don't discard
|
|
VkPipelineRasterizationStateCreateInfo *rs =
|
|
(VkPipelineRasterizationStateCreateInfo *)pipeCreateInfo.pRasterizationState;
|
|
rs->rasterizerDiscardEnable = false;
|
|
|
|
rdcarray<uint32_t> spirv =
|
|
*m_pDriver->GetShaderCache()->GetBuiltinBlob(BuiltinShader::QuadWriteFS);
|
|
|
|
// patch spirv, change descriptor set to descSet value
|
|
size_t it = 5;
|
|
while(it < spirv.size())
|
|
{
|
|
uint16_t WordCount = spirv[it] >> rdcspv::WordCountShift;
|
|
rdcspv::Op opcode = rdcspv::Op(spirv[it] & rdcspv::OpCodeMask);
|
|
|
|
if(opcode == rdcspv::Op::Decorate &&
|
|
spirv[it + 2] == (uint32_t)rdcspv::Decoration::DescriptorSet)
|
|
{
|
|
spirv[it + 3] = descSet;
|
|
break;
|
|
}
|
|
|
|
it += WordCount;
|
|
}
|
|
|
|
VkShaderModuleCreateInfo modinfo = {
|
|
VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
|
|
NULL,
|
|
0,
|
|
spirv.size() * sizeof(uint32_t),
|
|
&spirv[0],
|
|
};
|
|
|
|
VkShaderModule module;
|
|
|
|
VkDevice dev = m_pDriver->GetDev();
|
|
|
|
vkr = m_pDriver->vkCreateShaderModule(dev, &modinfo, NULL, &module);
|
|
m_pDriver->CheckVkResult(vkr);
|
|
|
|
bool found = false;
|
|
for(uint32_t i = 0; i < pipeCreateInfo.stageCount; i++)
|
|
{
|
|
VkPipelineShaderStageCreateInfo &sh =
|
|
(VkPipelineShaderStageCreateInfo &)pipeCreateInfo.pStages[i];
|
|
if(sh.stage == VK_SHADER_STAGE_FRAGMENT_BIT)
|
|
{
|
|
sh.module = module;
|
|
sh.pName = "main";
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!found)
|
|
{
|
|
// we know this is safe because it's pointing to a static array that's
|
|
// big enough for all shaders
|
|
|
|
VkPipelineShaderStageCreateInfo &sh =
|
|
(VkPipelineShaderStageCreateInfo &)pipeCreateInfo.pStages[pipeCreateInfo.stageCount++];
|
|
sh.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
|
sh.pNext = NULL;
|
|
sh.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
|
|
sh.module = module;
|
|
sh.pName = "main";
|
|
sh.pSpecializationInfo = NULL;
|
|
}
|
|
|
|
vkr = m_pDriver->vkCreateGraphicsPipelines(dev, VK_NULL_HANDLE, 1, &pipeCreateInfo, NULL,
|
|
&pipe.pipe);
|
|
m_pDriver->CheckVkResult(vkr);
|
|
|
|
m_pDriver->vkDestroyShaderModule(dev, module, NULL);
|
|
|
|
pipe.descSet = descSet;
|
|
|
|
m_PipelineCache[pipestate.graphics.pipeline] = pipe;
|
|
}
|
|
|
|
// modify state for first draw call
|
|
pipestate.graphics.pipeline = GetResID(pipe.pipe);
|
|
RDCASSERT(pipestate.graphics.descSets.size() >= pipe.descSet);
|
|
pipestate.graphics.descSets.resize(pipe.descSet + 1);
|
|
pipestate.graphics.descSets[pipe.descSet].pipeLayout = GetResID(pipe.pipeLayout);
|
|
pipestate.graphics.descSets[pipe.descSet].descSet = GetResID(m_DescSet);
|
|
|
|
if(cmd)
|
|
pipestate.BindPipeline(m_pDriver, cmd, VulkanRenderState::BindGraphics, false);
|
|
}
|
|
|
|
bool PostDraw(uint32_t eid, VkCommandBuffer cmd)
|
|
{
|
|
if(!m_Events.contains(eid))
|
|
return false;
|
|
|
|
// restore the render state and go ahead with the real draw
|
|
m_pDriver->GetCmdRenderState() = m_PrevState;
|
|
|
|
RDCASSERT(cmd);
|
|
m_pDriver->GetCmdRenderState().BindPipeline(m_pDriver, cmd, VulkanRenderState::BindGraphics,
|
|
false);
|
|
|
|
return true;
|
|
}
|
|
|
|
void PostRedraw(uint32_t eid, VkCommandBuffer cmd)
|
|
{
|
|
// nothing to do
|
|
}
|
|
|
|
// Dispatches don't rasterize, so do nothing
|
|
void PreDispatch(uint32_t eid, VkCommandBuffer cmd) {}
|
|
bool PostDispatch(uint32_t eid, VkCommandBuffer cmd) { return false; }
|
|
void PostRedispatch(uint32_t eid, VkCommandBuffer cmd) {}
|
|
// Ditto copy/etc
|
|
void PreMisc(uint32_t eid, ActionFlags flags, VkCommandBuffer cmd) {}
|
|
bool PostMisc(uint32_t eid, ActionFlags flags, VkCommandBuffer cmd) { return false; }
|
|
void PostRemisc(uint32_t eid, ActionFlags flags, VkCommandBuffer cmd) {}
|
|
void PreEndCommandBuffer(VkCommandBuffer cmd) {}
|
|
void AliasEvent(uint32_t primary, uint32_t alias)
|
|
{
|
|
// don't care
|
|
}
|
|
bool SplitSecondary() { return false; }
|
|
bool ForceLoadRPs() override { return false; }
|
|
void PreCmdExecute(uint32_t baseEid, uint32_t secondaryFirst, uint32_t secondaryLast,
|
|
VkCommandBuffer cmd)
|
|
{
|
|
}
|
|
void PostCmdExecute(uint32_t baseEid, uint32_t secondaryFirst, uint32_t secondaryLast,
|
|
VkCommandBuffer cmd)
|
|
{
|
|
}
|
|
|
|
WrappedVulkan *m_pDriver;
|
|
VkDescriptorSetLayout m_DescSetLayout;
|
|
VkDescriptorSet m_DescSet;
|
|
const rdcarray<uint32_t> &m_Events;
|
|
|
|
// cache modified pipelines
|
|
struct CachedPipeline
|
|
{
|
|
uint32_t descSet;
|
|
VkPipelineLayout pipeLayout;
|
|
VkPipeline pipe;
|
|
};
|
|
std::map<ResourceId, CachedPipeline> m_PipelineCache;
|
|
VulkanRenderState m_PrevState;
|
|
};
|
|
|
|
void VulkanDebugManager::PatchOutputLocation(VkShaderModule &mod, BuiltinShader shaderType,
|
|
uint32_t framebufferIndex)
|
|
{
|
|
union
|
|
{
|
|
uint32_t *spirv;
|
|
float *data;
|
|
} alias;
|
|
|
|
rdcarray<uint32_t> spv = *m_pDriver->GetShaderCache()->GetBuiltinBlob(shaderType);
|
|
|
|
alias.spirv = &spv[0];
|
|
size_t spirvLength = spv.size();
|
|
|
|
bool patched = false;
|
|
|
|
size_t it = 5;
|
|
while(it < spirvLength)
|
|
{
|
|
uint16_t WordCount = alias.spirv[it] >> rdcspv::WordCountShift;
|
|
rdcspv::Op opcode = rdcspv::Op(alias.spirv[it] & rdcspv::OpCodeMask);
|
|
|
|
if(opcode == rdcspv::Op::Decorate &&
|
|
(rdcspv::Decoration(alias.spirv[it + 2]) == rdcspv::Decoration::Location))
|
|
{
|
|
alias.spirv[it + 3] = framebufferIndex;
|
|
|
|
patched = true;
|
|
break;
|
|
}
|
|
|
|
it += WordCount;
|
|
}
|
|
|
|
if(!patched)
|
|
RDCERR("Didn't patch the output location");
|
|
|
|
VkShaderModuleCreateInfo modinfo = {
|
|
VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
|
|
NULL,
|
|
0,
|
|
spv.size() * sizeof(uint32_t),
|
|
alias.spirv,
|
|
};
|
|
|
|
VkResult vkr = m_pDriver->vkCreateShaderModule(m_Device, &modinfo, NULL, &mod);
|
|
CheckVkResult(vkr);
|
|
}
|
|
|
|
void VulkanDebugManager::PatchFixedColShader(VkShaderModule &mod, float col[4])
|
|
{
|
|
union
|
|
{
|
|
uint32_t *spirv;
|
|
float *data;
|
|
} alias;
|
|
|
|
rdcarray<uint32_t> spv = *m_pDriver->GetShaderCache()->GetBuiltinBlob(BuiltinShader::FixedColFS);
|
|
|
|
alias.spirv = &spv[0];
|
|
size_t spirvLength = spv.size();
|
|
|
|
int patched = 0;
|
|
|
|
size_t it = 5;
|
|
while(it < spirvLength)
|
|
{
|
|
uint16_t WordCount = alias.spirv[it] >> rdcspv::WordCountShift;
|
|
rdcspv::Op opcode = rdcspv::Op(alias.spirv[it] & rdcspv::OpCodeMask);
|
|
|
|
if(opcode == rdcspv::Op::Constant)
|
|
{
|
|
if(alias.data[it + 3] >= 1.0f && alias.data[it + 3] <= 1.5f)
|
|
alias.data[it + 3] = col[0];
|
|
else if(alias.data[it + 3] >= 2.0f && alias.data[it + 3] <= 2.5f)
|
|
alias.data[it + 3] = col[1];
|
|
else if(alias.data[it + 3] >= 3.0f && alias.data[it + 3] <= 3.5f)
|
|
alias.data[it + 3] = col[2];
|
|
else if(alias.data[it + 3] >= 4.0f && alias.data[it + 3] <= 4.5f)
|
|
alias.data[it + 3] = col[3];
|
|
else
|
|
RDCERR("Unexpected constant value");
|
|
|
|
patched++;
|
|
}
|
|
|
|
it += WordCount;
|
|
}
|
|
|
|
if(patched != 4)
|
|
RDCERR("Didn't patch all constants");
|
|
|
|
VkShaderModuleCreateInfo modinfo = {
|
|
VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
|
|
NULL,
|
|
0,
|
|
spv.size() * sizeof(uint32_t),
|
|
alias.spirv,
|
|
};
|
|
|
|
VkResult vkr = m_pDriver->vkCreateShaderModule(m_Device, &modinfo, NULL, &mod);
|
|
CheckVkResult(vkr);
|
|
}
|
|
|
|
void VulkanDebugManager::PatchLineStripIndexBuffer(const ActionDescription *action,
|
|
GPUBuffer &indexBuffer, uint32_t &indexCount)
|
|
{
|
|
VulkanRenderState &rs = m_pDriver->m_RenderState;
|
|
|
|
bytebuf indices;
|
|
|
|
uint8_t *idx8 = NULL;
|
|
uint16_t *idx16 = NULL;
|
|
uint32_t *idx32 = NULL;
|
|
|
|
if(action->flags & ActionFlags::Indexed)
|
|
{
|
|
GetBufferData(rs.ibuffer.buf,
|
|
rs.ibuffer.offs + uint64_t(action->indexOffset) * rs.ibuffer.bytewidth,
|
|
uint64_t(action->numIndices) * rs.ibuffer.bytewidth, indices);
|
|
|
|
if(rs.ibuffer.bytewidth == 4)
|
|
idx32 = (uint32_t *)indices.data();
|
|
else if(rs.ibuffer.bytewidth == 1)
|
|
idx8 = (uint8_t *)indices.data();
|
|
else
|
|
idx16 = (uint16_t *)indices.data();
|
|
}
|
|
|
|
// we just patch up to 32-bit since we'll be adding more indices and we might overflow 16-bit.
|
|
rdcarray<uint32_t> patchedIndices;
|
|
|
|
::PatchLineStripIndexBuffer(action, MakePrimitiveTopology(rs.primitiveTopology, 3), idx8, idx16,
|
|
idx32, patchedIndices);
|
|
|
|
indexBuffer.Create(m_pDriver, m_Device, patchedIndices.size() * sizeof(uint32_t), 1,
|
|
GPUBuffer::eGPUBufferIBuffer);
|
|
|
|
void *ptr = indexBuffer.Map(0, patchedIndices.size() * sizeof(uint32_t));
|
|
if(!ptr)
|
|
return;
|
|
memcpy(ptr, patchedIndices.data(), patchedIndices.size() * sizeof(uint32_t));
|
|
indexBuffer.Unmap();
|
|
|
|
rs.ibuffer.offs = 0;
|
|
rs.ibuffer.bytewidth = 4;
|
|
rs.ibuffer.buf = GetResID(indexBuffer.buf);
|
|
|
|
VkBufferMemoryBarrier uploadbarrier = {
|
|
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(indexBuffer.buf),
|
|
0,
|
|
indexBuffer.totalsize,
|
|
};
|
|
|
|
VkCommandBuffer cmd = m_pDriver->GetNextCmd();
|
|
|
|
if(cmd == VK_NULL_HANDLE)
|
|
return;
|
|
|
|
VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, NULL,
|
|
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT};
|
|
|
|
VkResult vkr = ObjDisp(m_Device)->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
|
|
CheckVkResult(vkr);
|
|
|
|
// ensure host writes finish before using as index buffer
|
|
DoPipelineBarrier(cmd, 1, &uploadbarrier);
|
|
|
|
ObjDisp(m_Device)->EndCommandBuffer(Unwrap(cmd));
|
|
|
|
indexCount = (uint32_t)patchedIndices.size();
|
|
}
|
|
|
|
RenderOutputSubresource VulkanReplay::GetRenderOutputSubresource(ResourceId id)
|
|
{
|
|
const VulkanRenderState &state = m_pDriver->m_RenderState;
|
|
VulkanCreationInfo &c = m_pDriver->m_CreationInfo;
|
|
|
|
for(ResourceId viewid : state.GetFramebufferAttachments())
|
|
{
|
|
const VulkanCreationInfo::ImageView &viewInfo = c.m_ImageView[viewid];
|
|
|
|
if(viewid == id || viewInfo.image == id)
|
|
{
|
|
return RenderOutputSubresource(viewInfo.range.baseMipLevel,
|
|
c.m_ImageView[viewid].range.baseArrayLayer,
|
|
c.m_ImageView[viewid].range.layerCount);
|
|
}
|
|
}
|
|
|
|
return RenderOutputSubresource(~0U, ~0U, 0);
|
|
}
|
|
|
|
ResourceId VulkanReplay::RenderOverlay(ResourceId texid, FloatVector clearCol, DebugOverlay overlay,
|
|
uint32_t eventId, const rdcarray<uint32_t> &passEvents)
|
|
{
|
|
const VkDevDispatchTable *vt = ObjDisp(m_Device);
|
|
|
|
RenderOutputSubresource sub = GetRenderOutputSubresource(texid);
|
|
|
|
if(sub.slice == ~0U)
|
|
{
|
|
RDCERR("Rendering overlay for %s couldn't find output to get subresource.", ToStr(texid).c_str());
|
|
sub = RenderOutputSubresource(0, 0, 1);
|
|
}
|
|
|
|
VulkanShaderCache *shaderCache = m_pDriver->GetShaderCache();
|
|
|
|
VulkanCreationInfo::Image &iminfo = m_pDriver->m_CreationInfo.m_Image[texid];
|
|
|
|
VkCommandBuffer cmd = m_pDriver->GetNextCmd();
|
|
|
|
if(cmd == VK_NULL_HANDLE)
|
|
return ResourceId();
|
|
|
|
VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, NULL,
|
|
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT};
|
|
|
|
VkResult vkr = vt->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
|
|
CheckVkResult(vkr);
|
|
|
|
VkMarkerRegion::Begin(StringFormat::Fmt("RenderOverlay %d", overlay), cmd);
|
|
|
|
uint32_t multiviewMask = m_Overlay.MultiViewMask;
|
|
|
|
VulkanRenderState &state = m_pDriver->m_RenderState;
|
|
if(state.dynamicRendering.active)
|
|
{
|
|
multiviewMask = state.dynamicRendering.viewMask;
|
|
}
|
|
else if(state.GetRenderPass() != ResourceId())
|
|
{
|
|
const VulkanCreationInfo::RenderPass &rp =
|
|
m_pDriver->m_CreationInfo.m_RenderPass[state.GetRenderPass()];
|
|
|
|
multiviewMask = 0;
|
|
for(uint32_t v : rp.subpasses[state.subpass].multiviews)
|
|
multiviewMask |= 1U << v;
|
|
}
|
|
|
|
// if the overlay image is the wrong size, free it
|
|
if(m_Overlay.Image != VK_NULL_HANDLE &&
|
|
(iminfo.extent.width != m_Overlay.ImageDim.width ||
|
|
iminfo.extent.height != m_Overlay.ImageDim.height || iminfo.samples != m_Overlay.Samples ||
|
|
iminfo.mipLevels != m_Overlay.MipLevels || iminfo.arrayLayers != m_Overlay.ArrayLayers ||
|
|
multiviewMask != m_Overlay.MultiViewMask))
|
|
{
|
|
m_pDriver->vkDestroyRenderPass(m_Device, m_Overlay.NoDepthRP, NULL);
|
|
m_pDriver->vkDestroyFramebuffer(m_Device, m_Overlay.NoDepthFB, NULL);
|
|
m_pDriver->vkDestroyImageView(m_Device, m_Overlay.ImageView, NULL);
|
|
m_pDriver->vkDestroyImage(m_Device, m_Overlay.Image, NULL);
|
|
|
|
m_Overlay.Image = VK_NULL_HANDLE;
|
|
m_Overlay.ImageView = VK_NULL_HANDLE;
|
|
m_Overlay.NoDepthRP = VK_NULL_HANDLE;
|
|
m_Overlay.NoDepthFB = VK_NULL_HANDLE;
|
|
}
|
|
|
|
VkImageSubresourceRange subRange = {VK_IMAGE_ASPECT_COLOR_BIT, sub.mip, 1, sub.slice,
|
|
sub.numSlices};
|
|
|
|
const VkFormat overlayFormat = VK_FORMAT_R16G16B16A16_SFLOAT;
|
|
|
|
VkRenderPassMultiviewCreateInfo multiviewRP = {VK_STRUCTURE_TYPE_RENDER_PASS_MULTIVIEW_CREATE_INFO};
|
|
multiviewRP.correlationMaskCount = 1;
|
|
multiviewRP.pCorrelationMasks = &multiviewMask;
|
|
multiviewRP.subpassCount = 1;
|
|
multiviewRP.pViewMasks = &multiviewMask;
|
|
|
|
// create the overlay image if we don't have one already
|
|
// we go through the driver's creation functions so creation info
|
|
// is saved and the resources are registered as live resources for
|
|
// their IDs.
|
|
if(m_Overlay.Image == VK_NULL_HANDLE)
|
|
{
|
|
m_Overlay.ImageDim.width = iminfo.extent.width;
|
|
m_Overlay.ImageDim.height = iminfo.extent.height;
|
|
m_Overlay.MipLevels = iminfo.mipLevels;
|
|
m_Overlay.ArrayLayers = iminfo.arrayLayers;
|
|
m_Overlay.Samples = iminfo.samples;
|
|
m_Overlay.MultiViewMask = multiviewMask;
|
|
|
|
VkImageCreateInfo imInfo = {
|
|
VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
|
NULL,
|
|
0,
|
|
VK_IMAGE_TYPE_2D,
|
|
overlayFormat,
|
|
{m_Overlay.ImageDim.width, m_Overlay.ImageDim.height, 1},
|
|
(uint32_t)iminfo.mipLevels,
|
|
(uint32_t)iminfo.arrayLayers,
|
|
iminfo.samples,
|
|
VK_IMAGE_TILING_OPTIMAL,
|
|
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT |
|
|
VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
|
|
VK_SHARING_MODE_EXCLUSIVE,
|
|
0,
|
|
NULL,
|
|
VK_IMAGE_LAYOUT_UNDEFINED,
|
|
};
|
|
|
|
vkr = m_pDriver->vkCreateImage(m_Device, &imInfo, NULL, &m_Overlay.Image);
|
|
CheckVkResult(vkr);
|
|
|
|
NameVulkanObject(m_Overlay.Image, "m_Overlay.Image");
|
|
|
|
VkMemoryRequirements mrq = {0};
|
|
m_pDriver->vkGetImageMemoryRequirements(m_Device, m_Overlay.Image, &mrq);
|
|
|
|
// if no memory is allocated, or it's not enough,
|
|
// then allocate
|
|
if(m_Overlay.ImageMem == VK_NULL_HANDLE || mrq.size > m_Overlay.ImageMemSize)
|
|
{
|
|
if(m_Overlay.ImageMem != VK_NULL_HANDLE)
|
|
{
|
|
m_pDriver->vkFreeMemory(m_Device, m_Overlay.ImageMem, NULL);
|
|
}
|
|
|
|
VkMemoryAllocateInfo allocInfo = {
|
|
VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, NULL, mrq.size,
|
|
m_pDriver->GetGPULocalMemoryIndex(mrq.memoryTypeBits),
|
|
};
|
|
|
|
vkr = m_pDriver->vkAllocateMemory(m_Device, &allocInfo, NULL, &m_Overlay.ImageMem);
|
|
CheckVkResult(vkr);
|
|
|
|
if(vkr != VK_SUCCESS)
|
|
return ResourceId();
|
|
|
|
m_Overlay.ImageMemSize = mrq.size;
|
|
}
|
|
|
|
vkr = m_pDriver->vkBindImageMemory(m_Device, m_Overlay.Image, m_Overlay.ImageMem, 0);
|
|
CheckVkResult(vkr);
|
|
|
|
// need to update image layout into valid state
|
|
|
|
m_pDriver->FindImageState(GetResID(m_Overlay.Image))
|
|
->InlineTransition(
|
|
cmd, m_pDriver->m_QueueFamilyIdx, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, 0,
|
|
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, m_pDriver->GetImageTransitionInfo());
|
|
|
|
VkAttachmentDescription colDesc = {
|
|
0,
|
|
imInfo.format,
|
|
imInfo.samples,
|
|
VK_ATTACHMENT_LOAD_OP_LOAD,
|
|
VK_ATTACHMENT_STORE_OP_STORE,
|
|
VK_ATTACHMENT_LOAD_OP_DONT_CARE,
|
|
VK_ATTACHMENT_STORE_OP_DONT_CARE,
|
|
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
|
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
|
};
|
|
|
|
VkAttachmentReference colRef = {0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
|
|
|
|
VkSubpassDescription subp = {
|
|
0, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
|
0, NULL, // inputs
|
|
1, &colRef, // color
|
|
NULL, // resolve
|
|
NULL, // depth-stencil
|
|
0, NULL, // preserve
|
|
};
|
|
|
|
VkRenderPassCreateInfo rpinfo = {
|
|
VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
|
|
NULL,
|
|
0,
|
|
1,
|
|
&colDesc,
|
|
1,
|
|
&subp,
|
|
0,
|
|
NULL, // dependencies
|
|
};
|
|
|
|
if(multiviewMask > 0)
|
|
rpinfo.pNext = &multiviewRP;
|
|
|
|
vkr = m_pDriver->vkCreateRenderPass(m_Device, &rpinfo, NULL, &m_Overlay.NoDepthRP);
|
|
CheckVkResult(vkr);
|
|
}
|
|
|
|
if(m_Overlay.ViewMip != sub.mip || m_Overlay.ViewSlice != sub.slice ||
|
|
m_Overlay.ViewNumSlices != sub.numSlices || m_Overlay.ImageView == VK_NULL_HANDLE)
|
|
{
|
|
m_pDriver->vkDestroyFramebuffer(m_Device, m_Overlay.NoDepthFB, NULL);
|
|
m_pDriver->vkDestroyImageView(m_Device, m_Overlay.ImageView, NULL);
|
|
|
|
m_Overlay.ViewMip = sub.mip;
|
|
m_Overlay.ViewSlice = sub.slice;
|
|
m_Overlay.ViewNumSlices = sub.numSlices;
|
|
|
|
VkImageViewCreateInfo viewInfo = {
|
|
VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
|
NULL,
|
|
0,
|
|
m_Overlay.Image,
|
|
VK_IMAGE_VIEW_TYPE_2D,
|
|
overlayFormat,
|
|
{VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY,
|
|
VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY},
|
|
subRange,
|
|
};
|
|
|
|
vkr = m_pDriver->vkCreateImageView(m_Device, &viewInfo, NULL, &m_Overlay.ImageView);
|
|
CheckVkResult(vkr);
|
|
|
|
// Create framebuffer rendering just to overlay image, no depth
|
|
VkFramebufferCreateInfo fbinfo = {
|
|
VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
|
|
NULL,
|
|
0,
|
|
m_Overlay.NoDepthRP,
|
|
1,
|
|
&m_Overlay.ImageView,
|
|
RDCMAX(1U, m_Overlay.ImageDim.width >> sub.mip),
|
|
RDCMAX(1U, m_Overlay.ImageDim.height >> sub.mip),
|
|
sub.numSlices,
|
|
};
|
|
|
|
vkr = m_pDriver->vkCreateFramebuffer(m_Device, &fbinfo, NULL, &m_Overlay.NoDepthFB);
|
|
CheckVkResult(vkr);
|
|
|
|
// can't create a framebuffer or renderpass for overlay image + depth as that
|
|
// needs to match the depth texture type wherever our draw is.
|
|
}
|
|
|
|
// bail out if the render area is outside our image.
|
|
// This is an order-of-operations problem, if the overlay is set when the event is changed it is
|
|
// refreshed before the UI layer can update the current texture.
|
|
if(state.renderArea.offset.x + state.renderArea.extent.width >
|
|
(m_Overlay.ImageDim.width >> sub.mip) ||
|
|
state.renderArea.offset.y + state.renderArea.extent.height >
|
|
(m_Overlay.ImageDim.height >> sub.mip))
|
|
{
|
|
return GetResID(m_Overlay.Image);
|
|
}
|
|
|
|
{
|
|
VkImageSubresourceRange fullSubRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, VK_REMAINING_MIP_LEVELS,
|
|
0, VK_REMAINING_ARRAY_LAYERS};
|
|
|
|
VkImageMemoryBarrier barrier = {
|
|
VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
|
NULL,
|
|
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT,
|
|
VK_ACCESS_TRANSFER_WRITE_BIT,
|
|
VK_IMAGE_LAYOUT_UNDEFINED,
|
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
Unwrap(m_Overlay.Image),
|
|
fullSubRange};
|
|
|
|
DoPipelineBarrier(cmd, 1, &barrier);
|
|
|
|
float black[4] = {};
|
|
vt->CmdClearColorImage(Unwrap(cmd), Unwrap(m_Overlay.Image), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
|
(VkClearColorValue *)black, 1, &fullSubRange);
|
|
|
|
barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
|
barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
|
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
|
barrier.dstAccessMask =
|
|
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
|
|
|
DoPipelineBarrier(cmd, 1, &barrier);
|
|
}
|
|
|
|
const ActionDescription *mainDraw = m_pDriver->GetAction(eventId);
|
|
|
|
const VulkanCreationInfo::Pipeline &pipeInfo =
|
|
m_pDriver->m_CreationInfo.m_Pipeline[state.graphics.pipeline];
|
|
|
|
// Secondary commands can't have render passes
|
|
if((mainDraw && !(mainDraw->flags & ActionFlags::Drawcall)) ||
|
|
!m_pDriver->m_Partial[WrappedVulkan::Primary].renderPassActive)
|
|
{
|
|
// don't do anything, no action capable of making overlays selected
|
|
float black[] = {0.0f, 0.0f, 0.0f, 0.0f};
|
|
|
|
VkImageMemoryBarrier barrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
|
NULL,
|
|
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
|
VK_ACCESS_TRANSFER_WRITE_BIT,
|
|
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
Unwrap(m_Overlay.Image),
|
|
subRange};
|
|
|
|
DoPipelineBarrier(cmd, 1, &barrier);
|
|
|
|
vt->CmdClearColorImage(Unwrap(cmd), Unwrap(m_Overlay.Image), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
|
(VkClearColorValue *)black, 1, &subRange);
|
|
|
|
std::swap(barrier.oldLayout, barrier.newLayout);
|
|
std::swap(barrier.srcAccessMask, barrier.dstAccessMask);
|
|
barrier.dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT;
|
|
|
|
DoPipelineBarrier(cmd, 1, &barrier);
|
|
}
|
|
else if(overlay == DebugOverlay::NaN || overlay == DebugOverlay::Clipping)
|
|
{
|
|
float black[] = {0.0f, 0.0f, 0.0f, 0.0f};
|
|
|
|
VkImageMemoryBarrier barrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
|
NULL,
|
|
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
|
VK_ACCESS_TRANSFER_WRITE_BIT,
|
|
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
Unwrap(m_Overlay.Image),
|
|
subRange};
|
|
|
|
DoPipelineBarrier(cmd, 1, &barrier);
|
|
|
|
vt->CmdClearColorImage(Unwrap(cmd), Unwrap(m_Overlay.Image), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
|
(VkClearColorValue *)black, 1, &subRange);
|
|
|
|
std::swap(barrier.oldLayout, barrier.newLayout);
|
|
std::swap(barrier.srcAccessMask, barrier.dstAccessMask);
|
|
barrier.dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT;
|
|
|
|
DoPipelineBarrier(cmd, 1, &barrier);
|
|
}
|
|
else if(overlay == DebugOverlay::Drawcall || overlay == DebugOverlay::Wireframe)
|
|
{
|
|
float highlightCol[] = {0.8f, 0.1f, 0.8f, 1.0f};
|
|
float bgclearCol[] = {0.0f, 0.0f, 0.0f, 0.5f};
|
|
|
|
if(overlay == DebugOverlay::Wireframe)
|
|
{
|
|
highlightCol[0] = 200 / 255.0f;
|
|
highlightCol[1] = 1.0f;
|
|
highlightCol[2] = 0.0f;
|
|
|
|
bgclearCol[0] = 200 / 255.0f;
|
|
bgclearCol[1] = 1.0f;
|
|
bgclearCol[2] = 0.0f;
|
|
bgclearCol[3] = 0.0f;
|
|
}
|
|
|
|
VkImageMemoryBarrier barrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
|
NULL,
|
|
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
|
VK_ACCESS_TRANSFER_WRITE_BIT,
|
|
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
Unwrap(m_Overlay.Image),
|
|
subRange};
|
|
|
|
DoPipelineBarrier(cmd, 1, &barrier);
|
|
|
|
vt->CmdClearColorImage(Unwrap(cmd), Unwrap(m_Overlay.Image), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
|
(VkClearColorValue *)bgclearCol, 1, &subRange);
|
|
|
|
std::swap(barrier.oldLayout, barrier.newLayout);
|
|
std::swap(barrier.srcAccessMask, barrier.dstAccessMask);
|
|
barrier.dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT;
|
|
|
|
DoPipelineBarrier(cmd, 1, &barrier);
|
|
|
|
if(!pipeInfo.rasterizerDiscardEnable)
|
|
{
|
|
vkr = vt->EndCommandBuffer(Unwrap(cmd));
|
|
CheckVkResult(vkr);
|
|
|
|
// backup state
|
|
VulkanRenderState prevstate = state;
|
|
|
|
// make patched shader
|
|
VkShaderModule mod = VK_NULL_HANDLE;
|
|
|
|
GetDebugManager()->PatchFixedColShader(mod, highlightCol);
|
|
|
|
// make patched pipeline
|
|
VkGraphicsPipelineCreateInfo pipeCreateInfo;
|
|
|
|
m_pDriver->GetShaderCache()->MakeGraphicsPipelineInfo(pipeCreateInfo,
|
|
prevstate.graphics.pipeline);
|
|
|
|
// disable all tests possible
|
|
VkPipelineDepthStencilStateCreateInfo *ds =
|
|
(VkPipelineDepthStencilStateCreateInfo *)pipeCreateInfo.pDepthStencilState;
|
|
ds->depthTestEnable = false;
|
|
ds->depthWriteEnable = false;
|
|
ds->stencilTestEnable = false;
|
|
ds->depthBoundsTestEnable = false;
|
|
|
|
VkPipelineRasterizationStateCreateInfo *rs =
|
|
(VkPipelineRasterizationStateCreateInfo *)pipeCreateInfo.pRasterizationState;
|
|
rs->cullMode = VK_CULL_MODE_NONE;
|
|
rs->rasterizerDiscardEnable = false;
|
|
|
|
// disable tests in dynamic state too
|
|
state.depthTestEnable = VK_FALSE;
|
|
state.depthWriteEnable = VK_FALSE;
|
|
state.depthCompareOp = VK_COMPARE_OP_ALWAYS;
|
|
state.stencilTestEnable = VK_FALSE;
|
|
state.depthBoundsTestEnable = VK_FALSE;
|
|
state.cullMode = VK_CULL_MODE_NONE;
|
|
|
|
// disable all discard rectangles
|
|
RemoveNextStruct(&pipeCreateInfo,
|
|
VK_STRUCTURE_TYPE_PIPELINE_DISCARD_RECTANGLE_STATE_CREATE_INFO_EXT);
|
|
|
|
if(m_pDriver->GetDeviceEnabledFeatures().depthClamp)
|
|
{
|
|
rs->depthClampEnable = true;
|
|
}
|
|
|
|
// disable line stipple
|
|
VkPipelineRasterizationLineStateCreateInfoEXT *lineRasterState =
|
|
(VkPipelineRasterizationLineStateCreateInfoEXT *)FindNextStruct(
|
|
rs, VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_LINE_STATE_CREATE_INFO_EXT);
|
|
|
|
if(lineRasterState)
|
|
{
|
|
lineRasterState->stippledLineEnable = VK_FALSE;
|
|
}
|
|
|
|
uint32_t patchedIndexCount = 0;
|
|
GPUBuffer patchedIB;
|
|
|
|
if(overlay == DebugOverlay::Wireframe)
|
|
{
|
|
rs->lineWidth = 1.0f;
|
|
|
|
if(mainDraw == NULL)
|
|
{
|
|
// do nothing
|
|
}
|
|
else if(m_pDriver->GetDeviceEnabledFeatures().fillModeNonSolid)
|
|
{
|
|
rs->polygonMode = VK_POLYGON_MODE_LINE;
|
|
}
|
|
else if(prevstate.primitiveTopology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST ||
|
|
prevstate.primitiveTopology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP ||
|
|
prevstate.primitiveTopology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN ||
|
|
prevstate.primitiveTopology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY ||
|
|
prevstate.primitiveTopology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY)
|
|
{
|
|
// bad drivers (aka mobile) won't have non-solid fill mode, so we have to fall back to
|
|
// manually patching the index buffer and using a line list. This doesn't work with
|
|
// adjacency or patchlist topologies since those imply a vertex processing pipeline that
|
|
// requires a particular topology, or can't be implicitly converted to lines at input
|
|
// stage.
|
|
// It's unlikely those features will be used on said poor hw, so this should still catch
|
|
// most cases.
|
|
VkPipelineInputAssemblyStateCreateInfo *ia =
|
|
(VkPipelineInputAssemblyStateCreateInfo *)pipeCreateInfo.pInputAssemblyState;
|
|
|
|
ia->topology = VK_PRIMITIVE_TOPOLOGY_LINE_STRIP;
|
|
|
|
// thankfully, primitive restart is always supported! This makes the index buffer a bit
|
|
// more
|
|
// compact in the common cases where we don't need to repeat two indices for a triangle's
|
|
// three lines, instead we have a single restart index after each triangle.
|
|
ia->primitiveRestartEnable = true;
|
|
|
|
GetDebugManager()->PatchLineStripIndexBuffer(mainDraw, patchedIB, patchedIndexCount);
|
|
}
|
|
else
|
|
{
|
|
RDCWARN("Unable to draw wireframe overlay for %s topology draw via software patching",
|
|
ToStr(prevstate.primitiveTopology).c_str());
|
|
}
|
|
}
|
|
|
|
VkPipelineColorBlendStateCreateInfo *cb =
|
|
(VkPipelineColorBlendStateCreateInfo *)pipeCreateInfo.pColorBlendState;
|
|
cb->logicOpEnable = false;
|
|
cb->attachmentCount = 1; // only one colour attachment
|
|
for(uint32_t i = 0; i < cb->attachmentCount; i++)
|
|
{
|
|
VkPipelineColorBlendAttachmentState *att =
|
|
(VkPipelineColorBlendAttachmentState *)&cb->pAttachments[i];
|
|
att->blendEnable = false;
|
|
att->colorWriteMask = 0xf;
|
|
}
|
|
|
|
// set scissors to max for drawcall
|
|
if(overlay == DebugOverlay::Drawcall)
|
|
{
|
|
for(size_t i = 0; i < pipeCreateInfo.pViewportState->scissorCount; i++)
|
|
{
|
|
VkRect2D &sc = (VkRect2D &)pipeCreateInfo.pViewportState->pScissors[i];
|
|
sc.offset.x = 0;
|
|
sc.offset.y = 0;
|
|
sc.extent.width = 16384;
|
|
sc.extent.height = 16384;
|
|
}
|
|
}
|
|
|
|
// set our renderpass and shader
|
|
pipeCreateInfo.renderPass = m_Overlay.NoDepthRP;
|
|
pipeCreateInfo.subpass = 0;
|
|
|
|
bool found = false;
|
|
for(uint32_t i = 0; i < pipeCreateInfo.stageCount; i++)
|
|
{
|
|
VkPipelineShaderStageCreateInfo &sh =
|
|
(VkPipelineShaderStageCreateInfo &)pipeCreateInfo.pStages[i];
|
|
if(sh.stage == VK_SHADER_STAGE_FRAGMENT_BIT)
|
|
{
|
|
sh.module = mod;
|
|
sh.pName = "main";
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!found)
|
|
{
|
|
// we know this is safe because it's pointing to a static array that's
|
|
// big enough for all shaders
|
|
|
|
VkPipelineShaderStageCreateInfo &sh =
|
|
(VkPipelineShaderStageCreateInfo &)pipeCreateInfo.pStages[pipeCreateInfo.stageCount++];
|
|
sh.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
|
sh.pNext = NULL;
|
|
sh.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
|
|
sh.module = mod;
|
|
sh.pName = "main";
|
|
sh.pSpecializationInfo = NULL;
|
|
}
|
|
|
|
VkPipeline pipe = VK_NULL_HANDLE;
|
|
|
|
vkr = m_pDriver->vkCreateGraphicsPipelines(m_Device, VK_NULL_HANDLE, 1, &pipeCreateInfo, NULL,
|
|
&pipe);
|
|
CheckVkResult(vkr);
|
|
|
|
// modify state
|
|
state.SetRenderPass(GetResID(m_Overlay.NoDepthRP));
|
|
state.subpass = 0;
|
|
state.SetFramebuffer(m_pDriver, GetResID(m_Overlay.NoDepthFB));
|
|
|
|
state.graphics.pipeline = GetResID(pipe);
|
|
|
|
// set dynamic scissors in case pipeline was using them
|
|
if(overlay == DebugOverlay::Drawcall)
|
|
{
|
|
for(size_t i = 0; i < state.scissors.size(); i++)
|
|
{
|
|
state.scissors[i].offset.x = 0;
|
|
state.scissors[i].offset.y = 0;
|
|
state.scissors[i].extent.width = 16384;
|
|
state.scissors[i].extent.height = 16384;
|
|
}
|
|
}
|
|
|
|
if(overlay == DebugOverlay::Wireframe)
|
|
state.lineWidth = 1.0f;
|
|
|
|
if(overlay == DebugOverlay::Drawcall || overlay == DebugOverlay::Wireframe)
|
|
state.conditionalRendering.forceDisable = true;
|
|
|
|
if(patchedIndexCount == 0)
|
|
{
|
|
m_pDriver->ReplayLog(0, eventId, eReplay_OnlyDraw);
|
|
}
|
|
else
|
|
{
|
|
// if we patched the index buffer we need to manually play the draw with a higher index
|
|
// count
|
|
// and no index offset.
|
|
cmd = m_pDriver->GetNextCmd();
|
|
|
|
if(cmd == VK_NULL_HANDLE)
|
|
return ResourceId();
|
|
|
|
vkr = ObjDisp(cmd)->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
|
|
CheckVkResult(vkr);
|
|
|
|
// do single draw
|
|
state.BeginRenderPassAndApplyState(m_pDriver, cmd, VulkanRenderState::BindGraphics, false);
|
|
ActionDescription action = *mainDraw;
|
|
action.numIndices = patchedIndexCount;
|
|
action.baseVertex = 0;
|
|
action.indexOffset = 0;
|
|
m_pDriver->ReplayDraw(cmd, action);
|
|
state.EndRenderPass(cmd);
|
|
|
|
vkr = ObjDisp(cmd)->EndCommandBuffer(Unwrap(cmd));
|
|
CheckVkResult(vkr);
|
|
}
|
|
|
|
// submit & flush so that we don't have to keep pipeline around for a while
|
|
m_pDriver->SubmitCmds();
|
|
m_pDriver->FlushQ();
|
|
|
|
cmd = m_pDriver->GetNextCmd();
|
|
|
|
if(cmd == VK_NULL_HANDLE)
|
|
return ResourceId();
|
|
|
|
vkr = vt->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
|
|
CheckVkResult(vkr);
|
|
|
|
// restore state
|
|
state = prevstate;
|
|
|
|
patchedIB.Destroy();
|
|
|
|
m_pDriver->vkDestroyPipeline(m_Device, pipe, NULL);
|
|
m_pDriver->vkDestroyShaderModule(m_Device, mod, NULL);
|
|
}
|
|
}
|
|
else if(overlay == DebugOverlay::ViewportScissor)
|
|
{
|
|
// clear the whole image to opaque black. We'll overwite the render area with transparent black
|
|
// before rendering the viewport/scissors
|
|
float black[] = {0.0f, 0.0f, 0.0f, 0.0f};
|
|
|
|
VkImageMemoryBarrier barrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
|
NULL,
|
|
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
|
VK_ACCESS_TRANSFER_WRITE_BIT,
|
|
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
Unwrap(m_Overlay.Image),
|
|
subRange};
|
|
|
|
DoPipelineBarrier(cmd, 1, &barrier);
|
|
|
|
vt->CmdClearColorImage(Unwrap(cmd), Unwrap(m_Overlay.Image), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
|
(VkClearColorValue *)black, 1, &subRange);
|
|
|
|
std::swap(barrier.oldLayout, barrier.newLayout);
|
|
std::swap(barrier.srcAccessMask, barrier.dstAccessMask);
|
|
barrier.dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT;
|
|
|
|
DoPipelineBarrier(cmd, 1, &barrier);
|
|
|
|
black[3] = 0.0f;
|
|
|
|
if(!pipeInfo.rasterizerDiscardEnable)
|
|
{
|
|
vkr = vt->EndCommandBuffer(Unwrap(cmd));
|
|
CheckVkResult(vkr);
|
|
|
|
float highlightCol[] = {1.0f, 0.0f, 0.0f, 1.0f};
|
|
|
|
// backup state
|
|
VulkanRenderState prevstate = state;
|
|
|
|
// make patched shader
|
|
VkShaderModule mod[2] = {0};
|
|
VkPipeline pipe[2] = {0};
|
|
|
|
// first shader, no culling, writes red
|
|
GetDebugManager()->PatchFixedColShader(mod[0], highlightCol);
|
|
|
|
highlightCol[0] = 0.0f;
|
|
highlightCol[1] = 1.0f;
|
|
|
|
// second shader, normal culling, writes green
|
|
GetDebugManager()->PatchFixedColShader(mod[1], highlightCol);
|
|
|
|
// make patched pipeline
|
|
VkGraphicsPipelineCreateInfo pipeCreateInfo;
|
|
|
|
m_pDriver->GetShaderCache()->MakeGraphicsPipelineInfo(pipeCreateInfo,
|
|
prevstate.graphics.pipeline);
|
|
|
|
// disable all tests possible
|
|
VkPipelineDepthStencilStateCreateInfo *ds =
|
|
(VkPipelineDepthStencilStateCreateInfo *)pipeCreateInfo.pDepthStencilState;
|
|
ds->depthTestEnable = false;
|
|
ds->depthWriteEnable = false;
|
|
ds->stencilTestEnable = false;
|
|
ds->depthBoundsTestEnable = false;
|
|
|
|
VkPipelineRasterizationStateCreateInfo *rs =
|
|
(VkPipelineRasterizationStateCreateInfo *)pipeCreateInfo.pRasterizationState;
|
|
rs->cullMode = VK_CULL_MODE_NONE; // first render without any culling
|
|
rs->rasterizerDiscardEnable = false;
|
|
|
|
// disable tests in dynamic state too
|
|
state.depthTestEnable = VK_FALSE;
|
|
state.depthWriteEnable = VK_FALSE;
|
|
state.depthCompareOp = VK_COMPARE_OP_ALWAYS;
|
|
state.stencilTestEnable = VK_FALSE;
|
|
state.depthBoundsTestEnable = VK_FALSE;
|
|
state.cullMode = VK_CULL_MODE_NONE;
|
|
|
|
if(m_pDriver->GetDeviceEnabledFeatures().depthClamp)
|
|
rs->depthClampEnable = true;
|
|
|
|
VkPipelineColorBlendStateCreateInfo *cb =
|
|
(VkPipelineColorBlendStateCreateInfo *)pipeCreateInfo.pColorBlendState;
|
|
cb->logicOpEnable = false;
|
|
cb->attachmentCount = 1; // only one colour attachment
|
|
for(uint32_t i = 0; i < cb->attachmentCount; i++)
|
|
{
|
|
VkPipelineColorBlendAttachmentState *att =
|
|
(VkPipelineColorBlendAttachmentState *)&cb->pAttachments[i];
|
|
att->blendEnable = false;
|
|
att->colorWriteMask = 0xf;
|
|
}
|
|
|
|
// set our renderpass and shader
|
|
pipeCreateInfo.renderPass = m_Overlay.NoDepthRP;
|
|
pipeCreateInfo.subpass = 0;
|
|
|
|
VkPipelineShaderStageCreateInfo *fragShader = NULL;
|
|
|
|
for(uint32_t i = 0; i < pipeCreateInfo.stageCount; i++)
|
|
{
|
|
VkPipelineShaderStageCreateInfo &sh =
|
|
(VkPipelineShaderStageCreateInfo &)pipeCreateInfo.pStages[i];
|
|
if(sh.stage == VK_SHADER_STAGE_FRAGMENT_BIT)
|
|
{
|
|
sh.module = mod[0];
|
|
sh.pName = "main";
|
|
fragShader = &sh;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(fragShader == NULL)
|
|
{
|
|
// we know this is safe because it's pointing to a static array that's
|
|
// big enough for all shaders
|
|
|
|
VkPipelineShaderStageCreateInfo &sh =
|
|
(VkPipelineShaderStageCreateInfo &)pipeCreateInfo.pStages[pipeCreateInfo.stageCount++];
|
|
sh.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
|
sh.pNext = NULL;
|
|
sh.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
|
|
sh.module = mod[0];
|
|
sh.pName = "main";
|
|
sh.pSpecializationInfo = NULL;
|
|
|
|
fragShader = &sh;
|
|
}
|
|
|
|
vkr = m_pDriver->vkCreateGraphicsPipelines(m_Device, VK_NULL_HANDLE, 1, &pipeCreateInfo, NULL,
|
|
&pipe[0]);
|
|
CheckVkResult(vkr);
|
|
|
|
fragShader->module = mod[1];
|
|
|
|
vkr = m_pDriver->vkCreateGraphicsPipelines(m_Device, VK_NULL_HANDLE, 1, &pipeCreateInfo, NULL,
|
|
&pipe[1]);
|
|
CheckVkResult(vkr);
|
|
|
|
// modify state
|
|
state.SetRenderPass(GetResID(m_Overlay.NoDepthRP));
|
|
state.subpass = 0;
|
|
state.SetFramebuffer(m_pDriver, GetResID(m_Overlay.NoDepthFB));
|
|
|
|
state.graphics.pipeline = GetResID(pipe[0]);
|
|
state.scissors = prevstate.scissors;
|
|
|
|
for(VkRect2D &sc : state.scissors)
|
|
{
|
|
sc.offset.x = 0;
|
|
sc.offset.y = 0;
|
|
sc.extent.width = 16384;
|
|
sc.extent.height = 16384;
|
|
}
|
|
|
|
m_pDriver->ReplayLog(0, eventId, eReplay_OnlyDraw);
|
|
|
|
state.graphics.pipeline = GetResID(pipe[1]);
|
|
state.scissors = prevstate.scissors;
|
|
|
|
m_pDriver->ReplayLog(0, eventId, eReplay_OnlyDraw);
|
|
|
|
// restore state
|
|
state = prevstate;
|
|
|
|
cmd = m_pDriver->GetNextCmd();
|
|
|
|
if(cmd == VK_NULL_HANDLE)
|
|
return ResourceId();
|
|
|
|
vkr = vt->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
|
|
CheckVkResult(vkr);
|
|
|
|
{
|
|
VkClearValue clearval = {};
|
|
VkRenderPassBeginInfo rpbegin = {
|
|
VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
|
NULL,
|
|
Unwrap(m_Overlay.NoDepthRP),
|
|
Unwrap(m_Overlay.NoDepthFB),
|
|
state.renderArea,
|
|
1,
|
|
&clearval,
|
|
};
|
|
vt->CmdBeginRenderPass(Unwrap(cmd), &rpbegin, VK_SUBPASS_CONTENTS_INLINE);
|
|
|
|
VkViewport viewport = state.views[0];
|
|
vt->CmdSetViewport(Unwrap(cmd), 0, 1, &viewport);
|
|
|
|
uint32_t uboOffs = 0;
|
|
|
|
CheckerboardUBOData *ubo = (CheckerboardUBOData *)m_Overlay.m_CheckerUBO.Map(&uboOffs);
|
|
if(!ubo)
|
|
return ResourceId();
|
|
|
|
ubo->BorderWidth = 3;
|
|
ubo->CheckerSquareDimension = 16.0f;
|
|
|
|
// set primary/secondary to the same to 'disable' checkerboard
|
|
ubo->PrimaryColor = ubo->SecondaryColor = Vec4f(0.1f, 0.1f, 0.1f, 1.0f);
|
|
ubo->InnerColor = Vec4f(0.2f, 0.2f, 0.9f, 0.4f);
|
|
|
|
// set viewport rect
|
|
ubo->RectPosition = Vec2f(viewport.x, viewport.y);
|
|
ubo->RectSize = Vec2f(viewport.width, viewport.height);
|
|
|
|
if(m_pDriver->GetExtensions(GetRecord(m_Device)).ext_AMD_negative_viewport_height ||
|
|
m_pDriver->GetExtensions(GetRecord(m_Device)).ext_KHR_maintenance1)
|
|
{
|
|
ubo->RectSize.y = fabsf(viewport.height);
|
|
|
|
// VK_KHR_maintenance1 requires the position to be adjusted as well
|
|
if(m_pDriver->GetExtensions(GetRecord(m_Device)).ext_KHR_maintenance1 &&
|
|
viewport.height < 0.0f)
|
|
ubo->RectPosition.y += viewport.height;
|
|
}
|
|
|
|
m_Overlay.m_CheckerUBO.Unmap();
|
|
|
|
vt->CmdBindPipeline(Unwrap(cmd), VK_PIPELINE_BIND_POINT_GRAPHICS,
|
|
Unwrap(m_Overlay.m_CheckerF16Pipeline[SampleIndex(iminfo.samples)]));
|
|
vt->CmdBindDescriptorSets(Unwrap(cmd), VK_PIPELINE_BIND_POINT_GRAPHICS,
|
|
Unwrap(m_Overlay.m_CheckerPipeLayout), 0, 1,
|
|
UnwrapPtr(m_Overlay.m_CheckerDescSet), 1, &uboOffs);
|
|
|
|
vt->CmdDraw(Unwrap(cmd), 4, 1, 0, 0);
|
|
|
|
if(!state.scissors.empty())
|
|
{
|
|
Vec4f scissor((float)state.scissors[0].offset.x, (float)state.scissors[0].offset.y,
|
|
(float)state.scissors[0].extent.width,
|
|
(float)state.scissors[0].extent.height);
|
|
|
|
ubo = (CheckerboardUBOData *)m_Overlay.m_CheckerUBO.Map(&uboOffs);
|
|
if(!ubo)
|
|
return ResourceId();
|
|
|
|
ubo->BorderWidth = 3;
|
|
ubo->CheckerSquareDimension = 16.0f;
|
|
|
|
// black/white checkered border
|
|
ubo->PrimaryColor = Vec4f(1.0f, 1.0f, 1.0f, 1.0f);
|
|
ubo->SecondaryColor = Vec4f(0.0f, 0.0f, 0.0f, 1.0f);
|
|
|
|
// nothing at all inside
|
|
ubo->InnerColor = Vec4f(0.0f, 0.0f, 0.0f, 0.0f);
|
|
|
|
ubo->RectPosition = Vec2f(scissor.x, scissor.y);
|
|
ubo->RectSize = Vec2f(scissor.z, scissor.w);
|
|
|
|
m_Overlay.m_CheckerUBO.Unmap();
|
|
|
|
viewport.x = scissor.x;
|
|
viewport.y = scissor.y;
|
|
viewport.width = scissor.z;
|
|
viewport.height = scissor.w;
|
|
|
|
vt->CmdSetViewport(Unwrap(cmd), 0, 1, &viewport);
|
|
vt->CmdBindDescriptorSets(Unwrap(cmd), VK_PIPELINE_BIND_POINT_GRAPHICS,
|
|
Unwrap(m_Overlay.m_CheckerPipeLayout), 0, 1,
|
|
UnwrapPtr(m_Overlay.m_CheckerDescSet), 1, &uboOffs);
|
|
|
|
vt->CmdDraw(Unwrap(cmd), 4, 1, 0, 0);
|
|
}
|
|
|
|
vt->CmdEndRenderPass(Unwrap(cmd));
|
|
}
|
|
|
|
vkr = vt->EndCommandBuffer(Unwrap(cmd));
|
|
CheckVkResult(vkr);
|
|
|
|
// submit & flush so that we don't have to keep pipeline around for a while
|
|
m_pDriver->SubmitCmds();
|
|
m_pDriver->FlushQ();
|
|
|
|
cmd = m_pDriver->GetNextCmd();
|
|
|
|
if(cmd == VK_NULL_HANDLE)
|
|
return ResourceId();
|
|
|
|
vkr = vt->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
|
|
CheckVkResult(vkr);
|
|
|
|
for(int i = 0; i < 2; i++)
|
|
{
|
|
m_pDriver->vkDestroyPipeline(m_Device, pipe[i], NULL);
|
|
m_pDriver->vkDestroyShaderModule(m_Device, mod[i], NULL);
|
|
}
|
|
}
|
|
}
|
|
else if(overlay == DebugOverlay::BackfaceCull)
|
|
{
|
|
float highlightCol[] = {0.0f, 0.0f, 0.0f, 0.0f};
|
|
|
|
VkImageMemoryBarrier barrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
|
NULL,
|
|
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
|
VK_ACCESS_TRANSFER_WRITE_BIT,
|
|
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
Unwrap(m_Overlay.Image),
|
|
subRange};
|
|
|
|
DoPipelineBarrier(cmd, 1, &barrier);
|
|
|
|
vt->CmdClearColorImage(Unwrap(cmd), Unwrap(m_Overlay.Image), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
|
(VkClearColorValue *)highlightCol, 1, &subRange);
|
|
|
|
std::swap(barrier.oldLayout, barrier.newLayout);
|
|
std::swap(barrier.srcAccessMask, barrier.dstAccessMask);
|
|
barrier.dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT;
|
|
|
|
DoPipelineBarrier(cmd, 1, &barrier);
|
|
|
|
highlightCol[0] = 1.0f;
|
|
highlightCol[1] = 0.0f;
|
|
highlightCol[3] = 1.0f;
|
|
|
|
if(!pipeInfo.rasterizerDiscardEnable)
|
|
{
|
|
vkr = vt->EndCommandBuffer(Unwrap(cmd));
|
|
CheckVkResult(vkr);
|
|
|
|
// backup state
|
|
VulkanRenderState prevstate = state;
|
|
|
|
// make patched shader
|
|
VkShaderModule mod[2] = {0};
|
|
VkPipeline pipe[2] = {0};
|
|
|
|
// first shader, no culling, writes red
|
|
GetDebugManager()->PatchFixedColShader(mod[0], highlightCol);
|
|
|
|
highlightCol[0] = 0.0f;
|
|
highlightCol[1] = 1.0f;
|
|
|
|
// second shader, normal culling, writes green
|
|
GetDebugManager()->PatchFixedColShader(mod[1], highlightCol);
|
|
|
|
// make patched pipeline
|
|
VkGraphicsPipelineCreateInfo pipeCreateInfo;
|
|
|
|
m_pDriver->GetShaderCache()->MakeGraphicsPipelineInfo(pipeCreateInfo,
|
|
prevstate.graphics.pipeline);
|
|
|
|
// disable all tests possible
|
|
VkPipelineDepthStencilStateCreateInfo *ds =
|
|
(VkPipelineDepthStencilStateCreateInfo *)pipeCreateInfo.pDepthStencilState;
|
|
ds->depthTestEnable = false;
|
|
ds->depthWriteEnable = false;
|
|
ds->stencilTestEnable = false;
|
|
ds->depthBoundsTestEnable = false;
|
|
|
|
VkPipelineRasterizationStateCreateInfo *rs =
|
|
(VkPipelineRasterizationStateCreateInfo *)pipeCreateInfo.pRasterizationState;
|
|
VkCullModeFlags origCullMode = prevstate.cullMode;
|
|
rs->cullMode = VK_CULL_MODE_NONE; // first render without any culling
|
|
rs->rasterizerDiscardEnable = false;
|
|
|
|
if(m_pDriver->GetDeviceEnabledFeatures().depthClamp)
|
|
rs->depthClampEnable = true;
|
|
|
|
// disable tests in dynamic state too
|
|
state.depthTestEnable = VK_FALSE;
|
|
state.depthWriteEnable = VK_FALSE;
|
|
state.depthCompareOp = VK_COMPARE_OP_ALWAYS;
|
|
state.stencilTestEnable = VK_FALSE;
|
|
state.depthBoundsTestEnable = VK_FALSE;
|
|
state.cullMode = VK_CULL_MODE_NONE;
|
|
|
|
VkPipelineColorBlendStateCreateInfo *cb =
|
|
(VkPipelineColorBlendStateCreateInfo *)pipeCreateInfo.pColorBlendState;
|
|
cb->logicOpEnable = false;
|
|
cb->attachmentCount = 1; // only one colour attachment
|
|
for(uint32_t i = 0; i < cb->attachmentCount; i++)
|
|
{
|
|
VkPipelineColorBlendAttachmentState *att =
|
|
(VkPipelineColorBlendAttachmentState *)&cb->pAttachments[i];
|
|
att->blendEnable = false;
|
|
att->colorWriteMask = 0xf;
|
|
}
|
|
|
|
// set our renderpass and shader
|
|
pipeCreateInfo.renderPass = m_Overlay.NoDepthRP;
|
|
pipeCreateInfo.subpass = 0;
|
|
|
|
VkPipelineShaderStageCreateInfo *fragShader = NULL;
|
|
|
|
for(uint32_t i = 0; i < pipeCreateInfo.stageCount; i++)
|
|
{
|
|
VkPipelineShaderStageCreateInfo &sh =
|
|
(VkPipelineShaderStageCreateInfo &)pipeCreateInfo.pStages[i];
|
|
if(sh.stage == VK_SHADER_STAGE_FRAGMENT_BIT)
|
|
{
|
|
sh.module = mod[0];
|
|
sh.pName = "main";
|
|
fragShader = &sh;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(fragShader == NULL)
|
|
{
|
|
// we know this is safe because it's pointing to a static array that's
|
|
// big enough for all shaders
|
|
|
|
VkPipelineShaderStageCreateInfo &sh =
|
|
(VkPipelineShaderStageCreateInfo &)pipeCreateInfo.pStages[pipeCreateInfo.stageCount++];
|
|
sh.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
|
sh.pNext = NULL;
|
|
sh.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
|
|
sh.module = mod[0];
|
|
sh.pName = "main";
|
|
sh.pSpecializationInfo = NULL;
|
|
|
|
fragShader = &sh;
|
|
}
|
|
|
|
vkr = m_pDriver->vkCreateGraphicsPipelines(m_Device, VK_NULL_HANDLE, 1, &pipeCreateInfo, NULL,
|
|
&pipe[0]);
|
|
CheckVkResult(vkr);
|
|
|
|
fragShader->module = mod[1];
|
|
rs->cullMode = origCullMode;
|
|
|
|
vkr = m_pDriver->vkCreateGraphicsPipelines(m_Device, VK_NULL_HANDLE, 1, &pipeCreateInfo, NULL,
|
|
&pipe[1]);
|
|
CheckVkResult(vkr);
|
|
|
|
// modify state
|
|
state.SetRenderPass(GetResID(m_Overlay.NoDepthRP));
|
|
state.subpass = 0;
|
|
state.SetFramebuffer(m_pDriver, GetResID(m_Overlay.NoDepthFB));
|
|
|
|
state.graphics.pipeline = GetResID(pipe[0]);
|
|
|
|
m_pDriver->ReplayLog(0, eventId, eReplay_OnlyDraw);
|
|
|
|
state.graphics.pipeline = GetResID(pipe[1]);
|
|
state.cullMode = origCullMode;
|
|
|
|
m_pDriver->ReplayLog(0, eventId, eReplay_OnlyDraw);
|
|
|
|
// submit & flush so that we don't have to keep pipeline around for a while
|
|
m_pDriver->SubmitCmds();
|
|
m_pDriver->FlushQ();
|
|
|
|
cmd = m_pDriver->GetNextCmd();
|
|
|
|
if(cmd == VK_NULL_HANDLE)
|
|
return ResourceId();
|
|
|
|
vkr = vt->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
|
|
CheckVkResult(vkr);
|
|
|
|
// restore state
|
|
state = prevstate;
|
|
|
|
for(int i = 0; i < 2; i++)
|
|
{
|
|
m_pDriver->vkDestroyPipeline(m_Device, pipe[i], NULL);
|
|
m_pDriver->vkDestroyShaderModule(m_Device, mod[i], NULL);
|
|
}
|
|
}
|
|
}
|
|
else if(overlay == DebugOverlay::Depth || overlay == DebugOverlay::Stencil)
|
|
{
|
|
float highlightCol[] = {0.0f, 0.0f, 0.0f, 0.0f};
|
|
|
|
VkImageMemoryBarrier barrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
|
NULL,
|
|
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
|
VK_ACCESS_TRANSFER_WRITE_BIT,
|
|
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
Unwrap(m_Overlay.Image),
|
|
subRange};
|
|
|
|
DoPipelineBarrier(cmd, 1, &barrier);
|
|
|
|
vt->CmdClearColorImage(Unwrap(cmd), Unwrap(m_Overlay.Image), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
|
(VkClearColorValue *)highlightCol, 1, &subRange);
|
|
|
|
std::swap(barrier.oldLayout, barrier.newLayout);
|
|
std::swap(barrier.srcAccessMask, barrier.dstAccessMask);
|
|
barrier.dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT;
|
|
|
|
DoPipelineBarrier(cmd, 1, &barrier);
|
|
|
|
if(!pipeInfo.rasterizerDiscardEnable)
|
|
{
|
|
vkr = vt->EndCommandBuffer(Unwrap(cmd));
|
|
CheckVkResult(vkr);
|
|
|
|
VkFramebuffer depthFB = VK_NULL_HANDLE;
|
|
VkRenderPass depthRP = VK_NULL_HANDLE;
|
|
|
|
VulkanCreationInfo &createinfo = m_pDriver->m_CreationInfo;
|
|
|
|
ResourceId depthStencilView;
|
|
|
|
if(state.dynamicRendering.active)
|
|
{
|
|
depthStencilView = GetResID(state.dynamicRendering.depth.imageView);
|
|
if(depthStencilView == ResourceId())
|
|
depthStencilView = GetResID(state.dynamicRendering.stencil.imageView);
|
|
}
|
|
else
|
|
{
|
|
RDCASSERT(state.subpass < createinfo.m_RenderPass[state.GetRenderPass()].subpasses.size());
|
|
int32_t dsIdx =
|
|
createinfo.m_RenderPass[state.GetRenderPass()].subpasses[state.subpass].depthstencilAttachment;
|
|
|
|
// make a renderpass and framebuffer for rendering to overlay color and using
|
|
// depth buffer from the orignial render
|
|
if(dsIdx >= 0 &&
|
|
dsIdx < (int32_t)createinfo.m_Framebuffer[state.GetFramebuffer()].attachments.size())
|
|
{
|
|
depthStencilView = state.GetFramebufferAttachments()[dsIdx];
|
|
}
|
|
}
|
|
|
|
if(depthStencilView != ResourceId())
|
|
{
|
|
VkAttachmentDescription attDescs[] = {
|
|
{0, overlayFormat, VK_SAMPLE_COUNT_1_BIT, VK_ATTACHMENT_LOAD_OP_LOAD,
|
|
VK_ATTACHMENT_STORE_OP_STORE, VK_ATTACHMENT_LOAD_OP_DONT_CARE,
|
|
VK_ATTACHMENT_STORE_OP_DONT_CARE, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
|
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL},
|
|
{0, VK_FORMAT_UNDEFINED, VK_SAMPLE_COUNT_1_BIT, // will patch this just below
|
|
VK_ATTACHMENT_LOAD_OP_LOAD, VK_ATTACHMENT_STORE_OP_STORE, VK_ATTACHMENT_LOAD_OP_LOAD,
|
|
VK_ATTACHMENT_STORE_OP_STORE, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
|
|
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL},
|
|
};
|
|
|
|
VulkanCreationInfo::ImageView &depthViewInfo = createinfo.m_ImageView[depthStencilView];
|
|
|
|
ResourceId depthIm = depthViewInfo.image;
|
|
VulkanCreationInfo::Image &depthImageInfo = createinfo.m_Image[depthIm];
|
|
|
|
attDescs[1].format = depthImageInfo.format;
|
|
attDescs[0].samples = attDescs[1].samples = iminfo.samples;
|
|
|
|
{
|
|
LockedConstImageStateRef imState = m_pDriver->FindConstImageState(depthIm);
|
|
if(imState)
|
|
{
|
|
// find the state that overlaps the view's subresource range start. We assume all
|
|
// subresources are correctly in the same state (as they should be) so we just need to
|
|
// find the first match.
|
|
auto it = imState->subresourceStates.RangeBegin(depthViewInfo.range);
|
|
if(it != imState->subresourceStates.end())
|
|
attDescs[1].initialLayout = attDescs[1].finalLayout = it->state().newLayout;
|
|
}
|
|
}
|
|
|
|
VkAttachmentReference colRef = {0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
|
|
VkAttachmentReference dsRef = {1, attDescs[1].initialLayout};
|
|
|
|
VkSubpassDescription subp = {
|
|
0, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
|
0, NULL, // inputs
|
|
1, &colRef, // color
|
|
NULL, // resolve
|
|
&dsRef, // depth-stencil
|
|
0, NULL, // preserve
|
|
};
|
|
|
|
VkRenderPassCreateInfo rpinfo = {
|
|
VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
|
|
NULL,
|
|
0,
|
|
2,
|
|
attDescs,
|
|
1,
|
|
&subp,
|
|
0,
|
|
NULL, // dependencies
|
|
};
|
|
|
|
if(multiviewMask > 0)
|
|
rpinfo.pNext = &multiviewRP;
|
|
|
|
vkr = m_pDriver->vkCreateRenderPass(m_Device, &rpinfo, NULL, &depthRP);
|
|
CheckVkResult(vkr);
|
|
|
|
VkImageView views[] = {
|
|
m_Overlay.ImageView,
|
|
m_pDriver->GetResourceManager()->GetCurrentHandle<VkImageView>(depthStencilView),
|
|
};
|
|
|
|
// Create framebuffer rendering just to overlay image, no depth
|
|
VkFramebufferCreateInfo fbinfo = {
|
|
VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
|
|
NULL,
|
|
0,
|
|
depthRP,
|
|
2,
|
|
views,
|
|
(uint32_t)m_Overlay.ImageDim.width,
|
|
(uint32_t)m_Overlay.ImageDim.height,
|
|
1,
|
|
};
|
|
|
|
vkr = m_pDriver->vkCreateFramebuffer(m_Device, &fbinfo, NULL, &depthFB);
|
|
CheckVkResult(vkr);
|
|
}
|
|
|
|
// if depthRP is NULL, so is depthFB, and it means no depth buffer was
|
|
// bound, so we just render green.
|
|
|
|
highlightCol[0] = 1.0f;
|
|
highlightCol[1] = 0.0f;
|
|
highlightCol[3] = 1.0f;
|
|
|
|
// backup state
|
|
VulkanRenderState prevstate = state;
|
|
|
|
// make patched shader
|
|
VkShaderModule failmod = {}, passmod = {};
|
|
VkPipeline failpipe = {}, passpipe = {};
|
|
|
|
// first shader, no depth/stencil testing, writes red
|
|
GetDebugManager()->PatchFixedColShader(failmod, highlightCol);
|
|
|
|
highlightCol[0] = 0.0f;
|
|
highlightCol[1] = 1.0f;
|
|
|
|
// second shader, enabled depth/stencil testing, writes green
|
|
GetDebugManager()->PatchFixedColShader(passmod, highlightCol);
|
|
|
|
// make patched pipeline
|
|
VkGraphicsPipelineCreateInfo pipeCreateInfo;
|
|
|
|
m_pDriver->GetShaderCache()->MakeGraphicsPipelineInfo(pipeCreateInfo,
|
|
prevstate.graphics.pipeline);
|
|
|
|
// disable all tests possible
|
|
VkPipelineDepthStencilStateCreateInfo *ds =
|
|
(VkPipelineDepthStencilStateCreateInfo *)pipeCreateInfo.pDepthStencilState;
|
|
VkBool32 origDepthTest = prevstate.depthTestEnable;
|
|
ds->depthTestEnable = false;
|
|
ds->depthWriteEnable = false;
|
|
VkBool32 origStencilTest = prevstate.stencilTestEnable;
|
|
ds->stencilTestEnable = false;
|
|
ds->depthBoundsTestEnable = false;
|
|
|
|
VkPipelineColorBlendStateCreateInfo *cb =
|
|
(VkPipelineColorBlendStateCreateInfo *)pipeCreateInfo.pColorBlendState;
|
|
cb->logicOpEnable = false;
|
|
cb->attachmentCount = 1; // only one colour attachment
|
|
for(uint32_t i = 0; i < cb->attachmentCount; i++)
|
|
{
|
|
VkPipelineColorBlendAttachmentState *att =
|
|
(VkPipelineColorBlendAttachmentState *)&cb->pAttachments[i];
|
|
att->blendEnable = false;
|
|
att->colorWriteMask = 0xf;
|
|
}
|
|
|
|
// subpass 0 in either render pass
|
|
pipeCreateInfo.subpass = 0;
|
|
|
|
VkPipelineShaderStageCreateInfo *fragShader = NULL;
|
|
|
|
for(uint32_t i = 0; i < pipeCreateInfo.stageCount; i++)
|
|
{
|
|
VkPipelineShaderStageCreateInfo &sh =
|
|
(VkPipelineShaderStageCreateInfo &)pipeCreateInfo.pStages[i];
|
|
if(sh.stage == VK_SHADER_STAGE_FRAGMENT_BIT)
|
|
{
|
|
sh.pName = "main";
|
|
fragShader = &sh;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(fragShader == NULL)
|
|
{
|
|
// we know this is safe because it's pointing to a static array that's
|
|
// big enough for all shaders
|
|
|
|
VkPipelineShaderStageCreateInfo &sh =
|
|
(VkPipelineShaderStageCreateInfo &)pipeCreateInfo.pStages[pipeCreateInfo.stageCount++];
|
|
sh.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
|
sh.pNext = NULL;
|
|
sh.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
|
|
sh.pName = "main";
|
|
sh.pSpecializationInfo = NULL;
|
|
|
|
fragShader = &sh;
|
|
}
|
|
|
|
fragShader->module = passmod;
|
|
|
|
if(depthRP != VK_NULL_HANDLE)
|
|
{
|
|
if(overlay == DebugOverlay::Depth)
|
|
ds->depthTestEnable = origDepthTest;
|
|
else
|
|
ds->stencilTestEnable = origStencilTest;
|
|
pipeCreateInfo.renderPass = depthRP;
|
|
}
|
|
else
|
|
{
|
|
pipeCreateInfo.renderPass = m_Overlay.NoDepthRP;
|
|
}
|
|
|
|
vkr = m_pDriver->vkCreateGraphicsPipelines(m_Device, VK_NULL_HANDLE, 1, &pipeCreateInfo, NULL,
|
|
&passpipe);
|
|
CheckVkResult(vkr);
|
|
|
|
fragShader->module = failmod;
|
|
|
|
// set our renderpass and shader
|
|
pipeCreateInfo.renderPass = m_Overlay.NoDepthRP;
|
|
|
|
// disable culling/discard and enable depth clamp. That way we show any failures due to these
|
|
VkPipelineRasterizationStateCreateInfo *rs =
|
|
(VkPipelineRasterizationStateCreateInfo *)pipeCreateInfo.pRasterizationState;
|
|
rs->cullMode = VK_CULL_MODE_NONE;
|
|
rs->rasterizerDiscardEnable = false;
|
|
|
|
if(m_pDriver->GetDeviceEnabledFeatures().depthClamp)
|
|
rs->depthClampEnable = true;
|
|
|
|
vkr = m_pDriver->vkCreateGraphicsPipelines(m_Device, VK_NULL_HANDLE, 1, &pipeCreateInfo, NULL,
|
|
&failpipe);
|
|
CheckVkResult(vkr);
|
|
|
|
// modify state
|
|
state.SetRenderPass(GetResID(m_Overlay.NoDepthRP));
|
|
state.subpass = 0;
|
|
state.SetFramebuffer(m_pDriver, GetResID(m_Overlay.NoDepthFB));
|
|
|
|
state.graphics.pipeline = GetResID(failpipe);
|
|
|
|
// disable tests in dynamic state too
|
|
state.depthTestEnable = VK_FALSE;
|
|
state.depthWriteEnable = VK_FALSE;
|
|
state.stencilTestEnable = VK_FALSE;
|
|
state.depthBoundsTestEnable = VK_FALSE;
|
|
state.cullMode = VK_CULL_MODE_NONE;
|
|
|
|
m_pDriver->ReplayLog(0, eventId, eReplay_OnlyDraw);
|
|
|
|
state.graphics.pipeline = GetResID(passpipe);
|
|
if(depthRP != VK_NULL_HANDLE)
|
|
{
|
|
state.SetRenderPass(GetResID(depthRP));
|
|
state.SetFramebuffer(m_pDriver, GetResID(depthFB));
|
|
}
|
|
|
|
if(overlay == DebugOverlay::Depth)
|
|
state.depthTestEnable = origDepthTest;
|
|
else
|
|
state.stencilTestEnable = origStencilTest;
|
|
|
|
m_pDriver->ReplayLog(0, eventId, eReplay_OnlyDraw);
|
|
|
|
// submit & flush so that we don't have to keep pipeline around for a while
|
|
m_pDriver->SubmitCmds();
|
|
m_pDriver->FlushQ();
|
|
|
|
cmd = m_pDriver->GetNextCmd();
|
|
|
|
if(cmd == VK_NULL_HANDLE)
|
|
return ResourceId();
|
|
|
|
vkr = vt->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
|
|
CheckVkResult(vkr);
|
|
|
|
// restore state
|
|
state = prevstate;
|
|
|
|
m_pDriver->vkDestroyPipeline(m_Device, failpipe, NULL);
|
|
m_pDriver->vkDestroyShaderModule(m_Device, failmod, NULL);
|
|
m_pDriver->vkDestroyPipeline(m_Device, passpipe, NULL);
|
|
m_pDriver->vkDestroyShaderModule(m_Device, passmod, NULL);
|
|
|
|
if(depthRP != VK_NULL_HANDLE)
|
|
{
|
|
m_pDriver->vkDestroyRenderPass(m_Device, depthRP, NULL);
|
|
m_pDriver->vkDestroyFramebuffer(m_Device, depthFB, NULL);
|
|
}
|
|
}
|
|
}
|
|
else if(overlay == DebugOverlay::ClearBeforeDraw || overlay == DebugOverlay::ClearBeforePass)
|
|
{
|
|
// clear the overlay image itself
|
|
float black[] = {0.0f, 0.0f, 0.0f, 0.0f};
|
|
|
|
VkImageMemoryBarrier barrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
|
NULL,
|
|
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
|
VK_ACCESS_TRANSFER_WRITE_BIT,
|
|
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
Unwrap(m_Overlay.Image),
|
|
subRange};
|
|
|
|
DoPipelineBarrier(cmd, 1, &barrier);
|
|
|
|
vt->CmdClearColorImage(Unwrap(cmd), Unwrap(m_Overlay.Image), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
|
(VkClearColorValue *)black, 1, &subRange);
|
|
|
|
std::swap(barrier.oldLayout, barrier.newLayout);
|
|
std::swap(barrier.srcAccessMask, barrier.dstAccessMask);
|
|
barrier.dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT;
|
|
|
|
DoPipelineBarrier(cmd, 1, &barrier);
|
|
|
|
rdcarray<uint32_t> events = passEvents;
|
|
|
|
if(overlay == DebugOverlay::ClearBeforeDraw)
|
|
events.clear();
|
|
|
|
events.push_back(eventId);
|
|
|
|
{
|
|
vkr = vt->EndCommandBuffer(Unwrap(cmd));
|
|
CheckVkResult(vkr);
|
|
|
|
#if ENABLED(SINGLE_FLUSH_VALIDATE)
|
|
m_pDriver->SubmitCmds();
|
|
#endif
|
|
|
|
size_t startEvent = 0;
|
|
|
|
// this is a bit of a hack, but it's the simplest fix without refactoring the ClearBefore..
|
|
// overlay to copy the result off into the overlay texture (which has its own issues with
|
|
// alpha channels since the overlay expects to be blended). If we are selecting a new event
|
|
// with ClearBeforeDraw enabled then we do this here, and then later the pipeline is fetched
|
|
// and that triggers the bindless feedback - which will reset the state and undo our clear. If
|
|
// we force it to be cached here then it won't mess things up later.
|
|
FetchShaderFeedback(eventId);
|
|
|
|
// if we're ClearBeforePass the first event will be a vkBeginRenderPass.
|
|
// if there are any other events, we need to play up to right before them
|
|
// so that we have all the render state set up to do
|
|
// BeginRenderPassAndApplyState and a clear. If it's just the begin, we
|
|
// just play including it, do the clear, then we won't replay anything
|
|
// in the loop below
|
|
if(overlay == DebugOverlay::ClearBeforePass)
|
|
{
|
|
const ActionDescription *action = m_pDriver->GetAction(events[0]);
|
|
if(action && action->flags & ActionFlags::BeginPass)
|
|
{
|
|
if(events.size() == 1)
|
|
{
|
|
m_pDriver->ReplayLog(0, events[0], eReplay_Full);
|
|
}
|
|
else
|
|
{
|
|
startEvent = 1;
|
|
m_pDriver->ReplayLog(0, events[1], eReplay_WithoutDraw);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_pDriver->ReplayLog(0, events[0], eReplay_WithoutDraw);
|
|
}
|
|
|
|
cmd = m_pDriver->GetNextCmd();
|
|
|
|
if(cmd == VK_NULL_HANDLE)
|
|
return ResourceId();
|
|
|
|
vkr = vt->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
|
|
CheckVkResult(vkr);
|
|
|
|
state.BeginRenderPassAndApplyState(m_pDriver, cmd, VulkanRenderState::BindGraphics, false);
|
|
|
|
VkClearAttachment clearatt = {VK_IMAGE_ASPECT_COLOR_BIT, 0, {}};
|
|
memcpy(clearatt.clearValue.color.float32, &clearCol, sizeof(clearatt.clearValue.color.float32));
|
|
rdcarray<VkClearAttachment> atts;
|
|
|
|
if(state.dynamicRendering.active)
|
|
{
|
|
for(size_t i = 0; i < state.dynamicRendering.color.size(); i++)
|
|
{
|
|
if(state.dynamicRendering.color[i].imageView == VK_NULL_HANDLE)
|
|
continue;
|
|
|
|
clearatt.colorAttachment = (uint32_t)i;
|
|
atts.push_back(clearatt);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
VulkanCreationInfo::RenderPass &rp =
|
|
m_pDriver->m_CreationInfo.m_RenderPass[state.GetRenderPass()];
|
|
|
|
for(size_t i = 0; i < rp.subpasses[state.subpass].colorAttachments.size(); i++)
|
|
{
|
|
clearatt.colorAttachment = (uint32_t)i;
|
|
atts.push_back(clearatt);
|
|
}
|
|
}
|
|
|
|
// Try to clear depth as well, to help debug shadow rendering
|
|
if(state.graphics.pipeline != ResourceId() && IsDepthOrStencilFormat(iminfo.format))
|
|
{
|
|
VkCompareOp depthCompareOp = state.depthCompareOp;
|
|
|
|
// If the depth func is equal or not equal, don't clear at all since the output would be
|
|
// altered in an way that would cause replay to produce mostly incorrect results.
|
|
// Similarly, skip if the depth func is always, as we'd have a 50% chance of guessing the
|
|
// wrong clear value.
|
|
if(depthCompareOp != VK_COMPARE_OP_EQUAL && depthCompareOp != VK_COMPARE_OP_NOT_EQUAL &&
|
|
depthCompareOp != VK_COMPARE_OP_ALWAYS)
|
|
{
|
|
// If the depth func is less or less equal, clear to 1 instead of 0
|
|
bool depthFuncLess =
|
|
depthCompareOp == VK_COMPARE_OP_LESS || depthCompareOp == VK_COMPARE_OP_LESS_OR_EQUAL;
|
|
float depthClear = depthFuncLess ? 1.0f : 0.0f;
|
|
|
|
VkClearAttachment clearDepthAtt = {
|
|
VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, 0, {}};
|
|
clearDepthAtt.clearValue.depthStencil.depth = depthClear;
|
|
clearDepthAtt.clearValue.depthStencil.stencil = 0;
|
|
|
|
atts.push_back(clearDepthAtt);
|
|
}
|
|
}
|
|
|
|
VkClearRect rect = {
|
|
state.renderArea, 0, 1,
|
|
};
|
|
|
|
vt->CmdClearAttachments(Unwrap(cmd), (uint32_t)atts.size(), &atts[0], 1, &rect);
|
|
|
|
state.EndRenderPass(cmd);
|
|
|
|
vkr = vt->EndCommandBuffer(Unwrap(cmd));
|
|
CheckVkResult(vkr);
|
|
|
|
for(size_t i = startEvent; i < events.size(); i++)
|
|
{
|
|
m_pDriver->ReplayLog(events[i], events[i], eReplay_OnlyDraw);
|
|
|
|
if(overlay == DebugOverlay::ClearBeforePass && i + 1 < events.size())
|
|
m_pDriver->ReplayLog(events[i] + 1, events[i + 1], eReplay_WithoutDraw);
|
|
}
|
|
|
|
cmd = m_pDriver->GetNextCmd();
|
|
|
|
if(cmd == VK_NULL_HANDLE)
|
|
return ResourceId();
|
|
|
|
vkr = vt->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
|
|
CheckVkResult(vkr);
|
|
}
|
|
}
|
|
else if(overlay == DebugOverlay::QuadOverdrawPass || overlay == DebugOverlay::QuadOverdrawDraw)
|
|
{
|
|
if(m_Overlay.m_QuadResolvePipeline[0] != VK_NULL_HANDLE && !pipeInfo.rasterizerDiscardEnable)
|
|
{
|
|
VulkanRenderState prevstate = state;
|
|
|
|
SCOPED_TIMER("Quad Overdraw");
|
|
|
|
float black[] = {0.0f, 0.0f, 0.0f, 0.0f};
|
|
|
|
VkImageMemoryBarrier barrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
|
NULL,
|
|
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
|
VK_ACCESS_TRANSFER_WRITE_BIT,
|
|
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
Unwrap(m_Overlay.Image),
|
|
subRange};
|
|
|
|
DoPipelineBarrier(cmd, 1, &barrier);
|
|
|
|
vt->CmdClearColorImage(Unwrap(cmd), Unwrap(m_Overlay.Image),
|
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, (VkClearColorValue *)black, 1,
|
|
&subRange);
|
|
|
|
std::swap(barrier.oldLayout, barrier.newLayout);
|
|
std::swap(barrier.srcAccessMask, barrier.dstAccessMask);
|
|
barrier.dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT;
|
|
|
|
DoPipelineBarrier(cmd, 1, &barrier);
|
|
|
|
rdcarray<uint32_t> events = passEvents;
|
|
|
|
if(overlay == DebugOverlay::QuadOverdrawDraw)
|
|
events.clear();
|
|
|
|
events.push_back(eventId);
|
|
|
|
// if we're rendering the whole pass, and the first action is a BeginRenderPass, don't include
|
|
// it in the list. We want to start by replaying into the renderpass so that we have the
|
|
// correct state being applied.
|
|
if(overlay == DebugOverlay::QuadOverdrawPass)
|
|
{
|
|
const ActionDescription *action = m_pDriver->GetAction(events[0]);
|
|
if(action->flags & ActionFlags::BeginPass)
|
|
events.erase(0);
|
|
}
|
|
|
|
VkImage quadImg;
|
|
VkDeviceMemory quadImgMem;
|
|
VkImageView quadImgView;
|
|
|
|
VkImageCreateInfo imInfo = {
|
|
VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
|
NULL,
|
|
0,
|
|
VK_IMAGE_TYPE_2D,
|
|
VK_FORMAT_R32_UINT,
|
|
{RDCMAX(1U, m_Overlay.ImageDim.width >> 1), RDCMAX(1U, m_Overlay.ImageDim.height >> 1), 1},
|
|
1,
|
|
4,
|
|
VK_SAMPLE_COUNT_1_BIT,
|
|
VK_IMAGE_TILING_OPTIMAL,
|
|
VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
|
|
VK_SHARING_MODE_EXCLUSIVE,
|
|
0,
|
|
NULL,
|
|
VK_IMAGE_LAYOUT_UNDEFINED,
|
|
};
|
|
|
|
vkr = m_pDriver->vkCreateImage(m_Device, &imInfo, NULL, &quadImg);
|
|
CheckVkResult(vkr);
|
|
|
|
NameVulkanObject(quadImg, "m_Overlay.quadImg");
|
|
|
|
VkMemoryRequirements mrq = {0};
|
|
|
|
m_pDriver->vkGetImageMemoryRequirements(m_Device, quadImg, &mrq);
|
|
|
|
VkMemoryAllocateInfo allocInfo = {
|
|
VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, NULL, mrq.size,
|
|
m_pDriver->GetGPULocalMemoryIndex(mrq.memoryTypeBits),
|
|
};
|
|
|
|
vkr = m_pDriver->vkAllocateMemory(m_Device, &allocInfo, NULL, &quadImgMem);
|
|
CheckVkResult(vkr);
|
|
|
|
if(vkr != VK_SUCCESS)
|
|
return ResourceId();
|
|
|
|
vkr = m_pDriver->vkBindImageMemory(m_Device, quadImg, quadImgMem, 0);
|
|
CheckVkResult(vkr);
|
|
|
|
VkImageViewCreateInfo viewinfo = {
|
|
VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
|
NULL,
|
|
0,
|
|
quadImg,
|
|
VK_IMAGE_VIEW_TYPE_2D_ARRAY,
|
|
VK_FORMAT_R32_UINT,
|
|
{VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY,
|
|
VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY},
|
|
{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 4},
|
|
};
|
|
|
|
vkr = m_pDriver->vkCreateImageView(m_Device, &viewinfo, NULL, &quadImgView);
|
|
CheckVkResult(vkr);
|
|
|
|
// update descriptor to point to our R32 result image
|
|
VkDescriptorImageInfo imdesc = {0};
|
|
imdesc.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
|
|
imdesc.sampler = VK_NULL_HANDLE;
|
|
imdesc.imageView = Unwrap(quadImgView);
|
|
|
|
VkWriteDescriptorSet write = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
|
NULL,
|
|
Unwrap(m_Overlay.m_QuadDescSet),
|
|
0,
|
|
0,
|
|
1,
|
|
VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
|
|
&imdesc,
|
|
NULL,
|
|
NULL};
|
|
vt->UpdateDescriptorSets(Unwrap(m_Device), 1, &write, 0, NULL);
|
|
|
|
VkImageMemoryBarrier quadImBarrier = {
|
|
VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
|
NULL,
|
|
0,
|
|
VK_ACCESS_TRANSFER_WRITE_BIT,
|
|
VK_IMAGE_LAYOUT_UNDEFINED,
|
|
VK_IMAGE_LAYOUT_GENERAL,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
Unwrap(quadImg),
|
|
{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 4},
|
|
};
|
|
|
|
// clear all to black
|
|
DoPipelineBarrier(cmd, 1, &quadImBarrier);
|
|
vt->CmdClearColorImage(Unwrap(cmd), Unwrap(quadImg), VK_IMAGE_LAYOUT_GENERAL,
|
|
(VkClearColorValue *)&black, 1, &quadImBarrier.subresourceRange);
|
|
|
|
quadImBarrier.srcAccessMask = quadImBarrier.dstAccessMask;
|
|
quadImBarrier.oldLayout = quadImBarrier.newLayout;
|
|
|
|
quadImBarrier.dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
|
|
|
|
// set to general layout, for load/store operations
|
|
DoPipelineBarrier(cmd, 1, &quadImBarrier);
|
|
|
|
VkMemoryBarrier memBarrier = {
|
|
VK_STRUCTURE_TYPE_MEMORY_BARRIER, NULL, VK_ACCESS_ALL_WRITE_BITS, VK_ACCESS_ALL_READ_BITS,
|
|
};
|
|
|
|
DoPipelineBarrier(cmd, 1, &memBarrier);
|
|
|
|
// end this cmd buffer so the image is in the right state for the next part
|
|
vkr = vt->EndCommandBuffer(Unwrap(cmd));
|
|
CheckVkResult(vkr);
|
|
|
|
#if ENABLED(SINGLE_FLUSH_VALIDATE)
|
|
m_pDriver->SubmitCmds();
|
|
#endif
|
|
|
|
m_pDriver->ReplayLog(0, events[0], eReplay_WithoutDraw);
|
|
|
|
{
|
|
// declare callback struct here
|
|
VulkanQuadOverdrawCallback cb(m_pDriver, m_Overlay.m_QuadDescSetLayout,
|
|
m_Overlay.m_QuadDescSet, events);
|
|
|
|
m_pDriver->ReplayLog(events.front(), events.back(), eReplay_Full);
|
|
|
|
// resolve pass
|
|
{
|
|
cmd = m_pDriver->GetNextCmd();
|
|
|
|
if(cmd == VK_NULL_HANDLE)
|
|
return ResourceId();
|
|
|
|
vkr = vt->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
|
|
CheckVkResult(vkr);
|
|
|
|
quadImBarrier.srcAccessMask = quadImBarrier.dstAccessMask;
|
|
quadImBarrier.oldLayout = quadImBarrier.newLayout;
|
|
|
|
quadImBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
|
|
|
// wait for writing to finish
|
|
DoPipelineBarrier(cmd, 1, &quadImBarrier);
|
|
|
|
VkClearValue clearval = {};
|
|
VkRenderPassBeginInfo rpbegin = {
|
|
VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
|
NULL,
|
|
Unwrap(m_Overlay.NoDepthRP),
|
|
Unwrap(m_Overlay.NoDepthFB),
|
|
state.renderArea,
|
|
1,
|
|
&clearval,
|
|
};
|
|
vt->CmdBeginRenderPass(Unwrap(cmd), &rpbegin, VK_SUBPASS_CONTENTS_INLINE);
|
|
|
|
vt->CmdBindPipeline(Unwrap(cmd), VK_PIPELINE_BIND_POINT_GRAPHICS,
|
|
Unwrap(m_Overlay.m_QuadResolvePipeline[SampleIndex(iminfo.samples)]));
|
|
vt->CmdBindDescriptorSets(Unwrap(cmd), VK_PIPELINE_BIND_POINT_GRAPHICS,
|
|
Unwrap(m_Overlay.m_QuadResolvePipeLayout), 0, 1,
|
|
UnwrapPtr(m_Overlay.m_QuadDescSet), 0, NULL);
|
|
|
|
VkViewport viewport = {
|
|
0.0f, 0.0f, (float)m_Overlay.ImageDim.width, (float)m_Overlay.ImageDim.height,
|
|
0.0f, 1.0f};
|
|
vt->CmdSetViewport(Unwrap(cmd), 0, 1, &viewport);
|
|
|
|
vt->CmdDraw(Unwrap(cmd), 4, 1, 0, 0);
|
|
vt->CmdEndRenderPass(Unwrap(cmd));
|
|
|
|
vkr = vt->EndCommandBuffer(Unwrap(cmd));
|
|
CheckVkResult(vkr);
|
|
}
|
|
|
|
m_pDriver->SubmitCmds();
|
|
m_pDriver->FlushQ();
|
|
|
|
m_pDriver->vkDestroyImageView(m_Device, quadImgView, NULL);
|
|
m_pDriver->vkDestroyImage(m_Device, quadImg, NULL);
|
|
m_pDriver->vkFreeMemory(m_Device, quadImgMem, NULL);
|
|
}
|
|
|
|
// restore back to normal
|
|
m_pDriver->ReplayLog(0, eventId, eReplay_WithoutDraw);
|
|
|
|
cmd = m_pDriver->GetNextCmd();
|
|
|
|
if(cmd == VK_NULL_HANDLE)
|
|
return ResourceId();
|
|
|
|
vkr = vt->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
|
|
CheckVkResult(vkr);
|
|
}
|
|
}
|
|
else if(overlay == DebugOverlay::TriangleSizePass || overlay == DebugOverlay::TriangleSizeDraw)
|
|
{
|
|
if(!pipeInfo.rasterizerDiscardEnable)
|
|
{
|
|
VulkanRenderState prevstate = state;
|
|
|
|
VkPipelineShaderStageCreateInfo stages[3] = {
|
|
{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, NULL, 0, VK_SHADER_STAGE_VERTEX_BIT,
|
|
shaderCache->GetBuiltinModule(BuiltinShader::MeshVS), "main", NULL},
|
|
{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, NULL, 0, VK_SHADER_STAGE_FRAGMENT_BIT,
|
|
shaderCache->GetBuiltinModule(BuiltinShader::TrisizeFS), "main", NULL},
|
|
{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, NULL, 0, VK_SHADER_STAGE_GEOMETRY_BIT,
|
|
shaderCache->GetBuiltinModule(BuiltinShader::TrisizeGS), "main", NULL},
|
|
};
|
|
|
|
if(stages[0].module != VK_NULL_HANDLE && stages[1].module != VK_NULL_HANDLE &&
|
|
stages[2].module != VK_NULL_HANDLE)
|
|
{
|
|
SCOPED_TIMER("Triangle Size");
|
|
|
|
float black[] = {0.0f, 0.0f, 0.0f, 0.0f};
|
|
|
|
VkImageMemoryBarrier barrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
|
NULL,
|
|
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
|
VK_ACCESS_TRANSFER_WRITE_BIT,
|
|
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
Unwrap(m_Overlay.Image),
|
|
subRange};
|
|
|
|
DoPipelineBarrier(cmd, 1, &barrier);
|
|
|
|
vt->CmdClearColorImage(Unwrap(cmd), Unwrap(m_Overlay.Image),
|
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, (VkClearColorValue *)black, 1,
|
|
&subRange);
|
|
|
|
std::swap(barrier.oldLayout, barrier.newLayout);
|
|
std::swap(barrier.srcAccessMask, barrier.dstAccessMask);
|
|
barrier.dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT;
|
|
|
|
DoPipelineBarrier(cmd, 1, &barrier);
|
|
|
|
// end this cmd buffer so the image is in the right state for the next part
|
|
vkr = vt->EndCommandBuffer(Unwrap(cmd));
|
|
CheckVkResult(vkr);
|
|
|
|
#if ENABLED(SINGLE_FLUSH_VALIDATE)
|
|
m_pDriver->SubmitCmds();
|
|
#endif
|
|
|
|
rdcarray<uint32_t> events = passEvents;
|
|
|
|
if(overlay == DebugOverlay::TriangleSizeDraw)
|
|
events.clear();
|
|
|
|
while(!events.empty())
|
|
{
|
|
const ActionDescription *action = m_pDriver->GetAction(events[0]);
|
|
|
|
// remove any non-drawcalls, like the pass boundary.
|
|
if(!action || !(action->flags & ActionFlags::Drawcall))
|
|
events.erase(0);
|
|
else
|
|
break;
|
|
}
|
|
|
|
events.push_back(eventId);
|
|
|
|
m_pDriver->ReplayLog(0, events[0], eReplay_WithoutDraw);
|
|
|
|
uint32_t meshOffs = 0;
|
|
MeshUBOData *data = (MeshUBOData *)m_MeshRender.UBO.Map(&meshOffs);
|
|
if(!data)
|
|
return ResourceId();
|
|
|
|
data->mvp = Matrix4f::Identity();
|
|
data->invProj = Matrix4f::Identity();
|
|
data->color = Vec4f();
|
|
data->homogenousInput = 1;
|
|
data->pointSpriteSize = Vec2f(0.0f, 0.0f);
|
|
data->displayFormat = 0;
|
|
data->rawoutput = 1;
|
|
data->flipY = 0;
|
|
data->padding = Vec2f();
|
|
m_MeshRender.UBO.Unmap();
|
|
|
|
uint32_t viewOffs = 0;
|
|
Vec4f *ubo = (Vec4f *)m_Overlay.m_TriSizeUBO.Map(&viewOffs);
|
|
if(!ubo)
|
|
return ResourceId();
|
|
*ubo = Vec4f(state.views[0].width, state.views[0].height);
|
|
m_Overlay.m_TriSizeUBO.Unmap();
|
|
|
|
uint32_t offsets[2] = {meshOffs, viewOffs};
|
|
|
|
VkDescriptorBufferInfo bufdesc;
|
|
m_MeshRender.UBO.FillDescriptor(bufdesc);
|
|
|
|
VkWriteDescriptorSet write = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
|
NULL,
|
|
Unwrap(m_Overlay.m_TriSizeDescSet),
|
|
0,
|
|
0,
|
|
1,
|
|
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,
|
|
NULL,
|
|
&bufdesc,
|
|
NULL};
|
|
vt->UpdateDescriptorSets(Unwrap(m_Device), 1, &write, 0, NULL);
|
|
|
|
m_Overlay.m_TriSizeUBO.FillDescriptor(bufdesc);
|
|
write.dstBinding = 2;
|
|
vt->UpdateDescriptorSets(Unwrap(m_Device), 1, &write, 0, NULL);
|
|
|
|
VkRenderPass RP = m_Overlay.NoDepthRP;
|
|
VkFramebuffer FB = m_Overlay.NoDepthFB;
|
|
|
|
VulkanCreationInfo &createinfo = m_pDriver->m_CreationInfo;
|
|
|
|
ResourceId depthStencilView;
|
|
|
|
if(state.dynamicRendering.active)
|
|
{
|
|
depthStencilView = GetResID(state.dynamicRendering.depth.imageView);
|
|
if(depthStencilView == ResourceId())
|
|
depthStencilView = GetResID(state.dynamicRendering.stencil.imageView);
|
|
}
|
|
else
|
|
{
|
|
RDCASSERT(state.subpass < createinfo.m_RenderPass[state.GetRenderPass()].subpasses.size());
|
|
int32_t dsIdx =
|
|
createinfo.m_RenderPass[state.GetRenderPass()].subpasses[state.subpass].depthstencilAttachment;
|
|
|
|
// make a renderpass and framebuffer for rendering to overlay color and using
|
|
// depth buffer from the orignial render
|
|
if(dsIdx >= 0 &&
|
|
dsIdx < (int32_t)createinfo.m_Framebuffer[state.GetFramebuffer()].attachments.size())
|
|
{
|
|
depthStencilView = state.GetFramebufferAttachments()[dsIdx];
|
|
}
|
|
}
|
|
|
|
if(depthStencilView != ResourceId())
|
|
{
|
|
VkAttachmentDescription attDescs[] = {
|
|
{0, overlayFormat, VK_SAMPLE_COUNT_1_BIT, VK_ATTACHMENT_LOAD_OP_LOAD,
|
|
VK_ATTACHMENT_STORE_OP_STORE, VK_ATTACHMENT_LOAD_OP_DONT_CARE,
|
|
VK_ATTACHMENT_STORE_OP_DONT_CARE, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
|
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL},
|
|
{0, VK_FORMAT_UNDEFINED, VK_SAMPLE_COUNT_1_BIT, // will patch this just below
|
|
VK_ATTACHMENT_LOAD_OP_LOAD, VK_ATTACHMENT_STORE_OP_STORE, VK_ATTACHMENT_LOAD_OP_LOAD,
|
|
VK_ATTACHMENT_STORE_OP_STORE, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
|
|
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL},
|
|
};
|
|
|
|
VulkanCreationInfo::ImageView &depthViewInfo = createinfo.m_ImageView[depthStencilView];
|
|
|
|
ResourceId depthIm = depthViewInfo.image;
|
|
VulkanCreationInfo::Image &depthImageInfo = createinfo.m_Image[depthIm];
|
|
|
|
attDescs[1].format = depthImageInfo.format;
|
|
attDescs[0].samples = attDescs[1].samples = iminfo.samples;
|
|
|
|
{
|
|
LockedConstImageStateRef imState = m_pDriver->FindConstImageState(depthIm);
|
|
if(imState)
|
|
{
|
|
// find the state that overlaps the view's subresource range start. We assume all
|
|
// subresources are correctly in the same state (as they should be) so we just need to
|
|
// find the first match.
|
|
auto it = imState->subresourceStates.RangeBegin(depthViewInfo.range);
|
|
if(it != imState->subresourceStates.end())
|
|
attDescs[1].initialLayout = attDescs[1].finalLayout = it->state().newLayout;
|
|
}
|
|
}
|
|
|
|
VkAttachmentReference colRef = {0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
|
|
VkAttachmentReference dsRef = {1, attDescs[1].initialLayout};
|
|
|
|
VkSubpassDescription subp = {
|
|
0, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
|
0, NULL, // inputs
|
|
1, &colRef, // color
|
|
NULL, // resolve
|
|
&dsRef, // depth-stencil
|
|
0, NULL, // preserve
|
|
};
|
|
|
|
VkRenderPassCreateInfo rpinfo = {
|
|
VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
|
|
NULL,
|
|
0,
|
|
2,
|
|
attDescs,
|
|
1,
|
|
&subp,
|
|
0,
|
|
NULL, // dependencies
|
|
};
|
|
|
|
if(multiviewMask > 0)
|
|
rpinfo.pNext = &multiviewRP;
|
|
|
|
vkr = m_pDriver->vkCreateRenderPass(m_Device, &rpinfo, NULL, &RP);
|
|
CheckVkResult(vkr);
|
|
|
|
VkImageView views[] = {
|
|
m_Overlay.ImageView,
|
|
m_pDriver->GetResourceManager()->GetCurrentHandle<VkImageView>(depthStencilView),
|
|
};
|
|
|
|
// Create framebuffer rendering just to overlay image, no depth
|
|
VkFramebufferCreateInfo fbinfo = {
|
|
VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
|
|
NULL,
|
|
0,
|
|
RP,
|
|
2,
|
|
views,
|
|
RDCMAX(1U, m_Overlay.ImageDim.width >> sub.mip),
|
|
RDCMAX(1U, m_Overlay.ImageDim.height >> sub.mip),
|
|
sub.numSlices,
|
|
};
|
|
|
|
vkr = m_pDriver->vkCreateFramebuffer(m_Device, &fbinfo, NULL, &FB);
|
|
CheckVkResult(vkr);
|
|
}
|
|
|
|
VkGraphicsPipelineCreateInfo pipeCreateInfo;
|
|
|
|
m_pDriver->GetShaderCache()->MakeGraphicsPipelineInfo(pipeCreateInfo,
|
|
state.graphics.pipeline);
|
|
|
|
VkPipelineInputAssemblyStateCreateInfo ia = {
|
|
VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO};
|
|
|
|
// ia.topology will be set below on a per-draw basis
|
|
|
|
VkVertexInputBindingDescription binds[] = {
|
|
// primary
|
|
{0, 0, VK_VERTEX_INPUT_RATE_VERTEX},
|
|
// secondary
|
|
{1, 0, VK_VERTEX_INPUT_RATE_VERTEX},
|
|
};
|
|
|
|
VkVertexInputAttributeDescription vertAttrs[] = {
|
|
{0, 0, VK_FORMAT_R32G32B32A32_SFLOAT, 0}, {1, 0, VK_FORMAT_R32G32B32A32_SFLOAT, 0},
|
|
};
|
|
|
|
VkPipelineVertexInputStateCreateInfo vi = {
|
|
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
|
|
NULL,
|
|
0,
|
|
1,
|
|
binds,
|
|
2,
|
|
vertAttrs,
|
|
};
|
|
|
|
VkPipelineColorBlendAttachmentState attState = {
|
|
false,
|
|
VK_BLEND_FACTOR_ONE,
|
|
VK_BLEND_FACTOR_ZERO,
|
|
VK_BLEND_OP_ADD,
|
|
VK_BLEND_FACTOR_ONE,
|
|
VK_BLEND_FACTOR_ZERO,
|
|
VK_BLEND_OP_ADD,
|
|
0xf,
|
|
};
|
|
|
|
VkPipelineColorBlendStateCreateInfo cb = {
|
|
VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
|
|
NULL,
|
|
0,
|
|
false,
|
|
VK_LOGIC_OP_NO_OP,
|
|
1,
|
|
&attState,
|
|
{1.0f, 1.0f, 1.0f, 1.0f},
|
|
};
|
|
|
|
pipeCreateInfo.stageCount = 3;
|
|
pipeCreateInfo.pStages = stages;
|
|
pipeCreateInfo.pTessellationState = NULL;
|
|
pipeCreateInfo.renderPass = RP;
|
|
pipeCreateInfo.subpass = 0;
|
|
pipeCreateInfo.layout = m_Overlay.m_TriSizePipeLayout;
|
|
pipeCreateInfo.basePipelineHandle = VK_NULL_HANDLE;
|
|
pipeCreateInfo.basePipelineIndex = 0;
|
|
pipeCreateInfo.pInputAssemblyState = &ia;
|
|
pipeCreateInfo.pVertexInputState = &vi;
|
|
pipeCreateInfo.pColorBlendState = &cb;
|
|
|
|
uint32_t &dynamicStateCount = (uint32_t &)pipeCreateInfo.pDynamicState->dynamicStateCount;
|
|
VkDynamicState *dynamicStateList =
|
|
(VkDynamicState *)pipeCreateInfo.pDynamicState->pDynamicStates;
|
|
|
|
// remove any dynamic states we don't want
|
|
for(uint32_t i = 0; i < dynamicStateCount;)
|
|
{
|
|
// we are controlling the vertex binding so we don't need the stride or input to be
|
|
// dynamic.
|
|
// Similarly we're controlling the topology so that doesn't need to be dynamic
|
|
if(dynamicStateList[i] == VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE ||
|
|
dynamicStateList[i] == VK_DYNAMIC_STATE_VERTEX_INPUT_EXT ||
|
|
dynamicStateList[i] == VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY)
|
|
{
|
|
// swap with the last item if this isn't the last one
|
|
if(i != dynamicStateCount - 1)
|
|
std::swap(dynamicStateList[i], dynamicStateList[dynamicStateCount - 1]);
|
|
|
|
// then pop the last item.
|
|
dynamicStateCount--;
|
|
|
|
// process this item again. If we swapped we'll then consider that dynamic state, and if
|
|
// we didn't then this was the last item and i will be past dynamicStateCount now
|
|
continue;
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
typedef rdcpair<uint32_t, Topology> PipeKey;
|
|
|
|
std::map<PipeKey, VkPipeline> pipes;
|
|
|
|
for(size_t i = 0; i < events.size(); i++)
|
|
{
|
|
cmd = m_pDriver->GetNextCmd();
|
|
|
|
if(cmd == VK_NULL_HANDLE)
|
|
return ResourceId();
|
|
|
|
vkr = vt->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
|
|
CheckVkResult(vkr);
|
|
|
|
VkClearValue clearval = {};
|
|
VkRenderPassBeginInfo rpbegin = {
|
|
VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
|
NULL,
|
|
Unwrap(RP),
|
|
Unwrap(FB),
|
|
{
|
|
{0, 0},
|
|
{
|
|
RDCMAX(1U, m_Overlay.ImageDim.width >> sub.mip),
|
|
RDCMAX(1U, m_Overlay.ImageDim.height >> sub.mip),
|
|
},
|
|
},
|
|
1,
|
|
&clearval,
|
|
};
|
|
vt->CmdBeginRenderPass(Unwrap(cmd), &rpbegin, VK_SUBPASS_CONTENTS_INLINE);
|
|
|
|
const ActionDescription *action = m_pDriver->GetAction(events[i]);
|
|
|
|
for(uint32_t inst = 0; action && inst < RDCMAX(1U, action->numInstances); inst++)
|
|
{
|
|
MeshFormat fmt = GetPostVSBuffers(events[i], inst, 0, MeshDataStage::GSOut);
|
|
if(fmt.vertexResourceId == ResourceId())
|
|
fmt = GetPostVSBuffers(events[i], inst, 0, MeshDataStage::VSOut);
|
|
|
|
if(fmt.vertexResourceId != ResourceId())
|
|
{
|
|
ia.topology = MakeVkPrimitiveTopology(fmt.topology);
|
|
|
|
binds[0].stride = binds[1].stride = fmt.vertexByteStride;
|
|
|
|
PipeKey key = make_rdcpair(fmt.vertexByteStride, fmt.topology);
|
|
VkPipeline pipe = pipes[key];
|
|
|
|
if(pipe == VK_NULL_HANDLE)
|
|
{
|
|
vkr = m_pDriver->vkCreateGraphicsPipelines(m_Device, VK_NULL_HANDLE, 1,
|
|
&pipeCreateInfo, NULL, &pipe);
|
|
CheckVkResult(vkr);
|
|
}
|
|
|
|
VkBuffer vb =
|
|
m_pDriver->GetResourceManager()->GetCurrentHandle<VkBuffer>(fmt.vertexResourceId);
|
|
|
|
VkDeviceSize offs = fmt.vertexByteOffset;
|
|
vt->CmdBindVertexBuffers(Unwrap(cmd), 0, 1, UnwrapPtr(vb), &offs);
|
|
|
|
pipes[key] = pipe;
|
|
|
|
vt->CmdBindDescriptorSets(Unwrap(cmd), VK_PIPELINE_BIND_POINT_GRAPHICS,
|
|
Unwrap(m_Overlay.m_TriSizePipeLayout), 0, 1,
|
|
UnwrapPtr(m_Overlay.m_TriSizeDescSet), 2, offsets);
|
|
|
|
vt->CmdBindPipeline(Unwrap(cmd), VK_PIPELINE_BIND_POINT_GRAPHICS, Unwrap(pipe));
|
|
|
|
const VkPipelineDynamicStateCreateInfo *dyn = pipeCreateInfo.pDynamicState;
|
|
|
|
for(uint32_t dynState = 0; dyn && dynState < dyn->dynamicStateCount; dynState++)
|
|
{
|
|
VkDynamicState d = dyn->pDynamicStates[dynState];
|
|
|
|
if(!state.views.empty() && d == VK_DYNAMIC_STATE_VIEWPORT)
|
|
{
|
|
vt->CmdSetViewport(Unwrap(cmd), 0, (uint32_t)state.views.size(), &state.views[0]);
|
|
}
|
|
else if(!state.scissors.empty() && d == VK_DYNAMIC_STATE_SCISSOR)
|
|
{
|
|
vt->CmdSetScissor(Unwrap(cmd), 0, (uint32_t)state.scissors.size(),
|
|
&state.scissors[0]);
|
|
}
|
|
else if(d == VK_DYNAMIC_STATE_LINE_WIDTH)
|
|
{
|
|
vt->CmdSetLineWidth(Unwrap(cmd), state.lineWidth);
|
|
}
|
|
else if(d == VK_DYNAMIC_STATE_DEPTH_BIAS)
|
|
{
|
|
vt->CmdSetDepthBias(Unwrap(cmd), state.bias.depth, state.bias.biasclamp,
|
|
state.bias.slope);
|
|
}
|
|
else if(d == VK_DYNAMIC_STATE_BLEND_CONSTANTS)
|
|
{
|
|
vt->CmdSetBlendConstants(Unwrap(cmd), state.blendConst);
|
|
}
|
|
else if(d == VK_DYNAMIC_STATE_DEPTH_BOUNDS)
|
|
{
|
|
vt->CmdSetDepthBounds(Unwrap(cmd), state.mindepth, state.maxdepth);
|
|
}
|
|
else if(d == VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK)
|
|
{
|
|
vt->CmdSetStencilCompareMask(Unwrap(cmd), VK_STENCIL_FACE_BACK_BIT,
|
|
state.back.compare);
|
|
vt->CmdSetStencilCompareMask(Unwrap(cmd), VK_STENCIL_FACE_FRONT_BIT,
|
|
state.front.compare);
|
|
}
|
|
else if(d == VK_DYNAMIC_STATE_STENCIL_WRITE_MASK)
|
|
{
|
|
vt->CmdSetStencilWriteMask(Unwrap(cmd), VK_STENCIL_FACE_BACK_BIT, state.back.write);
|
|
vt->CmdSetStencilWriteMask(Unwrap(cmd), VK_STENCIL_FACE_FRONT_BIT,
|
|
state.front.write);
|
|
}
|
|
else if(d == VK_DYNAMIC_STATE_STENCIL_REFERENCE)
|
|
{
|
|
vt->CmdSetStencilReference(Unwrap(cmd), VK_STENCIL_FACE_BACK_BIT, state.back.ref);
|
|
vt->CmdSetStencilReference(Unwrap(cmd), VK_STENCIL_FACE_FRONT_BIT, state.front.ref);
|
|
}
|
|
else if(d == VK_DYNAMIC_STATE_VIEWPORT_WITH_COUNT)
|
|
{
|
|
vt->CmdSetViewportWithCountEXT(Unwrap(cmd), (uint32_t)state.views.size(),
|
|
state.views.data());
|
|
}
|
|
else if(d == VK_DYNAMIC_STATE_SCISSOR_WITH_COUNT)
|
|
{
|
|
vt->CmdSetScissorWithCountEXT(Unwrap(cmd), (uint32_t)state.scissors.size(),
|
|
state.scissors.data());
|
|
}
|
|
else if(d == VK_DYNAMIC_STATE_CULL_MODE)
|
|
{
|
|
vt->CmdSetCullModeEXT(Unwrap(cmd), state.cullMode);
|
|
}
|
|
else if(d == VK_DYNAMIC_STATE_FRONT_FACE)
|
|
{
|
|
vt->CmdSetFrontFaceEXT(Unwrap(cmd), state.frontFace);
|
|
}
|
|
else if(d == VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY)
|
|
{
|
|
RDCERR("Primitive topology dynamic state found, should have been stripped");
|
|
}
|
|
else if(d == VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE)
|
|
{
|
|
RDCERR(
|
|
"Vertex input binding stride dynamic state found, should have been stripped");
|
|
}
|
|
else if(d == VK_DYNAMIC_STATE_DEPTH_TEST_ENABLE)
|
|
{
|
|
vt->CmdSetDepthTestEnableEXT(Unwrap(cmd), state.depthTestEnable);
|
|
}
|
|
else if(d == VK_DYNAMIC_STATE_DEPTH_WRITE_ENABLE)
|
|
{
|
|
vt->CmdSetDepthWriteEnableEXT(Unwrap(cmd), state.depthWriteEnable);
|
|
}
|
|
else if(d == VK_DYNAMIC_STATE_DEPTH_COMPARE_OP)
|
|
{
|
|
vt->CmdSetDepthCompareOpEXT(Unwrap(cmd), state.depthCompareOp);
|
|
}
|
|
else if(d == VK_DYNAMIC_STATE_DEPTH_BOUNDS_TEST_ENABLE)
|
|
{
|
|
vt->CmdSetDepthBoundsTestEnableEXT(Unwrap(cmd), state.depthBoundsTestEnable);
|
|
}
|
|
else if(d == VK_DYNAMIC_STATE_STENCIL_TEST_ENABLE)
|
|
{
|
|
vt->CmdSetStencilTestEnableEXT(Unwrap(cmd), state.stencilTestEnable);
|
|
}
|
|
else if(d == VK_DYNAMIC_STATE_STENCIL_OP)
|
|
{
|
|
vt->CmdSetStencilOpEXT(Unwrap(cmd), VK_STENCIL_FACE_FRONT_BIT, state.front.failOp,
|
|
state.front.passOp, state.front.depthFailOp,
|
|
state.front.compareOp);
|
|
vt->CmdSetStencilOpEXT(Unwrap(cmd), VK_STENCIL_FACE_BACK_BIT, state.front.failOp,
|
|
state.front.passOp, state.front.depthFailOp,
|
|
state.front.compareOp);
|
|
}
|
|
else if(d == VK_DYNAMIC_STATE_COLOR_WRITE_ENABLE_EXT)
|
|
{
|
|
vt->CmdSetColorWriteEnableEXT(Unwrap(cmd), (uint32_t)state.colorWriteEnable.size(),
|
|
state.colorWriteEnable.data());
|
|
}
|
|
else if(d == VK_DYNAMIC_STATE_DEPTH_BIAS_ENABLE)
|
|
{
|
|
vt->CmdSetDepthBiasEnableEXT(Unwrap(cmd), state.depthBiasEnable);
|
|
}
|
|
else if(d == VK_DYNAMIC_STATE_LOGIC_OP_EXT)
|
|
{
|
|
vt->CmdSetLogicOpEXT(Unwrap(cmd), state.logicOp);
|
|
}
|
|
else if(d == VK_DYNAMIC_STATE_PATCH_CONTROL_POINTS_EXT)
|
|
{
|
|
vt->CmdSetPatchControlPointsEXT(Unwrap(cmd), state.patchControlPoints);
|
|
}
|
|
else if(d == VK_DYNAMIC_STATE_PRIMITIVE_RESTART_ENABLE)
|
|
{
|
|
vt->CmdSetPrimitiveRestartEnableEXT(Unwrap(cmd), state.primRestartEnable);
|
|
}
|
|
else if(d == VK_DYNAMIC_STATE_RASTERIZER_DISCARD_ENABLE)
|
|
{
|
|
vt->CmdSetRasterizerDiscardEnableEXT(Unwrap(cmd), state.rastDiscardEnable);
|
|
}
|
|
else if(d == VK_DYNAMIC_STATE_VERTEX_INPUT_EXT)
|
|
{
|
|
RDCERR("Vertex input dynamic state found, should have been stripped");
|
|
}
|
|
}
|
|
|
|
if(fmt.indexByteStride)
|
|
{
|
|
VkIndexType idxtype = VK_INDEX_TYPE_UINT16;
|
|
if(fmt.indexByteStride == 4)
|
|
idxtype = VK_INDEX_TYPE_UINT32;
|
|
else if(fmt.indexByteStride == 1)
|
|
idxtype = VK_INDEX_TYPE_UINT8_EXT;
|
|
|
|
if(fmt.indexResourceId != ResourceId())
|
|
{
|
|
VkBuffer ib =
|
|
m_pDriver->GetResourceManager()->GetLiveHandle<VkBuffer>(fmt.indexResourceId);
|
|
|
|
vt->CmdBindIndexBuffer(Unwrap(cmd), Unwrap(ib), fmt.indexByteOffset, idxtype);
|
|
vt->CmdDrawIndexed(Unwrap(cmd), fmt.numIndices, 1, 0, fmt.baseVertex, 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
vt->CmdDraw(Unwrap(cmd), fmt.numIndices, 1, 0, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
vt->CmdEndRenderPass(Unwrap(cmd));
|
|
|
|
vkr = vt->EndCommandBuffer(Unwrap(cmd));
|
|
CheckVkResult(vkr);
|
|
|
|
if(overlay == DebugOverlay::TriangleSizePass)
|
|
{
|
|
m_pDriver->ReplayLog(events[i], events[i], eReplay_OnlyDraw);
|
|
|
|
if(i + 1 < events.size())
|
|
m_pDriver->ReplayLog(events[i], events[i + 1], eReplay_WithoutDraw);
|
|
}
|
|
}
|
|
|
|
m_pDriver->SubmitCmds();
|
|
m_pDriver->FlushQ();
|
|
|
|
if(depthStencilView != ResourceId())
|
|
{
|
|
m_pDriver->vkDestroyFramebuffer(m_Device, FB, NULL);
|
|
m_pDriver->vkDestroyRenderPass(m_Device, RP, NULL);
|
|
}
|
|
|
|
for(auto it = pipes.begin(); it != pipes.end(); ++it)
|
|
m_pDriver->vkDestroyPipeline(m_Device, it->second, NULL);
|
|
}
|
|
|
|
// restore back to normal
|
|
m_pDriver->ReplayLog(0, eventId, eReplay_WithoutDraw);
|
|
|
|
// restore state
|
|
state = prevstate;
|
|
|
|
cmd = m_pDriver->GetNextCmd();
|
|
|
|
if(cmd == VK_NULL_HANDLE)
|
|
return ResourceId();
|
|
|
|
vkr = vt->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
|
|
CheckVkResult(vkr);
|
|
}
|
|
}
|
|
|
|
VkMarkerRegion::End(cmd);
|
|
|
|
vkr = vt->EndCommandBuffer(Unwrap(cmd));
|
|
CheckVkResult(vkr);
|
|
|
|
#if ENABLED(SINGLE_FLUSH_VALIDATE)
|
|
m_pDriver->SubmitCmds();
|
|
#endif
|
|
|
|
return GetResID(m_Overlay.Image);
|
|
}
|