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  return;
61 
62  switch( rSpriteRecord.meChangeType )
63  {
65  if( !mbIsMove )
66  {
67  // no move yet - this must be the first one
69  rSpriteRecord.maOldPos,
70  rSpriteRecord.maOldPos + rSpriteRecord.maUpdateArea.getRange() );
71  mbIsMove = true;
72  }
73 
74  maMoveEndArea = rSpriteRecord.maUpdateArea;
75  break;
76 
78  // update end update area of the
79  // sprite. Thus, every update() action
80  // _after_ the last move will correctly
81  // update the final repaint area. And this
82  // does not interfere with subsequent
83  // moves, because moves always perform a
84  // hard set of maMoveEndArea to their
85  // stored value
86  maMoveEndArea.expand( rSpriteRecord.maUpdateArea );
87  mbIsGenericUpdate = true;
88  break;
89 
90  default:
91  ENSURE_OR_THROW( false,
92  "Unexpected case in SpriteUpdater::operator()" );
93  break;
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( maSprites.begin(), maSprites.end() );
238  std::sort( aSortedSpriteVector.begin(),
239  aSortedSpriteVector.end(),
240  aSpriteComparator );
241 
242  // extract all referenced sprites from the maChangeRecords
243  // (copy sprites, make the list unique, regarding the
244  // sprite pointer). This assumes that, until this scope
245  // ends, nobody changes the maChangeRecords vector!
246  VectorOfSprites aUpdatableSprites;
247  for( const auto& rChangeRecord : maChangeRecords )
248  {
249  const Sprite::Reference& rSprite( rChangeRecord.getSprite() );
250  if( rSprite.is() )
251  aUpdatableSprites.push_back( rSprite );
252  }
253 
254  std::sort( aUpdatableSprites.begin(),
255  aUpdatableSprites.end(),
256  aSpriteComparator );
257 
258  VectorOfSprites::iterator aEnd=
259  std::unique( aUpdatableSprites.begin(),
260  aUpdatableSprites.end() );
261 
262  // for each unique sprite, check the change event vector,
263  // calculate the update operation from that, and add the
264  // result to the aUpdateArea.
265  std::for_each( aUpdatableSprites.begin(),
266  aEnd,
267  SpriteUpdater( rUpdateAreas,
268  maChangeRecords) );
269 
270  // TODO(P2): Implement your own output iterator adapter, to
271  // avoid that totally superfluous temp aUnchangedSprites
272  // vector.
273 
274  // add all sprites to rUpdateAreas, that are _not_ already
275  // contained in the uniquified vector of changed ones
276  // (i.e. the difference between aSortedSpriteVector and
277  // aUpdatableSprites).
278  VectorOfSprites aUnchangedSprites;
279  std::set_difference( aSortedSpriteVector.begin(),
280  aSortedSpriteVector.end(),
281  aUpdatableSprites.begin(),
282  aEnd,
283  std::back_insert_iterator< VectorOfSprites >(aUnchangedSprites),
284  aSpriteComparator );
285 
286  // add each remaining unchanged sprite to connected ranges,
287  // marked as "don't need update"
288  for( const auto& pUnchangedSprite : aUnchangedSprites )
289  {
290  const ::basegfx::B2DRange& rUpdateArea( pUnchangedSprite->getUpdateArea() );
291  rUpdateAreas.addRange(
293  SpriteInfo( pUnchangedSprite,
294  rUpdateArea,
295  false ) );
296  }
297  }
298 
299 #if OSL_DEBUG_LEVEL > 0
300  static bool impIsEqualB2DRange(const basegfx::B2DRange& rRangeA, const basegfx::B2DRange& rRangeB, double fSmallValue)
301  {
302  return fabs(rRangeB.getMinX() - rRangeA.getMinX()) <= fSmallValue
303  && fabs(rRangeB.getMinY() - rRangeA.getMinY()) <= fSmallValue
304  && fabs(rRangeB.getMaxX() - rRangeA.getMaxX()) <= fSmallValue
305  && fabs(rRangeB.getMaxY() - rRangeA.getMaxY()) <= fSmallValue;
306  }
307 
308  static bool impIsEqualB2DVector(const basegfx::B2DVector& rVecA, const basegfx::B2DVector& rVecB, double fSmallValue)
309  {
310  return fabs(rVecB.getX() - rVecA.getX()) <= fSmallValue
311  && fabs(rVecB.getY() - rVecA.getY()) <= fSmallValue;
312  }
313 #endif
314 
316  ::basegfx::B2DRectangle& o_rMoveEnd,
317  const UpdateArea& rUpdateArea,
318  std::size_t nNumSprites ) const
319  {
320  // check for a solitary move, which consists of exactly two
321  // pure-move entries, the first with valid, the second with
322  // invalid sprite (see SpriteTracer::commit()). Note that we
323  // cannot simply store some flag in SpriteTracer::commit()
324  // above and just check that here, since during the connected
325  // range calculations, other sprites might get merged into the
326  // same region (thus spoiling the scrolling move
327  // optimization).
328  if( nNumSprites != 2 )
329  return false;
330 
331  const SpriteConnectedRanges::ComponentListType::const_iterator aFirst(
332  rUpdateArea.maComponentList.begin() );
333  SpriteConnectedRanges::ComponentListType::const_iterator aSecond(
334  aFirst );
335  ++aSecond;
336 
337  if( !aFirst->second.isPureMove() ||
338  !aSecond->second.isPureMove() ||
339  !aFirst->second.getSprite().is() ||
340  // use _true_ update area, not the rounded version
341  !aFirst->second.getSprite()->isAreaUpdateOpaque( aFirst->second.getUpdateArea() ) ||
342  aSecond->second.getSprite().is() )
343  {
344  // either no move update, or incorrect sprite, or sprite
345  // content not fully opaque over update region.
346  return false;
347  }
348 
349  o_rMoveStart = aSecond->second.getUpdateArea();
350  o_rMoveEnd = aFirst->second.getUpdateArea();
351 
352 #if OSL_DEBUG_LEVEL > 0
353  ::basegfx::B2DRectangle aTotalBounds( o_rMoveStart );
354  aTotalBounds.expand( o_rMoveEnd );
355 
357  "canvas",
358  "SpriteRedrawManager::isAreaUpdateScroll(): sprite area and total area mismatch");
359  SAL_WARN_IF(!impIsEqualB2DVector(o_rMoveStart.getRange(), o_rMoveEnd.getRange(), 0.5),
360  "canvas",
361  "SpriteRedrawManager::isAreaUpdateScroll(): scroll start and end area have mismatching size");
362 #endif
363 
364  return true;
365  }
366 
367  bool SpriteRedrawManager::isAreaUpdateNotOpaque( const ::basegfx::B2DRectangle& rUpdateRect,
368  const AreaComponent& rComponent ) const
369  {
370  const Sprite::Reference& pAffectedSprite( rComponent.second.getSprite() );
371 
372  if( !pAffectedSprite.is() )
373  return true; // no sprite, no opaque update!
374 
375  return !pAffectedSprite->isAreaUpdateOpaque( rUpdateRect );
376  }
377 
379  std::size_t nNumSprites ) const
380  {
381  // check whether the sprites in the update area's list will
382  // fully cover the given area _and_ do that in an opaque way
383  // (i.e. no alpha, no non-rectangular sprite content).
384 
385  // TODO(P1): Come up with a smarter early-exit criterion here
386  // (though, I think, the case that _lots_ of sprites _fully_
387  // cover a rectangular area _without_ any holes is extremely
388  // improbable)
389 
390  // avoid checking large number of sprites (and probably fail,
391  // anyway). Note: the case nNumSprites < 1 should normally not
392  // happen, as handleArea() calls backgroundPaint() then.
393  if( nNumSprites > 3 || nNumSprites < 1 )
394  return false;
395 
396  // now, calc the _true_ update area, by merging all sprite's
397  // true update areas into one rectangle
398  ::basegfx::B2DRange aTrueArea( rUpdateArea.maComponentList.begin()->second.getUpdateArea() );
399  for( const auto& rArea : rUpdateArea.maComponentList )
400  aTrueArea.expand(rArea.second.getUpdateArea());
401 
402  const SpriteConnectedRanges::ComponentListType::const_iterator aEnd(
403  rUpdateArea.maComponentList.end() );
404 
405  // and check whether _any_ of the sprites tells that its area
406  // update will not be opaque.
407  return std::none_of( rUpdateArea.maComponentList.begin(),
408  aEnd,
409  [&aTrueArea, this]( const std::pair< ::basegfx::B2DRange, SpriteInfo >& cp )
410  { return this->isAreaUpdateNotOpaque(aTrueArea, cp); } );
411  }
412 
413  bool SpriteRedrawManager::areSpritesChanged( const UpdateArea& rUpdateArea ) const
414  {
415  // check whether SpriteInfo::needsUpdate returns false for
416  // all elements of this area's contained sprites
417 
418  // if not a single changed sprite found - just ignore this
419  // component (return false)
420  const SpriteConnectedRanges::ComponentListType::const_iterator aEnd(
421  rUpdateArea.maComponentList.end() );
422  return std::any_of( rUpdateArea.maComponentList.begin(),
423  aEnd,
424  []( const std::pair< ::basegfx::B2DRange, SpriteInfo >& cp )
425  { return cp.second.needsUpdate(); } );
426  }
427 
429  maSprites(),
430  maChangeRecords()
431  {
432  }
433 
435  {
436  // drop all references
437  maChangeRecords.clear();
438 
439  // dispose all sprites - the spritecanvas, and by delegation,
440  // this object, is the owner of the sprites. After all, a
441  // sprite without a canvas to render into makes not terribly
442  // much sense.
443  for( const auto& rCurr : boost::adaptors::reverse(maSprites) )
444  rCurr->dispose();
445 
446  maSprites.clear();
447  }
448 
450  {
451  maChangeRecords.clear();
452  }
453 
455  {
456  maSprites.push_back( rSprite );
457  }
458 
460  {
461  maSprites.erase(std::remove(maSprites.begin(), maSprites.end(), rSprite), maSprites.end());
462  }
463 
465  const ::basegfx::B2DPoint& rOldPos,
466  const ::basegfx::B2DPoint& rNewPos,
467  const ::basegfx::B2DVector& rSpriteSize )
468  {
469  maChangeRecords.emplace_back( rSprite,
470  rOldPos,
471  rNewPos,
472  rSpriteSize );
473  }
474 
476  const ::basegfx::B2DPoint& rPos,
477  const ::basegfx::B2DRange& rUpdateArea )
478  {
479  maChangeRecords.emplace_back( rSprite,
480  rPos,
481  rUpdateArea );
482  }
483 
484 }
485 
486 /* 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.