From 5b696996cdd94be95ccfe63b8693e0134fb2d571 Mon Sep 17 00:00:00 2001
From: Brick <[EMAIL REDACTED]>
Date: Thu, 24 Aug 2023 19:00:17 +0100
Subject: [PATCH] Added ResampleFrame_SSE
---
src/audio/SDL_audio.c | 1 +
src/audio/SDL_audiocvt.c | 141 +++++++++++++++++++++++++++++------
src/audio/SDL_audiotypecvt.c | 5 --
src/audio/SDL_sysaudio.h | 1 +
4 files changed, 119 insertions(+), 29 deletions(-)
diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c
index c4162738623a..b48d2b97dcde 100644
--- a/src/audio/SDL_audio.c
+++ b/src/audio/SDL_audio.c
@@ -493,6 +493,7 @@ int SDL_InitAudio(const char *driver_name)
}
SDL_ChooseAudioConverters();
+ SDL_SetupAudioResampler();
SDL_RWLock *device_list_lock = SDL_CreateRWLock(); // create this early, so if it fails we don't have to tear down the whole audio subsystem.
if (!device_list_lock) {
diff --git a/src/audio/SDL_audiocvt.c b/src/audio/SDL_audiocvt.c
index b3163665e29a..3a78c349c1b9 100644
--- a/src/audio/SDL_audiocvt.c
+++ b/src/audio/SDL_audiocvt.c
@@ -84,10 +84,9 @@ static int GetHistoryBufferSampleFrames(const int required_resampler_frames)
#define RESAMPLER_SAMPLES_PER_FRAME (RESAMPLER_ZERO_CROSSINGS * 2)
-#define RESAMPLER_FULL_FILTER_SIZE RESAMPLER_SAMPLES_PER_FRAME * (RESAMPLER_SAMPLES_PER_ZERO_CROSSING + 1)
+#define RESAMPLER_FULL_FILTER_SIZE (RESAMPLER_SAMPLES_PER_FRAME * (RESAMPLER_SAMPLES_PER_ZERO_CROSSING + 1))
-// TODO: Add SIMD-accelerated versions
-static void ResampleFrame(const float* src, float* dst, const float* raw_filter, const float interp, const int chans)
+static void ResampleFrame_Scalar(const float* src, float* dst, const float* raw_filter, const float interp, const int chans)
{
int i, chan;
@@ -124,33 +123,122 @@ static void ResampleFrame(const float* src, float* dst, const float* raw_filter,
return;
}
- // Try and give the compiler a hint about how many channels there are
- if (chans < 1 || chans > 8) {
- SDL_assert(!"Invalid channel count");
+ for (chan = 0; chan < chans; chan++) {
+ float f = 0.0f;
+
+ for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; i++) {
+ f += src[i * chans + chan] * filter[i];
+ }
+
+ dst[chan] = f;
+ }
+}
+
+#ifdef SDL_SSE_INTRINSICS
+static void SDL_TARGETING("sse") ResampleFrame_SSE(const float* src, float* dst, const float* raw_filter, const float interp, const int chans)
+{
+#if RESAMPLER_SAMPLES_PER_FRAME != 10
+#error Invalid samples per frame
+#endif
+
+ // Load the filter
+ __m128 f0 = _mm_loadu_ps(raw_filter + 0);
+ __m128 f1 = _mm_loadu_ps(raw_filter + 4);
+ __m128 f2 = _mm_loadl_pi(_mm_setzero_ps(), (const __m64*)(raw_filter + 8));
+
+ __m128 g0 = _mm_loadu_ps(raw_filter + 10);
+ __m128 g1 = _mm_loadu_ps(raw_filter + 14);
+ __m128 g2 = _mm_loadl_pi(_mm_setzero_ps(), (const __m64*)(raw_filter + 18));
+
+ __m128 interp1 = _mm_set1_ps(interp);
+ __m128 interp2 = _mm_sub_ps(_mm_set1_ps(1.0f), _mm_set1_ps(interp));
+
+ // Linear interpolate the filter
+ f0 = _mm_add_ps(_mm_mul_ps(f0, interp2), _mm_mul_ps(g0, interp1));
+ f1 = _mm_add_ps(_mm_mul_ps(f1, interp2), _mm_mul_ps(g1, interp1));
+ f2 = _mm_add_ps(_mm_mul_ps(f2, interp2), _mm_mul_ps(g2, interp1));
+
+ if (chans == 2) {
+ // Duplicate each of the filter elements
+ g0 = _mm_shuffle_ps(f0, f0, _MM_SHUFFLE(3, 3, 2, 2));
+ f0 = _mm_shuffle_ps(f0, f0, _MM_SHUFFLE(1, 1, 0, 0));
+ g1 = _mm_shuffle_ps(f1, f1, _MM_SHUFFLE(3, 3, 2, 2));
+ f1 = _mm_shuffle_ps(f1, f1, _MM_SHUFFLE(1, 1, 0, 0));
+ f2 = _mm_shuffle_ps(f2, f2, _MM_SHUFFLE(1, 1, 0, 0));
+
+ // Multiply the filter by the input
+ f0 = _mm_mul_ps(f0, _mm_loadu_ps(src + 0));
+ g0 = _mm_mul_ps(g0, _mm_loadu_ps(src + 4));
+ f1 = _mm_mul_ps(f1, _mm_loadu_ps(src + 8));
+ g1 = _mm_mul_ps(g1, _mm_loadu_ps(src + 12));
+ f2 = _mm_mul_ps(f2, _mm_loadu_ps(src + 16));
+
+ // Calculate the sum
+ f0 = _mm_add_ps(_mm_add_ps(_mm_add_ps(f0, g0), _mm_add_ps(f1, g1)), f2);
+ f0 = _mm_add_ps(f0, _mm_movehl_ps(f0, f0));
+
+ // Store the result
+ _mm_storel_pi((__m64*) dst, f0);
return;
}
- // Calculate the result in-place
- for (chan = 0; chan < chans; ++chan) {
- dst[chan] = 0.0f;
+ if (chans == 1) {
+ // Multiply the filter by the input
+ f0 = _mm_mul_ps(f0, _mm_loadu_ps(src + 0));
+ f1 = _mm_mul_ps(f1, _mm_loadu_ps(src + 4));
+ f2 = _mm_mul_ps(f2, _mm_loadl_pi(_mm_setzero_ps(), (const __m64*)(src + 8)));
+
+ // Calculate the sum
+ f0 = _mm_add_ps(f0, f1);
+ f0 = _mm_add_ps(_mm_add_ps(f0, f2), _mm_movehl_ps(f0, f0));
+ f0 = _mm_add_ss(f0, _mm_shuffle_ps(f0, f0, _MM_SHUFFLE(1, 1, 1, 1)));
+
+ // Store the result
+ _mm_store_ss(dst, f0);
+ return;
}
- for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; i++) {
- const float* inputs = &src[i * chans];
- const float scale = filter[i];
+ float filter[RESAMPLER_SAMPLES_PER_FRAME];
+ _mm_storeu_ps(filter + 0, f0);
+ _mm_storeu_ps(filter + 4, f1);
+ _mm_storel_pi((__m64*)(filter + 8), f2);
+
+ int i, chan = 0;
- for (chan = 0; chan < chans; chan++) {
- dst[chan] += inputs[chan] * scale;
+ for (; chan + 4 <= chans; chan++) {
+ f0 = _mm_setzero_ps();
+
+ for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; i++) {
+ f0 = _mm_add_ps(f0, _mm_mul_ps(_mm_loadu_ps(&src[i * chans + chan]), _mm_load1_ps(&filter[i])));
}
+
+ _mm_storeu_ps(&dst[chan], f0);
+ }
+
+ for (; chan < chans; chan++) {
+ f0 = _mm_setzero_ps();
+
+ for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; i++) {
+ f0 = _mm_add_ss(f0, _mm_mul_ss(_mm_load_ss(&src[i * chans + chan]), _mm_load_ss(&filter[i])));
+ }
+
+ _mm_store_ss(&dst[chan], f0);
}
}
+#endif
+
+static void (*ResampleFrame)(const float* src, float* dst, const float* raw_filter, const float interp, const int chans);
static float FullResamplerFilter[RESAMPLER_FULL_FILTER_SIZE];
void SDL_SetupAudioResampler()
{
- // Build a table combining the left and right wings, for faster access
+ static SDL_bool setup = SDL_FALSE;
+ if (setup) {
+ return;
+ }
+ // Build a table combining the left and right wings, for faster access
int i, j;
for (i = 0; i < RESAMPLER_SAMPLES_PER_ZERO_CROSSING; ++i) {
@@ -171,6 +259,16 @@ void SDL_SetupAudioResampler()
FullResamplerFilter[lwing] = 0.0f;
FullResamplerFilter[rwing] = 0.0f;
}
+
+ ResampleFrame = ResampleFrame_Scalar;
+
+#ifdef SDL_SSE_INTRINSICS
+ if (SDL_HasSSE()) {
+ ResampleFrame = ResampleFrame_SSE;
+ }
+#endif
+
+ setup = SDL_TRUE;
}
static void ResampleAudio(const int chans, const float *inbuf, const int inframes, float *outbuf, const int outframes,
@@ -651,6 +749,7 @@ SDL_AudioStream *SDL_CreateAudioStream(const SDL_AudioSpec *src_spec, const SDL_
// Make sure we've chosen audio conversion functions (SIMD, scalar, etc.)
SDL_ChooseAudioConverters(); // !!! FIXME: let's do this during SDL_Init
+ SDL_SetupAudioResampler();
retval->packetlen = packetlen;
SDL_memcpy(&retval->src_spec, src_spec, sizeof (SDL_AudioSpec));
@@ -825,17 +924,12 @@ static Uint8 *EnsureStreamWorkBufferSize(SDL_AudioStream *stream, size_t newlen)
static int CalculateAudioStreamWorkBufSize(const SDL_AudioStream *stream, int input_frames, int output_frames)
{
- int workbuflen = SDL_max(input_frames, output_frames) * stream->max_sample_frame_size;
+ int workbuf_frames = input_frames + (stream->resampler_padding_frames * 2);
+ int workbuflen = workbuf_frames * stream->max_sample_frame_size;
if (stream->resample_rate) {
- int resample_frame_size = stream->pre_resample_channels * sizeof(float);
-
- // Calculate space needed to move to format/channels used for resampling stage.
- int inputlen = (input_frames + (stream->resampler_padding_frames * 2)) * resample_frame_size;
-
- workbuflen = SDL_max(workbuflen, inputlen);
-
// Calculate space needed after resample (which lives in a second copy in the same buffer).
+ int resample_frame_size = stream->pre_resample_channels * sizeof(float);
workbuflen += output_frames * resample_frame_size;
}
@@ -888,7 +982,6 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int le
input_frames = GetResamplerNeededInputFrames(output_frames, resample_rate, stream->resample_offset);
}
- // !!! FIXME: this could be less aggressive about allocation, if we decide the necessary size at each stage and select the maximum required.
int work_buffer_capacity = CalculateAudioStreamWorkBufSize(stream, input_frames, output_frames);
Uint8* work_buffer = EnsureStreamWorkBufferSize(stream, work_buffer_capacity);
diff --git a/src/audio/SDL_audiotypecvt.c b/src/audio/SDL_audiotypecvt.c
index cbf19a998099..416e9ab914cc 100644
--- a/src/audio/SDL_audiotypecvt.c
+++ b/src/audio/SDL_audiotypecvt.c
@@ -962,8 +962,6 @@ void (*SDL_Convert_F32_to_U8)(Uint8 *dst, const float *src, int num_samples) = N
void (*SDL_Convert_F32_to_S16)(Sint16 *dst, const float *src, int num_samples) = NULL;
void (*SDL_Convert_F32_to_S32)(Sint32 *dst, const float *src, int num_samples) = NULL;
-extern void SDL_SetupAudioResampler(void);
-
void SDL_ChooseAudioConverters(void)
{
static SDL_bool converters_chosen = SDL_FALSE;
@@ -971,9 +969,6 @@ void SDL_ChooseAudioConverters(void)
return;
}
- // FIXME: Hacks on top of hacks.
- SDL_SetupAudioResampler();
-
#define SET_CONVERTER_FUNCS(fntype) \
SDL_Convert_S8_to_F32 = SDL_Convert_S8_to_F32_##fntype; \
SDL_Convert_U8_to_F32 = SDL_Convert_U8_to_F32_##fntype; \
diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h
index 9b2fda886019..3861aad9aa35 100644
--- a/src/audio/SDL_sysaudio.h
+++ b/src/audio/SDL_sysaudio.h
@@ -72,6 +72,7 @@ const SDL_AudioFormat *SDL_ClosestAudioFormats(SDL_AudioFormat format);
// Must be called at least once before using converters (SDL_CreateAudioStream will call it !!! FIXME but probably shouldn't).
extern void SDL_ChooseAudioConverters(void);
+extern void SDL_SetupAudioResampler(void);
/* Backends should call this as devices are added to the system (such as
a USB headset being plugged in), and should also be called for