mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-07 08:36:02 -10:00
s16_to_float: run-time AVX2 and AVX-512 conversion
convert_s16_to_float widens every s16 core's audio before the resampler, on every flush. It gains AVX2 and AVX-512 forms of the SSE2 loop, picked by convert_s16_to_float_init_simd() from cpu_features_get(), built per function so older CPUs keep SSE2 and compilers without per-function targets (before GCC 5, Clang 4, MSVC 2017 15.3) leave them out. Each sample is an exact sign-extend and convert, then one multiply by gain / 32768: the same single rounding as the SSE2 and scalar paths, so every target gives the same float for the same sample. AVX2 takes 16 samples a step; AVX-512 masks its last step, so it has no scalar tail. One 534-frame stereo batch (1068 samples) on an AVX-512 Xeon: SSE2 0.14 us, AVX2 0.07-0.09 us, AVX-512 0.08-0.10 us. New libretro-common/samples/audio/s16_to_float pins each tier the CPU has and compares it bit for bit with the scalar rule: every s16 value at six gains, then every length up to 100 at odd offsets, with a guard word after each buffer.
This commit is contained in:
1 parent
5811daa1e5
commit
7b23ab8aa4
4 files changed
+246
-2
No files matched your search
@@ -103,6 +103,7 @@ jobs:
|
||||
# failure. These are built AND executed.
|
||||
declare -a RUN_TARGETS=(
|
||||
rslc_test
|
||||
s16_to_float_test
|
||||
float_to_s16_test
|
||||
float_to_s32_test
|
||||
compat_fnmatch_test
|
||||
|
||||
@@ -94,7 +94,89 @@ void convert_s16_to_float_init_simd(void)
|
||||
s16_to_float_neon_enabled = true;
|
||||
}
|
||||
#else
|
||||
void convert_s16_to_float(float *s,
|
||||
/* AVX2 and AVX-512 forms of the SSE2 loop below, built per function and
|
||||
* chosen by convert_s16_to_float_init_simd(), so older CPUs keep SSE2
|
||||
* and compilers without per-function targets leave them out. Each
|
||||
* sample is one exact widen and one multiply by gain / 32768 - the same
|
||||
* single rounding as the SSE2 and scalar paths, so every target gives
|
||||
* the same float for the same s16. */
|
||||
#if defined(__SSE2__) && (defined(__x86_64__) || defined(__i386__) \
|
||||
|| defined(_M_X64) || defined(_M_IX86))
|
||||
#if defined(_MSC_VER) && !defined(__clang__)
|
||||
#if _MSC_VER >= 1911
|
||||
#define S16_TO_FLOAT_HAVE_WIDE 1
|
||||
#define S16_TO_FLOAT_TARGET_AVX2
|
||||
#define S16_TO_FLOAT_TARGET_AVX512
|
||||
#endif
|
||||
#elif (defined(__clang__) && __clang_major__ >= 4) \
|
||||
|| (defined(__GNUC__) && !defined(__clang__) && __GNUC__ >= 5)
|
||||
#define S16_TO_FLOAT_HAVE_WIDE 1
|
||||
#define S16_TO_FLOAT_TARGET_AVX2 __attribute__((target("avx2")))
|
||||
#define S16_TO_FLOAT_TARGET_AVX512 \
|
||||
__attribute__((target("avx512f,avx512bw,avx512vl")))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef S16_TO_FLOAT_HAVE_WIDE
|
||||
#include <immintrin.h>
|
||||
|
||||
/* 0 = SSE2, 1 = AVX2, 2 = AVX-512; set once by init, before audio runs. */
|
||||
static int s16_to_float_tier = 0;
|
||||
|
||||
#ifdef S16_TO_FLOAT_TEST_HOOKS
|
||||
/* -1 leaves the tier alone; otherwise caps it for a test. */
|
||||
int s16_to_float_tier_cap = -1;
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER) && !defined(__clang__)
|
||||
#define S16_TO_FLOAT_ZEROUPPER() _mm256_zeroupper()
|
||||
#else
|
||||
#define S16_TO_FLOAT_ZEROUPPER()
|
||||
#endif
|
||||
|
||||
/* Sixteen samples a step; returns how many it converted, leaving the
|
||||
* rest (under 16) to the SSE2 loop and the scalar tail. */
|
||||
S16_TO_FLOAT_TARGET_AVX2
|
||||
static size_t s16_to_float_avx2(float *s, const int16_t *in, size_t len,
|
||||
float gain)
|
||||
{
|
||||
size_t i;
|
||||
const __m256 factor = _mm256_set1_ps(gain / 0x8000);
|
||||
for (i = 0; i + 16 <= len; i += 16)
|
||||
{
|
||||
_mm256_storeu_ps(s + i, _mm256_mul_ps(_mm256_cvtepi32_ps(
|
||||
_mm256_cvtepi16_epi32(_mm_loadu_si128(
|
||||
(const __m128i*)(in + i)))), factor));
|
||||
_mm256_storeu_ps(s + i + 8, _mm256_mul_ps(_mm256_cvtepi32_ps(
|
||||
_mm256_cvtepi16_epi32(_mm_loadu_si128(
|
||||
(const __m128i*)(in + i + 8)))), factor));
|
||||
}
|
||||
S16_TO_FLOAT_ZEROUPPER();
|
||||
return i;
|
||||
}
|
||||
|
||||
/* Sixteen samples a step, the last masked, so no scalar tail. */
|
||||
S16_TO_FLOAT_TARGET_AVX512
|
||||
static void s16_to_float_avx512(float *s, const int16_t *in, size_t len,
|
||||
float gain)
|
||||
{
|
||||
size_t i;
|
||||
const __m512 factor = _mm512_set1_ps(gain / 0x8000);
|
||||
for (i = 0; i < len; i += 16)
|
||||
{
|
||||
size_t left = len - i;
|
||||
__mmask16 m = (left >= 16) ? (__mmask16)0xffff
|
||||
: (__mmask16)((1u << left) - 1u);
|
||||
_mm512_mask_storeu_ps(s + i, m, _mm512_mul_ps(_mm512_cvtepi32_ps(
|
||||
_mm512_cvtepi16_epi32(_mm256_maskz_loadu_epi16(m,
|
||||
in + i))), factor));
|
||||
}
|
||||
S16_TO_FLOAT_ZEROUPPER();
|
||||
}
|
||||
#endif
|
||||
|
||||
/* The SSE2 / AltiVec / Allegrex loop and the scalar tail. */
|
||||
static void convert_s16_to_float_base(float *s,
|
||||
const int16_t *in, size_t len, float gain)
|
||||
{
|
||||
unsigned i = 0;
|
||||
@@ -204,6 +286,41 @@ void convert_s16_to_float(float *s,
|
||||
s[i] = (float)in[i] * gain;
|
||||
}
|
||||
|
||||
void convert_s16_to_float_init_simd(void) { }
|
||||
void convert_s16_to_float(float *s,
|
||||
const int16_t *in, size_t len, float gain)
|
||||
{
|
||||
#ifdef S16_TO_FLOAT_HAVE_WIDE
|
||||
int tier = s16_to_float_tier;
|
||||
#ifdef S16_TO_FLOAT_TEST_HOOKS
|
||||
if (s16_to_float_tier_cap >= 0 && tier > s16_to_float_tier_cap)
|
||||
tier = s16_to_float_tier_cap;
|
||||
#endif
|
||||
if (tier == 2)
|
||||
{
|
||||
s16_to_float_avx512(s, in, len, gain);
|
||||
return;
|
||||
}
|
||||
if (tier == 1)
|
||||
{
|
||||
size_t done = s16_to_float_avx2(s, in, len, gain);
|
||||
s += done;
|
||||
in += done;
|
||||
len -= done;
|
||||
}
|
||||
#endif
|
||||
convert_s16_to_float_base(s, in, len, gain);
|
||||
}
|
||||
|
||||
void convert_s16_to_float_init_simd(void)
|
||||
{
|
||||
#ifdef S16_TO_FLOAT_HAVE_WIDE
|
||||
uint64_t cpu = cpu_features_get();
|
||||
s16_to_float_tier = 0;
|
||||
if (cpu & RETRO_SIMD_AVX2)
|
||||
s16_to_float_tier = 1;
|
||||
if (cpu & RETRO_SIMD_AVX512)
|
||||
s16_to_float_tier = 2;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
include ../../sample_objdir.mk
|
||||
|
||||
TARGET := s16_to_float_test
|
||||
|
||||
LIBRETRO_COMM_DIR := ../../..
|
||||
|
||||
COMMON_SOURCES := \
|
||||
$(LIBRETRO_COMM_DIR)/audio/conversion/s16_to_float.c \
|
||||
$(LIBRETRO_COMM_DIR)/features/features_cpu.c \
|
||||
$(LIBRETRO_COMM_DIR)/compat/compat_strl.c
|
||||
|
||||
COMMON_OBJS := $(call sample_objs,$(COMMON_SOURCES:.c=.o))
|
||||
OBJS := s16_to_float_test.o $(COMMON_OBJS)
|
||||
|
||||
CFLAGS += -DS16_TO_FLOAT_TEST_HOOKS -Wall -pedantic -std=gnu99 -g1 -O2 -I$(LIBRETRO_COMM_DIR)/include
|
||||
LDFLAGS += -lm
|
||||
|
||||
ifneq ($(SANITIZER),)
|
||||
CFLAGS := -fsanitize=$(SANITIZER) -fno-omit-frame-pointer $(CFLAGS)
|
||||
LDFLAGS := -fsanitize=$(SANITIZER) $(LDFLAGS)
|
||||
endif
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
%.o: %.c
|
||||
$(CC) -c -o $@ $< $(CFLAGS)
|
||||
|
||||
$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
|
||||
@mkdir -p $(@D)
|
||||
$(CC) -c -o $@ $< $(CFLAGS)
|
||||
|
||||
$(TARGET): $(OBJS)
|
||||
$(CC) -o $@ $^ $(LDFLAGS)
|
||||
|
||||
clean:
|
||||
rm -rf $(SAMPLE_OBJDIR)
|
||||
rm -f $(TARGET) $(OBJS)
|
||||
|
||||
.PHONY: clean
|
||||
@@ -0,0 +1,87 @@
|
||||
/* convert_s16_to_float against the scalar rule, (float)x * (gain /
|
||||
* 32768), bit for bit: every s16 value at several gains, then every
|
||||
* length up to 100 at odd offsets so each vector body, the AVX-512
|
||||
* masked tail and the scalar remainder run, with a guard word after
|
||||
* each buffer. Every tier the CPU has (SSE2, AVX2, AVX-512) is pinned
|
||||
* in turn; all must give the same float for the same sample. */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <audio/conversion/s16_to_float.h>
|
||||
|
||||
#ifdef S16_TO_FLOAT_TEST_HOOKS
|
||||
extern int s16_to_float_tier_cap;
|
||||
#endif
|
||||
|
||||
static unsigned failures;
|
||||
|
||||
static void check(const int16_t *in, size_t n, float gain,
|
||||
const char *what, int tier)
|
||||
{
|
||||
float *out = (float*)malloc((n + 1) * sizeof(*out));
|
||||
float g = gain / 0x8000;
|
||||
uint32_t guard = 0x5A5A5A5Au;
|
||||
size_t i;
|
||||
unsigned bad = 0;
|
||||
memcpy(&out[n], &guard, sizeof(guard));
|
||||
convert_s16_to_float(out, in, n, gain);
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
float want = (float)in[i] * g;
|
||||
if (memcmp(&out[i], &want, sizeof(want)) && bad++ < 4)
|
||||
printf("FAIL tier %d %s gain %g n=%u [%u]: in %d got %.9g want %.9g\n",
|
||||
tier, what, gain, (unsigned)n, (unsigned)i, in[i],
|
||||
out[i], want);
|
||||
}
|
||||
if (memcmp(&out[n], &guard, sizeof(guard)))
|
||||
{
|
||||
printf("FAIL tier %d %s n=%u: wrote past the end\n", tier, what,
|
||||
(unsigned)n);
|
||||
bad++;
|
||||
}
|
||||
failures += bad ? 1 : 0;
|
||||
free(out);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
static const float gains[] = { 1.0f, 0.5f, 0.0f, 1.37f, 3.98107f,
|
||||
0.000123f };
|
||||
int16_t *in = (int16_t*)malloc(65536 * sizeof(int16_t));
|
||||
size_t i, n, g;
|
||||
int tier;
|
||||
|
||||
for (i = 0; i < 65536; i++)
|
||||
in[i] = (int16_t)(uint16_t)(i * 40503u);
|
||||
|
||||
convert_s16_to_float_init_simd();
|
||||
|
||||
for (tier = 0; tier <= 2; tier++)
|
||||
{
|
||||
#ifdef S16_TO_FLOAT_TEST_HOOKS
|
||||
s16_to_float_tier_cap = tier;
|
||||
#else
|
||||
if (tier)
|
||||
break;
|
||||
#endif
|
||||
for (g = 0; g < sizeof(gains) / sizeof(gains[0]); g++)
|
||||
check(in, 65536, gains[g], "every value", tier);
|
||||
for (n = 0; n <= 100; n++)
|
||||
{
|
||||
check(in, n, 1.0f, "length", tier);
|
||||
check(in + 1 + (n % 7), n, 0.75f, "offset", tier);
|
||||
}
|
||||
}
|
||||
|
||||
free(in);
|
||||
if (failures)
|
||||
{
|
||||
printf("s16_to_float_test: %u failing cases\n", failures);
|
||||
return 1;
|
||||
}
|
||||
printf("s16_to_float_test: every tier matches the scalar rule\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user