mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-07-08 16:50:44 +00:00
f256218e17
* Each binding element within an arrayed descriptor has a bool indicating if it's dynamically used or not (which will be set to true if the feedback isn't available). Each descriptor has a uint32_t indicating how many elements are dynamically used - which is useful for the UI to hide the root of an array that has no used elements, or to heuristically decide whether to expand or elide the contents.
3928 lines
134 KiB
C++
3928 lines
134 KiB
C++
/******************************************************************************
|
|
* The MIT License (MIT)
|
|
*
|
|
* Copyright (c) 2015-2019 Baldur Karlsson
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
******************************************************************************/
|
|
|
|
#include "vk_replay.h"
|
|
#include <float.h>
|
|
#include "driver/ihv/amd/amd_rgp.h"
|
|
#include "maths/camera.h"
|
|
#include "maths/formatpacking.h"
|
|
#include "maths/matrix.h"
|
|
#include "serialise/rdcfile.h"
|
|
#include "strings/string_utils.h"
|
|
#include "vk_core.h"
|
|
#include "vk_debug.h"
|
|
#include "vk_resources.h"
|
|
#include "vk_shader_cache.h"
|
|
|
|
#define VULKAN 1
|
|
#include "data/glsl/glsl_ubos_cpp.h"
|
|
|
|
static const char *SPIRVDisassemblyTarget = "SPIR-V (RenderDoc)";
|
|
static const char *LiveDriverDisassemblyTarget = "Live driver disassembly";
|
|
|
|
VulkanReplay::VulkanReplay()
|
|
{
|
|
if(RenderDoc::Inst().GetCrashHandler())
|
|
RenderDoc::Inst().GetCrashHandler()->RegisterMemoryRegion(this, sizeof(VulkanReplay));
|
|
|
|
m_pDriver = NULL;
|
|
m_Proxy = false;
|
|
|
|
m_HighlightCache.driver = this;
|
|
|
|
m_OutputWinID = 1;
|
|
m_ActiveWinID = 0;
|
|
m_BindDepth = false;
|
|
|
|
m_DebugWidth = m_DebugHeight = 1;
|
|
|
|
RDCEraseEl(m_DriverInfo);
|
|
}
|
|
|
|
VulkanDebugManager *VulkanReplay::GetDebugManager()
|
|
{
|
|
return m_pDriver->GetDebugManager();
|
|
}
|
|
|
|
VulkanResourceManager *VulkanReplay::GetResourceManager()
|
|
{
|
|
return m_pDriver->GetResourceManager();
|
|
}
|
|
|
|
void VulkanReplay::Shutdown()
|
|
{
|
|
SAFE_DELETE(m_RGP);
|
|
|
|
m_pDriver->Shutdown();
|
|
delete m_pDriver;
|
|
}
|
|
|
|
APIProperties VulkanReplay::GetAPIProperties()
|
|
{
|
|
APIProperties ret = m_pDriver->APIProps;
|
|
|
|
ret.pipelineType = GraphicsAPI::Vulkan;
|
|
ret.localRenderer = GraphicsAPI::Vulkan;
|
|
ret.degraded = false;
|
|
ret.shadersMutable = false;
|
|
ret.rgpCapture = m_RGP != NULL && m_RGP->DriverSupportsInterop();
|
|
|
|
return ret;
|
|
}
|
|
|
|
ReplayStatus VulkanReplay::ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers)
|
|
{
|
|
return m_pDriver->ReadLogInitialisation(rdc, storeStructuredBuffers);
|
|
}
|
|
|
|
void VulkanReplay::ReplayLog(uint32_t endEventID, ReplayLogType replayType)
|
|
{
|
|
m_pDriver->ReplayLog(0, endEventID, replayType);
|
|
}
|
|
|
|
const SDFile &VulkanReplay::GetStructuredFile()
|
|
{
|
|
return m_pDriver->GetStructuredFile();
|
|
}
|
|
|
|
vector<uint32_t> VulkanReplay::GetPassEvents(uint32_t eventId)
|
|
{
|
|
vector<uint32_t> passEvents;
|
|
|
|
const DrawcallDescription *draw = m_pDriver->GetDrawcall(eventId);
|
|
|
|
if(!draw)
|
|
return passEvents;
|
|
|
|
// for vulkan a pass == a renderpass, if we're not inside a
|
|
// renderpass then there are no pass events.
|
|
const DrawcallDescription *start = draw;
|
|
while(start)
|
|
{
|
|
// if we've come to the beginning of a pass, break out of the loop, we've
|
|
// found the start.
|
|
// Note that vkCmdNextSubPass has both Begin and End flags set, so it will
|
|
// break out here before we hit the terminating case looking for DrawFlags::EndPass
|
|
if(start->flags & DrawFlags::BeginPass)
|
|
break;
|
|
|
|
// if we come to the END of a pass, since we were iterating backwards that
|
|
// means we started outside of a pass, so return empty set.
|
|
// Note that vkCmdNextSubPass has both Begin and End flags set, so it will
|
|
// break out above before we hit this terminating case
|
|
if(start->flags & DrawFlags::EndPass)
|
|
return passEvents;
|
|
|
|
// if we've come to the start of the log we were outside of a render pass
|
|
// to start with
|
|
if(start->previous == NULL)
|
|
return passEvents;
|
|
|
|
// step back
|
|
start = start->previous;
|
|
|
|
// something went wrong, start->previous was non-zero but we didn't
|
|
// get a draw. Abort
|
|
if(!start)
|
|
return passEvents;
|
|
}
|
|
|
|
// store all the draw eventIDs up to the one specified at the start
|
|
while(start)
|
|
{
|
|
if(start == draw)
|
|
break;
|
|
|
|
// include pass boundaries, these will be filtered out later
|
|
// so we don't actually do anything (init postvs/draw overlay)
|
|
// but it's useful to have the first part of the pass as part
|
|
// of the list
|
|
if(start->flags & (DrawFlags::Drawcall | DrawFlags::PassBoundary))
|
|
passEvents.push_back(start->eventId);
|
|
|
|
start = start->next;
|
|
}
|
|
|
|
return passEvents;
|
|
}
|
|
|
|
ResourceId VulkanReplay::GetLiveID(ResourceId id)
|
|
{
|
|
if(!m_pDriver->GetResourceManager()->HasLiveResource(id))
|
|
return ResourceId();
|
|
return m_pDriver->GetResourceManager()->GetLiveID(id);
|
|
}
|
|
|
|
FrameRecord VulkanReplay::GetFrameRecord()
|
|
{
|
|
return m_pDriver->GetFrameRecord();
|
|
}
|
|
|
|
vector<DebugMessage> VulkanReplay::GetDebugMessages()
|
|
{
|
|
return m_pDriver->GetDebugMessages();
|
|
}
|
|
|
|
ResourceDescription &VulkanReplay::GetResourceDesc(ResourceId id)
|
|
{
|
|
auto it = m_ResourceIdx.find(id);
|
|
if(it == m_ResourceIdx.end())
|
|
{
|
|
m_ResourceIdx[id] = m_Resources.size();
|
|
m_Resources.push_back(ResourceDescription());
|
|
m_Resources.back().resourceId = id;
|
|
return m_Resources.back();
|
|
}
|
|
|
|
return m_Resources[it->second];
|
|
}
|
|
|
|
const std::vector<ResourceDescription> &VulkanReplay::GetResources()
|
|
{
|
|
return m_Resources;
|
|
}
|
|
|
|
std::vector<ResourceId> VulkanReplay::GetTextures()
|
|
{
|
|
std::vector<ResourceId> texs;
|
|
|
|
for(auto it = m_pDriver->m_ImageLayouts.begin(); it != m_pDriver->m_ImageLayouts.end(); ++it)
|
|
{
|
|
// skip textures that aren't from the capture
|
|
if(m_pDriver->GetResourceManager()->GetOriginalID(it->first) == it->first)
|
|
continue;
|
|
|
|
texs.push_back(it->first);
|
|
}
|
|
|
|
return texs;
|
|
}
|
|
|
|
std::vector<ResourceId> VulkanReplay::GetBuffers()
|
|
{
|
|
std::vector<ResourceId> bufs;
|
|
|
|
for(auto it = m_pDriver->m_CreationInfo.m_Buffer.begin();
|
|
it != m_pDriver->m_CreationInfo.m_Buffer.end(); ++it)
|
|
{
|
|
// skip textures that aren't from the capture
|
|
if(m_pDriver->GetResourceManager()->GetOriginalID(it->first) == it->first)
|
|
continue;
|
|
|
|
bufs.push_back(it->first);
|
|
}
|
|
|
|
return bufs;
|
|
}
|
|
|
|
TextureDescription VulkanReplay::GetTexture(ResourceId id)
|
|
{
|
|
VulkanCreationInfo::Image &iminfo = m_pDriver->m_CreationInfo.m_Image[id];
|
|
|
|
TextureDescription ret = {};
|
|
ret.resourceId = m_pDriver->GetResourceManager()->GetOriginalID(id);
|
|
ret.arraysize = iminfo.arrayLayers;
|
|
ret.creationFlags = iminfo.creationFlags;
|
|
ret.cubemap = iminfo.cube;
|
|
ret.width = iminfo.extent.width;
|
|
ret.height = iminfo.extent.height;
|
|
ret.depth = iminfo.extent.depth;
|
|
ret.mips = iminfo.mipLevels;
|
|
|
|
ret.byteSize = 0;
|
|
for(uint32_t s = 0; s < ret.mips; s++)
|
|
ret.byteSize += GetByteSize(ret.width, ret.height, ret.depth, iminfo.format, s);
|
|
ret.byteSize *= ret.arraysize;
|
|
|
|
ret.msQual = 0;
|
|
ret.msSamp = RDCMAX(1U, (uint32_t)iminfo.samples);
|
|
|
|
ret.format = MakeResourceFormat(iminfo.format);
|
|
|
|
switch(iminfo.type)
|
|
{
|
|
case VK_IMAGE_TYPE_1D:
|
|
ret.type = iminfo.arrayLayers > 1 ? TextureType::Texture1DArray : TextureType::Texture1D;
|
|
ret.dimension = 1;
|
|
break;
|
|
case VK_IMAGE_TYPE_2D:
|
|
if(ret.msSamp > 1)
|
|
ret.type = iminfo.arrayLayers > 1 ? TextureType::Texture2DMSArray : TextureType::Texture2DMS;
|
|
else if(ret.cubemap)
|
|
ret.type = iminfo.arrayLayers > 6 ? TextureType::TextureCubeArray : TextureType::TextureCube;
|
|
else
|
|
ret.type = iminfo.arrayLayers > 1 ? TextureType::Texture2DArray : TextureType::Texture2D;
|
|
ret.dimension = 2;
|
|
break;
|
|
case VK_IMAGE_TYPE_3D:
|
|
ret.type = TextureType::Texture3D;
|
|
ret.dimension = 3;
|
|
break;
|
|
default:
|
|
ret.dimension = 2;
|
|
RDCERR("Unexpected image type");
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
BufferDescription VulkanReplay::GetBuffer(ResourceId id)
|
|
{
|
|
VulkanCreationInfo::Buffer &bufinfo = m_pDriver->m_CreationInfo.m_Buffer[id];
|
|
|
|
BufferDescription ret;
|
|
ret.resourceId = m_pDriver->GetResourceManager()->GetOriginalID(id);
|
|
ret.length = bufinfo.size;
|
|
ret.creationFlags = BufferCategory::NoFlags;
|
|
|
|
if(bufinfo.usage & (VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT))
|
|
ret.creationFlags |= BufferCategory::ReadWrite;
|
|
if(bufinfo.usage & (VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT))
|
|
ret.creationFlags |= BufferCategory::Constants;
|
|
if(bufinfo.usage & (VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT))
|
|
ret.creationFlags |= BufferCategory::Indirect;
|
|
if(bufinfo.usage & (VK_BUFFER_USAGE_INDEX_BUFFER_BIT))
|
|
ret.creationFlags |= BufferCategory::Index;
|
|
if(bufinfo.usage & (VK_BUFFER_USAGE_VERTEX_BUFFER_BIT))
|
|
ret.creationFlags |= BufferCategory::Vertex;
|
|
|
|
return ret;
|
|
}
|
|
|
|
rdcarray<ShaderEntryPoint> VulkanReplay::GetShaderEntryPoints(ResourceId shader)
|
|
{
|
|
auto shad = m_pDriver->m_CreationInfo.m_ShaderModule.find(shader);
|
|
|
|
if(shad == m_pDriver->m_CreationInfo.m_ShaderModule.end())
|
|
return {};
|
|
|
|
std::vector<std::string> entries = shad->second.spirv.EntryPoints();
|
|
|
|
rdcarray<ShaderEntryPoint> ret;
|
|
|
|
for(const std::string &e : entries)
|
|
ret.push_back({e, shad->second.spirv.StageForEntry(e)});
|
|
|
|
return ret;
|
|
}
|
|
|
|
ShaderReflection *VulkanReplay::GetShader(ResourceId shader, ShaderEntryPoint entry)
|
|
{
|
|
auto shad = m_pDriver->m_CreationInfo.m_ShaderModule.find(shader);
|
|
|
|
if(shad == m_pDriver->m_CreationInfo.m_ShaderModule.end())
|
|
{
|
|
RDCERR("Can't get shader details");
|
|
return NULL;
|
|
}
|
|
|
|
shad->second.m_Reflections[entry.name].Init(GetResourceManager(), shader, shad->second.spirv,
|
|
entry.name,
|
|
VkShaderStageFlagBits(1 << uint32_t(entry.stage)));
|
|
|
|
return &shad->second.m_Reflections[entry.name].refl;
|
|
}
|
|
|
|
vector<string> VulkanReplay::GetDisassemblyTargets()
|
|
{
|
|
vector<string> ret;
|
|
|
|
VkDevice dev = m_pDriver->GetDev();
|
|
const VkLayerDispatchTable *vt = ObjDisp(dev);
|
|
|
|
if(vt->GetShaderInfoAMD)
|
|
ret.push_back(LiveDriverDisassemblyTarget);
|
|
|
|
// default is always first
|
|
ret.insert(ret.begin(), SPIRVDisassemblyTarget);
|
|
|
|
// could add canonical disassembly here if spirv-dis is available
|
|
// Ditto for SPIRV-cross (to glsl/hlsl)
|
|
|
|
return ret;
|
|
}
|
|
|
|
string VulkanReplay::DisassembleShader(ResourceId pipeline, const ShaderReflection *refl,
|
|
const string &target)
|
|
{
|
|
auto it = m_pDriver->m_CreationInfo.m_ShaderModule.find(
|
|
GetResourceManager()->GetLiveID(refl->resourceId));
|
|
|
|
if(it == m_pDriver->m_CreationInfo.m_ShaderModule.end())
|
|
return "; Invalid Shader Specified";
|
|
|
|
if(target == SPIRVDisassemblyTarget || target.empty())
|
|
{
|
|
std::string &disasm = it->second.m_Reflections[refl->entryPoint.c_str()].disassembly;
|
|
|
|
if(disasm.empty())
|
|
disasm = it->second.spirv.Disassemble(refl->entryPoint.c_str());
|
|
|
|
return disasm;
|
|
}
|
|
|
|
VkDevice dev = m_pDriver->GetDev();
|
|
const VkLayerDispatchTable *vt = ObjDisp(dev);
|
|
|
|
if(target == LiveDriverDisassemblyTarget && vt->GetShaderInfoAMD)
|
|
{
|
|
if(pipeline == ResourceId())
|
|
{
|
|
return "; No pipeline specified, live driver disassembly is not available\n"
|
|
"; Shader must be disassembled with a specific pipeline to get live driver assembly.";
|
|
}
|
|
|
|
VkPipeline pipe = m_pDriver->GetResourceManager()->GetLiveHandle<VkPipeline>(pipeline);
|
|
|
|
VkShaderStageFlagBits stageBit =
|
|
VkShaderStageFlagBits(1 << it->second.m_Reflections[refl->entryPoint.c_str()].stageIndex);
|
|
|
|
size_t size;
|
|
vt->GetShaderInfoAMD(Unwrap(dev), Unwrap(pipe), stageBit, VK_SHADER_INFO_TYPE_DISASSEMBLY_AMD,
|
|
&size, NULL);
|
|
|
|
std::string disasm;
|
|
disasm.resize(size);
|
|
vt->GetShaderInfoAMD(Unwrap(dev), Unwrap(pipe), stageBit, VK_SHADER_INFO_TYPE_DISASSEMBLY_AMD,
|
|
&size, (void *)disasm.data());
|
|
|
|
return disasm;
|
|
}
|
|
|
|
return StringFormat::Fmt("; Invalid disassembly target %s", target.c_str());
|
|
}
|
|
|
|
void VulkanReplay::PickPixel(ResourceId texture, uint32_t x, uint32_t y, uint32_t sliceFace,
|
|
uint32_t mip, uint32_t sample, CompType typeHint, float pixel[4])
|
|
{
|
|
int oldW = m_DebugWidth, oldH = m_DebugHeight;
|
|
|
|
m_DebugWidth = m_DebugHeight = 1;
|
|
|
|
VulkanCreationInfo::Image &iminfo = m_pDriver->m_CreationInfo.m_Image[texture];
|
|
|
|
bool isStencil = IsStencilFormat(iminfo.format);
|
|
|
|
// do a second pass to render the stencil, if needed
|
|
for(int pass = 0; pass < (isStencil ? 2 : 1); pass++)
|
|
{
|
|
// render picked pixel to readback F32 RGBA texture
|
|
{
|
|
TextureDisplay texDisplay;
|
|
|
|
texDisplay.red = texDisplay.green = texDisplay.blue = texDisplay.alpha = true;
|
|
texDisplay.hdrMultiplier = -1.0f;
|
|
texDisplay.linearDisplayAsGamma = true;
|
|
texDisplay.flipY = false;
|
|
texDisplay.mip = mip;
|
|
texDisplay.sampleIdx = sample;
|
|
texDisplay.customShaderId = ResourceId();
|
|
texDisplay.sliceFace = sliceFace;
|
|
texDisplay.overlay = DebugOverlay::NoOverlay;
|
|
texDisplay.rangeMin = 0.0f;
|
|
texDisplay.rangeMax = 1.0f;
|
|
texDisplay.scale = 1.0f;
|
|
texDisplay.resourceId = texture;
|
|
texDisplay.typeHint = typeHint;
|
|
texDisplay.rawOutput = true;
|
|
texDisplay.xOffset = -float(x);
|
|
texDisplay.yOffset = -float(y);
|
|
|
|
// only render green (stencil) in second pass
|
|
if(pass == 1)
|
|
{
|
|
texDisplay.green = true;
|
|
texDisplay.red = texDisplay.blue = texDisplay.alpha = false;
|
|
}
|
|
|
|
VkClearValue clearval = {};
|
|
VkRenderPassBeginInfo rpbegin = {
|
|
VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
|
NULL,
|
|
Unwrap(m_PixelPick.RP),
|
|
Unwrap(m_PixelPick.FB),
|
|
{{
|
|
0, 0,
|
|
},
|
|
{1, 1}},
|
|
1,
|
|
&clearval,
|
|
};
|
|
|
|
RenderTextureInternal(texDisplay, rpbegin, eTexDisplay_F32Render | eTexDisplay_MipShift);
|
|
}
|
|
|
|
VkDevice dev = m_pDriver->GetDev();
|
|
VkCommandBuffer cmd = m_pDriver->GetNextCmd();
|
|
const VkLayerDispatchTable *vt = ObjDisp(dev);
|
|
|
|
VkResult vkr = VK_SUCCESS;
|
|
|
|
{
|
|
VkImageMemoryBarrier pickimBarrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
|
NULL,
|
|
0,
|
|
0,
|
|
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
|
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
Unwrap(m_PixelPick.Image),
|
|
{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
|
|
|
|
// update image layout from color attachment to transfer source, with proper memory barriers
|
|
pickimBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
|
pickimBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
|
|
|
VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, NULL,
|
|
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT};
|
|
|
|
vkr = vt->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
DoPipelineBarrier(cmd, 1, &pickimBarrier);
|
|
pickimBarrier.oldLayout = pickimBarrier.newLayout;
|
|
pickimBarrier.srcAccessMask = pickimBarrier.dstAccessMask;
|
|
|
|
// do copy
|
|
VkBufferImageCopy region = {
|
|
0, 128, 1, {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, {0, 0, 0}, {1, 1, 1},
|
|
};
|
|
vt->CmdCopyImageToBuffer(Unwrap(cmd), Unwrap(m_PixelPick.Image),
|
|
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
|
Unwrap(m_PixelPick.ReadbackBuffer.buf), 1, ®ion);
|
|
|
|
// update image layout back to color attachment
|
|
pickimBarrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
|
pickimBarrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
|
DoPipelineBarrier(cmd, 1, &pickimBarrier);
|
|
|
|
vt->EndCommandBuffer(Unwrap(cmd));
|
|
}
|
|
|
|
// submit cmds and wait for idle so we can readback
|
|
m_pDriver->SubmitCmds();
|
|
m_pDriver->FlushQ();
|
|
|
|
float *pData = NULL;
|
|
vt->MapMemory(Unwrap(dev), Unwrap(m_PixelPick.ReadbackBuffer.mem), 0, VK_WHOLE_SIZE, 0,
|
|
(void **)&pData);
|
|
|
|
VkMappedMemoryRange range = {
|
|
VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
|
|
NULL,
|
|
Unwrap(m_PixelPick.ReadbackBuffer.mem),
|
|
0,
|
|
VK_WHOLE_SIZE,
|
|
};
|
|
|
|
vkr = vt->InvalidateMappedMemoryRanges(Unwrap(dev), 1, &range);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
RDCASSERT(pData != NULL);
|
|
|
|
if(pData == NULL)
|
|
{
|
|
RDCERR("Failed ot map readback buffer memory");
|
|
}
|
|
else
|
|
{
|
|
// only write stencil to .y
|
|
if(pass == 1)
|
|
{
|
|
pixel[1] = ((uint32_t *)pData)[0] / 255.0f;
|
|
}
|
|
else
|
|
{
|
|
pixel[0] = pData[0];
|
|
pixel[1] = pData[1];
|
|
pixel[2] = pData[2];
|
|
pixel[3] = pData[3];
|
|
}
|
|
}
|
|
|
|
vt->UnmapMemory(Unwrap(dev), Unwrap(m_PixelPick.ReadbackBuffer.mem));
|
|
}
|
|
|
|
m_DebugWidth = oldW;
|
|
m_DebugHeight = oldH;
|
|
}
|
|
|
|
void VulkanReplay::RenderCheckerboard()
|
|
{
|
|
auto it = m_OutputWindows.find(m_ActiveWinID);
|
|
if(m_ActiveWinID == 0 || it == m_OutputWindows.end())
|
|
return;
|
|
|
|
OutputWindow &outw = it->second;
|
|
|
|
// if the swapchain failed to create, do nothing. We will try to recreate it
|
|
// again in CheckResizeOutputWindow (once per render 'frame')
|
|
if(outw.swap == VK_NULL_HANDLE)
|
|
return;
|
|
|
|
VkDevice dev = m_pDriver->GetDev();
|
|
VkCommandBuffer cmd = m_pDriver->GetNextCmd();
|
|
const VkLayerDispatchTable *vt = ObjDisp(dev);
|
|
|
|
VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, NULL,
|
|
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT};
|
|
|
|
VkResult vkr = vt->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
uint32_t uboOffs = 0;
|
|
|
|
VkRenderPassBeginInfo rpbegin = {
|
|
VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
|
NULL,
|
|
Unwrap(outw.rp),
|
|
Unwrap(outw.fb),
|
|
{{
|
|
0, 0,
|
|
},
|
|
{m_DebugWidth, m_DebugHeight}},
|
|
0,
|
|
NULL,
|
|
};
|
|
vt->CmdBeginRenderPass(Unwrap(cmd), &rpbegin, VK_SUBPASS_CONTENTS_INLINE);
|
|
|
|
if(m_Overlay.m_CheckerPipeline != VK_NULL_HANDLE)
|
|
{
|
|
CheckerboardUBOData *data = (CheckerboardUBOData *)m_Overlay.m_CheckerUBO.Map(&uboOffs);
|
|
data->BorderWidth = 0.0f;
|
|
data->RectPosition = Vec2f();
|
|
data->RectSize = Vec2f();
|
|
data->CheckerSquareDimension = 64.0f;
|
|
data->InnerColor = Vec4f();
|
|
|
|
data->PrimaryColor = ConvertSRGBToLinear(RenderDoc::Inst().DarkCheckerboardColor());
|
|
data->SecondaryColor = ConvertSRGBToLinear(RenderDoc::Inst().LightCheckerboardColor());
|
|
m_Overlay.m_CheckerUBO.Unmap();
|
|
|
|
vt->CmdBindPipeline(Unwrap(cmd), VK_PIPELINE_BIND_POINT_GRAPHICS,
|
|
outw.dsimg == VK_NULL_HANDLE ? Unwrap(m_Overlay.m_CheckerPipeline)
|
|
: Unwrap(m_Overlay.m_CheckerMSAAPipeline));
|
|
vt->CmdBindDescriptorSets(Unwrap(cmd), VK_PIPELINE_BIND_POINT_GRAPHICS,
|
|
Unwrap(m_Overlay.m_CheckerPipeLayout), 0, 1,
|
|
UnwrapPtr(m_Overlay.m_CheckerDescSet), 1, &uboOffs);
|
|
|
|
VkViewport viewport = {0.0f, 0.0f, (float)m_DebugWidth, (float)m_DebugHeight, 0.0f, 1.0f};
|
|
vt->CmdSetViewport(Unwrap(cmd), 0, 1, &viewport);
|
|
|
|
vt->CmdDraw(Unwrap(cmd), 4, 1, 0, 0);
|
|
|
|
if(m_pDriver->GetDriverInfo().QualcommLeakingUBOOffsets())
|
|
{
|
|
uboOffs = 0;
|
|
vt->CmdBindDescriptorSets(Unwrap(cmd), VK_PIPELINE_BIND_POINT_GRAPHICS,
|
|
Unwrap(m_Overlay.m_CheckerPipeLayout), 0, 1,
|
|
UnwrapPtr(m_Overlay.m_CheckerDescSet), 1, &uboOffs);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// some mobile chips fail to create the checkerboard pipeline. Use an alternate approach with
|
|
// CmdClearAttachment and many rects.
|
|
|
|
Vec4f lightCol = RenderDoc::Inst().LightCheckerboardColor();
|
|
Vec4f darkCol = RenderDoc::Inst().DarkCheckerboardColor();
|
|
|
|
VkClearAttachment light = {
|
|
VK_IMAGE_ASPECT_COLOR_BIT, 0, {{{lightCol.x, lightCol.y, lightCol.z, lightCol.w}}}};
|
|
VkClearAttachment dark = {
|
|
VK_IMAGE_ASPECT_COLOR_BIT, 0, {{{darkCol.x, darkCol.y, darkCol.z, darkCol.w}}}};
|
|
|
|
VkClearRect fullRect = {{
|
|
{0, 0}, {outw.width, outw.height},
|
|
},
|
|
0,
|
|
1};
|
|
|
|
vt->CmdClearAttachments(Unwrap(cmd), 1, &light, 1, &fullRect);
|
|
|
|
std::vector<VkClearRect> squares;
|
|
|
|
for(int32_t y = 0; y < (int32_t)outw.height; y += 128)
|
|
{
|
|
for(int32_t x = 0; x < (int32_t)outw.width; x += 128)
|
|
{
|
|
VkClearRect square = {{
|
|
{x, y}, {64, 64},
|
|
},
|
|
0,
|
|
1};
|
|
|
|
squares.push_back(square);
|
|
|
|
square.rect.offset.x += 64;
|
|
square.rect.offset.y += 64;
|
|
squares.push_back(square);
|
|
}
|
|
}
|
|
|
|
vt->CmdClearAttachments(Unwrap(cmd), 1, &dark, (uint32_t)squares.size(), squares.data());
|
|
}
|
|
|
|
vt->CmdEndRenderPass(Unwrap(cmd));
|
|
|
|
vkr = vt->EndCommandBuffer(Unwrap(cmd));
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
#if ENABLED(SINGLE_FLUSH_VALIDATE)
|
|
m_pDriver->SubmitCmds();
|
|
#endif
|
|
}
|
|
|
|
void VulkanReplay::RenderHighlightBox(float w, float h, float scale)
|
|
{
|
|
auto it = m_OutputWindows.find(m_ActiveWinID);
|
|
if(m_ActiveWinID == 0 || it == m_OutputWindows.end())
|
|
return;
|
|
|
|
OutputWindow &outw = it->second;
|
|
|
|
// if the swapchain failed to create, do nothing. We will try to recreate it
|
|
// again in CheckResizeOutputWindow (once per render 'frame')
|
|
if(outw.swap == VK_NULL_HANDLE)
|
|
return;
|
|
|
|
VkDevice dev = m_pDriver->GetDev();
|
|
VkCommandBuffer cmd = m_pDriver->GetNextCmd();
|
|
const VkLayerDispatchTable *vt = ObjDisp(dev);
|
|
|
|
VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, NULL,
|
|
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT};
|
|
|
|
VkResult vkr = vt->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
{
|
|
VkRenderPassBeginInfo rpbegin = {
|
|
VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
|
NULL,
|
|
Unwrap(outw.rp),
|
|
Unwrap(outw.fb),
|
|
{{
|
|
0, 0,
|
|
},
|
|
{m_DebugWidth, m_DebugHeight}},
|
|
0,
|
|
NULL,
|
|
};
|
|
vt->CmdBeginRenderPass(Unwrap(cmd), &rpbegin, VK_SUBPASS_CONTENTS_INLINE);
|
|
|
|
VkClearAttachment black = {VK_IMAGE_ASPECT_COLOR_BIT, 0, {{{0.0f, 0.0f, 0.0f, 1.0f}}}};
|
|
VkClearAttachment white = {VK_IMAGE_ASPECT_COLOR_BIT, 0, {{{1.0f, 1.0f, 1.0f, 1.0f}}}};
|
|
|
|
uint32_t sz = uint32_t(scale);
|
|
|
|
VkOffset2D tl = {int32_t(w / 2.0f + 0.5f), int32_t(h / 2.0f + 0.5f)};
|
|
|
|
VkClearRect rect[4] = {
|
|
{{
|
|
{tl.x, tl.y}, {1, sz},
|
|
},
|
|
0,
|
|
1},
|
|
{{
|
|
{tl.x + (int32_t)sz, tl.y}, {1, sz + 1},
|
|
},
|
|
0,
|
|
1},
|
|
{{
|
|
{tl.x, tl.y}, {sz, 1},
|
|
},
|
|
0,
|
|
1},
|
|
{{
|
|
{tl.x, tl.y + (int32_t)sz}, {sz, 1},
|
|
},
|
|
0,
|
|
1},
|
|
};
|
|
|
|
// inner
|
|
vt->CmdClearAttachments(Unwrap(cmd), 1, &white, 4, rect);
|
|
|
|
rect[0].rect.offset.x--;
|
|
rect[1].rect.offset.x++;
|
|
rect[2].rect.offset.x--;
|
|
rect[3].rect.offset.x--;
|
|
|
|
rect[0].rect.offset.y--;
|
|
rect[1].rect.offset.y--;
|
|
rect[2].rect.offset.y--;
|
|
rect[3].rect.offset.y++;
|
|
|
|
rect[0].rect.extent.height += 2;
|
|
rect[1].rect.extent.height += 2;
|
|
rect[2].rect.extent.width += 2;
|
|
rect[3].rect.extent.width += 2;
|
|
|
|
// outer
|
|
vt->CmdClearAttachments(Unwrap(cmd), 1, &black, 4, rect);
|
|
|
|
vt->CmdEndRenderPass(Unwrap(cmd));
|
|
}
|
|
|
|
vkr = vt->EndCommandBuffer(Unwrap(cmd));
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
#if ENABLED(SINGLE_FLUSH_VALIDATE)
|
|
m_pDriver->SubmitCmds();
|
|
#endif
|
|
}
|
|
|
|
void VulkanReplay::GetBufferData(ResourceId buff, uint64_t offset, uint64_t len, bytebuf &retData)
|
|
{
|
|
GetDebugManager()->GetBufferData(buff, offset, len, retData);
|
|
}
|
|
|
|
bool VulkanReplay::IsRenderOutput(ResourceId id)
|
|
{
|
|
for(const VKPipe::Attachment &att : m_VulkanPipelineState.currentPass.framebuffer.attachments)
|
|
{
|
|
if(att.viewResourceId == id || att.imageResourceId == id)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void VulkanReplay::FileChanged()
|
|
{
|
|
}
|
|
|
|
void VulkanReplay::GetInitialDriverVersion()
|
|
{
|
|
RDCEraseEl(m_DriverInfo);
|
|
|
|
VkInstance inst = m_pDriver->GetInstance();
|
|
|
|
uint32_t count;
|
|
VkResult vkr = ObjDisp(inst)->EnumeratePhysicalDevices(Unwrap(inst), &count, NULL);
|
|
|
|
if(vkr != VK_SUCCESS)
|
|
{
|
|
RDCERR("Couldn't enumerate physical devices");
|
|
return;
|
|
}
|
|
|
|
if(count == 0)
|
|
{
|
|
RDCERR("No physical devices available");
|
|
}
|
|
|
|
count = 1;
|
|
VkPhysicalDevice firstDevice = VK_NULL_HANDLE;
|
|
|
|
vkr = ObjDisp(inst)->EnumeratePhysicalDevices(Unwrap(inst), &count, &firstDevice);
|
|
|
|
// incomplete is expected if multiple GPUs are present, and we're just grabbing the first
|
|
if(vkr != VK_SUCCESS && vkr != VK_INCOMPLETE)
|
|
{
|
|
RDCERR("Couldn't fetch first physical device");
|
|
return;
|
|
}
|
|
|
|
VkPhysicalDeviceProperties props;
|
|
ObjDisp(inst)->GetPhysicalDeviceProperties(firstDevice, &props);
|
|
|
|
SetDriverInformation(props);
|
|
}
|
|
|
|
void VulkanReplay::SetDriverInformation(const VkPhysicalDeviceProperties &props)
|
|
{
|
|
VkDriverInfo info(props);
|
|
m_DriverInfo.vendor = info.Vendor();
|
|
std::string versionString =
|
|
StringFormat::Fmt("%s %u.%u.%u", props.deviceName, info.Major(), info.Minor(), info.Patch());
|
|
versionString.resize(RDCMIN(versionString.size(), ARRAY_COUNT(m_DriverInfo.version) - 1));
|
|
memcpy(m_DriverInfo.version, versionString.c_str(), versionString.size());
|
|
}
|
|
|
|
void VulkanReplay::SavePipelineState(uint32_t eventId)
|
|
{
|
|
const VulkanRenderState &state = m_pDriver->m_RenderState;
|
|
VulkanCreationInfo &c = m_pDriver->m_CreationInfo;
|
|
|
|
VulkanResourceManager *rm = m_pDriver->GetResourceManager();
|
|
|
|
VkMarkerRegion::Begin(StringFormat::Fmt("FetchShaderFeedback for %u", eventId));
|
|
|
|
FetchShaderFeedback(eventId);
|
|
|
|
VkMarkerRegion::End();
|
|
|
|
m_VulkanPipelineState = VKPipe::State();
|
|
|
|
m_VulkanPipelineState.pushconsts.resize(state.pushConstSize);
|
|
memcpy(m_VulkanPipelineState.pushconsts.data(), state.pushconsts, state.pushConstSize);
|
|
|
|
// General pipeline properties
|
|
m_VulkanPipelineState.compute.pipelineResourceId = rm->GetOriginalID(state.compute.pipeline);
|
|
m_VulkanPipelineState.graphics.pipelineResourceId = rm->GetOriginalID(state.graphics.pipeline);
|
|
|
|
if(state.compute.pipeline != ResourceId())
|
|
{
|
|
const VulkanCreationInfo::Pipeline &p = c.m_Pipeline[state.compute.pipeline];
|
|
|
|
m_VulkanPipelineState.compute.pipelineLayoutResourceId = rm->GetOriginalID(p.layout);
|
|
|
|
m_VulkanPipelineState.compute.flags = p.flags;
|
|
|
|
VKPipe::Shader &stage = m_VulkanPipelineState.computeShader;
|
|
|
|
int i = 5; // 5 is the CS idx (VS, TCS, TES, GS, FS, CS)
|
|
{
|
|
stage.resourceId = rm->GetOriginalID(p.shaders[i].module);
|
|
stage.entryPoint = p.shaders[i].entryPoint;
|
|
|
|
stage.stage = ShaderStage::Compute;
|
|
if(p.shaders[i].mapping)
|
|
stage.bindpointMapping = *p.shaders[i].mapping;
|
|
if(p.shaders[i].refl)
|
|
stage.reflection = p.shaders[i].refl;
|
|
|
|
stage.specialization.resize(p.shaders[i].specialization.size());
|
|
for(size_t s = 0; s < p.shaders[i].specialization.size(); s++)
|
|
{
|
|
stage.specialization[s].specializationId = p.shaders[i].specialization[s].specID;
|
|
stage.specialization[s].data = p.shaders[i].specialization[s].data;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_VulkanPipelineState.compute.pipelineLayoutResourceId = ResourceId();
|
|
m_VulkanPipelineState.compute.flags = 0;
|
|
m_VulkanPipelineState.computeShader = VKPipe::Shader();
|
|
}
|
|
|
|
if(state.graphics.pipeline != ResourceId())
|
|
{
|
|
const VulkanCreationInfo::Pipeline &p = c.m_Pipeline[state.graphics.pipeline];
|
|
|
|
m_VulkanPipelineState.graphics.pipelineLayoutResourceId = rm->GetOriginalID(p.layout);
|
|
|
|
m_VulkanPipelineState.graphics.flags = p.flags;
|
|
|
|
// Input Assembly
|
|
m_VulkanPipelineState.inputAssembly.indexBuffer.resourceId = rm->GetOriginalID(state.ibuffer.buf);
|
|
m_VulkanPipelineState.inputAssembly.indexBuffer.byteOffset = state.ibuffer.offs;
|
|
m_VulkanPipelineState.inputAssembly.primitiveRestartEnable = p.primitiveRestartEnable;
|
|
|
|
// Vertex Input
|
|
m_VulkanPipelineState.vertexInput.attributes.resize(p.vertexAttrs.size());
|
|
for(size_t i = 0; i < p.vertexAttrs.size(); i++)
|
|
{
|
|
m_VulkanPipelineState.vertexInput.attributes[i].location = p.vertexAttrs[i].location;
|
|
m_VulkanPipelineState.vertexInput.attributes[i].binding = p.vertexAttrs[i].binding;
|
|
m_VulkanPipelineState.vertexInput.attributes[i].byteOffset = p.vertexAttrs[i].byteoffset;
|
|
m_VulkanPipelineState.vertexInput.attributes[i].format =
|
|
MakeResourceFormat(p.vertexAttrs[i].format);
|
|
}
|
|
|
|
m_VulkanPipelineState.vertexInput.bindings.resize(p.vertexBindings.size());
|
|
for(size_t i = 0; i < p.vertexBindings.size(); i++)
|
|
{
|
|
m_VulkanPipelineState.vertexInput.bindings[i].byteStride = p.vertexBindings[i].bytestride;
|
|
m_VulkanPipelineState.vertexInput.bindings[i].vertexBufferBinding =
|
|
p.vertexBindings[i].vbufferBinding;
|
|
m_VulkanPipelineState.vertexInput.bindings[i].perInstance = p.vertexBindings[i].perInstance;
|
|
m_VulkanPipelineState.vertexInput.bindings[i].instanceDivisor =
|
|
p.vertexBindings[i].instanceDivisor;
|
|
}
|
|
|
|
m_VulkanPipelineState.vertexInput.vertexBuffers.resize(state.vbuffers.size());
|
|
for(size_t i = 0; i < state.vbuffers.size(); i++)
|
|
{
|
|
m_VulkanPipelineState.vertexInput.vertexBuffers[i].resourceId =
|
|
rm->GetOriginalID(state.vbuffers[i].buf);
|
|
m_VulkanPipelineState.vertexInput.vertexBuffers[i].byteOffset = state.vbuffers[i].offs;
|
|
}
|
|
|
|
// Shader Stages
|
|
VKPipe::Shader *stages[] = {
|
|
&m_VulkanPipelineState.vertexShader, &m_VulkanPipelineState.tessControlShader,
|
|
&m_VulkanPipelineState.tessEvalShader, &m_VulkanPipelineState.geometryShader,
|
|
&m_VulkanPipelineState.fragmentShader,
|
|
};
|
|
|
|
for(size_t i = 0; i < ARRAY_COUNT(stages); i++)
|
|
{
|
|
stages[i]->resourceId = rm->GetOriginalID(p.shaders[i].module);
|
|
stages[i]->entryPoint = p.shaders[i].entryPoint;
|
|
|
|
stages[i]->stage = StageFromIndex(i);
|
|
if(p.shaders[i].mapping)
|
|
stages[i]->bindpointMapping = *p.shaders[i].mapping;
|
|
if(p.shaders[i].refl)
|
|
stages[i]->reflection = p.shaders[i].refl;
|
|
|
|
stages[i]->specialization.resize(p.shaders[i].specialization.size());
|
|
for(size_t s = 0; s < p.shaders[i].specialization.size(); s++)
|
|
{
|
|
stages[i]->specialization[s].specializationId = p.shaders[i].specialization[s].specID;
|
|
stages[i]->specialization[s].data = p.shaders[i].specialization[s].data;
|
|
}
|
|
}
|
|
|
|
// Tessellation
|
|
m_VulkanPipelineState.tessellation.numControlPoints = p.patchControlPoints;
|
|
|
|
m_VulkanPipelineState.tessellation.domainOriginUpperLeft =
|
|
p.tessellationDomainOrigin == VK_TESSELLATION_DOMAIN_ORIGIN_UPPER_LEFT;
|
|
|
|
// Transform feedback
|
|
m_VulkanPipelineState.transformFeedback.buffers.resize(state.xfbbuffers.size());
|
|
for(size_t i = 0; i < state.xfbbuffers.size(); i++)
|
|
{
|
|
m_VulkanPipelineState.transformFeedback.buffers[i].bufferResourceId =
|
|
rm->GetOriginalID(state.xfbbuffers[i].buf);
|
|
m_VulkanPipelineState.transformFeedback.buffers[i].byteOffset = state.xfbbuffers[i].offs;
|
|
m_VulkanPipelineState.transformFeedback.buffers[i].byteSize = state.xfbbuffers[i].size;
|
|
|
|
m_VulkanPipelineState.transformFeedback.buffers[i].active = false;
|
|
m_VulkanPipelineState.transformFeedback.buffers[i].counterBufferResourceId = ResourceId();
|
|
m_VulkanPipelineState.transformFeedback.buffers[i].counterBufferOffset = 0;
|
|
|
|
if(i >= state.firstxfbcounter)
|
|
{
|
|
size_t xfb = i - state.firstxfbcounter;
|
|
if(xfb < state.xfbcounters.size())
|
|
{
|
|
m_VulkanPipelineState.transformFeedback.buffers[i].active = true;
|
|
m_VulkanPipelineState.transformFeedback.buffers[i].counterBufferResourceId =
|
|
rm->GetOriginalID(state.xfbcounters[xfb].buf);
|
|
m_VulkanPipelineState.transformFeedback.buffers[i].counterBufferOffset =
|
|
state.xfbcounters[xfb].offs;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Viewport/Scissors
|
|
size_t numViewScissors = p.viewportCount;
|
|
m_VulkanPipelineState.viewportScissor.viewportScissors.resize(numViewScissors);
|
|
for(size_t i = 0; i < numViewScissors; i++)
|
|
{
|
|
if(i < state.views.size())
|
|
{
|
|
m_VulkanPipelineState.viewportScissor.viewportScissors[i].vp.x = state.views[i].x;
|
|
m_VulkanPipelineState.viewportScissor.viewportScissors[i].vp.y = state.views[i].y;
|
|
m_VulkanPipelineState.viewportScissor.viewportScissors[i].vp.width = state.views[i].width;
|
|
m_VulkanPipelineState.viewportScissor.viewportScissors[i].vp.height = state.views[i].height;
|
|
m_VulkanPipelineState.viewportScissor.viewportScissors[i].vp.minDepth =
|
|
state.views[i].minDepth;
|
|
m_VulkanPipelineState.viewportScissor.viewportScissors[i].vp.maxDepth =
|
|
state.views[i].maxDepth;
|
|
}
|
|
else
|
|
{
|
|
RDCEraseEl(m_VulkanPipelineState.viewportScissor.viewportScissors[i].vp);
|
|
}
|
|
|
|
if(i < state.scissors.size())
|
|
{
|
|
m_VulkanPipelineState.viewportScissor.viewportScissors[i].scissor.x =
|
|
state.scissors[i].offset.x;
|
|
m_VulkanPipelineState.viewportScissor.viewportScissors[i].scissor.y =
|
|
state.scissors[i].offset.y;
|
|
m_VulkanPipelineState.viewportScissor.viewportScissors[i].scissor.width =
|
|
state.scissors[i].extent.width;
|
|
m_VulkanPipelineState.viewportScissor.viewportScissors[i].scissor.height =
|
|
state.scissors[i].extent.height;
|
|
}
|
|
else
|
|
{
|
|
RDCEraseEl(m_VulkanPipelineState.viewportScissor.viewportScissors[i].scissor);
|
|
}
|
|
}
|
|
|
|
{
|
|
m_VulkanPipelineState.viewportScissor.discardRectangles.resize(p.discardRectangles.size());
|
|
for(size_t i = 0; i < p.discardRectangles.size() && i < state.discardRectangles.size(); i++)
|
|
{
|
|
m_VulkanPipelineState.viewportScissor.discardRectangles[i].x =
|
|
state.discardRectangles[i].offset.x;
|
|
m_VulkanPipelineState.viewportScissor.discardRectangles[i].y =
|
|
state.discardRectangles[i].offset.y;
|
|
m_VulkanPipelineState.viewportScissor.discardRectangles[i].width =
|
|
state.discardRectangles[i].extent.width;
|
|
m_VulkanPipelineState.viewportScissor.discardRectangles[i].height =
|
|
state.discardRectangles[i].extent.height;
|
|
}
|
|
|
|
m_VulkanPipelineState.viewportScissor.discardRectanglesExclusive =
|
|
(p.discardMode == VK_DISCARD_RECTANGLE_MODE_EXCLUSIVE_EXT);
|
|
}
|
|
|
|
// Rasterizer
|
|
m_VulkanPipelineState.rasterizer.depthClampEnable = p.depthClampEnable;
|
|
m_VulkanPipelineState.rasterizer.depthClipEnable = p.depthClipEnable;
|
|
m_VulkanPipelineState.rasterizer.rasterizerDiscardEnable = p.rasterizerDiscardEnable;
|
|
m_VulkanPipelineState.rasterizer.frontCCW = p.frontFace == VK_FRONT_FACE_COUNTER_CLOCKWISE;
|
|
|
|
m_VulkanPipelineState.rasterizer.conservativeRasterization = ConservativeRaster::Disabled;
|
|
switch(p.conservativeRasterizationMode)
|
|
{
|
|
case VK_CONSERVATIVE_RASTERIZATION_MODE_UNDERESTIMATE_EXT:
|
|
m_VulkanPipelineState.rasterizer.conservativeRasterization =
|
|
ConservativeRaster::Underestimate;
|
|
break;
|
|
case VK_CONSERVATIVE_RASTERIZATION_MODE_OVERESTIMATE_EXT:
|
|
m_VulkanPipelineState.rasterizer.conservativeRasterization = ConservativeRaster::Overestimate;
|
|
break;
|
|
default: break;
|
|
}
|
|
|
|
m_VulkanPipelineState.rasterizer.extraPrimitiveOverestimationSize =
|
|
p.extraPrimitiveOverestimationSize;
|
|
|
|
switch(p.polygonMode)
|
|
{
|
|
case VK_POLYGON_MODE_POINT:
|
|
m_VulkanPipelineState.rasterizer.fillMode = FillMode::Point;
|
|
break;
|
|
case VK_POLYGON_MODE_LINE:
|
|
m_VulkanPipelineState.rasterizer.fillMode = FillMode::Wireframe;
|
|
break;
|
|
case VK_POLYGON_MODE_FILL: m_VulkanPipelineState.rasterizer.fillMode = FillMode::Solid; break;
|
|
default:
|
|
m_VulkanPipelineState.rasterizer.fillMode = FillMode::Solid;
|
|
RDCERR("Unexpected value for FillMode %x", p.polygonMode);
|
|
break;
|
|
}
|
|
|
|
switch(p.cullMode)
|
|
{
|
|
case VK_CULL_MODE_NONE: m_VulkanPipelineState.rasterizer.cullMode = CullMode::NoCull; break;
|
|
case VK_CULL_MODE_FRONT_BIT:
|
|
m_VulkanPipelineState.rasterizer.cullMode = CullMode::Front;
|
|
break;
|
|
case VK_CULL_MODE_BACK_BIT: m_VulkanPipelineState.rasterizer.cullMode = CullMode::Back; break;
|
|
case VK_CULL_MODE_FRONT_AND_BACK:
|
|
m_VulkanPipelineState.rasterizer.cullMode = CullMode::FrontAndBack;
|
|
break;
|
|
default:
|
|
m_VulkanPipelineState.rasterizer.cullMode = CullMode::NoCull;
|
|
RDCERR("Unexpected value for CullMode %x", p.cullMode);
|
|
break;
|
|
}
|
|
|
|
m_VulkanPipelineState.rasterizer.depthBias = state.bias.depth;
|
|
m_VulkanPipelineState.rasterizer.depthBiasClamp = state.bias.biasclamp;
|
|
m_VulkanPipelineState.rasterizer.slopeScaledDepthBias = state.bias.slope;
|
|
m_VulkanPipelineState.rasterizer.lineWidth = state.lineWidth;
|
|
|
|
// MSAA
|
|
m_VulkanPipelineState.multisample.rasterSamples = p.rasterizationSamples;
|
|
m_VulkanPipelineState.multisample.sampleShadingEnable = p.sampleShadingEnable;
|
|
m_VulkanPipelineState.multisample.minSampleShading = p.minSampleShading;
|
|
m_VulkanPipelineState.multisample.sampleMask = p.sampleMask;
|
|
|
|
m_VulkanPipelineState.multisample.sampleLocations.customLocations.clear();
|
|
if(p.sampleLocations.enabled)
|
|
{
|
|
m_VulkanPipelineState.multisample.sampleLocations.gridWidth =
|
|
state.sampleLocations.gridSize.width;
|
|
m_VulkanPipelineState.multisample.sampleLocations.gridHeight =
|
|
state.sampleLocations.gridSize.height;
|
|
m_VulkanPipelineState.multisample.sampleLocations.customLocations.reserve(
|
|
state.sampleLocations.locations.size());
|
|
for(const VkSampleLocationEXT &loc : state.sampleLocations.locations)
|
|
{
|
|
m_VulkanPipelineState.multisample.sampleLocations.customLocations.push_back(
|
|
{loc.x, loc.y, 0.0f, 0.0f});
|
|
}
|
|
}
|
|
|
|
// Color Blend
|
|
m_VulkanPipelineState.colorBlend.alphaToCoverageEnable = p.alphaToCoverageEnable;
|
|
m_VulkanPipelineState.colorBlend.alphaToOneEnable = p.alphaToOneEnable;
|
|
|
|
m_VulkanPipelineState.colorBlend.blends.resize(p.attachments.size());
|
|
for(size_t i = 0; i < p.attachments.size(); i++)
|
|
{
|
|
m_VulkanPipelineState.colorBlend.blends[i].enabled = p.attachments[i].blendEnable;
|
|
|
|
// due to shared structs, this is slightly duplicated - Vulkan doesn't have separate states
|
|
// for logic operations
|
|
m_VulkanPipelineState.colorBlend.blends[i].logicOperationEnabled = p.logicOpEnable;
|
|
m_VulkanPipelineState.colorBlend.blends[i].logicOperation = MakeLogicOp(p.logicOp);
|
|
|
|
m_VulkanPipelineState.colorBlend.blends[i].colorBlend.source =
|
|
MakeBlendMultiplier(p.attachments[i].blend.Source);
|
|
m_VulkanPipelineState.colorBlend.blends[i].colorBlend.destination =
|
|
MakeBlendMultiplier(p.attachments[i].blend.Destination);
|
|
m_VulkanPipelineState.colorBlend.blends[i].colorBlend.operation =
|
|
MakeBlendOp(p.attachments[i].blend.Operation);
|
|
|
|
m_VulkanPipelineState.colorBlend.blends[i].alphaBlend.source =
|
|
MakeBlendMultiplier(p.attachments[i].alphaBlend.Source);
|
|
m_VulkanPipelineState.colorBlend.blends[i].alphaBlend.destination =
|
|
MakeBlendMultiplier(p.attachments[i].alphaBlend.Destination);
|
|
m_VulkanPipelineState.colorBlend.blends[i].alphaBlend.operation =
|
|
MakeBlendOp(p.attachments[i].alphaBlend.Operation);
|
|
|
|
m_VulkanPipelineState.colorBlend.blends[i].writeMask = p.attachments[i].channelWriteMask;
|
|
}
|
|
|
|
memcpy(m_VulkanPipelineState.colorBlend.blendFactor, state.blendConst, sizeof(float) * 4);
|
|
|
|
// Depth Stencil
|
|
m_VulkanPipelineState.depthStencil.depthTestEnable = p.depthTestEnable;
|
|
m_VulkanPipelineState.depthStencil.depthWriteEnable = p.depthWriteEnable;
|
|
m_VulkanPipelineState.depthStencil.depthBoundsEnable = p.depthBoundsEnable;
|
|
m_VulkanPipelineState.depthStencil.depthFunction = MakeCompareFunc(p.depthCompareOp);
|
|
m_VulkanPipelineState.depthStencil.stencilTestEnable = p.stencilTestEnable;
|
|
|
|
m_VulkanPipelineState.depthStencil.frontFace.passOperation = MakeStencilOp(p.front.passOp);
|
|
m_VulkanPipelineState.depthStencil.frontFace.failOperation = MakeStencilOp(p.front.failOp);
|
|
m_VulkanPipelineState.depthStencil.frontFace.depthFailOperation =
|
|
MakeStencilOp(p.front.depthFailOp);
|
|
m_VulkanPipelineState.depthStencil.frontFace.function = MakeCompareFunc(p.front.compareOp);
|
|
|
|
m_VulkanPipelineState.depthStencil.backFace.passOperation = MakeStencilOp(p.back.passOp);
|
|
m_VulkanPipelineState.depthStencil.backFace.failOperation = MakeStencilOp(p.back.failOp);
|
|
m_VulkanPipelineState.depthStencil.backFace.depthFailOperation =
|
|
MakeStencilOp(p.back.depthFailOp);
|
|
m_VulkanPipelineState.depthStencil.backFace.function = MakeCompareFunc(p.back.compareOp);
|
|
|
|
m_VulkanPipelineState.depthStencil.minDepthBounds = state.mindepth;
|
|
m_VulkanPipelineState.depthStencil.maxDepthBounds = state.maxdepth;
|
|
|
|
m_VulkanPipelineState.depthStencil.frontFace.reference = state.front.ref;
|
|
m_VulkanPipelineState.depthStencil.frontFace.compareMask = state.front.compare;
|
|
m_VulkanPipelineState.depthStencil.frontFace.writeMask = state.front.write;
|
|
|
|
m_VulkanPipelineState.depthStencil.backFace.reference = state.back.ref;
|
|
m_VulkanPipelineState.depthStencil.backFace.compareMask = state.back.compare;
|
|
m_VulkanPipelineState.depthStencil.backFace.writeMask = state.back.write;
|
|
}
|
|
else
|
|
{
|
|
m_VulkanPipelineState.graphics.pipelineLayoutResourceId = ResourceId();
|
|
|
|
m_VulkanPipelineState.graphics.flags = 0;
|
|
|
|
m_VulkanPipelineState.vertexInput.attributes.clear();
|
|
m_VulkanPipelineState.vertexInput.bindings.clear();
|
|
m_VulkanPipelineState.vertexInput.vertexBuffers.clear();
|
|
|
|
VKPipe::Shader *stages[] = {
|
|
&m_VulkanPipelineState.vertexShader, &m_VulkanPipelineState.tessControlShader,
|
|
&m_VulkanPipelineState.tessEvalShader, &m_VulkanPipelineState.geometryShader,
|
|
&m_VulkanPipelineState.fragmentShader,
|
|
};
|
|
|
|
for(size_t i = 0; i < ARRAY_COUNT(stages); i++)
|
|
*stages[i] = VKPipe::Shader();
|
|
|
|
m_VulkanPipelineState.viewportScissor.viewportScissors.clear();
|
|
m_VulkanPipelineState.viewportScissor.discardRectangles.clear();
|
|
m_VulkanPipelineState.viewportScissor.discardRectanglesExclusive = true;
|
|
|
|
m_VulkanPipelineState.colorBlend.blends.clear();
|
|
}
|
|
|
|
if(state.renderPass != ResourceId())
|
|
{
|
|
// Renderpass
|
|
m_VulkanPipelineState.currentPass.renderpass.resourceId = rm->GetOriginalID(state.renderPass);
|
|
m_VulkanPipelineState.currentPass.renderpass.subpass = state.subpass;
|
|
if(state.renderPass != ResourceId())
|
|
{
|
|
m_VulkanPipelineState.currentPass.renderpass.inputAttachments =
|
|
c.m_RenderPass[state.renderPass].subpasses[state.subpass].inputAttachments;
|
|
m_VulkanPipelineState.currentPass.renderpass.colorAttachments =
|
|
c.m_RenderPass[state.renderPass].subpasses[state.subpass].colorAttachments;
|
|
m_VulkanPipelineState.currentPass.renderpass.resolveAttachments =
|
|
c.m_RenderPass[state.renderPass].subpasses[state.subpass].resolveAttachments;
|
|
m_VulkanPipelineState.currentPass.renderpass.depthstencilAttachment =
|
|
c.m_RenderPass[state.renderPass].subpasses[state.subpass].depthstencilAttachment;
|
|
m_VulkanPipelineState.currentPass.renderpass.fragmentDensityAttachment =
|
|
c.m_RenderPass[state.renderPass].subpasses[state.subpass].fragmentDensityAttachment;
|
|
|
|
m_VulkanPipelineState.currentPass.renderpass.multiviews =
|
|
c.m_RenderPass[state.renderPass].subpasses[state.subpass].multiviews;
|
|
}
|
|
|
|
m_VulkanPipelineState.currentPass.framebuffer.resourceId = rm->GetOriginalID(state.framebuffer);
|
|
|
|
if(state.framebuffer != ResourceId())
|
|
{
|
|
m_VulkanPipelineState.currentPass.framebuffer.width = c.m_Framebuffer[state.framebuffer].width;
|
|
m_VulkanPipelineState.currentPass.framebuffer.height =
|
|
c.m_Framebuffer[state.framebuffer].height;
|
|
m_VulkanPipelineState.currentPass.framebuffer.layers =
|
|
c.m_Framebuffer[state.framebuffer].layers;
|
|
|
|
m_VulkanPipelineState.currentPass.framebuffer.attachments.resize(
|
|
c.m_Framebuffer[state.framebuffer].attachments.size());
|
|
for(size_t i = 0; i < c.m_Framebuffer[state.framebuffer].attachments.size(); i++)
|
|
{
|
|
ResourceId viewid = c.m_Framebuffer[state.framebuffer].attachments[i].view;
|
|
|
|
if(viewid != ResourceId())
|
|
{
|
|
m_VulkanPipelineState.currentPass.framebuffer.attachments[i].viewResourceId =
|
|
rm->GetOriginalID(viewid);
|
|
m_VulkanPipelineState.currentPass.framebuffer.attachments[i].imageResourceId =
|
|
rm->GetOriginalID(c.m_ImageView[viewid].image);
|
|
|
|
m_VulkanPipelineState.currentPass.framebuffer.attachments[i].viewFormat =
|
|
MakeResourceFormat(c.m_ImageView[viewid].format);
|
|
m_VulkanPipelineState.currentPass.framebuffer.attachments[i].firstMip =
|
|
c.m_ImageView[viewid].range.baseMipLevel;
|
|
m_VulkanPipelineState.currentPass.framebuffer.attachments[i].firstSlice =
|
|
c.m_ImageView[viewid].range.baseArrayLayer;
|
|
m_VulkanPipelineState.currentPass.framebuffer.attachments[i].numMips =
|
|
c.m_ImageView[viewid].range.levelCount;
|
|
m_VulkanPipelineState.currentPass.framebuffer.attachments[i].numSlices =
|
|
c.m_ImageView[viewid].range.layerCount;
|
|
|
|
memcpy(m_VulkanPipelineState.currentPass.framebuffer.attachments[i].swizzle,
|
|
c.m_ImageView[viewid].swizzle, sizeof(TextureSwizzle) * 4);
|
|
}
|
|
else
|
|
{
|
|
m_VulkanPipelineState.currentPass.framebuffer.attachments[i].viewResourceId = ResourceId();
|
|
m_VulkanPipelineState.currentPass.framebuffer.attachments[i].imageResourceId = ResourceId();
|
|
|
|
m_VulkanPipelineState.currentPass.framebuffer.attachments[i].firstMip = 0;
|
|
m_VulkanPipelineState.currentPass.framebuffer.attachments[i].firstSlice = 0;
|
|
m_VulkanPipelineState.currentPass.framebuffer.attachments[i].numMips = 1;
|
|
m_VulkanPipelineState.currentPass.framebuffer.attachments[i].numSlices = 1;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_VulkanPipelineState.currentPass.framebuffer.width = 0;
|
|
m_VulkanPipelineState.currentPass.framebuffer.height = 0;
|
|
m_VulkanPipelineState.currentPass.framebuffer.layers = 0;
|
|
}
|
|
|
|
m_VulkanPipelineState.currentPass.renderArea.x = state.renderArea.offset.x;
|
|
m_VulkanPipelineState.currentPass.renderArea.y = state.renderArea.offset.y;
|
|
m_VulkanPipelineState.currentPass.renderArea.width = state.renderArea.extent.width;
|
|
m_VulkanPipelineState.currentPass.renderArea.height = state.renderArea.extent.height;
|
|
}
|
|
else
|
|
{
|
|
m_VulkanPipelineState.currentPass.renderpass.resourceId = ResourceId();
|
|
m_VulkanPipelineState.currentPass.renderpass.subpass = 0;
|
|
m_VulkanPipelineState.currentPass.renderpass.inputAttachments.clear();
|
|
m_VulkanPipelineState.currentPass.renderpass.colorAttachments.clear();
|
|
m_VulkanPipelineState.currentPass.renderpass.resolveAttachments.clear();
|
|
m_VulkanPipelineState.currentPass.renderpass.depthstencilAttachment = -1;
|
|
m_VulkanPipelineState.currentPass.renderpass.fragmentDensityAttachment = -1;
|
|
|
|
m_VulkanPipelineState.currentPass.framebuffer.resourceId = ResourceId();
|
|
m_VulkanPipelineState.currentPass.framebuffer.attachments.clear();
|
|
}
|
|
|
|
// Descriptor sets
|
|
m_VulkanPipelineState.graphics.descriptorSets.resize(state.graphics.descSets.size());
|
|
m_VulkanPipelineState.compute.descriptorSets.resize(state.compute.descSets.size());
|
|
|
|
{
|
|
rdcarray<VKPipe::DescriptorSet> *dsts[] = {
|
|
&m_VulkanPipelineState.graphics.descriptorSets, &m_VulkanPipelineState.compute.descriptorSets,
|
|
};
|
|
|
|
const std::vector<VulkanStatePipeline::DescriptorAndOffsets> *srcs[] = {
|
|
&state.graphics.descSets, &state.compute.descSets,
|
|
};
|
|
|
|
for(size_t p = 0; p < ARRAY_COUNT(srcs); p++)
|
|
{
|
|
bool hasUsedBinds = false;
|
|
const BindIdx *usedBindsData = NULL;
|
|
size_t usedBindsSize = 0;
|
|
|
|
{
|
|
const DynamicUsedBinds &usage = m_BindlessFeedback.Usage[eventId];
|
|
bool curCompute = (p == 1);
|
|
if(usage.valid && usage.compute == curCompute)
|
|
{
|
|
hasUsedBinds = true;
|
|
usedBindsData = usage.used.data();
|
|
usedBindsSize = usage.used.size();
|
|
}
|
|
}
|
|
|
|
BindIdx curBind;
|
|
|
|
for(size_t i = 0; i < srcs[p]->size(); i++)
|
|
{
|
|
ResourceId src = (*srcs[p])[i].descSet;
|
|
VKPipe::DescriptorSet &dst = (*dsts[p])[i];
|
|
|
|
curBind.set = (uint32_t)i;
|
|
|
|
ResourceId layoutId = m_pDriver->m_DescriptorSetState[src].layout;
|
|
|
|
// push descriptors don't have a real descriptor set backing them
|
|
if(c.m_DescSetLayout[layoutId].flags & VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR)
|
|
{
|
|
dst.descriptorSetResourceId = ResourceId();
|
|
dst.pushDescriptor = true;
|
|
}
|
|
else
|
|
{
|
|
dst.descriptorSetResourceId = rm->GetOriginalID(src);
|
|
dst.pushDescriptor = false;
|
|
}
|
|
|
|
dst.layoutResourceId = rm->GetOriginalID(layoutId);
|
|
dst.bindings.resize(m_pDriver->m_DescriptorSetState[src].currentBindings.size());
|
|
for(size_t b = 0; b < m_pDriver->m_DescriptorSetState[src].currentBindings.size(); b++)
|
|
{
|
|
DescriptorSetSlot *info = m_pDriver->m_DescriptorSetState[src].currentBindings[b];
|
|
const DescSetLayout::Binding &layoutBind = c.m_DescSetLayout[layoutId].bindings[b];
|
|
|
|
curBind.bind = (uint32_t)b;
|
|
|
|
bool dynamicOffset = false;
|
|
|
|
dst.bindings[b].descriptorCount = layoutBind.descriptorCount;
|
|
dst.bindings[b].stageFlags = (ShaderStageMask)layoutBind.stageFlags;
|
|
switch(layoutBind.descriptorType)
|
|
{
|
|
case VK_DESCRIPTOR_TYPE_SAMPLER: dst.bindings[b].type = BindType::Sampler; break;
|
|
case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
|
|
dst.bindings[b].type = BindType::ImageSampler;
|
|
break;
|
|
case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
|
|
dst.bindings[b].type = BindType::ReadOnlyImage;
|
|
break;
|
|
case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
|
|
dst.bindings[b].type = BindType::ReadWriteImage;
|
|
break;
|
|
case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
|
|
dst.bindings[b].type = BindType::ReadOnlyTBuffer;
|
|
break;
|
|
case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER:
|
|
dst.bindings[b].type = BindType::ReadWriteTBuffer;
|
|
break;
|
|
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
|
|
dst.bindings[b].type = BindType::ConstantBuffer;
|
|
break;
|
|
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
|
|
dst.bindings[b].type = BindType::ReadWriteBuffer;
|
|
break;
|
|
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
|
|
dst.bindings[b].type = BindType::ConstantBuffer;
|
|
dynamicOffset = true;
|
|
break;
|
|
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC:
|
|
dst.bindings[b].type = BindType::ReadWriteBuffer;
|
|
dynamicOffset = true;
|
|
break;
|
|
case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT:
|
|
dst.bindings[b].type = BindType::InputAttachment;
|
|
break;
|
|
default: dst.bindings[b].type = BindType::Unknown; RDCERR("Unexpected descriptor type");
|
|
}
|
|
|
|
dst.bindings[b].binds.resize(layoutBind.descriptorCount);
|
|
for(uint32_t a = 0; a < layoutBind.descriptorCount; a++)
|
|
{
|
|
VKPipe::BindingElement &dstel = dst.bindings[b].binds[a];
|
|
|
|
curBind.arrayidx = a;
|
|
|
|
// if we have a list of used binds, and this is an array descriptor (so would be
|
|
// expected to be in the list), check it for dynamic usage.
|
|
if(layoutBind.descriptorCount > 1 && hasUsedBinds)
|
|
{
|
|
// if we exhausted the list, all other elements are unused
|
|
if(usedBindsSize == 0)
|
|
{
|
|
dstel.dynamicallyUsed = false;
|
|
}
|
|
else
|
|
{
|
|
// we never saw the current value of usedBindsData (which is odd, we should have
|
|
// when iterating over all descriptors. This could only happen if there's some
|
|
// layout mismatch or a feedback bug that lead to an invalid entry in the list).
|
|
// Keep advancing until we get to one that is >= our current bind
|
|
while(curBind > *usedBindsData && usedBindsSize)
|
|
{
|
|
usedBindsData++;
|
|
usedBindsSize--;
|
|
}
|
|
|
|
// the next used bind is equal to this one. Mark it as dynamically used, and consume
|
|
if(curBind == *usedBindsData)
|
|
{
|
|
dstel.dynamicallyUsed = true;
|
|
usedBindsData++;
|
|
usedBindsSize--;
|
|
}
|
|
// the next used bind is after the current one, this is not used.
|
|
else if(curBind < *usedBindsData)
|
|
{
|
|
dstel.dynamicallyUsed = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(dstel.dynamicallyUsed)
|
|
dst.bindings[b].dynamicallyUsedCount++;
|
|
|
|
if(layoutBind.descriptorType == VK_DESCRIPTOR_TYPE_SAMPLER ||
|
|
layoutBind.descriptorType == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER)
|
|
{
|
|
if(layoutBind.immutableSampler)
|
|
{
|
|
dst.bindings[b].binds[a].samplerResourceId = layoutBind.immutableSampler[a];
|
|
dst.bindings[b].binds[a].immutableSampler = true;
|
|
}
|
|
else if(info[a].imageInfo.sampler != VK_NULL_HANDLE)
|
|
{
|
|
dst.bindings[b].binds[a].samplerResourceId = GetResID(info[a].imageInfo.sampler);
|
|
}
|
|
|
|
if(dst.bindings[b].binds[a].samplerResourceId != ResourceId())
|
|
{
|
|
VKPipe::BindingElement &el = dst.bindings[b].binds[a];
|
|
const VulkanCreationInfo::Sampler &sampl = c.m_Sampler[el.samplerResourceId];
|
|
|
|
ResourceId liveId = el.samplerResourceId;
|
|
|
|
el.samplerResourceId = rm->GetOriginalID(el.samplerResourceId);
|
|
|
|
// sampler info
|
|
el.filter = MakeFilter(sampl.minFilter, sampl.magFilter, sampl.mipmapMode,
|
|
sampl.maxAnisotropy > 1.0f, sampl.compareEnable,
|
|
sampl.reductionMode);
|
|
el.addressU = MakeAddressMode(sampl.address[0]);
|
|
el.addressV = MakeAddressMode(sampl.address[1]);
|
|
el.addressW = MakeAddressMode(sampl.address[2]);
|
|
el.mipBias = sampl.mipLodBias;
|
|
el.maxAnisotropy = sampl.maxAnisotropy;
|
|
el.compareFunction = MakeCompareFunc(sampl.compareOp);
|
|
el.minLOD = sampl.minLod;
|
|
el.maxLOD = sampl.maxLod;
|
|
MakeBorderColor(sampl.borderColor, (FloatVector *)el.borderColor);
|
|
el.unnormalized = sampl.unnormalizedCoordinates;
|
|
|
|
if(sampl.ycbcr != ResourceId())
|
|
{
|
|
const VulkanCreationInfo::YCbCrSampler &ycbcr = c.m_YCbCrSampler[sampl.ycbcr];
|
|
el.ycbcrSampler = rm->GetOriginalID(sampl.ycbcr);
|
|
|
|
el.ycbcrModel = ycbcr.ycbcrModel;
|
|
el.ycbcrRange = ycbcr.ycbcrRange;
|
|
memcpy(el.ycbcrSwizzle, ycbcr.swizzle, sizeof(TextureSwizzle) * 4);
|
|
el.xChromaOffset = ycbcr.xChromaOffset;
|
|
el.yChromaOffset = ycbcr.yChromaOffset;
|
|
el.chromaFilter = ycbcr.chromaFilter;
|
|
el.forceExplicitReconstruction = ycbcr.forceExplicitReconstruction;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(layoutBind.descriptorType == VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE ||
|
|
layoutBind.descriptorType == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER ||
|
|
layoutBind.descriptorType == VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT ||
|
|
layoutBind.descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE)
|
|
{
|
|
VkImageView view = info[a].imageInfo.imageView;
|
|
|
|
if(view != VK_NULL_HANDLE)
|
|
{
|
|
ResourceId viewid = GetResID(view);
|
|
|
|
dst.bindings[b].binds[a].viewResourceId = rm->GetOriginalID(viewid);
|
|
dst.bindings[b].binds[a].resourceResourceId =
|
|
rm->GetOriginalID(c.m_ImageView[viewid].image);
|
|
dst.bindings[b].binds[a].viewFormat =
|
|
MakeResourceFormat(c.m_ImageView[viewid].format);
|
|
|
|
memcpy(dst.bindings[b].binds[a].swizzle, c.m_ImageView[viewid].swizzle,
|
|
sizeof(TextureSwizzle) * 4);
|
|
dst.bindings[b].binds[a].firstMip = c.m_ImageView[viewid].range.baseMipLevel;
|
|
dst.bindings[b].binds[a].firstSlice = c.m_ImageView[viewid].range.baseArrayLayer;
|
|
dst.bindings[b].binds[a].numMips = c.m_ImageView[viewid].range.levelCount;
|
|
dst.bindings[b].binds[a].numSlices = c.m_ImageView[viewid].range.layerCount;
|
|
|
|
// temporary hack, store image layout enum in byteOffset as it's not used for images
|
|
dst.bindings[b].binds[a].byteOffset = info[a].imageInfo.imageLayout;
|
|
}
|
|
else
|
|
{
|
|
dst.bindings[b].binds[a].viewResourceId = ResourceId();
|
|
dst.bindings[b].binds[a].resourceResourceId = ResourceId();
|
|
dst.bindings[b].binds[a].firstMip = 0;
|
|
dst.bindings[b].binds[a].firstSlice = 0;
|
|
dst.bindings[b].binds[a].numMips = 1;
|
|
dst.bindings[b].binds[a].numSlices = 1;
|
|
}
|
|
}
|
|
if(layoutBind.descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER ||
|
|
layoutBind.descriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER)
|
|
{
|
|
VkBufferView view = info[a].texelBufferView;
|
|
|
|
if(view != VK_NULL_HANDLE)
|
|
{
|
|
ResourceId viewid = GetResID(view);
|
|
|
|
dst.bindings[b].binds[a].viewResourceId = rm->GetOriginalID(viewid);
|
|
dst.bindings[b].binds[a].resourceResourceId =
|
|
rm->GetOriginalID(c.m_BufferView[viewid].buffer);
|
|
dst.bindings[b].binds[a].byteOffset = c.m_BufferView[viewid].offset;
|
|
dst.bindings[b].binds[a].viewFormat =
|
|
MakeResourceFormat(c.m_BufferView[viewid].format);
|
|
if(dynamicOffset)
|
|
{
|
|
union
|
|
{
|
|
VkImageLayout l;
|
|
uint32_t u;
|
|
} offs;
|
|
|
|
RDCCOMPILE_ASSERT(sizeof(VkImageLayout) == sizeof(uint32_t),
|
|
"VkImageLayout isn't 32-bit sized");
|
|
|
|
offs.l = info[a].imageInfo.imageLayout;
|
|
|
|
dst.bindings[b].binds[a].byteOffset += offs.u;
|
|
}
|
|
dst.bindings[b].binds[a].byteSize = c.m_BufferView[viewid].size;
|
|
}
|
|
else
|
|
{
|
|
dst.bindings[b].binds[a].viewResourceId = ResourceId();
|
|
dst.bindings[b].binds[a].resourceResourceId = ResourceId();
|
|
dst.bindings[b].binds[a].byteOffset = 0;
|
|
dst.bindings[b].binds[a].byteSize = 0;
|
|
}
|
|
}
|
|
if(layoutBind.descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER ||
|
|
layoutBind.descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC ||
|
|
layoutBind.descriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER ||
|
|
layoutBind.descriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC)
|
|
{
|
|
dst.bindings[b].binds[a].viewResourceId = ResourceId();
|
|
|
|
if(info[a].bufferInfo.buffer != VK_NULL_HANDLE)
|
|
dst.bindings[b].binds[a].resourceResourceId =
|
|
rm->GetOriginalID(GetResID(info[a].bufferInfo.buffer));
|
|
|
|
dst.bindings[b].binds[a].byteOffset = info[a].bufferInfo.offset;
|
|
if(dynamicOffset)
|
|
{
|
|
union
|
|
{
|
|
VkImageLayout l;
|
|
uint32_t u;
|
|
} offs;
|
|
|
|
RDCCOMPILE_ASSERT(sizeof(VkImageLayout) == sizeof(uint32_t),
|
|
"VkImageLayout isn't 32-bit sized");
|
|
|
|
offs.l = info[a].imageInfo.imageLayout;
|
|
|
|
dst.bindings[b].binds[a].byteOffset += offs.u;
|
|
}
|
|
|
|
dst.bindings[b].binds[a].byteSize = info[a].bufferInfo.range;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// image layouts
|
|
{
|
|
m_VulkanPipelineState.images.resize(m_pDriver->m_ImageLayouts.size());
|
|
size_t i = 0;
|
|
for(auto it = m_pDriver->m_ImageLayouts.begin(); it != m_pDriver->m_ImageLayouts.end(); ++it)
|
|
{
|
|
VKPipe::ImageData &img = m_VulkanPipelineState.images[i];
|
|
|
|
img.resourceId = rm->GetOriginalID(it->first);
|
|
|
|
img.layouts.resize(it->second.subresourceStates.size());
|
|
for(size_t l = 0; l < it->second.subresourceStates.size(); l++)
|
|
{
|
|
img.layouts[l].name = ToStr(it->second.subresourceStates[l].newLayout);
|
|
img.layouts[l].baseMip = it->second.subresourceStates[l].subresourceRange.baseMipLevel;
|
|
img.layouts[l].baseLayer = it->second.subresourceStates[l].subresourceRange.baseArrayLayer;
|
|
img.layouts[l].numLayer = it->second.subresourceStates[l].subresourceRange.layerCount;
|
|
img.layouts[l].numMip = it->second.subresourceStates[l].subresourceRange.levelCount;
|
|
}
|
|
|
|
if(img.layouts.empty())
|
|
{
|
|
img.layouts.push_back(VKPipe::ImageLayout());
|
|
img.layouts[0].name = "Unknown";
|
|
}
|
|
|
|
i++;
|
|
}
|
|
}
|
|
|
|
if(state.conditionalRendering.buffer != ResourceId())
|
|
{
|
|
m_VulkanPipelineState.conditionalRendering.bufferId =
|
|
rm->GetOriginalID(state.conditionalRendering.buffer);
|
|
m_VulkanPipelineState.conditionalRendering.byteOffset = state.conditionalRendering.offset;
|
|
m_VulkanPipelineState.conditionalRendering.isInverted =
|
|
state.conditionalRendering.flags == VK_CONDITIONAL_RENDERING_INVERTED_BIT_EXT;
|
|
|
|
bytebuf data;
|
|
GetBufferData(state.conditionalRendering.buffer, state.conditionalRendering.offset,
|
|
sizeof(uint32_t), data);
|
|
|
|
uint32_t value;
|
|
memcpy(&value, data.data(), sizeof(uint32_t));
|
|
|
|
m_VulkanPipelineState.conditionalRendering.isPassing = value != 0;
|
|
|
|
if(m_VulkanPipelineState.conditionalRendering.isInverted)
|
|
m_VulkanPipelineState.conditionalRendering.isPassing =
|
|
!m_VulkanPipelineState.conditionalRendering.isPassing;
|
|
}
|
|
}
|
|
|
|
void VulkanReplay::FillCBufferVariables(ResourceId shader, string entryPoint, uint32_t cbufSlot,
|
|
rdcarray<ShaderVariable> &outvars, const bytebuf &data)
|
|
{
|
|
// Correct SPIR-V will ultimately need to set explicit layout information for each type.
|
|
// For now, just assume D3D11 packing (float4 alignment on float4s, float3s, matrices, arrays and
|
|
// structures)
|
|
|
|
auto it = m_pDriver->m_CreationInfo.m_ShaderModule.find(shader);
|
|
|
|
if(it == m_pDriver->m_CreationInfo.m_ShaderModule.end())
|
|
{
|
|
RDCERR("Can't get shader details");
|
|
return;
|
|
}
|
|
|
|
ShaderReflection &refl = it->second.m_Reflections[entryPoint].refl;
|
|
ShaderBindpointMapping &mapping = it->second.m_Reflections[entryPoint].mapping;
|
|
|
|
if(cbufSlot >= (uint32_t)refl.constantBlocks.count())
|
|
{
|
|
RDCERR("Invalid cbuffer slot");
|
|
return;
|
|
}
|
|
|
|
ConstantBlock &c = refl.constantBlocks[cbufSlot];
|
|
|
|
if(c.bufferBacked)
|
|
{
|
|
StandardFillCBufferVariables(c.variables, outvars, data);
|
|
}
|
|
else
|
|
{
|
|
// specialised path to display specialization constants
|
|
if(mapping.constantBlocks[c.bindPoint].bindset == SpecializationConstantBindSet)
|
|
{
|
|
// TODO we shouldn't be looking up the pipeline here, this query should work regardless.
|
|
ResourceId pipeline = refl.stage == ShaderStage::Compute
|
|
? m_pDriver->m_RenderState.compute.pipeline
|
|
: m_pDriver->m_RenderState.graphics.pipeline;
|
|
if(pipeline != ResourceId())
|
|
{
|
|
auto pipeIt = m_pDriver->m_CreationInfo.m_Pipeline.find(pipeline);
|
|
|
|
if(pipeIt != m_pDriver->m_CreationInfo.m_Pipeline.end())
|
|
{
|
|
auto specInfo =
|
|
pipeIt->second.shaders[it->second.m_Reflections[entryPoint].stageIndex].specialization;
|
|
|
|
FillSpecConstantVariables(c.variables, outvars, specInfo);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bytebuf pushdata;
|
|
pushdata.resize(sizeof(m_pDriver->m_RenderState.pushconsts));
|
|
memcpy(&pushdata[0], m_pDriver->m_RenderState.pushconsts, pushdata.size());
|
|
StandardFillCBufferVariables(c.variables, outvars, pushdata);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool VulkanReplay::GetMinMax(ResourceId texid, uint32_t sliceFace, uint32_t mip, uint32_t sample,
|
|
CompType typeHint, float *minval, float *maxval)
|
|
{
|
|
ImageLayouts &layouts = m_pDriver->m_ImageLayouts[texid];
|
|
|
|
if(IsDepthAndStencilFormat(layouts.format))
|
|
{
|
|
// for depth/stencil we need to run the code twice - once to fetch depth and once to fetch
|
|
// stencil - since we can't process float depth and int stencil at the same time
|
|
Vec4f depth[2] = {
|
|
{0.0f, 0.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 1.0f, 1.0f},
|
|
};
|
|
Vec4u stencil[2] = {{0, 0, 0, 0}, {1, 1, 1, 1}};
|
|
|
|
bool success =
|
|
GetMinMax(texid, sliceFace, mip, sample, typeHint, false, &depth[0].x, &depth[1].x);
|
|
|
|
if(!success)
|
|
return false;
|
|
|
|
success = GetMinMax(texid, sliceFace, mip, sample, typeHint, true, (float *)&stencil[0].x,
|
|
(float *)&stencil[1].x);
|
|
|
|
if(!success)
|
|
return false;
|
|
|
|
// copy across into green channel, casting up to float, dividing by the range for this texture
|
|
depth[0].y = float(stencil[0].x) / 255.0f;
|
|
depth[1].y = float(stencil[1].x) / 255.0f;
|
|
|
|
memcpy(minval, &depth[0].x, sizeof(depth[0]));
|
|
memcpy(maxval, &depth[1].x, sizeof(depth[1]));
|
|
|
|
return true;
|
|
}
|
|
|
|
return GetMinMax(texid, sliceFace, mip, sample, typeHint, false, minval, maxval);
|
|
}
|
|
|
|
bool VulkanReplay::GetMinMax(ResourceId texid, uint32_t sliceFace, uint32_t mip, uint32_t sample,
|
|
CompType typeHint, bool stencil, float *minval, float *maxval)
|
|
{
|
|
VkDevice dev = m_pDriver->GetDev();
|
|
VkCommandBuffer cmd = m_pDriver->GetNextCmd();
|
|
const VkLayerDispatchTable *vt = ObjDisp(dev);
|
|
|
|
ImageLayouts &layouts = m_pDriver->m_ImageLayouts[texid];
|
|
VulkanCreationInfo::Image &iminfo = m_pDriver->m_CreationInfo.m_Image[texid];
|
|
TextureDisplayViews &texviews = m_TexRender.TextureViews[texid];
|
|
VkImage liveIm = m_pDriver->GetResourceManager()->GetCurrentHandle<VkImage>(texid);
|
|
|
|
if(!IsStencilFormat(iminfo.format))
|
|
stencil = false;
|
|
|
|
CreateTexImageView(liveIm, iminfo, typeHint, texviews);
|
|
|
|
VkImageView liveImView = texviews.views[0];
|
|
|
|
// if it's not stencil-only and we're displaying stencil, use view 1
|
|
if(texviews.castedFormat != VK_FORMAT_S8_UINT && stencil)
|
|
liveImView = texviews.views[1];
|
|
|
|
RDCASSERT(liveImView != VK_NULL_HANDLE);
|
|
|
|
VkDescriptorImageInfo imdesc = {0};
|
|
imdesc.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
|
imdesc.imageView = Unwrap(liveImView);
|
|
imdesc.sampler = Unwrap(m_General.PointSampler);
|
|
|
|
uint32_t descSetBinding = 0;
|
|
uint32_t intTypeIndex = 0;
|
|
|
|
if(IsUIntFormat(texviews.castedFormat))
|
|
{
|
|
descSetBinding = 10;
|
|
intTypeIndex = 1;
|
|
}
|
|
else if(IsSIntFormat(texviews.castedFormat))
|
|
{
|
|
descSetBinding = 15;
|
|
intTypeIndex = 2;
|
|
}
|
|
else
|
|
{
|
|
descSetBinding = 5;
|
|
}
|
|
|
|
int textype = 0;
|
|
|
|
if(iminfo.type == VK_IMAGE_TYPE_1D)
|
|
{
|
|
textype = RESTYPE_TEX1D;
|
|
}
|
|
else if(iminfo.type == VK_IMAGE_TYPE_3D)
|
|
{
|
|
textype = RESTYPE_TEX3D;
|
|
}
|
|
else if(iminfo.type == VK_IMAGE_TYPE_2D)
|
|
{
|
|
textype = RESTYPE_TEX2D;
|
|
if(iminfo.samples != VK_SAMPLE_COUNT_1_BIT)
|
|
textype = RESTYPE_TEX2DMS;
|
|
}
|
|
|
|
if(stencil)
|
|
{
|
|
descSetBinding = 10;
|
|
intTypeIndex = 1;
|
|
}
|
|
|
|
descSetBinding += textype;
|
|
|
|
if(m_Histogram.m_MinMaxTilePipe[textype][intTypeIndex] == VK_NULL_HANDLE)
|
|
return false;
|
|
|
|
VkDescriptorBufferInfo bufdescs[3];
|
|
RDCEraseEl(bufdescs);
|
|
m_Histogram.m_MinMaxTileResult.FillDescriptor(bufdescs[0]);
|
|
m_Histogram.m_MinMaxResult.FillDescriptor(bufdescs[1]);
|
|
m_Histogram.m_HistogramUBO.FillDescriptor(bufdescs[2]);
|
|
|
|
VkDescriptorImageInfo altimdesc[2] = {};
|
|
for(uint32_t i = 1; i < GetYUVPlaneCount(texviews.castedFormat); i++)
|
|
{
|
|
RDCASSERT(texviews.views[i] != VK_NULL_HANDLE);
|
|
altimdesc[i - 1].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
|
altimdesc[i - 1].imageView = Unwrap(texviews.views[i]);
|
|
altimdesc[i - 1].sampler = Unwrap(m_General.PointSampler);
|
|
}
|
|
|
|
VkWriteDescriptorSet writeSet[] = {
|
|
|
|
// first pass on tiles
|
|
{
|
|
VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, Unwrap(m_Histogram.m_HistogramDescSet[0]),
|
|
0, 0, 1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, NULL, &bufdescs[0],
|
|
NULL // destination = tile result
|
|
},
|
|
{
|
|
VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, Unwrap(m_Histogram.m_HistogramDescSet[0]),
|
|
1, 0, 1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, NULL, &bufdescs[0],
|
|
NULL // source = unused, bind tile result
|
|
},
|
|
{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, Unwrap(m_Histogram.m_HistogramDescSet[0]), 2,
|
|
0, 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, NULL, &bufdescs[2], NULL},
|
|
|
|
// sampled view
|
|
{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, Unwrap(m_Histogram.m_HistogramDescSet[0]),
|
|
descSetBinding, 0, 1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, &imdesc, NULL, NULL},
|
|
// YUV secondary planes (if needed)
|
|
{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, Unwrap(m_Histogram.m_HistogramDescSet[0]), 10,
|
|
0, GetYUVPlaneCount(texviews.castedFormat) - 1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
|
altimdesc, NULL, NULL},
|
|
|
|
// second pass from tiles to result
|
|
{
|
|
VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, Unwrap(m_Histogram.m_HistogramDescSet[1]),
|
|
0, 0, 1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, NULL, &bufdescs[1],
|
|
NULL // destination = result
|
|
},
|
|
{
|
|
VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, Unwrap(m_Histogram.m_HistogramDescSet[1]),
|
|
1, 0, 1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, NULL, &bufdescs[0],
|
|
NULL // source = tile result
|
|
},
|
|
{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, Unwrap(m_Histogram.m_HistogramDescSet[1]), 2,
|
|
0, 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, NULL, &bufdescs[2], NULL},
|
|
};
|
|
|
|
vector<VkWriteDescriptorSet> writeSets;
|
|
for(size_t i = 0; i < ARRAY_COUNT(writeSet); i++)
|
|
{
|
|
if(writeSet[i].descriptorCount > 0)
|
|
writeSets.push_back(writeSet[i]);
|
|
}
|
|
|
|
for(size_t i = 0; i < ARRAY_COUNT(m_TexRender.DummyWrites); i++)
|
|
{
|
|
VkWriteDescriptorSet &write = m_TexRender.DummyWrites[i];
|
|
|
|
// don't write dummy data in the actual slot
|
|
if(write.dstBinding == descSetBinding)
|
|
continue;
|
|
|
|
// don't overwrite YUV texture slots if it's a YUV planar format
|
|
if(write.dstBinding == 10)
|
|
{
|
|
if(write.dstArrayElement == 0 && GetYUVPlaneCount(texviews.castedFormat) >= 2)
|
|
continue;
|
|
if(write.dstArrayElement == 1 && GetYUVPlaneCount(texviews.castedFormat) >= 3)
|
|
continue;
|
|
}
|
|
|
|
write.dstSet = Unwrap(m_Histogram.m_HistogramDescSet[0]);
|
|
writeSets.push_back(write);
|
|
}
|
|
|
|
vt->UpdateDescriptorSets(Unwrap(dev), (uint32_t)writeSets.size(), &writeSets[0], 0, NULL);
|
|
|
|
HistogramUBOData *data = (HistogramUBOData *)m_Histogram.m_HistogramUBO.Map(NULL);
|
|
|
|
data->HistogramTextureResolution.x = (float)RDCMAX(uint32_t(iminfo.extent.width) >> mip, 1U);
|
|
data->HistogramTextureResolution.y = (float)RDCMAX(uint32_t(iminfo.extent.height) >> mip, 1U);
|
|
data->HistogramTextureResolution.z = (float)RDCMAX(uint32_t(iminfo.extent.depth) >> mip, 1U);
|
|
if(iminfo.type != VK_IMAGE_TYPE_3D)
|
|
data->HistogramSlice = (float)sliceFace + 0.001f;
|
|
else
|
|
data->HistogramSlice = (float)(sliceFace >> mip);
|
|
data->HistogramMip = (int)mip;
|
|
data->HistogramNumSamples = iminfo.samples;
|
|
data->HistogramSample = (int)RDCCLAMP(sample, 0U, uint32_t(iminfo.samples) - 1);
|
|
if(sample == ~0U)
|
|
data->HistogramSample = -iminfo.samples;
|
|
data->HistogramMin = 0.0f;
|
|
data->HistogramMax = 1.0f;
|
|
data->HistogramChannels = 0xf;
|
|
|
|
Vec4u YUVDownsampleRate = {};
|
|
Vec4u YUVAChannels = {};
|
|
|
|
GetYUVShaderParameters(texviews.castedFormat, YUVDownsampleRate, YUVAChannels);
|
|
|
|
data->HistogramYUVDownsampleRate = YUVDownsampleRate;
|
|
data->HistogramYUVAChannels = YUVAChannels;
|
|
|
|
m_Histogram.m_HistogramUBO.Unmap();
|
|
|
|
VkImageMemoryBarrier srcimBarrier = {
|
|
VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
|
NULL,
|
|
0,
|
|
0,
|
|
VK_IMAGE_LAYOUT_UNDEFINED,
|
|
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
Unwrap(liveIm),
|
|
{0, 0, 1, 0, 1} // will be overwritten by subresourceRange below
|
|
};
|
|
|
|
// ensure all previous writes have completed
|
|
srcimBarrier.srcAccessMask = VK_ACCESS_ALL_WRITE_BITS;
|
|
// before we go reading
|
|
srcimBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
|
|
|
VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, NULL,
|
|
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT};
|
|
|
|
vt->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
|
|
|
|
for(size_t si = 0; si < layouts.subresourceStates.size(); si++)
|
|
{
|
|
srcimBarrier.subresourceRange = layouts.subresourceStates[si].subresourceRange;
|
|
srcimBarrier.oldLayout = layouts.subresourceStates[si].newLayout;
|
|
DoPipelineBarrier(cmd, 1, &srcimBarrier);
|
|
}
|
|
|
|
srcimBarrier.oldLayout = srcimBarrier.newLayout;
|
|
|
|
srcimBarrier.srcAccessMask = 0;
|
|
srcimBarrier.dstAccessMask = 0;
|
|
|
|
int blocksX = (int)ceil(iminfo.extent.width / float(HGRAM_PIXELS_PER_TILE * HGRAM_TILES_PER_BLOCK));
|
|
int blocksY =
|
|
(int)ceil(iminfo.extent.height / float(HGRAM_PIXELS_PER_TILE * HGRAM_TILES_PER_BLOCK));
|
|
|
|
vt->CmdBindPipeline(Unwrap(cmd), VK_PIPELINE_BIND_POINT_COMPUTE,
|
|
Unwrap(m_Histogram.m_MinMaxTilePipe[textype][intTypeIndex]));
|
|
vt->CmdBindDescriptorSets(Unwrap(cmd), VK_PIPELINE_BIND_POINT_COMPUTE,
|
|
Unwrap(m_Histogram.m_HistogramPipeLayout), 0, 1,
|
|
UnwrapPtr(m_Histogram.m_HistogramDescSet[0]), 0, NULL);
|
|
|
|
vt->CmdDispatch(Unwrap(cmd), blocksX, blocksY, 1);
|
|
|
|
// image layout back to normal
|
|
for(size_t si = 0; si < layouts.subresourceStates.size(); si++)
|
|
{
|
|
srcimBarrier.subresourceRange = layouts.subresourceStates[si].subresourceRange;
|
|
srcimBarrier.newLayout = layouts.subresourceStates[si].newLayout;
|
|
srcimBarrier.dstAccessMask = MakeAccessMask(srcimBarrier.newLayout);
|
|
DoPipelineBarrier(cmd, 1, &srcimBarrier);
|
|
}
|
|
|
|
VkBufferMemoryBarrier tilebarrier = {
|
|
VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
|
|
NULL,
|
|
VK_ACCESS_SHADER_WRITE_BIT,
|
|
VK_ACCESS_SHADER_READ_BIT,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
Unwrap(m_Histogram.m_MinMaxTileResult.buf),
|
|
0,
|
|
m_Histogram.m_MinMaxTileResult.totalsize,
|
|
};
|
|
|
|
// ensure shader writes complete before coalescing the tiles
|
|
DoPipelineBarrier(cmd, 1, &tilebarrier);
|
|
|
|
vt->CmdBindPipeline(Unwrap(cmd), VK_PIPELINE_BIND_POINT_COMPUTE,
|
|
Unwrap(m_Histogram.m_MinMaxResultPipe[intTypeIndex]));
|
|
vt->CmdBindDescriptorSets(Unwrap(cmd), VK_PIPELINE_BIND_POINT_COMPUTE,
|
|
Unwrap(m_Histogram.m_HistogramPipeLayout), 0, 1,
|
|
UnwrapPtr(m_Histogram.m_HistogramDescSet[1]), 0, NULL);
|
|
|
|
vt->CmdDispatch(Unwrap(cmd), 1, 1, 1);
|
|
|
|
// ensure shader writes complete before copying back to readback buffer
|
|
tilebarrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
|
|
tilebarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
|
tilebarrier.buffer = Unwrap(m_Histogram.m_MinMaxResult.buf);
|
|
tilebarrier.size = m_Histogram.m_MinMaxResult.totalsize;
|
|
|
|
DoPipelineBarrier(cmd, 1, &tilebarrier);
|
|
|
|
VkBufferCopy bufcopy = {
|
|
0, 0, m_Histogram.m_MinMaxResult.totalsize,
|
|
};
|
|
|
|
vt->CmdCopyBuffer(Unwrap(cmd), Unwrap(m_Histogram.m_MinMaxResult.buf),
|
|
Unwrap(m_Histogram.m_MinMaxReadback.buf), 1, &bufcopy);
|
|
|
|
// wait for copy to complete before mapping
|
|
tilebarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
|
tilebarrier.dstAccessMask = VK_ACCESS_HOST_READ_BIT;
|
|
tilebarrier.buffer = Unwrap(m_Histogram.m_MinMaxReadback.buf);
|
|
tilebarrier.size = m_Histogram.m_MinMaxResult.totalsize;
|
|
|
|
DoPipelineBarrier(cmd, 1, &tilebarrier);
|
|
|
|
vt->EndCommandBuffer(Unwrap(cmd));
|
|
|
|
// submit cmds and wait for idle so we can readback
|
|
m_pDriver->SubmitCmds();
|
|
m_pDriver->FlushQ();
|
|
|
|
Vec4f *minmax = (Vec4f *)m_Histogram.m_MinMaxReadback.Map(NULL);
|
|
|
|
minval[0] = minmax[0].x;
|
|
minval[1] = minmax[0].y;
|
|
minval[2] = minmax[0].z;
|
|
minval[3] = minmax[0].w;
|
|
|
|
maxval[0] = minmax[1].x;
|
|
maxval[1] = minmax[1].y;
|
|
maxval[2] = minmax[1].z;
|
|
maxval[3] = minmax[1].w;
|
|
|
|
m_Histogram.m_MinMaxReadback.Unmap();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool VulkanReplay::GetHistogram(ResourceId texid, uint32_t sliceFace, uint32_t mip, uint32_t sample,
|
|
CompType typeHint, float minval, float maxval, bool channels[4],
|
|
vector<uint32_t> &histogram)
|
|
{
|
|
if(minval >= maxval)
|
|
return false;
|
|
|
|
VkDevice dev = m_pDriver->GetDev();
|
|
VkCommandBuffer cmd = m_pDriver->GetNextCmd();
|
|
const VkLayerDispatchTable *vt = ObjDisp(dev);
|
|
|
|
ImageLayouts &layouts = m_pDriver->m_ImageLayouts[texid];
|
|
VulkanCreationInfo::Image &iminfo = m_pDriver->m_CreationInfo.m_Image[texid];
|
|
TextureDisplayViews &texviews = m_TexRender.TextureViews[texid];
|
|
VkImage liveIm = m_pDriver->GetResourceManager()->GetCurrentHandle<VkImage>(texid);
|
|
|
|
bool stencil = false;
|
|
// detect if stencil is selected
|
|
if(IsStencilFormat(iminfo.format) && !channels[0] && channels[1] && !channels[2] && !channels[3])
|
|
stencil = true;
|
|
|
|
CreateTexImageView(liveIm, iminfo, typeHint, texviews);
|
|
|
|
uint32_t descSetBinding = 0;
|
|
uint32_t intTypeIndex = 0;
|
|
|
|
if(IsUIntFormat(texviews.castedFormat))
|
|
{
|
|
descSetBinding = 10;
|
|
intTypeIndex = 1;
|
|
}
|
|
else if(IsSIntFormat(texviews.castedFormat))
|
|
{
|
|
descSetBinding = 15;
|
|
intTypeIndex = 2;
|
|
}
|
|
else
|
|
{
|
|
descSetBinding = 5;
|
|
}
|
|
|
|
int textype = 0;
|
|
|
|
if(iminfo.type == VK_IMAGE_TYPE_1D)
|
|
{
|
|
textype = RESTYPE_TEX1D;
|
|
}
|
|
else if(iminfo.type == VK_IMAGE_TYPE_3D)
|
|
{
|
|
textype = RESTYPE_TEX3D;
|
|
}
|
|
else if(iminfo.type == VK_IMAGE_TYPE_2D)
|
|
{
|
|
textype = RESTYPE_TEX2D;
|
|
if(iminfo.samples != VK_SAMPLE_COUNT_1_BIT)
|
|
textype = RESTYPE_TEX2DMS;
|
|
}
|
|
|
|
if(stencil)
|
|
{
|
|
descSetBinding = 10;
|
|
intTypeIndex = 1;
|
|
|
|
// rescale the range so that stencil seems to fit to 0-1
|
|
minval *= 255.0f;
|
|
maxval *= 255.0f;
|
|
|
|
// shuffle the channel selection, since stencil comes back in red
|
|
std::swap(channels[0], channels[1]);
|
|
}
|
|
|
|
descSetBinding += textype;
|
|
|
|
if(m_Histogram.m_HistogramPipe[textype][intTypeIndex] == VK_NULL_HANDLE)
|
|
{
|
|
histogram.resize(HGRAM_NUM_BUCKETS);
|
|
for(size_t i = 0; i < HGRAM_NUM_BUCKETS; i++)
|
|
histogram[i] = 1;
|
|
return false;
|
|
}
|
|
|
|
VkImageView liveImView = texviews.views[0];
|
|
|
|
// if it's not stencil-only and we're displaying stencil, use view 1
|
|
if(stencil && texviews.castedFormat != VK_FORMAT_S8_UINT)
|
|
liveImView = texviews.views[1];
|
|
|
|
RDCASSERT(liveImView != VK_NULL_HANDLE);
|
|
|
|
VkDescriptorImageInfo imdesc = {0};
|
|
imdesc.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
|
|
imdesc.imageView = Unwrap(liveImView);
|
|
imdesc.sampler = Unwrap(m_General.PointSampler);
|
|
|
|
VkDescriptorBufferInfo bufdescs[2];
|
|
RDCEraseEl(bufdescs);
|
|
m_Histogram.m_HistogramBuf.FillDescriptor(bufdescs[0]);
|
|
m_Histogram.m_HistogramUBO.FillDescriptor(bufdescs[1]);
|
|
|
|
VkDescriptorImageInfo altimdesc[2] = {};
|
|
for(uint32_t i = 1; i < GetYUVPlaneCount(texviews.castedFormat); i++)
|
|
{
|
|
RDCASSERT(texviews.views[i] != VK_NULL_HANDLE);
|
|
altimdesc[i - 1].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
|
altimdesc[i - 1].imageView = Unwrap(texviews.views[i]);
|
|
altimdesc[i - 1].sampler = Unwrap(m_General.PointSampler);
|
|
}
|
|
|
|
VkWriteDescriptorSet writeSet[] = {
|
|
|
|
// histogram pass
|
|
{
|
|
VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, Unwrap(m_Histogram.m_HistogramDescSet[0]),
|
|
0, 0, 1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, NULL, &bufdescs[0],
|
|
NULL // destination = histogram result
|
|
},
|
|
{
|
|
VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, Unwrap(m_Histogram.m_HistogramDescSet[0]),
|
|
1, 0, 1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, NULL, &bufdescs[0],
|
|
NULL // source = unused, bind histogram result
|
|
},
|
|
{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, Unwrap(m_Histogram.m_HistogramDescSet[0]), 2,
|
|
0, 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, NULL, &bufdescs[1], NULL},
|
|
// sampled view
|
|
{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, Unwrap(m_Histogram.m_HistogramDescSet[0]),
|
|
descSetBinding, 0, 1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, &imdesc, NULL, NULL},
|
|
// YUV secondary planes (if needed)
|
|
{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, Unwrap(m_Histogram.m_HistogramDescSet[0]), 10,
|
|
0, GetYUVPlaneCount(texviews.castedFormat) - 1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
|
altimdesc, NULL, NULL},
|
|
};
|
|
|
|
vector<VkWriteDescriptorSet> writeSets;
|
|
for(size_t i = 0; i < ARRAY_COUNT(writeSet); i++)
|
|
{
|
|
if(writeSet[i].descriptorCount > 0)
|
|
writeSets.push_back(writeSet[i]);
|
|
}
|
|
|
|
for(size_t i = 0; i < ARRAY_COUNT(m_TexRender.DummyWrites); i++)
|
|
{
|
|
VkWriteDescriptorSet &write = m_TexRender.DummyWrites[i];
|
|
|
|
// don't write dummy data in the actual slot
|
|
if(write.dstBinding == descSetBinding)
|
|
continue;
|
|
|
|
// don't overwrite YUV texture slots if it's a YUV planar format
|
|
if(write.dstBinding == 10)
|
|
{
|
|
if(write.dstArrayElement == 0 && GetYUVPlaneCount(texviews.castedFormat) >= 2)
|
|
continue;
|
|
if(write.dstArrayElement == 1 && GetYUVPlaneCount(texviews.castedFormat) >= 3)
|
|
continue;
|
|
}
|
|
|
|
write.dstSet = Unwrap(m_Histogram.m_HistogramDescSet[0]);
|
|
writeSets.push_back(write);
|
|
}
|
|
|
|
vt->UpdateDescriptorSets(Unwrap(dev), (uint32_t)writeSets.size(), &writeSets[0], 0, NULL);
|
|
|
|
HistogramUBOData *data = (HistogramUBOData *)m_Histogram.m_HistogramUBO.Map(NULL);
|
|
|
|
data->HistogramTextureResolution.x = (float)RDCMAX(uint32_t(iminfo.extent.width) >> mip, 1U);
|
|
data->HistogramTextureResolution.y = (float)RDCMAX(uint32_t(iminfo.extent.height) >> mip, 1U);
|
|
data->HistogramTextureResolution.z = (float)RDCMAX(uint32_t(iminfo.extent.depth) >> mip, 1U);
|
|
if(iminfo.type != VK_IMAGE_TYPE_3D)
|
|
data->HistogramSlice = (float)sliceFace + 0.001f;
|
|
else
|
|
data->HistogramSlice = (float)(sliceFace >> mip);
|
|
data->HistogramMip = (int)mip;
|
|
data->HistogramNumSamples = iminfo.samples;
|
|
data->HistogramSample = (int)RDCCLAMP(sample, 0U, uint32_t(iminfo.samples) - 1);
|
|
if(sample == ~0U)
|
|
data->HistogramSample = -iminfo.samples;
|
|
data->HistogramMin = minval;
|
|
|
|
// The calculation in the shader normalises each value between min and max, then multiplies by the
|
|
// number of buckets.
|
|
// But any value equal to HistogramMax must go into NUM_BUCKETS-1, so add a small delta.
|
|
data->HistogramMax = maxval + maxval * 1e-6f;
|
|
|
|
uint32_t chans = 0;
|
|
if(channels[0])
|
|
chans |= 0x1;
|
|
if(channels[1])
|
|
chans |= 0x2;
|
|
if(channels[2])
|
|
chans |= 0x4;
|
|
if(channels[3])
|
|
chans |= 0x8;
|
|
|
|
data->HistogramChannels = chans;
|
|
data->HistogramFlags = 0;
|
|
|
|
Vec4u YUVDownsampleRate = {};
|
|
Vec4u YUVAChannels = {};
|
|
|
|
GetYUVShaderParameters(texviews.castedFormat, YUVDownsampleRate, YUVAChannels);
|
|
|
|
data->HistogramYUVDownsampleRate = YUVDownsampleRate;
|
|
data->HistogramYUVAChannels = YUVAChannels;
|
|
|
|
m_Histogram.m_HistogramUBO.Unmap();
|
|
|
|
VkImageMemoryBarrier srcimBarrier = {
|
|
VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
|
NULL,
|
|
0,
|
|
0,
|
|
VK_IMAGE_LAYOUT_UNDEFINED,
|
|
VK_IMAGE_LAYOUT_GENERAL,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
Unwrap(liveIm),
|
|
{0, 0, 1, 0, 1} // will be overwritten by subresourceRange below
|
|
};
|
|
|
|
// ensure all previous writes have completed
|
|
srcimBarrier.srcAccessMask = VK_ACCESS_ALL_WRITE_BITS;
|
|
// before we go reading
|
|
srcimBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
|
|
|
VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, NULL,
|
|
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT};
|
|
|
|
vt->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
|
|
|
|
for(size_t si = 0; si < layouts.subresourceStates.size(); si++)
|
|
{
|
|
srcimBarrier.subresourceRange = layouts.subresourceStates[si].subresourceRange;
|
|
srcimBarrier.oldLayout = layouts.subresourceStates[si].newLayout;
|
|
DoPipelineBarrier(cmd, 1, &srcimBarrier);
|
|
}
|
|
|
|
srcimBarrier.oldLayout = srcimBarrier.newLayout;
|
|
|
|
srcimBarrier.srcAccessMask = 0;
|
|
srcimBarrier.dstAccessMask = 0;
|
|
|
|
int blocksX = (int)ceil(iminfo.extent.width / float(HGRAM_PIXELS_PER_TILE * HGRAM_TILES_PER_BLOCK));
|
|
int blocksY =
|
|
(int)ceil(iminfo.extent.height / float(HGRAM_PIXELS_PER_TILE * HGRAM_TILES_PER_BLOCK));
|
|
|
|
vt->CmdFillBuffer(Unwrap(cmd), Unwrap(m_Histogram.m_HistogramBuf.buf), 0,
|
|
m_Histogram.m_HistogramBuf.totalsize, 0);
|
|
|
|
vt->CmdBindPipeline(Unwrap(cmd), VK_PIPELINE_BIND_POINT_COMPUTE,
|
|
Unwrap(m_Histogram.m_HistogramPipe[textype][intTypeIndex]));
|
|
vt->CmdBindDescriptorSets(Unwrap(cmd), VK_PIPELINE_BIND_POINT_COMPUTE,
|
|
Unwrap(m_Histogram.m_HistogramPipeLayout), 0, 1,
|
|
UnwrapPtr(m_Histogram.m_HistogramDescSet[0]), 0, NULL);
|
|
|
|
vt->CmdDispatch(Unwrap(cmd), blocksX, blocksY, 1);
|
|
|
|
// image layout back to normal
|
|
for(size_t si = 0; si < layouts.subresourceStates.size(); si++)
|
|
{
|
|
srcimBarrier.subresourceRange = layouts.subresourceStates[si].subresourceRange;
|
|
srcimBarrier.newLayout = layouts.subresourceStates[si].newLayout;
|
|
srcimBarrier.dstAccessMask = MakeAccessMask(srcimBarrier.newLayout);
|
|
DoPipelineBarrier(cmd, 1, &srcimBarrier);
|
|
}
|
|
|
|
VkBufferMemoryBarrier tilebarrier = {
|
|
VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
|
|
NULL,
|
|
VK_ACCESS_SHADER_WRITE_BIT,
|
|
VK_ACCESS_TRANSFER_READ_BIT,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
Unwrap(m_Histogram.m_HistogramBuf.buf),
|
|
0,
|
|
m_Histogram.m_HistogramBuf.totalsize,
|
|
};
|
|
|
|
// ensure shader writes complete before copying to readback buf
|
|
DoPipelineBarrier(cmd, 1, &tilebarrier);
|
|
|
|
VkBufferCopy bufcopy = {
|
|
0, 0, m_Histogram.m_HistogramBuf.totalsize,
|
|
};
|
|
|
|
vt->CmdCopyBuffer(Unwrap(cmd), Unwrap(m_Histogram.m_HistogramBuf.buf),
|
|
Unwrap(m_Histogram.m_HistogramReadback.buf), 1, &bufcopy);
|
|
|
|
// wait for copy to complete before mapping
|
|
tilebarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
|
tilebarrier.dstAccessMask = VK_ACCESS_HOST_READ_BIT;
|
|
tilebarrier.buffer = Unwrap(m_Histogram.m_HistogramReadback.buf);
|
|
tilebarrier.size = m_Histogram.m_HistogramReadback.totalsize;
|
|
|
|
DoPipelineBarrier(cmd, 1, &tilebarrier);
|
|
|
|
vt->EndCommandBuffer(Unwrap(cmd));
|
|
|
|
// submit cmds and wait for idle so we can readback
|
|
m_pDriver->SubmitCmds();
|
|
m_pDriver->FlushQ();
|
|
|
|
uint32_t *buckets = (uint32_t *)m_Histogram.m_HistogramReadback.Map(NULL);
|
|
|
|
histogram.assign(buckets, buckets + HGRAM_NUM_BUCKETS);
|
|
|
|
m_Histogram.m_HistogramReadback.Unmap();
|
|
|
|
return true;
|
|
}
|
|
|
|
vector<EventUsage> VulkanReplay::GetUsage(ResourceId id)
|
|
{
|
|
return m_pDriver->GetUsage(id);
|
|
}
|
|
|
|
void VulkanReplay::GetTextureData(ResourceId tex, uint32_t arrayIdx, uint32_t mip,
|
|
const GetTextureDataParams ¶ms, bytebuf &data)
|
|
{
|
|
bool wasms = false;
|
|
|
|
if(m_pDriver->m_CreationInfo.m_Image.find(tex) == m_pDriver->m_CreationInfo.m_Image.end())
|
|
{
|
|
RDCERR("Trying to get texture data for unknown ID %llu!", tex);
|
|
return;
|
|
}
|
|
|
|
const VulkanCreationInfo::Image &imInfo = m_pDriver->m_CreationInfo.m_Image[tex];
|
|
|
|
ImageLayouts &layouts = m_pDriver->m_ImageLayouts[tex];
|
|
|
|
VkImageCreateInfo imCreateInfo = {
|
|
VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
|
NULL,
|
|
0,
|
|
imInfo.type,
|
|
imInfo.format,
|
|
imInfo.extent,
|
|
(uint32_t)imInfo.mipLevels,
|
|
(uint32_t)imInfo.arrayLayers,
|
|
imInfo.samples,
|
|
VK_IMAGE_TILING_OPTIMAL,
|
|
VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
|
|
VK_SHARING_MODE_EXCLUSIVE,
|
|
0,
|
|
NULL,
|
|
VK_IMAGE_LAYOUT_UNDEFINED,
|
|
};
|
|
|
|
bool isDepth =
|
|
(layouts.subresourceStates[0].subresourceRange.aspectMask & VK_IMAGE_ASPECT_DEPTH_BIT) != 0;
|
|
bool isStencil =
|
|
(layouts.subresourceStates[0].subresourceRange.aspectMask & VK_IMAGE_ASPECT_STENCIL_BIT) != 0;
|
|
VkImageAspectFlags srcAspectMask = layouts.subresourceStates[0].subresourceRange.aspectMask;
|
|
|
|
VkImage srcImage = Unwrap(GetResourceManager()->GetCurrentHandle<VkImage>(tex));
|
|
VkImage tmpImage = VK_NULL_HANDLE;
|
|
VkDeviceMemory tmpMemory = VK_NULL_HANDLE;
|
|
|
|
uint32_t srcQueueIndex = layouts.queueFamilyIndex;
|
|
|
|
VkFramebuffer *tmpFB = NULL;
|
|
VkImageView *tmpView = NULL;
|
|
uint32_t numFBs = 0;
|
|
VkRenderPass tmpRP = VK_NULL_HANDLE;
|
|
|
|
VkDevice dev = m_pDriver->GetDev();
|
|
VkCommandBuffer cmd = m_pDriver->GetNextCmd();
|
|
const VkLayerDispatchTable *vt = ObjDisp(dev);
|
|
|
|
VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, NULL,
|
|
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT};
|
|
|
|
VkResult vkr = vt->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
if(imInfo.samples > 1)
|
|
{
|
|
// make image n-array instead of n-samples
|
|
imCreateInfo.arrayLayers *= imCreateInfo.samples;
|
|
imCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
|
|
|
|
wasms = true;
|
|
}
|
|
|
|
VkCommandBuffer extQCmd = VK_NULL_HANDLE;
|
|
|
|
if(params.remap != RemapTexture::NoRemap)
|
|
{
|
|
int renderFlags = 0;
|
|
|
|
// force readback texture to RGBA8 unorm
|
|
if(params.remap == RemapTexture::RGBA8)
|
|
{
|
|
imCreateInfo.format =
|
|
IsSRGBFormat(imCreateInfo.format) ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM;
|
|
}
|
|
else if(params.remap == RemapTexture::RGBA16)
|
|
{
|
|
imCreateInfo.format = VK_FORMAT_R16G16B16A16_SFLOAT;
|
|
renderFlags = eTexDisplay_F16Render;
|
|
}
|
|
else if(params.remap == RemapTexture::RGBA32)
|
|
{
|
|
imCreateInfo.format = VK_FORMAT_R32G32B32A32_SFLOAT;
|
|
renderFlags = eTexDisplay_F32Render;
|
|
}
|
|
else
|
|
{
|
|
RDCERR("Unsupported remap format: %u", params.remap);
|
|
}
|
|
|
|
// force to 1 array slice, 1 mip
|
|
imCreateInfo.arrayLayers = 1;
|
|
imCreateInfo.mipLevels = 1;
|
|
// force to 2D
|
|
imCreateInfo.imageType = VK_IMAGE_TYPE_2D;
|
|
imCreateInfo.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
|
|
|
imCreateInfo.extent.width = RDCMAX(1U, imCreateInfo.extent.width >> mip);
|
|
imCreateInfo.extent.height = RDCMAX(1U, imCreateInfo.extent.height >> mip);
|
|
imCreateInfo.extent.depth = RDCMAX(1U, imCreateInfo.extent.depth >> mip);
|
|
|
|
// convert a 3D texture into a 2D array, so we can render to the slices without needing
|
|
// KHR_maintenance1
|
|
if(imCreateInfo.extent.depth > 1)
|
|
{
|
|
imCreateInfo.arrayLayers = imCreateInfo.extent.depth;
|
|
imCreateInfo.extent.depth = 1;
|
|
}
|
|
|
|
// create render texture similar to readback texture
|
|
vt->CreateImage(Unwrap(dev), &imCreateInfo, NULL, &tmpImage);
|
|
|
|
VkMemoryRequirements mrq = {0};
|
|
vt->GetImageMemoryRequirements(Unwrap(dev), tmpImage, &mrq);
|
|
|
|
VkMemoryAllocateInfo allocInfo = {
|
|
VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, NULL, mrq.size,
|
|
m_pDriver->GetGPULocalMemoryIndex(mrq.memoryTypeBits),
|
|
};
|
|
|
|
vkr = vt->AllocateMemory(Unwrap(dev), &allocInfo, NULL, &tmpMemory);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
vkr = vt->BindImageMemory(Unwrap(dev), tmpImage, tmpMemory, 0);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
VkImageMemoryBarrier dstimBarrier = {
|
|
VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
|
NULL,
|
|
0,
|
|
0,
|
|
VK_IMAGE_LAYOUT_UNDEFINED,
|
|
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
tmpImage,
|
|
{VK_IMAGE_ASPECT_COLOR_BIT, 0, VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS},
|
|
};
|
|
|
|
// move tmp image into transfer destination layout
|
|
DoPipelineBarrier(cmd, 1, &dstimBarrier);
|
|
|
|
// end this command buffer, the rendertexture below will use its own and we want to ensure
|
|
// ordering
|
|
vt->EndCommandBuffer(Unwrap(cmd));
|
|
|
|
#if ENABLED(SINGLE_FLUSH_VALIDATE)
|
|
m_pDriver->SubmitCmds();
|
|
#endif
|
|
|
|
// create framebuffer/render pass to render to
|
|
VkAttachmentDescription attDesc = {0,
|
|
imCreateInfo.format,
|
|
VK_SAMPLE_COUNT_1_BIT,
|
|
VK_ATTACHMENT_LOAD_OP_LOAD,
|
|
VK_ATTACHMENT_STORE_OP_STORE,
|
|
VK_ATTACHMENT_LOAD_OP_DONT_CARE,
|
|
VK_ATTACHMENT_STORE_OP_DONT_CARE,
|
|
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
|
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
|
|
|
|
VkAttachmentReference attRef = {0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
|
|
|
|
VkSubpassDescription sub = {
|
|
0, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
|
0, NULL, // inputs
|
|
1, &attRef, // color
|
|
NULL, // resolve
|
|
NULL, // depth-stencil
|
|
0, NULL, // preserve
|
|
};
|
|
|
|
VkRenderPassCreateInfo rpinfo = {
|
|
VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
|
|
NULL,
|
|
0,
|
|
1,
|
|
&attDesc,
|
|
1,
|
|
&sub,
|
|
0,
|
|
NULL, // dependencies
|
|
};
|
|
vt->CreateRenderPass(Unwrap(dev), &rpinfo, NULL, &tmpRP);
|
|
|
|
numFBs = imCreateInfo.arrayLayers;
|
|
tmpFB = new VkFramebuffer[numFBs];
|
|
tmpView = new VkImageView[numFBs];
|
|
|
|
int oldW = m_DebugWidth, oldH = m_DebugHeight;
|
|
|
|
m_DebugWidth = imCreateInfo.extent.width;
|
|
m_DebugHeight = imCreateInfo.extent.height;
|
|
|
|
// if 3d texture, render each slice separately, otherwise render once
|
|
for(uint32_t i = 0; i < numFBs; i++)
|
|
{
|
|
if(numFBs > 1 && (i % m_TexRender.UBO.GetRingCount()) == 0)
|
|
{
|
|
m_pDriver->SubmitCmds();
|
|
m_pDriver->FlushQ();
|
|
}
|
|
|
|
TextureDisplay texDisplay;
|
|
|
|
texDisplay.red = texDisplay.green = texDisplay.blue = texDisplay.alpha = true;
|
|
texDisplay.hdrMultiplier = -1.0f;
|
|
texDisplay.linearDisplayAsGamma = false;
|
|
texDisplay.overlay = DebugOverlay::NoOverlay;
|
|
texDisplay.flipY = false;
|
|
texDisplay.mip = mip;
|
|
texDisplay.sampleIdx = imInfo.type == VK_IMAGE_TYPE_3D ? 0 : (params.resolve ? ~0U : arrayIdx);
|
|
texDisplay.customShaderId = ResourceId();
|
|
texDisplay.sliceFace = imInfo.type == VK_IMAGE_TYPE_3D ? i : arrayIdx;
|
|
if(imInfo.samples > 1)
|
|
texDisplay.sliceFace /= imInfo.samples;
|
|
texDisplay.rangeMin = params.blackPoint;
|
|
texDisplay.rangeMax = params.whitePoint;
|
|
texDisplay.scale = 1.0f;
|
|
texDisplay.resourceId = tex;
|
|
texDisplay.typeHint = CompType::Typeless;
|
|
texDisplay.rawOutput = false;
|
|
texDisplay.xOffset = 0;
|
|
texDisplay.yOffset = 0;
|
|
|
|
VkImageViewCreateInfo viewInfo = {
|
|
VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
|
NULL,
|
|
0,
|
|
tmpImage,
|
|
VK_IMAGE_VIEW_TYPE_2D,
|
|
imCreateInfo.format,
|
|
{VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY,
|
|
VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY},
|
|
{
|
|
VK_IMAGE_ASPECT_COLOR_BIT, 0, VK_REMAINING_MIP_LEVELS, i, 1,
|
|
},
|
|
};
|
|
|
|
vt->CreateImageView(Unwrap(dev), &viewInfo, NULL, &tmpView[i]);
|
|
|
|
VkFramebufferCreateInfo fbinfo = {
|
|
VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
|
|
NULL,
|
|
0,
|
|
tmpRP,
|
|
1,
|
|
&tmpView[i],
|
|
(uint32_t)imCreateInfo.extent.width,
|
|
(uint32_t)imCreateInfo.extent.height,
|
|
1,
|
|
};
|
|
|
|
vkr = vt->CreateFramebuffer(Unwrap(dev), &fbinfo, NULL, &tmpFB[i]);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
VkClearValue clearval = {};
|
|
VkRenderPassBeginInfo rpbegin = {
|
|
VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
|
NULL,
|
|
tmpRP,
|
|
tmpFB[i],
|
|
{{
|
|
0, 0,
|
|
},
|
|
{imCreateInfo.extent.width, imCreateInfo.extent.height}},
|
|
1,
|
|
&clearval,
|
|
};
|
|
|
|
RenderTextureInternal(texDisplay, rpbegin, renderFlags);
|
|
|
|
// for textures with stencil, do another draw to copy the stencil
|
|
if(isStencil)
|
|
{
|
|
texDisplay.red = texDisplay.blue = texDisplay.alpha = false;
|
|
RenderTextureInternal(texDisplay, rpbegin, renderFlags | eTexDisplay_GreenOnly);
|
|
}
|
|
}
|
|
|
|
m_DebugWidth = oldW;
|
|
m_DebugHeight = oldH;
|
|
|
|
srcImage = tmpImage;
|
|
srcQueueIndex = m_pDriver->GetQueueFamilyIndex();
|
|
|
|
// fetch a new command buffer for copy & readback
|
|
cmd = m_pDriver->GetNextCmd();
|
|
|
|
vkr = vt->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
// ensure all writes happen before copy & readback
|
|
dstimBarrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
|
dstimBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
|
dstimBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
|
dstimBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
|
|
|
DoPipelineBarrier(cmd, 1, &dstimBarrier);
|
|
|
|
// these have already been selected, don't need to fetch that subresource
|
|
// when copying back to readback buffer
|
|
arrayIdx = 0;
|
|
mip = 0;
|
|
|
|
// no longer depth, if it was
|
|
isDepth = false;
|
|
isStencil = false;
|
|
}
|
|
else if(wasms && params.resolve)
|
|
{
|
|
// force to 1 array slice, 1 mip
|
|
imCreateInfo.arrayLayers = 1;
|
|
imCreateInfo.mipLevels = 1;
|
|
|
|
imCreateInfo.extent.width = RDCMAX(1U, imCreateInfo.extent.width >> mip);
|
|
imCreateInfo.extent.height = RDCMAX(1U, imCreateInfo.extent.height >> mip);
|
|
|
|
// create resolve texture
|
|
vt->CreateImage(Unwrap(dev), &imCreateInfo, NULL, &tmpImage);
|
|
|
|
VkMemoryRequirements mrq = {0};
|
|
vt->GetImageMemoryRequirements(Unwrap(dev), tmpImage, &mrq);
|
|
|
|
VkMemoryAllocateInfo allocInfo = {
|
|
VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, NULL, mrq.size,
|
|
m_pDriver->GetGPULocalMemoryIndex(mrq.memoryTypeBits),
|
|
};
|
|
|
|
vkr = vt->AllocateMemory(Unwrap(dev), &allocInfo, NULL, &tmpMemory);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
vkr = vt->BindImageMemory(Unwrap(dev), tmpImage, tmpMemory, 0);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
RDCASSERT(!isDepth && !isStencil);
|
|
|
|
VkImageResolve resolveRegion = {
|
|
{VK_IMAGE_ASPECT_COLOR_BIT, mip, arrayIdx, 1},
|
|
{0, 0, 0},
|
|
{VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1},
|
|
{0, 0, 0},
|
|
imCreateInfo.extent,
|
|
};
|
|
|
|
VkImageMemoryBarrier srcimBarrier = {
|
|
VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
|
NULL,
|
|
0,
|
|
0,
|
|
VK_IMAGE_LAYOUT_UNDEFINED,
|
|
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
|
srcQueueIndex,
|
|
m_pDriver->GetQueueFamilyIndex(),
|
|
srcImage,
|
|
{srcAspectMask, 0, VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS},
|
|
};
|
|
|
|
VkImageMemoryBarrier dstimBarrier = {
|
|
VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
|
NULL,
|
|
0,
|
|
0,
|
|
VK_IMAGE_LAYOUT_UNDEFINED,
|
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
tmpImage,
|
|
{srcAspectMask, 0, VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS},
|
|
};
|
|
|
|
// ensure all previous writes have completed
|
|
srcimBarrier.srcAccessMask = VK_ACCESS_ALL_WRITE_BITS;
|
|
// before we go resolving
|
|
srcimBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
|
|
|
if(srcimBarrier.srcQueueFamilyIndex != srcimBarrier.dstQueueFamilyIndex)
|
|
{
|
|
extQCmd = m_pDriver->GetExtQueueCmd(srcimBarrier.srcQueueFamilyIndex);
|
|
|
|
vkr = ObjDisp(extQCmd)->BeginCommandBuffer(Unwrap(extQCmd), &beginInfo);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
}
|
|
|
|
for(size_t si = 0; si < layouts.subresourceStates.size(); si++)
|
|
{
|
|
srcimBarrier.subresourceRange = layouts.subresourceStates[si].subresourceRange;
|
|
srcimBarrier.oldLayout = layouts.subresourceStates[si].newLayout;
|
|
DoPipelineBarrier(cmd, 1, &srcimBarrier);
|
|
|
|
if(extQCmd != VK_NULL_HANDLE)
|
|
DoPipelineBarrier(extQCmd, 1, &srcimBarrier);
|
|
}
|
|
|
|
if(extQCmd != VK_NULL_HANDLE)
|
|
{
|
|
vkr = ObjDisp(extQCmd)->EndCommandBuffer(Unwrap(extQCmd));
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
m_pDriver->SubmitAndFlushExtQueue(layouts.queueFamilyIndex);
|
|
}
|
|
|
|
srcimBarrier.oldLayout = srcimBarrier.newLayout;
|
|
|
|
srcimBarrier.srcAccessMask = 0;
|
|
srcimBarrier.dstAccessMask = 0;
|
|
|
|
// move tmp image into transfer destination layout
|
|
DoPipelineBarrier(cmd, 1, &dstimBarrier);
|
|
|
|
// resolve from live texture to resolve texture
|
|
vt->CmdResolveImage(Unwrap(cmd), srcImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
|
Unwrap(tmpImage), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &resolveRegion);
|
|
|
|
std::swap(srcimBarrier.srcQueueFamilyIndex, srcimBarrier.dstQueueFamilyIndex);
|
|
|
|
if(extQCmd != VK_NULL_HANDLE)
|
|
{
|
|
vkr = ObjDisp(extQCmd)->BeginCommandBuffer(Unwrap(extQCmd), &beginInfo);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
}
|
|
|
|
// image layout back to normal
|
|
for(size_t si = 0; si < layouts.subresourceStates.size(); si++)
|
|
{
|
|
srcimBarrier.subresourceRange = layouts.subresourceStates[si].subresourceRange;
|
|
srcimBarrier.newLayout = layouts.subresourceStates[si].newLayout;
|
|
DoPipelineBarrier(cmd, 1, &srcimBarrier);
|
|
|
|
if(extQCmd != VK_NULL_HANDLE)
|
|
DoPipelineBarrier(extQCmd, 1, &srcimBarrier);
|
|
}
|
|
|
|
// wait for resolve to finish before copy to buffer
|
|
dstimBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
|
dstimBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
|
dstimBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
|
dstimBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
|
|
|
DoPipelineBarrier(cmd, 1, &dstimBarrier);
|
|
|
|
if(extQCmd != VK_NULL_HANDLE)
|
|
{
|
|
// ensure this resolve happens before handing back the source image to the original queue
|
|
vkr = vt->EndCommandBuffer(Unwrap(cmd));
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
m_pDriver->SubmitCmds();
|
|
m_pDriver->FlushQ();
|
|
|
|
vkr = ObjDisp(extQCmd)->EndCommandBuffer(Unwrap(extQCmd));
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
m_pDriver->SubmitAndFlushExtQueue(layouts.queueFamilyIndex);
|
|
|
|
extQCmd = VK_NULL_HANDLE;
|
|
|
|
// fetch a new command buffer for remaining work
|
|
cmd = m_pDriver->GetNextCmd();
|
|
|
|
vkr = vt->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
}
|
|
|
|
srcImage = tmpImage;
|
|
srcQueueIndex = m_pDriver->GetQueueFamilyIndex();
|
|
|
|
// these have already been selected, don't need to fetch that subresource
|
|
// when copying back to readback buffer
|
|
arrayIdx = 0;
|
|
mip = 0;
|
|
}
|
|
else if(wasms)
|
|
{
|
|
// copy/expand multisampled live texture to array readback texture
|
|
|
|
// multiply array layers by sample count
|
|
uint32_t numSamples = (uint32_t)imInfo.samples;
|
|
imCreateInfo.mipLevels = 1;
|
|
imCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
|
|
imCreateInfo.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
|
|
|
|
if(IsDepthOrStencilFormat(imCreateInfo.format))
|
|
imCreateInfo.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
|
|
else
|
|
imCreateInfo.usage |= VK_IMAGE_USAGE_STORAGE_BIT;
|
|
|
|
// create resolve texture
|
|
vt->CreateImage(Unwrap(dev), &imCreateInfo, NULL, &tmpImage);
|
|
|
|
VkMemoryRequirements mrq = {0};
|
|
vt->GetImageMemoryRequirements(Unwrap(dev), tmpImage, &mrq);
|
|
|
|
VkMemoryAllocateInfo allocInfo = {
|
|
VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, NULL, mrq.size,
|
|
m_pDriver->GetGPULocalMemoryIndex(mrq.memoryTypeBits),
|
|
};
|
|
|
|
vkr = vt->AllocateMemory(Unwrap(dev), &allocInfo, NULL, &tmpMemory);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
vkr = vt->BindImageMemory(Unwrap(dev), tmpImage, tmpMemory, 0);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
VkImageMemoryBarrier srcimBarrier = {
|
|
VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
|
NULL,
|
|
0,
|
|
0,
|
|
VK_IMAGE_LAYOUT_UNDEFINED,
|
|
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
|
srcQueueIndex,
|
|
m_pDriver->GetQueueFamilyIndex(),
|
|
srcImage,
|
|
{srcAspectMask, 0, VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS},
|
|
};
|
|
|
|
VkImageMemoryBarrier dstimBarrier = {
|
|
VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
|
NULL,
|
|
0,
|
|
0,
|
|
VK_IMAGE_LAYOUT_UNDEFINED,
|
|
VK_IMAGE_LAYOUT_GENERAL,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
tmpImage,
|
|
{srcAspectMask, 0, VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS},
|
|
};
|
|
|
|
// ensure all previous writes have completed
|
|
srcimBarrier.srcAccessMask = VK_ACCESS_ALL_WRITE_BITS;
|
|
// before we go copying to array
|
|
srcimBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
|
|
|
if(srcimBarrier.srcQueueFamilyIndex != srcimBarrier.dstQueueFamilyIndex)
|
|
{
|
|
extQCmd = m_pDriver->GetExtQueueCmd(srcimBarrier.srcQueueFamilyIndex);
|
|
|
|
vkr = ObjDisp(extQCmd)->BeginCommandBuffer(Unwrap(extQCmd), &beginInfo);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
}
|
|
|
|
for(size_t si = 0; si < layouts.subresourceStates.size(); si++)
|
|
{
|
|
srcimBarrier.subresourceRange = layouts.subresourceStates[si].subresourceRange;
|
|
srcimBarrier.oldLayout = layouts.subresourceStates[si].newLayout;
|
|
DoPipelineBarrier(cmd, 1, &srcimBarrier);
|
|
|
|
if(extQCmd != VK_NULL_HANDLE)
|
|
DoPipelineBarrier(extQCmd, 1, &srcimBarrier);
|
|
}
|
|
|
|
if(extQCmd != VK_NULL_HANDLE)
|
|
{
|
|
vkr = ObjDisp(extQCmd)->EndCommandBuffer(Unwrap(extQCmd));
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
m_pDriver->SubmitAndFlushExtQueue(layouts.queueFamilyIndex);
|
|
}
|
|
|
|
srcimBarrier.oldLayout = srcimBarrier.newLayout;
|
|
|
|
srcimBarrier.srcAccessMask = 0;
|
|
srcimBarrier.dstAccessMask = 0;
|
|
|
|
// move tmp image into transfer destination layout
|
|
DoPipelineBarrier(cmd, 1, &dstimBarrier);
|
|
|
|
vkr = vt->EndCommandBuffer(Unwrap(cmd));
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
// expand multisamples out to array
|
|
GetDebugManager()->CopyTex2DMSToArray(tmpImage, srcImage, imCreateInfo.extent,
|
|
imCreateInfo.arrayLayers / numSamples, numSamples,
|
|
imCreateInfo.format);
|
|
|
|
// fetch a new command buffer for copy & readback
|
|
cmd = m_pDriver->GetNextCmd();
|
|
|
|
vkr = vt->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
srcimBarrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
|
|
|
if(extQCmd != VK_NULL_HANDLE)
|
|
{
|
|
vkr = ObjDisp(extQCmd)->BeginCommandBuffer(Unwrap(extQCmd), &beginInfo);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
}
|
|
|
|
std::swap(srcimBarrier.srcQueueFamilyIndex, srcimBarrier.dstQueueFamilyIndex);
|
|
|
|
// image layout back to normal
|
|
for(size_t si = 0; si < layouts.subresourceStates.size(); si++)
|
|
{
|
|
srcimBarrier.subresourceRange = layouts.subresourceStates[si].subresourceRange;
|
|
srcimBarrier.newLayout = layouts.subresourceStates[si].newLayout;
|
|
srcimBarrier.dstAccessMask = MakeAccessMask(srcimBarrier.newLayout);
|
|
DoPipelineBarrier(cmd, 1, &srcimBarrier);
|
|
|
|
if(extQCmd != VK_NULL_HANDLE)
|
|
DoPipelineBarrier(extQCmd, 1, &srcimBarrier);
|
|
}
|
|
|
|
// wait for copy to finish before copy to buffer
|
|
dstimBarrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
|
|
dstimBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
|
dstimBarrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL;
|
|
dstimBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
|
|
|
DoPipelineBarrier(cmd, 1, &dstimBarrier);
|
|
|
|
if(extQCmd != VK_NULL_HANDLE)
|
|
{
|
|
// ensure this resolve happens before handing back the source image to the original queue
|
|
vkr = vt->EndCommandBuffer(Unwrap(cmd));
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
m_pDriver->SubmitCmds();
|
|
m_pDriver->FlushQ();
|
|
|
|
vkr = ObjDisp(extQCmd)->EndCommandBuffer(Unwrap(extQCmd));
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
m_pDriver->SubmitAndFlushExtQueue(layouts.queueFamilyIndex);
|
|
|
|
extQCmd = VK_NULL_HANDLE;
|
|
|
|
// fetch a new command buffer for remaining work
|
|
cmd = m_pDriver->GetNextCmd();
|
|
|
|
vkr = vt->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
}
|
|
|
|
srcImage = tmpImage;
|
|
srcQueueIndex = m_pDriver->GetQueueFamilyIndex();
|
|
}
|
|
|
|
VkImageMemoryBarrier srcimBarrier = {
|
|
VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
|
NULL,
|
|
0,
|
|
0,
|
|
VK_IMAGE_LAYOUT_UNDEFINED,
|
|
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
|
srcQueueIndex,
|
|
m_pDriver->GetQueueFamilyIndex(),
|
|
srcImage,
|
|
{srcAspectMask, 0, VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS},
|
|
};
|
|
|
|
// if we have no tmpImage, we're copying directly from the real image
|
|
if(tmpImage == VK_NULL_HANDLE)
|
|
{
|
|
// ensure all previous writes have completed
|
|
srcimBarrier.srcAccessMask = VK_ACCESS_ALL_WRITE_BITS;
|
|
// before we go resolving
|
|
srcimBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
|
|
|
if(srcimBarrier.srcQueueFamilyIndex != srcimBarrier.dstQueueFamilyIndex)
|
|
{
|
|
extQCmd = m_pDriver->GetExtQueueCmd(srcimBarrier.srcQueueFamilyIndex);
|
|
|
|
vkr = ObjDisp(extQCmd)->BeginCommandBuffer(Unwrap(extQCmd), &beginInfo);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
}
|
|
|
|
for(size_t si = 0; si < layouts.subresourceStates.size(); si++)
|
|
{
|
|
srcimBarrier.subresourceRange = layouts.subresourceStates[si].subresourceRange;
|
|
srcimBarrier.oldLayout = layouts.subresourceStates[si].newLayout;
|
|
DoPipelineBarrier(cmd, 1, &srcimBarrier);
|
|
|
|
if(extQCmd != VK_NULL_HANDLE)
|
|
DoPipelineBarrier(extQCmd, 1, &srcimBarrier);
|
|
}
|
|
|
|
if(extQCmd != VK_NULL_HANDLE)
|
|
{
|
|
vkr = ObjDisp(extQCmd)->EndCommandBuffer(Unwrap(extQCmd));
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
m_pDriver->SubmitAndFlushExtQueue(layouts.queueFamilyIndex);
|
|
}
|
|
}
|
|
|
|
VkImageAspectFlags copyAspects = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
|
|
if(isDepth)
|
|
copyAspects = VK_IMAGE_ASPECT_DEPTH_BIT;
|
|
else if(isStencil)
|
|
copyAspects = VK_IMAGE_ASPECT_STENCIL_BIT;
|
|
|
|
VkBufferImageCopy copyregion[2] = {
|
|
{
|
|
0,
|
|
0,
|
|
0,
|
|
{copyAspects, mip, arrayIdx, 1},
|
|
{
|
|
0, 0, 0,
|
|
},
|
|
imCreateInfo.extent,
|
|
},
|
|
// second region is only used for combined depth-stencil images
|
|
{
|
|
0,
|
|
0,
|
|
0,
|
|
{VK_IMAGE_ASPECT_STENCIL_BIT, mip, arrayIdx, 1},
|
|
{
|
|
0, 0, 0,
|
|
},
|
|
imCreateInfo.extent,
|
|
},
|
|
};
|
|
|
|
for(int i = 0; i < 2; i++)
|
|
{
|
|
copyregion[i].imageExtent.width = RDCMAX(1U, copyregion[i].imageExtent.width >> mip);
|
|
copyregion[i].imageExtent.height = RDCMAX(1U, copyregion[i].imageExtent.height >> mip);
|
|
copyregion[i].imageExtent.depth = RDCMAX(1U, copyregion[i].imageExtent.depth >> mip);
|
|
}
|
|
|
|
uint32_t dataSize = 0;
|
|
|
|
// for most combined depth-stencil images this will be large enough for both to be copied
|
|
// separately, but for D24S8 we need to add extra space since they won't be copied packed
|
|
dataSize = GetByteSize(imInfo.extent.width, imInfo.extent.height, imInfo.extent.depth,
|
|
imCreateInfo.format, mip);
|
|
|
|
if(imCreateInfo.format == VK_FORMAT_D24_UNORM_S8_UINT)
|
|
{
|
|
dataSize = AlignUp(dataSize, 4U);
|
|
dataSize += GetByteSize(imInfo.extent.width, imInfo.extent.height, imInfo.extent.depth,
|
|
VK_FORMAT_S8_UINT, mip);
|
|
}
|
|
|
|
VkBufferCreateInfo bufInfo = {
|
|
VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
|
NULL,
|
|
0,
|
|
dataSize,
|
|
VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
|
};
|
|
|
|
VkBuffer readbackBuf = VK_NULL_HANDLE;
|
|
vkr = vt->CreateBuffer(Unwrap(dev), &bufInfo, NULL, &readbackBuf);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
VkMemoryRequirements mrq = {0};
|
|
|
|
vt->GetBufferMemoryRequirements(Unwrap(dev), readbackBuf, &mrq);
|
|
|
|
VkMemoryAllocateInfo allocInfo = {
|
|
VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, NULL, dataSize,
|
|
m_pDriver->GetReadbackMemoryIndex(mrq.memoryTypeBits),
|
|
};
|
|
|
|
VkDeviceMemory readbackMem = VK_NULL_HANDLE;
|
|
vkr = vt->AllocateMemory(Unwrap(dev), &allocInfo, NULL, &readbackMem);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
vkr = vt->BindBufferMemory(Unwrap(dev), readbackBuf, readbackMem, 0);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
if(isDepth && isStencil)
|
|
{
|
|
copyregion[1].bufferOffset =
|
|
GetByteSize(imInfo.extent.width, imInfo.extent.height, imInfo.extent.depth,
|
|
GetDepthOnlyFormat(imCreateInfo.format), mip);
|
|
|
|
copyregion[1].bufferOffset = AlignUp(copyregion[1].bufferOffset, (VkDeviceSize)4);
|
|
|
|
vt->CmdCopyImageToBuffer(Unwrap(cmd), srcImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
|
readbackBuf, 2, copyregion);
|
|
}
|
|
else if(imInfo.type == VK_IMAGE_TYPE_3D && params.remap != RemapTexture::NoRemap)
|
|
{
|
|
// copy in each slice from the 2D array we created to render out the 3D texture
|
|
for(uint32_t i = 0; i < imCreateInfo.arrayLayers; i++)
|
|
{
|
|
copyregion[0].imageSubresource.baseArrayLayer = i;
|
|
copyregion[0].bufferOffset =
|
|
i * GetByteSize(imInfo.extent.width, imInfo.extent.height, 1, imCreateInfo.format, mip);
|
|
vt->CmdCopyImageToBuffer(Unwrap(cmd), srcImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
|
readbackBuf, 1, copyregion);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(imInfo.type == VK_IMAGE_TYPE_3D)
|
|
copyregion[0].imageSubresource.baseArrayLayer = 0;
|
|
|
|
// copy from desired subresource in srcImage to buffer
|
|
vt->CmdCopyImageToBuffer(Unwrap(cmd), srcImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
|
readbackBuf, 1, copyregion);
|
|
}
|
|
|
|
// if we have no tmpImage, we're copying directly from the real image
|
|
if(tmpImage == VK_NULL_HANDLE)
|
|
{
|
|
// ensure transfer has completed
|
|
srcimBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
|
|
|
std::swap(srcimBarrier.srcQueueFamilyIndex, srcimBarrier.dstQueueFamilyIndex);
|
|
|
|
if(extQCmd != VK_NULL_HANDLE)
|
|
{
|
|
vkr = ObjDisp(extQCmd)->BeginCommandBuffer(Unwrap(extQCmd), &beginInfo);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
}
|
|
|
|
// image layout back to normal
|
|
for(size_t si = 0; si < layouts.subresourceStates.size(); si++)
|
|
{
|
|
srcimBarrier.subresourceRange = layouts.subresourceStates[si].subresourceRange;
|
|
srcimBarrier.newLayout = layouts.subresourceStates[si].newLayout;
|
|
srcimBarrier.dstAccessMask = MakeAccessMask(srcimBarrier.newLayout);
|
|
DoPipelineBarrier(cmd, 1, &srcimBarrier);
|
|
|
|
if(extQCmd != VK_NULL_HANDLE)
|
|
DoPipelineBarrier(extQCmd, 1, &srcimBarrier);
|
|
}
|
|
}
|
|
|
|
VkBufferMemoryBarrier bufBarrier = {
|
|
VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
|
|
NULL,
|
|
VK_ACCESS_TRANSFER_WRITE_BIT,
|
|
VK_ACCESS_HOST_READ_BIT,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
readbackBuf,
|
|
0,
|
|
dataSize,
|
|
};
|
|
|
|
// wait for copy to finish before reading back to host
|
|
DoPipelineBarrier(cmd, 1, &bufBarrier);
|
|
|
|
vt->EndCommandBuffer(Unwrap(cmd));
|
|
|
|
m_pDriver->SubmitCmds();
|
|
m_pDriver->FlushQ();
|
|
|
|
if(extQCmd != VK_NULL_HANDLE)
|
|
{
|
|
vkr = ObjDisp(extQCmd)->EndCommandBuffer(Unwrap(extQCmd));
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
m_pDriver->SubmitAndFlushExtQueue(layouts.queueFamilyIndex);
|
|
|
|
extQCmd = VK_NULL_HANDLE;
|
|
}
|
|
|
|
// map the buffer and copy to return buffer
|
|
byte *pData = NULL;
|
|
vkr = vt->MapMemory(Unwrap(dev), readbackMem, 0, VK_WHOLE_SIZE, 0, (void **)&pData);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
VkMappedMemoryRange range = {
|
|
VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, NULL, readbackMem, 0, VK_WHOLE_SIZE,
|
|
};
|
|
|
|
vkr = vt->InvalidateMappedMemoryRanges(Unwrap(dev), 1, &range);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
RDCASSERT(pData != NULL);
|
|
|
|
data.resize(dataSize);
|
|
|
|
if(isDepth && isStencil)
|
|
{
|
|
size_t pixelCount =
|
|
imCreateInfo.extent.width * imCreateInfo.extent.height * imCreateInfo.extent.depth;
|
|
|
|
// for some reason reading direct from mapped memory here is *super* slow on android (1.5s to
|
|
// iterate over the image), so we memcpy to a temporary buffer.
|
|
std::vector<byte> tmp;
|
|
tmp.resize((size_t)copyregion[1].bufferOffset + pixelCount * sizeof(uint8_t));
|
|
memcpy(tmp.data(), pData, tmp.size());
|
|
|
|
if(imCreateInfo.format == VK_FORMAT_D16_UNORM_S8_UINT)
|
|
{
|
|
uint16_t *dSrc = (uint16_t *)tmp.data();
|
|
uint8_t *sSrc = (uint8_t *)(tmp.data() + copyregion[1].bufferOffset);
|
|
|
|
uint16_t *dDst = (uint16_t *)data.data();
|
|
uint16_t *sDst = dDst + 1; // interleaved, next pixel
|
|
|
|
for(size_t i = 0; i < pixelCount; i++)
|
|
{
|
|
*dDst = *dSrc;
|
|
*sDst = *sSrc;
|
|
|
|
// increment source pointers by 1 since they're separate, and dest pointers by 2 since
|
|
// they're interleaved
|
|
dDst += 2;
|
|
sDst += 2;
|
|
|
|
sSrc++;
|
|
dSrc++;
|
|
}
|
|
}
|
|
else if(imCreateInfo.format == VK_FORMAT_D24_UNORM_S8_UINT)
|
|
{
|
|
// we can copy the depth from D24 as a 32-bit integer, since the remaining bits are garbage
|
|
// and we overwrite them with stencil
|
|
uint32_t *dSrc = (uint32_t *)tmp.data();
|
|
uint8_t *sSrc = (uint8_t *)(tmp.data() + copyregion[1].bufferOffset);
|
|
|
|
uint32_t *dst = (uint32_t *)data.data();
|
|
|
|
for(size_t i = 0; i < pixelCount; i++)
|
|
{
|
|
// pack the data together again, stencil in top bits
|
|
*dst = (*dSrc & 0x00ffffff) | (uint32_t(*sSrc) << 24);
|
|
|
|
dst++;
|
|
sSrc++;
|
|
dSrc++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
uint32_t *dSrc = (uint32_t *)tmp.data();
|
|
uint8_t *sSrc = (uint8_t *)(tmp.data() + copyregion[1].bufferOffset);
|
|
|
|
uint32_t *dDst = (uint32_t *)data.data();
|
|
uint32_t *sDst = dDst + 1; // interleaved, next pixel
|
|
|
|
for(size_t i = 0; i < pixelCount; i++)
|
|
{
|
|
*dDst = *dSrc;
|
|
*sDst = *sSrc;
|
|
|
|
// increment source pointers by 1 since they're separate, and dest pointers by 2 since
|
|
// they're interleaved
|
|
dDst += 2;
|
|
sDst += 2;
|
|
|
|
sSrc++;
|
|
dSrc++;
|
|
}
|
|
}
|
|
// need to manually copy to interleave pixels
|
|
}
|
|
else
|
|
{
|
|
memcpy(data.data(), pData, dataSize);
|
|
}
|
|
|
|
vt->UnmapMemory(Unwrap(dev), readbackMem);
|
|
|
|
// clean up temporary objects
|
|
vt->DestroyBuffer(Unwrap(dev), readbackBuf, NULL);
|
|
vt->FreeMemory(Unwrap(dev), readbackMem, NULL);
|
|
|
|
if(tmpImage != VK_NULL_HANDLE)
|
|
{
|
|
vt->DestroyImage(Unwrap(dev), tmpImage, NULL);
|
|
vt->FreeMemory(Unwrap(dev), tmpMemory, NULL);
|
|
}
|
|
|
|
if(tmpFB != NULL)
|
|
{
|
|
for(uint32_t i = 0; i < numFBs; i++)
|
|
{
|
|
vt->DestroyFramebuffer(Unwrap(dev), tmpFB[i], NULL);
|
|
vt->DestroyImageView(Unwrap(dev), tmpView[i], NULL);
|
|
}
|
|
delete[] tmpFB;
|
|
delete[] tmpView;
|
|
vt->DestroyRenderPass(Unwrap(dev), tmpRP, NULL);
|
|
}
|
|
}
|
|
|
|
void VulkanReplay::BuildCustomShader(string source, string entry,
|
|
const ShaderCompileFlags &compileFlags, ShaderStage type,
|
|
ResourceId *id, string *errors)
|
|
{
|
|
SPIRVShaderStage stage = SPIRVShaderStage::Invalid;
|
|
|
|
switch(type)
|
|
{
|
|
case ShaderStage::Vertex: stage = SPIRVShaderStage::Vertex; break;
|
|
case ShaderStage::Hull: stage = SPIRVShaderStage::TessControl; break;
|
|
case ShaderStage::Domain: stage = SPIRVShaderStage::TessEvaluation; break;
|
|
case ShaderStage::Geometry: stage = SPIRVShaderStage::Geometry; break;
|
|
case ShaderStage::Pixel: stage = SPIRVShaderStage::Fragment; break;
|
|
case ShaderStage::Compute: stage = SPIRVShaderStage::Compute; break;
|
|
default:
|
|
RDCERR("Unexpected type in BuildShader!");
|
|
*id = ResourceId();
|
|
return;
|
|
}
|
|
|
|
vector<string> sources;
|
|
sources.push_back(source);
|
|
vector<uint32_t> spirv;
|
|
|
|
SPIRVCompilationSettings settings(SPIRVSourceLanguage::VulkanGLSL, stage);
|
|
|
|
string output = CompileSPIRV(settings, sources, spirv);
|
|
|
|
if(spirv.empty())
|
|
{
|
|
*id = ResourceId();
|
|
*errors = output;
|
|
return;
|
|
}
|
|
|
|
VkShaderModuleCreateInfo modinfo = {
|
|
VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
|
|
NULL,
|
|
0,
|
|
spirv.size() * sizeof(uint32_t),
|
|
&spirv[0],
|
|
};
|
|
|
|
VkShaderModule module;
|
|
VkResult vkr = m_pDriver->vkCreateShaderModule(m_pDriver->GetDev(), &modinfo, NULL, &module);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
*id = GetResID(module);
|
|
}
|
|
|
|
void VulkanReplay::FreeCustomShader(ResourceId id)
|
|
{
|
|
if(id == ResourceId())
|
|
return;
|
|
|
|
m_pDriver->ReleaseResource(GetResourceManager()->GetCurrentResource(id));
|
|
}
|
|
|
|
ResourceId VulkanReplay::ApplyCustomShader(ResourceId shader, ResourceId texid, uint32_t mip,
|
|
uint32_t arrayIdx, uint32_t sampleIdx, CompType typeHint)
|
|
{
|
|
if(shader == ResourceId() || texid == ResourceId())
|
|
return ResourceId();
|
|
|
|
VulkanCreationInfo::Image &iminfo = m_pDriver->m_CreationInfo.m_Image[texid];
|
|
|
|
GetDebugManager()->CreateCustomShaderTex(iminfo.extent.width, iminfo.extent.height, mip);
|
|
|
|
int oldW = m_DebugWidth, oldH = m_DebugHeight;
|
|
|
|
m_DebugWidth = RDCMAX(1U, iminfo.extent.width >> mip);
|
|
m_DebugHeight = RDCMAX(1U, iminfo.extent.height >> mip);
|
|
|
|
TextureDisplay disp;
|
|
disp.red = disp.green = disp.blue = disp.alpha = true;
|
|
disp.flipY = false;
|
|
disp.xOffset = 0.0f;
|
|
disp.yOffset = 0.0f;
|
|
disp.customShaderId = shader;
|
|
disp.resourceId = texid;
|
|
disp.typeHint = typeHint;
|
|
disp.hdrMultiplier = -1.0f;
|
|
disp.linearDisplayAsGamma = false;
|
|
disp.mip = mip;
|
|
disp.sampleIdx = sampleIdx;
|
|
disp.overlay = DebugOverlay::NoOverlay;
|
|
disp.rangeMin = 0.0f;
|
|
disp.rangeMax = 1.0f;
|
|
disp.rawOutput = false;
|
|
disp.scale = 1.0f;
|
|
disp.sliceFace = arrayIdx;
|
|
|
|
VkClearValue clearval = {{{0.0f, 0.0f, 0.0f, 1.0f}}};
|
|
VkRenderPassBeginInfo rpbegin = {
|
|
VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
|
NULL,
|
|
Unwrap(GetDebugManager()->GetCustomRenderpass()),
|
|
Unwrap(GetDebugManager()->GetCustomFramebuffer()),
|
|
{{
|
|
0, 0,
|
|
},
|
|
{m_DebugWidth, m_DebugHeight}},
|
|
1,
|
|
&clearval,
|
|
};
|
|
|
|
RenderTextureInternal(disp, rpbegin, eTexDisplay_MipShift);
|
|
|
|
m_DebugWidth = oldW;
|
|
m_DebugHeight = oldH;
|
|
|
|
return GetResID(GetDebugManager()->GetCustomTexture());
|
|
}
|
|
|
|
void VulkanReplay::BuildTargetShader(ShaderEncoding sourceEncoding, bytebuf source, string entry,
|
|
const ShaderCompileFlags &compileFlags, ShaderStage type,
|
|
ResourceId *id, string *errors)
|
|
{
|
|
vector<uint32_t> spirv;
|
|
|
|
if(sourceEncoding == ShaderEncoding::GLSL)
|
|
{
|
|
SPIRVShaderStage stage = SPIRVShaderStage::Invalid;
|
|
|
|
switch(type)
|
|
{
|
|
case ShaderStage::Vertex: stage = SPIRVShaderStage::Vertex; break;
|
|
case ShaderStage::Hull: stage = SPIRVShaderStage::TessControl; break;
|
|
case ShaderStage::Domain: stage = SPIRVShaderStage::TessEvaluation; break;
|
|
case ShaderStage::Geometry: stage = SPIRVShaderStage::Geometry; break;
|
|
case ShaderStage::Pixel: stage = SPIRVShaderStage::Fragment; break;
|
|
case ShaderStage::Compute: stage = SPIRVShaderStage::Compute; break;
|
|
default:
|
|
RDCERR("Unexpected type in BuildShader!");
|
|
*id = ResourceId();
|
|
return;
|
|
}
|
|
|
|
vector<string> sources;
|
|
sources.push_back(std::string((char *)source.begin(), (char *)source.end()));
|
|
|
|
SPIRVCompilationSettings settings(SPIRVSourceLanguage::VulkanGLSL, stage);
|
|
|
|
string output = CompileSPIRV(settings, sources, spirv);
|
|
|
|
if(spirv.empty())
|
|
{
|
|
*id = ResourceId();
|
|
*errors = output;
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
spirv.resize(source.size() / 4);
|
|
memcpy(&spirv[0], source.data(), source.size());
|
|
}
|
|
|
|
VkShaderModuleCreateInfo modinfo = {
|
|
VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
|
|
NULL,
|
|
0,
|
|
spirv.size() * sizeof(uint32_t),
|
|
&spirv[0],
|
|
};
|
|
|
|
VkShaderModule module;
|
|
VkResult vkr = m_pDriver->vkCreateShaderModule(m_pDriver->GetDev(), &modinfo, NULL, &module);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
*id = GetResID(module);
|
|
}
|
|
|
|
void VulkanReplay::FreeTargetResource(ResourceId id)
|
|
{
|
|
if(id == ResourceId())
|
|
return;
|
|
|
|
m_pDriver->ReleaseResource(GetResourceManager()->GetCurrentResource(id));
|
|
}
|
|
|
|
void VulkanReplay::ReplaceResource(ResourceId from, ResourceId to)
|
|
{
|
|
VkDevice dev = m_pDriver->GetDev();
|
|
|
|
VulkanResourceManager *rm = m_pDriver->GetResourceManager();
|
|
|
|
// we're passed in the original ID but we want the live ID for comparison
|
|
ResourceId liveid = rm->GetLiveID(from);
|
|
|
|
VkShaderModule srcShaderModule = rm->GetCurrentHandle<VkShaderModule>(liveid);
|
|
VkShaderModule dstShaderModule = rm->GetCurrentHandle<VkShaderModule>(to);
|
|
|
|
// remake and replace any pipelines that referenced this shader
|
|
for(auto it = m_pDriver->m_CreationInfo.m_Pipeline.begin();
|
|
it != m_pDriver->m_CreationInfo.m_Pipeline.end(); ++it)
|
|
{
|
|
bool refdShader = false;
|
|
for(size_t i = 0; i < ARRAY_COUNT(it->second.shaders); i++)
|
|
{
|
|
if(it->second.shaders[i].module == liveid)
|
|
{
|
|
refdShader = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(refdShader)
|
|
{
|
|
VkPipeline pipe = VK_NULL_HANDLE;
|
|
const VulkanCreationInfo::Pipeline &pipeInfo = m_pDriver->m_CreationInfo.m_Pipeline[it->first];
|
|
if(pipeInfo.renderpass != ResourceId()) // check if this is a graphics or compute pipeline
|
|
{
|
|
VkGraphicsPipelineCreateInfo pipeCreateInfo;
|
|
m_pDriver->GetShaderCache()->MakeGraphicsPipelineInfo(pipeCreateInfo, it->first);
|
|
|
|
// replace the relevant module
|
|
for(uint32_t i = 0; i < pipeCreateInfo.stageCount; i++)
|
|
{
|
|
VkPipelineShaderStageCreateInfo &sh =
|
|
(VkPipelineShaderStageCreateInfo &)pipeCreateInfo.pStages[i];
|
|
|
|
if(sh.module == srcShaderModule)
|
|
sh.module = dstShaderModule;
|
|
}
|
|
|
|
// create the new graphics pipeline
|
|
VkResult vkr = m_pDriver->vkCreateGraphicsPipelines(dev, VK_NULL_HANDLE, 1, &pipeCreateInfo,
|
|
NULL, &pipe);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
}
|
|
else
|
|
{
|
|
VkComputePipelineCreateInfo pipeCreateInfo;
|
|
m_pDriver->GetShaderCache()->MakeComputePipelineInfo(pipeCreateInfo, it->first);
|
|
|
|
// replace the relevant module
|
|
VkPipelineShaderStageCreateInfo &sh = pipeCreateInfo.stage;
|
|
RDCASSERT(sh.module == srcShaderModule);
|
|
sh.module = dstShaderModule;
|
|
|
|
// create the new compute pipeline
|
|
VkResult vkr = m_pDriver->vkCreateComputePipelines(dev, VK_NULL_HANDLE, 1, &pipeCreateInfo,
|
|
NULL, &pipe);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
}
|
|
|
|
// remove the replacements
|
|
rm->ReplaceResource(it->first, GetResID(pipe));
|
|
rm->ReplaceResource(rm->GetOriginalID(it->first), GetResID(pipe));
|
|
}
|
|
}
|
|
|
|
// make the actual shader module replacements
|
|
rm->ReplaceResource(from, to);
|
|
rm->ReplaceResource(liveid, to);
|
|
|
|
ClearPostVSCache();
|
|
ClearFeedbackCache();
|
|
}
|
|
|
|
void VulkanReplay::RemoveReplacement(ResourceId id)
|
|
{
|
|
VkDevice dev = m_pDriver->GetDev();
|
|
|
|
VulkanResourceManager *rm = m_pDriver->GetResourceManager();
|
|
|
|
// we're passed in the original ID but we want the live ID for comparison
|
|
ResourceId liveid = rm->GetLiveID(id);
|
|
|
|
if(!rm->HasReplacement(id))
|
|
return;
|
|
|
|
// remove the actual shader module replacements
|
|
rm->RemoveReplacement(id);
|
|
rm->RemoveReplacement(liveid);
|
|
|
|
// remove any replacements on pipelines that referenced this shader
|
|
for(auto it = m_pDriver->m_CreationInfo.m_Pipeline.begin();
|
|
it != m_pDriver->m_CreationInfo.m_Pipeline.end(); ++it)
|
|
{
|
|
bool refdShader = false;
|
|
for(size_t i = 0; i < ARRAY_COUNT(it->second.shaders); i++)
|
|
{
|
|
if(it->second.shaders[i].module == liveid)
|
|
{
|
|
refdShader = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(refdShader)
|
|
{
|
|
VkPipeline pipe = rm->GetCurrentHandle<VkPipeline>(it->first);
|
|
|
|
// delete the replacement pipeline
|
|
m_pDriver->vkDestroyPipeline(dev, pipe, NULL);
|
|
|
|
// remove both live and original replacements, since we will have made these above
|
|
rm->RemoveReplacement(it->first);
|
|
rm->RemoveReplacement(rm->GetOriginalID(it->first));
|
|
}
|
|
}
|
|
|
|
ClearPostVSCache();
|
|
ClearFeedbackCache();
|
|
}
|
|
|
|
vector<PixelModification> VulkanReplay::PixelHistory(vector<EventUsage> events, ResourceId target,
|
|
uint32_t x, uint32_t y, uint32_t slice,
|
|
uint32_t mip, uint32_t sampleIdx,
|
|
CompType typeHint)
|
|
{
|
|
VULKANNOTIMP("PixelHistory");
|
|
return vector<PixelModification>();
|
|
}
|
|
|
|
ShaderDebugTrace VulkanReplay::DebugVertex(uint32_t eventId, uint32_t vertid, uint32_t instid,
|
|
uint32_t idx, uint32_t instOffset, uint32_t vertOffset)
|
|
{
|
|
VULKANNOTIMP("DebugVertex");
|
|
return ShaderDebugTrace();
|
|
}
|
|
|
|
ShaderDebugTrace VulkanReplay::DebugPixel(uint32_t eventId, uint32_t x, uint32_t y, uint32_t sample,
|
|
uint32_t primitive)
|
|
{
|
|
VULKANNOTIMP("DebugPixel");
|
|
return ShaderDebugTrace();
|
|
}
|
|
|
|
ShaderDebugTrace VulkanReplay::DebugThread(uint32_t eventId, const uint32_t groupid[3],
|
|
const uint32_t threadid[3])
|
|
{
|
|
VULKANNOTIMP("DebugThread");
|
|
return ShaderDebugTrace();
|
|
}
|
|
|
|
ResourceId VulkanReplay::CreateProxyTexture(const TextureDescription &templateTex)
|
|
{
|
|
VULKANNOTIMP("CreateProxyTexture");
|
|
return ResourceId();
|
|
}
|
|
|
|
void VulkanReplay::SetProxyTextureData(ResourceId texid, uint32_t arrayIdx, uint32_t mip,
|
|
byte *data, size_t dataSize)
|
|
{
|
|
VULKANNOTIMP("SetProxyTextureData");
|
|
}
|
|
|
|
bool VulkanReplay::IsTextureSupported(const ResourceFormat &format)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool VulkanReplay::NeedRemapForFetch(const ResourceFormat &format)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ResourceId VulkanReplay::CreateProxyBuffer(const BufferDescription &templateBuf)
|
|
{
|
|
VULKANNOTIMP("CreateProxyBuffer");
|
|
return ResourceId();
|
|
}
|
|
|
|
void VulkanReplay::SetProxyBufferData(ResourceId bufid, byte *data, size_t dataSize)
|
|
{
|
|
VULKANNOTIMP("SetProxyTextureData");
|
|
}
|
|
|
|
ReplayStatus Vulkan_CreateReplayDevice(RDCFile *rdc, IReplayDriver **driver)
|
|
{
|
|
RDCDEBUG("Creating a VulkanReplay replay device");
|
|
|
|
// disable the layer env var, just in case the user left it set from a previous capture run
|
|
Process::RegisterEnvironmentModification(
|
|
EnvironmentModification(EnvMod::Set, EnvSep::NoSep, "ENABLE_VULKAN_RENDERDOC_CAPTURE", "0"));
|
|
|
|
// disable buggy and user-hostile NV optimus layer, which can completely delete physical devices
|
|
// (not just rearrange them) and cause problems between capture and replay.
|
|
Process::RegisterEnvironmentModification(
|
|
EnvironmentModification(EnvMod::Set, EnvSep::NoSep, "DISABLE_LAYER_NV_OPTIMUS_1", ""));
|
|
|
|
Process::ApplyEnvironmentModification();
|
|
|
|
void *module = LoadVulkanLibrary();
|
|
|
|
if(module == NULL)
|
|
{
|
|
RDCERR("Failed to load vulkan library");
|
|
return ReplayStatus::APIInitFailed;
|
|
}
|
|
|
|
VkInitParams initParams;
|
|
|
|
uint64_t ver = VkInitParams::CurrentVersion;
|
|
|
|
// if we have an RDCFile, open the frame capture section and serialise the init params.
|
|
// if not, we're creating a proxy-capable device so use default-initialised init params.
|
|
if(rdc)
|
|
{
|
|
int sectionIdx = rdc->SectionIndex(SectionType::FrameCapture);
|
|
|
|
if(sectionIdx < 0)
|
|
return ReplayStatus::InternalError;
|
|
|
|
ver = rdc->GetSectionProperties(sectionIdx).version;
|
|
|
|
if(!VkInitParams::IsSupportedVersion(ver))
|
|
{
|
|
RDCERR("Incompatible Vulkan serialise version %llu", ver);
|
|
return ReplayStatus::APIIncompatibleVersion;
|
|
}
|
|
|
|
StreamReader *reader = rdc->ReadSection(sectionIdx);
|
|
|
|
ReadSerialiser ser(reader, Ownership::Stream);
|
|
|
|
SystemChunk chunk = ser.ReadChunk<SystemChunk>();
|
|
|
|
if(chunk != SystemChunk::DriverInit)
|
|
{
|
|
RDCERR("Expected to get a DriverInit chunk, instead got %u", chunk);
|
|
return ReplayStatus::FileCorrupted;
|
|
}
|
|
|
|
SERIALISE_ELEMENT(initParams);
|
|
|
|
if(ser.IsErrored())
|
|
{
|
|
RDCERR("Failed reading driver init params.");
|
|
return ReplayStatus::FileIOFailed;
|
|
}
|
|
}
|
|
|
|
InitReplayTables(module);
|
|
|
|
AMDRGPControl *rgp = new AMDRGPControl();
|
|
|
|
if(!rgp->Initialised())
|
|
SAFE_DELETE(rgp);
|
|
|
|
WrappedVulkan *vk = new WrappedVulkan();
|
|
ReplayStatus status = vk->Initialise(initParams, ver);
|
|
|
|
if(status != ReplayStatus::Succeeded)
|
|
{
|
|
SAFE_DELETE(rgp);
|
|
|
|
delete vk;
|
|
return status;
|
|
}
|
|
|
|
RDCLOG("Created device.");
|
|
VulkanReplay *replay = vk->GetReplay();
|
|
replay->SetProxy(rdc == NULL);
|
|
replay->SetRGP(rgp);
|
|
|
|
*driver = (IReplayDriver *)replay;
|
|
|
|
replay->GetInitialDriverVersion();
|
|
|
|
return ReplayStatus::Succeeded;
|
|
}
|
|
|
|
struct VulkanDriverRegistration
|
|
{
|
|
VulkanDriverRegistration()
|
|
{
|
|
RenderDoc::Inst().RegisterReplayProvider(RDCDriver::Vulkan, &Vulkan_CreateReplayDevice);
|
|
RenderDoc::Inst().SetVulkanLayerCheck(&VulkanReplay::CheckVulkanLayer);
|
|
RenderDoc::Inst().SetVulkanLayerInstall(&VulkanReplay::InstallVulkanLayer);
|
|
}
|
|
};
|
|
|
|
static VulkanDriverRegistration VkDriverRegistration;
|
|
|
|
void Vulkan_ProcessStructured(RDCFile *rdc, SDFile &output)
|
|
{
|
|
WrappedVulkan vulkan;
|
|
|
|
int sectionIdx = rdc->SectionIndex(SectionType::FrameCapture);
|
|
|
|
if(sectionIdx < 0)
|
|
return;
|
|
|
|
vulkan.SetStructuredExport(rdc->GetSectionProperties(sectionIdx).version);
|
|
ReplayStatus status = vulkan.ReadLogInitialisation(rdc, true);
|
|
|
|
if(status == ReplayStatus::Succeeded)
|
|
vulkan.GetStructuredFile().Swap(output);
|
|
}
|
|
|
|
static StructuredProcessRegistration VulkanProcessRegistration(RDCDriver::Vulkan,
|
|
&Vulkan_ProcessStructured); |