mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-08-09 08:11:06 +00:00
3150 lines
113 KiB
C++
3150 lines
113 KiB
C++
/******************************************************************************
|
|
* The MIT License (MIT)
|
|
*
|
|
* Copyright (c) 2020 Baldur Karlsson
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
******************************************************************************/
|
|
|
|
#include "core/settings.h"
|
|
#include "driver/shaders/spirv/spirv_debug.h"
|
|
#include "driver/shaders/spirv/spirv_editor.h"
|
|
#include "driver/shaders/spirv/spirv_op_helpers.h"
|
|
#include "maths/formatpacking.h"
|
|
#include "vk_core.h"
|
|
#include "vk_debug.h"
|
|
#include "vk_replay.h"
|
|
#include "vk_shader_cache.h"
|
|
|
|
#undef None
|
|
|
|
RDOC_DEBUG_CONFIG(rdcstr, Vulkan_Debug_PSDebugDumpDirPath, "",
|
|
"Path to dump pixel shader debugging generated SPIR-V files.");
|
|
RDOC_DEBUG_CONFIG(bool, Vulkan_Debug_DisableBufferDeviceAddress, false,
|
|
"Disable use of buffer device address for PS Input fetch.");
|
|
|
|
struct DescSetBindingSnapshot
|
|
{
|
|
rdcarray<VkDescriptorImageInfo> imageInfos;
|
|
rdcarray<VkDescriptorBufferInfo> buffers;
|
|
rdcarray<VkBufferView> texelBuffers;
|
|
|
|
template <typename T>
|
|
const rdcarray<T> &get() const;
|
|
};
|
|
|
|
template <>
|
|
const rdcarray<VkDescriptorImageInfo> &DescSetBindingSnapshot::get() const
|
|
{
|
|
return imageInfos;
|
|
}
|
|
template <>
|
|
const rdcarray<VkDescriptorBufferInfo> &DescSetBindingSnapshot::get() const
|
|
{
|
|
return buffers;
|
|
}
|
|
template <>
|
|
const rdcarray<VkBufferView> &DescSetBindingSnapshot::get() const
|
|
{
|
|
return texelBuffers;
|
|
}
|
|
|
|
struct DescSetSnapshot
|
|
{
|
|
rdcarray<DescSetBindingSnapshot> bindings;
|
|
};
|
|
|
|
// should match the descriptor set layout created in ShaderDebugData::Init()
|
|
enum class ShaderDebugBind
|
|
{
|
|
Tex1D = 1,
|
|
First = Tex1D,
|
|
Tex2D,
|
|
Tex3D,
|
|
Tex2DMS,
|
|
Buffer,
|
|
Sampler,
|
|
Count,
|
|
};
|
|
|
|
struct Vec3i
|
|
{
|
|
int32_t x, y, z;
|
|
};
|
|
|
|
struct ShaderDebugParameters
|
|
{
|
|
uint32_t operation;
|
|
VkBool32 useGrad;
|
|
ShaderDebugBind dim;
|
|
Vec3i texel_uvw;
|
|
int texel_lod;
|
|
Vec3f uvw;
|
|
Vec3f ddx;
|
|
Vec3f ddy;
|
|
Vec3i offset;
|
|
int sampleIdx;
|
|
float compare;
|
|
float lod;
|
|
float minlod;
|
|
rdcspv::GatherChannel gatherChannel;
|
|
};
|
|
|
|
class VulkanAPIWrapper : public rdcspv::DebugAPIWrapper
|
|
{
|
|
rdcarray<DescSetSnapshot> m_DescSets;
|
|
|
|
public:
|
|
VulkanAPIWrapper(WrappedVulkan *vk, VulkanCreationInfo &creation, VkShaderStageFlagBits stage)
|
|
: m_DebugData(vk->GetReplay()->GetShaderDebugData()), m_Creation(creation)
|
|
{
|
|
m_pDriver = vk;
|
|
|
|
const VulkanRenderState &state = m_pDriver->GetRenderState();
|
|
|
|
const bool compute = (stage == VK_SHADER_STAGE_COMPUTE_BIT);
|
|
|
|
// snapshot descriptor set contents
|
|
const rdcarray<VulkanStatePipeline::DescriptorAndOffsets> &descSets =
|
|
compute ? state.compute.descSets : state.graphics.descSets;
|
|
|
|
const VulkanCreationInfo::Pipeline &pipe =
|
|
m_Creation.m_Pipeline[compute ? state.compute.pipeline : state.graphics.pipeline];
|
|
const VulkanCreationInfo::PipelineLayout &pipeLayout = m_Creation.m_PipelineLayout[pipe.layout];
|
|
|
|
for(const VkPushConstantRange &range : pipeLayout.pushRanges)
|
|
{
|
|
if(range.stageFlags & stage)
|
|
{
|
|
pushData.resize(RDCMAX((uint32_t)pushData.size(), range.offset + range.size));
|
|
|
|
RDCASSERT(range.offset + range.size < sizeof(state.pushconsts));
|
|
|
|
memcpy(pushData.data() + range.offset, state.pushconsts + range.offset, range.size);
|
|
}
|
|
}
|
|
|
|
m_DescSets.resize(RDCMIN(descSets.size(), pipeLayout.descSetLayouts.size()));
|
|
for(size_t set = 0; set < m_DescSets.size(); set++)
|
|
{
|
|
uint32_t dynamicOffset = 0;
|
|
|
|
DescSetSnapshot &dstSet = m_DescSets[set];
|
|
|
|
const rdcarray<DescriptorSetSlot *> &curBinds =
|
|
m_pDriver->GetCurrentDescSetBindings(descSets[set].descSet);
|
|
const DescSetLayout &setLayout = m_Creation.m_DescSetLayout[pipeLayout.descSetLayouts[set]];
|
|
|
|
for(size_t bind = 0; bind < setLayout.bindings.size(); bind++)
|
|
{
|
|
const DescSetLayout::Binding &bindLayout = setLayout.bindings[bind];
|
|
|
|
if(bindLayout.stageFlags & stage)
|
|
{
|
|
DescriptorSetSlot *curSlots = curBinds[bind];
|
|
|
|
dstSet.bindings.resize(bind + 1);
|
|
|
|
DescSetBindingSnapshot &dstBind = dstSet.bindings[bind];
|
|
|
|
switch(bindLayout.descriptorType)
|
|
{
|
|
case VK_DESCRIPTOR_TYPE_SAMPLER:
|
|
case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
|
|
case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
|
|
case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
|
|
case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT:
|
|
{
|
|
dstBind.imageInfos.resize(bindLayout.descriptorCount);
|
|
for(uint32_t i = 0; i < bindLayout.descriptorCount; i++)
|
|
{
|
|
dstBind.imageInfos[i].imageLayout = curSlots[i].imageInfo.imageLayout;
|
|
dstBind.imageInfos[i].imageView =
|
|
m_pDriver->GetResourceManager()->GetCurrentHandle<VkImageView>(
|
|
curSlots[i].imageInfo.imageView);
|
|
dstBind.imageInfos[i].sampler =
|
|
m_pDriver->GetResourceManager()->GetCurrentHandle<VkSampler>(
|
|
bindLayout.immutableSampler ? bindLayout.immutableSampler[i]
|
|
: curSlots[i].imageInfo.sampler);
|
|
}
|
|
break;
|
|
}
|
|
case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
|
|
case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER:
|
|
{
|
|
dstBind.texelBuffers.resize(bindLayout.descriptorCount);
|
|
for(uint32_t i = 0; i < bindLayout.descriptorCount; i++)
|
|
{
|
|
dstBind.texelBuffers[i] =
|
|
m_pDriver->GetResourceManager()->GetCurrentHandle<VkBufferView>(
|
|
curSlots[i].texelBufferView);
|
|
}
|
|
break;
|
|
}
|
|
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
|
|
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
|
|
{
|
|
dstBind.buffers.resize(bindLayout.descriptorCount);
|
|
for(uint32_t i = 0; i < bindLayout.descriptorCount; i++)
|
|
{
|
|
dstBind.buffers[i].offset = curSlots[i].bufferInfo.offset;
|
|
dstBind.buffers[i].range = curSlots[i].bufferInfo.range;
|
|
dstBind.buffers[i].buffer =
|
|
m_pDriver->GetResourceManager()->GetCurrentHandle<VkBuffer>(
|
|
curSlots[i].bufferInfo.buffer);
|
|
}
|
|
break;
|
|
}
|
|
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
|
|
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC:
|
|
{
|
|
dstBind.buffers.resize(bindLayout.descriptorCount);
|
|
for(uint32_t i = 0; i < bindLayout.descriptorCount; i++)
|
|
{
|
|
dstBind.buffers[i].offset = curSlots[i].bufferInfo.offset;
|
|
dstBind.buffers[i].range = curSlots[i].bufferInfo.range;
|
|
dstBind.buffers[i].buffer =
|
|
m_pDriver->GetResourceManager()->GetCurrentHandle<VkBuffer>(
|
|
curSlots[i].bufferInfo.buffer);
|
|
|
|
dstBind.buffers[i].offset += descSets[set].offsets[dynamicOffset++];
|
|
}
|
|
break;
|
|
}
|
|
default: RDCERR("Unexpected descriptor type");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
~VulkanAPIWrapper()
|
|
{
|
|
m_pDriver->FlushQ();
|
|
|
|
VkDevice dev = m_pDriver->GetDev();
|
|
for(auto it = m_SampleViews.begin(); it != m_SampleViews.end(); it++)
|
|
m_pDriver->vkDestroyImageView(dev, it->second, NULL);
|
|
for(auto it = m_BiasSamplers.begin(); it != m_BiasSamplers.end(); it++)
|
|
m_pDriver->vkDestroySampler(dev, it->second, NULL);
|
|
}
|
|
|
|
virtual void AddDebugMessage(MessageCategory c, MessageSeverity sv, MessageSource src,
|
|
rdcstr d) override
|
|
{
|
|
m_pDriver->AddDebugMessage(c, sv, src, d);
|
|
}
|
|
|
|
virtual void ReadConstantBufferValue(uint32_t set, uint32_t bind, uint32_t offset,
|
|
uint32_t byteSize, void *dst) override
|
|
{
|
|
rdcpair<uint32_t, uint32_t> key = make_rdcpair(set, bind);
|
|
auto insertIt = cbufferCache.insert(std::make_pair(key, bytebuf()));
|
|
bytebuf &data = insertIt.first->second;
|
|
if(insertIt.second)
|
|
{
|
|
if(set == PushConstantBindSet)
|
|
{
|
|
data = pushData;
|
|
}
|
|
else
|
|
{
|
|
// TODO handle arrays here
|
|
BindpointIndex index(set, bind, 0);
|
|
|
|
bool valid = true;
|
|
const VkDescriptorBufferInfo &bufData =
|
|
GetDescriptor<VkDescriptorBufferInfo>("reading constant buffer value", index, valid);
|
|
if(valid)
|
|
m_pDriver->GetDebugManager()->GetBufferData(GetResID(bufData.buffer), bufData.offset,
|
|
bufData.range, data);
|
|
}
|
|
}
|
|
|
|
if(offset + byteSize <= data.size())
|
|
memcpy(dst, data.data() + offset, byteSize);
|
|
}
|
|
|
|
virtual void FillInputValue(ShaderVariable &var, ShaderBuiltin builtin, uint32_t location,
|
|
uint32_t component) override
|
|
{
|
|
if(builtin != ShaderBuiltin::Undefined)
|
|
{
|
|
auto it = builtin_inputs.find(builtin);
|
|
if(it != builtin_inputs.end())
|
|
{
|
|
var.value = it->second.value;
|
|
return;
|
|
}
|
|
|
|
RDCERR("Couldn't get input for %s", ToStr(builtin).c_str());
|
|
return;
|
|
}
|
|
|
|
// TODO handle components
|
|
RDCASSERT(component == 0);
|
|
|
|
if(location < location_inputs.size())
|
|
{
|
|
var.value = location_inputs[location].value;
|
|
return;
|
|
}
|
|
|
|
RDCERR("Couldn't get input for %s at location=%u, component=%u", var.name.c_str(), location,
|
|
component);
|
|
}
|
|
|
|
virtual DerivativeDeltas GetDerivative(ShaderBuiltin builtin, uint32_t location,
|
|
uint32_t component) override
|
|
{
|
|
if(builtin != ShaderBuiltin::Undefined)
|
|
{
|
|
auto it = builtin_derivatives.find(builtin);
|
|
if(it != builtin_derivatives.end())
|
|
return it->second;
|
|
|
|
RDCERR("Couldn't get input for %s", ToStr(builtin).c_str());
|
|
return DerivativeDeltas();
|
|
}
|
|
|
|
// TODO handle components
|
|
RDCASSERT(component == 0);
|
|
|
|
if(location < location_derivatives.size())
|
|
return location_derivatives[location];
|
|
|
|
RDCERR("Couldn't get derivative for location=%u, component=%u", location, component);
|
|
return DerivativeDeltas();
|
|
}
|
|
|
|
bool CalculateSampleGather(rdcspv::ThreadState &lane, rdcspv::Op opcode,
|
|
DebugAPIWrapper::TextureType texType, BindpointIndex imageBind,
|
|
BindpointIndex samplerBind, const ShaderVariable &uv,
|
|
const ShaderVariable &ddxCalc, const ShaderVariable &ddyCalc,
|
|
const ShaderVariable &compare, rdcspv::GatherChannel gatherChannel,
|
|
const rdcspv::ImageOperandsAndParamDatas &operands,
|
|
ShaderVariable &output) override
|
|
{
|
|
ShaderDebugParameters params = {};
|
|
|
|
const bool buffer = (texType & DebugAPIWrapper::Buffer_Texture) != 0;
|
|
const bool uintTex = (texType & DebugAPIWrapper::UInt_Texture) != 0;
|
|
const bool sintTex = (texType & DebugAPIWrapper::SInt_Texture) != 0;
|
|
|
|
// fetch the right type of descriptor depending on if we're buffer or not
|
|
BindpointIndex invalidIndex(-1, -1, ~0U);
|
|
bool valid = true;
|
|
rdcstr access = StringFormat::Fmt("performing %s operation", ToStr(opcode).c_str());
|
|
const VkDescriptorImageInfo &imageInfo =
|
|
buffer ? GetDescriptor<VkDescriptorImageInfo>(access, invalidIndex, valid)
|
|
: GetDescriptor<VkDescriptorImageInfo>(access, imageBind, valid);
|
|
const VkBufferView &bufferView = buffer
|
|
? GetDescriptor<VkBufferView>(access, imageBind, valid)
|
|
: GetDescriptor<VkBufferView>(access, invalidIndex, valid);
|
|
|
|
// fetch the sampler (if there's no sampler, this will silently return dummy data without
|
|
// marking invalid
|
|
const VkDescriptorImageInfo &samplerInfo =
|
|
GetDescriptor<VkDescriptorImageInfo>(access, samplerBind, valid);
|
|
|
|
// if any descriptor lookup failed, return now
|
|
if(!valid)
|
|
return false;
|
|
|
|
VkMarkerRegion markerRegion("CalculateSampleGather");
|
|
|
|
VkSampler sampler = samplerInfo.sampler;
|
|
VkImageView view = imageInfo.imageView;
|
|
VkImageLayout layout = imageInfo.imageLayout;
|
|
|
|
// promote view to Array view
|
|
|
|
const VulkanCreationInfo::ImageView &viewProps = m_Creation.m_ImageView[GetResID(view)];
|
|
const VulkanCreationInfo::Image &imageProps = m_Creation.m_Image[viewProps.image];
|
|
VkImageType imageType = imageProps.type;
|
|
uint32_t samples = (uint32_t)imageProps.samples;
|
|
|
|
VkDevice dev = m_pDriver->GetDev();
|
|
|
|
// how many co-ordinates should there be
|
|
int coords = 0, gradCoords = 0;
|
|
switch(viewProps.viewType)
|
|
{
|
|
case VK_IMAGE_VIEW_TYPE_1D:
|
|
coords = 1;
|
|
gradCoords = 1;
|
|
break;
|
|
case VK_IMAGE_VIEW_TYPE_2D:
|
|
coords = 2;
|
|
gradCoords = 2;
|
|
break;
|
|
case VK_IMAGE_VIEW_TYPE_3D:
|
|
coords = 3;
|
|
gradCoords = 3;
|
|
break;
|
|
case VK_IMAGE_VIEW_TYPE_CUBE:
|
|
coords = 3;
|
|
gradCoords = 3;
|
|
break;
|
|
case VK_IMAGE_VIEW_TYPE_1D_ARRAY:
|
|
coords = 2;
|
|
gradCoords = 1;
|
|
break;
|
|
case VK_IMAGE_VIEW_TYPE_2D_ARRAY:
|
|
coords = 3;
|
|
gradCoords = 2;
|
|
break;
|
|
case VK_IMAGE_VIEW_TYPE_CUBE_ARRAY:
|
|
coords = 4;
|
|
gradCoords = 3;
|
|
break;
|
|
case VK_IMAGE_VIEW_TYPE_RANGE_SIZE:
|
|
case VK_IMAGE_VIEW_TYPE_MAX_ENUM:
|
|
RDCERR("Invalid image view type %s", ToStr(viewProps.viewType).c_str());
|
|
return false;
|
|
}
|
|
|
|
switch(imageType)
|
|
{
|
|
case VK_IMAGE_TYPE_1D: params.dim = ShaderDebugBind::Tex1D; break;
|
|
case VK_IMAGE_TYPE_2D:
|
|
params.dim = ShaderDebugBind::Tex2D;
|
|
if(samples > 1)
|
|
params.dim = ShaderDebugBind::Tex2DMS;
|
|
break;
|
|
case VK_IMAGE_TYPE_3D: params.dim = ShaderDebugBind::Tex3D; break;
|
|
default:
|
|
{
|
|
RDCERR("Unsupported image type %s", ToStr(imageType).c_str());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if(buffer)
|
|
{
|
|
params.dim = ShaderDebugBind::Buffer;
|
|
coords = gradCoords = 1;
|
|
}
|
|
|
|
// handle query opcodes now
|
|
switch(opcode)
|
|
{
|
|
case rdcspv::Op::ImageQueryLevels:
|
|
{
|
|
output.value.u.x = viewProps.range.levelCount;
|
|
if(viewProps.range.levelCount == VK_REMAINING_MIP_LEVELS)
|
|
output.value.u.x = imageProps.mipLevels - viewProps.range.baseMipLevel;
|
|
return true;
|
|
}
|
|
case rdcspv::Op::ImageQuerySamples:
|
|
{
|
|
output.value.u.x = (uint32_t)imageProps.samples;
|
|
return true;
|
|
}
|
|
case rdcspv::Op::ImageQuerySize:
|
|
case rdcspv::Op::ImageQuerySizeLod:
|
|
{
|
|
uint32_t mip = viewProps.range.baseMipLevel;
|
|
|
|
if(opcode == rdcspv::Op::ImageQuerySizeLod)
|
|
mip += lane.GetSrc(operands.lod).value.u.x;
|
|
|
|
int i = 0;
|
|
output.value.uv[i++] = RDCMAX(1U, imageProps.extent.width >> mip);
|
|
if(coords >= 2)
|
|
output.value.uv[i++] = RDCMAX(1U, imageProps.extent.height >> mip);
|
|
if(viewProps.viewType == VK_IMAGE_VIEW_TYPE_3D)
|
|
output.value.uv[i++] = RDCMAX(1U, imageProps.extent.depth >> mip);
|
|
|
|
if(viewProps.viewType == VK_IMAGE_VIEW_TYPE_1D_ARRAY ||
|
|
viewProps.viewType == VK_IMAGE_VIEW_TYPE_2D_ARRAY)
|
|
output.value.uv[i++] = imageProps.arrayLayers;
|
|
else if(viewProps.viewType == VK_IMAGE_VIEW_TYPE_CUBE_ARRAY)
|
|
output.value.uv[i++] = imageProps.arrayLayers / 6;
|
|
|
|
return true;
|
|
}
|
|
default: break;
|
|
}
|
|
|
|
// create our own view (if we haven't already for this view) so we can promote to array
|
|
VkImageView sampleView = m_SampleViews[GetResID(view)];
|
|
if(sampleView == VK_NULL_HANDLE)
|
|
{
|
|
VkImageViewCreateInfo viewInfo = {VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
|
|
viewInfo.image = m_pDriver->GetResourceManager()->GetCurrentHandle<VkImage>(viewProps.image);
|
|
viewInfo.format = viewProps.format;
|
|
viewInfo.viewType = viewProps.viewType;
|
|
if(viewInfo.viewType == VK_IMAGE_VIEW_TYPE_1D)
|
|
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_1D_ARRAY;
|
|
else if(viewInfo.viewType == VK_IMAGE_VIEW_TYPE_2D)
|
|
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
|
|
|
|
viewInfo.components = viewProps.componentMapping;
|
|
viewInfo.subresourceRange = viewProps.range;
|
|
|
|
// if KHR_maintenance2 is available, ensure we have sampled usage available
|
|
VkImageViewUsageCreateInfo usageCreateInfo = {VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO};
|
|
if(m_pDriver->GetExtensions(NULL).ext_KHR_maintenance2)
|
|
{
|
|
usageCreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT;
|
|
viewInfo.pNext = &usageCreateInfo;
|
|
}
|
|
|
|
VkResult vkr = m_pDriver->vkCreateImageView(dev, &viewInfo, NULL, &sampleView);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
m_SampleViews[GetResID(view)] = sampleView;
|
|
}
|
|
|
|
if(operands.flags & rdcspv::ImageOperands::Bias)
|
|
{
|
|
float bias = lane.GetSrc(operands.bias).value.f.x;
|
|
if(bias != 0.0f)
|
|
{
|
|
// bias can only be used with implicit lod operations, but we want to do everything with
|
|
// explicit lod operations. So we instead push the bias into a new sampler, which is
|
|
// entirely equivalent.
|
|
|
|
// first check to see if we have one already, since the bias is probably going to be
|
|
// coherent.
|
|
SamplerBiasKey key = {GetResID(sampler), bias};
|
|
|
|
auto insertIt = m_BiasSamplers.insert(std::make_pair(key, VkSampler()));
|
|
if(insertIt.second)
|
|
{
|
|
const VulkanCreationInfo::Sampler &samplerProps = m_Creation.m_Sampler[key.first];
|
|
|
|
VkSamplerCreateInfo sampInfo = {VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO};
|
|
sampInfo.magFilter = samplerProps.magFilter;
|
|
sampInfo.minFilter = samplerProps.minFilter;
|
|
sampInfo.mipmapMode = samplerProps.mipmapMode;
|
|
sampInfo.addressModeU = samplerProps.address[0];
|
|
sampInfo.addressModeV = samplerProps.address[1];
|
|
sampInfo.addressModeW = samplerProps.address[2];
|
|
sampInfo.mipLodBias = samplerProps.mipLodBias;
|
|
sampInfo.maxAnisotropy = samplerProps.maxAnisotropy;
|
|
sampInfo.compareEnable = samplerProps.compareEnable;
|
|
sampInfo.compareOp = samplerProps.compareOp;
|
|
sampInfo.minLod = samplerProps.minLod;
|
|
sampInfo.maxLod = samplerProps.maxLod;
|
|
sampInfo.borderColor = samplerProps.borderColor;
|
|
sampInfo.unnormalizedCoordinates = samplerProps.unnormalizedCoordinates;
|
|
|
|
VkSamplerReductionModeCreateInfo reductionInfo = {
|
|
VK_STRUCTURE_TYPE_SAMPLER_REDUCTION_MODE_CREATE_INFO};
|
|
if(samplerProps.reductionMode != VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE)
|
|
{
|
|
reductionInfo.reductionMode = samplerProps.reductionMode;
|
|
reductionInfo.pNext = sampInfo.pNext;
|
|
sampInfo.pNext = &reductionInfo;
|
|
}
|
|
|
|
VkSamplerYcbcrConversionInfo ycbcrInfo = {VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO};
|
|
if(samplerProps.ycbcr != ResourceId())
|
|
{
|
|
ycbcrInfo.conversion =
|
|
m_pDriver->GetResourceManager()->GetCurrentHandle<VkSamplerYcbcrConversion>(
|
|
viewProps.image);
|
|
ycbcrInfo.pNext = sampInfo.pNext;
|
|
sampInfo.pNext = &ycbcrInfo;
|
|
}
|
|
|
|
// now add the shader's bias on
|
|
sampInfo.mipLodBias += bias;
|
|
|
|
VkResult vkr = m_pDriver->vkCreateSampler(dev, &sampInfo, NULL, &sampler);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
insertIt.first->second = sampler;
|
|
}
|
|
else
|
|
{
|
|
sampler = insertIt.first->second;
|
|
}
|
|
}
|
|
}
|
|
|
|
params.operation = (uint32_t)opcode;
|
|
|
|
switch(opcode)
|
|
{
|
|
case rdcspv::Op::ImageFetch:
|
|
{
|
|
// co-ordinates after the used ones are read as 0s. This allows us to then read an implicit
|
|
// 0 for array layer when we promote accesses to arrays.
|
|
params.texel_uvw.x = uv.value.u.x;
|
|
if(coords >= 2)
|
|
params.texel_uvw.y = uv.value.u.y;
|
|
if(coords >= 3)
|
|
params.texel_uvw.z = uv.value.u.z;
|
|
|
|
if(!buffer && operands.flags & rdcspv::ImageOperands::Lod)
|
|
params.texel_lod = lane.GetSrc(operands.lod).value.i.x;
|
|
else
|
|
params.texel_lod = 0;
|
|
|
|
if(operands.flags & rdcspv::ImageOperands::Sample)
|
|
params.sampleIdx = lane.GetSrc(operands.sample).value.u.x;
|
|
|
|
break;
|
|
}
|
|
case rdcspv::Op::ImageSampleExplicitLod:
|
|
case rdcspv::Op::ImageSampleImplicitLod:
|
|
{
|
|
params.uvw.x = uv.value.f.x;
|
|
if(coords >= 2)
|
|
params.uvw.y = uv.value.f.y;
|
|
if(coords >= 3)
|
|
params.uvw.z = uv.value.f.z;
|
|
|
|
if(operands.flags & rdcspv::ImageOperands::MinLod)
|
|
params.minlod = lane.GetSrc(operands.minLod).value.f.x;
|
|
|
|
if(operands.flags & rdcspv::ImageOperands::Lod)
|
|
{
|
|
params.lod = lane.GetSrc(operands.lod).value.f.x;
|
|
params.useGrad = VK_FALSE;
|
|
}
|
|
else if(operands.flags & rdcspv::ImageOperands::Grad)
|
|
{
|
|
ShaderVariable ddx = lane.GetSrc(operands.grad.first);
|
|
ShaderVariable ddy = lane.GetSrc(operands.grad.second);
|
|
|
|
params.useGrad = VK_TRUE;
|
|
|
|
params.ddx.x = ddx.value.f.x;
|
|
if(gradCoords >= 2)
|
|
params.ddx.y = ddx.value.f.y;
|
|
if(gradCoords >= 3)
|
|
params.ddx.z = ddx.value.f.z;
|
|
|
|
params.ddy.x = ddy.value.f.x;
|
|
if(gradCoords >= 2)
|
|
params.ddy.y = ddy.value.f.y;
|
|
if(gradCoords >= 3)
|
|
params.ddy.z = ddy.value.f.z;
|
|
}
|
|
|
|
if(opcode == rdcspv::Op::ImageSampleImplicitLod)
|
|
{
|
|
// use grad to sub in for the implicit lod
|
|
params.useGrad = VK_TRUE;
|
|
|
|
params.ddx.x = ddxCalc.value.f.x;
|
|
if(gradCoords >= 2)
|
|
params.ddx.y = ddxCalc.value.f.y;
|
|
if(gradCoords >= 3)
|
|
params.ddx.z = ddxCalc.value.f.z;
|
|
|
|
params.ddy.x = ddyCalc.value.f.x;
|
|
if(gradCoords >= 2)
|
|
params.ddy.y = ddyCalc.value.f.y;
|
|
if(gradCoords >= 3)
|
|
params.ddy.z = ddyCalc.value.f.z;
|
|
}
|
|
|
|
if(operands.flags & rdcspv::ImageOperands::ConstOffset)
|
|
{
|
|
ShaderVariable constOffset = lane.GetSrc(operands.constOffset);
|
|
params.offset.x = constOffset.value.i.x;
|
|
if(gradCoords >= 2)
|
|
params.offset.y = constOffset.value.i.y;
|
|
if(gradCoords >= 3)
|
|
params.offset.z = constOffset.value.i.z;
|
|
}
|
|
else if(operands.flags & rdcspv::ImageOperands::Offset)
|
|
{
|
|
ShaderVariable offset = lane.GetSrc(operands.offset);
|
|
params.offset.x = offset.value.i.x;
|
|
if(gradCoords >= 2)
|
|
params.offset.y = offset.value.i.y;
|
|
if(gradCoords >= 3)
|
|
params.offset.z = offset.value.i.z;
|
|
}
|
|
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
RDCERR("Unsupported opcode %s", ToStr(opcode).c_str());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
VkPipeline pipe = MakePipe(params, uintTex, sintTex);
|
|
|
|
VkDescriptorImageInfo samplerWriteInfo = {Unwrap(sampler), VK_NULL_HANDLE,
|
|
VK_IMAGE_LAYOUT_UNDEFINED};
|
|
VkDescriptorImageInfo imageWriteInfo = {VK_NULL_HANDLE, Unwrap(sampleView), layout};
|
|
|
|
VkWriteDescriptorSet writeSets[] = {
|
|
{
|
|
VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, Unwrap(m_DebugData.DescSet),
|
|
(uint32_t)params.dim, 0, 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, &imageWriteInfo, NULL, NULL,
|
|
},
|
|
{
|
|
VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, Unwrap(m_DebugData.DescSet),
|
|
(uint32_t)ShaderDebugBind::Sampler, 0, 1, VK_DESCRIPTOR_TYPE_SAMPLER, &samplerWriteInfo,
|
|
NULL, NULL,
|
|
},
|
|
};
|
|
|
|
if(buffer)
|
|
{
|
|
writeSets[0].pTexelBufferView = &bufferView;
|
|
writeSets[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER;
|
|
}
|
|
|
|
ObjDisp(dev)->UpdateDescriptorSets(Unwrap(dev), sampler != VK_NULL_HANDLE ? 2 : 1, writeSets, 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};
|
|
|
|
VkResult vkr = ObjDisp(cmd)->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
VkClearValue clear = {};
|
|
|
|
VkRenderPassBeginInfo rpbegin = {
|
|
VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
|
NULL,
|
|
Unwrap(m_DebugData.RenderPass),
|
|
Unwrap(m_DebugData.Framebuffer),
|
|
{{0, 0}, {1, 1}},
|
|
1,
|
|
&clear,
|
|
};
|
|
ObjDisp(cmd)->CmdBeginRenderPass(Unwrap(cmd), &rpbegin, VK_SUBPASS_CONTENTS_INLINE);
|
|
|
|
ObjDisp(cmd)->CmdBindPipeline(Unwrap(cmd), VK_PIPELINE_BIND_POINT_GRAPHICS, Unwrap(pipe));
|
|
ObjDisp(cmd)->CmdBindDescriptorSets(Unwrap(cmd), VK_PIPELINE_BIND_POINT_GRAPHICS,
|
|
Unwrap(m_DebugData.PipeLayout), 0, 1,
|
|
UnwrapPtr(m_DebugData.DescSet), 0, NULL);
|
|
|
|
ObjDisp(cmd)->CmdDraw(Unwrap(cmd), 4, 1, 0, 0);
|
|
|
|
ObjDisp(cmd)->CmdEndRenderPass(Unwrap(cmd));
|
|
|
|
VkBufferImageCopy region = {
|
|
0, sizeof(Vec4f), 1, {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, {0, 0, 0}, {1, 1, 1},
|
|
};
|
|
ObjDisp(cmd)->CmdCopyImageToBuffer(Unwrap(cmd), Unwrap(m_DebugData.Image),
|
|
VK_IMAGE_LAYOUT_GENERAL,
|
|
Unwrap(m_DebugData.ReadbackBuffer.buf), 1, ®ion);
|
|
|
|
vkr = ObjDisp(cmd)->EndCommandBuffer(Unwrap(cmd));
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
m_pDriver->SubmitCmds();
|
|
m_pDriver->FlushQ();
|
|
}
|
|
|
|
m_pDriver->vkDestroyPipeline(dev, pipe, NULL);
|
|
|
|
Vec4f *ret = (Vec4f *)m_DebugData.ReadbackBuffer.Map(NULL, 0);
|
|
|
|
memcpy(output.value.uv, ret, sizeof(Vec4f));
|
|
|
|
m_DebugData.ReadbackBuffer.Unmap();
|
|
|
|
return true;
|
|
}
|
|
|
|
virtual bool CalculateMathOp(rdcspv::ThreadState &lane, rdcspv::GLSLstd450 op,
|
|
const rdcarray<ShaderVariable> ¶ms, ShaderVariable &output) override
|
|
{
|
|
RDCASSERT(params.size() <= 3, params.size());
|
|
|
|
if(m_DebugData.MathPipe == VK_NULL_HANDLE)
|
|
{
|
|
ShaderDebugParameters pipeParams = {};
|
|
pipeParams.operation = (uint32_t)rdcspv::Op::ExtInst;
|
|
m_DebugData.MathPipe = MakePipe(pipeParams, false, false);
|
|
}
|
|
|
|
{
|
|
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 = ObjDisp(cmd)->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
VkClearValue clear = {};
|
|
|
|
VkRenderPassBeginInfo rpbegin = {
|
|
VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
|
NULL,
|
|
Unwrap(m_DebugData.RenderPass),
|
|
Unwrap(m_DebugData.Framebuffer),
|
|
{{0, 0}, {1, 1}},
|
|
1,
|
|
&clear,
|
|
};
|
|
ObjDisp(cmd)->CmdBeginRenderPass(Unwrap(cmd), &rpbegin, VK_SUBPASS_CONTENTS_INLINE);
|
|
|
|
ObjDisp(cmd)->CmdBindPipeline(Unwrap(cmd), VK_PIPELINE_BIND_POINT_GRAPHICS,
|
|
Unwrap(m_DebugData.MathPipe));
|
|
|
|
// push the parameters
|
|
for(size_t i = 0; i < params.size(); i++)
|
|
ObjDisp(cmd)->CmdPushConstants(Unwrap(cmd), Unwrap(m_DebugData.PipeLayout),
|
|
VK_SHADER_STAGE_ALL, uint32_t(sizeof(Vec4f) * i),
|
|
sizeof(Vec4f), params[i].value.fv);
|
|
|
|
// push the operation afterwards
|
|
ObjDisp(cmd)->CmdPushConstants(Unwrap(cmd), Unwrap(m_DebugData.PipeLayout),
|
|
VK_SHADER_STAGE_ALL, sizeof(Vec4f) * 3, sizeof(uint32_t), &op);
|
|
|
|
ObjDisp(cmd)->CmdDraw(Unwrap(cmd), 4, 1, 0, 0);
|
|
|
|
ObjDisp(cmd)->CmdEndRenderPass(Unwrap(cmd));
|
|
|
|
VkBufferImageCopy region = {
|
|
0, sizeof(Vec4f), 1, {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, {0, 0, 0}, {1, 1, 1},
|
|
};
|
|
ObjDisp(cmd)->CmdCopyImageToBuffer(Unwrap(cmd), Unwrap(m_DebugData.Image),
|
|
VK_IMAGE_LAYOUT_GENERAL,
|
|
Unwrap(m_DebugData.ReadbackBuffer.buf), 1, ®ion);
|
|
|
|
vkr = ObjDisp(cmd)->EndCommandBuffer(Unwrap(cmd));
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
m_pDriver->SubmitCmds();
|
|
m_pDriver->FlushQ();
|
|
}
|
|
|
|
Vec4f *ret = (Vec4f *)m_DebugData.ReadbackBuffer.Map(NULL, 0);
|
|
|
|
memcpy(output.value.uv, ret, sizeof(Vec4f));
|
|
|
|
m_DebugData.ReadbackBuffer.Unmap();
|
|
|
|
// these two operations change the type of the output
|
|
if(op == rdcspv::GLSLstd450::Length || op == rdcspv::GLSLstd450::Distance)
|
|
output.columns = 1;
|
|
|
|
return true;
|
|
}
|
|
|
|
std::map<ShaderBuiltin, ShaderVariable> builtin_inputs;
|
|
rdcarray<ShaderVariable> location_inputs;
|
|
|
|
std::map<ShaderBuiltin, DerivativeDeltas> builtin_derivatives;
|
|
rdcarray<DerivativeDeltas> location_derivatives;
|
|
|
|
private:
|
|
WrappedVulkan *m_pDriver = NULL;
|
|
ShaderDebugData &m_DebugData;
|
|
VulkanCreationInfo &m_Creation;
|
|
|
|
std::map<ResourceId, VkImageView> m_SampleViews;
|
|
|
|
typedef rdcpair<ResourceId, float> SamplerBiasKey;
|
|
std::map<SamplerBiasKey, VkSampler> m_BiasSamplers;
|
|
|
|
bytebuf pushData;
|
|
|
|
std::map<rdcpair<uint32_t, uint32_t>, bytebuf> cbufferCache;
|
|
template <typename T>
|
|
const T &GetDescriptor(const rdcstr &access, BindpointIndex index, bool &valid)
|
|
{
|
|
static T dummy = {};
|
|
|
|
if(index.bindset < 0)
|
|
{
|
|
// invalid index, return a dummy data but don't mark as invalid
|
|
return dummy;
|
|
}
|
|
|
|
if(index.bindset >= m_DescSets.count())
|
|
{
|
|
m_pDriver->AddDebugMessage(
|
|
MessageCategory::Execution, MessageSeverity::High, MessageSource::RuntimeWarning,
|
|
StringFormat::Fmt(
|
|
"Out of bounds access to unbound descriptor set %u (binding %u) when %s",
|
|
index.bindset, index.bind, access.c_str()));
|
|
valid = false;
|
|
return dummy;
|
|
}
|
|
|
|
const DescSetSnapshot &setData = m_DescSets[index.bindset];
|
|
|
|
if(index.bind >= setData.bindings.count())
|
|
{
|
|
m_pDriver->AddDebugMessage(
|
|
MessageCategory::Execution, MessageSeverity::High, MessageSource::RuntimeWarning,
|
|
StringFormat::Fmt(
|
|
"Out of bounds access to non-existant descriptor set %u binding %u when %s",
|
|
index.bindset, index.bind, access.c_str()));
|
|
valid = false;
|
|
return dummy;
|
|
}
|
|
|
|
const DescSetBindingSnapshot &bindData = setData.bindings[index.bind];
|
|
|
|
const rdcarray<T> &elemData = bindData.get<T>();
|
|
|
|
if(elemData.empty())
|
|
{
|
|
m_pDriver->AddDebugMessage(
|
|
MessageCategory::Execution, MessageSeverity::High, MessageSource::RuntimeWarning,
|
|
StringFormat::Fmt("descriptor set %u binding %u is not bound, when %s", index.bindset,
|
|
index.bind, access.c_str()));
|
|
valid = false;
|
|
return dummy;
|
|
}
|
|
|
|
if(index.arrayIndex >= elemData.size())
|
|
{
|
|
m_pDriver->AddDebugMessage(MessageCategory::Execution, MessageSeverity::High,
|
|
MessageSource::RuntimeWarning,
|
|
StringFormat::Fmt("descriptor set %u binding %u has %zu "
|
|
"descriptors, index %u is out of bounds when %s",
|
|
index.bindset, index.bind, elemData.size(),
|
|
index.arrayIndex, access.c_str()));
|
|
valid = false;
|
|
return dummy;
|
|
}
|
|
|
|
return elemData[index.arrayIndex];
|
|
}
|
|
|
|
VkPipeline MakePipe(const ShaderDebugParameters ¶ms, bool uintTex, bool sintTex)
|
|
{
|
|
VkSpecializationMapEntry specMaps[sizeof(params) / sizeof(uint32_t)];
|
|
for(size_t i = 0; i < ARRAY_COUNT(specMaps); i++)
|
|
{
|
|
specMaps[i].constantID = uint32_t(i);
|
|
specMaps[i].offset = uint32_t(sizeof(uint32_t) * i);
|
|
specMaps[i].size = sizeof(uint32_t);
|
|
}
|
|
|
|
VkSpecializationInfo specInfo = {};
|
|
specInfo.dataSize = sizeof(params);
|
|
specInfo.pData = ¶ms;
|
|
specInfo.mapEntryCount = ARRAY_COUNT(specMaps);
|
|
specInfo.pMapEntries = specMaps;
|
|
|
|
uint32_t shaderIndex = 0;
|
|
if(uintTex)
|
|
shaderIndex = 1;
|
|
else if(sintTex)
|
|
shaderIndex = 2;
|
|
|
|
if(params.operation == (uint32_t)rdcspv::Op::ExtInst)
|
|
shaderIndex = 3;
|
|
|
|
if(m_DebugData.Module[shaderIndex] == VK_NULL_HANDLE)
|
|
{
|
|
rdcarray<uint32_t> spirv;
|
|
|
|
if(shaderIndex == 3)
|
|
GenerateMathShaderModule(spirv);
|
|
else
|
|
GenerateSamplingShaderModule(spirv, uintTex, sintTex);
|
|
|
|
VkShaderModuleCreateInfo moduleCreateInfo = {VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
|
|
moduleCreateInfo.pCode = spirv.data();
|
|
moduleCreateInfo.codeSize = spirv.size() * sizeof(uint32_t);
|
|
|
|
VkResult vkr = m_pDriver->vkCreateShaderModule(m_pDriver->GetDev(), &moduleCreateInfo, NULL,
|
|
&m_DebugData.Module[shaderIndex]);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
const char *filename[] = {
|
|
"/debug_psgather_float.spv", "/debug_psgather_uint.spv", "/debug_psgather_sint.spv",
|
|
"/debug_psmath.spv",
|
|
};
|
|
|
|
if(!Vulkan_Debug_PSDebugDumpDirPath.empty())
|
|
FileIO::WriteAll(Vulkan_Debug_PSDebugDumpDirPath + filename[shaderIndex], spirv);
|
|
}
|
|
|
|
const VkPipelineShaderStageCreateInfo shaderStages[2] = {
|
|
{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, NULL, 0, VK_SHADER_STAGE_VERTEX_BIT,
|
|
m_pDriver->GetShaderCache()->GetBuiltinModule(BuiltinShader::BlitVS), "main", NULL},
|
|
{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, NULL, 0, VK_SHADER_STAGE_FRAGMENT_BIT,
|
|
m_DebugData.Module[shaderIndex], "main", &specInfo},
|
|
};
|
|
|
|
const VkPipelineDynamicStateCreateInfo dynamicState = {
|
|
VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
|
|
};
|
|
|
|
const VkPipelineMultisampleStateCreateInfo msaa = {
|
|
VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, NULL, 0, VK_SAMPLE_COUNT_1_BIT,
|
|
};
|
|
|
|
const VkPipelineDepthStencilStateCreateInfo depthStencil = {
|
|
VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
|
|
};
|
|
|
|
VkPipelineColorBlendAttachmentState colAttach = {};
|
|
colAttach.colorWriteMask = 0xf;
|
|
|
|
const VkPipelineColorBlendStateCreateInfo colorBlend = {
|
|
VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
|
|
NULL,
|
|
0,
|
|
false,
|
|
VK_LOGIC_OP_NO_OP,
|
|
1,
|
|
&colAttach,
|
|
};
|
|
|
|
const VkPipelineVertexInputStateCreateInfo vertexInput = {
|
|
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
|
|
};
|
|
|
|
VkPipelineInputAssemblyStateCreateInfo inputAssembly = {
|
|
VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
|
|
};
|
|
|
|
inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
|
|
|
|
VkRect2D s = {};
|
|
s.extent.width = s.extent.height = 1;
|
|
|
|
VkViewport v = {};
|
|
v.width = v.height = v.maxDepth = 1.0f;
|
|
|
|
VkPipelineViewportStateCreateInfo viewScissor = {
|
|
VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
|
|
};
|
|
viewScissor.viewportCount = viewScissor.scissorCount = 1;
|
|
viewScissor.pScissors = &s;
|
|
viewScissor.pViewports = &v;
|
|
|
|
VkPipelineRasterizationStateCreateInfo raster = {
|
|
VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
|
|
};
|
|
|
|
raster.lineWidth = 1.0f;
|
|
|
|
const VkGraphicsPipelineCreateInfo graphicsPipeInfo = {
|
|
VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
|
|
NULL,
|
|
0,
|
|
2,
|
|
shaderStages,
|
|
&vertexInput,
|
|
&inputAssembly,
|
|
NULL, // tess
|
|
&viewScissor,
|
|
&raster,
|
|
&msaa,
|
|
&depthStencil,
|
|
&colorBlend,
|
|
&dynamicState,
|
|
m_DebugData.PipeLayout,
|
|
m_DebugData.RenderPass,
|
|
0, // sub pass
|
|
VK_NULL_HANDLE, // base pipeline handle
|
|
-1, // base pipeline index
|
|
};
|
|
|
|
VkPipeline pipe = VK_NULL_HANDLE;
|
|
VkResult vkr = m_pDriver->vkCreateGraphicsPipelines(m_pDriver->GetDev(), VK_NULL_HANDLE, 1,
|
|
&graphicsPipeInfo, NULL, &pipe);
|
|
if(vkr != VK_SUCCESS)
|
|
{
|
|
RDCERR("Failed creating debug pipeline");
|
|
return VK_NULL_HANDLE;
|
|
}
|
|
|
|
return pipe;
|
|
}
|
|
|
|
void GenerateMathShaderModule(rdcarray<uint32_t> &spirv)
|
|
{
|
|
rdcspv::Editor editor(spirv);
|
|
|
|
// create as SPIR-V 1.0 for best compatibility
|
|
editor.CreateEmpty(1, 0);
|
|
|
|
editor.AddCapability(rdcspv::Capability::Shader);
|
|
|
|
rdcspv::Id entryId = editor.MakeId();
|
|
|
|
editor.AddOperation(
|
|
editor.Begin(rdcspv::Section::MemoryModel),
|
|
rdcspv::OpMemoryModel(rdcspv::AddressingModel::Logical, rdcspv::MemoryModel::GLSL450));
|
|
|
|
rdcspv::Id glsl450 = editor.ImportExtInst("GLSL.std.450");
|
|
|
|
rdcspv::Id u32 = editor.DeclareType(rdcspv::scalar<uint32_t>());
|
|
rdcspv::Id f32 = editor.DeclareType(rdcspv::scalar<float>());
|
|
rdcspv::Id v4f32 = editor.DeclareType(rdcspv::Vector(rdcspv::scalar<float>(), 4));
|
|
|
|
rdcspv::Id pushStructID =
|
|
editor.AddType(rdcspv::OpTypeStruct(editor.MakeId(), {v4f32, v4f32, v4f32, u32}));
|
|
editor.AddDecoration(rdcspv::OpDecorate(pushStructID, rdcspv::Decoration::Block));
|
|
editor.AddDecoration(rdcspv::OpMemberDecorate(
|
|
pushStructID, 0, rdcspv::DecorationParam<rdcspv::Decoration::Offset>(0)));
|
|
editor.AddDecoration(rdcspv::OpMemberDecorate(
|
|
pushStructID, 1, rdcspv::DecorationParam<rdcspv::Decoration::Offset>(sizeof(Vec4f))));
|
|
editor.AddDecoration(rdcspv::OpMemberDecorate(
|
|
pushStructID, 2, rdcspv::DecorationParam<rdcspv::Decoration::Offset>(sizeof(Vec4f) * 2)));
|
|
editor.AddDecoration(rdcspv::OpMemberDecorate(
|
|
pushStructID, 3, rdcspv::DecorationParam<rdcspv::Decoration::Offset>(sizeof(Vec4f) * 3)));
|
|
editor.SetMemberName(pushStructID, 0, "a");
|
|
editor.SetMemberName(pushStructID, 1, "b");
|
|
editor.SetMemberName(pushStructID, 2, "c");
|
|
editor.SetMemberName(pushStructID, 3, "op");
|
|
|
|
rdcspv::Id pushPtrType =
|
|
editor.DeclareType(rdcspv::Pointer(pushStructID, rdcspv::StorageClass::PushConstant));
|
|
rdcspv::Id pushVar = editor.AddVariable(
|
|
rdcspv::OpVariable(pushPtrType, editor.MakeId(), rdcspv::StorageClass::PushConstant));
|
|
editor.SetName(pushVar, "pushData");
|
|
|
|
rdcspv::Id pushv4f32Type =
|
|
editor.DeclareType(rdcspv::Pointer(v4f32, rdcspv::StorageClass::PushConstant));
|
|
rdcspv::Id pushu32Type =
|
|
editor.DeclareType(rdcspv::Pointer(u32, rdcspv::StorageClass::PushConstant));
|
|
|
|
rdcspv::Id outPtrType = editor.DeclareType(rdcspv::Pointer(v4f32, rdcspv::StorageClass::Output));
|
|
|
|
rdcspv::Id outVar = editor.AddVariable(
|
|
rdcspv::OpVariable(outPtrType, editor.MakeId(), rdcspv::StorageClass::Output));
|
|
editor.AddDecoration(
|
|
rdcspv::OpDecorate(outVar, rdcspv::DecorationParam<rdcspv::Decoration::Location>(0)));
|
|
|
|
editor.SetName(outVar, "output");
|
|
|
|
// register the entry point
|
|
editor.AddOperation(
|
|
editor.Begin(rdcspv::Section::EntryPoints),
|
|
rdcspv::OpEntryPoint(rdcspv::ExecutionModel::Fragment, entryId, "main", {outVar}));
|
|
editor.AddOperation(editor.Begin(rdcspv::Section::ExecutionMode),
|
|
rdcspv::OpExecutionMode(entryId, rdcspv::ExecutionMode::OriginUpperLeft));
|
|
|
|
rdcspv::Id voidType = editor.DeclareType(rdcspv::scalar<void>());
|
|
rdcspv::Id funcType = editor.DeclareType(rdcspv::FunctionType(voidType, {}));
|
|
|
|
rdcspv::OperationList func;
|
|
func.add(rdcspv::OpFunction(voidType, entryId, rdcspv::FunctionControl::None, funcType));
|
|
func.add(rdcspv::OpLabel(editor.MakeId()));
|
|
|
|
rdcspv::Id consts[] = {
|
|
editor.AddConstantImmediate<uint32_t>(0), editor.AddConstantImmediate<uint32_t>(1),
|
|
editor.AddConstantImmediate<uint32_t>(2), editor.AddConstantImmediate<uint32_t>(3),
|
|
};
|
|
|
|
// load the parameters and the op
|
|
rdcspv::Id aPtr =
|
|
func.add(rdcspv::OpAccessChain(pushv4f32Type, editor.MakeId(), pushVar, {consts[0]}));
|
|
rdcspv::Id bPtr =
|
|
func.add(rdcspv::OpAccessChain(pushv4f32Type, editor.MakeId(), pushVar, {consts[1]}));
|
|
rdcspv::Id cPtr =
|
|
func.add(rdcspv::OpAccessChain(pushv4f32Type, editor.MakeId(), pushVar, {consts[2]}));
|
|
rdcspv::Id opPtr =
|
|
func.add(rdcspv::OpAccessChain(pushu32Type, editor.MakeId(), pushVar, {consts[3]}));
|
|
rdcspv::Id a = func.add(rdcspv::OpLoad(v4f32, editor.MakeId(), aPtr));
|
|
rdcspv::Id b = func.add(rdcspv::OpLoad(v4f32, editor.MakeId(), bPtr));
|
|
rdcspv::Id c = func.add(rdcspv::OpLoad(v4f32, editor.MakeId(), cPtr));
|
|
rdcspv::Id opParam = func.add(rdcspv::OpLoad(u32, editor.MakeId(), opPtr));
|
|
|
|
rdcspv::Id breakLabel = editor.MakeId();
|
|
rdcspv::Id defaultLabel = editor.MakeId();
|
|
|
|
rdcarray<rdcspv::PairLiteralIntegerIdRef> targets;
|
|
|
|
rdcspv::OperationList cases;
|
|
|
|
// all these operations take one parameter and only operate on floats (possibly vectors)
|
|
for(rdcspv::GLSLstd450 op : {
|
|
rdcspv::GLSLstd450::Sin, rdcspv::GLSLstd450::Cos,
|
|
rdcspv::GLSLstd450::Tan, rdcspv::GLSLstd450::Asin,
|
|
rdcspv::GLSLstd450::Acos, rdcspv::GLSLstd450::Atan,
|
|
rdcspv::GLSLstd450::Sinh, rdcspv::GLSLstd450::Cosh,
|
|
rdcspv::GLSLstd450::Tanh, rdcspv::GLSLstd450::Asinh,
|
|
rdcspv::GLSLstd450::Acosh, rdcspv::GLSLstd450::Atanh,
|
|
rdcspv::GLSLstd450::Exp, rdcspv::GLSLstd450::Log,
|
|
rdcspv::GLSLstd450::Exp2, rdcspv::GLSLstd450::Log2,
|
|
rdcspv::GLSLstd450::Sqrt, rdcspv::GLSLstd450::InverseSqrt,
|
|
rdcspv::GLSLstd450::Normalize,
|
|
})
|
|
{
|
|
rdcspv::Id label = editor.MakeId();
|
|
targets.push_back({(uint32_t)op, label});
|
|
|
|
cases.add(rdcspv::OpLabel(label));
|
|
rdcspv::Id result = cases.add(rdcspv::OpGLSL450(v4f32, editor.MakeId(), glsl450, op, {a}));
|
|
cases.add(rdcspv::OpStore(outVar, result));
|
|
cases.add(rdcspv::OpBranch(breakLabel));
|
|
}
|
|
|
|
// these take two parameters, but are otherwise identical
|
|
for(rdcspv::GLSLstd450 op : {rdcspv::GLSLstd450::Atan2, rdcspv::GLSLstd450::Pow})
|
|
{
|
|
rdcspv::Id label = editor.MakeId();
|
|
targets.push_back({(uint32_t)op, label});
|
|
|
|
cases.add(rdcspv::OpLabel(label));
|
|
rdcspv::Id result = cases.add(rdcspv::OpGLSL450(v4f32, editor.MakeId(), glsl450, op, {a, b}));
|
|
cases.add(rdcspv::OpStore(outVar, result));
|
|
cases.add(rdcspv::OpBranch(breakLabel));
|
|
}
|
|
|
|
rdcspv::Id zerof = editor.AddConstantImmediate<float>(0.0f);
|
|
|
|
{
|
|
rdcspv::GLSLstd450 op = rdcspv::GLSLstd450::Fma;
|
|
|
|
rdcspv::Id label = editor.MakeId();
|
|
targets.push_back({(uint32_t)op, label});
|
|
|
|
cases.add(rdcspv::OpLabel(label));
|
|
rdcspv::Id result =
|
|
cases.add(rdcspv::OpGLSL450(v4f32, editor.MakeId(), glsl450, op, {a, b, c}));
|
|
cases.add(rdcspv::OpStore(outVar, result));
|
|
cases.add(rdcspv::OpBranch(breakLabel));
|
|
}
|
|
|
|
// these ones are special
|
|
{
|
|
rdcspv::GLSLstd450 op = rdcspv::GLSLstd450::Length;
|
|
|
|
rdcspv::Id label = editor.MakeId();
|
|
targets.push_back({(uint32_t)op, label});
|
|
|
|
cases.add(rdcspv::OpLabel(label));
|
|
rdcspv::Id result = cases.add(rdcspv::OpGLSL450(f32, editor.MakeId(), glsl450, op, {a}));
|
|
rdcspv::Id resultvec = cases.add(
|
|
rdcspv::OpCompositeConstruct(v4f32, editor.MakeId(), {result, zerof, zerof, zerof}));
|
|
cases.add(rdcspv::OpStore(outVar, resultvec));
|
|
cases.add(rdcspv::OpBranch(breakLabel));
|
|
}
|
|
|
|
{
|
|
rdcspv::GLSLstd450 op = rdcspv::GLSLstd450::Distance;
|
|
|
|
rdcspv::Id label = editor.MakeId();
|
|
targets.push_back({(uint32_t)op, label});
|
|
|
|
cases.add(rdcspv::OpLabel(label));
|
|
rdcspv::Id result = cases.add(rdcspv::OpGLSL450(f32, editor.MakeId(), glsl450, op, {a, b}));
|
|
rdcspv::Id resultvec = cases.add(
|
|
rdcspv::OpCompositeConstruct(v4f32, editor.MakeId(), {result, zerof, zerof, zerof}));
|
|
cases.add(rdcspv::OpStore(outVar, resultvec));
|
|
cases.add(rdcspv::OpBranch(breakLabel));
|
|
}
|
|
|
|
{
|
|
rdcspv::GLSLstd450 op = rdcspv::GLSLstd450::Refract;
|
|
|
|
rdcspv::Id label = editor.MakeId();
|
|
targets.push_back({(uint32_t)op, label});
|
|
|
|
cases.add(rdcspv::OpLabel(label));
|
|
rdcspv::Id eta = cases.add(rdcspv::OpCompositeExtract(f32, editor.MakeId(), c, {0}));
|
|
rdcspv::Id result =
|
|
cases.add(rdcspv::OpGLSL450(v4f32, editor.MakeId(), glsl450, op, {a, b, eta}));
|
|
cases.add(rdcspv::OpStore(outVar, result));
|
|
cases.add(rdcspv::OpBranch(breakLabel));
|
|
}
|
|
|
|
func.add(rdcspv::OpSelectionMerge(breakLabel, rdcspv::SelectionControl::None));
|
|
func.add(rdcspv::OpSwitch(opParam, defaultLabel, targets));
|
|
|
|
func.append(cases);
|
|
|
|
// default: store NULL data
|
|
func.add(rdcspv::OpLabel(defaultLabel));
|
|
func.add(rdcspv::OpStore(outVar,
|
|
editor.AddConstant(rdcspv::OpConstantNull(v4f32, editor.MakeId()))));
|
|
func.add(rdcspv::OpBranch(breakLabel));
|
|
|
|
func.add(rdcspv::OpLabel(breakLabel));
|
|
func.add(rdcspv::OpReturn());
|
|
func.add(rdcspv::OpFunctionEnd());
|
|
|
|
editor.AddFunction(func);
|
|
}
|
|
|
|
void GenerateSamplingShaderModule(rdcarray<uint32_t> &spirv, bool uintTex, bool sintTex)
|
|
{
|
|
// this could be done as a glsl shader, but glslang has some bugs compiling the specialisation
|
|
// constants, so we generate it by hand - which isn't too hard
|
|
|
|
rdcspv::Editor editor(spirv);
|
|
|
|
// create as SPIR-V 1.0 for best compatibility
|
|
editor.CreateEmpty(1, 0);
|
|
|
|
editor.AddCapability(rdcspv::Capability::Shader);
|
|
editor.AddCapability(rdcspv::Capability::Sampled1D);
|
|
editor.AddCapability(rdcspv::Capability::SampledBuffer);
|
|
|
|
if(m_pDriver->GetDeviceFeatures().shaderResourceMinLod)
|
|
editor.AddCapability(rdcspv::Capability::MinLod);
|
|
if(m_pDriver->GetDeviceFeatures().shaderImageGatherExtended)
|
|
editor.AddCapability(rdcspv::Capability::ImageGatherExtended);
|
|
|
|
rdcspv::Id entryId = editor.MakeId();
|
|
|
|
editor.AddOperation(
|
|
editor.Begin(rdcspv::Section::MemoryModel),
|
|
rdcspv::OpMemoryModel(rdcspv::AddressingModel::Logical, rdcspv::MemoryModel::GLSL450));
|
|
|
|
rdcspv::Id u32 = editor.DeclareType(rdcspv::scalar<uint32_t>());
|
|
rdcspv::Id i32 = editor.DeclareType(rdcspv::scalar<int32_t>());
|
|
rdcspv::Id f32 = editor.DeclareType(rdcspv::scalar<float>());
|
|
|
|
rdcspv::Id v2i32 = editor.DeclareType(rdcspv::Vector(rdcspv::scalar<int32_t>(), 2));
|
|
rdcspv::Id v3i32 = editor.DeclareType(rdcspv::Vector(rdcspv::scalar<int32_t>(), 3));
|
|
rdcspv::Id v2f32 = editor.DeclareType(rdcspv::Vector(rdcspv::scalar<float>(), 2));
|
|
rdcspv::Id v3f32 = editor.DeclareType(rdcspv::Vector(rdcspv::scalar<float>(), 3));
|
|
|
|
rdcspv::Scalar base = rdcspv::scalar<float>();
|
|
if(uintTex)
|
|
base = rdcspv::scalar<uint32_t>();
|
|
else if(sintTex)
|
|
base = rdcspv::scalar<int32_t>();
|
|
|
|
rdcspv::Id resultType = editor.DeclareType(rdcspv::Vector(base, 4));
|
|
|
|
// add specialisation constants for all the parameters
|
|
#define SPEC_ID(name) uint32_t(offsetof(ShaderDebugParameters, name) / sizeof(uint32_t))
|
|
|
|
#define DECL_SPECID(type, name, value) \
|
|
rdcspv::Id name = editor.AddSpecConstantImmediate<type>(0U, SPEC_ID(value)); \
|
|
editor.SetName(name, "spec_" #name);
|
|
|
|
DECL_SPECID(uint32_t, operation, operation);
|
|
DECL_SPECID(bool, useGrad, useGrad);
|
|
DECL_SPECID(uint32_t, dim, dim);
|
|
DECL_SPECID(int32_t, texel_u, texel_uvw.x);
|
|
DECL_SPECID(int32_t, texel_v, texel_uvw.y);
|
|
DECL_SPECID(int32_t, texel_w, texel_uvw.z);
|
|
DECL_SPECID(int32_t, texel_lod, texel_lod);
|
|
DECL_SPECID(float, u, uvw.x);
|
|
DECL_SPECID(float, v, uvw.y);
|
|
DECL_SPECID(float, w, uvw.z);
|
|
DECL_SPECID(float, dudx, ddx.x);
|
|
DECL_SPECID(float, dvdx, ddx.y);
|
|
DECL_SPECID(float, dwdx, ddx.z);
|
|
DECL_SPECID(float, dudy, ddy.x);
|
|
DECL_SPECID(float, dvdy, ddy.y);
|
|
DECL_SPECID(float, dwdy, ddy.z);
|
|
DECL_SPECID(int32_t, offset_x, offset.x);
|
|
DECL_SPECID(int32_t, offset_y, offset.y);
|
|
DECL_SPECID(int32_t, offset_z, offset.z);
|
|
DECL_SPECID(int32_t, sampleIdx, sampleIdx);
|
|
DECL_SPECID(float, compare, compare);
|
|
DECL_SPECID(float, lod, lod);
|
|
DECL_SPECID(float, minlod, minlod);
|
|
DECL_SPECID(int32_t, gatherChannel, gatherChannel);
|
|
|
|
rdcspv::Id texel_uv = editor.AddConstant(
|
|
rdcspv::OpSpecConstantComposite(v2i32, editor.MakeId(), {texel_u, texel_v}));
|
|
rdcspv::Id texel_uvw = editor.AddConstant(
|
|
rdcspv::OpSpecConstantComposite(v3i32, editor.MakeId(), {texel_u, texel_v, texel_w}));
|
|
|
|
editor.SetName(texel_uv, "texel_uv");
|
|
editor.SetName(texel_uvw, "texel_uvw");
|
|
|
|
rdcspv::Id uv =
|
|
editor.AddConstant(rdcspv::OpSpecConstantComposite(v2f32, editor.MakeId(), {u, v}));
|
|
rdcspv::Id uvw =
|
|
editor.AddConstant(rdcspv::OpSpecConstantComposite(v3f32, editor.MakeId(), {u, v, w}));
|
|
|
|
editor.SetName(uv, "uv");
|
|
editor.SetName(uvw, "uvw");
|
|
|
|
rdcspv::Id ddx_uv =
|
|
editor.AddConstant(rdcspv::OpSpecConstantComposite(v2f32, editor.MakeId(), {dudx, dvdx}));
|
|
rdcspv::Id ddx_uvw = editor.AddConstant(
|
|
rdcspv::OpSpecConstantComposite(v3f32, editor.MakeId(), {dudx, dvdx, dwdx}));
|
|
|
|
editor.SetName(ddx_uv, "ddx_uv");
|
|
editor.SetName(ddx_uvw, "ddx_uvw");
|
|
|
|
rdcspv::Id ddy_uv =
|
|
editor.AddConstant(rdcspv::OpSpecConstantComposite(v2f32, editor.MakeId(), {dudy, dvdy}));
|
|
rdcspv::Id ddy_uvw = editor.AddConstant(
|
|
rdcspv::OpSpecConstantComposite(v3f32, editor.MakeId(), {dudy, dvdy, dwdy}));
|
|
|
|
editor.SetName(ddy_uv, "ddy_uv");
|
|
editor.SetName(ddy_uvw, "ddy_uvw");
|
|
|
|
rdcspv::Id offset_xy = editor.AddConstant(
|
|
rdcspv::OpSpecConstantComposite(v2i32, editor.MakeId(), {offset_x, offset_y}));
|
|
rdcspv::Id offset_xyz = editor.AddConstant(
|
|
rdcspv::OpSpecConstantComposite(v3i32, editor.MakeId(), {offset_x, offset_y, offset_z}));
|
|
|
|
editor.SetName(offset_xy, "offset_xy");
|
|
editor.SetName(offset_xyz, "offset_xyz");
|
|
|
|
// create the output. It's always a 4-wide vector
|
|
rdcspv::Id outPtrType =
|
|
editor.DeclareType(rdcspv::Pointer(resultType, rdcspv::StorageClass::Output));
|
|
|
|
rdcspv::Id outVar = editor.AddVariable(
|
|
rdcspv::OpVariable(outPtrType, editor.MakeId(), rdcspv::StorageClass::Output));
|
|
editor.AddDecoration(
|
|
rdcspv::OpDecorate(outVar, rdcspv::DecorationParam<rdcspv::Decoration::Location>(0)));
|
|
|
|
editor.SetName(outVar, "output");
|
|
|
|
rdcspv::ImageFormat unk = rdcspv::ImageFormat::Unknown;
|
|
|
|
// create the five textures and sampler
|
|
rdcspv::Id texSampTypes[(uint32_t)ShaderDebugBind::Count] = {
|
|
rdcspv::Id(),
|
|
editor.DeclareType(rdcspv::Image(base, rdcspv::Dim::_1D, 0, 1, 0, 1, unk)),
|
|
editor.DeclareType(rdcspv::Image(base, rdcspv::Dim::_2D, 0, 1, 0, 1, unk)),
|
|
editor.DeclareType(rdcspv::Image(base, rdcspv::Dim::_3D, 0, 0, 0, 1, unk)),
|
|
editor.DeclareType(rdcspv::Image(base, rdcspv::Dim::_2D, 0, 1, 1, 1, unk)),
|
|
editor.DeclareType(rdcspv::Image(base, rdcspv::Dim::Buffer, 0, 0, 0, 1, unk)),
|
|
editor.DeclareType(rdcspv::Sampler()),
|
|
};
|
|
rdcspv::Id texSampVars[(uint32_t)ShaderDebugBind::Count];
|
|
rdcspv::Id texSampCombinedTypes[(uint32_t)ShaderDebugBind::Count] = {
|
|
rdcspv::Id(),
|
|
editor.DeclareType(rdcspv::SampledImage(texSampTypes[1])),
|
|
editor.DeclareType(rdcspv::SampledImage(texSampTypes[2])),
|
|
editor.DeclareType(rdcspv::SampledImage(texSampTypes[3])),
|
|
editor.DeclareType(rdcspv::SampledImage(texSampTypes[4])),
|
|
editor.DeclareType(rdcspv::SampledImage(texSampTypes[5])),
|
|
rdcspv::Id(),
|
|
};
|
|
|
|
for(size_t i = (size_t)ShaderDebugBind::First; i < (size_t)ShaderDebugBind::Count; i++)
|
|
{
|
|
rdcspv::Id ptrType =
|
|
editor.DeclareType(rdcspv::Pointer(texSampTypes[i], rdcspv::StorageClass::UniformConstant));
|
|
|
|
texSampVars[i] = editor.AddVariable(
|
|
rdcspv::OpVariable(ptrType, editor.MakeId(), rdcspv::StorageClass::UniformConstant));
|
|
|
|
editor.AddDecoration(rdcspv::OpDecorate(
|
|
texSampVars[i], rdcspv::DecorationParam<rdcspv::Decoration::DescriptorSet>(0U)));
|
|
editor.AddDecoration(rdcspv::OpDecorate(
|
|
texSampVars[i], rdcspv::DecorationParam<rdcspv::Decoration::Binding>((uint32_t)i)));
|
|
}
|
|
|
|
editor.SetName(texSampVars[(size_t)ShaderDebugBind::Tex1D], "Tex1D");
|
|
editor.SetName(texSampVars[(size_t)ShaderDebugBind::Tex2D], "Tex2D");
|
|
editor.SetName(texSampVars[(size_t)ShaderDebugBind::Tex3D], "Tex3D");
|
|
editor.SetName(texSampVars[(size_t)ShaderDebugBind::Tex2DMS], "Tex2DMS");
|
|
editor.SetName(texSampVars[(size_t)ShaderDebugBind::Buffer], "Buffer");
|
|
editor.SetName(texSampVars[(size_t)ShaderDebugBind::Sampler], "Sampler");
|
|
|
|
rdcspv::Id sampVar = texSampVars[(size_t)ShaderDebugBind::Sampler];
|
|
|
|
// register the entry point
|
|
editor.AddOperation(
|
|
editor.Begin(rdcspv::Section::EntryPoints),
|
|
rdcspv::OpEntryPoint(rdcspv::ExecutionModel::Fragment, entryId, "main", {outVar}));
|
|
editor.AddOperation(editor.Begin(rdcspv::Section::ExecutionMode),
|
|
rdcspv::OpExecutionMode(entryId, rdcspv::ExecutionMode::OriginUpperLeft));
|
|
|
|
rdcspv::Id voidType = editor.DeclareType(rdcspv::scalar<void>());
|
|
rdcspv::Id funcType = editor.DeclareType(rdcspv::FunctionType(voidType, {}));
|
|
|
|
rdcspv::OperationList func;
|
|
func.add(rdcspv::OpFunction(voidType, entryId, rdcspv::FunctionControl::None, funcType));
|
|
func.add(rdcspv::OpLabel(editor.MakeId()));
|
|
|
|
// first store NULL data in, so the output is always initialised
|
|
|
|
rdcspv::Id breakLabel = editor.MakeId();
|
|
rdcspv::Id defaultLabel = editor.MakeId();
|
|
|
|
// combine the operation with the image type:
|
|
// operation * 10 + dim
|
|
RDCCOMPILE_ASSERT(size_t(ShaderDebugBind::Count) < 10, "Combining value ranges will overlap!");
|
|
rdcspv::Id switchVal = func.add(rdcspv::OpIMul(u32, editor.MakeId(), operation,
|
|
editor.AddConstantImmediate<uint32_t>(10U)));
|
|
switchVal = func.add(rdcspv::OpIAdd(u32, editor.MakeId(), switchVal, dim));
|
|
|
|
// switch on the combined operation and image type value
|
|
rdcarray<rdcspv::PairLiteralIntegerIdRef> targets;
|
|
|
|
rdcspv::OperationList cases;
|
|
|
|
rdcspv::Id texel_coord[(uint32_t)ShaderDebugBind::Count] = {
|
|
rdcspv::Id(),
|
|
texel_uv, // 1D - u and array
|
|
texel_uvw, // 2D - u,v and array
|
|
texel_uvw, // 3D - u,v,w
|
|
texel_uvw, // 2DMS - u,v and array
|
|
texel_u, // Buffer - u
|
|
};
|
|
|
|
rdcspv::Id coord[(uint32_t)ShaderDebugBind::Count] = {
|
|
rdcspv::Id(),
|
|
uv, // 1D - u and array
|
|
uvw, // 2D - u,v and array
|
|
uvw, // 3D - u,v,w
|
|
uvw, // 2DMS - u,v and array
|
|
u, // Buffer - u
|
|
};
|
|
|
|
rdcspv::Id offsets[(uint32_t)ShaderDebugBind::Count] = {
|
|
rdcspv::Id(),
|
|
offset_x, // 1D - u
|
|
offset_xy, // 2D - u,v
|
|
offset_xyz, // 3D - u,v,w
|
|
offset_xy, // 2DMS - u,v
|
|
offset_x, // Buffer - u
|
|
};
|
|
|
|
rdcspv::Id ddxs[(uint32_t)ShaderDebugBind::Count] = {
|
|
rdcspv::Id(),
|
|
dudx, // 1D - u
|
|
ddx_uv, // 2D - u,v
|
|
ddx_uvw, // 3D - u,v,w
|
|
ddx_uv, // 2DMS - u,v
|
|
dudx, // Buffer - u
|
|
};
|
|
|
|
rdcspv::Id ddys[(uint32_t)ShaderDebugBind::Count] = {
|
|
rdcspv::Id(),
|
|
dudy, // 1D - u
|
|
ddy_uv, // 2D - u,v
|
|
ddy_uvw, // 3D - u,v,w
|
|
ddy_uv, // 2DMS - u,v
|
|
dudy, // Buffer - u
|
|
};
|
|
|
|
uint32_t sampIdx = (uint32_t)ShaderDebugBind::Sampler;
|
|
|
|
for(uint32_t i = (uint32_t)ShaderDebugBind::First; i < (uint32_t)ShaderDebugBind::Count; i++)
|
|
{
|
|
if(i == sampIdx)
|
|
continue;
|
|
|
|
{
|
|
rdcspv::Op op = rdcspv::Op::ImageFetch;
|
|
|
|
rdcspv::Id label = editor.MakeId();
|
|
targets.push_back({(uint32_t)op * 10 + i, label});
|
|
|
|
rdcspv::ImageOperandsAndParamDatas imageOperands;
|
|
|
|
if(i != (uint32_t)ShaderDebugBind::Buffer && i != (uint32_t)ShaderDebugBind::Tex2DMS)
|
|
imageOperands.setLod(texel_lod);
|
|
|
|
cases.add(rdcspv::OpLabel(label));
|
|
rdcspv::Id loaded =
|
|
cases.add(rdcspv::OpLoad(texSampTypes[i], editor.MakeId(), texSampVars[i]));
|
|
rdcspv::Id sampleResult = cases.add(rdcspv::OpImageFetch(
|
|
resultType, editor.MakeId(), loaded, texel_coord[i], imageOperands));
|
|
cases.add(rdcspv::OpStore(outVar, sampleResult));
|
|
cases.add(rdcspv::OpBranch(breakLabel));
|
|
}
|
|
|
|
// buffers and multisampled images don't support sampling, so skip the other operations at
|
|
// this point
|
|
if(i == (uint32_t)ShaderDebugBind::Buffer || i == (uint32_t)ShaderDebugBind::Tex2DMS)
|
|
continue;
|
|
|
|
for(rdcspv::Op op : {rdcspv::Op::ImageSampleExplicitLod, rdcspv::Op::ImageSampleImplicitLod})
|
|
{
|
|
rdcspv::Id label = editor.MakeId();
|
|
targets.push_back({(uint32_t)op * 10 + i, label});
|
|
|
|
rdcspv::ImageOperandsAndParamDatas imageOperands;
|
|
|
|
imageOperands.setConstOffset(offsets[i]);
|
|
|
|
cases.add(rdcspv::OpLabel(label));
|
|
rdcspv::Id loadedImage =
|
|
cases.add(rdcspv::OpLoad(texSampTypes[i], editor.MakeId(), texSampVars[i]));
|
|
rdcspv::Id loadedSampler =
|
|
cases.add(rdcspv::OpLoad(texSampTypes[sampIdx], editor.MakeId(), texSampVars[sampIdx]));
|
|
|
|
rdcspv::Id mergeLabel = editor.MakeId();
|
|
rdcspv::Id gradCase = editor.MakeId();
|
|
rdcspv::Id lodCase = editor.MakeId();
|
|
cases.add(rdcspv::OpSelectionMerge(mergeLabel, rdcspv::SelectionControl::None));
|
|
cases.add(rdcspv::OpBranchConditional(useGrad, gradCase, lodCase));
|
|
|
|
rdcspv::Id lodResult;
|
|
{
|
|
cases.add(rdcspv::OpLabel(lodCase));
|
|
rdcspv::ImageOperandsAndParamDatas operands = imageOperands;
|
|
operands.setLod(lod);
|
|
rdcspv::Id combined = cases.add(rdcspv::OpSampledImage(
|
|
texSampCombinedTypes[i], editor.MakeId(), loadedImage, loadedSampler));
|
|
lodResult = cases.add(rdcspv::OpImageSampleExplicitLod(resultType, editor.MakeId(),
|
|
combined, coord[i], operands));
|
|
cases.add(rdcspv::OpBranch(mergeLabel));
|
|
}
|
|
|
|
rdcspv::Id gradResult;
|
|
{
|
|
cases.add(rdcspv::OpLabel(gradCase));
|
|
rdcspv::ImageOperandsAndParamDatas operands = imageOperands;
|
|
operands.setGrad(ddxs[i], ddys[i]);
|
|
rdcspv::Id combined = cases.add(rdcspv::OpSampledImage(
|
|
texSampCombinedTypes[i], editor.MakeId(), loadedImage, loadedSampler));
|
|
gradResult = cases.add(rdcspv::OpImageSampleExplicitLod(resultType, editor.MakeId(),
|
|
combined, coord[i], operands));
|
|
cases.add(rdcspv::OpBranch(mergeLabel));
|
|
}
|
|
|
|
cases.add(rdcspv::OpLabel(mergeLabel));
|
|
rdcspv::Id sampleResult = cases.add(rdcspv::OpPhi(
|
|
resultType, editor.MakeId(), {{lodResult, lodCase}, {gradResult, gradCase}}));
|
|
cases.add(rdcspv::OpStore(outVar, sampleResult));
|
|
cases.add(rdcspv::OpBranch(breakLabel));
|
|
}
|
|
}
|
|
|
|
func.add(rdcspv::OpSelectionMerge(breakLabel, rdcspv::SelectionControl::None));
|
|
func.add(rdcspv::OpSwitch(switchVal, defaultLabel, targets));
|
|
|
|
func.append(cases);
|
|
|
|
// default: store NULL data
|
|
func.add(rdcspv::OpLabel(defaultLabel));
|
|
func.add(rdcspv::OpStore(
|
|
outVar, editor.AddConstant(rdcspv::OpConstantNull(resultType, editor.MakeId()))));
|
|
func.add(rdcspv::OpBranch(breakLabel));
|
|
|
|
func.add(rdcspv::OpLabel(breakLabel));
|
|
func.add(rdcspv::OpReturn());
|
|
func.add(rdcspv::OpFunctionEnd());
|
|
|
|
editor.AddFunction(func);
|
|
}
|
|
};
|
|
|
|
enum StorageMode
|
|
{
|
|
Binding,
|
|
EXT_bda,
|
|
KHR_bda,
|
|
};
|
|
|
|
enum class InputSpecConstant
|
|
{
|
|
Address = 0,
|
|
ArrayLength,
|
|
DestX,
|
|
DestY,
|
|
Count,
|
|
};
|
|
|
|
static const uint32_t validMagicNumber = 12345;
|
|
|
|
struct PSHit
|
|
{
|
|
Vec4f pos;
|
|
uint32_t prim;
|
|
uint32_t sample;
|
|
uint32_t valid;
|
|
uint32_t padding;
|
|
// PSInput base, ddx, ....
|
|
};
|
|
|
|
static void CreatePSInputFetcher(rdcarray<uint32_t> &fragspv, uint32_t &structStride,
|
|
VulkanCreationInfo::ShaderModuleReflection &shadRefl,
|
|
StorageMode storageMode, bool usePrimitiveID, bool useSampleID)
|
|
{
|
|
rdcspv::Editor editor(fragspv);
|
|
|
|
editor.Prepare();
|
|
|
|
// first delete all functions. We will recreate the entry point with just what we need
|
|
{
|
|
rdcarray<rdcspv::Id> removedIds;
|
|
|
|
rdcspv::Iter it = editor.Begin(rdcspv::Section::Functions);
|
|
rdcspv::Iter end = editor.End(rdcspv::Section::Functions);
|
|
while(it < end)
|
|
{
|
|
removedIds.push_back(rdcspv::OpDecoder(it).result);
|
|
editor.Remove(it);
|
|
it++;
|
|
}
|
|
|
|
// remove any OpName that refers to deleted IDs - functions or results
|
|
it = editor.Begin(rdcspv::Section::Debug);
|
|
end = editor.End(rdcspv::Section::Debug);
|
|
while(it < end)
|
|
{
|
|
if(it.opcode() == rdcspv::Op::Name)
|
|
{
|
|
rdcspv::OpName name(it);
|
|
|
|
if(removedIds.contains(name.target))
|
|
editor.Remove(it);
|
|
}
|
|
it++;
|
|
}
|
|
}
|
|
|
|
rdcspv::MemoryAccessAndParamDatas alignedAccess;
|
|
alignedAccess.setAligned(sizeof(uint32_t));
|
|
|
|
rdcspv::Id uint32Type = editor.DeclareType(rdcspv::scalar<uint32_t>());
|
|
rdcspv::Id floatType = editor.DeclareType(rdcspv::scalar<float>());
|
|
rdcspv::Id boolType = editor.DeclareType(rdcspv::scalar<bool>());
|
|
|
|
rdcarray<rdcspv::Id> uintConsts;
|
|
|
|
auto getUIntConst = [&uintConsts, &editor](uint32_t c) {
|
|
for(uint32_t i = (uint32_t)uintConsts.size(); i <= c; i++)
|
|
uintConsts.push_back(editor.AddConstantImmediate<uint32_t>(uint32_t(i)));
|
|
|
|
return uintConsts[c];
|
|
};
|
|
|
|
rdcspv::StorageClass bufferClass;
|
|
|
|
if(storageMode == Binding)
|
|
bufferClass = editor.StorageBufferClass();
|
|
else
|
|
bufferClass = rdcspv::StorageClass::PhysicalStorageBuffer;
|
|
|
|
// remove all other entry point
|
|
rdcspv::Id entryID;
|
|
for(const rdcspv::EntryPoint &e : editor.GetEntries())
|
|
{
|
|
if(e.name == shadRefl.entryPoint)
|
|
{
|
|
entryID = e.id;
|
|
break;
|
|
}
|
|
}
|
|
|
|
rdcarray<rdcspv::Id> addedInputs;
|
|
|
|
// builtin inputs we need
|
|
struct BuiltinAccess
|
|
{
|
|
rdcspv::Id base;
|
|
uint32_t member = ~0U;
|
|
} fragCoord, primitiveID, sampleIndex;
|
|
|
|
// look to see which ones are already provided
|
|
for(size_t i = 0; i < shadRefl.refl.inputSignature.size(); i++)
|
|
{
|
|
const SigParameter ¶m = shadRefl.refl.inputSignature[i];
|
|
|
|
BuiltinAccess *access = NULL;
|
|
|
|
if(param.systemValue == ShaderBuiltin::Position)
|
|
access = &fragCoord;
|
|
else if(param.systemValue == ShaderBuiltin::PrimitiveIndex)
|
|
access = &primitiveID;
|
|
else if(param.systemValue == ShaderBuiltin::MSAASampleIndex)
|
|
access = &sampleIndex;
|
|
|
|
if(access)
|
|
{
|
|
SPIRVInterfaceAccess &patch = shadRefl.patchData.inputs[i];
|
|
access->base = patch.ID;
|
|
// should only be one deep at most, built-in interface block isn't allowed to be nested
|
|
RDCASSERT(patch.accessChain.size() <= 1);
|
|
if(!patch.accessChain.empty())
|
|
access->member = patch.accessChain[0];
|
|
}
|
|
}
|
|
|
|
// now declare any variables we didn't already have
|
|
if(fragCoord.base == rdcspv::Id())
|
|
{
|
|
rdcspv::Id type = editor.DeclareType(rdcspv::Vector(rdcspv::scalar<float>(), 4));
|
|
rdcspv::Id ptrType = editor.DeclareType(rdcspv::Pointer(type, rdcspv::StorageClass::Input));
|
|
|
|
fragCoord.base =
|
|
editor.AddVariable(rdcspv::OpVariable(ptrType, editor.MakeId(), rdcspv::StorageClass::Input));
|
|
|
|
editor.AddDecoration(rdcspv::OpDecorate(
|
|
fragCoord.base,
|
|
rdcspv::DecorationParam<rdcspv::Decoration::BuiltIn>(rdcspv::BuiltIn::FragCoord)));
|
|
|
|
addedInputs.push_back(fragCoord.base);
|
|
}
|
|
if(primitiveID.base == rdcspv::Id() && usePrimitiveID)
|
|
{
|
|
rdcspv::Id type = editor.DeclareType(rdcspv::scalar<uint32_t>());
|
|
rdcspv::Id ptrType = editor.DeclareType(rdcspv::Pointer(type, rdcspv::StorageClass::Input));
|
|
|
|
primitiveID.base =
|
|
editor.AddVariable(rdcspv::OpVariable(ptrType, editor.MakeId(), rdcspv::StorageClass::Input));
|
|
|
|
editor.AddDecoration(rdcspv::OpDecorate(
|
|
primitiveID.base,
|
|
rdcspv::DecorationParam<rdcspv::Decoration::BuiltIn>(rdcspv::BuiltIn::PrimitiveId)));
|
|
editor.AddDecoration(rdcspv::OpDecorate(primitiveID.base, rdcspv::Decoration::Flat));
|
|
|
|
addedInputs.push_back(primitiveID.base);
|
|
|
|
editor.AddCapability(rdcspv::Capability::Geometry);
|
|
}
|
|
if(sampleIndex.base == rdcspv::Id() && useSampleID)
|
|
{
|
|
rdcspv::Id type = editor.DeclareType(rdcspv::scalar<uint32_t>());
|
|
rdcspv::Id ptrType = editor.DeclareType(rdcspv::Pointer(type, rdcspv::StorageClass::Input));
|
|
|
|
sampleIndex.base =
|
|
editor.AddVariable(rdcspv::OpVariable(ptrType, editor.MakeId(), rdcspv::StorageClass::Input));
|
|
|
|
editor.AddDecoration(rdcspv::OpDecorate(
|
|
sampleIndex.base,
|
|
rdcspv::DecorationParam<rdcspv::Decoration::BuiltIn>(rdcspv::BuiltIn::SampleId)));
|
|
editor.AddDecoration(rdcspv::OpDecorate(sampleIndex.base, rdcspv::Decoration::Flat));
|
|
|
|
addedInputs.push_back(sampleIndex.base);
|
|
|
|
editor.AddCapability(rdcspv::Capability::SampleRateShading);
|
|
}
|
|
|
|
// add our inputs to the entry point's ID list. Since we're expanding the list we have to copy,
|
|
// erase, and insert. Modifying in-place doesn't support expanding
|
|
if(!addedInputs.empty())
|
|
{
|
|
rdcspv::Iter it = editor.GetEntry(entryID);
|
|
|
|
// this copies into the helper struct
|
|
rdcspv::OpEntryPoint entry(it);
|
|
|
|
// add our IDs
|
|
entry.iface.append(addedInputs);
|
|
|
|
// erase the old one
|
|
editor.Remove(it);
|
|
|
|
editor.AddOperation(it, entry);
|
|
}
|
|
|
|
rdcspv::Id PSInput;
|
|
|
|
enum Variant
|
|
{
|
|
Variant_Base,
|
|
Variant_ddxcoarse,
|
|
Variant_ddycoarse,
|
|
Variant_ddxfine,
|
|
Variant_ddyfine,
|
|
Variant_Count,
|
|
};
|
|
|
|
struct valueAndDerivs
|
|
{
|
|
rdcspv::Id valueType;
|
|
rdcspv::Id data[Variant_Count];
|
|
uint32_t structIndex;
|
|
rdcspv::OperationList storeOps;
|
|
};
|
|
|
|
rdcarray<valueAndDerivs> values;
|
|
values.resize(shadRefl.refl.inputSignature.size());
|
|
|
|
{
|
|
rdcarray<rdcspv::Id> ids;
|
|
rdcarray<uint32_t> offsets;
|
|
rdcarray<uint32_t> indices;
|
|
for(size_t i = 0; i < shadRefl.refl.inputSignature.size(); i++)
|
|
{
|
|
const SigParameter ¶m = shadRefl.refl.inputSignature[i];
|
|
|
|
rdcspv::Scalar base;
|
|
switch(param.compType)
|
|
{
|
|
case CompType::Float: base = rdcspv::scalar<float>(); break;
|
|
case CompType::UInt: base = rdcspv::scalar<uint32_t>(); break;
|
|
case CompType::SInt: base = rdcspv::scalar<int32_t>(); break;
|
|
case CompType::Double: base = rdcspv::scalar<double>(); break;
|
|
default: RDCERR("Unexpected type %s", ToStr(param.compType).c_str());
|
|
}
|
|
|
|
values[i].structIndex = (uint32_t)offsets.size();
|
|
|
|
offsets.push_back(structStride);
|
|
structStride += param.compCount * (base.width / 8);
|
|
|
|
if(param.compCount == 1)
|
|
values[i].valueType = editor.DeclareType(base);
|
|
else
|
|
values[i].valueType = editor.DeclareType(rdcspv::Vector(base, param.compCount));
|
|
|
|
ids.push_back(values[i].valueType);
|
|
|
|
// align offset conservatively, to 16-byte aligned. We do this with explicit uints so we can
|
|
// preview with spirv-cross (and because it doesn't cost anything particularly)
|
|
uint32_t paddingWords = ((16 - (structStride % 16)) / 4) % 4;
|
|
for(uint32_t p = 0; p < paddingWords; p++)
|
|
{
|
|
ids.push_back(uint32Type);
|
|
offsets.push_back(structStride);
|
|
structStride += 4;
|
|
}
|
|
}
|
|
|
|
PSInput = editor.DeclareStructType(ids);
|
|
|
|
for(size_t i = 0; i < offsets.size(); i++)
|
|
{
|
|
editor.AddDecoration(rdcspv::OpMemberDecorate(
|
|
PSInput, uint32_t(i), rdcspv::DecorationParam<rdcspv::Decoration::Offset>(offsets[i])));
|
|
}
|
|
|
|
for(size_t i = 0; i < values.size(); i++)
|
|
editor.SetMemberName(PSInput, values[i].structIndex, shadRefl.refl.inputSignature[i].varName);
|
|
|
|
editor.SetName(PSInput, "__rd_PSInput");
|
|
}
|
|
|
|
rdcspv::Id float4Type = editor.DeclareType(rdcspv::Vector(rdcspv::scalar<float>(), 4));
|
|
rdcspv::Id float2Type = editor.DeclareType(rdcspv::Vector(rdcspv::scalar<float>(), 2));
|
|
|
|
rdcspv::Id arrayLength =
|
|
editor.AddSpecConstantImmediate<uint32_t>(1U, (uint32_t)InputSpecConstant::ArrayLength);
|
|
|
|
editor.SetName(arrayLength, "arrayLength");
|
|
|
|
rdcspv::Id destX = editor.AddSpecConstantImmediate<float>(0.0f, (uint32_t)InputSpecConstant::DestX);
|
|
rdcspv::Id destY = editor.AddSpecConstantImmediate<float>(0.0f, (uint32_t)InputSpecConstant::DestY);
|
|
|
|
editor.SetName(destX, "destX");
|
|
editor.SetName(destY, "destY");
|
|
|
|
rdcspv::Id destXY = editor.AddConstant(
|
|
rdcspv::OpSpecConstantComposite(float2Type, editor.MakeId(), {destX, destY}));
|
|
|
|
editor.SetName(destXY, "destXY");
|
|
|
|
rdcspv::Id PSHit = editor.DeclareStructType({
|
|
// float4 pos;
|
|
float4Type,
|
|
// uint prim;
|
|
uint32Type,
|
|
// uint sample;
|
|
uint32Type,
|
|
// uint valid;
|
|
uint32Type,
|
|
// uint padding;
|
|
uint32Type,
|
|
|
|
// IN
|
|
PSInput,
|
|
// INddxcoarse
|
|
PSInput,
|
|
// INddycoarse
|
|
PSInput,
|
|
// INddxfine
|
|
PSInput,
|
|
// INddxfine
|
|
PSInput,
|
|
});
|
|
|
|
{
|
|
editor.SetName(PSHit, "__rd_PSHit");
|
|
|
|
uint32_t offs = 0, member = 0;
|
|
|
|
editor.AddDecoration(rdcspv::OpMemberDecorate(
|
|
PSHit, member, rdcspv::DecorationParam<rdcspv::Decoration::Offset>(offs)));
|
|
editor.SetMemberName(PSHit, member, "pos");
|
|
offs += sizeof(Vec4f);
|
|
member++;
|
|
|
|
editor.AddDecoration(rdcspv::OpMemberDecorate(
|
|
PSHit, member, rdcspv::DecorationParam<rdcspv::Decoration::Offset>(offs)));
|
|
editor.SetMemberName(PSHit, member, "prim");
|
|
offs += sizeof(uint32_t);
|
|
member++;
|
|
|
|
editor.AddDecoration(rdcspv::OpMemberDecorate(
|
|
PSHit, member, rdcspv::DecorationParam<rdcspv::Decoration::Offset>(offs)));
|
|
editor.SetMemberName(PSHit, member, "sample");
|
|
offs += sizeof(uint32_t);
|
|
member++;
|
|
|
|
editor.AddDecoration(rdcspv::OpMemberDecorate(
|
|
PSHit, member, rdcspv::DecorationParam<rdcspv::Decoration::Offset>(offs)));
|
|
editor.SetMemberName(PSHit, member, "valid");
|
|
offs += sizeof(uint32_t);
|
|
member++;
|
|
|
|
editor.AddDecoration(rdcspv::OpMemberDecorate(
|
|
PSHit, member, rdcspv::DecorationParam<rdcspv::Decoration::Offset>(offs)));
|
|
editor.SetMemberName(PSHit, member, "padding");
|
|
offs += sizeof(uint32_t);
|
|
member++;
|
|
|
|
RDCASSERT((offs % sizeof(Vec4f)) == 0);
|
|
RDCASSERT((structStride % sizeof(Vec4f)) == 0);
|
|
|
|
editor.AddDecoration(rdcspv::OpMemberDecorate(
|
|
PSHit, member, rdcspv::DecorationParam<rdcspv::Decoration::Offset>(offs)));
|
|
editor.SetMemberName(PSHit, member, "IN");
|
|
offs += structStride;
|
|
member++;
|
|
|
|
editor.AddDecoration(rdcspv::OpMemberDecorate(
|
|
PSHit, member, rdcspv::DecorationParam<rdcspv::Decoration::Offset>(offs)));
|
|
editor.SetMemberName(PSHit, member, "INddxcoarse");
|
|
offs += structStride;
|
|
member++;
|
|
|
|
editor.AddDecoration(rdcspv::OpMemberDecorate(
|
|
PSHit, member, rdcspv::DecorationParam<rdcspv::Decoration::Offset>(offs)));
|
|
editor.SetMemberName(PSHit, member, "INddycoarse");
|
|
offs += structStride;
|
|
member++;
|
|
|
|
editor.AddDecoration(rdcspv::OpMemberDecorate(
|
|
PSHit, member, rdcspv::DecorationParam<rdcspv::Decoration::Offset>(offs)));
|
|
editor.SetMemberName(PSHit, member, "INddxfine");
|
|
offs += structStride;
|
|
member++;
|
|
|
|
editor.AddDecoration(rdcspv::OpMemberDecorate(
|
|
PSHit, member, rdcspv::DecorationParam<rdcspv::Decoration::Offset>(offs)));
|
|
editor.SetMemberName(PSHit, member, "INddyfine");
|
|
offs += structStride;
|
|
member++;
|
|
}
|
|
|
|
rdcspv::Id PSHitRTArray = editor.AddType(rdcspv::OpTypeRuntimeArray(editor.MakeId(), PSHit));
|
|
|
|
editor.AddDecoration(rdcspv::OpDecorate(
|
|
PSHitRTArray, rdcspv::DecorationParam<rdcspv::Decoration::ArrayStride>(structStride * 5 +
|
|
sizeof(Vec4f) * 2)));
|
|
|
|
rdcspv::Id bufBase = editor.DeclareStructType({
|
|
// uint hit_count;
|
|
uint32Type,
|
|
// <uint3 padding>
|
|
|
|
// PSHit hits[];
|
|
PSHitRTArray,
|
|
});
|
|
|
|
{
|
|
editor.SetName(bufBase, "__rd_HitStorage");
|
|
|
|
editor.AddDecoration(rdcspv::OpMemberDecorate(
|
|
bufBase, 0, rdcspv::DecorationParam<rdcspv::Decoration::Offset>(0)));
|
|
editor.SetMemberName(bufBase, 0, "hit_count");
|
|
|
|
editor.AddDecoration(rdcspv::OpMemberDecorate(
|
|
bufBase, 1, rdcspv::DecorationParam<rdcspv::Decoration::Offset>(sizeof(Vec4f))));
|
|
editor.SetMemberName(bufBase, 1, "hits");
|
|
}
|
|
|
|
rdcspv::Id bufptrtype;
|
|
rdcspv::Id ssboVar;
|
|
rdcspv::Id addressConstant;
|
|
|
|
if(storageMode == Binding)
|
|
{
|
|
// the pointers are SSBO pointers
|
|
bufptrtype = editor.DeclareType(rdcspv::Pointer(bufBase, bufferClass));
|
|
|
|
// patch all bindings up by 1
|
|
for(rdcspv::Iter it = editor.Begin(rdcspv::Section::Annotations),
|
|
end = editor.End(rdcspv::Section::Annotations);
|
|
it < end; ++it)
|
|
{
|
|
// we will use descriptor set 0 for our own purposes if we don't have a buffer address.
|
|
//
|
|
// Since bindings are arbitrary, we just increase all user bindings to make room, and we'll
|
|
// redeclare the descriptor set layouts and pipeline layout. This is inevitable in the case
|
|
// where all descriptor sets are already used. In theory we only have to do this with set 0,
|
|
// but that requires knowing which variables are in set 0 and it's simpler to increase all
|
|
// bindings.
|
|
if(it.opcode() == rdcspv::Op::Decorate)
|
|
{
|
|
rdcspv::OpDecorate dec(it);
|
|
if(dec.decoration == rdcspv::Decoration::Binding)
|
|
{
|
|
RDCASSERT(dec.decoration.binding != 0xffffffff);
|
|
dec.decoration.binding += 1;
|
|
it = dec;
|
|
}
|
|
}
|
|
}
|
|
|
|
// add our SSBO variable, at set 0 binding 0
|
|
ssboVar = editor.MakeId();
|
|
editor.AddVariable(rdcspv::OpVariable(bufptrtype, ssboVar, bufferClass));
|
|
editor.AddDecoration(
|
|
rdcspv::OpDecorate(ssboVar, rdcspv::DecorationParam<rdcspv::Decoration::DescriptorSet>(0)));
|
|
editor.AddDecoration(
|
|
rdcspv::OpDecorate(ssboVar, rdcspv::DecorationParam<rdcspv::Decoration::Binding>(0)));
|
|
|
|
editor.SetName(ssboVar, "__rd_HitBuffer");
|
|
|
|
editor.DecorateStorageBufferStruct(bufBase);
|
|
}
|
|
else
|
|
{
|
|
bufptrtype = editor.DeclareType(rdcspv::Pointer(bufBase, bufferClass));
|
|
|
|
// add the extension
|
|
editor.AddExtension(storageMode == KHR_bda ? "SPV_KHR_physical_storage_buffer"
|
|
: "SPV_EXT_physical_storage_buffer");
|
|
|
|
// change the memory model to physical storage buffer 64
|
|
rdcspv::Iter it = editor.Begin(rdcspv::Section::MemoryModel);
|
|
rdcspv::OpMemoryModel model(it);
|
|
model.addressingModel = rdcspv::AddressingModel::PhysicalStorageBuffer64;
|
|
it = model;
|
|
|
|
// add capabilities
|
|
editor.AddCapability(rdcspv::Capability::PhysicalStorageBufferAddresses);
|
|
editor.AddCapability(rdcspv::Capability::Int64);
|
|
|
|
// declare the address constant which we will specialise later. There is a chicken-and-egg where
|
|
// this function determines how big the buffer needs to be so instead of hardcoding the address
|
|
// here we let it be allocated later and specialised in.
|
|
addressConstant =
|
|
editor.AddSpecConstantImmediate<uint64_t>(0ULL, (uint32_t)InputSpecConstant::Address);
|
|
|
|
editor.SetName(addressConstant, "__rd_bufAddress");
|
|
|
|
// struct is block decorated
|
|
editor.AddDecoration(rdcspv::OpDecorate(bufBase, rdcspv::Decoration::Block));
|
|
}
|
|
|
|
rdcspv::Id float4InPtr =
|
|
editor.DeclareType(rdcspv::Pointer(float4Type, rdcspv::StorageClass::Input));
|
|
rdcspv::Id float4BufPtr = editor.DeclareType(rdcspv::Pointer(float4Type, bufferClass));
|
|
|
|
rdcspv::Id uint32InPtr =
|
|
editor.DeclareType(rdcspv::Pointer(uint32Type, rdcspv::StorageClass::Input));
|
|
rdcspv::Id uint32BufPtr = editor.DeclareType(rdcspv::Pointer(uint32Type, bufferClass));
|
|
|
|
rdcspv::Id glsl450 = editor.ImportExtInst("GLSL.std.450");
|
|
|
|
editor.AddCapability(rdcspv::Capability::DerivativeControl);
|
|
|
|
{
|
|
rdcspv::OperationList ops;
|
|
|
|
rdcspv::Id voidType = editor.DeclareType(rdcspv::scalar<void>());
|
|
|
|
ops.add(rdcspv::OpFunction(voidType, entryID, rdcspv::FunctionControl::None,
|
|
editor.DeclareType(rdcspv::FunctionType(voidType, {}))));
|
|
|
|
ops.add(rdcspv::OpLabel(editor.MakeId()));
|
|
{
|
|
// grab all the values here and get any derivatives we need now before we branch non-uniformly
|
|
for(size_t i = 0; i < values.size(); i++)
|
|
{
|
|
const SPIRVInterfaceAccess &access = shadRefl.patchData.inputs[i];
|
|
const SigParameter ¶m = shadRefl.refl.inputSignature[i];
|
|
|
|
rdcarray<rdcspv::Id> accessIndices;
|
|
for(uint32_t idx : access.accessChain)
|
|
accessIndices.push_back(getUIntConst(idx));
|
|
|
|
rdcspv::Id ptrType =
|
|
editor.DeclareType(rdcspv::Pointer(values[i].valueType, rdcspv::StorageClass::Input));
|
|
rdcspv::Id ptr =
|
|
ops.add(rdcspv::OpAccessChain(ptrType, editor.MakeId(), access.ID, accessIndices));
|
|
rdcspv::Id base = ops.add(rdcspv::OpLoad(values[i].valueType, editor.MakeId(), ptr));
|
|
|
|
values[i].data[Variant_Base] = base;
|
|
|
|
editor.SetName(base, StringFormat::Fmt("__rd_base_%zu_%s", i, param.varName.c_str()));
|
|
|
|
// only float values have derivatives
|
|
if(param.compType == CompType::Float)
|
|
{
|
|
values[i].data[Variant_ddxcoarse] =
|
|
ops.add(rdcspv::OpDPdxCoarse(values[i].valueType, editor.MakeId(), base));
|
|
values[i].data[Variant_ddycoarse] =
|
|
ops.add(rdcspv::OpDPdyCoarse(values[i].valueType, editor.MakeId(), base));
|
|
values[i].data[Variant_ddxfine] =
|
|
ops.add(rdcspv::OpDPdxFine(values[i].valueType, editor.MakeId(), base));
|
|
values[i].data[Variant_ddyfine] =
|
|
ops.add(rdcspv::OpDPdyFine(values[i].valueType, editor.MakeId(), base));
|
|
|
|
editor.SetName(values[i].data[Variant_ddxcoarse],
|
|
StringFormat::Fmt("__rd_ddxcoarse_%zu_%s", i, param.varName.c_str()));
|
|
editor.SetName(values[i].data[Variant_ddycoarse],
|
|
StringFormat::Fmt("__rd_ddycoarse_%zu_%s", i, param.varName.c_str()));
|
|
editor.SetName(values[i].data[Variant_ddxfine],
|
|
StringFormat::Fmt("__rd_ddxfine_%zu_%s", i, param.varName.c_str()));
|
|
editor.SetName(values[i].data[Variant_ddyfine],
|
|
StringFormat::Fmt("__rd_ddyfine_%zu_%s", i, param.varName.c_str()));
|
|
}
|
|
else
|
|
{
|
|
values[i].data[Variant_ddxcoarse] = values[i].data[Variant_ddycoarse] =
|
|
values[i].data[Variant_ddxfine] = values[i].data[Variant_ddyfine] =
|
|
editor.AddConstant(rdcspv::OpConstantNull(values[i].valueType, editor.MakeId()));
|
|
|
|
editor.SetName(values[i].data[Variant_ddxcoarse],
|
|
StringFormat::Fmt("__rd_noderiv_%zu_%s", i, param.varName.c_str()));
|
|
}
|
|
}
|
|
|
|
rdcspv::Id structPtr = ssboVar;
|
|
|
|
if(structPtr == rdcspv::Id())
|
|
{
|
|
// if we don't have the struct as a bind, we need to cast it from the pointer
|
|
structPtr = ops.add(rdcspv::OpConvertUToPtr(bufptrtype, editor.MakeId(), addressConstant));
|
|
|
|
editor.SetName(structPtr, "HitBuffer");
|
|
}
|
|
|
|
rdcspv::Id uintPtr = editor.DeclareType(rdcspv::Pointer(uint32Type, bufferClass));
|
|
|
|
// get a pointer to buffer.hit_count
|
|
rdcspv::Id hit_count =
|
|
ops.add(rdcspv::OpAccessChain(uintPtr, editor.MakeId(), structPtr, {getUIntConst(0)}));
|
|
|
|
rdcspv::Id scope = editor.AddConstantImmediate<uint32_t>((uint32_t)rdcspv::Scope::Device);
|
|
rdcspv::Id semantics =
|
|
editor.AddConstantImmediate<uint32_t>((uint32_t)rdcspv::MemorySemantics::AcquireRelease);
|
|
|
|
// look up the fragcoord
|
|
rdcspv::Id fragCoordLoaded = editor.MakeId();
|
|
if(fragCoord.member == ~0U)
|
|
{
|
|
ops.add(rdcspv::OpLoad(float4Type, fragCoordLoaded, fragCoord.base));
|
|
}
|
|
else
|
|
{
|
|
rdcspv::Id posptr =
|
|
ops.add(rdcspv::OpAccessChain(float4InPtr, editor.MakeId(), fragCoord.base,
|
|
{editor.AddConstantImmediate(fragCoord.member)}));
|
|
ops.add(rdcspv::OpLoad(float4Type, fragCoordLoaded, posptr));
|
|
}
|
|
|
|
rdcspv::Id bool2Type = editor.DeclareType(rdcspv::Vector(rdcspv::scalar<bool>(), 2));
|
|
|
|
// grab x and y
|
|
rdcspv::Id fragXY = ops.add(rdcspv::OpVectorShuffle(
|
|
float2Type, editor.MakeId(), fragCoordLoaded, fragCoordLoaded, {0, 1}));
|
|
|
|
// subtract from the destination co-ord
|
|
rdcspv::Id fragXYRelative =
|
|
ops.add(rdcspv::OpFSub(float2Type, editor.MakeId(), fragXY, destXY));
|
|
|
|
// abs()
|
|
rdcspv::Id fragXYAbs = ops.add(rdcspv::OpGLSL450(float2Type, editor.MakeId(), glsl450,
|
|
rdcspv::GLSLstd450::FAbs, {fragXYRelative}));
|
|
|
|
rdcspv::Id half = editor.AddConstantImmediate<float>(0.5f);
|
|
rdcspv::Id threshold =
|
|
editor.AddConstant(rdcspv::OpConstantComposite(float2Type, editor.MakeId(), {half, half}));
|
|
|
|
// less than 0.5
|
|
rdcspv::Id inPixelXY =
|
|
ops.add(rdcspv::OpFOrdLessThan(bool2Type, editor.MakeId(), fragXYAbs, threshold));
|
|
|
|
// both less than 0.5
|
|
rdcspv::Id inPixel = ops.add(rdcspv::OpAll(boolType, editor.MakeId(), inPixelXY));
|
|
|
|
// bool inPixel = all(abs(gl_FragCoord.xy - dest.xy) < 0.5f);
|
|
|
|
rdcspv::Id killLabel = editor.MakeId();
|
|
rdcspv::Id continueLabel = editor.MakeId();
|
|
ops.add(rdcspv::OpSelectionMerge(killLabel, rdcspv::SelectionControl::None));
|
|
ops.add(rdcspv::OpBranchConditional(inPixel, continueLabel, killLabel));
|
|
ops.add(rdcspv::OpLabel(continueLabel));
|
|
|
|
// allocate a slot with atomic add
|
|
rdcspv::Id slot = ops.add(rdcspv::OpAtomicIAdd(uint32Type, editor.MakeId(), hit_count, scope,
|
|
semantics, getUIntConst(1)));
|
|
|
|
editor.SetName(slot, "slot");
|
|
|
|
rdcspv::Id inRange = ops.add(rdcspv::OpULessThan(boolType, editor.MakeId(), slot, arrayLength));
|
|
|
|
rdcspv::Id killLabel2 = editor.MakeId();
|
|
continueLabel = editor.MakeId();
|
|
ops.add(rdcspv::OpSelectionMerge(killLabel2, rdcspv::SelectionControl::None));
|
|
ops.add(rdcspv::OpBranchConditional(inRange, continueLabel, killLabel2));
|
|
ops.add(rdcspv::OpLabel(continueLabel));
|
|
|
|
rdcspv::Id hitptr = editor.DeclareType(rdcspv::Pointer(PSHit, bufferClass));
|
|
|
|
// get a pointer to the hit for our slot
|
|
rdcspv::Id hit =
|
|
ops.add(rdcspv::OpAccessChain(hitptr, editor.MakeId(), structPtr, {getUIntConst(1), slot}));
|
|
|
|
// store fixed properties
|
|
|
|
rdcspv::Id storePtr =
|
|
ops.add(rdcspv::OpAccessChain(float4BufPtr, editor.MakeId(), hit, {getUIntConst(0)}));
|
|
ops.add(rdcspv::OpStore(storePtr, fragCoordLoaded, alignedAccess));
|
|
|
|
rdcspv::Id loaded;
|
|
if(primitiveID.base != rdcspv::Id())
|
|
{
|
|
if(primitiveID.member == ~0U)
|
|
{
|
|
loaded = ops.add(rdcspv::OpLoad(uint32Type, editor.MakeId(), primitiveID.base));
|
|
}
|
|
else
|
|
{
|
|
rdcspv::Id posptr =
|
|
ops.add(rdcspv::OpAccessChain(uint32InPtr, editor.MakeId(), primitiveID.base,
|
|
{editor.AddConstantImmediate(primitiveID.member)}));
|
|
loaded = ops.add(rdcspv::OpLoad(uint32Type, editor.MakeId(), posptr));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// explicitly store 0
|
|
loaded = getUIntConst(0);
|
|
}
|
|
|
|
storePtr =
|
|
ops.add(rdcspv::OpAccessChain(uint32BufPtr, editor.MakeId(), hit, {getUIntConst(1)}));
|
|
ops.add(rdcspv::OpStore(storePtr, loaded, alignedAccess));
|
|
|
|
if(sampleIndex.base != rdcspv::Id())
|
|
{
|
|
if(sampleIndex.member == ~0U)
|
|
{
|
|
loaded = ops.add(rdcspv::OpLoad(uint32Type, editor.MakeId(), sampleIndex.base));
|
|
}
|
|
else
|
|
{
|
|
rdcspv::Id posptr =
|
|
ops.add(rdcspv::OpAccessChain(uint32InPtr, editor.MakeId(), sampleIndex.base,
|
|
{editor.AddConstantImmediate(sampleIndex.member)}));
|
|
loaded = ops.add(rdcspv::OpLoad(uint32Type, editor.MakeId(), posptr));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// explicitly store 0
|
|
loaded = getUIntConst(0);
|
|
}
|
|
|
|
storePtr =
|
|
ops.add(rdcspv::OpAccessChain(uint32BufPtr, editor.MakeId(), hit, {getUIntConst(2)}));
|
|
ops.add(rdcspv::OpStore(storePtr, loaded, alignedAccess));
|
|
|
|
storePtr =
|
|
ops.add(rdcspv::OpAccessChain(uint32BufPtr, editor.MakeId(), hit, {getUIntConst(3)}));
|
|
ops.add(rdcspv::OpStore(storePtr, editor.AddConstantImmediate(validMagicNumber), alignedAccess));
|
|
|
|
// store 0 in the padding
|
|
storePtr =
|
|
ops.add(rdcspv::OpAccessChain(uint32BufPtr, editor.MakeId(), hit, {getUIntConst(4)}));
|
|
ops.add(rdcspv::OpStore(storePtr, getUIntConst(0), alignedAccess));
|
|
|
|
{
|
|
rdcspv::Id inputPtrType = editor.DeclareType(rdcspv::Pointer(PSInput, bufferClass));
|
|
|
|
rdcspv::Id outputPtrs[Variant_Count] = {
|
|
ops.add(rdcspv::OpAccessChain(inputPtrType, editor.MakeId(), hit, {getUIntConst(5)})),
|
|
ops.add(rdcspv::OpAccessChain(inputPtrType, editor.MakeId(), hit, {getUIntConst(6)})),
|
|
ops.add(rdcspv::OpAccessChain(inputPtrType, editor.MakeId(), hit, {getUIntConst(7)})),
|
|
ops.add(rdcspv::OpAccessChain(inputPtrType, editor.MakeId(), hit, {getUIntConst(8)})),
|
|
ops.add(rdcspv::OpAccessChain(inputPtrType, editor.MakeId(), hit, {getUIntConst(9)})),
|
|
};
|
|
|
|
for(size_t i = 0; i < values.size(); i++)
|
|
{
|
|
rdcspv::Id ptrType = editor.DeclareType(rdcspv::Pointer(values[i].valueType, bufferClass));
|
|
|
|
for(size_t j = 0; j < Variant_Count; j++)
|
|
{
|
|
rdcspv::Id ptr = ops.add(rdcspv::OpAccessChain(ptrType, editor.MakeId(), outputPtrs[j],
|
|
{getUIntConst(values[i].structIndex)}));
|
|
ops.add(rdcspv::OpStore(ptr, values[i].data[j], alignedAccess));
|
|
}
|
|
}
|
|
}
|
|
|
|
// join up with the early-outs we did
|
|
ops.add(rdcspv::OpBranch(killLabel2));
|
|
ops.add(rdcspv::OpLabel(killLabel2));
|
|
ops.add(rdcspv::OpBranch(killLabel));
|
|
ops.add(rdcspv::OpLabel(killLabel));
|
|
}
|
|
// don't return, kill. This makes it well-defined that we don't write anything to our outputs
|
|
ops.add(rdcspv::OpKill());
|
|
|
|
ops.add(rdcspv::OpFunctionEnd());
|
|
|
|
editor.AddFunction(ops);
|
|
}
|
|
}
|
|
|
|
ShaderDebugTrace *VulkanReplay::DebugVertex(uint32_t eventId, uint32_t vertid, uint32_t instid,
|
|
uint32_t idx)
|
|
{
|
|
if(!GetAPIProperties().shaderDebugging)
|
|
{
|
|
RDCUNIMPLEMENTED("Vertex debugging not yet implemented for Vulkan");
|
|
return new ShaderDebugTrace;
|
|
}
|
|
|
|
const VulkanRenderState &state = m_pDriver->GetRenderState();
|
|
VulkanCreationInfo &c = m_pDriver->m_CreationInfo;
|
|
|
|
VkMarkerRegion region(
|
|
StringFormat::Fmt("DebugVertex @ %u of (%u,%u,%u)", eventId, vertid, instid, idx));
|
|
|
|
const DrawcallDescription *draw = m_pDriver->GetDrawcall(eventId);
|
|
|
|
if(!(draw->flags & DrawFlags::Drawcall))
|
|
return new ShaderDebugTrace();
|
|
|
|
const VulkanCreationInfo::Pipeline &pipe = c.m_Pipeline[state.graphics.pipeline];
|
|
VulkanCreationInfo::ShaderModule &shader = c.m_ShaderModule[pipe.shaders[0].module];
|
|
rdcstr entryPoint = pipe.shaders[0].entryPoint;
|
|
const rdcarray<SpecConstant> &spec = pipe.shaders[0].specialization;
|
|
|
|
VulkanCreationInfo::ShaderModuleReflection &shadRefl =
|
|
shader.GetReflection(entryPoint, state.graphics.pipeline);
|
|
|
|
shadRefl.PopulateDisassembly(shader.spirv);
|
|
|
|
VulkanAPIWrapper *apiWrapper = new VulkanAPIWrapper(m_pDriver, c, VK_SHADER_STAGE_VERTEX_BIT);
|
|
|
|
std::map<ShaderBuiltin, ShaderVariable> &builtins = apiWrapper->builtin_inputs;
|
|
builtins[ShaderBuiltin::BaseInstance] = ShaderVariable(rdcstr(), draw->instanceOffset, 0U, 0U, 0U);
|
|
builtins[ShaderBuiltin::BaseVertex] = ShaderVariable(
|
|
rdcstr(), (draw->flags & DrawFlags::Indexed) ? draw->baseVertex : draw->vertexOffset, 0U, 0U,
|
|
0U);
|
|
builtins[ShaderBuiltin::DeviceIndex] = ShaderVariable(rdcstr(), 0U, 0U, 0U, 0U);
|
|
builtins[ShaderBuiltin::DrawIndex] = ShaderVariable(rdcstr(), draw->drawIndex, 0U, 0U, 0U);
|
|
builtins[ShaderBuiltin::VertexIndex] = ShaderVariable(rdcstr(), vertid, 0U, 0U, 0U);
|
|
builtins[ShaderBuiltin::InstanceIndex] = ShaderVariable(rdcstr(), instid, 0U, 0U, 0U);
|
|
|
|
rdcarray<ShaderVariable> &locations = apiWrapper->location_inputs;
|
|
for(const VulkanCreationInfo::Pipeline::Attribute &attr : pipe.vertexAttrs)
|
|
{
|
|
if(attr.location >= locations.size())
|
|
locations.resize(attr.location + 1);
|
|
|
|
ShaderValue &val = locations[attr.location].value;
|
|
|
|
bytebuf data;
|
|
|
|
size_t size = GetByteSize(1, 1, 1, attr.format, 0);
|
|
|
|
if(attr.binding < pipe.vertexBindings.size())
|
|
{
|
|
const VulkanCreationInfo::Pipeline::Binding &bind = pipe.vertexBindings[attr.binding];
|
|
|
|
if(bind.vbufferBinding < state.vbuffers.size())
|
|
{
|
|
const VulkanRenderState::VertBuffer &vb = state.vbuffers[bind.vbufferBinding];
|
|
|
|
uint32_t vertexOffset = 0;
|
|
|
|
if(bind.perInstance)
|
|
{
|
|
if(bind.instanceDivisor == 0)
|
|
vertexOffset = draw->instanceOffset * bind.bytestride;
|
|
else
|
|
vertexOffset = draw->instanceOffset + (instid / bind.instanceDivisor) * bind.bytestride;
|
|
}
|
|
else
|
|
{
|
|
vertexOffset = idx * bind.bytestride;
|
|
}
|
|
|
|
GetDebugManager()->GetBufferData(vb.buf, vb.offs + attr.byteoffset + vertexOffset, size,
|
|
data);
|
|
}
|
|
}
|
|
|
|
if(size > data.size())
|
|
{
|
|
// out of bounds read
|
|
m_pDriver->AddDebugMessage(
|
|
MessageCategory::Execution, MessageSeverity::Medium, MessageSource::RuntimeWarning,
|
|
StringFormat::Fmt(
|
|
"Attribute location %u from binding %u reads out of bounds at vertex %u "
|
|
"(index %u) in instance %u.",
|
|
attr.location, attr.binding, vertid, idx, instid));
|
|
|
|
if(IsUIntFormat(attr.format) || IsSIntFormat(attr.format))
|
|
val.u = {0, 0, 0, 1};
|
|
else
|
|
val.f = {0.0f, 0.0f, 0.0f, 1.0f};
|
|
}
|
|
else
|
|
{
|
|
FloatVector decoded = ConvertComponents(MakeResourceFormat(attr.format), data.data());
|
|
|
|
val.f.x = decoded.x;
|
|
val.f.y = decoded.y;
|
|
val.f.z = decoded.z;
|
|
val.f.w = decoded.w;
|
|
}
|
|
}
|
|
|
|
rdcspv::Debugger *debugger = new rdcspv::Debugger;
|
|
debugger->Parse(shader.spirv.GetSPIRV());
|
|
ShaderDebugTrace *ret = debugger->BeginDebug(apiWrapper, ShaderStage::Vertex, entryPoint, spec,
|
|
shadRefl.instructionLines, shadRefl.patchData, 0);
|
|
|
|
return ret;
|
|
}
|
|
|
|
ShaderDebugTrace *VulkanReplay::DebugPixel(uint32_t eventId, uint32_t x, uint32_t y,
|
|
uint32_t sample, uint32_t primitive)
|
|
{
|
|
if(!GetAPIProperties().shaderDebugging)
|
|
{
|
|
RDCUNIMPLEMENTED("Pixel debugging not yet implemented for Vulkan");
|
|
return new ShaderDebugTrace;
|
|
}
|
|
|
|
if(!m_pDriver->GetDeviceFeatures().fragmentStoresAndAtomics)
|
|
{
|
|
RDCWARN("Pixel debugging is not supported without fragment stores");
|
|
return new ShaderDebugTrace;
|
|
}
|
|
|
|
VkDevice dev = m_pDriver->GetDev();
|
|
VkResult vkr = VK_SUCCESS;
|
|
|
|
const VulkanRenderState &state = m_pDriver->GetRenderState();
|
|
VulkanCreationInfo &c = m_pDriver->m_CreationInfo;
|
|
|
|
VkMarkerRegion region(StringFormat::Fmt("DebugPixel @ %u of (%u,%u) sample %u primitive %u",
|
|
eventId, x, y, sample, primitive));
|
|
|
|
const DrawcallDescription *draw = m_pDriver->GetDrawcall(eventId);
|
|
|
|
if(!(draw->flags & DrawFlags::Drawcall))
|
|
return new ShaderDebugTrace();
|
|
|
|
const VulkanCreationInfo::Pipeline &pipe = c.m_Pipeline[state.graphics.pipeline];
|
|
VulkanCreationInfo::ShaderModule &shader = c.m_ShaderModule[pipe.shaders[4].module];
|
|
rdcstr entryPoint = pipe.shaders[4].entryPoint;
|
|
const rdcarray<SpecConstant> &spec = pipe.shaders[4].specialization;
|
|
|
|
VulkanCreationInfo::ShaderModuleReflection &shadRefl =
|
|
shader.GetReflection(entryPoint, state.graphics.pipeline);
|
|
|
|
shadRefl.PopulateDisassembly(shader.spirv);
|
|
|
|
VulkanAPIWrapper *apiWrapper = new VulkanAPIWrapper(m_pDriver, c, VK_SHADER_STAGE_FRAGMENT_BIT);
|
|
|
|
std::map<ShaderBuiltin, ShaderVariable> &builtins = apiWrapper->builtin_inputs;
|
|
builtins[ShaderBuiltin::DeviceIndex] = ShaderVariable(rdcstr(), 0U, 0U, 0U, 0U);
|
|
builtins[ShaderBuiltin::DrawIndex] = ShaderVariable(rdcstr(), draw->drawIndex, 0U, 0U, 0U);
|
|
|
|
// If the pipe contains a geometry shader, then Primitive ID cannot be used in the pixel
|
|
// shader without being emitted from the geometry shader. For now, check if this semantic
|
|
// will succeed in a new pixel shader with the rest of the pipe unchanged
|
|
bool usePrimitiveID = false;
|
|
if(pipe.shaders[3].module != ResourceId())
|
|
{
|
|
VulkanCreationInfo::ShaderModuleReflection &gsRefl =
|
|
c.m_ShaderModule[pipe.shaders[3].module].GetReflection(pipe.shaders[3].entryPoint,
|
|
state.graphics.pipeline);
|
|
|
|
// check to see if the shader outputs a primitive ID
|
|
for(const SigParameter &e : gsRefl.refl.outputSignature)
|
|
{
|
|
if(e.systemValue == ShaderBuiltin::PrimitiveIndex)
|
|
{
|
|
usePrimitiveID = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// no geometry shader - safe to use as long as the geometry shader capability is available
|
|
usePrimitiveID = m_pDriver->GetDeviceFeatures().geometryShader != VK_FALSE;
|
|
}
|
|
|
|
bool useSampleID = m_pDriver->GetDeviceFeatures().sampleRateShading != VK_FALSE;
|
|
|
|
StorageMode storageMode = Binding;
|
|
|
|
if(m_pDriver->GetDeviceFeatures().shaderInt64)
|
|
{
|
|
if(m_pDriver->GetExtensions(NULL).ext_KHR_buffer_device_address)
|
|
storageMode = KHR_bda;
|
|
else if(m_pDriver->GetExtensions(NULL).ext_EXT_buffer_device_address)
|
|
storageMode = EXT_bda;
|
|
}
|
|
|
|
if(Vulkan_Debug_DisableBufferDeviceAddress)
|
|
storageMode = Binding;
|
|
|
|
rdcarray<uint32_t> fragspv = shader.spirv.GetSPIRV();
|
|
|
|
if(!Vulkan_Debug_PSDebugDumpDirPath.empty())
|
|
FileIO::WriteAll(Vulkan_Debug_PSDebugDumpDirPath + "/debug_psinput_before.spv", fragspv);
|
|
|
|
uint32_t structStride = 0;
|
|
CreatePSInputFetcher(fragspv, structStride, shadRefl, storageMode, usePrimitiveID, useSampleID);
|
|
|
|
if(!Vulkan_Debug_PSDebugDumpDirPath.empty())
|
|
FileIO::WriteAll(Vulkan_Debug_PSDebugDumpDirPath + "/debug_psinput_after.spv", fragspv);
|
|
|
|
uint32_t overdrawLevels = 100; // maximum number of overdraw levels
|
|
|
|
VkGraphicsPipelineCreateInfo graphicsInfo = {};
|
|
|
|
m_pDriver->GetShaderCache()->MakeGraphicsPipelineInfo(graphicsInfo, state.graphics.pipeline);
|
|
|
|
// struct size is PSHit header plus 5x structStride = base, ddxcoarse, ddycoarse, ddxfine, ddyfine
|
|
uint32_t structSize = sizeof(PSHit) + structStride * 5;
|
|
|
|
VkDeviceSize feedbackStorageSize = overdrawLevels * structSize + sizeof(Vec4f) + 1024;
|
|
|
|
if(feedbackStorageSize > m_BindlessFeedback.FeedbackBuffer.sz)
|
|
{
|
|
uint32_t flags = GPUBuffer::eGPUBufferGPULocal | GPUBuffer::eGPUBufferSSBO;
|
|
|
|
if(storageMode != Binding)
|
|
flags |= GPUBuffer::eGPUBufferAddressable;
|
|
|
|
m_BindlessFeedback.FeedbackBuffer.Destroy();
|
|
m_BindlessFeedback.FeedbackBuffer.Create(m_pDriver, dev, feedbackStorageSize, 1, flags);
|
|
}
|
|
|
|
struct SpecData
|
|
{
|
|
VkDeviceAddress bufferAddress;
|
|
uint32_t arrayLength;
|
|
float destX;
|
|
float destY;
|
|
} specData;
|
|
|
|
specData.arrayLength = overdrawLevels;
|
|
specData.destX = float(x) + 0.5f;
|
|
specData.destY = float(y) + 0.5f;
|
|
|
|
VkDescriptorPool descpool = VK_NULL_HANDLE;
|
|
rdcarray<VkDescriptorSetLayout> setLayouts;
|
|
rdcarray<VkDescriptorSet> descSets;
|
|
|
|
VkPipelineLayout pipeLayout = VK_NULL_HANDLE;
|
|
|
|
if(storageMode != Binding)
|
|
{
|
|
RDCCOMPILE_ASSERT(VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO ==
|
|
VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO_EXT,
|
|
"KHR and EXT buffer_device_address should be interchangeable here.");
|
|
VkBufferDeviceAddressInfo getAddressInfo = {VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO};
|
|
getAddressInfo.buffer = m_BindlessFeedback.FeedbackBuffer.buf;
|
|
|
|
if(storageMode == KHR_bda)
|
|
specData.bufferAddress = m_pDriver->vkGetBufferDeviceAddress(dev, &getAddressInfo);
|
|
else
|
|
specData.bufferAddress = m_pDriver->vkGetBufferDeviceAddressEXT(dev, &getAddressInfo);
|
|
}
|
|
else
|
|
{
|
|
VkDescriptorSetLayoutBinding newBindings[] = {
|
|
{0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VkShaderStageFlags(VK_SHADER_STAGE_FRAGMENT_BIT),
|
|
NULL},
|
|
};
|
|
RDCCOMPILE_ASSERT(ARRAY_COUNT(newBindings) == 1,
|
|
"Should only be one new descriptor for fetching PS inputs");
|
|
|
|
// create a duplicate set of descriptor sets, all visible to compute, with bindings shifted to
|
|
// account for new ones we need. This also copies the existing bindings into the new sets
|
|
PatchReservedDescriptors(state.graphics, descpool, setLayouts, descSets,
|
|
VkShaderStageFlagBits(), newBindings, ARRAY_COUNT(newBindings));
|
|
|
|
// create pipeline layout with new descriptor set layouts
|
|
const rdcarray<VkPushConstantRange> &push = c.m_PipelineLayout[pipe.layout].pushRanges;
|
|
|
|
VkPipelineLayoutCreateInfo pipeLayoutInfo = {
|
|
VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
|
|
NULL,
|
|
0,
|
|
(uint32_t)setLayouts.size(),
|
|
setLayouts.data(),
|
|
(uint32_t)push.size(),
|
|
push.data(),
|
|
};
|
|
|
|
vkr = m_pDriver->vkCreatePipelineLayout(dev, &pipeLayoutInfo, NULL, &pipeLayout);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
graphicsInfo.layout = pipeLayout;
|
|
|
|
// vkUpdateDescriptorSet desc set to point to buffer
|
|
VkDescriptorBufferInfo desc = {0};
|
|
|
|
m_BindlessFeedback.FeedbackBuffer.FillDescriptor(desc);
|
|
|
|
VkWriteDescriptorSet write = {
|
|
VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
|
NULL,
|
|
Unwrap(descSets[0]),
|
|
0,
|
|
0,
|
|
1,
|
|
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
|
|
NULL,
|
|
&desc,
|
|
NULL,
|
|
};
|
|
|
|
ObjDisp(dev)->UpdateDescriptorSets(Unwrap(dev), 1, &write, 0, NULL);
|
|
}
|
|
|
|
// create fragment shader with modified code
|
|
VkShaderModuleCreateInfo moduleCreateInfo = {VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
|
|
|
|
VkSpecializationMapEntry specMaps[] = {
|
|
{
|
|
(uint32_t)InputSpecConstant::Address, offsetof(SpecData, bufferAddress),
|
|
sizeof(SpecData::bufferAddress),
|
|
},
|
|
{
|
|
(uint32_t)InputSpecConstant::ArrayLength, offsetof(SpecData, arrayLength),
|
|
sizeof(SpecData::arrayLength),
|
|
},
|
|
{
|
|
(uint32_t)InputSpecConstant::DestX, offsetof(SpecData, destX), sizeof(SpecData::destX),
|
|
},
|
|
{
|
|
(uint32_t)InputSpecConstant::DestY, offsetof(SpecData, destY), sizeof(SpecData::destY),
|
|
},
|
|
};
|
|
|
|
VkSpecializationInfo specInfo = {};
|
|
specInfo.dataSize = sizeof(specData);
|
|
specInfo.pData = &specData;
|
|
specInfo.mapEntryCount = ARRAY_COUNT(specMaps);
|
|
specInfo.pMapEntries = specMaps;
|
|
|
|
RDCCOMPILE_ASSERT((size_t)InputSpecConstant::Count == ARRAY_COUNT(specMaps),
|
|
"Spec constants changed");
|
|
|
|
rdcarray<VkShaderModule> modules;
|
|
|
|
for(uint32_t i = 0; i < graphicsInfo.stageCount; i++)
|
|
{
|
|
VkPipelineShaderStageCreateInfo &stage =
|
|
(VkPipelineShaderStageCreateInfo &)graphicsInfo.pStages[i];
|
|
|
|
if(stage.stage == VK_SHADER_STAGE_FRAGMENT_BIT)
|
|
{
|
|
moduleCreateInfo.pCode = fragspv.data();
|
|
moduleCreateInfo.codeSize = fragspv.size() * sizeof(uint32_t);
|
|
|
|
vkr = m_pDriver->vkCreateShaderModule(dev, &moduleCreateInfo, NULL, &stage.module);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
stage.pSpecializationInfo = &specInfo;
|
|
|
|
modules.push_back(stage.module);
|
|
}
|
|
else if(storageMode == Binding)
|
|
{
|
|
// if we're stealing a binding point, we need to patch all other shaders
|
|
rdcarray<uint32_t> spirv = c.m_ShaderModule[GetResID(stage.module)].spirv.GetSPIRV();
|
|
|
|
{
|
|
rdcspv::Editor editor(spirv);
|
|
|
|
editor.Prepare();
|
|
|
|
// patch all bindings up by 1
|
|
for(rdcspv::Iter it = editor.Begin(rdcspv::Section::Annotations),
|
|
end = editor.End(rdcspv::Section::Annotations);
|
|
it < end; ++it)
|
|
{
|
|
if(it.opcode() == rdcspv::Op::Decorate)
|
|
{
|
|
rdcspv::OpDecorate dec(it);
|
|
if(dec.decoration == rdcspv::Decoration::Binding)
|
|
{
|
|
RDCASSERT(dec.decoration.binding != 0xffffffff);
|
|
dec.decoration.binding += 1;
|
|
it = dec;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
moduleCreateInfo.pCode = spirv.data();
|
|
moduleCreateInfo.codeSize = spirv.size() * sizeof(uint32_t);
|
|
|
|
vkr = m_pDriver->vkCreateShaderModule(dev, &moduleCreateInfo, NULL, &stage.module);
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
modules.push_back(stage.module);
|
|
}
|
|
}
|
|
|
|
VkPipeline inputsPipe;
|
|
vkr =
|
|
m_pDriver->vkCreateGraphicsPipelines(dev, VK_NULL_HANDLE, 1, &graphicsInfo, NULL, &inputsPipe);
|
|
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(inputsPipe);
|
|
|
|
if(storageMode == Binding)
|
|
{
|
|
// Treplace descriptor set IDs with our temporary sets. The offsets we keep the same. If the
|
|
// original draw had no sets, we ensure there's room (with no offsets needed)
|
|
if(modifiedstate.graphics.descSets.empty())
|
|
modifiedstate.graphics.descSets.resize(1);
|
|
|
|
for(size_t i = 0; i < descSets.size(); i++)
|
|
{
|
|
modifiedstate.graphics.descSets[i].pipeLayout = GetResID(pipeLayout);
|
|
modifiedstate.graphics.descSets[i].descSet = GetResID(descSets[i]);
|
|
}
|
|
}
|
|
|
|
{
|
|
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);
|
|
|
|
// fill destination buffer with 0s to ensure a baseline to then feedback against
|
|
ObjDisp(dev)->CmdFillBuffer(Unwrap(cmd), Unwrap(m_BindlessFeedback.FeedbackBuffer.buf), 0,
|
|
feedbackStorageSize, 0);
|
|
|
|
VkBufferMemoryBarrier feedbackbufBarrier = {
|
|
VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
|
|
NULL,
|
|
VK_ACCESS_TRANSFER_WRITE_BIT,
|
|
VK_ACCESS_SHADER_WRITE_BIT,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
Unwrap(m_BindlessFeedback.FeedbackBuffer.buf),
|
|
0,
|
|
feedbackStorageSize,
|
|
};
|
|
|
|
// wait for the above fill to finish.
|
|
DoPipelineBarrier(cmd, 1, &feedbackbufBarrier);
|
|
|
|
modifiedstate.BeginRenderPassAndApplyState(m_pDriver, cmd, VulkanRenderState::BindGraphics);
|
|
|
|
if(draw->flags & DrawFlags::Indexed)
|
|
{
|
|
ObjDisp(cmd)->CmdDrawIndexed(Unwrap(cmd), draw->numIndices, draw->numInstances,
|
|
draw->indexOffset, draw->baseVertex, draw->instanceOffset);
|
|
}
|
|
else
|
|
{
|
|
ObjDisp(cmd)->CmdDraw(Unwrap(cmd), draw->numIndices, draw->numInstances, draw->vertexOffset,
|
|
draw->instanceOffset);
|
|
}
|
|
|
|
modifiedstate.EndRenderPass(cmd);
|
|
|
|
vkr = ObjDisp(dev)->EndCommandBuffer(Unwrap(cmd));
|
|
RDCASSERTEQUAL(vkr, VK_SUCCESS);
|
|
|
|
m_pDriver->SubmitCmds();
|
|
m_pDriver->FlushQ();
|
|
}
|
|
|
|
bytebuf data;
|
|
GetBufferData(GetResID(m_BindlessFeedback.FeedbackBuffer.buf), 0, 0, data);
|
|
|
|
byte *base = data.data();
|
|
uint32_t numHits = *(uint32_t *)base;
|
|
|
|
if(numHits > overdrawLevels)
|
|
{
|
|
RDCERR("%u hits, more than max overdraw levels allowed %u. Clamping", numHits, overdrawLevels);
|
|
numHits = overdrawLevels;
|
|
}
|
|
|
|
base += sizeof(Vec4f);
|
|
|
|
PSHit *winner = NULL;
|
|
|
|
RDCLOG("Got %u hits", numHits);
|
|
|
|
// if we encounter multiple hits at our destination pixel co-ord (or any other) we
|
|
// check to see if a specific primitive was requested (via primitive parameter not
|
|
// being set to ~0U). If it was, debug that pixel, otherwise do a best-estimate
|
|
// of which fragment was the last to successfully depth test and debug that, just by
|
|
// checking if the depth test is ordered and picking the final fragment in the series
|
|
|
|
// figure out the TL pixel's coords. Assume even top left (towards 0,0)
|
|
// this isn't spec'd but is a reasonable assumption.
|
|
int xTL = x & (~1);
|
|
int yTL = y & (~1);
|
|
|
|
// get the index of our desired pixel
|
|
int destIdx = (x - xTL) + 2 * (y - yTL);
|
|
|
|
VkCompareOp depthOp = pipe.depthCompareOp;
|
|
|
|
// depth tests disabled acts the same as always compare mode
|
|
if(!pipe.depthTestEnable)
|
|
depthOp = VK_COMPARE_OP_ALWAYS;
|
|
|
|
for(uint32_t i = 0; i < numHits; i++)
|
|
{
|
|
PSHit *hit = (PSHit *)(base + structStride * i);
|
|
|
|
if(hit->valid != validMagicNumber)
|
|
{
|
|
RDCWARN("Hit %u doesn't have valid magic number");
|
|
continue;
|
|
}
|
|
|
|
// see if this hit is a closer match than the previous winner.
|
|
|
|
// if there's no previous winner it's clearly better
|
|
if(winner == NULL)
|
|
{
|
|
winner = hit;
|
|
continue;
|
|
}
|
|
|
|
// if we're looking for a specific primitive
|
|
if(primitive != ~0U)
|
|
{
|
|
// and this hit is a match and the winner isn't, it's better
|
|
if(winner->prim != primitive && hit->prim == primitive)
|
|
{
|
|
winner = hit;
|
|
continue;
|
|
}
|
|
|
|
// if the winner is a match and we're not, we can't be better so stop now
|
|
if(winner->prim == primitive && hit->prim != primitive)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// if we're looking for a particular sample, check that
|
|
if(sample != ~0U)
|
|
{
|
|
if(winner->sample != sample && hit->sample == sample)
|
|
{
|
|
winner = hit;
|
|
continue;
|
|
}
|
|
|
|
if(winner->sample == sample && hit->sample != sample)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// otherwise apply depth test
|
|
switch(depthOp)
|
|
{
|
|
case VK_COMPARE_OP_NEVER:
|
|
case VK_COMPARE_OP_EQUAL:
|
|
case VK_COMPARE_OP_NOT_EQUAL:
|
|
case VK_COMPARE_OP_ALWAYS:
|
|
default:
|
|
// don't emulate equal or not equal since we don't know the reference value. Take any hit
|
|
// (thus meaning the last hit)
|
|
winner = hit;
|
|
break;
|
|
case VK_COMPARE_OP_LESS:
|
|
if(hit->pos.z < winner->pos.z)
|
|
winner = hit;
|
|
break;
|
|
case VK_COMPARE_OP_LESS_OR_EQUAL:
|
|
if(hit->pos.z <= winner->pos.z)
|
|
winner = hit;
|
|
break;
|
|
case VK_COMPARE_OP_GREATER:
|
|
if(hit->pos.z > winner->pos.z)
|
|
winner = hit;
|
|
break;
|
|
case VK_COMPARE_OP_GREATER_OR_EQUAL:
|
|
if(hit->pos.z >= winner->pos.z)
|
|
winner = hit;
|
|
break;
|
|
}
|
|
}
|
|
|
|
ShaderDebugTrace *ret = NULL;
|
|
|
|
if(winner)
|
|
{
|
|
rdcspv::Debugger *debugger = new rdcspv::Debugger;
|
|
debugger->Parse(shader.spirv.GetSPIRV());
|
|
|
|
// the data immediately follows the PSHit header. Every piece of data is vec4 aligned, and the
|
|
// output is in input signature order.
|
|
byte *PSInputs = (byte *)(winner + 1);
|
|
Vec4f *value = (Vec4f *)(PSInputs + 0 * structStride);
|
|
Vec4f *ddxcoarse = (Vec4f *)(PSInputs + 1 * structStride);
|
|
Vec4f *ddycoarse = (Vec4f *)(PSInputs + 2 * structStride);
|
|
Vec4f *ddxfine = (Vec4f *)(PSInputs + 3 * structStride);
|
|
Vec4f *ddyfine = (Vec4f *)(PSInputs + 4 * structStride);
|
|
|
|
for(size_t i = 0; i < shadRefl.refl.inputSignature.size(); i++)
|
|
{
|
|
const SigParameter ¶m = shadRefl.refl.inputSignature[i];
|
|
|
|
bool builtin = true;
|
|
if(param.systemValue == ShaderBuiltin::Undefined)
|
|
{
|
|
builtin = false;
|
|
apiWrapper->location_inputs.resize(
|
|
RDCMAX((uint32_t)apiWrapper->location_inputs.size(), param.regIndex + 1));
|
|
apiWrapper->location_derivatives.resize(
|
|
RDCMAX((uint32_t)apiWrapper->location_derivatives.size(), param.regIndex + 1));
|
|
}
|
|
|
|
ShaderVariable &var = builtin ? apiWrapper->builtin_inputs[param.systemValue]
|
|
: apiWrapper->location_inputs[param.regIndex];
|
|
rdcspv::DebugAPIWrapper::DerivativeDeltas &deriv =
|
|
builtin ? apiWrapper->builtin_derivatives[param.systemValue]
|
|
: apiWrapper->location_derivatives[param.regIndex];
|
|
|
|
memcpy(&var.value.uv, &value[i], sizeof(Vec4f));
|
|
memcpy(&deriv.ddxcoarse, &ddxcoarse[i], sizeof(Vec4f));
|
|
memcpy(&deriv.ddycoarse, &ddycoarse[i], sizeof(Vec4f));
|
|
memcpy(&deriv.ddxfine, &ddxfine[i], sizeof(Vec4f));
|
|
memcpy(&deriv.ddyfine, &ddyfine[i], sizeof(Vec4f));
|
|
}
|
|
|
|
ret = debugger->BeginDebug(apiWrapper, ShaderStage::Pixel, entryPoint, spec,
|
|
shadRefl.instructionLines, shadRefl.patchData, destIdx);
|
|
}
|
|
else
|
|
{
|
|
RDCLOG("Didn't get any valid hit to debug");
|
|
delete apiWrapper;
|
|
|
|
ret = new ShaderDebugTrace;
|
|
}
|
|
|
|
if(descpool != VK_NULL_HANDLE)
|
|
{
|
|
// delete descriptors. Technically we don't have to free the descriptor sets, but our tracking
|
|
// on replay doesn't handle destroying children of pooled objects so we do it explicitly anyway.
|
|
m_pDriver->vkFreeDescriptorSets(dev, descpool, (uint32_t)descSets.size(), descSets.data());
|
|
|
|
m_pDriver->vkDestroyDescriptorPool(dev, descpool, NULL);
|
|
}
|
|
|
|
for(VkDescriptorSetLayout layout : setLayouts)
|
|
m_pDriver->vkDestroyDescriptorSetLayout(dev, layout, NULL);
|
|
|
|
// delete pipeline layout
|
|
m_pDriver->vkDestroyPipelineLayout(dev, pipeLayout, NULL);
|
|
|
|
// delete pipeline
|
|
m_pDriver->vkDestroyPipeline(dev, inputsPipe, NULL);
|
|
|
|
// delete shader modules
|
|
for(VkShaderModule s : modules)
|
|
m_pDriver->vkDestroyShaderModule(dev, s, NULL);
|
|
|
|
return ret;
|
|
}
|
|
|
|
ShaderDebugTrace *VulkanReplay::DebugThread(uint32_t eventId, const uint32_t groupid[3],
|
|
const uint32_t threadid[3])
|
|
{
|
|
if(!GetAPIProperties().shaderDebugging)
|
|
{
|
|
RDCUNIMPLEMENTED("Compute debugging not yet implemented for Vulkan");
|
|
return new ShaderDebugTrace;
|
|
}
|
|
|
|
const VulkanRenderState &state = m_pDriver->GetRenderState();
|
|
VulkanCreationInfo &c = m_pDriver->m_CreationInfo;
|
|
|
|
VkMarkerRegion region(StringFormat::Fmt("DebugThread @ %u of (%u,%u,%u) (%u,%u,%u)", eventId,
|
|
groupid[0], groupid[1], groupid[2], threadid[0],
|
|
threadid[1], threadid[2]));
|
|
|
|
const DrawcallDescription *draw = m_pDriver->GetDrawcall(eventId);
|
|
|
|
if(!(draw->flags & DrawFlags::Dispatch))
|
|
return new ShaderDebugTrace();
|
|
|
|
const VulkanCreationInfo::Pipeline &pipe = c.m_Pipeline[state.compute.pipeline];
|
|
VulkanCreationInfo::ShaderModule &shader = c.m_ShaderModule[pipe.shaders[5].module];
|
|
rdcstr entryPoint = pipe.shaders[5].entryPoint;
|
|
const rdcarray<SpecConstant> &spec = pipe.shaders[5].specialization;
|
|
|
|
VulkanCreationInfo::ShaderModuleReflection &shadRefl =
|
|
shader.GetReflection(entryPoint, state.compute.pipeline);
|
|
|
|
shadRefl.PopulateDisassembly(shader.spirv);
|
|
|
|
VulkanAPIWrapper *apiWrapper = new VulkanAPIWrapper(m_pDriver, c, VK_SHADER_STAGE_COMPUTE_BIT);
|
|
|
|
uint32_t threadDim[3];
|
|
threadDim[0] = shadRefl.refl.dispatchThreadsDimension[0];
|
|
threadDim[1] = shadRefl.refl.dispatchThreadsDimension[1];
|
|
threadDim[2] = shadRefl.refl.dispatchThreadsDimension[2];
|
|
|
|
std::map<ShaderBuiltin, ShaderVariable> &builtins = apiWrapper->builtin_inputs;
|
|
builtins[ShaderBuiltin::DispatchSize] =
|
|
ShaderVariable(rdcstr(), draw->dispatchDimension[0], draw->dispatchDimension[1],
|
|
draw->dispatchDimension[2], 0U);
|
|
builtins[ShaderBuiltin::DispatchThreadIndex] = ShaderVariable(
|
|
rdcstr(), groupid[0] * threadDim[0] + threadid[0], groupid[1] * threadDim[1] + threadid[1],
|
|
groupid[2] * threadDim[2] + threadid[2], 0U);
|
|
builtins[ShaderBuiltin::GroupIndex] =
|
|
ShaderVariable(rdcstr(), groupid[0], groupid[1], groupid[2], 0U);
|
|
builtins[ShaderBuiltin::GroupSize] =
|
|
ShaderVariable(rdcstr(), threadDim[0], threadDim[1], threadDim[2], 0U);
|
|
builtins[ShaderBuiltin::GroupThreadIndex] =
|
|
ShaderVariable(rdcstr(), threadid[0], threadid[1], threadid[2], 0U);
|
|
builtins[ShaderBuiltin::GroupFlatIndex] = ShaderVariable(
|
|
rdcstr(), threadid[2] * threadDim[0] * threadDim[1] + threadid[1] * threadDim[0] + threadid[0],
|
|
0U, 0U, 0U);
|
|
builtins[ShaderBuiltin::DeviceIndex] = ShaderVariable(rdcstr(), 0U, 0U, 0U, 0U);
|
|
|
|
rdcspv::Debugger *debugger = new rdcspv::Debugger;
|
|
debugger->Parse(shader.spirv.GetSPIRV());
|
|
ShaderDebugTrace *ret = debugger->BeginDebug(apiWrapper, ShaderStage::Compute, entryPoint, spec,
|
|
shadRefl.instructionLines, shadRefl.patchData, 0);
|
|
|
|
return ret;
|
|
}
|
|
|
|
rdcarray<ShaderDebugState> VulkanReplay::ContinueDebug(ShaderDebugger *debugger)
|
|
{
|
|
rdcspv::Debugger *spvDebugger = (rdcspv::Debugger *)debugger;
|
|
|
|
if(!spvDebugger)
|
|
return {};
|
|
|
|
VkMarkerRegion region("ContinueDebug Simulation Loop");
|
|
|
|
return spvDebugger->ContinueDebug();
|
|
}
|