Update sounds at ~60Hz instead of ~10Hz, fixes #141

For some reason sounds were only updated/started about every 100ms,
which could lead to delays of up to around 100ms after a sound gets
started (with idSoundEmitterLocal::StartSound()) until OpenAL is finally
told to play the sound.
Also, the actual delay drifted over time between 1ms and 100ms, as the
sound ticks weren't a fixed multiple of the (16ms) gameticks - and the
sound updates didn't even happen at the regular 92-94ms intervals they
should because they run in the async thread which only updates every
16ms...
Because of this, the machine gun and other rapid firing weapons sounded
like they shot in bursts or left out shots occasionally, even though
they don't.
Anyway, now sound is updated every 16ms in the async thread so delays
are <= 16ms and hopefully less noticeable.

You can still get the old behavior with com_asyncSound 2 if you want.
This commit is contained in:
Daniel Gibson 2020-06-01 20:17:19 +02:00
parent e6f3713169
commit 504b572ae4
4 changed files with 28 additions and 31 deletions

View file

@ -83,12 +83,8 @@ idCVar com_purgeAll( "com_purgeAll", "0", CVAR_BOOL | CVAR_ARCHIVE | CVAR_SYSTEM
idCVar com_memoryMarker( "com_memoryMarker", "-1", CVAR_INTEGER | CVAR_SYSTEM | CVAR_INIT, "used as a marker for memory stats" ); idCVar com_memoryMarker( "com_memoryMarker", "-1", CVAR_INTEGER | CVAR_SYSTEM | CVAR_INIT, "used as a marker for memory stats" );
idCVar com_preciseTic( "com_preciseTic", "1", CVAR_BOOL|CVAR_SYSTEM, "run one game tick every async thread update" ); idCVar com_preciseTic( "com_preciseTic", "1", CVAR_BOOL|CVAR_SYSTEM, "run one game tick every async thread update" );
idCVar com_asyncInput( "com_asyncInput", "0", CVAR_BOOL|CVAR_SYSTEM, "sample input from the async thread" ); idCVar com_asyncInput( "com_asyncInput", "0", CVAR_BOOL|CVAR_SYSTEM, "sample input from the async thread" );
#define ASYNCSOUND_INFO "0: mix sound inline, 1: memory mapped async mix, 2: callback mixing, 3: write async mix" #define ASYNCSOUND_INFO "0: mix sound inline, 1 or 3: async update every 16ms 2: async update about every 100ms (original behavior)"
#if defined( __unix__ ) && !defined( MACOS_X ) idCVar com_asyncSound( "com_asyncSound", "1", CVAR_INTEGER|CVAR_SYSTEM, ASYNCSOUND_INFO, 0, 3 );
idCVar com_asyncSound( "com_asyncSound", "3", CVAR_INTEGER|CVAR_SYSTEM|CVAR_ROM, ASYNCSOUND_INFO );
#else
idCVar com_asyncSound( "com_asyncSound", "1", CVAR_INTEGER|CVAR_SYSTEM, ASYNCSOUND_INFO, 0, 1 );
#endif
idCVar com_forceGenericSIMD( "com_forceGenericSIMD", "0", CVAR_BOOL | CVAR_SYSTEM | CVAR_NOCHEAT, "force generic platform independent SIMD" ); idCVar com_forceGenericSIMD( "com_forceGenericSIMD", "0", CVAR_BOOL | CVAR_SYSTEM | CVAR_NOCHEAT, "force generic platform independent SIMD" );
idCVar com_developer( "developer", "0", CVAR_BOOL|CVAR_SYSTEM|CVAR_NOCHEAT, "developer mode" ); idCVar com_developer( "developer", "0", CVAR_BOOL|CVAR_SYSTEM|CVAR_NOCHEAT, "developer mode" );
idCVar com_allowConsole( "com_allowConsole", "0", CVAR_BOOL | CVAR_SYSTEM | CVAR_NOCHEAT, "allow toggling console with the tilde key" ); idCVar com_allowConsole( "com_allowConsole", "0", CVAR_BOOL | CVAR_SYSTEM | CVAR_NOCHEAT, "allow toggling console with the tilde key" );
@ -2506,11 +2502,14 @@ void idCommonLocal::SingleAsyncTic( void ) {
switch ( com_asyncSound.GetInteger() ) { switch ( com_asyncSound.GetInteger() ) {
case 1: case 1:
soundSystem->AsyncUpdate( stat->milliseconds );
break;
case 3: case 3:
// DG: these are now used for the new default behavior of "update every async tic (every 16ms)"
soundSystem->AsyncUpdateWrite( stat->milliseconds ); soundSystem->AsyncUpdateWrite( stat->milliseconds );
break; break;
case 2:
// DG: use 2 for the old "update only 10x/second" behavior in case anyone likes that..
soundSystem->AsyncUpdate( stat->milliseconds );
break;
} }
// we update com_ticNumber after all the background tasks // we update com_ticNumber after all the background tasks
@ -2754,6 +2753,9 @@ static unsigned int AsyncTimer(unsigned int interval, void *) {
// calculate the next interval to get as close to 60fps as possible // calculate the next interval to get as close to 60fps as possible
unsigned int now = SDL_GetTicks(); unsigned int now = SDL_GetTicks();
unsigned int tick = com_ticNumber * USERCMD_MSEC; unsigned int tick = com_ticNumber * USERCMD_MSEC;
// FIXME: this is pretty broken and basically always returns 1 because now now is much bigger than tic
// (probably com_tickNumber only starts incrementing a second after engine starts?)
// only reason this works is common->Async() checking again before calling SingleAsyncTic()
if (now >= tick) if (now >= tick)
return 1; return 1;

View file

@ -2524,7 +2524,7 @@ extern bool CheckOpenALDeviceAndRecoverIfNeeded();
void idSessionLocal::Frame() { void idSessionLocal::Frame() {
if ( com_asyncSound.GetInteger() == 0 ) { if ( com_asyncSound.GetInteger() == 0 ) {
soundSystem->AsyncUpdate( Sys_Milliseconds() ); soundSystem->AsyncUpdateWrite( Sys_Milliseconds() );
} }
// DG: periodically check if sound device is still there and try to reset it if not // DG: periodically check if sound device is still there and try to reset it if not

View file

@ -695,7 +695,10 @@ int idSoundSystemLocal::AsyncMix( int soundTime, float *mixBuffer ) {
/* /*
=================== ===================
idSoundSystemLocal::AsyncUpdate idSoundSystemLocal::AsyncUpdate
called from async sound thread when com_asyncSound == 1 ( Windows ) called from async sound thread when com_asyncSound == 2
DG: using this for the "traditional" sound updates that
only happen about every 100ms (and lead to delays between 1 and 110ms between
starting a sound in gamecode and it being played), for people who like that..
=================== ===================
*/ */
int idSoundSystemLocal::AsyncUpdate( int inTime ) { int idSoundSystemLocal::AsyncUpdate( int inTime ) {
@ -772,9 +775,12 @@ int idSoundSystemLocal::AsyncUpdate( int inTime ) {
/* /*
=================== ===================
idSoundSystemLocal::AsyncUpdateWrite idSoundSystemLocal::AsyncUpdateWrite
sound output using a write API. all the scheduling based on time DG: using this now for 60Hz sound updates
we mix MIXBUFFER_SAMPLES at a time, but we feed the audio device with smaller chunks (and more often) called from async sound thread when com_asyncSound is 3 or 1
called by the sound thread when com_asyncSound is 3 ( Linux ) also called from main thread if com_asyncSound == 0
(those were the default values used in dhewm3 on unix-likes (except mac) or rest)
with this, once every async tic new sounds are started and existing ones updated,
instead of once every ~100ms.
=================== ===================
*/ */
int idSoundSystemLocal::AsyncUpdateWrite( int inTime ) { int idSoundSystemLocal::AsyncUpdateWrite( int inTime ) {
@ -783,21 +789,7 @@ int idSoundSystemLocal::AsyncUpdateWrite( int inTime ) {
return 0; return 0;
} }
unsigned int dwCurrentBlock = (unsigned int)( inTime * 44.1f / MIXBUFFER_SAMPLES ); int sampleTime = inTime * 44.1f;
if ( nextWriteBlock == 0xffffffff ) {
nextWriteBlock = dwCurrentBlock;
}
if ( dwCurrentBlock < nextWriteBlock ) {
return 0;
}
if ( nextWriteBlock != dwCurrentBlock ) {
Sys_Printf( "missed %d sound updates\n", dwCurrentBlock - nextWriteBlock );
}
int sampleTime = dwCurrentBlock * MIXBUFFER_SAMPLES;
int numSpeakers = s_numberOfSpeakers.GetInteger(); int numSpeakers = s_numberOfSpeakers.GetInteger();
// enable audio hardware caching // enable audio hardware caching
@ -811,8 +803,6 @@ int idSoundSystemLocal::AsyncUpdateWrite( int inTime ) {
// disable audio hardware caching (this updates ALL settings since last alcSuspendContext) // disable audio hardware caching (this updates ALL settings since last alcSuspendContext)
alcProcessContext( openalContext ); alcProcessContext( openalContext );
// only move to the next block if the write was successful
nextWriteBlock = dwCurrentBlock + 1;
CurrentSoundTime = sampleTime; CurrentSoundTime = sampleTime;
return Sys_Milliseconds() - inTime; return Sys_Milliseconds() - inTime;

View file

@ -1857,7 +1857,11 @@ void idSoundWorldLocal::AddChannelContribution( idSoundEmitterLocal *sound, idSo
chan->triggered = false; chan->triggered = false;
} }
} }
} else { }
#if 1 // DG: I /think/ this was only relevant for the old sound backends?
// FIXME: completely remove else branch, but for testing leave it in under com_asyncSound 2
// (which also does the old 92-100ms updates)
else if( com_asyncSound.GetInteger() == 2 ) {
if ( slowmoActive && !chan->disallowSlow ) { if ( slowmoActive && !chan->disallowSlow ) {
idSlowChannel slow = sound->GetSlowChannel( chan ); idSlowChannel slow = sound->GetSlowChannel( chan );
@ -1952,6 +1956,7 @@ void idSoundWorldLocal::AddChannelContribution( idSoundEmitterLocal *sound, idSo
} }
} }
#endif // 1/0
soundSystemLocal.soundStats.activeSounds++; soundSystemLocal.soundStats.activeSounds++;