diff --git a/engine/include/XCEngine/RHI/OpenGL/OpenGLCommandList.h b/engine/include/XCEngine/RHI/OpenGL/OpenGLCommandList.h index 1ff4ef98..49c06725 100644 --- a/engine/include/XCEngine/RHI/OpenGL/OpenGLCommandList.h +++ b/engine/include/XCEngine/RHI/OpenGL/OpenGLCommandList.h @@ -13,6 +13,7 @@ class OpenGLBuffer; class OpenGLVertexArray; class OpenGLShader; class OpenGLTexture; +class OpenGLPipelineState; enum class PrimitiveType { Points, @@ -192,11 +193,17 @@ public: void DrawIndexed(uint32_t indexCount, uint32_t instanceCount, uint32_t startIndex, int32_t baseVertex, uint32_t startInstance) override; private: + void EnsureInternalVertexArrayBound(); + void DisableConfiguredVertexAttributes(); + unsigned int m_primitiveType; unsigned int m_currentVAO; unsigned int m_currentProgram; + unsigned int m_internalVAO; + OpenGLPipelineState* m_currentPipelineState; + std::vector m_enabledVertexAttributes; OpenGLShader* m_currentShader; }; } // namespace RHI -} // namespace XCEngine \ No newline at end of file +} // namespace XCEngine diff --git a/engine/src/RHI/OpenGL/OpenGLCommandList.cpp b/engine/src/RHI/OpenGL/OpenGLCommandList.cpp index 149fad12..5bd5bb61 100644 --- a/engine/src/RHI/OpenGL/OpenGLCommandList.cpp +++ b/engine/src/RHI/OpenGL/OpenGLCommandList.cpp @@ -11,10 +11,57 @@ namespace XCEngine { namespace RHI { +namespace { + +struct OpenGLVertexAttribFormat { + GLint componentCount = 0; + GLenum type = GL_FLOAT; + GLboolean normalized = GL_FALSE; + bool integer = false; +}; + +bool GetOpenGLVertexAttribFormat(Format format, OpenGLVertexAttribFormat& attributeFormat) { + switch (format) { + case Format::R8_UNorm: + attributeFormat = { 1, GL_UNSIGNED_BYTE, GL_TRUE, false }; + return true; + case Format::R8G8_UNorm: + attributeFormat = { 2, GL_UNSIGNED_BYTE, GL_TRUE, false }; + return true; + case Format::R8G8B8A8_UNorm: + attributeFormat = { 4, GL_UNSIGNED_BYTE, GL_TRUE, false }; + return true; + case Format::R16_Float: + attributeFormat = { 1, GL_HALF_FLOAT, GL_FALSE, false }; + return true; + case Format::R16G16B16A16_Float: + attributeFormat = { 4, GL_HALF_FLOAT, GL_FALSE, false }; + return true; + case Format::R32_Float: + attributeFormat = { 1, GL_FLOAT, GL_FALSE, false }; + return true; + case Format::R32G32B32A32_Float: + attributeFormat = { 4, GL_FLOAT, GL_FALSE, false }; + return true; + case Format::R32_UInt: + attributeFormat = { 1, GL_UNSIGNED_INT, GL_FALSE, true }; + return true; + case Format::R32G32B32A32_UInt: + attributeFormat = { 4, GL_UNSIGNED_INT, GL_FALSE, true }; + return true; + default: + return false; + } +} + +} // namespace + OpenGLCommandList::OpenGLCommandList() : m_primitiveType(GL_TRIANGLES) , m_currentVAO(0) , m_currentProgram(0) + , m_internalVAO(0) + , m_currentPipelineState(nullptr) , m_currentShader(nullptr) { } @@ -407,10 +454,17 @@ void OpenGLCommandList::PopDebugGroup() { } void OpenGLCommandList::Shutdown() { + DisableConfiguredVertexAttributes(); + if (m_internalVAO != 0) { + glDeleteVertexArrays(1, &m_internalVAO); + m_internalVAO = 0; + } + m_currentPipelineState = nullptr; m_currentShader = nullptr; } void OpenGLCommandList::Reset() { + m_currentPipelineState = nullptr; } void OpenGLCommandList::Close() { @@ -598,7 +652,10 @@ void OpenGLCommandList::ClearDepthStencil(RHIResourceView* depthStencil, float d void OpenGLCommandList::SetPipelineState(RHIPipelineState* pipelineState) { if (pipelineState) { + m_currentPipelineState = static_cast(pipelineState); pipelineState->Bind(); + } else { + m_currentPipelineState = nullptr; } } @@ -633,6 +690,63 @@ void OpenGLCommandList::SetComputeDescriptorSets( } void OpenGLCommandList::SetVertexBuffers(uint32_t startSlot, uint32_t count, RHIResourceView** buffers, const uint64_t* offsets, const uint32_t* strides) { + EnsureInternalVertexArrayBound(); + DisableConfiguredVertexAttributes(); + + if (m_currentPipelineState != nullptr) { + const InputLayoutDesc& inputLayout = m_currentPipelineState->GetInputLayout(); + if (!inputLayout.elements.empty()) { + for (uint32_t attributeIndex = 0; attributeIndex < inputLayout.elements.size(); ++attributeIndex) { + const InputElementDesc& element = inputLayout.elements[attributeIndex]; + if (element.inputSlot < startSlot || element.inputSlot >= startSlot + count) { + continue; + } + + const uint32_t bindingIndex = element.inputSlot - startSlot; + if (buffers[bindingIndex] == nullptr) { + continue; + } + + OpenGLResourceView* view = static_cast(buffers[bindingIndex]); + if (!view->IsValid()) { + continue; + } + + OpenGLVertexAttribFormat attributeFormat = {}; + if (!GetOpenGLVertexAttribFormat(static_cast(element.format), attributeFormat)) { + continue; + } + + const GLuint glBuffer = view->GetBuffer(); + const uint32_t stride = strides[bindingIndex] > 0 ? strides[bindingIndex] : view->GetBufferStride(); + const uint64_t offset = view->GetBufferOffset() + offsets[bindingIndex] + element.alignedByteOffset; + + glBindBuffer(GL_ARRAY_BUFFER, glBuffer); + glEnableVertexAttribArray(attributeIndex); + if (attributeFormat.integer) { + glVertexAttribIPointer( + attributeIndex, + attributeFormat.componentCount, + attributeFormat.type, + static_cast(stride), + reinterpret_cast(static_cast(offset))); + } else { + glVertexAttribPointer( + attributeIndex, + attributeFormat.componentCount, + attributeFormat.type, + attributeFormat.normalized, + static_cast(stride), + reinterpret_cast(static_cast(offset))); + } + m_enabledVertexAttributes.push_back(attributeIndex); + } + + glBindBuffer(GL_ARRAY_BUFFER, 0); + return; + } + } + for (uint32_t i = 0; i < count; i++) { if (!buffers[i]) continue; OpenGLResourceView* view = static_cast(buffers[i]); @@ -644,12 +758,14 @@ void OpenGLCommandList::SetVertexBuffers(uint32_t startSlot, uint32_t count, RHI const uint32_t stride = strides[i] > 0 ? strides[i] : view->GetBufferStride(); const uint64_t offset = view->GetBufferOffset() + offsets[i]; glVertexAttribPointer(startSlot + i, stride / sizeof(float), GL_FLOAT, GL_FALSE, stride, reinterpret_cast(static_cast(offset))); + m_enabledVertexAttributes.push_back(startSlot + i); } glBindBuffer(GL_ARRAY_BUFFER, 0); } void OpenGLCommandList::SetIndexBuffer(RHIResourceView* buffer, uint64_t offset) { if (!buffer) return; + EnsureInternalVertexArrayBound(); OpenGLResourceView* view = static_cast(buffer); if (!view->IsValid()) return; @@ -694,5 +810,21 @@ void OpenGLCommandList::SetBlendFactor(const float factor[4]) { glBlendColor(factor[0], factor[1], factor[2], factor[3]); } +void OpenGLCommandList::EnsureInternalVertexArrayBound() { + if (m_internalVAO == 0) { + glGenVertexArrays(1, &m_internalVAO); + } + + m_currentVAO = m_internalVAO; + glBindVertexArray(m_internalVAO); +} + +void OpenGLCommandList::DisableConfiguredVertexAttributes() { + for (unsigned int attributeIndex : m_enabledVertexAttributes) { + glDisableVertexAttribArray(attributeIndex); + } + m_enabledVertexAttributes.clear(); +} + } // namespace RHI } // namespace XCEngine diff --git a/tests/RHI/unit/test_command_list.cpp b/tests/RHI/unit/test_command_list.cpp index d7ee2741..fb5e356f 100644 --- a/tests/RHI/unit/test_command_list.cpp +++ b/tests/RHI/unit/test_command_list.cpp @@ -6,6 +6,8 @@ #include "XCEngine/RHI/RHIPipelineState.h" #include "XCEngine/RHI/RHIRenderPass.h" #include "XCEngine/RHI/RHIFramebuffer.h" +#include "XCEngine/RHI/OpenGL/OpenGLDevice.h" +#include using namespace XCEngine::RHI; @@ -521,3 +523,125 @@ TEST_P(RHITestFixture, CommandList_SetShader) { shader->Shutdown(); delete shader; } + +TEST_P(RHITestFixture, CommandList_SetVertexBuffers_InterleavedInputLayoutUsesPipelineLayout) { + if (GetBackendType() != RHIType::OpenGL) { + GTEST_SKIP() << "OpenGL-specific vertex layout binding"; + } + + auto* openGLDevice = static_cast(GetDevice()); + ASSERT_NE(openGLDevice, nullptr); + ASSERT_TRUE(openGLDevice->MakeContextCurrent()); + + GraphicsPipelineDesc pipelineDesc = {}; + pipelineDesc.renderTargetFormats[0] = static_cast(Format::R8G8B8A8_UNorm); + + InputElementDesc position = {}; + position.semanticName = "POSITION"; + position.semanticIndex = 0; + position.format = static_cast(Format::R32G32B32A32_Float); + position.inputSlot = 0; + position.alignedByteOffset = 0; + pipelineDesc.inputLayout.elements.push_back(position); + + InputElementDesc color = {}; + color.semanticName = "COLOR"; + color.semanticIndex = 0; + color.format = static_cast(Format::R32G32B32A32_Float); + color.inputSlot = 0; + color.alignedByteOffset = sizeof(float) * 4; + pipelineDesc.inputLayout.elements.push_back(color); + + static const char* vertexSource = R"(#version 430 +layout(location = 0) in vec4 aPosition; +layout(location = 1) in vec4 aColor; +out vec4 vColor; +void main() { + gl_Position = aPosition; + vColor = aColor; +} +)"; + static const char* fragmentSource = R"(#version 430 +in vec4 vColor; +layout(location = 0) out vec4 fragColor; +void main() { + fragColor = vColor; +} +)"; + + pipelineDesc.vertexShader.source.assign(vertexSource, vertexSource + strlen(vertexSource)); + pipelineDesc.vertexShader.sourceLanguage = ShaderLanguage::GLSL; + pipelineDesc.vertexShader.profile = L"vs_4_30"; + + pipelineDesc.fragmentShader.source.assign(fragmentSource, fragmentSource + strlen(fragmentSource)); + pipelineDesc.fragmentShader.sourceLanguage = ShaderLanguage::GLSL; + pipelineDesc.fragmentShader.profile = L"fs_4_30"; + + RHIPipelineState* pipelineState = GetDevice()->CreatePipelineState(pipelineDesc); + ASSERT_NE(pipelineState, nullptr); + + BufferDesc bufferDesc = {}; + bufferDesc.size = sizeof(float) * 8 * 3; + bufferDesc.stride = sizeof(float) * 8; + bufferDesc.bufferType = static_cast(BufferType::Vertex); + + RHIBuffer* buffer = GetDevice()->CreateBuffer(bufferDesc); + ASSERT_NE(buffer, nullptr); + + ResourceViewDesc viewDesc = {}; + viewDesc.dimension = ResourceViewDimension::Buffer; + viewDesc.structureByteStride = bufferDesc.stride; + RHIResourceView* vbv = GetDevice()->CreateVertexBufferView(buffer, viewDesc); + ASSERT_NE(vbv, nullptr); + + CommandListDesc cmdDesc = {}; + cmdDesc.commandListType = static_cast(CommandQueueType::Direct); + RHICommandList* cmdList = GetDevice()->CreateCommandList(cmdDesc); + ASSERT_NE(cmdList, nullptr); + + RHIResourceView* views[] = { vbv }; + uint64_t offsets[] = { 0 }; + uint32_t strides[] = { bufferDesc.stride }; + + cmdList->Reset(); + cmdList->SetPipelineState(pipelineState); + cmdList->SetVertexBuffers(0, 1, views, offsets, strides); + + GLint enabled0 = 0; + GLint enabled1 = 0; + GLint size0 = 0; + GLint size1 = 0; + GLint stride0 = 0; + GLint stride1 = 0; + void* pointer0 = nullptr; + void* pointer1 = nullptr; + + glGetVertexAttribiv(0, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &enabled0); + glGetVertexAttribiv(1, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &enabled1); + glGetVertexAttribiv(0, GL_VERTEX_ATTRIB_ARRAY_SIZE, &size0); + glGetVertexAttribiv(1, GL_VERTEX_ATTRIB_ARRAY_SIZE, &size1); + glGetVertexAttribiv(0, GL_VERTEX_ATTRIB_ARRAY_STRIDE, &stride0); + glGetVertexAttribiv(1, GL_VERTEX_ATTRIB_ARRAY_STRIDE, &stride1); + glGetVertexAttribPointerv(0, GL_VERTEX_ATTRIB_ARRAY_POINTER, &pointer0); + glGetVertexAttribPointerv(1, GL_VERTEX_ATTRIB_ARRAY_POINTER, &pointer1); + + EXPECT_EQ(enabled0, GL_TRUE); + EXPECT_EQ(enabled1, GL_TRUE); + EXPECT_EQ(size0, 4); + EXPECT_EQ(size1, 4); + EXPECT_EQ(stride0, static_cast(bufferDesc.stride)); + EXPECT_EQ(stride1, static_cast(bufferDesc.stride)); + EXPECT_EQ(reinterpret_cast(pointer0), 0u); + EXPECT_EQ(reinterpret_cast(pointer1), sizeof(float) * 4u); + + cmdList->Close(); + + cmdList->Shutdown(); + delete cmdList; + vbv->Shutdown(); + delete vbv; + buffer->Shutdown(); + delete buffer; + pipelineState->Shutdown(); + delete pipelineState; +}