#include #include #include // usleep #include #include #include #include //memcpy #include "canvaspixel.h" typedef struct CanvasLayer { GLuint size; GLenum format; GLuint tex; GLuint pbo1; GLuint pbo2; GLubyte *data; size_t mem; } CanvasLayer; // Global state static int canvaspixel_display = -1; static unsigned int canvaspixel_tex_size = 1024; static int canvaspixel_width=0; static int canvaspixel_height=0; static GLFWwindow* canvaspixel_win; static CanvasLayer *canvaspixel_base; static CanvasLayer *canvaspixel_overlay; pthread_t canvaspixel_thread; void glfw_error_callback(int error, const char* description) { printf("GLFW Error: %d %s", error, description); } static inline int min(int a, int b) { return a < b ? a : b; } static inline int max(int a, int b) { return a > b ? a : b; } // User callbacks void (*canvaspixel_on_close_cb)(); void (*canvaspixel_on_resize_cb)(); void (*canvaspixel_on_key_cb)(int, int, int); static int canvaspixel_do_layout = 0; static CanvasLayer* canvaspixel_layer_alloc(int size, int alpha) { CanvasLayer * layer = malloc(sizeof(CanvasLayer)); layer->size = size; layer->format = alpha ? GL_RGBA : GL_RGB; layer->mem = size * size * (alpha ? 4 : 3); layer->data = malloc(sizeof(GLubyte) * layer->mem); memset(layer->data, 0, layer->mem); return layer; } static void canvaspixel_layer_bind(CanvasLayer* layer) { // Create texture object glGenTextures(1, &(layer->tex)); glBindTexture( GL_TEXTURE_2D, layer->tex); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glBindTexture( GL_TEXTURE_2D, 0); // Create two PBOs glGenBuffers(1, &(layer->pbo1)); glBindBuffer( GL_PIXEL_UNPACK_BUFFER, layer->pbo1); glBufferData( GL_PIXEL_UNPACK_BUFFER, layer->mem, NULL, GL_STREAM_DRAW); glGenBuffers(1, &(layer->pbo2)); glBindBuffer( GL_PIXEL_UNPACK_BUFFER, layer->pbo2); glBufferData( GL_PIXEL_UNPACK_BUFFER, layer->mem, NULL, GL_STREAM_DRAW); glBindBuffer( GL_PIXEL_UNPACK_BUFFER, 0); } static void canvaspixel_layer_unbind(CanvasLayer * layer) { if (layer->tex) { glDeleteTextures(1, &(layer->tex)); glDeleteBuffers(1, &(layer->pbo1)); glDeleteBuffers(1, &(layer->pbo2)); } } static void canvaspixel_layer_free(CanvasLayer * layer) { canvaspixel_layer_unbind(layer); free(layer->data); free(layer); } static void canvaspixel_on_resize(GLFWwindow* window, int w, int h); static void canvaspixel_on_key(GLFWwindow* window, int key, int scancode, int action, int mods); static void canvaspixel_on_key(GLFWwindow* window, int key, int scancode, int action, int mods) { if (action == GLFW_PRESS && canvaspixel_on_key_cb) (*canvaspixel_on_key_cb)(key, scancode, mods); } static void canvaspixel_on_resize(GLFWwindow* window, int w, int h) { canvaspixel_width = w; canvaspixel_height = h; if(canvaspixel_on_resize_cb) (*canvaspixel_on_resize_cb)(); } static void canvaspixel_window_setup() { if(canvaspixel_win) { glfwDestroyWindow(canvaspixel_win); } glfwWindowHint(GLFW_DOUBLEBUFFER, 1); if (canvaspixel_display >= 0) { int mcount; GLFWmonitor** monitors = glfwGetMonitors(&mcount); canvaspixel_display %= mcount; GLFWmonitor* monitor = monitors[canvaspixel_display]; const GLFWvidmode* mode = glfwGetVideoMode(monitor); glfwWindowHint(GLFW_RED_BITS, mode->redBits); glfwWindowHint(GLFW_GREEN_BITS, mode->greenBits); glfwWindowHint(GLFW_BLUE_BITS, mode->blueBits); glfwWindowHint(GLFW_REFRESH_RATE, mode->refreshRate); canvaspixel_win = glfwCreateWindow(mode->width, mode->height, "Pixelflut", monitor, NULL); } else { canvaspixel_win = glfwCreateWindow(800, 600, "Pixelflut", NULL, NULL); } if (!canvaspixel_win) { printf("Could not create OpenGL context and/or window"); return; } glfwMakeContextCurrent(canvaspixel_win); // TODO: Move GL stuff to better place //glShadeModel(GL_FLAT); // shading mathod: GL_SMOOTH or GL_FLAT glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // 4-byte pixel alignment //glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); //glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); //glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST); glDisable(GL_DEPTH_TEST); glDisable(GL_LIGHTING); glDisable(GL_CULL_FACE); glEnable(GL_TEXTURE_2D); //glfwSetWindowUserPointer(canvaspixel_win, (void*) this); glfwSwapInterval(1); glfwSetKeyCallback(canvaspixel_win, &canvaspixel_on_key); glfwSetFramebufferSizeCallback(canvaspixel_win, &canvaspixel_on_resize); glfwGetFramebufferSize(canvaspixel_win, &canvaspixel_width, &canvaspixel_height); canvaspixel_on_resize(canvaspixel_win, canvaspixel_width, canvaspixel_height); canvaspixel_do_layout = 0; } static void canvaspixel_draw_layer(CanvasLayer * layer) { if (!layer || !layer->data) return; GLuint pboNext = layer->pbo1; GLuint pboIndex = layer->pbo2; layer->pbo1 = pboIndex; layer->pbo2 = pboNext; // Switch PBOs on each call. One is updated, one is drawn. // Update texture from first PBO glBindBuffer( GL_PIXEL_UNPACK_BUFFER, pboIndex); glBindTexture( GL_TEXTURE_2D, layer->tex); glTexImage2D( GL_TEXTURE_2D, 0, layer->format, layer->size, layer->size, 0, layer->format, GL_UNSIGNED_BYTE, 0); glBindTexture( GL_TEXTURE_2D, 0); // Update second PBO with new pixel data glBindBuffer( GL_PIXEL_UNPACK_BUFFER, pboNext); GLubyte *ptr = (GLubyte*) glMapBuffer( GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY); memcpy(ptr, layer->data, layer->mem); glUnmapBuffer( GL_PIXEL_UNPACK_BUFFER); glBindBuffer( GL_PIXEL_UNPACK_BUFFER, 0); //// Actually draw stuff. The texture should be updated in the meantime. if (layer->format == GL_RGBA) { glEnable( GL_BLEND); glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } else { glDisable( GL_BLEND); } glPushMatrix(); glBindTexture( GL_TEXTURE_2D, layer->tex); glBegin( GL_QUADS); glTexCoord2f(0, 0); glVertex3f(0.0f, 0.0f, 0.0f); glTexCoord2f(0, 1); glVertex3f(0.0f, layer->size, 0.0f); glTexCoord2f(1, 1); glVertex3f(layer->size, layer->size, 0.0f); glTexCoord2f(1, 0); glVertex3f(layer->size, 0.0f, 0.0f); glEnd(); glBindTexture( GL_TEXTURE_2D, 0); glPopMatrix(); } static void* canvaspixel_render_loop(void * arg) { glfwSetErrorCallback(glfw_error_callback); if (!glfwInit()) { puts("GLFW initialization failed"); if(canvaspixel_on_close_cb) (*canvaspixel_on_close_cb)(); glfwTerminate(); return NULL; } canvaspixel_window_setup(); int err = glewInit(); if (err != GLEW_OK) { puts("GLEW initialization failed"); printf("Error: %s\n", glewGetErrorString(err)); if(canvaspixel_on_close_cb) (*canvaspixel_on_close_cb)(); return NULL; } canvaspixel_layer_bind(canvaspixel_base); canvaspixel_layer_bind(canvaspixel_overlay); double last_frame = glfwGetTime(); while ("pixels are coming") { if (canvaspixel_do_layout) { canvaspixel_layer_unbind(canvaspixel_base); canvaspixel_layer_unbind(canvaspixel_overlay); canvaspixel_window_setup(); canvaspixel_layer_bind(canvaspixel_base); canvaspixel_layer_bind(canvaspixel_overlay); } if (glfwWindowShouldClose(canvaspixel_win)) break; glfwGetFramebufferSize(canvaspixel_win, &canvaspixel_width, &canvaspixel_height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0, canvaspixel_width, canvaspixel_height, 0, -1, 1); glViewport(0, 0, (GLsizei) canvaspixel_width, (GLsizei) canvaspixel_height); glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT); glPushMatrix(); GLuint texSize = canvaspixel_base->size; if(canvaspixel_width > texSize || canvaspixel_height > texSize) { float scale = ((float) max(canvaspixel_width, canvaspixel_height)) / (float) texSize; glScalef(scale, scale, 1); } canvaspixel_draw_layer(canvaspixel_base); // TODO: Overlay is not used yet //canvaspixel_draw_layer(canvaspixel_overlay); glPopMatrix(); glfwPollEvents(); glfwSwapBuffers(canvaspixel_win); double now = glfwGetTime(); double dt = now - last_frame; last_frame = now; double sleep = 1.0 / 30 - dt; if (sleep > 0) { usleep(sleep * 1000000); } } if(canvaspixel_on_close_cb) (*canvaspixel_on_close_cb)(); glfwTerminate(); canvaspixel_layer_free(canvaspixel_base); canvaspixel_layer_free(canvaspixel_overlay); return NULL; } // Public functions void canvaspixel_start(unsigned int texSize, void (*on_close)()) { canvaspixel_on_close_cb = on_close; canvaspixel_tex_size = texSize; canvaspixel_base = canvaspixel_layer_alloc(canvaspixel_tex_size, 0); canvaspixel_overlay = canvaspixel_layer_alloc(canvaspixel_tex_size, 1); if (pthread_create(&canvaspixel_thread, NULL, canvaspixel_render_loop, NULL)) { puts("Failed to start render thread"); exit(1); } } void canvaspixel_setcb_key(void (*on_key)(int key, int scancode, int mods)) { canvaspixel_on_key_cb = on_key; } void canvaspixel_setcb_resize(void (*on_resize)()) { canvaspixel_on_resize_cb = on_resize; } void canvaspixel_close() { glfwSetWindowShouldClose(canvaspixel_win, 1); } void canvaspixel_fullscreen(int display) { canvaspixel_display = display; canvaspixel_do_layout = 1; } int canvaspixel_get_display() { return canvaspixel_display; } // Return a pointer to the GLubyte for a given pixel, or NULL for out of bound coordinates. static inline GLubyte* canvaspixel_offset(CanvasLayer * layer, unsigned int x, unsigned int y) { if (x < 0 || y < 0 || x >= layer->size || y >= layer->size || layer->data == NULL) return NULL; return layer->data + ((y * layer->size) + x) * (layer->format == GL_RGBA ? 4 : 3); } void canvaspixel_set_px(unsigned int x, unsigned int y, uint32_t rgba) { CanvasLayer * layer = canvaspixel_base; GLubyte* ptr = canvaspixel_offset(layer, x, y); if (ptr == NULL) { return; } GLubyte r = (rgba & 0xff000000) >> 24; GLubyte g = (rgba & 0x00ff0000) >> 16; GLubyte b = (rgba & 0x0000ff00) >> 8; GLubyte a = (rgba & 0x000000ff) >> 0; if (layer->format == GL_RGBA) { ptr[0] = r; ptr[1] = g; ptr[2] = b; ptr[3] = a; return; } if (a == 0) { return; } if (a < 0xff) { GLuint na = 0xff - a; r = (a * r + na * (ptr[0])) / 0xff; g = (a * g + na * (ptr[1])) / 0xff; b = (a * b + na * (ptr[2])) / 0xff; } ptr[0] = r; ptr[1] = g; ptr[2] = b; } void canvaspixel_fill(uint32_t rgba) { CanvasLayer * layer = canvaspixel_base; for(int x=0; xsize; x++) for(int y=0; ysize; y++) canvaspixel_set_px(x, y, rgba); } void canvaspixel_get_px(unsigned int x, unsigned int y, uint32_t *rgba) { CanvasLayer * layer = canvaspixel_base; GLubyte* ptr = canvaspixel_offset(layer, x, y); if (ptr == NULL) { *rgba = 0x000000; } else { *rgba = (ptr[0] << 24) + (ptr[1] << 16) + (ptr[2] << 8) + 0xff; } } void canvaspixel_get_size(unsigned int *w, unsigned int *h) { int texSize = canvaspixel_base->size; if(canvaspixel_width > texSize || canvaspixel_height > texSize) { float scale = ((float) max(canvaspixel_width, canvaspixel_height)) / texSize; *w = min(texSize, canvaspixel_width/scale); *h = min(texSize, canvaspixel_height/scale); } else { *w = canvaspixel_width; *h = canvaspixel_height; } }