Wednesday, February 27, 2013

Sprites in the OpenGL Programmable Pipeline

Recently, I was on Stack Overflow and I found an interesting question regarding sprites in the programmable pipeline. I was curious, and did some research. I'm not sure that "sprites" is exactly the right terminology for this, but I believe that this is as close to "sprites" as the programmable pipeline gets. Anyway, I wrote a little test program (for GLX and OpenGL 3.2 and beyond). Hope you find it interesting!

Compile with:
clang++ -DGL_GLEXT_PROTOTYPES -DGLX_GLXEXT_PROTOTYPES -Wall -O0 -ggdb -o main $(pkg-config --libs --cflags gl x11) main.cc


#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <time.h>
#include <inttypes.h>
#include <GL/gl.h>
#include <GL/glext.h>
#include <GL/glx.h>
#include <GL/glxext.h>
#include <X11/Xlib.h>

static int done = 0;

static inline uint64_t monotonicTime() {
  struct timespec ts;
  int res = clock_gettime(CLOCK_MONOTONIC, &ts);
  assert(!res);
  uint64_t b = ts.tv_sec * 1000000;
  return b + ts.tv_nsec / 1000;
}

static int handler(Display *display) {
  done = 1;
  return 0;
}

class OpenGLStuff {
 public:
  OpenGLStuff(const int width, const int height) : point_count(30), circle_count(10) {
    // Print some debug stuff
    printf("%s\n", glGetString(GL_VENDOR));
    printf("%s\n", glGetString(GL_RENDERER));
    printf("%s\n", glGetString(GL_VERSION));
    printf("%s\n", glGetString(GL_SHADING_LANGUAGE_VERSION));
    // Set up some boring first-run stuff
    glViewport(0, 0, width, height);
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glEnable(GL_PROGRAM_POINT_SIZE);
    // I'm actually interested in the maximum sized sprite I can draw is
    float range[2];
    glGetFloatv(GL_POINT_SIZE_RANGE, range);
    printf("Point size range: %f x %f\n", range[0], range[1]);

    const GLchar *vertex_shader_source[] = {
      //"#version 430\n",
      "#version 150\n",
      "\n",
      "in vec4 in_position;\n",
      "uniform int in_point_count;\n",
      "uniform int in_circle_count;\n",
      "uniform int in_time;\n",
      "\n",
      "void main() {\n",
      "  float p = float(gl_InstanceID) / float(in_point_count);\n",
      "  float pi = asin(1.0) * 2.0;\n",
      "  float speed = (1.0 / 2.0) * 1000000.0 * 2.0 * pi;\n",
      "  float radius = 0.1*sin(float(in_time)/(speed/10)) + (float(gl_InstanceID/in_point_count)/float(in_circle_count)) * sin(float(in_time)/speed);\n",
      "  gl_Position = in_position + vec4(radius*sin(2*pi*p), radius*cos(2*pi*p), 0.0, 0.0);\n",
      "  gl_PointSize = (sin(radius*2.0*pi + float(in_time)/(speed*4.0))+1.0)/2.0*63.0*20.0;\n",
      "}\n",
      ""
    };
    const GLchar *fragment_shader_source[] = {
      //"#version 430\n",
      "#version 150\n",
      "\n",
      "out vec4 out_color;\n",
      "\n",
      "void main() {\n",
      "  vec2 c = (gl_PointCoord - vec2(0.5, 0.5)) * 2;\n",
      "  float v = c.x*c.x + c.y*c.y;\n",
      "  if (v < 1.0) {\n",
      "    out_color = (1.0-v*v)*vec4(0.0, gl_PointCoord.y, gl_PointCoord.x, 1.0);\n",
      "  } else {\n",
      "    out_color = vec4(0.0, 0.0, 0.0, 0.0);\n",
      "  }\n",
      "}\n",
      ""
    };

    // Set up shader stuff
    GLint compile_status;
    GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER);
    GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
    program = glCreateProgram();
    glShaderSource(vertex_shader, sizeof(vertex_shader_source)/sizeof(GLchar*), vertex_shader_source, NULL);
    glShaderSource(fragment_shader, sizeof(fragment_shader_source)/sizeof(GLchar*), fragment_shader_source, NULL);
    glCompileShader(vertex_shader);
    glCompileShader(fragment_shader);
    glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &compile_status);
    assert(compile_status == GL_TRUE);
    glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &compile_status);
    assert(compile_status == GL_TRUE);
    glAttachShader(program, vertex_shader);
    glAttachShader(program, fragment_shader);
    glDeleteShader(vertex_shader);
    glDeleteShader(fragment_shader);
    glBindFragDataLocation(program, 0, "out_color");
    glLinkProgram(program);

    // Get variable linkages
    glUseProgram(program);
    GLint position_attribute = glGetAttribLocation(program, "in_position");
    GLint point_count_uniform = glGetUniformLocation(program, "in_point_count");
    GLint circle_count_uniform = glGetUniformLocation(program, "in_circle_count");
    time_uniform = glGetUniformLocation(program, "in_time");

    float position_data[] = {0.0f, 0.0f, 0.0f, 1.0f};
    amount_of_data = sizeof(position_data)/(sizeof(float)*4);

    // Upload data to the card
    glGenVertexArrays(1, &vertex_array);
    glBindVertexArray(vertex_array);
    glGenBuffers(1, &vertex_position_buffer);
    glBindBuffer(GL_ARRAY_BUFFER, vertex_position_buffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(position_data), position_data, GL_STATIC_DRAW);
    glEnableVertexAttribArray(position_attribute);
    glVertexAttribPointer(position_attribute, 4, GL_FLOAT, GL_FALSE, 0, 0);

    // Specify some constants to the shader program
    glUniform1i(point_count_uniform, point_count);
    glUniform1i(circle_count_uniform, circle_count);

    // Make sure everything went well
    GLenum error = glGetError();
    assert(error == GL_NO_ERROR);

    beginning = monotonicTime();
  }
  ~OpenGLStuff() {
    glDeleteProgram(program);
    glDeleteBuffers(1, &vertex_position_buffer);
    glDeleteVertexArrays(1, &vertex_array);
  }
  void DrawScene() {
    uint64_t before = monotonicTime();
    glClear(GL_COLOR_BUFFER_BIT);
    glUniform1i(time_uniform, (GLint)(before - beginning));
    glDrawArraysInstanced(GL_POINTS, 0, amount_of_data, circle_count*point_count);
    glFlush();
  }
  void Resize(const int width, const int height) {
          glViewport(0, 0, width, height);
  }
 private:
  uint64_t beginning;
  GLint time_uniform;
  const int point_count;
  const int circle_count;
  size_t amount_of_data;
  GLuint vertex_array;
  GLuint vertex_position_buffer;
  GLuint program;
};

class GLXStuff {
 public:
  GLXStuff(const int width, const int height) {
    /*
    Step 1: Get a connection to the X server
    Step 2: Get a FBConfig
    Step 3: Create a versioned context
    Step 4: Create a drawable
    Step 5: Make the context current on the drawable
    */
    int fbcount;
    Bool success;
    // Step 1
    display = XOpenDisplay(NULL);
    XSetIOErrorHandler(handler);
    int screen = DefaultScreen(display);
    Window root_window = RootWindow(display, screen);
    assert(display);
    printf("%d screen(s)\n", ScreenCount(display));
    // Step 2
    const int visual_attribs[] = {
      GLX_DOUBLEBUFFER, True,
      GLX_RED_SIZE, 8,
      GLX_GREEN_SIZE, 8,
      GLX_BLUE_SIZE, 8,
      GLX_ALPHA_SIZE, 8,
      GLX_DEPTH_SIZE, 24,
      GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
      //GLX_CONFIG_CAVEAT, GLX_NONE,
      //GLX_SAMPLE_BUFFERS, 1,
      //GLX_SAMPLES, 16,
      None
    };
    GLXFBConfig* configs = glXChooseFBConfig(display, screen, visual_attribs, &fbcount);
    assert(configs);
    assert(fbcount);
    printf("%d configs\n", fbcount);
    // Naively take the first config
    GLXFBConfig config = configs[0];
    // Step 3
    // If you don't want a versioned context, you can use this function and
    // GLX will return you ::some:: version of a context. You'll then have to
    // use glGet(GL_MAJOR_VERSION) and glGet(GL_MINOR_VERSION) to figure out
    // if what it gave us is acceptable.
    //context = glXCreateNewContext(display, config, GLX_RGBA_TYPE, NULL, True);
    const int context_attribs[] = {
      GLX_CONTEXT_MAJOR_VERSION_ARB, 3,
      GLX_CONTEXT_MINOR_VERSION_ARB, 2,
      //GLX_CONTEXT_MAJOR_VERSION_ARB, 4,
      //GLX_CONTEXT_MINOR_VERSION_ARB, 3,
      None
    };
    context = glXCreateContextAttribsARB(display, config, NULL, True, context_attribs);
    assert(context);
    XFree(configs);
    // Step 4
    XVisualInfo *visual_info = glXGetVisualFromFBConfig(display, config);
    assert(visual_info);
    XSetWindowAttributes swa;
    Colormap cmap = XCreateColormap(display, root_window, visual_info->visual, AllocNone);
    swa.colormap = cmap;
    swa.background_pixmap = None;
    swa.border_pixel = BlackPixel(display, screen);
    swa.event_mask = StructureNotifyMask;
    window = XCreateWindow(display, RootWindow(display, screen), 0, 0, width, height, 50, visual_info->depth, InputOutput, visual_info->visual, CWBorderPixel|CWColormap|CWEventMask, &swa);
    assert(window);
    XMapWindow(display, window);
    // Step 5
    success = glXMakeCurrent(display, window, context);
    assert(success);
  }
  
  ~GLXStuff() {
    XDestroyWindow(display, window);
    glXDestroyContext(display, context);
    XCloseDisplay(display);
  }
  void SwapBuffers() {
    glXSwapBuffers(display, window);
  }
  void HandleEvents(OpenGLStuff &opengl_stuff) {
    while (XPending(display)) {
      XEvent event;
      XNextEvent(display, &event);
      switch (event.type) {
        case ConfigureNotify:
          opengl_stuff.Resize(event.xconfigure.width, event.xconfigure.height);
          break;
        case ClientMessage:
          done = 1;
          break;
      }
    }
  }
 private:
  Display* display;
  Window window;
  GLXContext context;
};

int main(int argc, char *argv[]) {
  const int width = 1300, height = 1300;
  GLXStuff glx_stuff(width, height);
  OpenGLStuff opengl_stuff(width, height);
  printf("Done!\n");
    
  // 60 FPS loop. glXSwapBuffers is synchronous.
  while (!done) {
    glx_stuff.HandleEvents(opengl_stuff);
    if (!done) {
      opengl_stuff.DrawScene();
      glx_stuff.SwapBuffers();
    }
  }
  return 0;
}

No comments:

Post a Comment