summaryrefslogtreecommitdiff
path: root/vcl
diff options
context:
space:
mode:
authorTomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>2019-04-12 22:38:13 +0900
committerTomaž Vajngerl <quikee@gmail.com>2019-04-13 04:23:01 +0200
commit9695896d8f0e3d4b2961c7a753c279a70f5bbaf2 (patch)
tree1d7286c0a0d66a727ef464fff6f8cc16493617cf /vcl
parent7a092e254111a2d98446e7140ef24c652c245bfa (diff)
Improve multi-threaded scaling in BitmapScaleSuperFilter
The old approach was to calculate the number of stripes of the bitmap per thread and later create the exact number tasks (ScaleTask) as there are threads, where each task would process stripes it had been given. This is needlesly complicated as the job of a thread pool is to properly delegate the tasks between threads. This was now changed so that we create one stripe per ScaleTask and let the threadpool delegate the tasks to its threads (that are available). It also wanted to be clever and use the main thread to do the work also, but it had a major flaw. The threadpool started to process the tasks only when "waitUntilDone" method was called, but the code first processed its slices and then called the threadpool method to start processing. Because of this the performance of scaling wasn't as good as it could be. This behaviour was now changed so that the main thread isn't involved in processing. It just creates the task, runs the threadpool and waits until the tasks are finished. Change-Id: I1e8c733bdbced8867d0a7f1190f0421a0cc3e067 Reviewed-on: https://gerrit.libreoffice.org/70668 Reviewed-by: Tomaž Vajngerl <quikee@gmail.com> Tested-by: Tomaž Vajngerl <quikee@gmail.com>
Diffstat (limited to 'vcl')
-rw-r--r--vcl/qa/cppunit/BitmapTest.cxx94
-rw-r--r--vcl/source/bitmap/BitmapScaleSuperFilter.cxx72
2 files changed, 129 insertions, 37 deletions
diff --git a/vcl/qa/cppunit/BitmapTest.cxx b/vcl/qa/cppunit/BitmapTest.cxx
index 632a243af0cf..52abfb6c10e5 100644
--- a/vcl/qa/cppunit/BitmapTest.cxx
+++ b/vcl/qa/cppunit/BitmapTest.cxx
@@ -44,6 +44,7 @@ class BitmapTest : public CppUnit::TestFixture
void testN8Greyscale();
void testConvert();
void testScale();
+ void testScale2();
void testCRC();
void testGreyPalette();
void testCustom8BitPalette();
@@ -58,6 +59,7 @@ class BitmapTest : public CppUnit::TestFixture
CPPUNIT_TEST(testN4Greyscale);
CPPUNIT_TEST(testN8Greyscale);
CPPUNIT_TEST(testScale);
+ CPPUNIT_TEST(testScale2);
CPPUNIT_TEST(testCRC);
CPPUNIT_TEST(testGreyPalette);
CPPUNIT_TEST(testCustom8BitPalette);
@@ -453,6 +455,98 @@ void BitmapTest::testScale()
}
}
+bool checkBitmapColor(Bitmap const& rBitmap, Color const& rExpectedColor)
+{
+ bool bResult = true;
+ Bitmap aBitmap(rBitmap);
+ Bitmap::ScopedReadAccess pReadAccess(aBitmap);
+ long nHeight = pReadAccess->Height();
+ long nWidth = pReadAccess->Width();
+ for (long y = 0; y < nHeight; ++y)
+ {
+ Scanline pScanlineRead = pReadAccess->GetScanline(y);
+ for (long x = 0; x < nWidth; ++x)
+ {
+ Color aColor = pReadAccess->GetPixelFromData(pScanlineRead, x).GetColor();
+ if (aColor != rExpectedColor)
+ bResult = false;
+ }
+ }
+
+ return bResult;
+}
+
+void BitmapTest::testScale2()
+{
+ const bool bExportBitmap(false);
+
+ Bitmap aBitmap24Bit(Size(4096, 4096), 24);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(24), aBitmap24Bit.GetBitCount());
+ Color aBitmapColor = COL_YELLOW;
+ {
+ BitmapScopedWriteAccess aWriteAccess(aBitmap24Bit);
+ aWriteAccess->Erase(aBitmapColor);
+ }
+
+ if (bExportBitmap)
+ {
+ SvFileStream aStream("scale_before.png", StreamMode::WRITE | StreamMode::TRUNC);
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ rFilter.compressAsPNG(aBitmap24Bit, aStream);
+ }
+
+ // Scale - 65x65
+ CPPUNIT_ASSERT_EQUAL(static_cast<long>(4096), aBitmap24Bit.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(static_cast<long>(4096), aBitmap24Bit.GetSizePixel().Height());
+ Bitmap aScaledBitmap = aBitmap24Bit;
+ aScaledBitmap.Scale(Size(65, 65));
+
+ if (bExportBitmap)
+ {
+ SvFileStream aStream("scale_after_65x65.png", StreamMode::WRITE | StreamMode::TRUNC);
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ rFilter.compressAsPNG(aScaledBitmap, aStream);
+ }
+
+ CPPUNIT_ASSERT_EQUAL(static_cast<long>(65), aScaledBitmap.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(static_cast<long>(65), aScaledBitmap.GetSizePixel().Height());
+ CPPUNIT_ASSERT(checkBitmapColor(aScaledBitmap, aBitmapColor));
+
+ // Scale - 64x64
+ CPPUNIT_ASSERT_EQUAL(static_cast<long>(4096), aBitmap24Bit.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(static_cast<long>(4096), aBitmap24Bit.GetSizePixel().Height());
+ aScaledBitmap = aBitmap24Bit;
+ aScaledBitmap.Scale(Size(64, 64));
+
+ if (bExportBitmap)
+ {
+ SvFileStream aStream("scale_after_64x64.png", StreamMode::WRITE | StreamMode::TRUNC);
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ rFilter.compressAsPNG(aScaledBitmap, aStream);
+ }
+
+ CPPUNIT_ASSERT_EQUAL(static_cast<long>(64), aScaledBitmap.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(static_cast<long>(64), aScaledBitmap.GetSizePixel().Height());
+ CPPUNIT_ASSERT(checkBitmapColor(aScaledBitmap, aBitmapColor));
+
+ // Scale - 63x63
+ CPPUNIT_ASSERT_EQUAL(static_cast<long>(4096), aBitmap24Bit.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(static_cast<long>(4096), aBitmap24Bit.GetSizePixel().Height());
+ aScaledBitmap = aBitmap24Bit;
+ aScaledBitmap.Scale(Size(63, 63));
+
+ if (bExportBitmap)
+ {
+ SvFileStream aStream("scale_after_63x63.png", StreamMode::WRITE | StreamMode::TRUNC);
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ rFilter.compressAsPNG(aScaledBitmap, aStream);
+ }
+
+ CPPUNIT_ASSERT_EQUAL(static_cast<long>(63), aScaledBitmap.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(static_cast<long>(63), aScaledBitmap.GetSizePixel().Height());
+ CPPUNIT_ASSERT(checkBitmapColor(aScaledBitmap, aBitmapColor));
+}
+
typedef std::unordered_map<sal_uInt64, const char*> CRCHash;
void checkAndInsert(CRCHash& rHash, sal_uInt64 nCRC, const char* pLocation)
diff --git a/vcl/source/bitmap/BitmapScaleSuperFilter.cxx b/vcl/source/bitmap/BitmapScaleSuperFilter.cxx
index ea73f3b10a04..a10bd8ccc17c 100644
--- a/vcl/source/bitmap/BitmapScaleSuperFilter.cxx
+++ b/vcl/source/bitmap/BitmapScaleSuperFilter.cxx
@@ -77,29 +77,32 @@ struct ScaleContext {
}
};
-#define SCALE_THREAD_STRIP 32
-struct ScaleRangeContext {
- ScaleContext *mrCtx;
- long mnStartY, mnEndY;
- ScaleRangeContext( ScaleContext *rCtx, long nStartY )
- : mrCtx( rCtx ), mnStartY( nStartY ),
- mnEndY( nStartY + SCALE_THREAD_STRIP ) {}
-};
+constexpr long constScaleThreadStrip = 32;
-typedef void (*ScaleRangeFn)(ScaleContext &rCtx, long nStartY, long nEndY);
+typedef void (*ScaleRangeFn)(ScaleContext &rContext, long nStartY, long nEndY);
class ScaleTask : public comphelper::ThreadTask
{
- ScaleRangeFn const mpFn;
- std::vector< ScaleRangeContext > maStrips;
+ ScaleRangeFn const mpScaleRangeFunction;
+ ScaleContext& mrContext;
+ const long mnStartY;
+ const long mnEndY;
+
public:
- explicit ScaleTask( const std::shared_ptr<comphelper::ThreadTaskTag>& pTag, ScaleRangeFn pFn )
- : comphelper::ThreadTask(pTag), mpFn( pFn ) {}
- void push( ScaleRangeContext const &aRC ) { maStrips.push_back( aRC ); }
+ explicit ScaleTask(const std::shared_ptr<comphelper::ThreadTaskTag>& pTag,
+ ScaleRangeFn pScaleRangeFunction,
+ ScaleContext& rContext,
+ long nStartY, long nEndY)
+ : comphelper::ThreadTask(pTag)
+ , mpScaleRangeFunction(pScaleRangeFunction)
+ , mrContext(rContext)
+ , mnStartY(nStartY)
+ , mnEndY(nEndY)
+ {}
+
virtual void doWork() override
{
- for (auto const& strip : maStrips)
- mpFn( *(strip.mrCtx), strip.mnStartY, strip.mnEndY );
+ mpScaleRangeFunction(mrContext, mnStartY, mnEndY);
}
};
@@ -1026,12 +1029,11 @@ BitmapEx BitmapScaleSuperFilter::execute(BitmapEx const& rBitmap) const
// We want to thread - only if there is a lot of work to do:
// We work hard when there is a large destination image, or
// A large source image.
- bool bHorizontalWork = pReadAccess->Width() > 512 || pWriteAccess->Width() > 512;
+ bool bHorizontalWork = pReadAccess->Height() >= 512 && pReadAccess->Width() >= 512;
bool bUseThreads = true;
static bool bDisableThreadedScaling = getenv ("VCL_NO_THREAD_SCALE");
- if ( bDisableThreadedScaling || !bHorizontalWork ||
- nEndY - nStartY < SCALE_THREAD_STRIP )
+ if (bDisableThreadedScaling || !bHorizontalWork)
{
SAL_INFO("vcl.gdi", "Scale in main thread");
bUseThreads = false;
@@ -1044,26 +1046,22 @@ BitmapEx BitmapScaleSuperFilter::execute(BitmapEx const& rBitmap) const
// partition and queue work
comphelper::ThreadPool &rShared = comphelper::ThreadPool::getSharedOptimalPool();
std::shared_ptr<comphelper::ThreadTaskTag> pTag = comphelper::ThreadPool::createThreadTaskTag();
- sal_uInt32 nThreads = rShared.getWorkerCount();
- assert( nThreads > 0 );
- sal_uInt32 nStrips = ((nEndY - nStartY) + SCALE_THREAD_STRIP - 1) / SCALE_THREAD_STRIP;
- sal_uInt32 nStripsPerThread = nStrips / nThreads;
- SAL_INFO("vcl.gdi", "Scale in " << nStrips << " strips " << nStripsPerThread << " per thread we have " << nThreads << " CPU threads ");
- long nStripY = nStartY;
- for ( sal_uInt32 t = 0; t < nThreads - 1; t++ )
+
+ long nStripYStart = nStartY;
+ long nStripYEnd = nStripYStart + constScaleThreadStrip - 1;
+
+ while (nStripYEnd < nEndY)
{
- std::unique_ptr<ScaleTask> pTask(new ScaleTask( pTag, pScaleRangeFn ));
- for ( sal_uInt32 j = 0; j < nStripsPerThread; j++ )
- {
- ScaleRangeContext aRC( &aContext, nStripY );
- pTask->push( aRC );
- nStripY += SCALE_THREAD_STRIP;
- }
- rShared.pushTask( std::move(pTask) );
+ std::unique_ptr<ScaleTask> pTask(new ScaleTask(pTag, pScaleRangeFn, aContext, nStripYStart, nStripYEnd));
+ rShared.pushTask(std::move(pTask));
+ nStripYStart += constScaleThreadStrip;
+ nStripYEnd += constScaleThreadStrip;
+ }
+ if (nStripYStart <= nEndY)
+ {
+ std::unique_ptr<ScaleTask> pTask(new ScaleTask(pTag, pScaleRangeFn, aContext, nStripYStart, nEndY));
+ rShared.pushTask(std::move(pTask));
}
- // finish any remaining bits here
- pScaleRangeFn( aContext, nStripY, nEndY );
-
rShared.waitUntilDone(pTag);
SAL_INFO("vcl.gdi", "All threaded scaling tasks complete");
}