diff options
| author | Vaxry <vaxry@vaxry.net> | 2026-04-04 14:40:28 +0100 |
|---|---|---|
| committer | Vaxry <43317083+vaxerski@users.noreply.github.com> | 2026-04-04 11:53:49 -0400 |
| commit | a151c671659e57ce7367f0a8fc2c71dacb4508fd (patch) | |
| tree | 339a719e882d85e27d7771b4c2cf6ff921499d17 /src/managers/animation/AnimationManager.cpp | |
| parent | 13fec3de9b40dbd075ce0ec5c88ef86117e25443 (diff) | |
animation: avoid redundant damage calls in tick
animated variables would independently damage, that's redundant.
Diffstat (limited to 'src/managers/animation/AnimationManager.cpp')
| -rw-r--r-- | src/managers/animation/AnimationManager.cpp | 336 |
1 files changed, 204 insertions, 132 deletions
diff --git a/src/managers/animation/AnimationManager.cpp b/src/managers/animation/AnimationManager.cpp index 5c52a7a5..85d6197e 100644 --- a/src/managers/animation/AnimationManager.cpp +++ b/src/managers/animation/AnimationManager.cpp @@ -66,74 +66,77 @@ static void updateColorVariable(CAnimatedVariable<CHyprColor>& av, const float P av.value() = {lerped, lerp(av.begun().a, av.goal().a, POINTY)}; } -template <Animable VarType> -static void handleUpdate(CAnimatedVariable<VarType>& av, bool warp) { - PHLWINDOW PWINDOW = av.m_Context.pWindow.lock(); - PHLWORKSPACE PWORKSPACE = av.m_Context.pWorkspace.lock(); - PHLLS PLAYER = av.m_Context.pLayer.lock(); - PHLMONITOR PMONITOR = nullptr; - bool animationsDisabled = warp; - - if (PWINDOW) { - if (av.m_Context.eDamagePolicy == AVARDAMAGE_ENTIRE) - g_pHyprRenderer->damageWindow(PWINDOW); - else if (av.m_Context.eDamagePolicy == AVARDAMAGE_BORDER) { - const auto PDECO = PWINDOW->getDecorationByType(DECORATION_BORDER); +static SAnimationContext& getContext(Hyprutils::Animation::CBaseAnimatedVariable* pAV) { + switch (pAV->m_Type) { + case AVARTYPE_FLOAT: return dc<CAnimatedVariable<float>*>(pAV)->m_Context; + case AVARTYPE_VECTOR: return dc<CAnimatedVariable<Vector2D>*>(pAV)->m_Context; + case AVARTYPE_COLOR: return dc<CAnimatedVariable<CHyprColor>*>(pAV)->m_Context; + default: UNREACHABLE(); + } +} + +static void damageWindowForPolicies(PHLWINDOW pWindow, bool entire, bool border, bool shadow) { + if (entire) + g_pHyprRenderer->damageWindow(pWindow); // damageWindow already damages all decorations + else { + if (border) { + const auto PDECO = pWindow->getDecorationByType(DECORATION_BORDER); PDECO->damageEntire(); - } else if (av.m_Context.eDamagePolicy == AVARDAMAGE_SHADOW) { - const auto PDECO = PWINDOW->getDecorationByType(DECORATION_SHADOW); + } + if (shadow) { + const auto PDECO = pWindow->getDecorationByType(DECORATION_SHADOW); PDECO->damageEntire(); } + } +} - PMONITOR = PWINDOW->m_monitor.lock(); - if (!PMONITOR) - return; +static void preDamageWorkspace(PHLWORKSPACE pWorkspace, PHLMONITOR pMonitor) { + // don't damage the whole monitor on workspace change, unless it's a special workspace, because dim/blur etc + if (pWorkspace->m_isSpecialWorkspace) + g_pHyprRenderer->damageMonitor(pMonitor); + + // TODO: just make this into a damn callback already vax... + for (auto const& w : g_pCompositor->m_windows) { + if (!w->m_isMapped || w->isHidden() || w->m_workspace != pWorkspace) + continue; + + if (w->m_isFloating && !w->m_pinned) { + // still doing the full damage hack for floating because sometimes when the window + // goes through multiple monitors the last rendered frame is missing damage somehow?? + const CBox windowBoxNoOffset = w->getFullWindowBoundingBox(); + const CBox monitorBox = {pMonitor->m_position, pMonitor->m_size}; + if (windowBoxNoOffset.intersection(monitorBox) != windowBoxNoOffset) // on edges between multiple monitors + g_pHyprRenderer->damageWindow(w, true); + } - animationsDisabled = PWINDOW->m_ruleApplicator->noAnim().valueOr(animationsDisabled); - } else if (PWORKSPACE) { - PMONITOR = PWORKSPACE->m_monitor.lock(); - if (!PMONITOR) - return; + if (pWorkspace->m_isSpecialWorkspace) + g_pHyprRenderer->damageWindow(w, true); // hack for special too because it can cross multiple monitors + } - // don't damage the whole monitor on workspace change, unless it's a special workspace, because dim/blur etc - if (PWORKSPACE->m_isSpecialWorkspace) - g_pHyprRenderer->damageMonitor(PMONITOR); - - // TODO: just make this into a damn callback already vax... - for (auto const& w : g_pCompositor->m_windows) { - if (!w->m_isMapped || w->isHidden() || w->m_workspace != PWORKSPACE) - continue; - - if (w->m_isFloating && !w->m_pinned) { - // still doing the full damage hack for floating because sometimes when the window - // goes through multiple monitors the last rendered frame is missing damage somehow?? - const CBox windowBoxNoOffset = w->getFullWindowBoundingBox(); - const CBox monitorBox = {PMONITOR->m_position, PMONITOR->m_size}; - if (windowBoxNoOffset.intersection(monitorBox) != windowBoxNoOffset) // on edges between multiple monitors - g_pHyprRenderer->damageWindow(w, true); - } + // damage any workspace window that is on any monitor + for (auto const& w : g_pCompositor->m_windows) { + if (!validMapped(w) || w->m_workspace != pWorkspace || w->m_pinned) + continue; - if (PWORKSPACE->m_isSpecialWorkspace) - g_pHyprRenderer->damageWindow(w, true); // hack for special too because it can cross multiple monitors - } + g_pHyprRenderer->damageWindow(w); + } +} - // damage any workspace window that is on any monitor - for (auto const& w : g_pCompositor->m_windows) { - if (!validMapped(w) || w->m_workspace != PWORKSPACE || w->m_pinned) - continue; +template <Animable VarType> +static void handleUpdate(CAnimatedVariable<VarType>& av, bool warp) { + bool animationsDisabled = warp; - g_pHyprRenderer->damageWindow(w); - } - } else if (PLAYER) { - // "some fucking layers miss 1 pixel???" -- vaxry - CBox expandBox = CBox{PLAYER->m_realPosition->value(), PLAYER->m_realSize->value()}; - expandBox.expand(5); - g_pHyprRenderer->damageBox(expandBox); - - PMONITOR = g_pCompositor->getMonitorFromVector(PLAYER->m_realPosition->goal() + PLAYER->m_realSize->goal() / 2.F); - if (!PMONITOR) + if (auto w = av.m_Context.pWindow.lock()) { + if (!w->m_monitor.lock()) + return; + animationsDisabled = w->m_ruleApplicator->noAnim().valueOr(animationsDisabled); + } else if (auto ws = av.m_Context.pWorkspace.lock()) { + if (!ws->m_monitor.lock()) return; - animationsDisabled = animationsDisabled || PLAYER->m_ruleApplicator->noanim().valueOrDefault(); + } else if (auto ls = av.m_Context.pLayer.lock()) { + if (!g_pCompositor->getMonitorFromVector(ls->m_realPosition->goal() + ls->m_realSize->goal() / 2.F)) + return; + animationsDisabled = animationsDisabled || ls->m_ruleApplicator->noanim().valueOrDefault(); } const auto SPENT = av.getPercent(); @@ -147,97 +150,166 @@ static void handleUpdate(CAnimatedVariable<VarType>& av, bool warp) { updateVariable<VarType>(av, POINTY, WARP); av.onUpdate(); +} - switch (av.m_Context.eDamagePolicy) { - case AVARDAMAGE_ENTIRE: { - if (PWINDOW) - g_pHyprRenderer->damageWindow(PWINDOW); - else if (PWORKSPACE) { - for (auto const& w : g_pCompositor->m_windows) { - if (!validMapped(w) || w->m_workspace != PWORKSPACE) - continue; +void CHyprAnimationManager::tick() { + static std::chrono::time_point lastTick = std::chrono::high_resolution_clock::now(); + m_lastTickTimeMs = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now() - lastTick).count() / 1000.0; + lastTick = std::chrono::high_resolution_clock::now(); - // damage any workspace window that is on any monitor - if (!w->m_pinned) - g_pHyprRenderer->damageWindow(w); - } - } else if (PLAYER) { - if (PLAYER->m_layer <= 1 && PMONITOR) - PMONITOR->m_blurFBDirty = true; + static auto PANIMENABLED = CConfigValue<Hyprlang::INT>("animations:enabled"); - // some fucking layers miss 1 pixel??? - CBox expandBox = CBox{PLAYER->m_realPosition->value(), PLAYER->m_realSize->value()}; - expandBox.expand(5); - g_pHyprRenderer->damageBox(expandBox); - } - break; - } - case AVARDAMAGE_BORDER: { - RASSERT(PWINDOW, "Tried to AVARDAMAGE_BORDER a non-window AVAR!"); + if (m_vActiveAnimatedVariables.empty()) { + tickDone(); + return; + } - const auto PDECO = PWINDOW->getDecorationByType(DECORATION_BORDER); - PDECO->damageEntire(); + const auto CPY = m_vActiveAnimatedVariables; + + // batch damage per owner to avoid redundant damage calls, otherwise + // we could be damaging many many times too much + struct SDamageOwner { + PHLWINDOW window; + PHLWORKSPACE workspace; + PHLLS layer; + PHLMONITOR monitor; + bool entire = false; + bool border = false; + bool shadow = false; + }; - break; - } - case AVARDAMAGE_SHADOW: { - RASSERT(PWINDOW, "Tried to AVARDAMAGE_SHADOW a non-window AVAR!"); + std::vector<SDamageOwner> owners; + owners.reserve(4); - const auto PDECO = PWINDOW->getDecorationByType(DECORATION_SHADOW); + // Collect per-owner damage policies + for (const auto& PAV : CPY) { + if (!PAV) + continue; - PDECO->damageEntire(); + const auto& ctx = getContext(PAV.get()); + + SDamageOwner* owner = nullptr; - break; + if (auto w = ctx.pWindow.lock()) { + for (auto& o : owners) { + if (o.window == w) { + owner = &o; + break; + } + } + if (!owner) { + auto monitor = w->m_monitor.lock(); + if (!monitor) + continue; + owners.emplace_back(SDamageOwner{.window = w, .monitor = monitor}); + owner = &owners.back(); + } + } else if (auto ws = ctx.pWorkspace.lock()) { + for (auto& o : owners) { + if (o.workspace == ws) { + owner = &o; + break; + } + } + if (!owner) { + auto monitor = ws->m_monitor.lock(); + if (!monitor) + continue; + owners.emplace_back(SDamageOwner{.workspace = ws, .monitor = monitor}); + owner = &owners.back(); + } + } else if (auto ls = ctx.pLayer.lock()) { + for (auto& o : owners) { + if (o.layer == ls) { + owner = &o; + break; + } + } + if (!owner) { + auto monitor = g_pCompositor->getMonitorFromVector(ls->m_realPosition->goal() + ls->m_realSize->goal() / 2.F); + if (!monitor) + continue; + owners.emplace_back(SDamageOwner{.layer = ls, .monitor = monitor}); + owner = &owners.back(); + } + } else + continue; + + switch (ctx.eDamagePolicy) { + case AVARDAMAGE_ENTIRE: owner->entire = true; break; + case AVARDAMAGE_BORDER: owner->border = true; break; + case AVARDAMAGE_SHADOW: owner->shadow = true; break; + default: break; } - default: { - break; + } + + // pre-damage each owner once (old state) + for (const auto& owner : owners) { + if (owner.window) + damageWindowForPolicies(owner.window, owner.entire, owner.border, owner.shadow); + else if (owner.workspace) + preDamageWorkspace(owner.workspace, owner.monitor); + else if (owner.layer) { + CBox expandBox = CBox{owner.layer->m_realPosition->value(), owner.layer->m_realSize->value()}; + expandBox.expand(5); + g_pHyprRenderer->damageBox(expandBox); } } - // manually schedule a frame - if (PMONITOR && !PMONITOR->inFullscreenMode()) - g_pCompositor->scheduleFrameForMonitor(PMONITOR, Aquamarine::IOutput::AQ_SCHEDULE_ANIMATION); -} + // update all variable values + for (const auto& PAV : CPY) { + if (!PAV) + continue; + + const auto LOCK = PAV.lock(); + bool warp = !*PANIMENABLED || !PAV->enabled(); + + switch (PAV->m_Type) { + case AVARTYPE_FLOAT: { + auto pTypedAV = dc<CAnimatedVariable<float>*>(PAV.get()); + RASSERT(pTypedAV, "Failed to upcast animated float"); + handleUpdate(*pTypedAV, warp); + } break; + case AVARTYPE_VECTOR: { + auto pTypedAV = dc<CAnimatedVariable<Vector2D>*>(PAV.get()); + RASSERT(pTypedAV, "Failed to upcast animated Vector2D"); + handleUpdate(*pTypedAV, warp); + } break; + case AVARTYPE_COLOR: { + auto pTypedAV = dc<CAnimatedVariable<CHyprColor>*>(PAV.get()); + RASSERT(pTypedAV, "Failed to upcast animated CHyprColor"); + handleUpdate(*pTypedAV, warp); + } break; + default: UNREACHABLE(); + } + } -void CHyprAnimationManager::tick() { - static std::chrono::time_point lastTick = std::chrono::high_resolution_clock::now(); - m_lastTickTimeMs = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now() - lastTick).count() / 1000.0; - lastTick = std::chrono::high_resolution_clock::now(); + // post-damage each owner once (new state) + schedule frames + for (const auto& owner : owners) { + if (owner.window) + damageWindowForPolicies(owner.window, owner.entire, owner.border, owner.shadow); + else if (owner.workspace) { + if (owner.entire) { + for (auto const& w : g_pCompositor->m_windows) { + if (!validMapped(w) || w->m_workspace != owner.workspace || w->m_pinned) + continue; - static auto PANIMENABLED = CConfigValue<Hyprlang::INT>("animations:enabled"); + g_pHyprRenderer->damageWindow(w); + } + } + } else if (owner.layer) { + if (owner.entire) { + if (owner.layer->m_layer <= 1) + owner.monitor->m_blurFBDirty = true; - if (!m_vActiveAnimatedVariables.empty()) { - const auto CPY = m_vActiveAnimatedVariables; - - for (const auto& PAV : CPY) { - if (!PAV) - continue; - - // lock this value while we are doing handleUpdate to avoid a UAF if an update callback destroys it - const auto LOCK = PAV.lock(); - - // for disabled anims just warp - bool warp = !*PANIMENABLED || !PAV->enabled(); - - switch (PAV->m_Type) { - case AVARTYPE_FLOAT: { - auto pTypedAV = dc<CAnimatedVariable<float>*>(PAV.get()); - RASSERT(pTypedAV, "Failed to upcast animated float"); - handleUpdate(*pTypedAV, warp); - } break; - case AVARTYPE_VECTOR: { - auto pTypedAV = dc<CAnimatedVariable<Vector2D>*>(PAV.get()); - RASSERT(pTypedAV, "Failed to upcast animated Vector2D"); - handleUpdate(*pTypedAV, warp); - } break; - case AVARTYPE_COLOR: { - auto pTypedAV = dc<CAnimatedVariable<CHyprColor>*>(PAV.get()); - RASSERT(pTypedAV, "Failed to upcast animated CHyprColor"); - handleUpdate(*pTypedAV, warp); - } break; - default: UNREACHABLE(); + CBox expandBox = CBox{owner.layer->m_realPosition->value(), owner.layer->m_realSize->value()}; + expandBox.expand(5); + g_pHyprRenderer->damageBox(expandBox); } } + + if (!owner.monitor->inFullscreenMode()) + g_pCompositor->scheduleFrameForMonitor(owner.monitor, Aquamarine::IOutput::AQ_SCHEDULE_ANIMATION); } tickDone(); |
