use floating job for playback wait

This commit is contained in:
jacob 2025-07-11 14:57:39 -05:00
parent 0ce5a1ed87
commit e02858fd21
5 changed files with 77 additions and 64 deletions

View File

@ -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) {

View File

@ -20,12 +20,9 @@
#include <initguid.h>
#include <objbase.h>
#include <uuids.h>
#include <avrt.h>
#include <Audioclient.h>
#include <mmdeviceapi.h>
#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 */
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);
}
}

View File

@ -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
};

View File

@ -19,6 +19,7 @@
# include <fileapi.h>
# include <dwmapi.h>
# include <bcrypt.h>
# include <avrt.h>
#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);

View File

@ -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);