diff --git a/core/math/linalg/specific.odin b/core/math/linalg/specific.odin index 055c112bb..e940ee05d 100644 --- a/core/math/linalg/specific.odin +++ b/core/math/linalg/specific.odin @@ -737,35 +737,50 @@ matrix4_look_at :: proc(eye, centre, up: Vector3) -> Matrix4 { } -matrix4_perspective :: proc(fovy, aspect, near, far: Float) -> (m: Matrix4) { +matrix4_perspective :: proc(fovy, aspect, near, far: Float, flip_z_axis := true) -> (m: Matrix4) { tan_half_fovy := math.tan(0.5 * fovy); m[0][0] = 1 / (aspect*tan_half_fovy); m[1][1] = 1 / (tan_half_fovy); - m[2][2] = -(far + near) / (far - near); - m[2][3] = -1; + m[2][2] = +(far + near) / (far - near); + m[2][3] = +1; m[3][2] = -2*far*near / (far - near); + + if flip_z_axis { + m[2] = -m[2]; + } + return; } -matrix_ortho3d :: proc(left, right, bottom, top, near, far: Float) -> (m: Matrix4) { +matrix_ortho3d :: proc(left, right, bottom, top, near, far: Float, flip_z_axis := true) -> (m: Matrix4) { m[0][0] = +2 / (right - left); m[1][1] = +2 / (top - bottom); - m[2][2] = -2 / (far - near); + m[2][2] = +2 / (far - near); m[3][0] = -(right + left) / (right - left); m[3][1] = -(top + bottom) / (top - bottom); m[3][2] = -(far + near) / (far- near); m[3][3] = 1; + + if flip_z_axis { + m[2] = -m[2]; + } + return; } -matrix4_infinite_perspective :: proc(fovy, aspect, near: Float) -> (m: Matrix4) { +matrix4_infinite_perspective :: proc(fovy, aspect, near: Float, flip_z_axis := true) -> (m: Matrix4) { tan_half_fovy := math.tan(0.5 * fovy); m[0][0] = 1 / (aspect*tan_half_fovy); m[1][1] = 1 / (tan_half_fovy); - m[2][2] = -1; - m[2][3] = -1; + m[2][2] = +1; + m[2][3] = +1; m[3][2] = -2*near; + + if flip_z_axis { + m[2] = -m[2]; + } + return; } diff --git a/core/sync/sync_windows.odin b/core/sync/sync_windows.odin index a99ac8497..b1b73752e 100644 --- a/core/sync/sync_windows.odin +++ b/core/sync/sync_windows.odin @@ -83,4 +83,4 @@ condition_signal :: proc(using c: ^Condition) { condition_wait_for :: proc(using c: ^Condition) { result := win32.wait_for_single_object(event, win32.INFINITE); assert(result != win32.WAIT_FAILED); -} \ No newline at end of file +} diff --git a/core/sys/unix/pthread_unix.odin b/core/sys/unix/pthread_unix.odin index 885048a59..cd3b5c198 100644 --- a/core/sys/unix/pthread_unix.odin +++ b/core/sys/unix/pthread_unix.odin @@ -51,6 +51,8 @@ foreign pthread { // know what you are doing! pthread_attr_setstack :: proc(attrs: ^pthread_attr_t, stack_ptr: rawptr, stack_size: u64) -> c.int ---; pthread_attr_getstack :: proc(attrs: ^pthread_attr_t, stack_ptr: ^rawptr, stack_size: ^u64) -> c.int ---; + + pthread_yield :: proc() -> c.int ---; } @(default_calling_convention="c") @@ -104,4 +106,4 @@ foreign pthread { pthread_mutexattr_setpshared :: proc(attrs: ^pthread_mutexattr_t, value: c.int) -> c.int ---; pthread_mutexattr_getpshared :: proc(attrs: ^pthread_mutexattr_t, result: ^c.int) -> c.int ---; -} \ No newline at end of file +} diff --git a/core/thread/thread.odin b/core/thread/thread.odin index c326b30f1..00ee2f4b3 100644 --- a/core/thread/thread.odin +++ b/core/thread/thread.odin @@ -1,6 +1,6 @@ -package thread; +package thread -import "core:runtime"; +import "core:runtime" Thread_Proc :: #type proc(^Thread); @@ -12,4 +12,4 @@ Thread :: struct { init_context: runtime.Context, use_init_context: bool, -} \ No newline at end of file +} diff --git a/core/thread/thread_pool.odin b/core/thread/thread_pool.odin new file mode 100644 index 000000000..61d326f0c --- /dev/null +++ b/core/thread/thread_pool.odin @@ -0,0 +1,147 @@ +package thread + +import "intrinsics" +import "core:sync" +import "core:mem" + +Task_Status :: enum i32 { + Ready, + Busy, + Waiting, + Term, +} + +Task_Proc :: #type proc(task: ^Task); + +Task :: struct { + procedure: Task_Proc, + data: rawptr, + user_index: int, +} + +Task_Id :: distinct i32; +INVALID_TASK_ID :: Task_Id(-1); + + +Pool :: struct { + allocator: mem.Allocator, + mutex: sync.Mutex, + sem_available: sync.Semaphore, + processing_task_count: int, // atomic + is_running: bool, + + threads: []^Thread, + + tasks: [dynamic]Task, +} + +pool_init :: proc(pool: ^Pool, thread_count: int, allocator := context.allocator) { + worker_thread_internal :: proc(t: ^Thread) { + pool := (^Pool)(t.data); + + for pool.is_running { + sync.semaphore_wait_for(&pool.sem_available); + + if task, ok := pool_try_and_pop_task(pool); ok { + pool_do_work(pool, &task); + } + } + + sync.semaphore_post(&pool.sem_available, 1); + } + + + context.allocator = allocator; + pool.allocator = allocator; + pool.tasks = make([dynamic]Task); + pool.threads = make([]^Thread, thread_count); + + sync.mutex_init(&pool.mutex); + sync.semaphore_init(&pool.sem_available); + pool.is_running = true; + + for _, i in pool.threads { + t := create(worker_thread_internal); + t.user_index = i; + t.data = pool; + pool.threads[i] = t; + } +} + +pool_destroy :: proc(pool: ^Pool) { + delete(pool.tasks); + delete(pool.threads, pool.allocator); + + sync.mutex_destroy(&pool.mutex); + sync.semaphore_destroy(&pool.sem_available); +} + +pool_start :: proc(pool: ^Pool) { + for t in pool.threads { + start(t); + } +} + +pool_join :: proc(pool: ^Pool) { + pool.is_running = false; + + sync.semaphore_post(&pool.sem_available, len(pool.threads)); + + yield(); + + for t in pool.threads { + join(t); + } +} + +pool_add_task :: proc(pool: ^Pool, procedure: Task_Proc, data: rawptr, user_index: int = 0) { + sync.mutex_lock(&pool.mutex); + defer sync.mutex_unlock(&pool.mutex); + + task: Task; + task.procedure = procedure; + task.data = data; + task.user_index = user_index; + + append(&pool.tasks, task); + sync.semaphore_post(&pool.sem_available, 1); +} + +pool_try_and_pop_task :: proc(pool: ^Pool) -> (task: Task, got_task: bool = false) { + if sync.mutex_try_lock(&pool.mutex) { + if len(pool.tasks) != 0 { + intrinsics.atomic_add(&pool.processing_task_count, 1); + task = pool.tasks[0]; + got_task = true; + ordered_remove(&pool.tasks, 0); + } + sync.mutex_unlock(&pool.mutex); + } + return; +} + + +pool_do_work :: proc(pool: ^Pool, task: ^Task) { + task.procedure(task); + intrinsics.atomic_sub(&pool.processing_task_count, 1); +} + + +pool_wait_and_process :: proc(pool: ^Pool) { + for len(pool.tasks) != 0 || intrinsics.atomic_load(&pool.processing_task_count) != 0 { + if task, ok := pool_try_and_pop_task(pool); ok { + pool_do_work(pool, &task); + } + + // Safety kick + if len(pool.tasks) != 0 && intrinsics.atomic_load(&pool.processing_task_count) == 0 { + sync.mutex_lock(&pool.mutex); + sync.semaphore_post(&pool.sem_available, len(pool.tasks)); + sync.mutex_unlock(&pool.mutex); + } + + yield(); + } + + pool_join(pool); +} diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index 9a1680043..15f345c7c 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -155,3 +155,8 @@ destroy :: proc(t: ^Thread) { t.unix_thread = {}; free(t); } + + +yield :: proc() { + unix.pthread_yield() +} diff --git a/core/thread/thread_windows.odin b/core/thread/thread_windows.odin index fb8915057..a558e1b0a 100644 --- a/core/thread/thread_windows.odin +++ b/core/thread/thread_windows.odin @@ -92,3 +92,7 @@ destroy :: proc(thread: ^Thread) { terminate :: proc(using thread : ^Thread, exit_code : u32) { win32.terminate_thread(win32_thread, exit_code); } + +yield :: proc() { + win32.sleep(0); +} diff --git a/examples/demo/demo.odin b/examples/demo/demo.odin index 7b3a07f73..935b24286 100644 --- a/examples/demo/demo.odin +++ b/examples/demo/demo.odin @@ -4,6 +4,7 @@ import "core:fmt" import "core:mem" import "core:os" import "core:thread" +import "core:time" import "core:reflect" import "intrinsics" @@ -1102,38 +1103,65 @@ prefix_table := [?]string{ threading_example :: proc() { fmt.println("\n# threading_example"); - worker_proc :: proc(t: ^thread.Thread) { - for iteration in 1..5 { - fmt.printf("Thread %d is on iteration %d\n", t.user_index, iteration); - fmt.printf("`%s`: iteration %d\n", prefix_table[t.user_index], iteration); - // win32.sleep(1); - } - } - - threads := make([dynamic]^thread.Thread, 0, len(prefix_table)); - defer delete(threads); - - for in prefix_table { - if t := thread.create(worker_proc); t != nil { - t.init_context = context; - t.use_init_context = true; - t.user_index = len(threads); - append(&threads, t); - thread.start(t); - } - } - - for len(threads) > 0 { - for i := 0; i < len(threads); /**/ { - if t := threads[i]; thread.is_done(t) { - fmt.printf("Thread %d is done\n", t.user_index); - thread.destroy(t); - - ordered_remove(&threads, i); - } else { - i += 1; + { // Basic Threads + fmt.println("\n## Basic Threads"); + worker_proc :: proc(t: ^thread.Thread) { + for iteration in 1..5 { + fmt.printf("Thread %d is on iteration %d\n", t.user_index, iteration); + fmt.printf("`%s`: iteration %d\n", prefix_table[t.user_index], iteration); + time.sleep(1 * time.Millisecond); } } + + threads := make([dynamic]^thread.Thread, 0, len(prefix_table)); + defer delete(threads); + + for in prefix_table { + if t := thread.create(worker_proc); t != nil { + t.init_context = context; + t.use_init_context = true; + t.user_index = len(threads); + append(&threads, t); + thread.start(t); + } + } + + for len(threads) > 0 { + for i := 0; i < len(threads); /**/ { + if t := threads[i]; thread.is_done(t) { + fmt.printf("Thread %d is done\n", t.user_index); + thread.destroy(t); + + ordered_remove(&threads, i); + } else { + i += 1; + } + } + } + } + + { // Thread Pool + fmt.println("\n## Thread Pool"); + task_proc :: proc(t: ^thread.Task) { + index := t.user_index % len(prefix_table); + for iteration in 1..5 { + fmt.printf("Worker Task %d is on iteration %d\n", t.user_index, iteration); + fmt.printf("`%s`: iteration %d\n", prefix_table[index], iteration); + time.sleep(1 * time.Millisecond); + } + } + + pool: thread.Pool; + thread.pool_init(pool=&pool, thread_count=3); + defer thread.pool_destroy(&pool); + + + for i in 0..<30 { + thread.pool_add_task(pool=&pool, procedure=task_proc, data=nil, user_index=i); + } + + thread.pool_start(&pool); + thread.pool_wait_and_process(&pool); } }