Files
renderdoc/renderdoc/driver/vulkan/vk_debug.cpp
T
baldurk ed018f23e8 Add shader edit & replace for vulkan (without source for now)
* Currently at least glslang doesn't emit OpSource with source data
  embedded, so we don't pull it out and for the moment we just pre-fill
  the shader editor with the disassembly text (which is sort of but not
  really GLSL) as a better-than-nothing default.
2016-06-09 15:30:31 -07:00

6904 lines
230 KiB
C++

/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015-2016 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_debug.h"
#include <float.h>
#include "3rdparty/glslang/SPIRV/spirv.hpp"
#include "3rdparty/stb/stb_truetype.h"
#include "common/shader_cache.h"
#include "data/spv/debuguniforms.h"
#include "driver/shaders/spirv/spirv_common.h"
#include "maths/camera.h"
#include "maths/formatpacking.h"
#include "maths/matrix.h"
#include "serialise/string_utils.h"
#include "vk_core.h"
const VkDeviceSize STAGE_BUFFER_BYTE_SIZE = 16 * 1024 * 1024ULL;
void VulkanDebugManager::GPUBuffer::Create(WrappedVulkan *driver, VkDevice dev, VkDeviceSize size,
uint32_t ringSize, uint32_t flags)
{
m_pDriver = driver;
device = dev;
align = (VkDeviceSize)driver->GetDeviceProps().limits.minUniformBufferOffsetAlignment;
sz = size;
// offset must be aligned, so ensure we have at least ringSize
// copies accounting for that
totalsize = ringSize == 1 ? size : AlignUp(size, align) * ringSize;
curoffset = 0;
ringCount = ringSize;
VkBufferCreateInfo bufInfo = {
VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, NULL, 0, totalsize, 0,
};
bufInfo.usage |= VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
bufInfo.usage |= VK_BUFFER_USAGE_TRANSFER_DST_BIT;
bufInfo.usage |= VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
if(flags & eGPUBufferVBuffer)
bufInfo.usage |= VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
if(flags & eGPUBufferSSBO)
bufInfo.usage |= VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
VkResult vkr = driver->vkCreateBuffer(dev, &bufInfo, NULL, &buf);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
VkMemoryRequirements mrq = {};
driver->vkGetBufferMemoryRequirements(dev, buf, &mrq);
VkMemoryAllocateInfo allocInfo = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, NULL, mrq.size, 0};
if(flags & eGPUBufferReadback)
allocInfo.memoryTypeIndex = driver->GetReadbackMemoryIndex(mrq.memoryTypeBits);
else if(flags & eGPUBufferGPULocal)
allocInfo.memoryTypeIndex = driver->GetGPULocalMemoryIndex(mrq.memoryTypeBits);
else
allocInfo.memoryTypeIndex = driver->GetUploadMemoryIndex(mrq.memoryTypeBits);
vkr = driver->vkAllocateMemory(dev, &allocInfo, NULL, &mem);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
vkr = driver->vkBindBufferMemory(dev, buf, mem, 0);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
}
void VulkanDebugManager::GPUBuffer::FillDescriptor(VkDescriptorBufferInfo &desc)
{
desc.buffer = Unwrap(buf);
desc.offset = 0;
desc.range = sz;
}
void VulkanDebugManager::GPUBuffer::Destroy()
{
m_pDriver->vkDestroyBuffer(device, buf, NULL);
m_pDriver->vkFreeMemory(device, mem, NULL);
}
void *VulkanDebugManager::GPUBuffer::Map(uint32_t *bindoffset, VkDeviceSize usedsize)
{
VkDeviceSize offset = bindoffset ? curoffset : 0;
VkDeviceSize size = usedsize > 0 ? usedsize : sz;
// wrap around the ring, assuming the ring is large enough
// that this memory is now free
if(offset + sz > totalsize)
offset = 0;
RDCASSERT(offset + sz <= totalsize);
// offset must be aligned
curoffset = AlignUp(offset + size, align);
if(bindoffset)
*bindoffset = (uint32_t)offset;
void *ptr = NULL;
VkResult vkr = m_pDriver->vkMapMemory(device, mem, offset, size, 0, (void **)&ptr);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
return ptr;
}
void *VulkanDebugManager::GPUBuffer::Map(VkDeviceSize &bindoffset, VkDeviceSize usedsize)
{
uint32_t offs = 0;
void *ret = Map(&offs, usedsize);
bindoffset = offs;
return ret;
}
void VulkanDebugManager::GPUBuffer::Unmap()
{
m_pDriver->vkUnmapMemory(device, mem);
}
struct VulkanBlobShaderCallbacks
{
bool Create(uint32_t size, byte *data, vector<uint32_t> **ret) const
{
RDCASSERT(ret);
vector<uint32_t> *blob = new vector<uint32_t>();
blob->resize(size / sizeof(uint32_t));
memcpy(&(*blob)[0], data, size);
*ret = blob;
return true;
}
void Destroy(vector<uint32_t> *blob) const { delete blob; }
uint32_t GetSize(vector<uint32_t> *blob) const
{
return (uint32_t)(blob->size() * sizeof(uint32_t));
}
byte *GetData(vector<uint32_t> *blob) const { return (byte *)&(*blob)[0]; }
} ShaderCacheCallbacks;
string VulkanDebugManager::GetSPIRVBlob(SPIRVShaderStage shadType,
const std::vector<std::string> &sources,
vector<uint32_t> **outBlob)
{
RDCASSERT(sources.size() > 0);
uint32_t hash = strhash(sources[0].c_str());
for(size_t i = 1; i < sources.size(); i++)
hash = strhash(sources[i].c_str(), hash);
char typestr[2] = {'a', 0};
typestr[0] += (char)shadType;
hash = strhash(typestr, hash);
if(m_ShaderCache.find(hash) != m_ShaderCache.end())
{
*outBlob = m_ShaderCache[hash];
return "";
}
vector<uint32_t> *spirv = new vector<uint32_t>();
string errors = CompileSPIRV(shadType, sources, *spirv);
if(!errors.empty())
{
string logerror = errors;
if(logerror.length() > 1024)
logerror = logerror.substr(0, 1024) + "...";
RDCWARN("Shader compile error:\n%s", logerror.c_str());
delete spirv;
*outBlob = NULL;
return errors;
}
*outBlob = spirv;
if(m_CacheShaders)
{
m_ShaderCache[hash] = spirv;
m_ShaderCacheDirty = true;
}
return errors;
}
VulkanDebugManager::VulkanDebugManager(WrappedVulkan *driver, VkDevice dev)
{
m_pDriver = driver;
m_State = m_pDriver->GetState();
driver->GetReplay()->PostDeviceInitCounters();
m_ResourceManager = m_pDriver->GetResourceManager();
//////////////////////////////////////////////////////////////////////////////////////////////////
// Zero initialise all of the members so that when deleting we can just destroy everything and let
// objects that weren't created just silently be skipped
m_DescriptorPool = VK_NULL_HANDLE;
m_LinearSampler = VK_NULL_HANDLE;
m_PointSampler = VK_NULL_HANDLE;
m_CheckerboardDescSetLayout = VK_NULL_HANDLE;
m_CheckerboardPipeLayout = VK_NULL_HANDLE;
m_CheckerboardDescSet = VK_NULL_HANDLE;
m_CheckerboardPipeline = VK_NULL_HANDLE;
m_CheckerboardMSAAPipeline = VK_NULL_HANDLE;
RDCEraseEl(m_CheckerboardUBO);
m_TexDisplayDescSetLayout = VK_NULL_HANDLE;
m_TexDisplayPipeLayout = VK_NULL_HANDLE;
RDCEraseEl(m_TexDisplayDescSet);
m_TexDisplayNextSet = 0;
m_TexDisplayPipeline = VK_NULL_HANDLE;
m_TexDisplayBlendPipeline = VK_NULL_HANDLE;
m_TexDisplayF32Pipeline = VK_NULL_HANDLE;
RDCEraseEl(m_TexDisplayUBO);
RDCEraseEl(m_TexDisplayDummyImages);
RDCEraseEl(m_TexDisplayDummyImageViews);
RDCEraseEl(m_TexDisplayDummyWrites);
RDCEraseEl(m_TexDisplayDummyInfos);
m_TexDisplayDummyMemory = VK_NULL_HANDLE;
m_CustomTexWidth = m_CustomTexHeight = 0;
m_CustomTexImg = VK_NULL_HANDLE;
m_CustomTexImgView = VK_NULL_HANDLE;
m_CustomTexMemSize = 0;
m_CustomTexMem = VK_NULL_HANDLE;
m_CustomTexFB = VK_NULL_HANDLE;
m_CustomTexRP = VK_NULL_HANDLE;
m_CustomTexPipeline = VK_NULL_HANDLE;
m_PickPixelImageMem = VK_NULL_HANDLE;
m_PickPixelImage = VK_NULL_HANDLE;
m_PickPixelImageView = VK_NULL_HANDLE;
m_PickPixelFB = VK_NULL_HANDLE;
m_PickPixelRP = VK_NULL_HANDLE;
m_TextDescSetLayout = VK_NULL_HANDLE;
m_TextPipeLayout = VK_NULL_HANDLE;
m_TextDescSet = VK_NULL_HANDLE;
m_TextPipeline = VK_NULL_HANDLE;
RDCEraseEl(m_TextGeneralUBO);
RDCEraseEl(m_TextGlyphUBO);
RDCEraseEl(m_TextStringUBO);
m_TextAtlas = VK_NULL_HANDLE;
m_TextAtlasMem = VK_NULL_HANDLE;
m_TextAtlasView = VK_NULL_HANDLE;
m_OverlayImageMem = VK_NULL_HANDLE;
m_OverlayImage = VK_NULL_HANDLE;
m_OverlayImageView = VK_NULL_HANDLE;
m_OverlayNoDepthFB = VK_NULL_HANDLE;
m_OverlayNoDepthRP = VK_NULL_HANDLE;
RDCEraseEl(m_OverlayDim);
m_OverlayMemSize = 0;
m_QuadDescSetLayout = VK_NULL_HANDLE;
m_QuadResolvePipeLayout = VK_NULL_HANDLE;
m_QuadDescSet = VK_NULL_HANDLE;
RDCEraseEl(m_QuadResolvePipeline);
m_QuadSPIRV = NULL;
m_MeshDescSetLayout = VK_NULL_HANDLE;
m_MeshPipeLayout = VK_NULL_HANDLE;
m_MeshDescSet = VK_NULL_HANDLE;
RDCEraseEl(m_MeshModules);
m_HistogramDescSetLayout = VK_NULL_HANDLE;
m_HistogramPipeLayout = VK_NULL_HANDLE;
RDCEraseEl(m_HistogramDescSet);
RDCEraseEl(m_MinMaxResultPipe);
RDCEraseEl(m_MinMaxTilePipe);
RDCEraseEl(m_HistogramPipe);
m_OutlineDescSetLayout = VK_NULL_HANDLE;
m_OutlinePipeLayout = VK_NULL_HANDLE;
m_OutlineDescSet = VK_NULL_HANDLE;
RDCEraseEl(m_OutlinePipeline);
m_MeshFetchDescSetLayout = VK_NULL_HANDLE;
m_MeshFetchDescSet = VK_NULL_HANDLE;
m_MeshPickDescSetLayout = VK_NULL_HANDLE;
m_MeshPickDescSet = VK_NULL_HANDLE;
m_MeshPickLayout = VK_NULL_HANDLE;
m_MeshPickPipeline = VK_NULL_HANDLE;
m_FontCharSize = 1.0f;
m_FontCharAspect = 1.0f;
m_FixedColSPIRV = NULL;
m_Device = dev;
//////////////////////////////////////////////////////////////////////////////////////////////////
// Do some work that's needed both during capture and during replay
// Load shader cache, if present
bool success = LoadShaderCache("vkshaders.cache", m_ShaderCacheMagic, m_ShaderCacheVersion,
m_ShaderCache, ShaderCacheCallbacks);
// if we failed to load from the cache
m_ShaderCacheDirty = !success;
VkResult vkr = VK_SUCCESS;
// create linear sampler
VkSamplerCreateInfo sampInfo = {
VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
NULL,
0,
VK_FILTER_LINEAR,
VK_FILTER_LINEAR,
VK_SAMPLER_MIPMAP_MODE_NEAREST,
VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
0.0f, // lod bias
false, // enable aniso
1.0f, // max aniso
false,
VK_COMPARE_OP_NEVER,
0.0f,
128.0f, // min/max lod
VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE,
false, // unnormalized
};
vkr = m_pDriver->vkCreateSampler(dev, &sampInfo, NULL, &m_LinearSampler);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
VkDescriptorPoolSize captureDescPoolTypes[] = {
{
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2,
},
{
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1,
},
{
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1,
},
};
VkDescriptorPoolSize replayDescPoolTypes[] = {
{
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 128,
},
{
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 128,
},
{
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 320,
},
{
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 32,
},
{
VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 32,
},
};
VkDescriptorPoolCreateInfo descpoolInfo = {
VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
NULL,
0,
9 + ARRAY_COUNT(m_TexDisplayDescSet),
ARRAY_COUNT(replayDescPoolTypes),
&replayDescPoolTypes[0],
};
// during capture we only need one text descriptor set, so rather than
// trying to wait and steal descriptors from a user-side pool, we just
// create our own very small pool.
if(m_State >= WRITING)
{
descpoolInfo.maxSets = 1;
descpoolInfo.poolSizeCount = ARRAY_COUNT(captureDescPoolTypes);
descpoolInfo.pPoolSizes = &captureDescPoolTypes[0];
}
// create descriptor pool
vkr = m_pDriver->vkCreateDescriptorPool(dev, &descpoolInfo, NULL, &m_DescriptorPool);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
// declare some common creation info structs
VkPipelineLayoutCreateInfo pipeLayoutInfo = {
VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
NULL,
0,
1,
NULL,
0,
NULL, // push constant ranges
};
VkDescriptorSetAllocateInfo descSetAllocInfo = {VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
NULL, m_DescriptorPool, 1, NULL};
// compatible render passes for creating pipelines.
// Only one of these is needed during capture for the pipeline create, but
// they are short-lived so just create all of them and share creation code
VkRenderPass RGBA32RP = VK_NULL_HANDLE;
VkRenderPass RGBA8RP = VK_NULL_HANDLE;
VkRenderPass RGBA16RP = VK_NULL_HANDLE;
VkRenderPass RGBA8MSRP = VK_NULL_HANDLE;
VkRenderPass RGBA16MSRP[8] = {0};
RDCCOMPILE_ASSERT(ARRAY_COUNT(RGBA16MSRP) == ARRAY_COUNT(m_OutlinePipeline),
"Arrays are mismatched in size!");
RDCCOMPILE_ASSERT(ARRAY_COUNT(RGBA16MSRP) == ARRAY_COUNT(m_QuadResolvePipeline),
"Arrays are mismatched in size!");
{
VkAttachmentDescription attDesc = {0,
VK_FORMAT_R8G8B8A8_UNORM,
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
};
m_pDriver->vkCreateRenderPass(dev, &rpinfo, NULL, &RGBA8RP);
attDesc.format = VK_FORMAT_R32G32B32A32_SFLOAT;
m_pDriver->vkCreateRenderPass(dev, &rpinfo, NULL, &RGBA32RP);
attDesc.format = VK_FORMAT_R16G16B16A16_SFLOAT;
m_pDriver->vkCreateRenderPass(dev, &rpinfo, NULL, &RGBA16RP);
attDesc.samples = VULKAN_MESH_VIEW_SAMPLES;
attDesc.format = VK_FORMAT_R8G8B8A8_SRGB;
m_pDriver->vkCreateRenderPass(dev, &rpinfo, NULL, &RGBA8MSRP);
attDesc.format = VK_FORMAT_R16G16B16A16_SFLOAT;
uint32_t samplesHandled = 0;
// create a 16F multisampled renderpass for each possible multisample size
for(size_t i = 0; i < ARRAY_COUNT(RGBA16MSRP); i++)
{
attDesc.samples = VkSampleCountFlagBits(1 << i);
if(m_pDriver->GetDeviceProps().limits.framebufferColorSampleCounts & (uint32_t)attDesc.samples)
{
m_pDriver->vkCreateRenderPass(dev, &rpinfo, NULL, &RGBA16MSRP[i]);
samplesHandled |= (uint32_t)attDesc.samples;
}
}
RDCASSERTEQUAL((uint32_t)m_pDriver->GetDeviceProps().limits.framebufferColorSampleCounts,
samplesHandled);
}
// declare the pipeline creation info and all of its sub-structures
// these are modified as appropriate for each pipeline we create
VkPipelineShaderStageCreateInfo stages[2] = {
{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, NULL, 0, VK_SHADER_STAGE_VERTEX_BIT,
VK_NULL_HANDLE, "main", NULL},
{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, NULL, 0, VK_SHADER_STAGE_FRAGMENT_BIT,
VK_NULL_HANDLE, "main", NULL},
};
VkPipelineVertexInputStateCreateInfo vi = {
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
NULL,
0,
0,
NULL, // vertex bindings
0,
NULL, // vertex attributes
};
VkPipelineInputAssemblyStateCreateInfo ia = {
VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
NULL,
0,
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP,
false,
};
VkRect2D scissor = {{0, 0}, {4096, 4096}};
VkPipelineViewportStateCreateInfo vp = {
VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, NULL, 0, 1, NULL, 1, &scissor};
VkPipelineRasterizationStateCreateInfo rs = {
VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
NULL,
0,
true,
false,
VK_POLYGON_MODE_FILL,
VK_CULL_MODE_NONE,
VK_FRONT_FACE_CLOCKWISE,
false,
0.0f,
0.0f,
0.0f,
1.0f,
};
VkPipelineMultisampleStateCreateInfo msaa = {
VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
NULL,
0,
VK_SAMPLE_COUNT_1_BIT,
false,
0.0f,
NULL,
false,
false,
};
VkPipelineDepthStencilStateCreateInfo ds = {
VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
NULL,
0,
false,
false,
VK_COMPARE_OP_ALWAYS,
false,
false,
{VK_STENCIL_OP_KEEP, VK_STENCIL_OP_KEEP, VK_STENCIL_OP_KEEP, VK_COMPARE_OP_ALWAYS, 0, 0, 0},
{VK_STENCIL_OP_KEEP, VK_STENCIL_OP_KEEP, VK_STENCIL_OP_KEEP, VK_COMPARE_OP_ALWAYS, 0, 0, 0},
0.0f,
1.0f,
};
VkPipelineColorBlendAttachmentState attState = {
false,
VK_BLEND_FACTOR_ONE,
VK_BLEND_FACTOR_ZERO,
VK_BLEND_OP_ADD,
VK_BLEND_FACTOR_ONE,
VK_BLEND_FACTOR_ZERO,
VK_BLEND_OP_ADD,
0xf,
};
VkPipelineColorBlendStateCreateInfo cb = {
VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
NULL,
0,
false,
VK_LOGIC_OP_NO_OP,
1,
&attState,
{1.0f, 1.0f, 1.0f, 1.0f}};
VkDynamicState dynstates[] = {VK_DYNAMIC_STATE_VIEWPORT};
VkPipelineDynamicStateCreateInfo dyn = {
VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
NULL,
0,
ARRAY_COUNT(dynstates),
dynstates,
};
VkGraphicsPipelineCreateInfo pipeInfo = {
VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
NULL,
0,
2,
stages,
&vi,
&ia,
NULL, // tess
&vp,
&rs,
&msaa,
&ds,
&cb,
&dyn,
VK_NULL_HANDLE,
RGBA8RP,
0, // sub pass
VK_NULL_HANDLE, // base pipeline handle
-1, // base pipeline index
};
// declare a few more misc things that are needed on both paths
VkDescriptorBufferInfo bufInfo[6];
RDCEraseEl(bufInfo);
vector<string> sources;
VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, NULL,
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT};
//////////////////////////////////////////////////////////////////////////////////////
// if we're writing, only create text-rendering related resources,
// then tidy up early and return
if(m_State >= WRITING)
{
{
VkDescriptorSetLayoutBinding layoutBinding[] = {
{
0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1, VK_SHADER_STAGE_ALL, NULL,
},
{
1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_ALL, NULL,
},
{
2, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1, VK_SHADER_STAGE_ALL, NULL,
},
{
3, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_ALL, NULL,
}};
VkDescriptorSetLayoutCreateInfo descsetLayoutInfo = {
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
NULL,
0,
ARRAY_COUNT(layoutBinding),
&layoutBinding[0],
};
vkr = m_pDriver->vkCreateDescriptorSetLayout(dev, &descsetLayoutInfo, NULL,
&m_TextDescSetLayout);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
}
pipeLayoutInfo.pSetLayouts = &m_TextDescSetLayout;
vkr = m_pDriver->vkCreatePipelineLayout(dev, &pipeLayoutInfo, NULL, &m_TextPipeLayout);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
descSetAllocInfo.pSetLayouts = &m_TextDescSetLayout;
vkr = m_pDriver->vkAllocateDescriptorSets(dev, &descSetAllocInfo, &m_TextDescSet);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
m_TextGeneralUBO.Create(
driver, dev, 128, 100,
0); // make the ring conservatively large to handle many lines of text * several frames
RDCCOMPILE_ASSERT(sizeof(FontUBOData) <= 128, "font uniforms size");
m_TextStringUBO.Create(driver, dev, 4096, 10, 0); // we only use a subset of the
// [MAX_SINGLE_LINE_LENGTH] array needed
// for each line, so this ring can be
// smaller
RDCCOMPILE_ASSERT(sizeof(StringUBOData) <= 4096, "font uniforms size");
attState.blendEnable = true;
attState.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
attState.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
for(size_t i = 0; i < 2; i++)
{
sources.resize(4);
sources[0] = "#version 430 core\n";
sources[1] = GetEmbeddedResource(spv_debuguniforms_h);
sources[2] = i == 0 ? GetEmbeddedResource(spv_text_vert) : GetEmbeddedResource(spv_text_frag);
vector<uint32_t> *spirv;
string err = GetSPIRVBlob(i == 0 ? eSPIRVVertex : eSPIRVFragment, sources, &spirv);
RDCASSERT(err.empty() && spirv);
VkShaderModuleCreateInfo modinfo = {
VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
NULL,
0,
spirv->size() * sizeof(uint32_t),
&(*spirv)[0],
};
vkr = m_pDriver->vkCreateShaderModule(dev, &modinfo, NULL, &stages[i].module);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
}
pipeInfo.layout = m_TextPipeLayout;
vkr = m_pDriver->vkCreateGraphicsPipelines(dev, VK_NULL_HANDLE, 1, &pipeInfo, NULL,
&m_TextPipeline);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
m_pDriver->vkDestroyShaderModule(dev, stages[0].module, NULL);
m_pDriver->vkDestroyShaderModule(dev, stages[1].module, NULL);
// create the actual font texture data and glyph data, for upload
{
const uint32_t width = FONT_TEX_WIDTH, height = FONT_TEX_HEIGHT;
VkImageCreateInfo imInfo = {
VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
NULL,
0,
VK_IMAGE_TYPE_2D,
VK_FORMAT_R8_UNORM,
{width, height, 1},
1,
1,
VK_SAMPLE_COUNT_1_BIT,
VK_IMAGE_TILING_OPTIMAL,
VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
VK_SHARING_MODE_EXCLUSIVE,
0,
NULL,
VK_IMAGE_LAYOUT_UNDEFINED,
};
string font = GetEmbeddedResource(sourcecodepro_ttf);
byte *ttfdata = (byte *)font.c_str();
const int firstChar = FONT_FIRST_CHAR;
const int lastChar = FONT_LAST_CHAR;
const int numChars = lastChar - firstChar + 1;
RDCCOMPILE_ASSERT(FONT_FIRST_CHAR == int(' '), "Font defines are messed up");
byte *buf = new byte[width * height];
const float pixelHeight = 20.0f;
stbtt_bakedchar chardata[numChars];
stbtt_BakeFontBitmap(ttfdata, 0, pixelHeight, buf, width, height, firstChar, numChars,
chardata);
m_FontCharSize = pixelHeight;
m_FontCharAspect = chardata->xadvance / pixelHeight;
stbtt_fontinfo f = {0};
stbtt_InitFont(&f, ttfdata, 0);
int ascent = 0;
stbtt_GetFontVMetrics(&f, &ascent, NULL, NULL);
float maxheight = float(ascent) * stbtt_ScaleForPixelHeight(&f, pixelHeight);
// create and fill image
{
vkr = m_pDriver->vkCreateImage(dev, &imInfo, NULL, &m_TextAtlas);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
VkMemoryRequirements mrq = {0};
m_pDriver->vkGetImageMemoryRequirements(dev, m_TextAtlas, &mrq);
VkImageSubresource subr = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0};
VkSubresourceLayout layout = {0};
m_pDriver->vkGetImageSubresourceLayout(dev, m_TextAtlas, &subr, &layout);
// allocate readback memory
VkMemoryAllocateInfo allocInfo = {
VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, NULL, mrq.size,
driver->GetGPULocalMemoryIndex(mrq.memoryTypeBits),
};
vkr = m_pDriver->vkAllocateMemory(dev, &allocInfo, NULL, &m_TextAtlasMem);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
vkr = m_pDriver->vkBindImageMemory(dev, m_TextAtlas, m_TextAtlasMem, 0);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
VkImageViewCreateInfo viewInfo = {
VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
NULL,
0,
m_TextAtlas,
VK_IMAGE_VIEW_TYPE_2D,
imInfo.format,
{VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_ZERO, VK_COMPONENT_SWIZZLE_ZERO,
VK_COMPONENT_SWIZZLE_ONE},
{
VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1,
},
};
vkr = m_pDriver->vkCreateImageView(dev, &viewInfo, NULL, &m_TextAtlasView);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
// create temporary memory and buffer to upload atlas
m_TextAtlasUpload.Create(driver, dev, 32768, 1,
0); // doesn't need to be ring'd, as it's static
RDCCOMPILE_ASSERT(width * height <= 32768, "font uniform size");
byte *pData = (byte *)m_TextAtlasUpload.Map();
RDCASSERT(pData);
memcpy(pData, buf, width * height);
m_TextAtlasUpload.Unmap();
}
m_TextGlyphUBO.Create(driver, dev, 4096, 1,
0); // doesn't need to be ring'd, as it's static
RDCCOMPILE_ASSERT(sizeof(Vec4f) * 2 * (numChars + 1) < 4096, "font uniform size");
FontGlyphData *glyphData = (FontGlyphData *)m_TextGlyphUBO.Map();
for(int i = 0; i < numChars; i++)
{
stbtt_bakedchar *b = chardata + i;
float x = b->xoff;
float y = b->yoff + maxheight;
glyphData[i].posdata =
Vec4f(x / b->xadvance, y / pixelHeight, b->xadvance / float(b->x1 - b->x0),
pixelHeight / float(b->y1 - b->y0));
glyphData[i].uvdata = Vec4f(b->x0, b->y0, b->x1, b->y1);
}
m_TextGlyphUBO.Unmap();
}
// perform GPU copy from m_TextAtlasUpload to m_TextAtlas with appropriate barriers
{
VkCommandBuffer textAtlasUploadCmd = driver->GetNextCmd();
vkr = ObjDisp(textAtlasUploadCmd)->BeginCommandBuffer(Unwrap(textAtlasUploadCmd), &beginInfo);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
// need to update image layout into valid state first
VkImageMemoryBarrier copysrcbarrier = {
VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
NULL,
0,
VK_ACCESS_HOST_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
0,
0, // MULTIDEVICE - need to actually pick the right queue family here maybe?
Unwrap(m_TextAtlas),
{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
DoPipelineBarrier(textAtlasUploadCmd, 1, &copysrcbarrier);
VkBufferMemoryBarrier uploadbarrier = {
VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
NULL,
VK_ACCESS_HOST_WRITE_BIT,
VK_ACCESS_TRANSFER_READ_BIT,
VK_QUEUE_FAMILY_IGNORED,
VK_QUEUE_FAMILY_IGNORED,
Unwrap(m_TextAtlasUpload.buf),
0,
m_TextAtlasUpload.totalsize,
};
// ensure host writes finish before copy
DoPipelineBarrier(textAtlasUploadCmd, 1, &uploadbarrier);
VkBufferImageCopy bufRegion = {
0,
0,
0,
{VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1},
{
0, 0, 0,
},
{FONT_TEX_WIDTH, FONT_TEX_HEIGHT, 1},
};
// copy to image
ObjDisp(textAtlasUploadCmd)
->CmdCopyBufferToImage(Unwrap(textAtlasUploadCmd), Unwrap(m_TextAtlasUpload.buf),
Unwrap(m_TextAtlas), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1,
&bufRegion);
VkImageMemoryBarrier copydonebarrier = {
VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
NULL,
copysrcbarrier.dstAccessMask,
VK_ACCESS_SHADER_READ_BIT,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
0,
0, // MULTIDEVICE - need to actually pick the right queue family here maybe?
Unwrap(m_TextAtlas),
{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
// ensure atlas is filled before reading in shader
DoPipelineBarrier(textAtlasUploadCmd, 1, &copydonebarrier);
ObjDisp(textAtlasUploadCmd)->EndCommandBuffer(Unwrap(textAtlasUploadCmd));
}
m_TextGeneralUBO.FillDescriptor(bufInfo[0]);
m_TextGlyphUBO.FillDescriptor(bufInfo[1]);
m_TextStringUBO.FillDescriptor(bufInfo[2]);
VkDescriptorImageInfo atlasImInfo;
atlasImInfo.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
atlasImInfo.imageView = Unwrap(m_TextAtlasView);
atlasImInfo.sampler = Unwrap(m_LinearSampler);
VkWriteDescriptorSet textSetWrites[] = {
{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, Unwrap(m_TextDescSet), 0, 0, 1,
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, NULL, &bufInfo[0], NULL},
{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, Unwrap(m_TextDescSet), 1, 0, 1,
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, NULL, &bufInfo[1], NULL},
{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, Unwrap(m_TextDescSet), 2, 0, 1,
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, NULL, &bufInfo[2], NULL},
{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, Unwrap(m_TextDescSet), 3, 0, 1,
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, &atlasImInfo, NULL, NULL},
};
ObjDisp(dev)->UpdateDescriptorSets(Unwrap(dev), ARRAY_COUNT(textSetWrites), textSetWrites, 0,
NULL);
m_pDriver->vkDestroyRenderPass(dev, RGBA16RP, NULL);
m_pDriver->vkDestroyRenderPass(dev, RGBA32RP, NULL);
m_pDriver->vkDestroyRenderPass(dev, RGBA8RP, NULL);
m_pDriver->vkDestroyRenderPass(dev, RGBA8MSRP, NULL);
return;
}
//////////////////////////////////////////////////////////////////////////////////////
// everything created below this point is only needed during replay, and will be NULL
// while in the captured application
// create point sampler
sampInfo.minFilter = VK_FILTER_NEAREST;
sampInfo.magFilter = VK_FILTER_NEAREST;
vkr = m_pDriver->vkCreateSampler(dev, &sampInfo, NULL, &m_PointSampler);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
{
VkDescriptorSetLayoutBinding layoutBinding[] = {{
0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1, VK_SHADER_STAGE_ALL, NULL,
}};
VkDescriptorSetLayoutCreateInfo descsetLayoutInfo = {
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
NULL,
0,
ARRAY_COUNT(layoutBinding),
&layoutBinding[0],
};
vkr = m_pDriver->vkCreateDescriptorSetLayout(dev, &descsetLayoutInfo, NULL,
&m_CheckerboardDescSetLayout);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
// identical layout
vkr = m_pDriver->vkCreateDescriptorSetLayout(dev, &descsetLayoutInfo, NULL, &m_MeshDescSetLayout);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
// identical layout
vkr = m_pDriver->vkCreateDescriptorSetLayout(dev, &descsetLayoutInfo, NULL,
&m_OutlineDescSetLayout);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
}
{
VkDescriptorSetLayoutBinding layoutBinding[] = {{
0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_ALL, NULL,
}};
VkDescriptorSetLayoutCreateInfo descsetLayoutInfo = {
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
NULL,
0,
ARRAY_COUNT(layoutBinding),
&layoutBinding[0],
};
vkr = m_pDriver->vkCreateDescriptorSetLayout(dev, &descsetLayoutInfo, NULL,
&m_MeshFetchDescSetLayout);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
}
{
VkDescriptorSetLayoutBinding layoutBinding[] = {
{
0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_ALL, NULL,
},
{
1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_ALL, NULL,
},
{
2, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_ALL, NULL,
},
{
3, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_ALL, NULL,
},
};
VkDescriptorSetLayoutCreateInfo descsetLayoutInfo = {
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
NULL,
0,
ARRAY_COUNT(layoutBinding),
&layoutBinding[0],
};
vkr = m_pDriver->vkCreateDescriptorSetLayout(dev, &descsetLayoutInfo, NULL,
&m_MeshPickDescSetLayout);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
}
{
VkDescriptorSetLayoutBinding layoutBinding[] = {
{
0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1, VK_SHADER_STAGE_ALL, NULL,
},
{
6, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_ALL, NULL,
},
{
7, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_ALL, NULL,
},
{
8, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_ALL, NULL,
},
{
9, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_ALL, NULL,
},
{
10, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_ALL, NULL,
},
{
11, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_ALL, NULL,
},
{
12, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_ALL, NULL,
},
{
13, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_ALL, NULL,
},
{
14, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_ALL, NULL,
},
{
15, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_ALL, NULL,
},
{
16, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_ALL, NULL,
},
{
17, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_ALL, NULL,
},
{
18, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_ALL, NULL,
},
{
19, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_ALL, NULL,
},
{
20, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_ALL, NULL,
},
};
VkDescriptorSetLayoutCreateInfo descsetLayoutInfo = {
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
NULL,
0,
ARRAY_COUNT(layoutBinding),
&layoutBinding[0],
};
vkr = m_pDriver->vkCreateDescriptorSetLayout(dev, &descsetLayoutInfo, NULL,
&m_TexDisplayDescSetLayout);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
}
{
VkDescriptorSetLayoutBinding layoutBinding[] = {
{
0, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_ALL, NULL,
},
{
1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_ALL, NULL,
},
};
VkDescriptorSetLayoutCreateInfo descsetLayoutInfo = {
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
NULL,
0,
ARRAY_COUNT(layoutBinding),
&layoutBinding[0],
};
vkr = m_pDriver->vkCreateDescriptorSetLayout(dev, &descsetLayoutInfo, NULL, &m_QuadDescSetLayout);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
}
{
VkDescriptorSetLayoutBinding layoutBinding[] = {
{
0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_ALL, NULL,
},
{
1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_ALL, NULL,
},
{
2, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_ALL, NULL,
},
{
6, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_ALL, NULL,
},
{
7, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_ALL, NULL,
},
{
8, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_ALL, NULL,
},
{
9, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_ALL, NULL,
},
{
11, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_ALL, NULL,
},
{
12, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_ALL, NULL,
},
{
13, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_ALL, NULL,
},
{
14, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_ALL, NULL,
},
{
16, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_ALL, NULL,
},
{
17, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_ALL, NULL,
},
{
18, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_ALL, NULL,
},
{
19, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_ALL, NULL,
},
};
VkDescriptorSetLayoutCreateInfo descsetLayoutInfo = {
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
NULL,
0,
ARRAY_COUNT(layoutBinding),
&layoutBinding[0],
};
vkr = m_pDriver->vkCreateDescriptorSetLayout(dev, &descsetLayoutInfo, NULL,
&m_HistogramDescSetLayout);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
}
pipeLayoutInfo.pSetLayouts = &m_TexDisplayDescSetLayout;
vkr = m_pDriver->vkCreatePipelineLayout(dev, &pipeLayoutInfo, NULL, &m_TexDisplayPipeLayout);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
pipeLayoutInfo.pSetLayouts = &m_CheckerboardDescSetLayout;
vkr = m_pDriver->vkCreatePipelineLayout(dev, &pipeLayoutInfo, NULL, &m_CheckerboardPipeLayout);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
pipeLayoutInfo.pSetLayouts = &m_QuadDescSetLayout;
vkr = m_pDriver->vkCreatePipelineLayout(dev, &pipeLayoutInfo, NULL, &m_QuadResolvePipeLayout);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
pipeLayoutInfo.pSetLayouts = &m_OutlineDescSetLayout;
vkr = m_pDriver->vkCreatePipelineLayout(dev, &pipeLayoutInfo, NULL, &m_OutlinePipeLayout);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
pipeLayoutInfo.pSetLayouts = &m_MeshDescSetLayout;
vkr = m_pDriver->vkCreatePipelineLayout(dev, &pipeLayoutInfo, NULL, &m_MeshPipeLayout);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
pipeLayoutInfo.pSetLayouts = &m_HistogramDescSetLayout;
vkr = m_pDriver->vkCreatePipelineLayout(dev, &pipeLayoutInfo, NULL, &m_HistogramPipeLayout);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
pipeLayoutInfo.pSetLayouts = &m_MeshPickDescSetLayout;
vkr = m_pDriver->vkCreatePipelineLayout(dev, &pipeLayoutInfo, NULL, &m_MeshPickLayout);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
descSetAllocInfo.pSetLayouts = &m_CheckerboardDescSetLayout;
vkr = m_pDriver->vkAllocateDescriptorSets(dev, &descSetAllocInfo, &m_CheckerboardDescSet);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
descSetAllocInfo.pSetLayouts = &m_TexDisplayDescSetLayout;
for(size_t i = 0; i < ARRAY_COUNT(m_TexDisplayDescSet); i++)
{
vkr = m_pDriver->vkAllocateDescriptorSets(dev, &descSetAllocInfo, &m_TexDisplayDescSet[i]);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
}
descSetAllocInfo.pSetLayouts = &m_QuadDescSetLayout;
vkr = m_pDriver->vkAllocateDescriptorSets(dev, &descSetAllocInfo, &m_QuadDescSet);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
descSetAllocInfo.pSetLayouts = &m_OutlineDescSetLayout;
vkr = m_pDriver->vkAllocateDescriptorSets(dev, &descSetAllocInfo, &m_OutlineDescSet);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
descSetAllocInfo.pSetLayouts = &m_MeshDescSetLayout;
vkr = m_pDriver->vkAllocateDescriptorSets(dev, &descSetAllocInfo, &m_MeshDescSet);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
descSetAllocInfo.pSetLayouts = &m_HistogramDescSetLayout;
vkr = m_pDriver->vkAllocateDescriptorSets(dev, &descSetAllocInfo, &m_HistogramDescSet[0]);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
vkr = m_pDriver->vkAllocateDescriptorSets(dev, &descSetAllocInfo, &m_HistogramDescSet[1]);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
descSetAllocInfo.pSetLayouts = &m_MeshFetchDescSetLayout;
vkr = m_pDriver->vkAllocateDescriptorSets(dev, &descSetAllocInfo, &m_MeshFetchDescSet);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
descSetAllocInfo.pSetLayouts = &m_MeshPickDescSetLayout;
vkr = m_pDriver->vkAllocateDescriptorSets(dev, &descSetAllocInfo, &m_MeshPickDescSet);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
// sizes are always 0 so that these buffers are created on demand
m_MeshPickIBSize = 0;
m_MeshPickVBSize = 0;
m_MeshPickUBO.Create(driver, dev, 128, 1, 0);
RDCCOMPILE_ASSERT(sizeof(MeshPickUBOData) <= 128, "mesh pick UBO size");
const size_t meshPickResultSize = maxMeshPicks * sizeof(FloatVector) + sizeof(uint32_t);
m_MeshPickResult.Create(driver, dev, meshPickResultSize, 1,
GPUBuffer::eGPUBufferGPULocal | GPUBuffer::eGPUBufferSSBO);
m_MeshPickResultReadback.Create(driver, dev, meshPickResultSize, 1, GPUBuffer::eGPUBufferReadback);
m_ReadbackWindow.Create(driver, dev, STAGE_BUFFER_BYTE_SIZE, 1, GPUBuffer::eGPUBufferReadback);
m_OutlineUBO.Create(driver, dev, 128, 10, 0);
RDCCOMPILE_ASSERT(sizeof(OutlineUBOData) <= 128, "outline UBO size");
m_CheckerboardUBO.Create(driver, dev, 128, 10, 0);
m_TexDisplayUBO.Create(driver, dev, 128, 10, 0);
RDCCOMPILE_ASSERT(sizeof(TexDisplayUBOData) <= 128, "tex display size");
string shaderSources[] = {
GetEmbeddedResource(spv_blit_vert), GetEmbeddedResource(spv_checkerboard_frag),
GetEmbeddedResource(spv_texdisplay_frag), GetEmbeddedResource(spv_mesh_vert),
GetEmbeddedResource(spv_mesh_geom), GetEmbeddedResource(spv_mesh_frag),
GetEmbeddedResource(spv_minmaxtile_comp), GetEmbeddedResource(spv_minmaxresult_comp),
GetEmbeddedResource(spv_histogram_comp), GetEmbeddedResource(spv_outline_frag),
GetEmbeddedResource(spv_quadresolve_frag), GetEmbeddedResource(spv_quadwrite_frag),
GetEmbeddedResource(spv_mesh_comp),
};
SPIRVShaderStage shaderStages[] = {
eSPIRVVertex, eSPIRVFragment, eSPIRVFragment, eSPIRVVertex, eSPIRVGeometry,
eSPIRVFragment, eSPIRVCompute, eSPIRVCompute, eSPIRVCompute, eSPIRVFragment,
eSPIRVFragment, eSPIRVFragment, eSPIRVCompute,
};
enum shaderIdx
{
BLITVS,
CHECKERBOARDFS,
TEXDISPLAYFS,
MESHVS,
MESHGS,
MESHFS,
MINMAXTILECS,
MINMAXRESULTCS,
HISTOGRAMCS,
OUTLINEFS,
QUADRESOLVEFS,
QUADWRITEFS,
MESHCS,
NUM_SHADERS,
};
vector<uint32_t> *shaderSPIRV[NUM_SHADERS];
VkShaderModule module[NUM_SHADERS];
RDCCOMPILE_ASSERT(ARRAY_COUNT(shaderSources) == ARRAY_COUNT(shaderStages), "Mismatched arrays!");
RDCCOMPILE_ASSERT(ARRAY_COUNT(shaderSources) == NUM_SHADERS, "Mismatched arrays!");
m_CacheShaders = true;
{
sources.push_back(GetEmbeddedResource(spv_fixedcol_frag));
string err = GetSPIRVBlob(eSPIRVFragment, sources, &m_FixedColSPIRV);
RDCASSERT(err.empty() && m_FixedColSPIRV);
}
sources.resize(4);
sources[1] = GetEmbeddedResource(spv_debuguniforms_h);
// the newest AMD driver (at time of committing) has texelFetch fixed,
// but it came out recently so I want a short transition period with the
// workaround in place while people update. So we just check if we're
// on AMD and look at the modified date of amdvlk32/64.dll. Cheeky!
bool texelFetchBrokenAMDDriver = true;
if(m_pDriver->IsAMD())
{
#if defined(RENDERDOC_PLATFORM_WIN32)
#if defined(RDC64BIT)
const char *moduleName = "amdvlk64.dll";
#else
const char *moduleName = "amdvlk32.dll";
#endif
// can't check version number reported as it's fixed at 0.9.0, so
// we go by module modified timestamp
HMODULE mod = GetModuleHandleA(moduleName);
if(mod)
{
wchar_t curFile[512] = {};
GetModuleFileNameW(mod, curFile, 512);
string vlkPath = StringFormat::Wide2UTF8(wstring(curFile));
uint64_t timestamp = FileIO::GetModifiedTimestamp(vlkPath);
// Any driver with modified date after this time (2016-04-17)
// should be fine.
const uint64_t referenceTimestamp = 1460880000;
if(timestamp > referenceTimestamp)
texelFetchBrokenAMDDriver = false;
else
RDCWARN(
"Detected an older AMD driver, enabling workaround - try updating to the latest "
"version");
}
else
{
RDCWARN("AMD device detected but can't find %s loaded", moduleName);
}
#endif
}
for(size_t i = 0; i < ARRAY_COUNT(module); i++)
{
// these modules will be compiled later
if(i == HISTOGRAMCS || i == MINMAXTILECS || i == MINMAXRESULTCS)
continue;
sources[0] = "#version 430 core\n";
if(m_pDriver->IsAMD() && texelFetchBrokenAMDDriver)
sources[0] += "#define NO_TEXEL_FETCH\n";
sources[2] = "";
sources[3] = shaderSources[i];
if(sources[3].find("#include \"texsample.h\"") != string::npos)
sources[2] = GetEmbeddedResource(spv_texsample_h);
// hoist up any #extension directives
size_t extsearch = 0;
do
{
extsearch = sources[3].find("#extension", extsearch);
if(extsearch == string::npos)
break;
size_t begin = extsearch;
extsearch = sources[3].find('\n', extsearch);
sources[0] += sources[3].substr(begin, extsearch - begin + 1);
} while(extsearch != string::npos);
string err = GetSPIRVBlob(shaderStages[i], sources, &shaderSPIRV[i]);
RDCASSERT(err.empty() && shaderSPIRV[i]);
VkShaderModuleCreateInfo modinfo = {
VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
NULL,
0,
shaderSPIRV[i]->size() * sizeof(uint32_t),
&(*shaderSPIRV[i])[0],
};
if(i == QUADWRITEFS)
{
m_QuadSPIRV = shaderSPIRV[i];
module[i] = VK_NULL_HANDLE;
continue;
}
vkr = m_pDriver->vkCreateShaderModule(dev, &modinfo, NULL, &module[i]);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
}
m_CacheShaders = false;
attState.blendEnable = false;
pipeInfo.layout = m_CheckerboardPipeLayout;
pipeInfo.renderPass = RGBA8RP;
stages[0].module = module[BLITVS];
stages[1].module = module[CHECKERBOARDFS];
vkr = m_pDriver->vkCreateGraphicsPipelines(dev, VK_NULL_HANDLE, 1, &pipeInfo, NULL,
&m_CheckerboardPipeline);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
msaa.rasterizationSamples = VULKAN_MESH_VIEW_SAMPLES;
pipeInfo.renderPass = RGBA8MSRP;
vkr = m_pDriver->vkCreateGraphicsPipelines(dev, VK_NULL_HANDLE, 1, &pipeInfo, NULL,
&m_CheckerboardMSAAPipeline);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
msaa.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
pipeInfo.renderPass = RGBA8RP;
stages[0].module = module[BLITVS];
stages[1].module = module[TEXDISPLAYFS];
pipeInfo.layout = m_TexDisplayPipeLayout;
vkr = m_pDriver->vkCreateGraphicsPipelines(dev, VK_NULL_HANDLE, 1, &pipeInfo, NULL,
&m_TexDisplayPipeline);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
pipeInfo.renderPass = RGBA32RP;
vkr = m_pDriver->vkCreateGraphicsPipelines(dev, VK_NULL_HANDLE, 1, &pipeInfo, NULL,
&m_TexDisplayF32Pipeline);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
pipeInfo.renderPass = RGBA8RP;
attState.blendEnable = true;
attState.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
attState.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
vkr = m_pDriver->vkCreateGraphicsPipelines(dev, VK_NULL_HANDLE, 1, &pipeInfo, NULL,
&m_TexDisplayBlendPipeline);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
stages[0].module = module[BLITVS];
stages[1].module = module[OUTLINEFS];
pipeInfo.layout = m_OutlinePipeLayout;
attState.srcAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
attState.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
for(size_t i = 0; i < ARRAY_COUNT(m_OutlinePipeline); i++)
{
if(RGBA16MSRP[i] == VK_NULL_HANDLE)
continue;
// if we have a 16F renderpass for this sample count then create a pipeline
pipeInfo.renderPass = RGBA16MSRP[i];
msaa.rasterizationSamples = VkSampleCountFlagBits(1 << i);
vkr = m_pDriver->vkCreateGraphicsPipelines(dev, VK_NULL_HANDLE, 1, &pipeInfo, NULL,
&m_OutlinePipeline[i]);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
}
attState.blendEnable = false;
stages[0].module = module[BLITVS];
stages[1].module = module[QUADRESOLVEFS];
pipeInfo.layout = m_QuadResolvePipeLayout;
for(size_t i = 0; i < ARRAY_COUNT(m_QuadResolvePipeline); i++)
{
if(RGBA16MSRP[i] == VK_NULL_HANDLE)
continue;
pipeInfo.renderPass = RGBA16MSRP[i];
msaa.rasterizationSamples = VkSampleCountFlagBits(1 << i);
vkr = m_pDriver->vkCreateGraphicsPipelines(dev, VK_NULL_HANDLE, 1, &pipeInfo, NULL,
&m_QuadResolvePipeline[i]);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
}
msaa.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
VkComputePipelineCreateInfo compPipeInfo = {
VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
NULL,
0,
{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, NULL, 0, VK_SHADER_STAGE_COMPUTE_BIT,
VK_NULL_HANDLE, "main", NULL},
m_HistogramPipeLayout,
VK_NULL_HANDLE,
0, // base pipeline VkPipeline
};
sources.resize(5);
sources[0] = "#version 430 core\n";
if(m_pDriver->IsAMD() && texelFetchBrokenAMDDriver)
sources[0] += "#define NO_TEXEL_FETCH\n";
sources[1] = GetEmbeddedResource(spv_debuguniforms_h);
sources[2] = GetEmbeddedResource(spv_texsample_h);
for(size_t t = eTexType_1D; t < eTexType_Max; t++)
{
for(size_t f = 0; f < 3; f++)
{
VkShaderModule minmaxtile = VK_NULL_HANDLE;
VkShaderModule minmaxresult = VK_NULL_HANDLE;
VkShaderModule histogram = VK_NULL_HANDLE;
string err;
vector<uint32_t> *blob = NULL;
VkShaderModuleCreateInfo modinfo = {
VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, NULL, 0, 0, NULL,
};
sources[3] = string("#define SHADER_RESTYPE ") + ToStr::Get(t) + "\n";
sources[3] += string("#define UINT_TEX ") + (f == 1 ? "1" : "0") + "\n";
sources[3] += string("#define SINT_TEX ") + (f == 2 ? "1" : "0") + "\n";
sources[4] = shaderSources[HISTOGRAMCS];
err = GetSPIRVBlob(eSPIRVCompute, sources, &blob);
RDCASSERT(err.empty() && blob);
modinfo.codeSize = blob->size() * sizeof(uint32_t);
modinfo.pCode = &(*blob)[0];
vkr = m_pDriver->vkCreateShaderModule(dev, &modinfo, NULL, &histogram);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
sources[4] = shaderSources[MINMAXTILECS];
err = GetSPIRVBlob(eSPIRVCompute, sources, &blob);
RDCASSERT(err.empty() && blob);
modinfo.codeSize = blob->size() * sizeof(uint32_t);
modinfo.pCode = &(*blob)[0];
vkr = m_pDriver->vkCreateShaderModule(dev, &modinfo, NULL, &minmaxtile);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
if(t == 1)
{
sources[4] = shaderSources[MINMAXRESULTCS];
err = GetSPIRVBlob(eSPIRVCompute, sources, &blob);
RDCASSERT(err.empty() && blob);
modinfo.codeSize = blob->size() * sizeof(uint32_t);
modinfo.pCode = &(*blob)[0];
vkr = m_pDriver->vkCreateShaderModule(dev, &modinfo, NULL, &minmaxresult);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
}
compPipeInfo.stage.module = minmaxtile;
vkr = m_pDriver->vkCreateComputePipelines(dev, VK_NULL_HANDLE, 1, &compPipeInfo, NULL,
&m_MinMaxTilePipe[t][f]);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
compPipeInfo.stage.module = histogram;
vkr = m_pDriver->vkCreateComputePipelines(dev, VK_NULL_HANDLE, 1, &compPipeInfo, NULL,
&m_HistogramPipe[t][f]);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
if(t == 1)
{
compPipeInfo.stage.module = minmaxresult;
vkr = m_pDriver->vkCreateComputePipelines(dev, VK_NULL_HANDLE, 1, &compPipeInfo, NULL,
&m_MinMaxResultPipe[f]);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
}
m_pDriver->vkDestroyShaderModule(dev, histogram, NULL);
m_pDriver->vkDestroyShaderModule(dev, minmaxtile, NULL);
if(t == 1)
m_pDriver->vkDestroyShaderModule(dev, minmaxresult, NULL);
}
}
{
compPipeInfo.stage.module = module[MESHCS];
compPipeInfo.layout = m_MeshPickLayout;
vkr = m_pDriver->vkCreateComputePipelines(dev, VK_NULL_HANDLE, 1, &compPipeInfo, NULL,
&m_MeshPickPipeline);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
}
m_pDriver->vkDestroyRenderPass(dev, RGBA16RP, NULL);
m_pDriver->vkDestroyRenderPass(dev, RGBA32RP, NULL);
m_pDriver->vkDestroyRenderPass(dev, RGBA8RP, NULL);
m_pDriver->vkDestroyRenderPass(dev, RGBA8MSRP, NULL);
for(size_t i = 0; i < ARRAY_COUNT(RGBA16MSRP); i++)
m_pDriver->vkDestroyRenderPass(dev, RGBA16MSRP[i], NULL);
for(size_t i = 0; i < ARRAY_COUNT(module); i++)
{
// hold onto the shaders/modules we use later
if(i == MESHVS)
{
m_MeshModules[0] = module[i];
}
else if(i == MESHGS)
{
m_MeshModules[1] = module[i];
}
else if(i == MESHFS)
{
m_MeshModules[2] = module[i];
}
else if(i == BLITVS)
{
m_BlitVSModule = module[i];
}
else if(i == HISTOGRAMCS || i == MINMAXTILECS || i == MINMAXRESULTCS)
{
// not compiled normally
continue;
}
else if(module[i] != VK_NULL_HANDLE)
{
m_pDriver->vkDestroyShaderModule(dev, module[i], NULL);
}
}
VkCommandBuffer replayDataCmd = driver->GetNextCmd();
vkr = ObjDisp(replayDataCmd)->BeginCommandBuffer(Unwrap(replayDataCmd), &beginInfo);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
// create dummy images for filling out the texdisplay descriptors
// in slots that are skipped by dynamic branching (e.g. 3D texture
// when we're displaying a 2D, etc).
{
int index = 0;
VkDeviceSize offsets[ARRAY_COUNT(m_TexDisplayDummyImages)];
VkDeviceSize curOffset = 0;
// we pick RGBA8 formats to be guaranteed they will be supported
VkFormat formats[] = {VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_R8G8B8A8_UINT, VK_FORMAT_R8G8B8A8_SINT};
VkImageType types[] = {VK_IMAGE_TYPE_1D, VK_IMAGE_TYPE_2D, VK_IMAGE_TYPE_3D, VK_IMAGE_TYPE_2D};
VkSampleCountFlagBits sampleCounts[] = {VK_SAMPLE_COUNT_1_BIT, VK_SAMPLE_COUNT_1_BIT,
VK_SAMPLE_COUNT_1_BIT, VK_SAMPLE_COUNT_4_BIT};
// type max is one higher than the last RESTYPE, and RESTYPES are 1-indexed
RDCCOMPILE_ASSERT(RESTYPE_TEXTYPEMAX - 1 == ARRAY_COUNT(types),
"RESTYPE values don't match formats for dummy images");
RDCCOMPILE_ASSERT(
ARRAY_COUNT(m_TexDisplayDummyImages) == ARRAY_COUNT(m_TexDisplayDummyImageViews),
"dummy image arrays mismatched sizes");
RDCCOMPILE_ASSERT(ARRAY_COUNT(m_TexDisplayDummyImages) == ARRAY_COUNT(m_TexDisplayDummyWrites),
"dummy image arrays mismatched sizes");
RDCCOMPILE_ASSERT(ARRAY_COUNT(m_TexDisplayDummyImages) == ARRAY_COUNT(m_TexDisplayDummyInfos),
"dummy image arrays mismatched sizes");
VkMemoryAllocateInfo allocInfo = {
VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, NULL, 0, ~0U,
};
for(size_t fmt = 0; fmt < ARRAY_COUNT(formats); fmt++)
{
for(size_t type = 0; type < ARRAY_COUNT(types); type++)
{
// create 1x1 image of the right size
VkImageCreateInfo imInfo = {
VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
NULL,
0,
types[type],
formats[fmt],
{1, 1, 1},
1,
1,
sampleCounts[type],
VK_IMAGE_TILING_OPTIMAL,
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
VK_SHARING_MODE_EXCLUSIVE,
0,
NULL,
VK_IMAGE_LAYOUT_UNDEFINED,
};
vkr = m_pDriver->vkCreateImage(dev, &imInfo, NULL, &m_TexDisplayDummyImages[index]);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
VkMemoryRequirements mrq = {0};
m_pDriver->vkGetImageMemoryRequirements(dev, m_TexDisplayDummyImages[index], &mrq);
uint32_t memIndex = driver->GetGPULocalMemoryIndex(mrq.memoryTypeBits);
// make sure all images can use the same memory type
RDCASSERTMSG("memory type indices don't overlap!",
allocInfo.memoryTypeIndex == ~0U || allocInfo.memoryTypeIndex == memIndex,
allocInfo.memoryTypeIndex, memIndex, fmt, type);
allocInfo.memoryTypeIndex = memIndex;
// align to our alignment, then increment curOffset by our size
curOffset = AlignUp(curOffset, mrq.alignment);
offsets[index] = curOffset;
curOffset += mrq.size;
// fill out the descriptor set write to the write binding - set will be filled out
// on demand when we're actulaly using these writes.
m_TexDisplayDummyWrites[index].descriptorCount = 1;
m_TexDisplayDummyWrites[index].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
m_TexDisplayDummyWrites[index].pNext = NULL;
m_TexDisplayDummyWrites[index].dstSet = VK_NULL_HANDLE;
m_TexDisplayDummyWrites[index].dstBinding =
5 * uint32_t(fmt + 1) + uint32_t(type) + 1; // 5 + RESTYPE_x
m_TexDisplayDummyWrites[index].dstArrayElement = 0;
m_TexDisplayDummyWrites[index].descriptorCount = 1;
m_TexDisplayDummyWrites[index].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
m_TexDisplayDummyWrites[index].pImageInfo = &m_TexDisplayDummyInfos[index];
m_TexDisplayDummyWrites[index].pBufferInfo = NULL;
m_TexDisplayDummyWrites[index].pTexelBufferView = NULL;
m_TexDisplayDummyInfos[index].sampler = Unwrap(m_PointSampler);
m_TexDisplayDummyInfos[index].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
index++;
}
}
// align up a bit just to be safe
allocInfo.allocationSize = AlignUp(curOffset, (VkDeviceSize)1024ULL);
// allocate one big block
vkr = m_pDriver->vkAllocateMemory(dev, &allocInfo, NULL, &m_TexDisplayDummyMemory);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
// bind all the image memory
for(index = 0; index < (int)ARRAY_COUNT(m_TexDisplayDummyImages); index++)
{
vkr = m_pDriver->vkBindImageMemory(dev, m_TexDisplayDummyImages[index],
m_TexDisplayDummyMemory, offsets[index]);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
}
// now that the image memory is bound, we can create the image views and fill the descriptor set
// writes.
index = 0;
for(size_t fmt = 0; fmt < ARRAY_COUNT(formats); fmt++)
{
for(size_t type = 0; type < ARRAY_COUNT(types); type++)
{
VkImageViewCreateInfo viewInfo = {
VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
NULL,
0,
m_TexDisplayDummyImages[index],
VkImageViewType(types[type]), // image/view type enums overlap for 1D/2D/3D
formats[fmt],
{VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY,
VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY},
{
VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1,
},
};
RDCCOMPILE_ASSERT((uint32_t)VK_IMAGE_TYPE_1D == (uint32_t)VK_IMAGE_VIEW_TYPE_1D,
"Image/view type enums don't overlap!");
RDCCOMPILE_ASSERT((uint32_t)VK_IMAGE_TYPE_2D == (uint32_t)VK_IMAGE_VIEW_TYPE_2D,
"Image/view type enums don't overlap!");
RDCCOMPILE_ASSERT((uint32_t)VK_IMAGE_TYPE_3D == (uint32_t)VK_IMAGE_VIEW_TYPE_3D,
"Image/view type enums don't overlap!");
vkr = m_pDriver->vkCreateImageView(dev, &viewInfo, NULL, &m_TexDisplayDummyImageViews[index]);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
m_TexDisplayDummyInfos[index].imageView = Unwrap(m_TexDisplayDummyImageViews[index]);
// need to update image layout into valid state
VkImageMemoryBarrier barrier = {
VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
NULL,
0,
VK_ACCESS_SHADER_READ_BIT,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
0,
0, // MULTIDEVICE - need to actually pick the right queue family here maybe?
Unwrap(m_TexDisplayDummyImages[index]),
{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
DoPipelineBarrier(replayDataCmd, 1, &barrier);
index++;
}
}
}
m_OverdrawRampUBO.Create(driver, dev, 2048, 1, 0); // no ring needed, fixed data
RDCCOMPILE_ASSERT(sizeof(overdrawRamp) <= 2048, "overdraw ramp uniforms size");
void *ramp = m_OverdrawRampUBO.Map();
memcpy(ramp, overdrawRamp, sizeof(overdrawRamp));
m_OverdrawRampUBO.Unmap();
// pick pixel data
{
// create image
VkImageCreateInfo imInfo = {
VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
NULL,
0,
VK_IMAGE_TYPE_2D,
VK_FORMAT_R32G32B32A32_SFLOAT,
{1, 1, 1},
1,
1,
VK_SAMPLE_COUNT_1_BIT,
VK_IMAGE_TILING_OPTIMAL,
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT,
VK_SHARING_MODE_EXCLUSIVE,
0,
NULL,
VK_IMAGE_LAYOUT_UNDEFINED,
};
vkr = m_pDriver->vkCreateImage(dev, &imInfo, NULL, &m_PickPixelImage);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
VkMemoryRequirements mrq = {0};
m_pDriver->vkGetImageMemoryRequirements(dev, m_PickPixelImage, &mrq);
VkImageSubresource subr = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0};
VkSubresourceLayout layout = {0};
m_pDriver->vkGetImageSubresourceLayout(dev, m_PickPixelImage, &subr, &layout);
// allocate readback memory
VkMemoryAllocateInfo allocInfo = {
VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, NULL, mrq.size,
driver->GetGPULocalMemoryIndex(mrq.memoryTypeBits),
};
vkr = m_pDriver->vkAllocateMemory(dev, &allocInfo, NULL, &m_PickPixelImageMem);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
vkr = m_pDriver->vkBindImageMemory(dev, m_PickPixelImage, m_PickPixelImageMem, 0);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
VkImageViewCreateInfo viewInfo = {
VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
NULL,
0,
m_PickPixelImage,
VK_IMAGE_VIEW_TYPE_2D,
VK_FORMAT_R32G32B32A32_SFLOAT,
{VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY,
VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY},
{
VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1,
},
};
vkr = m_pDriver->vkCreateImageView(dev, &viewInfo, NULL, &m_PickPixelImageView);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
// need to update image layout into valid state
VkImageMemoryBarrier barrier = {
VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
NULL,
0,
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
0,
0, // MULTIDEVICE - need to actually pick the right queue family here maybe?
Unwrap(m_PickPixelImage),
{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
DoPipelineBarrier(replayDataCmd, 1, &barrier);
// create render pass
VkAttachmentDescription attDesc = {0,
VK_FORMAT_R32G32B32A32_SFLOAT,
VK_SAMPLE_COUNT_1_BIT,
VK_ATTACHMENT_LOAD_OP_CLEAR,
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
};
vkr = m_pDriver->vkCreateRenderPass(dev, &rpinfo, NULL, &m_PickPixelRP);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
// create framebuffer
VkFramebufferCreateInfo fbinfo = {
VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
NULL,
0,
m_PickPixelRP,
1,
&m_PickPixelImageView,
1,
1,
1,
};
vkr = m_pDriver->vkCreateFramebuffer(dev, &fbinfo, NULL, &m_PickPixelFB);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
// since we always sync for readback, doesn't need to be ring'd
m_PickPixelReadbackBuffer.Create(driver, dev, sizeof(float) * 4, 1,
GPUBuffer::eGPUBufferReadback);
}
m_MeshUBO.Create(driver, dev, sizeof(MeshUBOData), 16, 0);
m_MeshBBoxVB.Create(driver, dev, sizeof(Vec4f) * 128, 16, GPUBuffer::eGPUBufferVBuffer);
Vec4f TLN = Vec4f(-1.0f, 1.0f, 0.0f, 1.0f); // TopLeftNear, etc...
Vec4f TRN = Vec4f(1.0f, 1.0f, 0.0f, 1.0f);
Vec4f BLN = Vec4f(-1.0f, -1.0f, 0.0f, 1.0f);
Vec4f BRN = Vec4f(1.0f, -1.0f, 0.0f, 1.0f);
Vec4f TLF = Vec4f(-1.0f, 1.0f, 1.0f, 1.0f);
Vec4f TRF = Vec4f(1.0f, 1.0f, 1.0f, 1.0f);
Vec4f BLF = Vec4f(-1.0f, -1.0f, 1.0f, 1.0f);
Vec4f BRF = Vec4f(1.0f, -1.0f, 1.0f, 1.0f);
Vec4f axisFrustum[] = {
// axis marker vertices
Vec4f(0.0f, 0.0f, 0.0f, 1.0f), Vec4f(1.0f, 0.0f, 0.0f, 1.0f), Vec4f(0.0f, 0.0f, 0.0f, 1.0f),
Vec4f(0.0f, 1.0f, 0.0f, 1.0f), Vec4f(0.0f, 0.0f, 0.0f, 1.0f), Vec4f(0.0f, 0.0f, 1.0f, 1.0f),
// frustum vertices
TLN, TRN, TRN, BRN, BRN, BLN, BLN, TLN,
TLN, TLF, TRN, TRF, BLN, BLF, BRN, BRF,
TLF, TRF, TRF, BRF, BRF, BLF, BLF, TLF,
};
// doesn't need to be ring'd as it's immutable
m_MeshAxisFrustumVB.Create(driver, dev, sizeof(axisFrustum), 1, GPUBuffer::eGPUBufferVBuffer);
Vec4f *axisData = (Vec4f *)m_MeshAxisFrustumVB.Map();
memcpy(axisData, axisFrustum, sizeof(axisFrustum));
m_MeshAxisFrustumVB.Unmap();
const uint32_t maxTexDim = 16384;
const uint32_t blockPixSize = HGRAM_PIXELS_PER_TILE * HGRAM_TILES_PER_BLOCK;
const uint32_t maxBlocksNeeded = (maxTexDim * maxTexDim) / (blockPixSize * blockPixSize);
const size_t byteSize =
2 * sizeof(Vec4f) * HGRAM_TILES_PER_BLOCK * HGRAM_TILES_PER_BLOCK * maxBlocksNeeded;
m_MinMaxTileResult.Create(driver, dev, byteSize, 1, GPUBuffer::eGPUBufferSSBO);
m_MinMaxResult.Create(driver, dev, sizeof(Vec4f) * 2, 1, GPUBuffer::eGPUBufferSSBO);
m_MinMaxReadback.Create(driver, dev, sizeof(Vec4f) * 2, 1, GPUBuffer::eGPUBufferReadback);
m_HistogramBuf.Create(driver, dev, sizeof(uint32_t) * 4 * HGRAM_NUM_BUCKETS, 1,
GPUBuffer::eGPUBufferSSBO);
m_HistogramReadback.Create(driver, dev, sizeof(uint32_t) * 4 * HGRAM_NUM_BUCKETS, 1,
GPUBuffer::eGPUBufferReadback);
// don't need to ring this, as we hard-sync for readback anyway
m_HistogramUBO.Create(driver, dev, sizeof(HistogramUBOData), 1, 0);
ObjDisp(replayDataCmd)->EndCommandBuffer(Unwrap(replayDataCmd));
// tex display descriptors are updated right before rendering,
// so we don't have to update them here
m_CheckerboardUBO.FillDescriptor(bufInfo[0]);
m_MeshUBO.FillDescriptor(bufInfo[1]);
m_OutlineUBO.FillDescriptor(bufInfo[2]);
m_OverdrawRampUBO.FillDescriptor(bufInfo[3]);
m_MeshPickUBO.FillDescriptor(bufInfo[4]);
m_MeshPickResult.FillDescriptor(bufInfo[5]);
VkWriteDescriptorSet analysisSetWrites[] = {
{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, Unwrap(m_CheckerboardDescSet), 0, 0, 1,
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, NULL, &bufInfo[0], NULL},
{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, Unwrap(m_MeshDescSet), 0, 0, 1,
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, NULL, &bufInfo[1], NULL},
{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, Unwrap(m_OutlineDescSet), 0, 0, 1,
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, NULL, &bufInfo[2], NULL},
{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, Unwrap(m_QuadDescSet), 1, 0, 1,
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, NULL, &bufInfo[3], NULL},
{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, Unwrap(m_MeshPickDescSet), 0, 0, 1,
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, NULL, &bufInfo[4], NULL},
{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, Unwrap(m_MeshPickDescSet), 3, 0, 1,
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, NULL, &bufInfo[5], NULL},
};
ObjDisp(dev)->UpdateDescriptorSets(Unwrap(dev), ARRAY_COUNT(analysisSetWrites), analysisSetWrites,
0, NULL);
}
VulkanDebugManager::~VulkanDebugManager()
{
VkDevice dev = m_Device;
if(m_ShaderCacheDirty)
{
SaveShaderCache("vkshaders.cache", m_ShaderCacheMagic, m_ShaderCacheVersion, m_ShaderCache,
ShaderCacheCallbacks);
}
else
{
for(auto it = m_ShaderCache.begin(); it != m_ShaderCache.end(); ++it)
ShaderCacheCallbacks.Destroy(it->second);
}
for(auto it = m_PostVSData.begin(); it != m_PostVSData.end(); ++it)
{
m_pDriver->vkDestroyBuffer(dev, it->second.vsout.buf, NULL);
m_pDriver->vkDestroyBuffer(dev, it->second.vsout.idxBuf, NULL);
m_pDriver->vkFreeMemory(dev, it->second.vsout.bufmem, NULL);
m_pDriver->vkFreeMemory(dev, it->second.vsout.idxBufMem, NULL);
}
m_PostVSData.clear();
// since we don't have properly registered resources, releasing our descriptor
// pool here won't remove the descriptor sets, so we need to free our own
// tracking data (not the API objects) for descriptor sets.
for(auto it = m_CachedMeshPipelines.begin(); it != m_CachedMeshPipelines.end(); ++it)
for(uint32_t i = 0; i < MeshDisplayPipelines::ePipe_Count; i++)
m_pDriver->vkDestroyPipeline(dev, it->second.pipes[i], NULL);
for(size_t i = 0; i < ARRAY_COUNT(m_MeshModules); i++)
m_pDriver->vkDestroyShaderModule(dev, m_MeshModules[i], NULL);
m_pDriver->vkDestroyDescriptorPool(dev, m_DescriptorPool, NULL);
m_pDriver->vkDestroySampler(dev, m_LinearSampler, NULL);
m_pDriver->vkDestroySampler(dev, m_PointSampler, NULL);
m_pDriver->vkDestroyDescriptorSetLayout(dev, m_CheckerboardDescSetLayout, NULL);
m_pDriver->vkDestroyPipelineLayout(dev, m_CheckerboardPipeLayout, NULL);
m_pDriver->vkDestroyPipeline(dev, m_CheckerboardPipeline, NULL);
m_pDriver->vkDestroyPipeline(dev, m_CheckerboardMSAAPipeline, NULL);
m_pDriver->vkDestroyDescriptorSetLayout(dev, m_TexDisplayDescSetLayout, NULL);
m_pDriver->vkDestroyPipelineLayout(dev, m_TexDisplayPipeLayout, NULL);
m_pDriver->vkDestroyPipeline(dev, m_TexDisplayPipeline, NULL);
m_pDriver->vkDestroyPipeline(dev, m_TexDisplayBlendPipeline, NULL);
m_pDriver->vkDestroyPipeline(dev, m_TexDisplayF32Pipeline, NULL);
for(size_t i = 0; i < ARRAY_COUNT(m_TexDisplayDummyImages); i++)
{
m_pDriver->vkDestroyImageView(dev, m_TexDisplayDummyImageViews[i], NULL);
m_pDriver->vkDestroyImage(dev, m_TexDisplayDummyImages[i], NULL);
}
m_pDriver->vkFreeMemory(dev, m_TexDisplayDummyMemory, NULL);
m_pDriver->vkDestroyRenderPass(dev, m_CustomTexRP, NULL);
m_pDriver->vkDestroyFramebuffer(dev, m_CustomTexFB, NULL);
m_pDriver->vkDestroyImage(dev, m_CustomTexImg, NULL);
m_pDriver->vkDestroyImageView(dev, m_CustomTexImgView, NULL);
m_pDriver->vkFreeMemory(dev, m_CustomTexMem, NULL);
m_pDriver->vkDestroyPipeline(dev, m_CustomTexPipeline, NULL);
m_CheckerboardUBO.Destroy();
m_TexDisplayUBO.Destroy();
m_PickPixelReadbackBuffer.Destroy();
m_pDriver->vkDestroyFramebuffer(dev, m_PickPixelFB, NULL);
m_pDriver->vkDestroyRenderPass(dev, m_PickPixelRP, NULL);
m_pDriver->vkDestroyImageView(dev, m_PickPixelImageView, NULL);
m_pDriver->vkDestroyImage(dev, m_PickPixelImage, NULL);
m_pDriver->vkFreeMemory(dev, m_PickPixelImageMem, NULL);
m_pDriver->vkDestroyDescriptorSetLayout(dev, m_TextDescSetLayout, NULL);
m_pDriver->vkDestroyPipelineLayout(dev, m_TextPipeLayout, NULL);
m_pDriver->vkDestroyPipeline(dev, m_TextPipeline, NULL);
m_TextGeneralUBO.Destroy();
m_TextGlyphUBO.Destroy();
m_TextStringUBO.Destroy();
m_TextAtlasUpload.Destroy();
m_pDriver->vkDestroyImageView(dev, m_TextAtlasView, NULL);
m_pDriver->vkDestroyImage(dev, m_TextAtlas, NULL);
m_pDriver->vkFreeMemory(dev, m_TextAtlasMem, NULL);
m_pDriver->vkDestroyDescriptorSetLayout(dev, m_MeshDescSetLayout, NULL);
m_pDriver->vkDestroyPipelineLayout(dev, m_MeshPipeLayout, NULL);
m_MeshUBO.Destroy();
m_MeshBBoxVB.Destroy();
m_MeshAxisFrustumVB.Destroy();
m_pDriver->vkDestroyDescriptorSetLayout(dev, m_OutlineDescSetLayout, NULL);
m_pDriver->vkDestroyPipelineLayout(dev, m_OutlinePipeLayout, NULL);
for(size_t i = 0; i < ARRAY_COUNT(m_OutlinePipeline); i++)
m_pDriver->vkDestroyPipeline(dev, m_OutlinePipeline[i], NULL);
m_OutlineUBO.Destroy();
m_pDriver->vkDestroyDescriptorSetLayout(dev, m_HistogramDescSetLayout, NULL);
m_pDriver->vkDestroyPipelineLayout(dev, m_HistogramPipeLayout, NULL);
for(size_t t = 1; t < eTexType_Max; t++)
{
for(size_t f = 0; f < 3; f++)
{
m_pDriver->vkDestroyPipeline(dev, m_MinMaxTilePipe[t][f], NULL);
m_pDriver->vkDestroyPipeline(dev, m_HistogramPipe[t][f], NULL);
if(t == 1)
m_pDriver->vkDestroyPipeline(dev, m_MinMaxResultPipe[f], NULL);
}
}
m_ReadbackWindow.Destroy();
m_MinMaxTileResult.Destroy();
m_MinMaxResult.Destroy();
m_MinMaxReadback.Destroy();
m_HistogramBuf.Destroy();
m_HistogramReadback.Destroy();
m_HistogramUBO.Destroy();
m_OverdrawRampUBO.Destroy();
m_MeshPickUBO.Destroy();
m_MeshPickIB.Destroy();
m_MeshPickIBUpload.Destroy();
m_MeshPickVB.Destroy();
m_MeshPickVBUpload.Destroy();
m_MeshPickResult.Destroy();
m_MeshPickResultReadback.Destroy();
m_pDriver->vkDestroyDescriptorSetLayout(dev, m_MeshPickDescSetLayout, NULL);
m_pDriver->vkDestroyPipelineLayout(dev, m_MeshPickLayout, NULL);
m_pDriver->vkDestroyPipeline(dev, m_MeshPickPipeline, NULL);
m_pDriver->vkDestroyDescriptorSetLayout(dev, m_MeshFetchDescSetLayout, NULL);
m_pDriver->vkDestroyFramebuffer(dev, m_OverlayNoDepthFB, NULL);
m_pDriver->vkDestroyRenderPass(dev, m_OverlayNoDepthRP, NULL);
m_pDriver->vkDestroyImageView(dev, m_OverlayImageView, NULL);
m_pDriver->vkDestroyImage(dev, m_OverlayImage, NULL);
m_pDriver->vkFreeMemory(dev, m_OverlayImageMem, NULL);
m_pDriver->vkDestroyDescriptorSetLayout(dev, m_QuadDescSetLayout, NULL);
m_pDriver->vkDestroyPipelineLayout(dev, m_QuadResolvePipeLayout, NULL);
for(size_t i = 0; i < ARRAY_COUNT(m_QuadResolvePipeline); i++)
m_pDriver->vkDestroyPipeline(dev, m_QuadResolvePipeline[i], NULL);
}
void VulkanDebugManager::BeginText(const TextPrintState &textstate)
{
VkClearValue clearval = {};
VkRenderPassBeginInfo rpbegin = {
VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
NULL,
Unwrap(textstate.rp),
Unwrap(textstate.fb),
{{
0, 0,
},
{textstate.w, textstate.h}},
1,
&clearval,
};
ObjDisp(textstate.cmd)->CmdBeginRenderPass(Unwrap(textstate.cmd), &rpbegin, VK_SUBPASS_CONTENTS_INLINE);
ObjDisp(textstate.cmd)
->CmdBindPipeline(Unwrap(textstate.cmd), VK_PIPELINE_BIND_POINT_GRAPHICS,
Unwrap(m_TextPipeline));
VkViewport viewport = {0.0f, 0.0f, (float)textstate.w, (float)textstate.h, 0.0f, 1.0f};
ObjDisp(textstate.cmd)->CmdSetViewport(Unwrap(textstate.cmd), 0, 1, &viewport);
}
void VulkanDebugManager::RenderText(const TextPrintState &textstate, float x, float y,
const char *textfmt, ...)
{
static char tmpBuf[4096];
va_list args;
va_start(args, textfmt);
StringFormat::vsnprintf(tmpBuf, 4095, textfmt, args);
tmpBuf[4095] = '\0';
va_end(args);
RenderTextInternal(textstate, x, y, tmpBuf);
}
void VulkanDebugManager::RenderTextInternal(const TextPrintState &textstate, float x, float y,
const char *text)
{
uint32_t offsets[2] = {0};
FontUBOData *ubo = (FontUBOData *)m_TextGeneralUBO.Map(&offsets[0]);
ubo->TextPosition.x = x;
ubo->TextPosition.y = y;
ubo->FontScreenAspect.x = 1.0f / float(textstate.w);
ubo->FontScreenAspect.y = 1.0f / float(textstate.h);
ubo->TextSize = m_FontCharSize;
ubo->FontScreenAspect.x *= m_FontCharAspect;
ubo->CharacterSize.x = 1.0f / float(FONT_TEX_WIDTH);
ubo->CharacterSize.y = 1.0f / float(FONT_TEX_HEIGHT);
m_TextGeneralUBO.Unmap();
size_t len = strlen(text);
RDCASSERT(len <= MAX_SINGLE_LINE_LENGTH);
// only map enough for our string
StringUBOData *stringData = (StringUBOData *)m_TextStringUBO.Map(&offsets[1], len * sizeof(Vec4u));
for(size_t i = 0; i < strlen(text); i++)
stringData->chars[i].x = uint32_t(text[i] - ' ');
m_TextStringUBO.Unmap();
ObjDisp(textstate.cmd)
->CmdBindDescriptorSets(Unwrap(textstate.cmd), VK_PIPELINE_BIND_POINT_GRAPHICS,
Unwrap(m_TextPipeLayout), 0, 1, UnwrapPtr(m_TextDescSet), 2, offsets);
ObjDisp(textstate.cmd)->CmdDraw(Unwrap(textstate.cmd), 4, (uint32_t)strlen(text), 0, 0);
}
void VulkanDebugManager::ReplaceResource(ResourceId from, ResourceId to)
{
VkDevice dev = m_pDriver->GetDev();
// we're passed in the original ID but we want the live ID for comparison
ResourceId liveid = GetResourceManager()->GetLiveID(from);
VkShaderModule srcShaderModule = GetResourceManager()->GetCurrentHandle<VkShaderModule>(liveid);
VkShaderModule dstShaderModule = GetResourceManager()->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)
{
VkGraphicsPipelineCreateInfo pipeCreateInfo;
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 pipeline
VkPipeline pipe = VK_NULL_HANDLE;
VkResult vkr =
m_pDriver->vkCreateGraphicsPipelines(dev, VK_NULL_HANDLE, 1, &pipeCreateInfo, NULL, &pipe);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
// remove the replacements
GetResourceManager()->ReplaceResource(it->first, GetResID(pipe));
GetResourceManager()->ReplaceResource(GetResourceManager()->GetOriginalID(it->first),
GetResID(pipe));
}
}
// make the actual shader module replacements
GetResourceManager()->ReplaceResource(from, to);
GetResourceManager()->ReplaceResource(liveid, to);
}
void VulkanDebugManager::RemoveReplacement(ResourceId id)
{
VkDevice dev = m_pDriver->GetDev();
// we're passed in the original ID but we want the live ID for comparison
ResourceId liveid = GetResourceManager()->GetLiveID(id);
// remove the actual shader module replacements
GetResourceManager()->RemoveReplacement(id);
GetResourceManager()->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 = GetResourceManager()->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
GetResourceManager()->RemoveReplacement(it->first);
GetResourceManager()->RemoveReplacement(GetResourceManager()->GetOriginalID(it->first));
}
}
}
void VulkanDebugManager::CreateCustomShaderTex(uint32_t width, uint32_t height)
{
VkDevice dev = m_Device;
if(m_CustomTexImg != VK_NULL_HANDLE)
{
if(width == m_CustomTexWidth && height == m_CustomTexHeight)
return;
m_pDriver->vkDestroyRenderPass(dev, m_CustomTexRP, NULL);
m_pDriver->vkDestroyFramebuffer(dev, m_CustomTexFB, NULL);
m_pDriver->vkDestroyImageView(dev, m_CustomTexImgView, NULL);
m_pDriver->vkDestroyImage(dev, m_CustomTexImg, NULL);
}
m_CustomTexWidth = width;
m_CustomTexHeight = height;
VkResult vkr = VK_SUCCESS;
VkImageCreateInfo imInfo = {
VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
NULL,
0,
VK_IMAGE_TYPE_2D,
VK_FORMAT_R16G16B16A16_SFLOAT,
{width, height, 1},
1,
1,
VK_SAMPLE_COUNT_1_BIT,
VK_IMAGE_TILING_OPTIMAL,
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT |
VK_IMAGE_USAGE_TRANSFER_SRC_BIT,
VK_SHARING_MODE_EXCLUSIVE,
0,
NULL,
VK_IMAGE_LAYOUT_UNDEFINED,
};
vkr = m_pDriver->vkCreateImage(m_Device, &imInfo, NULL, &m_CustomTexImg);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
VkMemoryRequirements mrq = {0};
m_pDriver->vkGetImageMemoryRequirements(m_Device, m_CustomTexImg, &mrq);
// if no memory is allocated, or it's not enough,
// then allocate
if(m_CustomTexMem == VK_NULL_HANDLE || mrq.size > m_CustomTexMemSize)
{
if(m_CustomTexMem != VK_NULL_HANDLE)
m_pDriver->vkFreeMemory(m_Device, m_CustomTexMem, NULL);
VkMemoryAllocateInfo allocInfo = {
VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, NULL, mrq.size,
m_pDriver->GetGPULocalMemoryIndex(mrq.memoryTypeBits),
};
vkr = m_pDriver->vkAllocateMemory(m_Device, &allocInfo, NULL, &m_CustomTexMem);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
m_CustomTexMemSize = mrq.size;
}
vkr = m_pDriver->vkBindImageMemory(m_Device, m_CustomTexImg, m_CustomTexMem, 0);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
VkImageViewCreateInfo viewInfo = {
VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
NULL,
0,
m_CustomTexImg,
VK_IMAGE_VIEW_TYPE_2D,
imInfo.format,
{VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY,
VK_COMPONENT_SWIZZLE_IDENTITY},
{
VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1,
},
};
vkr = m_pDriver->vkCreateImageView(m_Device, &viewInfo, NULL, &m_CustomTexImgView);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
// need to update image layout into valid state
VkImageMemoryBarrier barrier = {
VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
NULL,
0,
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
0,
0, // MULTIDEVICE - need to actually pick the right queue family here maybe?
Unwrap(m_CustomTexImg),
{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
m_pDriver->m_ImageLayouts[GetResID(m_CustomTexImg)].subresourceStates[0].newLayout =
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkCommandBuffer cmd = m_pDriver->GetNextCmd();
VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, NULL,
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT};
ObjDisp(dev)->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
DoPipelineBarrier(cmd, 1, &barrier);
vkr = ObjDisp(dev)->EndCommandBuffer(Unwrap(cmd));
RDCASSERTEQUAL(vkr, VK_SUCCESS);
#if defined(SINGLE_FLUSH_VALIDATE)
m_pDriver->SubmitCmds();
#endif
VkAttachmentDescription colDesc = {0,
imInfo.format,
imInfo.samples,
VK_ATTACHMENT_LOAD_OP_LOAD,
VK_ATTACHMENT_STORE_OP_STORE,
VK_ATTACHMENT_LOAD_OP_DONT_CARE,
VK_ATTACHMENT_STORE_OP_DONT_CARE,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
VkAttachmentReference colRef = {0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
VkSubpassDescription sub = {
0, VK_PIPELINE_BIND_POINT_GRAPHICS,
0, NULL, // inputs
1, &colRef, // color
NULL, // resolve
NULL, // depth-stencil
0, NULL, // preserve
};
VkRenderPassCreateInfo rpinfo = {
VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
NULL,
0,
1,
&colDesc,
1,
&sub,
0,
NULL, // dependencies
};
vkr = m_pDriver->vkCreateRenderPass(m_Device, &rpinfo, NULL, &m_CustomTexRP);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
// Create framebuffer rendering just to overlay image, no depth
VkFramebufferCreateInfo fbinfo = {
VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
NULL,
0,
m_CustomTexRP,
1,
&m_CustomTexImgView,
width,
height,
1,
};
vkr = m_pDriver->vkCreateFramebuffer(m_Device, &fbinfo, NULL, &m_CustomTexFB);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
}
void VulkanDebugManager::CreateCustomShaderPipeline(ResourceId shader)
{
VkDevice dev = m_Device;
if(shader == ResourceId())
return;
if(m_CustomTexPipeline != VK_NULL_HANDLE)
{
if(m_CustomTexShader == shader)
return;
m_pDriver->vkDestroyPipeline(dev, m_CustomTexPipeline, NULL);
}
m_CustomTexShader = shader;
// declare the pipeline creation info and all of its sub-structures
// these are modified as appropriate for each pipeline we create
VkPipelineShaderStageCreateInfo stages[2] = {
{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, NULL, 0, VK_SHADER_STAGE_VERTEX_BIT,
m_BlitVSModule, "main", NULL},
{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, NULL, 0, VK_SHADER_STAGE_FRAGMENT_BIT,
GetResourceManager()->GetCurrentHandle<VkShaderModule>(shader), "main", NULL},
};
VkPipelineVertexInputStateCreateInfo vi = {
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
NULL,
0,
0,
NULL, // vertex bindings
0,
NULL, // vertex attributes
};
VkPipelineInputAssemblyStateCreateInfo ia = {
VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
NULL,
0,
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP,
false,
};
VkRect2D scissor = {{0, 0}, {4096, 4096}};
VkPipelineViewportStateCreateInfo vp = {
VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, NULL, 0, 1, NULL, 1, &scissor};
VkPipelineRasterizationStateCreateInfo rs = {
VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
NULL,
0,
true,
false,
VK_POLYGON_MODE_FILL,
VK_CULL_MODE_NONE,
VK_FRONT_FACE_CLOCKWISE,
false,
0.0f,
0.0f,
0.0f,
1.0f,
};
VkPipelineMultisampleStateCreateInfo msaa = {
VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
NULL,
0,
VK_SAMPLE_COUNT_1_BIT,
false,
0.0f,
NULL,
false,
false,
};
VkPipelineDepthStencilStateCreateInfo ds = {
VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
NULL,
0,
false,
false,
VK_COMPARE_OP_ALWAYS,
false,
false,
{VK_STENCIL_OP_KEEP, VK_STENCIL_OP_KEEP, VK_STENCIL_OP_KEEP, VK_COMPARE_OP_ALWAYS, 0, 0, 0},
{VK_STENCIL_OP_KEEP, VK_STENCIL_OP_KEEP, VK_STENCIL_OP_KEEP, VK_COMPARE_OP_ALWAYS, 0, 0, 0},
0.0f,
1.0f,
};
VkPipelineColorBlendAttachmentState attState = {
false,
VK_BLEND_FACTOR_ONE,
VK_BLEND_FACTOR_ZERO,
VK_BLEND_OP_ADD,
VK_BLEND_FACTOR_ONE,
VK_BLEND_FACTOR_ZERO,
VK_BLEND_OP_ADD,
0xf,
};
VkPipelineColorBlendStateCreateInfo cb = {
VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
NULL,
0,
false,
VK_LOGIC_OP_NO_OP,
1,
&attState,
{1.0f, 1.0f, 1.0f, 1.0f}};
VkDynamicState dynstates[] = {VK_DYNAMIC_STATE_VIEWPORT};
VkPipelineDynamicStateCreateInfo dyn = {
VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
NULL,
0,
ARRAY_COUNT(dynstates),
dynstates,
};
VkGraphicsPipelineCreateInfo pipeInfo = {
VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
NULL,
0,
2,
stages,
&vi,
&ia,
NULL, // tess
&vp,
&rs,
&msaa,
&ds,
&cb,
&dyn,
m_TexDisplayPipeLayout,
m_CustomTexRP,
0, // sub pass
VK_NULL_HANDLE, // base pipeline handle
-1, // base pipeline index
};
VkResult vkr = m_pDriver->vkCreateGraphicsPipelines(dev, VK_NULL_HANDLE, 1, &pipeInfo, NULL,
&m_CustomTexPipeline);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
}
FloatVector VulkanDebugManager::InterpretVertex(byte *data, uint32_t vert, const MeshDisplay &cfg,
byte *end, bool &valid)
{
FloatVector ret(0.0f, 0.0f, 0.0f, 1.0f);
data += vert * cfg.position.stride;
float *out = &ret.x;
ResourceFormat fmt;
fmt.compByteWidth = cfg.position.compByteWidth;
fmt.compCount = cfg.position.compCount;
fmt.compType = cfg.position.compType;
if(cfg.position.specialFormat == eSpecial_R10G10B10A2)
{
if(data + 4 >= end)
{
valid = false;
return ret;
}
Vec4f v = ConvertFromR10G10B10A2(*(uint32_t *)data);
ret.x = v.x;
ret.y = v.y;
ret.z = v.z;
ret.w = v.w;
return ret;
}
else if(cfg.position.specialFormat == eSpecial_R11G11B10)
{
if(data + 4 >= end)
{
valid = false;
return ret;
}
Vec3f v = ConvertFromR11G11B10(*(uint32_t *)data);
ret.x = v.x;
ret.y = v.y;
ret.z = v.z;
return ret;
}
if(data + cfg.position.compCount * cfg.position.compByteWidth > end)
{
valid = false;
return ret;
}
for(uint32_t i = 0; i < cfg.position.compCount; i++)
{
*out = ConvertComponent(fmt, data);
data += cfg.position.compByteWidth;
out++;
}
if(cfg.position.bgraOrder)
{
FloatVector reversed;
reversed.x = ret.z;
reversed.y = ret.y;
reversed.z = ret.x;
reversed.w = ret.w;
return reversed;
}
return ret;
}
uint32_t VulkanDebugManager::PickVertex(uint32_t eventID, const MeshDisplay &cfg, uint32_t x,
uint32_t y, uint32_t w, uint32_t h)
{
VkDevice dev = m_pDriver->GetDev();
const VkLayerDispatchTable *vt = ObjDisp(dev);
Matrix4f projMat = Matrix4f::Perspective(90.0f, 0.1f, 100000.0f, float(w) / float(h));
Matrix4f camMat = cfg.cam ? cfg.cam->GetMatrix() : Matrix4f::Identity();
Matrix4f PickMVP = projMat.Mul(camMat);
ResourceFormat resFmt;
resFmt.compByteWidth = cfg.position.compByteWidth;
resFmt.compCount = cfg.position.compCount;
resFmt.compType = cfg.position.compType;
resFmt.special = false;
if(cfg.position.specialFormat != eSpecial_Unknown)
{
resFmt.special = true;
resFmt.specialFormat = cfg.position.specialFormat;
}
if(cfg.position.unproject)
{
// the derivation of the projection matrix might not be right (hell, it could be an
// orthographic projection). But it'll be close enough likely.
Matrix4f guessProj =
Matrix4f::Perspective(cfg.fov, cfg.position.nearPlane, cfg.position.farPlane, cfg.aspect);
if(cfg.ortho)
guessProj = Matrix4f::Orthographic(cfg.position.nearPlane, cfg.position.farPlane);
PickMVP = projMat.Mul(camMat.Mul(guessProj.Inverse()));
}
MeshPickUBOData *ubo = (MeshPickUBOData *)m_MeshPickUBO.Map();
ubo->coords.x = (float)x;
ubo->coords.y = (float)y;
ubo->viewport.x = (float)w;
ubo->viewport.y = (float)h;
ubo->mvp = PickMVP;
ubo->use_indices = cfg.position.idxByteWidth ? 1U : 0U;
ubo->numVerts = cfg.position.numVerts;
ubo->unproject = cfg.position.unproject;
m_MeshPickUBO.Unmap();
vector<byte> idxs;
if(cfg.position.idxByteWidth && cfg.position.idxbuf != ResourceId())
GetBufferData(cfg.position.idxbuf, cfg.position.idxoffs, 0, idxs);
// We copy into our own buffers to promote to the target type (uint32) that the
// shader expects. Most IBs will be 16-bit indices, most VBs will not be float4.
if(!idxs.empty())
{
// resize up on demand
if(m_MeshPickIBSize < cfg.position.numVerts * sizeof(uint32_t))
{
if(m_MeshPickIBSize > 0)
{
m_MeshPickIB.Destroy();
m_MeshPickIBUpload.Destroy();
}
m_MeshPickIBSize = cfg.position.numVerts * sizeof(uint32_t);
m_MeshPickIB.Create(m_pDriver, dev, m_MeshPickIBSize, 1,
GPUBuffer::eGPUBufferGPULocal | GPUBuffer::eGPUBufferSSBO);
m_MeshPickIBUpload.Create(m_pDriver, dev, m_MeshPickIBSize, 1, 0);
}
uint32_t *outidxs = (uint32_t *)m_MeshPickIBUpload.Map();
uint16_t *idxs16 = (uint16_t *)&idxs[0];
uint32_t *idxs32 = (uint32_t *)&idxs[0];
// if indices are 16-bit, manually upcast them so the shader only
// has to deal with one type
if(cfg.position.idxByteWidth == 2)
{
for(uint32_t i = 0; i < cfg.position.numVerts; i++)
outidxs[i] = idxs16[i];
}
else
{
memcpy(outidxs, idxs32, cfg.position.numVerts * sizeof(uint32_t));
}
m_MeshPickIBUpload.Unmap();
}
if(m_MeshPickVBSize < cfg.position.numVerts * sizeof(FloatVector))
{
if(m_MeshPickVBSize > 0)
{
m_MeshPickVB.Destroy();
m_MeshPickVBUpload.Destroy();
}
m_MeshPickVBSize = cfg.position.numVerts * sizeof(FloatVector);
m_MeshPickVB.Create(m_pDriver, dev, m_MeshPickVBSize, 1,
GPUBuffer::eGPUBufferGPULocal | GPUBuffer::eGPUBufferSSBO);
m_MeshPickVBUpload.Create(m_pDriver, dev, m_MeshPickVBSize, 1, 0);
}
// unpack and linearise the data
{
vector<byte> oldData;
GetBufferData(cfg.position.buf, cfg.position.offset, 0, oldData);
byte *data = &oldData[0];
byte *dataEnd = data + oldData.size();
bool valid = true;
FloatVector *vbData = (FloatVector *)m_MeshPickVBUpload.Map();
for(uint32_t i = 0; i < cfg.position.numVerts; i++)
vbData[i] = InterpretVertex(data, i, cfg, dataEnd, valid);
m_MeshPickVBUpload.Unmap();
}
VkDescriptorBufferInfo ibInfo = {};
VkDescriptorBufferInfo vbInfo = {};
m_MeshPickVB.FillDescriptor(vbInfo);
m_MeshPickIB.FillDescriptor(ibInfo);
VkWriteDescriptorSet writes[] = {
{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, Unwrap(m_MeshPickDescSet), 1, 0, 1,
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, NULL, &vbInfo, NULL},
{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, Unwrap(m_MeshPickDescSet), 2, 0, 1,
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, NULL, &ibInfo, NULL},
};
if(!idxs.empty())
vt->UpdateDescriptorSets(Unwrap(m_Device), 2, writes, 0, NULL);
else
vt->UpdateDescriptorSets(Unwrap(m_Device), 1, writes, 0, NULL);
VkCommandBuffer cmd = m_pDriver->GetNextCmd();
VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, NULL,
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT};
VkBufferCopy bufCopy = {0, 0, 0};
vt->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
// reset first uint (used as atomic counter) to 0
vt->CmdFillBuffer(Unwrap(cmd), Unwrap(m_MeshPickResult.buf), 0, sizeof(uint32_t) * 4, 0);
VkBufferMemoryBarrier bufBarrier = {
VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
NULL,
VK_ACCESS_TRANSFER_WRITE_BIT,
VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT,
VK_QUEUE_FAMILY_IGNORED,
VK_QUEUE_FAMILY_IGNORED,
Unwrap(m_MeshPickResult.buf),
0,
VK_WHOLE_SIZE,
};
// wait for zero to be written to atomic counter before using in shader
DoPipelineBarrier(cmd, 1, &bufBarrier);
// copy uploaded VB and if needed IB
if(!idxs.empty())
{
// wait for writes
bufBarrier.buffer = Unwrap(m_MeshPickIBUpload.buf);
bufBarrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT;
bufBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
DoPipelineBarrier(cmd, 1, &bufBarrier);
// do copy
bufCopy.size = m_MeshPickIBSize;
vt->CmdCopyBuffer(Unwrap(cmd), Unwrap(m_MeshPickIBUpload.buf), Unwrap(m_MeshPickIB.buf), 1,
&bufCopy);
// wait for copy
bufBarrier.buffer = Unwrap(m_MeshPickIB.buf);
bufBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
bufBarrier.dstAccessMask = VK_ACCESS_UNIFORM_READ_BIT;
DoPipelineBarrier(cmd, 1, &bufBarrier);
}
// wait for writes
bufBarrier.buffer = Unwrap(m_MeshPickVBUpload.buf);
bufBarrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT;
bufBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
DoPipelineBarrier(cmd, 1, &bufBarrier);
// do copy
bufCopy.size = m_MeshPickVBSize;
vt->CmdCopyBuffer(Unwrap(cmd), Unwrap(m_MeshPickVBUpload.buf), Unwrap(m_MeshPickVB.buf), 1,
&bufCopy);
// wait for copy
bufBarrier.buffer = Unwrap(m_MeshPickVB.buf);
bufBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
bufBarrier.dstAccessMask = VK_ACCESS_UNIFORM_READ_BIT;
DoPipelineBarrier(cmd, 1, &bufBarrier);
vt->CmdBindPipeline(Unwrap(cmd), VK_PIPELINE_BIND_POINT_COMPUTE, Unwrap(m_MeshPickPipeline));
vt->CmdBindDescriptorSets(Unwrap(cmd), VK_PIPELINE_BIND_POINT_COMPUTE, Unwrap(m_MeshPickLayout),
0, 1, UnwrapPtr(m_MeshPickDescSet), 0, NULL);
uint32_t workgroupx = uint32_t(cfg.position.numVerts / 128 + 1);
vt->CmdDispatch(Unwrap(cmd), workgroupx, 1, 1);
// wait for shader to finish writing before transferring to readback buffer
bufBarrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
bufBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
bufBarrier.buffer = Unwrap(m_MeshPickResult.buf);
DoPipelineBarrier(cmd, 1, &bufBarrier);
bufCopy.size = m_MeshPickResult.totalsize;
// copy to readback buffer
vt->CmdCopyBuffer(Unwrap(cmd), Unwrap(m_MeshPickResult.buf), Unwrap(m_MeshPickResultReadback.buf),
1, &bufCopy);
// wait for transfer to finish before reading on CPU
bufBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
bufBarrier.dstAccessMask = VK_ACCESS_HOST_READ_BIT;
bufBarrier.buffer = Unwrap(m_MeshPickResultReadback.buf);
DoPipelineBarrier(cmd, 1, &bufBarrier);
VkResult vkr = vt->EndCommandBuffer(Unwrap(cmd));
RDCASSERTEQUAL(vkr, VK_SUCCESS);
#if defined(SINGLE_FLUSH_VALIDATE)
m_pDriver->SubmitCmds();
#endif
m_pDriver->SubmitCmds();
m_pDriver->FlushQ();
uint32_t *pickResultData = (uint32_t *)m_MeshPickResultReadback.Map();
uint32_t numResults = *pickResultData;
uint32_t ret = ~0U;
struct PickResult
{
uint32_t vertid;
uint32_t idx;
float len;
float depth;
};
PickResult *pickResults = (PickResult *)(pickResultData + 4);
if(numResults > 0)
{
PickResult *closest = pickResults;
// min with size of results buffer to protect against overflows
for(uint32_t i = 1; i < RDCMIN((uint32_t)maxMeshPicks, numResults); i++)
{
// We need to keep the picking order consistent in the face
// of random buffer appends, when multiple vertices have the
// identical position (e.g. if UVs or normals are different).
//
// We could do something to try and disambiguate, but it's
// never going to be intuitive, it's just going to flicker
// confusingly.
if(pickResults[i].len < closest->len ||
(pickResults[i].len == closest->len && pickResults[i].depth < closest->depth) ||
(pickResults[i].len == closest->len && pickResults[i].depth == closest->depth &&
pickResults[i].vertid < closest->vertid))
closest = pickResults + i;
}
ret = closest->vertid;
}
m_MeshPickResultReadback.Unmap();
return ret;
}
void VulkanDebugManager::EndText(const TextPrintState &textstate)
{
ObjDisp(textstate.cmd)->CmdEndRenderPass(Unwrap(textstate.cmd));
}
void VulkanDebugManager::GetBufferData(ResourceId buff, uint64_t offset, uint64_t len,
vector<byte> &ret)
{
VkDevice dev = m_pDriver->GetDev();
const VkLayerDispatchTable *vt = ObjDisp(dev);
VkBuffer srcBuf = m_pDriver->GetResourceManager()->GetCurrentHandle<VkBuffer>(buff);
if(srcBuf == VK_NULL_HANDLE)
{
RDCERR("Getting buffer data for unknown buffer %llu!", buff);
return;
}
if(len == 0)
{
len = m_pDriver->m_CreationInfo.m_Buffer[buff].size - offset;
}
if(len > 0 && VkDeviceSize(offset + len) > m_pDriver->m_CreationInfo.m_Buffer[buff].size)
{
RDCWARN("Attempting to read off the end of the array. Will be clamped");
len = RDCMIN(len, m_pDriver->m_CreationInfo.m_Buffer[buff].size - offset);
}
ret.resize((size_t)len);
VkDeviceSize srcoffset = (VkDeviceSize)offset;
size_t dstoffset = 0;
VkDeviceSize sizeRemaining = (VkDeviceSize)len;
VkCommandBuffer cmd = m_pDriver->GetNextCmd();
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);
VkBufferMemoryBarrier bufBarrier = {
VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
NULL,
0,
VK_ACCESS_TRANSFER_READ_BIT,
VK_QUEUE_FAMILY_IGNORED,
VK_QUEUE_FAMILY_IGNORED,
Unwrap(srcBuf),
srcoffset,
sizeRemaining,
};
bufBarrier.srcAccessMask = VK_ACCESS_ALL_WRITE_BITS;
// wait for previous writes to happen before we copy to our window buffer
DoPipelineBarrier(cmd, 1, &bufBarrier);
vkr = vt->EndCommandBuffer(Unwrap(cmd));
RDCASSERTEQUAL(vkr, VK_SUCCESS);
#if defined(SINGLE_FLUSH_VALIDATE)
m_pDriver->SubmitCmds();
#endif
while(sizeRemaining > 0)
{
VkDeviceSize chunkSize = RDCMIN(sizeRemaining, STAGE_BUFFER_BYTE_SIZE);
vkr = vt->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
VkBufferCopy region = {srcoffset, 0, chunkSize};
vt->CmdCopyBuffer(Unwrap(cmd), Unwrap(srcBuf), Unwrap(m_ReadbackWindow.buf), 1, &region);
bufBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
bufBarrier.dstAccessMask = VK_ACCESS_HOST_READ_BIT;
bufBarrier.buffer = Unwrap(m_ReadbackWindow.buf);
bufBarrier.offset = 0;
bufBarrier.size = chunkSize;
// wait for transfer to happen before we read
DoPipelineBarrier(cmd, 1, &bufBarrier);
vkr = vt->EndCommandBuffer(Unwrap(cmd));
RDCASSERTEQUAL(vkr, VK_SUCCESS);
m_pDriver->SubmitCmds();
m_pDriver->FlushQ();
byte *pData = NULL;
vkr = vt->MapMemory(Unwrap(dev), Unwrap(m_ReadbackWindow.mem), 0, VK_WHOLE_SIZE, 0,
(void **)&pData);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
RDCASSERT(pData != NULL);
memcpy(&ret[dstoffset], pData, (size_t)chunkSize);
dstoffset += (size_t)chunkSize;
sizeRemaining -= chunkSize;
vt->UnmapMemory(Unwrap(dev), Unwrap(m_ReadbackWindow.mem));
}
vt->DeviceWaitIdle(Unwrap(dev));
}
void VulkanDebugManager::MakeGraphicsPipelineInfo(VkGraphicsPipelineCreateInfo &pipeCreateInfo,
ResourceId pipeline)
{
VulkanCreationInfo::Pipeline &pipeInfo = m_pDriver->m_CreationInfo.m_Pipeline[pipeline];
static VkPipelineShaderStageCreateInfo stages[6];
static VkSpecializationInfo specInfo[6];
static vector<VkSpecializationMapEntry> specMapEntries;
size_t specEntries = 0;
for(uint32_t i = 0; i < 6; i++)
if(pipeInfo.shaders[i].module != ResourceId())
if(!pipeInfo.shaders[i].specialization.empty())
specEntries += pipeInfo.shaders[i].specialization.size();
specMapEntries.resize(specEntries);
VkSpecializationMapEntry *entry = &specMapEntries[0];
uint32_t stageCount = 0;
for(uint32_t i = 0; i < 6; i++)
{
if(pipeInfo.shaders[i].module != ResourceId())
{
stages[stageCount].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
stages[stageCount].stage = (VkShaderStageFlagBits)(1 << i);
stages[stageCount].module =
GetResourceManager()->GetCurrentHandle<VkShaderModule>(pipeInfo.shaders[i].module);
stages[stageCount].pName = pipeInfo.shaders[i].entryPoint.c_str();
stages[stageCount].pNext = NULL;
stages[stageCount].pSpecializationInfo = NULL;
if(!pipeInfo.shaders[i].specialization.empty())
{
stages[stageCount].pSpecializationInfo = &specInfo[i];
specInfo[i].pMapEntries = entry;
specInfo[i].mapEntryCount = (uint32_t)pipeInfo.shaders[i].specialization.size();
byte *minDataPtr = NULL;
byte *maxDataPtr = NULL;
for(size_t s = 0; s < pipeInfo.shaders[i].specialization.size(); s++)
{
entry[s].constantID = pipeInfo.shaders[i].specialization[s].specID;
entry[s].size = pipeInfo.shaders[i].specialization[s].size;
if(minDataPtr == NULL)
minDataPtr = pipeInfo.shaders[i].specialization[s].data;
else
minDataPtr = RDCMIN(minDataPtr, pipeInfo.shaders[i].specialization[s].data);
maxDataPtr = RDCMAX(minDataPtr, pipeInfo.shaders[i].specialization[s].data + entry[s].size);
}
for(size_t s = 0; s < pipeInfo.shaders[i].specialization.size(); s++)
entry[s].offset = (uint32_t)(pipeInfo.shaders[i].specialization[s].data - minDataPtr);
specInfo[i].dataSize = (maxDataPtr - minDataPtr);
specInfo[i].pData = (const void *)minDataPtr;
entry += specInfo[i].mapEntryCount;
}
stageCount++;
}
}
static VkPipelineVertexInputStateCreateInfo vi = {
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO};
static VkVertexInputAttributeDescription viattr[128] = {};
static VkVertexInputBindingDescription vibind[128] = {};
vi.pVertexAttributeDescriptions = viattr;
vi.pVertexBindingDescriptions = vibind;
vi.vertexAttributeDescriptionCount = (uint32_t)pipeInfo.vertexAttrs.size();
vi.vertexBindingDescriptionCount = (uint32_t)pipeInfo.vertexBindings.size();
for(uint32_t i = 0; i < vi.vertexAttributeDescriptionCount; i++)
{
viattr[i].binding = pipeInfo.vertexAttrs[i].binding;
viattr[i].offset = pipeInfo.vertexAttrs[i].byteoffset;
viattr[i].format = pipeInfo.vertexAttrs[i].format;
viattr[i].location = pipeInfo.vertexAttrs[i].location;
}
for(uint32_t i = 0; i < vi.vertexBindingDescriptionCount; i++)
{
vibind[i].binding = pipeInfo.vertexBindings[i].vbufferBinding;
vibind[i].stride = pipeInfo.vertexBindings[i].bytestride;
vibind[i].inputRate = pipeInfo.vertexBindings[i].perInstance ? VK_VERTEX_INPUT_RATE_INSTANCE
: VK_VERTEX_INPUT_RATE_VERTEX;
}
RDCASSERT(ARRAY_COUNT(viattr) >= pipeInfo.vertexAttrs.size());
RDCASSERT(ARRAY_COUNT(vibind) >= pipeInfo.vertexBindings.size());
static VkPipelineInputAssemblyStateCreateInfo ia = {
VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO};
ia.topology = pipeInfo.topology;
ia.primitiveRestartEnable = pipeInfo.primitiveRestartEnable;
static VkPipelineTessellationStateCreateInfo tess = {
VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO};
tess.patchControlPoints = pipeInfo.patchControlPoints;
static VkPipelineViewportStateCreateInfo vp = {
VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO};
static VkViewport views[32] = {};
static VkRect2D scissors[32] = {};
memcpy(views, &pipeInfo.viewports[0], pipeInfo.viewports.size() * sizeof(VkViewport));
vp.pViewports = &views[0];
vp.viewportCount = (uint32_t)pipeInfo.viewports.size();
memcpy(scissors, &pipeInfo.scissors[0], pipeInfo.scissors.size() * sizeof(VkRect2D));
vp.pScissors = &scissors[0];
vp.scissorCount = (uint32_t)pipeInfo.scissors.size();
RDCASSERT(ARRAY_COUNT(views) >= pipeInfo.viewports.size());
RDCASSERT(ARRAY_COUNT(scissors) >= pipeInfo.scissors.size());
static VkPipelineRasterizationStateCreateInfo rs = {
VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO};
rs.depthClampEnable = pipeInfo.depthClampEnable;
rs.rasterizerDiscardEnable = pipeInfo.rasterizerDiscardEnable,
rs.polygonMode = pipeInfo.polygonMode;
rs.cullMode = pipeInfo.cullMode;
rs.frontFace = pipeInfo.frontFace;
rs.depthBiasEnable = pipeInfo.depthBiasEnable;
rs.depthBiasConstantFactor = pipeInfo.depthBiasConstantFactor;
rs.depthBiasClamp = pipeInfo.depthBiasClamp;
rs.depthBiasSlopeFactor = pipeInfo.depthBiasSlopeFactor;
rs.lineWidth = pipeInfo.lineWidth;
static VkPipelineMultisampleStateCreateInfo msaa = {
VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO};
msaa.rasterizationSamples = pipeInfo.rasterizationSamples;
msaa.sampleShadingEnable = pipeInfo.sampleShadingEnable;
msaa.minSampleShading = pipeInfo.minSampleShading;
msaa.pSampleMask = &pipeInfo.sampleMask;
msaa.alphaToCoverageEnable = pipeInfo.alphaToCoverageEnable;
msaa.alphaToOneEnable = pipeInfo.alphaToOneEnable;
static VkPipelineDepthStencilStateCreateInfo ds = {
VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO};
ds.depthTestEnable = pipeInfo.depthTestEnable;
ds.depthWriteEnable = pipeInfo.depthWriteEnable;
ds.depthCompareOp = pipeInfo.depthCompareOp;
ds.depthBoundsTestEnable = pipeInfo.depthBoundsEnable;
ds.stencilTestEnable = pipeInfo.stencilTestEnable;
ds.front = pipeInfo.front;
ds.back = pipeInfo.back;
ds.minDepthBounds = pipeInfo.minDepthBounds;
ds.maxDepthBounds = pipeInfo.maxDepthBounds;
static VkPipelineColorBlendStateCreateInfo cb = {
VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO};
cb.logicOpEnable = pipeInfo.logicOpEnable;
cb.logicOp = pipeInfo.logicOp;
memcpy(cb.blendConstants, pipeInfo.blendConst, sizeof(cb.blendConstants));
static VkPipelineColorBlendAttachmentState atts[32] = {};
cb.attachmentCount = (uint32_t)pipeInfo.attachments.size();
cb.pAttachments = atts;
for(uint32_t i = 0; i < cb.attachmentCount; i++)
{
atts[i].blendEnable = pipeInfo.attachments[i].blendEnable;
atts[i].colorWriteMask = pipeInfo.attachments[i].channelWriteMask;
atts[i].alphaBlendOp = pipeInfo.attachments[i].alphaBlend.Operation;
atts[i].srcAlphaBlendFactor = pipeInfo.attachments[i].alphaBlend.Source;
atts[i].dstAlphaBlendFactor = pipeInfo.attachments[i].alphaBlend.Destination;
atts[i].colorBlendOp = pipeInfo.attachments[i].blend.Operation;
atts[i].srcColorBlendFactor = pipeInfo.attachments[i].blend.Source;
atts[i].dstColorBlendFactor = pipeInfo.attachments[i].blend.Destination;
}
RDCASSERT(ARRAY_COUNT(atts) >= pipeInfo.attachments.size());
static VkDynamicState dynSt[VK_DYNAMIC_STATE_RANGE_SIZE];
static VkPipelineDynamicStateCreateInfo dyn = {VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO};
dyn.dynamicStateCount = 0;
dyn.pDynamicStates = dynSt;
for(uint32_t i = 0; i < VK_DYNAMIC_STATE_RANGE_SIZE; i++)
if(pipeInfo.dynamicStates[i])
dynSt[dyn.dynamicStateCount++] = (VkDynamicState)i;
// since we don't have to worry about threading, we point everything at the above static structs
VkGraphicsPipelineCreateInfo ret = {
VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
NULL,
pipeInfo.flags,
stageCount,
stages,
&vi,
&ia,
&tess,
&vp,
&rs,
&msaa,
&ds,
&cb,
&dyn,
GetResourceManager()->GetCurrentHandle<VkPipelineLayout>(pipeInfo.layout),
GetResourceManager()->GetCurrentHandle<VkRenderPass>(pipeInfo.renderpass),
pipeInfo.subpass,
VK_NULL_HANDLE, // base pipeline handle
0, // base pipeline index
};
pipeCreateInfo = ret;
}
void VulkanDebugManager::PatchFixedColShader(VkShaderModule &mod, float col[4])
{
union
{
uint32_t *spirv;
float *data;
} alias;
vector<uint32_t> spv = *m_FixedColSPIRV;
alias.spirv = &spv[0];
size_t spirvLength = spv.size();
size_t it = 5;
while(it < spirvLength)
{
uint16_t WordCount = alias.spirv[it] >> spv::WordCountShift;
spv::Op opcode = spv::Op(alias.spirv[it] & spv::OpCodeMask);
if(opcode == spv::OpConstant)
{
if(alias.data[it + 3] == 1.1f)
alias.data[it + 3] = col[0];
else if(alias.data[it + 3] == 2.2f)
alias.data[it + 3] = col[1];
else if(alias.data[it + 3] == 3.3f)
alias.data[it + 3] = col[2];
else if(alias.data[it + 3] == 4.4f)
alias.data[it + 3] = col[3];
else
RDCERR("Unexpected constant value");
}
it += WordCount;
}
VkShaderModuleCreateInfo modinfo = {
VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
NULL,
0,
spv.size() * sizeof(uint32_t),
alias.spirv,
};
VkResult vkr = m_pDriver->vkCreateShaderModule(m_Device, &modinfo, NULL, &mod);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
}
struct QuadOverdrawCallback : public DrawcallCallback
{
QuadOverdrawCallback(WrappedVulkan *vk, const vector<uint32_t> &events)
: m_pDriver(vk), m_pDebug(vk->GetDebugManager()), m_Events(events), m_PrevState(NULL)
{
m_pDriver->SetDrawcallCB(this);
}
~QuadOverdrawCallback() { m_pDriver->SetDrawcallCB(NULL); }
void PreDraw(uint32_t eid, VkCommandBuffer cmd)
{
if(std::find(m_Events.begin(), m_Events.end(), eid) == m_Events.end())
return;
// we customise the pipeline to disable framebuffer writes, but perform normal testing
// and substitute our quad calculation fragment shader that writes to a storage image
// that is bound in a new descriptor set.
VkResult vkr = VK_SUCCESS;
m_PrevState = m_pDriver->GetRenderState();
VulkanRenderState &pipestate = m_pDriver->GetRenderState();
// check cache first
pair<uint32_t, VkPipeline> pipe = m_PipelineCache[pipestate.graphics.pipeline];
// if we don't get a hit, create a modified pipeline
if(pipe.second == VK_NULL_HANDLE)
{
VulkanCreationInfo &c = *pipestate.m_CreationInfo;
VulkanCreationInfo::Pipeline &p = c.m_Pipeline[pipestate.graphics.pipeline];
VkDescriptorSetLayout *descSetLayouts;
// descSet will be the index of our new descriptor set
uint32_t descSet = (uint32_t)c.m_PipelineLayout[p.layout].descSetLayouts.size();
descSetLayouts = new VkDescriptorSetLayout[descSet + 1];
for(uint32_t i = 0; i < descSet; i++)
descSetLayouts[i] = m_pDriver->GetResourceManager()->GetCurrentHandle<VkDescriptorSetLayout>(
c.m_PipelineLayout[p.layout].descSetLayouts[i]);
// this layout has storage image and
descSetLayouts[descSet] = m_pDebug->m_QuadDescSetLayout;
const vector<VkPushConstantRange> &push = c.m_PipelineLayout[p.layout].pushRanges;
VkPipelineLayoutCreateInfo pipeLayoutInfo = {
VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
NULL,
0,
descSet + 1,
descSetLayouts,
(uint32_t)push.size(),
push.empty() ? NULL : &push[0],
};
// create pipeline layout with same descriptor set layouts, plus our mesh output set
VkPipelineLayout pipeLayout;
vkr =
m_pDriver->vkCreatePipelineLayout(m_pDriver->GetDev(), &pipeLayoutInfo, NULL, &pipeLayout);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
SAFE_DELETE_ARRAY(descSetLayouts);
VkGraphicsPipelineCreateInfo pipeCreateInfo;
m_pDebug->MakeGraphicsPipelineInfo(pipeCreateInfo, pipestate.graphics.pipeline);
// repoint pipeline layout
pipeCreateInfo.layout = pipeLayout;
// disable colour writes/blends
VkPipelineColorBlendStateCreateInfo *cb =
(VkPipelineColorBlendStateCreateInfo *)pipeCreateInfo.pColorBlendState;
for(uint32_t i = 0; i < cb->attachmentCount; i++)
{
VkPipelineColorBlendAttachmentState *att =
(VkPipelineColorBlendAttachmentState *)&cb->pAttachments[i];
att->blendEnable = false;
att->colorWriteMask = 0x0;
}
// disable depth/stencil writes
VkPipelineDepthStencilStateCreateInfo *ds =
(VkPipelineDepthStencilStateCreateInfo *)pipeCreateInfo.pDepthStencilState;
ds->depthWriteEnable = false;
ds->stencilTestEnable = false;
ds->depthBoundsTestEnable = false;
ds->front.compareOp = ds->back.compareOp = VK_COMPARE_OP_ALWAYS;
ds->front.compareMask = ds->back.compareMask = ds->front.writeMask = ds->back.writeMask = 0xff;
ds->front.reference = ds->back.reference = 0;
ds->front.passOp = ds->front.failOp = ds->front.depthFailOp = VK_STENCIL_OP_KEEP;
ds->back.passOp = ds->back.failOp = ds->back.depthFailOp = VK_STENCIL_OP_KEEP;
// don't discard
VkPipelineRasterizationStateCreateInfo *rs =
(VkPipelineRasterizationStateCreateInfo *)pipeCreateInfo.pRasterizationState;
rs->rasterizerDiscardEnable = false;
vector<uint32_t> spirv = *m_pDebug->m_QuadSPIRV;
// patch spirv, change descriptor set to descSet value
size_t it = 5;
while(it < spirv.size())
{
uint16_t WordCount = spirv[it] >> spv::WordCountShift;
spv::Op opcode = spv::Op(spirv[it] & spv::OpCodeMask);
if(opcode == spv::OpDecorate && spirv[it + 2] == spv::DecorationDescriptorSet)
{
spirv[it + 3] = descSet;
break;
}
it += WordCount;
}
VkShaderModuleCreateInfo modinfo = {
VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
NULL,
0,
spirv.size() * sizeof(uint32_t),
&spirv[0],
};
VkShaderModule module;
VkDevice dev = m_pDriver->GetDev();
vkr = ObjDisp(dev)->CreateShaderModule(Unwrap(dev), &modinfo, NULL, &module);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
m_pDriver->GetResourceManager()->WrapResource(Unwrap(dev), module);
bool found = false;
for(uint32_t i = 0; i < pipeCreateInfo.stageCount; i++)
{
VkPipelineShaderStageCreateInfo &sh =
(VkPipelineShaderStageCreateInfo &)pipeCreateInfo.pStages[i];
if(sh.stage == VK_SHADER_STAGE_FRAGMENT_BIT)
{
sh.module = module;
sh.pName = "main";
found = true;
break;
}
}
if(!found)
{
// we know this is safe because it's pointing to a static array that's
// big enough for all shaders
VkPipelineShaderStageCreateInfo &sh =
(VkPipelineShaderStageCreateInfo &)pipeCreateInfo.pStages[pipeCreateInfo.stageCount++];
sh.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
sh.pNext = NULL;
sh.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
sh.module = module;
sh.pName = "main";
sh.pSpecializationInfo = NULL;
}
vkr = m_pDriver->vkCreateGraphicsPipelines(dev, VK_NULL_HANDLE, 1, &pipeCreateInfo, NULL,
&pipe.second);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
ObjDisp(dev)->DestroyShaderModule(Unwrap(dev), Unwrap(module), NULL);
m_pDriver->GetResourceManager()->ReleaseWrappedResource(module);
pipe.first = descSet;
m_PipelineCache[pipestate.graphics.pipeline] = pipe;
}
// modify state for first draw call
pipestate.graphics.pipeline = GetResID(pipe.second);
RDCASSERT(pipestate.graphics.descSets.size() >= pipe.first);
pipestate.graphics.descSets.resize(pipe.first + 1);
pipestate.graphics.descSets[pipe.first].descSet = GetResID(m_pDebug->m_QuadDescSet);
if(cmd)
pipestate.BindPipeline(cmd);
}
bool PostDraw(uint32_t eid, VkCommandBuffer cmd)
{
if(std::find(m_Events.begin(), m_Events.end(), eid) == m_Events.end())
return false;
// restore the render state and go ahead with the real draw
m_pDriver->GetRenderState() = m_PrevState;
RDCASSERT(cmd);
m_pDriver->GetRenderState().BindPipeline(cmd);
return true;
}
void PostRedraw(uint32_t eid, VkCommandBuffer cmd)
{
// nothing to do
}
// Dispatches don't rasterize, so do nothing
void PreDispatch(uint32_t eid, VkCommandBuffer cmd) {}
bool PostDispatch(uint32_t eid, VkCommandBuffer cmd) { return false; }
void PostRedispatch(uint32_t eid, VkCommandBuffer cmd) {}
bool RecordAllCmds() { return false; }
void AliasEvent(uint32_t primary, uint32_t alias)
{
// don't care
}
WrappedVulkan *m_pDriver;
VulkanDebugManager *m_pDebug;
const vector<uint32_t> &m_Events;
// cache modified pipelines
map<ResourceId, pair<uint32_t, VkPipeline> > m_PipelineCache;
VulkanRenderState m_PrevState;
};
ResourceId VulkanDebugManager::RenderOverlay(ResourceId texid, TextureDisplayOverlay overlay,
uint32_t eventID, const vector<uint32_t> &passEvents)
{
const VkLayerDispatchTable *vt = ObjDisp(m_Device);
VulkanCreationInfo::Image &iminfo = m_pDriver->m_CreationInfo.m_Image[texid];
VkCommandBuffer cmd = m_pDriver->GetNextCmd();
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 the overlay image is the wrong size, free it
if(m_OverlayImage != VK_NULL_HANDLE &&
(iminfo.extent.width != m_OverlayDim.width || iminfo.extent.height != m_OverlayDim.height))
{
m_pDriver->vkDestroyRenderPass(m_Device, m_OverlayNoDepthRP, NULL);
m_pDriver->vkDestroyFramebuffer(m_Device, m_OverlayNoDepthFB, NULL);
m_pDriver->vkDestroyImageView(m_Device, m_OverlayImageView, NULL);
m_pDriver->vkDestroyImage(m_Device, m_OverlayImage, NULL);
m_OverlayImage = VK_NULL_HANDLE;
m_OverlayImageView = VK_NULL_HANDLE;
m_OverlayNoDepthRP = VK_NULL_HANDLE;
m_OverlayNoDepthFB = VK_NULL_HANDLE;
}
// create the overlay image if we don't have one already
// we go through the driver's creation functions so creation info
// is saved and the resources are registered as live resources for
// their IDs.
if(m_OverlayImage == VK_NULL_HANDLE)
{
m_OverlayDim.width = iminfo.extent.width;
m_OverlayDim.height = iminfo.extent.height;
VkImageCreateInfo imInfo = {
VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
NULL,
0,
VK_IMAGE_TYPE_2D,
VK_FORMAT_R16G16B16A16_SFLOAT,
{m_OverlayDim.width, m_OverlayDim.height, 1},
1,
1,
iminfo.samples,
VK_IMAGE_TILING_OPTIMAL,
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT |
VK_IMAGE_USAGE_TRANSFER_SRC_BIT,
VK_SHARING_MODE_EXCLUSIVE,
0,
NULL,
VK_IMAGE_LAYOUT_UNDEFINED,
};
vkr = m_pDriver->vkCreateImage(m_Device, &imInfo, NULL, &m_OverlayImage);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
VkMemoryRequirements mrq = {0};
m_pDriver->vkGetImageMemoryRequirements(m_Device, m_OverlayImage, &mrq);
// if no memory is allocated, or it's not enough,
// then allocate
if(m_OverlayImageMem == VK_NULL_HANDLE || mrq.size > m_OverlayMemSize)
{
if(m_OverlayImageMem != VK_NULL_HANDLE)
{
m_pDriver->vkFreeMemory(m_Device, m_OverlayImageMem, NULL);
}
VkMemoryAllocateInfo allocInfo = {
VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, NULL, mrq.size,
m_pDriver->GetGPULocalMemoryIndex(mrq.memoryTypeBits),
};
vkr = m_pDriver->vkAllocateMemory(m_Device, &allocInfo, NULL, &m_OverlayImageMem);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
m_OverlayMemSize = mrq.size;
}
vkr = m_pDriver->vkBindImageMemory(m_Device, m_OverlayImage, m_OverlayImageMem, 0);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
VkImageViewCreateInfo viewInfo = {
VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
NULL,
0,
m_OverlayImage,
VK_IMAGE_VIEW_TYPE_2D,
imInfo.format,
{VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY,
VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY},
{
VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1,
},
};
vkr = m_pDriver->vkCreateImageView(m_Device, &viewInfo, NULL, &m_OverlayImageView);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
// need to update image layout into valid state
VkImageMemoryBarrier barrier = {
VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
NULL,
0,
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
0,
0, // MULTIDEVICE - need to actually pick the right queue family here maybe?
Unwrap(m_OverlayImage),
{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
m_pDriver->m_ImageLayouts[GetResID(m_OverlayImage)].subresourceStates[0].newLayout =
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
DoPipelineBarrier(cmd, 1, &barrier);
VkAttachmentDescription colDesc = {0,
imInfo.format,
imInfo.samples,
VK_ATTACHMENT_LOAD_OP_LOAD,
VK_ATTACHMENT_STORE_OP_STORE,
VK_ATTACHMENT_LOAD_OP_DONT_CARE,
VK_ATTACHMENT_STORE_OP_DONT_CARE,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
VkAttachmentReference colRef = {0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
VkSubpassDescription sub = {
0, VK_PIPELINE_BIND_POINT_GRAPHICS,
0, NULL, // inputs
1, &colRef, // color
NULL, // resolve
NULL, // depth-stencil
0, NULL, // preserve
};
VkRenderPassCreateInfo rpinfo = {
VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
NULL,
0,
1,
&colDesc,
1,
&sub,
0,
NULL, // dependencies
};
vkr = m_pDriver->vkCreateRenderPass(m_Device, &rpinfo, NULL, &m_OverlayNoDepthRP);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
// Create framebuffer rendering just to overlay image, no depth
VkFramebufferCreateInfo fbinfo = {
VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
NULL,
0,
m_OverlayNoDepthRP,
1,
&m_OverlayImageView,
(uint32_t)m_OverlayDim.width,
(uint32_t)m_OverlayDim.height,
1,
};
vkr = m_pDriver->vkCreateFramebuffer(m_Device, &fbinfo, NULL, &m_OverlayNoDepthFB);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
// can't create a framebuffer or renderpass for overlay image + depth as that
// needs to match the depth texture type wherever our draw is.
}
VkImageSubresourceRange subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
if(!m_pDriver->m_PartialReplayData.renderPassActive)
{
// don't do anything, no drawcall capable of making overlays selected
float black[] = {0.0f, 0.0f, 0.0f, 0.0f};
VkImageMemoryBarrier barrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
NULL,
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_ACCESS_TRANSFER_WRITE_BIT,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_QUEUE_FAMILY_IGNORED,
VK_QUEUE_FAMILY_IGNORED,
Unwrap(m_OverlayImage),
{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
DoPipelineBarrier(cmd, 1, &barrier);
vt->CmdClearColorImage(Unwrap(cmd), Unwrap(m_OverlayImage), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
(VkClearColorValue *)black, 1, &subresourceRange);
std::swap(barrier.oldLayout, barrier.newLayout);
std::swap(barrier.srcAccessMask, barrier.dstAccessMask);
barrier.dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT;
DoPipelineBarrier(cmd, 1, &barrier);
}
else if(overlay == eTexOverlay_NaN || overlay == eTexOverlay_Clipping)
{
float black[] = {0.0f, 0.0f, 0.0f, 0.0f};
VkImageMemoryBarrier barrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
NULL,
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_ACCESS_TRANSFER_WRITE_BIT,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_QUEUE_FAMILY_IGNORED,
VK_QUEUE_FAMILY_IGNORED,
Unwrap(m_OverlayImage),
{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
DoPipelineBarrier(cmd, 1, &barrier);
vt->CmdClearColorImage(Unwrap(cmd), Unwrap(m_OverlayImage), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
(VkClearColorValue *)black, 1, &subresourceRange);
std::swap(barrier.oldLayout, barrier.newLayout);
std::swap(barrier.srcAccessMask, barrier.dstAccessMask);
barrier.dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT;
DoPipelineBarrier(cmd, 1, &barrier);
}
else if(overlay == eTexOverlay_Drawcall || overlay == eTexOverlay_Wireframe)
{
float highlightCol[] = {0.8f, 0.1f, 0.8f, 0.0f};
if(overlay == eTexOverlay_Wireframe)
{
highlightCol[0] = 200 / 255.0f;
highlightCol[1] = 1.0f;
highlightCol[2] = 0.0f;
}
VkImageMemoryBarrier barrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
NULL,
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_ACCESS_TRANSFER_WRITE_BIT,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_QUEUE_FAMILY_IGNORED,
VK_QUEUE_FAMILY_IGNORED,
Unwrap(m_OverlayImage),
{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
DoPipelineBarrier(cmd, 1, &barrier);
vt->CmdClearColorImage(Unwrap(cmd), Unwrap(m_OverlayImage), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
(VkClearColorValue *)highlightCol, 1, &subresourceRange);
std::swap(barrier.oldLayout, barrier.newLayout);
std::swap(barrier.srcAccessMask, barrier.dstAccessMask);
barrier.dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT;
DoPipelineBarrier(cmd, 1, &barrier);
highlightCol[3] = 1.0f;
// backup state
VulkanRenderState prevstate = m_pDriver->m_RenderState;
// make patched shader
VkShaderModule mod = VK_NULL_HANDLE;
PatchFixedColShader(mod, highlightCol);
// make patched pipeline
VkGraphicsPipelineCreateInfo pipeCreateInfo;
MakeGraphicsPipelineInfo(pipeCreateInfo, prevstate.graphics.pipeline);
// disable all tests possible
VkPipelineDepthStencilStateCreateInfo *ds =
(VkPipelineDepthStencilStateCreateInfo *)pipeCreateInfo.pDepthStencilState;
ds->depthTestEnable = false;
ds->depthWriteEnable = false;
ds->stencilTestEnable = false;
ds->depthBoundsTestEnable = false;
VkPipelineRasterizationStateCreateInfo *rs =
(VkPipelineRasterizationStateCreateInfo *)pipeCreateInfo.pRasterizationState;
rs->cullMode = VK_CULL_MODE_NONE;
rs->rasterizerDiscardEnable = false;
rs->depthClampEnable = true;
if(overlay == eTexOverlay_Wireframe && m_pDriver->GetDeviceFeatures().fillModeNonSolid)
{
rs->polygonMode = VK_POLYGON_MODE_LINE;
rs->lineWidth = 1.0f;
}
VkPipelineColorBlendStateCreateInfo *cb =
(VkPipelineColorBlendStateCreateInfo *)pipeCreateInfo.pColorBlendState;
cb->logicOpEnable = false;
cb->attachmentCount = 1; // only one colour attachment
for(uint32_t i = 0; i < cb->attachmentCount; i++)
{
VkPipelineColorBlendAttachmentState *att =
(VkPipelineColorBlendAttachmentState *)&cb->pAttachments[i];
att->blendEnable = false;
att->colorWriteMask = 0xf;
}
// set scissors to max
for(size_t i = 0; i < pipeCreateInfo.pViewportState->scissorCount; i++)
{
VkRect2D &sc = (VkRect2D &)pipeCreateInfo.pViewportState->pScissors[i];
sc.offset.x = 0;
sc.offset.y = 0;
sc.extent.width = 4096;
sc.extent.height = 4096;
}
// set our renderpass and shader
pipeCreateInfo.renderPass = m_OverlayNoDepthRP;
bool found = false;
for(uint32_t i = 0; i < pipeCreateInfo.stageCount; i++)
{
VkPipelineShaderStageCreateInfo &sh =
(VkPipelineShaderStageCreateInfo &)pipeCreateInfo.pStages[i];
if(sh.stage == VK_SHADER_STAGE_FRAGMENT_BIT)
{
sh.module = mod;
sh.pName = "main";
found = true;
break;
}
}
if(!found)
{
// we know this is safe because it's pointing to a static array that's
// big enough for all shaders
VkPipelineShaderStageCreateInfo &sh =
(VkPipelineShaderStageCreateInfo &)pipeCreateInfo.pStages[pipeCreateInfo.stageCount++];
sh.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
sh.pNext = NULL;
sh.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
sh.module = mod;
sh.pName = "main";
sh.pSpecializationInfo = NULL;
}
vkr = vt->EndCommandBuffer(Unwrap(cmd));
RDCASSERTEQUAL(vkr, VK_SUCCESS);
VkPipeline pipe = VK_NULL_HANDLE;
vkr = m_pDriver->vkCreateGraphicsPipelines(m_Device, VK_NULL_HANDLE, 1, &pipeCreateInfo, NULL,
&pipe);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
// modify state
m_pDriver->m_RenderState.renderPass = GetResID(m_OverlayNoDepthRP);
m_pDriver->m_RenderState.subpass = 0;
m_pDriver->m_RenderState.framebuffer = GetResID(m_OverlayNoDepthFB);
m_pDriver->m_RenderState.graphics.pipeline = GetResID(pipe);
// set dynamic scissors in case pipeline was using them
for(size_t i = 0; i < m_pDriver->m_RenderState.scissors.size(); i++)
{
m_pDriver->m_RenderState.scissors[i].offset.x = 0;
m_pDriver->m_RenderState.scissors[i].offset.x = 0;
m_pDriver->m_RenderState.scissors[i].extent.width = 4096;
m_pDriver->m_RenderState.scissors[i].extent.height = 4096;
}
if(overlay == eTexOverlay_Wireframe)
m_pDriver->m_RenderState.lineWidth = 1.0f;
m_pDriver->ReplayLog(0, eventID, eReplay_OnlyDraw);
// submit & flush so that we don't have to keep pipeline around for a while
m_pDriver->SubmitCmds();
m_pDriver->FlushQ();
cmd = m_pDriver->GetNextCmd();
vkr = vt->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
// restore state
m_pDriver->m_RenderState = prevstate;
m_pDriver->vkDestroyPipeline(m_Device, pipe, NULL);
m_pDriver->vkDestroyShaderModule(m_Device, mod, NULL);
}
else if(overlay == eTexOverlay_ViewportScissor)
{
// clear the whole image to opaque black. We'll overwite the render area with transparent black
// before rendering the viewport/scissors
float black[] = {0.0f, 0.0f, 0.0f, 1.0f};
VkImageMemoryBarrier barrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
NULL,
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_ACCESS_TRANSFER_WRITE_BIT,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_QUEUE_FAMILY_IGNORED,
VK_QUEUE_FAMILY_IGNORED,
Unwrap(m_OverlayImage),
{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
DoPipelineBarrier(cmd, 1, &barrier);
vt->CmdClearColorImage(Unwrap(cmd), Unwrap(m_OverlayImage), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
(VkClearColorValue *)black, 1, &subresourceRange);
std::swap(barrier.oldLayout, barrier.newLayout);
std::swap(barrier.srcAccessMask, barrier.dstAccessMask);
barrier.dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT;
DoPipelineBarrier(cmd, 1, &barrier);
black[3] = 0.0f;
{
VkClearValue clearval = {};
VkRenderPassBeginInfo rpbegin = {
VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
NULL,
Unwrap(m_OverlayNoDepthRP),
Unwrap(m_OverlayNoDepthFB),
m_pDriver->m_RenderState.renderArea,
1,
&clearval,
};
vt->CmdBeginRenderPass(Unwrap(cmd), &rpbegin, VK_SUBPASS_CONTENTS_INLINE);
VkClearRect rect = {
{{
m_pDriver->m_RenderState.renderArea.offset.x,
m_pDriver->m_RenderState.renderArea.offset.y,
},
{
m_pDriver->m_RenderState.renderArea.extent.width,
m_pDriver->m_RenderState.renderArea.extent.height,
}},
0,
1,
};
VkClearAttachment blackclear = {VK_IMAGE_ASPECT_COLOR_BIT, 0, {}};
vt->CmdClearAttachments(Unwrap(cmd), 1, &blackclear, 1, &rect);
VkViewport viewport = m_pDriver->m_RenderState.views[0];
vt->CmdSetViewport(Unwrap(cmd), 0, 1, &viewport);
uint32_t uboOffs = 0;
OutlineUBOData *ubo = (OutlineUBOData *)m_OutlineUBO.Map(&uboOffs);
ubo->Inner_Color = Vec4f(0.2f, 0.2f, 0.9f, 0.7f);
ubo->Border_Color = Vec4f(0.1f, 0.1f, 0.1f, 1.0f);
ubo->Scissor = 0;
ubo->ViewRect = (Vec4f &)viewport;
m_OutlineUBO.Unmap();
vt->CmdBindPipeline(Unwrap(cmd), VK_PIPELINE_BIND_POINT_GRAPHICS,
Unwrap(m_OutlinePipeline[SampleIndex(iminfo.samples)]));
vt->CmdBindDescriptorSets(Unwrap(cmd), VK_PIPELINE_BIND_POINT_GRAPHICS,
Unwrap(m_OutlinePipeLayout), 0, 1, UnwrapPtr(m_OutlineDescSet), 1,
&uboOffs);
vt->CmdDraw(Unwrap(cmd), 4, 1, 0, 0);
if(!m_pDriver->m_RenderState.scissors.empty())
{
Vec4f scissor((float)m_pDriver->m_RenderState.scissors[0].offset.x,
(float)m_pDriver->m_RenderState.scissors[0].offset.y,
(float)m_pDriver->m_RenderState.scissors[0].extent.width,
(float)m_pDriver->m_RenderState.scissors[0].extent.height);
ubo = (OutlineUBOData *)m_OutlineUBO.Map(&uboOffs);
ubo->Inner_Color = Vec4f(0.2f, 0.2f, 0.9f, 0.7f);
ubo->Border_Color = Vec4f(0.1f, 0.1f, 0.1f, 1.0f);
ubo->Scissor = 1;
ubo->ViewRect = scissor;
m_OutlineUBO.Unmap();
viewport.x = scissor.x;
viewport.y = scissor.y;
viewport.width = scissor.z;
viewport.height = scissor.w;
vt->CmdSetViewport(Unwrap(cmd), 0, 1, &viewport);
vt->CmdBindDescriptorSets(Unwrap(cmd), VK_PIPELINE_BIND_POINT_GRAPHICS,
Unwrap(m_OutlinePipeLayout), 0, 1, UnwrapPtr(m_OutlineDescSet), 1,
&uboOffs);
vt->CmdDraw(Unwrap(cmd), 4, 1, 0, 0);
}
vt->CmdEndRenderPass(Unwrap(cmd));
}
}
else if(overlay == eTexOverlay_BackfaceCull)
{
float highlightCol[] = {0.0f, 0.0f, 0.0f, 0.0f};
VkImageMemoryBarrier barrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
NULL,
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_ACCESS_TRANSFER_WRITE_BIT,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_QUEUE_FAMILY_IGNORED,
VK_QUEUE_FAMILY_IGNORED,
Unwrap(m_OverlayImage),
{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
DoPipelineBarrier(cmd, 1, &barrier);
vt->CmdClearColorImage(Unwrap(cmd), Unwrap(m_OverlayImage), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
(VkClearColorValue *)highlightCol, 1, &subresourceRange);
std::swap(barrier.oldLayout, barrier.newLayout);
std::swap(barrier.srcAccessMask, barrier.dstAccessMask);
barrier.dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT;
DoPipelineBarrier(cmd, 1, &barrier);
highlightCol[0] = 1.0f;
highlightCol[3] = 1.0f;
// backup state
VulkanRenderState prevstate = m_pDriver->m_RenderState;
// make patched shader
VkShaderModule mod[2] = {0};
VkPipeline pipe[2] = {0};
// first shader, no culling, writes red
PatchFixedColShader(mod[0], highlightCol);
highlightCol[0] = 0.0f;
highlightCol[1] = 1.0f;
// second shader, normal culling, writes green
PatchFixedColShader(mod[1], highlightCol);
// make patched pipeline
VkGraphicsPipelineCreateInfo pipeCreateInfo;
MakeGraphicsPipelineInfo(pipeCreateInfo, prevstate.graphics.pipeline);
// disable all tests possible
VkPipelineDepthStencilStateCreateInfo *ds =
(VkPipelineDepthStencilStateCreateInfo *)pipeCreateInfo.pDepthStencilState;
ds->depthTestEnable = false;
ds->depthWriteEnable = false;
ds->stencilTestEnable = false;
ds->depthBoundsTestEnable = false;
VkPipelineRasterizationStateCreateInfo *rs =
(VkPipelineRasterizationStateCreateInfo *)pipeCreateInfo.pRasterizationState;
VkCullModeFlags origCullMode = rs->cullMode;
rs->cullMode = VK_CULL_MODE_NONE; // first render without any culling
rs->rasterizerDiscardEnable = false;
rs->depthClampEnable = true;
VkPipelineColorBlendStateCreateInfo *cb =
(VkPipelineColorBlendStateCreateInfo *)pipeCreateInfo.pColorBlendState;
cb->logicOpEnable = false;
cb->attachmentCount = 1; // only one colour attachment
for(uint32_t i = 0; i < cb->attachmentCount; i++)
{
VkPipelineColorBlendAttachmentState *att =
(VkPipelineColorBlendAttachmentState *)&cb->pAttachments[i];
att->blendEnable = false;
att->colorWriteMask = 0xf;
}
// set scissors to max
for(size_t i = 0; i < pipeCreateInfo.pViewportState->scissorCount; i++)
{
VkRect2D &sc = (VkRect2D &)pipeCreateInfo.pViewportState->pScissors[i];
sc.offset.x = 0;
sc.offset.y = 0;
sc.extent.width = 4096;
sc.extent.height = 4096;
}
// set our renderpass and shader
pipeCreateInfo.renderPass = m_OverlayNoDepthRP;
VkPipelineShaderStageCreateInfo *fragShader = NULL;
for(uint32_t i = 0; i < pipeCreateInfo.stageCount; i++)
{
VkPipelineShaderStageCreateInfo &sh =
(VkPipelineShaderStageCreateInfo &)pipeCreateInfo.pStages[i];
if(sh.stage == VK_SHADER_STAGE_FRAGMENT_BIT)
{
sh.module = mod[0];
sh.pName = "main";
fragShader = &sh;
break;
}
}
if(fragShader == NULL)
{
// we know this is safe because it's pointing to a static array that's
// big enough for all shaders
VkPipelineShaderStageCreateInfo &sh =
(VkPipelineShaderStageCreateInfo &)pipeCreateInfo.pStages[pipeCreateInfo.stageCount++];
sh.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
sh.pNext = NULL;
sh.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
sh.module = mod[0];
sh.pName = "main";
sh.pSpecializationInfo = NULL;
fragShader = &sh;
}
vkr = vt->EndCommandBuffer(Unwrap(cmd));
RDCASSERTEQUAL(vkr, VK_SUCCESS);
vkr = m_pDriver->vkCreateGraphicsPipelines(m_Device, VK_NULL_HANDLE, 1, &pipeCreateInfo, NULL,
&pipe[0]);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
fragShader->module = mod[1];
rs->cullMode = origCullMode;
vkr = m_pDriver->vkCreateGraphicsPipelines(m_Device, VK_NULL_HANDLE, 1, &pipeCreateInfo, NULL,
&pipe[1]);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
// modify state
m_pDriver->m_RenderState.renderPass = GetResID(m_OverlayNoDepthRP);
m_pDriver->m_RenderState.subpass = 0;
m_pDriver->m_RenderState.framebuffer = GetResID(m_OverlayNoDepthFB);
m_pDriver->m_RenderState.graphics.pipeline = GetResID(pipe[0]);
// set dynamic scissors in case pipeline was using them
for(size_t i = 0; i < m_pDriver->m_RenderState.scissors.size(); i++)
{
m_pDriver->m_RenderState.scissors[i].offset.x = 0;
m_pDriver->m_RenderState.scissors[i].offset.x = 0;
m_pDriver->m_RenderState.scissors[i].extent.width = 4096;
m_pDriver->m_RenderState.scissors[i].extent.height = 4096;
}
m_pDriver->ReplayLog(0, eventID, eReplay_OnlyDraw);
m_pDriver->m_RenderState.graphics.pipeline = GetResID(pipe[1]);
m_pDriver->ReplayLog(0, eventID, eReplay_OnlyDraw);
// submit & flush so that we don't have to keep pipeline around for a while
m_pDriver->SubmitCmds();
m_pDriver->FlushQ();
cmd = m_pDriver->GetNextCmd();
vkr = vt->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
// restore state
m_pDriver->m_RenderState = prevstate;
for(int i = 0; i < 2; i++)
{
m_pDriver->vkDestroyPipeline(m_Device, pipe[i], NULL);
m_pDriver->vkDestroyShaderModule(m_Device, mod[i], NULL);
}
}
else if(overlay == eTexOverlay_Depth || overlay == eTexOverlay_Stencil)
{
float highlightCol[] = {0.0f, 0.0f, 0.0f, 0.0f};
VkImageMemoryBarrier barrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
NULL,
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_ACCESS_TRANSFER_WRITE_BIT,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_QUEUE_FAMILY_IGNORED,
VK_QUEUE_FAMILY_IGNORED,
Unwrap(m_OverlayImage),
{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
DoPipelineBarrier(cmd, 1, &barrier);
vt->CmdClearColorImage(Unwrap(cmd), Unwrap(m_OverlayImage), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
(VkClearColorValue *)highlightCol, 1, &subresourceRange);
std::swap(barrier.oldLayout, barrier.newLayout);
std::swap(barrier.srcAccessMask, barrier.dstAccessMask);
barrier.dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT;
DoPipelineBarrier(cmd, 1, &barrier);
VkFramebuffer depthFB = VK_NULL_HANDLE;
VkRenderPass depthRP = VK_NULL_HANDLE;
const VulkanRenderState &state = m_pDriver->m_RenderState;
VulkanCreationInfo &createinfo = m_pDriver->m_CreationInfo;
RDCASSERT(state.subpass < createinfo.m_RenderPass[state.renderPass].subpasses.size());
int32_t dsIdx =
createinfo.m_RenderPass[state.renderPass].subpasses[state.subpass].depthstencilAttachment;
// make a renderpass and framebuffer for rendering to overlay color and using
// depth buffer from the orignial render
if(dsIdx >= 0 && dsIdx < (int32_t)createinfo.m_Framebuffer[state.framebuffer].attachments.size())
{
VkAttachmentDescription attDescs[] = {
{0, VK_FORMAT_R16G16B16A16_SFLOAT, VK_SAMPLE_COUNT_1_BIT, VK_ATTACHMENT_LOAD_OP_LOAD,
VK_ATTACHMENT_STORE_OP_STORE, VK_ATTACHMENT_LOAD_OP_DONT_CARE,
VK_ATTACHMENT_STORE_OP_DONT_CARE, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL},
{0, VK_FORMAT_UNDEFINED, VK_SAMPLE_COUNT_1_BIT, // will patch this just below
VK_ATTACHMENT_LOAD_OP_LOAD, VK_ATTACHMENT_STORE_OP_STORE, VK_ATTACHMENT_LOAD_OP_LOAD,
VK_ATTACHMENT_STORE_OP_STORE, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL},
};
ResourceId depthView = createinfo.m_Framebuffer[state.framebuffer].attachments[dsIdx].view;
ResourceId depthIm = createinfo.m_ImageView[depthView].image;
attDescs[1].format = createinfo.m_Image[depthIm].format;
attDescs[0].samples = attDescs[1].samples = iminfo.samples;
VkAttachmentReference colRef = {0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
VkAttachmentReference dsRef = {1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL};
VkSubpassDescription sub = {
0, VK_PIPELINE_BIND_POINT_GRAPHICS,
0, NULL, // inputs
1, &colRef, // color
NULL, // resolve
&dsRef, // depth-stencil
0, NULL, // preserve
};
VkRenderPassCreateInfo rpinfo = {
VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
NULL,
0,
2,
attDescs,
1,
&sub,
0,
NULL, // dependencies
};
vkr = m_pDriver->vkCreateRenderPass(m_Device, &rpinfo, NULL, &depthRP);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
VkImageView views[] = {
m_OverlayImageView, GetResourceManager()->GetCurrentHandle<VkImageView>(depthView),
};
// Create framebuffer rendering just to overlay image, no depth
VkFramebufferCreateInfo fbinfo = {
VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
NULL,
0,
depthRP,
2,
views,
(uint32_t)m_OverlayDim.width,
(uint32_t)m_OverlayDim.height,
1,
};
vkr = m_pDriver->vkCreateFramebuffer(m_Device, &fbinfo, NULL, &depthFB);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
}
// if depthRP is NULL, so is depthFB, and it means no depth buffer was
// bound, so we just render green.
highlightCol[0] = 1.0f;
highlightCol[3] = 1.0f;
// backup state
VulkanRenderState prevstate = m_pDriver->m_RenderState;
// make patched shader
VkShaderModule mod[2] = {0};
VkPipeline pipe[2] = {0};
// first shader, no depth testing, writes red
PatchFixedColShader(mod[0], highlightCol);
highlightCol[0] = 0.0f;
highlightCol[1] = 1.0f;
// second shader, enabled depth testing, writes green
PatchFixedColShader(mod[1], highlightCol);
// make patched pipeline
VkGraphicsPipelineCreateInfo pipeCreateInfo;
MakeGraphicsPipelineInfo(pipeCreateInfo, prevstate.graphics.pipeline);
// disable all tests possible
VkPipelineDepthStencilStateCreateInfo *ds =
(VkPipelineDepthStencilStateCreateInfo *)pipeCreateInfo.pDepthStencilState;
VkBool32 origDepthTest = ds->depthTestEnable;
ds->depthTestEnable = false;
ds->depthWriteEnable = false;
VkBool32 origStencilTest = ds->stencilTestEnable;
ds->stencilTestEnable = false;
ds->depthBoundsTestEnable = false;
VkPipelineRasterizationStateCreateInfo *rs =
(VkPipelineRasterizationStateCreateInfo *)pipeCreateInfo.pRasterizationState;
rs->cullMode = VK_CULL_MODE_NONE;
rs->rasterizerDiscardEnable = false;
rs->depthClampEnable = true;
VkPipelineColorBlendStateCreateInfo *cb =
(VkPipelineColorBlendStateCreateInfo *)pipeCreateInfo.pColorBlendState;
cb->logicOpEnable = false;
cb->attachmentCount = 1; // only one colour attachment
for(uint32_t i = 0; i < cb->attachmentCount; i++)
{
VkPipelineColorBlendAttachmentState *att =
(VkPipelineColorBlendAttachmentState *)&cb->pAttachments[i];
att->blendEnable = false;
att->colorWriteMask = 0xf;
}
// set scissors to max
for(size_t i = 0; i < pipeCreateInfo.pViewportState->scissorCount; i++)
{
VkRect2D &sc = (VkRect2D &)pipeCreateInfo.pViewportState->pScissors[i];
sc.offset.x = 0;
sc.offset.y = 0;
sc.extent.width = 4096;
sc.extent.height = 4096;
}
// set our renderpass and shader
pipeCreateInfo.renderPass = m_OverlayNoDepthRP;
VkPipelineShaderStageCreateInfo *fragShader = NULL;
for(uint32_t i = 0; i < pipeCreateInfo.stageCount; i++)
{
VkPipelineShaderStageCreateInfo &sh =
(VkPipelineShaderStageCreateInfo &)pipeCreateInfo.pStages[i];
if(sh.stage == VK_SHADER_STAGE_FRAGMENT_BIT)
{
sh.module = mod[0];
sh.pName = "main";
fragShader = &sh;
break;
}
}
if(fragShader == NULL)
{
// we know this is safe because it's pointing to a static array that's
// big enough for all shaders
VkPipelineShaderStageCreateInfo &sh =
(VkPipelineShaderStageCreateInfo &)pipeCreateInfo.pStages[pipeCreateInfo.stageCount++];
sh.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
sh.pNext = NULL;
sh.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
sh.module = mod[0];
sh.pName = "main";
sh.pSpecializationInfo = NULL;
fragShader = &sh;
}
vkr = vt->EndCommandBuffer(Unwrap(cmd));
RDCASSERTEQUAL(vkr, VK_SUCCESS);
vkr = m_pDriver->vkCreateGraphicsPipelines(m_Device, VK_NULL_HANDLE, 1, &pipeCreateInfo, NULL,
&pipe[0]);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
fragShader->module = mod[1];
if(depthRP != VK_NULL_HANDLE)
{
if(overlay == eTexOverlay_Depth)
ds->depthTestEnable = origDepthTest;
else
ds->stencilTestEnable = origStencilTest;
pipeCreateInfo.renderPass = depthRP;
}
vkr = m_pDriver->vkCreateGraphicsPipelines(m_Device, VK_NULL_HANDLE, 1, &pipeCreateInfo, NULL,
&pipe[1]);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
// modify state
m_pDriver->m_RenderState.renderPass = GetResID(m_OverlayNoDepthRP);
m_pDriver->m_RenderState.subpass = 0;
m_pDriver->m_RenderState.framebuffer = GetResID(m_OverlayNoDepthFB);
m_pDriver->m_RenderState.graphics.pipeline = GetResID(pipe[0]);
// set dynamic scissors in case pipeline was using them
for(size_t i = 0; i < m_pDriver->m_RenderState.scissors.size(); i++)
{
m_pDriver->m_RenderState.scissors[i].offset.x = 0;
m_pDriver->m_RenderState.scissors[i].offset.x = 0;
m_pDriver->m_RenderState.scissors[i].extent.width = 4096;
m_pDriver->m_RenderState.scissors[i].extent.height = 4096;
}
m_pDriver->ReplayLog(0, eventID, eReplay_OnlyDraw);
m_pDriver->m_RenderState.graphics.pipeline = GetResID(pipe[1]);
if(depthRP != VK_NULL_HANDLE)
{
m_pDriver->m_RenderState.renderPass = GetResID(depthRP);
m_pDriver->m_RenderState.framebuffer = GetResID(depthFB);
}
m_pDriver->ReplayLog(0, eventID, eReplay_OnlyDraw);
// submit & flush so that we don't have to keep pipeline around for a while
m_pDriver->SubmitCmds();
m_pDriver->FlushQ();
cmd = m_pDriver->GetNextCmd();
vkr = vt->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
// restore state
m_pDriver->m_RenderState = prevstate;
for(int i = 0; i < 2; i++)
{
m_pDriver->vkDestroyPipeline(m_Device, pipe[i], NULL);
m_pDriver->vkDestroyShaderModule(m_Device, mod[i], NULL);
}
if(depthRP != VK_NULL_HANDLE)
{
m_pDriver->vkDestroyRenderPass(m_Device, depthRP, NULL);
m_pDriver->vkDestroyFramebuffer(m_Device, depthFB, NULL);
}
}
else if(overlay == eTexOverlay_ClearBeforeDraw || overlay == eTexOverlay_ClearBeforePass)
{
// clear the overlay image itself
float black[] = {0.0f, 0.0f, 0.0f, 0.0f};
VkImageMemoryBarrier barrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
NULL,
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_ACCESS_TRANSFER_WRITE_BIT,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_QUEUE_FAMILY_IGNORED,
VK_QUEUE_FAMILY_IGNORED,
Unwrap(m_OverlayImage),
{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
DoPipelineBarrier(cmd, 1, &barrier);
vt->CmdClearColorImage(Unwrap(cmd), Unwrap(m_OverlayImage), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
(VkClearColorValue *)black, 1, &subresourceRange);
std::swap(barrier.oldLayout, barrier.newLayout);
std::swap(barrier.srcAccessMask, barrier.dstAccessMask);
barrier.dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT;
DoPipelineBarrier(cmd, 1, &barrier);
vector<uint32_t> events = passEvents;
if(overlay == eTexOverlay_ClearBeforeDraw)
events.clear();
events.push_back(eventID);
{
vkr = vt->EndCommandBuffer(Unwrap(cmd));
RDCASSERTEQUAL(vkr, VK_SUCCESS);
#if defined(SINGLE_FLUSH_VALIDATE)
m_pDriver->SubmitCmds();
#endif
size_t startEvent = 0;
// if we're ClearBeforePass the first event will be a vkBeginRenderPass.
// if there are any other events, we need to play up to right before them
// so that we have all the render state set up to do
// BeginRenderPassAndApplyState and a clear. If it's just the begin, we
// just play including it, do the clear, then we won't replay anything
// in the loop below
if(overlay == eTexOverlay_ClearBeforePass)
{
const FetchDrawcall *draw = m_pDriver->GetDrawcall(events[0]);
if(draw && draw->flags & eDraw_BeginPass)
{
if(events.size() == 1)
{
m_pDriver->ReplayLog(0, events[0], eReplay_Full);
}
else
{
startEvent = 1;
m_pDriver->ReplayLog(0, events[1], eReplay_WithoutDraw);
}
}
}
else
{
m_pDriver->ReplayLog(0, events[0], eReplay_WithoutDraw);
}
cmd = m_pDriver->GetNextCmd();
vkr = vt->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
m_pDriver->m_RenderState.BeginRenderPassAndApplyState(cmd);
VkClearAttachment blackclear = {VK_IMAGE_ASPECT_COLOR_BIT, 0, {}};
vector<VkClearAttachment> atts;
VulkanCreationInfo::Framebuffer &fb =
m_pDriver->m_CreationInfo.m_Framebuffer[m_pDriver->m_RenderState.framebuffer];
VulkanCreationInfo::RenderPass &rp =
m_pDriver->m_CreationInfo.m_RenderPass[m_pDriver->m_RenderState.renderPass];
for(size_t i = 0; i < rp.subpasses[m_pDriver->m_RenderState.subpass].colorAttachments.size();
i++)
{
blackclear.colorAttachment =
rp.subpasses[m_pDriver->m_RenderState.subpass].colorAttachments[i];
atts.push_back(blackclear);
}
VkClearRect rect = {
{
{0, 0}, {fb.width, fb.height},
},
0,
1,
};
vt->CmdClearAttachments(Unwrap(cmd), (uint32_t)atts.size(), &atts[0], 1, &rect);
m_pDriver->m_RenderState.EndRenderPass(cmd);
vkr = vt->EndCommandBuffer(Unwrap(cmd));
RDCASSERTEQUAL(vkr, VK_SUCCESS);
for(size_t i = startEvent; i < events.size(); i++)
{
m_pDriver->ReplayLog(events[i], events[i], eReplay_OnlyDraw);
if(overlay == eTexOverlay_ClearBeforePass && i + 1 < events.size())
m_pDriver->ReplayLog(events[i] + 1, events[i + 1], eReplay_WithoutDraw);
}
cmd = m_pDriver->GetNextCmd();
vkr = vt->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
}
}
else if(overlay == eTexOverlay_QuadOverdrawPass || overlay == eTexOverlay_QuadOverdrawDraw)
{
VulkanRenderState prevstate = m_pDriver->m_RenderState;
{
SCOPED_TIMER("Quad Overdraw");
float black[] = {0.0f, 0.0f, 0.0f, 0.0f};
VkImageMemoryBarrier barrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
NULL,
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_ACCESS_TRANSFER_WRITE_BIT,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_QUEUE_FAMILY_IGNORED,
VK_QUEUE_FAMILY_IGNORED,
Unwrap(m_OverlayImage),
{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
DoPipelineBarrier(cmd, 1, &barrier);
vt->CmdClearColorImage(Unwrap(cmd), Unwrap(m_OverlayImage),
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, (VkClearColorValue *)black, 1,
&subresourceRange);
std::swap(barrier.oldLayout, barrier.newLayout);
std::swap(barrier.srcAccessMask, barrier.dstAccessMask);
barrier.dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT;
DoPipelineBarrier(cmd, 1, &barrier);
vector<uint32_t> events = passEvents;
if(overlay == eTexOverlay_QuadOverdrawDraw)
events.clear();
events.push_back(eventID);
VkImage quadImg;
VkDeviceMemory quadImgMem;
VkImageView quadImgView;
VkImageCreateInfo imInfo = {
VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
NULL,
0,
VK_IMAGE_TYPE_2D,
VK_FORMAT_R32_UINT,
{RDCMAX(1U, m_OverlayDim.width >> 1), RDCMAX(1U, m_OverlayDim.height >> 1), 1},
1,
4,
VK_SAMPLE_COUNT_1_BIT,
VK_IMAGE_TILING_OPTIMAL,
VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
VK_SHARING_MODE_EXCLUSIVE,
0,
NULL,
VK_IMAGE_LAYOUT_UNDEFINED,
};
vkr = m_pDriver->vkCreateImage(m_Device, &imInfo, NULL, &quadImg);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
VkMemoryRequirements mrq = {0};
m_pDriver->vkGetImageMemoryRequirements(m_Device, quadImg, &mrq);
VkMemoryAllocateInfo allocInfo = {
VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, NULL, mrq.size,
m_pDriver->GetGPULocalMemoryIndex(mrq.memoryTypeBits),
};
vkr = m_pDriver->vkAllocateMemory(m_Device, &allocInfo, NULL, &quadImgMem);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
vkr = m_pDriver->vkBindImageMemory(m_Device, quadImg, quadImgMem, 0);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
VkImageViewCreateInfo viewinfo = {
VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
NULL,
0,
quadImg,
VK_IMAGE_VIEW_TYPE_2D_ARRAY,
VK_FORMAT_R32_UINT,
{VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_ZERO, VK_COMPONENT_SWIZZLE_ZERO,
VK_COMPONENT_SWIZZLE_ONE},
{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 4},
};
vkr = m_pDriver->vkCreateImageView(m_Device, &viewinfo, NULL, &quadImgView);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
// update descriptor to point to our R32 result image
VkDescriptorImageInfo imdesc = {0};
imdesc.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
imdesc.sampler = VK_NULL_HANDLE;
imdesc.imageView = Unwrap(quadImgView);
VkWriteDescriptorSet write = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
NULL,
Unwrap(m_QuadDescSet),
0,
0,
1,
VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
&imdesc,
NULL,
NULL};
vt->UpdateDescriptorSets(Unwrap(m_Device), 1, &write, 0, NULL);
VkImageMemoryBarrier quadImBarrier = {
VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
NULL,
0,
VK_ACCESS_TRANSFER_WRITE_BIT,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_GENERAL,
0,
0, // MULTIDEVICE - need to actually pick the right queue family here maybe?
Unwrap(quadImg),
{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 4}};
// clear all to black
DoPipelineBarrier(cmd, 1, &quadImBarrier);
vt->CmdClearColorImage(Unwrap(cmd), Unwrap(quadImg), VK_IMAGE_LAYOUT_GENERAL,
(VkClearColorValue *)&black, 1, &quadImBarrier.subresourceRange);
quadImBarrier.srcAccessMask = quadImBarrier.dstAccessMask;
quadImBarrier.oldLayout = quadImBarrier.newLayout;
quadImBarrier.dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
// set to general layout, for load/store operations
DoPipelineBarrier(cmd, 1, &quadImBarrier);
VkMemoryBarrier memBarrier = {
VK_STRUCTURE_TYPE_MEMORY_BARRIER, NULL, VK_ACCESS_ALL_WRITE_BITS, VK_ACCESS_ALL_READ_BITS,
};
DoPipelineBarrier(cmd, 1, &memBarrier);
// end this cmd buffer so the image is in the right state for the next part
vkr = vt->EndCommandBuffer(Unwrap(cmd));
RDCASSERTEQUAL(vkr, VK_SUCCESS);
#if defined(SINGLE_FLUSH_VALIDATE)
m_pDriver->SubmitCmds();
#endif
m_pDriver->ReplayLog(0, events[0], eReplay_WithoutDraw);
// declare callback struct here
QuadOverdrawCallback cb(m_pDriver, events);
m_pDriver->ReplayLog(events.front(), events.back(), eReplay_Full);
// resolve pass
{
cmd = m_pDriver->GetNextCmd();
vkr = vt->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
quadImBarrier.srcAccessMask = quadImBarrier.dstAccessMask;
quadImBarrier.oldLayout = quadImBarrier.newLayout;
quadImBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
// wait for writing to finish
DoPipelineBarrier(cmd, 1, &quadImBarrier);
VkClearValue clearval = {};
VkRenderPassBeginInfo rpbegin = {
VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
NULL,
Unwrap(m_OverlayNoDepthRP),
Unwrap(m_OverlayNoDepthFB),
m_pDriver->m_RenderState.renderArea,
1,
&clearval,
};
vt->CmdBeginRenderPass(Unwrap(cmd), &rpbegin, VK_SUBPASS_CONTENTS_INLINE);
vt->CmdBindPipeline(Unwrap(cmd), VK_PIPELINE_BIND_POINT_GRAPHICS,
Unwrap(m_QuadResolvePipeline[SampleIndex(iminfo.samples)]));
vt->CmdBindDescriptorSets(Unwrap(cmd), VK_PIPELINE_BIND_POINT_GRAPHICS,
Unwrap(m_QuadResolvePipeLayout), 0, 1, UnwrapPtr(m_QuadDescSet),
0, NULL);
VkViewport viewport = {0.0f, 0.0f, (float)m_OverlayDim.width, (float)m_OverlayDim.height,
0.0f, 1.0f};
vt->CmdSetViewport(Unwrap(cmd), 0, 1, &viewport);
vt->CmdDraw(Unwrap(cmd), 4, 1, 0, 0);
vt->CmdEndRenderPass(Unwrap(cmd));
vkr = vt->EndCommandBuffer(Unwrap(cmd));
RDCASSERTEQUAL(vkr, VK_SUCCESS);
}
m_pDriver->SubmitCmds();
m_pDriver->FlushQ();
m_pDriver->vkDestroyImageView(m_Device, quadImgView, NULL);
m_pDriver->vkDestroyImage(m_Device, quadImg, NULL);
m_pDriver->vkFreeMemory(m_Device, quadImgMem, NULL);
for(auto it = cb.m_PipelineCache.begin(); it != cb.m_PipelineCache.end(); ++it)
{
m_pDriver->vkDestroyPipeline(m_Device, it->second.second, NULL);
}
}
// restore back to normal
m_pDriver->ReplayLog(0, eventID, eReplay_WithoutDraw);
cmd = m_pDriver->GetNextCmd();
vkr = vt->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
}
vkr = vt->EndCommandBuffer(Unwrap(cmd));
RDCASSERTEQUAL(vkr, VK_SUCCESS);
#if defined(SINGLE_FLUSH_VALIDATE)
m_pDriver->SubmitCmds();
#endif
return GetResID(m_OverlayImage);
}
MeshDisplayPipelines VulkanDebugManager::CacheMeshDisplayPipelines(const MeshFormat &primary,
const MeshFormat &secondary)
{
// generate a key to look up the map
uint64_t key = 0;
uint64_t bit = 0;
if(primary.idxByteWidth == 4)
key |= 1ULL << bit;
bit++;
RDCASSERT((uint32_t)primary.topo < 64);
key |= uint64_t((uint32_t)primary.topo & 0x3f) << bit;
bit += 6;
ResourceFormat fmt;
fmt.special = primary.specialFormat != eSpecial_Unknown;
fmt.specialFormat = primary.specialFormat;
fmt.compByteWidth = primary.compByteWidth;
fmt.compCount = primary.compCount;
fmt.compType = primary.compType;
VkFormat primaryFmt = MakeVkFormat(fmt);
fmt.special = secondary.specialFormat != eSpecial_Unknown;
fmt.specialFormat = secondary.specialFormat;
fmt.compByteWidth = secondary.compByteWidth;
fmt.compCount = secondary.compCount;
fmt.compType = secondary.compType;
VkFormat secondaryFmt = secondary.buf == ResourceId() ? VK_FORMAT_UNDEFINED : MakeVkFormat(fmt);
RDCCOMPILE_ASSERT(VK_FORMAT_RANGE_SIZE <= 255,
"Mesh pipeline cache key needs an extra bit for format");
key |= uint64_t((uint32_t)primaryFmt & 0xff) << bit;
bit += 8;
key |= uint64_t((uint32_t)secondaryFmt & 0xff) << bit;
bit += 8;
RDCASSERT(primary.stride <= 0xffff);
key |= uint64_t((uint32_t)primary.stride & 0xffff) << bit;
bit += 16;
if(secondary.buf != ResourceId())
{
RDCASSERT(secondary.stride <= 0xffff);
key |= uint64_t((uint32_t)secondary.stride & 0xffff) << bit;
}
bit += 16;
MeshDisplayPipelines &cache = m_CachedMeshPipelines[key];
if(cache.pipes[eShade_None] != VK_NULL_HANDLE)
return cache;
const VkLayerDispatchTable *vt = ObjDisp(m_Device);
VkResult vkr = VK_SUCCESS;
// should we try and evict old pipelines from the cache here?
// or just keep them forever
VkVertexInputBindingDescription binds[] = {// primary
{0, primary.stride, VK_VERTEX_INPUT_RATE_VERTEX},
// secondary
{1, secondary.stride, VK_VERTEX_INPUT_RATE_VERTEX}};
RDCASSERT(primaryFmt != VK_FORMAT_UNDEFINED);
VkVertexInputAttributeDescription vertAttrs[] = {
// primary
{
0, 0, primaryFmt, 0,
},
// secondary
{
1, 0, primaryFmt, 0,
},
};
VkPipelineVertexInputStateCreateInfo vi = {
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, NULL, 0, 1, binds, 2, vertAttrs,
};
VkPipelineShaderStageCreateInfo stages[3] = {
{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, NULL, 0, VK_SHADER_STAGE_ALL_GRAPHICS,
VK_NULL_HANDLE, "main", NULL},
{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, NULL, 0, VK_SHADER_STAGE_ALL_GRAPHICS,
VK_NULL_HANDLE, "main", NULL},
{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, NULL, 0, VK_SHADER_STAGE_ALL_GRAPHICS,
VK_NULL_HANDLE, "main", NULL},
};
VkPipelineInputAssemblyStateCreateInfo ia = {
VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, NULL, 0,
primary.topo >= eTopology_PatchList ? VK_PRIMITIVE_TOPOLOGY_POINT_LIST
: MakeVkPrimitiveTopology(primary.topo),
false,
};
VkRect2D scissor = {{0, 0}, {4096, 4096}};
VkPipelineViewportStateCreateInfo vp = {
VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, NULL, 0, 1, NULL, 1, &scissor};
VkPipelineRasterizationStateCreateInfo rs = {
VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
NULL,
0,
false,
false,
VK_POLYGON_MODE_FILL,
VK_CULL_MODE_NONE,
VK_FRONT_FACE_CLOCKWISE,
false,
0.0f,
0.0f,
0.0f,
1.0f,
};
VkPipelineMultisampleStateCreateInfo msaa = {
VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
NULL,
0,
VULKAN_MESH_VIEW_SAMPLES,
false,
0.0f,
NULL,
false,
false};
VkPipelineDepthStencilStateCreateInfo ds = {
VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
NULL,
0,
true,
true,
VK_COMPARE_OP_LESS_OR_EQUAL,
false,
false,
{VK_STENCIL_OP_KEEP, VK_STENCIL_OP_KEEP, VK_STENCIL_OP_KEEP, VK_COMPARE_OP_ALWAYS, 0, 0, 0},
{VK_STENCIL_OP_KEEP, VK_STENCIL_OP_KEEP, VK_STENCIL_OP_KEEP, VK_COMPARE_OP_ALWAYS, 0, 0, 0},
0.0f,
1.0f,
};
VkPipelineColorBlendAttachmentState attState = {
false,
VK_BLEND_FACTOR_ONE,
VK_BLEND_FACTOR_ZERO,
VK_BLEND_OP_ADD,
VK_BLEND_FACTOR_ONE,
VK_BLEND_FACTOR_ZERO,
VK_BLEND_OP_ADD,
0xf,
};
VkPipelineColorBlendStateCreateInfo cb = {
VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
NULL,
0,
false,
VK_LOGIC_OP_NO_OP,
1,
&attState,
{1.0f, 1.0f, 1.0f, 1.0f}};
VkDynamicState dynstates[] = {VK_DYNAMIC_STATE_VIEWPORT};
VkPipelineDynamicStateCreateInfo dyn = {
VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
NULL,
0,
ARRAY_COUNT(dynstates),
dynstates,
};
VkRenderPass rp; // compatible render pass
{
VkAttachmentDescription attDesc[] = {
{0, VK_FORMAT_R8G8B8A8_SRGB, VULKAN_MESH_VIEW_SAMPLES, VK_ATTACHMENT_LOAD_OP_LOAD,
VK_ATTACHMENT_STORE_OP_STORE, VK_ATTACHMENT_LOAD_OP_DONT_CARE,
VK_ATTACHMENT_STORE_OP_DONT_CARE, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL},
{0, VK_FORMAT_D32_SFLOAT, VULKAN_MESH_VIEW_SAMPLES, VK_ATTACHMENT_LOAD_OP_LOAD,
VK_ATTACHMENT_STORE_OP_STORE, VK_ATTACHMENT_LOAD_OP_DONT_CARE,
VK_ATTACHMENT_STORE_OP_DONT_CARE, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL},
};
VkAttachmentReference attRef = {0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
VkAttachmentReference dsRef = {1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL};
VkSubpassDescription sub = {
0, VK_PIPELINE_BIND_POINT_GRAPHICS,
0, NULL, // inputs
1, &attRef, // color
NULL, // resolve
&dsRef, // depth-stencil
0, NULL, // preserve
};
VkRenderPassCreateInfo rpinfo = {
VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
NULL,
0,
2,
attDesc,
1,
&sub,
0,
NULL, // dependencies
};
vt->CreateRenderPass(Unwrap(m_Device), &rpinfo, NULL, &rp);
}
VkGraphicsPipelineCreateInfo pipeInfo = {
VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
NULL,
0,
2,
stages,
&vi,
&ia,
NULL, // tess
&vp,
&rs,
&msaa,
&ds,
&cb,
&dyn,
Unwrap(m_MeshPipeLayout),
rp,
0, // sub pass
VK_NULL_HANDLE, // base pipeline handle
0, // base pipeline index
};
// wireframe pipeline
stages[0].module = Unwrap(m_MeshModules[0]);
stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
stages[1].module = Unwrap(m_MeshModules[2]);
stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
rs.polygonMode = VK_POLYGON_MODE_LINE;
rs.lineWidth = 1.0f;
ds.depthTestEnable = false;
vkr = vt->CreateGraphicsPipelines(Unwrap(m_Device), VK_NULL_HANDLE, 1, &pipeInfo, NULL,
&cache.pipes[MeshDisplayPipelines::ePipe_Wire]);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
ds.depthTestEnable = true;
vkr = vt->CreateGraphicsPipelines(Unwrap(m_Device), VK_NULL_HANDLE, 1, &pipeInfo, NULL,
&cache.pipes[MeshDisplayPipelines::ePipe_WireDepth]);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
// solid shading pipeline
rs.polygonMode = VK_POLYGON_MODE_FILL;
ds.depthTestEnable = false;
vkr = vt->CreateGraphicsPipelines(Unwrap(m_Device), VK_NULL_HANDLE, 1, &pipeInfo, NULL,
&cache.pipes[MeshDisplayPipelines::ePipe_Solid]);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
ds.depthTestEnable = true;
vkr = vt->CreateGraphicsPipelines(Unwrap(m_Device), VK_NULL_HANDLE, 1, &pipeInfo, NULL,
&cache.pipes[MeshDisplayPipelines::ePipe_SolidDepth]);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
if(secondary.buf != ResourceId())
{
// pull secondary information from second vertex buffer
vertAttrs[1].binding = 1;
vertAttrs[1].format = secondaryFmt;
RDCASSERT(secondaryFmt != VK_FORMAT_UNDEFINED);
vi.vertexBindingDescriptionCount = 2;
vkr = vt->CreateGraphicsPipelines(Unwrap(m_Device), VK_NULL_HANDLE, 1, &pipeInfo, NULL,
&cache.pipes[MeshDisplayPipelines::ePipe_Secondary]);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
}
vertAttrs[1].binding = 0;
vi.vertexBindingDescriptionCount = 1;
// flat lit pipeline, needs geometry shader to calculate face normals
stages[0].module = Unwrap(m_MeshModules[0]);
stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
stages[1].module = Unwrap(m_MeshModules[1]);
stages[1].stage = VK_SHADER_STAGE_GEOMETRY_BIT;
stages[2].module = Unwrap(m_MeshModules[2]);
stages[2].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
pipeInfo.stageCount = 3;
vkr = vt->CreateGraphicsPipelines(Unwrap(m_Device), VK_NULL_HANDLE, 1, &pipeInfo, NULL,
&cache.pipes[MeshDisplayPipelines::ePipe_Lit]);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
for(uint32_t i = 0; i < MeshDisplayPipelines::ePipe_Count; i++)
if(cache.pipes[i] != VK_NULL_HANDLE)
GetResourceManager()->WrapResource(Unwrap(m_Device), cache.pipes[i]);
vt->DestroyRenderPass(Unwrap(m_Device), rp, NULL);
return cache;
}
inline uint32_t MakeSPIRVOp(spv::Op op, uint32_t WordCount)
{
return (uint32_t(op) & spv::OpCodeMask) | (WordCount << spv::WordCountShift);
}
static void AddOutputDumping(const ShaderReflection &refl, const char *entryName, uint32_t descSet,
uint32_t vertexIndexOffset, uint32_t instanceIndexOffset,
uint32_t numVerts, vector<uint32_t> &modSpirv, uint32_t &bufStride)
{
uint32_t *spirv = &modSpirv[0];
size_t spirvLength = modSpirv.size();
int numOutputs = refl.OutputSig.count;
RDCASSERT(numOutputs > 0);
// save the id bound. We use this whenever we need to allocate ourselves
// a new ID
uint32_t idBound = spirv[3];
// we do multiple passes through the SPIR-V to simplify logic, rather than
// trying to do as few passes as possible.
// first try to find a few IDs of things we know we'll probably need:
// * gl_VertexID, gl_InstanceID (identified by a DecorationBuiltIn)
// * Int32 type, signed and unsigned
// * Float types, half, float and double
// * Input Pointer to Int32 (for declaring gl_VertexID)
// * UInt32 constants from 0 up to however many outputs we have
// * The entry point we're after
//
// At the same time we find the highest descriptor set used and add a
// new descriptor set binding on the end for our output buffer. This is
// much easier than trying to add a new bind to an existing descriptor
// set (which would cascade into a new descriptor set layout, new pipeline
// layout, etc etc!). However, this might push us over the limit on number
// of descriptor sets.
//
// we also note the index where decorations end, and the index where
// functions start, for if we need to add new decorations or new
// types/constants/global variables
uint32_t vertidxID = 0;
uint32_t instidxID = 0;
uint32_t sint32ID = 0;
uint32_t sint32PtrInID = 0;
uint32_t uint32ID = 0;
uint32_t halfID = 0;
uint32_t floatID = 0;
uint32_t doubleID = 0;
uint32_t entryID = 0;
struct outputIDs
{
uint32_t constID; // constant ID for the index of this output
uint32_t basetypeID; // the type ID for this output. Must be present already by definition!
uint32_t uniformPtrID; // Uniform Pointer ID for this output. Used to write the output data
uint32_t varID; // we get this from the output signature, ID of actual variable
uint32_t
childIdx; // if the output variable is a struct, this is the member idx of this output
};
outputIDs outs[100] = {};
RDCASSERT(numOutputs < 100);
size_t entryInterfaceOffset = 0;
size_t entryWordCountOffset = 0;
uint16_t entryWordCount = 0;
size_t decorateOffset = 0;
size_t typeVarOffset = 0;
size_t it = 5;
while(it < spirvLength)
{
uint16_t WordCount = spirv[it] >> spv::WordCountShift;
spv::Op opcode = spv::Op(spirv[it] & spv::OpCodeMask);
if(opcode == spv::OpDecorate && spirv[it + 2] == spv::DecorationBuiltIn &&
spirv[it + 3] == spv::BuiltInVertexIndex)
vertidxID = spirv[it + 1];
if(opcode == spv::OpDecorate && spirv[it + 2] == spv::DecorationBuiltIn &&
spirv[it + 3] == spv::BuiltInInstanceIndex)
instidxID = spirv[it + 1];
if(opcode == spv::OpTypeInt && spirv[it + 2] == 32 && spirv[it + 3] == 1)
sint32ID = spirv[it + 1];
if(opcode == spv::OpTypeInt && spirv[it + 2] == 32 && spirv[it + 3] == 0)
uint32ID = spirv[it + 1];
if(opcode == spv::OpTypeFloat && spirv[it + 2] == 16)
halfID = spirv[it + 1];
if(opcode == spv::OpTypeFloat && spirv[it + 2] == 32)
floatID = spirv[it + 1];
if(opcode == spv::OpTypeFloat && spirv[it + 2] == 64)
doubleID = spirv[it + 1];
if(opcode == spv::OpTypePointer && spirv[it + 2] == spv::StorageClassInput &&
spirv[it + 3] == sint32ID)
sint32PtrInID = spirv[it + 1];
for(int i = 0; i < numOutputs; i++)
{
if(opcode == spv::OpConstant && spirv[it + 1] == uint32ID && spirv[it + 3] == (uint32_t)i)
{
if(outs[i].constID != 0)
RDCWARN("identical constant declared with two different IDs %u %u!", spirv[it + 2],
outs[i].constID); // not sure if this is valid or not
outs[i].constID = spirv[it + 2];
}
if(refl.OutputSig[i].compCount > 1 && opcode == spv::OpTypeVector)
{
uint32_t baseID = 0;
if(refl.OutputSig[i].compType == eCompType_UInt)
baseID = uint32ID;
else if(refl.OutputSig[i].compType == eCompType_SInt)
baseID = sint32ID;
else if(refl.OutputSig[i].compType == eCompType_Float)
baseID = floatID;
else if(refl.OutputSig[i].compType == eCompType_Double)
baseID = doubleID;
else
RDCERR("Unexpected component type for output signature element");
// if we have the base type, see if this is the right sized vector of that type
if(baseID != 0 && spirv[it + 2] == baseID && spirv[it + 3] == refl.OutputSig[i].compCount)
outs[i].basetypeID = spirv[it + 1];
}
// if we've found the base type, try and identify uniform pointers to that type
if(outs[i].basetypeID != 0 && opcode == spv::OpTypePointer &&
spirv[it + 2] == spv::StorageClassUniform && spirv[it + 3] == outs[i].basetypeID)
outs[i].uniformPtrID = spirv[it + 1];
}
if(opcode == spv::OpEntryPoint)
{
const char *name = (const char *)&spirv[it + 3];
if(!strcmp(name, entryName))
{
if(entryID != 0)
RDCERR("Same entry point declared twice! %s", entryName);
entryID = spirv[it + 2];
}
// need to update the WordCount when we add IDs, so store this
entryWordCountOffset = it;
entryWordCount = WordCount;
// where to insert new interface IDs if we add them
entryInterfaceOffset = it + WordCount;
}
// when we reach the types, decorations are over
if(decorateOffset == 0 && opcode >= spv::OpTypeVoid && opcode <= spv::OpTypeForwardPointer)
decorateOffset = it;
// stop when we reach the functions, types are over
if(opcode == spv::OpFunction)
{
typeVarOffset = it;
break;
}
it += WordCount;
}
RDCASSERT(entryID != 0);
for(int i = 0; i < numOutputs; i++)
{
// handle non-vectors once here
if(refl.OutputSig[i].compCount == 1)
{
if(refl.OutputSig[i].compType == eCompType_UInt)
outs[i].basetypeID = uint32ID;
else if(refl.OutputSig[i].compType == eCompType_SInt)
outs[i].basetypeID = sint32ID;
else if(refl.OutputSig[i].compType == eCompType_Float)
outs[i].basetypeID = floatID;
else if(refl.OutputSig[i].compType == eCompType_Double)
outs[i].basetypeID = doubleID;
else
RDCERR("Unexpected component type for output signature element");
}
// must have at least found the base type, or something has gone seriously wrong
RDCASSERT(outs[i].basetypeID != 0);
// bit of a hack, these were stored from SPIR-V disassembly
outs[i].varID = atoi(refl.OutputSig[i].semanticIdxName.elems);
outs[i].childIdx = refl.OutputSig[i].semanticIndex;
}
if(vertidxID == 0)
{
// need to declare our own "in int gl_VertexID;"
// if needed add new ID for sint32 type
if(sint32ID == 0)
{
sint32ID = idBound++;
uint32_t typeOp[] = {
MakeSPIRVOp(spv::OpTypeInt, 4), sint32ID,
32U, // 32-bit
1U, // signed
};
// insert at the end of the types/variables section
modSpirv.insert(modSpirv.begin() + typeVarOffset, typeOp, typeOp + ARRAY_COUNT(typeOp));
// update offsets to account for inserted op
typeVarOffset += ARRAY_COUNT(typeOp);
}
// if needed, new ID for input ptr type
if(sint32PtrInID == 0)
{
sint32PtrInID = idBound;
idBound++;
uint32_t typeOp[] = {
MakeSPIRVOp(spv::OpTypePointer, 4), sint32PtrInID, spv::StorageClassInput, sint32ID,
};
// insert at the end of the types/variables section
modSpirv.insert(modSpirv.begin() + typeVarOffset, typeOp, typeOp + ARRAY_COUNT(typeOp));
// update offsets to account for inserted op
typeVarOffset += ARRAY_COUNT(typeOp);
}
// new ID for vertex index
vertidxID = idBound;
idBound++;
uint32_t varOp[] = {
MakeSPIRVOp(spv::OpVariable, 4),
sint32PtrInID, // type
vertidxID, // variable id
spv::StorageClassInput,
};
// insert at the end of the types/variables section
modSpirv.insert(modSpirv.begin() + typeVarOffset, varOp, varOp + ARRAY_COUNT(varOp));
// update offsets to account for inserted op
typeVarOffset += ARRAY_COUNT(varOp);
uint32_t decorateOp[] = {
MakeSPIRVOp(spv::OpDecorate, 4), vertidxID, spv::DecorationBuiltIn, spv::BuiltInVertexIndex,
};
// insert at the end of the decorations before the types
modSpirv.insert(modSpirv.begin() + decorateOffset, decorateOp,
decorateOp + ARRAY_COUNT(decorateOp));
// update offsets to account for inserted op
typeVarOffset += ARRAY_COUNT(decorateOp);
decorateOffset += ARRAY_COUNT(decorateOp);
modSpirv[entryWordCountOffset] = MakeSPIRVOp(spv::OpEntryPoint, ++entryWordCount);
// need to add this input to the declared interface on OpEntryPoint
modSpirv.insert(modSpirv.begin() + entryInterfaceOffset, vertidxID);
// update offsets to account for inserted ID
entryInterfaceOffset++;
typeVarOffset++;
decorateOffset++;
}
if(instidxID == 0)
{
// we can assume that after vertxidxID was added above, that the types
// are available. We just have to add the actual instance id variable
// new ID for vertex index
instidxID = idBound;
idBound++;
uint32_t varOp[] = {
MakeSPIRVOp(spv::OpVariable, 4),
sint32PtrInID, // type
instidxID, // variable id
spv::StorageClassInput,
};
// insert at the end of the types/variables section
modSpirv.insert(modSpirv.begin() + typeVarOffset, varOp, varOp + ARRAY_COUNT(varOp));
// update offsets to account for inserted op
typeVarOffset += ARRAY_COUNT(varOp);
uint32_t decorateOp[] = {
MakeSPIRVOp(spv::OpDecorate, 4), instidxID, spv::DecorationBuiltIn, spv::BuiltInInstanceIndex,
};
// insert at the end of the decorations before the types
modSpirv.insert(modSpirv.begin() + decorateOffset, decorateOp,
decorateOp + ARRAY_COUNT(decorateOp));
// update offsets to account for inserted op
typeVarOffset += ARRAY_COUNT(decorateOp);
decorateOffset += ARRAY_COUNT(decorateOp);
modSpirv[entryWordCountOffset] = MakeSPIRVOp(spv::OpEntryPoint, ++entryWordCount);
// need to add this input to the declared interface on OpEntryPoint
modSpirv.insert(modSpirv.begin() + entryInterfaceOffset, instidxID);
// update offsets to account for inserted ID
entryInterfaceOffset++;
typeVarOffset++;
decorateOffset++;
}
// if needed add new ID for uint32 type
if(uint32ID == 0)
{
uint32ID = idBound++;
uint32_t typeOp[] = {
MakeSPIRVOp(spv::OpTypeInt, 4), uint32ID,
32U, // 32-bit
0U, // unsigned
};
// insert at the end of the types/variables section
modSpirv.insert(modSpirv.begin() + typeVarOffset, typeOp, typeOp + ARRAY_COUNT(typeOp));
// update offsets to account for inserted op
typeVarOffset += ARRAY_COUNT(typeOp);
}
// add any constants we're missing
for(int i = 0; i < numOutputs; i++)
{
if(outs[i].constID == 0)
{
outs[i].constID = idBound++;
uint32_t constantOp[] = {
MakeSPIRVOp(spv::OpConstant, 4), uint32ID, outs[i].constID, (uint32_t)i,
};
// insert at the end of the types/variables/constants section
modSpirv.insert(modSpirv.begin() + typeVarOffset, constantOp,
constantOp + ARRAY_COUNT(constantOp));
// update offsets to account for inserted op
typeVarOffset += ARRAY_COUNT(constantOp);
}
}
// add any uniform pointer types we're missing. Note that it's quite likely
// output types will overlap (think - 5 outputs, 3 of which are float4/vec4)
// so any time we create a new uniform pointer type, we update all subsequent
// outputs to refer to it.
for(int i = 0; i < numOutputs; i++)
{
if(outs[i].uniformPtrID == 0)
{
outs[i].uniformPtrID = idBound++;
uint32_t typeOp[] = {
MakeSPIRVOp(spv::OpTypePointer, 4), outs[i].uniformPtrID, spv::StorageClassUniform,
outs[i].basetypeID,
};
// insert at the end of the types/variables/constants section
modSpirv.insert(modSpirv.begin() + typeVarOffset, typeOp, typeOp + ARRAY_COUNT(typeOp));
// update offsets to account for inserted op
typeVarOffset += ARRAY_COUNT(typeOp);
// update subsequent outputs of identical type
for(int j = i + 1; j < numOutputs; j++)
{
if(outs[i].basetypeID == outs[j].basetypeID)
{
RDCASSERT(outs[j].uniformPtrID == 0);
outs[j].uniformPtrID = outs[i].uniformPtrID;
}
}
}
}
uint32_t outBufferVarID = 0;
uint32_t numVertsConstID = 0;
uint32_t vertexIndexOffsetConstID = 0;
uint32_t instanceIndexOffsetConstID = 0;
// now add the structure type etc for our output buffer
{
uint32_t vertStructID = idBound++;
uint32_t vertStructOp[2 + 100] = {
MakeSPIRVOp(spv::OpTypeStruct, 2 + numOutputs), vertStructID,
};
for(int o = 0; o < numOutputs; o++)
vertStructOp[2 + o] = outs[o].basetypeID;
// insert at the end of the types/variables section
modSpirv.insert(modSpirv.begin() + typeVarOffset, vertStructOp, vertStructOp + 2 + numOutputs);
// update offsets to account for inserted op
typeVarOffset += 2 + numOutputs;
uint32_t runtimeArrayID = idBound++;
uint32_t runtimeArrayOp[] = {
MakeSPIRVOp(spv::OpTypeRuntimeArray, 3), runtimeArrayID, vertStructID,
};
// insert at the end of the types/variables section
modSpirv.insert(modSpirv.begin() + typeVarOffset, runtimeArrayOp,
runtimeArrayOp + ARRAY_COUNT(runtimeArrayOp));
// update offsets to account for inserted op
typeVarOffset += ARRAY_COUNT(runtimeArrayOp);
// add a constant for the number of verts, the 'instance stride' of the array
numVertsConstID = idBound++;
uint32_t instanceStrideConstOp[] = {
MakeSPIRVOp(spv::OpConstant, 4), sint32ID, numVertsConstID, numVerts,
};
// insert at the end of the types/variables section
modSpirv.insert(modSpirv.begin() + typeVarOffset, instanceStrideConstOp,
instanceStrideConstOp + ARRAY_COUNT(instanceStrideConstOp));
// update offsets to account for inserted op
typeVarOffset += ARRAY_COUNT(instanceStrideConstOp);
// add a constant for the value that VertexIndex starts at, so we can get a 0-based vertex index
vertexIndexOffsetConstID = idBound++;
uint32_t vertexIndexOffsetConstOp[] = {
MakeSPIRVOp(spv::OpConstant, 4), sint32ID, vertexIndexOffsetConstID, vertexIndexOffset,
};
// insert at the end of the types/variables section
modSpirv.insert(modSpirv.begin() + typeVarOffset, vertexIndexOffsetConstOp,
vertexIndexOffsetConstOp + ARRAY_COUNT(vertexIndexOffsetConstOp));
// update offsets to account for inserted op
typeVarOffset += ARRAY_COUNT(vertexIndexOffsetConstOp);
// add a constant for the value that InstanceIndex starts at, so we can get a 0-based instance
// index
instanceIndexOffsetConstID = idBound++;
uint32_t instanceIndexOffsetConstOp[] = {
MakeSPIRVOp(spv::OpConstant, 4), sint32ID, instanceIndexOffsetConstID, instanceIndexOffset,
};
// insert at the end of the types/variables section
modSpirv.insert(modSpirv.begin() + typeVarOffset, instanceIndexOffsetConstOp,
instanceIndexOffsetConstOp + ARRAY_COUNT(instanceIndexOffsetConstOp));
// update offsets to account for inserted op
typeVarOffset += ARRAY_COUNT(instanceIndexOffsetConstOp);
uint32_t outputStructID = idBound++;
uint32_t outputStructOp[] = {
MakeSPIRVOp(spv::OpTypeStruct, 3), outputStructID, runtimeArrayID,
};
// insert at the end of the types/variables section
modSpirv.insert(modSpirv.begin() + typeVarOffset, outputStructOp,
outputStructOp + ARRAY_COUNT(outputStructOp));
// update offsets to account for inserted op
typeVarOffset += ARRAY_COUNT(outputStructOp);
uint32_t outputStructPtrID = idBound++;
uint32_t outputStructPtrOp[] = {
MakeSPIRVOp(spv::OpTypePointer, 4), outputStructPtrID, spv::StorageClassUniform,
outputStructID,
};
// insert at the end of the types/variables section
modSpirv.insert(modSpirv.begin() + typeVarOffset, outputStructPtrOp,
outputStructPtrOp + ARRAY_COUNT(outputStructPtrOp));
// update offsets to account for inserted op
typeVarOffset += ARRAY_COUNT(outputStructPtrOp);
outBufferVarID = idBound++;
uint32_t outputVarOp[] = {
MakeSPIRVOp(spv::OpVariable, 4), outputStructPtrID, outBufferVarID, spv::StorageClassUniform,
};
// insert at the end of the types/variables section
modSpirv.insert(modSpirv.begin() + typeVarOffset, outputVarOp,
outputVarOp + ARRAY_COUNT(outputVarOp));
// update offsets to account for inserted op
typeVarOffset += ARRAY_COUNT(outputVarOp);
// need to add decorations as appropriate
vector<uint32_t> decorations;
// reserve room for 1 member decorate per output, plus
// other fixed decorations
decorations.reserve(5 * numOutputs + 20);
uint32_t memberOffset = 0;
for(int o = 0; o < numOutputs; o++)
{
uint32_t elemSize = 0;
if(refl.OutputSig[o].compType == eCompType_Double)
elemSize = 8;
else if(refl.OutputSig[o].compType == eCompType_SInt ||
refl.OutputSig[o].compType == eCompType_UInt ||
refl.OutputSig[o].compType == eCompType_Float)
elemSize = 4;
else
RDCERR("Unexpected component type for output signature element");
uint32_t numComps = refl.OutputSig[o].compCount;
// ensure member is std430 packed (vec4 alignment for vec3/vec4)
if(numComps == 2)
memberOffset = AlignUp(memberOffset, 2U * elemSize);
else if(numComps > 2)
memberOffset = AlignUp(memberOffset, 4U * elemSize);
decorations.push_back(MakeSPIRVOp(spv::OpMemberDecorate, 5));
decorations.push_back(vertStructID);
decorations.push_back((uint32_t)o);
decorations.push_back(spv::DecorationOffset);
decorations.push_back(memberOffset);
memberOffset += elemSize * refl.OutputSig[o].compCount;
}
// align to 16 bytes (vec4) since we will almost certainly have
// a vec4 in the struct somewhere, and even in std430 alignment,
// the base struct alignment is still the largest base alignment
// of any member
memberOffset = AlignUp16(memberOffset);
// the array is the only element in the output struct, so
// it's at offset 0
decorations.push_back(MakeSPIRVOp(spv::OpMemberDecorate, 5));
decorations.push_back(outputStructID);
decorations.push_back(0);
decorations.push_back(spv::DecorationOffset);
decorations.push_back(0);
// set array stride
decorations.push_back(MakeSPIRVOp(spv::OpDecorate, 4));
decorations.push_back(runtimeArrayID);
decorations.push_back(spv::DecorationArrayStride);
decorations.push_back(memberOffset);
bufStride = memberOffset;
// set object type
decorations.push_back(MakeSPIRVOp(spv::OpDecorate, 3));
decorations.push_back(outputStructID);
decorations.push_back(spv::DecorationBufferBlock);
// set binding
decorations.push_back(MakeSPIRVOp(spv::OpDecorate, 4));
decorations.push_back(outBufferVarID);
decorations.push_back(spv::DecorationDescriptorSet);
decorations.push_back(descSet);
decorations.push_back(MakeSPIRVOp(spv::OpDecorate, 4));
decorations.push_back(outBufferVarID);
decorations.push_back(spv::DecorationBinding);
decorations.push_back(0);
// insert at the end of the types/variables section
modSpirv.insert(modSpirv.begin() + decorateOffset, decorations.begin(), decorations.end());
// update offsets to account for inserted op
typeVarOffset += decorations.size();
decorateOffset += decorations.size();
}
vector<uint32_t> dumpCode;
{
// bit of a conservative resize. Each output if in a struct could have
// AccessChain on source = 4 uint32s
// Load source = 4 uint32s
// AccessChain on dest = 7 uint32s
// Store dest = 3 uint32s
//
// loading the indices, and multiplying to get the destination array
// slot is constant on top of that
dumpCode.reserve(numOutputs * (4 + 4 + 7 + 3) + 4 + 4 + 5 + 5);
uint32_t loadedVtxID = idBound++;
dumpCode.push_back(MakeSPIRVOp(spv::OpLoad, 4));
dumpCode.push_back(sint32ID);
dumpCode.push_back(loadedVtxID);
dumpCode.push_back(vertidxID);
uint32_t loadedInstID = idBound++;
dumpCode.push_back(MakeSPIRVOp(spv::OpLoad, 4));
dumpCode.push_back(sint32ID);
dumpCode.push_back(loadedInstID);
dumpCode.push_back(instidxID);
uint32_t rebasedInstID = idBound++;
dumpCode.push_back(MakeSPIRVOp(spv::OpISub, 5));
dumpCode.push_back(sint32ID);
dumpCode.push_back(rebasedInstID); // rebasedInst =
dumpCode.push_back(loadedInstID); // gl_InstanceIndex -
dumpCode.push_back(instanceIndexOffsetConstID); // instanceIndexOffset
uint32_t startVertID = idBound++;
dumpCode.push_back(MakeSPIRVOp(spv::OpIMul, 5));
dumpCode.push_back(sint32ID);
dumpCode.push_back(startVertID); // startVert =
dumpCode.push_back(rebasedInstID); // rebasedInst *
dumpCode.push_back(numVertsConstID); // numVerts
uint32_t rebasedVertID = idBound++;
dumpCode.push_back(MakeSPIRVOp(spv::OpISub, 5));
dumpCode.push_back(sint32ID);
dumpCode.push_back(rebasedVertID); // rebasedVert =
dumpCode.push_back(loadedVtxID); // gl_VertexIndex -
dumpCode.push_back(vertexIndexOffsetConstID); // vertexIndexOffset
uint32_t arraySlotID = idBound++;
dumpCode.push_back(MakeSPIRVOp(spv::OpIAdd, 5));
dumpCode.push_back(sint32ID);
dumpCode.push_back(arraySlotID); // arraySlot =
dumpCode.push_back(startVertID); // startVert +
dumpCode.push_back(rebasedVertID); // rebasedVert
for(int o = 0; o < numOutputs; o++)
{
uint32_t loaded = 0;
// not a structure member or array child, can load directly
if(outs[o].childIdx == ~0U && refl.OutputSig[o].arrayIndex == ~0U)
{
loaded = idBound++;
dumpCode.push_back(MakeSPIRVOp(spv::OpLoad, 4));
dumpCode.push_back(outs[o].basetypeID);
dumpCode.push_back(loaded);
dumpCode.push_back(outs[o].varID);
}
else
{
uint32_t readPtr = idBound++;
loaded = idBound++;
uint32_t chainLength = 1;
if(outs[o].childIdx != ~0U && refl.OutputSig[o].arrayIndex != ~0U)
chainLength = 2;
// structure member, need to access chain first
dumpCode.push_back(MakeSPIRVOp(spv::OpAccessChain, 4 + chainLength));
dumpCode.push_back(outs[o].uniformPtrID);
dumpCode.push_back(readPtr); // readPtr =
dumpCode.push_back(outs[o].varID); // outStructWhatever
if(outs[o].childIdx != ~0U)
{
RDCASSERT(outs[o].childIdx < (uint32_t)numOutputs);
dumpCode.push_back(outs[outs[o].childIdx].constID); // .actualOut
}
if(refl.OutputSig[o].arrayIndex != ~0U)
{
RDCASSERT(refl.OutputSig[o].arrayIndex < (uint32_t)numOutputs);
dumpCode.push_back(outs[refl.OutputSig[o].arrayIndex].constID); // [element]
}
dumpCode.push_back(MakeSPIRVOp(spv::OpLoad, 4));
dumpCode.push_back(outs[o].basetypeID);
dumpCode.push_back(loaded);
dumpCode.push_back(readPtr);
}
// access chain the destination
uint32_t writePtr = idBound++;
dumpCode.push_back(MakeSPIRVOp(spv::OpAccessChain, 7));
dumpCode.push_back(outs[o].uniformPtrID);
dumpCode.push_back(writePtr);
dumpCode.push_back(outBufferVarID); // outBuffer
dumpCode.push_back(outs[0].constID); // .verts
dumpCode.push_back(arraySlotID); // [arraySlot]
dumpCode.push_back(outs[o].constID); // .out_...
dumpCode.push_back(MakeSPIRVOp(spv::OpStore, 3));
dumpCode.push_back(writePtr);
dumpCode.push_back(loaded);
}
}
// update these values, since vector will have resized and/or reallocated above
spirv = &modSpirv[0];
spirvLength = modSpirv.size();
bool infunc = false;
it = 5;
while(it < spirvLength)
{
uint16_t WordCount = spirv[it] >> spv::WordCountShift;
spv::Op opcode = spv::Op(spirv[it] & spv::OpCodeMask);
// find the start of the entry point
if(opcode == spv::OpFunction && spirv[it + 2] == entryID)
infunc = true;
// insert the dumpCode before any spv::OpReturn.
// we should not have any spv::OpReturnValue since this is
// the entry point. Neither should we have OpKill etc.
if(infunc && opcode == spv::OpReturn)
{
modSpirv.insert(modSpirv.begin() + it, dumpCode.begin(), dumpCode.end());
it += dumpCode.size();
// update these values, since vector will have resized and/or reallocated above
spirv = &modSpirv[0];
spirvLength = modSpirv.size();
}
// done patching entry point
if(opcode == spv::OpFunctionEnd && infunc)
break;
it += WordCount;
}
// patch up the new id bound
spirv[3] = idBound;
}
void VulkanDebugManager::InitPostVSBuffers(uint32_t eventID)
{
// go through any aliasing
if(m_PostVSAlias.find(eventID) != m_PostVSAlias.end())
eventID = m_PostVSAlias[eventID];
if(m_PostVSData.find(eventID) != m_PostVSData.end())
return;
if(!m_pDriver->GetDeviceFeatures().vertexPipelineStoresAndAtomics)
return;
const VulkanRenderState &state = m_pDriver->m_RenderState;
VulkanCreationInfo &creationInfo = m_pDriver->m_CreationInfo;
if(state.graphics.pipeline == ResourceId())
return;
const VulkanCreationInfo::Pipeline &pipeInfo = creationInfo.m_Pipeline[state.graphics.pipeline];
if(pipeInfo.shaders[0].module == ResourceId())
return;
const VulkanCreationInfo::ShaderModule &moduleInfo =
creationInfo.m_ShaderModule[pipeInfo.shaders[0].module];
ShaderReflection *refl = pipeInfo.shaders[0].refl;
// no outputs from this shader? unexpected but theoretically possible (dummy VS before
// tessellation maybe). Just fill out an empty data set
if(refl->OutputSig.count == 0)
{
// empty vertex output signature
m_PostVSData[eventID].vsin.topo = pipeInfo.topology;
m_PostVSData[eventID].vsout.buf = VK_NULL_HANDLE;
m_PostVSData[eventID].vsout.instStride = 0;
m_PostVSData[eventID].vsout.vertStride = 0;
m_PostVSData[eventID].vsout.nearPlane = 0.0f;
m_PostVSData[eventID].vsout.farPlane = 0.0f;
m_PostVSData[eventID].vsout.useIndices = false;
m_PostVSData[eventID].vsout.hasPosOut = false;
m_PostVSData[eventID].vsout.idxBuf = VK_NULL_HANDLE;
m_PostVSData[eventID].vsout.topo = pipeInfo.topology;
return;
}
const FetchDrawcall *drawcall = m_pDriver->GetDrawcall(eventID);
if(drawcall->numIndices == 0)
return;
uint32_t descSet = (uint32_t)creationInfo.m_PipelineLayout[pipeInfo.layout].descSetLayouts.size();
// we go through the driver for all these creations since they need to be properly
// registered in order to be put in the partial replay state
VkResult vkr = VK_SUCCESS;
VkDevice dev = m_Device;
VkDescriptorSetLayout *descSetLayouts;
// descSet will be the index of our new descriptor set
descSetLayouts = new VkDescriptorSetLayout[descSet + 1];
for(uint32_t i = 0; i < descSet; i++)
descSetLayouts[i] = GetResourceManager()->GetCurrentHandle<VkDescriptorSetLayout>(
creationInfo.m_PipelineLayout[pipeInfo.layout].descSetLayouts[i]);
// this layout just says it has one storage buffer
descSetLayouts[descSet] = m_MeshFetchDescSetLayout;
const vector<VkPushConstantRange> &push = creationInfo.m_PipelineLayout[pipeInfo.layout].pushRanges;
VkPipelineLayoutCreateInfo pipeLayoutInfo = {
VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
NULL,
0,
descSet + 1,
descSetLayouts,
(uint32_t)push.size(),
push.empty() ? NULL : &push[0],
};
// create pipeline layout with same descriptor set layouts, plus our mesh output set
VkPipelineLayout pipeLayout;
vkr = m_pDriver->vkCreatePipelineLayout(dev, &pipeLayoutInfo, NULL, &pipeLayout);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
SAFE_DELETE_ARRAY(descSetLayouts);
VkGraphicsPipelineCreateInfo pipeCreateInfo;
// get pipeline create info
MakeGraphicsPipelineInfo(pipeCreateInfo, state.graphics.pipeline);
// repoint pipeline layout
pipeCreateInfo.layout = pipeLayout;
// set primitive topology to point list
VkPipelineInputAssemblyStateCreateInfo *ia =
(VkPipelineInputAssemblyStateCreateInfo *)pipeCreateInfo.pInputAssemblyState;
VkPrimitiveTopology topo = ia->topology;
ia->topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
// remove all stages but the vertex shader, we just want to run it and write the data,
// we don't want to tessellate/geometry shade, nor rasterize (which we disable below)
uint32_t vertIdx = pipeCreateInfo.stageCount;
for(uint32_t i = 0; i < pipeCreateInfo.stageCount; i++)
{
if(pipeCreateInfo.pStages[i].stage & VK_SHADER_STAGE_VERTEX_BIT)
{
vertIdx = i;
break;
}
}
RDCASSERT(vertIdx < pipeCreateInfo.stageCount);
if(vertIdx != 0)
(VkPipelineShaderStageCreateInfo &)pipeCreateInfo.pStages[0] = pipeCreateInfo.pStages[vertIdx];
pipeCreateInfo.stageCount = 1;
// enable rasterizer discard
VkPipelineRasterizationStateCreateInfo *rs =
(VkPipelineRasterizationStateCreateInfo *)pipeCreateInfo.pRasterizationState;
rs->rasterizerDiscardEnable = true;
VkBuffer meshBuffer = VK_NULL_HANDLE, readbackBuffer = VK_NULL_HANDLE;
VkDeviceMemory meshMem = VK_NULL_HANDLE, readbackMem = VK_NULL_HANDLE;
VkBuffer idxBuf = VK_NULL_HANDLE, uniqIdxBuf = VK_NULL_HANDLE;
VkDeviceMemory idxBufMem = VK_NULL_HANDLE, uniqIdxBufMem = VK_NULL_HANDLE;
uint32_t numVerts = drawcall->numIndices;
VkDeviceSize bufSize = 0;
vector<uint32_t> indices;
uint32_t idxsize = state.ibuffer.bytewidth;
bool index16 = (idxsize == 2);
uint32_t numIndices = numVerts;
vector<byte> idxdata;
uint16_t *idx16 = NULL;
uint32_t *idx32 = NULL;
uint32_t minIndex = 0, maxIndex = 0;
uint32_t vertexIndexOffset = 0;
if((drawcall->flags & eDraw_UseIBuffer) != 0)
{
// fetch ibuffer
GetBufferData(state.ibuffer.buf, state.ibuffer.offs + drawcall->indexOffset * idxsize,
drawcall->numIndices * idxsize, idxdata);
// figure out what the maximum index could be, so we can clamp our index buffer to something
// sane
uint32_t maxIdx = 0;
// if there are no active bindings assume the vertex shader is generating its own data
// and don't clamp the indices
if(pipeCreateInfo.pVertexInputState->vertexBindingDescriptionCount == 0)
maxIdx = ~0U;
for(uint32_t b = 0; b < pipeCreateInfo.pVertexInputState->vertexBindingDescriptionCount; b++)
{
const VkVertexInputBindingDescription &input =
pipeCreateInfo.pVertexInputState->pVertexBindingDescriptions[b];
// only vertex inputs (not instance inputs) count
if(input.inputRate == VK_VERTEX_INPUT_RATE_VERTEX)
{
if(b >= state.vbuffers.size())
continue;
ResourceId buf = state.vbuffers[b].buf;
VkDeviceSize offs = state.vbuffers[b].offs;
VkDeviceSize bufsize = creationInfo.m_Buffer[buf].size;
// the maximum valid index on this particular input is the one that reaches
// the end of the buffer. The maximum valid index at all is the one that reads
// off the end of ALL buffers (so we max it with any other maxindex value
// calculated).
if(input.stride > 0)
maxIdx = RDCMAX(maxIdx, uint32_t((bufsize - offs) / input.stride));
}
}
// in case the vertex buffers were set but had invalid stride (0), max with the number
// of vertices too. This is fine since the max here is just a conservative limit
maxIdx = RDCMAX(maxIdx, drawcall->numIndices);
// do ibuffer rebasing/remapping
idx16 = (uint16_t *)&idxdata[0];
idx32 = (uint32_t *)&idxdata[0];
// only read as many indices as were available in the buffer
numIndices =
RDCMIN(uint32_t(index16 ? idxdata.size() / 2 : idxdata.size() / 4), drawcall->numIndices);
// grab all unique vertex indices referenced
for(uint32_t i = 0; i < numIndices; i++)
{
uint32_t i32 = index16 ? uint32_t(idx16[i]) : idx32[i];
// we clamp to maxIdx here, to avoid any invalid indices like 0xffffffff
// from filtering through. Worst case we index to the end of the vertex
// buffers which is generally much more reasonable
i32 = RDCMIN(maxIdx, i32);
auto it = std::lower_bound(indices.begin(), indices.end(), i32);
if(it != indices.end() && *it == i32)
continue;
indices.insert(it, i32);
}
// if we read out of bounds, we'll also have a 0 index being referenced
// (as 0 is read). Don't insert 0 if we already have 0 though
if(numIndices < drawcall->numIndices && (indices.empty() || indices[0] != 0))
indices.insert(indices.begin(), 0);
minIndex = indices[0];
maxIndex = indices[indices.size() - 1];
vertexIndexOffset = minIndex + drawcall->baseVertex;
// set numVerts
numVerts = maxIndex - minIndex + 1;
// create buffer with unique 0-based indices
VkBufferCreateInfo bufInfo = {
VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
NULL,
0,
indices.size() * sizeof(uint32_t),
VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
};
vkr = m_pDriver->vkCreateBuffer(dev, &bufInfo, NULL, &uniqIdxBuf);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
VkMemoryRequirements mrq = {0};
m_pDriver->vkGetBufferMemoryRequirements(dev, uniqIdxBuf, &mrq);
VkMemoryAllocateInfo allocInfo = {
VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, NULL, mrq.size,
m_pDriver->GetUploadMemoryIndex(mrq.memoryTypeBits),
};
vkr = m_pDriver->vkAllocateMemory(dev, &allocInfo, NULL, &uniqIdxBufMem);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
vkr = m_pDriver->vkBindBufferMemory(dev, uniqIdxBuf, uniqIdxBufMem, 0);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
byte *idxData = NULL;
vkr = m_pDriver->vkMapMemory(m_Device, uniqIdxBufMem, 0, VK_WHOLE_SIZE, 0, (void **)&idxData);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
memcpy(idxData, &indices[0], indices.size() * sizeof(uint32_t));
m_pDriver->vkUnmapMemory(m_Device, uniqIdxBufMem);
bufInfo.size = numIndices * idxsize;
vkr = m_pDriver->vkCreateBuffer(dev, &bufInfo, NULL, &idxBuf);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
m_pDriver->vkGetBufferMemoryRequirements(dev, idxBuf, &mrq);
allocInfo.allocationSize = mrq.size;
allocInfo.memoryTypeIndex = m_pDriver->GetUploadMemoryIndex(mrq.memoryTypeBits);
vkr = m_pDriver->vkAllocateMemory(dev, &allocInfo, NULL, &idxBufMem);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
vkr = m_pDriver->vkBindBufferMemory(dev, idxBuf, idxBufMem, 0);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
}
else
{
// firstVertex
vertexIndexOffset = drawcall->vertexOffset;
}
uint32_t bufStride = 0;
vector<uint32_t> modSpirv = moduleInfo.spirv.spirv;
AddOutputDumping(*refl, pipeInfo.shaders[0].entryPoint.c_str(), descSet, vertexIndexOffset,
drawcall->instanceOffset, numVerts, modSpirv, bufStride);
// create vertex shader with modified code
VkShaderModuleCreateInfo moduleCreateInfo = {
VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, NULL, 0,
modSpirv.size() * sizeof(uint32_t), &modSpirv[0],
};
VkShaderModule module;
vkr = m_pDriver->vkCreateShaderModule(dev, &moduleCreateInfo, NULL, &module);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
// change vertex shader to use our modified code
for(uint32_t i = 0; i < pipeCreateInfo.stageCount; i++)
{
VkPipelineShaderStageCreateInfo &sh =
(VkPipelineShaderStageCreateInfo &)pipeCreateInfo.pStages[i];
if(sh.stage == VK_SHADER_STAGE_VERTEX_BIT)
{
sh.module = module;
// entry point name remains the same
break;
}
}
// create new pipeline
VkPipeline pipe;
vkr = m_pDriver->vkCreateGraphicsPipelines(m_Device, VK_NULL_HANDLE, 1, &pipeCreateInfo, NULL,
&pipe);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
// make copy of state to draw from
VulkanRenderState modifiedstate = state;
// bind created pipeline to partial replay state
modifiedstate.graphics.pipeline = GetResID(pipe);
// push back extra descriptor set to partial replay state
// note that we examined the used pipeline layout above and inserted our descriptor set
// after any the application used. So there might be more bound, but we want to ensure to
// bind to the slot we're using
modifiedstate.graphics.descSets.resize(descSet + 1);
modifiedstate.graphics.descSets[descSet].descSet = GetResID(m_MeshFetchDescSet);
if((drawcall->flags & eDraw_UseIBuffer) == 0)
{
// create buffer of sufficient size (num indices * bufStride)
VkBufferCreateInfo bufInfo = {
VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
NULL,
0,
drawcall->numIndices * RDCMAX(1U, drawcall->numInstances) * bufStride,
0,
};
bufSize = bufInfo.size;
bufInfo.usage |= VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
bufInfo.usage |= VK_BUFFER_USAGE_TRANSFER_DST_BIT;
bufInfo.usage |= VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
bufInfo.usage |= VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
vkr = m_pDriver->vkCreateBuffer(dev, &bufInfo, NULL, &meshBuffer);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
vkr = m_pDriver->vkCreateBuffer(dev, &bufInfo, NULL, &readbackBuffer);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
VkMemoryRequirements mrq = {0};
m_pDriver->vkGetBufferMemoryRequirements(dev, meshBuffer, &mrq);
VkMemoryAllocateInfo allocInfo = {
VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, NULL, mrq.size,
m_pDriver->GetGPULocalMemoryIndex(mrq.memoryTypeBits),
};
vkr = m_pDriver->vkAllocateMemory(dev, &allocInfo, NULL, &meshMem);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
vkr = m_pDriver->vkBindBufferMemory(dev, meshBuffer, meshMem, 0);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
m_pDriver->vkGetBufferMemoryRequirements(dev, readbackBuffer, &mrq);
allocInfo.memoryTypeIndex = m_pDriver->GetReadbackMemoryIndex(mrq.memoryTypeBits);
vkr = m_pDriver->vkAllocateMemory(dev, &allocInfo, NULL, &readbackMem);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
vkr = m_pDriver->vkBindBufferMemory(dev, readbackBuffer, readbackMem, 0);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
// vkUpdateDescriptorSet desc set to point to buffer
VkDescriptorBufferInfo fetchdesc = {0};
fetchdesc.buffer = meshBuffer;
fetchdesc.offset = 0;
fetchdesc.range = bufInfo.size;
VkWriteDescriptorSet write = {
VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, m_MeshFetchDescSet, 0, 0, 1,
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, NULL, &fetchdesc, NULL};
m_pDriver->vkUpdateDescriptorSets(dev, 1, &write, 0, NULL);
VkCommandBuffer cmd = m_pDriver->GetNextCmd();
VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, NULL,
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT};
vkr = ObjDisp(dev)->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
// do single draw
modifiedstate.BeginRenderPassAndApplyState(cmd);
ObjDisp(cmd)->CmdDraw(Unwrap(cmd), drawcall->numIndices, drawcall->numInstances,
drawcall->vertexOffset, drawcall->instanceOffset);
modifiedstate.EndRenderPass(cmd);
VkBufferMemoryBarrier meshbufbarrier = {
VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
NULL,
VK_ACCESS_SHADER_WRITE_BIT,
VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT,
VK_QUEUE_FAMILY_IGNORED,
VK_QUEUE_FAMILY_IGNORED,
Unwrap(meshBuffer),
0,
bufInfo.size,
};
// wait for writing to finish
DoPipelineBarrier(cmd, 1, &meshbufbarrier);
VkBufferCopy bufcopy = {
0, 0, bufInfo.size,
};
// copy to readback buffer
ObjDisp(dev)->CmdCopyBuffer(Unwrap(cmd), Unwrap(meshBuffer), Unwrap(readbackBuffer), 1, &bufcopy);
meshbufbarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
meshbufbarrier.dstAccessMask = VK_ACCESS_HOST_READ_BIT;
meshbufbarrier.buffer = Unwrap(readbackBuffer);
// wait for copy to finish
DoPipelineBarrier(cmd, 1, &meshbufbarrier);
vkr = ObjDisp(dev)->EndCommandBuffer(Unwrap(cmd));
RDCASSERTEQUAL(vkr, VK_SUCCESS);
// submit & flush so that we don't have to keep pipeline around for a while
m_pDriver->SubmitCmds();
m_pDriver->FlushQ();
}
else
{
// create buffer of sufficient size
// this can't just be bufStride * num unique indices per instance, as we don't
// have a compact 0-based index to index into the buffer. We must use
// index-minIndex which is 0-based but potentially sparse, so this buffer may
// be more or less wasteful
VkBufferCreateInfo bufInfo = {
VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
NULL,
0,
numVerts * RDCMAX(1U, drawcall->numInstances) * bufStride,
0,
};
bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
bufInfo.usage |= VK_BUFFER_USAGE_TRANSFER_DST_BIT;
bufInfo.usage |= VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
bufInfo.usage |= VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
vkr = m_pDriver->vkCreateBuffer(dev, &bufInfo, NULL, &meshBuffer);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
vkr = m_pDriver->vkCreateBuffer(dev, &bufInfo, NULL, &readbackBuffer);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
VkMemoryRequirements mrq = {0};
m_pDriver->vkGetBufferMemoryRequirements(dev, meshBuffer, &mrq);
VkMemoryAllocateInfo allocInfo = {
VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, NULL, mrq.size,
m_pDriver->GetGPULocalMemoryIndex(mrq.memoryTypeBits),
};
vkr = m_pDriver->vkAllocateMemory(dev, &allocInfo, NULL, &meshMem);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
vkr = m_pDriver->vkBindBufferMemory(dev, meshBuffer, meshMem, 0);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
m_pDriver->vkGetBufferMemoryRequirements(dev, readbackBuffer, &mrq);
allocInfo.memoryTypeIndex = m_pDriver->GetReadbackMemoryIndex(mrq.memoryTypeBits);
vkr = m_pDriver->vkAllocateMemory(dev, &allocInfo, NULL, &readbackMem);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
vkr = m_pDriver->vkBindBufferMemory(dev, readbackBuffer, readbackMem, 0);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
VkBufferMemoryBarrier meshbufbarrier = {
VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
NULL,
VK_ACCESS_HOST_WRITE_BIT,
VK_ACCESS_INDEX_READ_BIT,
VK_QUEUE_FAMILY_IGNORED,
VK_QUEUE_FAMILY_IGNORED,
Unwrap(uniqIdxBuf),
0,
indices.size() * sizeof(uint32_t),
};
VkCommandBuffer cmd = m_pDriver->GetNextCmd();
VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, NULL,
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT};
vkr = ObjDisp(dev)->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
// wait for upload to finish
DoPipelineBarrier(cmd, 1, &meshbufbarrier);
// fill destination buffer with 0s to ensure unwritten vertices have sane data
ObjDisp(dev)->CmdFillBuffer(Unwrap(cmd), Unwrap(meshBuffer), 0, bufInfo.size, 0);
// wait to finish
meshbufbarrier.buffer = Unwrap(meshBuffer);
meshbufbarrier.size = bufInfo.size;
DoPipelineBarrier(cmd, 1, &meshbufbarrier);
// set bufSize
bufSize = numVerts * RDCMAX(1U, drawcall->numInstances) * bufStride;
// bind unique'd ibuffer
modifiedstate.ibuffer.bytewidth = 4;
modifiedstate.ibuffer.offs = 0;
modifiedstate.ibuffer.buf = GetResID(uniqIdxBuf);
// vkUpdateDescriptorSet desc set to point to buffer
VkDescriptorBufferInfo fetchdesc = {0};
fetchdesc.buffer = meshBuffer;
fetchdesc.offset = 0;
fetchdesc.range = bufInfo.size;
VkWriteDescriptorSet write = {
VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, m_MeshFetchDescSet, 0, 0, 1,
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, NULL, &fetchdesc, NULL};
m_pDriver->vkUpdateDescriptorSets(dev, 1, &write, 0, NULL);
// do single draw
modifiedstate.BeginRenderPassAndApplyState(cmd);
ObjDisp(cmd)->CmdDrawIndexed(Unwrap(cmd), (uint32_t)indices.size(), drawcall->numInstances, 0,
drawcall->baseVertex, drawcall->instanceOffset);
modifiedstate.EndRenderPass(cmd);
// rebase existing index buffer to point to the right elements in our stream-out'd
// vertex buffer
// An index buffer could be something like: 500, 520, 518, 553, 554, 556
// in which case we can't use the existing index buffer without filling 499 slots of vertex
// data with padding. Instead we rebase the indices based on the smallest index so it becomes
// 0, 1, 2, 1, 3, 2 and then that matches our stream-out'd buffer.
//
// Note that there could also be gaps in the indices as above which must remain as
// we don't have a 0-based dense 'vertex id' to base our SSBO indexing off, only index value.
if(index16)
{
for(uint32_t i = 0; i < numIndices; i++)
idx16[i] = idx16[i] - uint16_t(minIndex);
}
else
{
for(uint32_t i = 0; i < numIndices; i++)
idx32[i] -= minIndex;
}
// upload rebased memory
byte *idxData = NULL;
vkr = m_pDriver->vkMapMemory(m_Device, idxBufMem, 0, VK_WHOLE_SIZE, 0, (void **)&idxData);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
memcpy(idxData, idx32, numIndices * idxsize);
m_pDriver->vkUnmapMemory(m_Device, idxBufMem);
meshbufbarrier.buffer = Unwrap(idxBuf);
meshbufbarrier.size = numIndices * idxsize;
// wait for upload to finish
DoPipelineBarrier(cmd, 1, &meshbufbarrier);
// wait for mesh output writing to finish
meshbufbarrier.buffer = Unwrap(meshBuffer);
meshbufbarrier.size = bufSize;
meshbufbarrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
meshbufbarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
DoPipelineBarrier(cmd, 1, &meshbufbarrier);
VkBufferCopy bufcopy = {
0, 0, bufInfo.size,
};
// copy to readback buffer
ObjDisp(dev)->CmdCopyBuffer(Unwrap(cmd), Unwrap(meshBuffer), Unwrap(readbackBuffer), 1, &bufcopy);
meshbufbarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
meshbufbarrier.dstAccessMask = VK_ACCESS_HOST_READ_BIT;
meshbufbarrier.buffer = Unwrap(readbackBuffer);
// wait for copy to finish
DoPipelineBarrier(cmd, 1, &meshbufbarrier);
vkr = ObjDisp(dev)->EndCommandBuffer(Unwrap(cmd));
RDCASSERTEQUAL(vkr, VK_SUCCESS);
// submit & flush so that we don't have to keep pipeline around for a while
m_pDriver->SubmitCmds();
m_pDriver->FlushQ();
}
// readback mesh data
byte *byteData = NULL;
vkr = m_pDriver->vkMapMemory(m_Device, readbackMem, 0, VK_WHOLE_SIZE, 0, (void **)&byteData);
// do near/far calculations
float nearp = 0.1f;
float farp = 100.0f;
Vec4f *pos0 = (Vec4f *)byteData;
bool found = false;
// expect position at the start of the buffer, as system values are sorted first
// and position is the first value
for(uint32_t i = 1; refl->OutputSig[0].systemValue == eAttr_Position && i < numVerts; i++)
{
//////////////////////////////////////////////////////////////////////////////////
// derive near/far, assuming a standard perspective matrix
//
// the transformation from from pre-projection {Z,W} to post-projection {Z,W}
// is linear. So we can say Zpost = Zpre*m + c . Here we assume Wpre = 1
// and we know Wpost = Zpre from the perspective matrix.
// we can then see from the perspective matrix that
// m = F/(F-N)
// c = -(F*N)/(F-N)
//
// with re-arranging and substitution, we then get:
// N = -c/m
// F = c/(1-m)
//
// so if we can derive m and c then we can determine N and F. We can do this with
// two points, and we pick them reasonably distinct on z to reduce floating-point
// error
Vec4f *pos = (Vec4f *)(byteData + i * bufStride);
// skip invalid vertices (w=0)
if(pos->w != 0.0f && fabs(pos->w - pos0->w) > 0.01f && fabs(pos->z - pos0->z) > 0.01f)
{
Vec2f A(pos0->w, pos0->z);
Vec2f B(pos->w, pos->z);
float m = (B.y - A.y) / (B.x - A.x);
float c = B.y - B.x * m;
if(m == 1.0f)
continue;
if(-c / m <= 0.000001f)
continue;
nearp = -c / m;
farp = c / (1 - m);
found = true;
break;
}
}
// if we didn't find anything, all z's and w's were identical.
// If the z is positive and w greater for the first element then
// we detect this projection as reversed z with infinite far plane
if(!found && pos0->z > 0.0f && pos0->w > pos0->z)
{
nearp = pos0->z;
farp = FLT_MAX;
}
m_pDriver->vkUnmapMemory(m_Device, readbackMem);
// clean up temporary memories
m_pDriver->vkDestroyBuffer(m_Device, readbackBuffer, NULL);
m_pDriver->vkFreeMemory(m_Device, readbackMem, NULL);
if(uniqIdxBuf != VK_NULL_HANDLE)
{
m_pDriver->vkDestroyBuffer(m_Device, uniqIdxBuf, NULL);
m_pDriver->vkFreeMemory(m_Device, uniqIdxBufMem, NULL);
}
// fill out m_PostVSData
m_PostVSData[eventID].vsin.topo = topo;
m_PostVSData[eventID].vsout.topo = topo;
m_PostVSData[eventID].vsout.buf = meshBuffer;
m_PostVSData[eventID].vsout.bufmem = meshMem;
m_PostVSData[eventID].vsout.vertStride = bufStride;
m_PostVSData[eventID].vsout.nearPlane = nearp;
m_PostVSData[eventID].vsout.farPlane = farp;
m_PostVSData[eventID].vsout.useIndices = (drawcall->flags & eDraw_UseIBuffer) > 0;
m_PostVSData[eventID].vsout.numVerts = drawcall->numIndices;
m_PostVSData[eventID].vsout.instStride = 0;
if(drawcall->flags & eDraw_Instanced)
m_PostVSData[eventID].vsout.instStride = uint32_t(bufSize / RDCMAX(1U, drawcall->numInstances));
m_PostVSData[eventID].vsout.idxBuf = VK_NULL_HANDLE;
if(m_PostVSData[eventID].vsout.useIndices && idxBuf != VK_NULL_HANDLE)
{
m_PostVSData[eventID].vsout.idxBuf = idxBuf;
m_PostVSData[eventID].vsout.idxBufMem = idxBufMem;
m_PostVSData[eventID].vsout.idxFmt =
state.ibuffer.bytewidth == 2 ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32;
}
m_PostVSData[eventID].vsout.hasPosOut = refl->OutputSig[0].systemValue == eAttr_Position;
// delete pipeline layout
m_pDriver->vkDestroyPipelineLayout(dev, pipeLayout, NULL);
// delete pipeline
m_pDriver->vkDestroyPipeline(dev, pipe, NULL);
// delete shader/shader module
m_pDriver->vkDestroyShaderModule(dev, module, NULL);
}
MeshFormat VulkanDebugManager::GetPostVSBuffers(uint32_t eventID, uint32_t instID, MeshDataStage stage)
{
// go through any aliasing
if(m_PostVSAlias.find(eventID) != m_PostVSAlias.end())
eventID = m_PostVSAlias[eventID];
VulkanPostVSData postvs;
RDCEraseEl(postvs);
if(m_PostVSData.find(eventID) != m_PostVSData.end())
postvs = m_PostVSData[eventID];
VulkanPostVSData::StageData s = postvs.GetStage(stage);
MeshFormat ret;
if(s.useIndices && s.idxBuf != VK_NULL_HANDLE)
{
ret.idxbuf = GetResID(s.idxBuf);
ret.idxByteWidth = s.idxFmt == VK_INDEX_TYPE_UINT16 ? 2 : 4;
}
else
{
ret.idxbuf = ResourceId();
ret.idxByteWidth = 0;
}
ret.idxoffs = 0;
ret.baseVertex = 0;
if(s.buf != VK_NULL_HANDLE)
ret.buf = GetResID(s.buf);
else
ret.buf = ResourceId();
ret.offset = s.instStride * instID;
ret.stride = s.vertStride;
ret.compCount = 4;
ret.compByteWidth = 4;
ret.compType = eCompType_Float;
ret.specialFormat = eSpecial_Unknown;
ret.showAlpha = false;
ret.bgraOrder = false;
ret.topo = MakePrimitiveTopology(s.topo, 1);
ret.numVerts = s.numVerts;
ret.unproject = s.hasPosOut;
ret.nearPlane = s.nearPlane;
ret.farPlane = s.farPlane;
return ret;
}