x264_cpu_mask_misalign_sse();
#endif
}
+
+static void x264_lookahead_thread_init( x264_t *h )
+{
+#if HAVE_MMX
+ /* Misalign mask has to be set separately for each thread. */
+ if( h->param.cpu&X264_CPU_SSE_MISALIGN )
+ x264_cpu_mask_misalign_sse();
+#endif
+}
#endif
/****************************************************************************
if( h->param.i_threads == X264_THREADS_AUTO )
h->param.i_threads = x264_cpu_num_processors() * (h->param.b_sliced_threads?2:3)/2;
+ if( h->param.i_lookahead_threads == X264_THREADS_AUTO )
+ h->param.i_lookahead_threads = h->param.i_threads / (h->param.b_sliced_threads?1:6);
+ int max_sliced_threads = X264_MAX( 1, (h->param.i_height+15)/16 / 4 );
if( h->param.i_threads > 1 )
{
#if !HAVE_THREAD
/* Avoid absurdly small thread slices as they can reduce performance
* and VBV compliance. Capped at an arbitrary 4 rows per thread. */
if( h->param.b_sliced_threads )
- {
- int max_threads = (h->param.i_height+15)/16 / 4;
- h->param.i_threads = X264_MIN( h->param.i_threads, max_threads );
- }
+ h->param.i_threads = X264_MIN( h->param.i_threads, max_sliced_threads );
}
h->param.i_threads = x264_clip3( h->param.i_threads, 1, X264_THREAD_MAX );
+ h->param.i_lookahead_threads = x264_clip3( h->param.i_lookahead_threads, 1, X264_MIN( max_sliced_threads, X264_LOOKAHEAD_THREAD_MAX ) );
if( h->param.i_threads == 1 )
+ {
h->param.b_sliced_threads = 0;
+ h->param.i_lookahead_threads = 1;
+ }
h->i_thread_frames = h->param.b_sliced_threads ? 1 : h->param.i_threads;
if( h->i_thread_frames > 1 )
h->param.nalu_process = NULL;
if( h->param.i_threads > 1 &&
x264_threadpool_init( &h->threadpool, h->param.i_threads, (void*)x264_encoder_thread_init, h ) )
goto fail;
+ if( h->param.i_lookahead_threads > 1 &&
+ x264_threadpool_init( &h->lookaheadpool, h->param.i_lookahead_threads, (void*)x264_lookahead_thread_init, h ) )
+ goto fail;
h->thread[0] = h;
for( int i = 1; i < h->param.i_threads + !!h->param.i_sync_lookahead; i++ )
CHECKED_MALLOC( h->thread[i], sizeof(x264_t) );
+ if( h->param.i_lookahead_threads > 1 )
+ for( int i = 0; i < h->param.i_lookahead_threads; i++ )
+ {
+ CHECKED_MALLOC( h->lookahead_thread[i], sizeof(x264_t) );
+ *h->lookahead_thread[i] = *h;
+ }
for( int i = 0; i < h->param.i_threads; i++ )
{
x264_threadpool_wait_all( h );
if( h->param.i_threads > 1 )
x264_threadpool_delete( h->threadpool );
+ if( h->param.i_lookahead_threads > 1 )
+ x264_threadpool_delete( h->lookaheadpool );
if( h->i_thread_frames > 1 )
{
for( int i = 0; i < h->i_thread_frames; i++ )
if( h->thread[i]->fref[0][j] && h->thread[i]->fref[0][j]->b_duplicate )
x264_frame_delete( h->thread[i]->fref[0][j] );
+ if( h->param.i_lookahead_threads > 1 )
+ for( int i = 0; i < h->param.i_lookahead_threads; i++ )
+ x264_free( h->lookahead_thread[i] );
+
for( int i = h->param.i_threads - 1; i >= 0; i-- )
{
x264_frame_t **frame;
}
}
+/* Output buffers are separated by 128 bytes to avoid false sharing of cachelines
+ * in multithreaded lookahead. */
+#define PAD_SIZE 32
+/* cost_est, cost_est_aq, intra_mbs, num rows */
+#define NUM_INTS 4
+#define COST_EST 0
+#define COST_EST_AQ 1
+#define INTRA_MBS 2
+#define NUM_ROWS 3
+#define ROW_SATD (NUM_INTS + (h->mb.i_mb_y - h->i_threadslice_start))
+
static void x264_slicetype_mb_cost( x264_t *h, x264_mb_analysis_t *a,
x264_frame_t **frames, int p0, int p1, int b,
- int dist_scale_factor, int do_search[2], const x264_weight_t *w )
+ int dist_scale_factor, int do_search[2], const x264_weight_t *w,
+ int *output_inter, int *output_intra )
{
x264_frame_t *fref0 = frames[p0];
x264_frame_t *fref1 = frames[p1];
#define MVC(mv) { CP32( mvc[i_mvc], mv ); i_mvc++; }
if( i_mb_x < h->mb.i_mb_width - 1 )
MVC( fenc_mv[1] );
- if( i_mb_y < h->mb.i_mb_height - 1 )
+ if( i_mb_y < h->i_threadslice_end - 1 )
{
MVC( fenc_mv[i_mb_stride] );
if( i_mb_x > 0 )
int i_icost_aq = i_icost;
if( h->param.rc.i_aq_mode )
i_icost_aq = (i_icost_aq * fenc->i_inv_qscale_factor[i_mb_xy] + 128) >> 8;
- fenc->i_row_satds[0][0][h->mb.i_mb_y] += i_icost_aq;
+ output_intra[ROW_SATD] += i_icost_aq;
if( b_frame_score_mb )
{
- fenc->i_cost_est[0][0] += i_icost;
- fenc->i_cost_est_aq[0][0] += i_icost_aq;
+ output_intra[COST_EST] += i_icost;
+ output_intra[COST_EST_AQ] += i_icost_aq;
}
}
i_bcost += lowres_penalty;
list_used = 0;
}
if( b_frame_score_mb )
- fenc->i_intra_mbs[b-p0] += b_intra;
+ output_inter[INTRA_MBS] += b_intra;
}
/* In an I-frame, we've already added the results above in the intra section. */
int i_bcost_aq = i_bcost;
if( h->param.rc.i_aq_mode )
i_bcost_aq = (i_bcost_aq * fenc->i_inv_qscale_factor[i_mb_xy] + 128) >> 8;
- fenc->i_row_satds[b-p0][p1-b][h->mb.i_mb_y] += i_bcost_aq;
+ output_inter[ROW_SATD] += i_bcost_aq;
if( b_frame_score_mb )
{
/* Don't use AQ-weighted costs for slicetype decision, only for ratecontrol. */
- fenc->i_cost_est[b-p0][p1-b] += i_bcost;
- fenc->i_cost_est_aq[b-p0][p1-b] += i_bcost_aq;
+ output_inter[COST_EST] += i_bcost;
+ output_inter[COST_EST_AQ] += i_bcost_aq;
}
}
(h->mb.i_mb_width - 2) * (h->mb.i_mb_height - 2) :\
h->mb.i_mb_width * h->mb.i_mb_height)
+typedef struct
+{
+ x264_t *h;
+ x264_mb_analysis_t *a;
+ x264_frame_t **frames;
+ int p0;
+ int p1;
+ int b;
+ int dist_scale_factor;
+ int *do_search;
+ const x264_weight_t *w;
+ int *output_inter;
+ int *output_intra;
+} x264_slicetype_slice_t;
+
+static void x264_slicetype_slice_cost( x264_slicetype_slice_t *s )
+{
+ x264_t *h = s->h;
+
+ /* Lowres lookahead goes backwards because the MVs are used as predictors in the main encode.
+ * This considerably improves MV prediction overall. */
+
+ /* The edge mbs seem to reduce the predictive quality of the
+ * whole frame's score, but are needed for a spatial distribution. */
+ int do_edges = h->param.rc.b_mb_tree || h->param.rc.i_vbv_buffer_size || h->mb.i_mb_width <= 2 || h->mb.i_mb_height <= 2;
+
+ int start_y = X264_MIN( h->i_threadslice_end - 1, h->mb.i_mb_height - 2 + do_edges );
+ int end_y = X264_MAX( h->i_threadslice_start, 1 - do_edges );
+ int start_x = h->mb.i_mb_width - 2 + do_edges;
+ int end_x = 1 - do_edges;
+
+ for( h->mb.i_mb_y = start_y; h->mb.i_mb_y >= end_y; h->mb.i_mb_y-- )
+ for( h->mb.i_mb_x = start_x; h->mb.i_mb_x >= end_x; h->mb.i_mb_x-- )
+ x264_slicetype_mb_cost( h, s->a, s->frames, s->p0, s->p1, s->b, s->dist_scale_factor,
+ s->do_search, s->w, s->output_inter, s->output_intra );
+}
+
static int x264_slicetype_frame_cost( x264_t *h, x264_mb_analysis_t *a,
x264_frame_t **frames, int p0, int p1, int b,
int b_intra_penalty )
int i_score = 0;
int do_search[2];
const x264_weight_t *w = x264_weight_none;
+ x264_frame_t *fenc = frames[b];
+
/* Check whether we already evaluated this frame
* If we have tried this frame as P, then we have also tried
* the preceding frames as B. (is this still true?) */
/* Also check that we already calculated the row SATDs for the current frame. */
- if( frames[b]->i_cost_est[b-p0][p1-b] >= 0 && (!h->param.rc.i_vbv_buffer_size || frames[b]->i_row_satds[b-p0][p1-b][0] != -1) )
- i_score = frames[b]->i_cost_est[b-p0][p1-b];
+ if( fenc->i_cost_est[b-p0][p1-b] >= 0 && (!h->param.rc.i_vbv_buffer_size || fenc->i_row_satds[b-p0][p1-b][0] != -1) )
+ i_score = fenc->i_cost_est[b-p0][p1-b];
else
{
int dist_scale_factor = 128;
- int *row_satd = frames[b]->i_row_satds[b-p0][p1-b];
- int *row_satd_intra = frames[b]->i_row_satds[0][0];
/* For each list, check to see whether we have lowres motion-searched this reference frame before. */
- do_search[0] = b != p0 && frames[b]->lowres_mvs[0][b-p0-1][0][0] == 0x7FFF;
- do_search[1] = b != p1 && frames[b]->lowres_mvs[1][p1-b-1][0][0] == 0x7FFF;
+ do_search[0] = b != p0 && fenc->lowres_mvs[0][b-p0-1][0][0] == 0x7FFF;
+ do_search[1] = b != p1 && fenc->lowres_mvs[1][p1-b-1][0][0] == 0x7FFF;
if( do_search[0] )
{
if( h->param.analyse.i_weighted_pred && b == p1 )
{
x264_emms();
- x264_weights_analyse( h, frames[b], frames[p0], 1 );
- w = frames[b]->weight[0];
+ x264_weights_analyse( h, fenc, frames[p0], 1 );
+ w = fenc->weight[0];
}
- frames[b]->lowres_mvs[0][b-p0-1][0][0] = 0;
+ fenc->lowres_mvs[0][b-p0-1][0][0] = 0;
}
- if( do_search[1] ) frames[b]->lowres_mvs[1][p1-b-1][0][0] = 0;
+ if( do_search[1] ) fenc->lowres_mvs[1][p1-b-1][0][0] = 0;
- if( b == p1 )
- frames[b]->i_intra_mbs[b-p0] = 0;
- if( !frames[b]->b_intra_calculated )
- {
- frames[b]->i_cost_est[0][0] = 0;
- frames[b]->i_cost_est_aq[0][0] = 0;
- }
if( p1 != p0 )
dist_scale_factor = ( ((b-p0) << 8) + ((p1-p0) >> 1) ) / (p1-p0);
- frames[b]->i_cost_est[b-p0][p1-b] = 0;
- frames[b]->i_cost_est_aq[b-p0][p1-b] = 0;
-
- /* Lowres lookahead goes backwards because the MVs are used as predictors in the main encode.
- * This considerably improves MV prediction overall. */
+ int output_buf_size = h->mb.i_mb_height + (NUM_INTS + PAD_SIZE) * h->param.i_lookahead_threads;
+ int *output_inter[X264_LOOKAHEAD_THREAD_MAX+1];
+ int *output_intra[X264_LOOKAHEAD_THREAD_MAX+1];
+ output_inter[0] = h->scratch_buffer2;
+ output_intra[0] = output_inter[0] + output_buf_size;
- /* The edge mbs seem to reduce the predictive quality of the
- * whole frame's score, but are needed for a spatial distribution. */
- if( h->param.rc.b_mb_tree || h->param.rc.i_vbv_buffer_size ||
- h->mb.i_mb_width <= 2 || h->mb.i_mb_height <= 2 )
+ if( h->param.i_lookahead_threads > 1 )
{
- for( h->mb.i_mb_y = h->mb.i_mb_height - 1; h->mb.i_mb_y >= 0; h->mb.i_mb_y-- )
+ x264_slicetype_slice_t s[X264_LOOKAHEAD_THREAD_MAX];
+
+ for( int i = 0; i < h->param.i_lookahead_threads; i++ )
{
- row_satd[h->mb.i_mb_y] = 0;
- if( !frames[b]->b_intra_calculated )
- row_satd_intra[h->mb.i_mb_y] = 0;
- for( h->mb.i_mb_x = h->mb.i_mb_width - 1; h->mb.i_mb_x >= 0; h->mb.i_mb_x-- )
- x264_slicetype_mb_cost( h, a, frames, p0, p1, b, dist_scale_factor, do_search, w );
+ x264_t *t = h->lookahead_thread[i];
+
+ /* FIXME move this somewhere else */
+ t->mb.i_me_method = h->mb.i_me_method;
+ t->mb.i_subpel_refine = h->mb.i_subpel_refine;
+ t->mb.b_chroma_me = h->mb.b_chroma_me;
+
+ s[i] = (x264_slicetype_slice_t){ t, a, frames, p0, p1, b, dist_scale_factor, do_search, w,
+ output_inter[i], output_intra[i] };
+
+ t->i_threadslice_start = ((h->mb.i_mb_height * i + h->param.i_lookahead_threads/2) / h->param.i_lookahead_threads);
+ t->i_threadslice_end = ((h->mb.i_mb_height * (i+1) + h->param.i_lookahead_threads/2) / h->param.i_lookahead_threads);
+
+ int thread_height = t->i_threadslice_end - t->i_threadslice_start;
+ int thread_output_size = thread_height + NUM_INTS;
+ memset( output_inter[i], 0, thread_output_size * sizeof(int) );
+ memset( output_intra[i], 0, thread_output_size * sizeof(int) );
+ output_inter[i][NUM_ROWS] = output_intra[i][NUM_ROWS] = thread_height;
+
+ output_inter[i+1] = output_inter[i] + thread_output_size + PAD_SIZE;
+ output_intra[i+1] = output_intra[i] + thread_output_size + PAD_SIZE;
+
+ x264_threadpool_run( h->lookaheadpool, (void*)x264_slicetype_slice_cost, &s[i] );
}
+ for( int i = 0; i < h->param.i_lookahead_threads; i++ )
+ x264_threadpool_wait( h->lookaheadpool, &s[i] );
}
else
{
- for( h->mb.i_mb_y = h->mb.i_mb_height - 2; h->mb.i_mb_y >= 1; h->mb.i_mb_y-- )
- for( h->mb.i_mb_x = h->mb.i_mb_width - 2; h->mb.i_mb_x >= 1; h->mb.i_mb_x-- )
- x264_slicetype_mb_cost( h, a, frames, p0, p1, b, dist_scale_factor, do_search, w );
+ h->i_threadslice_start = 0;
+ h->i_threadslice_end = h->mb.i_mb_height;
+ memset( output_inter[0], 0, (output_buf_size - PAD_SIZE) * sizeof(int) );
+ memset( output_intra[0], 0, (output_buf_size - PAD_SIZE) * sizeof(int) );
+ output_inter[0][NUM_ROWS] = output_intra[0][NUM_ROWS] = h->mb.i_mb_height;
+ x264_slicetype_slice_t s = (x264_slicetype_slice_t){ h, a, frames, p0, p1, b, dist_scale_factor, do_search, w,
+ output_inter[0], output_intra[0] };
+ x264_slicetype_slice_cost( &s );
+ }
+
+ /* Sum up accumulators */
+ if( b == p1 )
+ fenc->i_intra_mbs[b-p0] = 0;
+ if( !fenc->b_intra_calculated )
+ {
+ fenc->i_cost_est[0][0] = 0;
+ fenc->i_cost_est_aq[0][0] = 0;
+ }
+ fenc->i_cost_est[b-p0][p1-b] = 0;
+ fenc->i_cost_est_aq[b-p0][p1-b] = 0;
+
+ int *row_satd_inter = fenc->i_row_satds[b-p0][p1-b];
+ int *row_satd_intra = fenc->i_row_satds[0][0];
+ for( int i = 0; i < h->param.i_lookahead_threads; i++ )
+ {
+ if( b == p1 )
+ fenc->i_intra_mbs[b-p0] += output_inter[i][INTRA_MBS];
+ if( !fenc->b_intra_calculated )
+ {
+ fenc->i_cost_est[0][0] += output_intra[i][COST_EST];
+ fenc->i_cost_est_aq[0][0] += output_intra[i][COST_EST_AQ];
+ }
+
+ fenc->i_cost_est[b-p0][p1-b] += output_inter[i][COST_EST];
+ fenc->i_cost_est_aq[b-p0][p1-b] += output_inter[i][COST_EST_AQ];
+
+ if( h->param.rc.i_vbv_buffer_size )
+ {
+ int row_count = output_inter[i][NUM_ROWS];
+ memcpy( row_satd_inter, output_inter[i] + NUM_INTS, row_count * sizeof(int) );
+ if( !fenc->b_intra_calculated )
+ memcpy( row_satd_intra, output_intra[i] + NUM_INTS, row_count * sizeof(int) );
+ row_satd_inter += row_count;
+ row_satd_intra += row_count;
+ }
}
- i_score = frames[b]->i_cost_est[b-p0][p1-b];
+ i_score = fenc->i_cost_est[b-p0][p1-b];
if( b != p1 )
i_score = (uint64_t)i_score * 100 / (120 + h->param.i_bframe_bias);
else
- frames[b]->b_intra_calculated = 1;
+ fenc->b_intra_calculated = 1;
- frames[b]->i_cost_est[b-p0][p1-b] = i_score;
+ fenc->i_cost_est[b-p0][p1-b] = i_score;
x264_emms();
}
{
// arbitrary penalty for I-blocks after B-frames
int nmb = NUM_MBS;
- i_score += (uint64_t)i_score * frames[b]->i_intra_mbs[b-p0] / (nmb * 8);
+ i_score += (uint64_t)i_score * fenc->i_intra_mbs[b-p0] / (nmb * 8);
}
return i_score;
}