+// This function calls all the eedi2 filters in sequence for a given plane.
+// It outputs the final interpolated image to pv->eedi_full[DST2PF].
+void eedi2_interpolate_plane( hb_filter_private_t * pv, int k )
+{
+ /* We need all these pointers. No, seriously.
+ I swear. It's not a joke. They're used.
+ All nine of them. */
+ uint8_t * mskp = pv->eedi_half[MSKPF][k];
+ uint8_t * srcp = pv->eedi_half[SRCPF][k];
+ uint8_t * tmpp = pv->eedi_half[TMPPF][k];
+ uint8_t * dstp = pv->eedi_half[DSTPF][k];
+ uint8_t * dst2p = pv->eedi_full[DST2PF][k];
+ uint8_t * tmp2p2 = pv->eedi_full[TMP2PF2][k];
+ uint8_t * msk2p = pv->eedi_full[MSK2PF][k];
+ uint8_t * tmp2p = pv->eedi_full[TMP2PF][k];
+ uint8_t * dst2mp = pv->eedi_full[DST2MPF][k];
+ int * cx2 = pv->cx2;
+ int * cy2 = pv->cy2;
+ int * cxy = pv->cxy;
+ int * tmpc = pv->tmpc;
+
+ int pitch = pv->ref_stride[k];
+ int height = pv->height[k]; int width = pv->width[k];
+ int half_height = height / 2;
+
+ // edge mask
+ eedi2_build_edge_mask( mskp, pitch, srcp, pitch,
+ pv->magnitude_threshold, pv->variance_threshold, pv->laplacian_threshold,
+ half_height, width );
+ eedi2_erode_edge_mask( mskp, pitch, tmpp, pitch, pv->erosion_threshold, half_height, width );
+ eedi2_dilate_edge_mask( tmpp, pitch, mskp, pitch, pv->dilation_threshold, half_height, width );
+ eedi2_erode_edge_mask( mskp, pitch, tmpp, pitch, pv->erosion_threshold, half_height, width );
+ eedi2_remove_small_gaps( tmpp, pitch, mskp, pitch, half_height, width );
+
+ // direction mask
+ eedi2_calc_directions( k, mskp, pitch, srcp, pitch, tmpp, pitch,
+ pv->maximum_search_distance, pv->noise_threshold,
+ half_height, width );
+ eedi2_filter_dir_map( mskp, pitch, tmpp, pitch, dstp, pitch, half_height, width );
+ eedi2_expand_dir_map( mskp, pitch, dstp, pitch, tmpp, pitch, half_height, width );
+ eedi2_filter_map( mskp, pitch, tmpp, pitch, dstp, pitch, half_height, width );
+
+ // upscale 2x vertically
+ eedi2_upscale_by_2( srcp, dst2p, half_height, pitch );
+ eedi2_upscale_by_2( dstp, tmp2p2, half_height, pitch );
+ eedi2_upscale_by_2( mskp, msk2p, half_height, pitch );
+
+ // upscale the direction mask
+ eedi2_mark_directions_2x( msk2p, pitch, tmp2p2, pitch, tmp2p, pitch, pv->tff, height, width );
+ eedi2_filter_dir_map_2x( msk2p, pitch, tmp2p, pitch, dst2mp, pitch, pv->tff, height, width );
+ eedi2_expand_dir_map_2x( msk2p, pitch, dst2mp, pitch, tmp2p, pitch, pv->tff, height, width );
+ eedi2_fill_gaps_2x( msk2p, pitch, tmp2p, pitch, dst2mp, pitch, pv->tff, height, width );
+ eedi2_fill_gaps_2x( msk2p, pitch, dst2mp, pitch, tmp2p, pitch, pv->tff, height, width );
+
+ // interpolate a full-size plane
+ eedi2_interpolate_lattice( k, tmp2p, pitch, dst2p, pitch, tmp2p2, pitch, pv->tff,
+ pv->noise_threshold, height, width );
+
+ if( pv->post_processing == 1 || pv->post_processing == 3 )
+ {
+ // make sure the edge directions are consistent
+ eedi2_bit_blit( tmp2p2, pitch, tmp2p, pitch, pv->width[k], pv->height[k] );
+ eedi2_filter_dir_map_2x( msk2p, pitch, tmp2p, pitch, dst2mp, pitch, pv->tff, height, width );
+ eedi2_expand_dir_map_2x( msk2p, pitch, dst2mp, pitch, tmp2p, pitch, pv->tff, height, width );
+ eedi2_post_process( tmp2p, pitch, tmp2p2, pitch, dst2p, pitch, pv->tff, height, width );
+ }
+ if( pv->post_processing == 2 || pv->post_processing == 3 )
+ {
+ // filter junctions and corners
+ eedi2_gaussian_blur1( srcp, pitch, tmpp, pitch, srcp, pitch, half_height, width );
+ eedi2_calc_derivatives( srcp, pitch, half_height, width, cx2, cy2, cxy );
+ eedi2_gaussian_blur_sqrt2( cx2, tmpc, cx2, pitch, half_height, width);
+ eedi2_gaussian_blur_sqrt2( cy2, tmpc, cy2, pitch, half_height, width);
+ eedi2_gaussian_blur_sqrt2( cxy, tmpc, cxy, pitch, half_height, width);
+ eedi2_post_process_corner( cx2, cy2, cxy, pitch, tmp2p2, pitch, dst2p, pitch, height, width, pv->tff );
+ }
+}
+
+/*
+ * eedi2 interpolate this plane in a single thread.
+ */
+void eedi2_filter_thread( void *thread_args_v )
+{
+ eedi2_arguments_t *eedi2_work = NULL;
+ hb_filter_private_t * pv;
+ int run = 1;
+ int plane;
+ eedi2_thread_arg_t *thread_args = thread_args_v;
+
+ pv = thread_args->pv;
+ plane = thread_args->plane;
+
+ hb_log("eedi2 thread started for plane %d", plane);
+
+ while( run )
+ {
+ /*
+ * Wait here until there is work to do. hb_lock() blocks until
+ * render releases it to say that there is more work to do.
+ */
+ hb_lock( pv->eedi2_begin_lock[plane] );
+
+ eedi2_work = &pv->eedi2_arguments[plane];
+
+ if( eedi2_work->stop )
+ {
+ /*
+ * No more work to do, exit this thread.
+ */
+ run = 0;
+ continue;
+ }
+
+ /*
+ * Process plane
+ */
+ eedi2_interpolate_plane( pv, plane );
+
+ /*
+ * Finished this segment, let everyone know.
+ */
+ hb_unlock( pv->eedi2_complete_lock[plane] );
+ }
+ free( thread_args_v );
+}
+
+// Sets up the input field planes for EEDI2 in pv->eedi_half[SRCPF]
+// and then runs eedi2_filter_thread for each plane.
+void eedi2_planer( hb_filter_private_t * pv )
+{
+ /* Copy the first field from the source to a half-height frame. */
+ int i;
+ for( i = 0; i < 3; i++ )
+ {
+ int pitch = pv->ref_stride[i];
+ int start_line = !pv->tff;
+ eedi2_fill_half_height_buffer_plane( &pv->ref[1][i][pitch*start_line], pv->eedi_half[SRCPF][i], pitch, pv->height[i] );
+ }
+
+ int plane;
+ for( plane = 0; plane < 3; plane++ )
+ {
+ /*
+ * Let the thread for this plane know that we've setup work
+ * for it by releasing the begin lock (ensuring that the
+ * complete lock is already locked so that we block when
+ * we try to lock it again below).
+ */
+ hb_lock( pv->eedi2_complete_lock[plane] );
+ hb_unlock( pv->eedi2_begin_lock[plane] );
+ }
+
+ /*
+ * Wait until all three threads have completed by trying to get
+ * the complete lock that we locked earlier for each thread, which
+ * will block until that thread has completed the work on that
+ * plane.
+ */
+ for( plane = 0; plane < 3; plane++ )
+ {
+ hb_lock( pv->eedi2_complete_lock[plane] );
+ hb_unlock( pv->eedi2_complete_lock[plane] );
+ }
+}
+