LibreOffice Module drawinglayer (master) 1
vclhelperbufferdevice.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
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 .
18 */
19
20#include <sal/config.h>
21#include <sal/log.hxx>
22
23#include <algorithm>
24#include <map>
25#include <vector>
26
29#include <vcl/bitmapex.hxx>
32#include <vcl/timer.hxx>
33#include <vcl/lazydelete.hxx>
34#include <vcl/dibtools.hxx>
36#include <mutex>
37
38#ifdef DBG_UTIL
39#include <tools/stream.hxx>
40#endif
41
42// #define SPEED_COMPARE
43#ifdef SPEED_COMPARE
44#include <tools/time.hxx>
45#endif
46
47// buffered VDev usage
48namespace
49{
50class VDevBuffer : public Timer
51{
52private:
53 struct Entry
54 {
56 Entry(const VclPtr<VirtualDevice>& vdev)
57 : buf(vdev)
58 {
59 }
60 };
61
62 std::mutex m_aMutex;
63
64 // available buffers
65 std::vector<Entry> maFreeBuffers;
66
67 // allocated/used buffers (remembered to allow deleting them in destructor)
68 std::vector<Entry> maUsedBuffers;
69
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;
74
75 static bool isSizeSuitable(const VclPtr<VirtualDevice>& device, const Size& size);
76
77public:
78 VDevBuffer();
79 virtual ~VDevBuffer() override;
80
81 VclPtr<VirtualDevice> alloc(OutputDevice& rOutDev, const Size& rSizePixel);
82 void free(VirtualDevice& rDevice);
83
84 // Timer virtuals
85 virtual void Invoke() override;
86};
87
88VDevBuffer::VDevBuffer()
89 : Timer("drawinglayer::VDevBuffer via Invoke()")
90{
91 SetTimeout(10L * 1000L); // ten seconds
92}
93
94VDevBuffer::~VDevBuffer()
95{
96 std::unique_lock aGuard(m_aMutex);
97 Stop();
98
99 while (!maFreeBuffers.empty())
100 {
101 maFreeBuffers.back().buf.disposeAndClear();
102 maFreeBuffers.pop_back();
103 }
104
105 while (!maUsedBuffers.empty())
106 {
107 maUsedBuffers.back().buf.disposeAndClear();
108 maUsedBuffers.pop_back();
109 }
110}
111
112bool VDevBuffer::isSizeSuitable(const VclPtr<VirtualDevice>& device, const Size& rSizePixel)
113{
114 if (device->GetOutputWidthPixel() >= rSizePixel.getWidth()
115 && device->GetOutputHeightPixel() >= rSizePixel.getHeight())
116 {
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().
126 requireSmall = true;
127 if (requireSmall)
128 {
129 if (rSizePixel.getWidth() <= 32 && rSizePixel.getHeight() <= 32
130 && (device->GetOutputWidthPixel() > 32 || device->GetOutputHeightPixel() > 32))
131 {
132 return false;
133 }
134 }
135 return true;
136 }
137 return false;
138}
139
140VclPtr<VirtualDevice> VDevBuffer::alloc(OutputDevice& rOutDev, const Size& rSizePixel)
141{
142 std::unique_lock aGuard(m_aMutex);
143 VclPtr<VirtualDevice> pRetval;
144
145 sal_Int32 nBits = rOutDev.GetBitCount();
146
147 bool bOkay(false);
148 if (!maFreeBuffers.empty())
149 {
150 auto aFound(maFreeBuffers.end());
151
152 for (auto a = maFreeBuffers.begin(); a != maFreeBuffers.end(); ++a)
153 {
154 assert(a->buf && "Empty pointer in VDevBuffer (!)");
155
156 if (nBits == a->buf->GetBitCount())
157 {
158 // candidate is valid due to bit depth
159 if (aFound != maFreeBuffers.end())
160 {
161 // already found
162 if (bOkay)
163 {
164 // found is valid
165 const bool bCandidateOkay = isSizeSuitable(a->buf, rSizePixel);
166
167 if (bCandidateOkay)
168 {
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());
174
175 if (aCandidateSquare < aSquare)
176 {
177 // candidate is valid and smaller, use it
178 aFound = a;
179 }
180 }
181 else
182 {
183 // found is valid, candidate is not. Keep found
184 }
185 }
186 else
187 {
188 // found is invalid, use candidate
189 aFound = a;
190 bOkay = isSizeSuitable(aFound->buf, rSizePixel);
191 }
192 }
193 else
194 {
195 // none yet, use candidate
196 aFound = a;
197 bOkay = isSizeSuitable(aFound->buf, rSizePixel);
198 }
199 }
200 }
201
202 if (aFound != maFreeBuffers.end())
203 {
204 pRetval = aFound->buf;
205 maFreeBuffers.erase(aFound);
206 }
207 }
208
209 if (pRetval)
210 {
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())
216 {
217 maDeviceTemplates.erase(pRetval);
218 pRetval.disposeAndClear();
219 }
220 else
221 {
222 if (bOkay)
223 {
224 pRetval->Erase(pRetval->PixelToLogic(
225 tools::Rectangle(0, 0, rSizePixel.getWidth(), rSizePixel.getHeight())));
226 }
227 else
228 {
229 pRetval->SetOutputSizePixel(rSizePixel, true);
230 }
231 }
232 }
233
234 // no success yet, create new buffer
235 if (!pRetval)
236 {
237 pRetval = VclPtr<VirtualDevice>::Create(rOutDev, DeviceFormat::WITHOUT_ALPHA);
238 maDeviceTemplates[pRetval] = &rOutDev;
239 pRetval->SetOutputSizePixel(rSizePixel, true);
240 }
241 else
242 {
243 // reused, reset some values
244 pRetval->SetMapMode();
245 pRetval->SetRasterOp(RasterOp::OverPaint);
246 }
247
248 // remember allocated buffer
249 maUsedBuffers.emplace_back(pRetval);
250
251 return pRetval;
252}
253
254void VDevBuffer::free(VirtualDevice& rDevice)
255{
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())
263 {
264 maFreeBuffers.emplace_back(*aUsedFound);
265 maUsedBuffers.erase(aUsedFound);
266 SAL_WARN_IF(maFreeBuffers.size() > 1000, "drawinglayer",
267 "excessive cached buffers, " << maFreeBuffers.size() << " entries!");
268 }
269 Start();
270}
271
272void VDevBuffer::Invoke()
273{
274 std::unique_lock aGuard(m_aMutex);
275
276 while (!maFreeBuffers.empty())
277 {
278 auto aLastOne = maFreeBuffers.back();
279 maDeviceTemplates.erase(aLastOne.buf);
280 aLastOne.buf.disposeAndClear();
281 maFreeBuffers.pop_back();
282 }
283}
284
285#ifdef SPEED_COMPARE
286void doSpeedCompare(double fTrans, const Bitmap& rContent, const tools::Rectangle& rDestPixel,
287 OutputDevice& rOutDev)
288{
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);
296
297 const Size aSizePixel(rDestPixel.GetSize());
298
299 // init statics
300 if (nIndex > nAvInd)
301 {
302 for (a = 0; a < nAvInd; a++)
303 fFactors[a] = 1.0;
304 nIndex = 0;
305 }
306
307 // get start time
308 const sal_uInt64 nTimeA(tools::Time::GetSystemTicks());
309
310 // loop nRepeat times to get somewhat better timings, else
311 // numbers are pretty small
312 for (a = 0; a < nRepeat; a++)
313 {
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));
318 }
319
320 // get intermediate time
321 const sal_uInt64 nTimeB(tools::Time::GetSystemTicks());
322
323 // loop nRepeat times
324 for (a = 0; a < nRepeat; a++)
325 {
326 // New method using DrawTransformedBitmapEx & fTrans directly
328 aSizePixel.Width(), aSizePixel.Height(),
329 rDestPixel.TopLeft().X(), rDestPixel.TopLeft().Y()),
330 BitmapEx(rContent), 1 - fTrans);
331 }
332
333 // get end time
334 const sal_uInt64 nTimeC(tools::Time::GetSystemTicks());
335
336 // calculate deltas
337 const sal_uInt64 nTimeFormer(nTimeB - nTimeA);
338 const sal_uInt64 nTimeNew(nTimeC - nTimeB);
339
340 // compare & note down
341 if (nTimeFormer != nTimeNew && 0 != nTimeFormer && 0 != nTimeNew)
342 {
343 if ((nTimeFormer < 10 || nTimeNew < 10) && nRepeat < 500)
344 {
345 nRepeat += 1;
346 SAL_INFO("drawinglayer.processor2d", "Increment nRepeat to " << nRepeat);
347 return;
348 }
349
350 const double fNewFactor((double)nTimeFormer / nTimeNew);
351 fFactors[nIndex % nAvInd] = fNewFactor;
352 nIndex++;
353 double fAverage(0.0);
354 {
355 for (a = 0; a < nAvInd; a++)
356 fAverage += fFactors[a];
357 fAverage /= nAvInd;
358 }
359 if (fNewFactor < 1.0)
360 nWorseTotal++;
361 else
362 nBetterTotal++;
363
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);
372 }
373}
374#endif
375}
376
377// support for rendering Bitmap and BitmapEx contents
378namespace drawinglayer
379{
380// static global VDev buffer for VclProcessor2D/VclPixelProcessor2D
381VDevBuffer& getVDevBuffer()
382{
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();
388}
389
391 : mrOutDev(rOutDev)
392 , mpContent(nullptr)
393 , mpAlpha(nullptr)
394{
395 basegfx::B2DRange aRangePixel(rRange);
397 maDestPixel = tools::Rectangle(floor(aRangePixel.getMinX()), floor(aRangePixel.getMinY()),
398 ceil(aRangePixel.getMaxX()), ceil(aRangePixel.getMaxY()));
400
401 if (!isVisible())
402 return;
403
405
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 (!)");
410
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);
417 mrOutDev.EnableMapMode(bWasEnabledSrc);
418
419 MapMode aNewMapMode(mrOutDev.GetMapMode());
420
421 const Point aLogicTopLeft(mrOutDev.PixelToLogic(maDestPixel.TopLeft()));
422 aNewMapMode.SetOrigin(Point(-aLogicTopLeft.X(), -aLogicTopLeft.Y()));
423
424 mpContent->SetMapMode(aNewMapMode);
425
426 // copy AA flag for new target
427 mpContent->SetAntialiasing(mrOutDev.GetAntialiasing());
428
429 // copy RasterOp (e.g. may be RasterOp::Xor on destination)
430 mpContent->SetRasterOp(mrOutDev.GetRasterOp());
431}
432
434{
435 if (mpContent)
436 {
437 getVDevBuffer().free(*mpContent);
438 }
439
440 if (mpAlpha)
441 {
442 getVDevBuffer().free(*mpAlpha);
443 }
444}
445
446void impBufferDevice::paint(double fTrans)
447{
448 if (!isVisible())
449 return;
450
451 const Point aEmptyPoint;
452 const Size aSizePixel(maDestPixel.GetSize());
453 const bool bWasEnabledDst(mrOutDev.IsMapModeEnabled());
454
455 mrOutDev.EnableMapMode(false);
456 mpContent->EnableMapMode(false);
457
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")));
462
463 if (!sDumpPath.isEmpty() && bDoSaveForVisualControl)
464 {
465 SvFileStream aNew(sDumpPath + "content.bmp", StreamMode::WRITE | StreamMode::TRUNC);
466 Bitmap aContent(mpContent->GetBitmap(aEmptyPoint, aSizePixel));
467 WriteDIB(aContent, aNew, false, true);
468 }
469#endif
470
471 // during painting the buffer, disable evtl. set RasterOp (may be RasterOp::Xor)
472 const RasterOp aOrigRasterOp(mrOutDev.GetRasterOp());
473 mrOutDev.SetRasterOp(RasterOp::OverPaint);
474
475 if (mpAlpha)
476 {
477 mpAlpha->EnableMapMode(false);
478 AlphaMask aAlphaMask(mpAlpha->GetBitmap(aEmptyPoint, aSizePixel));
479 aAlphaMask.Invert(); // convert transparency to alpha
480
481#ifdef DBG_UTIL
482 if (!sDumpPath.isEmpty() && bDoSaveForVisualControl)
483 {
484 SvFileStream aNew(sDumpPath + "transparence.bmp",
485 StreamMode::WRITE | StreamMode::TRUNC);
486 WriteDIB(aAlphaMask.GetBitmap(), aNew, false, true);
487 }
488#endif
489
490 Bitmap aContent(mpContent->GetBitmap(aEmptyPoint, aSizePixel));
491 mrOutDev.DrawBitmapEx(maDestPixel.TopLeft(), BitmapEx(aContent, aAlphaMask));
492 }
493 else if (0.0 != fTrans)
494 {
495 const Bitmap aContent(mpContent->GetBitmap(aEmptyPoint, aSizePixel));
496
497#ifdef SPEED_COMPARE
498 static bool bCompareFormerAndNewTimings(true);
499
500 if (bCompareFormerAndNewTimings)
501 {
502 doSpeedCompare(fTrans, aContent, maDestPixel, mrOutDev);
503 }
504 else
505#endif
506 // Note: this extra scope is needed due to 'clang plugin indentation'. It complains
507 // that lines 494 and (now) 539 are 'statement mis-aligned compared to neighbours'.
508 // That is true if SPEED_COMPARE is not defined. Not nice, but have to fix this.
509 {
510 // For the case we have a unified transparency value there is a former
511 // and new method to paint that which can be used. To decide on measurements,
512 // I added 'doSpeedCompare' above which can be activated by defining
513 // SPEED_COMPARE at the top of this file.
514 // I added the used Testdoc: blurplay3.odg as
515 // https://bugs.documentfoundation.org/attachment.cgi?id=182463
516 // I did measure on
517 //
518 // Linux Dbg:
519 // Former: 21 New: 32 It got WORSE (factor 1.523810) (av. last 500 Former/New is 0.968533, WorseTotal: 515, BetterTotal: 934)
520 //
521 // Linux Pro:
522 // Former: 27 New: 44 It got WORSE (factor 1.629630) (av. last 500 Former/New is 0.923256, WorseTotal: 433, BetterTotal: 337)
523 //
524 // Win Dbg:
525 // Former: 21 New: 78 It got WORSE (factor 3.714286) (av. last 500 Former/New is 1.007176, WorseTotal: 85, BetterTotal: 1428)
526 //
527 // Win Pro:
528 // Former: 3 New: 4 It got WORSE (factor 1.333333) (av. last 500 Former/New is 1.054167, WorseTotal: 143, BetterTotal: 3909)
529 //
530 // Note: I am aware that the Dbg are of limited usefulness, but include them here
531 // for reference.
532 //
533 // The important part is "av. last 500 Former/New is %ld" which describes the averaged factor from Former/New
534 // over the last 500 measurements. When < 1.0 Former is better (Linux), > 1.0 (Win) New is better. Since the
535 // factor on Win is still close to 1.0 what means we lose nearly nothing and Linux Former is better, I will
536 // use Former for now.
537 //
538 // To easily allow to change this (maybe system-dependent) I add a static switch here,
539 // also for eventually experimenting (hint: can be changed in the debugger).
540 static bool bUseNew(false);
541
542 if (bUseNew)
543 {
544 // New method using DrawTransformedBitmapEx & fTrans directly
546 aSizePixel.Width(), aSizePixel.Height(),
548 maDestPixel.TopLeft().Y()),
549 BitmapEx(aContent), 1 - fTrans);
550 }
551 else
552 {
553 // "Former" method using a temporary AlphaMask & DrawBitmapEx
554 sal_uInt8 nMaskValue(static_cast<sal_uInt8>(basegfx::fround(fTrans * 255.0)));
555 const AlphaMask aAlphaMask(aSizePixel, &nMaskValue);
556 mrOutDev.DrawBitmapEx(maDestPixel.TopLeft(), BitmapEx(aContent, aAlphaMask));
557 }
558 }
559 }
560 else
561 {
562 mrOutDev.DrawOutDev(maDestPixel.TopLeft(), aSizePixel, aEmptyPoint, aSizePixel, *mpContent);
563 }
564
565 mrOutDev.SetRasterOp(aOrigRasterOp);
566 mrOutDev.EnableMapMode(bWasEnabledDst);
567}
568
570{
571 SAL_WARN_IF(!mpContent, "drawinglayer",
572 "impBufferDevice: No content, check isVisible() before accessing (!)");
573 return *mpContent;
574}
575
577{
578 SAL_WARN_IF(!mpContent, "drawinglayer",
579 "impBufferDevice: No content, check isVisible() before accessing (!)");
580 if (!mpAlpha)
581 {
583 mpAlpha->SetMapMode(mpContent->GetMapMode());
584
585 // copy AA flag for new target; masking needs to be smooth
586 mpAlpha->SetAntialiasing(mpContent->GetAntialiasing());
587 }
588
589 return *mpAlpha;
590}
591} // end of namespace drawinglayer
592
593/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
RasterOp
Bitmap const & GetBitmap() const
bool Invert()
void SetOrigin(const Point &rOrigin)
void EnableMapMode(bool bEnable=true)
basegfx::B2DHomMatrix GetViewTransformation() const
void DrawBitmapEx(const Point &rDestPt, const BitmapEx &rBitmapEx)
void DrawTransformedBitmapEx(const basegfx::B2DHomMatrix &rTransformation, const BitmapEx &rBitmapEx, double fAlpha=1.0)
RasterOp GetRasterOp() const
SAL_WARN_UNUSED_RESULT Point PixelToLogic(const Point &rDevicePt) const
Size GetOutputSizePixel() const
virtual sal_uInt16 GetBitCount() const
void SetRasterOp(RasterOp eRasterOp)
SAL_DLLPRIVATE void DrawOutDev(const Point &, const Size &, const Point &, const Size &, const Printer &)=delete
const MapMode & GetMapMode() const
AntialiasingFlags GetAntialiasing() const
OutDevType GetOutDevType() const
bool IsMapModeEnabled() const
constexpr tools::Long Y() const
constexpr tools::Long X() const
constexpr tools::Long getHeight() const
constexpr tools::Long Height() const
constexpr tools::Long getWidth() const
constexpr tools::Long Width() const
virtual void Invoke() override
void disposeAndClear()
static VclPtr< reference_type > Create(Arg &&... arg)
BASEGFX_DLLPUBLIC void transform(const B2DHomMatrix &rMatrix)
TYPE getMaxX() const
TYPE getMinX() const
TYPE getMinY() const
TYPE getMaxY() const
impBufferDevice(OutputDevice &rOutDev, const basegfx::B2DRange &rRange)
constexpr Point TopLeft() const
constexpr Size GetSize() const
tools::Rectangle & Intersection(const tools::Rectangle &rRect)
static sal_uInt64 GetSystemTicks()
bool VCL_DLLPUBLIC WriteDIB(const Bitmap &rSource, SvStream &rOStm, bool bCompressed, bool bFileHeader)
aCursorMoveIdle Stop()
sal_Int32 nIndex
uno_Any a
#define SAL_WARN_IF(condition, area, stream)
#define SAL_INFO(area, stream)
VCL_DLLPUBLIC bool isVCLSkiaEnabled()
B2DHomMatrix createScaleTranslateB2DHomMatrix(double fScaleX, double fScaleY, double fTranslateX, double fTranslateY)
B2IRange fround(const B2DRange &rRange)
VDevBuffer & getVDevBuffer()
int sprintf(char(&s)[N], char const *format, T &&... arguments)
OUTDEV_WINDOW
std::mutex m_aMutex
sal_uIntPtr sal_uLong
unsigned char sal_uInt8