From 793027df23eb2f30a09414549f686a678e970d33 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Tue, 21 Apr 2026 12:19:15 +0800 Subject: [PATCH] Refactor RHI swapchain present policy --- ...esentPolicy_RootRefactorPlan_2026-04-21.md | 232 ++++++++++++++++++ ...tor_帧调度与UI合成性能重构计划_过期归档_2026-04-21.md} | 0 editor/src/Platform/D3D12WindowRenderer.h | 2 + .../D3D12WindowRendererImGuiInterop.h | 2 +- .../XCEngine/RHI/D3D12/D3D12SwapChain.h | 4 +- .../XCEngine/RHI/OpenGL/OpenGLSwapChain.h | 4 +- engine/include/XCEngine/RHI/RHISwapChain.h | 2 + engine/include/XCEngine/RHI/RHITypes.h | 12 + .../XCEngine/RHI/Vulkan/VulkanSwapChain.h | 7 +- engine/src/RHI/D3D12/D3D12Device.cpp | 4 +- engine/src/RHI/D3D12/D3D12SwapChain.cpp | 213 ++++++++++++++-- engine/src/RHI/OpenGL/OpenGLDevice.cpp | 2 +- engine/src/RHI/OpenGL/OpenGLSwapChain.cpp | 77 +++++- engine/src/RHI/Vulkan/VulkanDevice.cpp | 2 +- engine/src/RHI/Vulkan/VulkanSwapChain.cpp | 138 ++++++++--- .../D3D12/D3D12WindowSwapChainPresenter.cpp | 22 +- .../D3D12/D3D12WindowSwapChainPresenter.h | 1 - 17 files changed, 636 insertions(+), 88 deletions(-) create mode 100644 docs/plan/NewEditor_RHI_PresentPolicy_RootRefactorPlan_2026-04-21.md rename docs/{plan/NewEditor_帧调度与UI合成性能重构计划_2026-04-21.md => used/NewEditor_帧调度与UI合成性能重构计划_过期归档_2026-04-21.md} (100%) diff --git a/docs/plan/NewEditor_RHI_PresentPolicy_RootRefactorPlan_2026-04-21.md b/docs/plan/NewEditor_RHI_PresentPolicy_RootRefactorPlan_2026-04-21.md new file mode 100644 index 00000000..d709aa1d --- /dev/null +++ b/docs/plan/NewEditor_RHI_PresentPolicy_RootRefactorPlan_2026-04-21.md @@ -0,0 +1,232 @@ +# NewEditor RHI Present Policy Root Refactor Plan + +## Background + +The current `new_editor` empty-window frame rate is limited by the windowed presentation path rather than by panel content cost. The deeper problem is not a single `Present(0, 0)` call, but that the RHI swapchain abstraction does not model presentation policy in a backend-correct way: + +- D3D12 expresses most presentation policy at swapchain creation time plus DXGI present flags. +- Vulkan expresses it mainly through swapchain present mode selection. +- OpenGL expresses it through swap interval state, not per-present flags. + +The current `RHISwapChain::Present(syncInterval, flags)` interface mixes these semantics and leaves each backend to interpret them differently. + +## Goals + +- Fix the problem at the RHI root instead of patching `new_editor` locally. +- Introduce a formal, cross-backend presentation policy model. +- Make D3D12 support unlocked windowed presentation when the platform allows it. +- Preserve compatibility for existing call sites during the first refactor stage. +- Keep backend fallback behavior explicit and queryable. + +## Non-Goals + +- Do not optimize panel/widget content costs in this refactor. +- Do not redesign the entire window manager or render loop. +- Do not remove legacy `Present(syncInterval, flags)` calls in one step if doing so would cause broad compile breakage. + +## Root Cause + +1. `SwapChainDesc` does not carry a formal presentation policy. +2. `RHISwapChain::Present(syncInterval, flags)` is not a sound cross-backend abstraction. +3. D3D12 swapchain creation is still using a conservative DXGI path and does not expose tearing/unlocked presentation as a first-class capability. +4. `new_editor` cannot request a high-FPS presentation policy through the RHI because the RHI does not represent that choice. + +## Refactor Strategy + +### Stage 1: Introduce formal presentation policy in the RHI + +- Add `SwapChainPresentMode` to `RHITypes.h`. +- Add `SwapChainPresentPolicy` to `RHITypes.h`. +- Extend `SwapChainDesc` with `presentPolicy`. +- Extend `RHISwapChain` with read-only introspection for the active presentation policy/mode. +- Keep the old `Present(syncInterval, flags)` signature temporarily for compatibility, but move backend behavior to the stored policy instead of the ad hoc parameters. + +### Stage 2: Rework D3D12 swapchain creation around policy + +- Change D3D12 swapchain initialization to consume the full `SwapChainDesc`. +- Detect tearing support through DXGI. +- Move D3D12 creation to a modern `CreateSwapChainForHwnd` path with explicit flags. +- Map policy to actual D3D12 behavior: + - `Fifo` -> vsync path + - `Immediate` -> tearing/unlocked path when supported, otherwise explicit fallback + - `Mailbox` -> explicit fallback because DXGI has no direct mailbox equivalent +- Store both requested policy and active mode on the swapchain instance. + +### Stage 3: Align Vulkan and OpenGL to the same policy model + +- Vulkan: + - choose present mode from policy + - store active mode after fallback resolution +- OpenGL: + - apply swap interval from policy + - store active mode after fallback resolution + +### Stage 4: Rewire `new_editor` to request the intended policy + +- `new_editor` D3D12 presenter should request `Immediate` presentation for editor responsiveness. +- Keep the choice explicit in the presenter instead of hard-coding magic present flags. + +### Stage 5: Compatibility cleanup + +- Update direct window renderer usages that create swapchains. +- Keep old `Present(syncInterval, flags)` call sites compiling during this stage. +- After the new policy path is proven, consider a later cleanup pass to remove the legacy arguments entirely. + +## Files Expected To Change + +- `engine/include/XCEngine/RHI/RHITypes.h` +- `engine/include/XCEngine/RHI/RHISwapChain.h` +- `engine/include/XCEngine/RHI/D3D12/D3D12SwapChain.h` +- `engine/src/RHI/D3D12/D3D12SwapChain.cpp` +- `engine/src/RHI/D3D12/D3D12Device.cpp` +- `engine/include/XCEngine/RHI/Vulkan/VulkanSwapChain.h` +- `engine/src/RHI/Vulkan/VulkanSwapChain.cpp` +- `engine/include/XCEngine/RHI/OpenGL/OpenGLSwapChain.h` +- `engine/src/RHI/OpenGL/OpenGLSwapChain.cpp` +- `new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenter.cpp` +- `editor/src/Platform/D3D12WindowRenderer.h` +- `editor/src/Platform/D3D12WindowRendererImGuiInterop.h` + +## Validation Plan + +- Build `XCUIEditorApp` +- Build `XCUIEditorAppLib` +- Build any engine targets directly consuming the modified swapchain interfaces +- Verify that: + - RHI compiles across D3D12/Vulkan/OpenGL backends + - `new_editor` requests `Immediate` + - D3D12 reports a sane fallback when tearing is unavailable + - no existing call site is left using removed API + +## Risks + +- Old editor code may still depend on the legacy present arguments. +- OpenGL swap interval extension availability may differ by platform/driver. +- D3D12 fallback semantics must be explicit to avoid silent behavior changes. + +## Immediate Execution Order + +1. Add RHI types for present policy. +2. Update `RHISwapChain` interface. +3. Port D3D12 swapchain internals to the new model. +4. Update `new_editor` to request `Immediate`. +5. Port Vulkan/OpenGL to the same policy model. +6. Build and fix fallout. + +## Detailed Execution Checklist + +### Phase 0: Stabilize the in-progress refactor so the tree is buildable again + +- Remove or re-declare any stale `D3D12SwapChain::Initialize(...)` overloads so header/source signatures match. +- Stop hard-coding `Format::R8G8B8A8_UNorm` inside D3D12 swapchain creation and consume `SwapChainDesc.format`. +- Ensure D3D12 swapchain state resets are complete: + - `m_presentPolicy` + - `m_activePresentMode` + - `m_tearingSupported` + - `m_bufferCount` +- Preserve legacy `Present(syncInterval, flags)` call sites for now, but make backend behavior come from stored policy. + +### Phase 1: Move present semantics into backend-owned swapchain state + +- D3D12: + - carry full `SwapChainDesc` through initialization + - resolve requested mode to actual mode once, during creation + - derive DXGI creation flags and present flags from resolved mode + - apply maximum frame latency from `SwapChainPresentPolicy` in the backend, not in the editor wrapper +- Vulkan: + - carry requested `SwapChainPresentPolicy` + - pick a Vulkan present mode from supported modes with explicit fallback order + - expose both requested policy and resolved active mode +- OpenGL: + - carry requested `SwapChainPresentPolicy` + - resolve swap interval once from policy + - explicitly report fallback when immediate swap interval is unavailable + +### Phase 2: Rewire caller intent explicitly + +- `new_editor` main window presenter: + - request `SwapChainPresentMode::Immediate` + - request low frame latency explicitly through `SwapChainDesc.presentPolicy` + - stop passing magic `Present(0, 0)` semantics as the source of truth +- legacy editor D3D12 window renderer: + - request the same policy so both editors exercise the same RHI path + +### Phase 3: Validation and fallout cleanup + +- Build `XCUIEditorAppLib` +- Build `XCUIEditorApp` +- Fix any pure-virtual fallout in non-D3D12 backends +- Verify these invariants in code: + - `SwapChainDesc.presentPolicy` is the only place callers express intent + - `RHISwapChain::GetActivePresentMode()` reports the real backend-resolved mode + - D3D12 resize preserves tearing-capable flags when immediate mode is active + - no editor path still depends on ad hoc present flags for behavior + +## File-Level Work Breakdown + +### RHI contract + +- `engine/include/XCEngine/RHI/RHITypes.h` +- `engine/include/XCEngine/RHI/RHISwapChain.h` + +Work: + +- keep policy types centralized in the public RHI contract +- make swapchain introspection part of the base interface + +### D3D12 backend + +- `engine/include/XCEngine/RHI/D3D12/D3D12SwapChain.h` +- `engine/src/RHI/D3D12/D3D12SwapChain.cpp` +- `engine/src/RHI/D3D12/D3D12Device.cpp` + +Work: + +- consume full `SwapChainDesc` +- resolve present mode once +- create flip-model swapchain with policy-derived flags +- apply frame latency in the backend +- preserve compatibility surface for existing callers during this pass + +### Vulkan backend + +- `engine/include/XCEngine/RHI/Vulkan/VulkanSwapChain.h` +- `engine/src/RHI/Vulkan/VulkanSwapChain.cpp` +- `engine/src/RHI/Vulkan/VulkanDevice.cpp` + +Work: + +- route policy through initialization +- map policy to Vulkan present modes with explicit fallback +- publish resolved mode through the common interface + +### OpenGL backend + +- `engine/include/XCEngine/RHI/OpenGL/OpenGLSwapChain.h` +- `engine/src/RHI/OpenGL/OpenGLSwapChain.cpp` +- `engine/src/RHI/OpenGL/OpenGLDevice.cpp` + +Work: + +- store requested policy +- map policy to swap interval behavior +- publish resolved mode through the common interface + +### Editor call sites + +- `new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenter.cpp` +- `editor/src/Platform/D3D12WindowRenderer.h` +- `editor/src/Platform/D3D12WindowRendererImGuiInterop.h` + +Work: + +- make intent explicit through `SwapChainDesc.presentPolicy` +- keep presentation calls compatible while backend semantics migrate + +## Acceptance Criteria For This Refactor Pass + +- The codebase builds for the edited targets without interface drift. +- `new_editor` and legacy editor both request immediate presentation through the RHI contract. +- D3D12 no longer depends on caller-supplied per-present sync arguments to decide whether it runs unlocked. +- Vulkan/OpenGL no longer fail to compile because of the new `RHISwapChain` pure virtual interface. +- Any remaining performance limit after this pass can be attributed to another subsystem with the present path already architecturally corrected. diff --git a/docs/plan/NewEditor_帧调度与UI合成性能重构计划_2026-04-21.md b/docs/used/NewEditor_帧调度与UI合成性能重构计划_过期归档_2026-04-21.md similarity index 100% rename from docs/plan/NewEditor_帧调度与UI合成性能重构计划_2026-04-21.md rename to docs/used/NewEditor_帧调度与UI合成性能重构计划_过期归档_2026-04-21.md diff --git a/editor/src/Platform/D3D12WindowRenderer.h b/editor/src/Platform/D3D12WindowRenderer.h index 76aab005..3f38bcad 100644 --- a/editor/src/Platform/D3D12WindowRenderer.h +++ b/editor/src/Platform/D3D12WindowRenderer.h @@ -83,6 +83,8 @@ public: swapChainDesc.width = static_cast(width); swapChainDesc.height = static_cast(height); swapChainDesc.bufferCount = kSwapChainBufferCount; + swapChainDesc.presentPolicy.preferredMode = RHI::SwapChainPresentMode::Immediate; + swapChainDesc.presentPolicy.maxFrameLatency = 1u; m_swapChain = m_device->CreateSwapChain(swapChainDesc, m_commandQueue); if (m_swapChain == nullptr || GetD3D12SwapChain() == nullptr) { Shutdown(); diff --git a/editor/src/Platform/D3D12WindowRendererImGuiInterop.h b/editor/src/Platform/D3D12WindowRendererImGuiInterop.h index cb713c85..5d6c048f 100644 --- a/editor/src/Platform/D3D12WindowRendererImGuiInterop.h +++ b/editor/src/Platform/D3D12WindowRendererImGuiInterop.h @@ -74,7 +74,7 @@ inline void RenderImGuiFrame( void* commandLists[] = { renderContext.commandList }; renderContext.commandQueue->ExecuteCommandLists(1, commandLists); - windowRenderer.GetSwapChain()->Present(1, 0); + windowRenderer.GetSwapChain()->Present(); } } // namespace Platform diff --git a/engine/include/XCEngine/RHI/D3D12/D3D12SwapChain.h b/engine/include/XCEngine/RHI/D3D12/D3D12SwapChain.h index 16b42a99..817dd6d9 100644 --- a/engine/include/XCEngine/RHI/D3D12/D3D12SwapChain.h +++ b/engine/include/XCEngine/RHI/D3D12/D3D12SwapChain.h @@ -57,9 +57,11 @@ private: HWND windowHandle, uint32_t width, uint32_t height, - uint32_t bufferCount); + uint32_t bufferCount, + Format format); bool RefreshBackBuffers(); void ReleaseBackBuffers(); + void ApplyMaximumFrameLatency(); uint32_t ResolveResizeFlags() const; uint32_t ResolvePresentFlags() const; uint32_t ResolvePresentSyncInterval() const; diff --git a/engine/include/XCEngine/RHI/OpenGL/OpenGLSwapChain.h b/engine/include/XCEngine/RHI/OpenGL/OpenGLSwapChain.h index 13446131..df90dd67 100644 --- a/engine/include/XCEngine/RHI/OpenGL/OpenGLSwapChain.h +++ b/engine/include/XCEngine/RHI/OpenGL/OpenGLSwapChain.h @@ -31,7 +31,7 @@ public: OpenGLSwapChain(); ~OpenGLSwapChain() override; - bool Initialize(OpenGLDevice* device, HWND window, int width, int height); + bool Initialize(OpenGLDevice* device, const SwapChainDesc& desc); void Shutdown() override; void Present(uint32_t syncInterval = 1, uint32_t flags = 0) override; @@ -48,6 +48,8 @@ public: int GetHeight() const { return m_height; } private: + void ApplyPresentPolicy(); + OpenGLDevice* m_device = nullptr; HWND m_hwnd = nullptr; int m_width = 0; diff --git a/engine/include/XCEngine/RHI/RHISwapChain.h b/engine/include/XCEngine/RHI/RHISwapChain.h index 9b48b6e5..0967c8bb 100644 --- a/engine/include/XCEngine/RHI/RHISwapChain.h +++ b/engine/include/XCEngine/RHI/RHISwapChain.h @@ -16,6 +16,8 @@ public: virtual uint32_t GetCurrentBackBufferIndex() const = 0; virtual RHITexture* GetCurrentBackBuffer() = 0; virtual void Present(uint32_t syncInterval = 1, uint32_t flags = 0) = 0; + virtual const SwapChainPresentPolicy& GetPresentPolicy() const = 0; + virtual SwapChainPresentMode GetActivePresentMode() const = 0; virtual void Resize(uint32_t width, uint32_t height) = 0; virtual void* GetNativeHandle() = 0; diff --git a/engine/include/XCEngine/RHI/RHITypes.h b/engine/include/XCEngine/RHI/RHITypes.h index 51ba7bb0..34bc08d6 100644 --- a/engine/include/XCEngine/RHI/RHITypes.h +++ b/engine/include/XCEngine/RHI/RHITypes.h @@ -198,6 +198,17 @@ struct SamplerDesc { float maxLod; }; +enum class SwapChainPresentMode : uint8_t { + Fifo = 0, + Mailbox, + Immediate +}; + +struct SwapChainPresentPolicy { + SwapChainPresentMode preferredMode = SwapChainPresentMode::Fifo; + uint32_t maxFrameLatency = 2; +}; + struct SwapChainDesc { void* windowHandle = nullptr; uint32_t width = 1280; @@ -210,6 +221,7 @@ struct SwapChainDesc { uint32_t sampleQuality = 0; uint32_t swapEffect = 0; uint32_t flags = 0; + SwapChainPresentPolicy presentPolicy = {}; }; struct RenderTargetViewDesc { diff --git a/engine/include/XCEngine/RHI/Vulkan/VulkanSwapChain.h b/engine/include/XCEngine/RHI/Vulkan/VulkanSwapChain.h index 58793368..6d9a0a6b 100644 --- a/engine/include/XCEngine/RHI/Vulkan/VulkanSwapChain.h +++ b/engine/include/XCEngine/RHI/Vulkan/VulkanSwapChain.h @@ -20,7 +20,10 @@ public: VulkanSwapChain() = default; ~VulkanSwapChain() override; - bool Initialize(VulkanDevice* device, VulkanCommandQueue* presentQueue, HWND__* window, uint32_t width, uint32_t height); + bool Initialize( + VulkanDevice* device, + VulkanCommandQueue* presentQueue, + const SwapChainDesc& desc); bool AcquireNextImage(); void Shutdown() override; @@ -54,6 +57,8 @@ private: uint32_t m_width = 0; uint32_t m_height = 0; uint32_t m_currentImageIndex = 0; + uint32_t m_requestedBufferCount = 2; + Format m_format = Format::R8G8B8A8_UNorm; SwapChainPresentPolicy m_presentPolicy = {}; SwapChainPresentMode m_activePresentMode = SwapChainPresentMode::Fifo; std::vector> m_backBuffers; diff --git a/engine/src/RHI/D3D12/D3D12Device.cpp b/engine/src/RHI/D3D12/D3D12Device.cpp index 760845d8..5b431b06 100644 --- a/engine/src/RHI/D3D12/D3D12Device.cpp +++ b/engine/src/RHI/D3D12/D3D12Device.cpp @@ -1369,9 +1369,7 @@ RHISwapChain* D3D12Device::CreateSwapChain(const SwapChainDesc& desc, RHICommand } auto* swapChain = new D3D12SwapChain(); - HWND hwnd = static_cast(desc.windowHandle); - if (swapChain->Initialize(m_factory.Get(), nativeQueue, hwnd, - desc.width, desc.height, desc.bufferCount)) { + if (swapChain->Initialize(m_factory.Get(), nativeQueue, desc)) { return swapChain; } delete swapChain; diff --git a/engine/src/RHI/D3D12/D3D12SwapChain.cpp b/engine/src/RHI/D3D12/D3D12SwapChain.cpp index 32feaff7..b26fa380 100644 --- a/engine/src/RHI/D3D12/D3D12SwapChain.cpp +++ b/engine/src/RHI/D3D12/D3D12SwapChain.cpp @@ -1,9 +1,72 @@ #include "XCEngine/RHI/D3D12/D3D12SwapChain.h" + +#include #include namespace XCEngine { namespace RHI { +namespace { + +DXGI_FORMAT ToDxgiFormat(Format format) { + switch (format) { + case Format::R8G8B8A8_SRGB: return DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; + case Format::R8G8B8A8_UNorm: + default: + return DXGI_FORMAT_R8G8B8A8_UNORM; + } +} + +bool QueryDxgiTearingSupport(IDXGIFactory4* factory) { + if (factory == nullptr) { + return false; + } + + ComPtr factory5 = {}; + if (FAILED(factory->QueryInterface(IID_PPV_ARGS(&factory5))) || factory5 == nullptr) { + return false; + } + + BOOL allowTearing = FALSE; + return SUCCEEDED(factory5->CheckFeatureSupport( + DXGI_FEATURE_PRESENT_ALLOW_TEARING, + &allowTearing, + sizeof(allowTearing))) && + allowTearing == TRUE; +} + +SwapChainPresentMode ResolveD3D12PresentMode( + const SwapChainPresentPolicy& policy, + bool tearingSupported) { + switch (policy.preferredMode) { + case SwapChainPresentMode::Immediate: + return tearingSupported + ? SwapChainPresentMode::Immediate + : SwapChainPresentMode::Fifo; + case SwapChainPresentMode::Mailbox: + return SwapChainPresentMode::Fifo; + case SwapChainPresentMode::Fifo: + default: + return SwapChainPresentMode::Fifo; + } +} + +uint32_t ResolveDxgiSwapChainFlags( + SwapChainPresentMode presentMode, + const SwapChainPresentPolicy& policy, + bool tearingSupported) { + uint32_t flags = 0u; + if (presentMode == SwapChainPresentMode::Immediate && tearingSupported) { + flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; + } + if (policy.maxFrameLatency > 0u) { + flags |= DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT; + } + return flags; +} + +} // namespace + D3D12SwapChain::D3D12SwapChain() : m_width(0) , m_height(0) @@ -15,34 +78,50 @@ D3D12SwapChain::~D3D12SwapChain() { } bool D3D12SwapChain::Initialize(IDXGIFactory4* factory, ID3D12CommandQueue* commandQueue, HWND windowHandle, uint32_t width, uint32_t height, uint32_t bufferCount) { - DXGI_SWAP_CHAIN_DESC swapChainDesc = {}; - swapChainDesc.BufferCount = bufferCount; - swapChainDesc.BufferDesc.Width = width; - swapChainDesc.BufferDesc.Height = height; - swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; - swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; - swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; - swapChainDesc.OutputWindow = windowHandle; - swapChainDesc.SampleDesc.Count = 1; - swapChainDesc.SampleDesc.Quality = 0; - swapChainDesc.Windowed = TRUE; + SwapChainDesc desc = {}; + desc.windowHandle = windowHandle; + desc.width = width; + desc.height = height; + desc.bufferCount = bufferCount; + return Initialize(factory, commandQueue, desc); +} - IDXGISwapChain* swapChain = nullptr; - HRESULT hResult = factory->CreateSwapChain(commandQueue, &swapChainDesc, &swapChain); - if (FAILED(hResult)) { +bool D3D12SwapChain::Initialize( + IDXGIFactory4* factory, + ID3D12CommandQueue* commandQueue, + const SwapChainDesc& desc) { + if (factory == nullptr || commandQueue == nullptr || desc.windowHandle == nullptr || + desc.width == 0u || desc.height == 0u) { return false; } - hResult = swapChain->QueryInterface(IID_PPV_ARGS(&m_swapChain)); - if (FAILED(hResult)) { + const HWND windowHandle = static_cast(desc.windowHandle); + const uint32_t bufferCount = (std::max)(desc.bufferCount, 2u); + m_tearingSupported = QueryDxgiTearingSupport(factory); + m_presentPolicy = desc.presentPolicy; + if (m_presentPolicy.maxFrameLatency == 0u) { + m_presentPolicy.maxFrameLatency = 1u; + } + m_presentPolicy.maxFrameLatency = + (std::min)(m_presentPolicy.maxFrameLatency, bufferCount); + m_activePresentMode = ResolveD3D12PresentMode(m_presentPolicy, m_tearingSupported); + if (!BuildSwapChain( + factory, + commandQueue, + windowHandle, + desc.width, + desc.height, + bufferCount, + desc.format)) { return false; } m_commandQueue = commandQueue; - m_width = width; - m_height = height; + m_width = desc.width; + m_height = desc.height; m_bufferCount = bufferCount; m_lastResizeResult = S_OK; + ApplyMaximumFrameLatency(); return RefreshBackBuffers(); } @@ -62,7 +141,15 @@ bool D3D12SwapChain::Initialize(IDXGISwapChain* swapChain, uint32_t width, uint3 m_width = width; m_height = height; m_bufferCount = desc.BufferCount; + m_tearingSupported = (desc.Flags & DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING) != 0u; + m_presentPolicy = {}; + m_presentPolicy.maxFrameLatency = + (std::max)(1u, (std::min)(m_presentPolicy.maxFrameLatency, m_bufferCount)); + m_activePresentMode = m_tearingSupported + ? SwapChainPresentMode::Immediate + : SwapChainPresentMode::Fifo; m_lastResizeResult = S_OK; + ApplyMaximumFrameLatency(); return RefreshBackBuffers(); } @@ -73,7 +160,11 @@ void D3D12SwapChain::Shutdown() { m_swapChain.Reset(); m_width = 0; m_height = 0; + m_bufferCount = 2u; m_lastResizeResult = S_OK; + m_presentPolicy = {}; + m_activePresentMode = SwapChainPresentMode::Fifo; + m_tearingSupported = false; } uint32_t D3D12SwapChain::GetCurrentBackBufferIndex() const { @@ -101,7 +192,14 @@ const D3D12Texture& D3D12SwapChain::GetBackBuffer(uint32_t index) const { } void D3D12SwapChain::Present(uint32_t syncInterval, uint32_t flags) { - m_swapChain->Present(syncInterval, flags); + (void)syncInterval; + (void)flags; + + if (m_swapChain == nullptr) { + return; + } + + m_swapChain->Present(ResolvePresentSyncInterval(), ResolvePresentFlags()); } void D3D12SwapChain::Resize(uint32_t width, uint32_t height) { @@ -119,7 +217,7 @@ void D3D12SwapChain::Resize(uint32_t width, uint32_t height) { width, height, DXGI_FORMAT_UNKNOWN, - 0); + ResolveResizeFlags()); m_lastResizeResult = hResult; if (FAILED(hResult)) { m_width = previousWidth; @@ -170,5 +268,80 @@ void D3D12SwapChain::ReleaseBackBuffers() { m_backBuffers.clear(); } +bool D3D12SwapChain::BuildSwapChain( + IDXGIFactory4* factory, + ID3D12CommandQueue* commandQueue, + HWND windowHandle, + uint32_t width, + uint32_t height, + uint32_t bufferCount, + Format format) { + if (factory == nullptr || commandQueue == nullptr || windowHandle == nullptr) { + return false; + } + + DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {}; + swapChainDesc.Width = width; + swapChainDesc.Height = height; + swapChainDesc.Format = ToDxgiFormat(format); + swapChainDesc.Stereo = FALSE; + swapChainDesc.SampleDesc.Count = 1u; + swapChainDesc.SampleDesc.Quality = 0u; + swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + swapChainDesc.BufferCount = bufferCount; + swapChainDesc.Scaling = DXGI_SCALING_STRETCH; + swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; + swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED; + swapChainDesc.Flags = ResolveDxgiSwapChainFlags( + m_activePresentMode, + m_presentPolicy, + m_tearingSupported); + + ComPtr swapChain = {}; + const HRESULT hResult = factory->CreateSwapChainForHwnd( + commandQueue, + windowHandle, + &swapChainDesc, + nullptr, + nullptr, + &swapChain); + if (FAILED(hResult) || swapChain == nullptr) { + return false; + } + + factory->MakeWindowAssociation(windowHandle, DXGI_MWA_NO_ALT_ENTER); + return SUCCEEDED(swapChain.As(&m_swapChain)) && m_swapChain != nullptr; +} + +void D3D12SwapChain::ApplyMaximumFrameLatency() { + if (m_swapChain == nullptr) { + return; + } + + ComPtr swapChain2 = {}; + if (FAILED(m_swapChain.As(&swapChain2)) || swapChain2 == nullptr) { + return; + } + + swapChain2->SetMaximumFrameLatency((std::max)(1u, m_presentPolicy.maxFrameLatency)); +} + +uint32_t D3D12SwapChain::ResolveResizeFlags() const { + return ResolveDxgiSwapChainFlags( + m_activePresentMode, + m_presentPolicy, + m_tearingSupported); +} + +uint32_t D3D12SwapChain::ResolvePresentFlags() const { + return m_activePresentMode == SwapChainPresentMode::Immediate && m_tearingSupported + ? DXGI_PRESENT_ALLOW_TEARING + : 0u; +} + +uint32_t D3D12SwapChain::ResolvePresentSyncInterval() const { + return m_activePresentMode == SwapChainPresentMode::Fifo ? 1u : 0u; +} + } // namespace RHI } // namespace XCEngine diff --git a/engine/src/RHI/OpenGL/OpenGLDevice.cpp b/engine/src/RHI/OpenGL/OpenGLDevice.cpp index fe152e24..a71b25cd 100644 --- a/engine/src/RHI/OpenGL/OpenGLDevice.cpp +++ b/engine/src/RHI/OpenGL/OpenGLDevice.cpp @@ -1312,7 +1312,7 @@ RHISwapChain* OpenGLDevice::CreateSwapChain(const SwapChainDesc& desc, RHIComman auto* swapChain = new OpenGLSwapChain(); HWND hwnd = static_cast(desc.windowHandle); - if (hwnd && InitializeWithExistingWindow(hwnd) && swapChain->Initialize(this, hwnd, desc.width, desc.height)) { + if (hwnd && InitializeWithExistingWindow(hwnd) && swapChain->Initialize(this, desc)) { return swapChain; } diff --git a/engine/src/RHI/OpenGL/OpenGLSwapChain.cpp b/engine/src/RHI/OpenGL/OpenGLSwapChain.cpp index 7c242af9..c79d197f 100644 --- a/engine/src/RHI/OpenGL/OpenGLSwapChain.cpp +++ b/engine/src/RHI/OpenGL/OpenGLSwapChain.cpp @@ -9,6 +9,34 @@ namespace XCEngine { namespace RHI { +namespace { + +OpenGLFormat ResolveSwapChainFormat(Format format) { + switch (format) { + case Format::R16G16B16A16_Float: + return OpenGLFormat::RGBA16F; + case Format::R32G32B32A32_Float: + return OpenGLFormat::RGBA32F; + case Format::R8G8B8A8_SRGB: + case Format::R8G8B8A8_UNorm: + default: + return OpenGLFormat::RGBA8; + } +} + +using WglSwapIntervalProc = BOOL(WINAPI*)(int interval); + +WglSwapIntervalProc ResolveWglSwapIntervalProc() { + return reinterpret_cast(wglGetProcAddress("wglSwapIntervalEXT")); +} + +bool TrySetSwapInterval(int interval) { + WglSwapIntervalProc swapIntervalProc = ResolveWglSwapIntervalProc(); + return swapIntervalProc != nullptr && swapIntervalProc(interval) == TRUE; +} + +} // namespace + OpenGLSwapChain::OpenGLSwapChain() : m_device(nullptr) , m_hwnd(nullptr) @@ -21,17 +49,33 @@ OpenGLSwapChain::~OpenGLSwapChain() { Shutdown(); } -bool OpenGLSwapChain::Initialize(OpenGLDevice* device, HWND window, int width, int height) { +bool OpenGLSwapChain::Initialize(OpenGLDevice* device, const SwapChainDesc& desc) { + if (device == nullptr || desc.windowHandle == nullptr || desc.width == 0u || desc.height == 0u) { + return false; + } + m_device = device; - m_hwnd = window; - m_width = width; - m_height = height; + m_hwnd = static_cast(desc.windowHandle); + m_width = static_cast(desc.width); + m_height = static_cast(desc.height); + m_presentPolicy = desc.presentPolicy; + if (m_presentPolicy.maxFrameLatency == 0u) { + m_presentPolicy.maxFrameLatency = 1u; + } if (!m_backBufferTexture) { m_backBufferTexture = new OpenGLTexture(); - m_backBufferTexture->Initialize(OpenGLTextureType::Texture2D, m_width, m_height, 1, 1, OpenGLFormat::RGBA8, nullptr); + m_backBufferTexture->Initialize( + OpenGLTextureType::Texture2D, + m_width, + m_height, + 1, + 1, + ResolveSwapChainFormat(desc.format), + nullptr); } + ApplyPresentPolicy(); return true; } @@ -42,6 +86,10 @@ void OpenGLSwapChain::Shutdown() { } m_hwnd = nullptr; m_device = nullptr; + m_width = 0; + m_height = 0; + m_presentPolicy = {}; + m_activePresentMode = SwapChainPresentMode::Fifo; } void OpenGLSwapChain::SwapBuffers() { @@ -87,9 +135,24 @@ void OpenGLSwapChain::Present(uint32_t syncInterval, uint32_t flags) { } } +void OpenGLSwapChain::ApplyPresentPolicy() { + m_activePresentMode = SwapChainPresentMode::Fifo; + if (m_device == nullptr || !m_device->MakeContextCurrent()) { + return; + } + + if (m_presentPolicy.preferredMode == SwapChainPresentMode::Immediate && + TrySetSwapInterval(0)) { + m_activePresentMode = SwapChainPresentMode::Immediate; + return; + } + + TrySetSwapInterval(1); +} + void OpenGLSwapChain::Resize(uint32_t width, uint32_t height) { - m_width = width; - m_height = height; + m_width = static_cast(width); + m_height = static_cast(height); } uint32_t OpenGLSwapChain::GetCurrentBackBufferIndex() const { diff --git a/engine/src/RHI/Vulkan/VulkanDevice.cpp b/engine/src/RHI/Vulkan/VulkanDevice.cpp index 1e9aa7fa..64ed2c76 100644 --- a/engine/src/RHI/Vulkan/VulkanDevice.cpp +++ b/engine/src/RHI/Vulkan/VulkanDevice.cpp @@ -623,7 +623,7 @@ RHITexture* VulkanDevice::CreateTexture(const TextureDesc& desc, const void* ini RHISwapChain* VulkanDevice::CreateSwapChain(const SwapChainDesc& desc, RHICommandQueue* presentQueue) { auto* swapChain = new VulkanSwapChain(); - if (swapChain->Initialize(this, static_cast(presentQueue), static_cast(desc.windowHandle), desc.width, desc.height)) { + if (swapChain->Initialize(this, static_cast(presentQueue), desc)) { return swapChain; } delete swapChain; diff --git a/engine/src/RHI/Vulkan/VulkanSwapChain.cpp b/engine/src/RHI/Vulkan/VulkanSwapChain.cpp index 09d26e28..3415d0e4 100644 --- a/engine/src/RHI/Vulkan/VulkanSwapChain.cpp +++ b/engine/src/RHI/Vulkan/VulkanSwapChain.cpp @@ -15,22 +15,109 @@ namespace XCEngine { namespace RHI { +namespace { + +bool HasPresentMode( + const std::vector& supportedPresentModes, + VkPresentModeKHR presentMode) { + for (VkPresentModeKHR candidate : supportedPresentModes) { + if (candidate == presentMode) { + return true; + } + } + + return false; +} + +VkPresentModeKHR ResolvePresentMode( + const SwapChainPresentPolicy& policy, + const std::vector& supportedPresentModes, + SwapChainPresentMode& outActiveMode) { + switch (policy.preferredMode) { + case SwapChainPresentMode::Mailbox: + if (HasPresentMode(supportedPresentModes, VK_PRESENT_MODE_MAILBOX_KHR)) { + outActiveMode = SwapChainPresentMode::Mailbox; + return VK_PRESENT_MODE_MAILBOX_KHR; + } + outActiveMode = SwapChainPresentMode::Fifo; + return VK_PRESENT_MODE_FIFO_KHR; + case SwapChainPresentMode::Immediate: + if (HasPresentMode(supportedPresentModes, VK_PRESENT_MODE_IMMEDIATE_KHR)) { + outActiveMode = SwapChainPresentMode::Immediate; + return VK_PRESENT_MODE_IMMEDIATE_KHR; + } + if (HasPresentMode(supportedPresentModes, VK_PRESENT_MODE_MAILBOX_KHR)) { + outActiveMode = SwapChainPresentMode::Mailbox; + return VK_PRESENT_MODE_MAILBOX_KHR; + } + outActiveMode = SwapChainPresentMode::Fifo; + return VK_PRESENT_MODE_FIFO_KHR; + case SwapChainPresentMode::Fifo: + default: + outActiveMode = SwapChainPresentMode::Fifo; + return VK_PRESENT_MODE_FIFO_KHR; + } +} + +VkSurfaceFormatKHR ResolveSurfaceFormat( + const std::vector& availableFormats, + Format preferredFormat) { + const VkFormat preferredVkFormat = ToVulkanFormat(preferredFormat); + if (preferredVkFormat != VK_FORMAT_UNDEFINED) { + for (const VkSurfaceFormatKHR& candidate : availableFormats) { + if (candidate.format == preferredVkFormat) { + return candidate; + } + } + } + + for (const VkSurfaceFormatKHR& candidate : availableFormats) { + if (candidate.format == VK_FORMAT_R8G8B8A8_UNORM) { + return candidate; + } + } + + for (const VkSurfaceFormatKHR& candidate : availableFormats) { + if (candidate.format == VK_FORMAT_B8G8R8A8_UNORM) { + return candidate; + } + } + + return availableFormats.front(); +} + +} // namespace + VulkanSwapChain::~VulkanSwapChain() { Shutdown(); } -bool VulkanSwapChain::Initialize(VulkanDevice* device, VulkanCommandQueue* presentQueue, HWND__* window, uint32_t width, uint32_t height) { - if (device == nullptr || presentQueue == nullptr || window == nullptr) { +bool VulkanSwapChain::Initialize( + VulkanDevice* device, + VulkanCommandQueue* presentQueue, + const SwapChainDesc& desc) { + if (device == nullptr || + presentQueue == nullptr || + desc.windowHandle == nullptr || + desc.width == 0u || + desc.height == 0u) { return false; } m_device = device; m_presentQueue = presentQueue; - m_window = window; - m_width = width; - m_height = height; + m_window = static_cast(desc.windowHandle); + m_width = desc.width; + m_height = desc.height; + m_requestedBufferCount = (std::max)(desc.bufferCount, 2u); + m_format = desc.format; + m_presentPolicy = desc.presentPolicy; + if (m_presentPolicy.maxFrameLatency == 0u) { + m_presentPolicy.maxFrameLatency = 1u; + } + m_activePresentMode = SwapChainPresentMode::Fifo; - if (!CreateSurface(window)) { + if (!CreateSurface(m_window)) { return false; } @@ -60,21 +147,7 @@ bool VulkanSwapChain::CreateSwapChainResources() { std::vector formats(formatCount); vkGetPhysicalDeviceSurfaceFormatsKHR(m_device->GetPhysicalDevice(), m_surface, &formatCount, formats.data()); - VkSurfaceFormatKHR selectedFormat = formats[0]; - for (const VkSurfaceFormatKHR& candidate : formats) { - if (candidate.format == VK_FORMAT_R8G8B8A8_UNORM) { - selectedFormat = candidate; - break; - } - } - if (selectedFormat.format != VK_FORMAT_R8G8B8A8_UNORM) { - for (const VkSurfaceFormatKHR& candidate : formats) { - if (candidate.format == VK_FORMAT_B8G8R8A8_UNORM) { - selectedFormat = candidate; - break; - } - } - } + VkSurfaceFormatKHR selectedFormat = ResolveSurfaceFormat(formats, m_format); m_surfaceFormat = selectedFormat.format; if ((capabilities.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_DST_BIT) == 0 || @@ -89,19 +162,13 @@ bool VulkanSwapChain::CreateSwapChainResources() { vkGetPhysicalDeviceSurfacePresentModesKHR(m_device->GetPhysicalDevice(), m_surface, &presentModeCount, presentModes.data()); } - VkPresentModeKHR presentMode = VK_PRESENT_MODE_FIFO_KHR; - for (VkPresentModeKHR candidate : presentModes) { - if (candidate == VK_PRESENT_MODE_MAILBOX_KHR) { - presentMode = candidate; - break; - } - } - m_activePresentMode = - presentMode == VK_PRESENT_MODE_MAILBOX_KHR - ? SwapChainPresentMode::Mailbox - : SwapChainPresentMode::Fifo; + VkPresentModeKHR presentMode = ResolvePresentMode( + m_presentPolicy, + presentModes, + m_activePresentMode); - const uint32_t requestedImageCount = (std::max)(2, capabilities.minImageCount); + const uint32_t requestedImageCount = + (std::max)(m_requestedBufferCount, capabilities.minImageCount); const uint32_t imageCount = capabilities.maxImageCount > 0 ? std::min(requestedImageCount, capabilities.maxImageCount) : requestedImageCount; @@ -162,6 +229,7 @@ void VulkanSwapChain::DestroySwapChainResources() { vkDestroySwapchainKHR(m_device->GetDevice(), m_swapChain, nullptr); m_swapChain = VK_NULL_HANDLE; } + m_currentImageIndex = 0u; } bool VulkanSwapChain::AcquireNextImage() { @@ -208,7 +276,11 @@ void VulkanSwapChain::Shutdown() { m_device = nullptr; m_width = 0; m_height = 0; + m_requestedBufferCount = 2u; m_currentImageIndex = 0; + m_format = Format::R8G8B8A8_UNorm; + m_presentPolicy = {}; + m_activePresentMode = SwapChainPresentMode::Fifo; } RHITexture* VulkanSwapChain::GetCurrentBackBuffer() { diff --git a/new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenter.cpp b/new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenter.cpp index cad56538..aa568999 100644 --- a/new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenter.cpp +++ b/new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenter.cpp @@ -12,6 +12,7 @@ using ::XCEngine::RHI::ResourceStates; using ::XCEngine::RHI::ResourceViewDesc; using ::XCEngine::RHI::ResourceViewDimension; using ::XCEngine::RHI::SwapChainDesc; +using ::XCEngine::RHI::SwapChainPresentMode; bool D3D12WindowSwapChainPresenter::Initialize( D3D12HostDevice& hostDevice, @@ -57,6 +58,8 @@ bool D3D12WindowSwapChainPresenter::CreateSwapChain(int width, int height) { swapChainDesc.width = static_cast(width); swapChainDesc.height = static_cast(height); swapChainDesc.bufferCount = kSwapChainBufferCount; + swapChainDesc.presentPolicy.preferredMode = SwapChainPresentMode::Immediate; + swapChainDesc.presentPolicy.maxFrameLatency = 1u; m_swapChain = m_hostDevice->GetRHIDevice()->CreateSwapChain( swapChainDesc, m_hostDevice->GetRHICommandQueue()); @@ -65,8 +68,6 @@ bool D3D12WindowSwapChainPresenter::CreateSwapChain(int width, int height) { return false; } - ConfigureFrameLatency(); - if (!RecreateBackBufferViews()) { m_lastError = "Failed to create swap chain back buffer views."; return false; @@ -77,21 +78,6 @@ bool D3D12WindowSwapChainPresenter::CreateSwapChain(int width, int height) { return true; } -void D3D12WindowSwapChainPresenter::ConfigureFrameLatency() { - D3D12SwapChain* d3d12SwapChain = GetD3D12SwapChain(); - if (d3d12SwapChain == nullptr) { - return; - } - - auto* nativeSwapChain = - static_cast(d3d12SwapChain->GetNativeHandle()); - if (nativeSwapChain == nullptr) { - return; - } - - nativeSwapChain->SetMaximumFrameLatency(kSwapChainBufferCount); -} - void D3D12WindowSwapChainPresenter::DestroySwapChain() { ReleaseBackBufferViews(); @@ -292,7 +278,7 @@ bool D3D12WindowSwapChainPresenter::PresentFrame() { return false; } - m_swapChain->Present(0, 0); + m_swapChain->Present(); m_lastError.clear(); return true; } diff --git a/new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenter.h b/new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenter.h index e1588f7e..8faef2b8 100644 --- a/new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenter.h +++ b/new_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenter.h @@ -41,7 +41,6 @@ public: private: bool CreateSwapChain(int width, int height); - void ConfigureFrameLatency(); void DestroySwapChain(); bool RecreateSwapChain(int width, int height); ::XCEngine::RHI::D3D12SwapChain* GetD3D12SwapChain() const;