commit 6d2cd51a177122b7867717190133cfde56a7e2d4
parent 486a6217433139495d32052b0fc4ee6545c38094
Author: MikoĊaj Lenczewski <mblenczewski@gmail.com>
Date: Sun, 19 Jan 2025 15:18:52 +0000
Initial server implementation
Diffstat:
M | build.sh | | | 6 | +++++- |
M | src/main.c | | | 228 | ++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------- |
M | src/mandelbrot.h | | | 118 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------ |
A | src/server.c | | | 186 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
4 files changed, 442 insertions(+), 96 deletions(-)
diff --git a/build.sh b/build.sh
@@ -12,4 +12,8 @@ set -ex
mkdir -p bin
-$CC -o bin/mandelbrot src/main.c $WARNINGS $CFLAGS $CPPFLAGS $LDFLAGS
+$CC -o bin/mandelbrot src/main.c \
+ $WARNINGS $CFLAGS $CPPFLAGS $LDFLAGS
+
+$CC -o bin/mandelbrot-server src/server.c \
+ $WARNINGS $CFLAGS $CPPFLAGS $LDFLAGS
diff --git a/src/main.c b/src/main.c
@@ -2,25 +2,52 @@
#include "mandelbrot.h"
+#include <sys/socket.h>
+#include <netdb.h>
+
+#include <fcntl.h>
+
#include <getopt.h>
-struct opts opts = {
+enum palette_type {
+ PALETTE_BASIC,
+ PALETTE_MONOCHROME,
+ PALETTE_GRAYSCALE,
+ PALETTE_HISTOGRAM,
+ PALETTE_SMOOTH,
+};
+
+struct opts {
+ int verbose;
+ char *server_host;
+ char *server_port;
+ uint64_t xres, yres;
+ uint64_t max_iters;
+ enum palette_type palette;
+ char *outfile;
+} opts = {
.verbose = 0,
+ .server_host = NULL,
+ .server_port = NULL,
.xres = 800, .yres = 600,
.max_iters = 100,
.palette = PALETTE_SMOOTH,
.outfile = "/tmp/mandelbrot.bmp",
};
+#define OPTSTR "hvS:r:i:p:o:"
+
static void
usage(char *prog)
{
- fprintf(stderr, "Usage: %s [-hv] [-r <xres>x<yres> ] [-i <maxiters>] "
+ fprintf(stderr, "Usage: %s [-hv] [-S <servaddr>] "
+ "[-r <xres>x<yres> ] [-i <maxiters>] "
"[-p basic|mono|gray|hist|smooth ] [-o <out.bmp>]\n",
prog);
fprintf(stderr, "\t-h : display help information\n");
fprintf(stderr, "\t-v : enable verbose logging\n");
+ fprintf(stderr, "\t-S : attempt to connect to the given server addr\n");
fprintf(stderr, "\t-r : set output resolution (default: 800x600)\n");
fprintf(stderr, "\t-i : set maximum iterations per pixel (default: 100)\n");
fprintf(stderr, "\t-p : set colour palette (default: smooth)\n");
@@ -37,6 +64,19 @@ parse_opts(int argc, char **argv, struct opts *opts)
opts->verbose = 1;
break;
+ case 'S': {
+ char *split = strrchr(optarg, ':');
+ if (!split) {
+ fprintf(stderr, "Failed to parse server addr: %s\n", optarg);
+ return -1;
+ }
+
+ *split = '\0';
+
+ opts->server_host = optarg;
+ opts->server_port = ++split;
+ } break;
+
case 'r': {
char *saveptr;
char *xres = strtok_r(optarg, "x", &saveptr);
@@ -95,52 +135,6 @@ parse_opts(int argc, char **argv, struct opts *opts)
}
static inline uint32_t
-pixel(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
-{
- return (uint32_t) a << 24 |
- (uint32_t) r << 16 |
- (uint32_t) g << 8 |
- (uint32_t) b;
-}
-
-#define LERP(v0, v1, t) (((v0) * (1 - (t))) + ((v1) * (t)))
-
-static inline uint32_t
-point(float x, float y, uint32_t max_iters, float *out)
-{
- // z = 0+0i
- // c = x + i*y
- // z_i = z_j ^ 2 + c
- // = (re_j + i * im_j) ^ 2 + (x + i * y)
- // = ((re_j + i * im_j) * (re_j + i * im_j)) + (x + i * y)
- // = (re_j^2 + 2 * (re_j * i * im_j) + (i^2 + im_j^2)) + (x + i * y)
- // = (re_j^2 - im_j^2) + i * (2 * re_j * im_j) + (x + i * y)
- // = (re_j^2 - im_j^2 + x) + i * ((2 * re_j * im_j) + y)
-
- float re = 0.0, im = 0.0, abs2 = 0.0;
-
- uint32_t i = 0;
- while (abs2 < 4.0 && i < max_iters) {
- float new_re = (re * re) - (im * im) + x;
- float new_im = (2 * re * im) + y;
-
- abs2 = (new_re * new_re) + (new_im * new_im);
- re = new_re;
- im = new_im;
-
- i++;
- }
-
- *out = abs2;
-
- return i;
-}
-
-#define MIN(a, b) ((a) < (b) ? (a) : (b))
-#define MAX(a, b) ((a) > (b) ? (a) : (b))
-#define CLAMP(v, v0, v1) MIN(MAX((v), (v0)), (v1))
-
-static inline uint32_t
hsv_to_rgb(float hue, float sat, float val)
{
float h = fmodf(hue, 360.0);
@@ -205,35 +199,93 @@ palette(float abs2, uint32_t iters, uint32_t max_iters, enum palette_type type)
}
static void
-render_mandelbrot(uint32_t *buf, size_t width, size_t height,
- size_t max_iters, enum palette_type palette_type)
+paint_region(struct render_region *region, uint32_t *pixbuf, uint32_t *iterbuf, float *abs2buf,
+ enum palette_type palette_type)
{
- float x0 = -2.0, y0 = -1.5, x1 = 0.5, y1 = 1.5;
-
- for (uint32_t py = 0; py < height; py++) {
- float yfrac = ((float) py / height);
- float y = LERP(y0, y1, yfrac);
-
- for (uint32_t px = 0; px < width; px++) {
- float xfrac = ((float) px / width);
- float x = LERP(x0, x1, xfrac);
-
- float abs2;
- uint32_t iters = point(x, y, max_iters, &abs2);
- uint32_t pixel = palette(abs2, iters, max_iters, palette_type);
-
-#if 0
- printf("(%03u, %03u): %03u iters, 0x%02x%02x%02x%02x\n",
- px, py, iters,
- ((uint8_t *) &pixel)[0],
- ((uint8_t *) &pixel)[1],
- ((uint8_t *) &pixel)[2],
- ((uint8_t *) &pixel)[3]);
-#endif
-
- buf[py * width + px] = pixel;
+ size_t pixels = region->xres * region->yres;
+
+ for (size_t i = 0; i < pixels; i++)
+ pixbuf[i] = palette(abs2buf[i], iterbuf[i], region->max_iters,
+ palette_type);
+}
+
+static void
+render_networked_mandelbrot(char const *host, char const *port,
+ uint32_t *pixbuf, uint32_t *iterbuf, float *abs2buf,
+ size_t width, size_t height, size_t max_iters,
+ enum palette_type palette_type)
+{
+ struct addrinfo hints = {
+ .ai_family = AF_UNSPEC,
+ .ai_socktype = SOCK_STREAM,
+ .ai_protocol = IPPROTO_TCP,
+ .ai_flags = AI_NUMERICSERV,
+ }, *addrinfo, *ptr;
+
+ int res;
+ if ((res = getaddrinfo(host, port, &hints, &addrinfo))) {
+ fprintf(stderr, "getaddrinfo(): %s\n", gai_strerror(res));
+ exit(EXIT_FAILURE);
+ }
+
+ int fd;
+ for (ptr = addrinfo; ptr; ptr = ptr->ai_next) {
+ fd = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
+ if (fd < 0)
+ continue;
+
+ if (connect(fd, ptr->ai_addr, ptr->ai_addrlen) < 0) {
+ close(fd);
+ continue;
}
+
+ break;
}
+
+ freeaddrinfo(addrinfo);
+
+ if (!ptr) {
+ fprintf(stderr, "Failed to connect to server\n");
+ exit(EXIT_FAILURE);
+ }
+
+ struct render_region region = {
+ .xres = width, .yres = height,
+ .escape_val = 2,
+ .max_iters = max_iters,
+ .x0 = -2.0, .y0 = -1.5, .x1 = 0.5, .y1 = 1.5,
+ };
+
+ // TODO: assemble request packet as fixed point
+ int nbytes_send = sendall(fd, ®ion, sizeof region);
+ printf("sent: %d bytes\n", nbytes_send);
+
+ // TODO: actually wait on a number of render results
+ size_t pixels = width * height;
+ int nbytes_recv = 0;
+ nbytes_recv += recvall(fd, iterbuf, pixels * sizeof *iterbuf);
+ nbytes_recv += recvall(fd, abs2buf, pixels * sizeof *abs2buf);
+ printf("received: %d bytes\n", nbytes_recv);
+
+ paint_region(®ion, pixbuf, iterbuf, abs2buf, palette_type);
+
+ close(fd);
+}
+
+static void
+render_mandelbrot(uint32_t *pixbuf, uint32_t *iterbuf, float *abs2buf,
+ size_t width, size_t height, size_t max_iters,
+ enum palette_type palette_type)
+{
+ struct render_region region = {
+ .xres = width, .yres = height,
+ .escape_val = 2,
+ .max_iters = max_iters,
+ .x0 = -2.0, .y0 = -1.5, .x1 = 0.5, .y1 = 1.5,
+ };
+
+ render_region(®ion, iterbuf, abs2buf);
+ paint_region(®ion, pixbuf, iterbuf, abs2buf, palette_type);
}
static void
@@ -289,6 +341,10 @@ main(int argc, char **argv)
if (opts.verbose) {
fprintf(stderr, "info: verbose: %d\n", opts.verbose);
+ if (opts.server_host && opts.server_port) {
+ fprintf(stderr, "info: server: %s:%s\n",
+ opts.server_host, opts.server_port);
+ }
fprintf(stderr, "info: resolution: %" PRIu64 "x%" PRIu64 "\n",
opts.xres, opts.yres);
fprintf(stderr, "info: max iters: %" PRIu64 "\n", opts.max_iters);
@@ -297,15 +353,31 @@ main(int argc, char **argv)
}
size_t pixels = opts.xres * opts.yres;
- uint32_t *buffer = malloc(pixels * sizeof *buffer);
- assert(buffer);
- render_mandelbrot(buffer, opts.xres, opts.yres, opts.max_iters, opts.palette);
+ uint32_t *pixbuf = malloc(pixels * sizeof *pixbuf);
+ assert(pixbuf);
+
+ uint32_t *iterbuf = malloc(pixels * sizeof *iterbuf);
+ assert(iterbuf);
+
+ float *abs2buf = malloc(pixels * sizeof *abs2buf);
+ assert(abs2buf);
+
+ if (opts.server_host && opts.server_port) {
+ render_networked_mandelbrot(opts.server_host, opts.server_port,
+ pixbuf, iterbuf, abs2buf,
+ opts.xres, opts.yres, opts.max_iters,
+ opts.palette);
+ } else {
+ render_mandelbrot(pixbuf, iterbuf, abs2buf,
+ opts.xres, opts.yres, opts.max_iters,
+ opts.palette);
+ }
int fd = creat(opts.outfile, 0644);
assert(fd > 0);
- write_bitmap(fd, buffer, opts.xres, opts.yres);
+ write_bitmap(fd, pixbuf, opts.xres, opts.yres);
close(fd);
diff --git a/src/mandelbrot.h b/src/mandelbrot.h
@@ -10,31 +10,115 @@
#include <string.h>
#include <unistd.h>
-#include <fcntl.h>
-
#include <math.h>
+#include <sys/socket.h>
+
/* ===========================================================================
*/
-enum palette_type {
- PALETTE_BASIC,
- PALETTE_MONOCHROME,
- PALETTE_GRAYSCALE,
- PALETTE_HISTOGRAM,
- PALETTE_SMOOTH,
-};
+#define LERP(v0, v1, t) (((v0) * (1 - (t))) + ((v1) * (t)))
+
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+#define CLAMP(v, v0, v1) MIN(MAX((v), (v0)), (v1))
+
+#define ARRLEN(arr) (sizeof (arr) / sizeof (arr)[0])
-struct opts {
- int verbose;
- uint64_t xres, yres;
- uint64_t max_iters;
- enum palette_type palette;
- char *outfile;
+#define FLATIDX(y, width, x) ((y) * (width) + (x))
+
+struct render_region {
+ uint32_t xres, yres;
+ uint32_t escape_val;
+ uint32_t max_iters;
+ float x0, y0, x1, y1; // TODO: store in fixed point?
};
-#define OPTSTR "hvr:i:p:o:"
+static inline uint32_t
+pixel(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
+{
+ return (uint32_t) a << 24 |
+ (uint32_t) r << 16 |
+ (uint32_t) g << 8 |
+ (uint32_t) b;
+}
+
+static inline uint32_t
+render_point(float x, float y, uint32_t max_iters, uint32_t escape_val, float *out)
+{
+ // z = 0+0i
+ // c = x + i*y
+ // z_i = z_j ^ 2 + c
+ // = (re_j + i * im_j) ^ 2 + (x + i * y)
+ // = ((re_j + i * im_j) * (re_j + i * im_j)) + (x + i * y)
+ // = (re_j^2 + 2 * (re_j * i * im_j) + (i^2 + im_j^2)) + (x + i * y)
+ // = (re_j^2 - im_j^2) + i * (2 * re_j * im_j) + (x + i * y)
+ // = (re_j^2 - im_j^2 + x) + i * ((2 * re_j * im_j) + y)
+
+ float re = 0.0, im = 0.0, abs2 = 0.0, esc2 = (float) escape_val * escape_val;
+
+ uint32_t i = 0;
+ while (abs2 < esc2 && i < max_iters) {
+ float new_re = (re * re) - (im * im) + x;
+ float new_im = (2 * re * im) + y;
+
+ abs2 = (new_re * new_re) + (new_im * new_im);
+ re = new_re;
+ im = new_im;
+
+ i++;
+ }
+
+ *out = abs2;
+
+ return i;
+}
+
+static inline void
+render_region(struct render_region *region, uint32_t *iterbuf, float *abs2buf)
+{
+ for (uint32_t py = 0; py < region->yres; py++) {
+ float yfrac = ((float) py / region->yres);
+ float y = LERP(region->y0, region->y1, yfrac);
+
+ for (uint32_t px = 0; px < region->xres; px++) {
+ float xfrac = ((float) px / region->xres);
+ float x = LERP(region->x0, region->x1, xfrac);
+
+ float abs2;
+ uint32_t iters = render_point(x, y, region->max_iters,
+ region->escape_val, &abs2);
+
+ iterbuf[FLATIDX(py, region->xres, px)] = iters;
+ abs2buf[FLATIDX(py, region->xres, px)] = abs2;
+ }
+ }
+}
+
+static inline size_t
+sendall(int fd, void *buf, size_t len)
+{
+ size_t total = 0;
+ do {
+ ssize_t curr = send(fd, (uint8_t *) buf + total, len - total, 0);
+ if (curr <= 0) break;
+ total += curr;
+ } while (total < len);
+
+ return total;
+}
+
+static inline size_t
+recvall(int fd, void *buf, size_t len)
+{
+ size_t total = 0;
+ do {
+ ssize_t curr = recv(fd, (uint8_t *) buf + total , len - total, 0);
+ if (curr <= 0) break;
+ total += curr;
+ } while (total < len);
-extern struct opts opts;
+ return total;
+}
#endif /* MANDELBROT_H */
diff --git a/src/server.c b/src/server.c
@@ -0,0 +1,186 @@
+#define _XOPEN_SOURCE 700
+#define _DEFAULT_SOURCE 1
+
+#include "mandelbrot.h"
+
+#include <poll.h>
+
+#include <sys/socket.h>
+#include <netdb.h>
+
+#include <sys/sysinfo.h>
+
+#include <getopt.h>
+
+struct opts {
+ int verbose;
+ int threads;
+ char *host, *port;
+ size_t max_clients;
+} opts = {
+ .verbose = 0,
+ .threads = 1,
+ .host = "localhost",
+ .port = NULL,
+ .max_clients = 16,
+};
+
+#define OPTSTR "hvt:a:p:"
+
+static void
+usage(char *prog)
+{
+ fprintf(stderr, "Usage: %s [-hv] [-t <threads>] [-a <addr> -p <port>] "
+ "[-c <max-clients>]\n", prog);
+ fprintf(stderr, "\t-h : display usage information\n");
+ fprintf(stderr, "\t-v : enable verbose logging\n");
+ fprintf(stderr, "\t-t : set number of threads to use (default: 1)\n");
+ fprintf(stderr, "\t-a : set socket addr to bind to (default: localhost)\n");
+ fprintf(stderr, "\t-p : set socket port to bind to (default: any)\n");
+ fprintf(stderr, "\t-c : set maximum concurrent clients (default: 16)\n");
+}
+
+static int
+parse_opts(int argc, char **argv, struct opts *opts)
+{
+ int opt;
+ while ((opt = getopt(argc, argv, OPTSTR)) > 0) {
+ switch (opt) {
+ case 'v':
+ opts->verbose = 1;
+ break;
+
+ case 't':
+ opts->threads = strtoull(optarg, NULL, 10);
+ if (opts->threads == 0) {
+ opts->threads = get_nprocs_conf();
+ fprintf(stderr, "info: using %d threads\n", opts->threads);
+ }
+ break;
+
+ case 'a':
+ opts->host = optarg;
+ break;
+
+ case 'p':
+ opts->port = optarg;
+ break;
+
+ case 'c':
+ opts->max_clients = strtoull(optarg, NULL, 10);
+ if (opts->max_clients == 0)
+ opts->max_clients = 1;
+ break;
+
+ default:
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int
+bind_socket(char const *host, char const *port)
+{
+ struct addrinfo hints = {
+ .ai_family = AF_UNSPEC,
+ .ai_socktype = SOCK_STREAM,
+ .ai_protocol = IPPROTO_TCP,
+ .ai_flags = AI_PASSIVE,
+ }, *addrinfo, *ptr;
+
+ int res;
+ if ((res = getaddrinfo(host, port, &hints, &addrinfo))) {
+ fprintf(stderr, "getaddrinfo(): %s\n", gai_strerror(res));
+ return -1;
+ }
+
+ int fd;
+ for (ptr = addrinfo; ptr; ptr = ptr->ai_next) {
+ fd = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
+ if (fd < 0)
+ continue;
+
+ if (bind(fd, ptr->ai_addr, ptr->ai_addrlen) < 0) {
+ close(fd);
+ continue;
+ }
+
+ if (listen(fd, 16) < 0) {
+ close(fd);
+ continue;
+ }
+
+ break;
+ }
+
+ freeaddrinfo(addrinfo);
+
+ if (!ptr)
+ return -1;
+
+ struct sockaddr_storage addr;
+ socklen_t addrlen = sizeof addr;
+ getsockname(fd, (struct sockaddr *) &addr, &addrlen);
+
+ char hostbuf[NI_MAXHOST], servbuf[NI_MAXSERV];
+ getnameinfo((struct sockaddr *) &addr, addrlen,
+ hostbuf, sizeof hostbuf, servbuf, sizeof servbuf,
+ NI_NUMERICHOST | NI_NUMERICSERV);
+
+ printf("Bound socket to %s:%s\n", hostbuf, servbuf);
+
+ return fd;
+}
+
+int
+main(int argc, char **argv)
+{
+ if (parse_opts(argc, argv, &opts) < 0) {
+ usage(argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ if (opts.verbose) {
+ fprintf(stderr, "info: verbose: %d\n", opts.verbose);
+ fprintf(stderr, "info: threads: %d\n", opts.threads);
+ fprintf(stderr, "info: sockaddr: %s:%s\n",
+ opts.host, opts.port ? opts.port : "0");
+ }
+
+ int socket = bind_socket(opts.host, opts.port);
+ assert(socket > 0);
+
+ while (1) {
+ int client = accept(socket, NULL, NULL);
+ printf("[%d] accept()\n", client);
+
+ struct render_region region;
+ size_t nbytes_recv = recvall(client, ®ion, sizeof region);
+ printf("[%d] recv() %zu bytes\n", client, nbytes_recv);
+
+ size_t pixels = region.xres * region.yres;
+
+ uint32_t *iterbuf = malloc(pixels * sizeof *iterbuf);
+ assert(iterbuf);
+
+ float *abs2buf = malloc(pixels * sizeof *abs2buf);
+ assert(abs2buf);
+
+ render_region(®ion, iterbuf, abs2buf);
+
+ size_t nbytes_send = 0;
+ nbytes_send += sendall(client, iterbuf, pixels * sizeof *iterbuf);
+ nbytes_send += sendall(client, abs2buf, pixels * sizeof *abs2buf);
+ printf("[%d] send() %zu bytes\n", client, nbytes_send);
+
+ free(abs2buf);
+ free(iterbuf);
+
+ close(client);
+ printf("[%d] close()\n", client);
+ }
+
+ exit(EXIT_SUCCESS);
+}