#include "mandelbrot.h"
#include <SDL3/SDL_events.h>
#include <SDL3/SDL_init.h>
#include <SDL3/SDL_log.h>
#include <SDL3/SDL_oldnames.h>
#include <SDL3/SDL_rect.h>
#include <SDL3/SDL_render.h>
#include <SDL3/SDL_surface.h>
#include <SDL3/SDL_video.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
extern int points[WINDOW_WIDTH * WINDOW_HEIGHT];
#include <getopt.h>
int parse_arguments(int argc, char** argv, int* num_threads, int* max_iter) {
int opt;
int threads = 1;
int max_iterations = 100;
static struct option long_options[] = {{"threads", required_argument, 0, 't'},
{"iteration", required_argument, 0, 'i'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}};
while ((opt = getopt_long(argc, argv, "t:i:h", long_options, NULL)) != -1) {
switch (opt) {
case 't':
threads = atoi(optarg);
if (threads <= 0) {
fprintf(stderr, "Error: threads must be a positive integer\n");
return -1;
}
break;
case 'i':
max_iterations = atoi(optarg);
if (max_iterations <= 0) {
fprintf(stderr, "Error: max iterations must be a positive integer\n");
return -1;
}
break;
case 'h':
printf("Usage: %s [options]\n", argv[0]);
printf(" --threads N, -t N Number of threads to use (default: 1)\n");
printf(
" --iteration N, -i N Maximum iterations for Mandelbrot (default: 100)\n");
printf(" --help, -h Show this help message\n");
return -1;
default:
fprintf(stderr, "Use --help for usage information\n");
return -1;
}
}
*num_threads = threads;
*max_iter = max_iterations;
return 0;
}
state_t* init() {
state_t* state = malloc(sizeof(state_t));
if (!state) {
fprintf(stderr, "malloc failed\n");
return NULL;
}
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) {
fprintf(stderr, "SDL_Init failed: %s\n", SDL_GetError());
free(state);
return NULL;
}
SDL_Window* window =
SDL_CreateWindow(WINDOW_TITLE, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_HIDDEN);
if (window == NULL) {
fprintf(stderr, "CreateWindow failed: %s\n", SDL_GetError());
SDL_Quit();
free(state);
return NULL;
}
SDL_Surface* surface = SDL_GetWindowSurface(window);
if (!surface) {
fprintf(stderr, "GetWindowSurface failed: %s\n", SDL_GetError());
SDL_DestroyWindow(window);
SDL_Quit();
free(state);
return NULL;
}
SDL_Renderer* renderer = SDL_CreateSoftwareRenderer(surface);
if (renderer == NULL) {
fprintf(stderr, "CreateSoftwareRenderer failed: %s\n", SDL_GetError());
SDL_DestroyWindow(window);
SDL_DestroySurface(surface);
SDL_Quit();
free(state);
return NULL;
}
state->window = window;
state->surface = surface;
state->renderer = renderer;
return state;
}
typedef struct {
position_t* pos;
int start_row;
int end_row;
int max_iter;
} thread_data_t;
void* calculate_points_thread(void* arg) {
thread_data_t* data = (thread_data_t*)arg;
position_t* pos = data->pos;
int max_iter = data->max_iter;
for (int py = data->start_row; py < data->end_row; py++) {
for (int px = 0; px < WINDOW_WIDTH; px++) {
double cr = pos->x_min + (pos->x_max - pos->x_min) * px / (WINDOW_WIDTH - 1);
double ci = pos->y_min + (pos->y_max - pos->y_min) * py / (WINDOW_HEIGHT - 1);
points[py * WINDOW_WIDTH + px] = mandelbrot_iterations(cr, ci, max_iter);
}
}
return NULL;
}
void calculate_points_pthread(position_t* pos, int num_threads, int max_iteration) {
pthread_t threads[num_threads];
thread_data_t thread_data[num_threads];
int rows_per_thread = WINDOW_HEIGHT / num_threads;
int remaining_rows = WINDOW_HEIGHT % num_threads;
for (int t = 0; t < num_threads; t++) {
thread_data[t].pos = pos;
thread_data[t].start_row = t * rows_per_thread;
thread_data[t].end_row = (t + 1) * rows_per_thread;
thread_data[t].max_iter = max_iteration;
if (t == num_threads - 1) {
thread_data[t].end_row += remaining_rows;
}
pthread_create(&threads[t], NULL, calculate_points_thread, &thread_data[t]);
}
for (int t = 0; t < num_threads; t++) {
pthread_join(threads[t], NULL);
}
}
int main(int argc, char** argv) {
int num_threads;
int max_iteration;
int parse_result = parse_arguments(argc, argv, &num_threads, &max_iteration);
if (parse_result == -1) {
return 1;
}
state_t* state = init();
if (state == NULL) {
return -1;
}
state->num_threads = num_threads;
state->max_iteration = max_iteration;
printf("Using %d threads with %d max iterations\n", state->num_threads, state->max_iteration);
SDL_Event e;
bool running = true;
SDL_ShowWindow(state->window);
position_t position;
position.x_min = -2.0;
position.x_max = 1.0;
position.y_min = -1.2;
position.y_max = 1.2;
bool pos_updated = true;
char debug_text[256] = "";
bool is_dragging = false;
float drag_start_x, drag_start_y;
double drag_start_x_min, drag_start_x_max;
double drag_start_y_min, drag_start_y_max;
while (running) {
while (SDL_PollEvent(&e)) {
if (e.type == SDL_EVENT_QUIT) {
running = false;
}
if (e.type == SDL_EVENT_MOUSE_WHEEL) {
float mx, my;
SDL_GetMouseState(&mx, &my);
double cr =
position.x_min + (position.x_max - position.x_min) * mx / (WINDOW_WIDTH - 1);
double ci =
position.y_min + (position.y_max - position.y_min) * my / (WINDOW_HEIGHT - 1);
double range_y = position.y_max - position.y_min;
float factor = (e.wheel.y > 0) ? 0.9f : 1.1f;
double new_height = range_y * factor;
double aspect = (double)WINDOW_WIDTH / WINDOW_HEIGHT;
double new_width = new_height * aspect;
position.x_min = cr - (double)mx / (WINDOW_WIDTH - 1) * new_width;
position.x_max = position.x_min + new_width;
position.y_min = ci - (double)my / (WINDOW_HEIGHT - 1) * new_height;
position.y_max = position.y_min + new_height;
pos_updated = true;
}
if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
if (e.button.button == SDL_BUTTON_LEFT) {
is_dragging = true;
drag_start_x = (float)e.button.x;
drag_start_y = (float)e.button.y;
drag_start_x_min = position.x_min;
drag_start_x_max = position.x_max;
drag_start_y_min = position.y_min;
drag_start_y_max = position.y_max;
}
float mx, my;
SDL_GetMouseState(&mx, &my);
double cr =
position.x_min + (position.x_max - position.x_min) * mx / (WINDOW_WIDTH - 1);
double ci =
position.y_min + (position.y_max - position.y_min) * my / (WINDOW_HEIGHT - 1);
int iter;
double zr, zi;
mandelbrot_at_point(cr, ci, &iter, &zr, &zi);
snprintf(debug_text, sizeof(debug_text),
"c = %.6f %+.6fi | z = %.6f %+.6fi | iteration %d", cr, ci, zr, zi, iter);
}
if (e.type == SDL_EVENT_MOUSE_MOTION) {
if (is_dragging) {
float mx = (float)e.motion.x;
float my = (float)e.motion.y;
double dx = mx - drag_start_x;
double dy = my - drag_start_y;
double view_width = drag_start_x_max - drag_start_x_min;
double view_height = drag_start_y_max - drag_start_y_min;
double x_shift = dx * view_width / (WINDOW_WIDTH - 1);
double y_shift = dy * view_height / (WINDOW_HEIGHT - 1);
position.x_min = drag_start_x_min - x_shift;
position.x_max = drag_start_x_max - x_shift;
position.y_min = drag_start_y_min - y_shift;
position.y_max = drag_start_y_max - y_shift;
pos_updated = true;
}
}
if (e.type == SDL_EVENT_MOUSE_BUTTON_UP) {
if (e.button.button == SDL_BUTTON_LEFT) {
is_dragging = false;
}
}
}
if (pos_updated) {
calculate_points_pthread(&position, state->num_threads, state->max_iteration);
pos_updated = false;
}
draw_points(state->surface);
if (debug_text[0] != '\0') {
SDL_SetRenderDrawColor(state->renderer, 64, 224, 208, 255);
SDL_RenderDebugText(state->renderer, 10, 10, debug_text);
}
SDL_RenderPresent(state->renderer);
SDL_UpdateWindowSurface(state->window);
SDL_Delay(16);
}
SDL_DestroyRenderer(state->renderer);
SDL_DestroyWindow(state->window);
SDL_Quit();
return 0;
}