Explicit typecasting to fix some Nvidia CL issues.

#pragma OPENCL EXTENSION cl_khr_byte_addressable_store : enable
#pragma OPENCL EXTENSION cl_amd_printf : enable

typedef unsigned char uc;
typedef signed char sc;

__inline signed char vp8_filter_mask(sc, sc, uchar8);
__inline signed char vp8_simple_filter_mask(signed char, signed char, uc, uc, uc, uc);
__inline signed char vp8_hevmask(signed char, uchar4);

__inline uchar8 vp8_mbfilter(signed char mask,signed char hev,uchar8);

void vp8_simple_filter(signed char mask,global uc *base, int op1_off,int op0_off,int oq0_off,int oq1_off);

constant int threads[3] = {16, 8, 8};

#ifndef __CL_VERSION_1_0__
#define __CL_VERSION_1_0__ 100
#endif

#if __OPENCL_VERSION__ == __CL_VERSION_1_0__
#define clamp(x,y,z) vp8_char_clamp(x)
char vp8_char_clamp(int in){
    return max(min(in, 127), -128);
}
#endif

typedef struct
{
    signed char lim[16];
    signed char flim[16];
    signed char thr[16];
    signed char mbflim[16];
} loop_filter_info;

typedef struct
{
    int mb_rows;
    int mb_cols;
} frame_info;

//OpenCL built-in functions (
size_t get_global_id(unsigned int);
size_t get_global_size(unsigned int);

uchar4 vp8_filter(
    signed char mask,
    signed char hev,
    uchar4 base
)
{
    char4 pq = convert_char4(base) ^ (char4)0x80;

    char vp8_filter;
    char2 Filter;
    char4 u;

    /* add outer taps if we have high edge variance */
    vp8_filter = sub_sat(pq.s0, pq.s3);
    vp8_filter &= hev;

    /* inner taps */
    vp8_filter = clamp(vp8_filter + 3 * (pq.s2 - pq.s1), -128, 127);
    vp8_filter &= mask;

    /* save bottom 3 bits so that we round one side +4 and the other +3
     * if it equals 4 we'll set to adjust by -1 to account for the fact
     * we'd round 3 the other way
     */
    Filter = add_sat((char2)vp8_filter, (char2){4,3});
    Filter.s0 >>= 3;
    Filter.s1 >>= 3;

    /* outer tap adjustments */
    vp8_filter = Filter.s0 + 1;
    vp8_filter >>= 1;
    vp8_filter &= ~hev;

    u.s0 = add_sat(pq.s0, vp8_filter);
    u.s1 = add_sat(pq.s1, Filter.s1);
    u.s2 = sub_sat(pq.s2, Filter.s0);
    u.s3 = sub_sat(pq.s3, vp8_filter);

    return convert_uchar4(u ^ (char4)0x80);

}

// Filters horizontal edges of inner blocks in a Macroblock
void vp8_loop_filter_horizontal_edge_worker(
    global unsigned char *s_base,
    global int *offsets,
    global int *pitches, /* pitch */
    global loop_filter_info *lfi,
    global int *filters,
    int filter_type,
    int filter_level,
    int cur_iter,
    size_t num_blocks,
    size_t thread,
    size_t plane,
    size_t block
){
    size_t num_planes = 3;

    if ( cur_iter == 0 || plane == 0){
        if (cur_iter > 0){
            num_planes = 1;
        }

        int p = pitches[plane];
        int block_offset = num_blocks*11 + cur_iter*num_blocks*num_planes + block*num_planes+plane;
        int s_off = offsets[block_offset];

        int  hev = 0; /* high edge variance */
        signed char mask = 0;

        global signed char *limit, *flimit, *thresh;
        global loop_filter_info *lf_info;

        lf_info = &lfi[filter_level];
        flimit = lf_info->flim;
        limit = lf_info->lim;
        thresh = lf_info->thr;

        s_off += thread;

        uchar8 data;
        data.s0 = s_base[s_off-4*p];
        data.s1 = s_base[s_off-3*p];
        data.s2 = s_base[s_off-2*p];
        data.s3 = s_base[s_off-p];
        data.s4 = s_base[s_off];
        data.s5 = s_base[s_off+p];
        data.s6 = s_base[s_off+2*p];
        data.s7 = s_base[s_off+3*p];

        mask = vp8_filter_mask(limit[thread], flimit[thread], data);

        hev = vp8_hevmask(thresh[thread], data.s2345);

        data.s2345 = vp8_filter(mask, hev, data.s2345);

        s_base[s_off - 2*p] = data.s2;
        s_base[s_off - p  ] = data.s3;
        s_base[s_off      ] = data.s4;
        s_base[s_off + p  ] = data.s5;
    }
}

void vp8_loop_filter_vertical_edge_worker(
    global unsigned char *s_base,
    global int *offsets,
    global int *pitches,
    global loop_filter_info *lfi,
    global int *filters,
    int filter_type,
    int filter_level,
    int cur_iter,
    size_t num_blocks,
    size_t num_planes,
    size_t thread,
    size_t plane,
    size_t block
){
    if ((cur_iter == 1 || plane == 0)){

        int p = pitches[plane];
        int block_offset = cur_iter*num_blocks*num_planes + block*num_planes+plane;
        int s_off = offsets[block_offset];

        int  hev = 0; /* high edge variance */
        signed char mask = 0;

        s_off += p * thread;

        uchar8 data = vload8(0, &s_base[s_off-4]);

        mask = vp8_filter_mask(lfi->lim[thread], lfi->flim[thread], data);

        hev = vp8_hevmask(lfi->thr[thread], data.s2345);

        data.s2345 = vp8_filter(mask, hev, data.s2345);

        vstore4(data.s2345, 0, &s_base[s_off-2]);
    }
}

void vp8_mbloop_filter_horizontal_edge_worker(
    global unsigned char *s_base,
    global int *offsets,
    global int *pitches,
    global loop_filter_info *lfi,
    global int *filters,
    int filter_type,
    int filter_level,
    size_t num_blocks,
    size_t num_planes,
    size_t thread,
    size_t plane,
    size_t block
){

    int p = pitches[plane];
    int s_off = offsets[8*num_blocks + block*num_planes+plane] + thread;

    uchar8 data;
    data.s0 = s_base[s_off-4*p];
    data.s1 = s_base[s_off-3*p];
    data.s2 = s_base[s_off-2*p];
    data.s3 = s_base[s_off-p];
    data.s4 = s_base[s_off];
    data.s5 = s_base[s_off+p];
    data.s6 = s_base[s_off+2*p];
    data.s7 = s_base[s_off+3*p];

    char mask = vp8_filter_mask(lfi->lim[thread], lfi->mbflim[thread], data);

    char hev = vp8_hevmask(lfi->thr[thread], data.s2345);

    data = vp8_mbfilter(mask, hev, data);

    s_base[s_off - 3*p] = data.s1;
    s_base[s_off - 2*p] = data.s2;
    s_base[s_off - 1*p] = data.s3;
    s_base[s_off      ] = data.s4;
    s_base[s_off + p  ] = data.s5;
    s_base[s_off + 2*p] = data.s6;

}

void vp8_mbloop_filter_vertical_edge_worker(
    global unsigned char *s_base,
    global int *offsets,
    global int *pitches,
    global loop_filter_info *lfi,
    global int *filters,
    int filter_type,
    int filter_level,
    size_t num_blocks,
    size_t num_planes,
    size_t thread,
    size_t plane,
    size_t block
){

    int p = pitches[plane];
    int block_offset = block*num_planes+plane;
    int s_off = offsets[block_offset];

    signed char hev = 0; /* high edge variance */
    signed char mask = 0;

    s_off += p * thread;

    uchar8 data = vload8(0, &s_base[s_off-4]);

    mask = vp8_filter_mask(lfi->lim[thread], lfi->mbflim[thread], data);

    hev = vp8_hevmask(lfi->thr[thread], data.s2345);

    data = vp8_mbfilter(mask, hev, data);

    vstore8(data, 0, &s_base[s_off-4]);
}

kernel void vp8_loop_filter_all_edges_kernel(
    global unsigned char *s_base,
    global int *offsets_in,
    global int *pitches,
    global loop_filter_info *lfi,
    global int *filters_in,
    int priority_level,
    int num_levels,
    global int *block_offsets,
    global int *priority_num_blocks
){
    size_t thread = get_global_id(0);
    size_t plane = get_global_id(1);
    size_t block = get_global_id(2);

    for (int i = 0; i < num_levels; i++){
        int block_offset = block_offsets[priority_level];

        global int *offsets = &offsets_in[16*block_offset];
        global int *filters = &filters_in[4*block_offset];
        size_t num_blocks = priority_num_blocks[priority_level];
        int filter_level = filters[block];
        global loop_filter_info *lf_info = &lfi[filter_level];
        int thread_level_filter = (thread<threads[plane]) & (filter_level!=0);
        int dc_diffs_filter = thread_level_filter & (filters[num_blocks*DC_DIFFS_LOCATION + block] > 0);

        if (block < num_blocks){
            //Prefetch vertical edge source pixels into global cache (horizontal isn't worth it)
            for(int pln = 0; pln < 3; pln++){
                int p = pitches[pln];
                int offset = block*3+pln;
                int s_off = offsets[offset];
                for (int thread = 0; thread < 16; thread++){
                    prefetch(&s_base[s_off+p*thread-4], 8);
                }
            }

            if (thread_level_filter & (filters[num_blocks*COLS_LOCATION + block] > 0)){
                vp8_mbloop_filter_vertical_edge_worker(s_base, offsets, pitches, lf_info, filters,
                        COLS_LOCATION, filter_level, num_blocks, 3, thread, plane, block);
            }

            //YUV planes, then 2 more passes of Y plane
            if (dc_diffs_filter){
                vp8_loop_filter_vertical_edge_worker(s_base, offsets, pitches, lf_info, filters,
                        DC_DIFFS_LOCATION, filter_level, 1, num_blocks, 3, thread, plane, block);
                vp8_loop_filter_vertical_edge_worker(s_base, offsets, pitches, lf_info, filters,
                        DC_DIFFS_LOCATION, filter_level, 6, num_blocks, 1, thread, plane, block);
                vp8_loop_filter_vertical_edge_worker(s_base, offsets, pitches, lf_info, filters,
                        DC_DIFFS_LOCATION, filter_level, 7, num_blocks, 1, thread, plane, block);
            }

        }

        barrier(CLK_GLOBAL_MEM_FENCE);

        if (block < num_blocks){

            if (thread_level_filter & (filters[num_blocks*ROWS_LOCATION + block] > 0)){
                vp8_mbloop_filter_horizontal_edge_worker(s_base, offsets, pitches, lf_info,
                    filters, ROWS_LOCATION, filter_level, num_blocks, 3, thread, plane, block);
            }

            //YUV planes, then 2 more passes of Y plane
            if (dc_diffs_filter){
                vp8_loop_filter_horizontal_edge_worker(s_base, offsets, pitches, lfi, filters,
                        DC_DIFFS_LOCATION, filter_level, 0, num_blocks, thread, plane, block);
                vp8_loop_filter_horizontal_edge_worker(s_base, offsets, pitches, lfi, filters,
                        DC_DIFFS_LOCATION, filter_level, 3, num_blocks, thread, plane, block);
                vp8_loop_filter_horizontal_edge_worker(s_base, offsets, pitches, lfi, filters,
                        DC_DIFFS_LOCATION, filter_level, 4, num_blocks, thread, plane, block);
            }
        }

        barrier(CLK_GLOBAL_MEM_FENCE);
        priority_level++;
    }

}

kernel void vp8_loop_filter_horizontal_edges_kernel(
    global unsigned char *s_base,
    global int *offsets,
    global int *pitches, /* pitch */
    global loop_filter_info *lfi,
    global int *filters,
    int priority_level,
    int num_levels,
    global int *block_offsets,
    global int *priority_num_blocks
){
    size_t thread = get_global_id(0);
    size_t plane = get_global_id(1);
    size_t block = get_global_id(2);
    size_t num_blocks = get_global_size(2);

    int block_offset = block_offsets[priority_level];

    filters = &filters[4*block_offset];
    offsets = &offsets[16*block_offset];
    int filter_level = filters[block];
    global loop_filter_info *lf_info = &lfi[filter_level];

    int thread_level_filter = (thread<threads[plane]) & (filter_level!=0);
    int do_filter = filters[num_blocks*ROWS_LOCATION + block] > 0;
    do_filter &= thread_level_filter;
    if (do_filter){
        vp8_mbloop_filter_horizontal_edge_worker(s_base, offsets, pitches, lf_info,
            filters, ROWS_LOCATION, filter_level, num_blocks, 3, thread, plane, block);
    }

    //YUV planes, then 2 more passes of Y plane
    if (thread_level_filter & (filters[num_blocks*DC_DIFFS_LOCATION + block] > 0)){
        vp8_loop_filter_horizontal_edge_worker(s_base, offsets, pitches, lfi, filters,
                DC_DIFFS_LOCATION, filter_level, 0, num_blocks, thread, plane, block);
        vp8_loop_filter_horizontal_edge_worker(s_base, offsets, pitches, lfi, filters,
                DC_DIFFS_LOCATION, filter_level, 3, num_blocks, thread, plane, block);
        vp8_loop_filter_horizontal_edge_worker(s_base, offsets, pitches, lfi, filters,
                DC_DIFFS_LOCATION, filter_level, 4, num_blocks, thread, plane, block);
    }
}

kernel void vp8_loop_filter_vertical_edges_kernel(
    global unsigned char *s_base,
    global int *offsets,
    global int *pitches,
    global loop_filter_info *lfi,
    global int *filters,
    int priority_level,
    int num_levels,
    global int *block_offsets,
    global int *priority_num_blocks
){
    size_t thread = get_global_id(0);
    size_t plane = get_global_id(1);
    size_t block = get_global_id(2);
    size_t num_blocks = get_global_size(2);

    int block_offset = block_offsets[priority_level];

    filters = &filters[4*block_offset];
    offsets = &offsets[16*block_offset];
    int filter_level = filters[block];
    global loop_filter_info *lf_info = &lfi[filter_level];

    int thread_level_filter = (thread<threads[plane]) & (filter_level!=0);
    int do_filter = filters[num_blocks*COLS_LOCATION + block] > 0;
    do_filter &= thread_level_filter;
    if (do_filter){
        vp8_mbloop_filter_vertical_edge_worker(s_base, offsets, pitches, lf_info, filters,
            COLS_LOCATION, filter_level, num_blocks, 3, thread, plane, block);
    }

    //YUV planes, then 2 more passes of Y plane
    if (thread_level_filter & (filters[num_blocks*DC_DIFFS_LOCATION + block] > 0)){
        vp8_loop_filter_vertical_edge_worker(s_base, offsets, pitches, lf_info, filters,
                DC_DIFFS_LOCATION, filter_level, 1, num_blocks, 3, thread, plane, block);
        vp8_loop_filter_vertical_edge_worker(s_base, offsets, pitches, lf_info, filters,
                DC_DIFFS_LOCATION, filter_level, 6, num_blocks, 1, thread, plane, block);
        vp8_loop_filter_vertical_edge_worker(s_base, offsets, pitches, lf_info, filters,
                DC_DIFFS_LOCATION, filter_level, 7, num_blocks, 1, thread, plane, block);
    }

}

void vp8_loop_filter_simple_horizontal_edge_worker
(
    global unsigned char *s_base,
    global int *offsets,
    global int *pitches,
    global loop_filter_info *lfi,
    global int *filters_in,
    int use_mbflim,
    int filter_type,
    int cur_iter,
    int priority_level,
    global int *block_offsets,
    global int *priority_num_blocks
){
    int block_offset = block_offsets[priority_level];
    int filter_offset = 4*block_offset;
    int priority_offset = 8*block_offset;
    private size_t plane = get_global_id(1);
    private size_t block = get_global_id(2);
    size_t num_planes = get_global_size(1);
    size_t num_blocks = priority_num_blocks[priority_level];

    global int *filters = &filters_in[filter_offset];

    if (filters[num_blocks*filter_type + block] > 0){
        int filter_level = filters[block];
        if (filter_level){
            int p = pitches[plane];
            int block_offset = cur_iter*num_blocks*num_planes + block*num_planes+plane;
            int s_off = offsets[block_offset+priority_offset];

            signed char mask = 0;
            size_t i= get_global_id(0);

            global signed char *limit, *flimit;
            global loop_filter_info *lf_info;

            if (i < threads[plane]){
                lf_info = &lfi[filter_level];
                if (use_mbflim == 0){
                    flimit = lf_info->flim;
                } else {
                    flimit = lf_info->mbflim;
                }

                limit = lf_info->lim;

                s_off += i;
                mask = vp8_simple_filter_mask(limit[i], flimit[i], s_base[s_off-2*p], s_base[s_off-p], s_base[s_off], s_base[s_off+p]);
                vp8_simple_filter(mask, s_base, s_off - 2 * p, s_off - 1 * p, s_off, s_off + 1 * p);
            }
        }
    }
}

void vp8_loop_filter_simple_vertical_edge_worker(
    global unsigned char *s_base,
    global int *offsets, /* Y or YUV offsets for EACH block being processed*/
    global int *pitches, /* 1 or 3 values for Y or YUV pitches*/
    global loop_filter_info *lfi, /* Single struct for the frame */
    global int *filters_in, /* Filters for each block being processed */
    int use_mbflim, /* Use lfi->flim or lfi->mbflim, need once per kernel call */
    int filter_type, /* Should dc_diffs, rows, or cols be used?*/
    int cur_iter,
    int priority_level,
    global int *block_offsets,
    global int *priority_num_blocks
){
    int block_offset = block_offsets[priority_level];
    int filter_offset = 4*block_offset;
    int priority_offset = 8*block_offset;
    private size_t block = get_global_id(2);
    size_t num_blocks = priority_num_blocks[priority_level];

    global int *filters = &filters_in[filter_offset];

    if (filters[filter_type * num_blocks + block] > 0){
        int filter_level = filters[block];
        if (filter_level){
            int p = pitches[0];
            int block_offset = cur_iter*num_blocks + block;
            int s_off = offsets[block_offset+priority_offset];

            signed char mask = 0;
            size_t i= get_global_id(0);

            global signed char *limit, *flimit;
            global loop_filter_info *lf_info;

            if (i < threads[0]){
                lf_info = &lfi[filter_level];
                if (use_mbflim == 0){
                    flimit = lf_info->flim;
                } else {
                    flimit = lf_info->mbflim;
                }

                limit = lf_info->lim;

                s_off += p * i;
                mask = vp8_simple_filter_mask(limit[i], flimit[i], s_base[s_off-2], s_base[s_off-1], s_base[s_off], s_base[s_off+1]);
                vp8_simple_filter(mask, s_base, s_off - 2, s_off - 1, s_off, s_off + 1);
            }
        }
    }
}

kernel void vp8_loop_filter_simple_vertical_edges_kernel
(
    global unsigned char *s_base,
    global int *offsets, /* Y or YUV offsets for EACH block being processed*/
    global int *pitches, /* 1 or 3 values for Y or YUV pitches*/
    global loop_filter_info *lfi, /* Single struct for the frame */
    global int *filters_in, /* Filters for each block being processed */
    int filter_type, /* Should dc_diffs, rows, or cols be used?*/
    int priority_level,
    int num_levels,
    global int *block_offsets,
    global int *priority_num_blocks
){
    vp8_loop_filter_simple_vertical_edge_worker(s_base, offsets, pitches,
            lfi, filters_in, 1, COLS_LOCATION, 0, priority_level,
            block_offsets, priority_num_blocks
    );

    //3 Y plane iterations
    vp8_loop_filter_simple_vertical_edge_worker(s_base, offsets, pitches,
            lfi, filters_in, 0, DC_DIFFS_LOCATION, 1, priority_level,
            block_offsets, priority_num_blocks
    );
    vp8_loop_filter_simple_vertical_edge_worker(s_base, offsets, pitches,
            lfi, filters_in, 0, DC_DIFFS_LOCATION, 2, priority_level,
            block_offsets, priority_num_blocks
    );
    vp8_loop_filter_simple_vertical_edge_worker(s_base, offsets, pitches,
            lfi, filters_in, 0, DC_DIFFS_LOCATION, 3, priority_level,
            block_offsets, priority_num_blocks
    );
}

kernel void vp8_loop_filter_simple_horizontal_edges_kernel
(
    global unsigned char *s_base,
    global int *offsets,
    global int *pitches,
    global loop_filter_info *lfi,
    global int *filters_in,
    int priority_level,
    int num_levels,
    global int *block_offsets,
    global int *priority_num_blocks
){
    vp8_loop_filter_simple_horizontal_edge_worker(s_base, offsets, pitches, lfi,
            filters_in, 1, ROWS_LOCATION, 4, priority_level,
            block_offsets, priority_num_blocks
    );
    vp8_loop_filter_simple_horizontal_edge_worker(s_base, offsets, pitches, lfi,
            filters_in, 0, DC_DIFFS_LOCATION, 5, priority_level,
            block_offsets, priority_num_blocks
    );
    vp8_loop_filter_simple_horizontal_edge_worker(s_base, offsets, pitches, lfi,
            filters_in, 0, DC_DIFFS_LOCATION, 6, priority_level,
            block_offsets, priority_num_blocks
    );
    vp8_loop_filter_simple_horizontal_edge_worker(s_base, offsets, pitches, lfi,
            filters_in, 0, DC_DIFFS_LOCATION, 7, priority_level,
            block_offsets, priority_num_blocks
    );
}

kernel void vp8_loop_filter_simple_all_edges_kernel
(
    global unsigned char *s_base,
    global int *offsets,
    global int *pitches,
    global loop_filter_info *lfi,
    global int *filters_in,
    int priority_level,
    int num_levels,
    global int *block_offsets,
    global int *priority_num_blocks
)
{

    for (int i = 0; i < num_levels; i++){
        if (get_global_id(2) < priority_num_blocks[priority_level]){
            vp8_loop_filter_simple_vertical_edge_worker(s_base, offsets, pitches,
                    lfi, filters_in, 1, COLS_LOCATION, 0, priority_level,
                    block_offsets, priority_num_blocks
            );

            //3 Y plane iterations
            vp8_loop_filter_simple_vertical_edge_worker(s_base, offsets, pitches,
                    lfi, filters_in, 0, DC_DIFFS_LOCATION, 1, priority_level,
                    block_offsets, priority_num_blocks
            );
            vp8_loop_filter_simple_vertical_edge_worker(s_base, offsets, pitches,
                    lfi, filters_in, 0, DC_DIFFS_LOCATION, 2, priority_level,
                    block_offsets, priority_num_blocks
            );
            vp8_loop_filter_simple_vertical_edge_worker(s_base, offsets, pitches,
                    lfi, filters_in, 0, DC_DIFFS_LOCATION, 3, priority_level,
                    block_offsets, priority_num_blocks
            );
        }

        barrier(CLK_GLOBAL_MEM_FENCE);

        if (get_global_id(2) < priority_num_blocks[priority_level]){
            vp8_loop_filter_simple_horizontal_edge_worker(s_base, offsets, pitches, lfi,
                    filters_in, 1, ROWS_LOCATION, 4, priority_level,
                    block_offsets, priority_num_blocks
            );
            vp8_loop_filter_simple_horizontal_edge_worker(s_base, offsets, pitches, lfi,
                    filters_in, 0, DC_DIFFS_LOCATION, 5, priority_level,
                    block_offsets, priority_num_blocks
            );
            vp8_loop_filter_simple_horizontal_edge_worker(s_base, offsets, pitches, lfi,
                    filters_in, 0, DC_DIFFS_LOCATION, 6, priority_level,
                    block_offsets, priority_num_blocks
            );
            vp8_loop_filter_simple_horizontal_edge_worker(s_base, offsets, pitches, lfi,
                    filters_in, 0, DC_DIFFS_LOCATION, 7, priority_level,
                    block_offsets, priority_num_blocks
            );
        }

        barrier(CLK_GLOBAL_MEM_FENCE);
        priority_level++;
    }
}

//Inline and non-kernel functions follow.

__inline uchar8 vp8_mbfilter(
    signed char mask,
    signed char hev,
    uchar8 base
)
{
    char4 s;
    char4 u;
    signed char vp8_filter;

    char2 filter;

    char8 pq = convert_char8(base);
    pq ^= (char8){0, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0};

    /* add outer taps if we have high edge variance */
    vp8_filter = sub_sat(pq.s2, pq.s5);
    vp8_filter = clamp(vp8_filter + 3 * (pq.s4 - pq.s3), -128, 127);
    vp8_filter &= mask;

    filter = (char2)vp8_filter;
    filter &= hev;

    /* save bottom 3 bits so that we round one side +4 and the other +3 */
    filter = add_sat(filter, (char2){4,3});
    filter.s0 >>= 3;
    filter.s1 >>= 3;

    pq.s4 = sub_sat(pq.s4, filter.s0);
    pq.s3 = add_sat(pq.s3, filter.s1);

    /* only apply wider filter if not high edge variance */
    vp8_filter &= ~hev;
    filter.s1 = vp8_filter;

    /* roughly 3/7th, 2/7th, and 1/7th difference across boundary */
    u.s0 = clamp((63 + filter.s1 * 27) >> 7, -128, 127);
    u.s1 = clamp((63 + filter.s1 * 18) >> 7, -128, 127);
    u.s2 = clamp((63 + filter.s1 * 9) >> 7, -128, 127);

    s.s012 = sub_sat(pq.s456, u.s012);
    pq.s456 = s.s012 ^ (char3)0x80;

    s.s012 = add_sat(pq.s321, u.s012);
    pq.s321 = s.s012 ^ (char3)0x80;

    return convert_uchar8(pq);
}

/* is there high variance internal edge ( 11111111 yes, 00000000 no) */
__inline signed char vp8_hevmask(signed char thresh, uchar4 pq)
{
    signed char hev;
    hev  = (abs_diff(pq.s0, pq.s1) > thresh) * -1;
    hev  |= (abs_diff(pq.s3, pq.s2) > thresh) * -1;
    return hev;
}

/* should we apply any filter at all ( 11111111 yes, 00000000 no) */
__inline signed char vp8_filter_mask( signed char limit, signed char flimit,
        uchar8 pq)
{
    signed char mask = 0;

    //Only apply the filter if the difference is LESS than 'limit'
    mask |= (abs_diff(pq.s0, pq.s1) > limit);
    mask |= (abs_diff(pq.s1, pq.s2) > limit);
    mask |= (abs_diff(pq.s2, pq.s3) > limit);
    mask |= (abs_diff(pq.s5, pq.s4) > limit);
    mask |= (abs_diff(pq.s6, pq.s5) > limit);
    mask |= (abs_diff(pq.s7, pq.s6) > limit);
    mask |= (abs_diff(pq.s3, pq.s4) * 2 + abs_diff(pq.s2, pq.s5) / 2  > flimit * 2 + limit);
    mask *= -1;
    return ~mask;

}

/* should we apply any filter at all ( 11111111 yes, 00000000 no) */
__inline signed char vp8_simple_filter_mask(
    signed char limit,
    signed char flimit,
    uc p1,
    uc p0,
    uc q0,
    uc q1
)
{
    signed char mask = (abs_diff(p0, q0) * 2 + abs_diff(p1, q1) / 2  <= flimit * 2 + limit) * -1;
    return mask;
}

__inline void vp8_simple_filter(
    signed char mask,
    global uc *base,
    int op1_off,
    int op0_off,
    int oq0_off,
    int oq1_off
)
{

    global uc *op1 = base + op1_off;
    global uc *op0 = base + op0_off;
    global uc *oq0 = base + oq0_off;
    global uc *oq1 = base + oq1_off;

    signed char vp8_filter;
    char2 filter;

    char4 pq = (char4){*op1, *op0, *oq0, *oq1};
    pq ^= (char4)0x80;

    signed char u;

    vp8_filter = sub_sat(pq.s0, pq.s3);
    vp8_filter = clamp(vp8_filter + 3 * (pq.s2 - pq.s1), -128, 127);
    vp8_filter &= mask;

    /* save bottom 3 bits so that we round one side +4 and the other +3 */
    filter = add_sat((char2)vp8_filter, (char2){4,3});
    filter.s0 >>= 3;
    filter.s1 >>= 3;

    u = sub_sat(pq.s2, filter.s0);
    *oq0  = u ^ 0x80;

    u = add_sat(pq.s1, filter.s1);
    *op0 = u ^ 0x80;
}

#pragma OPENCL EXTENSION cl_khr_byte_addressable_store : enable
#pragma OPENCL EXTENSION cl_amd_printf : enable

typedef unsigned char uc;
typedef signed char sc;

__inline signed char vp8_filter_mask(sc, sc, uchar8);
__inline signed char vp8_simple_filter_mask(signed char, signed char, uc, uc, uc, uc);
__inline signed char vp8_hevmask(signed char, uchar4);

__inline uchar8 vp8_mbfilter(signed char mask,signed char hev,uchar8);

void vp8_simple_filter(signed char mask,global uc *base, int op1_off,int op0_off,int oq0_off,int oq1_off);

constant int threads[3] = {16, 8, 8};

#ifndef __CL_VERSION_1_0__
#define __CL_VERSION_1_0__ 100
#endif

#if __OPENCL_VERSION__ == __CL_VERSION_1_0__
#define clamp(x,y,z) vp8_char_clamp(x)
char vp8_char_clamp(int in){
    return max(min(in, 127), -128);
}
#endif

typedef struct
{
    signed char lim[16];
    signed char flim[16];
    signed char thr[16];
    signed char mbflim[16];
} loop_filter_info;

typedef struct
{
    int mb_rows;
    int mb_cols;
} frame_info;

//OpenCL built-in functions (
size_t get_global_id(unsigned int);
size_t get_global_size(unsigned int);

uchar4 vp8_filter(
    signed char mask,
    signed char hev,
    uchar4 base
)
{
    char4 pq = convert_char4(base) ^ 0x80;

    char vp8_filter;
    char2 Filter;
    char4 u;

    /* add outer taps if we have high edge variance */
    vp8_filter = sub_sat(pq.s0, pq.s3);
    vp8_filter &= hev;

    /* inner taps */
    vp8_filter = clamp(vp8_filter + 3 * (pq.s2 - pq.s1), -128, 127);
    vp8_filter &= mask;

    /* save bottom 3 bits so that we round one side +4 and the other +3
     * if it equals 4 we'll set to adjust by -1 to account for the fact
     * we'd round 3 the other way
     */
    Filter = add_sat((char2)vp8_filter, (char2){4,3});
    Filter.s0 >>= 3;
    Filter.s1 >>= 3;

    /* outer tap adjustments */
    vp8_filter = Filter.s0 + 1;
    vp8_filter >>= 1;
    vp8_filter &= ~hev;

    u.s0 = add_sat(pq.s0, vp8_filter);
    u.s1 = add_sat(pq.s1, Filter.s1);
    u.s2 = sub_sat(pq.s2, Filter.s0);
    u.s3 = sub_sat(pq.s3, vp8_filter);

    return convert_uchar4(u ^ 0x80);

}

// Filters horizontal edges of inner blocks in a Macroblock
void vp8_loop_filter_horizontal_edge_worker(
    global unsigned char *s_base,
    global int *offsets,
    global int *pitches, /* pitch */
    global loop_filter_info *lfi,
    global int *filters,
    int filter_type,
    int filter_level,
    int cur_iter,
    size_t num_blocks,
    size_t thread,
    size_t plane,
    size_t block
){
    size_t num_planes = 3;

    if ( cur_iter == 0 || plane == 0){
        if (cur_iter > 0){
            num_planes = 1;
        }

        int p = pitches[plane];
        int block_offset = num_blocks*11 + cur_iter*num_blocks*num_planes + block*num_planes+plane;
        int s_off = offsets[block_offset];

        int  hev = 0; /* high edge variance */
        signed char mask = 0;

        global signed char *limit, *flimit, *thresh;
        global loop_filter_info *lf_info;

        lf_info = &lfi[filter_level];
        flimit = lf_info->flim;
        limit = lf_info->lim;
        thresh = lf_info->thr;

        s_off += thread;

        uchar8 data;
        data.s0 = s_base[s_off-4*p];
        data.s1 = s_base[s_off-3*p];
        data.s2 = s_base[s_off-2*p];
        data.s3 = s_base[s_off-p];
        data.s4 = s_base[s_off];
        data.s5 = s_base[s_off+p];
        data.s6 = s_base[s_off+2*p];
        data.s7 = s_base[s_off+3*p];

        mask = vp8_filter_mask(limit[thread], flimit[thread], data);

        hev = vp8_hevmask(thresh[thread], data.s2345);

        data.s2345 = vp8_filter(mask, hev, data.s2345);

        s_base[s_off - 2*p] = data.s2;
        s_base[s_off - p  ] = data.s3;
        s_base[s_off      ] = data.s4;
        s_base[s_off + p  ] = data.s5;
    }
}

void vp8_loop_filter_vertical_edge_worker(
    global unsigned char *s_base,
    global int *offsets,
    global int *pitches,
    global loop_filter_info *lfi,
    global int *filters,
    int filter_type,
    int filter_level,
    int cur_iter,
    size_t num_blocks,
    size_t num_planes,
    size_t thread,
    size_t plane,
    size_t block
){
    if ((cur_iter == 1 || plane == 0)){

        int p = pitches[plane];
        int block_offset = cur_iter*num_blocks*num_planes + block*num_planes+plane;
        int s_off = offsets[block_offset];

        int  hev = 0; /* high edge variance */
        signed char mask = 0;

        s_off += p * thread;

        uchar8 data = vload8(0, &s_base[s_off-4]);

        mask = vp8_filter_mask(lfi->lim[thread], lfi->flim[thread], data);

        hev = vp8_hevmask(lfi->thr[thread], data.s2345);

        data.s2345 = vp8_filter(mask, hev, data.s2345);

        vstore4(data.s2345, 0, &s_base[s_off-2]);
    }
}

void vp8_mbloop_filter_horizontal_edge_worker(
    global unsigned char *s_base,
    global int *offsets,
    global int *pitches,
    global loop_filter_info *lfi,
    global int *filters,
    int filter_type,
    int filter_level,
    size_t num_blocks,
    size_t num_planes,
    size_t thread,
    size_t plane,
    size_t block
){

    int p = pitches[plane];
    int s_off = offsets[8*num_blocks + block*num_planes+plane] + thread;

    uchar8 data;
    data.s0 = s_base[s_off-4*p];
    data.s1 = s_base[s_off-3*p];
    data.s2 = s_base[s_off-2*p];
    data.s3 = s_base[s_off-p];
    data.s4 = s_base[s_off];
    data.s5 = s_base[s_off+p];
    data.s6 = s_base[s_off+2*p];
    data.s7 = s_base[s_off+3*p];

    char mask = vp8_filter_mask(lfi->lim[thread], lfi->mbflim[thread], data);

    char hev = vp8_hevmask(lfi->thr[thread], data.s2345);

    data = vp8_mbfilter(mask, hev, data);

    s_base[s_off - 3*p] = data.s1;
    s_base[s_off - 2*p] = data.s2;
    s_base[s_off - 1*p] = data.s3;
    s_base[s_off      ] = data.s4;
    s_base[s_off + p  ] = data.s5;
    s_base[s_off + 2*p] = data.s6;

}

void vp8_mbloop_filter_vertical_edge_worker(
    global unsigned char *s_base,
    global int *offsets,
    global int *pitches,
    global loop_filter_info *lfi,
    global int *filters,
    int filter_type,
    int filter_level,
    size_t num_blocks,
    size_t num_planes,
    size_t thread,
    size_t plane,
    size_t block
){

    int p = pitches[plane];
    int block_offset = block*num_planes+plane;
    int s_off = offsets[block_offset];

    signed char hev = 0; /* high edge variance */
    signed char mask = 0;

    s_off += p * thread;

    uchar8 data = vload8(0, &s_base[s_off-4]);

    mask = vp8_filter_mask(lfi->lim[thread], lfi->mbflim[thread], data);

    hev = vp8_hevmask(lfi->thr[thread], data.s2345);

    data = vp8_mbfilter(mask, hev, data);

    vstore8(data, 0, &s_base[s_off-4]);
}

kernel void vp8_loop_filter_all_edges_kernel(
    global unsigned char *s_base,
    global int *offsets_in,
    global int *pitches,
    global loop_filter_info *lfi,
    global int *filters_in,
    int priority_level,
    int num_levels,
    global int *block_offsets,
    global int *priority_num_blocks
){
    size_t thread = get_global_id(0);
    size_t plane = get_global_id(1);
    size_t block = get_global_id(2);

    for (int i = 0; i < num_levels; i++){
        int block_offset = block_offsets[priority_level];

        global int *offsets = &offsets_in[16*block_offset];
        global int *filters = &filters_in[4*block_offset];
        size_t num_blocks = priority_num_blocks[priority_level];
        int filter_level = filters[block];
        global loop_filter_info *lf_info = &lfi[filter_level];
        int thread_level_filter = (thread<threads[plane]) & (filter_level!=0);
        int dc_diffs_filter = thread_level_filter & (filters[num_blocks*DC_DIFFS_LOCATION + block] > 0);

        if (block < num_blocks){
            //Prefetch vertical edge source pixels into global cache (horizontal isn't worth it)
            for(int pln = 0; pln < 3; pln++){
                int p = pitches[pln];
                int offset = block*3+pln;
                int s_off = offsets[offset];
                for (int thread = 0; thread < 16; thread++){
                    prefetch(&s_base[s_off+p*thread-4], 8);
                }
            }

            if (thread_level_filter & (filters[num_blocks*COLS_LOCATION + block] > 0)){
                vp8_mbloop_filter_vertical_edge_worker(s_base, offsets, pitches, lf_info, filters,
                        COLS_LOCATION, filter_level, num_blocks, 3, thread, plane, block);
            }

            //YUV planes, then 2 more passes of Y plane
            if (dc_diffs_filter){
                vp8_loop_filter_vertical_edge_worker(s_base, offsets, pitches, lf_info, filters,
                        DC_DIFFS_LOCATION, filter_level, 1, num_blocks, 3, thread, plane, block);
                vp8_loop_filter_vertical_edge_worker(s_base, offsets, pitches, lf_info, filters,
                        DC_DIFFS_LOCATION, filter_level, 6, num_blocks, 1, thread, plane, block);
                vp8_loop_filter_vertical_edge_worker(s_base, offsets, pitches, lf_info, filters,
                        DC_DIFFS_LOCATION, filter_level, 7, num_blocks, 1, thread, plane, block);
            }

        }

        barrier(CLK_GLOBAL_MEM_FENCE);

        if (block < num_blocks){

            if (thread_level_filter & (filters[num_blocks*ROWS_LOCATION + block] > 0)){
                vp8_mbloop_filter_horizontal_edge_worker(s_base, offsets, pitches, lf_info,
                    filters, ROWS_LOCATION, filter_level, num_blocks, 3, thread, plane, block);
            }

            //YUV planes, then 2 more passes of Y plane
            if (dc_diffs_filter){
                vp8_loop_filter_horizontal_edge_worker(s_base, offsets, pitches, lfi, filters,
                        DC_DIFFS_LOCATION, filter_level, 0, num_blocks, thread, plane, block);
                vp8_loop_filter_horizontal_edge_worker(s_base, offsets, pitches, lfi, filters,
                        DC_DIFFS_LOCATION, filter_level, 3, num_blocks, thread, plane, block);
                vp8_loop_filter_horizontal_edge_worker(s_base, offsets, pitches, lfi, filters,
                        DC_DIFFS_LOCATION, filter_level, 4, num_blocks, thread, plane, block);
            }
        }

        barrier(CLK_GLOBAL_MEM_FENCE);
        priority_level++;
    }

}

kernel void vp8_loop_filter_horizontal_edges_kernel(
    global unsigned char *s_base,
    global int *offsets,
    global int *pitches, /* pitch */
    global loop_filter_info *lfi,
    global int *filters,
    int priority_level,
    int num_levels,
    global int *block_offsets,
    global int *priority_num_blocks
){
    size_t thread = get_global_id(0);
    size_t plane = get_global_id(1);
    size_t block = get_global_id(2);
    size_t num_blocks = get_global_size(2);

    int block_offset = block_offsets[priority_level];

    filters = &filters[4*block_offset];
    offsets = &offsets[16*block_offset];
    int filter_level = filters[block];
    global loop_filter_info *lf_info = &lfi[filter_level];

    int thread_level_filter = (thread<threads[plane]) & (filter_level!=0);
    int do_filter = filters[num_blocks*ROWS_LOCATION + block] > 0;
    do_filter &= thread_level_filter;
    if (do_filter){
        vp8_mbloop_filter_horizontal_edge_worker(s_base, offsets, pitches, lf_info,
            filters, ROWS_LOCATION, filter_level, num_blocks, 3, thread, plane, block);
    }

    //YUV planes, then 2 more passes of Y plane
    if (thread_level_filter & (filters[num_blocks*DC_DIFFS_LOCATION + block] > 0)){
        vp8_loop_filter_horizontal_edge_worker(s_base, offsets, pitches, lfi, filters,
                DC_DIFFS_LOCATION, filter_level, 0, num_blocks, thread, plane, block);
        vp8_loop_filter_horizontal_edge_worker(s_base, offsets, pitches, lfi, filters,
                DC_DIFFS_LOCATION, filter_level, 3, num_blocks, thread, plane, block);
        vp8_loop_filter_horizontal_edge_worker(s_base, offsets, pitches, lfi, filters,
                DC_DIFFS_LOCATION, filter_level, 4, num_blocks, thread, plane, block);
    }
}

kernel void vp8_loop_filter_vertical_edges_kernel(
    global unsigned char *s_base,
    global int *offsets,
    global int *pitches,
    global loop_filter_info *lfi,
    global int *filters,
    int priority_level,
    int num_levels,
    global int *block_offsets,
    global int *priority_num_blocks
){
    size_t thread = get_global_id(0);
    size_t plane = get_global_id(1);
    size_t block = get_global_id(2);
    size_t num_blocks = get_global_size(2);

    int block_offset = block_offsets[priority_level];

    filters = &filters[4*block_offset];
    offsets = &offsets[16*block_offset];
    int filter_level = filters[block];
    global loop_filter_info *lf_info = &lfi[filter_level];

    int thread_level_filter = (thread<threads[plane]) & (filter_level!=0);
    int do_filter = filters[num_blocks*COLS_LOCATION + block] > 0;
    do_filter &= thread_level_filter;
    if (do_filter){
        vp8_mbloop_filter_vertical_edge_worker(s_base, offsets, pitches, lf_info, filters,
            COLS_LOCATION, filter_level, num_blocks, 3, thread, plane, block);
    }

    //YUV planes, then 2 more passes of Y plane
    if (thread_level_filter & (filters[num_blocks*DC_DIFFS_LOCATION + block] > 0)){
        vp8_loop_filter_vertical_edge_worker(s_base, offsets, pitches, lf_info, filters,
                DC_DIFFS_LOCATION, filter_level, 1, num_blocks, 3, thread, plane, block);
        vp8_loop_filter_vertical_edge_worker(s_base, offsets, pitches, lf_info, filters,
                DC_DIFFS_LOCATION, filter_level, 6, num_blocks, 1, thread, plane, block);
        vp8_loop_filter_vertical_edge_worker(s_base, offsets, pitches, lf_info, filters,
                DC_DIFFS_LOCATION, filter_level, 7, num_blocks, 1, thread, plane, block);
    }

}

void vp8_loop_filter_simple_horizontal_edge_worker
(
    global unsigned char *s_base,
    global int *offsets,
    global int *pitches,
    global loop_filter_info *lfi,
    global int *filters_in,
    int use_mbflim,
    int filter_type,
    int cur_iter,
    int priority_level,
    global int *block_offsets,
    global int *priority_num_blocks
){
    int block_offset = block_offsets[priority_level];
    int filter_offset = 4*block_offset;
    int priority_offset = 8*block_offset;
    private size_t plane = get_global_id(1);
    private size_t block = get_global_id(2);
    size_t num_planes = get_global_size(1);
    size_t num_blocks = priority_num_blocks[priority_level];

    global int *filters = &filters_in[filter_offset];

    if (filters[num_blocks*filter_type + block] > 0){
        int filter_level = filters[block];
        if (filter_level){
            int p = pitches[plane];
            int block_offset = cur_iter*num_blocks*num_planes + block*num_planes+plane;
            int s_off = offsets[block_offset+priority_offset];

            signed char mask = 0;
            size_t i= get_global_id(0);

            global signed char *limit, *flimit;
            global loop_filter_info *lf_info;

            if (i < threads[plane]){
                lf_info = &lfi[filter_level];
                if (use_mbflim == 0){
                    flimit = lf_info->flim;
                } else {
                    flimit = lf_info->mbflim;
                }

                limit = lf_info->lim;

                s_off += i;
                mask = vp8_simple_filter_mask(limit[i], flimit[i], s_base[s_off-2*p], s_base[s_off-p], s_base[s_off], s_base[s_off+p]);
                vp8_simple_filter(mask, s_base, s_off - 2 * p, s_off - 1 * p, s_off, s_off + 1 * p);
            }
        }
    }
}

void vp8_loop_filter_simple_vertical_edge_worker(
    global unsigned char *s_base,
    global int *offsets, /* Y or YUV offsets for EACH block being processed*/
    global int *pitches, /* 1 or 3 values for Y or YUV pitches*/
    global loop_filter_info *lfi, /* Single struct for the frame */
    global int *filters_in, /* Filters for each block being processed */
    int use_mbflim, /* Use lfi->flim or lfi->mbflim, need once per kernel call */
    int filter_type, /* Should dc_diffs, rows, or cols be used?*/
    int cur_iter,
    int priority_level,
    global int *block_offsets,
    global int *priority_num_blocks
){
    int block_offset = block_offsets[priority_level];
    int filter_offset = 4*block_offset;
    int priority_offset = 8*block_offset;
    private size_t block = get_global_id(2);
    size_t num_blocks = priority_num_blocks[priority_level];

    global int *filters = &filters_in[filter_offset];

    if (filters[filter_type * num_blocks + block] > 0){
        int filter_level = filters[block];
        if (filter_level){
            int p = pitches[0];
            int block_offset = cur_iter*num_blocks + block;
            int s_off = offsets[block_offset+priority_offset];

            signed char mask = 0;
            size_t i= get_global_id(0);

            global signed char *limit, *flimit;
            global loop_filter_info *lf_info;

            if (i < threads[0]){
                lf_info = &lfi[filter_level];
                if (use_mbflim == 0){
                    flimit = lf_info->flim;
                } else {
                    flimit = lf_info->mbflim;
                }

                limit = lf_info->lim;

                s_off += p * i;
                mask = vp8_simple_filter_mask(limit[i], flimit[i], s_base[s_off-2], s_base[s_off-1], s_base[s_off], s_base[s_off+1]);
                vp8_simple_filter(mask, s_base, s_off - 2, s_off - 1, s_off, s_off + 1);
            }
        }
    }
}

kernel void vp8_loop_filter_simple_vertical_edges_kernel
(
    global unsigned char *s_base,
    global int *offsets, /* Y or YUV offsets for EACH block being processed*/
    global int *pitches, /* 1 or 3 values for Y or YUV pitches*/
    global loop_filter_info *lfi, /* Single struct for the frame */
    global int *filters_in, /* Filters for each block being processed */
    int filter_type, /* Should dc_diffs, rows, or cols be used?*/
    int priority_level,
    int num_levels,
    global int *block_offsets,
    global int *priority_num_blocks
){
    vp8_loop_filter_simple_vertical_edge_worker(s_base, offsets, pitches,
            lfi, filters_in, 1, COLS_LOCATION, 0, priority_level,
            block_offsets, priority_num_blocks
    );

    //3 Y plane iterations
    vp8_loop_filter_simple_vertical_edge_worker(s_base, offsets, pitches,
            lfi, filters_in, 0, DC_DIFFS_LOCATION, 1, priority_level,
            block_offsets, priority_num_blocks
    );
    vp8_loop_filter_simple_vertical_edge_worker(s_base, offsets, pitches,
            lfi, filters_in, 0, DC_DIFFS_LOCATION, 2, priority_level,
            block_offsets, priority_num_blocks
    );
    vp8_loop_filter_simple_vertical_edge_worker(s_base, offsets, pitches,
            lfi, filters_in, 0, DC_DIFFS_LOCATION, 3, priority_level,
            block_offsets, priority_num_blocks
    );
}

kernel void vp8_loop_filter_simple_horizontal_edges_kernel
(
    global unsigned char *s_base,
    global int *offsets,
    global int *pitches,
    global loop_filter_info *lfi,
    global int *filters_in,
    int priority_level,
    int num_levels,
    global int *block_offsets,
    global int *priority_num_blocks
){
    vp8_loop_filter_simple_horizontal_edge_worker(s_base, offsets, pitches, lfi,
            filters_in, 1, ROWS_LOCATION, 4, priority_level,
            block_offsets, priority_num_blocks
    );
    vp8_loop_filter_simple_horizontal_edge_worker(s_base, offsets, pitches, lfi,
            filters_in, 0, DC_DIFFS_LOCATION, 5, priority_level,
            block_offsets, priority_num_blocks
    );
    vp8_loop_filter_simple_horizontal_edge_worker(s_base, offsets, pitches, lfi,
            filters_in, 0, DC_DIFFS_LOCATION, 6, priority_level,
            block_offsets, priority_num_blocks
    );
    vp8_loop_filter_simple_horizontal_edge_worker(s_base, offsets, pitches, lfi,
            filters_in, 0, DC_DIFFS_LOCATION, 7, priority_level,
            block_offsets, priority_num_blocks
    );
}

kernel void vp8_loop_filter_simple_all_edges_kernel
(
    global unsigned char *s_base,
    global int *offsets,
    global int *pitches,
    global loop_filter_info *lfi,
    global int *filters_in,
    int priority_level,
    int num_levels,
    global int *block_offsets,
    global int *priority_num_blocks
)
{

    for (int i = 0; i < num_levels; i++){
        if (get_global_id(2) < priority_num_blocks[priority_level]){
            vp8_loop_filter_simple_vertical_edge_worker(s_base, offsets, pitches,
                    lfi, filters_in, 1, COLS_LOCATION, 0, priority_level,
                    block_offsets, priority_num_blocks
            );

            //3 Y plane iterations
            vp8_loop_filter_simple_vertical_edge_worker(s_base, offsets, pitches,
                    lfi, filters_in, 0, DC_DIFFS_LOCATION, 1, priority_level,
                    block_offsets, priority_num_blocks
            );
            vp8_loop_filter_simple_vertical_edge_worker(s_base, offsets, pitches,
                    lfi, filters_in, 0, DC_DIFFS_LOCATION, 2, priority_level,
                    block_offsets, priority_num_blocks
            );
            vp8_loop_filter_simple_vertical_edge_worker(s_base, offsets, pitches,
                    lfi, filters_in, 0, DC_DIFFS_LOCATION, 3, priority_level,
                    block_offsets, priority_num_blocks
            );
        }

        barrier(CLK_GLOBAL_MEM_FENCE);

        if (get_global_id(2) < priority_num_blocks[priority_level]){
            vp8_loop_filter_simple_horizontal_edge_worker(s_base, offsets, pitches, lfi,
                    filters_in, 1, ROWS_LOCATION, 4, priority_level,
                    block_offsets, priority_num_blocks
            );
            vp8_loop_filter_simple_horizontal_edge_worker(s_base, offsets, pitches, lfi,
                    filters_in, 0, DC_DIFFS_LOCATION, 5, priority_level,
                    block_offsets, priority_num_blocks
            );
            vp8_loop_filter_simple_horizontal_edge_worker(s_base, offsets, pitches, lfi,
                    filters_in, 0, DC_DIFFS_LOCATION, 6, priority_level,
                    block_offsets, priority_num_blocks
            );
            vp8_loop_filter_simple_horizontal_edge_worker(s_base, offsets, pitches, lfi,
                    filters_in, 0, DC_DIFFS_LOCATION, 7, priority_level,
                    block_offsets, priority_num_blocks
            );
        }

        barrier(CLK_GLOBAL_MEM_FENCE);
        priority_level++;
    }
}

//Inline and non-kernel functions follow.

__inline uchar8 vp8_mbfilter(
    signed char mask,
    signed char hev,
    uchar8 base
)
{
    char4 s;
    char4 u;
    signed char vp8_filter;

    char2 filter;

    char8 pq = convert_char8(base);
    pq ^= (char8){0, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0};

    /* add outer taps if we have high edge variance */
    vp8_filter = sub_sat(pq.s2, pq.s5);
    vp8_filter = clamp(vp8_filter + 3 * (pq.s4 - pq.s3), -128, 127);
    vp8_filter &= mask;

    filter = (char2)vp8_filter;
    filter &= hev;

    /* save bottom 3 bits so that we round one side +4 and the other +3 */
    filter = add_sat(filter, (char2){4,3});
    filter.s0 >>= 3;
    filter.s1 >>= 3;

    pq.s4 = sub_sat(pq.s4, filter.s0);
    pq.s3 = add_sat(pq.s3, filter.s1);

    /* only apply wider filter if not high edge variance */
    vp8_filter &= ~hev;
    filter.s1 = vp8_filter;

    /* roughly 3/7th, 2/7th, and 1/7th difference across boundary */
    u.s0 = clamp((63 + filter.s1 * 27) >> 7, -128, 127);
    u.s1 = clamp((63 + filter.s1 * 18) >> 7, -128, 127);
    u.s2 = clamp((63 + filter.s1 * 9) >> 7, -128, 127);

    s.s012 = sub_sat(pq.s456, u.s012);
    pq.s456 = s.s012 ^ 0x80;

    s.s012 = add_sat(pq.s321, u.s012);
    pq.s321 = s.s012 ^ 0x80;

    return convert_uchar8(pq);
}

/* is there high variance internal edge ( 11111111 yes, 00000000 no) */
__inline signed char vp8_hevmask(signed char thresh, uchar4 pq)
{
    signed char hev;
    hev  = (abs_diff(pq.s0, pq.s1) > thresh) * -1;
    hev  |= (abs_diff(pq.s3, pq.s2) > thresh) * -1;
    return hev;
}

/* should we apply any filter at all ( 11111111 yes, 00000000 no) */
__inline signed char vp8_filter_mask( signed char limit, signed char flimit,
        uchar8 pq)
{
    signed char mask = 0;

    //Only apply the filter if the difference is LESS than 'limit'
    mask |= (abs_diff(pq.s0, pq.s1) > limit);
    mask |= (abs_diff(pq.s1, pq.s2) > limit);
    mask |= (abs_diff(pq.s2, pq.s3) > limit);
    mask |= (abs_diff(pq.s5, pq.s4) > limit);
    mask |= (abs_diff(pq.s6, pq.s5) > limit);
    mask |= (abs_diff(pq.s7, pq.s6) > limit);
    mask |= (abs_diff(pq.s3, pq.s4) * 2 + abs_diff(pq.s2, pq.s5) / 2  > flimit * 2 + limit);
    mask *= -1;
    return ~mask;

}

/* should we apply any filter at all ( 11111111 yes, 00000000 no) */
__inline signed char vp8_simple_filter_mask(
    signed char limit,
    signed char flimit,
    uc p1,
    uc p0,
    uc q0,
    uc q1
)
{
    signed char mask = (abs_diff(p0, q0) * 2 + abs_diff(p1, q1) / 2  <= flimit * 2 + limit) * -1;
    return mask;
}

__inline void vp8_simple_filter(
    signed char mask,
    global uc *base,
    int op1_off,
    int op0_off,
    int oq0_off,
    int oq1_off
)
{

    global uc *op1 = base + op1_off;
    global uc *op0 = base + op0_off;
    global uc *oq0 = base + oq0_off;
    global uc *oq1 = base + oq1_off;

    signed char vp8_filter;
    char2 filter;

    char4 pq = (char4){*op1, *op0, *oq0, *oq1};
    pq ^= 0x80;

    signed char u;

    vp8_filter = sub_sat(pq.s0, pq.s3);
    vp8_filter = clamp(vp8_filter + 3 * (pq.s2 - pq.s1), -128, 127);
    vp8_filter &= mask;

    /* save bottom 3 bits so that we round one side +4 and the other +3 */
    filter = add_sat((char2)vp8_filter, (char2){4,3});
    filter.s0 >>= 3;
    filter.s1 >>= 3;

    u = sub_sat(pq.s2, filter.s0);
    *oq0  = u ^ 0x80;

    u = add_sat(pq.s1, filter.s1);
    *op0 = u ^ 0x80;
}


Routing: working on queue

Routing: working on queue

//QUEUE

struct _QueueElement
{
  ushort2 block;
  ushort node;
  ushort _unused_;
  float priority;
  uint state;
};
typedef struct _QueueElement QueueElement;

#define  QueueElementEmpty 0
#define  QueueElementReady 1
#define  QueueElementReading 2
#define  QueueElementRead 0
void checkAndSetQueueState(volatile global QueueElement* element, uint state, uint expected)
{
  while(atomic_cmpxchg(&element->state, expected, state) != expected);
}
void setQueueState(volatile global QueueElement* element, uint state)
{
  uint old = atomic_xchg(&element->state, state);
  //check: old + 1 % (QueueElementReading+1) == state
}

struct _QueueGlobal
{
  uint front;
  uint back;
  int filllevel;
};
typedef struct _QueueGlobal QueueGlobal;

#define QueueLinkActive 0x80000000
uint getQueuePosAndLock(volatile global uint* myQueueLink)
{
  while(true)
  {
    uint old = atomic_or(myQueueLink,QueueLinkActive);
    if(!(old & QueueLinkActive))
    {
      //this one is not active
      return 0;
    }
    else if((old & ~QueueLinkActive) == 0)
    {
      //it is just modified -> load again
      continue;
    }
    else
    {
      //this is the current position
      return (old & ~QueueLinkActive);
    }
  }
}

void setQueuePos(volatile global uint* myQueueLink, uint pos)
{
  uint old = atomic_xchg(myQueueLink, pos | QueueLinkActive);
  //check old == QueueLinkActive
}

void unLockQueuePos(volatile global uint* myQueueLink)
{
  uint old = atomic_xchg(myQueueLink, 0);
}

void Enqueue(volatile global QueueGlobal* queueInfo,
             volatile global QueueElement* queue,
             volatile global uint* queueLink,
             QueueElement* element,
             uint2 dim,
             uint queueSize)
{
  //check if element is already in tehe Queue:
  volatile global uint* myQueueLink = queueLink + (element->block.x + element->block.y*dim.x + dim.x*dim.y*element->node);
  uint queuePos = getQueuePosAndLock(myQueueLink);
  if(queuePos != 0)
  {
    //just update the priority, could be that this has just been dropped out of the queue.
    //as long as the queue is big enough, this should not influence any other
    //as long as the float is positive this should produce the right result!
    atomic_max((volatile global uint*)(&queue[queuePos].priority), as_uint(element->priority));
  }
  else
  {
    //inc size
    uint filllevel = atomic_inc(&queueInfo->filllevel);

    //make sure there is enough space?
    //assert(filllevel < queueSize)

    //insert a new element
    uint pos = atomic_inc(&queueInfo->back)%queueSize;

    //make sure it is free?
    //checkAndSetQueueState(queue + pos, QueueElementEmpty, QueueElementEmpty);

    //insert the data
    queue[pos].block = element->block;
    queue[pos].priority = element->priority;
    queue[pos].node = element->node;
    write_mem_fence(CLK_GLOBAL_MEM_FENCE);

    //set the link
    setQueuePos(myQueueLink, pos);
    write_mem_fence(CLK_GLOBAL_MEM_FENCE);

    //activate the queue element
    setQueueState(queue + pos,  QueueElementReady);
  }
}

bool Dequeue(volatile global QueueGlobal* queueInfo,
             volatile global QueueElement* queue,
             volatile global uint* queueLink,
             QueueElement* element,
             uint2 dim,
             uint queueSize)
{
  //is there something to get?
  if(atomic_dec(&queueInfo->filllevel) <= 0)
  {
    atomic_inc(&queueInfo->filllevel);
    return false;
  }

  //pop the front of the queue
  uint pos = atomic_inc(&queueInfo->front)%queueSize;

  //wait for the element to be written/change to reading
  checkAndSetQueueState(queue + pos, QueueElementReading, QueueElementReady);

  //read
  element->block = queue[pos].block;
  element->node =  queue[pos].node;

  volatile global uint* myQueueLink = queueLink + (element->block.x + element->block.y*dim.x + dim.x*dim.y*element->node);

  //remove link
  unLockQueuePos(myQueueLink);

  //free queue element
  setQueueState(queue + pos, QueueElementRead);
  return true;
}
//

float getPenalty(read_only image2d_t costmap, int2 pos)
{
  const sampler_t sampler = CLK_FILTER_NEAREST
                          | CLK_NORMALIZED_COORDS_FALSE
                          | CLK_ADDRESS_CLAMP_TO_EDGE;

  return read_imagef(costmap, sampler, pos).x;
}

//it might be better to do that via opengl or something (arbitrary node shapes etc)
__kernel void prepareRouting(read_only image2d_t costmap,
                           global float* nodes,
                           global uint4* queue,
                           global uint* queue_pos,
                           global uint4* startingPoints,
                           const uint numStartingPoints,
                           const int2 dim,
                           const int2 blocks)
{
  __local bool hasStartingPoint;

  int3 id = {get_global_id(0), get_global_id(1), get_global_id(2)};

  if(get_local_id(0) == 0 && get_local_id(1) == 0 && get_local_id(2) == 0)
    hasStartingPoint = false;
  barrier(CLK_LOCAL_MEM_FENCE);

  if(get_local_id(0) < dim.x && get_local_id(01) < dim.y)
  {
    float cost = 0.01f*MAXFLOAT;
    if(startingPoints[id.z].x <= id.x && id.x <= startingPoints[id.z].z &&
        startingPoints[id.z].y <= id.y && id.y <= startingPoints[id.z].w)
    {
        cost = 0;
        hasStartingPoint = true;
    }
    //prepare data
    nodes[dim.x*id.y+id.x + dim.x*dim.y*id.z] = cost;
    if(hasStartingPoint == true)
    {
      //TODO insert into queue
    }
  }

  //nodes[dim.x*id.y+id.x] = 255*min(1.0f,getPenalty(costmap, id));
}

local float* accessLocalCost(local float* l_costs, int2 id)
{
  return l_costs + (id.x+1 + (id.y+1)*(get_local_size(0)+2));
}

__kernel void routing(read_only image2d_t costmap,
                      global float* nodes,
                      global uint4* queue,
                      global uint* queue_pos,
                      global uint4* startingPoints,
                      const uint numStartingPoints,
                      const int2 dim,
                      const int2 blocks,
                      local float* l_costs)
{
  int3 id = {get_global_id(0), get_global_id(1), get_global_id(2)};

  //copy cost to local
  int local_linid = get_local_id(1)*get_local_size(0) + get_local_id(0);
  for(int i = local_linid; i < (get_local_size(0)+2)*(get_local_size(1)+2); i += get_local_size(0)*get_local_size(1))
  {
    int2 global_pos = (int2)(get_group_id(0)*get_local_size(0),get_group_id(1)*get_local_size(1));
    global_pos += (int2)(i%(get_local_size(0)+2),i/(get_local_size(0)+2)) - (int2)(1,1);

    float incost = 0.01f*MAXFLOAT;
    if(global_pos.x > 0 && global_pos.x < dim.x &&
       global_pos.y > 0 && global_pos.y < dim.y)
      incost = nodes[dim.x*global_pos.y + global_pos.x + dim.x*dim.y*id.z];
    l_costs[i] = incost;
  }

  local bool change;
  if(local_linid == 0)
    change = true;
  barrier(CLK_LOCAL_MEM_FENCE);

  int2 localid = (int2)(get_local_id(0),get_local_id(1));
  int2 globalid = (int2)(get_global_id(0),get_global_id(1));

  //iterate over it
  float myPenalty = getPenalty(costmap, globalid);
  float local * myval = accessLocalCost(l_costs, localid);
  while(change)
  {
    if(local_linid == 0)
      change = false;
    barrier(CLK_LOCAL_MEM_FENCE);

    float lastmyval = *myval;

    int2 offset;
    for(offset.y = -1; offset.y  <= 1; ++offset.y)
      for(offset.x = -1; offset.x <= 1; ++offset.x)
      {

        float d = native_sqrt((float)(offset.x*offset.x+offset.y*offset.y));
        float penalty = 0.5f*(myPenalty + getPenalty(costmap, globalid + offset));
        float c_other = *accessLocalCost(l_costs, localid + offset);
        *myval = min(*myval, c_other + 0.01f*d + d*penalty);
      }

    if(*myval != lastmyval)
      change = true;

    barrier(CLK_LOCAL_MEM_FENCE);
  }

  //write back
  nodes[dim.x*globalid.y + globalid.x + dim.x*dim.y*id.z] = *myval;
  //nodes[dim.x*globalid.y + globalid.x + dim.x*dim.y*id.z] =  getPenalty(costmap, globalid);
}

#if 0

/*
 The values stored in the resulting map contain the cost the reach each node and
 which is its predecessor on the path there. It contains 32 Bits for each node
 where the first two bits represent the direction of the predecessor and the
 remaining bits form the actual cost of reaching the node.
 */

constant uint MASK_VALUE        = 0x3fffffff;
constant uint MASK_DIRECTION    = 0xc0000000;
constant uint VALUE_NOT_VISITED = 0xffffffff;

//------------------------------------------------------------------------------
// Heap
//------------------------------------------------------------------------------
typedef struct
{
  global uint *values; //!< The values to be sorted
  global uint *heap;   //!< Sorted indices (by values)
  uint size;           //!< Current heap size
  int2 dim;            //!< Dimensions of values
} data_t;

#define _value(index) (d->values[ d->heap[index] ] & MASK_VALUE)
#define _parent(index) (index / 2)
#define _child_left(index) (2 * index + 1)
#define _child_right(index) (2 * index + 2)

/**
 * @param index The index of the node to insert
 */
void heap_insert( data_t *d,
                  const uint index )
{
  uint cur = d->size++;
  uint val = _value(cur);
  uint parent = _parent(cur);

  // move up
  while( cur && val < _value(parent) )
  {
    d->heap[ cur ] = d->heap[ parent ];

    cur = parent;
    parent = _parent(cur);
  }

  d->heap[ cur ] = index;
}

/**
 *
 * @return Index of the minimum node
 */
uint heap_removeMin( data_t *d )
{
  // Remove first element
  uint cur = 0,
       front = d->heap[ cur ],
       child = _child_left(cur);

  // Propagate hole down the tree
  while( child < d->size )
  {
    // Change to right child if it exists and is smaller than the left one
    if( child + 1 < d->size && _value(child + 1) < _value(child) )
      child += 1;

    d->heap[ cur ] = d->heap[ child ];
    cur = child;
    child = _child_left(cur);
  }

  // Put last element into hole...
  uint index = d->heap[ --d->size ];
  uint val = _value(index);
  uint parent = _parent(cur);

  // ...and let it bubble up
  while( cur && val < _value(parent) )
  {
    d->heap[ cur ] = d->heap[ parent ];

    cur = parent;
    parent = _parent(cur);
  }

  d->heap[ cur ] = index;
  return front;
}

//------------------------------------------------------------------------------
// Dijkstra
//------------------------------------------------------------------------------
float getPenalty(read_only image2d_t costmap, int x, int y)
{
  const sampler_t sampler = CLK_FILTER_NEAREST
                          | CLK_NORMALIZED_COORDS_FALSE
                          | CLK_ADDRESS_CLAMP_TO_EDGE;

  return read_imagef(costmap, sampler, (int2)(x, y)).x;
}

//------------------------------------------------------------------------------
// Kernel
//------------------------------------------------------------------------------

#define makeIndex(pos, dim) (pos.y * dim.x + pos.x)

__kernel void test_kernel( read_only image2d_t costmap,
                           global uint* nodes,
                           global uint* queue,
                           const int2 dim,
                           const int2 start,
                           const int2 target )
{
  data_t data = {
    .values = nodes,
    .heap = queue,
    .size = 0,
    .dim = dim
  };

  const float link_width = 4,
              alpha_L = 0.01,
              alpha_P = 2.99,
              // simplify and scale the cost calculation a bit...
              factor = 100 * (0.5/alpha_L * alpha_P * link_width + 1);

  // Initialize all to "Not visited" and startnode to zero
  for(uint i = 0; i < dim.x * dim.y; ++i)
    nodes[i] = VALUE_NOT_VISITED;

  uint start_index = makeIndex(start, dim);
  nodes[ start_index ] = 0;
  heap_insert(&data, start_index);
  int count = 10;
  uint max_size = 0;
  // Start with Dijkstra
  do
  {
    max_size = max(data.size, max_size);
    uint index = heap_removeMin(&data);
    int px = index % dim.x, // DON'T use int2 -> don't know why but % doesn't work...
        py = index / dim.x;

    // Update all 8 neighbours
    for( int x = max(px - 1, 0); x <= min(px + 1, dim.x); ++x )
      for( int y = max(py - 1, 0); y <= min(py + 1, dim.y); ++y )
      {
        if( px == x && py == y )
          continue;

        int index_n = makeIndex((int2)(x, y), dim);

        if( nodes[index_n] == VALUE_NOT_VISITED )
          heap_insert(&data, index_n);

        float len = (x == px || y == py) ? 1 : 1.4f;
        float cost = factor
                   * len
                   * (getPenalty(costmap, x, y) + getPenalty(costmap, px, py));

        int total_cost = nodes[index] + (int)cost;

        // update/relax
        if( total_cost < nodes[index_n] )
        {
          nodes[index_n] = total_cost;
        }
      }
  } while( data.size && count-- );

  nodes[ 0 ] = max_size;
}

#endif