Handle immutable/static samplers on Vulkan and D3D12

* On vulkan immutable samplers mostly work as-is because they have a descriptor
  set space even if they may not be written dynamically. We just set a flag so
  the replay API is aware they're compile-time constant.
* On D3D12 we follow the path of root constants and other non-descriptor backed
  bindings, by creating virtual descriptor store in the root signature where the
  static samplers are created.
This commit is contained in:
baldurk
2024-03-11 13:58:31 +00:00
parent fa22c7c7fc
commit 13986a0da5
8 changed files with 117 additions and 42 deletions
+44
View File
@@ -1089,6 +1089,50 @@ rdcstr PIX3DecodeEventString(const UINT64 *pData, UINT64 &color)
return formatString;
}
D3D12_SAMPLER_DESC2 ConvertStaticSampler(const D3D12_STATIC_SAMPLER_DESC1 &samp)
{
D3D12_SAMPLER_DESC2 desc;
desc.Filter = samp.Filter;
desc.AddressU = samp.AddressU;
desc.AddressV = samp.AddressV;
desc.AddressW = samp.AddressW;
desc.MipLODBias = samp.MipLODBias;
desc.MaxAnisotropy = samp.MaxAnisotropy;
desc.ComparisonFunc = samp.ComparisonFunc;
switch(samp.BorderColor)
{
default:
case D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK:
desc.FloatBorderColor[0] = desc.FloatBorderColor[1] = desc.FloatBorderColor[2] =
desc.FloatBorderColor[3] = 0.0f;
break;
case D3D12_STATIC_BORDER_COLOR_OPAQUE_BLACK:
desc.FloatBorderColor[0] = desc.FloatBorderColor[1] = desc.FloatBorderColor[2] = 0.0f;
desc.FloatBorderColor[3] = 1.0f;
break;
case D3D12_STATIC_BORDER_COLOR_OPAQUE_WHITE:
desc.FloatBorderColor[0] = desc.FloatBorderColor[1] = desc.FloatBorderColor[2] =
desc.FloatBorderColor[3] = 1.0f;
break;
case D3D12_STATIC_BORDER_COLOR_OPAQUE_BLACK_UINT:
desc.UintBorderColor[0] = desc.UintBorderColor[1] = desc.UintBorderColor[2] = 0;
desc.UintBorderColor[3] = 1;
// this flag is optional in D3D, add it here to ensure we can check it elsewhere
desc.Flags |= D3D12_SAMPLER_FLAG_UINT_BORDER_COLOR;
break;
case D3D12_STATIC_BORDER_COLOR_OPAQUE_WHITE_UINT:
desc.UintBorderColor[0] = desc.UintBorderColor[1] = desc.UintBorderColor[2] =
desc.UintBorderColor[3] = 1;
// this flag is optional in D3D, add it here to ensure we can check it elsewhere
desc.Flags |= D3D12_SAMPLER_FLAG_UINT_BORDER_COLOR;
break;
}
desc.MinLOD = samp.MinLOD;
desc.MaxLOD = samp.MaxLOD;
desc.Flags = samp.Flags;
return desc;
}
D3D12_DEPTH_STENCILOP_DESC1 Upconvert(const D3D12_DEPTH_STENCILOP_DESC &face)
{
D3D12_DEPTH_STENCILOP_DESC1 ret = {};
+1
View File
@@ -233,6 +233,7 @@ struct BarrierSet
D3D12_DEPTH_STENCIL_DESC2 Upconvert(const D3D12_DEPTH_STENCIL_DESC1 &desc);
D3D12_RASTERIZER_DESC2 Upconvert(const D3D12_RASTERIZER_DESC &desc);
D3D12_SAMPLER_DESC2 ConvertStaticSampler(const D3D12_STATIC_SAMPLER_DESC1 &samp);
ShaderStageMask ConvertVisibility(D3D12_SHADER_VISIBILITY ShaderVisibility);
UINT GetNumSubresources(ID3D12Device *dev, const D3D12_RESOURCE_DESC *desc);
+47 -5
View File
@@ -2346,6 +2346,12 @@ rdcarray<Descriptor> D3D12Replay::GetDescriptors(ResourceId descriptorStore,
ID3D12DeviceChild *res = rm->GetCurrentAs<ID3D12DeviceChild>(descriptorStore);
if(WrappedID3D12RootSignature::IsAlloc(res))
{
// root signature descriptor storage is for static samplers
return ret;
}
if(WrappedID3D12PipelineState::IsAlloc(res))
{
const D3D12RenderState &rs = m_pDevice->GetQueue()->GetCommandData()->m_RenderState;
@@ -2502,6 +2508,32 @@ rdcarray<SamplerDescriptor> D3D12Replay::GetSamplerDescriptors(ResourceId descri
ID3D12DeviceChild *res = rm->GetCurrentAs<ID3D12DeviceChild>(descriptorStore);
if(WrappedID3D12RootSignature::IsAlloc(res))
{
WrappedID3D12RootSignature *sig = (WrappedID3D12RootSignature *)res;
size_t dst = 0;
for(const DescriptorRange &r : ranges)
{
uint32_t staticIdx = r.offset;
for(uint32_t i = 0; i < r.count; i++)
{
if(staticIdx >= sig->sig.StaticSamplers.size())
{
// silently drop out of bounds descriptor reads
}
else
{
FillSamplerDescriptor(ret[dst], ConvertStaticSampler(sig->sig.StaticSamplers[staticIdx]));
ret[dst].creationTimeConstant = true;
}
dst++;
staticIdx++;
}
}
return ret;
}
if(WrappedID3D12PipelineState::IsAlloc(res))
{
// root constants, not sampler data
@@ -2577,6 +2609,20 @@ rdcarray<DescriptorAccess> D3D12Replay::GetDescriptorAccess()
for(DescriptorAccess &access : ret)
{
const D3D12RenderState::RootSignature &rootSig = pipe->IsGraphics() ? rs.graphics : rs.compute;
uint32_t rootIndex = (uint32_t)access.byteSize;
access.byteSize = 1;
// access off the end of the root signature list indicates a static sampler. We virtualise
// this as root signature descriptor storage
if(access.type == DescriptorType::Sampler && rootIndex >= rootSig.sigelems.size())
{
access.descriptorStore = rm->GetOriginalID(rootSig.rootsig);
// the access byteOffset is the index of the static sampler
continue;
}
if(access.type == DescriptorType::Sampler)
access.descriptorStore =
samplerHeap ? rm->GetOriginalID(samplerHeap->GetResourceID()) : ResourceId();
@@ -2584,10 +2630,7 @@ rdcarray<DescriptorAccess> D3D12Replay::GetDescriptorAccess()
access.descriptorStore =
resourceHeap ? rm->GetOriginalID(resourceHeap->GetResourceID()) : ResourceId();
uint32_t rootIndex = (uint32_t)access.byteSize;
const D3D12RenderState::SignatureElement &rootEl =
pipe->IsGraphics() ? rs.graphics.sigelems[rootIndex] : rs.compute.sigelems[rootIndex];
access.byteSize = 1;
const D3D12RenderState::SignatureElement &rootEl = rootSig.sigelems[rootIndex];
// this indicates a root parameter
if(access.byteOffset == ~0U)
@@ -2598,7 +2641,6 @@ rdcarray<DescriptorAccess> D3D12Replay::GetDescriptorAccess()
// other types of virtual constants to handle we can use the pipeline state directly
access.descriptorStore = rm->GetOriginalID(pipe->GetResourceID());
access.byteOffset = rootIndex;
access.byteSize = 1;
}
else
{
@@ -713,6 +713,21 @@ rdcpair<uint32_t, uint32_t> FindMatchingRootParameter(const D3D12RootSignature *
}
}
// if not found above, and looking for samplers, look at static samplers next
if(rangeType == D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER)
{
// indicate that we're looking up static samplers
uint32_t numRoots = (uint32_t)sig->Parameters.size();
for(uint32_t samp = 0; samp < sig->StaticSamplers.size(); samp++)
{
if(sig->StaticSamplers[samp].RegisterSpace == space &&
sig->StaticSamplers[samp].ShaderRegister == bind)
{
return {numRoots, samp};
}
}
}
return {~0U, 0};
}
+1 -37
View File
@@ -619,43 +619,7 @@ D3D12Descriptor D3D12DebugAPIWrapper::FindDescriptor(DXBCBytecode::OperandType t
{
if(samp.RegisterSpace == slot.registerSpace && samp.ShaderRegister == slot.shaderRegister)
{
D3D12_SAMPLER_DESC2 desc;
desc.Filter = samp.Filter;
desc.AddressU = samp.AddressU;
desc.AddressV = samp.AddressV;
desc.AddressW = samp.AddressW;
desc.MipLODBias = samp.MipLODBias;
desc.MaxAnisotropy = samp.MaxAnisotropy;
desc.ComparisonFunc = samp.ComparisonFunc;
switch(samp.BorderColor)
{
default:
case D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK:
desc.FloatBorderColor[0] = desc.FloatBorderColor[1] = desc.FloatBorderColor[2] =
desc.FloatBorderColor[3] = 0.0f;
break;
case D3D12_STATIC_BORDER_COLOR_OPAQUE_BLACK:
desc.FloatBorderColor[0] = desc.FloatBorderColor[1] = desc.FloatBorderColor[2] = 0.0f;
desc.FloatBorderColor[3] = 1.0f;
break;
case D3D12_STATIC_BORDER_COLOR_OPAQUE_WHITE:
desc.FloatBorderColor[0] = desc.FloatBorderColor[1] = desc.FloatBorderColor[2] =
desc.FloatBorderColor[3] = 1.0f;
break;
case D3D12_STATIC_BORDER_COLOR_OPAQUE_BLACK_UINT:
desc.UintBorderColor[0] = desc.UintBorderColor[1] = desc.UintBorderColor[2] = 0;
desc.UintBorderColor[3] = 1;
break;
case D3D12_STATIC_BORDER_COLOR_OPAQUE_WHITE_UINT:
desc.UintBorderColor[0] = desc.UintBorderColor[1] = desc.UintBorderColor[2] =
desc.UintBorderColor[3] = 1;
break;
}
desc.MinLOD = samp.MinLOD;
desc.MaxLOD = samp.MaxLOD;
desc.Flags = samp.Flags;
D3D12_SAMPLER_DESC2 desc = ConvertStaticSampler(samp);
descriptor.Init(&desc);
return descriptor;
}
+3
View File
@@ -716,6 +716,9 @@ struct DescriptorSetSlot
// used for buffers and inline blocks. We could steal some bits here if we needed them since 48
// bits would be plenty for a long time.
//
// Immutable samplers set this to 1 to indicate for replay purposes that the sampler came from an
// immutable binding when looking purely at the descriptor without knowing its layout
VkDeviceSize offset;
// resource IDs are kept separate rather than overlapping/union'ing with other types. This
+2
View File
@@ -453,6 +453,8 @@ void DescSetLayout::CreateBindingsArray(BindingStorage &bindingStorage, uint32_t
// samplers set the type from the layout. That way even if the descriptor is never
// written we still process immutable samplers properly.
bindingStorage.binds[i][a].type = convert(bindings[i].layoutDescType);
bindingStorage.binds[i][a].offset = 1;
}
}
+4
View File
@@ -2516,6 +2516,10 @@ void VulkanReplay::FillSamplerDescriptor(SamplerDescriptor &dstel, const Descrip
dstel.unnormalized = sampl.unnormalizedCoordinates;
dstel.seamlessCubemaps = sampl.seamless;
// immutable samplers set the offset to non-zero so that we can check it here without knowing what
// layout this descriptor binding came from
dstel.creationTimeConstant = srcel.offset != 0;
if(sampl.ycbcr != ResourceId())
{
const VulkanCreationInfo::YCbCrSampler &ycbcr = c.m_YCbCrSampler[sampl.ycbcr];