Grok 20.3.2
TileCompletion.h
Go to the documentation of this file.
1/*
2 * Copyright (C) 2016-2026 Grok Image Compression Inc.
3 *
4 * This source code is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU Affero General Public License, version 3,
6 * as published by the Free Software Foundation.
7 *
8 * This source code is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Affero General Public License for more details.
12 *
13 * You should have received a copy of the GNU Affero General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 */
17
18#pragma once
19
20#include <atomic>
21#include <mutex>
22#include <condition_variable>
23#include <stdexcept>
24#include <cstdint>
25#include <optional>
26#include <vector>
27#include <functional>
28#include "MinHeap.h"
29#include "MemManager.h"
30
31namespace grk
32{
33
41{
42public:
43 // Callback type for when a full row of tiles is completed
44 using RowCompletionCallback = std::function<void(uint16_t tileIndexBegin, uint16_t tileIndexEnd)>;
45 // Callback fired after wait() releases tile rows, to trigger deferred scheduling
46 using RowsReleasedCallback = std::function<void()>;
47
48 // Constructor for full region or subregion
49 TileCompletion(TileCache* tileCache, const Rect32& imageBounds, uint32_t tileWidth,
50 uint32_t tileHeight, RowCompletionCallback callback,
51 RowsReleasedCallback rowsReleasedCallback = nullptr,
52 std::optional<Rect16> tileSubRegion = std::nullopt)
53 : tileCache_(tileCache), currentTileY_(0), lastClearedTileY_(-1), rowCallback_(callback),
54 rowsReleasedCallback_(rowsReleasedCallback)
55 {
56 // Calculate image dimensions from bounds
57 uint32_t imageWidth = imageBounds.x1 - imageBounds.x0;
58 uint32_t imageHeight = imageBounds.y1 - imageBounds.y0;
59
60 if(imageWidth == 0 || imageHeight == 0 || tileWidth == 0 || tileHeight == 0)
61 throw std::invalid_argument("Dimensions must be positive");
62
63 numTileCols_ = static_cast<uint16_t>(((uint64_t)imageWidth + tileWidth - 1) / tileWidth);
64 numTileRows_ = static_cast<uint16_t>(((uint64_t)imageHeight + tileHeight - 1) / tileHeight);
65
66 // Default to full region if subregion is not provided
67 if(tileSubRegion.has_value())
68 {
69 tileX0_ = tileSubRegion->x0;
70 tileX1_ = tileSubRegion->x1; // Exclusive upper bound
71 tileY0_ = tileSubRegion->y0;
72 tileY1_ = tileSubRegion->y1; // Exclusive upper bound
73 }
74 else
75 {
76 tileX0_ = 0;
77 tileX1_ = numTileCols_; // Exclusive upper bound
78 tileY0_ = 0;
79 tileY1_ = numTileRows_; // Exclusive upper bound
80 }
81
82 // Validate subregion
84 throw std::invalid_argument("Invalid tile range");
85
86 tileWidth_ = tileWidth;
87 tileHeight_ = tileHeight;
88 imageBounds_ = imageBounds;
89
90 // Calculate dimensions of subregion (number of tiles in half-open range)
91 subregionWidth_ = static_cast<uint16_t>(tileX1_ - tileX0_);
92 subregionHeight_ = static_cast<uint16_t>(tileY1_ - tileY0_);
93
94 // Initialize completion tracking
97
98 grklog.debug("Image bounds: x0=%u, y0=%u, x1=%u, y1=%u, tileWidth=%u, tileHeight=%u",
101 }
102
103 void complete(uint16_t tileIndex)
104 {
105 uint16_t tileX = tileIndex % numTileCols_;
106 uint16_t tileY = tileIndex / numTileCols_;
107
108 // Ignore tiles outside the subregion (half-open: x0 <= x < x1, y0 <= y < y1)
109 if(tileX < tileX0_ || tileX >= tileX1_ || tileY < tileY0_ || tileY >= tileY1_)
110 return;
111
112 // Convert global tile index to local index in subregion (row-major order)
113 uint16_t localX = static_cast<uint16_t>(tileX - tileX0_);
114 uint16_t localY = static_cast<uint16_t>(tileY - tileY0_);
115 uint16_t localIndex = static_cast<uint16_t>(localY * subregionWidth_ + localX);
116
117 bool shouldCallback = false;
118 uint16_t cbBegin = 0, cbEnd = 0;
119
120 {
121 std::lock_guard<std::mutex> lock(mutex_);
122 if(!completedTiles_[localIndex])
123 {
124 completedTiles_[localIndex] = true;
125 completedTilesPerRow_[localY]++; // Increment completed tile count for this row
126 grklog.debug("Tile %d (local %d, tileX=%u, tileY=%u) completed", tileIndex, localIndex,
127 tileX, tileY);
128
129 // Check if the entire row is completed
131 {
132 shouldCallback = true;
133 cbBegin = (uint16_t)(tileY * numTileCols_ + tileX0_);
134 cbEnd = (uint16_t)(tileY * numTileCols_ + tileX1_);
135 grklog.debug("Row %u completed, indices %u up to %u", tileY, cbBegin, cbEnd);
136 }
137
138 auto popped = heap_.push_and_pop(localIndex);
139 if(popped.has_value())
140 {
141 localContiguousEnd_ = static_cast<int32_t>(*popped);
143 {
144 completionCV_.notify_one();
145 grklog.debug("Completed tiles until index %d", localContiguousEnd_);
146 }
147 }
148 }
149 else
150 {
151 grklog.debug("Tile %d (local %d, tileX=%u, tileY=%u) already completed", tileIndex,
152 localIndex, tileX, tileY);
153 }
154 }
155
156 // Call row callback OUTSIDE the lock to allow it to block
157 // on back-pressure without deadlocking with wait()
158 if(shouldCallback)
159 rowCallback_(cbBegin, cbEnd);
160 }
161
162 bool wait(grk_wait_swath* swath)
163 {
164 uint32_t x0 = swath->x0;
165 uint32_t y0 = swath->y0;
166 uint32_t x1 = swath->x1;
167 uint32_t y1 = swath->y1;
168
169 bool shouldNotifyRelease = false;
170
171 grklog.debug("Swath canvas: x0=%u, y0=%u, x1=%u, y1=%u", x0, y0, x1, y1);
172
173 // Validate swath bounds against image bounds
174 if(x0 >= x1 || y0 >= y1 || x1 > imageBounds_.x1 || y1 > imageBounds_.y1)
175 {
176 swath->tile_x0 = 0;
177 swath->tile_y0 = 0;
178 swath->tile_x1 = 0;
179 swath->tile_y1 = 0;
180 swath->num_tile_cols = 0;
181 grklog.debug("Invalid swath bounds: x0=%u, y0=%u, x1=%u, y1=%u", x0, y0, x1, y1);
182 return false;
183 }
184
185 // Convert pixel coordinates to tile coordinates, accounting for image offset
186 uint32_t x0Rel = x0 - imageBounds_.x0;
187 uint32_t y0Rel = y0 - imageBounds_.y0;
188 uint32_t x1Rel = x1 - imageBounds_.x0;
189 uint32_t y1Rel = y1 - imageBounds_.y0;
190
191 uint32_t x0Div = x0Rel / tileWidth_;
192 uint32_t y0Div = y0Rel / tileHeight_;
193 uint32_t x1Div = (x1Rel - 1) / tileWidth_;
194 uint32_t y1Div = (y1Rel - 1) / tileHeight_;
195
196 // Check for overflow before casting to uint16_t
197 if(x0Div > UINT16_MAX || y0Div > UINT16_MAX || x1Div > UINT16_MAX || y1Div > UINT16_MAX)
198 {
199 swath->tile_x0 = 0;
200 swath->tile_y0 = 0;
201 swath->tile_x1 = 0;
202 swath->tile_y1 = 0;
203 swath->num_tile_cols = 0;
204 grklog.debug("Tile coordinate overflow: x0Div=%u, y0Div=%u, x1Div=%u, y1Div=%u", x0Div, y0Div,
205 x1Div, y1Div);
206 return false;
207 }
208
209 // Compute tile coordinates, constrained to subregion
210 uint16_t tileX0 = std::max(tileX0_, static_cast<uint16_t>(x0Div));
211 uint16_t tileY0 = std::max(tileY0_, static_cast<uint16_t>(y0Div));
212 uint16_t tileX1 = std::min(tileX1_, static_cast<uint16_t>(x1Div + 1)); // Exclusive upper bound
213 uint16_t tileY1 = std::min(tileY1_, static_cast<uint16_t>(y1Div + 1)); // Exclusive upper bound
214
215 grklog.debug("Computed tile coords: x0Div=%u, y0Div=%u, x1Div=%u, y1Div=%u", x0Div, y0Div,
216 x1Div, y1Div);
217 grklog.debug("Constrained tile coords: tileX0=%u, tileY0=%u, tileX1=%u, tileY1=%u", tileX0,
218 tileY0, tileX1, tileY1);
219
220 // Populate swath with tile coordinates and grid info
221 swath->tile_x0 = tileX0;
222 swath->tile_y0 = tileY0;
223 swath->tile_x1 = tileX1;
224 swath->tile_y1 = tileY1;
226
227 // Tell the scheduler how far ahead we need tiles decompressed
228 neededTileY1_.store(static_cast<int32_t>(tileY1), std::memory_order_release);
229
230 // Check if tile row has advanced for clearing previous rows
231 {
232 std::lock_guard<std::mutex> lock(mutex_);
233 if(tileY0 > currentTileY_ && static_cast<int32_t>(tileY0) > lastClearedTileY_)
234 {
235 // Clear all completed tiles in previous tile rows
236 for(uint16_t clearTileY = static_cast<uint16_t>(
237 std::max(static_cast<int32_t>(tileY0_), lastClearedTileY_ + 1));
238 clearTileY < tileY0; ++clearTileY)
239 {
240 for(uint16_t tileX = tileX0_; tileX < tileX1_; ++tileX)
241 {
242 uint32_t tileIndexCalc = static_cast<uint32_t>(clearTileY) * numTileCols_ + tileX;
243 if(tileIndexCalc > UINT16_MAX)
244 continue; // Skip if tile index overflows
245 uint16_t tileIndex = static_cast<uint16_t>(tileIndexCalc);
246 uint16_t localX = tileX - tileX0_;
247 uint16_t localY = clearTileY - tileY0_;
248 if(localX < subregionWidth_ && localY < subregionHeight_)
249 {
250 uint16_t localIndex = static_cast<uint16_t>(localY * subregionWidth_ + localX);
251 if(completedTiles_[localIndex])
252 {
253 grklog.debug(
254 "Clearing ITileProcessor at tile index %d (local %d, tileX=%u, tileY=%u)",
255 tileIndex, localIndex, tileX, clearTileY);
256 tileCache_->releaseForSwath(tileIndex);
257 }
258 }
259 }
260 }
261 lastClearedTileY_ = static_cast<int32_t>(tileY0) - 1;
262 grklog.debug("Cleared tile rows up to tileY=%d", lastClearedTileY_);
263 shouldNotifyRelease = true;
264 }
265 currentTileY_ = tileY0;
266 grklog.debug("Tile row transition: currentTileY=%u, lastClearedTileY=%d", currentTileY_,
268 }
269
270 // Return freed pages to the OS so RSS drops after tile release
271 if(shouldNotifyRelease)
272 {
276 }
277
278 // Compute all local indices in the swath
279 std::vector<uint16_t> swathIndices;
280 for(uint16_t tileY = tileY0; tileY < tileY1; ++tileY)
281 {
282 for(uint16_t tileX = tileX0; tileX < tileX1; ++tileX)
283 {
284 uint16_t localX = tileX - tileX0_;
285 uint16_t localY = tileY - tileY0_;
286 uint16_t localIndex = static_cast<uint16_t>(localY * subregionWidth_ + localX);
287 swathIndices.push_back(localIndex);
288 }
289 }
290 uint16_t localEnd = swathIndices.empty() ? 0 : swathIndices.back();
291
292 // Wait until all tiles in the swath are completed
293 {
294 std::unique_lock<std::mutex> lock(mutex_);
295 auto allCompleted = [&]() {
296 for(uint16_t idx : swathIndices)
297 {
298 if(!completedTiles_[idx])
299 return false;
300 }
301 return true;
302 };
303 if(!allCompleted())
304 {
305 grklog.debug(
306 "Waiting for swath ending at tile %d (local %d), tiles: x0=%d, y0=%d, x1=%d, y1=%d",
307 (tileY1 - 1) * numTileCols_ + (tileX1 - 1), localEnd, tileX0, tileY0, tileX1, tileY1);
308 if(localWaitEnd_ < localEnd)
309 localWaitEnd_ = localEnd;
310 completionCV_.wait(lock, allCompleted);
311 grklog.debug("End wait with contiguous end %d", localContiguousEnd_);
312 }
313 else
314 {
315 grklog.debug(
316 "No waiting for swath ending at tile %d (local %d), tiles: x0=%d, y0=%d, x1=%d, y1=%d",
317 (tileY1 - 1) * numTileCols_ + (tileX1 - 1), localEnd, tileX0, tileY0, tileX1, tileY1);
318 }
319 }
320 bool finalWait =
321 localContiguousEnd_ == static_cast<int32_t>(subregionWidth_ * subregionHeight_ - 1);
322
323 grklog.debug("Swath completed: tileX0=%d, tileY0=%d, tileX1=%d, tileY1=%d", swath->tile_x0,
324 swath->tile_y0, swath->tile_x1, swath->tile_y1);
325 return finalWait;
326 }
327
328 int32_t getLastClearedTileY() const
329 {
330 std::lock_guard<std::mutex> lock(mutex_);
331 return lastClearedTileY_;
332 }
333
334 void setLastClearedTileY(int32_t val)
335 {
336 std::lock_guard<std::mutex> lock(mutex_);
337 lastClearedTileY_ = val;
338 }
339
340 int32_t getNeededTileY1() const
341 {
342 return neededTileY1_.load(std::memory_order_acquire);
343 }
344
345 uint16_t getNumTileCols() const
346 {
347 return numTileCols_;
348 }
349
350private:
351 TileCache* tileCache_ = nullptr; // Stored for row-based clearing
353 std::vector<bool> completedTiles_; // Tracks completion status of each tile in subregion
354 std::vector<uint16_t> completedTilesPerRow_; // Tracks number of completed tiles per row
355 int32_t localWaitEnd_ = -1;
357 mutable std::mutex mutex_;
358 std::condition_variable completionCV_;
364 uint16_t currentTileY_; // Tracks the current swath's starting tile row
365 int32_t lastClearedTileY_; // Tracks the last tile row cleared (-1 = none)
366 std::atomic<int32_t> neededTileY1_{0}; // Max tile row the consumer is currently waiting for
367 RowCompletionCallback rowCallback_; // Callback for row completion
368 RowsReleasedCallback rowsReleasedCallback_; // Callback after wait() releases rows
369};
370
371} // namespace grk
static void releaseFreedPages()
Definition MemManager.h:63
A simple non-thread-safe min-heap for tracking contiguous sequences of size_t indices.
Definition MinHeap.h:34
Caches tile processors so that repeated decompress calls on the same codec can reuse SOT metadata,...
Definition TileCache.h:108
uint16_t subregionHeight_
Definition TileCompletion.h:363
RowsReleasedCallback rowsReleasedCallback_
Definition TileCompletion.h:368
TileCompletion(TileCache *tileCache, const Rect32 &imageBounds, uint32_t tileWidth, uint32_t tileHeight, RowCompletionCallback callback, RowsReleasedCallback rowsReleasedCallback=nullptr, std::optional< Rect16 > tileSubRegion=std::nullopt)
Definition TileCompletion.h:49
bool wait(grk_wait_swath *swath)
Definition TileCompletion.h:162
uint16_t numTileRows_
Definition TileCompletion.h:359
uint32_t tileWidth_
Definition TileCompletion.h:360
void setLastClearedTileY(int32_t val)
Definition TileCompletion.h:334
uint16_t currentTileY_
Definition TileCompletion.h:364
std::vector< uint16_t > completedTilesPerRow_
Definition TileCompletion.h:354
uint16_t subregionWidth_
Definition TileCompletion.h:363
int32_t getLastClearedTileY() const
Definition TileCompletion.h:328
RowCompletionCallback rowCallback_
Definition TileCompletion.h:367
int32_t localWaitEnd_
Definition TileCompletion.h:355
int32_t lastClearedTileY_
Definition TileCompletion.h:365
SimpleHeap< uint16_t > heap_
Definition TileCompletion.h:352
uint16_t tileX1_
Definition TileCompletion.h:362
uint16_t tileY0_
Definition TileCompletion.h:362
std::atomic< int32_t > neededTileY1_
Definition TileCompletion.h:366
std::condition_variable completionCV_
Definition TileCompletion.h:358
uint32_t tileHeight_
Definition TileCompletion.h:360
Rect32 imageBounds_
Definition TileCompletion.h:361
std::function< void(uint16_t tileIndexBegin, uint16_t tileIndexEnd)> RowCompletionCallback
Definition TileCompletion.h:44
std::mutex mutex_
Definition TileCompletion.h:357
int32_t getNeededTileY1() const
Definition TileCompletion.h:340
uint16_t tileY1_
Definition TileCompletion.h:362
int32_t localContiguousEnd_
Definition TileCompletion.h:356
TileCache * tileCache_
Definition TileCompletion.h:351
std::vector< bool > completedTiles_
Definition TileCompletion.h:353
uint16_t getNumTileCols() const
Definition TileCompletion.h:345
std::function< void()> RowsReleasedCallback
Definition TileCompletion.h:46
void complete(uint16_t tileIndex)
Definition TileCompletion.h:103
uint16_t numTileCols_
Definition TileCompletion.h:359
uint16_t tileX0_
Definition TileCompletion.h:362
ResWindow.
Definition CompressedChunkCache.h:36
ILogger & grklog
Definition Logger.cpp:24
Rect< uint32_t > Rect32
Definition geometry.h:64
T x1
Definition geometry.h:192
T x0
Definition geometry.h:192
T y0
Definition geometry.h:192
T y1
Definition geometry.h:192
Specify swath region to wait on during asynchronous decompression.
Definition grok.h:1004
uint32_t y0
Input: Pixel coordinate: top-left y.
Definition grok.h:1006
uint16_t num_tile_cols
Output: Total tile columns in the image tile grid.
Definition grok.h:1013
uint32_t x1
Input: Pixel coordinate: bottom-right x (exclusive).
Definition grok.h:1007
uint32_t y1
Input: Pixel coordinate: bottom-right y (exclusive).
Definition grok.h:1008
uint16_t tile_y0
Output: Global tile row start (inclusive).
Definition grok.h:1010
uint16_t tile_y1
Output: Global tile row end (exclusive).
Definition grok.h:1012
uint16_t tile_x0
Output: Global tile column start (inclusive).
Definition grok.h:1009
uint16_t tile_x1
Output: Global tile column end (exclusive).
Definition grok.h:1011
uint32_t x0
Input: Pixel coordinate: top-left x.
Definition grok.h:1005