Compare commits

..

1 commit

Author SHA1 Message Date
9413841222 fix thread arg 2025-10-23 22:46:13 +02:00
4 changed files with 60 additions and 49 deletions

View file

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

BIN
visor Executable file

Binary file not shown.

View file

@ -10,9 +10,7 @@
#define DEFAULT_THREADS 4 #define DEFAULT_THREADS 4
#define MAX_THREADS 32 #define MAX_THREADS 32
// Stringizing requires extra step for the constants to get replaced before stringizing #define STR(x) #x
#define _STR(x) #x
#define STR(x) _STR(x)
@ -30,24 +28,15 @@ struct threadInfo {
/* /*
* Concurrency model explained: * Concurrency model explained:
* One main thread, along with many workers in a thread pool, who split the work of * One reader thread (the GUI thread) of the pixel buffer, along with many
* rendering roughly equally. The workers don't do any work until the pixel map which they * writers, who split the work of rendering roughly equally. The writers don't
* are meant to work on is marked as available, and they are all singalled to start * do any work until the pixel map which they are meant to work on is marked as
* drawing. * available.
* *
* 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;
@ -84,7 +73,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 *worker_thread(void *arg) void *writer_thread(void *arg)
{ {
struct threadInfo *info = arg; struct threadInfo *info = arg;
while (true) { while (true) {
@ -121,7 +110,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 worker_thread function, and // loop in the writer_thread function, and
// enter a wait state // enter a wait state
} }
pthread_mutex_unlock(&pixmapMutex); pthread_mutex_unlock(&pixmapMutex);
@ -138,13 +127,13 @@ void draw_mandelbrot(struct threadInfo *info)
} }
pthread_mutex_lock(&pixmapMutex); pthread_mutex_lock(&pixmapMutex);
info->complete = true; info->complete = true;
bool allWorkersComplete = true; bool allWritersComplete = 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) {
allWorkersComplete = false; allWritersComplete = false;
} }
} }
if (allWorkersComplete) { if (allWritersComplete) {
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);
} }
@ -259,7 +248,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, &worker_thread, &threads[count])) { if (pthread_create(&id, NULL, &writer_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;
@ -284,7 +273,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), 0, NULL); stat = g_application_run(G_APPLICATION(app), 1, argv);
g_object_unref(app); g_object_unref(app);
return stat; return stat;
@ -306,12 +295,33 @@ 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
@ -327,6 +337,7 @@ 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));
@ -335,15 +346,15 @@ static void app_activate(GApplication *app)
gboolean queue_redraw_plane(void *arg) gboolean queue_redraw_plane(void *arg)
{ {
(void) arg; (void) arg;
bool allWorkersHadCompleted = true; bool allWritersHadCompleted = 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) {
allWorkersHadCompleted = false; allWritersHadCompleted = false;
break; break;
} }
} }
if (!allWorkersHadCompleted) { if (!allWritersHadCompleted) {
return G_SOURCE_REMOVE; return G_SOURCE_REMOVE;
} }
@ -357,14 +368,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 allWorkersHadCompleted = true; bool allWritersHadCompleted = 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) {
allWorkersHadCompleted = false; allWritersHadCompleted = false;
break; break;
} }
} }
if (allWorkersHadCompleted) { if (allWritersHadCompleted) {
cairo_set_source_surface(cr, surface, 0, 0); cairo_set_source_surface(cr, surface, 0, 0);
cairo_paint(cr); cairo_paint(cr);
} }
@ -382,27 +393,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 allWorkersStopped; bool allWritersStopped;
do { // Wait until all writing threads have been stopped do { // Wait until all writing threads have been stopped
allWorkersStopped = true; allWritersStopped = 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) {
allWorkersStopped = false; allWritersStopped = false;
break; break;
} }
} }
} while (!allWorkersStopped); } while (!allWritersStopped);
// 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 allWorkersHadCompleted = true; bool allWritersHadCompleted = 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) {
allWorkersHadCompleted = false; allWritersHadCompleted = false;
break; break;
} }
} }
if (!allWorkersHadCompleted && surface != NULL) { if (!allWritersHadCompleted && surface != NULL) {
cairo_surface_mark_dirty(surface); cairo_surface_mark_dirty(surface);
} }

BIN
visor.o Normal file

Binary file not shown.