From 0ea61a1260754cc175aaed5448ef62b68f10e54e Mon Sep 17 00:00:00 2001 From: Cheng Chen Date: Thu, 10 Sep 2026 15:02:52 -0700 Subject: [PATCH 1/2] Add a new speed feature to analyze the structure orientation The speed feature shows a good tradeoff for speed 5. RA performance: Speed A1 BD-Rate (YUV) A1 Speedup A1 Ratio Speed_4 +0.16% +1.61% 10.1 Speed_5 +0.14% +3.09% 22.1 Speed A2 BD-Rate (YUV) A2 Speedup A2 Ratio Speed_4 +0.19% +2.96% 15.6 Speed_5 +0.21% +2.89% 13.8 STATS_CHANGED Change-Id: Iea96600b64fdd27c83efc1250d46b4a90ccb5878 --- av2/encoder/encodeframe_utils.h | 8 ++ av2/encoder/partition_search.c | 172 +++++++++++++++++++++++++++++++- av2/encoder/speed_features.c | 2 + av2/encoder/speed_features.h | 2 + 4 files changed, 180 insertions(+), 4 deletions(-) diff --git a/av2/encoder/encodeframe_utils.h b/av2/encoder/encodeframe_utils.h index abf77c6135..275e56bc19 100644 --- a/av2/encoder/encodeframe_utils.h +++ b/av2/encoder/encodeframe_utils.h @@ -152,6 +152,14 @@ typedef struct PartitionSearchState { int ss_x; int ss_y; + // Block scale directional structure of the source block, filled in lazily the + // first time the orientation based partition pruning needs it. + // src_var_rows is the variance of the row mean profile, + // src_var_cols that of the column mean profile. + uint64_t src_var_rows; + uint64_t src_var_cols; + bool src_var_valid; + // This flag will be set if best partition is found from the search. bool found_best_partition; diff --git a/av2/encoder/partition_search.c b/av2/encoder/partition_search.c index 5caf6af101..f341319151 100644 --- a/av2/encoder/partition_search.c +++ b/av2/encoder/partition_search.c @@ -3354,6 +3354,11 @@ static void init_partition_search_state_params( tree_type = SHARED_PART; } + // The source structure profiles are measured lazily, per block. + part_search_state->src_var_valid = false; + part_search_state->src_var_rows = 0; + part_search_state->src_var_cols = 0; + // Initialization of block size related parameters. blk_params->mi_step = mi_size_wide[bsize] / 2; blk_params->mi_step_h = mi_size_high[bsize] / 2; @@ -3636,6 +3641,142 @@ static void prune_rect_with_mlp(const AV2_COMP *cpi, MACROBLOCK *x, } } +// How much stronger the structure along one axis must be than along the other +// before partitions cutting across it are pruned. The plain rectangular +// partitions carry far more of the coding gain than the extended ones, so they +// demand markedly stronger evidence before being dropped. +#define EXT_PART_ANISOTROPY 4 +#define RECT_PART_ANISOTROPY 8 +// Largest number of samples taken along each axis when profiling a block. +#define PART_PROFILE_SAMPLES 32 +static AVM_INLINE uint64_t orientation_anisotropy_for_bsize(BLOCK_SIZE bsize, + uint64_t base) { + const int dim = AVMMAX(block_size_wide[bsize], block_size_high[bsize]); + if (dim >= 64) return base * 4; + if (dim >= 32) return base * 2; + return base; +} +static AVM_INLINE uint64_t +orientation_anisotropy_for_frame(const AV2_COMP *const cpi, uint64_t base) { + const GF_GROUP *const gf_group = &cpi->gf_group; + const int layer_depth = gf_group->layer_depth[gf_group->index]; + if (layer_depth <= 0) return 0; + if (layer_depth == 1) return base * 4; + if (layer_depth == 2) return base * 2; + return base; +} + +// Measures how much the source block varies along each axis at partition scale. +// +// Per pixel gradients are a poor guide here: fine texture excites the +// horizontal and the vertical gradient about equally, so on ordinary detailed +// content their ratio stays near one no matter how the block is actually +// structured. What a partition can exploit is variation at the scale of its own +// sub-blocks, and that is exactly what the mean profiles capture. Averaging +// each row collapses the horizontal detail and leaves the top-to-bottom +// structure a horizontal cut could separate; averaging each column leaves the +// left-to-right structure a vertical cut could separate. The variance of the +// two profiles, both normalised to per pixel means so they are directly +// comparable, is the measurement stored here. +// +// The block is sampled on a lattice of at most 32x32 points, so the cost is +// negligible next to a single partition RD trial. +static AVM_INLINE void compute_source_profile_variance( + const MACROBLOCK *x, BLOCK_SIZE bsize, + PartitionSearchState *part_search_state) { + const struct buf_2d *const src = &x->plane[AVM_PLANE_Y].src; + const int bw = block_size_wide[bsize]; + const int bh = block_size_high[bsize]; + const int step_x = AVMMAX(1, bw / PART_PROFILE_SAMPLES); + const int step_y = AVMMAX(1, bh / PART_PROFILE_SAMPLES); + const int num_x = AVMMIN(PART_PROFILE_SAMPLES, bw / step_x); + const int num_y = AVMMIN(PART_PROFILE_SAMPLES, bh / step_y); + const int stride = src->stride; + + int64_t row_sum[PART_PROFILE_SAMPLES] = { 0 }; + int64_t col_sum[PART_PROFILE_SAMPLES] = { 0 }; + int64_t total = 0; + + for (int si = 0; si < num_y; ++si) { + const uint16_t *const row = src->buf + (ptrdiff_t)si * step_y * stride; + for (int sj = 0; sj < num_x; ++sj) { + const int64_t value = row[sj * step_x]; + row_sum[si] += value; + col_sum[sj] += value; + } + total += row_sum[si]; + } + + const int64_t mean = total / ((int64_t)num_x * num_y); + int64_t var_rows = 0; + for (int si = 0; si < num_y; ++si) { + const int64_t diff = row_sum[si] / num_x - mean; + var_rows += diff * diff; + } + int64_t var_cols = 0; + for (int sj = 0; sj < num_x; ++sj) { + const int64_t diff = col_sum[sj] / num_y - mean; + var_cols += diff * diff; + } + + // Variation a horizontal cut can separate, and the vertical cut counterpart. + part_search_state->src_var_rows = (uint64_t)(var_rows / num_y); + part_search_state->src_var_cols = (uint64_t)(var_cols / num_x); + part_search_state->src_var_valid = true; +} + +// Prunes the partitions whose cuts run across the dominant orientation of the +// source structure. +// +// A vertical cut can only pay off when the block content actually changes from +// left to right, and a horizontal cut only when it changes from top to bottom. +// The two profile variances give a cheap, prediction independent test for +// exactly that. When one direction carries nearly all of the block scale +// variation, the partitions splitting along the other one are spending full RD +// trials on cuts that have nothing to separate, so they are dropped. +// +// `anisotropy` is how many times larger one variance must be than the other +// before the pruning applies; callers pass a higher value for the plain +// rectangular partitions, which are more valuable and therefore need stronger +// evidence than the extended ones. The test is deliberately one sided: when the +// two variances are comparable (including the degenerate all-zero case of a +// flat block) nothing is pruned, so only clearly directional content is +// affected. +static AVM_INLINE void prune_partitions_by_structure_orientation( + const MACROBLOCK *x, PartitionSearchState *part_search_state, + const PARTITION_TYPE *horz_parts, const PARTITION_TYPE *vert_parts, + int num_parts, uint64_t base_anisotropy, const AV2_COMP *const cpi) { + if (!cpi->sf.part_sf.prune_by_struct_orient) return; + if (part_search_state->forced_partition != PARTITION_INVALID) return; + if (x->must_find_valid_partition) return; + + const BLOCK_SIZE bsize = part_search_state->part_blk_params.bsize; + // Below 16x16 the sampled profiles are too short for the ratio to be + // meaningful, and the partitions are cheap to evaluate anyway. + if (block_size_wide[bsize] < 16 || block_size_high[bsize] < 16) return; + + const uint64_t frame_scaled = orientation_anisotropy_for_frame(cpi, 1); + if (frame_scaled == 0) return; + const uint64_t anisotropy = + orientation_anisotropy_for_bsize(bsize, base_anisotropy) * frame_scaled; + + if (!part_search_state->src_var_valid) + compute_source_profile_variance(x, bsize, part_search_state); + + const uint64_t var_rows = part_search_state->src_var_rows; + const uint64_t var_cols = part_search_state->src_var_cols; + + if (var_cols * anisotropy < var_rows) { + // Structure is essentially top-to-bottom only: nothing for a vertical cut + // to separate. + for (int i = 0; i < num_parts; ++i) + part_search_state->prune_partition[vert_parts[i]] = true; + } else if (var_rows * anisotropy < var_cols) { + for (int i = 0; i < num_parts; ++i) + part_search_state->prune_partition[horz_parts[i]] = true; + } +} + // Prune rectangular partition types based on mlp. One of the features of mlp is // partition none rdcost. It is possible that none rd might not be evaluated by // now if adaptive_partition_search_order is enabled. In this case default none @@ -3692,6 +3833,13 @@ static void prune_rect_partitions(AV2_COMP *const cpi, ThreadData *td, &part_search_state->prune_partition[PARTITION_VERT]); } + { + static const PARTITION_TYPE horz[] = { PARTITION_HORZ }; + static const PARTITION_TYPE vert[] = { PARTITION_VERT }; + prune_partitions_by_structure_orientation(x, part_search_state, horz, vert, + 1, RECT_PART_ANISOTROPY, cpi); + } + if (cpi->sf.part_sf.prune_rect_with_none_rd && part_search_state->forced_partition == PARTITION_INVALID && !frame_is_intra_only(cm) && part_none_rd < INT64_MAX) { @@ -4643,7 +4791,7 @@ static AVM_INLINE void prune_part_4_with_partition_boundary( /*!\brief Pruning logic for PARTITION_HORZ_3 and PARTITION_VERT_3 based on * non-ext partitions search results. */ static AVM_INLINE void prune_ext_partitions_3way( - AV2_COMP *const cpi, PC_TREE *pc_tree, + AV2_COMP *const cpi, const MACROBLOCK *x, PC_TREE *pc_tree, PartitionSearchState *part_search_state, bool *partition_boundaries) { const AV2_COMMON *const cm = &cpi->common; const PARTITION_SPEED_FEATURES *part_sf = &cpi->sf.part_sf; @@ -4654,6 +4802,13 @@ static AVM_INLINE void prune_ext_partitions_3way( return; } + { + static const PARTITION_TYPE horz3[] = { PARTITION_HORZ_3 }; + static const PARTITION_TYPE vert3[] = { PARTITION_VERT_3 }; + prune_partitions_by_structure_orientation( + x, part_search_state, horz3, vert3, 1, EXT_PART_ANISOTROPY, cpi); + } + // Prune horz 3 with speed features if (part_search_state->partition_allowed[PARTITION_HORZ_3] && !frame_is_intra_only(cm) && forced_partition != PARTITION_HORZ_3) { @@ -4854,13 +5009,22 @@ static INLINE void search_intra_region_partitioning( /*!\brief Pruning logic for PARTITION_HORZ_4A/B and PARTITION_VERT_4A/B based on * non-ext and 3 way partitions search results.*/ static AVM_INLINE void prune_ext_partitions_4way( - AV2_COMP *const cpi, PC_TREE *pc_tree, + AV2_COMP *const cpi, const MACROBLOCK *x, PC_TREE *pc_tree, PartitionSearchState *part_search_state, bool *partition_boundaries) { const AV2_COMMON *const cm = &cpi->common; const PARTITION_SPEED_FEATURES *part_sf = &cpi->sf.part_sf; const PARTITION_TYPE forced_partition = part_search_state->forced_partition; const int cur_region_type = pc_tree->region_type; + { + static const PARTITION_TYPE horz4[] = { PARTITION_HORZ_4A, + PARTITION_HORZ_4B }; + static const PARTITION_TYPE vert4[] = { PARTITION_VERT_4A, + PARTITION_VERT_4B }; + prune_partitions_by_structure_orientation( + x, part_search_state, horz4, vert4, 2, EXT_PART_ANISOTROPY, cpi); + } + // Prune HORZ 4A with speed features if (part_search_state->partition_allowed[PARTITION_HORZ_4A] && forced_partition != PARTITION_HORZ_4A) { @@ -5887,7 +6051,7 @@ bool av2_rd_pick_partition( &cpi->sf.part_sf, bsize, eff_max_recursion_depth, false); bool partition_boundaries[MAX_MIB_SQUARE] = { 0 }; - prune_ext_partitions_3way(cpi, pc_tree, &part_search_state, + prune_ext_partitions_3way(cpi, x, pc_tree, &part_search_state, partition_boundaries); for (PARTITION_TYPE partition_type = PARTITION_HORZ_3; partition_type <= PARTITION_VERT_3; ++partition_type) { @@ -5901,7 +6065,7 @@ bool av2_rd_pick_partition( const int uneven_4way_recur_depth = get_ext_partitions_recur_depth( &cpi->sf.part_sf, bsize, eff_max_recursion_depth, true); - prune_ext_partitions_4way(cpi, pc_tree, &part_search_state, + prune_ext_partitions_4way(cpi, x, pc_tree, &part_search_state, partition_boundaries); for (PARTITION_TYPE partition_type = PARTITION_HORZ_4A; partition_type <= PARTITION_VERT_4B; ++partition_type) { diff --git a/av2/encoder/speed_features.c b/av2/encoder/speed_features.c index 74f4674c8d..ad431f7afd 100644 --- a/av2/encoder/speed_features.c +++ b/av2/encoder/speed_features.c @@ -662,6 +662,7 @@ static void set_good_speed_features_framesize_independent( } if (speed >= 5) { + sf->part_sf.prune_by_struct_orient = true; sf->part_sf.simple_motion_search_prune_agg = 3; sf->inter_sf.disable_interinter_wedge = 1; sf->inter_sf.prune_inter_modes_if_skippable = 1; @@ -843,6 +844,7 @@ static AVM_INLINE void init_part_sf(PARTITION_SPEED_FEATURES *part_sf) { part_sf->disable_extended_sdp = false; part_sf->force_max_pb_aspect_ratio = 0; + part_sf->prune_by_struct_orient = false; } static AVM_INLINE void init_mv_sf(MV_SPEED_FEATURES *mv_sf) { diff --git a/av2/encoder/speed_features.h b/av2/encoder/speed_features.h index 120754efa2..3926f44bee 100644 --- a/av2/encoder/speed_features.h +++ b/av2/encoder/speed_features.h @@ -481,6 +481,8 @@ typedef struct PARTITION_SPEED_FEATURES { // Force the max partition-block aspect ratio unsigned int force_max_pb_aspect_ratio; + + bool prune_by_struct_orient; } PARTITION_SPEED_FEATURES; // True when the two-pass superblock partition search runs at all. From 400e33c5a0d9face44ba852de204da37260f0b0b Mon Sep 17 00:00:00 2001 From: Cheng Chen Date: Fri, 11 Sep 2026 17:16:42 -0700 Subject: [PATCH 2/2] Support Level 2 orientation partition pruning for 4K at Speed >= 6 The performance on RA, speed 6, A1: BD-Rate (YUV) Speedup Ratio +1.09% +14.32% 13.1 STATS_CHANGED Change-Id: I5e74f4ffd130e16864e834a94627a8bf2d285bb7 --- av2/encoder/partition_search.c | 15 +++++++++++---- av2/encoder/speed_features.c | 4 ++++ av2/encoder/speed_features.h | 7 ++++++- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/av2/encoder/partition_search.c b/av2/encoder/partition_search.c index f341319151..3ba4bb68cc 100644 --- a/av2/encoder/partition_search.c +++ b/av2/encoder/partition_search.c @@ -3755,10 +3755,17 @@ static AVM_INLINE void prune_partitions_by_structure_orientation( // meaningful, and the partitions are cheap to evaluate anyway. if (block_size_wide[bsize] < 16 || block_size_high[bsize] < 16) return; - const uint64_t frame_scaled = orientation_anisotropy_for_frame(cpi, 1); - if (frame_scaled == 0) return; - const uint64_t anisotropy = - orientation_anisotropy_for_bsize(bsize, base_anisotropy) * frame_scaled; + uint64_t anisotropy; + if (cpi->sf.part_sf.prune_by_struct_orient >= 2) { + // Level 2 (unblunted mode for 4K at speed >= 6): prune across all frames + // regardless of temporal layer depth (patch 0006d). + anisotropy = base_anisotropy; + } else { + const uint64_t frame_scaled = orientation_anisotropy_for_frame(cpi, 1); + if (frame_scaled == 0) return; + anisotropy = + orientation_anisotropy_for_bsize(bsize, base_anisotropy) * frame_scaled; + } if (!part_search_state->src_var_valid) compute_source_profile_variance(x, bsize, part_search_state); diff --git a/av2/encoder/speed_features.c b/av2/encoder/speed_features.c index ad431f7afd..37141807d8 100644 --- a/av2/encoder/speed_features.c +++ b/av2/encoder/speed_features.c @@ -274,6 +274,10 @@ static void set_good_speed_feature_framesize_dependent( if (is_720p_or_larger) { sf->inter_sf.prune_ref_mv_idx_search = 2; } + + if (is_4k_or_larger) { + sf->part_sf.prune_by_struct_orient = 2; + } } sf->part_sf.use_square_partition_only_threshold = BLOCK_LARGEST; } diff --git a/av2/encoder/speed_features.h b/av2/encoder/speed_features.h index 3926f44bee..dcada95727 100644 --- a/av2/encoder/speed_features.h +++ b/av2/encoder/speed_features.h @@ -482,7 +482,12 @@ typedef struct PARTITION_SPEED_FEATURES { // Force the max partition-block aspect ratio unsigned int force_max_pb_aspect_ratio; - bool prune_by_struct_orient; + // Prune rectangular and extended partitions based on structure orientation + // of the source signal. + // 0: disabled + // 1: standard (size-graded anisotropy with GOP temporal layer protection) + // 2: unblunted (prunes across all GOP temporal layers for 4K at speed >= 6) + int prune_by_struct_orient; } PARTITION_SPEED_FEATURES; // True when the two-pass superblock partition search runs at all.