1 /* 2 * Copyright 2021 Google LLC 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8 #ifndef skgpu_graphite_geom_BoundsManager_DEFINED 9 #define skgpu_graphite_geom_BoundsManager_DEFINED 10 11 #include "include/core/SkScalar.h" 12 #include "include/core/SkSize.h" 13 #include "include/private/base/SkAssert.h" 14 #include "include/private/base/SkTemplates.h" 15 #include "src/base/SkBlockAllocator.h" 16 #include "src/base/SkTBlockList.h" 17 #include "src/base/SkVx.h" 18 #include "src/gpu/graphite/DrawOrder.h" 19 #include "src/gpu/graphite/geom/Rect.h" 20 21 #include <cstdint> 22 #include <cstring> 23 #include <memory> 24 25 namespace skgpu::graphite { 26 27 /** 28 * BoundsManager is an acceleration structure for device-space related pixel bounds queries. 29 * The BoundsManager tracks a single ordinal value per bounds: the CompressedPaintersOrder of a draw 30 * The CompressedPaintersOrder enforces a specific submission order of draws to the GPU but can 31 * re-arrange draws out of their original painter's order if the GREATER depth test and the draw's Z 32 * value resolve out-of-order rendering. 33 * 34 * It supports querying the most recent draw intersecting a bounding rect (represented as a 35 * CompressedPaintersOrder value), and recording a (bounds, CompressedPaintersOrder) pair. 36 */ 37 class BoundsManager { 38 public: ~BoundsManager()39 virtual ~BoundsManager() {} 40 41 virtual CompressedPaintersOrder getMostRecentDraw(const Rect& bounds) const = 0; 42 43 virtual void recordDraw(const Rect& bounds, CompressedPaintersOrder order) = 0; 44 45 virtual void reset() = 0; 46 }; 47 48 // TODO: Select one most-effective BoundsManager implementation, make it the only option, and remove 49 // virtual-ness. For now, this seems useful for correctness testing by comparing against trivial 50 // implementations and for identifying how much "smarts" are actually worthwhile. 51 52 // A BoundsManager that produces exact painter's order and assumes nothing is occluded. 53 class NaiveBoundsManager final : public BoundsManager { 54 public: ~NaiveBoundsManager()55 ~NaiveBoundsManager() override {} 56 getMostRecentDraw(const Rect & bounds)57 CompressedPaintersOrder getMostRecentDraw(const Rect& bounds) const override { 58 return fLatestDraw; 59 } 60 61 recordDraw(const Rect & bounds,CompressedPaintersOrder order)62 void recordDraw(const Rect& bounds, CompressedPaintersOrder order) override { 63 if (fLatestDraw < order) { 64 fLatestDraw = order; 65 } 66 } 67 reset()68 void reset() override { 69 fLatestDraw = CompressedPaintersOrder::First(); 70 } 71 72 private: 73 CompressedPaintersOrder fLatestDraw = CompressedPaintersOrder::First(); 74 }; 75 76 // A BoundsManager that tracks every draw and can exactly determine all queries 77 // using a brute force search. 78 class BruteForceBoundsManager : public BoundsManager { 79 public: ~BruteForceBoundsManager()80 ~BruteForceBoundsManager() override {} 81 getMostRecentDraw(const Rect & bounds)82 CompressedPaintersOrder getMostRecentDraw(const Rect& bounds) const override { 83 SkASSERT(fRects.count() == fOrders.count()); 84 85 Rect::ComplementRect boundsComplement(bounds); 86 CompressedPaintersOrder max = CompressedPaintersOrder::First(); 87 auto orderIter = fOrders.items().begin(); 88 for (const Rect& r : fRects.items()) { 89 if (r.intersects(boundsComplement) && max < *orderIter) { 90 max = *orderIter; 91 } 92 ++orderIter; 93 } 94 return max; 95 } 96 recordDraw(const Rect & bounds,CompressedPaintersOrder order)97 void recordDraw(const Rect& bounds, CompressedPaintersOrder order) override { 98 fRects.push_back(bounds); 99 fOrders.push_back(order); 100 } 101 reset()102 void reset() override { 103 fRects.reset(); 104 fOrders.reset(); 105 } 106 count()107 int count() const { return fRects.count(); } 108 replayDraws(BoundsManager * manager)109 void replayDraws(BoundsManager* manager) const { 110 auto orderIter = fOrders.items().begin(); 111 for (const Rect& r : fRects.items()) { 112 manager->recordDraw(r, *orderIter); 113 ++orderIter; 114 } 115 } 116 117 private: 118 // fRects and fOrders are parallel, but kept separate to avoid wasting padding since Rect is 119 // an over-aligned type. 120 SkTBlockList<Rect, 16> fRects{SkBlockAllocator::GrowthPolicy::kFibonacci}; 121 SkTBlockList<CompressedPaintersOrder, 16> fOrders{SkBlockAllocator::GrowthPolicy::kFibonacci}; 122 }; 123 124 // A BoundsManager that tracks highest CompressedPaintersOrder over a uniform spatial grid. 125 class GridBoundsManager : public BoundsManager { 126 public: 127 // 'gridSize' is the number of cells in the X and Y directions, splitting the pixels from [0,0] 128 // to 'deviceSize' into uniformly-sized cells. Make(const SkISize & deviceSize,const SkISize & gridSize)129 static std::unique_ptr<GridBoundsManager> Make(const SkISize& deviceSize, 130 const SkISize& gridSize) { 131 SkASSERT(deviceSize.width() > 0 && deviceSize.height() > 0); 132 SkASSERT(gridSize.width() >= 1 && gridSize.height() >= 1); 133 134 return std::unique_ptr<GridBoundsManager>(new GridBoundsManager(deviceSize, gridSize)); 135 } 136 Make(const SkISize & deviceSize,int gridSize)137 static std::unique_ptr<GridBoundsManager> Make(const SkISize& deviceSize, int gridSize) { 138 return Make(deviceSize, {gridSize, gridSize}); 139 } 140 141 static std::unique_ptr<GridBoundsManager> MakeRes(SkISize deviceSize, 142 int gridCellSize, 143 int maxGridSize=0) { 144 SkASSERT(deviceSize.width() > 0 && deviceSize.height() > 0); 145 SkASSERT(gridCellSize >= 1); 146 147 int gridWidth = SkScalarCeilToInt(deviceSize.width() / (float) gridCellSize); 148 if (maxGridSize > 0 && gridWidth > maxGridSize) { 149 // We'd have too many sizes so clamp the grid resolution, leave the device size alone 150 // since the grid cell size can't be preserved anyways. 151 gridWidth = maxGridSize; 152 } else { 153 // Pad out the device size to keep cell size the same 154 deviceSize.fWidth = gridWidth * gridCellSize; 155 } 156 157 int gridHeight = SkScalarCeilToInt(deviceSize.height() / (float) gridCellSize); 158 if (maxGridSize > 0 && gridHeight > maxGridSize) { 159 gridHeight = maxGridSize; 160 } else { 161 deviceSize.fHeight = gridHeight * gridCellSize; 162 } 163 return Make(deviceSize, {gridWidth, gridHeight}); 164 } 165 ~GridBoundsManager()166 ~GridBoundsManager() override {} 167 168 getMostRecentDraw(const Rect & bounds)169 CompressedPaintersOrder getMostRecentDraw(const Rect& bounds) const override { 170 SkASSERT(!bounds.isEmptyNegativeOrNaN()); 171 172 auto ltrb = this->getGridCoords(bounds); 173 const CompressedPaintersOrder* p = fNodes.data() + ltrb[1] * fGridWidth + ltrb[0]; 174 int h = ltrb[3] - ltrb[1]; 175 int w = ltrb[2] - ltrb[0]; 176 177 CompressedPaintersOrder max = CompressedPaintersOrder::First(); 178 for (int y = 0; y <= h; ++y ) { 179 for (int x = 0; x <= w; ++x) { 180 CompressedPaintersOrder v = *(p + x); 181 if (v > max) { 182 max = v; 183 } 184 } 185 p = p + fGridWidth; 186 } 187 188 return max; 189 } 190 recordDraw(const Rect & bounds,CompressedPaintersOrder order)191 void recordDraw(const Rect& bounds, CompressedPaintersOrder order) override { 192 SkASSERT(!bounds.isEmptyNegativeOrNaN()); 193 194 auto ltrb = this->getGridCoords(bounds); 195 CompressedPaintersOrder* p = fNodes.data() + ltrb[1] * fGridWidth + ltrb[0]; 196 int h = ltrb[3] - ltrb[1]; 197 int w = ltrb[2] - ltrb[0]; 198 199 for (int y = 0; y <= h; ++y) { 200 for (int x = 0; x <= w; ++x) { 201 CompressedPaintersOrder v = *(p + x); 202 if (order > v) { 203 *(p + x) = order; 204 } 205 } 206 p = p + fGridWidth; 207 } 208 } 209 reset()210 void reset() override { 211 memset(fNodes.data(), 0, sizeof(CompressedPaintersOrder) * fGridWidth * fGridHeight); 212 } 213 214 private: GridBoundsManager(const SkISize & deviceSize,const SkISize & gridSize)215 GridBoundsManager(const SkISize& deviceSize, const SkISize& gridSize) 216 : fScaleX(gridSize.width() / (float) deviceSize.width()) 217 , fScaleY(gridSize.height() / (float) deviceSize.height()) 218 , fGridWidth(gridSize.width()) 219 , fGridHeight(gridSize.height()) 220 , fNodes((size_t) fGridWidth * fGridHeight) { 221 // Reset is needed to zero-out the uninitialized fNodes values. 222 this->reset(); 223 } 224 getGridCoords(const Rect & bounds)225 skvx::int4 getGridCoords(const Rect& bounds) const { 226 // Normalize bounds by 1/wh of device bounds, then scale up to number of cells per side. 227 // fScaleXY includes both 1/wh and the grid dimension scaling, then clamp to [0, gridDim-1]. 228 return pin(skvx::cast<int32_t>(bounds.ltrb() * skvx::float2(fScaleX, fScaleY).xyxy()), 229 skvx::int4(0), 230 skvx::int2(fGridWidth, fGridHeight).xyxy() - 1); 231 } 232 233 const float fScaleX; 234 const float fScaleY; 235 236 const int fGridWidth; 237 const int fGridHeight; 238 239 skia_private::AutoTMalloc<CompressedPaintersOrder> fNodes; 240 }; 241 242 // A BoundsManager that first relies on BruteForceBoundsManager for N draw calls, and then switches 243 // to the GridBoundsManager if it exceeds its limit. For low N, the brute force approach is 244 // surprisingly efficient, has the highest accuracy, and very low memory overhead. Once the draw 245 // call count is large enough, the grid's lower performance complexity outweigh its memory cost and 246 // reduced accuracy. 247 class HybridBoundsManager : public BoundsManager { 248 public: 249 HybridBoundsManager(const SkISize& deviceSize, 250 int gridCellSize, 251 int maxBruteForceN, 252 int maxGridSize=0) fDeviceSize(deviceSize)253 : fDeviceSize(deviceSize) 254 , fGridCellSize(gridCellSize) 255 , fMaxBruteForceN(maxBruteForceN) 256 , fMaxGridSize(maxGridSize) 257 , fCurrentManager(&fBruteForceManager) { 258 SkASSERT(deviceSize.width() >= 1 && deviceSize.height() >= 1 && 259 gridCellSize >= 1 && maxBruteForceN >= 1); 260 } 261 getMostRecentDraw(const Rect & bounds)262 CompressedPaintersOrder getMostRecentDraw(const Rect& bounds) const override { 263 return fCurrentManager->getMostRecentDraw(bounds); 264 } 265 recordDraw(const Rect & bounds,CompressedPaintersOrder order)266 void recordDraw(const Rect& bounds, CompressedPaintersOrder order) override { 267 this->updateCurrentManagerIfNeeded(); 268 fCurrentManager->recordDraw(bounds, order); 269 } 270 reset()271 void reset() override { 272 const bool usedGrid = fCurrentManager == fGridManager.get(); 273 if (usedGrid) { 274 // Reset the grid manager so it's ready to use next frame, but don't delete it. 275 fGridManager->reset(); 276 // Assume brute force manager was reset when we swapped to the grid originally 277 fCurrentManager = &fBruteForceManager; 278 } else { 279 if (fGridManager) { 280 // Clean up the grid manager that was created over a frame ago without being used. 281 // This could lead to re-allocating the grid every-other frame, but it's a simple 282 // way to ensure we don't hold onto the grid in perpetuity if it's not needed. 283 fGridManager = nullptr; 284 } 285 fBruteForceManager.reset(); 286 SkASSERT(fCurrentManager == &fBruteForceManager); 287 } 288 } 289 290 private: 291 const SkISize fDeviceSize; 292 const int fGridCellSize; 293 const int fMaxBruteForceN; 294 const int fMaxGridSize; 295 296 BoundsManager* fCurrentManager; 297 298 BruteForceBoundsManager fBruteForceManager; 299 300 // The grid manager starts out null and is created the first time we exceed fMaxBruteForceN. 301 // However, even if we reset back to the brute force manager, we keep the grid around under the 302 // assumption that the owning Device will have similar frame-to-frame draw counts and will need 303 // to upgrade to the grid manager again. 304 std::unique_ptr<GridBoundsManager> fGridManager; 305 updateCurrentManagerIfNeeded()306 void updateCurrentManagerIfNeeded() { 307 if (fCurrentManager == fGridManager.get() || 308 fBruteForceManager.count() < fMaxBruteForceN) { 309 // Already using the grid or the about-to-be-recorded draw will not cause us to exceed 310 // the brute force limit, so no need to change the current manager implementation. 311 return; 312 } 313 // Else we need to switch from the brute force manager to the grid manager 314 if (!fGridManager) { 315 fGridManager = GridBoundsManager::MakeRes(fDeviceSize, fGridCellSize, fMaxGridSize); 316 } 317 fCurrentManager = fGridManager.get(); 318 319 // Fill out the grid manager with the recorded draws in the brute force manager 320 fBruteForceManager.replayDraws(fCurrentManager); 321 fBruteForceManager.reset(); 322 } 323 }; 324 325 } // namespace skgpu::graphite 326 327 #endif // skgpu_graphite_geom_BoundsManager_DEFINED 328