The modal backdrop blur was params.blurRadius (96) scaled by the user's global acrylic blur-strength slider (blurRadiusMultiplier), so lowering the slider also weakened every modal's backdrop. Make the modal blur a fixed hardcoded value. Add AcrylicParams::absoluteBlurRadius: when set, applyBlur() uses the radius as-is and skips the multiplier (threaded through both the GL and DX11 blur paths + the no-op stub). DrawFullWindowBlurBackdrop sets it, so the modal backdrop is always a 96px blur regardless of the slider. Panels/other acrylic still scale with the slider as before. Verified: rendering a modal at blur_multiplier 0.1 vs 2.0 now produces an identical backdrop (max pixel diff 1/255) — previously those were ~9.6px vs ~192px of blur. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
472 lines
15 KiB
C++
472 lines
15 KiB
C++
// DragonX Wallet - ImGui Edition
|
||
// Copyright 2024-2026 The Hush Developers
|
||
// Released under the GPLv3
|
||
|
||
#pragma once
|
||
|
||
#include "framebuffer.h"
|
||
#include "blur_shader.h"
|
||
#include "noise_texture.h"
|
||
#include "imgui.h"
|
||
|
||
#include <memory>
|
||
#include <string>
|
||
|
||
#ifdef DRAGONX_USE_DX11
|
||
#include <d3d11.h>
|
||
#endif
|
||
|
||
namespace dragonx {
|
||
namespace ui {
|
||
namespace effects {
|
||
|
||
/**
|
||
* @brief Acrylic material parameters
|
||
*
|
||
* These parameters control the appearance of the acrylic effect,
|
||
* matching Microsoft's Fluent Design system.
|
||
*/
|
||
struct AcrylicParams {
|
||
// Tint color (RGBA, pre-multiplied alpha)
|
||
ImVec4 tintColor = ImVec4(0.1f, 0.1f, 0.12f, 1.0f);
|
||
|
||
// Tint opacity (0.0 = fully transparent, 1.0 = fully opaque)
|
||
float tintOpacity = 0.75f;
|
||
|
||
// Luminosity opacity (controls saturation pass-through)
|
||
// Lower values = more desaturated/milky appearance
|
||
float luminosityOpacity = 0.5f;
|
||
|
||
// Blur radius in pixels (typical: 20-60)
|
||
float blurRadius = 30.0f;
|
||
|
||
// When true, blurRadius is used AS-IS — NOT scaled by the user's blur-strength slider
|
||
// (blurRadiusMultiplier). The modal backdrop sets this so its blur is a fixed value, independent
|
||
// of the global acrylic slider.
|
||
bool absoluteBlurRadius = false;
|
||
|
||
// Noise texture opacity (typical: 0.02-0.04)
|
||
float noiseOpacity = 0.02f;
|
||
|
||
// Fallback color when acrylic is disabled.
|
||
// The alpha channel also controls the glass opacity of the blurred
|
||
// background when acrylic IS active (lower alpha = more see-through).
|
||
ImVec4 fallbackColor = ImVec4(0.15f, 0.15f, 0.18f, 0.5f);
|
||
|
||
// Whether acrylic is enabled (false = use fallback)
|
||
bool enabled = true;
|
||
};
|
||
|
||
/**
|
||
* @brief Acrylic quality levels
|
||
*/
|
||
enum class AcrylicQuality {
|
||
Off, // Solid fallback color only
|
||
Low, // Single blur pass, downsampled
|
||
Medium, // Two blur passes, slight downsample
|
||
High // Full quality blur
|
||
};
|
||
|
||
/**
|
||
* @brief Acrylic fallback modes for graceful degradation
|
||
*/
|
||
enum class AcrylicFallback {
|
||
None, // Full acrylic (blur + tint + noise)
|
||
TintedOnly, // No blur, just semi-transparent tint overlay
|
||
Solid // Opaque fallback color only
|
||
};
|
||
|
||
/**
|
||
* @brief Acrylic settings for accessibility and performance
|
||
*/
|
||
struct AcrylicSettings {
|
||
bool enabled = true; // Master toggle
|
||
bool reducedTransparency = false; // Accessibility: use solid colors
|
||
float uiOpacity = 1.0f; // Card/sidebar opacity multiplier (0.3–1.0)
|
||
AcrylicQuality quality = AcrylicQuality::Medium; // Medium default for balance of quality/performance
|
||
float blurRadiusMultiplier = 1.0f; // Scales all blur radii (0.5 - 2.0)
|
||
float noiseOpacityMultiplier = 1.0f; // Scales noise opacity (0.0 - 2.0)
|
||
|
||
// Auto-disable conditions (checked each frame)
|
||
bool disableOnBattery = false; // Disable when on battery power
|
||
bool disableOnLowFPS = false; // Disable if FPS drops below threshold
|
||
float lowFPSThreshold = 30.0f; // FPS threshold for auto-disable
|
||
};
|
||
|
||
/**
|
||
* @brief System capabilities for acrylic effects
|
||
*/
|
||
struct AcrylicCapabilities {
|
||
bool hasFramebufferSupport = false;
|
||
bool hasShaderSupport = false;
|
||
bool hasTextureSupport = false;
|
||
int maxTextureSize = 0;
|
||
std::string glVersion;
|
||
std::string glRenderer;
|
||
bool isLowEndGPU = false;
|
||
bool isOnBattery = false; // Linux: check /sys/class/power_supply
|
||
};
|
||
|
||
/**
|
||
* @brief Main acrylic material rendering system
|
||
*
|
||
* Implements Microsoft Fluent Design's acrylic material effect:
|
||
* - Gaussian blur of background content
|
||
* - Tint color overlay
|
||
* - Luminosity blend for depth
|
||
* - Subtle noise texture for grain
|
||
*/
|
||
class AcrylicMaterial {
|
||
public:
|
||
AcrylicMaterial();
|
||
~AcrylicMaterial();
|
||
|
||
// Non-copyable
|
||
AcrylicMaterial(const AcrylicMaterial&) = delete;
|
||
AcrylicMaterial& operator=(const AcrylicMaterial&) = delete;
|
||
|
||
/**
|
||
* @brief Initialize the acrylic system
|
||
* @return true if successful
|
||
*/
|
||
bool init();
|
||
|
||
/**
|
||
* @brief Release all resources
|
||
*/
|
||
void shutdown();
|
||
|
||
/**
|
||
* @brief Check if system is initialized
|
||
*/
|
||
bool isInitialized() const { return initialized_; }
|
||
|
||
/**
|
||
* @brief Update internal buffers for new viewport size
|
||
* @param width Viewport width
|
||
* @param height Viewport height
|
||
*/
|
||
void resize(int width, int height);
|
||
|
||
/**
|
||
* @brief Capture current screen content for blur source
|
||
*
|
||
* Call this BEFORE rendering any acrylic surfaces.
|
||
* Only recaptures when the background is marked dirty.
|
||
*/
|
||
void captureBackground();
|
||
|
||
/**
|
||
* @brief Capture background directly from the current framebuffer.
|
||
*
|
||
* Intended to be called from an ImGui draw callback inserted at
|
||
* the end of the BackgroundDrawList. At that point during
|
||
* RenderDrawData(), only the background (gradient / image / noise)
|
||
* has been rasterized — no UI elements are in the framebuffer yet.
|
||
* This gives a clean background capture for acrylic blur.
|
||
*
|
||
* Respects the dirtyFrames_ counter so the capture and blur
|
||
* are only recomputed when the background actually changes
|
||
* (resize, theme change, etc.).
|
||
*/
|
||
void captureBackgroundDirect();
|
||
|
||
/**
|
||
* @brief Capture the LIVE framebuffer (whatever has been drawn so far this
|
||
* frame), bypassing the dirtyFrames_ gate. Used by a full-window modal
|
||
* overlay to blur the live UI behind it: insert GetLiveCaptureCallback()
|
||
* at the start of the overlay window's draw list so it fires mid-pass,
|
||
* after the app UI is rasterized and before the overlay's own backdrop.
|
||
* Always forces a re-blur of the fresh capture.
|
||
*/
|
||
void captureLiveFramebuffer();
|
||
|
||
/**
|
||
* @brief Returns true once a valid background capture has been made.
|
||
*
|
||
* drawRect() checks this and falls back to a tinted fill on the
|
||
* very first frame before the callback has fired.
|
||
*/
|
||
bool hasValidCapture() const { return hasValidCapture_; }
|
||
|
||
/// Mark background as needing recapture (call on resize, theme change, etc.)
|
||
/// Uses a 2-frame counter so the capture re-runs after the new background
|
||
/// has actually been rendered (theme switch happens mid-frame, before the
|
||
/// BackgroundDrawList is rebuilt with new colors/images).
|
||
void markBackgroundDirty() { dirtyFrames_ = 2; }
|
||
|
||
/**
|
||
* @brief Render an acrylic-filled rectangle
|
||
*
|
||
* @param drawList ImGui draw list to add commands to
|
||
* @param pMin Top-left corner in screen coordinates
|
||
* @param pMax Bottom-right corner in screen coordinates
|
||
* @param params Acrylic parameters
|
||
* @param rounding Corner rounding
|
||
*/
|
||
void drawRect(ImDrawList* drawList, const ImVec2& pMin, const ImVec2& pMax,
|
||
const AcrylicParams& params, float rounding = 0.0f);
|
||
|
||
/**
|
||
* @brief Get the blurred background texture for manual rendering
|
||
*/
|
||
ImTextureID getBlurredTexture() const;
|
||
|
||
/**
|
||
* @brief Get the noise texture
|
||
*/
|
||
ImTextureID getNoiseTexture() const;
|
||
|
||
// ========================================================================
|
||
// Settings
|
||
// ========================================================================
|
||
|
||
/**
|
||
* @brief Set global acrylic quality
|
||
*/
|
||
void setQuality(AcrylicQuality quality);
|
||
AcrylicQuality getQuality() const { return settings_.quality; }
|
||
|
||
/**
|
||
* @brief Enable/disable acrylic globally
|
||
*/
|
||
void setEnabled(bool enabled) { settings_.enabled = enabled; }
|
||
bool isEnabled() const { return settings_.enabled; }
|
||
|
||
/**
|
||
* @brief Set blur radius multiplier (scales all blur radii)
|
||
* @param multiplier Value from 0.0 to 5.0
|
||
*/
|
||
void setBlurMultiplier(float multiplier) {
|
||
float clamped = std::max(0.0f, std::min(5.0f, multiplier));
|
||
settings_.blurRadiusMultiplier = clamped;
|
||
}
|
||
float getBlurMultiplier() const { return settings_.blurRadiusMultiplier; }
|
||
|
||
/**
|
||
* @brief Set reduced transparency mode (accessibility)
|
||
*/
|
||
void setReducedTransparency(bool reduced) { settings_.reducedTransparency = reduced; }
|
||
bool getReducedTransparency() const { return settings_.reducedTransparency; }
|
||
|
||
/**
|
||
* @brief Set UI opacity multiplier for cards/sidebar (0.3–1.0, 1=opaque)
|
||
*/
|
||
void setUIOpacity(float opacity) {
|
||
settings_.uiOpacity = std::max(0.3f, std::min(1.0f, opacity));
|
||
}
|
||
float getUIOpacity() const { return settings_.uiOpacity; }
|
||
|
||
/**
|
||
* @brief Set noise opacity multiplier (0.0 = no noise, 1.0 = default, 2.0 = double)
|
||
*/
|
||
void setNoiseOpacityMultiplier(float m) {
|
||
settings_.noiseOpacityMultiplier = std::max(0.0f, std::min(5.0f, m));
|
||
}
|
||
float getNoiseOpacityMultiplier() const { return settings_.noiseOpacityMultiplier; }
|
||
|
||
/**
|
||
* @brief Get full settings struct
|
||
*/
|
||
const AcrylicSettings& getSettings() const { return settings_; }
|
||
void setSettings(const AcrylicSettings& settings) { settings_ = settings; }
|
||
|
||
// ========================================================================
|
||
// Fallback System
|
||
// ========================================================================
|
||
|
||
/**
|
||
* @brief Detect system capabilities and determine fallback mode
|
||
*
|
||
* Checks OpenGL capabilities, GPU info, and user preferences to
|
||
* determine the appropriate rendering mode.
|
||
*
|
||
* @return The recommended fallback mode
|
||
*/
|
||
AcrylicFallback detectFallback() const;
|
||
|
||
/**
|
||
* @brief Get current fallback mode (cached)
|
||
*/
|
||
AcrylicFallback getCurrentFallback() const { return currentFallback_; }
|
||
|
||
/**
|
||
* @brief Force a specific fallback mode
|
||
*/
|
||
void setForcedFallback(AcrylicFallback fallback) {
|
||
forcedFallback_ = fallback;
|
||
hasForcedFallback_ = true;
|
||
}
|
||
|
||
/**
|
||
* @brief Clear forced fallback and use auto-detection
|
||
*/
|
||
void clearForcedFallback() { hasForcedFallback_ = false; }
|
||
|
||
/**
|
||
* @brief Get detected system capabilities
|
||
*/
|
||
const AcrylicCapabilities& getCapabilities() const { return capabilities_; }
|
||
|
||
/**
|
||
* @brief Refresh capability detection (call after context changes)
|
||
*/
|
||
void refreshCapabilities();
|
||
|
||
// ========================================================================
|
||
// Preset Parameters
|
||
// ========================================================================
|
||
|
||
/**
|
||
* @brief Get dark theme acrylic preset
|
||
*/
|
||
static AcrylicParams getDarkPreset();
|
||
|
||
/**
|
||
* @brief Get light theme acrylic preset
|
||
*/
|
||
static AcrylicParams getLightPreset();
|
||
|
||
/**
|
||
* @brief Get DragonX branded acrylic preset
|
||
*/
|
||
static AcrylicParams getDragonXPreset();
|
||
|
||
/**
|
||
* @brief Get popup/dialog acrylic preset
|
||
*/
|
||
static AcrylicParams getPopupPreset();
|
||
|
||
/**
|
||
* @brief Get sidebar acrylic preset
|
||
*/
|
||
static AcrylicParams getSidebarPreset();
|
||
|
||
private:
|
||
/**
|
||
* @brief Apply blur passes to captured content
|
||
*/
|
||
void applyBlur(float radius, bool ignoreMultiplier = false);
|
||
|
||
/**
|
||
* @brief Composite final acrylic appearance
|
||
*/
|
||
void compositeAcrylic(const ImVec2& pMin, const ImVec2& pMax,
|
||
const AcrylicParams& params);
|
||
|
||
/**
|
||
* @brief Check if effect should be skipped (settings/quality)
|
||
*/
|
||
bool shouldSkipEffect() const;
|
||
|
||
/**
|
||
* @brief Draw tinted-only fallback (no blur)
|
||
*/
|
||
void drawTintedRect(ImDrawList* drawList, const ImVec2& pMin, const ImVec2& pMax,
|
||
const AcrylicParams& params, float rounding);
|
||
|
||
bool initialized_ = false;
|
||
AcrylicSettings settings_; // All settings in one struct
|
||
|
||
// Fallback system
|
||
AcrylicCapabilities capabilities_;
|
||
AcrylicFallback currentFallback_ = AcrylicFallback::None;
|
||
AcrylicFallback forcedFallback_ = AcrylicFallback::None;
|
||
bool hasForcedFallback_ = false;
|
||
|
||
// Screen dimensions
|
||
int viewportWidth_ = 0;
|
||
int viewportHeight_ = 0;
|
||
|
||
// Noise texture config
|
||
int noiseTextureSize_ = 256; // Texture resolution (larger = less repetition)
|
||
|
||
// Caching for performance
|
||
float lastBlurRadius_ = 0.0f;
|
||
bool blurCacheValid_ = false;
|
||
int dirtyFrames_ = 2; // >0 means recapture needed; counts down each frame
|
||
bool hasValidCapture_ = false; // true once a clean BG capture exists
|
||
|
||
// Capture and blur framebuffers
|
||
Framebuffer captureBuffer_;
|
||
FramebufferPingPong blurBuffers_;
|
||
|
||
// Shaders
|
||
BlurShader blurShader_;
|
||
|
||
// Fullscreen quad for post-processing
|
||
FullscreenQuad quad_;
|
||
|
||
// Noise texture
|
||
NoiseTexture noiseTexture_;
|
||
|
||
// Composite shader (tint + noise overlay)
|
||
GLuint compositeShader_ = 0;
|
||
GLint uCompositeTintColor_ = -1;
|
||
GLint uCompositeTintOpacity_ = -1;
|
||
GLint uCompositeLuminosity_ = -1;
|
||
GLint uCompositeNoiseOpacity_ = -1;
|
||
GLint uCompositeBlurTex_ = -1;
|
||
GLint uCompositeNoiseTex_ = -1;
|
||
GLint uCompositeTexScale_ = -1;
|
||
|
||
#ifdef DRAGONX_USE_DX11
|
||
// ---- DX11 acrylic resources (used instead of GL objects above) ----
|
||
ID3D11Device* dx_device_ = nullptr;
|
||
ID3D11DeviceContext* dx_context_ = nullptr;
|
||
|
||
// Capture buffer (full viewport resolution)
|
||
ID3D11Texture2D* dx_captureTex_ = nullptr;
|
||
ID3D11ShaderResourceView* dx_captureSRV_ = nullptr;
|
||
ID3D11RenderTargetView* dx_captureRTV_ = nullptr;
|
||
|
||
// Blur ping-pong buffers (possibly downscaled)
|
||
ID3D11Texture2D* dx_blurTex_[2] = {};
|
||
ID3D11ShaderResourceView* dx_blurSRV_[2] = {};
|
||
ID3D11RenderTargetView* dx_blurRTV_[2] = {};
|
||
int dx_blurWidth_ = 0;
|
||
int dx_blurHeight_ = 0;
|
||
int dx_blurCurrent_ = 0; // which buffer is "source"
|
||
|
||
// Shaders & pipeline objects
|
||
ID3D11VertexShader* dx_blurVS_ = nullptr;
|
||
ID3D11PixelShader* dx_blurPS_ = nullptr;
|
||
ID3D11InputLayout* dx_inputLayout_ = nullptr;
|
||
ID3D11Buffer* dx_blurCB_ = nullptr;
|
||
ID3D11Buffer* dx_vertexBuf_ = nullptr;
|
||
ID3D11SamplerState* dx_sampler_ = nullptr;
|
||
ID3D11RasterizerState* dx_blurRS_ = nullptr;
|
||
ID3D11BlendState* dx_blurBS_ = nullptr;
|
||
ID3D11DepthStencilState* dx_blurDSS_ = nullptr;
|
||
|
||
// Noise texture (DX11)
|
||
ID3D11Texture2D* dx_noiseTex_ = nullptr;
|
||
ID3D11ShaderResourceView* dx_noiseSRV_ = nullptr;
|
||
|
||
// Internal helpers
|
||
bool dx_initPipeline();
|
||
void dx_releasePipeline();
|
||
void dx_createRenderTarget(ID3D11Texture2D*& tex,
|
||
ID3D11ShaderResourceView*& srv,
|
||
ID3D11RenderTargetView*& rtv,
|
||
int w, int h,
|
||
DXGI_FORMAT format);
|
||
void dx_releaseRenderTarget(ID3D11Texture2D*& tex,
|
||
ID3D11ShaderResourceView*& srv,
|
||
ID3D11RenderTargetView*& rtv);
|
||
#endif // DRAGONX_USE_DX11
|
||
};
|
||
|
||
// ============================================================================
|
||
// Global Acrylic Instance
|
||
// ============================================================================
|
||
|
||
/**
|
||
* @brief Get the global acrylic material instance
|
||
*/
|
||
AcrylicMaterial& getAcrylicMaterial();
|
||
|
||
} // namespace effects
|
||
} // namespace ui
|
||
} // namespace dragonx
|