Add new common pipeline state accessors for descriptor-based accesses

* These are temporarily given separate names, to allow them to exist in parallel
  with the existing helpers, but in future these will be renamed when the older
  helpers are removed.
This commit is contained in:
baldurk
2024-03-18 10:24:07 +00:00
parent bbbfb5f3d2
commit 2a41eb54dd
7 changed files with 560 additions and 4 deletions
+1
View File
@@ -417,6 +417,7 @@ TEMPLATE_ARRAY_INSTANTIATE(rdcarray, Descriptor)
TEMPLATE_ARRAY_INSTANTIATE(rdcarray, SamplerDescriptor)
TEMPLATE_ARRAY_INSTANTIATE(rdcarray, DescriptorAccess)
TEMPLATE_ARRAY_INSTANTIATE(rdcarray, DescriptorLogicalLocation)
TEMPLATE_ARRAY_INSTANTIATE(rdcarray, UsedDescriptor)
TEMPLATE_NAMESPACE_ARRAY_INSTANTIATE(rdcarray, VKPipe, Attachment)
TEMPLATE_NAMESPACE_ARRAY_INSTANTIATE(rdcarray, VKPipe, BindingElement)
TEMPLATE_NAMESPACE_ARRAY_INSTANTIATE(rdcarray, VKPipe, DescriptorBinding)
+53
View File
@@ -954,6 +954,59 @@ first, and fall back to this name if no reflection information is available in t
DECLARE_REFLECTION_STRUCT(DescriptorLogicalLocation);
DOCUMENT(R"(Combined information about a single descriptor that has been used, both the information
about its access and its contents.
This is a helper struct for the common pipeline state abstraction to trade off simplicity of access
against optimal access.
)");
struct UsedDescriptor
{
DOCUMENT("");
UsedDescriptor() = default;
UsedDescriptor(const UsedDescriptor &) = default;
UsedDescriptor &operator=(const UsedDescriptor &) = default;
bool operator==(const UsedDescriptor &o) const
{
return access == o.access && descriptor == o.descriptor && sampler == o.sampler;
}
bool operator<(const UsedDescriptor &o) const
{
if(!(access == o.access))
return access < o.access;
if(!(descriptor == o.descriptor))
return descriptor < o.descriptor;
if(!(sampler == o.sampler))
return sampler < o.sampler;
return false;
}
DOCUMENT(R"(The access information of which shader reflection object accessed which descriptor.
:type: DescriptorAccess
)");
DescriptorAccess access;
DOCUMENT(R"(The contents of the accessed descriptor, if it is a normal non-sampler descriptor.
For sampler descriptors this is empty.
:type: Descriptor
)");
Descriptor descriptor;
DOCUMENT(R"(The contents of the accessed descriptor, if it is a sampler descriptor.
For normal descriptors this is empty.
:type: SamplerDescriptor
)");
SamplerDescriptor sampler;
};
DECLARE_REFLECTION_STRUCT(UsedDescriptor);
DOCUMENT("Information about a single constant buffer binding.");
struct BoundCBuffer
{
+100
View File
@@ -77,6 +77,14 @@ public:
m_GL = NULL;
m_Vulkan = vk;
}
void SetDescriptorAccess(rdcarray<DescriptorAccess> &&descriptorAccess,
rdcarray<Descriptor> &&descriptors, rdcarray<SamplerDescriptor> &&samplers)
{
m_Access = descriptorAccess;
m_Descriptors = descriptors;
m_SamplerDescriptors = samplers;
}
#endif
DOCUMENT(R"(Determines whether or not a capture is currently loaded.
@@ -381,6 +389,71 @@ For some APIs that don't distinguish by entry point, this may be empty.
)");
rdcarray<BoundResourceArray> GetReadWriteResources(ShaderStage stage, bool onlyUsed = false) const;
DOCUMENT(R"(Retrieves the current list of descriptor accesses, as cached from a call to
:meth:`ReplayController.GetDescriptorAccess`. The return value is identical, this is here for
convenience of access.
:return: The descriptor accesses.
:rtype: List[DescriptorAccess]
)");
const rdcarray<DescriptorAccess> &GetDescriptorAccess() const { return m_Access; }
DOCUMENT(R"(Retrieves all descriptor information for all descriptors accessed at the current event.
:param bool onlyUsed: Omit descriptors bound or declared but not accessed.
:return: All descriptors accessed at the current event.
:rtype: List[UsedDescriptor]
)");
rdcarray<UsedDescriptor> GetAllUsedDescriptors(bool onlyUsed = false) const;
DOCUMENT(R"(Retrieves the constant block at a given binding.
:param ShaderStage stage: The shader stage to fetch from.
:param int index: The index in the shader's ConstantBlocks array to look up.
:param int arrayIdx: For APIs that support arrays of constant buffers in a single binding, the index
in that array to look up.
:return: The constant buffer at the specified binding.
:rtype: UsedDescriptor
)");
UsedDescriptor GetConstantBlockDescriptor(ShaderStage stage, uint32_t index,
uint32_t arrayIdx) const;
DOCUMENT(R"(Retrieves the constant blocks used by a particular shader stage.
:param ShaderStage stage: The shader stage to fetch from.
:param bool onlyUsed: Omit descriptors bound or declared but not accessed.
:return: The currently bound constant blocks.
:rtype: List[UsedDescriptor]
)");
rdcarray<UsedDescriptor> GetConstantBlockDescriptors(ShaderStage stage, bool onlyUsed = false) const;
DOCUMENT(R"(Retrieves the read-only resources used by a particular shader stage.
:param ShaderStage stage: The shader stage to fetch from.
:param bool onlyUsed: Omit descriptors bound or declared but not accessed.
:return: The currently bound read-only resources.
:rtype: List[UsedDescriptor]
)");
rdcarray<UsedDescriptor> GetReadOnlyDescriptors(ShaderStage stage, bool onlyUsed = false) const;
DOCUMENT(R"(Retrieves the samplers bound to a particular shader stage.
:param ShaderStage stage: The shader stage to fetch from.
:param bool onlyUsed: Omit descriptors bound or declared but not accessed.
:return: The currently bound sampler resources.
:rtype: List[UsedDescriptor]
)");
rdcarray<UsedDescriptor> GetSamplerDescriptors(ShaderStage stage, bool onlyUsed = false) const;
DOCUMENT(R"(Retrieves the read/write resources used by a particular shader stage.
:param ShaderStage stage: The shader stage to fetch from.
:param bool onlyUsed: Omit descriptors bound or declared but not accessed.
:return: The currently bound read/write resources.
:rtype: List[UsedDescriptor]
)");
rdcarray<UsedDescriptor> GetReadWriteDescriptors(ShaderStage stage, bool onlyUsed = false) const;
DOCUMENT(R"(Retrieves the read/write resources bound to the depth-stencil output.
:return: The currently bound depth-stencil resource.
@@ -402,6 +475,27 @@ For some APIs that don't distinguish by entry point, this may be empty.
)");
rdcarray<BoundResource> GetOutputTargets() const;
DOCUMENT(R"(Retrieves the read/write resources bound to the depth-stencil output.
:return: The currently bound depth-stencil resource.
:rtype: Descriptor
)");
Descriptor GetDepthTargetDescriptor() const;
DOCUMENT(R"(Retrieves the read/write resources bound to the depth-stencil resolve output.
:return: The currently bound depth-stencil resolve resource.
:rtype: Descriptor
)");
Descriptor GetDepthResolveTargetDescriptor() const;
DOCUMENT(R"(Retrieves the resources bound to the color outputs.
:return: The currently bound output targets.
:rtype: List[Descriptor]
)");
rdcarray<Descriptor> GetOutputTargetDescriptors() const;
DOCUMENT(R"(Retrieves the current color blending states, per target.
:return: The currently color blend states.
@@ -446,4 +540,10 @@ private:
bool IsD3D12Stage(ShaderStage stage) const;
bool IsGLStage(ShaderStage stage) const;
bool IsVulkanStage(ShaderStage stage) const;
rdcarray<DescriptorAccess> m_Access;
rdcarray<Descriptor> m_Descriptors;
rdcarray<SamplerDescriptor> m_SamplerDescriptors;
void ApplyVulkanDynamicOffsets(UsedDescriptor &used) const;
};
+355
View File
@@ -1849,6 +1849,142 @@ rdcarray<BoundResourceArray> PipeState::GetReadWriteResources(ShaderStage stage,
return ret;
}
rdcarray<UsedDescriptor> PipeState::GetAllUsedDescriptors(bool onlyUsed) const
{
rdcarray<UsedDescriptor> ret;
ret.reserve(m_Access.size());
for(size_t i = 0; i < m_Access.size(); i++)
{
if(onlyUsed == false || !m_Access[i].staticallyUnused)
{
if(i < m_Descriptors.size())
ret.push_back({m_Access[i], m_Descriptors[i], m_SamplerDescriptors[i]});
}
}
return ret;
}
void PipeState::ApplyVulkanDynamicOffsets(UsedDescriptor &used) const
{
if(IsCaptureVK())
{
const rdcarray<VKPipe::DescriptorSet> &sets = used.access.stage == ShaderStage::Compute
? m_Vulkan->compute.descriptorSets
: m_Vulkan->graphics.descriptorSets;
for(const VKPipe::DescriptorSet &set : sets)
{
for(const VKPipe::DynamicOffset &offs : set.dynamicOffsets)
{
if(set.descriptorSetResourceId == used.access.descriptorStore &&
offs.descriptorByteOffset == used.access.byteOffset)
{
used.descriptor.byteOffset += offs.dynamicBufferByteOffset;
}
}
}
}
}
UsedDescriptor PipeState::GetConstantBlockDescriptor(ShaderStage stage, uint32_t index,
uint32_t arrayIdx) const
{
for(size_t i = 0; i < m_Access.size(); i++)
{
if(m_Access[i].stage == stage && IsConstantBlockDescriptor(m_Access[i].type) &&
m_Access[i].index == index && m_Access[i].arrayElement == arrayIdx)
{
if(i < m_Descriptors.size())
{
UsedDescriptor ret = {m_Access[i], m_Descriptors[i], SamplerDescriptor()};
ApplyVulkanDynamicOffsets(ret);
return ret;
}
break;
}
}
return UsedDescriptor();
}
rdcarray<UsedDescriptor> PipeState::GetConstantBlockDescriptors(ShaderStage stage, bool onlyUsed) const
{
rdcarray<UsedDescriptor> ret;
for(size_t i = 0; i < m_Access.size(); i++)
{
if(m_Access[i].stage == stage && IsConstantBlockDescriptor(m_Access[i].type) &&
(onlyUsed == false || !m_Access[i].staticallyUnused))
{
if(i < m_Descriptors.size())
{
ret.push_back({m_Access[i], m_Descriptors[i], SamplerDescriptor()});
ApplyVulkanDynamicOffsets(ret.back());
}
}
}
return ret;
}
rdcarray<UsedDescriptor> PipeState::GetReadOnlyDescriptors(ShaderStage stage, bool onlyUsed) const
{
rdcarray<UsedDescriptor> ret;
for(size_t i = 0; i < m_Access.size(); i++)
{
if(m_Access[i].stage == stage && IsReadOnlyDescriptor(m_Access[i].type) &&
(onlyUsed == false || !m_Access[i].staticallyUnused))
{
if(i < m_Descriptors.size() && i < m_SamplerDescriptors.size())
{
ret.push_back({m_Access[i], m_Descriptors[i], m_SamplerDescriptors[i]});
ApplyVulkanDynamicOffsets(ret.back());
}
}
}
return ret;
}
rdcarray<UsedDescriptor> PipeState::GetSamplerDescriptors(ShaderStage stage, bool onlyUsed) const
{
rdcarray<UsedDescriptor> ret;
for(size_t i = 0; i < m_Access.size(); i++)
{
if(m_Access[i].stage == stage && IsSamplerDescriptor(m_Access[i].type) &&
(onlyUsed == false || !m_Access[i].staticallyUnused))
{
if(i < m_Descriptors.size())
{
ret.push_back({m_Access[i], Descriptor(), m_SamplerDescriptors[i]});
ApplyVulkanDynamicOffsets(ret.back());
}
}
}
return ret;
}
rdcarray<UsedDescriptor> PipeState::GetReadWriteDescriptors(ShaderStage stage, bool onlyUsed) const
{
rdcarray<UsedDescriptor> ret;
for(size_t i = 0; i < m_Access.size(); i++)
{
if(m_Access[i].stage == stage && IsReadWriteDescriptor(m_Access[i].type) &&
(onlyUsed == false || !m_Access[i].staticallyUnused))
{
if(i < m_SamplerDescriptors.size())
ret.push_back({m_Access[i], m_Descriptors[i], SamplerDescriptor()});
}
}
return ret;
}
BoundResource PipeState::GetDepthTarget() const
{
if(IsCaptureLoaded())
@@ -2011,6 +2147,225 @@ rdcarray<BoundResource> PipeState::GetOutputTargets() const
return ret;
}
Descriptor PipeState::GetDepthTargetDescriptor() const
{
Descriptor ret;
ret.type = DescriptorType::ReadWriteImage;
if(IsCaptureLoaded())
{
if(IsCaptureD3D11())
{
const D3D11Pipe::View &depthTarget = m_D3D11->outputMerger.depthTarget;
ret.resource = depthTarget.resourceResourceId;
ret.view = depthTarget.viewResourceId;
ret.firstMip = depthTarget.firstMip & 0xff;
ret.numMips = depthTarget.numMips & 0xff;
ret.firstSlice = depthTarget.firstSlice & 0xffff;
ret.numSlices = depthTarget.numSlices & 0xffff;
ret.format = depthTarget.viewFormat;
ret.textureType = depthTarget.type;
}
else if(IsCaptureD3D12())
{
const D3D12Pipe::View &depthTarget = m_D3D12->outputMerger.depthTarget;
ret.resource = depthTarget.resourceId;
ret.firstMip = depthTarget.firstMip & 0xff;
ret.numMips = depthTarget.numMips & 0xff;
ret.firstSlice = depthTarget.firstSlice & 0xffff;
ret.numSlices = depthTarget.numSlices & 0xffff;
ret.format = depthTarget.viewFormat;
ret.textureType = depthTarget.type;
}
else if(IsCaptureGL())
{
const GLPipe::Attachment &depthAttachment = m_GL->framebuffer.drawFBO.depthAttachment;
ret.resource = depthAttachment.resourceId;
ret.firstMip = depthAttachment.mipLevel & 0xff;
ret.numMips = 1;
ret.firstSlice = depthAttachment.slice & 0xffff;
ret.numSlices = depthAttachment.numSlices & 0xffff;
ret.swizzle = depthAttachment.swizzle;
ret.textureType = ret.numSlices > 1 ? TextureType::Texture2DArray : TextureType::Texture2D;
}
else if(IsCaptureVK())
{
const VKPipe::RenderPass &rp = m_Vulkan->currentPass.renderpass;
const VKPipe::Framebuffer &fb = m_Vulkan->currentPass.framebuffer;
if(rp.depthstencilAttachment >= 0 && rp.depthstencilAttachment < fb.attachments.count())
{
const VKPipe::Attachment &depthAttachment = fb.attachments[rp.depthstencilAttachment];
ret.resource = depthAttachment.imageResourceId;
ret.view = depthAttachment.viewResourceId;
ret.firstMip = depthAttachment.firstMip & 0xff;
ret.numMips = depthAttachment.numMips & 0xff;
ret.firstSlice = depthAttachment.firstSlice & 0xffff;
ret.numSlices = depthAttachment.numSlices & 0xffff;
ret.format = depthAttachment.viewFormat;
ret.swizzle = depthAttachment.swizzle;
ret.textureType = ret.numSlices > 1 ? TextureType::Texture2DArray : TextureType::Texture2D;
}
}
}
return ret;
}
Descriptor PipeState::GetDepthResolveTargetDescriptor() const
{
Descriptor ret;
ret.type = DescriptorType::ReadWriteImage;
if(IsCaptureLoaded())
{
if(IsCaptureVK())
{
const VKPipe::RenderPass &rp = m_Vulkan->currentPass.renderpass;
const VKPipe::Framebuffer &fb = m_Vulkan->currentPass.framebuffer;
if(rp.depthstencilResolveAttachment >= 0 &&
rp.depthstencilResolveAttachment < fb.attachments.count())
{
const VKPipe::Attachment &depthResolveAttachment =
fb.attachments[rp.depthstencilResolveAttachment];
ret.resource = depthResolveAttachment.imageResourceId;
ret.view = depthResolveAttachment.viewResourceId;
ret.firstMip = depthResolveAttachment.firstMip & 0xff;
ret.numMips = depthResolveAttachment.numMips & 0xff;
ret.firstSlice = depthResolveAttachment.firstSlice & 0xffff;
ret.numSlices = depthResolveAttachment.numSlices & 0xffff;
ret.format = depthResolveAttachment.viewFormat;
ret.swizzle = depthResolveAttachment.swizzle;
ret.textureType = ret.numSlices > 1 ? TextureType::Texture2DArray : TextureType::Texture2D;
}
}
}
return ret;
}
rdcarray<Descriptor> PipeState::GetOutputTargetDescriptors() const
{
rdcarray<Descriptor> ret;
if(IsCaptureLoaded())
{
if(IsCaptureD3D11())
{
ret.resize(m_D3D11->outputMerger.renderTargets.count());
for(int i = 0; i < m_D3D11->outputMerger.renderTargets.count(); i++)
{
const D3D11Pipe::View &rt = m_D3D11->outputMerger.renderTargets[i];
ret[i].resource = rt.resourceResourceId;
ret[i].view = rt.viewResourceId;
ret[i].firstMip = rt.firstMip & 0xff;
ret[i].numMips = rt.numMips & 0xff;
ret[i].firstSlice = rt.firstSlice & 0xffff;
ret[i].numSlices = rt.numSlices & 0xffff;
ret[i].format = rt.viewFormat;
ret[i].textureType = rt.type;
}
}
else if(IsCaptureD3D12())
{
ret.resize(m_D3D12->outputMerger.renderTargets.count());
for(int i = 0; i < m_D3D12->outputMerger.renderTargets.count(); i++)
{
const D3D12Pipe::View &rt = m_D3D12->outputMerger.renderTargets[i];
ret[i].resource = rt.resourceId;
ret[i].firstMip = rt.firstMip & 0xff;
ret[i].numMips = rt.numMips & 0xff;
ret[i].firstSlice = rt.firstSlice & 0xffff;
ret[i].numSlices = rt.numSlices & 0xffff;
ret[i].format = rt.viewFormat;
ret[i].textureType = rt.type;
}
}
else if(IsCaptureGL())
{
ret.resize(m_GL->framebuffer.drawFBO.drawBuffers.count());
for(int i = 0; i < m_GL->framebuffer.drawFBO.drawBuffers.count(); i++)
{
int db = m_GL->framebuffer.drawFBO.drawBuffers[i];
if(db >= 0)
{
const GLPipe::Attachment &col = m_GL->framebuffer.drawFBO.colorAttachments[db];
ret[i].resource = col.resourceId;
ret[i].firstMip = col.mipLevel & 0xff;
ret[i].numMips = 1;
ret[i].firstSlice = col.slice & 0xffff;
ret[i].numSlices = col.numSlices & 0xffff;
ret[i].swizzle = col.swizzle;
ret[i].textureType =
ret[i].numSlices > 1 ? TextureType::Texture2DArray : TextureType::Texture2D;
}
}
}
else if(IsCaptureVK())
{
const VKPipe::RenderPass &rp = m_Vulkan->currentPass.renderpass;
const VKPipe::Framebuffer &fb = m_Vulkan->currentPass.framebuffer;
int idx = 0;
ret.resize(rp.colorAttachments.count() + rp.resolveAttachments.count());
for(int i = 0; i < rp.colorAttachments.count(); i++)
{
if(rp.colorAttachments[i] < (uint32_t)fb.attachments.count())
{
const VKPipe::Attachment &col = fb.attachments[rp.colorAttachments[i]];
ret[idx].resource = col.imageResourceId;
ret[idx].view = col.viewResourceId;
ret[idx].firstMip = col.firstMip & 0xff;
ret[idx].numMips = col.numMips & 0xff;
ret[idx].firstSlice = col.firstSlice & 0xffff;
ret[idx].numSlices = col.numSlices & 0xffff;
ret[idx].format = col.viewFormat;
ret[idx].swizzle = col.swizzle;
ret[idx].textureType =
ret[idx].numSlices > 1 ? TextureType::Texture2DArray : TextureType::Texture2D;
}
idx++;
}
for(int i = 0; i < rp.resolveAttachments.count(); i++)
{
if(rp.resolveAttachments[i] < (uint32_t)fb.attachments.count())
{
const VKPipe::Attachment &resolve = fb.attachments[rp.resolveAttachments[i]];
ret[idx].resource = resolve.imageResourceId;
ret[idx].view = resolve.viewResourceId;
ret[idx].firstMip = resolve.firstMip & 0xff;
ret[idx].numMips = resolve.numMips & 0xff;
ret[idx].firstSlice = resolve.firstSlice & 0xffff;
ret[idx].numSlices = resolve.numSlices & 0xffff;
ret[idx].format = resolve.viewFormat;
ret[idx].swizzle = resolve.swizzle;
ret[idx].textureType =
ret[idx].numSlices > 1 ? TextureType::Texture2DArray : TextureType::Texture2D;
}
idx++;
}
}
}
return ret;
}
rdcarray<ColorBlend> PipeState::GetColorBlends() const
{
if(IsCaptureLoaded())
+1 -1
View File
@@ -593,7 +593,7 @@ Multiple ranges within the store can be queried at once, and are returned in a c
:return: The descriptor accesses.
:rtype: List[DescriptorAccess]
)");
virtual rdcarray<DescriptorAccess> GetDescriptorAccess() = 0;
virtual const rdcarray<DescriptorAccess> &GetDescriptorAccess() = 0;
DOCUMENT(R"(Retrieve the logical locations for descriptors in a given descriptor store.
+49 -2
View File
@@ -131,11 +131,11 @@ rdcarray<Descriptor> ReplayController::GetDescriptors(ResourceId descriptorStore
return m_pDevice->GetDescriptors(m_pDevice->GetLiveID(descriptorStore), ranges);
}
rdcarray<DescriptorAccess> ReplayController::GetDescriptorAccess()
const rdcarray<DescriptorAccess> &ReplayController::GetDescriptorAccess()
{
CHECK_REPLAY_THREAD();
return m_pDevice->GetDescriptorAccess(m_EventID);
return m_PipeState.GetDescriptorAccess();
}
rdcarray<DescriptorLogicalLocation> ReplayController::GetDescriptorLocations(
@@ -2257,4 +2257,51 @@ void ReplayController::FetchPipelineState(uint32_t eventId)
m_PipeState.SetState(&m_GLPipelineState);
else if(m_APIProps.pipelineType == GraphicsAPI::Vulkan)
m_PipeState.SetState(&m_VulkanPipelineState);
rdcarray<DescriptorAccess> access = m_pDevice->GetDescriptorAccess(eventId);
rdcarray<Descriptor> descs;
rdcarray<SamplerDescriptor> samps;
descs.reserve(access.size());
samps.reserve(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 : access)
{
if(acc.descriptorStore != store)
{
if(store != ResourceId())
{
descs.append(m_pDevice->GetDescriptors(store, ranges));
samps.append(m_pDevice->GetSamplerDescriptors(store, ranges));
}
store = m_pDevice->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().count++;
continue;
}
DescriptorRange range;
range.offset = acc.byteOffset;
range.descriptorSize = acc.byteSize;
ranges.push_back(range);
}
if(store != ResourceId())
{
descs.append(m_pDevice->GetDescriptors(store, ranges));
samps.append(m_pDevice->GetSamplerDescriptors(store, ranges));
}
m_PipeState.SetDescriptorAccess(std::move(access), std::move(descs), std::move(samps));
}
+1 -1
View File
@@ -154,7 +154,7 @@ public:
const rdcarray<DescriptorRange> &ranges);
rdcarray<SamplerDescriptor> GetSamplerDescriptors(ResourceId descriptorStore,
const rdcarray<DescriptorRange> &ranges);
rdcarray<DescriptorAccess> GetDescriptorAccess();
const rdcarray<DescriptorAccess> &GetDescriptorAccess();
rdcarray<DescriptorLogicalLocation> GetDescriptorLocations(ResourceId descriptorStore,
const rdcarray<DescriptorRange> &ranges);