SDL: Limit CPU features through a hint

From 1d0e5286aa2132e304715a5351cc77d43f611e3f Mon Sep 17 00:00:00 2001
From: Anonymous Maarten <[EMAIL REDACTED]>
Date: Fri, 23 Feb 2024 23:22:41 +0100
Subject: [PATCH] Limit CPU features through a hint

---
 include/SDL3/SDL_hints.h  | 31 ++++++++++++++++
 src/cpuinfo/SDL_cpuinfo.c | 75 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 106 insertions(+)

diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h
index 2939d75e75a29..8bbfc1344a2bf 100644
--- a/include/SDL3/SDL_hints.h
+++ b/include/SDL3/SDL_hints.h
@@ -371,6 +371,37 @@ extern "C" {
  */
 #define SDL_HINT_CAMERA_DRIVER "SDL_CAMERA_DRIVER"
 
+/**
+ *  A variable that limits what CPU features are available.
+ *
+ *  By default, SDL marks all features the current CPU supports as available.
+ *  This hint allows to limit these to a subset.
+ *
+ *  When the hint is unset, or empty, SDL will enable all detected CPU
+ *  features.
+ *
+ * The variable can be set to a comma separated list containing the following items:
+ *   "all"
+ *   "altivec"
+ *   "sse"
+ *   "sse2"
+ *   "sse3"
+ *   "sse41"
+ *   "sse42"
+ *   "avx"
+ *   "avx2"
+ *   "avx512f"
+ *   "arm-simd"
+ *   "neon"
+ *   "lsx"
+ *   "lasx"
+ *
+ *  The items can be prefixed by '+'/'-' to add/remove features.
+ *
+ *  This hint is available since SDL 3.0.0.
+ */
+#define SDL_HINT_CPU_FEATURE_MASK "SDL_CPU_FEATURE_MASK"
+
 /**
  * A variable controlling whether DirectInput should be used for controllers
  *
diff --git a/src/cpuinfo/SDL_cpuinfo.c b/src/cpuinfo/SDL_cpuinfo.c
index 0fd3d14bd0e37..49aaf09360476 100644
--- a/src/cpuinfo/SDL_cpuinfo.c
+++ b/src/cpuinfo/SDL_cpuinfo.c
@@ -858,6 +858,80 @@ int SDL_GetCPUCacheLineSize(void)
 static Uint32 SDL_CPUFeatures = 0xFFFFFFFF;
 static Uint32 SDL_SIMDAlignment = 0xFFFFFFFF;
 
+static SDL_bool ref_string_equals(const char *ref, const char *test, const char *end_test) {
+    size_t len_test = end_test - test;
+    return SDL_strncmp(ref, test, len_test) == 0 && ref[len_test] == '\0' && (test[len_test] == '\0' || test[len_test] == ',');
+}
+
+static Uint32 SDLCALL SDL_CPUFeatureMaskFromHint(void)
+{
+    Uint32 result_mask = 0xFFFFFFFF;
+
+    const char *hint = SDL_GetHint(SDL_HINT_CPU_FEATURE_MASK);
+    
+    if (hint) {
+        for (const char *spot = hint, *next; *spot; spot = next) {
+            const char *end = SDL_strchr(spot, ',');
+            Uint32 spot_mask;
+            SDL_bool add_spot_mask = SDL_TRUE;
+            if (end) {
+                next = end + 1;
+            } else {
+                size_t len = SDL_strlen(spot);
+                end = spot + len;
+                next = end;
+            }
+            if (spot[0] == '+') {
+                add_spot_mask = SDL_TRUE;
+                spot += 1;
+            } else if (spot[0] == '-') {
+                add_spot_mask = SDL_FALSE;
+                spot += 1;
+            }
+            if (ref_string_equals("all", spot, end)) {
+                spot_mask = 0xFFFFFFFF;
+            } else if (ref_string_equals("altivec", spot, end)) {
+                spot_mask= CPU_HAS_ALTIVEC;
+            } else if (ref_string_equals("mmx", spot, end)) {
+                spot_mask = CPU_HAS_MMX;
+            } else if (ref_string_equals("sse", spot, end)) {
+                spot_mask = CPU_HAS_SSE;
+            } else if (ref_string_equals("sse2", spot, end)) {
+                spot_mask = CPU_HAS_SSE2;
+            } else if (ref_string_equals("sse3", spot, end)) {
+                spot_mask = CPU_HAS_SSE3;
+            } else if (ref_string_equals("sse41", spot, end)) {
+                spot_mask = CPU_HAS_SSE41;
+            } else if (ref_string_equals("sse42", spot, end)) {
+                spot_mask = CPU_HAS_SSE42;
+            } else if (ref_string_equals("avx", spot, end)) {
+                spot_mask = CPU_HAS_AVX;
+            } else if (ref_string_equals("avx2", spot, end)) {
+                spot_mask = CPU_HAS_AVX2;
+            } else if (ref_string_equals("avx512f", spot, end)) {
+                spot_mask = CPU_HAS_AVX512F;
+            } else if (ref_string_equals("arm-simd", spot, end)) {
+                spot_mask = CPU_HAS_ARM_SIMD;
+            } else if (ref_string_equals("neon", spot, end)) {
+                spot_mask = CPU_HAS_NEON;
+            } else if (ref_string_equals("lsx", spot, end)) {
+                spot_mask = CPU_HAS_LSX;
+            } else if (ref_string_equals("lasx", spot, end)) {
+                spot_mask = CPU_HAS_LASX;
+            } else {
+                /* Ignore unknown/incorrect cpu feature(s) */
+                continue;
+            }
+            if (add_spot_mask) {
+                result_mask |= spot_mask;
+            } else {
+                result_mask &= ~spot_mask;
+            }
+        }
+    }
+    return result_mask;
+}
+
 static Uint32 SDL_GetCPUFeatures(void)
 {
     if (SDL_CPUFeatures == 0xFFFFFFFF) {
@@ -920,6 +994,7 @@ static Uint32 SDL_GetCPUFeatures(void)
             SDL_CPUFeatures |= CPU_HAS_LASX;
             SDL_SIMDAlignment = SDL_max(SDL_SIMDAlignment, 32);
         }
+        SDL_CPUFeatures &= SDL_CPUFeatureMaskFromHint();
     }
     return SDL_CPUFeatures;
 }