| /* |
| * Copyright (C) 2015 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 "BakedOpState.h" |
| |
| #include "ClipArea.h" |
| |
| namespace android { |
| namespace uirenderer { |
| |
| static int computeClipSideFlags(const Rect& clip, const Rect& bounds) { |
| int clipSideFlags = 0; |
| if (clip.left > bounds.left) clipSideFlags |= OpClipSideFlags::Left; |
| if (clip.top > bounds.top) clipSideFlags |= OpClipSideFlags::Top; |
| if (clip.right < bounds.right) clipSideFlags |= OpClipSideFlags::Right; |
| if (clip.bottom < bounds.bottom) clipSideFlags |= OpClipSideFlags::Bottom; |
| return clipSideFlags; |
| } |
| |
| ResolvedRenderState::ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot, |
| const RecordedOp& recordedOp, bool expandForStroke, |
| bool expandForPathTexture) { |
| // resolvedMatrix = parentMatrix * localMatrix |
| transform.loadMultiply(*snapshot.transform, recordedOp.localMatrix); |
| |
| // resolvedClippedBounds = intersect(resolvedMatrix * opBounds, resolvedClipRect) |
| clippedBounds = recordedOp.unmappedBounds; |
| if (CC_UNLIKELY(expandForStroke)) { |
| // account for non-hairline stroke |
| clippedBounds.outset(recordedOp.paint->getStrokeWidth() * 0.5f); |
| } else if (CC_UNLIKELY(expandForPathTexture)) { |
| clippedBounds.outset(1); |
| } |
| transform.mapRect(clippedBounds); |
| if (CC_UNLIKELY(expandForStroke && |
| (!transform.isPureTranslate() || recordedOp.paint->getStrokeWidth() < 1.0f))) { |
| // account for hairline stroke when stroke may be < 1 scaled pixel |
| // Non translate || strokeWidth < 1 is conservative, but will cover all cases |
| clippedBounds.outset(0.5f); |
| } |
| |
| // resolvedClipRect = intersect(parentMatrix * localClip, parentClip) |
| clipState = snapshot.serializeIntersectedClip(allocator, recordedOp.localClip, |
| *(snapshot.transform)); |
| LOG_ALWAYS_FATAL_IF(!clipState, "must clip!"); |
| |
| const Rect& clipRect = clipState->rect; |
| if (CC_UNLIKELY(clipRect.isEmpty() || !clippedBounds.intersects(clipRect))) { |
| // Rejected based on either empty clip, or bounds not intersecting with clip |
| |
| // Note: we could rewind the clipState object in situations where the clipRect is empty, |
| // but *only* if the caching logic within ClipArea was aware of the rewind. |
| clipState = nullptr; |
| clippedBounds.setEmpty(); |
| } else { |
| // Not rejected! compute true clippedBounds, clipSideFlags, and path mask |
| clipSideFlags = computeClipSideFlags(clipRect, clippedBounds); |
| clippedBounds.doIntersect(clipRect); |
| |
| if (CC_UNLIKELY(snapshot.projectionPathMask)) { |
| // map projection path mask from render target space into op space, |
| // so intersection with op geometry is possible |
| Matrix4 inverseTransform; |
| inverseTransform.loadInverse(transform); |
| SkMatrix skInverseTransform; |
| inverseTransform.copyTo(skInverseTransform); |
| |
| auto localMask = allocator.create<SkPath>(); |
| snapshot.projectionPathMask->transform(skInverseTransform, localMask); |
| localProjectionPathMask = localMask; |
| } |
| } |
| } |
| |
| ResolvedRenderState::ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot, |
| const Matrix4& localTransform, const ClipBase* localClip) { |
| transform.loadMultiply(*snapshot.transform, localTransform); |
| clipState = snapshot.serializeIntersectedClip(allocator, localClip, *(snapshot.transform)); |
| clippedBounds = clipState->rect; |
| clipSideFlags = OpClipSideFlags::Full; |
| localProjectionPathMask = nullptr; |
| } |
| |
| ResolvedRenderState::ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot) |
| : transform(*snapshot.transform) |
| , clipState(snapshot.mutateClipArea().serializeClip(allocator)) |
| , clippedBounds(clipState->rect) |
| , clipSideFlags(OpClipSideFlags::Full) |
| , localProjectionPathMask(nullptr) {} |
| |
| ResolvedRenderState::ResolvedRenderState(const ClipRect* clipRect, const Rect& dstRect) |
| : transform(Matrix4::identity()) |
| , clipState(clipRect) |
| , clippedBounds(dstRect) |
| , clipSideFlags(computeClipSideFlags(clipRect->rect, dstRect)) |
| , localProjectionPathMask(nullptr) { |
| clippedBounds.doIntersect(clipRect->rect); |
| } |
| |
| BakedOpState* BakedOpState::tryConstruct(LinearAllocator& allocator, Snapshot& snapshot, |
| const RecordedOp& recordedOp) { |
| if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr; |
| BakedOpState* bakedState = |
| allocator.create_trivial<BakedOpState>(allocator, snapshot, recordedOp, false, false); |
| if (bakedState->computedState.clippedBounds.isEmpty()) { |
| // bounds are empty, so op is rejected |
| allocator.rewindIfLastAlloc(bakedState); |
| return nullptr; |
| } |
| return bakedState; |
| } |
| |
| BakedOpState* BakedOpState::tryConstructUnbounded(LinearAllocator& allocator, Snapshot& snapshot, |
| const RecordedOp& recordedOp) { |
| if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr; |
| return allocator.create_trivial<BakedOpState>(allocator, snapshot, recordedOp); |
| } |
| |
| BakedOpState* BakedOpState::tryStrokeableOpConstruct(LinearAllocator& allocator, Snapshot& snapshot, |
| const RecordedOp& recordedOp, |
| StrokeBehavior strokeBehavior, |
| bool expandForPathTexture) { |
| if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr; |
| bool expandForStroke = |
| (strokeBehavior == StrokeBehavior::Forced || |
| (recordedOp.paint && recordedOp.paint->getStyle() != SkPaint::kFill_Style)); |
| |
| BakedOpState* bakedState = allocator.create_trivial<BakedOpState>( |
| allocator, snapshot, recordedOp, expandForStroke, expandForPathTexture); |
| if (bakedState->computedState.clippedBounds.isEmpty()) { |
| // bounds are empty, so op is rejected |
| // NOTE: this won't succeed if a clip was allocated |
| allocator.rewindIfLastAlloc(bakedState); |
| return nullptr; |
| } |
| return bakedState; |
| } |
| |
| BakedOpState* BakedOpState::tryShadowOpConstruct(LinearAllocator& allocator, Snapshot& snapshot, |
| const ShadowOp* shadowOpPtr) { |
| if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr; |
| |
| // clip isn't empty, so construct the op |
| return allocator.create_trivial<BakedOpState>(allocator, snapshot, shadowOpPtr); |
| } |
| |
| BakedOpState* BakedOpState::directConstruct(LinearAllocator& allocator, const ClipRect* clip, |
| const Rect& dstRect, const RecordedOp& recordedOp) { |
| return allocator.create_trivial<BakedOpState>(clip, dstRect, recordedOp); |
| } |
| |
| void BakedOpState::setupOpacity(const SkPaint* paint) { |
| computedState.opaqueOverClippedBounds = computedState.transform.isSimple() && |
| computedState.clipState->mode == ClipMode::Rectangle && |
| MathUtils::areEqual(alpha, 1.0f) && |
| !roundRectClipState && PaintUtils::isOpaquePaint(paint); |
| } |
| |
| } // namespace uirenderer |
| } // namespace android |