From cdbe01067909b912f0f0d4f677df1201afb655c1 Mon Sep 17 00:00:00 2001 From: Francesco Cozzuto Date: Tue, 17 Sep 2024 11:10:09 +0200 Subject: [PATCH] Parallelize --- Makefile | 6 +- src/clock.c | 102 +++++++++++++++ src/clock.h | 31 +++++ src/main.c | 206 ++++++++++++++++++++++------- src/profile.c | 70 ++++++++++ src/profile.h | 48 +++++++ src/sync.c | 353 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/sync.h | 78 +++++++++++ src/thread.c | 75 +++++++++++ src/thread.h | 45 +++++++ 10 files changed, 963 insertions(+), 51 deletions(-) create mode 100644 src/clock.c create mode 100644 src/clock.h create mode 100644 src/profile.c create mode 100644 src/profile.h create mode 100644 src/sync.c create mode 100644 src/sync.h create mode 100644 src/thread.c create mode 100644 src/thread.h diff --git a/Makefile b/Makefile index 96a02fd..ebd8eca 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,13 @@ ifeq ($(OS),Windows_NT) EXT = .exe - CFLAGS = -ggdb -I3p/glad/include -I3p/glfw-3.4.bin.WIN64/include -L3p/glfw-3.4.bin.WIN64/lib-mingw-w64 + CFLAGS = -O2 -ggdb -I3p/glad/include -I3p/glfw-3.4.bin.WIN64/include -L3p/glfw-3.4.bin.WIN64/lib-mingw-w64 LDFLAGS = -lglfw3 -lopengl32 -lgdi32 else UNAME_S := $(shell uname -s) ifeq ($(UNAME_S),Linux) EXT = - CFLAGS = -ggdb -I3p/glad/include #-fsanitize=address,undefined + CFLAGS = -O2 -ggdb -I3p/glad/include #-fsanitize=address,undefined LDFLAGS = -lglfw -lm endif ifeq ($(UNAME_S),Darwin) @@ -20,7 +20,7 @@ endif all: path_trace$(EXT) path_trace$(EXT): Makefile $(wildcard src/*.c src/*.h) - gcc -o $@ src/main.c src/utils.c src/camera.c src/mesh.c src/vector.c 3p/glad/src/glad.c -std=c11 $(CFLAGS) $(LDFLAGS) + gcc -o $@ src/main.c src/utils.c src/camera.c src/mesh.c src/vector.c src/thread.c src/sync.c src/clock.c src/profile.c 3p/glad/src/glad.c -std=c11 $(CFLAGS) $(LDFLAGS) clean: rm path_trace path_trace.exe diff --git a/src/clock.c b/src/clock.c new file mode 100644 index 0000000..772f5e2 --- /dev/null +++ b/src/clock.c @@ -0,0 +1,102 @@ +/* + * This is free and unencumbered software released into the public domain. + * + * Anyone is free to copy, modify, publish, use, compile, sell, or + * distribute this software, either in source code form or as a compiled + * binary, for any purpose, commercial or non-commercial, and by any + * means. + * + * In jurisdictions that recognize copyright laws, the author or authors + * of this software dedicate any and all copyright interest in the + * software to the public domain. We make this dedication for the benefit + * of the public at large and to the detriment of our heirs and + * successors. We intend this dedication to be an overt act of + * relinquishment in perpetuity of all present and future rights to this + * software under copyright law. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * For more information, please refer to + */ + +#include "clock.h" + +#ifdef _WIN32 +#define WIN32_MEAN_AND_LEAN +#include +#else +#include +#include +#endif + +/* + * Returns the current absolute time in microsecods + * TODO: Specify since when the time is calculated + */ +uint64_t get_absolute_time_us(void) +{ + #ifdef _WIN32 + FILETIME filetime; + GetSystemTimePreciseAsFileTime(&filetime); + uint64_t time = (uint64_t) filetime.dwLowDateTime | ((uint64_t) filetime.dwHighDateTime << 32); + time /= 10; + return time; + #else + struct timespec buffer; + if (clock_gettime(CLOCK_REALTIME, &buffer)) + abort(); + uint64_t time = buffer.tv_sec * 1000000 + buffer.tv_nsec / 1000; + return time; + #endif +} + +/* + * Returns the current time in nanoseconds since + * an unspecified point in time. + */ +uint64_t get_relative_time_ns(void) +{ + #ifdef _WIN32 + { + int64_t count; + int64_t freq; + int ok; + + ok = QueryPerformanceCounter((LARGE_INTEGER*) &count); + if (!ok) abort(); + + ok = QueryPerformanceFrequency((LARGE_INTEGER*) &freq); + if (!ok) abort(); + + uint64_t res = 1000000000 * (double) count / freq; + return res; + } + #else + { + struct timespec time; + + if (clock_gettime(CLOCK_REALTIME, &time)) + abort(); + + uint64_t res; + + uint64_t sec = time.tv_sec; + if (sec > UINT64_MAX / 1000000000) + abort(); + res = sec * 1000000000; + + uint64_t nsec = time.tv_nsec; + if (res > UINT64_MAX - nsec) + abort(); + res += nsec; + + return res; + } + #endif +} \ No newline at end of file diff --git a/src/clock.h b/src/clock.h new file mode 100644 index 0000000..6acf231 --- /dev/null +++ b/src/clock.h @@ -0,0 +1,31 @@ +/* + * This is free and unencumbered software released into the public domain. + * + * Anyone is free to copy, modify, publish, use, compile, sell, or + * distribute this software, either in source code form or as a compiled + * binary, for any purpose, commercial or non-commercial, and by any + * means. + * + * In jurisdictions that recognize copyright laws, the author or authors + * of this software dedicate any and all copyright interest in the + * software to the public domain. We make this dedication for the benefit + * of the public at large and to the detriment of our heirs and + * successors. We intend this dedication to be an overt act of + * relinquishment in perpetuity of all present and future rights to this + * software under copyright law. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * For more information, please refer to + */ + +#include + +uint64_t get_absolute_time_us(void); +uint64_t get_relative_time_ns(void); \ No newline at end of file diff --git a/src/main.c b/src/main.c index fe43078..6394f3f 100644 --- a/src/main.c +++ b/src/main.c @@ -11,6 +11,8 @@ #include "utils.h" #include "camera.h" #include "vector.h" +#include "thread.h" +#include "sync.h" #include "mesh.h" typedef struct { @@ -150,11 +152,11 @@ void framebuffer_size_callback(GLFWwindow* window, int width, int height) glViewport(0, 0, width, height); } -void reset_accum(void); +void invalidate_accumulation(void); void cursor_pos_callback(GLFWwindow *window, double x, double y) { - reset_accum(); + invalidate_accumulation(); rotate_camera(x, y); } @@ -336,25 +338,25 @@ unsigned int pcg_hash(unsigned int input) return (word >> 22U) ^ word; } -float random_float(unsigned int *seed) +float random_float(void) { // *seed = pcg_hash(*seed); // return (float) *seed / UINT_MAX; return (float) rand() / RAND_MAX; } -Vector3 random_vector(unsigned int seed) +Vector3 random_vector(void) { return (Vector3) { - .x = random_float(&seed) * 2 - 1, - .y = random_float(&seed) * 2 - 1, - .z = random_float(&seed) * 2 - 1, + .x = random_float() * 2 - 1, + .y = random_float() * 2 - 1, + .z = random_float() * 2 - 1, }; } -Vector3 random_direction(unsigned int seed) +Vector3 random_direction(void) { - return normalize(random_vector(seed)); + return normalize(random_vector()); } Vector3 reflect(Vector3 dir, Vector3 normal) @@ -418,13 +420,16 @@ HitInfo trace_ray(Ray ray) Vector3 pixel(float x, float y) { - Ray ray = ray_through_screen_at(x, y, (float) screen_w/screen_h); + Ray original_ray = ray_through_screen_at(x, y, (float) screen_w/screen_h); Vector3 contrib = {1, 1, 1}; Vector3 light = {0, 0, 0}; - int rays_per_pixel = 1; - for (int j = 0; j < rays_per_pixel; j++) { + int diffuse_rays = 1; + int specular_rays = 1; + + Ray ray = original_ray; + for (int j = 0; j < diffuse_rays; j++) { int bounces = 4; for (int i = 0; i < bounces; i++) { @@ -440,43 +445,129 @@ Vector3 pixel(float x, float y) Material material = objects[hit.object].material; contrib = mulv(contrib, material.albedo); light = combine(light, material.emission_color, 1, material.emission_power); -#if 0 - Vector3 reflect_dir = reflect(ray.direction, scale(hit.normal, -1)); - Vector3 noise_dir = scale(random_direction(), 0.5); - if (dotv(noise_dir, reflect_dir) < 0) - noise_dir = scale(noise_dir, -1); - float roughness = objects[hit.object].material.roughness; - Vector3 new_dir = combine(noise_dir, reflect_dir, roughness, 1); -#endif - - Vector3 new_dir = random_direction(i * 1000000 + x * 1000 + y); -/* + Vector3 new_dir = random_direction(); if (dotv(new_dir, hit.normal) < 0) new_dir = scale(new_dir, -1); -*/ + ray = (Ray) { combine(hit.point, new_dir, 1, 0.001), new_dir }; } } - light = scale(light, 1.0f/rays_per_pixel); + + contrib = (Vector3) {1, 1, 1}; + ray = original_ray; + for (int j = 0; j < specular_rays; j++) { + + int bounces = 4; + for (int i = 0; i < bounces; i++) { + + HitInfo hit = trace_ray(ray); + if (hit.object == -1) { + Vector3 sky_color = {0.6, 0.7, 0.9}; + //Vector3 sky_color = {0, 0, 0}; + light = combine(light, mulv(sky_color, contrib), 1, 1); + break; + } + + Vector3 reflect_dir = reflect(ray.direction, scale(hit.normal, -1)); + + float roughness = objects[hit.object].material.roughness; + Vector3 noise_dir = scale(random_direction(), roughness); + if (dotv(noise_dir, reflect_dir) < 0) + noise_dir = scale(noise_dir, -1); + + Vector3 new_dir = combine(noise_dir, reflect_dir, roughness, 1 - roughness); + + ray = (Ray) { combine(hit.point, new_dir, 1, 0.001), new_dir }; + } + } + + light = scale(light, 1.0f/(specular_rays + diffuse_rays)); return light; } +uint32_t accum_generation = 0; Vector3 *accum = NULL; Vector3 *frame = NULL; int frame_w = 0; int frame_h = 0; unsigned int frame_texture; -int accum_index = 1; +uint64_t accum_index = 1; +os_mutex_t frame_mutex; -void reset_accum(void) +os_threadreturn worker(void*) { + uint32_t local_accum_generation = 0; + Vector3 *local_accum = NULL; + uint64_t local_accum_index = 1; + int local_frame_w = 0; + int local_frame_h = 0; + + for (;;) { + + bool resize = false; + + os_mutex_lock(&frame_mutex); + if (accum != NULL && local_accum != NULL && local_accum_generation == accum_generation) { + for (int i = 0; i < frame_w * frame_h; i++) + accum[i] = combine(accum[i], local_accum[i], 1, 1); + accum_index += local_accum_index; + } + memset(local_accum, 0, sizeof(Vector3) * local_frame_w * local_frame_h); + if (local_frame_w != frame_w || local_frame_h != frame_h) + resize = true; + local_accum_generation = accum_generation; + local_frame_w = frame_w; + local_frame_h = frame_h; + local_accum_index = 1; + os_mutex_unlock(&frame_mutex); + + if (resize) { + + if (local_accum) + free(local_accum); + + local_accum = malloc(sizeof(Vector3) * local_frame_w * local_frame_h); + if (!local_accum) { + printf("OUT OF MEMORY\n"); + abort(); + } + + memset(local_accum, 0, sizeof(Vector3) * local_frame_w * local_frame_h); + } + + if (local_accum) { + for (int j = 0; j < local_frame_h; j++) + for (int i = 0; i < local_frame_w; i++) { + float u = (float) i / (local_frame_w - 1); + float v = (float) j / (local_frame_h - 1); + u = 1 - u; + v = 1 - v; + + Vector3 color = pixel(u, v); + + int pixel_index = j * local_frame_w + i; + local_accum[pixel_index] = combine(local_accum[pixel_index], color, 1, 1); + } + + local_accum_index++; + } + } +} + +void invalidate_accumulation(void) +{ + os_mutex_lock(&frame_mutex); accum_index = 1; + accum_generation++; + os_mutex_unlock(&frame_mutex); } void update_frame_texture(float s) { + os_mutex_lock(&frame_mutex); + if (frame_w != s * screen_w || frame_h != s * screen_h) { frame_w = s * screen_w; frame_h = s * screen_h; @@ -493,41 +584,60 @@ void update_frame_texture(float s) accum_index = 1; } - if (accum_index == 1) - memset(accum, 0, sizeof(Vector3) * frame_w * frame_h); + if (accum_index == 1) { + + //memset(accum, 0, sizeof(Vector3) * frame_w * frame_h); + + for (int j = 0; j < frame_h; j++) + for (int i = 0; i < frame_w; i++) { + float u = (float) i / (frame_w - 1); + float v = (float) j / (frame_h - 1); + u = 1 - u; + v = 1 - v; + int pixel_index = j * frame_w + i; + accum[pixel_index] = pixel(u, v); + } + } for (int j = 0; j < frame_h; j++) for (int i = 0; i < frame_w; i++) { + float u = (float) i / (frame_w - 1); float v = (float) j / (frame_h - 1); u = 1 - u; v = 1 - v; - Vector3 color = pixel(u, v); - int pixel_index = j * frame_w + i; - accum[pixel_index] = combine(accum[pixel_index], color, 1, 1); frame[pixel_index] = scale(accum[pixel_index], 1.0f / accum_index); } - - accum_index++; glBindTexture(GL_TEXTURE_2D, frame_texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, frame_w, frame_h, 0, GL_RGB, GL_FLOAT, frame); glBindTexture(GL_TEXTURE_2D, 0); + + os_mutex_unlock(&frame_mutex); } int main(void) { -/* - add_object(cube((Material) {.emission_color={0}, .emission_power=0, .metallic=0, .roughness=1, .albedo=(Vector3) {1, 0, 0}}, (Vector3) {0, 0, 0}, (Vector3) {10, 5, 0.1})), - add_object(cube((Material) {.emission_color={0}, .emission_power=0, .metallic=0, .roughness=1, .albedo=(Vector3) {1, 0, 0}}, (Vector3) {0, 0, 0}, (Vector3) {0.1, 5, 10})), -*/ - add_object(cube((Material) {.emission_color={0}, .emission_power=0, .metallic=0, .roughness=1, .albedo=(Vector3) {0.4, 0.3, 0.9}}, (Vector3) {0, -0.1, 0}, (Vector3) {10, 0.1, 10})), - add_object(cube((Material) {.emission_color={0}, .emission_power=0, .metallic=0, .roughness=1, .albedo=(Vector3) {1, 0, 0}}, (Vector3) {7, 0, 8}, (Vector3) {1, 1, 1})), - add_object(cube((Material) {.emission_color={0}, .emission_power=0, .metallic=0, .roughness=1, .albedo=(Vector3) {1, 0, 1}}, (Vector3) {6, 0, 7}, (Vector3) {1, 1, 1})), - add_object(sphere((Material) {.emission_color={1, 0, 0}, .emission_power=0.3, .metallic=0, .roughness=0, .albedo=(Vector3) {1, 0, 0}}, (Vector3) {3, 1, 3}, 1)), - add_object(sphere((Material) {.emission_color={0}, .emission_power=0, .metallic=0, .roughness=0, .albedo=(Vector3) {0, 1, 0}}, (Vector3) {5, 1, 3}, 1)), + add_object(cube((Material) {.emission_color={0}, .emission_power=0, .metallic=0, .roughness=1, .albedo=(Vector3) {1, 0, 0}}, (Vector3) {0, 0, 0}, (Vector3) {10, 5, 0.1})); + add_object(cube((Material) {.emission_color={0}, .emission_power=0, .metallic=0, .roughness=1, .albedo=(Vector3) {1, 0, 0}}, (Vector3) {0, 0, 0}, (Vector3) {0.1, 5, 10})); + + add_object(cube((Material) {.emission_color={0}, .emission_power=0, .metallic=0, .roughness=1, .albedo=(Vector3) {0.4, 0.3, 0.9}}, (Vector3) {0, -0.1, 0}, (Vector3) {10, 0.1, 10})); + add_object(cube((Material) {.emission_color={0}, .emission_power=0, .metallic=0, .roughness=1, .albedo=(Vector3) {1, 0, 0}}, (Vector3) {7, 0, 8}, (Vector3) {1, 1, 1})); + add_object(cube((Material) {.emission_color={0}, .emission_power=0, .metallic=0, .roughness=1, .albedo=(Vector3) {1, 0, 1}}, (Vector3) {6, 0, 7}, (Vector3) {1, 1, 1})); + add_object(sphere((Material) {.emission_color={1, 0.4, 0}, .emission_power=3, .metallic=0, .roughness=0, .albedo=(Vector3) {1, 0.4, 0}}, (Vector3) {3, 1, 3}, 1)); + add_object(sphere((Material) {.emission_color={0}, .emission_power=0, .metallic=0, .roughness=0, .albedo=(Vector3) {0, 1, 0}}, (Vector3) {5, 1, 3}, 1)); + + os_mutex_create(&frame_mutex); + + os_thread workers[16]; + int num_workers = 0; + + for (int i = 0; i < 16; i++) { + os_thread_create(&workers[i], NULL, worker); + num_workers++; + } glfwSetErrorCallback(error_callback); @@ -604,14 +714,14 @@ int main(void) glfwGetWindowSize(window, &screen_w, &screen_h); float speed = 0.5; - if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) { move_camera(UP, speed); accum_index = 1; } - if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) { move_camera(DOWN, speed); accum_index = 1; } - if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) { move_camera(LEFT, speed); accum_index = 1; } - if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) { move_camera(RIGHT, speed); accum_index = 1; } + if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) { move_camera(UP, speed); invalidate_accumulation(); } + if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) { move_camera(DOWN, speed); invalidate_accumulation(); } + if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) { move_camera(LEFT, speed); invalidate_accumulation(); } + if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) { move_camera(RIGHT, speed); invalidate_accumulation(); } Vector3 clear_color = {1, 1, 1}; - update_frame_texture(1); + update_frame_texture(0.4); glViewport(0, 0, screen_w, screen_h); glClearColor(clear_color.x, clear_color.y, clear_color.z, 1.0f); diff --git a/src/profile.c b/src/profile.c new file mode 100644 index 0000000..ed05f5c --- /dev/null +++ b/src/profile.c @@ -0,0 +1,70 @@ +#include +#include "profile.h" + +void human_readable_time_interval(uint64_t ns, char *dst, size_t max) +{ + if (ns > 1000000000) + snprintf(dst, max, "%.1Lf s", (long double) ns / 1000000000); + else if (ns > 1000000) + snprintf(dst, max, "%.1Lf ms", (long double) ns / 1000000); + else if (ns > 1000) + snprintf(dst, max, "%.1Lf us", (long double) ns / 1000); + else + snprintf(dst, max, "%.1Lf ns", (long double) ns); +} + +static int +sort_callback(const void *a, const void *b) +{ + const profile_t *p1 = a; + const profile_t *p2 = b; + uint64_t n1 = atomic_load(&p1->elapsed_cycles); + uint64_t n2 = atomic_load(&p2->elapsed_cycles); + if (n1 > n2) return +1; + if (n1 < n2) return -1; + return 0; +} + +void print_profile_results(profile_results_t res_list[], int num_results, long double ns_per_cycle) +{ + int entry_count = 0; + for (int i = 0; i < num_results; i++) + entry_count += res_list[i].count; + + profile_t entries_[128]; + profile_t *entries = entries_; + + if (entry_count >= (int) (sizeof(entries_)/sizeof(profile_t))) { + entries = malloc(entry_count * sizeof(profile_t)); + if (entries == NULL) abort(); + } + + for (int i = 0, k = 0; i < num_results; i++) + for (int j = 0; j < res_list[i].count; j++) + entries[k++] = res_list[i].array[j]; + + qsort(entries, entry_count, sizeof(profile_t), sort_callback); + + if (entry_count > 0) + fprintf(stderr, "| %-30s | %-10s | %-10s | %-10s |\n", + "Name", "Calls", "Total", "Latency"); + + + for (int i = 0; i < entry_count; i++) { + if (!entries[i].name) continue; + int call_count = entries[i].call_count; + uint64_t elapsed_cycles = entries[i].elapsed_cycles; + + long double total_ns = ns_per_cycle * elapsed_cycles; + long double latency_ns = total_ns / call_count; + + char total[128]; + human_readable_time_interval(total_ns, total, sizeof(total)); + + char latency[128]; + human_readable_time_interval(latency_ns, latency, sizeof(latency)); + + fprintf(stderr, "| %-30s | %-10d | %-10s | %-10s |\n", + entries[i].name, call_count, total, latency); + } +} \ No newline at end of file diff --git a/src/profile.h b/src/profile.h new file mode 100644 index 0000000..c636137 --- /dev/null +++ b/src/profile.h @@ -0,0 +1,48 @@ +#ifndef PROFILE_H +#define PROFILE_H + +#include +#include +#include + +typedef struct { + const char *_Atomic name; + _Atomic uint64_t elapsed_cycles; + _Atomic uint64_t call_count; +} profile_t; + +typedef struct { + profile_t *array; + int count; +} profile_results_t; + +#ifdef PROFILE + +#define PROFILE_START \ + profile_t *profile__ = &profile_table__[__COUNTER__]; \ + profile__->name = __func__; \ + uint64_t profile_start__ = __rdtsc(); + +#define PROFILE_END \ + atomic_fetch_add(&profile__->elapsed_cycles, __rdtsc() - profile_start__); \ + atomic_fetch_add(&profile__->call_count, 1); + +#define PROFILE_GLOBAL_START \ + static profile_t profile_table__[]; + +#define PROFILE_GLOBAL_END \ + static profile_t profile_table__[__COUNTER__]; + +#define PROFILE_RESULTS (profile_results_t) {profile_table__, sizeof(profile_table__) / sizeof(profile_table__[0])} + +#else +#define PROFILE_START +#define PROFILE_END +#define PROFILE_GLOBAL_START +#define PROFILE_GLOBAL_END +#define PROFILE_RESULTS (profile_results_t) {NULL, 0} +#endif +#endif + +void human_readable_time_interval(uint64_t ns, char *dst, size_t max); +void print_profile_results(profile_results_t res_list[], int num_results, long double ns_per_cycle); \ No newline at end of file diff --git a/src/sync.c b/src/sync.c new file mode 100644 index 0000000..831167e --- /dev/null +++ b/src/sync.c @@ -0,0 +1,353 @@ +/* + * This is free and unencumbered software released into the public domain. + * + * Anyone is free to copy, modify, publish, use, compile, sell, or + * distribute this software, either in source code form or as a compiled + * binary, for any purpose, commercial or non-commercial, and by any + * means. + * + * In jurisdictions that recognize copyright laws, the author or authors + * of this software dedicate any and all copyright interest in the + * software to the public domain. We make this dedication for the benefit + * of the public at large and to the detriment of our heirs and + * successors. We intend this dedication to be an overt act of + * relinquishment in perpetuity of all present and future rights to this + * software under copyright law. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * For more information, please refer to + */ + +#include +#include + +#if defined(__linux__) +#include +#endif + +#include "sync.h" +#include "clock.h" + +//#define SYNC_PRINT_ERRORS +#ifdef SYNC_PRINT_ERRORS +#include +#include +#endif + +PROFILE_GLOBAL_START; + +void os_mutex_create(os_mutex_t *mutex) +{ + PROFILE_START; + +#if defined(_WIN32) + InitializeCriticalSection(mutex); +#elif defined(__linux__) + if (pthread_mutex_init(mutex, NULL)) + abort(); +#else + (void) mutex; +#endif + + PROFILE_END; +} + +void os_mutex_delete(os_mutex_t *mutex) +{ + PROFILE_START; + +#if defined(_WIN32) + DeleteCriticalSection(mutex); +#elif defined(__linux__) + if (pthread_mutex_destroy(mutex)) + abort(); +#else + (void) mutex; +#endif + + PROFILE_END; +} + +void os_mutex_lock(os_mutex_t *mutex) +{ + PROFILE_START; + +#if defined(_WIN32) + EnterCriticalSection(mutex); +#elif defined(__linux__) + if (pthread_mutex_lock(mutex)) + abort(); +#else + (void) mutex; +#endif + + PROFILE_END; +} + +void os_mutex_unlock(os_mutex_t *mutex) +{ + PROFILE_START; + +#if defined(_WIN32) + LeaveCriticalSection(mutex); +#elif defined(__linux__) + if (pthread_mutex_unlock(mutex)) + abort(); +#else + (void) mutex; +#endif + + PROFILE_END; +} + +void os_condvar_create(os_condvar_t *condvar) +{ + PROFILE_START; + +#if defined(_WIN32) + InitializeConditionVariable(condvar); +#elif defined(__linux__) + if (pthread_cond_init(condvar, NULL)) + abort(); +#else + (void) condvar; +#endif + + PROFILE_END; +} + +void os_condvar_delete(os_condvar_t *condvar) +{ + PROFILE_START; + +#if defined(__linux__) + if (pthread_cond_destroy(condvar)) + abort(); +#else + (void) condvar; +#endif + + PROFILE_END; +} + +bool os_condvar_wait(os_condvar_t *condvar, os_mutex_t *mutex, int timeout_ms) +{ + PROFILE_START; + +#if defined(_WIN32) + + DWORD timeout = INFINITE; + if (timeout_ms >= 0) timeout = timeout_ms; + if (!SleepConditionVariableCS(condvar, mutex, timeout)) { + if (GetLastError() == ERROR_TIMEOUT) { + PROFILE_END; + return false; + } + abort(); + } + + PROFILE_END; + return true; + +#elif defined(__linux__) + + int err; + if (timeout_ms < 0) + err = pthread_cond_wait(condvar, mutex); + else { + uint64_t wakeup_ms = (uint64_t) timeout_ms + get_absolute_time_us() / 1000; + struct timespec abstime = { + .tv_sec = wakeup_ms / 1000, + .tv_nsec = (wakeup_ms % 1000) * 1000000, + }; + err = pthread_cond_timedwait(condvar, mutex, &abstime); + } + if (err) { + if (err == ETIMEDOUT) { + PROFILE_END; + return false; + } +#ifdef SYNC_PRINT_ERRORS + fprintf(stderr, "ERROR!! pthread_cond_wait/timedwait: %s\n", strerror(err)); +#endif + abort(); + } + + PROFILE_END; + return true; + +#else + PROFILE_END; + (void) condvar; +#endif +} + +void os_condvar_signal(os_condvar_t *condvar) +{ + PROFILE_START; + +#if defined(_WIN32) + WakeConditionVariable(condvar); +#elif defined(__linux__) + if (pthread_cond_signal(condvar)) + abort(); +#else + (void) condvar; +#endif + + PROFILE_END; +} + +void semaphore_create(semaphore_t *sem, int count) +{ + PROFILE_START; + + sem->count = count; + os_mutex_create(&sem->mutex); + os_condvar_create(&sem->cond); + + PROFILE_END; +} + +void semaphore_delete(semaphore_t *sem) +{ + PROFILE_START; + + os_mutex_delete(&sem->mutex); + os_condvar_delete(&sem->cond); + + PROFILE_END; +} + +bool semaphore_wait(semaphore_t *sem, int count, int timeout_ms) +{ + PROFILE_START; + + assert(count > 0); + + uint64_t start_time_ms = get_relative_time_ns() / 1000000; + + os_mutex_lock(&sem->mutex); + while (sem->count < count) { + + uint64_t current_time_ms = get_relative_time_ns() / 1000000; + + int remaining_ms = -1; + if (timeout_ms >= 0) + remaining_ms = timeout_ms - (int) (current_time_ms - start_time_ms); + + if (!os_condvar_wait(&sem->cond, &sem->mutex, remaining_ms)) { + os_mutex_unlock(&sem->mutex); + PROFILE_END; + return false; + } + } + sem->count -= count; + os_mutex_unlock(&sem->mutex); + + PROFILE_END; + return true; +} + +void semaphore_signal(semaphore_t *sem, int count) +{ + PROFILE_START; + + assert(count > 0); + + os_mutex_lock(&sem->mutex); + sem->count += count; + if (sem->count > 0) + os_condvar_signal(&sem->cond); + os_mutex_unlock(&sem->mutex); + + PROFILE_END; +} + +bool os_semaphore_create(os_semaphore_t *sem, int count, int max) +{ + PROFILE_START; + + int ok; + + #ifdef _WIN32 + SECURITY_ATTRIBUTES *attr = NULL; // Default + const char *name = NULL; // No name + void *handle = CreateSemaphoreA(attr, count, max, name); + if (handle == NULL) { + PROFILE_END; + return false; + } + sem->data = handle; + ok = 1; + #else + (void) max; // POSIX doesn't use this + ok = sem_init(&sem->data, 0, count) == 0; + #endif + + PROFILE_END; + return ok; +} + +bool os_semaphore_delete(os_semaphore_t *sem) +{ + PROFILE_START; + + int ok; + + #ifdef _WIN32 + CloseHandle(sem->data); + ok = 1; + #else + ok = sem_destroy(&sem->data) == 0; + #endif + + PROFILE_END; + return ok; +} + +bool os_semaphore_wait(os_semaphore_t *sem) +{ + PROFILE_START; + + int ok; + + #ifdef _WIN32 + ok = WaitForSingleObject(sem->data, INFINITE) == WAIT_OBJECT_0; + #else + ok = sem_wait(&sem->data) == 0; + #endif + + PROFILE_END; + return ok; +} + +bool os_semaphore_signal(os_semaphore_t *sem) +{ + PROFILE_START; + + int ok; + + #ifdef _WIN32 + ok = ReleaseSemaphore(sem->data, 1, NULL); + #else + ok = sem_post(&sem->data) == 0; + #endif + + PROFILE_END; + return ok; +} + +PROFILE_GLOBAL_END; + +profile_results_t sync_profile_results(void) +{ + return PROFILE_RESULTS; +} \ No newline at end of file diff --git a/src/sync.h b/src/sync.h new file mode 100644 index 0000000..45ff950 --- /dev/null +++ b/src/sync.h @@ -0,0 +1,78 @@ +/* + * This is free and unencumbered software released into the public domain. + * + * Anyone is free to copy, modify, publish, use, compile, sell, or + * distribute this software, either in source code form or as a compiled + * binary, for any purpose, commercial or non-commercial, and by any + * means. + * + * In jurisdictions that recognize copyright laws, the author or authors + * of this software dedicate any and all copyright interest in the + * software to the public domain. We make this dedication for the benefit + * of the public at large and to the detriment of our heirs and + * successors. We intend this dedication to be an overt act of + * relinquishment in perpetuity of all present and future rights to this + * software under copyright law. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * For more information, please refer to + */ + +#include +#include +#include "profile.h" + +#ifdef _WIN32 +#define WIN32_MEAN_AND_LEAN +#include +typedef CRITICAL_SECTION os_mutex_t; +typedef CONDITION_VARIABLE os_condvar_t; +#elif defined(__linux__) +#include +#include +typedef pthread_mutex_t os_mutex_t; +typedef pthread_cond_t os_condvar_t; +#endif + +typedef struct { +#ifdef _WIN32 + void *data; +#else + sem_t data; +#endif +} os_semaphore_t; + +typedef struct { + int count; + os_mutex_t mutex; + os_condvar_t cond; +} semaphore_t; + +void os_mutex_create(os_mutex_t *mutex); +void os_mutex_delete(os_mutex_t *mutex); +void os_mutex_lock (os_mutex_t *mutex); +void os_mutex_unlock(os_mutex_t *mutex); + +void os_condvar_create(os_condvar_t *condvar); +void os_condvar_delete(os_condvar_t *condvar); +bool os_condvar_wait (os_condvar_t *condvar, os_mutex_t *mutex, int timeout_ms); +void os_condvar_signal(os_condvar_t *condvar); + +void semaphore_create(semaphore_t *sem, int count); +void semaphore_delete(semaphore_t *sem); +bool semaphore_wait (semaphore_t *sem, int count, int timeout_ms); +void semaphore_signal(semaphore_t *sem, int count); + +bool os_semaphore_create(os_semaphore_t *sem, int count, int max); +bool os_semaphore_delete(os_semaphore_t *sem); +bool os_semaphore_wait (os_semaphore_t *sem); +bool os_semaphore_signal(os_semaphore_t *sem); + +profile_results_t sync_profile_results(void); \ No newline at end of file diff --git a/src/thread.c b/src/thread.c new file mode 100644 index 0000000..6b9531c --- /dev/null +++ b/src/thread.c @@ -0,0 +1,75 @@ +/* + * This is free and unencumbered software released into the public domain. + * + * Anyone is free to copy, modify, publish, use, compile, sell, or + * distribute this software, either in source code form or as a compiled + * binary, for any purpose, commercial or non-commercial, and by any + * means. + * + * In jurisdictions that recognize copyright laws, the author or authors + * of this software dedicate any and all copyright interest in the + * software to the public domain. We make this dedication for the benefit + * of the public at large and to the detriment of our heirs and + * successors. We intend this dedication to be an overt act of + * relinquishment in perpetuity of all present and future rights to this + * software under copyright law. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * For more information, please refer to + */ + +#include + +#ifdef _WIN32 +#else +#include +#endif + +#include "thread.h" + +void os_thread_create(os_thread *thread, void *arg, os_threadreturn (*func)(void*)) +{ + #if defined(_WIN32) + os_thread thread_ = CreateThread(NULL, 0, func, arg, 0, NULL); + if (thread_ == INVALID_HANDLE_VALUE) + abort(); + *thread = thread_; + #elif defined(__linux__) + int ret = pthread_create(thread, NULL, func, arg); + if (ret) abort(); + #endif +} + +os_threadreturn os_thread_join(os_thread thread) +{ + #if defined(_WIN32) + os_threadreturn result; + WaitForSingleObject(thread, INFINITE); + if (!GetExitCodeThread(thread, &result)) + abort(); + CloseHandle(thread); + return result; + #elif defined(__linux__) + os_threadreturn result; + int ret = pthread_join(thread, &result); + if (ret) abort(); + return result; + #else + (void) thread; + #endif +} + +uint64_t get_thread_id(void) +{ + static _Atomic uint64_t next_id = 1; + static _Thread_local uint64_t id = 0; + if (id == 0) id = atomic_fetch_add(&next_id, 1); + return id; +} \ No newline at end of file diff --git a/src/thread.h b/src/thread.h new file mode 100644 index 0000000..e40904a --- /dev/null +++ b/src/thread.h @@ -0,0 +1,45 @@ +/* + * This is free and unencumbered software released into the public domain. + * + * Anyone is free to copy, modify, publish, use, compile, sell, or + * distribute this software, either in source code form or as a compiled + * binary, for any purpose, commercial or non-commercial, and by any + * means. + * + * In jurisdictions that recognize copyright laws, the author or authors + * of this software dedicate any and all copyright interest in the + * software to the public domain. We make this dedication for the benefit + * of the public at large and to the detriment of our heirs and + * successors. We intend this dedication to be an overt act of + * relinquishment in perpetuity of all present and future rights to this + * software under copyright law. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * For more information, please refer to + */ + +#include +#include + +#if defined(_WIN32) +#define WIN32_MEAN_AND_LEAN +#include +typedef void *os_thread; +typedef unsigned long os_threadreturn; +#elif defined(__linux__) +#include +typedef pthread_t os_thread; +typedef void *os_threadreturn; +#endif + +uint64_t get_thread_id(void); + +void os_thread_create(os_thread *thread, void *arg, os_threadreturn (*func)(void*)); +os_threadreturn os_thread_join(os_thread thread); \ No newline at end of file