SDL: Added SDL_HINT_WINDOWS_ERASE_BACKGROUND_MODE (thanks @lostgoat!)

From 063511211948ef36e37f9efb145c210205159607 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Thu, 13 Jun 2024 14:54:36 -0700
Subject: [PATCH] Added SDL_HINT_WINDOWS_ERASE_BACKGROUND_MODE (thanks
 @lostgoat!)

---
 include/SDL3/SDL_hints.h              | 16 ++++++++++++++++
 src/video/windows/SDL_windowsevents.c | 17 ++++++++++++++++-
 src/video/windows/SDL_windowswindow.c | 25 +++++++++++++++++++++++++
 src/video/windows/SDL_windowswindow.h |  8 ++++++++
 4 files changed, 65 insertions(+), 1 deletion(-)

diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h
index a316838654cb3..f0d96ed44b2f5 100644
--- a/include/SDL3/SDL_hints.h
+++ b/include/SDL3/SDL_hints.h
@@ -3552,6 +3552,22 @@ extern "C" {
  */
 #define SDL_HINT_WINDOWS_USE_D3D9EX "SDL_WINDOWS_USE_D3D9EX"
 
+/**
+ * A variable controlling whether SDL will clear the window contents when
+ * the WM_ERASEBKGND message is received.
+ *
+ * The variable can be set to the following values:
+ *
+ * - "0"/"never": Never clear the window.
+ * - "1"/"initial": Clear the window when the first WM_ERASEBKGND event fires. (default)
+ * - "2"/"always": Clear the window on every WM_ERASEBKGND event.
+ *
+ * This hint should be set before creating a window.
+ *
+ * \since This hint is available since SDL 3.0.0.
+ */
+#define SDL_HINT_WINDOWS_ERASE_BACKGROUND_MODE "SDL_WINDOWS_ERASE_BACKGROUND_MODE"
+
 /**
  * A variable controlling whether back-button-press events on Windows Phone to
  * be marked as handled.
diff --git a/src/video/windows/SDL_windowsevents.c b/src/video/windows/SDL_windowsevents.c
index 3ac4912d40fa0..6715178292e3b 100644
--- a/src/video/windows/SDL_windowsevents.c
+++ b/src/video/windows/SDL_windowsevents.c
@@ -369,6 +369,21 @@ static SDL_bool ShouldGenerateWindowCloseOnAltF4(void)
     return SDL_GetHintBoolean(SDL_HINT_WINDOWS_CLOSE_ON_ALT_F4, SDL_TRUE);
 }
 
+static SDL_bool ShouldClearWindowOnEraseBackground(SDL_WindowData *data)
+{
+    switch (data->hint_erase_background_mode) {
+    case SDL_ERASEBACKGROUNDMODE_NEVER:
+        return SDL_FALSE;
+    case SDL_ERASEBACKGROUNDMODE_INITIAL:
+        return !data->videodata->cleared;
+    case SDL_ERASEBACKGROUNDMODE_ALWAYS:
+        return SDL_TRUE;
+    default:
+        // Unexpected value, fallback to default behaviour
+        return !data->videodata->cleared;
+    }
+}
+
 #if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
 /* We want to generate mouse events from mouse and pen, and touch events from touchscreens */
 #define MI_WP_SIGNATURE      0xFF515700
@@ -1689,7 +1704,7 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara
 
         /* We'll do our own drawing, prevent flicker */
     case WM_ERASEBKGND:
-        if (!data->videodata->cleared) {
+        if (ShouldClearWindowOnEraseBackground(data)) {
             RECT client_rect;
             HBRUSH brush;
             data->videodata->cleared = SDL_TRUE;
diff --git a/src/video/windows/SDL_windowswindow.c b/src/video/windows/SDL_windowswindow.c
index d42be71cf763d..014a9207ca92b 100644
--- a/src/video/windows/SDL_windowswindow.c
+++ b/src/video/windows/SDL_windowswindow.c
@@ -352,6 +352,30 @@ static void SDLCALL WIN_MouseRelativeModeCenterChanged(void *userdata, const cha
     data->mouse_relative_mode_center = SDL_GetStringBoolean(hint, SDL_TRUE);
 }
 
+static SDL_WindowEraseBackgroundMode GetEraseBackgroundModeHint()
+{
+    const char *hint = SDL_GetHint(SDL_HINT_WINDOWS_ERASE_BACKGROUND_MODE);
+    if (!hint)
+        return SDL_ERASEBACKGROUNDMODE_INITIAL;
+
+    if (SDL_strstr(hint, "never"))
+        return SDL_ERASEBACKGROUNDMODE_NEVER;
+
+    if (SDL_strstr(hint, "initial"))
+        return SDL_ERASEBACKGROUNDMODE_INITIAL;
+
+    if (SDL_strstr(hint, "always"))
+        return SDL_ERASEBACKGROUNDMODE_ALWAYS;
+
+    int mode = SDL_GetStringInteger(hint, 1);
+    if (mode < 0 || mode > 2) {
+        SDL_Log("GetEraseBackgroundModeHint: invalid value for SDL_HINT_WINDOWS_ERASE_BACKGROUND_MODE. Fallback to default");
+        return SDL_ERASEBACKGROUNDMODE_INITIAL;
+    }
+
+    return mode;
+}
+
 static int SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, HWND hwnd, HWND parent)
 {
     SDL_VideoData *videodata = _this->driverdata;
@@ -377,6 +401,7 @@ static int SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, HWND hwnd
     data->initializing = SDL_TRUE;
     data->last_displayID = window->last_displayID;
     data->dwma_border_color = DWMWA_COLOR_DEFAULT;
+    data->hint_erase_background_mode = GetEraseBackgroundModeHint();
 
     if (SDL_GetHintBoolean("SDL_WINDOW_RETAIN_CONTENT", SDL_FALSE)) {
         data->copybits_flag = 0;
diff --git a/src/video/windows/SDL_windowswindow.h b/src/video/windows/SDL_windowswindow.h
index f1f142784bbbb..3cf0f833e3b78 100644
--- a/src/video/windows/SDL_windowswindow.h
+++ b/src/video/windows/SDL_windowswindow.h
@@ -41,6 +41,13 @@ typedef enum SDL_WindowRect
     SDL_WINDOWRECT_FLOATING
 } SDL_WindowRect;
 
+typedef enum SDL_WindowEraseBackgroundMode
+{
+    SDL_ERASEBACKGROUNDMODE_NEVER,
+    SDL_ERASEBACKGROUNDMODE_INITIAL,
+    SDL_ERASEBACKGROUNDMODE_ALWAYS,
+} SDL_WindowEraseBackgroundMode;
+
 struct SDL_WindowData
 {
     SDL_Window *window;
@@ -74,6 +81,7 @@ struct SDL_WindowData
     SDL_DisplayID last_displayID;
     WCHAR *ICMFileName;
     SDL_Window *keyboard_focus;
+    SDL_WindowEraseBackgroundMode hint_erase_background_mode;
     struct SDL_VideoData *videodata;
 #ifdef SDL_VIDEO_OPENGL_EGL
     EGLSurface egl_surface;