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> fRects{16, SkBlockAllocator::GrowthPolicy::kFibonacci}; 117 SkTBlockList<CompressedPaintersOrder> fOrders{16, 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 MakeRes(const SkISize & deviceSize,int gridCellSize)137 static std::unique_ptr<GridBoundsManager> MakeRes(const SkISize& deviceSize, int gridCellSize) { 138 SkASSERT(deviceSize.width() > 0 && deviceSize.height() > 0); 139 SkASSERT(gridCellSize >= 1); 140 141 int gridWidth = SkScalarCeilToInt(deviceSize.width() / (float) gridCellSize); 142 int gridHeight = SkScalarCeilToInt(deviceSize.height() / (float) gridCellSize); 143 144 // This keeps the grid cells exactly at the requested resolution, but pads the right and 145 // bottom edges out to a multiple of the cell size. The alternative is pass in the unpadded 146 // device size, which would mean the actual cell size will be smaller than the requested 147 // (by (deviceSize % gridCellSize)/gridDims). 148 SkISize paddedDeviceSize = {gridWidth * gridCellSize, gridHeight * gridCellSize}; 149 return Make(paddedDeviceSize, {gridWidth, gridHeight}); 150 } 151 ~GridBoundsManager()152 ~GridBoundsManager() override {} 153 154 getMostRecentDraw(const Rect & bounds)155 CompressedPaintersOrder getMostRecentDraw(const Rect& bounds) const override { 156 SkASSERT(!bounds.isEmptyNegativeOrNaN()); 157 158 auto ltrb = this->getGridCoords(bounds); 159 const CompressedPaintersOrder* p = fNodes.data() + ltrb[1] * fGridWidth + ltrb[0]; 160 int h = ltrb[3] - ltrb[1]; 161 int w = ltrb[2] - ltrb[0]; 162 163 CompressedPaintersOrder max = CompressedPaintersOrder::First(); 164 for (int y = 0; y <= h; ++y ) { 165 for (int x = 0; x <= w; ++x) { 166 CompressedPaintersOrder v = *(p + x); 167 if (v > max) { 168 max = v; 169 } 170 } 171 p = p + fGridWidth; 172 } 173 174 return max; 175 } 176 recordDraw(const Rect & bounds,CompressedPaintersOrder order)177 void recordDraw(const Rect& bounds, CompressedPaintersOrder order) override { 178 SkASSERT(!bounds.isEmptyNegativeOrNaN()); 179 180 auto ltrb = this->getGridCoords(bounds); 181 CompressedPaintersOrder* p = fNodes.data() + ltrb[1] * fGridWidth + ltrb[0]; 182 int h = ltrb[3] - ltrb[1]; 183 int w = ltrb[2] - ltrb[0]; 184 185 for (int y = 0; y <= h; ++y) { 186 for (int x = 0; x <= w; ++x) { 187 CompressedPaintersOrder v = *(p + x); 188 if (order > v) { 189 *(p + x) = order; 190 } 191 } 192 p = p + fGridWidth; 193 } 194 } 195 reset()196 void reset() override { 197 memset(fNodes.data(), 0, sizeof(CompressedPaintersOrder) * fGridWidth * fGridHeight); 198 } 199 200 private: GridBoundsManager(const SkISize & deviceSize,const SkISize & gridSize)201 GridBoundsManager(const SkISize& deviceSize, const SkISize& gridSize) 202 : fScaleX(gridSize.width() / (float) deviceSize.width()) 203 , fScaleY(gridSize.height() / (float) deviceSize.height()) 204 , fGridWidth(gridSize.width()) 205 , fGridHeight(gridSize.height()) 206 , fNodes((size_t) fGridWidth * fGridHeight) { 207 // Reset is needed to zero-out the uninitialized fNodes values. 208 this->reset(); 209 } 210 getGridCoords(const Rect & bounds)211 skvx::int4 getGridCoords(const Rect& bounds) const { 212 // Normalize bounds by 1/wh of device bounds, then scale up to number of cells per side. 213 // fScaleXY includes both 1/wh and the grid dimension scaling, then clamp to [0, gridDim-1]. 214 return pin(skvx::cast<int32_t>(bounds.ltrb() * skvx::float2(fScaleX, fScaleY).xyxy()), 215 skvx::int4(0), 216 skvx::int2(fGridWidth, fGridHeight).xyxy() - 1); 217 } 218 219 const float fScaleX; 220 const float fScaleY; 221 222 const int fGridWidth; 223 const int fGridHeight; 224 225 skia_private::AutoTMalloc<CompressedPaintersOrder> fNodes; 226 }; 227 228 // A BoundsManager that first relies on BruteForceBoundsManager for N draw calls, and then switches 229 // to the GridBoundsManager if it exceeds its limit. For low N, the brute force approach is 230 // surprisingly efficient, has the highest accuracy, and very low memory overhead. Once the draw 231 // call count is large enough, the grid's lower performance complexity outweigh its memory cost and 232 // reduced accuracy. 233 class HybridBoundsManager : public BoundsManager { 234 public: HybridBoundsManager(const SkISize & deviceSize,int gridCellSize,int maxBruteForceN)235 HybridBoundsManager(const SkISize& deviceSize, 236 int gridCellSize, 237 int maxBruteForceN) 238 : fDeviceSize(deviceSize) 239 , fGridCellSize(gridCellSize) 240 , fMaxBruteForceN(maxBruteForceN) 241 , fCurrentManager(&fBruteForceManager) { 242 SkASSERT(deviceSize.width() >= 1 && deviceSize.height() >= 1 && 243 gridCellSize >= 1 && maxBruteForceN >= 1); 244 } 245 getMostRecentDraw(const Rect & bounds)246 CompressedPaintersOrder getMostRecentDraw(const Rect& bounds) const override { 247 return fCurrentManager->getMostRecentDraw(bounds); 248 } 249 recordDraw(const Rect & bounds,CompressedPaintersOrder order)250 void recordDraw(const Rect& bounds, CompressedPaintersOrder order) override { 251 this->updateCurrentManagerIfNeeded(); 252 fCurrentManager->recordDraw(bounds, order); 253 } 254 reset()255 void reset() override { 256 const bool usedGrid = fCurrentManager == fGridManager.get(); 257 if (usedGrid) { 258 // Reset the grid manager so it's ready to use next frame, but don't delete it. 259 fGridManager->reset(); 260 // Assume brute force manager was reset when we swapped to the grid originally 261 fCurrentManager = &fBruteForceManager; 262 } else { 263 if (fGridManager) { 264 // Clean up the grid manager that was created over a frame ago without being used. 265 // This could lead to re-allocating the grid every-other frame, but it's a simple 266 // way to ensure we don't hold onto the grid in perpetuity if it's not needed. 267 fGridManager = nullptr; 268 } 269 fBruteForceManager.reset(); 270 SkASSERT(fCurrentManager == &fBruteForceManager); 271 } 272 } 273 274 private: 275 const SkISize fDeviceSize; 276 const int fGridCellSize; 277 const int fMaxBruteForceN; 278 279 BoundsManager* fCurrentManager; 280 281 BruteForceBoundsManager fBruteForceManager; 282 283 // The grid manager starts out null and is created the first time we exceed fMaxBruteForceN. 284 // However, even if we reset back to the brute force manager, we keep the grid around under the 285 // assumption that the owning Device will have similar frame-to-frame draw counts and will need 286 // to upgrade to the grid manager again. 287 std::unique_ptr<GridBoundsManager> fGridManager; 288 updateCurrentManagerIfNeeded()289 void updateCurrentManagerIfNeeded() { 290 if (fCurrentManager == fGridManager.get() || 291 fBruteForceManager.count() < fMaxBruteForceN) { 292 // Already using the grid or the about-to-be-recorded draw will not cause us to exceed 293 // the brute force limit, so no need to change the current manager implementation. 294 return; 295 } 296 // Else we need to switch from the brute force manager to the grid manager 297 if (!fGridManager) { 298 fGridManager = GridBoundsManager::MakeRes(fDeviceSize, fGridCellSize); 299 } 300 fCurrentManager = fGridManager.get(); 301 302 // Fill out the grid manager with the recorded draws in the brute force manager 303 fBruteForceManager.replayDraws(fCurrentManager); 304 fBruteForceManager.reset(); 305 } 306 }; 307 308 } // namespace skgpu::graphite 309 310 #endif // skgpu_graphite_geom_BoundsManager_DEFINED 311