mandelbrot

mandelbrot.git
git clone git://git.lenczewski.org/mandelbrot.git
Log | Files | Refs

commit 6d2cd51a177122b7867717190133cfde56a7e2d4
parent 486a6217433139495d32052b0fc4ee6545c38094
Author: MikoĊ‚aj Lenczewski <mblenczewski@gmail.com>
Date:   Sun, 19 Jan 2025 15:18:52 +0000

Initial server implementation

Diffstat:
Mbuild.sh | 6+++++-
Msrc/main.c | 228++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Msrc/mandelbrot.h | 118+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Asrc/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, &region, 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(&region, 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(&region, iterbuf, abs2buf); + paint_region(&region, 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, &region, 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(&region, 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); +}