Add fast descriptor lookup for known descriptor formats

* The idea here is to directly extract a pointer and/or size and do few lookups
  (hopefully only one) to verify that we have recognised the descriptor. We
  still have the trie lookup as backup.
This commit is contained in:
baldurk
2025-06-05 15:38:42 +01:00
parent 76119c5811
commit dacb5aab79
2 changed files with 666 additions and 0 deletions
+655
View File
@@ -46,6 +46,10 @@ RDOC_DEBUG_CONFIG(bool, Vulkan_Debug_SingleSubmitFlushing, false,
"Every command buffer is submitted and fully flushed to the GPU, to narrow down "
"the source of problems.");
RDOC_CONFIG(bool, Vulkan_Debug_UseFastDescriptorLookup, true,
"Use fast pattern-matching lookup to try to identify descriptors before falling back "
"to trie lookup.");
uint64_t VkInitParams::GetSerialiseSize()
{
// misc bytes and fixed integer members
@@ -5454,6 +5458,657 @@ size_t WrappedVulkan::DescriptorDataSize(VkDescriptorType type)
return ret;
}
void WrappedVulkan::LookupDescriptor(byte *descriptorBytes, size_t descriptorSize,
DescriptorType type, DescriptorSetSlot &data)
{
const size_t combinedSize = m_DescriptorBufferProperties.combinedImageSamplerDescriptorSize;
const size_t sampledSize = m_DescriptorBufferProperties.sampledImageDescriptorSize;
const size_t samplerSize = m_DescriptorBufferProperties.samplerDescriptorSize;
union
{
VkDescriptorImageInfo imInfo;
VkDescriptorAddressInfoEXT bufinfo;
VkSampler sampler;
};
bufinfo = {};
VkDescriptorGetInfoEXT info = {
VK_STRUCTURE_TYPE_DESCRIPTOR_GET_INFO_EXT,
};
info.data.pUniformBuffer = &bufinfo;
byte tempMem[256] = {};
// start with the descriptor bytes in case the driver doesn't initialise them all. If this
// contains random bytes from user memory we want it to match
memcpy(tempMem, descriptorBytes, descriptorSize);
if(Vulkan_Debug_UseFastDescriptorLookup())
{
switch(type)
{
case DescriptorType::Sampler:
{
ResourceId samp = GetSamplerForDescriptor(descriptorBytes, descriptorSize);
if(samp != ResourceId())
{
data = {};
data.SetSampler(samp);
// verify that descriptor roundtrips that our detection was correct
info.type = VK_DESCRIPTOR_TYPE_SAMPLER;
sampler = Unwrap(GetResourceManager()->GetCurrentHandle<VkSampler>(samp));
ObjDisp(m_Device)->GetDescriptorEXT(Unwrap(m_Device), &info, descriptorSize, tempMem);
if(memcmp(tempMem, descriptorBytes, descriptorSize) == 0)
return;
}
break;
}
case DescriptorType::ImageSampler:
case DescriptorType::Image:
case DescriptorType::ReadWriteImage:
{
if(type == DescriptorType::ImageSampler)
info.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
else if(type == DescriptorType::Image)
info.type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
else if(type == DescriptorType::ReadWriteImage)
info.type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
ResourceId samp;
ResourceId view;
if(type == DescriptorType::ImageSampler)
{
if(m_DescriptorLookup.sampled == ImageDescriptorFormat::Indexed2012)
{
// for the indexed format, the sampler/view are encoded together
view = GetImageViewForDescriptor(descriptorBytes, descriptorSize, type);
samp = GetSamplerForDescriptor(descriptorBytes, descriptorSize);
}
else if(m_DescriptorLookup.sampled == ImageDescriptorFormat::UnknownImageDescriptor)
{
break;
}
else
{
// all known formats we expect an image view to be followed by a sampler
if(combinedSize == m_DescriptorLookup.combinedSamplerOffset + samplerSize)
{
view = GetImageViewForDescriptor(descriptorBytes, sampledSize, type);
samp = GetSamplerForDescriptor(
descriptorBytes + m_DescriptorLookup.combinedSamplerOffset, samplerSize);
}
else
{
RDCWARN(
"non-indexed combined image/sampler is not (padded) image followed by sampler");
}
}
if(samp == ResourceId())
{
RDCWARN("Fast-detection failed to get sampler for image/sampler descriptor");
break;
}
}
else
{
view = GetImageViewForDescriptor(descriptorBytes, descriptorSize, type);
}
// exit silently, may be unknown descriptor format which would spam
if(view == ResourceId())
break;
rdcarray<VkImageLayout> layouts;
if(!m_IgnoreLayoutForDescriptors)
{
layouts = m_DescriptorLookup.generalImageLayouts;
if(m_CreationInfo.m_ImageView[view].isDepthImage)
layouts.append(m_DescriptorLookup.depthImageLayouts);
}
imInfo.sampler = Unwrap(GetResourceManager()->GetCurrentHandle<VkSampler>(samp));
imInfo.imageView = Unwrap(GetResourceManager()->GetCurrentHandle<VkImageView>(view));
// always iterate at least once even if the layouts array is empty
for(size_t i = 0; i < layouts.size() || (i == 0 && layouts.empty()); i++)
{
imInfo.imageLayout = i < layouts.size() ? layouts[i] : VK_IMAGE_LAYOUT_GENERAL;
ObjDisp(m_Device)->GetDescriptorEXT(Unwrap(m_Device), &info, descriptorSize, tempMem);
if(memcmp(tempMem, descriptorBytes, descriptorSize) == 0)
{
data.SetImageSampler(info.type, view, samp, imInfo.imageLayout);
return;
}
}
RDCWARN("Fast-detection failed for %s descriptor", ToStr(type).c_str());
break;
}
case DescriptorType::TypedBuffer:
case DescriptorType::ReadWriteTypedBuffer:
case DescriptorType::ConstantBuffer:
case DescriptorType::ReadWriteBuffer:
case DescriptorType::AccelerationStructure:
{
if(type == DescriptorType::TypedBuffer)
info.type = VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER;
else if(type == DescriptorType::ReadWriteTypedBuffer)
info.type = VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER;
else if(type == DescriptorType::ConstantBuffer)
info.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
else if(type == DescriptorType::ReadWriteBuffer)
info.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
else if(type == DescriptorType::AccelerationStructure)
info.type = VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR;
const bool texelBuffer =
type == DescriptorType::TypedBuffer || type == DescriptorType::ReadWriteTypedBuffer;
VkDeviceAddress address;
VkDeviceSize size;
GetPointerAndSizeForDescriptor(descriptorBytes, descriptorSize, type, address, size);
if(address == 0)
{
// exit silently, may be unknown descriptor format which would spam
break;
}
else
{
if(type == DescriptorType::AccelerationStructure)
{
info.data.accelerationStructure = address;
}
else
{
bufinfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_ADDRESS_INFO_EXT;
bufinfo.address = address;
bufinfo.range = size;
}
bufinfo.format = VK_FORMAT_UNDEFINED;
for(size_t i = 0, n = texelBuffer ? m_DescriptorLookup.texelFormats.size() : 1; i < n; i++)
{
if(texelBuffer)
bufinfo.format = m_DescriptorLookup.texelFormats[i];
if(type != DescriptorType::AccelerationStructure)
{
// a couple of formats modify the address or size in non-trivial ways that need to be patched
// here. This also handles converting an element size back into a byte size trivially
GetFinalBufferParameters(descriptorBytes, descriptorSize, type, bufinfo.format,
address, size, bufinfo.address, bufinfo.range);
}
ObjDisp(m_Device)->GetDescriptorEXT(Unwrap(m_Device), &info, descriptorSize, tempMem);
if(memcmp(tempMem, descriptorBytes, descriptorSize) != 0)
{
// try sign extending if the top bit is set
address |= (0xffffULL << 48);
if(type == DescriptorType::AccelerationStructure)
info.data.accelerationStructure = address;
else
bufinfo.address = address;
ObjDisp(m_Device)->GetDescriptorEXT(Unwrap(m_Device), &info, descriptorSize, tempMem);
}
if(memcmp(tempMem, descriptorBytes, descriptorSize) == 0)
{
ResourceId id;
VkDeviceSize offs;
if(type != DescriptorType::AccelerationStructure)
{
GetResIDFromAddr(bufinfo.address, id, offs);
if(id == ResourceId() && (bufinfo.address & (1ULL << 47)))
{
// try sign extending if the top bit is set
GetResIDFromAddr(bufinfo.address | (0xffffULL << 48), id, offs);
}
if(id == ResourceId())
{
RDCWARN("Unknown buffer at descriptor address %llx", bufinfo.address);
}
else
{
data.SetBuffer(info.type, id, offs, bufinfo.range, bufinfo.format);
return;
}
}
else
{
id = m_ASLookupByAddr[address];
if(id == ResourceId() && (address & (1ULL << 47)))
{
// try sign extending if the top bit is set
id = m_ASLookupByAddr[address | (0xffffULL << 48)];
}
if(id == ResourceId())
{
RDCWARN("Unknown AS at descriptor address %llx", address);
}
else
{
data.SetAccelerationStructure(
VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR,
GetResourceManager()->GetCurrentHandle<VkAccelerationStructureKHR>(id));
return;
}
}
}
}
RDCWARN("Fast-detection failed to get match for %s descriptor", ToStr(type).c_str());
}
break;
}
case DescriptorType::Buffer:
case DescriptorType::Unknown: RDCERR("Invalid descriptor type being looked up"); break;
}
}
data = m_DescriptorLookup.fallback.lookup({descriptorBytes, descriptorSize});
#if ENABLED(RDOC_DEVEL)
if(!m_DescriptorLookup.fallback.contains({descriptorBytes, descriptorSize}))
{
RDCERR("Trie descriptor lookup failed");
// dump the descriptor
uint64_t *descriptorU64 = (uint64_t *)descriptorBytes;
for(uint32_t i = 0; i * 8 < descriptorSize; i++)
RDCLOG(" [%u]: %llx", i, descriptorU64[i]);
}
#endif
}
ResourceId WrappedVulkan::GetSamplerForDescriptor(byte *descriptorBytes, size_t descriptorSize)
{
if(m_DescriptorLookup.sampled == ImageDescriptorFormat::Indexed2012)
{
if(descriptorSize == sizeof(uint32_t))
{
uint32_t idx = (*(uint32_t *)descriptorBytes) >> 20;
if(idx > 0 && idx < m_DescriptorLookup.samplerPalette.size())
{
return m_DescriptorLookup.samplerPalette[idx];
}
RDCWARN("Indexed sampler descriptor index is %u", idx);
return ResourceId();
}
RDCWARN("Indexed sampler descriptor is %zu bytes", descriptorSize);
return ResourceId();
}
DescriptorTrieNode data = m_DescriptorLookup.samplers.lookup({descriptorBytes, descriptorSize});
// we should find this
if(data.sampler == ResourceId())
RDCWARN("Couldn't find solo sampler in sampler lookup trie");
return data.sampler;
}
ResourceId WrappedVulkan::GetImageViewForDescriptor(byte *descriptorBytes, size_t descriptorSize,
DescriptorType type)
{
ImageDescriptorFormat format = m_DescriptorLookup.sampled;
if(type == DescriptorType::ReadWriteImage)
format = m_DescriptorLookup.storage;
// we assumed if one image type is indexed, all are
if(format == ImageDescriptorFormat::Indexed2012)
{
if(descriptorSize == sizeof(uint32_t))
{
uint32_t idx = (*(uint32_t *)descriptorBytes) & 0xfffff;
if(idx > 0 && idx < m_DescriptorLookup.imageViewPalette.size())
{
return m_DescriptorLookup.imageViewPalette[idx];
}
RDCWARN("Indexed view descriptor index is %u", idx);
return ResourceId();
}
RDCWARN("Indexed view descriptor is %zu bytes", descriptorSize);
return ResourceId();
}
// other descriptors are recognised by pointer
uint64_t ptr = 0;
if(format == ImageDescriptorFormat::PointerShifted_32 ||
format == ImageDescriptorFormat::PointerShifted_64)
{
if(descriptorSize == 32 || descriptorSize == 64)
{
ptr = (((uint64_t *)descriptorBytes)[0] << 8) & ((1ULL << 48) - 1);
}
else
{
RDCWARN("Unexpected descriptor format for detected format %u: %u", format, descriptorSize);
return ResourceId();
}
}
else if(format == ImageDescriptorFormat::Pointer2_64)
{
if(descriptorSize == 64)
{
ptr = ((uint64_t *)descriptorBytes)[2] & ((1ULL << 48) - 1);
}
else
{
RDCWARN("Unexpected descriptor format for detected format %u: %u", format, descriptorSize);
return ResourceId();
}
}
else if(format == ImageDescriptorFormat::Pointer4_64)
{
if(descriptorSize == 64)
{
ptr = ((uint64_t *)descriptorBytes)[4] & ((1ULL << 48) - 1);
}
else
{
RDCWARN("Unexpected descriptor format for detected format %u: %u", format, descriptorSize);
return ResourceId();
}
}
else
{
return ResourceId();
}
ResourceId imageId;
uint64_t unused;
m_DescriptorLookup.imageAddresses.GetResIDFromAddr(ptr, imageId, unused);
if(imageId == ResourceId() && (ptr & (1ULL << 47)))
{
// try sign extending if the top bit is set
m_DescriptorLookup.imageAddresses.GetResIDFromAddr(ptr | (0xffffULL << 48), imageId, unused);
}
if(imageId == ResourceId())
{
RDCWARN("View descriptor gave unrecognised pointer %llx", ptr);
return ResourceId();
}
ResourceId viewId =
m_CreationInfo.m_Image[imageId].getViewFromDescriptor(descriptorBytes, descriptorSize);
if(viewId == ResourceId())
{
RDCWARN("View descriptor gave pointer %llx for image %s but was unrecognised", ptr,
ToStr(imageId).c_str());
return ResourceId();
}
return viewId;
}
void WrappedVulkan::GetPointerAndSizeForDescriptor(byte *descriptorBytes, size_t descriptorSize,
DescriptorType type, VkDeviceAddress &address,
VkDeviceSize &size)
{
address = 0;
size = 0;
BufferDescriptorFormat format = m_DescriptorLookup.uniformBuffer;
if(type == DescriptorType::ReadWriteBuffer)
format = m_DescriptorLookup.storageBuffer;
else if(type == DescriptorType::TypedBuffer)
format = m_DescriptorLookup.uniformTexelBuffer;
else if(type == DescriptorType::ReadWriteTypedBuffer)
format = m_DescriptorLookup.storageTexelBuffer;
else if(type == DescriptorType::AccelerationStructure)
format = m_DescriptorLookup.accelStructure;
if(format == BufferDescriptorFormat::Pointer_8 && descriptorSize == sizeof(uint64_t))
{
uint64_t *packed = (uint64_t *)descriptorBytes;
address = (packed[0] & ((1ULL << 48) - 1));
}
else if(format == BufferDescriptorFormat::Packed_4519_Aligned16_8 &&
descriptorSize == sizeof(uint64_t))
{
uint64_t packed = *(uint64_t *)descriptorBytes;
address = (packed & ((1ULL << 45) - 1)) << 4;
size = (packed >> 45) << 4;
}
else if(format == BufferDescriptorFormat::Packed_4519_Aligned256_8 &&
descriptorSize == sizeof(uint64_t))
{
uint64_t packed = *(uint64_t *)descriptorBytes;
address = (packed & ((1ULL << 45) - 1)) << 4;
size = (packed >> 45) << 4;
}
else if(format == BufferDescriptorFormat::Pointer_ElemSize_16 &&
descriptorSize == sizeof(uint64_t) * 2)
{
uint64_t *packed = (uint64_t *)descriptorBytes;
address = (packed[0] & ((1ULL << 48) - 1));
size = (packed[1] & 0xFFFFFFFFULL);
}
else if(format == BufferDescriptorFormat::PointerDivided_ElemSize_16 &&
descriptorSize == sizeof(uint64_t) * 2)
{
uint64_t *packed = (uint64_t *)descriptorBytes;
address = (packed[0] & ((1ULL << 48) - 1));
size = (packed[1] & 0xFFFFFFFFULL);
// address is incomplete, but can't be fixed until we know the texel size
}
else if(format == BufferDescriptorFormat::Pointer0_16 && descriptorSize == sizeof(uint64_t) * 2)
{
uint64_t *packed = (uint64_t *)descriptorBytes;
address = (packed[0] & ((1ULL << 48) - 1));
}
else if(format == BufferDescriptorFormat::ByteSize0_Pointer1_32 &&
descriptorSize == sizeof(uint64_t) * 4)
{
uint64_t *packed = (uint64_t *)descriptorBytes;
address = (packed[1] & ((1ULL << 48) - 1));
size = (packed[0] >> 32);
}
else if(format == BufferDescriptorFormat::Pointer1_32 && descriptorSize == sizeof(uint64_t) * 4)
{
uint64_t *packed = (uint64_t *)descriptorBytes;
address = (packed[1] & ((1ULL << 48) - 1));
}
else if(format == BufferDescriptorFormat::Pointer2_64 && descriptorSize == sizeof(uint64_t) * 4)
{
uint64_t *packed = (uint64_t *)descriptorBytes;
address = (packed[2] & ((1ULL << 48) - 1));
}
else if(format == BufferDescriptorFormat::Pointer4_ByteSize5_Unaligned_64 &&
descriptorSize == sizeof(uint64_t) * 8)
{
uint64_t *packed = (uint64_t *)descriptorBytes;
address = (packed[4] & ((1ULL << 48) - 1));
size = (packed[5] >> 32);
}
else if(format == BufferDescriptorFormat::Pointer4_ByteSize5_Aligned_64 &&
descriptorSize == sizeof(uint64_t) * 8)
{
uint64_t *packed = (uint64_t *)descriptorBytes;
address = (packed[4] & ((1ULL << 48) - 1));
size = (packed[5] >> 32);
}
else if(format == BufferDescriptorFormat::ElemSizeScattered1_Pointer4_64 &&
descriptorSize == sizeof(uint64_t) * 8)
{
uint64_t *packed = (uint64_t *)descriptorBytes;
address = (packed[4] & ((1ULL << 48) - 1));
uint64_t sizeScattered = packed[1];
size = (sizeScattered & 0x7f) | ((sizeScattered >> 9) & 0x1fff80) | ((sizeScattered >> 53) << 21);
// this is still swizzled on the low bits, but we won't know how to decode that until we have the texel format
}
else if((format == BufferDescriptorFormat::Strided4_MultiDescriptor_64 ||
format == BufferDescriptorFormat::Strided2_MultiDescriptor_64 ||
format == BufferDescriptorFormat::Strided1_MultiDescriptor_64) &&
(descriptorSize == sizeof(uint64_t) * 8 * 1 || descriptorSize == sizeof(uint64_t) * 8 * 2))
{
const uint32_t stride = format == BufferDescriptorFormat::Strided4_MultiDescriptor_64 ? 4
: format == BufferDescriptorFormat::Strided2_MultiDescriptor_64 ? 2
: 1;
uint64_t *packed = (uint64_t *)descriptorBytes;
address = (packed[2] & ((1ULL << 48) - 1));
address += ((packed[1] >> 16) & 0x3f) << stride;
size = (packed[0] >> 32) << stride;
}
else if(format == BufferDescriptorFormat::ElemSize0_Pointer2_64 &&
descriptorSize == sizeof(uint64_t) * 8)
{
uint64_t *packed = (uint64_t *)descriptorBytes;
address = (packed[2] & ((1ULL << 48) - 1));
size = (packed[0] >> 32);
}
}
void WrappedVulkan::GetFinalBufferParameters(byte *descriptorBytes, size_t descriptorSize,
DescriptorType type, VkFormat texelFormat,
VkDeviceAddress inAddress, VkDeviceSize inSize,
VkDeviceAddress &outAddress, VkDeviceSize &outSize)
{
BufferDescriptorFormat format = m_DescriptorLookup.uniformBuffer;
if(type == DescriptorType::ReadWriteBuffer)
format = m_DescriptorLookup.storageBuffer;
else if(type == DescriptorType::TypedBuffer)
format = m_DescriptorLookup.uniformTexelBuffer;
else if(type == DescriptorType::ReadWriteTypedBuffer)
format = m_DescriptorLookup.storageTexelBuffer;
else if(type == DescriptorType::AccelerationStructure)
format = m_DescriptorLookup.accelStructure;
uint32_t elemSize = 1;
if(type == DescriptorType::TypedBuffer || type == DescriptorType::ReadWriteTypedBuffer)
elemSize = GetByteSize(1, 1, 1, texelFormat, 0) & 0xffff;
if(format == BufferDescriptorFormat::ElemSize0_Pointer2_64 ||
format == BufferDescriptorFormat::Pointer_ElemSize_16)
{
outAddress = inAddress;
outSize = inSize * elemSize;
}
else if(format == BufferDescriptorFormat::PointerDivided_ElemSize_16)
{
outAddress = inAddress * elemSize;
// for elemSize==12 the address didn't divide evenly so we need to grab the remainder
if(elemSize == 12)
{
uint64_t *packed = (uint64_t *)descriptorBytes;
uint64_t remainder = (packed[1] >> 32) & 0x3f;
// the actual pattern to this is entirely unknown and it's assumed to be some internal base
// offset, but this seems consistent as the 3 remainders (0, 4, 8 bytes) are always 23 apart in these bits
outAddress += 4 * (remainder / 23);
}
// outAddress += remainder;
outSize = inSize * elemSize;
}
else if(format == BufferDescriptorFormat::ElemSizeScattered1_Pointer4_64)
{
outAddress = inAddress;
if(elemSize >= 4)
{
outSize = (inSize + 1) * elemSize;
}
else
{
// unswizzle the 2-byte/1-byte size. There's probably a fancier way to express this
// bit-twiddling but it's more readable to have this verbosely specified
//
// the general scheme for encoding is:
//
// lop off the bottom 8 bits, which we call 'x'
//
// swizzle those bits in this formula:
// 1 byte elements: ((x & 0xfc) + 6 - (x & 0x3))
// 2 byte elements: ((x & 0xfe) + 2 - (x & 0x1))
//
// note that this means the bottom bits can be swizzled into 9 bits of data and increment into
// the upper bits, so we need to deal with carrying. E.g. for x = 0xfd this produces 0x102 result
const uint32_t upperMask = elemSize == 1 ? 0xfc : 0xfe;
const uint32_t lowerMask = 0xff - upperMask;
const uint32_t offset = lowerMask << 1;
// segment the lower swizzled bits. There may be leakage due to the carry bit but we handle that
const uint64_t upperSize = inSize & ~0xff;
const uint32_t lowerSize = inSize & 0xff;
// need a carry bit for some cases, this will be subtracted later
const uint32_t carry = lowerSize < offset ? 128 : 0;
// this is (mostly) the result of the (x & 0xfc) - (x & 0x3) subtraction
const uint32_t xsubbed = lowerSize + carry - offset;
// assuming a lower mask of 0x3 (bottom two bits) if xsubbed ends in 00 then it must have been
// aligned, if it ended in 11 then it must have been 01 subtracted from the value above, etc.
// this will give us the original bottom two bits of x by subtracting and masking
const uint32_t xlow = ((lowerMask + 1) - (xsubbed & lowerMask)) & lowerMask;
// the upper bits of the size are added on unconditionally, we also undo the +1 to the range here
outSize = upperSize + 1;
// no bits = no subtraction! x must have been aligned when we did the sum so just the upperMask bits are used
if(xlow == 0)
outSize += xsubbed;
else
// xlow had some bits, so we figure out what it must have been subtracted from and add that to the upper mask
outSize += ((xsubbed & upperMask) + (lowerMask + 1) + xlow);
// subtract any carry we added now
outSize -= carry;
// finally convert to bytes
outSize *= elemSize;
}
}
else
{
// byte sizes, no translation needed
outAddress = inAddress;
outSize = inSize;
}
}
void WrappedVulkan::RegisterDescriptor(const bytebuf &key, const DescriptorSetSlot &data)
{
m_DescriptorLookup.fallback.insert(key, data);
+11
View File
@@ -511,6 +511,17 @@ private:
DescriptorLookups m_DescriptorLookup;
void RegisterDescriptor(const bytebuf &key, const DescriptorSetSlot &data);
void LookupDescriptor(byte *descriptorBytes, size_t descriptorSize, DescriptorType type,
DescriptorSetSlot &data);
ResourceId GetSamplerForDescriptor(byte *descriptorBytes, size_t descriptorSize);
ResourceId GetImageViewForDescriptor(byte *descriptorBytes, size_t descriptorSize,
DescriptorType type);
void GetPointerAndSizeForDescriptor(byte *descriptorBytes, size_t descriptorSize,
DescriptorType type, VkDeviceAddress &address,
VkDeviceSize &size);
void GetFinalBufferParameters(byte *descriptorBytes, size_t descriptorSize, DescriptorType type,
VkFormat texelFormat, VkDeviceAddress inAddress, VkDeviceSize inSize,
VkDeviceAddress &outAddress, VkDeviceSize &outSize);
bool m_NULLDescriptorPatternSaved = false;
bool m_IgnoreLayoutForDescriptors = false;