RecordingCanvas.cpp revision 98787e6c9b2c10b1ab7820bdac168686025b924a
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    Matrix4 identity;
234    identity.loadIdentity();
235
236    addOp(new (alloc()) RectOp(
237            mState.getRenderTargetClipBounds(),
238            identity,
239            mState.getRenderTargetClipBounds(),
240            refPaint(&paint)));
241}
242
243// Geometry
244void RecordingCanvas::drawPoints(const float* points, int count, const SkPaint& paint) {
245    LOG_ALWAYS_FATAL("TODO!");
246}
247void RecordingCanvas::drawLines(const float* points, int count, const SkPaint& paint) {
248    LOG_ALWAYS_FATAL("TODO!");
249}
250void RecordingCanvas::drawRect(float left, float top, float right, float bottom, const SkPaint& paint) {
251    addOp(new (alloc()) RectOp(
252            Rect(left, top, right, bottom),
253            *(mState.currentSnapshot()->transform),
254            mState.getRenderTargetClipBounds(),
255            refPaint(&paint)));
256}
257
258void RecordingCanvas::drawSimpleRects(const float* rects, int vertexCount, const SkPaint* paint) {
259    if (rects == nullptr) return;
260
261    Vertex* rectData = (Vertex*) mDisplayList->allocator.alloc(vertexCount * sizeof(Vertex));
262    Vertex* vertex = rectData;
263
264    float left = FLT_MAX;
265    float top = FLT_MAX;
266    float right = FLT_MIN;
267    float bottom = FLT_MIN;
268    for (int index = 0; index < vertexCount; index += 4) {
269        float l = rects[index + 0];
270        float t = rects[index + 1];
271        float r = rects[index + 2];
272        float b = rects[index + 3];
273
274        Vertex::set(vertex++, l, t);
275        Vertex::set(vertex++, r, t);
276        Vertex::set(vertex++, l, b);
277        Vertex::set(vertex++, r, b);
278
279        left = std::min(left, l);
280        top = std::min(top, t);
281        right = std::max(right, r);
282        bottom = std::max(bottom, b);
283    }
284    addOp(new (alloc()) SimpleRectsOp(
285            Rect(left, top, right, bottom),
286            *(mState.currentSnapshot()->transform),
287            mState.getRenderTargetClipBounds(),
288            refPaint(paint), rectData, vertexCount));
289}
290
291void RecordingCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) {
292    if (paint.getStyle() == SkPaint::kFill_Style
293            && (!paint.isAntiAlias() || mState.currentTransform()->isSimple())) {
294        int count = 0;
295        Vector<float> rects;
296        SkRegion::Iterator it(region);
297        while (!it.done()) {
298            const SkIRect& r = it.rect();
299            rects.push(r.fLeft);
300            rects.push(r.fTop);
301            rects.push(r.fRight);
302            rects.push(r.fBottom);
303            count += 4;
304            it.next();
305        }
306        drawSimpleRects(rects.array(), count, &paint);
307    } else {
308        SkRegion::Iterator it(region);
309        while (!it.done()) {
310            const SkIRect& r = it.rect();
311            drawRect(r.fLeft, r.fTop, r.fRight, r.fBottom, paint);
312            it.next();
313        }
314    }
315}
316void RecordingCanvas::drawRoundRect(float left, float top, float right, float bottom,
317            float rx, float ry, const SkPaint& paint) {
318    LOG_ALWAYS_FATAL("TODO!");
319}
320void RecordingCanvas::drawCircle(float x, float y, float radius, const SkPaint& paint) {
321    LOG_ALWAYS_FATAL("TODO!");
322}
323void RecordingCanvas::drawOval(float left, float top, float right, float bottom, const SkPaint& paint) {
324    LOG_ALWAYS_FATAL("TODO!");
325}
326void RecordingCanvas::drawArc(float left, float top, float right, float bottom,
327            float startAngle, float sweepAngle, bool useCenter, const SkPaint& paint) {
328    LOG_ALWAYS_FATAL("TODO!");
329}
330void RecordingCanvas::drawPath(const SkPath& path, const SkPaint& paint) {
331    LOG_ALWAYS_FATAL("TODO!");
332}
333
334// Bitmap-based
335void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, float left, float top, const SkPaint* paint) {
336    save(SkCanvas::kMatrix_SaveFlag);
337    translate(left, top);
338    drawBitmap(&bitmap, paint);
339    restore();
340}
341
342void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, const SkMatrix& matrix,
343                            const SkPaint* paint) {
344    if (matrix.isIdentity()) {
345        drawBitmap(&bitmap, paint);
346    } else if (!(matrix.getType() & ~(SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask))
347            && MathUtils::isPositive(matrix.getScaleX())
348            && MathUtils::isPositive(matrix.getScaleY())) {
349        // SkMatrix::isScaleTranslate() not available in L
350        SkRect src;
351        SkRect dst;
352        bitmap.getBounds(&src);
353        matrix.mapRect(&dst, src);
354        drawBitmap(bitmap, src.fLeft, src.fTop, src.fRight, src.fBottom,
355                   dst.fLeft, dst.fTop, dst.fRight, dst.fBottom, paint);
356    } else {
357        save(SkCanvas::kMatrix_SaveFlag);
358        concat(matrix);
359        drawBitmap(&bitmap, paint);
360        restore();
361    }
362}
363void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, float srcLeft, float srcTop,
364            float srcRight, float srcBottom, float dstLeft, float dstTop,
365            float dstRight, float dstBottom, const SkPaint* paint) {
366    if (srcLeft == 0 && srcTop == 0
367            && srcRight == bitmap.width()
368            && srcBottom == bitmap.height()
369            && (srcBottom - srcTop == dstBottom - dstTop)
370            && (srcRight - srcLeft == dstRight - dstLeft)) {
371        // transform simple rect to rect drawing case into position bitmap ops, since they merge
372        save(SkCanvas::kMatrix_SaveFlag);
373        translate(dstLeft, dstTop);
374        drawBitmap(&bitmap, paint);
375        restore();
376    } else {
377        LOG_ALWAYS_FATAL("TODO!");
378    }
379}
380void RecordingCanvas::drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight,
381            const float* vertices, const int* colors, const SkPaint* paint) {
382    LOG_ALWAYS_FATAL("TODO!");
383}
384void RecordingCanvas::drawNinePatch(const SkBitmap& bitmap, const android::Res_png_9patch& chunk,
385            float dstLeft, float dstTop, float dstRight, float dstBottom,
386            const SkPaint* paint) {
387    LOG_ALWAYS_FATAL("TODO!");
388}
389
390// Text
391void RecordingCanvas::drawText(const uint16_t* glyphs, const float* positions, int count,
392            const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop,
393            float boundsRight, float boundsBottom, float totalAdvance) {
394    LOG_ALWAYS_FATAL("TODO!");
395}
396void RecordingCanvas::drawPosText(const uint16_t* text, const float* positions, int count,
397            int posCount, const SkPaint& paint) {
398    LOG_ALWAYS_FATAL("TODO!");
399}
400void RecordingCanvas::drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
401            float hOffset, float vOffset, const SkPaint& paint) {
402    LOG_ALWAYS_FATAL("TODO!");
403}
404
405void RecordingCanvas::drawBitmap(const SkBitmap* bitmap, const SkPaint* paint) {
406    addOp(new (alloc()) BitmapOp(
407            Rect(0, 0, bitmap->width(), bitmap->height()),
408            *(mState.currentSnapshot()->transform),
409            mState.getRenderTargetClipBounds(),
410            refPaint(paint), refBitmap(*bitmap)));
411}
412void RecordingCanvas::drawRenderNode(RenderNode* renderNode) {
413    RenderNodeOp* op = new (alloc()) RenderNodeOp(
414            Rect(0, 0, renderNode->getWidth(), renderNode->getHeight()), // are these safe? they're theoretically dynamic
415            *(mState.currentSnapshot()->transform),
416            mState.getRenderTargetClipBounds(),
417            renderNode);
418    int opIndex = addOp(op);
419    int childIndex = mDisplayList->addChild(op);
420
421    // update the chunk's child indices
422    DisplayList::Chunk& chunk = mDisplayList->chunks.back();
423    chunk.endChildIndex = childIndex + 1;
424
425    if (renderNode->stagingProperties().isProjectionReceiver()) {
426        // use staging property, since recording on UI thread
427        mDisplayList->projectionReceiveIndex = opIndex;
428    }
429}
430
431size_t RecordingCanvas::addOp(RecordedOp* op) {
432    // TODO: validate if "addDrawOp" quickrejection logic is useful before adding
433    int insertIndex = mDisplayList->ops.size();
434    mDisplayList->ops.push_back(op);
435    if (mDeferredBarrierType != DeferredBarrierType::None) {
436        // op is first in new chunk
437        mDisplayList->chunks.emplace_back();
438        DisplayList::Chunk& newChunk = mDisplayList->chunks.back();
439        newChunk.beginOpIndex = insertIndex;
440        newChunk.endOpIndex = insertIndex + 1;
441        newChunk.reorderChildren = (mDeferredBarrierType == DeferredBarrierType::OutOfOrder);
442
443        int nextChildIndex = mDisplayList->children.size();
444        newChunk.beginChildIndex = newChunk.endChildIndex = nextChildIndex;
445        mDeferredBarrierType = DeferredBarrierType::None;
446    } else {
447        // standard case - append to existing chunk
448        mDisplayList->chunks.back().endOpIndex = insertIndex + 1;
449    }
450    return insertIndex;
451}
452
453void RecordingCanvas::refBitmapsInShader(const SkShader* shader) {
454    if (!shader) return;
455
456    // If this paint has an SkShader that has an SkBitmap add
457    // it to the bitmap pile
458    SkBitmap bitmap;
459    SkShader::TileMode xy[2];
460    if (shader->isABitmap(&bitmap, nullptr, xy)) {
461        refBitmap(bitmap);
462        return;
463    }
464    SkShader::ComposeRec rec;
465    if (shader->asACompose(&rec)) {
466        refBitmapsInShader(rec.fShaderA);
467        refBitmapsInShader(rec.fShaderB);
468        return;
469    }
470}
471
472}; // namespace uirenderer
473}; // namespace android
474