1 /*
2 * Copyright 2010, The Android Open Source Project
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26 #define LOG_TAG "Tile"
27 #define LOG_NDEBUG 1
28
29 #include "config.h"
30 #include "Tile.h"
31
32 #if USE(ACCELERATED_COMPOSITING)
33
34 #include "AndroidLog.h"
35 #include "GLUtils.h"
36 #include "BaseRenderer.h"
37 #include "TextureInfo.h"
38 #include "TileTexture.h"
39 #include "TilesManager.h"
40
41 // If the dirty portion of a tile exceeds this ratio, fully repaint.
42 // Lower values give fewer partial repaints, thus fewer front-to-back
43 // texture copies (cost will vary by device). It's a tradeoff between
44 // the rasterization cost and the FBO texture recopy cost when using
45 // GPU for the transfer queue.
46 #define MAX_INVAL_AREA 0.6
47
48 namespace WebCore {
49
Tile(bool isLayerTile)50 Tile::Tile(bool isLayerTile)
51 : m_x(-1)
52 , m_y(-1)
53 , m_frontTexture(0)
54 , m_backTexture(0)
55 , m_lastDrawnTexture(0)
56 , m_scale(1)
57 , m_dirty(true)
58 , m_repaintsPending(0)
59 , m_fullRepaint(true)
60 , m_isLayerTile(isLayerTile)
61 , m_drawCount(0)
62 , m_state(Unpainted)
63 {
64 #ifdef DEBUG_COUNT
65 ClassTracker::instance()->increment("Tile");
66 #endif
67 }
68
~Tile()69 Tile::~Tile()
70 {
71 if (m_backTexture)
72 m_backTexture->release(this);
73 if (m_frontTexture)
74 m_frontTexture->release(this);
75
76 #ifdef DEBUG_COUNT
77 ClassTracker::instance()->decrement("Tile");
78 #endif
79 }
80
81 // All the following functions must be called from the main GL thread.
82
setContents(int x,int y,float scale,bool isExpandedPrefetchTile)83 void Tile::setContents(int x, int y, float scale, bool isExpandedPrefetchTile)
84 {
85 // TODO: investigate whether below check/discard is necessary
86 if ((m_x != x)
87 || (m_y != y)
88 || (m_scale != scale)) {
89 // neither texture is relevant
90 discardTextures();
91 }
92
93 android::AutoMutex lock(m_atomicSync);
94 m_x = x;
95 m_y = y;
96 m_scale = scale;
97 m_drawCount = TilesManager::instance()->getDrawGLCount();
98 if (isExpandedPrefetchTile)
99 m_drawCount--; // deprioritize expanded painting region
100 }
101
reserveTexture()102 void Tile::reserveTexture()
103 {
104 TileTexture* texture = TilesManager::instance()->getAvailableTexture(this);
105
106 android::AutoMutex lock(m_atomicSync);
107 if (texture && m_backTexture != texture) {
108 ALOGV("tile %p reserving texture %p, back was %p (front %p)",
109 this, texture, m_backTexture, m_frontTexture);
110 m_state = Unpainted;
111 m_backTexture = texture;
112 }
113
114 if (m_state == UpToDate) {
115 ALOGV("moving tile %p to unpainted, since it reserved while up to date", this);
116 m_dirty = true;
117 m_state = Unpainted;
118 }
119 }
120
removeTexture(TileTexture * texture)121 bool Tile::removeTexture(TileTexture* texture)
122 {
123 ALOGV("%p removeTexture %p, back %p front %p",
124 this, texture, m_backTexture, m_frontTexture);
125 // We update atomically, so paintBitmap() can see the correct value
126 android::AutoMutex lock(m_atomicSync);
127 if (m_frontTexture == texture) {
128 if (m_state == UpToDate) {
129 ALOGV("front texture removed, state was UpToDate, now becoming unpainted, bt is %p", m_backTexture);
130 m_state = Unpainted;
131 }
132
133 m_frontTexture = 0;
134 }
135 if (m_backTexture == texture) {
136 m_state = Unpainted;
137 m_backTexture = 0;
138 }
139
140 // mark dirty regardless of which texture was taken - the back texture may
141 // have been ready to swap
142 m_dirty = true;
143
144 return true;
145 }
146
markAsDirty()147 void Tile::markAsDirty()
148 {
149 android::AutoMutex lock(m_atomicSync);
150 m_dirtyArea.setEmpty(); // empty dirty rect prevents fast blit path
151 markAsDirtyInternal();
152 }
153
markAsDirty(const SkRegion & dirtyArea)154 void Tile::markAsDirty(const SkRegion& dirtyArea)
155 {
156 if (dirtyArea.isEmpty())
157 return;
158 android::AutoMutex lock(m_atomicSync);
159 m_dirtyArea.op(dirtyArea, SkRegion::kUnion_Op);
160
161 // Check if we actually intersect with the area
162 bool intersect = false;
163 SkRegion::Iterator cliperator(dirtyArea);
164 SkRect realTileRect;
165 SkRect dirtyRect;
166 while (!cliperator.done()) {
167 dirtyRect.set(cliperator.rect());
168 if (intersectWithRect(m_x, m_y, TilesManager::tileWidth(), TilesManager::tileHeight(),
169 m_scale, dirtyRect, realTileRect)) {
170 intersect = true;
171 break;
172 }
173 cliperator.next();
174 }
175
176 if (!intersect)
177 return;
178
179 markAsDirtyInternal();
180 }
181
markAsDirtyInternal()182 void Tile::markAsDirtyInternal()
183 {
184 // NOTE: callers must hold lock on m_atomicSync
185
186 m_dirty = true;
187 if (m_state == UpToDate) {
188 // We only mark a tile as unpainted in 'markAsDirty' if its status is
189 // UpToDate: marking dirty means we need to repaint, but don't stop the
190 // current paint
191 m_state = Unpainted;
192 } else if (m_state != Unpainted) {
193 // TODO: fix it so that they can paint while deferring the markAsDirty
194 // call (or block updates)
195 ALOGV("Warning: tried to mark tile %p at %d, %d islayertile %d as dirty, state %d",
196 this, m_x, m_y, isLayerTile(), m_state);
197
198 // prefetch tiles can be marked dirty while in the process of painting,
199 // due to not using an update lock. force them to fail validate step.
200 m_state = Unpainted;
201 }
202 }
203
isDirty()204 bool Tile::isDirty()
205 {
206 android::AutoMutex lock(m_atomicSync);
207 return m_dirty;
208 }
209
isRepaintPending()210 bool Tile::isRepaintPending()
211 {
212 android::AutoMutex lock(m_atomicSync);
213 return m_repaintsPending != 0;
214 }
215
setRepaintPending(bool pending)216 void Tile::setRepaintPending(bool pending)
217 {
218 android::AutoMutex lock(m_atomicSync);
219 m_repaintsPending += pending ? 1 : -1;
220 }
221
drawGL(float opacity,const SkRect & rect,float scale,const TransformationMatrix * transform,bool forceBlending,bool usePointSampling,const FloatRect & fillPortion)222 bool Tile::drawGL(float opacity, const SkRect& rect, float scale,
223 const TransformationMatrix* transform,
224 bool forceBlending, bool usePointSampling,
225 const FloatRect& fillPortion)
226 {
227 if (m_x < 0 || m_y < 0 || m_scale != scale)
228 return false;
229
230 // No need to mutex protect reads of m_backTexture as it is only written to by
231 // the consumer thread.
232 if (!m_frontTexture)
233 return false;
234
235 if (fillPortion.maxX() < 1.0f || fillPortion.maxY() < 1.0f
236 || fillPortion.x() > 0.0f || fillPortion.y() > 0.0f)
237 ALOGV("drawing tile %p (%d, %d with fill portions %f %f->%f, %f",
238 this, m_x, m_y, fillPortion.x(), fillPortion.y(),
239 fillPortion.maxX(), fillPortion.maxY());
240
241 m_frontTexture->drawGL(isLayerTile(), rect, opacity, transform,
242 forceBlending, usePointSampling, fillPortion);
243 m_lastDrawnTexture = m_frontTexture;
244 return true;
245 }
246
isTileReady()247 bool Tile::isTileReady()
248 {
249 // Return true if the tile's most recently drawn texture is up to date
250 android::AutoMutex lock(m_atomicSync);
251 TileTexture * texture = (m_state == ReadyToSwap) ? m_backTexture : m_frontTexture;
252
253 if (!texture)
254 return false;
255
256 if (texture->owner() != this)
257 return false;
258
259 if (m_dirty)
260 return false;
261
262 if (m_state != ReadyToSwap && m_state != UpToDate)
263 return false;
264
265 return true;
266 }
267
intersectWithRect(int x,int y,int tileWidth,int tileHeight,float scale,const SkRect & dirtyRect,SkRect & realTileRect)268 bool Tile::intersectWithRect(int x, int y, int tileWidth, int tileHeight,
269 float scale, const SkRect& dirtyRect,
270 SkRect& realTileRect)
271 {
272 // compute the rect to corresponds to pixels
273 realTileRect.fLeft = x * tileWidth;
274 realTileRect.fTop = y * tileHeight;
275 realTileRect.fRight = realTileRect.fLeft + tileWidth;
276 realTileRect.fBottom = realTileRect.fTop + tileHeight;
277
278 // scale the dirtyRect for intersect computation.
279 SkRect realDirtyRect = SkRect::MakeWH(dirtyRect.width() * scale,
280 dirtyRect.height() * scale);
281 realDirtyRect.offset(dirtyRect.fLeft * scale, dirtyRect.fTop * scale);
282
283 if (!realTileRect.intersect(realDirtyRect))
284 return false;
285 return true;
286 }
287
isTileVisible(const IntRect & viewTileBounds)288 bool Tile::isTileVisible(const IntRect& viewTileBounds)
289 {
290 return (m_x >= viewTileBounds.x()
291 && m_x < viewTileBounds.x() + viewTileBounds.width()
292 && m_y >= viewTileBounds.y()
293 && m_y < viewTileBounds.y() + viewTileBounds.height());
294 }
295
296 // This is called from the texture generation thread
paintBitmap(TilePainter * painter,BaseRenderer * renderer)297 void Tile::paintBitmap(TilePainter* painter, BaseRenderer* renderer)
298 {
299 // We acquire the values below atomically. This ensures that we are reading
300 // values correctly across cores. Further, once we have these values they
301 // can be updated by other threads without consequence.
302 m_atomicSync.lock();
303 bool dirty = m_dirty;
304 TileTexture* texture = m_backTexture;
305 SkRegion dirtyArea = m_dirtyArea;
306 float scale = m_scale;
307 const int x = m_x;
308 const int y = m_y;
309
310 if (!dirty || !texture) {
311 m_atomicSync.unlock();
312 return;
313 }
314 if (m_state != Unpainted) {
315 ALOGV("Warning: started painting tile %p, but was at state %d, ft %p bt %p",
316 this, m_state, m_frontTexture, m_backTexture);
317 }
318 m_state = PaintingStarted;
319 TextureInfo* textureInfo = texture->getTextureInfo();
320 m_atomicSync.unlock();
321
322 // at this point we can safely check the ownership (if the texture got
323 // transferred to another Tile under us)
324 if (texture->owner() != this) {
325 return;
326 }
327
328 // setup the common renderInfo fields;
329 TileRenderInfo renderInfo;
330 renderInfo.x = x;
331 renderInfo.y = y;
332 renderInfo.scale = scale;
333 renderInfo.tileSize = texture->getSize();
334 renderInfo.tilePainter = painter;
335 renderInfo.baseTile = this;
336 renderInfo.textureInfo = textureInfo;
337
338 const float tileWidth = renderInfo.tileSize.width();
339 const float tileHeight = renderInfo.tileSize.height();
340
341 renderer->renderTiledContent(renderInfo);
342
343 m_atomicSync.lock();
344
345 if (texture == m_backTexture) {
346 // set the fullrepaint flags
347 m_fullRepaint = false;
348
349 // The various checks to see if we are still dirty...
350
351 m_dirty = false;
352
353 if (m_scale != scale)
354 m_dirty = true;
355
356 m_dirtyArea.setEmpty();
357
358 ALOGV("painted tile %p (%d, %d), texture %p, dirty=%d", this, x, y, texture, m_dirty);
359
360 validatePaint();
361 } else {
362 ALOGV("tile %p no longer owns texture %p, m_state %d. ft %p bt %p",
363 this, texture, m_state, m_frontTexture, m_backTexture);
364 }
365
366 m_atomicSync.unlock();
367 }
368
discardTextures()369 void Tile::discardTextures() {
370 android::AutoMutex lock(m_atomicSync);
371 ALOGV("%p discarding bt %p, ft %p",
372 this, m_backTexture, m_frontTexture);
373 if (m_frontTexture) {
374 m_frontTexture->release(this);
375 m_frontTexture = 0;
376 }
377 if (m_backTexture) {
378 m_backTexture->release(this);
379 m_backTexture = 0;
380 }
381 m_dirtyArea.setEmpty();
382 m_fullRepaint = true;
383
384 m_dirty = true;
385 m_state = Unpainted;
386 }
387
discardBackTexture()388 void Tile::discardBackTexture() {
389 android::AutoMutex lock(m_atomicSync);
390 if (m_backTexture) {
391 m_backTexture->release(this);
392 m_backTexture = 0;
393 }
394 m_state = Unpainted;
395 m_dirty = true;
396 }
397
swapTexturesIfNeeded()398 bool Tile::swapTexturesIfNeeded() {
399 android::AutoMutex lock(m_atomicSync);
400 if (m_state == ReadyToSwap) {
401 // discard old texture and swap the new one in its place
402 if (m_frontTexture)
403 m_frontTexture->release(this);
404
405 m_frontTexture = m_backTexture;
406 m_backTexture = 0;
407 m_state = UpToDate;
408 ALOGV("display texture for %p at %d, %d front is now %p, back is %p",
409 this, m_x, m_y, m_frontTexture, m_backTexture);
410
411 return true;
412 }
413 return false;
414 }
415
backTextureTransfer()416 void Tile::backTextureTransfer() {
417 android::AutoMutex lock(m_atomicSync);
418 if (m_state == PaintingStarted)
419 m_state = TransferredUnvalidated;
420 else if (m_state == ValidatedUntransferred)
421 m_state = ReadyToSwap;
422 else {
423 // shouldn't have transferred a tile in any other state, log
424 ALOGV("Note: transferred tile %p at %d %d, state wasn't paintingstarted or validated: %d",
425 this, m_x, m_y, m_state);
426 }
427 }
428
backTextureTransferFail()429 void Tile::backTextureTransferFail() {
430 // transfer failed for some reason, mark dirty so it will (repaint and) be
431 // retransferred.
432 android::AutoMutex lock(m_atomicSync);
433 m_state = Unpainted;
434 m_dirty = true;
435 // whether validatePaint is called before or after, it won't do anything
436 }
437
onBlitUpdate()438 void Tile::onBlitUpdate()
439 {
440 // The front texture was directly updated with a blit, so mark this as clean
441 android::AutoMutex lock(m_atomicSync);
442 m_dirty = false;
443 m_dirtyArea.setEmpty();
444 m_state = Tile::UpToDate;
445 }
446
validatePaint()447 void Tile::validatePaint() {
448 // ONLY CALL while m_atomicSync is locked (at the end of paintBitmap())
449
450 if (!m_dirty) {
451 // since after the paint, the tile isn't dirty, 'validate' it - this
452 // may happed before or after the transfer queue operation. Only
453 // when both have happened, mark as 'ReadyToSwap'
454 if (m_state == PaintingStarted)
455 m_state = ValidatedUntransferred;
456 else if (m_state == TransferredUnvalidated) {
457 // When the backTexture has been marked pureColor, we will skip the
458 // transfer and marked as ReadyToSwap, in this case, we don't want
459 // to reset m_dirty bit to true.
460 m_state = ReadyToSwap;
461 } else {
462 ALOGV("Note: validated tile %p at %d %d, state wasn't paintingstarted or transferred %d",
463 this, m_x, m_y, m_state);
464 // failed transferring, in which case mark dirty (since
465 // paintBitmap() may have cleared m_dirty)
466 m_dirty = true;
467 }
468 } else {
469 ALOGV("Note: paint was unsuccessful.");
470 m_state = Unpainted;
471 }
472
473 }
474
475 } // namespace WebCore
476
477 #endif // USE(ACCELERATED_COMPOSITING)
478