feat(effects): overlay-time live-framebuffer blur behind the portfolio modal

The acrylic system only blurred a frozen snapshot of the static app background. Add
an overlay-time re-capture so the Manage-portfolio modal blurs the LIVE tab content
behind it.

- AcrylicMaterial::captureLiveFramebuffer() (GL blit of FB 0 / DX11 CopyResource of the
  backbuffer + no-backend stub) mirrors captureBackgroundDirect but bypasses the
  dirtyFrames_ gate so it refreshes every frame the overlay is open, forcing a re-blur.
- ImGuiAcrylic::GetLiveCaptureCallback() returns a draw callback for it.
- market_tab: the modal inserts that callback (+ ImDrawCallback_ResetRenderState) at the
  START of its overlay draw list, before the backdrop, so the capture holds the app UI
  drawn below and the backdrop blurs it. On close, InvalidateCapture() re-captures the
  background so other glass panels don't keep blurring the stale live capture.
- Panel theme-effects are suppressed during the market render while the modal is open
  (their foreground-draw-list borders draw above all windows and would otherwise bleed
  over the overlay). Market tab body is rendered again (no longer skipped).
- Backdrop draws the acrylic at full strength (fallbackColor.w=1) over an opaque base so
  the blurred content reads clearly; lighter dim.

Design mapped via an Explore investigation of the capture/blur pipeline; compiles on
both OpenGL (Linux) and DX11 (Windows cross-build). Needs on-device visual verification.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-03 20:26:11 -05:00
parent 2035f71c65
commit 0e8816e220
6 changed files with 95 additions and 9 deletions

View File

@@ -355,6 +355,20 @@ void AcrylicMaterial::captureBackgroundDirect()
blurCacheValid_ = false; // force re-blur with fresh capture
}
void AcrylicMaterial::captureLiveFramebuffer()
{
// Like captureBackgroundDirect but with NO dirtyFrames_ gate — refresh every frame the
// overlay is open so the blur tracks the live UI. Fires mid-RenderDrawData (from a callback
// inserted at the overlay window's draw-list start), so FB 0 already holds the app UI drawn
// below the overlay. A paired ImDrawCallback_ResetRenderState restores ImGui's GL state.
if (!initialized_ || shouldSkipEffect()) return;
if (viewportWidth_ <= 0 || viewportHeight_ <= 0) return;
glDisable(GL_SCISSOR_TEST);
captureBuffer_.captureScreen(viewportWidth_, viewportHeight_);
hasValidCapture_ = true;
blurCacheValid_ = false; // force re-blur with the fresh live capture
}
bool AcrylicMaterial::shouldSkipEffect() const
{
return !settings_.enabled ||
@@ -1238,6 +1252,26 @@ void AcrylicMaterial::captureBackgroundDirect()
blurCacheValid_ = false;
}
void AcrylicMaterial::captureLiveFramebuffer()
{
// DX11 twin of the GL live capture: copy the currently-bound backbuffer (which holds the app
// UI drawn below the overlay) into the capture texture, with NO dirtyFrames_ gate.
if (!initialized_ || shouldSkipEffect()) return;
if (viewportWidth_ <= 0 || viewportHeight_ <= 0) return;
if (!dx_captureTex_ || !dx_context_) return;
ID3D11RenderTargetView* curRTV = nullptr;
dx_context_->OMGetRenderTargets(1, &curRTV, nullptr);
if (!curRTV) return;
ID3D11Resource* bbRes = nullptr;
curRTV->GetResource(&bbRes);
curRTV->Release();
if (!bbRes) return;
dx_context_->CopyResource(dx_captureTex_, bbRes);
bbRes->Release();
hasValidCapture_ = true;
blurCacheValid_ = false;
}
void AcrylicMaterial::applyBlur(float radius)
{
if (!dx_blurSRV_[0] || !dx_blurPS_ || !dx_context_) return;
@@ -1666,6 +1700,7 @@ void AcrylicMaterial::shutdown() {}
void AcrylicMaterial::resize(int, int) {}
void AcrylicMaterial::captureBackground() {}
void AcrylicMaterial::captureBackgroundDirect() {}
void AcrylicMaterial::captureLiveFramebuffer() {}
void AcrylicMaterial::applyBlur(float) {}
ImTextureID AcrylicMaterial::getBlurredTexture() const { return 0; }
ImTextureID AcrylicMaterial::getNoiseTexture() const { return 0; }

View File

@@ -166,6 +166,16 @@ public:
*/
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.
*

View File

@@ -236,6 +236,17 @@ ImDrawCallback GetBackgroundCaptureCallback()
return BackgroundCaptureCallback;
}
static void LiveCaptureCallback(const ImDrawList*, const ImDrawCmd*)
{
if (!s_initialized) return;
getAcrylicMaterial().captureLiveFramebuffer();
}
ImDrawCallback GetLiveCaptureCallback()
{
return LiveCaptureCallback;
}
// ============================================================================
// Drawing Functions
// ============================================================================
@@ -657,6 +668,7 @@ void BeginFrame(int, int) {}
void CaptureBackground() {}
void InvalidateCapture() {}
ImDrawCallback GetBackgroundCaptureCallback() { return nullptr; }
ImDrawCallback GetLiveCaptureCallback() { return nullptr; }
void DrawAcrylicRect(ImDrawList* drawList,
const ImVec2& pMin, const ImVec2& pMax,

View File

@@ -89,6 +89,19 @@ void InvalidateCapture();
*/
ImDrawCallback GetBackgroundCaptureCallback();
/**
* @brief Returns a draw callback that captures the LIVE framebuffer (no dirtyFrames_
* gate). Insert it at the START of a full-window modal overlay's draw list — before the
* overlay draws its own backdrop — so the acrylic blurs the live app UI behind the modal:
* @code
* ImDrawList* dl = ImGui::GetWindowDrawList();
* dl->AddCallback(ImGuiAcrylic::GetLiveCaptureCallback(), nullptr);
* dl->AddCallback(ImDrawCallback_ResetRenderState, nullptr);
* // ... then draw the overlay's acrylic/dim backdrop ...
* @endcode
*/
ImDrawCallback GetLiveCaptureCallback();
// ============================================================================
// Drawing Functions
// ============================================================================