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