Compare commits
No commits in common. "master" and "master" have entirely different histories.
2 changed files with 59 additions and 48 deletions
26
Makefile
26
Makefile
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
Loading…
Add table
Reference in a new issue