diff --git a/engine/include/XCEngine/RHI/OpenGL/OpenGLCommandList.h b/engine/include/XCEngine/RHI/OpenGL/OpenGLCommandList.h index 49c06725..05025228 100644 --- a/engine/include/XCEngine/RHI/OpenGL/OpenGLCommandList.h +++ b/engine/include/XCEngine/RHI/OpenGL/OpenGLCommandList.h @@ -14,6 +14,7 @@ class OpenGLVertexArray; class OpenGLShader; class OpenGLTexture; class OpenGLPipelineState; +class OpenGLFramebuffer; enum class PrimitiveType { Points, @@ -193,6 +194,7 @@ public: void DrawIndexed(uint32_t indexCount, uint32_t instanceCount, uint32_t startIndex, int32_t baseVertex, uint32_t startInstance) override; private: + void ReleaseComposedFramebuffer(); void EnsureInternalVertexArrayBound(); void DisableConfiguredVertexAttributes(); @@ -203,6 +205,7 @@ private: OpenGLPipelineState* m_currentPipelineState; std::vector m_enabledVertexAttributes; OpenGLShader* m_currentShader; + OpenGLFramebuffer* m_composedFramebuffer; }; } // namespace RHI diff --git a/engine/include/XCEngine/RHI/OpenGL/OpenGLFramebuffer.h b/engine/include/XCEngine/RHI/OpenGL/OpenGLFramebuffer.h index b845e977..593d193d 100644 --- a/engine/include/XCEngine/RHI/OpenGL/OpenGLFramebuffer.h +++ b/engine/include/XCEngine/RHI/OpenGL/OpenGLFramebuffer.h @@ -18,8 +18,9 @@ enum class FramebufferAttachmentType { struct FramebufferAttachment { unsigned int texture = 0; + unsigned int target = 0; int mipLevel = 0; - int layer = 0; + int layer = -1; FramebufferAttachmentType type = FramebufferAttachmentType::Color; uint32_t format = 0; }; @@ -71,4 +72,4 @@ private: }; } // namespace RHI -} // namespace XCEngine \ No newline at end of file +} // namespace XCEngine diff --git a/engine/include/XCEngine/RHI/OpenGL/OpenGLResourceView.h b/engine/include/XCEngine/RHI/OpenGL/OpenGLResourceView.h index 62cb2987..c8666f12 100644 --- a/engine/include/XCEngine/RHI/OpenGL/OpenGLResourceView.h +++ b/engine/include/XCEngine/RHI/OpenGL/OpenGLResourceView.h @@ -1,5 +1,6 @@ #pragma once +#include "XCEngine/RHI/OpenGL/OpenGLFramebuffer.h" #include "XCEngine/RHI/RHIResourceView.h" #include "XCEngine/RHI/RHITypes.h" @@ -63,6 +64,8 @@ public: uint64_t GetBufferOffset() const { return m_bufferOffset; } uint32_t GetBufferSize() const { return m_bufferSize; } uint32_t GetBufferStride() const { return m_bufferStride; } + const FramebufferAttachment& GetFramebufferAttachment() const { return m_framebufferAttachment; } + const OpenGLTexture* GetTextureResource() const { return m_texture; } private: ResourceViewType m_viewType; @@ -76,6 +79,7 @@ private: OpenGLFramebuffer* m_framebuffer; OpenGLTextureUnitAllocator* m_textureUnitAllocator; OpenGLUniformBufferManager* m_uniformBufferManager; + FramebufferAttachment m_framebufferAttachment; uint64_t m_bufferOffset; uint32_t m_bufferSize; uint32_t m_bufferStride; diff --git a/engine/src/RHI/OpenGL/OpenGLCommandList.cpp b/engine/src/RHI/OpenGL/OpenGLCommandList.cpp index 5888dade..b73e172e 100644 --- a/engine/src/RHI/OpenGL/OpenGLCommandList.cpp +++ b/engine/src/RHI/OpenGL/OpenGLCommandList.cpp @@ -65,7 +65,8 @@ OpenGLCommandList::OpenGLCommandList() , m_currentProgram(0) , m_internalVAO(0) , m_currentPipelineState(nullptr) - , m_currentShader(nullptr) { + , m_currentShader(nullptr) + , m_composedFramebuffer(nullptr) { } OpenGLCommandList::~OpenGLCommandList() { @@ -457,6 +458,7 @@ void OpenGLCommandList::PopDebugGroup() { } void OpenGLCommandList::Shutdown() { + ReleaseComposedFramebuffer(); DisableConfiguredVertexAttributes(); if (m_internalVAO != 0) { glDeleteVertexArrays(1, &m_internalVAO); @@ -467,12 +469,21 @@ void OpenGLCommandList::Shutdown() { } void OpenGLCommandList::Reset() { + ReleaseComposedFramebuffer(); m_currentPipelineState = nullptr; } void OpenGLCommandList::Close() { } +void OpenGLCommandList::ReleaseComposedFramebuffer() { + if (m_composedFramebuffer != nullptr) { + m_composedFramebuffer->Shutdown(); + delete m_composedFramebuffer; + m_composedFramebuffer = nullptr; + } +} + void OpenGLCommandList::SetShader(RHIShader* shader) { m_currentShader = static_cast(shader); if (m_currentShader) { @@ -593,29 +604,63 @@ void OpenGLCommandList::SetScissorRects(uint32_t count, const Rect* rects) { void OpenGLCommandList::SetRenderTargets(uint32_t count, RHIResourceView** renderTargets, RHIResourceView* depthStencil) { if (count > 0 && renderTargets != nullptr) { - std::vector fbos(count); + const bool requiresComposedFramebuffer = count > 1 || depthStencil != nullptr; + std::vector drawBuffers(count, GL_NONE); for (uint32_t i = 0; i < count; ++i) { - if (renderTargets[i]) { - OpenGLResourceView* view = static_cast(renderTargets[i]); - fbos[i] = view->GetFramebuffer(); + if (renderTargets[i] != nullptr) { + drawBuffers[i] = ToOpenGLColorAttachment(i); } } - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbos[0]); + if (requiresComposedFramebuffer) { + uint32_t width = 0; + uint32_t height = 0; - std::vector drawBuffers(count); - for (uint32_t i = 0; i < count; ++i) { - drawBuffers[i] = ToOpenGLColorAttachment(i); + for (uint32_t i = 0; i < count; ++i) { + auto* view = static_cast(renderTargets[i]); + const OpenGLTexture* texture = view != nullptr ? view->GetTextureResource() : nullptr; + if (texture != nullptr) { + width = texture->GetWidth(); + height = texture->GetHeight(); + break; + } + } + + if ((width == 0 || height == 0) && depthStencil != nullptr) { + auto* dsv = static_cast(depthStencil); + const OpenGLTexture* depthTexture = dsv != nullptr ? dsv->GetTextureResource() : nullptr; + if (depthTexture != nullptr) { + width = depthTexture->GetWidth(); + height = depthTexture->GetHeight(); + } + } + + ReleaseComposedFramebuffer(); + m_composedFramebuffer = new OpenGLFramebuffer(); + if (!m_composedFramebuffer->Initialize(nullptr, width, height, count, renderTargets, depthStencil)) { + ReleaseComposedFramebuffer(); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + glDrawBuffers(0, nullptr); + return; + } + + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_composedFramebuffer->GetFramebuffer()); + } else { + ReleaseComposedFramebuffer(); + OpenGLResourceView* view = static_cast(renderTargets[0]); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, view != nullptr ? view->GetFramebuffer() : 0); } - glDrawBuffers(count, drawBuffers.data()); + glDrawBuffers(count, drawBuffers.data()); } else if (depthStencil) { + ReleaseComposedFramebuffer(); OpenGLResourceView* dsv = static_cast(depthStencil); if (dsv && dsv->IsValid()) { glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dsv->GetFramebuffer()); } glDrawBuffers(0, nullptr); } else { + ReleaseComposedFramebuffer(); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); } diff --git a/engine/src/RHI/OpenGL/OpenGLDevice.cpp b/engine/src/RHI/OpenGL/OpenGLDevice.cpp index c75dd43a..47ece74f 100644 --- a/engine/src/RHI/OpenGL/OpenGLDevice.cpp +++ b/engine/src/RHI/OpenGL/OpenGLDevice.cpp @@ -84,6 +84,42 @@ OpenGLFormat ToOpenGLTextureFormat(Format format) { } } +GLenum ResolveFramebufferTextureTarget(OpenGLTextureType textureType, const ResourceViewDesc& desc) { + switch (textureType) { + case OpenGLTextureType::Texture1D: + return GL_TEXTURE_1D; + case OpenGLTextureType::Texture2D: + return GL_TEXTURE_2D; + case OpenGLTextureType::TextureCube: { + const uint32_t face = desc.firstArraySlice < 6 ? desc.firstArraySlice : 0; + return GL_TEXTURE_CUBE_MAP_POSITIVE_X + face; + } + case OpenGLTextureType::Texture2DArray: + case OpenGLTextureType::Texture3D: + case OpenGLTextureType::TextureCubeArray: + return 0; + default: + return GL_TEXTURE_2D; + } +} + +int ResolveFramebufferAttachmentLayer(OpenGLTextureType textureType, const ResourceViewDesc& desc) { + switch (textureType) { + case OpenGLTextureType::Texture2DArray: + case OpenGLTextureType::Texture3D: + case OpenGLTextureType::TextureCubeArray: + return static_cast(desc.firstArraySlice); + default: + return -1; + } +} + +FramebufferAttachmentType ResolveDepthAttachmentType(Format format) { + return format == Format::D24_UNorm_S8_UInt + ? FramebufferAttachmentType::DepthStencil + : FramebufferAttachmentType::Depth; +} + } // namespace OpenGLDevice::OpenGLDevice() @@ -634,9 +670,11 @@ RHIResourceView* OpenGLDevice::CreateRenderTargetView(RHITexture* texture, const FramebufferAttachment colorAttachment = {}; colorAttachment.texture = glTexture->GetID(); + colorAttachment.target = ResolveFramebufferTextureTarget(glTexture->GetOpenGLType(), desc); colorAttachment.mipLevel = static_cast(desc.mipLevel); + colorAttachment.layer = ResolveFramebufferAttachmentLayer(glTexture->GetOpenGLType(), desc); colorAttachment.type = FramebufferAttachmentType::Color; - colorAttachment.format = desc.format; + colorAttachment.format = desc.format != 0 ? desc.format : static_cast(texture->GetFormat()); fbDesc.colorAttachments.push_back(colorAttachment); auto* framebuffer = new OpenGLFramebuffer(); @@ -668,9 +706,11 @@ RHIResourceView* OpenGLDevice::CreateDepthStencilView(RHITexture* texture, const FramebufferAttachment depthAttachment = {}; depthAttachment.texture = glTexture->GetID(); + depthAttachment.target = ResolveFramebufferTextureTarget(glTexture->GetOpenGLType(), desc); depthAttachment.mipLevel = static_cast(desc.mipLevel); - depthAttachment.type = FramebufferAttachmentType::DepthStencil; - depthAttachment.format = desc.format; + depthAttachment.layer = ResolveFramebufferAttachmentLayer(glTexture->GetOpenGLType(), desc); + depthAttachment.format = desc.format != 0 ? desc.format : static_cast(texture->GetFormat()); + depthAttachment.type = ResolveDepthAttachmentType(static_cast(depthAttachment.format)); fbDesc.depthAttachment = depthAttachment; auto* framebuffer = new OpenGLFramebuffer(); diff --git a/engine/src/RHI/OpenGL/OpenGLFramebuffer.cpp b/engine/src/RHI/OpenGL/OpenGLFramebuffer.cpp index 318d9443..ec79d99d 100644 --- a/engine/src/RHI/OpenGL/OpenGLFramebuffer.cpp +++ b/engine/src/RHI/OpenGL/OpenGLFramebuffer.cpp @@ -6,6 +6,38 @@ namespace XCEngine { namespace RHI { +namespace { + +bool IsTexture2DAttachmentTarget(GLenum target) { + return target == GL_TEXTURE_2D || + (target >= GL_TEXTURE_CUBE_MAP_POSITIVE_X && target <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z); +} + +void AttachFramebufferTexture(GLenum attachmentPoint, const FramebufferAttachment& attachment) { + if (attachment.texture == 0) { + return; + } + + if (attachment.layer >= 0) { + glFramebufferTextureLayer(GL_FRAMEBUFFER, attachmentPoint, attachment.texture, attachment.mipLevel, attachment.layer); + return; + } + + if (attachment.target == GL_TEXTURE_1D) { + glFramebufferTexture1D(GL_FRAMEBUFFER, attachmentPoint, GL_TEXTURE_1D, attachment.texture, attachment.mipLevel); + return; + } + + if (IsTexture2DAttachmentTarget(attachment.target)) { + glFramebufferTexture2D(GL_FRAMEBUFFER, attachmentPoint, attachment.target, attachment.texture, attachment.mipLevel); + return; + } + + glFramebufferTexture(GL_FRAMEBUFFER, attachmentPoint, attachment.texture, attachment.mipLevel); +} + +} // namespace + OpenGLFramebuffer::OpenGLFramebuffer() : m_framebuffer(0) , m_width(0) @@ -32,45 +64,23 @@ bool OpenGLFramebuffer::Initialize(const FramebufferDesc& desc) { for (size_t i = 0; i < desc.colorAttachments.size() && i < 16; ++i) { const auto& attachment = desc.colorAttachments[i]; GLenum glAttachment = ToOpenGLColorAttachment(static_cast(i)); - - switch (attachment.type) { - case FramebufferAttachmentType::Color: - if (attachment.layer == 0 && attachment.mipLevel == 0) { - glFramebufferTexture2D(GL_FRAMEBUFFER, glAttachment, GL_TEXTURE_2D, attachment.texture, 0); - } else if (attachment.layer > 0) { - glFramebufferTextureLayer(GL_FRAMEBUFFER, glAttachment, attachment.texture, attachment.mipLevel, attachment.layer); - } else { - glFramebufferTexture(GL_FRAMEBUFFER, glAttachment, attachment.texture, attachment.mipLevel); - } - break; - } + AttachFramebufferTexture(glAttachment, attachment); drawBuffers[drawBufferCount++] = glAttachment; } if (desc.depthAttachment.texture != 0) { GLenum depthAttachment = ToOpenGLDepthAttachment(); - if (desc.stencilAttachment.texture != 0) { + if (desc.depthAttachment.type == FramebufferAttachmentType::DepthStencil || desc.stencilAttachment.texture != 0) { depthAttachment = ToOpenGLDepthStencilAttachment(); } - - if (desc.depthAttachment.layer == 0 && desc.depthAttachment.mipLevel == 0) { - glFramebufferTexture2D(GL_FRAMEBUFFER, depthAttachment, GL_TEXTURE_2D, desc.depthAttachment.texture, 0); - } else if (desc.depthAttachment.layer > 0) { - glFramebufferTextureLayer(GL_FRAMEBUFFER, depthAttachment, desc.depthAttachment.texture, desc.depthAttachment.mipLevel, desc.depthAttachment.layer); - } else { - glFramebufferTexture(GL_FRAMEBUFFER, depthAttachment, desc.depthAttachment.texture, desc.depthAttachment.mipLevel); - } + AttachFramebufferTexture(depthAttachment, desc.depthAttachment); } - if (desc.stencilAttachment.texture != 0 && desc.stencilAttachment.texture != desc.depthAttachment.texture) { + if (desc.stencilAttachment.texture != 0 && + (desc.stencilAttachment.texture != desc.depthAttachment.texture || + desc.depthAttachment.type != FramebufferAttachmentType::DepthStencil)) { GLenum stencilAttachment = ToOpenGLStencilAttachment(); - if (desc.stencilAttachment.layer == 0 && desc.stencilAttachment.mipLevel == 0) { - glFramebufferTexture2D(GL_FRAMEBUFFER, stencilAttachment, GL_TEXTURE_2D, desc.stencilAttachment.texture, 0); - } else if (desc.stencilAttachment.layer > 0) { - glFramebufferTextureLayer(GL_FRAMEBUFFER, stencilAttachment, desc.stencilAttachment.texture, desc.stencilAttachment.mipLevel, desc.stencilAttachment.layer); - } else { - glFramebufferTexture(GL_FRAMEBUFFER, stencilAttachment, desc.stencilAttachment.texture, desc.stencilAttachment.mipLevel); - } + AttachFramebufferTexture(stencilAttachment, desc.stencilAttachment); } if (drawBufferCount > 0) { @@ -118,9 +128,9 @@ bool OpenGLFramebuffer::Initialize(class RHIRenderPass* renderPass, uint32_t wid if (colorAttachments[i]) { OpenGLResourceView* view = static_cast(colorAttachments[i]); GLenum glAttachment = ToOpenGLColorAttachment(i); - unsigned int texture = view->GetTexture(); - if (texture) { - glFramebufferTexture2D(GL_FRAMEBUFFER, glAttachment, GL_TEXTURE_2D, texture, 0); + const auto& attachment = view->GetFramebufferAttachment(); + if (attachment.texture != 0) { + AttachFramebufferTexture(glAttachment, attachment); } drawBuffers[drawBufferCount++] = glAttachment; } @@ -128,9 +138,13 @@ bool OpenGLFramebuffer::Initialize(class RHIRenderPass* renderPass, uint32_t wid if (depthStencilAttachment) { OpenGLResourceView* view = static_cast(depthStencilAttachment); - unsigned int texture = view->GetTexture(); - if (texture) { - glFramebufferTexture2D(GL_FRAMEBUFFER, ToOpenGLDepthStencilAttachment(), GL_TEXTURE_2D, texture, 0); + const auto& attachment = view->GetFramebufferAttachment(); + if (attachment.texture != 0) { + const GLenum depthAttachment = + attachment.type == FramebufferAttachmentType::DepthStencil + ? ToOpenGLDepthStencilAttachment() + : ToOpenGLDepthAttachment(); + AttachFramebufferTexture(depthAttachment, attachment); } } diff --git a/engine/src/RHI/OpenGL/OpenGLResourceView.cpp b/engine/src/RHI/OpenGL/OpenGLResourceView.cpp index f7cf1492..5880dd57 100644 --- a/engine/src/RHI/OpenGL/OpenGLResourceView.cpp +++ b/engine/src/RHI/OpenGL/OpenGLResourceView.cpp @@ -10,6 +10,53 @@ namespace XCEngine { namespace RHI { +namespace { + +GLenum ResolveFramebufferTextureTarget(OpenGLTextureType textureType, const ResourceViewDesc& desc) { + switch (textureType) { + case OpenGLTextureType::Texture1D: + return GL_TEXTURE_1D; + case OpenGLTextureType::Texture2D: + return GL_TEXTURE_2D; + case OpenGLTextureType::TextureCube: { + const uint32_t face = desc.firstArraySlice < 6 ? desc.firstArraySlice : 0; + return GL_TEXTURE_CUBE_MAP_POSITIVE_X + face; + } + case OpenGLTextureType::Texture2DArray: + case OpenGLTextureType::Texture3D: + case OpenGLTextureType::TextureCubeArray: + return 0; + default: + return GL_TEXTURE_2D; + } +} + +int ResolveFramebufferAttachmentLayer(OpenGLTextureType textureType, const ResourceViewDesc& desc) { + switch (textureType) { + case OpenGLTextureType::Texture2DArray: + case OpenGLTextureType::Texture3D: + case OpenGLTextureType::TextureCubeArray: + return static_cast(desc.firstArraySlice); + default: + return -1; + } +} + +Format ResolveViewFormat(OpenGLTexture* texture, const ResourceViewDesc& desc) { + if (desc.format != 0) { + return static_cast(desc.format); + } + return texture ? texture->GetFormat() : Format::Unknown; +} + +FramebufferAttachmentType ResolveDepthAttachmentType(Format format) { + return format == Format::D24_UNorm_S8_UInt + ? FramebufferAttachmentType::DepthStencil + : FramebufferAttachmentType::Depth; +} + +} // namespace + OpenGLResourceView::OpenGLResourceView() : m_viewType(ResourceViewType::RenderTarget) , m_format(Format::Unknown) @@ -47,6 +94,7 @@ void OpenGLResourceView::Shutdown() { m_framebuffer = nullptr; m_textureUnitAllocator = nullptr; m_uniformBufferManager = nullptr; + m_framebufferAttachment = {}; m_framebufferID = 0; m_texture = nullptr; m_buffer = nullptr; @@ -101,11 +149,17 @@ bool OpenGLResourceView::InitializeAsRenderTarget( } m_viewType = ResourceViewType::RenderTarget; - m_format = static_cast(desc.format); + m_format = ResolveViewFormat(texture, desc); m_dimension = desc.dimension; m_texture = texture; m_framebuffer = framebuffer; m_framebufferID = framebuffer->GetFramebuffer(); + m_framebufferAttachment.texture = texture->GetID(); + m_framebufferAttachment.target = ResolveFramebufferTextureTarget(texture->GetOpenGLType(), desc); + m_framebufferAttachment.mipLevel = static_cast(desc.mipLevel); + m_framebufferAttachment.layer = ResolveFramebufferAttachmentLayer(texture->GetOpenGLType(), desc); + m_framebufferAttachment.type = FramebufferAttachmentType::Color; + m_framebufferAttachment.format = static_cast(m_format); return true; } @@ -118,11 +172,17 @@ bool OpenGLResourceView::InitializeAsDepthStencil( } m_viewType = ResourceViewType::DepthStencil; - m_format = static_cast(desc.format); + m_format = ResolveViewFormat(texture, desc); m_dimension = desc.dimension; m_texture = texture; m_framebuffer = framebuffer; m_framebufferID = framebuffer->GetFramebuffer(); + m_framebufferAttachment.texture = texture->GetID(); + m_framebufferAttachment.target = ResolveFramebufferTextureTarget(texture->GetOpenGLType(), desc); + m_framebufferAttachment.mipLevel = static_cast(desc.mipLevel); + m_framebufferAttachment.layer = ResolveFramebufferAttachmentLayer(texture->GetOpenGLType(), desc); + m_framebufferAttachment.type = ResolveDepthAttachmentType(m_format); + m_framebufferAttachment.format = static_cast(m_format); return true; } @@ -135,7 +195,7 @@ bool OpenGLResourceView::InitializeAsShaderResource( } m_viewType = ResourceViewType::ShaderResource; - m_format = static_cast(desc.format); + m_format = ResolveViewFormat(texture, desc); m_dimension = desc.dimension; m_texture = texture; m_textureUnit = -1; @@ -157,7 +217,7 @@ bool OpenGLResourceView::InitializeAsUnorderedAccess( } m_viewType = ResourceViewType::UnorderedAccess; - m_format = static_cast(desc.format); + m_format = ResolveViewFormat(texture, desc); m_dimension = desc.dimension; m_texture = texture; m_textureUnit = unit; diff --git a/tests/RHI/unit/test_command_list.cpp b/tests/RHI/unit/test_command_list.cpp index fb5e356f..9ebf0d77 100644 --- a/tests/RHI/unit/test_command_list.cpp +++ b/tests/RHI/unit/test_command_list.cpp @@ -7,6 +7,7 @@ #include "XCEngine/RHI/RHIRenderPass.h" #include "XCEngine/RHI/RHIFramebuffer.h" #include "XCEngine/RHI/OpenGL/OpenGLDevice.h" +#include "XCEngine/RHI/OpenGL/OpenGLResourceView.h" #include using namespace XCEngine::RHI; @@ -363,6 +364,145 @@ TEST_P(RHITestFixture, CommandList_SetRenderTargets_WithRealViews) { delete texture; } +TEST_P(RHITestFixture, CommandList_SetRenderTargets_BindsColorAndDepthAttachmentsOnOpenGL) { + if (GetBackendType() != RHIType::OpenGL) { + GTEST_SKIP() << "OpenGL-specific framebuffer attachment verification"; + } + + TextureDesc colorDesc = {}; + colorDesc.width = 128; + colorDesc.height = 128; + colorDesc.format = static_cast(Format::R8G8B8A8_UNorm); + colorDesc.textureType = static_cast(TextureType::Texture2D); + + TextureDesc depthDesc = {}; + depthDesc.width = 128; + depthDesc.height = 128; + depthDesc.format = static_cast(Format::D24_UNorm_S8_UInt); + depthDesc.textureType = static_cast(TextureType::Texture2D); + + RHITexture* colorTexture = GetDevice()->CreateTexture(colorDesc); + RHITexture* depthTexture = GetDevice()->CreateTexture(depthDesc); + ASSERT_NE(colorTexture, nullptr); + ASSERT_NE(depthTexture, nullptr); + + ResourceViewDesc colorViewDesc = {}; + colorViewDesc.format = colorDesc.format; + RHIResourceView* rtv = GetDevice()->CreateRenderTargetView(colorTexture, colorViewDesc); + + ResourceViewDesc depthViewDesc = {}; + depthViewDesc.format = depthDesc.format; + RHIResourceView* dsv = GetDevice()->CreateDepthStencilView(depthTexture, depthViewDesc); + ASSERT_NE(rtv, nullptr); + ASSERT_NE(dsv, nullptr); + + CommandListDesc cmdDesc = {}; + cmdDesc.commandListType = static_cast(CommandQueueType::Direct); + RHICommandList* cmdList = GetDevice()->CreateCommandList(cmdDesc); + ASSERT_NE(cmdList, nullptr); + + cmdList->Reset(); + cmdList->SetRenderTargets(1, &rtv, dsv); + + GLint framebuffer = 0; + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &framebuffer); + EXPECT_NE(framebuffer, 0); + EXPECT_EQ(glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER), GL_FRAMEBUFFER_COMPLETE); + + GLint colorAttachment = 0; + glGetFramebufferAttachmentParameteriv( + GL_DRAW_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0, + GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, + &colorAttachment); + EXPECT_EQ(static_cast(colorAttachment), static_cast(rtv)->GetTexture()); + + GLint depthAttachment = 0; + glGetFramebufferAttachmentParameteriv( + GL_DRAW_FRAMEBUFFER, + GL_DEPTH_ATTACHMENT, + GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, + &depthAttachment); + EXPECT_EQ(static_cast(depthAttachment), static_cast(dsv)->GetTexture()); + + cmdList->Close(); + + cmdList->Shutdown(); + delete cmdList; + delete dsv; + delete rtv; + depthTexture->Shutdown(); + delete depthTexture; + colorTexture->Shutdown(); + delete colorTexture; +} + +TEST_P(RHITestFixture, CommandList_SetRenderTargets_BindsMultipleColorAttachmentsOnOpenGL) { + if (GetBackendType() != RHIType::OpenGL) { + GTEST_SKIP() << "OpenGL-specific multiple render target verification"; + } + + TextureDesc texDesc = {}; + texDesc.width = 128; + texDesc.height = 128; + texDesc.format = static_cast(Format::R8G8B8A8_UNorm); + texDesc.textureType = static_cast(TextureType::Texture2D); + + RHITexture* texture0 = GetDevice()->CreateTexture(texDesc); + RHITexture* texture1 = GetDevice()->CreateTexture(texDesc); + ASSERT_NE(texture0, nullptr); + ASSERT_NE(texture1, nullptr); + + ResourceViewDesc viewDesc = {}; + viewDesc.format = texDesc.format; + RHIResourceView* rtv0 = GetDevice()->CreateRenderTargetView(texture0, viewDesc); + RHIResourceView* rtv1 = GetDevice()->CreateRenderTargetView(texture1, viewDesc); + ASSERT_NE(rtv0, nullptr); + ASSERT_NE(rtv1, nullptr); + + RHIResourceView* rtvs[] = { rtv0, rtv1 }; + + CommandListDesc cmdDesc = {}; + cmdDesc.commandListType = static_cast(CommandQueueType::Direct); + RHICommandList* cmdList = GetDevice()->CreateCommandList(cmdDesc); + ASSERT_NE(cmdList, nullptr); + + cmdList->Reset(); + cmdList->SetRenderTargets(2, rtvs, nullptr); + + GLint framebuffer = 0; + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &framebuffer); + EXPECT_NE(framebuffer, 0); + EXPECT_EQ(glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER), GL_FRAMEBUFFER_COMPLETE); + + GLint colorAttachment0 = 0; + glGetFramebufferAttachmentParameteriv( + GL_DRAW_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0, + GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, + &colorAttachment0); + EXPECT_EQ(static_cast(colorAttachment0), static_cast(rtv0)->GetTexture()); + + GLint colorAttachment1 = 0; + glGetFramebufferAttachmentParameteriv( + GL_DRAW_FRAMEBUFFER, + GL_COLOR_ATTACHMENT1, + GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, + &colorAttachment1); + EXPECT_EQ(static_cast(colorAttachment1), static_cast(rtv1)->GetTexture()); + + cmdList->Close(); + + cmdList->Shutdown(); + delete cmdList; + delete rtv1; + delete rtv0; + texture1->Shutdown(); + delete texture1; + texture0->Shutdown(); + delete texture0; +} + TEST_P(RHITestFixture, CommandList_CopyResource_WithRealResources) { TextureDesc texDesc = {}; texDesc.width = 256;