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:
@@ -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
|
||||
|
||||
@@ -99,6 +99,11 @@ static int s_pf_sel = -1; // selected group index in the master list; -1 = no
|
||||
static bool s_pf_sec_appearance = true; // right-pane accordion: which section is expanded
|
||||
static bool s_pf_sec_price = false;
|
||||
static bool s_pf_sec_addresses = false;
|
||||
// Backdrop live-capture: capture once on open + on viewport resize (not every frame). The blur is a
|
||||
// frozen snapshot while the modal is open — efficient and flicker-free.
|
||||
static bool s_pf_was_open = false;
|
||||
static int s_pf_capture_frames = 0; // frames left to (re)capture the live backdrop
|
||||
static float s_pf_capture_w = 0.0f, s_pf_capture_h = 0.0f; // viewport size at the last capture trigger
|
||||
static char s_pf_label[64] = {0};
|
||||
static std::vector<std::string> s_pf_addrs;
|
||||
static std::string s_pf_icon; // selected wallet-icon name ("" = none)
|
||||
@@ -396,6 +401,12 @@ static void RenderPortfolioEditor(App* app)
|
||||
material::MarkOverlayDialogActive();
|
||||
ImGuiViewport* vp = ImGui::GetMainViewport();
|
||||
ImVec2 vpPos = vp->Pos, vpSize = vp->Size;
|
||||
// Trigger a live backdrop (re)capture on OPEN and on viewport RESIZE only. A few frames are used
|
||||
// so the capture lands after the theme-effect suppression (frame after open) settles.
|
||||
bool justOpened = !s_pf_was_open;
|
||||
s_pf_was_open = true;
|
||||
if (justOpened || s_pf_capture_w != vpSize.x || s_pf_capture_h != vpSize.y) s_pf_capture_frames = 3;
|
||||
s_pf_capture_w = vpSize.x; s_pf_capture_h = vpSize.y;
|
||||
// Don't force-focus the scrim while a popup (basis/interval combo, color picker) is open, or it
|
||||
// would close the popup; the same guard gates the outside-click dismiss below.
|
||||
const bool popupOpen = ImGui::IsPopupOpen("", ImGuiPopupFlags_AnyPopupId | ImGuiPopupFlags_AnyPopupLevel);
|
||||
@@ -411,13 +422,20 @@ static void RenderPortfolioEditor(App* app)
|
||||
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse |
|
||||
ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoSavedSettings);
|
||||
ImDrawList* dl = ImGui::GetWindowDrawList();
|
||||
// Capture the live UI drawn below this overlay (then reset ImGui's render state) so the backdrop
|
||||
// blurs the live tab content, not the frozen wallpaper. Must precede the backdrop draw.
|
||||
if (ImDrawCallback liveCap = effects::ImGuiAcrylic::GetLiveCaptureCallback()) {
|
||||
dl->AddCallback(liveCap, nullptr);
|
||||
dl->AddCallback(ImDrawCallback_ResetRenderState, nullptr);
|
||||
// Only on capture frames (open/resize): capture the live UI drawn below this overlay (then reset
|
||||
// ImGui's render state) so the backdrop blurs the live tab content. Other frames reuse the frozen
|
||||
// blurred snapshot (blurCacheValid_), so there's no per-frame capture/re-blur cost or flicker.
|
||||
if (s_pf_capture_frames > 0) {
|
||||
if (ImDrawCallback liveCap = effects::ImGuiAcrylic::GetLiveCaptureCallback()) {
|
||||
dl->AddCallback(liveCap, nullptr);
|
||||
dl->AddCallback(ImDrawCallback_ResetRenderState, nullptr);
|
||||
}
|
||||
s_pf_capture_frames--;
|
||||
}
|
||||
material::DrawFullWindowBlurBackdrop(dl, vpPos, ImVec2(vpPos.x + vpSize.x, vpPos.y + vpSize.y));
|
||||
// Skip the blur on the very first frame (live content isn't captured until this frame's render) —
|
||||
// shows the plain opaque scrim for one frame instead of a blur of stale background pixels.
|
||||
material::DrawFullWindowBlurBackdrop(dl, vpPos, ImVec2(vpPos.x + vpSize.x, vpPos.y + vpSize.y),
|
||||
/*allowBlur=*/!justOpened);
|
||||
ImGui::SetCursorScreenPos(vpPos);
|
||||
ImGui::InvisibleButton("##pfInputBlocker", vpSize,
|
||||
ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight | ImGuiButtonFlags_MouseButtonMiddle);
|
||||
@@ -950,8 +968,12 @@ static void RenderPortfolioEditor(App* app)
|
||||
s_portfolio_editor_open = false;
|
||||
// Commit named edits on any close path (Esc / outside-click / Close); unnamed drafts are dropped.
|
||||
// Also invalidate the acrylic capture so the background (not the last live capture) is re-captured
|
||||
// for the other glass panels once the overlay is gone.
|
||||
if (!s_portfolio_editor_open) { commitIfNeeded(); effects::ImGuiAcrylic::InvalidateCapture(); }
|
||||
// for the other glass panels once the overlay is gone, and arm a fresh capture for the next open.
|
||||
if (!s_portfolio_editor_open) {
|
||||
commitIfNeeded();
|
||||
effects::ImGuiAcrylic::InvalidateCapture();
|
||||
s_pf_was_open = false;
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
Reference in New Issue
Block a user