Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / drawinglayer / source / processor2d / vclhelperbufferdevice.cxx
blobc76225194dd7a8d3a2830073e4ae93d7f71e8d54
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <sal/config.h>
21 #include <sal/log.hxx>
23 #include <algorithm>
24 #include <map>
25 #include <vector>
27 #include "vclhelperbufferdevice.hxx"
28 #include <basegfx/range/b2drange.hxx>
29 #include <vcl/bitmapex.hxx>
30 #include <basegfx/matrix/b2dhommatrix.hxx>
31 #include <basegfx/matrix/b2dhommatrixtools.hxx>
32 #include <vcl/timer.hxx>
33 #include <vcl/lazydelete.hxx>
34 #include <vcl/dibtools.hxx>
35 #include <vcl/skia/SkiaHelper.hxx>
36 #include <mutex>
38 #ifdef DBG_UTIL
39 #include <tools/stream.hxx>
40 #endif
42 // #define SPEED_COMPARE
43 #ifdef SPEED_COMPARE
44 #include <tools/time.hxx>
45 #endif
47 // buffered VDev usage
48 namespace
50 class VDevBuffer : public Timer
52 private:
53 struct Entry
55 VclPtr<VirtualDevice> buf;
56 Entry(const VclPtr<VirtualDevice>& vdev)
57 : buf(vdev)
62 std::mutex m_aMutex;
64 // available buffers
65 std::vector<Entry> maFreeBuffers;
67 // allocated/used buffers (remembered to allow deleting them in destructor)
68 std::vector<Entry> maUsedBuffers;
70 // remember what outputdevice was the template passed to VirtualDevice::Create
71 // so we can test if that OutputDevice was disposed before reusing a
72 // virtualdevice because that isn't safe to do at least for Gtk2
73 std::map<VclPtr<VirtualDevice>, VclPtr<OutputDevice>> maDeviceTemplates;
75 static bool isSizeSuitable(const VclPtr<VirtualDevice>& device, const Size& size);
77 public:
78 VDevBuffer();
79 virtual ~VDevBuffer() override;
81 VclPtr<VirtualDevice> alloc(OutputDevice& rOutDev, const Size& rSizePixel);
82 void free(VirtualDevice& rDevice);
84 // Timer virtuals
85 virtual void Invoke() override;
88 VDevBuffer::VDevBuffer()
89 : Timer("drawinglayer::VDevBuffer via Invoke()")
91 SetTimeout(10L * 1000L); // ten seconds
94 VDevBuffer::~VDevBuffer()
96 std::unique_lock aGuard(m_aMutex);
97 Stop();
99 while (!maFreeBuffers.empty())
101 maFreeBuffers.back().buf.disposeAndClear();
102 maFreeBuffers.pop_back();
105 while (!maUsedBuffers.empty())
107 maUsedBuffers.back().buf.disposeAndClear();
108 maUsedBuffers.pop_back();
112 bool VDevBuffer::isSizeSuitable(const VclPtr<VirtualDevice>& device, const Size& rSizePixel)
114 if (device->GetOutputWidthPixel() >= rSizePixel.getWidth()
115 && device->GetOutputHeightPixel() >= rSizePixel.getHeight())
117 bool requireSmall = false;
118 #if defined(UNX)
119 // HACK: See the small size handling in SvpSalVirtualDevice::CreateSurface().
120 // Make sure to not reuse a larger device when a small one should be preferred.
121 if (device->GetRenderBackendName() == "svp")
122 requireSmall = true;
123 #endif
124 // The same for Skia, see renderMethodToUseForSize().
125 if (SkiaHelper::isVCLSkiaEnabled())
126 requireSmall = true;
127 if (requireSmall)
129 if (rSizePixel.getWidth() <= 32 && rSizePixel.getHeight() <= 32
130 && (device->GetOutputWidthPixel() > 32 || device->GetOutputHeightPixel() > 32))
132 return false;
135 return true;
137 return false;
140 VclPtr<VirtualDevice> VDevBuffer::alloc(OutputDevice& rOutDev, const Size& rSizePixel)
142 std::unique_lock aGuard(m_aMutex);
143 VclPtr<VirtualDevice> pRetval;
145 sal_Int32 nBits = rOutDev.GetBitCount();
147 bool bOkay(false);
148 if (!maFreeBuffers.empty())
150 auto aFound(maFreeBuffers.end());
152 for (auto a = maFreeBuffers.begin(); a != maFreeBuffers.end(); ++a)
154 assert(a->buf && "Empty pointer in VDevBuffer (!)");
156 if (nBits == a->buf->GetBitCount())
158 // candidate is valid due to bit depth
159 if (aFound != maFreeBuffers.end())
161 // already found
162 if (bOkay)
164 // found is valid
165 const bool bCandidateOkay = isSizeSuitable(a->buf, rSizePixel);
167 if (bCandidateOkay)
169 // found and candidate are valid
170 const sal_uLong aSquare(aFound->buf->GetOutputWidthPixel()
171 * aFound->buf->GetOutputHeightPixel());
172 const sal_uLong aCandidateSquare(a->buf->GetOutputWidthPixel()
173 * a->buf->GetOutputHeightPixel());
175 if (aCandidateSquare < aSquare)
177 // candidate is valid and smaller, use it
178 aFound = a;
181 else
183 // found is valid, candidate is not. Keep found
186 else
188 // found is invalid, use candidate
189 aFound = a;
190 bOkay = isSizeSuitable(aFound->buf, rSizePixel);
193 else
195 // none yet, use candidate
196 aFound = a;
197 bOkay = isSizeSuitable(aFound->buf, rSizePixel);
202 if (aFound != maFreeBuffers.end())
204 pRetval = aFound->buf;
205 maFreeBuffers.erase(aFound);
209 if (pRetval)
211 // found a suitable cached virtual device, but the
212 // outputdevice it was based on has been disposed,
213 // drop it and create a new one instead as reusing
214 // such devices is unsafe under at least Gtk2
215 if (maDeviceTemplates[pRetval]->isDisposed())
217 maDeviceTemplates.erase(pRetval);
218 pRetval.disposeAndClear();
220 else
222 if (bOkay)
224 pRetval->Erase(pRetval->PixelToLogic(
225 tools::Rectangle(0, 0, rSizePixel.getWidth(), rSizePixel.getHeight())));
227 else
229 pRetval->SetOutputSizePixel(rSizePixel, true);
234 // no success yet, create new buffer
235 if (!pRetval)
237 pRetval = VclPtr<VirtualDevice>::Create(rOutDev, DeviceFormat::WITHOUT_ALPHA);
238 maDeviceTemplates[pRetval] = &rOutDev;
239 pRetval->SetOutputSizePixel(rSizePixel, true);
241 else
243 // reused, reset some values
244 pRetval->SetMapMode();
245 pRetval->SetRasterOp(RasterOp::OverPaint);
248 // remember allocated buffer
249 maUsedBuffers.emplace_back(pRetval);
251 return pRetval;
254 void VDevBuffer::free(VirtualDevice& rDevice)
256 std::unique_lock aGuard(m_aMutex);
257 const auto aUsedFound
258 = std::find_if(maUsedBuffers.begin(), maUsedBuffers.end(),
259 [&rDevice](const Entry& el) { return el.buf == &rDevice; });
260 SAL_WARN_IF(aUsedFound == maUsedBuffers.end(), "drawinglayer",
261 "OOps, non-registered buffer freed (!)");
262 if (aUsedFound != maUsedBuffers.end())
264 maFreeBuffers.emplace_back(*aUsedFound);
265 maUsedBuffers.erase(aUsedFound);
266 SAL_WARN_IF(maFreeBuffers.size() > 1000, "drawinglayer",
267 "excessive cached buffers, " << maFreeBuffers.size() << " entries!");
269 Start();
272 void VDevBuffer::Invoke()
274 std::unique_lock aGuard(m_aMutex);
276 while (!maFreeBuffers.empty())
278 auto aLastOne = maFreeBuffers.back();
279 maDeviceTemplates.erase(aLastOne.buf);
280 aLastOne.buf.disposeAndClear();
281 maFreeBuffers.pop_back();
285 #ifdef SPEED_COMPARE
286 void doSpeedCompare(double fTrans, const Bitmap& rContent, const tools::Rectangle& rDestPixel,
287 OutputDevice& rOutDev)
289 const int nAvInd(500);
290 static double fFactors[nAvInd];
291 static int nIndex(nAvInd + 1);
292 static int nRepeat(5);
293 static int nWorseTotal(0);
294 static int nBetterTotal(0);
295 int a(0);
297 const Size aSizePixel(rDestPixel.GetSize());
299 // init statics
300 if (nIndex > nAvInd)
302 for (a = 0; a < nAvInd; a++)
303 fFactors[a] = 1.0;
304 nIndex = 0;
307 // get start time
308 const sal_uInt64 nTimeA(tools::Time::GetSystemTicks());
310 // loop nRepeat times to get somewhat better timings, else
311 // numbers are pretty small
312 for (a = 0; a < nRepeat; a++)
314 // "Former" method using a temporary AlphaMask & DrawBitmapEx
315 sal_uInt8 nMaskValue(static_cast<sal_uInt8>(basegfx::fround(fTrans * 255.0)));
316 const AlphaMask aAlphaMask(aSizePixel, &nMaskValue);
317 rOutDev.DrawBitmapEx(rDestPixel.TopLeft(), BitmapEx(rContent, aAlphaMask));
320 // get intermediate time
321 const sal_uInt64 nTimeB(tools::Time::GetSystemTicks());
323 // loop nRepeat times
324 for (a = 0; a < nRepeat; a++)
326 // New method using DrawTransformedBitmapEx & fTrans directly
327 rOutDev.DrawTransformedBitmapEx(basegfx::utils::createScaleTranslateB2DHomMatrix(
328 aSizePixel.Width(), aSizePixel.Height(),
329 rDestPixel.TopLeft().X(), rDestPixel.TopLeft().Y()),
330 BitmapEx(rContent), 1 - fTrans);
333 // get end time
334 const sal_uInt64 nTimeC(tools::Time::GetSystemTicks());
336 // calculate deltas
337 const sal_uInt64 nTimeFormer(nTimeB - nTimeA);
338 const sal_uInt64 nTimeNew(nTimeC - nTimeB);
340 // compare & note down
341 if (nTimeFormer != nTimeNew && 0 != nTimeFormer && 0 != nTimeNew)
343 if ((nTimeFormer < 10 || nTimeNew < 10) && nRepeat < 500)
345 nRepeat += 1;
346 SAL_INFO("drawinglayer.processor2d", "Increment nRepeat to " << nRepeat);
347 return;
350 const double fNewFactor((double)nTimeFormer / nTimeNew);
351 fFactors[nIndex % nAvInd] = fNewFactor;
352 nIndex++;
353 double fAverage(0.0);
355 for (a = 0; a < nAvInd; a++)
356 fAverage += fFactors[a];
357 fAverage /= nAvInd;
359 if (fNewFactor < 1.0)
360 nWorseTotal++;
361 else
362 nBetterTotal++;
364 char buf[300];
365 sprintf(buf,
366 "Former: %ld New: %ld It got %s (factor %f) (av. last %d Former/New is %f, "
367 "WorseTotal: %d, BetterTotal: %d)",
368 nTimeFormer, nTimeNew, fNewFactor < 1.0 ? "WORSE" : "BETTER",
369 fNewFactor < 1.0 ? 1.0 / fNewFactor : fNewFactor, nAvInd, fAverage, nWorseTotal,
370 nBetterTotal);
371 SAL_INFO("drawinglayer.processor2d", buf);
374 #endif
377 // support for rendering Bitmap and BitmapEx contents
378 namespace drawinglayer
380 // static global VDev buffer for VclProcessor2D/VclPixelProcessor2D
381 VDevBuffer& getVDevBuffer()
383 // secure global instance with Vcl's safe destroyer of external (seen by
384 // library base) stuff, the remembered VDevs need to be deleted before
385 // Vcl's deinit
386 static vcl::DeleteOnDeinit<VDevBuffer> aVDevBuffer{};
387 return *aVDevBuffer.get();
390 impBufferDevice::impBufferDevice(OutputDevice& rOutDev, const basegfx::B2DRange& rRange)
391 : mrOutDev(rOutDev)
392 , mpContent(nullptr)
393 , mpAlpha(nullptr)
395 basegfx::B2DRange aRangePixel(rRange);
396 aRangePixel.transform(mrOutDev.GetViewTransformation());
397 maDestPixel = tools::Rectangle(floor(aRangePixel.getMinX()), floor(aRangePixel.getMinY()),
398 ceil(aRangePixel.getMaxX()), ceil(aRangePixel.getMaxY()));
399 maDestPixel.Intersection({ {}, mrOutDev.GetOutputSizePixel() });
401 if (!isVisible())
402 return;
404 mpContent = getVDevBuffer().alloc(mrOutDev, maDestPixel.GetSize());
406 // #i93485# assert when copying from window to VDev is used
407 SAL_WARN_IF(
408 mrOutDev.GetOutDevType() == OUTDEV_WINDOW, "drawinglayer",
409 "impBufferDevice render helper: Copying from Window to VDev, this should be avoided (!)");
411 // initialize buffer by blitting content of source to prepare for
412 // transparence/ copying back
413 const bool bWasEnabledSrc(mrOutDev.IsMapModeEnabled());
414 mrOutDev.EnableMapMode(false);
415 mpContent->DrawOutDev(Point(), maDestPixel.GetSize(), maDestPixel.TopLeft(),
416 maDestPixel.GetSize(), mrOutDev);
417 mrOutDev.EnableMapMode(bWasEnabledSrc);
419 MapMode aNewMapMode(mrOutDev.GetMapMode());
421 const Point aLogicTopLeft(mrOutDev.PixelToLogic(maDestPixel.TopLeft()));
422 aNewMapMode.SetOrigin(Point(-aLogicTopLeft.X(), -aLogicTopLeft.Y()));
424 mpContent->SetMapMode(aNewMapMode);
426 // copy AA flag for new target
427 mpContent->SetAntialiasing(mrOutDev.GetAntialiasing());
429 // copy RasterOp (e.g. may be RasterOp::Xor on destination)
430 mpContent->SetRasterOp(mrOutDev.GetRasterOp());
433 impBufferDevice::~impBufferDevice()
435 if (mpContent)
437 getVDevBuffer().free(*mpContent);
440 if (mpAlpha)
442 getVDevBuffer().free(*mpAlpha);
446 void impBufferDevice::paint(double fTrans)
448 if (!isVisible())
449 return;
451 const Point aEmptyPoint;
452 const Size aSizePixel(maDestPixel.GetSize());
453 const bool bWasEnabledDst(mrOutDev.IsMapModeEnabled());
455 mrOutDev.EnableMapMode(false);
456 mpContent->EnableMapMode(false);
458 #ifdef DBG_UTIL
459 // VCL_DUMP_BMP_PATH should be like C:/path/ or ~/path/
460 static bool bDoSaveForVisualControl(false); // loplugin:constvars:ignore
461 static const OUString sDumpPath(OUString::createFromAscii(std::getenv("VCL_DUMP_BMP_PATH")));
463 if (!sDumpPath.isEmpty() && bDoSaveForVisualControl)
465 SvFileStream aNew(sDumpPath + "content.bmp", StreamMode::WRITE | StreamMode::TRUNC);
466 Bitmap aContent(mpContent->GetBitmap(aEmptyPoint, aSizePixel));
467 WriteDIB(aContent, aNew, false, true);
469 #endif
471 // during painting the buffer, disable evtl. set RasterOp (may be RasterOp::Xor)
472 const RasterOp aOrigRasterOp(mrOutDev.GetRasterOp());
473 mrOutDev.SetRasterOp(RasterOp::OverPaint);
475 if (mpAlpha)
477 mpAlpha->EnableMapMode(false);
478 AlphaMask aAlphaMask(mpAlpha->GetBitmap(aEmptyPoint, aSizePixel));
480 #ifdef DBG_UTIL
481 if (!sDumpPath.isEmpty() && bDoSaveForVisualControl)
483 SvFileStream aNew(sDumpPath + "transparence.bmp",
484 StreamMode::WRITE | StreamMode::TRUNC);
485 WriteDIB(aAlphaMask.GetBitmap(), aNew, false, true);
487 #endif
489 Bitmap aContent(mpContent->GetBitmap(aEmptyPoint, aSizePixel));
490 mrOutDev.DrawBitmapEx(maDestPixel.TopLeft(), BitmapEx(aContent, aAlphaMask));
492 else if (0.0 != fTrans)
494 const Bitmap aContent(mpContent->GetBitmap(aEmptyPoint, aSizePixel));
496 #ifdef SPEED_COMPARE
497 static bool bCompareFormerAndNewTimings(true);
499 if (bCompareFormerAndNewTimings)
501 doSpeedCompare(fTrans, aContent, maDestPixel, mrOutDev);
503 else
504 #endif
505 // Note: this extra scope is needed due to 'clang plugin indentation'. It complains
506 // that lines 494 and (now) 539 are 'statement mis-aligned compared to neighbours'.
507 // That is true if SPEED_COMPARE is not defined. Not nice, but have to fix this.
509 // For the case we have a unified transparency value there is a former
510 // and new method to paint that which can be used. To decide on measurements,
511 // I added 'doSpeedCompare' above which can be activated by defining
512 // SPEED_COMPARE at the top of this file.
513 // I added the used Testdoc: blurplay3.odg as
514 // https://bugs.documentfoundation.org/attachment.cgi?id=182463
515 // I did measure on
517 // Linux Dbg:
518 // Former: 21 New: 32 It got WORSE (factor 1.523810) (av. last 500 Former/New is 0.968533, WorseTotal: 515, BetterTotal: 934)
520 // Linux Pro:
521 // Former: 27 New: 44 It got WORSE (factor 1.629630) (av. last 500 Former/New is 0.923256, WorseTotal: 433, BetterTotal: 337)
523 // Win Dbg:
524 // Former: 21 New: 78 It got WORSE (factor 3.714286) (av. last 500 Former/New is 1.007176, WorseTotal: 85, BetterTotal: 1428)
526 // Win Pro:
527 // Former: 3 New: 4 It got WORSE (factor 1.333333) (av. last 500 Former/New is 1.054167, WorseTotal: 143, BetterTotal: 3909)
529 // Note: I am aware that the Dbg are of limited usefulness, but include them here
530 // for reference.
532 // The important part is "av. last 500 Former/New is %ld" which describes the averaged factor from Former/New
533 // over the last 500 measurements. When < 1.0 Former is better (Linux), > 1.0 (Win) New is better. Since the
534 // factor on Win is still close to 1.0 what means we lose nearly nothing and Linux Former is better, I will
535 // use Former for now.
537 // To easily allow to change this (maybe system-dependent) I add a static switch here,
538 // also for eventually experimenting (hint: can be changed in the debugger).
539 static bool bUseNew(false);
541 if (bUseNew)
543 // New method using DrawTransformedBitmapEx & fTrans directly
544 mrOutDev.DrawTransformedBitmapEx(basegfx::utils::createScaleTranslateB2DHomMatrix(
545 aSizePixel.Width(), aSizePixel.Height(),
546 maDestPixel.TopLeft().X(),
547 maDestPixel.TopLeft().Y()),
548 BitmapEx(aContent), 1 - fTrans);
550 else
552 // "Former" method using a temporary AlphaMask & DrawBitmapEx
553 sal_uInt8 nMaskValue(static_cast<sal_uInt8>(basegfx::fround(fTrans * 255.0)));
554 const AlphaMask aAlphaMask(aSizePixel, &nMaskValue);
555 mrOutDev.DrawBitmapEx(maDestPixel.TopLeft(), BitmapEx(aContent, aAlphaMask));
559 else
561 mrOutDev.DrawOutDev(maDestPixel.TopLeft(), aSizePixel, aEmptyPoint, aSizePixel, *mpContent);
564 mrOutDev.SetRasterOp(aOrigRasterOp);
565 mrOutDev.EnableMapMode(bWasEnabledDst);
568 VirtualDevice& impBufferDevice::getContent()
570 SAL_WARN_IF(!mpContent, "drawinglayer",
571 "impBufferDevice: No content, check isVisible() before accessing (!)");
572 return *mpContent;
575 VirtualDevice& impBufferDevice::getTransparence()
577 SAL_WARN_IF(!mpContent, "drawinglayer",
578 "impBufferDevice: No content, check isVisible() before accessing (!)");
579 if (!mpAlpha)
581 mpAlpha = getVDevBuffer().alloc(mrOutDev, maDestPixel.GetSize());
582 mpAlpha->SetMapMode(mpContent->GetMapMode());
584 // copy AA flag for new target; masking needs to be smooth
585 mpAlpha->SetAntialiasing(mpContent->GetAntialiasing());
588 return *mpAlpha;
590 } // end of namespace drawinglayer
592 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */