xref: /trunk/main/canvas/source/tools/spriteredrawmanager.cxx (revision 91144cd0085a7583d2099b982122deb2184ab956)
1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 // MARKER(update_precomp.py): autogen include statement, do not remove
25 #include "precompiled_canvas.hxx"
26 
27 #include <canvas/debug.hxx>
28 #include <tools/diagnose_ex.h>
29 #include <canvas/spriteredrawmanager.hxx>
30 
31 #include <basegfx/range/b2drectangle.hxx>
32 #include <basegfx/tools/canvastools.hxx>
33 #include <basegfx/vector/b2dsize.hxx>
34 #include <basegfx/range/rangeexpander.hxx>
35 
36 #include <algorithm>
37 #include <functional>
38 #include <iterator>
39 #include <boost/bind.hpp>
40 
41 
42 namespace canvas
43 {
44     namespace
45     {
46         /** Helper class to condense sprite updates into a single action
47 
48             This class tracks the sprite changes over the recorded
49             change list, and generates a single update action from
50             that (note that per screen update, several moves,
51             visibility changes and content updates might happen)
52          */
53         class SpriteTracer
54         {
55         public:
SpriteTracer(const Sprite::Reference & rAffectedSprite)56             SpriteTracer( const Sprite::Reference& rAffectedSprite ) :
57                 mpAffectedSprite(rAffectedSprite),
58                 maMoveStartArea(),
59                 maMoveEndArea(),
60                 mbIsMove( false ),
61                 mbIsGenericUpdate( false )
62             {
63             }
64 
operator ()(const SpriteRedrawManager::SpriteChangeRecord & rSpriteRecord)65             void operator()( const SpriteRedrawManager::SpriteChangeRecord& rSpriteRecord )
66             {
67                 // only deal with change events from the currently
68                 // affected sprite
69                 if( rSpriteRecord.mpAffectedSprite == mpAffectedSprite )
70                 {
71                     switch( rSpriteRecord.meChangeType )
72                     {
73                         case SpriteRedrawManager::SpriteChangeRecord::move:
74                             if( !mbIsMove )
75                             {
76                                 // no move yet - this must be the first one
77                                 maMoveStartArea = ::basegfx::B2DRectangle(
78                                     rSpriteRecord.maOldPos,
79                                     rSpriteRecord.maOldPos + rSpriteRecord.maUpdateArea.getRange() );
80                                 mbIsMove        = true;
81                             }
82 
83                             maMoveEndArea   = rSpriteRecord.maUpdateArea;
84                             break;
85 
86                         case SpriteRedrawManager::SpriteChangeRecord::update:
87                             // update end update area of the
88                             // sprite. Thus, every update() action
89                             // _after_ the last move will correctly
90                             // update the final repaint area. And this
91                             // does not interfere with subsequent
92                             // moves, because moves always perform a
93                             // hard set of maMoveEndArea to their
94                             // stored value
95                             maMoveEndArea.expand( rSpriteRecord.maUpdateArea );
96                             mbIsGenericUpdate = true;
97                             break;
98 
99                         default:
100                             ENSURE_OR_THROW( false,
101                                               "Unexpected case in SpriteUpdater::operator()" );
102                             break;
103                     }
104                 }
105             }
106 
commit(SpriteRedrawManager::SpriteConnectedRanges & rUpdateCollector) const107             void commit( SpriteRedrawManager::SpriteConnectedRanges& rUpdateCollector ) const
108             {
109                 if( mbIsMove )
110                 {
111                     if( !maMoveStartArea.isEmpty() ||
112                         !maMoveEndArea.isEmpty() )
113                     {
114                         // if mbIsGenericUpdate is false, this is a
115                         // pure move (i.e. no other update
116                         // operations). Pass that information on to
117                         // the SpriteInfo
118                         const bool bIsPureMove( !mbIsGenericUpdate );
119 
120                         // ignore the case that start and end update
121                         // area overlap - the b2dconnectedranges
122                         // handle that, anyway. doing it this way
123                         // ensures that we have both old and new area
124                         // stored
125 
126                         // round all given range up to enclosing
127                         // integer rectangle - since the whole thing
128                         // here is about
129 
130                         // first, draw the new sprite position
131                         rUpdateCollector.addRange(
132                             ::basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange( maMoveEndArea ),
133                             SpriteRedrawManager::SpriteInfo(
134                                 mpAffectedSprite,
135                                 maMoveEndArea,
136                                 true,
137                                 bIsPureMove ) );
138 
139                         // then, clear the old place (looks smoother
140                         // this way)
141                         rUpdateCollector.addRange(
142                             ::basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange( maMoveStartArea ),
143                             SpriteRedrawManager::SpriteInfo(
144                                 Sprite::Reference(),
145                                 maMoveStartArea,
146                                 true,
147                                 bIsPureMove ) );
148                     }
149                 }
150                 else if( mbIsGenericUpdate &&
151                          !maMoveEndArea.isEmpty() )
152                 {
153                     rUpdateCollector.addRange(
154                         ::basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange( maMoveEndArea ),
155                         SpriteRedrawManager::SpriteInfo(
156                             mpAffectedSprite,
157                             maMoveEndArea,
158                             true ) );
159                 }
160             }
161 
162         private:
163             Sprite::Reference       mpAffectedSprite;
164             ::basegfx::B2DRectangle maMoveStartArea;
165             ::basegfx::B2DRectangle maMoveEndArea;
166 
167             /// True, if at least one move was encountered
168             bool                    mbIsMove;
169 
170             /// True, if at least one generic update was encountered
171             bool                    mbIsGenericUpdate;
172         };
173 
174 
175         /** SpriteChecker functor, which for every sprite checks the
176             given update vector for necessary screen updates
177          */
178         class SpriteUpdater
179         {
180         public:
181             /** Generate update area list
182 
183                 @param rUpdater
184                 Reference to an updater object, which will receive the
185                 update areas.
186 
187                 @param rChangeContainer
188                 Container with all sprite change requests
189 
190              */
SpriteUpdater(SpriteRedrawManager::SpriteConnectedRanges & rUpdater,const SpriteRedrawManager::VectorOfChangeRecords & rChangeContainer)191             SpriteUpdater( SpriteRedrawManager::SpriteConnectedRanges&          rUpdater,
192                            const SpriteRedrawManager::VectorOfChangeRecords&    rChangeContainer ) :
193                 mrUpdater( rUpdater ),
194                 mrChangeContainer( rChangeContainer )
195             {
196             }
197 
198             /** Call this method for every sprite on your screen
199 
200                 This method scans the change container, collecting all
201                 update info for the given sprite into one or two
202                 update operations, which in turn are inserted into the
203                 connected ranges processor.
204 
205                 @param rSprite
206                 Current sprite to collect update info for.
207              */
operator ()(const Sprite::Reference & rSprite)208             void operator()( const Sprite::Reference& rSprite )
209             {
210                 const SpriteTracer aSpriteTracer(
211                     ::std::for_each( mrChangeContainer.begin(),
212                                      mrChangeContainer.end(),
213                                      SpriteTracer( rSprite ) ) );
214 
215                 aSpriteTracer.commit( mrUpdater );
216             }
217 
218         private:
219             SpriteRedrawManager::SpriteConnectedRanges&         mrUpdater;
220             const SpriteRedrawManager::VectorOfChangeRecords&   mrChangeContainer;
221         };
222     }
223 
setupUpdateAreas(SpriteConnectedRanges & rUpdateAreas) const224     void SpriteRedrawManager::setupUpdateAreas( SpriteConnectedRanges& rUpdateAreas ) const
225     {
226         // TODO(T3): This is NOT thread safe at all. This only works
227         // under the assumption that NOBODY changes ANYTHING
228         // concurrently, while this method is on the stack. We should
229         // really rework the canvas::Sprite interface, in such a way
230         // that it dumps ALL its state with a single, atomic
231         // call. Then, we store that state locally. This prolly goes
232         // in line with the problem of having sprite state available
233         // for the frame before the last frame; plus, it avoids
234         // frequent locks of the object mutices
235         SpriteComparator aSpriteComparator;
236 
237         // put all sprites that have changed content into update areas
238         ListOfSprites::const_iterator       aCurrSprite( maSprites.begin() );
239         const ListOfSprites::const_iterator aEndSprite ( maSprites.end() );
240         while( aCurrSprite != aEndSprite )
241         {
242             if( (*aCurrSprite)->isContentChanged() )
243                 const_cast<SpriteRedrawManager*>(this)->updateSprite( *aCurrSprite,
244                                                                       (*aCurrSprite)->getPosPixel(),
245                                                                       (*aCurrSprite)->getUpdateArea() );
246             ++aCurrSprite;
247         }
248 
249         // sort sprites after prio
250         VectorOfSprites aSortedSpriteVector;
251         ::std::copy( maSprites.begin(),
252                      maSprites.end(),
253                      ::std::back_insert_iterator< VectorOfSprites >(aSortedSpriteVector) );
254         ::std::sort( aSortedSpriteVector.begin(),
255                      aSortedSpriteVector.end(),
256                      aSpriteComparator );
257 
258         // extract all referenced sprites from the maChangeRecords
259         // (copy sprites, make the list unique, regarding the
260         // sprite pointer). This assumes that, until this scope
261         // ends, nobody changes the maChangeRecords vector!
262         VectorOfSprites aUpdatableSprites;
263         VectorOfChangeRecords::const_iterator       aCurrRecord( maChangeRecords.begin() );
264         const VectorOfChangeRecords::const_iterator aEndRecords( maChangeRecords.end() );
265         while( aCurrRecord != aEndRecords )
266         {
267             const Sprite::Reference& rSprite( aCurrRecord->getSprite() );
268             if( rSprite.is() )
269                 aUpdatableSprites.push_back( rSprite );
270             ++aCurrRecord;
271         }
272 
273         VectorOfSprites::iterator aBegin( aUpdatableSprites.begin() );
274         VectorOfSprites::iterator aEnd  ( aUpdatableSprites.end() );
275         ::std::sort( aBegin,
276                      aEnd,
277                      aSpriteComparator );
278 
279         aEnd = ::std::unique( aBegin, aEnd );
280 
281         // for each unique sprite, check the change event vector,
282         // calculate the update operation from that, and add the
283         // result to the aUpdateArea.
284         ::std::for_each( aBegin,
285                          aEnd,
286                          SpriteUpdater( rUpdateAreas,
287                                         maChangeRecords) );
288 
289         // TODO(P2): Implement your own output iterator adapter, to
290         // avoid that totally superfluous temp aUnchangedSprites
291         // vector.
292 
293         // add all sprites to rUpdateAreas, that are _not_ already
294         // contained in the uniquified vector of changed ones
295         // (i.e. the difference between aSortedSpriteVector and
296         // aUpdatableSprites).
297         VectorOfSprites aUnchangedSprites;
298         ::std::set_difference( aSortedSpriteVector.begin(),
299                                aSortedSpriteVector.end(),
300                                aBegin, aEnd,
301                                ::std::back_insert_iterator< VectorOfSprites >(aUnchangedSprites) );
302 
303         // add each remaining unchanged sprite to connected ranges,
304         // marked as "don't need update"
305         VectorOfSprites::const_iterator         aCurr( aUnchangedSprites.begin() );
306         const VectorOfSprites::const_iterator   aEnd2( aUnchangedSprites.end() );
307         while( aCurr != aEnd2 )
308         {
309             const ::basegfx::B2DRange& rUpdateArea( (*aCurr)->getUpdateArea() );
310             rUpdateAreas.addRange(
311                 ::basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange( rUpdateArea ),
312                 SpriteInfo(*aCurr,
313                            rUpdateArea,
314                            false) );
315             ++aCurr;
316         }
317     }
318 
319 #if OSL_DEBUG_LEVEL > 0
impIsEqualB2DRange(const basegfx::B2DRange & rRangeA,const basegfx::B2DRange & rRangeB,double fSmallValue)320     bool impIsEqualB2DRange(const basegfx::B2DRange& rRangeA, const basegfx::B2DRange& rRangeB, double fSmallValue)
321     {
322         return fabs(rRangeB.getMinX() - rRangeA.getMinX()) <= fSmallValue
323             && fabs(rRangeB.getMinY() - rRangeA.getMinY()) <= fSmallValue
324             && fabs(rRangeB.getMaxX() - rRangeA.getMaxX()) <= fSmallValue
325             && fabs(rRangeB.getMaxY() - rRangeA.getMaxY()) <= fSmallValue;
326     }
327 
impIsEqualB2DVector(const basegfx::B2DVector & rVecA,const basegfx::B2DVector & rVecB,double fSmallValue)328     bool impIsEqualB2DVector(const basegfx::B2DVector& rVecA, const basegfx::B2DVector& rVecB, double fSmallValue)
329     {
330         return fabs(rVecB.getX() - rVecA.getX()) <= fSmallValue
331             && fabs(rVecB.getY() - rVecA.getY()) <= fSmallValue;
332     }
333 #endif
334 
isAreaUpdateScroll(::basegfx::B2DRectangle & o_rMoveStart,::basegfx::B2DRectangle & o_rMoveEnd,const UpdateArea & rUpdateArea,::std::size_t nNumSprites) const335     bool SpriteRedrawManager::isAreaUpdateScroll( ::basegfx::B2DRectangle&  o_rMoveStart,
336                                                   ::basegfx::B2DRectangle&  o_rMoveEnd,
337                                                   const UpdateArea&         rUpdateArea,
338                                                   ::std::size_t             nNumSprites ) const
339     {
340         // check for a solitary move, which consists of exactly two
341         // pure-move entries, the first with valid, the second with
342         // invalid sprite (see SpriteTracer::commit()).  Note that we
343         // cannot simply store some flag in SpriteTracer::commit()
344         // above and just check that here, since during the connected
345         // range calculations, other sprites might get merged into the
346         // same region (thus spoiling the scrolling move
347         // optimization).
348         if( nNumSprites != 2 )
349             return false;
350 
351         const SpriteConnectedRanges::ComponentListType::const_iterator aFirst(
352             rUpdateArea.maComponentList.begin() );
353         SpriteConnectedRanges::ComponentListType::const_iterator aSecond(
354             aFirst ); ++aSecond;
355 
356         if( !aFirst->second.isPureMove() ||
357             !aSecond->second.isPureMove() ||
358             !aFirst->second.getSprite().is() ||
359             // use _true_ update area, not the rounded version
360             !aFirst->second.getSprite()->isAreaUpdateOpaque( aFirst->second.getUpdateArea() ) ||
361             aSecond->second.getSprite().is() )
362         {
363             // either no move update, or incorrect sprite, or sprite
364             // content not fully opaque over update region.
365             return false;
366         }
367 
368         o_rMoveStart      = aSecond->second.getUpdateArea();
369         o_rMoveEnd        = aFirst->second.getUpdateArea();
370 
371 #if OSL_DEBUG_LEVEL > 0
372         ::basegfx::B2DRectangle aTotalBounds( o_rMoveStart );
373         aTotalBounds.expand( o_rMoveEnd );
374 
375         OSL_POSTCOND(impIsEqualB2DRange(rUpdateArea.maTotalBounds, basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange(aTotalBounds), 0.5),
376             "SpriteRedrawManager::isAreaUpdateScroll(): sprite area and total area mismatch");
377         OSL_POSTCOND(impIsEqualB2DVector(o_rMoveStart.getRange(), o_rMoveEnd.getRange(), 0.5),
378             "SpriteRedrawManager::isAreaUpdateScroll(): scroll start and end area have mismatching size");
379 #endif
380 
381         return true;
382     }
383 
isAreaUpdateNotOpaque(const::basegfx::B2DRectangle & rUpdateRect,const AreaComponent & rComponent) const384     bool SpriteRedrawManager::isAreaUpdateNotOpaque( const ::basegfx::B2DRectangle& rUpdateRect,
385                                                      const AreaComponent&           rComponent ) const
386     {
387         const Sprite::Reference& pAffectedSprite( rComponent.second.getSprite() );
388 
389         if( !pAffectedSprite.is() )
390             return true; // no sprite, no opaque update!
391 
392         return !pAffectedSprite->isAreaUpdateOpaque( rUpdateRect );
393     }
394 
isAreaUpdateOpaque(const UpdateArea & rUpdateArea,::std::size_t nNumSprites) const395     bool SpriteRedrawManager::isAreaUpdateOpaque( const UpdateArea& rUpdateArea,
396                                                   ::std::size_t     nNumSprites ) const
397     {
398         // check whether the sprites in the update area's list will
399         // fully cover the given area _and_ do that in an opaque way
400         // (i.e. no alpha, no non-rectangular sprite content).
401 
402         // TODO(P1): Come up with a smarter early-exit criterion here
403         // (though, I think, the case that _lots_ of sprites _fully_
404         // cover a rectangular area _without_ any holes is extremely
405         // improbable)
406 
407         // avoid checking large number of sprites (and probably fail,
408         // anyway). Note: the case nNumSprites < 1 should normally not
409         // happen, as handleArea() calls backgroundPaint() then.
410         if( nNumSprites > 3 || nNumSprites < 1 )
411             return false;
412 
413         const SpriteConnectedRanges::ComponentListType::const_iterator aBegin(
414             rUpdateArea.maComponentList.begin() );
415         const SpriteConnectedRanges::ComponentListType::const_iterator aEnd(
416             rUpdateArea.maComponentList.end() );
417 
418         // now, calc the _true_ update area, by merging all sprite's
419         // true update areas into one rectangle
420         ::basegfx::B2DRange aTrueArea( aBegin->second.getUpdateArea() );
421         ::std::for_each( aBegin,
422                          aEnd,
423                          ::boost::bind( ::basegfx::B2DRangeExpander(aTrueArea),
424                                         ::boost::bind( &SpriteInfo::getUpdateArea,
425                                                        ::boost::bind( ::std::select2nd<AreaComponent>(),
426                                                                       _1 ) ) ) );
427 
428         // and check whether _any_ of the sprites tells that its area
429         // update will not be opaque.
430         return (::std::find_if( aBegin,
431                                 aEnd,
432                                 ::boost::bind( &SpriteRedrawManager::isAreaUpdateNotOpaque,
433                                                this,
434                                                ::boost::cref(aTrueArea),
435                                                _1 ) ) == aEnd );
436     }
437 
areSpritesChanged(const UpdateArea & rUpdateArea) const438     bool SpriteRedrawManager::areSpritesChanged( const UpdateArea& rUpdateArea ) const
439     {
440         // check whether SpriteInfo::needsUpdate returns false for
441         // all elements of this area's contained sprites
442         //
443         // if not a single changed sprite found - just ignore this
444         // component (return false)
445         const SpriteConnectedRanges::ComponentListType::const_iterator aEnd(
446             rUpdateArea.maComponentList.end() );
447         return (::std::find_if( rUpdateArea.maComponentList.begin(),
448                                 aEnd,
449                                 ::boost::bind( &SpriteInfo::needsUpdate,
450                                                ::boost::bind(
451                                                    ::std::select2nd<SpriteConnectedRanges::ComponentType>(),
452                                                    _1 ) ) ) != aEnd );
453     }
454 
SpriteRedrawManager()455     SpriteRedrawManager::SpriteRedrawManager() :
456         maSprites(),
457         maChangeRecords()
458     {
459     }
460 
disposing()461     void SpriteRedrawManager::disposing()
462     {
463         // drop all references
464         maChangeRecords.clear();
465 
466         // dispose all sprites - the spritecanvas, and by delegation,
467         // this object, is the owner of the sprites. After all, a
468         // sprite without a canvas to render into makes not terribly
469         // much sense.
470 
471         // TODO(Q3): Once boost 1.33 is in, change back to for_each
472         // with ::boost::mem_fn. For the time being, explicit loop due
473         // to cdecl declaration of all UNO methods.
474         ListOfSprites::reverse_iterator aCurr( maSprites.rbegin() );
475         ListOfSprites::reverse_iterator aEnd( maSprites.rend() );
476         while( aCurr != aEnd )
477             (*aCurr++)->dispose();
478 
479         maSprites.clear();
480     }
481 
clearChangeRecords()482     void SpriteRedrawManager::clearChangeRecords()
483     {
484         maChangeRecords.clear();
485     }
486 
showSprite(const Sprite::Reference & rSprite)487     void SpriteRedrawManager::showSprite( const Sprite::Reference& rSprite )
488     {
489         maSprites.push_back( rSprite );
490     }
491 
hideSprite(const Sprite::Reference & rSprite)492     void SpriteRedrawManager::hideSprite( const Sprite::Reference& rSprite )
493     {
494         maSprites.remove( rSprite );
495     }
496 
moveSprite(const Sprite::Reference & rSprite,const::basegfx::B2DPoint & rOldPos,const::basegfx::B2DPoint & rNewPos,const::basegfx::B2DVector & rSpriteSize)497     void SpriteRedrawManager::moveSprite( const Sprite::Reference&      rSprite,
498                                           const ::basegfx::B2DPoint&    rOldPos,
499                                           const ::basegfx::B2DPoint&    rNewPos,
500                                           const ::basegfx::B2DVector&   rSpriteSize )
501     {
502         maChangeRecords.push_back( SpriteChangeRecord( rSprite,
503                                                        rOldPos,
504                                                        rNewPos,
505                                                        rSpriteSize ) );
506     }
507 
updateSprite(const Sprite::Reference & rSprite,const::basegfx::B2DPoint & rPos,const::basegfx::B2DRange & rUpdateArea)508     void SpriteRedrawManager::updateSprite( const Sprite::Reference&    rSprite,
509                                             const ::basegfx::B2DPoint&  rPos,
510                                             const ::basegfx::B2DRange&  rUpdateArea )
511     {
512         maChangeRecords.push_back( SpriteChangeRecord( rSprite,
513                                                        rPos,
514                                                        rUpdateArea ) );
515     }
516 
517 }
518