# 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.