From 56a878480ce286eec5397585111fd5e87d060f7f Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Thu, 6 Nov 2025 14:34:20 +1000 Subject: [PATCH 1/8] blur --- src/Menu/BackgroundBlur.cpp | 726 ++++++++++++++++++++++++++++++++++++ src/Menu/BackgroundBlur.h | 70 ++++ 2 files changed, 796 insertions(+) create mode 100644 src/Menu/BackgroundBlur.cpp create mode 100644 src/Menu/BackgroundBlur.h diff --git a/src/Menu/BackgroundBlur.cpp b/src/Menu/BackgroundBlur.cpp new file mode 100644 index 000000000..7103bd40e --- /dev/null +++ b/src/Menu/BackgroundBlur.cpp @@ -0,0 +1,726 @@ +// Based on Unrimp rendering engine's separable blur implementation +// Credits: Christian Ofenberg and the Unrimp project (https://github.com/cofenberg/unrimp) +// License: MIT License + +#include "BackgroundBlur.h" +#include "../Globals.h" + +#include +#include +#include +#include +#include + +#include "RE/Skyrim.h" + +using namespace std::literals; + +/** + * THEME MANAGER IMPLEMENTATION NOTES + * =================================== + * + * BLUR SHADER PARAMETERS: + * ----------------------- + * The background blur system uses constant buffers to pass parameters to HLSL shaders: + * + * BlurBuffer (cbuffer b0): + * - TexelSize.xy: Inverse texture dimensions (1/width, 1/height) for UV calculations + * - TexelSize.z: Blur strength multiplier (0.0-1.0 from BackgroundBlur theme setting) + * - BlurParams.x: Number of blur samples (default: 13, must be odd for centered kernel) + * + * The blur uses a separable Gaussian kernel split into two passes: + * 1. Horizontal pass: Samples along X-axis, outputs to intermediate texture + * 2. Vertical pass: Samples along Y-axis from intermediate, outputs final result + */ + + // Blur System Constants + // --------------------- + // Text contrast boost per unit blur: Compensates for reduced clarity behind blurred backgrounds + constexpr float BLUR_TEXT_CONTRAST_FACTOR = 0.05f; // 5% brightness boost at max blur + + // Gaussian blur sigma: Controls blur kernel spread (standard deviation) + constexpr float GAUSSIAN_BLUR_SIGMA = 2.0f; + +namespace BackgroundBlur +{ + // Module-local state + namespace + { + std::mutex resourceMutex; + float currentIntensity = 0.0f; + bool enabled = false; + + // DirectX resources (RAII managed) + winrt::com_ptr vertexShader; + winrt::com_ptr horizontalPixelShader; + winrt::com_ptr verticalPixelShader; + winrt::com_ptr constantBuffer; + winrt::com_ptr samplerState; + winrt::com_ptr blendState; + + // Intermediate blur textures + winrt::com_ptr blurTexture1; + winrt::com_ptr blurTexture2; + winrt::com_ptr blurRTV1; + winrt::com_ptr blurRTV2; + winrt::com_ptr blurSRV1; + winrt::com_ptr blurSRV2; + + UINT textureWidth = 0; + UINT textureHeight = 0; + + bool initialized = false; + bool initializationFailed = false; + + // Blur shader constants structure + struct BlurConstants + { + float texelSize[4]; // x = 1/width, y = 1/height, z = blur strength, w = unused + int blurParams[4]; // x = samples, y = unused, z = unused, w = unused + }; + + // Inline HLSL shader code - Horizontal Pass + const char* GetHorizontalBlurShader() + { + return R"( +cbuffer BlurBuffer : register(b0) +{ + float4 TexelSize; + int4 BlurParams; +}; + +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; +} + +float GaussianWeight(float offset) +{ + const float SIGMA = 2.0f; + const float v = 2.0f * SIGMA * SIGMA; + return exp(-(offset * offset) / v) / (3.14159265f * v); +} + +float4 PS_Main(VS_OUTPUT input) : SV_TARGET +{ + float4 result = float4(0.0f, 0.0f, 0.0f, 0.0f); + float totalWeight = 0.0f; + + const int samples = min(BlurParams.x, 15); + const int halfSamples = samples / 2; + + for (int i = -halfSamples; i <= halfSamples; ++i) + { + float2 sampleCoord = input.TexCoord + float2(i * TexelSize.x, 0.0f); + float weight = GaussianWeight(float(i)); + + if (sampleCoord.x >= 0.0f && sampleCoord.x <= 1.0f) + { + result += InputTexture.Sample(LinearSampler, sampleCoord) * weight; + totalWeight += weight; + } + } + + if (totalWeight > 0.0f) + result /= totalWeight; + + return result; +} +)"; + } + + const char* GetVerticalBlurShader() + { + return R"( +cbuffer BlurBuffer : register(b0) +{ + float4 TexelSize; + int4 BlurParams; +}; + +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; +} + +float GaussianWeight(float offset) +{ + const float SIGMA = 2.0f; + const float v = 2.0f * SIGMA * SIGMA; + return exp(-(offset * offset) / v) / (3.14159265f * v); +} + +float4 PS_Main(VS_OUTPUT input) : SV_TARGET +{ + float4 result = float4(0.0f, 0.0f, 0.0f, 0.0f); + float totalWeight = 0.0f; + + const int samples = min(BlurParams.x, 15); + const int halfSamples = samples / 2; + + for (int i = -halfSamples; i <= halfSamples; ++i) + { + float2 sampleCoord = input.TexCoord + float2(0.0f, i * TexelSize.y); + float weight = GaussianWeight(float(i)); + + if (sampleCoord.y >= 0.0f && sampleCoord.y <= 1.0f) + { + result += InputTexture.Sample(LinearSampler, sampleCoord) * weight; + totalWeight += weight; + } + } + + if (totalWeight > 0.0f) + result /= totalWeight; + + return result; +} +)"; + } + + bool CompileShader(const char* shaderSource, const char* entryPoint, const char* target, + ID3DBlob** outBlob) + { + ID3DBlob* errorBlob = nullptr; + HRESULT hr = D3DCompile( + shaderSource, + strlen(shaderSource), + "InlineBlurShader", + nullptr, + nullptr, + entryPoint, + target, + D3DCOMPILE_OPTIMIZATION_LEVEL3, + 0, + outBlob, + &errorBlob); + + if (FAILED(hr)) { + if (errorBlob) { + logger::error("Blur shader compilation failed: {}", static_cast(errorBlob->GetBufferPointer())); + errorBlob->Release(); + } + return false; + } + + return true; + } + + } // anonymous namespace + + bool Initialize() + { + std::lock_guard lock(resourceMutex); + + if (initialized || initializationFailed) { + return initialized; + } + + auto device = globals::d3d::device; + if (!device) { + initializationFailed = true; + return false; + } + + // Compile vertex shader + ID3DBlob* vsBlob = nullptr; + if (!CompileShader(GetHorizontalBlurShader(), "VS_Main", "vs_5_0", &vsBlob)) { + initializationFailed = true; + return false; + } + + HRESULT hr = device->CreateVertexShader(vsBlob->GetBufferPointer(), vsBlob->GetBufferSize(), nullptr, vertexShader.put()); + vsBlob->Release(); + + if (FAILED(hr)) { + logger::error("Failed to create blur vertex shader"); + initializationFailed = true; + return false; + } + + // Compile horizontal pixel shader + ID3DBlob* hpsBlob = nullptr; + if (!CompileShader(GetHorizontalBlurShader(), "PS_Main", "ps_5_0", &hpsBlob)) { + initializationFailed = true; + return false; + } + + hr = device->CreatePixelShader(hpsBlob->GetBufferPointer(), hpsBlob->GetBufferSize(), nullptr, horizontalPixelShader.put()); + hpsBlob->Release(); + + if (FAILED(hr)) { + logger::error("Failed to create horizontal blur pixel shader"); + initializationFailed = true; + return false; + } + + // Compile vertical pixel shader + ID3DBlob* vpsBlob = nullptr; + if (!CompileShader(GetVerticalBlurShader(), "PS_Main", "ps_5_0", &vpsBlob)) { + initializationFailed = true; + return false; + } + + hr = device->CreatePixelShader(vpsBlob->GetBufferPointer(), vpsBlob->GetBufferSize(), nullptr, verticalPixelShader.put()); + vpsBlob->Release(); + + if (FAILED(hr)) { + logger::error("Failed to create vertical blur pixel shader"); + initializationFailed = true; + return false; + } + + // Create constant buffer + D3D11_BUFFER_DESC bufferDesc = {}; + bufferDesc.Usage = D3D11_USAGE_DEFAULT; + bufferDesc.ByteWidth = sizeof(BlurConstants); + bufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; + + hr = device->CreateBuffer(&bufferDesc, nullptr, constantBuffer.put()); + if (FAILED(hr)) { + logger::error("Failed to create blur constant buffer"); + initializationFailed = true; + return false; + } + + // Create sampler state + D3D11_SAMPLER_DESC samplerDesc = {}; + samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; + samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP; + samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP; + samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP; + 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; + return false; + } + + // Create blend state + D3D11_BLEND_DESC blendDesc = {}; + blendDesc.RenderTarget[0].BlendEnable = TRUE; + blendDesc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA; + blendDesc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; + blendDesc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; + blendDesc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; + 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; + return false; + } + + initialized = true; + return true; + } + + void CreateBlurTextures(UINT width, UINT height, DXGI_FORMAT format) + { + std::lock_guard lock(resourceMutex); + + if (width == textureWidth && height == textureHeight && blurTexture1 && blurTexture2) { + return; + } + + auto device = globals::d3d::device; + if (!device) { + return; + } + + // Release old textures + blurTexture1 = nullptr; + blurTexture2 = nullptr; + blurRTV1 = nullptr; + blurRTV2 = nullptr; + blurSRV1 = nullptr; + blurSRV2 = nullptr; + + // Create texture description + D3D11_TEXTURE2D_DESC texDesc = {}; + texDesc.Width = width; + texDesc.Height = height; + texDesc.MipLevels = 1; + texDesc.ArraySize = 1; + texDesc.Format = format; + texDesc.SampleDesc.Count = 1; + texDesc.Usage = D3D11_USAGE_DEFAULT; + texDesc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE; + + // Create first blur texture + HRESULT hr = device->CreateTexture2D(&texDesc, nullptr, blurTexture1.put()); + if (FAILED(hr)) { + logger::error("Failed to create blur texture 1"); + 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; + 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; + 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; + 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; + 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; + return; + } + + textureWidth = width; + textureHeight = height; + } + + void PerformBlur(ID3D11Texture2D* sourceTexture, ID3D11RenderTargetView* targetRTV, ImVec2 menuMin, ImVec2 menuMax) + { + std::lock_guard lock(resourceMutex); + + auto context = globals::d3d::context; + if (!context || !sourceTexture || !targetRTV) { + return; + } + + if (!vertexShader || !horizontalPixelShader || !verticalPixelShader) { + return; + } + + if (!blurTexture1 || !blurTexture2) { + return; + } + + // Get source texture description + 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; + context->OMGetRenderTargets(1, &originalRTV, &originalDSV); + + D3D11_VIEWPORT originalViewport; + UINT numViewports = 1; + context->RSGetViewports(&numViewports, &originalViewport); + + ID3D11RasterizerState* originalRS = nullptr; + context->RSGetState(&originalRS); + + // Calculate blur parameters + float blurRadius = currentIntensity * 10.0f; + int sampleCount = (std::max)(5, (std::min)(15, static_cast(9 + currentIntensity * 6))); + + BlurConstants constants = {}; + constants.texelSize[0] = blurRadius / static_cast(textureWidth); + constants.texelSize[1] = blurRadius / static_cast(textureHeight); + constants.texelSize[2] = currentIntensity; + 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); + + // Set up viewport for blur + D3D11_VIEWPORT blurViewport = {}; + blurViewport.Width = static_cast(textureWidth); + blurViewport.Height = static_cast(textureHeight); + blurViewport.MinDepth = 0.0f; + blurViewport.MaxDepth = 1.0f; + context->RSSetViewports(1, &blurViewport); + + // Set common shader resources + auto constantBufferPtr = constantBuffer.get(); + auto samplerStatePtr = samplerState.get(); + context->VSSetShader(vertexShader.get(), nullptr, 0); + context->PSSetConstantBuffers(0, 1, &constantBufferPtr); + context->PSSetSamplers(0, 1, &samplerStatePtr); + + // First pass: Horizontal blur + auto rtv1Ptr = blurRTV1.get(); + context->OMSetRenderTargets(1, &rtv1Ptr, nullptr); + context->PSSetShader(horizontalPixelShader.get(), nullptr, 0); + context->PSSetShaderResources(0, 1, &sourceSRV); + context->Draw(3, 0); + + // Second pass: Vertical blur + ID3D11ShaderResourceView* nullSRV = nullptr; + auto rtv2Ptr = blurRTV2.get(); + auto srv1Ptr = blurSRV1.get(); + context->OMSetRenderTargets(1, &rtv2Ptr, nullptr); + context->PSSetShader(verticalPixelShader.get(), nullptr, 0); + context->PSSetShaderResources(0, 1, &nullSRV); + context->PSSetShaderResources(0, 1, &srv1Ptr); + context->Draw(3, 0); + + // Final composition with scissor test + context->RSSetViewports(1, &originalViewport); + + 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)); + + D3D11_RASTERIZER_DESC rsDesc = {}; + if (originalRS) { + originalRS->GetDesc(&rsDesc); + } else { + rsDesc.FillMode = D3D11_FILL_SOLID; + rsDesc.CullMode = D3D11_CULL_BACK; + rsDesc.FrontCounterClockwise = FALSE; + rsDesc.DepthClipEnable = TRUE; + } + rsDesc.ScissorEnable = TRUE; + + ID3D11RasterizerState* scissorRS = nullptr; + globals::d3d::device->CreateRasterizerState(&rsDesc, &scissorRS); + if (scissorRS) { + context->RSSetState(scissorRS); + context->RSSetScissorRects(1, &scissorRect); + } + + context->OMSetRenderTargets(1, &targetRTV, nullptr); + float blendFactor[4] = { 1.0f, 1.0f, 1.0f, currentIntensity * 0.8f }; + context->OMSetBlendState(blendState.get(), blendFactor, 0xFFFFFFFF); + + auto srv2Ptr = blurSRV2.get(); + context->PSSetShaderResources(0, 1, &nullSRV); + context->PSSetShaderResources(0, 1, &srv2Ptr); + context->Draw(3, 0); + + // Restore state + context->OMSetRenderTargets(1, &originalRTV, originalDSV); + context->OMSetBlendState(nullptr, nullptr, 0xFFFFFFFF); + context->PSSetShaderResources(0, 1, &nullSRV); + context->RSSetState(originalRS); + context->RSSetScissorRects(0, nullptr); + + // Cleanup + if (sourceSRV) sourceSRV->Release(); + if (originalRTV) originalRTV->Release(); + if (originalDSV) originalDSV->Release(); + if (originalRS) originalRS->Release(); + if (scissorRS) scissorRS->Release(); + } + + void Cleanup() + { + std::lock_guard lock(resourceMutex); + + vertexShader = nullptr; + horizontalPixelShader = nullptr; + verticalPixelShader = nullptr; + constantBuffer = nullptr; + samplerState = nullptr; + blendState = nullptr; + + blurTexture1 = nullptr; + blurTexture2 = nullptr; + blurRTV1 = nullptr; + blurRTV2 = nullptr; + blurSRV1 = nullptr; + blurSRV2 = nullptr; + + textureWidth = 0; + textureHeight = 0; + enabled = false; + currentIntensity = 0.0f; + initialized = false; + initializationFailed = false; + } + + void SetIntensity(float intensity) + { + currentIntensity = std::clamp(intensity, 0.0f, 1.0f); + enabled = (currentIntensity > 0.0f); + } + + float GetIntensity() + { + return currentIntensity; + } + + bool IsEnabled() + { + return enabled && initialized; + } + + void GetTextureDimensions(UINT& outWidth, UINT& outHeight) + { + std::lock_guard lock(resourceMutex); + outWidth = textureWidth; + outHeight = textureHeight; + } + + void RenderBackgroundBlur() + { + if (!enabled || currentIntensity <= 0.0f) { + return; + } + + if (!initialized || initializationFailed) { + return; + } + + auto device = globals::d3d::device; + auto context = globals::d3d::context; + if (!device || !context) { + return; + } + + // Get current render target + ID3D11RenderTargetView* currentRTV = nullptr; + context->OMGetRenderTargets(1, ¤tRTV, nullptr); + + if (!currentRTV) { + return; + } + + // Get render target texture and its dimensions + ID3D11Resource* currentRT = nullptr; + currentRTV->GetResource(¤tRT); + + ID3D11Texture2D* currentTexture = nullptr; + HRESULT hr = currentRT->QueryInterface(__uuidof(ID3D11Texture2D), (void**)¤tTexture); + + if (FAILED(hr) || !currentTexture) { + if (currentRT) currentRT->Release(); + if (currentRTV) currentRTV->Release(); + return; + } + + 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) { + 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(); + currentRT->Release(); + currentRTV->Release(); + 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) { + continue; + } + + // Skip child windows - only blur root windows to cover headers and footers + if (window->ParentWindow != nullptr) { + continue; + } + + // Skip Performance Overlay window (no blur) + if (window->Name && std::string_view(window->Name) == "Performance Overlay") { + continue; + } + + // Skip if window has no background (fully transparent) + if (window->Flags & ImGuiWindowFlags_NoBackground) { + continue; + } + + // Get window outer bounds (includes title bar, borders, etc.) + // Use window's inner rect which includes all content drawn inside the window + // including custom headers and footers, not just OuterRectClipped + ImRect windowRect = window->Rect(); + ImVec2 windowMin = windowRect.Min; + ImVec2 windowMax = windowRect.Max; + + // Perform blur for this window area + PerformBlur(currentTexture, currentRTV, windowMin, windowMax); + } + + // Cleanup + currentTexture->Release(); + currentRT->Release(); + currentRTV->Release(); + } + +} // namespace BackgroundBlur diff --git a/src/Menu/BackgroundBlur.h b/src/Menu/BackgroundBlur.h new file mode 100644 index 000000000..9183b72e3 --- /dev/null +++ b/src/Menu/BackgroundBlur.h @@ -0,0 +1,70 @@ +#pragma once + +#include +#include +#include + +struct ImVec2; + +namespace BackgroundBlur +{ + /** + * @brief Initializes blur shaders and GPU resources + * @return True if initialization succeeded + */ + bool Initialize(); + + /** + * @brief Renders background blur behind all visible ImGui windows + * This is the main entry point - call after ImGui::Render() but before ImGui_ImplDX11_RenderDrawData() + */ + 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 + * @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) + */ + void PerformBlur(ID3D11Texture2D* sourceTexture, ID3D11RenderTargetView* targetRTV, ImVec2 menuMin, ImVec2 menuMax); + + /** + * @brief Cleans up all blur resources + */ + void Cleanup(); + + /** + * @brief Sets the blur intensity for next render + * @param intensity Blur strength (0.0 = disabled, 1.0 = maximum) + */ + void SetIntensity(float intensity); + + /** + * @brief Gets current blur intensity + * @return Current blur intensity value + */ + float GetIntensity(); + + /** + * @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 51223f0c24d577b547ebf332e44481776c24c8b5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 6 Nov 2025 04:35:19 +0000 Subject: [PATCH 2/8] =?UTF-8?q?style:=20=F0=9F=8E=A8=20apply=20pre-commit.?= =?UTF-8?q?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 | 55 +++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/src/Menu/BackgroundBlur.cpp b/src/Menu/BackgroundBlur.cpp index 7103bd40e..67e9fb2a6 100644 --- a/src/Menu/BackgroundBlur.cpp +++ b/src/Menu/BackgroundBlur.cpp @@ -5,9 +5,9 @@ #include "BackgroundBlur.h" #include "../Globals.h" -#include #include #include +#include #include #include @@ -33,13 +33,13 @@ using namespace std::literals; * 2. Vertical pass: Samples along Y-axis from intermediate, outputs final result */ - // Blur System Constants - // --------------------- - // Text contrast boost per unit blur: Compensates for reduced clarity behind blurred backgrounds - constexpr float BLUR_TEXT_CONTRAST_FACTOR = 0.05f; // 5% brightness boost at max blur +// Blur System Constants +// --------------------- +// Text contrast boost per unit blur: Compensates for reduced clarity behind blurred backgrounds +constexpr float BLUR_TEXT_CONTRAST_FACTOR = 0.05f; // 5% brightness boost at max blur - // Gaussian blur sigma: Controls blur kernel spread (standard deviation) - constexpr float GAUSSIAN_BLUR_SIGMA = 2.0f; +// Gaussian blur sigma: Controls blur kernel spread (standard deviation) +constexpr float GAUSSIAN_BLUR_SIGMA = 2.0f; namespace BackgroundBlur { @@ -118,25 +118,25 @@ float4 PS_Main(VS_OUTPUT input) : SV_TARGET { float4 result = float4(0.0f, 0.0f, 0.0f, 0.0f); float totalWeight = 0.0f; - + const int samples = min(BlurParams.x, 15); const int halfSamples = samples / 2; - + for (int i = -halfSamples; i <= halfSamples; ++i) { float2 sampleCoord = input.TexCoord + float2(i * TexelSize.x, 0.0f); float weight = GaussianWeight(float(i)); - + if (sampleCoord.x >= 0.0f && sampleCoord.x <= 1.0f) { result += InputTexture.Sample(LinearSampler, sampleCoord) * weight; totalWeight += weight; } } - + if (totalWeight > 0.0f) result /= totalWeight; - + return result; } )"; @@ -180,25 +180,25 @@ float4 PS_Main(VS_OUTPUT input) : SV_TARGET { float4 result = float4(0.0f, 0.0f, 0.0f, 0.0f); float totalWeight = 0.0f; - + const int samples = min(BlurParams.x, 15); const int halfSamples = samples / 2; - + for (int i = -halfSamples; i <= halfSamples; ++i) { float2 sampleCoord = input.TexCoord + float2(0.0f, i * TexelSize.y); float weight = GaussianWeight(float(i)); - + if (sampleCoord.y >= 0.0f && sampleCoord.y <= 1.0f) { result += InputTexture.Sample(LinearSampler, sampleCoord) * weight; totalWeight += weight; } } - + if (totalWeight > 0.0f) result /= totalWeight; - + return result; } )"; @@ -572,11 +572,16 @@ float4 PS_Main(VS_OUTPUT input) : SV_TARGET context->RSSetScissorRects(0, nullptr); // Cleanup - if (sourceSRV) sourceSRV->Release(); - if (originalRTV) originalRTV->Release(); - if (originalDSV) originalDSV->Release(); - if (originalRS) originalRS->Release(); - if (scissorRS) scissorRS->Release(); + if (sourceSRV) + sourceSRV->Release(); + if (originalRTV) + originalRTV->Release(); + if (originalDSV) + originalDSV->Release(); + if (originalRS) + originalRS->Release(); + if (scissorRS) + scissorRS->Release(); } void Cleanup() @@ -660,8 +665,10 @@ float4 PS_Main(VS_OUTPUT input) : SV_TARGET HRESULT hr = currentRT->QueryInterface(__uuidof(ID3D11Texture2D), (void**)¤tTexture); if (FAILED(hr) || !currentTexture) { - if (currentRT) currentRT->Release(); - if (currentRTV) currentRTV->Release(); + if (currentRT) + currentRT->Release(); + if (currentRTV) + currentRTV->Release(); return; } From 36c10f4b82a86238fb58ca682c3a930a2a1406af Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Tue, 25 Nov 2025 19:07:15 +1000 Subject: [PATCH 3/8] refactor --- .../Menu/BackgroundBlurHorizontal.hlsl | 59 +++++ .../Shaders/Menu/BackgroundBlurVertical.hlsl | 59 +++++ src/Menu/BackgroundBlur.cpp | 239 +++--------------- 3 files changed, 149 insertions(+), 208 deletions(-) create mode 100644 package/Shaders/Menu/BackgroundBlurHorizontal.hlsl create mode 100644 package/Shaders/Menu/BackgroundBlurVertical.hlsl diff --git a/package/Shaders/Menu/BackgroundBlurHorizontal.hlsl b/package/Shaders/Menu/BackgroundBlurHorizontal.hlsl new file mode 100644 index 000000000..2df1e0000 --- /dev/null +++ b/package/Shaders/Menu/BackgroundBlurHorizontal.hlsl @@ -0,0 +1,59 @@ +// Horizontal Blur Pass Shader +// Part of the BackgroundBlur system - separable Gaussian blur implementation + +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 +}; + +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; +} + +float GaussianWeight(float offset) +{ + const float SIGMA = 2.0f; + const float v = 2.0f * SIGMA * SIGMA; + return exp(-(offset * offset) / v) / (3.14159265f * v); +} + +float4 PS_Main(VS_OUTPUT input) : SV_TARGET +{ + float4 result = float4(0.0f, 0.0f, 0.0f, 0.0f); + float totalWeight = 0.0f; + + const int samples = min(BlurParams.x, 15); + const int halfSamples = samples / 2; + + for (int i = -halfSamples; i <= halfSamples; ++i) + { + float2 sampleCoord = input.TexCoord + float2(i * TexelSize.x, 0.0f); + float weight = GaussianWeight(float(i)); + + if (sampleCoord.x >= 0.0f && sampleCoord.x <= 1.0f) + { + result += InputTexture.Sample(LinearSampler, sampleCoord) * weight; + totalWeight += weight; + } + } + + if (totalWeight > 0.0f) + result /= totalWeight; + + return result; +} diff --git a/package/Shaders/Menu/BackgroundBlurVertical.hlsl b/package/Shaders/Menu/BackgroundBlurVertical.hlsl new file mode 100644 index 000000000..27e5d713b --- /dev/null +++ b/package/Shaders/Menu/BackgroundBlurVertical.hlsl @@ -0,0 +1,59 @@ +// Vertical Blur Pass Shader +// Part of the BackgroundBlur system - separable Gaussian blur implementation + +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 +}; + +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; +} + +float GaussianWeight(float offset) +{ + const float SIGMA = 2.0f; + const float v = 2.0f * SIGMA * SIGMA; + return exp(-(offset * offset) / v) / (3.14159265f * v); +} + +float4 PS_Main(VS_OUTPUT input) : SV_TARGET +{ + float4 result = float4(0.0f, 0.0f, 0.0f, 0.0f); + float totalWeight = 0.0f; + + const int samples = min(BlurParams.x, 15); + const int halfSamples = samples / 2; + + for (int i = -halfSamples; i <= halfSamples; ++i) + { + float2 sampleCoord = input.TexCoord + float2(0.0f, i * TexelSize.y); + float weight = GaussianWeight(float(i)); + + if (sampleCoord.y >= 0.0f && sampleCoord.y <= 1.0f) + { + result += InputTexture.Sample(LinearSampler, sampleCoord) * weight; + totalWeight += weight; + } + } + + if (totalWeight > 0.0f) + result /= totalWeight; + + return result; +} diff --git a/src/Menu/BackgroundBlur.cpp b/src/Menu/BackgroundBlur.cpp index 67e9fb2a6..c19b4c78b 100644 --- a/src/Menu/BackgroundBlur.cpp +++ b/src/Menu/BackgroundBlur.cpp @@ -4,10 +4,10 @@ #include "BackgroundBlur.h" #include "../Globals.h" +#include "../Util.h" #include #include -#include #include #include @@ -57,6 +57,7 @@ namespace BackgroundBlur winrt::com_ptr constantBuffer; winrt::com_ptr samplerState; winrt::com_ptr blendState; + winrt::com_ptr scissorRasterizerState; // Intermediate blur textures winrt::com_ptr blurTexture1; @@ -79,159 +80,6 @@ namespace BackgroundBlur int blurParams[4]; // x = samples, y = unused, z = unused, w = unused }; - // Inline HLSL shader code - Horizontal Pass - const char* GetHorizontalBlurShader() - { - return R"( -cbuffer BlurBuffer : register(b0) -{ - float4 TexelSize; - int4 BlurParams; -}; - -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; -} - -float GaussianWeight(float offset) -{ - const float SIGMA = 2.0f; - const float v = 2.0f * SIGMA * SIGMA; - return exp(-(offset * offset) / v) / (3.14159265f * v); -} - -float4 PS_Main(VS_OUTPUT input) : SV_TARGET -{ - float4 result = float4(0.0f, 0.0f, 0.0f, 0.0f); - float totalWeight = 0.0f; - - const int samples = min(BlurParams.x, 15); - const int halfSamples = samples / 2; - - for (int i = -halfSamples; i <= halfSamples; ++i) - { - float2 sampleCoord = input.TexCoord + float2(i * TexelSize.x, 0.0f); - float weight = GaussianWeight(float(i)); - - if (sampleCoord.x >= 0.0f && sampleCoord.x <= 1.0f) - { - result += InputTexture.Sample(LinearSampler, sampleCoord) * weight; - totalWeight += weight; - } - } - - if (totalWeight > 0.0f) - result /= totalWeight; - - return result; -} -)"; - } - - const char* GetVerticalBlurShader() - { - return R"( -cbuffer BlurBuffer : register(b0) -{ - float4 TexelSize; - int4 BlurParams; -}; - -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; -} - -float GaussianWeight(float offset) -{ - const float SIGMA = 2.0f; - const float v = 2.0f * SIGMA * SIGMA; - return exp(-(offset * offset) / v) / (3.14159265f * v); -} - -float4 PS_Main(VS_OUTPUT input) : SV_TARGET -{ - float4 result = float4(0.0f, 0.0f, 0.0f, 0.0f); - float totalWeight = 0.0f; - - const int samples = min(BlurParams.x, 15); - const int halfSamples = samples / 2; - - for (int i = -halfSamples; i <= halfSamples; ++i) - { - float2 sampleCoord = input.TexCoord + float2(0.0f, i * TexelSize.y); - float weight = GaussianWeight(float(i)); - - if (sampleCoord.y >= 0.0f && sampleCoord.y <= 1.0f) - { - result += InputTexture.Sample(LinearSampler, sampleCoord) * weight; - totalWeight += weight; - } - } - - if (totalWeight > 0.0f) - result /= totalWeight; - - return result; -} -)"; - } - - bool CompileShader(const char* shaderSource, const char* entryPoint, const char* target, - ID3DBlob** outBlob) - { - ID3DBlob* errorBlob = nullptr; - HRESULT hr = D3DCompile( - shaderSource, - strlen(shaderSource), - "InlineBlurShader", - nullptr, - nullptr, - entryPoint, - target, - D3DCOMPILE_OPTIMIZATION_LEVEL3, - 0, - outBlob, - &errorBlob); - - if (FAILED(hr)) { - if (errorBlob) { - logger::error("Blur shader compilation failed: {}", static_cast(errorBlob->GetBufferPointer())); - errorBlob->Release(); - } - return false; - } - - return true; - } - } // anonymous namespace bool Initialize() @@ -248,50 +96,26 @@ float4 PS_Main(VS_OUTPUT input) : SV_TARGET return false; } - // Compile vertex shader - ID3DBlob* vsBlob = nullptr; - if (!CompileShader(GetHorizontalBlurShader(), "VS_Main", "vs_5_0", &vsBlob)) { - initializationFailed = true; - return false; - } - - HRESULT hr = device->CreateVertexShader(vsBlob->GetBufferPointer(), vsBlob->GetBufferSize(), nullptr, vertexShader.put()); - vsBlob->Release(); - - if (FAILED(hr)) { - logger::error("Failed to create blur vertex shader"); + // Compile vertex shader from horizontal blur file (both share same vertex shader) + vertexShader = 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 - ID3DBlob* hpsBlob = nullptr; - if (!CompileShader(GetHorizontalBlurShader(), "PS_Main", "ps_5_0", &hpsBlob)) { - initializationFailed = true; - return false; - } - - hr = device->CreatePixelShader(hpsBlob->GetBufferPointer(), hpsBlob->GetBufferSize(), nullptr, horizontalPixelShader.put()); - hpsBlob->Release(); - - if (FAILED(hr)) { - logger::error("Failed to create horizontal blur pixel shader"); + horizontalPixelShader = 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 - ID3DBlob* vpsBlob = nullptr; - if (!CompileShader(GetVerticalBlurShader(), "PS_Main", "ps_5_0", &vpsBlob)) { - initializationFailed = true; - return false; - } - - hr = device->CreatePixelShader(vpsBlob->GetBufferPointer(), vpsBlob->GetBufferSize(), nullptr, verticalPixelShader.put()); - vpsBlob->Release(); - - if (FAILED(hr)) { - logger::error("Failed to create vertical blur pixel shader"); + verticalPixelShader = 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; } @@ -302,7 +126,7 @@ float4 PS_Main(VS_OUTPUT input) : SV_TARGET bufferDesc.ByteWidth = sizeof(BlurConstants); bufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; - hr = device->CreateBuffer(&bufferDesc, nullptr, constantBuffer.put()); + HRESULT hr = device->CreateBuffer(&bufferDesc, nullptr, constantBuffer.put()); if (FAILED(hr)) { logger::error("Failed to create blur constant buffer"); initializationFailed = true; @@ -344,6 +168,21 @@ float4 PS_Main(VS_OUTPUT input) : SV_TARGET return false; } + // Create scissor-enabled rasterizer state + D3D11_RASTERIZER_DESC rsDesc = {}; + rsDesc.FillMode = D3D11_FILL_SOLID; + rsDesc.CullMode = D3D11_CULL_BACK; + 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; + return false; + } + initialized = true; return true; } @@ -537,23 +376,8 @@ float4 PS_Main(VS_OUTPUT input) : SV_TARGET scissorRect.right = static_cast((std::min)(static_cast(sourceDesc.Width), menuMax.x)); scissorRect.bottom = static_cast((std::min)(static_cast(sourceDesc.Height), menuMax.y)); - D3D11_RASTERIZER_DESC rsDesc = {}; - if (originalRS) { - originalRS->GetDesc(&rsDesc); - } else { - rsDesc.FillMode = D3D11_FILL_SOLID; - rsDesc.CullMode = D3D11_CULL_BACK; - rsDesc.FrontCounterClockwise = FALSE; - rsDesc.DepthClipEnable = TRUE; - } - rsDesc.ScissorEnable = TRUE; - - ID3D11RasterizerState* scissorRS = nullptr; - globals::d3d::device->CreateRasterizerState(&rsDesc, &scissorRS); - if (scissorRS) { - context->RSSetState(scissorRS); - context->RSSetScissorRects(1, &scissorRect); - } + context->RSSetState(scissorRasterizerState.get()); + context->RSSetScissorRects(1, &scissorRect); context->OMSetRenderTargets(1, &targetRTV, nullptr); float blendFactor[4] = { 1.0f, 1.0f, 1.0f, currentIntensity * 0.8f }; @@ -580,8 +404,6 @@ float4 PS_Main(VS_OUTPUT input) : SV_TARGET originalDSV->Release(); if (originalRS) originalRS->Release(); - if (scissorRS) - scissorRS->Release(); } void Cleanup() @@ -594,6 +416,7 @@ float4 PS_Main(VS_OUTPUT input) : SV_TARGET constantBuffer = nullptr; samplerState = nullptr; blendState = nullptr; + scissorRasterizerState = nullptr; blurTexture1 = nullptr; blurTexture2 = nullptr; From e07d14afcee371e541ed39ba36c5084ac19ce027 Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Tue, 25 Nov 2025 19:21:33 +1000 Subject: [PATCH 4/8] optimisations --- .../Menu/BackgroundBlurHorizontal.hlsl | 48 ++++--- .../Shaders/Menu/BackgroundBlurVertical.hlsl | 48 ++++--- src/Menu/BackgroundBlur.cpp | 132 ++++++++++++++---- 3 files changed, 155 insertions(+), 73 deletions(-) diff --git a/package/Shaders/Menu/BackgroundBlurHorizontal.hlsl b/package/Shaders/Menu/BackgroundBlurHorizontal.hlsl index 2df1e0000..fbe5b1217 100644 --- a/package/Shaders/Menu/BackgroundBlurHorizontal.hlsl +++ b/package/Shaders/Menu/BackgroundBlurHorizontal.hlsl @@ -25,35 +25,37 @@ VS_OUTPUT VS_Main(uint vertexID : SV_VertexID) return output; } -float GaussianWeight(float offset) -{ - const float SIGMA = 2.0f; - const float v = 2.0f * SIGMA * SIGMA; - return exp(-(offset * offset) / v) / (3.14159265f * v); -} +// Precomputed normalized Gaussian weights (sigma = 2.0) +static const float WEIGHTS[8] = { + 0.1760327f, // offset 0 (center) + 0.1658591f, // offset ±1 + 0.1403215f, // offset ±2 + 0.1069852f, // offset ±3 + 0.0732894f, // offset ±4 + 0.0451904f, // offset ±5 + 0.0248657f, // offset ±6 + 0.0122423f // offset ±7 +}; float4 PS_Main(VS_OUTPUT input) : SV_TARGET { - float4 result = float4(0.0f, 0.0f, 0.0f, 0.0f); - float totalWeight = 0.0f; - const int samples = min(BlurParams.x, 15); const int halfSamples = samples / 2; - - for (int i = -halfSamples; i <= halfSamples; ++i) + + // Sample center pixel + float4 result = InputTexture.Sample(LinearSampler, input.TexCoord) * WEIGHTS[0]; + + // Sample symmetric pairs (2x loop unrolling opportunity) + [unroll(7)] + for (int i = 1; i <= halfSamples; ++i) { - float2 sampleCoord = input.TexCoord + float2(i * TexelSize.x, 0.0f); - float weight = GaussianWeight(float(i)); - - if (sampleCoord.x >= 0.0f && sampleCoord.x <= 1.0f) - { - result += InputTexture.Sample(LinearSampler, sampleCoord) * weight; - totalWeight += weight; - } + float weight = WEIGHTS[min(i, 7)]; + float offset = i * TexelSize.x; + + // Sampler CLAMP mode handles edge cases - no bounds check needed + result += InputTexture.Sample(LinearSampler, input.TexCoord + float2(offset, 0.0f)) * weight; + result += InputTexture.Sample(LinearSampler, input.TexCoord - float2(offset, 0.0f)) * weight; } - - if (totalWeight > 0.0f) - result /= totalWeight; - + return result; } diff --git a/package/Shaders/Menu/BackgroundBlurVertical.hlsl b/package/Shaders/Menu/BackgroundBlurVertical.hlsl index 27e5d713b..5b2c698d4 100644 --- a/package/Shaders/Menu/BackgroundBlurVertical.hlsl +++ b/package/Shaders/Menu/BackgroundBlurVertical.hlsl @@ -25,35 +25,37 @@ VS_OUTPUT VS_Main(uint vertexID : SV_VertexID) return output; } -float GaussianWeight(float offset) -{ - const float SIGMA = 2.0f; - const float v = 2.0f * SIGMA * SIGMA; - return exp(-(offset * offset) / v) / (3.14159265f * v); -} +// Precomputed normalized Gaussian weights (sigma = 2.0) +static const float WEIGHTS[8] = { + 0.1760327f, // offset 0 (center) + 0.1658591f, // offset ±1 + 0.1403215f, // offset ±2 + 0.1069852f, // offset ±3 + 0.0732894f, // offset ±4 + 0.0451904f, // offset ±5 + 0.0248657f, // offset ±6 + 0.0122423f // offset ±7 +}; float4 PS_Main(VS_OUTPUT input) : SV_TARGET { - float4 result = float4(0.0f, 0.0f, 0.0f, 0.0f); - float totalWeight = 0.0f; - const int samples = min(BlurParams.x, 15); const int halfSamples = samples / 2; - - for (int i = -halfSamples; i <= halfSamples; ++i) + + // Sample center pixel + float4 result = InputTexture.Sample(LinearSampler, input.TexCoord) * WEIGHTS[0]; + + // Sample symmetric pairs (2x loop unrolling opportunity) + [unroll(7)] + for (int i = 1; i <= halfSamples; ++i) { - float2 sampleCoord = input.TexCoord + float2(0.0f, i * TexelSize.y); - float weight = GaussianWeight(float(i)); - - if (sampleCoord.y >= 0.0f && sampleCoord.y <= 1.0f) - { - result += InputTexture.Sample(LinearSampler, sampleCoord) * weight; - totalWeight += weight; - } + float weight = WEIGHTS[min(i, 7)]; + float offset = i * TexelSize.y; + + // Sampler CLAMP mode handles edge cases - no bounds check needed + result += InputTexture.Sample(LinearSampler, input.TexCoord + float2(0.0f, offset)) * weight; + result += InputTexture.Sample(LinearSampler, input.TexCoord - float2(0.0f, offset)) * weight; } - - if (totalWeight > 0.0f) - result /= totalWeight; - + return result; } diff --git a/src/Menu/BackgroundBlur.cpp b/src/Menu/BackgroundBlur.cpp index c19b4c78b..d6cba9d4c 100644 --- a/src/Menu/BackgroundBlur.cpp +++ b/src/Menu/BackgroundBlur.cpp @@ -50,6 +50,9 @@ namespace BackgroundBlur float currentIntensity = 0.0f; bool enabled = false; + // Downsample factor for performance (4 = quarter resolution, 16x fewer pixels) + constexpr UINT DOWNSAMPLE_FACTOR = 8; + // DirectX resources (RAII managed) winrt::com_ptr vertexShader; winrt::com_ptr horizontalPixelShader; @@ -59,7 +62,12 @@ namespace BackgroundBlur winrt::com_ptr blendState; winrt::com_ptr scissorRasterizerState; - // Intermediate blur textures + // Downsampled textures for blur (quarter-res for performance) + winrt::com_ptr downsampleTexture; + winrt::com_ptr downsampleRTV; + winrt::com_ptr downsampleSRV; + + // Intermediate blur textures (at downsampled resolution) winrt::com_ptr blurTexture1; winrt::com_ptr blurTexture2; winrt::com_ptr blurRTV1; @@ -69,6 +77,8 @@ namespace BackgroundBlur UINT textureWidth = 0; UINT textureHeight = 0; + UINT downsampledWidth = 0; + UINT downsampledHeight = 0; bool initialized = false; bool initializationFailed = false; @@ -97,7 +107,7 @@ namespace BackgroundBlur } // Compile vertex shader from horizontal blur file (both share same vertex shader) - vertexShader = static_cast(Util::CompileShader(L"Data\\Shaders\\Menu\\BackgroundBlurHorizontal.hlsl", {}, "vs_5_0", "VS_Main")); + 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; @@ -105,7 +115,7 @@ namespace BackgroundBlur } // Compile horizontal pixel shader - horizontalPixelShader = static_cast(Util::CompileShader(L"Data\\Shaders\\Menu\\BackgroundBlurHorizontal.hlsl", {}, "ps_5_0", "PS_Main")); + 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; @@ -113,7 +123,7 @@ namespace BackgroundBlur } // Compile vertical pixel shader - verticalPixelShader = static_cast(Util::CompileShader(L"Data\\Shaders\\Menu\\BackgroundBlurVertical.hlsl", {}, "ps_5_0", "PS_Main")); + 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; @@ -200,7 +210,14 @@ namespace BackgroundBlur return; } + // Calculate downsampled dimensions + 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; @@ -208,10 +225,10 @@ namespace BackgroundBlur blurSRV1 = nullptr; blurSRV2 = nullptr; - // Create texture description + // Create downsampled texture description D3D11_TEXTURE2D_DESC texDesc = {}; - texDesc.Width = width; - texDesc.Height = height; + texDesc.Width = dsWidth; + texDesc.Height = dsHeight; texDesc.MipLevels = 1; texDesc.ArraySize = 1; texDesc.Format = format; @@ -219,8 +236,30 @@ namespace BackgroundBlur texDesc.Usage = D3D11_USAGE_DEFAULT; texDesc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE; - // Create first blur texture - HRESULT hr = device->CreateTexture2D(&texDesc, nullptr, blurTexture1.put()); + // 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"); return; @@ -276,6 +315,8 @@ namespace BackgroundBlur textureWidth = width; textureHeight = height; + downsampledWidth = dsWidth; + downsampledHeight = dsHeight; } void PerformBlur(ID3D11Texture2D* sourceTexture, ID3D11RenderTargetView* targetRTV, ImVec2 menuMin, ImVec2 menuMax) @@ -319,13 +360,46 @@ namespace BackgroundBlur ID3D11RasterizerState* originalRS = nullptr; context->RSGetState(&originalRS); - // Calculate blur parameters + // Downsample source to quarter resolution with bilinear filtering + 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); + + auto downsampleRTVPtr = downsampleRTV.get(); + context->OMSetRenderTargets(1, &downsampleRTVPtr, nullptr); + + auto constantBufferPtr = constantBuffer.get(); + auto samplerStatePtr = samplerState.get(); + context->VSSetShader(vertexShader.get(), nullptr, 0); + context->PSSetSamplers(0, 1, &samplerStatePtr); + + // Simple copy to downsample (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); + + // Calculate blur parameters (working at quarter resolution) float blurRadius = currentIntensity * 10.0f; int sampleCount = (std::max)(5, (std::min)(15, static_cast(9 + currentIntensity * 6))); BlurConstants constants = {}; - constants.texelSize[0] = blurRadius / static_cast(textureWidth); - constants.texelSize[1] = blurRadius / static_cast(textureHeight); + constants.texelSize[0] = blurRadius / static_cast(downsampledWidth); + constants.texelSize[1] = blurRadius / static_cast(downsampledHeight); constants.texelSize[2] = currentIntensity; constants.texelSize[3] = 0.0f; constants.blurParams[0] = sampleCount; @@ -335,39 +409,36 @@ namespace BackgroundBlur context->UpdateSubresource(constantBuffer.get(), 0, nullptr, &constants, 0, 0); - // Set up viewport for blur + // Set up viewport for blur (quarter resolution) D3D11_VIEWPORT blurViewport = {}; - blurViewport.Width = static_cast(textureWidth); - blurViewport.Height = static_cast(textureHeight); + blurViewport.Width = static_cast(downsampledWidth); + blurViewport.Height = static_cast(downsampledHeight); blurViewport.MinDepth = 0.0f; blurViewport.MaxDepth = 1.0f; context->RSSetViewports(1, &blurViewport); - // Set common shader resources - auto constantBufferPtr = constantBuffer.get(); - auto samplerStatePtr = samplerState.get(); - context->VSSetShader(vertexShader.get(), nullptr, 0); context->PSSetConstantBuffers(0, 1, &constantBufferPtr); - context->PSSetSamplers(0, 1, &samplerStatePtr); - // First pass: Horizontal blur + // First pass: Horizontal blur (on downsampled texture) auto rtv1Ptr = blurRTV1.get(); + auto downsampleSRVPtr = downsampleSRV.get(); context->OMSetRenderTargets(1, &rtv1Ptr, nullptr); context->PSSetShader(horizontalPixelShader.get(), nullptr, 0); - context->PSSetShaderResources(0, 1, &sourceSRV); + context->PSSetShaderResources(0, 1, &downsampleSRVPtr); context->Draw(3, 0); - // Second pass: Vertical blur - ID3D11ShaderResourceView* nullSRV = nullptr; + // Second pass: Vertical blur (on downsampled texture) + context->PSSetShaderResources(0, 1, &nullSRV); auto rtv2Ptr = blurRTV2.get(); auto srv1Ptr = blurSRV1.get(); context->OMSetRenderTargets(1, &rtv2Ptr, nullptr); context->PSSetShader(verticalPixelShader.get(), nullptr, 0); - context->PSSetShaderResources(0, 1, &nullSRV); context->PSSetShaderResources(0, 1, &srv1Ptr); context->Draw(3, 0); + context->PSSetShaderResources(0, 1, &nullSRV); - // Final composition with scissor test + // Final composition: upscale from quarter-res with scissor test + // Bilinear sampler smooths the upscale automatically context->RSSetViewports(1, &originalViewport); D3D11_RECT scissorRect; @@ -383,10 +454,11 @@ namespace BackgroundBlur float blendFactor[4] = { 1.0f, 1.0f, 1.0f, currentIntensity * 0.8f }; context->OMSetBlendState(blendState.get(), blendFactor, 0xFFFFFFFF); + // Use blurred quarter-res texture, bilinear filtering upscales smoothly auto srv2Ptr = blurSRV2.get(); - context->PSSetShaderResources(0, 1, &nullSRV); context->PSSetShaderResources(0, 1, &srv2Ptr); context->Draw(3, 0); + context->PSSetShaderResources(0, 1, &nullSRV); // Restore state context->OMSetRenderTargets(1, &originalRTV, originalDSV); @@ -418,6 +490,10 @@ namespace BackgroundBlur blendState = nullptr; scissorRasterizerState = nullptr; + downsampleTexture = nullptr; + downsampleRTV = nullptr; + downsampleSRV = nullptr; + blurTexture1 = nullptr; blurTexture2 = nullptr; blurRTV1 = nullptr; @@ -427,6 +503,8 @@ namespace BackgroundBlur textureWidth = 0; textureHeight = 0; + downsampledWidth = 0; + downsampledHeight = 0; enabled = false; currentIntensity = 0.0f; initialized = false; From 971eda26fe58acdc897e10314dd0c04e6de194d6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 09:21:55 +0000 Subject: [PATCH 5/8] =?UTF-8?q?style:=20=F0=9F=8E=A8=20apply=20pre-commit.?= =?UTF-8?q?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/BackgroundBlurHorizontal.hlsl | 8 ++++---- package/Shaders/Menu/BackgroundBlurVertical.hlsl | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package/Shaders/Menu/BackgroundBlurHorizontal.hlsl b/package/Shaders/Menu/BackgroundBlurHorizontal.hlsl index fbe5b1217..ba8166b84 100644 --- a/package/Shaders/Menu/BackgroundBlurHorizontal.hlsl +++ b/package/Shaders/Menu/BackgroundBlurHorizontal.hlsl @@ -41,21 +41,21 @@ float4 PS_Main(VS_OUTPUT input) : SV_TARGET { const int samples = min(BlurParams.x, 15); const int halfSamples = samples / 2; - + // Sample center pixel float4 result = InputTexture.Sample(LinearSampler, input.TexCoord) * WEIGHTS[0]; - + // Sample symmetric pairs (2x loop unrolling opportunity) [unroll(7)] for (int i = 1; i <= halfSamples; ++i) { float weight = WEIGHTS[min(i, 7)]; float offset = i * TexelSize.x; - + // Sampler CLAMP mode handles edge cases - no bounds check needed result += InputTexture.Sample(LinearSampler, input.TexCoord + float2(offset, 0.0f)) * weight; result += InputTexture.Sample(LinearSampler, input.TexCoord - float2(offset, 0.0f)) * weight; } - + return result; } diff --git a/package/Shaders/Menu/BackgroundBlurVertical.hlsl b/package/Shaders/Menu/BackgroundBlurVertical.hlsl index 5b2c698d4..9b3388ad2 100644 --- a/package/Shaders/Menu/BackgroundBlurVertical.hlsl +++ b/package/Shaders/Menu/BackgroundBlurVertical.hlsl @@ -41,21 +41,21 @@ float4 PS_Main(VS_OUTPUT input) : SV_TARGET { const int samples = min(BlurParams.x, 15); const int halfSamples = samples / 2; - + // Sample center pixel float4 result = InputTexture.Sample(LinearSampler, input.TexCoord) * WEIGHTS[0]; - + // Sample symmetric pairs (2x loop unrolling opportunity) [unroll(7)] for (int i = 1; i <= halfSamples; ++i) { float weight = WEIGHTS[min(i, 7)]; float offset = i * TexelSize.y; - + // Sampler CLAMP mode handles edge cases - no bounds check needed result += InputTexture.Sample(LinearSampler, input.TexCoord + float2(0.0f, offset)) * weight; result += InputTexture.Sample(LinearSampler, input.TexCoord - float2(0.0f, offset)) * weight; } - + return result; } From 2e0a3c06fe3f49e6abb548557572595171ac4984 Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Tue, 25 Nov 2025 20:14:20 +1000 Subject: [PATCH 6/8] fixes --- .../Menu/BackgroundBlurHorizontal.hlsl | 24 ++++-- .../Shaders/Menu/BackgroundBlurVertical.hlsl | 24 ++++-- src/Menu/BackgroundBlur.cpp | 80 +++++++++---------- src/Menu/BackgroundBlur.h | 13 +-- 4 files changed, 73 insertions(+), 68 deletions(-) diff --git a/package/Shaders/Menu/BackgroundBlurHorizontal.hlsl b/package/Shaders/Menu/BackgroundBlurHorizontal.hlsl index ba8166b84..28e99bf45 100644 --- a/package/Shaders/Menu/BackgroundBlurHorizontal.hlsl +++ b/package/Shaders/Menu/BackgroundBlurHorizontal.hlsl @@ -41,21 +41,29 @@ float4 PS_Main(VS_OUTPUT input) : SV_TARGET { const int samples = min(BlurParams.x, 15); const int halfSamples = samples / 2; - + + // Compute normalization factor for actual weights used + float weightSum = WEIGHTS[0]; + [unroll(7)] + for (int j = 1; j <= halfSamples; ++j) + { + weightSum += 2.0f * WEIGHTS[min(j, 7)]; + } + const float normalization = 1.0f / weightSum; + // Sample center pixel - float4 result = InputTexture.Sample(LinearSampler, input.TexCoord) * WEIGHTS[0]; - - // Sample symmetric pairs (2x loop unrolling opportunity) + float4 result = InputTexture.Sample(LinearSampler, input.TexCoord) * (WEIGHTS[0] * normalization); + + // Sample symmetric pairs [unroll(7)] for (int i = 1; i <= halfSamples; ++i) { - float weight = WEIGHTS[min(i, 7)]; + float weight = WEIGHTS[min(i, 7)] * normalization; float offset = i * TexelSize.x; - - // Sampler CLAMP mode handles edge cases - no bounds check needed + result += InputTexture.Sample(LinearSampler, input.TexCoord + float2(offset, 0.0f)) * weight; result += InputTexture.Sample(LinearSampler, input.TexCoord - float2(offset, 0.0f)) * weight; } - + return result; } diff --git a/package/Shaders/Menu/BackgroundBlurVertical.hlsl b/package/Shaders/Menu/BackgroundBlurVertical.hlsl index 9b3388ad2..ed9d845a9 100644 --- a/package/Shaders/Menu/BackgroundBlurVertical.hlsl +++ b/package/Shaders/Menu/BackgroundBlurVertical.hlsl @@ -41,21 +41,29 @@ float4 PS_Main(VS_OUTPUT input) : SV_TARGET { const int samples = min(BlurParams.x, 15); const int halfSamples = samples / 2; - + + // Compute normalization factor for actual weights used + float weightSum = WEIGHTS[0]; + [unroll(7)] + for (int j = 1; j <= halfSamples; ++j) + { + weightSum += 2.0f * WEIGHTS[min(j, 7)]; + } + const float normalization = 1.0f / weightSum; + // Sample center pixel - float4 result = InputTexture.Sample(LinearSampler, input.TexCoord) * WEIGHTS[0]; - - // Sample symmetric pairs (2x loop unrolling opportunity) + float4 result = InputTexture.Sample(LinearSampler, input.TexCoord) * (WEIGHTS[0] * normalization); + + // Sample symmetric pairs [unroll(7)] for (int i = 1; i <= halfSamples; ++i) { - float weight = WEIGHTS[min(i, 7)]; + float weight = WEIGHTS[min(i, 7)] * normalization; float offset = i * TexelSize.y; - - // Sampler CLAMP mode handles edge cases - no bounds check needed + result += InputTexture.Sample(LinearSampler, input.TexCoord + float2(0.0f, offset)) * weight; result += InputTexture.Sample(LinearSampler, input.TexCoord - float2(0.0f, offset)) * weight; } - + return result; } diff --git a/src/Menu/BackgroundBlur.cpp b/src/Menu/BackgroundBlur.cpp index d6cba9d4c..64384113b 100644 --- a/src/Menu/BackgroundBlur.cpp +++ b/src/Menu/BackgroundBlur.cpp @@ -1,4 +1,4 @@ -// Based on Unrimp rendering engine's separable blur implementation +// Inspired by Unrimp rendering engine's separable blur implementation // Credits: Christian Ofenberg and the Unrimp project (https://github.com/cofenberg/unrimp) // License: MIT License @@ -15,31 +15,11 @@ using namespace std::literals; -/** - * THEME MANAGER IMPLEMENTATION NOTES - * =================================== - * - * BLUR SHADER PARAMETERS: - * ----------------------- - * The background blur system uses constant buffers to pass parameters to HLSL shaders: - * - * BlurBuffer (cbuffer b0): - * - TexelSize.xy: Inverse texture dimensions (1/width, 1/height) for UV calculations - * - TexelSize.z: Blur strength multiplier (0.0-1.0 from BackgroundBlur theme setting) - * - BlurParams.x: Number of blur samples (default: 13, must be odd for centered kernel) - * - * The blur uses a separable Gaussian kernel split into two passes: - * 1. Horizontal pass: Samples along X-axis, outputs to intermediate texture - * 2. Vertical pass: Samples along Y-axis from intermediate, outputs final result - */ - -// Blur System Constants -// --------------------- -// Text contrast boost per unit blur: Compensates for reduced clarity behind blurred backgrounds -constexpr float BLUR_TEXT_CONTRAST_FACTOR = 0.05f; // 5% brightness boost at max blur - -// Gaussian blur sigma: Controls blur kernel spread (standard deviation) -constexpr float GAUSSIAN_BLUR_SIGMA = 2.0f; +// Blur intensity hardcoded. Super downscaled blur is very sensitive, this value looks best. +constexpr float BLUR_INTENSITY = 0.03f; + +// Downsampling factor (8 = eighth resolution for performance) +constexpr UINT DOWNSAMPLE_FACTOR = 8; namespace BackgroundBlur { @@ -47,12 +27,8 @@ namespace BackgroundBlur namespace { std::mutex resourceMutex; - float currentIntensity = 0.0f; bool enabled = false; - // Downsample factor for performance (4 = quarter resolution, 16x fewer pixels) - constexpr UINT DOWNSAMPLE_FACTOR = 8; - // DirectX resources (RAII managed) winrt::com_ptr vertexShader; winrt::com_ptr horizontalPixelShader; @@ -262,6 +238,9 @@ namespace BackgroundBlur 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; } @@ -270,6 +249,9 @@ namespace BackgroundBlur if (FAILED(hr)) { logger::error("Failed to create blur texture 2"); blurTexture1 = nullptr; + downsampleTexture = nullptr; + downsampleRTV = nullptr; + downsampleSRV = nullptr; return; } @@ -279,6 +261,9 @@ namespace BackgroundBlur logger::error("Failed to create blur RTV 1"); blurTexture1 = nullptr; blurTexture2 = nullptr; + downsampleTexture = nullptr; + downsampleRTV = nullptr; + downsampleSRV = nullptr; return; } @@ -288,6 +273,9 @@ namespace BackgroundBlur blurTexture1 = nullptr; blurTexture2 = nullptr; blurRTV1 = nullptr; + downsampleTexture = nullptr; + downsampleRTV = nullptr; + downsampleSRV = nullptr; return; } @@ -299,6 +287,9 @@ namespace BackgroundBlur blurTexture2 = nullptr; blurRTV1 = nullptr; blurRTV2 = nullptr; + downsampleTexture = nullptr; + downsampleRTV = nullptr; + downsampleSRV = nullptr; return; } @@ -310,6 +301,9 @@ namespace BackgroundBlur blurRTV1 = nullptr; blurRTV2 = nullptr; blurSRV1 = nullptr; + downsampleTexture = nullptr; + downsampleRTV = nullptr; + downsampleSRV = nullptr; return; } @@ -393,14 +387,14 @@ namespace BackgroundBlur ID3D11ShaderResourceView* nullSRV = nullptr; context->PSSetShaderResources(0, 1, &nullSRV); - // Calculate blur parameters (working at quarter resolution) - float blurRadius = currentIntensity * 10.0f; - int sampleCount = (std::max)(5, (std::min)(15, static_cast(9 + currentIntensity * 6))); + // Calculate blur parameters at eighth resolution + float blurRadius = BLUR_INTENSITY * 10.0f; + int sampleCount = 9; BlurConstants constants = {}; constants.texelSize[0] = blurRadius / static_cast(downsampledWidth); constants.texelSize[1] = blurRadius / static_cast(downsampledHeight); - constants.texelSize[2] = currentIntensity; + constants.texelSize[2] = BLUR_INTENSITY; constants.texelSize[3] = 0.0f; constants.blurParams[0] = sampleCount; constants.blurParams[1] = 0; @@ -451,7 +445,7 @@ namespace BackgroundBlur context->RSSetScissorRects(1, &scissorRect); context->OMSetRenderTargets(1, &targetRTV, nullptr); - float blendFactor[4] = { 1.0f, 1.0f, 1.0f, currentIntensity * 0.8f }; + float blendFactor[4] = { 1.0f, 1.0f, 1.0f, BLUR_INTENSITY * 0.8f }; context->OMSetBlendState(blendState.get(), blendFactor, 0xFFFFFFFF); // Use blurred quarter-res texture, bilinear filtering upscales smoothly @@ -511,15 +505,14 @@ namespace BackgroundBlur initializationFailed = false; } - void SetIntensity(float intensity) + void SetEnabled(bool enable) { - currentIntensity = std::clamp(intensity, 0.0f, 1.0f); - enabled = (currentIntensity > 0.0f); + enabled = enable; } - float GetIntensity() + bool GetEnabled() { - return currentIntensity; + return enabled; } bool IsEnabled() @@ -536,7 +529,7 @@ namespace BackgroundBlur void RenderBackgroundBlur() { - if (!enabled || currentIntensity <= 0.0f) { + if (!enabled) { return; } @@ -604,6 +597,11 @@ namespace BackgroundBlur continue; } + // Skip tooltip windows + if (window->Flags & ImGuiWindowFlags_Tooltip) { + continue; + } + // Skip Performance Overlay window (no blur) if (window->Name && std::string_view(window->Name) == "Performance Overlay") { continue; diff --git a/src/Menu/BackgroundBlur.h b/src/Menu/BackgroundBlur.h index 9183b72e3..c4891a904 100644 --- a/src/Menu/BackgroundBlur.h +++ b/src/Menu/BackgroundBlur.h @@ -42,17 +42,8 @@ namespace BackgroundBlur */ void Cleanup(); - /** - * @brief Sets the blur intensity for next render - * @param intensity Blur strength (0.0 = disabled, 1.0 = maximum) - */ - void SetIntensity(float intensity); - - /** - * @brief Gets current blur intensity - * @return Current blur intensity value - */ - float GetIntensity(); + void SetEnabled(bool enable); + bool GetEnabled(); /** * @brief Checks if blur is enabled From f51556c3bdba571219aeade41642185c12963808 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 10:15:16 +0000 Subject: [PATCH 7/8] =?UTF-8?q?style:=20=F0=9F=8E=A8=20apply=20pre-commit.?= =?UTF-8?q?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/BackgroundBlurHorizontal.hlsl | 10 +++++----- package/Shaders/Menu/BackgroundBlurVertical.hlsl | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package/Shaders/Menu/BackgroundBlurHorizontal.hlsl b/package/Shaders/Menu/BackgroundBlurHorizontal.hlsl index 28e99bf45..4c941f435 100644 --- a/package/Shaders/Menu/BackgroundBlurHorizontal.hlsl +++ b/package/Shaders/Menu/BackgroundBlurHorizontal.hlsl @@ -41,7 +41,7 @@ float4 PS_Main(VS_OUTPUT input) : SV_TARGET { const int samples = min(BlurParams.x, 15); const int halfSamples = samples / 2; - + // Compute normalization factor for actual weights used float weightSum = WEIGHTS[0]; [unroll(7)] @@ -50,20 +50,20 @@ float4 PS_Main(VS_OUTPUT input) : SV_TARGET weightSum += 2.0f * WEIGHTS[min(j, 7)]; } const float normalization = 1.0f / weightSum; - + // Sample center pixel float4 result = InputTexture.Sample(LinearSampler, input.TexCoord) * (WEIGHTS[0] * normalization); - + // Sample symmetric pairs [unroll(7)] for (int i = 1; i <= halfSamples; ++i) { float weight = WEIGHTS[min(i, 7)] * normalization; float offset = i * TexelSize.x; - + result += InputTexture.Sample(LinearSampler, input.TexCoord + float2(offset, 0.0f)) * weight; result += InputTexture.Sample(LinearSampler, input.TexCoord - float2(offset, 0.0f)) * weight; } - + return result; } diff --git a/package/Shaders/Menu/BackgroundBlurVertical.hlsl b/package/Shaders/Menu/BackgroundBlurVertical.hlsl index ed9d845a9..b851f2339 100644 --- a/package/Shaders/Menu/BackgroundBlurVertical.hlsl +++ b/package/Shaders/Menu/BackgroundBlurVertical.hlsl @@ -41,7 +41,7 @@ float4 PS_Main(VS_OUTPUT input) : SV_TARGET { const int samples = min(BlurParams.x, 15); const int halfSamples = samples / 2; - + // Compute normalization factor for actual weights used float weightSum = WEIGHTS[0]; [unroll(7)] @@ -50,20 +50,20 @@ float4 PS_Main(VS_OUTPUT input) : SV_TARGET weightSum += 2.0f * WEIGHTS[min(j, 7)]; } const float normalization = 1.0f / weightSum; - + // Sample center pixel float4 result = InputTexture.Sample(LinearSampler, input.TexCoord) * (WEIGHTS[0] * normalization); - + // Sample symmetric pairs [unroll(7)] for (int i = 1; i <= halfSamples; ++i) { float weight = WEIGHTS[min(i, 7)] * normalization; float offset = i * TexelSize.y; - + result += InputTexture.Sample(LinearSampler, input.TexCoord + float2(0.0f, offset)) * weight; result += InputTexture.Sample(LinearSampler, input.TexCoord - float2(0.0f, offset)) * weight; } - + return result; } From 98a205337224b1051bcfc2601a745302b345e1f5 Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Wed, 26 Nov 2025 19:18:31 +1000 Subject: [PATCH 8/8] Update BackgroundBlur.cpp --- src/Menu/BackgroundBlur.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Menu/BackgroundBlur.cpp b/src/Menu/BackgroundBlur.cpp index 64384113b..6becee2ae 100644 --- a/src/Menu/BackgroundBlur.cpp +++ b/src/Menu/BackgroundBlur.cpp @@ -500,7 +500,6 @@ namespace BackgroundBlur downsampledWidth = 0; downsampledHeight = 0; enabled = false; - currentIntensity = 0.0f; initialized = false; initializationFailed = false; }