diff options
author | Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk> | 2024-03-18 00:17:54 +0900 |
---|---|---|
committer | Tomaž Vajngerl <quikee@gmail.com> | 2024-03-30 15:21:10 +0100 |
commit | 324f2e135427f2f24cf7eb9a4fab4aa903329ae5 (patch) | |
tree | bdcf04a1cd47b22a7612e903f9c68aef17c1dc41 | |
parent | 174430d7a831eede078b6718d991b506d39180f1 (diff) |
vcl: change (graphic) Manager into a general memory manager
Graphic memory manager was changes so that it can work with any
object that implements a specific interface (MemoryManaged). With
this it will be possible to use other objects (that take a lot of
memory) to be managed by the manager. It is also a first step to
move memory managin responsibilities away from Graphic and move
it into the specific objects instead (BitmapEx, Animation and
VectorGraphic).
Change-Id: I7638bd89a1c9ece5c4bc95b506d2192492894ef3
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/164958
Tested-by: Jenkins
Reviewed-by: Tomaž Vajngerl <quikee@gmail.com>
-rw-r--r-- | vcl/CppunitTest_vcl_graphic_test.mk | 1 | ||||
-rw-r--r-- | vcl/inc/graphic/Manager.hxx | 67 | ||||
-rw-r--r-- | vcl/inc/graphic/MemoryManaged.hxx | 145 | ||||
-rw-r--r-- | vcl/inc/impgraph.hxx | 12 | ||||
-rw-r--r-- | vcl/qa/cppunit/GraphicMemoryTest.cxx | 310 | ||||
-rw-r--r-- | vcl/source/app/svapp.cxx | 4 | ||||
-rw-r--r-- | vcl/source/gdi/graph.cxx | 30 | ||||
-rw-r--r-- | vcl/source/gdi/impgraph.cxx | 93 | ||||
-rw-r--r-- | vcl/source/graphic/Manager.cxx | 286 |
9 files changed, 658 insertions, 290 deletions
diff --git a/vcl/CppunitTest_vcl_graphic_test.mk b/vcl/CppunitTest_vcl_graphic_test.mk index 6089512f5b5b..33e818e23f05 100644 --- a/vcl/CppunitTest_vcl_graphic_test.mk +++ b/vcl/CppunitTest_vcl_graphic_test.mk @@ -11,6 +11,7 @@ $(eval $(call gb_CppunitTest_CppunitTest,vcl_graphic_test)) $(eval $(call gb_CppunitTest_add_exception_objects,vcl_graphic_test, \ vcl/qa/cppunit/GraphicTest \ + vcl/qa/cppunit/GraphicMemoryTest \ vcl/qa/cppunit/GraphicDescriptorTest \ vcl/qa/cppunit/GraphicFormatDetectorTest \ vcl/qa/cppunit/GraphicNativeMetadataTest \ diff --git a/vcl/inc/graphic/Manager.hxx b/vcl/inc/graphic/Manager.hxx index d239f6a8b01d..0a4ee3fbc84d 100644 --- a/vcl/inc/graphic/Manager.hxx +++ b/vcl/inc/graphic/Manager.hxx @@ -11,66 +11,51 @@ #include <sal/types.h> #include <rtl/strbuf.hxx> -#include <vcl/bitmapex.hxx> -#include <vcl/animate/Animation.hxx> -#include <vcl/vectorgraphicdata.hxx> #include <vcl/timer.hxx> -#include <vcl/GraphicExternalLink.hxx> -#include <vcl/gfxlink.hxx> #include <memory> #include <mutex> #include <chrono> #include <o3tl/sorted_vector.hxx> -class ImpGraphic; - namespace vcl::graphic { -class Manager final +class MemoryManaged; + +class VCL_DLLPUBLIC MemoryManager final { private: + o3tl::sorted_vector<MemoryManaged*> maObjectList; + sal_Int64 mnTotalSize = 0; std::mutex maMutex; // instead of SolarMutex because graphics can live past vcl main - o3tl::sorted_vector<ImpGraphic*> m_pImpGraphicList; - std::chrono::seconds mnAllowedIdleTime; - bool mbSwapEnabled; - bool mbReducingGraphicMemory; - sal_Int64 mnMemoryLimit; - sal_Int64 mnUsedSize; - Timer maSwapOutTimer; - - Manager(); - - void registerGraphic(const std::shared_ptr<ImpGraphic>& rImpGraphic); - void loopGraphicsAndSwapOut(std::unique_lock<std::mutex>& rGuard, bool bDropAll); - DECL_LINK(SwapOutTimerHandler, Timer*, void); + std::chrono::seconds mnAllowedIdleTime = std::chrono::seconds(1); + bool mbSwapEnabled = true; + bool mbReducingGraphicMemory = false; + sal_Int64 mnMemoryLimit = 10'000'000; + Timer maSwapOutTimer; + sal_Int32 mnTimeout = 1'000; + sal_Int64 mnSmallFrySize = 100'000; - static sal_Int64 getGraphicSizeBytes(const ImpGraphic* pImpGraphic); - void reduceGraphicMemory(std::unique_lock<std::mutex>& rGuard, bool bDropAll = false); + DECL_LINK(ReduceMemoryTimerHandler, Timer*, void); public: - static Manager& get(); - - void dropCache(); - void dumpState(rtl::OStringBuffer& rState); + MemoryManager(); + void registerObject(MemoryManaged* pObject); + void unregisterObject(MemoryManaged* pObject); + void changeExisting(MemoryManaged* pObject, sal_Int64 nNewSize); - void swappedIn(const ImpGraphic* pImpGraphic, sal_Int64 nSizeBytes); - void swappedOut(const ImpGraphic* pImpGraphic, sal_Int64 nSizeBytes); + void swappedIn(MemoryManaged* pObject, sal_Int64 nNewSize); + void swappedOut(MemoryManaged* pObject, sal_Int64 nNewSize); - void changeExisting(const ImpGraphic* pImpGraphic, sal_Int64 nOldSize); - void unregisterGraphic(ImpGraphic* pImpGraphic); + static MemoryManager& get(); + o3tl::sorted_vector<MemoryManaged*> const& getManagedObjects() { return maObjectList; } + sal_Int64 getTotalSize() { return mnTotalSize; } - std::shared_ptr<ImpGraphic> copy(std::shared_ptr<ImpGraphic> const& pImpGraphic); - std::shared_ptr<ImpGraphic> newInstance(); - std::shared_ptr<ImpGraphic> newInstance(const BitmapEx& rBitmapEx); - std::shared_ptr<ImpGraphic> newInstance(std::shared_ptr<GfxLink> const& rLink, - sal_Int32 nPageIndex = 0); - std::shared_ptr<ImpGraphic> - newInstance(const std::shared_ptr<VectorGraphicData>& rVectorGraphicDataPtr); - std::shared_ptr<ImpGraphic> newInstance(const Animation& rAnimation); - std::shared_ptr<ImpGraphic> newInstance(const GDIMetaFile& rMtf); - std::shared_ptr<ImpGraphic> newInstance(const GraphicExternalLink& rGraphicLink); + void reduceMemory(std::unique_lock<std::mutex>& rGuard, bool bDropAll = false); + void loopAndReduceMemory(std::unique_lock<std::mutex>& rGuard, bool bDropAll = false); + void reduceAllAndNow(); + void dumpState(rtl::OStringBuffer& rState); }; } // end namespace vcl::graphic diff --git a/vcl/inc/graphic/MemoryManaged.hxx b/vcl/inc/graphic/MemoryManaged.hxx new file mode 100644 index 000000000000..d88e87979ba9 --- /dev/null +++ b/vcl/inc/graphic/MemoryManaged.hxx @@ -0,0 +1,145 @@ +/* -*- 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/. + */ + +#pragma once + +#include <sal/types.h> +#include <rtl/strbuf.hxx> +#include <vcl/timer.hxx> + +#include <memory> +#include <mutex> +#include <chrono> +#include <o3tl/sorted_vector.hxx> + +namespace vcl::graphic +{ +class VCL_DLLPUBLIC MemoryManaged +{ +private: + sal_Int64 mnCurrentSizeBytes = 0; + bool mbIsRegistered = false; + +protected: + // Copy + MemoryManaged(MemoryManaged const& rMemoryManaged) + : mbIsRegistered(rMemoryManaged.mbIsRegistered) + { + // if the original is registered we need to register as well + if (mbIsRegistered) + registerIntoManager(); + } + + // Move + MemoryManaged(MemoryManaged&& rMemoryManaged) + : mbIsRegistered(rMemoryManaged.mbIsRegistered) + { + // if the original is registered we need to register as well + if (mbIsRegistered) + registerIntoManager(); + } + + MemoryManaged(bool bRegister) + { + if (bRegister) + registerIntoManager(); + } + + virtual ~MemoryManaged() { unregisterFromManager(); } + + void updateCurrentSizeInBytes(sal_Int64 nNewSize) + { + if (mbIsRegistered) + { + auto& rManager = vcl::graphic::MemoryManager::get(); + rManager.changeExisting(this, nNewSize); + } + else + { + mnCurrentSizeBytes = nNewSize; + } + } + + bool isRegistered() const { return mbIsRegistered; } + + void changeExisting(sal_Int64 nNewSize) + { + if (mbIsRegistered) + { + auto& rManager = vcl::graphic::MemoryManager::get(); + rManager.changeExisting(this, nNewSize); + } + else + { + mnCurrentSizeBytes = nNewSize; + } + } + + void swappedIn(sal_Int64 nNewSize) + { + if (mbIsRegistered) + { + auto& rManager = vcl::graphic::MemoryManager::get(); + rManager.swappedIn(this, nNewSize); + } + else + { + mnCurrentSizeBytes = nNewSize; + } + } + + void swappedOut(sal_Int64 nNewSize) + { + if (mbIsRegistered) + { + auto& rManager = vcl::graphic::MemoryManager::get(); + rManager.swappedOut(this, nNewSize); + } + else + { + mnCurrentSizeBytes = nNewSize; + } + } + +public: + sal_Int64 getCurrentSizeInBytes() const { return mnCurrentSizeBytes; } + + void setCurrentSizeInBytes(sal_Int64 nSize) { mnCurrentSizeBytes = nSize; } + +protected: + void registerIntoManager() + { + if (!mbIsRegistered) + { + auto& rManager = vcl::graphic::MemoryManager::get(); + rManager.registerObject(this); + mbIsRegistered = true; + } + } + + void unregisterFromManager() + { + if (mbIsRegistered) + { + auto& rManager = vcl::graphic::MemoryManager::get(); + rManager.unregisterObject(this); + mbIsRegistered = false; + } + } + +public: + virtual bool canReduceMemory() const = 0; + virtual bool reduceMemory() = 0; + virtual std::chrono::high_resolution_clock::time_point getLastUsed() const = 0; + virtual void dumpState(rtl::OStringBuffer& rState) = 0; +}; + +} // end namespace vcl::graphic + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/inc/impgraph.hxx b/vcl/inc/impgraph.hxx index 707810b9de2e..35542db74641 100644 --- a/vcl/inc/impgraph.hxx +++ b/vcl/inc/impgraph.hxx @@ -24,6 +24,7 @@ #include <vcl/gdimtf.hxx> #include <vcl/graph.hxx> #include "graphic/Manager.hxx" +#include "graphic/MemoryManaged.hxx" #include "graphic/GraphicID.hxx" #include <optional> @@ -56,11 +57,10 @@ enum class GraphicContentType : sal_Int32 Vector }; -class ImpGraphic final +class ImpGraphic final : public vcl::graphic::MemoryManaged { friend class Graphic; friend class GraphicID; - friend class vcl::graphic::Manager; private: @@ -160,6 +160,8 @@ private: sal_uLong getSizeBytes() const; + void ensureCurrentSizeInBytes(); + void draw(OutputDevice& rOutDev, const Point& rDestPt) const; void draw(OutputDevice& rOutDev, const Point& rDestPt, const Size& rDestSize) const; @@ -212,6 +214,10 @@ private: // Set the pref map mode, but don't force swap-in void setValuesForPrefMapMod(const MapMode& rPrefMapMode); + bool canReduceMemory() const override; + bool reduceMemory() override; + std::chrono::high_resolution_clock::time_point getLastUsed() const override; + public: void resetChecksum() { mnChecksum = 0; } bool swapIn(); @@ -220,7 +226,7 @@ public: VCL_DLLPUBLIC SvStream* getSwapFileStream() const; // public only because of use in GraphicFilter void updateFromLoadedGraphic(const ImpGraphic* graphic); - void dumpState(rtl::OStringBuffer &rState); + void dumpState(rtl::OStringBuffer &rState) override; }; /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/qa/cppunit/GraphicMemoryTest.cxx b/vcl/qa/cppunit/GraphicMemoryTest.cxx new file mode 100644 index 000000000000..95e64e471246 --- /dev/null +++ b/vcl/qa/cppunit/GraphicMemoryTest.cxx @@ -0,0 +1,310 @@ +/* -*- 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/. + */ + +#include <test/bootstrapfixture.hxx> + +#include <com/sun/star/beans/PropertyValue.hpp> + +#include <vcl/BitmapReadAccess.hxx> +#include <vcl/graph.hxx> +#include <vcl/graphicfilter.hxx> +#include <tools/stream.hxx> + +#include <impgraph.hxx> +#include <graphic/GraphicFormatDetector.hxx> +#include <graphic/MemoryManaged.hxx> + +using namespace css; + +namespace +{ +BitmapEx createBitmap(Size aSize, bool bAlpha = false) +{ + Bitmap aBitmap(aSize, vcl::PixelFormat::N24_BPP); + aBitmap.Erase(COL_LIGHTRED); + + aBitmap.SetPrefSize(Size(aSize.Width() * 2, aSize.Height() * 3)); + aBitmap.SetPrefMapMode(MapMode(MapUnit::Map100thMM)); + + if (bAlpha) + { + sal_uInt8 uAlphaValue = 0x80; + AlphaMask aAlphaMask(aSize, &uAlphaValue); + + return BitmapEx(aBitmap, aAlphaMask); + } + else + { + return BitmapEx(aBitmap); + } +} + +void createBitmapAndExportForType(SvStream& rStream, std::u16string_view sType, + Size aSize = Size(120, 100), bool bAlpha = false) +{ + BitmapEx aBitmapEx = createBitmap(aSize, bAlpha); + + uno::Sequence<beans::PropertyValue> aFilterData; + GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter(); + sal_uInt16 nFilterFormat = rGraphicFilter.GetExportFormatNumberForShortName(sType); + rGraphicFilter.ExportGraphic(aBitmapEx, u"none", rStream, nFilterFormat, &aFilterData); + + rStream.Seek(STREAM_SEEK_TO_BEGIN); +} + +Graphic makeUnloadedGraphic(std::u16string_view sType, Size aSize = Size(120, 100), + bool bAlpha = false) +{ + SvMemoryStream aStream; + GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter(); + createBitmapAndExportForType(aStream, sType, aSize, bAlpha); + return rGraphicFilter.ImportUnloadedGraphic(aStream); +} + +constexpr OUString DATA_DIRECTORY = u"/vcl/qa/cppunit/data/"_ustr; + +Graphic loadGraphic(std::u16string_view rFilename) +{ + test::Directories aDirectories; + OUString aFilename = aDirectories.getURLFromSrc(DATA_DIRECTORY) + rFilename; + SvFileStream aFileStream(aFilename, StreamMode::READ); + GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter(); + + Graphic aGraphic; + CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, rGraphicFilter.ImportGraphic(aGraphic, u"", aFileStream, + GRFILTER_FORMAT_DONTKNOW)); + return aGraphic; +} + +Graphic loadUnloadedGraphic(std::u16string_view rFilename) +{ + test::Directories aDirectories; + OUString aFilename = aDirectories.getURLFromSrc(DATA_DIRECTORY) + rFilename; + SvFileStream aFileStream(aFilename, StreamMode::READ); + GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter(); + return rGraphicFilter.ImportUnloadedGraphic(aFileStream); +} + +} // end anonymous + +class GraphicMemoryTest : public test::BootstrapFixture +{ +}; + +CPPUNIT_TEST_FIXTURE(GraphicMemoryTest, testMemoryManager_Empty) +{ + auto& rManager = vcl::graphic::MemoryManager::get(); + CPPUNIT_ASSERT_EQUAL(size_t(0), rManager.getManagedObjects().size()); + + Graphic aGraphic1; + CPPUNIT_ASSERT_EQUAL(size_t(0), rManager.getManagedObjects().size()); + + Graphic aGraphic2; + CPPUNIT_ASSERT_EQUAL(size_t(0), rManager.getManagedObjects().size()); + + Graphic aGraphic3; + CPPUNIT_ASSERT_EQUAL(size_t(0), rManager.getManagedObjects().size()); + + aGraphic1 = aGraphic2; + CPPUNIT_ASSERT_EQUAL(size_t(0), rManager.getManagedObjects().size()); + + aGraphic2 = aGraphic3; + CPPUNIT_ASSERT_EQUAL(size_t(0), rManager.getManagedObjects().size()); +} + +CPPUNIT_TEST_FIXTURE(GraphicMemoryTest, testMemoryManager_Unloaded) +{ + auto& rManager = vcl::graphic::MemoryManager::get(); + CPPUNIT_ASSERT_EQUAL(size_t(0), rManager.getManagedObjects().size()); + + Graphic aGraphic_100 = makeUnloadedGraphic(u"jpg", Size(100, 100)); + Graphic aGraphic_200 = makeUnloadedGraphic(u"png", Size(200, 100)); + Graphic aGraphic_300 = makeUnloadedGraphic(u"jpg", Size(300, 100)); + + CPPUNIT_ASSERT_EQUAL(size_t(0), rManager.getManagedObjects().size()); + + aGraphic_100.makeAvailable(); + CPPUNIT_ASSERT_EQUAL(size_t(1), rManager.getManagedObjects().size()); + CPPUNIT_ASSERT_EQUAL(sal_Int64(30000), rManager.getTotalSize()); + CPPUNIT_ASSERT_EQUAL(sal_uLong(30000), aGraphic_100.GetSizeBytes()); + + aGraphic_200.makeAvailable(); + CPPUNIT_ASSERT_EQUAL(size_t(2), rManager.getManagedObjects().size()); + CPPUNIT_ASSERT_EQUAL(sal_Int64(90000), rManager.getTotalSize()); + CPPUNIT_ASSERT_EQUAL(sal_uLong(30000), aGraphic_100.GetSizeBytes()); + CPPUNIT_ASSERT_EQUAL(sal_uLong(60000), aGraphic_200.GetSizeBytes()); + + aGraphic_300.makeAvailable(); + CPPUNIT_ASSERT_EQUAL(size_t(3), rManager.getManagedObjects().size()); + CPPUNIT_ASSERT_EQUAL(sal_Int64(180000), rManager.getTotalSize()); + CPPUNIT_ASSERT_EQUAL(sal_uLong(30000), aGraphic_100.GetSizeBytes()); + CPPUNIT_ASSERT_EQUAL(sal_uLong(60000), aGraphic_200.GetSizeBytes()); + CPPUNIT_ASSERT_EQUAL(sal_uLong(90000), aGraphic_300.GetSizeBytes()); +} + +CPPUNIT_TEST_FIXTURE(GraphicMemoryTest, testMemoryManager_Destrucion) +{ + auto& rManager = vcl::graphic::MemoryManager::get(); + CPPUNIT_ASSERT_EQUAL(size_t(0), rManager.getManagedObjects().size()); + + Graphic aGraphic_100 = makeUnloadedGraphic(u"jpg", Size(100, 100)); + Graphic aGraphic_200 = makeUnloadedGraphic(u"png", Size(200, 100)); + Graphic aGraphic_300 = makeUnloadedGraphic(u"jpg", Size(300, 100)); + + CPPUNIT_ASSERT_EQUAL(size_t(0), rManager.getManagedObjects().size()); + + aGraphic_100.makeAvailable(); + aGraphic_200.makeAvailable(); + aGraphic_300.makeAvailable(); + CPPUNIT_ASSERT_EQUAL(size_t(3), rManager.getManagedObjects().size()); + CPPUNIT_ASSERT_EQUAL(sal_Int64(180000), rManager.getTotalSize()); + + aGraphic_200 = Graphic(); + CPPUNIT_ASSERT_EQUAL(size_t(2), rManager.getManagedObjects().size()); + CPPUNIT_ASSERT_EQUAL(sal_Int64(120000), rManager.getTotalSize()); + + aGraphic_300 = Graphic(); + CPPUNIT_ASSERT_EQUAL(size_t(1), rManager.getManagedObjects().size()); + CPPUNIT_ASSERT_EQUAL(sal_Int64(30000), rManager.getTotalSize()); +} + +CPPUNIT_TEST_FIXTURE(GraphicMemoryTest, testMemoryManager_Copy) +{ + auto& rManager = vcl::graphic::MemoryManager::get(); + CPPUNIT_ASSERT_EQUAL(size_t(0), rManager.getManagedObjects().size()); + + Graphic aGraphic_100 = makeUnloadedGraphic(u"jpg", Size(100, 100)); + Graphic aGraphic_200 = makeUnloadedGraphic(u"png", Size(200, 100)); + Graphic aGraphic_300 = makeUnloadedGraphic(u"jpg", Size(300, 100)); + + Graphic aSVG = loadUnloadedGraphic(u"SimpleExample.svg"); + + CPPUNIT_ASSERT_EQUAL(size_t(0), rManager.getManagedObjects().size()); + + aGraphic_100.makeAvailable(); + aGraphic_200.makeAvailable(); + aGraphic_300.makeAvailable(); +} + +CPPUNIT_TEST_FIXTURE(GraphicMemoryTest, testMemoryManager) +{ + auto& rManager = vcl::graphic::MemoryManager::get(); + CPPUNIT_ASSERT_EQUAL(size_t(0), rManager.getManagedObjects().size()); + + Graphic aGraphic; + CPPUNIT_ASSERT_EQUAL(size_t(0), rManager.getManagedObjects().size()); + + Graphic aGraphicPng = loadGraphic(u"TypeDetectionExample.png"); + { + CPPUNIT_ASSERT_EQUAL(GraphicType::Bitmap, aGraphicPng.GetType()); + CPPUNIT_ASSERT_EQUAL(sal_uLong(300), aGraphicPng.GetSizeBytes()); + CPPUNIT_ASSERT_EQUAL(true, aGraphicPng.isAvailable()); + } + + CPPUNIT_ASSERT_EQUAL(size_t(1), rManager.getManagedObjects().size()); + CPPUNIT_ASSERT_EQUAL(sal_Int64(300), rManager.getTotalSize()); + + CPPUNIT_ASSERT_EQUAL(false, aGraphicPng.ImplGetImpGraphic()->isSwappedOut()); + CPPUNIT_ASSERT_EQUAL(true, aGraphicPng.ImplGetImpGraphic()->swapOut()); + CPPUNIT_ASSERT_EQUAL(true, aGraphicPng.ImplGetImpGraphic()->isSwappedOut()); + CPPUNIT_ASSERT_EQUAL(false, aGraphicPng.isAvailable()); + + CPPUNIT_ASSERT_EQUAL(size_t(1), rManager.getManagedObjects().size()); + CPPUNIT_ASSERT_EQUAL(sal_Int64(0), rManager.getTotalSize()); + + Graphic aGraphicJpg = makeUnloadedGraphic(u"jpg"); + { + CPPUNIT_ASSERT_EQUAL(tools::Long(120), aGraphicJpg.GetSizePixel().Width()); + CPPUNIT_ASSERT_EQUAL(tools::Long(100), aGraphicJpg.GetSizePixel().Height()); + CPPUNIT_ASSERT_EQUAL(false, aGraphicJpg.isAvailable()); + } + + CPPUNIT_ASSERT_EQUAL(size_t(1), rManager.getManagedObjects().size()); + CPPUNIT_ASSERT_EQUAL(sal_Int64(0), rManager.getTotalSize()); + + aGraphicJpg.makeAvailable(); + + CPPUNIT_ASSERT_EQUAL(size_t(2), rManager.getManagedObjects().size()); + CPPUNIT_ASSERT_EQUAL(sal_Int64(36000), rManager.getTotalSize()); + + CPPUNIT_ASSERT_EQUAL(true, aGraphicPng.makeAvailable()); + CPPUNIT_ASSERT_EQUAL(false, aGraphicPng.ImplGetImpGraphic()->isSwappedOut()); + + CPPUNIT_ASSERT_EQUAL(size_t(2), rManager.getManagedObjects().size()); + CPPUNIT_ASSERT_EQUAL(sal_Int64(36300), rManager.getTotalSize()); + + aGraphicJpg = aGraphic; + + CPPUNIT_ASSERT_EQUAL(size_t(1), rManager.getManagedObjects().size()); + CPPUNIT_ASSERT_EQUAL(sal_Int64(300), rManager.getTotalSize()); +} + +namespace +{ +class TestManaged : public vcl::graphic::MemoryManaged +{ +public: + std::chrono::high_resolution_clock::time_point maLastUsed + = std::chrono::high_resolution_clock::now(); + + TestManaged(bool bRegister, sal_Int64 nSize = 0) + : MemoryManaged(bRegister) + { + updateCurrentSizeInBytes(nSize); + } + + void setCurrentSize(sal_Int64 nSize) { updateCurrentSizeInBytes(nSize); } + + void callRegister() { registerIntoManager(); } + + void callUnregister() { unregisterFromManager(); } + + bool canReduceMemory() const override { return false; } + + bool reduceMemory() override { return false; } + + std::chrono::high_resolution_clock::time_point getLastUsed() const override + { + return maLastUsed; + } + + void dumpState(rtl::OStringBuffer& /*rState*/) override {} +}; +} + +CPPUNIT_TEST_FIXTURE(GraphicMemoryTest, testMemoryManagerX) +{ + auto& rManager = vcl::graphic::MemoryManager::get(); + CPPUNIT_ASSERT_EQUAL(size_t(0), rManager.getManagedObjects().size()); + + TestManaged aTestManaged(false, 1000); + CPPUNIT_ASSERT_EQUAL(size_t(0), rManager.getManagedObjects().size()); + + TestManaged aTestManaged2(true, 100); + CPPUNIT_ASSERT_EQUAL(size_t(1), rManager.getManagedObjects().size()); + CPPUNIT_ASSERT_EQUAL(sal_Int64(100), rManager.getTotalSize()); + + aTestManaged2.setCurrentSize(400); + CPPUNIT_ASSERT_EQUAL(size_t(1), rManager.getManagedObjects().size()); + CPPUNIT_ASSERT_EQUAL(sal_Int64(400), rManager.getTotalSize()); + + aTestManaged.setCurrentSize(600); + CPPUNIT_ASSERT_EQUAL(size_t(1), rManager.getManagedObjects().size()); + CPPUNIT_ASSERT_EQUAL(sal_Int64(400), rManager.getTotalSize()); + + aTestManaged.callRegister(); + CPPUNIT_ASSERT_EQUAL(size_t(2), rManager.getManagedObjects().size()); + CPPUNIT_ASSERT_EQUAL(sal_Int64(1000), rManager.getTotalSize()); + + aTestManaged.callUnregister(); + CPPUNIT_ASSERT_EQUAL(size_t(1), rManager.getManagedObjects().size()); + CPPUNIT_ASSERT_EQUAL(sal_Int64(400), rManager.getTotalSize()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/app/svapp.cxx b/vcl/source/app/svapp.cxx index e0995e0793e2..3b150e1bd956 100644 --- a/vcl/source/app/svapp.cxx +++ b/vcl/source/app/svapp.cxx @@ -1775,7 +1775,7 @@ void dumpState(rtl::OStringBuffer &rState) pWin = Application::GetNextTopLevelWindow( pWin ); } - vcl::graphic::Manager::get().dumpState(rState); + vcl::graphic::MemoryManager::get().dumpState(rState); pSVData->dumpState(rState); @@ -1792,7 +1792,7 @@ void trimMemory(int nTarget) if (!pSVData) // shutting down return; pSVData->dropCaches(); - vcl::graphic::Manager::get().dropCache(); + vcl::graphic::MemoryManager::get().reduceAllAndNow(); // TODO: ideally - free up any deeper dirtied thread stacks. // comphelper::ThreadPool::getSharedOptimalPool().shutdown(); } diff --git a/vcl/source/gdi/graph.cxx b/vcl/source/gdi/graph.cxx index 252ee189b892..2c3e05235225 100644 --- a/vcl/source/gdi/graph.cxx +++ b/vcl/source/gdi/graph.cxx @@ -159,14 +159,14 @@ void ImplDrawDefault(OutputDevice& rOutDev, const OUString* pText, } // end anonymous namespace Graphic::Graphic() - : mxImpGraphic(vcl::graphic::Manager::get().newInstance()) + : mxImpGraphic(new ImpGraphic()) { } Graphic::Graphic(const Graphic& rGraphic) { if( rGraphic.IsAnimated() ) - mxImpGraphic = vcl::graphic::Manager::get().copy(rGraphic.mxImpGraphic); + mxImpGraphic = std::make_shared<ImpGraphic>(*rGraphic.mxImpGraphic); else mxImpGraphic = rGraphic.mxImpGraphic; } @@ -177,17 +177,17 @@ Graphic::Graphic(Graphic&& rGraphic) noexcept } Graphic::Graphic(std::shared_ptr<GfxLink> const & rGfxLink, sal_Int32 nPageIndex) - : mxImpGraphic(vcl::graphic::Manager::get().newInstance(rGfxLink, nPageIndex)) + : mxImpGraphic(new ImpGraphic(rGfxLink, nPageIndex)) { } Graphic::Graphic(GraphicExternalLink const & rGraphicExternalLink) - : mxImpGraphic(vcl::graphic::Manager::get().newInstance(rGraphicExternalLink)) + : mxImpGraphic(new ImpGraphic(rGraphicExternalLink)) { } -Graphic::Graphic(const BitmapEx& rBmpEx) - : mxImpGraphic(vcl::graphic::Manager::get().newInstance(rBmpEx)) +Graphic::Graphic(const BitmapEx& rBitmapEx) + : mxImpGraphic(new ImpGraphic(rBitmapEx)) { } @@ -195,7 +195,7 @@ Graphic::Graphic(const BitmapEx& rBmpEx) // and we need to be able to see and preserve 'stock' images too. Graphic::Graphic(const Image& rImage) // FIXME: should really defer the BitmapEx load. - : mxImpGraphic(std::make_shared<ImpGraphic>(rImage.GetBitmapEx())) + : mxImpGraphic(new ImpGraphic(rImage.GetBitmapEx())) { OUString aStock = rImage.GetStock(); if (aStock.getLength()) @@ -203,17 +203,17 @@ Graphic::Graphic(const Image& rImage) } Graphic::Graphic(const std::shared_ptr<VectorGraphicData>& rVectorGraphicDataPtr) - : mxImpGraphic(vcl::graphic::Manager::get().newInstance(rVectorGraphicDataPtr)) + : mxImpGraphic(new ImpGraphic(rVectorGraphicDataPtr)) { } Graphic::Graphic(const Animation& rAnimation) - : mxImpGraphic(vcl::graphic::Manager::get().newInstance(rAnimation)) + : mxImpGraphic(new ImpGraphic(rAnimation)) { } -Graphic::Graphic(const GDIMetaFile& rMtf) - : mxImpGraphic(vcl::graphic::Manager::get().newInstance(rMtf)) +Graphic::Graphic(const GDIMetaFile& rMetaFile) + : mxImpGraphic(new ImpGraphic(rMetaFile)) { } @@ -225,19 +225,19 @@ Graphic::Graphic( const css::uno::Reference< css::graphic::XGraphic >& rxGraphic if( pGraphic ) { if (pGraphic->IsAnimated()) - mxImpGraphic = vcl::graphic::Manager::get().copy(pGraphic->mxImpGraphic); + mxImpGraphic = std::make_shared<ImpGraphic>(*pGraphic->mxImpGraphic); else mxImpGraphic = pGraphic->mxImpGraphic; } else - mxImpGraphic = vcl::graphic::Manager::get().newInstance(); + mxImpGraphic = std::make_shared<ImpGraphic>(); } void Graphic::ImplTestRefCount() { if (mxImpGraphic.use_count() > 1) { - mxImpGraphic = vcl::graphic::Manager::get().copy(mxImpGraphic); + mxImpGraphic = std::make_shared<ImpGraphic>(*mxImpGraphic); } } @@ -256,7 +256,7 @@ Graphic& Graphic::operator=( const Graphic& rGraphic ) if( &rGraphic != this ) { if( rGraphic.IsAnimated() ) - mxImpGraphic = vcl::graphic::Manager::get().copy(rGraphic.mxImpGraphic); + mxImpGraphic = std::make_shared<ImpGraphic>(*rGraphic.mxImpGraphic); else mxImpGraphic = rGraphic.mxImpGraphic; } diff --git a/vcl/source/gdi/impgraph.cxx b/vcl/source/gdi/impgraph.cxx index ceb6c111cf7b..42ea57c1a820 100644 --- a/vcl/source/gdi/impgraph.cxx +++ b/vcl/source/gdi/impgraph.cxx @@ -82,12 +82,14 @@ SvStream* ImpGraphic::getSwapFileStream() const } ImpGraphic::ImpGraphic(bool bDefault) - : meType(bDefault ? GraphicType::Default : GraphicType::NONE) + : MemoryManaged(false) + , meType(bDefault ? GraphicType::Default : GraphicType::NONE) { } ImpGraphic::ImpGraphic(const ImpGraphic& rImpGraphic) - : maMetaFile(rImpGraphic.maMetaFile) + : MemoryManaged(rImpGraphic) + , maMetaFile(rImpGraphic.maMetaFile) , maBitmapEx(rImpGraphic.maBitmapEx) , maSwapInfo(rImpGraphic.maSwapInfo) , mpContext(rImpGraphic.mpContext) @@ -101,6 +103,8 @@ ImpGraphic::ImpGraphic(const ImpGraphic& rImpGraphic) , maGraphicExternalLink(rImpGraphic.maGraphicExternalLink) , mbPrepared(rImpGraphic.mbPrepared) { + updateCurrentSizeInBytes(mnSizeBytes); + // Special case for animations if (rImpGraphic.mpAnimation) { @@ -110,7 +114,8 @@ ImpGraphic::ImpGraphic(const ImpGraphic& rImpGraphic) } ImpGraphic::ImpGraphic(ImpGraphic&& rImpGraphic) noexcept - : maMetaFile(std::move(rImpGraphic.maMetaFile)) + : MemoryManaged(rImpGraphic) + , maMetaFile(std::move(rImpGraphic.maMetaFile)) , maBitmapEx(std::move(rImpGraphic.maBitmapEx)) , maSwapInfo(std::move(rImpGraphic.maSwapInfo)) , mpAnimation(std::move(rImpGraphic.mpAnimation)) @@ -125,12 +130,15 @@ ImpGraphic::ImpGraphic(ImpGraphic&& rImpGraphic) noexcept , maGraphicExternalLink(rImpGraphic.maGraphicExternalLink) , mbPrepared (rImpGraphic.mbPrepared) { + updateCurrentSizeInBytes(mnSizeBytes); + rImpGraphic.clear(); rImpGraphic.mbDummyContext = false; } ImpGraphic::ImpGraphic(std::shared_ptr<GfxLink> xGfxLink, sal_Int32 nPageIndex) - : mpGfxLink(std::move(xGfxLink)) + : MemoryManaged(true) + , mpGfxLink(std::move(xGfxLink)) , meType(GraphicType::Bitmap) , mbSwapOut(true) { @@ -140,53 +148,63 @@ ImpGraphic::ImpGraphic(std::shared_ptr<GfxLink> xGfxLink, sal_Int32 nPageIndex) maSwapInfo.mbIsAnimated = false; maSwapInfo.mnAnimationLoopCount = 0; maSwapInfo.mnPageIndex = nPageIndex; + + ensureCurrentSizeInBytes(); } ImpGraphic::ImpGraphic(GraphicExternalLink aGraphicExternalLink) - : meType(GraphicType::Default) + : MemoryManaged(true) + , meType(GraphicType::Default) , maGraphicExternalLink(std::move(aGraphicExternalLink)) { + ensureCurrentSizeInBytes(); } ImpGraphic::ImpGraphic(const BitmapEx& rBitmapEx) - : maBitmapEx(rBitmapEx) + : MemoryManaged(!rBitmapEx.IsEmpty()) + , maBitmapEx(rBitmapEx) , meType(rBitmapEx.IsEmpty() ? GraphicType::NONE : GraphicType::Bitmap) { + ensureCurrentSizeInBytes(); } ImpGraphic::ImpGraphic(const std::shared_ptr<VectorGraphicData>& rVectorGraphicDataPtr) - : maVectorGraphicData(rVectorGraphicDataPtr) + : MemoryManaged(bool(rVectorGraphicDataPtr)) + , maVectorGraphicData(rVectorGraphicDataPtr) , meType(rVectorGraphicDataPtr ? GraphicType::Bitmap : GraphicType::NONE) { + ensureCurrentSizeInBytes(); } ImpGraphic::ImpGraphic(const Animation& rAnimation) - : maBitmapEx(rAnimation.GetBitmapEx()) + : MemoryManaged(true) + , maBitmapEx(rAnimation.GetBitmapEx()) , mpAnimation(std::make_unique<Animation>(rAnimation)) , meType(GraphicType::Bitmap) { + ensureCurrentSizeInBytes(); } ImpGraphic::ImpGraphic(const GDIMetaFile& rMetafile) - : maMetaFile(rMetafile) + : MemoryManaged(true) + , maMetaFile(rMetafile) , meType(GraphicType::GdiMetafile) { + ensureCurrentSizeInBytes(); } ImpGraphic::~ImpGraphic() { - vcl::graphic::Manager::get().unregisterGraphic(this); } ImpGraphic& ImpGraphic::operator=( const ImpGraphic& rImpGraphic ) { if( &rImpGraphic != this ) { - sal_Int64 aOldSizeBytes = mnSizeBytes; - maMetaFile = rImpGraphic.maMetaFile; meType = rImpGraphic.meType; mnSizeBytes = rImpGraphic.mnSizeBytes; + updateCurrentSizeInBytes(mnSizeBytes); maSwapInfo = rImpGraphic.maSwapInfo; mpContext = rImpGraphic.mpContext; @@ -214,7 +232,7 @@ ImpGraphic& ImpGraphic::operator=( const ImpGraphic& rImpGraphic ) maVectorGraphicData = rImpGraphic.maVectorGraphicData; maLastUsed = std::chrono::high_resolution_clock::now(); - vcl::graphic::Manager::get().changeExisting(this, aOldSizeBytes); + changeExisting(mnSizeBytes); } return *this; @@ -222,8 +240,6 @@ ImpGraphic& ImpGraphic::operator=( const ImpGraphic& rImpGraphic ) ImpGraphic& ImpGraphic::operator=(ImpGraphic&& rImpGraphic) { - sal_Int64 aOldSizeBytes = mnSizeBytes; - maMetaFile = std::move(rImpGraphic.maMetaFile); meType = rImpGraphic.meType; mnSizeBytes = rImpGraphic.mnSizeBytes; @@ -243,7 +259,7 @@ ImpGraphic& ImpGraphic::operator=(ImpGraphic&& rImpGraphic) rImpGraphic.mbDummyContext = false; maLastUsed = std::chrono::high_resolution_clock::now(); - vcl::graphic::Manager::get().changeExisting(this, aOldSizeBytes); + changeExisting(mnSizeBytes); return *this; } @@ -403,9 +419,9 @@ void ImpGraphic::clear() // cleanup clearGraphics(); meType = GraphicType::NONE; - sal_Int64 nOldSize = mnSizeBytes; mnSizeBytes = 0; - vcl::graphic::Manager::get().changeExisting(this, nOldSize); + + changeExisting(mnSizeBytes); maGraphicExternalLink.msURL.clear(); } @@ -925,6 +941,14 @@ void ImpGraphic::setPrefMapMode(const MapMode& rPrefMapMode) setValuesForPrefMapMod(rPrefMapMode); } +void ImpGraphic::ensureCurrentSizeInBytes() +{ + if (isAvailable()) + changeExisting(getSizeBytes()); + else + changeExisting(0); +} + sal_uLong ImpGraphic::getSizeBytes() const { if (mnSizeBytes > 0) @@ -1266,8 +1290,6 @@ bool ImpGraphic::swapOut() bool bResult = false; - sal_Int64 nByteSize = getSizeBytes(); - // We have GfxLink so we have the source available if (mpGfxLink && mpGfxLink->IsNative()) { @@ -1324,7 +1346,7 @@ bool ImpGraphic::swapOut() if (bResult) { // Signal to manager that we have swapped out - vcl::graphic::Manager::get().swappedOut(this, nByteSize); + swappedOut(0); } return bResult; @@ -1332,14 +1354,17 @@ bool ImpGraphic::swapOut() bool ImpGraphic::ensureAvailable() const { - auto pThis = const_cast<ImpGraphic*>(this); - bool bResult = true; if (isSwappedOut()) + { + auto pThis = const_cast<ImpGraphic*>(this); + pThis->registerIntoManager(); + bResult = pThis->swapIn(); + } - pThis->maLastUsed = std::chrono::high_resolution_clock::now(); + maLastUsed = std::chrono::high_resolution_clock::now(); return bResult; } @@ -1526,7 +1551,7 @@ bool ImpGraphic::swapIn() if (bReturn) { - vcl::graphic::Manager::get().swappedIn(this, getSizeBytes()); + swappedIn(getSizeBytes()); } return bReturn; @@ -1731,4 +1756,22 @@ sal_Int32 ImpGraphic::getPageNumber() const return maVectorGraphicData->getPageIndex(); return -1; } + +bool ImpGraphic::canReduceMemory() const +{ + if (mpContext) + return false; + return !isSwappedOut(); +} + +bool ImpGraphic::reduceMemory() +{ + return swapOut(); +} + +std::chrono::high_resolution_clock::time_point ImpGraphic::getLastUsed() const +{ + return maLastUsed; +} + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/graphic/Manager.cxx b/vcl/source/graphic/Manager.cxx index 4bb4ce79534b..fb316aef153d 100644 --- a/vcl/source/graphic/Manager.cxx +++ b/vcl/source/graphic/Manager.cxx @@ -26,8 +26,6 @@ using namespace css; -namespace vcl::graphic -{ namespace { void setupConfigurationValuesIfPossible(sal_Int64& rMemoryLimit, @@ -51,267 +49,147 @@ void setupConfigurationValuesIfPossible(sal_Int64& rMemoryLimit, } } -Manager& Manager::get() +namespace vcl::graphic { - static Manager gStaticManager; - return gStaticManager; -} - -Manager::Manager() - : mnAllowedIdleTime(10) - , mbSwapEnabled(true) - , mbReducingGraphicMemory(false) - , mnMemoryLimit(300000000) - , mnUsedSize(0) - , maSwapOutTimer("graphic::Manager maSwapOutTimer") +MemoryManager::MemoryManager() + : maSwapOutTimer("MemoryManager::MemoryManager maSwapOutTimer") { setupConfigurationValuesIfPossible(mnMemoryLimit, mnAllowedIdleTime, mbSwapEnabled); if (mbSwapEnabled) { - maSwapOutTimer.SetInvokeHandler(LINK(this, Manager, SwapOutTimerHandler)); - maSwapOutTimer.SetTimeout(10000); + maSwapOutTimer.SetInvokeHandler(LINK(this, MemoryManager, ReduceMemoryTimerHandler)); + maSwapOutTimer.SetTimeout(mnTimeout); maSwapOutTimer.Start(); } } -void Manager::loopGraphicsAndSwapOut(std::unique_lock<std::mutex>& rGuard, bool bDropAll) +MemoryManager& MemoryManager::get() { - // make a copy of m_pImpGraphicList because if we swap out a svg, the svg - // filter may create more temp Graphics which are auto-added to - // m_pImpGraphicList invalidating a loop over m_pImpGraphicList, e.g. - // reexport of tdf118346-1.odg - o3tl::sorted_vector<ImpGraphic*> aImpGraphicList = m_pImpGraphicList; - - for (ImpGraphic* pEachImpGraphic : aImpGraphicList) - { - if (mnUsedSize < sal_Int64(mnMemoryLimit * 0.7) && !bDropAll) - return; - - if (pEachImpGraphic->isSwappedOut()) - continue; - - sal_Int64 nCurrentGraphicSize = getGraphicSizeBytes(pEachImpGraphic); - if (nCurrentGraphicSize > 100000 || bDropAll) - { - if (!pEachImpGraphic->mpContext) - { - auto aCurrent = std::chrono::high_resolution_clock::now(); - auto aDeltaTime = aCurrent - pEachImpGraphic->maLastUsed; - auto aSeconds = std::chrono::duration_cast<std::chrono::seconds>(aDeltaTime); - - if (aSeconds > mnAllowedIdleTime) - { - // unlock because svgio can call back into us - rGuard.unlock(); - pEachImpGraphic->swapOut(); - rGuard.lock(); - } - } - } - } + static MemoryManager gStaticManager; + return gStaticManager; } -void Manager::reduceGraphicMemory(std::unique_lock<std::mutex>& rGuard, bool bDropAll) +IMPL_LINK(MemoryManager, ReduceMemoryTimerHandler, Timer*, pTimer, void) { - // maMutex is locked in callers - - if (!mbSwapEnabled) - return; - - if (mnUsedSize < mnMemoryLimit && !bDropAll) - return; - - // avoid recursive reduceGraphicMemory on reexport of tdf118346-1.odg to odg - if (mbReducingGraphicMemory) - return; - - mbReducingGraphicMemory = true; - - loopGraphicsAndSwapOut(rGuard, bDropAll); - - sal_Int64 calculatedSize = 0; - for (ImpGraphic* pEachImpGraphic : m_pImpGraphicList) - { - if (!pEachImpGraphic->isSwappedOut()) - { - calculatedSize += getGraphicSizeBytes(pEachImpGraphic); - } - } - - if (calculatedSize != mnUsedSize) - { - assert(rGuard.owns_lock() && rGuard.mutex() == &maMutex); - // coverity[missing_lock: FALSE] - as above assert - mnUsedSize = calculatedSize; - } - - mbReducingGraphicMemory = false; + std::unique_lock aGuard(maMutex); + pTimer->Stop(); + reduceMemory(aGuard); + pTimer->Start(); } -void Manager::dropCache() +void MemoryManager::registerObject(MemoryManaged* pMemoryManaged) { std::unique_lock aGuard(maMutex); - reduceGraphicMemory(aGuard, true); + // Insert and update the used size (bytes) + assert(aGuard.owns_lock() && aGuard.mutex() == &maMutex); + // coverity[missing_lock: FALSE] - as above assert + mnTotalSize += pMemoryManaged->getCurrentSizeInBytes(); + maObjectList.insert(pMemoryManaged); } -void Manager::dumpState(rtl::OStringBuffer& rState) +void MemoryManager::unregisterObject(MemoryManaged* pMemoryManaged) { std::unique_lock aGuard(maMutex); + mnTotalSize -= pMemoryManaged->getCurrentSizeInBytes(); + maObjectList.erase(pMemoryManaged); +} - rState.append("\nImage Manager items:\t"); - rState.append(static_cast<sal_Int32>(m_pImpGraphicList.size())); - rState.append("\tsize:\t"); - rState.append(static_cast<sal_Int64>(mnUsedSize / 1024)); - rState.append("\tkb"); +void MemoryManager::changeExisting(MemoryManaged* pMemoryManaged, sal_Int64 nNewSize) +{ + std::scoped_lock aGuard(maMutex); + sal_Int64 nOldSize = pMemoryManaged->getCurrentSizeInBytes(); + mnTotalSize -= nOldSize; + mnTotalSize += nNewSize; + pMemoryManaged->setCurrentSizeInBytes(nNewSize); +} - for (ImpGraphic* pEachImpGraphic : m_pImpGraphicList) - { - pEachImpGraphic->dumpState(rState); - } +void MemoryManager::swappedIn(MemoryManaged* pMemoryManaged, sal_Int64 nNewSize) +{ + changeExisting(pMemoryManaged, nNewSize); } -sal_Int64 Manager::getGraphicSizeBytes(const ImpGraphic* pImpGraphic) +void MemoryManager::swappedOut(MemoryManaged* pMemoryManaged, sal_Int64 nNewSize) { - if (!pImpGraphic->isAvailable()) - return 0; - return pImpGraphic->getSizeBytes(); + changeExisting(pMemoryManaged, nNewSize); } -IMPL_LINK(Manager, SwapOutTimerHandler, Timer*, pTimer, void) +void MemoryManager::reduceAllAndNow() { std::unique_lock aGuard(maMutex); - - pTimer->Stop(); - reduceGraphicMemory(aGuard); - pTimer->Start(); + reduceMemory(aGuard, true); } -void Manager::registerGraphic(const std::shared_ptr<ImpGraphic>& pImpGraphic) +void MemoryManager::dumpState(rtl::OStringBuffer& rState) { std::unique_lock aGuard(maMutex); - // make some space first - if (mnUsedSize > mnMemoryLimit) - reduceGraphicMemory(aGuard); - - // Insert and update the used size (bytes) - assert(aGuard.owns_lock() && aGuard.mutex() == &maMutex); - // coverity[missing_lock: FALSE] - as above assert - mnUsedSize += getGraphicSizeBytes(pImpGraphic.get()); - m_pImpGraphicList.insert(pImpGraphic.get()); - - // calculate size of the graphic set - sal_Int64 calculatedSize = 0; - for (ImpGraphic* pEachImpGraphic : m_pImpGraphicList) - { - if (!pEachImpGraphic->isSwappedOut()) - { - calculatedSize += getGraphicSizeBytes(pEachImpGraphic); - } - } + rState.append("\nMemory Manager items:\t"); + rState.append(static_cast<sal_Int32>(maObjectList.size())); + rState.append("\tsize:\t"); + rState.append(static_cast<sal_Int64>(mnTotalSize / 1024)); + rState.append("\tkb"); - if (calculatedSize != mnUsedSize) + for (MemoryManaged* pMemoryManaged : maObjectList) { - SAL_INFO_IF(calculatedSize != mnUsedSize, "vcl.gdi", - "Calculated size mismatch. Variable size is '" - << mnUsedSize << "' but calculated size is '" << calculatedSize << "'"); - mnUsedSize = calculatedSize; + pMemoryManaged->dumpState(rState); } } -void Manager::unregisterGraphic(ImpGraphic* pImpGraphic) +void MemoryManager::reduceMemory(std::unique_lock<std::mutex>& rGuard, bool bDropAll) { - std::scoped_lock aGuard(maMutex); - - mnUsedSize -= getGraphicSizeBytes(pImpGraphic); - m_pImpGraphicList.erase(pImpGraphic); -} + // maMutex is locked in callers -std::shared_ptr<ImpGraphic> Manager::copy(std::shared_ptr<ImpGraphic> const& rImpGraphicPtr) -{ - auto pReturn = std::make_shared<ImpGraphic>(*rImpGraphicPtr); - registerGraphic(pReturn); - return pReturn; -} + if (!mbSwapEnabled) + return; -std::shared_ptr<ImpGraphic> Manager::newInstance() -{ - auto pReturn = std::make_shared<ImpGraphic>(); - registerGraphic(pReturn); - return pReturn; -} + if (mnTotalSize < mnMemoryLimit && !bDropAll) + return; -std::shared_ptr<ImpGraphic> Manager::newInstance(std::shared_ptr<GfxLink> const& rGfxLink, - sal_Int32 nPageIndex) -{ - auto pReturn = std::make_shared<ImpGraphic>(rGfxLink, nPageIndex); - registerGraphic(pReturn); - return pReturn; -} + // avoid recursive reduceGraphicMemory on reexport of tdf118346-1.odg to odg + if (mbReducingGraphicMemory) + return; -std::shared_ptr<ImpGraphic> Manager::newInstance(const BitmapEx& rBitmapEx) -{ - auto pReturn = std::make_shared<ImpGraphic>(rBitmapEx); - registerGraphic(pReturn); - return pReturn; -} + mbReducingGraphicMemory = true; -std::shared_ptr<ImpGraphic> Manager::newInstance(const Animation& rAnimation) -{ - auto pReturn = std::make_shared<ImpGraphic>(rAnimation); - registerGraphic(pReturn); - return pReturn; -} + loopAndReduceMemory(rGuard, bDropAll); -std::shared_ptr<ImpGraphic> -Manager::newInstance(const std::shared_ptr<VectorGraphicData>& rVectorGraphicDataPtr) -{ - auto pReturn = std::make_shared<ImpGraphic>(rVectorGraphicDataPtr); - registerGraphic(pReturn); - return pReturn; + mbReducingGraphicMemory = false; } -std::shared_ptr<ImpGraphic> Manager::newInstance(const GDIMetaFile& rMetaFile) +void MemoryManager::loopAndReduceMemory(std::unique_lock<std::mutex>& rGuard, bool bDropAll) { - auto pReturn = std::make_shared<ImpGraphic>(rMetaFile); - registerGraphic(pReturn); - return pReturn; -} + // make a copy of m_pImpGraphicList because if we swap out a svg, the svg + // filter may create more temp Graphics which are auto-added to + // m_pImpGraphicList invalidating a loop over m_pImpGraphicList, e.g. + // reexport of tdf118346-1.odg -std::shared_ptr<ImpGraphic> Manager::newInstance(const GraphicExternalLink& rGraphicLink) -{ - auto pReturn = std::make_shared<ImpGraphic>(rGraphicLink); - registerGraphic(pReturn); - return pReturn; -} + o3tl::sorted_vector<MemoryManaged*> aObjectListCopy = maObjectList; -void Manager::swappedIn(const ImpGraphic* pImpGraphic, sal_Int64 nSizeBytes) -{ - std::scoped_lock aGuard(maMutex); - if (pImpGraphic) + for (MemoryManaged* pMemoryManaged : aObjectListCopy) { - mnUsedSize += nSizeBytes; - } -} + if (!pMemoryManaged->canReduceMemory()) + continue; -void Manager::swappedOut(const ImpGraphic* pImpGraphic, sal_Int64 nSizeBytes) -{ - std::scoped_lock aGuard(maMutex); - if (pImpGraphic) - { - mnUsedSize -= nSizeBytes; + sal_Int64 nCurrentSizeInBytes = pMemoryManaged->getCurrentSizeInBytes(); + if (nCurrentSizeInBytes > mnSmallFrySize || bDropAll) // ignore small-fry + { + auto aCurrent = std::chrono::high_resolution_clock::now(); + auto aDeltaTime = aCurrent - pMemoryManaged->getLastUsed(); + auto aSeconds = std::chrono::duration_cast<std::chrono::seconds>(aDeltaTime); + + if (aSeconds > mnAllowedIdleTime) + { + // unlock because svgio can call back into us + rGuard.unlock(); + pMemoryManaged->reduceMemory(); + rGuard.lock(); + } + } } } -void Manager::changeExisting(const ImpGraphic* pImpGraphic, sal_Int64 nOldSizeBytes) -{ - std::scoped_lock aGuard(maMutex); - - mnUsedSize -= nOldSizeBytes; - mnUsedSize += getGraphicSizeBytes(pImpGraphic); -} } // end vcl::graphic /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |