aboutsummaryrefslogtreecommitdiff
path: root/src/async_image.c
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2021-12-20 10:26:12 +0100
committerdec05eba <dec05eba@protonmail.com>2021-12-21 20:22:33 +0100
commit44e987c8521a99519350a42292bcfcd28451dcbd (patch)
tree699015a5dd459e96e0b19f4836f7dcffc1e347de /src/async_image.c
parent6bb40bf0c5cd8ee8fb87640fd04b2c595f84c1d3 (diff)
Async load images
Diffstat (limited to 'src/async_image.c')
-rw-r--r--src/async_image.c297
1 files changed, 297 insertions, 0 deletions
diff --git a/src/async_image.c b/src/async_image.c
new file mode 100644
index 0000000..4e509dc
--- /dev/null
+++ b/src/async_image.c
@@ -0,0 +1,297 @@
+#include "../include/async_image.h"
+#include "../include/alloc.h"
+#include "../include/hashmap.h"
+#include <pthread.h>
+#include <semaphore.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+typedef struct {
+ mgui_async_image *async_image;
+} async_image_task;
+
+typedef struct {
+ async_image_task *tasks;
+ size_t capacity;
+ size_t start_index;
+ size_t num_occupied_slots;
+} async_image_task_fifo;
+
+static bool initialized = false;
+static pthread_t images_thread;
+static mgui_hashmap images;
+static pthread_mutex_t mutex;
+static sem_t task_sem;
+static async_image_task_fifo tasks;
+static uint32_t update_counter = 0;
+
+static size_t align_up(size_t value, size_t alignment) {
+ size_t over = value % alignment;
+ if(over == 0)
+ return value;
+ return value - over + alignment;
+}
+
+static void async_image_task_fifo_init(async_image_task_fifo *self) {
+ self->tasks = NULL;
+ self->capacity = 0;
+ self->start_index = 0;
+ self->num_occupied_slots = 0;
+}
+
+static void async_image_task_fifo_deinit(async_image_task_fifo *self) {
+ mgui_free(self->tasks);
+ self->tasks = NULL;
+ self->capacity = 0;
+ self->start_index = 0;
+ self->num_occupied_slots = 0;
+}
+
+static void async_image_task_fifo_ensure_capacity(async_image_task_fifo *self, size_t new_capacity) {
+ if(self->capacity >= new_capacity)
+ return;
+
+ size_t capacity = self->capacity;
+ if(capacity == 0)
+ capacity = 8;
+
+ while(capacity < new_capacity) {
+ capacity = capacity + (capacity >> 1); /* capacity *= 1.5 */
+ }
+
+ capacity = align_up(capacity, sizeof(async_image_task));
+ self->tasks = mgui_realloc(self->tasks, capacity);
+
+ /* Straighten circular buffer. For example: convert [4][5][][][1][2][3][][] to [][][][][1][2][3][4][5] */
+ if(self->capacity > 0) {
+ const size_t num_allocated_slots = self->capacity / sizeof(async_image_task);
+ const size_t end_index = (self->start_index + self->num_occupied_slots) % num_allocated_slots;
+ if(end_index < self->start_index) {
+ for(size_t i = 0; i <= end_index; ++i) {
+ self->tasks[self->num_occupied_slots + i] = self->tasks[end_index - i];
+ }
+ }
+ }
+ self->capacity = capacity;
+}
+
+static void async_image_task_fifo_append(async_image_task_fifo *self, async_image_task *task) {
+ async_image_task_fifo_ensure_capacity(self, (self->num_occupied_slots + 1) * sizeof(async_image_task));
+ const size_t num_allocated_slots = self->capacity / sizeof(async_image_task);
+ const size_t insert_index = (self->start_index + self->num_occupied_slots) % num_allocated_slots;
+ self->tasks[insert_index] = *task;
+ ++self->num_occupied_slots;
+}
+
+/* pop first item if not empty. returns false if empty */
+static bool async_image_task_fifo_pop(async_image_task_fifo *self, async_image_task *task_out) {
+ if(self->num_occupied_slots == 0)
+ return false;
+
+ *task_out = self->tasks[self->start_index];
+ const size_t num_allocated_slots = self->capacity / sizeof(async_image_task);
+ self->start_index = (self->start_index + 1) % num_allocated_slots;
+ --self->num_occupied_slots;
+ return true;
+}
+
+/* Return false from |callback| to stop the iteration */
+static void async_image_task_fifo_for_each(async_image_task_fifo *self, bool (*callback)(async_image_task *task, void *userdata), void *userdata) {
+ if(self->capacity == 0)
+ return;
+
+ const size_t num_allocated_slots = self->capacity / sizeof(async_image_task);
+ for(size_t i = 0; i < self->num_occupied_slots; ++i) {
+ const size_t index = (self->start_index + i) % num_allocated_slots;
+ if(!callback(&self->tasks[index], userdata))
+ return;
+ }
+}
+
+static void* images_thread_callback(void *arg) {
+ (void)arg;
+ async_image_task task;
+ while(initialized) {
+ sem_wait(&task_sem);
+ if(!initialized)
+ break;
+
+ pthread_mutex_lock(&mutex);
+ const bool has_task = async_image_task_fifo_pop(&tasks, &task);
+ const bool empty_task = !has_task || !task.async_image;
+ if(!empty_task)
+ ++task.async_image->ref_count;
+ pthread_mutex_unlock(&mutex);
+
+ if(empty_task)
+ continue;
+
+ /* TODO: Create thumbnail (resize to target size) and load that instead */
+ if(mgl_image_load_from_file(&task.async_image->image, task.async_image->filepath) == 0)
+ task.async_image->state = MGUI_ASYNC_IMAGE_LOADED;
+ else
+ task.async_image->state = MGUI_ASYNC_IMAGE_FAILED_TO_LOAD;
+
+ pthread_mutex_lock(&mutex);
+ if(task.async_image->ref_count > 0)
+ --task.async_image->ref_count;
+ pthread_mutex_unlock(&mutex);
+ }
+ return NULL;
+}
+
+void mgui_async_image_init() {
+ if(!initialized) {
+ initialized = true;
+ mgui_hashmap_init(&images);
+ async_image_task_fifo_init(&tasks);
+
+ if(sem_init(&task_sem, 0, 0) != 0) {
+ fprintf(stderr, "mgui error: failed to initialize images semaphore\n");
+ abort();
+ }
+
+ if(pthread_mutex_init(&mutex, NULL) != 0) {
+ fprintf(stderr, "mgui error: failed to initialize images mutex\n");
+ abort();
+ }
+
+ if(pthread_create(&images_thread, NULL, images_thread_callback, NULL) != 0) {
+ fprintf(stderr, "mgui error: failed to initialize images thread\n");
+ abort();
+ }
+ }
+}
+
+static bool images_free(void *value, void *userdata) {
+ (void)userdata;
+ mgui_async_image *async_image = value;
+ mgl_image_unload(&async_image->image);
+ mgl_texture_unload(&async_image->texture);
+ mgui_free(async_image->filepath);
+ mgui_free(async_image);
+ return true;
+}
+
+void mgui_async_image_deinit() {
+ if(initialized) {
+ initialized = false;
+ sem_post(&task_sem);
+ pthread_join(images_thread, NULL);
+ pthread_mutex_destroy(&mutex);
+ sem_destroy(&task_sem);
+
+ async_image_task task;
+ while(async_image_task_fifo_pop(&tasks, &task)) {}
+
+ async_image_task_fifo_deinit(&tasks);
+ mgui_hashmap_for_each(&images, images_free, NULL);
+ mgui_hashmap_deinit(&images);
+ }
+}
+
+static bool remove_unreferenced_tasks(async_image_task *task, void *userdata) {
+ (void)userdata;
+ if(task->async_image)
+ return true;
+
+ if(task->async_image->updated != update_counter) {
+ task->async_image = NULL;
+ return false;
+ } else {
+ return true;
+ }
+}
+
+static bool images_unload_unreferenced(void *value, void *userdata) {
+ (void)userdata;
+ mgui_async_image *async_image = value;
+ if(async_image->updated != update_counter) {
+ bool remove_image = false;
+ if(async_image->state != MGUI_ASYNC_IMAGE_LOADING) {
+ async_image->state = MGUI_ASYNC_IMAGE_UNLOADED;
+ mgl_image_unload(&async_image->image);
+ mgl_texture_unload(&async_image->texture);
+
+ pthread_mutex_lock(&mutex);
+ /* TODO: Check if this actually happens */
+ if(async_image->ref_count == 0) {
+ mgui_free(async_image->filepath);
+ mgui_free(async_image);
+ remove_image = true;
+ }
+ pthread_mutex_unlock(&mutex);
+ }
+ return remove_image;
+ } else {
+ return false;
+ }
+}
+
+void mgui_async_image_unload_unreferenced() {
+ pthread_mutex_lock(&mutex);
+ async_image_task_fifo_for_each(&tasks, remove_unreferenced_tasks, NULL);
+ pthread_mutex_unlock(&mutex);
+
+ mgui_hashmap_for_each_erase(&images, images_unload_unreferenced, NULL);
+
+ ++update_counter;
+}
+
+mgui_async_image* mgui_async_image_get_by_path(const char *filepath) {
+ const size_t filepath_len = strlen(filepath);
+
+ pthread_mutex_lock(&mutex);
+ void *value;
+ if(!mgui_hashmap_get(&images, filepath, filepath_len, &value)) {
+ mgui_async_image *async_image = mgui_alloc(sizeof(mgui_async_image));
+ async_image->image.data = NULL;
+ async_image->texture.id = 0;
+ async_image->hash = 0;
+ async_image->state = MGUI_ASYNC_IMAGE_LOADING;
+ async_image->updated = update_counter;
+ async_image->ref_count = 0;
+ async_image->filepath = mgui_alloc(filepath_len + 1);
+ memcpy(async_image->filepath, filepath, filepath_len);
+ async_image->filepath[filepath_len] = '\0';
+ mgui_hashmap_insert(&images, filepath, filepath_len, async_image, &async_image->hash);
+ value = async_image;
+
+ async_image_task task = {
+ .async_image = async_image
+ };
+ async_image_task_fifo_append(&tasks, &task);
+ sem_post(&task_sem);
+ }
+
+ mgui_async_image *async_image = value;
+ ++async_image->ref_count;
+ pthread_mutex_unlock(&mutex);
+
+ return async_image;
+}
+
+void mgui_async_image_update(mgui_async_image *self) {
+ if(self->state == MGUI_ASYNC_IMAGE_LOADED) {
+ self->state = MGUI_ASYNC_IMAGE_APPLIED;
+ if(mgl_texture_init(&self->texture) == 0)
+ mgl_texture_load_from_image(&self->texture, &self->image, NULL);
+ mgl_image_unload(&self->image);
+ } else if(self->state == MGUI_ASYNC_IMAGE_UNLOADED) {
+ self->state = MGUI_ASYNC_IMAGE_LOADING;
+ async_image_task task = {
+ .async_image = self
+ };
+ async_image_task_fifo_append(&tasks, &task);
+ sem_post(&task_sem);
+ }
+ self->updated = update_counter;
+}
+
+void mgui_async_image_unref(mgui_async_image *self) {
+ pthread_mutex_lock(&mutex);
+ if(self->ref_count > 0)
+ --self->ref_count;
+ pthread_mutex_unlock(&mutex);
+}