mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-05-13 13:30:44 +00:00
3598 lines
140 KiB
C++
3598 lines
140 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.
|
|
******************************************************************************/
|
|
|
|
/*
|
|
* The general algorithm for pixel history is this:
|
|
*
|
|
* we get passed a list of all events that could have touched the target texture
|
|
* We replay all events (up to and including the last event that could have
|
|
* touched the target texture) with a number of callbacks:
|
|
*
|
|
* - First callback: Occlusion callback (VulkanOcclusionCallback)
|
|
* This callback performs an occlusion query around each draw event that was
|
|
* passed in. Execute the draw with a modified pipeline that disables most tests,
|
|
* and uses a fixed color fragment shader, so that we get a non 0 occlusion
|
|
* result even if a test failed for the event.
|
|
*
|
|
* After this callback we collect all events where occlusion result > 0 and all
|
|
* other non-draw events (copy, render pass boundaries, resolve). We also filter
|
|
* out events where the image view used did not overlap in the array layer.
|
|
* The callbacks below will only deal with these events.
|
|
*
|
|
* - Second callback: Color and stencil callback (VulkanColorAndStencilCallback)
|
|
* This callback retrieves color/depth values before and after each event, and
|
|
* uses a stencil increment to count the number of fragments for each event.
|
|
* We have to stop the render pass before and after each event if there is one active,
|
|
* since we can't run vkCmdCopy* or vkCmdExecuteCommands inside a render pass.
|
|
* We then copy colour information and associated depth value, and resume a
|
|
* render pass, if there was one. Before each draw event we also execute the same
|
|
* draw twice with a stencil increment state: 1) with a fixed color fragment shader
|
|
* to count the number of fragments not accounting for shader discard, 2) with the
|
|
* original fragment shader to count the number of fragments accounting for shader
|
|
* discard.
|
|
*
|
|
* - Third callback: Tests failed callback (TestsFailedCallback)
|
|
* This callback is used to determine which tests (culling, depth, stencil, etc)
|
|
* failed (if any) for each draw event. This replays each draw event a number of times
|
|
* with an occlusion query for each test that might have failed (leaves the test
|
|
* under question in the original state, and disables all tests that come after).
|
|
*
|
|
* At this point we retrieve the stencil results that represent the number of fragments,
|
|
* and duplicate events that have multiple fragments.
|
|
*
|
|
* - Fourth callback: Per fragment callback (VulkanPixelHistoryPerFragmentCallback)
|
|
* This callback is used to get per fragment data for each event and fragment (primitive ID,
|
|
* shader output value, post event value for each fragment).
|
|
* For each fragment the draw is replayed 3 times:
|
|
* 1) with a fragment shader that outputs primitive ID only
|
|
* 2) with blending OFF, to get shader output value
|
|
* 3) with blending ON, to get post modification value
|
|
* For each such replay we set the stencil reference to the fragment number and set the
|
|
* stencil compare to equal, so it passes for that particular fragment only.
|
|
*
|
|
* - Fifth callback: Discarded fragments callback (VulkanPixelHistoryDiscardedFragmentsCallback)
|
|
* This callback is used to determine which individual fragments were discarded in a fragment
|
|
* shader.
|
|
* Only runs for the events where the number of fragments accounting for shader discard is less
|
|
* that the number of fragments not accounting for shader discard.
|
|
* This replays the particular fragment (by adjusting parameters in vkCmdDraw* call) with an
|
|
* occlusion query.
|
|
*
|
|
* We slot the per frament data correctly accounting for the fragments that were discarded.
|
|
*
|
|
* Current Limitations:
|
|
*
|
|
* - Multiple subpasses
|
|
* Currently if there are multiple subpasses used in a single render pass, pixel history will
|
|
* return only partial information. This is primarily because current implementation relies
|
|
* on stopping/resuming render passes. This only afects VulkanColorAndStencilCallback and
|
|
* VulkanPixelHistoryPerFragmentCallback callbacks, since they rely on copy operations.
|
|
* To support multiple subpasses we will have to:
|
|
* Create a mirror render pass for each render pass we need to stop, where the all
|
|
* loadOps are set to VK_ATTACHMENT_LOAD_OP_LOAD and store ops are set to
|
|
* VK_ATTACHMENT_STORE_OP_STORE.
|
|
* When we need to stop a render passs, we will repeatedly call vkCmdNextSubpass until we are
|
|
* on the last subpass. When we resume the subpass we will call vkCmdNextSubpass until we reach
|
|
* the original subpass. BeginRenderPassAndApplyState can be extended to provide an option
|
|
* not to override the used render pass (right now overrides with the load RP that has a single
|
|
* subpass).
|
|
*/
|
|
|
|
#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 : uint32_t
|
|
{
|
|
TestEnabled_DepthClipping = 1 << 0,
|
|
TestEnabled_Culling = 1 << 1,
|
|
TestEnabled_Scissor = 1 << 2,
|
|
TestEnabled_SampleMask = 1 << 3,
|
|
TestEnabled_DepthBounds = 1 << 4,
|
|
TestEnabled_StencilTesting = 1 << 5,
|
|
TestEnabled_DepthTesting = 1 << 6,
|
|
TestEnabled_FragmentDiscard = 1 << 7,
|
|
|
|
Blending_Enabled = 1 << 8,
|
|
UnboundFragmentShader = 1 << 9,
|
|
TestMustFail_Culling = 1 << 10,
|
|
TestMustFail_Scissor = 1 << 11,
|
|
TestMustPass_Scissor = 1 << 12,
|
|
TestMustFail_DepthTesting = 1 << 13,
|
|
TestMustFail_StencilTesting = 1 << 14,
|
|
TestMustFail_SampleMask = 1 << 15,
|
|
|
|
DepthTest_Shift = 29,
|
|
DepthTest_Always = 0U << DepthTest_Shift,
|
|
DepthTest_Never = 1U << DepthTest_Shift,
|
|
DepthTest_Equal = 2U << DepthTest_Shift,
|
|
DepthTest_NotEqual = 3U << DepthTest_Shift,
|
|
DepthTest_Less = 4U << DepthTest_Shift,
|
|
DepthTest_LessEqual = 5U << DepthTest_Shift,
|
|
DepthTest_Greater = 6U << DepthTest_Shift,
|
|
DepthTest_GreaterEqual = 7U << DepthTest_Shift,
|
|
};
|
|
|
|
struct CopyPixelParams
|
|
{
|
|
VkImage srcImage;
|
|
VkFormat srcImageFormat;
|
|
VkImageLayout srcImageLayout;
|
|
bool multisampled;
|
|
bool multiview;
|
|
};
|
|
|
|
struct PixelHistoryResources
|
|
{
|
|
VkBuffer dstBuffer;
|
|
VkDeviceMemory bufferMemory;
|
|
|
|
// Used for offscreen rendering for draw call events.
|
|
VkImage colorImage;
|
|
VkImageView colorImageView;
|
|
VkFormat dsFormat;
|
|
VkImage dsImage;
|
|
VkImageView dsImageView;
|
|
VkDeviceMemory gpuMem;
|
|
};
|
|
|
|
struct PixelHistoryCallbackInfo
|
|
{
|
|
// Original image for which pixel history is requested.
|
|
VkImage targetImage;
|
|
// Information about the original target image.
|
|
VkFormat targetImageFormat;
|
|
uint32_t layers;
|
|
uint32_t mipLevels;
|
|
VkSampleCountFlagBits samples;
|
|
VkExtent3D extent;
|
|
// Information about the location of the pixel for which history was requested.
|
|
Subresource targetSubresource;
|
|
uint32_t x;
|
|
uint32_t y;
|
|
uint32_t sampleMask;
|
|
|
|
// Image used to get per fragment data.
|
|
VkImage subImage;
|
|
VkImageView subImageView;
|
|
|
|
// Image used to get stencil counts.
|
|
VkFormat dsFormat;
|
|
VkImage dsImage;
|
|
VkImageView dsImageView;
|
|
|
|
// Buffer used to copy colour and depth information
|
|
VkBuffer dstBuffer;
|
|
};
|
|
|
|
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 + 8];
|
|
};
|
|
|
|
struct EventInfo
|
|
{
|
|
PixelHistoryValue premod;
|
|
PixelHistoryValue postmod;
|
|
uint8_t dsWithoutShaderDiscard[8];
|
|
uint8_t padding[8];
|
|
uint8_t dsWithShaderDiscard[8];
|
|
uint8_t padding1[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,
|
|
const PixelHistoryCallbackInfo &callbackInfo, VkQueryPool occlusionPool)
|
|
: m_pDriver(vk),
|
|
m_ShaderCache(shaderCache),
|
|
m_CallbackInfo(callbackInfo),
|
|
m_OcclusionPool(occlusionPool)
|
|
{
|
|
m_pDriver->SetDrawcallCB(this);
|
|
|
|
if(m_pDriver->GetDeviceEnabledFeatures().occlusionQueryPrecise)
|
|
m_QueryFlags |= VK_QUERY_CONTROL_PRECISE_BIT;
|
|
}
|
|
|
|
virtual ~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);
|
|
for(const VkImageView &imageView : m_ImageViewsToDestroy)
|
|
m_pDriver->vkDestroyImageView(m_pDriver->GetDev(), imageView, NULL);
|
|
m_pDriver->GetReplay()->ResetPixelHistoryDescriptorPool();
|
|
}
|
|
// 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_CallbackInfo.x;
|
|
float fy = (float)m_CallbackInfo.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_CallbackInfo.x;
|
|
scissor.offset.y = m_CallbackInfo.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:
|
|
// MakeIncrementStencilPipelineCI fills in the provided pipeCreateInfo
|
|
// to create a graphics pipeline that is based on the original. The modifications
|
|
// to the original pipeline: disables depth test and write, 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), all color modifications are disabled.
|
|
// Optionally disables other tests like culling, depth bounds.
|
|
void MakeIncrementStencilPipelineCI(uint32_t eid, ResourceId pipe,
|
|
VkGraphicsPipelineCreateInfo &pipeCreateInfo,
|
|
rdcarray<VkPipelineShaderStageCreateInfo> &stages,
|
|
bool disableTests)
|
|
{
|
|
const VulkanCreationInfo::Pipeline &p = m_pDriver->GetDebugManager()->GetPipelineInfo(pipe);
|
|
m_pDriver->GetShaderCache()->MakeGraphicsPipelineInfo(pipeCreateInfo, pipe);
|
|
|
|
// make state we want to control dynamic
|
|
AddDynamicStates(pipeCreateInfo);
|
|
|
|
VkPipelineRasterizationStateCreateInfo *rs =
|
|
(VkPipelineRasterizationStateCreateInfo *)pipeCreateInfo.pRasterizationState;
|
|
VkPipelineDepthStencilStateCreateInfo *ds =
|
|
(VkPipelineDepthStencilStateCreateInfo *)pipeCreateInfo.pDepthStencilState;
|
|
VkPipelineMultisampleStateCreateInfo *ms =
|
|
(VkPipelineMultisampleStateCreateInfo *)pipeCreateInfo.pMultisampleState;
|
|
|
|
// TODO: should leave in the original state.
|
|
ds->depthTestEnable = VK_FALSE;
|
|
ds->depthWriteEnable = VK_FALSE;
|
|
ds->depthBoundsTestEnable = VK_FALSE;
|
|
|
|
if(disableTests)
|
|
{
|
|
rs->cullMode = VK_CULL_MODE_NONE;
|
|
rs->rasterizerDiscardEnable = VK_FALSE;
|
|
if(m_pDriver->GetDeviceEnabledFeatures().depthClamp)
|
|
rs->depthClampEnable = true;
|
|
}
|
|
|
|
// Set up the stencil state.
|
|
{
|
|
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;
|
|
}
|
|
|
|
// Narrow on the specific pixel and sample.
|
|
{
|
|
ms->pSampleMask = &m_CallbackInfo.sampleMask;
|
|
}
|
|
|
|
// Turn off all 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;
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
// ensures the state we want to control is dynamic, whether or not it was dynamic in the original
|
|
// pipeline
|
|
void AddDynamicStates(VkGraphicsPipelineCreateInfo &pipeCreateInfo)
|
|
{
|
|
// we need to add it. Check if the dynamic state is already pointing to our internal array (in
|
|
// the case of adding multiple dynamic states in a row), and otherwise initialise our array from
|
|
// it and repoint. Then we can add the new state
|
|
VkPipelineDynamicStateCreateInfo *dynState =
|
|
(VkPipelineDynamicStateCreateInfo *)pipeCreateInfo.pDynamicState;
|
|
|
|
// copy over the original dynamic states
|
|
m_DynamicStates.assign(dynState->pDynamicStates, dynState->dynamicStateCount);
|
|
|
|
// add the ones we want
|
|
if(!m_DynamicStates.contains(VK_DYNAMIC_STATE_SCISSOR))
|
|
m_DynamicStates.push_back(VK_DYNAMIC_STATE_SCISSOR);
|
|
if(!m_DynamicStates.contains(VK_DYNAMIC_STATE_STENCIL_REFERENCE))
|
|
m_DynamicStates.push_back(VK_DYNAMIC_STATE_STENCIL_REFERENCE);
|
|
if(!m_DynamicStates.contains(VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK))
|
|
m_DynamicStates.push_back(VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK);
|
|
if(!m_DynamicStates.contains(VK_DYNAMIC_STATE_STENCIL_WRITE_MASK))
|
|
m_DynamicStates.push_back(VK_DYNAMIC_STATE_STENCIL_WRITE_MASK);
|
|
|
|
// now point at our storage for the array
|
|
dynState->pDynamicStates = m_DynamicStates.data();
|
|
dynState->dynamicStateCount = (uint32_t)m_DynamicStates.size();
|
|
}
|
|
|
|
// 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 colorIdx attachment.
|
|
VkRenderPass CreateRenderPass(ResourceId rp, bool &multiview,
|
|
VkFormat newColorFormat = VK_FORMAT_UNDEFINED, uint32_t colorIdx = 0)
|
|
{
|
|
const VulkanCreationInfo::RenderPass &rpInfo =
|
|
m_pDriver->GetDebugManager()->GetRenderPassInfo(rp);
|
|
// TODO: this should retrieve the correct subpass, once multiple subpasses
|
|
// are supported.
|
|
// Currently only single subpass render passes are supported.
|
|
const VulkanCreationInfo::RenderPass::Subpass &sub = rpInfo.subpasses.front();
|
|
|
|
// 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 = m_CallbackInfo.dsFormat;
|
|
dsAtt.samples = m_CallbackInfo.samples;
|
|
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.
|
|
if(newColorFormat != VK_FORMAT_UNDEFINED)
|
|
{
|
|
if(colorIdx < descs.size())
|
|
{
|
|
// It is an existing attachment.
|
|
descs[colorIdx].format = newColorFormat;
|
|
}
|
|
else
|
|
{
|
|
// We are adding a new color attachment.
|
|
VkAttachmentReference attRef = {};
|
|
attRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
|
attRef.attachment = colorIdx;
|
|
colorAttachments.push_back(attRef);
|
|
subpassDesc.colorAttachmentCount = (uint32_t)colorAttachments.size();
|
|
subpassDesc.pColorAttachments = colorAttachments.data();
|
|
|
|
VkAttachmentDescription attDesc = {};
|
|
attDesc.format = newColorFormat;
|
|
attDesc.samples = m_CallbackInfo.samples;
|
|
attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
|
attDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
|
attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
|
attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
|
attDesc.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
|
attDesc.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
|
descs.push_back(attDesc);
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
uint32_t multiviewMask = 0;
|
|
|
|
VkRenderPassMultiviewCreateInfo multiviewRP = {
|
|
VK_STRUCTURE_TYPE_RENDER_PASS_MULTIVIEW_CREATE_INFO};
|
|
multiviewRP.correlationMaskCount = 1;
|
|
multiviewRP.pCorrelationMasks = &multiviewMask;
|
|
multiviewRP.subpassCount = 1;
|
|
multiviewRP.pViewMasks = &multiviewMask;
|
|
|
|
multiview = false;
|
|
if(sub.multiviews.size() > 1)
|
|
{
|
|
multiviewMask = 1U << m_CallbackInfo.targetSubresource.slice;
|
|
rpCreateInfo.pNext = &multiviewRP;
|
|
multiview = true;
|
|
}
|
|
|
|
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 original target image view with
|
|
// the newColorAtt.
|
|
VkFramebuffer CreateFramebuffer(ResourceId rp, VkRenderPass newRp, ResourceId origFb,
|
|
VkImageView newColorAtt = VK_NULL_HANDLE, uint32_t colorIdx = 0)
|
|
{
|
|
const VulkanCreationInfo::RenderPass &rpInfo =
|
|
m_pDriver->GetDebugManager()->GetRenderPassInfo(rp);
|
|
// Currently only single subpass render passes are supported.
|
|
const VulkanCreationInfo::RenderPass::Subpass &sub = rpInfo.subpasses.front();
|
|
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);
|
|
}
|
|
|
|
// Either modify the existing color attachment view, or add a new one.
|
|
if(newColorAtt != VK_NULL_HANDLE)
|
|
{
|
|
if(colorIdx < atts.size())
|
|
atts[colorIdx] = newColorAtt;
|
|
else
|
|
atts.push_back(newColorAtt);
|
|
}
|
|
|
|
// Either modify the existing depth stencil attachment, or add one.
|
|
if(sub.depthstencilAttachment != -1)
|
|
atts[sub.depthstencilAttachment] = m_CallbackInfo.dsImageView;
|
|
else
|
|
atts.push_back(m_CallbackInfo.dsImageView);
|
|
|
|
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;
|
|
}
|
|
|
|
VkDescriptorSet GetCopyDescriptor(VkImage image, VkFormat format, uint32_t baseMip,
|
|
uint32_t baseSlice)
|
|
{
|
|
auto it = m_CopyDescriptors.find(image);
|
|
if(it != m_CopyDescriptors.end())
|
|
return it->second;
|
|
|
|
VkImageViewCreateInfo viewInfo = {VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
|
|
viewInfo.image = image;
|
|
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
|
|
viewInfo.format = format;
|
|
viewInfo.subresourceRange = {0, baseMip, 1, baseSlice, 1};
|
|
|
|
if(IsDepthOrStencilFormat(format))
|
|
{
|
|
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
|
}
|
|
else
|
|
{
|
|
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
uint32_t bs = GetByteSize(1, 1, 1, format, 0);
|
|
|
|
if(bs == 1)
|
|
viewInfo.format = VK_FORMAT_R8_UINT;
|
|
else if(bs == 2)
|
|
viewInfo.format = VK_FORMAT_R16_UINT;
|
|
else if(bs == 4)
|
|
viewInfo.format = VK_FORMAT_R32_UINT;
|
|
else if(bs == 8)
|
|
viewInfo.format = VK_FORMAT_R32G32_UINT;
|
|
else if(bs == 16)
|
|
viewInfo.format = VK_FORMAT_R32G32B32A32_UINT;
|
|
}
|
|
|
|
VkImageView imageView;
|
|
VkResult vkr = m_pDriver->vkCreateImageView(m_pDriver->GetDev(), &viewInfo, NULL, &imageView);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
m_ImageViewsToDestroy.push_back(imageView);
|
|
|
|
VkImageView imageView2 = VK_NULL_HANDLE;
|
|
if(IsStencilFormat(format))
|
|
{
|
|
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT;
|
|
vkr = m_pDriver->vkCreateImageView(m_pDriver->GetDev(), &viewInfo, NULL, &imageView2);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
m_ImageViewsToDestroy.push_back(imageView2);
|
|
}
|
|
|
|
VkDescriptorSet descSet = m_pDriver->GetReplay()->GetPixelHistoryDescriptor();
|
|
m_pDriver->GetReplay()->UpdatePixelHistoryDescriptor(descSet, m_CallbackInfo.dstBuffer,
|
|
imageView, imageView2);
|
|
m_CopyDescriptors.insert(std::make_pair(image, descSet));
|
|
return descSet;
|
|
}
|
|
|
|
void CopyImagePixel(VkCommandBuffer cmd, CopyPixelParams &p, size_t offset)
|
|
{
|
|
VkImageAspectFlags aspectFlags = 0;
|
|
bool depthCopy = IsDepthOrStencilFormat(p.srcImageFormat);
|
|
if(depthCopy)
|
|
{
|
|
if(IsDepthOnlyFormat(p.srcImageFormat) || IsDepthAndStencilFormat(p.srcImageFormat))
|
|
aspectFlags |= VK_IMAGE_ASPECT_DEPTH_BIT;
|
|
if(IsStencilFormat(p.srcImageFormat))
|
|
aspectFlags |= VK_IMAGE_ASPECT_STENCIL_BIT;
|
|
}
|
|
else
|
|
{
|
|
aspectFlags = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
}
|
|
|
|
uint32_t baseMip = m_CallbackInfo.targetSubresource.mip;
|
|
uint32_t baseSlice = m_CallbackInfo.targetSubresource.slice;
|
|
// The images that are created specifically for evaluating pixel history are
|
|
// already based on the target mip/slice. Unless we're using multiview, in which case the output
|
|
// is still view-dependent.
|
|
if(!p.multiview &&
|
|
((p.srcImage == m_CallbackInfo.subImage) || (p.srcImage == m_CallbackInfo.dsImage)))
|
|
{
|
|
baseMip = 0;
|
|
baseSlice = 0;
|
|
}
|
|
// For pipeline barriers.
|
|
VkImageSubresourceRange subresource = {aspectFlags, baseMip, 1, baseSlice, 1};
|
|
|
|
// For multi-sampled images can't call vkCmdCopyImageToBuffer directly,
|
|
// copy using a compute shader into a staging image first.
|
|
if(p.multisampled)
|
|
{
|
|
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_SHADER_READ_BIT, p.srcImageLayout, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
|
VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, Unwrap(p.srcImage), subresource};
|
|
SanitiseOldImageLayout(barrier.oldLayout);
|
|
VkDescriptorSet descSet = GetCopyDescriptor(p.srcImage, p.srcImageFormat, baseMip, baseSlice);
|
|
|
|
// Transition src image to SHADER_READ_ONLY_OPTIMAL.
|
|
DoPipelineBarrier(cmd, 1, &barrier);
|
|
|
|
m_pDriver->GetReplay()->CopyPixelForPixelHistory(
|
|
cmd, {(int32_t)m_CallbackInfo.x, (int32_t)m_CallbackInfo.y},
|
|
m_CallbackInfo.targetSubresource.sample, (uint32_t)offset / 16, p.srcImageFormat, descSet);
|
|
|
|
// Transition src image back to its layout.
|
|
barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
|
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
|
barrier.oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
|
barrier.newLayout = p.srcImageLayout;
|
|
SanitiseNewImageLayout(barrier.newLayout);
|
|
|
|
DoPipelineBarrier(cmd, 1, &barrier);
|
|
}
|
|
else
|
|
{
|
|
rdcarray<VkBufferImageCopy> regions;
|
|
VkBufferImageCopy region = {};
|
|
region.bufferOffset = (uint64_t)offset;
|
|
region.bufferRowLength = 0;
|
|
region.bufferImageHeight = 0;
|
|
region.imageOffset.x = m_CallbackInfo.x;
|
|
region.imageOffset.y = m_CallbackInfo.y;
|
|
region.imageOffset.z = 0;
|
|
region.imageExtent.width = 1U;
|
|
region.imageExtent.height = 1U;
|
|
region.imageExtent.depth = 1U;
|
|
region.imageSubresource.baseArrayLayer = baseSlice;
|
|
region.imageSubresource.mipLevel = baseMip;
|
|
region.imageSubresource.layerCount = 1;
|
|
|
|
if(!depthCopy)
|
|
{
|
|
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
regions.push_back(region);
|
|
}
|
|
else
|
|
{
|
|
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
|
if(IsDepthOnlyFormat(p.srcImageFormat) || IsDepthAndStencilFormat(p.srcImageFormat))
|
|
{
|
|
regions.push_back(region);
|
|
}
|
|
if(IsStencilFormat(p.srcImageFormat))
|
|
{
|
|
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT;
|
|
region.bufferOffset = offset + 4;
|
|
regions.push_back(region);
|
|
}
|
|
}
|
|
|
|
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), subresource};
|
|
SanitiseOldImageLayout(barrier.oldLayout);
|
|
DoPipelineBarrier(cmd, 1, &barrier);
|
|
|
|
ObjDisp(cmd)->CmdCopyImageToBuffer(
|
|
Unwrap(cmd), Unwrap(p.srcImage), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
|
Unwrap(m_CallbackInfo.dstBuffer), (uint32_t)regions.size(), regions.data());
|
|
|
|
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;
|
|
SanitiseNewImageLayout(barrier.newLayout);
|
|
DoPipelineBarrier(cmd, 1, &barrier);
|
|
}
|
|
}
|
|
|
|
bool HasMultipleSubpasses()
|
|
{
|
|
ResourceId rp = m_pDriver->GetCmdRenderState().renderPass;
|
|
if(rp == ResourceId())
|
|
return false;
|
|
const VulkanCreationInfo::RenderPass &rpInfo =
|
|
m_pDriver->GetDebugManager()->GetRenderPassInfo(rp);
|
|
return (rpInfo.subpasses.size() > 1);
|
|
}
|
|
|
|
// Returns teh color attachment index that corresponds to the target image for
|
|
// pixel history.
|
|
uint32_t GetColorAttachmentIndex(const VulkanRenderState &renderstate)
|
|
{
|
|
if(IsDepthOrStencilFormat(m_CallbackInfo.targetImageFormat))
|
|
return 0;
|
|
|
|
uint32_t framebufferIndex = 0;
|
|
const rdcarray<ResourceId> &atts = renderstate.GetFramebufferAttachments();
|
|
|
|
for(uint32_t i = 0; i < atts.size(); i++)
|
|
{
|
|
ResourceId img = m_pDriver->GetDebugManager()->GetImageViewInfo(atts[i]).image;
|
|
if(img == GetResID(m_CallbackInfo.targetImage))
|
|
{
|
|
framebufferIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
const VulkanCreationInfo::RenderPass &rpInfo =
|
|
m_pDriver->GetDebugManager()->GetRenderPassInfo(renderstate.renderPass);
|
|
const VulkanCreationInfo::RenderPass::Subpass &sub = rpInfo.subpasses.front();
|
|
for(uint32_t i = 0; i < sub.colorAttachments.size(); i++)
|
|
{
|
|
if(framebufferIndex == sub.colorAttachments[i])
|
|
return i;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
WrappedVulkan *m_pDriver;
|
|
VkQueryControlFlags m_QueryFlags = 0;
|
|
PixelHistoryShaderCache *m_ShaderCache;
|
|
PixelHistoryCallbackInfo m_CallbackInfo;
|
|
VkQueryPool m_OcclusionPool;
|
|
rdcarray<VkRenderPass> m_RpsToDestroy;
|
|
rdcarray<VkFramebuffer> m_FbsToDestroy;
|
|
rdcarray<VkDynamicState> m_DynamicStates;
|
|
std::map<VkImage, VkDescriptorSet> m_CopyDescriptors;
|
|
rdcarray<VkImageView> m_ImageViewsToDestroy;
|
|
};
|
|
|
|
// 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,
|
|
const PixelHistoryCallbackInfo &callbackInfo, VkQueryPool occlusionPool,
|
|
const rdcarray<EventUsage> &allEvents)
|
|
: VulkanPixelHistoryCallback(vk, shaderCache, callbackInfo, occlusionPool)
|
|
{
|
|
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();
|
|
|
|
VkPipeline pipe = GetPixelOcclusionPipeline(eid, prevState.graphics.pipeline,
|
|
GetColorAttachmentIndex(prevState));
|
|
// set the scissor
|
|
for(uint32_t i = 0; i < pipestate.views.size(); i++)
|
|
ScissorToPixel(pipestate.views[i], pipestate.scissors[i]);
|
|
// set stencil state (though it's unused here)
|
|
pipestate.front.compare = pipestate.front.write = 0xff;
|
|
pipestate.front.ref = 0;
|
|
pipestate.back = pipestate.front;
|
|
pipestate.graphics.pipeline = GetResID(pipe);
|
|
ReplayDrawWithQuery(cmd, eid);
|
|
|
|
m_pDriver->GetCmdRenderState() = prevState;
|
|
m_pDriver->GetCmdRenderState().BindPipeline(m_pDriver, cmd, VulkanRenderState::BindGraphics,
|
|
false);
|
|
}
|
|
|
|
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,
|
|
false);
|
|
|
|
uint32_t occlIndex = (uint32_t)m_OcclusionQueries.size();
|
|
ObjDisp(cmd)->CmdBeginQuery(Unwrap(cmd), m_OcclusionPool, occlIndex, m_QueryFlags);
|
|
|
|
m_pDriver->ReplayDraw(cmd, *drawcall);
|
|
|
|
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 outputIndex)
|
|
{
|
|
auto it = m_PipeCache.find(pipeline);
|
|
if(it != m_PipeCache.end())
|
|
return it->second;
|
|
|
|
VkGraphicsPipelineCreateInfo pipeCreateInfo = {};
|
|
rdcarray<VkPipelineShaderStageCreateInfo> stages;
|
|
MakeIncrementStencilPipelineCI(eid, pipeline, pipeCreateInfo, stages, true);
|
|
|
|
for(uint32_t i = 0; i < pipeCreateInfo.stageCount; i++)
|
|
{
|
|
if(stages[i].stage == VK_SHADER_STAGE_FRAGMENT_BIT)
|
|
{
|
|
stages[i].module = m_ShaderCache->GetFixedColShader(outputIndex);
|
|
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:
|
|
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,
|
|
const PixelHistoryCallbackInfo &callbackInfo,
|
|
const rdcarray<uint32_t> &events)
|
|
: VulkanPixelHistoryCallback(vk, shaderCache, callbackInfo, VK_NULL_HANDLE),
|
|
m_Events(events),
|
|
multipleSubpassWarningPrinted(false)
|
|
{
|
|
}
|
|
|
|
~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;
|
|
|
|
if(HasMultipleSubpasses())
|
|
{
|
|
if(!multipleSubpassWarningPrinted)
|
|
{
|
|
RDCWARN("Multiple subpasses in a render pass are not supported for pixel history.");
|
|
multipleSubpassWarningPrinted = true;
|
|
}
|
|
return;
|
|
}
|
|
|
|
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);
|
|
|
|
CopyPixel(eid, cmd, storeOffset);
|
|
|
|
ResourceId prevRenderpass = pipestate.renderPass;
|
|
ResourceId prevFramebuffer = pipestate.GetFramebuffer();
|
|
rdcarray<ResourceId> prevFBattachments = pipestate.GetFramebufferAttachments();
|
|
|
|
uint32_t prevSubpass = pipestate.subpass;
|
|
|
|
{
|
|
bool multiview = false;
|
|
VkRenderPass newRp = CreateRenderPass(pipestate.renderPass, multiview);
|
|
VkFramebuffer newFb =
|
|
CreateFramebuffer(pipestate.renderPass, newRp, pipestate.GetFramebuffer());
|
|
|
|
PipelineReplacements replacements = GetPipelineReplacements(
|
|
eid, pipestate.graphics.pipeline, newRp, GetColorAttachmentIndex(prevState));
|
|
|
|
for(uint32_t i = 0; i < pipestate.views.size(); i++)
|
|
ScissorToPixel(pipestate.views[i], pipestate.scissors[i]);
|
|
|
|
// TODO: should fill depth value from the original DS attachment.
|
|
|
|
// 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.graphics.pipeline = GetResID(replacements.fixedShaderStencil);
|
|
pipestate.front.compare = pipestate.front.write = 0xff;
|
|
pipestate.front.ref = 0;
|
|
pipestate.back = pipestate.front;
|
|
ReplayDraw(cmd, eid, true);
|
|
|
|
CopyPixelParams params = {};
|
|
params.srcImage = m_CallbackInfo.dsImage;
|
|
params.srcImageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
|
params.srcImageFormat = m_CallbackInfo.dsFormat;
|
|
params.multisampled = (m_CallbackInfo.samples != VK_SAMPLE_COUNT_1_BIT);
|
|
params.multiview = multiview;
|
|
// Copy stencil value that indicates the number of fragments ignoring
|
|
// shader discard.
|
|
CopyImagePixel(cmd, params, storeOffset + offsetof(struct EventInfo, dsWithoutShaderDiscard));
|
|
|
|
// TODO: in between reset the depth value.
|
|
|
|
// 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);
|
|
|
|
CopyImagePixel(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;
|
|
|
|
if(HasMultipleSubpasses())
|
|
{
|
|
if(!multipleSubpassWarningPrinted)
|
|
{
|
|
RDCWARN("Multiple subpasses in a render pass are not supported for pixel history.");
|
|
multipleSubpassWarningPrinted = true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
m_pDriver->GetCmdRenderState().EndRenderPass(cmd);
|
|
|
|
size_t storeOffset = m_EventIndices.size() * sizeof(EventInfo);
|
|
|
|
CopyPixel(eid, 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;
|
|
|
|
if(HasMultipleSubpasses())
|
|
{
|
|
if(!multipleSubpassWarningPrinted)
|
|
{
|
|
RDCWARN("Multiple subpasses in a render pass are not supported for pixel history.");
|
|
multipleSubpassWarningPrinted = true;
|
|
}
|
|
return;
|
|
}
|
|
|
|
m_pDriver->GetCmdRenderState().EndRenderPass(cmd);
|
|
|
|
// Copy
|
|
size_t storeOffset = m_EventIndices.size() * sizeof(EventInfo);
|
|
CopyPixel(eventId, 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;
|
|
|
|
if(HasMultipleSubpasses())
|
|
{
|
|
if(!multipleSubpassWarningPrinted)
|
|
{
|
|
RDCWARN("Multiple subpasses in a render pass are not supported for pixel history.");
|
|
multipleSubpassWarningPrinted = true;
|
|
}
|
|
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(eventId, 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(eid, 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(eid, 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)
|
|
{
|
|
if(!m_Events.contains(eid))
|
|
return;
|
|
if(HasMultipleSubpasses())
|
|
{
|
|
if(!multipleSubpassWarningPrinted)
|
|
{
|
|
RDCWARN("Multiple subpasses in a render pass are not supported for pixel history.");
|
|
multipleSubpassWarningPrinted = true;
|
|
}
|
|
return;
|
|
}
|
|
PreDispatch(eid, cmd);
|
|
}
|
|
bool PostMisc(uint32_t eid, DrawFlags flags, VkCommandBuffer cmd)
|
|
{
|
|
if(!m_Events.contains(eid))
|
|
return false;
|
|
if(HasMultipleSubpasses())
|
|
{
|
|
if(!multipleSubpassWarningPrinted)
|
|
{
|
|
RDCWARN("Multiple subpasses in a render pass are not supported for pixel history.");
|
|
multipleSubpassWarningPrinted = true;
|
|
}
|
|
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;
|
|
}
|
|
|
|
VkFormat GetDepthFormat(uint32_t eventId)
|
|
{
|
|
if(IsDepthOrStencilFormat(m_CallbackInfo.targetImageFormat))
|
|
return m_CallbackInfo.targetImageFormat;
|
|
auto it = m_DepthFormats.find(eventId);
|
|
if(it == m_DepthFormats.end())
|
|
return VK_FORMAT_UNDEFINED;
|
|
return it->second;
|
|
}
|
|
|
|
private:
|
|
void CopyPixel(uint32_t eid, VkCommandBuffer cmd, size_t offset)
|
|
{
|
|
CopyPixelParams targetCopyParams = {};
|
|
targetCopyParams.srcImage = m_CallbackInfo.targetImage;
|
|
targetCopyParams.srcImageFormat = m_CallbackInfo.targetImageFormat;
|
|
targetCopyParams.multisampled = (m_CallbackInfo.samples != VK_SAMPLE_COUNT_1_BIT);
|
|
VkImageAspectFlagBits aspect = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
if(IsDepthOrStencilFormat(m_CallbackInfo.targetImageFormat))
|
|
{
|
|
offset += offsetof(struct PixelHistoryValue, depth);
|
|
aspect = VK_IMAGE_ASPECT_DEPTH_BIT;
|
|
}
|
|
targetCopyParams.srcImageLayout = m_pDriver->GetDebugManager()->GetImageLayout(
|
|
GetResID(m_CallbackInfo.targetImage), aspect, m_CallbackInfo.targetSubresource.mip,
|
|
m_CallbackInfo.targetSubresource.slice);
|
|
CopyImagePixel(cmd, targetCopyParams, offset);
|
|
|
|
// If the target image is a depth/stencil attachment, we already
|
|
// copied the value above.
|
|
if(IsDepthOrStencilFormat(m_CallbackInfo.targetImageFormat))
|
|
return;
|
|
|
|
const DrawcallDescription *draw = m_pDriver->GetDrawcall(eid);
|
|
if(draw && draw->depthOut != ResourceId())
|
|
{
|
|
ResourceId resId = m_pDriver->GetResourceManager()->GetLiveID(draw->depthOut);
|
|
VkImage depthImage = m_pDriver->GetResourceManager()->GetCurrentHandle<VkImage>(resId);
|
|
const VulkanCreationInfo::Image &imginfo = m_pDriver->GetDebugManager()->GetImageInfo(resId);
|
|
CopyPixelParams depthCopyParams = targetCopyParams;
|
|
depthCopyParams.srcImage = depthImage;
|
|
depthCopyParams.srcImageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
|
depthCopyParams.srcImageFormat = imginfo.format;
|
|
depthCopyParams.multisampled = (imginfo.samples != VK_SAMPLE_COUNT_1_BIT);
|
|
CopyImagePixel(cmd, depthCopyParams, offset + offsetof(struct PixelHistoryValue, depth));
|
|
m_DepthFormats.insert(std::make_pair(eid, imginfo.format));
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
|
|
ObjDisp(cmd)->CmdSetStencilCompareMask(Unwrap(cmd), VK_STENCIL_FACE_FRONT_AND_BACK, 0xff);
|
|
ObjDisp(cmd)->CmdSetStencilWriteMask(Unwrap(cmd), VK_STENCIL_FACE_FRONT_AND_BACK, 0xff);
|
|
|
|
if(clear)
|
|
{
|
|
VkClearAttachment att = {};
|
|
att.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT;
|
|
VkClearRect rect = {};
|
|
rect.rect.offset.x = m_CallbackInfo.x;
|
|
rect.rect.offset.y = m_CallbackInfo.y;
|
|
rect.rect.extent.width = 1;
|
|
rect.rect.extent.height = 1;
|
|
rect.baseArrayLayer = 0;
|
|
rect.layerCount = m_CallbackInfo.layers;
|
|
ObjDisp(cmd)->CmdClearAttachments(Unwrap(cmd), 1, &att, 1, &rect);
|
|
}
|
|
|
|
const DrawcallDescription *drawcall = m_pDriver->GetDrawcall(eventId);
|
|
m_pDriver->ReplayDraw(cmd, *drawcall);
|
|
|
|
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 outputIndex)
|
|
{
|
|
// 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;
|
|
MakeIncrementStencilPipelineCI(eid, pipeline, pipeCreateInfo, stages, false);
|
|
// No need to change depth stencil state, it is already
|
|
// set to always pass, and increment.
|
|
pipeCreateInfo.renderPass = rp;
|
|
|
|
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(outputIndex);
|
|
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;
|
|
}
|
|
|
|
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;
|
|
bool multipleSubpassWarningPrinted;
|
|
std::map<uint32_t, VkFormat> m_DepthFormats;
|
|
};
|
|
|
|
// TestsFailedCallback replays draws to figure out which tests failed (for ex., depth,
|
|
// stencil test etc).
|
|
struct TestsFailedCallback : public VulkanPixelHistoryCallback
|
|
{
|
|
TestsFailedCallback(WrappedVulkan *vk, PixelHistoryShaderCache *shaderCache,
|
|
const PixelHistoryCallbackInfo &callbackInfo, VkQueryPool occlusionPool,
|
|
rdcarray<uint32_t> events)
|
|
: VulkanPixelHistoryCallback(vk, shaderCache, callbackInfo, occlusionPool), 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 prevState = m_pDriver->GetCmdRenderState();
|
|
|
|
ReplayDrawWithTests(cmd, eid, eventFlags, curPipeline, GetColorAttachmentIndex(prevState));
|
|
|
|
m_pDriver->GetCmdRenderState() = 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) {}
|
|
bool HasEventFlags(uint32_t eventId) { return m_EventFlags.find(eventId) != m_EventFlags.end(); }
|
|
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.depthClipEnable && !p.depthClampEnable)
|
|
flags |= TestEnabled_DepthClipping;
|
|
|
|
if(pipestate.cullMode != VK_CULL_MODE_NONE)
|
|
flags |= TestEnabled_Culling;
|
|
|
|
if(pipestate.cullMode == VK_CULL_MODE_FRONT_AND_BACK)
|
|
flags |= TestMustFail_Culling;
|
|
}
|
|
|
|
// Depth and Stencil tests.
|
|
{
|
|
if(pipestate.depthBoundsTestEnable)
|
|
flags |= TestEnabled_DepthBounds;
|
|
|
|
if(pipestate.depthTestEnable)
|
|
{
|
|
if(pipestate.depthCompareOp != VK_COMPARE_OP_ALWAYS)
|
|
flags |= TestEnabled_DepthTesting;
|
|
if(pipestate.depthCompareOp == VK_COMPARE_OP_NEVER)
|
|
flags |= TestMustFail_DepthTesting;
|
|
|
|
if(pipestate.depthCompareOp == VK_COMPARE_OP_NEVER)
|
|
flags |= DepthTest_Never;
|
|
if(pipestate.depthCompareOp == VK_COMPARE_OP_LESS)
|
|
flags |= DepthTest_Less;
|
|
if(pipestate.depthCompareOp == VK_COMPARE_OP_EQUAL)
|
|
flags |= DepthTest_Equal;
|
|
if(pipestate.depthCompareOp == VK_COMPARE_OP_LESS_OR_EQUAL)
|
|
flags |= DepthTest_LessEqual;
|
|
if(pipestate.depthCompareOp == VK_COMPARE_OP_GREATER)
|
|
flags |= DepthTest_Greater;
|
|
if(pipestate.depthCompareOp == VK_COMPARE_OP_NOT_EQUAL)
|
|
flags |= DepthTest_NotEqual;
|
|
if(pipestate.depthCompareOp == VK_COMPARE_OP_GREATER_OR_EQUAL)
|
|
flags |= DepthTest_GreaterEqual;
|
|
if(pipestate.depthCompareOp == VK_COMPARE_OP_ALWAYS)
|
|
flags |= DepthTest_Always;
|
|
}
|
|
else
|
|
{
|
|
flags |= DepthTest_Always;
|
|
}
|
|
|
|
if(pipestate.stencilTestEnable)
|
|
{
|
|
if(pipestate.front.compareOp != VK_COMPARE_OP_ALWAYS ||
|
|
pipestate.back.compareOp != VK_COMPARE_OP_ALWAYS)
|
|
flags |= TestEnabled_StencilTesting;
|
|
|
|
if(pipestate.front.compareOp == VK_COMPARE_OP_NEVER &&
|
|
pipestate.back.compareOp == VK_COMPARE_OP_NEVER)
|
|
flags |= TestMustFail_StencilTesting;
|
|
else if(pipestate.front.compareOp == VK_COMPARE_OP_NEVER &&
|
|
pipestate.cullMode == VK_CULL_MODE_BACK_BIT)
|
|
flags |= TestMustFail_StencilTesting;
|
|
else if(pipestate.cullMode == VK_CULL_MODE_FRONT_BIT &&
|
|
pipestate.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 = pipestate.scissors.data();
|
|
uint32_t scissorCount = (uint32_t)pipestate.scissors.size();
|
|
|
|
for(uint32_t i = 0; i < scissorCount; i++)
|
|
{
|
|
const VkOffset2D &offset = pScissors[i].offset;
|
|
const VkExtent2D &extent = pScissors[i].extent;
|
|
if(((int32_t)m_CallbackInfo.x >= offset.x) && ((int32_t)m_CallbackInfo.y >= offset.y) &&
|
|
((int32_t)m_CallbackInfo.x < ((int64_t)offset.x + (int64_t)extent.width)) &&
|
|
((int32_t)m_CallbackInfo.y < ((int64_t)offset.y + (int64_t)extent.height)))
|
|
inRegion = true;
|
|
else
|
|
inAllRegions = false;
|
|
}
|
|
if(!inRegion)
|
|
flags |= TestMustFail_Scissor;
|
|
if(inAllRegions)
|
|
flags |= TestMustPass_Scissor;
|
|
}
|
|
|
|
// Blending
|
|
{
|
|
if(m_pDriver->GetDeviceEnabledFeatures().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;
|
|
}
|
|
}
|
|
|
|
if(p.shaders[StageIndex(VK_SHADER_STAGE_FRAGMENT_BIT)].module == ResourceId())
|
|
flags |= UnboundFragmentShader;
|
|
|
|
// Samples
|
|
{
|
|
// TODO: figure out if we always need to check this.
|
|
flags |= TestEnabled_SampleMask;
|
|
|
|
// compare to ms->pSampleMask
|
|
if((p.sampleMask & m_CallbackInfo.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_DisableDepthClipping = 1 << 4,
|
|
PipelineCreationFlags_FixedColorShader = 1 << 5,
|
|
PipelineCreationFlags_IntersectOriginalScissor = 1 << 6,
|
|
};
|
|
|
|
void ReplayDrawWithTests(VkCommandBuffer cmd, uint32_t eid, uint32_t eventFlags,
|
|
ResourceId basePipeline, uint32_t outputIndex)
|
|
{
|
|
// 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);
|
|
}
|
|
|
|
VulkanRenderState &pipestate = m_pDriver->GetCmdRenderState();
|
|
rdcarray<VkRect2D> prevScissors = pipestate.scissors;
|
|
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_DisableDepthClipping |
|
|
PipelineCreationFlags_DisableDepthBoundsTest | PipelineCreationFlags_DisableStencilTest |
|
|
PipelineCreationFlags_FixedColorShader;
|
|
VkPipeline pipe = CreatePipeline(basePipeline, pipeFlags, replacementShaders, outputIndex);
|
|
VkMarkerRegion::Set(StringFormat::Fmt("Test culling on %u", eid), cmd);
|
|
ReplayDraw(cmd, pipe, eid, TestEnabled_Culling);
|
|
}
|
|
|
|
if(eventFlags & TestEnabled_DepthClipping)
|
|
{
|
|
uint32_t pipeFlags =
|
|
PipelineCreationFlags_DisableDepthTest | PipelineCreationFlags_DisableDepthBoundsTest |
|
|
PipelineCreationFlags_DisableStencilTest | PipelineCreationFlags_FixedColorShader;
|
|
VkPipeline pipe = CreatePipeline(basePipeline, pipeFlags, replacementShaders, outputIndex);
|
|
VkMarkerRegion::Set(StringFormat::Fmt("Test depth clipping on %u", eid), cmd);
|
|
ReplayDraw(cmd, pipe, eid, TestEnabled_DepthClipping);
|
|
}
|
|
|
|
// 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, replacementShaders, outputIndex);
|
|
// This will change the scissor for the later tests, but since those
|
|
// tests happen later in the pipeline, it does not matter.
|
|
for(uint32_t i = 0; i < pipestate.views.size(); i++)
|
|
IntersectScissors(prevScissors[i], pipestate.scissors[i]);
|
|
VkMarkerRegion::Set(StringFormat::Fmt("Test scissor on %u", eid), cmd);
|
|
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, replacementShaders, outputIndex);
|
|
VkMarkerRegion::Set(StringFormat::Fmt("Test sample mask on %u", eid), cmd);
|
|
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, replacementShaders, outputIndex);
|
|
VkMarkerRegion::Set(StringFormat::Fmt("Test depth bounds on %u", eid), cmd);
|
|
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, replacementShaders, outputIndex);
|
|
VkMarkerRegion::Set(StringFormat::Fmt("Test stencil on %u", eid), cmd);
|
|
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, replacementShaders, outputIndex);
|
|
VkMarkerRegion::Set(StringFormat::Fmt("Test depth on %u", eid), cmd);
|
|
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, replacementShaders, outputIndex);
|
|
VkMarkerRegion::Set(StringFormat::Fmt("Test shader discard on %u", eid), cmd);
|
|
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,
|
|
const rdcarray<VkShaderModule> &replacementShaders, uint32_t outputIndex)
|
|
{
|
|
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;
|
|
VkPipelineMultisampleStateCreateInfo *ms =
|
|
(VkPipelineMultisampleStateCreateInfo *)ci.pMultisampleState;
|
|
|
|
AddDynamicStates(ci);
|
|
|
|
// Only interested in a single sample.
|
|
ms->pSampleMask = &m_CallbackInfo.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;
|
|
if(pipeCreateFlags & PipelineCreationFlags_DisableDepthClipping)
|
|
rs->depthClampEnable = VK_TRUE;
|
|
|
|
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(outputIndex);
|
|
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();
|
|
|
|
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, m_QueryFlags);
|
|
|
|
const DrawcallDescription *drawcall = m_pDriver->GetDrawcall(eventId);
|
|
m_pDriver->ReplayDraw(cmd, *drawcall);
|
|
|
|
ObjDisp(cmd)->CmdEndQuery(Unwrap(cmd), m_OcclusionPool, index);
|
|
}
|
|
|
|
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,
|
|
const PixelHistoryCallbackInfo &callbackInfo,
|
|
const std::map<uint32_t, uint32_t> &eventFragments,
|
|
const std::map<uint32_t, ModificationValue> &eventPremods)
|
|
: VulkanPixelHistoryCallback(vk, shaderCache, callbackInfo, VK_NULL_HANDLE),
|
|
m_EventFragments(eventFragments),
|
|
m_EventPremods(eventPremods)
|
|
{
|
|
}
|
|
|
|
~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];
|
|
|
|
uint32_t framebufferIndex = 0;
|
|
uint32_t colorOutputIndex = 0;
|
|
const rdcarray<ResourceId> &atts = prevState.GetFramebufferAttachments();
|
|
const VulkanCreationInfo::RenderPass &rpInfo =
|
|
m_pDriver->GetDebugManager()->GetRenderPassInfo(prevState.renderPass);
|
|
const VulkanCreationInfo::RenderPass::Subpass &sub = rpInfo.subpasses.front();
|
|
|
|
if(IsDepthOrStencilFormat(m_CallbackInfo.targetImageFormat))
|
|
{
|
|
// Going to add another color attachment.
|
|
framebufferIndex = (uint32_t)atts.size();
|
|
colorOutputIndex = (uint32_t)sub.colorAttachments.size();
|
|
}
|
|
else
|
|
{
|
|
for(uint32_t i = 0; i < atts.size(); i++)
|
|
{
|
|
ResourceId img = m_pDriver->GetDebugManager()->GetImageViewInfo(atts[i]).image;
|
|
if(img == GetResID(m_CallbackInfo.targetImage))
|
|
{
|
|
framebufferIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
for(uint32_t i = 0; i < sub.colorAttachments.size(); i++)
|
|
{
|
|
if(framebufferIndex == sub.colorAttachments[i])
|
|
{
|
|
colorOutputIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool multiview = false;
|
|
VkRenderPass newRp = CreateRenderPass(state.renderPass, multiview,
|
|
VK_FORMAT_R32G32B32A32_SFLOAT, framebufferIndex);
|
|
|
|
VkFramebuffer newFb = CreateFramebuffer(state.renderPass, newRp, state.GetFramebuffer(),
|
|
m_CallbackInfo.subImageView, framebufferIndex);
|
|
|
|
Pipelines pipes = CreatePerFragmentPipelines(curPipeline, newRp, eid, 0, colorOutputIndex);
|
|
|
|
for(uint32_t i = 0; i < state.views.size(); i++)
|
|
{
|
|
ScissorToPixel(state.views[i], state.scissors[i]);
|
|
|
|
state.scissors[i].offset.x &= ~0x1;
|
|
state.scissors[i].offset.y &= ~0x1;
|
|
state.scissors[i].extent = {2, 2};
|
|
}
|
|
|
|
state.renderPass = GetResID(newRp);
|
|
state.SetFramebuffer(m_pDriver, GetResID(newFb));
|
|
|
|
VkPipeline pipesIter[2];
|
|
pipesIter[0] = pipes.primitiveIdPipe;
|
|
pipesIter[1] = pipes.shaderOutPipe;
|
|
|
|
CopyPixelParams colourCopyParams = {};
|
|
colourCopyParams.srcImage = m_CallbackInfo.subImage;
|
|
colourCopyParams.srcImageFormat = VK_FORMAT_R32G32B32A32_SFLOAT;
|
|
colourCopyParams.multisampled = (m_CallbackInfo.samples != VK_SAMPLE_COUNT_1_BIT);
|
|
colourCopyParams.multiview = multiview;
|
|
if(IsDepthOrStencilFormat(m_CallbackInfo.targetImageFormat))
|
|
{
|
|
colourCopyParams.srcImageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
|
}
|
|
else
|
|
{
|
|
// Use the layout of the image we are substituting for.
|
|
VkImageLayout srcImageLayout = m_pDriver->GetDebugManager()->GetImageLayout(
|
|
GetResID(m_CallbackInfo.targetImage), VK_IMAGE_ASPECT_COLOR_BIT,
|
|
m_CallbackInfo.targetSubresource.mip, m_CallbackInfo.targetSubresource.slice);
|
|
colourCopyParams.srcImageLayout = srcImageLayout;
|
|
}
|
|
|
|
bool depthEnabled = prevState.depthTestEnable != VK_FALSE;
|
|
|
|
VkMarkerRegion::Set(StringFormat::Fmt("Event %u has %u fragments", eid, numFragmentsInEvent),
|
|
cmd);
|
|
|
|
// 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++)
|
|
{
|
|
uint32_t storeOffset = (fragsProcessed + f) * sizeof(PerFragmentInfo);
|
|
|
|
VkMarkerRegion region(cmd, StringFormat::Fmt("Getting %s for %u",
|
|
i == 0 ? "primitive ID" : "shader output", eid));
|
|
|
|
if(i == 0 && !m_pDriver->GetDeviceEnabledFeatures().geometryShader)
|
|
{
|
|
// without geometryShader, can't read primitive ID in pixel shader
|
|
VkMarkerRegion::Set("Can't get primitive ID without geometryShader feature", cmd);
|
|
|
|
ObjDisp(cmd)->CmdFillBuffer(Unwrap(cmd), Unwrap(m_CallbackInfo.dstBuffer), storeOffset,
|
|
16, ~0U);
|
|
continue;
|
|
}
|
|
|
|
if(pipesIter[i] == VK_NULL_HANDLE)
|
|
{
|
|
// without one of the pipelines (e.g. if there was a geometry shader in use and we can't
|
|
// read primitive ID in the fragment shader) we can't continue.
|
|
// technically we can if the geometry shader outs a primitive ID, but that is unlikely.
|
|
VkMarkerRegion::Set("Can't get primitive ID with geometry shader in use", cmd);
|
|
|
|
ObjDisp(cmd)->CmdFillBuffer(Unwrap(cmd), Unwrap(m_CallbackInfo.dstBuffer), storeOffset,
|
|
16, ~0U);
|
|
continue;
|
|
}
|
|
|
|
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_CallbackInfo.dsImage),
|
|
{VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, 0,
|
|
1, 0, m_CallbackInfo.layers}};
|
|
|
|
DoPipelineBarrier(cmd, 1, &barrier);
|
|
|
|
// Reset depth to 0.0f, depth test is set to always pass.
|
|
// This way we get the value for just that fragment.
|
|
// Reset stencil to 0.
|
|
VkClearDepthStencilValue dsValue = {};
|
|
dsValue.depth = 0.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 = m_CallbackInfo.layers;
|
|
range.levelCount = 1;
|
|
|
|
ObjDisp(cmd)->CmdClearDepthStencilImage(Unwrap(cmd), Unwrap(m_CallbackInfo.dsImage),
|
|
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)->CmdSetStencilCompareMask(Unwrap(cmd), VK_STENCIL_FACE_FRONT_AND_BACK, 0xff);
|
|
ObjDisp(cmd)->CmdSetStencilWriteMask(Unwrap(cmd), VK_STENCIL_FACE_FRONT_AND_BACK, 0xff);
|
|
ObjDisp(cmd)->CmdSetStencilReference(Unwrap(cmd), VK_STENCIL_FACE_FRONT_AND_BACK, f);
|
|
const DrawcallDescription *drawcall = m_pDriver->GetDrawcall(eid);
|
|
m_pDriver->ReplayDraw(cmd, *drawcall);
|
|
state.EndRenderPass(cmd);
|
|
|
|
if(i == 1)
|
|
{
|
|
storeOffset += offsetof(struct PerFragmentInfo, shaderOut);
|
|
if(depthEnabled)
|
|
{
|
|
CopyPixelParams depthCopyParams = colourCopyParams;
|
|
depthCopyParams.srcImage = m_CallbackInfo.dsImage;
|
|
depthCopyParams.srcImageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
|
depthCopyParams.srcImageFormat = m_CallbackInfo.dsFormat;
|
|
CopyImagePixel(cmd, depthCopyParams,
|
|
storeOffset + offsetof(struct PixelHistoryValue, depth));
|
|
}
|
|
}
|
|
CopyImagePixel(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;
|
|
}
|
|
|
|
// use the original renderpass and framebuffer attachment
|
|
state.renderPass = prevState.renderPass;
|
|
state.SetFramebuffer(prevState.GetFramebuffer(), prevState.GetFramebufferAttachments());
|
|
|
|
colourCopyParams.srcImage = m_CallbackInfo.targetImage;
|
|
colourCopyParams.srcImageFormat = m_CallbackInfo.targetImageFormat;
|
|
colourCopyParams.multisampled = (m_CallbackInfo.samples != VK_SAMPLE_COUNT_1_BIT);
|
|
VkImageAspectFlagBits aspect = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
if(IsDepthOrStencilFormat(m_CallbackInfo.targetImageFormat))
|
|
aspect = VK_IMAGE_ASPECT_DEPTH_BIT;
|
|
colourCopyParams.srcImageLayout = m_pDriver->GetDebugManager()->GetImageLayout(
|
|
GetResID(m_CallbackInfo.targetImage), aspect, m_CallbackInfo.targetSubresource.mip,
|
|
m_CallbackInfo.targetSubresource.slice);
|
|
|
|
const ModificationValue &premod = m_EventPremods[eid];
|
|
// For every fragment except the last one, retrieve post-modification
|
|
// value.
|
|
for(uint32_t f = 0; f < numFragmentsInEvent - 1; f++)
|
|
{
|
|
VkMarkerRegion region(cmd, StringFormat::Fmt("Getting postmod for fragment %u in %u", f, eid));
|
|
|
|
// 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_CallbackInfo.x;
|
|
rect.rect.offset.y = m_CallbackInfo.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);
|
|
|
|
if(f == 0)
|
|
{
|
|
// Before starting the draw, initialize the pixel to the premodification value
|
|
// for this event, for both color and depth.
|
|
VkClearAttachment clearAtts[2] = {};
|
|
|
|
clearAtts[0].aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
clearAtts[0].colorAttachment = colorOutputIndex;
|
|
memcpy(clearAtts[0].clearValue.color.float32, premod.col.floatValue,
|
|
sizeof(clearAtts[0].clearValue.color));
|
|
|
|
clearAtts[1].aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
|
clearAtts[1].clearValue.depthStencil.depth = premod.depth;
|
|
|
|
if(IsDepthOrStencilFormat(m_CallbackInfo.targetImageFormat))
|
|
ObjDisp(cmd)->CmdClearAttachments(Unwrap(cmd), 1, clearAtts + 1, 1, &rect);
|
|
else
|
|
ObjDisp(cmd)->CmdClearAttachments(Unwrap(cmd), 2, clearAtts, 1, &rect);
|
|
}
|
|
|
|
ObjDisp(cmd)->CmdSetStencilCompareMask(Unwrap(cmd), VK_STENCIL_FACE_FRONT_AND_BACK, 0xff);
|
|
ObjDisp(cmd)->CmdSetStencilWriteMask(Unwrap(cmd), VK_STENCIL_FACE_FRONT_AND_BACK, 0xff);
|
|
ObjDisp(cmd)->CmdSetStencilReference(Unwrap(cmd), VK_STENCIL_FACE_FRONT_AND_BACK, f);
|
|
const DrawcallDescription *drawcall = m_pDriver->GetDrawcall(eid);
|
|
m_pDriver->ReplayDraw(cmd, *drawcall);
|
|
state.EndRenderPass(cmd);
|
|
|
|
CopyImagePixel(cmd, colourCopyParams, (fragsProcessed + f) * sizeof(PerFragmentInfo) +
|
|
offsetof(struct PerFragmentInfo, postMod));
|
|
|
|
if(depthImage != VK_NULL_HANDLE)
|
|
{
|
|
CopyPixelParams depthCopyParams = colourCopyParams;
|
|
depthCopyParams.srcImage = depthImage;
|
|
depthCopyParams.srcImageLayout = sub.depthLayout;
|
|
depthCopyParams.srcImageFormat = depthFormat;
|
|
CopyImagePixel(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) {}
|
|
// CreatePerFragmentPipelines for getting per fragment information.
|
|
Pipelines CreatePerFragmentPipelines(ResourceId pipe, VkRenderPass rp, uint32_t eid,
|
|
uint32_t fragmentIndex, uint32_t colorOutputIndex)
|
|
{
|
|
const VulkanCreationInfo::Pipeline &p = m_pDriver->GetDebugManager()->GetPipelineInfo(pipe);
|
|
VkGraphicsPipelineCreateInfo pipeCreateInfo = {};
|
|
rdcarray<VkPipelineShaderStageCreateInfo> stages;
|
|
m_pDriver->GetShaderCache()->MakeGraphicsPipelineInfo(pipeCreateInfo, pipe);
|
|
|
|
AddDynamicStates(pipeCreateInfo);
|
|
|
|
VkPipelineDepthStencilStateCreateInfo *ds =
|
|
(VkPipelineDepthStencilStateCreateInfo *)pipeCreateInfo.pDepthStencilState;
|
|
|
|
// 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;
|
|
}
|
|
|
|
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();
|
|
|
|
// the postmod pipe is used with the original renderpass and attachment setup
|
|
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);
|
|
|
|
pipeCreateInfo.renderPass = rp;
|
|
|
|
VkPipelineColorBlendStateCreateInfo *cbs =
|
|
(VkPipelineColorBlendStateCreateInfo *)pipeCreateInfo.pColorBlendState;
|
|
// Turn off blending so that we can get shader output values.
|
|
VkPipelineColorBlendAttachmentState *atts =
|
|
(VkPipelineColorBlendAttachmentState *)cbs->pAttachments;
|
|
rdcarray<VkPipelineColorBlendAttachmentState> newAtts;
|
|
|
|
// Check if we need to add a new color attachment.
|
|
if(colorOutputIndex == cbs->attachmentCount)
|
|
{
|
|
newAtts.resize(cbs->attachmentCount + 1);
|
|
memcpy(newAtts.data(), cbs->pAttachments,
|
|
cbs->attachmentCount * sizeof(VkPipelineColorBlendAttachmentState));
|
|
VkPipelineColorBlendAttachmentState newAtt = {};
|
|
if(cbs->attachmentCount > 0)
|
|
{
|
|
// If there are existing color attachments, copy the blend information from it.
|
|
// It will be adjusted later.
|
|
newAtt = cbs->pAttachments[0];
|
|
}
|
|
else
|
|
{
|
|
newAtt.blendEnable = VK_FALSE;
|
|
newAtt.srcColorBlendFactor = VK_BLEND_FACTOR_DST_COLOR;
|
|
}
|
|
newAtts[cbs->attachmentCount] = newAtt;
|
|
cbs->attachmentCount = (uint32_t)newAtts.size();
|
|
cbs->pAttachments = newAtts.data();
|
|
|
|
atts = newAtts.data();
|
|
}
|
|
|
|
for(uint32_t i = 0; i < cbs->attachmentCount; i++)
|
|
{
|
|
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;
|
|
}
|
|
|
|
{
|
|
ds->depthBoundsTestEnable = VK_FALSE;
|
|
ds->depthCompareOp = VK_COMPARE_OP_ALWAYS;
|
|
}
|
|
|
|
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.
|
|
VkPipelineShaderStageCreateInfo stageCI = {};
|
|
stageCI.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
|
stageCI.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
|
|
stageCI.module = m_ShaderCache->GetPrimitiveIdShader(colorOutputIndex);
|
|
stageCI.pName = "main";
|
|
bool gsFound = false;
|
|
bool fsFound = false;
|
|
for(uint32_t i = 0; i < pipeCreateInfo.stageCount; i++)
|
|
{
|
|
if(stages[i].stage == VK_SHADER_STAGE_GEOMETRY_BIT)
|
|
{
|
|
gsFound = true;
|
|
break;
|
|
}
|
|
if(stages[i].stage == VK_SHADER_STAGE_FRAGMENT_BIT)
|
|
{
|
|
stages[i] = stageCI;
|
|
fsFound = true;
|
|
}
|
|
}
|
|
if(!fsFound)
|
|
{
|
|
stages.push_back(stageCI);
|
|
pipeCreateInfo.stageCount = (uint32_t)stages.size();
|
|
pipeCreateInfo.pStages = stages.data();
|
|
}
|
|
|
|
if(!gsFound)
|
|
{
|
|
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);
|
|
}
|
|
else
|
|
{
|
|
pipes.primitiveIdPipe = VK_NULL_HANDLE;
|
|
RDCWARN("Can't get primitive ID at event %u due to geometry shader usage", eid);
|
|
}
|
|
|
|
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;
|
|
// Pre-modification values for events to initialize attachments to,
|
|
// so that we can get blended post-modification values.
|
|
std::map<uint32_t, ModificationValue> m_EventPremods;
|
|
// Number of fragments processed so far.
|
|
uint32_t fragsProcessed = 0;
|
|
|
|
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,
|
|
const PixelHistoryCallbackInfo &callbackInfo,
|
|
std::map<uint32_t, rdcarray<int32_t> > events,
|
|
VkQueryPool occlusionPool)
|
|
: VulkanPixelHistoryCallback(vk, shaderCache, callbackInfo, 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);
|
|
for(uint32_t i = 0; i < state.views.size(); i++)
|
|
ScissorToPixel(state.views[i], state.scissors[i]);
|
|
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, m_QueryFlags);
|
|
const DrawcallDescription *drawcall = m_pDriver->GetDrawcall(eid);
|
|
uint32_t primId = primIds[i];
|
|
DrawcallDescription draw = *drawcall;
|
|
draw.numIndices = RENDERDOC_NumVerticesPerPrimitive(drawcall->topology);
|
|
draw.indexOffset += RENDERDOC_VertexOffset(drawcall->topology, primId);
|
|
draw.vertexOffset += RENDERDOC_VertexOffset(drawcall->topology, primId);
|
|
// TODO once pixel history distinguishes between instances, draw only the instance for
|
|
// this fragment.
|
|
// TODO replay with a dummy index buffer so that all primitives other than the target one are
|
|
// degenerate - that way the vertex index etc is still the same as it should be.
|
|
m_pDriver->ReplayDraw(cmd, draw);
|
|
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 = {};
|
|
MakeIncrementStencilPipelineCI(eid, pipe, pipeCreateInfo, stages, false);
|
|
|
|
{
|
|
VkPipelineDepthStencilStateCreateInfo *ds =
|
|
(VkPipelineDepthStencilStateCreateInfo *)pipeCreateInfo.pDepthStencilState;
|
|
ds->stencilTestEnable = VK_FALSE;
|
|
}
|
|
|
|
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,
|
|
VkImage targetImage, VkExtent3D extent,
|
|
VkFormat format, VkSampleCountFlagBits samples,
|
|
const Subresource &sub, uint32_t numEvents)
|
|
{
|
|
VkMarkerRegion region(StringFormat::Fmt("PixelHistorySetupResources %ux%ux%u %s %ux MSAA",
|
|
extent.width, extent.height, extent.depth,
|
|
ToStr(format).c_str(), samples));
|
|
VulkanCreationInfo::Image targetImageInfo = GetImageInfo(GetResID(targetImage));
|
|
|
|
VkImage colorImage;
|
|
VkImageView colorImageView;
|
|
|
|
VkImage dsImage;
|
|
VkImageView dsImageView;
|
|
|
|
VkDeviceMemory gpuMem;
|
|
|
|
VkBuffer dstBuffer;
|
|
VkDeviceMemory bufferMemory;
|
|
|
|
VkResult vkr;
|
|
VkDevice dev = m_pDriver->GetDev();
|
|
|
|
VkDeviceSize totalMemorySize = 0;
|
|
|
|
VkFormat dsFormat = VK_FORMAT_D32_SFLOAT_S8_UINT;
|
|
|
|
if(!(m_pDriver->GetFormatProperties(dsFormat).optimalTilingFeatures &
|
|
VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT))
|
|
dsFormat = VK_FORMAT_D24_UNORM_S8_UINT;
|
|
|
|
RDCDEBUG("Using depth-stencil format %s", ToStr(dsFormat).c_str());
|
|
|
|
// Create Images
|
|
VkImageCreateInfo imgInfo = {VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
|
|
imgInfo.imageType = VK_IMAGE_TYPE_2D;
|
|
imgInfo.mipLevels = targetImageInfo.mipLevels;
|
|
imgInfo.arrayLayers = targetImageInfo.arrayLayers;
|
|
imgInfo.samples = samples;
|
|
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 |
|
|
VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
|
if(samples != VK_SAMPLE_COUNT_1_BIT)
|
|
imgInfo.usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
|
|
|
|
vkr = m_pDriver->vkCreateImage(dev, &imgInfo, NULL, &colorImage);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
ImageState colorImageState = ImageState(colorImage, ImageInfo(imgInfo), eFrameRef_None);
|
|
|
|
VkMemoryRequirements colorImageMrq = {0};
|
|
m_pDriver->vkGetImageMemoryRequirements(dev, colorImage, &colorImageMrq);
|
|
totalMemorySize = colorImageMrq.size;
|
|
|
|
imgInfo.format = dsFormat;
|
|
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, &dsImage);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
ImageState stencilImageState = ImageState(dsImage, ImageInfo(imgInfo), eFrameRef_None);
|
|
|
|
VkMemoryRequirements stencilImageMrq = {0};
|
|
m_pDriver->vkGetImageMemoryRequirements(dev, dsImage, &stencilImageMrq);
|
|
VkDeviceSize offset = AlignUp(totalMemorySize, stencilImageMrq.alignment);
|
|
totalMemorySize = offset + stencilImageMrq.size;
|
|
|
|
VkMemoryAllocateInfo allocInfo = {
|
|
VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, NULL, totalMemorySize,
|
|
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, dsImage, gpuMem, offset);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
NameVulkanObject(colorImage, "Pixel History color image");
|
|
NameVulkanObject(dsImage, "Pixel History depth image");
|
|
|
|
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, imgInfo.arrayLayers};
|
|
|
|
if(samples != VK_SAMPLE_COUNT_1_BIT)
|
|
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
|
|
|
|
vkr = m_pDriver->vkCreateImageView(m_Device, &viewInfo, NULL, &colorImageView);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
viewInfo.image = dsImage;
|
|
viewInfo.format = dsFormat;
|
|
viewInfo.subresourceRange = {VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, 0, 1, 0,
|
|
imgInfo.arrayLayers};
|
|
|
|
vkr = m_pDriver->vkCreateImageView(m_Device, &viewInfo, NULL, &dsImageView);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
VkBufferCreateInfo bufferInfo = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
|
|
// TODO: the size for memory is calculated to fit pre and post modification values and
|
|
// stencil values. But we might run out of space when getting per fragment data.
|
|
bufferInfo.size = AlignUp((uint32_t)(numEvents * sizeof(EventInfo)), 4096U);
|
|
bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_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.dsFormat = dsFormat;
|
|
resources.dsImage = dsImage;
|
|
resources.dsImageView = dsImageView;
|
|
resources.gpuMem = gpuMem;
|
|
|
|
resources.bufferMemory = bufferMemory;
|
|
resources.dstBuffer = dstBuffer;
|
|
|
|
return true;
|
|
}
|
|
|
|
VkDescriptorSet VulkanReplay::GetPixelHistoryDescriptor()
|
|
{
|
|
VkDescriptorSet descSet;
|
|
|
|
VkDescriptorSetAllocateInfo descSetAllocInfo = {
|
|
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
|
|
NULL,
|
|
m_PixelHistory.MSCopyDescPool,
|
|
1,
|
|
&m_PixelHistory.MSCopyDescSetLayout,
|
|
};
|
|
|
|
// don't expect this to fail (or if it does then it should be immediately obvious, not transient).
|
|
VkResult vkr =
|
|
m_pDriver->vkAllocateDescriptorSets(m_pDriver->GetDev(), &descSetAllocInfo, &descSet);
|
|
if(vkr != VK_SUCCESS)
|
|
RDCERR("Failed creating object");
|
|
m_PixelHistory.allocedSets.push_back(descSet);
|
|
return descSet;
|
|
}
|
|
|
|
void VulkanReplay::ResetPixelHistoryDescriptorPool()
|
|
{
|
|
for(VkDescriptorSet descset : m_PixelHistory.allocedSets)
|
|
GetResourceManager()->ReleaseWrappedResource(descset, true);
|
|
m_PixelHistory.allocedSets.clear();
|
|
m_pDriver->vkResetDescriptorPool(m_pDriver->GetDev(), m_PixelHistory.MSCopyDescPool, 0);
|
|
}
|
|
|
|
void VulkanReplay::UpdatePixelHistoryDescriptor(VkDescriptorSet descSet, VkBuffer buffer,
|
|
VkImageView imgView1, VkImageView imgView2)
|
|
{
|
|
VkDescriptorBufferInfo destdesc = {0};
|
|
destdesc.buffer = Unwrap(buffer);
|
|
destdesc.range = VK_WHOLE_SIZE;
|
|
|
|
{
|
|
VkDescriptorImageInfo srcdesc = {};
|
|
srcdesc.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
|
srcdesc.imageView = Unwrap(imgView1);
|
|
srcdesc.sampler = Unwrap(m_General.PointSampler); // not used - we use texelFetch
|
|
|
|
VkDescriptorImageInfo srcdesc2 = {};
|
|
srcdesc2.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
|
if(imgView2 != VK_NULL_HANDLE)
|
|
srcdesc2.imageView = Unwrap(imgView2);
|
|
else
|
|
srcdesc2.imageView = Unwrap(imgView1);
|
|
srcdesc2.sampler = Unwrap(m_General.PointSampler);
|
|
|
|
VkWriteDescriptorSet writeSet[] = {
|
|
{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, Unwrap(descSet), 0, 0, 1,
|
|
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, &srcdesc, NULL, NULL},
|
|
{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, Unwrap(descSet), 1, 0, 1,
|
|
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, &srcdesc2, NULL, NULL},
|
|
{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, Unwrap(descSet), 2, 0, 1,
|
|
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, NULL, &destdesc, NULL},
|
|
};
|
|
|
|
ObjDisp(m_pDriver->GetDev())
|
|
->UpdateDescriptorSets(Unwrap(m_pDriver->GetDev()), ARRAY_COUNT(writeSet), writeSet, 0, NULL);
|
|
}
|
|
}
|
|
|
|
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.dsImage != VK_NULL_HANDLE)
|
|
m_pDriver->vkDestroyImage(dev, r.dsImage, NULL);
|
|
if(r.dsImageView != VK_NULL_HANDLE)
|
|
m_pDriver->vkDestroyImageView(dev, r.dsImageView, 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 CreateOcclusionPool(WrappedVulkan *vk, uint32_t poolSize, VkQueryPool *pQueryPool)
|
|
{
|
|
VkMarkerRegion region(StringFormat::Fmt("CreateOcclusionPool %u", poolSize));
|
|
|
|
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)
|
|
{
|
|
VkImageLayout ret = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
|
|
auto state = m_pDriver->FindConstImageState(image);
|
|
if(!state)
|
|
{
|
|
RDCERR("Could not find image state for %s", ToStr(image).c_str());
|
|
return ret;
|
|
}
|
|
|
|
if(state->GetImageInfo().extent.depth > 1)
|
|
ret = state->GetImageLayout(aspect, mip, 0);
|
|
else
|
|
ret = state->GetImageLayout(aspect, mip, slice);
|
|
|
|
SanitiseReplayImageLayout(ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
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_DepthClipping)
|
|
{
|
|
uint64_t occlData = tfCb->GetOcclusionResult(eventId, TestEnabled_DepthClipping);
|
|
mod.depthClipped = (occlData == 0);
|
|
}
|
|
|
|
if(mod.depthClipped)
|
|
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.depthBoundsFailed = (occlData == 0);
|
|
}
|
|
if(mod.depthBoundsFailed)
|
|
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 = DecodeFormattedComponents(fmt, value.color);
|
|
memcpy(mod.col.floatValue, &v4, sizeof(v4));
|
|
}
|
|
|
|
float GetDepthValue(VkFormat depthFormat, const PixelHistoryValue &value)
|
|
{
|
|
FloatVector v4 = DecodeFormattedComponents(MakeResourceFormat(depthFormat), (byte *)&value.depth);
|
|
return v4.x;
|
|
}
|
|
|
|
rdcarray<PixelModification> VulkanReplay::PixelHistory(rdcarray<EventUsage> events,
|
|
ResourceId target, uint32_t x, uint32_t y,
|
|
const Subresource &sub, CompType typeCast)
|
|
{
|
|
rdcarray<PixelModification> history;
|
|
|
|
if(events.empty())
|
|
return history;
|
|
|
|
const VulkanCreationInfo::Image &imginfo = GetDebugManager()->GetImageInfo(target);
|
|
if(imginfo.format == VK_FORMAT_UNDEFINED)
|
|
return history;
|
|
|
|
rdcstr regionName = StringFormat::Fmt(
|
|
"PixelHistory: pixel: (%u, %u) on %s subresource (%u, %u, %u) cast to %s with %zu events", x, y,
|
|
ToStr(target).c_str(), sub.mip, sub.slice, sub.sample, ToStr(typeCast).c_str(), events.size());
|
|
|
|
RDCDEBUG("%s", regionName.c_str());
|
|
|
|
VkMarkerRegion region(regionName);
|
|
|
|
uint32_t sampleIdx = sub.sample;
|
|
|
|
// 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 = {};
|
|
// TODO: perhaps should do this after making an occlusion query, since we will
|
|
// get a smaller subset of events that passed the occlusion query.
|
|
VkImage targetImage = GetResourceManager()->GetCurrentHandle<VkImage>(target);
|
|
GetDebugManager()->PixelHistorySetupResources(resources, targetImage, imginfo.extent,
|
|
imginfo.format, imginfo.samples, sub,
|
|
(uint32_t)events.size());
|
|
|
|
PixelHistoryShaderCache *shaderCache = new PixelHistoryShaderCache(m_pDriver);
|
|
|
|
PixelHistoryCallbackInfo callbackInfo = {};
|
|
callbackInfo.targetImage = targetImage;
|
|
callbackInfo.targetImageFormat = imginfo.format;
|
|
callbackInfo.layers = imginfo.arrayLayers;
|
|
callbackInfo.mipLevels = imginfo.mipLevels;
|
|
callbackInfo.samples = imginfo.samples;
|
|
callbackInfo.extent = imginfo.extent;
|
|
callbackInfo.targetSubresource = sub;
|
|
callbackInfo.x = x;
|
|
callbackInfo.y = y;
|
|
callbackInfo.sampleMask = sampleMask;
|
|
callbackInfo.subImage = resources.colorImage;
|
|
callbackInfo.subImageView = resources.colorImageView;
|
|
callbackInfo.dsImage = resources.dsImage;
|
|
callbackInfo.dsFormat = resources.dsFormat;
|
|
callbackInfo.dsImageView = resources.dsImageView;
|
|
callbackInfo.dstBuffer = resources.dstBuffer;
|
|
|
|
VulkanOcclusionCallback occlCb(m_pDriver, shaderCache, callbackInfo, occlusionPool, events);
|
|
{
|
|
VkMarkerRegion occlRegion("VulkanOcclusionCallback");
|
|
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: Check that the slice and mip matches.
|
|
VulkanCreationInfo::ImageView viewInfo =
|
|
m_pDriver->GetDebugManager()->GetImageViewInfo(events[ev].view);
|
|
uint32_t layerEnd = viewInfo.range.baseArrayLayer + viewInfo.range.layerCount;
|
|
if(sub.slice < viewInfo.range.baseArrayLayer || sub.slice >= layerEnd)
|
|
{
|
|
RDCDEBUG("Usage %d at %u didn't refer to the matching mip/slice (%u/%u)", events[ev].usage,
|
|
events[ev].eventId, sub.mip, sub.slice);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if(directWrite || clear)
|
|
{
|
|
modEvents.push_back(events[ev].eventId);
|
|
}
|
|
else
|
|
{
|
|
uint64_t occlData = occlCb.GetOcclusionResult((uint32_t)events[ev].eventId);
|
|
VkMarkerRegion::Set(StringFormat::Fmt("%u has occl %llu", events[ev].eventId, occlData));
|
|
if(occlData > 0)
|
|
{
|
|
drawEvents.push_back(events[ev].eventId);
|
|
modEvents.push_back(events[ev].eventId);
|
|
}
|
|
}
|
|
}
|
|
|
|
VulkanColorAndStencilCallback cb(m_pDriver, shaderCache, callbackInfo, modEvents);
|
|
{
|
|
VkMarkerRegion colorStencilRegion("VulkanColorAndStencilCallback");
|
|
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)
|
|
{
|
|
VkMarkerRegion testsRegion("TestsFailedCallback");
|
|
VkQueryPool tfOcclusionPool;
|
|
CreateOcclusionPool(m_pDriver, (uint32_t)drawEvents.size() * 6, &tfOcclusionPool);
|
|
|
|
tfCb = new TestsFailedCallback(m_pDriver, shaderCache, callbackInfo, 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);
|
|
VkMarkerRegion::Set(StringFormat::Fmt("%u has flags %x", eventId, flags));
|
|
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;
|
|
if(flags & UnboundFragmentShader)
|
|
mod.unboundPS = 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;
|
|
std::map<uint32_t, ModificationValue> eventPremods;
|
|
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.
|
|
mod.preMod.SetInvalid();
|
|
mod.postMod.SetInvalid();
|
|
mod.shaderOut.SetInvalid();
|
|
h++;
|
|
continue;
|
|
}
|
|
const EventInfo &ei = eventsInfo[eventIndex];
|
|
FillInColor(fmt, ei.premod, mod.preMod);
|
|
FillInColor(fmt, ei.postmod, mod.postMod);
|
|
VkFormat depthFormat = cb.GetDepthFormat(mod.eventId);
|
|
if(depthFormat != VK_FORMAT_UNDEFINED)
|
|
{
|
|
mod.preMod.stencil = ei.premod.stencil;
|
|
mod.postMod.stencil = ei.postmod.stencil;
|
|
if(multisampled)
|
|
{
|
|
mod.preMod.depth = ei.premod.depth.fdepth;
|
|
mod.postMod.depth = ei.postmod.depth.fdepth;
|
|
}
|
|
else
|
|
{
|
|
mod.preMod.depth = GetDepthValue(depthFormat, ei.premod);
|
|
mod.postMod.depth = GetDepthValue(depthFormat, ei.postmod);
|
|
}
|
|
}
|
|
|
|
int32_t frags = int32_t(ei.dsWithoutShaderDiscard[4]);
|
|
int32_t fragsClipped = int32_t(ei.dsWithShaderDiscard[4]);
|
|
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;
|
|
eventPremods[mod.eventId] = mod.preMod;
|
|
}
|
|
|
|
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[4], ei.dsWithShaderDiscard[4]);
|
|
}
|
|
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, callbackInfo,
|
|
eventsWithFrags, eventPremods);
|
|
{
|
|
VkMarkerRegion perFragmentRegion("VulkanPixelHistoryPerFragmentCallback");
|
|
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++;
|
|
}
|
|
}
|
|
|
|
// without the geometry shader feature we can't get the primitive ID, so we can't establish
|
|
// discard per-primitive so we assume all shaders don't discard.
|
|
if(m_pDriver->GetDeviceEnabledFeatures().geometryShader)
|
|
{
|
|
if(primitivesToCheck > 0)
|
|
{
|
|
VkMarkerRegion discardedRegion("VulkanPixelHistoryDiscardedFragmentsCallback");
|
|
VkQueryPool occlPool;
|
|
CreateOcclusionPool(m_pDriver, primitivesToCheck, &occlPool);
|
|
|
|
// Replay to see which primitives were discarded.
|
|
VulkanPixelHistoryDiscardedFragmentsCallback discardedCb(
|
|
m_pDriver, shaderCache, callbackInfo, 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);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// mark that we have no primitive IDs
|
|
for(size_t h = 0; h < history.size(); h++)
|
|
history[h].primitiveID = ~0U;
|
|
}
|
|
|
|
uint32_t discardOffset = 0;
|
|
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(fmt, bp[offset].postMod, history[h].postMod);
|
|
// MSAA depth is expanded out to floats in the compute shader
|
|
if((uint32_t)callbackInfo.samples > 1)
|
|
history[h].postMod.depth = bp[offset].postMod.depth.fdepth;
|
|
else
|
|
history[h].postMod.depth = GetDepthValue(cb.GetDepthFormat(eid), bp[offset].postMod);
|
|
}
|
|
// If it is not the first fragment for the event, set the preMod to the
|
|
// postMod of the previous fragment.
|
|
if(h > 0 && (history[h].eventId == history[h - 1].eventId))
|
|
{
|
|
history[h].preMod = history[h - 1].postMod;
|
|
}
|
|
}
|
|
|
|
// check the depth value between premod/shaderout against the known test if we have valid
|
|
// depth values, as we don't have per-fragment depth test information.
|
|
if(history[h].preMod.depth >= 0.0f && history[h].shaderOut.depth >= 0.0f && tfCb &&
|
|
tfCb->HasEventFlags(history[h].eventId))
|
|
{
|
|
uint32_t flags = tfCb->GetEventFlags(history[h].eventId);
|
|
|
|
flags &= 0x7 << DepthTest_Shift;
|
|
|
|
VkFormat dfmt = cb.GetDepthFormat(eid);
|
|
float shadDepth = history[h].shaderOut.depth;
|
|
|
|
// quantise depth to match before comparing
|
|
if(dfmt == VK_FORMAT_D24_UNORM_S8_UINT || dfmt == VK_FORMAT_X8_D24_UNORM_PACK32)
|
|
{
|
|
shadDepth = float(uint32_t(float(shadDepth * 0xffffff))) / float(0xffffff);
|
|
}
|
|
else if(dfmt == VK_FORMAT_D16_UNORM || dfmt == VK_FORMAT_D16_UNORM_S8_UINT)
|
|
{
|
|
shadDepth = float(uint32_t(float(shadDepth * 0xffff))) / float(0xffff);
|
|
}
|
|
|
|
bool passed = true;
|
|
if(flags == DepthTest_Equal)
|
|
passed = (shadDepth == history[h].preMod.depth);
|
|
else if(flags == DepthTest_NotEqual)
|
|
passed = (shadDepth != history[h].preMod.depth);
|
|
else if(flags == DepthTest_Less)
|
|
passed = (shadDepth < history[h].preMod.depth);
|
|
else if(flags == DepthTest_LessEqual)
|
|
passed = (shadDepth <= history[h].preMod.depth);
|
|
else if(flags == DepthTest_Greater)
|
|
passed = (shadDepth > history[h].preMod.depth);
|
|
else if(flags == DepthTest_GreaterEqual)
|
|
passed = (shadDepth >= history[h].preMod.depth);
|
|
|
|
history[h].depthTestFailed = !passed;
|
|
}
|
|
}
|
|
}
|
|
|
|
SAFE_DELETE(tfCb);
|
|
|
|
GetDebugManager()->PixelHistoryDestroyResources(resources);
|
|
ObjDisp(dev)->DestroyQueryPool(Unwrap(dev), occlusionPool, NULL);
|
|
delete shaderCache;
|
|
|
|
return history;
|
|
}
|