correct apron implementation

correct apron implementation

#include "clcommons/common.h"
#include "clcommons/image.h"

#ifndef PIXELT
#define PIXELT uint
#endif
#ifndef LABELT
#define LABELT uint
#endif
#ifndef CONNECTIVITYPIXELT
#define CONNECTIVITYPIXELT uint
#endif
#ifndef LDSPIXELT
#define LDSPIXELT uint
#endif
#ifndef LDSLABELT
#define LDSLABELT uint
#endif
#ifndef LDSCONNECTIVITYPIXELT
#define LDSCONNECTIVITYPIXELT uint
#endif

typedef PIXELT PixelT;
typedef LDSPIXELT LDSPixelT;
typedef LABELT LabelT;
typedef LDSLABELT LDSLabelT;
typedef CONNECTIVITYPIXELT ConnectivityPixelT;
typedef LDSCONNECTIVITYPIXELT LDSConnectivityPixelT;

#ifndef BG_VALUE
#define BG_VALUE 0
#endif

#ifndef CONNECTIVITY  //4 or 8
#define CONNECTIVITY 8
#endif

#ifndef WORKGROUP_TILE_SIZE_X
#define WORKGROUP_TILE_SIZE_X 32
#endif
#ifndef WORKGROUP_TILE_SIZE_Y
#define WORKGROUP_TILE_SIZE_Y 8
#endif

#ifndef WORKITEM_REPEAT_X
#define WORKITEM_REPEAT_X 1
#endif
#ifndef WORKITEM_REPEAT_Y
#define WORKITEM_REPEAT_Y 4
#endif

#define TILE_COLS (WORKGROUP_TILE_SIZE_X * WORKITEM_REPEAT_X)
#define TILE_ROWS (WORKGROUP_TILE_SIZE_Y * WORKITEM_REPEAT_Y)

enum ConnectivityEnum {
    UP = (1<<0),
    LEFT = (1<<1),
    DOWN = (1<<2),
    RIGHT = (1<<3),
    LEFT_UP = (1<<4),
    LEFT_DOWN = (1<<5),
    RIGHT_UP = (1<<6),
    RIGHT_DOWN = (1<<7)
};

#define isConnected(p1, p2) ((p1) == (p2))

#define pixel_at(type, basename, r, c) image_pixel_at(type, PASTE2(basename, _p), im_rows, im_cols, PASTE2(basename, _pitch), (r), (c))

//global dimensions: divUp(image.cols, block.x), divUp(image.rows, block.y);
//__attribute__((reqd_work_group_size(WORKGROUP_TILE_SIZE_X, WORKGROUP_TILE_SIZE_Y, 1)))
//__kernel void
//make_connectivity_image(uint im_rows, uint im_cols, __global PixelT *image_p, uint image_pitch, __global ConnectivityPixelT *connectivityim_p, uint connectivityim_pitch){
//    const uint c = get_global_id(1);
//    const uint r = get_global_id(0);
//    const bool valid_pixel_task = (c < im_cols) & (r < im_rows);
//
//    if(valid_pixel_task){
//        PixelT pixel = pixel_at(PixelT, image, r, c);
//        ConnectivityPixelT connectivity = 0;
//
//#if CONNECTIVITY == 8
//        connectivity |= c > 0 && r > 0                     && isConnected(pixel, pixel_at(PixelT, image, r-1, c - 1)) ? LEFT_UP : 0
//        connectivity |= c > 0                              && isConnected(pixel, pixel_at(PixelT, image, r, c - 1)) ? LEFT : 0
//        connectivity |= c > 0 && r < im_rows - 1           && isConnected(pixel, pixel_at(PixelT, image, r+1, c - 1)) ? LEFT_DOWN : 0
//        connectivity |=          r < im_rows - 1           && isConnected(pixel, pixel_at(PixelT, image, r+1, c)) ? DOWN : 0
//        connectivity |= c < im_cols - 1 && r < im_rows - 1 && isConnected(pixel, pixel_at(PixelT, image, r+1, c + 1)) ? RIGHT_DOWN : 0
//        connectivity |= c < im_cols - 1                    && isConnected(pixel, pixel_at(PixelT, image, r, c + 1)) ? RIGHT : 0
//        connectivity |= c < im_cols - 1 && r > 0           && isConnected(pixel, pixel_at(PixelT, image, r-1, c + 1)) ? RIGHT_UP : 0
//        connectivity |=          r > 0                     && isConnected(pixel, pixel_at(PixelT, image, r-1, c)) ? UP : 0
//#else
//        connectivity |= c > 0                              && isConnected(pixel, pixel_at(PixelT, image, r, c - 1)) ? LEFT : 0
//        connectivity |=          r < im_rows - 1           && isConnected(pixel, pixel_at(PixelT, image, r+1, c)) ? DOWN : 0
//        connectivity |= c < im_cols - 1                    && isConnected(pixel, pixel_at(PixelT, image, r, c + 1)) ? RIGHT : 0
//        connectivity |=          r > 0                     && isConnected(pixel, pixel_at(PixelT, image, r-1, c)) ? UP : 0
//#endif
//
//        pixel_at(ConnectivityPixelT, connectivity, r, c) = connectivity;
//    }
//}

#define apron_pixel(apron, t_r, t_c) apron[(t_r+ 1)][(t_c + 1)]
//global dimensions: divUp(im_cols, tile_cols), divUp(im_rows, tile_rows);
__attribute__((reqd_work_group_size(WORKGROUP_TILE_SIZE_X, WORKGROUP_TILE_SIZE_Y, 1)))
__kernel void
make_connectivity_image(uint im_rows, uint im_cols, __global PixelT *image_p, uint image_pitch, __global ConnectivityPixelT *connectivityim_p, uint connectivityim_pitch){
    const uint tile_col_blocksize = get_local_size(0);
    const uint tile_row_blocksize = get_local_size(1);
    const uint tile_col_block = get_group_id(0) + get_global_offset(0) / tile_col_blocksize;
    const uint tile_row_block = get_group_id(1) + get_global_offset(1) / tile_row_blocksize;
    const uint tile_col = get_local_id(0);
    const uint tile_row = get_local_id(1);
    const bool valid_pixel_task = (get_global_id(0) < im_cols) & (get_global_id(1) < im_rows);

    uint tile_rows = tile_row_blocksize;
    uint tile_cols = tile_col_blocksize;

    const uint tile_row_start = tile_row_blocksize * tile_rows;
    const uint tile_col_start = tile_col_blocksize * tile_cols;
    const uint tile_row_end = min((tile_row_blocksize + 1) * tile_rows, (uint) im_rows);
    const uint tile_col_end = min((tile_col_blocksize + 1) * tile_cols, (uint) im_cols);
    //adjust to true tile dimensions
    tile_rows = tile_row_end - tile_row_start;
    tile_cols = tile_col_end - tile_col_start;
    const uint apron_tile_cols = tile_cols + 2;;
    //const uint n_tile_pixels = tile_rows * tile_cols;
    const uint n_work_items = get_local_size(0) * get_local_size(1);
    const uint n_apron_tile_pixels = (tile_rows + 2) * (apron_tile_cols);
    __local LDSPixelT im_tile[TILE_ROWS + 2][TILE_COLS + 2];
    __local LDSConnectivityPixelT connectivity_tile[TILE_ROWS][TILE_COLS];

    const uint tid = get_local_id(0) * get_local_size(0) + get_local_id(1);
    for(uint im_tile_fill_task_id = tid; im_tile_fill_task_id < n_apron_tile_pixels; im_tile_fill_task_id += n_work_items){
        const uint im_apron_tile_row = im_tile_fill_task_id / apron_tile_cols;
        const uint im_apron_tile_col = im_tile_fill_task_id % apron_tile_cols;
        const int g_c = ((int)(im_apron_tile_col + tile_col_block * tile_col_blocksize)) - 1;
        const int g_r = ((int)(im_apron_tile_row + tile_row_block * tile_row_blocksize)) - 1;

        im_tile[im_apron_tile_row][im_apron_tile_col] = image_tex2D(PixelT, image_p, (int) im_rows, (int) im_cols, image_pitch, g_r, g_c, ADDRESS_ZERO);
    }
    lds_barrier();

    #pragma unroll
    for (int i = 0; i < WORKITEM_REPEAT_Y; ++i){
        #pragma unroll
        for (int j = 0; j < WORKITEM_REPEAT_X; ++j){
            const uint c = get_local_id(0) + WORKGROUP_TILE_SIZE_X * j;
            const uint r = get_local_id(1) + WORKGROUP_TILE_SIZE_Y * i;
            const uint g_c = get_global_id(1);
            const uint g_r = get_global_id(0);
            PixelT pixel = apron_pixel(im_tile, r, c);
            ConnectivityPixelT connectivity = 0;

#if CONNECTIVITY == 8
            connectivity |= c > 0 && r > 0                         && isConnected(pixel, apron_pixel(im_tile, r-1, c - 1)) ? LEFT_UP : 0;
            connectivity |= c > 0                                  && isConnected(pixel, apron_pixel(im_tile, r  , c - 1)) ? LEFT : 0;
            connectivity |= c > 0 && r < tile_rows - 1             && isConnected(pixel, apron_pixel(im_tile, r+1, c - 1)) ? LEFT_DOWN : 0;
            connectivity |=          r < tile_rows - 1             && isConnected(pixel, apron_pixel(im_tile, r+1, c    )) ? DOWN : 0;
            connectivity |= c < tile_cols - 1 && r < tile_rows - 1 && isConnected(pixel, apron_pixel(im_tile, r+1, c + 1)) ? RIGHT_DOWN : 0;
            connectivity |= c < tile_cols - 1                      && isConnected(pixel, apron_pixel(im_tile, r  , c + 1)) ? RIGHT : 0;
            connectivity |= c < tile_cols - 1 && r > 0             && isConnected(pixel, apron_pixel(im_tile, r-1, c + 1)) ? RIGHT_UP : 0;
            connectivity |=          r > 0                         && isConnected(pixel, apron_pixel(im_tile, r-1, c    )) ? UP : 0;
#else
            connectivity |= c > 0                                  && isConnected(pixel, apron_pixel(im_tile, r  , c - 1)) ? LEFT : 0;
            connectivity |=          r < tile_rows - 1             && isConnected(pixel, apron_pixel(im_tile, r+1, c    )) ? DOWN : 0;
            connectivity |= c < tile_cols - 1                      && isConnected(pixel, apron_pixel(im_tile, r  , c + 1)) ? RIGHT : 0;
            connectivity |=          r > 0                         && isConnected(pixel, apron_pixel(im_tile, r-1, c    )) ? UP : 0;
#endif
            connectivity_tile[r][c] = connectivity;
        }
    }
    lds_barrier();

    for(uint im_tile_fill_task_id = tid; im_tile_fill_task_id < n_tile_pixels; im_tile_fill_task_id += n_work_items){
        const uint im_tile_row = im_tile_fill_task_id / tile_cols;
        const uint im_tile_col = im_tile_fill_task_id % tile_cols;
        const uint g_c = im_tile_col + tile_col_block * tile_col_blocksize;
        const uint g_r = im_tile_row + tile_row_block * tile_row_blocksize;

        pixel_at(ConnectivityPixelT, connectivityim, g_r, g_c) = connectivity_tile[im_tile_row][im_tile_col];
    }
}

__attribute__((reqd_work_group_size(WORKGROUP_TILE_SIZE_X, WORKGROUP_TILE_SIZE_Y, 1)))
__kernel void
label_tiles(uint im_rows, uint im_cols, __global ConnectivityPixelT *labelim_p, uint labelim_pitch, __global ConnectivityPixelT *connectivityim_p, uint connectivityim_pitch){
    const uint x = get_local_id(0) + get_group_id(0) * TILE_COLS;
    const uint y = get_local_id(1) + get_group_id(1) * TILE_ROWS;

    //if (x >= im_rows || y >= im_rows) return;

    //currently x is 1
    //int bounds = ((y + WORKITEM_REPEAT_Y) < im_rows);

    __local LDSLabelT label_tile_im[TILE_ROWS][TILE_COLS];
    __local LDSConnectivityPixelT  edge_tile_im[TILE_ROWS][TILE_COLS];

    LDSLabelT new_labels[WORKITEM_REPEAT_Y][WORKITEM_REPEAT_X];
    LDSLabelT old_labels[WORKITEM_REPEAT_Y][WORKITEM_REPEAT_X];

    #pragma unroll
    for (int i = 0; i < WORKITEM_REPEAT_Y; ++i){
        #pragma unroll
        for (int j = 0; j < WORKITEM_REPEAT_X; ++j){
            const uint yloc = get_local_id(1) + WORKGROUP_TILE_SIZE_Y * i;
            const uint xloc = get_local_id(0) + WORKGROUP_TILE_SIZE_X * j;
            const bool valid_pixel_task = (xloc < im_cols) & (yloc < im_rows);
            ConnectivityPixelT c = valid_pixel_task ? pixel_at(ConnectivityPixelT, connectivityim, y + WORKGROUP_TILE_SIZE_Y * i, x + WORKGROUP_TILE_SIZE_X * j) : 0;

            c = xloc == 0 ? c & ~(LEFT|LEFT_DOWN|LEFT_UP) : c;
            c = yloc == 0 ? c & ~(UP|LEFT_UP|RIGHT_UP) : c;

            c = xloc == TILE_COLS - 1 ? c & ~(RIGHT|RIGHT_DOWN|RIGHT_UP) : c;
            c = yloc == TILE_ROWS - 1 ? c & ~(DOWN|LEFT_DOWN|RIGHT_DOWN) : c;

            new_labels[i][j] = yloc * TILE_COLS + xloc;
            edge_tile_im[yloc][xloc] = c;
        }
    }

    for (int k = 0; ;++k){
        //make copies
        #pragma unroll
        for (int i = 0; i < WORKITEM_REPEAT_Y; ++i){
            #pragma unroll
            for (int j = 0; j < WORKITEM_REPEAT_X; ++j){
                const uint yloc = get_local_id(1) + WORKGROUP_TILE_SIZE_Y * i;
                const uint xloc = get_local_id(0) + WORKGROUP_TILE_SIZE_X * j;

                old_labels[i][j]          = new_labels[i][j];
                label_tile_im[yloc][xloc] = new_labels[i][j];
            }
        }
        lds_barrier();

        //take minimum label of local neighboorhood - single writer, multi reader version
        #pragma unroll
        for (int i = 0; i < WORKITEM_REPEAT_Y; ++i){
            #pragma unroll
            for (int j = 0; j < WORKITEM_REPEAT_X; ++j){
                const uint yloc = get_local_id(1) + WORKGROUP_TILE_SIZE_Y * i;
                const uint xloc = get_local_id(0) + WORKGROUP_TILE_SIZE_X * j;

                const ConnectivityPixelT connectivity = edge_tile_im[yloc][xloc];
                LDSLabelT label = new_labels[i][j];

#if CONNECTIVITY == 8
                if (connectivity & UP)
                   label = min(label, label_tile_im[yloc - 1][xloc]);
                if (connectivity & LEFT)
                   label = min(label, label_tile_im[yloc][xloc - 1]);
                if (connectivity & LEFT_UP)
                   label = min(label, label_tile_im[yloc - 1][xloc - 1]);
                if (connectivity &  LEFT_DOWN)
                   label = min(label, label_tile_im[yloc + 1][xloc - 1]);
                if (connectivity &  DOWN)
                   label = min(label, label_tile_im[yloc + 1][xloc]);
                if (connectivity & RIGHT_DOWN)
                   label = min(label, label_tile_im[yloc - 1][xloc + 1]);
                if (connectivity & RIGHT)
                   label = min(label, label_tile_im[yloc][xloc + 1]);
                if (connectivity & RIGHT_UP)
                   label = min(label, label_tile_im[yloc + 1][xloc + 1]);

#else
                if (connectivity & UP)
                   label = min(label, label_tile_im[yloc - 1][xloc]);
                if (connectivity &  DOWN)
                   label = min(label, label_tile_im[yloc + 1][xloc]);
                if (connectivity & LEFT)
                   label = min(label, label_tile_im[yloc][xloc - 1]);
                if (connectivity & RIGHT)
                   label = min(label, label_tile_im[yloc][xloc + 1]);
#endif

                new_labels[i][j] = label;
            }
        }
        lds_barrier();

        __local int changed;
        if((get_local_id(1) == 0) & (get_local_id(0) == 0)){
            changed = 0;
        }
        lds_barrier();

        __local LDSLabelT *labels = &label_tile_im[0][0];

        int pchanged = 0;
        #pragma unroll
        for(int i = 0; i < WORKITEM_REPEAT_Y; ++i){
            #pragma unroll
            for(int j = 0; j < WORKITEM_REPEAT_X; ++j){
                if(new_labels[i][j] < old_labels[i][j]){
                    pchanged++;
                    atomic_min(labels + old_labels[i][j], new_labels[i][j]);
                }
            }
        }
        atomic_add(&changed, pchanged);
        lds_barrier();

        //if there are no updates, we are finished
        if(!changed){
            break;
        }

        //Compact paths
        #pragma unroll
        for(int i = 0; i < WORKITEM_REPEAT_Y; ++i){
            #pragma unroll
            for(int j = 0; j < WORKITEM_REPEAT_X; ++j){
                LDSLabelT label = new_labels[i][j];

                //find root label
                while(labels[label] < label){
                    label = labels[label];
                }

                new_labels[i][j] = label;
            }
        }
        lds_barrier();
    }

    //save with adjusted global (untiled) labels
    #pragma unroll
    for(int i = 0; i < WORKITEM_REPEAT_Y; ++i){
        #pragma unroll
        for(int j = 0; j < WORKITEM_REPEAT_X; ++j){
            const LabelT tile_label = new_labels[i][j];
            const uint yloc = (tile_label / TILE_COLS) + get_group_id(0) * TILE_COLS;
            const uint xloc = (tile_label % TILE_COLS) + get_group_id(1) * TILE_ROWS;

            const LabelT glabel = yloc * im_cols + xloc;
            const bool valid_pixel_task = (xloc < im_cols) & (yloc < im_rows);
            if(valid_pixel_task){
                pixel_at(LabelT, labelim, yloc, xloc) = glabel;
            }
        }
    }
}

inline
LabelT find_root_global(const __global LabelT *labelim_p, uint labelim_pitch, LabelT label, const uint im_rows, const uint im_cols){
    for(;;){
        const uint y = label / im_cols;
        const uint x = label % im_cols;
        const LabelT parent = pixel_at(LabelT, labelim, y, x);

        if(label == parent){
            break;
        }

        label = parent;
    }
    return label;
}

__kernel
void compact_paths_global(uint im_rows, uint im_cols, __global LabelT *labelim_p, uint labelim_pitch){
    const uint x = get_global_id(0);
    const uint y = get_global_id(1);

    if((x < im_cols) & (y < im_rows)){
        pixel_at(LabelT, labelim, y, x) = find_root_global(labelim_p, labelim_pitch, pixel_at(LabelT, labelim, y, x), im_rows, im_cols);
    }
}

inline
void merge_edge_labels(const uint im_rows, const uint im_cols, __global LabelT *labelim_p, const uint labelim_pitch, const LabelT l1, const LabelT l2, uint *changed){
    const LabelT r1 = find_root_global(labelim_p, labelim_pitch, l1, im_rows, im_cols);
    const LabelT r2 = find_root_global(labelim_p, labelim_pitch, l2, im_rows, im_cols);

    if(r1 == r2){
        return;
    }

    const LabelT mi = min(r1, r2);
    const LabelT ma = max(r1, r2);

    const uint y = ma / im_cols;
    const uint x = ma % im_cols;

    atomic_min(&pixel_at(LabelT, labelim, y, x), mi);
    *changed = true;
}

__kernel
void cross_merge(
    const uint n_vert_tiles, const uint n_horz_tiles,
    uint tile_rows, uint tile_cols,
    const uint im_rows, const uint im_cols,
    const __global ConnectivityPixelT *connectivityim_p, const uint connectivityim_pitch,
    __global LabelT *labelim_p, const uint labelim_pitch,
    const uint yIncomplete, uint xIncomplete
){
    const uint ngroups_x = get_num_groups(0);
    const uint ngroups_y = get_num_groups(1);

    const uint tid = get_local_linear_id();
    const uint stride = get_local_size(1) * get_local_size(0);

    const uint ybegin = get_group_id(1) * (n_vert_tiles * tile_rows);
    uint yend   = ybegin + n_vert_tiles * tile_rows;

    if (get_group_id(1) == ngroups_y - 1){
        yend -= yIncomplete * tile_rows;
        yend -= tile_rows;
        tile_rows = (im_rows % tile_rows);

        yend += tile_rows;
    }

    const uint xbegin = get_group_id(0) * n_horz_tiles * tile_cols;
    uint xend   = xbegin + n_horz_tiles * tile_cols;

    if(get_group_id(0) == ngroups_x - 1){
        if (xIncomplete) yend = ybegin;
        xend -= xIncomplete * tile_cols;
        xend -= tile_cols;
        tile_cols = (im_cols % tile_cols);

        xend += tile_cols;
    }

    if (get_group_id(1) == (ngroups_y - 1) && yIncomplete){
        xend = xbegin;
    }

    const uint tasksV = (n_horz_tiles - 1) * (yend - ybegin);
    const uint tasksH = (n_vert_tiles - 1) * (xend - xbegin);

    const uint total = tasksH + tasksV;

    __local uint changed;
    uint pchanged;
    do{
        pchanged = false;
        for(uint taskIdx = tid; taskIdx < total; taskIdx += stride){
            if(taskIdx < tasksH){
                const uint indexH = taskIdx;

                const uint row = indexH / (xend - xbegin);
                const uint col = indexH % (xend - xbegin);

                const uint y = ybegin + (row + 1) * tile_rows;
                const uint x = xbegin + col;

                const ConnectivityPixelT e = pixel_at(ConnectivityPixelT, connectivityim, y, x);
                if(e & UP){
                    const LabelT lc = pixel_at(LabelT, labelim, y, x);
                    const LabelT lu = pixel_at(LabelT, labelim, y - 1, x);
                    merge_edge_labels(im_rows, im_cols, labelim_p, labelim_pitch, lc, lu, &pchanged);
                }
            }else{
                const uint indexV = taskIdx - tasksH;

                const uint col = indexV / (yend - ybegin);
                const uint row = indexV % (yend - ybegin);

                const uint x = xbegin + (col + 1) * tile_cols;
                const uint y = ybegin + row;

                const ConnectivityPixelT e = pixel_at(ConnectivityPixelT, connectivityim, y, x);
                if(e & LEFT){
                    const LabelT lc = pixel_at(LabelT, labelim, y, x);
                    const LabelT lu = pixel_at(LabelT, labelim, y, x - 1);
                    merge_edge_labels(im_rows, im_cols, labelim_p, labelim_pitch, lc, lu, &pchanged);
                }
            }
        }
        atomic_add(&changed, pchanged);
        lds_barrier();
        pchanged = changed;
    }while(pchanged);
}

__kernel
void mark_root_classes(
    uint im_rows, uint im_cols,
    __global PixelT *image_p, uint image_pitch,
    __global const LabelT* labelim_p, const uint labelim_pitch,
    __global uint* is_root_class_image_p, const uint is_root_class_image_pitch
){
    const uint c = get_global_id(0);
    const uint r = get_global_id(1);
    const bool valid_pixel_task = (c < im_cols) & (r < im_rows);

    const uint linear_index = c + r * im_cols;
    if(valid_pixel_task){
        const PixelT pixel = pixel_at(PixelT, image, r, c);
        const LabelT label = pixel_at(LabelT, labelim, r, c);
        pixel_at(uint, is_root_class_image, r, c) = (pixel != BG_VALUE) & (label == linear_index);
    }
}

__kernel
void relabel_with_scanline_order(
    uint im_rows, uint im_cols,
    __global LabelT* labelim_out_p, const uint labelim_out_pitch,
    __global const PixelT* image_p, const uint image_pitch,
    __global const LabelT* labelim_p, const uint labelim_pitch,
    __global uint* scanline_prefix_sum_of_root_classes_p, const uint scanline_prefix_sum_of_root_classes_pitch
){
    const uint c = get_global_id(0);
    const uint r = get_global_id(1);
    const bool valid_pixel_task = (c < im_cols) & (r < im_rows);
    const uint linear_index = c + r * im_cols;

    if(valid_pixel_task){
        const LabelT pixel = pixel_at(PixelT, image, r, c);
        LabelT final_label = 0;
        if(pixel != BG_VALUE){
            const LabelT label = pixel_at(LabelT, labelim, r, c);
            const uint label_r = label / im_cols;
            const uint label_c = label % im_cols;
            const uint scan_id = pixel_at(uint, scanline_prefix_sum_of_root_classes, label_r, label_c);
            final_label = scan_id + 1;
        }
        pixel_at(LabelT, labelim, r, c) = final_label;
    }
}

#include "clcommons/common.h"
#include "clcommons/image.h"

#ifndef PIXELT
#define PIXELT uint
#endif
#ifndef LABELT
#define LABELT uint
#endif
#ifndef CONNECTIVITYPIXELT
#define CONNECTIVITYPIXELT uint
#endif
#ifndef LDSPIXELT
#define LDSPIXELT uint
#endif
#ifndef LDSLABELT
#define LDSLABELT uint
#endif
#ifndef LDSCONNECTIVITYPIXELT
#define LDSCONNECTIVITYPIXELT uint
#endif

typedef PIXELT PixelT;
typedef LDSPIXELT LDSPixelT;
typedef LABELT LabelT;
typedef LDSLABELT LDSLabelT;
typedef CONNECTIVITYPIXELT ConnectivityPixelT;
typedef LDSCONNECTIVITYPIXELT LDSConnectivityPixelT;

#ifndef BG_VALUE
#define BG_VALUE 0
#endif

#ifndef CONNECTIVITY  //4 or 8
#define CONNECTIVITY 8
#endif

#ifndef WORKGROUP_TILE_SIZE_X
#define WORKGROUP_TILE_SIZE_X 32
#endif
#ifndef WORKGROUP_TILE_SIZE_Y
#define WORKGROUP_TILE_SIZE_Y 8
#endif

#ifndef WORKITEM_REPEAT_X
#define WORKITEM_REPEAT_X 1
#endif
#ifndef WORKITEM_REPEAT_Y
#define WORKITEM_REPEAT_Y 4
#endif

#define TILE_COLS (WORKGROUP_TILE_SIZE_X * WORKITEM_REPEAT_X)
#define TILE_ROWS (WORKGROUP_TILE_SIZE_Y * WORKITEM_REPEAT_Y)

enum ConnectivityEnum {
    UP = (1<<0),
    LEFT = (1<<1),
    DOWN = (1<<2),
    RIGHT = (1<<3),
    LEFT_UP = (1<<4),
    LEFT_DOWN = (1<<5),
    RIGHT_UP = (1<<6),
    RIGHT_DOWN = (1<<7)
};

#define isConnected(p1, p2) ((p1) == (p2))

#define pixel_at(type, basename, r, c) image_pixel_at(type, PASTE2(basename, _p), im_rows, im_cols, PASTE2(basename, _pitch), (r), (c))

//global dimensions: divUp(image.cols, block.x), divUp(image.rows, block.y);
//__attribute__((reqd_work_group_size(WORKGROUP_TILE_SIZE_X, WORKGROUP_TILE_SIZE_Y, 1)))
//__kernel void
//make_connectivity_image(uint im_rows, uint im_cols, __global PixelT *image_p, uint image_pitch, __global ConnectivityPixelT *connectivityim_p, uint connectivityim_pitch){
//    const uint c = get_global_id(1);
//    const uint r = get_global_id(0);
//    const bool valid_pixel_task = (c < im_cols) & (r < im_rows);
//
//    if(valid_pixel_task){
//        PixelT pixel = pixel_at(PixelT, image, r, c);
//        ConnectivityPixelT connectivity = 0;
//
//#if CONNECTIVITY == 8
//        connectivity |= c > 0 && r > 0                     && isConnected(pixel, pixel_at(PixelT, image, r-1, c - 1)) ? LEFT_UP : 0
//        connectivity |= c > 0                              && isConnected(pixel, pixel_at(PixelT, image, r, c - 1)) ? LEFT : 0
//        connectivity |= c > 0 && r < im_rows - 1           && isConnected(pixel, pixel_at(PixelT, image, r+1, c - 1)) ? LEFT_DOWN : 0
//        connectivity |=          r < im_rows - 1           && isConnected(pixel, pixel_at(PixelT, image, r+1, c)) ? DOWN : 0
//        connectivity |= c < im_cols - 1 && r < im_rows - 1 && isConnected(pixel, pixel_at(PixelT, image, r+1, c + 1)) ? RIGHT_DOWN : 0
//        connectivity |= c < im_cols - 1                    && isConnected(pixel, pixel_at(PixelT, image, r, c + 1)) ? RIGHT : 0
//        connectivity |= c < im_cols - 1 && r > 0           && isConnected(pixel, pixel_at(PixelT, image, r-1, c + 1)) ? RIGHT_UP : 0
//        connectivity |=          r > 0                     && isConnected(pixel, pixel_at(PixelT, image, r-1, c)) ? UP : 0
//#else
//        connectivity |= c > 0                              && isConnected(pixel, pixel_at(PixelT, image, r, c - 1)) ? LEFT : 0
//        connectivity |=          r < im_rows - 1           && isConnected(pixel, pixel_at(PixelT, image, r+1, c)) ? DOWN : 0
//        connectivity |= c < im_cols - 1                    && isConnected(pixel, pixel_at(PixelT, image, r, c + 1)) ? RIGHT : 0
//        connectivity |=          r > 0                     && isConnected(pixel, pixel_at(PixelT, image, r-1, c)) ? UP : 0
//#endif
//
//        pixel_at(ConnectivityPixelT, connectivity, r, c) = connectivity;
//    }
//}

//global dimensions: divUp(im_cols, workgroup_tile_size_x), divUp(im_rows, workgroup_tile_size_y);
__attribute__((reqd_work_group_size(WORKGROUP_TILE_SIZE_X, WORKGROUP_TILE_SIZE_Y, 1)))
__kernel void
make_connectivity_image(uint im_rows, uint im_cols, __global PixelT *image_p, uint image_pitch, __global ConnectivityPixelT *connectivityim_p, uint connectivityim_pitch){
    const uint tile_col_blocksize = get_local_size(0);
    const uint tile_row_blocksize = get_local_size(1);
    const uint tile_col_block = get_group_id(0) + get_global_offset(0) / tile_col_blocksize;
    const uint tile_row_block = get_group_id(1) + get_global_offset(1) / tile_row_blocksize;
    const uint tile_col = get_local_id(0);
    const uint tile_row = get_local_id(1);
    const bool valid_pixel_task = (get_global_id(0) < im_cols) & (get_global_id(1) < im_rows);

    uint tile_rows = tile_row_blocksize;
    uint tile_cols = tile_col_blocksize;

    const uint tile_row_start = tile_row_blocksize * tile_rows;
    const uint tile_col_start = tile_col_blocksize * tile_cols;
    const uint tile_row_end = min((tile_row_blocksize + 1) * tile_rows, (uint) im_rows);
    const uint tile_col_end = min((tile_col_blocksize + 1) * tile_cols, (uint) im_cols);
    //adjust to true tile dimensions
    tile_rows = tile_row_end - tile_row_start;
    tile_cols = tile_col_end - tile_col_start;
    const uint n_tile_pixels = tile_rows * tile_cols;
    __local LDSPixelT im_tile[WORKGROUP_TILE_SIZE_Y][WORKGROUP_TILE_SIZE_X];
    __local ConnectivityPixelT connectivity_tile[WORKGROUP_TILE_SIZE_Y][WORKGROUP_TILE_SIZE_X];

    const uint tid = tile_row * tile_cols + tile_col;
    for(uint im_tile_fill_task_id = tid; im_tile_fill_task_id < n_tile_pixels; im_tile_fill_task_id += n_tile_pixels){
        const uint im_tile_row = im_tile_fill_task_id / tile_cols;
        const uint im_tile_col = im_tile_fill_task_id % tile_cols;
        const uint g_c = im_tile_col + tile_col_block * tile_col_blocksize;
        const uint g_r = im_tile_row + tile_row_block * tile_row_blocksize;

        im_tile[im_tile_row][im_tile_col] = pixel_at(PixelT, image, g_r, g_c);
    }
    lds_barrier();

    {
        const uint c = get_local_id(1);
        const uint r = get_local_id(0);
        const uint g_c = get_global_id(1);
        const uint g_r = get_global_id(0);
        PixelT pixel = valid_pixel_task ? pixel_at(PixelT, image, r, c) : 0;
        ConnectivityPixelT connectivity = 0;

#if CONNECTIVITY == 8
        connectivity |= c > 0 && r > 0                         && isConnected(pixel, im_tile[r-1][c - 1]) ? LEFT_UP : 0;
        connectivity |= c > 0                                  && isConnected(pixel, im_tile[r][c - 1]  ) ? LEFT : 0;
        connectivity |= c > 0 && r < tile_rows - 1             && isConnected(pixel, im_tile[r+1][c - 1]) ? LEFT_DOWN : 0;
        connectivity |=          r < tile_rows - 1             && isConnected(pixel, im_tile[r+1][c]    ) ? DOWN : 0;
        connectivity |= c < tile_cols - 1 && r < tile_rows - 1 && isConnected(pixel, im_tile[r+1][c + 1]) ? RIGHT_DOWN : 0;
        connectivity |= c < tile_cols - 1                      && isConnected(pixel, im_tile[r][c + 1]  ) ? RIGHT : 0;
        connectivity |= c < tile_cols - 1 && r > 0             && isConnected(pixel, im_tile[r-1][c + 1]) ? RIGHT_UP : 0;
        connectivity |=          r > 0                         && isConnected(pixel, im_tile[r-1][c]    ) ? UP : 0;
#else
        connectivity |= c > 0                                  && isConnected(pixel, im_tile[r][c - 1]) ? LEFT : 0;
        connectivity |=          r < tile_rows - 1             && isConnected(pixel, im_tile[r+1][c]  ) ? DOWN : 0;
        connectivity |= c < tile_cols - 1                      && isConnected(pixel, im_tile[r][c + 1]) ? RIGHT : 0;
        connectivity |=          r > 0                         && isConnected(pixel, im_tile[r-1][c]  ) ? UP : 0;
#endif
        connectivity_tile[r][c] = connectivity;

    }
    lds_barrier();

    for(uint im_tile_fill_task_id = tid; im_tile_fill_task_id < n_tile_pixels; im_tile_fill_task_id += n_tile_pixels){
        const uint im_tile_row = im_tile_fill_task_id / tile_cols;
        const uint im_tile_col = im_tile_fill_task_id % tile_cols;
        const uint g_c = im_tile_col + tile_col_block * tile_col_blocksize;
        const uint g_r = im_tile_row + tile_row_block * tile_row_blocksize;

        pixel_at(ConnectivityPixelT, connectivityim, g_r, g_c) = connectivity_tile[im_tile_row][im_tile_col];
    }
}

__attribute__((reqd_work_group_size(WORKGROUP_TILE_SIZE_X, WORKGROUP_TILE_SIZE_Y, 1)))
__kernel void
label_tiles(uint im_rows, uint im_cols, __global ConnectivityPixelT *labelim_p, uint labelim_pitch, __global ConnectivityPixelT *connectivityim_p, uint connectivityim_pitch){
    const uint x = get_local_id(0) + get_group_id(0) * TILE_COLS;
    const uint y = get_local_id(1) + get_group_id(1) * TILE_ROWS;

    //if (x >= im_rows || y >= im_rows) return;

    //currently x is 1
    //int bounds = ((y + WORKITEM_REPEAT_Y) < im_rows);

    __local LDSLabelT label_tile_im[TILE_ROWS][TILE_COLS];
    __local LDSConnectivityPixelT  edge_tile_im[TILE_ROWS][TILE_COLS];

    LDSLabelT new_labels[WORKITEM_REPEAT_Y][WORKITEM_REPEAT_X];
    LDSLabelT old_labels[WORKITEM_REPEAT_Y][WORKITEM_REPEAT_X];

    #pragma unroll
    for (int i = 0; i < WORKITEM_REPEAT_Y; ++i){
        #pragma unroll
        for (int j = 0; j < WORKITEM_REPEAT_X; ++j){
            const uint yloc = get_local_id(1) + WORKGROUP_TILE_SIZE_Y * i;
            const uint xloc = get_local_id(0) + WORKGROUP_TILE_SIZE_X * j;
            const bool valid_pixel_task = (xloc < im_cols) & (yloc < im_rows);
            ConnectivityPixelT c = valid_pixel_task ? pixel_at(ConnectivityPixelT, connectivityim, y + WORKGROUP_TILE_SIZE_Y * i, x + WORKGROUP_TILE_SIZE_X * j) : 0;

            c = xloc == 0 ? c & ~(LEFT|LEFT_DOWN|LEFT_UP) : c;
            c = yloc == 0 ? c & ~(UP|LEFT_UP|RIGHT_UP) : c;

            c = xloc == TILE_COLS - 1 ? c & ~(RIGHT|RIGHT_DOWN|RIGHT_UP) : c;
            c = yloc == TILE_ROWS - 1 ? c & ~(DOWN|LEFT_DOWN|RIGHT_DOWN) : c;

            new_labels[i][j] = yloc * TILE_COLS + xloc;
            edge_tile_im[yloc][xloc] = c;
        }
    }

    for (int k = 0; ;++k){
        //make copies
        #pragma unroll
        for (int i = 0; i < WORKITEM_REPEAT_Y; ++i){
            #pragma unroll
            for (int j = 0; j < WORKITEM_REPEAT_X; ++j){
                const uint yloc = get_local_id(1) + WORKGROUP_TILE_SIZE_Y * i;
                const uint xloc = get_local_id(0) + WORKGROUP_TILE_SIZE_X * j;

                old_labels[i][j]          = new_labels[i][j];
                label_tile_im[yloc][xloc] = new_labels[i][j];
            }
        }
        lds_barrier();

        //take minimum label of local neighboorhood - single writer, multi reader version
        #pragma unroll
        for (int i = 0; i < WORKITEM_REPEAT_Y; ++i){
            #pragma unroll
            for (int j = 0; j < WORKITEM_REPEAT_X; ++j){
                const uint yloc = get_local_id(1) + WORKGROUP_TILE_SIZE_Y * i;
                const uint xloc = get_local_id(0) + WORKGROUP_TILE_SIZE_X * j;

                const ConnectivityPixelT connectivity = edge_tile_im[yloc][xloc];
                LDSLabelT label = new_labels[i][j];

#if CONNECTIVITY == 8
                if (connectivity & UP)
                   label = min(label, label_tile_im[yloc - 1][xloc]);
                if (connectivity & LEFT)
                   label = min(label, label_tile_im[yloc][xloc - 1]);
                if (connectivity & LEFT_UP)
                   label = min(label, label_tile_im[yloc - 1][xloc - 1]);
                if (connectivity &  LEFT_DOWN)
                   label = min(label, label_tile_im[yloc + 1][xloc - 1]);
                if (connectivity &  DOWN)
                   label = min(label, label_tile_im[yloc + 1][xloc]);
                if (connectivity & RIGHT_DOWN)
                   label = min(label, label_tile_im[yloc - 1][xloc + 1]);
                if (connectivity & RIGHT)
                   label = min(label, label_tile_im[yloc][xloc + 1]);
                if (connectivity & RIGHT_UP)
                   label = min(label, label_tile_im[yloc + 1][xloc + 1]);

#else
                if (connectivity & UP)
                   label = min(label, label_tile_im[yloc - 1][xloc]);
                if (connectivity &  DOWN)
                   label = min(label, label_tile_im[yloc + 1][xloc]);
                if (connectivity & LEFT)
                   label = min(label, label_tile_im[yloc][xloc - 1]);
                if (connectivity & RIGHT)
                   label = min(label, label_tile_im[yloc][xloc + 1]);
#endif

                new_labels[i][j] = label;
            }
        }
        lds_barrier();

        __local int changed;
        if((get_local_id(1) == 0) & (get_local_id(0) == 0)){
            changed = 0;
        }
        lds_barrier();

        __local LDSLabelT *labels = &label_tile_im[0][0];

        int pchanged = 0;
        #pragma unroll
        for(int i = 0; i < WORKITEM_REPEAT_Y; ++i){
            #pragma unroll
            for(int j = 0; j < WORKITEM_REPEAT_X; ++j){
                if(new_labels[i][j] < old_labels[i][j]){
                    pchanged++;
                    atomic_min(labels + old_labels[i][j], new_labels[i][j]);
                }
            }
        }
        atomic_add(&changed, pchanged);
        lds_barrier();

        //if there are no updates, we are finished
        if(!changed){
            break;
        }

        //Compact paths
        #pragma unroll
        for(int i = 0; i < WORKITEM_REPEAT_Y; ++i){
            #pragma unroll
            for(int j = 0; j < WORKITEM_REPEAT_X; ++j){
                LDSLabelT label = new_labels[i][j];

                //find root label
                while(labels[label] < label){
                    label = labels[label];
                }

                new_labels[i][j] = label;
            }
        }
        lds_barrier();
    }

    //save with adjusted global (untiled) labels
    #pragma unroll
    for(int i = 0; i < WORKITEM_REPEAT_Y; ++i){
        #pragma unroll
        for(int j = 0; j < WORKITEM_REPEAT_X; ++j){
            const LabelT tile_label = new_labels[i][j];
            const uint yloc = (tile_label / TILE_COLS) + get_group_id(0) * TILE_COLS;
            const uint xloc = (tile_label % TILE_COLS) + get_group_id(1) * TILE_ROWS;

            const LabelT glabel = yloc * im_cols + xloc;
            const bool valid_pixel_task = (xloc < im_cols) & (yloc < im_rows);
            if(valid_pixel_task){
                pixel_at(LabelT, labelim, yloc, xloc) = glabel;
            }
        }
    }
}

inline
LabelT find_root_global(const __global LabelT *labelim_p, uint labelim_pitch, LabelT label, const uint im_rows, const uint im_cols){
    for(;;){
        const uint y = label / im_cols;
        const uint x = label % im_cols;
        const LabelT parent = pixel_at(LabelT, labelim, y, x);

        if(label == parent){
            break;
        }

        label = parent;
    }
    return label;
}

__kernel
void compact_paths_global(uint im_rows, uint im_cols, __global LabelT *labelim_p, uint labelim_pitch){
    const uint x = get_global_id(0);
    const uint y = get_global_id(1);

    if((x < im_cols) & (y < im_rows)){
        pixel_at(LabelT, labelim, y, x) = find_root_global(labelim_p, labelim_pitch, pixel_at(LabelT, labelim, y, x), im_rows, im_cols);
    }
}

inline
void merge_edge_labels(const uint im_rows, const uint im_cols, __global LabelT *labelim_p, const uint labelim_pitch, const LabelT l1, const LabelT l2, uint *changed){
    const LabelT r1 = find_root_global(labelim_p, labelim_pitch, l1, im_rows, im_cols);
    const LabelT r2 = find_root_global(labelim_p, labelim_pitch, l2, im_rows, im_cols);

    if(r1 == r2){
        return;
    }

    const LabelT mi = min(r1, r2);
    const LabelT ma = max(r1, r2);

    const uint y = ma / im_cols;
    const uint x = ma % im_cols;

    atomic_min(&pixel_at(LabelT, labelim, y, x), mi);
    *changed = true;
}

__kernel
void cross_merge(
    const uint n_vert_tiles, const uint n_horz_tiles,
    uint tile_rows, uint tile_cols,
    const uint im_rows, const uint im_cols,
    const __global ConnectivityPixelT *connectivityim_p, const uint connectivityim_pitch,
    __global LabelT *labelim_p, const uint labelim_pitch,
    const uint yIncomplete, uint xIncomplete
){
    const uint ngroups_x = get_num_groups(0);
    const uint ngroups_y = get_num_groups(1);

    const uint tid = get_local_linear_id();
    const uint stride = get_local_size(1) * get_local_size(0);

    const uint ybegin = get_group_id(1) * (n_vert_tiles * tile_rows);
    uint yend   = ybegin + n_vert_tiles * tile_rows;

    if (get_group_id(1) == ngroups_y - 1){
        yend -= yIncomplete * tile_rows;
        yend -= tile_rows;
        tile_rows = (im_rows % tile_rows);

        yend += tile_rows;
    }

    const uint xbegin = get_group_id(0) * n_horz_tiles * tile_cols;
    uint xend   = xbegin + n_horz_tiles * tile_cols;

    if(get_group_id(0) == ngroups_x - 1){
        if (xIncomplete) yend = ybegin;
        xend -= xIncomplete * tile_cols;
        xend -= tile_cols;
        tile_cols = (im_cols % tile_cols);

        xend += tile_cols;
    }

    if (get_group_id(1) == (ngroups_y - 1) && yIncomplete){
        xend = xbegin;
    }

    const uint tasksV = (n_horz_tiles - 1) * (yend - ybegin);
    const uint tasksH = (n_vert_tiles - 1) * (xend - xbegin);

    const uint total = tasksH + tasksV;

    __local uint changed;
    uint pchanged;
    do{
        pchanged = false;
        for(uint taskIdx = tid; taskIdx < total; taskIdx += stride){
            if(taskIdx < tasksH){
                const uint indexH = taskIdx;

                const uint row = indexH / (xend - xbegin);
                const uint col = indexH % (xend - xbegin);

                const uint y = ybegin + (row + 1) * tile_rows;
                const uint x = xbegin + col;

                const ConnectivityPixelT e = pixel_at(ConnectivityPixelT, connectivityim, y, x);
                if(e & UP){
                    const LabelT lc = pixel_at(LabelT, labelim, y, x);
                    const LabelT lu = pixel_at(LabelT, labelim, y - 1, x);
                    merge_edge_labels(im_rows, im_cols, labelim_p, labelim_pitch, lc, lu, &pchanged);
                }
            }else{
                const uint indexV = taskIdx - tasksH;

                const uint col = indexV / (yend - ybegin);
                const uint row = indexV % (yend - ybegin);

                const uint x = xbegin + (col + 1) * tile_cols;
                const uint y = ybegin + row;

                const ConnectivityPixelT e = pixel_at(ConnectivityPixelT, connectivityim, y, x);
                if(e & LEFT){
                    const LabelT lc = pixel_at(LabelT, labelim, y, x);
                    const LabelT lu = pixel_at(LabelT, labelim, y, x - 1);
                    merge_edge_labels(im_rows, im_cols, labelim_p, labelim_pitch, lc, lu, &pchanged);
                }
            }
        }
        atomic_add(&changed, pchanged);
        lds_barrier();
        pchanged = changed;
    }while(pchanged);
}

__kernel
void mark_root_classes(
    uint im_rows, uint im_cols,
    __global PixelT *image_p, uint image_pitch,
    __global const LabelT* labelim_p, const uint labelim_pitch,
    __global uint* is_root_class_image_p, const uint is_root_class_image_pitch
){
    const uint c = get_global_id(0);
    const uint r = get_global_id(1);
    const bool valid_pixel_task = (c < im_cols) & (r < im_rows);

    const uint linear_index = c + r * im_cols;
    if(valid_pixel_task){
        const PixelT pixel = pixel_at(PixelT, image, r, c);
        const LabelT label = pixel_at(LabelT, labelim, r, c);
        pixel_at(uint, is_root_class_image, r, c) = (pixel != BG_VALUE) & (label == linear_index);
    }
}

__kernel
void relabel_with_scanline_order(
    uint im_rows, uint im_cols,
    __global LabelT* labelim_out_p, const uint labelim_out_pitch,
    __global const PixelT* image_p, const uint image_pitch,
    __global const LabelT* labelim_p, const uint labelim_pitch,
    __global uint* scanline_prefix_sum_of_root_classes_p, const uint scanline_prefix_sum_of_root_classes_pitch
){
    const uint c = get_global_id(0);
    const uint r = get_global_id(1);
    const bool valid_pixel_task = (c < im_cols) & (r < im_rows);
    const uint linear_index = c + r * im_cols;

    if(valid_pixel_task){
        const LabelT pixel = pixel_at(PixelT, image, r, c);
        LabelT final_label = 0;
        if(pixel != BG_VALUE){
            const LabelT label = pixel_at(LabelT, labelim, r, c);
            const uint label_r = label / im_cols;
            const uint label_c = label % im_cols;
            const uint scan_id = pixel_at(uint, scanline_prefix_sum_of_root_classes, label_r, label_c);
            final_label = scan_id + 1;
        }
        pixel_at(LabelT, labelim, r, c) = final_label;
    }
}


Better CL code. Same bugs,,,

Better CL code. Same bugs,,,

#include <Math.cl>
/*
//Find the intersection distance between a ray and a plane
//made from three poits

//check the intersection between a ray and a triangle
//place output data into the hit pointer

RaycastHit* raycast(Ray* ray, int object_count, Object* objects[MAX_OBJECT_COUNT]) {
    RaycastHit* hit = 0;
    float dist = INFINITY;

    for (int index = 0; index < object_count; index++) {
        Mesh* mesh = objects[index]->mesh;
        Object* object = objects[index];

        for (int tris = 0; tris < mesh->triangle_count; tris++) {
            float3 A = mult_matrix(&object->matrix, (float4)(*mesh->vertices[tris*3], 0)).xyz;
            float3 B = mult_matrix(&object->matrix, (float4)(*mesh->vertices[tris*3 + 1], 0)).xyz;
            float3 C = mult_matrix(&object->matrix, (float4)(*mesh->vertices[tris*3 + 2], 0)).xyz;

            float hitdist = ray_plane(ray, A, B, C);
            if (hitdist < dist) {
                RaycastHit hit2;
                if (ray_triangle_check(ray, A, B, C, hitdist, &hit2)) {
                    hit2.triangle_index = tris;
                    hit2.object = objects[index];
                    hit = &hit2;
                }
            }
        }
    }

    return hit;
}

bool in_triangle(float3 point, float3 A, float3 B, float3 C) {
    float area = magnitude(cross(B - A, C - A));

    float3 QA = A - point;
    float3 QB = B - point;
    float3 QC = C - point;
    float value = (magnitude(cross(QB, QC)) +
                   magnitude(cross(QA, QC)) +
                   magnitude(cross(QA, QB)))/area;
    return round_float(value, 6) == 1;
}

void trace(__write_only image2d_t renderTexture,
           int2 pos, float16 *projection_matrix,
           int object_count, Object* objects[MAX_OBJECT_COUNT]) {
    Ray ray;
    ray.origin = mult_matrix(projection_matrix, (float4)(0, 0, 0, 1)).xyz;
    ray.direction = mult_matrix(projection_matrix, (float4)((float)pos.x, (float)pos.y, 1, 0)).xyz;

    RaycastHit* hit = raycast(&ray, object_count, objects);

    if (hit) {
        write_imagef(renderTexture, pos, (float4)(1, 0, 0, 1));
    }
    else {
        write_imagef(renderTexture, pos, (float4)(0, 1, 0, 1));
    }

    if (hit) {
        int iteration = 0;
        float4 endColor = (float4)(0, 0, 0, 1);
        float strength = 1;

        while (hit && iteration < 4) {
            float4 color;
            //ambient
            color = hit->object->material->emission + hit->object->material->ambient;

            //lighting
            float4 light = light_trace(hit->point + (float)0.00001*hit->normal, lights);
            color += hit->object->material->diffuse;////////// *light;

            endColor += color*strength;
            if (false) { //reflection
                hit = raycast(&ray, objects);
            }
            else {
                break;
            }
            iteration += 1;
        }
        write_imagef(renderTexture, pos, endColor);
    }*/
//}

//A vertex is made up of a position, a normal and a UV coordinate
typedef struct {
    float4 position;
    float4 normal;
    float2 uv;
} Vertex;

//A camera is made up of it's position and it's orientation
typedef struct {
    float4 position;
    float4 forward;
    float4 up;
    float4 right;
} Camera;

//A Ray-Hit point is made up of it's intersection point and information about it.
typedef struct {
    float4 point;
    float4 normal;
    float4 bary;
    float dist;
} Hit;

//A ray is simply a point and a direction
typedef struct {
    float4 origin;
    float4 direction;
} Ray;

//Intersect a ray and a plane made of three points
float ray_plane(Ray* ray, float4 A, float4 B, float4 C, Hit *hit) {
    hit->normal = cross(C - A, B - A);
    float dotDirection = dot(hit->normal, ray->direction);
    //Return the distance from the origin of the ray to the point of intersation
    return -dot(A - ray->origin, hit->normal) / dotDirection; // < INFINITY

    //Filter out planes facing the other direction (No 2 sided faces)
    if (dotDirection > 0) {
        return -dot(ray->origin, hit->normal + A) / dotDirection;
    }
    else {
        return INFINITY;
    }
}

//Check whether an intersection point is inside the triangle A, B, C
//Intersection point is dist along the ray.
void ray_triangle_check(Ray* ray, float4 A, float4 B, float4 C, float dist, Hit* hit) {
    hit->point = ray->origin + ray->direction*dist;
    float area = length(hit->normal);

    //Optimisation
    float4 QA = A - hit->point;
    float4 QB = B - hit->point;
    float4 QC = C - hit->point;

    //Barycentric intersection test
    hit->bary.x = length(cross(QB, QC))/area;
    hit->bary.y = length(cross(QA, QC))/area;
    hit->bary.z = length(cross(QA, QB))/area;
    //6 decimal precision. Not very good, but good enough
    hit->bary.w = round_float(hit->bary.x + hit->bary.y + hit->bary.z, 6);
    hit->dist = dist;
    //Check for bary intersection
    if (hit->bary.w == 1.0) {
        hit->dist = dist;
    }
}

bool raycast(Ray* ray, __constant Vertex *vertices, int vertices_len, Hit *hit) {
    hit->dist = INFINITY;

    //Iterate through triangles
    for (int index = 0; index < vertices_len; index += 3) {
        //Get vertex coordiates of every triangle
        float4 A = vertices[index + 0].position;//mult_matrix(&object->matrix, (float4)(*mesh->vertices[tris*3], 0)).xyz;
        float4 B = vertices[index + 1].position;//mult_matrix(&object->matrix, (float4)(*mesh->vertices[tris*3 + 1], 0)).xyz;
        float4 C = vertices[index + 2].position;//mult_matrix(&object->matrix, (float4)(*mesh->vertices[tris*3 + 2], 0)).xyz;

        //Get the hitdistace from the plane A, B, C
        float hitdist = ray_plane(ray, A, B, C, hit);

        if (hitdist < hit->dist) {
            //Check for triangle intersection
            //ray_triangle_check(ray, A, B, C, hitdist, hit);
            hit->dist = hitdist;
        }
    }

    //Return whether or not the ray hit anything
    if (hit->dist < INFINITY) {
        return true;
    }
    return false;
}

__kernel void raytrace(__write_only image2d_t renderTexture,
                       Camera camera,
                       __constant Vertex *vertices, int vertices_len) {
    //texture position
    int2 pos = (int2)(get_global_id(0), get_global_id(1));
    //texture size
    float2 size = (float2)(get_global_size(0), get_global_size(1));
    //uv coordinates
    float2 npos = (float2)(pos.x/size.x - 0.5, pos.y/size.y - 0.5);

    Ray ray;
    ray.origin = camera.position;
    ray.direction = camera.forward + npos.y*camera.up + npos.x*camera.right;

    float4 color = (float4)(0.0, 0.0, 0.0, 1.0);

    Hit hit;

    //Do raytracing
    if (raycast(&ray, vertices, vertices_len, &hit)) {
        color = (float4)(1.0, 1.0, 1.0, 1.0);
    }

    //color = (ray.direction + (float4)(1.0, 1.0, 1.0, 1.0))/2;

    write_imagef(renderTexture, pos, color);
}

#include <Math.cl>
/*
//Find the intersection distance between a ray and a plane
//made from three poits

//check the intersection between a ray and a triangle
//place output data into the hit pointer

RaycastHit* raycast(Ray* ray, int object_count, Object* objects[MAX_OBJECT_COUNT]) {
    RaycastHit* hit = 0;
    float dist = INFINITY;

    for (int index = 0; index < object_count; index++) {
        Mesh* mesh = objects[index]->mesh;
        Object* object = objects[index];

        for (int tris = 0; tris < mesh->triangle_count; tris++) {
            float3 A = mult_matrix(&object->matrix, (float4)(*mesh->vertices[tris*3], 0)).xyz;
            float3 B = mult_matrix(&object->matrix, (float4)(*mesh->vertices[tris*3 + 1], 0)).xyz;
            float3 C = mult_matrix(&object->matrix, (float4)(*mesh->vertices[tris*3 + 2], 0)).xyz;

            float hitdist = ray_plane(ray, A, B, C);
            if (hitdist < dist) {
                RaycastHit hit2;
                if (ray_triangle_check(ray, A, B, C, hitdist, &hit2)) {
                    hit2.triangle_index = tris;
                    hit2.object = objects[index];
                    hit = &hit2;
                }
            }
        }
    }

    return hit;
}

bool in_triangle(float3 point, float3 A, float3 B, float3 C) {
    float area = magnitude(cross(B - A, C - A));

    float3 QA = A - point;
    float3 QB = B - point;
    float3 QC = C - point;
    float value = (magnitude(cross(QB, QC)) +
                   magnitude(cross(QA, QC)) +
                   magnitude(cross(QA, QB)))/area;
    return round_float(value, 6) == 1;
}

void trace(__write_only image2d_t renderTexture,
           int2 pos, float16 *projection_matrix,
           int object_count, Object* objects[MAX_OBJECT_COUNT]) {
    Ray ray;
    ray.origin = mult_matrix(projection_matrix, (float4)(0, 0, 0, 1)).xyz;
    ray.direction = mult_matrix(projection_matrix, (float4)((float)pos.x, (float)pos.y, 1, 0)).xyz;

    RaycastHit* hit = raycast(&ray, object_count, objects);

    if (hit) {
        write_imagef(renderTexture, pos, (float4)(1, 0, 0, 1));
    }
    else {
        write_imagef(renderTexture, pos, (float4)(0, 1, 0, 1));
    }

    if (hit) {
        int iteration = 0;
        float4 endColor = (float4)(0, 0, 0, 1);
        float strength = 1;

        while (hit && iteration < 4) {
            float4 color;
            //ambient
            color = hit->object->material->emission + hit->object->material->ambient;

            //lighting
            float4 light = light_trace(hit->point + (float)0.00001*hit->normal, lights);
            color += hit->object->material->diffuse;////////// *light;

            endColor += color*strength;
            if (false) { //reflection
                hit = raycast(&ray, objects);
            }
            else {
                break;
            }
            iteration += 1;
        }
        write_imagef(renderTexture, pos, endColor);
    }*/
//}

typedef struct {
    float4 position;
    float4 normal;
    float2 uv;
} Vertex;

typedef struct {
    float4 position;
    float4 forward;
    float4 up;
    float4 right;
} Camera;

typedef struct {
    float4 point;
    float4 normal;
    float4 bary;
    float dist;
} Hit;

typedef struct {
    float4 origin;
    float4 direction;
} Ray;

float ray_plane(Ray* ray, float4 A, float4 B, float4 C, Hit *hit) {
    hit->normal = -cross(C - A, B - A);
    float dotDirection = dot(hit->normal, ray->direction);
    return -dot(ray->origin, hit->normal + A) / dotDirection;

    if (dotDirection > 0) {
        return -dot(ray->origin, hit->normal + A) / dotDirection;
    }
    else {
        return INFINITY;
    }
}

void ray_triangle_check(Ray* ray, float4 A, float4 B, float4 C, float dist, Hit* hit) {
    hit->point = ray->origin + ray->direction*dist;
    float area = length(hit->normal);

    float4 QA = A - hit->point;
    float4 QB = B - hit->point;
    float4 QC = C - hit->point;
    hit->bary.x = length(cross(QB, QC))/area;
    hit->bary.y = length(cross(QA, QC))/area;
    hit->bary.z = length(cross(QA, QB))/area;
    hit->bary.w = round_float(hit->bary.x + hit->bary.y + hit->bary.z, 6);
    hit->dist = dist;
    if (hit->bary.w == 1.0) {
        hit->dist = dist;
    }
}

bool raycast(Ray* ray, __constant Vertex *vertices, int vertices_len, Hit *hit) {
    hit->dist = INFINITY;

    for (int index = 0; index < vertices_len; index += 3) {
        float4 A = vertices[index + 0].position;//mult_matrix(&object->matrix, (float4)(*mesh->vertices[tris*3], 0)).xyz;
        float4 B = vertices[index + 1].position;//mult_matrix(&object->matrix, (float4)(*mesh->vertices[tris*3 + 1], 0)).xyz;
        float4 C = vertices[index + 2].position;//mult_matrix(&object->matrix, (float4)(*mesh->vertices[tris*3 + 2], 0)).xyz;

        float hitdist = ray_plane(ray, A, B, C, hit);

        if (hitdist < hit->dist) {
            ray_triangle_check(ray, A, B, C, hitdist, hit);
        }
    }

    if (hit->dist < INFINITY) {
        return true;
    }
    return false;
}

__kernel void raytrace(__write_only image2d_t renderTexture,
                       Camera camera,
                       __constant Vertex *vertices, int vertices_len) {
    //texture position
    int2 pos = (int2)(get_global_id(0), get_global_id(1));
    //texture size
    float2 size = (float2)(get_global_size(0), get_global_size(1));
    //uv coordinates
    float2 npos = (float2)(pos.x/size.x - 0.5, pos.y/size.y - 0.5);

    Ray ray;
    ray.origin = camera.position;
    ray.direction = camera.forward + npos.y*camera.up + npos.x*camera.right;

    float4 color = (float4)(0.0, 0.0, 0.0, 1.0);

    Hit hit;

    if (raycast(&ray, vertices, vertices_len, &hit)) {
        color = (float4)(1.0, 1.0, 1.0, 1.0);
    }

    //color = (ray.direction + (float4)(1.0, 1.0, 1.0, 1.0))/2;

    write_imagef(renderTexture, pos, color);
}


some opts in generic kernel

some opts in generic kernel


plexfix: ignore /tmp/

plexfix: ignore /tmp/

Instead of trying to figure out which torrents to ignore, for the
purposes of plexfix, just ignore everything in directories matching
"/tmp/".

;; plexfix :: fix filenames for Plex Media Server

(eval-when (compile eval load)
  ;;(require :anydate)
  (require :list2)
  (require :acldns)
  (require :smtp)
  (require :osi)
  (require :shell)
  (require :autozoom)
  (require :tget-utils "utils.fasl"))

(defpackage :cl-user
  (:use #:common-lisp
	#:excl
	#:excl.osi
	#:excl.shell
	#:net.post-office))

(in-package :cl-user)

(defvar *debug* nil)
(defvar *no-execute* nil)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defun plexfix (filename &key (stream t)
			      ((:no-execute *no-execute*) *no-execute*))
  (if* (not (probe-file filename))
     then (.error "~a does not exist." filename)
   elseif (file-directory-p filename)
     then (let ((files (directory (pathname-as-directory filename)))
		rar)
	    (if* (setq rar
		   (member "rar" files :key #'pathname-type :test #'string=))
	       then (setq rar (car rar))
		    (format stream "extracting ~a...~%" (file-namestring rar))
		    (excl.osi:command-output
		     (format nil "unrar e ~a ./" (file-namestring rar))
		     :directory (directory-namestring rar))
	       else (dolist (file files)
		      ;; handle mp4 and mkv files, for now, warn of the
		      ;; rest, ignoring some known files
		      (if* (member (pathname-type file) '("mp4" "mkv")
				   :test #'string=)
			 then (plexfix-1 stream file)
		       elseif (member (pathname-type file)
				      '("nfo" "txt" "srt")
				      :test #'string=)
			 thenret
			 else (format stream "Unknown file:~a.~%" file)))))
   elseif (ignore-file-p filename)
     thenret
     else (plexfix-1 stream filename)))

(defun ignore-file-p (filename)
  ;; don't fix anything in /tmp/... since they almost never need fixing
  ;; and plexfix would make more of a mess.  This makes all the previous
  ;; ignore rules obsolete.
  (match-re "/tmp/" (namestring filename) :return nil))

(defun plexfix-1 (stream filename &aux new-name type)
  (multiple-value-bind (series-name season episode pms-fail year month day)
      (parse-name-season-and-episode (file-namestring filename)
				     :episode-required t
				     :junk-allowed t)
    (when (not pms-fail) (return-from plexfix-1 nil))

    ;; PMS will not see this file, so make a symlink for it that it will
    ;; see.
    (setq type (pathname-type filename))
    (setq new-name
      (if* year
	 then ;; date-based name
	      (format nil "~a.~d.~2,'0d.~2,'0d.~a"
		      series-name year month day type)
       elseif (consp episode)
	 then (format nil "~a.S~2,'0dE~2,'0dE~2,'0d.~a"
		      series-name season (car episode) (cdr episode) type)
	 else (format nil "~a.S~2,'0dE~2,'0d.~a"
		      series-name season episode type)))
    (setq new-name (merge-pathnames new-name filename))
    (with-verbosity 1
      (format stream "OLD name: ~a~%" filename)
      (format stream "NEW name: ~a~%" new-name))

    (if* *no-execute*
       then (format stream "Would symlink ~a ~a~%" filename new-name)
       else (make-symlink filename new-name))

    ;; Look for .srt file with the same name, and make a symlink for that,
    ;; too.  The Roku can't use subtitles unless the file has the same
    ;; name!
    (let ((srt (merge-pathnames (make-pathname :type "srt")
				filename))
	  new-srt)
      (when (probe-file srt)
	(with-verbosity 1
	  (format stream "Making link for srt file, too.~%"))
	(setq new-srt (merge-pathnames (make-pathname :type "srt") new-name))
	(if* *no-execute*
	   then (format stream "Would symlink ~a ~a~%" srt new-srt)
	   else (make-symlink srt new-srt))))))

(defun make-symlink (target link-name)
  ;; like symlink, but the link-name is relative, meaning we need to change
  ;; to the directory first
  (chdir (path-pathname link-name))
  (symlink (file-namestring target) (file-namestring link-name)
	   :raw t))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(define-condition plexfix (error) ())

(defun .error (format-string &rest format-arguments)
  ;; This separates known tget errors from unexpected program errors.  All
  ;; calls to error in this code should be to this function.  Any calls to
  ;; error or cerror cause a stack trace.
  (error 'plexfix :format-control format-string
	 :format-arguments format-arguments))

(defun user::main (&aux (stream t))
  (flet
      ((send-output ()
	 (when (not (eq 't stream))
	   (let ((body (get-output-stream-string stream)))
	     (when (string/= "" body)
	       (ignore-errors
		(send-letter
;;;;TODO: parameterize:
		 "192.168.0.1"
		 (sys:getenv "USER")
		 (sys:getenv "USER")
		 body
		 :subject "plexfix"))))))
       (doit (&aux torrent-dir torrent-name)
	 (system:with-command-line-arguments
	     (("d" :short debug)
	      ("m" :short no-email)
	      ("n" :short no-execute)
	      ("q" :short quiet)
	      ("v" :short verbose :allow-multiple-options))
	     (rest)
	   (if* (and (null rest)
		     (setq torrent-dir (sys:getenv "TR_TORRENT_DIR"))
		     (setq torrent-name (sys:getenv "TR_TORRENT_NAME")))
	      then ;; called from Transmission
		   (setq rest
		     (list
		      (namestring
		       (merge-pathnames torrent-name
					(pathname-as-directory torrent-dir)))))
	    elseif (or (null rest) (/= 1 (length rest)))
	      then (.error "usage: plexfix file."))
	   (when debug (setq *debug* t))
	   (when no-execute (setq *no-execute* t))
	   (setq *verbose* (or verbose
			       (when quiet 0)
			       1))
	   (when (and (not debug) (not no-email))
	     (setq stream (make-string-output-stream)))
	   (plexfix (car rest) :stream stream))))

    (if* *debug* ;; -d on command line doesn't effect this test!
       then (format t ";;;NOTE: debugging mode is on~%")
	    (doit)
       else (top-level.debug:with-auto-zoom-and-exit (*standard-output*)
	      (handler-case (doit)
		(plexfix (c)
		  ;; 'plexfix errors don't get a backtrace, since those are
		  ;; expected or, at least, planned for.  The unexpected
		  ;; ones get the zoom.
		  (format stream "~&~a~&" c)
		  (send-output)
		  (exit 1 :quiet t)))))
    (send-output)
    (exit 0 :quiet t)))

;; plexfix :: fix filenames for Plex Media Server
;;
;; TODO:
;;  - handle rar files

(eval-when (compile eval load)
  ;;(require :anydate)
  (require :list2)
  (require :acldns)
  (require :smtp)
  (require :osi)
  (require :shell)
  (require :autozoom)
  (require :tget-utils "utils.fasl"))

(defpackage :cl-user
  (:use #:common-lisp
	#:excl
	#:excl.osi
	#:excl.shell
	#:net.post-office))

(in-package :cl-user)

(defvar *debug* nil)
(defvar *no-execute* nil)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defun plexfix (filename &key (stream t)
			      ((:no-execute *no-execute*) *no-execute*))
  (if* (not (probe-file filename))
     then (.error "~a does not exist." filename)
   elseif (file-directory-p filename)
     then (let ((files (directory (pathname-as-directory filename)))
		rar)
	    (if* (setq rar
		   (member "rar" files :key #'pathname-type :test #'string=))
	       then (setq rar (car rar))
		    (format stream "extracting ~a...~%" (file-namestring rar))
		    (excl.osi:command-output
		     (format nil "unrar e ~a ./" (file-namestring rar))
		     :directory (directory-namestring rar))
	       else (dolist (file files)
		      ;; handle mp4 and mkv files, for now, warn of the
		      ;; rest, ignoring some known files
		      (if* (member (pathname-type file) '("mp4" "mkv")
				   :test #'string=)
			 then (plexfix-1 stream file)
		       elseif (member (pathname-type file)
				      '("nfo" "txt" "srt")
				      :test #'string=)
			 thenret
			 else (format stream "Unknown file:~a.~%" file)))))
   elseif (ignore-file-p filename)
     thenret
     else (plexfix-1 stream filename)))

(defun ignore-file-p (filename)
  (or (match-re "^ufc" (file-namestring filename) :return nil :case-fold t)
      (match-re "^bellator" (file-namestring filename) :return nil
		:case-fold t)))

(defun plexfix-1 (stream filename &aux new-name type)
  (multiple-value-bind (series-name season episode pms-fail year month day)
      (parse-name-season-and-episode (file-namestring filename)
				     :episode-required t
				     :junk-allowed t)
    (when (not pms-fail) (return-from plexfix-1 nil))

    ;; PMS will not see this file, so make a symlink for it that it will
    ;; see.
    (setq type (pathname-type filename))
    (setq new-name
      (if* year
	 then ;; date-based name
	      (format nil "~a.~d.~2,'0d.~2,'0d.~a"
		      series-name year month day type)
       elseif (consp episode)
	 then (format nil "~a.S~2,'0dE~2,'0dE~2,'0d.~a"
		      series-name season (car episode) (cdr episode) type)
	 else (format nil "~a.S~2,'0dE~2,'0d.~a"
		      series-name season episode type)))
    (setq new-name (merge-pathnames new-name filename))
    (with-verbosity 1
      (format stream "OLD name: ~a~%" filename)
      (format stream "NEW name: ~a~%" new-name))

    (if* *no-execute*
       then (format stream "Would symlink ~a ~a~%" filename new-name)
       else (make-symlink filename new-name))

    ;; Look for .srt file with the same name, and make a symlink for that,
    ;; too.  The Roku can't use subtitles unless the file has the same
    ;; name!
    (let ((srt (merge-pathnames (make-pathname :type "srt")
				filename))
	  new-srt)
      (when (probe-file srt)
	(with-verbosity 1
	  (format stream "Making link for srt file, too.~%"))
	(setq new-srt (merge-pathnames (make-pathname :type "srt") new-name))
	(if* *no-execute*
	   then (format stream "Would symlink ~a ~a~%" srt new-srt)
	   else (make-symlink srt new-srt))))))

(defun make-symlink (target link-name)
  ;; like symlink, but the link-name is relative, meaning we need to change
  ;; to the directory first
  (chdir (path-pathname link-name))
  (symlink (file-namestring target) (file-namestring link-name)
	   :raw t))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(define-condition plexfix (error) ())

(defun .error (format-string &rest format-arguments)
  ;; This separates known tget errors from unexpected program errors.  All
  ;; calls to error in this code should be to this function.  Any calls to
  ;; error or cerror cause a stack trace.
  (error 'plexfix :format-control format-string
	 :format-arguments format-arguments))

(defun user::main (&aux (stream t))
  (flet
      ((send-output ()
	 (when (not (eq 't stream))
	   (let ((body (get-output-stream-string stream)))
	     (when (string/= "" body)
	       (ignore-errors
		(send-letter
;;;;TODO: parameterize:
		 "192.168.0.1"
		 (sys:getenv "USER")
		 (sys:getenv "USER")
		 body
		 :subject "plexfix"))))))
       (doit (&aux torrent-dir torrent-name)
	 (system:with-command-line-arguments
	     (("d" :short debug)
	      ("m" :short no-email)
	      ("n" :short no-execute)
	      ("q" :short quiet)
	      ("v" :short verbose :allow-multiple-options))
	     (rest)
	   (if* (and (null rest)
		     (setq torrent-dir (sys:getenv "TR_TORRENT_DIR"))
		     (setq torrent-name (sys:getenv "TR_TORRENT_NAME")))
	      then ;; called from Transmission
		   (setq rest
		     (list
		      (namestring
		       (merge-pathnames torrent-name
					(pathname-as-directory torrent-dir)))))
	    elseif (or (null rest) (/= 1 (length rest)))
	      then (.error "usage: plexfix file."))
	   (when debug (setq *debug* t))
	   (when no-execute (setq *no-execute* t))
	   (setq *verbose* (or verbose
			       (when quiet 0)
			       1))
	   (when (and (not debug) (not no-email))
	     (setq stream (make-string-output-stream)))
	   (plexfix (car rest) :stream stream))))

    (if* *debug* ;; -d on command line doesn't effect this test!
       then (format t ";;;NOTE: debugging mode is on~%")
	    (doit)
       else (top-level.debug:with-auto-zoom-and-exit (*standard-output*)
	      (handler-case (doit)
		(plexfix (c)
		  ;; 'plexfix errors don't get a backtrace, since those are
		  ;; expected or, at least, planned for.  The unexpected
		  ;; ones get the zoom.
		  (format stream "~&~a~&" c)
		  (send-output)
		  (exit 1 :quiet t)))))
    (send-output)
    (exit 0 :quiet t)))