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),
49  maMoveEndArea(),
50  mbIsMove( false ),
51  mbIsGenericUpdate( false )
52  {
53  }
54 
55  void operator()( const SpriteRedrawManager::SpriteChangeRecord& rSpriteRecord )
56  {
57  // only deal with change events from the currently
58  // affected sprite
59  if( rSpriteRecord.mpAffectedSprite == mpAffectedSprite )
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 
97  void commit( SpriteRedrawManager::SpriteConnectedRanges& rUpdateCollector ) const
98  {
99  if( mbIsMove )
100  {
101  if( !maMoveStartArea.isEmpty() ||
103  {
104  // if mbIsGenericUpdate is false, this is a
105  // pure move (i.e. no other update
106  // operations). Pass that information on to
107  // the SpriteInfo
108  const bool bIsPureMove( !mbIsGenericUpdate );
109 
110  // ignore the case that start and end update
111  // area overlap - the b2dconnectedranges
112  // handle that, anyway. doing it this way
113  // ensures that we have both old and new area
114  // stored
115 
116  // round all given range up to enclosing
117  // integer rectangle - since the whole thing
118  // here is about
119 
120  // first, draw the new sprite position
121  rUpdateCollector.addRange(
123  SpriteRedrawManager::SpriteInfo(
126  true,
127  bIsPureMove ) );
128 
129  // then, clear the old place (looks smoother
130  // this way)
131  rUpdateCollector.addRange(
133  SpriteRedrawManager::SpriteInfo(
136  true,
137  bIsPureMove ) );
138  }
139  }
140  else if( mbIsGenericUpdate &&
142  {
143  rUpdateCollector.addRange(
145  SpriteRedrawManager::SpriteInfo(
148  true ) );
149  }
150  }
151 
152  private:
156 
158  bool mbIsMove;
159 
162  };
163 
164 
168  class SpriteUpdater
169  {
170  public:
181  SpriteUpdater( SpriteRedrawManager::SpriteConnectedRanges& rUpdater,
182  const SpriteRedrawManager::VectorOfChangeRecords& rChangeContainer ) :
183  mrUpdater( rUpdater ),
184  mrChangeContainer( rChangeContainer )
185  {
186  }
187 
198  void operator()( const Sprite::Reference& rSprite )
199  {
200  SpriteTracer aSpriteTracer( rSprite );
201 
202  for (auto const& aChange : mrChangeContainer)
203  aSpriteTracer( aChange );
204 
205  aSpriteTracer.commit( mrUpdater );
206  }
207 
208  private:
211  };
212  }
213 
215  {
216  // TODO(T3): This is NOT thread safe at all. This only works
217  // under the assumption that NOBODY changes ANYTHING
218  // concurrently, while this method is on the stack. We should
219  // really rework the canvas::Sprite interface, in such a way
220  // that it dumps ALL its state with a single, atomic
221  // call. Then, we store that state locally. This prolly goes
222  // in line with the problem of having sprite state available
223  // for the frame before the last frame; plus, it avoids
224  // frequent locks of the object mutexes
225  SpriteWeakOrder aSpriteComparator;
226 
227  // put all sprites that have changed content into update areas
228  for( const auto& pSprite : maSprites )
229  {
230  if( pSprite->isContentChanged() )
231  const_cast< SpriteRedrawManager* >( this )->updateSprite( pSprite,
232  pSprite->getPosPixel(),
233  pSprite->getUpdateArea() );
234  }
235 
236  // sort sprites after prio
237  VectorOfSprites aSortedSpriteVector;
238  std::copy( maSprites.begin(),
239  maSprites.end(),
240  std::back_insert_iterator< VectorOfSprites >(aSortedSpriteVector) );
241  std::sort( aSortedSpriteVector.begin(),
242  aSortedSpriteVector.end(),
243  aSpriteComparator );
244 
245  // extract all referenced sprites from the maChangeRecords
246  // (copy sprites, make the list unique, regarding the
247  // sprite pointer). This assumes that, until this scope
248  // ends, nobody changes the maChangeRecords vector!
249  VectorOfSprites aUpdatableSprites;
250  for( const auto& rChangeRecord : maChangeRecords )
251  {
252  const Sprite::Reference& rSprite( rChangeRecord.getSprite() );
253  if( rSprite.is() )
254  aUpdatableSprites.push_back( rSprite );
255  }
256 
257  std::sort( aUpdatableSprites.begin(),
258  aUpdatableSprites.end(),
259  aSpriteComparator );
260 
261  VectorOfSprites::iterator aEnd=
262  std::unique( aUpdatableSprites.begin(),
263  aUpdatableSprites.end() );
264 
265  // for each unique sprite, check the change event vector,
266  // calculate the update operation from that, and add the
267  // result to the aUpdateArea.
268  std::for_each( aUpdatableSprites.begin(),
269  aEnd,
270  SpriteUpdater( rUpdateAreas,
271  maChangeRecords) );
272 
273  // TODO(P2): Implement your own output iterator adapter, to
274  // avoid that totally superfluous temp aUnchangedSprites
275  // vector.
276 
277  // add all sprites to rUpdateAreas, that are _not_ already
278  // contained in the uniquified vector of changed ones
279  // (i.e. the difference between aSortedSpriteVector and
280  // aUpdatableSprites).
281  VectorOfSprites aUnchangedSprites;
282  std::set_difference( aSortedSpriteVector.begin(),
283  aSortedSpriteVector.end(),
284  aUpdatableSprites.begin(),
285  aEnd,
286  std::back_insert_iterator< VectorOfSprites >(aUnchangedSprites),
287  aSpriteComparator );
288 
289  // add each remaining unchanged sprite to connected ranges,
290  // marked as "don't need update"
291  for( const auto& pUnchangedSprite : aUnchangedSprites )
292  {
293  const ::basegfx::B2DRange& rUpdateArea( pUnchangedSprite->getUpdateArea() );
294  rUpdateAreas.addRange(
296  SpriteInfo( pUnchangedSprite,
297  rUpdateArea,
298  false ) );
299  }
300  }
301 
302 #if OSL_DEBUG_LEVEL > 0
303  static bool impIsEqualB2DRange(const basegfx::B2DRange& rRangeA, const basegfx::B2DRange& rRangeB, double fSmallValue)
304  {
305  return fabs(rRangeB.getMinX() - rRangeA.getMinX()) <= fSmallValue
306  && fabs(rRangeB.getMinY() - rRangeA.getMinY()) <= fSmallValue
307  && fabs(rRangeB.getMaxX() - rRangeA.getMaxX()) <= fSmallValue
308  && fabs(rRangeB.getMaxY() - rRangeA.getMaxY()) <= fSmallValue;
309  }
310 
311  static bool impIsEqualB2DVector(const basegfx::B2DVector& rVecA, const basegfx::B2DVector& rVecB, double fSmallValue)
312  {
313  return fabs(rVecB.getX() - rVecA.getX()) <= fSmallValue
314  && fabs(rVecB.getY() - rVecA.getY()) <= fSmallValue;
315  }
316 #endif
317 
319  ::basegfx::B2DRectangle& o_rMoveEnd,
320  const UpdateArea& rUpdateArea,
321  std::size_t nNumSprites ) const
322  {
323  // check for a solitary move, which consists of exactly two
324  // pure-move entries, the first with valid, the second with
325  // invalid sprite (see SpriteTracer::commit()). Note that we
326  // cannot simply store some flag in SpriteTracer::commit()
327  // above and just check that here, since during the connected
328  // range calculations, other sprites might get merged into the
329  // same region (thus spoiling the scrolling move
330  // optimization).
331  if( nNumSprites != 2 )
332  return false;
333 
334  const SpriteConnectedRanges::ComponentListType::const_iterator aFirst(
335  rUpdateArea.maComponentList.begin() );
336  SpriteConnectedRanges::ComponentListType::const_iterator aSecond(
337  aFirst );
338  ++aSecond;
339 
340  if( !aFirst->second.isPureMove() ||
341  !aSecond->second.isPureMove() ||
342  !aFirst->second.getSprite().is() ||
343  // use _true_ update area, not the rounded version
344  !aFirst->second.getSprite()->isAreaUpdateOpaque( aFirst->second.getUpdateArea() ) ||
345  aSecond->second.getSprite().is() )
346  {
347  // either no move update, or incorrect sprite, or sprite
348  // content not fully opaque over update region.
349  return false;
350  }
351 
352  o_rMoveStart = aSecond->second.getUpdateArea();
353  o_rMoveEnd = aFirst->second.getUpdateArea();
354 
355 #if OSL_DEBUG_LEVEL > 0
356  ::basegfx::B2DRectangle aTotalBounds( o_rMoveStart );
357  aTotalBounds.expand( o_rMoveEnd );
358 
360  "canvas",
361  "SpriteRedrawManager::isAreaUpdateScroll(): sprite area and total area mismatch");
362  SAL_WARN_IF(!impIsEqualB2DVector(o_rMoveStart.getRange(), o_rMoveEnd.getRange(), 0.5),
363  "canvas",
364  "SpriteRedrawManager::isAreaUpdateScroll(): scroll start and end area have mismatching size");
365 #endif
366 
367  return true;
368  }
369 
370  bool SpriteRedrawManager::isAreaUpdateNotOpaque( const ::basegfx::B2DRectangle& rUpdateRect,
371  const AreaComponent& rComponent ) const
372  {
373  const Sprite::Reference& pAffectedSprite( rComponent.second.getSprite() );
374 
375  if( !pAffectedSprite.is() )
376  return true; // no sprite, no opaque update!
377 
378  return !pAffectedSprite->isAreaUpdateOpaque( rUpdateRect );
379  }
380 
382  std::size_t nNumSprites ) const
383  {
384  // check whether the sprites in the update area's list will
385  // fully cover the given area _and_ do that in an opaque way
386  // (i.e. no alpha, no non-rectangular sprite content).
387 
388  // TODO(P1): Come up with a smarter early-exit criterion here
389  // (though, I think, the case that _lots_ of sprites _fully_
390  // cover a rectangular area _without_ any holes is extremely
391  // improbable)
392 
393  // avoid checking large number of sprites (and probably fail,
394  // anyway). Note: the case nNumSprites < 1 should normally not
395  // happen, as handleArea() calls backgroundPaint() then.
396  if( nNumSprites > 3 || nNumSprites < 1 )
397  return false;
398 
399  // now, calc the _true_ update area, by merging all sprite's
400  // true update areas into one rectangle
401  ::basegfx::B2DRange aTrueArea( rUpdateArea.maComponentList.begin()->second.getUpdateArea() );
402  for( const auto& rArea : rUpdateArea.maComponentList )
403  aTrueArea.expand(rArea.second.getUpdateArea());
404 
405  const SpriteConnectedRanges::ComponentListType::const_iterator aEnd(
406  rUpdateArea.maComponentList.end() );
407 
408  // and check whether _any_ of the sprites tells that its area
409  // update will not be opaque.
410  return std::none_of( rUpdateArea.maComponentList.begin(),
411  aEnd,
412  [&aTrueArea, this]( const std::pair< ::basegfx::B2DRange, SpriteInfo >& cp )
413  { return this->isAreaUpdateNotOpaque(aTrueArea, cp); } );
414  }
415 
416  bool SpriteRedrawManager::areSpritesChanged( const UpdateArea& rUpdateArea ) const
417  {
418  // check whether SpriteInfo::needsUpdate returns false for
419  // all elements of this area's contained sprites
420 
421  // if not a single changed sprite found - just ignore this
422  // component (return false)
423  const SpriteConnectedRanges::ComponentListType::const_iterator aEnd(
424  rUpdateArea.maComponentList.end() );
425  return std::any_of( rUpdateArea.maComponentList.begin(),
426  aEnd,
427  []( const std::pair< ::basegfx::B2DRange, SpriteInfo >& cp )
428  { return cp.second.needsUpdate(); } );
429  }
430 
432  maSprites(),
433  maChangeRecords()
434  {
435  }
436 
438  {
439  // drop all references
440  maChangeRecords.clear();
441 
442  // dispose all sprites - the spritecanvas, and by delegation,
443  // this object, is the owner of the sprites. After all, a
444  // sprite without a canvas to render into makes not terribly
445  // much sense.
446  for( const auto& rCurr : boost::adaptors::reverse(maSprites) )
447  rCurr->dispose();
448 
449  maSprites.clear();
450  }
451 
453  {
454  maChangeRecords.clear();
455  }
456 
458  {
459  maSprites.push_back( rSprite );
460  }
461 
463  {
464  maSprites.erase(std::remove(maSprites.begin(), maSprites.end(), rSprite), maSprites.end());
465  }
466 
468  const ::basegfx::B2DPoint& rOldPos,
469  const ::basegfx::B2DPoint& rNewPos,
470  const ::basegfx::B2DVector& rSpriteSize )
471  {
472  maChangeRecords.emplace_back( rSprite,
473  rOldPos,
474  rNewPos,
475  rSpriteSize );
476  }
477 
479  const ::basegfx::B2DPoint& rPos,
480  const ::basegfx::B2DRange& rUpdateArea )
481  {
482  maChangeRecords.emplace_back( rSprite,
483  rPos,
484  rUpdateArea );
485  }
486 
487 }
488 
489 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
SpriteRedrawManager::SpriteConnectedRanges & mrUpdater
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
double getX() const
double getY() const
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)
SpriteVector maSprites
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.