From 7cd914593f3daaa0daa7d8385282c4b90cc4e561 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Tue, 23 Jan 2024 16:59:19 -0800
Subject: [PATCH] Added HDR surface properties and tone mapping from HDR to SDR
This currently only supports PQ, but can be expanded in the future
---
include/SDL3/SDL_surface.h | 61 +++++++++
src/video/SDL_blit.c | 88 ++++++++++---
src/video/SDL_blit.h | 14 +++
src/video/SDL_blit_slow.c | 245 +++++++++++++++++++++++++++++++++++++
src/video/SDL_blit_slow.h | 1 +
5 files changed, 391 insertions(+), 18 deletions(-)
diff --git a/include/SDL3/SDL_surface.h b/include/SDL3/SDL_surface.h
index 3f378a7d8437..6240d8abe603 100644
--- a/include/SDL3/SDL_surface.h
+++ b/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.
*
diff --git a/src/video/SDL_blit.c b/src/video/SDL_blit.c
index 56c61a19e35a..71854c8eac8b 100644
--- a/src/video/SDL_blit.c
+++ b/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;
diff --git a/src/video/SDL_blit.h b/src/video/SDL_blit.h
index be30fa64fa8f..9679a8a86771 100644
--- a/src/video/SDL_blit.h
+++ b/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) { \
diff --git a/src/video/SDL_blit_slow.c b/src/video/SDL_blit_slow.c
index 000c5419df7d..5fdfe6f370bd 100644
--- a/src/video/SDL_blit_slow.c
+++ b/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;
+ }
+}
diff --git a/src/video/SDL_blit_slow.h b/src/video/SDL_blit_slow.h
index 40e1a44cdcdb..d789aa8a3aa4 100644
--- a/src/video/SDL_blit_slow.h
+++ b/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_ */