mandelbrot

Mandelbrot viewer written in C and GLSL
git clone anongit@rnpnr.xyz:mandelbrot.git
Log | Files | Refs | Feed | LICENSE

Commit: 0bf15dc5e7dca2380ac36205fc781de8cc034872
Author: Randy Palamar
Date:   Wed, 22 May 2024 19:19:53 -0600

start drawing the thing

Diffstat:
ALICENSE | 13+++++++++++++
Abuild.sh | 9+++++++++
Afrag.glsl | 34++++++++++++++++++++++++++++++++++
Amain.c | 260+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Avert.glsl | 9+++++++++
5 files changed, 325 insertions(+), 0 deletions(-)

diff --git a/LICENSE b/LICENSE @@ -0,0 +1,13 @@ +© 2024 Randy Palamar <randy@rnpnr.xyz> + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/build.sh b/build.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +set -x + +cflags="-march=native -O3 -g3 -Wall " +cflags="$cflags $(pkg-config --cflags glfw3 gl)" +ldflags=$(pkg-config --static --libs glfw3 gl) + +cc $cflags main.c $ldflags -o mandelbrot diff --git a/frag.glsl b/frag.glsl @@ -0,0 +1,34 @@ +/* see LICENSE for licensing details */ +#version 460 core + +out vec4 colour; + +uniform uvec2 u_screen_dim; + +vec2 map_mandelbrot(vec2 v) +{ + float x = v.x * 2.47 - 2; + float y = v.y * 2.14 - 1.12; + return vec2(x, y); +} + +void main() +{ + vec2 xy0 = map_mandelbrot(gl_FragCoord.xy / u_screen_dim.xy); + colour = vec4(0, 0, 0.1, 1); + + int i; + float xx = 0, yy = 0; + vec2 xy = xy0; + for (i = 0; i < 256 && xx + yy < 4.0; i++) { + xx = xy.x * xy.x; + yy = xy.y * xy.y; + xy = vec2(xx - yy + xy0.x, 2 * xy.x * xy.y + xy0.y); + } + if (i < 256) { + float r = (i >> 0) / 255.0; + float g = (i >> 2) / 255.0; + float b = (i >> 3) / 255.0; + colour = vec4(r, g, b, 1.0); + } +} diff --git a/main.c b/main.c @@ -0,0 +1,260 @@ +/* see LICENSE for licensing details */ +#include <fcntl.h> +#include <stddef.h> +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <unistd.h> + +#include <GL/gl.h> +#include <GLES3/gl32.h> +#include <GLFW/glfw3.h> + +typedef float f32; +typedef uint8_t u8; +typedef int32_t i32; +typedef uint32_t u32; +typedef ptrdiff_t size; + +typedef struct { size len; u8 *data; } s8; + +typedef union { + struct { u8 a, b, g, r; }; + u32 rgba; +} Colour; + +static struct { + GLFWwindow *window; + u32 vao, vbo; + i32 pid; + i32 height, width; + i32 u_screen_dim; + Colour clear_colour; +} g_glctx; + +static void +debug_logger(u32 src, u32 type, u32 id, u32 lvl, i32 len, const char *msg, const void *data) +{ + (void)src; (void)type; (void)id; (void)data; + fputs("[gl error ", stderr); + switch(lvl) { + case GL_DEBUG_SEVERITY_HIGH: fputs("HIGH]: ", stderr); break; + case GL_DEBUG_SEVERITY_MEDIUM: fputs("MEDIUM]: ", stderr); break; + case GL_DEBUG_SEVERITY_LOW: fputs("LOW]: ", stderr); break; + default: fputs("(default)]: ", stderr); break; + } + fwrite(msg, 1, len, stderr); + fputc('\n', stderr); +} + +static void +error_callback(int code, const char *desc) +{ + fprintf(stderr, "GLFW Error (0x%04X): %s\n", code, desc); +} + +static void +fb_callback(GLFWwindow *win, i32 w, i32 h) +{ + g_glctx.height = h; + g_glctx.width = w; + glViewport(0, 0, w, h); + if (g_glctx.u_screen_dim != -1) + glUniform2ui(g_glctx.u_screen_dim, w, h); +} + +static void +key_callback(GLFWwindow *win, i32 key, i32 sc, i32 action, i32 mod) +{ + (void)sc; (void)mod; + if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) + glfwSetWindowShouldClose(win, GL_TRUE); +} + +static void +clear_colour(Colour c) +{ + glClearColor(c.r / 255.0f, c.b / 255.0f, c.g / 255.0f, c.a / 255.0f); + glClear(GL_COLOR_BUFFER_BIT); +} + +static void +init_renderer(void) +{ + glDebugMessageCallback(debug_logger, NULL); + glEnable(GL_DEBUG_OUTPUT); + glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, + GL_DEBUG_SEVERITY_NOTIFICATION, + 0, 0, GL_FALSE); + + glGenVertexArrays(1, &g_glctx.vao); + glBindVertexArray(g_glctx.vao); + + f32 vertices[] = { + -1.0f, 1.0f, + -1.0f, -1.0f, + 1.0f, 1.0f, + 1.0f, -1.0f, + }; + glGenBuffers(1, &g_glctx.vbo); + glBindBuffer(GL_ARRAY_BUFFER, g_glctx.vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), &vertices, GL_STATIC_DRAW); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0); +} + +static i32 +spawn_window(void) +{ + GLFWmonitor *mon = glfwGetPrimaryMonitor(); + if (!mon) + return -1; + glfwGetMonitorWorkarea(mon, NULL, NULL, &g_glctx.width, &g_glctx.height); + + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + + g_glctx.window = glfwCreateWindow(g_glctx.width, g_glctx.height, + "Mandelbrot Viewer", NULL, NULL); + if (g_glctx.window == NULL) + return -1; + + glfwMakeContextCurrent(g_glctx.window); + + /* disable vsync */ + glfwSwapInterval(0); + + glfwSetFramebufferSizeCallback(g_glctx.window, fb_callback); + glfwSetKeyCallback(g_glctx.window, key_callback); + //glfwSetScrollCallback(glctx.window, scroll_callback); + + g_glctx.clear_colour = (Colour){ .r = 64, .b = 64, .g = 64, .a = 255 }; + clear_colour(g_glctx.clear_colour); + + init_renderer(); + + return 0; +} + +static s8 +read_whole_file(char *file) +{ + s8 ret = {0}; + i32 fd = open(file, O_RDONLY); + if (fd < 0) + return ret; + ret.len = lseek(fd, 0L, SEEK_END); + ret.data = malloc(ret.len); + lseek(fd, 0L, SEEK_SET); + if (ret.data == NULL) { + ret.len = 0; + close(fd); + return ret; + } + if (ret.len != read(fd, ret.data, ret.len)) { + ret.len = 0; + free(ret.data); + } + close(fd); + return ret; +} + +static u32 +compile_shader(u32 type, s8 s) +{ + u32 sid = glCreateShader(type); + glShaderSource(sid, 1, (const char **)&s.data, (int *)&s.len); + glCompileShader(sid); + + i32 res = 0; + glGetShaderiv(sid, GL_COMPILE_STATUS, &res); + if (res != GL_TRUE) { + i32 len, len2; + glGetShaderiv(sid, GL_INFO_LOG_LENGTH, &len); + char *data = malloc(len); + glGetShaderInfoLog(sid, len, &len2, data); + fputs("compile_shader: ", stderr); + fwrite(data, 1, len2, stderr); + fputc('\n', stderr); + free(data); + glDeleteShader(sid); + return 0; + } + + return sid; +} + +static i32 +program_from_files(char *vert, char *frag) +{ + s8 vertex = read_whole_file(vert); + s8 fragment = read_whole_file(frag); + if (vertex.len == 0 || fragment.len == 0) + return -1; + i32 pid = glCreateProgram(); + u32 vid = compile_shader(GL_VERTEX_SHADER, vertex); + u32 fid = compile_shader(GL_FRAGMENT_SHADER, fragment); + + if (fid == 0 || vid == 0) + return -1; + + glAttachShader(pid, vid); + glAttachShader(pid, fid); + glLinkProgram(pid); + glValidateProgram(pid); + glDeleteShader(vid); + glDeleteShader(fid); + glUseProgram(pid); + + g_glctx.u_screen_dim = glGetUniformLocation(pid, "u_screen_dim"); + if (g_glctx.u_screen_dim != -1) + glUniform2ui(g_glctx.u_screen_dim, g_glctx.width, g_glctx.height); + + free(vertex.data); + free(fragment.data); + + return pid; +} + +int +main(void) +{ + if (!glfwInit()) + return -1; + glfwSetErrorCallback(error_callback); + + spawn_window(); + + if (g_glctx.window == NULL) { + glfwTerminate(); + return -1; + } + + g_glctx.pid = program_from_files("vert.glsl", "frag.glsl"); + if (g_glctx.pid == -1) { + glfwTerminate(); + return -1; + } + + u32 fcount = 0; + f32 last_time = 0; + while (!glfwWindowShouldClose(g_glctx.window)) { + glfwPollEvents(); + + f32 current_time = glfwGetTime(); + f32 dt = current_time - last_time; + last_time = current_time; + if (++fcount > 1000) { + printf("FPS: %0.03f | dt = %0.03f [ms]\n", + 1 / dt, dt * 1e3); + fcount = 0; + } + + clear_colour(g_glctx.clear_colour); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + glfwSwapBuffers(g_glctx.window); + } + + return 0; +} diff --git a/vert.glsl b/vert.glsl @@ -0,0 +1,9 @@ +/* see LICENSE for licensing details */ +#version 460 core + +layout(location = 0) in vec4 position; + +void main() +{ + gl_Position = position; +}