RecordingCanvas.cpp revision 5430ab220b231a96b71c3e030d0303d9ce008b05
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
240static Rect calcBoundsOfPoints(const float* points, int floatCount) {
241    Rect unmappedBounds(points[0], points[1], points[0], points[1]);
242    for (int i = 2; i < floatCount; i += 2) {
243        unmappedBounds.expandToCover(points[i], points[i + 1]);
244    }
245    return unmappedBounds;
246}
247
248// Geometry
249void RecordingCanvas::drawPoints(const float* points, int floatCount, const SkPaint& paint) {
250    if (floatCount < 2) return;
251    floatCount &= ~0x1; // round down to nearest two
252
253    addOp(new (alloc()) PointsOp(
254            calcBoundsOfPoints(points, floatCount),
255            *mState.currentSnapshot()->transform,
256            mState.getRenderTargetClipBounds(),
257            refPaint(&paint), refBuffer<float>(points, floatCount), floatCount));
258}
259
260void RecordingCanvas::drawLines(const float* points, int floatCount, const SkPaint& paint) {
261    if (floatCount < 4) return;
262    floatCount &= ~0x3; // round down to nearest four
263
264    addOp(new (alloc()) LinesOp(
265            calcBoundsOfPoints(points, floatCount),
266            *mState.currentSnapshot()->transform,
267            mState.getRenderTargetClipBounds(),
268            refPaint(&paint), refBuffer<float>(points, floatCount), floatCount));
269}
270
271void RecordingCanvas::drawRect(float left, float top, float right, float bottom, const SkPaint& paint) {
272    addOp(new (alloc()) RectOp(
273            Rect(left, top, right, bottom),
274            *(mState.currentSnapshot()->transform),
275            mState.getRenderTargetClipBounds(),
276            refPaint(&paint)));
277}
278
279void RecordingCanvas::drawSimpleRects(const float* rects, int vertexCount, const SkPaint* paint) {
280    if (rects == nullptr) return;
281
282    Vertex* rectData = (Vertex*) mDisplayList->allocator.alloc(vertexCount * sizeof(Vertex));
283    Vertex* vertex = rectData;
284
285    float left = FLT_MAX;
286    float top = FLT_MAX;
287    float right = FLT_MIN;
288    float bottom = FLT_MIN;
289    for (int index = 0; index < vertexCount; index += 4) {
290        float l = rects[index + 0];
291        float t = rects[index + 1];
292        float r = rects[index + 2];
293        float b = rects[index + 3];
294
295        Vertex::set(vertex++, l, t);
296        Vertex::set(vertex++, r, t);
297        Vertex::set(vertex++, l, b);
298        Vertex::set(vertex++, r, b);
299
300        left = std::min(left, l);
301        top = std::min(top, t);
302        right = std::max(right, r);
303        bottom = std::max(bottom, b);
304    }
305    addOp(new (alloc()) SimpleRectsOp(
306            Rect(left, top, right, bottom),
307            *(mState.currentSnapshot()->transform),
308            mState.getRenderTargetClipBounds(),
309            refPaint(paint), rectData, vertexCount));
310}
311
312void RecordingCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) {
313    if (paint.getStyle() == SkPaint::kFill_Style
314            && (!paint.isAntiAlias() || mState.currentTransform()->isSimple())) {
315        int count = 0;
316        Vector<float> rects;
317        SkRegion::Iterator it(region);
318        while (!it.done()) {
319            const SkIRect& r = it.rect();
320            rects.push(r.fLeft);
321            rects.push(r.fTop);
322            rects.push(r.fRight);
323            rects.push(r.fBottom);
324            count += 4;
325            it.next();
326        }
327        drawSimpleRects(rects.array(), count, &paint);
328    } else {
329        SkRegion::Iterator it(region);
330        while (!it.done()) {
331            const SkIRect& r = it.rect();
332            drawRect(r.fLeft, r.fTop, r.fRight, r.fBottom, paint);
333            it.next();
334        }
335    }
336}
337void RecordingCanvas::drawRoundRect(float left, float top, float right, float bottom,
338            float rx, float ry, const SkPaint& paint) {
339    addOp(new (alloc()) RoundRectOp(
340            Rect(left, top, right, bottom),
341            *(mState.currentSnapshot()->transform),
342            mState.getRenderTargetClipBounds(),
343            refPaint(&paint), rx, ry));
344}
345
346void RecordingCanvas::drawCircle(float x, float y, float radius, const SkPaint& paint) {
347    if (radius <= 0) return;
348    drawOval(x - radius, y - radius, x + radius, y + radius, paint);
349}
350
351void RecordingCanvas::drawOval(float left, float top, float right, float bottom, const SkPaint& paint) {
352    addOp(new (alloc()) OvalOp(
353            Rect(left, top, right, bottom),
354            *(mState.currentSnapshot()->transform),
355            mState.getRenderTargetClipBounds(),
356            refPaint(&paint)));
357}
358
359void RecordingCanvas::drawArc(float left, float top, float right, float bottom,
360        float startAngle, float sweepAngle, bool useCenter, const SkPaint& paint) {
361    addOp(new (alloc()) ArcOp(
362            Rect(left, top, right, bottom),
363            *(mState.currentSnapshot()->transform),
364            mState.getRenderTargetClipBounds(),
365            refPaint(&paint),
366            startAngle, sweepAngle, useCenter));
367}
368
369void RecordingCanvas::drawPath(const SkPath& path, const SkPaint& paint) {
370    addOp(new (alloc()) PathOp(
371            Rect(path.getBounds()),
372            *(mState.currentSnapshot()->transform),
373            mState.getRenderTargetClipBounds(),
374            refPaint(&paint), refPath(&path)));
375}
376
377// Bitmap-based
378void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, float left, float top, const SkPaint* paint) {
379    save(SkCanvas::kMatrix_SaveFlag);
380    translate(left, top);
381    drawBitmap(&bitmap, paint);
382    restore();
383}
384
385void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, const SkMatrix& matrix,
386                            const SkPaint* paint) {
387    if (matrix.isIdentity()) {
388        drawBitmap(&bitmap, paint);
389    } else if (!(matrix.getType() & ~(SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask))
390            && MathUtils::isPositive(matrix.getScaleX())
391            && MathUtils::isPositive(matrix.getScaleY())) {
392        // SkMatrix::isScaleTranslate() not available in L
393        SkRect src;
394        SkRect dst;
395        bitmap.getBounds(&src);
396        matrix.mapRect(&dst, src);
397        drawBitmap(bitmap, src.fLeft, src.fTop, src.fRight, src.fBottom,
398                   dst.fLeft, dst.fTop, dst.fRight, dst.fBottom, paint);
399    } else {
400        save(SkCanvas::kMatrix_SaveFlag);
401        concat(matrix);
402        drawBitmap(&bitmap, paint);
403        restore();
404    }
405}
406
407void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, float srcLeft, float srcTop,
408            float srcRight, float srcBottom, float dstLeft, float dstTop,
409            float dstRight, float dstBottom, const SkPaint* paint) {
410    if (srcLeft == 0 && srcTop == 0
411            && srcRight == bitmap.width()
412            && srcBottom == bitmap.height()
413            && (srcBottom - srcTop == dstBottom - dstTop)
414            && (srcRight - srcLeft == dstRight - dstLeft)) {
415        // transform simple rect to rect drawing case into position bitmap ops, since they merge
416        save(SkCanvas::kMatrix_SaveFlag);
417        translate(dstLeft, dstTop);
418        drawBitmap(&bitmap, paint);
419        restore();
420    } else {
421        addOp(new (alloc()) BitmapRectOp(
422                Rect(dstLeft, dstTop, dstRight, dstBottom),
423                *(mState.currentSnapshot()->transform),
424                mState.getRenderTargetClipBounds(),
425                refPaint(paint), refBitmap(bitmap),
426                Rect(srcLeft, srcTop, srcRight, srcBottom)));
427    }
428}
429
430void RecordingCanvas::drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight,
431            const float* vertices, const int* colors, const SkPaint* paint) {
432    int vertexCount = (meshWidth + 1) * (meshHeight + 1);
433    addOp(new (alloc()) BitmapMeshOp(
434            calcBoundsOfPoints(vertices, vertexCount * 2),
435            *(mState.currentSnapshot()->transform),
436            mState.getRenderTargetClipBounds(),
437            refPaint(paint), refBitmap(bitmap), meshWidth, meshHeight,
438            refBuffer<float>(vertices, vertexCount * 2), // 2 floats per vertex
439            refBuffer<int>(colors, vertexCount))); // 1 color per vertex
440}
441
442void RecordingCanvas::drawNinePatch(const SkBitmap& bitmap, const android::Res_png_9patch& patch,
443            float dstLeft, float dstTop, float dstRight, float dstBottom,
444            const SkPaint* paint) {
445    addOp(new (alloc()) PatchOp(
446            Rect(dstLeft, dstTop, dstRight, dstBottom),
447            *(mState.currentSnapshot()->transform),
448            mState.getRenderTargetClipBounds(),
449            refPaint(paint), refBitmap(bitmap), refPatch(&patch)));
450}
451
452// Text
453void RecordingCanvas::drawText(const uint16_t* glyphs, const float* positions, int glyphCount,
454            const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop,
455            float boundsRight, float boundsBottom, float totalAdvance) {
456    if (!glyphs || !positions || glyphCount <= 0 || PaintUtils::paintWillNotDrawText(paint)) return;
457    glyphs = refBuffer<glyph_t>(glyphs, glyphCount);
458    positions = refBuffer<float>(positions, glyphCount * 2);
459
460    // TODO: either must account for text shadow in bounds, or record separate ops for text shadows
461    addOp(new (alloc()) TextOp(
462            Rect(boundsLeft, boundsTop, boundsRight, boundsBottom),
463            *(mState.currentSnapshot()->transform),
464            mState.getRenderTargetClipBounds(),
465            refPaint(&paint), glyphs, positions, glyphCount, x, y));
466    drawTextDecorations(x, y, totalAdvance, paint);
467}
468
469void RecordingCanvas::drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
470            float hOffset, float vOffset, const SkPaint& paint) {
471    LOG_ALWAYS_FATAL("TODO!");
472}
473
474void RecordingCanvas::drawBitmap(const SkBitmap* bitmap, const SkPaint* paint) {
475    addOp(new (alloc()) BitmapOp(
476            Rect(bitmap->width(), bitmap->height()),
477            *(mState.currentSnapshot()->transform),
478            mState.getRenderTargetClipBounds(),
479            refPaint(paint), refBitmap(*bitmap)));
480}
481void RecordingCanvas::drawRenderNode(RenderNode* renderNode) {
482    auto&& stagingProps = renderNode->stagingProperties();
483    RenderNodeOp* op = new (alloc()) RenderNodeOp(
484            Rect(stagingProps.getWidth(), stagingProps.getHeight()),
485            *(mState.currentSnapshot()->transform),
486            mState.getRenderTargetClipBounds(),
487            renderNode);
488    int opIndex = addOp(op);
489    int childIndex = mDisplayList->addChild(op);
490
491    // update the chunk's child indices
492    DisplayList::Chunk& chunk = mDisplayList->chunks.back();
493    chunk.endChildIndex = childIndex + 1;
494
495    if (renderNode->stagingProperties().isProjectionReceiver()) {
496        // use staging property, since recording on UI thread
497        mDisplayList->projectionReceiveIndex = opIndex;
498    }
499}
500
501size_t RecordingCanvas::addOp(RecordedOp* op) {
502    // TODO: validate if "addDrawOp" quickrejection logic is useful before adding
503    int insertIndex = mDisplayList->ops.size();
504    mDisplayList->ops.push_back(op);
505    if (mDeferredBarrierType != DeferredBarrierType::None) {
506        // op is first in new chunk
507        mDisplayList->chunks.emplace_back();
508        DisplayList::Chunk& newChunk = mDisplayList->chunks.back();
509        newChunk.beginOpIndex = insertIndex;
510        newChunk.endOpIndex = insertIndex + 1;
511        newChunk.reorderChildren = (mDeferredBarrierType == DeferredBarrierType::OutOfOrder);
512
513        int nextChildIndex = mDisplayList->children.size();
514        newChunk.beginChildIndex = newChunk.endChildIndex = nextChildIndex;
515        mDeferredBarrierType = DeferredBarrierType::None;
516    } else {
517        // standard case - append to existing chunk
518        mDisplayList->chunks.back().endOpIndex = insertIndex + 1;
519    }
520    return insertIndex;
521}
522
523void RecordingCanvas::refBitmapsInShader(const SkShader* shader) {
524    if (!shader) return;
525
526    // If this paint has an SkShader that has an SkBitmap add
527    // it to the bitmap pile
528    SkBitmap bitmap;
529    SkShader::TileMode xy[2];
530    if (shader->isABitmap(&bitmap, nullptr, xy)) {
531        refBitmap(bitmap);
532        return;
533    }
534    SkShader::ComposeRec rec;
535    if (shader->asACompose(&rec)) {
536        refBitmapsInShader(rec.fShaderA);
537        refBitmapsInShader(rec.fShaderB);
538        return;
539    }
540}
541
542}; // namespace uirenderer
543}; // namespace android
544