John Reck | dc95f10 | 2020-11-16 12:35:02 -0500 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2020 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 | #pragma once |
| 18 | |
| 19 | // TODO: Can we get the dependencies scoped down more? |
| 20 | #include "CanvasOps.h" |
| 21 | #include "CanvasOpBuffer.h" |
| 22 | #include <SaveFlags.h> |
| 23 | |
John Reck | dc95f10 | 2020-11-16 12:35:02 -0500 | [diff] [blame] | 24 | #include <ui/FatVector.h> |
| 25 | |
| 26 | #include <optional> |
| 27 | |
| 28 | namespace android::uirenderer { |
| 29 | |
| 30 | // Exists to avoid forcing all this common logic into the templated class |
| 31 | class CanvasStateHelper { |
| 32 | protected: |
| 33 | CanvasStateHelper(int width, int height); |
| 34 | ~CanvasStateHelper() = default; |
| 35 | |
| 36 | struct SaveEntry { |
| 37 | bool clip : 1 = false; |
| 38 | bool matrix : 1 = false; |
| 39 | bool layer : 1 = false; |
| 40 | }; |
| 41 | |
Michael Ludwig | 8c5c96f | 2021-08-18 01:36:21 +0000 | [diff] [blame^] | 42 | template <typename T> |
| 43 | struct DeferredEntry { |
| 44 | T entry; |
| 45 | int deferredSaveCount = 0; |
| 46 | |
| 47 | DeferredEntry() = default; |
| 48 | DeferredEntry(const T& t) : entry(t) {} |
| 49 | }; |
| 50 | |
| 51 | struct ConservativeClip { |
| 52 | SkIRect bounds = SkIRect::MakeEmpty(); |
| 53 | bool rect = true; |
| 54 | bool aa = false; |
| 55 | |
| 56 | bool quickReject(const SkMatrix& matrix, const SkRect& bounds) const; |
| 57 | |
| 58 | void apply(SkClipOp op, const SkMatrix& matrix, const SkRect& bounds, bool aa, |
| 59 | bool fillsBounds); |
| 60 | }; |
| 61 | |
John Reck | dc95f10 | 2020-11-16 12:35:02 -0500 | [diff] [blame] | 62 | constexpr SaveEntry saveEntryForLayer() { |
| 63 | return { |
| 64 | .clip = true, |
| 65 | .matrix = true, |
| 66 | .layer = true, |
| 67 | }; |
| 68 | } |
| 69 | |
| 70 | constexpr SaveEntry flagsToSaveEntry(SaveFlags::Flags flags) { |
| 71 | return SaveEntry { |
| 72 | .clip = static_cast<bool>(flags & SaveFlags::Clip), |
| 73 | .matrix = static_cast<bool>(flags & SaveFlags::Matrix), |
| 74 | .layer = false |
| 75 | }; |
| 76 | } |
| 77 | |
| 78 | bool internalSave(SaveEntry saveEntry); |
John Reck | b5eeb18 | 2020-12-09 13:45:39 -0500 | [diff] [blame] | 79 | |
John Reck | dc95f10 | 2020-11-16 12:35:02 -0500 | [diff] [blame] | 80 | void internalSaveLayer(const SkCanvas::SaveLayerRec& layerRec) { |
| 81 | internalSave({ |
| 82 | .clip = true, |
| 83 | .matrix = true, |
| 84 | .layer = true |
| 85 | }); |
| 86 | internalClipRect(*layerRec.fBounds, SkClipOp::kIntersect); |
| 87 | } |
| 88 | |
| 89 | bool internalRestore(); |
| 90 | |
| 91 | void internalClipRect(const SkRect& rect, SkClipOp op); |
| 92 | void internalClipPath(const SkPath& path, SkClipOp op); |
| 93 | |
Michael Ludwig | 8c5c96f | 2021-08-18 01:36:21 +0000 | [diff] [blame^] | 94 | // The canvas' clip will never expand beyond these bounds since intersect |
| 95 | // and difference operations only subtract pixels. |
John Reck | dc95f10 | 2020-11-16 12:35:02 -0500 | [diff] [blame] | 96 | SkIRect mInitialBounds; |
Michael Ludwig | 8c5c96f | 2021-08-18 01:36:21 +0000 | [diff] [blame^] | 97 | // Every save() gets a SaveEntry to track what needs to be restored. |
John Reck | dc95f10 | 2020-11-16 12:35:02 -0500 | [diff] [blame] | 98 | FatVector<SaveEntry, 6> mSaveStack; |
Michael Ludwig | 8c5c96f | 2021-08-18 01:36:21 +0000 | [diff] [blame^] | 99 | // Transform and clip entries record a deferred save count and do not |
| 100 | // make a new entry until that particular state is modified. |
| 101 | FatVector<DeferredEntry<SkMatrix>, 6> mTransformStack; |
| 102 | FatVector<DeferredEntry<ConservativeClip>, 6> mClipStack; |
John Reck | dc95f10 | 2020-11-16 12:35:02 -0500 | [diff] [blame] | 103 | |
Michael Ludwig | 8c5c96f | 2021-08-18 01:36:21 +0000 | [diff] [blame^] | 104 | const ConservativeClip& clip() const { return mClipStack.back().entry; } |
John Reck | dc95f10 | 2020-11-16 12:35:02 -0500 | [diff] [blame] | 105 | |
Michael Ludwig | 8c5c96f | 2021-08-18 01:36:21 +0000 | [diff] [blame^] | 106 | ConservativeClip& clip(); |
John Reck | dc95f10 | 2020-11-16 12:35:02 -0500 | [diff] [blame] | 107 | |
John Reck | b5eeb18 | 2020-12-09 13:45:39 -0500 | [diff] [blame] | 108 | void resetState(int width, int height); |
| 109 | |
Michael Ludwig | 8c5c96f | 2021-08-18 01:36:21 +0000 | [diff] [blame^] | 110 | // Stack manipulation for transform and clip stacks |
| 111 | template <typename T, size_t N> |
| 112 | void pushEntry(FatVector<DeferredEntry<T>, N>* stack) { |
| 113 | stack->back().deferredSaveCount += 1; |
| 114 | } |
| 115 | |
| 116 | template <typename T, size_t N> |
| 117 | void popEntry(FatVector<DeferredEntry<T>, N>* stack) { |
| 118 | if (!(stack->back().deferredSaveCount--)) { |
| 119 | stack->pop_back(); |
| 120 | } |
| 121 | } |
| 122 | |
| 123 | template <typename T, size_t N> |
| 124 | T& writableEntry(FatVector<DeferredEntry<T>, N>* stack) { |
| 125 | DeferredEntry<T>& back = stack->back(); |
| 126 | if (back.deferredSaveCount == 0) { |
| 127 | return back.entry; |
| 128 | } else { |
| 129 | back.deferredSaveCount -= 1; |
| 130 | // saved in case references move when re-allocating vector storage |
| 131 | T state = back.entry; |
| 132 | return stack->emplace_back(state).entry; |
| 133 | } |
| 134 | } |
| 135 | |
John Reck | dc95f10 | 2020-11-16 12:35:02 -0500 | [diff] [blame] | 136 | public: |
| 137 | int saveCount() const { return mSaveStack.size(); } |
| 138 | |
| 139 | SkRect getClipBounds() const; |
| 140 | bool quickRejectRect(float left, float top, float right, float bottom) const; |
| 141 | bool quickRejectPath(const SkPath& path) const; |
| 142 | |
Michael Ludwig | 8c5c96f | 2021-08-18 01:36:21 +0000 | [diff] [blame^] | 143 | bool isClipAA() const { return clip().aa; } |
| 144 | bool isClipEmpty() const { return clip().bounds.isEmpty(); } |
| 145 | bool isClipRect() const { return clip().rect; } |
| 146 | bool isClipComplex() const { return !isClipEmpty() && (isClipAA() || !isClipRect()); } |
John Reck | dc95f10 | 2020-11-16 12:35:02 -0500 | [diff] [blame] | 147 | |
Michael Ludwig | 8c5c96f | 2021-08-18 01:36:21 +0000 | [diff] [blame^] | 148 | const SkMatrix& transform() const { return mTransformStack.back().entry; } |
| 149 | |
| 150 | SkMatrix& transform(); |
John Reck | dc95f10 | 2020-11-16 12:35:02 -0500 | [diff] [blame] | 151 | |
| 152 | // For compat with existing HWUI Canvas interface |
| 153 | void getMatrix(SkMatrix* outMatrix) const { |
| 154 | *outMatrix = transform(); |
| 155 | } |
| 156 | |
| 157 | void setMatrix(const SkMatrix& matrix) { |
| 158 | transform() = matrix; |
| 159 | } |
| 160 | |
| 161 | void concat(const SkMatrix& matrix) { |
| 162 | transform().preConcat(matrix); |
| 163 | } |
| 164 | |
| 165 | void rotate(float degrees) { |
| 166 | SkMatrix m; |
| 167 | m.setRotate(degrees); |
| 168 | concat(m); |
| 169 | } |
| 170 | |
| 171 | void scale(float sx, float sy) { |
| 172 | SkMatrix m; |
| 173 | m.setScale(sx, sy); |
| 174 | concat(m); |
| 175 | } |
| 176 | |
| 177 | void skew(float sx, float sy) { |
| 178 | SkMatrix m; |
| 179 | m.setSkew(sx, sy); |
| 180 | concat(m); |
| 181 | } |
| 182 | |
| 183 | void translate(float dx, float dy) { |
| 184 | transform().preTranslate(dx, dy); |
| 185 | } |
| 186 | }; |
| 187 | |
| 188 | // Front-end canvas that handles queries, up-front state, and produces CanvasOp<> output downstream |
| 189 | template <typename CanvasOpReceiver> |
| 190 | class CanvasFrontend final : public CanvasStateHelper { |
| 191 | public: |
| 192 | template<class... Args> |
| 193 | CanvasFrontend(int width, int height, Args&&... args) : CanvasStateHelper(width, height), |
John Reck | d34d6ce | 2021-01-19 21:29:24 -0500 | [diff] [blame] | 194 | mReceiver(std::in_place, std::forward<Args>(args)...) { } |
John Reck | dc95f10 | 2020-11-16 12:35:02 -0500 | [diff] [blame] | 195 | |
| 196 | void save(SaveFlags::Flags flags = SaveFlags::MatrixClip) { |
| 197 | if (internalSave(flagsToSaveEntry(flags))) { |
| 198 | submit<CanvasOpType::Save>({}); |
| 199 | } |
| 200 | } |
| 201 | |
| 202 | void restore() { |
| 203 | if (internalRestore()) { |
| 204 | submit<CanvasOpType::Restore>({}); |
| 205 | } |
| 206 | } |
| 207 | |
| 208 | template <CanvasOpType T> |
| 209 | void draw(CanvasOp<T>&& op) { |
| 210 | // The front-end requires going through certain front-doors, which these aren't. |
| 211 | static_assert(T != CanvasOpType::Save, "Must use CanvasFrontend::save() call instead"); |
| 212 | static_assert(T != CanvasOpType::Restore, "Must use CanvasFrontend::restore() call instead"); |
| 213 | |
| 214 | if constexpr (T == CanvasOpType::SaveLayer) { |
| 215 | internalSaveLayer(op.saveLayerRec); |
| 216 | } |
| 217 | if constexpr (T == CanvasOpType::SaveBehind) { |
| 218 | // Don't use internalSaveLayer as this doesn't apply clipping, it's a "regular" save |
| 219 | // But we do want to flag it as a layer, such that restore is Definitely Required |
| 220 | internalSave(saveEntryForLayer()); |
| 221 | } |
| 222 | if constexpr (T == CanvasOpType::ClipRect) { |
| 223 | internalClipRect(op.rect, op.op); |
| 224 | } |
| 225 | if constexpr (T == CanvasOpType::ClipPath) { |
| 226 | internalClipPath(op.path, op.op); |
| 227 | } |
| 228 | |
| 229 | submit(std::move(op)); |
| 230 | } |
| 231 | |
John Reck | d34d6ce | 2021-01-19 21:29:24 -0500 | [diff] [blame] | 232 | const CanvasOpReceiver& receiver() const { |
| 233 | LOG_ALWAYS_FATAL_IF(!mReceiver.has_value()); |
| 234 | return *mReceiver; |
| 235 | } |
John Reck | b5eeb18 | 2020-12-09 13:45:39 -0500 | [diff] [blame] | 236 | |
| 237 | CanvasOpReceiver finish() { |
| 238 | auto ret = std::move(mReceiver.value()); |
| 239 | mReceiver.reset(); |
| 240 | return std::move(ret); |
| 241 | } |
| 242 | |
| 243 | template<class... Args> |
| 244 | void reset(int newWidth, int newHeight, Args&&... args) { |
| 245 | resetState(newWidth, newHeight); |
| 246 | mReceiver.emplace(std::forward<Args>(args)...); |
| 247 | } |
John Reck | dc95f10 | 2020-11-16 12:35:02 -0500 | [diff] [blame] | 248 | |
| 249 | private: |
John Reck | b5eeb18 | 2020-12-09 13:45:39 -0500 | [diff] [blame] | 250 | std::optional<CanvasOpReceiver> mReceiver; |
John Reck | dc95f10 | 2020-11-16 12:35:02 -0500 | [diff] [blame] | 251 | |
| 252 | template <CanvasOpType T> |
| 253 | void submit(CanvasOp<T>&& op) { |
John Reck | d34d6ce | 2021-01-19 21:29:24 -0500 | [diff] [blame] | 254 | LOG_ALWAYS_FATAL_IF(!mReceiver.has_value()); |
John Reck | b5eeb18 | 2020-12-09 13:45:39 -0500 | [diff] [blame] | 255 | mReceiver->push_container(CanvasOpContainer(std::move(op), transform())); |
John Reck | dc95f10 | 2020-11-16 12:35:02 -0500 | [diff] [blame] | 256 | } |
| 257 | }; |
| 258 | |
| 259 | } // namespace android::uirenderer |