diff options
-rw-r--r-- | .clang-format | 6 | ||||
-rw-r--r-- | .gitignore | 25 | ||||
-rw-r--r-- | license | 22 | ||||
-rw-r--r-- | mandle.c | 407 | ||||
-rw-r--r-- | meson.build | 19 | ||||
-rw-r--r-- | readme.md | 11 | ||||
-rw-r--r-- | subprojects/sdl2.wrap | 12 |
7 files changed, 502 insertions, 0 deletions
diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..a3ef233 --- /dev/null +++ b/.clang-format @@ -0,0 +1,6 @@ +BasedOnStyle: LLVM +IndentWidth: 8 +UseTab: Always +BreakBeforeBraces: Linux +AllowShortIfStatementsOnASingleLine: false +IndentCaseLabels: false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..35df969 --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +*.o +*.ko +*.obj +*.elf +*.gch +*.pch +*.dll +*.so +*.so.* +*.dylib +*.exe +*.out +*.app +*.pdb +/build*/ +/subprojects/* +!/subprojects/*.wrap +meson-logs +meson-private +meson_benchmark_setup.dat +meson_test_setup.dat +build.ninja +.ninja_deps +.ninja_logs +compile_commands.json @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2022 Marc Pervaz Boocha + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/mandle.c b/mandle.c new file mode 100644 index 0000000..318675f --- /dev/null +++ b/mandle.c @@ -0,0 +1,407 @@ +#include <complex.h> +#include <math.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <SDL2/SDL.h> + +// Some polyfills for complex numbers +#if !defined(CMPLX) +#define CMPLX(x, y) ((double complex)((double)(x) + I * (double)(y))) +#define CMPLXF(x, y) ((float complex)((float)(x) + I * (float)(y))) +#define CMPLXL(x, y) \ + ((long double complex)((long double)(x) + I * (long double)(y))) +#endif + +static uintmax_t mandle(long double complex const c) +{ + const uintmax_t max = UINTMAX_MAX; + if ((pow(creal(c) - 0.25, 2) + pow(cimag(c), 2)) * + (pow(creal(c), 2) + (creal(c) / 2) + pow(cimag(c), 2) - + 0.1875) < + pow(cimag(c), 2) / 4 || + pow(creal(c) + 1, 2) + pow(cimag(c), 2) < 0.0625) { + return 0; + } + long double complex z = c; + long double complex dz = CMPLXL(1, 0); + + for (uintmax_t iteration = 0; iteration <= max; iteration++) { + if (pow(creal(dz), 2) + pow(cimag(dz), 2) * pow(cimag(dz), 2) < + 1e-300) { + break; + } + if (pow(creal(z), 2) + pow(cimag(z), 2) > 4.0) { + return iteration; + } + + z = z * z + c; + dz *= 2 * z; + } + return 0; +} + +static inline void putpixel(SDL_Surface *surface, SDL_Point const point, + SDL_Color color) +{ + if (surface->w <= point.x || surface->h <= point.y) { + return; + } + uint32_t const pixel = + SDL_MapRGBA(surface->format, color.r, color.g, color.b, color.a); + int bpp = surface->format->BytesPerPixel; + if (SDL_MUSTLOCK(surface)) { + SDL_LockSurface(surface); + } + + uint8_t *p = (uint8_t *)surface->pixels + point.y * surface->pitch + + point.x * bpp; + + switch (bpp) { + case 1: + *p = pixel; + break; + case 2: + *(uint16_t *)p = pixel; + break; + case 3: + if (SDL_BYTEORDER == SDL_BIG_ENDIAN) { + p[0] = pixel >> 16 & UINT8_MAX; + p[1] = pixel >> 8 & UINT8_MAX; + p[2] = pixel & UINT8_MAX; + } else { + p[0] = pixel & UINT8_MAX; + p[1] = (pixel >> 8) & UINT8_MAX; + p[2] = (pixel >> 16) & UINT8_MAX; + } + break; + case 4: + *(uint32_t *)p = pixel; + break; + } + + if (SDL_MUSTLOCK(surface)) { + SDL_UnlockSurface(surface); + } +} + +static inline SDL_Color color_scheme(uintmax_t iteration) +{ + uint8_t color_scale = iteration & UINT8_MAX; + unsigned type = iteration >> 8; + unsigned color_mask = 255 * (type % 5) / 4; + SDL_Color color = {.r = 0, .b = 0, .g = 0, .a = 255}; + switch ((type / 5) % 19) { + case 0: + color = (SDL_Color){ + .r = 0, .b = color_mask, .g = color_scale, .a = 255}; + break; + case 1: + color = (SDL_Color){ + .r = 255, .b = color_mask, .g = color_scale, .a = 255}; + break; + case 2: + color = (SDL_Color){ + .r = color_mask, .b = 0, .g = color_scale, .a = 255}; + break; + case 3: + color = (SDL_Color){ + .r = color_mask, .b = 255, .g = color_scale, .a = 255}; + break; + case 4: + color = (SDL_Color){.r = color_mask, + .b = color_mask, + .g = color_scale, + .a = 255}; + break; + case 5: + color = (SDL_Color){ + .r = 0, .b = color_scale, .g = color_mask, .a = 255}; + break; + case 6: + color = (SDL_Color){ + .r = 255, .b = color_scale, .g = color_mask, .a = 255}; + break; + case 7: + color = (SDL_Color){ + .r = color_mask, .b = color_scale, .g = 0, .a = 255}; + break; + case 8: + color = (SDL_Color){ + .r = color_mask, .b = color_scale, .g = 255, .a = 255}; + break; + case 9: + color = (SDL_Color){.r = color_mask, + .b = color_scale, + .g = color_mask, + .a = 255}; + break; + case 10: + color = (SDL_Color){ + .r = color_scale, .b = 0, .g = color_mask, .a = 255}; + break; + case 11: + color = (SDL_Color){ + .r = color_scale, .b = 255, .g = color_mask, .a = 255}; + break; + case 12: + color = (SDL_Color){ + .r = color_scale, .b = color_mask, .g = 0, .a = 255}; + break; + case 13: + color = (SDL_Color){ + .r = color_scale, .b = color_mask, .g = 255, .a = 255}; + break; + case 14: + color = (SDL_Color){.r = color_scale, + .b = color_mask, + .g = color_mask, + .a = 255}; + break; + case 15: + color = (SDL_Color){.r = color_scale, + .b = color_scale, + .g = color_mask, + .a = 255}; + break; + case 16: + color = (SDL_Color){.r = color_scale, + .b = color_mask, + .g = color_scale, + .a = 255}; + break; + case 17: + color = (SDL_Color){.r = color_mask, + .b = color_scale, + .g = color_scale, + .a = 255}; + break; + case 18: + color = (SDL_Color){.r = color_scale, + .b = color_scale, + .g = color_scale, + .a = 255}; + break; + } + return (type / (5 * 19)) % 2 ? color + : (SDL_Color){.r = ~color.r, + .b = ~color.b, + .g = ~color.g, + .a = color.a}; +} + +struct mandle_thr { + SDL_Surface *surface; + SDL_Point point; + SDL_mutex *mutex; + SDL_cond *cond; + SDL_mutex *cond_mutex; + uint32_t job_id; + char sleep_no; + bool quit; + long double complex centre; + long double scale; +}; + +static int mandle_thr(void *ptr) +{ + struct mandle_thr *data = ptr; + uint32_t id = 0; + SDL_LockMutex(data->cond_mutex); + data->sleep_no++; + SDL_CondWait(data->cond, data->cond_mutex); + data->sleep_no--; + SDL_UnlockMutex(data->cond_mutex); + SDL_LockMutex(data->mutex); + while (true) { + if (data->quit) { + SDL_UnlockMutex(data->mutex); + return 0; + } + if (id != data->job_id) { + id = data->job_id; + } + SDL_Point point = data->point; + if (data->point.x < data->surface->w) { + data->point.x++; + } else if (data->point.y < data->surface->h) { + data->point.y++; + data->point.x = 0; + } else { + SDL_UnlockMutex(data->mutex); + SDL_LockMutex(data->cond_mutex); + data->sleep_no++; + SDL_CondWait(data->cond, data->cond_mutex); + data->sleep_no--; + SDL_UnlockMutex(data->cond_mutex); + SDL_LockMutex(data->mutex); + id = data->job_id; + } + long double complex z = + data->scale * 2.50 * + CMPLXL((long double)point.x + 1e-10 - (long double)data->surface->w/2, + (long double)point.y + 1e-10 - (long double)data->surface->h/2) / + (long double)data->surface->w + + data->centre; + SDL_UnlockMutex(data->mutex); + + SDL_Color color = color_scheme(mandle(z)); + + SDL_LockMutex(data->mutex); + putpixel(data->surface, point, color); + } +} + +static uint32_t update_screen(uint32_t interval, void *param) +{ + SDL_Event event; + event.type = SDL_USEREVENT; + event.user = (SDL_UserEvent){ + .type = SDL_USEREVENT, + .code = *(uint32_t *)param, + .data1 = NULL, + .data2 = NULL, + }; + + SDL_PushEvent(&event); + return interval; +} + +int main(int argc, char *argv[argc + 1]) +{ + (void)argv; + SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER); + SDL_Window *window = SDL_CreateWindow("Mandle", SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, 640, 480, + SDL_WINDOW_RESIZABLE); + if (!window) { + SDL_LogMessage(SDL_LOG_CATEGORY_ERROR, SDL_LOG_PRIORITY_ERROR, + "Unable to create window: %s", SDL_GetError()); + return EXIT_FAILURE; + } + + long double complex centre = CMPLXL(-0.765, 0); + long double scale = 1; + long double complex const org_centre = centre; + + uint32_t TimeEvent = SDL_RegisterEvents(1); + SDL_TimerID timer = SDL_AddTimer(40, update_screen, &TimeEvent); + SDL_Event event; + bool render = true; + + struct mandle_thr data = { + .mutex = SDL_CreateMutex(), + .cond_mutex = SDL_CreateMutex(), + .cond = SDL_CreateCond(), + .quit = false, + .job_id = 0, + }; + + SDL_Thread *thread[SDL_GetCPUCount()]; + for (size_t i = 0; i < sizeof thread / sizeof thread[0]; ++i) { + thread[i] = SDL_CreateThread(mandle_thr, "Renderer", &data); + if (!thread[i]) { + SDL_LogMessage( + SDL_LOG_CATEGORY_RENDER, SDL_LOG_PRIORITY_ERROR, + "SDL_CreateThread failed: %s\n", SDL_GetError()); + } + } + + while (!data.quit) { + SDL_WaitEvent(&event); + switch (event.type) { + case SDL_QUIT: + data.quit = true; + break; + case SDL_WINDOWEVENT: + switch (event.window.event) { + case SDL_WINDOWEVENT_SIZE_CHANGED: + render = true; + break; + } + break; + case SDL_KEYDOWN: + switch (event.key.keysym.sym) { + case SDLK_UP: + centre -= CMPLXL(0, 0.1 * scale); + render = true; + break; + case SDLK_DOWN: + centre += CMPLXL(0, 0.1 * scale); + render = true; + break; + case SDLK_RIGHT: + centre += CMPLXL(0.1 * scale, 0); + render = true; + break; + case SDLK_LEFT: + centre -= CMPLXL(0.1 * scale, 0); + render = true; + break; + case SDLK_EQUALS: + case SDLK_KP_PLUS: + scale /= 2; + render = true; + break; + case SDLK_MINUS: + scale *= 2; + render = true; + break; + case SDLK_0: + centre = org_centre; + scale = 1; + render = true; + break; + case SDLK_q: { + data.quit = true; + break; + } + case SDLK_F11: { + bool flag = SDL_GetWindowFlags(window) & + SDL_WINDOW_FULLSCREEN_DESKTOP; + SDL_SetWindowFullscreen( + window, + flag ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP); + break; + } + } + break; + case SDL_USEREVENT: { + SDL_UpdateWindowSurface(window); + break; + } + } + if (render) { + char title[50] = {0}; + snprintf(title, sizeof title, "Mandle %LG%+LGi x%Lf", + creall(centre), cimagl(centre), 1 / scale); + SDL_SetWindowTitle(window, title); + SDL_LockMutex(data.mutex); + data.job_id++; + data.point = (SDL_Point){.x = 0, .y = 0}; + data.centre = centre; + data.surface = SDL_GetWindowSurface(window); + data.scale = scale; + SDL_FillRect(data.surface, NULL, + SDL_MapRGB(data.surface->format, 0, 0, 0)); + SDL_UnlockMutex(data.mutex); + SDL_CondBroadcast(data.cond); + render = false; + } + } + SDL_CondBroadcast(data.cond); + for (size_t i = 0; i < sizeof thread / sizeof thread[0]; ++i) { + SDL_WaitThread(thread[i], NULL); + } + SDL_RemoveTimer(timer); + SDL_DestroyMutex(data.mutex); + SDL_DestroyMutex(data.cond_mutex); + SDL_DestroyCond(data.cond); + + SDL_DestroyWindow(window); + SDL_Quit(); + return EXIT_SUCCESS; +} diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..2daafa7 --- /dev/null +++ b/meson.build @@ -0,0 +1,19 @@ +project('mandle', 'c', + version : '0.1', + license: 'MIT', + default_options : [ + 'c_std=c17', + 'warning_level=3', + 'b_ndebug=if-release' + ]) + +cc = meson.get_compiler('c') + +m_dep = cc.find_library('m', required : false) +sdl_dep = dependency('sdl2') + +executable('mandle', + 'mandle.c', + dependencies : [sdl_dep, m_dep], + win_subsystem: 'windows', + install : true) diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..89081a9 --- /dev/null +++ b/readme.md @@ -0,0 +1,11 @@ +# Mandle +This is an interactive mandlebrot viewer written in C11 and uses [SDL2](https://www.libsdl.org/) for rendering. + +## Build +It needs [SDL2](https://www.libsdl.org/) and [meson](https://mesonbuild.com/) to build. + +```bash +meson build +ninja -C build +ninja -C build install +```
\ No newline at end of file diff --git a/subprojects/sdl2.wrap b/subprojects/sdl2.wrap new file mode 100644 index 0000000..b1affeb --- /dev/null +++ b/subprojects/sdl2.wrap @@ -0,0 +1,12 @@ +[wrap-file] +directory = SDL2-2.0.20 +source_url = https://libsdl.org/release/SDL2-2.0.20.tar.gz +source_filename = SDL2-2.0.20.tar.gz +source_hash = c56aba1d7b5b0e7e999e4a7698c70b63a3394ff9704b5f6e1c57e0c16f04dd06 +patch_filename = sdl2_2.0.20-3_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/sdl2_2.0.20-3/get_patch +patch_hash = ade644ba46cefa4f1f9e57aa23bacc5dabf762d1f90d8416a1e1e4b0b7a188c4 + +[provide] +sdl2 = sdl2_dep + |