Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 44 additions & 25 deletions src/Features/HDRDisplay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -833,6 +833,20 @@ void HDRDisplay::RestoreFramebuffer()
framebufferRedirected = false;
}

bool HDRDisplay::IsFGCompositingThisFrame() const
{
// FG compositing is skipped in menus and loading screens to avoid ghosting / brightness issues,
// when the game is paused (UI stays gamma — HDROutputCS must composite instead), and in VR.
auto& upscaling = globals::features::upscaling;
auto* ui = globals::game::ui;
bool isMainOrLoadingMenu = globals::state->isMainMenuOpen || globals::state->isLoadingMenuOpen;
return upscaling.d3d12SwapChainActive &&
upscaling.settings.frameGenerationMode &&
ui && !ui->GameIsPaused() &&
!isMainOrLoadingMenu &&
!globals::game::isVR;
}

void HDRDisplay::SetUIBuffer()
{
// VR: ISCopy reads kFRAMEBUFFER.SRV to distribute the frame to the HMD and
Expand All @@ -844,16 +858,37 @@ void HDRDisplay::SetUIBuffer()

auto& fb = globals::game::renderer->GetRuntimeData().renderTargets[RE::RENDER_TARGET::kFRAMEBUFFER];

// Handle Frame Generation case - redirect to FG's UI buffer
// D3D12 swap chain path: route UI to uiBufferWrapped only when a compositor
// (ApplyHDR or FFX FG UI composition) will read it; otherwise render UI
// directly into the wrapped back buffer so it survives Present when both
// compositors are skipped (HDR unloaded + FG off/paused). If HDR is loaded
// but the shader is missing, keep UI in kFRAMEBUFFER so the ApplyHDR
// fallback copy carries it to the wrapped back buffer.
if (globals::features::upscaling.d3d12SwapChainActive) {
auto& upscaling = globals::features::upscaling;
if (!upscaling.dx12SwapChain.uiBufferWrapped || !upscaling.dx12SwapChain.uiBufferWrapped->rtv)
if (!upscaling.dx12SwapChain.swapChainBufferWrapped || !upscaling.dx12SwapChain.swapChainBufferWrapped->rtv)
return;

bool ffxWillComposite = IsFGCompositingThisFrame();
bool hdrReady = loaded && hdrDataCB && outputTexture;
bool hdrShaderAvailable = hdrReady && GetHDROutputCS() != nullptr;
bool needsUIBuffer = hdrShaderAvailable || ffxWillComposite;
bool hdrWillFallbackCopy = hdrReady && !hdrShaderAvailable;

if (needsUIBuffer && (!upscaling.dx12SwapChain.uiBufferWrapped || !upscaling.dx12SwapChain.uiBufferWrapped->rtv))
return;

float clearColor[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
globals::d3d::context->ClearRenderTargetView(upscaling.dx12SwapChain.uiBufferWrapped->rtv, clearColor);
ID3D11RenderTargetView* targetRTV = needsUIBuffer ?
upscaling.dx12SwapChain.uiBufferWrapped->rtv :
hdrWillFallbackCopy ? fb.RTV :
upscaling.dx12SwapChain.swapChainBufferWrapped->rtv;

fb.RTV = upscaling.dx12SwapChain.uiBufferWrapped->rtv;
if (needsUIBuffer) {
float clearColor[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
globals::d3d::context->ClearRenderTargetView(targetRTV, clearColor);
}

fb.RTV = targetRTV;
globals::d3d::context->OMSetRenderTargets(1, &fb.RTV, nullptr);
return;
}
Expand Down Expand Up @@ -983,7 +1018,8 @@ void HDRDisplay::ApplyHDR()

// Copy kFRAMEBUFFER directly to destination (bypassing HDR processing)
if (upscaling.d3d12SwapChainActive) {
// Frame Gen path: copy to D3D12 swap chain wrapped buffer
// SetUIBuffer keeps non-FG fallback UI in kFRAMEBUFFER; FG keeps using
// uiBufferWrapped for FidelityFX UI composition.
context->CopyResource(upscaling.dx12SwapChain.swapChainBufferWrapped->resource11, framebufferRT.texture);
} else {
// Normal path: copy directly to swap chain back buffer
Expand Down Expand Up @@ -1206,16 +1242,8 @@ void HDRDisplay::ScaleUIBrightnessForFG()
TracyD3D11Zone(globals::state->tracyCtx, "UI Brightness Scale");

auto& upscaling = globals::features::upscaling;
bool isMainOrLoadingMenu = globals::state->isMainMenuOpen || globals::state->isLoadingMenuOpen;

auto* ui = globals::game::ui;
// FG merges PQ UI from this pass; when paused, UI stays gamma — HDROutput must composite (skipUIComposite stays 0).
bool fgCompositing = upscaling.d3d12SwapChainActive &&
upscaling.settings.frameGenerationMode &&
ui && !ui->GameIsPaused() &&
!isMainOrLoadingMenu &&
!globals::game::isVR;
if (!fgCompositing)
if (!IsFGCompositingThisFrame())
return;

if (!settings.enableHDR)
Expand Down Expand Up @@ -1307,18 +1335,9 @@ void HDRDisplay::UpdateHDRData() const
if (!hdrDataCB)
return;

auto& upscaling = globals::features::upscaling;

// Don't skip UI composite in main menu or loading screens - causes ghosting and brightness issues
bool isMainOrLoadingMenu = globals::state->isMainMenuOpen || globals::state->isLoadingMenuOpen;

auto* ui = globals::game::ui;
bool fgActiveThisFrame = upscaling.d3d12SwapChainActive &&
upscaling.settings.frameGenerationMode &&
ui && !ui->GameIsPaused() &&
!isMainOrLoadingMenu &&
!globals::game::isVR;
bool skipUIComposite = fgActiveThisFrame;
bool skipUIComposite = IsFGCompositingThisFrame();

// Linear Lighting keeps the pipeline linear throughout.
// Without it, ISHDR gamma-encodes its output even in HDR mode.
Expand Down
3 changes: 3 additions & 0 deletions src/Features/HDRDisplay.h
Original file line number Diff line number Diff line change
Expand Up @@ -148,4 +148,7 @@ struct HDRDisplay : public Feature
private:
bool showHDRWarningPopup = false;
bool pendingHDREnable = false;

// True when FFX frame generation is actively compositing UI this frame.
bool IsFGCompositingThisFrame() const;
};
8 changes: 7 additions & 1 deletion src/Features/Upscaling.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,18 @@ HRESULT WINAPI hk_D3D11CreateDeviceAndSwapChainUpscaling(
if (upscaling.IsBackendInitialized())
upscaling.CheckBackendFeatures(pAdapter);

// Use better swap effect to prevent tearing and improve performance
// FLIP_DISCARD requires BufferCount >= 2 and a flip-model-compatible (non-sRGB) format.
pSwapChainDesc->SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
if (pSwapChainDesc->BufferCount < 2)
pSwapChainDesc->BufferCount = 2;

if (globals::features::hdrDisplay.loaded) {
logger::info("[Upscaling] Upgrading swap chain format from {} to R10G10B10A2_UNORM for HDR", static_cast<int>(pSwapChainDesc->BufferDesc.Format));
pSwapChainDesc->BufferDesc.Format = DXGI_FORMAT_R10G10B10A2_UNORM;
} else if (pSwapChainDesc->BufferDesc.Format == DXGI_FORMAT_B8G8R8A8_UNORM_SRGB) {
pSwapChainDesc->BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
} else if (pSwapChainDesc->BufferDesc.Format == DXGI_FORMAT_R8G8B8A8_UNORM_SRGB) {
pSwapChainDesc->BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
}

bool shouldProxy = !globals::game::isVR;
Expand Down
Loading