diff options
| -rw-r--r-- | source/slang/slang-parameter-binding.cpp | 76 | ||||
| -rw-r--r-- | source/slang/slang-type-layout.cpp | 3 | ||||
| -rw-r--r-- | tests/compute/entry-point-uniform-params.slang | 4 | ||||
| -rw-r--r-- | tests/compute/interface-shader-param-in-struct.slang | 2 | ||||
| -rw-r--r-- | tests/compute/interface-shader-param2.slang | 2 | ||||
| -rw-r--r-- | tests/compute/interface-shader-param3.slang | 2 | ||||
| -rw-r--r-- | tests/compute/interface-shader-param4.slang | 2 | ||||
| -rw-r--r-- | tests/language-feature/shader-params/entry-point-uniform-params.slang | 2 | ||||
| -rw-r--r-- | tests/vkray/entry-point-params.slang | 14 | ||||
| -rw-r--r-- | tests/vkray/entry-point-params.slang.glsl | 26 | ||||
| -rw-r--r-- | tools/gfx/d3d11/render-d3d11.cpp | 166 | ||||
| -rw-r--r-- | tools/gfx/d3d12/render-d3d12.cpp | 254 | ||||
| -rw-r--r-- | tools/gfx/open-gl/render-gl.cpp | 16 | ||||
| -rw-r--r-- | tools/gfx/render.h | 6 | ||||
| -rw-r--r-- | tools/gfx/vulkan/render-vk.cpp | 395 | ||||
| -rw-r--r-- | tools/gfx/vulkan/vk-api.h | 1 | ||||
| -rw-r--r-- | tools/render-test/shader-input-layout.cpp | 5 | ||||
| -rw-r--r-- | tools/render-test/shader-input-layout.h | 3 | ||||
| -rw-r--r-- | tools/render-test/shader-renderer-util.cpp | 26 |
19 files changed, 860 insertions, 145 deletions
diff --git a/source/slang/slang-parameter-binding.cpp b/source/slang/slang-parameter-binding.cpp index 6f2a1632e..7676c89b0 100644 --- a/source/slang/slang-parameter-binding.cpp +++ b/source/slang/slang-parameter-binding.cpp @@ -2108,7 +2108,7 @@ static RefPtr<TypeLayout> computeEntryPointParameterTypeLayout( struct ScopeLayoutBuilder { ParameterBindingContext* m_context = nullptr; - LayoutRulesImpl* m_rules = nullptr; + TypeLayoutContext m_layoutContext; RefPtr<StructTypeLayout> m_structLayout; UniformLayoutInfo m_structLayoutInfo; @@ -2120,14 +2120,24 @@ struct ScopeLayoutBuilder StructTypeLayoutBuilder m_pendingDataTypeLayoutBuilder; void beginLayout( - ParameterBindingContext* context) + ParameterBindingContext* context, + TypeLayoutContext layoutContext) { m_context = context; - m_rules = context->getRulesFamily()->getConstantBufferRules(); + m_layoutContext = layoutContext; + + auto rules = layoutContext.rules; m_structLayout = new StructTypeLayout(); - m_structLayout->rules = m_rules; + m_structLayout->rules = rules; + + m_structLayoutInfo = rules->BeginStructLayout(); + } - m_structLayoutInfo = m_rules->BeginStructLayout(); + + void beginLayout( + ParameterBindingContext* context) + { + beginLayout(context, context->layoutContext); } void _addParameter( @@ -2144,7 +2154,8 @@ struct ScopeLayoutBuilder uniformSize, varLayout->typeLayout->uniformAlignment); - LayoutSize uniformOffset = m_rules->AddStructField( + auto rules = m_layoutContext.rules; + LayoutSize uniformOffset = rules->AddStructField( &m_structLayoutInfo, fieldInfo); @@ -2170,7 +2181,8 @@ struct ScopeLayoutBuilder // if( auto fieldPendingDataTypeLayout = varLayout->typeLayout->pendingDataTypeLayout ) { - m_pendingDataTypeLayoutBuilder.beginLayoutIfNeeded(nullptr, m_rules); + auto rules = m_layoutContext.rules; + m_pendingDataTypeLayoutBuilder.beginLayoutIfNeeded(nullptr, rules); auto fieldPendingDataVarLayout = m_pendingDataTypeLayoutBuilder.addField(varLayout->varDecl, fieldPendingDataTypeLayout); m_structLayout->pendingDataTypeLayout = m_pendingDataTypeLayoutBuilder.getTypeLayout(); @@ -2196,7 +2208,8 @@ struct ScopeLayoutBuilder { auto fieldPendingTypeLayout = fieldPendingVarLayout->typeLayout; - m_pendingDataTypeLayoutBuilder.beginLayoutIfNeeded(nullptr, m_rules); + auto rules = m_layoutContext.rules; + m_pendingDataTypeLayoutBuilder.beginLayoutIfNeeded(nullptr, rules); m_structLayout->pendingDataTypeLayout = m_pendingDataTypeLayoutBuilder.getTypeLayout(); auto fieldUniformLayoutInfo = fieldPendingTypeLayout->FindResourceInfo(LayoutResourceKind::Uniform); @@ -2209,7 +2222,7 @@ struct ScopeLayoutBuilder fieldUniformSize, fieldPendingTypeLayout->uniformAlignment); - LayoutSize uniformOffset = m_rules->AddStructField( + LayoutSize uniformOffset = rules->AddStructField( m_pendingDataTypeLayoutBuilder.getStructLayoutInfo(), fieldInfo); @@ -2225,7 +2238,8 @@ struct ScopeLayoutBuilder { // Finish computing the layout for the ordindary data (if any). // - m_rules->EndStructLayout(&m_structLayoutInfo); + auto rules = m_layoutContext.rules; + rules->EndStructLayout(&m_structLayoutInfo); m_pendingDataTypeLayoutBuilder.endLayout(); // Copy the final layout information computed for ordinary data @@ -2241,7 +2255,7 @@ struct ScopeLayoutBuilder // to reflect the constant buffer that will be generated. // scopeTypeLayout = createConstantBufferTypeLayoutIfNeeded( - m_context->layoutContext, + m_layoutContext, scopeTypeLayout); // We now have a bunch of layout information, which we should @@ -2508,8 +2522,46 @@ static RefPtr<EntryPointLayout> collectEntryPointParameters( // in the parameter list (e.g., a `uniform float4x4 mvp` parameter), // which is what the `ScopeLayoutBuilder` is designed to help with. // + TypeLayoutContext layoutContext = context->layoutContext; + + if(isKhronosTarget(context->getTargetRequest())) + { + // For Vulkan/SPIR-V targets, there are various cases for + // how parameters that would otherwise just be a `ConstantBuffer<...>` + // get passed, that the compiler and application need to agree + // on. + // + // As a matter of policy, the Slang compiler will interpret + // direct entry-point `uniform` parameters as being passed + // using whatever is the most natural and efficient mechanism + // based on the shader stage. + // + // In the case of rasterization and compute shaders, this means + // passing entry-point `uniform` parmaeters via a "push constant" + // buffer. + // + // In the case of ray-tracing shaders, this means passing entry-point + // `uniform` parameters via the "shader record." + // + switch( entryPoint->getStage() ) + { + default: + layoutContext = layoutContext.with(layoutContext.getRulesFamily()->getPushConstantBufferRules()); + break; + + case Stage::AnyHit: + case Stage::Callable: + case Stage::ClosestHit: + case Stage::Intersection: + case Stage::Miss: + case Stage::RayGeneration: + layoutContext = layoutContext.with(layoutContext.getRulesFamily()->getShaderRecordConstantBufferRules()); + break; + } + } + SimpleScopeLayoutBuilder scopeBuilder; - scopeBuilder.beginLayout(context); + scopeBuilder.beginLayout(context, layoutContext); auto paramsStructLayout = scopeBuilder.m_structLayout; paramsStructLayout->type = entryPointType; diff --git a/source/slang/slang-type-layout.cpp b/source/slang/slang-type-layout.cpp index 8b4c80554..9b00fc88c 100644 --- a/source/slang/slang-type-layout.cpp +++ b/source/slang/slang-type-layout.cpp @@ -2363,11 +2363,8 @@ RefPtr<TypeLayout> createConstantBufferTypeLayoutIfNeeded( if(!needsConstantBuffer(context, elementTypeLayout)) return elementTypeLayout; - auto parameterGroupRules = context.getRulesFamily()->getConstantBufferRules(); - return _createParameterGroupTypeLayout( context - .with(parameterGroupRules) .with(context.targetReq->getDefaultMatrixLayoutMode()), nullptr, elementTypeLayout); diff --git a/tests/compute/entry-point-uniform-params.slang b/tests/compute/entry-point-uniform-params.slang index 4ca8d6786..736a4c05a 100644 --- a/tests/compute/entry-point-uniform-params.slang +++ b/tests/compute/entry-point-uniform-params.slang @@ -32,9 +32,9 @@ ConstantBuffer<Signs> signs; [numthreads(4, 1, 1)] void computeMain( -//TEST_INPUT:cbuffer(data=[2 0 0 0 3 0 0 0]):name=stuff +//TEST_INPUT:root_constants(data=[2 0 0 0 3 0 0 0]):name=stuff uniform Stuff stuff, -//TEST_INPUT:cbuffer(data=[3]):onlyCPULikeBinding,name=things +//TEST_INPUT:root_constants(data=[3]):onlyCPULikeBinding,name=things uniform Things things, //TEST_INPUT:ubuffer(data=[0 0 0 0], stride=4):out,name=outputBuffer diff --git a/tests/compute/interface-shader-param-in-struct.slang b/tests/compute/interface-shader-param-in-struct.slang index 04854906a..05db7fb39 100644 --- a/tests/compute/interface-shader-param-in-struct.slang +++ b/tests/compute/interface-shader-param-in-struct.slang @@ -79,7 +79,7 @@ struct Stuff [numthreads(4, 1, 1)] void computeMain( -//TEST_INPUT:cbuffer(data=[256]): +//TEST_INPUT:root_constants(data=[256]): uniform Stuff stuff, uint3 dispatchThreadID : SV_DispatchThreadID) diff --git a/tests/compute/interface-shader-param2.slang b/tests/compute/interface-shader-param2.slang index 0d15d9657..6127b3618 100644 --- a/tests/compute/interface-shader-param2.slang +++ b/tests/compute/interface-shader-param2.slang @@ -51,7 +51,7 @@ ConstantBuffer<IRandomNumberGenerationStrategy> gStrategy; [numthreads(4, 1, 1)] void computeMain( -//TEST_INPUT:cbuffer(data=[8 0 0 0], stride=4): +//TEST_INPUT:root_constants(data=[8 0 0 0], stride=4): uniform IModifier modifier, uint3 dispatchThreadID : SV_DispatchThreadID) { diff --git a/tests/compute/interface-shader-param3.slang b/tests/compute/interface-shader-param3.slang index 7e42595a1..17236642c 100644 --- a/tests/compute/interface-shader-param3.slang +++ b/tests/compute/interface-shader-param3.slang @@ -83,7 +83,7 @@ void computeMain( // // Here's the incantation to make the test runner fill in the constant buffer: // -//TEST_INPUT:cbuffer(data=[256 0 0 0 16 0 0 0], stride=4): +//TEST_INPUT:root_constants(data=[256 0 0 0 16 0 0 0], stride=4): // // So, the value `256` will be used for `extra` and the value `16` // will be written to the first four bytes of the concrete value diff --git a/tests/compute/interface-shader-param4.slang b/tests/compute/interface-shader-param4.slang index 625fc751c..6226fd4c6 100644 --- a/tests/compute/interface-shader-param4.slang +++ b/tests/compute/interface-shader-param4.slang @@ -83,7 +83,7 @@ void computeMain( // the previous test, the concrete type plugged in for `modifier` // has no uniform/ordinary data, so we don't need to fill it in. // -//TEST_INPUT:cbuffer(data=[256]): +//TEST_INPUT:root_constants(data=[256]): uint3 dispatchThreadID : SV_DispatchThreadID) { diff --git a/tests/language-feature/shader-params/entry-point-uniform-params.slang b/tests/language-feature/shader-params/entry-point-uniform-params.slang index b94fc4556..b92af5818 100644 --- a/tests/language-feature/shader-params/entry-point-uniform-params.slang +++ b/tests/language-feature/shader-params/entry-point-uniform-params.slang @@ -23,7 +23,7 @@ int test(int val, int a, int b) [shader("compute")] void computeMain( -//TEST_INPUT:cbuffer(data=[256 1]):name=d +//TEST_INPUT:root_constants(data=[256 1]):name=d uniform Data d, //TEST_INPUT:ubuffer(data=[0 0 0 0], stride=4):out,name=outputBuffer diff --git a/tests/vkray/entry-point-params.slang b/tests/vkray/entry-point-params.slang new file mode 100644 index 000000000..84c8ecb50 --- /dev/null +++ b/tests/vkray/entry-point-params.slang @@ -0,0 +1,14 @@ +// entry-point-params.slang + +// Test that entry-point `uniform` parameters on ray-tracing +// shaders properly map to the "shader record" in SPIR-V output. + +//TEST:CROSS_COMPILE: -profile glsl_460 -stage raygeneration -entry main -target spirv-assembly + +RWStructuredBuffer<float> buffer; + +void main( + uniform float value) +{ + buffer[DispatchRaysIndex().x] = value; +} diff --git a/tests/vkray/entry-point-params.slang.glsl b/tests/vkray/entry-point-params.slang.glsl new file mode 100644 index 000000000..8333f21c9 --- /dev/null +++ b/tests/vkray/entry-point-params.slang.glsl @@ -0,0 +1,26 @@ +//TEST_IGNORE_FILE: +#version 460 +#extension GL_NV_ray_tracing : require + +layout(std430, binding = 0) +buffer _S1 { + float _data[]; +} buffer_0; + +struct EntryPointParams_0 +{ + float value_0; +}; + +layout(shaderRecordNV) +buffer _S2 +{ + EntryPointParams_0 _data; +} _S3; + +void main() +{ + uvec3 _S4 = gl_LaunchIDNV; + buffer_0._data[_S4.x] = _S3._data.value_0; + return; +} 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)); |
