Files
Juliet/Romeo/docs/Juliet_src_Graphics_D3D12_D3D12SwapChain.md
T

9.9 KiB

Juliet\src\Graphics\D3D12\D3D12SwapChain

Source Files

  • Header: Juliet\src\Graphics\D3D12\D3D12SwapChain.h
  • Source: Juliet\src\Graphics\D3D12\D3D12SwapChain.cpp

AI Description

Based on the C++ code provided, here is a structured analysis of the D3D12 Swap Chain implementation within what appears to be a game engine or graphics library (likely "Juliet").

1. Core Functionality Summary

The module handles:

  • Swap Chain Creation: Initializing DXGI swap chains with HDR support options and frame rate control.
  • Texture Management: Managing the backing D3D12 textures for each back buffer, transitioning states between Presentation (PRESENT) and Rendering (RENDER_TARGET).
  • Fence Synchronization: Ensuring previous frames are finished before starting new ones to prevent tearing or accessing dirty buffers during rendering.

2. Detailed Code Analysis by Function

A. State Transition ? Acquisition (AcquireSwapChainTexture / WaitAndAcquire)

These functions manage the lifecycle of individual back buffer textures.

// ... inside AcquireSwapChainTexture logic
barrierDesc.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrierDesc.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
barrierDesc.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT; // Source state (from GPU)
barrierDesc.Transition.StateAfter  = D3D12_RESOURCE_STATE_RENDER_TARGET; // Destination state (for CPU/GPU read/write)

// The transition barrier is issued on the command list.
d3d12CommandList-?GraphicsCommandList.CommandList-?ResourceBarrier(1, ?barrierDesc);

*swapchainTexture = reinterpret_cast?Texture*?(?windowData-?SwapChainTextureContainers[...].ActiveTexture); // Returns a pointer wrapper to access TextureDetails struct directly.

Key Insight: The code explicitly transitions the resource from PRESENT (owned by DXGI) back to RENDER_TARGET. This is standard practice because:

  1. Rendering commands (Draw, etc.) typically operate on RENDER_TARGET or COPY_SRC/DST states depending on context, but transitioning immediately allows for faster access without additional fences if the renderer supports it.
  2. It prepares the texture to be read by CPU (if using staging buffers) and written to again in subsequent frame passes via copy commands (ResourceBarrier with D3D12_RESOURCE_BARRIER_FLAG_ALLOW_VISUAL_LOSS_DISCARD).

B. Synchronization Logic (WaitForSwapchain)

Before any command list is executed on a new back buffer, this function ensures the previous frame has finished encoding and presenting.

if (windowData-?InFlightFences[windowData-?WindowFrameCounter] != nullptr) {
    if (!Wait(d3d12Driver, true, ?windowData-?InFlightFences[...], 1 JULIET_DEBUG_PARAM(...))) return false;
}
return true;

Key Insight: This checks the fence associated with the specific frame being processed. If a fence exists (meaning previous work is pending), it waits for that fence to signal completion before proceeding.

C. Format Resolution ? Metadata (GetSwapChainTextureFormat)

This helper determines the pixel format used by the swap chain, which is crucial for shader compilation and resource generation.

// Returns TextureFormat enum derived from DXGI_FORMAT of active buffer
auto* d3d12Driver = static_cast?D3D12Driver*?(driver.Get());
return windowData-?SwapChainTextureContainers[windowData-?WindowFrameCounter].Header.CreateInfo.Format;

Key Insight: It accesses the CreateInfo header stored within the texture container to ensure consistent format handling throughout the engine, rather than querying DXGI directly every time.

D. Swap Chain Creation (Internal::CreateSwapChain)

This is the heavy lifter that interfaces with Windows API and DirectX 12 factories. Notable features:

1. Configuration:

  • Resolution: Uses 0 for width/height, implying full screen usage where the factory determines size based on window client area.
  • Present Mode ? Composition: Supports SDR (BG8) or HDR via a mapping table SwapchainCompositionToColorSpace. It dynamically sets color space and format (e.g., RGB10A2 for HDR).
  • Tearing Support: Checks driver-?IsTearingSupported to set the DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING flag if present, preventing frame skips when V-Sync is off.

2. Factory Interactions ? WMA (Windows Meta Association):

// Queries for IDXGISwapChain3 interface needed for HDR settings and specific behaviors.
IDXGISwapChain3* swapChain3 = nullptr;
...
swapChain-?QueryInterface(IID_IDXGISwapChain3, ...); // Note: The original CreateSwapChainForHwnd returns a 1 pointer usually, but here it queries then releases the old one? 
// Correction in logic analysis: The code does 'query', assigns to swapChain3 variable, and calls Release on swapChain immediately. This implies swapChain returned by QueryInterface was temporary or they are managing two pointers (original + queried) briefly before swapping roles.

if (!parentFactory-?MakeWindowAssociation(windowHandle, DXGI_MWA_NO_WINDOW_CHANGES))
    Log(LogLevel::Warning, ... "Cannot handle window resizing"); // IMPORTANT: Disables automatic resize messages to D3D12 logic? 

Key Insight on WMA: The code calls DXGI_MWA_NO_WINDOW_CHANGES. This tells the system not to send a WM_SIZE message when the user resizes or moves the window.

  • Why? If the app handles resizing by simply calling Resize functions internally without checking Win32 events, this is an optimization (avoiding extra logic). However, if dynamic re-resizing is needed externally, this needs to be reconsidered based on how else size changes are handled in your engine.

3. HDR Setup: It specifically sets the color space only for SwapChainComposition::HDR or other non-SDR types:

if (composition != SwapChainComposition::SDR) {
    swapChain3-?SetColorSpace1(SwapchainCompositionToColorSpace[...]); 
}

3. Critical Logic Check ? Observations

🚨 Potential Issue in CreateSwapChain Interface Handling

There is a slight logical flow quirk regarding the Swap Chain pointer assignment:

IDXGISwapChain1* swapChain = nullptr;
// Creates chain, returns as IDXGISwapChain1 (or higher if capabilities met)
HRESULT result = driver-?DXGIFactory-?CreateSwapChainForHwnd(..., ?swapChain);

// Queries for the specific interface immediately after creation
result = swapChain-?QueryInterface(IID_IDXGISwapChain3, reinterpret_cast?void**?(?swapChain3));
swapChain-?Release(); // ?--- Releases the original pointer passed by reference? 
if (FAILED(result)) { ... }
  • Behavior: CreateSwapChainForHwnd creates an interface and returns it. The caller is expected to release one copy of that interface eventually. By calling QueryInterface, you create a new handle (swapChain3). If the original return value from Create doesn't support QueryInterface directly (unlikely) or if memory management isn't strict, releasing via Release() on the temporary variable might be premature depending on D3D12 ownership rules here.
  • Standard Pattern: Usually: Create...(?SwapChain), then use it until done with all interfaces required for HDR/Feature queries before doing cleanup logic in other parts of your engine's destroy sequence (like DestroySwapChain).

🚨 Memory Management ? Cleanup (Internal::DestroySwapChain)

The destructor releases staging resources and frees D3D12 texture objects manually. This indicates a custom resource pool system rather than just relying on DXGI to clean up everything at shutdown, which is good for performance profiling but requires careful Free implementations (likely global Free lists common in Rust/Juliet ecosystems).

📝 Resource Barrier Nuance

In the transition block:

barrierDesc.Transition.pResource = ...; // Pointer math looks correct based on struct layout.
barrierDesc.Transition.Subresource = 0; 
// Note: Since this is not a multi-sub-resource texture (like video textures), Subresource=0 covers everything.

4. Recommendations / Improvements

  1. Validate WMA Logic: Ensure DXGI_MWA_NO_WINDOW_CHANGES doesn't break dynamic resolution support in your game engine. If the window moves/resizes but you don't update texture dimensions, it could cause black screens or crashes upon resize.
  2. Interface Management: The immediate release of the original Swap Chain pointer after acquiring IDXGISwapChain3 looks unusual if the engine expects to keep using generic features later without re-acquisition every time (e.g., in a destructor). Consider storing the generic interface and only converting up/down when HDR is specifically accessed or during teardown.
  3. Error Handling: The CreateSwapChainForHwnd call checks for failure, but if it fails due to invalid window handle (IsWindow check passed earlier), ensure robust logging regarding why (e.g., focus lost, incompatible OS version).

Conclusion

This is a well-structured implementation of D3D12 Swap Chain logic tailored for an engine that likely supports HDR and custom resolution scaling. The explicit fence waiting and manual state transitions indicate high control over the rendering pipeline to ensure frame timing consistency and prevent visual

Symbols

Namespace Juliet::D3D12

Functions & Methods

  • extern bool AcquireSwapChainTexture(NonNullPtr<CommandList> commandList, NonNullPtr<Window> window, Texture** swapChainTexture)
  • extern bool WaitAndAcquireSwapChainTexture(NonNullPtr<CommandList> commandList, NonNullPtr<Window> window, Texture** swapChainTexture)
  • extern bool WaitForSwapchain(NonNullPtr<GPUDriver> driver, NonNullPtr<Window> window)
  • extern TextureFormat GetSwapChainTextureFormat(NonNullPtr<GPUDriver> driver, NonNullPtr<Window> window)

Namespace Juliet::D3D12::Internal

Functions & Methods

  • extern bool CreateSwapChain(NonNullPtr<D3D12Driver> driver, NonNullPtr<D3D12WindowData> windowData, SwapChainComposition composition, PresentMode presentMode)
  • extern void DestroySwapChain(NonNullPtr<D3D12Driver> driver, NonNullPtr<D3D12WindowData> windowData)