summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorTim Foley <tfoleyNV@users.noreply.github.com>2020-08-05 11:47:18 -0700
committerGitHub <noreply@github.com>2020-08-05 11:47:18 -0700
commite713b56a63dcbf945e3e0e6d82666318795c74ff (patch)
tree7883169c68f9516d1ebff70c5529b6f10933e1d5 /tools
parent6fb2aa70a2681bffbac7e8de67e9598105389945 (diff)
Change the policy for entry-point uniform parameters on Vulkan (#1476)
Entry point `uniform` parameters were a feature of the original Cg and HLSL, but have not been used much in production shader code. One of our goals on Slang is to reduce the (ab)use of the global scope, so bringing entry point `uniform` parameters up to a greater level of usability is an important goal. Some policy choices about how global vs. entry-point `uniform` parameters behave have already been made, that shape decisions looking forward: * For DXBC/DXIL, it makes the most sense to follow the lead of fxc/dxc, by treating entry point `uniform` parameters as a kind of syntax sugar for global shader parameters. Any parameters of "ordinary" types are bundles up into an implicit constant buffer, and all the resources (including the implicit constant buffer) are assigned `register`s just as for globals. It is up to the application to decide how to bind those parameters via a root signature (using root descriptors, root constants, descriptor tables, local vs. global root signature, etc.) * For CPU, it makes sense to pass global vs. entry-point parameters as two different pointers, although the details of what we do for CPU are the least constrained across all current targets. * For CUDA compute, it makes the most sense to map global shader parameters to `__constant__` global data, and entry-point `uniform` parameters to kernel parameters. This choice ensures that the signature of a kernel when translated from Slang->CUDA follows the Principle of Least Surprise, at the cost of making entry-point vs. global parameters be passed via different mechanisms. * For OptiX ray tracing, it makes sense to expand on the precedent from CUDA compute: pass global parameters via global `__constant__` data (as is already expected by OptiX for whole-launch parameters), and pass entry-point `uniform` parameters via the "shader record." This establishes a precedent that for ray-tracing shaders, global-scope parameters map to the "global root signature" concept from DXR, while entry-point `uniform` parameters map to a "local root signature" or "shader record." * For Vulkan ray tracing, the precedent from OptiX then argues that entry-point `uniform` parameters should map to the Vulkan "shader record" concept (and thus cannot support things like resource types). * The remaining interesting case is what to do for non-ray-tracing shaders on Vulkan. The dev team agrees that the most reasonable choice to make for non-ray-tracing Vulkan shaders is to map entry-point `uniform` parameters to "push constants." In particular, this makes it easy to express the case of a compute kernel with direct parameters of ordinary/value types in the way that will be implemented most efficiently. The big picture is then that a kernel like: ```hlsl void computeMain(uniform float someValue) { ... } ``` will map to output GLSL like: ```glsl layout(push_constant) uniform { float someValue; } U; void main() { ... } ``` If the user really wanted a constant-buffer binding to be created instead, they can easily change their input to make the buffer explicit: ```hlsl struct Params { float someValue; } void computeMain(uniform ConstantBuffer<Params> params) { ... } ``` (Forcing the user to be explicit about the desire for a buffer here creates a nice symmetry between Vulkan and CUDA; in the first case the user sets up the data in host memory and passes it to the GPU by copy, while in the second case the user must allocate and set up a device-memory buffer for the data. This symmetry extends to D3D if the application chooses to map entry-point `uniform` parameters to root constants.) This change implements logic in the "parameter binding" part of the Slang compiler to make sure that entry-point `uniform` parameters are wrapped up in a push-constant buffer rather than an ordinary constant buffer for non-ray-tracing shaders on Vulkan (and in a shader record "buffer" for the ray-tracing case). The majority of the actual work was in adding support for root/push constants to the test framework and the graphics API abstraction it uses. To be clear about that support: * Root constant ranges are (perhaps confusingly) treated as a new kind of "slot" that can appear on a descriptor set. This choice ensures that the implicit numbering of registers/spaces used by the back-ends can account for these ranges correctly. * The `TEST_INPUT` lines are extended to allow a `root_constants` case that behaves more or less like `cbuffer` * The CPU and CUDA paths can treat a `root_constants` input identically to a `cbuffer`. They already allocate the actual buffers based on reflection, and just use `cbuffer` as a directive that causes bytes to be copied in. * On D3D12 and Vulkan, a descriptor set allocates a `List<char>` to hold the bytes of root constant data assigned into it, and these bytes are flushed to the command list when the table is actually bound (usually right before rendering). * On D3D11, a descriptor set treats a root constant range more or less like a constant buffer range (with a single buffer), except that it also automatically allocates a buffer to hold the data. Assigning "root constant" data automatically copies it into that buffer. The small number of tests that used entry-point `uniform` parameters of ordinary types were updated to use the new `root_constant` input type, and the bugs that surfaced were fixed. A new test to confirm that entry-point `uniform` parameters map to the shader record for VK ray tracing was added. An important but technically unrelated change is the removal of the `DescriptorSetImpl::Binding` type and related function from the Vulkan implementation of `Renderer`. That type was created to ensure that objects that are bound into a descriptor set don't get released while the descriptor set is still alive, but the implementation relied on a complicated linear search to check for existing bindings, which could create a performance issue for descriptor sets that include large arrays of descriptors. The new implementation makes use of the approach already present in the various `Renderer` implementations (including the Vulkan one) for assigning ranges in a descriptor set a flat/linear index for where their pertinent data is to be bound. As a result, the Vulkan `DescriptorSetImpl` now uses a single flat array of `RefPtr`s to track bound objects, and has no need for linear search when binding. Co-authored-by: Yong He <yonghe@outlook.com>
Diffstat (limited to 'tools')
-rw-r--r--tools/gfx/d3d11/render-d3d11.cpp166
-rw-r--r--tools/gfx/d3d12/render-d3d12.cpp254
-rw-r--r--tools/gfx/open-gl/render-gl.cpp16
-rw-r--r--tools/gfx/render.h6
-rw-r--r--tools/gfx/vulkan/render-vk.cpp395
-rw-r--r--tools/gfx/vulkan/vk-api.h1
-rw-r--r--tools/render-test/shader-input-layout.cpp5
-rw-r--r--tools/render-test/shader-input-layout.h3
-rw-r--r--tools/render-test/shader-renderer-util.cpp26
9 files changed, 749 insertions, 123 deletions
diff --git a/tools/gfx/d3d11/render-d3d11.cpp b/tools/gfx/d3d11/render-d3d11.cpp
index cf2ae75e2..4eba4edaf 100644
--- a/tools/gfx/d3d11/render-d3d11.cpp
+++ b/tools/gfx/d3d11/render-d3d11.cpp
@@ -139,14 +139,66 @@ public:
class DescriptorSetLayoutImpl : public DescriptorSetLayout
{
public:
+ // Each descriptor set for the D3D11 renderer stores distinct
+ // arrays for each kind of shader-visible entity D3D11 understands:
+ // shader resource views (SRVs), unordered access views (UAVs),
+ // constant buffers (CBs), and samplers.
+ //
+ // (This description will ignore compiled image/sampler pairs,
+ // since they aren't really well supported at present)
+ //
+ // Each descriptor range in an input `DescriptorSetLayout::Desc`
+ // will map to a range of entries in one of those arrays, but
+ // in general there can be multiple `DescriptorSlotType`s that
+ // map to the same `D3D11DescriptorSlotType`.
+ //
+ // Each `RangeInfo` in a D3D11 descriptor set layout represents
+ // of of the descriptor slot ranges in the original `Desc`,
+ // and stores the information that is relevant to its layout
+ // in our D3D11 implementation.
+
struct RangeInfo
{
+ /// The type of descriptors in the range, in D3D11 terms (SRVs, UAVs, etc.)
D3D11DescriptorSlotType type;
+
+ /// The start index of this range in the relevant descriptor-type-specific array.
+ ///
+ /// Note: This is *not* the same as the index of the range, both because multiple
+ /// `DescriptorSlotType`s might map to the same array in the D3D11 implementation,
+ /// and also because a given range might store multiple descriptors (so a 3-texture
+ /// range that comes after a 5-texture range will have an `arrayIndex` of 5 but
+ /// a range index of 1).
+ ///
UInt arrayIndex;
+
+ /// For the case of a combined image/sampler pair, the `arrayIndex` is an index
+ /// into the array of SRVs, and we store a separate index into the array of
+ /// samplers.
+ ///
UInt pairedSamplerArrayIndex;
};
List<RangeInfo> m_ranges;
+ // Because D3D11 does not support root constants as they appear in
+ // D3D12 and Vulkan, we need to map root-constant ranges in the original `Desc`
+ // over to ordinary constant buffers. Each root-constant range (of whatever
+ // size) will map to a constant-buffer range of a single buffer.
+ //
+ // In order to be able to properly allocate/initialize these root constant
+ // buffers, we store additional information about them in a flattened array
+ // that only stores information for root constant ranges.
+
+ struct RootConstantRangeInfo
+ {
+ /// Index of the `RangeInfo` corresponding to this root-constant range
+ Index rangeIndex;
+
+ /// Size of the original root-constant range, in bytes.
+ UInt size;
+ };
+ List<RootConstantRangeInfo> m_rootConstantRanges;
+
UInt m_counts[int(D3D11DescriptorSlotType::CountOf)];
};
@@ -174,6 +226,13 @@ public:
UInt index,
ResourceView* textureView,
SamplerState* sampler) override;
+ virtual void setRootConstants(
+ UInt range,
+ UInt offset,
+ UInt size,
+ void const* data) override;
+
+ D3D11Renderer* m_renderer = nullptr;
RefPtr<DescriptorSetLayoutImpl> m_layout;
@@ -1762,6 +1821,7 @@ Result D3D11Renderer::createDescriptorSetLayout(const DescriptorSetLayout::Desc&
DescriptorSetLayoutImpl::RangeInfo rangeInfo;
+ UInt slotCount = rangeDesc.count;
switch(rangeDesc.type)
{
default:
@@ -1776,6 +1836,11 @@ Result D3D11Renderer::createDescriptorSetLayout(const DescriptorSetLayout::Desc&
rangeInfo.type = D3D11DescriptorSlotType::CombinedTextureSampler;
break;
+ case DescriptorSlotType::RootConstant:
+ // A root-constant range will be treated as if it were
+ // a constant-buffer range with a single buffer in it.
+ //
+ slotCount = 1;
case DescriptorSlotType::UniformBuffer:
case DescriptorSlotType::DynamicUniformBuffer:
rangeInfo.type = D3D11DescriptorSlotType::ConstantBuffer;
@@ -1803,17 +1868,31 @@ Result D3D11Renderer::createDescriptorSetLayout(const DescriptorSetLayout::Desc&
rangeInfo.arrayIndex = counts[srvTypeIndex];
rangeInfo.pairedSamplerArrayIndex = counts[samplerTypeIndex];
- counts[srvTypeIndex] += rangeDesc.count;
- counts[samplerTypeIndex] += rangeDesc.count;
+ counts[srvTypeIndex] += slotCount;
+ counts[samplerTypeIndex] += slotCount;
}
else
{
auto typeIndex = int(rangeInfo.type);
rangeInfo.arrayIndex = counts[typeIndex];
- counts[typeIndex] += rangeDesc.count;
+ counts[typeIndex] += slotCount;
}
+ Index rangeIndex = descriptorSetLayoutImpl->m_ranges.getCount();
descriptorSetLayoutImpl->m_ranges.add(rangeInfo);
+
+ if(rangeDesc.type == DescriptorSlotType::RootConstant)
+ {
+ // If the range represents a root constant range, then
+ // we need to also store the information we will need when
+ // allocating a constant buffer to provide backing storage
+ // for the range.
+ //
+ DescriptorSetLayoutImpl::RootConstantRangeInfo rootConstantRangeInfo;
+ rootConstantRangeInfo.rangeIndex = rangeIndex;
+ rootConstantRangeInfo.size = rangeDesc.count;
+ descriptorSetLayoutImpl->m_rootConstantRanges.add(rootConstantRangeInfo);
+ }
}
for(int ii = 0; ii < int(D3D11DescriptorSlotType::CountOf); ++ii)
@@ -1860,12 +1939,55 @@ Result D3D11Renderer::createDescriptorSet(DescriptorSetLayout* layout, Descripto
RefPtr<DescriptorSetImpl> descriptorSetImpl = new DescriptorSetImpl();
+ descriptorSetImpl->m_renderer = this;
descriptorSetImpl->m_layout = layoutImpl;
descriptorSetImpl->m_cbs .setCount(layoutImpl->m_counts[int(D3D11DescriptorSlotType::ConstantBuffer)]);
descriptorSetImpl->m_srvs .setCount(layoutImpl->m_counts[int(D3D11DescriptorSlotType::ShaderResourceView)]);
descriptorSetImpl->m_uavs .setCount(layoutImpl->m_counts[int(D3D11DescriptorSlotType::UnorderedAccessView)]);
descriptorSetImpl->m_samplers.setCount(layoutImpl->m_counts[int(D3D11DescriptorSlotType::Sampler)]);
+ // If the layout includes any root constant ranges, then
+ // we will need to allocate a constant buffer for each
+ // range to provide "backing storage" for its data.
+ //
+ for(auto rootConstantRange : layoutImpl->m_rootConstantRanges)
+ {
+ // The root constant range will refer to a descriptor slot
+ // range that represents a range with a single constant
+ // buffer in it. We need to grab that range so that we
+ // know what constant-buffer bindign slot to fill in.
+ //
+ auto rangeIndex = rootConstantRange.rangeIndex;
+ auto bufferRange = layoutImpl->m_ranges[rangeIndex];
+
+ // We will allocate the constant buffer that provides
+ // backing storage directly using D3D11 API calls,
+ // rather than allocate it as a buffer resource.
+ //
+ // TODO: We could revisit that decision if allocating
+ // a buffer resource proves easier down the line.
+
+ // Note: A D3D11 constant buffer must be a multiple of 16 bytes
+ // in size, so we will round up the allocation size to match
+ // the requirement.
+ //
+ UINT size = (UINT) rootConstantRange.size;
+ size = (size + 15) & ~15;
+
+ D3D11_BUFFER_DESC bufferDesc;
+ bufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
+ bufferDesc.ByteWidth = size;
+ bufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
+ bufferDesc.MiscFlags = 0;
+ bufferDesc.StructureByteStride = 0;
+ bufferDesc.Usage = D3D11_USAGE_DYNAMIC;
+
+ Slang::ComPtr<ID3D11Buffer> buffer;
+ SLANG_RETURN_ON_FAIL(m_device->CreateBuffer(&bufferDesc, nullptr, buffer.writeRef()));
+
+ descriptorSetImpl->m_cbs[bufferRange.arrayIndex] = buffer;
+ }
+
*outDescriptorSet = descriptorSetImpl.detach();
return SLANG_OK;
}
@@ -2277,6 +2399,44 @@ void D3D11Renderer::DescriptorSetImpl::setCombinedTextureSampler(
m_srvs[rangeInfo.pairedSamplerArrayIndex + index] = srvImpl->m_srv;
}
+void D3D11Renderer::DescriptorSetImpl::setRootConstants(
+ UInt range,
+ UInt offset,
+ UInt size,
+ void const* data)
+{
+ // The `range` parameter represents the index of a descriptor
+ // slot range in the layout of this descriptor set.
+ //
+ // A root constant range will have been translated into
+ // a constnat buffer range at creation time for the layout.
+ //
+ auto& rangeInfo = m_layout->m_ranges[range];
+ assert(rangeInfo.type == D3D11DescriptorSlotType::ConstantBuffer);
+
+ // At the time the descriptor set was allocated, a
+ // constant buffer will have been created and bound
+ // into `m_cbs` to provide backing storage for the
+ // root constant range.
+ //
+ auto dxBuffer = m_cbs[rangeInfo.arrayIndex];
+ auto dxContext = m_renderer->m_immediateContext;
+
+ // Once we have the buffer that provides backing
+ // storage we simply need to map it and write
+ // the user-provided data into it.
+ //
+ D3D11_MAPPED_SUBRESOURCE mapped;
+ HRESULT hr = dxContext->Map(dxBuffer, 0, D3D11_MAP_WRITE_NO_OVERWRITE, 0, &mapped);
+ if( FAILED(hr) )
+ {
+ SLANG_ASSERT(!"failed to map backing storage for root constant range");
+ return;
+ }
+ memcpy((char*)mapped.pData + offset, data, size);
+ dxContext->Unmap(dxBuffer, 0);
+}
+
void D3D11Renderer::setDescriptorSet(PipelineType pipelineType, PipelineLayout* layout, UInt index, DescriptorSet* descriptorSet)
{
auto pipelineLayoutImpl = (PipelineLayoutImpl*)layout;
diff --git a/tools/gfx/d3d12/render-d3d12.cpp b/tools/gfx/d3d12/render-d3d12.cpp
index ad7b898f5..ba9bde63a 100644
--- a/tools/gfx/d3d12/render-d3d12.cpp
+++ b/tools/gfx/d3d12/render-d3d12.cpp
@@ -135,6 +135,7 @@ protected:
virtual void setRootConstantBufferView(int index, D3D12_GPU_VIRTUAL_ADDRESS gpuBufferLocation) = 0;
virtual void setRootDescriptorTable(int index, D3D12_GPU_DESCRIPTOR_HANDLE BaseDescriptor) = 0;
virtual void setRootSignature(ID3D12RootSignature* rootSignature) = 0;
+ virtual void setRootConstants(Index rootParamIndex, Index dstOffsetIn32BitValues, Index countOf32BitValues, void const* srcData) = 0;
};
struct FrameInfo
@@ -301,19 +302,90 @@ protected:
class DescriptorSetLayoutImpl : public DescriptorSetLayout
{
public:
+ // A "descriptor set" at the level of the `Renderer` API
+ // is similar to a D3D12 "descriptor table," but the match
+ // isn't perfect for a few reasons:
+ //
+ // * Our descriptor sets can contain both resources and
+ // samplers, while D3D12 descriptor tables are always
+ // resource-only or sampler-only.
+ //
+ // * Our descriptor sets can include root constant ranges,
+ // while under D3D12 a root constant range is thought
+ // of as belonging to the root signature directly.
+ //
+ // We navigate this mismatch in our implementation with
+ // the idea that a single `Renderer`-level descriptor set
+ // maps to zero or more D3D12 root parameters, which can
+ // include:
+ //
+ // * Zero or one root parameter that is used to bind a
+ // descriptor table of resources.
+ //
+ // * Zero or one root parameter that is used to bind a
+ // descriptor table of samplers.
+ //
+ // * Zero or more root parameters that represent ranges
+ // of root constants.
+ //
+ // Binding a descriptor set will band all of its associated
+ // root parameters.
+ //
+ // (Note: this representation could in theory be extended
+ // to also support root resources that are not table-bound)
+ //
+ // Each descriptor slot range in the original `Desc` maps
+ // to a single `RangeInfo` stored here, which captures
+ // derived information used when binding values into
+ // a descriptor table.
+ //
struct RangeInfo
{
+ /// The type of descriptor slot in the original `Desc`
DescriptorSlotType type;
+
+ /// The number of slots in this range
Int count;
+
+ /// The start index of this range in the appropriate type-specific array.
+ ///
+ /// E.g., for a sampler slot range, this would be the start index
+ /// for the range in the descriptor table used to store all the samplers.
Int arrayIndex;
};
-
List<RangeInfo> m_ranges;
+ // We need to track additional information about
+ // root cosntant ranges that isn't captured in
+ // `RangeInfo`, so we store an additional array
+ // that just captures the root constant ranges.
+ //
+ struct RootConstantRangeInfo
+ {
+ /// The D3D12 "root parameter index" for this range
+ Int rootParamIndex;
+
+ /// The size in bytes of this range
+ Int size;
+
+ /// The byte offset of this range's data in the backing storage for a descriptor set
+ Int offset;
+ };
+ List<RootConstantRangeInfo> m_rootConstantRanges;
+
+ /// The total size (in bytes) of root constant data across all contained ranged.
+ Int m_rootConstantDataSize = 0;
+
+ /// The D3D12-format descriptions of the descriptor ranges in this set
List<D3D12_DESCRIPTOR_RANGE> m_dxRanges;
+
+ /// The D3D12-format description of the root parameters introduced by this set
List<D3D12_ROOT_PARAMETER> m_dxRootParameters;
+ /// How many resource slots (total) were introduced by ranges?
Int m_resourceCount;
+
+ /// How many sampler slots (total) were introduce by ranges?
Int m_samplerCount;
};
@@ -335,6 +407,11 @@ protected:
UInt index,
ResourceView* textureView,
SamplerState* sampler) override;
+ virtual void setRootConstants(
+ UInt range,
+ UInt offset,
+ UInt size,
+ void const* data) override;
D3D12Renderer* m_renderer = nullptr; ///< Weak pointer - must be because if set on Renderer, will have a circular reference
RefPtr<DescriptorSetLayoutImpl> m_layout;
@@ -355,6 +432,9 @@ protected:
//
List<RefPtr<RefObject>> m_resourceObjects;
List<RefPtr<SamplerStateImpl>> m_samplerObjects;
+
+ /// Backing storage for root constant ranges in this descriptor set.
+ List<char> m_rootConstantData;
};
@@ -436,6 +516,14 @@ protected:
{
m_commandList->SetGraphicsRootSignature(rootSignature);
}
+ void setRootConstants(
+ Index rootParamIndex,
+ Index dstOffsetIn32BitValues,
+ Index countOf32BitValues,
+ void const* srcData) override
+ {
+ m_commandList->SetGraphicsRoot32BitConstants(UINT(rootParamIndex), UINT(countOf32BitValues), srcData, UINT(dstOffsetIn32BitValues));
+ }
GraphicsSubmitter(ID3D12GraphicsCommandList* commandList):
m_commandList(commandList)
@@ -459,6 +547,14 @@ protected:
{
m_commandList->SetComputeRootSignature(rootSignature);
}
+ void setRootConstants(
+ Index rootParamIndex,
+ Index dstOffsetIn32BitValues,
+ Index countOf32BitValues,
+ void const* srcData) override
+ {
+ m_commandList->SetComputeRoot32BitConstants(UINT(rootParamIndex), UINT(countOf32BitValues), srcData, UINT(dstOffsetIn32BitValues));
+ }
ComputeSubmitter(ID3D12GraphicsCommandList* commandList) :
m_commandList(commandList)
@@ -1304,6 +1400,16 @@ Result D3D12Renderer::_bindRenderState(PipelineStateImpl* pipelineStateImpl, ID3
submitter->setRootDescriptorTable(int(rootParameterIndex++), gpuHeap.getGpuHandle(gpuDescriptorTable));
}
}
+ if(auto rootConstantRangeCount = descriptorSetLayout->m_rootConstantRanges.getCount())
+ {
+ auto srcData = descriptorSet->m_rootConstantData.getBuffer();
+
+ for(auto& rootConstantRangeInfo : descriptorSetLayout->m_rootConstantRanges)
+ {
+ auto countOf32bitValues = rootConstantRangeInfo.size / sizeof(uint32_t);
+ submitter->setRootConstants(rootConstantRangeInfo.rootParamIndex, 0, countOf32bitValues, srcData + rootConstantRangeInfo.offset);
+ }
+ }
}
return SLANG_OK;
@@ -3092,6 +3198,35 @@ void D3D12Renderer::DescriptorSetImpl::setCombinedTextureSampler(
D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER);
}
+void D3D12Renderer::DescriptorSetImpl::setRootConstants(
+ UInt range,
+ UInt offset,
+ UInt size,
+ void const* data)
+{
+ // The `range` parameter is the index of the range in
+ // the original `DescriptorSetLayout::Desc`, which must
+ // have been a root-constant range for this call to be
+ // valid.
+ //
+ SLANG_ASSERT(range < m_layout->m_ranges.getCount());
+ auto& rangeInfo = m_layout->m_ranges[range];
+ SLANG_ASSERT(rangeInfo.type == DescriptorSlotType::RootConstant);
+
+ // The `arrayIndex` in that descriptor slot range is the "flat"
+ // index of the root constant range that the user is trying
+ // to write into. The root constant range represents a range
+ // of bytes in the `m_rootConstantData` buffer.
+ //
+ auto rootConstantIndex = rangeInfo.arrayIndex;
+ SLANG_ASSERT(rootConstantIndex >= 0);
+ SLANG_ASSERT(rootConstantIndex < m_layout->m_rootConstantRanges.getCount());
+ auto& rootConstantRangeInfo = m_layout->m_rootConstantRanges[rootConstantIndex];
+ SLANG_ASSERT(offset + size <= rootConstantRangeInfo.size);
+
+ memcpy((char*)m_rootConstantData.getBuffer() + rootConstantRangeInfo.offset + offset, data, size);
+}
+
void D3D12Renderer::setDescriptorSet(PipelineType pipelineType, PipelineLayout* layout, UInt index, DescriptorSet* descriptorSet)
{
// In D3D12, unlike Vulkan, binding a root signature invalidates *all* descriptor table
@@ -3174,6 +3309,11 @@ Result D3D12Renderer::createDescriptorSetLayout(const DescriptorSetLayout::Desc&
combinedRangeCount++;
break;
+ case DescriptorSlotType::RootConstant:
+ // A root constant slot range doesn't contribute
+ // to the toal number of resources or samplers.
+ break;
+
default:
dedicatedResourceCount += rangeDesc.count;
dedicatedResourceRangeCount++;
@@ -3270,6 +3410,54 @@ Result D3D12Renderer::createDescriptorSetLayout(const DescriptorSetLayout::Desc&
rangeInfo.arrayIndex = combinedCounter;
combinedCounter += rangeInfo.count;
break;
+
+ case DescriptorSlotType::RootConstant:
+ {
+ // A root constant range is a bit different than
+ // the other cases because it does *not* introduce
+ // any descriptor rangess into D3D12 descriptor tables,
+ // while it *does* introduce a distinct root parameter.
+ //
+ D3D12_ROOT_PARAMETER dxRootParameter = {};
+ dxRootParameter.ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS;
+ dxRootParameter.Constants.Num32BitValues = UINT(rangeInfo.count) / UINT(sizeof(uint32_t));
+
+ // When binding the data for the range to the pipeline,
+ // we will need to know the "root parameter index" in
+ // order to identify the range to D3D12.
+ //
+ auto rootParameterIndex = descriptorSetLayoutImpl->m_dxRootParameters.getCount();
+ descriptorSetLayoutImpl->m_dxRootParameters.add(dxRootParameter);
+
+ // We need to create and store additional tracking data
+ // to remember this root constant range and how to set it.
+ //
+ // The additional data includes the D3D12 root parameter index,
+ // and the size of the range (in bytes).
+ //
+ DescriptorSetLayoutImpl::RootConstantRangeInfo rootConstantRangeInfo;
+ rootConstantRangeInfo.rootParamIndex = rootParameterIndex;
+ rootConstantRangeInfo.size = rangeDesc.count;
+ //
+ // We also need to compute an offset for the data in the backing
+ // storage of a particular descriptor set; we also use this as
+ // a place to update the total size of the root constant data.
+ //
+ // Note: We don't deal with alignment issues here. D3D12 requires
+ // all root-constant data to be in multiples of 4 bytes and to be
+ // 4-byte aligned, and that should mean that alignment works
+ // out without extra effort on our part.
+ //
+ rootConstantRangeInfo.offset = descriptorSetLayoutImpl->m_rootConstantDataSize;
+ descriptorSetLayoutImpl->m_rootConstantDataSize += rootConstantRangeInfo.size;
+
+ auto rootConstantIndex = descriptorSetLayoutImpl->m_rootConstantRanges.getCount();
+ descriptorSetLayoutImpl->m_rootConstantRanges.add(rootConstantRangeInfo);
+
+ rangeInfo.arrayIndex = rootConstantIndex;
+ rangeInfo.count = 1;
+ }
+ break;
}
descriptorSetLayoutImpl->m_ranges.add(rangeInfo);
@@ -3326,6 +3514,36 @@ Result D3D12Renderer::createDescriptorSetLayout(const DescriptorSetLayout::Desc&
dxPairedSamplerRangeIndex = totalResourceRangeCount + combinedRangeCounter;
combinedRangeCounter++;
break;
+
+
+ case DescriptorSlotType::RootConstant:
+ {
+ // A root constant range consumes a `b` register binding
+ // under the D3D12 rules, because it is represented as
+ // a `cbuffer` or `ConstantBuffer` declaration in HLSL.
+ //
+ // We need to allocate a register for the root constant
+ // buffer here to make the bindings line up, but we
+ // will skip out of the rest of the logic (via a `continue`
+ // so that this range doesn't turn into a descriptor
+ // range in one of the D3D12 descriptor tables.
+ //
+ UInt bindingIndex = cbvCounter; cbvCounter += bindingCount;
+
+ auto rootConstantRangeIndex = descriptorSetLayoutImpl->m_ranges[rr].arrayIndex;
+ auto rootParamIndex = descriptorSetLayoutImpl->m_rootConstantRanges[rootConstantRangeIndex].rootParamIndex;
+
+ // The root constant range is represented in the D3D12
+ // root signature as its own root parameter (not in any
+ // table), and that root parameter needs to be set up
+ // to reference the correct binding space and index.
+ //
+ auto& dxRootParam = descriptorSetLayoutImpl->m_dxRootParameters[rootParamIndex];
+ dxRootParam.Constants.RegisterSpace = UINT(bindingSpace);
+ dxRootParam.Constants.ShaderRegister = UINT(bindingIndex);
+ continue;
+ }
+ break;
}
D3D12_DESCRIPTOR_RANGE& dxRange = descriptorSetLayoutImpl->m_dxRanges[dxRangeIndex];
@@ -3509,6 +3727,15 @@ Result D3D12Renderer::createPipelineLayout(const PipelineLayout::Desc& desc, Pip
auto& descriptorSetInfo = desc.descriptorSets[dd];
auto descriptorSetLayout = (DescriptorSetLayoutImpl*) descriptorSetInfo.layout;
+ // For now we assume that the register space used for
+ // logical descriptor set #N will be space N.
+ //
+ // Note: this is the same assumption made in the first
+ // loop, and any change/fix will need to be made to
+ // both places consistently.
+ //
+ UInt bindingSpace = dd;
+
// Copy root parameter information from the set layout to our
// overall pipeline layout.
for( auto setRootParameter : descriptorSetLayout->m_dxRootParameters )
@@ -3516,14 +3743,27 @@ Result D3D12Renderer::createPipelineLayout(const PipelineLayout::Desc& desc, Pip
auto& rootParameter = rootParameters[rootParameterCount++];
rootParameter = setRootParameter;
- // In the case where this parameter is a descriptor table, it
- // needs to point into our array of ranges (with offsets applied),
- // so we will fix up those pointers here.
- //
- if(rootParameter.ParameterType == D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE)
+ switch( rootParameter.ParameterType )
{
+ default:
+ break;
+
+ case D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE:
+ // In the case where this parameter is a descriptor table, it
+ // needs to point into our array of ranges (with offsets applied),
+ // so we will fix up those pointers here.
+ //
rootParameter.DescriptorTable.pDescriptorRanges = rangePtr;
rangePtr += rootParameter.DescriptorTable.NumDescriptorRanges;
+ break;
+
+ case D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS:
+ // In the case where the parameter is a root constant range,
+ // it needs to reflect the register space for the descriptor
+ // set, as computed based on sets specified.
+ //
+ rootParameter.Constants.RegisterSpace = UINT(bindingSpace);
+ break;
}
}
}
@@ -3596,6 +3836,8 @@ Result D3D12Renderer::createDescriptorSet(DescriptorSetLayout* layout, Descripto
descriptorSetImpl->m_samplerObjects.setCount(samplerCount);
}
+ descriptorSetImpl->m_rootConstantData.setCount(layoutImpl->m_rootConstantDataSize);
+
*outDescriptorSet = descriptorSetImpl.detach();
return SLANG_OK;
}
diff --git a/tools/gfx/open-gl/render-gl.cpp b/tools/gfx/open-gl/render-gl.cpp
index 91c31b71d..ee3977a74 100644
--- a/tools/gfx/open-gl/render-gl.cpp
+++ b/tools/gfx/open-gl/render-gl.cpp
@@ -275,6 +275,11 @@ public:
UInt index,
ResourceView* textureView,
SamplerState* sampler) override;
+ virtual void setRootConstants(
+ UInt range,
+ UInt offset,
+ UInt size,
+ void const* data) override;
RefPtr<DescriptorSetLayoutImpl> m_layout;
List<RefPtr<BufferResourceImpl>> m_constantBuffers;
@@ -1304,6 +1309,15 @@ void GLRenderer::DescriptorSetImpl::setCombinedTextureSampler(
m_samplers[arrayIndex] = samplerImpl;
}
+void GLRenderer::DescriptorSetImpl::setRootConstants(
+ UInt range,
+ UInt offset,
+ UInt size,
+ void const* data)
+{
+ SLANG_UNEXPECTED("unimplemented: setRootConstants for GlRenderer");
+}
+
void GLRenderer::setDescriptorSet(PipelineType pipelineType, PipelineLayout* layout, UInt index, DescriptorSet* descriptorSet)
{
auto descriptorSetImpl = (DescriptorSetImpl*)descriptorSet;
@@ -1339,6 +1353,8 @@ Result GLRenderer::createDescriptorSetLayout(const DescriptorSetLayout::Desc& de
glSlotType = GLDescriptorSlotType::CombinedTextureSampler;
break;
+ case DescriptorSlotType::RootConstant:
+ rangeDesc.count = 1;
case DescriptorSlotType::UniformBuffer:
case DescriptorSlotType::DynamicUniformBuffer:
glSlotType = GLDescriptorSlotType::ConstantBuffer;
diff --git a/tools/gfx/render.h b/tools/gfx/render.h
index 1aa932e0e..051b19742 100644
--- a/tools/gfx/render.h
+++ b/tools/gfx/render.h
@@ -519,6 +519,7 @@ enum class DescriptorSlotType
DynamicUniformBuffer,
DynamicStorageBuffer,
InputAttachment,
+ RootConstant,
};
class DescriptorSetLayout : public Slang::RefObject
@@ -602,6 +603,11 @@ public:
UInt index,
ResourceView* textureView,
SamplerState* sampler) = 0;
+ virtual void setRootConstants(
+ UInt range,
+ UInt offset,
+ UInt size,
+ void const* data) = 0;
};
enum class StencilOp : uint8_t
diff --git a/tools/gfx/vulkan/render-vk.cpp b/tools/gfx/vulkan/render-vk.cpp
index 53c210a20..b9cc82469 100644
--- a/tools/gfx/vulkan/render-vk.cpp
+++ b/tools/gfx/vulkan/render-vk.cpp
@@ -91,6 +91,9 @@ public:
protected:
+ /// Flush state from descriptor set bindings into `commandBuffer`
+ void _flushBindingState(VkCommandBuffer commandBuffer, VkPipelineBindPoint pipelineBindPoint);
+
class Buffer
{
public:
@@ -280,11 +283,57 @@ public:
VkDescriptorSetLayout m_descriptorSetLayout = VK_NULL_HANDLE;
VkDescriptorPool m_descriptorPool = VK_NULL_HANDLE;
+ // Vulkan descriptor sets are the closest in design to what
+ // the `Renderer` abstraction exposes as a `DescriptorSet`.
+ // The main difference is that a `DescriptorSet` can include
+ // root constant ranges, while under Vulkan push constant
+ // ranges are part of the `VkPipelineLayout`, but not part
+ // of any `VkDescriptorSetLayout`.
+ //
+ // Information about each descriptor slot range in the
+ // original `Desc` will be stored as `RangeInfo` values,
+ // which store the relevant information from the `Desc`
+ // as well as additional information specific to the
+ // Vulkan implementation path.
+ //
struct RangeInfo
{
- VkDescriptorType descriptorType;
+ /// The type of descriptor slot range from the original `Desc`
+ DescriptorSlotType type;
+
+ /// The start index of the range in the appropriate type-specific array
+ Index arrayIndex;
+
+ /// The equivalent Vulkan descriptor type, where applicable
+ VkDescriptorType vkDescriptorType;
+
+ /// The Vulkan `binding` index for this range
+ uint32_t vkBindingIndex;
};
List<RangeInfo> m_ranges;
+
+ // Because root constant ranges aren't part of a `VkDescriptorSetLayout`,
+ // we store additional data to represent the ranges so that
+ // we can store their data on a `DescriptorSetImpl` and then
+ // bind it to the API later.
+ //
+ struct RootConstantRangeInfo
+ {
+ /// The offset of the range's data in the backing storage.
+ Index offset;
+
+ /// The size of the range's data.
+ Index size;
+ };
+ Slang::List<RootConstantRangeInfo> m_rootConstantRanges;
+
+ /// The total size, in bytes, or root constant data for this descriptor set.
+ uint32_t m_rootConstantDataSize = 0;
+
+ /// The total number of reference counted objects that can be bound
+ /// to descriptor sets described by this layout.
+ ///
+ Index m_totalBoundObjectCount = 0;
};
class PipelineLayoutImpl : public PipelineLayout
@@ -306,28 +355,14 @@ public:
VulkanApi const* m_api;
VkPipelineLayout m_pipelineLayout = VK_NULL_HANDLE;
UInt m_descriptorSetCount = 0;
+
+ /// For each descriptor set, stores the start offset of that set's root constant data in the pipeline layout
+ List<uint32_t> m_descriptorSetRootConstantOffsets;
};
class DescriptorSetImpl : public DescriptorSet
{
public:
- // Record the view binding
- struct Binding
- {
- enum class Type : uint8_t
- {
- Unknown,
- ResourceView,
- SamplerState,
- BufferResource,
- CountOf,
- };
- Type type;
- uint32_t range;
- uint32_t index;
- RefPtr<RefObject> obj;
- };
-
DescriptorSetImpl(VKRenderer* renderer)
: m_renderer(renderer)
{
@@ -345,15 +380,21 @@ public:
UInt index,
ResourceView* textureView,
SamplerState* sampler) override;
-
- static Binding::Type _getBindingType(RefObject* ptr);
- void _setBinding(Binding::Type type, UInt range, UInt index, RefObject* ptr);
+ virtual void setRootConstants(
+ UInt range,
+ UInt offset,
+ UInt size,
+ void const* data) override;
VKRenderer* m_renderer = nullptr; ///< Weak pointer, can't be strong, because if set will become circular reference
RefPtr<DescriptorSetLayoutImpl> m_layout;
VkDescriptorSet m_descriptorSet = VK_NULL_HANDLE;
- List<Binding> m_bindings; ///< Records entities are bound to this descriptor set, and keeps the associated resources/views/state in scope
+ /// Records entities that are bound to this descriptor set, and keeps the associated resources/views/state in scope
+ List<RefPtr<RefObject>> m_boundObjects;
+
+ /// Backing storage for root constant ranges belonging to this descriptor set
+ List<char> m_rootConstantData;
};
struct BoundVertexBuffer
@@ -2019,6 +2060,46 @@ void VKRenderer::setPipelineState(PipelineType pipelineType, PipelineState* stat
m_currentPipeline = (PipelineStateImpl*)state;
}
+void VKRenderer::_flushBindingState(VkCommandBuffer commandBuffer, VkPipelineBindPoint pipelineBindPoint)
+{
+ auto pipeline = m_currentPipeline;
+
+ // We start by binding the pipeline state.
+ //
+ m_api.vkCmdBindPipeline(commandBuffer, pipelineBindPoint, pipeline->m_pipeline);
+
+ // Next we bind all the descriptor sets that were set in the `VKRenderer`.
+ //
+ auto pipelineLayoutImpl = pipeline->m_pipelineLayout.Ptr();
+ auto vkPipelineLayout = pipelineLayoutImpl->m_pipelineLayout;
+ auto descriptorSetCount = pipelineLayoutImpl->m_descriptorSetCount;
+ m_api.vkCmdBindDescriptorSets(commandBuffer, pipelineBindPoint, vkPipelineLayout,
+ 0, uint32_t(descriptorSetCount),
+ &m_currentDescriptorSets[0],
+ 0, nullptr);
+
+ // For any descriptor sets with root-constant ranges, we need to
+ // bind the relevant data to the context.
+ //
+ for(gfx::UInt ii = 0; ii < descriptorSetCount; ++ii)
+ {
+ auto descriptorSet = m_currentDescriptorSetImpls[ii];
+ auto descriptorSetLayout = descriptorSet->m_layout;
+ auto size = descriptorSetLayout->m_rootConstantDataSize;
+ if(size == 0)
+ continue;
+ auto data = descriptorSet->m_rootConstantData.getBuffer();
+
+ // The absolute offset of the descriptor set's data in
+ // the push-constant data for the entire pipeline was
+ // computed and cached in the pipeline layout.
+ //
+ uint32_t offset = pipelineLayoutImpl->m_descriptorSetRootConstantOffsets[ii];
+
+ m_api.vkCmdPushConstants(commandBuffer, vkPipelineLayout, VK_SHADER_STAGE_ALL, offset, size, data);
+ }
+}
+
void VKRenderer::draw(UInt vertexCount, UInt startVertex = 0)
{
auto pipeline = m_currentPipeline;
@@ -2033,13 +2114,7 @@ void VKRenderer::draw(UInt vertexCount, UInt startVertex = 0)
// Also create descriptor sets based on the given pipeline layout
VkCommandBuffer commandBuffer = m_deviceQueue.getCommandBuffer();
- m_api.vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->m_pipeline);
-
- auto pipelineLayoutImpl = pipeline->m_pipelineLayout.Ptr();
- m_api.vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayoutImpl->m_pipelineLayout,
- 0, uint32_t(pipelineLayoutImpl->m_descriptorSetCount),
- &m_currentDescriptorSets[0],
- 0, nullptr);
+ _flushBindingState(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS);
// Bind the vertex buffer
if (m_boundVertexBuffers.getCount() > 0 && m_boundVertexBuffers[0].m_buffer)
@@ -2073,13 +2148,7 @@ void VKRenderer::dispatchCompute(int x, int y, int z)
// Also create descriptor sets based on the given pipeline layout
VkCommandBuffer commandBuffer = m_deviceQueue.getCommandBuffer();
- m_api.vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline->m_pipeline);
-
- auto pipelineLayoutImpl = pipeline->m_pipelineLayout.Ptr();
- m_api.vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipelineLayoutImpl->m_pipelineLayout,
- 0, uint32_t(pipelineLayoutImpl->m_descriptorSetCount),
- &m_currentDescriptorSets[0],
- 0, nullptr);
+ _flushBindingState(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE);
m_api.vkCmdDispatch(commandBuffer, x, y, z);
}
@@ -2284,10 +2353,61 @@ Result VKRenderer::createDescriptorSetLayout(const DescriptorSetLayout::Desc& de
{
auto& srcRange = desc.slotRanges[rr];
+ if(srcRange.type == DescriptorSlotType::RootConstant)
+ {
+ // Root constant ranges are a special case, since they
+ // don't actually map to `VkDescriptorSetLayoutBinding`s
+ // like the other cases.
+
+ // We start by computing the offset of the range within
+ // the backing storage for the descriptor set, while
+ // also updating the computed total size of root constant
+ // data needed by the set.
+ //
+ auto size = uint32_t(srcRange.count);
+ auto offset = descriptorSetLayoutImpl->m_rootConstantDataSize;
+ descriptorSetLayoutImpl->m_rootConstantDataSize += size;
+
+ // We will keep track of the information for this
+ // range as part of the descriptor set layout.
+ //
+ DescriptorSetLayoutImpl::RootConstantRangeInfo rootConstantRangeInfo;
+ rootConstantRangeInfo.offset = offset;
+ rootConstantRangeInfo.size = size;
+
+ auto rootConstantRangeIndex = descriptorSetLayoutImpl->m_rootConstantRanges.getCount();
+ descriptorSetLayoutImpl->m_rootConstantRanges.add(rootConstantRangeInfo);
+
+ // We will also add a `RangeInfo` to reprsent this
+ // range, even though it doesn't map to a VK-level
+ // descriptor range.
+ //
+ DescriptorSetLayoutImpl::RangeInfo rangeInfo;
+ rangeInfo.type = srcRange.type;
+ rangeInfo.vkDescriptorType = VkDescriptorType(-1);
+ rangeInfo.arrayIndex = rootConstantRangeIndex;
+ descriptorSetLayoutImpl->m_ranges.add(rangeInfo);
+
+ // Finally, we bail out instead of performing
+ // the logic that applies to the other descriptor
+ // range types.
+ //
+ continue;
+ }
+
+ // Note: Because of the existence of root constant ranges,
+ // we cannot assume that the `binding` for a range is
+ // the same as its index in the input array of ranges.
+ //
+ // Instead, the `binding` for a range is its index in
+ // the output array of `VkDescriptorSetLayoutBinding`s.
+ //
+ uint32_t bindingIndex = uint32_t(dstBindings.getCount());
+
VkDescriptorType dstDescriptorType = translateDescriptorType(srcRange.type);
VkDescriptorSetLayoutBinding dstBinding;
- dstBinding.binding = uint32_t(rr);
+ dstBinding.binding = uint32_t(bindingIndex);
dstBinding.descriptorType = dstDescriptorType;
dstBinding.descriptorCount = uint32_t(srcRange.count);
dstBinding.stageFlags = VK_SHADER_STAGE_ALL;
@@ -2297,8 +2417,20 @@ Result VKRenderer::createDescriptorSetLayout(const DescriptorSetLayout::Desc& de
dstBindings.add(dstBinding);
+ UInt boundObjectCount = srcRange.count;
+ if( srcRange.type == DescriptorSlotType::CombinedImageSampler )
+ {
+ boundObjectCount = 2 * srcRange.count;
+ }
+
+ auto boundObjectArrayIndex = descriptorSetLayoutImpl->m_totalBoundObjectCount;
+ descriptorSetLayoutImpl->m_totalBoundObjectCount += boundObjectCount;
+
DescriptorSetLayoutImpl::RangeInfo rangeInfo;
- rangeInfo.descriptorType = dstDescriptorType;
+ rangeInfo.type = srcRange.type;
+ rangeInfo.vkDescriptorType = dstDescriptorType;
+ rangeInfo.vkBindingIndex = bindingIndex;
+ rangeInfo.arrayIndex = boundObjectArrayIndex;
descriptorSetLayoutImpl->m_ranges.add(rangeInfo);
}
@@ -2344,15 +2476,47 @@ Result VKRenderer::createPipelineLayout(const PipelineLayout::Desc& desc, Pipeli
UInt descriptorSetCount = desc.descriptorSetCount;
VkDescriptorSetLayout descriptorSetLayouts[kMaxDescriptorSets];
+ uint32_t descriptorSetRootConstantOffsets[kMaxDescriptorSets];
+ uint32_t totalRootConstantSize = 0;
for(UInt ii = 0; ii < descriptorSetCount; ++ii)
{
- descriptorSetLayouts[ii] = ((DescriptorSetLayoutImpl*) desc.descriptorSets[ii].layout)->m_descriptorSetLayout;
+ auto descriptorSetLayoutImpl = (DescriptorSetLayoutImpl*) desc.descriptorSets[ii].layout;
+ descriptorSetLayouts[ii] = descriptorSetLayoutImpl->m_descriptorSetLayout;
+
+ descriptorSetRootConstantOffsets[ii] = totalRootConstantSize;
+ totalRootConstantSize += descriptorSetLayoutImpl->m_rootConstantDataSize;
}
VkPipelineLayoutCreateInfo pipelineLayoutInfo = { VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO };
pipelineLayoutInfo.setLayoutCount = uint32_t(desc.descriptorSetCount);
pipelineLayoutInfo.pSetLayouts = &descriptorSetLayouts[0];
+ // Our abstraction allows the user to specify any number of root-constant
+ // ranges across all of their descriptor sets, but Vulkan has a restriction
+ // that a pipeline layout may only include a single push constant range
+ // accessible from a given stage. (In other words, the only situation where
+ // multiple push-constant ranges are allowed is if you want to have, say,
+ // distinct ranges for the vertex and fragment stages to access).
+ //
+ // We handle this by declaring at most one push constant range, which
+ // represents the concatenation of the data from all ranges that the
+ // user might have asked for.
+ //
+ // Note: The Slang compiler doesn't yet have logic to concatenate multiple
+ // push-constant ranges in this way, but if/when it does, it should hopefully
+ // Just Work with this logic.
+ //
+ VkPushConstantRange pushConstantRange;
+ if( totalRootConstantSize )
+ {
+ pushConstantRange.offset = 0;
+ pushConstantRange.size = totalRootConstantSize;
+ pushConstantRange.stageFlags = VK_SHADER_STAGE_ALL;
+
+ pipelineLayoutInfo.pushConstantRangeCount = 1;
+ pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange;
+ }
+
VkPipelineLayout pipelineLayout;
SLANG_VK_CHECK(m_api.vkCreatePipelineLayout(m_device, &pipelineLayoutInfo, nullptr, &pipelineLayout));
@@ -2360,6 +2524,12 @@ Result VKRenderer::createPipelineLayout(const PipelineLayout::Desc& desc, Pipeli
pipelineLayoutImpl->m_pipelineLayout = pipelineLayout;
pipelineLayoutImpl->m_descriptorSetCount = descriptorSetCount;
+ for(UInt ii = 0; ii < descriptorSetCount; ++ii)
+ {
+ pipelineLayoutImpl->m_descriptorSetRootConstantOffsets.add(
+ descriptorSetRootConstantOffsets[ii]);
+ }
+
*outLayout = pipelineLayoutImpl.detach();
return SLANG_OK;
}
@@ -2379,73 +2549,23 @@ Result VKRenderer::createDescriptorSet(DescriptorSetLayout* layout, DescriptorSe
RefPtr<DescriptorSetImpl> descriptorSetImpl = new DescriptorSetImpl(this);
descriptorSetImpl->m_layout = layoutImpl;
descriptorSetImpl->m_descriptorSet = descriptorSet;
- *outDescriptorSet = descriptorSetImpl.detach();
- return SLANG_OK;
-}
-/* static */VKRenderer::DescriptorSetImpl::Binding::Type VKRenderer::DescriptorSetImpl::_getBindingType(RefObject* ptr)
-{
- typedef Binding::Type Type;
+ descriptorSetImpl->m_rootConstantData.setCount(layoutImpl->m_rootConstantDataSize);
+ descriptorSetImpl->m_boundObjects.setCount(layoutImpl->m_totalBoundObjectCount);
- if (ptr)
- {
- if (dynamic_cast<ResourceView*>(ptr))
- {
- return Type::ResourceView;
- }
- else if (dynamic_cast<BufferResource*>(ptr))
- {
- return Type::BufferResource;
- }
- else if (dynamic_cast<SamplerState*>(ptr))
- {
- return Type::SamplerState;
- }
- }
- return Type::Unknown;
-}
-
-void VKRenderer::DescriptorSetImpl::_setBinding(Binding::Type type, UInt range, UInt index, RefObject* ptr)
-{
- SLANG_ASSERT(ptr == nullptr || _getBindingType(ptr) == type);
-
- const Index numBindings = m_bindings.getCount();
- for (Index i = 0; i < numBindings; ++i)
- {
- Binding& binding = m_bindings[i];
-
- if (binding.type == type && binding.range == uint32_t(range) && binding.index == uint32_t(index))
- {
- if (ptr)
- {
- binding.obj = ptr;
- }
- else
- {
- m_bindings.removeAt(i);
- }
-
- return;
- }
- }
-
- // If an entry is not found, and we have a pointer, create an entry
- if (ptr)
- {
- Binding binding;
- binding.type = type;
- binding.range = uint32_t(range);
- binding.index = uint32_t(index);
- binding.obj = ptr;
-
- m_bindings.add(binding);
- }
+ *outDescriptorSet = descriptorSetImpl.detach();
+ return SLANG_OK;
}
void VKRenderer::DescriptorSetImpl::setConstantBuffer(UInt range, UInt index, BufferResource* buffer)
{
auto bufferImpl = (BufferResourceImpl*)buffer;
+ SLANG_ASSERT(range < UInt(m_layout->m_ranges.getCount()));
+ auto& rangeInfo = m_layout->m_ranges[range];
+ auto bindingIndex = rangeInfo.vkBindingIndex;
+ auto boundObjectIndex = rangeInfo.arrayIndex + index;
+
VkDescriptorBufferInfo bufferInfo = {};
bufferInfo.buffer = bufferImpl->m_buffer.m_buffer;
bufferInfo.offset = 0;
@@ -2453,19 +2573,24 @@ void VKRenderer::DescriptorSetImpl::setConstantBuffer(UInt range, UInt index, Bu
VkWriteDescriptorSet writeInfo = { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET };
writeInfo.dstSet = m_descriptorSet;
- writeInfo.dstBinding = uint32_t(range);
+ writeInfo.dstBinding = uint32_t(bindingIndex);
writeInfo.dstArrayElement = uint32_t(index);
writeInfo.descriptorCount = 1;
- writeInfo.descriptorType = m_layout->m_ranges[range].descriptorType;
+ writeInfo.descriptorType = rangeInfo.vkDescriptorType;
writeInfo.pBufferInfo = &bufferInfo;
m_renderer->m_api.vkUpdateDescriptorSets(m_renderer->m_device, 1, &writeInfo, 0, nullptr);
-
- _setBinding(Binding::Type::BufferResource, range, index, buffer);
+ m_boundObjects[boundObjectIndex] = buffer;
}
void VKRenderer::DescriptorSetImpl::setResource(UInt range, UInt index, ResourceView* view)
{
+ SLANG_ASSERT(range < UInt(m_layout->m_ranges.getCount()));
+ auto& rangeInfo = m_layout->m_ranges[range];
+ auto bindingIndex = rangeInfo.vkBindingIndex;
+ auto boundObjectIndex = rangeInfo.arrayIndex + index;
+ auto descriptorType = rangeInfo.vkDescriptorType;
+
auto viewImpl = (ResourceViewImpl*)view;
switch (viewImpl->m_type)
{
@@ -2479,10 +2604,10 @@ void VKRenderer::DescriptorSetImpl::setResource(UInt range, UInt index, Resource
VkWriteDescriptorSet writeInfo = { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET };
writeInfo.dstSet = m_descriptorSet;
- writeInfo.dstBinding = uint32_t(range);
+ writeInfo.dstBinding = uint32_t(bindingIndex);
writeInfo.dstArrayElement = uint32_t(index);
writeInfo.descriptorCount = 1;
- writeInfo.descriptorType = m_layout->m_ranges[range].descriptorType;
+ writeInfo.descriptorType = descriptorType;
writeInfo.pImageInfo = &imageInfo;
m_renderer->m_api.vkUpdateDescriptorSets(m_renderer->m_device, 1, &writeInfo, 0, nullptr);
@@ -2495,10 +2620,10 @@ void VKRenderer::DescriptorSetImpl::setResource(UInt range, UInt index, Resource
VkWriteDescriptorSet writeInfo = { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET };
writeInfo.dstSet = m_descriptorSet;
- writeInfo.dstBinding = uint32_t(range);
+ writeInfo.dstBinding = uint32_t(bindingIndex);
writeInfo.dstArrayElement = uint32_t(index);
writeInfo.descriptorCount = 1;
- writeInfo.descriptorType = m_layout->m_ranges[range].descriptorType;
+ writeInfo.descriptorType = descriptorType;
writeInfo.pTexelBufferView = &bufferViewImpl->m_view;
m_renderer->m_api.vkUpdateDescriptorSets(m_renderer->m_device, 1, &writeInfo, 0, nullptr);
@@ -2516,25 +2641,32 @@ void VKRenderer::DescriptorSetImpl::setResource(UInt range, UInt index, Resource
VkWriteDescriptorSet writeInfo = { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET };
writeInfo.dstSet = m_descriptorSet;
- writeInfo.dstBinding = uint32_t(range);
+ writeInfo.dstBinding = uint32_t(bindingIndex);
writeInfo.dstArrayElement = uint32_t(index);
writeInfo.descriptorCount = 1;
- writeInfo.descriptorType = m_layout->m_ranges[range].descriptorType;
+ writeInfo.descriptorType = descriptorType;
writeInfo.pBufferInfo = &bufferInfo;
m_renderer->m_api.vkUpdateDescriptorSets(m_renderer->m_device, 1, &writeInfo, 0, nullptr);
}
break;
-
}
- _setBinding(Binding::Type::ResourceView, range, index, view);
+ m_boundObjects[boundObjectIndex] = view;
}
void VKRenderer::DescriptorSetImpl::setSampler(UInt range, UInt index, SamplerState* sampler)
{
+ SLANG_ASSERT(range < UInt(m_layout->m_ranges.getCount()));
+ auto& rangeInfo = m_layout->m_ranges[range];
+ SLANG_ASSERT(rangeInfo.type == DescriptorSlotType::Sampler);
+ auto bindingIndex = rangeInfo.vkBindingIndex;
+ auto boundObjectIndex = rangeInfo.arrayIndex + index;
+ auto descriptorType = rangeInfo.vkDescriptorType;
+
+ // TODO: Actually bind it!
- _setBinding(Binding::Type::SamplerState, range, index, sampler);
+ m_boundObjects[boundObjectIndex] = sampler;
}
void VKRenderer::DescriptorSetImpl::setCombinedTextureSampler(
@@ -2543,9 +2675,46 @@ void VKRenderer::DescriptorSetImpl::setCombinedTextureSampler(
ResourceView* textureView,
SamplerState* sampler)
{
+ SLANG_ASSERT(range < UInt(m_layout->m_ranges.getCount()));
+ auto& rangeInfo = m_layout->m_ranges[range];
+ SLANG_ASSERT(rangeInfo.type == DescriptorSlotType::CombinedImageSampler);
+ auto bindingIndex = rangeInfo.vkBindingIndex;
+ auto descriptorType = rangeInfo.vkDescriptorType;
+
+ // TODO: Actually bind it!
+
+ // Note: Each entry in a combined texture/sampler range consumes
+ // two entries in the `m_boundObjects` array, since we have
+ // to keep both the texture view and the sampler object live.
+ //
+ auto boundObjectIndex = rangeInfo.arrayIndex + 2 * index;
+ m_boundObjects[boundObjectIndex + 0] = textureView;
+ m_boundObjects[boundObjectIndex + 1] = sampler;
+}
+
+void VKRenderer::DescriptorSetImpl::setRootConstants(
+ UInt range,
+ UInt offset,
+ UInt size,
+ void const* data)
+{
+ // The `range` variabel is the index of one of the descriptor
+ // slot ranges, which had better be a `RootConstant` range.
+ //
+ SLANG_ASSERT(range < UInt(m_layout->m_ranges.getCount()));
+ auto& rangeInfo = m_layout->m_ranges[range];
+ SLANG_ASSERT(rangeInfo.type == DescriptorSlotType::RootConstant);
+
+ // The `arrayIndex` for the descriptor slot range will refer
+ // to a root constant range, which is the range to be set.
+ //
+ auto rootConstantIndex = rangeInfo.arrayIndex;
+ SLANG_ASSERT(rootConstantIndex >= 0);
+ SLANG_ASSERT(rootConstantIndex < m_layout->m_rootConstantRanges.getCount());
+ auto& rootConstantRangeInfo = m_layout->m_rootConstantRanges[rootConstantIndex];
+ SLANG_ASSERT(offset + size <= rootConstantRangeInfo.size);
- _setBinding(Binding::Type::SamplerState, range, index, sampler);
- _setBinding(Binding::Type::ResourceView, range, index, textureView);
+ memcpy(m_rootConstantData.getBuffer() + rootConstantRangeInfo.offset + offset, data, size);
}
void VKRenderer::setDescriptorSet(PipelineType pipelineType, PipelineLayout* layout, UInt index, DescriptorSet* descriptorSet)
diff --git a/tools/gfx/vulkan/vk-api.h b/tools/gfx/vulkan/vk-api.h
index 6f12d5ddb..58bcc89fd 100644
--- a/tools/gfx/vulkan/vk-api.h
+++ b/tools/gfx/vulkan/vk-api.h
@@ -84,6 +84,7 @@ namespace gfx {
x(vkCmdEndRenderPass) \
x(vkCmdPipelineBarrier) \
x(vkCmdCopyBufferToImage)\
+ x(vkCmdPushConstants) \
\
x(vkCreateFence) \
x(vkDestroyFence) \
diff --git a/tools/render-test/shader-input-layout.cpp b/tools/render-test/shader-input-layout.cpp
index 4c68899ef..5e487f1ad 100644
--- a/tools/render-test/shader-input-layout.cpp
+++ b/tools/render-test/shader-input-layout.cpp
@@ -140,6 +140,11 @@ namespace renderer_test
entry.type = ShaderInputType::Buffer;
entry.bufferDesc.type = InputBufferType::ConstantBuffer;
}
+ else if (parser.LookAhead("root_constants"))
+ {
+ entry.type = ShaderInputType::Buffer;
+ entry.bufferDesc.type = InputBufferType::RootConstantBuffer;
+ }
else if (parser.LookAhead("ubuffer"))
{
entry.type = ShaderInputType::Buffer;
diff --git a/tools/render-test/shader-input-layout.h b/tools/render-test/shader-input-layout.h
index 0831f73bb..4c8d0dfa2 100644
--- a/tools/render-test/shader-input-layout.h
+++ b/tools/render-test/shader-input-layout.h
@@ -42,7 +42,8 @@ struct InputTextureDesc
enum class InputBufferType
{
- ConstantBuffer, StorageBuffer
+ ConstantBuffer, StorageBuffer,
+ RootConstantBuffer,
};
struct InputBufferDesc
diff --git a/tools/render-test/shader-renderer-util.cpp b/tools/render-test/shader-renderer-util.cpp
index 987b63b48..b2911ffd5 100644
--- a/tools/render-test/shader-renderer-util.cpp
+++ b/tools/render-test/shader-renderer-util.cpp
@@ -195,6 +195,18 @@ static RefPtr<SamplerState> _createSamplerState(
case InputBufferType::StorageBuffer:
slotRangeDesc.type = DescriptorSlotType::StorageBuffer;
break;
+
+ case InputBufferType::RootConstantBuffer:
+ {
+ // A root constant buffer maps to a root constant range
+ // where the `count` of slots is equal to the number
+ // of bytes of data.
+ //
+ Slang::UInt size = srcEntry.bufferData.getCount() * sizeof(srcEntry.bufferData[0]);
+ slotRangeDesc.type = DescriptorSlotType::RootConstant;
+ slotRangeDesc.count = size;
+ }
+ break;
}
}
break;
@@ -269,6 +281,20 @@ static RefPtr<SamplerState> _createSamplerState(
const InputBufferDesc& srcBuffer = srcEntry.bufferDesc;
const size_t bufferSize = srcEntry.bufferData.getCount() * sizeof(uint32_t);
+ if( srcBuffer.type == InputBufferType::RootConstantBuffer )
+ {
+ // A root constant buffer at the HLSL/Slang level actually
+ // maps to root constant data stored directly in the descriptor
+ // set, and thus does not need/want us to allocate a buffer
+ // to hold the data.
+ //
+ // Instead, we set the data directly here and then bypass
+ // the logic that handles the buffer-backed cases below.
+ //
+ descriptorSet->setRootConstants(rangeIndex, 0, bufferSize, srcEntry.bufferData.getBuffer());
+ break;
+ }
+
RefPtr<BufferResource> bufferResource;
SLANG_RETURN_ON_FAIL(createBufferResource(srcEntry.bufferDesc, srcEntry.isOutput, bufferSize, srcEntry.bufferData.getBuffer(), renderer, bufferResource));