Coverage Report

Created: 2026-10-28 06:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/work/vvenc/source/Lib/EncoderLib/CuTree.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     CuTree.cpp
45
    \brief    look-ahead stage for CU-tree adaptive quantization
46
47
    Implements the temporal dependency model (TPL) of
48
    J. Han, P. Wilkins, Y. Xu, J. Bankoski, "A quantitative approach to the temporal dependency in video coding",
49
    arXiv:2108.11586, 2021 (https://arxiv.org/abs/2108.11586). The equation numbers in the comments refer to it.
50
*/
51
52
#include "CuTree.h"
53
#include "EncSlice.h"
54
#include "CommonLib/Picture.h"
55
#include "Utilities/NoMallocThreadPool.h"
56
57
#include <algorithm>
58
#include <atomic>
59
#include <cmath>
60
#include <condition_variable>
61
#include <memory>
62
#include <mutex>
63
#include <vector>
64
65
//! \ingroup EncoderLib
66
//! \{
67
68
namespace vvenc {
69
70
static constexpr int MAX_BLK_SIZE = tplBlkSide( NUM_TPL_BLK_SIZES - 1 );   // largest analysis block, half resolution
71
static constexpr int MV_PREC      = 16;                    // motion vectors are in 1/16 sample, as in MCTF
72
static constexpr int NUM_LEVELS   = 3;                     // half, quarter and eighth resolution
73
static constexpr int NO_REF       = MAX_INT;
74
// Border of the half-resolution pictures. Motion search and prediction clamp reads that go beyond it.
75
static constexpr int LOWRES_PAD   = 32;
76
77
enum CuTreePred : uint8_t
78
{
79
  CTP_INTRA = 0,
80
  CTP_L0    = 1,
81
  CTP_L1    = 2,
82
  CTP_BI    = 3
83
};
84
85
// Analysis data of one picture. Distortions are squared errors of full-resolution samples.
86
struct CuTree::Frame
87
{
88
  int                   poc       = 0;
89
  int                   codingNum = 0;
90
  int                   qp        = 0;       // the picture's QP from the temporal-layer cascade
91
  bool                  isIntra   = false;
92
  bool                  analysed  = false;
93
  bool                  hasImages = true;    // lowRes and recon are kept while other pictures can reference them
94
  int                   refPoc[2] = { NO_REF, NO_REF };
95
  int                   picQpOffset = 0;     // BIM 2's picture-level QP offset, see xSetPicQpOffsets
96
  PelStorage            lowRes[NUM_LEVELS];
97
  PelStorage            recon;               // half-resolution reconstruction at the picture's QP
98
  Array2D<MotionVector> mvs[2];              // half-resolution motion towards refPoc[0] and refPoc[1]
99
  std::vector<uint8_t>  predMode;
100
  std::vector<float>    distRec;             // distortion of the coded block, predicted from reconstructed references
101
  std::vector<float>    distSrc[2];          // the same with reference l replaced by its original
102
  std::vector<float>    deltaDist[2];        // extra distortion and rate caused by the coding error of reference l
103
  std::vector<float>    deltaRate[2];
104
  std::vector<double>   propDist;            // distortion and rate that later pictures owe to each block
105
  std::vector<double>   propRate;
106
};
107
108
// ====================================================================================================================
109
// Block helpers
110
// ====================================================================================================================
111
112
// The encoder's own (SIMD) Hadamard SATD of the difference between two analysis blocks.
113
struct BlockSatd
114
{
115
  FpDistFunc func;
116
  int        bitDepth;
117
  int        size;
118
119
  int operator()( const Pel* org, ptrdiff_t orgStride, const Pel* pred, ptrdiff_t predStride ) const
120
0
  {
121
0
    const DistParam dp( CPelBuf( org, ( int ) orgStride, size, size ), CPelBuf( pred, ( int ) predStride, size, size ),
122
0
                        func, bitDepth, 0, COMP_Y );
123
0
    return ( int ) func( dp );
124
0
  }
125
};
126
127
template<int N>
128
void tplPredBlockCore( const Pel* src, ptrdiff_t srcStride, int fx, int fy, Pel* dst )
129
0
{
130
0
  for( int j = 0; j < N; j++ )
131
0
  {
132
0
    const Pel* row0 = src + j * srcStride;
133
0
    const Pel* row1 = row0 + srcStride;
134
0
    for( int i = 0; i < N; i++ )
135
0
    {
136
0
      const int top    = row0[i] * ( MV_PREC - fx ) + row0[i + 1] * fx;
137
0
      const int bottom = row1[i] * ( MV_PREC - fx ) + row1[i + 1] * fx;
138
0
      dst[j * N + i] = Pel( ( top * ( MV_PREC - fy ) + bottom * fy + 128 ) >> 8 );
139
0
    }
140
0
  }
141
0
}
Unexecuted instantiation: void vvenc::tplPredBlockCore<8>(short const*, long, int, int, short*)
Unexecuted instantiation: void vvenc::tplPredBlockCore<16>(short const*, long, int, int, short*)
142
143
template void tplPredBlockCore<8> ( const Pel* src, ptrdiff_t srcStride, int fx, int fy, Pel* dst );
144
template void tplPredBlockCore<16>( const Pel* src, ptrdiff_t srcStride, int fx, int fy, Pel* dst );
145
146
void tplDownsampleRowCore( const Pel* src, ptrdiff_t srcStride, Pel* dst, int dstWidth )
147
0
{
148
0
  const Pel* below = src + srcStride;
149
0
  for( int x = 0; x < dstWidth; x++ )
150
0
  {
151
0
    dst[x] = Pel( ( src[2 * x] + src[2 * x + 1] + below[2 * x] + below[2 * x + 1] + 2 ) >> 2 );
152
0
  }
153
0
}
154
155
// Bilinear prediction of the n x n block at (x, y) displaced by mv (1/16 sample). Positions outside the padded
156
// reference repeat its edge, as the motion search does.
157
static void predictBlock( TplPredBlockFn predBlock, int n, const CPelBuf& ref, int x, int y, const MotionVector& mv, Pel* dst )
158
0
{
159
0
  const int fx = mv.x & ( MV_PREC - 1 );
160
0
  const int fy = mv.y & ( MV_PREC - 1 );
161
0
  const int x0 = x + ( mv.x >> 4 );
162
0
  const int y0 = y + ( mv.y >> 4 );
163
0
  const int minX = -LOWRES_PAD, maxX = ( int ) ref.width  + LOWRES_PAD - 1;
164
0
  const int minY = -LOWRES_PAD, maxY = ( int ) ref.height + LOWRES_PAD - 1;
165
166
0
  if( x0 >= minX && x0 + n <= maxX && y0 >= minY && y0 + n <= maxY )
167
0
  {
168
0
    predBlock( ref.buf + y0 * ref.stride + x0, ref.stride, fx, fy, dst );
169
0
    return;
170
0
  }
171
172
0
  for( int j = 0; j < n; j++ )
173
0
  {
174
0
    const Pel* row0 = ref.buf + Clip3( minY, maxY, y0 + j     ) * ref.stride;
175
0
    const Pel* row1 = ref.buf + Clip3( minY, maxY, y0 + j + 1 ) * ref.stride;
176
0
    for( int i = 0; i < n; i++ )
177
0
    {
178
0
      const int c0 = Clip3( minX, maxX, x0 + i );
179
0
      const int c1 = Clip3( minX, maxX, x0 + i + 1 );
180
0
      const int top    = row0[c0] * ( MV_PREC - fx ) + row0[c1] * fx;
181
0
      const int bottom = row1[c0] * ( MV_PREC - fx ) + row1[c1] * fx;
182
0
      dst[j * n + i] = Pel( ( top * ( MV_PREC - fy ) + bottom * fy + 128 ) >> 8 );
183
0
    }
184
0
  }
185
0
}
186
187
// The best of DC, vertical, horizontal and planar prediction of an N x N block from the neighbouring source samples,
188
// by SATD. The size is a template parameter so that the loops over the block keep a constant trip count.
189
template<int N>
190
static void predictIntra( const CPelBuf& pic, int x, int y, int bitDepth, const BlockSatd& satd, Pel* bestPred )
191
0
{
192
0
  static constexpr int n = N, log2n = N == 16 ? 4 : 3;
193
0
  const Pel* org     = pic.buf + y * pic.stride + x;
194
0
  const bool hasTop  = y > 0;
195
0
  const bool hasLeft = x > 0;
196
0
  Pel top[n], left[n];
197
0
  Pel pred[n * n];
198
199
0
  int sum = 0;
200
0
  for( int i = 0; i < n; i++ )
201
0
  {
202
0
    top[i]  = hasTop  ? org[i - pic.stride]     : 0;
203
0
    left[i] = hasLeft ? org[i * pic.stride - 1] : 0;
204
0
    sum    += ( hasTop ? top[i] : 0 ) + ( hasLeft ? left[i] : 0 );
205
0
  }
206
0
  const int num = ( hasTop ? n : 0 ) + ( hasLeft ? n : 0 );
207
208
0
  int best = MAX_INT;
209
0
  auto keepBest = [&]()
210
0
  {
211
0
    const int cost = satd( org, pic.stride, pred, n );
212
0
    if( cost < best )
213
0
    {
214
0
      best = cost;
215
0
      std::copy_n( pred, n * n, bestPred );
216
0
    }
217
0
  };
Unexecuted instantiation: CuTree.cpp:vvenc::predictIntra<16>(vvenc::AreaBuf<short const> const&, int, int, int, vvenc::BlockSatd const&, short*)::{lambda()#1}::operator()() const
Unexecuted instantiation: CuTree.cpp:vvenc::predictIntra<8>(vvenc::AreaBuf<short const> const&, int, int, int, vvenc::BlockSatd const&, short*)::{lambda()#1}::operator()() const
218
219
0
  const Pel dc = num ? Pel( ( sum + ( num >> 1 ) ) / num ) : Pel( 1 << ( bitDepth - 1 ) );
220
0
  std::fill_n( pred, n * n, dc );
221
0
  keepBest();
222
223
0
  if( hasTop )
224
0
  {
225
0
    for( int j = 0; j < n; j++ ) std::copy_n( top, n, pred + j * n );
226
0
    keepBest();
227
0
  }
228
0
  if( hasLeft )
229
0
  {
230
0
    for( int j = 0; j < n; j++ ) std::fill_n( pred + j * n, n, left[j] );
231
0
    keepBest();
232
0
  }
233
0
  if( hasTop && hasLeft )
234
0
  {
235
0
    for( int j = 0; j < n; j++ )
236
0
    {
237
0
      for( int i = 0; i < n; i++ )
238
0
      {
239
0
        const int hor = ( n - 1 - i ) * left[j] + ( i + 1 ) * top[n - 1];
240
0
        const int ver = ( n - 1 - j ) * top[i]  + ( j + 1 ) * left[n - 1];
241
0
        pred[j * n + i] = Pel( ( hor + ver + n ) >> ( log2n + 1 ) );
242
0
      }
243
0
    }
244
0
    keepBest();
245
0
  }
246
0
}
Unexecuted instantiation: CuTree.cpp:void vvenc::predictIntra<16>(vvenc::AreaBuf<short const> const&, int, int, int, vvenc::BlockSatd const&, short*)
Unexecuted instantiation: CuTree.cpp:void vvenc::predictIntra<8>(vvenc::AreaBuf<short const> const&, int, int, int, vvenc::BlockSatd const&, short*)
247
248
// 8-point Hadamard butterflies on v[0], v[s], ..., v[7 s].
249
static void hadamard8( int32_t* v, int s )
250
0
{
251
0
  const int32_t a0 = v[0] + v[4 * s], a1 = v[s] + v[5 * s], a2 = v[2 * s] + v[6 * s], a3 = v[3 * s] + v[7 * s];
252
0
  const int32_t a4 = v[0] - v[4 * s], a5 = v[s] - v[5 * s], a6 = v[2 * s] - v[6 * s], a7 = v[3 * s] - v[7 * s];
253
0
  const int32_t b0 = a0 + a2, b1 = a1 + a3, b2 = a0 - a2, b3 = a1 - a3;
254
0
  const int32_t b4 = a4 + a6, b5 = a5 + a7, b6 = a4 - a6, b7 = a5 - a7;
255
0
  v[0] = b0 + b1; v[s] = b0 - b1; v[2 * s] = b2 + b3; v[3 * s] = b2 - b3;
256
0
  v[4 * s] = b4 + b5; v[5 * s] = b4 - b5; v[6 * s] = b6 + b7; v[7 * s] = b6 - b7;
257
0
}
258
259
// 16-point Hadamard butterflies: one stage across the two halves, then the 8-point transform of each half.
260
static void hadamard16( int32_t* v, int s )
261
0
{
262
0
  for( int i = 0; i < 8; i++ )
263
0
  {
264
0
    const int32_t a = v[i * s], b = v[( i + 8 ) * s];
265
0
    v[i * s]       = a + b;
266
0
    v[( i + 8 ) * s] = a - b;
267
0
  }
268
0
  hadamard8( v, s );
269
0
  hadamard8( v + 8 * s, s );
270
0
}
271
272
// The Hadamard butterflies of one row or column of an N x N analysis block.
273
template<int N>
274
static void hadamardBlk( int32_t* v, int s )
275
0
{
276
0
  if( N == 16 ) hadamard16( v, s );
277
0
  else          hadamard8 ( v, s );
278
0
}
Unexecuted instantiation: CuTree.cpp:void vvenc::hadamardBlk<8>(int*, int)
Unexecuted instantiation: CuTree.cpp:void vvenc::hadamardBlk<16>(int*, int)
279
280
// The scalar block coder. Integer Hadamard butterflies are exact in any order, so the SIMD versions give the
281
// same numbers.
282
template<int N>
283
void tplCodeBlockCore( const Pel* org, ptrdiff_t orgStride, const Pel* pred, Pel* recon, ptrdiff_t reconStride,
284
                       const TplQuant& quant, int maxVal, int64_t& dist, int& bits )
285
0
{
286
0
  static constexpr int LOG2N = N == 16 ? 4 : 3;
287
0
  int32_t c[N * N];
288
0
  for( int y = 0; y < N; y++ )
289
0
  {
290
0
    for( int x = 0; x < N; x++ )
291
0
    {
292
0
      c[y * N + x] = org[y * orgStride + x] - pred[y * N + x];
293
0
    }
294
0
  }
295
0
  for( int y = 0; y < N; y++ ) hadamardBlk<N>( c + y * N, 1 );
296
0
  for( int x = 0; x < N; x++ ) hadamardBlk<N>( c + x, N );
297
298
0
  dist = 0;
299
0
  bits = 0;
300
0
  for( int i = 0; i < N * N; i++ )
301
0
  {
302
0
    const int32_t absCoeff = abs( c[i] );
303
0
    const int32_t level    = ( absCoeff * quant.scale + quant.round ) >> 16;
304
0
    const int32_t absDeq   = ( level * quant.deqStep + 8 ) >> 4;
305
0
    const int64_t err      = absCoeff - absDeq;
306
0
    dist += err * err;
307
0
    bits += level > 0 ? 2 * floorLog2( level ) + 3 : 0;
308
0
    c[i]  = c[i] < 0 ? -absDeq : absDeq;
309
0
  }
310
311
0
  if( recon )
312
0
  {
313
    // the unnormalised transform applied twice scales by N^2
314
0
    for( int y = 0; y < N; y++ ) hadamardBlk<N>( c + y * N, 1 );
315
0
    for( int x = 0; x < N; x++ ) hadamardBlk<N>( c + x, N );
316
0
    for( int y = 0; y < N; y++ )
317
0
    {
318
0
      for( int x = 0; x < N; x++ )
319
0
      {
320
0
        const int32_t res = ( c[y * N + x] + ( 1 << ( 2 * LOG2N - 1 ) ) ) >> ( 2 * LOG2N );
321
0
        recon[y * reconStride + x] = Pel( Clip3( 0, maxVal, pred[y * N + x] + res ) );
322
0
      }
323
0
    }
324
0
  }
325
0
}
Unexecuted instantiation: void vvenc::tplCodeBlockCore<8>(short const*, long, short const*, short*, long, vvenc::TplQuant const&, int, long&, int&)
Unexecuted instantiation: void vvenc::tplCodeBlockCore<16>(short const*, long, short const*, short*, long, vvenc::TplQuant const&, int, long&, int&)
326
327
template void tplCodeBlockCore<8> ( const Pel* org, ptrdiff_t orgStride, const Pel* pred, Pel* recon, ptrdiff_t reconStride,
328
                                    const TplQuant& quant, int maxVal, int64_t& dist, int& bits );
329
template void tplCodeBlockCore<16>( const Pel* org, ptrdiff_t orgStride, const Pel* pred, Pel* recon, ptrdiff_t reconStride,
330
                                    const TplQuant& quant, int maxVal, int64_t& dist, int& bits );
331
332
// Codes a half-resolution residual block the way TPL does with a Walsh-Hadamard transform. Distortion and rate are
333
// scaled to the full-resolution block it stands for, twice as wide and high: its low frequencies have twice the
334
// coefficient amplitude, so the step is halved and the squared error counts four times.
335
struct BlockCoder
336
{
337
  TplCodeBlockFn codeBlock;
338
  int            size;
339
  TplQuant       quant;
340
341
  // step of the orthonormal half-resolution coefficients, dead-zone rounding offset below one half
342
0
  BlockCoder( TplCodeBlockFn fn, int n, double step, double deadZone ) : codeBlock( fn ), size( n )
343
0
  {
344
0
    const double unnormStep = step * n;
345
0
    quant.scale   = int32_t( std::lround( 65536.0 / unnormStep ) );
346
0
    quant.round   = int32_t( std::lround( 65536.0 * deadZone ) );
347
0
    quant.deqStep = int32_t( std::lround( 16.0 * unnormStep ) );
348
0
  }
349
350
  struct Result
351
  {
352
    double dist;      // squared error, full-resolution scale
353
    double rate;      // estimated bits
354
  };
355
356
  Result code( const Pel* org, ptrdiff_t orgStride, const Pel* pred, Pel* recon, ptrdiff_t reconStride, int maxVal ) const
357
0
  {
358
0
    int64_t dist = 0;
359
0
    int     bits = 0;
360
0
    codeBlock( org, orgStride, pred, recon, reconStride, quant, maxVal, dist, bits );
361
    // unnormalised domain: size^2 times the sample error; full resolution: 4 times the half-resolution error
362
0
    return { double( dist ) / ( size * size / 4 ), double( bits ) };
363
0
  }
364
};
365
366
// Quantization step and Lagrange multiplier of a QP, in sample units of the internal bit depth.
367
static double qpToStep( int qp, int bitDepth )
368
0
{
369
0
  return std::pow( 2.0, ( qp - 4 ) / 6.0 ) * double( 1 << ( bitDepth - 8 ) );
370
0
}
371
372
static double qpToLambda( int qp, int bitDepth )
373
0
{
374
0
  return 0.57 * std::pow( 2.0, ( qp - 12 ) / 3.0 ) * double( 1 << ( 2 * ( bitDepth - 8 ) ) );
375
0
}
376
377
// ====================================================================================================================
378
// Stage
379
// ====================================================================================================================
380
381
CuTree::CuTree()
382
0
  : m_codeBlock    { tplCodeBlockCore<8>, tplCodeBlockCore<16> }
383
0
  , m_predBlock    { tplPredBlockCore<8>, tplPredBlockCore<16> }
384
0
  , m_downsampleRow( tplDownsampleRowCore )
385
0
  , m_encCfg       ( nullptr )
386
0
  , m_threadPool   ( nullptr )
387
0
  , m_motionSearch ( true )
388
0
  , m_hadFunc      ( nullptr )
389
0
  , m_lookAheadPics( 0 )
390
0
  , m_blkSizeIdx   ( TPL_BLK_8 )
391
0
  , m_blkSize      ( 8 )
392
0
  , m_blkWidth     ( 0 )
393
0
  , m_blkHeight    ( 0 )
394
0
  , m_qpBDOffset   ( 0 )
395
0
  , m_lambdaLeaf   ( 0.0 )
396
0
{
397
0
}
398
399
CuTree::~CuTree()
400
0
{
401
0
}
402
403
int CuTree::getNumLookAheadPics( const VVEncCfg& encCfg )
404
0
{
405
  // the key picture of a GOP is referenced by the whole next GOP
406
0
  return encCfg.m_GOPSize;
407
0
}
408
409
void CuTree::init( const VVEncCfg& encCfg, NoMallocThreadPool* threadPool )
410
0
{
411
0
  m_encCfg        = &encCfg;
412
0
  m_threadPool    = threadPool;
413
0
  m_lookAheadPics = getNumLookAheadPics( encCfg );
414
  // the configured size is that of the full-resolution block, 16 or 32
415
0
  m_blkSizeIdx    = encCfg.m_cuTreeBlockSize == 32 ? TPL_BLK_16 : TPL_BLK_8;
416
0
  m_blkSize       = tplBlkSide( m_blkSizeIdx );
417
0
  m_blkWidth      = ( encCfg.m_PadSourceWidth  / 2 ) / m_blkSize;
418
0
  m_blkHeight     = ( encCfg.m_PadSourceHeight / 2 ) / m_blkSize;
419
0
  m_qpBDOffset    = 6 * ( encCfg.m_internalBitDepth[CH_L] - 8 );
420
0
  m_motionSearch.init( encCfg, true, threadPool );
421
#if defined( TARGET_SIMD_X86 ) && ENABLE_SIMD_OPT
422
  initCuTree_X86();
423
#endif
424
0
  m_rdCost.create();
425
  // the Hadamard SATD of the block size; setDistParam picks it by the block width
426
0
  const CPelBuf blk( nullptr, m_blkSize, m_blkSize );
427
0
  m_hadFunc       = m_rdCost.setDistParam( blk, blk, encCfg.m_internalBitDepth[CH_L], DF_HAD ).distFunc;
428
429
  // TPL weighs rate with the Lagrange multiplier of the unreferenced pictures, the top temporal layer.
430
0
  GOPEntry topEntry;
431
0
  topEntry.setDefaultGOPEntry();
432
0
  topEntry.copyFromGopCfg( encCfg.m_GOPList[0] );
433
0
  for( int i = 1; i < encCfg.m_GOPSize && i < VVENC_MAX_GOP; i++ )
434
0
  {
435
0
    if( encCfg.m_GOPList[i].m_temporalId > topEntry.m_temporalId ) topEntry.copyFromGopCfg( encCfg.m_GOPList[i] );
436
0
  }
437
0
  topEntry.m_vtl = std::min( 5, encCfg.m_maxTLayer );
438
0
  const int leafQp = Clip3( -m_qpBDOffset, MAX_QP, EncSlice::getCascadeQP( encCfg, topEntry, false, encCfg.m_QP, m_qpBDOffset ) );
439
0
  m_lambdaLeaf    = qpToLambda( leafQp, encCfg.m_internalBitDepth[CH_L] );
440
0
}
441
442
// The rows of one xForEachRow call, shared by the stage thread and the helper tasks. A helper that starts after
443
// the call has returned still holds them and finds no row left.
444
struct CuTreeRows
445
{
446
  std::atomic<int>                  next{ 0 };
447
  int                               done    = 0;   // rows finished, guarded by mutex
448
  int                               numRows = 0;
449
  const std::function<void( int )>* fn      = nullptr;
450
  std::mutex                        mutex;
451
  std::condition_variable           allDone;
452
453
  void work()
454
0
  {
455
0
    for( int row = next++; row < numRows; row = next++ )
456
0
    {
457
0
      ( *fn )( row );
458
0
      std::lock_guard<std::mutex> lock( mutex );
459
0
      if( ++done == numRows )
460
0
      {
461
0
        allDone.notify_one();
462
0
      }
463
0
    }
464
0
  }
465
466
  void waitAllDone()
467
0
  {
468
0
    std::unique_lock<std::mutex> lock( mutex );
469
0
    allDone.wait( lock, [this] { return done == numRows; } );
470
0
  }
471
};
472
473
// Runs fn( row ) for rows 0 .. numRows - 1. The stage thread works through the rows itself and workers of the
474
// thread pool join in when they are free. Waiting for row tasks instead stalled the stage thread, and with it the
475
// stages before the encoder, behind the encoder's queued CTU tasks. Rows write disjoint data, so the result does
476
// not depend on which thread runs which row.
477
void CuTree::xForEachRow( int numRows, const std::function<void( int )>& fn ) const
478
0
{
479
0
  auto rows     = std::make_shared<CuTreeRows>();
480
0
  rows->numRows = numRows;
481
0
  rows->fn      = &fn;
482
483
0
  const int numHelpers = m_threadPool ? std::min( m_threadPool->numThreads(), numRows - 1 ) : 0;
484
0
  for( int i = 0; i < numHelpers; i++ )
485
0
  {
486
0
    m_threadPool->addBarrierTask( []( int, void* param )
487
0
    {
488
0
      std::unique_ptr<std::shared_ptr<CuTreeRows>> held( static_cast<std::shared_ptr<CuTreeRows>*>( param ) );
489
0
      ( *held )->work();
490
0
      return true;
491
0
    }, new std::shared_ptr<CuTreeRows>( rows ) );
492
0
  }
493
0
  rows->work();
494
0
  rows->waitAllDone(); // for the rows helpers took and still run
495
0
}
496
497
// Halves the luma resolution with 2x2 averages and pads the result.
498
void CuTree::xDownsample( const PelStorage& src, PelStorage& dst ) const
499
0
{
500
0
  const CPelBuf in      = src.Y();
501
0
  const int width       = in.width / 2;
502
0
  const int height      = in.height / 2;
503
0
  const int rowsPerTask = 16;
504
0
  dst.create( CHROMA_400, Area( 0, 0, width, height ), 0, LOWRES_PAD );
505
0
  const PelBuf out      = dst.Y();
506
507
0
  xForEachRow( ( height + rowsPerTask - 1 ) / rowsPerTask, [&]( int task )
508
0
  {
509
0
    for( int y = task * rowsPerTask; y < std::min( height, ( task + 1 ) * rowsPerTask ); y++ )
510
0
    {
511
0
      m_downsampleRow( in.buf + 2 * y * in.stride, in.stride, out.buf + y * out.stride, width );
512
0
    }
513
0
  } );
514
0
  dst.extendBorderPel( LOWRES_PAD, LOWRES_PAD );
515
0
}
516
517
void CuTree::initPicture( Picture* pic )
518
0
{
519
0
  std::unique_ptr<Frame> frame( new Frame );
520
0
  frame->poc = pic->poc;
521
522
0
  xDownsample( pic->getOrigBuffer(), frame->lowRes[0] );
523
0
  for( int level = 1; level < NUM_LEVELS; level++ )
524
0
  {
525
0
    xDownsample( frame->lowRes[level - 1], frame->lowRes[level] );
526
0
  }
527
528
0
  m_frames[pic->poc] = std::move( frame );
529
0
}
530
531
void CuTree::processPictures( const PicList& picList, AccessUnitList& auList, PicList& doneList, PicList& freeList )
532
0
{
533
0
  int batchEndPoc = 0;
534
0
  if( picList.empty() || ! xIsBatchReady( picList, batchEndPoc ) )
535
0
  {
536
0
    return;
537
0
  }
538
539
  // Every picture is coded from reconstructed references, so the references go first: analyse in coding order.
540
0
  const bool flush = picList.back()->isFlush;
541
0
  std::vector<Picture*> codingOrder( picList.begin(), picList.end() );
542
0
  std::sort( codingOrder.begin(), codingOrder.end(), []( const Picture* a, const Picture* b ) { return a->gopEntry->m_codingNum < b->gopEntry->m_codingNum; } );
543
0
  for( auto pic : codingOrder )
544
0
  {
545
0
    Frame* frame = xFindFrame( pic->poc );
546
0
    if( frame->analysed )
547
0
    {
548
0
      continue;
549
0
    }
550
0
    xAnalyse( *frame, pic, flush );
551
552
    // In a hierarchical GOP the top temporal layer is never referenced, its pictures are not needed any more.
553
0
    if( frame->analysed && m_encCfg->m_maxTLayer > 0 && pic->gopEntry->m_temporalId >= m_encCfg->m_maxTLayer )
554
0
    {
555
0
      for( auto& level : frame->lowRes ) level.destroy();
556
0
      frame->recon.destroy();
557
0
      frame->hasImages = false;
558
0
    }
559
0
  }
560
561
0
  xPropagate( picList.front()->poc );
562
0
  xSetPicQpOffsets( codingOrder, batchEndPoc );
563
564
0
  for( auto pic : picList )
565
0
  {
566
0
    if( pic->poc > batchEndPoc )
567
0
    {
568
0
      break;
569
0
    }
570
0
    xSetQpOffsets( *xFindFrame( pic->poc ), pic );
571
0
    doneList.push_back( pic );
572
0
    freeList.push_back( pic );
573
0
  }
574
575
  // Keep the pictures from the batch's last one on, which the next GOP references, and every picture a cached
576
  // later picture references. Pictures that have not arrived reference only pictures that are still cached.
577
0
  int oldestPocNeeded = batchEndPoc;
578
0
  for( auto itr = m_frames.upper_bound( batchEndPoc ); itr != m_frames.end(); itr++ )
579
0
  {
580
0
    for( int refPoc : itr->second->refPoc )
581
0
    {
582
0
      if( refPoc != NO_REF ) oldestPocNeeded = std::min( oldestPocNeeded, refPoc );
583
0
    }
584
0
  }
585
0
  m_frames.erase( m_frames.begin(), m_frames.lower_bound( oldestPocNeeded ) );
586
0
}
587
588
// A batch is the pictures of the GOP that comes next. It is ready when that GOP and the look-ahead after it
589
// have arrived, or when the encoder is flushed.
590
bool CuTree::xIsBatchReady( const PicList& picList, int& batchEndPoc ) const
591
0
{
592
0
  const int  gopNum = picList.front()->gopEntry->m_gopNum;
593
0
  const bool flush  = picList.back()->isFlush;
594
0
  bool gopComplete  = false;
595
596
0
  batchEndPoc = picList.front()->poc;
597
0
  for( auto pic : picList )
598
0
  {
599
0
    if( pic->gopEntry->m_gopNum != gopNum )
600
0
    {
601
0
      gopComplete = true;
602
0
      break;
603
0
    }
604
0
    batchEndPoc = pic->poc;
605
0
  }
606
607
0
  return flush || ( gopComplete && picList.back()->poc >= batchEndPoc + m_lookAheadPics );
608
0
}
609
610
CuTree::Frame* CuTree::xFindFrame( int poc ) const
611
0
{
612
0
  auto itr = m_frames.find( poc );
613
0
  return itr == m_frames.end() ? nullptr : itr->second.get();
614
0
}
615
616
// Codes every block of the picture as TPL does: picks intra, L0, L1 or bi-prediction by rate-distortion cost with
617
// the original references, then codes the chosen mode with the reconstructed references and keeps the
618
// reconstruction for the pictures that reference this one (Han et al., Eqs. 6 to 11).
619
void CuTree::xAnalyse( Frame& frame, Picture* pic, bool flush )
620
0
{
621
0
  const GOPEntry& gopEntry = *pic->gopEntry;
622
0
  const int bitDepth       = m_encCfg->m_internalBitDepth[CH_L];
623
0
  const int maxVal         = ( 1 << bitDepth ) - 1;
624
0
  const int numBlocks      = m_blkWidth * m_blkHeight;
625
626
0
  frame.codingNum = gopEntry.m_codingNum;
627
0
  frame.isIntra   = gopEntry.m_sliceType == 'I';
628
0
  frame.qp        = Clip3( -m_qpBDOffset, MAX_QP, EncSlice::getCascadeQP( *m_encCfg, gopEntry, frame.isIntra, m_encCfg->m_QP, m_qpBDOffset ) );
629
630
  // the nearest reference picture of each list, which must be reconstructed already
631
0
  Frame* refs[2] = { nullptr, nullptr };
632
0
  if( ! frame.isIntra )
633
0
  {
634
0
    const int lastPoc = m_frames.rbegin()->first;
635
0
    for( int l = 0; l < 2; l++ )
636
0
    {
637
0
      if( gopEntry.m_numRefPicsActive[l] <= 0 )
638
0
      {
639
0
        continue;
640
0
      }
641
0
      const int refPoc = frame.poc - gopEntry.m_deltaRefPics[l][0];
642
0
      if( l == 1 && refPoc == frame.refPoc[0] )
643
0
      {
644
0
        continue;
645
0
      }
646
0
      if( refPoc > lastPoc && ! flush )
647
0
      {
648
0
        return; // the reference has not arrived yet, analyse the picture later
649
0
      }
650
0
      refs[l] = xFindFrame( refPoc );
651
0
      if( refs[l] && ! refs[l]->hasImages )
652
0
      {
653
0
        refs[l] = nullptr; // not expected: only unreferenced pictures drop their images
654
0
      }
655
0
      if( refs[l] && ! refs[l]->analysed )
656
0
      {
657
0
        return; // the reference is not reconstructed yet
658
0
      }
659
0
      if( refs[l] )
660
0
      {
661
0
        frame.refPoc[l] = refPoc;
662
0
      }
663
0
    }
664
0
  }
665
0
  for( int l = 0; l < 2; l++ )
666
0
  {
667
0
    if( refs[l] )
668
0
    {
669
0
      xEstimateMotion( frame, *refs[l], frame.mvs[l] );
670
0
    }
671
0
  }
672
673
  // samples outside the block grid keep their source values
674
0
  const CPelBuf org = frame.lowRes[0].Y();
675
0
  frame.recon.create( CHROMA_400, Area( 0, 0, org.width, org.height ), 0, LOWRES_PAD );
676
0
  frame.recon.Y().copyFrom( org );
677
0
  frame.predMode.assign( numBlocks, CTP_INTRA );
678
0
  frame.distRec.assign( numBlocks, 0.f );
679
0
  for( int l = 0; l < 2; l++ )
680
0
  {
681
0
    frame.distSrc  [l].assign( numBlocks, 0.f );
682
0
    frame.deltaDist[l].assign( numBlocks, 0.f );
683
0
    frame.deltaRate[l].assign( numBlocks, 0.f );
684
0
  }
685
686
0
  const int            n         = m_blkSize;
687
0
  const TplPredBlockFn predBlock = m_predBlock[m_blkSizeIdx];
688
0
  const BlockSatd      satd      = { m_hadFunc, bitDepth, n };
689
0
  const double         step      = qpToStep( frame.qp, bitDepth ) / 2;
690
0
  const double         lambda    = qpToLambda( frame.qp, bitDepth );
691
  // rounding offsets of HM's quantizer without RDOQ
692
0
  const BlockCoder     intraCoder( m_codeBlock[m_blkSizeIdx], n, step, 1.0 / 3 );
693
0
  const BlockCoder     interCoder( m_codeBlock[m_blkSizeIdx], n, step, 1.0 / 6 );
694
0
  const PelBuf         recon     = frame.recon.Y();
695
696
0
  xForEachRow( m_blkHeight, [&]( int by )
697
0
  {
698
0
    Pel predSrc[2][MAX_BLK_SIZE * MAX_BLK_SIZE], predRec[2][MAX_BLK_SIZE * MAX_BLK_SIZE];
699
0
    Pel pred[MAX_BLK_SIZE * MAX_BLK_SIZE], predIntra[MAX_BLK_SIZE * MAX_BLK_SIZE];
700
701
0
    for( int bx = 0; bx < m_blkWidth; bx++ )
702
0
    {
703
0
      const int  idx    = by * m_blkWidth + bx;
704
0
      const int  x      = bx * n, y = by * n;
705
0
      const Pel* orgBlk = org.buf + y * org.stride + x;
706
0
      Pel*       recBlk = recon.buf + y * recon.stride + x;
707
0
      auto cost = [&]( const BlockCoder::Result& r ) { return r.dist + lambda * r.rate; };
708
709
0
      if( n == 16 ) predictIntra<16>( org, x, y, bitDepth, satd, predIntra );
710
0
      else          predictIntra<8> ( org, x, y, bitDepth, satd, predIntra );
711
0
      const BlockCoder::Result intra = intraCoder.code( orgBlk, org.stride, predIntra, nullptr, 0, maxVal );
712
713
      // mode decision with the original references
714
0
      int    bestMode = CTP_INTRA;
715
0
      double bestCost = cost( intra );
716
0
      BlockCoder::Result src[2] = {};
717
0
      for( int l = 0; l < 2; l++ )
718
0
      {
719
0
        if( ! refs[l] ) continue;
720
0
        predictBlock( predBlock, n, refs[l]->lowRes[0].Y(), x, y, frame.mvs[l].get( bx, by ), predSrc[l] );
721
0
        src[l] = interCoder.code( orgBlk, org.stride, predSrc[l], nullptr, 0, maxVal );
722
0
        if( cost( src[l] ) < bestCost )
723
0
        {
724
0
          bestCost = cost( src[l] );
725
0
          bestMode = l == 0 ? CTP_L0 : CTP_L1;
726
0
        }
727
0
      }
728
0
      if( refs[0] && refs[1] )
729
0
      {
730
0
        for( int i = 0; i < n * n; i++ ) pred[i] = Pel( ( predSrc[0][i] + predSrc[1][i] + 1 ) >> 1 );
731
0
        const BlockCoder::Result srcBi = interCoder.code( orgBlk, org.stride, pred, nullptr, 0, maxVal );
732
0
        if( cost( srcBi ) < bestCost )
733
0
        {
734
0
          bestCost = cost( srcBi );
735
0
          bestMode = CTP_BI;
736
0
        }
737
0
      }
738
0
      frame.predMode[idx] = uint8_t( bestMode );
739
740
0
      if( bestMode == CTP_INTRA )
741
0
      {
742
        // intra cuts the dependency
743
0
        frame.distRec[idx] = float( intraCoder.code( orgBlk, org.stride, predIntra, recBlk, recon.stride, maxVal ).dist );
744
0
        continue;
745
0
      }
746
747
      // code the chosen mode from the reconstructed references
748
0
      for( int l = 0; l < 2; l++ )
749
0
      {
750
0
        if( refs[l] ) predictBlock( predBlock, n, refs[l]->recon.Y(), x, y, frame.mvs[l].get( bx, by ), predRec[l] );
751
0
      }
752
0
      if( bestMode != CTP_BI )
753
0
      {
754
0
        const int l = bestMode == CTP_L0 ? 0 : 1;
755
0
        const BlockCoder::Result rec = interCoder.code( orgBlk, org.stride, predRec[l], recBlk, recon.stride, maxVal );
756
0
        frame.distRec  [idx]    = float( rec.dist );
757
0
        frame.distSrc  [l][idx] = float( std::min( src[l].dist, rec.dist ) );
758
0
        frame.deltaDist[l][idx] = float( std::max( 0.0, rec.dist - src[l].dist ) );
759
0
        frame.deltaRate[l][idx] = float( std::max( 0.0, rec.rate - src[l].rate ) );
760
0
        continue;
761
0
      }
762
763
      // bi-prediction: the dependency on each reference is measured by keeping the other one reconstructed
764
0
      for( int i = 0; i < n * n; i++ ) pred[i] = Pel( ( predRec[0][i] + predRec[1][i] + 1 ) >> 1 );
765
0
      const BlockCoder::Result rec = interCoder.code( orgBlk, org.stride, pred, recBlk, recon.stride, maxVal );
766
0
      frame.distRec[idx] = float( rec.dist );
767
0
      for( int l = 0; l < 2; l++ )
768
0
      {
769
0
        const Pel* other = predRec[1 - l];
770
0
        for( int i = 0; i < n * n; i++ ) pred[i] = Pel( ( predSrc[l][i] + other[i] + 1 ) >> 1 );
771
0
        const BlockCoder::Result mixed = interCoder.code( orgBlk, org.stride, pred, nullptr, 0, maxVal );
772
0
        frame.distSrc  [l][idx] = float( std::min( mixed.dist, rec.dist ) );
773
0
        frame.deltaDist[l][idx] = float( std::max( 0.0, rec.dist - mixed.dist ) );
774
0
        frame.deltaRate[l][idx] = float( std::max( 0.0, rec.rate - mixed.rate ) );
775
0
      }
776
0
    }
777
0
  } );
778
779
0
  frame.recon.extendBorderPel( LOWRES_PAD, LOWRES_PAD );
780
0
  frame.propDist.assign( numBlocks, 0.0 );
781
0
  frame.propRate.assign( numBlocks, 0.0 );
782
0
  frame.analysed = true;
783
0
}
784
785
// Hierarchical motion search: MCTF's integer search of 2N x 2N blocks at eighth resolution, refined by xSearchInteger
786
// at quarter and half resolution, then the N x N analysis blocks to quarter sample by xSearchFractional.
787
void CuTree::xEstimateMotion( const Frame& cur, const Frame& ref, Array2D<MotionVector>& mvs ) const
788
0
{
789
0
  const int searchSize = 2 * m_blkSize;
790
0
  Array2D<MotionVector> integerMvs[NUM_LEVELS];
791
792
0
  for( int level = NUM_LEVELS - 1; level >= 0; level-- )
793
0
  {
794
0
    const CPelBuf picLevel = cur.lowRes[level].Y();
795
0
    integerMvs[level].allocate( picLevel.width / searchSize + 1, picLevel.height / searchSize + 1 );
796
0
    if( level == NUM_LEVELS - 1 )
797
0
    {
798
0
      m_motionSearch.motionEstimationLuma( integerMvs[level], cur.lowRes[level], ref.lowRes[level], LOWRES_PAD, searchSize, nullptr, 2 );
799
0
    }
800
0
    else
801
0
    {
802
0
      xSearchInteger( cur.lowRes[level], ref.lowRes[level], integerMvs[level + 1], integerMvs[level] );
803
0
    }
804
0
  }
805
806
0
  const CPelBuf pic = cur.lowRes[0].Y();
807
0
  mvs.allocate( pic.width / m_blkSize + 1, pic.height / m_blkSize + 1 );
808
0
  xSearchFractional( cur, ref, integerMvs[0], mvs );
809
0
}
810
811
// The positions one block's motion search has checked. Checking a position again cannot change the result: its error
812
// is the same, and only a smaller error replaces the best. Neighbouring blocks often share a vector, and the
813
// refinement steps come back to positions checked before, so the searches skip them.
814
class CheckedPositions
815
{
816
public:
817
  // Records the position; false when it was checked before.
818
  bool add( int x, int y )
819
0
  {
820
0
    for( int i = 0; i < m_num; i++ )
821
0
    {
822
0
      if( m_x[i] == x && m_y[i] == y )
823
0
      {
824
0
        return false;
825
0
      }
826
0
    }
827
0
    if( m_num < CAPACITY ) // more positions than any search checks, see xSearchInteger
828
0
    {
829
0
      m_x[m_num]   = x;
830
0
      m_y[m_num++] = y;
831
0
    }
832
0
    return true;
833
0
  }
834
835
private:
836
  static constexpr int CAPACITY = 48;
837
  int m_x[CAPACITY], m_y[CAPACITY];
838
  int m_num = 0;
839
};
840
841
// Integer motion of the 2N x 2N blocks of a level from the coarser level: the best of the parent's and its
842
// neighbours' vectors (doubled) and zero, refined by a small diamond search; at most 1 + 9 + 8 * 4 positions.
843
void CuTree::xSearchInteger( const PelStorage& curPic, const PelStorage& refPic, const Array2D<MotionVector>& coarse, Array2D<MotionVector>& mvs ) const
844
0
{
845
0
  const int     bs     = 2 * m_blkSize;
846
0
  const CPelBuf org    = curPic.Y();
847
0
  const CPelBuf refBuf = refPic.Y();
848
0
  if( org.height < m_blkSize || org.width < m_blkSize )
849
0
  {
850
0
    return;
851
0
  }
852
0
  const int     rows   = ( org.height - m_blkSize ) / bs + 1;
853
0
  const int     cols   = ( org.width  - m_blkSize ) / bs + 1;
854
855
0
  xForEachRow( rows, [&]( int by )
856
0
  {
857
0
    for( int bx = 0; bx < cols; bx++ )
858
0
    {
859
0
      const int  x  = bx * bs, y = by * bs;
860
0
      const int  bw = std::min<int>( bs, org.width  - x ) & ~7;
861
0
      const int  bh = std::min<int>( bs, org.height - y ) & ~7;
862
0
      const Pel* orgBlk = org.buf + y * org.stride + x;
863
      // keep the displaced block inside the padded reference
864
0
      const int  minX = -LOWRES_PAD - x, maxX = int( refBuf.width )  + LOWRES_PAD - bw - x;
865
0
      const int  minY = -LOWRES_PAD - y, maxY = int( refBuf.height ) + LOWRES_PAD - bh - y;
866
0
      MotionVector     best;
867
0
      CheckedPositions checked;
868
0
      auto check = [&]( int dx, int dy )
869
0
      {
870
0
        dx = Clip3( minX, maxX, dx );
871
0
        dy = Clip3( minY, maxY, dy );
872
0
        if( ! checked.add( dx, dy ) )
873
0
        {
874
0
          return;
875
0
        }
876
0
        const Pel* refBlk = refBuf.buf + ( y + dy ) * refBuf.stride + x + dx;
877
0
        const int  error  = m_motionSearch.m_motionErrorLumaInt8( orgBlk, org.stride, refBlk, refBuf.stride, bw, bh, best.error );
878
0
        if( error < best.error )
879
0
        {
880
0
          best.set( dx * MV_PREC, dy * MV_PREC, error );
881
0
        }
882
0
      };
883
884
0
      check( 0, 0 );
885
0
      for( int py = by / 2 - 1; py <= by / 2 + 1; py++ )
886
0
      {
887
0
        for( int px = bx / 2 - 1; px <= bx / 2 + 1; px++ )
888
0
        {
889
0
          if( px >= 0 && py >= 0 && px < coarse.w() && py < coarse.h() && coarse.get( px, py ).error != INT_LEAST32_MAX )
890
0
          {
891
0
            const MotionVector& c = coarse.get( px, py );
892
0
            check( 2 * c.x / MV_PREC, 2 * c.y / MV_PREC );
893
0
          }
894
0
        }
895
0
      }
896
0
      for( int iter = 0; iter < 8; iter++ )
897
0
      {
898
0
        const MotionVector center = best;
899
0
        check( center.x / MV_PREC - 1, center.y / MV_PREC );
900
0
        check( center.x / MV_PREC + 1, center.y / MV_PREC );
901
0
        check( center.x / MV_PREC, center.y / MV_PREC - 1 );
902
0
        check( center.x / MV_PREC, center.y / MV_PREC + 1 );
903
0
        if( best.x == center.x && best.y == center.y )
904
0
        {
905
0
          break;
906
0
        }
907
0
      }
908
0
      mvs.get( bx, by ) = best;
909
0
    }
910
0
  } );
911
0
}
912
913
// Motion of the N x N analysis blocks from the integer motion of the 2N x 2N blocks: the best of the parent's and
914
// its neighbours' vectors and zero, refined to half and then quarter sample with the bilinear predictor.
915
void CuTree::xSearchFractional( const Frame& cur, const Frame& ref, const Array2D<MotionVector>& coarse, Array2D<MotionVector>& mvs ) const
916
0
{
917
0
  const CPelBuf org    = cur.lowRes[0].Y();
918
0
  const CPelBuf refBuf = ref.lowRes[0].Y();
919
0
  const int            n         = m_blkSize;
920
0
  const TplPredBlockFn predBlock = m_predBlock[m_blkSizeIdx];
921
0
  const int            rows      = org.height / n;
922
0
  const int            cols      = org.width  / n;
923
924
0
  xForEachRow( rows, [&]( int by )
925
0
  {
926
0
    Pel pred[MAX_BLK_SIZE * MAX_BLK_SIZE];
927
0
    for( int bx = 0; bx < cols; bx++ )
928
0
    {
929
0
      const int  x      = bx * n, y = by * n;
930
0
      const Pel* orgBlk = org.buf + y * org.stride + x;
931
0
      MotionVector     best;
932
0
      CheckedPositions checked;
933
0
      auto check = [&]( int mvx, int mvy )
934
0
      {
935
0
        if( ! checked.add( mvx, mvy ) )
936
0
        {
937
0
          return;
938
0
        }
939
0
        MotionVector mv;
940
0
        mv.set( mvx, mvy, 0 );
941
0
        predictBlock( predBlock, n, refBuf, x, y, mv, pred );
942
0
        const int error = m_motionSearch.m_motionErrorLumaInt8( orgBlk, org.stride, pred, n, n, n, best.error );
943
0
        if( error < best.error )
944
0
        {
945
0
          best.set( mvx, mvy, error );
946
0
        }
947
0
      };
948
949
0
      check( 0, 0 );
950
0
      for( int py = by / 2 - 1; py <= by / 2 + 1; py++ )
951
0
      {
952
0
        for( int px = bx / 2 - 1; px <= bx / 2 + 1; px++ )
953
0
        {
954
0
          if( px >= 0 && py >= 0 && px < coarse.w() && py < coarse.h() )
955
0
          {
956
0
            const MotionVector& c = coarse.get( px, py );
957
0
            check( c.x, c.y );
958
0
          }
959
0
        }
960
0
      }
961
0
      for( int step = MV_PREC / 2; step >= MV_PREC / 4; step /= 2 )
962
0
      {
963
0
        const MotionVector center = best;
964
0
        for( int dy = -step; dy <= step; dy += step )
965
0
        {
966
0
          for( int dx = -step; dx <= step; dx += step )
967
0
          {
968
0
            if( dx || dy )
969
0
            {
970
0
              check( center.x + dx, center.y + dy );
971
0
            }
972
0
          }
973
0
        }
974
0
      }
975
0
      mvs.get( bx, by ) = best;
976
0
    }
977
0
  } );
978
0
}
979
980
// Walks the analysed pictures from firstPoc on in reverse coding order and passes each block's dependency to the
981
// reference blocks it predicts from (Han et al., Eqs. 12, 16 and 17):
982
//   dD_ref += dD + dD / D_rec * D_prop
983
//   dR_ref += dR + log2( 2^(2 R_prop) / ( D_src / D_rec * 2^(2 R_prop) + 1 - D_src / D_rec ) )
984
void CuTree::xPropagate( int firstPoc )
985
0
{
986
0
  std::vector<Frame*> order;
987
0
  for( auto& entry : m_frames )
988
0
  {
989
0
    Frame& frame = *entry.second;
990
0
    std::fill( frame.propDist.begin(), frame.propDist.end(), 0.0 );
991
0
    std::fill( frame.propRate.begin(), frame.propRate.end(), 0.0 );
992
0
    if( frame.poc >= firstPoc && frame.analysed && ! frame.isIntra )
993
0
    {
994
0
      order.push_back( &frame );
995
0
    }
996
0
  }
997
0
  std::sort( order.begin(), order.end(), []( const Frame* a, const Frame* b ) { return a->codingNum > b->codingNum; } );
998
999
0
  for( Frame* frame : order )
1000
0
  {
1001
0
    Frame* refs[2] = { xFindFrame( frame->refPoc[0] ), xFindFrame( frame->refPoc[1] ) };
1002
1003
0
    for( int by = 0; by < m_blkHeight; by++ )
1004
0
    {
1005
0
      for( int bx = 0; bx < m_blkWidth; bx++ )
1006
0
      {
1007
0
        const int idx  = by * m_blkWidth + bx;
1008
0
        const int mode = frame->predMode[idx];
1009
0
        if( mode == CTP_INTRA || frame->distRec[idx] <= 0.f )
1010
0
        {
1011
0
          continue;
1012
0
        }
1013
0
        const double distRec  = frame->distRec[idx];
1014
0
        const double propRate = frame->propRate[idx];
1015
0
        const double growth   = propRate > 0.0 ? std::pow( 2.0, 2.0 * propRate ) : 1.0;
1016
1017
0
        for( int l = 0; l < 2; l++ )
1018
0
        {
1019
0
          const bool usesRef = mode == CTP_BI || mode == ( l == 0 ? CTP_L0 : CTP_L1 );
1020
0
          if( ! usesRef || ! refs[l] )
1021
0
          {
1022
0
            continue;
1023
0
          }
1024
0
          const double deltaDist = frame->deltaDist[l][idx];
1025
0
          const double srcRatio  = frame->distSrc[l][idx] / distRec;
1026
0
          const double dist      = deltaDist + deltaDist / distRec * frame->propDist[idx];
1027
0
          const double rate      = frame->deltaRate[l][idx] + ( propRate > 0.0 ? std::log2( growth / ( srcRatio * growth + 1.0 - srcRatio ) ) : 0.0 );
1028
0
          if( dist > 0.0 || rate > 0.0 )
1029
0
          {
1030
0
            xDistribute( *refs[l], bx, by, frame->mvs[l].get( bx, by ), dist, rate );
1031
0
          }
1032
0
        }
1033
0
      }
1034
0
    }
1035
0
  }
1036
0
}
1037
1038
// Adds the dependency to the up to four reference blocks that the displaced block overlaps, in proportion to the
1039
// overlapping area. Parts outside the picture are dropped.
1040
void CuTree::xDistribute( Frame& ref, int bx, int by, const MotionVector& mv, double dist, double rate ) const
1041
0
{
1042
0
  const int log2Units = floorLog2( m_blkSize * MV_PREC );   // log2 of one block in motion vector units
1043
0
  const int units     = 1 << log2Units;
1044
0
  const int posX = bx * units + mv.x;
1045
0
  const int posY = by * units + mv.y;
1046
  // floor division and remainder; positions can be negative, and >> rounds those down
1047
0
  const int x0 = posX >> log2Units;
1048
0
  const int y0 = posY >> log2Units;
1049
0
  const int fx = posX & ( units - 1 );
1050
0
  const int fy = posY & ( units - 1 );
1051
0
  const double distScale = dist / double( units * units );
1052
0
  const double rateScale = rate / double( units * units );
1053
1054
0
  const int xs[2] = { x0, x0 + 1 };
1055
0
  const int ys[2] = { y0, y0 + 1 };
1056
0
  const int ws[2] = { units - fx, fx };
1057
0
  const int hs[2] = { units - fy, fy };
1058
1059
0
  for( int j = 0; j < 2; j++ )
1060
0
  {
1061
0
    if( ys[j] < 0 || ys[j] >= m_blkHeight || hs[j] == 0 )
1062
0
    {
1063
0
      continue;
1064
0
    }
1065
0
    for( int i = 0; i < 2; i++ )
1066
0
    {
1067
0
      if( xs[i] < 0 || xs[i] >= m_blkWidth || ws[i] == 0 )
1068
0
      {
1069
0
        continue;
1070
0
      }
1071
0
      const int    idx  = ys[j] * m_blkWidth + xs[i];
1072
0
      const double area = double( ws[i] * hs[j] );
1073
0
      ref.propDist[idx] += distScale * area;
1074
0
      ref.propRate[idx] += rateScale * area;
1075
0
    }
1076
0
  }
1077
0
}
1078
1079
// The picture-level QP offsets of BIM 2 (MCTF): MCTF's offset for the pictures it analysed, and for the others 3/4 of
1080
// the mean of their two references' offsets, as EncGOP's initPicAuxQPOffsets derives them for BIM 2. They lower the QP
1081
// of whole pictures in static content, which CU-tree's CTU offsets, relative to the picture's own mean dependency,
1082
// cannot do. Runs in coding order, so the references come first; MCTF has to have run, its offsets are read before
1083
// xSetQpOffsets replaces them.
1084
void CuTree::xSetPicQpOffsets( const std::vector<Picture*>& codingOrder, int batchEndPoc )
1085
0
{
1086
0
  for( auto pic : codingOrder )
1087
0
  {
1088
0
    Frame* frame = xFindFrame( pic->poc );
1089
0
    if( pic->poc > batchEndPoc || ! frame )
1090
0
    {
1091
0
      continue;
1092
0
    }
1093
0
    if( ! pic->m_picShared->m_ctuBimQpOffset.empty() )
1094
0
    {
1095
0
      frame->picQpOffset = pic->m_picShared->m_picAuxQpOffset;
1096
0
      continue;
1097
0
    }
1098
0
    const Frame* ref0 = xFindFrame( frame->refPoc[0] );
1099
0
    const Frame* ref1 = xFindFrame( frame->refPoc[1] );
1100
0
    if( ref0 && ref1 )
1101
0
    {
1102
0
      const int sum      = ref0->picQpOffset + ref1->picQpOffset;
1103
0
      frame->picQpOffset = ( 3 * sum + ( sum < 0 ? 3 : 4 ) ) >> 3;   // rounding as in initPicAuxQPOffsets
1104
0
    }
1105
0
  }
1106
0
}
1107
1108
// The QP offset of each CTU (Han et al., Eqs. 19 to 21): with the dependency factor
1109
//   alpha = ( distortion + lambda * rate owed by later pictures ) / own distortion
1110
// of the CTU and of the picture, lambda_ctu = lambda * ( 1 + alpha_pic ) / ( 1 + alpha_ctu ). As lambda goes with
1111
// 2^(QP / 3), the QP offset is a multiple of -log2( beta ), beta = ( 1 + alpha_ctu ) / ( 1 + alpha_pic ). Like
1112
// libaom's superblock QP, the change is damped to the square root of beta for intra pictures and for CTUs below
1113
// the picture's average, and to the fourth root for CTUs above it in inter pictures. The offsets go where QPA
1114
// and the non-QPA path pick up the BIM offsets. The picture QP gets BIM 2's picture offset (xSetPicQpOffsets) and,
1115
// rounded, the mean of the CTU offsets, which the CTUs lose again: as with BIM 2, the CTU offsets average 0, so rate
1116
// control, which sets the picture QP itself, still gets the QP it chose.
1117
void CuTree::xSetQpOffsets( const Frame& frame, Picture* pic ) const
1118
0
{
1119
0
  const int ctuSize      = m_encCfg->m_bimCtuSize;
1120
0
  const int widthInCtus  = ( m_encCfg->m_PadSourceWidth  + ctuSize - 1 ) / ctuSize;
1121
0
  const int heightInCtus = ( m_encCfg->m_PadSourceHeight + ctuSize - 1 ) / ctuSize;
1122
0
  const int blksPerCtu   = ctuSize / ( 2 * m_blkSize );
1123
1124
0
  PicShared* picShared = pic->m_picShared;
1125
0
  picShared->m_ctuBimQpOffset.assign( widthInCtus * heightInCtus, 0 );
1126
0
  picShared->m_picAuxQpOffset = frame.picQpOffset;
1127
0
  if( m_blkWidth == 0 || m_blkHeight == 0 || ! frame.analysed )
1128
0
  {
1129
0
    return; // picture smaller than one analysis block, or not analysed
1130
0
  }
1131
1132
0
  auto blockDep = [&]( int idx ) { return frame.propDist[idx] + m_lambdaLeaf * frame.propRate[idx]; };
1133
1134
0
  double picDep = 0.0, picDist = 0.0;
1135
0
  for( int idx = 0; idx < m_blkWidth * m_blkHeight; idx++ )
1136
0
  {
1137
0
    picDep  += blockDep( idx );
1138
0
    picDist += frame.distRec[idx];
1139
0
  }
1140
0
  const double alphaPic = picDep / std::max( picDist, 1.0 );
1141
1142
0
  for( int cy = 0; cy < heightInCtus; cy++ )
1143
0
  {
1144
    // CTUs at the right and bottom edge may lie beyond the last full analysis block
1145
0
    const int by0 = std::min( cy * blksPerCtu, m_blkHeight - 1 );
1146
0
    const int by1 = std::max( by0 + 1, std::min( ( cy + 1 ) * blksPerCtu, m_blkHeight ) );
1147
0
    for( int cx = 0; cx < widthInCtus; cx++ )
1148
0
    {
1149
0
      const int bx0 = std::min( cx * blksPerCtu, m_blkWidth - 1 );
1150
0
      const int bx1 = std::max( bx0 + 1, std::min( ( cx + 1 ) * blksPerCtu, m_blkWidth ) );
1151
1152
0
      double dep = 0.0, dist = 0.0;
1153
0
      for( int by = by0; by < by1; by++ )
1154
0
      {
1155
0
        for( int bx = bx0; bx < bx1; bx++ )
1156
0
        {
1157
0
          dep  += blockDep( by * m_blkWidth + bx );
1158
0
          dist += frame.distRec[by * m_blkWidth + bx];
1159
0
        }
1160
0
      }
1161
0
      const double alphaCtu = dep / std::max( dist, 1.0 );
1162
0
      const double beta     = ( 1.0 + alphaCtu ) / ( 1.0 + alphaPic );
1163
0
      const double damping  = beta < 1.0 || frame.isIntra ? 0.5 : 0.25;
1164
0
      picShared->m_ctuBimQpOffset[cy * widthInCtus + cx] = int( std::lround( -m_encCfg->m_cuTreeScale * damping * std::log2( beta ) ) );
1165
0
    }
1166
0
  }
1167
1168
  // move the mean into the picture QP, rounded as MCTF does for BIM 2
1169
0
  const int numCtu = widthInCtus * heightInCtus;
1170
0
  int       sum    = 0;
1171
0
  for( int offset : picShared->m_ctuBimQpOffset ) sum += offset;
1172
0
  const int mean = ( sum + ( sum < 0 ? -( numCtu >> 1 ) : numCtu >> 1 ) ) / numCtu;
1173
0
  for( int& offset : picShared->m_ctuBimQpOffset ) offset -= mean;
1174
0
  picShared->m_picAuxQpOffset += mean;
1175
0
}
1176
1177
} // namespace vvenc
1178
1179
//! \}