RecordingCanvas.cpp revision a1717271caac5e8ea3808c331d4141ac01a42134
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    RenderNodeOp* op = new (alloc()) RenderNodeOp(
439            Rect(0, 0, renderNode->getWidth(), renderNode->getHeight()), // are these safe? they're theoretically dynamic
440            *(mState.currentSnapshot()->transform),
441            mState.getRenderTargetClipBounds(),
442            renderNode);
443    int opIndex = addOp(op);
444    int childIndex = mDisplayList->addChild(op);
445
446    // update the chunk's child indices
447    DisplayList::Chunk& chunk = mDisplayList->chunks.back();
448    chunk.endChildIndex = childIndex + 1;
449
450    if (renderNode->stagingProperties().isProjectionReceiver()) {
451        // use staging property, since recording on UI thread
452        mDisplayList->projectionReceiveIndex = opIndex;
453    }
454}
455
456size_t RecordingCanvas::addOp(RecordedOp* op) {
457    // TODO: validate if "addDrawOp" quickrejection logic is useful before adding
458    int insertIndex = mDisplayList->ops.size();
459    mDisplayList->ops.push_back(op);
460    if (mDeferredBarrierType != DeferredBarrierType::None) {
461        // op is first in new chunk
462        mDisplayList->chunks.emplace_back();
463        DisplayList::Chunk& newChunk = mDisplayList->chunks.back();
464        newChunk.beginOpIndex = insertIndex;
465        newChunk.endOpIndex = insertIndex + 1;
466        newChunk.reorderChildren = (mDeferredBarrierType == DeferredBarrierType::OutOfOrder);
467
468        int nextChildIndex = mDisplayList->children.size();
469        newChunk.beginChildIndex = newChunk.endChildIndex = nextChildIndex;
470        mDeferredBarrierType = DeferredBarrierType::None;
471    } else {
472        // standard case - append to existing chunk
473        mDisplayList->chunks.back().endOpIndex = insertIndex + 1;
474    }
475    return insertIndex;
476}
477
478void RecordingCanvas::refBitmapsInShader(const SkShader* shader) {
479    if (!shader) return;
480
481    // If this paint has an SkShader that has an SkBitmap add
482    // it to the bitmap pile
483    SkBitmap bitmap;
484    SkShader::TileMode xy[2];
485    if (shader->isABitmap(&bitmap, nullptr, xy)) {
486        refBitmap(bitmap);
487        return;
488    }
489    SkShader::ComposeRec rec;
490    if (shader->asACompose(&rec)) {
491        refBitmapsInShader(rec.fShaderA);
492        refBitmapsInShader(rec.fShaderB);
493        return;
494    }
495}
496
497}; // namespace uirenderer
498}; // namespace android
499