mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-08-07 15:20:58 +00:00
Also: - support for choosing mip level and slice - tests for secondary command buffers - adds a callback around vkCmdExecuteCommands - refactors pixel history occlusion callback into its own callback. Allows processing fewer events later on, and getting colour information separately. - keep track of subpassContents for vkCmdBeginRenderPass (inline or secondary)
2991 lines
116 KiB
C++
2991 lines
116 KiB
C++
/******************************************************************************
|
|
* The MIT License (MIT)
|
|
*
|
|
* Copyright (c) 2019-2020 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 "driver/shaders/spirv/spirv_editor.h"
|
|
#include "driver/shaders/spirv/spirv_op_helpers.h"
|
|
#include "maths/formatpacking.h"
|
|
#include "vk_debug.h"
|
|
#include "vk_replay.h"
|
|
#include "vk_shader_cache.h"
|
|
|
|
bool isDirectWrite(ResourceUsage usage)
|
|
{
|
|
return ((usage >= ResourceUsage::VS_RWResource && usage <= ResourceUsage::CS_RWResource) ||
|
|
usage == ResourceUsage::CopyDst || usage == ResourceUsage::Copy ||
|
|
usage == ResourceUsage::Resolve || usage == ResourceUsage::ResolveDst ||
|
|
usage == ResourceUsage::GenMips);
|
|
}
|
|
|
|
enum
|
|
{
|
|
TestEnabled_Culling = 1 << 0,
|
|
TestEnabled_Scissor = 1 << 1,
|
|
TestEnabled_SampleMask = 1 << 2,
|
|
TestEnabled_DepthBounds = 1 << 3,
|
|
TestEnabled_StencilTesting = 1 << 4,
|
|
TestEnabled_DepthTesting = 1 << 5,
|
|
TestEnabled_FragmentDiscard = 1 << 6,
|
|
|
|
Blending_Enabled = 1 << 7,
|
|
TestMustFail_Culling = 1 << 8,
|
|
TestMustFail_Scissor = 1 << 9,
|
|
TestMustPass_Scissor = 1 << 10,
|
|
TestMustFail_DepthTesting = 1 << 11,
|
|
TestMustFail_StencilTesting = 1 << 12,
|
|
TestMustFail_SampleMask = 1 << 13,
|
|
};
|
|
|
|
struct CopyPixelParams
|
|
{
|
|
bool multisampled;
|
|
bool floatTex;
|
|
bool uintTex;
|
|
bool intTex;
|
|
|
|
bool depthCopy;
|
|
bool stencilOnly;
|
|
VkImage srcImage;
|
|
VkFormat srcImageFormat;
|
|
VkImageLayout srcImageLayout;
|
|
VkOffset3D imageOffset;
|
|
uint32_t slice;
|
|
uint32_t mipLevel;
|
|
|
|
VkBuffer dstBuffer;
|
|
};
|
|
|
|
struct PixelHistoryResources
|
|
{
|
|
VkBuffer dstBuffer;
|
|
VkDeviceMemory bufferMemory;
|
|
|
|
// Used for offscreen rendering for draw call events.
|
|
VkImage colorImage;
|
|
VkImageView colorImageView;
|
|
VkImage stencilImage;
|
|
VkImageView stencilImageView;
|
|
VkDeviceMemory gpuMem;
|
|
};
|
|
|
|
struct PixelHistoryValue
|
|
{
|
|
// Max size is 4 component with 8 byte component width
|
|
uint8_t color[32];
|
|
union
|
|
{
|
|
uint32_t udepth;
|
|
float fdepth;
|
|
} depth;
|
|
int8_t stencil;
|
|
uint8_t padding[3];
|
|
};
|
|
|
|
struct EventInfo
|
|
{
|
|
PixelHistoryValue premod;
|
|
PixelHistoryValue postmod;
|
|
uint8_t dsWithoutShaderDiscard[8];
|
|
uint8_t dsWithShaderDiscard[8];
|
|
};
|
|
|
|
struct PerFragmentInfo
|
|
{
|
|
// primitive ID is copied from a R32G32B32A32 texture.
|
|
int32_t primitiveID;
|
|
uint32_t padding[3];
|
|
PixelHistoryValue shaderOut;
|
|
PixelHistoryValue postMod;
|
|
};
|
|
|
|
struct PipelineReplacements
|
|
{
|
|
VkPipeline fixedShaderStencil;
|
|
VkPipeline originalShaderStencil;
|
|
};
|
|
|
|
// PixelHistoryShaderCache manages temporary shaders created for pixel history.
|
|
struct PixelHistoryShaderCache
|
|
{
|
|
PixelHistoryShaderCache(WrappedVulkan *vk) : m_pDriver(vk) {}
|
|
~PixelHistoryShaderCache()
|
|
{
|
|
for(auto it = m_ShaderReplacements.begin(); it != m_ShaderReplacements.end(); ++it)
|
|
{
|
|
if(it->second != VK_NULL_HANDLE)
|
|
m_pDriver->vkDestroyShaderModule(m_pDriver->GetDev(), it->second, NULL);
|
|
}
|
|
for(auto it = m_FixedColFS.begin(); it != m_FixedColFS.end(); it++)
|
|
m_pDriver->vkDestroyShaderModule(m_pDriver->GetDev(), it->second, NULL);
|
|
for(auto it = m_PrimIDFS.begin(); it != m_PrimIDFS.end(); it++)
|
|
m_pDriver->vkDestroyShaderModule(m_pDriver->GetDev(), it->second, NULL);
|
|
}
|
|
|
|
// Returns a fragment shader module that outputs a fixed color to the given
|
|
// color attachment.
|
|
VkShaderModule GetFixedColShader(uint32_t framebufferIndex)
|
|
{
|
|
auto it = m_FixedColFS.find(framebufferIndex);
|
|
if(it != m_FixedColFS.end())
|
|
return it->second;
|
|
VkShaderModule sh;
|
|
m_pDriver->GetDebugManager()->PatchOutputLocation(sh, BuiltinShader::FixedColFS,
|
|
framebufferIndex);
|
|
m_FixedColFS.insert(std::make_pair(framebufferIndex, sh));
|
|
return sh;
|
|
}
|
|
|
|
// Returns a fragment shader module that outputs primitive ID to the given
|
|
// color attachment.
|
|
VkShaderModule GetPrimitiveIdShader(uint32_t framebufferIndex)
|
|
{
|
|
auto it = m_PrimIDFS.find(framebufferIndex);
|
|
if(it != m_PrimIDFS.end())
|
|
return it->second;
|
|
VkShaderModule sh;
|
|
m_pDriver->GetDebugManager()->PatchOutputLocation(sh, BuiltinShader::PixelHistoryPrimIDFS,
|
|
framebufferIndex);
|
|
m_PrimIDFS.insert(std::make_pair(framebufferIndex, sh));
|
|
return sh;
|
|
}
|
|
|
|
// Returns a shader that is equivalent to the given shader, but attempts to remove
|
|
// side effects of shader execution for the given entry point (for ex., writes
|
|
// to storage buffers/images).
|
|
VkShaderModule GetShaderWithoutSideEffects(ResourceId shaderId, const rdcstr &entryPoint)
|
|
{
|
|
ShaderKey shaderKey = make_rdcpair(shaderId, entryPoint);
|
|
auto it = m_ShaderReplacements.find(shaderKey);
|
|
// Check if we processed this shader before.
|
|
if(it != m_ShaderReplacements.end())
|
|
return it->second;
|
|
|
|
VkShaderModule shaderModule = CreateShaderReplacement(shaderId, entryPoint);
|
|
m_ShaderReplacements.insert(std::make_pair(shaderKey, shaderModule));
|
|
return shaderModule;
|
|
}
|
|
|
|
private:
|
|
VkShaderModule CreateShaderReplacement(ResourceId shaderId, const rdcstr &entryName)
|
|
{
|
|
const VulkanCreationInfo::ShaderModule &moduleInfo =
|
|
m_pDriver->GetDebugManager()->GetShaderInfo(shaderId);
|
|
rdcarray<uint32_t> modSpirv = moduleInfo.spirv.GetSPIRV();
|
|
rdcspv::Editor editor(modSpirv);
|
|
editor.Prepare();
|
|
|
|
for(const rdcspv::EntryPoint &entry : editor.GetEntries())
|
|
{
|
|
if(entry.name == entryName)
|
|
{
|
|
// In some cases a shader might just be binding a RW resource but not writing to it.
|
|
// If there are no writes (shader was not modified), no need to replace the shader,
|
|
// just insert VK_NULL_HANDLE to indicate that this shader has been processed.
|
|
VkShaderModule module = VK_NULL_HANDLE;
|
|
bool modified = StripShaderSideEffects(editor, entry.id);
|
|
if(modified)
|
|
{
|
|
VkShaderModuleCreateInfo moduleCreateInfo = {};
|
|
moduleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
|
|
moduleCreateInfo.pCode = modSpirv.data();
|
|
moduleCreateInfo.codeSize = modSpirv.byteSize();
|
|
VkResult vkr =
|
|
m_pDriver->vkCreateShaderModule(m_pDriver->GetDev(), &moduleCreateInfo, NULL, &module);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
}
|
|
return module;
|
|
}
|
|
}
|
|
RDCERR("Entry point %s not found", entryName.c_str());
|
|
return VK_NULL_HANDLE;
|
|
}
|
|
|
|
// Removes instructions from the shader that would produce side effects (writing
|
|
// to storage buffers, or images). Returns true if the shader was modified, and
|
|
// false if there were no instructions to remove.
|
|
bool StripShaderSideEffects(rdcspv::Editor &editor, const rdcspv::Id &entryId)
|
|
{
|
|
bool modified = false;
|
|
|
|
std::set<rdcspv::Id> patchedFunctions;
|
|
std::set<rdcspv::Id> functionPatchQueue;
|
|
functionPatchQueue.insert(entryId);
|
|
|
|
while(!functionPatchQueue.empty())
|
|
{
|
|
rdcspv::Id funcId;
|
|
{
|
|
auto it = functionPatchQueue.begin();
|
|
funcId = *functionPatchQueue.begin();
|
|
functionPatchQueue.erase(it);
|
|
patchedFunctions.insert(funcId);
|
|
}
|
|
|
|
rdcspv::Iter it = editor.GetID(funcId);
|
|
RDCASSERT(it.opcode() == rdcspv::Op::Function);
|
|
|
|
it++;
|
|
|
|
for(; it; ++it)
|
|
{
|
|
rdcspv::Op opcode = it.opcode();
|
|
if(opcode == rdcspv::Op::FunctionEnd)
|
|
break;
|
|
|
|
switch(opcode)
|
|
{
|
|
case rdcspv::Op::FunctionCall:
|
|
{
|
|
rdcspv::OpFunctionCall call(it);
|
|
if(functionPatchQueue.find(call.function) == functionPatchQueue.end() &&
|
|
patchedFunctions.find(call.function) == patchedFunctions.end())
|
|
functionPatchQueue.insert(call.function);
|
|
break;
|
|
}
|
|
case rdcspv::Op::CopyMemory:
|
|
case rdcspv::Op::AtomicStore:
|
|
case rdcspv::Op::Store:
|
|
{
|
|
rdcspv::Id pointer = rdcspv::Id::fromWord(it.word(1));
|
|
rdcspv::Id pointerType = editor.GetIDType(pointer);
|
|
RDCASSERT(pointerType != rdcspv::Id());
|
|
rdcspv::Iter pointerTypeIt = editor.GetID(pointerType);
|
|
rdcspv::OpTypePointer ptr(pointerTypeIt);
|
|
if(ptr.storageClass == rdcspv::StorageClass::Uniform ||
|
|
ptr.storageClass == rdcspv::StorageClass::StorageBuffer)
|
|
{
|
|
editor.Remove(it);
|
|
modified = true;
|
|
}
|
|
break;
|
|
}
|
|
case rdcspv::Op::ImageWrite:
|
|
{
|
|
editor.Remove(it);
|
|
modified = true;
|
|
break;
|
|
}
|
|
case rdcspv::Op::AtomicExchange:
|
|
case rdcspv::Op::AtomicCompareExchange:
|
|
case rdcspv::Op::AtomicCompareExchangeWeak:
|
|
case rdcspv::Op::AtomicIIncrement:
|
|
case rdcspv::Op::AtomicIDecrement:
|
|
case rdcspv::Op::AtomicIAdd:
|
|
case rdcspv::Op::AtomicISub:
|
|
case rdcspv::Op::AtomicSMin:
|
|
case rdcspv::Op::AtomicUMin:
|
|
case rdcspv::Op::AtomicSMax:
|
|
case rdcspv::Op::AtomicUMax:
|
|
case rdcspv::Op::AtomicAnd:
|
|
case rdcspv::Op::AtomicOr:
|
|
case rdcspv::Op::AtomicXor:
|
|
{
|
|
rdcspv::IdResultType resultType = rdcspv::IdResultType::fromWord(it.word(1));
|
|
rdcspv::IdResult result = rdcspv::IdResult::fromWord(it.word(2));
|
|
rdcspv::Id pointer = rdcspv::Id::fromWord(it.word(3));
|
|
rdcspv::IdScope memory = rdcspv::IdScope::fromWord(it.word(4));
|
|
rdcspv::IdMemorySemantics semantics = rdcspv::IdMemorySemantics::fromWord(it.word(5));
|
|
editor.Remove(it);
|
|
// All of these instructions produce a result ID that is the original
|
|
// value stored at the pointer. Since we removed the original instruction
|
|
// we replace it with an OpAtomicLoad in case the result ID is used.
|
|
// This is currently best effort and might be incorrect in some cases
|
|
// (for ex. if shader invocations need to see the updated value).
|
|
editor.AddOperation(
|
|
it, rdcspv::OpAtomicLoad(resultType, result, pointer, memory, semantics));
|
|
modified = true;
|
|
break;
|
|
}
|
|
default: break;
|
|
}
|
|
}
|
|
}
|
|
return modified;
|
|
}
|
|
|
|
WrappedVulkan *m_pDriver;
|
|
std::map<uint32_t, VkShaderModule> m_FixedColFS;
|
|
std::map<uint32_t, VkShaderModule> m_PrimIDFS;
|
|
|
|
// ShaderKey consists of original shader module ID and entry point name.
|
|
typedef rdcpair<ResourceId, rdcstr> ShaderKey;
|
|
std::map<ShaderKey, VkShaderModule> m_ShaderReplacements;
|
|
};
|
|
|
|
// VulkanPixelHistoryCallback is a generic VulkanDrawcallCallback that can be used for
|
|
// pixel history replays.
|
|
struct VulkanPixelHistoryCallback : public VulkanDrawcallCallback
|
|
{
|
|
VulkanPixelHistoryCallback(WrappedVulkan *vk, PixelHistoryShaderCache *shaderCache, uint32_t x,
|
|
uint32_t y, uint32_t sampleMask, VkQueryPool occlusionPool)
|
|
: m_pDriver(vk),
|
|
m_ShaderCache(shaderCache),
|
|
m_X(x),
|
|
m_Y(y),
|
|
m_SampleMask(sampleMask),
|
|
m_OcclusionPool(occlusionPool)
|
|
{
|
|
m_pDriver->SetDrawcallCB(this);
|
|
}
|
|
|
|
~VulkanPixelHistoryCallback()
|
|
{
|
|
m_pDriver->SetDrawcallCB(NULL);
|
|
for(const VkRenderPass &rp : m_RpsToDestroy)
|
|
m_pDriver->vkDestroyRenderPass(m_pDriver->GetDev(), rp, NULL);
|
|
for(const VkFramebuffer &fb : m_FbsToDestroy)
|
|
m_pDriver->vkDestroyFramebuffer(m_pDriver->GetDev(), fb, NULL);
|
|
}
|
|
// Update the given scissor to just the pixel for which pixel history was requested.
|
|
void ScissorToPixel(const VkViewport &view, VkRect2D &scissor)
|
|
{
|
|
float fx = (float)m_X;
|
|
float fy = (float)m_Y;
|
|
float y_start = view.y;
|
|
float y_end = view.y + view.height;
|
|
if(view.height < 0)
|
|
{
|
|
y_start = view.y + view.height;
|
|
y_end = view.y;
|
|
}
|
|
|
|
if(fx < view.x || fy < y_start || fx >= view.x + view.width || fy >= y_end)
|
|
{
|
|
scissor.offset.x = scissor.offset.y = scissor.extent.width = scissor.extent.height = 0;
|
|
}
|
|
else
|
|
{
|
|
scissor.offset.x = m_X;
|
|
scissor.offset.y = m_Y;
|
|
scissor.extent.width = scissor.extent.height = 1;
|
|
}
|
|
}
|
|
|
|
// Intersects the originalScissor and newScissor and writes intersection to the newScissor.
|
|
// newScissor always covers a single pixel, so if originalScissor does not touch that pixel
|
|
// returns an empty scissor.
|
|
void IntersectScissors(const VkRect2D &originalScissor, VkRect2D &newScissor)
|
|
{
|
|
RDCASSERT(newScissor.extent.height == 1);
|
|
RDCASSERT(newScissor.extent.width == 1);
|
|
if(originalScissor.offset.x > newScissor.offset.x ||
|
|
originalScissor.offset.x + originalScissor.extent.width <
|
|
newScissor.offset.x + newScissor.extent.width ||
|
|
originalScissor.offset.y > newScissor.offset.y ||
|
|
originalScissor.offset.y + originalScissor.extent.height <
|
|
newScissor.offset.y + newScissor.extent.height)
|
|
{
|
|
// scissor does not touch our target pixel, make it empty
|
|
newScissor.offset.x = newScissor.offset.y = newScissor.extent.width =
|
|
newScissor.extent.height = 0;
|
|
}
|
|
}
|
|
|
|
protected:
|
|
// MakeAllPassIncrementStencilPipelineCI fills in the provided pipeCreateInfo
|
|
// to create a graphics pipeline that is based on the original. The modifications
|
|
// to the original pipeline: disables all tests except stencil, stencil is set
|
|
// to always pass and increment, scissor is set to scissor around target pixel,
|
|
// all shaders are replaced with their "clean" versions (attempts to remove side
|
|
// effects).
|
|
void MakeAllPassIncrementStencilPipelineCI(uint32_t eid, ResourceId pipe,
|
|
VkGraphicsPipelineCreateInfo &pipeCreateInfo,
|
|
rdcarray<VkPipelineShaderStageCreateInfo> &stages)
|
|
{
|
|
const VulkanCreationInfo::Pipeline &p = m_pDriver->GetDebugManager()->GetPipelineInfo(pipe);
|
|
m_pDriver->GetShaderCache()->MakeGraphicsPipelineInfo(pipeCreateInfo, pipe);
|
|
|
|
VkPipelineRasterizationStateCreateInfo *rs =
|
|
(VkPipelineRasterizationStateCreateInfo *)pipeCreateInfo.pRasterizationState;
|
|
VkPipelineDepthStencilStateCreateInfo *ds =
|
|
(VkPipelineDepthStencilStateCreateInfo *)pipeCreateInfo.pDepthStencilState;
|
|
VkPipelineMultisampleStateCreateInfo *ms =
|
|
(VkPipelineMultisampleStateCreateInfo *)pipeCreateInfo.pMultisampleState;
|
|
VkPipelineViewportStateCreateInfo *vs =
|
|
(VkPipelineViewportStateCreateInfo *)pipeCreateInfo.pViewportState;
|
|
|
|
VkRect2D newScissors[16];
|
|
memset(newScissors, 0, sizeof(newScissors));
|
|
// Turn off all tests, except stencil which is set to always pass
|
|
// and increment.
|
|
{
|
|
rs->cullMode = VK_CULL_MODE_NONE;
|
|
rs->rasterizerDiscardEnable = VK_FALSE;
|
|
ds->depthTestEnable = VK_FALSE;
|
|
ds->depthWriteEnable = VK_FALSE;
|
|
ds->depthBoundsTestEnable = VK_FALSE;
|
|
if(m_pDriver->GetDeviceFeatures().depthClamp)
|
|
rs->depthClampEnable = true;
|
|
|
|
ds->stencilTestEnable = VK_TRUE;
|
|
ds->front.compareOp = VK_COMPARE_OP_ALWAYS;
|
|
ds->front.failOp = VK_STENCIL_OP_INCREMENT_AND_CLAMP;
|
|
ds->front.passOp = VK_STENCIL_OP_INCREMENT_AND_CLAMP;
|
|
ds->front.depthFailOp = VK_STENCIL_OP_INCREMENT_AND_CLAMP;
|
|
ds->front.compareMask = 0xff;
|
|
ds->front.writeMask = 0xff;
|
|
ds->front.reference = 0;
|
|
ds->back = ds->front;
|
|
|
|
ms->pSampleMask = &m_SampleMask;
|
|
|
|
// Change scissors unless they are set dynamically.
|
|
if(p.dynamicStates[VkDynamicScissor])
|
|
{
|
|
VulkanRenderState &pipestate = m_pDriver->GetCmdRenderState();
|
|
for(uint32_t i = 0; i < pipestate.views.size(); i++)
|
|
ScissorToPixel(pipestate.views[i], pipestate.scissors[i]);
|
|
}
|
|
else
|
|
{
|
|
for(uint32_t i = 0; i < vs->viewportCount; i++)
|
|
{
|
|
ScissorToPixel(vs->pViewports[i], newScissors[i]);
|
|
}
|
|
vs->pScissors = newScissors;
|
|
}
|
|
}
|
|
|
|
// TODO: this is wrong, should take into account subpass.
|
|
pipeCreateInfo.subpass = 0;
|
|
|
|
stages.resize(pipeCreateInfo.stageCount);
|
|
memcpy(stages.data(), pipeCreateInfo.pStages, stages.byteSize());
|
|
|
|
EventFlags eventFlags = m_pDriver->GetEventFlags(eid);
|
|
VkShaderModule replacementShaders[5] = {};
|
|
|
|
// Clean shaders
|
|
uint32_t numberOfStages = 5;
|
|
for(size_t i = 0; i < numberOfStages; i++)
|
|
{
|
|
if((eventFlags & PipeStageRWEventFlags(StageFromIndex(i))) != EventFlags::NoFlags)
|
|
replacementShaders[i] =
|
|
m_ShaderCache->GetShaderWithoutSideEffects(p.shaders[i].module, p.shaders[i].entryPoint);
|
|
}
|
|
for(uint32_t i = 0; i < pipeCreateInfo.stageCount; i++)
|
|
{
|
|
VkShaderModule replacement = replacementShaders[StageIndex(stages[i].stage)];
|
|
if(replacement != VK_NULL_HANDLE)
|
|
stages[i].module = replacement;
|
|
}
|
|
pipeCreateInfo.pStages = stages.data();
|
|
}
|
|
|
|
// CreateRenderPass creates a new VkRenderPass based on the original that has a separate
|
|
// depth-stencil attachment, and covers a single subpass. This will be used to replay
|
|
// a single draw. The new renderpass also replaces the depth stencil attachment, so
|
|
// it can be used to count the number of fragments. Optionally, the new renderpass
|
|
// changes the format for the color image that corresponds to subImage.
|
|
VkRenderPass CreateRenderPass(ResourceId rp, ResourceId fb, uint32_t subpassIdx,
|
|
VkImage subImage = VK_NULL_HANDLE,
|
|
VkFormat newFormat = VK_FORMAT_UNDEFINED)
|
|
{
|
|
const VulkanCreationInfo::RenderPass &rpInfo =
|
|
m_pDriver->GetDebugManager()->GetRenderPassInfo(rp);
|
|
const VulkanCreationInfo::RenderPass::Subpass &sub = rpInfo.subpasses[subpassIdx];
|
|
|
|
// Copy color and input attachments, and ignore resolve attachments.
|
|
// Since we are only using this renderpass to replay a single draw, we don't
|
|
// need to do resolve operations.
|
|
rdcarray<VkAttachmentReference> colorAttachments(sub.colorAttachments.size());
|
|
rdcarray<VkAttachmentReference> inputAttachments(sub.inputAttachments.size());
|
|
|
|
for(size_t i = 0; i < sub.colorAttachments.size(); i++)
|
|
{
|
|
colorAttachments[i].attachment = sub.colorAttachments[i];
|
|
colorAttachments[i].layout = sub.colorLayouts[i];
|
|
}
|
|
for(size_t i = 0; i < sub.inputAttachments.size(); i++)
|
|
{
|
|
inputAttachments[i].attachment = sub.inputAttachments[i];
|
|
inputAttachments[i].layout = sub.inputLayouts[i];
|
|
}
|
|
|
|
VkSubpassDescription subpassDesc = {};
|
|
subpassDesc.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
|
subpassDesc.inputAttachmentCount = (uint32_t)sub.inputAttachments.size();
|
|
subpassDesc.pInputAttachments = inputAttachments.data();
|
|
subpassDesc.colorAttachmentCount = (uint32_t)sub.colorAttachments.size();
|
|
subpassDesc.pColorAttachments = colorAttachments.data();
|
|
|
|
rdcarray<VkAttachmentDescription> descs(rpInfo.attachments.size());
|
|
for(uint32_t i = 0; i < rpInfo.attachments.size(); i++)
|
|
{
|
|
descs[i] = {};
|
|
descs[i].flags = rpInfo.attachments[i].flags;
|
|
descs[i].format = rpInfo.attachments[i].format;
|
|
descs[i].samples = rpInfo.attachments[i].samples;
|
|
descs[i].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
|
descs[i].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
|
descs[i].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
|
descs[i].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
|
descs[i].initialLayout = rpInfo.attachments[i].initialLayout;
|
|
descs[i].finalLayout = rpInfo.attachments[i].finalLayout;
|
|
}
|
|
for(uint32_t a = 0; a < subpassDesc.colorAttachmentCount; a++)
|
|
{
|
|
if(subpassDesc.pColorAttachments[a].attachment != VK_ATTACHMENT_UNUSED)
|
|
{
|
|
descs[subpassDesc.pColorAttachments[a].attachment].initialLayout =
|
|
descs[subpassDesc.pColorAttachments[a].attachment].finalLayout =
|
|
subpassDesc.pColorAttachments[a].layout;
|
|
}
|
|
}
|
|
|
|
for(uint32_t a = 0; a < subpassDesc.inputAttachmentCount; a++)
|
|
{
|
|
if(subpassDesc.pInputAttachments[a].attachment != VK_ATTACHMENT_UNUSED)
|
|
{
|
|
descs[subpassDesc.pInputAttachments[a].attachment].initialLayout =
|
|
descs[subpassDesc.pInputAttachments[a].attachment].finalLayout =
|
|
subpassDesc.pInputAttachments[a].layout;
|
|
}
|
|
}
|
|
|
|
VkAttachmentDescription dsAtt = {};
|
|
dsAtt.format = VK_FORMAT_D32_SFLOAT_S8_UINT;
|
|
dsAtt.samples = VK_SAMPLE_COUNT_1_BIT;
|
|
dsAtt.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
|
dsAtt.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
|
dsAtt.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
|
dsAtt.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE;
|
|
dsAtt.initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
|
dsAtt.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
|
|
|
// If there is already a depth stencil attachment, substitute it.
|
|
// Otherwise, add it at the end of all attachments.
|
|
VkAttachmentReference dsAttachment = {};
|
|
dsAttachment.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
|
if(sub.depthstencilAttachment != -1)
|
|
{
|
|
descs[sub.depthstencilAttachment] = dsAtt;
|
|
dsAttachment.attachment = sub.depthstencilAttachment;
|
|
}
|
|
else
|
|
{
|
|
descs.push_back(dsAtt);
|
|
dsAttachment.attachment = (uint32_t)rpInfo.attachments.size();
|
|
}
|
|
subpassDesc.pDepthStencilAttachment = &dsAttachment;
|
|
|
|
// If needed substitute the color attachment with the new format.
|
|
const VulkanCreationInfo::Framebuffer &fbInfo =
|
|
m_pDriver->GetDebugManager()->GetFramebufferInfo(fb);
|
|
for(uint32_t i = 0; i < fbInfo.attachments.size(); i++)
|
|
{
|
|
if(m_pDriver->GetDebugManager()->GetImageViewInfo(fbInfo.attachments[i].createdView).image ==
|
|
GetResID(subImage))
|
|
descs[i].format = newFormat;
|
|
}
|
|
|
|
VkRenderPassCreateInfo rpCreateInfo = {};
|
|
rpCreateInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
|
|
rpCreateInfo.attachmentCount = (uint32_t)descs.size();
|
|
rpCreateInfo.subpassCount = 1;
|
|
rpCreateInfo.pSubpasses = &subpassDesc;
|
|
rpCreateInfo.pAttachments = descs.data();
|
|
rpCreateInfo.dependencyCount = 0;
|
|
rpCreateInfo.pDependencies = NULL;
|
|
|
|
VkRenderPass renderpass;
|
|
VkResult vkr =
|
|
m_pDriver->vkCreateRenderPass(m_pDriver->GetDev(), &rpCreateInfo, NULL, &renderpass);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
m_RpsToDestroy.push_back(renderpass);
|
|
return renderpass;
|
|
}
|
|
|
|
// CreateFrambuffer creates a new VkFramebuffer that is based on the original, but
|
|
// substitutes the depth stencil image view. If there is no depth stencil attachment,
|
|
// it will be added. Optionally, also substitutes the color attachment that corresponds
|
|
// to subImage.
|
|
VkFramebuffer CreateFramebuffer(ResourceId rp, VkRenderPass newRp, uint32_t subpassIndex,
|
|
ResourceId origFb, VkImageView newDsImageView,
|
|
ResourceId subImage = ResourceId(),
|
|
VkImageView newImageView = VK_NULL_HANDLE)
|
|
{
|
|
const VulkanCreationInfo::RenderPass &rpInfo =
|
|
m_pDriver->GetDebugManager()->GetRenderPassInfo(rp);
|
|
const VulkanCreationInfo::RenderPass::Subpass &sub = rpInfo.subpasses[subpassIndex];
|
|
const VulkanCreationInfo::Framebuffer &fbInfo =
|
|
m_pDriver->GetDebugManager()->GetFramebufferInfo(origFb);
|
|
rdcarray<VkImageView> atts(fbInfo.attachments.size());
|
|
|
|
for(uint32_t i = 0; i < fbInfo.attachments.size(); i++)
|
|
{
|
|
atts[i] = m_pDriver->GetResourceManager()->GetCurrentHandle<VkImageView>(
|
|
fbInfo.attachments[i].createdView);
|
|
if(subImage != ResourceId())
|
|
{
|
|
ResourceId img =
|
|
m_pDriver->GetDebugManager()->GetImageViewInfo(fbInfo.attachments[i].createdView).image;
|
|
if(img == subImage)
|
|
atts[i] = newImageView;
|
|
}
|
|
}
|
|
|
|
if(sub.depthstencilAttachment != -1)
|
|
atts[sub.depthstencilAttachment] = newDsImageView;
|
|
else
|
|
atts.push_back(newDsImageView);
|
|
VkFramebufferCreateInfo fbCI = {VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO};
|
|
fbCI.renderPass = newRp;
|
|
fbCI.attachmentCount = (uint32_t)atts.size();
|
|
fbCI.pAttachments = atts.data();
|
|
fbCI.width = fbInfo.width;
|
|
fbCI.height = fbInfo.height;
|
|
fbCI.layers = fbInfo.layers;
|
|
|
|
VkFramebuffer framebuffer;
|
|
VkResult vkr = m_pDriver->vkCreateFramebuffer(m_pDriver->GetDev(), &fbCI, NULL, &framebuffer);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
m_FbsToDestroy.push_back(framebuffer);
|
|
return framebuffer;
|
|
}
|
|
|
|
WrappedVulkan *m_pDriver;
|
|
PixelHistoryShaderCache *m_ShaderCache;
|
|
uint32_t m_X;
|
|
uint32_t m_Y;
|
|
uint32_t m_SampleMask;
|
|
VkQueryPool m_OcclusionPool;
|
|
rdcarray<VkRenderPass> m_RpsToDestroy;
|
|
rdcarray<VkFramebuffer> m_FbsToDestroy;
|
|
};
|
|
|
|
// VulkanOcclusionCallback callback is used to determine which draw events might have
|
|
// modified the pixel by doing an occlusion query.
|
|
struct VulkanOcclusionCallback : public VulkanPixelHistoryCallback
|
|
{
|
|
VulkanOcclusionCallback(WrappedVulkan *vk, PixelHistoryShaderCache *shaderCache, VkImage image,
|
|
uint32_t x, uint32_t y, uint32_t sampleMask, VkQueryPool occlusionPool,
|
|
const rdcarray<EventUsage> &allEvents)
|
|
: VulkanPixelHistoryCallback(vk, shaderCache, x, y, sampleMask, occlusionPool), m_Image(image)
|
|
{
|
|
for(size_t i = 0; i < allEvents.size(); i++)
|
|
m_Events.push_back(allEvents[i].eventId);
|
|
}
|
|
|
|
~VulkanOcclusionCallback()
|
|
{
|
|
for(auto it = m_PipeCache.begin(); it != m_PipeCache.end(); ++it)
|
|
m_pDriver->vkDestroyPipeline(m_pDriver->GetDev(), it->second, NULL);
|
|
}
|
|
|
|
void PreDraw(uint32_t eid, VkCommandBuffer cmd)
|
|
{
|
|
if(!m_Events.contains(eid))
|
|
return;
|
|
VulkanRenderState prevState = m_pDriver->GetCmdRenderState();
|
|
VulkanRenderState &pipestate = m_pDriver->GetCmdRenderState();
|
|
const VulkanCreationInfo::Pipeline &p =
|
|
m_pDriver->GetDebugManager()->GetPipelineInfo(pipestate.graphics.pipeline);
|
|
|
|
uint32_t framebufferIndex = 0;
|
|
const rdcarray<ResourceId> &atts = pipestate.GetFramebufferAttachments();
|
|
for(uint32_t i = 0; i < atts.size(); i++)
|
|
{
|
|
ResourceId img = m_pDriver->GetDebugManager()->GetImageViewInfo(atts[i]).image;
|
|
if(img == GetResID(m_Image))
|
|
{
|
|
framebufferIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
VkPipeline pipe = GetPixelOcclusionPipeline(eid, prevState.graphics.pipeline, framebufferIndex);
|
|
if(p.dynamicStates[VkDynamicScissor])
|
|
for(uint32_t i = 0; i < pipestate.views.size(); i++)
|
|
ScissorToPixel(pipestate.views[i], pipestate.scissors[i]);
|
|
pipestate.graphics.pipeline = GetResID(pipe);
|
|
ReplayDrawWithQuery(cmd, eid);
|
|
|
|
m_pDriver->GetCmdRenderState() = prevState;
|
|
m_pDriver->GetCmdRenderState().BindPipeline(m_pDriver, cmd, VulkanRenderState::BindGraphics,
|
|
true);
|
|
}
|
|
|
|
bool PostDraw(uint32_t eid, VkCommandBuffer cmd) { return false; }
|
|
void PostRedraw(uint32_t eid, VkCommandBuffer cmd) {}
|
|
void PreDispatch(uint32_t eid, VkCommandBuffer cmd) { return; }
|
|
bool PostDispatch(uint32_t eid, VkCommandBuffer cmd) { return false; }
|
|
void PostRedispatch(uint32_t eid, VkCommandBuffer cmd) {}
|
|
void PreMisc(uint32_t eid, DrawFlags flags, VkCommandBuffer cmd) { return; }
|
|
bool PostMisc(uint32_t eid, DrawFlags flags, VkCommandBuffer cmd) { return false; }
|
|
void PostRemisc(uint32_t eid, DrawFlags flags, VkCommandBuffer cmd) {}
|
|
void PreEndCommandBuffer(VkCommandBuffer cmd) {}
|
|
void AliasEvent(uint32_t primary, uint32_t alias) {}
|
|
bool SplitSecondary() { 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)
|
|
{
|
|
}
|
|
|
|
void FetchOcclusionResults()
|
|
{
|
|
if(m_OcclusionQueries.size() == 0)
|
|
return;
|
|
|
|
m_OcclusionResults.resize(m_OcclusionQueries.size());
|
|
VkResult vkr = ObjDisp(m_pDriver->GetDev())
|
|
->GetQueryPoolResults(Unwrap(m_pDriver->GetDev()), m_OcclusionPool, 0,
|
|
(uint32_t)m_OcclusionResults.size(),
|
|
m_OcclusionResults.byteSize(),
|
|
m_OcclusionResults.data(), sizeof(uint64_t),
|
|
VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
}
|
|
|
|
uint64_t GetOcclusionResult(uint32_t eventId)
|
|
{
|
|
auto it = m_OcclusionQueries.find(eventId);
|
|
if(it == m_OcclusionQueries.end())
|
|
return 0;
|
|
RDCASSERT(it->second < m_OcclusionResults.size());
|
|
return m_OcclusionResults[it->second];
|
|
}
|
|
|
|
private:
|
|
// ReplayDrawWithQuery binds the pipeline in the current state, and replays a single
|
|
// draw with an occlusion query.
|
|
void ReplayDrawWithQuery(VkCommandBuffer cmd, uint32_t eventId)
|
|
{
|
|
const DrawcallDescription *drawcall = m_pDriver->GetDrawcall(eventId);
|
|
m_pDriver->GetCmdRenderState().BindPipeline(m_pDriver, cmd, VulkanRenderState::BindGraphics,
|
|
true);
|
|
|
|
uint32_t occlIndex = (uint32_t)m_OcclusionQueries.size();
|
|
ObjDisp(cmd)->CmdBeginQuery(Unwrap(cmd), m_OcclusionPool, occlIndex, 0);
|
|
|
|
if(drawcall->flags & DrawFlags::Indexed)
|
|
ObjDisp(cmd)->CmdDrawIndexed(Unwrap(cmd), drawcall->numIndices, drawcall->numInstances,
|
|
drawcall->indexOffset, drawcall->baseVertex,
|
|
drawcall->instanceOffset);
|
|
else
|
|
ObjDisp(cmd)->CmdDraw(Unwrap(cmd), drawcall->numIndices, drawcall->numInstances,
|
|
drawcall->vertexOffset, drawcall->instanceOffset);
|
|
|
|
ObjDisp(cmd)->CmdEndQuery(Unwrap(cmd), m_OcclusionPool, occlIndex);
|
|
m_OcclusionQueries.insert(std::make_pair(eventId, occlIndex));
|
|
}
|
|
|
|
VkPipeline GetPixelOcclusionPipeline(uint32_t eid, ResourceId pipeline, uint32_t framebufferIndex)
|
|
{
|
|
auto it = m_PipeCache.find(pipeline);
|
|
if(it != m_PipeCache.end())
|
|
return it->second;
|
|
|
|
VkGraphicsPipelineCreateInfo pipeCreateInfo = {};
|
|
rdcarray<VkPipelineShaderStageCreateInfo> stages;
|
|
MakeAllPassIncrementStencilPipelineCI(eid, pipeline, pipeCreateInfo, stages);
|
|
{
|
|
// We just need to determine if something attempted to write to pixel.
|
|
// Disable actual color modifications.
|
|
VkPipelineColorBlendStateCreateInfo *cbs =
|
|
(VkPipelineColorBlendStateCreateInfo *)pipeCreateInfo.pColorBlendState;
|
|
VkPipelineColorBlendAttachmentState *atts =
|
|
(VkPipelineColorBlendAttachmentState *)cbs->pAttachments;
|
|
for(uint32_t i = 0; i < cbs->attachmentCount; i++)
|
|
atts[i].colorWriteMask = 0;
|
|
}
|
|
for(uint32_t i = 0; i < pipeCreateInfo.stageCount; i++)
|
|
{
|
|
if(stages[i].stage == VK_SHADER_STAGE_FRAGMENT_BIT)
|
|
{
|
|
stages[i].module = m_ShaderCache->GetFixedColShader(framebufferIndex);
|
|
stages[i].pName = "main";
|
|
break;
|
|
}
|
|
}
|
|
VkPipeline pipe;
|
|
VkResult vkr = m_pDriver->vkCreateGraphicsPipelines(m_pDriver->GetDev(), VK_NULL_HANDLE, 1,
|
|
&pipeCreateInfo, NULL, &pipe);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
m_PipeCache.insert(std::make_pair(pipeline, pipe));
|
|
return pipe;
|
|
}
|
|
|
|
private:
|
|
VkImage m_Image;
|
|
std::map<ResourceId, VkPipeline> m_PipeCache;
|
|
rdcarray<uint32_t> m_Events;
|
|
// Key is event ID, and value is an index of where the occlusion result.
|
|
std::map<uint32_t, uint32_t> m_OcclusionQueries;
|
|
rdcarray<uint64_t> m_OcclusionResults;
|
|
};
|
|
|
|
struct VulkanColorAndStencilCallback : public VulkanPixelHistoryCallback
|
|
{
|
|
VulkanColorAndStencilCallback(WrappedVulkan *vk, PixelHistoryShaderCache *shaderCache, uint32_t x,
|
|
uint32_t y, VkImage image, VkFormat format, const Subresource &sub,
|
|
uint32_t sampleMask, VkImageView stencilImageView,
|
|
VkImage stencilImage, VkBuffer dstBuffer,
|
|
const rdcarray<uint32_t> &events)
|
|
: VulkanPixelHistoryCallback(vk, shaderCache, x, y, sampleMask, VK_NULL_HANDLE),
|
|
m_Image(image),
|
|
m_Format(format),
|
|
m_DstBuffer(dstBuffer),
|
|
m_StencilImageView(stencilImageView),
|
|
m_StencilImage(stencilImage),
|
|
m_Subresource(sub),
|
|
m_Events(events)
|
|
{
|
|
}
|
|
|
|
~VulkanColorAndStencilCallback()
|
|
{
|
|
for(auto it = m_PipeCache.begin(); it != m_PipeCache.end(); ++it)
|
|
{
|
|
m_pDriver->vkDestroyPipeline(m_pDriver->GetDev(), it->second.fixedShaderStencil, NULL);
|
|
m_pDriver->vkDestroyPipeline(m_pDriver->GetDev(), it->second.originalShaderStencil, NULL);
|
|
}
|
|
}
|
|
|
|
void PreDraw(uint32_t eid, VkCommandBuffer cmd)
|
|
{
|
|
if(!m_Events.contains(eid) || !m_pDriver->IsCmdPrimary())
|
|
return;
|
|
|
|
// TODO: can't end renderpass if we are not on the last subpass.
|
|
VulkanRenderState prevState = m_pDriver->GetCmdRenderState();
|
|
VulkanRenderState &pipestate = m_pDriver->GetCmdRenderState();
|
|
|
|
pipestate.EndRenderPass(cmd);
|
|
|
|
// Get pre-modification values
|
|
size_t storeOffset = m_EventIndices.size() * sizeof(EventInfo);
|
|
VkImage depthImage = VK_NULL_HANDLE;
|
|
VkFormat depthFormat = VK_FORMAT_UNDEFINED;
|
|
const DrawcallDescription *draw = m_pDriver->GetDrawcall(eid);
|
|
if(draw && draw->depthOut != ResourceId())
|
|
{
|
|
ResourceId resId = m_pDriver->GetResourceManager()->GetLiveID(draw->depthOut);
|
|
depthImage = m_pDriver->GetResourceManager()->GetCurrentHandle<VkImage>(resId);
|
|
const VulkanCreationInfo::Image &imginfo = m_pDriver->GetDebugManager()->GetImageInfo(resId);
|
|
depthFormat = imginfo.format;
|
|
}
|
|
|
|
CopyPixel(m_Image, m_Format, depthImage, depthFormat, cmd, storeOffset);
|
|
|
|
ResourceId prevRenderpass = pipestate.renderPass;
|
|
ResourceId prevFramebuffer = pipestate.GetFramebuffer();
|
|
rdcarray<ResourceId> prevFBattachments = pipestate.GetFramebufferAttachments();
|
|
|
|
uint32_t prevSubpass = pipestate.subpass;
|
|
|
|
{
|
|
VkRenderPass newRp =
|
|
CreateRenderPass(pipestate.renderPass, pipestate.GetFramebuffer(), pipestate.subpass);
|
|
VkFramebuffer newFb = CreateFramebuffer(pipestate.renderPass, newRp, pipestate.subpass,
|
|
pipestate.GetFramebuffer(), m_StencilImageView);
|
|
uint32_t framebufferIndex = 0;
|
|
const rdcarray<ResourceId> &atts = pipestate.GetFramebufferAttachments();
|
|
for(uint32_t i = 0; i < atts.size(); i++)
|
|
{
|
|
ResourceId img = m_pDriver->GetDebugManager()->GetImageViewInfo(atts[i]).image;
|
|
if(img == GetResID(m_Image))
|
|
{
|
|
framebufferIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
PipelineReplacements replacements =
|
|
GetPipelineReplacements(eid, pipestate.graphics.pipeline, newRp, framebufferIndex);
|
|
|
|
const VulkanCreationInfo::Pipeline &p =
|
|
m_pDriver->GetDebugManager()->GetPipelineInfo(pipestate.graphics.pipeline);
|
|
if(p.dynamicStates[VkDynamicScissor])
|
|
for(uint32_t i = 0; i < pipestate.views.size(); i++)
|
|
ScissorToPixel(pipestate.views[i], pipestate.scissors[i]);
|
|
|
|
// Replay the draw with a fixed color shader that never discards, and stencil
|
|
// increment to count number of fragments. We will get the number of fragments
|
|
// not accounting for shader discard.
|
|
pipestate.SetFramebuffer(m_pDriver, GetResID(newFb));
|
|
pipestate.renderPass = GetResID(newRp);
|
|
pipestate.subpass = 0;
|
|
pipestate.graphics.pipeline = GetResID(replacements.fixedShaderStencil);
|
|
ReplayDraw(cmd, eid, true);
|
|
|
|
CopyPixelParams params = {};
|
|
params.multisampled = false;
|
|
params.srcImage = m_StencilImage;
|
|
params.srcImageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
|
params.srcImageFormat = VK_FORMAT_D32_SFLOAT_S8_UINT;
|
|
params.imageOffset.x = int32_t(m_X);
|
|
params.imageOffset.y = int32_t(m_Y);
|
|
params.imageOffset.z = 0;
|
|
params.dstBuffer = m_DstBuffer;
|
|
params.depthCopy = true;
|
|
params.stencilOnly = true;
|
|
params.mipLevel = m_Subresource.mip;
|
|
params.slice = m_Subresource.slice;
|
|
// Copy stencil value that indicates the number of fragments ignoring
|
|
// shader discard.
|
|
m_pDriver->GetDebugManager()->PixelHistoryCopyPixel(
|
|
cmd, params, storeOffset + offsetof(struct EventInfo, dsWithoutShaderDiscard));
|
|
|
|
// Replay the draw with the original fragment shader to get the actual number
|
|
// of fragments, accounting for potential shader discard.
|
|
pipestate.graphics.pipeline = GetResID(replacements.originalShaderStencil);
|
|
ReplayDraw(cmd, eid, true);
|
|
|
|
m_pDriver->GetDebugManager()->PixelHistoryCopyPixel(
|
|
cmd, params, storeOffset + offsetof(struct EventInfo, dsWithShaderDiscard));
|
|
}
|
|
|
|
// Restore the state.
|
|
m_pDriver->GetCmdRenderState() = prevState;
|
|
pipestate.SetFramebuffer(prevFramebuffer, prevFBattachments);
|
|
pipestate.renderPass = prevRenderpass;
|
|
pipestate.subpass = prevSubpass;
|
|
|
|
// TODO: Need to re-start on the correct subpass.
|
|
if(pipestate.graphics.pipeline != ResourceId())
|
|
pipestate.BeginRenderPassAndApplyState(m_pDriver, cmd, VulkanRenderState::BindGraphics);
|
|
}
|
|
|
|
bool PostDraw(uint32_t eid, VkCommandBuffer cmd)
|
|
{
|
|
if(!m_Events.contains(eid) || !m_pDriver->IsCmdPrimary())
|
|
return false;
|
|
|
|
m_pDriver->GetCmdRenderState().EndRenderPass(cmd);
|
|
|
|
size_t storeOffset = m_EventIndices.size() * sizeof(EventInfo);
|
|
VkImage depthImage = VK_NULL_HANDLE;
|
|
VkFormat depthFormat = VK_FORMAT_UNDEFINED;
|
|
const DrawcallDescription *draw = m_pDriver->GetDrawcall(eid);
|
|
if(draw && draw->depthOut != ResourceId())
|
|
{
|
|
ResourceId resId = m_pDriver->GetResourceManager()->GetLiveID(draw->depthOut);
|
|
depthImage = m_pDriver->GetResourceManager()->GetCurrentHandle<VkImage>(resId);
|
|
const VulkanCreationInfo::Image &imginfo = m_pDriver->GetDebugManager()->GetImageInfo(resId);
|
|
depthFormat = imginfo.format;
|
|
}
|
|
|
|
CopyPixel(m_Image, m_Format, depthImage, depthFormat, cmd,
|
|
storeOffset + offsetof(struct EventInfo, postmod));
|
|
|
|
m_pDriver->GetCmdRenderState().BeginRenderPassAndApplyState(m_pDriver, cmd,
|
|
VulkanRenderState::BindGraphics);
|
|
|
|
// Get post-modification values
|
|
m_EventIndices.insert(std::make_pair(eid, m_EventIndices.size()));
|
|
return false;
|
|
}
|
|
|
|
void PostRedraw(uint32_t eid, VkCommandBuffer cmd)
|
|
{
|
|
// nothing to do
|
|
}
|
|
|
|
void PreCmdExecute(uint32_t baseEid, uint32_t secondaryFirst, uint32_t secondaryLast,
|
|
VkCommandBuffer cmd)
|
|
{
|
|
uint32_t eventId = 0;
|
|
if(m_Events.size() == 0)
|
|
return;
|
|
for(size_t i = 0; i < m_Events.size(); i++)
|
|
{
|
|
// Find the first event in range
|
|
if(m_Events[i] >= secondaryFirst && m_Events[i] <= secondaryLast)
|
|
{
|
|
eventId = m_Events[i];
|
|
break;
|
|
}
|
|
}
|
|
if(eventId == 0)
|
|
return;
|
|
|
|
m_pDriver->GetCmdRenderState().EndRenderPass(cmd);
|
|
|
|
// Copy
|
|
size_t storeOffset = m_EventIndices.size() * sizeof(EventInfo);
|
|
CopyPixel(m_Image, m_Format, VK_NULL_HANDLE, VK_FORMAT_UNDEFINED, cmd, storeOffset);
|
|
m_EventIndices.insert(std::make_pair(eventId, m_EventIndices.size()));
|
|
|
|
m_pDriver->GetCmdRenderState().BeginRenderPassAndApplyState(m_pDriver, cmd,
|
|
VulkanRenderState::BindNone);
|
|
}
|
|
|
|
void PostCmdExecute(uint32_t baseEid, uint32_t secondaryFirst, uint32_t secondaryLast,
|
|
VkCommandBuffer cmd)
|
|
{
|
|
uint32_t eventId = 0;
|
|
if(m_Events.size() == 0)
|
|
return;
|
|
for(int32_t i = (int32_t)m_Events.size() - 1; i >= 0; i--)
|
|
{
|
|
// Find the last event in range.
|
|
if(m_Events[i] >= secondaryFirst && m_Events[i] <= secondaryLast)
|
|
{
|
|
eventId = m_Events[i];
|
|
break;
|
|
}
|
|
}
|
|
if(eventId == 0)
|
|
return;
|
|
|
|
m_pDriver->GetCmdRenderState().EndRenderPass(cmd);
|
|
size_t storeOffset = 0;
|
|
auto it = m_EventIndices.find(eventId);
|
|
if(it != m_EventIndices.end())
|
|
{
|
|
storeOffset = it->second * sizeof(EventInfo);
|
|
}
|
|
else
|
|
{
|
|
storeOffset = m_EventIndices.size() * sizeof(EventInfo);
|
|
m_EventIndices.insert(std::make_pair(eventId, m_EventIndices.size()));
|
|
}
|
|
CopyPixel(m_Image, m_Format, VK_NULL_HANDLE, VK_FORMAT_UNDEFINED, cmd,
|
|
storeOffset + offsetof(struct EventInfo, postmod));
|
|
m_pDriver->GetCmdRenderState().BeginRenderPassAndApplyState(m_pDriver, cmd,
|
|
VulkanRenderState::BindNone);
|
|
}
|
|
|
|
void PreDispatch(uint32_t eid, VkCommandBuffer cmd)
|
|
{
|
|
if(!m_Events.contains(eid))
|
|
return;
|
|
|
|
size_t storeOffset = m_EventIndices.size() * sizeof(EventInfo);
|
|
CopyPixel(m_Image, m_Format, VK_NULL_HANDLE, VK_FORMAT_UNDEFINED, cmd, storeOffset);
|
|
}
|
|
bool PostDispatch(uint32_t eid, VkCommandBuffer cmd)
|
|
{
|
|
if(!m_Events.contains(eid))
|
|
return false;
|
|
|
|
size_t storeOffset = m_EventIndices.size() * sizeof(EventInfo);
|
|
CopyPixel(m_Image, m_Format, VK_NULL_HANDLE, VK_FORMAT_UNDEFINED, cmd,
|
|
storeOffset + offsetof(struct EventInfo, postmod));
|
|
m_EventIndices.insert(std::make_pair(eid, m_EventIndices.size()));
|
|
return false;
|
|
}
|
|
void PostRedispatch(uint32_t eid, VkCommandBuffer cmd) {}
|
|
void PreMisc(uint32_t eid, DrawFlags flags, VkCommandBuffer cmd) { PreDispatch(eid, cmd); }
|
|
bool PostMisc(uint32_t eid, DrawFlags flags, VkCommandBuffer cmd)
|
|
{
|
|
if(!m_Events.contains(eid))
|
|
return false;
|
|
if(flags & DrawFlags::BeginPass)
|
|
m_pDriver->GetCmdRenderState().EndRenderPass(cmd);
|
|
|
|
bool ret = PostDispatch(eid, cmd);
|
|
|
|
if(flags & DrawFlags::BeginPass)
|
|
m_pDriver->GetCmdRenderState().BeginRenderPassAndApplyState(m_pDriver, cmd,
|
|
VulkanRenderState::BindNone);
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool SplitSecondary() { return true; }
|
|
void PostRemisc(uint32_t eid, DrawFlags flags, VkCommandBuffer cmd) {}
|
|
void PreEndCommandBuffer(VkCommandBuffer cmd) {}
|
|
void AliasEvent(uint32_t primary, uint32_t alias)
|
|
{
|
|
RDCWARN(
|
|
"Alised events are not supported, results might be inaccurate. Primary event id: %u, "
|
|
"alias: %u.",
|
|
primary, alias);
|
|
}
|
|
|
|
int32_t GetEventIndex(uint32_t eventId)
|
|
{
|
|
auto it = m_EventIndices.find(eventId);
|
|
if(it == m_EventIndices.end())
|
|
// Most likely a secondary command buffer event for which there is no
|
|
// information.
|
|
return -1;
|
|
RDCASSERT(it != m_EventIndices.end());
|
|
return (int32_t)it->second;
|
|
}
|
|
|
|
private:
|
|
void CopyPixel(VkImage srcImage, VkFormat srcFormat, VkImage depthImage, VkFormat depthFormat,
|
|
VkCommandBuffer cmd, size_t offset)
|
|
{
|
|
CopyPixelParams colourCopyParams = {};
|
|
colourCopyParams.multisampled = false; // TODO: multisampled
|
|
colourCopyParams.srcImage = srcImage;
|
|
colourCopyParams.srcImageLayout =
|
|
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; // TODO: image layout
|
|
colourCopyParams.srcImageFormat = srcFormat;
|
|
colourCopyParams.imageOffset.x = int32_t(m_X);
|
|
colourCopyParams.imageOffset.y = int32_t(m_Y);
|
|
colourCopyParams.imageOffset.z = 0;
|
|
colourCopyParams.dstBuffer = m_DstBuffer;
|
|
colourCopyParams.slice = m_Subresource.slice;
|
|
colourCopyParams.mipLevel = m_Subresource.mip;
|
|
|
|
m_pDriver->GetDebugManager()->PixelHistoryCopyPixel(cmd, colourCopyParams, offset);
|
|
|
|
if(depthImage != VK_NULL_HANDLE)
|
|
{
|
|
CopyPixelParams depthCopyParams = colourCopyParams;
|
|
depthCopyParams.depthCopy = true;
|
|
depthCopyParams.srcImage = depthImage;
|
|
depthCopyParams.srcImageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
|
depthCopyParams.srcImageFormat = depthFormat;
|
|
m_pDriver->GetDebugManager()->PixelHistoryCopyPixel(
|
|
cmd, depthCopyParams, offset + offsetof(struct PixelHistoryValue, depth));
|
|
}
|
|
}
|
|
|
|
// ReplayDraw begins renderpass, executes a single draw defined by the eventId and
|
|
// ends the renderpass.
|
|
void ReplayDraw(VkCommandBuffer cmd, uint32_t eventId, bool clear = false)
|
|
{
|
|
m_pDriver->GetCmdRenderState().BeginRenderPassAndApplyState(m_pDriver, cmd,
|
|
VulkanRenderState::BindGraphics);
|
|
|
|
if(clear)
|
|
{
|
|
VkClearAttachment att = {};
|
|
att.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT;
|
|
VkClearRect rect = {};
|
|
rect.rect.offset.x = m_X;
|
|
rect.rect.offset.y = m_Y;
|
|
rect.rect.extent.width = 1;
|
|
rect.rect.extent.height = 1;
|
|
rect.baseArrayLayer = 0;
|
|
rect.layerCount = 1;
|
|
ObjDisp(cmd)->CmdClearAttachments(Unwrap(cmd), 1, &att, 1, &rect);
|
|
}
|
|
|
|
const DrawcallDescription *drawcall = m_pDriver->GetDrawcall(eventId);
|
|
if(drawcall->flags & DrawFlags::Indexed)
|
|
ObjDisp(cmd)->CmdDrawIndexed(Unwrap(cmd), drawcall->numIndices, drawcall->numInstances,
|
|
drawcall->indexOffset, drawcall->baseVertex,
|
|
drawcall->instanceOffset);
|
|
else
|
|
ObjDisp(cmd)->CmdDraw(Unwrap(cmd), drawcall->numIndices, drawcall->numInstances,
|
|
drawcall->vertexOffset, drawcall->instanceOffset);
|
|
|
|
m_pDriver->GetCmdRenderState().EndRenderPass(cmd);
|
|
}
|
|
|
|
// GetPipelineReplacements creates pipeline replacements that disable all tests,
|
|
// and use either fixed or original fragment shader, and shaders that don't
|
|
// have side effects.
|
|
PipelineReplacements GetPipelineReplacements(uint32_t eid, ResourceId pipeline, VkRenderPass rp,
|
|
uint32_t framebufferIndex)
|
|
{
|
|
// The map does not keep track of the event ID, event ID is only used to figure out
|
|
// which shaders need to be modified. Those flags are based on the shaders bound,
|
|
// so in theory all events should share those flags if they are using the same
|
|
// pipeline.
|
|
auto pipeIt = m_PipeCache.find(pipeline);
|
|
if(pipeIt != m_PipeCache.end())
|
|
return pipeIt->second;
|
|
|
|
VkGraphicsPipelineCreateInfo pipeCreateInfo = {};
|
|
rdcarray<VkPipelineShaderStageCreateInfo> stages;
|
|
MakeAllPassIncrementStencilPipelineCI(eid, pipeline, pipeCreateInfo, stages);
|
|
// No need to change depth stencil state, it is already
|
|
// set to always pass, and increment.
|
|
pipeCreateInfo.renderPass = rp;
|
|
|
|
{
|
|
// We just need to determine if something attempted to write to pixel.
|
|
// Disable actual color modifications.
|
|
VkPipelineColorBlendStateCreateInfo *cbs =
|
|
(VkPipelineColorBlendStateCreateInfo *)pipeCreateInfo.pColorBlendState;
|
|
VkPipelineColorBlendAttachmentState *atts =
|
|
(VkPipelineColorBlendAttachmentState *)cbs->pAttachments;
|
|
for(uint32_t i = 0; i < cbs->attachmentCount; i++)
|
|
atts[i].colorWriteMask = 0;
|
|
}
|
|
|
|
PipelineReplacements replacements = {};
|
|
VkResult vkr = m_pDriver->vkCreateGraphicsPipelines(m_pDriver->GetDev(), VK_NULL_HANDLE, 1,
|
|
&pipeCreateInfo, NULL,
|
|
&replacements.originalShaderStencil);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
for(uint32_t i = 0; i < pipeCreateInfo.stageCount; i++)
|
|
{
|
|
if(stages[i].stage == VK_SHADER_STAGE_FRAGMENT_BIT)
|
|
{
|
|
stages[i].module = m_ShaderCache->GetFixedColShader(framebufferIndex);
|
|
stages[i].pName = "main";
|
|
break;
|
|
}
|
|
}
|
|
|
|
vkr = m_pDriver->vkCreateGraphicsPipelines(m_pDriver->GetDev(), VK_NULL_HANDLE, 1,
|
|
&pipeCreateInfo, NULL,
|
|
&replacements.fixedShaderStencil);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
m_PipeCache.insert(std::make_pair(pipeline, replacements));
|
|
|
|
return replacements;
|
|
}
|
|
|
|
VkImage m_Image;
|
|
VkFormat m_Format;
|
|
VkBuffer m_DstBuffer;
|
|
Subresource m_Subresource;
|
|
|
|
VkImageView m_StencilImageView;
|
|
VkImage m_StencilImage;
|
|
|
|
std::map<ResourceId, PipelineReplacements> m_PipeCache;
|
|
rdcarray<uint32_t> m_Events;
|
|
// Key is event ID, and value is an index of where the event data is stored.
|
|
std::map<uint32_t, size_t> m_EventIndices;
|
|
};
|
|
|
|
// TestsFailedCallback replays draws to figure out which tests failed (for ex., depth,
|
|
// stencil test etc).
|
|
struct TestsFailedCallback : public VulkanPixelHistoryCallback
|
|
{
|
|
TestsFailedCallback(WrappedVulkan *vk, PixelHistoryShaderCache *shaderCache, uint32_t x,
|
|
uint32_t y, VkImage image, uint32_t sampleMask, VkQueryPool occlusionPool,
|
|
rdcarray<uint32_t> events)
|
|
: VulkanPixelHistoryCallback(vk, shaderCache, x, y, sampleMask, occlusionPool),
|
|
m_Image(image),
|
|
m_Events(events)
|
|
{
|
|
}
|
|
|
|
~TestsFailedCallback() {}
|
|
void PreDraw(uint32_t eid, VkCommandBuffer cmd)
|
|
{
|
|
if(!m_Events.contains(eid))
|
|
return;
|
|
|
|
VulkanRenderState &pipestate = m_pDriver->GetCmdRenderState();
|
|
const VulkanCreationInfo::Pipeline &p =
|
|
m_pDriver->GetDebugManager()->GetPipelineInfo(pipestate.graphics.pipeline);
|
|
uint32_t eventFlags = CalculateEventFlags(p, pipestate);
|
|
m_EventFlags[eid] = eventFlags;
|
|
|
|
// TODO: figure out if the shader has early fragments tests turned on,
|
|
// based on the currently bound fragment shader.
|
|
bool earlyFragmentTests = false;
|
|
m_HasEarlyFragments[eid] = earlyFragmentTests;
|
|
|
|
ResourceId curPipeline = pipestate.graphics.pipeline;
|
|
VulkanRenderState m_PrevState = m_pDriver->GetCmdRenderState();
|
|
|
|
uint32_t framebufferIndex = 0;
|
|
const rdcarray<ResourceId> &atts = pipestate.GetFramebufferAttachments();
|
|
for(uint32_t i = 0; i < atts.size(); i++)
|
|
{
|
|
ResourceId img = m_pDriver->GetDebugManager()->GetImageViewInfo(atts[i]).image;
|
|
if(img == GetResID(m_Image))
|
|
{
|
|
framebufferIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
ReplayDrawWithTests(cmd, eid, eventFlags, curPipeline, framebufferIndex);
|
|
|
|
m_pDriver->GetCmdRenderState() = m_PrevState;
|
|
m_pDriver->GetCmdRenderState().BindPipeline(m_pDriver, cmd, VulkanRenderState::BindGraphics,
|
|
false);
|
|
}
|
|
|
|
bool PostDraw(uint32_t eid, VkCommandBuffer cmd) { return false; }
|
|
void AliasEvent(uint32_t primary, uint32_t alias)
|
|
{
|
|
// TODO: handle aliased events.
|
|
}
|
|
|
|
void PostRedraw(uint32_t eid, VkCommandBuffer cmd)
|
|
{
|
|
// nothing to do
|
|
}
|
|
|
|
void PreDispatch(uint32_t eid, VkCommandBuffer cmd) {}
|
|
bool PostDispatch(uint32_t eid, VkCommandBuffer cmd) { return false; }
|
|
void PostRedispatch(uint32_t eid, VkCommandBuffer cmd) {}
|
|
void PreMisc(uint32_t eid, DrawFlags flags, VkCommandBuffer cmd) {}
|
|
bool PostMisc(uint32_t eid, DrawFlags flags, VkCommandBuffer cmd) { return false; }
|
|
void PostRemisc(uint32_t eid, DrawFlags flags, VkCommandBuffer cmd) {}
|
|
bool SplitSecondary() { 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)
|
|
{
|
|
}
|
|
void PreEndCommandBuffer(VkCommandBuffer cmd) {}
|
|
uint32_t GetEventFlags(uint32_t eventId)
|
|
{
|
|
auto it = m_EventFlags.find(eventId);
|
|
if(it == m_EventFlags.end())
|
|
RDCERR("Can't find event flags for event %u", eventId);
|
|
return it->second;
|
|
}
|
|
|
|
void FetchOcclusionResults()
|
|
{
|
|
if(m_OcclusionQueries.empty())
|
|
return;
|
|
m_OcclusionResults.resize(m_OcclusionQueries.size());
|
|
VkResult vkr =
|
|
ObjDisp(m_pDriver->GetDev())
|
|
->GetQueryPoolResults(Unwrap(m_pDriver->GetDev()), m_OcclusionPool, 0,
|
|
(uint32_t)m_OcclusionResults.size(), m_OcclusionResults.byteSize(),
|
|
m_OcclusionResults.data(), sizeof(m_OcclusionResults[0]),
|
|
VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
}
|
|
|
|
uint64_t GetOcclusionResult(uint32_t eventId, uint32_t test) const
|
|
{
|
|
auto it = m_OcclusionQueries.find(rdcpair<uint32_t, uint32_t>(eventId, test));
|
|
if(it == m_OcclusionQueries.end())
|
|
RDCERR("Can't locate occlusion query for event id %u and test flags %u", eventId, test);
|
|
if(it->second >= m_OcclusionResults.size())
|
|
RDCERR("Event %u, occlusion index is %u, and the total # of occlusion query data %zu",
|
|
eventId, it->second, m_OcclusionResults.size());
|
|
return m_OcclusionResults[it->second];
|
|
}
|
|
|
|
bool HasEarlyFragments(uint32_t eventId) const
|
|
{
|
|
auto it = m_HasEarlyFragments.find(eventId);
|
|
RDCASSERT(it != m_HasEarlyFragments.end());
|
|
return it->second;
|
|
}
|
|
|
|
private:
|
|
uint32_t CalculateEventFlags(const VulkanCreationInfo::Pipeline &p,
|
|
const VulkanRenderState &pipestate)
|
|
{
|
|
uint32_t flags = 0;
|
|
|
|
// Culling
|
|
{
|
|
if(p.cullMode != VK_CULL_MODE_NONE)
|
|
flags |= TestEnabled_Culling;
|
|
|
|
if(p.cullMode == VK_CULL_MODE_FRONT_AND_BACK)
|
|
flags |= TestMustFail_Culling;
|
|
}
|
|
|
|
// Depth and Stencil tests.
|
|
{
|
|
if(p.depthBoundsEnable)
|
|
flags |= TestEnabled_DepthBounds;
|
|
|
|
if(p.depthTestEnable)
|
|
{
|
|
if(p.depthCompareOp != VK_COMPARE_OP_ALWAYS)
|
|
flags |= TestEnabled_DepthTesting;
|
|
if(p.depthCompareOp == VK_COMPARE_OP_NEVER)
|
|
flags |= TestMustFail_DepthTesting;
|
|
}
|
|
|
|
if(p.stencilTestEnable)
|
|
{
|
|
if(p.front.compareOp != VK_COMPARE_OP_ALWAYS || p.back.compareOp != VK_COMPARE_OP_ALWAYS)
|
|
flags |= TestEnabled_StencilTesting;
|
|
|
|
if(p.front.compareOp == VK_COMPARE_OP_NEVER && p.back.compareOp == VK_COMPARE_OP_NEVER)
|
|
flags |= TestMustFail_StencilTesting;
|
|
else if(p.front.compareOp == VK_COMPARE_OP_NEVER && p.cullMode == VK_CULL_MODE_BACK_BIT)
|
|
flags |= TestMustFail_StencilTesting;
|
|
else if(p.cullMode == VK_CULL_MODE_FRONT_BIT && p.back.compareOp == VK_COMPARE_OP_NEVER)
|
|
flags |= TestMustFail_StencilTesting;
|
|
}
|
|
}
|
|
|
|
// Scissor
|
|
{
|
|
bool inRegion = false;
|
|
bool inAllRegions = true;
|
|
// Do we even need to know viewerport here?
|
|
const VkRect2D *pScissors;
|
|
uint32_t scissorCount;
|
|
if(p.dynamicStates[VkDynamicScissor])
|
|
{
|
|
pScissors = pipestate.scissors.data();
|
|
scissorCount = (uint32_t)pipestate.scissors.size();
|
|
}
|
|
else
|
|
{
|
|
pScissors = p.scissors.data();
|
|
scissorCount = (uint32_t)p.scissors.size();
|
|
}
|
|
for(uint32_t i = 0; i < scissorCount; i++)
|
|
{
|
|
const VkOffset2D &offset = pScissors[i].offset;
|
|
const VkExtent2D &extent = pScissors[i].extent;
|
|
if((m_X >= (uint32_t)offset.x) && (m_Y >= (uint32_t)offset.y) &&
|
|
(m_X < (offset.x + extent.width)) && (m_Y < (offset.y + extent.height)))
|
|
inRegion = true;
|
|
else
|
|
inAllRegions = false;
|
|
}
|
|
if(!inRegion)
|
|
flags |= TestMustFail_Scissor;
|
|
if(inAllRegions)
|
|
flags |= TestMustPass_Scissor;
|
|
}
|
|
|
|
// Blending
|
|
{
|
|
if(m_pDriver->GetDeviceFeatures().independentBlend)
|
|
{
|
|
for(size_t i = 0; i < p.attachments.size(); i++)
|
|
{
|
|
if(p.attachments[i].blendEnable)
|
|
{
|
|
flags |= Blending_Enabled;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Might not have attachments if rasterization is disabled
|
|
if(p.attachments.size() > 0 && p.attachments[0].blendEnable)
|
|
flags |= Blending_Enabled;
|
|
}
|
|
}
|
|
|
|
// Samples
|
|
{
|
|
// TODO: figure out if we always need to check this.
|
|
flags |= TestEnabled_SampleMask;
|
|
|
|
// compare to ms->pSampleMask
|
|
if((p.sampleMask & m_SampleMask) == 0)
|
|
flags |= TestMustFail_SampleMask;
|
|
}
|
|
|
|
// TODO: is shader discard always possible?
|
|
flags |= TestEnabled_FragmentDiscard;
|
|
return flags;
|
|
}
|
|
|
|
// Flags to create a pipeline for tests, can be combined to control how
|
|
// a pipeline is created.
|
|
enum
|
|
{
|
|
PipelineCreationFlags_DisableCulling = 1 << 0,
|
|
PipelineCreationFlags_DisableDepthTest = 1 << 1,
|
|
PipelineCreationFlags_DisableStencilTest = 1 << 2,
|
|
PipelineCreationFlags_DisableDepthBoundsTest = 1 << 3,
|
|
PipelineCreationFlags_FixedColorShader = 1 << 4,
|
|
PipelineCreationFlags_IntersectOriginalScissor = 1 << 5,
|
|
};
|
|
|
|
void ReplayDrawWithTests(VkCommandBuffer cmd, uint32_t eid, uint32_t eventFlags,
|
|
ResourceId basePipeline, uint32_t framebufferIndex)
|
|
{
|
|
// Backface culling
|
|
if(eventFlags & TestMustFail_Culling)
|
|
return;
|
|
|
|
const VulkanCreationInfo::Pipeline &p =
|
|
m_pDriver->GetDebugManager()->GetPipelineInfo(basePipeline);
|
|
EventFlags eventShaderFlags = m_pDriver->GetEventFlags(eid);
|
|
uint32_t numberOfStages = 5;
|
|
rdcarray<VkShaderModule> replacementShaders;
|
|
replacementShaders.resize(numberOfStages);
|
|
// Replace fragment shader because it might have early fragments
|
|
for(size_t i = 0; i < numberOfStages; i++)
|
|
{
|
|
if(p.shaders[i].module == ResourceId())
|
|
continue;
|
|
ShaderStage stage = StageFromIndex(i);
|
|
bool rwInStage = (eventShaderFlags & PipeStageRWEventFlags(stage)) != EventFlags::NoFlags;
|
|
if(rwInStage || (stage == ShaderStage::Fragment))
|
|
replacementShaders[i] =
|
|
m_ShaderCache->GetShaderWithoutSideEffects(p.shaders[i].module, p.shaders[i].entryPoint);
|
|
}
|
|
|
|
bool dynamicScissor = p.dynamicStates[VkDynamicScissor];
|
|
VulkanRenderState &pipestate = m_pDriver->GetCmdRenderState();
|
|
rdcarray<VkRect2D> prevScissors = pipestate.scissors;
|
|
if(dynamicScissor)
|
|
for(uint32_t i = 0; i < pipestate.views.size(); i++)
|
|
ScissorToPixel(pipestate.views[i], pipestate.scissors[i]);
|
|
|
|
if(eventFlags & TestEnabled_Culling)
|
|
{
|
|
uint32_t pipeFlags =
|
|
PipelineCreationFlags_DisableDepthTest | PipelineCreationFlags_DisableDepthBoundsTest |
|
|
PipelineCreationFlags_DisableStencilTest | PipelineCreationFlags_FixedColorShader;
|
|
VkPipeline pipe = CreatePipeline(basePipeline, pipeFlags, dynamicScissor, replacementShaders,
|
|
framebufferIndex);
|
|
ReplayDraw(cmd, pipe, eid, TestEnabled_Culling);
|
|
}
|
|
|
|
// Scissor
|
|
if(eventFlags & TestMustFail_Scissor)
|
|
return;
|
|
|
|
if((eventFlags & (TestEnabled_Scissor | TestMustPass_Scissor)) == TestEnabled_Scissor)
|
|
{
|
|
uint32_t pipeFlags =
|
|
PipelineCreationFlags_IntersectOriginalScissor | PipelineCreationFlags_DisableDepthTest |
|
|
PipelineCreationFlags_DisableDepthBoundsTest | PipelineCreationFlags_DisableStencilTest |
|
|
PipelineCreationFlags_FixedColorShader;
|
|
VkPipeline pipe = CreatePipeline(basePipeline, pipeFlags, dynamicScissor, replacementShaders,
|
|
framebufferIndex);
|
|
// This will change the dynamic scissor state for the later tests, but since those
|
|
// tests happen later in the pipeline, it does not matter.
|
|
if(dynamicScissor)
|
|
for(uint32_t i = 0; i < pipestate.views.size(); i++)
|
|
IntersectScissors(prevScissors[i], pipestate.scissors[i]);
|
|
ReplayDraw(cmd, pipe, eid, TestEnabled_Scissor);
|
|
}
|
|
|
|
// Sample mask
|
|
if(eventFlags & TestMustFail_SampleMask)
|
|
return;
|
|
|
|
if(eventFlags & TestEnabled_SampleMask)
|
|
{
|
|
uint32_t pipeFlags =
|
|
PipelineCreationFlags_DisableDepthBoundsTest | PipelineCreationFlags_DisableStencilTest |
|
|
PipelineCreationFlags_DisableDepthTest | PipelineCreationFlags_FixedColorShader;
|
|
VkPipeline pipe = CreatePipeline(basePipeline, pipeFlags, dynamicScissor, replacementShaders,
|
|
framebufferIndex);
|
|
ReplayDraw(cmd, pipe, eid, TestEnabled_SampleMask);
|
|
}
|
|
|
|
// Depth bounds
|
|
if(eventFlags & TestEnabled_DepthBounds)
|
|
{
|
|
uint32_t pipeFlags = PipelineCreationFlags_DisableStencilTest |
|
|
PipelineCreationFlags_DisableDepthTest |
|
|
PipelineCreationFlags_FixedColorShader;
|
|
VkPipeline pipe = CreatePipeline(basePipeline, pipeFlags, dynamicScissor, replacementShaders,
|
|
framebufferIndex);
|
|
ReplayDraw(cmd, pipe, eid, TestEnabled_DepthBounds);
|
|
}
|
|
|
|
// Stencil test
|
|
if(eventFlags & TestMustFail_StencilTesting)
|
|
return;
|
|
|
|
if(eventFlags & TestEnabled_StencilTesting)
|
|
{
|
|
uint32_t pipeFlags =
|
|
PipelineCreationFlags_DisableDepthTest | PipelineCreationFlags_FixedColorShader;
|
|
VkPipeline pipe = CreatePipeline(basePipeline, pipeFlags, dynamicScissor, replacementShaders,
|
|
framebufferIndex);
|
|
ReplayDraw(cmd, pipe, eid, TestEnabled_StencilTesting);
|
|
}
|
|
|
|
// Depth test
|
|
if(eventFlags & TestMustFail_DepthTesting)
|
|
return;
|
|
|
|
if(eventFlags & TestEnabled_DepthTesting)
|
|
{
|
|
// Previous test might have modified the stencil state, which could
|
|
// cause this event to fail.
|
|
uint32_t pipeFlags =
|
|
PipelineCreationFlags_DisableStencilTest | PipelineCreationFlags_FixedColorShader;
|
|
|
|
VkPipeline pipe = CreatePipeline(basePipeline, pipeFlags, dynamicScissor, replacementShaders,
|
|
framebufferIndex);
|
|
ReplayDraw(cmd, pipe, eid, TestEnabled_DepthTesting);
|
|
}
|
|
|
|
// Shader discard
|
|
if(eventFlags & TestEnabled_FragmentDiscard)
|
|
{
|
|
// With early fragment tests, sample counting (occlusion query) will be done before the shader
|
|
// executes.
|
|
// TODO: remove early fragment tests if it is ON.
|
|
uint32_t pipeFlags = PipelineCreationFlags_DisableDepthBoundsTest |
|
|
PipelineCreationFlags_DisableStencilTest |
|
|
PipelineCreationFlags_DisableDepthTest;
|
|
VkPipeline pipe = CreatePipeline(basePipeline, pipeFlags, dynamicScissor, replacementShaders,
|
|
framebufferIndex);
|
|
ReplayDraw(cmd, pipe, eid, TestEnabled_FragmentDiscard);
|
|
}
|
|
}
|
|
|
|
// Creates a pipeline that is based on the given pipeline and the given
|
|
// pipeline flags. Modifies the base pipeline according to the flags, and
|
|
// leaves the original pipeline behavior if a flag is not set.
|
|
VkPipeline CreatePipeline(ResourceId basePipeline, uint32_t pipeCreateFlags, bool dynamicScissor,
|
|
const rdcarray<VkShaderModule> &replacementShaders,
|
|
uint32_t framebufferIndex)
|
|
{
|
|
rdcpair<ResourceId, uint32_t> pipeKey(basePipeline, pipeCreateFlags);
|
|
auto it = m_PipeCache.find(pipeKey);
|
|
// Check if we processed this pipeline before.
|
|
if(it != m_PipeCache.end())
|
|
return it->second;
|
|
|
|
VkGraphicsPipelineCreateInfo ci = {};
|
|
m_pDriver->GetShaderCache()->MakeGraphicsPipelineInfo(ci, basePipeline);
|
|
VkPipelineRasterizationStateCreateInfo *rs =
|
|
(VkPipelineRasterizationStateCreateInfo *)ci.pRasterizationState;
|
|
VkPipelineDepthStencilStateCreateInfo *ds =
|
|
(VkPipelineDepthStencilStateCreateInfo *)ci.pDepthStencilState;
|
|
VkPipelineViewportStateCreateInfo *vs = (VkPipelineViewportStateCreateInfo *)ci.pViewportState;
|
|
VkPipelineMultisampleStateCreateInfo *ms =
|
|
(VkPipelineMultisampleStateCreateInfo *)ci.pMultisampleState;
|
|
|
|
// Only interested in a single sample.
|
|
ms->pSampleMask = &m_SampleMask;
|
|
// We are going to replay a draw multiple times, don't want to modify the
|
|
// depth value, not to influence later tests.
|
|
ds->depthWriteEnable = VK_FALSE;
|
|
|
|
if(pipeCreateFlags & PipelineCreationFlags_DisableCulling)
|
|
rs->cullMode = VK_CULL_MODE_NONE;
|
|
if(pipeCreateFlags & PipelineCreationFlags_DisableDepthTest)
|
|
ds->depthTestEnable = VK_FALSE;
|
|
if(pipeCreateFlags & PipelineCreationFlags_DisableStencilTest)
|
|
ds->stencilTestEnable = VK_FALSE;
|
|
if(pipeCreateFlags & PipelineCreationFlags_DisableDepthBoundsTest)
|
|
ds->depthBoundsTestEnable = VK_FALSE;
|
|
|
|
rdcarray<VkPipelineShaderStageCreateInfo> stages;
|
|
stages.resize(ci.stageCount);
|
|
memcpy(stages.data(), ci.pStages, stages.byteSize());
|
|
|
|
for(size_t i = 0; i < ci.stageCount; i++)
|
|
{
|
|
if((ci.pStages[i].stage == VK_SHADER_STAGE_FRAGMENT_BIT) &&
|
|
(pipeCreateFlags & PipelineCreationFlags_FixedColorShader))
|
|
{
|
|
stages[i].module = m_ShaderCache->GetFixedColShader(framebufferIndex);
|
|
stages[i].pName = "main";
|
|
}
|
|
else if(replacementShaders[StageIndex(stages[i].stage)] != VK_NULL_HANDLE)
|
|
{
|
|
stages[i].module = replacementShaders[StageIndex(stages[i].stage)];
|
|
}
|
|
}
|
|
ci.pStages = stages.data();
|
|
|
|
if(!dynamicScissor)
|
|
{
|
|
VkRect2D *pScissors = (VkRect2D *)vs->pScissors;
|
|
for(uint32_t i = 0; i < vs->viewportCount; i++)
|
|
{
|
|
ScissorToPixel(vs->pViewports[i], pScissors[i]);
|
|
if(pipeCreateFlags & PipelineCreationFlags_IntersectOriginalScissor)
|
|
IntersectScissors(vs->pScissors[i], pScissors[i]);
|
|
}
|
|
}
|
|
|
|
VkPipeline pipe;
|
|
VkResult vkr = m_pDriver->vkCreateGraphicsPipelines(m_pDriver->GetDev(), VK_NULL_HANDLE, 1, &ci,
|
|
NULL, &pipe);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
m_PipeCache.insert(std::make_pair(pipeKey, pipe));
|
|
return pipe;
|
|
}
|
|
|
|
void ReplayDraw(VkCommandBuffer cmd, VkPipeline pipe, int eventId, uint32_t test)
|
|
{
|
|
m_pDriver->GetCmdRenderState().graphics.pipeline = GetResID(pipe);
|
|
m_pDriver->GetCmdRenderState().BindPipeline(m_pDriver, cmd, VulkanRenderState::BindGraphics,
|
|
false);
|
|
|
|
uint32_t index = (uint32_t)m_OcclusionQueries.size();
|
|
if(m_OcclusionQueries.find(rdcpair<uint32_t, uint32_t>(eventId, test)) != m_OcclusionQueries.end())
|
|
RDCERR("A query already exist for event id %u and test %u", eventId, test);
|
|
m_OcclusionQueries.insert(std::make_pair(rdcpair<uint32_t, uint32_t>(eventId, test), index));
|
|
|
|
ObjDisp(cmd)->CmdBeginQuery(Unwrap(cmd), m_OcclusionPool, index, 0);
|
|
|
|
const DrawcallDescription *drawcall = m_pDriver->GetDrawcall(eventId);
|
|
if(drawcall->flags & DrawFlags::Indexed)
|
|
ObjDisp(cmd)->CmdDrawIndexed(Unwrap(cmd), drawcall->numIndices, drawcall->numInstances,
|
|
drawcall->indexOffset, drawcall->baseVertex,
|
|
drawcall->instanceOffset);
|
|
else
|
|
ObjDisp(cmd)->CmdDraw(Unwrap(cmd), drawcall->numIndices, drawcall->numInstances,
|
|
drawcall->vertexOffset, drawcall->instanceOffset);
|
|
|
|
ObjDisp(cmd)->CmdEndQuery(Unwrap(cmd), m_OcclusionPool, index);
|
|
}
|
|
|
|
VkImage m_Image;
|
|
rdcarray<uint32_t> m_Events;
|
|
// Key is event ID, value is the flags for that event.
|
|
std::map<uint32_t, uint32_t> m_EventFlags;
|
|
// Key is a pair <Base pipeline, pipeline flags>
|
|
std::map<rdcpair<ResourceId, uint32_t>, VkPipeline> m_PipeCache;
|
|
// Key: pair <event ID, test>
|
|
// value: the index where occlusion query is in m_OcclusionResults
|
|
std::map<rdcpair<uint32_t, uint32_t>, uint32_t> m_OcclusionQueries;
|
|
std::map<uint32_t, bool> m_HasEarlyFragments;
|
|
rdcarray<uint64_t> m_OcclusionResults;
|
|
};
|
|
|
|
// Callback used to get values for each fragment.
|
|
struct VulkanPixelHistoryPerFragmentCallback : VulkanPixelHistoryCallback
|
|
{
|
|
VulkanPixelHistoryPerFragmentCallback(WrappedVulkan *vk, PixelHistoryShaderCache *shaderCache,
|
|
uint32_t x, uint32_t y, const Subresource &sub,
|
|
uint32_t sampleMask,
|
|
std::map<uint32_t, uint32_t> eventFragments,
|
|
VkImage originalImage, VkFormat srcFormat, VkBuffer dstBuffer,
|
|
VkImage newImage, VkImageView newImageView, VkImage dsImage,
|
|
VkImageView dsImageView)
|
|
: VulkanPixelHistoryCallback(vk, shaderCache, x, y, sampleMask, VK_NULL_HANDLE),
|
|
m_EventFragments(eventFragments),
|
|
m_Image(originalImage),
|
|
m_SrcFormat(srcFormat),
|
|
m_Subresource(sub),
|
|
m_DstBuffer(dstBuffer),
|
|
m_PerFragmentImage(newImage),
|
|
m_PerFragmentImageView(newImageView),
|
|
m_StencilImage(dsImage),
|
|
m_StencilImageView(dsImageView)
|
|
{
|
|
}
|
|
|
|
~VulkanPixelHistoryPerFragmentCallback()
|
|
{
|
|
for(const VkPipeline &pipe : m_PipesToDestroy)
|
|
m_pDriver->vkDestroyPipeline(m_pDriver->GetDev(), pipe, NULL);
|
|
}
|
|
|
|
struct Pipelines
|
|
{
|
|
// Disable all tests, use the new render pass to render into a separate
|
|
// attachment, and use fragment shader that outputs primitive ID.
|
|
VkPipeline primitiveIdPipe;
|
|
// Turn off blending.
|
|
VkPipeline shaderOutPipe;
|
|
// Enable blending to get post event values.
|
|
VkPipeline postModPipe;
|
|
};
|
|
|
|
void PreDraw(uint32_t eid, VkCommandBuffer cmd)
|
|
{
|
|
if(m_EventFragments.find(eid) == m_EventFragments.end())
|
|
return;
|
|
|
|
VulkanRenderState prevState = m_pDriver->GetCmdRenderState();
|
|
VulkanRenderState &state = m_pDriver->GetCmdRenderState();
|
|
ResourceId curPipeline = state.graphics.pipeline;
|
|
state.EndRenderPass(cmd);
|
|
|
|
uint32_t numFragmentsInEvent = m_EventFragments[eid];
|
|
|
|
VkRenderPass newRp = CreateRenderPass(state.renderPass, state.GetFramebuffer(), state.subpass,
|
|
m_Image, VK_FORMAT_R32G32B32A32_SFLOAT);
|
|
|
|
VkFramebuffer newFb =
|
|
CreateFramebuffer(state.renderPass, newRp, state.subpass, state.GetFramebuffer(),
|
|
m_StencilImageView, GetResID(m_Image), m_PerFragmentImageView);
|
|
|
|
uint32_t framebufferIndex = 0;
|
|
const rdcarray<ResourceId> &atts = prevState.GetFramebufferAttachments();
|
|
for(uint32_t i = 0; i < atts.size(); i++)
|
|
{
|
|
ResourceId img = m_pDriver->GetDebugManager()->GetImageViewInfo(atts[i]).image;
|
|
if(img == GetResID(m_Image))
|
|
{
|
|
framebufferIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
Pipelines pipes = CreatePipelines(curPipeline, newRp, eid, false, 0, framebufferIndex);
|
|
|
|
state.renderPass = GetResID(newRp);
|
|
state.SetFramebuffer(m_pDriver, GetResID(newFb));
|
|
|
|
VkPipeline pipesIter[2];
|
|
pipesIter[0] = pipes.primitiveIdPipe;
|
|
pipesIter[1] = pipes.shaderOutPipe;
|
|
|
|
CopyPixelParams colourCopyParams = {};
|
|
colourCopyParams.multisampled = false; // TODO: multisampled
|
|
colourCopyParams.srcImage = m_PerFragmentImage;
|
|
colourCopyParams.srcImageLayout =
|
|
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; // TODO: image layout
|
|
colourCopyParams.srcImageFormat = VK_FORMAT_R32G32B32A32_SFLOAT;
|
|
colourCopyParams.imageOffset.x = int32_t(m_X);
|
|
colourCopyParams.imageOffset.y = int32_t(m_Y);
|
|
colourCopyParams.imageOffset.z = 0;
|
|
colourCopyParams.dstBuffer = m_DstBuffer;
|
|
colourCopyParams.slice = m_Subresource.slice;
|
|
colourCopyParams.mipLevel = m_Subresource.mip;
|
|
|
|
const VulkanCreationInfo::Pipeline &p =
|
|
m_pDriver->GetDebugManager()->GetPipelineInfo(prevState.graphics.pipeline);
|
|
bool depthEnabled = p.depthTestEnable;
|
|
|
|
// Get primitive ID and shader output value for each fragment.
|
|
for(uint32_t f = 0; f < numFragmentsInEvent; f++)
|
|
{
|
|
for(uint32_t i = 0; i < 2; i++)
|
|
{
|
|
VkImageMemoryBarrier barrier = {
|
|
VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
|
NULL,
|
|
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
|
|
VK_ACCESS_TRANSFER_WRITE_BIT,
|
|
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
|
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
Unwrap(m_StencilImage),
|
|
{VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, 0, 1, 0, 1}};
|
|
|
|
DoPipelineBarrier(cmd, 1, &barrier);
|
|
|
|
// If depth is enabled, clear out the depth value so that the test always
|
|
// passes. Depth writes only work if depth test is enabled.
|
|
// Regardless also need to reset the stencil back to 0.
|
|
VkClearDepthStencilValue dsValue = {};
|
|
dsValue.depth = 1.0f;
|
|
dsValue.stencil = 0;
|
|
VkImageSubresourceRange range = {};
|
|
range.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
|
|
range.baseArrayLayer = 0;
|
|
range.baseMipLevel = 0;
|
|
range.layerCount = 1;
|
|
range.levelCount = 1;
|
|
ObjDisp(cmd)->CmdClearDepthStencilImage(Unwrap(cmd), Unwrap(m_StencilImage),
|
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &dsValue, 1,
|
|
&range);
|
|
|
|
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
|
barrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
|
barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
|
barrier.newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
|
DoPipelineBarrier(cmd, 1, &barrier);
|
|
|
|
m_pDriver->GetCmdRenderState().graphics.pipeline = GetResID(pipesIter[i]);
|
|
|
|
m_pDriver->GetCmdRenderState().BeginRenderPassAndApplyState(
|
|
m_pDriver, cmd, VulkanRenderState::BindGraphics);
|
|
|
|
// Update stencil reference to the current fragment index, so that we get values
|
|
// for a single fragment only.
|
|
ObjDisp(cmd)->CmdSetStencilReference(Unwrap(cmd), VK_STENCIL_FACE_FRONT_AND_BACK, f);
|
|
const DrawcallDescription *drawcall = m_pDriver->GetDrawcall(eid);
|
|
if(drawcall->flags & DrawFlags::Indexed)
|
|
ObjDisp(cmd)->CmdDrawIndexed(Unwrap(cmd), drawcall->numIndices, drawcall->numInstances,
|
|
drawcall->indexOffset, drawcall->baseVertex,
|
|
drawcall->instanceOffset);
|
|
else
|
|
ObjDisp(cmd)->CmdDraw(Unwrap(cmd), drawcall->numIndices, drawcall->numInstances,
|
|
drawcall->vertexOffset, drawcall->instanceOffset);
|
|
state.EndRenderPass(cmd);
|
|
|
|
uint32_t storeOffset = (fragsProcessed + f) * sizeof(PerFragmentInfo);
|
|
if(i == 1)
|
|
{
|
|
storeOffset += offsetof(struct PerFragmentInfo, shaderOut);
|
|
if(depthEnabled)
|
|
{
|
|
CopyPixelParams depthCopyParams = colourCopyParams;
|
|
depthCopyParams.depthCopy = true;
|
|
depthCopyParams.srcImage = m_StencilImage;
|
|
depthCopyParams.srcImageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
|
depthCopyParams.srcImageFormat = VK_FORMAT_D32_SFLOAT_S8_UINT;
|
|
m_pDriver->GetDebugManager()->PixelHistoryCopyPixel(
|
|
cmd, depthCopyParams, storeOffset + offsetof(struct PixelHistoryValue, depth));
|
|
}
|
|
}
|
|
m_pDriver->GetDebugManager()->PixelHistoryCopyPixel(cmd, colourCopyParams, storeOffset);
|
|
}
|
|
}
|
|
|
|
VkImage depthImage = VK_NULL_HANDLE;
|
|
VkFormat depthFormat = VK_FORMAT_UNDEFINED;
|
|
const DrawcallDescription *draw = m_pDriver->GetDrawcall(eid);
|
|
if(draw && draw->depthOut != ResourceId())
|
|
{
|
|
ResourceId resId = m_pDriver->GetResourceManager()->GetLiveID(draw->depthOut);
|
|
depthImage = m_pDriver->GetResourceManager()->GetCurrentHandle<VkImage>(resId);
|
|
const VulkanCreationInfo::Image &imginfo = m_pDriver->GetDebugManager()->GetImageInfo(resId);
|
|
depthFormat = imginfo.format;
|
|
}
|
|
|
|
// Restore the original framebuffer and render pass, so that we can get
|
|
// post modification values.
|
|
state.SetFramebuffer(prevState.GetFramebuffer(), prevState.GetFramebufferAttachments());
|
|
state.renderPass = prevState.renderPass;
|
|
colourCopyParams.srcImage = m_Image;
|
|
colourCopyParams.srcImageFormat = m_SrcFormat;
|
|
|
|
// For every fragment except the last one, retrieve post-modification
|
|
// value.
|
|
for(uint32_t f = 0; f < numFragmentsInEvent - 1; f++)
|
|
{
|
|
// Get post-modification value, use the original framebuffer attachment.
|
|
state.graphics.pipeline = GetResID(pipes.postModPipe);
|
|
state.BeginRenderPassAndApplyState(m_pDriver, cmd, VulkanRenderState::BindGraphics);
|
|
// Have to reset stencil.
|
|
VkClearAttachment att = {};
|
|
att.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT;
|
|
VkClearRect rect = {};
|
|
rect.rect.offset.x = m_X;
|
|
rect.rect.offset.y = m_Y;
|
|
rect.rect.extent.width = 1;
|
|
rect.rect.extent.height = 1;
|
|
rect.baseArrayLayer = 0;
|
|
rect.layerCount = 1;
|
|
ObjDisp(cmd)->CmdClearAttachments(Unwrap(cmd), 1, &att, 1, &rect);
|
|
ObjDisp(cmd)->CmdSetStencilReference(Unwrap(cmd), VK_STENCIL_FACE_FRONT_AND_BACK, f);
|
|
const DrawcallDescription *drawcall = m_pDriver->GetDrawcall(eid);
|
|
if(drawcall->flags & DrawFlags::Indexed)
|
|
ObjDisp(cmd)->CmdDrawIndexed(Unwrap(cmd), drawcall->numIndices, drawcall->numInstances,
|
|
drawcall->indexOffset, drawcall->baseVertex,
|
|
drawcall->instanceOffset);
|
|
else
|
|
ObjDisp(cmd)->CmdDraw(Unwrap(cmd), drawcall->numIndices, drawcall->numInstances,
|
|
drawcall->vertexOffset, drawcall->instanceOffset);
|
|
state.EndRenderPass(cmd);
|
|
|
|
m_pDriver->GetDebugManager()->PixelHistoryCopyPixel(
|
|
cmd, colourCopyParams, (fragsProcessed + f) * sizeof(PerFragmentInfo) +
|
|
offsetof(struct PerFragmentInfo, postMod));
|
|
|
|
if(depthImage != VK_NULL_HANDLE)
|
|
{
|
|
CopyPixelParams depthCopyParams = colourCopyParams;
|
|
depthCopyParams.depthCopy = true;
|
|
depthCopyParams.srcImage = depthImage;
|
|
depthCopyParams.srcImageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
|
depthCopyParams.srcImageFormat = depthFormat;
|
|
m_pDriver->GetDebugManager()->PixelHistoryCopyPixel(
|
|
cmd, depthCopyParams, (fragsProcessed + f) * sizeof(PerFragmentInfo) +
|
|
offsetof(struct PerFragmentInfo, postMod) +
|
|
offsetof(struct PixelHistoryValue, depth));
|
|
}
|
|
}
|
|
|
|
m_EventIndices[eid] = fragsProcessed;
|
|
fragsProcessed += numFragmentsInEvent;
|
|
|
|
m_pDriver->GetCmdRenderState() = prevState;
|
|
m_pDriver->GetCmdRenderState().BeginRenderPassAndApplyState(m_pDriver, cmd,
|
|
VulkanRenderState::BindGraphics);
|
|
}
|
|
bool PostDraw(uint32_t eid, VkCommandBuffer cmd) { return false; }
|
|
void PostRedraw(uint32_t eid, VkCommandBuffer cmd) {}
|
|
// CreatePipelines for getting per fragment information.
|
|
Pipelines CreatePipelines(ResourceId pipe, VkRenderPass rp, uint32_t eid, bool dynamicScissor,
|
|
uint32_t fragmentIndex, uint32_t framebufferIndex)
|
|
{
|
|
const VulkanCreationInfo::Pipeline &p = m_pDriver->GetDebugManager()->GetPipelineInfo(pipe);
|
|
VkGraphicsPipelineCreateInfo pipeCreateInfo = {};
|
|
rdcarray<VkPipelineShaderStageCreateInfo> stages;
|
|
m_pDriver->GetShaderCache()->MakeGraphicsPipelineInfo(pipeCreateInfo, pipe);
|
|
|
|
VkPipelineDepthStencilStateCreateInfo *ds =
|
|
(VkPipelineDepthStencilStateCreateInfo *)pipeCreateInfo.pDepthStencilState;
|
|
VkPipelineMultisampleStateCreateInfo *ms =
|
|
(VkPipelineMultisampleStateCreateInfo *)pipeCreateInfo.pMultisampleState;
|
|
VkPipelineViewportStateCreateInfo *vs =
|
|
(VkPipelineViewportStateCreateInfo *)pipeCreateInfo.pViewportState;
|
|
|
|
VkRect2D newScissors[16];
|
|
memset(newScissors, 0, sizeof(newScissors));
|
|
// Modify the stencil state, so that only one fragment passes.
|
|
{
|
|
ds->stencilTestEnable = VK_TRUE;
|
|
ds->front.compareOp = VK_COMPARE_OP_EQUAL;
|
|
ds->front.failOp = VK_STENCIL_OP_INCREMENT_AND_CLAMP;
|
|
ds->front.passOp = VK_STENCIL_OP_INCREMENT_AND_CLAMP;
|
|
ds->front.depthFailOp = VK_STENCIL_OP_INCREMENT_AND_CLAMP;
|
|
ds->front.compareMask = 0xff;
|
|
ds->front.writeMask = 0xff;
|
|
ds->front.reference = 0;
|
|
ds->back = ds->front;
|
|
|
|
ms->pSampleMask = &m_SampleMask;
|
|
|
|
// Change scissors unless they are set dynamically.
|
|
if(p.dynamicStates[VkDynamicScissor])
|
|
{
|
|
VulkanRenderState &pipestate = m_pDriver->GetCmdRenderState();
|
|
for(uint32_t i = 0; i < pipestate.views.size(); i++)
|
|
ScissorToPixel(pipestate.views[i], pipestate.scissors[i]);
|
|
}
|
|
else
|
|
{
|
|
for(uint32_t i = 0; i < vs->viewportCount; i++)
|
|
{
|
|
ScissorToPixel(vs->pViewports[i], newScissors[i]);
|
|
}
|
|
vs->pScissors = newScissors;
|
|
}
|
|
}
|
|
|
|
// TODO: this is wrong, should take into account subpass.
|
|
pipeCreateInfo.subpass = 0;
|
|
|
|
stages.resize(pipeCreateInfo.stageCount);
|
|
memcpy(stages.data(), pipeCreateInfo.pStages, stages.byteSize());
|
|
|
|
EventFlags eventFlags = m_pDriver->GetEventFlags(eid);
|
|
VkShaderModule replacementShaders[5] = {};
|
|
|
|
// Clean shaders
|
|
uint32_t numberOfStages = 5;
|
|
for(size_t i = 0; i < numberOfStages; i++)
|
|
{
|
|
if((eventFlags & PipeStageRWEventFlags(StageFromIndex(i))) != EventFlags::NoFlags)
|
|
replacementShaders[i] =
|
|
m_ShaderCache->GetShaderWithoutSideEffects(p.shaders[i].module, p.shaders[i].entryPoint);
|
|
}
|
|
for(uint32_t i = 0; i < pipeCreateInfo.stageCount; i++)
|
|
{
|
|
VkShaderModule replacement = replacementShaders[StageIndex(stages[i].stage)];
|
|
if(replacement != VK_NULL_HANDLE)
|
|
stages[i].module = replacement;
|
|
}
|
|
pipeCreateInfo.pStages = stages.data();
|
|
|
|
VkPipelineDynamicStateCreateInfo *dynState =
|
|
(VkPipelineDynamicStateCreateInfo *)pipeCreateInfo.pDynamicState;
|
|
|
|
rdcarray<VkDynamicState> dynamicStates;
|
|
RDCASSERT(dynState != NULL);
|
|
if(!p.dynamicStates[VkDynamicStencilReference])
|
|
{
|
|
dynamicStates.resize(dynState->dynamicStateCount);
|
|
memcpy(dynamicStates.data(), dynState->pDynamicStates, dynamicStates.byteSize());
|
|
dynamicStates.push_back(VK_DYNAMIC_STATE_STENCIL_REFERENCE);
|
|
dynState->dynamicStateCount = (uint32_t)dynamicStates.size();
|
|
dynState->pDynamicStates = dynamicStates.data();
|
|
}
|
|
|
|
Pipelines pipes = {};
|
|
VkResult vkr = m_pDriver->vkCreateGraphicsPipelines(m_pDriver->GetDev(), VK_NULL_HANDLE, 1,
|
|
&pipeCreateInfo, NULL, &pipes.postModPipe);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
m_PipesToDestroy.push_back(pipes.postModPipe);
|
|
|
|
VkPipelineRasterizationStateCreateInfo *rs =
|
|
(VkPipelineRasterizationStateCreateInfo *)pipeCreateInfo.pRasterizationState;
|
|
// Disable some tests, leave depthTest and depthWriteEnable as is.
|
|
// If we disable depth test, depth information would not be written.
|
|
{
|
|
rs->cullMode = VK_CULL_MODE_NONE;
|
|
rs->rasterizerDiscardEnable = VK_FALSE;
|
|
ds->depthBoundsTestEnable = VK_FALSE;
|
|
if(ds->depthTestEnable)
|
|
ds->depthCompareOp = VK_COMPARE_OP_ALWAYS;
|
|
}
|
|
|
|
pipeCreateInfo.renderPass = rp;
|
|
VkPipelineColorBlendStateCreateInfo *cbs =
|
|
(VkPipelineColorBlendStateCreateInfo *)pipeCreateInfo.pColorBlendState;
|
|
// Turn off blending so that we can get shader output values.
|
|
VkPipelineColorBlendAttachmentState *atts =
|
|
(VkPipelineColorBlendAttachmentState *)cbs->pAttachments;
|
|
for(uint32_t i = 0; i < cbs->attachmentCount; i++)
|
|
{
|
|
if(i == framebufferIndex)
|
|
{
|
|
atts[i].blendEnable = 0;
|
|
atts[i].colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
|
|
VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
|
|
}
|
|
else
|
|
{
|
|
atts[i].colorWriteMask = 0;
|
|
}
|
|
}
|
|
|
|
vkr = m_pDriver->vkCreateGraphicsPipelines(m_pDriver->GetDev(), VK_NULL_HANDLE, 1,
|
|
&pipeCreateInfo, NULL, &pipes.shaderOutPipe);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
m_PipesToDestroy.push_back(pipes.shaderOutPipe);
|
|
|
|
{
|
|
ds->depthTestEnable = VK_FALSE;
|
|
ds->depthWriteEnable = VK_FALSE;
|
|
}
|
|
|
|
// Output the primitive ID.
|
|
for(uint32_t i = 0; i < pipeCreateInfo.stageCount; i++)
|
|
{
|
|
if(stages[i].stage == VK_SHADER_STAGE_FRAGMENT_BIT)
|
|
{
|
|
stages[i].module = m_ShaderCache->GetPrimitiveIdShader(framebufferIndex);
|
|
stages[i].pName = "main";
|
|
}
|
|
}
|
|
|
|
vkr = m_pDriver->vkCreateGraphicsPipelines(m_pDriver->GetDev(), VK_NULL_HANDLE, 1,
|
|
&pipeCreateInfo, NULL, &pipes.primitiveIdPipe);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
m_PipesToDestroy.push_back(pipes.primitiveIdPipe);
|
|
|
|
return pipes;
|
|
}
|
|
|
|
void PreDispatch(uint32_t eid, VkCommandBuffer cmd) {}
|
|
bool PostDispatch(uint32_t eid, VkCommandBuffer cmd) { return false; }
|
|
void PostRedispatch(uint32_t eid, VkCommandBuffer cmd) {}
|
|
void PreMisc(uint32_t eid, DrawFlags flags, VkCommandBuffer cmd) {}
|
|
bool PostMisc(uint32_t eid, DrawFlags flags, VkCommandBuffer cmd) { return false; }
|
|
void PostRemisc(uint32_t eid, DrawFlags flags, VkCommandBuffer cmd) {}
|
|
void PreEndCommandBuffer(VkCommandBuffer cmd) {}
|
|
void AliasEvent(uint32_t primary, uint32_t alias) {}
|
|
bool SplitSecondary() { 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)
|
|
{
|
|
}
|
|
|
|
uint32_t GetEventOffset(uint32_t eid)
|
|
{
|
|
auto it = m_EventIndices.find(eid);
|
|
RDCASSERT(it != m_EventIndices.end());
|
|
return it->second;
|
|
}
|
|
|
|
private:
|
|
// For each event, specifies where the occlusion query results start.
|
|
std::map<uint32_t, uint32_t> m_EventIndices;
|
|
// Number of fragments for each event.
|
|
std::map<uint32_t, uint32_t> m_EventFragments;
|
|
// Original image for which pixel history was requested.
|
|
VkImage m_Image;
|
|
VkFormat m_SrcFormat;
|
|
VkBuffer m_DstBuffer;
|
|
// Number of fragments processed so far.
|
|
uint32_t fragsProcessed = 0;
|
|
// These were created with VK_FORMAT_R32G32B32A32_SFLOAT format.
|
|
// The image is cleared between each replay for a fragment, and is
|
|
// used to get shader output value and primitive ID.
|
|
VkImageView m_PerFragmentImageView;
|
|
VkImage m_PerFragmentImage;
|
|
VkImage m_StencilImage;
|
|
VkImageView m_StencilImageView;
|
|
Subresource m_Subresource;
|
|
|
|
rdcarray<VkPipeline> m_PipesToDestroy;
|
|
};
|
|
|
|
// Callback used to determine the shader discard status for each fragment, where
|
|
// an event has multiple fragments with some being discarded in a fragment shader.
|
|
struct VulkanPixelHistoryDiscardedFragmentsCallback : VulkanPixelHistoryCallback
|
|
{
|
|
// Key is event ID and value is a list of primitive IDs
|
|
std::map<uint32_t, rdcarray<int32_t> > m_Events;
|
|
VulkanPixelHistoryDiscardedFragmentsCallback(WrappedVulkan *vk,
|
|
PixelHistoryShaderCache *shaderCache, uint32_t x,
|
|
uint32_t y, uint32_t sampleMask,
|
|
std::map<uint32_t, rdcarray<int32_t> > events,
|
|
VkQueryPool occlusionPool)
|
|
: VulkanPixelHistoryCallback(vk, shaderCache, x, y, sampleMask, occlusionPool),
|
|
m_Events(events)
|
|
{
|
|
}
|
|
|
|
~VulkanPixelHistoryDiscardedFragmentsCallback()
|
|
{
|
|
for(const VkPipeline &pipe : m_PipesToDestroy)
|
|
m_pDriver->vkDestroyPipeline(m_pDriver->GetDev(), pipe, NULL);
|
|
}
|
|
|
|
void PreDraw(uint32_t eid, VkCommandBuffer cmd)
|
|
{
|
|
if(m_Events.find(eid) == m_Events.end())
|
|
return;
|
|
|
|
const rdcarray<int32_t> primIds = m_Events[eid];
|
|
|
|
VulkanRenderState prevState = m_pDriver->GetCmdRenderState();
|
|
VulkanRenderState &state = m_pDriver->GetCmdRenderState();
|
|
// Create a pipeline with a scissor and colorWriteMask = 0, and disable all tests.
|
|
VkPipeline newPipe = CreatePipeline(state.graphics.pipeline, eid);
|
|
state.graphics.pipeline = GetResID(newPipe);
|
|
state.BindPipeline(m_pDriver, cmd, VulkanRenderState::BindGraphics, false);
|
|
for(uint32_t i = 0; i < primIds.size(); i++)
|
|
{
|
|
uint32_t queryId = (uint32_t)m_OcclusionIndices.size();
|
|
ObjDisp(cmd)->CmdBeginQuery(Unwrap(cmd), m_OcclusionPool, queryId, 0);
|
|
const DrawcallDescription *drawcall = m_pDriver->GetDrawcall(eid);
|
|
uint32_t primId = primIds[i];
|
|
// TODO once pixel history distinguishes between instances, draw only the instance for
|
|
// this fragment
|
|
if(drawcall->flags & DrawFlags::Indexed)
|
|
ObjDisp(cmd)->CmdDrawIndexed(
|
|
Unwrap(cmd), RENDERDOC_NumVerticesPerPrimitive(drawcall->topology),
|
|
RDCMAX(1U, drawcall->numInstances),
|
|
drawcall->indexOffset + RENDERDOC_VertexOffset(drawcall->topology, primId),
|
|
drawcall->baseVertex, drawcall->instanceOffset);
|
|
else
|
|
ObjDisp(cmd)->CmdDraw(
|
|
Unwrap(cmd), RENDERDOC_NumVerticesPerPrimitive(drawcall->topology),
|
|
RDCMAX(1U, drawcall->numInstances),
|
|
drawcall->vertexOffset + RENDERDOC_VertexOffset(drawcall->topology, primId),
|
|
drawcall->instanceOffset);
|
|
ObjDisp(cmd)->CmdEndQuery(Unwrap(cmd), m_OcclusionPool, queryId);
|
|
|
|
m_OcclusionIndices[make_rdcpair<uint32_t, uint32_t>(eid, primId)] = queryId;
|
|
}
|
|
m_pDriver->GetCmdRenderState() = prevState;
|
|
m_pDriver->GetCmdRenderState().BindPipeline(m_pDriver, cmd, VulkanRenderState::BindGraphics,
|
|
false);
|
|
}
|
|
|
|
VkPipeline CreatePipeline(ResourceId pipe, uint32_t eid)
|
|
{
|
|
rdcarray<VkPipelineShaderStageCreateInfo> stages;
|
|
VkGraphicsPipelineCreateInfo pipeCreateInfo = {};
|
|
MakeAllPassIncrementStencilPipelineCI(eid, pipe, pipeCreateInfo, stages);
|
|
|
|
VkPipelineDepthStencilStateCreateInfo *ds =
|
|
(VkPipelineDepthStencilStateCreateInfo *)pipeCreateInfo.pDepthStencilState;
|
|
VkPipelineColorBlendStateCreateInfo *cbs =
|
|
(VkPipelineColorBlendStateCreateInfo *)pipeCreateInfo.pColorBlendState;
|
|
|
|
{
|
|
// Disable all tests, but stencil.
|
|
ds->stencilTestEnable = VK_FALSE;
|
|
|
|
VkPipelineColorBlendAttachmentState *atts =
|
|
(VkPipelineColorBlendAttachmentState *)cbs->pAttachments;
|
|
for(uint32_t i = 0; i < cbs->attachmentCount; i++)
|
|
atts[i].colorWriteMask = 0;
|
|
}
|
|
|
|
VkPipeline newPipe;
|
|
VkResult vkr = m_pDriver->vkCreateGraphicsPipelines(m_pDriver->GetDev(), VK_NULL_HANDLE, 1,
|
|
&pipeCreateInfo, NULL, &newPipe);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
m_PipesToDestroy.push_back(newPipe);
|
|
return newPipe;
|
|
}
|
|
|
|
void FetchOcclusionResults()
|
|
{
|
|
m_OcclusionResults.resize(m_OcclusionIndices.size());
|
|
VkResult vkr = ObjDisp(m_pDriver->GetDev())
|
|
->GetQueryPoolResults(Unwrap(m_pDriver->GetDev()), m_OcclusionPool, 0,
|
|
(uint32_t)m_OcclusionIndices.size(),
|
|
m_OcclusionResults.byteSize(),
|
|
m_OcclusionResults.data(), sizeof(uint64_t),
|
|
VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
}
|
|
|
|
bool PrimitiveDiscarded(uint32_t eid, uint32_t primId)
|
|
{
|
|
auto it = m_OcclusionIndices.find(make_rdcpair<uint32_t, uint32_t>(eid, primId));
|
|
if(it == m_OcclusionIndices.end())
|
|
return false;
|
|
return m_OcclusionResults[it->second] == 0;
|
|
}
|
|
|
|
bool PostDraw(uint32_t eid, VkCommandBuffer cmd) { return false; }
|
|
void PostRedraw(uint32_t eid, VkCommandBuffer cmd) {}
|
|
void PreDispatch(uint32_t eid, VkCommandBuffer cmd) {}
|
|
bool PostDispatch(uint32_t eid, VkCommandBuffer cmd) { return false; }
|
|
void PostRedispatch(uint32_t eid, VkCommandBuffer cmd) {}
|
|
void PreMisc(uint32_t eid, DrawFlags flags, VkCommandBuffer cmd) {}
|
|
bool PostMisc(uint32_t eid, DrawFlags flags, VkCommandBuffer cmd) { return false; }
|
|
void PostRemisc(uint32_t eid, DrawFlags flags, VkCommandBuffer cmd) {}
|
|
void PreEndCommandBuffer(VkCommandBuffer cmd) {}
|
|
void AliasEvent(uint32_t primary, uint32_t alias) {}
|
|
bool SplitSecondary() { 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)
|
|
{
|
|
}
|
|
|
|
private:
|
|
std::map<rdcpair<uint32_t, uint32_t>, uint32_t> m_OcclusionIndices;
|
|
rdcarray<uint64_t> m_OcclusionResults;
|
|
|
|
rdcarray<VkPipeline> m_PipesToDestroy;
|
|
};
|
|
|
|
bool VulkanDebugManager::PixelHistorySetupResources(PixelHistoryResources &resources,
|
|
VkExtent3D extent, VkFormat format,
|
|
uint32_t numEvents)
|
|
{
|
|
VkImage colorImage;
|
|
VkImageView colorImageView;
|
|
VkImage stencilImage;
|
|
VkImageView stencilImageView;
|
|
VkDeviceMemory gpuMem;
|
|
|
|
VkBuffer dstBuffer;
|
|
VkDeviceMemory bufferMemory;
|
|
|
|
VkResult vkr;
|
|
VkDevice dev = m_pDriver->GetDev();
|
|
|
|
// Create Images
|
|
VkImageCreateInfo imgInfo = {VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
|
|
imgInfo.imageType = VK_IMAGE_TYPE_2D;
|
|
imgInfo.mipLevels = 1;
|
|
imgInfo.arrayLayers = 1;
|
|
imgInfo.samples = VK_SAMPLE_COUNT_1_BIT;
|
|
imgInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
|
imgInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
|
|
// Device local resources:
|
|
imgInfo.format = VK_FORMAT_R32G32B32A32_SFLOAT;
|
|
imgInfo.extent.width = extent.width;
|
|
imgInfo.extent.height = extent.height;
|
|
imgInfo.extent.depth = 1;
|
|
imgInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
|
imgInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
|
|
|
vkr = m_pDriver->vkCreateImage(dev, &imgInfo, NULL, &colorImage);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
VkImage wrappedColorImage = colorImage;
|
|
ImageState colorImageState = ImageState(wrappedColorImage, ImageInfo(imgInfo), eFrameRef_None);
|
|
|
|
VkMemoryRequirements colorImageMrq = {0};
|
|
m_pDriver->vkGetImageMemoryRequirements(dev, colorImage, &colorImageMrq);
|
|
|
|
imgInfo.format = VK_FORMAT_D32_SFLOAT_S8_UINT;
|
|
imgInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
|
imgInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
|
|
VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
|
|
|
|
vkr = m_pDriver->vkCreateImage(dev, &imgInfo, NULL, &stencilImage);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
VkImage wrappedStencilImage = stencilImage;
|
|
ImageState stencilImageState = ImageState(wrappedStencilImage, ImageInfo(imgInfo), eFrameRef_None);
|
|
|
|
VkMemoryRequirements stencilImageMrq = {0};
|
|
m_pDriver->vkGetImageMemoryRequirements(dev, stencilImage, &stencilImageMrq);
|
|
VkDeviceSize offset = AlignUp(colorImageMrq.size, stencilImageMrq.alignment);
|
|
|
|
VkMemoryAllocateInfo allocInfo = {
|
|
VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, NULL, offset + stencilImageMrq.size,
|
|
m_pDriver->GetGPULocalMemoryIndex(colorImageMrq.memoryTypeBits),
|
|
};
|
|
vkr = m_pDriver->vkAllocateMemory(m_Device, &allocInfo, NULL, &gpuMem);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
vkr = m_pDriver->vkBindImageMemory(m_Device, colorImage, gpuMem, 0);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
vkr = m_pDriver->vkBindImageMemory(m_Device, stencilImage, gpuMem, offset);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
VkImageViewCreateInfo viewInfo = {VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
|
|
viewInfo.image = colorImage;
|
|
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
|
viewInfo.format = VK_FORMAT_R32G32B32A32_SFLOAT;
|
|
viewInfo.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
|
|
|
vkr = m_pDriver->vkCreateImageView(m_Device, &viewInfo, NULL, &colorImageView);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
viewInfo.image = stencilImage;
|
|
viewInfo.format = VK_FORMAT_D32_SFLOAT_S8_UINT;
|
|
viewInfo.subresourceRange = {VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, 0, 1, 0, 1};
|
|
|
|
vkr = m_pDriver->vkCreateImageView(m_Device, &viewInfo, NULL, &stencilImageView);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
VkBufferCreateInfo bufferInfo = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
|
|
bufferInfo.size = AlignUp((uint32_t)(numEvents * sizeof(EventInfo)), 4096U);
|
|
bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
|
|
|
vkr = m_pDriver->vkCreateBuffer(m_Device, &bufferInfo, NULL, &dstBuffer);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
// Allocate memory
|
|
VkMemoryRequirements mrq = {};
|
|
m_pDriver->vkGetBufferMemoryRequirements(m_Device, dstBuffer, &mrq);
|
|
allocInfo.allocationSize = mrq.size;
|
|
allocInfo.memoryTypeIndex = m_pDriver->GetReadbackMemoryIndex(mrq.memoryTypeBits);
|
|
vkr = m_pDriver->vkAllocateMemory(m_Device, &allocInfo, NULL, &bufferMemory);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
vkr = m_pDriver->vkBindBufferMemory(m_Device, dstBuffer, bufferMemory, 0);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
VkCommandBuffer cmd = m_pDriver->GetNextCmd();
|
|
VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, NULL,
|
|
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT};
|
|
|
|
vkr = ObjDisp(dev)->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
ObjDisp(cmd)->CmdFillBuffer(Unwrap(cmd), Unwrap(dstBuffer), 0, VK_WHOLE_SIZE, 0);
|
|
colorImageState.InlineTransition(
|
|
cmd, m_pDriver->m_QueueFamilyIdx, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, 0,
|
|
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, m_pDriver->GetImageTransitionInfo());
|
|
stencilImageState.InlineTransition(
|
|
cmd, m_pDriver->m_QueueFamilyIdx, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, 0,
|
|
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, m_pDriver->GetImageTransitionInfo());
|
|
|
|
vkr = ObjDisp(dev)->EndCommandBuffer(Unwrap(cmd));
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
m_pDriver->SubmitCmds();
|
|
m_pDriver->FlushQ();
|
|
|
|
resources.colorImage = colorImage;
|
|
resources.colorImageView = colorImageView;
|
|
resources.stencilImage = stencilImage;
|
|
resources.stencilImageView = stencilImageView;
|
|
resources.gpuMem = gpuMem;
|
|
|
|
resources.bufferMemory = bufferMemory;
|
|
resources.dstBuffer = dstBuffer;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool VulkanDebugManager::PixelHistoryDestroyResources(const PixelHistoryResources &r)
|
|
{
|
|
VkDevice dev = m_pDriver->GetDev();
|
|
if(r.gpuMem != VK_NULL_HANDLE)
|
|
m_pDriver->vkFreeMemory(dev, r.gpuMem, NULL);
|
|
if(r.colorImage != VK_NULL_HANDLE)
|
|
m_pDriver->vkDestroyImage(dev, r.colorImage, NULL);
|
|
if(r.colorImageView != VK_NULL_HANDLE)
|
|
m_pDriver->vkDestroyImageView(dev, r.colorImageView, NULL);
|
|
if(r.stencilImage != VK_NULL_HANDLE)
|
|
m_pDriver->vkDestroyImage(dev, r.stencilImage, NULL);
|
|
if(r.stencilImageView != VK_NULL_HANDLE)
|
|
m_pDriver->vkDestroyImageView(dev, r.stencilImageView, NULL);
|
|
if(r.dstBuffer != VK_NULL_HANDLE)
|
|
m_pDriver->vkDestroyBuffer(dev, r.dstBuffer, NULL);
|
|
if(r.bufferMemory != VK_NULL_HANDLE)
|
|
m_pDriver->vkFreeMemory(dev, r.bufferMemory, NULL);
|
|
return true;
|
|
}
|
|
|
|
void VulkanDebugManager::PixelHistoryCopyPixel(VkCommandBuffer cmd, CopyPixelParams &p, size_t offset)
|
|
{
|
|
rdcarray<VkBufferImageCopy> regions;
|
|
// Check if depth image includes depth and stencil
|
|
VkImageAspectFlags aspectFlags = 0;
|
|
VkBufferImageCopy region = {};
|
|
region.bufferOffset = (uint64_t)offset;
|
|
region.bufferRowLength = 0;
|
|
region.bufferImageHeight = 0;
|
|
region.imageOffset = p.imageOffset;
|
|
region.imageExtent.width = 1U;
|
|
region.imageExtent.height = 1U;
|
|
region.imageExtent.depth = 1U;
|
|
if(!p.depthCopy)
|
|
{
|
|
region.imageSubresource =
|
|
VkImageSubresourceLayers{VK_IMAGE_ASPECT_COLOR_BIT, p.mipLevel, p.slice, 1};
|
|
regions.push_back(region);
|
|
aspectFlags = VkImageAspectFlags(VK_IMAGE_ASPECT_COLOR_BIT);
|
|
}
|
|
else if(p.stencilOnly)
|
|
{
|
|
region.imageSubresource =
|
|
VkImageSubresourceLayers{VK_IMAGE_ASPECT_STENCIL_BIT, p.mipLevel, p.slice, 1};
|
|
aspectFlags = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
|
|
regions.push_back(region);
|
|
}
|
|
else
|
|
{
|
|
region.imageSubresource =
|
|
VkImageSubresourceLayers{VK_IMAGE_ASPECT_DEPTH_BIT, p.mipLevel, p.slice, 1};
|
|
if(IsDepthOnlyFormat(p.srcImageFormat) || IsDepthAndStencilFormat(p.srcImageFormat))
|
|
{
|
|
regions.push_back(region);
|
|
aspectFlags |= VK_IMAGE_ASPECT_DEPTH_BIT;
|
|
}
|
|
if(IsStencilFormat(p.srcImageFormat))
|
|
{
|
|
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT;
|
|
region.bufferOffset = offset + 4;
|
|
regions.push_back(region);
|
|
aspectFlags |= VK_IMAGE_ASPECT_STENCIL_BIT;
|
|
}
|
|
}
|
|
|
|
VkImageMemoryBarrier barrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
|
NULL,
|
|
VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
|
|
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT |
|
|
VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
|
|
VK_ACCESS_TRANSFER_READ_BIT,
|
|
p.srcImageLayout,
|
|
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
Unwrap(p.srcImage),
|
|
{aspectFlags, 0, 1, 0, 1}};
|
|
|
|
DoPipelineBarrier(cmd, 1, &barrier);
|
|
|
|
ObjDisp(cmd)->CmdCopyImageToBuffer(Unwrap(cmd), Unwrap(p.srcImage),
|
|
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, Unwrap(p.dstBuffer),
|
|
(uint32_t)regions.size(), regions.data());
|
|
|
|
barrier.image = Unwrap(p.srcImage);
|
|
barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
|
barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
|
barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
|
barrier.newLayout = p.srcImageLayout;
|
|
DoPipelineBarrier(cmd, 1, &barrier);
|
|
}
|
|
|
|
void CreateOcclusionPool(WrappedVulkan *vk, uint32_t poolSize, VkQueryPool *pQueryPool)
|
|
{
|
|
VkDevice dev = vk->GetDev();
|
|
VkQueryPoolCreateInfo occlusionPoolCreateInfo = {VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO};
|
|
occlusionPoolCreateInfo.queryType = VK_QUERY_TYPE_OCCLUSION;
|
|
occlusionPoolCreateInfo.queryCount = poolSize;
|
|
// TODO: check that occlusion feature is available
|
|
VkResult vkr =
|
|
ObjDisp(dev)->CreateQueryPool(Unwrap(dev), &occlusionPoolCreateInfo, NULL, pQueryPool);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
VkCommandBuffer cmd = vk->GetNextCmd();
|
|
VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, NULL,
|
|
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT};
|
|
|
|
vkr = ObjDisp(dev)->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
ObjDisp(dev)->CmdResetQueryPool(Unwrap(cmd), *pQueryPool, 0, poolSize);
|
|
vkr = ObjDisp(dev)->EndCommandBuffer(Unwrap(cmd));
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
vk->SubmitCmds();
|
|
vk->FlushQ();
|
|
}
|
|
|
|
VkImageLayout VulkanDebugManager::GetImageLayout(ResourceId image, VkImageAspectFlagBits aspect,
|
|
uint32_t mip, uint32_t slice)
|
|
{
|
|
auto state = m_pDriver->FindConstImageState(image);
|
|
if(!state)
|
|
{
|
|
RDCERR("Could not find image state for %s", ToStr(image).c_str());
|
|
return VK_IMAGE_LAYOUT_UNDEFINED;
|
|
}
|
|
if(state->GetImageInfo().extent.depth > 1)
|
|
return state->GetImageLayout(aspect, mip, 0);
|
|
else
|
|
return state->GetImageLayout(aspect, mip, slice);
|
|
}
|
|
|
|
void UpdateTestsFailed(const TestsFailedCallback *tfCb, uint32_t eventId, uint32_t eventFlags,
|
|
PixelModification &mod)
|
|
{
|
|
bool earlyFragmentTests = tfCb->HasEarlyFragments(eventId);
|
|
|
|
if((eventFlags & (TestEnabled_Culling | TestMustFail_Culling)) == TestEnabled_Culling)
|
|
{
|
|
uint64_t occlData = tfCb->GetOcclusionResult(eventId, TestEnabled_Culling);
|
|
mod.backfaceCulled = (occlData == 0);
|
|
}
|
|
|
|
if(mod.backfaceCulled)
|
|
return;
|
|
|
|
if((eventFlags & (TestEnabled_Scissor | TestMustPass_Scissor | TestMustFail_Scissor)) ==
|
|
TestEnabled_Scissor)
|
|
{
|
|
uint64_t occlData = tfCb->GetOcclusionResult(eventId, TestEnabled_Scissor);
|
|
mod.scissorClipped = (occlData == 0);
|
|
}
|
|
if(mod.scissorClipped)
|
|
return;
|
|
|
|
// TODO: Exclusive Scissor Test if NV extension is turned on.
|
|
|
|
if((eventFlags & (TestEnabled_SampleMask | TestMustFail_SampleMask)) == TestEnabled_SampleMask)
|
|
{
|
|
uint64_t occlData = tfCb->GetOcclusionResult(eventId, TestEnabled_SampleMask);
|
|
mod.sampleMasked = (occlData == 0);
|
|
}
|
|
if(mod.sampleMasked)
|
|
return;
|
|
|
|
// Shader discard with default fragment tests order.
|
|
if(!earlyFragmentTests)
|
|
{
|
|
uint64_t occlData = tfCb->GetOcclusionResult(eventId, TestEnabled_FragmentDiscard);
|
|
mod.shaderDiscarded = (occlData == 0);
|
|
if(mod.shaderDiscarded)
|
|
return;
|
|
}
|
|
|
|
if(eventFlags & TestEnabled_DepthBounds)
|
|
{
|
|
uint64_t occlData = tfCb->GetOcclusionResult(eventId, TestEnabled_DepthBounds);
|
|
mod.depthClipped = (occlData == 0);
|
|
}
|
|
if(mod.depthClipped)
|
|
return;
|
|
|
|
if((eventFlags & (TestEnabled_StencilTesting | TestMustFail_StencilTesting)) ==
|
|
TestEnabled_StencilTesting)
|
|
{
|
|
uint64_t occlData = tfCb->GetOcclusionResult(eventId, TestEnabled_StencilTesting);
|
|
mod.stencilTestFailed = (occlData == 0);
|
|
}
|
|
if(mod.stencilTestFailed)
|
|
return;
|
|
|
|
if((eventFlags & (TestEnabled_DepthTesting | TestMustFail_DepthTesting)) == TestEnabled_DepthTesting)
|
|
{
|
|
uint64_t occlData = tfCb->GetOcclusionResult(eventId, TestEnabled_DepthTesting);
|
|
mod.depthTestFailed = (occlData == 0);
|
|
}
|
|
if(mod.depthTestFailed)
|
|
return;
|
|
|
|
// Shader discard with early fragment tests order.
|
|
if(earlyFragmentTests)
|
|
{
|
|
uint64_t occlData = tfCb->GetOcclusionResult(eventId, TestEnabled_FragmentDiscard);
|
|
mod.shaderDiscarded = (occlData == 0);
|
|
}
|
|
}
|
|
|
|
void FillInColor(ResourceFormat fmt, const PixelHistoryValue &value, ModificationValue &mod)
|
|
{
|
|
FloatVector v4 = ConvertComponents(fmt, value.color);
|
|
memcpy(mod.col.floatValue, &v4.x, sizeof(v4));
|
|
}
|
|
|
|
rdcarray<PixelModification> VulkanReplay::PixelHistory(rdcarray<EventUsage> events,
|
|
ResourceId target, uint32_t x, uint32_t y,
|
|
const Subresource &sub, CompType typeCast)
|
|
{
|
|
if(!GetAPIProperties().pixelHistory)
|
|
{
|
|
VULKANNOTIMP("PixelHistory");
|
|
return rdcarray<PixelModification>();
|
|
}
|
|
|
|
RDCDEBUG("PixelHistory: pixel: (%u, %u) with %u events", x, y, events.size());
|
|
rdcarray<PixelModification> history;
|
|
|
|
if(events.empty())
|
|
return history;
|
|
|
|
const VulkanCreationInfo::Image &imginfo = GetDebugManager()->GetImageInfo(target);
|
|
if(imginfo.format == VK_FORMAT_UNDEFINED)
|
|
return history;
|
|
|
|
uint32_t mip = sub.mip;
|
|
uint32_t slice = sub.slice;
|
|
uint32_t sampleIdx = sub.sample;
|
|
|
|
// TODO: figure out correct aspect.
|
|
VkImageLayout imgLayout =
|
|
GetDebugManager()->GetImageLayout(target, VK_IMAGE_ASPECT_COLOR_BIT, mip, slice);
|
|
RDCASSERTNOTEQUAL(imgLayout, VK_IMAGE_LAYOUT_UNDEFINED);
|
|
|
|
// TODO: use the given type hint for typeless textures
|
|
SCOPED_TIMER("VkDebugManager::PixelHistory");
|
|
|
|
if(sampleIdx > (uint32_t)imginfo.samples)
|
|
sampleIdx = 0;
|
|
|
|
uint32_t sampleMask = ~0U;
|
|
if(sampleIdx < 32)
|
|
sampleMask = 1U << sampleIdx;
|
|
|
|
bool multisampled = (imginfo.samples > 1);
|
|
|
|
if(sampleIdx == ~0U || !multisampled)
|
|
sampleIdx = 0;
|
|
|
|
VkDevice dev = m_pDriver->GetDev();
|
|
VkQueryPool occlusionPool;
|
|
CreateOcclusionPool(m_pDriver, (uint32_t)events.size(), &occlusionPool);
|
|
|
|
PixelHistoryResources resources = {};
|
|
GetDebugManager()->PixelHistorySetupResources(resources, imginfo.extent, imginfo.format,
|
|
(uint32_t)events.size());
|
|
|
|
PixelHistoryShaderCache *shaderCache = new PixelHistoryShaderCache(m_pDriver);
|
|
|
|
VkImage targetImage = GetResourceManager()->GetCurrentHandle<VkImage>(target);
|
|
VulkanOcclusionCallback occlCb(m_pDriver, shaderCache, targetImage, x, y, sampleMask,
|
|
occlusionPool, events);
|
|
m_pDriver->ReplayLog(0, events.back().eventId, eReplay_Full);
|
|
m_pDriver->SubmitCmds();
|
|
m_pDriver->FlushQ();
|
|
occlCb.FetchOcclusionResults();
|
|
|
|
// Gather all draw events that could have written to pixel for another replay pass,
|
|
// to determine if these draws failed for some reason (for ex., depth test).
|
|
rdcarray<uint32_t> modEvents;
|
|
rdcarray<uint32_t> drawEvents;
|
|
for(size_t ev = 0; ev < events.size(); ev++)
|
|
{
|
|
bool clear = (events[ev].usage == ResourceUsage::Clear);
|
|
bool directWrite = isDirectWrite(events[ev].usage);
|
|
|
|
if(events[ev].view != ResourceId())
|
|
{
|
|
// TODO
|
|
}
|
|
|
|
if(directWrite || clear)
|
|
{
|
|
modEvents.push_back(events[ev].eventId);
|
|
}
|
|
else
|
|
{
|
|
uint64_t occlData = occlCb.GetOcclusionResult((uint32_t)events[ev].eventId);
|
|
if(occlData > 0)
|
|
{
|
|
drawEvents.push_back(events[ev].eventId);
|
|
modEvents.push_back(events[ev].eventId);
|
|
}
|
|
}
|
|
}
|
|
|
|
VulkanColorAndStencilCallback cb(m_pDriver, shaderCache, x, y, targetImage, imginfo.format, sub,
|
|
sampleMask, resources.stencilImageView, resources.stencilImage,
|
|
resources.dstBuffer, modEvents);
|
|
m_pDriver->ReplayLog(0, events.back().eventId, eReplay_Full);
|
|
m_pDriver->SubmitCmds();
|
|
m_pDriver->FlushQ();
|
|
|
|
// If there are any draw events, do another replay pass, in order to figure out
|
|
// which tests failed for each draw event.
|
|
TestsFailedCallback *tfCb = NULL;
|
|
if(drawEvents.size() > 0)
|
|
{
|
|
VkQueryPool tfOcclusionPool;
|
|
CreateOcclusionPool(m_pDriver, (uint32_t)drawEvents.size() * 6, &tfOcclusionPool);
|
|
|
|
tfCb = new TestsFailedCallback(m_pDriver, shaderCache, x, y, targetImage, sampleMask,
|
|
tfOcclusionPool, drawEvents);
|
|
m_pDriver->ReplayLog(0, events.back().eventId, eReplay_Full);
|
|
m_pDriver->SubmitCmds();
|
|
m_pDriver->FlushQ();
|
|
tfCb->FetchOcclusionResults();
|
|
ObjDisp(dev)->DestroyQueryPool(Unwrap(dev), tfOcclusionPool, NULL);
|
|
}
|
|
|
|
for(size_t ev = 0; ev < events.size(); ev++)
|
|
{
|
|
uint32_t eventId = events[ev].eventId;
|
|
bool clear = (events[ev].usage == ResourceUsage::Clear);
|
|
bool directWrite = isDirectWrite(events[ev].usage);
|
|
if(drawEvents.contains(events[ev].eventId) || clear || directWrite)
|
|
{
|
|
PixelModification mod;
|
|
RDCEraseEl(mod);
|
|
|
|
mod.eventId = eventId;
|
|
mod.directShaderWrite = directWrite;
|
|
mod.unboundPS = false;
|
|
|
|
if(!clear && !directWrite)
|
|
{
|
|
RDCASSERT(tfCb != NULL);
|
|
uint32_t flags = tfCb->GetEventFlags(eventId);
|
|
if(flags & TestMustFail_Culling)
|
|
mod.backfaceCulled = true;
|
|
if(flags & TestMustFail_DepthTesting)
|
|
mod.depthTestFailed = true;
|
|
if(flags & TestMustFail_Scissor)
|
|
mod.scissorClipped = true;
|
|
if(flags & TestMustFail_SampleMask)
|
|
mod.sampleMasked = true;
|
|
|
|
UpdateTestsFailed(tfCb, eventId, flags, mod);
|
|
}
|
|
history.push_back(mod);
|
|
}
|
|
}
|
|
|
|
// Try to read memory back
|
|
|
|
EventInfo *eventsInfo;
|
|
VkResult vkr =
|
|
m_pDriver->vkMapMemory(dev, resources.bufferMemory, 0, VK_WHOLE_SIZE, 0, (void **)&eventsInfo);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
std::map<uint32_t, uint32_t> eventsWithFrags;
|
|
ResourceFormat fmt = MakeResourceFormat(imginfo.format);
|
|
|
|
for(size_t h = 0; h < history.size();)
|
|
{
|
|
PixelModification &mod = history[h];
|
|
|
|
int32_t eventIndex = cb.GetEventIndex(mod.eventId);
|
|
if(eventIndex == -1)
|
|
{
|
|
// There is no information, skip the event.
|
|
h++;
|
|
continue;
|
|
}
|
|
const EventInfo &ei = eventsInfo[eventIndex];
|
|
FillInColor(fmt, ei.premod, mod.preMod);
|
|
FillInColor(fmt, ei.postmod, mod.postMod);
|
|
mod.preMod.depth = ei.premod.depth.fdepth;
|
|
mod.preMod.stencil = ei.premod.stencil;
|
|
mod.postMod.depth = ei.postmod.depth.fdepth;
|
|
mod.postMod.stencil = ei.postmod.stencil;
|
|
|
|
int32_t frags = int32_t(ei.dsWithoutShaderDiscard[0]);
|
|
int32_t fragsClipped = int32_t(ei.dsWithShaderDiscard[0]);
|
|
mod.shaderOut.col.intValue[0] = frags;
|
|
mod.shaderOut.col.intValue[1] = fragsClipped;
|
|
bool someFragsClipped = (fragsClipped < frags);
|
|
mod.primitiveID = someFragsClipped;
|
|
// Draws in secondary command buffers will fail this check,
|
|
// so nothing else needs to be checked in the callback itself.
|
|
if(frags > 0)
|
|
eventsWithFrags[mod.eventId] = frags;
|
|
|
|
for(int32_t f = 1; f < frags; f++)
|
|
{
|
|
history.insert(h + 1, mod);
|
|
}
|
|
for(int32_t f = 0; f < frags; f++)
|
|
history[h + f].fragIndex = f;
|
|
h += RDCMAX(1, frags);
|
|
RDCDEBUG(
|
|
"PixelHistory event id: %u, fixed shader stencilValue = %u, original shader stencilValue = "
|
|
"%u",
|
|
mod.eventId, ei.dsWithoutShaderDiscard[0], ei.dsWithShaderDiscard[0]);
|
|
}
|
|
m_pDriver->vkUnmapMemory(dev, resources.bufferMemory);
|
|
|
|
if(eventsWithFrags.size() > 0)
|
|
{
|
|
// Replay to get shader output value, post modification value and primitive ID for every
|
|
// fragment.
|
|
VulkanPixelHistoryPerFragmentCallback perFragmentCB(
|
|
m_pDriver, shaderCache, x, y, sub, sampleMask, eventsWithFrags, targetImage, imginfo.format,
|
|
resources.dstBuffer, resources.colorImage, resources.colorImageView, resources.stencilImage,
|
|
resources.stencilImageView);
|
|
m_pDriver->ReplayLog(0, eventsWithFrags.rbegin()->first, eReplay_Full);
|
|
m_pDriver->SubmitCmds();
|
|
m_pDriver->FlushQ();
|
|
|
|
PerFragmentInfo *bp = NULL;
|
|
vkr = m_pDriver->vkMapMemory(dev, resources.bufferMemory, 0, VK_WHOLE_SIZE, 0, (void **)&bp);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
// Retrieve primitive ID values where fragment shader discarded some
|
|
// fragments. For these primitives we are going to perform an occlusion
|
|
// query to see if a primitive was discarded.
|
|
std::map<uint32_t, rdcarray<int32_t> > discardedPrimsEvents;
|
|
uint32_t primitivesToCheck = 0;
|
|
for(size_t h = 0; h < history.size(); h++)
|
|
{
|
|
uint32_t eid = history[h].eventId;
|
|
if(eventsWithFrags.find(eid) == eventsWithFrags.end())
|
|
continue;
|
|
uint32_t f = history[h].fragIndex;
|
|
bool someFragsClipped = (history[h].primitiveID == 1);
|
|
int32_t primId = bp[perFragmentCB.GetEventOffset(eid) + f].primitiveID;
|
|
history[h].primitiveID = primId;
|
|
if(someFragsClipped)
|
|
{
|
|
discardedPrimsEvents[eid].push_back(primId);
|
|
primitivesToCheck++;
|
|
}
|
|
}
|
|
|
|
if(primitivesToCheck > 0)
|
|
{
|
|
VkQueryPool occlPool;
|
|
CreateOcclusionPool(m_pDriver, primitivesToCheck, &occlPool);
|
|
|
|
// Replay to see which primitives were discarded.
|
|
VulkanPixelHistoryDiscardedFragmentsCallback discardedCb(
|
|
m_pDriver, shaderCache, x, y, sampleMask, discardedPrimsEvents, occlPool);
|
|
m_pDriver->ReplayLog(0, eventsWithFrags.rbegin()->first, eReplay_Full);
|
|
m_pDriver->SubmitCmds();
|
|
m_pDriver->FlushQ();
|
|
discardedCb.FetchOcclusionResults();
|
|
ObjDisp(dev)->DestroyQueryPool(Unwrap(dev), occlPool, NULL);
|
|
|
|
for(size_t h = 0; h < history.size(); h++)
|
|
history[h].shaderDiscarded =
|
|
discardedCb.PrimitiveDiscarded(history[h].eventId, history[h].primitiveID);
|
|
}
|
|
|
|
uint32_t discardOffset = 0;
|
|
ResourceFormat resourceFmt = MakeResourceFormat(imginfo.format);
|
|
ResourceFormat shaderOutFormat = MakeResourceFormat(VK_FORMAT_R32G32B32A32_SFLOAT);
|
|
for(size_t h = 0; h < history.size(); h++)
|
|
{
|
|
uint32_t eid = history[h].eventId;
|
|
uint32_t f = history[h].fragIndex;
|
|
// Reset discard offset if this is a new event.
|
|
if(h > 0 && (eid != history[h - 1].eventId))
|
|
discardOffset = 0;
|
|
if(eventsWithFrags.find(eid) != eventsWithFrags.end())
|
|
{
|
|
if(history[h].shaderDiscarded)
|
|
{
|
|
discardOffset++;
|
|
// Copy previous post-mod value if its not the first event
|
|
if(h > 0)
|
|
history[h].postMod = history[h - 1].postMod;
|
|
continue;
|
|
}
|
|
uint32_t offset = perFragmentCB.GetEventOffset(eid) + f - discardOffset;
|
|
FillInColor(shaderOutFormat, bp[offset].shaderOut, history[h].shaderOut);
|
|
history[h].shaderOut.depth = bp[offset].shaderOut.depth.fdepth;
|
|
|
|
if((h < history.size() - 1) && (history[h].eventId == history[h + 1].eventId))
|
|
{
|
|
// Get post-modification value if this is not the last fragment for the event.
|
|
FillInColor(resourceFmt, bp[offset].postMod, history[h].postMod);
|
|
history[h].postMod.depth = bp[offset].postMod.depth.fdepth;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
GetDebugManager()->PixelHistoryDestroyResources(resources);
|
|
ObjDisp(dev)->DestroyQueryPool(Unwrap(dev), occlusionPool, NULL);
|
|
delete shaderCache;
|
|
|
|
return history;
|
|
}
|