7
$\begingroup$

(Apologies: I am cross-posting from SO, where there is no answer. I figured it might be more appropriate to be on this site.)

I'm trying to build a compute shader in OpenGL to perform a skeletonization algorithm. I've tested the algorithm in a CPU only version, and it was correct there. However, I'm having some trouble porting it to compute shader code.

The problem is that no matter how many invocations of the compute shader I run, the output never changes past the first invocation. In fact, if I take out the check at the end of the while loop, the program never terminates.

I have two areas of memory that I use for input and output. I'm trying to do a trick with glBindBufferBase() in the main while loop where I swap the two of them (output of the last round becomes input for the current round). See lines 270 - 318 in main.cpp. This is so I don't copy the data back and forth between the CPU and GPU a bunch of times.

I have also reworked a version of this to memcpy IMG_1 back to data and then call glBufferData with data. It works correctly, but doing all of these CPU <-> GPU operations seems horribly inefficient.

So, my questions are:

1) Can I do this trick with glBindBuffers, where I swap them around so that I can operate on the data multiple times without moving it back to the CPU? In testing on a smaller problem (just adding short arrays), it worked.

2) If the trick is fine, where am I going wrong?

Note: this code needs a 640 x 400 size .pgm (black and white image) called "test.pgm". You can make one in GIMP, but be sure to save it as a binary one instead of an ASCII one.

This code is compiled with the following flags

g++ -g pgm.cpp main.cpp -lglut -lGLU -lGL -lm -lGLEW -o test

Also, forgive me for using C++ but doing C style tricks. I spend more time in C than I do C++.

main.cpp

// Include standard headers
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <math.h>
#include <errno.h>

//For stat
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

// Include GLEW
#include <GL/glew.h>

//Glut
#include <GL/glut.h>

//Project specific
#include "skeletonize.hpp"
#include "PGM.hpp"

// OpenGL shader info
GLuint programID;
GLuint output_image;

#define IMG_0               0
#define IMG_1               1
#define CMD                 2
#define NUM_BUFS            3

#define CMD_BUF_WIDTH       0
#define CMD_BUF_HEIGHT      1
#define CMD_BUF_CMD         2
#define CMD_BUF_RESPONSE    3
#define CMD_BUF_LEN         4

#define CMD_EXPAND          1
#define CMD_THIN_N          2
#define CMD_THIN_S          3
#define CMD_THIN_E          4
#define CMD_THIN_W          5
#define CMD_NORMALIZE       6
#define CMD_REGULARIZE      7

#define INITIALIZED         0
#define NOT_FINISHED        1

GLuint computeProgram;
GLuint buffers[NUM_BUFS];       //SSBO objects, one for IMG_0, one for IMG_1, and one for commands/response
static GLchar* computeSource;
GLuint shaderProgram;


//TODO: don't really need 2 textures yet, but will eventually when doing overlay of original image.
GLuint textures[2];

GLchar* LoadSource(const char* pFile)
{
    struct stat buf;
    GLchar *source;
    int fd;

    if (stat(pFile, &buf) == -1)
    {
        printf("Error opening file\n");
        printf("Error: %s\n", strerror(errno));
        return NULL;
    }

    fd = open(pFile, O_RDONLY);

    if (fd == -1)
    {
        printf("Error opening file. Error: %s\n", strerror(errno));
        return NULL;
    }

    source = new GLchar[buf.st_size + 1];

    if (read(fd, source, buf.st_size) == -1)
    {
        printf("Error reading file. Error: %s\n", strerror(errno));
        delete[] source;
        return NULL;
    }

    source[buf.st_size] = '\0'; //Shader compiler needs null to know end of input
    return source;
}


// Shader sources
const GLchar* vertexSource =
    "#version 450 core\n"
    "in vec2 position;"
    "in vec2 texcoord;"
    "out vec2 Texcoord;"
    "void main()"
    "{"
    "    Texcoord = texcoord;"
    "    gl_Position = vec4(position, 0.0, 1.0);"
    "}";

const GLchar* fragmentSource =
    "#version 450 core\n"
    "in vec2 Texcoord;"
    "out vec4 outColor;"
    "uniform sampler2D texData;"
    "void main()"
    "{"
    "   vec4 imColor = texture(texData, Texcoord);"
    "   outColor = vec4(0.0, imColor.r, 0.0, 1.0);"
    //"    outColor = texture(texData, Texcoord);"
    //"    outColor = vec4(1.0, 1.0, 0.0, 1.0);"
    "}";


void checkError(int line)
{
    GLint err;

    do
    {
        err = glGetError();
        switch (err)
        {
            case GL_NO_ERROR:
                //printf("%d: No error\n", line);
                break;
            case GL_INVALID_ENUM:
                printf("%d: Invalid enum!\n", line);
                break;
            case GL_INVALID_VALUE:
                printf("%d: Invalid value\n", line);
                break;
            case GL_INVALID_OPERATION:
                printf("%d: Invalid operation\n", line);
                break;
            case GL_INVALID_FRAMEBUFFER_OPERATION:
                printf("%d: Invalid framebuffer operation\n", line);
                break;
            case GL_OUT_OF_MEMORY:
                printf("%d: Out of memory\n", line);
                break;
            default:
                printf("%d: glGetError default case. Should not happen!\n", line);
        }
    } while (err != GL_NO_ERROR);
}

void display()
{

    glClearColor(0.0f, 0.0f, 1.0f, 0.0f);

    glClear(GL_COLOR_BUFFER_BIT);

    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

    glFlush();
    glutSwapBuffers();

}


void reshape(int width,int height)
{
    double w2h = (height>0) ? (double)width/height : 1;
    //  Set viewport as entire window
    glViewport(0,0, width,height);

}




void runComputeProgram(uint32_t *data, uint32_t *data2)
{
    int width = 640;
    int height = 400;

    uint32_t *ptr;
    uint32_t cmd[CMD_BUF_LEN];

    computeSource = LoadSource("compute.shader");
    if (computeSource == NULL)
    {
        return;
    }
    GLuint computeShader = glCreateShader(GL_COMPUTE_SHADER);
    glShaderSource(computeShader, 1, &computeSource, NULL);
    glCompileShader(computeShader);

    computeProgram = glCreateProgram();
    glAttachShader(computeProgram, computeShader);
    glLinkProgram(computeProgram);
    GLint status;
    glGetProgramiv(computeProgram, GL_LINK_STATUS, &status);

    if (status == GL_TRUE)
    {
        printf("link good\n");
    }
    else
    {
        printf("link bad\n");
        GLchar log[4096];
        GLsizei len;

        glGetProgramInfoLog(computeProgram, 4096, &len, log);

        printf("%s\n", log);
        return;
    }

    // First action is to transform the image into binary values (0, 1)

    cmd[CMD_BUF_CMD] = CMD_NORMALIZE;
    cmd[CMD_BUF_WIDTH] = width;
    cmd[CMD_BUF_HEIGHT] = height;

    glGenBuffers(NUM_BUFS, buffers);

    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, buffers[CMD]);
    glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(cmd), cmd, GL_DYNAMIC_DRAW);

    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, buffers[IMG_0]);
    glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(uint32_t) * width * height, data, GL_DYNAMIC_DRAW);

    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, buffers[IMG_1]);
    glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(uint32_t) * width * height, data2, GL_DYNAMIC_DRAW);

    glUseProgram(computeProgram);

    glDispatchCompute(width / 16, height / 16, 1);
    glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);

    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, buffers[IMG_1]);
    ptr = (uint32_t *)glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY);

    glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);

    // Rebind ptr for our while loop
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, buffers[CMD]);
    //glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(cmd), cmd, GL_DYNAMIC_DRAW);
    ptr = (uint32_t *)glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY);

    int i = 0;
    do
    {   
        printf("iteration: %d", i);
        glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
        cmd[CMD_BUF_RESPONSE] = INITIALIZED;

        switch (i % 4)
        {
            case 0:
                cmd[CMD_BUF_CMD] = CMD_THIN_N;
                break;
            case 1:
                cmd[CMD_BUF_CMD] = CMD_THIN_S;
                break;
            case 2:
                cmd[CMD_BUF_CMD] = CMD_THIN_E;
                break;
            case 3:
                cmd[CMD_BUF_CMD] = CMD_THIN_W;
                break;
        }        

        glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(cmd), cmd, GL_DYNAMIC_DRAW);

        glDispatchCompute(width / 16, height / 16, 1);
        glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);

        if (i % 2 == 0)
        {
            printf("Input is now img_1. Output is img_0\n");
            glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, buffers[IMG_1]);
            glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, buffers[IMG_0]);
            checkError(__LINE__);
        }
        else
        {
            printf("Input is now img_0. Output is img_1\n");
            glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, buffers[IMG_0]);
            glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, buffers[IMG_1]);
            checkError(__LINE__);
        }

        glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, buffers[CMD]);
        ptr = (uint32_t *)glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY);
        printf("cmd issued at start: %d response: %d\n",  ptr[CMD_BUF_CMD], ptr[CMD_BUF_RESPONSE]);
        i++;
    } while(ptr[CMD_BUF_RESPONSE] != INITIALIZED && i < 10); //Using i < 10, otherwise this never terminates

    glUnmapBuffer(GL_SHADER_STORAGE_BUFFER); // Free ptr

    // Transform Binary image (0, 1) to (0, 0xFFFFFFFF) values for texture display
    cmd[CMD_BUF_CMD] = CMD_REGULARIZE;        
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, buffers[CMD]);
    glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(cmd), cmd, GL_DYNAMIC_DRAW);

    glDispatchCompute(width / 16, height / 16, 1);
    glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);

    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, buffers[CMD]);
    ptr = (uint32_t *)glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY);
    printf("Regularize: cmd: %d  width: %d  height: %d response: %d\n",  ptr[CMD_BUF_CMD], ptr[CMD_BUF_WIDTH], ptr[CMD_BUF_HEIGHT], ptr[CMD_BUF_RESPONSE]);

    // Create texure
    glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);

    glGenTextures(2, textures);
    checkError(__LINE__);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, textures[0]);
        checkError(__LINE__);

    if (i % 2 == 0)
    {
        printf("output image is img_1\n");
        glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, buffers[IMG_1]);
        ptr = (uint32_t *)glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY);
    }
    else
    {        
        printf("output image is img_0\n");
        glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, buffers[IMG_0]);
        ptr = (uint32_t *)glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_READ_ONLY);
    }

    glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);

    glUseProgram(shaderProgram);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0,  GL_RED,  GL_UNSIGNED_INT, ptr);  //TODO: this is wrong. worry about later.
        checkError(__LINE__);

    glUniform1i(glGetUniformLocation(shaderProgram, "texData"), 0);
    checkError(__LINE__);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);





    checkError(__LINE__);
}

void initGL()
{
    // Vertices & texture init

    GLuint vao;
    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);

    GLuint vbo;
    glGenBuffers(1, &vbo);

    GLfloat vertices[] = {
        // X    Y      S    T
        -1.0f,  1.0f, 0.0f, 0.0f, // Top-left
         1.0f,  1.0f, 1.0f, 0.0f, // Top-right
         1.0f, -1.0f, 1.0f, 1.0f, // Bottom-right
        -1.0f, -1.0f, 0.0f, 1.0f  // Bottom-left
    };

    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    GLuint ebo;
    glGenBuffers(1, &ebo);

    GLuint elements[] = {
        0, 1, 2,
        2, 3, 0
    };

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(elements), elements, GL_STATIC_DRAW);

    // Create shaders

    GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexSource, NULL);
    glCompileShader(vertexShader);

    GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentSource, NULL);
    glCompileShader(fragmentShader);

    shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glBindFragDataLocation(shaderProgram, 0, "outColor");
    glLinkProgram(shaderProgram);
    glUseProgram(shaderProgram);

    // Vertex data specification
    GLint posAttrib = glGetAttribLocation(shaderProgram, "position");
    glEnableVertexAttribArray(posAttrib);
    glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), 0);

    GLint texAttrib = glGetAttribLocation(shaderProgram, "texcoord");
    glEnableVertexAttribArray(texAttrib);
    glVertexAttribPointer(texAttrib, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (void *)(2 * sizeof(GLfloat)));
    checkError(__LINE__);



}

int main(int argc, char** argv)
{
    // Image setup

    PGM pgmImage;
    pgmImage.ReadFile("test.pgm");

    uint32_t *data = new uint32_t[pgmImage.GetHeight() * pgmImage.GetWidth()];
    uint32_t *data2 = new uint32_t[pgmImage.GetHeight() * pgmImage.GetWidth()];
    unsigned int size = pgmImage.GetHeight() * pgmImage.GetWidth();
    uint8_t *pgmData = pgmImage.GetData();
    for (int i=0; i < size; i++)
    {
        data[i] = pgmData[i];
    }

    int count = 0;
    for (int i =0; i < pgmImage.GetHeight() * pgmImage.GetWidth(); i++)
    {
        if (data[i] == 0xFF)
        {
            count++;
        }
    }

    printf("count: %d\n", count);

    // Window Setup

    glutInitWindowSize(640, 400);
    glutInitWindowPosition (140, 140);
    glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
    glutInit(&argc, argv);

    glutCreateWindow( "OpenGL Application" );
    glutDisplayFunc(display);
    glutReshapeFunc(reshape);

    glewExperimental = true;
    if (glewInit() != GLEW_OK) {
        fprintf(stderr, "Failed to initialize GLEW\n");
        return -1;
    }

    initGL();
    runComputeProgram(data, data2);
    checkError(__LINE__);
    glutMainLoop();
    return 0;


}

compute.shader

#version 450 core
//#extension GL_NV_shader_buffer_load : enable

#define WIDTH           0    // Width of image
#define HEIGHT          1    // Height of image
#define CMD             2    // Command to execute
#define RESPONSE        3    // Response to command
#define BUF_LEN         4

#define CMD_UNUSED      1    // TODO: remove this. Will have to be mirroed in C code.
#define CMD_THIN_N      2
#define CMD_THIN_S      3
#define CMD_THIN_E      4
#define CMD_THIN_W      5
#define CMD_NORMALIZE   6
#define CMD_REGULARIZE  7

#define NOT_FINISHED 1

layout (local_size_x = 16, local_size_y = 16) in;
//layout (local_size_x = 1) in; //TODO: remove

layout (std430, binding = 0) buffer Cmd {
   uint cmd_buf[BUF_LEN]; //Width, height, command, response
};

layout (std430, binding = 1) buffer Img1 {
   uint image_0[];
};

layout (std430, binding = 2) buffer Img2 {
    uint image_1[];
};


int sigma(uint data[9]) {
    int i;
    int sigma = 0;

    // Assume 9 pixels, A0 (pixel of interest) -> A8
    // In image, A0 is center
    // 1 2 3
    // 8 0 4
    // 7 6 5

    for (i=1; i < 9; i++)
    {
        sigma += int(data[i]);
    }

    return sigma;
}


int chi(uint data[9]) {
    int chi;

    // Assume 9 pixels, A0 (pixel of interest) -> A8
    // 1 2 3
    // 8 0 4
    // 7 6 5

    chi = int(data[1] != data[3]) +
          int(data[3] != data[5]) +
          int(data[5] != data[7]) +
          int(data[7] != data[1]) +

          2 * ( int((data[2] > data[1]) && (data[2] > data[3])) ) +
          int((data[4] > data[3]) && (data[4] > data[5])) +
          int((data[6] > data[5]) && (data[6] > data[7])) +
          int((data[8] > data[7]) && (data[8] > data[1]));

   return chi;
}

// 1 2 3
// 8 0 4
// 7 6 5
int getPos(in int x, int y) {
   return y * int(cmd_buf[WIDTH]) + x;
}

uint getVal(in int pos) {
   return image_0[ uint(pos) ];
}

int removePoint(uint neighborhood[9]) {
    int x = int(gl_GlobalInvocationID.x);
    int y = int(gl_GlobalInvocationID.y);

    if (chi(neighborhood) == 2 && sigma(neighborhood) != 1) {
        image_1[getPos(x, y)] = 0;
        cmd_buf[RESPONSE] = NOT_FINISHED;
        return 1;
    }
    else
    {
        //TODO: needed? Swapping back and forth between input and output should account for this
        image_1[getPos(x,y)] = 1;
    }   
    return 0;
}


void getNeighborhood(inout uint neighborhood[9]) {
    int x = int(gl_GlobalInvocationID.x);
    int y = int(gl_GlobalInvocationID.y);
    int bottom = int(cmd_buf[WIDTH] * (cmd_buf[HEIGHT] - 1));
    int pos = getPos(x, y);
    int width = int(cmd_buf[WIDTH]);
    int height = int(cmd_buf[HEIGHT]);
    uint pixel;
    int i = 0;

    for (i=1; i < 9; i++) {
       neighborhood[i] = 2;
    }

    if (pos < width) {
        // Pixel on top, fill outiside image with zero
       neighborhood[1] = 0;
       neighborhood[2] = 0;
       neighborhood[3] = 0;
    }

    if (pos % width == 0) {
        // Pixel is on left edge. Fill area outside of image with zero
        neighborhood[1] = 0;
        neighborhood[8] = 0;
        neighborhood[7] = 0;
    }

    if ((pos % width) == (width - 1)) {
        // Pixel is on right edge.
        neighborhood[3] = 0;
        neighborhood[4] = 0;
        neighborhood[5] = 0;
    }

    if (pos >= bottom) {
        // Pixel is on bottom edge.
        neighborhood[5] = 0;
        neighborhood[6] = 0;
        neighborhood[7] = 0;
    }

    // Get remaining pixels
    for (i=1; i < 9; i++) {
        if (neighborhood[i] == 2) {
            switch (i) {
                case 1:
                    // Upper left pixel
                    neighborhood[i] = getVal(pos - 1 - width);
                    break;
                case 2:
                    // Upper middle pixel
                    neighborhood[i] = getVal(pos - width);
                    break;
                case 3:
                    // Upper right pixel
                    neighborhood[i] = getVal(pos + 1 - width);
                    break;
                case 4:
                    // Right pixel
                    neighborhood[i] = getVal(pos + 1);
                    break;
                case 5:
                    // Bottom right pixel
                    neighborhood[i] = getVal(pos + width + 1);
                    break;
                case 6:
                    // Bottom middle pixel
                    neighborhood[i] = getVal(pos + width);
                    break;
                case 7:
                    // Bottom left pixel
                    neighborhood[i] = getVal(pos + width - 1);
                    break;
                case 8:
                    // Left pixel
                    neighborhood[i] = getVal(pos - 1);
                    break;
            }
        }
    }
}




void normalize() {
    int x = int(gl_GlobalInvocationID.x);
    int y = int(gl_GlobalInvocationID.y);
    uint val = image_0[getPos(x, y)] == 0x0 ? 0 : 1;

    image_0[getPos(x, y)] = val;
    image_1[getPos(x, y)] = val;

}


void regularize() {
    int x = int(gl_GlobalInvocationID.x);
    int y = int(gl_GlobalInvocationID.y);
    uint val = image_0[getPos(x, y)] == 0x0 ? 0 : 0xFFFFFFFF;

    if (val != 0xFFFFFFFF)
    {
        cmd_buf[RESPONSE] = 99; //Test Value -- TODO: remove
    }

    image_1[getPos(x, y)] = val;
}



// North-South-East-West skeletonization
void skeleton() {
    int x = int(gl_GlobalInvocationID.x);
    int y = int(gl_GlobalInvocationID.y);
    uint neighborhood[9];
    neighborhood[0] = getVal(getPos(x, y));

    // Only consider cases where the center is 1
    if (neighborhood[0] != 1) {
        return;
    }

    getNeighborhood(neighborhood);

    switch (cmd_buf[CMD]) {
        case CMD_THIN_N:
            //north
            if (neighborhood[2] == 0 && neighborhood[6] == 1) {
                removePoint(neighborhood);
            }
            break;
        case CMD_THIN_S:
            //south
            if (neighborhood[2] == 1 && neighborhood[6] == 0) {
                removePoint(neighborhood);
            }
            break;
        case CMD_THIN_E:
            //east
            if (neighborhood[4] == 0 && neighborhood[8] == 1) {
                removePoint(neighborhood);
            }
            break;
        case CMD_THIN_W:
            //west
            if (neighborhood[4] == 1 && neighborhood[8] == 0) {
                removePoint(neighborhood);
            }
            break;
    }
}



void main() {

    switch (cmd_buf[CMD]) {
        case CMD_THIN_N:
        case CMD_THIN_S:
        case CMD_THIN_E:
        case CMD_THIN_W:
            skeleton();
            break;
        case CMD_NORMALIZE:
            normalize();
            break;
        case CMD_REGULARIZE:
            regularize();
            break;
    }
}

pgm.cpp

#include "PGM.hpp"

#define PGM_HEADER "P5"

PGM::PGM()
{
    mpData = NULL;
    Clear();
}

PGM::~PGM()
{
    Clear();
}

uint8_t* PGM::GetData()
{
    return mpImgData;
}


uint16_t PGM::GetWidth()
{
    return mWidth;
}

uint16_t PGM::GetHeight()
{
    return mHeight;
}

uint8_t PGM::GetMaxWhite()
{
    return mMaxWhite;
}

void PGM::Clear()
{
    if (mpData != NULL)
    {
        delete[] mpData;
    }

    mpImgData = NULL;
    mWidth = 0;
    mHeight = 0;
    mMaxWhite = 255;
}

// Finds the 
int PGM::PopulateFields(size_t size)
{
    int i;
    bool EOL = false;
    bool haveWhite = false;
    bool comment = false;

    if (mpData == NULL) { return -1; }

    // Check header
    if ((mpData[0] != 0x50) || (mpData[1] != 0x35)) { return -2; }

    //Ignore the comment
    //Start at 3rd position in file, after "P5" header    
    for (i = 2; i < size; i++)
    {
        if (mpData[i] == '#') 
        { 
            comment = true;
            continue;
        }

        if (mpData[i] == 0x0A && comment == true)
        {
            comment = false;
            break;
        }

        if (comment == true) 
        { 
            continue; 
        }

    }

    // Get width and height
    i++;
    sscanf((char *)&mpData[i], "%4" SCNu16 " %4" SCNu16, &mWidth, &mHeight);

    for (i; i < size; i++)
    {
        //Move past the width and height we just found
        if (mpData[i] == 0x0A && EOL == false)
        {
            EOL = true;
            continue;
        }

        // If past the width and height, now at the range. Save it.
        if (EOL == true && haveWhite == false)
        {
            sscanf((char *)&mpData[i], "%3" SCNu8, &mMaxWhite);
            haveWhite = true;
        }

        if (haveWhite == true && mpData[i] == 0x0A)
        {
            i++; //Move to next element - start of the actual data
            break;
        }
    }

    if (i == size)
    {
        return -3; //Did not find the start of data.
    }

    mpImgData = &mpData[i];

    return 0;
}

// Reads a PGM file. Returns 0 on success, other values on failure
int PGM::ReadFile(const char *pPath)
{
    struct stat st;
    int fd;

    if (this->mpData != NULL)
    {
        Clear();
    }

    if (stat(pPath, &st) != 0)
    {
        return 1;
    }

    fd = open(pPath, O_RDONLY);

    if (fd == -1)
    {
        return 1;
    }

    //this->mpData = (uint8_t *) malloc(st.st_size);
    mpData = new uint8_t[st.st_size];

    if (this->mpData == NULL)
    {
        return 2;
    }

    if (read(fd, this->mpData, st.st_size) == -1)
    {
        Clear();
    }

    close(fd);

    PopulateFields(st.st_size);

    return 0;
}

pgm.hpp

#ifndef __PGM_H__
#define __PGM_H__

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <inttypes.h>

class PGM
{
  public:
    int         ReadFile(const char *pPath);
    uint8_t*    GetData();
    uint16_t    GetWidth();
    uint16_t    GetHeight();
    uint8_t     GetMaxWhite();

    PGM();
    ~PGM();

  private:

    void        Clear();
    int         PopulateFields(size_t size);

    uint8_t     *mpData;
    uint8_t     *mpImgData;
    uint16_t    mWidth;
    uint16_t    mHeight;
    uint8_t     mMaxWhite;

};

#endif // __PGM_H__
$\endgroup$
  • 3
    $\begingroup$ Keeping all the data on the GPU and swapping in/out buffers as you're doing should be fine. It's called "ping-ponging" and is a very common technique in graphics. I can't spot the bug in your code, but your compute shader is quite complicated. I'd advise commenting out everything except a simple write to the buffer and see you can make it work that way. Then gradually build it back up again, testing each piece as you go. That should help narrow down the issue. $\endgroup$ – Nathan Reed Oct 18 '16 at 1:33
  • $\begingroup$ Thanks for looking at it! I'll give it some more investigation. I figured it was a swapping issue since if I print out how many pixels are 0x1 after each iteration, it goes something like (made up numbers) 1000, 900, 860, 890, 860, 890, 860, 890. However, if I just run one part of the switch case over and over, it will decrease monotonically until it can't anymore. Glad to know I'm not crazy about ping-ponging, though! :) $\endgroup$ – Maxthecat Oct 18 '16 at 2:21
  • $\begingroup$ For information: Cross posted from Stack Overflow $\endgroup$ – trichoplax Oct 18 '16 at 11:35
3
$\begingroup$

I found it! The issue stems from a problem in the swap. When we swap buffers, the output buffer becomes the input and the input buffer becomes the output. However, the buffer that is now the output was not updated to match!

To illustrate:

Init:
 In      Out
1 1 1   1 1 1
1 1 1   1 1 1
1 1 1   1 1 1

First Iteration (remove north corners):
 In      Out
1 1 1   0 1 0
1 1 1   1 1 1
1 1 1   1 1 1

Swap 
 In      Out
0 1 0   1 1 1
1 1 1   1 1 1
1 1 1   1 1 1

Second iteration (remove east corners):
 In      Out
0 1 0   1 1 1
1 1 1   1 1 1
1 1 1   1 1 0

... (and so on)

By modifying skeleton() in the compute shader to copy image_0 to image_1, prior to anything else, the algorithm works correctly.

void skeleton() {
    int x = int(gl_GlobalInvocationID.x);
    int y = int(gl_GlobalInvocationID.y);
    uint neighborhood[9];
    neighborhood[0] = getVal(getPos(x, y));

    image_1[getPos(x, y)] = image_0[getPos(x, y)];

    // Only consider cases where the center is 1
    if (neighborhood[0] != 1) {
        image_1[0] = 0;
        return;
    }
    ...

Additionally, I cleaned up the code quite a bit. For example, you can set the in and out buffers via glBindBufferBase() prior to the dispatch call.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.