mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-11-08 15:04:21 +00:00
6904 lines
261 KiB
C++
6904 lines
261 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.");
|
|
|
|
// 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(const ShaderBindIndex &bind) override
|
|
{
|
|
rdcspv::DeviceOpResult opResult;
|
|
size_t length = 0;
|
|
// BufferFunction guarantees the buffer cache readlock whilst the function is called
|
|
bool succeeded = BufferFunction(
|
|
bind, [&length](bytebuf *data) { length = data->size(); }, opResult);
|
|
RDCASSERT(succeeded);
|
|
RDCASSERTEQUAL(opResult, rdcspv::DeviceOpResult::Succeeded);
|
|
return length;
|
|
}
|
|
|
|
virtual void ReadBufferValue(const ShaderBindIndex &bind, uint64_t offset, uint64_t byteSize,
|
|
void *dst) override
|
|
{
|
|
rdcspv::DeviceOpResult opResult;
|
|
// BufferFunction guarantees the buffer cache readlock whilst the function is called
|
|
bool succeeded = BufferFunction(
|
|
bind,
|
|
[offset, byteSize, dst](bytebuf *data) {
|
|
if(offset + byteSize <= data->size())
|
|
memcpy(dst, data->data() + (size_t)offset, (size_t)byteSize);
|
|
},
|
|
opResult);
|
|
RDCASSERT(succeeded);
|
|
RDCASSERTEQUAL(opResult, rdcspv::DeviceOpResult::Succeeded);
|
|
}
|
|
|
|
virtual void WriteBufferValue(const ShaderBindIndex &bind, uint64_t offset, uint64_t byteSize,
|
|
const void *src) override
|
|
{
|
|
rdcspv::DeviceOpResult opResult;
|
|
// BufferFunction guarantees the buffer cache readlock whilst the function is called
|
|
bool succeeded = BufferFunction(
|
|
bind,
|
|
[offset, byteSize, src](bytebuf *data) {
|
|
if(offset + byteSize <= data->size())
|
|
memcpy(data->data() + (size_t)offset, src, (size_t)byteSize);
|
|
},
|
|
opResult);
|
|
RDCASSERT(succeeded);
|
|
RDCASSERTEQUAL(opResult, rdcspv::DeviceOpResult::Succeeded);
|
|
}
|
|
|
|
virtual void ReadAddress(uint64_t address, uint64_t byteSize, void *dst) override
|
|
{
|
|
rdcspv::DeviceOpResult opResult;
|
|
// BufferFunction guarantees the buffer cache readlock whilst the function is called
|
|
bool succeeded = BufferFunction(
|
|
address,
|
|
[byteSize, dst](bytebuf *data, size_t offset) {
|
|
if(offset + byteSize <= data->size())
|
|
memcpy(dst, data->data() + offset, (size_t)byteSize);
|
|
},
|
|
opResult);
|
|
RDCASSERT(succeeded);
|
|
RDCASSERTEQUAL(opResult, rdcspv::DeviceOpResult::Succeeded);
|
|
}
|
|
|
|
virtual void WriteAddress(uint64_t address, uint64_t byteSize, const void *src) override
|
|
{
|
|
rdcspv::DeviceOpResult opResult;
|
|
// BufferFunction guarantees the buffer cache readlock whilst the function is called
|
|
bool succeeded = BufferFunction(
|
|
address,
|
|
[byteSize, src](bytebuf *data, size_t offset) {
|
|
if(offset + byteSize <= data->size())
|
|
memcpy(data->data() + offset, src, (size_t)byteSize);
|
|
},
|
|
opResult);
|
|
RDCASSERT(succeeded);
|
|
RDCASSERTEQUAL(opResult, rdcspv::DeviceOpResult::Succeeded);
|
|
}
|
|
|
|
// Called from any thread
|
|
// Caller guarantees that if the image data is not cached then we are on the device thread
|
|
virtual rdcspv::DeviceOpResult ReadTexel(const ShaderBindIndex &imageBind,
|
|
const ShaderVariable &coord, uint32_t sample,
|
|
ShaderVariable &output) override
|
|
{
|
|
rdcspv::DeviceOpResult opResult;
|
|
bool isCached = false;
|
|
{
|
|
SCOPED_READLOCK(imageCacheLock);
|
|
isCached = GetImageDataFromCache(imageBind, opResult) != NULL;
|
|
RDCASSERTNOTEQUAL(opResult, rdcspv::DeviceOpResult::NeedsDevice);
|
|
}
|
|
|
|
if(!isCached)
|
|
{
|
|
// Add image data to the cache : cache should not be locked by this thread
|
|
PopulateImage(imageBind);
|
|
}
|
|
|
|
{
|
|
SCOPED_READLOCK(imageCacheLock);
|
|
ImageData *result = GetImageDataFromCache(imageBind, opResult);
|
|
if(!result)
|
|
{
|
|
RDCASSERTEQUAL(opResult, rdcspv::DeviceOpResult::Failed);
|
|
return rdcspv::DeviceOpResult::Failed;
|
|
}
|
|
|
|
ImageData &data = *result;
|
|
if(data.width == 0)
|
|
return rdcspv::DeviceOpResult::Failed;
|
|
|
|
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)
|
|
{
|
|
if(!IsDeviceThread())
|
|
return rdcspv::DeviceOpResult::NeedsDevice;
|
|
|
|
CHECK_DEVICE_THREAD();
|
|
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 rdcspv::DeviceOpResult::Failed;
|
|
}
|
|
|
|
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 rdcspv::DeviceOpResult::Succeeded;
|
|
}
|
|
|
|
// Called from any thread
|
|
// Caller guarantees that if the image data is not cached then we are on the device thread
|
|
virtual rdcspv::DeviceOpResult WriteTexel(const ShaderBindIndex &imageBind,
|
|
const ShaderVariable &coord, uint32_t sample,
|
|
const ShaderVariable &input) override
|
|
{
|
|
rdcspv::DeviceOpResult opResult;
|
|
ImageData *result = NULL;
|
|
{
|
|
SCOPED_READLOCK(imageCacheLock);
|
|
result = GetImageDataFromCache(imageBind, opResult);
|
|
RDCASSERTNOTEQUAL(opResult, rdcspv::DeviceOpResult::NeedsDevice);
|
|
}
|
|
|
|
if(!result)
|
|
{
|
|
// Add image data to the cache : cache should not be locked by this thread
|
|
PopulateImage(imageBind);
|
|
}
|
|
|
|
{
|
|
SCOPED_READLOCK(imageCacheLock);
|
|
result = GetImageDataFromCache(imageBind, opResult);
|
|
if(!result)
|
|
return rdcspv::DeviceOpResult::Failed;
|
|
|
|
ImageData &data = *result;
|
|
if(data.width == 0)
|
|
return rdcspv::DeviceOpResult::Failed;
|
|
|
|
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)
|
|
{
|
|
if(!IsDeviceThread())
|
|
return rdcspv::DeviceOpResult::NeedsDevice;
|
|
|
|
CHECK_DEVICE_THREAD();
|
|
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 rdcspv::DeviceOpResult::Failed;
|
|
}
|
|
|
|
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 rdcspv::DeviceOpResult::Succeeded;
|
|
}
|
|
|
|
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;
|
|
|
|
Threading::RWLock bufferCacheLock;
|
|
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;
|
|
}
|
|
};
|
|
|
|
Threading::RWLock imageCacheLock;
|
|
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];
|
|
}
|
|
|
|
// Called from any thread
|
|
ShaderBindIndex GenerateBufferBind(const uint64_t address, size_t &offs)
|
|
{
|
|
ResourceId id;
|
|
uint64_t ptrOffs;
|
|
m_pDriver->GetResIDFromAddr(address, id, ptrOffs);
|
|
offs = size_t(ptrOffs);
|
|
|
|
ShaderBindIndex bind;
|
|
bind.arrayElement = (address - offs) & 0xFFFFFFFFU;
|
|
|
|
return bind;
|
|
}
|
|
|
|
// Called from any thread
|
|
bool IsBufferCached(const ShaderBindIndex &bind) override
|
|
{
|
|
SCOPED_READLOCK(bufferCacheLock);
|
|
return bufferCache.find(bind) != bufferCache.end();
|
|
}
|
|
|
|
// Called from any thread
|
|
bool IsBufferCached(uint64_t address) override
|
|
{
|
|
size_t offs = 0;
|
|
ShaderBindIndex bind = GenerateBufferBind(address, offs);
|
|
return IsBufferCached(bind);
|
|
}
|
|
|
|
// Called from any thread
|
|
bytebuf *GetBufferDataFromCache(const ShaderBindIndex &bind, rdcspv::DeviceOpResult &opResult)
|
|
{
|
|
// Calling function responsible for acquiring bufferCache Read lock
|
|
auto findIt = bufferCache.find(bind);
|
|
if(findIt != bufferCache.end())
|
|
{
|
|
opResult = rdcspv::DeviceOpResult::Succeeded;
|
|
return &findIt->second;
|
|
}
|
|
|
|
opResult = rdcspv::DeviceOpResult::Failed;
|
|
|
|
// Not in the cache : populate must happen on the device thread
|
|
if(!IsDeviceThread())
|
|
opResult = rdcspv::DeviceOpResult::NeedsDevice;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
// Called from any thread
|
|
bool BufferFunction(const ShaderBindIndex &bind, const std::function<void(bytebuf *data)> &func,
|
|
rdcspv::DeviceOpResult &opResult)
|
|
{
|
|
bool isCached = false;
|
|
{
|
|
SCOPED_READLOCK(bufferCacheLock);
|
|
isCached = GetBufferDataFromCache(bind, opResult) != NULL;
|
|
if(opResult == rdcspv::DeviceOpResult::NeedsDevice)
|
|
return false;
|
|
}
|
|
|
|
if(!isCached)
|
|
{
|
|
// Add buffer data to the cache : cache should not be locked by this thread
|
|
PopulateBuffer(bind);
|
|
}
|
|
|
|
{
|
|
SCOPED_READLOCK(bufferCacheLock);
|
|
bytebuf *result = GetBufferDataFromCache(bind, opResult);
|
|
if(result)
|
|
{
|
|
// Guarantee the buffer cache readlock whilst the function is called
|
|
func(result);
|
|
return true;
|
|
}
|
|
|
|
RDCASSERTEQUAL(opResult, rdcspv::DeviceOpResult::Failed);
|
|
opResult = rdcspv::DeviceOpResult::Failed;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Called from any thread
|
|
bool BufferFunction(uint64_t address, const std::function<void(bytebuf *data, size_t offset)> &func,
|
|
rdcspv::DeviceOpResult &opResult)
|
|
{
|
|
size_t offs = 0;
|
|
ShaderBindIndex bind = GenerateBufferBind(address, offs);
|
|
bool isCached = false;
|
|
{
|
|
SCOPED_READLOCK(bufferCacheLock);
|
|
isCached = GetBufferDataFromCache(bind, opResult) != NULL;
|
|
if(opResult == rdcspv::DeviceOpResult::NeedsDevice)
|
|
return false;
|
|
}
|
|
|
|
if(!isCached)
|
|
{
|
|
// Add buffer data to the cache : cache should not be locked by this thread
|
|
PopulateBuffer(address, bind);
|
|
}
|
|
|
|
{
|
|
SCOPED_READLOCK(bufferCacheLock);
|
|
bytebuf *result = GetBufferDataFromCache(bind, opResult);
|
|
if(result)
|
|
{
|
|
// Guarantee the buffer cache readlock whilst the function is called
|
|
func(result, offs);
|
|
return true;
|
|
}
|
|
|
|
RDCASSERTEQUAL(opResult, rdcspv::DeviceOpResult::Failed);
|
|
opResult = rdcspv::DeviceOpResult::Failed;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Must be called from the replay manager thread (the debugger thread)
|
|
void PopulateBuffer(uint64_t address, ShaderBindIndex bind)
|
|
{
|
|
CHECK_DEVICE_THREAD();
|
|
// pick a non-overlapping bind namespace for direct pointer access
|
|
ResourceId id;
|
|
uint64_t ptrOffs;
|
|
|
|
m_pDriver->GetResIDFromAddr(address, id, ptrOffs);
|
|
if(id == ResourceId())
|
|
{
|
|
ShaderBindIndex noBind;
|
|
CHECK_DEVICE_THREAD();
|
|
auto insertIt = bufferCache.insert(std::make_pair(noBind, bytebuf()));
|
|
m_pDriver->AddDebugMessage(MessageCategory::Execution, MessageSeverity::High,
|
|
MessageSource::RuntimeWarning,
|
|
StringFormat::Fmt("invalid or OOB pointer access detected ."));
|
|
return;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
bytebuf data;
|
|
m_pDriver->GetDebugManager()->GetBufferData(id, 0, 0, data);
|
|
|
|
{
|
|
// Insert atomically with all the data filled in : to prevent race conditions
|
|
SCOPED_WRITELOCK(bufferCacheLock);
|
|
auto insertIt = bufferCache.insert(std::make_pair(bind, data));
|
|
RDCASSERT(insertIt.second);
|
|
}
|
|
}
|
|
|
|
// Must be called from the replay manager thread (the debugger thread)
|
|
void PopulateBuffer(const ShaderBindIndex &bind)
|
|
{
|
|
CHECK_DEVICE_THREAD();
|
|
bytebuf data;
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
{
|
|
// Insert atomically with all the data filled in : to prevent race conditions
|
|
SCOPED_WRITELOCK(bufferCacheLock);
|
|
auto insertIt = bufferCache.insert(std::make_pair(bind, data));
|
|
RDCASSERT(insertIt.second);
|
|
}
|
|
}
|
|
|
|
// Must be called from the replay manager thread (the debugger thread)
|
|
void PopulateImage(const ShaderBindIndex &bind)
|
|
{
|
|
CHECK_DEVICE_THREAD();
|
|
|
|
ImageData data;
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
// Insert atomically with all the data filled in : to prevent race conditions
|
|
SCOPED_WRITELOCK(imageCacheLock);
|
|
auto insertIt = imageCache.insert(std::make_pair(bind, data));
|
|
RDCASSERT(insertIt.second);
|
|
}
|
|
}
|
|
|
|
// Called from any thread
|
|
bool IsImageCached(const ShaderBindIndex &bind) override
|
|
{
|
|
SCOPED_READLOCK(imageCacheLock);
|
|
return imageCache.find(bind) != imageCache.end();
|
|
}
|
|
|
|
// Called from any thread
|
|
ImageData *GetImageDataFromCache(const ShaderBindIndex &bind, rdcspv::DeviceOpResult &opResult)
|
|
{
|
|
// Calling function responsible for acquiring imageCache Read lock
|
|
auto findIt = imageCache.find(bind);
|
|
if(findIt != imageCache.end())
|
|
{
|
|
opResult = rdcspv::DeviceOpResult::Succeeded;
|
|
return &findIt->second;
|
|
}
|
|
|
|
opResult = rdcspv::DeviceOpResult::Failed;
|
|
|
|
// Not in the cache : populate must happen on the device thread
|
|
if(!IsDeviceThread())
|
|
opResult = rdcspv::DeviceOpResult::NeedsDevice;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
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)
|
|
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;
|
|
Threading::JobSystem::SyncAllJobs();
|
|
}
|