/* ------------------------------------------------------------- * framebuffer_demo_tcp.c * * * 600×400 ARGB8888 framebuffer in RAM * * SDL2 window that displays the framebuffer * * Simple TCP server (single client) that streams raw pixel data * * Build (Linux/macOS): * gcc -Wall -Wextra -pedantic -std=c11 framebuffer_demo_tcp.c \ * -lSDL2 -lpthread -o framebuffer_demo_tcp * * Run: * ./framebuffer_demo_tcp # opens a window and listens on 0.0.0.0:12345 * * Test client (Unix‑like): * # Send a static colour (red) for one frame * dd if=/dev/zero bs=1 count=$((600*400*4)) | tr \\0 \\377 > red.bin * cat red.bin | nc 127.0.0.1 12345 * * ------------------------------------------------------------- */ #include #include #include #include #include // memcpy, memset #include // close, read, write #include // fcntl #include #include #include #include #include // select() #include /* ------------------------------------------------------------- * Configuration * ------------------------------------------------------------- */ #define WIDTH 600 #define HEIGHT 400 #define LISTEN_PORT 12345 // change if you like #define BACKLOG 1 // only one pending connection /* ------------------------------------------------------------- * Simple pixel format helpers * ------------------------------------------------------------- */ typedef Uint32 pixel_t; // 0xAARRGGBB in memory (little‑endian) static inline pixel_t make_pixel(Uint8 r, Uint8 g, Uint8 b, Uint8 a) { return (pixel_t)((a << 24) | (r << 16) | (g << 8) | b); } #define MAKE_OPAQUE_PIXEL(r,g,b) make_pixel((r),(g),(b),0xFF) /* ------------------------------------------------------------- * Global framebuffer (contiguous block) * ------------------------------------------------------------- */ static pixel_t framebuffer[HEIGHT][WIDTH]; // 600*400*4 = 960 000 bytes /* ------------------------------------------------------------- * Fill with a cute demo pattern (visible before any network data) * ------------------------------------------------------------- */ static void init_demo_pattern(void) { for (int y = 0; y < HEIGHT; ++y) { for (int x = 0; x < WIDTH; ++x) { Uint8 r = (Uint8)((x * 255) / (WIDTH - 1)); Uint8 g = (Uint8)((y * 255) / (HEIGHT - 1)); Uint8 b = 0x80; framebuffer[y][x] = MAKE_OPAQUE_PIXEL(r, g, b); } } } /* ------------------------------------------------------------- * ---------- TCP SERVER ------------------------------------ * ------------------------------------------------------------- */ /* Returns a non‑blocking listening socket, or -1 on error. */ static int create_listen_socket(void) { int lst = socket(AF_INET, SOCK_STREAM, 0); if (lst < 0) { perror("socket"); return -1; } /* Allow immediate reuse of the address (helps during rapid restarts). */ int opt = 1; setsockopt(lst, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); struct sockaddr_in addr = { .sin_family = AF_INET, .sin_port = htons(LISTEN_PORT), .sin_addr = { .s_addr = INADDR_ANY } }; if (bind(lst, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("bind"); close(lst); return -1; } if (listen(lst, BACKLOG) < 0) { perror("listen"); close(lst); return -1; } /* Make the socket non‑blocking so that accept() never blocks. */ int flags = fcntl(lst, F_GETFL, 0); fcntl(lst, F_SETFL, flags | O_NONBLOCK); printf("[net] Listening on 0.0.0.0:%d (single client allowed)\n", LISTEN_PORT); return lst; } /* ------------------------------------------------------------- * Helper: set a socket to non‑blocking mode * ------------------------------------------------------------- */ static void set_nonblocking(int fd) { int flags = fcntl(fd, F_GETFL, 0); if (flags != -1) fcntl(fd, F_SETFL, flags | O_NONBLOCK); } /* ------------------------------------------------------------- * Main entry point * ------------------------------------------------------------- */ int main(void) { /* ---------- SDL initialisation -------------------------------- */ if (SDL_Init(SDL_INIT_VIDEO) != 0) { fprintf(stderr, "SDL_Init error: %s\n", SDL_GetError()); return EXIT_FAILURE; } SDL_Window *window = SDL_CreateWindow("Framebuffer Demo – TCP Input", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, WIDTH, HEIGHT, SDL_WINDOW_SHOWN); if (!window) { fprintf(stderr, "SDL_CreateWindow error: %s\n", SDL_GetError()); SDL_Quit(); return EXIT_FAILURE; } SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); if (!renderer) { fprintf(stderr, "SDL_CreateRenderer error: %s\n", SDL_GetError()); SDL_DestroyWindow(window); SDL_Quit(); return EXIT_FAILURE; } SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, WIDTH, HEIGHT); if (!texture) { fprintf(stderr, "SDL_CreateTexture error: %s\n", SDL_GetError()); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); return EXIT_FAILURE; } /* ---------- Initialise demo framebuffer ----------------------- */ init_demo_pattern(); /* ---------- Initialise networking ----------------------------- */ int listen_sock = create_listen_socket(); if (listen_sock < 0) { /* Continue without network – the window will just show the demo. */ fprintf(stderr, "[net] Continuing without network input.\n"); } int client_sock = -1; // -1 means “no client connected” size_t bytes_needed = WIDTH * HEIGHT * sizeof(pixel_t); size_t recv_offset = 0; // how many bytes of the current frame we already have bool quit = false; SDL_Event ev; /* ---------- Main loop ---------------------------------------- */ while (!quit) { /* ---- 1) Process SDL events (non‑blocking) ------------- */ while (SDL_PollEvent(&ev)) { if (ev.type == SDL_QUIT) quit = true; else if (ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_ESCAPE) quit = true; } /* ---- 2) Accept a client if we have none yet ------------- */ if (listen_sock >= 0 && client_sock < 0) { struct sockaddr_in remote; socklen_t remote_len = sizeof(remote); int tmp = accept(listen_sock, (struct sockaddr *)&remote, &remote_len); if (tmp >= 0) { client_sock = tmp; set_nonblocking(client_sock); recv_offset = 0; // start a fresh frame char ip[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &remote.sin_addr, ip, sizeof(ip)); printf("[net] Client connected from %s:%d\n", ip, ntohs(remote.sin_port)); } else if (errno != EAGAIN && errno != EWOULDBLOCK) { perror("[net] accept"); } } /* ---- 3) Read raw pixel data from the client ------------- */ if (client_sock >= 0) { /* Try to read the remainder of the current frame. */ size_t still_needed = bytes_needed - recv_offset; ssize_t n = recv(client_sock, ((Uint8 *)framebuffer) + recv_offset, still_needed, 0); if (n > 0) { recv_offset += (size_t)n; if (recv_offset == bytes_needed) { /* Full frame received – it will be displayed on the next render pass. */ recv_offset = 0; // start collecting the next frame } } else if (n == 0) { /* Client performed an orderly shutdown. */ printf("[net] Client disconnected.\n"); close(client_sock); client_sock = -1; } else if (n < 0) { if (errno != EAGAIN && errno != EWOULDBLOCK) { perror("[net] recv"); close(client_sock); client_sock = -1; } /* If EAGAIN/EWOULDBLOCK we simply have no more data right now. */ } } /* ---- 4) Copy framebuffer into texture ------------------- */ void *tex_pixels; int tex_pitch; if (SDL_LockTexture(texture, NULL, &tex_pixels, &tex_pitch) != 0) { fprintf(stderr, "SDL_LockTexture error: %s\n", SDL_GetError()); break; } /* The texture pitch may be larger than WIDTH*4, so copy line‑by‑line. */ for (int y = 0; y < HEIGHT; ++y) { memcpy((Uint8 *)tex_pixels + y * tex_pitch, framebuffer[y], WIDTH * sizeof(pixel_t)); } SDL_UnlockTexture(texture); /* ---- 5) Render ------------------------------------------ */ SDL_RenderClear(renderer); SDL_RenderCopy(renderer, texture, NULL, NULL); SDL_RenderPresent(renderer); /* VSYNC in the renderer caps the loop to the monitor refresh rate. */ } /* ------------------------------------------------------------- * Cleanup * ------------------------------------------------------------- */ if (client_sock >= 0) close(client_sock); if (listen_sock >= 0) close(listen_sock); SDL_DestroyTexture(texture); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); return EXIT_SUCCESS; }