diff options
| author | Yong He <yonghe@outlook.com> | 2021-03-24 13:57:55 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-03-24 13:57:55 -0700 |
| commit | 98afb421f408aa8651afff3dba1b21fad71131fe (patch) | |
| tree | a8610d12ac7d74a8772cb6ecdd810da8216baa67 /tools/gfx/render-graphics-common.cpp | |
| parent | d0f7b7f0ed1d0d1388ce944cd1ad906bbd9afb35 (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/render-graphics-common.cpp')
| -rw-r--r-- | tools/gfx/render-graphics-common.cpp | 1766 |
1 files changed, 0 insertions, 1766 deletions
diff --git a/tools/gfx/render-graphics-common.cpp b/tools/gfx/render-graphics-common.cpp deleted file mode 100644 index b734bf391..000000000 --- a/tools/gfx/render-graphics-common.cpp +++ /dev/null @@ -1,1766 +0,0 @@ -#include "render-graphics-common.h" -#include "core/slang-basic.h" - -using namespace Slang; - -namespace gfx -{ - -class GraphicsCommonShaderObjectLayout : 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<GraphicsCommonShaderObjectLayout> layout; - Index bindingRangeIndex; - }; - - struct DescriptorSetInfo : public RefObject - { - ComPtr<IDescriptorSetLayout> layout; - Slang::Int space = -1; - }; - - struct Builder - { - public: - Builder(RendererBase* renderer) - : m_renderer(renderer) - {} - - RendererBase* 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; - - struct DescriptorSetBuildInfo : public RefObject - { - List<IDescriptorSetLayout::SlotRangeDesc> slotRangeDescs; - Index space; - }; - List<RefPtr<DescriptorSetBuildInfo>> m_descriptorSetBuildInfos; - Dictionary<Index, Index> m_mapSpaceToDescriptorSetIndex; - - Index findOrAddDescriptorSet(Index space) - { - Index index; - if (m_mapSpaceToDescriptorSetIndex.TryGetValue(space, index)) - return index; - - RefPtr<DescriptorSetBuildInfo> info = new DescriptorSetBuildInfo(); - info->space = space; - - index = m_descriptorSetBuildInfos.getCount(); - m_descriptorSetBuildInfos.add(info); - - m_mapSpaceToDescriptorSetIndex.Add(space, index); - return index; - } - - static DescriptorSlotType _mapDescriptorType(slang::BindingType slangBindingType) - { - switch (slangBindingType) - { - default: - return DescriptorSlotType::Unknown; - -#define CASE(FROM, TO) \ - case slang::BindingType::FROM: \ - return DescriptorSlotType::TO - - CASE(Sampler, Sampler); - CASE(CombinedTextureSampler, CombinedImageSampler); - CASE(Texture, SampledImage); - CASE(MutableTexture, StorageImage); - CASE(TypedBuffer, UniformTexelBuffer); - CASE(MutableTypedBuffer, StorageTexelBuffer); - CASE(RawBuffer, ReadOnlyStorageBuffer); - CASE(MutableRawBuffer, StorageBuffer); - CASE(InputRenderTarget, InputAttachment); - CASE(InlineUniformData, InlineUniformBlock); - CASE(RayTracingAccelerationStructure, RayTracingAccelerationStructure); - CASE(ConstantBuffer, UniformBuffer); - CASE(PushConstant, RootConstant); - -#undef CASE - } - } - - 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: - continue; - default: - break; - } - - auto gfxDescriptorType = _mapDescriptorType(slangBindingType); - - IDescriptorSetLayout::SlotRangeDesc descriptorRangeDesc; - descriptorRangeDesc.binding = - typeLayout->getDescriptorSetDescriptorRangeIndexOffset(s, r); - descriptorRangeDesc.count = - typeLayout->getDescriptorSetDescriptorRangeDescriptorCount(s, r); - descriptorRangeDesc.type = gfxDescriptorType; - - if (varLayout) - { - auto category = typeLayout->getDescriptorSetDescriptorRangeCategory(s, r); - descriptorRangeDesc.binding += varLayout->getOffset(category); - } - descriptorSetInfo->slotRangeDescs.add(descriptorRangeDesc); - } - } - } - - 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); - SlangInt count = 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; - - 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<GraphicsCommonShaderObjectLayout> subObjectLayout; - if (slangBindingType != slang::BindingType::ExistentialValue) - { - GraphicsCommonShaderObjectLayout::createForElementType( - m_renderer, - slangLeafTypeLayout->getElementTypeLayout(), - subObjectLayout.writeRef()); - } - - SubObjectRangeInfo subObjectRange; - subObjectRange.bindingRangeIndex = bindingRangeIndex; - subObjectRange.layout = subObjectLayout; - - m_subObjectRanges.add(subObjectRange); - } - return SLANG_OK; - } - - SlangResult build(GraphicsCommonShaderObjectLayout** outLayout) - { - auto layout = - RefPtr<GraphicsCommonShaderObjectLayout>(new GraphicsCommonShaderObjectLayout()); - SLANG_RETURN_ON_FAIL(layout->_init(this)); - - *outLayout = layout.detach(); - return SLANG_OK; - } - }; - - static Result createForElementType( - RendererBase* renderer, - slang::TypeLayoutReflection* elementType, - GraphicsCommonShaderObjectLayout** outLayout) - { - Builder builder(renderer); - builder.setElementTypeLayout(elementType); - return builder.build(outLayout); - } - - List<RefPtr<DescriptorSetInfo>> const& getDescriptorSets() { return m_descriptorSets; } - - 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; - - for (auto descriptorSetBuildInfo : builder->m_descriptorSetBuildInfos) - { - auto& slotRangeDescs = descriptorSetBuildInfo->slotRangeDescs; - IDescriptorSetLayout::Desc desc; - desc.slotRangeCount = slotRangeDescs.getCount(); - desc.slotRanges = slotRangeDescs.getBuffer(); - - ComPtr<IDescriptorSetLayout> descriptorSetLayout; - SLANG_RETURN_ON_FAIL( - m_renderer->createDescriptorSetLayout(desc, descriptorSetLayout.writeRef())); - - RefPtr<DescriptorSetInfo> descriptorSetInfo = new DescriptorSetInfo(); - descriptorSetInfo->layout = descriptorSetLayout; - descriptorSetInfo->space = descriptorSetBuildInfo->space; - - m_descriptorSets.add(descriptorSetInfo); - } - - 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<RefPtr<DescriptorSetInfo>> m_descriptorSets; - List<BindingRangeInfo> m_bindingRanges; - Index m_resourceViewCount = 0; - Index m_samplerCount = 0; - Index m_combinedTextureSamplerCount = 0; - Index m_subObjectCount = 0; - List<SubObjectRangeInfo> m_subObjectRanges; -}; - -class EntryPointLayout : public GraphicsCommonShaderObjectLayout -{ - typedef GraphicsCommonShaderObjectLayout Super; - -public: - struct VaryingInputInfo - {}; - - struct VaryingOutputInfo - {}; - - struct Builder : Super::Builder - { - Builder(IDevice* device) - : Super::Builder(static_cast<RendererBase*>(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 = translateStage(entryPointLayout->getStage()); - _addEntryPointParam(entryPointLayout->getVarLayout()); - _addEntryPointParam(entryPointLayout->getResultVarLayout()); - } - - slang::EntryPointLayout* m_slangEntryPointLayout = nullptr; - - gfx::StageType 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; } - - gfx::StageType getStage() const { return m_stage; } - - slang::EntryPointLayout* getSlangLayout() const { return m_slangEntryPointLayout; }; - - slang::EntryPointLayout* m_slangEntryPointLayout; - gfx::StageType m_stage; - List<VaryingInputInfo> m_varyingInputs; - List<VaryingOutputInfo> m_varyingOutputs; -}; - -class GraphicsCommonProgramLayout : public GraphicsCommonShaderObjectLayout -{ - typedef GraphicsCommonShaderObjectLayout Super; - -public: - struct EntryPointInfo - { - RefPtr<EntryPointLayout> layout; - Index rangeOffset; - }; - - struct Builder : Super::Builder - { - Builder( - RendererBase* renderer, - slang::IComponentType* program, - slang::ProgramLayout* programLayout) - : Super::Builder(renderer) - , m_program(program) - , m_programLayout(programLayout) - {} - - Result build(GraphicsCommonProgramLayout** outLayout) - { - RefPtr<GraphicsCommonProgramLayout> layout = new GraphicsCommonProgramLayout(); - 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]->slotRangeDescs.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; - }; - - Slang::Int getRenderTargetCount() { return m_renderTargetCount; } - - IPipelineLayout* getPipelineLayout() { return m_pipelineLayout; } - - Index findEntryPointIndex(gfx::StageType 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( - RendererBase* renderer, - slang::IComponentType* program, - slang::ProgramLayout* programLayout, - GraphicsCommonProgramLayout** outLayout) - { - GraphicsCommonProgramLayout::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; - - List<IPipelineLayout::DescriptorSetDesc> pipelineDescriptorSets; - _addDescriptorSetsRec(this, pipelineDescriptorSets); - -#if 0 - _createInputLayout(builder); -#endif - - auto fragmentEntryPointIndex = findEntryPointIndex(gfx::StageType::Fragment); - if (fragmentEntryPointIndex != -1) - { - auto fragmentEntryPoint = getEntryPoint(fragmentEntryPointIndex); - m_renderTargetCount = fragmentEntryPoint.layout->getVaryingOutputs().getCount(); - } - - if (m_program->getSpecializationParamCount() == 0) - { - IPipelineLayout::Desc pipelineLayoutDesc; - - // HACK: we set `renderTargetCount` to zero here becasue otherwise the D3D12 - // render back-end will adjust all UAV registers by this value to account - // for the `SV_Target<N>` outputs implicitly consuming `u<N>` registers for - // Shader Model 5.0. - // - // When using the shader object path, all registers are being set via Slang - // reflection information, and we do not need/want the automatic adjustment. - // - // TODO: Once we eliminate the non-shader-object path, this whole issue should - // be moot, because the `ProgramLayout` should own/be the pipeline layout anyway. - // - pipelineLayoutDesc.renderTargetCount = 0; - - pipelineLayoutDesc.descriptorSetCount = pipelineDescriptorSets.getCount(); - pipelineLayoutDesc.descriptorSets = pipelineDescriptorSets.getBuffer(); - - SLANG_RETURN_ON_FAIL( - renderer->createPipelineLayout(pipelineLayoutDesc, m_pipelineLayout.writeRef())); - } - return SLANG_OK; - } - - static void _addDescriptorSetsRec( - GraphicsCommonShaderObjectLayout* layout, - List<IPipelineLayout::DescriptorSetDesc>& ioPipelineDescriptorSets) - { - for (auto descriptorSetInfo : layout->getDescriptorSets()) - { - IPipelineLayout::DescriptorSetDesc pipelineDescriptorSet; - pipelineDescriptorSet.layout = descriptorSetInfo->layout; - pipelineDescriptorSet.space = descriptorSetInfo->space; - - ioPipelineDescriptorSets.add(pipelineDescriptorSet); - } - - // TODO: next we need to recurse into the "sub-objects" of `layout` and - // add their descriptor sets as well. - } - -#if 0 - Result _createInputLayout(Builder const* builder) - { - auto renderer = builder->m_renderer; - - List<InputElementDesc> const& inputElements = builder->getInputElements(); - SLANG_RETURN_ON_FAIL(renderer->createInputLayout(inputElements.getBuffer(), inputElements.getCount(), m_inputLayout.writeRef())); - - return SLANG_OK; - } -#endif - - ComPtr<slang::IComponentType> m_program; - slang::ProgramLayout* m_programLayout = nullptr; - - List<EntryPointInfo> m_entryPoints; - gfx::UInt m_renderTargetCount = 0; - - ComPtr<IPipelineLayout> m_pipelineLayout; -}; - -class GraphicsCommonShaderObject : public ShaderObjectBase -{ -public: - static Result create( - IDevice* device, - GraphicsCommonShaderObjectLayout* layout, - GraphicsCommonShaderObject** outShaderObject) - { - auto object = ComPtr<GraphicsCommonShaderObject>(new GraphicsCommonShaderObject()); - SLANG_RETURN_ON_FAIL(object->init(device, layout)); - - *outShaderObject = object.detach(); - return SLANG_OK; - } - - 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 - { - *outEntryPoint = nullptr; - return SLANG_OK; - } - - GraphicsCommonShaderObjectLayout* getLayout() - { - return static_cast<GraphicsCommonShaderObjectLayout*>(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) - { - size += offset; - offset = 0; - } - if((offset + size) >= availableSize) - { - size = availableSize - offset; - } - - memcpy(dest + offset, data, size); - - return SLANG_OK; - } - - 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; - - auto subObject = static_cast<GraphicsCommonShaderObject*>(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 - { - 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]; - - return SLANG_OK; - -#if 0 - SLANG_ASSERT(bindingRange.descriptorSetIndex >= 0); - SLANG_ASSERT(bindingRange.descriptorSetIndex < m_descriptorSets.getCount()); - auto& descriptorSet = m_descriptorSets[bindingRange.descriptorSetIndex]; - - descriptorSet->setConstantBuffer(bindingRange.rangeIndexInDescriptorSet, offset.bindingArrayIndex, buffer); - return SLANG_OK; -#endif - } - - 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] = 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] = 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 = textureView; - slot.sampler = 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 ProgramVars; - - Result init(IDevice* device, GraphicsCommonShaderObjectLayout* 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. - // - for(auto descriptorSetInfo : layout->getDescriptorSets()) - { - RefPtr<DescriptorSet> descriptorSet; - SLANG_RETURN_ON_FAIL(renderer->createDescriptorSet(descriptorSetInfo->layout, descriptorSet.writeRef())); - m_descriptorSets.add(descriptorSet); - } -#endif - - m_resourceViews.setCount(layout->getResourceViewCount()); - m_samplers.setCount(layout->getSamplerCount()); - m_combinedTextureSamplers.setCount(layout->getCombinedTextureSamplerCount()); - - // 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); - - for (auto subObjectRangeInfo : layout->getSubObjectRanges()) - { - auto subObjectLayout = subObjectRangeInfo.layout; - - // 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<GraphicsCommonShaderObject> subObject; - SLANG_RETURN_ON_FAIL( - GraphicsCommonShaderObject::create(device, subObjectLayout, subObject.writeRef())); - m_objects[bindingRangeInfo.baseIndex + i] = subObject; - } - } - - return SLANG_OK; - } - - Result apply( - RendererBase* renderer, - GraphicsComputeCommandEncoderBase* encoder, - PipelineType pipelineType, - IPipelineLayout* pipelineLayout, - Index& ioRootIndex) - { - GraphicsCommonShaderObjectLayout* layout = getLayout(); - - // Create the descritpor sets required by the layout... - // - List<ComPtr<IDescriptorSet>> descriptorSets; - for (auto descriptorSetInfo : layout->getDescriptorSets()) - { - ComPtr<IDescriptorSet> descriptorSet; - SLANG_RETURN_ON_FAIL( - renderer->createDescriptorSet(descriptorSetInfo->layout, IDescriptorSet::Flag::Transient, descriptorSet.writeRef())); - descriptorSets.add(descriptorSet); - } - - SLANG_RETURN_ON_FAIL(_bindIntoDescriptorSets(encoder, descriptorSets.getBuffer())); - - for (auto descriptorSet : descriptorSets) - { - encoder->setDescriptorSetImpl(pipelineType, pipelineLayout, ioRootIndex++, descriptorSet); - } - - return SLANG_OK; - } - - /// Write the uniform/ordinary data of this object into the given `dest` buffer at the given `offset` - Result _writeOrdinaryData( - GraphicsComputeCommandEncoderBase* encoder, - IBufferResource* buffer, - size_t offset, - size_t destSize, - GraphicsCommonShaderObjectLayout* specializedLayout) - { - 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() ) - { - 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<GraphicsCommonShaderObjectLayout> subObjectLayout; - SLANG_RETURN_ON_FAIL(subObject->_getSpecializedLayout(subObjectLayout.writeRef())); - - auto subObjectOffset = subObjectRangePendingDataOffset + i*subObjectRangePendingDataStride; - - subObject->_writeOrdinaryData(encoder, buffer, offset + subObjectOffset, destSize - subObjectOffset, subObjectLayout); - } - } - - 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(GraphicsCommonShaderObjectLayout* specializedLayout, Index subObjectRangeIndex) { return 0; } - size_t _getSubObjectRangePendingDataStride(GraphicsCommonShaderObjectLayout* specializedLayout, Index subObjectRangeIndex) { return 0; } - - /// Ensure that the `m_ordinaryDataBuffer` has been created, if it is needed - Result _ensureOrdinaryDataBufferCreatedIfNeeded(GraphicsComputeCommandEncoderBase* 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<GraphicsCommonShaderObjectLayout> 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( - GraphicsComputeCommandEncoderBase* encoder, - IDescriptorSet* descriptorSet, - Index* ioBaseRangeIndex, - Index subObjectRangeArrayIndex) - { - // 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 = *ioBaseRangeIndex; - - // 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) - { - descriptorSet->setConstantBuffer(baseRangeIndex, subObjectRangeArrayIndex, m_ordinaryDataBuffer); - baseRangeIndex++; - } - - return SLANG_OK; - } - - Result _bindIntoDescriptorSet( - GraphicsComputeCommandEncoderBase* encoder, - IDescriptorSet* descriptorSet, - Index baseRangeIndex, - Index subObjectRangeArrayIndex) - { - GraphicsCommonShaderObjectLayout* layout = getLayout(); - - _bindOrdinaryDataBufferIfNeeded( - encoder, descriptorSet, &baseRangeIndex, subObjectRangeArrayIndex); - - for (auto bindingRangeInfo : layout->getBindingRanges()) - { - switch (bindingRangeInfo.bindingType) - { - case slang::BindingType::VaryingInput: - case slang::BindingType::VaryingOutput: - continue; - - default: - break; - } - - SLANG_ASSERT(bindingRangeInfo.descriptorSetIndex == 0); - - auto count = bindingRangeInfo.count; - auto baseIndex = bindingRangeInfo.baseIndex; - - auto descriptorRangeIndex = baseRangeIndex + bindingRangeInfo.rangeIndexInDescriptorSet; - auto descriptorArrayBaseIndex = subObjectRangeArrayIndex * count; - - switch (bindingRangeInfo.bindingType) - { - case slang::BindingType::ConstantBuffer: - case slang::BindingType::ParameterBlock: - 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; - - case slang::BindingType::CombinedTextureSampler: - for (Index i = 0; i < count; ++i) - { - auto& slot = m_combinedTextureSamplers[baseIndex + i]; - descriptorSet->setCombinedTextureSampler( - descriptorRangeIndex, - descriptorArrayBaseIndex + i, - slot.textureView, - slot.sampler); - } - break; - - case slang::BindingType::Sampler: - for (Index i = 0; i < count; ++i) - { - descriptorSet->setSampler( - descriptorRangeIndex, - descriptorArrayBaseIndex + i, - m_samplers[baseIndex + i]); - } - break; - - default: - for (Index i = 0; i < count; ++i) - { - descriptorSet->setResource( - descriptorRangeIndex, - descriptorArrayBaseIndex + i, - m_resourceViews[baseIndex + i]); - } - break; - } - } - - return SLANG_OK; - } - -public: - virtual Result _bindIntoDescriptorSets(GraphicsComputeCommandEncoderBase* encoder, ComPtr<IDescriptorSet>* descriptorSets) - { - GraphicsCommonShaderObjectLayout* layout = getLayout(); - - Index baseRangeIndex = 0; - _bindOrdinaryDataBufferIfNeeded(encoder, descriptorSets[0], &baseRangeIndex, 0); - - // Fill in the descriptor sets based on binding ranges - // - for (auto bindingRangeInfo : layout->getBindingRanges()) - { - auto descriptorSet = descriptorSets[bindingRangeInfo.descriptorSetIndex]; - auto rangeIndex = bindingRangeInfo.rangeIndexInDescriptorSet; - auto baseIndex = bindingRangeInfo.baseIndex; - auto count = bindingRangeInfo.count; - switch (bindingRangeInfo.bindingType) - { - case slang::BindingType::ConstantBuffer: - case slang::BindingType::ParameterBlock: - for (Index i = 0; i < count; ++i) - { - GraphicsCommonShaderObject* subObject = m_objects[baseIndex + i]; - - subObject->_bindIntoDescriptorSet(encoder, descriptorSet, rangeIndex, i); - } - break; - - case slang::BindingType::CombinedTextureSampler: - for (Index i = 0; i < count; ++i) - { - auto& slot = m_combinedTextureSamplers[baseIndex + i]; - descriptorSet->setCombinedTextureSampler( - rangeIndex, i, slot.textureView, slot.sampler); - } - break; - - case slang::BindingType::Sampler: - for (Index i = 0; i < count; ++i) - { - descriptorSet->setSampler(rangeIndex, i, m_samplers[baseIndex + i]); - } - break; - - case slang::BindingType::VaryingInput: - case slang::BindingType::VaryingOutput: - break; - - case slang::BindingType::ExistentialValue: - // Here we are binding as if existential value is the same - // as a constant buffer or parameter block, which will lead - // to incorrect results... - for (Index i = 0; i < count; ++i) - { - GraphicsCommonShaderObject* subObject = m_objects[baseIndex + i]; - - subObject->_bindIntoDescriptorSet(encoder, descriptorSet, rangeIndex, i); - } - break; - - default: - for (Index i = 0; i < count; ++i) - { - descriptorSet->setResource(rangeIndex, i, m_resourceViews[baseIndex + i]); - } - break; - } - } - return SLANG_OK; - } - - /// Any "ordinary" / uniform data for this object - List<char> m_ordinaryData; - - List<ComPtr<IResourceView>> m_resourceViews; - - List<ComPtr<ISamplerState>> m_samplers; - - struct CombinedTextureSamplerSlot - { - ComPtr<IResourceView> textureView; - ComPtr<ISamplerState> sampler; - }; - List<CombinedTextureSamplerSlot> m_combinedTextureSamplers; - - List<RefPtr<GraphicsCommonShaderObject>> 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(GraphicsCommonShaderObjectLayout** outLayout) - { - if(!m_specializedLayout) - { - SLANG_RETURN_ON_FAIL(_createSpecializedLayout(m_specializedLayout.writeRef())); - } - *outLayout = RefPtr<GraphicsCommonShaderObjectLayout>(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(GraphicsCommonShaderObjectLayout** 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<GraphicsCommonShaderObjectLayout*>(layout.detach()); - return SLANG_OK; - } - - RefPtr<GraphicsCommonShaderObjectLayout> m_specializedLayout; -}; - -class EntryPointVars : public GraphicsCommonShaderObject -{ - typedef GraphicsCommonShaderObject Super; - -public: - static Result - create(IDevice* device, EntryPointLayout* layout, EntryPointVars** outShaderObject) - { - RefPtr<EntryPointVars> object = new EntryPointVars(); - SLANG_RETURN_ON_FAIL(object->init(device, layout)); - - *outShaderObject = object.detach(); - return SLANG_OK; - } - - EntryPointLayout* getLayout() { return static_cast<EntryPointLayout*>(m_layout.Ptr()); } - -protected: - Result init(IDevice* device, EntryPointLayout* layout) - { - SLANG_RETURN_ON_FAIL(Super::init(device, layout)); - return SLANG_OK; - } -}; - -class ProgramVars : public GraphicsCommonShaderObject -{ - typedef GraphicsCommonShaderObject Super; - -public: - static Result create(IDevice* device, GraphicsCommonProgramLayout* layout, ProgramVars** outShaderObject) - { - RefPtr<ProgramVars> object = new ProgramVars(); - SLANG_RETURN_ON_FAIL(object->init(device, layout)); - - *outShaderObject = object.detach(); - return SLANG_OK; - } - - GraphicsCommonProgramLayout* getLayout() { return static_cast<GraphicsCommonProgramLayout*>(m_layout.Ptr()); } - - void apply(RendererBase* renderer, GraphicsComputeCommandEncoderBase* encoder, PipelineType pipelineType) - { - auto pipelineLayout = encoder->m_currentPipeline->m_pipelineLayout.get(); - - Index rootIndex = 0; - GraphicsCommonShaderObject::apply(renderer, encoder, pipelineType, pipelineLayout, rootIndex); - -#if 0 - - Index descriptorSetCount = m_descriptorSets.getCount(); - for(Index descriptorSetIndex = 0; descriptorSetIndex < descriptorSetCount; ++descriptorSetIndex) - { - renderer->setDescriptorSet( - pipelineType, - pipelineLayout, - descriptorSetIndex, - m_descriptorSets[descriptorSetIndex]); - } -#endif - - // TODO: We also need to bind any descriptor sets that are - // part of sub-objects of this object. - } - - List<RefPtr<EntryPointVars>> 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 collectSpecializationArgs(ExtendedShaderObjectTypeList& args) override - { - SLANG_RETURN_ON_FAIL(GraphicsCommonShaderObject::collectSpecializationArgs(args)); - for (auto& entryPoint : m_entryPoints) - { - SLANG_RETURN_ON_FAIL(entryPoint->collectSpecializationArgs(args)); - } - return SLANG_OK; - } - -protected: - virtual Result _bindIntoDescriptorSets( - GraphicsComputeCommandEncoderBase* encoder, ComPtr<IDescriptorSet>* descriptorSets) override - { - SLANG_RETURN_ON_FAIL(Super::_bindIntoDescriptorSets(encoder, descriptorSets)); - - auto entryPointCount = m_entryPoints.getCount(); - for (Index i = 0; i < entryPointCount; ++i) - { - auto entryPoint = m_entryPoints[i]; - auto& entryPointInfo = getLayout()->getEntryPoint(i); - - SLANG_RETURN_ON_FAIL(entryPoint->_bindIntoDescriptorSet( - encoder, descriptorSets[0], entryPointInfo.rangeOffset, 0)); - } - - return SLANG_OK; - } - - Result init(IDevice* device, GraphicsCommonProgramLayout* layout) - { - SLANG_RETURN_ON_FAIL(Super::init(device, layout)); - - for (auto entryPointInfo : layout->getEntryPoints()) - { - RefPtr<EntryPointVars> entryPoint; - SLANG_RETURN_ON_FAIL( - EntryPointVars::create(device, entryPointInfo.layout, entryPoint.writeRef())); - m_entryPoints.add(entryPoint); - } - - return SLANG_OK; - } - - Result _createSpecializedLayout(GraphicsCommonShaderObjectLayout** outLayout) SLANG_OVERRIDE - { - ExtendedShaderObjectTypeList specializationArgs; - SLANG_RETURN_ON_FAIL(collectSpecializationArgs(specializationArgs)); - - // 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<GraphicsCommonProgramLayout> specializedLayout; - GraphicsCommonProgramLayout::create(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]; - - entryPointVars->m_specializedLayout = entryPointInfo.layout; - } - - *outLayout = specializedLayout.detach(); - return SLANG_OK; - } - - - List<RefPtr<EntryPointVars>> m_entryPoints; -}; - - -Result GraphicsAPIRenderer::createShaderObjectLayout( - slang::TypeLayoutReflection* typeLayout, - ShaderObjectLayoutBase** outLayout) -{ - RefPtr<GraphicsCommonShaderObjectLayout> layout; - SLANG_RETURN_ON_FAIL(GraphicsCommonShaderObjectLayout::createForElementType( - this, typeLayout, layout.writeRef())); - *outLayout = layout.detach(); - return SLANG_OK; -} - -Result GraphicsAPIRenderer::createShaderObject( - ShaderObjectLayoutBase* layout, - IShaderObject** outObject) -{ - RefPtr<GraphicsCommonShaderObject> shaderObject; - SLANG_RETURN_ON_FAIL(GraphicsCommonShaderObject::create(this, - reinterpret_cast<GraphicsCommonShaderObjectLayout*>(layout), shaderObject.writeRef())); - *outObject = shaderObject.detach(); - return SLANG_OK; -} - -Result SLANG_MCALL GraphicsAPIRenderer::createRootShaderObject( - IShaderProgram* program, - IShaderObject** outObject) -{ - auto commonProgram = dynamic_cast<GraphicsCommonShaderProgram*>(program); - - RefPtr<ProgramVars> shaderObject; - SLANG_RETURN_ON_FAIL(ProgramVars::create(this, - commonProgram->getLayout(), - shaderObject.writeRef())); - *outObject = shaderObject.detach(); - return SLANG_OK; -} - -Result GraphicsAPIRenderer::initProgramCommon( - GraphicsCommonShaderProgram* program, - IShaderProgram::Desc const& desc) -{ - auto slangProgram = desc.slangProgram; - if(!slangProgram) - return SLANG_OK; - - auto slangReflection = slangProgram->getLayout(0); - if(!slangReflection) - return SLANG_FAIL; - - RefPtr<GraphicsCommonProgramLayout> programLayout; - SLANG_RETURN_ON_FAIL(GraphicsCommonProgramLayout::create(this, slangProgram, slangReflection, programLayout.writeRef())); - - program->slangProgram = slangProgram; - program->m_layout = programLayout; - - return SLANG_OK; -} - -Result GraphicsComputeCommandEncoderBase::bindRootShaderObjectImpl( - PipelineType pipelineType, - IShaderObject* object) -{ - auto programVars = dynamic_cast<ProgramVars*>(object); - if (!programVars) - return SLANG_E_INVALID_HANDLE; - - RefPtr<PipelineStateBase> specializedPipeline; - SLANG_RETURN_ON_FAIL(m_rendererBase->maybeSpecializePipeline(m_currentPipeline, programVars, specializedPipeline)); - m_currentPipeline = specializedPipeline; - - // Apply shader parameter bindings. - programVars->apply(m_rendererBase, this, pipelineType); - return SLANG_OK; -} - -GraphicsCommonProgramLayout* gfx::GraphicsCommonShaderProgram::getLayout() const -{ - return static_cast<GraphicsCommonProgramLayout*>(m_layout.Ptr()); -} - -void GraphicsAPIRenderer::preparePipelineDesc(GraphicsPipelineStateDesc& desc) -{ - if (!desc.pipelineLayout) - { - auto program = dynamic_cast<GraphicsCommonShaderProgram*>(desc.program); - auto rootLayout = program->getLayout(); - desc.pipelineLayout = rootLayout->getPipelineLayout(); - } -} - -void GraphicsAPIRenderer::preparePipelineDesc(ComputePipelineStateDesc& desc) -{ - if (!desc.pipelineLayout) - { - auto program = dynamic_cast<GraphicsCommonShaderProgram*>(desc.program); - auto rootLayout = program->getLayout(); - desc.pipelineLayout = rootLayout->getPipelineLayout(); - } -} - -} |
