From 733b573963f34ad5f356d30babccb83e8ff9c3eb Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Thu, 26 Mar 2026 15:10:03 +0800 Subject: [PATCH] fix(rhi): make opengl descriptor binding set-aware --- .../XCEngine/RHI/OpenGL/OpenGLDescriptorSet.h | 3 + .../RHI/OpenGL/OpenGLPipelineLayout.h | 19 ++ engine/src/RHI/OpenGL/OpenGLCommandList.cpp | 15 +- engine/src/RHI/OpenGL/OpenGLDescriptorSet.cpp | 102 +++++++++- .../src/RHI/OpenGL/OpenGLPipelineLayout.cpp | 176 ++++++++++++++++++ tests/RHI/integration/sphere/main.cpp | 28 ++- tests/RHI/unit/test_pipeline_layout.cpp | 56 ++++++ tests/TEST_SPEC.md | 1 + 8 files changed, 379 insertions(+), 21 deletions(-) diff --git a/engine/include/XCEngine/RHI/OpenGL/OpenGLDescriptorSet.h b/engine/include/XCEngine/RHI/OpenGL/OpenGLDescriptorSet.h index 64f10933..1d043888 100644 --- a/engine/include/XCEngine/RHI/OpenGL/OpenGLDescriptorSet.h +++ b/engine/include/XCEngine/RHI/OpenGL/OpenGLDescriptorSet.h @@ -10,6 +10,7 @@ namespace XCEngine { namespace RHI { class OpenGLTextureUnitAllocator; +class OpenGLPipelineLayout; struct DescriptorBinding { uint32_t binding; @@ -30,6 +31,7 @@ public: void Bind() override; void Unbind() override; + void BindWithPipelineLayout(const OpenGLPipelineLayout* pipelineLayout, uint32_t setIndex); void Update(uint32_t offset, RHIResourceView* view) override; void UpdateSampler(uint32_t offset, RHISampler* sampler) override; @@ -48,6 +50,7 @@ public: private: DescriptorBinding* FindBinding(uint32_t binding); const DescriptorBinding* FindBinding(uint32_t binding) const; + void EnsureConstantBufferUploaded(); OpenGLTextureUnitAllocator* m_allocator; std::vector m_bindings; diff --git a/engine/include/XCEngine/RHI/OpenGL/OpenGLPipelineLayout.h b/engine/include/XCEngine/RHI/OpenGL/OpenGLPipelineLayout.h index 4b82fa9e..f287e440 100644 --- a/engine/include/XCEngine/RHI/OpenGL/OpenGLPipelineLayout.h +++ b/engine/include/XCEngine/RHI/OpenGL/OpenGLPipelineLayout.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include "../RHIPipelineLayout.h" @@ -9,6 +10,13 @@ namespace RHI { class OpenGLPipelineLayout : public RHIPipelineLayout { public: + struct SetBindingPointMapping { + std::unordered_map constantBufferBindingPoints; + std::unordered_map shaderResourceBindingPoints; + std::unordered_map unorderedAccessBindingPoints; + std::unordered_map samplerBindingPoints; + }; + OpenGLPipelineLayout() = default; ~OpenGLPipelineLayout() override = default; @@ -16,10 +24,21 @@ public: void Shutdown() override; void* GetNativeHandle() override { return m_initialized ? this : nullptr; } + bool UsesSetLayouts() const { return m_desc.setLayoutCount > 0 && m_desc.setLayouts != nullptr; } + uint32_t GetSetLayoutCount() const { return m_desc.setLayoutCount; } + bool HasConstantBufferBinding(uint32_t setIndex, uint32_t binding) const; + uint32_t GetConstantBufferBindingPoint(uint32_t setIndex, uint32_t binding) const; + bool HasShaderResourceBinding(uint32_t setIndex, uint32_t binding) const; + uint32_t GetShaderResourceBindingPoint(uint32_t setIndex, uint32_t binding) const; + bool HasUnorderedAccessBinding(uint32_t setIndex, uint32_t binding) const; + uint32_t GetUnorderedAccessBindingPoint(uint32_t setIndex, uint32_t binding) const; + bool HasSamplerBinding(uint32_t setIndex, uint32_t binding) const; + uint32_t GetSamplerBindingPoint(uint32_t setIndex, uint32_t binding) const; const RHIPipelineLayoutDesc& GetDesc() const { return m_desc; } private: RHIPipelineLayoutDesc m_desc = {}; + std::vector m_setBindingPointMappings; std::vector m_setLayouts; std::vector> m_setLayoutBindings; bool m_initialized = false; diff --git a/engine/src/RHI/OpenGL/OpenGLCommandList.cpp b/engine/src/RHI/OpenGL/OpenGLCommandList.cpp index 31aa0d94..eb89aede 100644 --- a/engine/src/RHI/OpenGL/OpenGLCommandList.cpp +++ b/engine/src/RHI/OpenGL/OpenGLCommandList.cpp @@ -1,6 +1,7 @@ #include "XCEngine/RHI/OpenGL/OpenGLCommandList.h" #include "XCEngine/RHI/OpenGL/OpenGLResourceView.h" #include "XCEngine/RHI/OpenGL/OpenGLPipelineState.h" +#include "XCEngine/RHI/OpenGL/OpenGLPipelineLayout.h" #include "XCEngine/RHI/OpenGL/OpenGLShader.h" #include "XCEngine/RHI/OpenGL/OpenGLFramebuffer.h" #include "XCEngine/RHI/OpenGL/OpenGLRenderPass.h" @@ -717,12 +718,13 @@ void OpenGLCommandList::SetGraphicsDescriptorSets( uint32_t count, RHIDescriptorSet** descriptorSets, RHIPipelineLayout* pipelineLayout) { - (void)firstSet; - (void)pipelineLayout; + OpenGLPipelineLayout* openGLPipelineLayout = static_cast(pipelineLayout); for (uint32_t i = 0; i < count; ++i) { if (descriptorSets[i] != nullptr) { - descriptorSets[i]->Bind(); + static_cast(descriptorSets[i])->BindWithPipelineLayout( + openGLPipelineLayout, + firstSet + i); } } } @@ -732,12 +734,13 @@ void OpenGLCommandList::SetComputeDescriptorSets( uint32_t count, RHIDescriptorSet** descriptorSets, RHIPipelineLayout* pipelineLayout) { - (void)firstSet; - (void)pipelineLayout; + OpenGLPipelineLayout* openGLPipelineLayout = static_cast(pipelineLayout); for (uint32_t i = 0; i < count; ++i) { if (descriptorSets[i] != nullptr) { - descriptorSets[i]->Bind(); + static_cast(descriptorSets[i])->BindWithPipelineLayout( + openGLPipelineLayout, + firstSet + i); } } } diff --git a/engine/src/RHI/OpenGL/OpenGLDescriptorSet.cpp b/engine/src/RHI/OpenGL/OpenGLDescriptorSet.cpp index a7a25221..18ad9b25 100644 --- a/engine/src/RHI/OpenGL/OpenGLDescriptorSet.cpp +++ b/engine/src/RHI/OpenGL/OpenGLDescriptorSet.cpp @@ -1,4 +1,5 @@ #include "XCEngine/RHI/OpenGL/OpenGLDescriptorSet.h" +#include "XCEngine/RHI/OpenGL/OpenGLPipelineLayout.h" #include "XCEngine/RHI/OpenGL/OpenGLTextureUnitAllocator.h" #include "XCEngine/RHI/OpenGL/OpenGLResourceView.h" #include "XCEngine/RHI/OpenGL/OpenGLSampler.h" @@ -7,6 +8,32 @@ namespace XCEngine { namespace RHI { +namespace { + +uint32_t ResolveBindingPoint( + const OpenGLPipelineLayout* pipelineLayout, + uint32_t setIndex, + const DescriptorBinding& binding) { + if (pipelineLayout == nullptr || !pipelineLayout->UsesSetLayouts()) { + return binding.binding; + } + + switch (binding.type) { + case DescriptorType::CBV: + return pipelineLayout->GetConstantBufferBindingPoint(setIndex, binding.binding); + case DescriptorType::SRV: + return pipelineLayout->GetShaderResourceBindingPoint(setIndex, binding.binding); + case DescriptorType::UAV: + return pipelineLayout->GetUnorderedAccessBindingPoint(setIndex, binding.binding); + case DescriptorType::Sampler: + return pipelineLayout->GetSamplerBindingPoint(setIndex, binding.binding); + default: + return UINT32_MAX; + } +} + +} // namespace + OpenGLDescriptorSet::OpenGLDescriptorSet() : m_allocator(nullptr) , m_layoutBindings(nullptr) @@ -97,6 +124,7 @@ void OpenGLDescriptorSet::Shutdown() { m_bindings.clear(); m_allocator = nullptr; + m_bound = false; if (m_layoutBindings != nullptr) { delete[] m_layoutBindings; @@ -105,6 +133,20 @@ void OpenGLDescriptorSet::Shutdown() { m_bindingCount = 0; } +void OpenGLDescriptorSet::EnsureConstantBufferUploaded() { + if (!m_constantBufferDirty || m_constantBufferData.empty()) { + return; + } + + if (m_constantBuffer == 0) { + glGenBuffers(1, reinterpret_cast(&m_constantBuffer)); + } + glBindBuffer(GL_UNIFORM_BUFFER, m_constantBuffer); + glBufferData(GL_UNIFORM_BUFFER, m_constantBufferData.size(), m_constantBufferData.data(), GL_DYNAMIC_DRAW); + glBindBuffer(GL_UNIFORM_BUFFER, 0); + m_constantBufferDirty = false; +} + void OpenGLDescriptorSet::Update(uint32_t offset, RHIResourceView* view) { if (view == nullptr) { return; @@ -134,15 +176,7 @@ void OpenGLDescriptorSet::UpdateSampler(uint32_t offset, RHISampler* sampler) { } void OpenGLDescriptorSet::Bind() { - if (m_constantBufferDirty && !m_constantBufferData.empty()) { - if (m_constantBuffer == 0) { - glGenBuffers(1, reinterpret_cast(&m_constantBuffer)); - } - glBindBuffer(GL_UNIFORM_BUFFER, m_constantBuffer); - glBufferData(GL_UNIFORM_BUFFER, m_constantBufferData.size(), m_constantBufferData.data(), GL_DYNAMIC_DRAW); - glBindBuffer(GL_UNIFORM_BUFFER, 0); - m_constantBufferDirty = false; - } + EnsureConstantBufferUploaded(); if (m_constantBuffer != 0) { for (const auto& binding : m_bindings) { @@ -178,6 +212,56 @@ void OpenGLDescriptorSet::Bind() { m_bound = true; } +void OpenGLDescriptorSet::BindWithPipelineLayout(const OpenGLPipelineLayout* pipelineLayout, uint32_t setIndex) { + EnsureConstantBufferUploaded(); + + if (m_constantBuffer != 0) { + for (const auto& binding : m_bindings) { + if (binding.type != DescriptorType::CBV) { + continue; + } + + const uint32_t baseBindingPoint = ResolveBindingPoint(pipelineLayout, setIndex, binding); + if (baseBindingPoint == UINT32_MAX) { + continue; + } + + for (uint32_t i = 0; i < binding.count; ++i) { + glBindBufferBase(GL_UNIFORM_BUFFER, baseBindingPoint + i, m_constantBuffer); + } + } + } + + for (const auto& binding : m_bindings) { + const uint32_t baseBindingPoint = ResolveBindingPoint(pipelineLayout, setIndex, binding); + if (baseBindingPoint == UINT32_MAX) { + continue; + } + + for (size_t i = 0; i < binding.textureIds.size(); ++i) { + const uint32_t bindingPoint = baseBindingPoint + static_cast(i); + const uint32_t textureId = binding.textureIds[i]; + if (textureId != 0) { + if (binding.type == DescriptorType::UAV) { + glBindImageTexture(bindingPoint, textureId, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA8); + } else if (binding.type != DescriptorType::Sampler) { + glActiveTexture(GL_TEXTURE0 + bindingPoint); + glBindTexture(GL_TEXTURE_2D, textureId); + } + } + + if (binding.type == DescriptorType::Sampler && i < binding.samplerIds.size()) { + const uint32_t samplerId = binding.samplerIds[i]; + if (samplerId != 0) { + glBindSampler(bindingPoint, samplerId); + } + } + } + } + + m_bound = true; +} + void OpenGLDescriptorSet::Unbind() { for (size_t i = 0; i < m_bindings.size(); ++i) { const auto& binding = m_bindings[i]; diff --git a/engine/src/RHI/OpenGL/OpenGLPipelineLayout.cpp b/engine/src/RHI/OpenGL/OpenGLPipelineLayout.cpp index c931fb0e..9a6b985f 100644 --- a/engine/src/RHI/OpenGL/OpenGLPipelineLayout.cpp +++ b/engine/src/RHI/OpenGL/OpenGLPipelineLayout.cpp @@ -1,5 +1,7 @@ #include "XCEngine/RHI/OpenGL/OpenGLPipelineLayout.h" +#include + namespace XCEngine { namespace RHI { @@ -27,10 +29,34 @@ void AccumulateDescriptorCounts(const DescriptorSetLayoutDesc& setLayout, RHIPip } } +std::vector GatherBindingsOfTypeSorted( + const DescriptorSetLayoutDesc& setLayout, + DescriptorType type) { + std::vector bindings; + bindings.reserve(setLayout.bindingCount); + + for (uint32_t bindingIndex = 0; bindingIndex < setLayout.bindingCount; ++bindingIndex) { + const DescriptorSetLayoutBinding& binding = setLayout.bindings[bindingIndex]; + if (static_cast(binding.type) == type) { + bindings.push_back(&binding); + } + } + + std::sort( + bindings.begin(), + bindings.end(), + [](const DescriptorSetLayoutBinding* left, const DescriptorSetLayoutBinding* right) { + return left->binding < right->binding; + }); + + return bindings; +} + } // namespace bool OpenGLPipelineLayout::Initialize(const RHIPipelineLayoutDesc& desc) { m_desc = desc; + m_setBindingPointMappings.clear(); m_setLayouts.clear(); m_setLayoutBindings.clear(); @@ -62,14 +88,164 @@ bool OpenGLPipelineLayout::Initialize(const RHIPipelineLayoutDesc& desc) { m_desc.setLayouts = m_setLayouts.data(); m_desc.setLayoutCount = static_cast(m_setLayouts.size()); + + m_setBindingPointMappings.assign(m_desc.setLayoutCount, SetBindingPointMapping{}); + + uint32_t nextCBVBindingPoint = 0; + uint32_t nextSRVBindingPoint = 0; + uint32_t nextUAVBindingPoint = 0; + uint32_t nextSamplerBindingPoint = 0; + + for (uint32_t setIndex = 0; setIndex < m_desc.setLayoutCount; ++setIndex) { + const DescriptorSetLayoutDesc& setLayout = m_desc.setLayouts[setIndex]; + SetBindingPointMapping& mapping = m_setBindingPointMappings[setIndex]; + + const auto appendBindings = + [&setLayout]( + DescriptorType type, + std::unordered_map& bindingPoints, + uint32_t& nextBindingPoint) { + const auto bindings = GatherBindingsOfTypeSorted(setLayout, type); + for (const DescriptorSetLayoutBinding* binding : bindings) { + bindingPoints[binding->binding] = nextBindingPoint; + nextBindingPoint += binding->count > 0 ? binding->count : 1u; + } + }; + + appendBindings( + DescriptorType::CBV, + mapping.constantBufferBindingPoints, + nextCBVBindingPoint); + appendBindings( + DescriptorType::SRV, + mapping.shaderResourceBindingPoints, + nextSRVBindingPoint); + appendBindings( + DescriptorType::UAV, + mapping.unorderedAccessBindingPoints, + nextUAVBindingPoint); + appendBindings( + DescriptorType::Sampler, + mapping.samplerBindingPoints, + nextSamplerBindingPoint); + } } m_initialized = true; return true; } +bool OpenGLPipelineLayout::HasConstantBufferBinding(uint32_t setIndex, uint32_t binding) const { + if (!UsesSetLayouts()) { + return setIndex == 0; + } + + if (setIndex >= m_setBindingPointMappings.size()) { + return false; + } + + return m_setBindingPointMappings[setIndex].constantBufferBindingPoints.find(binding) != + m_setBindingPointMappings[setIndex].constantBufferBindingPoints.end(); +} + +uint32_t OpenGLPipelineLayout::GetConstantBufferBindingPoint(uint32_t setIndex, uint32_t binding) const { + if (!UsesSetLayouts()) { + return setIndex == 0 ? binding : UINT32_MAX; + } + + if (setIndex >= m_setBindingPointMappings.size()) { + return UINT32_MAX; + } + + const auto& bindingPoints = m_setBindingPointMappings[setIndex].constantBufferBindingPoints; + auto it = bindingPoints.find(binding); + return it != bindingPoints.end() ? it->second : UINT32_MAX; +} + +bool OpenGLPipelineLayout::HasShaderResourceBinding(uint32_t setIndex, uint32_t binding) const { + if (!UsesSetLayouts()) { + return setIndex == 0; + } + + if (setIndex >= m_setBindingPointMappings.size()) { + return false; + } + + return m_setBindingPointMappings[setIndex].shaderResourceBindingPoints.find(binding) != + m_setBindingPointMappings[setIndex].shaderResourceBindingPoints.end(); +} + +uint32_t OpenGLPipelineLayout::GetShaderResourceBindingPoint(uint32_t setIndex, uint32_t binding) const { + if (!UsesSetLayouts()) { + return setIndex == 0 ? binding : UINT32_MAX; + } + + if (setIndex >= m_setBindingPointMappings.size()) { + return UINT32_MAX; + } + + const auto& bindingPoints = m_setBindingPointMappings[setIndex].shaderResourceBindingPoints; + auto it = bindingPoints.find(binding); + return it != bindingPoints.end() ? it->second : UINT32_MAX; +} + +bool OpenGLPipelineLayout::HasUnorderedAccessBinding(uint32_t setIndex, uint32_t binding) const { + if (!UsesSetLayouts()) { + return setIndex == 0; + } + + if (setIndex >= m_setBindingPointMappings.size()) { + return false; + } + + return m_setBindingPointMappings[setIndex].unorderedAccessBindingPoints.find(binding) != + m_setBindingPointMappings[setIndex].unorderedAccessBindingPoints.end(); +} + +uint32_t OpenGLPipelineLayout::GetUnorderedAccessBindingPoint(uint32_t setIndex, uint32_t binding) const { + if (!UsesSetLayouts()) { + return setIndex == 0 ? binding : UINT32_MAX; + } + + if (setIndex >= m_setBindingPointMappings.size()) { + return UINT32_MAX; + } + + const auto& bindingPoints = m_setBindingPointMappings[setIndex].unorderedAccessBindingPoints; + auto it = bindingPoints.find(binding); + return it != bindingPoints.end() ? it->second : UINT32_MAX; +} + +bool OpenGLPipelineLayout::HasSamplerBinding(uint32_t setIndex, uint32_t binding) const { + if (!UsesSetLayouts()) { + return setIndex == 0; + } + + if (setIndex >= m_setBindingPointMappings.size()) { + return false; + } + + return m_setBindingPointMappings[setIndex].samplerBindingPoints.find(binding) != + m_setBindingPointMappings[setIndex].samplerBindingPoints.end(); +} + +uint32_t OpenGLPipelineLayout::GetSamplerBindingPoint(uint32_t setIndex, uint32_t binding) const { + if (!UsesSetLayouts()) { + return setIndex == 0 ? binding : UINT32_MAX; + } + + if (setIndex >= m_setBindingPointMappings.size()) { + return UINT32_MAX; + } + + const auto& bindingPoints = m_setBindingPointMappings[setIndex].samplerBindingPoints; + auto it = bindingPoints.find(binding); + return it != bindingPoints.end() ? it->second : UINT32_MAX; +} + void OpenGLPipelineLayout::Shutdown() { m_desc = {}; + m_setBindingPointMappings.clear(); m_setLayouts.clear(); m_setLayoutBindings.clear(); m_initialized = false; diff --git a/tests/RHI/integration/sphere/main.cpp b/tests/RHI/integration/sphere/main.cpp index 86a66ce7..dd173bcb 100644 --- a/tests/RHI/integration/sphere/main.cpp +++ b/tests/RHI/integration/sphere/main.cpp @@ -122,10 +122,10 @@ MatrixBufferData CreateMatrixBufferData() { } const char kSphereHlsl[] = R"( -Texture2D gDiffuseTexture : register(t0); -SamplerState gSampler : register(s0); +Texture2D gDiffuseTexture : register(t1); +SamplerState gSampler : register(s1); -cbuffer MatrixBuffer : register(b0) { +cbuffer MatrixBuffer : register(b1) { float4x4 gProjectionMatrix; float4x4 gViewMatrix; float4x4 gModelMatrix; @@ -159,7 +159,7 @@ const char kSphereVertexShader[] = R"(#version 430 layout(location = 0) in vec4 aPosition; layout(location = 1) in vec2 aTexCoord; -layout(std140, binding = 0) uniform MatrixBuffer { +layout(std140, binding = 1) uniform MatrixBuffer { mat4 gProjectionMatrix; mat4 gViewMatrix; mat4 gModelMatrix; @@ -176,7 +176,7 @@ void main() { )"; const char kSphereFragmentShader[] = R"(#version 430 -layout(binding = 0) uniform sampler2D uTexture; +layout(binding = 1) uniform sampler2D uTexture; in vec2 vTexCoord; @@ -454,8 +454,24 @@ void SphereTest::InitializeSphereResources() { ASSERT_NE(mSamplerSet, nullptr); mSamplerSet->UpdateSampler(0, mSampler); + DescriptorSetLayoutBinding reservedSetBindings[3] = {}; + reservedSetBindings[0].binding = 0; + reservedSetBindings[0].type = static_cast(DescriptorType::CBV); + reservedSetBindings[0].count = 1; + reservedSetBindings[1].binding = 0; + reservedSetBindings[1].type = static_cast(DescriptorType::SRV); + reservedSetBindings[1].count = 1; + reservedSetBindings[2].binding = 0; + reservedSetBindings[2].type = static_cast(DescriptorType::Sampler); + reservedSetBindings[2].count = 1; + + DescriptorSetLayoutDesc reservedLayoutDesc = {}; + reservedLayoutDesc.bindings = reservedSetBindings; + reservedLayoutDesc.bindingCount = 3; + DescriptorSetLayoutDesc setLayouts[kSphereDescriptorSetCount] = {}; - // Reserve set0 so the integration test exercises non-zero firstSet binding. + // Reserve slot0 so the bound sets land on binding point 1 for every descriptor class. + setLayouts[0] = reservedLayoutDesc; setLayouts[1] = constantLayoutDesc; setLayouts[2] = textureLayoutDesc; setLayouts[3] = samplerLayoutDesc; diff --git a/tests/RHI/unit/test_pipeline_layout.cpp b/tests/RHI/unit/test_pipeline_layout.cpp index f8e40426..be7872d3 100644 --- a/tests/RHI/unit/test_pipeline_layout.cpp +++ b/tests/RHI/unit/test_pipeline_layout.cpp @@ -362,3 +362,59 @@ TEST_P(RHITestFixture, PipelineLayout_D3D12SeparatesOverlappingBindingsAcrossSet layout->Shutdown(); delete layout; } + +TEST_P(RHITestFixture, PipelineLayout_OpenGLSeparatesOverlappingBindingsAcrossSetSlots) { + if (GetBackendType() != RHIType::OpenGL) { + GTEST_SKIP() << "OpenGL-specific binding point verification"; + } + + DescriptorSetLayoutBinding set0Bindings[3] = {}; + set0Bindings[0].binding = 0; + set0Bindings[0].type = static_cast(DescriptorType::CBV); + set0Bindings[0].count = 1; + set0Bindings[1].binding = 0; + set0Bindings[1].type = static_cast(DescriptorType::SRV); + set0Bindings[1].count = 1; + set0Bindings[2].binding = 0; + set0Bindings[2].type = static_cast(DescriptorType::Sampler); + set0Bindings[2].count = 1; + + DescriptorSetLayoutBinding set1Bindings[3] = {}; + set1Bindings[0].binding = 0; + set1Bindings[0].type = static_cast(DescriptorType::CBV); + set1Bindings[0].count = 1; + set1Bindings[1].binding = 0; + set1Bindings[1].type = static_cast(DescriptorType::SRV); + set1Bindings[1].count = 1; + set1Bindings[2].binding = 0; + set1Bindings[2].type = static_cast(DescriptorType::Sampler); + set1Bindings[2].count = 1; + + DescriptorSetLayoutDesc setLayouts[2] = {}; + setLayouts[0].bindings = set0Bindings; + setLayouts[0].bindingCount = 3; + setLayouts[1].bindings = set1Bindings; + setLayouts[1].bindingCount = 3; + + RHIPipelineLayoutDesc desc = {}; + desc.setLayouts = setLayouts; + desc.setLayoutCount = 2; + + RHIPipelineLayout* layout = GetDevice()->CreatePipelineLayout(desc); + ASSERT_NE(layout, nullptr); + + auto* openGLLayout = static_cast(layout); + EXPECT_TRUE(openGLLayout->UsesSetLayouts()); + EXPECT_EQ(openGLLayout->GetSetLayoutCount(), 2u); + EXPECT_TRUE(openGLLayout->HasConstantBufferBinding(0, 0)); + EXPECT_TRUE(openGLLayout->HasConstantBufferBinding(1, 0)); + EXPECT_EQ(openGLLayout->GetConstantBufferBindingPoint(0, 0), 0u); + EXPECT_EQ(openGLLayout->GetConstantBufferBindingPoint(1, 0), 1u); + EXPECT_EQ(openGLLayout->GetShaderResourceBindingPoint(0, 0), 0u); + EXPECT_EQ(openGLLayout->GetShaderResourceBindingPoint(1, 0), 1u); + EXPECT_EQ(openGLLayout->GetSamplerBindingPoint(0, 0), 0u); + EXPECT_EQ(openGLLayout->GetSamplerBindingPoint(1, 0), 1u); + + layout->Shutdown(); + delete layout; +} diff --git a/tests/TEST_SPEC.md b/tests/TEST_SPEC.md index cd0511c7..c6ffaef9 100644 --- a/tests/TEST_SPEC.md +++ b/tests/TEST_SPEC.md @@ -175,6 +175,7 @@ tests/RHI/integration/ 5. 两个后端都必须与同一张 `GT.ppm` 做比对。 6. 新测试如果暴露抽象层缺口,应先补 RHI,再补测试。 7. 至少保留一个场景覆盖 `firstSet != 0` 的描述符绑定路径,当前由 `sphere` 负责验证。 +8. `sphere` 需要通过预留 `set0` 把实际 shader 绑定点推进到 `binding = 1`,避免出现“忽略 set 语义也能误通过”的假阳性。 ### 6.3 命名