Coverage Report

Created: 2025-06-13 06:29

/src/gdal/gcore/gdalcachedpixelaccessor.h
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  Fast access to individual pixels in a GDALRasterBand
5
 * Author:   Even Rouault <even dot rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2022, Planet Labs
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#ifndef GDAL_CACHED_PIXEL_ACCESSOR_INCLUDED
14
#define GDAL_CACHED_PIXEL_ACCESSOR_INCLUDED
15
16
#include "gdal_priv.h"
17
#include "cpl_error.h"
18
#include "cpl_float.h"
19
20
#include <algorithm>
21
#include <array>
22
#include <vector>
23
24
/************************************************************************/
25
/*                      GDALCachedPixelAccessor                         */
26
/************************************************************************/
27
28
/** Class to have reasonably fast random pixel access to a raster band, when
29
 * accessing multiple pixels that are close to each other.
30
 *
31
 * This gives faster access than using GDALRasterBand::RasterIO() with
32
 * a 1x1 window.
33
 *
34
 * @since GDAL 3.5
35
 */
36
template <class Type, int TILE_SIZE, int CACHED_TILE_COUNT = 4>
37
class GDALCachedPixelAccessor
38
{
39
    GDALRasterBand *m_poBand = nullptr;
40
41
    struct CachedTile
42
    {
43
        std::vector<Type> m_data{};
44
        int m_nTileX = -1;
45
        int m_nTileY = -1;
46
        bool m_bModified = false;
47
    };
48
49
    int m_nCachedTileCount = 0;
50
    std::array<CachedTile, CACHED_TILE_COUNT> m_aCachedTiles{};
51
52
    bool LoadTile(int nTileX, int nTileY);
53
    bool FlushTile(int iSlot);
54
55
    Type GetSlowPath(int nTileX, int nTileY, int nXInTile, int nYInTile,
56
                     bool *pbSuccess);
57
    bool SetSlowPath(int nTileX, int nTileY, int nXInTile, int nYInTile,
58
                     Type val);
59
60
    GDALCachedPixelAccessor(const GDALCachedPixelAccessor &) = delete;
61
    GDALCachedPixelAccessor &
62
    operator=(const GDALCachedPixelAccessor &) = delete;
63
64
  public:
65
    explicit GDALCachedPixelAccessor(GDALRasterBand *poBand);
66
    ~GDALCachedPixelAccessor();
67
68
    /** Assign the raster band if not known at construction time. */
69
    void SetBand(GDALRasterBand *poBand)
70
0
    {
71
0
        m_poBand = poBand;
72
0
    }
Unexecuted instantiation: GDALCachedPixelAccessor<float, 256, 64>::SetBand(GDALRasterBand*)
Unexecuted instantiation: GDALCachedPixelAccessor<double, 256, 64>::SetBand(GDALRasterBand*)
73
74
    Type Get(int nX, int nY, bool *pbSuccess = nullptr);
75
    bool Set(int nX, int nY, Type val);
76
77
    bool FlushCache();
78
    void ResetModifiedFlag();
79
};
80
81
/************************************************************************/
82
/*                      GDALCachedPixelAccessor()                       */
83
/************************************************************************/
84
85
/** Constructor.
86
 *
87
 * The template accepts the following parameters:
88
 * - Type: should be one of GByte, GUInt16, GInt16, GUInt32, GInt32, GUInt64,
89
 * GInt64, float or double
90
 * - TILE_SIZE: the tile size for the cache of GDALCachedPixelAccessor.
91
 *              Use a power of two for faster computation.
92
 *              It doesn't need to be the same of the underlying raster
93
 * - CACHED_TILE_COUNT: number of tiles to cache. Should be >= 1. Defaults to 4.
94
 *
95
 * @param poBand Raster band.
96
 */
97
template <class Type, int TILE_SIZE, int CACHED_TILE_COUNT>
98
GDALCachedPixelAccessor<Type, TILE_SIZE, CACHED_TILE_COUNT>::
99
    GDALCachedPixelAccessor(GDALRasterBand *poBand)
100
0
    : m_poBand(poBand)
101
0
{
102
0
}
Unexecuted instantiation: GDALCachedPixelAccessor<double, 256, 64>::GDALCachedPixelAccessor(GDALRasterBand*)
Unexecuted instantiation: GDALCachedPixelAccessor<float, 256, 64>::GDALCachedPixelAccessor(GDALRasterBand*)
103
104
/************************************************************************/
105
/*                     ~GDALCachedPixelAccessor()                       */
106
/************************************************************************/
107
108
/** Destructor.
109
 *
110
 * Will call FlushCache()
111
 */
112
template <class Type, int TILE_SIZE, int CACHED_TILE_COUNT>
113
GDALCachedPixelAccessor<Type, TILE_SIZE,
114
                        CACHED_TILE_COUNT>::~GDALCachedPixelAccessor()
115
0
{
116
0
    FlushCache();
117
0
}
Unexecuted instantiation: GDALCachedPixelAccessor<double, 256, 64>::~GDALCachedPixelAccessor()
Unexecuted instantiation: GDALCachedPixelAccessor<float, 256, 64>::~GDALCachedPixelAccessor()
118
119
/************************************************************************/
120
/*                            Get()                                     */
121
/************************************************************************/
122
123
/** Get the value of a pixel.
124
 *
125
 * No bound checking of nX, nY is done.
126
 *
127
 * @param nX X coordinate (between 0 and GetXSize()-1)
128
 * @param nY Y coordinate (between 0 and GetYSize()-1)
129
 * @param[out] pbSuccess Optional pointer to a success flag
130
 * @return the pixel value
131
 */
132
template <class Type, int TILE_SIZE, int CACHED_TILE_COUNT>
133
inline Type GDALCachedPixelAccessor<Type, TILE_SIZE, CACHED_TILE_COUNT>::Get(
134
    int nX, int nY, bool *pbSuccess)
135
0
{
136
0
    const int nTileX = nX / TILE_SIZE;
137
0
    const int nTileY = nY / TILE_SIZE;
138
0
    const int nXInTile = nX % TILE_SIZE;
139
0
    const int nYInTile = nY % TILE_SIZE;
140
0
    if (m_aCachedTiles[0].m_nTileX == nTileX &&
141
0
        m_aCachedTiles[0].m_nTileY == nTileY)
142
0
    {
143
0
        if (pbSuccess)
144
0
            *pbSuccess = true;
145
0
        return m_aCachedTiles[0].m_data[nYInTile * TILE_SIZE + nXInTile];
146
0
    }
147
0
    return GetSlowPath(nTileX, nTileY, nXInTile, nYInTile, pbSuccess);
148
0
}
Unexecuted instantiation: GDALCachedPixelAccessor<double, 256, 64>::Get(int, int, bool*)
Unexecuted instantiation: GDALCachedPixelAccessor<float, 256, 64>::Get(int, int, bool*)
149
150
/************************************************************************/
151
/*                       GetSlowPath()                                  */
152
/************************************************************************/
153
154
template <class Type, int TILE_SIZE, int CACHED_TILE_COUNT>
155
Type GDALCachedPixelAccessor<Type, TILE_SIZE, CACHED_TILE_COUNT>::GetSlowPath(
156
    int nTileX, int nTileY, int nXInTile, int nYInTile, bool *pbSuccess)
157
0
{
158
0
    for (int i = 1; i < m_nCachedTileCount; ++i)
159
0
    {
160
0
        const auto &cachedTile = m_aCachedTiles[i];
161
0
        if (cachedTile.m_nTileX == nTileX && cachedTile.m_nTileY == nTileY)
162
0
        {
163
0
            const auto ret = cachedTile.m_data[nYInTile * TILE_SIZE + nXInTile];
164
0
            CachedTile tmp = std::move(m_aCachedTiles[i]);
165
0
            for (int j = i; j >= 1; --j)
166
0
                m_aCachedTiles[j] = std::move(m_aCachedTiles[j - 1]);
167
0
            m_aCachedTiles[0] = std::move(tmp);
168
0
            if (pbSuccess)
169
0
                *pbSuccess = true;
170
0
            return ret;
171
0
        }
172
0
    }
173
0
    if (!LoadTile(nTileX, nTileY))
174
0
    {
175
0
        if (pbSuccess)
176
0
            *pbSuccess = false;
177
0
        return 0;
178
0
    }
179
0
    if (pbSuccess)
180
0
        *pbSuccess = true;
181
0
    return m_aCachedTiles[0].m_data[nYInTile * TILE_SIZE + nXInTile];
182
0
}
Unexecuted instantiation: GDALCachedPixelAccessor<double, 256, 64>::GetSlowPath(int, int, int, int, bool*)
Unexecuted instantiation: GDALCachedPixelAccessor<float, 256, 64>::GetSlowPath(int, int, int, int, bool*)
183
184
/************************************************************************/
185
/*                            Set()                                     */
186
/************************************************************************/
187
188
/** Set the value of a pixel.
189
 *
190
 * The actual modification of the underlying raster is deferred until the tile
191
 * is implicit flushed while loading a new tile, or an explicit call to
192
 * FlushCache().
193
 *
194
 * The destructor of GDALCachedPixelAccessor will take care of calling
195
 * FlushCache(), if the user hasn't done it explicitly.
196
 *
197
 * No bound checking of nX, nY is done.
198
 *
199
 * @param nX X coordinate (between 0 and GetXSize()-1)
200
 * @param nY Y coordinate (between 0 and GetYSize()-1)
201
 * @param val pixel value
202
 * @return true if success
203
 */
204
template <class Type, int TILE_SIZE, int CACHED_TILE_COUNT>
205
inline bool
206
GDALCachedPixelAccessor<Type, TILE_SIZE, CACHED_TILE_COUNT>::Set(int nX, int nY,
207
                                                                 Type val)
208
0
{
209
0
    const int nTileX = nX / TILE_SIZE;
210
0
    const int nTileY = nY / TILE_SIZE;
211
0
    const int nXInTile = nX % TILE_SIZE;
212
0
    const int nYInTile = nY % TILE_SIZE;
213
0
    if (m_aCachedTiles[0].m_nTileX == nTileX &&
214
0
        m_aCachedTiles[0].m_nTileY == nTileY)
215
0
    {
216
0
        m_aCachedTiles[0].m_data[nYInTile * TILE_SIZE + nXInTile] = val;
217
0
        m_aCachedTiles[0].m_bModified = true;
218
0
        return true;
219
0
    }
220
0
    return SetSlowPath(nTileX, nTileY, nXInTile, nYInTile, val);
221
0
}
222
223
/************************************************************************/
224
/*                         SetSlowPath()                                */
225
/************************************************************************/
226
227
template <class Type, int TILE_SIZE, int CACHED_TILE_COUNT>
228
bool GDALCachedPixelAccessor<Type, TILE_SIZE, CACHED_TILE_COUNT>::SetSlowPath(
229
    int nTileX, int nTileY, int nXInTile, int nYInTile, Type val)
230
0
{
231
0
    for (int i = 1; i < m_nCachedTileCount; ++i)
232
0
    {
233
0
        auto &cachedTile = m_aCachedTiles[i];
234
0
        if (cachedTile.m_nTileX == nTileX && cachedTile.m_nTileY == nTileY)
235
0
        {
236
0
            cachedTile.m_data[nYInTile * TILE_SIZE + nXInTile] = val;
237
0
            cachedTile.m_bModified = true;
238
0
            if (i > 0)
239
0
            {
240
0
                CachedTile tmp = std::move(m_aCachedTiles[i]);
241
0
                for (int j = i; j >= 1; --j)
242
0
                    m_aCachedTiles[j] = std::move(m_aCachedTiles[j - 1]);
243
0
                m_aCachedTiles[0] = std::move(tmp);
244
0
            }
245
0
            return true;
246
0
        }
247
0
    }
248
0
    if (!LoadTile(nTileX, nTileY))
249
0
    {
250
0
        return false;
251
0
    }
252
0
    m_aCachedTiles[0].m_data[nYInTile * TILE_SIZE + nXInTile] = val;
253
0
    m_aCachedTiles[0].m_bModified = true;
254
0
    return true;
255
0
}
256
257
/************************************************************************/
258
/*                            FlushCache()                              */
259
/************************************************************************/
260
261
/** Flush content of modified tiles and drop caches
262
 *
263
 * @return true if success
264
 */
265
template <class Type, int TILE_SIZE, int CACHED_TILE_COUNT>
266
bool GDALCachedPixelAccessor<Type, TILE_SIZE, CACHED_TILE_COUNT>::FlushCache()
267
0
{
268
0
    bool bRet = true;
269
0
    for (int i = 0; i < m_nCachedTileCount; ++i)
270
0
    {
271
0
        if (!FlushTile(i))
272
0
            bRet = false;
273
0
        m_aCachedTiles[i].m_nTileX = -1;
274
0
        m_aCachedTiles[i].m_nTileY = -1;
275
0
    }
276
0
    return bRet;
277
0
}
Unexecuted instantiation: GDALCachedPixelAccessor<double, 256, 64>::FlushCache()
Unexecuted instantiation: GDALCachedPixelAccessor<float, 256, 64>::FlushCache()
278
279
/************************************************************************/
280
/*                      ResetModifiedFlag()                             */
281
/************************************************************************/
282
283
/** Reset the modified flag for cached tiles.
284
 */
285
template <class Type, int TILE_SIZE, int CACHED_TILE_COUNT>
286
void GDALCachedPixelAccessor<Type, TILE_SIZE,
287
                             CACHED_TILE_COUNT>::ResetModifiedFlag()
288
0
{
289
0
    for (int i = 0; i < m_nCachedTileCount; ++i)
290
0
    {
291
0
        m_aCachedTiles[i].m_bModified = false;
292
0
    }
293
0
}
Unexecuted instantiation: GDALCachedPixelAccessor<double, 256, 64>::ResetModifiedFlag()
Unexecuted instantiation: GDALCachedPixelAccessor<float, 256, 64>::ResetModifiedFlag()
294
295
/************************************************************************/
296
/*                 GDALCachedPixelAccessorGetDataType                   */
297
/************************************************************************/
298
299
/*! @cond Doxygen_Suppress */
300
template <class T> struct GDALCachedPixelAccessorGetDataType
301
{
302
};
303
304
template <> struct GDALCachedPixelAccessorGetDataType<GByte>
305
{
306
    static constexpr GDALDataType DataType = GDT_Byte;
307
};
308
309
template <> struct GDALCachedPixelAccessorGetDataType<GInt8>
310
{
311
    static constexpr GDALDataType DataType = GDT_Int8;
312
};
313
314
template <> struct GDALCachedPixelAccessorGetDataType<GUInt16>
315
{
316
    static constexpr GDALDataType DataType = GDT_UInt16;
317
};
318
319
template <> struct GDALCachedPixelAccessorGetDataType<GInt16>
320
{
321
    static constexpr GDALDataType DataType = GDT_Int16;
322
};
323
324
template <> struct GDALCachedPixelAccessorGetDataType<GUInt32>
325
{
326
    static constexpr GDALDataType DataType = GDT_UInt32;
327
};
328
329
template <> struct GDALCachedPixelAccessorGetDataType<GInt32>
330
{
331
    static constexpr GDALDataType DataType = GDT_Int32;
332
};
333
#if SIZEOF_UNSIGNED_LONG == 8
334
// std::uint64_t on Linux 64-bit resolves as unsigned long
335
template <> struct GDALCachedPixelAccessorGetDataType<unsigned long>
336
{
337
    static constexpr GDALDataType DataType = GDT_UInt64;
338
};
339
340
template <> struct GDALCachedPixelAccessorGetDataType<long>
341
{
342
    static constexpr GDALDataType DataType = GDT_Int64;
343
};
344
#endif
345
template <> struct GDALCachedPixelAccessorGetDataType<GUInt64>
346
{
347
    static constexpr GDALDataType DataType = GDT_UInt64;
348
};
349
350
template <> struct GDALCachedPixelAccessorGetDataType<GInt64>
351
{
352
    static constexpr GDALDataType DataType = GDT_Int64;
353
};
354
355
template <> struct GDALCachedPixelAccessorGetDataType<GFloat16>
356
{
357
    static constexpr GDALDataType DataType = GDT_Float16;
358
};
359
360
template <> struct GDALCachedPixelAccessorGetDataType<float>
361
{
362
    static constexpr GDALDataType DataType = GDT_Float32;
363
};
364
365
template <> struct GDALCachedPixelAccessorGetDataType<double>
366
{
367
    static constexpr GDALDataType DataType = GDT_Float64;
368
};
369
370
/*! @endcond */
371
372
/************************************************************************/
373
/*                          LoadTile()                                  */
374
/************************************************************************/
375
376
template <class Type, int TILE_SIZE, int CACHED_TILE_COUNT>
377
bool GDALCachedPixelAccessor<Type, TILE_SIZE, CACHED_TILE_COUNT>::LoadTile(
378
    int nTileX, int nTileY)
379
0
{
380
0
    if (m_nCachedTileCount == CACHED_TILE_COUNT)
381
0
    {
382
0
        if (!FlushTile(CACHED_TILE_COUNT - 1))
383
0
            return false;
384
0
        CachedTile tmp = std::move(m_aCachedTiles[CACHED_TILE_COUNT - 1]);
385
0
        for (int i = CACHED_TILE_COUNT - 1; i >= 1; --i)
386
0
            m_aCachedTiles[i] = std::move(m_aCachedTiles[i - 1]);
387
0
        m_aCachedTiles[0] = std::move(tmp);
388
0
    }
389
0
    else
390
0
    {
391
0
        if (m_nCachedTileCount > 0)
392
0
            std::swap(m_aCachedTiles[0], m_aCachedTiles[m_nCachedTileCount]);
393
0
        m_aCachedTiles[0].m_data.resize(TILE_SIZE * TILE_SIZE);
394
0
        m_nCachedTileCount++;
395
0
    }
396
397
#if 0
398
    CPLDebug("GDAL", "Load tile(%d, %d) of band %d of dataset %s",
399
              nTileX, nTileY, m_poBand->GetBand(),
400
              m_poBand->GetDataset() ? m_poBand->GetDataset()->GetDescription() : "(unknown)");
401
#endif
402
0
    CPLAssert(!m_aCachedTiles[0].m_bModified);
403
0
    const int nXOff = nTileX * TILE_SIZE;
404
0
    const int nYOff = nTileY * TILE_SIZE;
405
0
    const int nReqXSize = std::min(m_poBand->GetXSize() - nXOff, TILE_SIZE);
406
0
    const int nReqYSize = std::min(m_poBand->GetYSize() - nYOff, TILE_SIZE);
407
0
    if (m_poBand->RasterIO(
408
0
            GF_Read, nXOff, nYOff, nReqXSize, nReqYSize,
409
0
            m_aCachedTiles[0].m_data.data(), nReqXSize, nReqYSize,
410
0
            GDALCachedPixelAccessorGetDataType<Type>::DataType, sizeof(Type),
411
0
            TILE_SIZE * sizeof(Type), nullptr) != CE_None)
412
0
    {
413
0
        m_aCachedTiles[0].m_nTileX = -1;
414
0
        m_aCachedTiles[0].m_nTileY = -1;
415
0
        return false;
416
0
    }
417
0
    m_aCachedTiles[0].m_nTileX = nTileX;
418
0
    m_aCachedTiles[0].m_nTileY = nTileY;
419
0
    return true;
420
0
}
Unexecuted instantiation: GDALCachedPixelAccessor<double, 256, 64>::LoadTile(int, int)
Unexecuted instantiation: GDALCachedPixelAccessor<float, 256, 64>::LoadTile(int, int)
421
422
/************************************************************************/
423
/*                          FlushTile()                                 */
424
/************************************************************************/
425
426
template <class Type, int TILE_SIZE, int CACHED_TILE_COUNT>
427
bool GDALCachedPixelAccessor<Type, TILE_SIZE, CACHED_TILE_COUNT>::FlushTile(
428
    int iSlot)
429
0
{
430
0
    if (!m_aCachedTiles[iSlot].m_bModified)
431
0
        return true;
432
433
0
    m_aCachedTiles[iSlot].m_bModified = false;
434
0
    const int nXOff = m_aCachedTiles[iSlot].m_nTileX * TILE_SIZE;
435
0
    const int nYOff = m_aCachedTiles[iSlot].m_nTileY * TILE_SIZE;
436
0
    const int nReqXSize = std::min(m_poBand->GetXSize() - nXOff, TILE_SIZE);
437
0
    const int nReqYSize = std::min(m_poBand->GetYSize() - nYOff, TILE_SIZE);
438
0
    return m_poBand->RasterIO(
439
0
               GF_Write, nXOff, nYOff, nReqXSize, nReqYSize,
440
0
               m_aCachedTiles[iSlot].m_data.data(), nReqXSize, nReqYSize,
441
0
               GDALCachedPixelAccessorGetDataType<Type>::DataType, sizeof(Type),
442
0
               TILE_SIZE * sizeof(Type), nullptr) == CE_None;
443
0
}
Unexecuted instantiation: GDALCachedPixelAccessor<double, 256, 64>::FlushTile(int)
Unexecuted instantiation: GDALCachedPixelAccessor<float, 256, 64>::FlushTile(int)
444
445
#endif  // GDAL_PIXEL_ACCESSOR_INCLUDED