Delta-code against scaled reference model
diff --git a/aom_dsp/bitwriter_buffer.c b/aom_dsp/bitwriter_buffer.c
index 7e41949..ac0710f 100644
--- a/aom_dsp/bitwriter_buffer.c
+++ b/aom_dsp/bitwriter_buffer.c
@@ -92,6 +92,7 @@
void aom_wb_write_primitive_quniform(struct aom_write_bit_buffer *wb,
uint16_t n, uint16_t v) {
if (n <= 1) return;
+ assert(v < n);
const int l = get_msb(n) + 1;
const int m = (1 << l) - n;
if (v < m) {
diff --git a/av1/common/av1_common_int.h b/av1/common/av1_common_int.h
index eee6f58..a1dac85 100644
--- a/av1/common/av1_common_int.h
+++ b/av1/common/av1_common_int.h
@@ -260,6 +260,12 @@
// Frame's level within the hierarchical structure
unsigned int pyramid_level;
+#if CONFIG_IMPROVED_GLOBAL_MOTION
+ // How many ref frames did this frame use?
+ // This is set to 0 for intra frames
+ int num_ref_frames;
+#endif // CONFIG_IMPROVED_GLOBAL_MOTION
+
MV_REF *mvs;
uint8_t *seg_map;
struct segmentation seg;
@@ -1567,6 +1573,18 @@
*/
DeltaQInfo delta_q_info;
+#if CONFIG_IMPROVED_GLOBAL_MOTION
+ /*!
+ * Base model used for delta-coding global motion parameters
+ */
+ WarpedMotionParams base_global_motion_model;
+
+ /*!
+ * Temporal length of `base_global_motion_model`
+ */
+ int base_global_motion_distance;
+#endif // CONFIG_IMPROVED_GLOBAL_MOTION
+
/*!
* Global motion parameters for each reference frame.
*/
diff --git a/av1/common/warped_motion.h b/av1/common/warped_motion.h
index d7f2bca..e10a279 100644
--- a/av1/common/warped_motion.h
+++ b/av1/common/warped_motion.h
@@ -341,4 +341,54 @@
return motion_mode == WARPED_CAUSAL;
#endif // CONFIG_INTERINTRA_WARP
}
+
+#if CONFIG_IMPROVED_GLOBAL_MOTION
+static INLINE void av1_scale_warp_model(const WarpedMotionParams *in_params,
+ int in_distance,
+ WarpedMotionParams *out_params,
+ int out_distance) {
+ static int param_shift[MAX_PARAMDIM - 1] = {
+ GM_TRANS_PREC_DIFF, GM_TRANS_PREC_DIFF, GM_ALPHA_PREC_DIFF,
+ GM_ALPHA_PREC_DIFF, GM_ALPHA_PREC_DIFF, GM_ALPHA_PREC_DIFF,
+ GM_ROW3HOMO_PREC_DIFF, GM_ROW3HOMO_PREC_DIFF
+ };
+
+ static int param_min[MAX_PARAMDIM - 1] = { GM_TRANS_MIN, GM_TRANS_MIN,
+ GM_ALPHA_MIN, GM_ALPHA_MIN,
+ GM_ALPHA_MIN, GM_ALPHA_MIN,
+ GM_ROW3HOMO_MIN, GM_ROW3HOMO_MIN };
+
+ static int param_max[MAX_PARAMDIM - 1] = { GM_TRANS_MAX, GM_TRANS_MAX,
+ GM_ALPHA_MAX, GM_ALPHA_MAX,
+ GM_ALPHA_MAX, GM_ALPHA_MAX,
+ GM_ROW3HOMO_MAX, GM_ROW3HOMO_MAX };
+
+ assert(in_distance != 0);
+ assert(out_distance != 0);
+
+ // Flip signs so that in_distance is positive.
+ // We do this because
+ // scaled_value = (... + divisor/2) / divisor
+ // is the simplest way to implement division with round-to-nearest in C,
+ // but it only works correctly if the divisor is positive
+ if (in_distance < 0) {
+ in_distance = -in_distance;
+ out_distance = -out_distance;
+ }
+
+ out_params->wmtype = in_params->wmtype;
+ for (int param = 0; param < MAX_PARAMDIM - 1; param++) {
+ int center = default_warp_params.wmmat[param];
+
+ int input = in_params->wmmat[param] - center;
+ int divisor = in_distance << param_shift[param];
+ int output = (int)(((int64_t)input * out_distance + divisor / 2) / divisor);
+ output = clamp(output, param_min[param], param_max[param])
+ << param_shift[param];
+
+ out_params->wmmat[param] = center + output;
+ }
+}
+#endif // CONFIG_IMPROVED_GLOBAL_MOTION
+
#endif // AOM_AV1_COMMON_WARPED_MOTION_H_
diff --git a/av1/decoder/decodeframe.c b/av1/decoder/decodeframe.c
index 8651d3c..6836712 100644
--- a/av1/decoder/decodeframe.c
+++ b/av1/decoder/decodeframe.c
@@ -6203,8 +6203,10 @@
static AOM_INLINE void read_global_motion(AV1_COMMON *cm,
struct aom_read_bit_buffer *rb) {
#if CONFIG_IMPROVED_GLOBAL_MOTION
+ const SequenceHeader *const seq_params = &cm->seq_params;
+ int num_total_refs = cm->ref_frames_info.num_total_refs;
bool use_global_motion = false;
- if (cm->seq_params.enable_global_motion) {
+ if (seq_params->enable_global_motion) {
use_global_motion = aom_rb_read_bit(rb);
}
if (!use_global_motion) {
@@ -6214,12 +6216,59 @@
}
return;
}
+
+ int our_ref = aom_rb_read_primitive_quniform(rb, num_total_refs + 1);
+ if (our_ref == num_total_refs) {
+ // Special case: Use IDENTITY model
+ cm->base_global_motion_model = default_warp_params;
+ cm->base_global_motion_distance = 1;
+ } else {
+ RefCntBuffer *buf = get_ref_frame_buf(cm, our_ref);
+ assert(buf);
+ int their_num_refs = buf->num_ref_frames;
+ if (their_num_refs == 0) {
+ // Special case: if an intra/key frame is used as a ref, use an
+ // IDENTITY model
+ cm->base_global_motion_model = default_warp_params;
+ cm->base_global_motion_distance = 1;
+ } else {
+ int their_ref = aom_rb_read_primitive_quniform(rb, their_num_refs);
+ cm->base_global_motion_model = buf->global_motion[their_ref];
+ cm->base_global_motion_distance =
+ get_relative_dist(&seq_params->order_hint_info, buf->order_hint,
+ buf->ref_order_hints[their_ref]);
+ }
+ }
#endif // CONFIG_IMPROVED_GLOBAL_MOTION
for (int frame = 0; frame < cm->ref_frames_info.num_total_refs; ++frame) {
+#if CONFIG_IMPROVED_GLOBAL_MOTION
+ int temporal_distance;
+ if (seq_params->order_hint_info.enable_order_hint) {
+ const RefCntBuffer *const ref_buf = get_ref_frame_buf(cm, frame);
+ temporal_distance = get_relative_dist(&seq_params->order_hint_info,
+ (int)cm->cur_frame->order_hint,
+ (int)ref_buf->order_hint);
+ } else {
+ temporal_distance = 1;
+ }
+
+ if (temporal_distance == 0) {
+ // Don't code global motion for frames at the same temporal instant
+ cm->global_motion[frame] = default_warp_params;
+ continue;
+ }
+
+ WarpedMotionParams ref_params_;
+ av1_scale_warp_model(&cm->base_global_motion_model,
+ cm->base_global_motion_distance, &ref_params_,
+ temporal_distance);
+ WarpedMotionParams *ref_params = &ref_params_;
+#else
const WarpedMotionParams *ref_params =
cm->prev_frame ? &cm->prev_frame->global_motion[frame]
: &default_warp_params;
+#endif // CONFIG_IMPROVED_GLOBAL_MOTION
int good_params =
#if !CONFIG_FLEX_MVRES
read_global_motion_params(&cm->global_motion[frame], ref_params, rb,
@@ -6782,6 +6831,10 @@
features->allow_ref_frame_mvs = 0;
cm->prev_frame = NULL;
+
+#if CONFIG_IMPROVED_GLOBAL_MOTION
+ cm->cur_frame->num_ref_frames = 0;
+#endif // CONFIG_IMPROVED_GLOBAL_MOTION
} else {
features->allow_ref_frame_mvs = 0;
#if CONFIG_TIP
@@ -6807,6 +6860,10 @@
}
#endif // CONFIG_IBC_SR_EXT
+#if CONFIG_IMPROVED_GLOBAL_MOTION
+ cm->cur_frame->num_ref_frames = 0;
+#endif // CONFIG_IMPROVED_GLOBAL_MOTION
+
} else if (pbi->need_resync != 1) { /* Skip if need resync */
// Implicitly derive the reference mapping
RefFrameMapPair ref_frame_map_pairs[REF_FRAMES];
@@ -6887,6 +6944,9 @@
}
av1_get_past_future_cur_ref_lists(cm, scores);
}
+#if CONFIG_IMPROVED_GLOBAL_MOTION
+ cm->cur_frame->num_ref_frames = cm->ref_frames_info.num_total_refs;
+#endif // CONFIG_IMPROVED_GLOBAL_MOTION
if (!features->error_resilient_mode && frame_size_override_flag) {
setup_frame_size_with_refs(cm, rb);
diff --git a/av1/encoder/bitstream.c b/av1/encoder/bitstream.c
index 2c94af4..afc3c26 100644
--- a/av1/encoder/bitstream.c
+++ b/av1/encoder/bitstream.c
@@ -4582,15 +4582,20 @@
static AOM_INLINE void write_global_motion(AV1_COMP *cpi,
struct aom_write_bit_buffer *wb) {
AV1_COMMON *const cm = &cpi->common;
+ int num_total_refs = cm->ref_frames_info.num_total_refs;
+#if CONFIG_IMPROVED_GLOBAL_MOTION
+ assert(cm->cur_frame->num_ref_frames == num_total_refs);
+#endif // CONFIG_IMPROVED_GLOBAL_MOTION
int frame;
#if CONFIG_IMPROVED_GLOBAL_MOTION
- if (!cm->seq_params.enable_global_motion) {
+ const SequenceHeader *const seq_params = &cm->seq_params;
+ if (!seq_params->enable_global_motion) {
return;
}
bool use_global_motion = false;
- for (frame = 0; frame < cm->ref_frames_info.num_total_refs; ++frame) {
+ for (frame = 0; frame < num_total_refs; ++frame) {
if (cm->global_motion[frame].wmtype != IDENTITY) {
use_global_motion = true;
break;
@@ -4601,12 +4606,58 @@
if (!use_global_motion) {
return;
}
+
+ int our_ref = cpi->gm_info.base_model_our_ref;
+ int their_ref = cpi->gm_info.base_model_their_ref;
+ aom_wb_write_primitive_quniform(wb, num_total_refs + 1, our_ref);
+ if (our_ref >= num_total_refs) {
+ // Special case: Use IDENTITY model
+ // Nothing more to code
+ assert(their_ref == -1);
+ } else {
+ RefCntBuffer *buf = get_ref_frame_buf(cm, our_ref);
+ assert(buf);
+ int their_num_refs = buf->num_ref_frames;
+ if (their_num_refs == 0) {
+ // Special case: if an intra/key frame is used as a ref, use an
+ // IDENTITY model
+ // Nothing more to code
+ assert(their_ref == -1);
+ } else {
+ aom_wb_write_primitive_quniform(wb, their_num_refs, their_ref);
+ }
+ }
#endif // CONFIG_IMPROVED_GLOBAL_MOTION
- for (frame = 0; frame < cm->ref_frames_info.num_total_refs; ++frame) {
+ for (frame = 0; frame < num_total_refs; ++frame) {
+#if CONFIG_IMPROVED_GLOBAL_MOTION
+ int temporal_distance;
+ if (seq_params->order_hint_info.enable_order_hint) {
+ const RefCntBuffer *const ref_buf = get_ref_frame_buf(cm, frame);
+ temporal_distance = get_relative_dist(&seq_params->order_hint_info,
+ (int)cm->cur_frame->order_hint,
+ (int)ref_buf->order_hint);
+ } else {
+ temporal_distance = 1;
+ }
+
+ if (temporal_distance == 0) {
+ // Don't code global motion for frames at the same temporal instant
+ assert(cm->global_motion[frame].wmtype == IDENTITY);
+ continue;
+ }
+
+ WarpedMotionParams ref_params_;
+ av1_scale_warp_model(&cm->base_global_motion_model,
+ cm->base_global_motion_distance, &ref_params_,
+ temporal_distance);
+ WarpedMotionParams *ref_params = &ref_params_;
+#else
const WarpedMotionParams *ref_params =
cm->prev_frame ? &cm->prev_frame->global_motion[frame]
: &default_warp_params;
+#endif // CONFIG_IMPROVED_GLOBAL_MOTION
+
write_global_motion_params(&cm->global_motion[frame], ref_params, wb,
#if !CONFIG_FLEX_MVRES
cm->features.allow_high_precision_mv);
diff --git a/av1/encoder/encode_strategy.c b/av1/encoder/encode_strategy.c
index a931f84..2683de9 100644
--- a/av1/encoder/encode_strategy.c
+++ b/av1/encoder/encode_strategy.c
@@ -1105,6 +1105,9 @@
AOMMIN(cm->seq_params.num_same_ref_compound,
cm->ref_frames_info.num_total_refs);
#endif // CONFIG_ALLOW_SAME_REF_COMPOUND
+#if CONFIG_IMPROVED_GLOBAL_MOTION
+ cm->cur_frame->num_ref_frames = cm->ref_frames_info.num_total_refs;
+#endif // CONFIG_IMPROVED_GLOBAL_MOTION
// ref_frame_flags is defined based on the external flag
// max-reference-frames.
diff --git a/av1/encoder/encoder.h b/av1/encoder/encoder.h
index 4b68d58..c272ed9 100644
--- a/av1/encoder/encoder.h
+++ b/av1/encoder/encoder.h
@@ -1978,6 +1978,25 @@
int segment_map_w; /*!< segment map width */
int segment_map_h; /*!< segment map height */
/**@}*/
+
+#if CONFIG_IMPROVED_GLOBAL_MOTION
+ /*!
+ * \brief Error ratio for each selected global motion model
+ *
+ * This is used to help decide which models will actually be used,
+ * because that decision has to be deferred until we actually select a
+ * base model to use
+ */
+ double erroradvantage[INTER_REFS_PER_FRAME];
+
+ /**
+ * \name Reference path for selected base model
+ */
+ /**@{*/
+ int base_model_our_ref; /*!< which of our ref frames to copy from */
+ int base_model_their_ref; /*!< which model to copy from that frame */
+ /**@}*/
+#endif // CONFIG_IMPROVED_GLOBAL_MOTION
} GlobalMotionInfo;
/*!
diff --git a/av1/encoder/global_motion_facade.c b/av1/encoder/global_motion_facade.c
index 69b725c..648ca60 100644
--- a/av1/encoder/global_motion_facade.c
+++ b/av1/encoder/global_motion_facade.c
@@ -106,14 +106,25 @@
// For the given reference frame, computes the global motion parameters for
// different motion models and finds the best.
+#if CONFIG_IMPROVED_GLOBAL_MOTION
+static AOM_INLINE void compute_global_motion_for_ref_frame(
+ AV1_COMP *cpi, YV12_BUFFER_CONFIG *ref_buf[INTER_REFS_PER_FRAME], int frame,
+ MotionModel *motion_models, uint8_t *segment_map, const int segment_map_w,
+ const int segment_map_h) {
+#else
static AOM_INLINE void compute_global_motion_for_ref_frame(
AV1_COMP *cpi, YV12_BUFFER_CONFIG *ref_buf[INTER_REFS_PER_FRAME], int frame,
MotionModel *motion_models, uint8_t *segment_map, const int segment_map_w,
const int segment_map_h, const WarpedMotionParams *ref_params) {
+#endif // CONFIG_IMPROVED_GLOBAL_MOTION
ThreadData *const td = &cpi->td;
MACROBLOCK *const x = &td->mb;
AV1_COMMON *const cm = &cpi->common;
MACROBLOCKD *const xd = &x->e_mbd;
+#if CONFIG_IMPROVED_GLOBAL_MOTION
+ GlobalMotionInfo *const gm_info = &cpi->gm_info;
+#endif // CONFIG_IMPROVED_GLOBAL_MOTION
+
int i;
int src_width = cpi->source->y_crop_width;
int src_height = cpi->source->y_crop_height;
@@ -204,6 +215,12 @@
if (tmp_wm_params.wmtype == IDENTITY) continue;
#endif // CONFIG_IMPROVED_GLOBAL_MOTION
+#if CONFIG_IMPROVED_GLOBAL_MOTION
+ // Apply initial quality filter, which depends only on the error metrics
+ // and not the model cost
+ if (warp_error >= ref_frame_error * erroradv_tr) continue;
+#endif // CONFIG_IMPROVED_GLOBAL_MOTION
+
if (warp_error < best_warp_error) {
best_ref_frame_error = ref_frame_error;
best_warp_error = warp_error;
@@ -246,6 +263,12 @@
// ref_frame_error == 0
assert(best_ref_frame_error > 0);
+#if CONFIG_IMPROVED_GLOBAL_MOTION
+ gm_info->erroradvantage[frame] =
+ (double)best_warp_error / best_ref_frame_error;
+
+ break;
+#else
// If the best error advantage found doesn't meet the threshold for
// this motion type, revert to IDENTITY.
if (!av1_is_enough_erroradvantage(
@@ -260,6 +283,7 @@
}
if (cm->global_motion[frame].wmtype != IDENTITY) break;
+#endif // CONFIG_IMPROVED_GLOBAL_MOTION
}
aom_free_flow_data(flow_data);
@@ -271,6 +295,11 @@
AV1_COMP *cpi, YV12_BUFFER_CONFIG *ref_buf[INTER_REFS_PER_FRAME], int frame,
MotionModel *motion_models, uint8_t *segment_map, int segment_map_w,
int segment_map_h) {
+#if CONFIG_IMPROVED_GLOBAL_MOTION
+ compute_global_motion_for_ref_frame(cpi, ref_buf, frame, motion_models,
+ segment_map, segment_map_w,
+ segment_map_h);
+#else
AV1_COMMON *const cm = &cpi->common;
const WarpedMotionParams *ref_params =
cm->prev_frame ? &cm->prev_frame->global_motion[frame]
@@ -279,6 +308,7 @@
compute_global_motion_for_ref_frame(cpi, ref_buf, frame, motion_models,
segment_map, segment_map_w, segment_map_h,
ref_params);
+#endif // CONFIG_IMPROVED_GLOBAL_MOTION
}
// Loops over valid reference frames and computes global motion estimation.
@@ -445,6 +475,150 @@
return true;
}
+#if CONFIG_IMPROVED_GLOBAL_MOTION
+// Select which global motion model to use as a base
+static AOM_INLINE void pick_base_gm_params(AV1_COMP *cpi) {
+ AV1_COMMON *const cm = &cpi->common;
+ const SequenceHeader *const seq_params = &cm->seq_params;
+ GlobalMotionInfo *const gm_info = &cpi->gm_info;
+ int num_total_refs = cm->ref_frames_info.num_total_refs;
+
+ int best_our_ref;
+ int best_their_ref;
+ const WarpedMotionParams *best_base_model;
+ int best_temporal_distance;
+ int best_num_models;
+ int best_cost;
+
+ // Bitmask of which models we will actually use if we accept the current
+ // best base model
+ uint8_t best_enable_models;
+
+ // First, evaluate the identity model as a base
+ {
+ int this_num_models = 0;
+ int this_cost =
+ aom_count_primitive_quniform(num_total_refs + 1, num_total_refs)
+ << AV1_PROB_COST_SHIFT;
+ uint8_t this_enable_models = 0;
+
+ for (int frame = 0; frame < num_total_refs; frame++) {
+ const WarpedMotionParams *model = &cm->global_motion[frame];
+ if (model->wmtype == IDENTITY) continue;
+
+ int model_cost = gm_get_params_cost(model, &default_warp_params,
+ cm->features.fr_mv_precision);
+ bool use_model = av1_is_enough_erroradvantage(
+ gm_info->erroradvantage[frame], model_cost);
+
+ if (use_model) {
+ this_num_models += 1;
+ this_cost += model_cost;
+ this_enable_models |= (1 << frame);
+ }
+ }
+
+ // Set initial values
+ best_our_ref = cm->ref_frames_info.num_total_refs;
+ best_their_ref = -1;
+ best_base_model = &default_warp_params;
+ best_temporal_distance = 1;
+ best_num_models = this_num_models;
+ best_cost = this_cost;
+ best_enable_models = this_enable_models;
+ }
+
+ // Then try each available reference model in turn
+ for (int our_ref = 0; our_ref < num_total_refs; ++our_ref) {
+ const int ref_disabled = !(cm->ref_frame_flags & (1 << our_ref));
+ RefCntBuffer *buf = get_ref_frame_buf(cm, our_ref);
+ // Skip looking at invalid ref frames
+ if (buf == NULL ||
+ (ref_disabled && cpi->sf.hl_sf.recode_loop != DISALLOW_RECODE)) {
+ continue;
+ }
+
+ int their_num_refs = buf->num_ref_frames;
+ for (int their_ref = 0; their_ref < their_num_refs; ++their_ref) {
+ const WarpedMotionParams *base_model = &buf->global_motion[their_ref];
+ if (base_model->wmtype == IDENTITY) {
+ continue;
+ }
+
+ int base_temporal_distance =
+ get_relative_dist(&seq_params->order_hint_info, buf->order_hint,
+ buf->ref_order_hints[their_ref]);
+
+ int this_num_models = 0;
+ int this_cost =
+ (aom_count_primitive_quniform(num_total_refs + 1, our_ref) +
+ aom_count_primitive_quniform(their_num_refs, their_ref))
+ << AV1_PROB_COST_SHIFT;
+ uint8_t this_enable_models = 0;
+
+ for (int frame = 0; frame < num_total_refs; frame++) {
+ const WarpedMotionParams *model = &cm->global_motion[frame];
+ if (model->wmtype == IDENTITY) continue;
+
+ int temporal_distance;
+ if (seq_params->order_hint_info.enable_order_hint) {
+ const RefCntBuffer *const ref_buf = get_ref_frame_buf(cm, frame);
+ temporal_distance = get_relative_dist(&seq_params->order_hint_info,
+ (int)cm->cur_frame->order_hint,
+ (int)ref_buf->order_hint);
+ } else {
+ temporal_distance = 1;
+ }
+
+ if (temporal_distance == 0) {
+ // Don't code global motion for frames at the same temporal instant
+ assert(model->wmtype == IDENTITY);
+ continue;
+ }
+
+ WarpedMotionParams ref_params;
+ av1_scale_warp_model(base_model, base_temporal_distance, &ref_params,
+ temporal_distance);
+
+ int model_cost = gm_get_params_cost(model, &ref_params,
+ cm->features.fr_mv_precision);
+ bool use_model = av1_is_enough_erroradvantage(
+ gm_info->erroradvantage[frame], model_cost);
+
+ if (use_model) {
+ this_num_models += 1;
+ this_cost += model_cost;
+ this_enable_models |= (1 << frame);
+ }
+ }
+
+ if (this_num_models > best_num_models ||
+ (this_num_models == best_num_models && this_cost < best_cost)) {
+ best_our_ref = our_ref;
+ best_their_ref = their_ref;
+ best_base_model = base_model;
+ best_temporal_distance = base_temporal_distance;
+ best_num_models = this_num_models;
+ best_cost = this_cost;
+ best_enable_models = this_enable_models;
+ }
+ }
+ }
+
+ gm_info->base_model_our_ref = best_our_ref;
+ gm_info->base_model_their_ref = best_their_ref;
+ cm->base_global_motion_model = *best_base_model;
+ cm->base_global_motion_distance = best_temporal_distance;
+
+ for (int frame = 0; frame < num_total_refs; frame++) {
+ if ((best_enable_models & (1 << frame)) == 0) {
+ // Disable this model
+ cm->global_motion[frame] = default_warp_params;
+ }
+ }
+}
+#endif // CONFIG_IMPROVED_GLOBAL_MOTION
+
// Initializes parameters used for computing global motion.
static AOM_INLINE void setup_global_motion_info_params(AV1_COMP *cpi) {
GlobalMotionInfo *const gm_info = &cpi->gm_info;
@@ -544,6 +718,12 @@
else
global_motion_estimation(cpi);
+#if CONFIG_IMPROVED_GLOBAL_MOTION
+ // Once we have determined the best motion model for each ref frame,
+ // choose the base parameters to minimize the total encoding cost
+ pick_base_gm_params(cpi);
+#endif // CONFIG_IMPROVED_GLOBAL_MOTION
+
// Check if the current frame has any valid global motion model across its
// reference frames
if (cpi->sf.gm_sf.disable_gm_search_based_on_stats) {