SDL: audio: precalculate if we can use simple copies instead of the full mixer.

From 23f60203a372e2671425f7de6c0a827fbd05545a Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Wed, 20 Sep 2023 09:58:27 -0400
Subject: [PATCH] audio: precalculate if we can use simple copies instead of
 the full mixer.

This just saves a bunch of conditionals (and an atomic get!) per iteration
of the audio thread.
---
 src/audio/SDL_audio.c    | 43 ++++++++++++++++++++++++++++++----------
 src/audio/SDL_sysaudio.h |  3 +++
 2 files changed, 35 insertions(+), 11 deletions(-)

diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c
index c9a636c3ce70..fdf5eece0b99 100644
--- a/src/audio/SDL_audio.c
+++ b/src/audio/SDL_audio.c
@@ -137,6 +137,20 @@ static int GetDefaultSampleFramesFromFreq(const int freq)
     }
 }
 
+// device should be locked when calling this.
+static SDL_bool AudioDeviceCanUseSimpleCopy(SDL_AudioDevice *device)
+{
+    SDL_assert(device != NULL);
+    return (
+        device->logical_devices &&  // there's a logical device
+        !device->logical_devices->next &&  // there's only _ONE_ logical device
+        !device->logical_devices->postmix && // there isn't a postmix callback
+        !SDL_AtomicGet(&device->logical_devices->paused) &&  // it isn't paused
+        device->logical_devices->bound_streams &&  // there's a bound stream
+        !device->logical_devices->bound_streams->next_binding  // there's only _ONE_ bound stream.
+    ) ? SDL_TRUE : SDL_FALSE;
+}
+
 
 // device management and hotplug...
 
@@ -198,6 +212,8 @@ static void DestroyLogicalAudioDevice(SDL_LogicalAudioDevice *logdev)
         SDL_UnlockMutex(stream->lock);
     }
 
+    logdev->physical_device->simple_copy = AudioDeviceCanUseSimpleCopy(logdev->physical_device);
+
     SDL_free(logdev);
 }
 
@@ -752,19 +768,12 @@ SDL_bool SDL_OutputAudioThreadIterate(SDL_AudioDevice *device)
         retval = SDL_FALSE;
     } else {
         SDL_assert(buffer_size <= device->buffer_size);  // you can ask for less, but not more.
+        SDL_assert(AudioDeviceCanUseSimpleCopy(device) == device->simple_copy);  // make sure this hasn't gotten out of sync.
 
         // can we do a basic copy without silencing/mixing the buffer? This is an extremely likely scenario, so we special-case it.
-        const SDL_bool simple_copy = device->logical_devices &&   // there's a logical device
-                                     !device->logical_devices->next &&  // there's only _ONE_ logical device
-                                     !device->logical_devices->postmix &&  // there isn't a postmix callback
-                                     !SDL_AtomicGet(&device->logical_devices->paused) &&   // it isn't paused
-                                     device->logical_devices->bound_streams &&  // there's a bound stream
-                                     !device->logical_devices->bound_streams->next_binding;  // there's only _ONE_ bound stream.
-
-        if (simple_copy) {
+        if (device->simple_copy) {
             SDL_LogicalAudioDevice *logdev = device->logical_devices;
             SDL_AudioStream *stream = logdev->bound_streams;
-
             const int br = GetAudioStreamDataInFormat(stream, device_buffer, buffer_size, &device->spec);
             if (br < 0) {  // Probably OOM. Kill the audio device; the whole thing is likely dying soon anyhow.
                 retval = SDL_FALSE;
@@ -1423,6 +1432,7 @@ SDL_AudioDeviceID SDL_OpenAudioDevice(SDL_AudioDeviceID devid, const SDL_AudioSp
                 device->logical_devices->prev = logdev;
             }
             device->logical_devices = logdev;
+            device->simple_copy = AudioDeviceCanUseSimpleCopy(device);
         }
         SDL_UnlockMutex(device->lock);
     }
@@ -1437,6 +1447,7 @@ static int SetLogicalAudioDevicePauseState(SDL_AudioDeviceID devid, int value)
         return -1;  // ObtainLogicalAudioDevice will have set an error.
     }
     SDL_AtomicSet(&logdev->paused, value);
+    logdev->physical_device->simple_copy = AudioDeviceCanUseSimpleCopy(logdev->physical_device);
     SDL_UnlockMutex(logdev->physical_device->lock);
     return 0;
 }
@@ -1482,6 +1493,8 @@ int SDL_SetAudioPostmixCallback(SDL_AudioDeviceID devid, SDL_AudioPostmixCallbac
             logdev->postmix_userdata = userdata;
         }
 
+        device->simple_copy = AudioDeviceCanUseSimpleCopy(device);
+
         SDL_UnlockMutex(device->lock);
     }
     return retval;
@@ -1565,6 +1578,8 @@ int SDL_BindAudioStreams(SDL_AudioDeviceID devid, SDL_AudioStream **streams, int
         }
     }
 
+    device->simple_copy = AudioDeviceCanUseSimpleCopy(device);
+
     SDL_UnlockMutex(device->lock);
 
     return retval;
@@ -1635,6 +1650,7 @@ void SDL_UnbindAudioStreams(SDL_AudioStream **streams, int num_streams)
             stream->bound_device = NULL;
             SDL_UnlockMutex(stream->lock);
             if (logdev) {
+                logdev->physical_device->simple_copy = AudioDeviceCanUseSimpleCopy(logdev->physical_device);
                 SDL_UnlockMutex(logdev->physical_device->lock);
             }
         }
@@ -1674,10 +1690,12 @@ SDL_AudioStream *SDL_OpenAudioDeviceStream(SDL_AudioDeviceID devid, const SDL_Au
 
     SDL_AudioDevice *physdevice = logdev->physical_device;
     SDL_assert(physdevice != NULL);
-    SDL_UnlockMutex(physdevice->lock);  // we don't need to hold the lock for any of this.
-    const SDL_bool iscapture = physdevice->iscapture;
 
     SDL_AtomicSet(&logdev->paused, 1);   // start the device paused, to match SDL2.
+    physdevice->simple_copy = AudioDeviceCanUseSimpleCopy(physdevice);
+
+    SDL_UnlockMutex(physdevice->lock);  // we don't need to hold the lock for any of this.
+    const SDL_bool iscapture = physdevice->iscapture;
 
     SDL_AudioStream *stream = NULL;
     if (iscapture) {
@@ -1831,6 +1849,9 @@ void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device)
                 new_default_device->logical_devices = logdev;
             }
 
+            current_default_device->simple_copy = AudioDeviceCanUseSimpleCopy(current_default_device);
+            new_default_device->simple_copy = AudioDeviceCanUseSimpleCopy(new_default_device);
+
             if (current_default_device->logical_devices == NULL) {   // nothing left on the current physical device, close it.
                 // !!! FIXME: we _need_ to release this lock, but doing so can cause a race condition if someone opens a device while we're closing it.
                 SDL_UnlockMutex(current_default_device->lock);  // can't hold the lock or the audio thread will deadlock while we WaitThread it.
diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h
index 52236dfd0584..3c859d83cdf1 100644
--- a/src/audio/SDL_sysaudio.h
+++ b/src/audio/SDL_sysaudio.h
@@ -267,6 +267,9 @@ struct SDL_AudioDevice
     // SDL_TRUE if this is a capture device instead of an output device
     SDL_bool iscapture;
 
+    // SDL_TRUE if audio thread can skip silence/mix/convert stages and just do a basic memcpy.
+    SDL_bool simple_copy;
+
     // Scratch buffers used for mixing.
     Uint8 *work_buffer;
     Uint8 *mix_buffer;