Browse Source

Added HDR surface properties and tone mapping from HDR to SDR

This currently only supports PQ, but can be expanded in the future
Sam Lantinga 1 year ago
parent
commit
7cd914593f
5 changed files with 391 additions and 18 deletions
  1. 61 0
      include/SDL3/SDL_surface.h
  2. 70 18
      src/video/SDL_blit.c
  3. 14 0
      src/video/SDL_blit.h
  4. 245 0
      src/video/SDL_blit_slow.c
  5. 1 0
      src/video/SDL_blit_slow.h

+ 61 - 0
include/SDL3/SDL_surface.h

@@ -137,6 +137,53 @@ typedef struct SDL_Surface
 typedef int (SDLCALL *SDL_blit) (struct SDL_Surface *src, const SDL_Rect *srcrect,
                                  struct SDL_Surface *dst, const SDL_Rect *dstrect);
 
+
+/**
+ * The color primaries, as described by https://www.itu.int/rec/T-REC-H.273-201612-S/en
+ */
+typedef enum
+{
+    SDL_COLOR_PRIMARIES_UNKNOWN = 0,
+    SDL_COLOR_PRIMARIES_BT709 = 1,
+    SDL_COLOR_PRIMARIES_IEC61966_2_4 = 1,
+    SDL_COLOR_PRIMARIES_UNSPECIFIED = 2,
+    SDL_COLOR_PRIMARIES_BT470M = 4,
+    SDL_COLOR_PRIMARIES_BT470BG = 5,
+    SDL_COLOR_PRIMARIES_BT601 = 6,
+    SDL_COLOR_PRIMARIES_SMPTE240 = 7,
+    SDL_COLOR_PRIMARIES_GENERIC_FILM = 8,
+    SDL_COLOR_PRIMARIES_BT2020 = 9,
+    SDL_COLOR_PRIMARIES_XYZ = 10,
+    SDL_COLOR_PRIMARIES_SMPTE431 = 11,
+    SDL_COLOR_PRIMARIES_SMPTE432 = 12, /* DCI P3 */
+    SDL_COLOR_PRIMARIES_EBU3213 = 22
+} SDL_ColorPrimaries;
+
+/**
+ * The transfer characteristics, as described by https://www.itu.int/rec/T-REC-H.273-201612-S/en
+ */
+typedef enum
+{
+    SDL_TRANSFER_CHARACTERISTICS_UNKNOWN = 0,
+    SDL_TRANSFER_CHARACTERISTICS_BT709 = 1,
+    SDL_TRANSFER_CHARACTERISTICS_UNSPECIFIED = 2,
+    SDL_TRANSFER_CHARACTERISTICS_BT470M = 4,  /* 2.2 gamma */
+    SDL_TRANSFER_CHARACTERISTICS_BT470BG = 5, /* 2.8 gamma */
+    SDL_TRANSFER_CHARACTERISTICS_BT601 = 6,
+    SDL_TRANSFER_CHARACTERISTICS_SMPTE240 = 7,
+    SDL_TRANSFER_CHARACTERISTICS_LINEAR = 8,
+    SDL_TRANSFER_CHARACTERISTICS_LOG100 = 9,
+    SDL_TRANSFER_CHARACTERISTICS_LOG100_SQRT10 = 10,
+    SDL_TRANSFER_CHARACTERISTICS_IEC61966 = 11,
+    SDL_TRANSFER_CHARACTERISTICS_BT1361 = 12,
+    SDL_TRANSFER_CHARACTERISTICS_SRGB = 13,
+    SDL_TRANSFER_CHARACTERISTICS_BT2020_10BIT = 14,
+    SDL_TRANSFER_CHARACTERISTICS_BT2020_12BIT = 15,
+    SDL_TRANSFER_CHARACTERISTICS_SMPTE2084 = 16, /* PQ */
+    SDL_TRANSFER_CHARACTERISTICS_SMPTE428 = 17,
+    SDL_TRANSFER_CHARACTERISTICS_HLG = 18
+} SDL_TransferCharacteristics;
+
 /**
  * The formula used for converting between YUV and RGB
  */
@@ -213,6 +260,14 @@ extern DECLSPEC void SDLCALL SDL_DestroySurface(SDL_Surface *surface);
 /**
  * Get the properties associated with a surface.
  *
+ * The following properties are understood by SDL:
+ *
+ * - `SDL_PROPERTY_SURFACE_HDR_BOOLEAN`: true if this surface has HDR properties
+ * - `SDL_PROPERTY_SURFACE_COLOR_PRIMARIES_NUMBER`: an SDL_ColorPrimaries value describing the surface colorspace
+ * - `SDL_PROPERTY_SURFACE_TRANSFER_CHARACTERISTICS_NUMBER`: an SDL_TransferCharacteristics value describing the surface colorspace
+ * - `SDL_PROPERTY_SURFACE_MAXCLL_NUMBER`: MaxCLL (Maximum Content Light Level) indicates the maximum light level of any single pixel (in cd/m2 or nits) of the entire playback sequence. MaxCLL is usually measured off the final delivered content after mastering. If one uses the full light level of the HDR mastering display and adds a hard clip at its maximum value, MaxCLL would be equal to the peak luminance of the mastering monitor.
+ * - `SDL_PROPERTY_SURFACE_MAXFALL_NUMBER`: MaxFALL (Maximum Frame Average Light Level) indicates the maximum value of the frame average light level (in cd/m2 or nits) of the entire playback sequence. MaxFALL is calculated by averaging the decoded luminance values of all the pixels within a frame. MaxFALL is usually much lower than MaxCLL.
+ *
  * \param surface the SDL_Surface structure to query
  * \returns a valid property ID on success or 0 on failure; call
  *          SDL_GetError() for more information.
@@ -224,6 +279,12 @@ extern DECLSPEC void SDLCALL SDL_DestroySurface(SDL_Surface *surface);
  */
 extern DECLSPEC SDL_PropertiesID SDLCALL SDL_GetSurfaceProperties(SDL_Surface *surface);
 
+#define SDL_PROPERTY_SURFACE_HDR_BOOLEAN                        "SDL.surface.HDR"
+#define SDL_PROPERTY_SURFACE_COLOR_PRIMARIES_NUMBER             "SDL.surface.color_primaries"
+#define SDL_PROPERTY_SURFACE_TRANSFER_CHARACTERISTICS_NUMBER    "SDL.surface.transfer_characteristics"
+#define SDL_PROPERTY_SURFACE_MAXCLL_NUMBER                      "SDL.surface.maxCLL"
+#define SDL_PROPERTY_SURFACE_MAXFALL_NUMBER                     "SDL.surface.maxFALL"
+
 /**
  * Set the palette used by a surface.
  *

+ 70 - 18
src/video/SDL_blit.c

@@ -183,12 +183,23 @@ static SDL_BlitFunc SDL_ChooseBlitFunc(Uint32 src_format, Uint32 dst_format, int
 }
 #endif /* SDL_HAVE_BLIT_AUTO */
 
+static SDL_bool IsSurfaceHDR(SDL_Surface *surface)
+{
+    if (surface->flags & SDL_SURFACE_USES_PROPERTIES) {
+        SDL_PropertiesID props = SDL_GetSurfaceProperties(surface);
+        return SDL_GetBooleanProperty(props, SDL_PROPERTY_SURFACE_HDR_BOOLEAN, SDL_FALSE);
+    }
+    return SDL_FALSE;
+}
+
 /* Figure out which of many blit routines to set up on a surface */
 int SDL_CalculateBlit(SDL_Surface *surface)
 {
     SDL_BlitFunc blit = NULL;
     SDL_BlitMap *map = surface->map;
     SDL_Surface *dst = map->dst;
+    SDL_bool src_HDR = IsSurfaceHDR(surface);
+    SDL_bool dst_HDR = IsSurfaceHDR(dst);
 
     /* We don't currently support blitting to < 8 bpp surfaces */
     if (dst->format->BitsPerPixel < 8) {
@@ -219,33 +230,74 @@ int SDL_CalculateBlit(SDL_Surface *surface)
 #endif
 
     /* Choose a standard blit function */
-    if (map->identity && !(map->info.flags & ~SDL_COPY_RLE_DESIRED)) {
-        blit = SDL_BlitCopy;
-    } else if (surface->format->Rloss > 8 || dst->format->Rloss > 8) {
-        blit = SDL_Blit_Slow;
+    if (src_HDR || dst_HDR) {
+        if (src_HDR && dst_HDR) {
+            /* See if they're in the same colorspace and light level */
+            SDL_PropertiesID src_props = SDL_GetSurfaceProperties(surface);
+            SDL_PropertiesID dst_props = SDL_GetSurfaceProperties(dst);
+            if ((SDL_GetNumberProperty(src_props, SDL_PROPERTY_SURFACE_COLOR_PRIMARIES_NUMBER, SDL_COLOR_PRIMARIES_UNKNOWN) !=
+                 SDL_GetNumberProperty(dst_props, SDL_PROPERTY_SURFACE_COLOR_PRIMARIES_NUMBER, SDL_COLOR_PRIMARIES_UNKNOWN)) ||
+                (SDL_GetNumberProperty(src_props, SDL_PROPERTY_SURFACE_TRANSFER_CHARACTERISTICS_NUMBER, SDL_TRANSFER_CHARACTERISTICS_UNKNOWN) !=
+                 SDL_GetNumberProperty(dst_props, SDL_PROPERTY_SURFACE_TRANSFER_CHARACTERISTICS_NUMBER, SDL_TRANSFER_CHARACTERISTICS_UNKNOWN)) ||
+                (SDL_GetNumberProperty(src_props, SDL_PROPERTY_SURFACE_MAXCLL_NUMBER, 0) !=
+                 SDL_GetNumberProperty(dst_props, SDL_PROPERTY_SURFACE_MAXCLL_NUMBER, 0)) ||
+                (SDL_GetNumberProperty(src_props, SDL_PROPERTY_SURFACE_MAXFALL_NUMBER, 0) !=
+                 SDL_GetNumberProperty(dst_props, SDL_PROPERTY_SURFACE_MAXFALL_NUMBER, 0))) {
+                SDL_InvalidateMap(map);
+                return SDL_SetError("Tone mapping between HDR surfaces not supported");
+            }
+
+            /* Fall through to the normal blit calculation (is this correct?) */
+
+        } else if (dst_HDR) {
+            SDL_InvalidateMap(map);
+            return SDL_SetError("Tone mapping from an SDR to an HDR surface not supported");
+        } else {
+            /* Tone mapping from an HDR surface to SDR surface */
+            SDL_PropertiesID props = SDL_GetSurfaceProperties(surface);
+            if (SDL_GetNumberProperty(props, SDL_PROPERTY_SURFACE_COLOR_PRIMARIES_NUMBER, SDL_COLOR_PRIMARIES_BT2020) == SDL_COLOR_PRIMARIES_BT2020 &&
+                SDL_GetNumberProperty(props, SDL_PROPERTY_SURFACE_TRANSFER_CHARACTERISTICS_NUMBER, SDL_TRANSFER_CHARACTERISTICS_SMPTE2084) == SDL_TRANSFER_CHARACTERISTICS_SMPTE2084) {
+                if (SDL_ISPIXELFORMAT_10BIT(surface->format->format)) {
+                    blit = SDL_Blit_Slow_PQtoSDR;
+                } else {
+                    SDL_InvalidateMap(map);
+                    return SDL_SetError("Surface has unknown HDR pixel format");
+                }
+            } else {
+                SDL_InvalidateMap(map);
+                return SDL_SetError("Surface has unknown HDR colorspace");
+            }
+        }
     }
+    if (!blit) {
+        if (map->identity && !(map->info.flags & ~SDL_COPY_RLE_DESIRED)) {
+            blit = SDL_BlitCopy;
+        } else if (surface->format->Rloss > 8 || dst->format->Rloss > 8) {
+            blit = SDL_Blit_Slow;
+        }
 #if SDL_HAVE_BLIT_0
-    else if (surface->format->BitsPerPixel < 8 &&
-             SDL_ISPIXELFORMAT_INDEXED(surface->format->format)) {
-        blit = SDL_CalculateBlit0(surface);
-    }
+        else if (surface->format->BitsPerPixel < 8 &&
+                 SDL_ISPIXELFORMAT_INDEXED(surface->format->format)) {
+            blit = SDL_CalculateBlit0(surface);
+        }
 #endif
 #if SDL_HAVE_BLIT_1
-    else if (surface->format->BytesPerPixel == 1 &&
-             SDL_ISPIXELFORMAT_INDEXED(surface->format->format)) {
-        blit = SDL_CalculateBlit1(surface);
-    }
+        else if (surface->format->BytesPerPixel == 1 &&
+                 SDL_ISPIXELFORMAT_INDEXED(surface->format->format)) {
+            blit = SDL_CalculateBlit1(surface);
+        }
 #endif
 #if SDL_HAVE_BLIT_A
-    else if (map->info.flags & SDL_COPY_BLEND) {
-        blit = SDL_CalculateBlitA(surface);
-    }
+        else if (map->info.flags & SDL_COPY_BLEND) {
+            blit = SDL_CalculateBlitA(surface);
+        }
 #endif
 #if SDL_HAVE_BLIT_N
-    else {
-        blit = SDL_CalculateBlitN(surface);
-    }
+        else {
+            blit = SDL_CalculateBlitN(surface);
+        }
 #endif
+    }
 #if SDL_HAVE_BLIT_AUTO
     if (!blit) {
         Uint32 src_format = surface->format->format;

+ 14 - 0
src/video/SDL_blit.h

@@ -360,6 +360,13 @@ extern SDL_BlitFunc SDL_CalculateBlitA(SDL_Surface *surface);
         b = ((Pixel >> 2) & 0xFF);               \
         a = SDL_expand_byte[6][(Pixel >> 30)];   \
     }
+#define RGBAFLOAT_FROM_ARGB2101010(Pixel, r, g, b, a)          \
+    {                                                          \
+        r = (float)((Pixel >> 20) & 0x3FF) / 1023.0f;          \
+        g = (float)((Pixel >> 10) & 0x3FF) / 1023.0f;          \
+        b = (float)((Pixel >> 0) & 0x3FF) / 1023.0f;           \
+        a = (float)SDL_expand_byte[6][(Pixel >> 30)] / 255.0f; \
+    }
 #define RGBA_FROM_ABGR2101010(Pixel, r, g, b, a) \
     {                                            \
         r = ((Pixel >> 2) & 0xFF);               \
@@ -367,6 +374,13 @@ extern SDL_BlitFunc SDL_CalculateBlitA(SDL_Surface *surface);
         b = ((Pixel >> 22) & 0xFF);              \
         a = SDL_expand_byte[6][(Pixel >> 30)];   \
     }
+#define RGBAFLOAT_FROM_ABGR2101010(Pixel, r, g, b, a)          \
+    {                                                          \
+        r = (float)((Pixel >> 0) & 0x3FF) / 1023.0f;           \
+        g = (float)((Pixel >> 10) & 0x3FF) / 1023.0f;          \
+        b = (float)((Pixel >> 20) & 0x3FF) / 1023.0f;          \
+        a = (float)SDL_expand_byte[6][(Pixel >> 30)] / 255.0f; \
+    }
 #define DISEMBLE_RGBA(buf, bpp, fmt, Pixel, r, g, b, a) \
     do {                                                \
         switch (bpp) {                                  \

+ 245 - 0
src/video/SDL_blit_slow.c

@@ -235,3 +235,248 @@ void SDL_Blit_Slow(SDL_BlitInfo *info)
         info->dst += info->dst_pitch;
     }
 }
+
+static float PQtoNits(float pq)
+{
+    const float c1 = 0.8359375f;
+    const float c2 = 18.8515625f;
+    const float c3 = 18.6875f;
+
+    const float oo_m1 = 1.0f / 0.1593017578125f;
+    const float oo_m2 = 1.0f / 78.84375f;
+
+    float num = SDL_max(SDL_powf(pq, oo_m2) - c1, 0.0f);
+    float den = c2 - c3 * pow(pq, oo_m2);
+
+    return 10000.0f * SDL_powf(num / den, oo_m1);
+}
+
+static void Convert2020to709(float v[3])
+{
+    static const float matrix[3][3] = {
+        { 1.660496f, -0.587656f, -0.072840f },
+        { -0.124547f, 1.132895f, -0.008348f },
+        { -0.018154f, -0.100597f, 1.118751f }
+    };
+
+    float out[3];
+    out[0] = matrix[0][0] * v[0] + matrix[0][1] * v[1] + matrix[0][2] * v[2];
+    out[1] = matrix[1][0] * v[0] + matrix[1][1] * v[1] + matrix[1][2] * v[2];
+    out[2] = matrix[2][0] * v[0] + matrix[2][1] * v[1] + matrix[2][2] * v[2];
+    v[0] = out[0];
+    v[1] = out[1];
+    v[2] = out[2];
+}
+
+/* This isn't really a tone mapping algorithm but it generally works well for HDR -> SDR display */
+static void PQtoSDR(float floatR, float floatG, float floatB, Uint32 *outR, Uint32 *outG, Uint32 *outB)
+{
+    float v[3];
+    int i;
+
+    v[0] = PQtoNits(floatR);
+    v[1] = PQtoNits(floatG);
+    v[2] = PQtoNits(floatB);
+
+    Convert2020to709(v);
+
+    for (i = 0; i < SDL_arraysize(v); ++i) {
+        v[i] /= 400.0f;
+        v[i] = SDL_clamp(v[i], 0.0f, 1.0f);
+        v[i] = SDL_powf(v[i], 1.0f / 2.2f);
+    }
+
+    *outR = (Uint32)(v[0] * 255);
+    *outG = (Uint32)(v[1] * 255);
+    *outB = (Uint32)(v[2] * 255);
+}
+
+void SDL_Blit_Slow_PQtoSDR(SDL_BlitInfo *info)
+{
+    const int flags = info->flags;
+    const Uint32 modulateR = info->r;
+    const Uint32 modulateG = info->g;
+    const Uint32 modulateB = info->b;
+    const Uint32 modulateA = info->a;
+    Uint32 srcpixel;
+    float srcFloatR, srcFloatG, srcFloatB, srcFloatA;
+    Uint32 srcR, srcG, srcB, srcA;
+    Uint32 dstpixel;
+    Uint32 dstR, dstG, dstB, dstA;
+    Uint64 srcy, srcx;
+    Uint64 posy, posx;
+    Uint64 incy, incx;
+    SDL_PixelFormat *src_fmt = info->src_fmt;
+    SDL_PixelFormat *dst_fmt = info->dst_fmt;
+    int srcbpp = src_fmt->BytesPerPixel;
+    int dstbpp = dst_fmt->BytesPerPixel;
+    int dstfmt_val;
+    Uint32 rgbmask = ~src_fmt->Amask;
+    Uint32 ckey = info->colorkey & rgbmask;
+
+    dstfmt_val = detect_format(dst_fmt);
+
+    incy = ((Uint64)info->src_h << 16) / info->dst_h;
+    incx = ((Uint64)info->src_w << 16) / info->dst_w;
+    posy = incy / 2; /* start at the middle of pixel */
+
+    while (info->dst_h--) {
+        Uint8 *src = 0;
+        Uint8 *dst = info->dst;
+        int n = info->dst_w;
+        posx = incx / 2; /* start at the middle of pixel */
+        srcy = posy >> 16;
+        while (n--) {
+            srcx = posx >> 16;
+            src = (info->src + (srcy * info->src_pitch) + (srcx * srcbpp));
+
+            /* 10-bit pixel format */
+            srcpixel = *((Uint32 *)(src));
+            switch (src_fmt->format) {
+            case SDL_PIXELFORMAT_XRGB2101010:
+                RGBAFLOAT_FROM_ARGB2101010(srcpixel, srcFloatR, srcFloatG, srcFloatB, srcFloatA);
+                srcFloatA = 1.0f;
+                break;
+            case SDL_PIXELFORMAT_XBGR2101010:
+                RGBAFLOAT_FROM_ABGR2101010(srcpixel, srcFloatR, srcFloatG, srcFloatB, srcFloatA);
+                srcFloatA = 1.0f;
+                break;
+            case SDL_PIXELFORMAT_ARGB2101010:
+                RGBAFLOAT_FROM_ARGB2101010(srcpixel, srcFloatR, srcFloatG, srcFloatB, srcFloatA);
+                break;
+            case SDL_PIXELFORMAT_ABGR2101010:
+                RGBAFLOAT_FROM_ABGR2101010(srcpixel, srcFloatR, srcFloatG, srcFloatB, srcFloatA);
+                break;
+            default:
+                /* Unsupported pixel format */
+                srcFloatR = srcFloatG = srcFloatB = srcFloatA = 0.0f;
+                break;
+            }
+
+            PQtoSDR(srcFloatR, srcFloatG, srcFloatB, &srcR, &srcG, &srcB);
+            srcA = (Uint32)(srcFloatA * 255);
+
+            if (flags & SDL_COPY_COLORKEY) {
+                /* srcpixel isn't set for 24 bpp */
+                if (srcbpp == 3) {
+                    srcpixel = (srcR << src_fmt->Rshift) |
+                               (srcG << src_fmt->Gshift) | (srcB << src_fmt->Bshift);
+                }
+                if ((srcpixel & rgbmask) == ckey) {
+                    posx += incx;
+                    dst += dstbpp;
+                    continue;
+                }
+            }
+            if ((flags & (SDL_COPY_BLEND | SDL_COPY_ADD | SDL_COPY_MOD | SDL_COPY_MUL))) {
+                if (FORMAT_HAS_ALPHA(dstfmt_val)) {
+                    DISEMBLE_RGBA(dst, dstbpp, dst_fmt, dstpixel, dstR, dstG, dstB, dstA);
+                } else if (FORMAT_HAS_NO_ALPHA(dstfmt_val)) {
+                    DISEMBLE_RGB(dst, dstbpp, dst_fmt, dstpixel, dstR, dstG, dstB);
+                    dstA = 0xFF;
+                } else {
+                    /* SDL_PIXELFORMAT_ARGB2101010 */
+                    dstpixel = *((Uint32 *)(dst));
+                    RGBA_FROM_ARGB2101010(dstpixel, dstR, dstG, dstB, dstA);
+                }
+            } else {
+                /* don't care */
+                dstR = dstG = dstB = dstA = 0;
+            }
+
+            if (flags & SDL_COPY_MODULATE_COLOR) {
+                srcR = (srcR * modulateR) / 255;
+                srcG = (srcG * modulateG) / 255;
+                srcB = (srcB * modulateB) / 255;
+            }
+            if (flags & SDL_COPY_MODULATE_ALPHA) {
+                srcA = (srcA * modulateA) / 255;
+            }
+            if (flags & (SDL_COPY_BLEND | SDL_COPY_ADD)) {
+                /* This goes away if we ever use premultiplied alpha */
+                if (srcA < 255) {
+                    srcR = (srcR * srcA) / 255;
+                    srcG = (srcG * srcA) / 255;
+                    srcB = (srcB * srcA) / 255;
+                }
+            }
+            switch (flags & (SDL_COPY_BLEND | SDL_COPY_ADD | SDL_COPY_MOD | SDL_COPY_MUL)) {
+            case 0:
+                dstR = srcR;
+                dstG = srcG;
+                dstB = srcB;
+                dstA = srcA;
+                break;
+            case SDL_COPY_BLEND:
+                dstR = srcR + ((255 - srcA) * dstR) / 255;
+                dstG = srcG + ((255 - srcA) * dstG) / 255;
+                dstB = srcB + ((255 - srcA) * dstB) / 255;
+                dstA = srcA + ((255 - srcA) * dstA) / 255;
+                break;
+            case SDL_COPY_ADD:
+                dstR = srcR + dstR;
+                if (dstR > 255) {
+                    dstR = 255;
+                }
+                dstG = srcG + dstG;
+                if (dstG > 255) {
+                    dstG = 255;
+                }
+                dstB = srcB + dstB;
+                if (dstB > 255) {
+                    dstB = 255;
+                }
+                break;
+            case SDL_COPY_MOD:
+                dstR = (srcR * dstR) / 255;
+                dstG = (srcG * dstG) / 255;
+                dstB = (srcB * dstB) / 255;
+                break;
+            case SDL_COPY_MUL:
+                dstR = ((srcR * dstR) + (dstR * (255 - srcA))) / 255;
+                if (dstR > 255) {
+                    dstR = 255;
+                }
+                dstG = ((srcG * dstG) + (dstG * (255 - srcA))) / 255;
+                if (dstG > 255) {
+                    dstG = 255;
+                }
+                dstB = ((srcB * dstB) + (dstB * (255 - srcA))) / 255;
+                if (dstB > 255) {
+                    dstB = 255;
+                }
+                break;
+            }
+            if (FORMAT_HAS_ALPHA(dstfmt_val)) {
+                ASSEMBLE_RGBA(dst, dstbpp, dst_fmt, dstR, dstG, dstB, dstA);
+            } else if (FORMAT_HAS_NO_ALPHA(dstfmt_val)) {
+                ASSEMBLE_RGB(dst, dstbpp, dst_fmt, dstR, dstG, dstB);
+            } else {
+                /* 10-bit pixel format */
+                Uint32 pixel;
+                switch (dst_fmt->format) {
+                case SDL_PIXELFORMAT_XRGB2101010:
+                    dstA = 0xFF;
+                    SDL_FALLTHROUGH;
+                case SDL_PIXELFORMAT_ARGB2101010:
+                    ARGB2101010_FROM_RGBA(pixel, dstR, dstG, dstB, dstA);
+                    break;
+                case SDL_PIXELFORMAT_XBGR2101010:
+                    dstA = 0xFF;
+                    SDL_FALLTHROUGH;
+                case SDL_PIXELFORMAT_ABGR2101010:
+                    ABGR2101010_FROM_RGBA(pixel, dstR, dstG, dstB, dstA);
+                    break;
+                default:
+                    pixel = 0;
+                    break;
+                }
+                *(Uint32 *)dst = pixel;
+            }
+            posx += incx;
+            dst += dstbpp;
+        }
+        posy += incy;
+        info->dst += info->dst_pitch;
+    }
+}

+ 1 - 0
src/video/SDL_blit_slow.h

@@ -25,5 +25,6 @@
 #include "SDL_internal.h"
 
 extern void SDL_Blit_Slow(SDL_BlitInfo *info);
+extern void SDL_Blit_Slow_PQtoSDR(SDL_BlitInfo *info);
 
 #endif /* SDL_blit_slow_h_ */