Optimise UI for large descriptor arrays with few dynamically used binds

* We tune the pipeline state view and texture viewer to only iterate over a
  small list of dynamically used binds in the (vastly more common) case where
  unused binds are not being shown.
This commit is contained in:
baldurk
2020-09-02 13:50:22 +01:00
parent afe3bee92d
commit 56f82f6bf1
11 changed files with 235 additions and 104 deletions
+11 -1
View File
@@ -315,6 +315,7 @@ struct BoundResourceArray
BoundResourceArray(Bindpoint b, const rdcarray<BoundResource> &r) : bindPoint(b), resources(r)
{
dynamicallyUsedCount = (uint32_t)r.size();
firstIndex = 0;
}
// for convenience for searching the array, we compare only using the BindPoint
bool operator==(const BoundResourceArray &o) const { return bindPoint == o.bindPoint; }
@@ -330,8 +331,17 @@ struct BoundResourceArray
Some APIs provide fine-grained usage based on dynamic shader feedback, to support 'bindless'
scenarios where only a small sparse subset of bound resources are actually used.
If this information isn't present this will be set to a large number.
)");
uint32_t dynamicallyUsedCount = 0;
uint32_t dynamicallyUsedCount = ~0U;
DOCUMENT(R"(Gives the array index of the first binding in :data:`resource`. If only a small subset
of the resources are used by the shader then the array may be rebased such that the first element is
not array index 0.
For more information see :data:`VKBindingElement.dynamicallyUsed`.
)");
int32_t firstIndex = 0;
};
DECLARE_REFLECTION_STRUCT(BoundResourceArray);
+6 -2
View File
@@ -305,10 +305,12 @@ For some APIs that don't distinguish by entry point, this may be empty.
DOCUMENT(R"(Retrieves the read-only resources bound to a particular shader stage.
:param ShaderStage stage: The shader stage to fetch from.
:param bool onlyUsed: Return only a subset of resources containing those actually used by the
shader.
:return: The currently bound read-only resources.
:rtype: ``list`` of :class:`BoundResourceArray` entries
)");
rdcarray<BoundResourceArray> GetReadOnlyResources(ShaderStage stage) const;
rdcarray<BoundResourceArray> GetReadOnlyResources(ShaderStage stage, bool onlyUsed = false) const;
DOCUMENT(R"(Retrieves the samplers bound to a particular shader stage.
@@ -321,10 +323,12 @@ For some APIs that don't distinguish by entry point, this may be empty.
DOCUMENT(R"(Retrieves the read/write resources bound to a particular shader stage.
:param ShaderStage stage: The shader stage to fetch from.
:param bool onlyUsed: Return only a subset of resources containing those actually used by the
shader.
:return: The currently bound read/write resources.
:rtype: ``list`` of :class:`BoundResourceArray` entries
)");
rdcarray<BoundResourceArray> GetReadWriteResources(ShaderStage stage) const;
rdcarray<BoundResourceArray> GetReadWriteResources(ShaderStage stage, bool onlyUsed = false) const;
DOCUMENT(R"(Retrieves the read/write resources bound to the depth-stencil output.
+48 -18
View File
@@ -1132,7 +1132,7 @@ rdcarray<BoundResourceArray> PipeState::GetSamplers(ShaderStage stage) const
return ret;
}
rdcarray<BoundResourceArray> PipeState::GetReadOnlyResources(ShaderStage stage) const
rdcarray<BoundResourceArray> PipeState::GetReadOnlyResources(ShaderStage stage, bool onlyUsed) const
{
rdcarray<BoundResourceArray> ret;
@@ -1251,18 +1251,33 @@ rdcarray<BoundResourceArray> PipeState::GetReadOnlyResources(ShaderStage stage)
ret.push_back(BoundResourceArray());
ret.back().bindPoint = Bindpoint(set, slot);
rdcarray<BoundResource> &val = ret.back().resources;
val.resize(bind.descriptorCount);
uint32_t count = bind.descriptorCount;
uint32_t firstIdx = 0;
if(onlyUsed)
{
firstIdx = (uint32_t)bind.firstUsedIndex;
if(bind.dynamicallyUsedCount < count)
count = bind.dynamicallyUsedCount;
if((uint32_t)bind.lastUsedIndex < count)
count = uint32_t(bind.lastUsedIndex - bind.firstUsedIndex + 1);
}
rdcarray<BoundResource> &val = ret.back().resources;
val.reserve(count);
ret.back().firstIndex = (int32_t)firstIdx;
ret.back().dynamicallyUsedCount = bind.dynamicallyUsedCount;
for(uint32_t i = 0; i < bind.descriptorCount; i++)
BoundResource res;
for(uint32_t i = firstIdx; i < firstIdx + count; i++)
{
val[i].resourceId = bind.binds[i].resourceResourceId;
val[i].dynamicallyUsed = bind.binds[i].dynamicallyUsed;
val[i].firstMip = (int)bind.binds[i].firstMip;
val[i].firstSlice = (int)bind.binds[i].firstSlice;
val[i].typeCast = bind.binds[i].viewFormat.compType;
res.resourceId = bind.binds[i].resourceResourceId;
res.dynamicallyUsed = bind.binds[i].dynamicallyUsed;
res.firstMip = (int)bind.binds[i].firstMip;
res.firstSlice = (int)bind.binds[i].firstSlice;
res.typeCast = bind.binds[i].viewFormat.compType;
val.push_back(res);
}
}
}
@@ -1275,7 +1290,7 @@ rdcarray<BoundResourceArray> PipeState::GetReadOnlyResources(ShaderStage stage)
return ret;
}
rdcarray<BoundResourceArray> PipeState::GetReadWriteResources(ShaderStage stage) const
rdcarray<BoundResourceArray> PipeState::GetReadWriteResources(ShaderStage stage, bool onlyUsed) const
{
rdcarray<BoundResourceArray> ret;
@@ -1417,18 +1432,33 @@ rdcarray<BoundResourceArray> PipeState::GetReadWriteResources(ShaderStage stage)
ret.push_back(BoundResourceArray());
ret.back().bindPoint = Bindpoint(set, slot);
rdcarray<BoundResource> &val = ret.back().resources;
val.resize(bind.descriptorCount);
uint32_t count = bind.descriptorCount;
uint32_t firstIdx = 0;
if(onlyUsed)
{
firstIdx = (uint32_t)bind.firstUsedIndex;
if(bind.dynamicallyUsedCount < count)
count = bind.dynamicallyUsedCount;
if((uint32_t)bind.lastUsedIndex < count)
count = uint32_t(bind.lastUsedIndex - bind.firstUsedIndex + 1);
}
rdcarray<BoundResource> &val = ret.back().resources;
val.reserve(count);
ret.back().firstIndex = (int32_t)firstIdx;
ret.back().dynamicallyUsedCount = bind.dynamicallyUsedCount;
for(uint32_t i = 0; i < bind.descriptorCount; i++)
BoundResource res;
for(uint32_t i = firstIdx; i < firstIdx + count; i++)
{
val[i].resourceId = bind.binds[i].resourceResourceId;
val[i].dynamicallyUsed = bind.binds[i].dynamicallyUsed;
val[i].firstMip = (int)bind.binds[i].firstMip;
val[i].firstSlice = (int)bind.binds[i].firstSlice;
val[i].typeCast = bind.binds[i].viewFormat.compType;
res.resourceId = bind.binds[i].resourceResourceId;
res.dynamicallyUsed = bind.binds[i].dynamicallyUsed;
res.firstMip = (int)bind.binds[i].firstMip;
res.firstSlice = (int)bind.binds[i].firstSlice;
res.typeCast = bind.binds[i].viewFormat.compType;
val.push_back(res);
}
}
}
+17 -1
View File
@@ -246,7 +246,23 @@ redundant iteration to determine whether any bindings are present.
For more information see :data:`VKBindingElement.dynamicallyUsed`.
)");
uint32_t dynamicallyUsedCount = 0;
uint32_t dynamicallyUsedCount = ~0U;
DOCUMENT(R"(Gives the index of the first binding in :data:`binds` that is dynamically used. Useful
to avoid redundant iteration in very large descriptor arrays with a small subset that are used.
For more information see :data:`VKBindingElement.dynamicallyUsed`.
)");
int32_t firstUsedIndex = 0;
DOCUMENT(R"(Gives the index of the first binding in :data:`binds` that is dynamically used. Useful
to avoid redundant iteration in very large descriptor arrays with a small subset that are used.
.. note::
This may be set to a higher value than the number of bindings, if no dynamic use information is
available. Ensure that this is an additional check on the bind and the count is still respected.
For more information see :data:`VKBindingElement.dynamicallyUsed`.
)");
int32_t lastUsedIndex = 0x7fffffff;
DOCUMENT("The :class:`BindType` of this binding.");
BindType type = BindType::Unknown;
DOCUMENT("The :class:`ShaderStageMask` where this binding is visible.");
+42 -3
View File
@@ -1036,7 +1036,20 @@ void VulkanReplay::SavePipelineState(uint32_t eventId)
VkMarkerRegion::End();
m_VulkanPipelineState = VKPipe::State();
{
// reset the pipeline state, but keep the descriptor set arrays. This prevents needless
// reallocations, we'll ensure that descriptors are fully overwritten below.
rdcarray<VKPipe::DescriptorSet> graphicsDescriptors;
rdcarray<VKPipe::DescriptorSet> computeDescriptors;
m_VulkanPipelineState.graphics.descriptorSets.swap(graphicsDescriptors);
m_VulkanPipelineState.compute.descriptorSets.swap(computeDescriptors);
m_VulkanPipelineState = VKPipe::State();
m_VulkanPipelineState.graphics.descriptorSets.swap(graphicsDescriptors);
m_VulkanPipelineState.compute.descriptorSets.swap(computeDescriptors);
}
m_VulkanPipelineState.pushconsts.resize(state.pushConstSize);
memcpy(m_VulkanPipelineState.pushconsts.data(), state.pushconsts, state.pushConstSize);
@@ -1668,11 +1681,18 @@ void VulkanReplay::SavePipelineState(uint32_t eventId)
default: dst.bindings[b].type = BindType::Unknown; RDCERR("Unexpected descriptor type");
}
dst.bindings[b].firstUsedIndex = -1;
dst.bindings[b].lastUsedIndex = -1;
dst.bindings[b].dynamicallyUsedCount = 0;
dst.bindings[b].binds.resize(dst.bindings[b].descriptorCount);
for(uint32_t a = 0; a < dst.bindings[b].descriptorCount; a++)
{
VKPipe::BindingElement &dstel = dst.bindings[b].binds[a];
// clear it so we don't have to manually reset all elements back to normal
memset(&dstel, 0, sizeof(dstel));
curBind.arrayIndex = a;
// if we have a list of used binds, and this is an array descriptor (so would be
@@ -1697,22 +1717,34 @@ void VulkanReplay::SavePipelineState(uint32_t eventId)
}
// the next used bind is equal to this one. Mark it as dynamically used, and consume
if(curBind == *usedBindsData)
if(usedBindsSize > 0 && curBind == *usedBindsData)
{
dstel.dynamicallyUsed = true;
usedBindsData++;
usedBindsSize--;
}
// the next used bind is after the current one, this is not used.
else if(curBind < *usedBindsData)
else if(usedBindsSize > 0 && curBind < *usedBindsData)
{
dstel.dynamicallyUsed = false;
}
}
}
else
{
dstel.dynamicallyUsed = true;
}
if(dstel.dynamicallyUsed)
{
dst.bindings[b].dynamicallyUsedCount++;
// we iterate in forward order, so we can unconditinoally set the last bind to the
// current one, and only set the first bind if we haven't encountered one before
dst.bindings[b].lastUsedIndex = a;
if(dst.bindings[b].firstUsedIndex < 0)
dst.bindings[b].firstUsedIndex = a;
}
// first handle the sampler separately because it might be in a combined descriptor
if(layoutBind.descriptorType == VK_DESCRIPTOR_TYPE_SAMPLER ||
@@ -1893,6 +1925,13 @@ void VulkanReplay::SavePipelineState(uint32_t eventId)
dst.bindings[b].binds[a].byteSize = info[a].bufferInfo.range;
}
}
// if no bindings were set these will still be negative. Set them to something sensible.
if(dst.bindings[b].firstUsedIndex < 0)
{
dst.bindings[b].firstUsedIndex = 0;
dst.bindings[b].lastUsedIndex = 0x7fffffff;
}
}
}
}
+3 -1
View File
@@ -1896,12 +1896,14 @@ void DoSerialise(SerialiserType &ser, VKPipe::DescriptorBinding &el)
{
SERIALISE_MEMBER(descriptorCount);
SERIALISE_MEMBER(dynamicallyUsedCount);
SERIALISE_MEMBER(firstUsedIndex);
SERIALISE_MEMBER(lastUsedIndex);
SERIALISE_MEMBER(type);
SERIALISE_MEMBER(stageFlags);
SERIALISE_MEMBER(binds);
SIZE_CHECK(40);
SIZE_CHECK(48);
}
template <typename SerialiserType>