summaryrefslogtreecommitdiffstats
path: root/tools/gfx/vulkan/render-vk.cpp
diff options
context:
space:
mode:
authorYong He <yonghe@outlook.com>2021-03-24 13:57:55 -0700
committerGitHub <noreply@github.com>2021-03-24 13:57:55 -0700
commit98afb421f408aa8651afff3dba1b21fad71131fe (patch)
treea8610d12ac7d74a8772cb6ecdd810da8216baa67 /tools/gfx/vulkan/render-vk.cpp
parentd0f7b7f0ed1d0d1388ce944cd1ad906bbd9afb35 (diff)
Reimplement Vulkan shader objects. (#1764)
* Reimplement Vulkan shader objects. This change reimplements Vulkan shader objects in the `gfx` layer so that it is no longer layered on top of the `DescriptorSet` abstraction. Since this is the last implementation that uses `DescriptorSet`, the change also removes all `DescriptorSet` related API from public `gfx` interface. The Vulkan implementation now passes all test cases, but it still have two issues: 1. The PushConstant setting is not correct, this is because we don't seem to be able to get correct reflection data about the size of push constants for an entry-point. 2. The `shader-toy` example can't run on Vulkan, because it currently sets nullptr to `Texture` bindings, and this change doesn't properly handle setting resource to null in `ShaderObject`s yet. If we can use the `nullDescriptor` feature on vulkan, this implementation will be simple. However we still want to decide whether we want to use a Vulkan 1.2 feature for this. * Fix up
Diffstat (limited to 'tools/gfx/vulkan/render-vk.cpp')
-rw-r--r--tools/gfx/vulkan/render-vk.cpp2965
1 files changed, 2106 insertions, 859 deletions
diff --git a/tools/gfx/vulkan/render-vk.cpp b/tools/gfx/vulkan/render-vk.cpp
index 21d437921..6fe72d909 100644
--- a/tools/gfx/vulkan/render-vk.cpp
+++ b/tools/gfx/vulkan/render-vk.cpp
@@ -3,10 +3,10 @@
//WORKING:#include "options.h"
#include "../renderer-shared.h"
-#include "../render-graphics-common.h"
#include "core/slang-basic.h"
#include "core/slang-blob.h"
+#include "core/slang-chunked-list.h"
#include "vk-api.h"
#include "vk-util.h"
@@ -38,14 +38,14 @@
namespace gfx {
using namespace Slang;
-class VKDevice : public GraphicsAPIRenderer
+class VKDevice : public RendererBase
{
public:
enum
{
kMaxRenderTargets = 8,
kMaxAttachments = kMaxRenderTargets + 1,
-
+ kMaxPushConstantSize = 256,
kMaxDescriptorSets = 8,
};
// Renderer implementation
@@ -85,13 +85,13 @@ public:
UInt inputElementCount,
IInputLayout** outLayout) override;
- virtual SLANG_NO_THROW Result SLANG_MCALL createDescriptorSetLayout(
- const IDescriptorSetLayout::Desc& desc,
- IDescriptorSetLayout** outLayout) override;
- virtual SLANG_NO_THROW Result SLANG_MCALL
- createPipelineLayout(const IPipelineLayout::Desc& desc, IPipelineLayout** outLayout) override;
+ virtual Result createShaderObjectLayout(
+ slang::TypeLayoutReflection* typeLayout,
+ ShaderObjectLayoutBase** outLayout) override;
+ virtual Result createShaderObject(ShaderObjectLayoutBase* layout, IShaderObject** outObject)
+ override;
virtual SLANG_NO_THROW Result SLANG_MCALL
- createDescriptorSet(IDescriptorSetLayout* layout, IDescriptorSet::Flag::Enum flag, IDescriptorSet** outDescriptorSet) override;
+ createRootShaderObject(IShaderProgram* program, IShaderObject** outObject) override;
virtual SLANG_NO_THROW Result SLANG_MCALL
createProgram(const IShaderProgram::Desc& desc, IShaderProgram** outProgram) override;
@@ -632,15 +632,728 @@ public:
}
};
- class ShaderProgramImpl: public GraphicsCommonShaderProgram
+ struct BoundVertexBuffer
+ {
+ RefPtr<BufferResourceImpl> m_buffer;
+ int m_stride;
+ int m_offset;
+ };
+
+ class PipelineStateImpl : public PipelineStateBase
+ {
+ public:
+ PipelineStateImpl(const VulkanApi& api):
+ m_api(&api)
+ {
+ }
+ ~PipelineStateImpl()
+ {
+ if (m_pipeline != VK_NULL_HANDLE)
+ {
+ m_api->vkDestroyPipeline(m_api->m_device, m_pipeline, nullptr);
+ }
+ }
+
+ void init(const GraphicsPipelineStateDesc& inDesc)
+ {
+ PipelineStateDesc pipelineDesc;
+ pipelineDesc.type = PipelineType::Graphics;
+ pipelineDesc.graphics = inDesc;
+ initializeBase(pipelineDesc);
+ }
+ void init(const ComputePipelineStateDesc& inDesc)
+ {
+ PipelineStateDesc pipelineDesc;
+ pipelineDesc.type = PipelineType::Compute;
+ pipelineDesc.compute = inDesc;
+ initializeBase(pipelineDesc);
+ }
+
+ const VulkanApi* m_api;
+
+ RefPtr<FramebufferLayoutImpl> m_framebufferLayout;
+
+ VkPipeline m_pipeline = VK_NULL_HANDLE;
+ };
+
+ class ShaderObjectLayoutImpl : public ShaderObjectLayoutBase
{
public:
+ struct BindingRangeInfo
+ {
+ slang::BindingType bindingType;
+ Index count;
+ Index baseIndex;
+ Index descriptorSetIndex;
+ Index rangeIndexInDescriptorSet;
+
+ // Returns true if this binding range consumes a specialization argument slot.
+ bool isSpecializationArg() const
+ {
+ return bindingType == slang::BindingType::ExistentialValue;
+ }
+ };
+
+ struct SubObjectRangeInfo
+ {
+ RefPtr<ShaderObjectLayoutImpl> layout;
+ Index bindingRangeIndex;
+ };
+
+ struct DescriptorSetInfo : public RefObject
+ {
+ List<VkDescriptorSetLayoutBinding> vkBindings;
+ Slang::Int space = -1;
+ };
+
+ struct Builder
+ {
+ public:
+ Builder(VKDevice* renderer)
+ : m_renderer(renderer)
+ {}
+
+ VKDevice* m_renderer;
+ slang::TypeLayoutReflection* m_elementTypeLayout;
+
+ List<BindingRangeInfo> m_bindingRanges;
+ List<SubObjectRangeInfo> m_subObjectRanges;
+
+ Index m_resourceViewCount = 0;
+ Index m_samplerCount = 0;
+ Index m_combinedTextureSamplerCount = 0;
+ Index m_subObjectCount = 0;
+ Index m_varyingInputCount = 0;
+ Index m_varyingOutputCount = 0;
+ uint32_t m_pushConstantSize = 0;
+ List<DescriptorSetInfo> m_descriptorSetBuildInfos;
+ Dictionary<Index, Index> m_mapSpaceToDescriptorSetIndex;
+
+ Index findOrAddDescriptorSet(Index space)
+ {
+ Index index;
+ if (m_mapSpaceToDescriptorSetIndex.TryGetValue(space, index))
+ return index;
+
+ DescriptorSetInfo info = {};
+ info.space = space;
+
+ index = m_descriptorSetBuildInfos.getCount();
+ m_descriptorSetBuildInfos.add(info);
+
+ m_mapSpaceToDescriptorSetIndex.Add(space, index);
+ return index;
+ }
+
+ static VkDescriptorType _mapDescriptorType(slang::BindingType slangBindingType)
+ {
+ switch (slangBindingType)
+ {
+ case slang::BindingType::PushConstant:
+ default:
+ SLANG_ASSERT("unsupported binding type");
+ return VK_DESCRIPTOR_TYPE_MAX_ENUM;
+
+ case slang::BindingType::Sampler:
+ return VK_DESCRIPTOR_TYPE_SAMPLER;
+ case slang::BindingType::CombinedTextureSampler:
+ return VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+ case slang::BindingType::Texture:
+ return VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
+ case slang::BindingType::MutableTexture:
+ return VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
+ case slang::BindingType::TypedBuffer:
+ return VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER;
+ case slang::BindingType::MutableTypedBuffer:
+ return VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER;
+ case slang::BindingType::RawBuffer:
+ case slang::BindingType::MutableRawBuffer:
+ return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+ case slang::BindingType::InputRenderTarget:
+ return VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;
+ case slang::BindingType::InlineUniformData:
+ return VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT;
+ case slang::BindingType::RayTracingAccelerationStructure:
+ return VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR;
+ case slang::BindingType::ConstantBuffer:
+ return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+ }
+ }
+
+ void _addDescriptorSets(
+ slang::TypeLayoutReflection* typeLayout,
+ slang::VariableLayoutReflection* varLayout = nullptr)
+ {
+ SlangInt descriptorSetCount = typeLayout->getDescriptorSetCount();
+ for (SlangInt s = 0; s < descriptorSetCount; ++s)
+ {
+ auto descriptorSetIndex =
+ findOrAddDescriptorSet(typeLayout->getDescriptorSetSpaceOffset(s));
+ auto& descriptorSetInfo = m_descriptorSetBuildInfos[descriptorSetIndex];
+
+ SlangInt descriptorRangeCount =
+ typeLayout->getDescriptorSetDescriptorRangeCount(s);
+ for (SlangInt r = 0; r < descriptorRangeCount; ++r)
+ {
+ auto slangBindingType =
+ typeLayout->getDescriptorSetDescriptorRangeType(s, r);
+
+ switch (slangBindingType)
+ {
+ case slang::BindingType::ExistentialValue:
+ case slang::BindingType::InlineUniformData:
+ case slang::BindingType::PushConstant:
+ continue;
+ default:
+ break;
+ }
+
+ auto vkDescriptorType = _mapDescriptorType(slangBindingType);
+
+ VkDescriptorSetLayoutBinding vkBindingRangeDesc = {};
+ vkBindingRangeDesc.binding =
+ (uint32_t)typeLayout->getDescriptorSetDescriptorRangeIndexOffset(s, r);
+ vkBindingRangeDesc.descriptorCount =
+ (uint32_t)typeLayout->getDescriptorSetDescriptorRangeDescriptorCount(
+ s, r);
+ vkBindingRangeDesc.descriptorType = vkDescriptorType;
+ vkBindingRangeDesc.stageFlags = VK_SHADER_STAGE_ALL;
+ if (varLayout)
+ {
+ auto category =
+ typeLayout->getDescriptorSetDescriptorRangeCategory(s, r);
+ vkBindingRangeDesc.binding += (uint32_t)varLayout->getOffset(category);
+ }
+ descriptorSetInfo.vkBindings.add(vkBindingRangeDesc);
+ }
+ }
+ }
+
+ Result setElementTypeLayout(slang::TypeLayoutReflection* typeLayout)
+ {
+ typeLayout = _unwrapParameterGroups(typeLayout);
+
+ m_elementTypeLayout = typeLayout;
+
+ // First we will use the Slang layout information to allocate
+ // the descriptor set layout(s) required to store values
+ // of the given type.
+ //
+ _addDescriptorSets(typeLayout);
+
+ // Next we will compute the binding ranges that are used to store
+ // the logical contents of the object in memory. These will relate
+ // to the descriptor ranges in the various sets, but not always
+ // in a one-to-one fashion.
+
+ SlangInt bindingRangeCount = typeLayout->getBindingRangeCount();
+ for (SlangInt r = 0; r < bindingRangeCount; ++r)
+ {
+ slang::BindingType slangBindingType = typeLayout->getBindingRangeType(r);
+ uint32_t count = (uint32_t)typeLayout->getBindingRangeBindingCount(r);
+ slang::TypeLayoutReflection* slangLeafTypeLayout =
+ typeLayout->getBindingRangeLeafTypeLayout(r);
+
+ SlangInt descriptorSetIndex = typeLayout->getBindingRangeDescriptorSetIndex(r);
+ SlangInt rangeIndexInDescriptorSet =
+ typeLayout->getBindingRangeFirstDescriptorRangeIndex(r);
+
+ Index baseIndex = 0;
+ switch (slangBindingType)
+ {
+ case slang::BindingType::ConstantBuffer:
+ case slang::BindingType::ParameterBlock:
+ case slang::BindingType::ExistentialValue:
+ baseIndex = m_subObjectCount;
+ m_subObjectCount += count;
+ break;
+
+ case slang::BindingType::Sampler:
+ baseIndex = m_samplerCount;
+ m_samplerCount += count;
+ break;
+
+ case slang::BindingType::CombinedTextureSampler:
+ baseIndex = m_combinedTextureSamplerCount;
+ m_combinedTextureSamplerCount += count;
+ break;
+
+ case slang::BindingType::VaryingInput:
+ baseIndex = m_varyingInputCount;
+ m_varyingInputCount += count;
+ break;
+
+ case slang::BindingType::VaryingOutput:
+ baseIndex = m_varyingOutputCount;
+ m_varyingOutputCount += count;
+ break;
+ case slang::BindingType::PushConstant:
+ baseIndex = m_pushConstantSize;
+ m_pushConstantSize += count;
+ break;
+ default:
+ baseIndex = m_resourceViewCount;
+ m_resourceViewCount += count;
+ break;
+ }
+
+ BindingRangeInfo bindingRangeInfo;
+ bindingRangeInfo.bindingType = slangBindingType;
+ bindingRangeInfo.count = count;
+ bindingRangeInfo.baseIndex = baseIndex;
+ bindingRangeInfo.descriptorSetIndex = descriptorSetIndex;
+ bindingRangeInfo.rangeIndexInDescriptorSet = rangeIndexInDescriptorSet;
+
+ m_bindingRanges.add(bindingRangeInfo);
+ }
+
+ SlangInt subObjectRangeCount = typeLayout->getSubObjectRangeCount();
+ for (SlangInt r = 0; r < subObjectRangeCount; ++r)
+ {
+ SlangInt bindingRangeIndex = typeLayout->getSubObjectRangeBindingRangeIndex(r);
+ auto slangBindingType = typeLayout->getBindingRangeType(bindingRangeIndex);
+ slang::TypeLayoutReflection* slangLeafTypeLayout =
+ typeLayout->getBindingRangeLeafTypeLayout(bindingRangeIndex);
+
+ // A sub-object range can either represent a sub-object of a known
+ // type, like a `ConstantBuffer<Foo>` or `ParameterBlock<Foo>`
+ // (in which case we can pre-compute a layout to use, based on
+ // the type `Foo`) *or* it can represent a sub-object of some
+ // existential type (e.g., `IBar`) in which case we cannot
+ // know the appropraite type/layout of sub-object to allocate.
+ //
+ RefPtr<ShaderObjectLayoutImpl> subObjectLayout;
+ if (slangBindingType != slang::BindingType::ExistentialValue)
+ {
+ ShaderObjectLayoutImpl::createForElementType(
+ m_renderer,
+ slangLeafTypeLayout->getElementTypeLayout(),
+ subObjectLayout.writeRef());
+ }
+
+ SubObjectRangeInfo subObjectRange;
+ subObjectRange.bindingRangeIndex = bindingRangeIndex;
+ subObjectRange.layout = subObjectLayout;
+
+ m_subObjectRanges.add(subObjectRange);
+ }
+ return SLANG_OK;
+ }
+
+ SlangResult build(ShaderObjectLayoutImpl** outLayout)
+ {
+ auto layout = RefPtr<ShaderObjectLayoutImpl>(new ShaderObjectLayoutImpl());
+ SLANG_RETURN_ON_FAIL(layout->_init(this));
+
+ *outLayout = layout.detach();
+ return SLANG_OK;
+ }
+ };
+
+ static Result createForElementType(
+ VKDevice* renderer,
+ slang::TypeLayoutReflection* elementType,
+ ShaderObjectLayoutImpl** outLayout)
+ {
+ Builder builder(renderer);
+ builder.setElementTypeLayout(elementType);
+ return builder.build(outLayout);
+ }
+
+ List<DescriptorSetInfo> const& getDescriptorSets() { return m_descriptorSetInfos; }
+
+ uint32_t getPushConstantSize() { return m_pushConstantSize; }
+
+ List<BindingRangeInfo> const& getBindingRanges() { return m_bindingRanges; }
+
+ Index getBindingRangeCount() { return m_bindingRanges.getCount(); }
+
+ BindingRangeInfo const& getBindingRange(Index index) { return m_bindingRanges[index]; }
+
+ slang::TypeLayoutReflection* getElementTypeLayout() { return m_elementTypeLayout; }
+
+ Index getResourceViewCount() { return m_resourceViewCount; }
+ Index getSamplerCount() { return m_samplerCount; }
+ Index getCombinedTextureSamplerCount() { return m_combinedTextureSamplerCount; }
+ Index getSubObjectCount() { return m_subObjectCount; }
+
+ SubObjectRangeInfo const& getSubObjectRange(Index index)
+ {
+ return m_subObjectRanges[index];
+ }
+ List<SubObjectRangeInfo> const& getSubObjectRanges() { return m_subObjectRanges; }
+
+ RendererBase* getRenderer() { return m_renderer; }
+
+ slang::TypeReflection* getType() { return m_elementTypeLayout->getType(); }
+
+ protected:
+ Result _init(Builder const* builder)
+ {
+ auto renderer = builder->m_renderer;
+
+ initBase(renderer, builder->m_elementTypeLayout);
+
+ m_bindingRanges = builder->m_bindingRanges;
+
+ m_descriptorSetInfos = _Move(builder->m_descriptorSetBuildInfos);
+ m_pushConstantSize = builder->m_pushConstantSize;
+ m_resourceViewCount = builder->m_resourceViewCount;
+ m_samplerCount = builder->m_samplerCount;
+ m_combinedTextureSamplerCount = builder->m_combinedTextureSamplerCount;
+ m_subObjectCount = builder->m_subObjectCount;
+ m_subObjectRanges = builder->m_subObjectRanges;
+ return SLANG_OK;
+ }
+
+ List<DescriptorSetInfo> m_descriptorSetInfos;
+ List<BindingRangeInfo> m_bindingRanges;
+ Index m_resourceViewCount = 0;
+ Index m_samplerCount = 0;
+ Index m_combinedTextureSamplerCount = 0;
+ Index m_subObjectCount = 0;
+ uint32_t m_pushConstantSize = 0;
+ List<SubObjectRangeInfo> m_subObjectRanges;
+ };
+
+ class EntryPointLayout : public ShaderObjectLayoutImpl
+ {
+ typedef ShaderObjectLayoutImpl Super;
+
+ public:
+ struct VaryingInputInfo
+ {};
+
+ struct VaryingOutputInfo
+ {};
+
+ struct Builder : Super::Builder
+ {
+ Builder(VKDevice* device)
+ : Super::Builder(device)
+ {}
+
+ Result build(EntryPointLayout** outLayout)
+ {
+ RefPtr<EntryPointLayout> layout = new EntryPointLayout();
+ SLANG_RETURN_ON_FAIL(layout->_init(this));
+
+ *outLayout = layout.detach();
+ return SLANG_OK;
+ }
+
+ void _addEntryPointParam(slang::VariableLayoutReflection* entryPointParam)
+ {
+ auto slangStage = entryPointParam->getStage();
+ auto typeLayout = entryPointParam->getTypeLayout();
+
+ SlangInt bindingRangeCount = typeLayout->getBindingRangeCount();
+ for (SlangInt r = 0; r < bindingRangeCount; ++r)
+ {
+ slang::BindingType slangBindingType = typeLayout->getBindingRangeType(r);
+ SlangInt count = typeLayout->getBindingRangeBindingCount(r);
+
+ switch (slangBindingType)
+ {
+ default:
+ break;
+
+ case slang::BindingType::VaryingInput:
+ {
+ VaryingInputInfo info;
+
+ m_varyingInputs.add(info);
+ }
+ break;
+
+ case slang::BindingType::VaryingOutput:
+ {
+ VaryingOutputInfo info;
+ m_varyingOutputs.add(info);
+ }
+ break;
+ }
+ }
+ }
+
+ void addEntryPointParams(slang::EntryPointLayout* entryPointLayout)
+ {
+ m_slangEntryPointLayout = entryPointLayout;
+
+ setElementTypeLayout(entryPointLayout->getTypeLayout());
+
+ m_stage = VulkanUtil::getShaderStage(entryPointLayout->getStage());
+ _addEntryPointParam(entryPointLayout->getVarLayout());
+ _addEntryPointParam(entryPointLayout->getResultVarLayout());
+ }
+
+ slang::EntryPointLayout* m_slangEntryPointLayout = nullptr;
+
+ VkShaderStageFlags m_stage;
+ List<VaryingInputInfo> m_varyingInputs;
+ List<VaryingOutputInfo> m_varyingOutputs;
+ };
+
+ Result _init(Builder const* builder)
+ {
+ auto renderer = builder->m_renderer;
+
+ SLANG_RETURN_ON_FAIL(Super::_init(builder));
+
+ m_slangEntryPointLayout = builder->m_slangEntryPointLayout;
+ m_stage = builder->m_stage;
+ m_varyingInputs = builder->m_varyingInputs;
+ m_varyingOutputs = builder->m_varyingOutputs;
+
+ return SLANG_OK;
+ }
+
+ List<VaryingInputInfo> const& getVaryingInputs() { return m_varyingInputs; }
+ List<VaryingOutputInfo> const& getVaryingOutputs() { return m_varyingOutputs; }
+
+ VkShaderStageFlags getStage() const { return m_stage; }
+
+ slang::EntryPointLayout* getSlangLayout() const { return m_slangEntryPointLayout; };
+
+ slang::EntryPointLayout* m_slangEntryPointLayout;
+ VkShaderStageFlags m_stage;
+ List<VaryingInputInfo> m_varyingInputs;
+ List<VaryingOutputInfo> m_varyingOutputs;
+ };
+
+ class RootShaderObjectLayout : public ShaderObjectLayoutImpl
+ {
+ typedef ShaderObjectLayoutImpl Super;
+
+ public:
+ ~RootShaderObjectLayout()
+ {
+ for (auto descSetLayout : m_vkDescriptorSetLayouts)
+ {
+ m_renderer->m_api.vkDestroyDescriptorSetLayout(
+ m_renderer->m_api.m_device, descSetLayout, nullptr);
+ }
+ if (m_pipelineLayout)
+ {
+ m_renderer->m_api.vkDestroyPipelineLayout(
+ m_renderer->m_api.m_device, m_pipelineLayout, nullptr);
+ }
+ }
+
+ struct EntryPointInfo
+ {
+ RefPtr<EntryPointLayout> layout;
+ Index rangeOffset;
+ };
+
+ struct Builder : Super::Builder
+ {
+ Builder(
+ VKDevice* renderer,
+ slang::IComponentType* program,
+ slang::ProgramLayout* programLayout)
+ : Super::Builder(renderer)
+ , m_program(program)
+ , m_programLayout(programLayout)
+ {}
+
+ Result build(RootShaderObjectLayout** outLayout)
+ {
+ RefPtr<RootShaderObjectLayout> layout = new RootShaderObjectLayout();
+ SLANG_RETURN_ON_FAIL(layout->_init(this));
+ *outLayout = layout.detach();
+ return SLANG_OK;
+ }
+
+ void addGlobalParams(slang::VariableLayoutReflection* globalsLayout)
+ {
+ setElementTypeLayout(globalsLayout->getTypeLayout());
+ }
+
+ void addEntryPoint(EntryPointLayout* entryPointLayout)
+ {
+ EntryPointInfo info;
+ info.layout = entryPointLayout;
+
+ if (m_descriptorSetBuildInfos.getCount())
+ {
+ info.rangeOffset = m_descriptorSetBuildInfos[0].vkBindings.getCount();
+ }
+ else
+ {
+ info.rangeOffset = 0;
+ }
+
+ auto slangEntryPointLayout = entryPointLayout->getSlangLayout();
+ _addDescriptorSets(
+ slangEntryPointLayout->getTypeLayout(), slangEntryPointLayout->getVarLayout());
+ m_entryPoints.add(info);
+ }
+
+ slang::IComponentType* m_program;
+ slang::ProgramLayout* m_programLayout;
+ List<EntryPointInfo> m_entryPoints;
+ };
+
+ Index findEntryPointIndex(VkShaderStageFlags stage)
+ {
+ auto entryPointCount = m_entryPoints.getCount();
+ for (Index i = 0; i < entryPointCount; ++i)
+ {
+ auto entryPoint = m_entryPoints[i];
+ if (entryPoint.layout->getStage() == stage)
+ return i;
+ }
+ return -1;
+ }
+
+ EntryPointInfo const& getEntryPoint(Index index) { return m_entryPoints[index]; }
+
+ List<EntryPointInfo> const& getEntryPoints() const { return m_entryPoints; }
+
+ static Result create(
+ VKDevice* renderer,
+ slang::IComponentType* program,
+ slang::ProgramLayout* programLayout,
+ RootShaderObjectLayout** outLayout)
+ {
+ RootShaderObjectLayout::Builder builder(renderer, program, programLayout);
+ builder.addGlobalParams(programLayout->getGlobalParamsVarLayout());
+
+ SlangInt entryPointCount = programLayout->getEntryPointCount();
+ for (SlangInt e = 0; e < entryPointCount; ++e)
+ {
+ auto slangEntryPoint = programLayout->getEntryPointByIndex(e);
+
+ EntryPointLayout::Builder entryPointBuilder(renderer);
+ entryPointBuilder.addEntryPointParams(slangEntryPoint);
+
+ RefPtr<EntryPointLayout> entryPointLayout;
+ SLANG_RETURN_ON_FAIL(entryPointBuilder.build(entryPointLayout.writeRef()));
+
+ builder.addEntryPoint(entryPointLayout);
+ }
+
+ SLANG_RETURN_ON_FAIL(builder.build(outLayout));
+
+ return SLANG_OK;
+ }
+
+ slang::IComponentType* getSlangProgram() const { return m_program; }
+ slang::ProgramLayout* getSlangProgramLayout() const { return m_programLayout; }
+
+ protected:
+ Result _init(Builder const* builder)
+ {
+ auto renderer = builder->m_renderer;
+
+ SLANG_RETURN_ON_FAIL(Super::_init(builder));
+
+ m_program = builder->m_program;
+ m_programLayout = builder->m_programLayout;
+ m_entryPoints = builder->m_entryPoints;
+ m_renderer = renderer;
+
+ if (m_program->getSpecializationParamCount() != 0)
+ return SLANG_OK;
+
+ // For fully specialized shader programs, we create a Vulkan pipeline layout now.
+
+ // First, create `VkDescriptorSetLayout`s for the global scope and all sub-objects
+ // referenced via a `ParameterBlock`.
+ SLANG_RETURN_ON_FAIL(addDescriptorSetLayoutRec(this));
+
+ // Next, collect push constant ranges. We will use one descriptor range for each
+ // entry point that has uniform parameters.
+ uint32_t pushConstantOffset = 0;
+ for (auto& entryPoint : m_entryPoints)
+ {
+ auto size = entryPoint.layout->getPushConstantSize();
+ if (size)
+ {
+ VkPushConstantRange pushConstantRange = {};
+ pushConstantRange.offset = pushConstantOffset;
+ pushConstantRange.size = size;
+ pushConstantRange.stageFlags =
+ VulkanUtil::getShaderStage(entryPoint.layout->getStage());
+ m_pushConstantRanges.add(pushConstantRange);
+ pushConstantOffset += size;
+ }
+ }
+
+ // Now call Vulkan API to create a pipeline layout.
+ VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = {};
+ pipelineLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
+ pipelineLayoutCreateInfo.setLayoutCount = (uint32_t)m_vkDescriptorSetLayouts.getCount();
+ pipelineLayoutCreateInfo.pSetLayouts = m_vkDescriptorSetLayouts.getBuffer();
+ if (m_pushConstantRanges.getCount())
+ {
+ pipelineLayoutCreateInfo.pushConstantRangeCount =
+ (uint32_t)m_pushConstantRanges.getCount();
+ pipelineLayoutCreateInfo.pPushConstantRanges =
+ m_pushConstantRanges.getBuffer();
+ }
+ SLANG_RETURN_ON_FAIL(m_renderer->m_api.vkCreatePipelineLayout(
+ m_renderer->m_api.m_device, &pipelineLayoutCreateInfo, nullptr, &m_pipelineLayout));
+ return SLANG_OK;
+ }
+
+ // Recusively create `VkDescriptorSetLayout` for all descriptor sets used by this and children
+ // shader objects and add them to `m_vkDescriptorSetLayouts`.
+ Result addDescriptorSetLayoutRec(ShaderObjectLayoutImpl* layout)
+ {
+ for (auto& descSetInfo : layout->getDescriptorSets())
+ {
+ VkDescriptorSetLayoutCreateInfo createInfo = {};
+ createInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+ createInfo.pBindings = descSetInfo.vkBindings.getBuffer();
+ createInfo.bindingCount = (uint32_t)descSetInfo.vkBindings.getCount();
+ VkDescriptorSetLayout vkDescSetLayout;
+ SLANG_RETURN_ON_FAIL(m_renderer->m_api.vkCreateDescriptorSetLayout(
+ m_renderer->m_api.m_device, &createInfo, nullptr, &vkDescSetLayout));
+ m_vkDescriptorSetLayouts.add(vkDescSetLayout);
+ }
+
+ // Note: entry point parameters in a `RootShaderObject` has already been included
+ // in `layout->getDescriptorSets()` during `RootShaderObjectLayout` construction,
+ // so we do not need to enumerate entry point array here.
+
+ // However, for sub-objects referenced through `ParameterBlock`s, we do need to
+ // add their descriptor sets to our pipeline layout.
+ // Binding ranges for sub-objects referenced through `ConstantBuffer`s are also
+ // included in this object's layout already, so no need to skip those.
+
+ for (auto& subObject : layout->getSubObjectRanges())
+ {
+ auto bindingRange = layout->getBindingRange(subObject.bindingRangeIndex);
+ if (bindingRange.bindingType == slang::BindingType::ParameterBlock)
+ {
+ SLANG_RETURN_ON_FAIL(addDescriptorSetLayoutRec(subObject.layout));
+ }
+ }
+
+ return SLANG_OK;
+ }
- ShaderProgramImpl(const VulkanApi& api, PipelineType pipelineType):
- m_api(&api),
- m_pipelineType(pipelineType)
+ public:
+ ComPtr<slang::IComponentType> m_program;
+ slang::ProgramLayout* m_programLayout = nullptr;
+ List<EntryPointInfo> m_entryPoints;
+ VkPipelineLayout m_pipelineLayout = VK_NULL_HANDLE;
+ Array<VkDescriptorSetLayout, kMaxDescriptorSets> m_vkDescriptorSetLayouts;
+ Array<VkPushConstantRange, 8> m_pushConstantRanges;
+ RefPtr<VKDevice> m_renderer;
+ };
+
+ class ShaderProgramImpl : public ShaderProgramBase
+ {
+ public:
+ ShaderProgramImpl(const VulkanApi& api, PipelineType pipelineType)
+ : m_api(&api)
+ , m_pipelineType(pipelineType)
{
- for (auto& shaderModule : m_modules) shaderModule = VK_NULL_HANDLE;
+ for (auto& shaderModule : m_modules)
+ shaderModule = VK_NULL_HANDLE;
}
~ShaderProgramImpl()
@@ -658,225 +1371,1311 @@ public:
PipelineType m_pipelineType;
- VkPipelineShaderStageCreateInfo m_compute;
- VkPipelineShaderStageCreateInfo m_vertex;
- VkPipelineShaderStageCreateInfo m_fragment;
-
- List<char> m_buffers[2]; //< To keep storage of code in scope
- VkShaderModule m_modules[2];
+ Array<VkPipelineShaderStageCreateInfo, 8> m_stageCreateInfos;
+ Array<ComPtr<ISlangBlob>, 8> m_codeBlobs; //< To keep storage of code in scope
+ Array<VkShaderModule, 8> m_modules;
+ RefPtr<RootShaderObjectLayout> m_rootObjectLayout;
};
- class DescriptorSetLayoutImpl : public IDescriptorSetLayout, public RefObject
+ class CommandBufferImpl;
+
+ class PipelineCommandEncoder : public RefObject
{
public:
- SLANG_REF_OBJECT_IUNKNOWN_ALL
- IDescriptorSetLayout* getInterface(const Guid& guid)
+ bool m_isOpen = false;
+ CommandBufferImpl* m_commandBuffer;
+ VkCommandBuffer m_vkCommandBuffer;
+ VkCommandBuffer m_vkPreCommandBuffer = VK_NULL_HANDLE;
+ VkPipeline m_boundPipelines[3] = {};
+ VKDevice* m_device = nullptr;
+ RefPtr<PipelineStateImpl> m_currentPipeline;
+
+ static int getBindPointIndex(VkPipelineBindPoint bindPoint)
{
- if (guid == GfxGUID::IID_ISlangUnknown || guid == GfxGUID::IID_IDescriptorSetLayout)
- return static_cast<IDescriptorSetLayout*>(this);
- return nullptr;
+ switch (bindPoint)
+ {
+ case VK_PIPELINE_BIND_POINT_GRAPHICS:
+ return 0;
+ case VK_PIPELINE_BIND_POINT_COMPUTE:
+ return 1;
+ case VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR:
+ return 2;
+ default:
+ assert(!"unknown pipeline type.");
+ return -1;
+ }
+ }
+ VulkanApi* m_api;
+
+ RefPtr<ShaderObjectBase> m_rootShaderObject;
+
+ void init(CommandBufferImpl* commandBuffer);
+
+ void endEncodingImpl()
+ {
+ m_isOpen = false;
+ for (auto& pipeline : m_boundPipelines)
+ pipeline = VK_NULL_HANDLE;
+ }
+
+ static void _uploadBufferData(
+ VkCommandBuffer commandBuffer,
+ BufferResourceImpl* buffer,
+ size_t offset,
+ size_t size,
+ void* data)
+ {
+ auto& api = buffer->m_renderer->m_api;
+
+ assert(buffer->m_uploadBuffer.isInitialized());
+
+ void* mappedData = nullptr;
+ SLANG_VK_CHECK(api.vkMapMemory(
+ api.m_device, buffer->m_uploadBuffer.m_memory, offset, size, 0, &mappedData));
+ memcpy(mappedData, data, size);
+ api.vkUnmapMemory(api.m_device, buffer->m_uploadBuffer.m_memory);
+
+ // Copy from staging buffer to real buffer
+ VkBufferCopy copyInfo = {};
+ copyInfo.size = size;
+ copyInfo.dstOffset = offset;
+ copyInfo.srcOffset = offset;
+ api.vkCmdCopyBuffer(
+ commandBuffer,
+ buffer->m_uploadBuffer.m_buffer,
+ buffer->m_buffer.m_buffer,
+ 1,
+ &copyInfo);
+ }
+
+ void uploadBufferDataImpl(IBufferResource* buffer, size_t offset, size_t size, void* data)
+ {
+ m_vkPreCommandBuffer = m_commandBuffer->getPreCommandBuffer();
+ _uploadBufferData(
+ m_vkPreCommandBuffer, static_cast<BufferResourceImpl*>(buffer), offset, size, data);
+ }
+
+ Result bindRootShaderObjectImpl(PipelineType pipelineType, IShaderObject* object);
+
+ void setPipelineStateImpl(IPipelineState* state)
+ {
+ m_currentPipeline = static_cast<PipelineStateImpl*>(state);
+ }
+
+ void flushBindingState(VkPipelineBindPoint pipelineBindPoint)
+ {
+ auto& api = *m_api;
+ // Get specialized pipeline state and bind it.
+ //
+ RefPtr<PipelineStateBase> newPipeline;
+ m_device->maybeSpecializePipeline(m_currentPipeline, m_rootShaderObject, newPipeline);
+ PipelineStateImpl* newPipelineImpl = static_cast<PipelineStateImpl*>(newPipeline.Ptr());
+ auto pipelineBindPointId = getBindPointIndex(pipelineBindPoint);
+ if (m_boundPipelines[pipelineBindPointId] != newPipelineImpl->m_pipeline)
+ {
+ api.vkCmdBindPipeline(
+ m_vkCommandBuffer, pipelineBindPoint, newPipelineImpl->m_pipeline);
+ m_boundPipelines[pipelineBindPointId] = newPipelineImpl->m_pipeline;
+ }
}
+ };
+
+ union VulkanDescriptorInfo
+ {
+ VkDescriptorBufferInfo bufferInfo;
+ VkDescriptorImageInfo imageInfo;
+ };
+ struct RootBindingState
+ {
+ ShortList<VkWriteDescriptorSet, 32> descriptorSetWrites;
+ ChunkedList<VulkanDescriptorInfo, 32> descriptorInfos;
+ ChunkedList<VkBufferView, 8> bufferViews;
+ Array<uint8_t, kMaxPushConstantSize> pushConstantBuffer;
+ Array<VkDescriptorSet, kMaxDescriptorSets> descriptorSets;
+ };
+ struct BindingOffset
+ {
+ uint32_t uniformOffset;
+ uint32_t pushConstantOffset;
+ uint32_t descriptorSetIndexOffset;
+ uint32_t subObjectDescriptorSetIndexOffset;
+ uint32_t descriptorRangeOffset;
+ };
+
+ class ShaderObjectImpl : public ShaderObjectBase
+ {
public:
- DescriptorSetLayoutImpl(const VulkanApi& api)
- : m_api(&api)
+ static Result create(
+ IDevice* device,
+ ShaderObjectLayoutImpl* layout,
+ ShaderObjectImpl** outShaderObject)
{
+ auto object = RefPtr<ShaderObjectImpl>(new ShaderObjectImpl());
+ SLANG_RETURN_ON_FAIL(object->init(device, layout));
+
+ *outShaderObject = object.detach();
+ return SLANG_OK;
}
- ~DescriptorSetLayoutImpl()
+ RendererBase* getDevice() { return m_layout->getDevice(); }
+
+ SLANG_NO_THROW UInt SLANG_MCALL getEntryPointCount() SLANG_OVERRIDE { return 0; }
+
+ SLANG_NO_THROW Result SLANG_MCALL getEntryPoint(UInt index, IShaderObject** outEntryPoint)
+ SLANG_OVERRIDE
{
- if(m_descriptorSetLayout != VK_NULL_HANDLE)
+ *outEntryPoint = nullptr;
+ return SLANG_OK;
+ }
+
+ ShaderObjectLayoutImpl* getLayout()
+ {
+ return static_cast<ShaderObjectLayoutImpl*>(m_layout.Ptr());
+ }
+
+ SLANG_NO_THROW slang::TypeLayoutReflection* SLANG_MCALL getElementTypeLayout()
+ SLANG_OVERRIDE
+ {
+ return m_layout->getElementTypeLayout();
+ }
+
+ SLANG_NO_THROW Result SLANG_MCALL
+ setData(ShaderOffset const& inOffset, void const* data, size_t inSize) SLANG_OVERRIDE
+ {
+ Index offset = inOffset.uniformOffset;
+ Index size = inSize;
+
+ char* dest = m_ordinaryData.getBuffer();
+ Index availableSize = m_ordinaryData.getCount();
+
+ // TODO: We really should bounds-check access rather than silently ignoring sets
+ // that are too large, but we have several test cases that set more data than
+ // an object actually stores on several targets...
+ //
+ if (offset < 0)
{
- m_api->vkDestroyDescriptorSetLayout(m_api->m_device, m_descriptorSetLayout, nullptr);
+ size += offset;
+ offset = 0;
}
+ if ((offset + size) >= availableSize)
+ {
+ size = availableSize - offset;
+ }
+
+ memcpy(dest + offset, data, size);
+
+ return SLANG_OK;
}
- VulkanApi const* m_api;
- VkDescriptorSetLayout m_descriptorSetLayout = VK_NULL_HANDLE;
+ virtual SLANG_NO_THROW Result SLANG_MCALL
+ setObject(ShaderOffset const& offset, IShaderObject* object) SLANG_OVERRIDE
+ {
+ if (offset.bindingRangeIndex < 0)
+ return SLANG_E_INVALID_ARG;
+ auto layout = getLayout();
+ if (offset.bindingRangeIndex >= layout->getBindingRangeCount())
+ return SLANG_E_INVALID_ARG;
- // 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
+ auto subObject = static_cast<ShaderObjectImpl*>(object);
+
+ auto bindingRangeIndex = offset.bindingRangeIndex;
+ auto& bindingRange = layout->getBindingRange(bindingRangeIndex);
+
+ m_objects[bindingRange.baseIndex + offset.bindingArrayIndex] = subObject;
+
+ // If the range being assigned into represents an interface/existential-type leaf field,
+ // then we need to consider how the `object` being assigned here affects specialization.
+ // We may also need to assign some data from the sub-object into the ordinary data
+ // buffer for the parent object.
+ //
+ if (bindingRange.bindingType == slang::BindingType::ExistentialValue)
+ {
+ // A leaf field of interface type is laid out inside of the parent object
+ // as a tuple of `(RTTI, WitnessTable, Payload)`. The layout of these fields
+ // is a contract between the compiler and any runtime system, so we will
+ // need to rely on details of the binary layout.
+
+ // We start by querying the layout/type of the concrete value that the application
+ // is trying to store into the field, and also the layout/type of the leaf
+ // existential-type field itself.
+ //
+ auto concreteTypeLayout = subObject->getElementTypeLayout();
+ auto concreteType = concreteTypeLayout->getType();
+ //
+ auto existentialTypeLayout =
+ layout->getElementTypeLayout()->getBindingRangeLeafTypeLayout(
+ bindingRangeIndex);
+ auto existentialType = existentialTypeLayout->getType();
+
+ // The first field of the tuple (offset zero) is the run-time type information
+ // (RTTI) ID for the concrete type being stored into the field.
+ //
+ // TODO: We need to be able to gather the RTTI type ID from `object` and then
+ // use `setData(offset, &TypeID, sizeof(TypeID))`.
+
+ // The second field of the tuple (offset 8) is the ID of the "witness" for the
+ // conformance of the concrete type to the interface used by this field.
+ //
+ auto witnessTableOffset = offset;
+ witnessTableOffset.uniformOffset += 8;
+ //
+ // Conformances of a type to an interface are computed and then stored by the
+ // Slang runtime, so we can look up the ID for this particular conformance (which
+ // will create it on demand).
+ //
+ ComPtr<slang::ISession> slangSession;
+ SLANG_RETURN_ON_FAIL(getRenderer()->getSlangSession(slangSession.writeRef()));
+ //
+ // Note: If the type doesn't actually conform to the required interface for
+ // this sub-object range, then this is the point where we will detect that
+ // fact and error out.
+ //
+ uint32_t conformanceID = 0xFFFFFFFF;
+ SLANG_RETURN_ON_FAIL(slangSession->getTypeConformanceWitnessSequentialID(
+ concreteType, existentialType, &conformanceID));
+ //
+ // Once we have the conformance ID, then we can write it into the object
+ // at the required offset.
+ //
+ SLANG_RETURN_ON_FAIL(
+ setData(witnessTableOffset, &conformanceID, sizeof(conformanceID)));
+
+ // The third field of the tuple (offset 16) is the "payload" that is supposed to
+ // hold the data for a value of the given concrete type.
+ //
+ auto payloadOffset = offset;
+ payloadOffset.uniformOffset += 16;
+
+ // There are two cases we need to consider here for how the payload might be used:
+ //
+ // * If the concrete type of the value being bound is one that can "fit" into the
+ // available payload space, then it should be stored in the payload.
+ //
+ // * If the concrete type of the value cannot fit in the payload space, then it
+ // will need to be stored somewhere else.
+ //
+ if (_doesValueFitInExistentialPayload(concreteTypeLayout, existentialTypeLayout))
+ {
+ // If the value can fit in the payload area, then we will go ahead and copy
+ // its bytes into that area.
+ //
+ setData(
+ payloadOffset,
+ subObject->m_ordinaryData.getBuffer(),
+ subObject->m_ordinaryData.getCount());
+ }
+ else
+ {
+ // If the value does *not *fit in the payload area, then there is nothing
+ // we can do at this point (beyond saving a reference to the sub-object, which
+ // was handled above).
+ //
+ // Once all the sub-objects have been set into the parent object, we can
+ // compute a specialized layout for it, and that specialized layout can tell
+ // us where the data for these sub-objects has been laid out.
+ }
+ }
+
+ return SLANG_E_NOT_IMPLEMENTED;
+ }
+
+ virtual SLANG_NO_THROW Result SLANG_MCALL
+ getObject(ShaderOffset const& offset, IShaderObject** outObject) SLANG_OVERRIDE
{
- /// The type of descriptor slot range from the original `Desc`
- DescriptorSlotType type;
+ SLANG_ASSERT(outObject);
+ if (offset.bindingRangeIndex < 0)
+ return SLANG_E_INVALID_ARG;
+ auto layout = getLayout();
+ if (offset.bindingRangeIndex >= layout->getBindingRangeCount())
+ return SLANG_E_INVALID_ARG;
+ auto& bindingRange = layout->getBindingRange(offset.bindingRangeIndex);
+
+ auto object = m_objects[bindingRange.baseIndex + offset.bindingArrayIndex].Ptr();
+ object->addRef();
+ *outObject = object;
+
+ // auto& subObjectRange =
+ // m_layout->getSubObjectRange(bindingRange.subObjectRangeIndex); *outObject =
+ // m_objects[subObjectRange.baseIndex + offset.bindingArrayIndex];
- /// The start index of the range in the appropriate type-specific array
- Index arrayIndex;
+ return SLANG_OK;
- /// The equivalent Vulkan descriptor type, where applicable
- VkDescriptorType vkDescriptorType;
+#if 0
+ SLANG_ASSERT(bindingRange.descriptorSetIndex >= 0);
+ SLANG_ASSERT(bindingRange.descriptorSetIndex < m_descriptorSets.getCount());
+ auto& descriptorSet = m_descriptorSets[bindingRange.descriptorSetIndex];
- /// The Vulkan `binding` index for this range
- uint32_t vkBindingIndex;
- };
- List<RangeInfo> m_ranges;
+ descriptorSet->setConstantBuffer(bindingRange.rangeIndexInDescriptorSet, offset.bindingArrayIndex, buffer);
+ return SLANG_OK;
+#endif
+ }
- // 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.
+ SLANG_NO_THROW Result SLANG_MCALL
+ setResource(ShaderOffset const& offset, IResourceView* resourceView) SLANG_OVERRIDE
+ {
+ if (offset.bindingRangeIndex < 0)
+ return SLANG_E_INVALID_ARG;
+ auto layout = getLayout();
+ if (offset.bindingRangeIndex >= layout->getBindingRangeCount())
+ return SLANG_E_INVALID_ARG;
+ auto& bindingRange = layout->getBindingRange(offset.bindingRangeIndex);
+
+ m_resourceViews[bindingRange.baseIndex + offset.bindingArrayIndex] =
+ static_cast<ResourceViewImpl*>(resourceView);
+ return SLANG_OK;
+ }
+
+ SLANG_NO_THROW Result SLANG_MCALL
+ setSampler(ShaderOffset const& offset, ISamplerState* sampler) SLANG_OVERRIDE
+ {
+ if (offset.bindingRangeIndex < 0)
+ return SLANG_E_INVALID_ARG;
+ auto layout = getLayout();
+ if (offset.bindingRangeIndex >= layout->getBindingRangeCount())
+ return SLANG_E_INVALID_ARG;
+ auto& bindingRange = layout->getBindingRange(offset.bindingRangeIndex);
+
+ m_samplers[bindingRange.baseIndex + offset.bindingArrayIndex] =
+ static_cast<SamplerStateImpl*>(sampler);
+ return SLANG_OK;
+ }
+
+ SLANG_NO_THROW Result SLANG_MCALL setCombinedTextureSampler(
+ ShaderOffset const& offset,
+ IResourceView* textureView,
+ ISamplerState* sampler) SLANG_OVERRIDE
+ {
+ if (offset.bindingRangeIndex < 0)
+ return SLANG_E_INVALID_ARG;
+ auto layout = getLayout();
+ if (offset.bindingRangeIndex >= layout->getBindingRangeCount())
+ return SLANG_E_INVALID_ARG;
+ auto& bindingRange = layout->getBindingRange(offset.bindingRangeIndex);
+
+ auto& slot =
+ m_combinedTextureSamplers[bindingRange.baseIndex + offset.bindingArrayIndex];
+ slot.textureView = static_cast<TextureResourceViewImpl*>(textureView);
+ slot.sampler = static_cast<SamplerStateImpl*>(sampler);
+ return SLANG_OK;
+ }
+
+ public:
+ // Appends all types that are used to specialize the element type of this shader object in
+ // `args` list.
+ virtual Result collectSpecializationArgs(ExtendedShaderObjectTypeList& args) override
+ {
+ auto& subObjectRanges = getLayout()->getSubObjectRanges();
+ // The following logic is built on the assumption that all fields that involve
+ // existential types (and therefore require specialization) will results in a sub-object
+ // range in the type layout. This allows us to simply scan the sub-object ranges to find
+ // out all specialization arguments.
+ Index subObjectRangeCount = subObjectRanges.getCount();
+ for (Index subObjectRangeIndex = 0; subObjectRangeIndex < subObjectRangeCount;
+ subObjectRangeIndex++)
+ {
+ auto const& subObjectRange = subObjectRanges[subObjectRangeIndex];
+ auto const& bindingRange =
+ getLayout()->getBindingRange(subObjectRange.bindingRangeIndex);
+
+ Index count = bindingRange.count;
+ SLANG_ASSERT(count == 1);
+
+ Index subObjectIndexInRange = 0;
+ auto subObject = m_objects[bindingRange.baseIndex + subObjectIndexInRange];
+
+ switch (bindingRange.bindingType)
+ {
+ case slang::BindingType::ExistentialValue:
+ {
+ // A binding type of `ExistentialValue` means the sub-object represents a
+ // interface-typed field. In this case the specialization argument for this
+ // field is the actual specialized type of the bound shader object. If the
+ // shader object's type is an ordinary type without existential fields, then
+ // the type argument will simply be the ordinary type. But if the sub
+ // object's type is itself a specialized type, we need to make sure to use
+ // that type as the specialization argument.
+
+ ExtendedShaderObjectType specializedSubObjType;
+ SLANG_RETURN_ON_FAIL(
+ subObject->getSpecializedShaderObjectType(&specializedSubObjType));
+ args.add(specializedSubObjType);
+ break;
+ }
+ case slang::BindingType::ParameterBlock:
+ case slang::BindingType::ConstantBuffer:
+ // Currently we only handle the case where the field's type is
+ // `ParameterBlock<SomeStruct>` or `ConstantBuffer<SomeStruct>`, where
+ // `SomeStruct` is a struct type (not directly an interface type). In this case,
+ // we just recursively collect the specialization arguments from the bound sub
+ // object.
+ SLANG_RETURN_ON_FAIL(subObject->collectSpecializationArgs(args));
+ // TODO: we need to handle the case where the field is of the form
+ // `ParameterBlock<IFoo>`. We should treat this case the same way as the
+ // `ExistentialValue` case here, but currently we lack a mechanism to
+ // distinguish the two scenarios.
+ break;
+ }
+ // TODO: need to handle another case where specialization happens on resources
+ // fields e.g. `StructuredBuffer<IFoo>`.
+ }
+ return SLANG_OK;
+ }
+
+ protected:
+ friend class RootShaderObjectLayout;
+
+ Result init(IDevice* device, ShaderObjectLayoutImpl* layout)
+ {
+ m_layout = layout;
+
+ // If the layout tells us that there is any uniform data,
+ // then we will allocate a CPU memory buffer to hold that data
+ // while it is being set from the host.
+ //
+ // Once the user is done setting the parameters/fields of this
+ // shader object, we will produce a GPU-memory version of the
+ // uniform data (which includes values from this object and
+ // any existential-type sub-objects).
+ //
+ size_t uniformSize = layout->getElementTypeLayout()->getSize();
+ if (uniformSize)
+ {
+ m_ordinaryData.setCount(uniformSize);
+ memset(m_ordinaryData.getBuffer(), 0, uniformSize);
+ }
+
+#if 0
+ // If the layout tells us there are any descriptor sets to
+ // allocate, then we do so now.
//
- struct RootConstantRangeInfo
+ for(auto descriptorSetInfo : layout->getDescriptorSets())
{
- /// The offset of the range's data in the backing storage.
- Index offset;
+ RefPtr<DescriptorSet> descriptorSet;
+ SLANG_RETURN_ON_FAIL(renderer->createDescriptorSet(descriptorSetInfo->layout, descriptorSet.writeRef()));
+ m_descriptorSets.add(descriptorSet);
+ }
+#endif
- /// The size of the range's data.
- Index size;
- };
- Slang::List<RootConstantRangeInfo> m_rootConstantRanges;
+ m_resourceViews.setCount(layout->getResourceViewCount());
+ m_samplers.setCount(layout->getSamplerCount());
+ m_combinedTextureSamplers.setCount(layout->getCombinedTextureSamplerCount());
- /// The total size, in bytes, or root constant data for this descriptor set.
- uint32_t m_rootConstantDataSize = 0;
+ // If the layout specifies that we have any sub-objects, then
+ // we need to size the array to account for them.
+ //
+ Index subObjectCount = layout->getSubObjectCount();
+ m_objects.setCount(subObjectCount);
- /// The total number of reference counted objects that can be bound
- /// to descriptor sets described by this layout.
- ///
- Index m_totalBoundObjectCount = 0;
+ for (auto subObjectRangeInfo : layout->getSubObjectRanges())
+ {
+ auto subObjectLayout = subObjectRangeInfo.layout;
- /// Vulkan Descriptor set bindings
- Slang::List<VkDescriptorSetLayoutBinding> m_vkBindings;
+ // In the case where the sub-object range represents an
+ // existential-type leaf field (e.g., an `IBar`), we
+ // cannot pre-allocate the object(s) to go into that
+ // range, since we can't possibly know what to allocate
+ // at this point.
+ //
+ if (!subObjectLayout)
+ continue;
+ //
+ // Otherwise, we will allocate a sub-object to fill
+ // in each entry in this range, based on the layout
+ // information we already have.
- };
+ auto& bindingRangeInfo =
+ layout->getBindingRange(subObjectRangeInfo.bindingRangeIndex);
+ for (Index i = 0; i < bindingRangeInfo.count; ++i)
+ {
+ RefPtr<ShaderObjectImpl> subObject;
+ SLANG_RETURN_ON_FAIL(
+ ShaderObjectImpl::create(device, subObjectLayout, subObject.writeRef()));
+ m_objects[bindingRangeInfo.baseIndex + i] = subObject;
+ }
+ }
- class PipelineLayoutImpl : public IPipelineLayout, public RefObject
- {
- public:
- SLANG_REF_OBJECT_IUNKNOWN_ALL
- IPipelineLayout* getInterface(const Guid& guid)
+ return SLANG_OK;
+ }
+
+ /// Write the uniform/ordinary data of this object into the given `dest` buffer at the given
+ /// `offset`
+ Result _writeOrdinaryData(
+ PipelineCommandEncoder* encoder,
+ IBufferResource* buffer,
+ size_t offset,
+ size_t destSize,
+ ShaderObjectLayoutImpl* specializedLayout)
{
- if (guid == GfxGUID::IID_ISlangUnknown || guid == GfxGUID::IID_IPipelineLayout)
+ auto src = m_ordinaryData.getBuffer();
+ auto srcSize = size_t(m_ordinaryData.getCount());
+
+ SLANG_ASSERT(srcSize <= destSize);
+
+ encoder->uploadBufferDataImpl(buffer, offset, srcSize, src);
+
+ // In the case where this object has any sub-objects of
+ // existential/interface type, we need to recurse on those objects
+ // that need to write their state into an appropriate "pending" allocation.
+ //
+ // Note: Any values that could fit into the "payload" included
+ // in the existential-type field itself will have already been
+ // written as part of `setObject()`. This loop only needs to handle
+ // those sub-objects that do not "fit."
+ //
+ // An implementers looking at this code might wonder if things could be changed
+ // so that *all* writes related to sub-objects for interface-type fields could
+ // be handled in this one location, rather than having some in `setObject()` and
+ // others handled here.
+ //
+ Index subObjectRangeCounter = 0;
+ for (auto const& subObjectRangeInfo : specializedLayout->getSubObjectRanges())
{
- return static_cast<IPipelineLayout*>(this);
+ Index subObjectRangeIndex = subObjectRangeCounter++;
+ auto const& bindingRangeInfo =
+ specializedLayout->getBindingRange(subObjectRangeInfo.bindingRangeIndex);
+
+ // We only need to handle sub-object ranges for interface/existential-type fields,
+ // because fields of constant-buffer or parameter-block type are responsible for
+ // the ordinary/uniform data of their own existential/interface-type sub-objects.
+ //
+ if (bindingRangeInfo.bindingType != slang::BindingType::ExistentialValue)
+ continue;
+
+ // Each sub-object range represents a single "leaf" field, but might be nested
+ // under zero or more outer arrays, such that the number of existential values
+ // in the same range can be one or more.
+ //
+ auto count = bindingRangeInfo.count;
+
+ // We are not concerned with the case where the existential value(s) in the range
+ // git into the payload part of the leaf field.
+ //
+ // In the case where the value didn't fit, the Slang layout strategy would have
+ // considered the requirements of the value as a "pending" allocation, and would
+ // allocate storage for the ordinary/uniform part of that pending allocation inside
+ // of the parent object's type layout.
+ //
+ // Here we assume that the Slang reflection API can provide us with a single byte
+ // offset and stride for the location of the pending data allocation in the
+ // specialized type layout, which will store the values for this sub-object range.
+ //
+ // TODO: The reflection API functions we are assuming here haven't been implemented
+ // yet, so the functions being called here are stubs.
+ //
+ // TODO: It might not be that a single sub-object range can reliably map to a single
+ // contiguous array with a single stride; we need to carefully consider what the
+ // layout logic does for complex cases with multiple layers of nested arrays and
+ // structures.
+ //
+ size_t subObjectRangePendingDataOffset =
+ _getSubObjectRangePendingDataOffset(specializedLayout, subObjectRangeIndex);
+ size_t subObjectRangePendingDataStride =
+ _getSubObjectRangePendingDataStride(specializedLayout, subObjectRangeIndex);
+
+ // If the range doesn't actually need/use the "pending" allocation at all, then
+ // we need to detect that case and skip such ranges.
+ //
+ // TODO: This should probably be handled on a per-object basis by caching a "does it
+ // fit?" bit as part of the information for bound sub-objects, given that we already
+ // compute the "does it fit?" status as part of `setObject()`.
+ //
+ if (subObjectRangePendingDataOffset == 0)
+ continue;
+
+ for (Slang::Index i = 0; i < count; ++i)
+ {
+ auto subObject = m_objects[bindingRangeInfo.baseIndex + i];
+
+ RefPtr<ShaderObjectLayoutImpl> subObjectLayout;
+ SLANG_RETURN_ON_FAIL(
+ subObject->_getSpecializedLayout(subObjectLayout.writeRef()));
+
+ auto subObjectOffset =
+ subObjectRangePendingDataOffset + i * subObjectRangePendingDataStride;
+
+ subObject->_writeOrdinaryData(
+ encoder,
+ buffer,
+ offset + subObjectOffset,
+ destSize - subObjectOffset,
+ subObjectLayout);
+ }
}
- return nullptr;
+
+ return SLANG_OK;
+ }
+
+ // As discussed in `_writeOrdinaryData()`, these methods are just stubs waiting for
+ // the "flat" Slang refelction information to provide access to the relevant data.
+ //
+ size_t _getSubObjectRangePendingDataOffset(
+ ShaderObjectLayoutImpl* specializedLayout,
+ Index subObjectRangeIndex)
+ {
+ return 0;
}
+ size_t _getSubObjectRangePendingDataStride(
+ ShaderObjectLayoutImpl* specializedLayout,
+ Index subObjectRangeIndex)
+ {
+ return 0;
+ }
+
public:
- PipelineLayoutImpl(const VulkanApi& api)
- : m_api(&api)
+ struct CombinedTextureSamplerSlot
+ {
+ RefPtr<TextureResourceViewImpl> textureView;
+ RefPtr<SamplerStateImpl> sampler;
+ };
+
+ static void writeBufferDescriptor(
+ RootBindingState* bindingState,
+ BindingOffset offset,
+ VkDescriptorType descriptorType,
+ BufferResourceImpl* buffer)
{
+ auto descriptorSet = bindingState->descriptorSets[offset.descriptorSetIndexOffset];
+ VkWriteDescriptorSet write = {};
+ write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ write.descriptorCount = 1;
+ write.descriptorType = descriptorType;
+ write.dstArrayElement = 0;
+ write.dstBinding = offset.descriptorRangeOffset;
+ write.dstSet = descriptorSet;
+ auto& bufferInfo = bindingState->descriptorInfos.reserveRange(1)->bufferInfo;
+ write.pBufferInfo = &bufferInfo;
+ bufferInfo.buffer = buffer->m_buffer.m_buffer;
+ bufferInfo.offset = 0;
+ bufferInfo.range = buffer->getDesc()->sizeInBytes;
+ bindingState->descriptorSetWrites.add(write);
}
- ~PipelineLayoutImpl()
+ static void writePlainBufferDescriptor(
+ RootBindingState* bindingState,
+ BindingOffset offset,
+ VkDescriptorType descriptorType,
+ ArrayView<RefPtr<ResourceViewImpl>> resourceViews)
{
- if (m_pipelineLayout != VK_NULL_HANDLE)
+ auto descriptorSet = bindingState->descriptorSets[offset.descriptorSetIndexOffset];
+ VkWriteDescriptorSet write = {};
+ write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ write.descriptorCount = (uint32_t)resourceViews.getCount();
+ write.descriptorType = descriptorType;
+ write.dstArrayElement = 0;
+ write.dstBinding = offset.descriptorRangeOffset;
+ write.dstSet = descriptorSet;
+ auto infos = bindingState->descriptorInfos.reserveRange(write.descriptorCount);
+ write.pBufferInfo = (VkDescriptorBufferInfo*)infos;
+ for (Index i = 0; i < resourceViews.getCount(); i++)
{
- m_api->vkDestroyPipelineLayout(m_api->m_device, m_pipelineLayout, nullptr);
+ auto bufferView = static_cast<PlainBufferResourceViewImpl*>(resourceViews[i].Ptr());
+ infos[i].bufferInfo.buffer = bufferView->m_buffer->m_buffer.m_buffer;
+ infos[i].bufferInfo.offset = 0;
+ infos[i].bufferInfo.range = bufferView->m_buffer->getDesc()->sizeInBytes;
}
+ bindingState->descriptorSetWrites.add(write);
}
- VulkanApi const* m_api;
- VkPipelineLayout m_pipelineLayout = VK_NULL_HANDLE;
- UInt m_descriptorSetCount = 0;
+ static void writeTexelBufferDescriptor(
+ RootBindingState* bindingState,
+ BindingOffset offset,
+ VkDescriptorType descriptorType,
+ ArrayView<RefPtr<ResourceViewImpl>> resourceViews)
+ {
+ auto descriptorSet = bindingState->descriptorSets[offset.descriptorSetIndexOffset];
+ VkWriteDescriptorSet write = {};
+ write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ write.descriptorCount = (uint32_t)resourceViews.getCount();
+ write.descriptorType = descriptorType;
+ write.dstArrayElement = 0;
+ write.dstBinding = offset.descriptorRangeOffset;
+ write.dstSet = descriptorSet;
+ auto views = bindingState->bufferViews.reserveRange(write.descriptorCount);
+ write.pTexelBufferView = views;
+ for (Index i = 0; i < resourceViews.getCount(); i++)
+ {
+ views[i] = static_cast<TexelBufferResourceViewImpl*>(resourceViews[i].Ptr())->m_view;
+ }
+ bindingState->descriptorSetWrites.add(write);
+ }
- /// For each descriptor set, stores the start offset of that set's root constant data in the pipeline layout
- List<uint32_t> m_descriptorSetRootConstantOffsets;
- };
+ static void writeTextureSamplerDescriptor(
+ RootBindingState* bindingState,
+ BindingOffset offset,
+ VkDescriptorType descriptorType,
+ ArrayView<CombinedTextureSamplerSlot> slots)
+ {
+ auto descriptorSet = bindingState->descriptorSets[offset.descriptorSetIndexOffset];
+ VkWriteDescriptorSet write = {};
+ write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ write.descriptorCount = (uint32_t)slots.getCount();
+ write.descriptorType = descriptorType;
+ write.dstArrayElement = 0;
+ write.dstBinding = offset.descriptorRangeOffset;
+ write.dstSet = descriptorSet;
+ auto infos = bindingState->descriptorInfos.reserveRange(write.descriptorCount);
+ write.pImageInfo = (VkDescriptorImageInfo*)infos;
+ for (Index i = 0; i < slots.getCount(); i++)
+ {
+ auto texture = slots[i].textureView;
+ auto sampler = slots[i].sampler;
+ auto& imageInfo = ((VkDescriptorImageInfo*)infos)[i];
+ imageInfo.imageView = texture->m_view;
+ imageInfo.imageLayout = texture->m_layout;
+ imageInfo.sampler = sampler->m_sampler;
+ }
+ bindingState->descriptorSetWrites.add(write);
+ }
- class DescriptorSetImpl : public IDescriptorSet, public RefObject
- {
- public:
- SLANG_REF_OBJECT_IUNKNOWN_ALL
- IDescriptorSet* getInterface(const Guid& guid)
+ static void writeTextureDescriptor(
+ RootBindingState* bindingState,
+ BindingOffset offset,
+ VkDescriptorType descriptorType,
+ ArrayView<RefPtr<ResourceViewImpl>> resourceViews)
{
- if (guid == GfxGUID::IID_ISlangUnknown || guid == GfxGUID::IID_IDescriptorSet)
- return static_cast<IDescriptorSet*>(this);
- return nullptr;
+ auto descriptorSet = bindingState->descriptorSets[offset.descriptorSetIndexOffset];
+ VkWriteDescriptorSet write = {};
+ write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ write.descriptorCount = (uint32_t)resourceViews.getCount();
+ write.descriptorType = descriptorType;
+ write.dstArrayElement = 0;
+ write.dstBinding = offset.descriptorRangeOffset;
+ write.dstSet = descriptorSet;
+ auto infos = bindingState->descriptorInfos.reserveRange(write.descriptorCount);
+ write.pImageInfo = (VkDescriptorImageInfo*)infos;
+ for (Index i = 0; i < resourceViews.getCount(); i++)
+ {
+ auto texture = static_cast<TextureResourceViewImpl*>(resourceViews[i].Ptr());
+ auto& imageInfo = ((VkDescriptorImageInfo*)infos)[i];
+ imageInfo.imageView = texture->m_view;
+ imageInfo.imageLayout = texture->m_layout;
+ imageInfo.sampler = 0;
+ }
+ bindingState->descriptorSetWrites.add(write);
+ }
+
+ static void writeSamplerDescriptor(
+ RootBindingState* bindingState,
+ BindingOffset offset,
+ VkDescriptorType descriptorType,
+ ArrayView<RefPtr<SamplerStateImpl>> samplers)
+ {
+ auto descriptorSet = bindingState->descriptorSets[offset.descriptorSetIndexOffset];
+ VkWriteDescriptorSet write = {};
+ write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ write.descriptorCount = (uint32_t)samplers.getCount();
+ write.descriptorType = descriptorType;
+ write.dstArrayElement = 0;
+ write.dstBinding = offset.descriptorRangeOffset;
+ write.dstSet = descriptorSet;
+ auto infos = bindingState->descriptorInfos.reserveRange(write.descriptorCount);
+ write.pImageInfo = (VkDescriptorImageInfo*)infos;
+ for (Index i = 0; i < samplers.getCount(); i++)
+ {
+ auto& imageInfo = ((VkDescriptorImageInfo*)infos)[i];
+ imageInfo.imageView = 0;
+ imageInfo.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
+ imageInfo.sampler = samplers[i]->m_sampler;
+ }
+ bindingState->descriptorSetWrites.add(write);
+ }
+
+ /// Ensure that the `m_ordinaryDataBuffer` has been created, if it is needed
+ Result _ensureOrdinaryDataBufferCreatedIfNeeded(PipelineCommandEncoder* encoder)
+ {
+ // If we have already created a buffer to hold ordinary data, then we should
+ // simply re-use that buffer rather than re-create it.
+ //
+ // TODO: Simply re-using the buffer without any kind of validation checks
+ // means that we are assuming that users cannot or will not perform any `set`
+ // operations on a shader object once an operation has requested this buffer
+ // be created. We need to enforce that rule if we want to rely on it.
+ //
+ if (m_ordinaryDataBuffer)
+ return SLANG_OK;
+
+ // Computing the size of the ordinary data buffer is *not* just as simple
+ // as using the size of the `m_ordinayData` array that we store. The reason
+ // for the added complexity is that interface-type fields may lead to the
+ // storage being specialized such that it needs extra appended data to
+ // store the concrete values that logically belong in those interface-type
+ // fields but wouldn't fit in the fixed-size allocation we gave them.
+ //
+ // TODO: We need to actually implement that logic by using reflection
+ // data computed for the specialized type of this shader object.
+ // For now we just make the simple assumption described above despite
+ // knowing that it is false.
+ //
+ RefPtr<ShaderObjectLayoutImpl> specializedLayout;
+ SLANG_RETURN_ON_FAIL(_getSpecializedLayout(specializedLayout.writeRef()));
+
+ auto specializedOrdinaryDataSize = specializedLayout->getElementTypeLayout()->getSize();
+ if (specializedOrdinaryDataSize == 0)
+ return SLANG_OK;
+
+ // Once we have computed how large the buffer should be, we can allocate
+ // it using the existing public `IDevice` API.
+ //
+ IDevice* device = getRenderer();
+ IBufferResource::Desc bufferDesc;
+ bufferDesc.init(specializedOrdinaryDataSize);
+ bufferDesc.cpuAccessFlags |= IResource::AccessFlag::Write;
+ SLANG_RETURN_ON_FAIL(device->createBufferResource(
+ IResource::Usage::ConstantBuffer,
+ bufferDesc,
+ nullptr,
+ m_ordinaryDataBuffer.writeRef()));
+
+ // Once the buffer is allocated, we can use `_writeOrdinaryData` to fill it in.
+ //
+ // Note that `_writeOrdinaryData` is potentially recursive in the case
+ // where this object contains interface/existential-type fields, so we
+ // don't need or want to inline it into this call site.
+ //
+ SLANG_RETURN_ON_FAIL(_writeOrdinaryData(
+ encoder, m_ordinaryDataBuffer, 0, specializedOrdinaryDataSize, specializedLayout));
+ return SLANG_OK;
+ }
+
+ /// Bind the buffer for ordinary/uniform data, if needed
+ Result _bindOrdinaryDataBufferIfNeeded(
+ PipelineCommandEncoder* encoder,
+ RootBindingState* bindingState,
+ BindingOffset& offset)
+ {
+ // We are going to need to tweak the base binding range index
+ // used for descriptor-set writes if and only if we actually
+ // bind a buffer for ordinary data.
+ //
+ auto& baseRangeIndex = offset.descriptorRangeOffset;
+
+ // We start by ensuring that the buffer is created, if it is needed.
+ //
+ SLANG_RETURN_ON_FAIL(_ensureOrdinaryDataBufferCreatedIfNeeded(encoder));
+
+ // If we did indeed need/create a buffer, then we must bind it into
+ // the given `descriptorSet` and update the base range index for
+ // subsequent binding operations to account for it.
+ //
+ if (m_ordinaryDataBuffer)
+ {
+ auto bufferImpl = static_cast<BufferResourceImpl*>(m_ordinaryDataBuffer.get());
+ writeBufferDescriptor(
+ bindingState, offset, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, bufferImpl);
+ offset.descriptorRangeOffset++;
+ }
+
+ return SLANG_OK;
}
+
public:
- DescriptorSetImpl(VKDevice* renderer)
- : m_renderer(renderer)
+ Result bindDescriptorRanges(
+ PipelineCommandEncoder* encoder,
+ RootBindingState* bindingState,
+ BindingOffset& offset)
{
+ auto layout = getLayout();
+
+ // Fill in the descriptor sets based on binding ranges
+ //
+ for (auto bindingRangeInfo : layout->getBindingRanges())
+ {
+ auto descriptorSet =
+ bindingState->descriptorSets[bindingRangeInfo.descriptorSetIndex];
+ auto rangeIndex =
+ bindingRangeInfo.rangeIndexInDescriptorSet + offset.descriptorRangeOffset;
+ auto baseIndex = bindingRangeInfo.baseIndex;
+ auto count = (uint32_t)bindingRangeInfo.count;
+ switch (bindingRangeInfo.bindingType)
+ {
+ case slang::BindingType::ConstantBuffer:
+ for (uint32_t i = 0; i < count; ++i)
+ {
+ ShaderObjectImpl* subObject = m_objects[baseIndex + i];
+ subObject->bindObject(encoder, bindingState, offset);
+ }
+ break;
+ case slang::BindingType::ParameterBlock:
+ for (uint32_t i = 0; i < count; ++i)
+ {
+ offset.subObjectDescriptorSetIndexOffset++;
+ ShaderObjectImpl* subObject = m_objects[baseIndex + i];
+ auto newOffset = offset;
+ newOffset.descriptorSetIndexOffset =
+ offset.subObjectDescriptorSetIndexOffset;
+ newOffset.uniformOffset = 0;
+ newOffset.descriptorRangeOffset = 0;
+ subObject->bindObject(encoder, bindingState, newOffset);
+ offset.subObjectDescriptorSetIndexOffset =
+ newOffset.subObjectDescriptorSetIndexOffset;
+ offset.pushConstantOffset = newOffset.pushConstantOffset;
+ }
+ break;
+ case slang::BindingType::Texture:
+ writeTextureDescriptor(
+ bindingState,
+ offset,
+ VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
+ m_resourceViews.getArrayView(baseIndex, count));
+ offset.descriptorRangeOffset++;
+ break;
+ case slang::BindingType::MutableTexture:
+ writeTextureDescriptor(
+ bindingState,
+ offset,
+ VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
+ m_resourceViews.getArrayView(baseIndex, count));
+ offset.descriptorRangeOffset++;
+ break;
+ case slang::BindingType::CombinedTextureSampler:
+ writeTextureSamplerDescriptor(
+ bindingState,
+ offset,
+ VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+ m_combinedTextureSamplers.getArrayView(baseIndex, count));
+ offset.descriptorRangeOffset++;
+ break;
+
+ case slang::BindingType::Sampler:
+ writeSamplerDescriptor(
+ bindingState,
+ offset,
+ VK_DESCRIPTOR_TYPE_SAMPLER,
+ m_samplers.getArrayView(baseIndex, count));
+ offset.descriptorRangeOffset++;
+ break;
+
+ case slang::BindingType::RawBuffer:
+ case slang::BindingType::MutableRawBuffer:
+ writePlainBufferDescriptor(
+ bindingState,
+ offset,
+ VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+ m_resourceViews.getArrayView(baseIndex, count));
+ offset.descriptorRangeOffset++;
+ break;
+
+ case slang::BindingType::TypedBuffer:
+ writeTexelBufferDescriptor(
+ bindingState,
+ offset,
+ VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER,
+ m_resourceViews.getArrayView(baseIndex, count));
+ offset.descriptorRangeOffset++;
+ break;
+ case slang::BindingType::MutableTypedBuffer:
+ writeTexelBufferDescriptor(
+ bindingState,
+ offset,
+ VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER,
+ m_resourceViews.getArrayView(baseIndex, count));
+ offset.descriptorRangeOffset++;
+ offset.descriptorRangeOffset++;
+ break;
+
+ case slang::BindingType::VaryingInput:
+ case slang::BindingType::VaryingOutput:
+ break;
+
+ case slang::BindingType::ExistentialValue:
+ //
+ // TODO: If the existential value is one that "fits" into the storage available,
+ // then we should write its data directly into that area. Otherwise, we need
+ // to bind its content as "pending" data which will come after any other data
+ // beloning to the same set (that is, it's starting descriptorRangeIndex will
+ // need to be one after the number of ranges accounted for in the original type)
+ //
+ break;
+
+ default:
+ SLANG_ASSERT(!"unsupported binding type");
+ return SLANG_FAIL;
+ break;
+ }
+ }
+ return SLANG_OK;
}
- ~DescriptorSetImpl()
+ virtual Result bindObject(
+ PipelineCommandEncoder* encoder,
+ RootBindingState* bindingState,
+ BindingOffset& offset)
{
- m_renderer->descriptorSetAllocator.free(m_descriptorSet);
+ SLANG_RETURN_ON_FAIL(_bindOrdinaryDataBufferIfNeeded(encoder, bindingState, offset));
+
+ SLANG_RETURN_ON_FAIL(bindDescriptorRanges(encoder, bindingState, offset));
+ return SLANG_OK;
}
- virtual SLANG_NO_THROW void SLANG_MCALL setConstantBuffer(UInt range, UInt index, IBufferResource* buffer) override;
- virtual SLANG_NO_THROW void SLANG_MCALL
- setResource(UInt range, UInt index, IResourceView* view) override;
- virtual SLANG_NO_THROW void SLANG_MCALL
- setSampler(UInt range, UInt index, ISamplerState* sampler) override;
- virtual SLANG_NO_THROW void SLANG_MCALL setCombinedTextureSampler(
- UInt range,
- UInt index,
- IResourceView* textureView,
- ISamplerState* sampler) override;
- virtual SLANG_NO_THROW void SLANG_MCALL
- setRootConstants(
- UInt range,
- UInt offset,
- UInt size,
- void const* data) override;
+ /// Any "ordinary" / uniform data for this object
+ List<char> m_ordinaryData;
- VKDevice* m_renderer = nullptr; ///< Weak pointer, can't be strong, because if set will become circular reference
- RefPtr<DescriptorSetLayoutImpl> m_layout;
- VulkanDescriptorSet m_descriptorSet = {};
+ List<RefPtr<ResourceViewImpl>> m_resourceViews;
- /// Records entities that are bound to this descriptor set, and keeps the associated resources/views/state in scope
- List<RefPtr<RefObject>> m_boundObjects;
+ List<RefPtr<SamplerStateImpl>> m_samplers;
- /// Backing storage for root constant ranges belonging to this descriptor set
- List<char> m_rootConstantData;
+ List<CombinedTextureSamplerSlot> m_combinedTextureSamplers;
- bool m_isTransient = false;
+ List<RefPtr<ShaderObjectImpl>> m_objects;
+
+ /// A constant buffer used to stored ordinary data for this object
+ /// and existential-type sub-objects.
+ ///
+ /// Created on demand with `_createOrdinaryDataBufferIfNeeded()`
+ ComPtr<IBufferResource> m_ordinaryDataBuffer;
+
+ /// Get the layout of this shader object with specialization arguments considered
+ ///
+ /// This operation should only be called after the shader object has been
+ /// fully filled in and finalized.
+ ///
+ Result _getSpecializedLayout(ShaderObjectLayoutImpl** outLayout)
+ {
+ if (!m_specializedLayout)
+ {
+ SLANG_RETURN_ON_FAIL(_createSpecializedLayout(m_specializedLayout.writeRef()));
+ }
+ *outLayout = RefPtr<ShaderObjectLayoutImpl>(m_specializedLayout).detach();
+ return SLANG_OK;
+ }
+
+ /// Create the layout for this shader object with specialization arguments considered
+ ///
+ /// This operation is virtual so that it can be customized by `ProgramVars`.
+ ///
+ virtual Result _createSpecializedLayout(ShaderObjectLayoutImpl** outLayout)
+ {
+ ExtendedShaderObjectType extendedType;
+ SLANG_RETURN_ON_FAIL(getSpecializedShaderObjectType(&extendedType));
+
+ auto renderer = getRenderer();
+ RefPtr<ShaderObjectLayoutBase> layout;
+ SLANG_RETURN_ON_FAIL(
+ renderer->getShaderObjectLayout(extendedType.slangType, layout.writeRef()));
+
+ *outLayout = static_cast<ShaderObjectLayoutImpl*>(layout.detach());
+ return SLANG_OK;
+ }
+
+ RefPtr<ShaderObjectLayoutImpl> m_specializedLayout;
};
- struct BoundVertexBuffer
+ class EntryPointShaderObject : public ShaderObjectImpl
{
- RefPtr<BufferResourceImpl> m_buffer;
- int m_stride;
- int m_offset;
+ typedef ShaderObjectImpl Super;
+
+ public:
+ static Result create(
+ IDevice* device,
+ EntryPointLayout* layout,
+ EntryPointShaderObject** outShaderObject)
+ {
+ RefPtr<EntryPointShaderObject> object = new EntryPointShaderObject();
+ SLANG_RETURN_ON_FAIL(object->init(device, layout));
+
+ *outShaderObject = object.detach();
+ return SLANG_OK;
+ }
+
+ EntryPointLayout* getLayout() { return static_cast<EntryPointLayout*>(m_layout.Ptr()); }
+
+ virtual Result bindObject(
+ PipelineCommandEncoder* encoder,
+ RootBindingState* bindingState,
+ BindingOffset& offset) override
+ {
+ // Copy entry point uniform data into global push constant buffer.
+ if (m_ordinaryData.getCount())
+ {
+ Index dstOffset = bindingState->pushConstantBuffer.getCount();
+ ::memcpy(
+ bindingState->pushConstantBuffer.getBuffer() + dstOffset,
+ m_ordinaryData.getBuffer(),
+ m_ordinaryData.getCount());
+ bindingState->pushConstantBuffer.setCount(dstOffset + m_ordinaryData.getCount());
+ }
+
+ SLANG_RETURN_ON_FAIL(bindDescriptorRanges(encoder, bindingState, offset));
+ return SLANG_OK;
+ }
+
+ protected:
+ Result init(IDevice* device, EntryPointLayout* layout)
+ {
+ SLANG_RETURN_ON_FAIL(Super::init(device, layout));
+ return SLANG_OK;
+ }
};
- class PipelineStateImpl : public PipelineStateBase
+ class RootShaderObjectImpl : public ShaderObjectImpl
{
+ typedef ShaderObjectImpl Super;
+
public:
- PipelineStateImpl(const VulkanApi& api):
- m_api(&api)
+ static Result create(
+ IDevice* device,
+ RootShaderObjectLayout* layout,
+ RootShaderObjectImpl** outShaderObject)
{
+ RefPtr<RootShaderObjectImpl> object = new RootShaderObjectImpl();
+ SLANG_RETURN_ON_FAIL(object->init(device, layout));
+
+ *outShaderObject = object.detach();
+ return SLANG_OK;
}
- ~PipelineStateImpl()
+
+ RootShaderObjectLayout* getLayout()
{
- if (m_pipeline != VK_NULL_HANDLE)
+ return static_cast<RootShaderObjectLayout*>(m_layout.Ptr());
+ }
+
+ RootShaderObjectLayout* getSpecializedLayout()
+ {
+ RefPtr<ShaderObjectLayoutImpl> specializedLayout;
+ _getSpecializedLayout(specializedLayout.writeRef());
+ return static_cast<RootShaderObjectLayout*>(m_specializedLayout.Ptr());
+ }
+
+ List<RefPtr<EntryPointShaderObject>> const& getEntryPoints() const { return m_entryPoints; }
+
+ UInt SLANG_MCALL getEntryPointCount() SLANG_OVERRIDE
+ {
+ return (UInt)m_entryPoints.getCount();
+ }
+ SlangResult SLANG_MCALL getEntryPoint(UInt index, IShaderObject** outEntryPoint)
+ SLANG_OVERRIDE
+ {
+ *outEntryPoint = m_entryPoints[index];
+ m_entryPoints[index]->addRef();
+ return SLANG_OK;
+ }
+
+ virtual Result bindObject(
+ PipelineCommandEncoder* encoder,
+ RootBindingState* bindingState,
+ BindingOffset& offset) override
+ {
+ SLANG_RETURN_ON_FAIL(Super::bindObject(encoder, bindingState, offset));
+
+ // Bind all entry points.
+ for (auto& entryPoint : m_entryPoints)
{
- m_api->vkDestroyPipeline(m_api->m_device, m_pipeline, nullptr);
+ entryPoint->bindObject(encoder, bindingState, offset);
}
+ return SLANG_OK;
}
- void init(const GraphicsPipelineStateDesc& inDesc)
+ virtual Result collectSpecializationArgs(ExtendedShaderObjectTypeList& args) override
{
- PipelineStateDesc pipelineDesc;
- pipelineDesc.type = PipelineType::Graphics;
- pipelineDesc.graphics = inDesc;
- initializeBase(pipelineDesc);
+ SLANG_RETURN_ON_FAIL(ShaderObjectImpl::collectSpecializationArgs(args));
+ for (auto& entryPoint : m_entryPoints)
+ {
+ SLANG_RETURN_ON_FAIL(entryPoint->collectSpecializationArgs(args));
+ }
+ return SLANG_OK;
}
- void init(const ComputePipelineStateDesc& inDesc)
+
+ protected:
+ Result init(IDevice* device, RootShaderObjectLayout* layout)
{
- PipelineStateDesc pipelineDesc;
- pipelineDesc.type = PipelineType::Compute;
- pipelineDesc.compute = inDesc;
- initializeBase(pipelineDesc);
+ SLANG_RETURN_ON_FAIL(Super::init(device, layout));
+
+ for (auto entryPointInfo : layout->getEntryPoints())
+ {
+ RefPtr<EntryPointShaderObject> entryPoint;
+ SLANG_RETURN_ON_FAIL(EntryPointShaderObject::create(
+ device, entryPointInfo.layout, entryPoint.writeRef()));
+ m_entryPoints.add(entryPoint);
+ }
+
+ return SLANG_OK;
}
- const VulkanApi* m_api;
+ Result _createSpecializedLayout(ShaderObjectLayoutImpl** outLayout) SLANG_OVERRIDE
+ {
+ ExtendedShaderObjectTypeList specializationArgs;
+ SLANG_RETURN_ON_FAIL(collectSpecializationArgs(specializationArgs));
- RefPtr<FramebufferLayoutImpl> m_framebufferLayout;
+ // Note: There is an important policy decision being made here that we need
+ // to approach carefully.
+ //
+ // We are doing two different things that affect the layout of a program:
+ //
+ // 1. We are *composing* one or more pieces of code (notably the shared global/module
+ // stuff and the per-entry-point stuff).
+ //
+ // 2. We are *specializing* code that includes generic/existential parameters
+ // to concrete types/values.
+ //
+ // We need to decide the relative *order* of these two steps, because of how it impacts
+ // layout. The layout for `specialize(compose(A,B), X, Y)` is potentially different
+ // form that of `compose(specialize(A,X), speciealize(B,Y))`, even when both are
+ // semantically equivalent programs.
+ //
+ // Right now we are using the first option: we are first generating a full composition
+ // of all the code we plan to use (global scope plus all entry points), and then
+ // specializing it to the concatenated specialization argumenst for all of that.
+ //
+ // In some cases, though, this model isn't appropriate. For example, when dealing with
+ // ray-tracing shaders and local root signatures, we really want the parameters of each
+ // entry point (actually, each entry-point *group*) to be allocated distinct storage,
+ // which really means we want to compute something like:
+ //
+ // SpecializedGlobals = specialize(compose(ModuleA, ModuleB, ...), X, Y, ...)
+ //
+ // SpecializedEP1 = compose(SpecializedGlobals, specialize(EntryPoint1, T, U, ...))
+ // SpecializedEP2 = compose(SpecializedGlobals, specialize(EntryPoint2, A, B, ...))
+ //
+ // Note how in this case all entry points agree on the layout for the shared/common
+ // parmaeters, but their layouts are also independent of one another.
+ //
+ // Furthermore, in this example, loading another entry point into the system would not
+ // rquire re-computing the layouts (or generated kernel code) for any of the entry
+ // points that had already been loaded (in contrast to a compose-then-specialize
+ // approach).
+ //
+ ComPtr<slang::IComponentType> specializedComponentType;
+ ComPtr<slang::IBlob> diagnosticBlob;
+ auto result = getLayout()->getSlangProgram()->specialize(
+ specializationArgs.components.getArrayView().getBuffer(),
+ specializationArgs.getCount(),
+ specializedComponentType.writeRef(),
+ diagnosticBlob.writeRef());
+
+ // TODO: print diagnostic message via debug output interface.
+
+ if (result != SLANG_OK)
+ return result;
+
+ auto slangSpecializedLayout = specializedComponentType->getLayout();
+ RefPtr<RootShaderObjectLayout> specializedLayout;
+ RootShaderObjectLayout::create(
+ static_cast<VKDevice*>(getRenderer()),
+ specializedComponentType,
+ slangSpecializedLayout,
+ specializedLayout.writeRef());
+
+ // Note: Computing the layout for the specialized program will have also computed
+ // the layouts for the entry points, and we really need to attach that information
+ // to them so that they don't go and try to compute their own specializations.
+ //
+ // TODO: Well, if we move to the specialization model described above then maybe
+ // we *will* want entry points to do their own specialization work...
+ //
+ auto entryPointCount = m_entryPoints.getCount();
+ for (Index i = 0; i < entryPointCount; ++i)
+ {
+ auto entryPointInfo = specializedLayout->getEntryPoint(i);
+ auto entryPointVars = m_entryPoints[i];
- VkPipeline m_pipeline = VK_NULL_HANDLE;
+ entryPointVars->m_specializedLayout = entryPointInfo.layout;
+ }
+
+ *outLayout = specializedLayout.detach();
+ return SLANG_OK;
+ }
+
+ List<RefPtr<EntryPointShaderObject>> m_entryPoints;
};
class CommandBufferImpl
@@ -955,246 +2754,6 @@ public:
}
public:
- static void _uploadBufferData(
- VkCommandBuffer commandBuffer,
- BufferResourceImpl* buffer,
- size_t offset,
- size_t size,
- void* data)
- {
- auto& api = buffer->m_renderer->m_api;
-
- assert(buffer->m_uploadBuffer.isInitialized());
-
- void* mappedData = nullptr;
- SLANG_VK_CHECK(api.vkMapMemory(
- api.m_device, buffer->m_uploadBuffer.m_memory, offset, size, 0, &mappedData));
- memcpy(mappedData, data, size);
- api.vkUnmapMemory(api.m_device, buffer->m_uploadBuffer.m_memory);
-
- // Copy from staging buffer to real buffer
- VkBufferCopy copyInfo = {};
- copyInfo.size = size;
- copyInfo.dstOffset = offset;
- copyInfo.srcOffset = offset;
- api.vkCmdCopyBuffer(
- commandBuffer,
- buffer->m_uploadBuffer.m_buffer,
- buffer->m_buffer.m_buffer,
- 1,
- &copyInfo);
- }
-
- class PipelineCommandEncoder
- : public GraphicsComputeCommandEncoderBase
- , public RefObject
- {
- public:
- bool m_isOpen = false;
- CommandBufferImpl* m_commandBuffer;
- VkCommandBuffer m_vkCommandBuffer;
- VkCommandBuffer m_vkPreCommandBuffer = VK_NULL_HANDLE;
- VkPipeline m_boundPipelines[3] = {};
- static int getBindPointIndex(VkPipelineBindPoint bindPoint)
- {
- switch (bindPoint)
- {
- case VK_PIPELINE_BIND_POINT_GRAPHICS:
- return 0;
- case VK_PIPELINE_BIND_POINT_COMPUTE:
- return 1;
- case VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR:
- return 2;
- default:
- assert(!"unknown pipeline type.");
- return -1;
- }
- }
- VulkanApi* m_api;
-
- RefPtr<PipelineLayoutImpl> m_currentPipelineLayout;
-
- RefPtr<DescriptorSetImpl> m_currentDescriptorSetImpls[kMaxDescriptorSets];
- VkDescriptorSet m_currentDescriptorSets[kMaxDescriptorSets];
-
- // Temporary list used by flushBindingState to avoid per-frame allocation.
- List<VkCopyDescriptorSet> m_descSetCopies;
-
- void init(CommandBufferImpl* commandBuffer)
- {
- m_commandBuffer = commandBuffer;
- m_rendererBase = static_cast<RendererBase*>(commandBuffer->m_renderer);
- m_vkCommandBuffer = m_commandBuffer->m_commandBuffer;
- m_api = &m_commandBuffer->m_renderer->m_api;
- }
-
- void endEncodingImpl()
- {
- m_isOpen = false;
-
- // Make m_currentDescriptorSets consistent with m_currentDescriptorSetImpls
- // so that we don't mistakenly treat any transient descriptor sets as "copied"
- // later.
- for (uint32_t i = 0; i < kMaxDescriptorSets; i++)
- {
- if (m_currentDescriptorSetImpls[i])
- {
- m_currentDescriptorSets[i] =
- m_currentDescriptorSetImpls[i]->m_descriptorSet.handle;
- }
- }
- for (auto& pipeline : m_boundPipelines)
- pipeline = VK_NULL_HANDLE;
- }
-
- virtual SLANG_NO_THROW void SLANG_MCALL setDescriptorSetImpl(
- PipelineType pipelineType,
- IPipelineLayout* layout,
- UInt index,
- IDescriptorSet* descriptorSet) override
- {
- // Ideally this should eventually be as simple as:
- //
- // m_api.vkCmdBindDescriptorSets(
- // commandBuffer,
- // translatePipelineBindPoint(pipelineType),
- // layout->m_pipelineLayout,
- // index,
- // 1,
- // ((DescriptorSetImpl*) descriptorSet)->m_descriptorSet,
- // 0,
- // nullptr);
- //
- // For now we are lazily flushing state right before drawing, so
- // we will hang onto the parameters that were passed in and then
- // use them later.
- //
-
- auto descriptorSetImpl = (DescriptorSetImpl*)descriptorSet;
- m_currentDescriptorSetImpls[index] = descriptorSetImpl;
- m_currentDescriptorSets[index] = descriptorSetImpl->m_descriptorSet.handle;
- }
-
- virtual SLANG_NO_THROW void SLANG_MCALL uploadBufferDataImpl(
- IBufferResource* buffer,
- size_t offset,
- size_t size,
- void* data) override
- {
- m_vkPreCommandBuffer = m_commandBuffer->getPreCommandBuffer();
- _uploadBufferData(
- m_vkPreCommandBuffer,
- static_cast<BufferResourceImpl*>(buffer),
- offset,
- size,
- data);
- }
-
- void setPipelineStateImpl(IPipelineState* state)
- {
- m_currentPipeline = static_cast<PipelineStateImpl*>(state);
- }
-
- void flushBindingState(VkPipelineBindPoint pipelineBindPoint)
- {
- auto& api = *m_api;
-
- auto pipeline = static_cast<PipelineStateImpl*>(m_currentPipeline.Ptr());
- auto& descSetCopies = m_descSetCopies;
- descSetCopies.clear();
- // We start by binding the pipeline state.
- //
- auto pipelineBindPointId = getBindPointIndex(pipelineBindPoint);
- if (m_boundPipelines[pipelineBindPointId] != pipeline->m_pipeline)
- {
- api.vkCmdBindPipeline(m_vkCommandBuffer, pipelineBindPoint, pipeline->m_pipeline);
- m_boundPipelines[pipelineBindPointId] = pipeline->m_pipeline;
- }
-
- // Next we bind all the descriptor sets that were set in the `VKDevice`.
- //
- auto pipelineLayoutImpl = static_cast<PipelineLayoutImpl*>(pipeline->m_pipelineLayout.get());
- auto vkPipelineLayout = pipelineLayoutImpl->m_pipelineLayout;
- auto descriptorSetCount = pipelineLayoutImpl->m_descriptorSetCount;
- for (uint32_t i = 0; i < (uint32_t)descriptorSetCount; i++)
- {
- if (m_currentDescriptorSetImpls[i]->m_isTransient)
- {
- // A transient descriptor set may go out of life cycle after command list
- // recording, therefore we must make a copy of it in the per-frame
- // descriptor pool.
-
- // If we have already created a transient copy for this descriptor set, skip
- // the copy.
- if (m_currentDescriptorSetImpls[i]->m_descriptorSet.handle !=
- m_currentDescriptorSets[i])
- continue;
-
- auto descSet = m_commandBuffer->m_transientDescSetAllocator->allocate(
- m_currentDescriptorSetImpls[i]->m_layout->m_descriptorSetLayout);
- uint32_t bindingIndex = 0;
- for (auto binding : m_currentDescriptorSetImpls[i]->m_layout->m_vkBindings)
- {
- VkCopyDescriptorSet copy = {};
- copy.sType = VK_STRUCTURE_TYPE_COPY_DESCRIPTOR_SET;
- copy.srcSet = m_currentDescriptorSetImpls[i]->m_descriptorSet.handle;
- copy.dstSet = descSet.handle;
- copy.srcBinding = copy.dstBinding = bindingIndex;
- copy.srcArrayElement = copy.dstArrayElement = 0;
- copy.descriptorCount = binding.descriptorCount;
- descSetCopies.add(copy);
- bindingIndex++;
- }
- m_currentDescriptorSets[i] = descSet.handle;
- }
- }
- if (descSetCopies.getCount())
- {
- api.vkUpdateDescriptorSets(
- api.m_device,
- 0,
- nullptr,
- (uint32_t)descSetCopies.getCount(),
- descSetCopies.getBuffer());
- }
- api.vkCmdBindDescriptorSets(
- m_vkCommandBuffer,
- 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];
-
- api.vkCmdPushConstants(
- m_vkCommandBuffer,
- vkPipelineLayout,
- VK_SHADER_STAGE_ALL,
- offset,
- size,
- data);
- }
- }
- };
class RenderCommandEncoder
: public IRenderCommandEncoder
, public PipelineCommandEncoder
@@ -1253,14 +2812,6 @@ public:
setPipelineStateImpl(pipelineState);
}
- virtual SLANG_NO_THROW void SLANG_MCALL setDescriptorSet(
- IPipelineLayout* layout,
- UInt index,
- IDescriptorSet* descriptorSet) override
- {
- setDescriptorSetImpl(PipelineType::Graphics, layout, index, descriptorSet);
- }
-
virtual SLANG_NO_THROW void SLANG_MCALL
bindRootShaderObject(IShaderObject* object) override
{
@@ -1488,14 +3039,6 @@ public:
setPipelineStateImpl(pipelineState);
}
- virtual SLANG_NO_THROW void SLANG_MCALL setDescriptorSet(
- IPipelineLayout* layout,
- UInt index,
- IDescriptorSet* descriptorSet) override
- {
- setDescriptorSetImpl(PipelineType::Compute, layout, index, descriptorSet);
- }
-
virtual SLANG_NO_THROW void SLANG_MCALL
bindRootShaderObject(IShaderObject* object) override
{
@@ -1568,7 +3111,7 @@ public:
virtual SLANG_NO_THROW void SLANG_MCALL
uploadBufferData(IBufferResource* buffer, size_t offset, size_t size, void* data) override
{
- _uploadBufferData(
+ PipelineCommandEncoder::_uploadBufferData(
m_commandBuffer->m_commandBuffer,
static_cast<BufferResourceImpl*>(buffer),
offset,
@@ -2161,9 +3704,8 @@ public:
/// Note that the outShaderModule value should be cleaned up when no longer needed by caller
/// via vkShaderModuleDestroy()
VkPipelineShaderStageCreateInfo compileEntryPoint(
- IShaderProgram::KernelDesc const& kernelDesc,
+ ISlangBlob* code,
VkShaderStageFlagBits stage,
- List<char>& outBuffer,
VkShaderModule& outShaderModule);
static VKAPI_ATTR VkBool32 VKAPI_CALL debugMessageCallback(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objType, uint64_t srcObject,
@@ -2194,6 +3736,73 @@ public:
uint32_t m_queueAllocCount;
};
+void VKDevice::PipelineCommandEncoder::init(CommandBufferImpl* commandBuffer)
+{
+ m_commandBuffer = commandBuffer;
+ m_device = commandBuffer->m_renderer;
+ m_vkCommandBuffer = m_commandBuffer->m_commandBuffer;
+ m_api = &m_commandBuffer->m_renderer->m_api;
+}
+
+Result VKDevice::PipelineCommandEncoder::bindRootShaderObjectImpl(
+ PipelineType pipelineType,
+ IShaderObject* object)
+{
+ // Obtain specialized root layout.
+ auto rootObjectImpl = static_cast<RootShaderObjectImpl*>(object);
+ m_rootShaderObject = rootObjectImpl;
+
+ auto specializedLayout = rootObjectImpl->getSpecializedLayout();
+ if (!specializedLayout)
+ return SLANG_FAIL;
+
+ RootBindingState bindState = {};
+ // Create transient descriptor tables for binding.
+ for (auto descSetLayout : specializedLayout->m_vkDescriptorSetLayouts)
+ {
+ auto descSet = m_commandBuffer->m_transientDescSetAllocator->allocate(descSetLayout);
+ bindState.descriptorSets.add(descSet.handle);
+ }
+
+ // Write bindings into descriptor sets. This step collects
+ // all `VkWriteDescriptorSet` operations in `bindState.descriptorSetWrites`.
+ BindingOffset offset = {};
+ rootObjectImpl->bindObject(this, &bindState, offset);
+
+ // Execute descriptor writes collected in `bindState.descriptorSetWrites`.
+ m_device->m_api.vkUpdateDescriptorSets(
+ m_device->m_device,
+ (uint32_t)bindState.descriptorSetWrites.getCount(),
+ bindState.descriptorSetWrites.getArrayView().arrayView.getBuffer(),
+ 0,
+ nullptr);
+
+ // Bind descriptor sets.
+ m_device->m_api.vkCmdBindDescriptorSets(
+ m_commandBuffer->m_commandBuffer,
+ VulkanUtil::getPipelineBindPoint(pipelineType),
+ specializedLayout->m_pipelineLayout,
+ 0,
+ (uint32_t)bindState.descriptorSets.getCount(),
+ bindState.descriptorSets.getBuffer(),
+ 0,
+ nullptr);
+
+ // Bind Push Constants.
+ for (auto pushConstantRange : specializedLayout->m_pushConstantRanges)
+ {
+ m_device->m_api.vkCmdPushConstants(
+ m_commandBuffer->m_commandBuffer,
+ specializedLayout->m_pipelineLayout,
+ pushConstantRange.stageFlags,
+ pushConstantRange.offset,
+ pushConstantRange.size,
+ bindState.pushConstantBuffer.getBuffer() + pushConstantRange.offset);
+ }
+ return SLANG_OK;
+}
+
+
/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! VKDevice::Buffer !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
Result VKDevice::Buffer::init(const VulkanApi& api, size_t bufferSize, VkBufferUsageFlags usage, VkMemoryPropertyFlags reqMemoryProperties)
@@ -2302,25 +3911,19 @@ VkBool32 VKDevice::handleDebugMessage(VkDebugReportFlagsEXT flags, VkDebugReport
}
VkPipelineShaderStageCreateInfo VKDevice::compileEntryPoint(
- IShaderProgram::KernelDesc const& kernelDesc,
+ ISlangBlob* code,
VkShaderStageFlagBits stage,
- List<char>& outBuffer,
VkShaderModule& outShaderModule)
{
- char const* dataBegin = (char const*) kernelDesc.codeBegin;
- char const* dataEnd = (char const*) kernelDesc.codeEnd;
+ char const* dataBegin = (char const*) code->getBufferPointer();
+ char const* dataEnd = (char const*)code->getBufferPointer() + code->getBufferSize();
// We need to make a copy of the code, since the Slang compiler
// will free the memory after a compile request is closed.
- size_t codeSize = dataEnd - dataBegin;
-
- outBuffer.insertRange(0, dataBegin, codeSize);
-
- char* codeBegin = outBuffer.getBuffer();
VkShaderModuleCreateInfo moduleCreateInfo = { VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO };
- moduleCreateInfo.pCode = (uint32_t*)codeBegin;
- moduleCreateInfo.codeSize = codeSize;
+ moduleCreateInfo.pCode = (uint32_t*)code->getBufferPointer();
+ moduleCreateInfo.codeSize = code->getBufferSize();
VkShaderModule module;
SLANG_VK_CHECK(m_api.vkCreateShaderModule(m_device, &moduleCreateInfo, nullptr, &module));
@@ -2638,7 +4241,7 @@ SlangResult VKDevice::initialize(const Desc& desc)
m_desc = desc;
- SLANG_RETURN_ON_FAIL(GraphicsAPIRenderer::initialize(desc));
+ SLANG_RETURN_ON_FAIL(RendererBase::initialize(desc));
SLANG_RETURN_ON_FAIL(m_module.init());
SLANG_RETURN_ON_FAIL(m_api.initGlobalProcs(m_module));
@@ -3668,454 +5271,98 @@ static VkImageViewType _calcImageViewType(ITextureResource::Type type, const ITe
return VK_IMAGE_VIEW_TYPE_MAX_ENUM;
}
-static VkDescriptorType translateDescriptorType(DescriptorSlotType type)
+Result VKDevice::createProgram(const IShaderProgram::Desc& desc, IShaderProgram** outProgram)
{
- switch(type)
+ RefPtr<ShaderProgramImpl> shaderProgram = new ShaderProgramImpl(m_api, desc.pipelineType);
+ shaderProgram->m_pipelineType = desc.pipelineType;
+ shaderProgram->slangProgram = desc.slangProgram;
+ RootShaderObjectLayout::create(
+ this,
+ desc.slangProgram,
+ desc.slangProgram->getLayout(),
+ shaderProgram->m_rootObjectLayout.writeRef());
+ if (desc.slangProgram->getSpecializationParamCount() != 0)
{
- default:
- return VK_DESCRIPTOR_TYPE_MAX_ENUM;
-
-#define CASE(SRC, DST) \
- case DescriptorSlotType::SRC: return VK_DESCRIPTOR_TYPE_##DST
-
- CASE(Sampler, SAMPLER);
- CASE(CombinedImageSampler, COMBINED_IMAGE_SAMPLER);
- CASE(SampledImage, SAMPLED_IMAGE);
- CASE(StorageImage, STORAGE_IMAGE);
- CASE(UniformTexelBuffer, UNIFORM_TEXEL_BUFFER);
- CASE(StorageTexelBuffer, STORAGE_TEXEL_BUFFER);
- CASE(UniformBuffer, UNIFORM_BUFFER);
- CASE(ReadOnlyStorageBuffer, STORAGE_BUFFER);
- CASE(StorageBuffer, STORAGE_BUFFER);
- CASE(DynamicUniformBuffer, UNIFORM_BUFFER_DYNAMIC);
- CASE(DynamicStorageBuffer, STORAGE_BUFFER_DYNAMIC);
- CASE(InputAttachment, INPUT_ATTACHMENT);
-
-#undef CASE
+ // For a specializable program, we don't invoke any actual slang compilation yet.
+ *outProgram = shaderProgram.detach();
+ return SLANG_OK;
}
-}
-
-Result VKDevice::createDescriptorSetLayout(const IDescriptorSetLayout::Desc& desc, IDescriptorSetLayout** outLayout)
-{
- RefPtr<DescriptorSetLayoutImpl> descriptorSetLayoutImpl = new DescriptorSetLayoutImpl(m_api);
-
- auto& dstBindings = descriptorSetLayoutImpl->m_vkBindings;
- Slang::List<uint32_t> descriptorCountForTypes;
-
- UInt rangeCount = desc.slotRangeCount;
- for(UInt rr = 0; rr < rangeCount; ++rr)
+ // For a fully specialized program, create `VkShaderModule`s for each shader stage.
+ auto programReflection = desc.slangProgram->getLayout();
+ for (SlangUInt i = 0; i < programReflection->getEntryPointCount(); i++)
{
- auto& srcRange = desc.slotRanges[rr];
-
- if(srcRange.type == DescriptorSlotType::RootConstant)
+ auto entryPointInfo = programReflection->getEntryPointByIndex(i);
+ auto stage = entryPointInfo->getStage();
+ ComPtr<ISlangBlob> kernelCode;
+ ComPtr<ISlangBlob> diagnostics;
+ auto compileResult = desc.slangProgram->getEntryPointCode(
+ (SlangInt)i, 0, kernelCode.writeRef(), diagnostics.writeRef());
+ if (diagnostics)
{
- // 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 represent 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;
+ // TODO: report compile error.
}
-
- // 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(bindingIndex);
- dstBinding.descriptorType = dstDescriptorType;
- dstBinding.descriptorCount = uint32_t(srcRange.count);
- dstBinding.stageFlags = VK_SHADER_STAGE_ALL;
- dstBinding.pImmutableSamplers = nullptr;
-
- if (descriptorCountForTypes.getCount() <= dstDescriptorType)
- {
- descriptorCountForTypes.setCount(dstDescriptorType + 1);
- }
-
- descriptorCountForTypes[dstDescriptorType] += uint32_t(srcRange.count);
-
- 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.type = srcRange.type;
- rangeInfo.vkDescriptorType = dstDescriptorType;
- rangeInfo.vkBindingIndex = bindingIndex;
- rangeInfo.arrayIndex = boundObjectArrayIndex;
- descriptorSetLayoutImpl->m_ranges.add(rangeInfo);
+ SLANG_RETURN_ON_FAIL(compileResult);
+ shaderProgram->m_codeBlobs.add(kernelCode);
+ VkShaderModule shaderModule;
+ shaderProgram->m_stageCreateInfos.add(compileEntryPoint(
+ kernelCode,
+ (VkShaderStageFlagBits)VulkanUtil::getShaderStage(stage),
+ shaderModule));
+ shaderProgram->m_modules.add(shaderModule);
}
-
- VkDescriptorSetLayoutCreateInfo descriptorSetLayoutInfo = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO };
- descriptorSetLayoutInfo.bindingCount = uint32_t(dstBindings.getCount());
- descriptorSetLayoutInfo.pBindings = dstBindings.getBuffer();
-
- VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE;
- SLANG_VK_CHECK(m_api.vkCreateDescriptorSetLayout(m_device, &descriptorSetLayoutInfo, nullptr, &descriptorSetLayout));
-
- descriptorSetLayoutImpl->m_descriptorSetLayout = descriptorSetLayout;
-
- *outLayout = descriptorSetLayoutImpl.detach();
+ *outProgram = shaderProgram.detach();
return SLANG_OK;
}
-Result VKDevice::createPipelineLayout(const IPipelineLayout::Desc& desc, IPipelineLayout** outLayout)
+Result VKDevice::createShaderObjectLayout(
+ slang::TypeLayoutReflection* typeLayout,
+ ShaderObjectLayoutBase** outLayout)
{
- UInt descriptorSetCount = desc.descriptorSetCount;
-
- VkDescriptorSetLayout descriptorSetLayouts[kMaxDescriptorSets];
- uint32_t descriptorSetRootConstantOffsets[kMaxDescriptorSets];
- uint32_t totalRootConstantSize = 0;
- for(UInt ii = 0; ii < descriptorSetCount; ++ii)
- {
- 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));
- RefPtr<PipelineLayoutImpl> pipelineLayoutImpl = new PipelineLayoutImpl(m_api);
- pipelineLayoutImpl->m_pipelineLayout = pipelineLayout;
- pipelineLayoutImpl->m_descriptorSetCount = descriptorSetCount;
-
- for(UInt ii = 0; ii < descriptorSetCount; ++ii)
- {
- pipelineLayoutImpl->m_descriptorSetRootConstantOffsets.add(
- descriptorSetRootConstantOffsets[ii]);
- }
-
- *outLayout = pipelineLayoutImpl.detach();
+ RefPtr<ShaderObjectLayoutImpl> layout;
+ SLANG_RETURN_ON_FAIL(
+ ShaderObjectLayoutImpl::createForElementType(this, typeLayout, layout.writeRef()));
+ *outLayout = layout.detach();
return SLANG_OK;
}
-Result VKDevice::createDescriptorSet(
- IDescriptorSetLayout* layout,
- IDescriptorSet::Flag::Enum flag,
- IDescriptorSet** outDescriptorSet)
+Result VKDevice::createShaderObject(ShaderObjectLayoutBase* layout, IShaderObject** outObject)
{
- auto layoutImpl = (DescriptorSetLayoutImpl*)layout;
-
- RefPtr<DescriptorSetImpl> descriptorSetImpl = new DescriptorSetImpl(this);
- descriptorSetImpl->m_layout = layoutImpl;
- descriptorSetImpl->m_descriptorSet =
- descriptorSetAllocator.allocate(layoutImpl->m_descriptorSetLayout);
- descriptorSetImpl->m_isTransient = (flag == IDescriptorSet::Flag::Enum::Transient);
- descriptorSetImpl->m_rootConstantData.setCount(layoutImpl->m_rootConstantDataSize);
- descriptorSetImpl->m_boundObjects.setCount(layoutImpl->m_totalBoundObjectCount);
-
- *outDescriptorSet = descriptorSetImpl.detach();
+ RefPtr<ShaderObjectImpl> shaderObject;
+ SLANG_RETURN_ON_FAIL(ShaderObjectImpl::create(
+ this, static_cast<ShaderObjectLayoutImpl*>(layout), shaderObject.writeRef()));
+ *outObject = shaderObject.detach();
return SLANG_OK;
}
-void VKDevice::DescriptorSetImpl::setConstantBuffer(UInt range, UInt index, IBufferResource* 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;
- bufferInfo.range = bufferImpl->getDesc()->sizeInBytes;
-
- VkWriteDescriptorSet writeInfo = { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET };
- writeInfo.dstSet = m_descriptorSet.handle;
- writeInfo.dstBinding = uint32_t(bindingIndex);
- writeInfo.dstArrayElement = uint32_t(index);
- writeInfo.descriptorCount = 1;
- writeInfo.descriptorType = rangeInfo.vkDescriptorType;
- writeInfo.pBufferInfo = &bufferInfo;
-
- m_renderer->m_api.vkUpdateDescriptorSets(m_renderer->m_device, 1, &writeInfo, 0, nullptr);
- m_boundObjects[boundObjectIndex] = dynamic_cast<RefObject*>(buffer);
-}
-
-void VKDevice::DescriptorSetImpl::setResource(UInt range, UInt index, IResourceView* view)
+Result SLANG_MCALL
+ VKDevice::createRootShaderObject(IShaderProgram* program, IShaderObject** outObject)
{
- 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;
- if (!viewImpl)
- return;
- switch (viewImpl->m_type)
- {
- case ResourceViewImpl::ViewType::Texture:
- {
- auto textureViewImpl = (TextureResourceViewImpl*)viewImpl;
- VkDescriptorImageInfo imageInfo = {};
- imageInfo.imageView = textureViewImpl->m_view;
- imageInfo.imageLayout = textureViewImpl->m_layout;
-
- VkWriteDescriptorSet writeInfo = { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET };
- writeInfo.dstSet = m_descriptorSet.handle;
- writeInfo.dstBinding = uint32_t(bindingIndex);
- writeInfo.dstArrayElement = uint32_t(index);
- writeInfo.descriptorCount = 1;
- writeInfo.descriptorType = descriptorType;
- writeInfo.pImageInfo = &imageInfo;
-
- m_renderer->m_api.vkUpdateDescriptorSets(m_renderer->m_device, 1, &writeInfo, 0, nullptr);
- }
- break;
-
- case ResourceViewImpl::ViewType::TexelBuffer:
- {
- auto bufferViewImpl = (TexelBufferResourceViewImpl*)viewImpl;
-
- VkWriteDescriptorSet writeInfo = { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET };
- writeInfo.dstSet = m_descriptorSet.handle;
- writeInfo.dstBinding = uint32_t(bindingIndex);
- writeInfo.dstArrayElement = uint32_t(index);
- writeInfo.descriptorCount = 1;
- writeInfo.descriptorType = descriptorType;
- writeInfo.pTexelBufferView = &bufferViewImpl->m_view;
-
- m_renderer->m_api.vkUpdateDescriptorSets(m_renderer->m_device, 1, &writeInfo, 0, nullptr);
- }
- break;
-
- case ResourceViewImpl::ViewType::PlainBuffer:
- {
- auto bufferViewImpl = (PlainBufferResourceViewImpl*) viewImpl;
-
- VkDescriptorBufferInfo bufferInfo = {};
- bufferInfo.buffer = bufferViewImpl->m_buffer->m_buffer.m_buffer;
- bufferInfo.offset = bufferViewImpl->offset;
- bufferInfo.range = bufferViewImpl->size;
-
- VkWriteDescriptorSet writeInfo = { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET };
- writeInfo.dstSet = m_descriptorSet.handle;
- writeInfo.dstBinding = uint32_t(bindingIndex);
- writeInfo.dstArrayElement = uint32_t(index);
- writeInfo.descriptorCount = 1;
- writeInfo.descriptorType = descriptorType;
- writeInfo.pBufferInfo = &bufferInfo;
-
- m_renderer->m_api.vkUpdateDescriptorSets(m_renderer->m_device, 1, &writeInfo, 0, nullptr);
- }
- break;
- }
-
- m_boundObjects[boundObjectIndex] = dynamic_cast<RefObject*>(view);
-}
-
-void VKDevice::DescriptorSetImpl::setSampler(UInt range, UInt index, ISamplerState* 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;
- if (!sampler)
- return;
- VkWriteDescriptorSet writeInfo = { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET };
- writeInfo.dstSet = m_descriptorSet.handle;
- writeInfo.dstBinding = uint32_t(bindingIndex);
- writeInfo.dstArrayElement = uint32_t(index);
- writeInfo.descriptorCount = 1;
- writeInfo.descriptorType = descriptorType;
- VkDescriptorImageInfo imageInfo = {};
- imageInfo.sampler = static_cast<SamplerStateImpl*>(sampler)->m_sampler;
- writeInfo.pImageInfo = &imageInfo;
- m_renderer->m_api.vkUpdateDescriptorSets(m_renderer->m_device, 1, &writeInfo, 0, nullptr);
-
- m_boundObjects[boundObjectIndex] = dynamic_cast<RefObject*>(sampler);
-}
-
-void VKDevice::DescriptorSetImpl::setCombinedTextureSampler(
- UInt range,
- UInt index,
- IResourceView* textureView,
- ISamplerState* 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] = dynamic_cast<RefObject*>(textureView);
- m_boundObjects[boundObjectIndex + 1] = dynamic_cast<RefObject*>(sampler);
+ auto programImpl = dynamic_cast<ShaderProgramImpl*>(program);
+ RefPtr<RootShaderObjectImpl> shaderObject;
+ SLANG_RETURN_ON_FAIL(RootShaderObjectImpl::create(
+ this, programImpl->m_rootObjectLayout, shaderObject.writeRef()));
+ *outObject = shaderObject.detach();
+ return SLANG_OK;
}
-void VKDevice::DescriptorSetImpl::setRootConstants(
- UInt range,
- UInt offset,
- UInt size,
- void const* data)
+Result VKDevice::createGraphicsPipelineState(const GraphicsPipelineStateDesc& inDesc, IPipelineState** outState)
{
- // 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 <= UInt(rootConstantRangeInfo.size));
-
- memcpy(m_rootConstantData.getBuffer() + rootConstantRangeInfo.offset + offset, data, size);
-}
+ GraphicsPipelineStateDesc desc = inDesc;
+ auto programImpl = static_cast<ShaderProgramImpl*>(desc.program);
-Result VKDevice::createProgram(const IShaderProgram::Desc& desc, IShaderProgram** outProgram)
-{
- if (desc.slangProgram && desc.slangProgram->getSpecializationParamCount() != 0)
+ if (!programImpl->m_rootObjectLayout->m_pipelineLayout)
{
- // For a specializable program, we don't invoke any actual slang compilation yet.
- RefPtr<ShaderProgramImpl> shaderProgram = new ShaderProgramImpl(m_api, desc.pipelineType);
- initProgramCommon(shaderProgram, desc);
- *outProgram = shaderProgram.detach();
+ RefPtr<PipelineStateImpl> pipelineStateImpl = new PipelineStateImpl(m_api);
+ pipelineStateImpl->init(desc);
+ *outState = pipelineStateImpl.detach();
return SLANG_OK;
}
- if( desc.kernelCount == 0 )
- {
- return createProgramFromSlang(this, desc, outProgram);
- }
-
- RefPtr<ShaderProgramImpl> impl = new ShaderProgramImpl(m_api, desc.pipelineType);
- if( desc.pipelineType == PipelineType::Compute)
- {
- auto computeKernel = desc.findKernel(StageType::Compute);
- impl->m_compute = compileEntryPoint(*computeKernel, VK_SHADER_STAGE_COMPUTE_BIT, impl->m_buffers[0], impl->m_modules[0]);
- }
- else
- {
- auto vertexKernel = desc.findKernel(StageType::Vertex);
- auto fragmentKernel = desc.findKernel(StageType::Fragment);
-
- impl->m_vertex = compileEntryPoint(*vertexKernel, VK_SHADER_STAGE_VERTEX_BIT, impl->m_buffers[0], impl->m_modules[0]);
- impl->m_fragment = compileEntryPoint(*fragmentKernel, VK_SHADER_STAGE_FRAGMENT_BIT, impl->m_buffers[1], impl->m_modules[1]);
- }
- initProgramCommon(impl, desc);
- *outProgram = impl.detach();
- return SLANG_OK;
-}
-
-Result VKDevice::createGraphicsPipelineState(const GraphicsPipelineStateDesc& inDesc, IPipelineState** outState)
-{
- GraphicsPipelineStateDesc desc = inDesc;
- preparePipelineDesc(desc);
-
VkPipelineCache pipelineCache = VK_NULL_HANDLE;
- auto programImpl = (ShaderProgramImpl*) desc.program;
- auto pipelineLayoutImpl = (PipelineLayoutImpl*) desc.pipelineLayout;
auto inputLayoutImpl = (InputLayoutImpl*) desc.inputLayout;
- // Shader Stages
- //
- // Currently only handles vertex/fragment.
-
- static const uint32_t kMaxShaderStages = 2;
- VkPipelineShaderStageCreateInfo shaderStages[kMaxShaderStages];
-
- uint32_t shaderStageCount = 0;
- shaderStages[shaderStageCount++] = programImpl->m_vertex;
- shaderStages[shaderStageCount++] = programImpl->m_fragment;
-
// VertexBuffer/s
// Currently only handles one
@@ -4241,8 +5488,8 @@ Result VKDevice::createGraphicsPipelineState(const GraphicsPipelineStateDesc& in
VkGraphicsPipelineCreateInfo pipelineInfo = { VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO };
pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
- pipelineInfo.stageCount = 2;
- pipelineInfo.pStages = shaderStages;
+ pipelineInfo.stageCount = (uint32_t)programImpl->m_stageCreateInfos.getCount();
+ pipelineInfo.pStages = programImpl->m_stageCreateInfos.getBuffer();
pipelineInfo.pVertexInputState = &vertexInputInfo;
pipelineInfo.pInputAssemblyState = &inputAssembly;
pipelineInfo.pViewportState = &viewportState;
@@ -4250,7 +5497,7 @@ Result VKDevice::createGraphicsPipelineState(const GraphicsPipelineStateDesc& in
pipelineInfo.pMultisampleState = &multisampling;
pipelineInfo.pColorBlendState = &colorBlending;
pipelineInfo.pDepthStencilState = &depthStencilStateInfo;
- pipelineInfo.layout = pipelineLayoutImpl->m_pipelineLayout;
+ pipelineInfo.layout = programImpl->m_rootObjectLayout->m_pipelineLayout;
pipelineInfo.renderPass = static_cast<FramebufferLayoutImpl*>(desc.framebufferLayout)->m_renderPass;
pipelineInfo.subpass = 0;
pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
@@ -4271,28 +5518,28 @@ Result VKDevice::createGraphicsPipelineState(const GraphicsPipelineStateDesc& in
Result VKDevice::createComputePipelineState(const ComputePipelineStateDesc& inDesc, IPipelineState** outState)
{
ComputePipelineStateDesc desc = inDesc;
- preparePipelineDesc(desc);
+ auto programImpl = static_cast<ShaderProgramImpl*>(desc.program);
+ if (!programImpl->m_rootObjectLayout->m_pipelineLayout)
+ {
+ RefPtr<PipelineStateImpl> pipelineStateImpl = new PipelineStateImpl(m_api);
+ pipelineStateImpl->init(desc);
+ *outState = pipelineStateImpl.detach();
+ return SLANG_OK;
+ }
VkPipelineCache pipelineCache = VK_NULL_HANDLE;
- auto programImpl = (ShaderProgramImpl*) desc.program;
- auto pipelineLayoutImpl = (PipelineLayoutImpl*) desc.pipelineLayout;
-
VkPipeline pipeline = VK_NULL_HANDLE;
- if (!programImpl->slangProgram || programImpl->slangProgram->getSpecializationParamCount() == 0)
- {
- VkComputePipelineCreateInfo computePipelineInfo = {
- VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO};
- computePipelineInfo.stage = programImpl->m_compute;
- computePipelineInfo.layout = pipelineLayoutImpl->m_pipelineLayout;
- SLANG_VK_CHECK(m_api.vkCreateComputePipelines(
- m_device, pipelineCache, 1, &computePipelineInfo, nullptr, &pipeline));
- }
+ VkComputePipelineCreateInfo computePipelineInfo = {
+ VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO };
+ computePipelineInfo.stage = programImpl->m_stageCreateInfos[0];
+ computePipelineInfo.layout = programImpl->m_rootObjectLayout->m_pipelineLayout;
+ SLANG_VK_CHECK(m_api.vkCreateComputePipelines(
+ m_device, pipelineCache, 1, &computePipelineInfo, nullptr, &pipeline));
RefPtr<PipelineStateImpl> pipelineStateImpl = new PipelineStateImpl(m_api);
pipelineStateImpl->m_pipeline = pipeline;
- pipelineStateImpl->m_pipelineLayout = pipelineLayoutImpl;
pipelineStateImpl->init(desc);
*outState = pipelineStateImpl.detach();
return SLANG_OK;