| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "Canvas.h" |
| #include "CanvasState.h" |
| #include "utils/MathUtils.h" |
| |
| namespace android { |
| namespace uirenderer { |
| |
| |
| CanvasState::CanvasState(CanvasStateClient& renderer) |
| : mDirtyClip(false) |
| , mWidth(-1) |
| , mHeight(-1) |
| , mSaveCount(1) |
| , mCanvas(renderer) |
| , mSnapshot(&mFirstSnapshot) { |
| } |
| |
| CanvasState::~CanvasState() { |
| // First call freeSnapshot on all but mFirstSnapshot |
| // to invoke all the dtors |
| freeAllSnapshots(); |
| |
| // Now actually release the memory |
| while (mSnapshotPool) { |
| void* temp = mSnapshotPool; |
| mSnapshotPool = mSnapshotPool->previous; |
| free(temp); |
| } |
| } |
| |
| void CanvasState::initializeRecordingSaveStack(int viewportWidth, int viewportHeight) { |
| if (mWidth != viewportWidth || mHeight != viewportHeight) { |
| mWidth = viewportWidth; |
| mHeight = viewportHeight; |
| mFirstSnapshot.initializeViewport(viewportWidth, viewportHeight); |
| mCanvas.onViewportInitialized(); |
| } |
| |
| freeAllSnapshots(); |
| mSnapshot = allocSnapshot(&mFirstSnapshot, SaveFlags::MatrixClip); |
| mSnapshot->setRelativeLightCenter(Vector3()); |
| mSaveCount = 1; |
| } |
| |
| void CanvasState::initializeSaveStack( |
| int viewportWidth, int viewportHeight, |
| float clipLeft, float clipTop, |
| float clipRight, float clipBottom, const Vector3& lightCenter) { |
| if (mWidth != viewportWidth || mHeight != viewportHeight) { |
| mWidth = viewportWidth; |
| mHeight = viewportHeight; |
| mFirstSnapshot.initializeViewport(viewportWidth, viewportHeight); |
| mCanvas.onViewportInitialized(); |
| } |
| |
| freeAllSnapshots(); |
| mSnapshot = allocSnapshot(&mFirstSnapshot, SaveFlags::MatrixClip); |
| mSnapshot->setClip(clipLeft, clipTop, clipRight, clipBottom); |
| mSnapshot->fbo = mCanvas.getTargetFbo(); |
| mSnapshot->setRelativeLightCenter(lightCenter); |
| mSaveCount = 1; |
| } |
| |
| Snapshot* CanvasState::allocSnapshot(Snapshot* previous, int savecount) { |
| void* memory; |
| if (mSnapshotPool) { |
| memory = mSnapshotPool; |
| mSnapshotPool = mSnapshotPool->previous; |
| mSnapshotPoolCount--; |
| } else { |
| memory = malloc(sizeof(Snapshot)); |
| } |
| return new (memory) Snapshot(previous, savecount); |
| } |
| |
| void CanvasState::freeSnapshot(Snapshot* snapshot) { |
| snapshot->~Snapshot(); |
| // Arbitrary number, just don't let this grown unbounded |
| if (mSnapshotPoolCount > 10) { |
| free((void*) snapshot); |
| } else { |
| snapshot->previous = mSnapshotPool; |
| mSnapshotPool = snapshot; |
| mSnapshotPoolCount++; |
| } |
| } |
| |
| void CanvasState::freeAllSnapshots() { |
| while (mSnapshot != &mFirstSnapshot) { |
| Snapshot* temp = mSnapshot; |
| mSnapshot = mSnapshot->previous; |
| freeSnapshot(temp); |
| } |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // Save (layer) |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * Guaranteed to save without side-effects |
| * |
| * This approach, here and in restoreSnapshot(), allows subclasses to directly manipulate the save |
| * stack, and ensures restoreToCount() doesn't call back into subclass overrides. |
| */ |
| int CanvasState::saveSnapshot(int flags) { |
| mSnapshot = allocSnapshot(mSnapshot, flags); |
| return mSaveCount++; |
| } |
| |
| int CanvasState::save(int flags) { |
| return saveSnapshot(flags); |
| } |
| |
| /** |
| * Guaranteed to restore without side-effects. |
| */ |
| void CanvasState::restoreSnapshot() { |
| Snapshot* toRemove = mSnapshot; |
| Snapshot* toRestore = mSnapshot->previous; |
| |
| mSaveCount--; |
| mSnapshot = toRestore; |
| |
| // subclass handles restore implementation |
| mCanvas.onSnapshotRestored(*toRemove, *toRestore); |
| |
| freeSnapshot(toRemove); |
| } |
| |
| void CanvasState::restore() { |
| if (mSaveCount > 1) { |
| restoreSnapshot(); |
| } |
| } |
| |
| void CanvasState::restoreToCount(int saveCount) { |
| if (saveCount < 1) saveCount = 1; |
| |
| while (mSaveCount > saveCount) { |
| restoreSnapshot(); |
| } |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // Matrix |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| void CanvasState::getMatrix(SkMatrix* matrix) const { |
| mSnapshot->transform->copyTo(*matrix); |
| } |
| |
| void CanvasState::translate(float dx, float dy, float dz) { |
| mSnapshot->transform->translate(dx, dy, dz); |
| } |
| |
| void CanvasState::rotate(float degrees) { |
| mSnapshot->transform->rotate(degrees, 0.0f, 0.0f, 1.0f); |
| } |
| |
| void CanvasState::scale(float sx, float sy) { |
| mSnapshot->transform->scale(sx, sy, 1.0f); |
| } |
| |
| void CanvasState::skew(float sx, float sy) { |
| mSnapshot->transform->skew(sx, sy); |
| } |
| |
| void CanvasState::setMatrix(const SkMatrix& matrix) { |
| mSnapshot->transform->load(matrix); |
| } |
| |
| void CanvasState::setMatrix(const Matrix4& matrix) { |
| *(mSnapshot->transform) = matrix; |
| } |
| |
| void CanvasState::concatMatrix(const SkMatrix& matrix) { |
| mat4 transform(matrix); |
| mSnapshot->transform->multiply(transform); |
| } |
| |
| void CanvasState::concatMatrix(const Matrix4& matrix) { |
| mSnapshot->transform->multiply(matrix); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // Clip |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| bool CanvasState::clipRect(float left, float top, float right, float bottom, SkRegion::Op op) { |
| mSnapshot->clip(Rect(left, top, right, bottom), op); |
| mDirtyClip = true; |
| return !mSnapshot->clipIsEmpty(); |
| } |
| |
| bool CanvasState::clipPath(const SkPath* path, SkRegion::Op op) { |
| mSnapshot->clipPath(*path, op); |
| mDirtyClip = true; |
| return !mSnapshot->clipIsEmpty(); |
| } |
| |
| bool CanvasState::clipRegion(const SkRegion* region, SkRegion::Op op) { |
| mSnapshot->clipRegionTransformed(*region, op); |
| mDirtyClip = true; |
| return !mSnapshot->clipIsEmpty(); |
| } |
| |
| void CanvasState::setClippingOutline(LinearAllocator& allocator, const Outline* outline) { |
| Rect bounds; |
| float radius; |
| if (!outline->getAsRoundRect(&bounds, &radius)) return; // only RR supported |
| |
| bool outlineIsRounded = MathUtils::isPositive(radius); |
| if (!outlineIsRounded || currentTransform()->isSimple()) { |
| // TODO: consider storing this rect separately, so that this can't be replaced with clip ops |
| clipRect(bounds.left, bounds.top, bounds.right, bounds.bottom, SkRegion::kIntersect_Op); |
| } |
| if (outlineIsRounded) { |
| setClippingRoundRect(allocator, bounds, radius, false); |
| } |
| } |
| |
| void CanvasState::setClippingRoundRect(LinearAllocator& allocator, |
| const Rect& rect, float radius, bool highPriority) { |
| mSnapshot->setClippingRoundRect(allocator, rect, radius, highPriority); |
| } |
| |
| void CanvasState::setProjectionPathMask(LinearAllocator& allocator, const SkPath* path) { |
| mSnapshot->setProjectionPathMask(allocator, path); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // Quick Rejection |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * Calculates whether content drawn within the passed bounds would be outside of, or intersect with |
| * the clipRect. Does not modify the scissor. |
| * |
| * @param clipRequired if not null, will be set to true if element intersects clip |
| * (and wasn't rejected) |
| * |
| * @param snapOut if set, the geometry will be treated as having an AA ramp. |
| * See Rect::snapGeometryToPixelBoundaries() |
| */ |
| bool CanvasState::calculateQuickRejectForScissor(float left, float top, |
| float right, float bottom, |
| bool* clipRequired, bool* roundRectClipRequired, |
| bool snapOut) const { |
| if (mSnapshot->isIgnored() || bottom <= top || right <= left) { |
| return true; |
| } |
| |
| Rect r(left, top, right, bottom); |
| currentTransform()->mapRect(r); |
| r.snapGeometryToPixelBoundaries(snapOut); |
| |
| Rect clipRect(currentRenderTargetClip()); |
| clipRect.snapToPixelBoundaries(); |
| |
| if (!clipRect.intersects(r)) return true; |
| |
| // clip is required if geometry intersects clip rect |
| if (clipRequired) { |
| *clipRequired = !clipRect.contains(r); |
| } |
| |
| // round rect clip is required if RR clip exists, and geometry intersects its corners |
| if (roundRectClipRequired) { |
| *roundRectClipRequired = mSnapshot->roundRectClipState != nullptr |
| && mSnapshot->roundRectClipState->areaRequiresRoundRectClip(r); |
| } |
| return false; |
| } |
| |
| bool CanvasState::quickRejectConservative(float left, float top, |
| float right, float bottom) const { |
| if (mSnapshot->isIgnored() || bottom <= top || right <= left) { |
| return true; |
| } |
| |
| Rect r(left, top, right, bottom); |
| currentTransform()->mapRect(r); |
| r.roundOut(); // rounded out to be conservative |
| |
| Rect clipRect(currentRenderTargetClip()); |
| clipRect.snapToPixelBoundaries(); |
| |
| if (!clipRect.intersects(r)) return true; |
| |
| return false; |
| } |
| |
| } // namespace uirenderer |
| } // namespace android |