mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-10-21 11:31:13 +00:00
Thread Simulation queues MathOp, SampleGatherOp requests and then the Thread is paused. The Debugger loop processes the MathOp queue, batches MathOps together into a single command list, Submit, Sync. The Debugger loop processes the SampleGatherOp queue, batches GPU SampleGatherOps together into a single command list, Submit, Sync. Some SampleGatherOps are run on the CPU and do not use the GPU. When the queued operations are completed, the results are written back into the Thread Simulation and the paused Threads are rerun.
6669 lines
253 KiB
C++
6669 lines
253 KiB
C++
/******************************************************************************
|
|
* The MIT License (MIT)
|
|
*
|
|
* Copyright (c) 2020-2025 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 "replay/common/var_dispatch_helpers.h"
|
|
#include "vk_core.h"
|
|
#include "vk_debug.h"
|
|
#include "vk_replay.h"
|
|
#include "vk_shader_cache.h"
|
|
|
|
#undef None
|
|
|
|
RDOC_CONFIG(rdcstr, Vulkan_Debug_PSDebugDumpDirPath, "",
|
|
"Path to dump shader debugging generated SPIR-V files.");
|
|
RDOC_CONFIG(bool, Vulkan_Debug_ShaderDebugLogging, false,
|
|
"Output verbose debug logging messages when debugging shaders.");
|
|
RDOC_EXTERN_CONFIG(bool, Vulkan_Hack_EnableGroupCaps);
|
|
|
|
// needed for old linux compilers
|
|
namespace std
|
|
{
|
|
template <>
|
|
struct hash<ShaderBuiltin>
|
|
{
|
|
std::size_t operator()(const ShaderBuiltin &e) const { return size_t(e); }
|
|
};
|
|
}
|
|
|
|
// should match the descriptor set layout created in ShaderDebugData::Init()
|
|
enum class ShaderDebugBind
|
|
{
|
|
Tex1D = 1,
|
|
First = Tex1D,
|
|
Tex2D = 2,
|
|
Tex3D = 3,
|
|
Tex2DMS = 4,
|
|
TexCube = 5,
|
|
Buffer = 6,
|
|
Sampler = 7,
|
|
Constants = 8,
|
|
Count,
|
|
MathResult = 9,
|
|
};
|
|
|
|
struct Vec3i
|
|
{
|
|
int32_t x, y, z;
|
|
};
|
|
|
|
struct GatherOffsets
|
|
{
|
|
int32_t u0, v0, u1, v1, u2, v2, u3, v3;
|
|
};
|
|
|
|
struct ShaderConstParameters
|
|
{
|
|
uint32_t operation;
|
|
VkBool32 useGradOrGatherOffsets;
|
|
ShaderDebugBind dim;
|
|
rdcspv::GatherChannel gatherChannel;
|
|
union
|
|
{
|
|
GatherOffsets gatherOffsets;
|
|
Vec3i constOffsets;
|
|
};
|
|
|
|
uint32_t hashKey(uint32_t shaderIndex) const
|
|
{
|
|
uint32_t hash = 5381;
|
|
hash = ((hash << 5) + hash) + shaderIndex;
|
|
hash = ((hash << 5) + hash) + operation;
|
|
hash = ((hash << 5) + hash) + useGradOrGatherOffsets;
|
|
hash = ((hash << 5) + hash) + (uint32_t)dim;
|
|
hash = ((hash << 5) + hash) + (uint32_t)gatherChannel;
|
|
hash = ((hash << 5) + hash) + gatherOffsets.u0;
|
|
hash = ((hash << 5) + hash) + gatherOffsets.v0;
|
|
hash = ((hash << 5) + hash) + gatherOffsets.u1;
|
|
hash = ((hash << 5) + hash) + gatherOffsets.v1;
|
|
hash = ((hash << 5) + hash) + gatherOffsets.u2;
|
|
hash = ((hash << 5) + hash) + gatherOffsets.v2;
|
|
hash = ((hash << 5) + hash) + gatherOffsets.u3;
|
|
hash = ((hash << 5) + hash) + gatherOffsets.v3;
|
|
return hash;
|
|
}
|
|
};
|
|
|
|
struct ShaderUniformParameters
|
|
{
|
|
Vec3i texel_uvw;
|
|
int texel_lod;
|
|
float uvwa[4];
|
|
float ddx[3];
|
|
float ddy[3];
|
|
Vec3i offset;
|
|
int sampleIdx;
|
|
float compare;
|
|
float lod;
|
|
float minlod;
|
|
};
|
|
|
|
#if defined(RELEASE)
|
|
#define CHECK_DEVICE_THREAD()
|
|
#else
|
|
#define CHECK_DEVICE_THREAD() \
|
|
RDCASSERTMSG("API Wrapper function called from non-device thread!", IsDeviceThread());
|
|
#endif // #if defined(RELEASE)
|
|
|
|
class VulkanAPIWrapper : public rdcspv::DebugAPIWrapper
|
|
{
|
|
public:
|
|
VulkanAPIWrapper(WrappedVulkan *vk, VulkanCreationInfo &creation, ShaderStage stage, uint32_t eid,
|
|
ResourceId shadId)
|
|
: m_DebugData(vk->GetReplay()->GetShaderDebugData()),
|
|
m_Creation(creation),
|
|
m_EventID(eid),
|
|
m_ShaderID(shadId),
|
|
deviceThreadID(Threading::GetCurrentID())
|
|
{
|
|
m_pDriver = vk;
|
|
|
|
// when we're first setting up, the state is pristine and no replay is needed
|
|
m_ResourcesDirty = false;
|
|
|
|
VulkanReplay *replay = m_pDriver->GetReplay();
|
|
|
|
// cache the descriptor access. This should be a superset of all descriptors we need to read from
|
|
m_Access = replay->GetDescriptorAccess(eid);
|
|
|
|
// filter to only accesses from the stage we care about, as access lookups will be stage-specific
|
|
m_Access.removeIf([stage](const DescriptorAccess &access) { return access.stage != stage; });
|
|
|
|
// fetch all descriptor contents now too
|
|
m_Descriptors.reserve(m_Access.size());
|
|
m_SamplerDescriptors.reserve(m_Access.size());
|
|
|
|
// we could collate ranges by descriptor store, but in practice we don't expect descriptors to
|
|
// be scattered across multiple stores. So to keep the code simple for now we do a linear sweep
|
|
ResourceId store;
|
|
rdcarray<DescriptorRange> ranges;
|
|
|
|
for(const DescriptorAccess &acc : m_Access)
|
|
{
|
|
if(acc.descriptorStore != store)
|
|
{
|
|
if(store != ResourceId())
|
|
{
|
|
m_Descriptors.append(replay->GetDescriptors(store, ranges));
|
|
m_SamplerDescriptors.append(replay->GetSamplerDescriptors(store, ranges));
|
|
}
|
|
|
|
store = replay->GetLiveID(acc.descriptorStore);
|
|
ranges.clear();
|
|
}
|
|
|
|
// if the last range is contiguous with this access, append this access as a new range to query
|
|
if(!ranges.empty() && ranges.back().descriptorSize == acc.byteSize &&
|
|
ranges.back().offset + ranges.back().descriptorSize == acc.byteOffset &&
|
|
ranges.back().type == acc.type)
|
|
{
|
|
ranges.back().count++;
|
|
continue;
|
|
}
|
|
|
|
DescriptorRange range = acc;
|
|
ranges.push_back(range);
|
|
}
|
|
|
|
if(store != ResourceId())
|
|
{
|
|
m_Descriptors.append(replay->GetDescriptors(store, ranges));
|
|
m_SamplerDescriptors.append(replay->GetSamplerDescriptors(store, ranges));
|
|
}
|
|
|
|
// apply dynamic offsets to our cached descriptors
|
|
// we iterate over descriptors first to find dynamic ones, then iterate over our cached set to
|
|
// apply. Neither array should be large but there should be fewer dynamic descriptors in total
|
|
{
|
|
const VulkanRenderState &state = m_pDriver->GetRenderState();
|
|
|
|
const rdcarray<VulkanStatePipeline::DescriptorAndOffsets> &src =
|
|
(stage == ShaderStage::Compute ? state.compute.descSets : state.graphics.descSets);
|
|
|
|
for(size_t i = 0; i < src.size(); i++)
|
|
{
|
|
const VulkanStatePipeline::DescriptorAndOffsets &srcData = src[i];
|
|
ResourceId sourceSet = srcData.descSet;
|
|
const uint32_t *srcOffset = srcData.offsets.begin();
|
|
|
|
// this could be either an unbound set, or descriptor buffers (which can't use dynamic offsets anyway)
|
|
if(sourceSet == ResourceId())
|
|
continue;
|
|
|
|
const VulkanCreationInfo::PipelineLayout &pipeLayoutInfo =
|
|
m_Creation.GetPipelineLayoutInfo(srcData.pipeLayout);
|
|
|
|
ResourceId setOrig = m_pDriver->GetResourceManager()->GetOriginalID(sourceSet);
|
|
|
|
const BindingStorage &bindStorage =
|
|
m_pDriver->GetCurrentDescSetBindingStorage(srcData.descSet);
|
|
const DescriptorSetSlot *first = bindStorage.binds.empty() ? NULL : bindStorage.binds[0];
|
|
for(size_t b = 0; b < bindStorage.binds.size(); b++)
|
|
{
|
|
const DescSetLayout::Binding &layoutBind =
|
|
m_Creation.GetDescSetLayout(pipeLayoutInfo.descSetLayouts[i]).bindings[b];
|
|
|
|
if(layoutBind.layoutDescType != VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC &&
|
|
layoutBind.layoutDescType != VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC)
|
|
continue;
|
|
|
|
uint64_t descriptorByteOffset = bindStorage.binds[b] - first;
|
|
|
|
// inline UBOs aren't dynamic and variable size can't be used with dynamic buffers, so
|
|
// the count is what it is at definition time
|
|
for(uint32_t a = 0; a < layoutBind.descriptorCount; a++)
|
|
{
|
|
uint32_t dynamicBufferByteOffset = *srcOffset;
|
|
srcOffset++;
|
|
|
|
for(size_t accIdx = 0; accIdx < m_Access.size(); accIdx++)
|
|
{
|
|
if(m_Access[accIdx].descriptorStore == setOrig &&
|
|
m_Access[accIdx].byteOffset == descriptorByteOffset + a)
|
|
{
|
|
m_Descriptors[accIdx].byteOffset += dynamicBufferByteOffset;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
RDCASSERT(ShaderDebugData::MAX_QUEUED_OPS * mathOpResultByteSize <
|
|
m_DebugData.ReadbackBuffer.TotalSize());
|
|
RDCASSERT(sampleGatherOpResultsStart +
|
|
ShaderDebugData::MAX_QUEUED_OPS * sampleGatherOpResultByteSize <
|
|
m_DebugData.ReadbackBuffer.TotalSize());
|
|
}
|
|
|
|
~VulkanAPIWrapper()
|
|
{
|
|
CHECK_DEVICE_THREAD();
|
|
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);
|
|
}
|
|
|
|
void ResetReplay()
|
|
{
|
|
CHECK_DEVICE_THREAD();
|
|
if(!m_ResourcesDirty)
|
|
{
|
|
VkMarkerRegion region("ResetReplay");
|
|
// replay the action to get back to 'normal' state for this event, and mark that we need to
|
|
// replay back to pristine state next time we need to fetch data.
|
|
m_pDriver->ReplayLog(0, m_EventID, eReplay_OnlyDraw);
|
|
}
|
|
m_ResourcesDirty = true;
|
|
}
|
|
|
|
virtual void AddDebugMessage(MessageCategory cat, MessageSeverity sev, MessageSource src,
|
|
rdcstr desc) override
|
|
{
|
|
CHECK_DEVICE_THREAD();
|
|
m_pDriver->AddDebugMessage(cat, sev, src, desc);
|
|
}
|
|
|
|
virtual ResourceId GetShaderID() override { return m_ShaderID; }
|
|
|
|
virtual uint64_t GetBufferLength(ShaderBindIndex bind) override
|
|
{
|
|
CHECK_DEVICE_THREAD();
|
|
return PopulateBuffer(bind).size();
|
|
}
|
|
|
|
virtual void ReadBufferValue(ShaderBindIndex bind, uint64_t offset, uint64_t byteSize,
|
|
void *dst) override
|
|
{
|
|
CHECK_DEVICE_THREAD();
|
|
const bytebuf &data = PopulateBuffer(bind);
|
|
|
|
if(offset + byteSize <= data.size())
|
|
memcpy(dst, data.data() + (size_t)offset, (size_t)byteSize);
|
|
}
|
|
|
|
virtual void WriteBufferValue(ShaderBindIndex bind, uint64_t offset, uint64_t byteSize,
|
|
const void *src) override
|
|
{
|
|
CHECK_DEVICE_THREAD();
|
|
bytebuf &data = PopulateBuffer(bind);
|
|
|
|
if(offset + byteSize <= data.size())
|
|
memcpy(data.data() + (size_t)offset, src, (size_t)byteSize);
|
|
}
|
|
|
|
virtual void ReadAddress(uint64_t address, uint64_t byteSize, void *dst) override
|
|
{
|
|
CHECK_DEVICE_THREAD();
|
|
size_t offset;
|
|
const bytebuf &data = PopulateBuffer(address, offset);
|
|
if(offset + byteSize <= data.size())
|
|
memcpy(dst, data.data() + offset, (size_t)byteSize);
|
|
}
|
|
|
|
virtual void WriteAddress(uint64_t address, uint64_t byteSize, const void *src) override
|
|
{
|
|
CHECK_DEVICE_THREAD();
|
|
size_t offset;
|
|
bytebuf &data = PopulateBuffer(address, offset);
|
|
if(offset + byteSize <= data.size())
|
|
memcpy(data.data() + offset, src, (size_t)byteSize);
|
|
}
|
|
|
|
virtual bool ReadTexel(ShaderBindIndex imageBind, const ShaderVariable &coord, uint32_t sample,
|
|
ShaderVariable &output) override
|
|
{
|
|
CHECK_DEVICE_THREAD();
|
|
ImageData &data = PopulateImage(imageBind);
|
|
|
|
if(data.width == 0)
|
|
return false;
|
|
|
|
uint32_t coords[4];
|
|
for(int i = 0; i < 4; i++)
|
|
coords[i] = uintComp(coord, i);
|
|
|
|
if(coords[0] > data.width || coords[1] > data.height || coords[2] > data.depth)
|
|
{
|
|
m_pDriver->AddDebugMessage(
|
|
MessageCategory::Execution, MessageSeverity::High, MessageSource::RuntimeWarning,
|
|
StringFormat::Fmt(
|
|
"Out of bounds access to image, coord %u,%u,%u outside of dimensions %ux%ux%u",
|
|
coords[0], coords[1], coords[2], data.width, data.height, data.depth));
|
|
return false;
|
|
}
|
|
|
|
CompType varComp = VarTypeCompType(output.type);
|
|
|
|
set0001(output);
|
|
|
|
ShaderVariable input;
|
|
input.columns = data.fmt.compCount;
|
|
|
|
// the only 'irregular' format we need to worry about handling for integer types is 10:10:10:2.
|
|
// All others are float/uint
|
|
if(data.fmt.type == ResourceFormatType::R10G10B10A2)
|
|
{
|
|
PixelValue val;
|
|
DecodePixelData(data.fmt, data.texel(coords, sample), val);
|
|
|
|
if(data.fmt.compType == CompType::UInt)
|
|
input.type = VarType::UInt;
|
|
else if(data.fmt.compType == CompType::SInt)
|
|
input.type = VarType::SInt;
|
|
else
|
|
input.type = VarType::Float;
|
|
|
|
memcpy(input.value.u32v.data(), val.uintValue.data(), val.uintValue.byteSize());
|
|
|
|
for(uint8_t c = 0; c < RDCMIN(output.columns, input.columns); c++)
|
|
{
|
|
if(data.fmt.compType == CompType::UInt)
|
|
setUintComp(output, c, uintComp(input, c));
|
|
else if(data.fmt.compType == CompType::SInt)
|
|
setIntComp(output, c, intComp(input, c));
|
|
else
|
|
setFloatComp(output, c, input.value.f32v[c]);
|
|
}
|
|
}
|
|
else if(data.fmt.compType == CompType::UInt)
|
|
{
|
|
RDCASSERT(varComp == CompType::UInt, varComp);
|
|
|
|
// set up input type for proper expansion below
|
|
if(data.fmt.compByteWidth == 1)
|
|
input.type = VarType::UByte;
|
|
else if(data.fmt.compByteWidth == 2)
|
|
input.type = VarType::UShort;
|
|
else if(data.fmt.compByteWidth == 4)
|
|
input.type = VarType::UInt;
|
|
else if(data.fmt.compByteWidth == 8)
|
|
input.type = VarType::ULong;
|
|
|
|
memcpy(input.value.u8v.data(), data.texel(coords, sample), data.texelSize);
|
|
|
|
for(uint8_t c = 0; c < RDCMIN(output.columns, input.columns); c++)
|
|
setUintComp(output, c, uintComp(input, c));
|
|
}
|
|
else if(data.fmt.compType == CompType::SInt)
|
|
{
|
|
RDCASSERT(varComp == CompType::SInt, varComp);
|
|
|
|
// set up input type for proper expansion below
|
|
if(data.fmt.compByteWidth == 1)
|
|
input.type = VarType::SByte;
|
|
else if(data.fmt.compByteWidth == 2)
|
|
input.type = VarType::SShort;
|
|
else if(data.fmt.compByteWidth == 4)
|
|
input.type = VarType::SInt;
|
|
else if(data.fmt.compByteWidth == 8)
|
|
input.type = VarType::SLong;
|
|
|
|
memcpy(input.value.u8v.data(), data.texel(coords, sample), data.texelSize);
|
|
|
|
for(uint8_t c = 0; c < RDCMIN(output.columns, input.columns); c++)
|
|
setIntComp(output, c, intComp(input, c));
|
|
}
|
|
else
|
|
{
|
|
RDCASSERT(varComp == CompType::Float, varComp);
|
|
|
|
// do the decode of whatever unorm/float/etc the format is
|
|
FloatVector v = DecodeFormattedComponents(data.fmt, data.texel(coords, sample));
|
|
|
|
// set it into f32v
|
|
input.value.f32v[0] = v.x;
|
|
input.value.f32v[1] = v.y;
|
|
input.value.f32v[2] = v.z;
|
|
input.value.f32v[3] = v.w;
|
|
|
|
// read as floats
|
|
input.type = VarType::Float;
|
|
|
|
for(uint8_t c = 0; c < RDCMIN(output.columns, input.columns); c++)
|
|
setFloatComp(output, c, input.value.f32v[c]);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
virtual bool WriteTexel(ShaderBindIndex imageBind, const ShaderVariable &coord, uint32_t sample,
|
|
const ShaderVariable &input) override
|
|
{
|
|
CHECK_DEVICE_THREAD();
|
|
ImageData &data = PopulateImage(imageBind);
|
|
|
|
if(data.width == 0)
|
|
return false;
|
|
|
|
uint32_t coords[4];
|
|
for(int i = 0; i < 4; i++)
|
|
coords[i] = uintComp(coord, i);
|
|
|
|
if(coords[0] > data.width || coords[1] > data.height || coords[2] > data.depth)
|
|
{
|
|
m_pDriver->AddDebugMessage(
|
|
MessageCategory::Execution, MessageSeverity::High, MessageSource::RuntimeWarning,
|
|
StringFormat::Fmt(
|
|
"Out of bounds access to image, coord %u,%u,%u outside of dimensions %ux%ux%u",
|
|
coords[0], coords[1], coords[2], data.width, data.height, data.depth));
|
|
return false;
|
|
}
|
|
|
|
CompType varComp = VarTypeCompType(input.type);
|
|
|
|
ShaderVariable output;
|
|
output.columns = data.fmt.compCount;
|
|
|
|
// the only 'irregular' format we need to worry about handling for integer types is 10:10:10:2.
|
|
// All others are float/uint
|
|
if(data.fmt.type == ResourceFormatType::R10G10B10A2)
|
|
{
|
|
// image writes are required to write a whole texel so we know we should have 4 components
|
|
RDCASSERTEQUAL(input.columns, 4);
|
|
|
|
uint32_t encoded = 0;
|
|
|
|
if(data.fmt.compType == CompType::SNorm)
|
|
encoded = ConvertToR10G10B10A2SNorm(Vec4f(input.value.f32v[0], input.value.f32v[1],
|
|
input.value.f32v[2], input.value.f32v[3]));
|
|
else if(data.fmt.compType == CompType::UInt)
|
|
encoded = ConvertToR10G10B10A2(Vec4u(input.value.u32v[0], input.value.u32v[1],
|
|
input.value.u32v[2], input.value.u32v[3]));
|
|
else
|
|
encoded = ConvertToR10G10B10A2(Vec4f(input.value.f32v[0], input.value.f32v[1],
|
|
input.value.f32v[2], input.value.f32v[3]));
|
|
|
|
memcpy(data.texel(coords, sample), &encoded, sizeof(uint32_t));
|
|
}
|
|
else if(data.fmt.compType == CompType::UInt)
|
|
{
|
|
RDCASSERT(varComp == CompType::UInt, varComp);
|
|
|
|
// set up output type for proper expansion below
|
|
if(data.fmt.compByteWidth == 1)
|
|
output.type = VarType::UByte;
|
|
else if(data.fmt.compByteWidth == 2)
|
|
output.type = VarType::UShort;
|
|
else if(data.fmt.compByteWidth == 4)
|
|
output.type = VarType::UInt;
|
|
else if(data.fmt.compByteWidth == 8)
|
|
output.type = VarType::ULong;
|
|
|
|
for(uint8_t c = 0; c < RDCMIN(output.columns, input.columns); c++)
|
|
setUintComp(output, c, uintComp(input, c));
|
|
|
|
memcpy(data.texel(coords, sample), output.value.u8v.data(), data.texelSize);
|
|
}
|
|
else if(data.fmt.compType == CompType::SInt)
|
|
{
|
|
RDCASSERT(varComp == CompType::SInt, varComp);
|
|
|
|
// set up input type for proper expansion below
|
|
if(data.fmt.compByteWidth == 1)
|
|
output.type = VarType::SByte;
|
|
else if(data.fmt.compByteWidth == 2)
|
|
output.type = VarType::SShort;
|
|
else if(data.fmt.compByteWidth == 4)
|
|
output.type = VarType::SInt;
|
|
else if(data.fmt.compByteWidth == 8)
|
|
output.type = VarType::SLong;
|
|
|
|
for(uint8_t c = 0; c < RDCMIN(output.columns, input.columns); c++)
|
|
setIntComp(output, c, intComp(input, c));
|
|
|
|
memcpy(data.texel(coords, sample), output.value.u8v.data(), data.texelSize);
|
|
}
|
|
else
|
|
{
|
|
RDCASSERT(varComp == CompType::Float, varComp);
|
|
|
|
// read as floats
|
|
output.type = VarType::Float;
|
|
|
|
for(uint8_t c = 0; c < RDCMIN(output.columns, input.columns); c++)
|
|
setFloatComp(output, c, input.value.f32v[c]);
|
|
|
|
FloatVector v;
|
|
|
|
// set it into f32v
|
|
v.x = input.value.f32v[0];
|
|
v.y = input.value.f32v[1];
|
|
v.z = input.value.f32v[2];
|
|
v.w = input.value.f32v[3];
|
|
|
|
EncodeFormattedComponents(data.fmt, v, data.texel(coords, sample));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
virtual void FillInputValue(ShaderVariable &var, ShaderBuiltin builtin, uint32_t threadIndex,
|
|
uint32_t location, uint32_t component) override
|
|
{
|
|
CHECK_DEVICE_THREAD();
|
|
if(builtin != ShaderBuiltin::Undefined)
|
|
{
|
|
if(threadIndex < thread_builtins.size())
|
|
{
|
|
auto it = thread_builtins[threadIndex].find(builtin);
|
|
if(it != thread_builtins[threadIndex].end())
|
|
{
|
|
var.value = it->second.value;
|
|
return;
|
|
}
|
|
}
|
|
|
|
auto it = global_builtins.find(builtin);
|
|
if(it != global_builtins.end())
|
|
{
|
|
var.value = it->second.value;
|
|
return;
|
|
}
|
|
|
|
RDCERR("Couldn't get input for %s", ToStr(builtin).c_str());
|
|
return;
|
|
}
|
|
|
|
if(threadIndex < location_inputs.size())
|
|
{
|
|
if(location < location_inputs[threadIndex].size())
|
|
{
|
|
if(var.rows == 1)
|
|
{
|
|
if(component + var.columns > 4)
|
|
RDCERR("Unexpected component %u for column count %u", component, var.columns);
|
|
|
|
for(uint8_t c = 0; c < var.columns; c++)
|
|
copyComp(var, c, location_inputs[threadIndex][location], component + c);
|
|
}
|
|
else
|
|
{
|
|
RDCASSERTEQUAL(component, 0);
|
|
for(uint8_t r = 0; r < var.rows; r++)
|
|
for(uint8_t c = 0; c < var.columns; c++)
|
|
copyComp(var, r * var.columns + c, location_inputs[threadIndex][location + c], r);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
RDCERR("Couldn't get input for %s at thread=%u, location=%u, component=%u", var.name.c_str(),
|
|
threadIndex, location, component);
|
|
}
|
|
|
|
uint32_t GetThreadProperty(uint32_t threadIndex, rdcspv::ThreadProperty prop) override
|
|
{
|
|
CHECK_DEVICE_THREAD();
|
|
if(prop >= rdcspv::ThreadProperty::Count)
|
|
return 0;
|
|
if(threadIndex >= thread_props.size())
|
|
return 0;
|
|
|
|
return thread_props[threadIndex][(size_t)prop];
|
|
}
|
|
|
|
bool QueueSampleGather(rdcspv::ThreadState &lane, rdcspv::Op opcode,
|
|
DebugAPIWrapper::TextureType texType, const ShaderBindIndex &imageBind,
|
|
const ShaderBindIndex &samplerBind, const ShaderVariable &uv,
|
|
const ShaderVariable &ddxCalc, const ShaderVariable &ddyCalc,
|
|
const ShaderVariable &compare, rdcspv::GatherChannel gatherChannel,
|
|
const rdcspv::ImageOperandsAndParamDatas &operands, ShaderVariable &output,
|
|
bool &hasResult) override
|
|
{
|
|
CHECK_DEVICE_THREAD();
|
|
ShaderConstParameters constParams = {};
|
|
ShaderUniformParameters uniformParams = {};
|
|
|
|
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
|
|
bool valid = true;
|
|
rdcstr access = StringFormat::Fmt("performing %s operation", ToStr(opcode).c_str());
|
|
const Descriptor &imageDescriptor = buffer ? GetDescriptor(access, ShaderBindIndex(), valid)
|
|
: GetDescriptor(access, imageBind, valid);
|
|
const Descriptor &bufferViewDescriptor = buffer
|
|
? GetDescriptor(access, imageBind, valid)
|
|
: GetDescriptor(access, ShaderBindIndex(), valid);
|
|
|
|
// fetch the sampler (if there's no sampler, this will silently return dummy data without
|
|
// marking invalid
|
|
const SamplerDescriptor &samplerDescriptor = GetSamplerDescriptor(access, samplerBind, valid);
|
|
|
|
// if any descriptor lookup failed, return now
|
|
if(!valid)
|
|
return false;
|
|
|
|
VkMarkerRegion markerRegion("QueueSampleGather");
|
|
|
|
VkBufferView bufferView =
|
|
m_pDriver->GetResourceManager()->GetLiveHandle<VkBufferView>(bufferViewDescriptor.view);
|
|
|
|
VkSampler sampler =
|
|
m_pDriver->GetResourceManager()->GetLiveHandle<VkSampler>(samplerDescriptor.object);
|
|
VkImageView view =
|
|
m_pDriver->GetResourceManager()->GetLiveHandle<VkImageView>(imageDescriptor.view);
|
|
VkImageLayout layout = convert((DescriptorSlotImageLayout)imageDescriptor.byteOffset);
|
|
|
|
// promote view to Array view
|
|
|
|
VulkanCreationInfo::ImageView defaultViewProps;
|
|
VulkanCreationInfo::Image defaultImageProps;
|
|
const VulkanCreationInfo::ImageView &viewProps =
|
|
buffer ? defaultViewProps : m_Creation.GetImageViewInfo(GetResID(view));
|
|
const VulkanCreationInfo::Image &imageProps =
|
|
buffer ? defaultImageProps : m_Creation.GetImageInfo(viewProps.image);
|
|
|
|
const bool depthTex = IsDepthOrStencilFormat(viewProps.format);
|
|
|
|
VkDevice dev = m_pDriver->GetDev();
|
|
|
|
// how many co-ordinates should there be
|
|
int coords = 0, gradCoords = 0;
|
|
if(buffer)
|
|
{
|
|
constParams.dim = ShaderDebugBind::Buffer;
|
|
coords = gradCoords = 1;
|
|
}
|
|
else
|
|
{
|
|
switch(viewProps.viewType)
|
|
{
|
|
case VK_IMAGE_VIEW_TYPE_1D:
|
|
coords = 1;
|
|
gradCoords = 1;
|
|
constParams.dim = ShaderDebugBind::Tex1D;
|
|
break;
|
|
case VK_IMAGE_VIEW_TYPE_2D:
|
|
coords = 2;
|
|
gradCoords = 2;
|
|
constParams.dim = ShaderDebugBind::Tex2D;
|
|
break;
|
|
case VK_IMAGE_VIEW_TYPE_3D:
|
|
coords = 3;
|
|
gradCoords = 3;
|
|
constParams.dim = ShaderDebugBind::Tex3D;
|
|
break;
|
|
case VK_IMAGE_VIEW_TYPE_CUBE:
|
|
coords = 3;
|
|
gradCoords = 3;
|
|
constParams.dim = ShaderDebugBind::TexCube;
|
|
break;
|
|
case VK_IMAGE_VIEW_TYPE_1D_ARRAY:
|
|
coords = 2;
|
|
gradCoords = 1;
|
|
constParams.dim = ShaderDebugBind::Tex1D;
|
|
break;
|
|
case VK_IMAGE_VIEW_TYPE_2D_ARRAY:
|
|
coords = 3;
|
|
gradCoords = 2;
|
|
constParams.dim = ShaderDebugBind::Tex2D;
|
|
break;
|
|
case VK_IMAGE_VIEW_TYPE_CUBE_ARRAY:
|
|
coords = 4;
|
|
gradCoords = 3;
|
|
constParams.dim = ShaderDebugBind::TexCube;
|
|
break;
|
|
case VK_IMAGE_VIEW_TYPE_MAX_ENUM:
|
|
RDCERR("Invalid image view type %s", ToStr(viewProps.viewType).c_str());
|
|
return false;
|
|
}
|
|
|
|
if(imageProps.samples > 1)
|
|
constParams.dim = ShaderDebugBind::Tex2DMS;
|
|
}
|
|
|
|
// handle query opcodes now
|
|
switch(opcode)
|
|
{
|
|
case rdcspv::Op::ImageQueryLevels:
|
|
{
|
|
output.value.u32v[0] = viewProps.range.levelCount;
|
|
if(viewProps.range.levelCount == VK_REMAINING_MIP_LEVELS)
|
|
output.value.u32v[0] = imageProps.mipLevels - viewProps.range.baseMipLevel;
|
|
hasResult = true;
|
|
return true;
|
|
}
|
|
case rdcspv::Op::ImageQuerySamples:
|
|
{
|
|
output.value.u32v[0] = (uint32_t)imageProps.samples;
|
|
hasResult = true;
|
|
return true;
|
|
}
|
|
case rdcspv::Op::ImageQuerySize:
|
|
case rdcspv::Op::ImageQuerySizeLod:
|
|
{
|
|
uint32_t mip = viewProps.range.baseMipLevel;
|
|
|
|
if(opcode == rdcspv::Op::ImageQuerySizeLod)
|
|
mip += uintComp(lane.GetSrc(operands.lod), 0);
|
|
|
|
RDCEraseEl(output.value);
|
|
|
|
int i = 0;
|
|
setUintComp(output, i++, RDCMAX(1U, imageProps.extent.width >> mip));
|
|
if(coords >= 2)
|
|
setUintComp(output, i++, RDCMAX(1U, imageProps.extent.height >> mip));
|
|
if(viewProps.viewType == VK_IMAGE_VIEW_TYPE_3D)
|
|
setUintComp(output, i++, RDCMAX(1U, imageProps.extent.depth >> mip));
|
|
|
|
if(viewProps.viewType == VK_IMAGE_VIEW_TYPE_1D_ARRAY ||
|
|
viewProps.viewType == VK_IMAGE_VIEW_TYPE_2D_ARRAY)
|
|
setUintComp(output, i++, imageProps.arrayLayers);
|
|
else if(viewProps.viewType == VK_IMAGE_VIEW_TYPE_CUBE ||
|
|
viewProps.viewType == VK_IMAGE_VIEW_TYPE_CUBE_ARRAY)
|
|
setUintComp(output, i++, imageProps.arrayLayers / 6);
|
|
|
|
if(buffer)
|
|
{
|
|
VkDeviceSize size;
|
|
VkFormat format;
|
|
if(bufferView == VK_NULL_HANDLE)
|
|
{
|
|
// descriptor buffer case - there is no buffer view so read directly out of the determined descriptor
|
|
format = MakeVkFormat(bufferViewDescriptor.format);
|
|
// size is not allowed to be VK_WHOLE_SIZE
|
|
size = bufferViewDescriptor.byteSize;
|
|
}
|
|
else
|
|
{
|
|
const VulkanCreationInfo::BufferView &bufViewProps =
|
|
m_Creation.GetBufferViewInfo(GetResID(bufferView));
|
|
|
|
size = bufViewProps.size;
|
|
format = bufViewProps.format;
|
|
|
|
if(size == VK_WHOLE_SIZE)
|
|
{
|
|
const VulkanCreationInfo::Buffer &bufProps =
|
|
m_Creation.GetBufferInfo(bufViewProps.buffer);
|
|
size = bufProps.size - bufViewProps.offset;
|
|
}
|
|
}
|
|
|
|
setUintComp(output, 0, uint32_t(size / GetByteSize(1, 1, 1, format, 0)));
|
|
}
|
|
|
|
hasResult = true;
|
|
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 && view != 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;
|
|
else if(viewInfo.viewType == VK_IMAGE_VIEW_TYPE_CUBE &&
|
|
m_pDriver->GetDeviceEnabledFeatures().imageCubeArray)
|
|
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_CUBE_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);
|
|
CHECK_VKR(m_pDriver, vkr);
|
|
|
|
m_SampleViews[GetResID(view)] = sampleView;
|
|
}
|
|
|
|
if(operands.flags & rdcspv::ImageOperands::Bias)
|
|
{
|
|
const ShaderVariable &biasVar = lane.GetSrc(operands.bias);
|
|
|
|
// silently cast parameters to 32-bit floats
|
|
float bias = floatComp(biasVar, 0);
|
|
|
|
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.GetSamplerInfo(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.anisotropyEnable = samplerProps.maxAnisotropy >= 1.0f;
|
|
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;
|
|
}
|
|
|
|
VkSamplerCustomBorderColorCreateInfoEXT borderInfo = {
|
|
VK_STRUCTURE_TYPE_SAMPLER_CUSTOM_BORDER_COLOR_CREATE_INFO_EXT};
|
|
if(samplerProps.customBorder)
|
|
{
|
|
borderInfo.customBorderColor = samplerProps.customBorderColor;
|
|
borderInfo.format = samplerProps.customBorderFormat;
|
|
|
|
borderInfo.pNext = sampInfo.pNext;
|
|
sampInfo.pNext = &borderInfo;
|
|
}
|
|
|
|
// now add the shader's bias on
|
|
sampInfo.mipLodBias += bias;
|
|
|
|
VkResult vkr = m_pDriver->vkCreateSampler(dev, &sampInfo, NULL, &sampler);
|
|
CHECK_VKR(m_pDriver, vkr);
|
|
|
|
insertIt.first->second = sampler;
|
|
}
|
|
else
|
|
{
|
|
sampler = insertIt.first->second;
|
|
}
|
|
}
|
|
}
|
|
|
|
constParams.operation = (uint32_t)opcode;
|
|
|
|
// proj opcodes have an extra q parameter, but we do the divide ourselves and 'demote' these to
|
|
// non-proj variants
|
|
bool proj = false;
|
|
switch(opcode)
|
|
{
|
|
case rdcspv::Op::ImageSampleProjExplicitLod:
|
|
{
|
|
constParams.operation = (uint32_t)rdcspv::Op::ImageSampleExplicitLod;
|
|
proj = true;
|
|
break;
|
|
}
|
|
case rdcspv::Op::ImageSampleProjImplicitLod:
|
|
{
|
|
constParams.operation = (uint32_t)rdcspv::Op::ImageSampleImplicitLod;
|
|
proj = true;
|
|
break;
|
|
}
|
|
case rdcspv::Op::ImageSampleProjDrefExplicitLod:
|
|
{
|
|
constParams.operation = (uint32_t)rdcspv::Op::ImageSampleDrefExplicitLod;
|
|
proj = true;
|
|
break;
|
|
}
|
|
case rdcspv::Op::ImageSampleProjDrefImplicitLod:
|
|
{
|
|
constParams.operation = (uint32_t)rdcspv::Op::ImageSampleDrefImplicitLod;
|
|
proj = true;
|
|
break;
|
|
}
|
|
default: break;
|
|
}
|
|
|
|
bool useCompare = false;
|
|
switch(opcode)
|
|
{
|
|
case rdcspv::Op::ImageDrefGather:
|
|
case rdcspv::Op::ImageSampleDrefExplicitLod:
|
|
case rdcspv::Op::ImageSampleDrefImplicitLod:
|
|
case rdcspv::Op::ImageSampleProjDrefExplicitLod:
|
|
case rdcspv::Op::ImageSampleProjDrefImplicitLod:
|
|
{
|
|
useCompare = true;
|
|
|
|
if(m_pDriver->GetDriverInfo().QualcommDrefNon2DCompileCrash() &&
|
|
constParams.dim != ShaderDebugBind::Tex2D)
|
|
{
|
|
m_pDriver->AddDebugMessage(
|
|
MessageCategory::Execution, MessageSeverity::High, MessageSource::RuntimeWarning,
|
|
"Dref sample against non-2D texture, this cannot be debugged due to a driver bug");
|
|
}
|
|
|
|
break;
|
|
}
|
|
default: break;
|
|
}
|
|
|
|
bool gatherOp = false;
|
|
|
|
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.
|
|
uniformParams.texel_uvw.x = uintComp(uv, 0);
|
|
if(coords >= 2)
|
|
uniformParams.texel_uvw.y = uintComp(uv, 1);
|
|
if(coords >= 3)
|
|
uniformParams.texel_uvw.z = uintComp(uv, 2);
|
|
|
|
if(!buffer && operands.flags & rdcspv::ImageOperands::Lod)
|
|
uniformParams.texel_lod = uintComp(lane.GetSrc(operands.lod), 0);
|
|
else
|
|
uniformParams.texel_lod = 0;
|
|
|
|
if(operands.flags & rdcspv::ImageOperands::Sample)
|
|
uniformParams.sampleIdx = uintComp(lane.GetSrc(operands.sample), 0);
|
|
|
|
break;
|
|
}
|
|
case rdcspv::Op::ImageGather:
|
|
case rdcspv::Op::ImageDrefGather:
|
|
{
|
|
gatherOp = true;
|
|
|
|
// silently cast parameters to 32-bit floats
|
|
for(int i = 0; i < coords; i++)
|
|
uniformParams.uvwa[i] = floatComp(uv, i);
|
|
|
|
if(useCompare)
|
|
uniformParams.compare = floatComp(compare, 0);
|
|
|
|
constParams.gatherChannel = gatherChannel;
|
|
|
|
if(operands.flags & rdcspv::ImageOperands::ConstOffsets)
|
|
{
|
|
ShaderVariable constOffsets = lane.GetSrc(operands.constOffsets);
|
|
|
|
constParams.useGradOrGatherOffsets = VK_TRUE;
|
|
|
|
// should be an array of ivec2
|
|
RDCASSERT(constOffsets.members.size() == 4);
|
|
|
|
// sign extend variables lower than 32-bits
|
|
for(int i = 0; i < 4; i++)
|
|
{
|
|
if(constOffsets.members[i].type == VarType::SByte)
|
|
{
|
|
constOffsets.members[i].value.s32v[0] = constOffsets.members[i].value.s8v[0];
|
|
constOffsets.members[i].value.s32v[1] = constOffsets.members[i].value.s8v[1];
|
|
}
|
|
else if(constOffsets.members[i].type == VarType::SShort)
|
|
{
|
|
constOffsets.members[i].value.s32v[0] = constOffsets.members[i].value.s16v[0];
|
|
constOffsets.members[i].value.s32v[1] = constOffsets.members[i].value.s16v[1];
|
|
}
|
|
}
|
|
|
|
constParams.gatherOffsets.u0 = constOffsets.members[0].value.s32v[0];
|
|
constParams.gatherOffsets.v0 = constOffsets.members[0].value.s32v[1];
|
|
constParams.gatherOffsets.u1 = constOffsets.members[1].value.s32v[0];
|
|
constParams.gatherOffsets.v1 = constOffsets.members[1].value.s32v[1];
|
|
constParams.gatherOffsets.u2 = constOffsets.members[2].value.s32v[0];
|
|
constParams.gatherOffsets.v2 = constOffsets.members[2].value.s32v[1];
|
|
constParams.gatherOffsets.u3 = constOffsets.members[3].value.s32v[0];
|
|
constParams.gatherOffsets.v3 = constOffsets.members[3].value.s32v[1];
|
|
}
|
|
|
|
break;
|
|
}
|
|
case rdcspv::Op::ImageQueryLod:
|
|
case rdcspv::Op::ImageSampleExplicitLod:
|
|
case rdcspv::Op::ImageSampleImplicitLod:
|
|
case rdcspv::Op::ImageSampleProjExplicitLod:
|
|
case rdcspv::Op::ImageSampleProjImplicitLod:
|
|
case rdcspv::Op::ImageSampleDrefExplicitLod:
|
|
case rdcspv::Op::ImageSampleDrefImplicitLod:
|
|
case rdcspv::Op::ImageSampleProjDrefExplicitLod:
|
|
case rdcspv::Op::ImageSampleProjDrefImplicitLod:
|
|
{
|
|
// silently cast parameters to 32-bit floats
|
|
for(int i = 0; i < coords; i++)
|
|
uniformParams.uvwa[i] = floatComp(uv, i);
|
|
|
|
if(proj)
|
|
{
|
|
// coords shouldn't be 4 because that's only valid for cube arrays which can't be
|
|
// projected
|
|
RDCASSERT(coords < 4);
|
|
|
|
// do the divide ourselves rather than severely complicating the sample shader (as proj
|
|
// variants need non-arrayed textures)
|
|
float q = floatComp(uv, coords);
|
|
|
|
uniformParams.uvwa[0] /= q;
|
|
uniformParams.uvwa[1] /= q;
|
|
uniformParams.uvwa[2] /= q;
|
|
}
|
|
|
|
if(operands.flags & rdcspv::ImageOperands::MinLod)
|
|
{
|
|
const ShaderVariable &minLodVar = lane.GetSrc(operands.minLod);
|
|
|
|
// silently cast parameters to 32-bit floats
|
|
uniformParams.minlod = floatComp(minLodVar, 0);
|
|
}
|
|
|
|
if(useCompare)
|
|
{
|
|
// silently cast parameters to 32-bit floats
|
|
uniformParams.compare = floatComp(compare, 0);
|
|
}
|
|
|
|
if(operands.flags & rdcspv::ImageOperands::Lod)
|
|
{
|
|
const ShaderVariable &lodVar = lane.GetSrc(operands.lod);
|
|
|
|
// silently cast parameters to 32-bit floats
|
|
uniformParams.lod = floatComp(lodVar, 0);
|
|
constParams.useGradOrGatherOffsets = VK_FALSE;
|
|
}
|
|
else if(operands.flags & rdcspv::ImageOperands::Grad)
|
|
{
|
|
ShaderVariable ddx = lane.GetSrc(operands.grad.first);
|
|
ShaderVariable ddy = lane.GetSrc(operands.grad.second);
|
|
|
|
constParams.useGradOrGatherOffsets = VK_TRUE;
|
|
|
|
// silently cast parameters to 32-bit floats
|
|
RDCASSERTEQUAL(ddx.type, ddy.type);
|
|
for(int i = 0; i < gradCoords; i++)
|
|
{
|
|
uniformParams.ddx[i] = floatComp(ddx, i);
|
|
uniformParams.ddy[i] = floatComp(ddy, i);
|
|
}
|
|
}
|
|
|
|
if(opcode == rdcspv::Op::ImageSampleImplicitLod ||
|
|
opcode == rdcspv::Op::ImageSampleProjImplicitLod || opcode == rdcspv::Op::ImageQueryLod)
|
|
{
|
|
// use grad to sub in for the implicit lod
|
|
constParams.useGradOrGatherOffsets = VK_TRUE;
|
|
|
|
// silently cast parameters to 32-bit floats
|
|
RDCASSERTEQUAL(ddxCalc.type, ddyCalc.type);
|
|
for(int i = 0; i < gradCoords; i++)
|
|
{
|
|
uniformParams.ddx[i] = floatComp(ddxCalc, i);
|
|
uniformParams.ddy[i] = floatComp(ddyCalc, i);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
RDCERR("Unsupported opcode %s", ToStr(opcode).c_str());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if(operands.flags & rdcspv::ImageOperands::ConstOffset)
|
|
{
|
|
ShaderVariable constOffset = lane.GetSrc(operands.constOffset);
|
|
|
|
// sign extend variables lower than 32-bits
|
|
for(uint8_t c = 0; c < constOffset.columns; c++)
|
|
{
|
|
if(constOffset.type == VarType::SByte)
|
|
constOffset.value.s32v[c] = constOffset.value.s8v[c];
|
|
else if(constOffset.type == VarType::SShort)
|
|
constOffset.value.s32v[c] = constOffset.value.s16v[c];
|
|
}
|
|
|
|
// pass offsets as uniform where possible - when the feature (widely available) on gather
|
|
// operations. On non-gather operations we are forced to use const offsets and must specialise
|
|
// the pipeline.
|
|
if(m_pDriver->GetDeviceEnabledFeatures().shaderImageGatherExtended && gatherOp)
|
|
{
|
|
uniformParams.offset.x = constOffset.value.s32v[0];
|
|
if(gradCoords >= 2)
|
|
uniformParams.offset.y = constOffset.value.s32v[1];
|
|
if(gradCoords >= 3)
|
|
uniformParams.offset.z = constOffset.value.s32v[2];
|
|
}
|
|
else
|
|
{
|
|
constParams.constOffsets.x = constOffset.value.s32v[0];
|
|
if(gradCoords >= 2)
|
|
constParams.constOffsets.y = constOffset.value.s32v[1];
|
|
if(gradCoords >= 3)
|
|
constParams.constOffsets.z = constOffset.value.s32v[2];
|
|
}
|
|
}
|
|
else if(operands.flags & rdcspv::ImageOperands::Offset)
|
|
{
|
|
ShaderVariable offset = lane.GetSrc(operands.offset);
|
|
|
|
// sign extend variables lower than 32-bits
|
|
for(uint8_t c = 0; c < offset.columns; c++)
|
|
{
|
|
if(offset.type == VarType::SByte)
|
|
offset.value.s32v[c] = offset.value.s8v[c];
|
|
else if(offset.type == VarType::SShort)
|
|
offset.value.s32v[c] = offset.value.s16v[c];
|
|
}
|
|
|
|
// if the app's shader used a dynamic offset, we can too!
|
|
uniformParams.offset.x = offset.value.s32v[0];
|
|
if(gradCoords >= 2)
|
|
uniformParams.offset.y = offset.value.s32v[1];
|
|
if(gradCoords >= 3)
|
|
uniformParams.offset.z = offset.value.s32v[2];
|
|
}
|
|
|
|
if(!m_pDriver->GetDeviceEnabledFeatures().shaderImageGatherExtended &&
|
|
(uniformParams.offset.x != 0 || uniformParams.offset.y != 0 || uniformParams.offset.z != 0))
|
|
{
|
|
m_pDriver->AddDebugMessage(
|
|
MessageCategory::Execution, MessageSeverity::High, MessageSource::RuntimeWarning,
|
|
StringFormat::Fmt("Use of constant offsets %d/%d/%d is not supported without "
|
|
"shaderImageGatherExtended device feature",
|
|
uniformParams.offset.x, uniformParams.offset.y, uniformParams.offset.z));
|
|
}
|
|
|
|
VkPipeline pipe = MakePipe(constParams, 32, depthTex, uintTex, sintTex);
|
|
|
|
if(pipe == VK_NULL_HANDLE)
|
|
{
|
|
m_pDriver->AddDebugMessage(MessageCategory::Execution, MessageSeverity::High,
|
|
MessageSource::RuntimeWarning,
|
|
"Failed to compile graphics pipeline for sampling operation");
|
|
return false;
|
|
}
|
|
|
|
VkCommandBuffer cmd = queuedOpCmdBuffer;
|
|
if(cmd == VK_NULL_HANDLE)
|
|
{
|
|
if(StartQueuedOps())
|
|
cmd = queuedOpCmdBuffer;
|
|
}
|
|
if(cmd == VK_NULL_HANDLE)
|
|
return false;
|
|
|
|
if(queueIndex >= ShaderDebugData::MAX_QUEUED_OPS)
|
|
{
|
|
m_pDriver->AddDebugMessage(MessageCategory::Execution, MessageSeverity::High,
|
|
MessageSource::RuntimeWarning, "Too many GPU queued operations");
|
|
return false;
|
|
}
|
|
|
|
VkDescriptorImageInfo samplerWriteInfo = {Unwrap(sampler), VK_NULL_HANDLE,
|
|
VK_IMAGE_LAYOUT_UNDEFINED};
|
|
VkDescriptorImageInfo imageWriteInfo = {VK_NULL_HANDLE, Unwrap(sampleView), layout};
|
|
|
|
VkDescriptorBufferInfo uniformWriteInfo = {};
|
|
m_DebugData.ConstantsBuffer.FillDescriptor(uniformWriteInfo);
|
|
const VkDescriptorSet realDescSet = Unwrap(m_DebugData.DescSets[queueIndex++]);
|
|
|
|
VkWriteDescriptorSet writeSets[] = {
|
|
{
|
|
VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
|
NULL,
|
|
realDescSet,
|
|
(uint32_t)ShaderDebugBind::Constants,
|
|
0,
|
|
1,
|
|
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,
|
|
NULL,
|
|
&uniformWriteInfo,
|
|
NULL,
|
|
},
|
|
{
|
|
VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
|
NULL,
|
|
realDescSet,
|
|
(uint32_t)constParams.dim,
|
|
0,
|
|
1,
|
|
VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
|
|
&imageWriteInfo,
|
|
NULL,
|
|
NULL,
|
|
},
|
|
{
|
|
VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
|
NULL,
|
|
realDescSet,
|
|
(uint32_t)ShaderDebugBind::Sampler,
|
|
0,
|
|
1,
|
|
VK_DESCRIPTOR_TYPE_SAMPLER,
|
|
&samplerWriteInfo,
|
|
NULL,
|
|
NULL,
|
|
},
|
|
};
|
|
|
|
if(buffer)
|
|
{
|
|
if(bufferView == VK_NULL_HANDLE)
|
|
{
|
|
// descriptor buffer, must create our own
|
|
|
|
BufViewKey key = {bufferViewDescriptor.resource, bufferViewDescriptor.byteOffset,
|
|
bufferViewDescriptor.byteSize, MakeVkFormat(bufferViewDescriptor.format)};
|
|
|
|
bufferView = m_SampleBufViews[key];
|
|
if(bufferView == VK_NULL_HANDLE)
|
|
{
|
|
VkBufferViewCreateInfo viewInfo = {
|
|
VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO,
|
|
NULL,
|
|
0,
|
|
m_pDriver->GetResourceManager()->GetLiveHandle<VkBuffer>(bufferViewDescriptor.resource),
|
|
key.format,
|
|
bufferViewDescriptor.byteOffset,
|
|
bufferViewDescriptor.byteSize,
|
|
};
|
|
|
|
VkResult vkr = m_pDriver->vkCreateBufferView(dev, &viewInfo, NULL, &bufferView);
|
|
CHECK_VKR(m_pDriver, vkr);
|
|
|
|
m_SampleBufViews[key] = bufferView;
|
|
}
|
|
}
|
|
|
|
writeSets[1].pTexelBufferView = UnwrapPtr(bufferView);
|
|
writeSets[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER;
|
|
}
|
|
|
|
// reset descriptor sets to dummy state
|
|
if(depthTex)
|
|
{
|
|
uint32_t resetIndex = 3;
|
|
|
|
rdcarray<VkWriteDescriptorSet> writes;
|
|
|
|
for(size_t i = 0; i < ARRAY_COUNT(m_DebugData.DummyWrites[resetIndex]); i++)
|
|
{
|
|
// not all textures may be supported for depth, so only update those that are valid
|
|
if(m_DebugData.DummyWrites[resetIndex][i].descriptorCount != 0)
|
|
{
|
|
m_DebugData.DummyWrites[resetIndex][i].dstSet = realDescSet;
|
|
writes.push_back(m_DebugData.DummyWrites[resetIndex][i]);
|
|
}
|
|
}
|
|
|
|
ObjDisp(dev)->UpdateDescriptorSets(Unwrap(dev), (uint32_t)writes.count(), writes.data(), 0,
|
|
NULL);
|
|
}
|
|
else
|
|
{
|
|
uint32_t resetIndex = 0;
|
|
if(uintTex)
|
|
resetIndex = 1;
|
|
else if(sintTex)
|
|
resetIndex = 2;
|
|
|
|
for(size_t i = 0; i < ARRAY_COUNT(m_DebugData.DummyWrites[resetIndex]); i++)
|
|
{
|
|
if(m_DebugData.DummyWrites[resetIndex][i].descriptorCount != 0)
|
|
m_DebugData.DummyWrites[resetIndex][i].dstSet = realDescSet;
|
|
}
|
|
ObjDisp(dev)->UpdateDescriptorSets(Unwrap(dev),
|
|
ARRAY_COUNT(m_DebugData.DummyWrites[resetIndex]),
|
|
m_DebugData.DummyWrites[resetIndex], 0, NULL);
|
|
}
|
|
|
|
// overwrite with our data
|
|
ObjDisp(dev)->UpdateDescriptorSets(Unwrap(dev), sampler != VK_NULL_HANDLE ? 3 : 2, writeSets, 0,
|
|
NULL);
|
|
|
|
uint32_t constsOffset = 0;
|
|
void *constants = m_DebugData.ConstantsBuffer.Map(&constsOffset, 0);
|
|
if(!constants)
|
|
return false;
|
|
|
|
memcpy(constants, &uniformParams, sizeof(uniformParams));
|
|
|
|
m_DebugData.ConstantsBuffer.Unmap();
|
|
|
|
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, &realDescSet, 1,
|
|
&constsOffset);
|
|
|
|
// push uvw/ddx/ddy for the vertex shader
|
|
ObjDisp(cmd)->CmdPushConstants(Unwrap(cmd), Unwrap(m_DebugData.PipeLayout), VK_SHADER_STAGE_ALL,
|
|
sizeof(Vec4f) * 0, sizeof(Vec4f), &uniformParams.uvwa);
|
|
ObjDisp(cmd)->CmdPushConstants(Unwrap(cmd), Unwrap(m_DebugData.PipeLayout), VK_SHADER_STAGE_ALL,
|
|
sizeof(Vec4f) * 1, sizeof(Vec3f), &uniformParams.ddx);
|
|
ObjDisp(cmd)->CmdPushConstants(Unwrap(cmd), Unwrap(m_DebugData.PipeLayout), VK_SHADER_STAGE_ALL,
|
|
sizeof(Vec4f) * 2, sizeof(Vec3f), &uniformParams.ddy);
|
|
|
|
ObjDisp(cmd)->CmdDraw(Unwrap(cmd), 3, 1, 0, 0);
|
|
|
|
ObjDisp(cmd)->CmdEndRenderPass(Unwrap(cmd));
|
|
|
|
RDCASSERT(sampleGatherOpResultOffset + sampleGatherOpResultByteSize <=
|
|
m_DebugData.ReadbackBuffer.TotalSize(),
|
|
sampleGatherOpResultOffset, sampleGatherOpResultByteSize,
|
|
m_DebugData.ReadbackBuffer.TotalSize());
|
|
VkBufferImageCopy region = {
|
|
sampleGatherOpResultOffset, sizeof(Vec4f), 1,
|
|
{VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, {0, 0, 0}, {1, 1, 1},
|
|
};
|
|
sampleGatherOpResultOffset += sampleGatherOpResultByteSize;
|
|
ObjDisp(cmd)->CmdCopyImageToBuffer(Unwrap(cmd), Unwrap(m_DebugData.Image),
|
|
VK_IMAGE_LAYOUT_GENERAL,
|
|
m_DebugData.ReadbackBuffer.UnwrappedBuffer(), 1, ®ion);
|
|
|
|
hasResult = false;
|
|
return true;
|
|
}
|
|
|
|
virtual bool QueueCalculateMathOp(rdcspv::GLSLstd450 op,
|
|
const rdcarray<ShaderVariable> ¶ms) override
|
|
{
|
|
CHECK_DEVICE_THREAD();
|
|
RDCASSERT(params.size() <= 3, params.size());
|
|
|
|
int floatSizeIdx = 0;
|
|
if(params[0].type == VarType::Half)
|
|
floatSizeIdx = 1;
|
|
else if(params[0].type == VarType::Double)
|
|
floatSizeIdx = 2;
|
|
|
|
if(m_DebugData.MathPipe[floatSizeIdx] == VK_NULL_HANDLE)
|
|
{
|
|
ShaderConstParameters pipeParams = {};
|
|
pipeParams.operation = (uint32_t)rdcspv::Op::ExtInst;
|
|
m_DebugData.MathPipe[floatSizeIdx] =
|
|
MakePipe(pipeParams, VarTypeByteSize(params[0].type) * 8, false, false, false);
|
|
|
|
if(m_DebugData.MathPipe[floatSizeIdx] == VK_NULL_HANDLE)
|
|
{
|
|
m_pDriver->AddDebugMessage(MessageCategory::Execution, MessageSeverity::High,
|
|
MessageSource::RuntimeWarning,
|
|
"Failed to compile graphics pipeline for math operation");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
VkCommandBuffer cmd = queuedOpCmdBuffer;
|
|
if(cmd == VK_NULL_HANDLE)
|
|
{
|
|
if(StartQueuedOps())
|
|
cmd = queuedOpCmdBuffer;
|
|
}
|
|
if(cmd == VK_NULL_HANDLE)
|
|
return false;
|
|
|
|
if(queueIndex >= ShaderDebugData::MAX_QUEUED_OPS)
|
|
{
|
|
m_pDriver->AddDebugMessage(MessageCategory::Execution, MessageSeverity::High,
|
|
MessageSource::RuntimeWarning, "Too many GPU queued operations");
|
|
return false;
|
|
}
|
|
VkDescriptorBufferInfo storageWriteInfo = {};
|
|
m_DebugData.MathResult.FillDescriptor(storageWriteInfo);
|
|
|
|
const VkDescriptorSet realDescSet = Unwrap(m_DebugData.DescSets[queueIndex++]);
|
|
|
|
VkWriteDescriptorSet writeSets[] = {
|
|
{
|
|
VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
|
NULL,
|
|
realDescSet,
|
|
(uint32_t)ShaderDebugBind::MathResult,
|
|
0,
|
|
1,
|
|
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
|
|
NULL,
|
|
&storageWriteInfo,
|
|
NULL,
|
|
},
|
|
};
|
|
|
|
VkDevice dev = m_pDriver->GetDev();
|
|
|
|
ObjDisp(dev)->UpdateDescriptorSets(Unwrap(dev), 1, writeSets, 0, NULL);
|
|
|
|
VkMarkerRegion markerRegion("QueueCalculateMathOp");
|
|
|
|
ObjDisp(cmd)->CmdBindPipeline(Unwrap(cmd), VK_PIPELINE_BIND_POINT_COMPUTE,
|
|
Unwrap(m_DebugData.MathPipe[floatSizeIdx]));
|
|
|
|
uint32_t constsOffset = 0;
|
|
ObjDisp(cmd)->CmdBindDescriptorSets(Unwrap(cmd), VK_PIPELINE_BIND_POINT_COMPUTE,
|
|
Unwrap(m_DebugData.PipeLayout), 0, 1, &realDescSet, 1,
|
|
&constsOffset);
|
|
|
|
// push the parameters
|
|
for(size_t i = 0; i < params.size(); i++)
|
|
{
|
|
RDCASSERTEQUAL(params[i].type, params[0].type);
|
|
double p[4] = {};
|
|
memcpy(p, params[i].value.f32v.data(), VarTypeByteSize(params[i].type) * params[i].columns);
|
|
ObjDisp(cmd)->CmdPushConstants(Unwrap(cmd), Unwrap(m_DebugData.PipeLayout),
|
|
VK_SHADER_STAGE_ALL, uint32_t(sizeof(p) * i), sizeof(p), p);
|
|
}
|
|
|
|
// push the operation afterwards
|
|
ObjDisp(cmd)->CmdPushConstants(Unwrap(cmd), Unwrap(m_DebugData.PipeLayout), VK_SHADER_STAGE_ALL,
|
|
sizeof(Vec4f) * 6, sizeof(uint32_t), &op);
|
|
|
|
ObjDisp(cmd)->CmdDispatch(Unwrap(cmd), 1, 1, 1);
|
|
|
|
VkBufferMemoryBarrier bufBarrier = {
|
|
VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
|
|
NULL,
|
|
VK_ACCESS_SHADER_WRITE_BIT,
|
|
VK_ACCESS_TRANSFER_READ_BIT,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
m_DebugData.MathResult.UnwrappedBuffer(),
|
|
0,
|
|
VK_WHOLE_SIZE,
|
|
};
|
|
|
|
DoPipelineBarrier(cmd, 1, &bufBarrier);
|
|
|
|
RDCASSERT(mathOpResultOffset + mathOpResultByteSize <= m_DebugData.ReadbackBuffer.TotalSize(),
|
|
mathOpResultOffset, mathOpResultByteSize, m_DebugData.ReadbackBuffer.TotalSize());
|
|
VkBufferCopy bufCopy = {0, mathOpResultOffset, mathOpResultByteSize};
|
|
mathOpResultOffset += mathOpResultByteSize;
|
|
ObjDisp(cmd)->CmdCopyBuffer(Unwrap(cmd), m_DebugData.MathResult.UnwrappedBuffer(),
|
|
m_DebugData.ReadbackBuffer.UnwrappedBuffer(), 1, &bufCopy);
|
|
return true;
|
|
}
|
|
|
|
bool StartQueuedOps()
|
|
{
|
|
CHECK_DEVICE_THREAD();
|
|
|
|
RDCASSERTEQUAL(queueIndex, 0);
|
|
RDCASSERTEQUAL(queuedOpCmdBuffer, VK_NULL_HANDLE);
|
|
RDCASSERTEQUAL(mathOpResultOffset, 0);
|
|
RDCASSERTEQUAL(sampleGatherOpResultOffset, 0);
|
|
|
|
if(queuedOpCmdBuffer != VK_NULL_HANDLE)
|
|
return false;
|
|
|
|
queuedOpCmdBuffer = m_pDriver->GetNextCmd();
|
|
if(queuedOpCmdBuffer == VK_NULL_HANDLE)
|
|
return false;
|
|
|
|
VkCommandBuffer cmd = queuedOpCmdBuffer;
|
|
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);
|
|
CHECK_VKR(m_pDriver, vkr);
|
|
|
|
sampleGatherOpResultOffset = sampleGatherOpResultsStart;
|
|
return true;
|
|
}
|
|
|
|
virtual bool GetQueuedResults(rdcarray<ShaderVariable *> &mathOpResults,
|
|
rdcarray<ShaderVariable *> &sampleGatherResults) override
|
|
{
|
|
CHECK_DEVICE_THREAD();
|
|
if(queuedOpCmdBuffer == VK_NULL_HANDLE)
|
|
return false;
|
|
|
|
VkBufferMemoryBarrier bufBarrier = {
|
|
VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
|
|
NULL,
|
|
VK_ACCESS_TRANSFER_WRITE_BIT,
|
|
VK_ACCESS_HOST_READ_BIT,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
m_DebugData.ReadbackBuffer.UnwrappedBuffer(),
|
|
0,
|
|
VK_WHOLE_SIZE,
|
|
};
|
|
|
|
VkCommandBuffer cmd = queuedOpCmdBuffer;
|
|
if(cmd == VK_NULL_HANDLE)
|
|
return false;
|
|
|
|
// wait for copy to finish before reading back to host
|
|
DoPipelineBarrier(cmd, 1, &bufBarrier);
|
|
|
|
VkResult vkr = ObjDisp(cmd)->EndCommandBuffer(Unwrap(cmd));
|
|
CHECK_VKR(m_pDriver, vkr);
|
|
|
|
m_pDriver->SubmitCmds();
|
|
m_pDriver->FlushQ();
|
|
|
|
queueIndex = 0;
|
|
queuedOpCmdBuffer = VK_NULL_HANDLE;
|
|
mathOpResultOffset = 0;
|
|
sampleGatherOpResultOffset = 0;
|
|
|
|
byte *gpuResults = (byte *)m_DebugData.ReadbackBuffer.Map(NULL, 0);
|
|
if(!gpuResults)
|
|
return false;
|
|
|
|
uintptr_t bufferEnd = (uintptr_t)(gpuResults + m_DebugData.ReadbackBuffer.TotalSize());
|
|
|
|
byte *gpuMathOpResults = gpuResults;
|
|
for(ShaderVariable *result : mathOpResults)
|
|
{
|
|
size_t countBytes = VarTypeByteSize(result->type) * result->columns;
|
|
RDCASSERT((uintptr_t)gpuMathOpResults + countBytes <= bufferEnd, (uintptr_t)gpuMathOpResults,
|
|
countBytes, bufferEnd);
|
|
RDCASSERT(countBytes <= mathOpResultByteSize, countBytes, mathOpResultByteSize);
|
|
memcpy(result->value.u32v.data(), gpuMathOpResults, countBytes);
|
|
gpuMathOpResults += mathOpResultByteSize;
|
|
}
|
|
|
|
byte *gpuSampleGatherOpResults = gpuResults + sampleGatherOpResultsStart;
|
|
for(ShaderVariable *result : sampleGatherResults)
|
|
{
|
|
float *retf = (float *)gpuSampleGatherOpResults;
|
|
uint32_t *retu = (uint32_t *)gpuSampleGatherOpResults;
|
|
int32_t *reti = (int32_t *)gpuSampleGatherOpResults;
|
|
|
|
size_t countBytes = 16;
|
|
RDCASSERT((uintptr_t)gpuSampleGatherOpResults + countBytes <= bufferEnd,
|
|
(uintptr_t)gpuSampleGatherOpResults, countBytes, bufferEnd);
|
|
RDCASSERT(countBytes <= sampleGatherOpResultByteSize, countBytes, sampleGatherOpResultByteSize);
|
|
// convert full precision results, we did all sampling at 32-bit precision
|
|
ShaderVariable &output = *result;
|
|
for(uint8_t c = 0; c < 4; c++)
|
|
{
|
|
if(VarTypeCompType(output.type) == CompType::Float)
|
|
setFloatComp(output, c, retf[c]);
|
|
else if(VarTypeCompType(output.type) == CompType::SInt)
|
|
setIntComp(output, c, reti[c]);
|
|
else
|
|
setUintComp(output, c, retu[c]);
|
|
}
|
|
gpuSampleGatherOpResults += sampleGatherOpResultByteSize;
|
|
}
|
|
|
|
m_DebugData.ReadbackBuffer.Unmap();
|
|
return true;
|
|
}
|
|
|
|
virtual bool QueuedOpsHasSpace() override { return queueIndex < ShaderDebugData::MAX_QUEUED_OPS; }
|
|
|
|
// global over all threads
|
|
std::unordered_map<ShaderBuiltin, ShaderVariable> global_builtins;
|
|
|
|
// per-thread builtins
|
|
rdcarray<std::unordered_map<ShaderBuiltin, ShaderVariable>> thread_builtins;
|
|
|
|
// per-thread custom inputs by location [thread][location]
|
|
rdcarray<rdcarray<ShaderVariable>> location_inputs;
|
|
|
|
rdcarray<rdcfixedarray<uint32_t, arraydim<rdcspv::ThreadProperty>()>> thread_props;
|
|
|
|
uint64_t GetDeviceThreadID() const { return deviceThreadID; }
|
|
bool IsDeviceThread() const { return Threading::GetCurrentID() == GetDeviceThreadID(); }
|
|
|
|
private:
|
|
WrappedVulkan *m_pDriver = NULL;
|
|
ShaderDebugData &m_DebugData;
|
|
const VulkanCreationInfo &m_Creation;
|
|
|
|
bool m_ResourcesDirty = false;
|
|
uint32_t m_EventID;
|
|
ResourceId m_ShaderID;
|
|
|
|
rdcarray<DescriptorAccess> m_Access;
|
|
rdcarray<Descriptor> m_Descriptors;
|
|
rdcarray<SamplerDescriptor> m_SamplerDescriptors;
|
|
|
|
std::map<ResourceId, VkImageView> m_SampleViews;
|
|
struct BufViewKey
|
|
{
|
|
ResourceId buf;
|
|
VkDeviceSize offs, size;
|
|
VkFormat format;
|
|
bool operator==(const BufViewKey &o) const
|
|
{
|
|
return buf == o.buf && offs == o.offs && size == o.size && format == o.format;
|
|
}
|
|
bool operator<(const BufViewKey &o) const
|
|
{
|
|
if(buf != o.buf)
|
|
return buf < o.buf;
|
|
if(offs != o.offs)
|
|
return offs < o.offs;
|
|
if(size != o.size)
|
|
return size < o.size;
|
|
if(format != o.format)
|
|
return format < o.format;
|
|
return false;
|
|
}
|
|
};
|
|
rdcflatmap<BufViewKey, VkBufferView> m_SampleBufViews;
|
|
|
|
typedef rdcpair<ResourceId, float> SamplerBiasKey;
|
|
std::map<SamplerBiasKey, VkSampler> m_BiasSamplers;
|
|
|
|
std::map<ShaderBindIndex, bytebuf> bufferCache;
|
|
|
|
VkCommandBuffer queuedOpCmdBuffer = VK_NULL_HANDLE;
|
|
VkDeviceSize mathOpResultOffset = 0;
|
|
VkDeviceSize sampleGatherOpResultOffset = 0;
|
|
uint32_t queueIndex = 0;
|
|
const VkDeviceSize mathOpResultByteSize = sizeof(Vec4f) * 2;
|
|
const VkDeviceSize sampleGatherOpResultByteSize = sizeof(Vec4f);
|
|
const VkDeviceSize sampleGatherOpResultsStart =
|
|
ShaderDebugData::MAX_QUEUED_OPS * mathOpResultByteSize;
|
|
|
|
struct ImageData
|
|
{
|
|
uint32_t width = 0, height = 0, depth = 0;
|
|
uint32_t texelSize = 0;
|
|
uint64_t rowPitch = 0, slicePitch = 0, samplePitch = 0;
|
|
ResourceFormat fmt;
|
|
bytebuf bytes;
|
|
|
|
byte *texel(const uint32_t *coord, uint32_t sample)
|
|
{
|
|
byte *ret = bytes.data();
|
|
|
|
ret += samplePitch * sample;
|
|
ret += slicePitch * coord[2];
|
|
ret += rowPitch * coord[1];
|
|
ret += texelSize * coord[0];
|
|
|
|
return ret;
|
|
}
|
|
};
|
|
|
|
std::map<ShaderBindIndex, ImageData> imageCache;
|
|
|
|
const Descriptor &GetDescriptor(const rdcstr &access, const ShaderBindIndex &index, bool &valid)
|
|
{
|
|
CHECK_DEVICE_THREAD();
|
|
static Descriptor dummy;
|
|
|
|
if(index.category == DescriptorCategory::Unknown)
|
|
{
|
|
// invalid index, return a dummy data but don't mark as invalid
|
|
return dummy;
|
|
}
|
|
|
|
int32_t a = m_Access.indexOf(index);
|
|
|
|
// this should not happen unless the debugging references an array element that we didn't
|
|
// detect dynamically. We could improve this by retrieving a more conservative access set
|
|
// internally so that all descriptors are 'accessed'
|
|
if(a < 0)
|
|
{
|
|
m_pDriver->AddDebugMessage(MessageCategory::Execution, MessageSeverity::High,
|
|
MessageSource::RuntimeWarning,
|
|
StringFormat::Fmt("Internal error: Binding %s %u[%u] did not "
|
|
"exist in calculated descriptor access when %s.",
|
|
ToStr(index.category).c_str(), index.index,
|
|
index.arrayElement, access.c_str()));
|
|
valid = false;
|
|
return dummy;
|
|
}
|
|
|
|
return m_Descriptors[a];
|
|
}
|
|
|
|
const SamplerDescriptor &GetSamplerDescriptor(const rdcstr &access, const ShaderBindIndex &index,
|
|
bool &valid)
|
|
{
|
|
CHECK_DEVICE_THREAD();
|
|
static SamplerDescriptor dummy;
|
|
|
|
if(index.category == DescriptorCategory::Unknown)
|
|
{
|
|
// invalid index, return a dummy data but don't mark as invalid
|
|
return dummy;
|
|
}
|
|
|
|
int32_t a = m_Access.indexOf(index);
|
|
|
|
// this should not happen unless the debugging references an array element that we didn't
|
|
// detect dynamically. We could improve this by retrieving a more conservative access set
|
|
// internally so that all descriptors are 'accessed'
|
|
if(a < 0)
|
|
{
|
|
m_pDriver->AddDebugMessage(MessageCategory::Execution, MessageSeverity::High,
|
|
MessageSource::RuntimeWarning,
|
|
StringFormat::Fmt("Internal error: Binding %s %u[%u] did not "
|
|
"exist in calculated descriptor access when %s.",
|
|
ToStr(index.category).c_str(), index.index,
|
|
index.arrayElement, access.c_str()));
|
|
valid = false;
|
|
return dummy;
|
|
}
|
|
|
|
return m_SamplerDescriptors[a];
|
|
}
|
|
|
|
bytebuf &PopulateBuffer(uint64_t address, size_t &offs)
|
|
{
|
|
CHECK_DEVICE_THREAD();
|
|
// pick a non-overlapping bind namespace for direct pointer access
|
|
ShaderBindIndex bind;
|
|
ResourceId id;
|
|
uint64_t ptrOffs;
|
|
|
|
m_pDriver->GetResIDFromAddr(address, id, ptrOffs);
|
|
if(id == ResourceId())
|
|
{
|
|
bind.arrayElement = 0;
|
|
auto insertIt = bufferCache.insert(std::make_pair(bind, bytebuf()));
|
|
m_pDriver->AddDebugMessage(MessageCategory::Execution, MessageSeverity::High,
|
|
MessageSource::RuntimeWarning,
|
|
StringFormat::Fmt("invalid or OOB pointer access detected ."));
|
|
return insertIt.first->second;
|
|
}
|
|
bind.arrayElement = (address - offs) & 0xFFFFFFFFU;
|
|
offs = size_t(ptrOffs);
|
|
|
|
auto insertIt = bufferCache.insert(std::make_pair(bind, bytebuf()));
|
|
bytebuf &data = insertIt.first->second;
|
|
if(insertIt.second)
|
|
{
|
|
// if the resources might be dirty from side-effects from the action, replay back to right
|
|
// before it.
|
|
if(m_ResourcesDirty)
|
|
{
|
|
VkMarkerRegion region("un-dirtying resources");
|
|
m_pDriver->ReplayLog(0, m_EventID, eReplay_WithoutDraw);
|
|
m_ResourcesDirty = false;
|
|
}
|
|
m_pDriver->GetDebugManager()->GetBufferData(id, 0, 0, data);
|
|
}
|
|
return data;
|
|
}
|
|
|
|
bytebuf &PopulateBuffer(ShaderBindIndex bind)
|
|
{
|
|
CHECK_DEVICE_THREAD();
|
|
auto insertIt = bufferCache.insert(std::make_pair(bind, bytebuf()));
|
|
bytebuf &data = insertIt.first->second;
|
|
if(insertIt.second)
|
|
{
|
|
CHECK_DEVICE_THREAD();
|
|
bool valid = true;
|
|
const Descriptor &bufData = GetDescriptor("accessing buffer value", bind, valid);
|
|
if(valid)
|
|
{
|
|
// if the resources might be dirty from side-effects from the action, replay back to right
|
|
// before it.
|
|
if(m_ResourcesDirty)
|
|
{
|
|
VkMarkerRegion region("un-dirtying resources");
|
|
m_pDriver->ReplayLog(0, m_EventID, eReplay_WithoutDraw);
|
|
m_ResourcesDirty = false;
|
|
}
|
|
|
|
if(bufData.resource != ResourceId())
|
|
{
|
|
m_pDriver->GetReplay()->GetBufferData(
|
|
m_pDriver->GetResourceManager()->GetLiveID(bufData.resource), bufData.byteOffset,
|
|
bufData.byteSize, data);
|
|
}
|
|
}
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
ImageData &PopulateImage(ShaderBindIndex bind)
|
|
{
|
|
CHECK_DEVICE_THREAD();
|
|
auto insertIt = imageCache.insert(std::make_pair(bind, ImageData()));
|
|
ImageData &data = insertIt.first->second;
|
|
if(insertIt.second)
|
|
{
|
|
CHECK_DEVICE_THREAD();
|
|
bool valid = true;
|
|
const Descriptor &imgData = GetDescriptor("performing image load/store", bind, valid);
|
|
if(valid)
|
|
{
|
|
// if the resources might be dirty from side-effects from the action, replay back to right
|
|
// before it.
|
|
if(m_ResourcesDirty)
|
|
{
|
|
VkMarkerRegion region("un-dirtying resources");
|
|
m_pDriver->ReplayLog(0, m_EventID, eReplay_WithoutDraw);
|
|
m_ResourcesDirty = false;
|
|
}
|
|
|
|
if(imgData.type == DescriptorType::TypedBuffer ||
|
|
imgData.type == DescriptorType::ReadWriteTypedBuffer)
|
|
{
|
|
VkFormat format;
|
|
VkDeviceSize byteWidth;
|
|
VkDeviceSize offset;
|
|
ResourceId buffer;
|
|
|
|
if(imgData.view == ResourceId())
|
|
{
|
|
// descriptor buffer, no buffer view
|
|
buffer = imgData.resource;
|
|
offset = imgData.byteOffset;
|
|
format = MakeVkFormat(imgData.format);
|
|
byteWidth = imgData.byteSize;
|
|
}
|
|
else
|
|
{
|
|
const VulkanCreationInfo::BufferView &viewProps = m_Creation.GetBufferViewInfo(
|
|
m_pDriver->GetResourceManager()->GetLiveID(imgData.view));
|
|
buffer = viewProps.buffer;
|
|
offset = viewProps.offset;
|
|
format = viewProps.format;
|
|
byteWidth = viewProps.size;
|
|
}
|
|
|
|
const VulkanCreationInfo::Buffer &bufferProps = m_Creation.GetBufferInfo(buffer);
|
|
|
|
// width in bytes, either from the view or from the remainder of the buffer
|
|
if(byteWidth == VK_WHOLE_SIZE)
|
|
byteWidth = bufferProps.size - offset;
|
|
|
|
data.fmt = MakeResourceFormat(format);
|
|
data.texelSize = (uint32_t)GetByteSize(1, 1, 1, format, 0);
|
|
|
|
// convert to a texel width, rounding down as per spec (only possible from VK_WHOLE_SIZE)
|
|
data.width = uint32_t(byteWidth / data.texelSize);
|
|
data.height = 1;
|
|
data.depth = 1;
|
|
|
|
data.samplePitch = data.slicePitch = data.rowPitch = data.width * data.texelSize;
|
|
|
|
m_pDriver->GetReplay()->GetBufferData(
|
|
m_pDriver->GetResourceManager()->GetLiveID(imgData.resource), offset, data.rowPitch,
|
|
data.bytes);
|
|
}
|
|
else if(imgData.view != ResourceId())
|
|
{
|
|
const VulkanCreationInfo::ImageView &viewProps =
|
|
m_Creation.GetImageViewInfo(m_pDriver->GetResourceManager()->GetLiveID(imgData.view));
|
|
const VulkanCreationInfo::Image &imageProps = m_Creation.GetImageInfo(viewProps.image);
|
|
|
|
uint32_t mip = viewProps.range.baseMipLevel;
|
|
|
|
data.width = RDCMAX(1U, imageProps.extent.width >> mip);
|
|
data.height = RDCMAX(1U, imageProps.extent.height >> mip);
|
|
if(imageProps.type == VK_IMAGE_TYPE_3D)
|
|
{
|
|
data.depth = RDCMAX(1U, imageProps.extent.depth >> mip);
|
|
}
|
|
else
|
|
{
|
|
data.depth = viewProps.range.layerCount;
|
|
if(data.depth == VK_REMAINING_ARRAY_LAYERS)
|
|
data.depth = imageProps.arrayLayers - viewProps.range.baseArrayLayer;
|
|
}
|
|
|
|
ResourceFormat fmt = MakeResourceFormat(imageProps.format);
|
|
|
|
data.fmt = MakeResourceFormat(imageProps.format);
|
|
data.texelSize = (uint32_t)GetByteSize(1, 1, 1, imageProps.format, 0);
|
|
data.rowPitch = (uint32_t)GetByteSize(data.width, 1, 1, imageProps.format, 0);
|
|
data.slicePitch = GetByteSize(data.width, data.height, 1, imageProps.format, 0);
|
|
data.samplePitch = GetByteSize(data.width, data.height, data.depth, imageProps.format, 0);
|
|
|
|
const uint32_t numSlices = imageProps.type == VK_IMAGE_TYPE_3D ? 1 : data.depth;
|
|
const uint32_t numSamples = (uint32_t)imageProps.samples;
|
|
|
|
data.bytes.reserve(size_t(data.samplePitch * numSamples));
|
|
|
|
// defaults are fine - no interpretation. Maybe we could use the view's typecast?
|
|
const GetTextureDataParams params = GetTextureDataParams();
|
|
|
|
for(uint32_t sample = 0; sample < numSamples; sample++)
|
|
{
|
|
for(uint32_t slice = 0; slice < numSlices; slice++)
|
|
{
|
|
bytebuf subBytes;
|
|
m_pDriver->GetReplay()->GetTextureData(
|
|
viewProps.image, Subresource(mip, slice, sample), params, subBytes);
|
|
|
|
// fast path, swap into output if there's only one slice and one sample (common case)
|
|
if(numSlices == 1 && numSamples == 1)
|
|
{
|
|
subBytes.swap(data.bytes);
|
|
}
|
|
else
|
|
{
|
|
data.bytes.append(subBytes);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
VkPipeline MakePipe(const ShaderConstParameters ¶ms, uint32_t floatBitSize, bool depthTex,
|
|
bool uintTex, bool sintTex)
|
|
{
|
|
CHECK_DEVICE_THREAD();
|
|
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(depthTex)
|
|
shaderIndex = 1;
|
|
if(uintTex)
|
|
shaderIndex = 2;
|
|
else if(sintTex)
|
|
shaderIndex = 3;
|
|
|
|
if(params.operation == (uint32_t)rdcspv::Op::ExtInst)
|
|
{
|
|
shaderIndex = 4;
|
|
if(floatBitSize == 16)
|
|
shaderIndex = 5;
|
|
else if(floatBitSize == 64)
|
|
shaderIndex = 6;
|
|
}
|
|
|
|
if(m_DebugData.Module[shaderIndex] == VK_NULL_HANDLE)
|
|
{
|
|
rdcarray<uint32_t> spirv;
|
|
|
|
if(params.operation == (uint32_t)rdcspv::Op::ExtInst)
|
|
{
|
|
GenerateMathShaderModule(spirv, floatBitSize);
|
|
}
|
|
else
|
|
{
|
|
RDCASSERTMSG("Assume sampling happens with 32-bit float inputs", floatBitSize == 32,
|
|
floatBitSize);
|
|
GenerateSamplingShaderModule(spirv, depthTex, 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]);
|
|
CHECK_VKR(m_pDriver, vkr);
|
|
|
|
const char *filename[] = {
|
|
"/debug_psgather_float.spv", "/debug_psgather_depth.spv", "/debug_psgather_uint.spv",
|
|
"/debug_psgather_sint.spv", "/debug_psmath32.spv", "/debug_psmath16.spv",
|
|
"/debug_psmath64.spv",
|
|
};
|
|
|
|
if(!Vulkan_Debug_PSDebugDumpDirPath().empty())
|
|
FileIO::WriteAll(Vulkan_Debug_PSDebugDumpDirPath() + filename[shaderIndex], spirv);
|
|
}
|
|
|
|
uint32_t key = params.hashKey(shaderIndex);
|
|
|
|
if(m_DebugData.m_Pipelines[key] != VK_NULL_HANDLE)
|
|
return m_DebugData.m_Pipelines[key];
|
|
|
|
RDCLOG(
|
|
"Making new pipeline for shader type %u, operation %s, dim %u, useGrad/useGatherOffsets %u,"
|
|
" gather channel %u, gather offsets...",
|
|
shaderIndex, ToStr((rdcspv::Op)params.operation).c_str(), params.dim,
|
|
params.useGradOrGatherOffsets, params.gatherChannel);
|
|
|
|
if(params.operation == (uint32_t)rdcspv::Op::ExtInst)
|
|
{
|
|
VkComputePipelineCreateInfo computePipeInfo = {
|
|
VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
|
|
NULL,
|
|
0,
|
|
{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, NULL, 0,
|
|
VK_SHADER_STAGE_COMPUTE_BIT, m_DebugData.Module[shaderIndex], "main", NULL},
|
|
m_DebugData.PipeLayout,
|
|
VK_NULL_HANDLE,
|
|
0,
|
|
};
|
|
|
|
VkPipeline pipe = VK_NULL_HANDLE;
|
|
VkResult vkr = m_pDriver->vkCreateComputePipelines(m_pDriver->GetDev(),
|
|
m_pDriver->GetShaderCache()->GetPipeCache(),
|
|
1, &computePipeInfo, NULL, &pipe);
|
|
if(vkr != VK_SUCCESS)
|
|
{
|
|
RDCERR("Failed creating debug pipeline: %s", ToStr(vkr).c_str());
|
|
return VK_NULL_HANDLE;
|
|
}
|
|
|
|
m_DebugData.m_Pipelines[key] = pipe;
|
|
|
|
return pipe;
|
|
}
|
|
|
|
const VkPipelineShaderStageCreateInfo shaderStages[2] = {
|
|
{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, NULL, 0, VK_SHADER_STAGE_VERTEX_BIT,
|
|
m_pDriver->GetShaderCache()->GetBuiltinModule(BuiltinShader::ShaderDebugSampleVS), "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(),
|
|
m_pDriver->GetShaderCache()->GetPipeCache(),
|
|
1, &graphicsPipeInfo, NULL, &pipe);
|
|
if(vkr != VK_SUCCESS)
|
|
{
|
|
RDCERR("Failed creating debug pipeline: %s", ToStr(vkr).c_str());
|
|
return VK_NULL_HANDLE;
|
|
}
|
|
|
|
m_DebugData.m_Pipelines[key] = pipe;
|
|
|
|
return pipe;
|
|
}
|
|
|
|
void GenerateMathShaderModule(rdcarray<uint32_t> &spirv, uint32_t floatBitSize)
|
|
{
|
|
CHECK_DEVICE_THREAD();
|
|
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::Scalar sizedScalar;
|
|
if(floatBitSize == 32)
|
|
sizedScalar = rdcspv::scalar<float>();
|
|
else if(floatBitSize == 64)
|
|
sizedScalar = rdcspv::scalar<double>();
|
|
else
|
|
sizedScalar = rdcspv::scalar<half_float::half>();
|
|
|
|
rdcspv::Id u32 = editor.DeclareType(rdcspv::scalar<uint32_t>());
|
|
rdcspv::Id floatType = editor.DeclareType(sizedScalar);
|
|
rdcspv::Id vec4Type = editor.DeclareType(rdcspv::Vector(sizedScalar, 4));
|
|
|
|
rdcspv::Id pushStructID =
|
|
editor.AddType(rdcspv::OpTypeStruct(editor.MakeId(), {vec4Type, vec4Type, vec4Type, 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) * 2)));
|
|
editor.AddDecoration(rdcspv::OpMemberDecorate(
|
|
pushStructID, 2, rdcspv::DecorationParam<rdcspv::Decoration::Offset>(sizeof(Vec4f) * 4)));
|
|
editor.AddDecoration(rdcspv::OpMemberDecorate(
|
|
pushStructID, 3, rdcspv::DecorationParam<rdcspv::Decoration::Offset>(sizeof(Vec4f) * 6)));
|
|
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 pushv4Type =
|
|
editor.DeclareType(rdcspv::Pointer(vec4Type, rdcspv::StorageClass::PushConstant));
|
|
rdcspv::Id pushu32Type =
|
|
editor.DeclareType(rdcspv::Pointer(u32, rdcspv::StorageClass::PushConstant));
|
|
|
|
rdcspv::Id storageStructType = editor.AddType(rdcspv::OpTypeStruct(editor.MakeId(), {vec4Type}));
|
|
editor.AddDecoration(rdcspv::OpMemberDecorate(
|
|
storageStructType, 0, rdcspv::DecorationParam<rdcspv::Decoration::Offset>(0)));
|
|
editor.DecorateStorageBufferStruct(storageStructType);
|
|
|
|
rdcspv::Id storageStructPtrType =
|
|
editor.DeclareType(rdcspv::Pointer(storageStructType, editor.StorageBufferClass()));
|
|
rdcspv::Id storageVec4PtrType =
|
|
editor.DeclareType(rdcspv::Pointer(vec4Type, editor.StorageBufferClass()));
|
|
|
|
rdcspv::Id storageVar = editor.AddVariable(
|
|
rdcspv::OpVariable(storageStructPtrType, editor.MakeId(), editor.StorageBufferClass()));
|
|
editor.AddDecoration(rdcspv::OpDecorate(
|
|
storageVar, rdcspv::DecorationParam<rdcspv::Decoration::DescriptorSet>(0U)));
|
|
editor.AddDecoration(rdcspv::OpDecorate(
|
|
storageVar,
|
|
rdcspv::DecorationParam<rdcspv::Decoration::Binding>((uint32_t)ShaderDebugBind::MathResult)));
|
|
|
|
editor.SetName(storageVar, "resultStorage");
|
|
|
|
// register the entry point
|
|
editor.AddOperation(editor.Begin(rdcspv::Section::EntryPoints),
|
|
rdcspv::OpEntryPoint(rdcspv::ExecutionModel::GLCompute, entryId, "main", {}));
|
|
editor.AddExecutionMode(rdcspv::OpExecutionMode(
|
|
entryId, rdcspv::ExecutionModeParam<rdcspv::ExecutionMode::LocalSize>(1, 1, 1)));
|
|
|
|
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),
|
|
};
|
|
|
|
rdcspv::Id zerof;
|
|
if(floatBitSize == 32)
|
|
zerof = editor.AddConstantImmediate<float>(0.0f);
|
|
else if(floatBitSize == 64)
|
|
zerof = editor.AddConstantImmediate<double>(0.0);
|
|
else
|
|
zerof = editor.AddConstantImmediate<half_float::half>(half_float::half(0.0f));
|
|
|
|
// load the parameters and the op
|
|
rdcspv::Id aPtr =
|
|
func.add(rdcspv::OpAccessChain(pushv4Type, editor.MakeId(), pushVar, {consts[0]}));
|
|
rdcspv::Id bPtr =
|
|
func.add(rdcspv::OpAccessChain(pushv4Type, editor.MakeId(), pushVar, {consts[1]}));
|
|
rdcspv::Id cPtr =
|
|
func.add(rdcspv::OpAccessChain(pushv4Type, 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(vec4Type, editor.MakeId(), aPtr));
|
|
rdcspv::Id b = func.add(rdcspv::OpLoad(vec4Type, editor.MakeId(), bPtr));
|
|
rdcspv::Id c = func.add(rdcspv::OpLoad(vec4Type, editor.MakeId(), cPtr));
|
|
rdcspv::Id opParam = func.add(rdcspv::OpLoad(u32, editor.MakeId(), opPtr));
|
|
|
|
// access chain the output
|
|
rdcspv::Id outVar =
|
|
func.add(rdcspv::OpAccessChain(storageVec4PtrType, editor.MakeId(), storageVar, {consts[0]}));
|
|
|
|
rdcspv::Id breakLabel = editor.MakeId();
|
|
rdcspv::Id defaultLabel = editor.MakeId();
|
|
|
|
rdcarray<rdcspv::SwitchPairU32LiteralId> 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,
|
|
})
|
|
{
|
|
// most operations aren't allowed on doubles
|
|
if(floatBitSize == 64 && op != rdcspv::GLSLstd450::Sqrt &&
|
|
op != rdcspv::GLSLstd450::InverseSqrt && op != rdcspv::GLSLstd450::Normalize)
|
|
continue;
|
|
|
|
rdcspv::Id label = editor.MakeId();
|
|
targets.push_back({(uint32_t)op, label});
|
|
|
|
cases.add(rdcspv::OpLabel(label));
|
|
rdcspv::Id result = cases.add(rdcspv::OpGLSL450(vec4Type, editor.MakeId(), glsl450, op, {a}));
|
|
cases.add(rdcspv::OpStore(outVar, result));
|
|
cases.add(rdcspv::OpBranch(breakLabel));
|
|
}
|
|
|
|
if(floatBitSize != 64)
|
|
{
|
|
// 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(vec4Type, editor.MakeId(), glsl450, op, {a, b}));
|
|
cases.add(rdcspv::OpStore(outVar, result));
|
|
cases.add(rdcspv::OpBranch(breakLabel));
|
|
}
|
|
}
|
|
|
|
{
|
|
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(vec4Type, 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(floatType, editor.MakeId(), glsl450, op, {a}));
|
|
rdcspv::Id resultvec = cases.add(
|
|
rdcspv::OpCompositeConstruct(vec4Type, 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(floatType, editor.MakeId(), glsl450, op, {a, b}));
|
|
rdcspv::Id resultvec = cases.add(
|
|
rdcspv::OpCompositeConstruct(vec4Type, 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(floatType, editor.MakeId(), c, {0}));
|
|
rdcspv::Id result =
|
|
cases.add(rdcspv::OpGLSL450(vec4Type, 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::OpSwitch32(opParam, defaultLabel, targets));
|
|
|
|
func.append(cases);
|
|
|
|
// default: store NULL data
|
|
func.add(rdcspv::OpLabel(defaultLabel));
|
|
func.add(rdcspv::OpStore(
|
|
outVar, editor.AddConstant(rdcspv::OpConstantNull(vec4Type, 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 depthTex, bool uintTex,
|
|
bool sintTex)
|
|
{
|
|
CHECK_DEVICE_THREAD();
|
|
// 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::ImageQuery);
|
|
editor.AddCapability(rdcspv::Capability::Sampled1D);
|
|
editor.AddCapability(rdcspv::Capability::SampledBuffer);
|
|
|
|
if(m_pDriver->GetDeviceEnabledFeatures().shaderResourceMinLod)
|
|
editor.AddCapability(rdcspv::Capability::MinLod);
|
|
if(m_pDriver->GetDeviceEnabledFeatures().shaderImageGatherExtended)
|
|
editor.AddCapability(rdcspv::Capability::ImageGatherExtended);
|
|
|
|
const bool cubeArray = (m_pDriver->GetDeviceEnabledFeatures().imageCubeArray != VK_FALSE);
|
|
|
|
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::Id v4f32 = editor.DeclareType(rdcspv::Vector(rdcspv::scalar<float>(), 4));
|
|
|
|
// int2[4]
|
|
rdcspv::Id a4v2i32 = editor.AddType(
|
|
rdcspv::OpTypeArray(editor.MakeId(), v2i32, editor.AddConstantImmediate<uint32_t>(4)));
|
|
|
|
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));
|
|
rdcspv::Id scalarResultType = editor.DeclareType(base);
|
|
|
|
// add specialisation constants for all the parameters
|
|
#define MEMBER_IDX(struct, name) uint32_t(offsetof(struct, name) / sizeof(uint32_t))
|
|
|
|
#define DECL_SPECID(type, name, value) \
|
|
rdcspv::Id name = \
|
|
editor.AddSpecConstantImmediate<type>(0U, MEMBER_IDX(ShaderConstParameters, value)); \
|
|
editor.SetName(name, "spec_" #name);
|
|
|
|
DECL_SPECID(uint32_t, operation, operation);
|
|
DECL_SPECID(bool, useGradOrGatherOffsets, useGradOrGatherOffsets);
|
|
DECL_SPECID(uint32_t, dim, dim);
|
|
DECL_SPECID(int32_t, gatherChannel, gatherChannel);
|
|
DECL_SPECID(int32_t, gather_u0, gatherOffsets.u0);
|
|
DECL_SPECID(int32_t, gather_v0, gatherOffsets.v0);
|
|
DECL_SPECID(int32_t, gather_u1, gatherOffsets.u1);
|
|
DECL_SPECID(int32_t, gather_v1, gatherOffsets.v1);
|
|
DECL_SPECID(int32_t, gather_u2, gatherOffsets.u2);
|
|
DECL_SPECID(int32_t, gather_v2, gatherOffsets.v2);
|
|
DECL_SPECID(int32_t, gather_u3, gatherOffsets.u3);
|
|
DECL_SPECID(int32_t, gather_v3, gatherOffsets.v3);
|
|
|
|
struct StructMember
|
|
{
|
|
rdcspv::Id loadedType;
|
|
rdcspv::Id ptrType;
|
|
rdcspv::Id loadedId;
|
|
const char *name;
|
|
uint32_t memberIndex;
|
|
};
|
|
|
|
rdcarray<rdcspv::Id> memberIds;
|
|
rdcarray<StructMember> cbufferMembers;
|
|
|
|
rdcspv::Id type_int32_t = editor.DeclareType(rdcspv::scalar<int32_t>());
|
|
rdcspv::Id type_float = editor.DeclareType(rdcspv::scalar<float>());
|
|
|
|
rdcspv::Id uniformptr_int32_t =
|
|
editor.DeclareType(rdcspv::Pointer(type_int32_t, rdcspv::StorageClass::Uniform));
|
|
rdcspv::Id uniformptr_float =
|
|
editor.DeclareType(rdcspv::Pointer(type_float, rdcspv::StorageClass::Uniform));
|
|
|
|
#define DECL_UNIFORM(type, name, value) \
|
|
rdcspv::Id name = editor.MakeId(); \
|
|
editor.SetName(name, "uniform_" #name); \
|
|
cbufferMembers.push_back({CONCAT(type_, type), CONCAT(uniformptr_, type), name, #name, \
|
|
MEMBER_IDX(ShaderUniformParameters, value)}); \
|
|
memberIds.push_back(CONCAT(type_, type));
|
|
DECL_UNIFORM(int32_t, texel_u, texel_uvw.x);
|
|
DECL_UNIFORM(int32_t, texel_v, texel_uvw.y);
|
|
DECL_UNIFORM(int32_t, texel_w, texel_uvw.z);
|
|
DECL_UNIFORM(int32_t, texel_lod, texel_lod);
|
|
DECL_UNIFORM(float, u, uvwa[0]);
|
|
DECL_UNIFORM(float, v, uvwa[1]);
|
|
DECL_UNIFORM(float, w, uvwa[2]);
|
|
DECL_UNIFORM(float, cube_a, uvwa[3]);
|
|
DECL_UNIFORM(float, dudx, ddx[0]);
|
|
DECL_UNIFORM(float, dvdx, ddx[1]);
|
|
DECL_UNIFORM(float, dwdx, ddx[2]);
|
|
DECL_UNIFORM(float, dudy, ddy[0]);
|
|
DECL_UNIFORM(float, dvdy, ddy[1]);
|
|
DECL_UNIFORM(float, dwdy, ddy[2]);
|
|
DECL_UNIFORM(int32_t, dynoffset_u, offset.x);
|
|
DECL_UNIFORM(int32_t, dynoffset_v, offset.y);
|
|
DECL_UNIFORM(int32_t, dynoffset_w, offset.z);
|
|
DECL_UNIFORM(int32_t, sampleIdx, sampleIdx);
|
|
DECL_UNIFORM(float, compare, compare);
|
|
DECL_UNIFORM(float, lod, lod);
|
|
DECL_UNIFORM(float, minlod, minlod);
|
|
|
|
rdcspv::Id cbufferStructID = editor.AddType(rdcspv::OpTypeStruct(editor.MakeId(), memberIds));
|
|
editor.AddDecoration(rdcspv::OpDecorate(cbufferStructID, rdcspv::Decoration::Block));
|
|
for(const StructMember &m : cbufferMembers)
|
|
{
|
|
editor.AddDecoration(rdcspv::OpMemberDecorate(
|
|
cbufferStructID, m.memberIndex,
|
|
rdcspv::DecorationParam<rdcspv::Decoration::Offset>(m.memberIndex * sizeof(uint32_t))));
|
|
editor.SetMemberName(cbufferStructID, m.memberIndex, m.name);
|
|
}
|
|
|
|
rdcspv::Id constoffset_u = gather_u0;
|
|
rdcspv::Id constoffset_uv = editor.AddConstant(
|
|
rdcspv::OpSpecConstantComposite(v2i32, editor.MakeId(), {gather_u0, gather_v0}));
|
|
rdcspv::Id constoffset_uvw = editor.AddConstant(
|
|
rdcspv::OpSpecConstantComposite(v3i32, editor.MakeId(), {gather_u0, gather_v0, gather_u1}));
|
|
editor.SetName(constoffset_u, "constoffset_u");
|
|
editor.SetName(constoffset_uv, "constoffset_uv");
|
|
editor.SetName(constoffset_uvw, "constoffset_uvw");
|
|
|
|
rdcspv::Id gather_0 = editor.AddConstant(
|
|
rdcspv::OpSpecConstantComposite(v2i32, editor.MakeId(), {gather_u0, gather_v0}));
|
|
rdcspv::Id gather_1 = editor.AddConstant(
|
|
rdcspv::OpSpecConstantComposite(v2i32, editor.MakeId(), {gather_u1, gather_v1}));
|
|
rdcspv::Id gather_2 = editor.AddConstant(
|
|
rdcspv::OpSpecConstantComposite(v2i32, editor.MakeId(), {gather_u2, gather_v2}));
|
|
rdcspv::Id gather_3 = editor.AddConstant(
|
|
rdcspv::OpSpecConstantComposite(v2i32, editor.MakeId(), {gather_u3, gather_v3}));
|
|
|
|
rdcspv::Id gatherOffsets = editor.AddConstant(rdcspv::OpSpecConstantComposite(
|
|
a4v2i32, editor.MakeId(), {gather_0, gather_1, gather_2, gather_3}));
|
|
|
|
editor.SetName(gatherOffsets, "gatherOffsets");
|
|
|
|
// 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::Cube, 0, cubeArray ? 1 : 0, 0, 1, unk)),
|
|
editor.DeclareType(rdcspv::Image(base, rdcspv::Dim::Buffer, 0, 0, 0, 1, unk)),
|
|
editor.DeclareType(rdcspv::Sampler()),
|
|
cbufferStructID,
|
|
};
|
|
rdcspv::Id bindVars[(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])),
|
|
editor.DeclareType(rdcspv::SampledImage(texSampTypes[6])),
|
|
rdcspv::Id(),
|
|
rdcspv::Id(),
|
|
};
|
|
|
|
for(size_t i = (size_t)ShaderDebugBind::First; i < (size_t)ShaderDebugBind::Count; i++)
|
|
{
|
|
rdcspv::StorageClass storageClass = rdcspv::StorageClass::UniformConstant;
|
|
|
|
if(depthTex)
|
|
{
|
|
if(i == (size_t)ShaderDebugBind::Tex3D && !m_pDriver->GetReplay()->Depth3DSupported())
|
|
continue;
|
|
else if(i == (size_t)ShaderDebugBind::TexCube && !m_pDriver->GetReplay()->DepthCubeSupported())
|
|
continue;
|
|
}
|
|
|
|
if(i == (size_t)ShaderDebugBind::Constants)
|
|
storageClass = rdcspv::StorageClass::Uniform;
|
|
|
|
rdcspv::Id ptrType = editor.DeclareType(rdcspv::Pointer(texSampTypes[i], storageClass));
|
|
|
|
bindVars[i] = editor.AddVariable(rdcspv::OpVariable(ptrType, editor.MakeId(), storageClass));
|
|
|
|
editor.AddDecoration(rdcspv::OpDecorate(
|
|
bindVars[i], rdcspv::DecorationParam<rdcspv::Decoration::DescriptorSet>(0U)));
|
|
editor.AddDecoration(rdcspv::OpDecorate(
|
|
bindVars[i], rdcspv::DecorationParam<rdcspv::Decoration::Binding>((uint32_t)i)));
|
|
}
|
|
|
|
editor.SetName(bindVars[(size_t)ShaderDebugBind::Tex1D], "Tex1D");
|
|
editor.SetName(bindVars[(size_t)ShaderDebugBind::Tex2D], "Tex2D");
|
|
if(bindVars[(size_t)ShaderDebugBind::Tex3D] != rdcspv::Id())
|
|
editor.SetName(bindVars[(size_t)ShaderDebugBind::Tex3D], "Tex3D");
|
|
editor.SetName(bindVars[(size_t)ShaderDebugBind::Tex2DMS], "Tex2DMS");
|
|
if(bindVars[(size_t)ShaderDebugBind::TexCube] != rdcspv::Id())
|
|
editor.SetName(bindVars[(size_t)ShaderDebugBind::TexCube], "TexCube");
|
|
editor.SetName(bindVars[(size_t)ShaderDebugBind::Buffer], "Buffer");
|
|
editor.SetName(bindVars[(size_t)ShaderDebugBind::Sampler], "Sampler");
|
|
editor.SetName(bindVars[(size_t)ShaderDebugBind::Constants], "CBuffer");
|
|
|
|
rdcspv::Id uvwa_ptr = editor.DeclareType(rdcspv::Pointer(v4f32, rdcspv::StorageClass::Input));
|
|
rdcspv::Id input_uvwa_var = editor.AddVariable(
|
|
rdcspv::OpVariable(uvwa_ptr, editor.MakeId(), rdcspv::StorageClass::Input));
|
|
editor.AddDecoration(rdcspv::OpDecorate(
|
|
input_uvwa_var, rdcspv::DecorationParam<rdcspv::Decoration::Location>(0)));
|
|
|
|
editor.SetName(input_uvwa_var, "input_uvwa");
|
|
|
|
// register the entry point
|
|
editor.AddOperation(editor.Begin(rdcspv::Section::EntryPoints),
|
|
rdcspv::OpEntryPoint(rdcspv::ExecutionModel::Fragment, entryId, "main",
|
|
{input_uvwa_var, outVar}));
|
|
editor.AddExecutionMode(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()));
|
|
|
|
// access chain and load all the cbuffer variables
|
|
for(const StructMember &m : cbufferMembers)
|
|
{
|
|
rdcspv::Id ptr = func.add(rdcspv::OpAccessChain(
|
|
m.ptrType, editor.MakeId(), bindVars[(size_t)ShaderDebugBind::Constants],
|
|
{editor.AddConstantImmediate<uint32_t>(m.memberIndex)}));
|
|
func.add(rdcspv::OpLoad(m.loadedType, m.loadedId, ptr));
|
|
}
|
|
|
|
// declare cbuffer composites
|
|
rdcspv::Id texel_uv =
|
|
func.add(rdcspv::OpCompositeConstruct(v2i32, editor.MakeId(), {texel_u, texel_v}));
|
|
rdcspv::Id texel_uvw =
|
|
func.add(rdcspv::OpCompositeConstruct(v3i32, editor.MakeId(), {texel_u, texel_v, texel_w}));
|
|
|
|
editor.SetName(texel_uv, "texel_uv");
|
|
editor.SetName(texel_uvw, "texel_uvw");
|
|
|
|
rdcspv::Id uv = func.add(rdcspv::OpCompositeConstruct(v2f32, editor.MakeId(), {u, v}));
|
|
rdcspv::Id uvw = func.add(rdcspv::OpCompositeConstruct(v3f32, editor.MakeId(), {u, v, w}));
|
|
rdcspv::Id uvwa =
|
|
func.add(rdcspv::OpCompositeConstruct(v4f32, editor.MakeId(), {u, v, w, cube_a}));
|
|
|
|
editor.SetName(uv, "uv");
|
|
editor.SetName(uvw, "uvw");
|
|
editor.SetName(uvwa, "uvwa");
|
|
|
|
rdcspv::Id ddx_uv = func.add(rdcspv::OpCompositeConstruct(v2f32, editor.MakeId(), {dudx, dvdx}));
|
|
rdcspv::Id ddx_uvw =
|
|
func.add(rdcspv::OpCompositeConstruct(v3f32, editor.MakeId(), {dudx, dvdx, dwdx}));
|
|
|
|
editor.SetName(ddx_uv, "ddx_uv");
|
|
editor.SetName(ddx_uvw, "ddx_uvw");
|
|
|
|
rdcspv::Id ddy_uv = func.add(rdcspv::OpCompositeConstruct(v2f32, editor.MakeId(), {dudy, dvdy}));
|
|
rdcspv::Id ddy_uvw =
|
|
func.add(rdcspv::OpCompositeConstruct(v3f32, editor.MakeId(), {dudy, dvdy, dwdy}));
|
|
|
|
editor.SetName(ddy_uv, "ddy_uv");
|
|
editor.SetName(ddy_uvw, "ddy_uvw");
|
|
|
|
rdcspv::Id dynoffset_uv =
|
|
func.add(rdcspv::OpCompositeConstruct(v2i32, editor.MakeId(), {dynoffset_u, dynoffset_v}));
|
|
rdcspv::Id dynoffset_uvw = func.add(rdcspv::OpCompositeConstruct(
|
|
v3i32, editor.MakeId(), {dynoffset_u, dynoffset_v, dynoffset_w}));
|
|
|
|
editor.SetName(dynoffset_uv, "dynoffset_uv");
|
|
editor.SetName(dynoffset_uvw, "dynoffset_uvw");
|
|
|
|
rdcspv::Id input_uvwa = func.add(rdcspv::OpLoad(v4f32, editor.MakeId(), input_uvwa_var));
|
|
rdcspv::Id input_uvw =
|
|
func.add(rdcspv::OpVectorShuffle(v3f32, editor.MakeId(), input_uvwa, input_uvwa, {0, 1, 2}));
|
|
rdcspv::Id input_uv =
|
|
func.add(rdcspv::OpVectorShuffle(v2f32, editor.MakeId(), input_uvw, input_uvw, {0, 1}));
|
|
rdcspv::Id input_u = func.add(rdcspv::OpCompositeExtract(f32, editor.MakeId(), input_uvw, {0}));
|
|
|
|
// 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::SwitchPairU32LiteralId> 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
|
|
rdcspv::Id(), // Cube
|
|
texel_u, // Buffer - u
|
|
};
|
|
|
|
// only used for QueryLod, so we can ignore MSAA/Buffer
|
|
rdcspv::Id input_coord[(uint32_t)ShaderDebugBind::Count] = {
|
|
rdcspv::Id(),
|
|
input_u, // 1D - u
|
|
input_uv, // 2D - u,v
|
|
input_uvw, // 3D - u,v,w
|
|
rdcspv::Id(), // 2DMS
|
|
input_uvw, // Cube - u,v,w
|
|
rdcspv::Id(), // Buffer
|
|
};
|
|
|
|
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
|
|
cubeArray ? uvwa : uvw, // Cube - u,v,w and array (if supported)
|
|
u, // Buffer - u
|
|
};
|
|
|
|
rdcspv::Id constoffset[(uint32_t)ShaderDebugBind::Count] = {
|
|
rdcspv::Id(),
|
|
constoffset_u, // 1D - u
|
|
constoffset_uv, // 2D - u,v
|
|
constoffset_uvw, // 3D - u,v,w
|
|
constoffset_uv, // 2DMS - u,v
|
|
rdcspv::Id(), // Cube - not valid
|
|
constoffset_u, // Buffer - u
|
|
};
|
|
|
|
rdcspv::Id dynoffset[(uint32_t)ShaderDebugBind::Count] = {
|
|
rdcspv::Id(),
|
|
dynoffset_u, // 1D - u
|
|
dynoffset_uv, // 2D - u,v
|
|
dynoffset_uvw, // 3D - u,v,w
|
|
dynoffset_uv, // 2DMS - u,v
|
|
rdcspv::Id(), // Cube - not valid
|
|
dynoffset_u, // 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
|
|
ddx_uvw, // Cube - u,v,w
|
|
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
|
|
ddy_uvw, // Cube - u,v,w
|
|
dudy, // Buffer - u
|
|
};
|
|
|
|
uint32_t sampIdx = (uint32_t)ShaderDebugBind::Sampler;
|
|
|
|
rdcspv::Id zerof = editor.AddConstantImmediate<float>(0.0f);
|
|
|
|
for(uint32_t i = (uint32_t)ShaderDebugBind::First; i < (uint32_t)ShaderDebugBind::Count; i++)
|
|
{
|
|
if(i == sampIdx || i == (uint32_t)ShaderDebugBind::Constants)
|
|
continue;
|
|
|
|
if(bindVars[i] == rdcspv::Id())
|
|
continue;
|
|
|
|
rdcspv::ImageOperandsAndParamDatas imageOperandsWithOffsets;
|
|
|
|
// most operations support offsets, set the operands commonly here.
|
|
// with the shaderImageGatherExtended feature, gather opcodes will always get their operands
|
|
// via uniforms to cut down on pipeline specialisations a little, but all other cases the
|
|
// offsets must be constant.
|
|
if(constoffset[i] != rdcspv::Id())
|
|
imageOperandsWithOffsets.setConstOffset(constoffset[i]);
|
|
|
|
// can't fetch from cubemaps
|
|
if(i != (uint32_t)ShaderDebugBind::TexCube)
|
|
{
|
|
rdcspv::Op op = rdcspv::Op::ImageFetch;
|
|
|
|
rdcspv::Id label = editor.MakeId();
|
|
targets.push_back({(uint32_t)op * 10 + i, label});
|
|
|
|
rdcspv::ImageOperandsAndParamDatas operands = imageOperandsWithOffsets;
|
|
|
|
if(i != (uint32_t)ShaderDebugBind::Buffer && i != (uint32_t)ShaderDebugBind::Tex2DMS)
|
|
operands.setLod(texel_lod);
|
|
|
|
if(i == (uint32_t)ShaderDebugBind::Tex2DMS)
|
|
operands.setSample(sampleIdx);
|
|
|
|
cases.add(rdcspv::OpLabel(label));
|
|
rdcspv::Id loaded = cases.add(rdcspv::OpLoad(texSampTypes[i], editor.MakeId(), bindVars[i]));
|
|
rdcspv::Id sampleResult = cases.add(
|
|
rdcspv::OpImageFetch(resultType, editor.MakeId(), loaded, texel_coord[i], operands));
|
|
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;
|
|
|
|
{
|
|
rdcspv::Op op = rdcspv::Op::ImageQueryLod;
|
|
|
|
rdcspv::Id label = editor.MakeId();
|
|
targets.push_back({(uint32_t)op * 10 + i, label});
|
|
|
|
cases.add(rdcspv::OpLabel(label));
|
|
rdcspv::Id loadedImage =
|
|
cases.add(rdcspv::OpLoad(texSampTypes[i], editor.MakeId(), bindVars[i]));
|
|
rdcspv::Id loadedSampler =
|
|
cases.add(rdcspv::OpLoad(texSampTypes[sampIdx], editor.MakeId(), bindVars[sampIdx]));
|
|
|
|
rdcspv::Id combined = cases.add(rdcspv::OpSampledImage(
|
|
texSampCombinedTypes[i], editor.MakeId(), loadedImage, loadedSampler));
|
|
|
|
rdcspv::Id sampleResult =
|
|
cases.add(rdcspv::OpImageQueryLod(v2f32, editor.MakeId(), combined, input_coord[i]));
|
|
sampleResult = cases.add(rdcspv::OpVectorShuffle(v4f32, editor.MakeId(), sampleResult,
|
|
sampleResult, {0, 1, 0, 1}));
|
|
|
|
// if we're sampling from an integer texture the output variable will be the same type.
|
|
// Just bitcast the float bits into it, which will come out the other side the right type.
|
|
if(uintTex || sintTex)
|
|
sampleResult = cases.add(rdcspv::OpBitcast(resultType, editor.MakeId(), sampleResult));
|
|
|
|
cases.add(rdcspv::OpStore(outVar, sampleResult));
|
|
cases.add(rdcspv::OpBranch(breakLabel));
|
|
}
|
|
|
|
for(rdcspv::Op op : {rdcspv::Op::ImageSampleExplicitLod, rdcspv::Op::ImageSampleImplicitLod})
|
|
{
|
|
rdcspv::Id label = editor.MakeId();
|
|
targets.push_back({(uint32_t)op * 10 + i, label});
|
|
|
|
cases.add(rdcspv::OpLabel(label));
|
|
rdcspv::Id loadedImage =
|
|
cases.add(rdcspv::OpLoad(texSampTypes[i], editor.MakeId(), bindVars[i]));
|
|
rdcspv::Id loadedSampler =
|
|
cases.add(rdcspv::OpLoad(texSampTypes[sampIdx], editor.MakeId(), bindVars[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(useGradOrGatherOffsets, gradCase, lodCase));
|
|
|
|
rdcspv::Id lodResult;
|
|
{
|
|
cases.add(rdcspv::OpLabel(lodCase));
|
|
rdcspv::ImageOperandsAndParamDatas operands = imageOperandsWithOffsets;
|
|
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 = imageOperandsWithOffsets;
|
|
operands.setGrad(ddxs[i], ddys[i]);
|
|
if(m_pDriver->GetDeviceEnabledFeatures().shaderResourceMinLod)
|
|
operands.setMinLod(minlod);
|
|
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));
|
|
}
|
|
|
|
// on Qualcomm we only emit Dref instructions against 2D textures, otherwise the compiler may
|
|
// crash.
|
|
if(m_pDriver->GetDriverInfo().QualcommDrefNon2DCompileCrash())
|
|
depthTex &= (i == (uint32_t)ShaderDebugBind::Tex2D);
|
|
|
|
// VUID-StandaloneSpirv-OpImage-04777
|
|
// OpImage*Dref must not consume an image whose Dim is 3D
|
|
if(i == (uint32_t)ShaderDebugBind::Tex3D)
|
|
depthTex = false;
|
|
|
|
// don't emit dref's for uint/sint textures
|
|
if(uintTex || sintTex)
|
|
depthTex = false;
|
|
|
|
if(depthTex)
|
|
{
|
|
for(rdcspv::Op op :
|
|
{rdcspv::Op::ImageSampleDrefExplicitLod, rdcspv::Op::ImageSampleDrefImplicitLod})
|
|
{
|
|
rdcspv::Id label = editor.MakeId();
|
|
targets.push_back({(uint32_t)op * 10 + i, label});
|
|
|
|
cases.add(rdcspv::OpLabel(label));
|
|
rdcspv::Id loadedImage =
|
|
cases.add(rdcspv::OpLoad(texSampTypes[i], editor.MakeId(), bindVars[i]));
|
|
rdcspv::Id loadedSampler =
|
|
cases.add(rdcspv::OpLoad(texSampTypes[sampIdx], editor.MakeId(), bindVars[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(useGradOrGatherOffsets, gradCase, lodCase));
|
|
|
|
rdcspv::Id lodResult;
|
|
{
|
|
cases.add(rdcspv::OpLabel(lodCase));
|
|
rdcspv::ImageOperandsAndParamDatas operands = imageOperandsWithOffsets;
|
|
operands.setLod(lod);
|
|
rdcspv::Id combined = cases.add(rdcspv::OpSampledImage(
|
|
texSampCombinedTypes[i], editor.MakeId(), loadedImage, loadedSampler));
|
|
|
|
lodResult = cases.add(rdcspv::OpImageSampleDrefExplicitLod(
|
|
scalarResultType, editor.MakeId(), combined, coord[i], compare, operands));
|
|
|
|
cases.add(rdcspv::OpBranch(mergeLabel));
|
|
}
|
|
|
|
rdcspv::Id gradResult;
|
|
{
|
|
cases.add(rdcspv::OpLabel(gradCase));
|
|
rdcspv::ImageOperandsAndParamDatas operands = imageOperandsWithOffsets;
|
|
operands.setGrad(ddxs[i], ddys[i]);
|
|
if(m_pDriver->GetDeviceEnabledFeatures().shaderResourceMinLod)
|
|
operands.setMinLod(minlod);
|
|
rdcspv::Id combined = cases.add(rdcspv::OpSampledImage(
|
|
texSampCombinedTypes[i], editor.MakeId(), loadedImage, loadedSampler));
|
|
|
|
gradResult = cases.add(rdcspv::OpImageSampleDrefExplicitLod(
|
|
scalarResultType, editor.MakeId(), combined, coord[i], compare, operands));
|
|
|
|
cases.add(rdcspv::OpBranch(mergeLabel));
|
|
}
|
|
|
|
cases.add(rdcspv::OpLabel(mergeLabel));
|
|
rdcspv::Id scalarSampleResult = cases.add(rdcspv::OpPhi(
|
|
scalarResultType, editor.MakeId(), {{lodResult, lodCase}, {gradResult, gradCase}}));
|
|
rdcspv::Id sampleResult = cases.add(rdcspv::OpCompositeConstruct(
|
|
resultType, editor.MakeId(), {scalarSampleResult, zerof, zerof, zerof}));
|
|
cases.add(rdcspv::OpStore(outVar, sampleResult));
|
|
cases.add(rdcspv::OpBranch(breakLabel));
|
|
}
|
|
}
|
|
|
|
// can only gather with 2D/Cube textures
|
|
if(i == (uint32_t)ShaderDebugBind::Tex1D || i == (uint32_t)ShaderDebugBind::Tex3D)
|
|
continue;
|
|
|
|
for(rdcspv::Op op : {rdcspv::Op::ImageGather, rdcspv::Op::ImageDrefGather})
|
|
{
|
|
if(op == rdcspv::Op::ImageDrefGather && !depthTex)
|
|
continue;
|
|
|
|
rdcspv::Id label = editor.MakeId();
|
|
targets.push_back({(uint32_t)op * 10 + i, label});
|
|
|
|
cases.add(rdcspv::OpLabel(label));
|
|
rdcspv::Id loadedImage =
|
|
cases.add(rdcspv::OpLoad(texSampTypes[i], editor.MakeId(), bindVars[i]));
|
|
rdcspv::Id loadedSampler =
|
|
cases.add(rdcspv::OpLoad(texSampTypes[sampIdx], editor.MakeId(), bindVars[sampIdx]));
|
|
|
|
rdcspv::Id sampleResult;
|
|
if(m_pDriver->GetDeviceEnabledFeatures().shaderImageGatherExtended)
|
|
{
|
|
rdcspv::Id mergeLabel = editor.MakeId();
|
|
rdcspv::Id constsCase = editor.MakeId();
|
|
rdcspv::Id baseCase = editor.MakeId();
|
|
cases.add(rdcspv::OpSelectionMerge(mergeLabel, rdcspv::SelectionControl::None));
|
|
cases.add(rdcspv::OpBranchConditional(useGradOrGatherOffsets, constsCase, baseCase));
|
|
|
|
rdcspv::Id baseResult;
|
|
{
|
|
cases.add(rdcspv::OpLabel(baseCase));
|
|
|
|
rdcspv::ImageOperandsAndParamDatas operands;
|
|
|
|
if(dynoffset[i] != rdcspv::Id())
|
|
operands.setOffset(dynoffset[i]);
|
|
|
|
rdcspv::Id combined = cases.add(rdcspv::OpSampledImage(
|
|
texSampCombinedTypes[i], editor.MakeId(), loadedImage, loadedSampler));
|
|
|
|
if(op == rdcspv::Op::ImageGather)
|
|
baseResult = cases.add(rdcspv::OpImageGather(resultType, editor.MakeId(), combined,
|
|
coord[i], gatherChannel, operands));
|
|
else
|
|
baseResult = cases.add(rdcspv::OpImageDrefGather(
|
|
resultType, editor.MakeId(), combined, coord[i], compare, operands));
|
|
|
|
cases.add(rdcspv::OpBranch(mergeLabel));
|
|
}
|
|
|
|
rdcspv::Id constsResult;
|
|
{
|
|
cases.add(rdcspv::OpLabel(constsCase));
|
|
rdcspv::ImageOperandsAndParamDatas operands; // don't use the offsets above
|
|
|
|
// if this feature isn't available, this path will never be exercised (since we only
|
|
// come in here when the actual shader used const offsets) so it's fine to drop it in
|
|
// that case to ensure the module is still legal.
|
|
if(m_pDriver->GetDeviceEnabledFeatures().shaderImageGatherExtended &&
|
|
i != (uint32_t)ShaderDebugBind::TexCube)
|
|
operands.setConstOffsets(gatherOffsets);
|
|
|
|
rdcspv::Id combined = cases.add(rdcspv::OpSampledImage(
|
|
texSampCombinedTypes[i], editor.MakeId(), loadedImage, loadedSampler));
|
|
|
|
if(op == rdcspv::Op::ImageGather)
|
|
constsResult = cases.add(rdcspv::OpImageGather(resultType, editor.MakeId(), combined,
|
|
coord[i], gatherChannel, operands));
|
|
else
|
|
constsResult = cases.add(rdcspv::OpImageDrefGather(
|
|
resultType, editor.MakeId(), combined, coord[i], compare, operands));
|
|
|
|
cases.add(rdcspv::OpBranch(mergeLabel));
|
|
}
|
|
|
|
cases.add(rdcspv::OpLabel(mergeLabel));
|
|
sampleResult = cases.add(rdcspv::OpPhi(
|
|
resultType, editor.MakeId(), {{baseResult, baseCase}, {constsResult, constsCase}}));
|
|
}
|
|
else
|
|
{
|
|
rdcspv::ImageOperandsAndParamDatas operands = imageOperandsWithOffsets;
|
|
|
|
rdcspv::Id combined = cases.add(rdcspv::OpSampledImage(
|
|
texSampCombinedTypes[i], editor.MakeId(), loadedImage, loadedSampler));
|
|
|
|
if(op == rdcspv::Op::ImageGather)
|
|
sampleResult = cases.add(rdcspv::OpImageGather(resultType, editor.MakeId(), combined,
|
|
coord[i], gatherChannel, operands));
|
|
else
|
|
sampleResult = cases.add(rdcspv::OpImageDrefGather(
|
|
resultType, editor.MakeId(), combined, coord[i], compare, operands));
|
|
}
|
|
|
|
cases.add(rdcspv::OpStore(outVar, sampleResult));
|
|
cases.add(rdcspv::OpBranch(breakLabel));
|
|
}
|
|
}
|
|
|
|
func.add(rdcspv::OpSelectionMerge(breakLabel, rdcspv::SelectionControl::None));
|
|
func.add(rdcspv::OpSwitch32(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);
|
|
}
|
|
|
|
const uint64_t deviceThreadID;
|
|
};
|
|
|
|
enum class InputSpecConstant
|
|
{
|
|
Address = 0,
|
|
AddressMSB,
|
|
ArrayLength,
|
|
DestX,
|
|
DestY,
|
|
DestThreadIDX,
|
|
DestThreadIDY,
|
|
DestThreadIDZ,
|
|
DestInstance,
|
|
DestVertex,
|
|
DestView,
|
|
Count,
|
|
};
|
|
|
|
struct SpecData
|
|
{
|
|
VkDeviceAddress bufferAddress;
|
|
uint32_t arrayLength;
|
|
uint32_t destVertex;
|
|
uint32_t destInstance;
|
|
uint32_t destView;
|
|
float destX;
|
|
float destY;
|
|
uint32_t globalThreadIdX;
|
|
uint32_t globalThreadIdY;
|
|
uint32_t globalThreadIdZ;
|
|
};
|
|
|
|
static const VkSpecializationMapEntry specMapsTemplate[] = {
|
|
{
|
|
(uint32_t)InputSpecConstant::Address,
|
|
offsetof(SpecData, bufferAddress),
|
|
// EXT_bda uses a 64-bit constant, as well as KHR_bda64
|
|
0,
|
|
},
|
|
{
|
|
(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),
|
|
},
|
|
{
|
|
(uint32_t)InputSpecConstant::DestThreadIDX,
|
|
offsetof(SpecData, globalThreadIdX),
|
|
sizeof(SpecData::globalThreadIdX),
|
|
},
|
|
{
|
|
(uint32_t)InputSpecConstant::DestThreadIDY,
|
|
offsetof(SpecData, globalThreadIdY),
|
|
sizeof(SpecData::globalThreadIdY),
|
|
},
|
|
{
|
|
(uint32_t)InputSpecConstant::DestThreadIDZ,
|
|
offsetof(SpecData, globalThreadIdZ),
|
|
sizeof(SpecData::globalThreadIdZ),
|
|
},
|
|
{
|
|
(uint32_t)InputSpecConstant::DestInstance,
|
|
offsetof(SpecData, destInstance),
|
|
sizeof(SpecData::destInstance),
|
|
},
|
|
{
|
|
(uint32_t)InputSpecConstant::DestVertex,
|
|
offsetof(SpecData, destVertex),
|
|
sizeof(SpecData::destVertex),
|
|
},
|
|
{
|
|
(uint32_t)InputSpecConstant::DestView,
|
|
offsetof(SpecData, destView),
|
|
sizeof(SpecData::destView),
|
|
},
|
|
{
|
|
(uint32_t)InputSpecConstant::AddressMSB,
|
|
offsetof(SpecData, bufferAddress) + 4,
|
|
sizeof(uint32_t),
|
|
},
|
|
};
|
|
|
|
RDCCOMPILE_ASSERT((size_t)InputSpecConstant::Count == ARRAY_COUNT(specMapsTemplate),
|
|
"Spec constants changed");
|
|
|
|
enum class SubgroupCapability : uint32_t
|
|
{
|
|
None = 0,
|
|
EXTBallot,
|
|
Vulkan1_1_NoBallot,
|
|
Vulkan1_1,
|
|
};
|
|
|
|
static const uint32_t validMagicNumber = 12345;
|
|
static const uint32_t NumReservedBindings = 1;
|
|
|
|
// things we need to readback once per hit thread
|
|
struct ResultDataBase
|
|
{
|
|
Vec4f pos;
|
|
|
|
uint32_t prim;
|
|
uint32_t sample;
|
|
uint32_t view;
|
|
uint32_t valid;
|
|
|
|
float ddxDerivCheck;
|
|
uint32_t quadLaneIndex;
|
|
uint32_t laneIndex;
|
|
uint32_t subgroupSize;
|
|
|
|
uint32_t globalBallot[4];
|
|
uint32_t electBallot[4];
|
|
uint32_t helperBallot[4];
|
|
|
|
uint32_t numSubgroups; // may be packed oddly so we don't assume we can calculate
|
|
uint32_t padding[3];
|
|
|
|
// LaneData lanes[N]
|
|
// each LaneData is prefixed by the subgroup struct below if needed, and then the stage struct unconditionally
|
|
};
|
|
|
|
// things we need per-lane with subgroups active, before any per-stage data
|
|
struct SubgroupLaneData
|
|
{
|
|
uint32_t elect; // for OpGroupNonUniformElect, if we don't have ballot
|
|
uint32_t isActive; // per lane active mask
|
|
uint32_t padding[2];
|
|
};
|
|
|
|
struct VertexLaneData
|
|
{
|
|
uint32_t inst; // allow/expect instance to vary across subgroup just in case
|
|
uint32_t vert; // vertex id (either auto-generated or index)
|
|
uint32_t view; // multiview view (if used)
|
|
uint32_t padding;
|
|
};
|
|
|
|
struct PixelLaneData
|
|
{
|
|
Vec4f fragCoord; // per-lane coord
|
|
uint32_t isHelper; // per-lane helper bit
|
|
uint32_t quadId; // the per-quad ID shared among all 4 threads, to differentiate between quads.
|
|
// is the laneIndex of the top-left thread (with an offset, so we can see 0 as invalid)
|
|
uint32_t quadLaneIndex; // the quadLaneIndex for quad-neighbours, in case we are fetching a subgroup
|
|
uint32_t padding;
|
|
};
|
|
|
|
struct ComputeLaneData
|
|
{
|
|
uint32_t threadid[3]; // per-lane thread id (in case it's not trivial)
|
|
uint32_t subIdxInGroup;
|
|
};
|
|
|
|
// we use the message passing method from the quadoverdraw to swap data between quad neighbours
|
|
// using fine derivatives. This is based on "Shader Amortization using Pixel Quad Message Passing",
|
|
// Eric Penner, GPU Pro 2.
|
|
//
|
|
// broadly, if we take ddx_fine and either add or subtract it we can swap horizontal information,
|
|
// and similarly vertical for ddy_fine. To swap across the diagonal we perform one fine derivative
|
|
// swap, and then use the other fine derivative on the result of that swap.
|
|
//
|
|
// +---+---+
|
|
// | 0 | 1 |
|
|
// +---+---+
|
|
// | 2 | 3 |
|
|
// +---+---+
|
|
//
|
|
// fine derivatives are obtained by subtracting the right-most neighbour from left-most in each row,
|
|
// and the bottom-most neighbour from top-most in each column.
|
|
//
|
|
// the pseudocode is as follows (following the quad overdraw closely) where X is the type of our value:
|
|
//
|
|
//
|
|
// bool quadX = (quadLaneIndex & 1) != 0;
|
|
// bool quadY = (quadLaneIndex & 2) != 0;
|
|
//
|
|
// bool readX = (readIndex & 1) != 0;
|
|
// bool readY = (readIndex & 2) != 0;
|
|
//
|
|
// float sign_x = quadX ? -1 : 1;
|
|
// float sign_y = quadY ? -1 : 1;
|
|
//
|
|
// X c1 = c0 + sign_x * ddx_fine(c0);
|
|
// X c2 = c0 + sign_y * ddy_fine(c0);
|
|
// X c3 = c2 + sign_x * ddx_fine(c2);
|
|
//
|
|
// if(readIndex == quadLaneIndex) // identity, handle gracefully
|
|
// return c0;
|
|
// else if(readY == quadY) // horizontal neighbour
|
|
// return c1;
|
|
// else if(readX == quadX) // vertical neighbour
|
|
// return c2;
|
|
// else
|
|
// return c3; // diagonal neighbour
|
|
//
|
|
|
|
static rdcspv::Id AddQuadSwizzleHelper(rdcspv::Editor &editor, uint32_t count)
|
|
{
|
|
rdcspv::Id func = editor.MakeId();
|
|
|
|
rdcspv::OperationList ops;
|
|
|
|
rdcspv::Id u32 = editor.DeclareType(rdcspv::scalar<uint32_t>());
|
|
|
|
rdcspv::Id type;
|
|
if(count == 1)
|
|
type = editor.DeclareType(rdcspv::scalar<float>());
|
|
else
|
|
type = editor.DeclareType(rdcspv::Vector(rdcspv::scalar<float>(), count));
|
|
|
|
rdcspv::Id funcType = editor.DeclareType(rdcspv::FunctionType(type, {type, u32, u32}));
|
|
|
|
ops.add(rdcspv::OpFunction(type, func, rdcspv::FunctionControl::None, funcType));
|
|
rdcspv::Id c0 = ops.add(rdcspv::OpFunctionParameter(type, editor.MakeId()));
|
|
rdcspv::Id quadLaneIndex = ops.add(rdcspv::OpFunctionParameter(u32, editor.MakeId()));
|
|
rdcspv::Id readIndex = ops.add(rdcspv::OpFunctionParameter(u32, editor.MakeId()));
|
|
ops.add(rdcspv::OpLabel(editor.MakeId()));
|
|
|
|
editor.SetName(c0, "c0");
|
|
editor.SetName(quadLaneIndex, "quadLaneIndex");
|
|
editor.SetName(readIndex, "readIndex");
|
|
|
|
rdcspv::Id zero = editor.AddConstantImmediate<uint32_t>(0U);
|
|
rdcspv::Id one = editor.AddConstantImmediate<uint32_t>(1U);
|
|
rdcspv::Id two = editor.AddConstantImmediate<uint32_t>(2U);
|
|
|
|
rdcspv::Id posOne = editor.AddConstantImmediate<float>(1.0f);
|
|
rdcspv::Id negOne = editor.AddConstantImmediate<float>(-1.0f);
|
|
|
|
rdcspv::Id boolType = editor.DeclareType(rdcspv::scalar<bool>());
|
|
|
|
rdcspv::Id quadX = ops.add(rdcspv::OpBitwiseAnd(u32, editor.MakeId(), quadLaneIndex, one));
|
|
quadX = ops.add(rdcspv::OpINotEqual(boolType, editor.MakeId(), quadX, zero));
|
|
rdcspv::Id quadY = ops.add(rdcspv::OpBitwiseAnd(u32, editor.MakeId(), quadLaneIndex, two));
|
|
quadY = ops.add(rdcspv::OpINotEqual(boolType, editor.MakeId(), quadY, zero));
|
|
|
|
rdcspv::Id readX = ops.add(rdcspv::OpBitwiseAnd(u32, editor.MakeId(), readIndex, one));
|
|
readX = ops.add(rdcspv::OpINotEqual(boolType, editor.MakeId(), readX, zero));
|
|
rdcspv::Id readY = ops.add(rdcspv::OpBitwiseAnd(u32, editor.MakeId(), readIndex, two));
|
|
readY = ops.add(rdcspv::OpINotEqual(boolType, editor.MakeId(), readY, zero));
|
|
|
|
rdcspv::Id horizNeighbour =
|
|
ops.add(rdcspv::OpLogicalEqual(boolType, editor.MakeId(), readY, quadY));
|
|
rdcspv::Id vertNeighbour = ops.add(rdcspv::OpLogicalEqual(boolType, editor.MakeId(), readX, quadX));
|
|
rdcspv::Id isIdentity =
|
|
ops.add(rdcspv::OpIEqual(boolType, editor.MakeId(), quadLaneIndex, readIndex));
|
|
|
|
editor.SetName(quadX, "quadX");
|
|
editor.SetName(quadY, "quadY");
|
|
editor.SetName(readX, "readX");
|
|
editor.SetName(readY, "readY");
|
|
|
|
if(count >= 2)
|
|
{
|
|
rdcspv::Id floatNtype = editor.DeclareType(rdcspv::Vector(rdcspv::scalar<float>(), count));
|
|
rdcspv::Id boolNtype = editor.DeclareType(rdcspv::Vector(rdcspv::scalar<bool>(), count));
|
|
|
|
rdcarray<rdcspv::Id> bcast;
|
|
|
|
bcast.fill(count, posOne);
|
|
posOne = ops.add(rdcspv::OpCompositeConstruct(floatNtype, editor.MakeId(), bcast));
|
|
bcast.fill(count, negOne);
|
|
negOne = ops.add(rdcspv::OpCompositeConstruct(floatNtype, editor.MakeId(), bcast));
|
|
|
|
bcast.fill(count, quadX);
|
|
quadX = ops.add(rdcspv::OpCompositeConstruct(boolNtype, editor.MakeId(), bcast));
|
|
bcast.fill(count, quadY);
|
|
quadY = ops.add(rdcspv::OpCompositeConstruct(boolNtype, editor.MakeId(), bcast));
|
|
}
|
|
|
|
rdcspv::Id sign_x = ops.add(rdcspv::OpSelect(type, editor.MakeId(), quadX, negOne, posOne));
|
|
rdcspv::Id sign_y = ops.add(rdcspv::OpSelect(type, editor.MakeId(), quadY, negOne, posOne));
|
|
editor.SetName(sign_x, "sign_x");
|
|
editor.SetName(sign_y, "sign_y");
|
|
|
|
rdcspv::Id ddxFine = ops.add(rdcspv::OpDPdxFine(type, editor.MakeId(), c0));
|
|
rdcspv::Id ddyFine = ops.add(rdcspv::OpDPdyFine(type, editor.MakeId(), c0));
|
|
|
|
rdcspv::Id c1 = ops.add(rdcspv::OpFMul(type, editor.MakeId(), sign_x, ddxFine));
|
|
c1 = ops.add(rdcspv::OpFAdd(type, editor.MakeId(), c0, c1));
|
|
editor.SetName(c1, "c1");
|
|
|
|
rdcspv::Id c2 = ops.add(rdcspv::OpFMul(type, editor.MakeId(), sign_y, ddyFine));
|
|
c2 = ops.add(rdcspv::OpFAdd(type, editor.MakeId(), c0, c2));
|
|
editor.SetName(c2, "c2");
|
|
|
|
rdcspv::Id ddxC2 = ops.add(rdcspv::OpDPdxFine(type, editor.MakeId(), c2));
|
|
|
|
rdcspv::Id c3 = ops.add(rdcspv::OpFMul(type, editor.MakeId(), sign_x, ddxC2));
|
|
c3 = ops.add(rdcspv::OpFAdd(type, editor.MakeId(), c2, c3));
|
|
editor.SetName(c3, "c3");
|
|
|
|
rdcspv::Id trueBranch = editor.MakeId(), falseBranch = editor.MakeId(),
|
|
mergeBranch = editor.MakeId();
|
|
|
|
// we'll want to read lane 0 on all lanes for the quadId, so handle that specially
|
|
ops.add(rdcspv::OpSelectionMerge(mergeBranch, rdcspv::SelectionControl::None));
|
|
ops.add(rdcspv::OpBranchConditional(isIdentity, trueBranch, falseBranch));
|
|
|
|
ops.add(rdcspv::OpLabel(trueBranch));
|
|
ops.add(rdcspv::OpReturnValue(c0));
|
|
ops.add(rdcspv::OpLabel(falseBranch));
|
|
ops.add(rdcspv::OpBranch(mergeBranch));
|
|
ops.add(rdcspv::OpLabel(mergeBranch));
|
|
|
|
// expanded flow control. Impossible labels happen after returns on both branches
|
|
//
|
|
// if(isIdentity) {
|
|
// ident;
|
|
// }
|
|
// notident;
|
|
//
|
|
// if(horizNeighbour) {
|
|
// horiz;
|
|
// } else {
|
|
// notHoriz;
|
|
// if(vertNeighbour) { vert; } else { diagonal; }
|
|
// impossible1;
|
|
// }
|
|
// impossible2;
|
|
rdcspv::Id horiz = editor.MakeId(), notHoriz = editor.MakeId(), vert = editor.MakeId(),
|
|
diagonal = editor.MakeId(), imposs1 = editor.MakeId(), imposs2 = editor.MakeId();
|
|
|
|
ops.add(rdcspv::OpSelectionMerge(imposs2, rdcspv::SelectionControl::None));
|
|
ops.add(rdcspv::OpBranchConditional(horizNeighbour, horiz, notHoriz));
|
|
|
|
ops.add(rdcspv::OpLabel(horiz));
|
|
ops.add(rdcspv::OpReturnValue(c1));
|
|
ops.add(rdcspv::OpLabel(notHoriz));
|
|
|
|
ops.add(rdcspv::OpSelectionMerge(imposs1, rdcspv::SelectionControl::None));
|
|
ops.add(rdcspv::OpBranchConditional(vertNeighbour, vert, diagonal));
|
|
|
|
ops.add(rdcspv::OpLabel(vert));
|
|
ops.add(rdcspv::OpReturnValue(c2));
|
|
ops.add(rdcspv::OpLabel(diagonal));
|
|
ops.add(rdcspv::OpReturnValue(c3));
|
|
|
|
ops.add(rdcspv::OpLabel(imposs1));
|
|
ops.add(rdcspv::OpUnreachable());
|
|
ops.add(rdcspv::OpLabel(imposs2));
|
|
ops.add(rdcspv::OpUnreachable());
|
|
|
|
ops.add(rdcspv::OpFunctionEnd());
|
|
|
|
editor.AddFunction(ops);
|
|
editor.SetName(func, "quadSwizzleHelper");
|
|
|
|
return func;
|
|
}
|
|
|
|
static void CreateInputFetcher(rdcarray<uint32_t> &spv,
|
|
VulkanCreationInfo::ShaderModuleReflection &shadRefl,
|
|
BufferStorageMode storageMode, bool usePrimitiveID, bool useSampleID,
|
|
bool useViewIndex, SubgroupCapability subgroupCapability,
|
|
uint32_t maxSubgroupSize)
|
|
{
|
|
rdcspv::Editor editor(spv);
|
|
|
|
ShaderStage stage = ShaderStage(shadRefl.stageIndex);
|
|
rdcspv::ThreadScope threadScope = shadRefl.patchData.threadScope;
|
|
|
|
uint32_t paramAlign = 16;
|
|
|
|
for(const SigParameter &sig : shadRefl.refl->inputSignature)
|
|
{
|
|
if(VarTypeByteSize(sig.varType) * sig.compCount > paramAlign)
|
|
paramAlign = 32;
|
|
}
|
|
|
|
// conservatively calculate structure stride with full amount for every input element
|
|
uint32_t structStride = (uint32_t)shadRefl.refl->inputSignature.size() * paramAlign;
|
|
|
|
switch(stage)
|
|
{
|
|
case ShaderStage::Vertex: structStride += sizeof(VertexLaneData); break;
|
|
case ShaderStage::Pixel: structStride += sizeof(PixelLaneData); break;
|
|
case ShaderStage::Task:
|
|
case ShaderStage::Mesh:
|
|
case ShaderStage::Compute: structStride += sizeof(ComputeLaneData); break;
|
|
default: break;
|
|
}
|
|
|
|
if(threadScope & rdcspv::ThreadScope::Subgroup)
|
|
{
|
|
structStride += sizeof(SubgroupLaneData);
|
|
}
|
|
|
|
// simulating full subgroups with ballot ability to read other lanes, we read all lanes data
|
|
const bool fullSubgroups = (subgroupCapability == SubgroupCapability::EXTBallot ||
|
|
subgroupCapability == SubgroupCapability::Vulkan1_1) &&
|
|
(threadScope & rdcspv::ThreadScope::Subgroup);
|
|
// faking subgroups without reading a subgroup's worth of data but we still read lane index and elect value
|
|
const bool minimalSubgroups = subgroupCapability != SubgroupCapability::None &&
|
|
(threadScope & rdcspv::ThreadScope::Subgroup);
|
|
|
|
editor.Prepare();
|
|
editor.SetBufferStorageMode(storageMode);
|
|
|
|
// remove any OpSource
|
|
{
|
|
// remove any OpName that refers to deleted IDs - functions or results
|
|
rdcspv::Iter it = editor.Begin(rdcspv::Section::DebugStringSource);
|
|
rdcspv::Iter end = editor.End(rdcspv::Section::DebugStringSource);
|
|
while(it < end)
|
|
{
|
|
if(it.opcode() == rdcspv::Op::Source || it.opcode() == rdcspv::Op::SourceContinued)
|
|
{
|
|
editor.Remove(it);
|
|
}
|
|
it++;
|
|
}
|
|
}
|
|
|
|
editor.OffsetBindingsToMatchReservation(NumReservedBindings);
|
|
|
|
// the original entry ID we're patching
|
|
rdcspv::Id originalEntry = editor.FindEntryID({shadRefl.entryPoint, stage});
|
|
// the new wrapped entry function we'll add
|
|
rdcspv::Id entryID = editor.MakeId();
|
|
|
|
// repoint the entry declaration
|
|
editor.ChangeEntry(originalEntry, entryID);
|
|
|
|
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>());
|
|
rdcspv::Id uint3Type = editor.DeclareType(rdcspv::Vector(rdcspv::scalar<uint32_t>(), 3));
|
|
rdcspv::Id uint4Type = editor.DeclareType(rdcspv::Vector(rdcspv::scalar<uint32_t>(), 4));
|
|
rdcspv::Id float4Type = editor.DeclareType(rdcspv::Vector(rdcspv::scalar<float>(), 4));
|
|
rdcspv::Id float3Type = editor.DeclareType(rdcspv::Vector(rdcspv::scalar<float>(), 3));
|
|
rdcspv::Id float2Type = editor.DeclareType(rdcspv::Vector(rdcspv::scalar<float>(), 2));
|
|
rdcspv::Id bool4Type = editor.DeclareType(rdcspv::Vector(rdcspv::scalar<bool>(), 4));
|
|
|
|
rdcarray<rdcspv::Id> newGlobals;
|
|
|
|
rdcspv::Id LaneDataStruct;
|
|
|
|
enum ResultBaseMember
|
|
{
|
|
ResultBase_pos,
|
|
ResultBase_prim,
|
|
ResultBase_sample,
|
|
ResultBase_view,
|
|
ResultBase_valid,
|
|
ResultBase_ddxDerivCheck,
|
|
ResultBase_quadLaneIndex,
|
|
ResultBase_laneIndex,
|
|
ResultBase_subgroupSize,
|
|
ResultBase_globalBallot,
|
|
ResultBase_electBallot,
|
|
ResultBase_helperBallot,
|
|
ResultBase_numSubgroups,
|
|
ResultBase_firstUser,
|
|
};
|
|
|
|
struct laneValue
|
|
{
|
|
rdcstr name;
|
|
// index in the LaneData struct
|
|
size_t structIndex;
|
|
// type ID of the value (float4, uint, etc)
|
|
rdcspv::Id type;
|
|
// direct loaded value per-lane
|
|
rdcspv::Id base;
|
|
|
|
// for pixel shaders, the quad's worth of swizzled data to load helper info from, whether or not
|
|
// we have subgroups active
|
|
rdcarray<rdcspv::Id> quadSwizzledData;
|
|
|
|
// the loadOps to prepare the base value, done separately so we can push each fixed value into
|
|
// an array while creating the struct to all be processed agnostically, instead of separating adding
|
|
// them to the struct from loading them OR interleaving struct definition while preparing our function
|
|
rdcspv::OperationList loadOps;
|
|
|
|
bool flat = false;
|
|
};
|
|
|
|
rdcarray<laneValue> laneValues;
|
|
|
|
rdcspv::Id subgroupScope = editor.AddConstantImmediate<uint32_t>((uint32_t)rdcspv::Scope::Subgroup);
|
|
|
|
rdcspv::Id isHelper, quadLaneIndex, quadId;
|
|
|
|
{
|
|
rdcarray<rdcspv::StructMember> structMembers;
|
|
uint32_t offset = 0;
|
|
|
|
// declare fixed lane data first
|
|
|
|
if(threadScope & rdcspv::ThreadScope::Subgroup)
|
|
{
|
|
laneValue elect;
|
|
elect.name = "__rd_globalElect";
|
|
elect.structIndex = structMembers.size();
|
|
elect.type = uint32Type;
|
|
// don't store elect value for legacy KHR path
|
|
if(subgroupCapability == SubgroupCapability::Vulkan1_1 ||
|
|
subgroupCapability == SubgroupCapability::Vulkan1_1_NoBallot)
|
|
{
|
|
elect.base = elect.loadOps.add(
|
|
rdcspv::OpGroupNonUniformElect(boolType, editor.MakeId(), subgroupScope));
|
|
elect.base = elect.loadOps.add(rdcspv::OpSelect(uint32Type, editor.MakeId(), elect.base,
|
|
editor.AddConstantImmediate<uint32_t>(1),
|
|
editor.AddConstantImmediate<uint32_t>(0)));
|
|
editor.SetName(elect.base, elect.name);
|
|
}
|
|
else
|
|
{
|
|
elect.base = editor.AddConstantImmediate<uint32_t>(0);
|
|
elect.flat = true;
|
|
}
|
|
laneValues.push_back(elect);
|
|
structMembers.push_back(
|
|
{uint32Type, elect.name, offset + (uint32_t)offsetof(SubgroupLaneData, elect)});
|
|
|
|
// we implicitly only write data for active lanes so we just set isActive to 1 always
|
|
laneValue isActive;
|
|
isActive.name = "__rd_active";
|
|
isActive.structIndex = structMembers.size();
|
|
isActive.type = uint32Type;
|
|
isActive.base = editor.AddConstantImmediate<uint32_t>(1);
|
|
isActive.flat = true;
|
|
laneValues.push_back(isActive);
|
|
structMembers.push_back(
|
|
{uint32Type, isActive.name, offset + (uint32_t)offsetof(SubgroupLaneData, isActive)});
|
|
|
|
structMembers.push_back(
|
|
{uint32Type, "__pad", offset + (uint32_t)offsetof(SubgroupLaneData, padding)});
|
|
structMembers.push_back(
|
|
{uint32Type, "__pad",
|
|
uint32_t(offset + offsetof(SubgroupLaneData, padding) + sizeof(uint32_t))});
|
|
|
|
offset += sizeof(SubgroupLaneData);
|
|
RDCCOMPILE_ASSERT(
|
|
(sizeof(SubgroupLaneData) / sizeof(Vec4f)) * sizeof(Vec4f) == sizeof(SubgroupLaneData),
|
|
"SubgroupLaneData is misaligned, ensure 16-byte aligned");
|
|
}
|
|
|
|
if(stage == ShaderStage::Vertex)
|
|
{
|
|
laneValue inst;
|
|
inst.name = "__rd_inst";
|
|
inst.structIndex = structMembers.size();
|
|
inst.type = uint32Type;
|
|
inst.base = editor.AddBuiltinInputLoad(inst.loadOps, newGlobals, stage,
|
|
rdcspv::BuiltIn::InstanceIndex, uint32Type);
|
|
editor.SetName(inst.base, inst.name);
|
|
laneValues.push_back(inst);
|
|
structMembers.push_back(
|
|
{uint32Type, inst.name, offset + (uint32_t)offsetof(VertexLaneData, inst)});
|
|
|
|
laneValue vert;
|
|
vert.name = "__rd_vert";
|
|
vert.structIndex = structMembers.size();
|
|
vert.type = uint32Type;
|
|
vert.base = editor.AddBuiltinInputLoad(vert.loadOps, newGlobals, stage,
|
|
rdcspv::BuiltIn::VertexIndex, uint32Type);
|
|
editor.SetName(vert.base, vert.name);
|
|
laneValues.push_back(vert);
|
|
structMembers.push_back(
|
|
{uint32Type, vert.name, offset + (uint32_t)offsetof(VertexLaneData, vert)});
|
|
|
|
if(useViewIndex)
|
|
{
|
|
laneValue view;
|
|
view.name = "__rd_view";
|
|
view.structIndex = structMembers.size();
|
|
view.type = uint32Type;
|
|
view.base = editor.AddBuiltinInputLoad(view.loadOps, newGlobals, stage,
|
|
rdcspv::BuiltIn::ViewIndex, uint32Type);
|
|
editor.SetName(view.base, view.name);
|
|
laneValues.push_back(view);
|
|
structMembers.push_back(
|
|
{uint32Type, view.name, offset + (uint32_t)offsetof(VertexLaneData, view)});
|
|
|
|
structMembers.push_back(
|
|
{uint32Type, "__pad", offset + (uint32_t)offsetof(VertexLaneData, padding)});
|
|
}
|
|
else
|
|
{
|
|
structMembers.push_back(
|
|
{uint32Type, "__rd_view", offset + (uint32_t)offsetof(VertexLaneData, view)});
|
|
structMembers.push_back(
|
|
{uint32Type, "__pad", offset + (uint32_t)offsetof(VertexLaneData, padding)});
|
|
}
|
|
|
|
offset += sizeof(VertexLaneData);
|
|
RDCCOMPILE_ASSERT(
|
|
(sizeof(VertexLaneData) / sizeof(Vec4f)) * sizeof(Vec4f) == sizeof(VertexLaneData),
|
|
"VertexLaneData is misaligned, ensure 16-byte aligned");
|
|
}
|
|
else if(stage == ShaderStage::Pixel)
|
|
{
|
|
laneValue fragCoord;
|
|
fragCoord.name = "__rd_pixelPos";
|
|
fragCoord.structIndex = structMembers.size();
|
|
fragCoord.type = float4Type;
|
|
fragCoord.base = editor.AddBuiltinInputLoad(fragCoord.loadOps, newGlobals, stage,
|
|
rdcspv::BuiltIn::FragCoord, float4Type);
|
|
editor.SetName(fragCoord.base, fragCoord.name);
|
|
laneValues.push_back(fragCoord);
|
|
structMembers.push_back(
|
|
{float4Type, fragCoord.name, offset + (uint32_t)offsetof(PixelLaneData, fragCoord)});
|
|
|
|
laneValue helper;
|
|
helper.name = "__rd_isHelper";
|
|
helper.structIndex = structMembers.size();
|
|
helper.type = uint32Type;
|
|
helper.base = editor.AddBuiltinInputLoad(helper.loadOps, newGlobals, stage,
|
|
rdcspv::BuiltIn::HelperInvocation, boolType);
|
|
helper.base = helper.loadOps.add(rdcspv::OpSelect(uint32Type, editor.MakeId(), helper.base,
|
|
editor.AddConstantImmediate<uint32_t>(1),
|
|
editor.AddConstantImmediate<uint32_t>(0)));
|
|
editor.SetName(helper.base, helper.name);
|
|
laneValues.push_back(helper);
|
|
structMembers.push_back(
|
|
{uint32Type, helper.name, offset + (uint32_t)offsetof(PixelLaneData, isHelper)});
|
|
|
|
laneValue quad;
|
|
quad.name = "__rd_quadId";
|
|
quad.structIndex = structMembers.size();
|
|
quad.type = uint32Type;
|
|
quad.flat = true;
|
|
// this will be handled specially similarly to helper
|
|
quad.base = editor.MakeId();
|
|
editor.SetName(quad.base, quad.name);
|
|
laneValues.push_back(quad);
|
|
structMembers.push_back(
|
|
{uint32Type, quad.name, offset + (uint32_t)offsetof(PixelLaneData, quadId)});
|
|
|
|
laneValue quadLane;
|
|
quadLane.name = "__rd_quadLane";
|
|
quadLane.structIndex = structMembers.size();
|
|
quadLane.type = uint32Type;
|
|
quadLane.base = editor.MakeId();
|
|
editor.SetName(quadLane.base, quadLane.name);
|
|
laneValues.push_back(quadLane);
|
|
structMembers.push_back(
|
|
{uint32Type, quadLane.name, offset + (uint32_t)offsetof(PixelLaneData, quadLaneIndex)});
|
|
|
|
// quad properties will be handled specially
|
|
isHelper = helper.base;
|
|
quadId = quad.base;
|
|
quadLaneIndex = quadLane.base;
|
|
|
|
structMembers.push_back(
|
|
{uint32Type, "__pad", offset + (uint32_t)offsetof(PixelLaneData, padding)});
|
|
|
|
offset += sizeof(PixelLaneData);
|
|
RDCCOMPILE_ASSERT(
|
|
(sizeof(PixelLaneData) / sizeof(Vec4f)) * sizeof(Vec4f) == sizeof(PixelLaneData),
|
|
"PixelLaneData is misaligned, ensure 16-byte aligned");
|
|
}
|
|
else if(stage == ShaderStage::Compute || stage == ShaderStage::Task || stage == ShaderStage::Mesh)
|
|
{
|
|
laneValue threadid;
|
|
threadid.name = "__rd_threadid";
|
|
threadid.structIndex = structMembers.size();
|
|
threadid.type = uint3Type;
|
|
threadid.base = editor.AddBuiltinInputLoad(threadid.loadOps, newGlobals, stage,
|
|
rdcspv::BuiltIn::LocalInvocationId, uint3Type);
|
|
editor.SetName(threadid.base, threadid.name);
|
|
laneValues.push_back(threadid);
|
|
structMembers.push_back(
|
|
{uint3Type, threadid.name, offset + (uint32_t)offsetof(ComputeLaneData, threadid)});
|
|
|
|
laneValue subid;
|
|
subid.name = "__rd_subgroupid";
|
|
subid.structIndex = structMembers.size();
|
|
subid.type = uint32Type;
|
|
subid.base = editor.AddBuiltinInputLoad(subid.loadOps, newGlobals, stage,
|
|
rdcspv::BuiltIn::SubgroupId, uint32Type);
|
|
editor.SetName(subid.base, subid.name);
|
|
laneValues.push_back(subid);
|
|
structMembers.push_back(
|
|
{uint32Type, subid.name, offset + (uint32_t)offsetof(ComputeLaneData, subIdxInGroup)});
|
|
|
|
offset += sizeof(ComputeLaneData);
|
|
RDCCOMPILE_ASSERT(
|
|
(sizeof(ComputeLaneData) / sizeof(Vec4f)) * sizeof(Vec4f) == sizeof(ComputeLaneData),
|
|
"ComputeLaneData is misaligned, ensure 16-byte aligned");
|
|
}
|
|
|
|
// now add input signature values
|
|
|
|
for(size_t i = 0; i < shadRefl.refl->inputSignature.size(); i++)
|
|
{
|
|
const SPIRVInterfaceAccess &access = shadRefl.patchData.inputs[i];
|
|
const SigParameter ¶m = shadRefl.refl->inputSignature[i];
|
|
|
|
rdcspv::Scalar base = rdcspv::scalar(param.varType);
|
|
|
|
uint32_t width = (base.width / 8);
|
|
|
|
rdcspv::Id loadType;
|
|
|
|
if(param.compCount == 1)
|
|
loadType = editor.DeclareType(base);
|
|
else
|
|
loadType = editor.DeclareType(rdcspv::Vector(base, param.compCount));
|
|
|
|
rdcspv::Id valueType;
|
|
|
|
// treat bools as uints
|
|
if(base.type == rdcspv::Op::TypeBool)
|
|
width = 4;
|
|
|
|
// we immediately upconvert any sub-32-bit types
|
|
if(width < 4)
|
|
{
|
|
width = 4;
|
|
base.width = 32;
|
|
|
|
if(param.compCount == 1)
|
|
valueType = editor.DeclareType(base);
|
|
else
|
|
valueType = editor.DeclareType(rdcspv::Vector(base, param.compCount));
|
|
}
|
|
else
|
|
{
|
|
valueType = loadType;
|
|
}
|
|
|
|
rdcarray<rdcspv::Id> accessIndices;
|
|
for(uint32_t idx : access.accessChain)
|
|
accessIndices.push_back(editor.AddConstantImmediate<uint32_t>(idx));
|
|
|
|
rdcspv::Id inputPtrType =
|
|
editor.DeclareType(rdcspv::Pointer(loadType, rdcspv::StorageClass::Input));
|
|
|
|
laneValue value;
|
|
value.name = param.varName;
|
|
value.structIndex = structMembers.size();
|
|
value.type = valueType;
|
|
|
|
if(value.name.beginsWith("gl_"))
|
|
value.name = "__rd_" + value.name.substr(3);
|
|
|
|
// if we have no access chain it's a global pointer of the type we want, so just load
|
|
// straight out of it
|
|
rdcspv::Id ptr;
|
|
if(accessIndices.empty())
|
|
ptr = access.ID;
|
|
else
|
|
ptr = value.loadOps.add(
|
|
rdcspv::OpAccessChain(inputPtrType, editor.MakeId(), access.ID, accessIndices));
|
|
|
|
value.base = value.loadOps.add(rdcspv::OpLoad(loadType, editor.MakeId(), ptr));
|
|
if(valueType == boolType)
|
|
{
|
|
valueType = uint32Type;
|
|
// can't store bools directly, need to convert to uint
|
|
value.base = value.loadOps.add(rdcspv::OpSelect(valueType, editor.MakeId(), value.base,
|
|
editor.AddConstantImmediate<uint32_t>(1),
|
|
editor.AddConstantImmediate<uint32_t>(0)));
|
|
}
|
|
if(valueType != loadType)
|
|
{
|
|
if(VarTypeCompType(param.varType) == CompType::Float)
|
|
value.base = value.loadOps.add(rdcspv::OpFConvert(valueType, editor.MakeId(), value.base));
|
|
else if(VarTypeCompType(param.varType) == CompType::SInt)
|
|
value.base = value.loadOps.add(rdcspv::OpSConvert(valueType, editor.MakeId(), value.base));
|
|
else if(VarTypeCompType(param.varType) == CompType::UInt)
|
|
value.base = value.loadOps.add(rdcspv::OpUConvert(valueType, editor.MakeId(), value.base));
|
|
}
|
|
editor.SetName(value.base, StringFormat::Fmt("__rd_base_%zu_%s", i, param.varName.c_str()));
|
|
// non-float inputs are considered flat
|
|
value.flat = VarTypeCompType(param.varType) != CompType::Float;
|
|
|
|
// mark this as non-flat so we still derive it for helper lanes as it will vary
|
|
if(param.systemValue == ShaderBuiltin::IndexInSubgroup)
|
|
value.flat = false;
|
|
|
|
laneValues.push_back(value);
|
|
|
|
if(valueType == boolType)
|
|
structMembers.push_back({uint32Type, value.name, offset});
|
|
else
|
|
structMembers.push_back({valueType, value.name, offset});
|
|
offset += param.compCount * width;
|
|
|
|
// 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 = ((paramAlign - (offset % 16)) / 4) % 4;
|
|
for(uint32_t p = 0; p < paddingWords; p++)
|
|
{
|
|
structMembers.push_back({uint32Type, "__pad", offset});
|
|
offset += 4;
|
|
}
|
|
}
|
|
|
|
RDCASSERT(offset <= structStride);
|
|
|
|
LaneDataStruct = editor.DeclareStructType("__rd_LaneData", structMembers);
|
|
}
|
|
|
|
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 destThreadIDX =
|
|
editor.AddSpecConstantImmediate<uint32_t>(0, (uint32_t)InputSpecConstant::DestThreadIDX);
|
|
rdcspv::Id destThreadIDY =
|
|
editor.AddSpecConstantImmediate<uint32_t>(0, (uint32_t)InputSpecConstant::DestThreadIDY);
|
|
rdcspv::Id destThreadIDZ =
|
|
editor.AddSpecConstantImmediate<uint32_t>(0, (uint32_t)InputSpecConstant::DestThreadIDZ);
|
|
|
|
editor.SetName(destThreadIDX, "destThreadIDX");
|
|
editor.SetName(destThreadIDY, "destThreadIDY");
|
|
editor.SetName(destThreadIDZ, "destThreadIDZ");
|
|
|
|
rdcspv::Id destThreadID = editor.AddConstant(rdcspv::OpSpecConstantComposite(
|
|
uint3Type, editor.MakeId(), {destThreadIDX, destThreadIDY, destThreadIDZ}));
|
|
|
|
editor.SetName(destThreadID, "destThreadID");
|
|
|
|
rdcspv::Id destInstance =
|
|
editor.AddSpecConstantImmediate<uint32_t>(0, (uint32_t)InputSpecConstant::DestInstance);
|
|
rdcspv::Id destVertex =
|
|
editor.AddSpecConstantImmediate<uint32_t>(0, (uint32_t)InputSpecConstant::DestVertex);
|
|
rdcspv::Id destView =
|
|
editor.AddSpecConstantImmediate<uint32_t>(0, (uint32_t)InputSpecConstant::DestView);
|
|
|
|
editor.SetName(destInstance, "destInstance");
|
|
editor.SetName(destVertex, "destVertex");
|
|
|
|
rdcspv::Id ResultDataBaseType;
|
|
|
|
uint32_t numLanes = 1;
|
|
|
|
if(stage == ShaderStage::Pixel)
|
|
numLanes = 4;
|
|
|
|
if(threadScope & rdcspv::ThreadScope::Quad)
|
|
numLanes = 4;
|
|
|
|
// if we need full subgroup scope (and we have ballots to read the lanes) declare a subgroup's worth of data
|
|
if(fullSubgroups)
|
|
numLanes = RDCMAX(numLanes, maxSubgroupSize);
|
|
|
|
// note we don't need to care about workgroup access - that is only possible on compute and we fill
|
|
// in the rest of the workgroup without reading its inputs, since on compute the only subgroup data
|
|
// we need is size + layout and we assume we can figure out the layout with one subgroup's worth of data
|
|
|
|
{
|
|
rdcarray<rdcspv::StructMember> members;
|
|
|
|
members.push_back({float4Type, "pos", offsetof(ResultDataBase, pos)});
|
|
members.push_back({uint32Type, "prim", offsetof(ResultDataBase, prim)});
|
|
members.push_back({uint32Type, "sample", offsetof(ResultDataBase, sample)});
|
|
members.push_back({uint32Type, "view", offsetof(ResultDataBase, view)});
|
|
members.push_back({uint32Type, "valid", offsetof(ResultDataBase, valid)});
|
|
members.push_back({floatType, "ddxDerivCheck", offsetof(ResultDataBase, ddxDerivCheck)});
|
|
members.push_back({uint32Type, "quadLaneIndex", offsetof(ResultDataBase, quadLaneIndex)});
|
|
members.push_back({uint32Type, "laneIndex", offsetof(ResultDataBase, laneIndex)});
|
|
members.push_back({uint32Type, "subgroupSize", offsetof(ResultDataBase, subgroupSize)});
|
|
members.push_back({uint4Type, "globalBallot", offsetof(ResultDataBase, globalBallot)});
|
|
members.push_back({uint4Type, "electBallot", offsetof(ResultDataBase, electBallot)});
|
|
members.push_back({uint4Type, "helperBallot", offsetof(ResultDataBase, helperBallot)});
|
|
members.push_back({uint32Type, "numSubgroups", offsetof(ResultDataBase, numSubgroups)});
|
|
|
|
// uint3 padding
|
|
|
|
const uint32_t dataStart = (uint32_t)AlignUp(sizeof(ResultDataBase), sizeof(Vec4f));
|
|
|
|
RDCASSERT((structStride % sizeof(Vec4f)) == 0);
|
|
|
|
rdcspv::Id LaneDataArray = editor.AddType(rdcspv::OpTypeArray(
|
|
editor.MakeId(), LaneDataStruct, editor.AddConstantImmediate<uint32_t>(numLanes)));
|
|
editor.AddDecoration(rdcspv::OpDecorate(
|
|
LaneDataArray, rdcspv::DecorationParam<rdcspv::Decoration::ArrayStride>(structStride)));
|
|
|
|
members.push_back({LaneDataArray, "LaneData", dataStart});
|
|
|
|
ResultDataBaseType = editor.DeclareStructType("ResultData", members);
|
|
}
|
|
|
|
rdcspv::Id ResultDataRTArray =
|
|
editor.AddType(rdcspv::OpTypeRuntimeArray(editor.MakeId(), ResultDataBaseType));
|
|
|
|
editor.AddDecoration(rdcspv::OpDecorate(ResultDataRTArray,
|
|
rdcspv::DecorationParam<rdcspv::Decoration::ArrayStride>(
|
|
structStride * numLanes + sizeof(ResultDataBase))));
|
|
|
|
rdcspv::Id bufBase =
|
|
editor.DeclareStructType("__rd_HitStorage", {
|
|
{uint32Type, "hit_count", 0},
|
|
{uint32Type, "total_count", sizeof(uint32_t)},
|
|
{uint32Type, "dummy", sizeof(uint32_t) * 2},
|
|
// uint padding
|
|
|
|
{ResultDataRTArray, "hits", sizeof(Vec4f)},
|
|
});
|
|
|
|
rdcspv::StorageClass bufferClass = editor.PrepareAddedBufferAccess();
|
|
|
|
rdcpair<rdcspv::Id, rdcspv::Id> hitBuffer = editor.AddBufferVariable(
|
|
newGlobals, bufBase, "__rd_HitBuffer", 0, (uint32_t)InputSpecConstant::Address, 0);
|
|
|
|
RDCCOMPILE_ASSERT(
|
|
uint32_t(InputSpecConstant::Address) + 1 == (uint32_t)InputSpecConstant::AddressMSB,
|
|
"Address spec constant IDs must be contiguous with MSB second");
|
|
|
|
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 uint4BufPtr = editor.DeclareType(rdcspv::Pointer(uint4Type, bufferClass));
|
|
rdcspv::Id floatBufPtr = editor.DeclareType(rdcspv::Pointer(floatType, bufferClass));
|
|
|
|
rdcspv::Id glsl450 = editor.ImportExtInst("GLSL.std.450");
|
|
|
|
// allow pixel shaders to use fine derivatives
|
|
if(stage == ShaderStage::Pixel)
|
|
editor.AddCapability(rdcspv::Capability::DerivativeControl);
|
|
|
|
// declare capabilities we might need
|
|
if(threadScope & rdcspv::ThreadScope::Subgroup)
|
|
{
|
|
if(subgroupCapability == SubgroupCapability::None)
|
|
{
|
|
// nothing, ignore, this could only happen if the shader uses only EXT_shader_subgroup_vote
|
|
// which we treat as degenerate
|
|
}
|
|
else if(subgroupCapability == SubgroupCapability::EXTBallot)
|
|
{
|
|
// this should already be present but let's be sure
|
|
editor.AddCapability(rdcspv::Capability::SubgroupBallotKHR);
|
|
}
|
|
else if(subgroupCapability == SubgroupCapability::Vulkan1_1_NoBallot)
|
|
{
|
|
// this should also already be present
|
|
editor.AddCapability(rdcspv::Capability::GroupNonUniform);
|
|
}
|
|
else if(subgroupCapability == SubgroupCapability::Vulkan1_1)
|
|
{
|
|
editor.AddCapability(rdcspv::Capability::GroupNonUniform);
|
|
|
|
// add this, the shader might not have used it but we need it to read other lanes
|
|
editor.AddCapability(rdcspv::Capability::GroupNonUniformBallot);
|
|
editor.AddCapability(rdcspv::Capability::GroupNonUniformVote);
|
|
}
|
|
}
|
|
|
|
rdcspv::Id vecNType[5] = {rdcspv::Id(), floatType, float2Type, float3Type, float4Type};
|
|
rdcspv::Id quadSwizzleHelper[5] = {};
|
|
|
|
if(stage == ShaderStage::Pixel)
|
|
{
|
|
for(uint32_t i = 1; i <= 4; i++)
|
|
{
|
|
quadSwizzleHelper[i] = AddQuadSwizzleHelper(editor, i);
|
|
}
|
|
}
|
|
|
|
{
|
|
rdcspv::OperationList ops;
|
|
|
|
rdcspv::Id voidType = editor.DeclareType(rdcspv::scalar<void>());
|
|
rdcspv::Id uintPtr = editor.DeclareType(rdcspv::Pointer(uint32Type, bufferClass));
|
|
|
|
rdcspv::Id scope = editor.AddConstantImmediate<uint32_t>((uint32_t)rdcspv::Scope::Device);
|
|
rdcspv::Id semantics =
|
|
editor.AddConstantImmediate<uint32_t>((uint32_t)rdcspv::MemorySemantics::AcquireRelease);
|
|
|
|
ops.add(rdcspv::OpFunction(voidType, entryID, rdcspv::FunctionControl::None,
|
|
editor.DeclareType(rdcspv::FunctionType(voidType, {}))));
|
|
|
|
rdcspv::Id structPtr;
|
|
|
|
ops.add(rdcspv::OpLabel(editor.MakeId()));
|
|
{
|
|
structPtr = editor.LoadBufferVariable(ops, hitBuffer);
|
|
|
|
// we store ddx as a derivative check - it is expected to be 1.0 so store that as fixed for other stages
|
|
rdcspv::Id fragCoord, ddxDerivativeCheck = editor.AddConstantImmediate<float>(1.0f);
|
|
rdcspv::Id laneIndex;
|
|
|
|
// identify the candidate thread in a stage-specific way
|
|
rdcspv::Id candidateThread;
|
|
|
|
// prepare stage-specific inputs and condition
|
|
if(stage == ShaderStage::Vertex)
|
|
{
|
|
// we should only be fetching data like this for full subgroups
|
|
RDCASSERT(fullSubgroups);
|
|
|
|
rdcspv::Id vert = editor.AddBuiltinInputLoad(ops, newGlobals, stage,
|
|
rdcspv::BuiltIn::VertexIndex, uint32Type);
|
|
rdcspv::Id inst = editor.AddBuiltinInputLoad(ops, newGlobals, stage,
|
|
rdcspv::BuiltIn::InstanceIndex, uint32Type);
|
|
|
|
rdcspv::Id equalVert = ops.add(rdcspv::OpIEqual(boolType, editor.MakeId(), vert, destVertex));
|
|
rdcspv::Id equalInstance =
|
|
ops.add(rdcspv::OpIEqual(boolType, editor.MakeId(), inst, destInstance));
|
|
|
|
candidateThread =
|
|
ops.add(rdcspv::OpLogicalAnd(boolType, editor.MakeId(), equalVert, equalInstance));
|
|
|
|
if(useViewIndex)
|
|
{
|
|
rdcspv::Id view = editor.AddBuiltinInputLoad(ops, newGlobals, stage,
|
|
rdcspv::BuiltIn::ViewIndex, uint32Type);
|
|
rdcspv::Id equalView = ops.add(rdcspv::OpIEqual(boolType, editor.MakeId(), view, destView));
|
|
candidateThread =
|
|
ops.add(rdcspv::OpLogicalAnd(boolType, editor.MakeId(), candidateThread, equalView));
|
|
}
|
|
}
|
|
else if(stage == ShaderStage::Pixel)
|
|
{
|
|
fragCoord = editor.AddBuiltinInputLoad(ops, newGlobals, stage, rdcspv::BuiltIn::FragCoord,
|
|
float4Type);
|
|
|
|
ddxDerivativeCheck = ops.add(rdcspv::OpDPdx(float4Type, editor.MakeId(), fragCoord));
|
|
editor.SetName(ddxDerivativeCheck, "ddxDerivativeCheck");
|
|
ddxDerivativeCheck =
|
|
ops.add(rdcspv::OpCompositeExtract(floatType, editor.MakeId(), ddxDerivativeCheck, {0}));
|
|
editor.SetName(ddxDerivativeCheck, "ddxDerivativeCheck_x");
|
|
|
|
// grab x and y
|
|
rdcspv::Id fragXY = ops.add(
|
|
rdcspv::OpVectorShuffle(float2Type, editor.MakeId(), fragCoord, fragCoord, {0, 1}));
|
|
|
|
/*
|
|
// figure out the TL pixel's coords and calculate our index relative to it. Assume even top
|
|
// left (towards 0,0) though the spec does not guarantee this is the actual quad
|
|
int yTL = y & (~1);
|
|
|
|
// get the index of our desired pixel
|
|
*/
|
|
|
|
rdcspv::Id mask = editor.AddConstantImmediate<uint32_t>(1);
|
|
|
|
// int x01 = x & 1;
|
|
rdcspv::Id xInt =
|
|
ops.add(rdcspv::OpCompositeExtract(floatType, editor.MakeId(), fragXY, {0}));
|
|
xInt = ops.add(rdcspv::OpConvertFToU(uint32Type, editor.MakeId(), xInt));
|
|
rdcspv::Id x01 = ops.add(rdcspv::OpBitwiseAnd(uint32Type, editor.MakeId(), xInt, mask));
|
|
|
|
// int y01 = y & 1;
|
|
rdcspv::Id yInt =
|
|
ops.add(rdcspv::OpCompositeExtract(floatType, editor.MakeId(), fragXY, {1}));
|
|
yInt = ops.add(rdcspv::OpConvertFToU(uint32Type, editor.MakeId(), yInt));
|
|
rdcspv::Id y01 = ops.add(rdcspv::OpBitwiseAnd(uint32Type, editor.MakeId(), yInt, mask));
|
|
|
|
// int destIdx = x01 + 2 * y01;
|
|
rdcspv::Id sum = ops.add(rdcspv::OpIMul(uint32Type, editor.MakeId(),
|
|
editor.AddConstantImmediate<uint32_t>(2), y01));
|
|
ops.add(rdcspv::OpIAdd(uint32Type, quadLaneIndex, sum, x01));
|
|
laneIndex = quadLaneIndex;
|
|
editor.SetName(quadLaneIndex, "quadLaneIndex");
|
|
|
|
// bool candidateThread = all(abs(gl_FragCoord.xy - dest.xy) < 0.5f);
|
|
rdcspv::Id bool2Type = editor.DeclareType(rdcspv::Vector(rdcspv::scalar<bool>(), 2));
|
|
|
|
// subtract frag coord from the destination co-ord in x-y to get relative
|
|
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
|
|
candidateThread = ops.add(rdcspv::OpAll(boolType, editor.MakeId(), inPixelXY));
|
|
}
|
|
else if(stage == ShaderStage::Compute || stage == ShaderStage::Task ||
|
|
stage == ShaderStage::Mesh)
|
|
{
|
|
// we should only be fetching data like this for full subgroups
|
|
RDCASSERT(fullSubgroups);
|
|
|
|
rdcspv::Id globalThread = editor.AddBuiltinInputLoad(
|
|
ops, newGlobals, stage, rdcspv::BuiltIn::GlobalInvocationId, uint3Type);
|
|
|
|
rdcspv::Id bool3Type = editor.DeclareType(rdcspv::Vector(rdcspv::scalar<bool>(), 3));
|
|
rdcspv::Id equal3 =
|
|
ops.add(rdcspv::OpIEqual(bool3Type, editor.MakeId(), globalThread, destThreadID));
|
|
|
|
candidateThread = ops.add(rdcspv::OpAll(boolType, editor.MakeId(), equal3));
|
|
}
|
|
|
|
rdcspv::Id quadIdxConst[4] = {
|
|
editor.AddConstantImmediate<uint32_t>(0),
|
|
editor.AddConstantImmediate<uint32_t>(1),
|
|
editor.AddConstantImmediate<uint32_t>(2),
|
|
editor.AddConstantImmediate<uint32_t>(3),
|
|
};
|
|
|
|
// load all data per-thread and calculate quad swizzled neighbour data as needed
|
|
for(laneValue &val : laneValues)
|
|
{
|
|
ops.append(val.loadOps);
|
|
|
|
// for pixel shaders we always need to grab quad swizzled data.
|
|
// we skip this for values we classify as flat as well as for the magic isHelper/quadLaneIndex
|
|
// which are handled specially and will be fixed up later when we go to store these
|
|
if(stage == ShaderStage::Pixel && val.base != isHelper && val.base != quadLaneIndex &&
|
|
!val.flat)
|
|
{
|
|
val.quadSwizzledData.resize(4);
|
|
|
|
const rdcspv::DataType &dataType = editor.GetDataType(val.type);
|
|
|
|
if(dataType.IsU32())
|
|
{
|
|
for(uint32_t q = 0; q < 4; q++)
|
|
{
|
|
rdcspv::Id valQ = val.base;
|
|
valQ = ops.add(rdcspv::OpConvertUToF(floatType, editor.MakeId(), valQ));
|
|
valQ = ops.add(rdcspv::OpFunctionCall(floatType, editor.MakeId(), quadSwizzleHelper[1],
|
|
{valQ, quadLaneIndex, quadIdxConst[q]}));
|
|
// named as convenience because spirv-cross declares a variable here and then does the cast at the usage
|
|
editor.SetName(valQ, StringFormat::Fmt("%s_swiz%u", val.name.c_str(), q));
|
|
|
|
valQ = ops.add(rdcspv::OpConvertFToU(uint32Type, editor.MakeId(), valQ));
|
|
editor.SetName(valQ, StringFormat::Fmt("%s_swiz%u_u", val.name.c_str(), q));
|
|
|
|
val.quadSwizzledData[q] = valQ;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// all other inputs that aren't uint32 should be floats, otherwise they should have been marked as flat
|
|
RDCASSERT(dataType.scalar().type == rdcspv::Op::TypeFloat);
|
|
|
|
uint32_t width = RDCMAX(1U, dataType.vector().count);
|
|
|
|
for(uint32_t q = 0; q < 4; q++)
|
|
{
|
|
val.quadSwizzledData[q] = ops.add(
|
|
rdcspv::OpFunctionCall(vecNType[width], editor.MakeId(), quadSwizzleHelper[width],
|
|
{val.base, quadLaneIndex, quadIdxConst[q]}));
|
|
editor.SetName(val.quadSwizzledData[q],
|
|
StringFormat::Fmt("%s_swiz%u", val.name.c_str(), q));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
rdcspv::Id subgroupSize, numSubgroups, globalBallot, electBallot, helperBallot;
|
|
|
|
// if we are doing even minimal subgroups, read the subgroup-relative lane index and subgroup size
|
|
if(minimalSubgroups || fullSubgroups)
|
|
{
|
|
if(subgroupCapability == SubgroupCapability::EXTBallot)
|
|
{
|
|
globalBallot = ops.add(rdcspv::OpSubgroupBallotKHR(
|
|
uint4Type, editor.MakeId(), editor.AddConstantImmediate<bool>(true)));
|
|
electBallot = editor.AddConstant(rdcspv::OpConstantNull(uint4Type, editor.MakeId()));
|
|
|
|
if(stage == ShaderStage::Pixel)
|
|
{
|
|
helperBallot = ops.add(rdcspv::OpINotEqual(boolType, editor.MakeId(), isHelper,
|
|
editor.AddConstantImmediate<uint32_t>(0)));
|
|
helperBallot =
|
|
ops.add(rdcspv::OpSubgroupBallotKHR(uint4Type, editor.MakeId(), helperBallot));
|
|
}
|
|
else
|
|
{
|
|
helperBallot = editor.AddConstant(rdcspv::OpConstantNull(uint4Type, editor.MakeId()));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
globalBallot = ops.add(rdcspv::OpGroupNonUniformBallot(
|
|
uint4Type, editor.MakeId(), subgroupScope, editor.AddConstantImmediate<bool>(true)));
|
|
electBallot =
|
|
ops.add(rdcspv::OpGroupNonUniformElect(boolType, editor.MakeId(), subgroupScope));
|
|
electBallot = ops.add(rdcspv::OpGroupNonUniformBallot(uint4Type, editor.MakeId(),
|
|
subgroupScope, electBallot));
|
|
|
|
if(stage == ShaderStage::Pixel)
|
|
{
|
|
helperBallot = ops.add(rdcspv::OpINotEqual(boolType, editor.MakeId(), isHelper,
|
|
editor.AddConstantImmediate<uint32_t>(0)));
|
|
helperBallot = ops.add(rdcspv::OpGroupNonUniformBallot(uint4Type, editor.MakeId(),
|
|
subgroupScope, helperBallot));
|
|
}
|
|
else
|
|
{
|
|
helperBallot = editor.AddConstant(rdcspv::OpConstantNull(uint4Type, editor.MakeId()));
|
|
}
|
|
}
|
|
|
|
laneIndex = editor.AddBuiltinInputLoad(
|
|
ops, newGlobals, stage, rdcspv::BuiltIn::SubgroupLocalInvocationId, uint32Type);
|
|
subgroupSize = editor.AddBuiltinInputLoad(ops, newGlobals, stage,
|
|
rdcspv::BuiltIn::SubgroupSize, uint32Type);
|
|
editor.SetName(laneIndex, "laneIndex");
|
|
editor.SetName(subgroupSize, "subgroupSize");
|
|
|
|
// subgroup ID & num subgroups is only available for compute
|
|
if(stage == ShaderStage::Compute || stage == ShaderStage::Task || stage == ShaderStage::Mesh)
|
|
{
|
|
numSubgroups = editor.AddBuiltinInputLoad(ops, newGlobals, stage,
|
|
rdcspv::BuiltIn::NumSubgroups, uint32Type);
|
|
editor.SetName(numSubgroups, "numSubgroups");
|
|
}
|
|
else
|
|
{
|
|
numSubgroups = editor.AddConstantImmediate<uint32_t>(0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
globalBallot = editor.AddConstant(rdcspv::OpConstantNull(uint4Type, editor.MakeId()));
|
|
electBallot = editor.AddConstant(rdcspv::OpConstantNull(uint4Type, editor.MakeId()));
|
|
helperBallot = editor.AddConstant(rdcspv::OpConstantNull(uint4Type, editor.MakeId()));
|
|
subgroupSize = editor.AddConstantImmediate<uint32_t>(0);
|
|
numSubgroups = editor.AddConstantImmediate<uint32_t>(0);
|
|
}
|
|
editor.SetName(globalBallot, "globalBallot");
|
|
|
|
// in a pixel shader we need to take extra steps to ensure we get helper data, and it depends
|
|
// on if we're fetching subgroups or not.
|
|
// if we're not fetching subgroups, we always fetch all 4 helpers and store them since that's
|
|
// all our data. if we ARE fetching subgroups, helper lanes will not write their own data so
|
|
// we do that from the candidate thread (only for the helper lanes, conditionally). We also
|
|
// store into the lane index of each quad with subgroups, as opposed to just 0-3 for a plain
|
|
// quad.
|
|
// if we're not in a pixel shader we don't do any of this
|
|
rdcspv::Id isHelperPerQuad[4] = {};
|
|
rdcspv::Id shouldStoreHelperPerQuad[4] = {};
|
|
rdcspv::Id quadLaneStoreIdx[4] = {};
|
|
|
|
if(stage == ShaderStage::Pixel)
|
|
{
|
|
// calculate the quadId that we need for pixels, the top-left thread's lane index
|
|
rdcspv::Id quadIdSwizzle =
|
|
ops.add(rdcspv::OpConvertUToF(floatType, editor.MakeId(), laneIndex));
|
|
quadIdSwizzle =
|
|
ops.add(rdcspv::OpFunctionCall(floatType, editor.MakeId(), quadSwizzleHelper[1],
|
|
{quadIdSwizzle, quadLaneIndex, quadIdxConst[0]}));
|
|
|
|
quadIdSwizzle = ops.add(rdcspv::OpConvertFToU(uint32Type, editor.MakeId(), quadIdSwizzle));
|
|
// add offset so that quad IDs are always non-zero
|
|
quadId = ops.add(rdcspv::OpIAdd(uint32Type, quadId, quadIdSwizzle,
|
|
editor.AddConstantImmediate<uint32_t>(10000)));
|
|
|
|
for(uint32_t q = 0; q < 4; q++)
|
|
{
|
|
isHelperPerQuad[q] = ops.add(rdcspv::OpConvertUToF(floatType, editor.MakeId(), isHelper));
|
|
isHelperPerQuad[q] =
|
|
ops.add(rdcspv::OpFunctionCall(floatType, editor.MakeId(), quadSwizzleHelper[1],
|
|
{isHelperPerQuad[q], quadLaneIndex, quadIdxConst[q]}));
|
|
isHelperPerQuad[q] =
|
|
ops.add(rdcspv::OpConvertFToU(uint32Type, editor.MakeId(), isHelperPerQuad[q]));
|
|
}
|
|
|
|
if(fullSubgroups)
|
|
{
|
|
for(uint32_t q = 0; q < 4; q++)
|
|
{
|
|
shouldStoreHelperPerQuad[q] =
|
|
ops.add(rdcspv::OpINotEqual(boolType, editor.MakeId(), isHelperPerQuad[q],
|
|
editor.AddConstantImmediate<uint32_t>(0)));
|
|
|
|
quadLaneStoreIdx[q] =
|
|
ops.add(rdcspv::OpConvertUToF(floatType, editor.MakeId(), laneIndex));
|
|
quadLaneStoreIdx[q] = ops.add(
|
|
rdcspv::OpFunctionCall(floatType, editor.MakeId(), quadSwizzleHelper[1],
|
|
{quadLaneStoreIdx[q], quadLaneIndex, quadIdxConst[q]}));
|
|
quadLaneStoreIdx[q] =
|
|
ops.add(rdcspv::OpConvertFToU(uint32Type, editor.MakeId(), quadLaneStoreIdx[q]));
|
|
|
|
editor.SetName(isHelperPerQuad[q], StringFormat::Fmt("isHelper%u", q));
|
|
editor.SetName(shouldStoreHelperPerQuad[q], StringFormat::Fmt("shouldStore%u", q));
|
|
editor.SetName(quadLaneStoreIdx[q], StringFormat::Fmt("quadLaneStoreIdx%u", q));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for(uint32_t q = 0; q < 4; q++)
|
|
{
|
|
shouldStoreHelperPerQuad[q] = editor.AddConstantImmediate<bool>(true);
|
|
quadLaneStoreIdx[q] = quadIdxConst[q];
|
|
}
|
|
}
|
|
}
|
|
|
|
// get a pointer to buffer.hit_count
|
|
rdcspv::Id hit_count = ops.add(rdcspv::OpAccessChain(
|
|
uintPtr, editor.MakeId(), structPtr, {editor.AddConstantImmediate<uint32_t>(0)}));
|
|
|
|
// get a pointer to buffer.total_count
|
|
rdcspv::Id total_count = ops.add(rdcspv::OpAccessChain(
|
|
uintPtr, editor.MakeId(), structPtr, {editor.AddConstantImmediate<uint32_t>(1)}));
|
|
|
|
editor.SetName(candidateThread, "candidateThread");
|
|
|
|
// if we are fetching full subgroups in a pixel shader, we need to know which threads are quad
|
|
// neighbours of the candidate so we can write their quad lane index properly
|
|
rdcspv::Id candidateThreadInQuad;
|
|
if(fullSubgroups && stage == ShaderStage::Pixel)
|
|
{
|
|
rdcspv::Id zeroF = editor.AddConstantImmediate<float>(0.0f);
|
|
|
|
// we do a simple check here - if candidateThread is true, or any ddx/ddy is non-zero, we're
|
|
// in the candidate quad
|
|
|
|
rdcspv::Id candidateThreadF = ops.add(rdcspv::OpSelect(
|
|
uint32Type, editor.MakeId(), candidateThread, editor.AddConstantImmediate<uint32_t>(1),
|
|
editor.AddConstantImmediate<uint32_t>(0)));
|
|
candidateThreadF =
|
|
ops.add(rdcspv::OpConvertUToF(floatType, editor.MakeId(), candidateThreadF));
|
|
|
|
rdcspv::Id candidateThreadDDXFine =
|
|
ops.add(rdcspv::OpDPdxFine(floatType, editor.MakeId(), candidateThreadF));
|
|
candidateThreadDDXFine = ops.add(
|
|
rdcspv::OpFOrdGreaterThan(boolType, editor.MakeId(), candidateThreadDDXFine, zeroF));
|
|
|
|
rdcspv::Id candidateThreadDDYFine =
|
|
ops.add(rdcspv::OpDPdyFine(floatType, editor.MakeId(), candidateThreadF));
|
|
candidateThreadDDYFine = ops.add(
|
|
rdcspv::OpFOrdGreaterThan(boolType, editor.MakeId(), candidateThreadDDYFine, zeroF));
|
|
|
|
rdcspv::Id candidateThreadDDXCoarse =
|
|
ops.add(rdcspv::OpDPdxCoarse(floatType, editor.MakeId(), candidateThreadF));
|
|
candidateThreadDDXCoarse = ops.add(
|
|
rdcspv::OpFOrdGreaterThan(boolType, editor.MakeId(), candidateThreadDDXCoarse, zeroF));
|
|
|
|
rdcspv::Id candidateThreadDDYCoarse =
|
|
ops.add(rdcspv::OpDPdyCoarse(floatType, editor.MakeId(), candidateThreadF));
|
|
candidateThreadDDYCoarse = ops.add(
|
|
rdcspv::OpFOrdGreaterThan(boolType, editor.MakeId(), candidateThreadDDYCoarse, zeroF));
|
|
|
|
candidateThreadInQuad = ops.add(rdcspv::OpLogicalOr(
|
|
boolType, editor.MakeId(), candidateThread, candidateThreadDDXFine));
|
|
candidateThreadInQuad = ops.add(rdcspv::OpLogicalOr(
|
|
boolType, editor.MakeId(), candidateThreadInQuad, candidateThreadDDYFine));
|
|
candidateThreadInQuad = ops.add(rdcspv::OpLogicalOr(
|
|
boolType, editor.MakeId(), candidateThreadInQuad, candidateThreadDDXCoarse));
|
|
candidateThreadInQuad = ops.add(rdcspv::OpLogicalOr(
|
|
boolType, editor.MakeId(), candidateThreadInQuad, candidateThreadDDYCoarse));
|
|
editor.SetName(candidateThreadInQuad, "candidateThreadInQuad");
|
|
}
|
|
|
|
rdcarray<rdcspv::Id> killLabels;
|
|
killLabels.push_back(editor.MakeId());
|
|
rdcspv::Id writeLabel = editor.MakeId();
|
|
|
|
rdcspv::Id writeCondition = candidateThread;
|
|
|
|
// if we're doing proper subgroup readback, keep the whole subgroup, otherwise branch non-uniformly
|
|
if(fullSubgroups)
|
|
{
|
|
if(subgroupCapability == SubgroupCapability::Vulkan1_1)
|
|
{
|
|
writeCondition = ops.add(rdcspv::OpGroupNonUniformAny(boolType, editor.MakeId(),
|
|
subgroupScope, candidateThread));
|
|
}
|
|
else
|
|
{
|
|
// KHR path, emulate a vote with any(ballot() != 0u) so we don't depend on the vote
|
|
// extension - we probably can, but don't have to
|
|
writeCondition =
|
|
ops.add(rdcspv::OpSubgroupBallotKHR(uint4Type, editor.MakeId(), candidateThread));
|
|
writeCondition = ops.add(rdcspv::OpINotEqual(
|
|
bool4Type, editor.MakeId(), writeCondition,
|
|
editor.AddConstant(rdcspv::OpConstantNull(uint4Type, editor.MakeId()))));
|
|
writeCondition = ops.add(rdcspv::OpAny(boolType, editor.MakeId(), writeCondition));
|
|
}
|
|
}
|
|
|
|
ops.add(rdcspv::OpSelectionMerge(killLabels.back(), rdcspv::SelectionControl::None));
|
|
ops.add(rdcspv::OpBranchConditional(writeCondition, writeLabel, killLabels.back()));
|
|
|
|
ops.add(rdcspv::OpLabel(writeLabel));
|
|
|
|
// for pixel shaders with subgroups, ensure we mask off helper lanes from the subgroup so they
|
|
// don't take part in the elect
|
|
if(fullSubgroups && stage == ShaderStage::Pixel)
|
|
{
|
|
killLabels.push_back(editor.MakeId());
|
|
writeLabel = editor.MakeId();
|
|
rdcspv::Id helperCondition = ops.add(rdcspv::OpIEqual(
|
|
boolType, editor.MakeId(), isHelper, editor.AddConstantImmediate<uint32_t>(0)));
|
|
ops.add(rdcspv::OpSelectionMerge(killLabels.back(), rdcspv::SelectionControl::None));
|
|
ops.add(rdcspv::OpBranchConditional(helperCondition, writeLabel, killLabels.back()));
|
|
ops.add(rdcspv::OpLabel(writeLabel));
|
|
}
|
|
|
|
rdcspv::Id slotAllocLabel = editor.MakeId(), slotMergeLabel = editor.MakeId();
|
|
|
|
// for subgroups the whole subgroup is in here, ensure we only alloc a slot with one lane
|
|
if(fullSubgroups)
|
|
{
|
|
rdcspv::Id nonHelperElected;
|
|
|
|
if(subgroupCapability == SubgroupCapability::Vulkan1_1)
|
|
nonHelperElected =
|
|
ops.add(rdcspv::OpGroupNonUniformElect(boolType, editor.MakeId(), subgroupScope));
|
|
else
|
|
nonHelperElected = ops.add(rdcspv::OpSubgroupFirstInvocationKHR(
|
|
boolType, editor.MakeId(), editor.AddConstantImmediate<bool>(true)));
|
|
ops.add(rdcspv::OpSelectionMerge(slotMergeLabel, rdcspv::SelectionControl::None));
|
|
ops.add(rdcspv::OpBranchConditional(nonHelperElected, slotAllocLabel, slotMergeLabel));
|
|
}
|
|
else
|
|
{
|
|
ops.add(rdcspv::OpBranch(slotAllocLabel));
|
|
}
|
|
|
|
ops.add(rdcspv::OpLabel(slotAllocLabel));
|
|
|
|
// increment total_count
|
|
ops.add(rdcspv::OpAtomicIAdd(uint32Type, editor.MakeId(), total_count, scope, semantics,
|
|
editor.AddConstantImmediate<uint32_t>(1)));
|
|
|
|
// allocate a slot with atomic add
|
|
rdcspv::Id slot =
|
|
ops.add(rdcspv::OpAtomicIAdd(uint32Type, editor.MakeId(), hit_count, scope, semantics,
|
|
editor.AddConstantImmediate<uint32_t>(1)));
|
|
|
|
editor.SetName(slot, "slotAlloc");
|
|
|
|
ops.add(rdcspv::OpBranch(slotMergeLabel));
|
|
|
|
ops.add(rdcspv::OpLabel(slotMergeLabel));
|
|
|
|
// now if we're in a subgroup we need to broadcast the slot to the whole group, and also OpPhi
|
|
// the previous slot depending on where we got it from
|
|
if(fullSubgroups)
|
|
{
|
|
slot = ops.add(rdcspv::OpPhi(
|
|
uint32Type, editor.MakeId(),
|
|
{{slot, slotAllocLabel}, {editor.AddConstantImmediate<uint32_t>(0U), writeLabel}}));
|
|
editor.SetName(slot, "slotToBroadcast");
|
|
|
|
if(subgroupCapability == SubgroupCapability::Vulkan1_1)
|
|
slot = ops.add(rdcspv::OpGroupNonUniformBroadcastFirst(uint32Type, editor.MakeId(),
|
|
subgroupScope, slot));
|
|
else
|
|
slot = ops.add(rdcspv::OpSubgroupFirstInvocationKHR(uint32Type, editor.MakeId(), slot));
|
|
editor.SetName(slot, "slot");
|
|
}
|
|
|
|
rdcspv::Id inRange = ops.add(rdcspv::OpULessThan(boolType, editor.MakeId(), slot, arrayLength));
|
|
|
|
killLabels.push_back(editor.MakeId());
|
|
writeLabel = editor.MakeId();
|
|
ops.add(rdcspv::OpSelectionMerge(killLabels.back(), rdcspv::SelectionControl::None));
|
|
ops.add(rdcspv::OpBranchConditional(inRange, writeLabel, killLabels.back()));
|
|
ops.add(rdcspv::OpLabel(writeLabel));
|
|
|
|
rdcspv::Id hitptr = editor.DeclareType(rdcspv::Pointer(ResultDataBaseType, bufferClass));
|
|
|
|
// get a pointer to the hit for our slot
|
|
rdcspv::Id hit = ops.add(rdcspv::OpAccessChain(
|
|
hitptr, editor.MakeId(), structPtr, {editor.AddConstantImmediate<uint32_t>(3), slot}));
|
|
|
|
// store fixed properties. In the subgroup case this needs to be conditional for only the candidate thread
|
|
rdcspv::Id fixedDataLabel = editor.MakeId(), fixedDataMerge = editor.MakeId();
|
|
|
|
if(fullSubgroups)
|
|
{
|
|
ops.add(rdcspv::OpSelectionMerge(fixedDataMerge, rdcspv::SelectionControl::None));
|
|
ops.add(rdcspv::OpBranchConditional(candidateThread, fixedDataLabel, fixedDataMerge));
|
|
}
|
|
else
|
|
{
|
|
ops.add(rdcspv::OpBranch(fixedDataLabel));
|
|
}
|
|
|
|
ops.add(rdcspv::OpLabel(fixedDataLabel));
|
|
|
|
rdcspv::Id storePtr =
|
|
ops.add(rdcspv::OpAccessChain(float4BufPtr, editor.MakeId(), hit,
|
|
{editor.AddConstantImmediate<uint32_t>(ResultBase_pos)}));
|
|
if(fragCoord != rdcspv::Id())
|
|
ops.add(rdcspv::OpStore(storePtr, fragCoord, alignedAccess));
|
|
|
|
rdcspv::Id primitiveID;
|
|
if(usePrimitiveID)
|
|
{
|
|
primitiveID = editor.AddBuiltinInputLoad(ops, newGlobals, stage,
|
|
rdcspv::BuiltIn::PrimitiveId, uint32Type);
|
|
editor.AddCapability(rdcspv::Capability::Geometry);
|
|
}
|
|
else
|
|
{
|
|
primitiveID = editor.AddConstantImmediate<uint32_t>(0);
|
|
}
|
|
|
|
storePtr =
|
|
ops.add(rdcspv::OpAccessChain(uint32BufPtr, editor.MakeId(), hit,
|
|
{editor.AddConstantImmediate<uint32_t>(ResultBase_prim)}));
|
|
ops.add(rdcspv::OpStore(storePtr, primitiveID, alignedAccess));
|
|
|
|
rdcspv::Id sampleIndex;
|
|
if(useSampleID)
|
|
{
|
|
sampleIndex = editor.AddBuiltinInputLoad(ops, newGlobals, stage, rdcspv::BuiltIn::SampleId,
|
|
uint32Type);
|
|
editor.AddCapability(rdcspv::Capability::SampleRateShading);
|
|
}
|
|
else
|
|
{
|
|
sampleIndex = editor.AddConstantImmediate<uint32_t>(0);
|
|
}
|
|
|
|
storePtr =
|
|
ops.add(rdcspv::OpAccessChain(uint32BufPtr, editor.MakeId(), hit,
|
|
{editor.AddConstantImmediate<uint32_t>(ResultBase_sample)}));
|
|
ops.add(rdcspv::OpStore(storePtr, sampleIndex, alignedAccess));
|
|
|
|
rdcspv::Id viewIndex;
|
|
if(useViewIndex)
|
|
{
|
|
viewIndex = editor.AddBuiltinInputLoad(ops, newGlobals, stage, rdcspv::BuiltIn::ViewIndex,
|
|
uint32Type);
|
|
editor.AddCapability(rdcspv::Capability::MultiView);
|
|
editor.AddExtension("SPV_KHR_multiview");
|
|
}
|
|
else
|
|
{
|
|
viewIndex = editor.AddConstantImmediate<uint32_t>(0);
|
|
}
|
|
|
|
storePtr =
|
|
ops.add(rdcspv::OpAccessChain(uint32BufPtr, editor.MakeId(), hit,
|
|
{editor.AddConstantImmediate<uint32_t>(ResultBase_view)}));
|
|
ops.add(rdcspv::OpStore(storePtr, viewIndex, alignedAccess));
|
|
|
|
storePtr =
|
|
ops.add(rdcspv::OpAccessChain(uint32BufPtr, editor.MakeId(), hit,
|
|
{editor.AddConstantImmediate<uint32_t>(ResultBase_valid)}));
|
|
ops.add(rdcspv::OpStore(storePtr, editor.AddConstantImmediate(validMagicNumber), alignedAccess));
|
|
|
|
// store derivative health check for pixel shaders
|
|
storePtr = ops.add(
|
|
rdcspv::OpAccessChain(floatBufPtr, editor.MakeId(), hit,
|
|
{editor.AddConstantImmediate<uint32_t>(ResultBase_ddxDerivCheck)}));
|
|
ops.add(rdcspv::OpStore(storePtr, ddxDerivativeCheck, alignedAccess));
|
|
|
|
// store the quadLaneIndex (in case it's different to laneIndex)
|
|
storePtr = ops.add(
|
|
rdcspv::OpAccessChain(uint32BufPtr, editor.MakeId(), hit,
|
|
{editor.AddConstantImmediate<uint32_t>(ResultBase_quadLaneIndex)}));
|
|
if(quadLaneIndex != rdcspv::Id())
|
|
ops.add(rdcspv::OpStore(storePtr, quadLaneIndex, alignedAccess));
|
|
|
|
// store the laneIndex
|
|
storePtr = ops.add(
|
|
rdcspv::OpAccessChain(uint32BufPtr, editor.MakeId(), hit,
|
|
{editor.AddConstantImmediate<uint32_t>(ResultBase_laneIndex)}));
|
|
ops.add(rdcspv::OpStore(storePtr, laneIndex, alignedAccess));
|
|
|
|
// if we have them, store subgroup properties, if they're not present they will be 0
|
|
storePtr = ops.add(
|
|
rdcspv::OpAccessChain(uint32BufPtr, editor.MakeId(), hit,
|
|
{editor.AddConstantImmediate<uint32_t>(ResultBase_subgroupSize)}));
|
|
ops.add(rdcspv::OpStore(storePtr, subgroupSize, alignedAccess));
|
|
storePtr = ops.add(
|
|
rdcspv::OpAccessChain(uint4BufPtr, editor.MakeId(), hit,
|
|
{editor.AddConstantImmediate<uint32_t>(ResultBase_globalBallot)}));
|
|
ops.add(rdcspv::OpStore(storePtr, globalBallot, alignedAccess));
|
|
storePtr = ops.add(
|
|
rdcspv::OpAccessChain(uint4BufPtr, editor.MakeId(), hit,
|
|
{editor.AddConstantImmediate<uint32_t>(ResultBase_electBallot)}));
|
|
ops.add(rdcspv::OpStore(storePtr, electBallot, alignedAccess));
|
|
storePtr = ops.add(
|
|
rdcspv::OpAccessChain(uint4BufPtr, editor.MakeId(), hit,
|
|
{editor.AddConstantImmediate<uint32_t>(ResultBase_helperBallot)}));
|
|
ops.add(rdcspv::OpStore(storePtr, helperBallot, alignedAccess));
|
|
storePtr = ops.add(
|
|
rdcspv::OpAccessChain(uint32BufPtr, editor.MakeId(), hit,
|
|
{editor.AddConstantImmediate<uint32_t>(ResultBase_numSubgroups)}));
|
|
ops.add(rdcspv::OpStore(storePtr, numSubgroups, alignedAccess));
|
|
|
|
// merge after doing the fixed data section
|
|
ops.add(rdcspv::OpBranch(fixedDataMerge));
|
|
ops.add(rdcspv::OpLabel(fixedDataMerge));
|
|
|
|
rdcspv::Id LaneDataPtrType = editor.DeclareType(rdcspv::Pointer(LaneDataStruct, bufferClass));
|
|
|
|
// now we conditionally store each helper lane. Only relevant for pixel shaders but we need
|
|
// all helper lanes for all active lanes to ensure we can get derivatives for any of them
|
|
if(stage == ShaderStage::Pixel)
|
|
{
|
|
for(uint32_t q = 0; q < 4; q++)
|
|
{
|
|
rdcspv::Id doHelperLabel = editor.MakeId(), skipHelperLabel = editor.MakeId();
|
|
|
|
ops.add(rdcspv::OpSelectionMerge(skipHelperLabel, rdcspv::SelectionControl::None));
|
|
ops.add(rdcspv::OpBranchConditional(shouldStoreHelperPerQuad[q], doHelperLabel,
|
|
skipHelperLabel));
|
|
ops.add(rdcspv::OpLabel(doHelperLabel));
|
|
|
|
rdcspv::Id outputPtr = ops.add(rdcspv::OpAccessChain(
|
|
LaneDataPtrType, editor.MakeId(), hit,
|
|
{editor.AddConstantImmediate<uint32_t>(ResultBase_firstUser), quadLaneStoreIdx[q]}));
|
|
|
|
for(laneValue &val : laneValues)
|
|
{
|
|
rdcspv::Id valueType = val.type;
|
|
if(valueType == boolType)
|
|
valueType = uint32Type;
|
|
rdcspv::Id ptrType = editor.DeclareType(rdcspv::Pointer(valueType, bufferClass));
|
|
|
|
rdcspv::Id valPtr = ops.add(rdcspv::OpAccessChain(
|
|
ptrType, editor.MakeId(), outputPtr,
|
|
{editor.AddConstantImmediate<uint32_t>((uint32_t)val.structIndex)}));
|
|
|
|
if(val.base == isHelper)
|
|
{
|
|
ops.add(rdcspv::OpStore(valPtr, isHelperPerQuad[q], alignedAccess));
|
|
}
|
|
else if(val.base == quadLaneIndex)
|
|
{
|
|
ops.add(rdcspv::OpStore(valPtr, quadIdxConst[q], alignedAccess));
|
|
}
|
|
else if(val.flat)
|
|
{
|
|
ops.add(rdcspv::OpStore(valPtr, val.base, alignedAccess));
|
|
}
|
|
else
|
|
{
|
|
RDCASSERT(!val.quadSwizzledData.empty());
|
|
ops.add(rdcspv::OpStore(valPtr, val.quadSwizzledData[q], alignedAccess));
|
|
}
|
|
}
|
|
|
|
ops.add(rdcspv::OpBranch(skipHelperLabel));
|
|
ops.add(rdcspv::OpLabel(skipHelperLabel));
|
|
}
|
|
}
|
|
|
|
// if we have full subgroups, each subgroup now writes its own data here, if we are in a
|
|
// non-pixel shader without subgroups we store the single thread's data here.
|
|
// the non-subgroup pixel shader case is handled above in the helper lanes (which will all store)
|
|
if(fullSubgroups || stage != ShaderStage::Pixel)
|
|
{
|
|
rdcspv::Id idx;
|
|
|
|
if(fullSubgroups)
|
|
idx = laneIndex;
|
|
else
|
|
idx = editor.AddConstantImmediate<uint32_t>(0U);
|
|
|
|
rdcspv::Id outputPtr = ops.add(rdcspv::OpAccessChain(
|
|
LaneDataPtrType, editor.MakeId(), hit,
|
|
{editor.AddConstantImmediate<uint32_t>(ResultBase_firstUser), idx}));
|
|
|
|
for(laneValue &val : laneValues)
|
|
{
|
|
rdcspv::Id valueType = val.type;
|
|
if(valueType == boolType)
|
|
valueType = uint32Type;
|
|
rdcspv::Id ptrType = editor.DeclareType(rdcspv::Pointer(valueType, bufferClass));
|
|
|
|
rdcspv::Id valPtr = ops.add(rdcspv::OpAccessChain(
|
|
ptrType, editor.MakeId(), outputPtr,
|
|
{editor.AddConstantImmediate<uint32_t>((uint32_t)val.structIndex)}));
|
|
|
|
ops.add(rdcspv::OpStore(valPtr, val.base, alignedAccess));
|
|
}
|
|
}
|
|
|
|
// join up with the early-outs we did, in reverse order
|
|
for(size_t i = 0; i < killLabels.size(); i++)
|
|
{
|
|
rdcspv::Id label = killLabels[killLabels.size() - 1 - i];
|
|
ops.add(rdcspv::OpBranch(label));
|
|
ops.add(rdcspv::OpLabel(label));
|
|
}
|
|
}
|
|
|
|
// we want to "call" the original function to ensure the compiler does hopefully as close
|
|
// codegen as possible to the original but we don't want to actually execute it. To do this we
|
|
// use an atomic max with a dummy value and only call the function if the value is *larger* -
|
|
// the compiler can't know what value was pre-existing in the buffer (though we know it was
|
|
// zero) so it can't eliminate either branch, but in practice we will always return
|
|
|
|
rdcspv::Id trueLabel = editor.MakeId();
|
|
rdcspv::Id falseLabel = editor.MakeId();
|
|
|
|
// get a pointer to buffer.dummy
|
|
rdcspv::Id dummy = ops.add(rdcspv::OpAccessChain(uintPtr, editor.MakeId(), structPtr,
|
|
{editor.AddConstantImmediate<uint32_t>(2)}));
|
|
|
|
dummy = ops.add(rdcspv::OpAtomicUMax(uint32Type, editor.MakeId(), dummy, scope, semantics,
|
|
editor.AddConstantImmediate<uint32_t>(1)));
|
|
editor.SetName(dummy, "dummy");
|
|
rdcspv::Id dummyCompare = ops.add(rdcspv::OpULessThan(
|
|
boolType, editor.MakeId(), dummy, editor.AddConstantImmediate<uint32_t>(2)));
|
|
|
|
ops.add(rdcspv::OpSelectionMerge(falseLabel, rdcspv::SelectionControl::None));
|
|
ops.add(rdcspv::OpBranchConditional(dummyCompare, trueLabel, falseLabel));
|
|
|
|
ops.add(rdcspv::OpLabel(trueLabel));
|
|
|
|
// don't return, kill. This makes it well-defined that we don't write anything to our outputs
|
|
if(ShaderStage(shadRefl.stageIndex) == ShaderStage::Pixel)
|
|
ops.add(rdcspv::OpKill());
|
|
else
|
|
ops.add(rdcspv::OpReturn());
|
|
|
|
ops.add(rdcspv::OpLabel(falseLabel));
|
|
|
|
ops.add(rdcspv::OpFunctionCall(voidType, editor.MakeId(), originalEntry));
|
|
|
|
ops.add(rdcspv::OpReturn());
|
|
|
|
ops.add(rdcspv::OpFunctionEnd());
|
|
|
|
editor.AddFunction(ops);
|
|
}
|
|
|
|
editor.AddEntryGlobals(entryID, newGlobals);
|
|
}
|
|
|
|
rdcpair<uint32_t, uint32_t> GetAlignAndOutputSize(VulkanCreationInfo::ShaderModuleReflection &shadRefl)
|
|
{
|
|
uint32_t paramAlign = 16;
|
|
|
|
for(const SigParameter &sig : shadRefl.refl->inputSignature)
|
|
{
|
|
if(VarTypeByteSize(sig.varType) * sig.compCount > paramAlign)
|
|
paramAlign = 32;
|
|
}
|
|
|
|
// conservatively calculate structure stride with full amount for every input element
|
|
uint32_t structStride = (uint32_t)shadRefl.refl->inputSignature.size() * paramAlign;
|
|
|
|
if(shadRefl.refl->stage == ShaderStage::Vertex)
|
|
structStride += sizeof(VertexLaneData);
|
|
else if(shadRefl.refl->stage == ShaderStage::Pixel)
|
|
structStride += sizeof(PixelLaneData);
|
|
else if(shadRefl.refl->stage == ShaderStage::Compute ||
|
|
shadRefl.refl->stage == ShaderStage::Task || shadRefl.refl->stage == ShaderStage::Mesh)
|
|
structStride += sizeof(ComputeLaneData);
|
|
|
|
if(shadRefl.patchData.threadScope & rdcspv::ThreadScope::Subgroup)
|
|
{
|
|
structStride += sizeof(SubgroupLaneData);
|
|
}
|
|
|
|
return {paramAlign, structStride};
|
|
}
|
|
|
|
VkDescriptorSetLayoutBinding MakeNewBinding(VkShaderStageFlagBits stage)
|
|
{
|
|
return {
|
|
0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT, NULL,
|
|
};
|
|
}
|
|
|
|
void VulkanReplay::CalculateSubgroupProperties(uint32_t &maxSubgroupSize,
|
|
SubgroupCapability &subgroupCapability)
|
|
{
|
|
maxSubgroupSize = 4;
|
|
|
|
// if we don't have subgroup ballots we assume we have no real meaningful subgroup capabilities at
|
|
// all except for 'basic'. The only thing basic lets you do is fetch the subgroup ID, and
|
|
// determine which lane is the first active (OpGroupNonUniformElect).
|
|
// in this case we effectively consider it non-subgroup and just read those values directly to
|
|
// fill in, but otherwise simulate as if there were no subgroup use.
|
|
|
|
// for our purposes vulkan 1.1 fully deprecated the old EXT_shader_subgroup_* pair of extensions
|
|
// as the only thing that wasn't deprecated was a non-constant broadcast ID which we don't need
|
|
if(m_pDriver->GetExtensions(NULL).vulkanVersion >= VK_API_VERSION_1_1)
|
|
{
|
|
VkPhysicalDeviceSubgroupProperties subProps = {
|
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES,
|
|
};
|
|
|
|
VkPhysicalDeviceProperties2 availBase = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2};
|
|
availBase.pNext = &subProps;
|
|
m_pDriver->vkGetPhysicalDeviceProperties2(m_pDriver->GetPhysDev(), &availBase);
|
|
|
|
maxSubgroupSize = subProps.subgroupSize;
|
|
subgroupCapability = SubgroupCapability::Vulkan1_1_NoBallot;
|
|
const VkSubgroupFeatureFlags requiredFlags =
|
|
(VK_SUBGROUP_FEATURE_BALLOT_BIT | VK_SUBGROUP_FEATURE_VOTE_BIT);
|
|
if((subProps.supportedOperations & requiredFlags) == requiredFlags)
|
|
subgroupCapability = SubgroupCapability::Vulkan1_1;
|
|
|
|
if(m_pDriver->GetExtensions(NULL).ext_EXT_subgroup_size_control)
|
|
{
|
|
VkPhysicalDeviceSubgroupSizeControlProperties subSizeProps = {
|
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_SIZE_CONTROL_PROPERTIES,
|
|
};
|
|
availBase.pNext = &subSizeProps;
|
|
m_pDriver->vkGetPhysicalDeviceProperties2(m_pDriver->GetPhysDev(), &availBase);
|
|
|
|
// use new upper bound in case it's higher with variable sizes
|
|
maxSubgroupSize = RDCMAX(maxSubgroupSize, subSizeProps.maxSubgroupSize);
|
|
}
|
|
}
|
|
else if(m_pDriver->GetExtensions(NULL).ext_EXT_shader_subgroup_ballot)
|
|
{
|
|
// the ballot extension only proides the subgroup size on the GPU so we need to allocate worst case up front
|
|
|
|
RDCWARN("Subgroup ballot extension is best extension enabled - using worst case subgroup size");
|
|
|
|
maxSubgroupSize = 128;
|
|
subgroupCapability = SubgroupCapability::EXTBallot;
|
|
}
|
|
else if(m_pDriver->GetExtensions(NULL).ext_EXT_shader_subgroup_vote)
|
|
{
|
|
// if only the vote extension is enabled we have no way to determine the subgroup size or
|
|
// anything, so we just fall back to treating this as a degenerate case with a single thread
|
|
|
|
RDCWARN("Subgroup vote extension is only subgroup feature enabled - treating as degenerate");
|
|
|
|
maxSubgroupSize = 1;
|
|
subgroupCapability = SubgroupCapability::None;
|
|
}
|
|
}
|
|
|
|
VkSpecializationInfo VulkanReplay::MakeSpecInfo(SpecData &specData, VkSpecializationMapEntry *specMaps)
|
|
{
|
|
memcpy(specMaps, specMapsTemplate, sizeof(specMapsTemplate));
|
|
|
|
specMaps[(uint32_t)InputSpecConstant::Address].size =
|
|
(m_StorageMode == BufferStorageMode::KHR_bda32 ? sizeof(uint32_t) : sizeof(uint64_t));
|
|
|
|
VkSpecializationInfo ret = {};
|
|
ret.dataSize = sizeof(specData);
|
|
ret.pData = &specData;
|
|
ret.mapEntryCount = (uint32_t)InputSpecConstant::Count;
|
|
ret.pMapEntries = specMaps;
|
|
return ret;
|
|
}
|
|
|
|
ShaderDebugTrace *VulkanReplay::DebugVertex(uint32_t eventId, uint32_t vertid, uint32_t instid,
|
|
uint32_t idx, uint32_t view)
|
|
{
|
|
const VulkanRenderState &state = m_pDriver->GetRenderState();
|
|
VulkanCreationInfo &c = m_pDriver->m_CreationInfo;
|
|
|
|
rdcstr regionName =
|
|
StringFormat::Fmt("DebugVertex @ %u of (%u,%u,%u,%u)", eventId, vertid, instid, idx, view);
|
|
|
|
VkMarkerRegion region(regionName);
|
|
|
|
if(Vulkan_Debug_ShaderDebugLogging())
|
|
RDCLOG("%s", regionName.c_str());
|
|
|
|
const ActionDescription *action = m_pDriver->GetAction(eventId);
|
|
|
|
if(!(action->flags & ActionFlags::Drawcall))
|
|
{
|
|
RDCLOG("No drawcall selected");
|
|
return new ShaderDebugTrace();
|
|
}
|
|
|
|
uint32_t vertOffset = 0, instOffset = 0;
|
|
if(!(action->flags & ActionFlags::Indexed))
|
|
vertOffset = action->vertexOffset;
|
|
|
|
if(action->flags & ActionFlags::Instanced)
|
|
instOffset = action->instanceOffset;
|
|
|
|
// get ourselves in pristine state before this action (without any side effects it may have had)
|
|
m_pDriver->ReplayLog(0, eventId, eReplay_WithoutDraw);
|
|
|
|
const VulkanCreationInfo::Pipeline &pipe = c.m_Pipeline[state.graphics.pipeline];
|
|
const VulkanCreationInfo::ShaderEntry &shaderEntry =
|
|
state.graphics.shaderObject ? c.m_ShaderObject[state.shaderObjects[0]].shad : pipe.shaders[0];
|
|
VulkanCreationInfo::ShaderModule &shader = c.m_ShaderModule[shaderEntry.module];
|
|
rdcstr entryPoint = shaderEntry.entryPoint;
|
|
const rdcarray<SpecConstant> &spec = shaderEntry.specialization;
|
|
|
|
VulkanCreationInfo::ShaderModuleReflection &shadRefl =
|
|
shader.GetReflection(ShaderStage::Vertex, entryPoint, state.graphics.pipeline);
|
|
|
|
if(!shadRefl.refl->debugInfo.debuggable)
|
|
{
|
|
RDCLOG("Shader is not debuggable: %s", shadRefl.refl->debugInfo.debugStatus.c_str());
|
|
return new ShaderDebugTrace();
|
|
}
|
|
|
|
if((shadRefl.patchData.threadScope & rdcspv::ThreadScope::Subgroup) &&
|
|
!m_pDriver->GetDeviceEnabledFeatures().vertexPipelineStoresAndAtomics)
|
|
{
|
|
RDCWARN("Subgroup vertex debugging is not supported without vertex stores");
|
|
return new ShaderDebugTrace;
|
|
}
|
|
|
|
shadRefl.PopulateDisassembly(shader.spirv);
|
|
|
|
VulkanAPIWrapper *apiWrapper =
|
|
new VulkanAPIWrapper(m_pDriver, c, ShaderStage::Vertex, eventId, shadRefl.refl->resourceId);
|
|
|
|
// clamp the view index to the number of multiviews, just to be sure
|
|
size_t numViews;
|
|
|
|
if(state.dynamicRendering.active)
|
|
numViews = Log2Ceil(state.dynamicRendering.viewMask + 1);
|
|
else
|
|
numViews = c.m_RenderPass[state.GetRenderPass()].subpasses[state.subpass].multiviews.size();
|
|
if(numViews > 1)
|
|
view = RDCMIN((uint32_t)numViews - 1, view);
|
|
else
|
|
view = 0;
|
|
|
|
SubgroupCapability subgroupCapability = SubgroupCapability::None;
|
|
uint32_t maxSubgroupSize = 1;
|
|
CalculateSubgroupProperties(maxSubgroupSize, subgroupCapability);
|
|
|
|
uint32_t numThreads = 1;
|
|
|
|
if(shadRefl.patchData.threadScope & rdcspv::ThreadScope::Subgroup)
|
|
numThreads = RDCMAX(numThreads, maxSubgroupSize);
|
|
|
|
apiWrapper->location_inputs.resize(numThreads);
|
|
apiWrapper->thread_builtins.resize(numThreads);
|
|
apiWrapper->thread_props.resize(numThreads);
|
|
|
|
apiWrapper->thread_props[0][(size_t)rdcspv::ThreadProperty::Active] = 1;
|
|
|
|
std::unordered_map<ShaderBuiltin, ShaderVariable> &global_builtins = apiWrapper->global_builtins;
|
|
global_builtins[ShaderBuiltin::BaseInstance] =
|
|
ShaderVariable(rdcstr(), action->instanceOffset, 0U, 0U, 0U);
|
|
global_builtins[ShaderBuiltin::BaseVertex] = ShaderVariable(
|
|
rdcstr(), (action->flags & ActionFlags::Indexed) ? action->baseVertex : action->vertexOffset,
|
|
0U, 0U, 0U);
|
|
global_builtins[ShaderBuiltin::DeviceIndex] = ShaderVariable(rdcstr(), 0U, 0U, 0U, 0U);
|
|
global_builtins[ShaderBuiltin::DrawIndex] = ShaderVariable(rdcstr(), action->drawIndex, 0U, 0U, 0U);
|
|
global_builtins[ShaderBuiltin::ViewportIndex] = ShaderVariable(rdcstr(), view, 0U, 0U, 0U);
|
|
global_builtins[ShaderBuiltin::MultiViewIndex] = ShaderVariable(rdcstr(), view, 0U, 0U, 0U);
|
|
|
|
bool useViewIndex = (view == ~0U) ? false : true;
|
|
if(useViewIndex)
|
|
{
|
|
ResourceId rp = state.GetRenderPass();
|
|
if(rp != ResourceId())
|
|
{
|
|
const VulkanCreationInfo::RenderPass &rpInfo =
|
|
m_pDriver->GetDebugManager()->GetRenderPassInfo(rp);
|
|
for(auto it = rpInfo.subpasses.begin(); it != rpInfo.subpasses.end(); ++it)
|
|
{
|
|
if(it->multiviews.isEmpty())
|
|
{
|
|
if(Vulkan_Debug_ShaderDebugLogging())
|
|
RDCLOG(
|
|
"Disabling useViewIndex because at least one subpass does not have multiple views");
|
|
useViewIndex = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
useViewIndex =
|
|
(state.dynamicRendering.active ? state.dynamicRendering.viewMask : pipe.viewMask) != 0;
|
|
if(!useViewIndex && Vulkan_Debug_ShaderDebugLogging())
|
|
RDCLOG("Disabling useViewIndex because viewMask is zero");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(Vulkan_Debug_ShaderDebugLogging())
|
|
RDCLOG("Disabling useViewIndex from input view %u", view);
|
|
}
|
|
|
|
// if we need to fetch subgroup data, do that now.
|
|
uint32_t laneIndex = 0;
|
|
if(numThreads > 1)
|
|
{
|
|
SpecData specData = {};
|
|
|
|
if(action->flags & ActionFlags::Indexed)
|
|
specData.destVertex = idx;
|
|
else
|
|
specData.destVertex = vertid + vertOffset;
|
|
specData.destInstance = instid + instOffset;
|
|
specData.destView = view == ~0U ? 0 : view;
|
|
|
|
uint32_t paramAlign, structStride;
|
|
rdctie(paramAlign, structStride) = GetAlignAndOutputSize(shadRefl);
|
|
|
|
uint32_t maxHits = 4; // we should only ever get one hit
|
|
|
|
// struct size is ResultDataBase header plus Nx structStride for the number of threads
|
|
uint32_t structSize = sizeof(ResultDataBase) + structStride * numThreads;
|
|
|
|
VkDeviceSize feedbackStorageSize = maxHits * structSize + 1024;
|
|
|
|
if(Vulkan_Debug_ShaderDebugLogging())
|
|
{
|
|
RDCLOG("Output structure is %u sized, output buffer is %llu bytes", structStride,
|
|
feedbackStorageSize);
|
|
}
|
|
|
|
m_PatchedShaderFeedback.ResizeFeedbackBuffer(m_pDriver, feedbackStorageSize);
|
|
|
|
specData.arrayLength = maxHits;
|
|
|
|
// make copy of state to draw from
|
|
VulkanRenderState modifiedstate = state;
|
|
|
|
RDCCOMPILE_ASSERT(NumReservedBindings == 1, "NumReservedBindings is wrong");
|
|
AddedDescriptorData patchedBufferdata = PrepareExtraBufferDescriptor(
|
|
modifiedstate, false, {MakeNewBinding(VK_SHADER_STAGE_VERTEX_BIT)}, false);
|
|
|
|
if(patchedBufferdata.empty())
|
|
{
|
|
delete apiWrapper;
|
|
|
|
ShaderDebugTrace *ret = new ShaderDebugTrace;
|
|
ret->stage = ShaderStage::Vertex;
|
|
|
|
return ret;
|
|
}
|
|
|
|
if(!patchedBufferdata.descSets.empty())
|
|
m_PatchedShaderFeedback.FeedbackBuffer.WriteDescriptor(Unwrap(patchedBufferdata.descSets[0]),
|
|
0, 0);
|
|
|
|
specData.bufferAddress = m_PatchedShaderFeedback.FeedbackBuffer.Address();
|
|
if(specData.bufferAddress && Vulkan_Debug_ShaderDebugLogging())
|
|
{
|
|
RDCLOG("Got buffer address of %llu", specData.bufferAddress);
|
|
}
|
|
|
|
// create shader with modified code
|
|
|
|
VkSpecializationMapEntry specMaps[(size_t)InputSpecConstant::Count];
|
|
RDCCOMPILE_ASSERT(sizeof(specMaps) == sizeof(specMapsTemplate),
|
|
"Specialisation maps have changed");
|
|
|
|
VkSpecializationInfo patchedSpecInfo = MakeSpecInfo(specData, specMaps);
|
|
|
|
auto patchCallback = [this, &shadRefl, &patchedSpecInfo, useViewIndex, subgroupCapability,
|
|
maxSubgroupSize](const AddedDescriptorData &patchedBufferdata,
|
|
VkShaderStageFlagBits stage, const char *entryName,
|
|
const rdcarray<uint32_t> &origSpirv,
|
|
rdcarray<uint32_t> &modSpirv,
|
|
const VkSpecializationInfo *&specInfo) {
|
|
if(stage != VK_SHADER_STAGE_VERTEX_BIT)
|
|
return false;
|
|
|
|
modSpirv = origSpirv;
|
|
|
|
if(!Vulkan_Debug_PSDebugDumpDirPath().empty())
|
|
FileIO::WriteAll(Vulkan_Debug_PSDebugDumpDirPath() + "/debug_vsinput_before.spv", modSpirv);
|
|
|
|
CreateInputFetcher(modSpirv, shadRefl, m_StorageMode, false, false, useViewIndex,
|
|
subgroupCapability, maxSubgroupSize);
|
|
|
|
if(!Vulkan_Debug_PSDebugDumpDirPath().empty())
|
|
FileIO::WriteAll(Vulkan_Debug_PSDebugDumpDirPath() + "/debug_vsinput_after.spv", modSpirv);
|
|
|
|
// overwrite user's specialisation info, assuming that the old specialisation info is not
|
|
// relevant for codegen (the only thing it would be used for)
|
|
specInfo = &patchedSpecInfo;
|
|
|
|
return true;
|
|
};
|
|
|
|
PrepareStateForPatchedShader(patchedBufferdata, modifiedstate, false, patchCallback);
|
|
|
|
if(!RunFeedbackAction(feedbackStorageSize, action, modifiedstate))
|
|
{
|
|
delete apiWrapper;
|
|
|
|
ShaderDebugTrace *ret = new ShaderDebugTrace;
|
|
ret->stage = ShaderStage::Vertex;
|
|
|
|
return ret;
|
|
}
|
|
|
|
bytebuf data;
|
|
GetDebugManager()->GetBufferData(m_PatchedShaderFeedback.FeedbackBuffer, 0, 0, data);
|
|
|
|
byte *base = data.data();
|
|
uint32_t hit_count = ((uint32_t *)base)[0];
|
|
// uint32_t total_count = ((uint32_t *)base)[1];
|
|
|
|
RDCASSERTMSG("Should only get one hit for vertex shaders", hit_count == 1, hit_count);
|
|
|
|
base += sizeof(Vec4f);
|
|
|
|
ResultDataBase *winner = (ResultDataBase *)base;
|
|
|
|
if(winner->valid != validMagicNumber)
|
|
{
|
|
RDCWARN("Hit doesn't have valid magic number");
|
|
|
|
delete apiWrapper;
|
|
|
|
ShaderDebugTrace *ret = new ShaderDebugTrace;
|
|
ret->stage = ShaderStage::Vertex;
|
|
|
|
return ret;
|
|
}
|
|
|
|
rdcspv::Debugger *debugger = new rdcspv::Debugger;
|
|
debugger->Parse(shader.spirv.GetSPIRV());
|
|
|
|
// the per-thread data immediately follows the ResultDataBase header. Every piece of data is
|
|
// uniformly aligned, either 16-byte by default or 32-byte if larger components exist. The
|
|
// output is in input signature order.
|
|
byte *LaneData = (byte *)(winner + 1);
|
|
|
|
numThreads = 4;
|
|
|
|
if(shadRefl.patchData.threadScope & rdcspv::ThreadScope::Subgroup)
|
|
{
|
|
RDCASSERTNOTEQUAL(winner->subgroupSize, 0);
|
|
numThreads = RDCMAX(numThreads, winner->subgroupSize);
|
|
}
|
|
|
|
apiWrapper->location_inputs.resize(numThreads);
|
|
apiWrapper->thread_builtins.resize(numThreads);
|
|
apiWrapper->thread_props.resize(numThreads);
|
|
|
|
for(uint32_t t = 0; t < numThreads; t++)
|
|
{
|
|
byte *value = LaneData + t * structStride;
|
|
|
|
{
|
|
SubgroupLaneData *subgroupData = (SubgroupLaneData *)value;
|
|
apiWrapper->thread_props[t][(size_t)rdcspv::ThreadProperty::Active] = subgroupData->isActive;
|
|
apiWrapper->thread_props[t][(size_t)rdcspv::ThreadProperty::Elected] = subgroupData->elect;
|
|
apiWrapper->thread_props[t][(size_t)rdcspv::ThreadProperty::SubgroupId] = t;
|
|
|
|
value += sizeof(SubgroupLaneData);
|
|
}
|
|
|
|
// read VertexLaneData
|
|
{
|
|
VertexLaneData *vertData = (VertexLaneData *)value;
|
|
|
|
apiWrapper->thread_builtins[t][ShaderBuiltin::InstanceIndex] =
|
|
ShaderVariable("InstanceIndex"_lit, vertData->inst, 0U, 0U, 0U);
|
|
apiWrapper->thread_builtins[t][ShaderBuiltin::VertexIndex] =
|
|
ShaderVariable("VertexIndex"_lit, vertData->vert, 0U, 0U, 0U);
|
|
apiWrapper->thread_builtins[t][ShaderBuiltin::MultiViewIndex] =
|
|
ShaderVariable("VertexIndex"_lit, vertData->view, 0U, 0U, 0U);
|
|
|
|
if(view != ~0U)
|
|
RDCASSERTEQUAL(vertData->view, view);
|
|
}
|
|
value += sizeof(VertexLaneData);
|
|
|
|
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[t].resize(
|
|
RDCMAX((uint32_t)apiWrapper->location_inputs.size(), param.regIndex + 1));
|
|
}
|
|
|
|
ShaderVariable &var = builtin ? apiWrapper->thread_builtins[t][param.systemValue]
|
|
: apiWrapper->location_inputs[t][param.regIndex];
|
|
|
|
var.rows = 1;
|
|
var.columns = param.compCount & 0xff;
|
|
var.type = param.varType;
|
|
|
|
const uint32_t comp = Bits::CountTrailingZeroes(uint32_t(param.regChannelMask));
|
|
const uint32_t elemSize = VarTypeByteSize(param.varType);
|
|
|
|
const size_t sz = elemSize * param.compCount;
|
|
|
|
memcpy((var.value.u8v.data()) + elemSize * comp, value + i * paramAlign, sz);
|
|
}
|
|
}
|
|
apiWrapper->global_builtins[ShaderBuiltin::SubgroupSize] =
|
|
ShaderVariable(rdcstr(), numThreads, 0U, 0U, 0U);
|
|
|
|
ShaderDebugTrace *ret = debugger->BeginDebug(apiWrapper, ShaderStage::Vertex, entryPoint, spec,
|
|
shadRefl.instructionLines, shadRefl.patchData,
|
|
winner->laneIndex, numThreads, numThreads);
|
|
apiWrapper->ResetReplay();
|
|
|
|
return ret;
|
|
}
|
|
else
|
|
{
|
|
// otherwise we can do a simple manual fetch of vertex inputs
|
|
laneIndex = 0;
|
|
|
|
std::unordered_map<ShaderBuiltin, ShaderVariable> &thread_builtins =
|
|
apiWrapper->thread_builtins[laneIndex];
|
|
if(action->flags & ActionFlags::Indexed)
|
|
thread_builtins[ShaderBuiltin::VertexIndex] = ShaderVariable(rdcstr(), idx, 0U, 0U, 0U);
|
|
else
|
|
thread_builtins[ShaderBuiltin::VertexIndex] =
|
|
ShaderVariable(rdcstr(), vertid + vertOffset, 0U, 0U, 0U);
|
|
thread_builtins[ShaderBuiltin::InstanceIndex] =
|
|
ShaderVariable(rdcstr(), instid + instOffset, 0U, 0U, 0U);
|
|
|
|
rdcarray<ShaderVariable> &locations = apiWrapper->location_inputs[laneIndex];
|
|
for(const VkVertexInputAttributeDescription2EXT &attr : state.vertexAttributes)
|
|
{
|
|
locations.resize_for_index(attr.location);
|
|
|
|
if(Vulkan_Debug_ShaderDebugLogging())
|
|
RDCLOG("Populating location %u", attr.location);
|
|
|
|
ShaderVariable &var = locations[attr.location];
|
|
|
|
bytebuf data;
|
|
|
|
size_t size = (size_t)GetByteSize(1, 1, 1, attr.format, 0);
|
|
|
|
bool found = false;
|
|
|
|
for(const VkVertexInputBindingDescription2EXT &bind : state.vertexBindings)
|
|
{
|
|
if(bind.binding != attr.binding)
|
|
continue;
|
|
|
|
if(bind.binding < state.vbuffers.size())
|
|
{
|
|
const VulkanRenderState::VertBuffer &vb = state.vbuffers[bind.binding];
|
|
|
|
if(vb.buf != ResourceId())
|
|
{
|
|
VkDeviceSize vertexOffset = 0;
|
|
|
|
found = true;
|
|
|
|
if(bind.inputRate == VK_VERTEX_INPUT_RATE_INSTANCE)
|
|
{
|
|
if(bind.divisor == 0)
|
|
vertexOffset = instOffset * vb.stride;
|
|
else
|
|
vertexOffset = (instOffset + (instid / bind.divisor)) * vb.stride;
|
|
}
|
|
else
|
|
{
|
|
vertexOffset = (idx + vertOffset) * vb.stride;
|
|
}
|
|
|
|
if(Vulkan_Debug_ShaderDebugLogging())
|
|
{
|
|
RDCLOG("Fetching from %s at %llu offset %zu bytes", ToStr(vb.buf).c_str(),
|
|
vb.offs + attr.offset + vertexOffset, size);
|
|
}
|
|
|
|
if(attr.offset + vertexOffset < vb.size)
|
|
GetDebugManager()->GetBufferData(vb.buf, vb.offs + attr.offset + vertexOffset, size,
|
|
data);
|
|
}
|
|
}
|
|
else if(Vulkan_Debug_ShaderDebugLogging())
|
|
{
|
|
RDCLOG("Vertex binding %u out of bounds from %zu vertex buffers", bind.binding,
|
|
state.vbuffers.size());
|
|
}
|
|
}
|
|
|
|
if(!found)
|
|
{
|
|
if(Vulkan_Debug_ShaderDebugLogging())
|
|
{
|
|
RDCLOG("Attribute binding %u out of bounds from %zu bindings", attr.binding,
|
|
pipe.vertexBindings.size());
|
|
}
|
|
}
|
|
|
|
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))
|
|
var.type = VarType::UInt;
|
|
else
|
|
var.type = VarType::Float;
|
|
|
|
set0001(var);
|
|
}
|
|
else
|
|
{
|
|
ResourceFormat fmt = MakeResourceFormat(attr.format);
|
|
|
|
// integer formats need to be read as-is, rather than converted to floats
|
|
if(fmt.compType == CompType::UInt || fmt.compType == CompType::SInt)
|
|
{
|
|
if(fmt.type == ResourceFormatType::R10G10B10A2)
|
|
{
|
|
// this is the only packed UINT format
|
|
Vec4u decoded = ConvertFromR10G10B10A2UInt(*(uint32_t *)data.data());
|
|
|
|
var.type = VarType::UInt;
|
|
|
|
setUintComp(var, 0, decoded.x);
|
|
setUintComp(var, 1, decoded.y);
|
|
setUintComp(var, 2, decoded.z);
|
|
setUintComp(var, 3, decoded.w);
|
|
}
|
|
else
|
|
{
|
|
var.type = VarType::UInt;
|
|
|
|
if(fmt.compType == CompType::UInt)
|
|
{
|
|
if(fmt.compByteWidth == 1)
|
|
var.type = VarType::UByte;
|
|
else if(fmt.compByteWidth == 2)
|
|
var.type = VarType::UShort;
|
|
else if(fmt.compByteWidth == 4)
|
|
var.type = VarType::UInt;
|
|
else if(fmt.compByteWidth == 8)
|
|
var.type = VarType::ULong;
|
|
}
|
|
else if(fmt.compType == CompType::SInt)
|
|
{
|
|
if(fmt.compByteWidth == 1)
|
|
var.type = VarType::SByte;
|
|
else if(fmt.compByteWidth == 2)
|
|
var.type = VarType::SShort;
|
|
else if(fmt.compByteWidth == 4)
|
|
var.type = VarType::SInt;
|
|
else if(fmt.compByteWidth == 8)
|
|
var.type = VarType::SLong;
|
|
}
|
|
|
|
RDCASSERTEQUAL(fmt.compByteWidth, VarTypeByteSize(var.type));
|
|
memcpy(var.value.u8v.data(), data.data(), fmt.compByteWidth * fmt.compCount);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FloatVector decoded = DecodeFormattedComponents(fmt, data.data());
|
|
|
|
var.type = VarType::Float;
|
|
|
|
setFloatComp(var, 0, decoded.x);
|
|
setFloatComp(var, 1, decoded.y);
|
|
setFloatComp(var, 2, decoded.z);
|
|
setFloatComp(var, 3, decoded.w);
|
|
}
|
|
}
|
|
}
|
|
|
|
rdcspv::Debugger *debugger = new rdcspv::Debugger;
|
|
debugger->Parse(shader.spirv.GetSPIRV());
|
|
|
|
apiWrapper->global_builtins[ShaderBuiltin::SubgroupSize] =
|
|
ShaderVariable(rdcstr(), numThreads, 0U, 0U, 0U);
|
|
|
|
ShaderDebugTrace *ret = debugger->BeginDebug(apiWrapper, ShaderStage::Vertex, entryPoint, spec,
|
|
shadRefl.instructionLines, shadRefl.patchData,
|
|
laneIndex, numThreads, numThreads);
|
|
apiWrapper->ResetReplay();
|
|
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
ShaderDebugTrace *VulkanReplay::DebugPixel(uint32_t eventId, uint32_t x, uint32_t y,
|
|
const DebugPixelInputs &inputs)
|
|
{
|
|
if(!m_pDriver->GetDeviceEnabledFeatures().fragmentStoresAndAtomics)
|
|
{
|
|
RDCWARN("Pixel debugging is not supported without fragment stores");
|
|
return new ShaderDebugTrace;
|
|
}
|
|
|
|
uint32_t sample = inputs.sample;
|
|
uint32_t primitive = inputs.primitive;
|
|
uint32_t view = inputs.view;
|
|
|
|
const VulkanRenderState &state = m_pDriver->GetRenderState();
|
|
VulkanCreationInfo &c = m_pDriver->m_CreationInfo;
|
|
|
|
rdcstr regionName = StringFormat::Fmt("DebugPixel @ %u of (%u,%u) sample %u primitive %u view %u",
|
|
eventId, x, y, sample, primitive, view);
|
|
|
|
VkMarkerRegion region(regionName);
|
|
|
|
if(Vulkan_Debug_ShaderDebugLogging())
|
|
RDCLOG("%s", regionName.c_str());
|
|
|
|
const ActionDescription *action = m_pDriver->GetAction(eventId);
|
|
|
|
if(!(action->flags & (ActionFlags::MeshDispatch | ActionFlags::Drawcall)))
|
|
{
|
|
RDCLOG("No drawcall selected");
|
|
return new ShaderDebugTrace();
|
|
}
|
|
|
|
const VulkanCreationInfo::Pipeline &pipe = c.m_Pipeline[state.graphics.pipeline];
|
|
ResourceId fragId = state.graphics.shaderObject ? state.shaderObjects[4] : pipe.shaders[4].module;
|
|
|
|
if(fragId == ResourceId())
|
|
{
|
|
RDCLOG("No pixel shader bound at draw");
|
|
return new ShaderDebugTrace();
|
|
}
|
|
|
|
// get ourselves in pristine state before this action (without any side effects it may have had)
|
|
m_pDriver->ReplayLog(0, eventId, eReplay_WithoutDraw);
|
|
|
|
const VulkanCreationInfo::ShaderEntry &fragEntry =
|
|
state.graphics.shaderObject ? c.m_ShaderObject[state.shaderObjects[4]].shad : pipe.shaders[4];
|
|
VulkanCreationInfo::ShaderModule &shader = c.m_ShaderModule[fragEntry.module];
|
|
rdcstr entryPoint = fragEntry.entryPoint;
|
|
const rdcarray<SpecConstant> &spec = fragEntry.specialization;
|
|
|
|
VulkanCreationInfo::ShaderModuleReflection &shadRefl =
|
|
shader.GetReflection(ShaderStage::Pixel, entryPoint, state.graphics.pipeline);
|
|
|
|
if(!shadRefl.refl->debugInfo.debuggable)
|
|
{
|
|
RDCLOG("Shader is not debuggable: %s", shadRefl.refl->debugInfo.debugStatus.c_str());
|
|
return new ShaderDebugTrace();
|
|
}
|
|
|
|
shadRefl.PopulateDisassembly(shader.spirv);
|
|
|
|
VulkanAPIWrapper *apiWrapper =
|
|
new VulkanAPIWrapper(m_pDriver, c, ShaderStage::Pixel, eventId, shadRefl.refl->resourceId);
|
|
|
|
SubgroupCapability subgroupCapability = SubgroupCapability::None;
|
|
uint32_t maxSubgroupSize = 1;
|
|
CalculateSubgroupProperties(maxSubgroupSize, subgroupCapability);
|
|
|
|
uint32_t numThreads = 4;
|
|
|
|
if(shadRefl.patchData.threadScope & rdcspv::ThreadScope::Subgroup)
|
|
numThreads = RDCMAX(numThreads, maxSubgroupSize);
|
|
|
|
std::unordered_map<ShaderBuiltin, ShaderVariable> &global_builtins = apiWrapper->global_builtins;
|
|
global_builtins[ShaderBuiltin::DeviceIndex] = ShaderVariable(rdcstr(), 0U, 0U, 0U, 0U);
|
|
global_builtins[ShaderBuiltin::DrawIndex] = ShaderVariable(rdcstr(), action->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;
|
|
ResourceId gsId = state.graphics.shaderObject ? state.shaderObjects[3] : pipe.shaders[3].module;
|
|
if(gsId != ResourceId())
|
|
{
|
|
const VulkanCreationInfo::ShaderEntry &gsEntry =
|
|
state.graphics.shaderObject ? c.m_ShaderObject[state.shaderObjects[3]].shad : pipe.shaders[3];
|
|
VulkanCreationInfo::ShaderModuleReflection &gsRefl =
|
|
c.m_ShaderModule[gsEntry.module].GetReflection(ShaderStage::Geometry, gsEntry.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)
|
|
{
|
|
if(Vulkan_Debug_ShaderDebugLogging())
|
|
{
|
|
RDCLOG("Geometry shader exports primitive ID, can use");
|
|
}
|
|
|
|
usePrimitiveID = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(Vulkan_Debug_ShaderDebugLogging())
|
|
{
|
|
if(!usePrimitiveID)
|
|
RDCLOG("Geometry shader doesn't export primitive ID, can't use");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// no geometry shader - safe to use as long as the geometry shader capability is available
|
|
usePrimitiveID = m_pDriver->GetDeviceEnabledFeatures().geometryShader != VK_FALSE;
|
|
|
|
if(Vulkan_Debug_ShaderDebugLogging())
|
|
{
|
|
RDCLOG("usePrimitiveID is %u because of bare capability", usePrimitiveID);
|
|
}
|
|
}
|
|
|
|
bool useSampleID = m_pDriver->GetDeviceEnabledFeatures().sampleRateShading != VK_FALSE;
|
|
|
|
if(Vulkan_Debug_ShaderDebugLogging())
|
|
{
|
|
RDCLOG("useSampleID is %u because of bare capability", useSampleID);
|
|
}
|
|
|
|
bool useViewIndex = (view == ~0U) ? false : true;
|
|
if(useViewIndex)
|
|
{
|
|
ResourceId rp = state.GetRenderPass();
|
|
if(rp != ResourceId())
|
|
{
|
|
const VulkanCreationInfo::RenderPass &rpInfo = GetDebugManager()->GetRenderPassInfo(rp);
|
|
for(auto it = rpInfo.subpasses.begin(); it != rpInfo.subpasses.end(); ++it)
|
|
{
|
|
if(it->multiviews.isEmpty())
|
|
{
|
|
if(Vulkan_Debug_ShaderDebugLogging())
|
|
RDCLOG(
|
|
"Disabling useViewIndex because at least one subpass does not have multiple views");
|
|
useViewIndex = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
useViewIndex =
|
|
(state.dynamicRendering.active ? state.dynamicRendering.viewMask : pipe.viewMask) != 0;
|
|
if(!useViewIndex && Vulkan_Debug_ShaderDebugLogging())
|
|
RDCLOG("Disabling useViewIndex because viewMask is zero");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(Vulkan_Debug_ShaderDebugLogging())
|
|
RDCLOG("Disabling useViewIndex from input view %u", view);
|
|
}
|
|
if(useViewIndex)
|
|
{
|
|
global_builtins[ShaderBuiltin::MultiViewIndex] = ShaderVariable(rdcstr(), view, 0U, 0U, 0U);
|
|
}
|
|
|
|
uint32_t paramAlign, structStride;
|
|
rdctie(paramAlign, structStride) = GetAlignAndOutputSize(shadRefl);
|
|
|
|
uint32_t overdrawLevels = 100; // maximum number of overdraw levels
|
|
|
|
// struct size is ResultDataBase header plus Nx structStride for the number of threads
|
|
uint32_t structSize = sizeof(ResultDataBase) + structStride * numThreads;
|
|
|
|
VkDeviceSize feedbackStorageSize = overdrawLevels * structSize + sizeof(Vec4f) + 1024;
|
|
|
|
if(Vulkan_Debug_ShaderDebugLogging())
|
|
{
|
|
RDCLOG("Output structure is %u sized, output buffer is %llu bytes", structStride,
|
|
feedbackStorageSize);
|
|
}
|
|
|
|
m_PatchedShaderFeedback.ResizeFeedbackBuffer(m_pDriver, feedbackStorageSize);
|
|
|
|
SpecData specData = {};
|
|
|
|
specData.arrayLength = overdrawLevels;
|
|
specData.destX = float(x) + 0.5f;
|
|
specData.destY = float(y) + 0.5f;
|
|
|
|
// make copy of state to draw from
|
|
VulkanRenderState modifiedstate = state;
|
|
|
|
AddedDescriptorData patchedBufferdata = PrepareExtraBufferDescriptor(
|
|
modifiedstate, false, {MakeNewBinding(VK_SHADER_STAGE_FRAGMENT_BIT)}, false);
|
|
|
|
if(patchedBufferdata.empty())
|
|
{
|
|
delete apiWrapper;
|
|
|
|
ShaderDebugTrace *ret = new ShaderDebugTrace;
|
|
ret->stage = ShaderStage::Pixel;
|
|
|
|
return ret;
|
|
}
|
|
|
|
if(!patchedBufferdata.descSets.empty())
|
|
m_PatchedShaderFeedback.FeedbackBuffer.WriteDescriptor(Unwrap(patchedBufferdata.descSets[0]), 0,
|
|
0);
|
|
|
|
specData.bufferAddress = m_PatchedShaderFeedback.FeedbackBuffer.Address();
|
|
if(specData.bufferAddress && Vulkan_Debug_ShaderDebugLogging())
|
|
{
|
|
RDCLOG("Got buffer address of %llu", specData.bufferAddress);
|
|
}
|
|
|
|
// create shader with modified code
|
|
|
|
VkSpecializationMapEntry specMaps[(size_t)InputSpecConstant::Count];
|
|
RDCCOMPILE_ASSERT(sizeof(specMaps) == sizeof(specMapsTemplate),
|
|
"Specialisation maps have changed");
|
|
|
|
VkSpecializationInfo patchedSpecInfo = MakeSpecInfo(specData, specMaps);
|
|
|
|
auto patchCallback = [this, &shadRefl, &patchedSpecInfo, usePrimitiveID, useSampleID,
|
|
useViewIndex, subgroupCapability, maxSubgroupSize](
|
|
const AddedDescriptorData &patchedBufferdata, VkShaderStageFlagBits stage,
|
|
const char *entryName, const rdcarray<uint32_t> &origSpirv,
|
|
rdcarray<uint32_t> &modSpirv, const VkSpecializationInfo *&specInfo) {
|
|
if(stage != VK_SHADER_STAGE_FRAGMENT_BIT)
|
|
return false;
|
|
|
|
modSpirv = origSpirv;
|
|
|
|
if(!Vulkan_Debug_PSDebugDumpDirPath().empty())
|
|
FileIO::WriteAll(Vulkan_Debug_PSDebugDumpDirPath() + "/debug_psinput_before.spv", modSpirv);
|
|
|
|
CreateInputFetcher(modSpirv, shadRefl, m_StorageMode, usePrimitiveID, useSampleID, useViewIndex,
|
|
subgroupCapability, maxSubgroupSize);
|
|
|
|
if(!Vulkan_Debug_PSDebugDumpDirPath().empty())
|
|
FileIO::WriteAll(Vulkan_Debug_PSDebugDumpDirPath() + "/debug_psinput_after.spv", modSpirv);
|
|
|
|
// overwrite user's specialisation info, assuming that the old specialisation info is not
|
|
// relevant for codegen (the only thing it would be used for)
|
|
specInfo = &patchedSpecInfo;
|
|
|
|
return true;
|
|
};
|
|
|
|
PrepareStateForPatchedShader(patchedBufferdata, modifiedstate, false, patchCallback);
|
|
|
|
if(!RunFeedbackAction(feedbackStorageSize, action, modifiedstate))
|
|
{
|
|
delete apiWrapper;
|
|
|
|
ShaderDebugTrace *ret = new ShaderDebugTrace;
|
|
ret->stage = ShaderStage::Pixel;
|
|
|
|
return ret;
|
|
}
|
|
|
|
bytebuf data;
|
|
GetDebugManager()->GetBufferData(m_PatchedShaderFeedback.FeedbackBuffer, 0, 0, data);
|
|
|
|
byte *base = data.data();
|
|
uint32_t hit_count = ((uint32_t *)base)[0];
|
|
uint32_t total_count = ((uint32_t *)base)[1];
|
|
|
|
if(hit_count > overdrawLevels)
|
|
{
|
|
RDCERR("%u hits, more than max overdraw levels allowed %u. Clamping", hit_count, overdrawLevels);
|
|
hit_count = overdrawLevels;
|
|
}
|
|
|
|
base += sizeof(Vec4f);
|
|
|
|
ResultDataBase *winner = NULL;
|
|
|
|
RDCLOG("Got %u hit candidates out of %u total instances", hit_count, total_count);
|
|
|
|
// 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
|
|
|
|
VkCompareOp depthOp = state.depthCompareOp;
|
|
|
|
// depth tests disabled acts the same as always compare mode
|
|
if(!state.depthTestEnable)
|
|
depthOp = VK_COMPARE_OP_ALWAYS;
|
|
|
|
for(uint32_t i = 0; i < hit_count; i++)
|
|
{
|
|
ResultDataBase *hit = (ResultDataBase *)(base + structSize * i);
|
|
|
|
if(hit->valid != validMagicNumber)
|
|
{
|
|
RDCWARN("Hit %u doesn't have valid magic number", i);
|
|
continue;
|
|
}
|
|
|
|
if(hit->ddxDerivCheck != 1.0f)
|
|
{
|
|
RDCWARN("Hit %u doesn't have valid derivatives", i);
|
|
continue;
|
|
}
|
|
|
|
// if we're looking for a specific view, ignore hits from the wrong view
|
|
if(useViewIndex)
|
|
{
|
|
if(hit->view != view)
|
|
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 per-thread data immediately follows the ResultDataBase header. Every piece of data is
|
|
// uniformly aligned, either 16-byte by default or 32-byte if larger components exist. The
|
|
// output is in input signature order.
|
|
byte *LaneData = (byte *)(winner + 1);
|
|
|
|
numThreads = 4;
|
|
|
|
if(shadRefl.patchData.threadScope & rdcspv::ThreadScope::Subgroup)
|
|
{
|
|
RDCASSERTNOTEQUAL(winner->subgroupSize, 0);
|
|
numThreads = RDCMAX(numThreads, winner->subgroupSize);
|
|
}
|
|
|
|
apiWrapper->location_inputs.resize(numThreads);
|
|
apiWrapper->thread_builtins.resize(numThreads);
|
|
apiWrapper->thread_props.resize(numThreads);
|
|
|
|
for(uint32_t t = 0; t < numThreads; t++)
|
|
{
|
|
byte *value = LaneData + t * structStride;
|
|
|
|
if(shadRefl.patchData.threadScope & rdcspv::ThreadScope::Subgroup)
|
|
{
|
|
SubgroupLaneData *subgroupData = (SubgroupLaneData *)value;
|
|
apiWrapper->thread_props[t][(size_t)rdcspv::ThreadProperty::Active] = subgroupData->isActive;
|
|
apiWrapper->thread_props[t][(size_t)rdcspv::ThreadProperty::Elected] = subgroupData->elect;
|
|
apiWrapper->thread_props[t][(size_t)rdcspv::ThreadProperty::SubgroupId] = t;
|
|
|
|
value += sizeof(SubgroupLaneData);
|
|
}
|
|
|
|
// read PixelLaneData
|
|
{
|
|
PixelLaneData *pixelData = (PixelLaneData *)value;
|
|
|
|
{
|
|
ShaderVariable &var = apiWrapper->thread_builtins[t][ShaderBuiltin::Position];
|
|
|
|
var.rows = 1;
|
|
var.columns = 4;
|
|
var.type = VarType::Float;
|
|
|
|
memcpy(var.value.u8v.data(), &pixelData->fragCoord, sizeof(Vec4f));
|
|
}
|
|
|
|
{
|
|
ShaderVariable &var = apiWrapper->thread_builtins[t][ShaderBuiltin::IsHelper];
|
|
|
|
var.rows = 1;
|
|
var.columns = 1;
|
|
var.type = VarType::Bool;
|
|
|
|
memcpy(var.value.u8v.data(), &pixelData->isHelper, sizeof(uint32_t));
|
|
}
|
|
|
|
if(numThreads == 4)
|
|
apiWrapper->thread_props[t][(size_t)rdcspv::ThreadProperty::Active] = 1;
|
|
apiWrapper->thread_props[t][(size_t)rdcspv::ThreadProperty::Helper] = pixelData->isHelper;
|
|
apiWrapper->thread_props[t][(size_t)rdcspv::ThreadProperty::QuadId] = pixelData->quadId;
|
|
apiWrapper->thread_props[t][(size_t)rdcspv::ThreadProperty::QuadLane] =
|
|
pixelData->quadLaneIndex;
|
|
}
|
|
value += sizeof(PixelLaneData);
|
|
|
|
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[t].resize(
|
|
RDCMAX((uint32_t)apiWrapper->location_inputs.size(), param.regIndex + 1));
|
|
}
|
|
|
|
ShaderVariable &var = builtin ? apiWrapper->thread_builtins[t][param.systemValue]
|
|
: apiWrapper->location_inputs[t][param.regIndex];
|
|
|
|
var.rows = 1;
|
|
var.columns = param.compCount & 0xff;
|
|
var.type = param.varType;
|
|
|
|
const uint32_t firstComp = Bits::CountTrailingZeroes(uint32_t(param.regChannelMask));
|
|
const uint32_t elemSize = VarTypeByteSize(param.varType);
|
|
|
|
// we always store in 32-bit types
|
|
const size_t sz = RDCMAX(4U, elemSize) * param.compCount;
|
|
|
|
memcpy((var.value.u8v.data()) + elemSize * firstComp, value + i * paramAlign, sz);
|
|
|
|
// convert down from stored 32-bit types if they were smaller
|
|
if(elemSize == 1)
|
|
{
|
|
ShaderVariable tmp = var;
|
|
|
|
for(uint32_t comp = 0; comp < param.compCount; comp++)
|
|
var.value.u8v[comp] = tmp.value.u32v[comp] & 0xff;
|
|
}
|
|
else if(elemSize == 2)
|
|
{
|
|
ShaderVariable tmp = var;
|
|
|
|
for(uint32_t comp = 0; comp < param.compCount; comp++)
|
|
{
|
|
if(VarTypeCompType(param.varType) == CompType::Float)
|
|
var.value.f16v[comp] = rdhalf::make(tmp.value.f32v[comp]);
|
|
else
|
|
var.value.u16v[comp] = tmp.value.u32v[comp] & 0xffff;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
apiWrapper->global_builtins[ShaderBuiltin::SubgroupSize] =
|
|
ShaderVariable(rdcstr(), numThreads, 0U, 0U, 0U);
|
|
|
|
ret = debugger->BeginDebug(apiWrapper, ShaderStage::Pixel, entryPoint, spec,
|
|
shadRefl.instructionLines, shadRefl.patchData, winner->laneIndex,
|
|
numThreads, numThreads);
|
|
apiWrapper->ResetReplay();
|
|
}
|
|
else
|
|
{
|
|
RDCLOG("Didn't get any valid hit to debug");
|
|
delete apiWrapper;
|
|
|
|
ret = new ShaderDebugTrace;
|
|
ret->stage = ShaderStage::Pixel;
|
|
}
|
|
|
|
patchedBufferdata.Free();
|
|
return ret;
|
|
}
|
|
|
|
ShaderDebugTrace *VulkanReplay::DebugThread(uint32_t eventId,
|
|
const rdcfixedarray<uint32_t, 3> &groupid,
|
|
const rdcfixedarray<uint32_t, 3> &threadid)
|
|
{
|
|
return DebugComputeCommon(ShaderStage::Compute, eventId, groupid, threadid);
|
|
}
|
|
|
|
ShaderDebugTrace *VulkanReplay::DebugMeshThread(uint32_t eventId,
|
|
const rdcfixedarray<uint32_t, 3> &groupid,
|
|
const rdcfixedarray<uint32_t, 3> &threadid)
|
|
{
|
|
return DebugComputeCommon(ShaderStage::Mesh, eventId, groupid, threadid);
|
|
}
|
|
|
|
ShaderDebugTrace *VulkanReplay::DebugComputeCommon(ShaderStage stage, uint32_t eventId,
|
|
const rdcfixedarray<uint32_t, 3> &groupid,
|
|
const rdcfixedarray<uint32_t, 3> &threadid)
|
|
{
|
|
const VulkanRenderState &state = m_pDriver->GetRenderState();
|
|
VulkanCreationInfo &c = m_pDriver->m_CreationInfo;
|
|
|
|
rdcstr regionName =
|
|
StringFormat::Fmt("Debug %s @ %u of (%u,%u,%u) (%u,%u,%u)", ToStr(stage).c_str(), eventId,
|
|
groupid[0], groupid[1], groupid[2], threadid[0], threadid[1], threadid[2]);
|
|
|
|
VkMarkerRegion region(regionName);
|
|
|
|
if(Vulkan_Debug_ShaderDebugLogging())
|
|
RDCLOG("%s", regionName.c_str());
|
|
|
|
const ActionDescription *action = m_pDriver->GetAction(eventId);
|
|
|
|
if(stage == ShaderStage::Compute)
|
|
{
|
|
if(!(action->flags & ActionFlags::Dispatch))
|
|
{
|
|
RDCLOG("No dispatch selected");
|
|
return new ShaderDebugTrace();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(!(action->flags & ActionFlags::MeshDispatch))
|
|
{
|
|
RDCLOG("No dispatch selected");
|
|
return new ShaderDebugTrace();
|
|
}
|
|
}
|
|
|
|
// get ourselves in pristine state before this dispatch (without any side effects it may have had)
|
|
m_pDriver->ReplayLog(0, eventId, eReplay_WithoutDraw);
|
|
|
|
const VulkanStatePipeline &stagePipeState =
|
|
stage == ShaderStage::Compute ? state.compute : state.graphics;
|
|
const VulkanCreationInfo::Pipeline &pipe = c.m_Pipeline[stagePipeState.pipeline];
|
|
const VulkanCreationInfo::ShaderEntry &shaderEntry =
|
|
stagePipeState.shaderObject ? c.m_ShaderObject[state.shaderObjects[(size_t)stage]].shad
|
|
: pipe.shaders[(size_t)stage];
|
|
VulkanCreationInfo::ShaderModule &shader = c.m_ShaderModule[shaderEntry.module];
|
|
rdcstr entryPoint = shaderEntry.entryPoint;
|
|
const rdcarray<SpecConstant> &spec = shaderEntry.specialization;
|
|
|
|
VulkanCreationInfo::ShaderModuleReflection &shadRefl =
|
|
shader.GetReflection(stage, entryPoint, stagePipeState.pipeline);
|
|
|
|
if(!shadRefl.refl->debugInfo.debuggable)
|
|
{
|
|
RDCLOG("Shader is not debuggable: %s", shadRefl.refl->debugInfo.debugStatus.c_str());
|
|
return new ShaderDebugTrace();
|
|
}
|
|
|
|
shadRefl.PopulateDisassembly(shader.spirv);
|
|
|
|
VulkanAPIWrapper *apiWrapper =
|
|
new VulkanAPIWrapper(m_pDriver, c, stage, eventId, shadRefl.refl->resourceId);
|
|
|
|
uint32_t threadDim[3];
|
|
threadDim[0] = shadRefl.refl->dispatchThreadsDimension[0];
|
|
threadDim[1] = shadRefl.refl->dispatchThreadsDimension[1];
|
|
threadDim[2] = shadRefl.refl->dispatchThreadsDimension[2];
|
|
|
|
SubgroupCapability subgroupCapability = SubgroupCapability::None;
|
|
uint32_t maxSubgroupSize = 1;
|
|
CalculateSubgroupProperties(maxSubgroupSize, subgroupCapability);
|
|
|
|
uint32_t numThreads = 1;
|
|
|
|
if(shadRefl.patchData.threadScope & rdcspv::ThreadScope::Subgroup)
|
|
numThreads = RDCMAX(numThreads, maxSubgroupSize);
|
|
if(shadRefl.patchData.threadScope & rdcspv::ThreadScope::Workgroup)
|
|
{
|
|
if(Vulkan_Hack_EnableGroupCaps())
|
|
numThreads = RDCMAX(numThreads, threadDim[0] * threadDim[1] * threadDim[2]);
|
|
}
|
|
|
|
apiWrapper->thread_builtins.resize(numThreads);
|
|
apiWrapper->thread_props.resize(numThreads);
|
|
|
|
std::unordered_map<ShaderBuiltin, ShaderVariable> &global_builtins = apiWrapper->global_builtins;
|
|
global_builtins[ShaderBuiltin::DispatchSize] =
|
|
ShaderVariable(rdcstr(), action->dispatchDimension[0], action->dispatchDimension[1],
|
|
action->dispatchDimension[2], 0U);
|
|
global_builtins[ShaderBuiltin::GroupSize] =
|
|
ShaderVariable(rdcstr(), threadDim[0], threadDim[1], threadDim[2], 0U);
|
|
global_builtins[ShaderBuiltin::DeviceIndex] = ShaderVariable(rdcstr(), 0U, 0U, 0U, 0U);
|
|
global_builtins[ShaderBuiltin::GroupIndex] =
|
|
ShaderVariable(rdcstr(), groupid[0], groupid[1], groupid[2], 0U);
|
|
|
|
// if we need to fetch subgroup data, do that now
|
|
uint32_t laneIndex = 0;
|
|
if(shadRefl.patchData.threadScope & rdcspv::ThreadScope::Subgroup)
|
|
{
|
|
SpecData specData = {};
|
|
|
|
specData.globalThreadIdX = groupid[0] * threadDim[0] + threadid[0];
|
|
specData.globalThreadIdY = groupid[1] * threadDim[1] + threadid[1];
|
|
specData.globalThreadIdZ = groupid[2] * threadDim[2] + threadid[2];
|
|
|
|
uint32_t paramAlign, structStride;
|
|
rdctie(paramAlign, structStride) = GetAlignAndOutputSize(shadRefl);
|
|
|
|
uint32_t maxHits = 4; // we should only ever get one hit
|
|
|
|
// struct size is ResultDataBase header plus Nx structStride for the number of threads
|
|
uint32_t structSize = sizeof(ResultDataBase) + structStride * maxSubgroupSize;
|
|
|
|
VkDeviceSize feedbackStorageSize = maxHits * structSize + 1024;
|
|
|
|
if(Vulkan_Debug_ShaderDebugLogging())
|
|
{
|
|
RDCLOG("Output structure is %u sized, output buffer is %llu bytes", structStride,
|
|
feedbackStorageSize);
|
|
}
|
|
|
|
m_PatchedShaderFeedback.ResizeFeedbackBuffer(m_pDriver, feedbackStorageSize);
|
|
|
|
specData.arrayLength = maxHits;
|
|
|
|
// make copy of state to draw from
|
|
VulkanRenderState modifiedstate = state;
|
|
|
|
VkShaderStageFlagBits stageBit = (VkShaderStageFlagBits)ShaderMaskFromIndex(shadRefl.stageIndex);
|
|
AddedDescriptorData patchedBufferdata = PrepareExtraBufferDescriptor(
|
|
modifiedstate, stage == ShaderStage::Compute, {MakeNewBinding(stageBit)}, false);
|
|
|
|
if(patchedBufferdata.empty())
|
|
{
|
|
delete apiWrapper;
|
|
|
|
ShaderDebugTrace *ret = new ShaderDebugTrace;
|
|
ret->stage = stage;
|
|
|
|
return ret;
|
|
}
|
|
|
|
if(!patchedBufferdata.descSets.empty())
|
|
m_PatchedShaderFeedback.FeedbackBuffer.WriteDescriptor(Unwrap(patchedBufferdata.descSets[0]),
|
|
0, 0);
|
|
|
|
specData.bufferAddress = m_PatchedShaderFeedback.FeedbackBuffer.Address();
|
|
if(specData.bufferAddress && Vulkan_Debug_ShaderDebugLogging())
|
|
{
|
|
RDCLOG("Got buffer address of %llu", specData.bufferAddress);
|
|
}
|
|
|
|
// create shader with modified code
|
|
|
|
VkSpecializationMapEntry specMaps[(size_t)InputSpecConstant::Count];
|
|
RDCCOMPILE_ASSERT(sizeof(specMaps) == sizeof(specMapsTemplate),
|
|
"Specialisation maps have changed");
|
|
|
|
VkSpecializationInfo patchedSpecInfo = MakeSpecInfo(specData, specMaps);
|
|
|
|
auto patchCallback = [this, stageBit, &shadRefl, &patchedSpecInfo, subgroupCapability,
|
|
maxSubgroupSize](const AddedDescriptorData &patchedBufferdata,
|
|
VkShaderStageFlagBits stage, const char *entryName,
|
|
const rdcarray<uint32_t> &origSpirv,
|
|
rdcarray<uint32_t> &modSpirv,
|
|
const VkSpecializationInfo *&specInfo) {
|
|
if(stage != stageBit)
|
|
return false;
|
|
|
|
modSpirv = origSpirv;
|
|
|
|
uint32_t idx = shadRefl.stageIndex;
|
|
|
|
static const rdcstr filename[NumShaderStages] = {
|
|
"shadinput_vertex.spv", "shadinput_hull.spv", "shadinput_domain.spv",
|
|
"shadinput_geometry.spv", "shadinput_pixel.spv", "shadinput_compute.spv",
|
|
"shadinput_task.spv", "shadinput_mesh.spv",
|
|
};
|
|
|
|
if(!Vulkan_Debug_PSDebugDumpDirPath().empty())
|
|
FileIO::WriteAll(Vulkan_Debug_PSDebugDumpDirPath() + "/before_" + filename[idx], modSpirv);
|
|
|
|
CreateInputFetcher(modSpirv, shadRefl, m_StorageMode, false, false, false, subgroupCapability,
|
|
maxSubgroupSize);
|
|
|
|
if(!Vulkan_Debug_PSDebugDumpDirPath().empty())
|
|
FileIO::WriteAll(Vulkan_Debug_PSDebugDumpDirPath() + "/after_" + filename[idx], modSpirv);
|
|
|
|
// overwrite user's specialisation info, assuming that the old specialisation info is not
|
|
// relevant for codegen (the only thing it would be used for)
|
|
specInfo = &patchedSpecInfo;
|
|
|
|
return true;
|
|
};
|
|
|
|
PrepareStateForPatchedShader(patchedBufferdata, modifiedstate, stage == ShaderStage::Compute,
|
|
patchCallback);
|
|
|
|
if(!RunFeedbackAction(feedbackStorageSize, action, modifiedstate))
|
|
{
|
|
delete apiWrapper;
|
|
|
|
ShaderDebugTrace *ret = new ShaderDebugTrace;
|
|
ret->stage = stage;
|
|
|
|
return ret;
|
|
}
|
|
|
|
bytebuf data;
|
|
GetDebugManager()->GetBufferData(m_PatchedShaderFeedback.FeedbackBuffer, 0, 0, data);
|
|
|
|
byte *base = data.data();
|
|
uint32_t hit_count = ((uint32_t *)base)[0];
|
|
// uint32_t total_count = ((uint32_t *)base)[1];
|
|
|
|
if(hit_count > maxHits)
|
|
{
|
|
RDCERR("%u hits, more than max overdraw levels allowed %u. Clamping", hit_count, maxHits);
|
|
hit_count = maxHits;
|
|
}
|
|
|
|
base += sizeof(Vec4f);
|
|
|
|
ResultDataBase *winner = (ResultDataBase *)base;
|
|
|
|
if(winner->valid != validMagicNumber)
|
|
{
|
|
RDCWARN("Hit doesn't have valid magic number");
|
|
|
|
delete apiWrapper;
|
|
|
|
ShaderDebugTrace *ret = new ShaderDebugTrace;
|
|
ret->stage = stage;
|
|
|
|
return ret;
|
|
}
|
|
|
|
rdcspv::Debugger *debugger = new rdcspv::Debugger;
|
|
debugger->Parse(shader.spirv.GetSPIRV());
|
|
|
|
// the per-thread data immediately follows the ResultDataBase header. Every piece of data is
|
|
// uniformly aligned, either 16-byte by default or 32-byte if larger components exist. The
|
|
// output is in input signature order.
|
|
byte *LaneData = (byte *)(winner + 1);
|
|
|
|
numThreads = 4;
|
|
const uint32_t subgroupSize = winner->subgroupSize;
|
|
|
|
if(shadRefl.patchData.threadScope & rdcspv::ThreadScope::Subgroup)
|
|
{
|
|
RDCASSERTNOTEQUAL(subgroupSize, 0);
|
|
numThreads = RDCMAX(numThreads, subgroupSize);
|
|
}
|
|
|
|
if(shadRefl.patchData.threadScope & rdcspv::ThreadScope::Workgroup)
|
|
{
|
|
numThreads = RDCMAX(numThreads, threadDim[0] * threadDim[1] * threadDim[2]);
|
|
}
|
|
|
|
apiWrapper->global_builtins[ShaderBuiltin::NumSubgroups] =
|
|
ShaderVariable(rdcstr(), winner->numSubgroups, 0U, 0U, 0U);
|
|
|
|
apiWrapper->location_inputs.resize(numThreads);
|
|
apiWrapper->thread_builtins.resize(numThreads);
|
|
apiWrapper->thread_props.resize(numThreads);
|
|
|
|
laneIndex = ~0U;
|
|
|
|
for(uint32_t t = 0; t < subgroupSize; t++)
|
|
{
|
|
byte *value = LaneData + t * structStride;
|
|
|
|
SubgroupLaneData *subgroupData = (SubgroupLaneData *)value;
|
|
value += sizeof(SubgroupLaneData);
|
|
|
|
ComputeLaneData *compData = (ComputeLaneData *)value;
|
|
value += sizeof(ComputeLaneData);
|
|
|
|
// should we try to verify that the GPU assigned subgroups as we expect? this assumes tightly wrapped subgroups
|
|
uint32_t lane = t;
|
|
|
|
if(shadRefl.patchData.threadScope & rdcspv::ThreadScope::Workgroup)
|
|
{
|
|
lane = compData->threadid[2] * threadDim[0] * threadDim[1] +
|
|
compData->threadid[1] * threadDim[0] + compData->threadid[0];
|
|
}
|
|
|
|
if(rdcfixedarray<uint32_t, 3>(compData->threadid) == threadid && subgroupData->isActive)
|
|
laneIndex = lane;
|
|
|
|
apiWrapper->thread_props[lane][(size_t)rdcspv::ThreadProperty::Active] = subgroupData->isActive;
|
|
apiWrapper->thread_props[lane][(size_t)rdcspv::ThreadProperty::Elected] = subgroupData->elect;
|
|
apiWrapper->thread_props[lane][(size_t)rdcspv::ThreadProperty::SubgroupId] = t;
|
|
|
|
apiWrapper->thread_builtins[lane][ShaderBuiltin::DispatchThreadIndex] =
|
|
ShaderVariable(rdcstr(), groupid[0] * threadDim[0] + compData->threadid[0],
|
|
groupid[1] * threadDim[1] + compData->threadid[1],
|
|
groupid[2] * threadDim[2] + compData->threadid[2], 0U);
|
|
apiWrapper->thread_builtins[lane][ShaderBuiltin::GroupThreadIndex] = ShaderVariable(
|
|
rdcstr(), compData->threadid[0], compData->threadid[1], compData->threadid[2], 0U);
|
|
apiWrapper->thread_builtins[lane][ShaderBuiltin::GroupFlatIndex] =
|
|
ShaderVariable(rdcstr(),
|
|
compData->threadid[2] * threadDim[0] * threadDim[1] +
|
|
compData->threadid[1] * threadDim[0] + compData->threadid[0],
|
|
0U, 0U, 0U);
|
|
apiWrapper->thread_builtins[lane][ShaderBuiltin::IndexInSubgroup] =
|
|
ShaderVariable(rdcstr(), t, 0U, 0U, 0U);
|
|
apiWrapper->thread_builtins[lane][ShaderBuiltin::SubgroupIndexInWorkgroup] =
|
|
ShaderVariable(rdcstr(), compData->subIdxInGroup, 0U, 0U, 0U);
|
|
}
|
|
|
|
if(laneIndex == ~0U)
|
|
{
|
|
RDCERR("Didn't find desired lane in subgroup data");
|
|
laneIndex = 0;
|
|
}
|
|
|
|
// if we're simulating the whole workgroup we need to fill in the thread IDs of other threads
|
|
if(shadRefl.patchData.threadScope & rdcspv::ThreadScope::Workgroup)
|
|
{
|
|
uint32_t i = 0;
|
|
for(uint32_t tz = 0; tz < threadDim[2]; tz++)
|
|
{
|
|
for(uint32_t ty = 0; ty < threadDim[1]; ty++)
|
|
{
|
|
for(uint32_t tx = 0; tx < threadDim[0]; tx++)
|
|
{
|
|
std::unordered_map<ShaderBuiltin, ShaderVariable> &thread_builtins =
|
|
apiWrapper->thread_builtins[i];
|
|
|
|
thread_builtins[ShaderBuiltin::GroupThreadIndex] =
|
|
ShaderVariable(rdcstr(), tx, ty, tz, 0U);
|
|
thread_builtins[ShaderBuiltin::GroupFlatIndex] = ShaderVariable(
|
|
rdcstr(), tz * threadDim[0] * threadDim[1] + ty * threadDim[0] + tx, 0U, 0U, 0U);
|
|
|
|
if(apiWrapper->thread_props[i][(size_t)rdcspv::ThreadProperty::Active])
|
|
{
|
|
// assert that this is the thread we expect it to be
|
|
RDCASSERTEQUAL(thread_builtins[ShaderBuiltin::DispatchThreadIndex].value.u32v[0],
|
|
groupid[0] * threadDim[0] + tx);
|
|
RDCASSERTEQUAL(thread_builtins[ShaderBuiltin::DispatchThreadIndex].value.u32v[1],
|
|
groupid[1] * threadDim[1] + ty);
|
|
RDCASSERTEQUAL(thread_builtins[ShaderBuiltin::DispatchThreadIndex].value.u32v[2],
|
|
groupid[2] * threadDim[2] + tz);
|
|
|
|
RDCASSERTEQUAL(thread_builtins[ShaderBuiltin::IndexInSubgroup].value.u32v[0],
|
|
i % subgroupSize);
|
|
RDCASSERTEQUAL(thread_builtins[ShaderBuiltin::SubgroupIndexInWorkgroup].value.u32v[0],
|
|
i / subgroupSize);
|
|
}
|
|
else
|
|
{
|
|
thread_builtins[ShaderBuiltin::DispatchThreadIndex] =
|
|
ShaderVariable(rdcstr(), groupid[0] * threadDim[0] + tx,
|
|
groupid[1] * threadDim[1] + ty, groupid[2] * threadDim[2] + tz, 0U);
|
|
// tightly wrap subgroups, this is likely not how the GPU actually assigns them
|
|
thread_builtins[ShaderBuiltin::IndexInSubgroup] =
|
|
ShaderVariable(rdcstr(), i % subgroupSize, 0U, 0U, 0U);
|
|
thread_builtins[ShaderBuiltin::SubgroupIndexInWorkgroup] =
|
|
ShaderVariable(rdcstr(), i / subgroupSize, 0U, 0U, 0U);
|
|
apiWrapper->thread_props[i][(size_t)rdcspv::ThreadProperty::Active] = 1;
|
|
apiWrapper->thread_props[i][(size_t)rdcspv::ThreadProperty::SubgroupId] =
|
|
i % subgroupSize;
|
|
}
|
|
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add inactive padding lanes to round up to the subgroup size
|
|
const uint32_t numPaddingThreads = AlignUp(numThreads, subgroupSize) - numThreads;
|
|
if(numPaddingThreads > 0)
|
|
{
|
|
uint32_t newNumThreads = numThreads + numPaddingThreads;
|
|
apiWrapper->thread_props.resize(newNumThreads);
|
|
apiWrapper->thread_builtins.resize(newNumThreads);
|
|
for(uint32_t i = numThreads; i < newNumThreads; ++i)
|
|
{
|
|
std::unordered_map<ShaderBuiltin, ShaderVariable> &thread_builtins =
|
|
apiWrapper->thread_builtins[i];
|
|
|
|
thread_builtins[ShaderBuiltin::DispatchThreadIndex] =
|
|
ShaderVariable(rdcstr(), -1, -1, -1, -1);
|
|
thread_builtins[ShaderBuiltin::GroupThreadIndex] = ShaderVariable(rdcstr(), -1, -1, -1, -1);
|
|
thread_builtins[ShaderBuiltin::GroupFlatIndex] = ShaderVariable(rdcstr(), -1, -1, -1, -1);
|
|
thread_builtins[ShaderBuiltin::IndexInSubgroup] =
|
|
ShaderVariable(rdcstr(), i % subgroupSize, 0U, 0U, 0U);
|
|
thread_builtins[ShaderBuiltin::SubgroupIndexInWorkgroup] =
|
|
ShaderVariable(rdcstr(), i / subgroupSize, 0U, 0U, 0U);
|
|
apiWrapper->thread_props[i][(size_t)rdcspv::ThreadProperty::Active] = 0;
|
|
apiWrapper->thread_props[i][(size_t)rdcspv::ThreadProperty::SubgroupId] = i % subgroupSize;
|
|
}
|
|
numThreads = newNumThreads;
|
|
}
|
|
apiWrapper->global_builtins[ShaderBuiltin::SubgroupSize] =
|
|
ShaderVariable(rdcstr(), subgroupSize, 0U, 0U, 0U);
|
|
|
|
ShaderDebugTrace *ret =
|
|
debugger->BeginDebug(apiWrapper, stage, entryPoint, spec, shadRefl.instructionLines,
|
|
shadRefl.patchData, laneIndex, numThreads, subgroupSize);
|
|
apiWrapper->ResetReplay();
|
|
|
|
return ret;
|
|
}
|
|
else
|
|
{
|
|
// if we have more than one thread here, that means we need to simulate the whole workgroup.
|
|
// we assume the layout of this is irrelevant and don't attempt to read it back from the GPU
|
|
// like we do with subgroups. We lay things out in plain linear order, along X and then Y and
|
|
// then Z, with groups iterated together.
|
|
if(numThreads > 1)
|
|
{
|
|
uint32_t i = 0;
|
|
for(uint32_t tz = 0; tz < threadDim[2]; tz++)
|
|
{
|
|
for(uint32_t ty = 0; ty < threadDim[1]; ty++)
|
|
{
|
|
for(uint32_t tx = 0; tx < threadDim[0]; tx++)
|
|
{
|
|
std::unordered_map<ShaderBuiltin, ShaderVariable> &thread_builtins =
|
|
apiWrapper->thread_builtins[i];
|
|
thread_builtins[ShaderBuiltin::DispatchThreadIndex] =
|
|
ShaderVariable(rdcstr(), groupid[0] * threadDim[0] + tx,
|
|
groupid[1] * threadDim[1] + ty, groupid[2] * threadDim[2] + tz, 0U);
|
|
thread_builtins[ShaderBuiltin::GroupThreadIndex] =
|
|
ShaderVariable(rdcstr(), tx, ty, tz, 0U);
|
|
thread_builtins[ShaderBuiltin::GroupFlatIndex] = ShaderVariable(
|
|
rdcstr(), tz * threadDim[0] * threadDim[1] + ty * threadDim[0] + tx, 0U, 0U, 0U);
|
|
apiWrapper->thread_props[i][(size_t)rdcspv::ThreadProperty::Active] = 1;
|
|
|
|
if(rdcfixedarray<uint32_t, 3>({tx, ty, tz}) == threadid)
|
|
{
|
|
laneIndex = i;
|
|
}
|
|
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// simple single-thread case
|
|
apiWrapper->thread_props[0][(size_t)rdcspv::ThreadProperty::Active] = 1;
|
|
apiWrapper->thread_props[0][(size_t)rdcspv::ThreadProperty::SubgroupId] = 0;
|
|
|
|
std::unordered_map<ShaderBuiltin, ShaderVariable> &thread_builtins =
|
|
apiWrapper->thread_builtins[0];
|
|
|
|
thread_builtins[ShaderBuiltin::DispatchThreadIndex] = ShaderVariable(
|
|
rdcstr(), groupid[0] * threadDim[0] + threadid[0],
|
|
groupid[1] * threadDim[1] + threadid[1], groupid[2] * threadDim[2] + threadid[2], 0U);
|
|
thread_builtins[ShaderBuiltin::GroupThreadIndex] =
|
|
ShaderVariable(rdcstr(), threadid[0], threadid[1], threadid[2], 0U);
|
|
thread_builtins[ShaderBuiltin::GroupFlatIndex] = ShaderVariable(
|
|
rdcstr(),
|
|
threadid[2] * threadDim[0] * threadDim[1] + threadid[1] * threadDim[0] + threadid[0], 0U,
|
|
0U, 0U);
|
|
}
|
|
|
|
rdcspv::Debugger *debugger = new rdcspv::Debugger;
|
|
debugger->Parse(shader.spirv.GetSPIRV());
|
|
|
|
apiWrapper->global_builtins[ShaderBuiltin::SubgroupSize] =
|
|
ShaderVariable(rdcstr(), 1U, 0U, 0U, 0U);
|
|
|
|
ShaderDebugTrace *ret =
|
|
debugger->BeginDebug(apiWrapper, stage, entryPoint, spec, shadRefl.instructionLines,
|
|
shadRefl.patchData, laneIndex, numThreads, 1);
|
|
apiWrapper->ResetReplay();
|
|
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
rdcarray<ShaderDebugState> VulkanReplay::ContinueDebug(ShaderDebugger *debugger)
|
|
{
|
|
rdcspv::Debugger *spvDebugger = (rdcspv::Debugger *)debugger;
|
|
|
|
if(!spvDebugger)
|
|
return {};
|
|
|
|
VkMarkerRegion region("ContinueDebug Simulation Loop");
|
|
|
|
for(size_t fmt = 0; fmt < ARRAY_COUNT(m_TexRender.DummyImageViews); fmt++)
|
|
{
|
|
for(size_t dim = 0; dim < ARRAY_COUNT(m_TexRender.DummyImageViews[0]); dim++)
|
|
{
|
|
if(m_TexRender.DummyImageViews[fmt][dim] == VK_NULL_HANDLE)
|
|
continue;
|
|
|
|
m_ShaderDebugData.DummyImageInfos[fmt][dim].imageLayout =
|
|
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
|
m_ShaderDebugData.DummyImageInfos[fmt][dim].imageView =
|
|
Unwrap(m_TexRender.DummyImageViews[fmt][dim]);
|
|
|
|
m_ShaderDebugData.DummyWrites[fmt][dim].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
|
m_ShaderDebugData.DummyWrites[fmt][dim].descriptorCount = 1;
|
|
m_ShaderDebugData.DummyWrites[fmt][dim].descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
|
|
m_ShaderDebugData.DummyWrites[fmt][dim].dstBinding = uint32_t(dim + 1);
|
|
m_ShaderDebugData.DummyWrites[fmt][dim].dstSet = VK_NULL_HANDLE;
|
|
m_ShaderDebugData.DummyWrites[fmt][dim].pImageInfo =
|
|
&m_ShaderDebugData.DummyImageInfos[fmt][dim];
|
|
}
|
|
|
|
m_ShaderDebugData.DummyImageInfos[fmt][5].sampler = Unwrap(m_TexRender.DummySampler);
|
|
|
|
m_ShaderDebugData.DummyWrites[fmt][5].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
|
m_ShaderDebugData.DummyWrites[fmt][5].descriptorCount = 1;
|
|
m_ShaderDebugData.DummyWrites[fmt][5].descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER;
|
|
m_ShaderDebugData.DummyWrites[fmt][5].dstBinding = (uint32_t)ShaderDebugBind::Sampler;
|
|
m_ShaderDebugData.DummyWrites[fmt][5].dstSet = VK_NULL_HANDLE;
|
|
m_ShaderDebugData.DummyWrites[fmt][5].pImageInfo = &m_ShaderDebugData.DummyImageInfos[fmt][5];
|
|
|
|
if(m_TexRender.DummyBufferView[fmt] != VK_NULL_HANDLE)
|
|
{
|
|
m_ShaderDebugData.DummyWrites[fmt][6].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
|
m_ShaderDebugData.DummyWrites[fmt][6].descriptorCount = 1;
|
|
m_ShaderDebugData.DummyWrites[fmt][6].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER;
|
|
m_ShaderDebugData.DummyWrites[fmt][6].dstBinding = (uint32_t)ShaderDebugBind::Buffer;
|
|
m_ShaderDebugData.DummyWrites[fmt][6].dstSet = VK_NULL_HANDLE;
|
|
m_ShaderDebugData.DummyWrites[fmt][6].pTexelBufferView =
|
|
UnwrapPtr(m_TexRender.DummyBufferView[fmt]);
|
|
}
|
|
}
|
|
|
|
rdcarray<ShaderDebugState> ret = spvDebugger->ContinueDebug();
|
|
|
|
VulkanAPIWrapper *api = (VulkanAPIWrapper *)spvDebugger->GetAPIWrapper();
|
|
api->ResetReplay();
|
|
|
|
return ret;
|
|
}
|
|
|
|
void VulkanReplay::FreeDebugger(ShaderDebugger *debugger)
|
|
{
|
|
delete debugger;
|
|
}
|