Compare commits

..

7 commits

Author SHA1 Message Date
Petar Kapriš
baead47f04 Replace mentions of "writer" threads with "worker" threads 2025-10-24 10:22:39 +02:00
Petar Kapriš
b2249f6639 Remove unnecessary buttons in GUI 2025-10-24 09:59:38 +02:00
Petar Kapriš
eef91c0cf7 Edit description of concurrency model 2025-10-24 09:52:36 +02:00
Petar Kapriš
8e5f5a38d9 Change program name 2025-10-24 08:31:45 +02:00
Petar Kapriš
39ad228c03 Fix help message 2025-10-24 08:28:52 +02:00
6ef8002f08 Edit style in command-line passing change 2025-10-24 07:57:26 +02:00
41a3cacca0 fix thread arg parsing by gtk 2025-10-23 22:49:37 +02:00
4 changed files with 49 additions and 60 deletions

View file

@ -8,35 +8,35 @@ LDFLAGS = `pkg-config --libs gtk4` -lm -lpthread
CC = gcc CC = gcc
SRC = visor.c SRC = mandelbrot-visualizer.c
OBJ = ${SRC:.c=.o} OBJ = ${SRC:.c=.o}
all: visor all: mandelbrot-visualizer
.c.o: .c.o:
$(CC) -c $(CFLAGS) $< $(CC) -c $(CFLAGS) $<
visor: $(OBJ) mandelbrot-visualizer: $(OBJ)
$(CC) -o $@ $(OBJ) $(LDFLAGS) $(CC) -o $@ $(OBJ) $(LDFLAGS)
dist: clean dist: clean
mkdir -p visor-$(VERSION) mkdir -p mandelbrot-visualizer-$(VERSION)
cp -R Makefile $(SRC) visor-$(VERSION) # Add other files once repo changes cp -R Makefile $(SRC) mandelbrot-visualizer-$(VERSION) # Add other files once repo changes
tar -cf - visor-$(VERSION) | gzip > visor-$(VERSION).tar.gz tar -cf - mandelbrot-visualizer-$(VERSION) | gzip > mandelbrot-visualizer-$(VERSION).tar.gz
rm -rf visor-$(VERSION) rm -rf mandelbrot-visualizer-$(VERSION)
clean: clean:
rm -f visor $(OBJ) rm -f mandelbrot-visualizer $(OBJ)
install: all install: all
mkdir -p $(DESTDIR)$(PREFIX)/bin mkdir -p $(DESTDIR)$(PREFIX)/bin
cp -f visor $(DESTDIR)$(PREFIX)/bin cp -f mandelbrot-visualizer $(DESTDIR)$(PREFIX)/bin
chmod 755 $(DESTDIR)$(PREFIX)/bin/visor chmod 755 $(DESTDIR)$(PREFIX)/bin/mandelbrot-visualizer
#mkdir -p $(DESTDIR)$(MANPREFIX)/man1 #mkdir -p $(DESTDIR)$(MANPREFIX)/man1
#chmod 644 $(DESTDIR)$(MANPREFIX)/man1/visor.1 #chmod 644 $(DESTDIR)$(MANPREFIX)/man1/mandelbrot-visualizer.1
uninstall: uninstall:
rm -f $(DESTDIR)$(PREFIX)/bin/visor#\ rm -f $(DESTDIR)$(PREFIX)/bin/mandelbrot-visualizer#\
#$(DESTDIR)$(MANPREFIX)/man1/visor.1 #$(DESTDIR)$(MANPREFIX)/man1/mandelbrot-visualizer.1
.PHONY: all options dist clean install uninstall .PHONY: all options dist clean install uninstall

View file

@ -10,7 +10,9 @@
#define DEFAULT_THREADS 4 #define DEFAULT_THREADS 4
#define MAX_THREADS 32 #define MAX_THREADS 32
#define STR(x) #x // Stringizing requires extra step for the constants to get replaced before stringizing
#define _STR(x) #x
#define STR(x) _STR(x)
@ -28,15 +30,24 @@ struct threadInfo {
/* /*
* Concurrency model explained: * Concurrency model explained:
* One reader thread (the GUI thread) of the pixel buffer, along with many * One main thread, along with many workers in a thread pool, who split the work of
* writers, who split the work of rendering roughly equally. The writers don't * rendering roughly equally. The workers don't do any work until the pixel map which they
* do any work until the pixel map which they are meant to work on is marked as * are meant to work on is marked as available, and they are all singalled to start
* available. * drawing.
* *
* Once it is marked as such, this means that the planeView structure is well * Once it is marked as such, this means that the planeView structure is well
* defined, that pixmap points to a memory region, which is allocated with * defined, that pixmap points to a memory region, which is allocated with
* enough memory for the plain view, and they can all start writing without an * enough memory for the plain view, and they can all start writing without an
* issue. * issue.
*
* Once they are all finished, the last thread to exit the drawing function will signal
* the main thread to "blit" the finished image to the screen.
*
* If any kind of change by the user happens (window resize, panning/zooming (TODO: not
* implemented yet), the main thread will cause all worker threads to stop working, if
* they haven't already (they occassionally poll while drawing to see if they should
* stop). Once they've all stopped, the main thread will update and replace the drawing
* area, and naturally, signal them to start drawing again.
*/ */
pthread_mutex_t pixmapMutex; pthread_mutex_t pixmapMutex;
@ -73,7 +84,7 @@ void color_lookup(int *r, int *g, int *b, double mu);
// worker code // worker code
gboolean queue_redraw_plane(void *arg); gboolean queue_redraw_plane(void *arg);
void *writer_thread(void *arg) void *worker_thread(void *arg)
{ {
struct threadInfo *info = arg; struct threadInfo *info = arg;
while (true) { while (true) {
@ -110,7 +121,7 @@ void draw_mandelbrot(struct threadInfo *info)
pthread_mutex_unlock(&pixmapMutex); pthread_mutex_unlock(&pixmapMutex);
return; return;
// after this, the thread will again enter the // after this, the thread will again enter the
// loop in the writer_thread function, and // loop in the worker_thread function, and
// enter a wait state // enter a wait state
} }
pthread_mutex_unlock(&pixmapMutex); pthread_mutex_unlock(&pixmapMutex);
@ -127,13 +138,13 @@ void draw_mandelbrot(struct threadInfo *info)
} }
pthread_mutex_lock(&pixmapMutex); pthread_mutex_lock(&pixmapMutex);
info->complete = true; info->complete = true;
bool allWritersComplete = true; bool allWorkersComplete = true;
for (int32_t i = 0; i < thread_count; i++) { for (int32_t i = 0; i < thread_count; i++) {
if (!threads[i].complete) { if (!threads[i].complete) {
allWritersComplete = false; allWorkersComplete = false;
} }
} }
if (allWritersComplete) { if (allWorkersComplete) {
cairo_surface_mark_dirty(surface); cairo_surface_mark_dirty(surface);
g_main_context_invoke(NULL, queue_redraw_plane, NULL); g_main_context_invoke(NULL, queue_redraw_plane, NULL);
} }
@ -248,7 +259,7 @@ int main(int argc, char **argv)
// integer, if the thread fails to start, it will simply be // integer, if the thread fails to start, it will simply be
// overwritten by the same value in the next run of the loop // overwritten by the same value in the next run of the loop
pthread_t id; pthread_t id;
if (pthread_create(&id, NULL, &writer_thread, &threads[count])) { if (pthread_create(&id, NULL, &worker_thread, &threads[count])) {
pthread_detach(id); pthread_detach(id);
fprintf(stderr, "Failed to create thread %d\n", i); fprintf(stderr, "Failed to create thread %d\n", i);
continue; continue;
@ -273,7 +284,7 @@ int main(int argc, char **argv)
app = gtk_application_new("cool.bonsai.mandelbrot-visualizer", app = gtk_application_new("cool.bonsai.mandelbrot-visualizer",
G_APPLICATION_DEFAULT_FLAGS); G_APPLICATION_DEFAULT_FLAGS);
g_signal_connect(app, "activate", G_CALLBACK(app_activate), NULL); g_signal_connect(app, "activate", G_CALLBACK(app_activate), NULL);
stat = g_application_run(G_APPLICATION(app), 1, argv); stat = g_application_run(G_APPLICATION(app), 0, NULL);
g_object_unref(app); g_object_unref(app);
return stat; return stat;
@ -295,33 +306,12 @@ GtkDrawingArea *da;
static void app_activate(GApplication *app) static void app_activate(GApplication *app)
{ {
GtkWidget *win; GtkWidget *win;
GtkWidget *btn1;
GtkWidget *btn2;
GtkWidget *bigBox; GtkWidget *bigBox;
GtkWidget *vertBox;
win = gtk_application_window_new(GTK_APPLICATION(app)); win = gtk_application_window_new(GTK_APPLICATION(app));
gtk_window_set_title(GTK_WINDOW(win), "Mandelbrot visualiser"); gtk_window_set_title(GTK_WINDOW(win), "Mandelbrot visualiser");
gtk_window_set_default_size(GTK_WINDOW(win), 800, 600); gtk_window_set_default_size(GTK_WINDOW(win), 800, 600);
btn1 = gtk_button_new_with_label("A");
gtk_widget_set_size_request(GTK_WIDGET(btn1), 20, 20);
gtk_widget_set_margin_bottom(GTK_WIDGET(btn1), 5);
gtk_widget_set_margin_top(GTK_WIDGET(btn1), 5);
gtk_widget_set_margin_start(GTK_WIDGET(btn1), 5);
gtk_widget_set_margin_end(GTK_WIDGET(btn1), 5);
btn2 = gtk_button_new_with_label("B");
gtk_widget_set_size_request(GTK_WIDGET(btn2), 20, 20);
gtk_widget_set_margin_bottom(GTK_WIDGET(btn2), 5);
gtk_widget_set_margin_top(GTK_WIDGET(btn2), 5);
gtk_widget_set_margin_start(GTK_WIDGET(btn2), 5);
gtk_widget_set_margin_end(GTK_WIDGET(btn2), 5);
vertBox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
gtk_widget_set_valign(GTK_WIDGET(vertBox), GTK_ALIGN_START);
gtk_box_append(GTK_BOX(vertBox), btn1);
gtk_box_append(GTK_BOX(vertBox), btn2);
bigBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); bigBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
// global to enable other functions to call for a redraw // global to enable other functions to call for a redraw
@ -337,7 +327,6 @@ static void app_activate(GApplication *app)
// TODO: implement later drag = gtk_gesture_drag_new(); // TODO: implement later drag = gtk_gesture_drag_new();
gtk_box_append(GTK_BOX(bigBox), GTK_WIDGET(da)); gtk_box_append(GTK_BOX(bigBox), GTK_WIDGET(da));
gtk_box_append(GTK_BOX(bigBox), vertBox);
gtk_window_set_child(GTK_WINDOW(win), bigBox); gtk_window_set_child(GTK_WINDOW(win), bigBox);
gtk_window_present(GTK_WINDOW(win)); gtk_window_present(GTK_WINDOW(win));
@ -346,15 +335,15 @@ static void app_activate(GApplication *app)
gboolean queue_redraw_plane(void *arg) gboolean queue_redraw_plane(void *arg)
{ {
(void) arg; (void) arg;
bool allWritersHadCompleted = true; bool allWorkersHadCompleted = true;
for (int32_t i = 0; i < thread_count; i++) { for (int32_t i = 0; i < thread_count; i++) {
if (!threads[i].complete) { if (!threads[i].complete) {
allWritersHadCompleted = false; allWorkersHadCompleted = false;
break; break;
} }
} }
if (!allWritersHadCompleted) { if (!allWorkersHadCompleted) {
return G_SOURCE_REMOVE; return G_SOURCE_REMOVE;
} }
@ -368,14 +357,14 @@ void blit_plane(GtkDrawingArea *da, cairo_t *cr, int width, int height, gpointer
(void) width; (void) width;
(void) height; (void) height;
(void) data; (void) data;
bool allWritersHadCompleted = true; bool allWorkersHadCompleted = true;
for (int32_t i = 0; i < thread_count; i++) { for (int32_t i = 0; i < thread_count; i++) {
if (!threads[i].complete) { if (!threads[i].complete) {
allWritersHadCompleted = false; allWorkersHadCompleted = false;
break; break;
} }
} }
if (allWritersHadCompleted) { if (allWorkersHadCompleted) {
cairo_set_source_surface(cr, surface, 0, 0); cairo_set_source_surface(cr, surface, 0, 0);
cairo_paint(cr); cairo_paint(cr);
} }
@ -393,27 +382,27 @@ void create_surface(GtkWidget *widget)
pixmapAvailable = false; // Mark drawing area as unavailable pixmapAvailable = false; // Mark drawing area as unavailable
pthread_mutex_unlock(&pixmapMutex); pthread_mutex_unlock(&pixmapMutex);
bool allWritersStopped; bool allWorkersStopped;
do { // Wait until all writing threads have been stopped do { // Wait until all writing threads have been stopped
allWritersStopped = true; allWorkersStopped = true;
for (int32_t i = 0; i < thread_count; i++) { for (int32_t i = 0; i < thread_count; i++) {
if (threads[i].drawing) { if (threads[i].drawing) {
allWritersStopped = false; allWorkersStopped = false;
break; break;
} }
} }
} while (!allWritersStopped); } while (!allWorkersStopped);
// If all drawers hadn't completed, we need to call // If all drawers hadn't completed, we need to call
// cairo_surface_mark_dirty in the main thread // cairo_surface_mark_dirty in the main thread
bool allWritersHadCompleted = true; bool allWorkersHadCompleted = true;
for (int32_t i = 0; i < thread_count; i++) { for (int32_t i = 0; i < thread_count; i++) {
if (!threads[i].complete) { if (!threads[i].complete) {
allWritersHadCompleted = false; allWorkersHadCompleted = false;
break; break;
} }
} }
if (!allWritersHadCompleted && surface != NULL) { if (!allWorkersHadCompleted && surface != NULL) {
cairo_surface_mark_dirty(surface); cairo_surface_mark_dirty(surface);
} }

BIN
visor

Binary file not shown.

BIN
visor.o

Binary file not shown.