static void hls_prediction_unit(HEVCContext *s, int x0, int y0,
                                int nPbW, int nPbH,
                                int log2_cb_size, int partIdx)
{
#define POS(c_idx, x, y)                                                              \
    &s->frame->data[c_idx][((y) >> s->sps->vshift[c_idx]) * s->frame->linesize[c_idx] + \
                           (((x) >> s->sps->hshift[c_idx]) << s->sps->pixel_shift)]
    HEVCLocalContext *lc = &s->HEVClc;
    int merge_idx = 0;
    struct MvField current_mv = {{{ 0 }}};

    int min_pu_width = s->sps->min_pu_width;

    MvField *tab_mvf = s->ref->tab_mvf;
    RefPicList  *refPicList = s->ref->refPicList;
    HEVCFrame *ref0, *ref1;

    int tmpstride = MAX_PB_SIZE;

    uint8_t *dst0 = POS(0, x0, y0);
    uint8_t *dst1 = POS(1, x0, y0);
    uint8_t *dst2 = POS(2, x0, y0);
    int log2_min_cb_size = s->sps->log2_min_cb_size;
    int min_cb_width     = s->sps->min_cb_width;
    int x_cb             = x0 >> log2_min_cb_size;
    int y_cb             = y0 >> log2_min_cb_size;
    int ref_idx[2];
    int mvp_flag[2];
    int x_pu, y_pu;
    int i, j;

    if (SAMPLE_CTB(s->skip_flag, x_cb, y_cb)) {
        if (s->sh.max_num_merge_cand > 1)
            merge_idx = ff_hevc_merge_idx_decode(s);
        else
            merge_idx = 0;

        ff_hevc_luma_mv_merge_mode(s, x0, y0,
                                   1 << log2_cb_size,
                                   1 << log2_cb_size,
                                   log2_cb_size, partIdx,
                                   merge_idx, &current_mv);
        x_pu = x0 >> s->sps->log2_min_pu_size;
        y_pu = y0 >> s->sps->log2_min_pu_size;

        for (i = 0; i < nPbW >> s->sps->log2_min_pu_size; i++)
            for (j = 0; j < nPbH >> s->sps->log2_min_pu_size; j++)
                tab_mvf[(y_pu + j) * min_pu_width + x_pu + i] = current_mv;
    } else { /* MODE_INTER */
        lc->pu.merge_flag = ff_hevc_merge_flag_decode(s);
        if (lc->pu.merge_flag) {
            if (s->sh.max_num_merge_cand > 1)
                merge_idx = ff_hevc_merge_idx_decode(s);
            else
                merge_idx = 0;

            ff_hevc_luma_mv_merge_mode(s, x0, y0, nPbW, nPbH, log2_cb_size,
                                       partIdx, merge_idx, &current_mv);
            x_pu = x0 >> s->sps->log2_min_pu_size;
            y_pu = y0 >> s->sps->log2_min_pu_size;

            for (i = 0; i < nPbW >> s->sps->log2_min_pu_size; i++)
                for (j = 0; j < nPbH >> s->sps->log2_min_pu_size; j++)
                    tab_mvf[(y_pu + j) * min_pu_width + x_pu + i] = current_mv;
        } else {
            enum InterPredIdc inter_pred_idc = PRED_L0;
            ff_hevc_set_neighbour_available(s, x0, y0, nPbW, nPbH);
            if (s->sh.slice_type == B_SLICE)
                inter_pred_idc = ff_hevc_inter_pred_idc_decode(s, nPbW, nPbH);

            if (inter_pred_idc != PRED_L1) {
                if (s->sh.nb_refs[L0]) {
                    ref_idx[0] = ff_hevc_ref_idx_lx_decode(s, s->sh.nb_refs[L0]);
                    current_mv.ref_idx[0] = ref_idx[0];
                }
                current_mv.pred_flag[0] = 1;
                hls_mvd_coding(s, x0, y0, 0);
                mvp_flag[0] = ff_hevc_mvp_lx_flag_decode(s);
                ff_hevc_luma_mv_mvp_mode(s, x0, y0, nPbW, nPbH, log2_cb_size,
                                         partIdx, merge_idx, &current_mv,
                                         mvp_flag[0], 0);
                current_mv.mv[0].x += lc->pu.mvd.x;
                current_mv.mv[0].y += lc->pu.mvd.y;
            }

            if (inter_pred_idc != PRED_L0) {
                if (s->sh.nb_refs[L1]) {
                    ref_idx[1] = ff_hevc_ref_idx_lx_decode(s, s->sh.nb_refs[L1]);
                    current_mv.ref_idx[1] = ref_idx[1];
                }

                if (s->sh.mvd_l1_zero_flag == 1 && inter_pred_idc == PRED_BI) {
                    lc->pu.mvd.x = 0;
                    lc->pu.mvd.y = 0;
                } else {
                    hls_mvd_coding(s, x0, y0, 1);
                }

                current_mv.pred_flag[1] = 1;
                mvp_flag[1] = ff_hevc_mvp_lx_flag_decode(s);
                ff_hevc_luma_mv_mvp_mode(s, x0, y0, nPbW, nPbH, log2_cb_size,
                                         partIdx, merge_idx, &current_mv,
                                         mvp_flag[1], 1);
                current_mv.mv[1].x += lc->pu.mvd.x;
                current_mv.mv[1].y += lc->pu.mvd.y;
            }

            x_pu = x0 >> s->sps->log2_min_pu_size;
            y_pu = y0 >> s->sps->log2_min_pu_size;

            for (i = 0; i < nPbW >> s->sps->log2_min_pu_size; i++)
                for(j = 0; j < nPbH >> s->sps->log2_min_pu_size; j++)
                    tab_mvf[(y_pu + j) * min_pu_width + x_pu + i] = current_mv;
        }
    }

    if (current_mv.pred_flag[0]) {
        ref0 = refPicList[0].ref[current_mv.ref_idx[0]];
        if (!ref0)
            return;
        hevc_await_progress(s, ref0, &current_mv.mv[0], y0, nPbH);
    }
    if (current_mv.pred_flag[1]) {
        ref1 = refPicList[1].ref[current_mv.ref_idx[1]];
        if (!ref1)
            return;
        hevc_await_progress(s, ref1, &current_mv.mv[1], y0, nPbH);
    }

    if (current_mv.pred_flag[0] && !current_mv.pred_flag[1]) {
        DECLARE_ALIGNED(16, int16_t,  tmp[MAX_PB_SIZE * MAX_PB_SIZE]);
        DECLARE_ALIGNED(16, int16_t, tmp2[MAX_PB_SIZE * MAX_PB_SIZE]);

        luma_mc(s, tmp, tmpstride, ref0->frame,
                &current_mv.mv[0], x0, y0, nPbW, nPbH);

        if ((s->sh.slice_type == P_SLICE && s->pps->weighted_pred_flag) ||
            (s->sh.slice_type == B_SLICE && s->pps->weighted_bipred_flag)) {
            s->hevcdsp.weighted_pred(s->sh.luma_log2_weight_denom,
                                     s->sh.luma_weight_l0[current_mv.ref_idx[0]],
                                     s->sh.luma_offset_l0[current_mv.ref_idx[0]],
                                     dst0, s->frame->linesize[0], tmp,
                                     tmpstride, nPbW, nPbH);
        } else {
            s->hevcdsp.put_unweighted_pred(dst0, s->frame->linesize[0], tmp, tmpstride, nPbW, nPbH);
        }
        chroma_mc(s, tmp, tmp2, tmpstride, ref0->frame,
                  &current_mv.mv[0], x0 / 2, y0 / 2, nPbW / 2, nPbH / 2);

        if ((s->sh.slice_type == P_SLICE && s->pps->weighted_pred_flag) ||
            (s->sh.slice_type == B_SLICE && s->pps->weighted_bipred_flag)) {
            s->hevcdsp.weighted_pred(s->sh.chroma_log2_weight_denom,
                                     s->sh.chroma_weight_l0[current_mv.ref_idx[0]][0],
                                     s->sh.chroma_offset_l0[current_mv.ref_idx[0]][0],
                                     dst1, s->frame->linesize[1], tmp, tmpstride,
                                     nPbW / 2, nPbH / 2);
            s->hevcdsp.weighted_pred(s->sh.chroma_log2_weight_denom,
                                     s->sh.chroma_weight_l0[current_mv.ref_idx[0]][1],
                                     s->sh.chroma_offset_l0[current_mv.ref_idx[0]][1],
                                     dst2, s->frame->linesize[2], tmp2, tmpstride,
                                     nPbW / 2, nPbH / 2);
        } else {
            s->hevcdsp.put_unweighted_pred(dst1, s->frame->linesize[1], tmp, tmpstride, nPbW/2, nPbH/2);
            s->hevcdsp.put_unweighted_pred(dst2, s->frame->linesize[2], tmp2, tmpstride, nPbW/2, nPbH/2);
        }
    } else if (!current_mv.pred_flag[0] && current_mv.pred_flag[1]) {
        DECLARE_ALIGNED(16, int16_t, tmp [MAX_PB_SIZE * MAX_PB_SIZE]);
        DECLARE_ALIGNED(16, int16_t, tmp2[MAX_PB_SIZE * MAX_PB_SIZE]);

        if (!ref1)
            return;

        luma_mc(s, tmp, tmpstride, ref1->frame,
                &current_mv.mv[1], x0, y0, nPbW, nPbH);

        if ((s->sh.slice_type == P_SLICE && s->pps->weighted_pred_flag) ||
            (s->sh.slice_type == B_SLICE && s->pps->weighted_bipred_flag)) {
            s->hevcdsp.weighted_pred(s->sh.luma_log2_weight_denom,
                                      s->sh.luma_weight_l1[current_mv.ref_idx[1]],
                                      s->sh.luma_offset_l1[current_mv.ref_idx[1]],
                                      dst0, s->frame->linesize[0], tmp, tmpstride,
                                      nPbW, nPbH);
        } else {
            s->hevcdsp.put_unweighted_pred(dst0, s->frame->linesize[0], tmp, tmpstride, nPbW, nPbH);
        }

        chroma_mc(s, tmp, tmp2, tmpstride, ref1->frame,
                  &current_mv.mv[1], x0/2, y0/2, nPbW/2, nPbH/2);

        if ((s->sh.slice_type == P_SLICE && s->pps->weighted_pred_flag) ||
            (s->sh.slice_type == B_SLICE && s->pps->weighted_bipred_flag)) {
            s->hevcdsp.weighted_pred(s->sh.chroma_log2_weight_denom,
                                     s->sh.chroma_weight_l1[current_mv.ref_idx[1]][0],
                                     s->sh.chroma_offset_l1[current_mv.ref_idx[1]][0],
                                     dst1, s->frame->linesize[1], tmp, tmpstride, nPbW/2, nPbH/2);
            s->hevcdsp.weighted_pred(s->sh.chroma_log2_weight_denom,
                                     s->sh.chroma_weight_l1[current_mv.ref_idx[1]][1],
                                     s->sh.chroma_offset_l1[current_mv.ref_idx[1]][1],
                                     dst2, s->frame->linesize[2], tmp2, tmpstride, nPbW/2, nPbH/2);
        } else {
            s->hevcdsp.put_unweighted_pred(dst1, s->frame->linesize[1], tmp, tmpstride, nPbW/2, nPbH/2);
            s->hevcdsp.put_unweighted_pred(dst2, s->frame->linesize[2], tmp2, tmpstride, nPbW/2, nPbH/2);
        }
    } else if (current_mv.pred_flag[0] && current_mv.pred_flag[1]) {
        DECLARE_ALIGNED(16, int16_t, tmp [MAX_PB_SIZE * MAX_PB_SIZE]);
        DECLARE_ALIGNED(16, int16_t, tmp2[MAX_PB_SIZE * MAX_PB_SIZE]);
        DECLARE_ALIGNED(16, int16_t, tmp3[MAX_PB_SIZE * MAX_PB_SIZE]);
        DECLARE_ALIGNED(16, int16_t, tmp4[MAX_PB_SIZE * MAX_PB_SIZE]);
        HEVCFrame *ref0 = refPicList[0].ref[current_mv.ref_idx[0]];
        HEVCFrame *ref1 = refPicList[1].ref[current_mv.ref_idx[1]];

        if (!ref0 || !ref1)
            return;

        luma_mc(s, tmp, tmpstride, ref0->frame,
                &current_mv.mv[0], x0, y0, nPbW, nPbH);
        luma_mc(s, tmp2, tmpstride, ref1->frame,
                &current_mv.mv[1], x0, y0, nPbW, nPbH);

        if ((s->sh.slice_type == P_SLICE && s->pps->weighted_pred_flag) ||
            (s->sh.slice_type == B_SLICE && s->pps->weighted_bipred_flag)) {
            s->hevcdsp.weighted_pred_avg(s->sh.luma_log2_weight_denom,
                                         s->sh.luma_weight_l0[current_mv.ref_idx[0]],
                                         s->sh.luma_weight_l1[current_mv.ref_idx[1]],
                                         s->sh.luma_offset_l0[current_mv.ref_idx[0]],
                                         s->sh.luma_offset_l1[current_mv.ref_idx[1]],
                                         dst0, s->frame->linesize[0],
                                         tmp, tmp2, tmpstride, nPbW, nPbH);
        } else {
            s->hevcdsp.put_weighted_pred_avg(dst0, s->frame->linesize[0],
                                             tmp, tmp2, tmpstride, nPbW, nPbH);
        }

        chroma_mc(s, tmp, tmp2, tmpstride, ref0->frame,
                  &current_mv.mv[0], x0 / 2, y0 / 2, nPbW / 2, nPbH / 2);
        chroma_mc(s, tmp3, tmp4, tmpstride, ref1->frame,
                  &current_mv.mv[1], x0 / 2, y0 / 2, nPbW / 2, nPbH / 2);

        if ((s->sh.slice_type == P_SLICE && s->pps->weighted_pred_flag) ||
            (s->sh.slice_type == B_SLICE && s->pps->weighted_bipred_flag)) {
            s->hevcdsp.weighted_pred_avg(s->sh.chroma_log2_weight_denom,
                                         s->sh.chroma_weight_l0[current_mv.ref_idx[0]][0],
                                         s->sh.chroma_weight_l1[current_mv.ref_idx[1]][0],
                                         s->sh.chroma_offset_l0[current_mv.ref_idx[0]][0],
                                         s->sh.chroma_offset_l1[current_mv.ref_idx[1]][0],
                                         dst1, s->frame->linesize[1], tmp, tmp3,
                                         tmpstride, nPbW / 2, nPbH / 2);
            s->hevcdsp.weighted_pred_avg(s->sh.chroma_log2_weight_denom,
                                         s->sh.chroma_weight_l0[current_mv.ref_idx[0]][1],
                                         s->sh.chroma_weight_l1[current_mv.ref_idx[1]][1],
                                         s->sh.chroma_offset_l0[current_mv.ref_idx[0]][1],
                                         s->sh.chroma_offset_l1[current_mv.ref_idx[1]][1],
                                         dst2, s->frame->linesize[2], tmp2, tmp4,
                                         tmpstride, nPbW / 2, nPbH / 2);
        } else {
            s->hevcdsp.put_weighted_pred_avg(dst1, s->frame->linesize[1], tmp, tmp3, tmpstride, nPbW/2, nPbH/2);
            s->hevcdsp.put_weighted_pred_avg(dst2, s->frame->linesize[2], tmp2, tmp4, tmpstride, nPbW/2, nPbH/2);
        }
    }
}