From 941034b3874de818b1354675be35c3404303a54c Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Sun, 12 Apr 2026 23:13:00 +0800 Subject: [PATCH] Wire viewport shell host chain and move host under app --- ...ditor_3D渲染主链正式接入计划_2026-04-12.md | 4 +- new_editor/CMakeLists.txt | 13 +- new_editor/{ => app}/Host/AutoScreenshot.cpp | 0 new_editor/{ => app}/Host/AutoScreenshot.h | 0 new_editor/app/Host/D3D12WindowRenderLoop.cpp | 63 +++ new_editor/app/Host/D3D12WindowRenderLoop.h | 20 + new_editor/app/Host/D3D12WindowRenderer.cpp | 400 ++++++++++++++++++ new_editor/app/Host/D3D12WindowRenderer.h | 92 ++++ .../{ => app}/Host/InputModifierTracker.h | 0 new_editor/{ => app}/Host/NativeRenderer.cpp | 0 new_editor/{ => app}/Host/NativeRenderer.h | 0 new_editor/app/Shell/ProductShellAsset.cpp | 24 +- .../Viewport/ProductViewportHostService.cpp | 225 ++++++++++ .../app/Viewport/ProductViewportHostService.h | 64 +++ .../Viewport/ProductViewportRenderTargets.cpp | 1 + .../Viewport/ProductViewportRenderTargets.h | 301 +++++++++++++ .../Viewport/ProductViewportSurfaceUtils.h | 130 ++++++ .../app/Viewport/ProductViewportTypes.h | 24 ++ .../app/Workspace/ProductEditorWorkspace.cpp | 65 ++- .../app/Workspace/ProductEditorWorkspace.h | 2 + tests/UI/Editor/CMakeLists.txt | 10 +- 21 files changed, 1424 insertions(+), 14 deletions(-) rename new_editor/{ => app}/Host/AutoScreenshot.cpp (100%) rename new_editor/{ => app}/Host/AutoScreenshot.h (100%) create mode 100644 new_editor/app/Host/D3D12WindowRenderLoop.cpp create mode 100644 new_editor/app/Host/D3D12WindowRenderLoop.h create mode 100644 new_editor/app/Host/D3D12WindowRenderer.cpp create mode 100644 new_editor/app/Host/D3D12WindowRenderer.h rename new_editor/{ => app}/Host/InputModifierTracker.h (100%) rename new_editor/{ => app}/Host/NativeRenderer.cpp (100%) rename new_editor/{ => app}/Host/NativeRenderer.h (100%) create mode 100644 new_editor/app/Viewport/ProductViewportHostService.cpp create mode 100644 new_editor/app/Viewport/ProductViewportHostService.h create mode 100644 new_editor/app/Viewport/ProductViewportRenderTargets.cpp create mode 100644 new_editor/app/Viewport/ProductViewportRenderTargets.h create mode 100644 new_editor/app/Viewport/ProductViewportSurfaceUtils.h create mode 100644 new_editor/app/Viewport/ProductViewportTypes.h diff --git a/docs/plan/NewEditor_3D渲染主链正式接入计划_2026-04-12.md b/docs/plan/NewEditor_3D渲染主链正式接入计划_2026-04-12.md index 25a80f5f..e956ab62 100644 --- a/docs/plan/NewEditor_3D渲染主链正式接入计划_2026-04-12.md +++ b/docs/plan/NewEditor_3D渲染主链正式接入计划_2026-04-12.md @@ -48,7 +48,7 @@ 1. `new_editor/app/Shell/ProductShellAsset.cpp` 里,`scene` 和 `game` 仍然只是 `HostedContent`,还没有切到正式的 `ViewportShell` 主线。 2. `new_editor/app/Workspace/ProductEditorWorkspace.cpp` 里,没有真正的 viewport service owner,也没有 scene/game render request 生命周期。 -3. `new_editor/Host/NativeRenderer.h/.cpp` 当前是 Direct2D/DirectWrite 宿主,只支持它自己的位图纹理路径。 +3. `new_editor/app/Host/NativeRenderer.h/.cpp` 当前是 Direct2D/DirectWrite 宿主,只支持它自己的位图纹理路径。 4. `engine/include/XCEngine/UI/Types.h` 虽然已经有 `UITextureHandleKind::ShaderResourceView`,但 `NativeRenderer` 目前并没有真正消费这类引擎 render target。 ### 2.3 根因结论 @@ -132,7 +132,7 @@ 主要工作: -1. 在 `new_editor/Host` 下建立正式的 window renderer +1. 在 `new_editor/app/Host` 下建立正式的 window renderer 2. 对齐旧 `editor/src/Platform/D3D12WindowRenderer.h` 的能力边界 3. 让 `Application` 能拿到: - `RHIDevice` diff --git a/new_editor/CMakeLists.txt b/new_editor/CMakeLists.txt index f95f0203..83fd3903 100644 --- a/new_editor/CMakeLists.txt +++ b/new_editor/CMakeLists.txt @@ -123,13 +123,15 @@ target_link_libraries(XCUIEditorLib PUBLIC ) add_library(XCUIEditorHost STATIC - Host/AutoScreenshot.cpp - Host/NativeRenderer.cpp + app/Host/AutoScreenshot.cpp + app/Host/D3D12WindowRenderer.cpp + app/Host/D3D12WindowRenderLoop.cpp + app/Host/NativeRenderer.cpp ) target_include_directories(XCUIEditorHost PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/app ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/engine/include ) @@ -139,7 +141,10 @@ xcui_editor_apply_common_target_settings(XCUIEditorHost PUBLIC) target_link_libraries(XCUIEditorHost PUBLIC XCEngine d2d1.lib + d3d12.lib + d3dcompiler.lib dwrite.lib + dxgi.lib windowscodecs.lib ) @@ -158,6 +163,8 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) app/Panels/ProductProjectPanel.cpp app/Project/ProductProjectBrowserModel.cpp app/Shell/ProductShellAsset.cpp + app/Viewport/ProductViewportHostService.cpp + app/Viewport/ProductViewportRenderTargets.cpp app/Workspace/ProductEditorWorkspace.cpp app/Workspace/ProductEditorWorkspaceEventRouter.cpp ) diff --git a/new_editor/Host/AutoScreenshot.cpp b/new_editor/app/Host/AutoScreenshot.cpp similarity index 100% rename from new_editor/Host/AutoScreenshot.cpp rename to new_editor/app/Host/AutoScreenshot.cpp diff --git a/new_editor/Host/AutoScreenshot.h b/new_editor/app/Host/AutoScreenshot.h similarity index 100% rename from new_editor/Host/AutoScreenshot.h rename to new_editor/app/Host/AutoScreenshot.h diff --git a/new_editor/app/Host/D3D12WindowRenderLoop.cpp b/new_editor/app/Host/D3D12WindowRenderLoop.cpp new file mode 100644 index 00000000..1bb501af --- /dev/null +++ b/new_editor/app/Host/D3D12WindowRenderLoop.cpp @@ -0,0 +1,63 @@ +#include "D3D12WindowRenderLoop.h" + +namespace XCEngine::UI::Editor::Host { + +bool RenderD3D12WindowFrame( + D3D12WindowRenderer& windowRenderer, + const float clearColor[4], + const D3D12WindowRenderCallback& beforePresent, + const D3D12WindowRenderCallback& afterPresent) { + const ::XCEngine::Rendering::RenderSurface* renderSurface = + windowRenderer.GetCurrentRenderSurface(); + ::XCEngine::Rendering::RenderContext renderContext = + windowRenderer.GetRenderContext(); + if (!renderContext.IsValid() || + renderContext.commandList == nullptr || + renderContext.commandQueue == nullptr || + windowRenderer.GetSwapChain() == nullptr || + renderSurface == nullptr) { + return false; + } + + auto* d3d12CommandList = + static_cast<::XCEngine::RHI::D3D12CommandList*>(renderContext.commandList); + if (d3d12CommandList == nullptr) { + return false; + } + + const auto& colorAttachments = renderSurface->GetColorAttachments(); + if (colorAttachments.empty() || colorAttachments[0] == nullptr) { + return false; + } + + ::XCEngine::RHI::RHIResourceView* renderTargetView = colorAttachments[0]; + renderContext.commandList->TransitionBarrier( + renderTargetView, + ::XCEngine::RHI::ResourceStates::Present, + ::XCEngine::RHI::ResourceStates::RenderTarget); + renderContext.commandList->SetRenderTargets(1, &renderTargetView, nullptr); + renderContext.commandList->ClearRenderTarget(renderTargetView, clearColor); + + if (beforePresent) { + beforePresent(renderContext, *renderSurface); + renderContext.commandList->SetRenderTargets(1, &renderTargetView, nullptr); + } + + if (afterPresent) { + afterPresent(renderContext, *renderSurface); + renderContext.commandList->SetRenderTargets(1, &renderTargetView, nullptr); + } + + renderContext.commandList->TransitionBarrier( + renderTargetView, + ::XCEngine::RHI::ResourceStates::RenderTarget, + ::XCEngine::RHI::ResourceStates::Present); + renderContext.commandList->Close(); + + void* commandLists[] = { renderContext.commandList }; + renderContext.commandQueue->ExecuteCommandLists(1, commandLists); + windowRenderer.GetSwapChain()->Present(1, 0); + return true; +} + +} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Host/D3D12WindowRenderLoop.h b/new_editor/app/Host/D3D12WindowRenderLoop.h new file mode 100644 index 00000000..7b8b204c --- /dev/null +++ b/new_editor/app/Host/D3D12WindowRenderLoop.h @@ -0,0 +1,20 @@ +#pragma once + +#include "D3D12WindowRenderer.h" + +#include + +namespace XCEngine::UI::Editor::Host { + +using D3D12WindowRenderCallback = + std::function; + +bool RenderD3D12WindowFrame( + D3D12WindowRenderer& windowRenderer, + const float clearColor[4], + const D3D12WindowRenderCallback& beforePresent = {}, + const D3D12WindowRenderCallback& afterPresent = {}); + +} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Host/D3D12WindowRenderer.cpp b/new_editor/app/Host/D3D12WindowRenderer.cpp new file mode 100644 index 00000000..6ea01af0 --- /dev/null +++ b/new_editor/app/Host/D3D12WindowRenderer.cpp @@ -0,0 +1,400 @@ +#include "D3D12WindowRenderer.h" + +#include + +namespace XCEngine::UI::Editor::Host { + +using ::XCEngine::RHI::CommandListDesc; +using ::XCEngine::RHI::CommandQueueDesc; +using ::XCEngine::RHI::DescriptorHeapType; +using ::XCEngine::RHI::DescriptorPoolDesc; +using ::XCEngine::RHI::D3D12CommandList; +using ::XCEngine::RHI::D3D12CommandQueue; +using ::XCEngine::RHI::D3D12DescriptorHeap; +using ::XCEngine::RHI::D3D12Device; +using ::XCEngine::RHI::D3D12SwapChain; +using ::XCEngine::RHI::D3D12Texture; +using ::XCEngine::RHI::Format; +using ::XCEngine::RHI::ResourceStates; +using ::XCEngine::RHI::ResourceViewDesc; +using ::XCEngine::RHI::ResourceViewDimension; +using ::XCEngine::RHI::RHICommandList; +using ::XCEngine::RHI::RHICommandQueue; +using ::XCEngine::RHI::RHIDevice; +using ::XCEngine::RHI::RHIDeviceDesc; +using ::XCEngine::RHI::RHIFactory; +using ::XCEngine::RHI::RHISwapChain; +using ::XCEngine::RHI::RHIType; +using ::XCEngine::RHI::SwapChainDesc; + +bool D3D12WindowRenderer::Initialize(HWND hwnd, int width, int height) { + Shutdown(); + + if (hwnd == nullptr || width <= 0 || height <= 0) { + return false; + } + + m_hwnd = hwnd; + m_width = width; + m_height = height; + + m_device = RHIFactory::CreateRHIDevice(RHIType::D3D12); + if (m_device == nullptr) { + Shutdown(); + return false; + } + + RHIDeviceDesc deviceDesc = {}; +#ifdef _DEBUG + deviceDesc.enableDebugLayer = true; + deviceDesc.enableGPUValidation = true; +#endif + if (!m_device->Initialize(deviceDesc)) { + Shutdown(); + return false; + } + + CommandQueueDesc queueDesc = {}; + queueDesc.queueType = static_cast(::XCEngine::RHI::CommandQueueType::Direct); + m_commandQueue = m_device->CreateCommandQueue(queueDesc); + if (m_commandQueue == nullptr || GetD3D12CommandQueue() == nullptr) { + Shutdown(); + return false; + } + + CommandListDesc commandListDesc = {}; + commandListDesc.commandListType = static_cast(::XCEngine::RHI::CommandQueueType::Direct); + m_commandList = m_device->CreateCommandList(commandListDesc); + if (m_commandList == nullptr || GetD3D12CommandList() == nullptr) { + Shutdown(); + return false; + } + m_commandList->Close(); + + SwapChainDesc swapChainDesc = {}; + swapChainDesc.windowHandle = hwnd; + swapChainDesc.width = static_cast(width); + swapChainDesc.height = static_cast(height); + swapChainDesc.bufferCount = kSwapChainBufferCount; + m_swapChain = m_device->CreateSwapChain(swapChainDesc, m_commandQueue); + if (m_swapChain == nullptr || GetD3D12SwapChain() == nullptr) { + Shutdown(); + return false; + } + + DescriptorPoolDesc srvPoolDesc = {}; + srvPoolDesc.type = DescriptorHeapType::CBV_SRV_UAV; + srvPoolDesc.descriptorCount = kSrvDescriptorCount; + srvPoolDesc.shaderVisible = true; + m_srvPool = m_device->CreateDescriptorPool(srvPoolDesc); + m_srvHeap = dynamic_cast(m_srvPool); + if (m_srvPool == nullptr || m_srvHeap == nullptr) { + Shutdown(); + return false; + } + + m_srvDescriptorSize = m_srvHeap->GetDescriptorSize(); + m_srvUsage.assign(kSrvDescriptorCount, false); + if (!RecreateBackBufferViews()) { + Shutdown(); + return false; + } + + return true; +} + +void D3D12WindowRenderer::Shutdown() { + WaitForGpuIdle(); + ReleaseBackBufferViews(); + + if (m_srvPool != nullptr) { + m_srvPool->Shutdown(); + delete m_srvPool; + m_srvPool = nullptr; + } + m_srvHeap = nullptr; + m_srvUsage.clear(); + + if (m_swapChain != nullptr) { + m_swapChain->Shutdown(); + delete m_swapChain; + m_swapChain = nullptr; + } + + if (m_commandList != nullptr) { + m_commandList->Shutdown(); + delete m_commandList; + m_commandList = nullptr; + } + + if (m_commandQueue != nullptr) { + m_commandQueue->Shutdown(); + delete m_commandQueue; + m_commandQueue = nullptr; + } + + if (m_device != nullptr) { + m_device->Shutdown(); + delete m_device; + m_device = nullptr; + } + + m_hwnd = nullptr; + m_width = 0; + m_height = 0; + m_srvDescriptorSize = 0; +} + +void D3D12WindowRenderer::Resize(int width, int height) { + if (width <= 0 || height <= 0 || m_swapChain == nullptr) { + return; + } + + WaitForGpuIdle(); + ReleaseBackBufferViews(); + + m_swapChain->Resize(static_cast(width), static_cast(height)); + m_width = width; + m_height = height; + RecreateBackBufferViews(); +} + +bool D3D12WindowRenderer::BeginFrame() { + D3D12CommandQueue* d3d12Queue = GetD3D12CommandQueue(); + D3D12CommandList* d3d12CommandList = GetD3D12CommandList(); + if (m_swapChain == nullptr || + d3d12Queue == nullptr || + d3d12CommandList == nullptr || + m_srvHeap == nullptr) { + return false; + } + + d3d12Queue->WaitForPreviousFrame(); + m_commandList->Reset(); + return true; +} + +ID3D12Device* D3D12WindowRenderer::GetDevice() const { + const D3D12Device* device = GetD3D12Device(); + return device != nullptr ? device->GetDevice() : nullptr; +} + +ID3D12DescriptorHeap* D3D12WindowRenderer::GetSrvHeap() const { + return m_srvHeap != nullptr ? m_srvHeap->GetDescriptorHeap() : nullptr; +} + +ID3D12CommandQueue* D3D12WindowRenderer::GetCommandQueue() const { + const D3D12CommandQueue* queue = GetD3D12CommandQueue(); + return queue != nullptr ? queue->GetCommandQueue() : nullptr; +} + +void D3D12WindowRenderer::AllocateShaderResourceDescriptor( + D3D12_CPU_DESCRIPTOR_HANDLE* outCpuHandle, + D3D12_GPU_DESCRIPTOR_HANDLE* outGpuHandle) { + AllocateShaderResourceDescriptorInternal(outCpuHandle, outGpuHandle); +} + +bool D3D12WindowRenderer::CreateShaderResourceTextureDescriptor( + RHIDevice* device, + ::XCEngine::RHI::RHITexture* texture, + D3D12_CPU_DESCRIPTOR_HANDLE* outCpuHandle, + D3D12_GPU_DESCRIPTOR_HANDLE* outGpuHandle) { + if (device == nullptr || + texture == nullptr || + outCpuHandle == nullptr || + outGpuHandle == nullptr) { + return false; + } + + *outCpuHandle = {}; + *outGpuHandle = {}; + + auto* nativeDevice = dynamic_cast(device); + auto* nativeTexture = dynamic_cast(texture); + if (nativeDevice == nullptr || + nativeTexture == nullptr || + nativeTexture->GetResource() == nullptr) { + return false; + } + + AllocateShaderResourceDescriptorInternal(outCpuHandle, outGpuHandle); + if (outCpuHandle->ptr == 0 || outGpuHandle->ptr == 0) { + *outCpuHandle = {}; + *outGpuHandle = {}; + return false; + } + + 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; + nativeDevice->GetDevice()->CreateShaderResourceView( + nativeTexture->GetResource(), + &srvDesc, + *outCpuHandle); + return true; +} + +void D3D12WindowRenderer::FreeShaderResourceDescriptor( + D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle, + D3D12_GPU_DESCRIPTOR_HANDLE gpuHandle) { + FreeShaderResourceDescriptorInternal(cpuHandle, gpuHandle); +} + +UINT D3D12WindowRenderer::GetSrvDescriptorSize() const { + return m_srvDescriptorSize; +} + +UINT D3D12WindowRenderer::GetSrvDescriptorCount() const { + return kSrvDescriptorCount; +} + +RHIDevice* D3D12WindowRenderer::GetRHIDevice() const { + return m_device; +} + +RHISwapChain* D3D12WindowRenderer::GetSwapChain() const { + return m_swapChain; +} + +const ::XCEngine::Rendering::RenderSurface* D3D12WindowRenderer::GetCurrentRenderSurface() const { + if (m_swapChain == nullptr) { + return nullptr; + } + + const std::uint32_t backBufferIndex = m_swapChain->GetCurrentBackBufferIndex(); + if (backBufferIndex >= m_backBufferSurfaces.size()) { + return nullptr; + } + + return &m_backBufferSurfaces[backBufferIndex]; +} + +::XCEngine::Rendering::RenderContext D3D12WindowRenderer::GetRenderContext() const { + ::XCEngine::Rendering::RenderContext context = {}; + context.device = m_device; + context.commandList = m_commandList; + context.commandQueue = m_commandQueue; + context.backendType = RHIType::D3D12; + return context; +} + +D3D12Device* D3D12WindowRenderer::GetD3D12Device() const { + return m_device != nullptr ? static_cast(m_device) : nullptr; +} + +D3D12CommandQueue* D3D12WindowRenderer::GetD3D12CommandQueue() const { + return m_commandQueue != nullptr ? static_cast(m_commandQueue) : nullptr; +} + +D3D12CommandList* D3D12WindowRenderer::GetD3D12CommandList() const { + return m_commandList != nullptr ? static_cast(m_commandList) : nullptr; +} + +D3D12SwapChain* D3D12WindowRenderer::GetD3D12SwapChain() const { + return m_swapChain != nullptr ? static_cast(m_swapChain) : nullptr; +} + +void D3D12WindowRenderer::AllocateShaderResourceDescriptorInternal( + D3D12_CPU_DESCRIPTOR_HANDLE* outCpuHandle, + D3D12_GPU_DESCRIPTOR_HANDLE* outGpuHandle) { + if (outCpuHandle == nullptr || outGpuHandle == nullptr) { + return; + } + + *outCpuHandle = {}; + *outGpuHandle = {}; + if (m_srvHeap == nullptr || + m_srvDescriptorSize == 0 || + m_srvUsage.empty()) { + return; + } + + for (std::size_t index = 0; index < m_srvUsage.size(); ++index) { + if (m_srvUsage[index]) { + continue; + } + + m_srvUsage[index] = true; + outCpuHandle->ptr = + m_srvHeap->GetCPUDescriptorHandleForHeapStart().ptr + + static_cast(index) * m_srvDescriptorSize; + outGpuHandle->ptr = + m_srvHeap->GetGPUDescriptorHandleForHeapStart().ptr + + static_cast(index) * m_srvDescriptorSize; + return; + } +} + +void D3D12WindowRenderer::FreeShaderResourceDescriptorInternal( + D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle, + D3D12_GPU_DESCRIPTOR_HANDLE) { + if (m_srvHeap == nullptr || + m_srvDescriptorSize == 0 || + cpuHandle.ptr < m_srvHeap->GetCPUDescriptorHandleForHeapStart().ptr) { + return; + } + + const SIZE_T offset = + cpuHandle.ptr - m_srvHeap->GetCPUDescriptorHandleForHeapStart().ptr; + const std::size_t index = + static_cast(offset / m_srvDescriptorSize); + if (index < m_srvUsage.size()) { + m_srvUsage[index] = false; + } +} + +void D3D12WindowRenderer::WaitForGpuIdle() { + if (m_commandQueue != nullptr) { + m_commandQueue->WaitForIdle(); + } +} + +void D3D12WindowRenderer::ReleaseBackBufferViews() { + for (auto* view : m_backBufferViews) { + if (view != nullptr) { + view->Shutdown(); + delete view; + } + } + m_backBufferViews.clear(); + m_backBufferSurfaces.clear(); +} + +bool D3D12WindowRenderer::RecreateBackBufferViews() { + D3D12SwapChain* d3d12SwapChain = GetD3D12SwapChain(); + if (m_device == nullptr || d3d12SwapChain == nullptr) { + return false; + } + + m_backBufferViews.resize(kSwapChainBufferCount, nullptr); + m_backBufferSurfaces.resize(kSwapChainBufferCount); + + ResourceViewDesc viewDesc = {}; + viewDesc.format = static_cast(Format::R8G8B8A8_UNorm); + viewDesc.dimension = ResourceViewDimension::Texture2D; + + for (std::uint32_t backBufferIndex = 0; backBufferIndex < kSwapChainBufferCount; ++backBufferIndex) { + m_backBufferViews[backBufferIndex] = m_device->CreateRenderTargetView( + &d3d12SwapChain->GetBackBuffer(backBufferIndex), + viewDesc); + if (m_backBufferViews[backBufferIndex] == nullptr) { + ReleaseBackBufferViews(); + return false; + } + + ::XCEngine::Rendering::RenderSurface& surface = m_backBufferSurfaces[backBufferIndex]; + surface = ::XCEngine::Rendering::RenderSurface( + static_cast(m_width), + static_cast(m_height)); + surface.SetColorAttachment(m_backBufferViews[backBufferIndex]); + surface.SetAutoTransitionEnabled(false); + surface.SetColorStateBefore(ResourceStates::RenderTarget); + surface.SetColorStateAfter(ResourceStates::RenderTarget); + } + + return true; +} + +} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Host/D3D12WindowRenderer.h b/new_editor/app/Host/D3D12WindowRenderer.h new file mode 100644 index 00000000..6ee6af38 --- /dev/null +++ b/new_editor/app/Host/D3D12WindowRenderer.h @@ -0,0 +1,92 @@ +#pragma once + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +namespace XCEngine::UI::Editor::Host { + +class D3D12WindowRenderer { +public: + static constexpr UINT kSrvDescriptorCount = 64; + static constexpr std::uint32_t kSwapChainBufferCount = 3; + + bool Initialize(HWND hwnd, int width, int height); + void Shutdown(); + void Resize(int width, int height); + bool BeginFrame(); + + ID3D12Device* GetDevice() const; + ID3D12DescriptorHeap* GetSrvHeap() const; + ID3D12CommandQueue* GetCommandQueue() const; + void AllocateShaderResourceDescriptor( + D3D12_CPU_DESCRIPTOR_HANDLE* outCpuHandle, + D3D12_GPU_DESCRIPTOR_HANDLE* outGpuHandle); + bool CreateShaderResourceTextureDescriptor( + ::XCEngine::RHI::RHIDevice* device, + ::XCEngine::RHI::RHITexture* texture, + D3D12_CPU_DESCRIPTOR_HANDLE* outCpuHandle, + D3D12_GPU_DESCRIPTOR_HANDLE* outGpuHandle); + void FreeShaderResourceDescriptor( + D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle, + D3D12_GPU_DESCRIPTOR_HANDLE gpuHandle); + UINT GetSrvDescriptorSize() const; + UINT GetSrvDescriptorCount() const; + ::XCEngine::RHI::RHIDevice* GetRHIDevice() const; + ::XCEngine::RHI::RHISwapChain* GetSwapChain() const; + const ::XCEngine::Rendering::RenderSurface* GetCurrentRenderSurface() const; + ::XCEngine::Rendering::RenderContext GetRenderContext() const; + +private: + ::XCEngine::RHI::D3D12Device* GetD3D12Device() const; + ::XCEngine::RHI::D3D12CommandQueue* GetD3D12CommandQueue() const; + ::XCEngine::RHI::D3D12CommandList* GetD3D12CommandList() const; + ::XCEngine::RHI::D3D12SwapChain* GetD3D12SwapChain() const; + void AllocateShaderResourceDescriptorInternal( + D3D12_CPU_DESCRIPTOR_HANDLE* outCpuHandle, + D3D12_GPU_DESCRIPTOR_HANDLE* outGpuHandle); + void FreeShaderResourceDescriptorInternal( + D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle, + D3D12_GPU_DESCRIPTOR_HANDLE gpuHandle); + void WaitForGpuIdle(); + void ReleaseBackBufferViews(); + bool RecreateBackBufferViews(); + + HWND m_hwnd = nullptr; + int m_width = 0; + int m_height = 0; + + ::XCEngine::RHI::RHIDevice* m_device = nullptr; + ::XCEngine::RHI::RHICommandQueue* m_commandQueue = nullptr; + ::XCEngine::RHI::RHICommandList* m_commandList = nullptr; + ::XCEngine::RHI::RHISwapChain* m_swapChain = nullptr; + ::XCEngine::RHI::RHIDescriptorPool* m_srvPool = nullptr; + ::XCEngine::RHI::D3D12DescriptorHeap* m_srvHeap = nullptr; + std::vector m_srvUsage = {}; + std::vector<::XCEngine::RHI::RHIResourceView*> m_backBufferViews = {}; + std::vector<::XCEngine::Rendering::RenderSurface> m_backBufferSurfaces = {}; + UINT m_srvDescriptorSize = 0; +}; + +} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/Host/InputModifierTracker.h b/new_editor/app/Host/InputModifierTracker.h similarity index 100% rename from new_editor/Host/InputModifierTracker.h rename to new_editor/app/Host/InputModifierTracker.h diff --git a/new_editor/Host/NativeRenderer.cpp b/new_editor/app/Host/NativeRenderer.cpp similarity index 100% rename from new_editor/Host/NativeRenderer.cpp rename to new_editor/app/Host/NativeRenderer.cpp diff --git a/new_editor/Host/NativeRenderer.h b/new_editor/app/Host/NativeRenderer.h similarity index 100% rename from new_editor/Host/NativeRenderer.h rename to new_editor/app/Host/NativeRenderer.h diff --git a/new_editor/app/Shell/ProductShellAsset.cpp b/new_editor/app/Shell/ProductShellAsset.cpp index 111bde88..e8e08e1e 100644 --- a/new_editor/app/Shell/ProductShellAsset.cpp +++ b/new_editor/app/Shell/ProductShellAsset.cpp @@ -21,8 +21,8 @@ UIEditorPanelRegistry BuildPanelRegistry() { UIEditorPanelRegistry registry = {}; registry.panels = { { "hierarchy", "Hierarchy", UIEditorPanelPresentationKind::HostedContent, true, false, false }, - { "scene", "Scene", UIEditorPanelPresentationKind::HostedContent, false, false, false }, - { "game", "Game", UIEditorPanelPresentationKind::HostedContent, false, false, false }, + { "scene", "Scene", UIEditorPanelPresentationKind::ViewportShell, false, false, false }, + { "game", "Game", UIEditorPanelPresentationKind::ViewportShell, false, false, false }, { "inspector", "Inspector", UIEditorPanelPresentationKind::HostedContent, true, false, false }, { "console", "Console", UIEditorPanelPresentationKind::HostedContent, true, false, false }, { "project", "Project", UIEditorPanelPresentationKind::HostedContent, false, false, false } @@ -421,6 +421,22 @@ UIEditorWorkspacePanelPresentationModel BuildHostedContentPresentation( return presentation; } +UIEditorWorkspacePanelPresentationModel BuildViewportPresentation( + std::string panelId, + std::string title, + std::string subtitle) { + UIEditorWorkspacePanelPresentationModel presentation = {}; + presentation.panelId = std::move(panelId); + presentation.kind = UIEditorPanelPresentationKind::ViewportShell; + presentation.viewportShellModel.spec.chrome.title = std::move(title); + presentation.viewportShellModel.spec.chrome.subtitle = std::move(subtitle); + presentation.viewportShellModel.spec.chrome.showTopBar = true; + presentation.viewportShellModel.spec.chrome.showBottomBar = true; + presentation.viewportShellModel.frame.statusText = + "Viewport request chain is active."; + return presentation; +} + UIEditorShellInteractionDefinition BuildBaseShellDefinition() { UIEditorShellInteractionDefinition definition = {}; definition.menuModel = BuildMenuModel(); @@ -432,8 +448,8 @@ UIEditorShellInteractionDefinition BuildBaseShellDefinition() { definition.statusSegments = {}; definition.workspacePresentations = { BuildHostedContentPresentation("hierarchy"), - BuildHostedContentPresentation("scene"), - BuildHostedContentPresentation("game"), + BuildViewportPresentation("scene", "Scene", "New Editor viewport shell"), + BuildViewportPresentation("game", "Game", "New Editor viewport shell"), BuildHostedContentPresentation("inspector"), BuildHostedContentPresentation("console"), BuildHostedContentPresentation("project") diff --git a/new_editor/app/Viewport/ProductViewportHostService.cpp b/new_editor/app/Viewport/ProductViewportHostService.cpp new file mode 100644 index 00000000..71019c71 --- /dev/null +++ b/new_editor/app/Viewport/ProductViewportHostService.cpp @@ -0,0 +1,225 @@ +#include "ProductViewportHostService.h" + +#include + +namespace XCEngine::UI::Editor::App { + +namespace { + +using ::XCEngine::RHI::ResourceStates; + +std::string BuildViewportPendingStatus(ProductViewportKind kind) { + return kind == ProductViewportKind::Scene + ? "Scene viewport host pending D3D12 presenter integration." + : "Game viewport host pending D3D12 presenter integration."; +} + +std::string BuildViewportReadyStatus(ProductViewportKind kind) { + return kind == ProductViewportKind::Scene + ? "Scene viewport render target ready; presenter still uses NativeRenderer." + : "Game viewport render target ready; presenter still uses NativeRenderer."; +} + +std::string BuildViewportPresentedStatus(ProductViewportKind kind) { + return kind == ProductViewportKind::Scene + ? "Scene viewport frame ready." + : "Game viewport frame ready."; +} + +} // namespace + +void ProductViewportHostService::AttachWindowRenderer( + Host::D3D12WindowRenderer& windowRenderer) { + if (m_windowRenderer == &windowRenderer) { + m_device = windowRenderer.GetRHIDevice(); + return; + } + + Shutdown(); + m_windowRenderer = &windowRenderer; + m_device = windowRenderer.GetRHIDevice(); +} + +void ProductViewportHostService::DetachWindowRenderer() { + Shutdown(); +} + +void ProductViewportHostService::SetSurfacePresentationEnabled(bool enabled) { + m_surfacePresentationEnabled = enabled; +} + +void ProductViewportHostService::Shutdown() { + for (ViewportEntry& entry : m_entries) { + DestroyViewportEntry(entry); + } + + m_windowRenderer = nullptr; + m_device = nullptr; + m_surfacePresentationEnabled = false; +} + +void ProductViewportHostService::BeginFrame() { + for (ViewportEntry& entry : m_entries) { + entry.requestedWidth = 0; + entry.requestedHeight = 0; + entry.requestedThisFrame = false; + entry.renderedThisFrame = false; + entry.kind = (&entry == &m_entries[0]) ? ProductViewportKind::Scene : ProductViewportKind::Game; + } +} + +ProductViewportFrame ProductViewportHostService::RequestViewport( + ProductViewportKind kind, + const ::XCEngine::UI::UISize& requestedSize) { + ViewportEntry& entry = GetEntry(kind); + entry.requestedThisFrame = requestedSize.width > 1.0f && requestedSize.height > 1.0f; + entry.requestedWidth = entry.requestedThisFrame + ? static_cast(requestedSize.width) + : 0u; + entry.requestedHeight = entry.requestedThisFrame + ? static_cast(requestedSize.height) + : 0u; + + if (!entry.requestedThisFrame) { + entry.statusText = "Viewport is waiting for a visible surface."; + return BuildFrame(entry, requestedSize); + } + + if (m_windowRenderer == nullptr || m_device == nullptr) { + entry.statusText = BuildViewportPendingStatus(kind); + return BuildFrame(entry, requestedSize); + } + + if (!EnsureViewportResources(entry)) { + entry.statusText = "Failed to create viewport render targets."; + return BuildFrame(entry, requestedSize); + } + + entry.statusText = m_surfacePresentationEnabled + ? BuildViewportPresentedStatus(kind) + : BuildViewportReadyStatus(kind); + return BuildFrame(entry, requestedSize); +} + +void ProductViewportHostService::RenderRequestedViewports( + const ::XCEngine::Rendering::RenderContext& renderContext) { + if (m_windowRenderer == nullptr || m_device == nullptr || !renderContext.IsValid()) { + return; + } + + for (ViewportEntry& entry : m_entries) { + if (!entry.requestedThisFrame || !EnsureViewportResources(entry)) { + continue; + } + + if (entry.kind == ProductViewportKind::Scene) { + ClearViewport(entry, renderContext, 0.16f, 0.17f, 0.19f, 1.0f); + } else { + ClearViewport(entry, renderContext, 0.11f, 0.11f, 0.12f, 1.0f); + } + + entry.renderedThisFrame = true; + entry.statusText = m_surfacePresentationEnabled + ? BuildViewportPresentedStatus(entry.kind) + : BuildViewportReadyStatus(entry.kind); + } +} + +ProductViewportHostService::ViewportEntry& ProductViewportHostService::GetEntry( + ProductViewportKind kind) { + const std::size_t index = kind == ProductViewportKind::Scene ? 0u : 1u; + ViewportEntry& entry = m_entries[index]; + entry.kind = kind; + return entry; +} + +const ProductViewportHostService::ViewportEntry& ProductViewportHostService::GetEntry( + ProductViewportKind kind) const { + const std::size_t index = kind == ProductViewportKind::Scene ? 0u : 1u; + return m_entries[index]; +} + +void ProductViewportHostService::DestroyViewportEntry(ViewportEntry& entry) { + DestroyProductViewportRenderTargets(m_windowRenderer, entry.renderTargets); + entry = {}; +} + +bool ProductViewportHostService::EnsureViewportResources(ViewportEntry& entry) { + const ProductViewportResourceReuseQuery reuseQuery = + BuildProductViewportRenderTargetsReuseQuery( + entry.kind, + entry.renderTargets, + entry.requestedWidth, + entry.requestedHeight); + if (CanReuseProductViewportResources(reuseQuery)) { + return true; + } + + if (m_windowRenderer == nullptr || + m_device == nullptr || + entry.requestedWidth == 0u || + entry.requestedHeight == 0u) { + return false; + } + + return CreateProductViewportRenderTargets( + entry.kind, + entry.requestedWidth, + entry.requestedHeight, + m_device, + *m_windowRenderer, + entry.renderTargets); +} + +void ProductViewportHostService::ClearViewport( + ViewportEntry& entry, + const ::XCEngine::Rendering::RenderContext& renderContext, + float r, + float g, + float b, + float a) { + if (renderContext.commandList == nullptr || + entry.renderTargets.colorView == nullptr || + entry.renderTargets.depthView == nullptr) { + return; + } + + auto* commandList = renderContext.commandList; + auto* colorView = entry.renderTargets.colorView; + const float clearColor[4] = { r, g, b, a }; + commandList->TransitionBarrier( + colorView, + entry.renderTargets.colorState, + ResourceStates::RenderTarget); + commandList->SetRenderTargets(1, &colorView, entry.renderTargets.depthView); + commandList->ClearRenderTarget(colorView, clearColor); + commandList->ClearDepthStencil(entry.renderTargets.depthView, 1.0f, 0); + commandList->TransitionBarrier( + colorView, + ResourceStates::RenderTarget, + ResourceStates::PixelShaderResource); + entry.renderTargets.colorState = ResourceStates::PixelShaderResource; + entry.renderTargets.hasValidObjectIdFrame = false; +} + +ProductViewportFrame ProductViewportHostService::BuildFrame( + const ViewportEntry& entry, + const ::XCEngine::UI::UISize& requestedSize) const { + ProductViewportFrame frame = {}; + frame.requestedSize = requestedSize; + frame.renderSize = ::XCEngine::UI::UISize( + static_cast(entry.renderTargets.width), + static_cast(entry.renderTargets.height)); + frame.wasRequested = entry.requestedThisFrame; + frame.statusText = entry.statusText; + + if (m_surfacePresentationEnabled && + entry.renderTargets.textureHandle.IsValid()) { + frame.texture = entry.renderTargets.textureHandle; + frame.hasTexture = true; + } + + return frame; +} + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Viewport/ProductViewportHostService.h b/new_editor/app/Viewport/ProductViewportHostService.h new file mode 100644 index 00000000..df090d15 --- /dev/null +++ b/new_editor/app/Viewport/ProductViewportHostService.h @@ -0,0 +1,64 @@ +#pragma once + +#include "ProductViewportRenderTargets.h" + +#include + +#include +#include + +#include +#include +#include + +namespace XCEngine::UI::Editor::App { + +class ProductViewportHostService { +public: + void AttachWindowRenderer(Host::D3D12WindowRenderer& windowRenderer); + void DetachWindowRenderer(); + void SetSurfacePresentationEnabled(bool enabled); + + void Shutdown(); + void BeginFrame(); + + ProductViewportFrame RequestViewport( + ProductViewportKind kind, + const ::XCEngine::UI::UISize& requestedSize); + + void RenderRequestedViewports( + const ::XCEngine::Rendering::RenderContext& renderContext); + +private: + struct ViewportEntry { + ProductViewportKind kind = ProductViewportKind::Scene; + std::uint32_t requestedWidth = 0; + std::uint32_t requestedHeight = 0; + bool requestedThisFrame = false; + bool renderedThisFrame = false; + ProductViewportRenderTargets renderTargets = {}; + std::string statusText = {}; + }; + + ViewportEntry& GetEntry(ProductViewportKind kind); + const ViewportEntry& GetEntry(ProductViewportKind kind) const; + void DestroyViewportEntry(ViewportEntry& entry); + bool EnsureViewportResources(ViewportEntry& entry); + void ClearViewport( + ViewportEntry& entry, + const ::XCEngine::Rendering::RenderContext& renderContext, + float r, + float g, + float b, + float a); + ProductViewportFrame BuildFrame( + const ViewportEntry& entry, + const ::XCEngine::UI::UISize& requestedSize) const; + + Host::D3D12WindowRenderer* m_windowRenderer = nullptr; + ::XCEngine::RHI::RHIDevice* m_device = nullptr; + bool m_surfacePresentationEnabled = false; + std::array m_entries = {}; +}; + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Viewport/ProductViewportRenderTargets.cpp b/new_editor/app/Viewport/ProductViewportRenderTargets.cpp new file mode 100644 index 00000000..ec3a38fd --- /dev/null +++ b/new_editor/app/Viewport/ProductViewportRenderTargets.cpp @@ -0,0 +1 @@ +#include "ProductViewportRenderTargets.h" diff --git a/new_editor/app/Viewport/ProductViewportRenderTargets.h b/new_editor/app/Viewport/ProductViewportRenderTargets.h new file mode 100644 index 00000000..ec867ffa --- /dev/null +++ b/new_editor/app/Viewport/ProductViewportRenderTargets.h @@ -0,0 +1,301 @@ +#pragma once + +#include "ProductViewportSurfaceUtils.h" + +#include + +#include +#include +#include +#include + +namespace XCEngine::UI::Editor::App { + +struct ProductViewportRenderTargets { + std::uint32_t width = 0; + std::uint32_t height = 0; + ::XCEngine::RHI::RHITexture* colorTexture = nullptr; + ::XCEngine::RHI::RHIResourceView* colorView = nullptr; + ::XCEngine::RHI::RHITexture* depthTexture = nullptr; + ::XCEngine::RHI::RHIResourceView* depthView = nullptr; + ::XCEngine::RHI::RHIResourceView* depthShaderView = nullptr; + ::XCEngine::RHI::RHITexture* objectIdTexture = nullptr; + ::XCEngine::RHI::RHITexture* objectIdDepthTexture = nullptr; + ::XCEngine::RHI::RHIResourceView* objectIdDepthView = nullptr; + ::XCEngine::RHI::RHIResourceView* objectIdView = nullptr; + ::XCEngine::RHI::RHIResourceView* objectIdShaderView = nullptr; + ::XCEngine::RHI::RHITexture* selectionMaskTexture = nullptr; + ::XCEngine::RHI::RHIResourceView* selectionMaskView = nullptr; + ::XCEngine::RHI::RHIResourceView* selectionMaskShaderView = nullptr; + D3D12_CPU_DESCRIPTOR_HANDLE srvCpuHandle = {}; + D3D12_GPU_DESCRIPTOR_HANDLE srvGpuHandle = {}; + ::XCEngine::UI::UITextureHandle textureHandle = {}; + ::XCEngine::RHI::ResourceStates colorState = ::XCEngine::RHI::ResourceStates::Common; + ::XCEngine::RHI::ResourceStates objectIdState = ::XCEngine::RHI::ResourceStates::Common; + ::XCEngine::RHI::ResourceStates selectionMaskState = ::XCEngine::RHI::ResourceStates::Common; + bool hasValidObjectIdFrame = false; +}; + +inline ProductViewportResourceReuseQuery BuildProductViewportRenderTargetsReuseQuery( + ProductViewportKind kind, + const ProductViewportRenderTargets& targets, + std::uint32_t requestedWidth, + std::uint32_t requestedHeight) { + ProductViewportResourceReuseQuery query = {}; + query.kind = kind; + query.width = targets.width; + query.height = targets.height; + query.requestedWidth = requestedWidth; + query.requestedHeight = requestedHeight; + query.resources.hasColorTexture = targets.colorTexture != nullptr; + query.resources.hasColorView = targets.colorView != nullptr; + query.resources.hasDepthTexture = targets.depthTexture != nullptr; + query.resources.hasDepthView = targets.depthView != nullptr; + query.resources.hasDepthShaderView = targets.depthShaderView != nullptr; + query.resources.hasObjectIdTexture = targets.objectIdTexture != nullptr; + query.resources.hasObjectIdDepthTexture = targets.objectIdDepthTexture != nullptr; + query.resources.hasObjectIdDepthView = targets.objectIdDepthView != nullptr; + query.resources.hasObjectIdView = targets.objectIdView != nullptr; + query.resources.hasObjectIdShaderView = targets.objectIdShaderView != nullptr; + query.resources.hasSelectionMaskTexture = targets.selectionMaskTexture != nullptr; + query.resources.hasSelectionMaskView = targets.selectionMaskView != nullptr; + query.resources.hasSelectionMaskShaderView = targets.selectionMaskShaderView != nullptr; + query.resources.hasTextureDescriptor = targets.textureHandle.IsValid(); + return query; +} + +inline ::XCEngine::Rendering::RenderSurface BuildProductViewportColorSurface( + const ProductViewportRenderTargets& targets) { + return BuildProductViewportRenderSurface( + targets.width, + targets.height, + targets.colorView, + targets.depthView, + targets.colorState); +} + +inline ::XCEngine::Rendering::RenderSurface BuildProductViewportObjectIdSurface( + const ProductViewportRenderTargets& targets) { + return BuildProductViewportRenderSurface( + targets.width, + targets.height, + targets.objectIdView, + targets.objectIdDepthView, + targets.objectIdState); +} + +inline ::XCEngine::Rendering::RenderSurface BuildProductViewportSelectionMaskSurface( + const ProductViewportRenderTargets& targets) { + return BuildProductViewportRenderSurface( + targets.width, + targets.height, + targets.selectionMaskView, + targets.depthView, + targets.selectionMaskState); +} + +namespace Internal { + +template +inline void ShutdownAndDeleteViewportResource(ResourceType*& resource) { + if (resource == nullptr) { + return; + } + + resource->Shutdown(); + delete resource; + resource = nullptr; +} + +inline bool CreateViewportColorResources( + ::XCEngine::RHI::RHIDevice* device, + ProductViewportRenderTargets& targets) { + const auto colorDesc = + BuildProductViewportTextureDesc(targets.width, targets.height, ::XCEngine::RHI::Format::R8G8B8A8_UNorm); + targets.colorTexture = device->CreateTexture(colorDesc); + if (targets.colorTexture == nullptr) { + return false; + } + + const auto colorViewDesc = + BuildProductViewportTextureViewDesc(::XCEngine::RHI::Format::R8G8B8A8_UNorm); + targets.colorView = device->CreateRenderTargetView(targets.colorTexture, colorViewDesc); + return targets.colorView != nullptr; +} + +inline bool CreateViewportDepthResources( + ::XCEngine::RHI::RHIDevice* device, + ProductViewportRenderTargets& targets) { + const auto depthDesc = + BuildProductViewportTextureDesc(targets.width, targets.height, ::XCEngine::RHI::Format::D24_UNorm_S8_UInt); + targets.depthTexture = device->CreateTexture(depthDesc); + if (targets.depthTexture == nullptr) { + return false; + } + + const auto depthViewDesc = + BuildProductViewportTextureViewDesc(::XCEngine::RHI::Format::D24_UNorm_S8_UInt); + targets.depthView = device->CreateDepthStencilView(targets.depthTexture, depthViewDesc); + if (targets.depthView == nullptr) { + return false; + } + + ::XCEngine::RHI::ResourceViewDesc depthShaderViewDesc = {}; + depthShaderViewDesc.dimension = ::XCEngine::RHI::ResourceViewDimension::Texture2D; + depthShaderViewDesc.mipLevel = 0; + targets.depthShaderView = device->CreateShaderResourceView( + targets.depthTexture, + depthShaderViewDesc); + return targets.depthShaderView != nullptr; +} + +inline bool CreateViewportObjectIdResources( + ::XCEngine::RHI::RHIDevice* device, + ProductViewportRenderTargets& targets) { + const auto objectIdDesc = + BuildProductViewportTextureDesc(targets.width, targets.height, ::XCEngine::RHI::Format::R8G8B8A8_UNorm); + targets.objectIdTexture = device->CreateTexture(objectIdDesc); + if (targets.objectIdTexture == nullptr) { + return false; + } + + const auto objectIdViewDesc = + BuildProductViewportTextureViewDesc(::XCEngine::RHI::Format::R8G8B8A8_UNorm); + targets.objectIdView = device->CreateRenderTargetView(targets.objectIdTexture, objectIdViewDesc); + if (targets.objectIdView == nullptr) { + return false; + } + + targets.objectIdShaderView = device->CreateShaderResourceView( + targets.objectIdTexture, + objectIdViewDesc); + if (targets.objectIdShaderView == nullptr) { + return false; + } + + const auto objectIdDepthDesc = + BuildProductViewportTextureDesc(targets.width, targets.height, ::XCEngine::RHI::Format::D24_UNorm_S8_UInt); + targets.objectIdDepthTexture = device->CreateTexture(objectIdDepthDesc); + if (targets.objectIdDepthTexture == nullptr) { + return false; + } + + const auto objectIdDepthViewDesc = + BuildProductViewportTextureViewDesc(::XCEngine::RHI::Format::D24_UNorm_S8_UInt); + targets.objectIdDepthView = device->CreateDepthStencilView( + targets.objectIdDepthTexture, + objectIdDepthViewDesc); + return targets.objectIdDepthView != nullptr; +} + +inline bool CreateViewportSelectionMaskResources( + ::XCEngine::RHI::RHIDevice* device, + ProductViewportRenderTargets& targets) { + const auto selectionMaskDesc = + BuildProductViewportTextureDesc(targets.width, targets.height, ::XCEngine::RHI::Format::R8G8B8A8_UNorm); + targets.selectionMaskTexture = device->CreateTexture(selectionMaskDesc); + if (targets.selectionMaskTexture == nullptr) { + return false; + } + + const auto selectionMaskViewDesc = + BuildProductViewportTextureViewDesc(::XCEngine::RHI::Format::R8G8B8A8_UNorm); + targets.selectionMaskView = device->CreateRenderTargetView( + targets.selectionMaskTexture, + selectionMaskViewDesc); + if (targets.selectionMaskView == nullptr) { + return false; + } + + targets.selectionMaskShaderView = device->CreateShaderResourceView( + targets.selectionMaskTexture, + selectionMaskViewDesc); + return targets.selectionMaskShaderView != nullptr; +} + +inline bool CreateViewportTextureDescriptor( + Host::D3D12WindowRenderer& windowRenderer, + ::XCEngine::RHI::RHIDevice* device, + ProductViewportRenderTargets& targets) { + if (!windowRenderer.CreateShaderResourceTextureDescriptor( + device, + targets.colorTexture, + &targets.srvCpuHandle, + &targets.srvGpuHandle)) { + return false; + } + + targets.textureHandle.nativeHandle = static_cast(targets.srvGpuHandle.ptr); + targets.textureHandle.width = targets.width; + targets.textureHandle.height = targets.height; + targets.textureHandle.kind = ::XCEngine::UI::UITextureHandleKind::ShaderResourceView; + return true; +} + +} // namespace Internal + +inline void DestroyProductViewportRenderTargets( + Host::D3D12WindowRenderer* windowRenderer, + ProductViewportRenderTargets& targets) { + if (windowRenderer != nullptr && targets.srvCpuHandle.ptr != 0) { + windowRenderer->FreeShaderResourceDescriptor(targets.srvCpuHandle, targets.srvGpuHandle); + } + + Internal::ShutdownAndDeleteViewportResource(targets.objectIdView); + Internal::ShutdownAndDeleteViewportResource(targets.objectIdShaderView); + Internal::ShutdownAndDeleteViewportResource(targets.objectIdDepthView); + Internal::ShutdownAndDeleteViewportResource(targets.objectIdDepthTexture); + Internal::ShutdownAndDeleteViewportResource(targets.objectIdTexture); + Internal::ShutdownAndDeleteViewportResource(targets.selectionMaskView); + Internal::ShutdownAndDeleteViewportResource(targets.selectionMaskShaderView); + Internal::ShutdownAndDeleteViewportResource(targets.selectionMaskTexture); + Internal::ShutdownAndDeleteViewportResource(targets.depthShaderView); + Internal::ShutdownAndDeleteViewportResource(targets.depthView); + Internal::ShutdownAndDeleteViewportResource(targets.depthTexture); + Internal::ShutdownAndDeleteViewportResource(targets.colorView); + Internal::ShutdownAndDeleteViewportResource(targets.colorTexture); + + targets.width = 0; + targets.height = 0; + targets.srvCpuHandle = {}; + targets.srvGpuHandle = {}; + targets.textureHandle = {}; + targets.colorState = ::XCEngine::RHI::ResourceStates::Common; + targets.objectIdState = ::XCEngine::RHI::ResourceStates::Common; + targets.selectionMaskState = ::XCEngine::RHI::ResourceStates::Common; + targets.hasValidObjectIdFrame = false; +} + +inline bool CreateProductViewportRenderTargets( + ProductViewportKind kind, + std::uint32_t width, + std::uint32_t height, + ::XCEngine::RHI::RHIDevice* device, + Host::D3D12WindowRenderer& windowRenderer, + ProductViewportRenderTargets& targets) { + if (width == 0u || height == 0u || device == nullptr) { + return false; + } + + DestroyProductViewportRenderTargets(&windowRenderer, targets); + targets.width = width; + targets.height = height; + + if (!Internal::CreateViewportColorResources(device, targets) || + !Internal::CreateViewportDepthResources(device, targets) || + (ProductViewportRequiresObjectIdResources(kind) && + (!Internal::CreateViewportObjectIdResources(device, targets) || + !Internal::CreateViewportSelectionMaskResources(device, targets))) || + !Internal::CreateViewportTextureDescriptor(windowRenderer, device, targets)) { + DestroyProductViewportRenderTargets(&windowRenderer, targets); + return false; + } + + targets.colorState = ::XCEngine::RHI::ResourceStates::Common; + targets.objectIdState = ::XCEngine::RHI::ResourceStates::Common; + targets.selectionMaskState = ::XCEngine::RHI::ResourceStates::Common; + targets.hasValidObjectIdFrame = false; + return true; +} + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Viewport/ProductViewportSurfaceUtils.h b/new_editor/app/Viewport/ProductViewportSurfaceUtils.h new file mode 100644 index 00000000..7c331c75 --- /dev/null +++ b/new_editor/app/Viewport/ProductViewportSurfaceUtils.h @@ -0,0 +1,130 @@ +#pragma once + +#include "ProductViewportTypes.h" + +#include +#include +#include +#include + +#include +#include +#include + +namespace XCEngine::UI::Editor::App { + +struct ProductViewportResourcePresence { + bool hasColorTexture = false; + bool hasColorView = false; + bool hasDepthTexture = false; + bool hasDepthView = false; + bool hasDepthShaderView = false; + bool hasObjectIdTexture = false; + bool hasObjectIdDepthTexture = false; + bool hasObjectIdDepthView = false; + bool hasObjectIdView = false; + bool hasObjectIdShaderView = false; + bool hasSelectionMaskTexture = false; + bool hasSelectionMaskView = false; + bool hasSelectionMaskShaderView = false; + bool hasTextureDescriptor = false; +}; + +struct ProductViewportResourceReuseQuery { + ProductViewportKind kind = ProductViewportKind::Scene; + std::uint32_t width = 0; + std::uint32_t height = 0; + std::uint32_t requestedWidth = 0; + std::uint32_t requestedHeight = 0; + ProductViewportResourcePresence resources = {}; +}; + +inline bool ProductViewportRequiresObjectIdResources(ProductViewportKind kind) { + return kind == ProductViewportKind::Scene; +} + +inline std::uint32_t ClampViewportPixelCoordinate(float value, std::uint32_t extent) { + if (extent == 0u) { + return 0u; + } + + const float maxCoordinate = static_cast(extent - 1u); + const float clamped = (std::max)(0.0f, (std::min)(value, maxCoordinate)); + return static_cast(std::floor(clamped)); +} + +inline bool CanReuseProductViewportResources(const ProductViewportResourceReuseQuery& query) { + if (query.requestedWidth == 0u || query.requestedHeight == 0u) { + return false; + } + + if (query.width != query.requestedWidth || query.height != query.requestedHeight) { + return false; + } + + if (!query.resources.hasColorTexture || + !query.resources.hasColorView || + !query.resources.hasDepthTexture || + !query.resources.hasDepthView || + !query.resources.hasTextureDescriptor) { + return false; + } + + if (!ProductViewportRequiresObjectIdResources(query.kind)) { + return true; + } + + return query.resources.hasObjectIdTexture && + query.resources.hasObjectIdDepthTexture && + query.resources.hasObjectIdDepthView && + query.resources.hasObjectIdView && + query.resources.hasObjectIdShaderView && + query.resources.hasDepthShaderView && + query.resources.hasSelectionMaskTexture && + query.resources.hasSelectionMaskView && + query.resources.hasSelectionMaskShaderView; +} + +inline ::XCEngine::RHI::TextureDesc BuildProductViewportTextureDesc( + std::uint32_t width, + std::uint32_t height, + ::XCEngine::RHI::Format format) { + ::XCEngine::RHI::TextureDesc desc = {}; + desc.width = width; + desc.height = height; + desc.depth = 1; + desc.mipLevels = 1; + desc.arraySize = 1; + desc.format = static_cast(format); + desc.textureType = static_cast(::XCEngine::RHI::TextureType::Texture2D); + desc.sampleCount = 1; + desc.sampleQuality = 0; + desc.flags = 0; + return desc; +} + +inline ::XCEngine::RHI::ResourceViewDesc BuildProductViewportTextureViewDesc( + ::XCEngine::RHI::Format format) { + ::XCEngine::RHI::ResourceViewDesc desc = {}; + desc.format = static_cast(format); + desc.dimension = ::XCEngine::RHI::ResourceViewDimension::Texture2D; + return desc; +} + +inline ::XCEngine::Rendering::RenderSurface BuildProductViewportRenderSurface( + std::uint32_t width, + std::uint32_t height, + ::XCEngine::RHI::RHIResourceView* colorView, + ::XCEngine::RHI::RHIResourceView* depthView, + ::XCEngine::RHI::ResourceStates colorStateBefore, + ::XCEngine::RHI::ResourceStates colorStateAfter = + ::XCEngine::RHI::ResourceStates::PixelShaderResource) { + ::XCEngine::Rendering::RenderSurface surface(width, height); + surface.SetColorAttachment(colorView); + surface.SetDepthAttachment(depthView); + surface.SetColorStateBefore(colorStateBefore); + surface.SetColorStateAfter(colorStateAfter); + return surface; +} + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Viewport/ProductViewportTypes.h b/new_editor/app/Viewport/ProductViewportTypes.h new file mode 100644 index 00000000..3e47acd5 --- /dev/null +++ b/new_editor/app/Viewport/ProductViewportTypes.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +#include +#include + +namespace XCEngine::UI::Editor::App { + +enum class ProductViewportKind : std::uint8_t { + Scene = 0, + Game +}; + +struct ProductViewportFrame { + ::XCEngine::UI::UITextureHandle texture = {}; + ::XCEngine::UI::UISize requestedSize = {}; + ::XCEngine::UI::UISize renderSize = {}; + bool hasTexture = false; + bool wasRequested = false; + std::string statusText = {}; +}; + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Workspace/ProductEditorWorkspace.cpp b/new_editor/app/Workspace/ProductEditorWorkspace.cpp index da5db163..e42902dd 100644 --- a/new_editor/app/Workspace/ProductEditorWorkspace.cpp +++ b/new_editor/app/Workspace/ProductEditorWorkspace.cpp @@ -16,6 +16,55 @@ using ::XCEngine::UI::UIDrawList; using ::XCEngine::UI::UIInputEvent; using ::XCEngine::UI::UIInputEventType; +bool IsProductViewportPanel(std::string_view panelId) { + return panelId == "scene" || panelId == "game"; +} + +ProductViewportKind ResolveProductViewportKind(std::string_view panelId) { + return panelId == "game" + ? ProductViewportKind::Game + : ProductViewportKind::Scene; +} + +void ApplyViewportFrameToPresentation( + const ProductViewportFrame& viewportFrame, + UIEditorWorkspacePanelPresentationModel& presentation) { + presentation.viewportShellModel.frame.texture = viewportFrame.texture; + presentation.viewportShellModel.frame.requestedSize = viewportFrame.requestedSize; + presentation.viewportShellModel.frame.presentedSize = viewportFrame.renderSize; + presentation.viewportShellModel.frame.hasTexture = viewportFrame.hasTexture; + presentation.viewportShellModel.frame.statusText = viewportFrame.statusText; +} + +std::vector BuildWorkspacePresentations( + const UIEditorShellInteractionDefinition& definition, + const UIEditorShellInteractionRequest& shellRequest, + ProductViewportHostService& viewportHostService) { + std::vector presentations = + definition.workspacePresentations; + for (UIEditorWorkspacePanelPresentationModel& presentation : presentations) { + if (presentation.kind != UIEditorPanelPresentationKind::ViewportShell || + !IsProductViewportPanel(presentation.panelId)) { + continue; + } + + const UIEditorWorkspaceViewportComposeRequest* viewportRequest = + FindUIEditorWorkspaceViewportPresentationRequest( + shellRequest.shellRequest.workspaceRequest, + presentation.panelId); + const ::XCEngine::UI::UISize requestedSize = + viewportRequest != nullptr + ? viewportRequest->viewportShellRequest.requestedViewportSize + : ::XCEngine::UI::UISize(); + ApplyViewportFrameToPresentation( + viewportHostService.RequestViewport( + ResolveProductViewportKind(presentation.panelId), + requestedSize), + presentation); + } + return presentations; +} + UIEditorShellComposeModel BuildShellComposeModelFromFrame( const UIEditorShellInteractionFrame& frame) { UIEditorShellComposeModel model = {}; @@ -93,6 +142,7 @@ void ProductEditorWorkspace::Shutdown() { m_shellFrame = {}; m_shellInteractionState = {}; m_traceEntries.clear(); + m_viewportHostService.Shutdown(); m_builtInIcons.Shutdown(); } @@ -103,8 +153,21 @@ void ProductEditorWorkspace::Update( std::string_view captureText) { const auto& metrics = ResolveUIEditorShellInteractionMetrics(); context.SyncSessionFromWorkspace(); - const UIEditorShellInteractionDefinition definition = + UIEditorShellInteractionDefinition definition = context.BuildShellDefinition(captureText); + m_viewportHostService.BeginFrame(); + const UIEditorShellInteractionRequest shellRequest = + ResolveUIEditorShellInteractionRequest( + bounds, + context.GetWorkspaceController(), + definition, + m_shellInteractionState, + metrics, + context.GetShellServices()); + definition.workspacePresentations = BuildWorkspacePresentations( + definition, + shellRequest, + m_viewportHostService); const std::vector hostedContentEvents = inputEvents; const std::vector shellEvents = HasHostedContentCapture() diff --git a/new_editor/app/Workspace/ProductEditorWorkspace.h b/new_editor/app/Workspace/ProductEditorWorkspace.h index 35aec773..edc759d5 100644 --- a/new_editor/app/Workspace/ProductEditorWorkspace.h +++ b/new_editor/app/Workspace/ProductEditorWorkspace.h @@ -6,6 +6,7 @@ #include "Panels/ProductHierarchyPanel.h" #include "Panels/ProductInspectorPanel.h" #include "Panels/ProductProjectPanel.h" +#include "Viewport/ProductViewportHostService.h" #include "Workspace/ProductEditorWorkspaceEventRouter.h" #include @@ -50,6 +51,7 @@ public: bool HasInteractiveCapture() const; private: + ProductViewportHostService m_viewportHostService = {}; ProductBuiltInIcons m_builtInIcons = {}; ProductConsolePanel m_consolePanel = {}; ProductHierarchyPanel m_hierarchyPanel = {}; diff --git a/tests/UI/Editor/CMakeLists.txt b/tests/UI/Editor/CMakeLists.txt index 97a44411..db66e0a9 100644 --- a/tests/UI/Editor/CMakeLists.txt +++ b/tests/UI/Editor/CMakeLists.txt @@ -5,14 +5,16 @@ project(XCEngine_EditorUITests) set(XCENGINE_EDITOR_UI_TESTS_EDITOR_ROOT "${CMAKE_SOURCE_DIR}/new_editor") -if(NOT EXISTS "${XCENGINE_EDITOR_UI_TESTS_EDITOR_ROOT}/Host/AutoScreenshot.h" - OR NOT EXISTS "${XCENGINE_EDITOR_UI_TESTS_EDITOR_ROOT}/Host/InputModifierTracker.h" - OR NOT EXISTS "${XCENGINE_EDITOR_UI_TESTS_EDITOR_ROOT}/Host/NativeRenderer.h") +if(NOT EXISTS "${XCENGINE_EDITOR_UI_TESTS_EDITOR_ROOT}/app/Host/AutoScreenshot.h" + OR NOT EXISTS "${XCENGINE_EDITOR_UI_TESTS_EDITOR_ROOT}/app/Host/InputModifierTracker.h" + OR NOT EXISTS "${XCENGINE_EDITOR_UI_TESTS_EDITOR_ROOT}/app/Host/NativeRenderer.h") message(FATAL_ERROR "Editor UI tests expect host headers under " - "${XCENGINE_EDITOR_UI_TESTS_EDITOR_ROOT}/Host.") + "${XCENGINE_EDITOR_UI_TESTS_EDITOR_ROOT}/app/Host.") endif() +include_directories("${XCENGINE_EDITOR_UI_TESTS_EDITOR_ROOT}/app") + add_subdirectory(unit) add_subdirectory(integration)