diff options
| author | Yong He <yonghe@outlook.com> | 2021-03-18 13:19:58 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-03-18 13:19:58 -0700 |
| commit | 0f9b3a95a6cc087bc1e34d4becff04fa6160c582 (patch) | |
| tree | a8b14958f13f53ee3918c727f4e5a06af377349c | |
| parent | 6e5d85efb9fa5f647f7f0c7ef784a9fd09b29023 (diff) | |
Remove `DescriptorSet` from D3D11 and GL devices. (#1761)
| -rw-r--r-- | examples/hello-world/main.cpp | 1 | ||||
| -rw-r--r-- | examples/shader-object/main.cpp | 1 | ||||
| -rw-r--r-- | examples/shader-toy/main.cpp | 111 | ||||
| -rw-r--r-- | examples/shader-toy/shader-toy.slang | 4 | ||||
| -rw-r--r-- | tools/gfx/cpu/render-cpu.cpp | 2 | ||||
| -rw-r--r-- | tools/gfx/d3d/d3d-util.cpp | 13 | ||||
| -rw-r--r-- | tools/gfx/d3d/d3d-util.h | 2 | ||||
| -rw-r--r-- | tools/gfx/d3d11/render-d3d11.cpp | 1872 | ||||
| -rw-r--r-- | tools/gfx/d3d12/render-d3d12.cpp | 91 | ||||
| -rw-r--r-- | tools/gfx/immediate-renderer-base.cpp | 45 | ||||
| -rw-r--r-- | tools/gfx/immediate-renderer-base.h | 8 | ||||
| -rw-r--r-- | tools/gfx/open-gl/render-gl.cpp | 1677 | ||||
| -rw-r--r-- | tools/gfx/render-graphics-common.cpp | 28 | ||||
| -rw-r--r-- | tools/gfx/renderer-shared.cpp | 39 | ||||
| -rw-r--r-- | tools/gfx/renderer-shared.h | 12 | ||||
| -rw-r--r-- | tools/gfx/vulkan/render-vk.cpp | 9 |
16 files changed, 2631 insertions, 1284 deletions
diff --git a/examples/hello-world/main.cpp b/examples/hello-world/main.cpp index b3bee9c07..c4bce7f79 100644 --- a/examples/hello-world/main.cpp +++ b/examples/hello-world/main.cpp @@ -244,6 +244,7 @@ Slang::Result initialize() // platforms/APIs. // IDevice::Desc deviceDesc = {}; + deviceDesc.deviceType = DeviceType::DirectX11; gfx::Result res = gfxCreateDevice(&deviceDesc, gDevice.writeRef()); if(SLANG_FAILED(res)) return res; diff --git a/examples/shader-object/main.cpp b/examples/shader-object/main.cpp index aaafc010a..796429c16 100644 --- a/examples/shader-object/main.cpp +++ b/examples/shader-object/main.cpp @@ -136,6 +136,7 @@ int main() // interacting with the graphics API. Slang::ComPtr<gfx::IDevice> device; IDevice::Desc deviceDesc = {}; + deviceDesc.deviceType = DeviceType::DirectX11; SLANG_RETURN_ON_FAIL(gfxCreateDevice(&deviceDesc, device.writeRef())); // Now we can load the shader code. diff --git a/examples/shader-toy/main.cpp b/examples/shader-toy/main.cpp index 27fdf4daa..8e377b42f 100644 --- a/examples/shader-toy/main.cpp +++ b/examples/shader-toy/main.cpp @@ -20,6 +20,7 @@ using Slang::ComPtr; // compiler, and API. // #include "slang-gfx.h" +#include "tools/gfx-util/shader-cursor.h" #include "tools/platform/window.h" #include "tools/platform/performance-counter.h" #include "source/core/slang-basic.h" @@ -275,70 +276,24 @@ Result loadShaderProgram(gfx::IDevice* device, ComPtr<gfx::IShaderProgram>& outS diagnoseIfNeeded(diagnosticsBlob); SLANG_RETURN_ON_FAIL(result); - // Given a linked program (one with no unresolved external references, - // and no not-yet-set specialization parameters), we can request kernel - // code for the entry points of that program by their indices. - // - // Because Slang supports a session with multiple active code generation - // targets, we also need to specify the index of the target we want - // code for, but since we have only a single target in this application, - // there isn't actually a choice. - // - int targetIndex = 0; - // - // Note: it is possible to get diagnostic messages when generating kernel - // code, but this is not a common occurence. Most semantic errors in - // user code will be detected at earlier steps in this compilation flow, - // but there are certain errors that are currently caught during final - // code generation (e.g., when using a function that is specific to one - // target, and then requesting kernel code for another target). - // - ComPtr<ISlangBlob> vertexShaderBlob; - result = linkedProgram->getEntryPointCode(vertexEntryPointIndex, targetIndex, vertexShaderBlob.writeRef(), diagnosticsBlob.writeRef()); - diagnoseIfNeeded(diagnosticsBlob); - SLANG_RETURN_ON_FAIL(result); - ComPtr<ISlangBlob> fragmentShaderBlob; - result = linkedProgram->getEntryPointCode(fragmentEntryPointIndex, targetIndex, fragmentShaderBlob.writeRef(), diagnosticsBlob.writeRef()); - diagnoseIfNeeded(diagnosticsBlob); - SLANG_RETURN_ON_FAIL(result); - - // Once kernel code has been extracted from Slang, the rest of the logic - // here is basically the same as in the `hello-world` example. - - char const* vertexCode = (char const*) vertexShaderBlob->getBufferPointer(); - char const* vertexCodeEnd = vertexCode + vertexShaderBlob->getBufferSize(); - - char const* fragmentCode = (char const*) fragmentShaderBlob->getBufferPointer(); - char const* fragmentCodeEnd = fragmentCode + fragmentShaderBlob->getBufferSize(); - - gfx::IShaderProgram::KernelDesc kernelDescs[] = - { - { gfx::StageType::Vertex, vertexCode, vertexCodeEnd }, - { gfx::StageType::Fragment, fragmentCode, fragmentCodeEnd }, - }; - gfx::IShaderProgram::Desc programDesc = {}; programDesc.pipelineType = gfx::PipelineType::Graphics; - programDesc.kernels = &kernelDescs[0]; - programDesc.kernelCount = 2; - + programDesc.slangProgram = linkedProgram.get(); auto shaderProgram = device->createProgram(programDesc); - outShaderProgram = shaderProgram; return SLANG_OK; } int gWindowWidth = 1024; int gWindowHeight = 768; -const uint32_t kSwapchainImageCount = 2; +static const uint32_t kSwapchainImageCount = 2; Slang::RefPtr<platform::Window> gWindow; Slang::ComPtr<gfx::IDevice> gDevice; -ComPtr<gfx::IBufferResource> gConstantBuffer; -ComPtr<gfx::IPipelineLayout> gPipelineLayout; +ComPtr<IShaderProgram> gShaderProgram; +ComPtr<gfx::IShaderObject> gRootObject[kSwapchainImageCount]; ComPtr<gfx::IFramebufferLayout> gFramebufferLayout; ComPtr<gfx::IPipelineState> gPipelineState; -ComPtr<gfx::IDescriptorSet> gDescriptorSet; ComPtr<gfx::IBufferResource> gVertexBuffer; ComPtr<gfx::ISwapchain> gSwapchain; Slang::List<ComPtr<gfx::IFramebuffer>> gFramebuffers; @@ -373,17 +328,6 @@ Result initialize() queueDesc.type = ICommandQueue::QueueType::Graphics; gQueue = gDevice->createCommandQueue(queueDesc); - int constantBufferSize = sizeof(Uniforms); - - IBufferResource::Desc constantBufferDesc; - constantBufferDesc.init(constantBufferSize); - constantBufferDesc.setDefaults(IResource::Usage::ConstantBuffer); - constantBufferDesc.cpuAccessFlags = IResource::AccessFlag::Write; - - gConstantBuffer = - gDevice->createBufferResource(IResource::Usage::ConstantBuffer, constantBufferDesc); - if(!gConstantBuffer) return SLANG_FAIL; - InputElementDesc inputElements[] = { { "POSITION", 0, Format::RG_Float32, offsetof(FullScreenTriangle::Vertex, position) }, }; @@ -401,39 +345,7 @@ Result initialize() &FullScreenTriangle::kVertices[0]); if(!gVertexBuffer) return SLANG_FAIL; - ComPtr<IShaderProgram> shaderProgram; - SLANG_RETURN_ON_FAIL(loadShaderProgram(gDevice, shaderProgram)); - - IDescriptorSetLayout::SlotRangeDesc slotRanges[] = - { - IDescriptorSetLayout::SlotRangeDesc(DescriptorSlotType::UniformBuffer), - }; - IDescriptorSetLayout::Desc descriptorSetLayoutDesc; - descriptorSetLayoutDesc.slotRangeCount = 1; - descriptorSetLayoutDesc.slotRanges = &slotRanges[0]; - auto descriptorSetLayout = gDevice->createDescriptorSetLayout(descriptorSetLayoutDesc); - if(!descriptorSetLayout) return SLANG_FAIL; - - IPipelineLayout::DescriptorSetDesc descriptorSets[] = - { - IPipelineLayout::DescriptorSetDesc( descriptorSetLayout ), - }; - IPipelineLayout::Desc pipelineLayoutDesc; - pipelineLayoutDesc.renderTargetCount = 1; - pipelineLayoutDesc.descriptorSetCount = 1; - pipelineLayoutDesc.descriptorSets = &descriptorSets[0]; - auto pipelineLayout = gDevice->createPipelineLayout(pipelineLayoutDesc); - if(!pipelineLayout) return SLANG_FAIL; - - gPipelineLayout = pipelineLayout; - - auto descriptorSet = - gDevice->createDescriptorSet(descriptorSetLayout, IDescriptorSet::Flag::Transient); - if(!descriptorSet) return SLANG_FAIL; - - descriptorSet->setConstantBuffer(0, 0, gConstantBuffer); - - gDescriptorSet = descriptorSet; + SLANG_RETURN_ON_FAIL(loadShaderProgram(gDevice, gShaderProgram)); // Create swapchain and framebuffers. gfx::ISwapchain::Desc swapchainDesc = {}; @@ -458,9 +370,8 @@ Result initialize() // Create pipeline. GraphicsPipelineStateDesc desc; desc.inputLayout = inputLayout; - desc.program = shaderProgram; + desc.program = gShaderProgram; desc.framebufferLayout = gFramebufferLayout; - desc.pipelineLayout = pipelineLayout; auto pipelineState = gDevice->createGraphicsPipelineState(desc); if (!pipelineState) return SLANG_FAIL; @@ -511,7 +422,6 @@ void renderFrame() } // Update uniform buffer. - auto uploadEncoder = commandBuffer->encodeResourceCommands(); Uniforms uniforms = {}; { @@ -532,9 +442,10 @@ void renderFrame() uniforms.iResolution[0] = float(gWindowWidth); uniforms.iResolution[1] = float(gWindowHeight); - uploadEncoder->uploadBufferData(gConstantBuffer, 0, sizeof(Uniforms), &uniforms); } - uploadEncoder->endEncoding(); + gRootObject[frameIndex] = gDevice->createRootShaderObject(gShaderProgram); + auto constantBuffer = gRootObject[frameIndex]->getObject(ShaderOffset()); + constantBuffer->setData(ShaderOffset(), &uniforms, sizeof(uniforms)); // Encode render commands. auto encoder = commandBuffer->encodeRenderCommands(gRenderPass, gFramebuffers[frameIndex]); @@ -545,7 +456,7 @@ void renderFrame() viewport.extentY = (float)gWindowHeight; encoder->setViewportAndScissor(viewport); encoder->setPipelineState(gPipelineState); - encoder->setDescriptorSet(gPipelineLayout, 0, gDescriptorSet); + encoder->bindRootShaderObject(gRootObject[frameIndex]); encoder->setVertexBuffer(0, gVertexBuffer, sizeof(FullScreenTriangle::Vertex)); encoder->setPrimitiveTopology(PrimitiveTopology::TriangleList); encoder->draw(3); diff --git a/examples/shader-toy/shader-toy.slang b/examples/shader-toy/shader-toy.slang index 98583f246..e434e2680 100644 --- a/examples/shader-toy/shader-toy.slang +++ b/examples/shader-toy/shader-toy.slang @@ -202,7 +202,7 @@ cbuffer ShaderToyUniforms // exposed by Shader Toy, but rather just the most commonly // used ones. // - // TODO: We can and should fill in teh rest over time. + // TODO: We can and should fill in the rest over time. // float4 iMouse; float2 iResolution; @@ -345,4 +345,4 @@ float fract(float value) float mix(float a, float b, float t) { return lerp(a, b, t); -}
\ No newline at end of file +} diff --git a/tools/gfx/cpu/render-cpu.cpp b/tools/gfx/cpu/render-cpu.cpp index 630ba2925..ab9224657 100644 --- a/tools/gfx/cpu/render-cpu.cpp +++ b/tools/gfx/cpu/render-cpu.cpp @@ -269,7 +269,7 @@ public: totalDataSize += levelDataSize; } - void* textureData = malloc(totalDataSize); + void* textureData = malloc((size_t)totalDataSize); m_data = textureData; if( initData ) diff --git a/tools/gfx/d3d/d3d-util.cpp b/tools/gfx/d3d/d3d-util.cpp index a9686ab7d..dfc19c927 100644 --- a/tools/gfx/d3d/d3d-util.cpp +++ b/tools/gfx/d3d/d3d-util.cpp @@ -511,6 +511,19 @@ static bool _isMatch(IDXGIAdapter* adapter, const Slang::UnownedStringSlice& low return false; } +bool D3DUtil::isUAVBinding(slang::BindingType bindingType) +{ + switch (bindingType) + { + case slang::BindingType::MutableRawBuffer: + case slang::BindingType::MutableTexture: + case slang::BindingType::MutableTypedBuffer: + return true; + default: + return false; + } +} + /* static */SlangResult D3DUtil::findAdapters(DeviceCheckFlags flags, const UnownedStringSlice& adapterName, IDXGIFactory* dxgiFactory, List<ComPtr<IDXGIAdapter>>& outDxgiAdapters) { Slang::String lowerAdapterName = Slang::String(adapterName).toLower(); diff --git a/tools/gfx/d3d/d3d-util.h b/tools/gfx/d3d/d3d-util.h index 4cbdcb61b..fc2353818 100644 --- a/tools/gfx/d3d/d3d-util.h +++ b/tools/gfx/d3d/d3d-util.h @@ -83,6 +83,8 @@ class D3DUtil /// True if the adapter is warp static bool isWarp(IDXGIFactory* dxgiFactory, IDXGIAdapter* adapter); + static bool isUAVBinding(slang::BindingType bindingType); + }; } // renderer_test diff --git a/tools/gfx/d3d11/render-d3d11.cpp b/tools/gfx/d3d11/render-d3d11.cpp index 840703c37..60fd4c7b3 100644 --- a/tools/gfx/d3d11/render-d3d11.cpp +++ b/tools/gfx/d3d11/render-d3d11.cpp @@ -99,13 +99,48 @@ public: IInputLayout** outLayout) override; virtual SLANG_NO_THROW Result SLANG_MCALL createDescriptorSetLayout( - const IDescriptorSetLayout::Desc& desc, IDescriptorSetLayout** outLayout) override; + const IDescriptorSetLayout::Desc& desc, IDescriptorSetLayout** outLayout) override + { + SLANG_UNUSED(desc); + SLANG_UNUSED(outLayout); + return SLANG_FAIL; + } virtual SLANG_NO_THROW Result SLANG_MCALL - createPipelineLayout(const IPipelineLayout::Desc& desc, IPipelineLayout** outLayout) override; + createPipelineLayout(const IPipelineLayout::Desc& desc, IPipelineLayout** outLayout) override + { + SLANG_UNUSED(desc); + SLANG_UNUSED(outLayout); + return SLANG_FAIL; + } virtual SLANG_NO_THROW Result SLANG_MCALL createDescriptorSet( IDescriptorSetLayout* layout, IDescriptorSet::Flag::Enum flag, - IDescriptorSet** outDescriptorSet) override; + IDescriptorSet** outDescriptorSet) override + { + SLANG_UNUSED(layout); + SLANG_UNUSED(flag); + SLANG_UNUSED(outDescriptorSet); + return SLANG_FAIL; + } + virtual SLANG_NO_THROW void SLANG_MCALL setDescriptorSet( + PipelineType pipelineType, + IPipelineLayout* layout, + UInt index, + IDescriptorSet* descriptorSet) override + { + SLANG_UNUSED(pipelineType); + SLANG_UNUSED(layout); + SLANG_UNUSED(index); + SLANG_UNUSED(descriptorSet); + } + + virtual Result createShaderObjectLayout( + slang::TypeLayoutReflection* typeLayout, + ShaderObjectLayoutBase** outLayout) override; + virtual Result createShaderObject(ShaderObjectLayoutBase* layout, IShaderObject** outObject) override; + virtual SLANG_NO_THROW Result SLANG_MCALL + createRootShaderObject(IShaderProgram* program, IShaderObject** outObject) override; + virtual void bindRootShaderObject(PipelineType pipelineType, IShaderObject* shaderObject) override; virtual SLANG_NO_THROW Result SLANG_MCALL createProgram(const IShaderProgram::Desc& desc, IShaderProgram** outProgram) override; @@ -128,12 +163,6 @@ public: virtual SLANG_NO_THROW void SLANG_MCALL setPrimitiveTopology(PrimitiveTopology topology) override; - virtual SLANG_NO_THROW void SLANG_MCALL setDescriptorSet( - PipelineType pipelineType, - IPipelineLayout* layout, - UInt index, - IDescriptorSet* descriptorSet) override; - virtual SLANG_NO_THROW void SLANG_MCALL setVertexBuffers( UInt startSlot, UInt slotCount, @@ -171,155 +200,7 @@ protected: D3D11Device* m_renderer; }; - enum class D3D11DescriptorSlotType - { - ConstantBuffer, - ShaderResourceView, - UnorderedAccessView, - Sampler, - - CombinedTextureSampler, - - CountOf, - }; - - class DescriptorSetLayoutImpl : public IDescriptorSetLayout, public RefObject - { - public: - SLANG_REF_OBJECT_IUNKNOWN_ALL - IDescriptorSetLayout* getInterface(const Guid& guid) - { - if (guid == GfxGUID::IID_ISlangUnknown || guid == GfxGUID::IID_IDescriptorSetLayout) - return static_cast<IDescriptorSetLayout*>(this); - return nullptr; - } - public: - // Each descriptor set for the D3D11 renderer stores distinct - // arrays for each kind of shader-visible entity D3D11 understands: - // shader resource views (SRVs), unordered access views (UAVs), - // constant buffers (CBs), and samplers. - // - // (This description will ignore compiled image/sampler pairs, - // since they aren't really well supported at present) - // - // Each descriptor range in an input `DescriptorSetLayout::Desc` - // will map to a range of entries in one of those arrays, but - // in general there can be multiple `DescriptorSlotType`s that - // map to the same `D3D11DescriptorSlotType`. - // - // Each `RangeInfo` in a D3D11 descriptor set layout represents - // of of the descriptor slot ranges in the original `Desc`, - // and stores the information that is relevant to its layout - // in our D3D11 implementation. - - struct RangeInfo - { - /// The type of descriptors in the range, in D3D11 terms (SRVs, UAVs, etc.) - D3D11DescriptorSlotType type; - - /// The start index of this range in the relevant descriptor-type-specific array. - /// - /// Note: This is *not* the same as the index of the range, both because multiple - /// `DescriptorSlotType`s might map to the same array in the D3D11 implementation, - /// and also because a given range might store multiple descriptors (so a 3-texture - /// range that comes after a 5-texture range will have an `arrayIndex` of 5 but - /// a range index of 1). - /// - UInt arrayIndex; - - /// For the case of a combined image/sampler pair, the `arrayIndex` is an index - /// into the array of SRVs, and we store a separate index into the array of - /// samplers. - /// - UInt pairedSamplerArrayIndex; - }; - List<RangeInfo> m_ranges; - - // Because D3D11 does not support root constants as they appear in - // D3D12 and Vulkan, we need to map root-constant ranges in the original `Desc` - // over to ordinary constant buffers. Each root-constant range (of whatever - // size) will map to a constant-buffer range of a single buffer. - // - // In order to be able to properly allocate/initialize these root constant - // buffers, we store additional information about them in a flattened array - // that only stores information for root constant ranges. - - struct RootConstantRangeInfo - { - /// Index of the `RangeInfo` corresponding to this root-constant range - Index rangeIndex; - - /// Size of the original root-constant range, in bytes. - UInt size; - }; - List<RootConstantRangeInfo> m_rootConstantRanges; - - UInt m_counts[int(D3D11DescriptorSlotType::CountOf)]; - }; - - class PipelineLayoutImpl : public IPipelineLayout, public RefObject - { - public: - SLANG_REF_OBJECT_IUNKNOWN_ALL - IPipelineLayout* getInterface(const Guid& guid) - { - if (guid == GfxGUID::IID_ISlangUnknown || guid == GfxGUID::IID_IPipelineLayout) - { - return static_cast<IPipelineLayout*>(this); - } - return nullptr; - } - public: - struct DescriptorSetInfo - { - RefPtr<DescriptorSetLayoutImpl> layout; - UInt baseIndices[int(D3D11DescriptorSlotType::CountOf)]; - }; - - List<DescriptorSetInfo> m_descriptorSets; - UINT m_uavCount; - }; - - class DescriptorSetImpl : public IDescriptorSet, public RefObject - { - public: - SLANG_REF_OBJECT_IUNKNOWN_ALL - IDescriptorSet* getInterface(const Guid& guid) - { - if (guid == GfxGUID::IID_ISlangUnknown || guid == GfxGUID::IID_IDescriptorSet) - return static_cast<IDescriptorSet*>(this); - return nullptr; - } - public: - 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; - - D3D11Device* m_renderer = nullptr; - - RefPtr<DescriptorSetLayoutImpl> m_layout; - - List<ComPtr<ID3D11Buffer>> m_cbs; - List<ComPtr<ID3D11ShaderResourceView>> m_srvs; - List<ComPtr<ID3D11UnorderedAccessView>> m_uavs; - List<ComPtr<ID3D11SamplerState>> m_samplers; - }; - - class ShaderProgramImpl : public GraphicsCommonShaderProgram + class ShaderProgramImpl : public ShaderProgramBase { public: ComPtr<ID3D11VertexShader> m_vertexShader; @@ -556,8 +437,1087 @@ protected: } }; + struct RootBindingState + { + List<ID3D11ShaderResourceView*> srvBindings; + List<ID3D11UnorderedAccessView*> uavBindings; + List<ID3D11SamplerState*> samplerBindings; + List<ID3D11Buffer*> constantBuffers; + }; + + class ShaderObjectLayoutImpl : public ShaderObjectLayoutBase + { + public: + struct BindingRangeInfo + { + slang::BindingType bindingType; + Index count; + Index baseIndex; + // baseIndex2 is used to specify samplers in a CombinedTextureSampler binding. + Index baseIndex2; + + // 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 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_srvCount = 0; + Index m_samplerCount = 0; + Index m_uavCount = 0; + Index m_subObjectCount = 0; + Index m_varyingInputCount = 0; + Index m_varyingOutputCount = 0; + + Result setElementTypeLayout(slang::TypeLayoutReflection* typeLayout) + { + typeLayout = _unwrapParameterGroups(typeLayout); + + m_elementTypeLayout = typeLayout; + + // Compute the binding ranges that are used to store + // the logical contents of the object in memory. + + 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); + + BindingRangeInfo bindingRangeInfo; + bindingRangeInfo.bindingType = slangBindingType; + bindingRangeInfo.count = count; + + switch (slangBindingType) + { + case slang::BindingType::ConstantBuffer: + case slang::BindingType::ParameterBlock: + case slang::BindingType::ExistentialValue: + bindingRangeInfo.baseIndex = m_subObjectCount; + m_subObjectCount += count; + break; + + case slang::BindingType::Sampler: + bindingRangeInfo.baseIndex = m_samplerCount; + m_samplerCount += count; + break; + + case slang::BindingType::CombinedTextureSampler: + bindingRangeInfo.baseIndex = m_srvCount; + bindingRangeInfo.baseIndex2 = m_samplerCount; + m_srvCount += count; + m_samplerCount += count; + break; + + case slang::BindingType::MutableRawBuffer: + case slang::BindingType::MutableTexture: + case slang::BindingType::MutableTypedBuffer: + bindingRangeInfo.baseIndex = m_uavCount; + m_uavCount += count; + break; + + case slang::BindingType::VaryingInput: + bindingRangeInfo.baseIndex = m_varyingInputCount; + m_varyingInputCount += count; + break; + + case slang::BindingType::VaryingOutput: + bindingRangeInfo.baseIndex = m_varyingOutputCount; + m_varyingOutputCount += count; + break; + + default: + bindingRangeInfo.baseIndex = m_srvCount; + m_srvCount += count; + break; + } + + + 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) + { + 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( + RendererBase* renderer, + slang::TypeLayoutReflection* elementType, + ShaderObjectLayoutImpl** outLayout) + { + Builder builder(renderer); + builder.setElementTypeLayout(elementType); + return builder.build(outLayout); + } + + 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 getSRVCount() { return m_srvCount; } + Index getSamplerCount() { return m_samplerCount; } + Index getUAVCount() { return m_uavCount; } + Index getSubObjectCount() { return m_subObjectCount; } + Index getVaryingOutputCount() { return m_varyingOutputCount; } + + 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_srvCount = builder->m_srvCount; + m_samplerCount = builder->m_samplerCount; + m_uavCount = builder->m_uavCount; + m_subObjectCount = builder->m_subObjectCount; + m_subObjectRanges = builder->m_subObjectRanges; + m_varyingInputCount = builder->m_varyingInputCount; + m_varyingOutputCount = builder->m_varyingOutputCount; + return SLANG_OK; + } + + List<BindingRangeInfo> m_bindingRanges; + Index m_srvCount = 0; + Index m_samplerCount = 0; + Index m_uavCount = 0; + Index m_subObjectCount = 0; + Index m_varyingInputCount = 0; + Index m_varyingOutputCount = 0; + List<SubObjectRangeInfo> m_subObjectRanges; + }; + + class RootShaderObjectLayoutImpl : public ShaderObjectLayoutImpl + { + typedef ShaderObjectLayoutImpl Super; + + public: + struct EntryPointInfo + { + RefPtr<ShaderObjectLayoutImpl> layout; + }; + + struct Builder : Super::Builder + { + Builder( + RendererBase* renderer, + slang::IComponentType* program, + slang::ProgramLayout* programLayout) + : Super::Builder(renderer) + , m_program(program) + , m_programLayout(programLayout) + {} + + Result build(RootShaderObjectLayoutImpl** outLayout) + { + RefPtr<RootShaderObjectLayoutImpl> layout = new RootShaderObjectLayoutImpl(); + SLANG_RETURN_ON_FAIL(layout->_init(this)); + + *outLayout = layout.detach(); + return SLANG_OK; + } + + void addGlobalParams(slang::VariableLayoutReflection* globalsLayout) + { + setElementTypeLayout(globalsLayout->getTypeLayout()); + } + + void addEntryPoint(SlangStage stage, ShaderObjectLayoutImpl* entryPointLayout) + { + EntryPointInfo info; + info.layout = entryPointLayout; + m_entryPoints.add(info); + } + + slang::IComponentType* m_program; + slang::ProgramLayout* m_programLayout; + List<EntryPointInfo> m_entryPoints; + }; + + EntryPointInfo& getEntryPoint(Index index) { return m_entryPoints[index]; } + + List<EntryPointInfo>& getEntryPoints() { return m_entryPoints; } + + static Result create( + RendererBase* renderer, + slang::IComponentType* program, + slang::ProgramLayout* programLayout, + RootShaderObjectLayoutImpl** outLayout) + { + RootShaderObjectLayoutImpl::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); + RefPtr<ShaderObjectLayoutImpl> entryPointLayout; + SLANG_RETURN_ON_FAIL(ShaderObjectLayoutImpl::createForElementType( + renderer, slangEntryPoint->getTypeLayout(), entryPointLayout.writeRef())); + builder.addEntryPoint(slangEntryPoint->getStage(), 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; + return SLANG_OK; + } + + ComPtr<slang::IComponentType> m_program; + slang::ProgramLayout* m_programLayout = nullptr; + + List<EntryPointInfo> m_entryPoints; + }; + + class ShaderObjectImpl : public ShaderObjectBase + { + public: + static Result create( + IDevice* device, + ShaderObjectLayoutImpl* layout, + ShaderObjectImpl** outShaderObject) + { + auto object = ComPtr<ShaderObjectImpl>(new ShaderObjectImpl()); + 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; + } + + 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) + { + 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<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 + { + 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; + return SLANG_OK; + } + + 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); + + auto resourceViewImpl = static_cast<ResourceViewImpl*>(resourceView); + if (D3DUtil::isUAVBinding(bindingRange.bindingType)) + { + SLANG_ASSERT(resourceViewImpl->m_type == ResourceViewImpl::Type::UAV); + m_uavs[bindingRange.baseIndex + offset.bindingArrayIndex] = static_cast<UnorderedAccessViewImpl*>(resourceView); + } + else + { + SLANG_ASSERT(resourceViewImpl->m_type == ResourceViewImpl::Type::SRV); + m_srvs[bindingRange.baseIndex + offset.bindingArrayIndex] = static_cast<ShaderResourceViewImpl*>(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); + m_srvs[bindingRange.baseIndex + offset.bindingArrayIndex] = static_cast<ShaderResourceViewImpl*>(textureView); + m_samplers[bindingRange.baseIndex2 + offset.bindingArrayIndex] = 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 ProgramVars; + + 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); + } + + m_srvs.setCount(layout->getSRVCount()); + m_samplers.setCount(layout->getSamplerCount()); + m_uavs.setCount(layout->getUAVCount()); + + // 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<ShaderObjectImpl> subObject; + SLANG_RETURN_ON_FAIL( + ShaderObjectImpl::create(device, subObjectLayout, subObject.writeRef())); + m_objects[bindingRangeInfo.baseIndex + i] = subObject; + } + } + + return SLANG_OK; + } + + /// Write the uniform/ordinary data of this object into the given `dest` buffer at the given `offset` + Result _writeOrdinaryData( + D3D11Device* device, + BufferResourceImpl* buffer, + size_t offset, + size_t destSize, + ShaderObjectLayoutImpl* specializedLayout) + { + auto src = m_ordinaryData.getBuffer(); + auto srcSize = size_t(m_ordinaryData.getCount()); + + SLANG_ASSERT(srcSize <= destSize); + + device->uploadBufferData(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<ShaderObjectLayoutImpl> subObjectLayout; + SLANG_RETURN_ON_FAIL(subObject->_getSpecializedLayout(subObjectLayout.writeRef())); + + auto subObjectOffset = subObjectRangePendingDataOffset + i * subObjectRangePendingDataStride; + + subObject->_writeOrdinaryData(device, 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(ShaderObjectLayoutImpl* specializedLayout, Index subObjectRangeIndex) { return 0; } + size_t _getSubObjectRangePendingDataStride(ShaderObjectLayoutImpl* specializedLayout, Index subObjectRangeIndex) { return 0; } + + /// Ensure that the `m_ordinaryDataBuffer` has been created, if it is needed + Result _ensureOrdinaryDataBufferCreatedIfNeeded(D3D11Device* device) + { + // 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. + // + + ComPtr<IBufferResource> bufferResourcePtr; + IBufferResource::Desc bufferDesc; + bufferDesc.init(specializedOrdinaryDataSize); + bufferDesc.cpuAccessFlags |= IResource::AccessFlag::Write; + SLANG_RETURN_ON_FAIL(device->createBufferResource( + IResource::Usage::ConstantBuffer, bufferDesc, nullptr, bufferResourcePtr.writeRef())); + m_ordinaryDataBuffer = static_cast<BufferResourceImpl*>(bufferResourcePtr.get()); + + // 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(device, m_ordinaryDataBuffer, 0, specializedOrdinaryDataSize, specializedLayout)); + + return SLANG_OK; + } + + /// Bind the buffer for ordinary/uniform data, if needed + Result _bindOrdinaryDataBufferIfNeeded( + D3D11Device* device, + RootBindingState* bindingState) + { + // We start by ensuring that the buffer is created, if it is needed. + // + SLANG_RETURN_ON_FAIL(_ensureOrdinaryDataBufferCreatedIfNeeded(device)); + + // If we did indeed need/create a buffer, then we must bind it + // into root binding state. + // + if (m_ordinaryDataBuffer) + { + bindingState->constantBuffers.add(m_ordinaryDataBuffer->m_buffer); + } + + return SLANG_OK; + } + public: + virtual Result bindObject(D3D11Device* device, RootBindingState* bindingState) + { + ShaderObjectLayoutImpl* layout = getLayout(); + + Index baseRangeIndex = 0; + SLANG_RETURN_ON_FAIL(_bindOrdinaryDataBufferIfNeeded(device, bindingState)); + + for (auto sampler : m_samplers) + bindingState->samplerBindings.add(sampler ? sampler->m_sampler.get() : nullptr); + + for (auto srv : m_srvs) + bindingState->srvBindings.add(srv ? srv->m_srv : nullptr); + + for (auto uav : m_uavs) + bindingState->uavBindings.add(uav ? uav->m_uav : nullptr); + + for (auto subObject : m_objects) + subObject->bindObject(device, bindingState); + + return SLANG_OK; + } + + /// Any "ordinary" / uniform data for this object + List<char> m_ordinaryData; + + List<RefPtr<ShaderResourceViewImpl>> m_srvs; + + List<RefPtr<SamplerStateImpl>> m_samplers; + + List<RefPtr<UnorderedAccessViewImpl>> m_uavs; + + 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()` + RefPtr<BufferResourceImpl> 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; + }; + + class RootShaderObjectImpl : public ShaderObjectImpl + { + typedef ShaderObjectImpl Super; + + public: + static Result create(IDevice* device, RootShaderObjectLayoutImpl* layout, RootShaderObjectImpl** outShaderObject) + { + RefPtr<RootShaderObjectImpl> object = new RootShaderObjectImpl(); + SLANG_RETURN_ON_FAIL(object->init(device, layout)); + + *outShaderObject = object.detach(); + return SLANG_OK; + } + + RootShaderObjectLayoutImpl* getLayout() { return static_cast<RootShaderObjectLayoutImpl*>(m_layout.Ptr()); } + + 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(ShaderObjectImpl::collectSpecializationArgs(args)); + for (auto& entryPoint : m_entryPoints) + { + SLANG_RETURN_ON_FAIL(entryPoint->collectSpecializationArgs(args)); + } + return SLANG_OK; + } + + protected: + virtual Result bindObject(D3D11Device* device, RootBindingState* bindingState) override + { + SLANG_RETURN_ON_FAIL(Super::bindObject(device, bindingState)); + + auto entryPointCount = m_entryPoints.getCount(); + for (Index i = 0; i < entryPointCount; ++i) + { + auto entryPoint = m_entryPoints[i]; + SLANG_RETURN_ON_FAIL(entryPoint->bindObject(device, bindingState)); + } + + return SLANG_OK; + } + + Result init(IDevice* device, RootShaderObjectLayoutImpl* layout) + { + SLANG_RETURN_ON_FAIL(Super::init(device, layout)); + + for (auto entryPointInfo : layout->getEntryPoints()) + { + RefPtr<ShaderObjectImpl> entryPoint; + SLANG_RETURN_ON_FAIL( + ShaderObjectImpl::create(device, entryPointInfo.layout, entryPoint.writeRef())); + m_entryPoints.add(entryPoint); + } + + return SLANG_OK; + } + + Result _createSpecializedLayout(ShaderObjectLayoutImpl** 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<RootShaderObjectLayoutImpl> specializedLayout; + RootShaderObjectLayoutImpl::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<ShaderObjectImpl>> m_entryPoints; + }; + void _flushGraphicsState(); - void _flushComputeState(); // D3D11Device members. @@ -574,7 +1534,7 @@ protected: ComPtr<PipelineStateImpl> m_currentPipelineState; - ComPtr<ID3D11UnorderedAccessView> m_uavBindings[int(PipelineType::CountOf)][kMaxUAVs]; + RootBindingState m_rootBindingState; bool m_framebufferBindingDirty = true; bool m_shaderBindingDirty = true; @@ -654,7 +1614,7 @@ SlangResult D3D11Device::initialize(const Desc& desc) { SLANG_RETURN_ON_FAIL(slangContext.initialize(desc.slang, SLANG_DXBC, "sm_5_0")); - SLANG_RETURN_ON_FAIL(GraphicsAPIRenderer::initialize(desc)); + SLANG_RETURN_ON_FAIL(RendererBase::initialize(desc)); // Initialize DeviceInfo { @@ -1808,63 +2768,78 @@ void D3D11Device::drawIndexed(UInt indexCount, UInt startIndex, UInt baseVertex) Result D3D11Device::createProgram(const IShaderProgram::Desc& desc, IShaderProgram** outProgram) { - if (desc.slangProgram && desc.slangProgram->getSpecializationParamCount() != 0) + SLANG_ASSERT(desc.slangProgram); + + if (desc.slangProgram->getSpecializationParamCount() != 0) { // For a specializable program, we don't invoke any actual slang compilation yet. RefPtr<ShaderProgramImpl> shaderProgram = new ShaderProgramImpl(); - initProgramCommon(shaderProgram, desc); + shaderProgram->slangProgram = desc.slangProgram; *outProgram = shaderProgram.detach(); return SLANG_OK; } - if( desc.kernelCount == 0 ) - { - return createProgramFromSlang(this, desc, outProgram); - } + // If the program is already specialized, compile and create shader kernels now. + SlangInt targetIndex = 0; + auto slangProgram = desc.slangProgram; + auto programLayout = slangProgram->getLayout(targetIndex); + if (!programLayout) + return SLANG_FAIL; + SlangUInt entryPointCount = programLayout->getEntryPointCount(); + if (entryPointCount == 0) + return SLANG_FAIL; - if (desc.pipelineType == PipelineType::Compute) + RefPtr<ShaderProgramImpl> shaderProgram = new ShaderProgramImpl(); + shaderProgram->slangProgram = desc.slangProgram; + + ScopeNVAPI scopeNVAPI; + SLANG_RETURN_ON_FAIL(scopeNVAPI.init(this, 0)); + for (SlangUInt i = 0; i < entryPointCount; i++) { - auto computeKernel = desc.findKernel(StageType::Compute); + ComPtr<ISlangBlob> kernelCode; + ComPtr<ISlangBlob> diagnostics; - ComPtr<ID3D11ComputeShader> computeShader; + auto compileResult = slangProgram->getEntryPointCode( + (SlangInt)i, 0, kernelCode.writeRef(), diagnostics.writeRef()); + if (diagnostics) { - ScopeNVAPI scopeNVAPI; - SLANG_RETURN_ON_FAIL(scopeNVAPI.init(this, 0)); - SLANG_RETURN_ON_FAIL(m_device->CreateComputeShader(computeKernel->codeBegin, computeKernel->getCodeSize(), nullptr, computeShader.writeRef())); + // TODO: dump compiler output. } - RefPtr<ShaderProgramImpl> shaderProgram = new ShaderProgramImpl(); - shaderProgram->m_computeShader.swap(computeShader); - initProgramCommon(shaderProgram, desc); - - *outProgram = shaderProgram.detach(); - return SLANG_OK; - } - else - { - auto vertexKernel = desc.findKernel(StageType::Vertex); - auto fragmentKernel = desc.findKernel(StageType::Fragment); - - ComPtr<ID3D11VertexShader> vertexShader; - ComPtr<ID3D11PixelShader> pixelShader; + SLANG_RETURN_ON_FAIL(compileResult); + auto entryPoint = programLayout->getEntryPointByIndex(i); + switch (entryPoint->getStage()) { - ScopeNVAPI scopeNVAPI; - SLANG_RETURN_ON_FAIL(scopeNVAPI.init(this, 0)); - - SLANG_RETURN_ON_FAIL(m_device->CreateVertexShader(vertexKernel->codeBegin, vertexKernel->getCodeSize(), nullptr, vertexShader.writeRef())); - SLANG_RETURN_ON_FAIL(m_device->CreatePixelShader(fragmentKernel->codeBegin, fragmentKernel->getCodeSize(), nullptr, pixelShader.writeRef())); + case SLANG_STAGE_COMPUTE: + SLANG_ASSERT(entryPointCount == 1); + SLANG_RETURN_ON_FAIL(m_device->CreateComputeShader( + kernelCode->getBufferPointer(), + kernelCode->getBufferSize(), + nullptr, + shaderProgram->m_computeShader.writeRef())); + break; + case SLANG_STAGE_VERTEX: + SLANG_RETURN_ON_FAIL(m_device->CreateVertexShader( + kernelCode->getBufferPointer(), + kernelCode->getBufferSize(), + nullptr, + shaderProgram->m_vertexShader.writeRef())); + break; + case SLANG_STAGE_FRAGMENT: + SLANG_RETURN_ON_FAIL(m_device->CreatePixelShader( + kernelCode->getBufferPointer(), + kernelCode->getBufferSize(), + nullptr, + shaderProgram->m_pixelShader.writeRef())); + break; + default: + SLANG_ASSERT(!"pipeline stage not implemented"); } - - RefPtr<ShaderProgramImpl> shaderProgram = new ShaderProgramImpl(); - shaderProgram->m_vertexShader.swap(vertexShader); - shaderProgram->m_pixelShader.swap(pixelShader); - initProgramCommon(shaderProgram, desc); - - *outProgram = shaderProgram.detach(); - return SLANG_OK; } + *outProgram = shaderProgram.detach(); + return SLANG_OK; } static D3D11_STENCIL_OP translateStencilOp(StencilOp op) @@ -1994,10 +2969,74 @@ D3D11_COLOR_WRITE_ENABLE translateRenderTargetWriteMask(RenderTargetWriteMaskT m return D3D11_COLOR_WRITE_ENABLE(result); } +Result D3D11Device::createShaderObjectLayout( + slang::TypeLayoutReflection* typeLayout, + ShaderObjectLayoutBase** outLayout) +{ + RefPtr<ShaderObjectLayoutImpl> layout; + SLANG_RETURN_ON_FAIL(ShaderObjectLayoutImpl::createForElementType( + this, typeLayout, layout.writeRef())); + *outLayout = layout.detach(); + return SLANG_OK; +} + +Result D3D11Device::createShaderObject(ShaderObjectLayoutBase* layout, IShaderObject** outObject) +{ + RefPtr<ShaderObjectImpl> shaderObject; + SLANG_RETURN_ON_FAIL(ShaderObjectImpl::create(this, + static_cast<ShaderObjectLayoutImpl*>(layout), shaderObject.writeRef())); + *outObject = shaderObject.detach(); + return SLANG_OK; +} + +Result D3D11Device::createRootShaderObject(IShaderProgram* program, IShaderObject** outObject) +{ + auto programImpl = static_cast<ShaderProgramImpl*>(program); + RefPtr<RootShaderObjectImpl> shaderObject; + RefPtr<RootShaderObjectLayoutImpl> rootLayout; + SLANG_RETURN_ON_FAIL(RootShaderObjectLayoutImpl::create( + this, programImpl->slangProgram, programImpl->slangProgram->getLayout(), rootLayout.writeRef())); + SLANG_RETURN_ON_FAIL(RootShaderObjectImpl::create( + this, rootLayout.Ptr(), shaderObject.writeRef())); + *outObject = shaderObject.detach(); + return SLANG_OK; +} + +void D3D11Device::bindRootShaderObject(PipelineType pipelineType, IShaderObject* shaderObject) +{ + RootShaderObjectImpl* rootShaderObjectImpl = static_cast<RootShaderObjectImpl*>(shaderObject); + RefPtr<PipelineStateBase> specializedPipeline; + maybeSpecializePipeline(m_currentPipelineState, rootShaderObjectImpl, specializedPipeline); + setPipelineState(specializedPipeline.Ptr()); + + m_rootBindingState.samplerBindings.clear(); + m_rootBindingState.srvBindings.clear(); + m_rootBindingState.uavBindings.clear(); + m_rootBindingState.constantBuffers.clear(); + static_cast<ShaderObjectImpl*>(shaderObject)->bindObject(this, &m_rootBindingState); + switch (pipelineType) + { + case PipelineType::Compute: + m_immediateContext->CSSetShaderResources(0, (UINT)m_rootBindingState.srvBindings.getCount(), m_rootBindingState.srvBindings.getBuffer()); + m_immediateContext->CSSetUnorderedAccessViews(0, (UINT)m_rootBindingState.uavBindings.getCount(), m_rootBindingState.uavBindings.getBuffer(), nullptr); + m_immediateContext->CSSetSamplers(0, (UINT)m_rootBindingState.samplerBindings.getCount(), m_rootBindingState.samplerBindings.getBuffer()); + m_immediateContext->CSSetConstantBuffers(0, (UINT)m_rootBindingState.constantBuffers.getCount(), m_rootBindingState.constantBuffers.getBuffer()); + break; + default: + m_immediateContext->VSSetShaderResources(0, (UINT)m_rootBindingState.srvBindings.getCount(), m_rootBindingState.srvBindings.getBuffer()); + m_immediateContext->PSSetShaderResources(0, (UINT)m_rootBindingState.srvBindings.getCount(), m_rootBindingState.srvBindings.getBuffer()); + m_immediateContext->VSSetSamplers(0, (UINT)m_rootBindingState.samplerBindings.getCount(), m_rootBindingState.samplerBindings.getBuffer()); + m_immediateContext->PSSetSamplers(0, (UINT)m_rootBindingState.samplerBindings.getCount(), m_rootBindingState.samplerBindings.getBuffer()); + m_immediateContext->VSSetConstantBuffers(0, (UINT)m_rootBindingState.constantBuffers.getCount(), m_rootBindingState.constantBuffers.getBuffer()); + m_immediateContext->PSSetConstantBuffers(0, (UINT)m_rootBindingState.constantBuffers.getCount(), m_rootBindingState.constantBuffers.getBuffer()); + m_shaderBindingDirty = true; + break; + } +} + Result D3D11Device::createGraphicsPipelineState(const GraphicsPipelineStateDesc& inDesc, IPipelineState** outState) { GraphicsPipelineStateDesc desc = inDesc; - preparePipelineDesc(desc); auto programImpl = (ShaderProgramImpl*) desc.program; @@ -2127,7 +3166,6 @@ Result D3D11Device::createGraphicsPipelineState(const GraphicsPipelineStateDesc& Result D3D11Device::createComputePipelineState(const ComputePipelineStateDesc& inDesc, IPipelineState** outState) { ComputePipelineStateDesc desc = inDesc; - preparePipelineDesc(desc); RefPtr<ComputePipelineStateImpl> state = new ComputePipelineStateImpl(); state->init(desc); @@ -2154,198 +3192,9 @@ void D3D11Device::copyBuffer( void D3D11Device::dispatchCompute(int x, int y, int z) { - _flushComputeState(); m_immediateContext->Dispatch(x, y, z); } -Result D3D11Device::createDescriptorSetLayout(const IDescriptorSetLayout::Desc& desc, IDescriptorSetLayout** outLayout) -{ - RefPtr<DescriptorSetLayoutImpl> descriptorSetLayoutImpl = new DescriptorSetLayoutImpl(); - - UInt counts[int(D3D11DescriptorSlotType::CountOf)] = { 0, }; - - UInt rangeCount = desc.slotRangeCount; - for(UInt rr = 0; rr < rangeCount; ++rr) - { - auto rangeDesc = desc.slotRanges[rr]; - - DescriptorSetLayoutImpl::RangeInfo rangeInfo; - - UInt slotCount = rangeDesc.count; - switch(rangeDesc.type) - { - default: - assert(!"invalid slot type"); - return SLANG_FAIL; - - case DescriptorSlotType::Sampler: - rangeInfo.type = D3D11DescriptorSlotType::Sampler; - break; - - case DescriptorSlotType::CombinedImageSampler: - rangeInfo.type = D3D11DescriptorSlotType::CombinedTextureSampler; - break; - - case DescriptorSlotType::RootConstant: - // A root-constant range will be treated as if it were - // a constant-buffer range with a single buffer in it. - // - slotCount = 1; - case DescriptorSlotType::UniformBuffer: - case DescriptorSlotType::DynamicUniformBuffer: - rangeInfo.type = D3D11DescriptorSlotType::ConstantBuffer; - break; - - case DescriptorSlotType::SampledImage: - case DescriptorSlotType::UniformTexelBuffer: - case DescriptorSlotType::InputAttachment: - case DescriptorSlotType::ReadOnlyStorageBuffer: - rangeInfo.type = D3D11DescriptorSlotType::ShaderResourceView; - break; - - case DescriptorSlotType::StorageImage: - case DescriptorSlotType::StorageTexelBuffer: - case DescriptorSlotType::StorageBuffer: - case DescriptorSlotType::DynamicStorageBuffer: - rangeInfo.type = D3D11DescriptorSlotType::UnorderedAccessView; - break; - } - - if(rangeInfo.type == D3D11DescriptorSlotType::CombinedTextureSampler) - { - auto srvTypeIndex = int(D3D11DescriptorSlotType::ShaderResourceView); - auto samplerTypeIndex = int(D3D11DescriptorSlotType::Sampler); - - rangeInfo.arrayIndex = counts[srvTypeIndex]; - rangeInfo.pairedSamplerArrayIndex = counts[samplerTypeIndex]; - - counts[srvTypeIndex] += slotCount; - counts[samplerTypeIndex] += slotCount; - } - else - { - auto typeIndex = int(rangeInfo.type); - - rangeInfo.arrayIndex = counts[typeIndex]; - counts[typeIndex] += slotCount; - } - Index rangeIndex = descriptorSetLayoutImpl->m_ranges.getCount(); - descriptorSetLayoutImpl->m_ranges.add(rangeInfo); - - if(rangeDesc.type == DescriptorSlotType::RootConstant) - { - // If the range represents a root constant range, then - // we need to also store the information we will need when - // allocating a constant buffer to provide backing storage - // for the range. - // - DescriptorSetLayoutImpl::RootConstantRangeInfo rootConstantRangeInfo; - rootConstantRangeInfo.rangeIndex = rangeIndex; - rootConstantRangeInfo.size = rangeDesc.count; - descriptorSetLayoutImpl->m_rootConstantRanges.add(rootConstantRangeInfo); - } - } - - for(int ii = 0; ii < int(D3D11DescriptorSlotType::CountOf); ++ii) - { - descriptorSetLayoutImpl->m_counts[ii] = counts[ii]; - } - - *outLayout = descriptorSetLayoutImpl.detach(); - return SLANG_OK; -} - -Result D3D11Device::createPipelineLayout(const IPipelineLayout::Desc& desc, IPipelineLayout** outLayout) -{ - RefPtr<PipelineLayoutImpl> pipelineLayoutImpl = new PipelineLayoutImpl(); - - UInt counts[int(D3D11DescriptorSlotType::CountOf)] = { 0, }; - - UInt setCount = desc.descriptorSetCount; - for(UInt ii = 0; ii < setCount; ++ii) - { - auto setDesc = desc.descriptorSets[ii]; - PipelineLayoutImpl::DescriptorSetInfo setInfo; - - setInfo.layout = (DescriptorSetLayoutImpl*) setDesc.layout; - - for(int jj = 0; jj < int(D3D11DescriptorSlotType::CountOf); ++jj) - { - setInfo.baseIndices[jj] = counts[jj]; - counts[jj] += setInfo.layout->m_counts[jj]; - } - - pipelineLayoutImpl->m_descriptorSets.add(setInfo); - } - - pipelineLayoutImpl->m_uavCount = UINT(counts[int(D3D11DescriptorSlotType::UnorderedAccessView)]); - - *outLayout = pipelineLayoutImpl.detach(); - return SLANG_OK; -} - -Result D3D11Device::createDescriptorSet(IDescriptorSetLayout* layout, IDescriptorSet::Flag::Enum flag, IDescriptorSet** outDescriptorSet) -{ - SLANG_UNUSED(flag); - - auto layoutImpl = (DescriptorSetLayoutImpl*)layout; - - RefPtr<DescriptorSetImpl> descriptorSetImpl = new DescriptorSetImpl(); - - descriptorSetImpl->m_renderer = this; - descriptorSetImpl->m_layout = layoutImpl; - descriptorSetImpl->m_cbs .setCount(layoutImpl->m_counts[int(D3D11DescriptorSlotType::ConstantBuffer)]); - descriptorSetImpl->m_srvs .setCount(layoutImpl->m_counts[int(D3D11DescriptorSlotType::ShaderResourceView)]); - descriptorSetImpl->m_uavs .setCount(layoutImpl->m_counts[int(D3D11DescriptorSlotType::UnorderedAccessView)]); - descriptorSetImpl->m_samplers.setCount(layoutImpl->m_counts[int(D3D11DescriptorSlotType::Sampler)]); - - // If the layout includes any root constant ranges, then - // we will need to allocate a constant buffer for each - // range to provide "backing storage" for its data. - // - for(auto rootConstantRange : layoutImpl->m_rootConstantRanges) - { - // The root constant range will refer to a descriptor slot - // range that represents a range with a single constant - // buffer in it. We need to grab that range so that we - // know what constant-buffer bindign slot to fill in. - // - auto rangeIndex = rootConstantRange.rangeIndex; - auto bufferRange = layoutImpl->m_ranges[rangeIndex]; - - // We will allocate the constant buffer that provides - // backing storage directly using D3D11 API calls, - // rather than allocate it as a buffer resource. - // - // TODO: We could revisit that decision if allocating - // a buffer resource proves easier down the line. - - // Note: A D3D11 constant buffer must be a multiple of 16 bytes - // in size, so we will round up the allocation size to match - // the requirement. - // - UINT size = (UINT) rootConstantRange.size; - size = (size + 15) & ~15; - - D3D11_BUFFER_DESC bufferDesc; - bufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; - bufferDesc.ByteWidth = size; - bufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; - bufferDesc.MiscFlags = 0; - bufferDesc.StructureByteStride = 0; - bufferDesc.Usage = D3D11_USAGE_DYNAMIC; - - Slang::ComPtr<ID3D11Buffer> buffer; - SLANG_RETURN_ON_FAIL(m_device->CreateBuffer(&bufferDesc, nullptr, buffer.writeRef())); - - descriptorSetImpl->m_cbs[bufferRange.arrayIndex] = buffer; - } - - *outDescriptorSet = descriptorSetImpl.detach(); - return SLANG_OK; -} - - void D3D11Device::_flushGraphicsState() { auto pipelineType = int(PipelineType::Graphics); @@ -2355,17 +3204,15 @@ void D3D11Device::_flushGraphicsState() m_shaderBindingDirty = false; auto pipelineState = static_cast<GraphicsPipelineStateImpl*>(m_currentPipelineState.get()); - auto pipelineLayout = - static_cast<PipelineLayoutImpl*>(pipelineState->m_pipelineLayout.get()); auto rtvCount = (UINT)m_currentFramebuffer->renderTargetViews.getCount(); - auto uavCount = pipelineLayout->m_uavCount; + auto uavCount = (UINT)m_rootBindingState.uavBindings.getCount(); m_immediateContext->OMSetRenderTargetsAndUnorderedAccessViews( rtvCount, m_currentFramebuffer->d3dRenderTargetViews.getArrayView().getBuffer(), m_currentFramebuffer->d3dDepthStencilView, rtvCount, uavCount, - m_uavBindings[pipelineType][0].readRef(), + m_rootBindingState.uavBindings.getBuffer(), nullptr); } if (m_depthStencilStateDirty) @@ -2377,239 +3224,4 @@ void D3D11Device::_flushGraphicsState() } } -void D3D11Device::_flushComputeState() -{ - auto pipelineType = int(PipelineType::Compute); - if (m_shaderBindingDirty) - { - m_shaderBindingDirty = false; - - auto pipelineState = static_cast<ComputePipelineStateImpl*>(m_currentPipelineState.get()); - auto pipelineLayout = - static_cast<PipelineLayoutImpl*>(pipelineState->m_pipelineLayout.get()); - - auto uavCount = pipelineLayout->m_uavCount; - - m_immediateContext->CSSetUnorderedAccessViews( - 0, - uavCount, - m_uavBindings[pipelineType][0].readRef(), - nullptr); - } -} - -void D3D11Device::DescriptorSetImpl::setConstantBuffer(UInt range, UInt index, IBufferResource* buffer) -{ - auto bufferImpl = (BufferResourceImpl*) buffer; - auto& rangeInfo = m_layout->m_ranges[range]; - - assert(rangeInfo.type == D3D11DescriptorSlotType::ConstantBuffer); - - m_cbs[rangeInfo.arrayIndex + index] = bufferImpl->m_buffer; } - -void D3D11Device::DescriptorSetImpl::setResource(UInt range, UInt index, IResourceView* view) -{ - auto viewImpl = (ResourceViewImpl*)view; - auto& rangeInfo = m_layout->m_ranges[range]; - auto flatIndex = rangeInfo.arrayIndex + index; - - switch (rangeInfo.type) - { - case D3D11DescriptorSlotType::ShaderResourceView: - { - if( viewImpl ) - { - assert(viewImpl->m_type == ResourceViewImpl::Type::SRV); - auto srvImpl = (ShaderResourceViewImpl*)viewImpl; - m_srvs[flatIndex] = srvImpl->m_srv; - } - else - { - m_srvs[flatIndex] = nullptr; - } - } - break; - - case D3D11DescriptorSlotType::UnorderedAccessView: - { - if( viewImpl ) - { - assert(viewImpl->m_type == ResourceViewImpl::Type::UAV); - auto uavImpl = (UnorderedAccessViewImpl*)viewImpl; - m_uavs[flatIndex] = uavImpl->m_uav; - } - else - { - m_uavs[flatIndex] = nullptr; - } - } - break; - - default: - assert(!"invalid to bind a resource view to this descriptor range"); - break; - } -} - -void D3D11Device::DescriptorSetImpl::setSampler(UInt range, UInt index, ISamplerState* sampler) -{ - auto samplerImpl = (SamplerStateImpl*) sampler; - auto& rangeInfo = m_layout->m_ranges[range]; - - assert(rangeInfo.type == D3D11DescriptorSlotType::Sampler); - - m_samplers[rangeInfo.arrayIndex + index] = samplerImpl->m_sampler; -} - -void D3D11Device::DescriptorSetImpl::setCombinedTextureSampler( - UInt range, - UInt index, - IResourceView* textureView, - ISamplerState* sampler) -{ - auto viewImpl = (ResourceViewImpl*) textureView; - auto samplerImpl = (SamplerStateImpl*)sampler; - - auto& rangeInfo = m_layout->m_ranges[range]; - assert(rangeInfo.type == D3D11DescriptorSlotType::CombinedTextureSampler); - - assert(viewImpl->m_type == ResourceViewImpl::Type::SRV); - auto srvImpl = (ShaderResourceViewImpl*)viewImpl; - m_srvs[rangeInfo.arrayIndex + index] = srvImpl->m_srv; - - m_samplers[rangeInfo.arrayIndex + index] = samplerImpl->m_sampler; - - // TODO: need a place to bind the matching sampler... - m_srvs[rangeInfo.pairedSamplerArrayIndex + index] = srvImpl->m_srv; -} - -void D3D11Device::DescriptorSetImpl::setRootConstants( - UInt range, - UInt offset, - UInt size, - void const* data) -{ - // The `range` parameter represents the index of a descriptor - // slot range in the layout of this descriptor set. - // - // A root constant range will have been translated into - // a constnat buffer range at creation time for the layout. - // - auto& rangeInfo = m_layout->m_ranges[range]; - assert(rangeInfo.type == D3D11DescriptorSlotType::ConstantBuffer); - - // At the time the descriptor set was allocated, a - // constant buffer will have been created and bound - // into `m_cbs` to provide backing storage for the - // root constant range. - // - auto dxBuffer = m_cbs[rangeInfo.arrayIndex]; - auto dxContext = m_renderer->m_immediateContext; - - // Once we have the buffer that provides backing - // storage we simply need to map it and write - // the user-provided data into it. - // - D3D11_MAPPED_SUBRESOURCE mapped; - HRESULT hr = dxContext->Map(dxBuffer, 0, D3D11_MAP_WRITE_NO_OVERWRITE, 0, &mapped); - if( FAILED(hr) ) - { - SLANG_ASSERT(!"failed to map backing storage for root constant range"); - return; - } - memcpy((char*)mapped.pData + offset, data, size); - dxContext->Unmap(dxBuffer, 0); -} - -void D3D11Device::setDescriptorSet(PipelineType pipelineType, IPipelineLayout* layout, UInt index, IDescriptorSet* descriptorSet) -{ - auto pipelineLayoutImpl = (PipelineLayoutImpl*)layout; - auto descriptorSetImpl = (DescriptorSetImpl*) descriptorSet; - - auto descriptorSetLayoutImpl = descriptorSetImpl->m_layout; - auto& setInfo = pipelineLayoutImpl->m_descriptorSets[index]; - - // Note: `setInfo->layout` and `descriptorSetLayoutImpl` need to be compatible - - // TODO: If/when we add per-stage visibility masks, it would be best to organize - // this as a loop over stages, so that we only do the binding that is required - // for each stage. - - { - const int slotType = int(D3D11DescriptorSlotType::ConstantBuffer); - const UINT slotCount = UINT(setInfo.layout->m_counts[slotType]); - if(slotCount) - { - const UINT startSlot = UINT(setInfo.baseIndices[slotType]); - - auto cbs = descriptorSetImpl->m_cbs[0].readRef(); - - m_immediateContext->VSSetConstantBuffers(startSlot, slotCount, cbs); - // ... - m_immediateContext->PSSetConstantBuffers(startSlot, slotCount, cbs); - - m_immediateContext->CSSetConstantBuffers(startSlot, slotCount, cbs); - } - } - - { - const int slotType = int(D3D11DescriptorSlotType::ShaderResourceView); - const UINT slotCount = UINT(setInfo.layout->m_counts[slotType]); - if(slotCount) - { - const UINT startSlot = UINT(setInfo.baseIndices[slotType]); - - auto srvs = descriptorSetImpl->m_srvs[0].readRef(); - - m_immediateContext->VSSetShaderResources(startSlot, slotCount, srvs); - // ... - m_immediateContext->PSSetShaderResources(startSlot, slotCount, srvs); - - m_immediateContext->CSSetShaderResources(startSlot, slotCount, srvs); - } - } - - { - const int slotType = int(D3D11DescriptorSlotType::Sampler); - const UINT slotCount = UINT(setInfo.layout->m_counts[slotType]); - if(slotCount) - { - const UINT startSlot = UINT(setInfo.baseIndices[slotType]); - - auto samplers = descriptorSetImpl->m_samplers[0].readRef(); - - m_immediateContext->VSSetSamplers(startSlot, slotCount, samplers); - // ... - m_immediateContext->PSSetSamplers(startSlot, slotCount, samplers); - - m_immediateContext->CSSetSamplers(startSlot, slotCount, samplers); - } - } - - { - // Note: UAVs are handled differently from other bindings, because - // D3D11 requires all UAVs to be set with a single call, rather - // than allowing incremental updates. We will therefore shadow - // the UAV bindings with `m_uavBindings` and then flush them - // as needed right before a draw/dispatch. - // - const int slotType = int(D3D11DescriptorSlotType::UnorderedAccessView); - const UInt slotCount = setInfo.layout->m_counts[slotType]; - if(slotCount) - { - UInt startSlot = setInfo.baseIndices[slotType]; - - auto uavs = descriptorSetImpl->m_uavs[0].readRef(); - - for(UINT ii = 0; ii < slotCount; ++ii) - { - m_uavBindings[int(pipelineType)][startSlot + ii] = uavs[ii]; - } - m_shaderBindingDirty = true; - } - } -} - -} - diff --git a/tools/gfx/d3d12/render-d3d12.cpp b/tools/gfx/d3d12/render-d3d12.cpp index 3225ed208..52e9b33a6 100644 --- a/tools/gfx/d3d12/render-d3d12.cpp +++ b/tools/gfx/d3d12/render-d3d12.cpp @@ -1548,7 +1548,9 @@ protected: ComPtr<ID3D12CommandQueue> m_queue; ComPtr<IDXGIFactory> m_dxgiFactory; ComPtr<IDXGISwapChain3> m_swapChain3; - + ComPtr<ID3D12Fence> m_fence; + ShortList<HANDLE, kMaxNumRenderFrames> m_frameEvents; + uint64_t fenceValue = 0; Result init( D3D12Device* renderer, const ISwapchain::Desc& swapchainDesc, @@ -1558,7 +1560,17 @@ protected: m_dxgiFactory = renderer->m_deviceInfo.m_dxgiFactory; SLANG_RETURN_ON_FAIL( D3DSwapchainBase::init(swapchainDesc, window, DXGI_SWAP_EFFECT_FLIP_DISCARD)); + renderer->m_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(m_fence.writeRef())); + SLANG_RETURN_ON_FAIL(m_swapChain->QueryInterface(m_swapChain3.writeRef())); + for (uint32_t i = 0; i < swapchainDesc.imageCount; i++) + { + m_frameEvents.add(CreateEventEx( + nullptr, + false, + CREATE_EVENT_INITIAL_SET | CREATE_EVENT_MANUAL_RESET, + EVENT_ALL_ACCESS)); + } return SLANG_OK; } @@ -1586,7 +1598,18 @@ protected: virtual IUnknown* getOwningDevice() override { return m_queue; } virtual SLANG_NO_THROW int SLANG_MCALL acquireNextImage() override { - return (int)m_swapChain3->GetCurrentBackBufferIndex(); + auto result = (int)m_swapChain3->GetCurrentBackBufferIndex(); + WaitForSingleObject(m_frameEvents[result], INFINITE); + ResetEvent(m_frameEvents[result]); + return result; + } + virtual SLANG_NO_THROW Result SLANG_MCALL present() override + { + SLANG_RETURN_ON_FAIL(D3DSwapchainBase::present()); + fenceValue++; + m_fence->SetEventOnCompletion(fenceValue, m_frameEvents[m_swapChain3->GetCurrentBackBufferIndex()]); + m_queue->Signal(m_fence, fenceValue); + return SLANG_OK; } }; @@ -2579,7 +2602,7 @@ Result D3D12Device::createTextureResource(IResource::Usage initialUsage, const I uint8_t* dstRow = dstLayer; for (int k = 0; k < mipSize.height; ++k) { - ::memcpy(dstRow, srcRow, mipRowSize); + ::memcpy(dstRow, srcRow, (size_t)mipRowSize); srcRow += srcMipRowPitch; dstRow += dstMipRowPitch; @@ -3185,11 +3208,14 @@ void D3D12Device::DescriptorSetImpl::setResource(UInt range, UInt index, IResour auto descriptorIndex = m_resourceTable + arrayIndex; m_resourceObjects[arrayIndex] = viewImpl; - dxDevice->CopyDescriptorsSimple( - 1, - m_resourceHeap->getCpuHandle(int(descriptorIndex)), - viewImpl->m_descriptor.cpuHandle, - D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + if (viewImpl) + { + dxDevice->CopyDescriptorsSimple( + 1, + m_resourceHeap->getCpuHandle(int(descriptorIndex)), + viewImpl->m_descriptor.cpuHandle, + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + } } void D3D12Device::DescriptorSetImpl::setSampler(UInt range, UInt index, ISamplerState* sampler) @@ -3216,28 +3242,31 @@ void D3D12Device::DescriptorSetImpl::setSampler(UInt range, UInt index, ISampler auto descriptorIndex = m_samplerTable + arrayIndex; m_samplerObjects[arrayIndex] = samplerImpl; - dxDevice->CopyDescriptorsSimple( - 1, - m_samplerHeap->getCpuHandle(int(descriptorIndex)), - samplerImpl->m_descriptor.cpuHandle, - D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER); + if (samplerImpl) + { + dxDevice->CopyDescriptorsSimple( + 1, + m_samplerHeap->getCpuHandle(int(descriptorIndex)), + samplerImpl->m_descriptor.cpuHandle, + D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER); + } } void D3D12Device::DescriptorSetImpl::setCombinedTextureSampler( UInt range, UInt index, - IResourceView* textureView, - ISamplerState* sampler) + IResourceView* textureView, + ISamplerState* sampler) { auto dxDevice = m_renderer->m_device; - auto viewImpl = (ResourceViewImpl*) textureView; - auto samplerImpl = (SamplerStateImpl*) sampler; + auto viewImpl = (ResourceViewImpl*)textureView; + auto samplerImpl = (SamplerStateImpl*)sampler; auto& rangeInfo = m_layout->m_ranges[range]; #ifdef _DEBUG - switch(rangeInfo.type) + switch (rangeInfo.type) { default: assert(!"incorrect slot type"); @@ -3253,18 +3282,24 @@ void D3D12Device::DescriptorSetImpl::setCombinedTextureSampler( auto samplerDescriptorIndex = m_samplerTable + arrayIndex; m_resourceObjects[arrayIndex] = viewImpl; - dxDevice->CopyDescriptorsSimple( - 1, - m_resourceHeap->getCpuHandle(int(resourceDescriptorIndex)), - viewImpl->m_descriptor.cpuHandle, - D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + if (viewImpl) + { + dxDevice->CopyDescriptorsSimple( + 1, + m_resourceHeap->getCpuHandle(int(resourceDescriptorIndex)), + viewImpl->m_descriptor.cpuHandle, + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + } m_samplerObjects[arrayIndex] = samplerImpl; - dxDevice->CopyDescriptorsSimple( - 1, - m_samplerHeap->getCpuHandle(int(samplerDescriptorIndex)), - samplerImpl->m_descriptor.cpuHandle, - D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER); + if (samplerImpl) + { + dxDevice->CopyDescriptorsSimple( + 1, + m_samplerHeap->getCpuHandle(int(samplerDescriptorIndex)), + samplerImpl->m_descriptor.cpuHandle, + D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER); + } } void D3D12Device::DescriptorSetImpl::setRootConstants( diff --git a/tools/gfx/immediate-renderer-base.cpp b/tools/gfx/immediate-renderer-base.cpp index 14c6cf217..92daa19ef 100644 --- a/tools/gfx/immediate-renderer-base.cpp +++ b/tools/gfx/immediate-renderer-base.cpp @@ -303,7 +303,7 @@ public: switch (name) { case CommandName::SetPipelineState: - m_renderer->_setPipelineState(m_writer.getObject<IPipelineState>(cmd.operands[0])); + m_renderer->setPipelineState(m_writer.getObject<IPipelineState>(cmd.operands[0])); break; case CommandName::BindRootShaderObject: m_renderer->bindRootShaderObject( @@ -450,43 +450,10 @@ public: }; } - ImmediateRendererBase::ImmediateRendererBase() { m_queue = new CommandQueueImpl(this); } -void ImmediateRendererBase::bindRootShaderObject(PipelineType pipelineType, IShaderObject* shaderObject) -{ - class ImmediateCommandEncoder : public GraphicsComputeCommandEncoderBase - { - public: - virtual SLANG_NO_THROW void SLANG_MCALL setDescriptorSetImpl( - PipelineType pipelineType, - IPipelineLayout* layout, - UInt index, - IDescriptorSet* descriptorSet) override - { - auto renderer = static_cast<ImmediateRendererBase*>(m_rendererBase); - renderer->setDescriptorSet(pipelineType, layout, index, descriptorSet); - } - - virtual SLANG_NO_THROW void SLANG_MCALL uploadBufferDataImpl( - IBufferResource* buffer, - size_t offset, - size_t size, - void* data) override - { - auto renderer = static_cast<ImmediateRendererBase*>(m_rendererBase); - renderer->uploadBufferData(buffer, offset, size, data); - } - }; - ImmediateCommandEncoder encoder; - encoder.m_rendererBase = this; - encoder.m_currentPipeline = static_cast<PipelineStateBase*>(m_currentPipelineState.get()); - encoder.bindRootShaderObjectImpl(pipelineType, shaderObject); - _setPipelineState(encoder.m_currentPipeline); -} - SLANG_NO_THROW Result SLANG_MCALL ImmediateRendererBase::createCommandQueue( const ICommandQueue::Desc& desc, ICommandQueue** outQueue) @@ -510,16 +477,6 @@ SLANG_NO_THROW Result SLANG_MCALL ImmediateRendererBase::createRenderPassLayout( return SLANG_OK; } -void ImmediateRendererBase::_setPipelineState(IPipelineState* state) -{ - PipelineStateBase* pipelineImpl = static_cast<PipelineStateBase*>(state); - if (!pipelineImpl->isSpecializable) - { - setPipelineState(state); - } - m_currentPipelineState = state; -} - void ImmediateRendererBase::uploadBufferData( IBufferResource* dst, size_t offset, diff --git a/tools/gfx/immediate-renderer-base.h b/tools/gfx/immediate-renderer-base.h index 477c25b13..311767822 100644 --- a/tools/gfx/immediate-renderer-base.h +++ b/tools/gfx/immediate-renderer-base.h @@ -4,7 +4,7 @@ // Provides shared implementation of public API objects for targets with // an immediate mode execution context. -#include "render-graphics-common.h" +#include "renderer-shared.h" namespace gfx { @@ -17,7 +17,7 @@ enum class MapFlavor WriteDiscard, }; -class ImmediateRendererBase : public GraphicsAPIRenderer +class ImmediateRendererBase : public RendererBase { private: ComPtr<IPipelineState> m_currentPipelineState; @@ -59,7 +59,7 @@ public: virtual SLANG_NO_THROW void SLANG_MCALL waitForGpu() = 0; virtual void* map(IBufferResource* buffer, MapFlavor flavor) = 0; virtual void unmap(IBufferResource* buffer) = 0; - void bindRootShaderObject(PipelineType pipelineType, IShaderObject* shaderObject); + virtual void bindRootShaderObject(PipelineType pipelineType, IShaderObject* shaderObject) = 0; public: Slang::ComPtr<ICommandQueue> m_queue; @@ -73,8 +73,6 @@ public: const IRenderPassLayout::Desc& desc, IRenderPassLayout** outRenderPassLayout) override; - void _setPipelineState(IPipelineState* state); - void uploadBufferData( IBufferResource* dst, size_t offset, diff --git a/tools/gfx/open-gl/render-gl.cpp b/tools/gfx/open-gl/render-gl.cpp index 2f2071a42..960670e1e 100644 --- a/tools/gfx/open-gl/render-gl.cpp +++ b/tools/gfx/open-gl/render-gl.cpp @@ -57,6 +57,7 @@ F(glUnmapBuffer, PFNGLUNMAPBUFFERPROC) \ F(glUseProgram, PFNGLUSEPROGRAMPROC) \ F(glBindBufferBase, PFNGLBINDBUFFERBASEPROC) \ + F(glBindBufferRange, PFNGLBINDBUFFERRANGEPROC) \ F(glVertexAttribPointer, PFNGLVERTEXATTRIBPOINTERPROC) \ F(glEnableVertexAttribArray, PFNGLENABLEVERTEXATTRIBARRAYPROC) \ F(glDisableVertexAttribArray, PFNGLDISABLEVERTEXATTRIBARRAYPROC) \ @@ -67,6 +68,7 @@ F(glDeleteSamplers, PFNGLDELETESAMPLERSPROC) \ F(glBindSampler, PFNGLBINDSAMPLERPROC) \ F(glTexImage3D, PFNGLTEXIMAGE3DPROC) \ + F(glBindImageTexture, PFNGLBINDIMAGETEXTUREPROC) \ F(glSamplerParameteri, PFNGLSAMPLERPARAMETERIPROC) \ F(glGenFramebuffers, PFNGLGENFRAMEBUFFERSPROC) \ F(glDeleteFramebuffers, PFNGLDELETEFRAMEBUFFERSPROC) \ @@ -79,6 +81,7 @@ F(glGenVertexArrays, PFNGLGENVERTEXARRAYSPROC) \ F(glBindVertexArray, PFNGLBINDVERTEXARRAYPROC) \ F(glDeleteVertexArrays, PFNGLDELETEVERTEXARRAYSPROC) \ + F(glDrawElementsBaseVertex, PFNGLDRAWELEMENTSBASEVERTEXPROC) \ /* end */ #define MAP_WGL_EXTENSION_FUNCS(F) \ @@ -127,11 +130,48 @@ public: IInputLayout** outLayout) override; virtual SLANG_NO_THROW Result SLANG_MCALL createDescriptorSetLayout( - const IDescriptorSetLayout::Desc& desc, IDescriptorSetLayout** outLayout) override; + const IDescriptorSetLayout::Desc& desc, IDescriptorSetLayout** outLayout) override + { + SLANG_UNUSED(desc); + SLANG_UNUSED(outLayout); + return SLANG_FAIL; + } virtual SLANG_NO_THROW Result SLANG_MCALL - createPipelineLayout(const IPipelineLayout::Desc& desc, IPipelineLayout** outLayout) override; + createPipelineLayout(const IPipelineLayout::Desc& desc, IPipelineLayout** outLayout) override + { + SLANG_UNUSED(desc); + SLANG_UNUSED(outLayout); + return SLANG_FAIL; + } + virtual SLANG_NO_THROW Result SLANG_MCALL createDescriptorSet( + IDescriptorSetLayout* layout, + IDescriptorSet::Flag::Enum flag, + IDescriptorSet** outDescriptorSet) override + { + SLANG_UNUSED(layout); + SLANG_UNUSED(flag); + SLANG_UNUSED(outDescriptorSet); + return SLANG_FAIL; + } + virtual SLANG_NO_THROW void SLANG_MCALL setDescriptorSet( + PipelineType pipelineType, + IPipelineLayout* layout, + UInt index, + IDescriptorSet* descriptorSet) override + { + SLANG_UNUSED(pipelineType); + SLANG_UNUSED(layout); + SLANG_UNUSED(index); + SLANG_UNUSED(descriptorSet); + } + + 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 void bindRootShaderObject(PipelineType pipelineType, IShaderObject* shaderObject) override; virtual SLANG_NO_THROW Result SLANG_MCALL createProgram(const IShaderProgram::Desc& desc, IShaderProgram** outProgram) override; @@ -154,12 +194,6 @@ public: virtual SLANG_NO_THROW void SLANG_MCALL setPrimitiveTopology(PrimitiveTopology topology) override; - virtual SLANG_NO_THROW void SLANG_MCALL setDescriptorSet( - PipelineType pipelineType, - IPipelineLayout* layout, - UInt index, - IDescriptorSet* descriptorSet) override; - virtual SLANG_NO_THROW void SLANG_MCALL setVertexBuffers( UInt startSlot, UInt slotCount, @@ -233,7 +267,8 @@ public: m_renderer(renderer), m_handle(id), m_initialUsage(initialUsage), - m_target(target) + m_target(target), + m_size(desc.sizeInBytes) {} ~BufferResourceImpl() { @@ -247,6 +282,7 @@ public: RefPtr<WeakSink<GLDevice> > m_renderer; GLuint m_handle; GLenum m_target; + UInt m_size; }; class TextureResourceImpl: public TextureResource @@ -301,13 +337,30 @@ public: return static_cast<IResourceView*>(this); return nullptr; } + public: + enum class Type + { + Texture, Buffer + }; + Type type; }; class TextureViewImpl : public ResourceViewImpl { public: RefPtr<TextureResourceImpl> m_resource; - GLuint m_textureID; + GLuint m_textureID; + GLuint m_target; + enum class TextureViewType + { + Texture, Image + }; + TextureViewType textureViewType; + GLint level; + GLboolean layered; + GLint layer; + GLenum access; + GLenum format; }; class BufferViewImpl : public ResourceViewImpl @@ -571,93 +624,7 @@ public: ShortList<RefPtr<TextureResourceImpl>> m_images; }; - enum class GLDescriptorSlotType - { - ConstantBuffer, - CombinedTextureSampler, - StorageBuffer, - CountOf, - }; - - class DescriptorSetLayoutImpl : public IDescriptorSetLayout, public RefObject - { - public: - SLANG_REF_OBJECT_IUNKNOWN_ALL - IDescriptorSetLayout* getInterface(const Guid& guid) - { - if (guid == GfxGUID::IID_ISlangUnknown || guid == GfxGUID::IID_IDescriptorSetLayout) - return static_cast<IDescriptorSetLayout*>(this); - return nullptr; - } - public: - struct RangeInfo - { - GLDescriptorSlotType type; - UInt arrayIndex; - }; - List<RangeInfo> m_ranges; - Int m_counts[int(GLDescriptorSlotType::CountOf)]; - }; - - class PipelineLayoutImpl : public IPipelineLayout, public RefObject - { - public: - SLANG_REF_OBJECT_IUNKNOWN_ALL - IPipelineLayout* getInterface(const Guid& guid) - { - if (guid == GfxGUID::IID_ISlangUnknown || guid == GfxGUID::IID_IPipelineLayout) - { - return static_cast<IPipelineLayout*>(this); - } - return nullptr; - } - public: - struct DescriptorSetInfo - { - RefPtr<DescriptorSetLayoutImpl> layout; - UInt baseArrayIndex[int(GLDescriptorSlotType::CountOf)]; - }; - - List<DescriptorSetInfo> m_sets; - }; - - class DescriptorSetImpl : public IDescriptorSet, public RefObject - { - public: - SLANG_REF_OBJECT_IUNKNOWN_ALL - IDescriptorSet* getInterface(const Guid& guid) - { - if (guid == GfxGUID::IID_ISlangUnknown || guid == GfxGUID::IID_IDescriptorSet) - return static_cast<IDescriptorSet*>(this); - return nullptr; - } - public: - 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; - - RefPtr<DescriptorSetLayoutImpl> m_layout; - List<RefPtr<BufferResourceImpl>> m_constantBuffers; - List<RefPtr<BufferResourceImpl>> m_storageBuffers; - List<RefPtr<TextureViewImpl>> m_textures; - List<RefPtr<SamplerStateImpl>> m_samplers; - }; - - class ShaderProgramImpl : public GraphicsCommonShaderProgram + class ShaderProgramImpl : public ShaderProgramBase { public: ShaderProgramImpl(WeakSink<GLDevice>* renderer, GLuint id): @@ -697,6 +664,1077 @@ public: } }; + struct RootBindingState + { + List<RefPtr<TextureViewImpl>> textureBindings; + List<RefPtr<TextureViewImpl>> imageBindings; + List<GLuint> samplerBindings; + List<GLuint> uniformBufferBindings; + List<GLuint> storageBufferBindings; + }; + + class ShaderObjectLayoutImpl : public ShaderObjectLayoutBase + { + public: + struct BindingRangeInfo + { + slang::BindingType bindingType; + Index count; + Index baseIndex; + + // 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 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_textureCount = 0; + Index m_imageCount = 0; + Index m_storageBufferCount = 0; + Index m_subObjectCount = 0; + + Result setElementTypeLayout(slang::TypeLayoutReflection* typeLayout) + { + typeLayout = _unwrapParameterGroups(typeLayout); + + m_elementTypeLayout = typeLayout; + + // Compute the binding ranges that are used to store + // the logical contents of the object in memory. + + 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); + + BindingRangeInfo bindingRangeInfo; + bindingRangeInfo.bindingType = slangBindingType; + bindingRangeInfo.count = count; + + switch (slangBindingType) + { + case slang::BindingType::ConstantBuffer: + case slang::BindingType::ParameterBlock: + case slang::BindingType::ExistentialValue: + bindingRangeInfo.baseIndex = m_subObjectCount; + m_subObjectCount += count; + break; + + case slang::BindingType::Sampler: + break; + + case slang::BindingType::Texture: + case slang::BindingType::CombinedTextureSampler: + bindingRangeInfo.baseIndex = m_textureCount; + m_textureCount += count; + break; + + case slang::BindingType::MutableTexture: + bindingRangeInfo.baseIndex = m_imageCount; + m_imageCount += count; + break; + + case slang::BindingType::MutableRawBuffer: + case slang::BindingType::MutableTypedBuffer: + bindingRangeInfo.baseIndex = m_storageBufferCount; + m_storageBufferCount += count; + break; + case slang::BindingType::VaryingInput: + case slang::BindingType::VaryingOutput: + break; + default: + SLANG_ASSERT(!"unsupported binding type."); + break; + } + 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) + { + 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( + RendererBase* renderer, + slang::TypeLayoutReflection* elementType, + ShaderObjectLayoutImpl** outLayout) + { + Builder builder(renderer); + builder.setElementTypeLayout(elementType); + return builder.build(outLayout); + } + + 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 getTextureCount() { return m_textureCount; } + Index getImageCount() { return m_imageCount; } + Index getStorageBufferCount() { return m_storageBufferCount; } + 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_textureCount = builder->m_textureCount; + m_imageCount = builder->m_imageCount; + m_storageBufferCount = builder->m_storageBufferCount; + m_subObjectCount = builder->m_subObjectCount; + m_subObjectRanges = builder->m_subObjectRanges; + return SLANG_OK; + } + + List<BindingRangeInfo> m_bindingRanges; + Index m_textureCount = 0; + Index m_imageCount = 0; + Index m_storageBufferCount = 0; + Index m_subObjectCount = 0; + List<SubObjectRangeInfo> m_subObjectRanges; + }; + + class RootShaderObjectLayoutImpl : public ShaderObjectLayoutImpl + { + typedef ShaderObjectLayoutImpl Super; + + public: + struct EntryPointInfo + { + RefPtr<ShaderObjectLayoutImpl> layout; + }; + + struct Builder : Super::Builder + { + Builder( + RendererBase* renderer, + slang::IComponentType* program, + slang::ProgramLayout* programLayout) + : Super::Builder(renderer) + , m_program(program) + , m_programLayout(programLayout) + {} + + Result build(RootShaderObjectLayoutImpl** outLayout) + { + RefPtr<RootShaderObjectLayoutImpl> layout = new RootShaderObjectLayoutImpl(); + SLANG_RETURN_ON_FAIL(layout->_init(this)); + + *outLayout = layout.detach(); + return SLANG_OK; + } + + void addGlobalParams(slang::VariableLayoutReflection* globalsLayout) + { + setElementTypeLayout(globalsLayout->getTypeLayout()); + } + + void addEntryPoint(SlangStage stage, ShaderObjectLayoutImpl* entryPointLayout) + { + EntryPointInfo info; + info.layout = entryPointLayout; + m_entryPoints.add(info); + } + + slang::IComponentType* m_program; + slang::ProgramLayout* m_programLayout; + List<EntryPointInfo> m_entryPoints; + }; + + EntryPointInfo& getEntryPoint(Index index) { return m_entryPoints[index]; } + + List<EntryPointInfo>& getEntryPoints() { return m_entryPoints; } + + static Result create( + RendererBase* renderer, + slang::IComponentType* program, + slang::ProgramLayout* programLayout, + RootShaderObjectLayoutImpl** outLayout) + { + RootShaderObjectLayoutImpl::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); + RefPtr<ShaderObjectLayoutImpl> entryPointLayout; + SLANG_RETURN_ON_FAIL(ShaderObjectLayoutImpl::createForElementType( + renderer, slangEntryPoint->getTypeLayout(), entryPointLayout.writeRef())); + builder.addEntryPoint(slangEntryPoint->getStage(), 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; + return SLANG_OK; + } + + ComPtr<slang::IComponentType> m_program; + slang::ProgramLayout* m_programLayout = nullptr; + + List<EntryPointInfo> m_entryPoints; + }; + + class ShaderObjectImpl : public ShaderObjectBase + { + public: + static Result create( + IDevice* device, + ShaderObjectLayoutImpl* layout, + ShaderObjectImpl** outShaderObject) + { + auto object = ComPtr<ShaderObjectImpl>(new ShaderObjectImpl()); + 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; + } + + 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) + { + 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<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 + { + 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; + return SLANG_OK; + } + + 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); + + auto resourceViewImpl = static_cast<ResourceViewImpl*>(resourceView); + switch (bindingRange.bindingType) + { + case slang::BindingType::MutableRawBuffer: + case slang::BindingType::MutableTypedBuffer: + case slang::BindingType::RawBuffer: + case slang::BindingType::TypedBuffer: + m_storageBuffers[bindingRange.baseIndex + offset.bindingArrayIndex] = static_cast<BufferViewImpl*>(resourceView); + break; + case slang::BindingType::MutableTexture: + m_images[bindingRange.baseIndex + offset.bindingArrayIndex] = static_cast<TextureViewImpl*>(resourceView); + break; + case slang::BindingType::Texture: + m_textures[bindingRange.baseIndex + offset.bindingArrayIndex] = static_cast<TextureViewImpl*>(resourceView); + m_samplers[bindingRange.baseIndex + offset.bindingArrayIndex] = nullptr; + break; + } + 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); + m_textures[bindingRange.baseIndex + offset.bindingArrayIndex] = static_cast<TextureViewImpl*>(textureView); + m_samplers[bindingRange.baseIndex + offset.bindingArrayIndex] = 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 ProgramVars; + + 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); + } + + m_samplers.setCount(layout->getTextureCount()); + m_textures.setCount(layout->getTextureCount()); + m_images.setCount(layout->getImageCount()); + m_storageBuffers.setCount(layout->getStorageBufferCount()); + + // 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<ShaderObjectImpl> subObject; + SLANG_RETURN_ON_FAIL( + ShaderObjectImpl::create(device, subObjectLayout, subObject.writeRef())); + m_objects[bindingRangeInfo.baseIndex + i] = subObject; + } + } + + return SLANG_OK; + } + + /// Write the uniform/ordinary data of this object into the given `dest` buffer at the given `offset` + Result _writeOrdinaryData( + GLDevice* device, + BufferResourceImpl* buffer, + size_t offset, + size_t destSize, + ShaderObjectLayoutImpl* specializedLayout) + { + auto src = m_ordinaryData.getBuffer(); + auto srcSize = size_t(m_ordinaryData.getCount()); + + SLANG_ASSERT(srcSize <= destSize); + + device->uploadBufferData(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<ShaderObjectLayoutImpl> subObjectLayout; + SLANG_RETURN_ON_FAIL(subObject->_getSpecializedLayout(subObjectLayout.writeRef())); + + auto subObjectOffset = subObjectRangePendingDataOffset + i * subObjectRangePendingDataStride; + + subObject->_writeOrdinaryData(device, 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(ShaderObjectLayoutImpl* specializedLayout, Index subObjectRangeIndex) { return 0; } + size_t _getSubObjectRangePendingDataStride(ShaderObjectLayoutImpl* specializedLayout, Index subObjectRangeIndex) { return 0; } + + /// Ensure that the `m_ordinaryDataBuffer` has been created, if it is needed + Result _ensureOrdinaryDataBufferCreatedIfNeeded(GLDevice* device) + { + // 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. + // + + ComPtr<IBufferResource> bufferResourcePtr; + IBufferResource::Desc bufferDesc; + bufferDesc.init(specializedOrdinaryDataSize); + bufferDesc.cpuAccessFlags |= IResource::AccessFlag::Write; + SLANG_RETURN_ON_FAIL(device->createBufferResource( + IResource::Usage::ConstantBuffer, bufferDesc, nullptr, bufferResourcePtr.writeRef())); + m_ordinaryDataBuffer = static_cast<BufferResourceImpl*>(bufferResourcePtr.get()); + + // 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(device, m_ordinaryDataBuffer, 0, specializedOrdinaryDataSize, specializedLayout)); + + return SLANG_OK; + } + + /// Bind the buffer for ordinary/uniform data, if needed + Result _bindOrdinaryDataBufferIfNeeded( + GLDevice* device, + RootBindingState* bindingState) + { + // We start by ensuring that the buffer is created, if it is needed. + // + SLANG_RETURN_ON_FAIL(_ensureOrdinaryDataBufferCreatedIfNeeded(device)); + + // If we did indeed need/create a buffer, then we must bind it + // into root binding state. + // + if (m_ordinaryDataBuffer) + { + bindingState->uniformBufferBindings.add(m_ordinaryDataBuffer->m_handle); + } + + return SLANG_OK; + } + public: + virtual Result bindObject(GLDevice* device, RootBindingState* bindingState) + { + ShaderObjectLayoutImpl* layout = getLayout(); + + Index baseRangeIndex = 0; + SLANG_RETURN_ON_FAIL(_bindOrdinaryDataBufferIfNeeded(device, bindingState)); + + for (auto sampler : m_samplers) + bindingState->samplerBindings.add(sampler ? sampler->m_samplerID : 0); + + bindingState->textureBindings.addRange(m_textures); + bindingState->imageBindings.addRange(m_images); + + for (auto buffer : m_storageBuffers) + bindingState->storageBufferBindings.add(buffer ? buffer->m_bufferID : 0); + + for (auto subObject : m_objects) + subObject->bindObject(device, bindingState); + + return SLANG_OK; + } + + /// Any "ordinary" / uniform data for this object + List<char> m_ordinaryData; + + List<RefPtr<TextureViewImpl>> m_textures; + + List<RefPtr<TextureViewImpl>> m_images; + + List<RefPtr<SamplerStateImpl>> m_samplers; + + List<RefPtr<BufferViewImpl>> m_storageBuffers; + + 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()` + RefPtr<BufferResourceImpl> 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; + }; + + class RootShaderObjectImpl : public ShaderObjectImpl + { + typedef ShaderObjectImpl Super; + + public: + static Result create(IDevice* device, RootShaderObjectLayoutImpl* layout, RootShaderObjectImpl** outShaderObject) + { + RefPtr<RootShaderObjectImpl> object = new RootShaderObjectImpl(); + SLANG_RETURN_ON_FAIL(object->init(device, layout)); + + *outShaderObject = object.detach(); + return SLANG_OK; + } + + RootShaderObjectLayoutImpl* getLayout() { return static_cast<RootShaderObjectLayoutImpl*>(m_layout.Ptr()); } + + 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(ShaderObjectImpl::collectSpecializationArgs(args)); + for (auto& entryPoint : m_entryPoints) + { + SLANG_RETURN_ON_FAIL(entryPoint->collectSpecializationArgs(args)); + } + return SLANG_OK; + } + + protected: + virtual Result bindObject(GLDevice* device, RootBindingState* bindingState) override + { + SLANG_RETURN_ON_FAIL(Super::bindObject(device, bindingState)); + + auto entryPointCount = m_entryPoints.getCount(); + for (Index i = 0; i < entryPointCount; ++i) + { + auto entryPoint = m_entryPoints[i]; + SLANG_RETURN_ON_FAIL(entryPoint->bindObject(device, bindingState)); + } + + return SLANG_OK; + } + + Result init(IDevice* device, RootShaderObjectLayoutImpl* layout) + { + SLANG_RETURN_ON_FAIL(Super::init(device, layout)); + + for (auto entryPointInfo : layout->getEntryPoints()) + { + RefPtr<ShaderObjectImpl> entryPoint; + SLANG_RETURN_ON_FAIL( + ShaderObjectImpl::create(device, entryPointInfo.layout, entryPoint.writeRef())); + m_entryPoints.add(entryPoint); + } + + return SLANG_OK; + } + + Result _createSpecializedLayout(ShaderObjectLayoutImpl** 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<RootShaderObjectLayoutImpl> specializedLayout; + RootShaderObjectLayoutImpl::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<ShaderObjectImpl>> m_entryPoints; + }; + enum class GlPixelFormat { Unknown, @@ -742,12 +1780,15 @@ public: RefPtr<FramebufferImpl> m_currentFramebuffer; RefPtr<WeakSink<GLDevice> > m_weakRenderer; - RefPtr<DescriptorSetImpl> m_boundDescriptorSets[kMaxDescriptorSetCount]; + RootBindingState m_rootBindingState; GLenum m_boundPrimitiveTopology = GL_TRIANGLES; GLuint m_boundVertexStreamBuffers[kMaxVertexStreams]; UInt m_boundVertexStreamStrides[kMaxVertexStreams]; UInt m_boundVertexStreamOffsets[kMaxVertexStreams]; + GLuint m_boundIndexBuffer = 0; + UInt m_boundIndexBufferOffset = 0; + UInt m_boundIndexBufferSize = 0; Desc m_desc; WindowHandle m_windowHandle; @@ -857,8 +1898,6 @@ void GLDevice::flushStateForDraw() (GLsizei)m_currentFramebuffer->m_drawBuffers.getCount(), m_currentFramebuffer->m_drawBuffers.getArrayView().getBuffer()); } - - glBindVertexArray(m_vao); auto inputLayout = m_currentPipelineState->m_inputLayout.Ptr(); auto attrCount = Index(inputLayout->m_attributeCount); for (Index ii = 0; ii < attrCount; ++ii) @@ -883,69 +1922,9 @@ void GLDevice::flushStateForDraw() { glDisableVertexAttribArray((GLuint)ii); } - // Next bind the descriptor sets as required by the layout - auto pipelineLayout = - static_cast<PipelineLayoutImpl*>(m_currentPipelineState->m_pipelineLayout.get()); - auto descriptorSetCount = pipelineLayout->m_sets.getCount(); - for(Index ii = 0; ii < descriptorSetCount; ++ii) + if (m_boundIndexBuffer) { - auto descriptorSet = m_boundDescriptorSets[ii]; - auto descriptorSetInfo = pipelineLayout->m_sets[ii]; - auto descriptorSetLayout = descriptorSetInfo.layout; - - // TODO: need to validate that `descriptorSet->m_layout` matches - // `descriptorSetLayout`. - - { - // First we will bind any uniform buffers that were specified. - - auto slotTypeIndex = int(GLDescriptorSlotType::ConstantBuffer); - auto count = descriptorSetLayout->m_counts[slotTypeIndex]; - auto baseIndex = descriptorSetInfo.baseArrayIndex[slotTypeIndex]; - - for(Int ii = 0; ii < count; ++ii) - { - auto bufferImpl = descriptorSet->m_constantBuffers[ii]; - glBindBufferBase(GL_UNIFORM_BUFFER, GLuint(ii), bufferImpl->m_handle); - } - } - - { - // Then we will bind any storage buffers that were specified. - - auto slotTypeIndex = int(GLDescriptorSlotType::StorageBuffer); - auto count = descriptorSetLayout->m_counts[slotTypeIndex]; - auto baseIndex = descriptorSetInfo.baseArrayIndex[slotTypeIndex]; - - for (Int ii = 0; ii < count; ++ii) - { - auto bufferImpl = descriptorSet->m_storageBuffers[ii]; - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, GLuint(ii), bufferImpl->m_handle); - } - } - - { - // Next we will bind any combined texture/sampler slots. - - auto slotTypeIndex = int(GLDescriptorSlotType::CombinedTextureSampler); - auto count = descriptorSetLayout->m_counts[slotTypeIndex]; - auto baseIndex = descriptorSetInfo.baseArrayIndex[slotTypeIndex]; - - // TODO: We should be able to use a single call to glBindTextures here, - // rather than a loop. This would also eliminate the need to retain - // the appropriate target (e.g., `GL_TEXTURE_2D` for binding). - - for(Int ii = 0; ii < count; ++ii) - { - auto textureViewImpl = descriptorSet->m_textures[ii]; - auto samplerImpl = descriptorSet->m_samplers[ii]; - - glActiveTexture(GLuint(GL_TEXTURE0 + ii)); - glBindTexture(GL_TEXTURE_2D, textureViewImpl->m_textureID); - - glBindSampler(GLuint(baseIndex + ii), samplerImpl->m_samplerID); - } - } + glBindBufferRange(GL_ELEMENT_ARRAY_BUFFER, 0, m_boundIndexBuffer, m_boundIndexBufferOffset, m_boundIndexBufferSize); } } @@ -1174,7 +2153,7 @@ SLANG_NO_THROW Result SLANG_MCALL GLDevice::initialize(const Desc& desc) { SLANG_RETURN_ON_FAIL(slangContext.initialize(desc.slang, SLANG_GLSL, "glsl_440")); - SLANG_RETURN_ON_FAIL(GraphicsAPIRenderer::initialize(desc)); + SLANG_RETURN_ON_FAIL(RendererBase::initialize(desc)); // Initialize DeviceInfo { @@ -1262,6 +2241,7 @@ SLANG_NO_THROW Result SLANG_MCALL GLDevice::initialize(const Desc& desc) return SLANG_FAIL; glGenVertexArrays(1, &m_vao); + glBindVertexArray(m_vao); if (glDebugMessageCallback) { @@ -1716,6 +2696,23 @@ SLANG_NO_THROW Result SLANG_MCALL GLDevice::createTextureView( RefPtr<TextureViewImpl> viewImpl = new TextureViewImpl(); viewImpl->m_resource = resourceImpl; viewImpl->m_textureID = resourceImpl->m_handle; + viewImpl->type = ResourceViewImpl::Type::Texture; + viewImpl->m_target = resourceImpl->m_target; + if (desc.type == IResourceView::Type::ShaderResource) + { + viewImpl->access = GL_READ_ONLY; + viewImpl->textureViewType = TextureViewImpl::TextureViewType::Texture; + } + else + { + viewImpl->access = GL_READ_WRITE; + viewImpl->textureViewType = TextureViewImpl::TextureViewType::Image; + } + const GlPixelFormatInfo& info = s_pixelFormatInfos[int(_getGlPixelFormat(desc.format))]; + viewImpl->format = info.internalFormat; + viewImpl->layered = GL_TRUE; + viewImpl->level = 0; + viewImpl->layer = 0; *outView = viewImpl.detach(); return SLANG_OK; } @@ -1728,6 +2725,7 @@ SLANG_NO_THROW Result SLANG_MCALL GLDevice::createBufferView( // TODO: actually do something? RefPtr<BufferViewImpl> viewImpl = new BufferViewImpl(); + viewImpl->type = ResourceViewImpl::Type::Buffer; viewImpl->m_resource = resourceImpl; viewImpl->m_bufferID = resourceImpl->m_handle; *outView = viewImpl.detach(); @@ -1820,6 +2818,10 @@ SLANG_NO_THROW void SLANG_MCALL GLDevice::setVertexBuffers( SLANG_NO_THROW void SLANG_MCALL GLDevice::setIndexBuffer(IBufferResource* buffer, Format indexFormat, UInt offset) { + auto bufferImpl = static_cast<BufferResourceImpl*>(buffer); + m_boundIndexBuffer = bufferImpl->m_handle; + m_boundIndexBufferOffset = offset; + m_boundIndexBufferSize = bufferImpl->m_size; } SLANG_NO_THROW void SLANG_MCALL GLDevice::setViewports(UInt count, Viewport const* viewports) @@ -1883,7 +2885,14 @@ SLANG_NO_THROW void SLANG_MCALL GLDevice::draw(UInt vertexCount, UInt startVerte SLANG_NO_THROW void SLANG_MCALL GLDevice::drawIndexed(UInt indexCount, UInt startIndex, UInt baseVertex) { - assert(!"unimplemented"); + flushStateForDraw(); + + glDrawElementsBaseVertex( + m_boundPrimitiveTopology, + (GLsizei)indexCount, + GL_UNSIGNED_INT, + (GLvoid*)(startIndex*sizeof(uint32_t)), + (GLint)baseVertex); } SLANG_NO_THROW void SLANG_MCALL GLDevice::dispatchCompute(int x, int y, int z) @@ -1891,239 +2900,58 @@ SLANG_NO_THROW void SLANG_MCALL GLDevice::dispatchCompute(int x, int y, int z) glDispatchCompute(x, y, z); } -SLANG_NO_THROW void SLANG_MCALL GLDevice::DescriptorSetImpl::setConstantBuffer( - UInt range, UInt index, IBufferResource* buffer) -{ - auto resourceImpl = (BufferResourceImpl*) buffer; - - auto layout = m_layout; - auto rangeInfo = layout->m_ranges[range]; - auto arrayIndex = rangeInfo.arrayIndex + index; - - m_constantBuffers[arrayIndex] = resourceImpl; -} - -SLANG_NO_THROW void SLANG_MCALL - GLDevice::DescriptorSetImpl::setResource(UInt range, UInt index, IResourceView* view) -{ - auto viewImpl = static_cast<ResourceViewImpl*>(view); - - auto layout = m_layout; - auto rangeInfo = layout->m_ranges[range]; - auto arrayIndex = rangeInfo.arrayIndex + index; - - switch (rangeInfo.type) - { - case GLDescriptorSlotType::StorageBuffer: - m_storageBuffers[arrayIndex] = static_cast<BufferViewImpl*>(viewImpl)->m_resource; - break; - default: - assert(!"unimplemented"); - break; - } -} - -SLANG_NO_THROW void SLANG_MCALL - GLDevice::DescriptorSetImpl::setSampler(UInt range, UInt index, ISamplerState* sampler) -{ - assert(!"unsupported"); -} - -SLANG_NO_THROW void SLANG_MCALL GLDevice::DescriptorSetImpl::setCombinedTextureSampler( - UInt range, - UInt index, - IResourceView* textureView, - ISamplerState* sampler) -{ - auto viewImpl = (TextureViewImpl*) textureView; - auto samplerImpl = (SamplerStateImpl*) sampler; - - auto layout = m_layout; - auto rangeInfo = layout->m_ranges[range]; - auto arrayIndex = rangeInfo.arrayIndex + index; - - m_textures[arrayIndex] = viewImpl; - m_samplers[arrayIndex] = samplerImpl; -} - -SLANG_NO_THROW void SLANG_MCALL GLDevice::DescriptorSetImpl::setRootConstants( - UInt range, - UInt offset, - UInt size, - void const* data) -{ - SLANG_UNEXPECTED("unimplemented: setRootConstants for GLDevice"); -} - -SLANG_NO_THROW void SLANG_MCALL GLDevice::setDescriptorSet( - PipelineType pipelineType, IPipelineLayout* layout, UInt index, IDescriptorSet* descriptorSet) -{ - auto descriptorSetImpl = (DescriptorSetImpl*)descriptorSet; - - // TODO: can we just bind things immediately here, rather than shadowing the state? - - m_boundDescriptorSets[index] = descriptorSetImpl; -} - -SLANG_NO_THROW Result SLANG_MCALL GLDevice::createDescriptorSetLayout( - const IDescriptorSetLayout::Desc& desc, IDescriptorSetLayout** outLayout) -{ - RefPtr<DescriptorSetLayoutImpl> layoutImpl = new DescriptorSetLayoutImpl(); - - Int counts[int(GLDescriptorSlotType::CountOf)] = { 0, }; - - Int rangeCount = desc.slotRangeCount; - for(Int rr = 0; rr < rangeCount; ++rr) - { - auto rangeDesc = desc.slotRanges[rr]; - DescriptorSetLayoutImpl::RangeInfo rangeInfo; - - GLDescriptorSlotType glSlotType; - switch( rangeDesc.type ) - { - default: - assert(!"unsupported"); - break; - - case DescriptorSlotType::StorageBuffer: - glSlotType = GLDescriptorSlotType::StorageBuffer; - break; - case DescriptorSlotType::CombinedImageSampler: - glSlotType = GLDescriptorSlotType::CombinedTextureSampler; - break; - - case DescriptorSlotType::RootConstant: - rangeDesc.count = 1; - case DescriptorSlotType::UniformBuffer: - case DescriptorSlotType::DynamicUniformBuffer: - glSlotType = GLDescriptorSlotType::ConstantBuffer; - break; - } - - rangeInfo.type = glSlotType; - rangeInfo.arrayIndex = counts[int(glSlotType)]; - counts[int(glSlotType)] += rangeDesc.count; - - layoutImpl->m_ranges.add(rangeInfo); - } - - for( Int ii = 0; ii < int(GLDescriptorSlotType::CountOf); ++ii ) - { - layoutImpl->m_counts[ii] = counts[ii]; - } - - *outLayout = layoutImpl.detach(); - return SLANG_OK; -} - -SLANG_NO_THROW Result SLANG_MCALL - GLDevice::createPipelineLayout(const IPipelineLayout::Desc& desc, IPipelineLayout** outLayout) -{ - RefPtr<PipelineLayoutImpl> layoutImpl = new PipelineLayoutImpl(); - - static const int kSlotTypeCount = int(GLDescriptorSlotType::CountOf); - Int counts[kSlotTypeCount] = { 0, }; - - Int setCount = desc.descriptorSetCount; - for( Int ii = 0; ii < setCount; ++ii ) - { - auto setLayout = (DescriptorSetLayoutImpl*) desc.descriptorSets[ii].layout; - - PipelineLayoutImpl::DescriptorSetInfo setInfo; - setInfo.layout = setLayout; - - for( Int ii = 0; ii < int(GLDescriptorSlotType::CountOf); ++ii ) - { - setInfo.baseArrayIndex[ii] = counts[ii]; - counts[ii] += setLayout->m_counts[ii]; - } - - layoutImpl->m_sets.add(setInfo); - } - - *outLayout = layoutImpl.detach(); - return SLANG_OK; -} - -SLANG_NO_THROW Result SLANG_MCALL - GLDevice::createDescriptorSet(IDescriptorSetLayout* layout, IDescriptorSet::Flag::Enum flag, IDescriptorSet** outDescriptorSet) -{ - SLANG_UNUSED(flag); - - auto layoutImpl = (DescriptorSetLayoutImpl*) layout; - - RefPtr<DescriptorSetImpl> descriptorSetImpl = new DescriptorSetImpl(); - - descriptorSetImpl->m_layout = layoutImpl; - - // TODO: storage for the arrays of bound objects could be tail allocated - // as part of the descriptor set, with offsets pre-computed in the - // descriptor set layout. - - { - auto slotTypeIndex = int(GLDescriptorSlotType::ConstantBuffer); - auto slotCount = layoutImpl->m_counts[slotTypeIndex]; - descriptorSetImpl->m_constantBuffers.setCount(slotCount); - } - { - auto slotTypeIndex = int(GLDescriptorSlotType::StorageBuffer); - auto slotCount = layoutImpl->m_counts[slotTypeIndex]; - descriptorSetImpl->m_storageBuffers.setCount(slotCount); - } - { - auto slotTypeIndex = int(GLDescriptorSlotType::CombinedTextureSampler); - auto slotCount = layoutImpl->m_counts[slotTypeIndex]; - - descriptorSetImpl->m_textures.setCount(slotCount); - descriptorSetImpl->m_samplers.setCount(slotCount); - } - - *outDescriptorSet = descriptorSetImpl.detach(); - return SLANG_OK; -} - Result GLDevice::createProgram(const IShaderProgram::Desc& desc, IShaderProgram** outProgram) { - if (desc.slangProgram && desc.slangProgram->getSpecializationParamCount() != 0) + if (desc.slangProgram->getSpecializationParamCount() != 0) { // For a specializable program, we don't invoke any actual slang compilation yet. RefPtr<ShaderProgramImpl> shaderProgram = new ShaderProgramImpl(m_weakRenderer, 0); - initProgramCommon(shaderProgram, desc); + shaderProgram->slangProgram = desc.slangProgram; *outProgram = shaderProgram.detach(); return SLANG_OK; } - if( desc.kernelCount == 0 ) - { - return createProgramFromSlang(this, desc, outProgram); - } - auto programID = glCreateProgram(); - if(desc.pipelineType == PipelineType::Compute ) - { - auto computeKernel = desc.findKernel(StageType::Compute); - auto computeShaderID = loadShader(GL_COMPUTE_SHADER, (char const*) computeKernel->codeBegin); - glAttachShader(programID, computeShaderID); - glLinkProgram(programID); - glDeleteShader(computeShaderID); - } - else - { - auto vertexKernel = desc.findKernel(StageType::Vertex); - auto fragmentKernel = desc.findKernel(StageType::Fragment); - - auto vertexShaderID = loadShader(GL_VERTEX_SHADER, (char const*) vertexKernel->codeBegin); - auto fragmentShaderID = loadShader(GL_FRAGMENT_SHADER, (char const*) fragmentKernel->codeBegin); - - glAttachShader(programID, vertexShaderID); - glAttachShader(programID, fragmentShaderID); - - - glLinkProgram(programID); - - glDeleteShader(vertexShaderID); - glDeleteShader(fragmentShaderID); + auto programLayout = desc.slangProgram->getLayout(); + ShortList<GLuint> shaderIDs; + for (SlangUInt i = 0; i < programLayout->getEntryPointCount(); i++) + { + ComPtr<ISlangBlob> kernelCode; + ComPtr<ISlangBlob> diagnostics; + SLANG_RETURN_ON_FAIL(desc.slangProgram->getEntryPointCode(i, 0, kernelCode.writeRef(), diagnostics.writeRef())); + GLenum glShaderType = 0; + auto stage = programLayout->getEntryPointByIndex(i)->getStage(); + switch (stage) + { + case SLANG_STAGE_COMPUTE: + glShaderType = GL_COMPUTE_SHADER; + break; + case SLANG_STAGE_VERTEX: + glShaderType = GL_VERTEX_SHADER; + break; + case SLANG_STAGE_FRAGMENT: + glShaderType = GL_FRAGMENT_SHADER; + break; + case SLANG_STAGE_GEOMETRY: + glShaderType = GL_GEOMETRY_SHADER; + break; + case SLANG_STAGE_DOMAIN: + glShaderType = GL_TESS_CONTROL_SHADER; + break; + case SLANG_STAGE_HULL: + glShaderType = GL_TESS_EVALUATION_SHADER; + break; + default: + SLANG_ASSERT(!"unsupported shader type."); + break; + } + auto shaderID = loadShader(glShaderType, (char const*)kernelCode->getBufferPointer()); + shaderIDs.add(shaderID); + glAttachShader(programID, shaderID); } + glLinkProgram(programID); + for (auto shaderID : shaderIDs) + glDeleteShader(shaderID); GLint success = GL_FALSE; glGetProgramiv(programID, GL_LINK_STATUS, &success); if (!success) @@ -2148,7 +2976,7 @@ Result GLDevice::createProgram(const IShaderProgram::Desc& desc, IShaderProgram* } RefPtr<ShaderProgramImpl> program = new ShaderProgramImpl(m_weakRenderer, programID); - initProgramCommon(program, desc); + program->slangProgram = desc.slangProgram; *outProgram = program.detach(); return SLANG_OK; } @@ -2156,7 +2984,6 @@ Result GLDevice::createProgram(const IShaderProgram::Desc& desc, IShaderProgram* Result GLDevice::createGraphicsPipelineState(const GraphicsPipelineStateDesc& inDesc, IPipelineState** outState) { GraphicsPipelineStateDesc desc = inDesc; - preparePipelineDesc(desc); auto programImpl = (ShaderProgramImpl*) desc.program; auto inputLayoutImpl = (InputLayoutImpl*) desc.inputLayout; @@ -2171,19 +2998,83 @@ Result GLDevice::createGraphicsPipelineState(const GraphicsPipelineStateDesc& in Result GLDevice::createComputePipelineState(const ComputePipelineStateDesc& inDesc, IPipelineState** outState) { ComputePipelineStateDesc desc = inDesc; - preparePipelineDesc(desc); auto programImpl = (ShaderProgramImpl*) desc.program; - auto pipelineLayoutImpl = (PipelineLayoutImpl*) desc.pipelineLayout; RefPtr<PipelineStateImpl> pipelineStateImpl = new PipelineStateImpl(); pipelineStateImpl->m_program = programImpl; - pipelineStateImpl->m_pipelineLayout = pipelineLayoutImpl; pipelineStateImpl->init(desc); *outState = pipelineStateImpl.detach(); return SLANG_OK; } +Result GLDevice::createShaderObjectLayout( + slang::TypeLayoutReflection* typeLayout, + ShaderObjectLayoutBase** outLayout) +{ + RefPtr<ShaderObjectLayoutImpl> layout; + SLANG_RETURN_ON_FAIL(ShaderObjectLayoutImpl::createForElementType( + this, typeLayout, layout.writeRef())); + *outLayout = layout.detach(); + return SLANG_OK; +} -} // renderer_test +Result GLDevice::createShaderObject(ShaderObjectLayoutBase* layout, IShaderObject** outObject) +{ + RefPtr<ShaderObjectImpl> shaderObject; + SLANG_RETURN_ON_FAIL(ShaderObjectImpl::create(this, + static_cast<ShaderObjectLayoutImpl*>(layout), shaderObject.writeRef())); + *outObject = shaderObject.detach(); + return SLANG_OK; +} +Result GLDevice::createRootShaderObject(IShaderProgram* program, IShaderObject** outObject) +{ + auto programImpl = static_cast<ShaderProgramImpl*>(program); + RefPtr<RootShaderObjectImpl> shaderObject; + RefPtr<RootShaderObjectLayoutImpl> rootLayout; + SLANG_RETURN_ON_FAIL(RootShaderObjectLayoutImpl::create( + this, programImpl->slangProgram, programImpl->slangProgram->getLayout(), rootLayout.writeRef())); + SLANG_RETURN_ON_FAIL(RootShaderObjectImpl::create( + this, rootLayout.Ptr(), shaderObject.writeRef())); + *outObject = shaderObject.detach(); + return SLANG_OK; +} + +void GLDevice::bindRootShaderObject(PipelineType pipelineType, IShaderObject* shaderObject) +{ + RootShaderObjectImpl* rootShaderObjectImpl = static_cast<RootShaderObjectImpl*>(shaderObject); + RefPtr<PipelineStateBase> specializedPipeline; + maybeSpecializePipeline(m_currentPipelineState, rootShaderObjectImpl, specializedPipeline); + setPipelineState(specializedPipeline.Ptr()); + + m_rootBindingState.imageBindings.clear(); + m_rootBindingState.samplerBindings.clear(); + m_rootBindingState.textureBindings.clear(); + m_rootBindingState.storageBufferBindings.clear(); + m_rootBindingState.uniformBufferBindings.clear(); + static_cast<ShaderObjectImpl*>(shaderObject)->bindObject(this, &m_rootBindingState); + for (Index i = 0; i < m_rootBindingState.imageBindings.getCount(); i++) + { + auto binding = m_rootBindingState.imageBindings[i]; + glBindImageTexture((GLuint)i, binding->m_textureID, binding->level, binding->layered, binding->layer, binding->access, binding->format); + } + for (Index i = 0; i < m_rootBindingState.textureBindings.getCount(); i++) + { + glActiveTexture((GLenum)(GL_TEXTURE0 + i)); + auto binding = m_rootBindingState.textureBindings[i]; + if (binding) + glBindTexture(binding->m_target, binding->m_textureID); + glBindSampler((GLuint)i, m_rootBindingState.samplerBindings[i]); + } + for (Index i = 0; i < m_rootBindingState.storageBufferBindings.getCount(); i++) + { + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, (GLuint)i, m_rootBindingState.storageBufferBindings[i]); + } + for (Index i = 0; i < m_rootBindingState.uniformBufferBindings.getCount(); i++) + { + glBindBufferBase(GL_UNIFORM_BUFFER, (GLuint)i, m_rootBindingState.uniformBufferBindings[i]); + } +} + +} // renderer_test diff --git a/tools/gfx/render-graphics-common.cpp b/tools/gfx/render-graphics-common.cpp index 5ae148ea0..b734bf391 100644 --- a/tools/gfx/render-graphics-common.cpp +++ b/tools/gfx/render-graphics-common.cpp @@ -226,23 +226,6 @@ public: bindingRangeInfo.rangeIndexInDescriptorSet = rangeIndexInDescriptorSet; m_bindingRanges.add(bindingRangeInfo); - -#if 0 - SlangInt binding = typeLayout->getBindingRangeIndexOffset(r); - SlangInt space = typeLayout->getBindingRangeSpaceOffset(r); - SlangInt subObjectRangeIndex = typeLayout->getBindingRangeSubObjectRangeIndex(r); - - DescriptorSetLayout::SlotRangeDesc slotRange; - slotRange.type = _mapDescriptorType(slangBindingType); - slotRange.count = count; - slotRange.binding = binding; - - Index descriptorSetIndex = findOrAddDescriptorSet(space); - RefPtr<DescriptorSetBuildInfo> descriptorSetInfo = m_descriptorSetInfos[descriptorSetIndex]; - - Index slotRangeIndex = descriptorSetInfo->slotRanges.getCount(); - descriptorSetInfo->slotRanges.add(slotRange); -#endif } SlangInt subObjectRangeCount = typeLayout->getSubObjectRangeCount(); @@ -275,17 +258,6 @@ public: m_subObjectRanges.add(subObjectRange); } - -#if 0 - SlangInt subObjectRangeCount = typeLayout->getSubObjectRangeCount(); - for(SlangInt r = 0; r < subObjectRangeCount; ++r) - { - - // TODO: Still need a way to map the binding ranges for - // the sub-object over so that they can be used to - // set/get the sub-object as needed. - } -#endif return SLANG_OK; } diff --git a/tools/gfx/renderer-shared.cpp b/tools/gfx/renderer-shared.cpp index 1222768db..a628cc997 100644 --- a/tools/gfx/renderer-shared.cpp +++ b/tools/gfx/renderer-shared.cpp @@ -364,45 +364,6 @@ void ShaderCache::addSpecializedPipeline(PipelineKey key, Slang::ComPtr<IPipelin specializedPipelines[key] = specializedPipeline; } -struct ShaderBinaryEntryHeader -{ - StageType stage; - uint32_t nameLength; - uint32_t codeLength; -}; - -Result ShaderBinary::loadFromBlob(ISlangBlob* blob) -{ - MemoryStreamBase memStream(Slang::FileAccess::Read, blob->getBufferPointer(), blob->getBufferSize()); - uint32_t nameLength = 0; - ShaderBinaryEntryHeader header; - if (memStream.read(&header, sizeof(header)) != sizeof(header)) - return SLANG_FAIL; - const uint8_t* name = memStream.getContents().getBuffer() + memStream.getPosition(); - const uint8_t* code = name + header.nameLength; - entryPointName = reinterpret_cast<const char*>(name); - stage = header.stage; - source.addRange(code, header.codeLength); - return SLANG_OK; -} - -Result ShaderBinary::writeToBlob(ISlangBlob** outBlob) -{ - OwnedMemoryStream outStream(FileAccess::Write); - ShaderBinaryEntryHeader header; - header.stage = stage; - header.nameLength = static_cast<uint32_t>(entryPointName.getLength() + 1); - header.codeLength = static_cast<uint32_t>(source.getCount()); - outStream.write(&header, sizeof(header)); - outStream.write(entryPointName.getBuffer(), header.nameLength - 1); - uint8_t zeroTerminator = 0; - outStream.write(&zeroTerminator, 1); - outStream.write(source.getBuffer(), header.codeLength); - RefPtr<RawBlob> blob = new RawBlob(outStream.getContents().getBuffer(), outStream.getContents().getCount()); - *outBlob = blob.detach(); - return SLANG_OK; -} - void ShaderObjectLayoutBase::initBase(RendererBase* renderer, slang::TypeLayoutReflection* elementTypeLayout) { m_renderer = renderer; diff --git a/tools/gfx/renderer-shared.h b/tools/gfx/renderer-shared.h index 2a77dcb93..5ca5f064b 100644 --- a/tools/gfx/renderer-shared.h +++ b/tools/gfx/renderer-shared.h @@ -148,7 +148,7 @@ protected: RendererBase* m_renderer; slang::TypeLayoutReflection* m_elementTypeLayout = nullptr; ShaderComponentID m_componentID = 0; - +public: static slang::TypeLayoutReflection* _unwrapParameterGroups(slang::TypeLayoutReflection* typeLayout) { for (;;) @@ -285,16 +285,6 @@ protected: void initializeBase(const PipelineStateDesc& inDesc); }; -class ShaderBinary : public Slang::RefObject -{ -public: - Slang::List<uint8_t> source; - StageType stage; - Slang::String entryPointName; - Result loadFromBlob(ISlangBlob* blob); - Result writeToBlob(ISlangBlob** outBlob); -}; - struct ComponentKey { Slang::UnownedStringSlice typeName; diff --git a/tools/gfx/vulkan/render-vk.cpp b/tools/gfx/vulkan/render-vk.cpp index 1c263d505..21d437921 100644 --- a/tools/gfx/vulkan/render-vk.cpp +++ b/tools/gfx/vulkan/render-vk.cpp @@ -3154,8 +3154,8 @@ Result VKDevice::createTextureResource(IResource::Usage initialUsage, const ITex int subResourceIndex = subResourceCounter++; auto initSubresource = initData[subResourceIndex]; - const ptrdiff_t srcRowStride = initSubresource.strideY; - const ptrdiff_t srcLayerStride = initSubresource.strideZ; + const ptrdiff_t srcRowStride = (ptrdiff_t)initSubresource.strideY; + const ptrdiff_t srcLayerStride = (ptrdiff_t)initSubresource.strideZ; auto dstRowSizeInBytes = calcRowSize(desc.format, mipSize.width); auto numRows = calcNumRows(desc.format, mipSize.height); @@ -3923,6 +3923,8 @@ void VKDevice::DescriptorSetImpl::setResource(UInt range, UInt index, IResourceV auto descriptorType = rangeInfo.vkDescriptorType; auto viewImpl = (ResourceViewImpl*)view; + if (!viewImpl) + return; switch (viewImpl->m_type) { case ResourceViewImpl::ViewType::Texture: @@ -3993,7 +3995,8 @@ void VKDevice::DescriptorSetImpl::setSampler(UInt range, UInt index, ISamplerSta 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); |
