diff options
author | Noel Grandin <noelgrandin@gmail.com> | 2025-03-13 18:37:54 +0200 |
---|---|---|
committer | Noel Grandin <noel.grandin@collabora.co.uk> | 2025-03-17 07:15:15 +0100 |
commit | 8a17b7f0a679ebf21bcfb425186b205d996d129b (patch) | |
tree | ab0b3f405b3a14ba51496b1ffbe6ef7106784883 /drawinglayer | |
parent | f382c1a267a93539f3c169530780889336d20b25 (diff) |
tdf#131595 Improve drawinglayer flushing mechanism.
Which dramatically speeds up switching between sheets in
the referenced document.
The problem here is that we have a timer for each buffered primitive.
And we have a lot of primitives here.
And the TimerManager does not scale well to lots and lots of timer,
because it uses a linked list.
So this change modifies the flushing mechanism, trading off some
precision for some speed.
Change-Id: I66a744f06af9b08d4e9b797d11db8d22f4060789
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/182876
Tested-by: Jenkins
Reviewed-by: Noel Grandin <noel.grandin@collabora.co.uk>
Diffstat (limited to 'drawinglayer')
11 files changed, 202 insertions, 199 deletions
diff --git a/drawinglayer/Library_drawinglayer.mk b/drawinglayer/Library_drawinglayer.mk index 14e40870d7f1..6997bd22457e 100644 --- a/drawinglayer/Library_drawinglayer.mk +++ b/drawinglayer/Library_drawinglayer.mk @@ -109,7 +109,6 @@ $(eval $(call gb_Library_add_exception_objects,drawinglayer,\ drawinglayer/source/primitive2d/bitmapprimitive2d \ drawinglayer/source/primitive2d/BitmapAlphaPrimitive2D \ drawinglayer/source/primitive2d/borderlineprimitive2d \ - drawinglayer/source/primitive2d/BufferedDecompositionGroupPrimitive2D \ drawinglayer/source/primitive2d/controlprimitive2d \ drawinglayer/source/primitive2d/cropprimitive2d \ drawinglayer/source/primitive2d/discretebitmapprimitive2d \ @@ -125,7 +124,6 @@ $(eval $(call gb_Library_add_exception_objects,drawinglayer,\ drawinglayer/source/primitive2d/graphicprimitivehelper2d \ drawinglayer/source/primitive2d/graphicprimitive2d \ drawinglayer/source/primitive2d/gridprimitive2d \ - drawinglayer/source/primitive2d/groupprimitive2d \ drawinglayer/source/primitive2d/helplineprimitive2d \ drawinglayer/source/primitive2d/hiddengeometryprimitive2d \ drawinglayer/source/primitive2d/invertprimitive2d \ diff --git a/drawinglayer/Library_drawinglayercore.mk b/drawinglayer/Library_drawinglayercore.mk index 7cff1a4b70bd..83d2c0faab6d 100644 --- a/drawinglayer/Library_drawinglayercore.mk +++ b/drawinglayer/Library_drawinglayercore.mk @@ -46,7 +46,10 @@ $(eval $(call gb_Library_use_custom_headers,drawinglayercore,\ $(eval $(call gb_Library_add_exception_objects,drawinglayercore,\ drawinglayer/source/primitive2d/baseprimitive2d \ drawinglayer/source/primitive2d/BufferedDecompositionPrimitive2D \ + drawinglayer/source/primitive2d/BufferedDecompositionFlusher \ + drawinglayer/source/primitive2d/BufferedDecompositionGroupPrimitive2D \ drawinglayer/source/primitive2d/Primitive2DContainer \ + drawinglayer/source/primitive2d/groupprimitive2d \ drawinglayer/source/primitive2d/Tools \ drawinglayer/source/geometry/viewinformation2d \ )) diff --git a/drawinglayer/source/primitive2d/BufferedDecompositionFlusher.cxx b/drawinglayer/source/primitive2d/BufferedDecompositionFlusher.cxx new file mode 100644 index 000000000000..6bc9312a6f7f --- /dev/null +++ b/drawinglayer/source/primitive2d/BufferedDecompositionFlusher.cxx @@ -0,0 +1,119 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <drawinglayer/primitive2d/BufferedDecompositionFlusher.hxx> +#include <tools/lazydelete.hxx> + +namespace drawinglayer::primitive2d +{ +/** + This is a "garbage collection" approach to flushing. + + We store entries in a set. Every 2 seconds, we scan the set for entries that have not + been used for 10 seconds or more, and if so, we flush the buffer primitives in those entries. + + This mechanism is __deliberately__ not perfect. + Sometimes things will be flushed a little too soon, sometimes things will wait a little too long, + since we only have a granularity of 2 seconds. + But what is gains from not being perfect, is scalability. + + It is very simple, scales to lots and lots of primitives without needing lots of timers, and performs + very little work in the common case. +*/ + +static BufferedDecompositionFlusher* getInstance() +{ + static tools::DeleteRtlReferenceOnDeinit<BufferedDecompositionFlusher> gaTimer( + new BufferedDecompositionFlusher); + return gaTimer.get().get(); +} + +// static +void BufferedDecompositionFlusher::update(const BufferedDecompositionPrimitive2D* p) +{ + if (auto flusher = getInstance()) + flusher->updateImpl(p); +} + +// static +void BufferedDecompositionFlusher::update(const BufferedDecompositionGroupPrimitive2D* p) +{ + if (auto flusher = getInstance()) + flusher->updateImpl(p); +} + +BufferedDecompositionFlusher::BufferedDecompositionFlusher() +{ + setRemainingTime(salhelper::TTimeValue(2, 0)); + start(); +} + +void BufferedDecompositionFlusher::updateImpl(const BufferedDecompositionPrimitive2D* p) +{ + std::unique_lock l(maMutex); + maRegistered1.insert(const_cast<BufferedDecompositionPrimitive2D*>(p)); +} + +void BufferedDecompositionFlusher::updateImpl(const BufferedDecompositionGroupPrimitive2D* p) +{ + std::unique_lock l(maMutex); + maRegistered2.insert(const_cast<BufferedDecompositionGroupPrimitive2D*>(p)); +} + +void SAL_CALL BufferedDecompositionFlusher::onShot() +{ + auto aNow = std::chrono::steady_clock::now(); + std::vector<rtl::Reference<BufferedDecompositionPrimitive2D>> aRemoved1; + std::vector<rtl::Reference<BufferedDecompositionGroupPrimitive2D>> aRemoved2; + { + std::unique_lock l(maMutex); + for (auto it = maRegistered1.begin(); it != maRegistered1.end();) + { + if (aNow - (*it)->maLastAccess > std::chrono::seconds(10)) + { + aRemoved1.push_back(*it); + it = maRegistered1.erase(it); + } + else + ++it; + } + for (auto it = maRegistered2.begin(); it != maRegistered2.end();) + { + if (aNow - (*it)->maLastAccess > std::chrono::seconds(10)) + { + aRemoved2.push_back(*it); + it = maRegistered2.erase(it); + } + else + ++it; + } + } + for (const auto& r : aRemoved1) + r->setBuffered2DDecomposition(nullptr); + for (const auto& r : aRemoved2) + r->setBuffered2DDecomposition(Primitive2DContainer{}); + setRemainingTime(salhelper::TTimeValue(2, 0)); + start(); +} + +} // end of namespace drawinglayer::primitive2d + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/BufferedDecompositionGroupPrimitive2D.cxx b/drawinglayer/source/primitive2d/BufferedDecompositionGroupPrimitive2D.cxx index fcdbe8d02d78..304228c1ca81 100644 --- a/drawinglayer/source/primitive2d/BufferedDecompositionGroupPrimitive2D.cxx +++ b/drawinglayer/source/primitive2d/BufferedDecompositionGroupPrimitive2D.cxx @@ -21,140 +21,80 @@ #include <drawinglayer/primitive2d/BufferedDecompositionGroupPrimitive2D.hxx> #include <drawinglayer/geometry/viewinformation2d.hxx> - -namespace -{ -class LocalCallbackTimer : public salhelper::Timer -{ -protected: - drawinglayer::primitive2d::BufferedDecompositionGroupPrimitive2D* pCustomer; - -public: - explicit LocalCallbackTimer( - drawinglayer::primitive2d::BufferedDecompositionGroupPrimitive2D& rCustomer) - : pCustomer(&rCustomer) - { - } - - void clearCallback() { pCustomer = nullptr; } - -protected: - virtual void SAL_CALL onShot() override; -}; - -void SAL_CALL LocalCallbackTimer::onShot() -{ - if (nullptr != pCustomer) - flushBufferedDecomposition(*pCustomer); -} -} +#include <drawinglayer/primitive2d/BufferedDecompositionFlusher.hxx> namespace drawinglayer::primitive2d { -void flushBufferedDecomposition(BufferedDecompositionGroupPrimitive2D& rTarget) -{ - rTarget.acquire(); - rTarget.setBuffered2DDecomposition(Primitive2DContainer()); - rTarget.release(); -} - bool BufferedDecompositionGroupPrimitive2D::hasBuffered2DDecomposition() const { - if (0 != maCallbackSeconds) - { - std::lock_guard Guard(maCallbackLock); + if (!mbFlushOnTimer) return !maBuffered2DDecomposition.empty(); - } else { + std::lock_guard Guard(maCallbackLock); return !maBuffered2DDecomposition.empty(); } } void BufferedDecompositionGroupPrimitive2D::setBuffered2DDecomposition(Primitive2DContainer&& rNew) { - if (0 == maCallbackSeconds) + if (!mbFlushOnTimer) { // no flush used, just set maBuffered2DDecomposition = std::move(rNew); - return; } - - if (maCallbackTimer.is()) - { - if (rNew.empty()) - { - // stop timer - maCallbackTimer->stop(); - } - else - { - // decomposition changed, touch - maCallbackTimer->setRemainingTime(salhelper::TTimeValue(maCallbackSeconds, 0)); - if (!maCallbackTimer->isTicking()) - maCallbackTimer->start(); - } - } - else if (!rNew.empty()) + else { - // decomposition defined/set/changed, init & start timer - maCallbackTimer.set(new LocalCallbackTimer(*this)); - maCallbackTimer->setRemainingTime(salhelper::TTimeValue(maCallbackSeconds, 0)); - maCallbackTimer->start(); - } + // decomposition changed, touch + maLastAccess = std::chrono::steady_clock::now(); + BufferedDecompositionFlusher::update(this); - // tdf#158913 need to secure change when flush/multithreading is in use - std::lock_guard Guard(maCallbackLock); - maBuffered2DDecomposition = std::move(rNew); + // tdf#158913 need to secure change when flush/multithreading is in use + std::lock_guard Guard(maCallbackLock); + maBuffered2DDecomposition = std::move(rNew); + } } BufferedDecompositionGroupPrimitive2D::BufferedDecompositionGroupPrimitive2D( Primitive2DContainer&& aChildren) : GroupPrimitive2D(std::move(aChildren)) - , maCallbackTimer() , maCallbackLock() - , maCallbackSeconds(0) + , mbFlushOnTimer(false) { } -BufferedDecompositionGroupPrimitive2D::~BufferedDecompositionGroupPrimitive2D() -{ - if (maCallbackTimer.is()) - { - // no more decomposition, end callback - static_cast<LocalCallbackTimer*>(maCallbackTimer.get())->clearCallback(); - maCallbackTimer->stop(); - } -} +BufferedDecompositionGroupPrimitive2D::~BufferedDecompositionGroupPrimitive2D() {} void BufferedDecompositionGroupPrimitive2D::get2DDecomposition( Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const { - if (!hasBuffered2DDecomposition()) - { - Primitive2DContainer aNewSequence; - create2DDecomposition(aNewSequence, rViewInformation); - const_cast<BufferedDecompositionGroupPrimitive2D*>(this)->setBuffered2DDecomposition( - std::move(aNewSequence)); - } - - if (0 == maCallbackSeconds) + if (!mbFlushOnTimer) { // no flush/multithreading is in use, just call + if (maBuffered2DDecomposition.empty()) + create2DDecomposition(maBuffered2DDecomposition, rViewInformation); rVisitor.visit(maBuffered2DDecomposition); - return; } - - // decomposition was used, touch/restart time - if (maCallbackTimer) - maCallbackTimer->setRemainingTime(salhelper::TTimeValue(maCallbackSeconds, 0)); - - // tdf#158913 need to secure 'visit' when flush/multithreading is in use, - // so that the local non-ref-Counted instance of the decomposition gets not - // manipulated (e.g. deleted) - std::lock_guard Guard(maCallbackLock); - rVisitor.visit(maBuffered2DDecomposition); + else + { + // tdf#158913 need to secure 'visit' when flush/multithreading is in use, + // so that the local non-ref-Counted instance of the decomposition gets not + // manipulated (e.g. deleted) + Primitive2DContainer xTmp; + { + // only hold the lock for long enough to get a valid reference + std::lock_guard Guard(maCallbackLock); + maLastAccess = std::chrono::steady_clock::now(); + if (maBuffered2DDecomposition.empty()) + { + create2DDecomposition(maBuffered2DDecomposition, rViewInformation); + BufferedDecompositionFlusher::update(this); + } + xTmp = maBuffered2DDecomposition; + } + rVisitor.visit(xTmp); + } } } // end of namespace drawinglayer::primitive2d diff --git a/drawinglayer/source/primitive2d/BufferedDecompositionPrimitive2D.cxx b/drawinglayer/source/primitive2d/BufferedDecompositionPrimitive2D.cxx index b04905744b50..f9e476021e44 100644 --- a/drawinglayer/source/primitive2d/BufferedDecompositionPrimitive2D.cxx +++ b/drawinglayer/source/primitive2d/BufferedDecompositionPrimitive2D.cxx @@ -21,136 +21,79 @@ #include <drawinglayer/primitive2d/BufferedDecompositionPrimitive2D.hxx> #include <drawinglayer/geometry/viewinformation2d.hxx> - -namespace -{ -class LocalCallbackTimer : public salhelper::Timer -{ -protected: - drawinglayer::primitive2d::BufferedDecompositionPrimitive2D* pCustomer; - -public: - explicit LocalCallbackTimer( - drawinglayer::primitive2d::BufferedDecompositionPrimitive2D& rCustomer) - : pCustomer(&rCustomer) - { - } - - void clearCallback() { pCustomer = nullptr; } - -protected: - virtual void SAL_CALL onShot() override; -}; - -void SAL_CALL LocalCallbackTimer::onShot() -{ - if (nullptr != pCustomer) - flushBufferedDecomposition(*pCustomer); -} -} +#include <drawinglayer/primitive2d/BufferedDecompositionFlusher.hxx> namespace drawinglayer::primitive2d { -void flushBufferedDecomposition(BufferedDecompositionPrimitive2D& rTarget) -{ - rTarget.acquire(); - rTarget.setBuffered2DDecomposition(nullptr); - rTarget.release(); -} - bool BufferedDecompositionPrimitive2D::hasBuffered2DDecomposition() const { - if (0 != maCallbackSeconds) + if (!mbFlushOnTimer) + return maBuffered2DDecomposition.is(); + else { std::lock_guard Guard(maCallbackLock); return maBuffered2DDecomposition.is(); } - else - return maBuffered2DDecomposition.is(); } void BufferedDecompositionPrimitive2D::setBuffered2DDecomposition(Primitive2DReference rNew) { - if (0 == maCallbackSeconds) + if (!mbFlushOnTimer) { // no flush used, just set maBuffered2DDecomposition = std::move(rNew); - return; } - - if (maCallbackTimer.is()) - { - if (!rNew) - { - // stop timer - maCallbackTimer->stop(); - } - else - { - // decomposition changed, touch - maCallbackTimer->setRemainingTime(salhelper::TTimeValue(maCallbackSeconds, 0)); - if (!maCallbackTimer->isTicking()) - maCallbackTimer->start(); - } - } - else if (rNew) + else { - // decomposition defined/set/changed, init & start timer - maCallbackTimer.set(new LocalCallbackTimer(*this)); - maCallbackTimer->setRemainingTime(salhelper::TTimeValue(maCallbackSeconds, 0)); - maCallbackTimer->start(); - } + // decomposition changed, touch + maLastAccess = std::chrono::steady_clock::now(); + BufferedDecompositionFlusher::update(this); - // tdf#158913 need to secure change when flush/multithreading is in use - std::lock_guard Guard(maCallbackLock); - maBuffered2DDecomposition = std::move(rNew); + // tdf#158913 need to secure change when flush/multithreading is in use + std::lock_guard Guard(maCallbackLock); + maBuffered2DDecomposition = std::move(rNew); + } } BufferedDecompositionPrimitive2D::BufferedDecompositionPrimitive2D() : maBuffered2DDecomposition() - , maCallbackTimer() , maCallbackLock() - , maCallbackSeconds(0) + , mbFlushOnTimer(false) { } -BufferedDecompositionPrimitive2D::~BufferedDecompositionPrimitive2D() -{ - if (maCallbackTimer.is()) - { - // no more decomposition, end callback - static_cast<LocalCallbackTimer*>(maCallbackTimer.get())->clearCallback(); - maCallbackTimer->stop(); - } -} +BufferedDecompositionPrimitive2D::~BufferedDecompositionPrimitive2D() {} void BufferedDecompositionPrimitive2D::get2DDecomposition( Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const { - if (!hasBuffered2DDecomposition()) - { - Primitive2DReference aNew = create2DDecomposition(rViewInformation); - const_cast<BufferedDecompositionPrimitive2D*>(this)->setBuffered2DDecomposition( - std::move(aNew)); - } - - if (0 == maCallbackSeconds) + if (!mbFlushOnTimer) { // no flush/multithreading is in use, just call + if (!maBuffered2DDecomposition) + maBuffered2DDecomposition = create2DDecomposition(rViewInformation); rVisitor.visit(maBuffered2DDecomposition); - return; } - - // decomposition was used, touch/restart time - if (maCallbackTimer) - maCallbackTimer->setRemainingTime(salhelper::TTimeValue(maCallbackSeconds, 0)); - - // tdf#158913 need to secure 'visit' when flush/multithreading is in use, - // so that the local non-ref-Counted instance of the decomposition gets not - // manipulated (e.g. deleted) - std::lock_guard Guard(maCallbackLock); - rVisitor.visit(maBuffered2DDecomposition); + else + { + // tdf#158913 need to secure 'visit' when flush/multithreading is in use, + // so that the local non-ref-Counted instance of the decomposition gets not + // manipulated (e.g. deleted) + Primitive2DReference xTmp; + { + // only hold the lock for long enough to get a valid reference + std::lock_guard Guard(maCallbackLock); + maLastAccess = std::chrono::steady_clock::now(); + if (!maBuffered2DDecomposition) + { + maBuffered2DDecomposition = create2DDecomposition(rViewInformation); + BufferedDecompositionFlusher::update(this); + } + xTmp = maBuffered2DDecomposition; + } + rVisitor.visit(xTmp); + } } } // end of namespace drawinglayer::primitive2d diff --git a/drawinglayer/source/primitive2d/glowprimitive2d.cxx b/drawinglayer/source/primitive2d/glowprimitive2d.cxx index ed212ae4da6d..33a3d59fcc6a 100644 --- a/drawinglayer/source/primitive2d/glowprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/glowprimitive2d.cxx @@ -44,7 +44,7 @@ GlowPrimitive2D::GlowPrimitive2D(const Color& rGlowColor, double fRadius, , maLastClippedRange() { // activate callback to flush buffered decomposition content - setCallbackSeconds(15); + activateFlushOnTimer(); } bool GlowPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const diff --git a/drawinglayer/source/primitive2d/graphicprimitive2d.cxx b/drawinglayer/source/primitive2d/graphicprimitive2d.cxx index 457bd64c95c6..0a5a5060b0cb 100644 --- a/drawinglayer/source/primitive2d/graphicprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/graphicprimitive2d.cxx @@ -177,7 +177,7 @@ GraphicPrimitive2D::GraphicPrimitive2D(basegfx::B2DHomMatrix aTransform, , maGraphicAttr(rGraphicAttr) { // activate callback to flush buffered decomposition content - setCallbackSeconds(20); + activateFlushOnTimer(); } GraphicPrimitive2D::GraphicPrimitive2D(basegfx::B2DHomMatrix aTransform, @@ -186,7 +186,7 @@ GraphicPrimitive2D::GraphicPrimitive2D(basegfx::B2DHomMatrix aTransform, , maGraphicObject(rGraphicObject) { // activate callback to flush buffered decomposition content - setCallbackSeconds(20); + activateFlushOnTimer(); } bool GraphicPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const diff --git a/drawinglayer/source/primitive2d/metafileprimitive2d.cxx b/drawinglayer/source/primitive2d/metafileprimitive2d.cxx index 46ddf6582571..a0a1550d1b66 100644 --- a/drawinglayer/source/primitive2d/metafileprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/metafileprimitive2d.cxx @@ -91,7 +91,7 @@ namespace drawinglayer::primitive2d maMetaFile(rMetaFile) { // activate callback to flush buffered decomposition content - setCallbackSeconds(20); + activateFlushOnTimer(); } bool MetafilePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const diff --git a/drawinglayer/source/primitive2d/sceneprimitive2d.cxx b/drawinglayer/source/primitive2d/sceneprimitive2d.cxx index 5300e8d82e05..02c51b01af53 100644 --- a/drawinglayer/source/primitive2d/sceneprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/sceneprimitive2d.cxx @@ -633,7 +633,7 @@ namespace drawinglayer::primitive2d mfOldDiscreteSizeY(0.0) { // activate callback to flush buffered decomposition content - setCallbackSeconds(45); + activateFlushOnTimer(); } bool ScenePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const diff --git a/drawinglayer/source/primitive2d/shadowprimitive2d.cxx b/drawinglayer/source/primitive2d/shadowprimitive2d.cxx index ff5600d1f5cf..3b932b92f8c5 100644 --- a/drawinglayer/source/primitive2d/shadowprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/shadowprimitive2d.cxx @@ -51,7 +51,7 @@ ShadowPrimitive2D::ShadowPrimitive2D(basegfx::B2DHomMatrix aShadowTransform, , maLastClippedRange() { // activate callback to flush buffered decomposition content - setCallbackSeconds(15); + activateFlushOnTimer(); } bool ShadowPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const diff --git a/drawinglayer/source/primitive2d/softedgeprimitive2d.cxx b/drawinglayer/source/primitive2d/softedgeprimitive2d.cxx index df05771f27a6..ed0348e13465 100644 --- a/drawinglayer/source/primitive2d/softedgeprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/softedgeprimitive2d.cxx @@ -40,7 +40,7 @@ SoftEdgePrimitive2D::SoftEdgePrimitive2D(double fRadius, Primitive2DContainer&& , maLastClippedRange() { // activate callback to flush buffered decomposition content - setCallbackSeconds(15); + activateFlushOnTimer(); } bool SoftEdgePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const |