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
66 changes: 57 additions & 9 deletions src/Features/HDRDisplay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,27 @@ namespace
hdr->RedirectFramebuffer();
func(a_this, a3, a_target, a_4, a_5);
hdr->RestoreFramebuffer();

// VR: RedirectFramebuffer made ISHDR write to hdrTexture (float16); after
// RestoreFramebuffer kFRAMEBUFFER reverts to its original texture.
// ISCopy reads kFRAMEBUFFER.SRV to distribute the frame to the HMD and
// companion window, so we must write the tonemapped content back into
// kFRAMEBUFFER before ISCopy runs.
//
// TODO (future HDR HMD support): The correct pipeline is to run the full
// HDR composite (PQ encode, paper white, peak nits) HERE, writing the
// result back to kFRAMEBUFFER so ISCopy distributes HDR-processed content
// to both the HMD and companion at their native sizes. The post-Present
// ApplyHDR path cannot do this correctly because ISCopy has already run
// and the companion back buffer (1024x1024) does not match outputTexture
// (sized from kMAIN). Requires hooking the ISCopy vfunc to fire
// HDROutputCS before distribution.
if (globals::game::isVR && hdr->settings.enableHDR &&
hdr->hdrTexture && hdr->hdrTexture->resource) {
auto& fb = globals::game::renderer->GetRuntimeData().renderTargets[RE::RENDER_TARGETS::kFRAMEBUFFER];
if (fb.texture)
globals::d3d::context->CopyResource(fb.texture, hdr->hdrTexture->resource.get());
}
}
static inline REL::Relocation<decltype(thunk)> func;
};
Expand Down Expand Up @@ -596,6 +617,12 @@ void HDRDisplay::SetupResources()
DXGI_SWAP_CHAIN_DESC scDesc;
if (SUCCEEDED(globals::d3d::swapChain->GetDesc(&scDesc))) {
swapChainFormat = scDesc.BufferDesc.Format;
logger::info("[HDR] Swap chain format: {} ({})", (int)swapChainFormat,
swapChainFormat == DXGI_FORMAT_R10G10B10A2_UNORM ? "R10G10B10A2_UNORM (HDR10)" :
swapChainFormat == DXGI_FORMAT_R16G16B16A16_FLOAT ? "R16G16B16A16_FLOAT (scRGB)" :
swapChainFormat == DXGI_FORMAT_R8G8B8A8_UNORM ? "R8G8B8A8_UNORM (SDR 8-bit)" :
swapChainFormat == DXGI_FORMAT_B8G8R8A8_UNORM ? "B8G8R8A8_UNORM (SDR 8-bit)" :
"other");
}

// Intermediate texture for HDR processing
Expand All @@ -614,7 +641,8 @@ void HDRDisplay::SetupResources()
hdrRtvDesc.Texture2D.MipSlice = 0;
hdrTexture->CreateRTV(hdrRtvDesc);

// Output texture - must match swap chain format for CopyResource to work
// Output texture must match the swap chain format: ApplyHDR does
// CopyResource(backBuffer, outputTexture) and CopyResource requires identical formats.
texDesc.Format = swapChainFormat;
srvDesc.Format = texDesc.Format;
uavDesc.Format = texDesc.Format;
Expand Down Expand Up @@ -763,6 +791,13 @@ void HDRDisplay::RestoreFramebuffer()

void HDRDisplay::SetUIBuffer()
{
// VR: ISCopy reads kFRAMEBUFFER.SRV to distribute the frame to the HMD and
// companion window. Redirecting kFRAMEBUFFER.RTV here would cause vanilla UI
// to render into uiTexture instead, so ISCopy would send a UI-less frame to
// the HMD. Leave kFRAMEBUFFER alone; vanilla UI bakes directly into it.
if (globals::game::isVR)
return;

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

// Handle Frame Generation case - redirect to FG's UI buffer
Expand Down Expand Up @@ -850,16 +885,29 @@ void HDRDisplay::ApplyHDR()
// When HDR is enabled, ISHDR wrote to hdrTexture (float16, values >1.0 preserved).
// When SDR, ISHDR wrote to kFRAMEBUFFER (UNORM, tonemapped 0-1).
auto& framebufferRT = renderer->GetRuntimeData().renderTargets[RE::RENDER_TARGETS::kFRAMEBUFFER];
ID3D11ShaderResourceView* sceneSRV = (settings.enableHDR && hdrTexture && hdrTexture->srv) ? hdrTexture->srv.get() : framebufferRT.SRV;

// Choose the correct UI buffer based on which path is active
// When D3D12 swap chain is active, vanilla UI renders to uiBufferWrapped
// Otherwise it renders to our uiTexture
// Scene SRV selection:
// - VR: kFRAMEBUFFER at this point has scene + vanilla UI + ImGui all baked in
// (thunk restored hdrTexture→kFRAMEBUFFER, vanilla UI rendered on top, ImGui
// just rendered to kFRAMEBUFFER.RTV above). Use it directly so the companion
// window gets everything without a separate uiTexture capture pass.
// - Non-VR HDR: hdrTexture has float16 scene values >1.0 preserved from ISHDR.
// - Non-VR SDR: kFRAMEBUFFER has the tonemapped 0-1 ISHDR output.
ID3D11ShaderResourceView* sceneSRV =
globals::game::isVR ? framebufferRT.SRV :
(settings.enableHDR && hdrTexture && hdrTexture->srv) ? hdrTexture->srv.get() :
framebufferRT.SRV;

// Choose the correct UI buffer based on which path is active.
// VR uses the framebuffer directly, which already contains vanilla UI/ImGui.
// Binding a separate uiTexture here would duplicate the UI layer.
ID3D11ShaderResourceView* uiSRV = nullptr;
if (upscaling.d3d12SwapChainActive && upscaling.dx12SwapChain.uiBufferWrapped) {
uiSRV = upscaling.dx12SwapChain.uiBufferWrapped->srv;
} else if (uiTexture && uiTexture->srv) {
uiSRV = uiTexture->srv.get();
if (!globals::game::isVR) {
if (upscaling.d3d12SwapChainActive && upscaling.dx12SwapChain.uiBufferWrapped) {
uiSRV = upscaling.dx12SwapChain.uiBufferWrapped->srv;
} else if (uiTexture && uiTexture->srv) {
uiSRV = uiTexture->srv.get();
}
}

ID3D11ShaderResourceView* views[2] = { sceneSRV, uiSRV };
Expand Down
16 changes: 10 additions & 6 deletions src/Hooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -254,15 +254,19 @@ struct IDXGISwapChain_Present
UINT viewportCount = 1;
globals::d3d::context->RSGetViewports(&viewportCount, &savedViewport);

// When FG is NOT active + HDR loaded: ImGui renders to hdr->uiTexture (we composite in ApplyHDR)
// When HDR is NOT loaded: ImGui renders directly to kFRAMEBUFFER (vanilla path)
// ImGui render target selection:
// - FG: kFRAMEBUFFER (FidelityFX composites afterwards)
// - VR: kFRAMEBUFFER — SetUIBuffer skips VR, so vanilla UI is already baked into
// kFRAMEBUFFER. Rendering ImGui here too means kFRAMEBUFFER.SRV has
// scene + vanilla UI + ImGui when ApplyHDR reads it at the end of this hook.
// - Non-VR HDR: uiTexture (ApplyHDR composites separately for precise UI brightness)
// - Vanilla/no-HDR: kFRAMEBUFFER directly (is the swap chain back buffer pre-upgrade)
if (frameGenActive) {
// FG path: render ImGui to the same buffer as vanilla UI (uiBufferWrapped)
// FidelityFX will composite this after frame interpolation
// FG path: render ImGui alongside vanilla UI in uiBufferWrapped
auto& data = globals::game::renderer->GetRuntimeData().renderTargets[RE::RENDER_TARGET::kFRAMEBUFFER];
globals::d3d::context->OMSetRenderTargets(1, &data.RTV, nullptr);
} else if (hdrReady) {
// Non-FG HDR path: render ImGui to hdr->uiTexture
} else if (hdrReady && !globals::game::isVR) {
// Non-VR HDR path: render ImGui to uiTexture for compositing in ApplyHDR
ID3D11RenderTargetView* uiRTV = nullptr;
D3D11_TEXTURE2D_DESC texDesc = {};

Expand Down
6 changes: 1 addition & 5 deletions src/ShaderCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1643,11 +1643,6 @@ namespace SIE
{
using enum RE::ImageSpaceManager::ImageSpaceEffectEnum;

if (!REL::Module::IsVR() && strcmp(imagespaceShader.name, "BSImagespaceShaderISTemporalAA") == 0) {
descriptor = RE::ImageSpaceManager::GetCurrentIndex(ISTemporalAA);
return true;
}

static const ankerl::unordered_dense::map<std::string_view, uint32_t> descriptors{
// { "BSImagespaceShaderISBlur", RE::ImageSpaceManager::GetCurrentIndex(ISBlur) },
// { "BSImagespaceShaderBlur3", RE::ImageSpaceManager::GetCurrentIndex(ISBlur3) },
Expand Down Expand Up @@ -1781,6 +1776,7 @@ namespace SIE
{ "BSImagespaceShaderISApplyVolumetricLighting", RE::ImageSpaceManager::GetCurrentIndex(ISApplyVolumetricLighting) },
{ "BSImagespaceShaderReflectionsRayTracing", RE::ImageSpaceManager::GetCurrentIndex(ISReflectionsRayTracing) },
//{ "BSImagespaceShaderReflectionsDebugSpecMask", RE::ImageSpaceManager::GetCurrentIndex(ISReflectionsDebugSpecMask) },
{ "BSImagespaceShaderISTemporalAA", RE::ImageSpaceManager::GetCurrentIndex(ISTemporalAA) },
{ "BSImagespaceShaderVolumetricLightingRaymarchCS", 256 },
{ "BSImagespaceShaderVolumetricLightingGenerateCS", 257 },
{ "BSImagespaceShaderVolumetricLightingBlurHCS", RE::ImageSpaceManager::GetCurrentIndex(ISVolumetricLightingBlurHCS) },
Expand Down
Loading