diff --git a/editor/src/Application.cpp b/editor/src/Application.cpp index 433e470e..cdc04b63 100644 --- a/editor/src/Application.cpp +++ b/editor/src/Application.cpp @@ -68,6 +68,9 @@ void Application::InitializeImGui(HWND hwnd) { m_imguiBackend, m_windowRenderer.GetDevice(), m_windowRenderer.GetCommandQueue()); + + m_viewportHostService.Initialize(m_imguiBackend, m_windowRenderer.GetRHIDevice()); + static_cast(m_editorContext.get())->SetViewportHostService(&m_viewportHostService); } void Application::AttachEditorLayer() { @@ -94,10 +97,18 @@ void Application::ShutdownEditorContext() { void Application::RenderEditorFrame() { static constexpr float kClearColor[4] = { 0.22f, 0.22f, 0.22f, 1.0f }; m_imguiBackend.BeginFrame(); + m_viewportHostService.BeginFrame(); m_layerStack.onImGuiRender(); UpdateWindowTitle(); ImGui::Render(); - m_windowRenderer.Render(m_imguiBackend, kClearColor); + m_windowRenderer.Render( + m_imguiBackend, + kClearColor, + [this](const Rendering::RenderContext& renderContext) { + if (m_editorContext) { + m_viewportHostService.RenderRequestedViewports(*m_editorContext, renderContext); + } + }); } bool Application::Initialize(HWND hwnd) { @@ -135,6 +146,10 @@ bool Application::Initialize(HWND hwnd) { void Application::Shutdown() { m_renderReady = false; DetachEditorLayer(); + if (m_editorContext) { + static_cast(m_editorContext.get())->SetViewportHostService(nullptr); + } + m_viewportHostService.Shutdown(); UI::ShutdownBuiltInIcons(); m_imguiBackend.Shutdown(); m_imguiSession.Shutdown(); diff --git a/editor/src/Application.h b/editor/src/Application.h index 90d7cd16..e2063533 100644 --- a/editor/src/Application.h +++ b/editor/src/Application.h @@ -3,6 +3,7 @@ #include "Platform/D3D12WindowRenderer.h" #include "UI/ImGuiBackendBridge.h" #include "UI/ImGuiSession.h" +#include "Viewport/ViewportHostService.h" #include #include @@ -35,6 +36,7 @@ public: Rendering::RenderContext GetMainRenderContext() const { return m_windowRenderer.GetRenderContext(); } RHI::RHIDevice* GetMainRHIDevice() const { return m_windowRenderer.GetRHIDevice(); } RHI::RHISwapChain* GetMainSwapChain() const { return m_windowRenderer.GetSwapChain(); } + IViewportHostService& GetViewportHostService() { return m_viewportHostService; } bool IsRenderReady() const { return m_renderReady; } HWND GetWindowHandle() const { return m_hwnd; } @@ -60,6 +62,7 @@ private: Platform::D3D12WindowRenderer m_windowRenderer; UI::ImGuiBackendBridge m_imguiBackend; UI::ImGuiSession m_imguiSession; + ViewportHostService m_viewportHostService; uint64_t m_exitRequestedHandlerId = 0; std::wstring m_lastWindowTitle; bool m_renderReady = false; diff --git a/editor/src/Core/EditorContext.h b/editor/src/Core/EditorContext.h index 9a726ef0..d8134476 100644 --- a/editor/src/Core/EditorContext.h +++ b/editor/src/Core/EditorContext.h @@ -10,6 +10,7 @@ #include "Managers/SceneManager.h" #include "Managers/ProjectManager.h" #include "EditorEvents.h" +#include "Viewport/IViewportHostService.h" #include #include @@ -56,6 +57,14 @@ public: return *m_undoManager; } + IViewportHostService* GetViewportHostService() override { + return m_viewportHostService; + } + + void SetViewportHostService(IViewportHostService* viewportHostService) { + m_viewportHostService = viewportHostService; + } + void SetActiveActionRoute(EditorActionRoute route) override { m_activeActionRoute = route; } @@ -78,6 +87,7 @@ private: std::unique_ptr m_sceneManager; std::unique_ptr m_undoManager; std::unique_ptr m_projectManager; + IViewportHostService* m_viewportHostService = nullptr; EditorActionRoute m_activeActionRoute = EditorActionRoute::None; std::string m_projectPath; uint64_t m_entityDeletedHandlerId; diff --git a/editor/src/Core/IEditorContext.h b/editor/src/Core/IEditorContext.h index af4745cf..46a34ba8 100644 --- a/editor/src/Core/IEditorContext.h +++ b/editor/src/Core/IEditorContext.h @@ -13,6 +13,7 @@ class ISelectionManager; class IProjectManager; class ISceneManager; class IUndoManager; +class IViewportHostService; class IEditorContext { public: @@ -23,6 +24,7 @@ public: virtual ISceneManager& GetSceneManager() = 0; virtual IProjectManager& GetProjectManager() = 0; virtual IUndoManager& GetUndoManager() = 0; + virtual IViewportHostService* GetViewportHostService() = 0; virtual void SetActiveActionRoute(EditorActionRoute route) = 0; virtual EditorActionRoute GetActiveActionRoute() const = 0; diff --git a/editor/src/Core/ISceneManager.h b/editor/src/Core/ISceneManager.h index ae35d1bf..4977479e 100644 --- a/editor/src/Core/ISceneManager.h +++ b/editor/src/Core/ISceneManager.h @@ -6,6 +6,10 @@ #include namespace XCEngine { +namespace Components { +class Scene; +} + namespace Editor { class ISceneManager { @@ -32,6 +36,8 @@ public: virtual void MarkSceneDirty() = 0; virtual const std::string& GetCurrentScenePath() const = 0; virtual const std::string& GetCurrentSceneName() const = 0; + virtual ::XCEngine::Components::Scene* GetScene() = 0; + virtual const ::XCEngine::Components::Scene* GetScene() const = 0; virtual void CreateDemoScene() = 0; }; diff --git a/editor/src/Managers/SceneManager.h b/editor/src/Managers/SceneManager.h index 1b0a900a..e6a8912c 100644 --- a/editor/src/Managers/SceneManager.h +++ b/editor/src/Managers/SceneManager.h @@ -59,6 +59,8 @@ public: void MarkSceneDirty() override; const std::string& GetCurrentScenePath() const override { return m_currentScenePath; } const std::string& GetCurrentSceneName() const override { return m_currentSceneName; } + ::XCEngine::Components::Scene* GetScene() override { return m_scene.get(); } + const ::XCEngine::Components::Scene* GetScene() const override { return m_scene.get(); } void CreateDemoScene() override; bool HasClipboardData() const { return m_clipboard.has_value(); } diff --git a/editor/src/Platform/D3D12WindowRenderer.h b/editor/src/Platform/D3D12WindowRenderer.h index 8b5f6c34..3ce7b935 100644 --- a/editor/src/Platform/D3D12WindowRenderer.h +++ b/editor/src/Platform/D3D12WindowRenderer.h @@ -16,6 +16,7 @@ #include #include +#include #include #include @@ -154,7 +155,10 @@ public: RecreateBackBufferViews(); } - void Render(UI::ImGuiBackendBridge& imguiBackend, const float clearColor[4]) { + void Render( + UI::ImGuiBackendBridge& imguiBackend, + const float clearColor[4], + const std::function& beforeUiRender = {}) { auto* d3d12Queue = GetD3D12CommandQueue(); auto* d3d12CommandList = GetD3D12CommandList(); if (m_swapChain == nullptr || @@ -167,6 +171,10 @@ public: d3d12Queue->WaitForPreviousFrame(); m_commandList->Reset(); + if (beforeUiRender) { + beforeUiRender(GetRenderContext()); + } + const uint32_t backBufferIndex = m_swapChain->GetCurrentBackBufferIndex(); if (backBufferIndex >= m_backBufferViews.size() || m_backBufferViews[backBufferIndex] == nullptr) { return; diff --git a/editor/src/Viewport/IViewportHostService.h b/editor/src/Viewport/IViewportHostService.h new file mode 100644 index 00000000..cec4ead5 --- /dev/null +++ b/editor/src/Viewport/IViewportHostService.h @@ -0,0 +1,42 @@ +#pragma once + +#include + +#include + +namespace XCEngine { +namespace Rendering { +struct RenderContext; +} // namespace Rendering + +namespace Editor { + +class IEditorContext; + +enum class EditorViewportKind { + Scene, + Game +}; + +struct EditorViewportFrame { + ImTextureID textureId = {}; + ImVec2 requestedSize = ImVec2(0.0f, 0.0f); + ImVec2 renderSize = ImVec2(0.0f, 0.0f); + bool hasTexture = false; + bool wasRequested = false; + std::string statusText; +}; + +class IViewportHostService { +public: + virtual ~IViewportHostService() = default; + + virtual void BeginFrame() = 0; + virtual EditorViewportFrame RequestViewport(EditorViewportKind kind, const ImVec2& requestedSize) = 0; + virtual void RenderRequestedViewports( + IEditorContext& context, + const Rendering::RenderContext& renderContext) = 0; +}; + +} // namespace Editor +} // namespace XCEngine diff --git a/editor/src/Viewport/ViewportHostService.h b/editor/src/Viewport/ViewportHostService.h new file mode 100644 index 00000000..a7c4abab --- /dev/null +++ b/editor/src/Viewport/ViewportHostService.h @@ -0,0 +1,338 @@ +#pragma once + +#include "Core/IEditorContext.h" +#include "Core/ISceneManager.h" +#include "IViewportHostService.h" +#include "UI/ImGuiBackendBridge.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace XCEngine { +namespace Editor { + +class ViewportHostService : public IViewportHostService { +public: + void Initialize(UI::ImGuiBackendBridge& backend, RHI::RHIDevice* device) { + Shutdown(); + m_backend = &backend; + m_device = device ? static_cast(device) : nullptr; + } + + void Shutdown() { + for (ViewportEntry& entry : m_entries) { + DestroyViewportResources(entry); + entry = {}; + } + + m_device = nullptr; + m_backend = nullptr; + m_sceneRenderer.reset(); + } + + void BeginFrame() override { + for (ViewportEntry& entry : m_entries) { + entry.requestedThisFrame = false; + entry.requestedWidth = 0; + entry.requestedHeight = 0; + } + } + + EditorViewportFrame RequestViewport(EditorViewportKind kind, const ImVec2& requestedSize) override { + ViewportEntry& entry = GetEntry(kind); + entry.requestedThisFrame = requestedSize.x > 1.0f && requestedSize.y > 1.0f; + entry.requestedWidth = entry.requestedThisFrame + ? static_cast(requestedSize.x) + : 0u; + entry.requestedHeight = entry.requestedThisFrame + ? static_cast(requestedSize.y) + : 0u; + + if (entry.requestedThisFrame && m_backend != nullptr && m_device != nullptr) { + EnsureViewportResources(entry); + } + + EditorViewportFrame frame = {}; + frame.textureId = entry.textureId; + frame.requestedSize = requestedSize; + frame.renderSize = ImVec2(static_cast(entry.width), static_cast(entry.height)); + frame.hasTexture = entry.textureId != ImTextureID{}; + frame.wasRequested = entry.requestedThisFrame; + frame.statusText = entry.statusText; + return frame; + } + + void RenderRequestedViewports( + IEditorContext& context, + const Rendering::RenderContext& renderContext) override { + if (m_backend == nullptr || m_device == nullptr || !renderContext.IsValid()) { + return; + } + + EnsureSceneRenderer(); + const auto* scene = context.GetSceneManager().GetScene(); + + for (ViewportEntry& entry : m_entries) { + if (!entry.requestedThisFrame) { + continue; + } + + if (!EnsureViewportResources(entry)) { + entry.statusText = "Failed to create viewport render targets"; + continue; + } + + RenderViewportEntry(entry, scene, renderContext); + } + } + +private: + struct ViewportEntry { + EditorViewportKind kind = EditorViewportKind::Scene; + uint32_t width = 0; + uint32_t height = 0; + uint32_t requestedWidth = 0; + uint32_t requestedHeight = 0; + bool requestedThisFrame = false; + RHI::RHITexture* colorTexture = nullptr; + RHI::RHIResourceView* colorView = nullptr; + RHI::RHITexture* depthTexture = nullptr; + RHI::RHIResourceView* depthView = nullptr; + D3D12_CPU_DESCRIPTOR_HANDLE imguiCpuHandle = {}; + D3D12_GPU_DESCRIPTOR_HANDLE imguiGpuHandle = {}; + ImTextureID textureId = {}; + RHI::ResourceStates colorState = RHI::ResourceStates::Common; + std::string statusText; + }; + + ViewportEntry& GetEntry(EditorViewportKind kind) { + const size_t index = kind == EditorViewportKind::Scene ? 0u : 1u; + m_entries[index].kind = kind; + return m_entries[index]; + } + + void EnsureSceneRenderer() { + if (!m_sceneRenderer) { + m_sceneRenderer = std::make_unique(); + } + } + + bool EnsureViewportResources(ViewportEntry& entry) { + if (entry.requestedWidth == 0 || entry.requestedHeight == 0) { + return false; + } + + if (entry.width == entry.requestedWidth && + entry.height == entry.requestedHeight && + entry.colorTexture != nullptr && + entry.colorView != nullptr && + entry.depthTexture != nullptr && + entry.depthView != nullptr && + entry.textureId != ImTextureID{}) { + return true; + } + + DestroyViewportResources(entry); + + entry.width = entry.requestedWidth; + entry.height = entry.requestedHeight; + + RHI::TextureDesc colorDesc = {}; + colorDesc.width = entry.width; + colorDesc.height = entry.height; + colorDesc.depth = 1; + colorDesc.mipLevels = 1; + colorDesc.arraySize = 1; + colorDesc.format = static_cast(RHI::Format::R8G8B8A8_UNorm); + colorDesc.textureType = static_cast(RHI::TextureType::Texture2D); + colorDesc.sampleCount = 1; + colorDesc.sampleQuality = 0; + colorDesc.flags = 0; + entry.colorTexture = m_device->CreateTexture(colorDesc); + if (entry.colorTexture == nullptr) { + DestroyViewportResources(entry); + return false; + } + + RHI::ResourceViewDesc colorViewDesc = {}; + colorViewDesc.format = static_cast(RHI::Format::R8G8B8A8_UNorm); + colorViewDesc.dimension = RHI::ResourceViewDimension::Texture2D; + entry.colorView = m_device->CreateRenderTargetView(entry.colorTexture, colorViewDesc); + if (entry.colorView == nullptr) { + DestroyViewportResources(entry); + return false; + } + + RHI::TextureDesc depthDesc = {}; + depthDesc.width = entry.width; + depthDesc.height = entry.height; + depthDesc.depth = 1; + depthDesc.mipLevels = 1; + depthDesc.arraySize = 1; + depthDesc.format = static_cast(RHI::Format::D24_UNorm_S8_UInt); + depthDesc.textureType = static_cast(RHI::TextureType::Texture2D); + depthDesc.sampleCount = 1; + depthDesc.sampleQuality = 0; + depthDesc.flags = 0; + entry.depthTexture = m_device->CreateTexture(depthDesc); + if (entry.depthTexture == nullptr) { + DestroyViewportResources(entry); + return false; + } + + RHI::ResourceViewDesc depthViewDesc = {}; + depthViewDesc.format = static_cast(RHI::Format::D24_UNorm_S8_UInt); + depthViewDesc.dimension = RHI::ResourceViewDimension::Texture2D; + entry.depthView = m_device->CreateDepthStencilView(entry.depthTexture, depthViewDesc); + if (entry.depthView == nullptr) { + DestroyViewportResources(entry); + return false; + } + + m_backend->AllocateTextureDescriptor(&entry.imguiCpuHandle, &entry.imguiGpuHandle); + if (entry.imguiCpuHandle.ptr == 0 || entry.imguiGpuHandle.ptr == 0) { + DestroyViewportResources(entry); + return false; + } + + auto* nativeTexture = static_cast(entry.colorTexture); + D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {}; + srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; + srvDesc.Texture2D.MipLevels = 1; + m_device->GetDevice()->CreateShaderResourceView( + nativeTexture->GetResource(), + &srvDesc, + entry.imguiCpuHandle); + + entry.textureId = static_cast(entry.imguiGpuHandle.ptr); + entry.colorState = RHI::ResourceStates::Common; + return true; + } + + void RenderViewportEntry( + ViewportEntry& entry, + const Components::Scene* scene, + const Rendering::RenderContext& renderContext) { + if (entry.colorView == nullptr || entry.depthView == nullptr) { + entry.statusText = "Viewport render target is unavailable"; + return; + } + + if (scene == nullptr) { + entry.statusText = "No active scene"; + ClearViewport(entry, renderContext, 0.07f, 0.08f, 0.10f, 1.0f); + return; + } + + const auto cameras = scene->FindObjectsOfType(); + if (cameras.empty()) { + entry.statusText = "No camera in scene"; + ClearViewport(entry, renderContext, 0.10f, 0.09f, 0.08f, 1.0f); + return; + } + + Rendering::RenderSurface surface(entry.width, entry.height); + surface.SetColorAttachment(entry.colorView); + surface.SetDepthAttachment(entry.depthView); + surface.SetColorStateBefore(entry.colorState); + surface.SetColorStateAfter(RHI::ResourceStates::PixelShaderResource); + + if (!m_sceneRenderer->Render(*scene, nullptr, renderContext, surface)) { + entry.statusText = "Scene renderer failed"; + ClearViewport(entry, renderContext, 0.18f, 0.07f, 0.07f, 1.0f); + return; + } + + entry.colorState = RHI::ResourceStates::PixelShaderResource; + entry.statusText.clear(); + } + + void ClearViewport( + ViewportEntry& entry, + const Rendering::RenderContext& renderContext, + float r, + float g, + float b, + float a) { + RHI::RHICommandList* commandList = renderContext.commandList; + if (commandList == nullptr) { + entry.statusText = "Viewport command list is unavailable"; + return; + } + + const float clearColor[4] = { r, g, b, a }; + RHI::RHIResourceView* colorView = entry.colorView; + commandList->TransitionBarrier( + colorView, + entry.colorState, + RHI::ResourceStates::RenderTarget); + commandList->SetRenderTargets(1, &colorView, entry.depthView); + commandList->ClearRenderTarget(colorView, clearColor); + commandList->ClearDepthStencil(entry.depthView, 1.0f, 0); + commandList->TransitionBarrier( + colorView, + RHI::ResourceStates::RenderTarget, + RHI::ResourceStates::PixelShaderResource); + entry.colorState = RHI::ResourceStates::PixelShaderResource; + } + + void DestroyViewportResources(ViewportEntry& entry) { + if (m_backend != nullptr && entry.imguiCpuHandle.ptr != 0) { + m_backend->FreeTextureDescriptor(entry.imguiCpuHandle, entry.imguiGpuHandle); + } + + if (entry.depthView != nullptr) { + entry.depthView->Shutdown(); + delete entry.depthView; + entry.depthView = nullptr; + } + + if (entry.depthTexture != nullptr) { + entry.depthTexture->Shutdown(); + delete entry.depthTexture; + entry.depthTexture = nullptr; + } + + if (entry.colorView != nullptr) { + entry.colorView->Shutdown(); + delete entry.colorView; + entry.colorView = nullptr; + } + + if (entry.colorTexture != nullptr) { + entry.colorTexture->Shutdown(); + delete entry.colorTexture; + entry.colorTexture = nullptr; + } + + entry.width = 0; + entry.height = 0; + entry.imguiCpuHandle = {}; + entry.imguiGpuHandle = {}; + entry.textureId = {}; + entry.colorState = RHI::ResourceStates::Common; + } + + UI::ImGuiBackendBridge* m_backend = nullptr; + RHI::D3D12Device* m_device = nullptr; + std::unique_ptr m_sceneRenderer; + std::array m_entries = {}; +}; + +} // namespace Editor +} // namespace XCEngine diff --git a/editor/src/panels/GameViewPanel.cpp b/editor/src/panels/GameViewPanel.cpp index df1359bb..6ccc5871 100644 --- a/editor/src/panels/GameViewPanel.cpp +++ b/editor/src/panels/GameViewPanel.cpp @@ -1,5 +1,6 @@ #include "Actions/ActionRouting.h" #include "GameViewPanel.h" +#include "ViewportPanelContent.h" #include "UI/UI.h" #include @@ -14,6 +15,7 @@ void GameViewPanel::Render() { return; } + RenderViewportPanelContent(*m_context, EditorViewportKind::Game); Actions::ObserveInactiveActionRoute(*m_context); } diff --git a/editor/src/panels/SceneViewPanel.cpp b/editor/src/panels/SceneViewPanel.cpp index 2457f05b..cd2df8a9 100644 --- a/editor/src/panels/SceneViewPanel.cpp +++ b/editor/src/panels/SceneViewPanel.cpp @@ -1,5 +1,6 @@ #include "Actions/ActionRouting.h" #include "SceneViewPanel.h" +#include "ViewportPanelContent.h" #include "UI/UI.h" #include @@ -14,6 +15,7 @@ void SceneViewPanel::Render() { return; } + RenderViewportPanelContent(*m_context, EditorViewportKind::Scene); Actions::ObserveInactiveActionRoute(*m_context); } diff --git a/editor/src/panels/ViewportPanelContent.h b/editor/src/panels/ViewportPanelContent.h new file mode 100644 index 00000000..6ec08af8 --- /dev/null +++ b/editor/src/panels/ViewportPanelContent.h @@ -0,0 +1,60 @@ +#pragma once + +#include "Core/IEditorContext.h" +#include "Viewport/IViewportHostService.h" + +#include + +#include + +namespace XCEngine { +namespace Editor { + +inline void DrawViewportStatusMessage(const std::string& message) { + if (message.empty()) { + return; + } + + ImDrawList* drawList = ImGui::GetWindowDrawList(); + if (drawList == nullptr) { + return; + } + + const ImVec2 min = ImGui::GetItemRectMin(); + const ImVec2 max = ImGui::GetItemRectMax(); + const ImVec2 textSize = ImGui::CalcTextSize(message.c_str()); + const ImVec2 textPos( + min.x + ((max.x - min.x) - textSize.x) * 0.5f, + min.y + ((max.y - min.y) - textSize.y) * 0.5f); + + drawList->AddText(textPos, ImGui::GetColorU32(ImGuiCol_TextDisabled), message.c_str()); +} + +inline void RenderViewportPanelContent(IEditorContext& context, EditorViewportKind kind) { + IViewportHostService* viewportHostService = context.GetViewportHostService(); + const ImVec2 availableSize = ImGui::GetContentRegionAvail(); + if (availableSize.x <= 1.0f || availableSize.y <= 1.0f) { + ImGui::Dummy(ImVec2(0.0f, 0.0f)); + return; + } + + if (viewportHostService == nullptr) { + ImGui::Dummy(availableSize); + DrawViewportStatusMessage("Viewport host is unavailable"); + return; + } + + const EditorViewportFrame frame = viewportHostService->RequestViewport(kind, availableSize); + if (frame.hasTexture) { + ImGui::Image(frame.textureId, availableSize); + DrawViewportStatusMessage(frame.statusText); + return; + } + + ImGui::Dummy(availableSize); + DrawViewportStatusMessage( + frame.statusText.empty() ? "Viewport is initializing" : frame.statusText); +} + +} // namespace Editor +} // namespace XCEngine