9.4 KiB
9.4 KiB
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_editorlocally. - 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
SwapChainDescdoes not carry a formal presentation policy.RHISwapChain::Present(syncInterval, flags)is not a sound cross-backend abstraction.- D3D12 swapchain creation is still using a conservative DXGI path and does not expose tearing/unlocked presentation as a first-class capability.
new_editorcannot 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
SwapChainPresentModetoRHITypes.h. - Add
SwapChainPresentPolicytoRHITypes.h. - Extend
SwapChainDescwithpresentPolicy. - Extend
RHISwapChainwith 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
CreateSwapChainForHwndpath with explicit flags. - Map policy to actual D3D12 behavior:
Fifo-> vsync pathImmediate-> tearing/unlocked path when supported, otherwise explicit fallbackMailbox-> 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_editorD3D12 presenter should requestImmediatepresentation 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.hengine/include/XCEngine/RHI/RHISwapChain.hengine/include/XCEngine/RHI/D3D12/D3D12SwapChain.hengine/src/RHI/D3D12/D3D12SwapChain.cppengine/src/RHI/D3D12/D3D12Device.cppengine/include/XCEngine/RHI/Vulkan/VulkanSwapChain.hengine/src/RHI/Vulkan/VulkanSwapChain.cppengine/include/XCEngine/RHI/OpenGL/OpenGLSwapChain.hengine/src/RHI/OpenGL/OpenGLSwapChain.cppnew_editor/app/Rendering/D3D12/D3D12WindowSwapChainPresenter.cppeditor/src/Platform/D3D12WindowRenderer.heditor/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_editorrequestsImmediate- 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
- Add RHI types for present policy.
- Update
RHISwapChaininterface. - Port D3D12 swapchain internals to the new model.
- Update
new_editorto requestImmediate. - Port Vulkan/OpenGL to the same policy model.
- 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_UNorminside D3D12 swapchain creation and consumeSwapChainDesc.format. - Ensure D3D12 swapchain state resets are complete:
m_presentPolicym_activePresentModem_tearingSupportedm_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
SwapChainDescthrough 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
SwapChainPresentPolicyin the backend, not in the editor wrapper
- carry full
- Vulkan:
- carry requested
SwapChainPresentPolicy - pick a Vulkan present mode from supported modes with explicit fallback order
- expose both requested policy and resolved active mode
- carry requested
- OpenGL:
- carry requested
SwapChainPresentPolicy - resolve swap interval once from policy
- explicitly report fallback when immediate swap interval is unavailable
- carry requested
Phase 2: Rewire caller intent explicitly
new_editormain 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
- request
- 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.presentPolicyis the only place callers express intentRHISwapChain::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.hengine/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.hengine/src/RHI/D3D12/D3D12SwapChain.cppengine/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.hengine/src/RHI/Vulkan/VulkanSwapChain.cppengine/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.hengine/src/RHI/OpenGL/OpenGLSwapChain.cppengine/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.cppeditor/src/Platform/D3D12WindowRenderer.heditor/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_editorand 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
RHISwapChainpure virtual interface. - Any remaining performance limit after this pass can be attributed to another subsystem with the present path already architecturally corrected.