1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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>
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>
39 #include <tools/stream.hxx>
42 // #define SPEED_COMPARE
44 #include <tools/time.hxx>
47 // buffered VDev usage
50 class VDevBuffer
: public Timer
55 VclPtr
<VirtualDevice
> buf
;
56 Entry(const VclPtr
<VirtualDevice
>& vdev
)
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
);
79 virtual ~VDevBuffer() override
;
81 VclPtr
<VirtualDevice
> alloc(OutputDevice
& rOutDev
, const Size
& rSizePixel
);
82 void free(VirtualDevice
& rDevice
);
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
);
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;
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")
124 // The same for Skia, see renderMethodToUseForSize().
125 if (SkiaHelper::isVCLSkiaEnabled())
129 if (rSizePixel
.getWidth() <= 32 && rSizePixel
.getHeight() <= 32
130 && (device
->GetOutputWidthPixel() > 32 || device
->GetOutputHeightPixel() > 32))
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();
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())
165 const bool bCandidateOkay
= isSizeSuitable(a
->buf
, rSizePixel
);
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
183 // found is valid, candidate is not. Keep found
188 // found is invalid, use candidate
190 bOkay
= isSizeSuitable(aFound
->buf
, rSizePixel
);
195 // none yet, use candidate
197 bOkay
= isSizeSuitable(aFound
->buf
, rSizePixel
);
202 if (aFound
!= maFreeBuffers
.end())
204 pRetval
= aFound
->buf
;
205 maFreeBuffers
.erase(aFound
);
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();
224 pRetval
->Erase(pRetval
->PixelToLogic(
225 tools::Rectangle(0, 0, rSizePixel
.getWidth(), rSizePixel
.getHeight())));
229 pRetval
->SetOutputSizePixel(rSizePixel
, true);
234 // no success yet, create new buffer
237 pRetval
= VclPtr
<VirtualDevice
>::Create(rOutDev
, DeviceFormat::WITHOUT_ALPHA
);
238 maDeviceTemplates
[pRetval
] = &rOutDev
;
239 pRetval
->SetOutputSizePixel(rSizePixel
, true);
243 // reused, reset some values
244 pRetval
->SetMapMode();
245 pRetval
->SetRasterOp(RasterOp::OverPaint
);
248 // remember allocated buffer
249 maUsedBuffers
.emplace_back(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!");
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();
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);
297 const Size
aSizePixel(rDestPixel
.GetSize());
302 for (a
= 0; a
< nAvInd
; a
++)
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
);
334 const sal_uInt64
nTimeC(tools::Time::GetSystemTicks());
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)
346 SAL_INFO("drawinglayer.processor2d", "Increment nRepeat to " << nRepeat
);
350 const double fNewFactor((double)nTimeFormer
/ nTimeNew
);
351 fFactors
[nIndex
% nAvInd
] = fNewFactor
;
353 double fAverage(0.0);
355 for (a
= 0; a
< nAvInd
; a
++)
356 fAverage
+= fFactors
[a
];
359 if (fNewFactor
< 1.0)
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
,
371 SAL_INFO("drawinglayer.processor2d", buf
);
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
386 static vcl::DeleteOnDeinit
<VDevBuffer
> aVDevBuffer
{};
387 return *aVDevBuffer
.get();
390 impBufferDevice::impBufferDevice(OutputDevice
& rOutDev
, const basegfx::B2DRange
& rRange
)
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() });
404 mpContent
= getVDevBuffer().alloc(mrOutDev
, maDestPixel
.GetSize());
406 // #i93485# assert when copying from window to VDev is used
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()
437 getVDevBuffer().free(*mpContent
);
442 getVDevBuffer().free(*mpAlpha
);
446 void impBufferDevice::paint(double fTrans
)
451 const Point aEmptyPoint
;
452 const Size
aSizePixel(maDestPixel
.GetSize());
453 const bool bWasEnabledDst(mrOutDev
.IsMapModeEnabled());
455 mrOutDev
.EnableMapMode(false);
456 mpContent
->EnableMapMode(false);
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);
471 // during painting the buffer, disable evtl. set RasterOp (may be RasterOp::Xor)
472 const RasterOp
aOrigRasterOp(mrOutDev
.GetRasterOp());
473 mrOutDev
.SetRasterOp(RasterOp::OverPaint
);
477 mpAlpha
->EnableMapMode(false);
478 AlphaMask
aAlphaMask(mpAlpha
->GetBitmap(aEmptyPoint
, aSizePixel
));
481 if (!sDumpPath
.isEmpty() && bDoSaveForVisualControl
)
483 SvFileStream
aNew(sDumpPath
+ "transparence.bmp",
484 StreamMode::WRITE
| StreamMode::TRUNC
);
485 WriteDIB(aAlphaMask
.GetBitmap(), aNew
, false, true);
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
));
497 static bool bCompareFormerAndNewTimings(true);
499 if (bCompareFormerAndNewTimings
)
501 doSpeedCompare(fTrans
, aContent
, maDestPixel
, mrOutDev
);
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
518 // Former: 21 New: 32 It got WORSE (factor 1.523810) (av. last 500 Former/New is 0.968533, WorseTotal: 515, BetterTotal: 934)
521 // Former: 27 New: 44 It got WORSE (factor 1.629630) (av. last 500 Former/New is 0.923256, WorseTotal: 433, BetterTotal: 337)
524 // Former: 21 New: 78 It got WORSE (factor 3.714286) (av. last 500 Former/New is 1.007176, WorseTotal: 85, BetterTotal: 1428)
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
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);
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
);
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
));
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 (!)");
575 VirtualDevice
& impBufferDevice::getTransparence()
577 SAL_WARN_IF(!mpContent
, "drawinglayer",
578 "impBufferDevice: No content, check isVisible() before accessing (!)");
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());
590 } // end of namespace drawinglayer
592 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */