Files
ObsidianDragon/src/ui/effects/acrylic.h
DanS 156735eb06 feat(modals): fixed modal-backdrop blur, independent of the acrylic slider
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>
2026-07-12 07:41:53 -05:00

472 lines
15 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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.31.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.31.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