/* * 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. */ #define LOG_TAG "OpenGLRenderer" #include #include "StatefulBaseRenderer.h" #include "utils/MathUtils.h" namespace android { namespace uirenderer { StatefulBaseRenderer::StatefulBaseRenderer() : mDirtyClip(false) , mWidth(-1) , mHeight(-1) , mSaveCount(1) , mFirstSnapshot(new Snapshot) , mSnapshot(mFirstSnapshot) { } void StatefulBaseRenderer::initializeSaveStack(float clipLeft, float clipTop, float clipRight, float clipBottom, const Vector3& lightCenter) { mSnapshot = new Snapshot(mFirstSnapshot, SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); mSnapshot->setClip(clipLeft, clipTop, clipRight, clipBottom); mSnapshot->fbo = getTargetFbo(); mSnapshot->setRelativeLightCenter(lightCenter); mSaveCount = 1; } void StatefulBaseRenderer::setViewport(int width, int height) { mWidth = width; mHeight = height; mFirstSnapshot->initializeViewport(width, height); onViewportInitialized(); // create a temporary 1st snapshot, so old snapshots are released, // and viewport can be queried safely. // TODO: remove, combine viewport + save stack initialization mSnapshot = new Snapshot(mFirstSnapshot, SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); mSaveCount = 1; } /////////////////////////////////////////////////////////////////////////////// // Save (layer) /////////////////////////////////////////////////////////////////////////////// /** * Non-virtual implementation of save, guaranteed to save without side-effects * * The approach here and in restoreSnapshot(), allows subclasses to directly manipulate the save * stack, and ensures restoreToCount() doesn't call back into subclass overrides. */ int StatefulBaseRenderer::saveSnapshot(int flags) { mSnapshot = new Snapshot(mSnapshot, flags); return mSaveCount++; } int StatefulBaseRenderer::save(int flags) { return saveSnapshot(flags); } /** * Non-virtual implementation of restore, guaranteed to restore without side-effects. */ void StatefulBaseRenderer::restoreSnapshot() { sp toRemove = mSnapshot; sp toRestore = mSnapshot->previous; mSaveCount--; mSnapshot = toRestore; // subclass handles restore implementation onSnapshotRestored(*toRemove, *toRestore); } void StatefulBaseRenderer::restore() { if (mSaveCount > 1) { restoreSnapshot(); } } void StatefulBaseRenderer::restoreToCount(int saveCount) { if (saveCount < 1) saveCount = 1; while (mSaveCount > saveCount) { restoreSnapshot(); } } /////////////////////////////////////////////////////////////////////////////// // Matrix /////////////////////////////////////////////////////////////////////////////// void StatefulBaseRenderer::getMatrix(SkMatrix* matrix) const { mSnapshot->transform->copyTo(*matrix); } void StatefulBaseRenderer::translate(float dx, float dy, float dz) { mSnapshot->transform->translate(dx, dy, dz); } void StatefulBaseRenderer::rotate(float degrees) { mSnapshot->transform->rotate(degrees, 0.0f, 0.0f, 1.0f); } void StatefulBaseRenderer::scale(float sx, float sy) { mSnapshot->transform->scale(sx, sy, 1.0f); } void StatefulBaseRenderer::skew(float sx, float sy) { mSnapshot->transform->skew(sx, sy); } void StatefulBaseRenderer::setMatrix(const SkMatrix& matrix) { mSnapshot->transform->load(matrix); } void StatefulBaseRenderer::setMatrix(const Matrix4& matrix) { mSnapshot->transform->load(matrix); } void StatefulBaseRenderer::concatMatrix(const SkMatrix& matrix) { mat4 transform(matrix); mSnapshot->transform->multiply(transform); } void StatefulBaseRenderer::concatMatrix(const Matrix4& matrix) { mSnapshot->transform->multiply(matrix); } /////////////////////////////////////////////////////////////////////////////// // Clip /////////////////////////////////////////////////////////////////////////////// bool StatefulBaseRenderer::clipRect(float left, float top, float right, float bottom, SkRegion::Op op) { if (CC_LIKELY(currentTransform()->rectToRect())) { mDirtyClip |= mSnapshot->clip(left, top, right, bottom, op); return !mSnapshot->clipRect->isEmpty(); } SkPath path; path.addRect(left, top, right, bottom); return StatefulBaseRenderer::clipPath(&path, op); } bool StatefulBaseRenderer::clipPath(const SkPath* path, SkRegion::Op op) { SkMatrix transform; currentTransform()->copyTo(transform); SkPath transformed; path->transform(transform, &transformed); SkRegion clip; if (!mSnapshot->previous->clipRegion->isEmpty()) { clip.setRegion(*mSnapshot->previous->clipRegion); } else { if (mSnapshot->previous == firstSnapshot()) { clip.setRect(0, 0, getWidth(), getHeight()); } else { Rect* bounds = mSnapshot->previous->clipRect; clip.setRect(bounds->left, bounds->top, bounds->right, bounds->bottom); } } SkRegion region; region.setPath(transformed, clip); // region is the transformed input path, masked by the previous clip mDirtyClip |= mSnapshot->clipRegionTransformed(region, op); return !mSnapshot->clipRect->isEmpty(); } bool StatefulBaseRenderer::clipRegion(const SkRegion* region, SkRegion::Op op) { mDirtyClip |= mSnapshot->clipRegionTransformed(*region, op); return !mSnapshot->clipRect->isEmpty(); } void StatefulBaseRenderer::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 StatefulBaseRenderer::setClippingRoundRect(LinearAllocator& allocator, const Rect& rect, float radius, bool highPriority) { mSnapshot->setClippingRoundRect(allocator, rect, radius, highPriority); } /////////////////////////////////////////////////////////////////////////////// // 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 StatefulBaseRenderer::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(*currentClipRect()); 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 != NULL && mSnapshot->roundRectClipState->areaRequiresRoundRectClip(r); } return false; } /** * Returns false if drawing won't be clipped out. * * Makes the decision conservatively, by rounding out the mapped rect before comparing with the * clipRect. To be used when perfect, pixel accuracy is not possible (esp. with tessellation) but * rejection is still desired. * * This function, unlike quickRejectSetupScissor, should be used where precise geometry information * isn't known (esp. when geometry adjusts based on scale). Generally, this will be first pass * rejection where precise rejection isn't important, or precise information isn't available. */ bool StatefulBaseRenderer::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(*currentClipRect()); clipRect.snapToPixelBoundaries(); if (!clipRect.intersects(r)) return true; return false; } }; // namespace uirenderer }; // namespace android