diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 1e3cee57..2c672694 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -164,6 +164,8 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanFence.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanCommandQueue.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanCommandList.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanRenderPass.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanFramebuffer.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanSwapChain.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanDevice.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanScreenshot.h @@ -177,6 +179,8 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanResourceView.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanCommandQueue.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanCommandList.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanRenderPass.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanFramebuffer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanSwapChain.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanDevice.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanScreenshot.cpp @@ -324,6 +328,7 @@ add_library(XCEngine STATIC # Rendering ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/RenderContext.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/RenderCameraData.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/CameraRenderRequest.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/VisibleRenderObject.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/RenderSceneExtractor.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/RenderPipeline.h diff --git a/engine/include/XCEngine/RHI/Vulkan/VulkanFramebuffer.h b/engine/include/XCEngine/RHI/Vulkan/VulkanFramebuffer.h new file mode 100644 index 00000000..23ee2a05 --- /dev/null +++ b/engine/include/XCEngine/RHI/Vulkan/VulkanFramebuffer.h @@ -0,0 +1,52 @@ +#pragma once + +#include "XCEngine/RHI/RHIFramebuffer.h" +#include "XCEngine/RHI/Vulkan/VulkanCommon.h" + +#include + +namespace XCEngine { +namespace RHI { + +class VulkanRenderPass; +class VulkanResourceView; +class VulkanTexture; + +class VulkanFramebuffer : public RHIFramebuffer { +public: + VulkanFramebuffer() = default; + ~VulkanFramebuffer() override; + + bool Initialize(VkDevice device, RHIRenderPass* renderPass, uint32_t width, uint32_t height, + uint32_t colorAttachmentCount, RHIResourceView** colorAttachments, + RHIResourceView* depthStencilAttachment); + bool Initialize(RHIRenderPass* renderPass, uint32_t width, uint32_t height, + uint32_t colorAttachmentCount, RHIResourceView** colorAttachments, + RHIResourceView* depthStencilAttachment) override; + void Shutdown() override; + + void* GetNativeHandle() override { return m_framebuffer; } + uint32_t GetWidth() const override { return m_width; } + uint32_t GetHeight() const override { return m_height; } + bool IsValid() const override { return m_framebuffer != VK_NULL_HANDLE; } + + VkFramebuffer GetFramebuffer() const { return m_framebuffer; } + VulkanRenderPass* GetRenderPass() const { return m_renderPass; } + uint32_t GetColorAttachmentCount() const { return static_cast(m_colorAttachmentViews.size()); } + VulkanResourceView* GetColorAttachmentView(uint32_t index) const; + VulkanResourceView* GetDepthStencilView() const { return m_depthStencilView; } + VulkanTexture* GetColorAttachmentTexture(uint32_t index) const; + VulkanTexture* GetDepthStencilTexture() const; + +private: + VkDevice m_device = VK_NULL_HANDLE; + VkFramebuffer m_framebuffer = VK_NULL_HANDLE; + VulkanRenderPass* m_renderPass = nullptr; + uint32_t m_width = 0; + uint32_t m_height = 0; + std::vector m_colorAttachmentViews; + VulkanResourceView* m_depthStencilView = nullptr; +}; + +} // namespace RHI +} // namespace XCEngine diff --git a/engine/include/XCEngine/RHI/Vulkan/VulkanRenderPass.h b/engine/include/XCEngine/RHI/Vulkan/VulkanRenderPass.h new file mode 100644 index 00000000..9570a461 --- /dev/null +++ b/engine/include/XCEngine/RHI/Vulkan/VulkanRenderPass.h @@ -0,0 +1,45 @@ +#pragma once + +#include "XCEngine/RHI/RHIRenderPass.h" +#include "XCEngine/RHI/Vulkan/VulkanCommon.h" + +#include + +namespace XCEngine { +namespace RHI { + +class VulkanRenderPass : public RHIRenderPass { +public: + VulkanRenderPass() = default; + ~VulkanRenderPass() override; + + bool Initialize(VkDevice device, uint32_t colorAttachmentCount, const AttachmentDesc* colorAttachments, + const AttachmentDesc* depthStencilAttachment); + bool Initialize(uint32_t colorAttachmentCount, const AttachmentDesc* colorAttachments, + const AttachmentDesc* depthStencilAttachment) override; + void Shutdown() override; + + uint32_t GetColorAttachmentCount() const override { return m_colorAttachmentCount; } + const AttachmentDesc* GetColorAttachments() const override { + return m_colorAttachments.empty() ? nullptr : m_colorAttachments.data(); + } + const AttachmentDesc* GetDepthStencilAttachment() const override { + return m_hasDepthStencil ? &m_depthStencilAttachment : nullptr; + } + void* GetNativeHandle() override { return nullptr; } + + VkRenderPass GetRenderPass() const { return m_renderPass; } + uint32_t GetAttachmentCount() const { return m_colorAttachmentCount + (m_hasDepthStencil ? 1u : 0u); } + bool HasDepthStencil() const { return m_hasDepthStencil; } + +private: + VkDevice m_device = VK_NULL_HANDLE; + VkRenderPass m_renderPass = VK_NULL_HANDLE; + uint32_t m_colorAttachmentCount = 0; + std::vector m_colorAttachments; + AttachmentDesc m_depthStencilAttachment = {}; + bool m_hasDepthStencil = false; +}; + +} // namespace RHI +} // namespace XCEngine diff --git a/engine/include/XCEngine/Rendering/CameraRenderRequest.h b/engine/include/XCEngine/Rendering/CameraRenderRequest.h new file mode 100644 index 00000000..0d03b871 --- /dev/null +++ b/engine/include/XCEngine/Rendering/CameraRenderRequest.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include + +namespace XCEngine { +namespace Components { +class CameraComponent; +class Scene; +} // namespace Components + +namespace Rendering { + +struct CameraRenderRequest { + const Components::Scene* scene = nullptr; + Components::CameraComponent* camera = nullptr; + RenderContext context; + RenderSurface surface; + float cameraDepth = 0.0f; + + bool IsValid() const { + return scene != nullptr && + camera != nullptr && + context.IsValid(); + } +}; + +} // namespace Rendering +} // namespace XCEngine diff --git a/engine/include/XCEngine/Rendering/CameraRenderer.h b/engine/include/XCEngine/Rendering/CameraRenderer.h index 4604c6b9..373c5d0c 100644 --- a/engine/include/XCEngine/Rendering/CameraRenderer.h +++ b/engine/include/XCEngine/Rendering/CameraRenderer.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -23,11 +24,7 @@ public: void SetPipeline(std::unique_ptr pipeline); RenderPipeline* GetPipeline() const { return m_pipeline.get(); } - bool Render( - const Components::Scene& scene, - Components::CameraComponent* overrideCamera, - const RenderContext& context, - const RenderSurface& surface); + bool Render(const CameraRenderRequest& request); private: RenderSceneExtractor m_sceneExtractor; diff --git a/engine/include/XCEngine/Rendering/RenderSceneExtractor.h b/engine/include/XCEngine/Rendering/RenderSceneExtractor.h index fafd138a..5d679f03 100644 --- a/engine/include/XCEngine/Rendering/RenderSceneExtractor.h +++ b/engine/include/XCEngine/Rendering/RenderSceneExtractor.h @@ -31,11 +31,16 @@ public: Components::CameraComponent* overrideCamera, uint32_t viewportWidth, uint32_t viewportHeight) const; - -private: + RenderSceneData ExtractForCamera( + const Components::Scene& scene, + Components::CameraComponent& camera, + uint32_t viewportWidth, + uint32_t viewportHeight) const; Components::CameraComponent* SelectCamera( const Components::Scene& scene, Components::CameraComponent* overrideCamera) const; + +private: RenderCameraData BuildCameraData( const Components::CameraComponent& camera, uint32_t viewportWidth, diff --git a/engine/include/XCEngine/Rendering/SceneRenderer.h b/engine/include/XCEngine/Rendering/SceneRenderer.h index 7b02089a..a1ce1a54 100644 --- a/engine/include/XCEngine/Rendering/SceneRenderer.h +++ b/engine/include/XCEngine/Rendering/SceneRenderer.h @@ -1,7 +1,10 @@ #pragma once +#include #include +#include + namespace XCEngine { namespace Components { class CameraComponent; @@ -19,6 +22,14 @@ public: void SetPipeline(std::unique_ptr pipeline); RenderPipeline* GetPipeline() const { return m_cameraRenderer.GetPipeline(); } + std::vector BuildRenderRequests( + const Components::Scene& scene, + Components::CameraComponent* overrideCamera, + const RenderContext& context, + const RenderSurface& surface) const; + + bool Render(const CameraRenderRequest& request); + bool Render(const std::vector& requests); bool Render( const Components::Scene& scene, Components::CameraComponent* overrideCamera, @@ -26,6 +37,7 @@ public: const RenderSurface& surface); private: + RenderSceneExtractor m_sceneExtractor; CameraRenderer m_cameraRenderer; }; diff --git a/engine/src/RHI/Vulkan/VulkanFramebuffer.cpp b/engine/src/RHI/Vulkan/VulkanFramebuffer.cpp new file mode 100644 index 00000000..f67987bc --- /dev/null +++ b/engine/src/RHI/Vulkan/VulkanFramebuffer.cpp @@ -0,0 +1,115 @@ +#include "XCEngine/RHI/Vulkan/VulkanFramebuffer.h" + +#include "XCEngine/RHI/Vulkan/VulkanRenderPass.h" +#include "XCEngine/RHI/Vulkan/VulkanResourceView.h" +#include "XCEngine/RHI/Vulkan/VulkanTexture.h" + +#include + +namespace XCEngine { +namespace RHI { + +VulkanFramebuffer::~VulkanFramebuffer() { + Shutdown(); +} + +bool VulkanFramebuffer::Initialize(VkDevice device, RHIRenderPass* renderPass, uint32_t width, uint32_t height, + uint32_t colorAttachmentCount, RHIResourceView** colorAttachments, + RHIResourceView* depthStencilAttachment) { + if (device == VK_NULL_HANDLE || renderPass == nullptr || width == 0 || height == 0) { + return false; + } + if (colorAttachmentCount > 0 && colorAttachments == nullptr) { + return false; + } + + auto* vulkanRenderPass = static_cast(renderPass); + if (vulkanRenderPass == nullptr || vulkanRenderPass->GetRenderPass() == VK_NULL_HANDLE) { + return false; + } + + Shutdown(); + + m_device = device; + m_renderPass = vulkanRenderPass; + m_width = width; + m_height = height; + m_colorAttachmentViews.reserve(colorAttachmentCount); + + std::vector attachments; + attachments.reserve(colorAttachmentCount + (depthStencilAttachment != nullptr ? 1u : 0u)); + + for (uint32_t i = 0; i < colorAttachmentCount; ++i) { + auto* view = static_cast(colorAttachments[i]); + if (view == nullptr || view->GetImageView() == VK_NULL_HANDLE) { + Shutdown(); + return false; + } + m_colorAttachmentViews.push_back(view); + attachments.push_back(view->GetImageView()); + } + + m_depthStencilView = static_cast(depthStencilAttachment); + if (m_depthStencilView != nullptr) { + if (m_depthStencilView->GetImageView() == VK_NULL_HANDLE) { + Shutdown(); + return false; + } + attachments.push_back(m_depthStencilView->GetImageView()); + } + + VkFramebufferCreateInfo createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + createInfo.renderPass = vulkanRenderPass->GetRenderPass(); + createInfo.attachmentCount = static_cast(attachments.size()); + createInfo.pAttachments = attachments.empty() ? nullptr : attachments.data(); + createInfo.width = width; + createInfo.height = height; + createInfo.layers = 1; + + if (vkCreateFramebuffer(device, &createInfo, nullptr, &m_framebuffer) != VK_SUCCESS) { + Shutdown(); + return false; + } + + return true; +} + +bool VulkanFramebuffer::Initialize(RHIRenderPass* renderPass, uint32_t width, uint32_t height, + uint32_t colorAttachmentCount, RHIResourceView** colorAttachments, + RHIResourceView* depthStencilAttachment) { + return Initialize(m_device, renderPass, width, height, colorAttachmentCount, colorAttachments, depthStencilAttachment); +} + +void VulkanFramebuffer::Shutdown() { + if (m_framebuffer != VK_NULL_HANDLE && m_device != VK_NULL_HANDLE) { + vkDestroyFramebuffer(m_device, m_framebuffer, nullptr); + } + + m_framebuffer = VK_NULL_HANDLE; + m_device = VK_NULL_HANDLE; + m_renderPass = nullptr; + m_width = 0; + m_height = 0; + m_colorAttachmentViews.clear(); + m_depthStencilView = nullptr; +} + +VulkanResourceView* VulkanFramebuffer::GetColorAttachmentView(uint32_t index) const { + if (index >= m_colorAttachmentViews.size()) { + return nullptr; + } + return m_colorAttachmentViews[index]; +} + +VulkanTexture* VulkanFramebuffer::GetColorAttachmentTexture(uint32_t index) const { + VulkanResourceView* view = GetColorAttachmentView(index); + return view != nullptr ? view->GetTexture() : nullptr; +} + +VulkanTexture* VulkanFramebuffer::GetDepthStencilTexture() const { + return m_depthStencilView != nullptr ? m_depthStencilView->GetTexture() : nullptr; +} + +} // namespace RHI +} // namespace XCEngine diff --git a/engine/src/RHI/Vulkan/VulkanRenderPass.cpp b/engine/src/RHI/Vulkan/VulkanRenderPass.cpp new file mode 100644 index 00000000..34a62ebc --- /dev/null +++ b/engine/src/RHI/Vulkan/VulkanRenderPass.cpp @@ -0,0 +1,156 @@ +#include "XCEngine/RHI/Vulkan/VulkanRenderPass.h" + +#include + +namespace XCEngine { +namespace RHI { + +namespace { + +VkAttachmentLoadOp ToVulkanLoadOp(LoadAction loadAction) { + switch (loadAction) { + case LoadAction::Clear: + return VK_ATTACHMENT_LOAD_OP_CLEAR; + case LoadAction::Load: + return VK_ATTACHMENT_LOAD_OP_LOAD; + case LoadAction::Undefined: + default: + return VK_ATTACHMENT_LOAD_OP_DONT_CARE; + } +} + +VkAttachmentStoreOp ToVulkanStoreOp(StoreAction storeAction) { + switch (storeAction) { + case StoreAction::Store: + case StoreAction::Resolve: + case StoreAction::StoreAndResolve: + return VK_ATTACHMENT_STORE_OP_STORE; + case StoreAction::Discard: + case StoreAction::Undefined: + default: + return VK_ATTACHMENT_STORE_OP_DONT_CARE; + } +} + +} // namespace + +VulkanRenderPass::~VulkanRenderPass() { + Shutdown(); +} + +bool VulkanRenderPass::Initialize(VkDevice device, uint32_t colorAttachmentCount, const AttachmentDesc* colorAttachments, + const AttachmentDesc* depthStencilAttachment) { + if (device == VK_NULL_HANDLE) { + return false; + } + if (colorAttachmentCount > 0 && colorAttachments == nullptr) { + return false; + } + + Shutdown(); + + m_device = device; + m_colorAttachmentCount = colorAttachmentCount; + m_colorAttachments.assign(colorAttachments, colorAttachments + colorAttachmentCount); + if (depthStencilAttachment != nullptr) { + m_depthStencilAttachment = *depthStencilAttachment; + m_hasDepthStencil = true; + } + + std::vector attachments; + attachments.reserve(colorAttachmentCount + (m_hasDepthStencil ? 1u : 0u)); + + std::vector colorAttachmentRefs; + colorAttachmentRefs.reserve(colorAttachmentCount); + + for (uint32_t i = 0; i < colorAttachmentCount; ++i) { + const AttachmentDesc& attachmentDesc = m_colorAttachments[i]; + const VkFormat format = ToVulkanFormat(attachmentDesc.format); + if (format == VK_FORMAT_UNDEFINED) { + Shutdown(); + return false; + } + + VkAttachmentDescription attachment = {}; + attachment.format = format; + attachment.samples = VK_SAMPLE_COUNT_1_BIT; + attachment.loadOp = ToVulkanLoadOp(attachmentDesc.loadOp); + attachment.storeOp = ToVulkanStoreOp(attachmentDesc.storeOp); + attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + attachment.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + attachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + attachments.push_back(attachment); + + VkAttachmentReference attachmentRef = {}; + attachmentRef.attachment = i; + attachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + colorAttachmentRefs.push_back(attachmentRef); + } + + VkAttachmentReference depthAttachmentRef = {}; + if (m_hasDepthStencil) { + const VkFormat format = ToVulkanFormat(m_depthStencilAttachment.format); + if (format == VK_FORMAT_UNDEFINED) { + Shutdown(); + return false; + } + + VkAttachmentDescription attachment = {}; + attachment.format = format; + attachment.samples = VK_SAMPLE_COUNT_1_BIT; + attachment.loadOp = ToVulkanLoadOp(m_depthStencilAttachment.loadOp); + attachment.storeOp = ToVulkanStoreOp(m_depthStencilAttachment.storeOp); + attachment.stencilLoadOp = ToVulkanLoadOp(m_depthStencilAttachment.stencilLoadOp); + attachment.stencilStoreOp = ToVulkanStoreOp(m_depthStencilAttachment.stencilStoreOp); + attachment.initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + attachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + attachments.push_back(attachment); + + depthAttachmentRef.attachment = colorAttachmentCount; + depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + } + + VkSubpassDescription subpass = {}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = static_cast(colorAttachmentRefs.size()); + subpass.pColorAttachments = colorAttachmentRefs.empty() ? nullptr : colorAttachmentRefs.data(); + if (m_hasDepthStencil) { + subpass.pDepthStencilAttachment = &depthAttachmentRef; + } + + VkRenderPassCreateInfo createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + createInfo.attachmentCount = static_cast(attachments.size()); + createInfo.pAttachments = attachments.empty() ? nullptr : attachments.data(); + createInfo.subpassCount = 1; + createInfo.pSubpasses = &subpass; + + if (vkCreateRenderPass(device, &createInfo, nullptr, &m_renderPass) != VK_SUCCESS) { + Shutdown(); + return false; + } + + return true; +} + +bool VulkanRenderPass::Initialize(uint32_t colorAttachmentCount, const AttachmentDesc* colorAttachments, + const AttachmentDesc* depthStencilAttachment) { + return Initialize(m_device, colorAttachmentCount, colorAttachments, depthStencilAttachment); +} + +void VulkanRenderPass::Shutdown() { + if (m_renderPass != VK_NULL_HANDLE && m_device != VK_NULL_HANDLE) { + vkDestroyRenderPass(m_device, m_renderPass, nullptr); + } + + m_renderPass = VK_NULL_HANDLE; + m_device = VK_NULL_HANDLE; + m_colorAttachmentCount = 0; + m_colorAttachments.clear(); + m_depthStencilAttachment = {}; + m_hasDepthStencil = false; +} + +} // namespace RHI +} // namespace XCEngine diff --git a/engine/src/Rendering/CameraRenderer.cpp b/engine/src/Rendering/CameraRenderer.cpp index 686a5f9a..843cb0fb 100644 --- a/engine/src/Rendering/CameraRenderer.cpp +++ b/engine/src/Rendering/CameraRenderer.cpp @@ -36,24 +36,21 @@ void CameraRenderer::SetPipeline(std::unique_ptr pipeline) { } bool CameraRenderer::Render( - const Components::Scene& scene, - Components::CameraComponent* overrideCamera, - const RenderContext& context, - const RenderSurface& surface) { - if (!context.IsValid() || m_pipeline == nullptr) { + const CameraRenderRequest& request) { + if (!request.IsValid() || m_pipeline == nullptr) { return false; } - RenderSceneData sceneData = m_sceneExtractor.Extract( - scene, - overrideCamera, - surface.GetWidth(), - surface.GetHeight()); + RenderSceneData sceneData = m_sceneExtractor.ExtractForCamera( + *request.scene, + *request.camera, + request.surface.GetWidth(), + request.surface.GetHeight()); if (!sceneData.HasCamera()) { return false; } - return m_pipeline->Render(context, surface, sceneData); + return m_pipeline->Render(request.context, request.surface, sceneData); } } // namespace Rendering diff --git a/engine/src/Rendering/RenderSceneExtractor.cpp b/engine/src/Rendering/RenderSceneExtractor.cpp index 5db015f2..573524db 100644 --- a/engine/src/Rendering/RenderSceneExtractor.cpp +++ b/engine/src/Rendering/RenderSceneExtractor.cpp @@ -70,6 +70,33 @@ RenderSceneData RenderSceneExtractor::Extract( return sceneData; } +RenderSceneData RenderSceneExtractor::ExtractForCamera( + const Components::Scene& scene, + Components::CameraComponent& camera, + uint32_t viewportWidth, + uint32_t viewportHeight) const { + RenderSceneData sceneData; + if (!IsUsableCamera(&camera)) { + return sceneData; + } + + sceneData.camera = &camera; + sceneData.cameraData = BuildCameraData(camera, viewportWidth, viewportHeight); + const Math::Vector3 cameraPosition = sceneData.cameraData.worldPosition; + + const std::vector rootGameObjects = scene.GetRootGameObjects(); + for (Components::GameObject* rootGameObject : rootGameObjects) { + ExtractVisibleItems(rootGameObject, cameraPosition, sceneData.visibleItems); + } + + std::stable_sort( + sceneData.visibleItems.begin(), + sceneData.visibleItems.end(), + CompareVisibleItems); + + return sceneData; +} + Components::CameraComponent* RenderSceneExtractor::SelectCamera( const Components::Scene& scene, Components::CameraComponent* overrideCamera) const { diff --git a/engine/src/Rendering/SceneRenderer.cpp b/engine/src/Rendering/SceneRenderer.cpp index a3a61811..74ddb803 100644 --- a/engine/src/Rendering/SceneRenderer.cpp +++ b/engine/src/Rendering/SceneRenderer.cpp @@ -1,8 +1,24 @@ #include "Rendering/SceneRenderer.h" +#include "Components/CameraComponent.h" + +#include + namespace XCEngine { namespace Rendering { +namespace { + +bool CompareCameraRenderRequest(const CameraRenderRequest& lhs, const CameraRenderRequest& rhs) { + if (lhs.cameraDepth != rhs.cameraDepth) { + return lhs.cameraDepth < rhs.cameraDepth; + } + + return lhs.camera < rhs.camera; +} + +} // namespace + SceneRenderer::SceneRenderer() = default; SceneRenderer::SceneRenderer(std::unique_ptr pipeline) @@ -13,12 +29,66 @@ void SceneRenderer::SetPipeline(std::unique_ptr pipeline) { m_cameraRenderer.SetPipeline(std::move(pipeline)); } +std::vector SceneRenderer::BuildRenderRequests( + const Components::Scene& scene, + Components::CameraComponent* overrideCamera, + const RenderContext& context, + const RenderSurface& surface) const { + std::vector requests; + + Components::CameraComponent* camera = m_sceneExtractor.SelectCamera(scene, overrideCamera); + if (camera == nullptr) { + return requests; + } + + CameraRenderRequest request; + request.scene = &scene; + request.camera = camera; + request.context = context; + request.surface = surface; + request.cameraDepth = camera->GetDepth(); + requests.push_back(request); + return requests; +} + +bool SceneRenderer::Render(const CameraRenderRequest& request) { + return m_cameraRenderer.Render(request); +} + +bool SceneRenderer::Render(const std::vector& requests) { + if (requests.empty()) { + return false; + } + + for (const CameraRenderRequest& request : requests) { + if (!request.IsValid()) { + return false; + } + } + + std::vector sortedRequests = requests; + std::stable_sort( + sortedRequests.begin(), + sortedRequests.end(), + CompareCameraRenderRequest); + + bool rendered = false; + for (const CameraRenderRequest& request : sortedRequests) { + if (!m_cameraRenderer.Render(request)) { + return false; + } + rendered = true; + } + + return rendered; +} + bool SceneRenderer::Render( const Components::Scene& scene, Components::CameraComponent* overrideCamera, const RenderContext& context, const RenderSurface& surface) { - return m_cameraRenderer.Render(scene, overrideCamera, context, surface); + return Render(BuildRenderRequests(scene, overrideCamera, context, surface)); } } // namespace Rendering diff --git a/tests/Rendering/unit/test_camera_scene_renderer.cpp b/tests/Rendering/unit/test_camera_scene_renderer.cpp index 5c7b2513..84058873 100644 --- a/tests/Rendering/unit/test_camera_scene_renderer.cpp +++ b/tests/Rendering/unit/test_camera_scene_renderer.cpp @@ -7,6 +7,7 @@ #include #include +#include using namespace XCEngine::Components; using namespace XCEngine::Rendering; @@ -21,6 +22,7 @@ struct MockPipelineState { uint32_t lastSurfaceHeight = 0; CameraComponent* lastCamera = nullptr; size_t lastVisibleItemCount = 0; + std::vector renderedCameras; }; class MockPipeline final : public RenderPipeline { @@ -47,6 +49,7 @@ public: m_state->lastSurfaceHeight = surface.GetHeight(); m_state->lastCamera = sceneData.camera; m_state->lastVisibleItemCount = sceneData.visibleItems.size(); + m_state->renderedCameras.push_back(sceneData.camera); return true; } @@ -80,8 +83,14 @@ TEST(CameraRenderer_Test, UsesOverrideCameraAndSurfaceSizeWhenSubmittingScene) { auto state = std::make_shared(); CameraRenderer renderer(std::make_unique(state)); - const RenderSurface surface(640, 480); - ASSERT_TRUE(renderer.Render(scene, overrideCamera, CreateValidContext(), surface)); + CameraRenderRequest request; + request.scene = &scene; + request.camera = overrideCamera; + request.context = CreateValidContext(); + request.surface = RenderSurface(640, 480); + request.cameraDepth = overrideCamera->GetDepth(); + + ASSERT_TRUE(renderer.Render(request)); EXPECT_EQ(state->renderCalls, 1); EXPECT_EQ(state->lastSurfaceWidth, 640u); EXPECT_EQ(state->lastSurfaceHeight, 480u); @@ -90,6 +99,37 @@ TEST(CameraRenderer_Test, UsesOverrideCameraAndSurfaceSizeWhenSubmittingScene) { EXPECT_EQ(state->lastVisibleItemCount, 0u); } +TEST(SceneRenderer_Test, BuildsSingleExplicitRequestFromSelectedCamera) { + Scene scene("SceneRendererRequestScene"); + + GameObject* lowCameraObject = scene.CreateGameObject("LowCamera"); + auto* lowCamera = lowCameraObject->AddComponent(); + lowCamera->SetPrimary(true); + lowCamera->SetDepth(1.0f); + + GameObject* highCameraObject = scene.CreateGameObject("HighCamera"); + auto* highCamera = highCameraObject->AddComponent(); + highCamera->SetPrimary(true); + highCamera->SetDepth(5.0f); + + SceneRenderer renderer; + const RenderContext context = CreateValidContext(); + const RenderSurface surface(320, 180); + + const std::vector defaultRequests = + renderer.BuildRenderRequests(scene, nullptr, context, surface); + ASSERT_EQ(defaultRequests.size(), 1u); + EXPECT_EQ(defaultRequests[0].camera, highCamera); + EXPECT_EQ(defaultRequests[0].cameraDepth, 5.0f); + EXPECT_EQ(defaultRequests[0].surface.GetWidth(), 320u); + EXPECT_EQ(defaultRequests[0].surface.GetHeight(), 180u); + + const std::vector overrideRequests = + renderer.BuildRenderRequests(scene, lowCamera, context, surface); + ASSERT_EQ(overrideRequests.size(), 1u); + EXPECT_EQ(overrideRequests[0].camera, lowCamera); +} + TEST(SceneRenderer_Test, ForwardsPipelineLifetimeAndRenderCallsToCameraRenderer) { Scene scene("SceneRendererScene"); @@ -125,3 +165,37 @@ TEST(SceneRenderer_Test, ForwardsPipelineLifetimeAndRenderCallsToCameraRenderer) EXPECT_EQ(initialState->shutdownCalls, 1); EXPECT_EQ(replacementState->shutdownCalls, 1); } + +TEST(SceneRenderer_Test, SortsManualCameraRequestsByDepthBeforeRendering) { + Scene scene("SceneRendererManualRequests"); + + GameObject* farCameraObject = scene.CreateGameObject("FarCamera"); + auto* farCamera = farCameraObject->AddComponent(); + farCamera->SetPrimary(true); + farCamera->SetDepth(10.0f); + + GameObject* nearCameraObject = scene.CreateGameObject("NearCamera"); + auto* nearCamera = nearCameraObject->AddComponent(); + nearCamera->SetPrimary(false); + nearCamera->SetDepth(1.0f); + + auto state = std::make_shared(); + SceneRenderer renderer(std::make_unique(state)); + + CameraRenderRequest farRequest; + farRequest.scene = &scene; + farRequest.camera = farCamera; + farRequest.context = CreateValidContext(); + farRequest.surface = RenderSurface(800, 600); + farRequest.cameraDepth = farCamera->GetDepth(); + + CameraRenderRequest nearRequest = farRequest; + nearRequest.camera = nearCamera; + nearRequest.cameraDepth = nearCamera->GetDepth(); + + const std::vector requests = { farRequest, nearRequest }; + ASSERT_TRUE(renderer.Render(requests)); + ASSERT_EQ(state->renderedCameras.size(), 2u); + EXPECT_EQ(state->renderedCameras[0], nearCamera); + EXPECT_EQ(state->renderedCameras[1], farCamera); +}