perf(market): capture the portfolio backdrop once on open (+resize), not every frame

Switch the live-blur behind the modal from a per-frame capture (which flickered from
per-frame re-blur non-determinism) to capture-once-on-open, re-captured only on resize.

- Insert the acrylic live-capture callback into the overlay draw list ONLY for a few
  frames on open and whenever the viewport size changes; other frames reuse the frozen
  blurred snapshot (blurCacheValid_), so there's no per-frame capture/re-blur cost. State:
  s_pf_was_open (reset on close), s_pf_capture_frames, s_pf_capture_w/h.

Two fixes from an adversarial review of the timing/pipeline interaction:
- Do NOT override the backdrop blurRadius to 40. applyBlur caches one blurred buffer
  keyed on radius, shared with every glass panel (market cards + the modal preview card,
  radius ~30). A different radius thrashed that cache every frame and made the backdrop
  sample the wrong blur, defeating the frozen-blur guarantee. Using the theme card's
  radius keeps one blur serving all surfaces, frozen.
- Skip the blur on the overlay's first frame (allowBlur=false) — the live content isn't
  captured until that frame's render, so blurring then would show stale/background pixels
  for one frame; draw the plain opaque scrim instead.

Compiles on OpenGL (Linux) + DX11 (Windows). 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 21:05:16 -05:00
parent 5afb339ba7
commit 4dc6b2ef8f
2 changed files with 39 additions and 11 deletions

View File

@@ -482,13 +482,19 @@ inline void DrawGlassPanel(ImDrawList* dl, const ImVec2& pMin,
// is active and we are NOT in low-performance mode — draws a strong blur of the (static) window
// backdrop on top plus a dim. In low-perf mode only the opaque base is drawn (no blur cost),
// mirroring DrawGlassPanel's gating.
inline void DrawFullWindowBlurBackdrop(ImDrawList* dl, const ImVec2& pMin, const ImVec2& pMax)
// `allowBlur=false` draws only the opaque base (used on the overlay's first frame, before the live
// content has been captured, to avoid a one-frame blur of stale/background pixels).
inline void DrawFullWindowBlurBackdrop(ImDrawList* dl, const ImVec2& pMin, const ImVec2& pMax,
bool allowBlur = true)
{
dl->AddRectFilled(pMin, pMax, IM_COL32(9, 10, 16, 255)); // opaque base (shows through if the blur is faint)
if (IsBackdropActive() && !dragonx::ui::effects::isLowSpecMode()
if (allowBlur && IsBackdropActive() && !dragonx::ui::effects::isLowSpecMode()
&& effects::ImGuiAcrylic::IsEnabled() && effects::ImGuiAcrylic::IsAvailable()) {
// Keep the theme card's blurRadius (do NOT override it): applyBlur caches a single blurred
// buffer keyed on radius, shared with every glass panel drawn this frame (market cards + the
// modal preview card). A different radius here would thrash that cache every frame and make
// the backdrop sample the wrong blur. Same radius => one blur serves all, and it stays frozen.
auto params = GetCurrentAcrylicTheme().card;
params.blurRadius = 40.0f; // strong — spans the whole window
params.fallbackColor.w = 1.0f; // full-strength blur so the live content reads
effects::ImGuiAcrylic::DrawAcrylicRect(dl, pMin, pMax, params, 0.0f);
dl->AddRectFilled(pMin, pMax, IM_COL32(6, 8, 18, 70)); // slight dim so the card reads as foreground