Coverage Report

Created: 2026-10-28 06:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/work/vvenc/source/Lib/CommonLib/MCTF.cpp
Line
Count
Source
1
/* -----------------------------------------------------------------------------
2
The copyright in this software is being made available under the Clear BSD
3
License, included below. No patent rights, trademark rights and/or 
4
other Intellectual Property Rights other than the copyrights concerning 
5
the Software are granted under this license.
6
7
The Clear BSD License
8
9
Copyright (c) 2019-2026, Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. & The VVenC Authors.
10
All rights reserved.
11
12
Redistribution and use in source and binary forms, with or without modification,
13
are permitted (subject to the limitations in the disclaimer below) provided that
14
the following conditions are met:
15
16
     * Redistributions of source code must retain the above copyright notice,
17
     this list of conditions and the following disclaimer.
18
19
     * Redistributions in binary form must reproduce the above copyright
20
     notice, this list of conditions and the following disclaimer in the
21
     documentation and/or other materials provided with the distribution.
22
23
     * Neither the name of the copyright holder nor the names of its
24
     contributors may be used to endorse or promote products derived from this
25
     software without specific prior written permission.
26
27
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY
28
THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
29
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
30
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
31
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
32
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
33
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
34
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
35
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
36
IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
37
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
38
POSSIBILITY OF SUCH DAMAGE.
39
40
41
------------------------------------------------------------------------------------------- */
42
43
44
/** \file     MCTF.cpp
45
\brief    MCTF class
46
*/
47
48
#include "MCTF.h"
49
#include <math.h>
50
#include "CommonLib/Picture.h"
51
#include "CommonLib/dtrace_buffer.h"
52
#include "Utilities/NoMallocThreadPool.h"
53
54
namespace vvenc {
55
56
#ifdef TRACE_ENABLE_ITT
57
static __itt_string_handle* itt_handle_est = __itt_string_handle_create( "MCTF_est" );
58
static __itt_domain* itt_domain_MCTF_est   = __itt_domain_create( "MCTFEst" );
59
static __itt_string_handle* itt_handle_flt = __itt_string_handle_create( "MCTF_flt" );
60
static __itt_domain* itt_domain_MCTF_flt   = __itt_domain_create( "MCTFFlt" );
61
#endif
62
63
// ====================================================================================================================
64
// Constructor / destructor / initialization / destroy
65
// ====================================================================================================================
66
67
const double MCTF::m_chromaFactor     =  0.55;
68
const double MCTF::m_sigmaMultiplier  =  9.0;
69
const int MCTF::m_range               = VVENC_MCTF_RANGE-2;
70
const int MCTF::m_motionVectorFactor  = 16;
71
const int MCTF::m_padding             = MCTF_PADDING;
72
const int16_t MCTF::m_interpolationFilter8[16][8] =
73
{
74
  {   0,   0,   0,  64,   0,   0,   0,   0 },   //0
75
  {   0,   1,  -3,  64,   4,  -2,   0,   0 },   //1 -->-->
76
  {   0,   1,  -6,  62,   9,  -3,   1,   0 },   //2 -->
77
  {   0,   2,  -8,  60,  14,  -5,   1,   0 },   //3 -->-->
78
  {   0,   2,  -9,  57,  19,  -7,   2,   0 },   //4
79
  {   0,   3, -10,  53,  24,  -8,   2,   0 },   //5 -->-->
80
  {   0,   3, -11,  50,  29,  -9,   2,   0 },   //6 -->
81
  {   0,   3, -11,  44,  35, -10,   3,   0 },   //7 -->-->
82
  {   0,   1,  -7,  38,  38,  -7,   1,   0 },   //8
83
  {   0,   3, -10,  35,  44, -11,   3,   0 },   //9 -->-->
84
  {   0,   2,  -9,  29,  50, -11,   3,   0 },   //10-->
85
  {   0,   2,  -8,  24,  53, -10,   3,   0 },   //11-->-->
86
  {   0,   2,  -7,  19,  57,  -9,   2,   0 },   //12
87
  {   0,   1,  -5,  14,  60,  -8,   2,   0 },   //13-->-->
88
  {   0,   1,  -3,   9,  62,  -6,   1,   0 },   //14-->
89
  {   0,   0,  -2,   4,  64,  -3,   1,   0 }    //15-->-->
90
};
91
92
const int16_t MCTF::m_interpolationFilter4[16][4] =
93
{
94
  {  0, 64,  0,  0 },    //0
95
  { -2, 62,  4,  0 },    //1 -->-->
96
  { -2, 58, 10, -2 },    //2 -->
97
  { -4, 56, 14, -2 },    //3 -->-->
98
  { -4, 54, 16, -2 },    //4
99
  { -6, 52, 20, -2 },    //5 -->-->
100
  { -6, 46, 28, -4 },    //6 -->
101
  { -4, 42, 30, -4 },    //7 -->-->
102
  { -4, 36, 36, -4 },    //8
103
  { -4, 30, 42, -4 },    //9 -->-->
104
  { -4, 28, 46, -6 },    //10-->
105
  { -2, 20, 52, -6 },    //11-->-->
106
  { -2, 16, 54, -4 },    //12
107
  { -2, 14, 56, -4 },    //13-->-->
108
  { -2, 10, 58, -2 },    //14-->
109
  {  0,  4, 62, -2 },    //15-->-->
110
};
111
112
const double MCTF::m_refStrengths[2][6] = // min(..., {3 or 5} / (1 + 2 * |POC offset|))
113
{ // abs(POC offset)
114
  // 1       2       3       4       5       6
115
  { 0.84375, 0.6, 0.4286, 0.3333, 0.2727, 0.2308 }, // RA
116
  { 1.12500, 1.0, 0.7143, 0.5556, 0.4545, 0.3846 }  // LD
117
};
118
119
const int    MCTF::m_cuTreeThresh[4] = { 75, 60,     30, 15 };
120
const double MCTF::m_cuTreeCenter    =           45;
121
122
int motionErrorLumaInt( const Pel* org, const ptrdiff_t origStride, const Pel* buf, const ptrdiff_t buffStride, const int w, const int h, const int besterror )
123
0
{
124
0
  int error = 0;
125
126
0
  for( int y1 = 0; y1 < h; y1++ )
127
0
  {
128
0
    const Pel* origRowStart   = org + y1 * origStride;
129
0
    const Pel* bufferRowStart = buf + y1 * buffStride;
130
131
0
    for( int x1 = 0; x1 < w; x1 += 2 )
132
0
    {
133
0
      int diff = origRowStart[x1] - bufferRowStart[x1];
134
0
      error += diff * diff;
135
0
      diff = origRowStart[x1 + 1] - bufferRowStart[x1 + 1];
136
0
      error += diff * diff;
137
0
    }
138
0
    if( error > besterror )
139
0
    {
140
0
      return error;
141
0
    }
142
0
  }
143
144
0
  return error;
145
0
}
146
147
int motionErrorLumaFrac6( const Pel *org, const ptrdiff_t origStride, const Pel *buf, const ptrdiff_t buffStride, const int w, const int h, const int16_t *xFilter, const int16_t *yFilter, const int bitDepth, const int besterror )
148
0
{
149
0
  int error = 0;
150
0
  Pel tempArray[64 + 8][64];
151
0
  int sum, base;
152
0
  const Pel maxSampleValue = ( 1 << bitDepth ) - 1;
153
154
0
  for( int y1 = 1; y1 < h + 7; y1++ )
155
0
  {
156
0
    const int yOffset = y1 - 3;
157
0
    const Pel *sourceRow = buf + yOffset * buffStride;
158
0
    for( int x1 = 0; x1 < w; x1++ )
159
0
    {
160
0
      sum = 0;
161
0
      base = x1 - 3;
162
0
      const Pel *rowStart = sourceRow + base;
163
164
0
      sum += xFilter[1] * rowStart[1];
165
0
      sum += xFilter[2] * rowStart[2];
166
0
      sum += xFilter[3] * rowStart[3];
167
0
      sum += xFilter[4] * rowStart[4];
168
0
      sum += xFilter[5] * rowStart[5];
169
0
      sum += xFilter[6] * rowStart[6];
170
171
0
      sum = ( sum + ( 1 << 5 ) ) >> 6;
172
0
      sum = sum < 0 ? 0 : ( sum > maxSampleValue ? maxSampleValue : sum );
173
174
0
      tempArray[y1][x1] = sum;
175
0
    }
176
0
  }
177
178
0
  for( int y1 = 0; y1 < h; y1++ )
179
0
  {
180
0
    const Pel *origRow = org + y1 * origStride;
181
0
    for( int x1 = 0; x1 < w; x1++ )
182
0
    {
183
0
      sum = 0;
184
0
      sum += yFilter[1] * tempArray[y1 + 1][x1];
185
0
      sum += yFilter[2] * tempArray[y1 + 2][x1];
186
0
      sum += yFilter[3] * tempArray[y1 + 3][x1];
187
0
      sum += yFilter[4] * tempArray[y1 + 4][x1];
188
0
      sum += yFilter[5] * tempArray[y1 + 5][x1];
189
0
      sum += yFilter[6] * tempArray[y1 + 6][x1];
190
191
0
      sum = ( sum + ( 1 << 5 ) ) >> 6;
192
0
      sum = sum < 0 ? 0 : ( sum > maxSampleValue ? maxSampleValue : sum );
193
194
0
      error += ( sum - origRow[x1] ) * ( sum - origRow[x1] );
195
0
    }
196
0
    if( error > besterror )
197
0
    {
198
0
      return error;
199
0
    }
200
0
  }
201
202
0
  return error;
203
0
}
204
205
int motionErrorLumaFrac4( const Pel* org, const ptrdiff_t origStride, const Pel* buf, const ptrdiff_t buffStride, const int w, const int h, const int16_t* xFilter, const int16_t* yFilter, const int bitDepth, const int besterror )
206
0
{
207
0
  int error = 0;
208
0
  Pel tempArray[64 + 4][64];
209
0
  int sum, base;
210
0
  const Pel maxSampleValue = ( 1 << bitDepth ) - 1;
211
212
0
  for( int y1 = 0; y1 < h + 3; y1++ )
213
0
  {
214
0
    const int yOffset = y1 - 1;
215
0
    const Pel* sourceRow = buf + yOffset * buffStride;
216
0
    for( int x1 = 0; x1 < w; x1++ )
217
0
    {
218
0
      sum = 0;
219
0
      base = x1 - 1;
220
0
      const Pel* rowStart = sourceRow + base;
221
222
0
      sum += xFilter[0] * rowStart[0];
223
0
      sum += xFilter[1] * rowStart[1];
224
0
      sum += xFilter[2] * rowStart[2];
225
0
      sum += xFilter[3] * rowStart[3];
226
227
0
      sum = ( sum + ( 1 << 5 ) ) >> 6;
228
0
      sum = sum < 0 ? 0 : ( sum > maxSampleValue ? maxSampleValue : sum );
229
230
0
      tempArray[y1][x1] = sum;
231
0
    }
232
0
  }
233
234
0
  for( int y1 = 0; y1 < h; y1++ )
235
0
  {
236
0
    const Pel* origRow = org + y1 * origStride;
237
0
    for( int x1 = 0; x1 < w; x1++ )
238
0
    {
239
0
      sum = 0;
240
0
      sum += yFilter[0] * tempArray[y1 + 0][x1];
241
0
      sum += yFilter[1] * tempArray[y1 + 1][x1];
242
0
      sum += yFilter[2] * tempArray[y1 + 2][x1];
243
0
      sum += yFilter[3] * tempArray[y1 + 3][x1];
244
245
0
      sum = ( sum + ( 1 << 5 ) ) >> 6;
246
0
      sum = sum < 0 ? 0 : ( sum > maxSampleValue ? maxSampleValue : sum );
247
248
0
      error += ( sum - origRow[x1] ) * ( sum - origRow[x1] );
249
0
    }
250
0
    if( error > besterror )
251
0
    {
252
0
      return error;
253
0
    }
254
0
  }
255
256
0
  return error;
257
0
}
258
259
void applyFrac8Core_6Tap( const Pel* org, const ptrdiff_t origStride, Pel* dst, const ptrdiff_t dstStride, const int w, const int h, const int16_t* xFilter, const int16_t* yFilter, const int bitDepth )
260
0
{
261
0
  const int numFilterTaps   = 7;
262
0
  const int centreTapOffset = 3;
263
0
  const int maxValue        = ( 1 << bitDepth ) - 1;
264
265
0
  Pel tempArray[64 + numFilterTaps][64];
266
267
0
  for( int by = 1; by < h + numFilterTaps - 1; by++ )
268
0
  {
269
0
    const int yOffset = by - centreTapOffset;
270
0
    const Pel *sourceRow = org + yOffset * origStride;
271
0
    for( int bx = 0; bx < w; bx++ )
272
0
    {
273
0
      int base = bx - centreTapOffset;
274
0
      const Pel *rowStart = sourceRow + base;
275
276
0
      int sum = 0;
277
0
      sum += xFilter[1] * rowStart[1];
278
0
      sum += xFilter[2] * rowStart[2];
279
0
      sum += xFilter[3] * rowStart[3];
280
0
      sum += xFilter[4] * rowStart[4];
281
0
      sum += xFilter[5] * rowStart[5];
282
0
      sum += xFilter[6] * rowStart[6];
283
284
0
      sum = ( sum + ( 1 << 5 ) ) >> 6;
285
0
      tempArray[by][bx] = sum;
286
0
    }
287
0
  }
288
289
0
  Pel *dstRow = dst;
290
0
  for( int by = 0; by < h; by++, dstRow += dstStride )
291
0
  {
292
0
    Pel *dstPel = dstRow;
293
0
    for( int bx = 0; bx < w; bx++, dstPel++ )
294
0
    {
295
0
      int sum = 0;
296
297
0
      sum += yFilter[1] * tempArray[by + 1][bx];
298
0
      sum += yFilter[2] * tempArray[by + 2][bx];
299
0
      sum += yFilter[3] * tempArray[by + 3][bx];
300
0
      sum += yFilter[4] * tempArray[by + 4][bx];
301
0
      sum += yFilter[5] * tempArray[by + 5][bx];
302
0
      sum += yFilter[6] * tempArray[by + 6][bx];
303
304
0
      sum = ( sum + ( 1 << 5 ) ) >> 6;
305
0
      sum = sum < 0 ? 0 : ( sum > maxValue ? maxValue : sum );
306
0
      *dstPel = sum;
307
0
    }
308
0
  }
309
0
}
310
311
void applyFrac8Core_4Tap( const Pel* org, const ptrdiff_t origStride, Pel* dst, const ptrdiff_t dstStride, const int w, const int h, const int16_t* xFilter, const int16_t* yFilter, const int bitDepth )
312
0
{
313
0
  const int numFilterTaps   = 3;
314
0
  const int centreTapOffset = 1;
315
0
  const int maxValue        = ( 1 << bitDepth ) - 1;
316
317
0
  Pel tempArray[64 + numFilterTaps][64];
318
319
0
  for( int by = 0; by < h + numFilterTaps; by++ )
320
0
  {
321
0
    const int yOffset    = by - centreTapOffset;
322
0
    const Pel* sourceRow = org + yOffset * origStride;
323
324
0
    for( int bx = 0; bx < w; bx++ )
325
0
    {
326
0
      int base = bx - centreTapOffset;
327
0
      const Pel* rowStart = sourceRow + base;
328
329
0
      int sum = 0;
330
0
      sum += xFilter[0] * rowStart[0];
331
0
      sum += xFilter[1] * rowStart[1];
332
0
      sum += xFilter[2] * rowStart[2];
333
0
      sum += xFilter[3] * rowStart[3];
334
335
0
      sum = ( sum + ( 1 << 5 ) ) >> 6;
336
0
      tempArray[by][bx] = sum;
337
0
    }
338
0
  }
339
340
0
  Pel* dstRow = dst;
341
0
  for( int by = 0; by < h; by++, dstRow += dstStride )
342
0
  {
343
0
    Pel* dstPel = dstRow;
344
0
    for( int bx = 0; bx < w; bx++, dstPel++ )
345
0
    {
346
0
      int sum = 0;
347
0
      sum += yFilter[0] * tempArray[by + 0][bx];
348
0
      sum += yFilter[1] * tempArray[by + 1][bx];
349
0
      sum += yFilter[2] * tempArray[by + 2][bx];
350
0
      sum += yFilter[3] * tempArray[by + 3][bx];
351
352
0
      sum = ( sum + ( 1 << 5 ) ) >> 6;
353
0
      sum = sum < 0 ? 0 : ( sum > maxValue ? maxValue : sum );
354
0
      *dstPel = sum;
355
0
    }
356
0
  }
357
0
}
358
359
inline static float fastExp( float n, float d )
360
0
{
361
  // using the e^x ~= ( 1 + x/n )^n for n -> inf
362
0
  float x = 1.0f + n / ( d * 1024 );
363
0
  x *= x; x *= x; x *= x; x *= x;
364
0
  x *= x; x *= x; x *= x; x *= x;
365
0
  x *= x; x *= x;
366
0
  return x;
367
0
}
368
369
static const int32_t xSzm[6] = {0, 1, 20, 336, 5440, 87296};
370
371
// works for bit depths up to incl. 12 and power-of-2 block dimensions in both directions
372
void applyPlanarCorrectionCore( const Pel* refPel, const ptrdiff_t refStride, Pel* dstPel, const ptrdiff_t dstStride, const int32_t w, const int32_t h, const ClpRng& clpRng, const uint16_t motionError )
373
0
{
374
0
  const int32_t blockSize = w * h;
375
0
  const int32_t log2Width = floorLog2 (w);
376
0
  const int32_t maxPelVal = clpRng.max();
377
0
  const int32_t mWeight   = std::min (512u, (uint32_t) motionError * (uint32_t) motionError);
378
0
  const int32_t xSum      = (blockSize * (w - 1)) >> 1;
379
0
  int32_t x1yzm = 0,  x2yzm = 0,  ySum = 0;
380
0
  int32_t b0, b1, b2;
381
0
  int64_t numer, denom;
382
383
0
  for (int32_t y = 0; y < h; y++) // sum up dot-products between indices and sample diffs
384
0
  {
385
0
    for (int32_t x = 0; x < w; x++)
386
0
    {
387
0
      const Pel* pDst = dstPel + y * dstStride + x;
388
0
      const Pel* pRef = refPel + y * refStride + x;
389
0
      const int32_t z = *pDst - *pRef;
390
391
0
      x1yzm += x * z;  x2yzm += y * z;  ySum += z;
392
0
    }
393
0
  }
394
395
0
  denom = blockSize * xSzm[log2Width]; // plane-fit parameters, in fixed-point arithmetic
396
0
  numer = (int64_t) mWeight * ((int64_t) x1yzm * blockSize - xSum * ySum);
397
0
  b1 = int32_t ((numer < 0 ? numer - (denom >> 1) : numer + (denom >> 1)) / denom);
398
0
  b1 = (b1 < INT16_MIN ? INT16_MIN : (b1 > INT16_MAX ? INT16_MAX : b1));
399
0
  numer = (int64_t) mWeight * ((int64_t) x2yzm * blockSize - xSum * ySum);
400
0
  b2 = int32_t ((numer < 0 ? numer - (denom >> 1) : numer + (denom >> 1)) / denom);
401
0
  b2 = (b2 > INT16_MAX ? INT16_MAX : (b2 < INT16_MIN ? INT16_MIN : b2));
402
0
  b0 = (mWeight * ySum - (b1 + b2) * xSum + (blockSize >> 1)) >> (log2Width << 1);
403
404
0
  if (b0 == 0 && b1 == 0 && b2 == 0) return;
405
406
0
  for (int32_t y = 0; y < h; y++) // perform deblocking by adding fitted correction plane
407
0
  {
408
0
    for (int32_t x = 0; x < w; x++)
409
0
    {
410
0
      Pel* const pDst = dstPel + y * dstStride + x;
411
0
      const int32_t p = (b0 + b1 * x + b2 * y + 256) >> 9; // fixed-point plane corrector
412
0
      const int32_t z = *pDst - p;
413
414
0
      *pDst = Pel (z < 0 ? 0 : (z > maxPelVal ? maxPelVal : z));
415
0
    }
416
0
  }
417
0
}
418
419
void applyBlockCore( const CPelBuf& src, PelBuf& dst, const CompArea& blk, const ClpRng& clpRng, const Pel** correctedPics, int numRefs, const int* verror, const double* refStrenghts, double weightScaling, double sigmaSq )
420
0
{
421
0
  const int         w = blk.width;
422
0
  const int         h = blk.height;
423
0
  const int        bx = blk.x;
424
0
  const int        by = blk.y;
425
426
0
  const ptrdiff_t srcStride = src.stride;
427
0
  const ptrdiff_t dstStride = dst.stride;
428
429
0
  const Pel *srcPel = src.bufAt( bx, by );
430
0
        Pel *dstPel = dst.bufAt( bx, by );
431
432
0
  const Pel maxSampleValue = clpRng.max();
433
434
0
  int vnoise[2 * VVENC_MCTF_RANGE] = { 0, };
435
0
  float vsw [2 * VVENC_MCTF_RANGE] = { 0.0f, };
436
0
  float vww [2 * VVENC_MCTF_RANGE] = { 0.0f, };
437
438
0
  int minError = INT32_MAX;
439
440
0
  for( int i = 0; i < numRefs; i++ )
441
0
  {
442
0
    int64_t variance = 0, diffsum = 0;
443
0
    const ptrdiff_t refStride = w;
444
0
    const Pel *     refPel    = correctedPics[i];
445
0
    for( int y1 = 0; y1 < h; y1++ )
446
0
    {
447
0
      for( int x1 = 0; x1 < w; x1++ )
448
0
      {
449
0
        const Pel pix = *( srcPel + srcStride * y1 + x1 );
450
0
        const Pel ref = *( refPel + refStride * y1 + x1 );
451
452
0
        const int diff = pix - ref;
453
0
        variance += diff * diff;
454
0
        if( x1 != w - 1 )
455
0
        {
456
0
          const Pel pixR = *( srcPel + srcStride * y1 + x1 + 1 );
457
0
          const Pel refR = *( refPel + refStride * y1 + x1 + 1 );
458
0
          const int diffR = pixR - refR;
459
0
          diffsum += ( diffR - diff ) * ( diffR - diff );
460
0
        }
461
0
        if( y1 != h - 1 )
462
0
        {
463
0
          const Pel pixD = *( srcPel + srcStride * y1 + x1 + srcStride );
464
0
          const Pel refD = *( refPel + refStride * y1 + x1 + refStride );
465
0
          const int diffD = pixD - refD;
466
0
          diffsum += ( diffD - diff ) * ( diffD - diff );
467
0
        }
468
0
      }
469
0
    }
470
0
    variance *= (int64_t) 1 << (2*(10-clpRng.bd));
471
0
    diffsum  *= (int64_t) 1 << (2*(10-clpRng.bd));
472
0
    const int cntV = w * h;
473
0
    const int cntD = 2 * cntV - w - h;
474
0
    vnoise[i] = ( int ) round( ( 15.0 * cntD / cntV * variance + 5.0 ) / ( diffsum + 5.0 ) );
475
0
    minError = std::min( minError, verror[i] );
476
0
  }
477
478
0
  for( int i = 0; i < numRefs; i++ )
479
0
  {
480
0
    const int error = verror[i];
481
0
    const int noise = vnoise[i];
482
0
    float ww = 1, sw = 1;
483
0
    ww *= ( noise < 25 ) ? 1.0 : 0.6;
484
0
    sw *= ( noise < 25 ) ? 1.0 : 0.8;
485
0
    ww *= ( error < 50 ) ? 1.2 : ( ( error > 100 ) ? 0.6 : 1.0 );
486
0
    sw *= ( error < 50 ) ? 1.0 : 0.8;
487
0
    ww *= ( ( minError + 1.0 ) / ( error + 1.0 ) );
488
489
0
    vww[i] = ww * weightScaling * refStrenghts[i];
490
0
    vsw[i] = sw * 2 * sigmaSq;
491
0
  }
492
493
0
  for( int y = 0; y < h; y++ )
494
0
  {
495
0
    for( int x = 0; x < w; x++ )
496
0
    {
497
0
      const Pel orgVal  = *( srcPel + srcStride * y + x );
498
0
      float temporalWeightSum = 1.0;
499
0
      float newVal = ( float ) orgVal;
500
501
0
      for( int i = 0; i < numRefs; i++ )
502
0
      {
503
0
        const Pel* pCorrectedPelPtr = correctedPics[i] + y * w + x;
504
0
        const int    refVal = *pCorrectedPelPtr;
505
0
        const int    diff   = refVal - orgVal;
506
0
        const float  diffSq = diff * diff;
507
508
0
        float weight = vww[i] * fastExp( -diffSq, vsw[i] );
509
0
        newVal += weight * refVal;
510
0
        temporalWeightSum += weight;
511
0
      }
512
0
      newVal /= temporalWeightSum;
513
0
      Pel sampleVal = ( Pel ) ( newVal + 0.5 );
514
0
      sampleVal = ( sampleVal < 0 ? 0 : ( sampleVal > maxSampleValue ? maxSampleValue : sampleVal ) );
515
0
      *( dstPel + dstStride * y + x ) = sampleVal;
516
0
    }
517
0
  }
518
0
}
519
520
double calcVarCore( const Pel* org, const ptrdiff_t origStride, const int w, const int h )
521
0
{
522
  // calculate average
523
0
  int avg = 0;
524
0
  for( int y1 = 0; y1 < h; y1++ )
525
0
  {
526
0
    for( int x1 = 0; x1 < w; x1++ )
527
0
    {
528
0
      avg = avg + *( org + x1 + y1 * origStride );
529
0
    }
530
0
  }
531
0
  avg <<= 4;
532
0
  avg = avg / ( w * h );
533
534
  // calculate variance
535
0
  int64_t variance = 0;
536
0
  for( int y1 = 0; y1 < h; y1++ )
537
0
  {
538
0
    for( int x1 = 0; x1 < w; x1++ )
539
0
    {
540
0
      int pix = *( org + x1 + y1 * origStride ) << 4;
541
0
      variance = variance + ( pix - avg ) * ( pix - avg );
542
0
    }
543
0
  }
544
545
0
  return variance / 256.0;
546
0
}
547
548
MCTF::MCTF( bool enableOpt )
549
1.49k
  : m_encCfg     ( nullptr )
550
1.49k
  , m_threadPool ( nullptr )
551
1.49k
  , m_isFinalPass( true )
552
1.49k
  , m_filterPoc  ( 0 )
553
1.49k
  , m_lastPicIn  ( nullptr )
554
1.49k
{
555
1.49k
  m_motionErrorLumaIntX     = motionErrorLumaInt;
556
1.49k
  m_motionErrorLumaInt8     = motionErrorLumaInt;
557
1.49k
  m_motionErrorLumaFracX[0] = motionErrorLumaFrac6;
558
1.49k
  m_motionErrorLumaFrac8[0] = motionErrorLumaFrac6;
559
1.49k
  m_motionErrorLumaFracX[1] = motionErrorLumaFrac4;
560
1.49k
  m_motionErrorLumaFrac8[1] = motionErrorLumaFrac4;
561
1.49k
  m_applyFrac[0][0]         = applyFrac8Core_6Tap;
562
1.49k
  m_applyFrac[0][1]         = applyFrac8Core_4Tap;
563
1.49k
  m_applyFrac[1][0]         = applyFrac8Core_6Tap;
564
1.49k
  m_applyFrac[1][1]         = applyFrac8Core_4Tap;
565
1.49k
  m_applyPlanarCorrection   = applyPlanarCorrectionCore;
566
1.49k
  m_applyBlock              = applyBlockCore;
567
1.49k
  m_calcVar                 = calcVarCore;
568
569
1.49k
  if( enableOpt )
570
1.49k
  {
571
#if defined( TARGET_SIMD_X86 ) && ENABLE_SIMD_OPT_MCTF
572
    initMCTF_X86();
573
#endif
574
#if defined( TARGET_SIMD_ARM ) && ENABLE_SIMD_OPT_MCTF
575
    initMCTF_ARM();
576
#endif
577
1.49k
  }
578
1.49k
}
579
580
MCTF::~MCTF()
581
1.49k
{
582
1.49k
}
583
584
void MCTF::init( const VVEncCfg& encCfg, bool isFinalPass, NoMallocThreadPool* threadPool )
585
1.49k
{
586
1.49k
  CHECK( encCfg.m_vvencMCTF.numFrames != encCfg.m_vvencMCTF.numStrength, "should have been checked before" );
587
588
1.49k
  m_encCfg      = &encCfg;
589
1.49k
  m_threadPool  = threadPool;
590
1.49k
  m_isFinalPass = isFinalPass;
591
1.49k
  m_filterPoc   = 0;
592
1.49k
  m_area        = Area( 0, 0, m_encCfg->m_PadSourceWidth, m_encCfg->m_PadSourceHeight );
593
594
  // TLayer (TL) dependent definition of drop frames: TL = 4,  TL = 3,  TL = 2,  TL = 1,  TL = 0
595
1.49k
  const static int sMCTFSpeed[5] { 0, 0, ((3<<12) + (2<<9) + (2<<6) + (0<<3) + 0),   ((3<<12) + (2<<9) + (2<<6) + (0<<3) + 0),   ((3<<12) + (3<<9) + (3<<6) + (2<<3) + 2) };
596
597
1.49k
  m_MCTFSpeedVal     = sMCTFSpeed[ m_encCfg->m_vvencMCTF.MCTFSpeed ];
598
1.49k
  m_lowResFltSearch  = m_encCfg->m_vvencMCTF.MCTFSpeed > 0;
599
1.49k
  m_searchPttrn      = m_encCfg->m_vvencMCTF.MCTFSpeed > 0 ? ( m_encCfg->m_vvencMCTF.MCTFSpeed >= 3 ? 2 : 1 ) : 0;
600
1.49k
  m_mctfUnitSize     = m_encCfg->m_vvencMCTF.MCTFUnitSize;
601
1.49k
}
602
603
// ====================================================================================================================
604
// Public member functions
605
// ====================================================================================================================
606
607
608
void MCTF::initPicture( Picture* pic )
609
1.49k
{
610
1.49k
  pic->getOrigBuf().extendBorderPel( MCTF_FRAME_MARGIN, true );
611
1.49k
  pic->setSccFlags( m_encCfg );
612
1.49k
}
613
614
void MCTF::processPictures( const PicList& picList, AccessUnitList& auList, PicList& doneList, PicList& freeList )
615
10.4k
{
616
  // ensure this is only processed if necessary 
617
10.4k
  if( picList.empty() || ( m_lastPicIn == picList.back() && ! picList.back()->isFlush ))
618
0
  {
619
0
    return;
620
0
  }
621
10.4k
  m_lastPicIn = picList.back();
622
623
  // filter one picture (either all or up to frames to be encoded)
624
10.4k
  if( picList.size()
625
10.4k
      && m_filterPoc <= picList.back()->poc
626
1.49k
      && ( m_encCfg->m_framesToBeEncoded <= 0 || m_filterPoc < m_encCfg->m_framesToBeEncoded ) )
627
1.49k
  {
628
    // setup fifo of pictures to be filtered
629
1.49k
    std::deque<Picture*> picFifo;
630
1.49k
    int filterIdx = 0;
631
1.49k
    for( auto pic : picList )
632
1.49k
    {
633
1.49k
      const int minPoc = m_filterPoc - VVENC_MCTF_RANGE;
634
1.49k
      const int maxPoc = m_encCfg->m_vvencMCTF.MCTFFutureReference ? m_filterPoc + VVENC_MCTF_RANGE : m_filterPoc;
635
1.49k
      if( pic->poc >= minPoc && pic->poc <= maxPoc )
636
1.49k
      {
637
1.49k
        picFifo.push_back( pic );
638
1.49k
        if( pic->poc < m_filterPoc )
639
0
        {
640
0
          filterIdx += 1;
641
0
        }
642
1.49k
      }
643
1.49k
    }
644
1.49k
    CHECK( picFifo.empty(), "MCTF: no pictures to be filtered found" );
645
1.49k
    CHECK( filterIdx >= (int)picFifo.size(), "MCTF: picture filter error" );
646
1.49k
    CHECK( picFifo[ filterIdx ]->poc != m_filterPoc, "MCTF: picture filter error" );
647
    // filter picture (when more than 1 picture is available for processing)
648
1.49k
    if( picFifo.size() > 1 )
649
0
    {
650
0
      filter( picFifo, filterIdx );
651
0
    }
652
    // set picture done
653
1.49k
    doneList.push_back( picFifo[ filterIdx ] );
654
1.49k
  }
655
656
  // mark pictures not needed anymore
657
10.4k
  for( auto pic : picList )
658
10.4k
  {
659
10.4k
    if( pic->poc > m_filterPoc - VVENC_MCTF_RANGE )
660
8.95k
      break;
661
1.49k
    freeList.push_back( pic );
662
1.49k
  }
663
10.4k
  m_filterPoc += 1;
664
10.4k
}
665
666
void MCTF::motionEstimationMCTF(Picture* curPic, std::deque<TemporalFilterSourcePicInfo> &srcFrameInfo, const PelStorage& origBuf, PelStorage& origSubsampled2, PelStorage& origSubsampled4, PelStorage& origSubsampled8, std::vector<double> &mvErr, double &minError, bool addLevel, bool calcErr)
667
0
{
668
0
  srcFrameInfo.push_back(TemporalFilterSourcePicInfo());
669
0
  TemporalFilterSourcePicInfo& srcPic = srcFrameInfo.back();
670
671
0
  const int wInBlks = (m_area.width + m_mctfUnitSize - 1) / m_mctfUnitSize;
672
0
  const int hInBlks = (m_area.height + m_mctfUnitSize - 1) / m_mctfUnitSize;
673
674
0
  srcPic.picBuffer.createFromBuf(curPic->getOrigBuf());
675
0
  srcPic.mvs.allocate(wInBlks, hInBlks);
676
0
  srcPic.index = std::min(5, std::abs(curPic->poc - m_filterPoc) - 1);
677
678
679
0
  {
680
0
    const int width = m_area.width;
681
0
    const int height = m_area.height;
682
0
    Array2D<MotionVector> mv_0(width / (m_mctfUnitSize * 8) + 1, height / (m_mctfUnitSize * 8) + 1);
683
0
    Array2D<MotionVector> mv_1(width / (m_mctfUnitSize * 4) + 1, height / (m_mctfUnitSize * 4) + 1);
684
0
    Array2D<MotionVector> mv_2(width / (m_mctfUnitSize * 2) + 1, height / (m_mctfUnitSize * 2) + 1);
685
686
0
    PelStorage bufferSub2;
687
0
    PelStorage bufferSub4;
688
689
0
    subsampleLuma(srcPic.picBuffer, bufferSub2);
690
0
    subsampleLuma(bufferSub2, bufferSub4);
691
692
0
    if (addLevel)
693
0
    {
694
0
      Array2D<MotionVector> mv_m(width / (m_mctfUnitSize * 16) + 1, height / (m_mctfUnitSize * 16) + 1);
695
0
      PelStorage bufferSub8;
696
0
      subsampleLuma(bufferSub4, bufferSub8);
697
0
      motionEstimationLuma(mv_m, origSubsampled8, bufferSub8, m_padding, 2 * m_mctfUnitSize);
698
0
      motionEstimationLuma(mv_0, origSubsampled4, bufferSub4, m_padding, 2 * m_mctfUnitSize, &mv_m, 2);
699
0
    }
700
0
    else
701
0
    {
702
0
      motionEstimationLuma(mv_0, origSubsampled4, bufferSub4, m_padding, 2 * m_mctfUnitSize);
703
0
    }
704
0
    motionEstimationLuma(mv_1, origSubsampled2, bufferSub2, m_padding, 2 * m_mctfUnitSize, &mv_0, 2);
705
0
    motionEstimationLuma(mv_2, origBuf, srcPic.picBuffer, MCTF_FRAME_MARGIN, 2 * m_mctfUnitSize, &mv_1, 2);
706
707
0
    motionEstimationLuma(srcPic.mvs, origBuf, srcPic.picBuffer, MCTF_FRAME_MARGIN, m_mctfUnitSize, &mv_2, 1, true);
708
709
0
    if (calcErr)
710
0
    {
711
0
      double sumErr = 0.0;
712
0
      for (int y = 0; y < srcPic.mvs.h(); y++) // going over ref pic in block steps
713
0
      {
714
0
        for (int x = 0; x < srcPic.mvs.w(); x++)
715
0
        {
716
0
          sumErr += srcPic.mvs.get(x, y).error;
717
0
        }
718
0
      }
719
0
      double S = 1.0 / (srcPic.mvs.w() * srcPic.mvs.h());
720
0
      mvErr.push_back(sumErr * S);
721
0
      minError = std::min(minError, sumErr * S);
722
0
    }
723
0
  }
724
0
}
725
726
void MCTF::filter( const std::deque<Picture*>& picFifo, int filterIdx )
727
0
{
728
0
  PROFILER_SCOPE_AND_STAGE( 1, g_timeProfiler, P_MCTF );
729
730
0
  Picture* pic = picFifo[ filterIdx ];
731
732
  // first-pass temporal downsampling
733
0
  if( ! m_isFinalPass && pic->gopEntry->m_skipFirstPass )
734
0
  {
735
0
    return;
736
0
  }
737
738
0
  const int mctfIdx            = pic->gopEntry ? pic->gopEntry->m_mctfIndex : -1;
739
0
  const double overallStrength = mctfIdx >= 0 ? m_encCfg->m_vvencMCTF.MCTFStrengths[ mctfIdx ] : -1.0;
740
0
  double   meanRmsAcrossPic    = 0.0;
741
0
  uint64_t sumSRmsAcrossPic    = 0;
742
0
  uint16_t nMax = 0, maxRmsCTU = 0;
743
0
  bool  isFilterThisFrame      = mctfIdx >= 0;
744
745
0
  int dropFrames = ( m_encCfg->m_usePerceptQPA ? VVENC_MCTF_RANGE >> 1 : 0 );
746
0
  if( mctfIdx >= 0 )
747
0
  {
748
0
    const int idxTLayer = m_encCfg->m_vvencMCTF.numFrames - (mctfIdx + 1);
749
0
    const int threshold = (m_MCTFSpeedVal >> (idxTLayer * 3)) & 7;
750
751
0
    dropFrames          = std::min(VVENC_MCTF_RANGE, threshold);
752
0
    isFilterThisFrame   = threshold < VVENC_MCTF_RANGE;
753
0
  }
754
755
0
  const int filterFrames = VVENC_MCTF_RANGE - 2 - dropFrames;
756
757
0
  int dropFramesFront = std::min( std::max(                                          filterIdx - filterFrames, 0 ), dropFrames + 2 );
758
0
  int dropFramesBack  = std::min( std::max( static_cast<int>( picFifo.size() ) - 1 - filterIdx - filterFrames, 0 ), dropFrames + 2 );
759
760
0
  if( !pic->useMCTF && !pic->gopEntry->m_isStartOfGop )
761
0
  {
762
0
    isFilterThisFrame = false;
763
0
  }
764
765
0
  if ( isFilterThisFrame )
766
0
  {
767
0
    bool  useMCTFadaptation = true;
768
0
    const bool condAddLevel = useMCTFadaptation && m_area.width >= 1920;
769
0
    std::vector<double> mvErr;
770
0
    double minError = MAX_DOUBLE;
771
772
0
    const PelStorage& origBuf = pic->getOrigBuffer();
773
0
          PelStorage& fltrBuf = pic->getFilteredOrigBuffer();
774
775
    // subsample original picture so it only needs to be done once
776
0
    PelStorage origSubsampled2;
777
0
    PelStorage origSubsampled4;
778
0
    PelStorage origSubsampled8;
779
0
    subsampleLuma( origBuf,         origSubsampled2 );
780
0
    subsampleLuma( origSubsampled2, origSubsampled4 );
781
0
    if (condAddLevel)
782
0
    {
783
0
      subsampleLuma(origSubsampled4, origSubsampled8);
784
0
    }
785
786
    // determine motion vectors
787
0
    std::deque<TemporalFilterSourcePicInfo> srcFrameInfo;
788
0
    for ( int i = dropFramesFront; i < picFifo.size() - dropFramesBack; i++ )
789
0
    {
790
0
      Picture* curPic = picFifo[ i ];
791
0
      if ( curPic->poc == m_filterPoc )
792
0
      {
793
0
        continue;
794
0
      }
795
0
      motionEstimationMCTF(curPic, srcFrameInfo, origBuf, origSubsampled2, origSubsampled4, origSubsampled8 ,mvErr, minError, condAddLevel, useMCTFadaptation);
796
0
    }
797
798
0
    int lastIndexRefFr = -1;
799
0
    if ((m_encCfg->m_vvencMCTF.MCTFSpeed < 4) && (minError > 80))
800
0
    {
801
0
      useMCTFadaptation = false;
802
0
    }
803
0
    if (useMCTFadaptation && minError)
804
0
    {
805
0
      const double errThr = 0.75 * minError * srcFrameInfo.size();
806
0
      int avgErrCond = 0;
807
0
      int minErrCond = 0;
808
0
      double factErr = m_encCfg->m_vvencMCTF.MCTFSpeed < 4 ? 1.0 : 2.0 ;
809
0
      double SizeThi = m_encCfg->m_vvencMCTF.MCTFSpeed < 4  ? filterFrames + 1 : 3.0;
810
811
0
      for (const double& framMvErr : mvErr)
812
0
      {
813
0
        if (factErr * framMvErr > errThr)
814
0
        {
815
0
          avgErrCond++;
816
0
        }
817
0
        if (framMvErr > SizeThi * minError)
818
0
        {
819
0
          minErrCond++;
820
0
        }
821
0
      }
822
0
      int newFilterFrames = minErrCond ? filterFrames : (filterFrames + 2 - avgErrCond);
823
0
      if (filterFrames <= 2 && newFilterFrames > 3)   newFilterFrames = 3;
824
825
0
      for (int curIdx = filterFrames + 1; (curIdx < newFilterFrames + 1)&&((lastIndexRefFr == -1)); curIdx++)
826
0
      {
827
0
        for (int i = 0; i < picFifo.size(); i++)
828
0
        {
829
0
          Picture* curPic = picFifo[i];
830
0
          if (curIdx == std::abs(curPic->poc - m_filterPoc))
831
0
          {
832
0
            motionEstimationMCTF(curPic, srcFrameInfo, origBuf, origSubsampled2, origSubsampled4, origSubsampled8, mvErr, minError, condAddLevel, m_encCfg->m_vvencMCTF.MCTFSpeed == 4);
833
0
            if (m_encCfg->m_vvencMCTF.MCTFSpeed == 4)
834
0
            {
835
0
              int nSize = (int(srcFrameInfo.size()) & 1) + int(srcFrameInfo.size());
836
0
              const double errThrcur = 0.75 * minError * nSize;
837
0
              if (mvErr.back() > errThrcur)
838
0
              {
839
0
                lastIndexRefFr = curIdx;
840
0
                break;
841
0
              }
842
0
            }
843
0
          }
844
0
        }
845
0
      }
846
0
      if ((lastIndexRefFr != -1))
847
0
      {
848
0
        for (auto it = srcFrameInfo.begin(); it != srcFrameInfo.end(); )
849
0
        {
850
0
          if ((it->index + 1) >= lastIndexRefFr)
851
0
          {
852
0
            it = srcFrameInfo.erase(it);
853
0
          }
854
0
          else
855
0
          {
856
0
            ++it;
857
0
          }
858
0
        }
859
0
      }
860
0
    }
861
862
    // filter
863
0
    if( pic->useMCTF )
864
0
    {
865
0
      fltrBuf.create( m_encCfg->m_internChromaFormat, m_area, 0, MCTF_FRAME_MARGIN );
866
0
      bilateralFilter( origBuf, srcFrameInfo, fltrBuf, overallStrength );
867
0
    }
868
869
    // The film grain analysis compares the original with a filtered copy of fixed strength, independent of the
870
    // filtering for the encoding, as the dedicated temporal filter of the VTM analysis does (strength 1.5). It analyses
871
    // the first picture of every GOP, whose motion has just been estimated.
872
0
    if( m_encCfg->m_fga && m_isFinalPass && pic->gopEntry->m_isStartOfGop )
873
0
    {
874
0
      static constexpr double FGA_FILTER_STRENGTH = 1.5;
875
0
      if( pic->useMCTF && overallStrength == FGA_FILTER_STRENGTH )
876
0
      {
877
        // the picture filtered for the encoding is exactly the one the analysis needs (from QP 20 on)
878
0
        pic->m_picShared->fgaUsesFilteredBuf() = true;
879
0
      }
880
0
      else
881
0
      {
882
0
        PelStorage& fgaBuf = pic->m_picShared->getFgaFilteredBuf();
883
0
        fgaBuf.create( m_encCfg->m_internChromaFormat, m_area );
884
0
        bilateralFilter( origBuf, srcFrameInfo, fgaBuf, FGA_FILTER_STRENGTH );
885
0
      }
886
887
      // The filtered picture gives the flat areas. With the improvements (--fga 2) the grain itself is measured from the
888
      // temporal residual of the picture and its neighbours, which are only available here.
889
0
      Picture* prevPic = nullptr;
890
0
      Picture* nextPic = nullptr;
891
0
      if( m_encCfg->m_fga >= 2 )
892
0
      {
893
0
        for( Picture* p : picFifo )
894
0
        {
895
0
          prevPic = p->poc == pic->poc - 1 ? p : prevPic;
896
0
          nextPic = p->poc == pic->poc + 1 ? p : nextPic;
897
0
        }
898
0
      }
899
0
      const CPelUnitBuf prevBuf = prevPic ? CPelUnitBuf( prevPic->getOrigBuffer() ) : CPelUnitBuf();
900
0
      const CPelUnitBuf nextBuf = nextPic ? CPelUnitBuf( nextPic->getOrigBuffer() ) : CPelUnitBuf();
901
0
      pic->m_picShared->fgaResidualWeight() = m_fgaResidual.compute( origBuf, prevPic ? &prevBuf : nullptr, nextPic ? &nextBuf : nullptr,
902
0
                                                                     pic->m_picShared->getFgaResidualBuf() );
903
0
    }
904
905
0
    if( m_encCfg->hasBlockQpOffsets() || m_encCfg->m_usePerceptQPA || pic->gopEntry->m_isStartOfGop )
906
0
    {
907
0
      const int ctuSize        = m_encCfg->m_bimCtuSize;
908
0
      const int widthInCtus    = ( m_area.width  + ctuSize - 1 ) / ctuSize;
909
0
      const int heightInCtus   = ( m_area.height + ctuSize - 1 ) / ctuSize;
910
0
      const int numCtu         = widthInCtus * heightInCtus;
911
0
      const int ctuBlocks      = ctuSize / m_mctfUnitSize;
912
913
0
      std::vector<double> sumError( numCtu * 2, 0 );
914
0
      std::vector<uint32_t> sumRMS( numCtu * 2, 0 ); // RMS of motion estimation error
915
0
      std::vector<uint16_t> maxRMS( numCtu * 2, 0 ); // maximum block estimation error
916
0
      std::vector<double> blkCount( numCtu * 2, 0 );
917
918
0
      int distFactor[2] = { 3,3 };
919
920
0
      for( auto& srcPic : srcFrameInfo )
921
0
      {
922
0
        if( srcPic.index >= 2 )
923
0
        {
924
0
          continue;
925
0
        }
926
927
0
        int dist = srcPic.index;
928
0
        distFactor[dist]--;
929
930
0
        for( int y = 0; y < srcPic.mvs.h(); y++ ) // going over ref pic in block steps
931
0
        {
932
0
          for( int x = 0; x < srcPic.mvs.w(); x++ )
933
0
          {
934
0
            const int ctuX    = x / ctuBlocks;
935
0
            const int ctuY    = y / ctuBlocks;
936
0
            const int ctuId   = ctuY * widthInCtus + ctuX;
937
0
            const auto& mvBlk = srcPic.mvs.get( x, y );
938
0
            sumError[dist * numCtu + ctuId] += mvBlk.error;
939
0
            sumRMS  [dist * numCtu + ctuId] += mvBlk.rmsme;
940
0
            maxRMS  [dist * numCtu + ctuId] = std::max( maxRMS[dist * numCtu + ctuId], mvBlk.rmsme );
941
0
            blkCount[dist * numCtu + ctuId] += mvBlk.overlap;
942
0
          }
943
0
        }
944
0
      }
945
946
0
      if( distFactor[0] < 3 && distFactor[1] < 3 && ( m_encCfg->m_usePerceptQPA || pic->gopEntry->m_isStartOfGop ) )
947
0
      {
948
0
        const double bd12bScale = double (m_encCfg->m_internalBitDepth[CH_L] < 12 ? 4 : 1);
949
950
0
        for( int i = 0; i < numCtu; i++ ) // start noise estimation with motion errors
951
0
        {
952
0
          const Position pos ((i % widthInCtus) * ctuSize, (i / widthInCtus) * ctuSize);
953
0
          const CompArea ctuArea  = clipArea (CompArea (COMP_Y, pic->chromaFormat, Area (pos.x, pos.y, ctuSize, ctuSize)), pic->Y());
954
0
          const unsigned avgIndex = pic->getOrigBuf (ctuArea).getAvg() >> (m_encCfg->m_internalBitDepth[CH_L] - 3); // one of 8 mean level regions
955
0
          double meanInCTU;
956
957
0
          sumRMS[i] = std::min (sumRMS[i], sumRMS[i + numCtu]);
958
0
          meanInCTU = bd12bScale * sumRMS[i] / blkCount[i];
959
0
          meanRmsAcrossPic += meanInCTU;
960
0
          if (meanInCTU < pic->m_picShared->m_minNoiseLevels[avgIndex])
961
0
          {
962
0
            pic->m_picShared->m_minNoiseLevels[avgIndex] = uint8_t (0.5 + meanInCTU); // scaled to 12 bit, see filterAndCalculateAverageActivity()
963
0
          }
964
965
0
          maxRMS[i] = std::min (maxRMS[i], maxRMS[i + numCtu]);
966
0
          maxRmsCTU = std::max (maxRmsCTU, maxRMS[i]);
967
0
          sumSRmsAcrossPic += (uint64_t) maxRMS[i] * maxRMS[i];
968
0
          if (maxRMS[i] > 0)
969
0
          {
970
0
            nMax++; // count all CTUs with non-zero motion error (excludes e.g. black borders). CTU with the motion error peak is subtracted below
971
0
          }
972
0
        }
973
0
        pic->m_picShared->m_picMotEstError = uint16_t (0.5 + meanRmsAcrossPic / numCtu);
974
975
0
        if( pic->gopEntry->m_isStartOfGop && !pic->useMCTF && m_encCfg->m_vvencMCTF.MCTF > 0 && meanRmsAcrossPic > numCtu * 27.0 )
976
0
        {
977
          // check application (re-enabling) of MCTF filter for key pictures, in case MCTF has been disabled based on SCC detection
978
0
          bool allNoiseZero = true;
979
0
          for( int i = 0; i < QPA_MAX_NOISE_LEVELS; i++ )
980
0
          {
981
0
            if( pic->m_picShared->m_minNoiseLevels[i] && pic->m_picShared->m_minNoiseLevels[i] < 255 )
982
0
            {
983
0
              allNoiseZero = false;
984
0
              break;
985
0
            }
986
0
          }
987
0
          int numZeroRMSCtus = 0;
988
0
          if( allNoiseZero )
989
0
          {
990
0
            for( int i = 0; i < numCtu; i++ )
991
0
            {
992
0
              if( sumRMS[i] == 0 )
993
0
              {
994
0
                numZeroRMSCtus += 1;
995
0
              }
996
0
            }
997
0
          }
998
0
          const bool doFilter = ( numZeroRMSCtus * 100 <= numCtu * 6 );
999
0
          if( doFilter )
1000
0
          {
1001
0
            fltrBuf.create( m_encCfg->m_internChromaFormat, m_area, 0, MCTF_FRAME_MARGIN );
1002
0
            bilateralFilter( origBuf, srcFrameInfo, fltrBuf, overallStrength );
1003
0
          }
1004
0
        }
1005
0
      }
1006
0
      if (m_encCfg->m_forceScc <= 0)
1007
0
      {
1008
0
        bool forceSCC = false;
1009
0
        if (pic->gopEntry->m_isStartOfGop)
1010
0
        {
1011
0
          forceSCC = true;
1012
0
          for (int j = 0; j < QPA_MAX_NOISE_LEVELS; j++)
1013
0
          {
1014
0
            if (pic->m_picShared->m_minNoiseLevels[j] < 255 && pic->m_picShared->m_minNoiseLevels[j])
1015
0
            {
1016
0
              forceSCC = false;
1017
0
              break;
1018
0
            }
1019
0
          }
1020
0
          if (forceSCC)
1021
0
          {
1022
0
            for (int s = 0; s < mvErr.size(); s++)
1023
0
            {
1024
0
              if (int(mvErr[s]) == 0)
1025
0
              {
1026
0
                forceSCC = false;
1027
0
                break;
1028
0
              }
1029
0
            }
1030
0
          }
1031
0
        }
1032
0
        pic->m_picShared->m_forceSCC = forceSCC;
1033
0
      }
1034
1035
      // BIM 2 uses these offsets as they are. CU-tree (BIM 1) keeps their picture offset, m_picAuxQpOffset, and
1036
      // replaces the CTU offsets with its own.
1037
0
      if( !m_encCfg->hasBlockQpOffsets() || !pic->useMCTF )
1038
0
      {
1039
0
        CHECKD( !pic->m_picShared->m_ctuBimQpOffset.empty(), "BIM disabled, but offset vector not empty!" );
1040
0
        return;
1041
0
      }
1042
1043
0
      pic->m_picShared->m_ctuBimQpOffset.resize( numCtu, 0 );
1044
1045
0
      if( distFactor[0] < 3 && distFactor[1] < 3 )
1046
0
      {
1047
0
        const double weight = std::min( 1.0, overallStrength );
1048
0
        const double factor = std::min( 1.0, sqrt((1920.0 * 1080.0) / double (m_encCfg->m_SourceWidth * m_encCfg->m_SourceHeight)) ) * ( ( m_encCfg->m_QP + m_encCfg->m_QPFraction ) / (MAX_QP + 1.0) );
1049
0
        int sumCtuQpOffsets = 0;
1050
1051
0
        meanRmsAcrossPic = (!m_encCfg->m_usePerceptQPA || !m_encCfg->m_salienceBasedOpt || maxRmsCTU == 0 || nMax < 2 ? 65535.0 : sqrt (double (sumSRmsAcrossPic - (uint64_t) maxRmsCTU * maxRmsCTU) / (nMax - 1.0)));
1052
1053
0
        for( int i = 0; i < numCtu; i++ )
1054
0
        {
1055
0
          const int avgErrD1 = ( int ) ( ( sumError[i         ] / blkCount[i         ] ) * distFactor[0] );
1056
0
          const int avgErrD2 = ( int ) ( ( sumError[i + numCtu] / blkCount[i + numCtu] ) * distFactor[1] );
1057
0
          int weightedErr = std::max( avgErrD1, avgErrD2 ) + abs( avgErrD2 - avgErrD1 ) * 3;
1058
0
          weightedErr     = ( int ) ( weightedErr * weight + ( 1 - weight ) * m_cuTreeCenter );
1059
1060
0
          int qpOffset = 0;
1061
1062
0
          if( weightedErr > m_cuTreeThresh[0] )
1063
0
          {
1064
0
            qpOffset = 2;
1065
0
          }
1066
0
          else if( weightedErr > m_cuTreeThresh[1] )
1067
0
          {
1068
0
            qpOffset = 1;
1069
0
          }
1070
0
          else if( weightedErr < m_cuTreeThresh[3] )
1071
0
          {
1072
0
            qpOffset = -2;
1073
0
          }
1074
0
          else if( weightedErr < m_cuTreeThresh[2] )
1075
0
          {
1076
0
            qpOffset = -1;
1077
0
          }
1078
1079
0
          if (meanRmsAcrossPic < maxRMS[i] * factor)
1080
0
          {
1081
0
            qpOffset += int (6.0 * log (std::max ((ctuSize > 64 ? 0.625 : 0.5) * maxRMS[i] * factor, meanRmsAcrossPic) / (maxRMS[i] * factor)) / (sqrt (weight) * log (2.0)) - 0.5);
1082
0
          }
1083
1084
0
          pic->m_picShared->m_ctuBimQpOffset[i] = qpOffset;
1085
0
          sumCtuQpOffsets += qpOffset;
1086
0
        }
1087
1088
0
        pic->m_picShared->m_picAuxQpOffset = ( sumCtuQpOffsets + ( sumCtuQpOffsets < 0 ? -(numCtu >> 1) : numCtu >> 1 ) ) / numCtu; // pic average
1089
0
        for( int i = 0; i < numCtu; i++ )
1090
0
        {
1091
0
          pic->m_picShared->m_ctuBimQpOffset[i] -= pic->m_picShared->m_picAuxQpOffset; // delta-QP relative to above average, see xGetQPForPicture
1092
0
        }
1093
0
      }
1094
0
      else
1095
0
      {
1096
0
        std::fill( pic->m_picShared->m_ctuBimQpOffset.begin(), pic->m_picShared->m_ctuBimQpOffset.end(), 0 );
1097
0
      }
1098
0
    }
1099
0
  }
1100
0
  else
1101
0
  {
1102
0
    pic->m_picShared->m_ctuBimQpOffset.resize( 0 );
1103
0
  }
1104
0
}
1105
1106
// ====================================================================================================================
1107
// Private member functions
1108
// ====================================================================================================================
1109
1110
void MCTF::subsampleLuma(const PelStorage &input, PelStorage &output, const int factor) const
1111
0
{
1112
0
  const int newWidth = input.Y().width / factor;
1113
0
  const int newHeight = input.Y().height / factor;
1114
0
  output.create(CHROMA_400, Area(0, 0, newWidth, newHeight), 0, m_padding);
1115
1116
0
  CHECK( factor != 2, "Only 2x2 subsampling is supported" );
1117
0
  g_pelBufOP.downsample2x2( input.Y().buf, input.Y().stride, output.Y().buf, output.Y().stride, newWidth, newHeight );
1118
0
  output.extendBorderPel(m_padding, m_padding);
1119
0
}
1120
1121
// The interpolation kernels above read up to 2 samples before and 3 samples after a block. The
1122
// bounds below include one more sample on each side.
1123
static constexpr int REF_TAPS_BEFORE = 3;
1124
static constexpr int REF_TAPS_AFTER  = 4;
1125
static constexpr int REF_MAX_BLOCK   = 64;   // largest block width and height the kernels accept
1126
1127
// Copies the w x h reference block at (x, y) with its filter taps into a per-thread buffer, clamping
1128
// the coordinates to the frame, and returns the block's first sample.
1129
static const Pel* copyClampedRefBlock( const CPelBuf& ref, const int x, const int y, const int w, const int h, ptrdiff_t& stride )
1130
0
{
1131
0
  CHECK( w > REF_MAX_BLOCK || h > REF_MAX_BLOCK, "MCTF reference block too large" );
1132
1133
  // A thread uses one copied block at a time, so one buffer per thread suffices.
1134
0
  static thread_local Pel tmp[( REF_MAX_BLOCK + REF_TAPS_BEFORE + REF_TAPS_AFTER ) * ( REF_MAX_BLOCK + REF_TAPS_BEFORE + REF_TAPS_AFTER )];
1135
1136
0
  const int left  = x - REF_TAPS_BEFORE;
1137
0
  const int top   = y - REF_TAPS_BEFORE;
1138
0
  const int cols  = w + REF_TAPS_BEFORE + REF_TAPS_AFTER;
1139
0
  const int rows  = h + REF_TAPS_BEFORE + REF_TAPS_AFTER;
1140
0
  const int width = ( int ) ref.width;
1141
1142
  // Columns [inBegin, inEnd) of the copy lie inside the frame; those left of it repeat the first
1143
  // sample of the row and those right of it the last one.
1144
0
  const int inBegin = Clip3( 0, cols, -left );
1145
0
  const int inEnd   = Clip3( 0, cols, width - left );
1146
1147
0
  for( int r = 0; r < rows; r++ )
1148
0
  {
1149
0
    const Pel* src = ref.buf + Clip3( 0, ( int ) ref.height - 1, top + r ) * ref.stride;
1150
0
    Pel*       dst = tmp + r * cols;
1151
0
    if( inBegin < inEnd )
1152
0
    {
1153
0
      std::fill_n( dst, inBegin, src[0] );
1154
0
      memcpy( dst + inBegin, src + left + inBegin, ( inEnd - inBegin ) * sizeof( Pel ) );
1155
0
      std::fill_n( dst + inEnd, cols - inEnd, src[width - 1] );
1156
0
    }
1157
0
    else
1158
0
    {
1159
      // The whole row of the copy lies right of the frame (inEnd == 0) or left of it.
1160
0
      std::fill_n( dst, cols, inEnd == 0 ? src[width - 1] : src[0] );
1161
0
    }
1162
0
  }
1163
1164
0
  stride = cols;
1165
0
  return tmp + REF_TAPS_BEFORE * cols + REF_TAPS_BEFORE;
1166
0
}
1167
1168
// Returns the samples of the w x h reference block at (x, y) and sets stride. The input frames only
1169
// keep MCTF_FRAME_MARGIN samples of replicated border, while the motion search reaches up to about
1170
// 100 samples outside the frame. A block whose filter taps leave the border is copied with clamped
1171
// coordinates, which gives the same samples as a wider replicated border.
1172
static inline const Pel* getRefBlock( const CPelBuf& ref, const int margin, const int x, const int y, const int w, const int h, ptrdiff_t& stride )
1173
0
{
1174
0
  if( x - REF_TAPS_BEFORE >= -margin && y - REF_TAPS_BEFORE >= -margin
1175
0
      && x + w + REF_TAPS_AFTER <= ( int ) ref.width + margin && y + h + REF_TAPS_AFTER <= ( int ) ref.height + margin )
1176
0
  {
1177
0
    stride = ref.stride;
1178
0
    return ref.buf + y * ref.stride + x;
1179
0
  }
1180
0
  return copyClampedRefBlock( ref, x, y, w, h, stride );
1181
0
}
1182
1183
int MCTF::motionErrorLuma(const PelStorage &orig,
1184
  const PelStorage &buffer,
1185
  const int refMargin,
1186
  const int x,
1187
  const int y,
1188
  int dx,
1189
  int dy,
1190
  const int bs,
1191
  const int besterror = MAX_INT) const
1192
0
{
1193
0
  int fx = dx & 0xf;
1194
0
  int fy = dy & 0xf;
1195
1196
0
  int error = 0;// dx * 10 + dy * 10;
1197
1198
0
  CHECKD( bs & 7, "Blocksize has to be a multiple of 8!" );
1199
1200
0
  const int w = std::min<int>( bs, orig.Y().width  - x ) & ~7;
1201
0
  const int h = std::min<int>( bs, orig.Y().height - y ) & ~7;
1202
1203
0
  CHECK( !w || !h, "Incompatible sizes!" );
1204
1205
0
  const int  origStride = orig.Y().stride;
1206
0
  const Pel* org        = orig.Y().buf + x + y * origStride;
1207
  // The motion vector is in 1/16 samples; the reference block starts at its integer part.
1208
0
  ptrdiff_t  buffStride = 0;
1209
0
  const Pel* buf        = getRefBlock( buffer.Y(), refMargin, x + ( dx >> 4 ), y + ( dy >> 4 ), w, h, buffStride );
1210
1211
0
  if( ( fx | fy ) == 0 )
1212
0
  {
1213
0
    return m_motionErrorLumaInt8( org, origStride, buf, buffStride, w, h, besterror );
1214
0
  }
1215
0
  else if( m_lowResFltSearch )
1216
0
  {
1217
0
    const int16_t *xFilter = m_interpolationFilter4[fx];
1218
0
    const int16_t *yFilter = m_interpolationFilter4[fy];
1219
1220
0
    return m_motionErrorLumaFrac8[1]( org, origStride, buf, buffStride, w, h, xFilter, yFilter, m_encCfg->m_internalBitDepth[CH_L], besterror );
1221
0
  }
1222
0
  else
1223
0
  {
1224
0
    const int16_t *xFilter = m_interpolationFilter8[fx];
1225
0
    const int16_t *yFilter = m_interpolationFilter8[fy];
1226
1227
0
    return m_motionErrorLumaFrac8[0]( org, origStride, buf, buffStride, w,h, xFilter, yFilter, m_encCfg->m_internalBitDepth[CH_L], besterror );
1228
0
  }
1229
1230
0
  return error;
1231
0
}
1232
1233
bool MCTF::estimateLumaLn( std::atomic_int& blockX_, std::atomic_int* prevLineX, Array2D<MotionVector> &mvs, const PelStorage &orig, const PelStorage &buffer, const int refMargin, const int blockSize,
1234
  const Array2D<MotionVector> *previous, const int factor, const bool doubleRes, int blockY, int bitDepth ) const
1235
0
{
1236
0
  PROFILER_SCOPE_AND_STAGE( 1, _TPROF, P_MCTF_SEARCH );
1237
1238
0
  const int stepSize = blockSize;
1239
0
  const int origWidth  = orig.Y().width;
1240
1241
0
  for( int blockX = blockX_.load(); blockX + 8 <= origWidth; blockX += stepSize, blockX_.store( blockX) )
1242
0
  {
1243
0
    if( prevLineX && blockX >= prevLineX->load() ) return false;
1244
1245
0
    int range = doubleRes ? 0 : ( m_searchPttrn == 2 ? 3 : 5 );
1246
0
    const int stepSize = blockSize;
1247
1248
0
    MotionVector best;
1249
1250
0
    if (previous == NULL)
1251
0
    {
1252
0
      range = 8;
1253
0
    }
1254
0
    else
1255
0
    {
1256
0
      for( int py = -1; py <= 1; py++ )
1257
0
      {
1258
0
        int testy = blockY / (2 * blockSize) + py;
1259
0
        if( (testy >= 0) && (testy < previous->h()) )
1260
0
        {
1261
0
          for (int px = -1; px <= 1; px++)
1262
0
          {
1263
0
            int testx = blockX / (2 * blockSize) + px;
1264
0
            if ((testx >= 0) && (testx < previous->w()) )
1265
0
            {
1266
0
              const MotionVector& old = previous->get(testx, testy);
1267
0
              int error = motionErrorLuma(orig, buffer, refMargin, blockX, blockY, old.x * factor, old.y * factor, blockSize, best.error);
1268
0
              if (error < best.error)
1269
0
              {
1270
0
                best.set(old.x * factor, old.y * factor, error);
1271
0
              }
1272
0
            }
1273
0
          }
1274
0
        }
1275
0
      }
1276
1277
0
      int error = motionErrorLuma( orig, buffer, refMargin, blockX, blockY, 0, 0, blockSize, best.error );
1278
0
      if( error < best.error )
1279
0
      {
1280
0
        best.set( 0, 0, error );
1281
0
      }
1282
0
    }
1283
0
    MotionVector prevBest = best;
1284
0
    const int d = previous == NULL && m_searchPttrn == 2 ? 2 : 1;
1285
0
    for( int y2 = prevBest.y / m_motionVectorFactor - range; y2 <= prevBest.y / m_motionVectorFactor + range; y2 += d )
1286
0
    {
1287
0
      for( int x2 = prevBest.x / m_motionVectorFactor - range; x2 <= prevBest.x / m_motionVectorFactor + range; x2 += d )
1288
0
      {
1289
0
        int error = motionErrorLuma( orig, buffer, refMargin, blockX, blockY, x2 * m_motionVectorFactor, y2 * m_motionVectorFactor, blockSize, best.error );
1290
0
        if( error < best.error )
1291
0
        {
1292
0
          best.set( x2 * m_motionVectorFactor, y2 * m_motionVectorFactor, error );
1293
0
        }
1294
0
      }
1295
0
    }
1296
0
    if (doubleRes)
1297
0
    { // merge into one loop, probably with precision array (here [12, 3] or maybe [4, 1]) with setable number of iterations
1298
0
      PROFILER_SCOPE_AND_STAGE( 1, _TPROF, P_MCTF_SEARCH_SUBPEL );
1299
1300
0
      prevBest = best;
1301
0
      int doubleRange = m_searchPttrn ? 6 : 12;
1302
0
      const int d1 = m_searchPttrn == 2 ? 6 : 4;
1303
1304
      // first iteration, 49 - 1 or 16 checks or 9 - 1 checks
1305
0
      for( int y2 = -doubleRange; y2 <= doubleRange; y2 += d1 )
1306
0
      {
1307
0
        for( int x2 = -doubleRange; x2 <= doubleRange; x2 += d1 )
1308
0
        {
1309
0
          if( x2 || y2 )
1310
0
          {
1311
0
            int error = motionErrorLuma( orig, buffer, refMargin, blockX, blockY, prevBest.x + x2, prevBest.y + y2, blockSize, best.error );
1312
0
            if( error < best.error )
1313
0
            {
1314
0
              best.set( prevBest.x + x2, prevBest.y + y2, error );
1315
0
            }
1316
0
          }
1317
0
        }
1318
0
      }
1319
1320
0
      prevBest = best;
1321
0
      doubleRange = 2;
1322
      // second iteration, 9 - 1 checks
1323
0
      for( int y2 = -doubleRange; y2 <= doubleRange; y2 += 2 )
1324
0
      {
1325
0
        for( int x2 = -doubleRange; x2 <= doubleRange; x2 += 2 )
1326
0
        {
1327
0
          if( x2 || y2 )
1328
0
          {
1329
0
            int error = motionErrorLuma( orig, buffer, refMargin, blockX, blockY, prevBest.x + x2, prevBest.y + y2, blockSize, best.error );
1330
0
            if( error < best.error )
1331
0
            {
1332
0
              best.set( prevBest.x + x2, prevBest.y + y2, error );
1333
0
            }
1334
0
          }
1335
0
        }
1336
0
      }
1337
1338
0
      prevBest = best;
1339
0
      doubleRange = 1;
1340
      // third iteration, 9 - 1 checks
1341
0
      for (int y2 = -doubleRange; y2 <= doubleRange; y2++)
1342
0
      {
1343
0
        for (int x2 = -doubleRange; x2 <= doubleRange; x2++)
1344
0
        {
1345
0
          if( x2 || y2 )
1346
0
          {
1347
0
            int error = motionErrorLuma( orig, buffer, refMargin, blockX, blockY, prevBest.x + x2, prevBest.y + y2, blockSize, best.error );
1348
0
            if( error < best.error )
1349
0
            {
1350
0
              best.set( prevBest.x + x2, prevBest.y + y2, error );
1351
0
            }
1352
0
          }
1353
0
        }
1354
0
      }
1355
0
    } 
1356
0
    if( blockY > 0 )
1357
0
    {
1358
0
      MotionVector aboveMV = mvs.get( blockX / stepSize, ( blockY - stepSize ) / stepSize );
1359
0
      int error = motionErrorLuma( orig, buffer, refMargin, blockX, blockY, aboveMV.x, aboveMV.y, blockSize, best.error );
1360
0
      if( error < best.error )
1361
0
      {
1362
0
        best.set( aboveMV.x, aboveMV.y, error );
1363
0
      }
1364
0
    }
1365
0
    if( blockX > 0 )
1366
0
    {
1367
0
      MotionVector leftMV = mvs.get( ( blockX - stepSize ) / stepSize, blockY / stepSize );
1368
0
      int error = motionErrorLuma( orig, buffer, refMargin, blockX, blockY, leftMV.x, leftMV.y, blockSize, best.error );
1369
0
      if( error < best.error )
1370
0
      {
1371
0
        best.set( leftMV.x, leftMV.y, error );
1372
0
      }
1373
0
    }
1374
1375
0
    if( doubleRes )
1376
0
    {
1377
0
      const int w = std::min<int>( blockSize, orig.Y().width  - blockX ) & ~7;
1378
0
      const int h = std::min<int>( blockSize, orig.Y().height - blockY ) & ~7;
1379
1380
0
      CHECKD(bitDepth>10, "unsupported internal bit depth (also in calcVar)" );
1381
0
      const double bdScale = double(1<<(2*(10-bitDepth)));
1382
0
      const double dvar = m_calcVar( orig.Y().bufAt( blockX, blockY ), orig.Y().stride, w, h ) * bdScale;
1383
0
      const double mse  = best.error * bdScale / double( w * h );
1384
1385
0
      best.error   = ( int ) ( 20 * ( ( best.error*bdScale + 5.0 ) / ( dvar + 5.0 ) ) + mse / 50.0 );
1386
0
      best.rmsme   = uint16_t( 0.5 + sqrt( mse ) );
1387
0
      best.overlap = ( ( double ) w * h ) / ( m_mctfUnitSize * m_mctfUnitSize );
1388
0
    }
1389
1390
0
    mvs.get(blockX / stepSize, blockY / stepSize) = best;
1391
0
  }
1392
1393
0
  return true;
1394
0
}
1395
1396
void MCTF::motionEstimationLuma(Array2D<MotionVector> &mvs, const PelStorage &orig, const PelStorage &buffer, const int refMargin, const int blockSize, const Array2D<MotionVector> *previous, const int factor, const bool doubleRes) const
1397
0
{
1398
0
  const int stepSize = blockSize;
1399
0
  const int origHeight = orig.Y().height;
1400
0
  const int bitDepth = m_encCfg->m_internalBitDepth[CH_L];
1401
1402
0
  if( m_threadPool )
1403
0
  {
1404
0
    struct EstParams
1405
0
    {
1406
0
      std::atomic_int blockX;
1407
0
      std::atomic_int* prevLineX;
1408
0
      Array2D<MotionVector> *mvs;
1409
0
      const PelStorage* orig; 
1410
0
      const PelStorage* buffer; 
1411
0
      int   refMargin;
1412
0
      const Array2D<MotionVector> *previous; 
1413
0
      int   blockSize; 
1414
0
      int   factor; 
1415
0
      bool  doubleRes;
1416
0
      int   blockY;
1417
0
      int   bitDepth;
1418
0
      const MCTF* mctf;
1419
0
    };
1420
1421
0
    std::vector<EstParams> EstParamsArray( origHeight/stepSize + 1 );
1422
1423
0
    WaitCounter taskCounter;
1424
1425
0
    for( int n = 0, blockY = 0; blockY + 8 <= origHeight; blockY += stepSize, n++ )
1426
0
    {
1427
0
      static auto task = []( int, void* task_param )
1428
0
      {
1429
0
        ITT_TASKSTART( itt_domain_MCTF_est, itt_handle_est );
1430
1431
0
        EstParams* params = static_cast<EstParams*>( task_param );
1432
0
        bool ret = params->mctf->estimateLumaLn( params->blockX, params->prevLineX, *params->mvs, *params->orig, *params->buffer, params->refMargin, params->blockSize, params->previous, params->factor, params->doubleRes, params->blockY, params->bitDepth );
1433
1434
0
        ITT_TASKEND( itt_domain_MCTF_est, itt_handle_est );
1435
0
        return ret;
1436
0
      };
1437
1438
0
      EstParams& cEstParams = EstParamsArray[n];
1439
0
      cEstParams.blockX = 0;
1440
0
      cEstParams.prevLineX = n == 0 ? nullptr : &EstParamsArray[n-1].blockX;
1441
0
      cEstParams.mvs = &mvs; 
1442
0
      cEstParams.orig = &orig;
1443
0
      cEstParams.buffer = &buffer; 
1444
0
      cEstParams.refMargin = refMargin;
1445
0
      cEstParams.previous = previous;
1446
0
      cEstParams.blockSize = blockSize; 
1447
0
      cEstParams.factor = factor;
1448
0
      cEstParams.doubleRes = doubleRes;
1449
0
      cEstParams.mctf = this;
1450
0
      cEstParams.blockY = blockY;
1451
0
      cEstParams.bitDepth = bitDepth;
1452
1453
0
      m_threadPool->addBarrierTask( task, &cEstParams, &taskCounter);
1454
0
    }
1455
0
    taskCounter.wait();
1456
0
  }
1457
0
  else
1458
0
  {
1459
0
    for( int blockY = 0; blockY + 8 <= origHeight; blockY += stepSize )
1460
0
    {
1461
0
      std::atomic_int blockX( 0 ), prevBlockX( orig.Y().width + stepSize );
1462
0
      estimateLumaLn( blockX, blockY ? &prevBlockX : nullptr, mvs, orig, buffer, refMargin, blockSize, previous, factor, doubleRes, blockY, bitDepth );
1463
0
    }
1464
1465
0
  }
1466
0
}
1467
1468
void MCTF::xFinalizeBlkLine( const PelStorage &orgPic, std::deque<TemporalFilterSourcePicInfo> &srcFrameInfo, PelStorage &newOrgPic, int yStart, const double sigmaSqCh[MAX_NUM_CH], double overallStrength ) const
1469
0
{
1470
0
  PROFILER_SCOPE_AND_STAGE( 1, _TPROF, P_MCTF_APPLY );
1471
1472
0
  const int numRefs = int(srcFrameInfo.size());
1473
1474
0
  int refStrengthRow = m_encCfg->m_picReordering ? 0 : 1;
1475
1476
  // max 64*64*8*2 = 2^(6+6+3+1)=2^16=64kbps, usually 16*16*8*2=2^(4+4+3+1)=4kbps, and allow for overread of one line
1477
0
  Pel* dstBufs = ( Pel* ) alloca( sizeof( Pel ) * ( numRefs * m_mctfUnitSize * m_mctfUnitSize + m_mctfUnitSize ) );
1478
1479
0
  for( int c = 0; c < getNumberValidComponents( m_encCfg->m_internChromaFormat ); c++ )
1480
0
  {
1481
0
    const ComponentID compID = ( ComponentID ) c;
1482
0
    const int height    = orgPic.bufs[c].height;
1483
0
    const int width     = orgPic.bufs[c].width;
1484
1485
0
    const double sigmaSq = sigmaSqCh[ toChannelType( compID) ];
1486
0
    const double weightScaling = overallStrength * ( isChroma( compID ) ? m_chromaFactor : 0.4 );
1487
0
    const ClpRng clpRng{ m_encCfg->m_internalBitDepth[toChannelType( compID )] };
1488
1489
0
    const int blkSizeY = m_mctfUnitSize >> getComponentScaleY( compID, m_encCfg->m_internChromaFormat );
1490
0
    const int blkSizeX = m_mctfUnitSize >> getComponentScaleX( compID, m_encCfg->m_internChromaFormat );
1491
0
    const int yOut     = yStart         >> getComponentScaleY( compID, m_encCfg->m_internChromaFormat );
1492
    // The chroma border is scaled like the chroma planes; take the narrower direction.
1493
0
    const int refMargin = MCTF_FRAME_MARGIN >> std::max( getComponentScaleX( compID, m_encCfg->m_internChromaFormat ), getComponentScaleY( compID, m_encCfg->m_internChromaFormat ) );
1494
1495
0
    for( int by = yOut, yBlkAddr = yStart / m_mctfUnitSize; by < std::min( yOut + blkSizeY, height ); by += blkSizeY, yBlkAddr++ )
1496
0
    {
1497
0
      const int h = std::min( blkSizeY, height - by );
1498
1499
0
      for( int bx = 0, xBlkAddr = 0; bx < width; bx += blkSizeX, xBlkAddr++ )
1500
0
      {
1501
0
        const int w = std::min( blkSizeX, width - bx );
1502
1503
0
        const int csx = getComponentScaleX( compID, m_encCfg->m_internChromaFormat );
1504
0
        const int csy = getComponentScaleY( compID, m_encCfg->m_internChromaFormat );
1505
1506
0
        const Pel* correctedPics[2 * VVENC_MCTF_RANGE] = { nullptr, };
1507
0
              Pel* currDst = dstBufs;
1508
0
        int verror     [2 * VVENC_MCTF_RANGE] = { 0,   };
1509
0
        double refStr  [2 * VVENC_MCTF_RANGE] = { 0.0, };
1510
1511
0
        for( int i = 0; i < numRefs; i++, currDst += w * h )
1512
0
        {
1513
0
                Pel* dst      = currDst;
1514
0
          const int dstStride = w;
1515
0
          correctedPics[i]    = dst;
1516
1517
0
          const MotionVector& mv = srcFrameInfo[i].mvs.get( xBlkAddr, yBlkAddr);
1518
0
          const int dx   = mv.x >> csx;
1519
0
          const int dy   = mv.y >> csy;
1520
0
          const int xInt = mv.x >> ( 4 + csx );
1521
0
          const int yInt = mv.y >> ( 4 + csy );
1522
1523
0
          ptrdiff_t  srcStride = 0;
1524
0
          const Pel* src       = getRefBlock( srcFrameInfo[i].picBuffer.bufs[compID], refMargin, bx + xInt, by + yInt, w, h, srcStride );
1525
1526
0
          if( m_lowResFltApply ) // || isChroma( compID )
1527
0
          {
1528
0
            const int16_t* xFilter = m_interpolationFilter4[dx & 0xf];
1529
0
            const int16_t* yFilter = m_interpolationFilter4[dy & 0xf]; // will add 6 bit.
1530
1531
0
            m_applyFrac[toChannelType( compID )][1]( src, srcStride, dst, dstStride, w, h, xFilter, yFilter, m_encCfg->m_internalBitDepth[toChannelType( compID )] );
1532
0
          }
1533
0
          else
1534
0
          {
1535
0
            const int16_t* xFilter = m_interpolationFilter8[dx & 0xf];
1536
0
            const int16_t* yFilter = m_interpolationFilter8[dy & 0xf]; // will add 6 bit.
1537
1538
0
            m_applyFrac[toChannelType( compID )][0]( src, srcStride, dst, dstStride, w, h, xFilter, yFilter, m_encCfg->m_internalBitDepth[toChannelType( compID )] );
1539
0
          }
1540
1541
0
          if( mv.rmsme > 0 && m_encCfg->m_QP <= 32 && w == h && w <= 32 ) // "deblocking"
1542
0
          {
1543
0
            m_applyPlanarCorrection( orgPic.bufs[c].bufAt( bx, by ), orgPic.bufs[c].stride, dst, dstStride, w, h, clpRng, mv.rmsme );
1544
0
          }
1545
1546
0
          verror[i] = mv.error;
1547
0
          refStr[i] = m_refStrengths[refStrengthRow][srcFrameInfo[i].index];
1548
0
        }
1549
1550
0
        m_applyBlock( orgPic.bufs[c], newOrgPic.bufs[c], CompArea( compID, orgPic.chromaFormat, Area( bx, by, w, h ) ), clpRng, correctedPics, numRefs, verror, refStr, weightScaling, sigmaSq );
1551
0
      }
1552
0
    }
1553
0
  }
1554
0
}
1555
1556
void MCTF::bilateralFilter(const PelStorage& orgPic, std::deque<TemporalFilterSourcePicInfo>& srcFrameInfo, PelStorage& newOrgPic, double overallStrength) const
1557
0
{
1558
0
  const double qp          = m_encCfg->m_QP + m_encCfg->m_QPFraction;
1559
0
  const double lumaSigmaSq = m_sigmaMultiplier * ( 128.0 + 3.0 / 256.0 * qp * qp * qp );
1560
0
  const double chromaSigmaSq = 30 * 30;
1561
1562
0
  double sigmaSqCh[MAX_NUM_CH];
1563
0
  for(int c=0; c< getNumberValidChannels(m_encCfg->m_internChromaFormat); c++)
1564
0
  {
1565
0
    const ChannelType ch=(ChannelType)c;
1566
0
    const Pel maxSampleValue = (1<<m_encCfg->m_internalBitDepth[ch])-1;
1567
0
    const double bitDepthDiffWeighting=1024.0 / (maxSampleValue+1);
1568
0
    sigmaSqCh[ch] = ( isChroma( ch ) ? chromaSigmaSq : lumaSigmaSq ) / ( bitDepthDiffWeighting * bitDepthDiffWeighting );
1569
0
  }
1570
1571
0
  if( m_threadPool )
1572
0
  {
1573
0
    struct FltParams
1574
0
    {
1575
0
      const PelStorage *orgPic; 
1576
0
      std::deque<TemporalFilterSourcePicInfo> *srcFrameInfo; 
1577
0
      PelStorage *newOrgPic;
1578
0
      const double *sigmaSqCh;
1579
0
      double overallStrength;
1580
0
      const MCTF* mctf;
1581
0
      int yStart; 
1582
0
    };
1583
1584
0
    std::vector<FltParams> FltParamsArray( orgPic.Y().height / m_mctfUnitSize + 1 );
1585
1586
0
    WaitCounter taskCounter;
1587
0
    for( int n = 0, yStart = 0; yStart < orgPic.Y().height; yStart += m_mctfUnitSize, n++ )
1588
0
    {
1589
0
      static auto task = []( int, void* task_param )
1590
0
      {
1591
0
        ITT_TASKSTART( itt_domain_MCTF_flt, itt_handle_flt );
1592
1593
0
        FltParams* params = static_cast<FltParams*>( task_param );
1594
0
        params->mctf->xFinalizeBlkLine( *params->orgPic, *params->srcFrameInfo, *params->newOrgPic, params->yStart, params->sigmaSqCh, params->overallStrength );
1595
1596
0
        ITT_TASKEND( itt_domain_MCTF_flt, itt_handle_flt );
1597
0
        return true;
1598
0
      };
1599
1600
0
      FltParams& cFltParams      = FltParamsArray[n];
1601
0
      cFltParams.orgPic          = &orgPic;
1602
0
      cFltParams.srcFrameInfo    = &srcFrameInfo;
1603
0
      cFltParams.newOrgPic       = &newOrgPic;
1604
0
      cFltParams.sigmaSqCh       = sigmaSqCh;
1605
0
      cFltParams.overallStrength = overallStrength;
1606
0
      cFltParams.mctf            = this;
1607
0
      cFltParams.yStart          = yStart;
1608
1609
0
      m_threadPool->addBarrierTask( task, &cFltParams, &taskCounter );
1610
0
    }
1611
0
    taskCounter.wait();
1612
0
  }
1613
0
  else
1614
0
  {
1615
0
    for (int yStart = 0; yStart < orgPic.Y().height; yStart += m_mctfUnitSize )
1616
0
    {
1617
0
      xFinalizeBlkLine( orgPic, srcFrameInfo, newOrgPic, yStart, sigmaSqCh, overallStrength );
1618
0
    }
1619
0
  }
1620
0
}
1621
1622
} // namespace vvenc
1623
1624
//! \}