From 68c34c53460ed7b2af6a475873e800236ad2cf60 Mon Sep 17 00:00:00 2001 From: Marcel Hellkamp Date: Wed, 28 Mar 2018 22:20:03 +0200 Subject: [PATCH 01/12] Added C implementation (incomplete) --- pixelnuke/Makefile | 23 ++ pixelnuke/canvas.c | 361 ++++++++++++++++++ pixelnuke/canvas.h | 24 ++ pixelnuke/net.c | 199 ++++++++++ pixelnuke/net.h | 38 ++ pixelnuke/pixelnuke.c | 104 +++++ .../java}/de/paws/pixelwar/Drawable.java | 0 .../java}/de/paws/pixelwar/Label.java | 0 .../java}/de/paws/pixelwar/NetCanvas.java | 0 .../de/paws/pixelwar/PixelClientHandler.java | 0 .../java}/de/paws/pixelwar/PixelServer.java | 0 11 files changed, 749 insertions(+) create mode 100644 pixelnuke/Makefile create mode 100644 pixelnuke/canvas.c create mode 100644 pixelnuke/canvas.h create mode 100644 pixelnuke/net.c create mode 100644 pixelnuke/net.h create mode 100644 pixelnuke/pixelnuke.c rename pixelwar/src/{ => main/java}/de/paws/pixelwar/Drawable.java (100%) rename pixelwar/src/{ => main/java}/de/paws/pixelwar/Label.java (100%) rename pixelwar/src/{ => main/java}/de/paws/pixelwar/NetCanvas.java (100%) rename pixelwar/src/{ => main/java}/de/paws/pixelwar/PixelClientHandler.java (100%) rename pixelwar/src/{ => main/java}/de/paws/pixelwar/PixelServer.java (100%) diff --git a/pixelnuke/Makefile b/pixelnuke/Makefile new file mode 100644 index 0000000..b784d54 --- /dev/null +++ b/pixelnuke/Makefile @@ -0,0 +1,23 @@ +.PHONY: default all clean + +CC = gcc +CFLAGS = -Wall -pthread -g +LIBS = -levent -levent_pthreads -lrt -lGL -lGLEW -lglfw +TARGET = pixelnuke + +default: $(TARGET) +all: default + +OBJECTS = $(patsubst %.c, %.o, $(wildcard *.c)) +HEADERS = $(wildcard *.h) + +%.o: %.c $(HEADERS) + $(CC) $(CFLAGS) -c $< -o $@ + +.PRECIOUS: $(TARGET) $(OBJECTS) + +$(TARGET): $(OBJECTS) + $(CC) $(CFLAGS) $(OBJECTS) -Wall $(LIBS) -o $@ + +clean: + -rm -f *.o $(TARGET) diff --git a/pixelnuke/canvas.c b/pixelnuke/canvas.c new file mode 100644 index 0000000..23e2f38 --- /dev/null +++ b/pixelnuke/canvas.c @@ -0,0 +1,361 @@ +#include +#include +#include // usleep +#include +#include +#include +#include //memcpy + +#include "canvas.h" + +typedef struct CanvasLayer { + GLuint size; + GLenum format; + GLuint tex; + GLuint pbo1; + GLuint pbo2; + GLubyte *data; + size_t mem; +} CanvasLayer; + +// Global state + +static int canvas_width, canvas_height; + +static int canvas_display = -1; +static GLFWwindow* canvas_win; +static CanvasLayer *canvas_base; +static CanvasLayer *canvas_overlay; + +// User callbacks + +void (*canvas_on_close_cb)(); +void (*canvas_on_resize_cb)(); +void (*canvas_on_key_cb)(); + +static int canvas_do_layout = 0; + +static CanvasLayer* canvas_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); + + // 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); + + return layer; +} + +static void canvas_layer_free(CanvasLayer * canvas) { + if (canvas->tex) { + glDeleteTextures(1, &(canvas->tex)); + glDeleteBuffers(1, &(canvas->pbo1)); + glDeleteBuffers(1, &(canvas->pbo2)); + } + free(canvas->data); + free(canvas); +} + +static void canvas_on_resize(GLFWwindow* window, int w, int h); +static void canvas_on_key(GLFWwindow* window, int key, int scancode, int action, + int mods); + +static void canvas_on_key(GLFWwindow* window, int key, int scancode, int action, + int mods) { + printf("KEY: %u %u %u %u\n", key, scancode, action, mods); + if (action != GLFW_PRESS) { + return; + } + if (canvas_on_key_cb) + (*canvas_on_key_cb)(key, scancode, action, mods); +} + +static void canvas_on_resize(GLFWwindow* window, int w, int h) { + if(canvas_on_resize_cb) + (*canvas_on_resize_cb)(); +} + +static void canvas_window_setup() { + GLFWwindow* old_win = canvas_win; + + glfwWindowHint(GLFW_DOUBLEBUFFER, 1); + if (canvas_display >= 0) { + int mcount; + GLFWmonitor** monitors = glfwGetMonitors(&mcount); + canvas_display %= mcount; + GLFWmonitor* monitor = monitors[canvas_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); + canvas_win = glfwCreateWindow(mode->width, mode->height, "Pixelflut", + monitor, old_win); + } else { + canvas_win = glfwCreateWindow(800, 600, "Pixelflut", NULL, old_win); + } + + if (!canvas_win) { + printf("Could not create OpenGL context and/or window"); + return; + } + + //glfwSetWindowUserPointer(canvas_win, (void*) this); + glfwMakeContextCurrent(canvas_win); + glfwSwapInterval(1); + glfwSetKeyCallback(canvas_win, &canvas_on_key); + glfwSetFramebufferSizeCallback(canvas_win, &canvas_on_resize); + + if (old_win) { + glfwDestroyWindow(old_win); + } + + canvas_do_layout = 0; +} + +static void canvas_draw_layer(CanvasLayer * layer) { + if (!layer) + 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* canvas_render_loop(void * arg) { + glfwMakeContextCurrent(canvas_win); + + double last_frame = glfwGetTime(); + + while ("pixels are coming") { + if (glfwWindowShouldClose(canvas_win)) + break; + + if (canvas_do_layout) + canvas_window_setup(); + + int w, h; + glfwGetFramebufferSize(canvas_win, &w, &h); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, canvas_width, canvas_height, 0, -1, 1); + glViewport(0, 0, (GLsizei) canvas_width, (GLsizei) canvas_height); + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + glPushMatrix(); + + //int ts = layer->texSize; + //if(width > ts || height > ts) { + // float scale = std::max(width, height) / (float) ts; + // glScalef(scale, scale, 1); + //} + + canvas_draw_layer(canvas_base); + canvas_draw_layer(canvas_overlay); + + glPopMatrix(); + glfwPollEvents(); + glfwSwapBuffers(canvas_win); + + double now = glfwGetTime(); + double dt = now - last_frame; + last_frame = now; + double sleep = 1.0 / 30 - dt; + printf("fps: %f\n", 1.0/dt); + if (sleep > 0) { + usleep(sleep * 1000000); + } + } + + if(canvas_on_close_cb) + (*canvas_on_close_cb)(); + + glfwTerminate(); + + return NULL; +} + +// Public functions + +pthread_t canvas_thread; + +void glfw_error_callback(int error, const char* description) { + printf("GLFW Error: %d %s", error, description); +} + +void canvas_start(unsigned int texSize, void (*on_close)()) { + + canvas_on_close_cb = on_close; + + glfwSetErrorCallback(glfw_error_callback); + if (!glfwInit()) { + puts("GLFW initialization failed"); + exit(1); + } + + canvas_window_setup(); + + int err = glewInit(); + if (err != GLEW_OK) { + puts("GLEW initialization failed"); + printf("Error: %s\n", glewGetErrorString(err)); + exit(1); + } + + //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); + + canvas_base = canvas_layer_alloc(texSize, 0); + canvas_overlay = canvas_layer_alloc(texSize, 1); + + glfwMakeContextCurrent(NULL); // Pass context to thread + if (pthread_create(&canvas_thread, NULL, canvas_render_loop, NULL)) { + puts("Failed to start render thread"); + exit(1); + } +} + +void canvas_setcb_key(void (*on_key)(int key, int scancode, int mods)) { + canvas_on_key_cb = on_key; +} + +void canvas_setcb_resize(void (*on_resize)()) { + canvas_on_resize_cb = on_resize; +} + +void canvas_close() { + glfwSetWindowShouldClose(canvas_win, 1); +} + +void canvas_fullscreen(int display) { + canvas_display = display; + canvas_do_layout = 1; +} + +int canvas_get_display() { + return canvas_display; +} + +// Return a pointer to the GLubyte for a given pixel, or NULL for out of bound coordinates. +static inline GLubyte* canvas_offset(CanvasLayer * layer, unsigned int x, + unsigned int y) { + if (x < 0 || y < 0 || x >= layer->size || y >= layer->size) + return NULL; + return layer->data + + ((y * layer->size) + x) * (layer->format == GL_RGBA ? 4 : 3); +} + +void canvas_set_px(unsigned int x, unsigned int y, unsigned int rgba) { + CanvasLayer * layer = canvas_base; + GLubyte* ptr = canvas_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; + return; + } + ptr[0] = r; + ptr[1] = g; + ptr[2] = b; +} + +void canvas_get_px(unsigned int x, unsigned int y, unsigned int *rgba) { + CanvasLayer * layer = canvas_base; + GLubyte* ptr = canvas_offset(layer, x, y); + if (ptr == NULL) { + *rgba = 0x000000; + } else { + *rgba = (ptr[0] << 24) + (ptr[1] << 16) + (ptr[2] << 8) + 0xff; + } +} + +void canvas_get_size(unsigned int *w, unsigned int *h) { + // TODO: Clip on window size + *w = canvas_base->size; + *h = canvas_base->size; +} + diff --git a/pixelnuke/canvas.h b/pixelnuke/canvas.h new file mode 100644 index 0000000..3404621 --- /dev/null +++ b/pixelnuke/canvas.h @@ -0,0 +1,24 @@ +#ifndef CANVAS_H_ +#define CANVAS_H_ + +// Open the canvas window and start the gui loop (in a separate thread) +void canvas_start(unsigned int texSize, void (*on_close)()); + +void canvas_setcb_key(void (*on_key)(int key, int scancode, int mods)); +void canvas_setcb_resize(void (*on_resize)()); + +// Close the canvas window and free any resources and contexts +void canvas_close(); + +void canvas_fullscreen(int display); +int canvas_get_display(); + +void canvas_set_px(unsigned int x, unsigned int y, unsigned int rgba); +void canvas_get_px(unsigned int x, unsigned int y, unsigned int *rgba); + +// get the current visible canvas size in pixel. +// The actual window might be bigger if scaling is enabled. +void canvas_get_size(unsigned int *width, unsigned int *height); + + +#endif /* CANVAS_H_ */ diff --git a/pixelnuke/net.c b/pixelnuke/net.c new file mode 100644 index 0000000..af506f6 --- /dev/null +++ b/pixelnuke/net.c @@ -0,0 +1,199 @@ +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "net.h" + +#define MAX_LINE 128 + +typedef struct NetClient { + int sock_fd; + struct bufferevent *buf_ev; + int state; + void *user; +} NetClient; + +#define NET_CSTATE_OPEN 0 +#define NET_CSTATE_CLOSING 1 + +// global state +static struct event_base *base; + +// User defined callbacks +static net_on_connect netcb_on_connect = NULL; +static net_on_read netcb_on_read = NULL; +static net_on_close netcb_on_close = NULL; + + + +// libevent callbacks + +static void netev_on_read(struct bufferevent *bev, void *ctx) { + struct evbuffer *input; + char *line; + size_t n; + NetClient *client = ctx; + + input = bufferevent_get_input(bev); + + if ((line = evbuffer_readln(input, &n, EVBUFFER_EOL_LF))) { + if(netcb_on_read) + (*netcb_on_read)(client, line); + free(line); + } + + if (evbuffer_get_length(input) >= MAX_LINE) { + net_err(client, "Line to long"); + } +} + +static void netev_on_write(struct bufferevent *bev, void *arg) { + NetClient *client = arg; + + if (client->state == NET_CSTATE_CLOSING && evbuffer_get_length(bufferevent_get_output(bev)) == 0) { + + if(netcb_on_close) + (*netcb_on_close)(client, 0); + + bufferevent_free(bev); + free(client); + } +} + +static void netev_on_error(struct bufferevent *bev, short error, void *arg) { + NetClient *client = arg; + + // TODO: Some logging? + if (error & BEV_EVENT_EOF) { + } else if (error & BEV_EVENT_ERROR) { + } else if (error & BEV_EVENT_TIMEOUT) { + } + + client->state = NET_CSTATE_CLOSING; + if(netcb_on_close) + (*netcb_on_close)(client, error); + + bufferevent_free(bev); + free(client); +} + +static void on_accept(evutil_socket_t listener, short event, void *arg) { + struct event_base *base = arg; + struct sockaddr_storage ss; + socklen_t slen = sizeof(ss); + int fd = accept(listener, (struct sockaddr*) &ss, &slen); + if (fd < 0) { + perror("accept failed"); + } else if (fd > FD_SETSIZE) { + close(fd); // TODO: verify if this is needed. Only for select()? But libevent uses poll/kqueue? + } else { + NetClient *client = calloc(1, sizeof(NetClient)); + if (client == NULL) { + perror("client malloc failed"); + close(fd); + return; + } + + client->sock_fd = fd; + client->buf_ev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE); + + evutil_make_socket_nonblocking(fd); + bufferevent_setcb(client->buf_ev, netev_on_read, netev_on_write, netev_on_error, client); + bufferevent_setwatermark(client->buf_ev, EV_READ, 0, MAX_LINE); + + if(netcb_on_connect) + (*netcb_on_connect)(client); + + if(client->state == NET_CSTATE_OPEN) + bufferevent_enable(client->buf_ev, EV_READ | EV_WRITE); + } +} + + +// Public functions + +void net_start(int port, net_on_connect on_connect, net_on_read on_read, net_on_close on_close) { + evutil_socket_t listener; + struct sockaddr_in sin; + struct event *listener_event; + + evthread_use_pthreads(); + + //setvbuf(stdout, NULL, _IONBF, 0); + + netcb_on_connect = on_connect; + netcb_on_read = on_read; + netcb_on_close = on_close; + + base = event_base_new(); + if (!base) + err(1, "Failed to create event_base"); + + evthread_make_base_notifiable(base); + + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = 0; + sin.sin_port = htons(1337); + listener = socket(AF_INET, SOCK_STREAM, 0); + evutil_make_socket_nonblocking(listener); + + if (bind(listener, (struct sockaddr*) &sin, sizeof(sin)) < 0) { + err(1, "bind failed"); + } + + if (listen(listener, 16) < 0) { + err(1, "listen failed"); + } + + listener_event = event_new(base, listener, EV_READ | EV_PERSIST, on_accept, (void*) base); + event_add(listener_event, NULL); + + event_base_dispatch(base); +} + + +void net_stop() { + event_base_loopbreak(base); +} + + +void net_send(NetClient *client, const char * msg) { + struct evbuffer *output = bufferevent_get_output(client->buf_ev); + evbuffer_add(output, msg, strlen(msg)); + evbuffer_add(output, "\n", 1); +} + +void net_close(NetClient *client) { + client->state = NET_CSTATE_CLOSING; + bufferevent_disable(client->buf_ev, EV_READ); +} + +void net_err(NetClient *client, const char * msg) { + struct evbuffer *output = bufferevent_get_output(client->buf_ev); + evbuffer_add(output, "ERROR: ", 7); + evbuffer_add(output, msg, strlen(msg)); + evbuffer_add(output, "\n", 1); + net_close(client); +} + +void net_set_user(NetClient *client, void *user) { + client->user = user; +} + +void net_get_user(NetClient *client, void **user) { + *user = client->user; +} + diff --git a/pixelnuke/net.h b/pixelnuke/net.h new file mode 100644 index 0000000..593ecdf --- /dev/null +++ b/pixelnuke/net.h @@ -0,0 +1,38 @@ +#ifndef NET_H_ +#define NET_H_ + +typedef struct NetClient NetClient; + +#define NET_CSTATE_OPEN 0 +#define NET_CSTATE_CLOSING 1 + +// Callback called immediately after a client connects +typedef void (*net_on_connect)(NetClient *client); + +// Callback called for each line of input. +// The char array is null-terminated and does not include the final line break. +// It is NOT owned by the callback and freed as soon as the callback returned. +typedef void (*net_on_read)(NetClient *client, char* line); + +// Callback called after a client disconnects. +// The second parameter is 0 for a normal client-induced disconnect and != 0 on errors. +typedef void (*net_on_close)(NetClient *client, int error); + +// Start the server and block until it is closed again. +void net_start(int port, net_on_connect on_connect, net_on_read on_read, net_on_close on_close); + +// Stop the server as soon as possible +void net_stop(); + +// Send a string to the client. A newline is added automatically. +void net_send(NetClient *client, const char * msg); +// Stop reading from this clients socket, send all bytes still in the output buffer, then close the connection. +void net_close(NetClient *client); +// Send an error message to the client, then close the connection. +void net_err(NetClient *client, const char * msg); + +// Get or set the user attachment, a pointer to an arbitrary data structure or NULL +void net_set_user(NetClient *client, void *user); +void net_get_user(NetClient *client, void **user); + +#endif /* NET_H_ */ diff --git a/pixelnuke/pixelnuke.c b/pixelnuke/pixelnuke.c new file mode 100644 index 0000000..ff0ba2f --- /dev/null +++ b/pixelnuke/pixelnuke.c @@ -0,0 +1,104 @@ +#include "net.h" +#include "canvas.h" + +#include +#include +#include //sprintf + +int px_width = 1024; +int px_height = 1024; + +const char * px_help_text = + "\ +PX x y: Get color at position (x,y)\n\ +PX x y rrggbb(aa): Draw a pixel (with optional alpha channel)\n\ +SIZE: Get canvas size"; + +// Helper functions + +static int util_str_starts_with(const char* prefix, const char* str) { + char cp, cs; + while ((cp = *prefix++) == (cs = *str++)) { + if (cp == 0) + return 1; + } + return !cp; +} + +// server callbacks +void px_on_connect(NetClient *client) { + +} + +void px_on_read(NetClient *client, char *line) { + if (util_str_starts_with("PX ", line)) { + const char * ptr = line + 3; + char * endptr = (char*) ptr; + errno = 0; + unsigned int x = strtoul(ptr, &endptr, 10); + if (endptr == ptr || errno) { + net_err(client, + "First parameter missing or invalid (should be decimal)"); + return; + } + unsigned int y = strtoul((ptr = endptr), &endptr, 10); + if (endptr == ptr || errno) { + net_err(client, + "Second parameter missing or invalid (should be decimal)"); + return; + } + if (*endptr == 0) { + char str[64]; + sprintf(str, "PX %u %u %x", x, y, 0xABCDEF); + net_send(client, str); + return; + } + unsigned int c = strtoul((ptr = endptr), &endptr, 16); + if (endptr == ptr || errno) { + net_err(client, + "Third parameter missing or invalid (should be hex color)"); + return; + } + printf("%d %d %u=%x\n", x, y, c, c); + } else if (util_str_starts_with("SIZE", line)) { + unsigned int w, h; + canvas_get_size(&w, &h); + char str[64]; + sprintf(str, "SIZE %d %d", w, h); + net_send(client, str); + } else if (util_str_starts_with("HELP", line)) { + net_send(client, px_help_text); + } +} + +void px_on_close(NetClient *client, int error) { +} + +void px_on_key(int key, int scancode, int mods) { + if (key == 300) { // F11 + int display = canvas_get_display(); + if(display<0) + canvas_fullscreen(0); + else + canvas_fullscreen(-1); + } else if (key == 301) { // F12 + canvas_fullscreen(canvas_get_display()+1); + } else if (key == 81 || key == 256) { // q or ESC + canvas_close(); + } +} + +void px_on_resize() {} +void px_on_window_close() { + printf("Window closed\n"); + net_stop(); +} + +int main(int argc, char **argv) { + canvas_start(1024, &px_on_window_close); + canvas_setcb_key(&px_on_key); + canvas_setcb_resize(&px_on_resize); + net_start(1337, &px_on_connect, &px_on_read, &px_on_close); + return 0; +} + diff --git a/pixelwar/src/de/paws/pixelwar/Drawable.java b/pixelwar/src/main/java/de/paws/pixelwar/Drawable.java similarity index 100% rename from pixelwar/src/de/paws/pixelwar/Drawable.java rename to pixelwar/src/main/java/de/paws/pixelwar/Drawable.java diff --git a/pixelwar/src/de/paws/pixelwar/Label.java b/pixelwar/src/main/java/de/paws/pixelwar/Label.java similarity index 100% rename from pixelwar/src/de/paws/pixelwar/Label.java rename to pixelwar/src/main/java/de/paws/pixelwar/Label.java diff --git a/pixelwar/src/de/paws/pixelwar/NetCanvas.java b/pixelwar/src/main/java/de/paws/pixelwar/NetCanvas.java similarity index 100% rename from pixelwar/src/de/paws/pixelwar/NetCanvas.java rename to pixelwar/src/main/java/de/paws/pixelwar/NetCanvas.java diff --git a/pixelwar/src/de/paws/pixelwar/PixelClientHandler.java b/pixelwar/src/main/java/de/paws/pixelwar/PixelClientHandler.java similarity index 100% rename from pixelwar/src/de/paws/pixelwar/PixelClientHandler.java rename to pixelwar/src/main/java/de/paws/pixelwar/PixelClientHandler.java diff --git a/pixelwar/src/de/paws/pixelwar/PixelServer.java b/pixelwar/src/main/java/de/paws/pixelwar/PixelServer.java similarity index 100% rename from pixelwar/src/de/paws/pixelwar/PixelServer.java rename to pixelwar/src/main/java/de/paws/pixelwar/PixelServer.java From 51143d90ed0631293be1d48565874c44515c0dee Mon Sep 17 00:00:00 2001 From: Marcel Hellkamp Date: Thu, 29 Mar 2018 15:23:49 +0200 Subject: [PATCH 02/12] Added pixelnuke to readme (+typos) --- README.md | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 88d7018..7f87452 100755 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Pixelflut defines four main commands that are always supported to get you starte You can send multiple commands over the same TCP socket by terminating each command with a single newline character (`\n`). -Example: `echo "PX 23 42 ff8000" | netcat pixelflut.xample.com 1337` +Example: `echo "PX 23 42 ff8000" | netcat pixelflut.example.com 1337` Server Implementations ---------------------- @@ -31,20 +31,31 @@ This repository contains multiple implementations of the pixelflut protocol. Pul Server written in Python, based on gevent and pygame. Easy to hack with, but a bit slow. - sudo apt-get install python-gevent python-pygame python-cairo cd pixelflut + sudo apt-get install python-gevent python-pygame python-cairo mkdir save python pixelflut.py brain.py #### `/pixelwar` (java server) -Server wirtten in Java8, based on netty and awt. Optimized for speed and large player groups, fast networks or high resolution projectors. +Server written in Java8, based on netty and awt. Optimized for speed and large player groups, fast networks or high resolution projectors. - sudo apt-get install maven openjdk-8-jdk cd pixelwar + sudo apt-get install maven openjdk-8-jdk mvn package java -jar target/pixelwar*-jar-with-dependencies.jar +#### `/pixelnuke` (C server) + +Server written in C, based on libevent2, OpenGL, GLFW and pthreads. It won't get any faster than this. + + cd pixelnuke + sudo apt-get install build-essential libevent-dev libglew-dev libglfw3-dev + make + ./pixelnuke + +Pull requests that improve performance or portability (e.g. Windows or RasPI) are always welcomed. + Links and Videos ---------------- From f04796ec8bb32cdc46a2752fd6b20bff42e79c9d Mon Sep 17 00:00:00 2001 From: Marcel Hellkamp Date: Thu, 29 Mar 2018 15:24:17 +0200 Subject: [PATCH 03/12] Build with -O2 by default and -g with `make debug` --- pixelnuke/Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pixelnuke/Makefile b/pixelnuke/Makefile index b784d54..ee8dcd1 100644 --- a/pixelnuke/Makefile +++ b/pixelnuke/Makefile @@ -1,13 +1,16 @@ .PHONY: default all clean CC = gcc -CFLAGS = -Wall -pthread -g +CFLAGS = -Wall -pthread -O2 LIBS = -levent -levent_pthreads -lrt -lGL -lGLEW -lglfw TARGET = pixelnuke default: $(TARGET) all: default +debug: CFLAGS += -DDEBUG -g +debug: default + OBJECTS = $(patsubst %.c, %.o, $(wildcard *.c)) HEADERS = $(wildcard *.h) From a7ac7da270f6ca315ba10fe96f9bca832858d183 Mon Sep 17 00:00:00 2001 From: Marcel Hellkamp Date: Thu, 29 Mar 2018 15:24:57 +0200 Subject: [PATCH 04/12] Do all the GL stuff in the render thread. --- pixelnuke/canvas.c | 174 +++++++++++++++++++++++++++------------------ pixelnuke/canvas.h | 6 +- 2 files changed, 108 insertions(+), 72 deletions(-) diff --git a/pixelnuke/canvas.c b/pixelnuke/canvas.c index 23e2f38..d307bd2 100644 --- a/pixelnuke/canvas.c +++ b/pixelnuke/canvas.c @@ -20,13 +20,18 @@ typedef struct CanvasLayer { // Global state -static int canvas_width, canvas_height; - static int canvas_display = -1; +static unsigned int canvas_tex_size = 1024; static GLFWwindow* canvas_win; static CanvasLayer *canvas_base; static CanvasLayer *canvas_overlay; +pthread_t canvas_thread; + +void glfw_error_callback(int error, const char* description) { + printf("GLFW Error: %d %s", error, description); +} + // User callbacks void (*canvas_on_close_cb)(); @@ -42,6 +47,22 @@ static CanvasLayer* canvas_layer_alloc(int size, int alpha) { layer->mem = size * size * (alpha ? 4 : 3); layer->data = malloc(sizeof(GLubyte) * layer->mem); + GLubyte* pt; + for(int x=0; xdata + (((y * size) + x) * (alpha ? 4 : 3)); + pt[0] = x%255; + pt[1] = y%255; + if(alpha) + pt[3] = 0; + } + } + + return layer; +} + +static void canvas_layer_bind(CanvasLayer* layer) { + // Create texture object glGenTextures(1, &(layer->tex)); glBindTexture( GL_TEXTURE_2D, layer->tex); @@ -57,18 +78,21 @@ static CanvasLayer* canvas_layer_alloc(int size, int alpha) { glBindBuffer( GL_PIXEL_UNPACK_BUFFER, layer->pbo2); glBufferData( GL_PIXEL_UNPACK_BUFFER, layer->mem, NULL, GL_STREAM_DRAW); glBindBuffer( GL_PIXEL_UNPACK_BUFFER, 0); - - return layer; } -static void canvas_layer_free(CanvasLayer * canvas) { - if (canvas->tex) { - glDeleteTextures(1, &(canvas->tex)); - glDeleteBuffers(1, &(canvas->pbo1)); - glDeleteBuffers(1, &(canvas->pbo2)); + +static void canvas_layer_unbind(CanvasLayer * layer) { + if (layer->tex) { + glDeleteTextures(1, &(layer->tex)); + glDeleteBuffers(1, &(layer->pbo1)); + glDeleteBuffers(1, &(layer->pbo2)); } - free(canvas->data); - free(canvas); +} + +static void canvas_layer_free(CanvasLayer * layer) { + canvas_layer_unbind(layer); + free(layer->data); + free(layer); } static void canvas_on_resize(GLFWwindow* window, int w, int h); @@ -91,7 +115,10 @@ static void canvas_on_resize(GLFWwindow* window, int w, int h) { } static void canvas_window_setup() { - GLFWwindow* old_win = canvas_win; + + if(canvas_win) { + glfwDestroyWindow(canvas_win); + } glfwWindowHint(GLFW_DOUBLEBUFFER, 1); if (canvas_display >= 0) { @@ -104,10 +131,9 @@ static void canvas_window_setup() { glfwWindowHint(GLFW_GREEN_BITS, mode->greenBits); glfwWindowHint(GLFW_BLUE_BITS, mode->blueBits); glfwWindowHint(GLFW_REFRESH_RATE, mode->refreshRate); - canvas_win = glfwCreateWindow(mode->width, mode->height, "Pixelflut", - monitor, old_win); + canvas_win = glfwCreateWindow(mode->width, mode->height, "Pixelflut", monitor, NULL); } else { - canvas_win = glfwCreateWindow(800, 600, "Pixelflut", NULL, old_win); + canvas_win = glfwCreateWindow(800, 600, "Pixelflut", NULL, NULL); } if (!canvas_win) { @@ -115,21 +141,29 @@ static void canvas_window_setup() { return; } - //glfwSetWindowUserPointer(canvas_win, (void*) this); glfwMakeContextCurrent(canvas_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(canvas_win, (void*) this); glfwSwapInterval(1); glfwSetKeyCallback(canvas_win, &canvas_on_key); glfwSetFramebufferSizeCallback(canvas_win, &canvas_on_resize); - if (old_win) { - glfwDestroyWindow(old_win); - } - canvas_do_layout = 0; } static void canvas_draw_layer(CanvasLayer * layer) { - if (!layer) + if (!layer || !layer->data) return; GLuint pboNext = layer->pbo1; @@ -179,35 +213,64 @@ static void canvas_draw_layer(CanvasLayer * layer) { } static void* canvas_render_loop(void * arg) { - glfwMakeContextCurrent(canvas_win); + + glfwSetErrorCallback(glfw_error_callback); + if (!glfwInit()) { + puts("GLFW initialization failed"); + if(canvas_on_close_cb) + (*canvas_on_close_cb)(); + glfwTerminate(); + return NULL; + } + + canvas_window_setup(); + + int err = glewInit(); + if (err != GLEW_OK) { + puts("GLEW initialization failed"); + printf("Error: %s\n", glewGetErrorString(err)); + if(canvas_on_close_cb) + (*canvas_on_close_cb)(); + return NULL; + } + + canvas_layer_bind(canvas_base); + canvas_layer_bind(canvas_overlay); double last_frame = glfwGetTime(); while ("pixels are coming") { + + if (canvas_do_layout) { + canvas_layer_unbind(canvas_base); + canvas_layer_unbind(canvas_overlay); + canvas_window_setup(); + canvas_layer_bind(canvas_base); + canvas_layer_bind(canvas_overlay); + } + if (glfwWindowShouldClose(canvas_win)) break; - if (canvas_do_layout) - canvas_window_setup(); - int w, h; glfwGetFramebufferSize(canvas_win, &w, &h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); - glOrtho(0, canvas_width, canvas_height, 0, -1, 1); - glViewport(0, 0, (GLsizei) canvas_width, (GLsizei) canvas_height); + glOrtho(0, w, h, 0, -1, 1); + glViewport(0, 0, (GLsizei) w, (GLsizei) h); glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT); glPushMatrix(); - //int ts = layer->texSize; - //if(width > ts || height > ts) { - // float scale = std::max(width, height) / (float) ts; - // glScalef(scale, scale, 1); - //} + GLuint texSize = canvas_base->size; + if(w > texSize || h > texSize) { + float scale = ((float) (w>h?w:h)) / (float) texSize; + glScalef(scale, scale, 1); + } canvas_draw_layer(canvas_base); - canvas_draw_layer(canvas_overlay); + // TODO: Overlay is not used yet + //canvas_draw_layer(canvas_overlay); glPopMatrix(); glfwPollEvents(); @@ -217,7 +280,6 @@ static void* canvas_render_loop(void * arg) { double dt = now - last_frame; last_frame = now; double sleep = 1.0 / 30 - dt; - printf("fps: %f\n", 1.0/dt); if (sleep > 0) { usleep(sleep * 1000000); } @@ -227,51 +289,23 @@ static void* canvas_render_loop(void * arg) { (*canvas_on_close_cb)(); glfwTerminate(); + canvas_layer_free(canvas_base); + canvas_layer_free(canvas_overlay); return NULL; } // Public functions -pthread_t canvas_thread; -void glfw_error_callback(int error, const char* description) { - printf("GLFW Error: %d %s", error, description); -} void canvas_start(unsigned int texSize, void (*on_close)()) { canvas_on_close_cb = on_close; + canvas_tex_size = texSize; + canvas_base = canvas_layer_alloc(canvas_tex_size, 0); + canvas_overlay = canvas_layer_alloc(canvas_tex_size, 1); - glfwSetErrorCallback(glfw_error_callback); - if (!glfwInit()) { - puts("GLFW initialization failed"); - exit(1); - } - - canvas_window_setup(); - - int err = glewInit(); - if (err != GLEW_OK) { - puts("GLEW initialization failed"); - printf("Error: %s\n", glewGetErrorString(err)); - exit(1); - } - - //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); - - canvas_base = canvas_layer_alloc(texSize, 0); - canvas_overlay = canvas_layer_alloc(texSize, 1); - - glfwMakeContextCurrent(NULL); // Pass context to thread if (pthread_create(&canvas_thread, NULL, canvas_render_loop, NULL)) { puts("Failed to start render thread"); exit(1); @@ -302,13 +336,13 @@ int canvas_get_display() { // Return a pointer to the GLubyte for a given pixel, or NULL for out of bound coordinates. static inline GLubyte* canvas_offset(CanvasLayer * layer, unsigned int x, unsigned int y) { - if (x < 0 || y < 0 || x >= layer->size || y >= layer->size) + 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 canvas_set_px(unsigned int x, unsigned int y, unsigned int rgba) { +void canvas_set_px(unsigned int x, unsigned int y, uint32_t rgba) { CanvasLayer * layer = canvas_base; GLubyte* ptr = canvas_offset(layer, x, y); @@ -343,7 +377,7 @@ void canvas_set_px(unsigned int x, unsigned int y, unsigned int rgba) { ptr[2] = b; } -void canvas_get_px(unsigned int x, unsigned int y, unsigned int *rgba) { +void canvas_get_px(unsigned int x, unsigned int y, uint32_t *rgba) { CanvasLayer * layer = canvas_base; GLubyte* ptr = canvas_offset(layer, x, y); if (ptr == NULL) { diff --git a/pixelnuke/canvas.h b/pixelnuke/canvas.h index 3404621..826478a 100644 --- a/pixelnuke/canvas.h +++ b/pixelnuke/canvas.h @@ -1,6 +1,8 @@ #ifndef CANVAS_H_ #define CANVAS_H_ +#include + // Open the canvas window and start the gui loop (in a separate thread) void canvas_start(unsigned int texSize, void (*on_close)()); @@ -13,8 +15,8 @@ void canvas_close(); void canvas_fullscreen(int display); int canvas_get_display(); -void canvas_set_px(unsigned int x, unsigned int y, unsigned int rgba); -void canvas_get_px(unsigned int x, unsigned int y, unsigned int *rgba); +void canvas_set_px(unsigned int x, unsigned int y, uint32_t rgba); +void canvas_get_px(unsigned int x, unsigned int y, uint32_t *rgba); // get the current visible canvas size in pixel. // The actual window might be bigger if scaling is enabled. From a80c3876c7bee76252c35f94abc285077e3ce685 Mon Sep 17 00:00:00 2001 From: Marcel Hellkamp Date: Thu, 29 Mar 2018 15:25:40 +0200 Subject: [PATCH 05/12] Fix protocol parser and logic. Stuff works now :D --- pixelnuke/pixelnuke.c | 63 ++++++++++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 16 deletions(-) diff --git a/pixelnuke/pixelnuke.c b/pixelnuke/pixelnuke.c index ff0ba2f..ff56918 100644 --- a/pixelnuke/pixelnuke.c +++ b/pixelnuke/pixelnuke.c @@ -8,12 +8,6 @@ int px_width = 1024; int px_height = 1024; -const char * px_help_text = - "\ -PX x y: Get color at position (x,y)\n\ -PX x y rrggbb(aa): Draw a pixel (with optional alpha channel)\n\ -SIZE: Get canvas size"; - // Helper functions static int util_str_starts_with(const char* prefix, const char* str) { @@ -35,39 +29,73 @@ void px_on_read(NetClient *client, char *line) { const char * ptr = line + 3; char * endptr = (char*) ptr; errno = 0; + unsigned int x = strtoul(ptr, &endptr, 10); if (endptr == ptr || errno) { net_err(client, "First parameter missing or invalid (should be decimal)"); return; } + unsigned int y = strtoul((ptr = endptr), &endptr, 10); if (endptr == ptr || errno) { net_err(client, "Second parameter missing or invalid (should be decimal)"); return; } + + // PX -> Get RGB color at position (x,y) or '0x000000' for out-of-range queries if (*endptr == 0) { + uint32_t c; + canvas_get_px(x, y, &c); char str[64]; - sprintf(str, "PX %u %u %x", x, y, 0xABCDEF); + sprintf(str, "PX %u %u %06X", x, y, (c >> 8)); net_send(client, str); return; } - unsigned int c = strtoul((ptr = endptr), &endptr, 16); + + // PX BB|RRGGBB|RRGGBBAA + unsigned long int c = strtoul((ptr = endptr), &endptr, 16); if (endptr == ptr || errno) { net_err(client, "Third parameter missing or invalid (should be hex color)"); return; } - printf("%d %d %u=%x\n", x, y, c, c); + + if (endptr - 1 - 6 == ptr) { + // RGB -> RGBA + c = (c << 8) + 0xff; + } else if (endptr - 1 - 8 == ptr) { + // done + } else if (endptr - 1 - 2 == ptr) { + // WW -> RGBA + c = (c << 24) + (c << 16) + (c << 8) + 0xff; + } else { + net_err(client, + "Color hex code must be 2, 6 or 8 characters long (WW, RGB or RGBA)"); + return; + } + + canvas_set_px(x, y, c); + } else if (util_str_starts_with("SIZE", line)) { - unsigned int w, h; - canvas_get_size(&w, &h); + char str[64]; - sprintf(str, "SIZE %d %d", w, h); + sprintf(str, "SIZE %d %d", px_width, px_height); net_send(client, str); + } else if (util_str_starts_with("HELP", line)) { - net_send(client, px_help_text); + + net_send(client, +"\ +PX x y: Get color at position (x,y)\n\ +PX x y rrggbb(aa): Draw a pixel (with optional alpha channel)\n\ +SIZE: Get canvas size"); + + } else { + + net_err(client, "Unknown command"); + } } @@ -77,18 +105,21 @@ void px_on_close(NetClient *client, int error) { void px_on_key(int key, int scancode, int mods) { if (key == 300) { // F11 int display = canvas_get_display(); - if(display<0) + if (display < 0) canvas_fullscreen(0); else canvas_fullscreen(-1); } else if (key == 301) { // F12 - canvas_fullscreen(canvas_get_display()+1); + canvas_fullscreen(canvas_get_display() + 1); } else if (key == 81 || key == 256) { // q or ESC canvas_close(); } } -void px_on_resize() {} +void px_on_resize() { + canvas_get_size(&px_width, &px_height); +} + void px_on_window_close() { printf("Window closed\n"); net_stop(); From 0f9305c5ac1c4d25d49077aa5517b3b14afb8c2f Mon Sep 17 00:00:00 2001 From: Marcel Hellkamp Date: Thu, 29 Mar 2018 15:26:15 +0200 Subject: [PATCH 06/12] Increase net read buffer for web scale performance! --- pixelnuke/net.c | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/pixelnuke/net.c b/pixelnuke/net.c index af506f6..90d66d7 100644 --- a/pixelnuke/net.c +++ b/pixelnuke/net.c @@ -17,7 +17,13 @@ #include "net.h" -#define MAX_LINE 128 +// Lines longer than this are considered an error. +#define NET_MAX_LINE 1024 + +// The server buffers up to NET_READ_BUFFER bytes per client connection. +// Lower values allow lots of clients to draw at the same time, each with a fair share. +// Higher values increase throughput but fast clients might be able to draw large batches at once. +#define NET_MAX_BUFFER 10240 typedef struct NetClient { int sock_fd; @@ -49,13 +55,13 @@ static void netev_on_read(struct bufferevent *bev, void *ctx) { input = bufferevent_get_input(bev); - if ((line = evbuffer_readln(input, &n, EVBUFFER_EOL_LF))) { - if(netcb_on_read) - (*netcb_on_read)(client, line); + // Change while->if for less throughput but more fair pixel distribution across client connections. + while ((line = evbuffer_readln(input, &n, EVBUFFER_EOL_LF))) { + (*netcb_on_read)(client, line); free(line); } - if (evbuffer_get_length(input) >= MAX_LINE) { + if (evbuffer_get_length(input) >= NET_MAX_LINE) { net_err(client, "Line to long"); } } @@ -112,7 +118,7 @@ static void on_accept(evutil_socket_t listener, short event, void *arg) { evutil_make_socket_nonblocking(fd); bufferevent_setcb(client->buf_ev, netev_on_read, netev_on_write, netev_on_error, client); - bufferevent_setwatermark(client->buf_ev, EV_READ, 0, MAX_LINE); + bufferevent_setwatermark(client->buf_ev, EV_READ, 0, NET_MAX_BUFFER); if(netcb_on_connect) (*netcb_on_connect)(client); @@ -142,13 +148,14 @@ void net_start(int port, net_on_connect on_connect, net_on_read on_read, net_on_ if (!base) err(1, "Failed to create event_base"); - evthread_make_base_notifiable(base); + //evthread_make_base_notifiable(base); sin.sin_family = AF_INET; sin.sin_addr.s_addr = 0; sin.sin_port = htons(1337); listener = socket(AF_INET, SOCK_STREAM, 0); evutil_make_socket_nonblocking(listener); + evutil_make_listen_socket_reuseable(listener); if (bind(listener, (struct sockaddr*) &sin, sizeof(sin)) < 0) { err(1, "bind failed"); @@ -177,8 +184,10 @@ void net_send(NetClient *client, const char * msg) { } void net_close(NetClient *client) { - client->state = NET_CSTATE_CLOSING; - bufferevent_disable(client->buf_ev, EV_READ); + if(client->state == NET_CSTATE_OPEN) { + client->state = NET_CSTATE_CLOSING; + bufferevent_disable(client->buf_ev, EV_READ); + } } void net_err(NetClient *client, const char * msg) { From 003a7f093885936b7b9cc758ed3f366254df3c6b Mon Sep 17 00:00:00 2001 From: Marcel Hellkamp Date: Thu, 29 Mar 2018 15:29:05 +0200 Subject: [PATCH 07/12] Added .gitignore --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a674924 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +pixelnuke/.* +pixelnuke/*.o +pixelnuke/pixelnuke + +pixelwar/.* +pixelwar/target From 7269957ce5407292d1558425ac8564c60a46fbae Mon Sep 17 00:00:00 2001 From: Marcel Hellkamp Date: Thu, 29 Mar 2018 15:55:55 +0200 Subject: [PATCH 08/12] Init layers as fully black (transparent if alpha channel is enabled) --- pixelnuke/canvas.c | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/pixelnuke/canvas.c b/pixelnuke/canvas.c index d307bd2..09928aa 100644 --- a/pixelnuke/canvas.c +++ b/pixelnuke/canvas.c @@ -46,18 +46,7 @@ static CanvasLayer* canvas_layer_alloc(int size, int alpha) { layer->format = alpha ? GL_RGBA : GL_RGB; layer->mem = size * size * (alpha ? 4 : 3); layer->data = malloc(sizeof(GLubyte) * layer->mem); - - GLubyte* pt; - for(int x=0; xdata + (((y * size) + x) * (alpha ? 4 : 3)); - pt[0] = x%255; - pt[1] = y%255; - if(alpha) - pt[3] = 0; - } - } - + memset(layer->data, 0, layer->mem); return layer; } From b736e443471eb79d7943a9a61df3e4c00b354ef2 Mon Sep 17 00:00:00 2001 From: Marcel Hellkamp Date: Thu, 29 Mar 2018 15:56:32 +0200 Subject: [PATCH 09/12] Fix canvas key callback signature --- pixelnuke/canvas.c | 10 +++------- pixelnuke/pixelnuke.c | 7 +++++-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/pixelnuke/canvas.c b/pixelnuke/canvas.c index 09928aa..69cc90f 100644 --- a/pixelnuke/canvas.c +++ b/pixelnuke/canvas.c @@ -36,7 +36,7 @@ void glfw_error_callback(int error, const char* description) { void (*canvas_on_close_cb)(); void (*canvas_on_resize_cb)(); -void (*canvas_on_key_cb)(); +void (*canvas_on_key_cb)(int, int, int); static int canvas_do_layout = 0; @@ -90,12 +90,8 @@ static void canvas_on_key(GLFWwindow* window, int key, int scancode, int action, static void canvas_on_key(GLFWwindow* window, int key, int scancode, int action, int mods) { - printf("KEY: %u %u %u %u\n", key, scancode, action, mods); - if (action != GLFW_PRESS) { - return; - } - if (canvas_on_key_cb) - (*canvas_on_key_cb)(key, scancode, action, mods); + if (action == GLFW_PRESS && canvas_on_key_cb) + (*canvas_on_key_cb)(key, scancode, mods); } static void canvas_on_resize(GLFWwindow* window, int w, int h) { diff --git a/pixelnuke/pixelnuke.c b/pixelnuke/pixelnuke.c index ff56918..669fcd2 100644 --- a/pixelnuke/pixelnuke.c +++ b/pixelnuke/pixelnuke.c @@ -5,8 +5,8 @@ #include #include //sprintf -int px_width = 1024; -int px_height = 1024; +unsigned int px_width = 1024; +unsigned int px_height = 1024; // Helper functions @@ -103,6 +103,9 @@ void px_on_close(NetClient *client, int error) { } void px_on_key(int key, int scancode, int mods) { + + printf("Key pressed: key:%d scancode:%d mods:%d\n", key, scancode, mods); + if (key == 300) { // F11 int display = canvas_get_display(); if (display < 0) From 3221ab184163c1b9916db095149f7fec3bb9a2bd Mon Sep 17 00:00:00 2001 From: Marcel Hellkamp Date: Thu, 29 Mar 2018 18:34:07 +0200 Subject: [PATCH 10/12] Fix alpha blending --- pixelnuke/canvas.c | 1 - 1 file changed, 1 deletion(-) diff --git a/pixelnuke/canvas.c b/pixelnuke/canvas.c index 69cc90f..cdf09b5 100644 --- a/pixelnuke/canvas.c +++ b/pixelnuke/canvas.c @@ -355,7 +355,6 @@ void canvas_set_px(unsigned int x, unsigned int y, uint32_t rgba) { r = (a * r + na * (ptr[0])) / 0xff; g = (a * g + na * (ptr[1])) / 0xff; b = (a * b + na * (ptr[2])) / 0xff; - return; } ptr[0] = r; ptr[1] = g; From 0090f518e90d60f4c164f3cbb1ceeaf8bd80f27b Mon Sep 17 00:00:00 2001 From: Marcel Hellkamp Date: Thu, 29 Mar 2018 18:37:18 +0200 Subject: [PATCH 11/12] Fix: Resize callback won't fire and canvas_get_size would return wrong size. The canvas_get_size function now returns the number of visible pixels after scaling. The canvas resize callback now fires after a window was created or updated. --- pixelnuke/canvas.c | 39 ++++++++++++++++++++++++++++++--------- pixelnuke/pixelnuke.c | 4 +++- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/pixelnuke/canvas.c b/pixelnuke/canvas.c index cdf09b5..a9535e8 100644 --- a/pixelnuke/canvas.c +++ b/pixelnuke/canvas.c @@ -22,6 +22,8 @@ typedef struct CanvasLayer { static int canvas_display = -1; static unsigned int canvas_tex_size = 1024; +static int canvas_width=0; +static int canvas_height=0; static GLFWwindow* canvas_win; static CanvasLayer *canvas_base; static CanvasLayer *canvas_overlay; @@ -32,6 +34,14 @@ 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 (*canvas_on_close_cb)(); @@ -95,6 +105,9 @@ static void canvas_on_key(GLFWwindow* window, int key, int scancode, int action, } static void canvas_on_resize(GLFWwindow* window, int w, int h) { + canvas_width = w; + canvas_height = h; + if(canvas_on_resize_cb) (*canvas_on_resize_cb)(); } @@ -144,6 +157,9 @@ static void canvas_window_setup() { glfwSetKeyCallback(canvas_win, &canvas_on_key); glfwSetFramebufferSizeCallback(canvas_win, &canvas_on_resize); + glfwGetFramebufferSize(canvas_win, &canvas_width, &canvas_height); + canvas_on_resize(canvas_win, canvas_width, canvas_height); + canvas_do_layout = 0; } @@ -237,19 +253,18 @@ static void* canvas_render_loop(void * arg) { if (glfwWindowShouldClose(canvas_win)) break; - int w, h; - glfwGetFramebufferSize(canvas_win, &w, &h); + glfwGetFramebufferSize(canvas_win, &canvas_width, &canvas_height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); - glOrtho(0, w, h, 0, -1, 1); - glViewport(0, 0, (GLsizei) w, (GLsizei) h); + glOrtho(0, canvas_width, canvas_height, 0, -1, 1); + glViewport(0, 0, (GLsizei) canvas_width, (GLsizei) canvas_height); glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT); glPushMatrix(); GLuint texSize = canvas_base->size; - if(w > texSize || h > texSize) { - float scale = ((float) (w>h?w:h)) / (float) texSize; + if(canvas_width > texSize || canvas_height > texSize) { + float scale = ((float) max(canvas_width, canvas_height)) / (float) texSize; glScalef(scale, scale, 1); } @@ -372,8 +387,14 @@ void canvas_get_px(unsigned int x, unsigned int y, uint32_t *rgba) { } void canvas_get_size(unsigned int *w, unsigned int *h) { - // TODO: Clip on window size - *w = canvas_base->size; - *h = canvas_base->size; + int texSize = canvas_base->size; + if(canvas_width > texSize || canvas_height > texSize) { + float scale = ((float) max(canvas_width, canvas_height)) / texSize; + *w = min(texSize, canvas_width/scale); + *h = min(texSize, canvas_height/scale); + } else { + *w = canvas_width; + *h = canvas_height; + } } diff --git a/pixelnuke/pixelnuke.c b/pixelnuke/pixelnuke.c index 669fcd2..6bb32f7 100644 --- a/pixelnuke/pixelnuke.c +++ b/pixelnuke/pixelnuke.c @@ -129,9 +129,11 @@ void px_on_window_close() { } int main(int argc, char **argv) { - canvas_start(1024, &px_on_window_close); canvas_setcb_key(&px_on_key); canvas_setcb_resize(&px_on_resize); + + canvas_start(1024, &px_on_window_close); + net_start(1337, &px_on_connect, &px_on_read, &px_on_close); return 0; } From c651046e3f1a8744ae042d669e317ae6632af361 Mon Sep 17 00:00:00 2001 From: Marcel Hellkamp Date: Thu, 29 Mar 2018 18:39:37 +0200 Subject: [PATCH 12/12] Added canvas_fill(rgba) and a 50% black fill bound to the 'c' key. --- pixelnuke/canvas.c | 8 ++++++++ pixelnuke/canvas.h | 1 + pixelnuke/pixelnuke.c | 2 ++ 3 files changed, 11 insertions(+) diff --git a/pixelnuke/canvas.c b/pixelnuke/canvas.c index a9535e8..68e0e1e 100644 --- a/pixelnuke/canvas.c +++ b/pixelnuke/canvas.c @@ -376,6 +376,14 @@ void canvas_set_px(unsigned int x, unsigned int y, uint32_t rgba) { ptr[2] = b; } +void canvas_fill(uint32_t rgba) { + CanvasLayer * layer = canvas_base; + for(int x=0; xsize; x++) + for(int y=0; ysize; y++) + canvas_set_px(x, y, rgba); +} + + void canvas_get_px(unsigned int x, unsigned int y, uint32_t *rgba) { CanvasLayer * layer = canvas_base; GLubyte* ptr = canvas_offset(layer, x, y); diff --git a/pixelnuke/canvas.h b/pixelnuke/canvas.h index 826478a..8a0af57 100644 --- a/pixelnuke/canvas.h +++ b/pixelnuke/canvas.h @@ -15,6 +15,7 @@ void canvas_close(); void canvas_fullscreen(int display); int canvas_get_display(); +void canvas_fill(uint32_t rgba); void canvas_set_px(unsigned int x, unsigned int y, uint32_t rgba); void canvas_get_px(unsigned int x, unsigned int y, uint32_t *rgba); diff --git a/pixelnuke/pixelnuke.c b/pixelnuke/pixelnuke.c index 6bb32f7..af6a5a6 100644 --- a/pixelnuke/pixelnuke.c +++ b/pixelnuke/pixelnuke.c @@ -114,6 +114,8 @@ void px_on_key(int key, int scancode, int mods) { canvas_fullscreen(-1); } else if (key == 301) { // F12 canvas_fullscreen(canvas_get_display() + 1); + } else if (key == 67) { // c + canvas_fill(0x00000088); } else if (key == 81 || key == 256) { // q or ESC canvas_close(); }