|  | /* | 
|  | * Copyright (c) 2025, Alliance for Open Media. All rights reserved | 
|  | * | 
|  | * This source code is subject to the terms of the BSD 3-Clause Clear License | 
|  | * and the Alliance for Open Media Patent License 1.0. If the BSD 3-Clause Clear | 
|  | * License was not distributed with this source code in the LICENSE file, you | 
|  | * can obtain it at aomedia.org/license/software-license/bsd-3-c-c/.  If the | 
|  | * Alliance for Open Media Patent License 1.0 was not distributed with this | 
|  | * source code in the PATENTS file, you can obtain it at | 
|  | * aomedia.org/license/patent-license/. | 
|  | */ | 
|  |  | 
|  | #include <math.h> | 
|  | #include <stdlib.h> | 
|  | #include <string.h> | 
|  |  | 
|  | #include "third_party/googletest/src/googletest/include/gtest/gtest.h" | 
|  | #include "test/register_state_check.h" | 
|  | #include "test/function_equivalence_test.h" | 
|  |  | 
|  | #include "config/aom_config.h" | 
|  | #include "config/aom_dsp_rtcd.h" | 
|  | #include "config/av1_rtcd.h" | 
|  |  | 
|  | #include "aom/aom_integer.h" | 
|  | #include "av1/common/enums.h" | 
|  | #include "av1/encoder/trellis_quant.h" | 
|  |  | 
|  | using libaom_test::FunctionEquivalenceTest; | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | template <typename F, typename T> | 
|  | class TcqRateTest : public FunctionEquivalenceTest<F> { | 
|  | protected: | 
|  | static const int kIterations = 100000; | 
|  |  | 
|  | virtual ~TcqRateTest() {} | 
|  |  | 
|  | virtual void Execute(T *rate_tst) = 0; | 
|  |  | 
|  | void Common() { | 
|  | Execute(&rate_tst_); | 
|  |  | 
|  | ASSERT_EQ(rate_ref_.rate_zero[0], rate_tst_.rate_zero[0]); | 
|  | ASSERT_EQ(rate_ref_.rate_zero[1], rate_tst_.rate_zero[1]); | 
|  | ASSERT_EQ(rate_ref_.rate_eob[0], rate_tst_.rate_eob[0]); | 
|  | ASSERT_EQ(rate_ref_.rate_eob[1], rate_tst_.rate_eob[1]); | 
|  | for (int i = 0; i < 8; i++) { | 
|  | ASSERT_EQ(rate_ref_.rate[i], rate_tst_.rate[i]); | 
|  | } | 
|  | } | 
|  |  | 
|  | T rate_ref_; | 
|  | T rate_tst_; | 
|  | }; | 
|  |  | 
|  | ////////////////////////////////////////////////////////////////////////////// | 
|  | // TCQ Rate calculation functions. | 
|  | ////////////////////////////////////////////////////////////////////////////// | 
|  |  | 
|  | typedef void (*TcqRateLuma)(const struct tcq_param_t *p, | 
|  | const struct prequant_t *pq, | 
|  | const struct tcq_coeff_ctx_t *coeff_ctx, | 
|  | int blk_pos, int diag_ctx, int eob_rate, | 
|  | struct tcq_rate_t *rd); | 
|  | typedef libaom_test::FuncParam<TcqRateLuma> TcqRateLumaTestFuncs; | 
|  |  | 
|  | class TcqRateLumaTest : public TcqRateTest<TcqRateLuma, tcq_rate_t> { | 
|  | protected: | 
|  | void Execute(tcq_rate_t *rate_tst) { | 
|  | params_.ref_func(¶m_, &pre_quant_, &coeff_ctx_, blk_pos_, diag_ctx_, | 
|  | eob_rate_, &rate_ref_); | 
|  | ASM_REGISTER_STATE_CHECK(params_.tst_func(¶m_, &pre_quant_, &coeff_ctx_, | 
|  | blk_pos_, diag_ctx_, eob_rate_, | 
|  | rate_tst)); | 
|  | } | 
|  | tcq_param_t param_; | 
|  | LV_MAP_COEFF_COST txb_costs_; | 
|  | prequant_t pre_quant_; | 
|  | tcq_coeff_ctx_t coeff_ctx_; | 
|  | int blk_pos_; | 
|  | int diag_ctx_; | 
|  | int eob_rate_; | 
|  | }; | 
|  |  | 
|  | typedef void (*TcqRateLfLuma)(const struct tcq_param_t *p, | 
|  | const struct prequant_t *pq, | 
|  | const struct tcq_coeff_ctx_t *coeff_ctx, | 
|  | int blk_pos, int diag_ctx, int eob_rate, | 
|  | int coeff_sign, struct tcq_rate_t *rd); | 
|  | typedef libaom_test::FuncParam<TcqRateLfLuma> TcqRateLfLumaTestFuncs; | 
|  |  | 
|  | class TcqRateLfLumaTest : public TcqRateTest<TcqRateLfLuma, tcq_rate_t> { | 
|  | protected: | 
|  | void Execute(tcq_rate_t *rate_tst) { | 
|  | params_.ref_func(¶m_, &pre_quant_, &coeff_ctx_, blk_pos_, diag_ctx_, | 
|  | eob_rate_, coeff_sign_, &rate_ref_); | 
|  | ASM_REGISTER_STATE_CHECK(params_.tst_func(¶m_, &pre_quant_, &coeff_ctx_, | 
|  | blk_pos_, diag_ctx_, eob_rate_, | 
|  | coeff_sign_, rate_tst)); | 
|  | } | 
|  | tcq_param_t param_; | 
|  | LV_MAP_COEFF_COST txb_costs_; | 
|  | prequant_t pre_quant_; | 
|  | tcq_coeff_ctx_t coeff_ctx_; | 
|  | int blk_pos_; | 
|  | int diag_ctx_; | 
|  | int eob_rate_; | 
|  | int coeff_sign_; | 
|  | int tmp_sign_[1024]; | 
|  | }; | 
|  |  | 
|  | static int generate_random_q_idx(libaom_test::ACMRandom *rng) { | 
|  | int r1 = rng->Rand8() & 15; | 
|  | int r2 = (r1 == 15) ? rng->Rand8() & 15 : 0; | 
|  | int r3 = (r2 == 15) ? rng->Rand16() & 8191 : 0; | 
|  | int r = r1 + r2 + r3; | 
|  | return r; | 
|  | } | 
|  |  | 
|  | // Init coeff syntax costs randomly | 
|  | // - base_cost[], lps_cost[], base_eob_cost[] | 
|  | // - base_cost_zero[], base_cost_low_tbl[], base_eob_cost_tbl[], mid_cost_tbl[] | 
|  | static void generate_random_cost_tables(libaom_test::ACMRandom *rng, | 
|  | LV_MAP_COEFF_COST *txb_costs) { | 
|  | int max = 2048 - 1; | 
|  | int n; | 
|  | int *p0; | 
|  |  | 
|  | // Init base costs | 
|  | n = sizeof(txb_costs->base_cost) / sizeof(txb_costs->base_cost[0][0][0]); | 
|  | p0 = &txb_costs->base_cost[0][0][0]; | 
|  | for (int i = 0; i < n; i++) { | 
|  | *p0++ = rng->Rand16() & max; | 
|  | } | 
|  | n = sizeof(txb_costs->base_lf_cost) / | 
|  | sizeof(txb_costs->base_lf_cost[0][0][0]); | 
|  | p0 = &txb_costs->base_lf_cost[0][0][0]; | 
|  | for (int i = 0; i < n; i++) { | 
|  | *p0++ = rng->Rand16() & max; | 
|  | } | 
|  |  | 
|  | // Init mid-range (lps) costs | 
|  | n = sizeof(txb_costs->lps_cost) / sizeof(txb_costs->lps_cost[0][0]); | 
|  | p0 = &txb_costs->lps_cost[0][0]; | 
|  | for (int i = 0; i < n; i++) { | 
|  | *p0++ = rng->Rand16() & max; | 
|  | } | 
|  | n = sizeof(txb_costs->lps_lf_cost) / sizeof(txb_costs->lps_lf_cost[0][0]); | 
|  | p0 = &txb_costs->lps_lf_cost[0][0]; | 
|  | for (int i = 0; i < n; i++) { | 
|  | *p0++ = rng->Rand16() & max; | 
|  | } | 
|  |  | 
|  | // Init base_eob costs | 
|  | n = sizeof(txb_costs->base_eob_cost) / sizeof(txb_costs->base_eob_cost[0][0]); | 
|  | p0 = &txb_costs->base_eob_cost[0][0]; | 
|  | for (int i = 0; i < n; i++) { | 
|  | *p0++ = rng->Rand16() & max; | 
|  | } | 
|  | n = sizeof(txb_costs->base_lf_eob_cost) / | 
|  | sizeof(txb_costs->base_lf_eob_cost[0][0]); | 
|  | p0 = &txb_costs->base_lf_eob_cost[0][0]; | 
|  | for (int i = 0; i < n; i++) { | 
|  | *p0++ = rng->Rand16() & max; | 
|  | } | 
|  |  | 
|  | // Rearrange costs into base_cost_zero[] array for quicker access. | 
|  | // (from av1/encoder/rd.c) | 
|  | for (int q_i = 0; q_i < TCQ_CTXS; q_i++) { | 
|  | for (int ctx = 0; ctx < SIG_COEF_CONTEXTS; ++ctx) { | 
|  | txb_costs->base_cost_zero[q_i][ctx] = txb_costs->base_cost[ctx][q_i][0]; | 
|  | } | 
|  | } | 
|  | // Rearrange costs into base_lf_cost_zero[] array for quicker access. | 
|  | for (int q_i = 0; q_i < TCQ_CTXS; q_i++) { | 
|  | for (int ctx = 0; ctx < LF_SIG_COEF_CONTEXTS; ++ctx) { | 
|  | txb_costs->base_lf_cost_zero[q_i][ctx] = | 
|  | txb_costs->base_lf_cost[ctx][q_i][0]; | 
|  | } | 
|  | } | 
|  | // Precompute some base_costs for trellis, interleaved for quick access. | 
|  | // Look-up take to retrive data from precomputed cost array | 
|  | static const uint8_t trel_abslev[15][4] = { | 
|  | { 2, 1, 1, 2 },  // qIdx=1 | 
|  | { 2, 3, 1, 2 },  // qidx=2 | 
|  | { 2, 3, 3, 2 },  // qidx=3 | 
|  | { 2, 3, 3, 4 },  // qidx=4 | 
|  | { 4, 3, 3, 4 },  // qidx=5 | 
|  | { 4, 5, 3, 4 },  // qidx=6 | 
|  | { 4, 5, 5, 4 },  // qidx=7 | 
|  | { 4, 5, 5, 6 },  // qidx=8 | 
|  | { 6, 5, 5, 6 },  // qidx=9 | 
|  | { 6, 7, 5, 6 },  // qidx=10 | 
|  | { 6, 7, 7, 6 },  // qidx=11 | 
|  | { 6, 7, 7, 8 },  // qidx=12 | 
|  | { 8, 7, 7, 8 },  // qidx=13 | 
|  | { 8, 9, 7, 8 },  // qidx=14 | 
|  | { 8, 9, 9, 8 },  // qidx=15 | 
|  | }; | 
|  | for (int idx = 0; idx < 5; idx++) { | 
|  | int a0 = AOMMIN(trel_abslev[idx][0], 3); | 
|  | int a1 = AOMMIN(trel_abslev[idx][1], 3); | 
|  | int a2 = AOMMIN(trel_abslev[idx][2], 3); | 
|  | int a3 = AOMMIN(trel_abslev[idx][3], 3); | 
|  | for (int ctx = 0; ctx < SIG_COEF_CONTEXTS; ++ctx) { | 
|  | // Q0, absLev 0 / 2 | 
|  | txb_costs->base_cost_low_tbl[idx][ctx][0][0] = | 
|  | txb_costs->base_cost[ctx][0][a0] + av1_cost_literal(1); | 
|  | txb_costs->base_cost_low_tbl[idx][ctx][0][1] = | 
|  | txb_costs->base_cost[ctx][0][a2] + av1_cost_literal(1); | 
|  | // Q1, absLev 1 / 3 | 
|  | txb_costs->base_cost_low_tbl[idx][ctx][1][0] = | 
|  | txb_costs->base_cost[ctx][1][a1] + av1_cost_literal(1); | 
|  | txb_costs->base_cost_low_tbl[idx][ctx][1][1] = | 
|  | txb_costs->base_cost[ctx][1][a3] + av1_cost_literal(1); | 
|  | } | 
|  | for (int ctx = 0; ctx < SIG_COEF_CONTEXTS_EOB; ++ctx) { | 
|  | // EOB coeff, absLev 0 / 2 | 
|  | txb_costs->base_eob_cost_tbl[idx][ctx][0] = | 
|  | txb_costs->base_eob_cost[ctx][a0 - 1] + av1_cost_literal(1); | 
|  | txb_costs->base_eob_cost_tbl[idx][ctx][1] = | 
|  | txb_costs->base_eob_cost[ctx][a2 - 1] + av1_cost_literal(1); | 
|  | } | 
|  | } | 
|  | for (int idx = 0; idx < 9; idx++) { | 
|  | int max = LF_BASE_SYMBOLS - 1; | 
|  | int a0 = AOMMIN(trel_abslev[idx][0], max); | 
|  | int a1 = AOMMIN(trel_abslev[idx][1], max); | 
|  | int a2 = AOMMIN(trel_abslev[idx][2], max); | 
|  | int a3 = AOMMIN(trel_abslev[idx][3], max); | 
|  | for (int ctx = 0; ctx < LF_SIG_COEF_CONTEXTS; ++ctx) { | 
|  | // Q0, absLev 0 / 2 | 
|  | txb_costs->base_lf_cost_low_tbl[idx][ctx][0][0] = | 
|  | txb_costs->base_lf_cost[ctx][0][a0] + av1_cost_literal(1); | 
|  | txb_costs->base_lf_cost_low_tbl[idx][ctx][0][1] = | 
|  | txb_costs->base_lf_cost[ctx][0][a2] + av1_cost_literal(1); | 
|  | // Q1, absLev 1 / 3 | 
|  | txb_costs->base_lf_cost_low_tbl[idx][ctx][1][0] = | 
|  | txb_costs->base_lf_cost[ctx][1][a1] + av1_cost_literal(1); | 
|  | txb_costs->base_lf_cost_low_tbl[idx][ctx][1][1] = | 
|  | txb_costs->base_lf_cost[ctx][1][a3] + av1_cost_literal(1); | 
|  | } | 
|  | for (int ctx = 0; ctx < SIG_COEF_CONTEXTS_EOB; ++ctx) { | 
|  | // EOB coeff, absLev 0 / 2 | 
|  | txb_costs->base_lf_eob_cost_tbl[idx][ctx][0] = | 
|  | txb_costs->base_lf_eob_cost[ctx][a0 - 1] + av1_cost_literal(1); | 
|  | txb_costs->base_lf_eob_cost_tbl[idx][ctx][1] = | 
|  | txb_costs->base_lf_eob_cost[ctx][a2 - 1] + av1_cost_literal(1); | 
|  | } | 
|  | } | 
|  | // Precalc mid costs for default region. | 
|  | for (int idx = 0; idx < 5 + 2 * COEFF_BASE_RANGE; idx++) { | 
|  | int a0 = get_low_range(trel_abslev[idx][0], 0); | 
|  | int a1 = get_low_range(trel_abslev[idx][1], 0); | 
|  | int a2 = get_low_range(trel_abslev[idx][2], 0); | 
|  | int a3 = get_low_range(trel_abslev[idx][3], 0); | 
|  | for (int ctx = 0; ctx < LEVEL_CONTEXTS; ++ctx) { | 
|  | // Q0, absLev 0 / 2 | 
|  | txb_costs->mid_cost_tbl[idx][ctx][0][0] = | 
|  | a0 < 0 ? 0 : txb_costs->lps_cost[ctx][a0]; | 
|  | txb_costs->mid_cost_tbl[idx][ctx][0][1] = | 
|  | a2 < 0 ? 0 : txb_costs->lps_cost[ctx][a2]; | 
|  | // Q1, absLev 1 / 3 | 
|  | txb_costs->mid_cost_tbl[idx][ctx][1][0] = | 
|  | a1 < 0 ? 0 : txb_costs->lps_cost[ctx][a1]; | 
|  | txb_costs->mid_cost_tbl[idx][ctx][1][1] = | 
|  | a3 < 0 ? 0 : txb_costs->lps_cost[ctx][a3]; | 
|  | } | 
|  | } | 
|  | // Precalc mid costs for default region. | 
|  | for (int idx = 0; idx < 9 + 2 * COEFF_BASE_RANGE; idx++) { | 
|  | int a0 = get_low_range(trel_abslev[idx][0], 1); | 
|  | int a1 = get_low_range(trel_abslev[idx][1], 1); | 
|  | int a2 = get_low_range(trel_abslev[idx][2], 1); | 
|  | int a3 = get_low_range(trel_abslev[idx][3], 1); | 
|  | for (int ctx = 0; ctx < LF_LEVEL_CONTEXTS; ++ctx) { | 
|  | // Q0, absLev 0 / 2 | 
|  | txb_costs->mid_lf_cost_tbl[idx][ctx][0][0] = | 
|  | a0 < 0 ? 0 : txb_costs->lps_lf_cost[ctx][a0]; | 
|  | txb_costs->mid_lf_cost_tbl[idx][ctx][0][1] = | 
|  | a2 < 0 ? 0 : txb_costs->lps_lf_cost[ctx][a2]; | 
|  | // Q1, absLev 1 / 3 | 
|  | txb_costs->mid_lf_cost_tbl[idx][ctx][1][0] = | 
|  | a1 < 0 ? 0 : txb_costs->lps_lf_cost[ctx][a1]; | 
|  | txb_costs->mid_lf_cost_tbl[idx][ctx][1][1] = | 
|  | a3 < 0 ? 0 : txb_costs->lps_lf_cost[ctx][a3]; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST_P(TcqRateLumaTest, RandomValues) { | 
|  | for (int iter = 0; iter < kIterations && !HasFatalFailure(); ++iter) { | 
|  | int log_scale = 1; | 
|  | int shift = 16 - log_scale + QUANT_FP_BITS; | 
|  | const int32_t quant[2] = { 1 << shift, 1 << shift }; | 
|  | int dqv = 1 << QUANT_TABLE_BITS; | 
|  | int tqc = iter < 16000 ? iter : generate_random_q_idx(&rng_); | 
|  |  | 
|  | // Initialize param structure. | 
|  | int bwl = 2 + (rng_.Rand8() & 3); | 
|  | int height = 1 << bwl; | 
|  | int max = (1 << bwl) - 1; | 
|  | int row = rng_.Rand8() & max; | 
|  | int col = rng_.Rand8() & max; | 
|  | row = AOMMAX(row, 4); | 
|  | col = AOMMAX(col, 4); | 
|  | int blk_pos = (row << bwl) + col; | 
|  | int scan_pos = blk_pos; | 
|  | int diag_ctx = get_nz_map_ctx_from_stats(0, blk_pos, bwl, TX_CLASS_2D, 0); | 
|  |  | 
|  | blk_pos_ = blk_pos; | 
|  | diag_ctx_ = diag_ctx; | 
|  | param_.bwl = bwl; | 
|  | param_.txb_height = height; | 
|  | param_.tx_class = 0; | 
|  | param_.txb_costs = &txb_costs_; | 
|  |  | 
|  | // Generate random syntax costs. | 
|  | generate_random_cost_tables(&rng_, &txb_costs_); | 
|  |  | 
|  | // Generate pre_quant info with random coeff. | 
|  | av1_pre_quant_c(tqc, &pre_quant_, quant, dqv, log_scale, scan_pos); | 
|  | eob_rate_ = rng_(512 * 4); | 
|  |  | 
|  | // Generate random coeff_ctx | 
|  | for (int i = 0; i < 8; i++) { | 
|  | coeff_ctx_.coef[i] = (rng_(4) << 4) + rng_(4); | 
|  | } | 
|  | coeff_ctx_.coef_eob = get_lower_levels_ctx_eob(bwl, height, scan_pos); | 
|  | coeff_ctx_.pad[0] = 0; | 
|  | coeff_ctx_.pad[1] = 0; | 
|  | coeff_ctx_.pad[2] = 0; | 
|  |  | 
|  | Common(); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST_P(TcqRateLfLumaTest, RandomValues) { | 
|  | for (int iter = 0; iter < kIterations && !HasFatalFailure(); ++iter) { | 
|  | int log_scale = 1; | 
|  | int shift = 16 - log_scale + QUANT_FP_BITS; | 
|  | const int32_t quant[2] = { 1 << shift, 1 << shift }; | 
|  | int dqv = 1 << QUANT_TABLE_BITS; | 
|  | int tqc = iter < 16000 ? iter : generate_random_q_idx(&rng_); | 
|  |  | 
|  | // Initialize param structure. | 
|  | int bwl = 2 + (rng_.Rand8() & 3); | 
|  | int height = 1 << bwl; | 
|  | int diag = rng_.Rand8() & 3; | 
|  | int row = rng_.Rand8() % (diag + 1); | 
|  | int col = diag - row; | 
|  | int blk_pos = (row << bwl) + col; | 
|  | int scan_pos = blk_pos; | 
|  | int diag_ctx = get_nz_map_ctx_from_stats_lf(0, blk_pos, bwl, TX_CLASS_2D); | 
|  | if (scan_pos > 0) { | 
|  | diag_ctx += 7 << 8; | 
|  | } | 
|  |  | 
|  | blk_pos_ = blk_pos; | 
|  | diag_ctx_ = diag_ctx; | 
|  | coeff_sign_ = rng_.Rand8() & 1; | 
|  | param_.bwl = bwl; | 
|  | param_.txb_height = height; | 
|  | param_.tx_class = 0; | 
|  | param_.txb_costs = &txb_costs_; | 
|  | param_.tmp_sign = tmp_sign_; | 
|  | param_.dc_sign_ctx = rng_.Rand8() % DC_SIGN_CONTEXTS; | 
|  | tmp_sign_[blk_pos] = rng_.Rand8() % CROSS_COMPONENT_CONTEXTS; | 
|  |  | 
|  | // Generate random syntax costs. | 
|  | generate_random_cost_tables(&rng_, &txb_costs_); | 
|  |  | 
|  | // Generate pre_quant info with random coeff. | 
|  | av1_pre_quant_c(tqc, &pre_quant_, quant, dqv, log_scale, scan_pos); | 
|  | eob_rate_ = rng_(512 * 4); | 
|  |  | 
|  | // Generate random coeff_ctx | 
|  | for (int i = 0; i < 8; i++) { | 
|  | coeff_ctx_.coef[i] = (rng_(4) << 4) + rng_(4); | 
|  | } | 
|  | coeff_ctx_.coef_eob = get_lower_levels_ctx_eob(bwl, height, scan_pos); | 
|  | coeff_ctx_.pad[0] = 0; | 
|  | coeff_ctx_.pad[1] = 0; | 
|  | coeff_ctx_.pad[2] = 0; | 
|  |  | 
|  | Common(); | 
|  | } | 
|  | } | 
|  |  | 
|  | #if HAVE_AVX2 | 
|  | INSTANTIATE_TEST_SUITE_P( | 
|  | AVX2, TcqRateLumaTest, | 
|  | ::testing::Values(TcqRateLumaTestFuncs(av1_get_rate_dist_def_luma_c, | 
|  | av1_get_rate_dist_def_luma_avx2))); | 
|  |  | 
|  | INSTANTIATE_TEST_SUITE_P( | 
|  | AVX2, TcqRateLfLumaTest, | 
|  | ::testing::Values(TcqRateLfLumaTestFuncs(av1_get_rate_dist_lf_luma_c, | 
|  | av1_get_rate_dist_lf_luma_avx2))); | 
|  | #endif  // HAVE_AVX2 | 
|  |  | 
|  | }  // namespace |