RecordingCanvas.cpp revision 54fa17f667c285a5c9225e238c8132dfe830ef36
1/* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17#include "RecordingCanvas.h" 18 19#include "RecordedOp.h" 20#include "RenderNode.h" 21 22namespace android { 23namespace uirenderer { 24 25RecordingCanvas::RecordingCanvas(size_t width, size_t height) 26 : mState(*this) 27 , mResourceCache(ResourceCache::getInstance()) { 28 reset(width, height); 29} 30 31RecordingCanvas::~RecordingCanvas() { 32 LOG_ALWAYS_FATAL_IF(mDisplayList, 33 "Destroyed a RecordingCanvas during a record!"); 34} 35 36void RecordingCanvas::reset(int width, int height) { 37 LOG_ALWAYS_FATAL_IF(mDisplayList, 38 "prepareDirty called a second time during a recording!"); 39 mDisplayList = new DisplayList(); 40 41 mState.initializeSaveStack(width, height, 0, 0, width, height, Vector3()); 42 43 mDeferredBarrierType = DeferredBarrierType::InOrder; 44 mState.setDirtyClip(false); 45 mRestoreSaveCount = -1; 46} 47 48DisplayList* RecordingCanvas::finishRecording() { 49 mPaintMap.clear(); 50 mRegionMap.clear(); 51 mPathMap.clear(); 52 DisplayList* displayList = mDisplayList; 53 mDisplayList = nullptr; 54 mSkiaCanvasProxy.reset(nullptr); 55 return displayList; 56} 57 58SkCanvas* RecordingCanvas::asSkCanvas() { 59 LOG_ALWAYS_FATAL_IF(!mDisplayList, 60 "attempting to get an SkCanvas when we are not recording!"); 61 if (!mSkiaCanvasProxy) { 62 mSkiaCanvasProxy.reset(new SkiaCanvasProxy(this)); 63 } 64 65 // SkCanvas instances default to identity transform, but should inherit 66 // the state of this Canvas; if this code was in the SkiaCanvasProxy 67 // constructor, we couldn't cache mSkiaCanvasProxy. 68 SkMatrix parentTransform; 69 getMatrix(&parentTransform); 70 mSkiaCanvasProxy.get()->setMatrix(parentTransform); 71 72 return mSkiaCanvasProxy.get(); 73} 74 75// ---------------------------------------------------------------------------- 76// CanvasStateClient implementation 77// ---------------------------------------------------------------------------- 78 79void RecordingCanvas::onViewportInitialized() { 80} 81 82void RecordingCanvas::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) { 83 if (removed.flags & Snapshot::kFlagIsFboLayer) { 84 addOp(new (alloc()) EndLayerOp()); 85 } 86} 87 88// ---------------------------------------------------------------------------- 89// android/graphics/Canvas state operations 90// ---------------------------------------------------------------------------- 91// Save (layer) 92int RecordingCanvas::save(SkCanvas::SaveFlags flags) { 93 return mState.save((int) flags); 94} 95 96void RecordingCanvas::RecordingCanvas::restore() { 97 if (mRestoreSaveCount < 0) { 98 restoreToCount(getSaveCount() - 1); 99 return; 100 } 101 102 mRestoreSaveCount--; 103 mState.restore(); 104} 105 106void RecordingCanvas::restoreToCount(int saveCount) { 107 mRestoreSaveCount = saveCount; 108 mState.restoreToCount(saveCount); 109} 110 111int RecordingCanvas::saveLayer(float left, float top, float right, float bottom, const SkPaint* paint, 112 SkCanvas::SaveFlags flags) { 113 if (!(flags & SkCanvas::kClipToLayer_SaveFlag)) { 114 LOG_ALWAYS_FATAL("unclipped layers not supported"); 115 } 116 // force matrix/clip isolation for layer 117 flags |= SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag; 118 119 120 const Snapshot& previous = *mState.currentSnapshot(); 121 122 // initialize the snapshot as though it almost represents an FBO layer so deferred draw 123 // operations will be able to store and restore the current clip and transform info, and 124 // quick rejection will be correct (for display lists) 125 126 const Rect untransformedBounds(left, top, right, bottom); 127 128 // determine clipped bounds relative to previous viewport. 129 Rect visibleBounds = untransformedBounds; 130 previous.transform->mapRect(visibleBounds); 131 132 133 visibleBounds.doIntersect(previous.getRenderTargetClip()); 134 visibleBounds.snapToPixelBoundaries(); 135 136 Rect previousViewport(0, 0, previous.getViewportWidth(), previous.getViewportHeight()); 137 visibleBounds.doIntersect(previousViewport); 138 139 // Map visible bounds back to layer space, and intersect with parameter bounds 140 Rect layerBounds = visibleBounds; 141 Matrix4 inverse; 142 inverse.loadInverse(*previous.transform); 143 inverse.mapRect(layerBounds); 144 layerBounds.doIntersect(untransformedBounds); 145 146 int saveValue = mState.save((int) flags); 147 Snapshot& snapshot = *mState.writableSnapshot(); 148 149 // layerBounds is now original bounds, but with clipped to clip 150 // and viewport to ensure it's minimal size. 151 if (layerBounds.isEmpty() || untransformedBounds.isEmpty()) { 152 // Don't bother recording layer, since it's been rejected 153 snapshot.resetClip(0, 0, 0, 0); 154 return saveValue; 155 } 156 157 snapshot.flags |= Snapshot::kFlagFboTarget | Snapshot::kFlagIsFboLayer; 158 snapshot.initializeViewport(untransformedBounds.getWidth(), untransformedBounds.getHeight()); 159 snapshot.transform->loadTranslate(-untransformedBounds.left, -untransformedBounds.top, 0.0f); 160 161 Rect clip = layerBounds; 162 clip.translate(-untransformedBounds.left, -untransformedBounds.top); 163 snapshot.resetClip(clip.left, clip.top, clip.right, clip.bottom); 164 snapshot.roundRectClipState = nullptr; 165 166 addOp(new (alloc()) BeginLayerOp( 167 Rect(left, top, right, bottom), 168 *previous.transform, // transform to *draw* with 169 previous.getRenderTargetClip(), // clip to *draw* with 170 refPaint(paint))); 171 172 return saveValue; 173} 174 175// Matrix 176void RecordingCanvas::rotate(float degrees) { 177 if (degrees == 0) return; 178 179 mState.rotate(degrees); 180} 181 182void RecordingCanvas::scale(float sx, float sy) { 183 if (sx == 1 && sy == 1) return; 184 185 mState.scale(sx, sy); 186} 187 188void RecordingCanvas::skew(float sx, float sy) { 189 mState.skew(sx, sy); 190} 191 192void RecordingCanvas::translate(float dx, float dy) { 193 if (dx == 0 && dy == 0) return; 194 195 mState.translate(dx, dy, 0); 196} 197 198// Clip 199bool RecordingCanvas::getClipBounds(SkRect* outRect) const { 200 Rect bounds = mState.getLocalClipBounds(); 201 *outRect = SkRect::MakeLTRB(bounds.left, bounds.top, bounds.right, bounds.bottom); 202 return !(outRect->isEmpty()); 203} 204bool RecordingCanvas::quickRejectRect(float left, float top, float right, float bottom) const { 205 return mState.quickRejectConservative(left, top, right, bottom); 206} 207bool RecordingCanvas::quickRejectPath(const SkPath& path) const { 208 SkRect bounds = path.getBounds(); 209 return mState.quickRejectConservative(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom); 210} 211bool RecordingCanvas::clipRect(float left, float top, float right, float bottom, SkRegion::Op op) { 212 return mState.clipRect(left, top, right, bottom, op); 213} 214bool RecordingCanvas::clipPath(const SkPath* path, SkRegion::Op op) { 215 return mState.clipPath(path, op); 216} 217bool RecordingCanvas::clipRegion(const SkRegion* region, SkRegion::Op op) { 218 return mState.clipRegion(region, op); 219} 220 221// ---------------------------------------------------------------------------- 222// android/graphics/Canvas draw operations 223// ---------------------------------------------------------------------------- 224void RecordingCanvas::drawColor(int color, SkXfermode::Mode mode) { 225 SkPaint paint; 226 paint.setColor(color); 227 paint.setXfermodeMode(mode); 228 drawPaint(paint); 229} 230 231void RecordingCanvas::drawPaint(const SkPaint& paint) { 232 // TODO: more efficient recording? 233 addOp(new (alloc()) RectOp( 234 mState.getRenderTargetClipBounds(), 235 Matrix4::identity(), 236 mState.getRenderTargetClipBounds(), 237 refPaint(&paint))); 238} 239 240// Geometry 241void RecordingCanvas::drawPoints(const float* points, int count, const SkPaint& paint) { 242 LOG_ALWAYS_FATAL("TODO!"); 243} 244 245void RecordingCanvas::drawLines(const float* points, int floatCount, const SkPaint& paint) { 246 if (floatCount < 4) return; 247 floatCount &= ~0x3; // round down to nearest four 248 249 Rect unmappedBounds(points[0], points[1], points[0], points[1]); 250 for (int i = 2; i < floatCount; i += 2) { 251 unmappedBounds.left = std::min(unmappedBounds.left, points[i]); 252 unmappedBounds.right = std::max(unmappedBounds.right, points[i]); 253 unmappedBounds.top = std::min(unmappedBounds.top, points[i + 1]); 254 unmappedBounds.bottom = std::max(unmappedBounds.bottom, points[i + 1]); 255 } 256 257 // since anything AA stroke with less than 1.0 pixel width is drawn with an alpha-reduced 258 // 1.0 stroke, treat 1.0 as minimum. 259 unmappedBounds.outset(std::max(paint.getStrokeWidth(), 1.0f) * 0.5f); 260 261 addOp(new (alloc()) LinesOp( 262 unmappedBounds, 263 *mState.currentSnapshot()->transform, 264 mState.getRenderTargetClipBounds(), 265 refPaint(&paint), refBuffer<float>(points, floatCount), floatCount)); 266} 267 268void RecordingCanvas::drawRect(float left, float top, float right, float bottom, const SkPaint& paint) { 269 addOp(new (alloc()) RectOp( 270 Rect(left, top, right, bottom), 271 *(mState.currentSnapshot()->transform), 272 mState.getRenderTargetClipBounds(), 273 refPaint(&paint))); 274} 275 276void RecordingCanvas::drawSimpleRects(const float* rects, int vertexCount, const SkPaint* paint) { 277 if (rects == nullptr) return; 278 279 Vertex* rectData = (Vertex*) mDisplayList->allocator.alloc(vertexCount * sizeof(Vertex)); 280 Vertex* vertex = rectData; 281 282 float left = FLT_MAX; 283 float top = FLT_MAX; 284 float right = FLT_MIN; 285 float bottom = FLT_MIN; 286 for (int index = 0; index < vertexCount; index += 4) { 287 float l = rects[index + 0]; 288 float t = rects[index + 1]; 289 float r = rects[index + 2]; 290 float b = rects[index + 3]; 291 292 Vertex::set(vertex++, l, t); 293 Vertex::set(vertex++, r, t); 294 Vertex::set(vertex++, l, b); 295 Vertex::set(vertex++, r, b); 296 297 left = std::min(left, l); 298 top = std::min(top, t); 299 right = std::max(right, r); 300 bottom = std::max(bottom, b); 301 } 302 addOp(new (alloc()) SimpleRectsOp( 303 Rect(left, top, right, bottom), 304 *(mState.currentSnapshot()->transform), 305 mState.getRenderTargetClipBounds(), 306 refPaint(paint), rectData, vertexCount)); 307} 308 309void RecordingCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) { 310 if (paint.getStyle() == SkPaint::kFill_Style 311 && (!paint.isAntiAlias() || mState.currentTransform()->isSimple())) { 312 int count = 0; 313 Vector<float> rects; 314 SkRegion::Iterator it(region); 315 while (!it.done()) { 316 const SkIRect& r = it.rect(); 317 rects.push(r.fLeft); 318 rects.push(r.fTop); 319 rects.push(r.fRight); 320 rects.push(r.fBottom); 321 count += 4; 322 it.next(); 323 } 324 drawSimpleRects(rects.array(), count, &paint); 325 } else { 326 SkRegion::Iterator it(region); 327 while (!it.done()) { 328 const SkIRect& r = it.rect(); 329 drawRect(r.fLeft, r.fTop, r.fRight, r.fBottom, paint); 330 it.next(); 331 } 332 } 333} 334void RecordingCanvas::drawRoundRect(float left, float top, float right, float bottom, 335 float rx, float ry, const SkPaint& paint) { 336 LOG_ALWAYS_FATAL("TODO!"); 337} 338void RecordingCanvas::drawCircle(float x, float y, float radius, const SkPaint& paint) { 339 LOG_ALWAYS_FATAL("TODO!"); 340} 341void RecordingCanvas::drawOval(float left, float top, float right, float bottom, const SkPaint& paint) { 342 LOG_ALWAYS_FATAL("TODO!"); 343} 344void RecordingCanvas::drawArc(float left, float top, float right, float bottom, 345 float startAngle, float sweepAngle, bool useCenter, const SkPaint& paint) { 346 LOG_ALWAYS_FATAL("TODO!"); 347} 348void RecordingCanvas::drawPath(const SkPath& path, const SkPaint& paint) { 349 LOG_ALWAYS_FATAL("TODO!"); 350} 351 352// Bitmap-based 353void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, float left, float top, const SkPaint* paint) { 354 save(SkCanvas::kMatrix_SaveFlag); 355 translate(left, top); 356 drawBitmap(&bitmap, paint); 357 restore(); 358} 359 360void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, const SkMatrix& matrix, 361 const SkPaint* paint) { 362 if (matrix.isIdentity()) { 363 drawBitmap(&bitmap, paint); 364 } else if (!(matrix.getType() & ~(SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask)) 365 && MathUtils::isPositive(matrix.getScaleX()) 366 && MathUtils::isPositive(matrix.getScaleY())) { 367 // SkMatrix::isScaleTranslate() not available in L 368 SkRect src; 369 SkRect dst; 370 bitmap.getBounds(&src); 371 matrix.mapRect(&dst, src); 372 drawBitmap(bitmap, src.fLeft, src.fTop, src.fRight, src.fBottom, 373 dst.fLeft, dst.fTop, dst.fRight, dst.fBottom, paint); 374 } else { 375 save(SkCanvas::kMatrix_SaveFlag); 376 concat(matrix); 377 drawBitmap(&bitmap, paint); 378 restore(); 379 } 380} 381void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, float srcLeft, float srcTop, 382 float srcRight, float srcBottom, float dstLeft, float dstTop, 383 float dstRight, float dstBottom, const SkPaint* paint) { 384 if (srcLeft == 0 && srcTop == 0 385 && srcRight == bitmap.width() 386 && srcBottom == bitmap.height() 387 && (srcBottom - srcTop == dstBottom - dstTop) 388 && (srcRight - srcLeft == dstRight - dstLeft)) { 389 // transform simple rect to rect drawing case into position bitmap ops, since they merge 390 save(SkCanvas::kMatrix_SaveFlag); 391 translate(dstLeft, dstTop); 392 drawBitmap(&bitmap, paint); 393 restore(); 394 } else { 395 LOG_ALWAYS_FATAL("TODO!"); 396 } 397} 398void RecordingCanvas::drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight, 399 const float* vertices, const int* colors, const SkPaint* paint) { 400 LOG_ALWAYS_FATAL("TODO!"); 401} 402void RecordingCanvas::drawNinePatch(const SkBitmap& bitmap, const android::Res_png_9patch& chunk, 403 float dstLeft, float dstTop, float dstRight, float dstBottom, 404 const SkPaint* paint) { 405 LOG_ALWAYS_FATAL("TODO!"); 406} 407 408// Text 409void RecordingCanvas::drawText(const uint16_t* glyphs, const float* positions, int glyphCount, 410 const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop, 411 float boundsRight, float boundsBottom, float totalAdvance) { 412 if (!glyphs || !positions || glyphCount <= 0 || PaintUtils::paintWillNotDrawText(paint)) return; 413 glyphs = refBuffer<glyph_t>(glyphs, glyphCount); 414 positions = refBuffer<float>(positions, glyphCount * 2); 415 416 addOp(new (alloc()) TextOp( 417 Rect(boundsLeft, boundsTop, boundsRight, boundsBottom), 418 *(mState.currentSnapshot()->transform), 419 mState.getRenderTargetClipBounds(), 420 refPaint(&paint), glyphs, positions, glyphCount, x, y)); 421 drawTextDecorations(x, y, totalAdvance, paint); 422} 423 424void RecordingCanvas::drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path, 425 float hOffset, float vOffset, const SkPaint& paint) { 426 // NOTE: can't use refPaint() directly, since it forces left alignment 427 LOG_ALWAYS_FATAL("TODO!"); 428} 429 430void RecordingCanvas::drawBitmap(const SkBitmap* bitmap, const SkPaint* paint) { 431 addOp(new (alloc()) BitmapOp( 432 Rect(0, 0, bitmap->width(), bitmap->height()), 433 *(mState.currentSnapshot()->transform), 434 mState.getRenderTargetClipBounds(), 435 refPaint(paint), refBitmap(*bitmap))); 436} 437void RecordingCanvas::drawRenderNode(RenderNode* renderNode) { 438 auto&& stagingProps = renderNode->stagingProperties(); 439 RenderNodeOp* op = new (alloc()) RenderNodeOp( 440 Rect(stagingProps.getWidth(), stagingProps.getHeight()), 441 *(mState.currentSnapshot()->transform), 442 mState.getRenderTargetClipBounds(), 443 renderNode); 444 int opIndex = addOp(op); 445 int childIndex = mDisplayList->addChild(op); 446 447 // update the chunk's child indices 448 DisplayList::Chunk& chunk = mDisplayList->chunks.back(); 449 chunk.endChildIndex = childIndex + 1; 450 451 if (renderNode->stagingProperties().isProjectionReceiver()) { 452 // use staging property, since recording on UI thread 453 mDisplayList->projectionReceiveIndex = opIndex; 454 } 455} 456 457size_t RecordingCanvas::addOp(RecordedOp* op) { 458 // TODO: validate if "addDrawOp" quickrejection logic is useful before adding 459 int insertIndex = mDisplayList->ops.size(); 460 mDisplayList->ops.push_back(op); 461 if (mDeferredBarrierType != DeferredBarrierType::None) { 462 // op is first in new chunk 463 mDisplayList->chunks.emplace_back(); 464 DisplayList::Chunk& newChunk = mDisplayList->chunks.back(); 465 newChunk.beginOpIndex = insertIndex; 466 newChunk.endOpIndex = insertIndex + 1; 467 newChunk.reorderChildren = (mDeferredBarrierType == DeferredBarrierType::OutOfOrder); 468 469 int nextChildIndex = mDisplayList->children.size(); 470 newChunk.beginChildIndex = newChunk.endChildIndex = nextChildIndex; 471 mDeferredBarrierType = DeferredBarrierType::None; 472 } else { 473 // standard case - append to existing chunk 474 mDisplayList->chunks.back().endOpIndex = insertIndex + 1; 475 } 476 return insertIndex; 477} 478 479void RecordingCanvas::refBitmapsInShader(const SkShader* shader) { 480 if (!shader) return; 481 482 // If this paint has an SkShader that has an SkBitmap add 483 // it to the bitmap pile 484 SkBitmap bitmap; 485 SkShader::TileMode xy[2]; 486 if (shader->isABitmap(&bitmap, nullptr, xy)) { 487 refBitmap(bitmap); 488 return; 489 } 490 SkShader::ComposeRec rec; 491 if (shader->asACompose(&rec)) { 492 refBitmapsInShader(rec.fShaderA); 493 refBitmapsInShader(rec.fShaderB); 494 return; 495 } 496} 497 498}; // namespace uirenderer 499}; // namespace android 500