From e02858fd2121dd4cfd1409074b311b5fac2b7a26 Mon Sep 17 00:00:00 2001 From: jacob Date: Fri, 11 Jul 2025 14:57:39 -0500 Subject: [PATCH] use floating job for playback wait --- src/gp_dx12.c | 2 +- src/playback_wasapi.c | 53 +++++++++++++++--------------------- src/sys.h | 22 +++++++-------- src/sys_win32.c | 62 ++++++++++++++++++++++++++++++------------- src/user.c | 2 +- 5 files changed, 77 insertions(+), 64 deletions(-) diff --git a/src/gp_dx12.c b/src/gp_dx12.c index aa349452..19d03c6d 100644 --- a/src/gp_dx12.c +++ b/src/gp_dx12.c @@ -2605,7 +2605,7 @@ struct gp_swapchain *gp_swapchain_alloc(struct sys_window *window, struct v2i32 HWND hwnd = (HWND)sys_window_get_internal_handle(window); struct command_queue *cq = G.command_queues[DX12_QUEUE_DIRECT]; - struct swapchain *swapchain = NULL; + struct swapchain *swapchain = 0; { struct snc_lock lock = snc_lock_e(&G.swapchains_mutex); if (G.first_free_swapchain) { diff --git a/src/playback_wasapi.c b/src/playback_wasapi.c index b94f081e..6add84d1 100644 --- a/src/playback_wasapi.c +++ b/src/playback_wasapi.c @@ -20,12 +20,9 @@ #include #include #include -#include #include #include -#pragma comment(lib, "avrt") - DEFINE_GUID(CLSID_MMDeviceEnumerator, 0xbcde0395, 0xe52f, 0x467c, 0x8e, 0x3d, 0xc4, 0x57, 0x92, 0x91, 0x69, 0x2e); DEFINE_GUID(IID_IMMDeviceEnumerator, 0xa95664d2, 0x9614, 0x4f35, 0xa7, 0x46, 0xde, 0x8d, 0xb6, 0x36, 0x17, 0xe6); DEFINE_GUID(IID_IAudioClient, 0x1cb9ad4c, 0xdbfa, 0x4c32, 0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2); @@ -43,8 +40,8 @@ GLOBAL struct { HANDLE event; IAudioRenderClient *playback; WAVEFORMATEX *buffer_format; - struct sys_thread *playback_scheduler_thread; u32 buffer_frames; + struct snc_counter playback_job_counter; } G = ZI, DEBUG_ALIAS(G, G_playback_wasapi); /* ========================== * @@ -53,14 +50,15 @@ GLOBAL struct { INTERNAL void wasapi_initialize(void); INTERNAL SYS_EXIT_FUNC(playback_shutdown); -INTERNAL SYS_THREAD_DEF(playback_scheduler_entry, _); +INTERNAL SYS_JOB_DEF(playback_job, _); struct playback_startup_receipt playback_startup(struct mixer_startup_receipt *mixer_sr) { (UNUSED)mixer_sr; wasapi_initialize(); - G.playback_scheduler_thread = sys_thread_alloc(playback_scheduler_entry, 0, LIT("Playback scheduler thread"), PROF_THREAD_GROUP_SCHEDULER); + /* Start playback job */ + sys_run(1, playback_job, 0, SYS_POOL_AUDIO, SYS_PRIORITY_HIGH, &G.playback_job_counter); sys_on_exit(&playback_shutdown); return (struct playback_startup_receipt) { 0 }; @@ -70,7 +68,7 @@ INTERNAL SYS_EXIT_FUNC(playback_shutdown) { __prof; atomic32_fetch_set(&G.shutdown, 1); - sys_thread_wait_release(G.playback_scheduler_thread); + snc_counter_wait(&G.playback_job_counter); } /* ========================== * @@ -223,41 +221,34 @@ INTERNAL void wasapi_update_end(struct wasapi_buffer *wspbuf, struct mixed_pcm_f * Playback thread entry * ========================== */ -INTERNAL SYS_JOB_DEF(playback_mix_job, _) +INTERNAL SYS_JOB_DEF(playback_wait_job, _) +{ + __prof; + WaitForSingleObject(G.event, INFINITE); +} + +INTERNAL SYS_JOB_DEF(playback_job, _) { __prof; (UNUSED)_; - struct arena_temp scratch = scratch_begin_no_conflict(); - { - struct wasapi_buffer wspbuf = wasapi_update_begin(); - struct mixed_pcm_f32 pcm = mixer_update(scratch.arena, wspbuf.frames_count); - wasapi_update_end(&wspbuf, pcm); - } - scratch_end(scratch); -} - -INTERNAL SYS_THREAD_DEF(playback_scheduler_entry, _) -{ - (UNUSED)_; - /* https://learn.microsoft.com/en-us/windows/win32/procthread/multimedia-class-scheduler-service#registry-settings */ - DWORD task = 0; - HANDLE mmc_handle = AvSetMmThreadCharacteristicsW(L"Pro Audio", &task); - ASSERT(mmc_handle); - (UNUSED)mmc_handle; /* FIXME: If playback fails at any point and mixer stops advancing, we * need to halt mixer to prevent memory leak when sounds are played. */ - /* TODO: Signal counter that running job wiats on, rather than scheduling job manually */ + /* TODO: Signal counter that running job wiats on, rather than scheduling job manually */ while (!atomic32_fetch(&G.shutdown)) { + struct arena_temp scratch = scratch_begin_no_conflict(); { __profn("Wasapi wait"); - WaitForSingleObject(G.event, INFINITE); - } - { - __profn("Run mix job & wait"); struct snc_counter counter = ZI; - sys_run(1, playback_mix_job, 0, SYS_POOL_DEDICATED, SYS_PRIORITY_CRITICAL, &counter); + sys_run(1, playback_wait_job, 0, SYS_POOL_FLOATING, SYS_PRIORITY_INHERIT, &counter); snc_counter_wait(&counter); } + { + __profn("Fill sample buffer"); + struct wasapi_buffer wspbuf = wasapi_update_begin(); + struct mixed_pcm_f32 pcm = mixer_update(scratch.arena, wspbuf.frames_count); + wasapi_update_end(&wspbuf, pcm); + } + scratch_end(scratch); } } diff --git a/src/sys.h b/src/sys.h index 313978e6..7d868031 100644 --- a/src/sys.h +++ b/src/sys.h @@ -58,17 +58,14 @@ void sys_make_current_job_unyielding(void); enum sys_pool { SYS_POOL_INHERIT = -1, - /* This pool contains workers that rougly map to dedicated performance cores on the CPU. - * Time critical jobs should be pushed here. */ - SYS_POOL_DEDICATED = 0, + /* The floating pool contains a large number of lower priority threads that have affinity over the entire CPU. + * Other pools should push jobs that only block and do no work here so that they can yield on the blocking job rather than blocking themselves. */ + SYS_POOL_FLOATING = 0, - /* This pool contains workers that that will not interfere with the dedicated pool. - * Asynchronous jobs (e.g. as asset streaming) should be pushed here. */ SYS_POOL_BACKGROUND = 1, - - /* This pool contains a large number of floating low priority worker threads with the intent that these threads will only block and do no actual work. - * Blocking operations (e.g. opening a file) should be isolated to jobs that get pushed to this pool. They can then be yielded on by jobs in other pools that actually do work. */ - SYS_POOL_FLOATING = 2, + SYS_POOL_AUDIO = 2, + SYS_POOL_USER = 3, + SYS_POOL_SIM = 4, NUM_SYS_POOLS }; @@ -76,10 +73,9 @@ enum sys_pool { /* Job execution order within a pool is based on priority. */ enum sys_priority { SYS_PRIORITY_INHERIT = -1, - SYS_PRIORITY_CRITICAL = 0, - SYS_PRIORITY_HIGH = 1, - SYS_PRIORITY_NORMAL = 2, - SYS_PRIORITY_LOW = 3, + SYS_PRIORITY_HIGH = 0, + SYS_PRIORITY_NORMAL = 1, + SYS_PRIORITY_LOW = 2, NUM_SYS_PRIORITIES }; diff --git a/src/sys_win32.c b/src/sys_win32.c index de2ab6d8..bd15ebd2 100644 --- a/src/sys_win32.c +++ b/src/sys_win32.c @@ -19,6 +19,7 @@ # include # include # include +# include #pragma warning(pop) #pragma comment(lib, "kernel32") @@ -29,6 +30,7 @@ #pragma comment(lib, "dwmapi") #pragma comment(lib, "bcrypt") #pragma comment(lib, "synchronization") +#pragma comment(lib, "avrt") #define SYS_WINDOW_EVENT_LISTENERS_MAX 512 #define WINDOW_CLASS_NAME L"power_play_window_class" @@ -248,6 +250,7 @@ struct alignas(64) job_pool { i32 num_worker_threads; i32 thread_priority; u64 thread_affinity_mask; + char *thread_mm_characteristics; struct arena *worker_threads_arena; struct sys_thread **worker_threads; struct worker_ctx *worker_contexts; @@ -782,10 +785,11 @@ INTERNAL struct fiber *fiber_alloc(struct job_pool *pool) return fiber; } -INTERNAL void fiber_release(struct job_pool *pool, struct fiber *fiber, i16 fiber_id) +INTERNAL void fiber_release(struct job_pool *pool, struct fiber *fiber) { tm_lock(&pool->free_fibers_lock); { + i16 fiber_id = fiber->id; fiber->parent_id = pool->first_free_fiber_id; pool->first_free_fiber_id = fiber_id; } @@ -876,7 +880,7 @@ INTERNAL void job_fiber_yield(struct fiber *fiber, struct fiber *parent_fiber) MemoryBarrier(); SwitchToFiber(parent_fiber->addr); MemoryBarrier(); - __prof_fiber_enter(fiber->name_cstr, PROF_THREAD_GROUP_FIBERS + MEBI(fiber->job_pool) + fiber->id + 1); + __prof_fiber_enter(fiber->name_cstr, PROF_THREAD_GROUP_FIBERS - MEBI(fiber->job_pool) + fiber->id + 1); } } @@ -895,7 +899,7 @@ INTERNAL void job_fiber_entry(void *id_ptr) { i16 id = (i32)(i64)id_ptr; struct fiber *fiber = fiber_from_id(id); - __prof_fiber_enter(fiber->name_cstr, PROF_THREAD_GROUP_FIBERS + MEBI(fiber->job_pool) + fiber->id + 1); + __prof_fiber_enter(fiber->name_cstr, PROF_THREAD_GROUP_FIBERS - MEBI(fiber->job_pool) + fiber->id + 1); for (;;) { /* Run job */ { @@ -947,6 +951,14 @@ INTERNAL SYS_THREAD_DEF(job_worker_entry, worker_ctx_arg) ASSERT(success); (UNUSED)success; } + + if (pool->thread_mm_characteristics) { + /* https://learn.microsoft.com/en-us/windows/win32/procthread/multimedia-class-scheduler-service#registry-settings */ + DWORD task = 0; + HANDLE mmc_handle = AvSetMmThreadCharacteristicsA(pool->thread_mm_characteristics, &task); + ASSERT(mmc_handle); + (UNUSED)mmc_handle; + } } i32 worker_fiber_id = sys_current_fiber_id(); @@ -1016,7 +1028,7 @@ INTERNAL SYS_THREAD_DEF(job_worker_entry, worker_ctx_arg) /* Use resumed fiber if present */ if (job_fiber_id > 0) { if (job_fiber) { - fiber_release(pool, job_fiber, job_fiber->id); + fiber_release(pool, job_fiber); } job_fiber = fiber_from_id(job_fiber_id); } @@ -1028,7 +1040,8 @@ INTERNAL SYS_THREAD_DEF(job_worker_entry, worker_ctx_arg) } job_fiber_id = job_fiber->id; { - __profnc("Run fiber", RGB32_F(0.25, 0.75, 0)); + //__profnc("Run fiber", RGB32_F(0.25, 0.75, 0)); + __profnc("Run fiber", RGB32_F(1, 1, 1)); __profvalue(job_fiber->id); struct yield_param yield = ZI; job_fiber->parent_id = worker_fiber_id; @@ -1219,9 +1232,6 @@ INTERNAL SYS_THREAD_DEF(job_scheduler_entry, _) b32 success = SetThreadPriority(GetCurrentThread(), priority); (UNUSED)success; ASSERT(success); - - success = SetThreadAffinityMask(GetCurrentThread(), (1 << 14)) != 0; - ASSERT(success); } /* Create high resolution timer */ @@ -1297,32 +1307,48 @@ INTERNAL SYS_THREAD_DEF(test_entry, _) for (enum sys_pool pool_kind = 0; pool_kind < (i32)countof(G.job_pools); ++pool_kind) { struct job_pool *pool = &G.job_pools[pool_kind]; struct string name_fmt = ZI; - i32 prof_group = PROF_THREAD_GROUP_FIBERS + MEBI(pool_kind); + i32 prof_group = PROF_THREAD_GROUP_FIBERS - MEBI(pool_kind); switch (pool_kind) { default: ASSERT(0); break; - case SYS_POOL_DEDICATED: + case SYS_POOL_SIM: { - name_fmt = LIT("Dedicated worker #%F"); - pool->thread_affinity_mask = 0xFFFFFFFFFFFFFFFFULL; + name_fmt = LIT("Sim worker #%F"); + pool->thread_affinity_mask = 0x000000000000000Full; pool->thread_priority = THREAD_PRIORITY_TIME_CRITICAL; pool->num_worker_threads = 4; } break; + case SYS_POOL_USER: + { + name_fmt = LIT("User worker #%F"); + pool->thread_affinity_mask = 0x00000000000000F0ull; + pool->thread_priority = THREAD_PRIORITY_TIME_CRITICAL; + pool->num_worker_threads = 4; + } break; + + case SYS_POOL_AUDIO: + { + name_fmt = LIT("Audio worker #%F"); + pool->thread_affinity_mask = 0x0000000000000300ull; + pool->thread_priority = THREAD_PRIORITY_TIME_CRITICAL; + pool->thread_mm_characteristics = "Pro Audio"; + pool->num_worker_threads = 2; + } break; + case SYS_POOL_BACKGROUND: { name_fmt = LIT("Background worker #%F"); - pool->thread_affinity_mask = 0xFFFFFFFFFFFFFFFFULL; - pool->thread_priority = 0; - pool->num_worker_threads = 4; + pool->thread_affinity_mask = 0x0000000000003000ull; + pool->num_worker_threads = 2; } break; case SYS_POOL_FLOATING: { name_fmt = LIT("Floating worker #%F"); - pool->thread_affinity_mask = 0xFFFFFFFFFFFFFFFFULL; + pool->thread_affinity_mask = 0xFFFFFFFFFFFFFFFFull; pool->thread_priority = 0; - pool->num_worker_threads = 4; + pool->num_worker_threads = 32; } break; } pool->worker_threads_arena = arena_alloc(GIBI(64)); @@ -2299,7 +2325,7 @@ INTERNAL SYS_THREAD_DEF(window_present_thread_entry_point, arg) struct sys_window_present_job_sig sig = ZI; sig.window = (struct sys_window *)window; sig.events = events; - sys_run(1, window->present_job, &sig, SYS_POOL_DEDICATED, SYS_PRIORITY_HIGH, &counter); + sys_run(1, window->present_job, &sig, SYS_POOL_USER, SYS_PRIORITY_HIGH, &counter); snc_counter_wait(&counter); } arena_reset(events_arena); diff --git a/src/user.c b/src/user.c index 1e9acd36..0ea96e81 100644 --- a/src/user.c +++ b/src/user.c @@ -243,7 +243,7 @@ struct user_startup_receipt user_startup(struct font_startup_receipt *font_sr, log_register_callback(debug_console_log_callback, LOG_LEVEL_DEBUG); /* Start sim job */ - sys_run(1, local_sim_job, 0, SYS_POOL_DEDICATED, SYS_PRIORITY_HIGH, &G.shutdown_job_counters); + sys_run(1, local_sim_job, 0, SYS_POOL_SIM, SYS_PRIORITY_HIGH, &G.shutdown_job_counters); sys_on_exit(&user_shutdown); G.window = sys_window_alloc(user_update_job);