Coverage Report

Created: 2026-08-08 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/astc-encoder/Source/astcenc_compute_variance.cpp
Line
Count
Source
1
// SPDX-License-Identifier: Apache-2.0
2
// ----------------------------------------------------------------------------
3
// Copyright 2011-2026 Arm Limited
4
//
5
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
6
// use this file except in compliance with the License. You may obtain a copy
7
// of the License at:
8
//
9
//     http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing, software
12
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
// License for the specific language governing permissions and limitations
15
// under the License.
16
// ----------------------------------------------------------------------------
17
18
#if !defined(ASTCENC_DECOMPRESS_ONLY)
19
20
/**
21
 * @brief Functions to calculate variance per component in a NxN footprint.
22
 *
23
 * We need N to be parametric, so the routine below uses summed area tables in order to execute in
24
 * O(1) time independent of how big N is.
25
 *
26
 * The addition uses a Brent-Kung-based parallel prefix adder. This uses the prefix tree to first
27
 * perform a binary reduction, and then distributes the results. This method means that there is no
28
 * serial dependency between a given element and the next one, and also significantly improves
29
 * numerical stability allowing us to use floats rather than doubles.
30
 */
31
32
#include "astcenc_internal.h"
33
34
#include <cassert>
35
36
/**
37
 * @brief Generate a prefix-sum array using the Brent-Kung algorithm.
38
 *
39
 * This will take an input array of the form:
40
 *     v0, v1, v2, ...
41
 * ... and modify in-place to turn it into a prefix-sum array of the form:
42
 *     v0, v0+v1, v0+v1+v2, ...
43
 *
44
 * @param d      The array to prefix-sum.
45
 * @param items  The number of items in the array.
46
 * @param stride The item spacing in the array; i.e. dense arrays should use 1.
47
 */
48
static void brent_kung_prefix_sum(
49
  vfloat4* d,
50
  size_t items,
51
  size_t stride
52
0
) {
53
0
  if (items < 2)
54
0
    return;
55
56
0
  size_t lc_stride = 2;
57
0
  size_t log2_stride = 1;
58
59
  // The reduction-tree loop
60
0
  do {
61
0
    size_t step = lc_stride >> 1;
62
0
    size_t start = lc_stride - 1;
63
0
    size_t iters = items >> log2_stride;
64
65
0
    vfloat4 *da = d + (start * stride);
66
0
    ptrdiff_t ofs = -static_cast<ptrdiff_t>(step * stride);
67
0
    size_t ofs_stride = stride << log2_stride;
68
69
0
    while (iters)
70
0
    {
71
0
      *da = *da + da[ofs];
72
0
      da += ofs_stride;
73
0
      iters--;
74
0
    }
75
76
0
    log2_stride += 1;
77
0
    lc_stride <<= 1;
78
0
  } while (lc_stride <= items);
79
80
  // The expansion-tree loop
81
0
  do {
82
0
    log2_stride -= 1;
83
0
    lc_stride >>= 1;
84
85
0
    size_t step = lc_stride >> 1;
86
0
    size_t start = step + lc_stride - 1;
87
0
    size_t iters = (items - step) >> log2_stride;
88
89
0
    vfloat4 *da = d + (start * stride);
90
0
    ptrdiff_t ofs = -static_cast<ptrdiff_t>(step * stride);
91
0
    size_t ofs_stride = stride << log2_stride;
92
93
0
    while (iters)
94
0
    {
95
0
      *da = *da + da[ofs];
96
0
      da += ofs_stride;
97
0
      iters--;
98
0
    }
99
0
  } while (lc_stride > 2);
100
0
}
101
102
/* See header for documentation. */
103
void compute_pixel_region_variance(
104
  astcenc_contexti& ctx,
105
  const pixel_region_args& arg
106
0
) {
107
  // Unpack the memory structure into local variables
108
0
  const astcenc_image* img = arg.img;
109
0
  astcenc_swizzle swz = arg.swz;
110
0
  bool have_z = arg.have_z;
111
112
0
  size_t size_x = arg.size_x;
113
0
  size_t size_y = arg.size_y;
114
0
  size_t size_z = arg.size_z;
115
116
0
  size_t offset_x = arg.offset_x;
117
0
  size_t offset_y = arg.offset_y;
118
0
  size_t offset_z = arg.offset_z;
119
120
0
  size_t alpha_kernel_radius = arg.alpha_kernel_radius;
121
122
0
  float*   input_alpha_averages = ctx.input_alpha_averages;
123
0
  vfloat4* work_memory = arg.work_memory;
124
125
  // Compute memory sizes and dimensions that we need
126
0
  size_t kernel_radius = alpha_kernel_radius;
127
0
  size_t kerneldim = 2 * kernel_radius + 1;
128
0
  size_t kernel_radius_xy = kernel_radius;
129
0
  size_t kernel_radius_z = have_z ? kernel_radius : 0;
130
131
0
  size_t padsize_x = size_x + kerneldim;
132
0
  size_t padsize_y = size_y + kerneldim;
133
0
  size_t padsize_z = size_z + (have_z ? kerneldim : 0);
134
0
  size_t sizeprod = padsize_x * padsize_y * padsize_z;
135
136
0
  int zd_start = have_z ? 1 : 0;
137
138
0
  vfloat4 *varbuf1 = work_memory;
139
0
  vfloat4 *varbuf2 = work_memory + sizeprod;
140
141
  // Scaling factors to apply to Y and Z for accesses into the work buffers
142
0
  size_t yst = padsize_x;
143
0
  size_t zst = padsize_x * padsize_y;
144
145
  // Scaling factors to apply to Y and Z for accesses into result buffers
146
0
  size_t ydt = img->dim_x;
147
0
  size_t zdt = img->dim_x * img->dim_y;
148
149
  // Macros to act as accessor functions for the work-memory
150
0
  #define VARBUF1(z, y, x) varbuf1[z * zst + y * yst + x]
151
0
  #define VARBUF2(z, y, x) varbuf2[z * zst + y * yst + x]
152
153
  // Load N and N^2 values into the work buffers
154
0
  if (img->data_type == ASTCENC_TYPE_U8)
155
0
  {
156
    // Swizzle data structure 4 = ZERO, 5 = ONE
157
0
    uint8_t data[6];
158
0
    data[ASTCENC_SWZ_0] = 0;
159
0
    data[ASTCENC_SWZ_1] = 255;
160
161
0
    for (size_t z = zd_start; z < padsize_z; z++)
162
0
    {
163
0
      size_t z_src = (z - zd_start) + offset_z;
164
0
      if (z_src <= kernel_radius_z)
165
0
      {
166
0
        z_src = 0;
167
0
      }
168
0
      else
169
0
      {
170
0
        z_src -= kernel_radius_z;
171
0
      }
172
173
0
      z_src = astc::min(z_src, static_cast<size_t>(img->dim_z - 1));
174
175
0
      uint8_t* data8 = static_cast<uint8_t*>(img->data[z_src]);
176
177
0
      for (size_t y = 1; y < padsize_y; y++)
178
0
      {
179
0
        size_t y_src = (y - 1) + offset_y;
180
0
        if (y_src <= kernel_radius_xy)
181
0
        {
182
0
          y_src = 0;
183
0
        }
184
0
        else
185
0
        {
186
0
          y_src -= kernel_radius_xy;
187
0
        }
188
189
0
        y_src = astc::min(y_src, static_cast<size_t>(img->dim_y - 1));
190
191
0
        for (size_t x = 1; x < padsize_x; x++)
192
0
        {
193
0
          size_t x_src = (x - 1) + offset_x;
194
0
          if (x_src <= kernel_radius_xy)
195
0
          {
196
0
            x_src = 0;
197
0
          }
198
0
          else
199
0
          {
200
0
            x_src -= kernel_radius_xy;
201
0
          }
202
203
0
          x_src = astc::min(x_src, static_cast<size_t>(img->dim_x - 1));
204
205
0
          data[0] = data8[(4 * img->dim_x * y_src) + (4 * x_src    )];
206
0
          data[1] = data8[(4 * img->dim_x * y_src) + (4 * x_src + 1)];
207
0
          data[2] = data8[(4 * img->dim_x * y_src) + (4 * x_src + 2)];
208
0
          data[3] = data8[(4 * img->dim_x * y_src) + (4 * x_src + 3)];
209
210
0
          uint8_t r = data[swz.r];
211
0
          uint8_t g = data[swz.g];
212
0
          uint8_t b = data[swz.b];
213
0
          uint8_t a = data[swz.a];
214
215
0
          vfloat4 d = vfloat4 (r * (1.0f / 255.0f),
216
0
                               g * (1.0f / 255.0f),
217
0
                               b * (1.0f / 255.0f),
218
0
                               a * (1.0f / 255.0f));
219
220
0
          VARBUF1(z, y, x) = d;
221
0
          VARBUF2(z, y, x) = d * d;
222
0
        }
223
0
      }
224
0
    }
225
0
  }
226
0
  else if (img->data_type == ASTCENC_TYPE_F16)
227
0
  {
228
    // Swizzle data structure 4 = ZERO, 5 = ONE (in FP16)
229
0
    uint16_t data[6];
230
0
    data[ASTCENC_SWZ_0] = 0;
231
0
    data[ASTCENC_SWZ_1] = 0x3C00;
232
233
0
    for (size_t z = zd_start; z < padsize_z; z++)
234
0
    {
235
0
      size_t z_src = (z - zd_start) + offset_z;
236
0
      if (z_src <= kernel_radius_z)
237
0
      {
238
0
        z_src = 0;
239
0
      }
240
0
      else
241
0
      {
242
0
        z_src -= kernel_radius_z;
243
0
      }
244
245
0
      z_src = astc::min(z_src, static_cast<size_t>(img->dim_z - 1));
246
247
0
      uint16_t* data16 = static_cast<uint16_t*>(img->data[z_src]);
248
249
0
      for (size_t y = 1; y < padsize_y; y++)
250
0
      {
251
0
        size_t y_src = (y - 1) + offset_y;
252
0
        if (y_src <= kernel_radius_xy)
253
0
        {
254
0
          y_src = 0;
255
0
        }
256
0
        else
257
0
        {
258
0
          y_src -= kernel_radius_xy;
259
0
        }
260
261
0
        y_src = astc::min(y_src, static_cast<size_t>(img->dim_y - 1));
262
263
0
        for (size_t x = 1; x < padsize_x; x++)
264
0
        {
265
0
          size_t x_src = (x - 1) + offset_x;
266
0
          if (x_src <= kernel_radius_xy)
267
0
          {
268
0
            x_src = 0;
269
0
          }
270
0
          else
271
0
          {
272
0
            x_src -= kernel_radius_xy;
273
0
          }
274
275
0
          x_src = astc::min(x_src, static_cast<size_t>(img->dim_x - 1));
276
277
0
          data[0] = data16[(4 * img->dim_x * y_src) + (4 * x_src    )];
278
0
          data[1] = data16[(4 * img->dim_x * y_src) + (4 * x_src + 1)];
279
0
          data[2] = data16[(4 * img->dim_x * y_src) + (4 * x_src + 2)];
280
0
          data[3] = data16[(4 * img->dim_x * y_src) + (4 * x_src + 3)];
281
282
0
          vint4 di(data[swz.r], data[swz.g], data[swz.b], data[swz.a]);
283
0
          vfloat4 d = float16_to_float(di);
284
285
0
          VARBUF1(z, y, x) = d;
286
0
          VARBUF2(z, y, x) = d * d;
287
0
        }
288
0
      }
289
0
    }
290
0
  }
291
0
  else // if (img->data_type == ASTCENC_TYPE_F32)
292
0
  {
293
0
    assert(img->data_type == ASTCENC_TYPE_F32);
294
295
    // Swizzle data structure 4 = ZERO, 5 = ONE (in FP16)
296
0
    float data[6];
297
0
    data[ASTCENC_SWZ_0] = 0.0f;
298
0
    data[ASTCENC_SWZ_1] = 1.0f;
299
300
0
    for (size_t z = zd_start; z < padsize_z; z++)
301
0
    {
302
0
      size_t z_src = (z - zd_start) + offset_z;
303
0
      if (z_src <= kernel_radius_z)
304
0
      {
305
0
        z_src = 0;
306
0
      }
307
0
      else
308
0
      {
309
0
        z_src -= kernel_radius_z;
310
0
      }
311
312
0
      z_src = astc::min(z_src, static_cast<size_t>(img->dim_z - 1));
313
314
0
      float* data32 = static_cast<float*>(img->data[z_src]);
315
316
0
      for (size_t y = 1; y < padsize_y; y++)
317
0
      {
318
0
        size_t y_src = (y - 1) + offset_y;
319
0
        if (y_src <= kernel_radius_xy)
320
0
        {
321
0
          y_src = 0;
322
0
        }
323
0
        else
324
0
        {
325
0
          y_src -= kernel_radius_xy;
326
0
        }
327
328
0
        y_src = astc::min(y_src, static_cast<size_t>(img->dim_y - 1));
329
330
0
        for (size_t x = 1; x < padsize_x; x++)
331
0
        {
332
0
          size_t x_src = (x - 1) + offset_x;
333
0
          if (x_src <= kernel_radius_xy)
334
0
          {
335
0
            x_src = 0;
336
0
          }
337
0
          else
338
0
          {
339
0
            x_src -= kernel_radius_xy;
340
0
          }
341
342
0
          x_src = astc::min(x_src, static_cast<size_t>(img->dim_x - 1));
343
344
0
          data[0] = data32[(4 * img->dim_x * y_src) + (4 * x_src    )];
345
0
          data[1] = data32[(4 * img->dim_x * y_src) + (4 * x_src + 1)];
346
0
          data[2] = data32[(4 * img->dim_x * y_src) + (4 * x_src + 2)];
347
0
          data[3] = data32[(4 * img->dim_x * y_src) + (4 * x_src + 3)];
348
349
0
          float r = data[swz.r];
350
0
          float g = data[swz.g];
351
0
          float b = data[swz.b];
352
0
          float a = data[swz.a];
353
354
0
          vfloat4 d(r, g, b, a);
355
356
0
          VARBUF1(z, y, x) = d;
357
0
          VARBUF2(z, y, x) = d * d;
358
0
        }
359
0
      }
360
0
    }
361
0
  }
362
363
  // Pad with an extra layer of 0s; this forms the edge of the SAT tables
364
0
  vfloat4 vbz = vfloat4::zero();
365
0
  for (size_t z = 0; z < padsize_z; z++)
366
0
  {
367
0
    for (size_t y = 0; y < padsize_y; y++)
368
0
    {
369
0
      VARBUF1(z, y, 0) = vbz;
370
0
      VARBUF2(z, y, 0) = vbz;
371
0
    }
372
373
0
    for (size_t x = 0; x < padsize_x; x++)
374
0
    {
375
0
      VARBUF1(z, 0, x) = vbz;
376
0
      VARBUF2(z, 0, x) = vbz;
377
0
    }
378
0
  }
379
380
0
  if (have_z)
381
0
  {
382
0
    for (size_t y = 0; y < padsize_y; y++)
383
0
    {
384
0
      for (size_t x = 0; x < padsize_x; x++)
385
0
      {
386
0
        VARBUF1(0, y, x) = vbz;
387
0
        VARBUF2(0, y, x) = vbz;
388
0
      }
389
0
    }
390
0
  }
391
392
  // Generate summed-area tables for N and N^2; this is done in-place, using
393
  // a Brent-Kung parallel-prefix based algorithm to minimize precision loss
394
0
  for (size_t z = zd_start; z < padsize_z; z++)
395
0
  {
396
0
    for (size_t y = 1; y < padsize_y; y++)
397
0
    {
398
0
      brent_kung_prefix_sum(&(VARBUF1(z, y, 1)), padsize_x - 1, 1);
399
0
      brent_kung_prefix_sum(&(VARBUF2(z, y, 1)), padsize_x - 1, 1);
400
0
    }
401
0
  }
402
403
0
  for (size_t z = zd_start; z < padsize_z; z++)
404
0
  {
405
0
    for (size_t x = 1; x < padsize_x; x++)
406
0
    {
407
0
      brent_kung_prefix_sum(&(VARBUF1(z, 1, x)), padsize_y - 1, yst);
408
0
      brent_kung_prefix_sum(&(VARBUF2(z, 1, x)), padsize_y - 1, yst);
409
0
    }
410
0
  }
411
412
0
  if (have_z)
413
0
  {
414
0
    for (size_t y = 1; y < padsize_y; y++)
415
0
    {
416
0
      for (size_t x = 1; x < padsize_x; x++)
417
0
      {
418
0
        brent_kung_prefix_sum(&(VARBUF1(1, y, x)), padsize_z - 1, zst);
419
0
        brent_kung_prefix_sum(&(VARBUF2(1, y, x)), padsize_z - 1, zst);
420
0
      }
421
0
    }
422
0
  }
423
424
  // Compute a few constants used in the variance-calculation.
425
0
  float alpha_kdim = static_cast<float>(2 * alpha_kernel_radius + 1);
426
0
  float alpha_rsamples;
427
428
0
  if (have_z)
429
0
  {
430
0
    alpha_rsamples = 1.0f / (alpha_kdim * alpha_kdim * alpha_kdim);
431
0
  }
432
0
  else
433
0
  {
434
0
    alpha_rsamples = 1.0f / (alpha_kdim * alpha_kdim);
435
0
  }
436
437
  // Use the summed-area tables to compute variance for each neighborhood
438
0
  if (have_z)
439
0
  {
440
0
    for (size_t z = 0; z < size_z; z++)
441
0
    {
442
0
      size_t z_src = z + kernel_radius_z;
443
0
      size_t z_dst = z + offset_z;
444
0
      size_t z_low  = z_src - alpha_kernel_radius;
445
0
      size_t z_high = z_src + alpha_kernel_radius + 1;
446
447
0
      for (size_t y = 0; y < size_y; y++)
448
0
      {
449
0
        size_t y_src = y + kernel_radius_xy;
450
0
        size_t y_dst = y + offset_y;
451
0
        size_t y_low  = y_src - alpha_kernel_radius;
452
0
        size_t y_high = y_src + alpha_kernel_radius + 1;
453
454
0
        for (size_t x = 0; x < size_x; x++)
455
0
        {
456
0
          size_t x_src = x + kernel_radius_xy;
457
0
          size_t x_dst = x + offset_x;
458
0
          size_t x_low  = x_src - alpha_kernel_radius;
459
0
          size_t x_high = x_src + alpha_kernel_radius + 1;
460
461
          // Summed-area table lookups for alpha average
462
0
          float vasum = (  VARBUF1(z_high,  y_low,  x_low).lane<3>()
463
0
                         - VARBUF1(z_high,  y_low, x_high).lane<3>()
464
0
                         - VARBUF1(z_high, y_high,  x_low).lane<3>()
465
0
                         + VARBUF1(z_high, y_high, x_high).lane<3>()) -
466
0
                        (  VARBUF1(z_low,   y_low,  x_low).lane<3>()
467
0
                         - VARBUF1(z_low,   y_low, x_high).lane<3>()
468
0
                         - VARBUF1(z_low,  y_high,  x_low).lane<3>()
469
0
                         + VARBUF1(z_low,  y_high, x_high).lane<3>());
470
471
0
          size_t out_index = z_dst * zdt + y_dst * ydt + x_dst;
472
0
          input_alpha_averages[out_index] = (vasum * alpha_rsamples);
473
0
        }
474
0
      }
475
0
    }
476
0
  }
477
0
  else
478
0
  {
479
0
    for (size_t y = 0; y < size_y; y++)
480
0
    {
481
0
      size_t y_src = y + kernel_radius_xy;
482
0
      size_t y_dst = y + offset_y;
483
0
      size_t y_low  = y_src - alpha_kernel_radius;
484
0
      size_t y_high = y_src + alpha_kernel_radius + 1;
485
486
0
      for (size_t x = 0; x < size_x; x++)
487
0
      {
488
0
        size_t x_src = x + kernel_radius_xy;
489
0
        size_t x_dst = x + offset_x;
490
0
        size_t x_low  = x_src - alpha_kernel_radius;
491
0
        size_t x_high = x_src + alpha_kernel_radius + 1;
492
493
        // Summed-area table lookups for alpha average
494
0
        float vasum = VARBUF1(0, y_low,  x_low).lane<3>()
495
0
                    - VARBUF1(0, y_low,  x_high).lane<3>()
496
0
                    - VARBUF1(0, y_high, x_low).lane<3>()
497
0
                    + VARBUF1(0, y_high, x_high).lane<3>();
498
499
0
        size_t out_index = y_dst * ydt + x_dst;
500
0
        input_alpha_averages[out_index] = (vasum * alpha_rsamples);
501
0
      }
502
0
    }
503
0
  }
504
0
}
505
506
/* See header for documentation. */
507
size_t init_compute_averages(
508
  const astcenc_image& img,
509
  size_t alpha_kernel_radius,
510
  const astcenc_swizzle& swz,
511
  avg_args& ag
512
0
) {
513
0
  size_t size_x = img.dim_x;
514
0
  size_t size_y = img.dim_y;
515
0
  size_t size_z = img.dim_z;
516
517
  // Compute maximum block size and from that the working memory buffer size
518
0
  size_t kernel_radius = alpha_kernel_radius;
519
0
  size_t kerneldim = 2 * kernel_radius + 1;
520
521
0
  bool have_z = size_z > 1;
522
0
  size_t max_blk_size_xy = have_z ? 16u : 32u;
523
524
0
  size_t max_blk_size_z_lim = have_z ? 16u : 1u;
525
0
  size_t max_blk_size_z = astc::min(size_z, max_blk_size_z_lim);
526
527
0
  size_t max_padsize_xy = max_blk_size_xy + kerneldim;
528
0
  size_t max_padsize_z = max_blk_size_z + (have_z ? kerneldim : 0);
529
530
  // Perform block-wise averages calculations across the image
531
  // Initialize fields which are not populated until later
532
0
  ag.arg.size_x = 0;
533
0
  ag.arg.size_y = 0;
534
0
  ag.arg.size_z = 0;
535
0
  ag.arg.offset_x = 0;
536
0
  ag.arg.offset_y = 0;
537
0
  ag.arg.offset_z = 0;
538
0
  ag.arg.work_memory = nullptr;
539
540
0
  ag.arg.img = &img;
541
0
  ag.arg.swz = swz;
542
0
  ag.arg.have_z = have_z;
543
0
  ag.arg.alpha_kernel_radius = alpha_kernel_radius;
544
545
0
  ag.img_size_x = size_x;
546
0
  ag.img_size_y = size_y;
547
0
  ag.img_size_z = size_z;
548
0
  ag.blk_size_xy = max_blk_size_xy;
549
0
  ag.blk_size_z = max_blk_size_z;
550
0
  ag.work_memory_size = 2 * max_padsize_xy * max_padsize_xy * max_padsize_z;
551
552
  // The parallel task count
553
0
  size_t tasks_z = astc::get_block_count_safe(size_z, max_blk_size_z);
554
0
  size_t tasks_y = astc::get_block_count_safe(size_y, max_blk_size_xy);
555
0
  return tasks_z * tasks_y;
556
0
}
557
558
#endif