SDL: Make the SDL3 surface ABI compatible with SDL2

From 2ad50e96754f8ef40a9e1139648501cbeedec476 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Wed, 20 Dec 2023 17:35:43 -0800
Subject: [PATCH] Make the SDL3 surface ABI compatible with SDL2

We'll use properties for new data associated with a surface, which lets us preserve ABI compatibility with SDL2 and any surfaces created by applications and passed in to SDL functions.
---
 include/SDL3/SDL_surface.h | 18 +++++++------
 src/video/SDL_surface.c    | 52 ++++++++++++++++++++++++++++++--------
 2 files changed, 51 insertions(+), 19 deletions(-)

diff --git a/include/SDL3/SDL_surface.h b/include/SDL3/SDL_surface.h
index fcc8c1d2cf5c..c6a3459b9b0e 100644
--- a/include/SDL3/SDL_surface.h
+++ b/include/SDL3/SDL_surface.h
@@ -50,11 +50,12 @@ extern "C" {
  *  Used internally (read-only).
  */
 /* @{ */
-#define SDL_SWSURFACE       0           /**< Just here for compatibility */
-#define SDL_PREALLOC        0x00000001  /**< Surface uses preallocated memory */
-#define SDL_RLEACCEL        0x00000002  /**< Surface is RLE encoded */
-#define SDL_DONTFREE        0x00000004  /**< Surface is referenced internally */
-#define SDL_SIMD_ALIGNED    0x00000008  /**< Surface uses aligned memory */
+#define SDL_SWSURFACE               0           /**< Just here for compatibility */
+#define SDL_PREALLOC                0x00000001  /**< Surface uses preallocated memory */
+#define SDL_RLEACCEL                0x00000002  /**< Surface is RLE encoded */
+#define SDL_DONTFREE                0x00000004  /**< Surface is referenced internally */
+#define SDL_SIMD_ALIGNED            0x00000008  /**< Surface uses aligned memory */
+#define SDL_SURFACE_USES_PROPERTIES 0x00000010  /**< Surface uses properties */
 /* @} *//* Surface flags */
 
 /**
@@ -104,7 +105,10 @@ typedef struct SDL_Surface
     void *pixels;               /**< Read-write */
 
     /** Application data associated with the surface */
-    SDL_PropertiesID props;     /**< Read-write */
+    union {
+        void *reserved;         /**< For ABI compatibility only, do not use */
+        SDL_PropertiesID props; /**< Read-only */
+    };
 
     /** information needed for surfaces requiring locks */
     int locked;                 /**< Read-only */
@@ -115,8 +119,6 @@ typedef struct SDL_Surface
     /** clipping information */
     SDL_Rect clip_rect;         /**< Read-only */
 
-    SDL_ScaleMode scaleMode;    /**< The scale mode */
-
     /** info for fast blit mapping to other surfaces */
     SDL_BlitMap *map;           /**< Private */
 
diff --git a/src/video/SDL_surface.c b/src/video/SDL_surface.c
index a86f8e068af6..0e9cd40ec55a 100644
--- a/src/video/SDL_surface.c
+++ b/src/video/SDL_surface.c
@@ -257,8 +257,16 @@ SDL_PropertiesID SDL_GetSurfaceProperties(SDL_Surface *surface)
         return 0;
     }
 
-    if (surface->props == 0) {
+    if (!(surface->flags & SDL_SURFACE_USES_PROPERTIES)) {
+        if (surface->reserved != NULL) {
+            SDL_SetError("Surface has userdata, incompatible with properties");
+            return 0;
+        }
+
         surface->props = SDL_CreateProperties();
+        if (surface->props) {
+            surface->flags |= SDL_SURFACE_USES_PROPERTIES;
+        }
     }
     return surface->props;
 }
@@ -776,7 +784,12 @@ int SDL_BlitSurface(SDL_Surface *src, const SDL_Rect *srcrect,
 int SDL_BlitSurfaceScaled(SDL_Surface *src, const SDL_Rect *srcrect,
                         SDL_Surface *dst, SDL_Rect *dstrect)
 {
-    return SDL_PrivateBlitSurfaceScaled(src, srcrect, dst, dstrect, src->scaleMode);
+    SDL_ScaleMode scale_mode = SDL_SCALEMODE_NEAREST;
+
+    if (SDL_GetSurfaceScaleMode(src, &scale_mode) < 0) {
+        return -1;
+    }
+    return SDL_PrivateBlitSurfaceScaled(src, srcrect, dst, dstrect, scale_mode);
 }
 
 int SDL_PrivateBlitSurfaceScaled(SDL_Surface *src, const SDL_Rect *srcrect,
@@ -944,7 +957,12 @@ int SDL_PrivateBlitSurfaceScaled(SDL_Surface *src, const SDL_Rect *srcrect,
 int SDL_BlitSurfaceUncheckedScaled(SDL_Surface *src, const SDL_Rect *srcrect,
                                    SDL_Surface *dst, const SDL_Rect *dstrect)
 {
-    return SDL_PrivateBlitSurfaceUncheckedScaled(src, srcrect, dst, dstrect, src->scaleMode);
+    SDL_ScaleMode scale_mode = SDL_SCALEMODE_NEAREST;
+
+    if (SDL_GetSurfaceScaleMode(src, &scale_mode) < 0) {
+        return -1;
+    }
+    return SDL_PrivateBlitSurfaceUncheckedScaled(src, srcrect, dst, dstrect, scale_mode);
 }
 
 int SDL_PrivateBlitSurfaceUncheckedScaled(SDL_Surface *src, const SDL_Rect *srcrect,
@@ -1051,8 +1069,12 @@ int SDL_PrivateBlitSurfaceUncheckedScaled(SDL_Surface *src, const SDL_Rect *srcr
     }
 }
 
+#define SDL_PROPERTY_SURFACE_SCALEMODE  "SDL.internal.surface.scale_mode"
+
 int SDL_SetSurfaceScaleMode(SDL_Surface *surface, SDL_ScaleMode scaleMode)
 {
+    SDL_PropertiesID props;
+
     if (!surface) {
         return SDL_InvalidParamError("surface");
     }
@@ -1061,13 +1083,15 @@ int SDL_SetSurfaceScaleMode(SDL_Surface *surface, SDL_ScaleMode scaleMode)
         return SDL_InvalidParamError("scaleMode");
     }
 
-    if (scaleMode == SDL_SCALEMODE_NEAREST) {
-        surface->scaleMode = SDL_SCALEMODE_NEAREST;
-    } else {
-        surface->scaleMode = SDL_SCALEMODE_LINEAR;
+    props = SDL_GetSurfaceProperties(surface);
+    if (!props) {
+        return -1;
     }
 
-    return 0;
+    if (scaleMode != SDL_SCALEMODE_NEAREST) {
+        scaleMode = SDL_SCALEMODE_LINEAR;
+    }
+    return SDL_SetNumberProperty(props, SDL_PROPERTY_SURFACE_SCALEMODE, scaleMode);
 }
 
 int SDL_GetSurfaceScaleMode(SDL_Surface *surface, SDL_ScaleMode *scaleMode)
@@ -1077,9 +1101,12 @@ int SDL_GetSurfaceScaleMode(SDL_Surface *surface, SDL_ScaleMode *scaleMode)
     }
 
     if (scaleMode) {
-        *scaleMode = surface->scaleMode;
+        if (surface->flags & SDL_SURFACE_USES_PROPERTIES) {
+            *scaleMode = (SDL_ScaleMode)SDL_GetNumberProperty(SDL_GetSurfaceProperties(surface), SDL_PROPERTY_SURFACE_SCALEMODE, SDL_SCALEMODE_NEAREST);
+        } else {
+            *scaleMode = SDL_SCALEMODE_NEAREST;
+        }
     }
-
     return 0;
 }
 
@@ -1595,7 +1622,10 @@ void SDL_DestroySurface(SDL_Surface *surface)
         return;
     }
 
-    SDL_DestroyProperties(surface->props);
+    if (surface->flags & SDL_SURFACE_USES_PROPERTIES) {
+        SDL_DestroyProperties(surface->props);
+    }
+
     SDL_InvalidateMap(surface->map);
     SDL_InvalidateAllBlitMap(surface);