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