commit c53860987a368f8e2f80c722c7b0bd48631e69f9
parent c55134c703ae3a7384f96c0290c15ebf444bac09
Author: MikoĊaj Lenczewski <mblenczewski@gmail.com>
Date: Sat, 18 Jan 2025 22:51:50 +0000
Implement basic cpu mandelbrot renderer
Diffstat:
3 files changed, 304 insertions(+), 3 deletions(-)
diff --git a/build.sh b/build.sh
@@ -6,7 +6,7 @@ WARNINGS="-Wall -Wextra -Wpedantic ${WERROR:+-Werror}"
CFLAGS="-std=c11 -g -O0"
CPPFLAGS=""
-LDFLAGS=""
+LDFLAGS="-lm"
set -ex
diff --git a/src/main.c b/src/main.c
@@ -1,9 +1,278 @@
+#define _XOPEN_SOURCE 700
+
#include "mandelbrot.h"
+#include <getopt.h>
+
+struct opts opts = {
+ .verbose = 0,
+ .xres = 800, .yres = 600,
+ .max_iters = 100,
+ .palette = PALETTE_SMOOTH,
+ .outfile = "/tmp/mandelbrot.bmp",
+};
+
+static void
+usage(char *prog)
+{
+ fprintf(stderr, "Usage: %s [-hv] [-r <xres>x<yres> ] [-i <maxiters>] "
+ "[-p 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-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");
+ fprintf(stderr, "\t-o : set output filename (default: /tmp/mandelbrot.bmp)\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 'r': {
+ char *saveptr;
+ char *xres = strtok_r(optarg, "x", &saveptr);
+ char *yres = strtok_r(NULL, "x", &saveptr);
+
+ if (!xres || !yres) {
+ fprintf(stderr, "Must provide resolution in format: <xres>x<yres>\n");
+ return -1;
+ }
+
+ if ((opts->xres = strtoull(xres, NULL, 10)) == 0) {
+ fprintf(stderr, "Failed to parse x-resolution: %s\n", xres);
+ return -1;
+ }
+
+ if ((opts->yres = strtoull(yres, NULL, 10)) == 0) {
+ fprintf(stderr, "Failed to parse y-resolution: %s\n", yres);
+ return -1;
+ }
+ } break;
+
+ case 'i':
+ if ((opts->max_iters = strtoull(optarg, NULL, 10)) == 0) {
+ fprintf(stderr, "Failed to parse max iters: %s\n", optarg);
+ return -1;
+ }
+ break;
+
+ case 'p':
+ if (strcmp(optarg, "mono") == 0) {
+ opts->palette = PALETTE_MONOCHROME;
+ } else if (strcmp(optarg, "gray") == 0) {
+ opts->palette = PALETTE_GRAYSCALE;
+ } else if (strcmp(optarg, "hist") == 0) {
+ opts->palette = PALETTE_HISTOGRAM;
+ } else if (strcmp(optarg, "smooth") == 0) {
+ opts->palette = PALETTE_SMOOTH;
+ } else {
+ fprintf(stderr, "Unknown palette: %s\n", optarg);
+ return -1;
+ }
+ break;
+
+ case 'o':
+ opts->outfile = optarg;
+ break;
+
+ default:
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+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
+palette(float abs2, uint32_t iters, uint32_t max_iters, enum palette_type type)
+{
+ float f = (float) iters / max_iters;
+
+ switch (type) {
+ case PALETTE_MONOCHROME:
+ return (iters == max_iters) ? pixel(0, 0, 0, 255)
+ : pixel(255, 255, 255, 255);
+
+ case PALETTE_GRAYSCALE:
+ return (iters == max_iters) ? pixel(0, 0, 0, 255)
+ : pixel(f * 255, f * 255, f * 255, 255);
+
+ case PALETTE_HISTOGRAM: {
+ // TODO: actually implement histogram colouring
+
+ return (iters == max_iters) ? pixel(0, 0, 0, 255)
+ : pixel(f * 255, f * 255, f * 255, 255);
+ } break;
+
+ case PALETTE_SMOOTH: {
+ float smooth_iters = (iters + 1) - (logf(logf(sqrtf(abs2))) / M_LN2);
+ float sf = CLAMP(smooth_iters / max_iters, 0.0f, 1.0f);
+ float v = 255 * (1 - sf);
+
+ return (iters == max_iters) ? pixel(0, 0, 0, 255) : pixel(v, v, v, 255);
+ } break;
+ }
+}
+
+static void
+render_mandelbrot(uint32_t *buf, size_t width, size_t height,
+ size_t max_iters, 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;
+ }
+ }
+}
+
+static void
+write_bitmap(int fd, uint32_t const *buf, size_t width, size_t height)
+{
+ uint32_t pixels = width * height;
+ uint16_t pixel_size = 8 * sizeof *buf;
+ uint32_t pixel_bytes = pixels * sizeof *buf;
+
+ uint32_t dib_header_size = 40;
+ uint32_t bitmap_header_size = 14;
+ uint32_t header_size = bitmap_header_size + dib_header_size;
+
+ uint32_t file_size = header_size + pixel_bytes;
+ uint32_t reserved = 0;
+
+ int32_t xres = width, yres = -height;
+ uint16_t planes = 0;
+ uint32_t compression = 0;
+ uint32_t xdpi = 0, ydpi = 0;
+ uint32_t palette = 0, important = 0;
+
+ // bitmap header
+ write(fd, "BM", 2);
+ write(fd, &file_size, sizeof file_size);
+ write(fd, &reserved, sizeof reserved);
+ write(fd, &header_size, sizeof header_size);
+
+ // dib header: BITMAPINFOHEADER
+ write(fd, &dib_header_size, sizeof dib_header_size);
+ write(fd, &xres, sizeof xres);
+ write(fd, &yres, sizeof yres);
+ write(fd, &planes, sizeof planes);
+ write(fd, &pixel_size, sizeof pixel_size);
+ write(fd, &compression, sizeof compression);
+ write(fd, &pixel_bytes, sizeof pixel_bytes);
+ write(fd, &xdpi, sizeof xdpi);
+ write(fd, &ydpi, sizeof ydpi);
+ write(fd, &palette, sizeof palette);
+ write(fd, &important, sizeof important);
+
+ // pixel data
+ write(fd, buf, pixel_bytes);
+}
+
int
main(int argc, char **argv)
{
- printf("Hello, World!\n");
+ if (parse_opts(argc, argv, &opts) < 0) {
+ usage(argv[0]);
+ exit(EXIT_FAILURE);
+ }
- return 0;
+ if (opts.verbose) {
+ fprintf(stderr, "info: verbose: %d\n", opts.verbose);
+ fprintf(stderr, "info: resolution: %" PRIu64 "x%" PRIu64 "\n",
+ opts.xres, opts.yres);
+ fprintf(stderr, "info: max iters: %" PRIu64 "\n", opts.max_iters);
+ fprintf(stderr, "info: palette: %d\n", opts.palette);
+ fprintf(stderr, "info: output: %s\n", opts.outfile);
+ }
+
+ 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);
+
+ int fd = creat(opts.outfile, 0644);
+ assert(fd > 0);
+
+ write_bitmap(fd, buffer, opts.xres, opts.yres);
+
+ close(fd);
+
+ exit(EXIT_SUCCESS);
}
diff --git a/src/mandelbrot.h b/src/mandelbrot.h
@@ -2,6 +2,38 @@
#define MANDELBROT_H
#include <assert.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdint.h>
#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <fcntl.h>
+
+#include <math.h>
+
+/* ===========================================================================
+ */
+
+enum palette_type {
+ PALETTE_MONOCHROME,
+ PALETTE_GRAYSCALE,
+ PALETTE_HISTOGRAM,
+ PALETTE_SMOOTH,
+};
+
+struct opts {
+ int verbose;
+ uint64_t xres, yres;
+ uint64_t max_iters;
+ enum palette_type palette;
+ char *outfile;
+};
+
+#define OPTSTR "hvr:i:p:o:"
+
+extern struct opts opts;
#endif /* MANDELBROT_H */