/src/libreoffice/canvas/source/tools/spriteredrawmanager.cxx
Line | Count | Source |
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 | | |
24 | | #include <basegfx/range/b2drectangle.hxx> |
25 | | #include <basegfx/utils/canvastools.hxx> |
26 | | #include <comphelper/diagnose_ex.hxx> |
27 | | #include <sal/log.hxx> |
28 | | |
29 | | #include <spriteredrawmanager.hxx> |
30 | | #include <boost/range/adaptor/reversed.hpp> |
31 | | #include <utility> |
32 | | |
33 | | namespace canvas |
34 | | { |
35 | | namespace |
36 | | { |
37 | | /** Helper class to condense sprite updates into a single action |
38 | | |
39 | | This class tracks the sprite changes over the recorded |
40 | | change list, and generates a single update action from |
41 | | that (note that per screen update, several moves, |
42 | | visibility changes and content updates might happen) |
43 | | */ |
44 | | class SpriteTracer |
45 | | { |
46 | | public: |
47 | | explicit SpriteTracer( Sprite::Reference rAffectedSprite ) : |
48 | 0 | mpAffectedSprite(std::move(rAffectedSprite)), |
49 | 0 | mbIsMove( false ), |
50 | 0 | mbIsGenericUpdate( false ) |
51 | 0 | { |
52 | 0 | } |
53 | | |
54 | | void operator()( const SpriteRedrawManager::SpriteChangeRecord& rSpriteRecord ) |
55 | 0 | { |
56 | | // only deal with change events from the currently |
57 | | // affected sprite |
58 | 0 | if( rSpriteRecord.mpAffectedSprite != mpAffectedSprite ) |
59 | 0 | return; |
60 | | |
61 | 0 | switch( rSpriteRecord.meChangeType ) |
62 | 0 | { |
63 | 0 | case SpriteRedrawManager::SpriteChangeRecord::ChangeType::move: |
64 | 0 | if( !mbIsMove ) |
65 | 0 | { |
66 | | // no move yet - this must be the first one |
67 | 0 | maMoveStartArea = ::basegfx::B2DRectangle( |
68 | 0 | rSpriteRecord.maOldPos, |
69 | 0 | rSpriteRecord.maOldPos + rSpriteRecord.maUpdateArea.getRange() ); |
70 | 0 | mbIsMove = true; |
71 | 0 | } |
72 | |
|
73 | 0 | maMoveEndArea = rSpriteRecord.maUpdateArea; |
74 | 0 | break; |
75 | | |
76 | 0 | case SpriteRedrawManager::SpriteChangeRecord::ChangeType::update: |
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 | 0 | maMoveEndArea.expand( rSpriteRecord.maUpdateArea ); |
86 | 0 | mbIsGenericUpdate = true; |
87 | 0 | break; |
88 | | |
89 | 0 | default: |
90 | 0 | ENSURE_OR_THROW( false, |
91 | 0 | "Unexpected case in SpriteUpdater::operator()" ); |
92 | 0 | break; |
93 | 0 | } |
94 | 0 | } |
95 | | |
96 | | void commit( SpriteRedrawManager::SpriteConnectedRanges& rUpdateCollector ) const |
97 | 0 | { |
98 | 0 | if( mbIsMove ) |
99 | 0 | { |
100 | 0 | if( !maMoveStartArea.isEmpty() || |
101 | 0 | !maMoveEndArea.isEmpty() ) |
102 | 0 | { |
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 | 0 | 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 | 0 | rUpdateCollector.addRange( |
121 | 0 | ::basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange( maMoveEndArea ), |
122 | 0 | SpriteRedrawManager::SpriteInfo( |
123 | 0 | mpAffectedSprite, |
124 | 0 | maMoveEndArea, |
125 | 0 | true, |
126 | 0 | bIsPureMove ) ); |
127 | | |
128 | | // then, clear the old place (looks smoother |
129 | | // this way) |
130 | 0 | rUpdateCollector.addRange( |
131 | 0 | ::basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange( maMoveStartArea ), |
132 | 0 | SpriteRedrawManager::SpriteInfo( |
133 | 0 | Sprite::Reference(), |
134 | 0 | maMoveStartArea, |
135 | 0 | true, |
136 | 0 | bIsPureMove ) ); |
137 | 0 | } |
138 | 0 | } |
139 | 0 | else if( mbIsGenericUpdate && |
140 | 0 | !maMoveEndArea.isEmpty() ) |
141 | 0 | { |
142 | 0 | rUpdateCollector.addRange( |
143 | 0 | ::basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange( maMoveEndArea ), |
144 | 0 | SpriteRedrawManager::SpriteInfo( |
145 | 0 | mpAffectedSprite, |
146 | 0 | maMoveEndArea, |
147 | 0 | true ) ); |
148 | 0 | } |
149 | 0 | } |
150 | | |
151 | | private: |
152 | | Sprite::Reference mpAffectedSprite; |
153 | | ::basegfx::B2DRectangle maMoveStartArea; |
154 | | ::basegfx::B2DRectangle maMoveEndArea; |
155 | | |
156 | | /// True, if at least one move was encountered |
157 | | bool mbIsMove; |
158 | | |
159 | | /// True, if at least one generic update was encountered |
160 | | bool mbIsGenericUpdate; |
161 | | }; |
162 | | |
163 | | |
164 | | /** SpriteChecker functor, which for every sprite checks the |
165 | | given update vector for necessary screen updates |
166 | | */ |
167 | | class SpriteUpdater |
168 | | { |
169 | | public: |
170 | | /** Generate update area list |
171 | | |
172 | | @param rUpdater |
173 | | Reference to an updater object, which will receive the |
174 | | update areas. |
175 | | |
176 | | @param rChangeContainer |
177 | | Container with all sprite change requests |
178 | | |
179 | | */ |
180 | | SpriteUpdater( SpriteRedrawManager::SpriteConnectedRanges& rUpdater, |
181 | | const SpriteRedrawManager::VectorOfChangeRecords& rChangeContainer ) : |
182 | 0 | mrUpdater( rUpdater ), |
183 | 0 | mrChangeContainer( rChangeContainer ) |
184 | 0 | { |
185 | 0 | } |
186 | | |
187 | | /** Call this method for every sprite on your screen |
188 | | |
189 | | This method scans the change container, collecting all |
190 | | update info for the given sprite into one or two |
191 | | update operations, which in turn are inserted into the |
192 | | connected ranges processor. |
193 | | |
194 | | @param rSprite |
195 | | Current sprite to collect update info for. |
196 | | */ |
197 | | void operator()( const Sprite::Reference& rSprite ) |
198 | 0 | { |
199 | 0 | SpriteTracer aSpriteTracer( rSprite ); |
200 | |
|
201 | 0 | for (auto const& aChange : mrChangeContainer) |
202 | 0 | aSpriteTracer( aChange ); |
203 | |
|
204 | 0 | aSpriteTracer.commit( mrUpdater ); |
205 | 0 | } |
206 | | |
207 | | private: |
208 | | SpriteRedrawManager::SpriteConnectedRanges& mrUpdater; |
209 | | const SpriteRedrawManager::VectorOfChangeRecords& mrChangeContainer; |
210 | | }; |
211 | | } |
212 | | |
213 | | void SpriteRedrawManager::setupUpdateAreas( SpriteConnectedRanges& rUpdateAreas ) const |
214 | 0 | { |
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 | 0 | SpriteWeakOrder aSpriteComparator; |
225 | | |
226 | | // put all sprites that have changed content into update areas |
227 | 0 | for( const auto& pSprite : maSprites ) |
228 | 0 | { |
229 | 0 | if( pSprite->isContentChanged() ) |
230 | 0 | const_cast< SpriteRedrawManager* >( this )->updateSprite( pSprite, |
231 | 0 | pSprite->getPosPixel(), |
232 | 0 | pSprite->getUpdateArea() ); |
233 | 0 | } |
234 | | |
235 | | // sort sprites after prio |
236 | 0 | VectorOfSprites aSortedSpriteVector( maSprites.begin(), maSprites.end() ); |
237 | 0 | std::sort( aSortedSpriteVector.begin(), |
238 | 0 | aSortedSpriteVector.end(), |
239 | 0 | 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 | 0 | VectorOfSprites aUpdatableSprites; |
246 | 0 | for( const auto& rChangeRecord : maChangeRecords ) |
247 | 0 | { |
248 | 0 | const Sprite::Reference& rSprite( rChangeRecord.getSprite() ); |
249 | 0 | if( rSprite.is() ) |
250 | 0 | aUpdatableSprites.push_back( rSprite ); |
251 | 0 | } |
252 | |
|
253 | 0 | std::sort( aUpdatableSprites.begin(), |
254 | 0 | aUpdatableSprites.end(), |
255 | 0 | aSpriteComparator ); |
256 | |
|
257 | 0 | VectorOfSprites::iterator aEnd= |
258 | 0 | std::unique( aUpdatableSprites.begin(), |
259 | 0 | 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 | 0 | std::for_each( aUpdatableSprites.begin(), |
265 | 0 | aEnd, |
266 | 0 | SpriteUpdater( rUpdateAreas, |
267 | 0 | maChangeRecords) ); |
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 | 0 | VectorOfSprites aUnchangedSprites; |
278 | 0 | std::set_difference( aSortedSpriteVector.begin(), |
279 | 0 | aSortedSpriteVector.end(), |
280 | 0 | aUpdatableSprites.begin(), |
281 | 0 | aEnd, |
282 | 0 | std::back_insert_iterator< VectorOfSprites >(aUnchangedSprites), |
283 | 0 | aSpriteComparator ); |
284 | | |
285 | | // add each remaining unchanged sprite to connected ranges, |
286 | | // marked as "don't need update" |
287 | 0 | for( const auto& pUnchangedSprite : aUnchangedSprites ) |
288 | 0 | { |
289 | 0 | const ::basegfx::B2DRange aUpdateArea( pUnchangedSprite->getUpdateArea() ); |
290 | 0 | rUpdateAreas.addRange( |
291 | 0 | ::basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange( aUpdateArea ), |
292 | 0 | SpriteInfo( pUnchangedSprite, |
293 | 0 | aUpdateArea, |
294 | 0 | false ) ); |
295 | 0 | } |
296 | 0 | } |
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 | | |
314 | | bool SpriteRedrawManager::isAreaUpdateScroll( ::basegfx::B2DRectangle& o_rMoveStart, |
315 | | ::basegfx::B2DRectangle& o_rMoveEnd, |
316 | | const UpdateArea& rUpdateArea, |
317 | | std::size_t nNumSprites ) const |
318 | 0 | { |
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 | 0 | if( nNumSprites != 2 ) |
328 | 0 | return false; |
329 | | |
330 | 0 | const SpriteConnectedRanges::ComponentListType::const_iterator aFirst( |
331 | 0 | rUpdateArea.maComponentList.begin() ); |
332 | 0 | SpriteConnectedRanges::ComponentListType::const_iterator aSecond( |
333 | 0 | aFirst ); |
334 | 0 | ++aSecond; |
335 | |
|
336 | 0 | if( !aFirst->second.isPureMove() || |
337 | 0 | !aSecond->second.isPureMove() || |
338 | 0 | !aFirst->second.getSprite().is() || |
339 | | // use _true_ update area, not the rounded version |
340 | 0 | !aFirst->second.getSprite()->isAreaUpdateOpaque( aFirst->second.getUpdateArea() ) || |
341 | 0 | aSecond->second.getSprite().is() ) |
342 | 0 | { |
343 | | // either no move update, or incorrect sprite, or sprite |
344 | | // content not fully opaque over update region. |
345 | 0 | return false; |
346 | 0 | } |
347 | | |
348 | 0 | o_rMoveStart = aSecond->second.getUpdateArea(); |
349 | 0 | o_rMoveEnd = aFirst->second.getUpdateArea(); |
350 | |
|
351 | | #if OSL_DEBUG_LEVEL > 0 |
352 | | ::basegfx::B2DRectangle aTotalBounds( o_rMoveStart ); |
353 | | aTotalBounds.expand( o_rMoveEnd ); |
354 | | |
355 | | SAL_WARN_IF(!impIsEqualB2DRange(rUpdateArea.maTotalBounds, basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange(aTotalBounds), 0.5), |
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 | 0 | return true; |
364 | 0 | } |
365 | | |
366 | | bool SpriteRedrawManager::isAreaUpdateNotOpaque( const ::basegfx::B2DRectangle& rUpdateRect, |
367 | | const AreaComponent& rComponent ) const |
368 | 0 | { |
369 | 0 | const Sprite::Reference& pAffectedSprite( rComponent.second.getSprite() ); |
370 | |
|
371 | 0 | if( !pAffectedSprite.is() ) |
372 | 0 | return true; // no sprite, no opaque update! |
373 | | |
374 | 0 | return !pAffectedSprite->isAreaUpdateOpaque( rUpdateRect ); |
375 | 0 | } |
376 | | |
377 | | bool SpriteRedrawManager::isAreaUpdateOpaque( const UpdateArea& rUpdateArea, |
378 | | std::size_t nNumSprites ) const |
379 | 0 | { |
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 | 0 | if( nNumSprites > 3 || nNumSprites < 1 ) |
393 | 0 | return false; |
394 | | |
395 | | // now, calc the _true_ update area, by merging all sprite's |
396 | | // true update areas into one rectangle |
397 | 0 | ::basegfx::B2DRange aTrueArea( rUpdateArea.maComponentList.begin()->second.getUpdateArea() ); |
398 | 0 | for( const auto& rArea : rUpdateArea.maComponentList ) |
399 | 0 | aTrueArea.expand(rArea.second.getUpdateArea()); |
400 | |
|
401 | 0 | const SpriteConnectedRanges::ComponentListType::const_iterator aEnd( |
402 | 0 | rUpdateArea.maComponentList.end() ); |
403 | | |
404 | | // and check whether _any_ of the sprites tells that its area |
405 | | // update will not be opaque. |
406 | 0 | return std::none_of( rUpdateArea.maComponentList.begin(), |
407 | 0 | aEnd, |
408 | 0 | [&aTrueArea, this]( const std::pair< ::basegfx::B2DRange, SpriteInfo >& cp ) |
409 | 0 | { return this->isAreaUpdateNotOpaque(aTrueArea, cp); } ); |
410 | 0 | } |
411 | | |
412 | | bool SpriteRedrawManager::areSpritesChanged( const UpdateArea& rUpdateArea ) const |
413 | 0 | { |
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 | 0 | const SpriteConnectedRanges::ComponentListType::const_iterator aEnd( |
420 | 0 | rUpdateArea.maComponentList.end() ); |
421 | 0 | return std::any_of( rUpdateArea.maComponentList.begin(), |
422 | 0 | aEnd, |
423 | 0 | []( const std::pair< ::basegfx::B2DRange, SpriteInfo >& cp ) |
424 | 0 | { return cp.second.needsUpdate(); } ); |
425 | 0 | } |
426 | | |
427 | | SpriteRedrawManager::SpriteRedrawManager() |
428 | 0 | { |
429 | 0 | } |
430 | | |
431 | | void SpriteRedrawManager::disposing() |
432 | 0 | { |
433 | | // drop all references |
434 | 0 | 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 | 0 | for( const auto& rCurr : boost::adaptors::reverse(maSprites) ) |
441 | 0 | rCurr->dispose(); |
442 | |
|
443 | 0 | maSprites.clear(); |
444 | 0 | } |
445 | | |
446 | | void SpriteRedrawManager::clearChangeRecords() |
447 | 0 | { |
448 | 0 | maChangeRecords.clear(); |
449 | 0 | } |
450 | | |
451 | | void SpriteRedrawManager::showSprite( const Sprite::Reference& rSprite ) |
452 | 0 | { |
453 | 0 | maSprites.push_back( rSprite ); |
454 | 0 | } |
455 | | |
456 | | void SpriteRedrawManager::hideSprite( const Sprite::Reference& rSprite ) |
457 | 0 | { |
458 | 0 | std::erase(maSprites, rSprite); |
459 | 0 | } |
460 | | |
461 | | void SpriteRedrawManager::moveSprite( const Sprite::Reference& rSprite, |
462 | | const ::basegfx::B2DPoint& rOldPos, |
463 | | const ::basegfx::B2DPoint& rNewPos, |
464 | | const ::basegfx::B2DVector& rSpriteSize ) |
465 | 0 | { |
466 | 0 | maChangeRecords.emplace_back( rSprite, |
467 | 0 | rOldPos, |
468 | 0 | rNewPos, |
469 | 0 | rSpriteSize ); |
470 | 0 | } |
471 | | |
472 | | void SpriteRedrawManager::updateSprite( const Sprite::Reference& rSprite, |
473 | | const ::basegfx::B2DPoint& rPos, |
474 | | const ::basegfx::B2DRange& rUpdateArea ) |
475 | 0 | { |
476 | 0 | maChangeRecords.emplace_back( rSprite, |
477 | 0 | rPos, |
478 | 0 | rUpdateArea ); |
479 | 0 | } |
480 | | |
481 | | } |
482 | | |
483 | | /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |