LibreOffice Module canvas (master) 1
spriteredrawmanager.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
22#include <algorithm>
23
27#include <sal/log.hxx>
28
30#include <boost/range/adaptor/reversed.hpp>
31#include <utility>
32
33namespace canvas
34{
35 namespace
36 {
44 class SpriteTracer
45 {
46 public:
47 explicit SpriteTracer( Sprite::Reference rAffectedSprite ) :
48 mpAffectedSprite(std::move(rAffectedSprite)),
49 mbIsMove( false ),
50 mbIsGenericUpdate( false )
51 {
52 }
53
54 void operator()( const SpriteRedrawManager::SpriteChangeRecord& rSpriteRecord )
55 {
56 // only deal with change events from the currently
57 // affected sprite
58 if( rSpriteRecord.mpAffectedSprite != mpAffectedSprite )
59 return;
60
61 switch( rSpriteRecord.meChangeType )
62 {
64 if( !mbIsMove )
65 {
66 // no move yet - this must be the first one
68 rSpriteRecord.maOldPos,
69 rSpriteRecord.maOldPos + rSpriteRecord.maUpdateArea.getRange() );
70 mbIsMove = true;
71 }
72
73 maMoveEndArea = rSpriteRecord.maUpdateArea;
74 break;
75
77 // update end update area of the
78 // sprite. Thus, every update() action
79 // _after_ the last move will correctly
80 // update the final repaint area. And this
81 // does not interfere with subsequent
82 // moves, because moves always perform a
83 // hard set of maMoveEndArea to their
84 // stored value
85 maMoveEndArea.expand( rSpriteRecord.maUpdateArea );
86 mbIsGenericUpdate = true;
87 break;
88
89 default:
90 ENSURE_OR_THROW( false,
91 "Unexpected case in SpriteUpdater::operator()" );
92 break;
93 }
94 }
95
96 void commit( SpriteRedrawManager::SpriteConnectedRanges& rUpdateCollector ) const
97 {
98 if( mbIsMove )
99 {
100 if( !maMoveStartArea.isEmpty() ||
102 {
103 // if mbIsGenericUpdate is false, this is a
104 // pure move (i.e. no other update
105 // operations). Pass that information on to
106 // the SpriteInfo
107 const bool bIsPureMove( !mbIsGenericUpdate );
108
109 // ignore the case that start and end update
110 // area overlap - the b2dconnectedranges
111 // handle that, anyway. doing it this way
112 // ensures that we have both old and new area
113 // stored
114
115 // round all given range up to enclosing
116 // integer rectangle - since the whole thing
117 // here is about
118
119 // first, draw the new sprite position
120 rUpdateCollector.addRange(
121 ::basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange( maMoveEndArea ),
122 SpriteRedrawManager::SpriteInfo(
125 true,
126 bIsPureMove ) );
127
128 // then, clear the old place (looks smoother
129 // this way)
130 rUpdateCollector.addRange(
131 ::basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange( maMoveStartArea ),
132 SpriteRedrawManager::SpriteInfo(
135 true,
136 bIsPureMove ) );
137 }
138 }
139 else if( mbIsGenericUpdate &&
141 {
142 rUpdateCollector.addRange(
143 ::basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange( maMoveEndArea ),
144 SpriteRedrawManager::SpriteInfo(
147 true ) );
148 }
149 }
150
151 private:
155
158
161 };
162
163
167 class SpriteUpdater
168 {
169 public:
180 SpriteUpdater( SpriteRedrawManager::SpriteConnectedRanges& rUpdater,
181 const SpriteRedrawManager::VectorOfChangeRecords& rChangeContainer ) :
182 mrUpdater( rUpdater ),
183 mrChangeContainer( rChangeContainer )
184 {
185 }
186
197 void operator()( const Sprite::Reference& rSprite )
198 {
199 SpriteTracer aSpriteTracer( rSprite );
200
201 for (auto const& aChange : mrChangeContainer)
202 aSpriteTracer( aChange );
203
204 aSpriteTracer.commit( mrUpdater );
205 }
206
207 private:
210 };
211 }
212
214 {
215 // TODO(T3): This is NOT thread safe at all. This only works
216 // under the assumption that NOBODY changes ANYTHING
217 // concurrently, while this method is on the stack. We should
218 // really rework the canvas::Sprite interface, in such a way
219 // that it dumps ALL its state with a single, atomic
220 // call. Then, we store that state locally. This prolly goes
221 // in line with the problem of having sprite state available
222 // for the frame before the last frame; plus, it avoids
223 // frequent locks of the object mutexes
224 SpriteWeakOrder aSpriteComparator;
225
226 // put all sprites that have changed content into update areas
227 for( const auto& pSprite : maSprites )
228 {
229 if( pSprite->isContentChanged() )
230 const_cast< SpriteRedrawManager* >( this )->updateSprite( pSprite,
231 pSprite->getPosPixel(),
232 pSprite->getUpdateArea() );
233 }
234
235 // sort sprites after prio
236 VectorOfSprites aSortedSpriteVector( maSprites.begin(), maSprites.end() );
237 std::sort( aSortedSpriteVector.begin(),
238 aSortedSpriteVector.end(),
239 aSpriteComparator );
240
241 // extract all referenced sprites from the maChangeRecords
242 // (copy sprites, make the list unique, regarding the
243 // sprite pointer). This assumes that, until this scope
244 // ends, nobody changes the maChangeRecords vector!
245 VectorOfSprites aUpdatableSprites;
246 for( const auto& rChangeRecord : maChangeRecords )
247 {
248 const Sprite::Reference& rSprite( rChangeRecord.getSprite() );
249 if( rSprite.is() )
250 aUpdatableSprites.push_back( rSprite );
251 }
252
253 std::sort( aUpdatableSprites.begin(),
254 aUpdatableSprites.end(),
255 aSpriteComparator );
256
257 VectorOfSprites::iterator aEnd=
258 std::unique( aUpdatableSprites.begin(),
259 aUpdatableSprites.end() );
260
261 // for each unique sprite, check the change event vector,
262 // calculate the update operation from that, and add the
263 // result to the aUpdateArea.
264 std::for_each( aUpdatableSprites.begin(),
265 aEnd,
266 SpriteUpdater( rUpdateAreas,
268
269 // TODO(P2): Implement your own output iterator adapter, to
270 // avoid that totally superfluous temp aUnchangedSprites
271 // vector.
272
273 // add all sprites to rUpdateAreas, that are _not_ already
274 // contained in the uniquified vector of changed ones
275 // (i.e. the difference between aSortedSpriteVector and
276 // aUpdatableSprites).
277 VectorOfSprites aUnchangedSprites;
278 std::set_difference( aSortedSpriteVector.begin(),
279 aSortedSpriteVector.end(),
280 aUpdatableSprites.begin(),
281 aEnd,
282 std::back_insert_iterator< VectorOfSprites >(aUnchangedSprites),
283 aSpriteComparator );
284
285 // add each remaining unchanged sprite to connected ranges,
286 // marked as "don't need update"
287 for( const auto& pUnchangedSprite : aUnchangedSprites )
288 {
289 const ::basegfx::B2DRange& rUpdateArea( pUnchangedSprite->getUpdateArea() );
290 rUpdateAreas.addRange(
291 ::basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange( rUpdateArea ),
292 SpriteInfo( pUnchangedSprite,
293 rUpdateArea,
294 false ) );
295 }
296 }
297
298#if OSL_DEBUG_LEVEL > 0
299 static bool impIsEqualB2DRange(const basegfx::B2DRange& rRangeA, const basegfx::B2DRange& rRangeB, double fSmallValue)
300 {
301 return fabs(rRangeB.getMinX() - rRangeA.getMinX()) <= fSmallValue
302 && fabs(rRangeB.getMinY() - rRangeA.getMinY()) <= fSmallValue
303 && fabs(rRangeB.getMaxX() - rRangeA.getMaxX()) <= fSmallValue
304 && fabs(rRangeB.getMaxY() - rRangeA.getMaxY()) <= fSmallValue;
305 }
306
307 static bool impIsEqualB2DVector(const basegfx::B2DVector& rVecA, const basegfx::B2DVector& rVecB, double fSmallValue)
308 {
309 return fabs(rVecB.getX() - rVecA.getX()) <= fSmallValue
310 && fabs(rVecB.getY() - rVecA.getY()) <= fSmallValue;
311 }
312#endif
313
315 ::basegfx::B2DRectangle& o_rMoveEnd,
316 const UpdateArea& rUpdateArea,
317 std::size_t nNumSprites ) const
318 {
319 // check for a solitary move, which consists of exactly two
320 // pure-move entries, the first with valid, the second with
321 // invalid sprite (see SpriteTracer::commit()). Note that we
322 // cannot simply store some flag in SpriteTracer::commit()
323 // above and just check that here, since during the connected
324 // range calculations, other sprites might get merged into the
325 // same region (thus spoiling the scrolling move
326 // optimization).
327 if( nNumSprites != 2 )
328 return false;
329
330 const SpriteConnectedRanges::ComponentListType::const_iterator aFirst(
331 rUpdateArea.maComponentList.begin() );
332 SpriteConnectedRanges::ComponentListType::const_iterator aSecond(
333 aFirst );
334 ++aSecond;
335
336 if( !aFirst->second.isPureMove() ||
337 !aSecond->second.isPureMove() ||
338 !aFirst->second.getSprite().is() ||
339 // use _true_ update area, not the rounded version
340 !aFirst->second.getSprite()->isAreaUpdateOpaque( aFirst->second.getUpdateArea() ) ||
341 aSecond->second.getSprite().is() )
342 {
343 // either no move update, or incorrect sprite, or sprite
344 // content not fully opaque over update region.
345 return false;
346 }
347
348 o_rMoveStart = aSecond->second.getUpdateArea();
349 o_rMoveEnd = aFirst->second.getUpdateArea();
350
351#if OSL_DEBUG_LEVEL > 0
352 ::basegfx::B2DRectangle aTotalBounds( o_rMoveStart );
353 aTotalBounds.expand( o_rMoveEnd );
354
356 "canvas",
357 "SpriteRedrawManager::isAreaUpdateScroll(): sprite area and total area mismatch");
358 SAL_WARN_IF(!impIsEqualB2DVector(o_rMoveStart.getRange(), o_rMoveEnd.getRange(), 0.5),
359 "canvas",
360 "SpriteRedrawManager::isAreaUpdateScroll(): scroll start and end area have mismatching size");
361#endif
362
363 return true;
364 }
365
366 bool SpriteRedrawManager::isAreaUpdateNotOpaque( const ::basegfx::B2DRectangle& rUpdateRect,
367 const AreaComponent& rComponent ) const
368 {
369 const Sprite::Reference& pAffectedSprite( rComponent.second.getSprite() );
370
371 if( !pAffectedSprite.is() )
372 return true; // no sprite, no opaque update!
373
374 return !pAffectedSprite->isAreaUpdateOpaque( rUpdateRect );
375 }
376
378 std::size_t nNumSprites ) const
379 {
380 // check whether the sprites in the update area's list will
381 // fully cover the given area _and_ do that in an opaque way
382 // (i.e. no alpha, no non-rectangular sprite content).
383
384 // TODO(P1): Come up with a smarter early-exit criterion here
385 // (though, I think, the case that _lots_ of sprites _fully_
386 // cover a rectangular area _without_ any holes is extremely
387 // improbable)
388
389 // avoid checking large number of sprites (and probably fail,
390 // anyway). Note: the case nNumSprites < 1 should normally not
391 // happen, as handleArea() calls backgroundPaint() then.
392 if( nNumSprites > 3 || nNumSprites < 1 )
393 return false;
394
395 // now, calc the _true_ update area, by merging all sprite's
396 // true update areas into one rectangle
397 ::basegfx::B2DRange aTrueArea( rUpdateArea.maComponentList.begin()->second.getUpdateArea() );
398 for( const auto& rArea : rUpdateArea.maComponentList )
399 aTrueArea.expand(rArea.second.getUpdateArea());
400
401 const SpriteConnectedRanges::ComponentListType::const_iterator aEnd(
402 rUpdateArea.maComponentList.end() );
403
404 // and check whether _any_ of the sprites tells that its area
405 // update will not be opaque.
406 return std::none_of( rUpdateArea.maComponentList.begin(),
407 aEnd,
408 [&aTrueArea, this]( const std::pair< ::basegfx::B2DRange, SpriteInfo >& cp )
409 { return this->isAreaUpdateNotOpaque(aTrueArea, cp); } );
410 }
411
412 bool SpriteRedrawManager::areSpritesChanged( const UpdateArea& rUpdateArea ) const
413 {
414 // check whether SpriteInfo::needsUpdate returns false for
415 // all elements of this area's contained sprites
416
417 // if not a single changed sprite found - just ignore this
418 // component (return false)
419 const SpriteConnectedRanges::ComponentListType::const_iterator aEnd(
420 rUpdateArea.maComponentList.end() );
421 return std::any_of( rUpdateArea.maComponentList.begin(),
422 aEnd,
423 []( const std::pair< ::basegfx::B2DRange, SpriteInfo >& cp )
424 { return cp.second.needsUpdate(); } );
425 }
426
428 {
429 }
430
432 {
433 // drop all references
434 maChangeRecords.clear();
435
436 // dispose all sprites - the spritecanvas, and by delegation,
437 // this object, is the owner of the sprites. After all, a
438 // sprite without a canvas to render into makes not terribly
439 // much sense.
440 for( const auto& rCurr : boost::adaptors::reverse(maSprites) )
441 rCurr->dispose();
442
443 maSprites.clear();
444 }
445
447 {
448 maChangeRecords.clear();
449 }
450
452 {
453 maSprites.push_back( rSprite );
454 }
455
457 {
458 maSprites.erase(std::remove(maSprites.begin(), maSprites.end(), rSprite), maSprites.end());
459 }
460
462 const ::basegfx::B2DPoint& rOldPos,
463 const ::basegfx::B2DPoint& rNewPos,
464 const ::basegfx::B2DVector& rSpriteSize )
465 {
466 maChangeRecords.emplace_back( rSprite,
467 rOldPos,
468 rNewPos,
469 rSpriteSize );
470 }
471
473 const ::basegfx::B2DPoint& rPos,
474 const ::basegfx::B2DRange& rUpdateArea )
475 {
476 maChangeRecords.emplace_back( rSprite,
477 rPos,
478 rUpdateArea );
479 }
480
481}
482
483/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
void addRange(const B2DRange &rRange, const UserData &rUserData)
B2DVector getRange() const
TYPE getMaxX() const
TYPE getMinX() const
TYPE getMinY() const
void expand(const Tuple2D< TYPE > &rTuple)
TYPE getMaxY() const
bool isEmpty() const
TYPE getX() const
TYPE getY() const
Data container for the connected components list.
This class manages smooth SpriteCanvas updates.
::std::vector< SpriteChangeRecord > VectorOfChangeRecords
bool areSpritesChanged(const UpdateArea &rUpdateArea) const
void clearChangeRecords()
Clear sprite change records (typically directly after a screen update)
::basegfx::B2DConnectedRanges< SpriteInfo > SpriteConnectedRanges
void moveSprite(const Sprite::Reference &rSprite, const ::basegfx::B2DPoint &rOldPos, const ::basegfx::B2DPoint &rNewPos, const ::basegfx::B2DVector &rSpriteSize)
bool isAreaUpdateNotOpaque(const ::basegfx::B2DRectangle &rUpdateRect, const AreaComponent &rComponent) const
void showSprite(const Sprite::Reference &rSprite)
bool isAreaUpdateScroll(::basegfx::B2DRectangle &o_rMoveStart, ::basegfx::B2DRectangle &o_rMoveEnd, const UpdateArea &rUpdateArea, ::std::size_t nNumSprites) const
Check whether given update area can be handled by a simple scroll.
void setupUpdateAreas(SpriteConnectedRanges &rUpdateAreas) const
Central method of this class.
void updateSprite(const Sprite::Reference &rSprite, const ::basegfx::B2DPoint &rPos, const ::basegfx::B2DRange &rUpdateArea)
SpriteConnectedRanges::ComponentType AreaComponent
::std::vector< Sprite::Reference > VectorOfSprites
void hideSprite(const Sprite::Reference &rSprite)
void disposing()
Must be called when user of this object gets disposed.
bool isAreaUpdateOpaque(const UpdateArea &rUpdateArea, ::std::size_t nNumSprites) const
VectorOfChangeRecords maChangeRecords
::rtl::Reference< Sprite > Reference
#define ENSURE_OR_THROW(c, m)
#define SAL_WARN_IF(condition, area, stream)
::basegfx::B2DRange b2DSurroundingIntegerRangeFromB2DRange(const ::basegfx::B2DRange &rRange)
static bool impIsEqualB2DVector(const basegfx::B2DVector &rVecA, const basegfx::B2DVector &rVecB, double fSmallValue)
static bool impIsEqualB2DRange(const basegfx::B2DRange &rRangeA, const basegfx::B2DRange &rRangeB, double fSmallValue)
::basegfx::B2DRectangle maMoveEndArea
SpriteRedrawManager::SpriteConnectedRanges & mrUpdater
::basegfx::B2DRectangle maMoveStartArea
Sprite::Reference mpAffectedSprite
const SpriteRedrawManager::VectorOfChangeRecords & mrChangeContainer
bool mbIsMove
True, if at least one move was encountered.
bool mbIsGenericUpdate
True, if at least one generic update was encountered.
Functor providing a StrictWeakOrdering for sprite references.