#include #include #include #include #include #include #include #include // 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; }