From b293838c27a1dc2a5afc11b755cc12ddf24aa4ba Mon Sep 17 00:00:00 2001 From: Dlizzio <77717521+Dlizzio@users.noreply.github.com> Date: Tue, 3 Feb 2026 10:38:12 -0700 Subject: [PATCH 01/21] fix(UI): Background Blur with Upscaling Fixes UI background blur with Upscaling. --- src/Features/Upscaling.cpp | 40 +++++ src/Features/Upscaling.h | 15 ++ src/Features/Upscaling/DX12SwapChain.cpp | 25 +++ src/Features/Upscaling/DX12SwapChain.h | 15 ++ src/Menu/BackgroundBlur.cpp | 213 ++++++++++++++++++----- 5 files changed, 267 insertions(+), 41 deletions(-) diff --git a/src/Features/Upscaling.cpp b/src/Features/Upscaling.cpp index faae04e175..0351708b21 100644 --- a/src/Features/Upscaling.cpp +++ b/src/Features/Upscaling.cpp @@ -1215,6 +1215,46 @@ IDXGISwapChain* Upscaling::GetProxySwapChain() return dx12SwapChain.GetSwapChainProxy(); } +ID3D11Texture2D* Upscaling::GetD3D11BackbufferTexture() const +{ + if (d3d12SwapChainActive) { + return dx12SwapChain.GetBackbufferTexture(); + } + return nullptr; +} + +ID3D11RenderTargetView* Upscaling::GetD3D11BackbufferRTV() const +{ + if (d3d12SwapChainActive) { + return dx12SwapChain.GetBackbufferRTV(); + } + return nullptr; +} + +ID3D11Texture2D* Upscaling::GetD3D11UIBufferTexture() const +{ + if (d3d12SwapChainActive) { + return dx12SwapChain.GetUIBufferTexture(); + } + return nullptr; +} + +ID3D11ShaderResourceView* Upscaling::GetD3D11UIBufferSRV() const +{ + if (d3d12SwapChainActive) { + return dx12SwapChain.GetUIBufferSRV(); + } + return nullptr; +} + +ID3D11RenderTargetView* Upscaling::GetD3D11UIBufferRTV() const +{ + if (d3d12SwapChainActive) { + return dx12SwapChain.GetUIBufferRTV(); + } + return nullptr; +} + void Upscaling::Upscale() { auto upscaleMethod = GetUpscaleMethod(); diff --git a/src/Features/Upscaling.h b/src/Features/Upscaling.h index cdeeda0818..3538877a20 100644 --- a/src/Features/Upscaling.h +++ b/src/Features/Upscaling.h @@ -200,6 +200,21 @@ struct Upscaling : Feature void CreateProxyInterop(); IDXGISwapChain* GetProxySwapChain(); + // Get the D3D11 backbuffer texture when D3D12 swap chain is active (for background blur) + ID3D11Texture2D* GetD3D11BackbufferTexture() const; + + // Get the pre-created RTV for the backbuffer when D3D12 swap chain is active + ID3D11RenderTargetView* GetD3D11BackbufferRTV() const; + + // Get the UI buffer texture (HUD during gameplay) when D3D12 swap chain is active + ID3D11Texture2D* GetD3D11UIBufferTexture() const; + + // Get the UI buffer SRV when D3D12 swap chain is active + ID3D11ShaderResourceView* GetD3D11UIBufferSRV() const; + + // Get the UI buffer RTV when D3D12 swap chain is active + ID3D11RenderTargetView* GetD3D11UIBufferRTV() const; + private: struct Main_UpdateJitter { diff --git a/src/Features/Upscaling/DX12SwapChain.cpp b/src/Features/Upscaling/DX12SwapChain.cpp index a2d005809f..8a6487ec2b 100644 --- a/src/Features/Upscaling/DX12SwapChain.cpp +++ b/src/Features/Upscaling/DX12SwapChain.cpp @@ -399,6 +399,31 @@ void DX12SwapChain::SetUIBuffer() } } +ID3D11Texture2D* DX12SwapChain::GetBackbufferTexture() const +{ + return swapChainBufferWrapped ? swapChainBufferWrapped->resource11 : nullptr; +} + +ID3D11RenderTargetView* DX12SwapChain::GetBackbufferRTV() const +{ + return swapChainBufferWrapped ? swapChainBufferWrapped->rtv : nullptr; +} + +ID3D11Texture2D* DX12SwapChain::GetUIBufferTexture() const +{ + return uiBufferWrapped ? uiBufferWrapped->resource11 : nullptr; +} + +ID3D11ShaderResourceView* DX12SwapChain::GetUIBufferSRV() const +{ + return uiBufferWrapped ? uiBufferWrapped->srv : nullptr; +} + +ID3D11RenderTargetView* DX12SwapChain::GetUIBufferRTV() const +{ + return uiBufferWrapped ? uiBufferWrapped->rtv : nullptr; +} + void DX12SwapChain::CreateSharedResources() { auto renderer = globals::game::renderer; diff --git a/src/Features/Upscaling/DX12SwapChain.h b/src/Features/Upscaling/DX12SwapChain.h index 19d51e276a..b0d6831261 100644 --- a/src/Features/Upscaling/DX12SwapChain.h +++ b/src/Features/Upscaling/DX12SwapChain.h @@ -113,6 +113,21 @@ class DX12SwapChain void SetUIBuffer(); + // Get the D3D11 backbuffer texture (game world content) + ID3D11Texture2D* GetBackbufferTexture() const; + + // Get the pre-created RTV for the backbuffer + ID3D11RenderTargetView* GetBackbufferRTV() const; + + // Get the UI buffer texture (HUD content during gameplay) + ID3D11Texture2D* GetUIBufferTexture() const; + + // Get the UI buffer SRV for reading + ID3D11ShaderResourceView* GetUIBufferSRV() const; + + // Get the UI buffer RTV for writing + ID3D11RenderTargetView* GetUIBufferRTV() const; + // D3D12 interop resource management void CreateSharedResources(); }; diff --git a/src/Menu/BackgroundBlur.cpp b/src/Menu/BackgroundBlur.cpp index 6becee2aec..c49641ff87 100644 --- a/src/Menu/BackgroundBlur.cpp +++ b/src/Menu/BackgroundBlur.cpp @@ -5,6 +5,7 @@ #include "BackgroundBlur.h" #include "../Globals.h" #include "../Util.h" +#include "../Features/Upscaling.h" #include #include @@ -38,7 +39,10 @@ namespace BackgroundBlur winrt::com_ptr blendState; winrt::com_ptr scissorRasterizerState; - // Downsampled textures for blur (quarter-res for performance) + // Blend state for compositing UI over game world (alpha blending) + winrt::com_ptr compositeBlendState; + + // Downsampled textures for blur (1/8 res for performance) winrt::com_ptr downsampleTexture; winrt::com_ptr downsampleRTV; winrt::com_ptr downsampleSRV; @@ -51,6 +55,15 @@ namespace BackgroundBlur winrt::com_ptr blurSRV1; winrt::com_ptr blurSRV2; + // Legacy composite texture members (kept for cleanup compatibility) + winrt::com_ptr compositeTexture; + winrt::com_ptr compositeRTV; + winrt::com_ptr compositeSRV; + + // 1x1 transparent black texture for clearing with scissor + winrt::com_ptr clearTexture; + winrt::com_ptr clearSRV; + UINT textureWidth = 0; UINT textureHeight = 0; UINT downsampledWidth = 0; @@ -154,6 +167,55 @@ namespace BackgroundBlur return false; } + // Create blend state for compositing UI over game world (pre-multiplied alpha blend) + // UI buffer uses pre-multiplied alpha, so SrcBlend=ONE and DestBlend=INV_SRC_ALPHA + D3D11_BLEND_DESC compositeBlendDesc = {}; + compositeBlendDesc.RenderTarget[0].BlendEnable = TRUE; + compositeBlendDesc.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE; + compositeBlendDesc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; + compositeBlendDesc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; + compositeBlendDesc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; + compositeBlendDesc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA; + compositeBlendDesc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; + compositeBlendDesc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; + + hr = device->CreateBlendState(&compositeBlendDesc, compositeBlendState.put()); + if (FAILED(hr)) { + logger::error("Failed to create composite blend state"); + initializationFailed = true; + return false; + } + + // Create 1x1 transparent black texture for clearing with scissor + D3D11_TEXTURE2D_DESC clearTexDesc = {}; + clearTexDesc.Width = 1; + clearTexDesc.Height = 1; + clearTexDesc.MipLevels = 1; + clearTexDesc.ArraySize = 1; + clearTexDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + clearTexDesc.SampleDesc.Count = 1; + clearTexDesc.Usage = D3D11_USAGE_IMMUTABLE; + clearTexDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE; + + uint32_t clearPixel = 0x00000000; // RGBA = 0,0,0,0 (transparent black) + D3D11_SUBRESOURCE_DATA clearData = {}; + clearData.pSysMem = &clearPixel; + clearData.SysMemPitch = 4; + + hr = device->CreateTexture2D(&clearTexDesc, &clearData, clearTexture.put()); + if (FAILED(hr)) { + logger::error("Failed to create clear texture"); + initializationFailed = true; + return false; + } + + hr = device->CreateShaderResourceView(clearTexture.get(), nullptr, clearSRV.put()); + if (FAILED(hr)) { + logger::error("Failed to create clear SRV"); + initializationFailed = true; + return false; + } + // Create scissor-enabled rasterizer state D3D11_RASTERIZER_DESC rsDesc = {}; rsDesc.FillMode = D3D11_FILL_SOLID; @@ -200,8 +262,11 @@ namespace BackgroundBlur blurRTV2 = nullptr; blurSRV1 = nullptr; blurSRV2 = nullptr; + compositeTexture = nullptr; + compositeRTV = nullptr; + compositeSRV = nullptr; - // Create downsampled texture description + // Create downsampled texture description (no full-res composite needed - all work at 1/8 res) D3D11_TEXTURE2D_DESC texDesc = {}; texDesc.Width = dsWidth; texDesc.Height = dsHeight; @@ -313,7 +378,7 @@ namespace BackgroundBlur downsampledHeight = dsHeight; } - void PerformBlur(ID3D11Texture2D* sourceTexture, ID3D11RenderTargetView* targetRTV, ImVec2 menuMin, ImVec2 menuMax) + void PerformBlur(ID3D11Texture2D* sourceTexture, ID3D11RenderTargetView* targetRTV, ImVec2 menuMin, ImVec2 menuMax, ID3D11ShaderResourceView* uiBufferSRV = nullptr, ID3D11RenderTargetView* uiBufferRTV = nullptr) { std::lock_guard lock(resourceMutex); @@ -334,14 +399,6 @@ namespace BackgroundBlur D3D11_TEXTURE2D_DESC sourceDesc; sourceTexture->GetDesc(&sourceDesc); - // Create SRV for source - ID3D11ShaderResourceView* sourceSRV = nullptr; - HRESULT hr = globals::d3d::device->CreateShaderResourceView(sourceTexture, nullptr, &sourceSRV); - if (FAILED(hr)) { - logger::error("Failed to create source SRV for blur"); - return; - } - // Save current state ID3D11RenderTargetView* originalRTV = nullptr; ID3D11DepthStencilView* originalDSV = nullptr; @@ -354,7 +411,23 @@ namespace BackgroundBlur ID3D11RasterizerState* originalRS = nullptr; context->RSGetState(&originalRS); - // Downsample source to quarter resolution with bilinear filtering + auto constantBufferPtr = constantBuffer.get(); + auto samplerStatePtr = samplerState.get(); + + // Create SRV for source texture + ID3D11ShaderResourceView* sourceSRV = nullptr; + HRESULT hr = globals::d3d::device->CreateShaderResourceView(sourceTexture, nullptr, &sourceSRV); + if (FAILED(hr)) { + logger::error("Failed to create source SRV for blur"); + if (originalRTV) originalRTV->Release(); + if (originalDSV) originalDSV->Release(); + if (originalRS) originalRS->Release(); + return; + } + + ID3D11ShaderResourceView* nullSRV = nullptr; + + // Set up downsample viewport - all work done at 1/8 resolution for performance D3D11_VIEWPORT downsampleViewport = {}; downsampleViewport.Width = static_cast(downsampledWidth); downsampleViewport.Height = static_cast(downsampledHeight); @@ -364,29 +437,31 @@ namespace BackgroundBlur auto downsampleRTVPtr = downsampleRTV.get(); context->OMSetRenderTargets(1, &downsampleRTVPtr, nullptr); - - auto constantBufferPtr = constantBuffer.get(); - auto samplerStatePtr = samplerState.get(); context->VSSetShader(vertexShader.get(), nullptr, 0); + context->PSSetShader(horizontalPixelShader.get(), nullptr, 0); context->PSSetSamplers(0, 1, &samplerStatePtr); - // Simple copy to downsample (bilinear filtering does the work) + // Step 1: Downsample game world directly (bilinear filtering does the work) BlurConstants downsampleConstants = {}; downsampleConstants.texelSize[0] = 1.0f / static_cast(sourceDesc.Width); downsampleConstants.texelSize[1] = 1.0f / static_cast(sourceDesc.Height); - downsampleConstants.texelSize[2] = 0.0f; - downsampleConstants.texelSize[3] = 0.0f; downsampleConstants.blurParams[0] = 1; // Single sample for downsample context->UpdateSubresource(constantBuffer.get(), 0, nullptr, &downsampleConstants, 0, 0); - context->PSSetConstantBuffers(0, 1, &constantBufferPtr); - context->PSSetShader(horizontalPixelShader.get(), nullptr, 0); context->PSSetShaderResources(0, 1, &sourceSRV); context->Draw(3, 0); - - ID3D11ShaderResourceView* nullSRV = nullptr; context->PSSetShaderResources(0, 1, &nullSRV); + // Step 2: Blend UI buffer at downsampled resolution (pre-multiplied alpha) + // Small HUD elements may be slightly softened but this is much faster + if (uiBufferSRV && compositeBlendState) { + context->OMSetBlendState(compositeBlendState.get(), nullptr, 0xFFFFFFFF); + context->PSSetShaderResources(0, 1, &uiBufferSRV); + context->Draw(3, 0); + context->PSSetShaderResources(0, 1, &nullSRV); + context->OMSetBlendState(nullptr, nullptr, 0xFFFFFFFF); + } + // Calculate blur parameters at eighth resolution float blurRadius = BLUR_INTENSITY * 10.0f; int sampleCount = 9; @@ -454,6 +529,21 @@ namespace BackgroundBlur context->Draw(3, 0); context->PSSetShaderResources(0, 1, &nullSRV); + // Clear the UI buffer in the scissor area so the HUD doesn't draw on top of our blur + // The HUD outside the menu area remains visible + if (uiBufferRTV && clearSRV) { + // Draw transparent black over just the scissor area to clear the HUD there + context->OMSetRenderTargets(1, &uiBufferRTV, nullptr); + // Use opaque blend to overwrite + context->OMSetBlendState(nullptr, nullptr, 0xFFFFFFFF); + // Scissor is still set from above + // Draw the 1x1 transparent black texture (shader will sample and output transparent) + auto clearSRVPtr = clearSRV.get(); + context->PSSetShaderResources(0, 1, &clearSRVPtr); + context->Draw(3, 0); + context->PSSetShaderResources(0, 1, &nullSRV); + } + // Restore state context->OMSetRenderTargets(1, &originalRTV, originalDSV); context->OMSetBlendState(nullptr, nullptr, 0xFFFFFFFF); @@ -482,12 +572,20 @@ namespace BackgroundBlur constantBuffer = nullptr; samplerState = nullptr; blendState = nullptr; + compositeBlendState = nullptr; scissorRasterizerState = nullptr; downsampleTexture = nullptr; downsampleRTV = nullptr; downsampleSRV = nullptr; + compositeTexture = nullptr; + compositeRTV = nullptr; + compositeSRV = nullptr; + + clearTexture = nullptr; + clearSRV = nullptr; + blurTexture1 = nullptr; blurTexture2 = nullptr; blurRTV1 = nullptr; @@ -542,27 +640,61 @@ namespace BackgroundBlur return; } - // Get current render target + // Check if upscaling with D3D12 swap chain is active + auto& upscaling = globals::features::upscaling; + bool useUpscalingBackbuffer = upscaling.d3d12SwapChainActive; + ID3D11RenderTargetView* currentRTV = nullptr; - context->OMGetRenderTargets(1, ¤tRTV, nullptr); + ID3D11Texture2D* currentTexture = nullptr; + ID3D11ShaderResourceView* uiBufferSRV = nullptr; // For compositing HUD before blur + ID3D11RenderTargetView* uiBufferRTV = nullptr; // For clearing HUD in blur area + bool ownsRTV = false; // Track if we need to release the RTV + + if (useUpscalingBackbuffer) { + // When D3D12 swap chain is active, get the backbuffer directly from upscaling + // because OMGetRenderTargets returns the UI buffer, not the game world + currentTexture = upscaling.GetD3D11BackbufferTexture(); + currentRTV = upscaling.GetD3D11BackbufferRTV(); + if (!currentTexture || !currentRTV) { + return; + } + // AddRef since we'll Release later in cleanup (to match non-upscaling path) + currentTexture->AddRef(); + currentRTV->AddRef(); + ownsRTV = true; + + // During gameplay (not paused), HUD is in separate UI buffer + // We'll composite it onto the backbuffer before blurring + auto ui = globals::game::ui; + bool gameNotPaused = ui && !ui->GameIsPaused(); + if (gameNotPaused) { + uiBufferSRV = upscaling.GetD3D11UIBufferSRV(); + uiBufferRTV = upscaling.GetD3D11UIBufferRTV(); + } + } else { + // Normal path: get current render target + context->OMGetRenderTargets(1, ¤tRTV, nullptr); - if (!currentRTV) { - return; - } + if (!currentRTV) { + return; + } + ownsRTV = true; - // Get render target texture and its dimensions - ID3D11Resource* currentRT = nullptr; - currentRTV->GetResource(¤tRT); + // Get render target texture and its dimensions + ID3D11Resource* currentRT = nullptr; + currentRTV->GetResource(¤tRT); - ID3D11Texture2D* currentTexture = nullptr; - HRESULT hr = currentRT->QueryInterface(__uuidof(ID3D11Texture2D), (void**)¤tTexture); + HRESULT hr = currentRT->QueryInterface(__uuidof(ID3D11Texture2D), (void**)¤tTexture); - if (FAILED(hr) || !currentTexture) { - if (currentRT) - currentRT->Release(); - if (currentRTV) - currentRTV->Release(); - return; + if (FAILED(hr) || !currentTexture) { + if (currentRT) + currentRT->Release(); + if (currentRTV) + currentRTV->Release(); + return; + } + + currentRT->Release(); } D3D11_TEXTURE2D_DESC texDesc; @@ -579,7 +711,6 @@ namespace BackgroundBlur ImGuiContext* ctx = ImGui::GetCurrentContext(); if (!ctx || ctx->Windows.Size == 0) { currentTexture->Release(); - currentRT->Release(); currentRTV->Release(); return; } @@ -619,12 +750,12 @@ namespace BackgroundBlur ImVec2 windowMax = windowRect.Max; // Perform blur for this window area - PerformBlur(currentTexture, currentRTV, windowMin, windowMax); + // Pass UI buffer SRV/RTV for compositing and clearing during upscaling gameplay + PerformBlur(currentTexture, currentRTV, windowMin, windowMax, uiBufferSRV, uiBufferRTV); } // Cleanup currentTexture->Release(); - currentRT->Release(); currentRTV->Release(); } From b531febcc36ead1d706f34c24e9235789a3bba18 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 3 Feb 2026 17:39:25 +0000 Subject: [PATCH 02/21] =?UTF-8?q?style:=20=F0=9F=8E=A8=20apply=20pre-commi?= =?UTF-8?q?t.ci=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automated formatting by clang-format, prettier, and other hooks. See https://pre-commit.ci for details. --- src/Menu/BackgroundBlur.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Menu/BackgroundBlur.cpp b/src/Menu/BackgroundBlur.cpp index c49641ff87..1e24e456ae 100644 --- a/src/Menu/BackgroundBlur.cpp +++ b/src/Menu/BackgroundBlur.cpp @@ -3,9 +3,9 @@ // License: MIT License #include "BackgroundBlur.h" +#include "../Features/Upscaling.h" #include "../Globals.h" #include "../Util.h" -#include "../Features/Upscaling.h" #include #include @@ -419,9 +419,12 @@ namespace BackgroundBlur HRESULT hr = globals::d3d::device->CreateShaderResourceView(sourceTexture, nullptr, &sourceSRV); if (FAILED(hr)) { logger::error("Failed to create source SRV for blur"); - if (originalRTV) originalRTV->Release(); - if (originalDSV) originalDSV->Release(); - if (originalRS) originalRS->Release(); + if (originalRTV) + originalRTV->Release(); + if (originalDSV) + originalDSV->Release(); + if (originalRS) + originalRS->Release(); return; } @@ -648,7 +651,7 @@ namespace BackgroundBlur ID3D11Texture2D* currentTexture = nullptr; ID3D11ShaderResourceView* uiBufferSRV = nullptr; // For compositing HUD before blur ID3D11RenderTargetView* uiBufferRTV = nullptr; // For clearing HUD in blur area - bool ownsRTV = false; // Track if we need to release the RTV + bool ownsRTV = false; // Track if we need to release the RTV if (useUpscalingBackbuffer) { // When D3D12 swap chain is active, get the backbuffer directly from upscaling From 97dde7caf539d1bfb39b82bceae5d420bf319788 Mon Sep 17 00:00:00 2001 From: Dlizzio <77717521+Dlizzio@users.noreply.github.com> Date: Wed, 4 Feb 2026 01:56:50 -0700 Subject: [PATCH 03/21] respect rounded corners --- .../Shaders/Menu/BackgroundBlurComposite.hlsl | 84 ++++++++++++++++++ src/Menu/BackgroundBlur.cpp | 87 ++++++++++++++++--- src/Menu/BackgroundBlur.h | 9 +- 3 files changed, 166 insertions(+), 14 deletions(-) create mode 100644 package/Shaders/Menu/BackgroundBlurComposite.hlsl diff --git a/package/Shaders/Menu/BackgroundBlurComposite.hlsl b/package/Shaders/Menu/BackgroundBlurComposite.hlsl new file mode 100644 index 0000000000..6c2beb3947 --- /dev/null +++ b/package/Shaders/Menu/BackgroundBlurComposite.hlsl @@ -0,0 +1,84 @@ +// Composite Blur Pass Shader with Rounded Rectangle Mask +// Part of the BackgroundBlur system - applies blurred texture with rounded corners + +cbuffer BlurBuffer : register(b0) +{ + float4 TexelSize; // x = 1/width, y = 1/height, z = blur strength, w = unused + int4 BlurParams; // x = samples, y = unused, z = unused, w = unused +}; + +cbuffer WindowBuffer : register(b1) +{ + float4 WindowRect; // x = minX, y = minY, z = maxX, w = maxY (in pixels) + float4 WindowParams; // x = cornerRadius, y = screenWidth, z = screenHeight, w = unused +}; + +SamplerState LinearSampler : register(s0); +Texture2D InputTexture : register(t0); + +struct VS_OUTPUT +{ + float4 Position : SV_POSITION; + float2 TexCoord : TEXCOORD0; +}; + +VS_OUTPUT VS_Main(uint vertexID : SV_VertexID) +{ + VS_OUTPUT output; + output.TexCoord = float2((vertexID << 1) & 2, vertexID & 2); + output.Position = float4(output.TexCoord * 2.0f - 1.0f, 0.0f, 1.0f); + output.Position.y = -output.Position.y; + return output; +} + +// Compute signed distance to a rounded rectangle +// Returns negative inside, positive outside +float RoundedRectSDF(float2 pixelPos, float2 rectMin, float2 rectMax, float radius) +{ + // Center of the rectangle + float2 rectCenter = (rectMin + rectMax) * 0.5f; + float2 rectHalfSize = (rectMax - rectMin) * 0.5f; + + // Clamp radius to not exceed half the smallest dimension + radius = min(radius, min(rectHalfSize.x, rectHalfSize.y)); + + // Distance from center + float2 p = abs(pixelPos - rectCenter) - rectHalfSize + radius; + + // SDF for rounded rectangle + return length(max(p, 0.0f)) + min(max(p.x, p.y), 0.0f) - radius; +} + +float4 PS_Main(VS_OUTPUT input) : SV_TARGET +{ + // Convert UV to pixel coordinates + float2 pixelPos = input.TexCoord * float2(WindowParams.y, WindowParams.z); + + // Get window bounds and corner radius + float2 rectMin = WindowRect.xy; + float2 rectMax = WindowRect.zw; + float cornerRadius = WindowParams.x; + + // Calculate signed distance to rounded rectangle + float sdf = RoundedRectSDF(pixelPos, rectMin, rectMax, cornerRadius); + + // Create smooth edge (anti-aliased) + // Negative = inside, positive outside + // Use 1.0 pixel transition for smooth edge + float alpha = saturate(-sdf); + + // Early out if completely outside + if (alpha <= 0.0f) + { + discard; + } + + // Sample the blurred texture + float4 blurColor = InputTexture.Sample(LinearSampler, input.TexCoord); + + // Apply rounded corner mask to alpha + // The blur strength is applied via blend state, so just use the rounded mask here + blurColor.a = alpha; + + return blurColor; +} diff --git a/src/Menu/BackgroundBlur.cpp b/src/Menu/BackgroundBlur.cpp index 1e24e456ae..28d93c9a15 100644 --- a/src/Menu/BackgroundBlur.cpp +++ b/src/Menu/BackgroundBlur.cpp @@ -34,7 +34,9 @@ namespace BackgroundBlur winrt::com_ptr vertexShader; winrt::com_ptr horizontalPixelShader; winrt::com_ptr verticalPixelShader; + winrt::com_ptr compositePixelShader; // For rounded corner compositing winrt::com_ptr constantBuffer; + winrt::com_ptr windowConstantBuffer; // For window rect and corner radius winrt::com_ptr samplerState; winrt::com_ptr blendState; winrt::com_ptr scissorRasterizerState; @@ -79,6 +81,13 @@ namespace BackgroundBlur int blurParams[4]; // x = samples, y = unused, z = unused, w = unused }; + // Window constants for rounded corner compositing + struct WindowConstants + { + float windowRect[4]; // x = minX, y = minY, z = maxX, w = maxY (in pixels) + float windowParams[4]; // x = cornerRadius, y = screenWidth, z = screenHeight, w = unused + }; + } // anonymous namespace bool Initialize() @@ -119,6 +128,14 @@ namespace BackgroundBlur return false; } + // Compile composite pixel shader (for rounded corner compositing) + compositePixelShader.attach(static_cast(Util::CompileShader(L"Data\\Shaders\\Menu\\BackgroundBlurComposite.hlsl", {}, "ps_5_0", "PS_Main"))); + if (!compositePixelShader) { + logger::error("Failed to compile composite blur pixel shader"); + initializationFailed = true; + return false; + } + // Create constant buffer D3D11_BUFFER_DESC bufferDesc = {}; bufferDesc.Usage = D3D11_USAGE_DEFAULT; @@ -132,6 +149,19 @@ namespace BackgroundBlur return false; } + // Create window constant buffer (for rounded corner parameters) + D3D11_BUFFER_DESC windowBufferDesc = {}; + windowBufferDesc.Usage = D3D11_USAGE_DEFAULT; + windowBufferDesc.ByteWidth = sizeof(WindowConstants); + windowBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; + + hr = device->CreateBuffer(&windowBufferDesc, nullptr, windowConstantBuffer.put()); + if (FAILED(hr)) { + logger::error("Failed to create window constant buffer"); + initializationFailed = true; + return false; + } + // Create sampler state D3D11_SAMPLER_DESC samplerDesc = {}; samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; @@ -378,7 +408,7 @@ namespace BackgroundBlur downsampledHeight = dsHeight; } - void PerformBlur(ID3D11Texture2D* sourceTexture, ID3D11RenderTargetView* targetRTV, ImVec2 menuMin, ImVec2 menuMax, ID3D11ShaderResourceView* uiBufferSRV = nullptr, ID3D11RenderTargetView* uiBufferRTV = nullptr) + void PerformBlur(ID3D11Texture2D* sourceTexture, ID3D11RenderTargetView* targetRTV, ImVec2 menuMin, ImVec2 menuMax, float cornerRadius, ID3D11ShaderResourceView* uiBufferSRV = nullptr, ID3D11RenderTargetView* uiBufferRTV = nullptr) { std::lock_guard lock(resourceMutex); @@ -509,15 +539,15 @@ namespace BackgroundBlur context->Draw(3, 0); context->PSSetShaderResources(0, 1, &nullSRV); - // Final composition: upscale from quarter-res with scissor test - // Bilinear sampler smooths the upscale automatically + // Final composition: upscale from quarter-res with rounded corner mask context->RSSetViewports(1, &originalViewport); + // Expand scissor rect slightly for anti-aliased rounded corner edges D3D11_RECT scissorRect; - scissorRect.left = static_cast((std::max)(0.0f, menuMin.x)); - scissorRect.top = static_cast((std::max)(0.0f, menuMin.y)); - scissorRect.right = static_cast((std::min)(static_cast(sourceDesc.Width), menuMax.x)); - scissorRect.bottom = static_cast((std::min)(static_cast(sourceDesc.Height), menuMax.y)); + scissorRect.left = static_cast((std::max)(0.0f, menuMin.x - 2.0f)); + scissorRect.top = static_cast((std::max)(0.0f, menuMin.y - 2.0f)); + scissorRect.right = static_cast((std::min)(static_cast(sourceDesc.Width), menuMax.x + 2.0f)); + scissorRect.bottom = static_cast((std::min)(static_cast(sourceDesc.Height), menuMax.y + 2.0f)); context->RSSetState(scissorRasterizerState.get()); context->RSSetScissorRects(1, &scissorRect); @@ -526,6 +556,27 @@ namespace BackgroundBlur float blendFactor[4] = { 1.0f, 1.0f, 1.0f, BLUR_INTENSITY * 0.8f }; context->OMSetBlendState(blendState.get(), blendFactor, 0xFFFFFFFF); + // Use composite shader with rounded corner mask if available, otherwise fallback + if (compositePixelShader && windowConstantBuffer) { + // Set up window constants for rounded corner compositing + WindowConstants windowConstants = {}; + windowConstants.windowRect[0] = menuMin.x; + windowConstants.windowRect[1] = menuMin.y; + windowConstants.windowRect[2] = menuMax.x; + windowConstants.windowRect[3] = menuMax.y; + windowConstants.windowParams[0] = cornerRadius; + windowConstants.windowParams[1] = static_cast(sourceDesc.Width); + windowConstants.windowParams[2] = static_cast(sourceDesc.Height); + windowConstants.windowParams[3] = 0.0f; + context->UpdateSubresource(windowConstantBuffer.get(), 0, nullptr, &windowConstants, 0, 0); + + context->PSSetShader(compositePixelShader.get(), nullptr, 0); + auto windowConstantBufferPtr = windowConstantBuffer.get(); + context->PSSetConstantBuffers(0, 1, &constantBufferPtr); + context->PSSetConstantBuffers(1, 1, &windowConstantBufferPtr); + } + // Note: if composite shader not available, vertical shader is still set from blur pass + // Use blurred quarter-res texture, bilinear filtering upscales smoothly auto srv2Ptr = blurSRV2.get(); context->PSSetShaderResources(0, 1, &srv2Ptr); @@ -535,11 +586,22 @@ namespace BackgroundBlur // Clear the UI buffer in the scissor area so the HUD doesn't draw on top of our blur // The HUD outside the menu area remains visible if (uiBufferRTV && clearSRV) { + // IMPORTANT: Switch back to horizontal shader for UI buffer clearing + // The composite shader expects WindowBuffer which isn't set up for this pass + context->PSSetShader(horizontalPixelShader.get(), nullptr, 0); + + // Restore scissor to exact window bounds for clearing + D3D11_RECT clearScissorRect; + clearScissorRect.left = static_cast((std::max)(0.0f, menuMin.x)); + clearScissorRect.top = static_cast((std::max)(0.0f, menuMin.y)); + clearScissorRect.right = static_cast((std::min)(static_cast(sourceDesc.Width), menuMax.x)); + clearScissorRect.bottom = static_cast((std::min)(static_cast(sourceDesc.Height), menuMax.y)); + context->RSSetScissorRects(1, &clearScissorRect); + // Draw transparent black over just the scissor area to clear the HUD there context->OMSetRenderTargets(1, &uiBufferRTV, nullptr); // Use opaque blend to overwrite context->OMSetBlendState(nullptr, nullptr, 0xFFFFFFFF); - // Scissor is still set from above // Draw the 1x1 transparent black texture (shader will sample and output transparent) auto clearSRVPtr = clearSRV.get(); context->PSSetShaderResources(0, 1, &clearSRVPtr); @@ -572,7 +634,9 @@ namespace BackgroundBlur vertexShader = nullptr; horizontalPixelShader = nullptr; verticalPixelShader = nullptr; + compositePixelShader = nullptr; constantBuffer = nullptr; + windowConstantBuffer = nullptr; samplerState = nullptr; blendState = nullptr; compositeBlendState = nullptr; @@ -752,9 +816,12 @@ namespace BackgroundBlur ImVec2 windowMin = windowRect.Min; ImVec2 windowMax = windowRect.Max; - // Perform blur for this window area + // Get window corner rounding from the window's style + float cornerRadius = window->WindowRounding; + + // Perform blur for this window area with rounded corners // Pass UI buffer SRV/RTV for compositing and clearing during upscaling gameplay - PerformBlur(currentTexture, currentRTV, windowMin, windowMax, uiBufferSRV, uiBufferRTV); + PerformBlur(currentTexture, currentRTV, windowMin, windowMax, cornerRadius, uiBufferSRV, uiBufferRTV); } // Cleanup diff --git a/src/Menu/BackgroundBlur.h b/src/Menu/BackgroundBlur.h index c4891a904c..24b9c94f56 100644 --- a/src/Menu/BackgroundBlur.h +++ b/src/Menu/BackgroundBlur.h @@ -29,13 +29,14 @@ namespace BackgroundBlur void CreateBlurTextures(UINT width, UINT height, DXGI_FORMAT format); /** - * @brief Performs two-pass Gaussian blur on source texture + * @brief Performs two-pass Gaussian blur on source texture with rounded corner support * @param sourceTexture Input texture to blur * @param targetRTV Output render target - * @param menuMin Top-left corner of menu area (for scissor test) - * @param menuMax Bottom-right corner of menu area (for scissor test) + * @param menuMin Top-left corner of menu area + * @param menuMax Bottom-right corner of menu area + * @param cornerRadius Corner radius for rounded rectangle mask */ - void PerformBlur(ID3D11Texture2D* sourceTexture, ID3D11RenderTargetView* targetRTV, ImVec2 menuMin, ImVec2 menuMax); + void PerformBlur(ID3D11Texture2D* sourceTexture, ID3D11RenderTargetView* targetRTV, ImVec2 menuMin, ImVec2 menuMax, float cornerRadius); /** * @brief Cleans up all blur resources From de06aa6510598ca04e48a4df470785bb3cefb146 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 08:57:29 +0000 Subject: [PATCH 04/21] =?UTF-8?q?style:=20=F0=9F=8E=A8=20apply=20pre-commi?= =?UTF-8?q?t.ci=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automated formatting by clang-format, prettier, and other hooks. See https://pre-commit.ci for details. --- .../Shaders/Menu/BackgroundBlurComposite.hlsl | 20 +++++++++---------- src/Menu/BackgroundBlur.cpp | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/package/Shaders/Menu/BackgroundBlurComposite.hlsl b/package/Shaders/Menu/BackgroundBlurComposite.hlsl index 6c2beb3947..68a8b97739 100644 --- a/package/Shaders/Menu/BackgroundBlurComposite.hlsl +++ b/package/Shaders/Menu/BackgroundBlurComposite.hlsl @@ -38,13 +38,13 @@ float RoundedRectSDF(float2 pixelPos, float2 rectMin, float2 rectMax, float radi // Center of the rectangle float2 rectCenter = (rectMin + rectMax) * 0.5f; float2 rectHalfSize = (rectMax - rectMin) * 0.5f; - + // Clamp radius to not exceed half the smallest dimension radius = min(radius, min(rectHalfSize.x, rectHalfSize.y)); - + // Distance from center float2 p = abs(pixelPos - rectCenter) - rectHalfSize + radius; - + // SDF for rounded rectangle return length(max(p, 0.0f)) + min(max(p.x, p.y), 0.0f) - radius; } @@ -53,32 +53,32 @@ float4 PS_Main(VS_OUTPUT input) : SV_TARGET { // Convert UV to pixel coordinates float2 pixelPos = input.TexCoord * float2(WindowParams.y, WindowParams.z); - + // Get window bounds and corner radius float2 rectMin = WindowRect.xy; float2 rectMax = WindowRect.zw; float cornerRadius = WindowParams.x; - + // Calculate signed distance to rounded rectangle float sdf = RoundedRectSDF(pixelPos, rectMin, rectMax, cornerRadius); - + // Create smooth edge (anti-aliased) // Negative = inside, positive outside // Use 1.0 pixel transition for smooth edge float alpha = saturate(-sdf); - + // Early out if completely outside if (alpha <= 0.0f) { discard; } - + // Sample the blurred texture float4 blurColor = InputTexture.Sample(LinearSampler, input.TexCoord); - + // Apply rounded corner mask to alpha // The blur strength is applied via blend state, so just use the rounded mask here blurColor.a = alpha; - + return blurColor; } diff --git a/src/Menu/BackgroundBlur.cpp b/src/Menu/BackgroundBlur.cpp index 28d93c9a15..a7405a0420 100644 --- a/src/Menu/BackgroundBlur.cpp +++ b/src/Menu/BackgroundBlur.cpp @@ -589,7 +589,7 @@ namespace BackgroundBlur // IMPORTANT: Switch back to horizontal shader for UI buffer clearing // The composite shader expects WindowBuffer which isn't set up for this pass context->PSSetShader(horizontalPixelShader.get(), nullptr, 0); - + // Restore scissor to exact window bounds for clearing D3D11_RECT clearScissorRect; clearScissorRect.left = static_cast((std::max)(0.0f, menuMin.x)); @@ -597,7 +597,7 @@ namespace BackgroundBlur clearScissorRect.right = static_cast((std::min)(static_cast(sourceDesc.Width), menuMax.x)); clearScissorRect.bottom = static_cast((std::min)(static_cast(sourceDesc.Height), menuMax.y)); context->RSSetScissorRects(1, &clearScissorRect); - + // Draw transparent black over just the scissor area to clear the HUD there context->OMSetRenderTargets(1, &uiBufferRTV, nullptr); // Use opaque blend to overwrite From ba49883a438c9d0de039920f7371b10c1f4f8203 Mon Sep 17 00:00:00 2001 From: Dlizzio <77717521+Dlizzio@users.noreply.github.com> Date: Wed, 4 Feb 2026 03:05:10 -0700 Subject: [PATCH 05/21] added dither --- .../Shaders/Menu/BackgroundBlurComposite.hlsl | 52 ++++++++++++++++++- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/package/Shaders/Menu/BackgroundBlurComposite.hlsl b/package/Shaders/Menu/BackgroundBlurComposite.hlsl index 6c2beb3947..807e0fdcdb 100644 --- a/package/Shaders/Menu/BackgroundBlurComposite.hlsl +++ b/package/Shaders/Menu/BackgroundBlurComposite.hlsl @@ -31,6 +31,50 @@ VS_OUTPUT VS_Main(uint vertexID : SV_VertexID) return output; } +// High quality 2D hash - returns two independent random values in [0,1] +// Uses different prime multipliers to avoid correlation between x and y +float2 Hash22(float2 p) +{ + // Two independent hashes using different constants + float3 p3 = frac(float3(p.xyx) * float3(0.1031f, 0.1030f, 0.0973f)); + p3 += dot(p3, p3.yzx + 33.33f); + return frac((p3.xx + p3.yz) * p3.zy); +} + +// Soft sampling with blurred dithering - takes 4 samples with jittered offsets +// and averages them to smooth out the noise while still breaking up blocky pixels +float4 SampleWithSoftening(float2 uv, float2 pixelPos, float2 texelSize) +{ + // Get base random offset for this pixel + float2 noise = Hash22(pixelPos); + + // Rotated grid offsets (45 degree rotation for better coverage) + // This creates a smooth disc-like sampling pattern + static const float2 offsets[4] = { + float2(-0.25f, -0.25f), + float2( 0.25f, -0.25f), + float2(-0.25f, 0.25f), + float2( 0.25f, 0.25f) + }; + + // Random rotation angle based on pixel position + float angle = noise.x * 6.28318530718f; + float s = sin(angle); + float c = cos(angle); + float2x2 rotation = float2x2(c, -s, s, c); + + // Sample 4 points with rotated jittered offsets and average + float4 result = 0; + [unroll] + for (int i = 0; i < 4; i++) + { + float2 jitter = mul(rotation, offsets[i]) * texelSize; + result += InputTexture.Sample(LinearSampler, uv + jitter); + } + + return result * 0.25f; +} + // Compute signed distance to a rounded rectangle // Returns negative inside, positive outside float RoundedRectSDF(float2 pixelPos, float2 rectMin, float2 rectMax, float radius) @@ -73,8 +117,12 @@ float4 PS_Main(VS_OUTPUT input) : SV_TARGET discard; } - // Sample the blurred texture - float4 blurColor = InputTexture.Sample(LinearSampler, input.TexCoord); + // Calculate texel size of the downsampled blur texture + // WindowParams.y/z are screen dimensions, blur texture is 1/8th of that + float2 blurTexelSize = 8.0f / float2(WindowParams.y, WindowParams.z); + + // Sample with soft dithering to hide blocky pixels from the downsampled blur + float4 blurColor = SampleWithSoftening(input.TexCoord, pixelPos, blurTexelSize); // Apply rounded corner mask to alpha // The blur strength is applied via blend state, so just use the rounded mask here From 45253b757ca951de66213b4c901ba59a1a5ac030 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 10:07:49 +0000 Subject: [PATCH 06/21] =?UTF-8?q?style:=20=F0=9F=8E=A8=20apply=20pre-commi?= =?UTF-8?q?t.ci=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automated formatting by clang-format, prettier, and other hooks. See https://pre-commit.ci for details. --- package/Shaders/Menu/BackgroundBlurComposite.hlsl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package/Shaders/Menu/BackgroundBlurComposite.hlsl b/package/Shaders/Menu/BackgroundBlurComposite.hlsl index e8d12a4207..6b76adc947 100644 --- a/package/Shaders/Menu/BackgroundBlurComposite.hlsl +++ b/package/Shaders/Menu/BackgroundBlurComposite.hlsl @@ -47,7 +47,7 @@ float4 SampleWithSoftening(float2 uv, float2 pixelPos, float2 texelSize) { // Get base random offset for this pixel float2 noise = Hash22(pixelPos); - + // Rotated grid offsets (45 degree rotation for better coverage) // This creates a smooth disc-like sampling pattern static const float2 offsets[4] = { @@ -56,13 +56,13 @@ float4 SampleWithSoftening(float2 uv, float2 pixelPos, float2 texelSize) float2(-0.25f, 0.25f), float2( 0.25f, 0.25f) }; - + // Random rotation angle based on pixel position float angle = noise.x * 6.28318530718f; float s = sin(angle); float c = cos(angle); float2x2 rotation = float2x2(c, -s, s, c); - + // Sample 4 points with rotated jittered offsets and average float4 result = 0; [unroll] @@ -71,7 +71,7 @@ float4 SampleWithSoftening(float2 uv, float2 pixelPos, float2 texelSize) float2 jitter = mul(rotation, offsets[i]) * texelSize; result += InputTexture.Sample(LinearSampler, uv + jitter); } - + return result * 0.25f; } @@ -118,7 +118,7 @@ float4 PS_Main(VS_OUTPUT input) : SV_TARGET } // Sample the blurred texture float4 blurColor = InputTexture.Sample(LinearSampler, input.TexCoord); - + // Apply rounded corner mask to alpha // The blur strength is applied via blend state, so just use the rounded mask here blurColor.a = alpha; From b8279ac77667810fe7507fd9f2efb456ec1ea8da Mon Sep 17 00:00:00 2001 From: Dlizzio <77717521+Dlizzio@users.noreply.github.com> Date: Wed, 4 Feb 2026 03:20:20 -0700 Subject: [PATCH 07/21] fix dither --- package/Shaders/Menu/BackgroundBlurComposite.hlsl | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/package/Shaders/Menu/BackgroundBlurComposite.hlsl b/package/Shaders/Menu/BackgroundBlurComposite.hlsl index e8d12a4207..ef23a3f5ca 100644 --- a/package/Shaders/Menu/BackgroundBlurComposite.hlsl +++ b/package/Shaders/Menu/BackgroundBlurComposite.hlsl @@ -116,8 +116,13 @@ float4 PS_Main(VS_OUTPUT input) : SV_TARGET { discard; } - // Sample the blurred texture - float4 blurColor = InputTexture.Sample(LinearSampler, input.TexCoord); + + // Calculate texel size of the downsampled blur texture + // WindowParams.y/z are screen dimensions, blur texture is 1/8th of that + float2 blurTexelSize = 8.0f / float2(WindowParams.y, WindowParams.z); + + // Sample with soft dithering to hide blocky pixels from the downsampled blur + float4 blurColor = SampleWithSoftening(input.TexCoord, pixelPos, blurTexelSize); // Apply rounded corner mask to alpha // The blur strength is applied via blend state, so just use the rounded mask here From fd752a168567c9f3900f1127441f3563b8ada090 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 10:21:32 +0000 Subject: [PATCH 08/21] =?UTF-8?q?style:=20=F0=9F=8E=A8=20apply=20pre-commi?= =?UTF-8?q?t.ci=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automated formatting by clang-format, prettier, and other hooks. See https://pre-commit.ci for details. --- package/Shaders/Menu/BackgroundBlurComposite.hlsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/Shaders/Menu/BackgroundBlurComposite.hlsl b/package/Shaders/Menu/BackgroundBlurComposite.hlsl index 2088938c00..6b76adc947 100644 --- a/package/Shaders/Menu/BackgroundBlurComposite.hlsl +++ b/package/Shaders/Menu/BackgroundBlurComposite.hlsl @@ -118,7 +118,7 @@ float4 PS_Main(VS_OUTPUT input) : SV_TARGET } // Sample the blurred texture float4 blurColor = InputTexture.Sample(LinearSampler, input.TexCoord); - + // Apply rounded corner mask to alpha // The blur strength is applied via blend state, so just use the rounded mask here blurColor.a = alpha; From c2ee3a285a7164096c724882538f3bbb456c23fa Mon Sep 17 00:00:00 2001 From: Dlizzio <77717521+Dlizzio@users.noreply.github.com> Date: Wed, 4 Feb 2026 03:40:01 -0700 Subject: [PATCH 09/21] fix dither again --- package/Shaders/Menu/BackgroundBlurComposite.hlsl | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/package/Shaders/Menu/BackgroundBlurComposite.hlsl b/package/Shaders/Menu/BackgroundBlurComposite.hlsl index 6b76adc947..091a609395 100644 --- a/package/Shaders/Menu/BackgroundBlurComposite.hlsl +++ b/package/Shaders/Menu/BackgroundBlurComposite.hlsl @@ -116,8 +116,13 @@ float4 PS_Main(VS_OUTPUT input) : SV_TARGET { discard; } - // Sample the blurred texture - float4 blurColor = InputTexture.Sample(LinearSampler, input.TexCoord); + + // Calculate texel size of the downsampled blur texture + // WindowParams.y/z are screen dimensions, blur texture is 1/8th of that + float2 blurTexelSize = 8.0f / float2(WindowParams.y, WindowParams.z); + + // Sample with soft dithering to hide blocky pixels from the downsampled blur + float4 blurColor = SampleWithSoftening(input.TexCoord, pixelPos, blurTexelSize); // Apply rounded corner mask to alpha // The blur strength is applied via blend state, so just use the rounded mask here From 903f327e0850eee4b763786180ec742472bb4d3c Mon Sep 17 00:00:00 2001 From: Dlizzio <77717521+Dlizzio@users.noreply.github.com> Date: Wed, 4 Feb 2026 04:16:57 -0700 Subject: [PATCH 10/21] respect corners of UI over HUD with upscaling --- .../Shaders/Menu/BackgroundBlurComposite.hlsl | 13 ++++ src/Menu/BackgroundBlur.cpp | 73 ++++++++++--------- 2 files changed, 52 insertions(+), 34 deletions(-) diff --git a/package/Shaders/Menu/BackgroundBlurComposite.hlsl b/package/Shaders/Menu/BackgroundBlurComposite.hlsl index 091a609395..7ae250fc87 100644 --- a/package/Shaders/Menu/BackgroundBlurComposite.hlsl +++ b/package/Shaders/Menu/BackgroundBlurComposite.hlsl @@ -130,3 +130,16 @@ float4 PS_Main(VS_OUTPUT input) : SV_TARGET return blurColor; } + +// Clear shader entry point - outputs transparent black inside rounded rect only +// Used to clear UI buffer (HUD) in the exact same shape as the blur +float4 PS_Clear(VS_OUTPUT input) : SV_TARGET +{ + float2 pixelPos = input.TexCoord * float2(WindowParams.y, WindowParams.z); + float sdf = RoundedRectSDF(pixelPos, WindowRect.xy, WindowRect.zw, WindowParams.x); + + // Discard pixels outside rounded rect to preserve HUD in corners + clip(-sdf - 0.001f); + + return float4(0.0f, 0.0f, 0.0f, 0.0f); +} diff --git a/src/Menu/BackgroundBlur.cpp b/src/Menu/BackgroundBlur.cpp index a7405a0420..4b11dc47f0 100644 --- a/src/Menu/BackgroundBlur.cpp +++ b/src/Menu/BackgroundBlur.cpp @@ -35,6 +35,7 @@ namespace BackgroundBlur winrt::com_ptr horizontalPixelShader; winrt::com_ptr verticalPixelShader; winrt::com_ptr compositePixelShader; // For rounded corner compositing + winrt::com_ptr clearPixelShader; // For rounded corner UI buffer clearing winrt::com_ptr constantBuffer; winrt::com_ptr windowConstantBuffer; // For window rect and corner radius winrt::com_ptr samplerState; @@ -136,6 +137,14 @@ namespace BackgroundBlur return false; } + // Compile clear pixel shader from same file (for rounded corner UI buffer clearing) + clearPixelShader.attach(static_cast(Util::CompileShader(L"Data\\Shaders\\Menu\\BackgroundBlurComposite.hlsl", {}, "ps_5_0", "PS_Clear"))); + if (!clearPixelShader) { + logger::error("Failed to compile clear pixel shader"); + initializationFailed = true; + return false; + } + // Create constant buffer D3D11_BUFFER_DESC bufferDesc = {}; bufferDesc.Usage = D3D11_USAGE_DEFAULT; @@ -552,13 +561,9 @@ namespace BackgroundBlur context->RSSetState(scissorRasterizerState.get()); context->RSSetScissorRects(1, &scissorRect); - context->OMSetRenderTargets(1, &targetRTV, nullptr); - float blendFactor[4] = { 1.0f, 1.0f, 1.0f, BLUR_INTENSITY * 0.8f }; - context->OMSetBlendState(blendState.get(), blendFactor, 0xFFFFFFFF); - - // Use composite shader with rounded corner mask if available, otherwise fallback - if (compositePixelShader && windowConstantBuffer) { - // Set up window constants for rounded corner compositing + // Set up window constants for rounded corner shaders (used by both composite and clear) + bool useRoundedCorners = compositePixelShader && clearPixelShader && windowConstantBuffer; + if (useRoundedCorners) { WindowConstants windowConstants = {}; windowConstants.windowRect[0] = menuMin.x; windowConstants.windowRect[1] = menuMin.y; @@ -569,44 +574,43 @@ namespace BackgroundBlur windowConstants.windowParams[2] = static_cast(sourceDesc.Height); windowConstants.windowParams[3] = 0.0f; context->UpdateSubresource(windowConstantBuffer.get(), 0, nullptr, &windowConstants, 0, 0); - - context->PSSetShader(compositePixelShader.get(), nullptr, 0); auto windowConstantBufferPtr = windowConstantBuffer.get(); - context->PSSetConstantBuffers(0, 1, &constantBufferPtr); context->PSSetConstantBuffers(1, 1, &windowConstantBufferPtr); } - // Note: if composite shader not available, vertical shader is still set from blur pass - // Use blurred quarter-res texture, bilinear filtering upscales smoothly + // Draw blur to target + context->OMSetRenderTargets(1, &targetRTV, nullptr); + float blendFactor[4] = { 1.0f, 1.0f, 1.0f, BLUR_INTENSITY * 0.8f }; + context->OMSetBlendState(blendState.get(), blendFactor, 0xFFFFFFFF); + context->PSSetShader(useRoundedCorners ? compositePixelShader.get() : verticalPixelShader.get(), nullptr, 0); auto srv2Ptr = blurSRV2.get(); context->PSSetShaderResources(0, 1, &srv2Ptr); context->Draw(3, 0); context->PSSetShaderResources(0, 1, &nullSRV); - // Clear the UI buffer in the scissor area so the HUD doesn't draw on top of our blur - // The HUD outside the menu area remains visible - if (uiBufferRTV && clearSRV) { - // IMPORTANT: Switch back to horizontal shader for UI buffer clearing - // The composite shader expects WindowBuffer which isn't set up for this pass - context->PSSetShader(horizontalPixelShader.get(), nullptr, 0); - - // Restore scissor to exact window bounds for clearing - D3D11_RECT clearScissorRect; - clearScissorRect.left = static_cast((std::max)(0.0f, menuMin.x)); - clearScissorRect.top = static_cast((std::max)(0.0f, menuMin.y)); - clearScissorRect.right = static_cast((std::min)(static_cast(sourceDesc.Width), menuMax.x)); - clearScissorRect.bottom = static_cast((std::min)(static_cast(sourceDesc.Height), menuMax.y)); - context->RSSetScissorRects(1, &clearScissorRect); - - // Draw transparent black over just the scissor area to clear the HUD there + // Clear UI buffer where blur was drawn (prevents HUD showing through) + if (uiBufferRTV) { context->OMSetRenderTargets(1, &uiBufferRTV, nullptr); - // Use opaque blend to overwrite context->OMSetBlendState(nullptr, nullptr, 0xFFFFFFFF); - // Draw the 1x1 transparent black texture (shader will sample and output transparent) - auto clearSRVPtr = clearSRV.get(); - context->PSSetShaderResources(0, 1, &clearSRVPtr); - context->Draw(3, 0); - context->PSSetShaderResources(0, 1, &nullSRV); + + if (useRoundedCorners) { + // Clear with same rounded shape - window constants already bound + context->PSSetShader(clearPixelShader.get(), nullptr, 0); + context->Draw(3, 0); + } else if (clearSRV) { + // Fallback: rectangular clear + context->PSSetShader(horizontalPixelShader.get(), nullptr, 0); + D3D11_RECT clearScissorRect; + clearScissorRect.left = static_cast((std::max)(0.0f, menuMin.x)); + clearScissorRect.top = static_cast((std::max)(0.0f, menuMin.y)); + clearScissorRect.right = static_cast((std::min)(static_cast(sourceDesc.Width), menuMax.x)); + clearScissorRect.bottom = static_cast((std::min)(static_cast(sourceDesc.Height), menuMax.y)); + context->RSSetScissorRects(1, &clearScissorRect); + auto clearSRVPtr = clearSRV.get(); + context->PSSetShaderResources(0, 1, &clearSRVPtr); + context->Draw(3, 0); + context->PSSetShaderResources(0, 1, &nullSRV); + } } // Restore state @@ -635,6 +639,7 @@ namespace BackgroundBlur horizontalPixelShader = nullptr; verticalPixelShader = nullptr; compositePixelShader = nullptr; + clearPixelShader = nullptr; constantBuffer = nullptr; windowConstantBuffer = nullptr; samplerState = nullptr; From 8fdd16884bf146f307ec3c6de18b81d8c63ebaa7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 11:17:51 +0000 Subject: [PATCH 11/21] =?UTF-8?q?style:=20=F0=9F=8E=A8=20apply=20pre-commi?= =?UTF-8?q?t.ci=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automated formatting by clang-format, prettier, and other hooks. See https://pre-commit.ci for details. --- package/Shaders/Menu/BackgroundBlurComposite.hlsl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package/Shaders/Menu/BackgroundBlurComposite.hlsl b/package/Shaders/Menu/BackgroundBlurComposite.hlsl index 7ae250fc87..351e0273d0 100644 --- a/package/Shaders/Menu/BackgroundBlurComposite.hlsl +++ b/package/Shaders/Menu/BackgroundBlurComposite.hlsl @@ -137,9 +137,9 @@ float4 PS_Clear(VS_OUTPUT input) : SV_TARGET { float2 pixelPos = input.TexCoord * float2(WindowParams.y, WindowParams.z); float sdf = RoundedRectSDF(pixelPos, WindowRect.xy, WindowRect.zw, WindowParams.x); - + // Discard pixels outside rounded rect to preserve HUD in corners clip(-sdf - 0.001f); - + return float4(0.0f, 0.0f, 0.0f, 0.0f); } From bb4b8caaef9611b1d330215a2c0a00745bd3505e Mon Sep 17 00:00:00 2001 From: Dlizzio <77717521+Dlizzio@users.noreply.github.com> Date: Wed, 4 Feb 2026 04:38:21 -0700 Subject: [PATCH 12/21] remove redundancy and consolidate --- .../Shaders/Menu/BackgroundBlurComposite.hlsl | 6 --- src/Menu/BackgroundBlur.cpp | 37 ++++--------------- 2 files changed, 8 insertions(+), 35 deletions(-) diff --git a/package/Shaders/Menu/BackgroundBlurComposite.hlsl b/package/Shaders/Menu/BackgroundBlurComposite.hlsl index 351e0273d0..8722615400 100644 --- a/package/Shaders/Menu/BackgroundBlurComposite.hlsl +++ b/package/Shaders/Menu/BackgroundBlurComposite.hlsl @@ -1,12 +1,6 @@ // Composite Blur Pass Shader with Rounded Rectangle Mask // Part of the BackgroundBlur system - applies blurred texture with rounded corners -cbuffer BlurBuffer : register(b0) -{ - float4 TexelSize; // x = 1/width, y = 1/height, z = blur strength, w = unused - int4 BlurParams; // x = samples, y = unused, z = unused, w = unused -}; - cbuffer WindowBuffer : register(b1) { float4 WindowRect; // x = minX, y = minY, z = maxX, w = maxY (in pixels) diff --git a/src/Menu/BackgroundBlur.cpp b/src/Menu/BackgroundBlur.cpp index 4b11dc47f0..b53d0d6dde 100644 --- a/src/Menu/BackgroundBlur.cpp +++ b/src/Menu/BackgroundBlur.cpp @@ -58,12 +58,7 @@ namespace BackgroundBlur winrt::com_ptr blurSRV1; winrt::com_ptr blurSRV2; - // Legacy composite texture members (kept for cleanup compatibility) - winrt::com_ptr compositeTexture; - winrt::com_ptr compositeRTV; - winrt::com_ptr compositeSRV; - - // 1x1 transparent black texture for clearing with scissor + // 1x1 transparent black texture for fallback rectangular clearing winrt::com_ptr clearTexture; winrt::com_ptr clearSRV; @@ -301,9 +296,6 @@ namespace BackgroundBlur blurRTV2 = nullptr; blurSRV1 = nullptr; blurSRV2 = nullptr; - compositeTexture = nullptr; - compositeRTV = nullptr; - compositeSRV = nullptr; // Create downsampled texture description (no full-res composite needed - all work at 1/8 res) D3D11_TEXTURE2D_DESC texDesc = {}; @@ -469,13 +461,13 @@ namespace BackgroundBlur ID3D11ShaderResourceView* nullSRV = nullptr; - // Set up downsample viewport - all work done at 1/8 resolution for performance - D3D11_VIEWPORT downsampleViewport = {}; - downsampleViewport.Width = static_cast(downsampledWidth); - downsampleViewport.Height = static_cast(downsampledHeight); - downsampleViewport.MinDepth = 0.0f; - downsampleViewport.MaxDepth = 1.0f; - context->RSSetViewports(1, &downsampleViewport); + // Set up viewport for all blur passes (1/8 resolution for performance) + D3D11_VIEWPORT blurViewport = {}; + blurViewport.Width = static_cast(downsampledWidth); + blurViewport.Height = static_cast(downsampledHeight); + blurViewport.MinDepth = 0.0f; + blurViewport.MaxDepth = 1.0f; + context->RSSetViewports(1, &blurViewport); auto downsampleRTVPtr = downsampleRTV.get(); context->OMSetRenderTargets(1, &downsampleRTVPtr, nullptr); @@ -519,15 +511,6 @@ namespace BackgroundBlur constants.blurParams[3] = 0; context->UpdateSubresource(constantBuffer.get(), 0, nullptr, &constants, 0, 0); - - // Set up viewport for blur (quarter resolution) - D3D11_VIEWPORT blurViewport = {}; - blurViewport.Width = static_cast(downsampledWidth); - blurViewport.Height = static_cast(downsampledHeight); - blurViewport.MinDepth = 0.0f; - blurViewport.MaxDepth = 1.0f; - context->RSSetViewports(1, &blurViewport); - context->PSSetConstantBuffers(0, 1, &constantBufferPtr); // First pass: Horizontal blur (on downsampled texture) @@ -651,10 +634,6 @@ namespace BackgroundBlur downsampleRTV = nullptr; downsampleSRV = nullptr; - compositeTexture = nullptr; - compositeRTV = nullptr; - compositeSRV = nullptr; - clearTexture = nullptr; clearSRV = nullptr; From 59868a471299447b51f842e3f20131bc1d81a010 Mon Sep 17 00:00:00 2001 From: Dlizzio <77717521+Dlizzio@users.noreply.github.com> Date: Fri, 6 Feb 2026 00:47:25 -0700 Subject: [PATCH 13/21] performance and consolidation --- .../Shaders/Menu/BackgroundBlurComposite.hlsl | 16 +- src/Features/Upscaling.cpp | 38 +--- src/Features/Upscaling.h | 16 +- src/Features/Upscaling/DX12SwapChain.cpp | 34 ++-- src/Features/Upscaling/DX12SwapChain.h | 26 ++- src/Menu/BackgroundBlur.cpp | 185 +++++------------- src/Menu/BackgroundBlur.h | 32 --- 7 files changed, 90 insertions(+), 257 deletions(-) diff --git a/package/Shaders/Menu/BackgroundBlurComposite.hlsl b/package/Shaders/Menu/BackgroundBlurComposite.hlsl index 8722615400..3e83d19cf1 100644 --- a/package/Shaders/Menu/BackgroundBlurComposite.hlsl +++ b/package/Shaders/Menu/BackgroundBlurComposite.hlsl @@ -53,8 +53,8 @@ float4 SampleWithSoftening(float2 uv, float2 pixelPos, float2 texelSize) // Random rotation angle based on pixel position float angle = noise.x * 6.28318530718f; - float s = sin(angle); - float c = cos(angle); + float s, c; + sincos(angle, s, c); float2x2 rotation = float2x2(c, -s, s, c); // Sample 4 points with rotated jittered offsets and average @@ -111,8 +111,16 @@ float4 PS_Main(VS_OUTPUT input) : SV_TARGET discard; } - // Calculate texel size of the downsampled blur texture - // WindowParams.y/z are screen dimensions, blur texture is 1/8th of that + // Fast path: pixels well inside the rounded rect don't need expensive dithering + // The 4-sample rotated jitter only helps hide blockiness at edges + if (sdf < -1.5f) + { + float4 blurColor = InputTexture.Sample(LinearSampler, input.TexCoord); + blurColor.a = 1.0f; + return blurColor; + } + + // Edge pixels: use soft dithering to hide blocky pixels from the downsampled blur float2 blurTexelSize = 8.0f / float2(WindowParams.y, WindowParams.z); // Sample with soft dithering to hide blocky pixels from the downsampled blur diff --git a/src/Features/Upscaling.cpp b/src/Features/Upscaling.cpp index 988a76d6ec..c7930d30c6 100644 --- a/src/Features/Upscaling.cpp +++ b/src/Features/Upscaling.cpp @@ -1538,44 +1538,12 @@ IDXGISwapChain* Upscaling::GetProxySwapChain() return dx12SwapChain.GetSwapChainProxy(); } -ID3D11Texture2D* Upscaling::GetD3D11BackbufferTexture() const +Upscaling::BlurResources Upscaling::GetBlurResources() const { if (d3d12SwapChainActive) { - return dx12SwapChain.GetBackbufferTexture(); + return dx12SwapChain.GetBlurResources(); } - return nullptr; -} - -ID3D11RenderTargetView* Upscaling::GetD3D11BackbufferRTV() const -{ - if (d3d12SwapChainActive) { - return dx12SwapChain.GetBackbufferRTV(); - } - return nullptr; -} - -ID3D11Texture2D* Upscaling::GetD3D11UIBufferTexture() const -{ - if (d3d12SwapChainActive) { - return dx12SwapChain.GetUIBufferTexture(); - } - return nullptr; -} - -ID3D11ShaderResourceView* Upscaling::GetD3D11UIBufferSRV() const -{ - if (d3d12SwapChainActive) { - return dx12SwapChain.GetUIBufferSRV(); - } - return nullptr; -} - -ID3D11RenderTargetView* Upscaling::GetD3D11UIBufferRTV() const -{ - if (d3d12SwapChainActive) { - return dx12SwapChain.GetUIBufferRTV(); - } - return nullptr; + return {}; } void Upscaling::Upscale() diff --git a/src/Features/Upscaling.h b/src/Features/Upscaling.h index c8147b6f45..a3f86bc126 100644 --- a/src/Features/Upscaling.h +++ b/src/Features/Upscaling.h @@ -232,20 +232,10 @@ struct Upscaling : Feature void CreateProxyInterop(); IDXGISwapChain* GetProxySwapChain(); - // Get the D3D11 backbuffer texture when D3D12 swap chain is active (for background blur) - ID3D11Texture2D* GetD3D11BackbufferTexture() const; + using BlurResources = DX12SwapChain::BlurResources; - // Get the pre-created RTV for the backbuffer when D3D12 swap chain is active - ID3D11RenderTargetView* GetD3D11BackbufferRTV() const; - - // Get the UI buffer texture (HUD during gameplay) when D3D12 swap chain is active - ID3D11Texture2D* GetD3D11UIBufferTexture() const; - - // Get the UI buffer SRV when D3D12 swap chain is active - ID3D11ShaderResourceView* GetD3D11UIBufferSRV() const; - - // Get the UI buffer RTV when D3D12 swap chain is active - ID3D11RenderTargetView* GetD3D11UIBufferRTV() const; + // Get all D3D11 resources needed for background blur when D3D12 swap chain is active + BlurResources GetBlurResources() const; private: struct Main_UpdateJitter diff --git a/src/Features/Upscaling/DX12SwapChain.cpp b/src/Features/Upscaling/DX12SwapChain.cpp index 8a6487ec2b..a9f5a8b184 100644 --- a/src/Features/Upscaling/DX12SwapChain.cpp +++ b/src/Features/Upscaling/DX12SwapChain.cpp @@ -399,29 +399,19 @@ void DX12SwapChain::SetUIBuffer() } } -ID3D11Texture2D* DX12SwapChain::GetBackbufferTexture() const +DX12SwapChain::BlurResources DX12SwapChain::GetBlurResources() const { - return swapChainBufferWrapped ? swapChainBufferWrapped->resource11 : nullptr; -} - -ID3D11RenderTargetView* DX12SwapChain::GetBackbufferRTV() const -{ - return swapChainBufferWrapped ? swapChainBufferWrapped->rtv : nullptr; -} - -ID3D11Texture2D* DX12SwapChain::GetUIBufferTexture() const -{ - return uiBufferWrapped ? uiBufferWrapped->resource11 : nullptr; -} - -ID3D11ShaderResourceView* DX12SwapChain::GetUIBufferSRV() const -{ - return uiBufferWrapped ? uiBufferWrapped->srv : nullptr; -} - -ID3D11RenderTargetView* DX12SwapChain::GetUIBufferRTV() const -{ - return uiBufferWrapped ? uiBufferWrapped->rtv : nullptr; + BlurResources res; + if (swapChainBufferWrapped) { + res.backbufferTex = swapChainBufferWrapped->resource11; + res.backbufferRTV = swapChainBufferWrapped->rtv; + res.backbufferSRV = swapChainBufferWrapped->srv; + } + if (uiBufferWrapped) { + res.uiBufferSRV = uiBufferWrapped->srv; + res.uiBufferRTV = uiBufferWrapped->rtv; + } + return res; } void DX12SwapChain::CreateSharedResources() diff --git a/src/Features/Upscaling/DX12SwapChain.h b/src/Features/Upscaling/DX12SwapChain.h index b0d6831261..69cda5c5ff 100644 --- a/src/Features/Upscaling/DX12SwapChain.h +++ b/src/Features/Upscaling/DX12SwapChain.h @@ -113,20 +113,18 @@ class DX12SwapChain void SetUIBuffer(); - // Get the D3D11 backbuffer texture (game world content) - ID3D11Texture2D* GetBackbufferTexture() const; - - // Get the pre-created RTV for the backbuffer - ID3D11RenderTargetView* GetBackbufferRTV() const; - - // Get the UI buffer texture (HUD content during gameplay) - ID3D11Texture2D* GetUIBufferTexture() const; - - // Get the UI buffer SRV for reading - ID3D11ShaderResourceView* GetUIBufferSRV() const; - - // Get the UI buffer RTV for writing - ID3D11RenderTargetView* GetUIBufferRTV() const; + // Resources needed by BackgroundBlur when D3D12 swap chain is active + struct BlurResources + { + ID3D11Texture2D* backbufferTex = nullptr; + ID3D11RenderTargetView* backbufferRTV = nullptr; + ID3D11ShaderResourceView* backbufferSRV = nullptr; + ID3D11ShaderResourceView* uiBufferSRV = nullptr; + ID3D11RenderTargetView* uiBufferRTV = nullptr; + }; + + // Get all resources needed for background blur in one call + BlurResources GetBlurResources() const; // D3D12 interop resource management void CreateSharedResources(); diff --git a/src/Menu/BackgroundBlur.cpp b/src/Menu/BackgroundBlur.cpp index b53d0d6dde..34a0876d52 100644 --- a/src/Menu/BackgroundBlur.cpp +++ b/src/Menu/BackgroundBlur.cpp @@ -58,9 +58,9 @@ namespace BackgroundBlur winrt::com_ptr blurSRV1; winrt::com_ptr blurSRV2; - // 1x1 transparent black texture for fallback rectangular clearing - winrt::com_ptr clearTexture; - winrt::com_ptr clearSRV; + // Cached SRV for non-upscaling path (avoids per-frame CreateShaderResourceView) + winrt::com_ptr cachedSourceSRV; + ID3D11Texture2D* cachedSourceTexture = nullptr; // raw pointer for cache invalidation check UINT textureWidth = 0; UINT textureHeight = 0; @@ -220,36 +220,6 @@ namespace BackgroundBlur return false; } - // Create 1x1 transparent black texture for clearing with scissor - D3D11_TEXTURE2D_DESC clearTexDesc = {}; - clearTexDesc.Width = 1; - clearTexDesc.Height = 1; - clearTexDesc.MipLevels = 1; - clearTexDesc.ArraySize = 1; - clearTexDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; - clearTexDesc.SampleDesc.Count = 1; - clearTexDesc.Usage = D3D11_USAGE_IMMUTABLE; - clearTexDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE; - - uint32_t clearPixel = 0x00000000; // RGBA = 0,0,0,0 (transparent black) - D3D11_SUBRESOURCE_DATA clearData = {}; - clearData.pSysMem = &clearPixel; - clearData.SysMemPitch = 4; - - hr = device->CreateTexture2D(&clearTexDesc, &clearData, clearTexture.put()); - if (FAILED(hr)) { - logger::error("Failed to create clear texture"); - initializationFailed = true; - return false; - } - - hr = device->CreateShaderResourceView(clearTexture.get(), nullptr, clearSRV.put()); - if (FAILED(hr)) { - logger::error("Failed to create clear SRV"); - initializationFailed = true; - return false; - } - // Create scissor-enabled rasterizer state D3D11_RASTERIZER_DESC rsDesc = {}; rsDesc.FillMode = D3D11_FILL_SOLID; @@ -409,12 +379,12 @@ namespace BackgroundBlur downsampledHeight = dsHeight; } - void PerformBlur(ID3D11Texture2D* sourceTexture, ID3D11RenderTargetView* targetRTV, ImVec2 menuMin, ImVec2 menuMax, float cornerRadius, ID3D11ShaderResourceView* uiBufferSRV = nullptr, ID3D11RenderTargetView* uiBufferRTV = nullptr) + void PerformBlur(ID3D11Texture2D* sourceTexture, ID3D11ShaderResourceView* sourceSRV, ID3D11RenderTargetView* targetRTV, ImVec2 menuMin, ImVec2 menuMax, float cornerRadius, ID3D11ShaderResourceView* uiBufferSRV = nullptr, ID3D11RenderTargetView* uiBufferRTV = nullptr) { std::lock_guard lock(resourceMutex); auto context = globals::d3d::context; - if (!context || !sourceTexture || !targetRTV) { + if (!context || !sourceTexture || !sourceSRV || !targetRTV) { return; } @@ -445,20 +415,6 @@ namespace BackgroundBlur auto constantBufferPtr = constantBuffer.get(); auto samplerStatePtr = samplerState.get(); - // Create SRV for source texture - ID3D11ShaderResourceView* sourceSRV = nullptr; - HRESULT hr = globals::d3d::device->CreateShaderResourceView(sourceTexture, nullptr, &sourceSRV); - if (FAILED(hr)) { - logger::error("Failed to create source SRV for blur"); - if (originalRTV) - originalRTV->Release(); - if (originalDSV) - originalDSV->Release(); - if (originalRS) - originalRS->Release(); - return; - } - ID3D11ShaderResourceView* nullSRV = nullptr; // Set up viewport for all blur passes (1/8 resolution for performance) @@ -504,11 +460,7 @@ namespace BackgroundBlur constants.texelSize[0] = blurRadius / static_cast(downsampledWidth); constants.texelSize[1] = blurRadius / static_cast(downsampledHeight); constants.texelSize[2] = BLUR_INTENSITY; - constants.texelSize[3] = 0.0f; constants.blurParams[0] = sampleCount; - constants.blurParams[1] = 0; - constants.blurParams[2] = 0; - constants.blurParams[3] = 0; context->UpdateSubresource(constantBuffer.get(), 0, nullptr, &constants, 0, 0); context->PSSetConstantBuffers(0, 1, &constantBufferPtr); @@ -576,24 +528,9 @@ namespace BackgroundBlur context->OMSetRenderTargets(1, &uiBufferRTV, nullptr); context->OMSetBlendState(nullptr, nullptr, 0xFFFFFFFF); - if (useRoundedCorners) { - // Clear with same rounded shape - window constants already bound - context->PSSetShader(clearPixelShader.get(), nullptr, 0); - context->Draw(3, 0); - } else if (clearSRV) { - // Fallback: rectangular clear - context->PSSetShader(horizontalPixelShader.get(), nullptr, 0); - D3D11_RECT clearScissorRect; - clearScissorRect.left = static_cast((std::max)(0.0f, menuMin.x)); - clearScissorRect.top = static_cast((std::max)(0.0f, menuMin.y)); - clearScissorRect.right = static_cast((std::min)(static_cast(sourceDesc.Width), menuMax.x)); - clearScissorRect.bottom = static_cast((std::min)(static_cast(sourceDesc.Height), menuMax.y)); - context->RSSetScissorRects(1, &clearScissorRect); - auto clearSRVPtr = clearSRV.get(); - context->PSSetShaderResources(0, 1, &clearSRVPtr); - context->Draw(3, 0); - context->PSSetShaderResources(0, 1, &nullSRV); - } + // Clear with same rounded shape - window constants already bound + context->PSSetShader(clearPixelShader.get(), nullptr, 0); + context->Draw(3, 0); } // Restore state @@ -604,8 +541,6 @@ namespace BackgroundBlur context->RSSetScissorRects(0, nullptr); // Cleanup - if (sourceSRV) - sourceSRV->Release(); if (originalRTV) originalRTV->Release(); if (originalDSV) @@ -634,8 +569,8 @@ namespace BackgroundBlur downsampleRTV = nullptr; downsampleSRV = nullptr; - clearTexture = nullptr; - clearSRV = nullptr; + cachedSourceSRV = nullptr; + cachedSourceTexture = nullptr; blurTexture1 = nullptr; blurTexture2 = nullptr; @@ -658,23 +593,6 @@ namespace BackgroundBlur enabled = enable; } - bool GetEnabled() - { - return enabled; - } - - bool IsEnabled() - { - return enabled && initialized; - } - - void GetTextureDimensions(UINT& outWidth, UINT& outHeight) - { - std::lock_guard lock(resourceMutex); - outWidth = textureWidth; - outHeight = textureHeight; - } - void RenderBackgroundBlur() { if (!enabled) { @@ -695,74 +613,71 @@ namespace BackgroundBlur auto& upscaling = globals::features::upscaling; bool useUpscalingBackbuffer = upscaling.d3d12SwapChainActive; - ID3D11RenderTargetView* currentRTV = nullptr; - ID3D11Texture2D* currentTexture = nullptr; - ID3D11ShaderResourceView* uiBufferSRV = nullptr; // For compositing HUD before blur - ID3D11RenderTargetView* uiBufferRTV = nullptr; // For clearing HUD in blur area - bool ownsRTV = false; // Track if we need to release the RTV + winrt::com_ptr currentTexture; + winrt::com_ptr currentRTV; + ID3D11ShaderResourceView* sourceSRV = nullptr; // Non-owning; lifetime managed elsewhere + ID3D11ShaderResourceView* uiBufferSRV = nullptr; + ID3D11RenderTargetView* uiBufferRTV = nullptr; if (useUpscalingBackbuffer) { - // When D3D12 swap chain is active, get the backbuffer directly from upscaling - // because OMGetRenderTargets returns the UI buffer, not the game world - currentTexture = upscaling.GetD3D11BackbufferTexture(); - currentRTV = upscaling.GetD3D11BackbufferRTV(); - if (!currentTexture || !currentRTV) { + // When D3D12 swap chain is active, get all resources in one call + auto res = upscaling.GetBlurResources(); + if (!res.backbufferTex || !res.backbufferRTV || !res.backbufferSRV) { return; } - // AddRef since we'll Release later in cleanup (to match non-upscaling path) - currentTexture->AddRef(); - currentRTV->AddRef(); - ownsRTV = true; + currentTexture.copy_from(res.backbufferTex); + currentRTV.copy_from(res.backbufferRTV); + sourceSRV = res.backbufferSRV; // During gameplay (not paused), HUD is in separate UI buffer - // We'll composite it onto the backbuffer before blurring auto ui = globals::game::ui; - bool gameNotPaused = ui && !ui->GameIsPaused(); - if (gameNotPaused) { - uiBufferSRV = upscaling.GetD3D11UIBufferSRV(); - uiBufferRTV = upscaling.GetD3D11UIBufferRTV(); + if (ui && !ui->GameIsPaused()) { + uiBufferSRV = res.uiBufferSRV; + uiBufferRTV = res.uiBufferRTV; } } else { // Normal path: get current render target - context->OMGetRenderTargets(1, ¤tRTV, nullptr); - - if (!currentRTV) { + ID3D11RenderTargetView* rawRTV = nullptr; + context->OMGetRenderTargets(1, &rawRTV, nullptr); + if (!rawRTV) { return; } - ownsRTV = true; - - // Get render target texture and its dimensions - ID3D11Resource* currentRT = nullptr; - currentRTV->GetResource(¤tRT); + currentRTV.attach(rawRTV); // Takes ownership of the AddRef from OMGetRenderTargets - HRESULT hr = currentRT->QueryInterface(__uuidof(ID3D11Texture2D), (void**)¤tTexture); + // Get render target texture + winrt::com_ptr currentRT; + currentRTV->GetResource(currentRT.put()); - if (FAILED(hr) || !currentTexture) { - if (currentRT) - currentRT->Release(); - if (currentRTV) - currentRTV->Release(); + winrt::com_ptr tex; + if (FAILED(currentRT->QueryInterface(IID_PPV_ARGS(tex.put()))) || !tex) { return; } - - currentRT->Release(); + currentTexture = tex; + + // Cache SRV for non-upscaling path (avoids CreateShaderResourceView every frame) + if (cachedSourceTexture != currentTexture.get()) { + cachedSourceSRV = nullptr; + HRESULT hr = device->CreateShaderResourceView(currentTexture.get(), nullptr, cachedSourceSRV.put()); + if (FAILED(hr)) { + logger::error("Failed to create cached source SRV for blur"); + return; + } + cachedSourceTexture = currentTexture.get(); + } + sourceSRV = cachedSourceSRV.get(); } D3D11_TEXTURE2D_DESC texDesc; currentTexture->GetDesc(&texDesc); // Create blur textures if needed - UINT currentWidth, currentHeight; - GetTextureDimensions(currentWidth, currentHeight); - if (currentWidth != texDesc.Width || currentHeight != texDesc.Height) { + if (textureWidth != texDesc.Width || textureHeight != texDesc.Height) { CreateBlurTextures(texDesc.Width, texDesc.Height, texDesc.Format); } // Find ImGui windows that need blur ImGuiContext* ctx = ImGui::GetCurrentContext(); if (!ctx || ctx->Windows.Size == 0) { - currentTexture->Release(); - currentRTV->Release(); return; } @@ -805,12 +720,8 @@ namespace BackgroundBlur // Perform blur for this window area with rounded corners // Pass UI buffer SRV/RTV for compositing and clearing during upscaling gameplay - PerformBlur(currentTexture, currentRTV, windowMin, windowMax, cornerRadius, uiBufferSRV, uiBufferRTV); + PerformBlur(currentTexture.get(), sourceSRV, currentRTV.get(), windowMin, windowMax, cornerRadius, uiBufferSRV, uiBufferRTV); } - - // Cleanup - currentTexture->Release(); - currentRTV->Release(); } } // namespace BackgroundBlur diff --git a/src/Menu/BackgroundBlur.h b/src/Menu/BackgroundBlur.h index 24b9c94f56..6ff24bb60f 100644 --- a/src/Menu/BackgroundBlur.h +++ b/src/Menu/BackgroundBlur.h @@ -20,43 +20,11 @@ namespace BackgroundBlur */ void RenderBackgroundBlur(); - /** - * @brief Creates or recreates blur textures with specified dimensions - * @param width Texture width in pixels - * @param height Texture height in pixels - * @param format Texture format - */ - void CreateBlurTextures(UINT width, UINT height, DXGI_FORMAT format); - - /** - * @brief Performs two-pass Gaussian blur on source texture with rounded corner support - * @param sourceTexture Input texture to blur - * @param targetRTV Output render target - * @param menuMin Top-left corner of menu area - * @param menuMax Bottom-right corner of menu area - * @param cornerRadius Corner radius for rounded rectangle mask - */ - void PerformBlur(ID3D11Texture2D* sourceTexture, ID3D11RenderTargetView* targetRTV, ImVec2 menuMin, ImVec2 menuMax, float cornerRadius); - /** * @brief Cleans up all blur resources */ void Cleanup(); void SetEnabled(bool enable); - bool GetEnabled(); - - /** - * @brief Checks if blur is enabled - * @return True if blur intensity > 0 - */ - bool IsEnabled(); - - /** - * @brief Gets current blur texture dimensions - * @param outWidth Output width - * @param outHeight Output height - */ - void GetTextureDimensions(UINT& outWidth, UINT& outHeight); } // namespace BackgroundBlur From 52c00d2c240fb3985f9b1b8e76422e617bb85ee5 Mon Sep 17 00:00:00 2001 From: Dlizzio <77717521+Dlizzio@users.noreply.github.com> Date: Fri, 6 Feb 2026 07:07:26 -0700 Subject: [PATCH 14/21] remove fast path dither --- package/Shaders/Menu/BackgroundBlurComposite.hlsl | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/package/Shaders/Menu/BackgroundBlurComposite.hlsl b/package/Shaders/Menu/BackgroundBlurComposite.hlsl index 3e83d19cf1..ada5343e80 100644 --- a/package/Shaders/Menu/BackgroundBlurComposite.hlsl +++ b/package/Shaders/Menu/BackgroundBlurComposite.hlsl @@ -111,16 +111,6 @@ float4 PS_Main(VS_OUTPUT input) : SV_TARGET discard; } - // Fast path: pixels well inside the rounded rect don't need expensive dithering - // The 4-sample rotated jitter only helps hide blockiness at edges - if (sdf < -1.5f) - { - float4 blurColor = InputTexture.Sample(LinearSampler, input.TexCoord); - blurColor.a = 1.0f; - return blurColor; - } - - // Edge pixels: use soft dithering to hide blocky pixels from the downsampled blur float2 blurTexelSize = 8.0f / float2(WindowParams.y, WindowParams.z); // Sample with soft dithering to hide blocky pixels from the downsampled blur From d84bf39c817c293a663905b237e2441c1ae3fcbf Mon Sep 17 00:00:00 2001 From: Dlizzio <77717521+Dlizzio@users.noreply.github.com> Date: Mon, 9 Feb 2026 06:36:15 -0700 Subject: [PATCH 15/21] enable blur by default & fullscreen blur for weather editor --- .../CommunityShaders/Themes/Default.json | 2 +- .../CommunityShaders/Themes/DragonBlood.json | 2 +- .../CommunityShaders/Themes/DwemerBronze.json | 2 +- .../CommunityShaders/Themes/HighContrast.json | 2 +- .../Plugins/CommunityShaders/Themes/Light.json | 2 +- .../CommunityShaders/Themes/NordicFrost.json | 2 +- src/Menu.h | 2 +- src/Menu/BackgroundBlur.cpp | 17 +++++++++++++++-- 8 files changed, 22 insertions(+), 9 deletions(-) diff --git a/package/SKSE/Plugins/CommunityShaders/Themes/Default.json b/package/SKSE/Plugins/CommunityShaders/Themes/Default.json index 4753d6cfb8..fff23637cd 100644 --- a/package/SKSE/Plugins/CommunityShaders/Themes/Default.json +++ b/package/SKSE/Plugins/CommunityShaders/Themes/Default.json @@ -44,7 +44,7 @@ "ShowFooter": true, "CenterHeader": true, "TooltipHoverDelay": 0.5, - "BackgroundBlurEnabled": false, + "BackgroundBlurEnabled": true, "Palette": { "Background": [0.03, 0.03, 0.03, 0.39216], "Text": [1.0, 1.0, 1.0, 1.0], diff --git a/package/SKSE/Plugins/CommunityShaders/Themes/DragonBlood.json b/package/SKSE/Plugins/CommunityShaders/Themes/DragonBlood.json index 2a45e7c592..9bf77425a9 100644 --- a/package/SKSE/Plugins/CommunityShaders/Themes/DragonBlood.json +++ b/package/SKSE/Plugins/CommunityShaders/Themes/DragonBlood.json @@ -44,7 +44,7 @@ "UseMonochromeIcons": true, "CenterHeader": true, "TooltipHoverDelay": 0.5, - "BackgroundBlurEnabled": false, + "BackgroundBlurEnabled": true, "Palette": { "Background": [0.25, 0.05, 0.05, 0.9], "Text": [1.0, 0.85, 0.85, 1.0], diff --git a/package/SKSE/Plugins/CommunityShaders/Themes/DwemerBronze.json b/package/SKSE/Plugins/CommunityShaders/Themes/DwemerBronze.json index a966e574a5..803e6a4db9 100644 --- a/package/SKSE/Plugins/CommunityShaders/Themes/DwemerBronze.json +++ b/package/SKSE/Plugins/CommunityShaders/Themes/DwemerBronze.json @@ -46,7 +46,7 @@ "ShowFooter": false, "CenterHeader": true, "TooltipHoverDelay": 0.5, - "BackgroundBlurEnabled": false, + "BackgroundBlurEnabled": true, "Palette": { "Background": [0.15, 0.12, 0.08, 0.9], "Text": [0.9, 0.75, 0.5, 1.0], diff --git a/package/SKSE/Plugins/CommunityShaders/Themes/HighContrast.json b/package/SKSE/Plugins/CommunityShaders/Themes/HighContrast.json index 250d5a07e1..28ebe7d7d1 100644 --- a/package/SKSE/Plugins/CommunityShaders/Themes/HighContrast.json +++ b/package/SKSE/Plugins/CommunityShaders/Themes/HighContrast.json @@ -44,7 +44,7 @@ "UseMonochromeIcons": false, "CenterHeader": true, "TooltipHoverDelay": 0.5, - "BackgroundBlurEnabled": false, + "BackgroundBlurEnabled": true, "Palette": { "Background": [0.0, 0.0, 0.0, 0.95], "Text": [1.0, 1.0, 1.0, 1.0], diff --git a/package/SKSE/Plugins/CommunityShaders/Themes/Light.json b/package/SKSE/Plugins/CommunityShaders/Themes/Light.json index e224d215f9..f2b25f35e6 100644 --- a/package/SKSE/Plugins/CommunityShaders/Themes/Light.json +++ b/package/SKSE/Plugins/CommunityShaders/Themes/Light.json @@ -44,7 +44,7 @@ "ShowFooter": false, "CenterHeader": true, "TooltipHoverDelay": 0.5, - "BackgroundBlurEnabled": false, + "BackgroundBlurEnabled": true, "Palette": { "Background": [0.98, 0.98, 0.98, 0.9], "Text": [0.08, 0.08, 0.08, 1.0], diff --git a/package/SKSE/Plugins/CommunityShaders/Themes/NordicFrost.json b/package/SKSE/Plugins/CommunityShaders/Themes/NordicFrost.json index 0437623a9e..739dcc077f 100644 --- a/package/SKSE/Plugins/CommunityShaders/Themes/NordicFrost.json +++ b/package/SKSE/Plugins/CommunityShaders/Themes/NordicFrost.json @@ -44,7 +44,7 @@ "UseMonochromeIcons": true, "CenterHeader": true, "TooltipHoverDelay": 0.5, - "BackgroundBlurEnabled": false, + "BackgroundBlurEnabled": true, "Palette": { "Background": [0.05, 0.15, 0.25, 0.9], "Text": [0.9, 0.95, 1.0, 1.0], diff --git a/src/Menu.h b/src/Menu.h index 08f6dc7fb8..9c8585bfea 100644 --- a/src/Menu.h +++ b/src/Menu.h @@ -255,7 +255,7 @@ class Menu bool ShowFooter = true; // whether to show the footer with game version/GPU info bool CenterHeader = false; // whether to center the header title and logo float TooltipHoverDelay = 0.5f; // tooltip hover delay in seconds - bool BackgroundBlurEnabled = false; // enable background blur effect + bool BackgroundBlurEnabled = true; // enable background blur effect // Scrollbar opacity settings struct ScrollbarOpacitySettings { diff --git a/src/Menu/BackgroundBlur.cpp b/src/Menu/BackgroundBlur.cpp index 34a0876d52..1cb551ad50 100644 --- a/src/Menu/BackgroundBlur.cpp +++ b/src/Menu/BackgroundBlur.cpp @@ -5,7 +5,9 @@ #include "BackgroundBlur.h" #include "../Features/Upscaling.h" #include "../Globals.h" +#include "../Menu.h" #include "../Util.h" +#include "../WeatherEditor/EditorWindow.h" #include #include @@ -681,15 +683,26 @@ namespace BackgroundBlur return; } + // Weather Editor mode: apply a single fullscreen blur instead of per-window blur + auto* editorWindow = EditorWindow::GetSingleton(); + if (editorWindow && editorWindow->open && Menu::GetSingleton()->IsEnabled) { + ImVec2 fullMin = { 0.0f, 0.0f }; + ImVec2 fullMax = { static_cast(texDesc.Width), static_cast(texDesc.Height) }; + PerformBlur(currentTexture.get(), sourceSRV, currentRTV.get(), fullMin, fullMax, 0.0f, uiBufferSRV, uiBufferRTV); + return; + } + // Apply blur behind each visible ImGui window for (int i = 0; i < ctx->Windows.Size; i++) { ImGuiWindow* window = ctx->Windows[i]; - if (!window || window->Hidden || !window->WasActive || window->SkipItems) { + // Don't check Hidden - it causes a 1-frame blur delay when windows reappear + if (!window || !window->WasActive || window->SkipItems) { continue; } // Skip child windows - only blur root windows to cover headers and footers - if (window->ParentWindow != nullptr) { + // Exception: docked windows are visually independent even though ParentWindow is set + if (window->ParentWindow != nullptr && !window->DockIsActive) { continue; } From e1f039842b5a0cffd160536a2f7827ece6fc3b29 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 9 Feb 2026 13:36:42 +0000 Subject: [PATCH 16/21] =?UTF-8?q?style:=20=F0=9F=8E=A8=20apply=20pre-commi?= =?UTF-8?q?t.ci=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automated formatting by clang-format, prettier, and other hooks. See https://pre-commit.ci for details. --- src/Menu.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Menu.h b/src/Menu.h index 9c8585bfea..83e014e66b 100644 --- a/src/Menu.h +++ b/src/Menu.h @@ -248,13 +248,13 @@ class Menu return roles; }(); - bool UseSimplePalette = false; // DEPRECATED: No longer affects behavior. UI now shows both Simple and Advanced controls. - bool ShowActionIcons = true; // whether to show action buttons as icons - bool UseMonochromeIcons = false; // whether to use monochrome (white) action icons with text color tinting - bool UseMonochromeLogo = false; // whether to use monochrome CS logo - bool ShowFooter = true; // whether to show the footer with game version/GPU info - bool CenterHeader = false; // whether to center the header title and logo - float TooltipHoverDelay = 0.5f; // tooltip hover delay in seconds + bool UseSimplePalette = false; // DEPRECATED: No longer affects behavior. UI now shows both Simple and Advanced controls. + bool ShowActionIcons = true; // whether to show action buttons as icons + bool UseMonochromeIcons = false; // whether to use monochrome (white) action icons with text color tinting + bool UseMonochromeLogo = false; // whether to use monochrome CS logo + bool ShowFooter = true; // whether to show the footer with game version/GPU info + bool CenterHeader = false; // whether to center the header title and logo + float TooltipHoverDelay = 0.5f; // tooltip hover delay in seconds bool BackgroundBlurEnabled = true; // enable background blur effect // Scrollbar opacity settings struct ScrollbarOpacitySettings From b97b9b44a7b9347736f913d8ae347265425a519d Mon Sep 17 00:00:00 2001 From: Dlizzio <77717521+Dlizzio@users.noreply.github.com> Date: Mon, 9 Feb 2026 07:11:49 -0700 Subject: [PATCH 17/21] remove weather editor specific changes --- src/Menu/BackgroundBlur.cpp | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/Menu/BackgroundBlur.cpp b/src/Menu/BackgroundBlur.cpp index 1cb551ad50..d798db5ea6 100644 --- a/src/Menu/BackgroundBlur.cpp +++ b/src/Menu/BackgroundBlur.cpp @@ -5,9 +5,7 @@ #include "BackgroundBlur.h" #include "../Features/Upscaling.h" #include "../Globals.h" -#include "../Menu.h" #include "../Util.h" -#include "../WeatherEditor/EditorWindow.h" #include #include @@ -683,15 +681,6 @@ namespace BackgroundBlur return; } - // Weather Editor mode: apply a single fullscreen blur instead of per-window blur - auto* editorWindow = EditorWindow::GetSingleton(); - if (editorWindow && editorWindow->open && Menu::GetSingleton()->IsEnabled) { - ImVec2 fullMin = { 0.0f, 0.0f }; - ImVec2 fullMax = { static_cast(texDesc.Width), static_cast(texDesc.Height) }; - PerformBlur(currentTexture.get(), sourceSRV, currentRTV.get(), fullMin, fullMax, 0.0f, uiBufferSRV, uiBufferRTV); - return; - } - // Apply blur behind each visible ImGui window for (int i = 0; i < ctx->Windows.Size; i++) { ImGuiWindow* window = ctx->Windows[i]; From 2122d1da9b647dc5ea4d65121adef87f2eb25ab2 Mon Sep 17 00:00:00 2001 From: Dlizzio <77717521+Dlizzio@users.noreply.github.com> Date: Mon, 9 Feb 2026 08:27:05 -0700 Subject: [PATCH 18/21] disable blur on loading screens without upscaling --- src/Menu/BackgroundBlur.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Menu/BackgroundBlur.cpp b/src/Menu/BackgroundBlur.cpp index d798db5ea6..34c94c2d2c 100644 --- a/src/Menu/BackgroundBlur.cpp +++ b/src/Menu/BackgroundBlur.cpp @@ -5,6 +5,7 @@ #include "BackgroundBlur.h" #include "../Features/Upscaling.h" #include "../Globals.h" +#include "../ShaderCache.h" #include "../Util.h" #include @@ -613,6 +614,16 @@ namespace BackgroundBlur auto& upscaling = globals::features::upscaling; bool useUpscalingBackbuffer = upscaling.d3d12SwapChainActive; + // Back buffer is black on main/loading menu during shader compilation without upscaling + if (!useUpscalingBackbuffer) { + auto ui = globals::game::ui; + bool isMainOrLoading = ui && (ui->IsMenuOpen(RE::MainMenu::MENU_NAME) || ui->IsMenuOpen(RE::LoadingMenu::MENU_NAME)); + auto shaderCache = globals::shaderCache; + if (isMainOrLoading && shaderCache && shaderCache->IsCompiling()) { + return; + } + } + winrt::com_ptr currentTexture; winrt::com_ptr currentRTV; ID3D11ShaderResourceView* sourceSRV = nullptr; // Non-owning; lifetime managed elsewhere From 6016e0198bb6a5c23f23a85906f91e55675b3ce3 Mon Sep 17 00:00:00 2001 From: Dlizzio <77717521+Dlizzio@users.noreply.github.com> Date: Mon, 9 Feb 2026 08:42:46 -0700 Subject: [PATCH 19/21] ignore framegen with load screen fix --- src/Menu/BackgroundBlur.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Menu/BackgroundBlur.cpp b/src/Menu/BackgroundBlur.cpp index 34c94c2d2c..364ce39785 100644 --- a/src/Menu/BackgroundBlur.cpp +++ b/src/Menu/BackgroundBlur.cpp @@ -615,7 +615,7 @@ namespace BackgroundBlur bool useUpscalingBackbuffer = upscaling.d3d12SwapChainActive; // Back buffer is black on main/loading menu during shader compilation without upscaling - if (!useUpscalingBackbuffer) { + if (!useUpscalingBackbuffer && !(upscaling.loaded && upscaling.IsUpscalingActive())) { auto ui = globals::game::ui; bool isMainOrLoading = ui && (ui->IsMenuOpen(RE::MainMenu::MENU_NAME) || ui->IsMenuOpen(RE::LoadingMenu::MENU_NAME)); auto shaderCache = globals::shaderCache; From a9a2c5a121af1853130b53ab7d9e6a3e2e1efb32 Mon Sep 17 00:00:00 2001 From: Dlizzio <77717521+Dlizzio@users.noreply.github.com> Date: Mon, 9 Feb 2026 11:30:25 -0700 Subject: [PATCH 20/21] magic numbers and constants --- .../Shaders/Menu/BackgroundBlurComposite.hlsl | 17 +++++--- src/Menu/BackgroundBlur.cpp | 41 +++++++++++++------ 2 files changed, 39 insertions(+), 19 deletions(-) diff --git a/package/Shaders/Menu/BackgroundBlurComposite.hlsl b/package/Shaders/Menu/BackgroundBlurComposite.hlsl index ada5343e80..e6e1424665 100644 --- a/package/Shaders/Menu/BackgroundBlurComposite.hlsl +++ b/package/Shaders/Menu/BackgroundBlurComposite.hlsl @@ -10,6 +10,11 @@ cbuffer WindowBuffer : register(b1) SamplerState LinearSampler : register(s0); Texture2D InputTexture : register(t0); +static const float TWO_PI = 6.28318530718f; +static const int NUM_JITTER_SAMPLES = 4; +static const float DOWNSAMPLE_FACTOR = 8.0f; +static const float CLIP_EPSILON = 0.001f; + struct VS_OUTPUT { float4 Position : SV_POSITION; @@ -44,7 +49,7 @@ float4 SampleWithSoftening(float2 uv, float2 pixelPos, float2 texelSize) // Rotated grid offsets (45 degree rotation for better coverage) // This creates a smooth disc-like sampling pattern - static const float2 offsets[4] = { + static const float2 offsets[NUM_JITTER_SAMPLES] = { float2(-0.25f, -0.25f), float2( 0.25f, -0.25f), float2(-0.25f, 0.25f), @@ -52,7 +57,7 @@ float4 SampleWithSoftening(float2 uv, float2 pixelPos, float2 texelSize) }; // Random rotation angle based on pixel position - float angle = noise.x * 6.28318530718f; + float angle = noise.x * TWO_PI; float s, c; sincos(angle, s, c); float2x2 rotation = float2x2(c, -s, s, c); @@ -60,13 +65,13 @@ float4 SampleWithSoftening(float2 uv, float2 pixelPos, float2 texelSize) // Sample 4 points with rotated jittered offsets and average float4 result = 0; [unroll] - for (int i = 0; i < 4; i++) + for (int i = 0; i < NUM_JITTER_SAMPLES; i++) { float2 jitter = mul(rotation, offsets[i]) * texelSize; result += InputTexture.Sample(LinearSampler, uv + jitter); } - return result * 0.25f; + return result / (float)NUM_JITTER_SAMPLES; } // Compute signed distance to a rounded rectangle @@ -111,7 +116,7 @@ float4 PS_Main(VS_OUTPUT input) : SV_TARGET discard; } - float2 blurTexelSize = 8.0f / float2(WindowParams.y, WindowParams.z); + float2 blurTexelSize = DOWNSAMPLE_FACTOR / float2(WindowParams.y, WindowParams.z); // Sample with soft dithering to hide blocky pixels from the downsampled blur float4 blurColor = SampleWithSoftening(input.TexCoord, pixelPos, blurTexelSize); @@ -131,7 +136,7 @@ float4 PS_Clear(VS_OUTPUT input) : SV_TARGET float sdf = RoundedRectSDF(pixelPos, WindowRect.xy, WindowRect.zw, WindowParams.x); // Discard pixels outside rounded rect to preserve HUD in corners - clip(-sdf - 0.001f); + clip(-sdf - CLIP_EPSILON); return float4(0.0f, 0.0f, 0.0f, 0.0f); } diff --git a/src/Menu/BackgroundBlur.cpp b/src/Menu/BackgroundBlur.cpp index 364ce39785..7c7a250881 100644 --- a/src/Menu/BackgroundBlur.cpp +++ b/src/Menu/BackgroundBlur.cpp @@ -23,6 +23,21 @@ constexpr float BLUR_INTENSITY = 0.03f; // Downsampling factor (8 = eighth resolution for performance) constexpr UINT DOWNSAMPLE_FACTOR = 8; +// Multiplier applied to BLUR_INTENSITY to derive the blur kernel radius +constexpr float BLUR_RADIUS_SCALE = 10.0f; + +// Number of samples per blur pass (Gaussian kernel taps) +constexpr int BLUR_SAMPLE_COUNT = 9; + +// Extra pixels added around scissor rect for anti-aliased rounded corner edges +constexpr float SCISSOR_AA_PADDING = 2.0f; + +// Scale factor applied to BLUR_INTENSITY for the final composite blend alpha +constexpr float BLEND_ALPHA_SCALE = 0.8f; + +// Vertex count for a fullscreen triangle draw call +constexpr UINT FULLSCREEN_TRIANGLE_VERTICES = 3; + namespace BackgroundBlur { // Module-local state @@ -440,7 +455,7 @@ namespace BackgroundBlur context->UpdateSubresource(constantBuffer.get(), 0, nullptr, &downsampleConstants, 0, 0); context->PSSetConstantBuffers(0, 1, &constantBufferPtr); context->PSSetShaderResources(0, 1, &sourceSRV); - context->Draw(3, 0); + context->Draw(FULLSCREEN_TRIANGLE_VERTICES, 0); context->PSSetShaderResources(0, 1, &nullSRV); // Step 2: Blend UI buffer at downsampled resolution (pre-multiplied alpha) @@ -448,14 +463,14 @@ namespace BackgroundBlur if (uiBufferSRV && compositeBlendState) { context->OMSetBlendState(compositeBlendState.get(), nullptr, 0xFFFFFFFF); context->PSSetShaderResources(0, 1, &uiBufferSRV); - context->Draw(3, 0); + context->Draw(FULLSCREEN_TRIANGLE_VERTICES, 0); context->PSSetShaderResources(0, 1, &nullSRV); context->OMSetBlendState(nullptr, nullptr, 0xFFFFFFFF); } // Calculate blur parameters at eighth resolution - float blurRadius = BLUR_INTENSITY * 10.0f; - int sampleCount = 9; + float blurRadius = BLUR_INTENSITY * BLUR_RADIUS_SCALE; + int sampleCount = BLUR_SAMPLE_COUNT; BlurConstants constants = {}; constants.texelSize[0] = blurRadius / static_cast(downsampledWidth); @@ -472,7 +487,7 @@ namespace BackgroundBlur context->OMSetRenderTargets(1, &rtv1Ptr, nullptr); context->PSSetShader(horizontalPixelShader.get(), nullptr, 0); context->PSSetShaderResources(0, 1, &downsampleSRVPtr); - context->Draw(3, 0); + context->Draw(FULLSCREEN_TRIANGLE_VERTICES, 0); // Second pass: Vertical blur (on downsampled texture) context->PSSetShaderResources(0, 1, &nullSRV); @@ -481,7 +496,7 @@ namespace BackgroundBlur context->OMSetRenderTargets(1, &rtv2Ptr, nullptr); context->PSSetShader(verticalPixelShader.get(), nullptr, 0); context->PSSetShaderResources(0, 1, &srv1Ptr); - context->Draw(3, 0); + context->Draw(FULLSCREEN_TRIANGLE_VERTICES, 0); context->PSSetShaderResources(0, 1, &nullSRV); // Final composition: upscale from quarter-res with rounded corner mask @@ -489,10 +504,10 @@ namespace BackgroundBlur // Expand scissor rect slightly for anti-aliased rounded corner edges D3D11_RECT scissorRect; - scissorRect.left = static_cast((std::max)(0.0f, menuMin.x - 2.0f)); - scissorRect.top = static_cast((std::max)(0.0f, menuMin.y - 2.0f)); - scissorRect.right = static_cast((std::min)(static_cast(sourceDesc.Width), menuMax.x + 2.0f)); - scissorRect.bottom = static_cast((std::min)(static_cast(sourceDesc.Height), menuMax.y + 2.0f)); + scissorRect.left = static_cast((std::max)(0.0f, menuMin.x - SCISSOR_AA_PADDING)); + scissorRect.top = static_cast((std::max)(0.0f, menuMin.y - SCISSOR_AA_PADDING)); + scissorRect.right = static_cast((std::min)(static_cast(sourceDesc.Width), menuMax.x + SCISSOR_AA_PADDING)); + scissorRect.bottom = static_cast((std::min)(static_cast(sourceDesc.Height), menuMax.y + SCISSOR_AA_PADDING)); context->RSSetState(scissorRasterizerState.get()); context->RSSetScissorRects(1, &scissorRect); @@ -516,12 +531,12 @@ namespace BackgroundBlur // Draw blur to target context->OMSetRenderTargets(1, &targetRTV, nullptr); - float blendFactor[4] = { 1.0f, 1.0f, 1.0f, BLUR_INTENSITY * 0.8f }; + float blendFactor[4] = { 1.0f, 1.0f, 1.0f, BLUR_INTENSITY * BLEND_ALPHA_SCALE }; context->OMSetBlendState(blendState.get(), blendFactor, 0xFFFFFFFF); context->PSSetShader(useRoundedCorners ? compositePixelShader.get() : verticalPixelShader.get(), nullptr, 0); auto srv2Ptr = blurSRV2.get(); context->PSSetShaderResources(0, 1, &srv2Ptr); - context->Draw(3, 0); + context->Draw(FULLSCREEN_TRIANGLE_VERTICES, 0); context->PSSetShaderResources(0, 1, &nullSRV); // Clear UI buffer where blur was drawn (prevents HUD showing through) @@ -531,7 +546,7 @@ namespace BackgroundBlur // Clear with same rounded shape - window constants already bound context->PSSetShader(clearPixelShader.get(), nullptr, 0); - context->Draw(3, 0); + context->Draw(FULLSCREEN_TRIANGLE_VERTICES, 0); } // Restore state From a9a44ea0ff52148caeca804b1a31c072e0bc3277 Mon Sep 17 00:00:00 2001 From: Dlizzio <77717521+Dlizzio@users.noreply.github.com> Date: Mon, 9 Feb 2026 11:44:18 -0700 Subject: [PATCH 21/21] lambda and helper functions --- src/Menu/BackgroundBlur.cpp | 298 +++++++++++------------------------- 1 file changed, 86 insertions(+), 212 deletions(-) diff --git a/src/Menu/BackgroundBlur.cpp b/src/Menu/BackgroundBlur.cpp index 7c7a250881..612faef843 100644 --- a/src/Menu/BackgroundBlur.cpp +++ b/src/Menu/BackgroundBlur.cpp @@ -100,6 +100,46 @@ namespace BackgroundBlur float windowParams[4]; // x = cornerRadius, y = screenWidth, z = screenHeight, w = unused }; + // Release all blur texture resources; caller must hold resourceMutex + void ReleaseBlurTextures() + { + downsampleTexture = nullptr; + downsampleRTV = nullptr; + downsampleSRV = nullptr; + blurTexture1 = nullptr; + blurTexture2 = nullptr; + blurRTV1 = nullptr; + blurRTV2 = nullptr; + blurSRV1 = nullptr; + blurSRV2 = nullptr; + textureWidth = 0; + textureHeight = 0; + downsampledWidth = 0; + downsampledHeight = 0; + } + + // Create a Texture2D with associated RTV and SRV + bool CreateTextureSet(ID3D11Device* device, const D3D11_TEXTURE2D_DESC& desc, + winrt::com_ptr& tex, + winrt::com_ptr& rtv, + winrt::com_ptr& srv, + const char* name) + { + if (FAILED(device->CreateTexture2D(&desc, nullptr, tex.put()))) { + logger::error("Failed to create {} texture", name); + return false; + } + if (FAILED(device->CreateRenderTargetView(tex.get(), nullptr, rtv.put()))) { + logger::error("Failed to create {} RTV", name); + return false; + } + if (FAILED(device->CreateShaderResourceView(tex.get(), nullptr, srv.put()))) { + logger::error("Failed to create {} SRV", name); + return false; + } + return true; + } + } // anonymous namespace bool Initialize() @@ -116,71 +156,45 @@ namespace BackgroundBlur return false; } - // Compile vertex shader from horizontal blur file (both share same vertex shader) - vertexShader.attach(static_cast(Util::CompileShader(L"Data\\Shaders\\Menu\\BackgroundBlurHorizontal.hlsl", {}, "vs_5_0", "VS_Main"))); - if (!vertexShader) { - logger::error("Failed to compile blur vertex shader"); - initializationFailed = true; - return false; - } - - // Compile horizontal pixel shader - horizontalPixelShader.attach(static_cast(Util::CompileShader(L"Data\\Shaders\\Menu\\BackgroundBlurHorizontal.hlsl", {}, "ps_5_0", "PS_Main"))); - if (!horizontalPixelShader) { - logger::error("Failed to compile horizontal blur pixel shader"); - initializationFailed = true; - return false; - } - - // Compile vertical pixel shader - verticalPixelShader.attach(static_cast(Util::CompileShader(L"Data\\Shaders\\Menu\\BackgroundBlurVertical.hlsl", {}, "ps_5_0", "PS_Main"))); - if (!verticalPixelShader) { - logger::error("Failed to compile vertical blur pixel shader"); - initializationFailed = true; - return false; - } + // Compile shaders + auto compileShader = [&](auto& shader, const wchar_t* path, const char* target, const char* entry, const char* name) -> bool { + shader.attach(static_cast(Util::CompileShader(path, {}, target, entry))); + if (!shader) { + logger::error("Failed to compile {}", name); + initializationFailed = true; + return false; + } + return true; + }; - // Compile composite pixel shader (for rounded corner compositing) - compositePixelShader.attach(static_cast(Util::CompileShader(L"Data\\Shaders\\Menu\\BackgroundBlurComposite.hlsl", {}, "ps_5_0", "PS_Main"))); - if (!compositePixelShader) { - logger::error("Failed to compile composite blur pixel shader"); - initializationFailed = true; + if (!compileShader(vertexShader, L"Data\\Shaders\\Menu\\BackgroundBlurHorizontal.hlsl", "vs_5_0", "VS_Main", "blur vertex shader") || + !compileShader(horizontalPixelShader, L"Data\\Shaders\\Menu\\BackgroundBlurHorizontal.hlsl", "ps_5_0", "PS_Main", "horizontal blur pixel shader") || + !compileShader(verticalPixelShader, L"Data\\Shaders\\Menu\\BackgroundBlurVertical.hlsl", "ps_5_0", "PS_Main", "vertical blur pixel shader") || + !compileShader(compositePixelShader, L"Data\\Shaders\\Menu\\BackgroundBlurComposite.hlsl", "ps_5_0", "PS_Main", "composite blur pixel shader") || + !compileShader(clearPixelShader, L"Data\\Shaders\\Menu\\BackgroundBlurComposite.hlsl", "ps_5_0", "PS_Clear", "clear pixel shader")) return false; - } - // Compile clear pixel shader from same file (for rounded corner UI buffer clearing) - clearPixelShader.attach(static_cast(Util::CompileShader(L"Data\\Shaders\\Menu\\BackgroundBlurComposite.hlsl", {}, "ps_5_0", "PS_Clear"))); - if (!clearPixelShader) { - logger::error("Failed to compile clear pixel shader"); - initializationFailed = true; - return false; - } + auto checkCreate = [&](HRESULT hr, const char* name) -> bool { + if (FAILED(hr)) { + logger::error("Failed to create {}", name); + initializationFailed = true; + return false; + } + return true; + }; - // Create constant buffer - D3D11_BUFFER_DESC bufferDesc = {}; - bufferDesc.Usage = D3D11_USAGE_DEFAULT; - bufferDesc.ByteWidth = sizeof(BlurConstants); - bufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; + // Create constant buffers + D3D11_BUFFER_DESC cbDesc = {}; + cbDesc.Usage = D3D11_USAGE_DEFAULT; + cbDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; - HRESULT hr = device->CreateBuffer(&bufferDesc, nullptr, constantBuffer.put()); - if (FAILED(hr)) { - logger::error("Failed to create blur constant buffer"); - initializationFailed = true; + cbDesc.ByteWidth = sizeof(BlurConstants); + if (!checkCreate(device->CreateBuffer(&cbDesc, nullptr, constantBuffer.put()), "blur constant buffer")) return false; - } - // Create window constant buffer (for rounded corner parameters) - D3D11_BUFFER_DESC windowBufferDesc = {}; - windowBufferDesc.Usage = D3D11_USAGE_DEFAULT; - windowBufferDesc.ByteWidth = sizeof(WindowConstants); - windowBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; - - hr = device->CreateBuffer(&windowBufferDesc, nullptr, windowConstantBuffer.put()); - if (FAILED(hr)) { - logger::error("Failed to create window constant buffer"); - initializationFailed = true; + cbDesc.ByteWidth = sizeof(WindowConstants); + if (!checkCreate(device->CreateBuffer(&cbDesc, nullptr, windowConstantBuffer.put()), "window constant buffer")) return false; - } // Create sampler state D3D11_SAMPLER_DESC samplerDesc = {}; @@ -191,15 +205,10 @@ namespace BackgroundBlur samplerDesc.MaxAnisotropy = 1; samplerDesc.MinLOD = 0; samplerDesc.MaxLOD = D3D11_FLOAT32_MAX; - - hr = device->CreateSamplerState(&samplerDesc, samplerState.put()); - if (FAILED(hr)) { - logger::error("Failed to create blur sampler state"); - initializationFailed = true; + if (!checkCreate(device->CreateSamplerState(&samplerDesc, samplerState.put()), "blur sampler state")) return false; - } - // Create blend state + // Create blend states D3D11_BLEND_DESC blendDesc = {}; blendDesc.RenderTarget[0].BlendEnable = TRUE; blendDesc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA; @@ -209,32 +218,14 @@ namespace BackgroundBlur blendDesc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO; blendDesc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; blendDesc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; - - hr = device->CreateBlendState(&blendDesc, blendState.put()); - if (FAILED(hr)) { - logger::error("Failed to create blur blend state"); - initializationFailed = true; + if (!checkCreate(device->CreateBlendState(&blendDesc, blendState.put()), "blur blend state")) return false; - } - // Create blend state for compositing UI over game world (pre-multiplied alpha blend) - // UI buffer uses pre-multiplied alpha, so SrcBlend=ONE and DestBlend=INV_SRC_ALPHA - D3D11_BLEND_DESC compositeBlendDesc = {}; - compositeBlendDesc.RenderTarget[0].BlendEnable = TRUE; - compositeBlendDesc.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE; - compositeBlendDesc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; - compositeBlendDesc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; - compositeBlendDesc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; - compositeBlendDesc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA; - compositeBlendDesc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; - compositeBlendDesc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; - - hr = device->CreateBlendState(&compositeBlendDesc, compositeBlendState.put()); - if (FAILED(hr)) { - logger::error("Failed to create composite blend state"); - initializationFailed = true; + // Composite: pre-multiplied alpha (SrcBlend=ONE, DestBlendAlpha=INV_SRC_ALPHA) + blendDesc.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE; + blendDesc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA; + if (!checkCreate(device->CreateBlendState(&blendDesc, compositeBlendState.put()), "composite blend state")) return false; - } // Create scissor-enabled rasterizer state D3D11_RASTERIZER_DESC rsDesc = {}; @@ -243,13 +234,8 @@ namespace BackgroundBlur rsDesc.FrontCounterClockwise = FALSE; rsDesc.DepthClipEnable = TRUE; rsDesc.ScissorEnable = TRUE; - - hr = device->CreateRasterizerState(&rsDesc, scissorRasterizerState.put()); - if (FAILED(hr)) { - logger::error("Failed to create scissor rasterizer state"); - initializationFailed = true; + if (!checkCreate(device->CreateRasterizerState(&rsDesc, scissorRasterizerState.put()), "scissor rasterizer state")) return false; - } initialized = true; return true; @@ -268,22 +254,11 @@ namespace BackgroundBlur return; } - // Calculate downsampled dimensions + ReleaseBlurTextures(); + UINT dsWidth = (std::max)(1u, width / DOWNSAMPLE_FACTOR); UINT dsHeight = (std::max)(1u, height / DOWNSAMPLE_FACTOR); - // Release old textures - downsampleTexture = nullptr; - downsampleRTV = nullptr; - downsampleSRV = nullptr; - blurTexture1 = nullptr; - blurTexture2 = nullptr; - blurRTV1 = nullptr; - blurRTV2 = nullptr; - blurSRV1 = nullptr; - blurSRV2 = nullptr; - - // Create downsampled texture description (no full-res composite needed - all work at 1/8 res) D3D11_TEXTURE2D_DESC texDesc = {}; texDesc.Width = dsWidth; texDesc.Height = dsHeight; @@ -294,98 +269,10 @@ namespace BackgroundBlur texDesc.Usage = D3D11_USAGE_DEFAULT; texDesc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE; - // Create downsample texture - HRESULT hr = device->CreateTexture2D(&texDesc, nullptr, downsampleTexture.put()); - if (FAILED(hr)) { - logger::error("Failed to create downsample texture"); - return; - } - - hr = device->CreateRenderTargetView(downsampleTexture.get(), nullptr, downsampleRTV.put()); - if (FAILED(hr)) { - logger::error("Failed to create downsample RTV"); - downsampleTexture = nullptr; - return; - } - - hr = device->CreateShaderResourceView(downsampleTexture.get(), nullptr, downsampleSRV.put()); - if (FAILED(hr)) { - logger::error("Failed to create downsample SRV"); - downsampleTexture = nullptr; - downsampleRTV = nullptr; - return; - } - - // Create first blur texture (at downsampled resolution) - hr = device->CreateTexture2D(&texDesc, nullptr, blurTexture1.put()); - if (FAILED(hr)) { - logger::error("Failed to create blur texture 1"); - downsampleTexture = nullptr; - downsampleRTV = nullptr; - downsampleSRV = nullptr; - return; - } - - // Create second blur texture - hr = device->CreateTexture2D(&texDesc, nullptr, blurTexture2.put()); - if (FAILED(hr)) { - logger::error("Failed to create blur texture 2"); - blurTexture1 = nullptr; - downsampleTexture = nullptr; - downsampleRTV = nullptr; - downsampleSRV = nullptr; - return; - } - - // Create render target views - hr = device->CreateRenderTargetView(blurTexture1.get(), nullptr, blurRTV1.put()); - if (FAILED(hr)) { - logger::error("Failed to create blur RTV 1"); - blurTexture1 = nullptr; - blurTexture2 = nullptr; - downsampleTexture = nullptr; - downsampleRTV = nullptr; - downsampleSRV = nullptr; - return; - } - - hr = device->CreateRenderTargetView(blurTexture2.get(), nullptr, blurRTV2.put()); - if (FAILED(hr)) { - logger::error("Failed to create blur RTV 2"); - blurTexture1 = nullptr; - blurTexture2 = nullptr; - blurRTV1 = nullptr; - downsampleTexture = nullptr; - downsampleRTV = nullptr; - downsampleSRV = nullptr; - return; - } - - // Create shader resource views - hr = device->CreateShaderResourceView(blurTexture1.get(), nullptr, blurSRV1.put()); - if (FAILED(hr)) { - logger::error("Failed to create blur SRV 1"); - blurTexture1 = nullptr; - blurTexture2 = nullptr; - blurRTV1 = nullptr; - blurRTV2 = nullptr; - downsampleTexture = nullptr; - downsampleRTV = nullptr; - downsampleSRV = nullptr; - return; - } - - hr = device->CreateShaderResourceView(blurTexture2.get(), nullptr, blurSRV2.put()); - if (FAILED(hr)) { - logger::error("Failed to create blur SRV 2"); - blurTexture1 = nullptr; - blurTexture2 = nullptr; - blurRTV1 = nullptr; - blurRTV2 = nullptr; - blurSRV1 = nullptr; - downsampleTexture = nullptr; - downsampleRTV = nullptr; - downsampleSRV = nullptr; + if (!CreateTextureSet(device, texDesc, downsampleTexture, downsampleRTV, downsampleSRV, "downsample") || + !CreateTextureSet(device, texDesc, blurTexture1, blurRTV1, blurSRV1, "blur 1") || + !CreateTextureSet(device, texDesc, blurTexture2, blurRTV2, blurSRV2, "blur 2")) { + ReleaseBlurTextures(); return; } @@ -581,24 +468,11 @@ namespace BackgroundBlur compositeBlendState = nullptr; scissorRasterizerState = nullptr; - downsampleTexture = nullptr; - downsampleRTV = nullptr; - downsampleSRV = nullptr; + ReleaseBlurTextures(); cachedSourceSRV = nullptr; cachedSourceTexture = nullptr; - blurTexture1 = nullptr; - blurTexture2 = nullptr; - blurRTV1 = nullptr; - blurRTV2 = nullptr; - blurSRV1 = nullptr; - blurSRV2 = nullptr; - - textureWidth = 0; - textureHeight = 0; - downsampledWidth = 0; - downsampledHeight = 0; enabled = false; initialized = false; initializationFailed = false;