Home | History | Annotate | Download | only in hwui
      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 "BakedOpState.h"
     18 
     19 #include "ClipArea.h"
     20 
     21 namespace android {
     22 namespace uirenderer {
     23 
     24 static int computeClipSideFlags(const Rect& clip, const Rect& bounds) {
     25     int clipSideFlags = 0;
     26     if (clip.left > bounds.left) clipSideFlags |= OpClipSideFlags::Left;
     27     if (clip.top > bounds.top) clipSideFlags |= OpClipSideFlags::Top;
     28     if (clip.right < bounds.right) clipSideFlags |= OpClipSideFlags::Right;
     29     if (clip.bottom < bounds.bottom) clipSideFlags |= OpClipSideFlags::Bottom;
     30     return clipSideFlags;
     31 }
     32 
     33 ResolvedRenderState::ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot,
     34         const RecordedOp& recordedOp, bool expandForStroke, bool expandForPathTexture) {
     35     // resolvedMatrix = parentMatrix * localMatrix
     36     transform.loadMultiply(*snapshot.transform, recordedOp.localMatrix);
     37 
     38     // resolvedClippedBounds = intersect(resolvedMatrix * opBounds, resolvedClipRect)
     39     clippedBounds = recordedOp.unmappedBounds;
     40     if (CC_UNLIKELY(expandForStroke)) {
     41         // account for non-hairline stroke
     42         clippedBounds.outset(recordedOp.paint->getStrokeWidth() * 0.5f);
     43     } else if (CC_UNLIKELY(expandForPathTexture)) {
     44         clippedBounds.outset(1);
     45     }
     46     transform.mapRect(clippedBounds);
     47     if (CC_UNLIKELY(expandForStroke
     48             && (!transform.isPureTranslate() || recordedOp.paint->getStrokeWidth() < 1.0f))) {
     49         // account for hairline stroke when stroke may be < 1 scaled pixel
     50         // Non translate || strokeWidth < 1 is conservative, but will cover all cases
     51         clippedBounds.outset(0.5f);
     52     }
     53 
     54     // resolvedClipRect = intersect(parentMatrix * localClip, parentClip)
     55     clipState = snapshot.serializeIntersectedClip(allocator,
     56             recordedOp.localClip, *(snapshot.transform));
     57     LOG_ALWAYS_FATAL_IF(!clipState, "must clip!");
     58 
     59     const Rect& clipRect = clipState->rect;
     60     if (CC_UNLIKELY(clipRect.isEmpty() || !clippedBounds.intersects(clipRect))) {
     61         // Rejected based on either empty clip, or bounds not intersecting with clip
     62 
     63         // Note: we could rewind the clipState object in situations where the clipRect is empty,
     64         // but *only* if the caching logic within ClipArea was aware of the rewind.
     65         clipState = nullptr;
     66         clippedBounds.setEmpty();
     67     } else {
     68         // Not rejected! compute true clippedBounds, clipSideFlags, and path mask
     69         clipSideFlags = computeClipSideFlags(clipRect, clippedBounds);
     70         clippedBounds.doIntersect(clipRect);
     71 
     72         if (CC_UNLIKELY(snapshot.projectionPathMask)) {
     73             // map projection path mask from render target space into op space,
     74             // so intersection with op geometry is possible
     75             Matrix4 inverseTransform;
     76             inverseTransform.loadInverse(transform);
     77             SkMatrix skInverseTransform;
     78             inverseTransform.copyTo(skInverseTransform);
     79 
     80             auto localMask = allocator.create<SkPath>();
     81             snapshot.projectionPathMask->transform(skInverseTransform, localMask);
     82             localProjectionPathMask = localMask;
     83         }
     84     }
     85 }
     86 
     87 ResolvedRenderState::ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot,
     88         const Matrix4& localTransform, const ClipBase* localClip) {
     89     transform.loadMultiply(*snapshot.transform, localTransform);
     90     clipState = snapshot.serializeIntersectedClip(allocator, localClip, *(snapshot.transform));
     91     clippedBounds = clipState->rect;
     92     clipSideFlags = OpClipSideFlags::Full;
     93     localProjectionPathMask = nullptr;
     94 }
     95 
     96 ResolvedRenderState::ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot)
     97         : transform(*snapshot.transform)
     98         , clipState(snapshot.mutateClipArea().serializeClip(allocator))
     99         , clippedBounds(clipState->rect)
    100         , clipSideFlags(OpClipSideFlags::Full)
    101         , localProjectionPathMask(nullptr) {}
    102 
    103 ResolvedRenderState::ResolvedRenderState(const ClipRect* clipRect, const Rect& dstRect)
    104         : transform(Matrix4::identity())
    105         , clipState(clipRect)
    106         , clippedBounds(dstRect)
    107         , clipSideFlags(computeClipSideFlags(clipRect->rect, dstRect))
    108         , localProjectionPathMask(nullptr) {
    109     clippedBounds.doIntersect(clipRect->rect);
    110 }
    111 
    112 BakedOpState* BakedOpState::tryConstruct(LinearAllocator& allocator,
    113         Snapshot& snapshot, const RecordedOp& recordedOp) {
    114     if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
    115     BakedOpState* bakedState = allocator.create_trivial<BakedOpState>(
    116             allocator, snapshot, recordedOp, false, false);
    117     if (bakedState->computedState.clippedBounds.isEmpty()) {
    118         // bounds are empty, so op is rejected
    119         allocator.rewindIfLastAlloc(bakedState);
    120         return nullptr;
    121     }
    122     return bakedState;
    123 }
    124 
    125 BakedOpState* BakedOpState::tryConstructUnbounded(LinearAllocator& allocator,
    126         Snapshot& snapshot, const RecordedOp& recordedOp) {
    127     if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
    128     return allocator.create_trivial<BakedOpState>(allocator, snapshot, recordedOp);
    129 }
    130 
    131 BakedOpState* BakedOpState::tryStrokeableOpConstruct(LinearAllocator& allocator,
    132         Snapshot& snapshot, const RecordedOp& recordedOp, StrokeBehavior strokeBehavior,
    133         bool expandForPathTexture) {
    134     if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
    135     bool expandForStroke = (strokeBehavior == StrokeBehavior::Forced
    136             || (recordedOp.paint && recordedOp.paint->getStyle() != SkPaint::kFill_Style));
    137 
    138     BakedOpState* bakedState = allocator.create_trivial<BakedOpState>(
    139            allocator, snapshot, recordedOp, expandForStroke, expandForPathTexture);
    140     if (bakedState->computedState.clippedBounds.isEmpty()) {
    141         // bounds are empty, so op is rejected
    142         // NOTE: this won't succeed if a clip was allocated
    143         allocator.rewindIfLastAlloc(bakedState);
    144         return nullptr;
    145     }
    146     return bakedState;
    147 }
    148 
    149 BakedOpState* BakedOpState::tryShadowOpConstruct(LinearAllocator& allocator,
    150         Snapshot& snapshot, const ShadowOp* shadowOpPtr) {
    151     if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
    152 
    153     // clip isn't empty, so construct the op
    154     return allocator.create_trivial<BakedOpState>(allocator, snapshot, shadowOpPtr);
    155 }
    156 
    157 BakedOpState* BakedOpState::directConstruct(LinearAllocator& allocator,
    158         const ClipRect* clip, const Rect& dstRect, const RecordedOp& recordedOp) {
    159     return allocator.create_trivial<BakedOpState>(clip, dstRect, recordedOp);
    160 }
    161 
    162 void BakedOpState::setupOpacity(const SkPaint* paint) {
    163     computedState.opaqueOverClippedBounds = computedState.transform.isSimple()
    164             && computedState.clipState->mode == ClipMode::Rectangle
    165             && MathUtils::areEqual(alpha, 1.0f)
    166             && !roundRectClipState
    167             && PaintUtils::isOpaquePaint(paint);
    168 }
    169 
    170 } // namespace uirenderer
    171 } // namespace android
    172