SDL: Added HDR10 display support for the direct3d11, direct3d12, and vulkan renderers

From 23b641070c2cda0a0d1429466c1af9c70e6bffaf Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Tue, 21 Jul 2026 11:07:51 -0700
Subject: [PATCH] Added HDR10 display support for the direct3d11, direct3d12,
 and vulkan renderers

---
 include/SDL3/SDL_surface.h                    |    4 +-
 src/render/SDL_render.c                       |   45 +-
 src/render/SDL_sysrender.h                    |    9 +-
 .../direct3d11/D3D11_PixelShader_Colors_PQ.h  |  432 +++++
 .../D3D11_PixelShader_Colors_PQ.hlsl          |    7 +
 .../direct3d11/D3D11_PixelShader_Common.hlsli |  119 +-
 .../D3D11_PixelShader_Textures_PQ.h           | 1698 ++++++++++++++++
 .../D3D11_PixelShader_Textures_PQ.hlsl        |    7 +
 .../D3D11_PixelShader_Textures_Simple.h       |  180 ++
 .../D3D11_PixelShader_Textures_Simple.hlsl    |    7 +
 src/render/direct3d11/SDL_render_d3d11.c      |   68 +-
 src/render/direct3d11/SDL_shaders_d3d11.c     |   15 +
 src/render/direct3d11/SDL_shaders_d3d11.h     |    3 +
 src/render/direct3d11/compile_shaders.bat     |    3 +
 .../direct3d12/D3D12_PixelShader_Advanced.h   | 1486 +++++++-------
 .../direct3d12/D3D12_PixelShader_Colors.h     |  442 +++--
 .../direct3d12/D3D12_PixelShader_Colors_PQ.h  |  592 ++++++
 .../D3D12_PixelShader_Colors_PQ.hlsl          |    8 +
 .../direct3d12/D3D12_PixelShader_Common.hlsli |  119 +-
 .../direct3d12/D3D12_PixelShader_Textures.h   |  523 ++---
 .../D3D12_PixelShader_Textures_PQ.h           | 1725 +++++++++++++++++
 .../D3D12_PixelShader_Textures_PQ.hlsl        |    8 +
 .../D3D12_PixelShader_Textures_Simple.h       |  494 +++++
 .../D3D12_PixelShader_Textures_Simple.hlsl    |    8 +
 .../direct3d12/D3D12_VertexShader_Advanced.h  |  577 +++---
 .../direct3d12/D3D12_VertexShader_Color.h     |  613 +++---
 .../direct3d12/D3D12_VertexShader_Texture.h   |  713 +++----
 src/render/direct3d12/SDL_render_d3d12.c      |   70 +-
 src/render/direct3d12/SDL_shaders_d3d12.c     |   27 +-
 src/render/direct3d12/SDL_shaders_d3d12.h     |    3 +
 .../direct3d12/SDL_shaders_d3d12_xboxone.cpp  |   27 +-
 .../SDL_shaders_d3d12_xboxseries.cpp          |   27 +-
 src/render/direct3d12/compile_shaders.bat     |    3 +
 src/render/gpu/SDL_render_gpu.c               |    6 +-
 src/render/metal/SDL_render_metal.m           |    8 +-
 src/render/vulkan/SDL_render_vulkan.c         |   79 +-
 src/render/vulkan/SDL_shaders_vulkan.c        |    9 +
 src/render/vulkan/SDL_shaders_vulkan.h        |    3 +
 .../vulkan/VULKAN_PixelShader_Advanced.h      |  862 ++++----
 .../vulkan/VULKAN_PixelShader_Advanced_YUV.h  |  818 ++++----
 .../vulkan/VULKAN_PixelShader_Colors_PQ.h     |   62 +
 .../vulkan/VULKAN_PixelShader_Colors_PQ.hlsl  |    7 +
 .../vulkan/VULKAN_PixelShader_Common.hlsli    |  119 +-
 .../vulkan/VULKAN_PixelShader_Textures_PQ.h   |  355 ++++
 .../VULKAN_PixelShader_Textures_PQ.hlsl       |    7 +
 .../VULKAN_PixelShader_Textures_Simple.h      |   22 +
 .../VULKAN_PixelShader_Textures_Simple.hlsl   |    7 +
 src/render/vulkan/compile_shaders.bat         |    3 +
 src/video/SDL_pixels.c                        |  121 +-
 src/video/SDL_pixels_c.h                      |    1 +
 src/video/SDL_surface.c                       |    8 +-
 test/testautomation_surface.c                 |    4 +
 test/testcolorspace.c                         |   13 +-
 53 files changed, 9403 insertions(+), 3173 deletions(-)
 create mode 100644 src/render/direct3d11/D3D11_PixelShader_Colors_PQ.h
 create mode 100644 src/render/direct3d11/D3D11_PixelShader_Colors_PQ.hlsl
 create mode 100644 src/render/direct3d11/D3D11_PixelShader_Textures_PQ.h
 create mode 100644 src/render/direct3d11/D3D11_PixelShader_Textures_PQ.hlsl
 create mode 100644 src/render/direct3d11/D3D11_PixelShader_Textures_Simple.h
 create mode 100644 src/render/direct3d11/D3D11_PixelShader_Textures_Simple.hlsl
 create mode 100644 src/render/direct3d12/D3D12_PixelShader_Colors_PQ.h
 create mode 100644 src/render/direct3d12/D3D12_PixelShader_Colors_PQ.hlsl
 create mode 100644 src/render/direct3d12/D3D12_PixelShader_Textures_PQ.h
 create mode 100644 src/render/direct3d12/D3D12_PixelShader_Textures_PQ.hlsl
 create mode 100644 src/render/direct3d12/D3D12_PixelShader_Textures_Simple.h
 create mode 100644 src/render/direct3d12/D3D12_PixelShader_Textures_Simple.hlsl
 create mode 100644 src/render/vulkan/VULKAN_PixelShader_Colors_PQ.h
 create mode 100644 src/render/vulkan/VULKAN_PixelShader_Colors_PQ.hlsl
 create mode 100644 src/render/vulkan/VULKAN_PixelShader_Textures_PQ.h
 create mode 100644 src/render/vulkan/VULKAN_PixelShader_Textures_PQ.hlsl
 create mode 100644 src/render/vulkan/VULKAN_PixelShader_Textures_Simple.h
 create mode 100644 src/render/vulkan/VULKAN_PixelShader_Textures_Simple.hlsl

diff --git a/include/SDL3/SDL_surface.h b/include/SDL3/SDL_surface.h
index 1194596cd251e..c2184e3ab250b 100644
--- a/include/SDL3/SDL_surface.h
+++ b/include/SDL3/SDL_surface.h
@@ -1703,7 +1703,7 @@ extern SDL_DECLSPEC Uint32 SDLCALL SDL_MapSurfaceRGB(SDL_Surface *surface, Uint8
 extern SDL_DECLSPEC Uint32 SDLCALL SDL_MapSurfaceRGBA(SDL_Surface *surface, Uint8 r, Uint8 g, Uint8 b, Uint8 a);
 
 /**
- * Retrieves a single pixel from a surface.
+ * Retrieves a single pixel from a surface as RGBA in the sRGB colorspace.
  *
  * This function prioritizes correctness over speed: it is suitable for unit
  * tests, but is not intended for use in a game engine.
@@ -1733,7 +1733,7 @@ extern SDL_DECLSPEC Uint32 SDLCALL SDL_MapSurfaceRGBA(SDL_Surface *surface, Uint
 extern SDL_DECLSPEC bool SDLCALL SDL_ReadSurfacePixel(SDL_Surface *surface, int x, int y, Uint8 *r, Uint8 *g, Uint8 *b, Uint8 *a);
 
 /**
- * Retrieves a single pixel from a surface.
+ * Retrieves a single pixel from a surface as RGBA in the sRGB colorspace.
  *
  * This function prioritizes correctness over speed: it is suitable for unit
  * tests, but is not intended for use in a game engine.
diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c
index e9645aca93d94..8d441e317cfd1 100644
--- a/src/render/SDL_render.c
+++ b/src/render/SDL_render.c
@@ -186,35 +186,17 @@ void SDL_SetupRendererColorspace(SDL_Renderer *renderer, SDL_PropertiesID props)
 #else
     renderer->output_colorspace = (SDL_Colorspace)SDL_GetNumberProperty(props, SDL_PROP_RENDERER_CREATE_OUTPUT_COLORSPACE_NUMBER, SDL_COLORSPACE_SRGB);
 #endif
+    renderer->current_colorspace = renderer->output_colorspace;
 }
 
-bool SDL_RenderingLinearSpace(SDL_Renderer *renderer)
-{
-    SDL_Colorspace colorspace;
-
-    if (renderer->target) {
-        colorspace = renderer->target->colorspace;
-    } else {
-        colorspace = renderer->output_colorspace;
-    }
-    if (colorspace == SDL_COLORSPACE_SRGB_LINEAR) {
-        return true;
-    }
-    return false;
-}
-
-void SDL_ConvertToLinear(SDL_FColor *color)
+void SDL_ConvertToLinear(SDL_Renderer *renderer, SDL_FColor *color)
 {
     color->r = SDL_sRGBtoLinear(color->r);
     color->g = SDL_sRGBtoLinear(color->g);
     color->b = SDL_sRGBtoLinear(color->b);
-}
-
-void SDL_ConvertFromLinear(SDL_FColor *color)
-{
-    color->r = SDL_sRGBfromLinear(color->r);
-    color->g = SDL_sRGBfromLinear(color->g);
-    color->b = SDL_sRGBfromLinear(color->b);
+    if (renderer->current_colorspace == SDL_COLORSPACE_HDR10) {
+        SDL_ConvertColor709to2020(&color->r, &color->g, &color->b);
+    }
 }
 
 static SDL_INLINE void DebugLogRenderCommands(const SDL_RenderCommand *cmd)
@@ -875,7 +857,8 @@ static void UpdateHDRProperties(SDL_Renderer *renderer)
         return;
     }
 
-    if (renderer->output_colorspace == SDL_COLORSPACE_SRGB_LINEAR) {
+    if (renderer->output_colorspace == SDL_COLORSPACE_SRGB_LINEAR ||
+        renderer->output_colorspace == SDL_COLORSPACE_HDR10) {
         renderer->SDR_white_point = SDL_GetFloatProperty(window_props, SDL_PROP_WINDOW_SDR_WHITE_LEVEL_FLOAT, 1.0f);
         renderer->HDR_headroom = SDL_GetFloatProperty(window_props, SDL_PROP_WINDOW_HDR_HEADROOM_FLOAT, 1.0f);
     } else {
@@ -2829,8 +2812,10 @@ bool SDL_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture)
     renderer->target = texture;
     if (texture) {
         renderer->view = &texture->view;
+        renderer->current_colorspace = texture->colorspace;
     } else {
         renderer->view = &renderer->main_view;
+        renderer->current_colorspace = renderer->output_colorspace;
     }
     UpdateColorScale(renderer);
 
@@ -5462,7 +5447,11 @@ SDL_Surface *SDL_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rect *rect)
             SDL_Texture *parent = SDL_GetPointerProperty(SDL_GetTextureProperties(target), SDL_PROP_TEXTURE_PARENT_POINTER, NULL);
             SDL_PixelFormat expected_format = (parent ? parent->format : target->format);
 
-            SDL_SetFloatProperty(props, SDL_PROP_SURFACE_SDR_WHITE_POINT_FLOAT, target->SDR_white_point);
+            if (SDL_COLORSPACETRANSFER(target->colorspace) == SDL_TRANSFER_CHARACTERISTICS_PQ) {
+                SDL_SetFloatProperty(props, SDL_PROP_SURFACE_SDR_WHITE_POINT_FLOAT, target->SDR_white_point * 80.0f);
+            } else {
+                SDL_SetFloatProperty(props, SDL_PROP_SURFACE_SDR_WHITE_POINT_FLOAT, target->SDR_white_point);
+            }
             SDL_SetFloatProperty(props, SDL_PROP_SURFACE_HDR_HEADROOM_FLOAT, target->HDR_headroom);
 
             // Set the expected surface format
@@ -5474,7 +5463,11 @@ SDL_Surface *SDL_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rect *rect)
                 surface->fmt = SDL_GetPixelFormatDetails(expected_format);
             }
         } else {
-            SDL_SetFloatProperty(props, SDL_PROP_SURFACE_SDR_WHITE_POINT_FLOAT, renderer->SDR_white_point);
+            if (SDL_COLORSPACETRANSFER(renderer->output_colorspace) == SDL_TRANSFER_CHARACTERISTICS_PQ) {
+                SDL_SetFloatProperty(props, SDL_PROP_SURFACE_SDR_WHITE_POINT_FLOAT, renderer->SDR_white_point * 80.0f);
+            } else {
+                SDL_SetFloatProperty(props, SDL_PROP_SURFACE_SDR_WHITE_POINT_FLOAT, renderer->SDR_white_point);
+            }
             SDL_SetFloatProperty(props, SDL_PROP_SURFACE_HDR_HEADROOM_FLOAT, renderer->HDR_headroom);
         }
     }
diff --git a/src/render/SDL_sysrender.h b/src/render/SDL_sysrender.h
index 49566067bac6f..87fbbaf35d242 100644
--- a/src/render/SDL_sysrender.h
+++ b/src/render/SDL_sysrender.h
@@ -322,6 +322,7 @@ struct SDL_Renderer
     // The list of palettes
     SDL_HashTable *palettes;
 
+    SDL_Colorspace current_colorspace;
     SDL_Colorspace output_colorspace;
     float SDR_white_point;
     float HDR_headroom;
@@ -407,9 +408,11 @@ extern bool SDL_AddSupportedTextureFormat(SDL_Renderer *renderer, SDL_PixelForma
 extern void SDL_SetupRendererColorspace(SDL_Renderer *renderer, SDL_PropertiesID props);
 
 // Colorspace conversion functions
-extern bool SDL_RenderingLinearSpace(SDL_Renderer *renderer);
-extern void SDL_ConvertToLinear(SDL_FColor *color);
-extern void SDL_ConvertFromLinear(SDL_FColor *color);
+#define SDL_RenderingLinearSpace(renderer) \
+    (renderer->current_colorspace == SDL_COLORSPACE_SRGB_LINEAR || \
+     renderer->current_colorspace == SDL_COLORSPACE_HDR10)
+
+extern void SDL_ConvertToLinear(SDL_Renderer *renderer, SDL_FColor *color);
 
 // Blend mode functions
 extern SDL_BlendFactor SDL_GetBlendModeSrcColorFactor(SDL_BlendMode blendMode);
diff --git a/src/render/direct3d11/D3D11_PixelShader_Colors_PQ.h b/src/render/direct3d11/D3D11_PixelShader_Colors_PQ.h
new file mode 100644
index 0000000000000..cc0a152fdb29e
--- /dev/null
+++ b/src/render/direct3d11/D3D11_PixelShader_Colors_PQ.h
@@ -0,0 +1,432 @@
+#if 0
+//
+// Generated by Microsoft (R) HLSL Shader Compiler 10.1
+//
+//
+// Buffer Definitions:
+//
+// cbuffer Constants
+// {
+//
+//   float scRGB_output;                // Offset:    0 Size:     4 [unused]
+//   float texture_type;                // Offset:    4 Size:     4 [unused]
+//   float input_type;                  // Offset:    8 Size:     4 [unused]
+//   float color_scale;                 // Offset:   12 Size:     4
+//   float4 texel_size;                 // Offset:   16 Size:    16 [unused]
+//   float tonemap_method;              // Offset:   32 Size:     4 [unused]
+//   float tonemap_factor1;             // Offset:   36 Size:     4 [unused]
+//   float tonemap_factor2;             // Offset:   40 Size:     4 [unused]
+//   float sdr_white_point;             // Offset:   44 Size:     4 [unused]
+//   float4 Yoffset;                    // Offset:   48 Size:    16 [unused]
+//   float4 Rcoeff;                     // Offset:   64 Size:    16 [unused]
+//   float4 Gcoeff;                     // Offset:   80 Size:    16 [unused]
+//   float4 Bcoeff;                     // Offset:   96 Size:    16 [unused]
+//
+// }
+//
+//
+// Resource Bindings:
+//
+// Name                                 Type  Format         Dim      HLSL Bind  Count
+// ------------------------------ ---------- ------- ----------- -------------- ------
+// Constants                         cbuffer      NA          NA            cb0      1
+//
+//
+//
+// Input signature:
+//
+// Name                 Index   Mask Register SysValue  Format   Used
+// -------------------- ----- ------ -------- -------- ------- ------
+// SV_POSITION              0   xyzw        0      POS   float
+// TEXCOORD                 0   xy          1     NONE   float
+// COLOR                    0   xyzw        2     NONE   float   xyzw
+//
+//
+// Output signature:
+//
+// Name                 Index   Mask Register SysValue  Format   Used
+// -------------------- ----- ------ -------- -------- ------- ------
+// SV_TARGET                0   xyzw        0   TARGET   float   xyzw
+//
+//
+// Constant buffer to DX9 shader constant mappings:
+//
+// Target Reg Buffer  Start Reg # of Regs        Data Conversion
+// ---------- ------- --------- --------- ----------------------
+// c0         cb0             0         1  ( FLT, FLT, FLT, FLT)
+//
+//
+// Level9 shader bytecode:
+//
+    ps_2_0
+    def c1, 0.00799999945, 0.159301758, 18.8515625, 0.8359375
+    def c2, 18.6875, 1, 78.84375, 0
+    dcl t1
+    mul r0.xyz, t1, c0.w
+    mul_sat r0.xyz, r0, c1.x
+    log r1.x, r0.x
+    log r1.y, r0.y
+    log r1.z, r0.z
+    mul r0.xyz, r1, c1.y
+    exp r1.x, r0.x
+    exp r1.y, r0.y
+    exp r1.z, r0.z
+    mad r0.xyz, r1, c1.z, c1.w
+    mad r1.xyz, r1, c2.x, c2.y
+    rcp r2.x, r1.x
+    rcp r2.y, r1.y
+    rcp r2.z, r1.z
+    mul r0.xyz, r0, r2
+    log r1.x, r0.x
+    log r1.y, r0.y
+    log r1.z, r0.z
+    mul r0.xyz, r1, c2.z
+    exp r1.x, r0.x
+    exp r1.y, r0.y
+    exp r1.z, r0.z
+    mov r1.w, t1.w
+    mov oC0, r1
+
+// approximately 24 instruction slots used
+ps_4_0
+dcl_constantbuffer CB0[1], immediateIndexed
+dcl_input_ps linear v2.xyzw
+dcl_output o0.xyzw
+dcl_temps 2
+mul r0.xyz, v2.xyzx, cb0[0].wwww
+mul_sat r0.xyz, r0.xyzx, l(0.008000, 0.008000, 0.008000, 0.000000)
+log r0.xyz, r0.xyzx
+mul r0.xyz, r0.xyzx, l(0.159302, 0.159302, 0.159302, 0.000000)
+exp r0.xyz, r0.xyzx
+mad r1.xyz, r0.xyzx, l(18.851562, 18.851562, 18.851562, 0.000000), l(0.835938, 0.835938, 0.835938, 0.000000)
+mad r0.xyz, r0.xyzx, l(18.687500, 18.687500, 18.687500, 0.000000), l(1.000000, 1.000000, 1.000000, 0.000000)
+div r0.xyz, r1.xyzx, r0.xyzx
+log r0.xyz, r0.xyzx
+mul r0.xyz, r0.xyzx, l(78.843750, 78.843750, 78.843750, 0.000000)
+exp o0.xyz, r0.xyzx
+mov o0.w, v2.w
+ret
+// Approximately 13 instruction slots used
+#endif
+
+const BYTE g_main[] =
+{
+     68,  88,  66,  67, 215, 236,
+     43, 101, 209, 206,  96, 142,
+    117, 123,  67, 203,  11, 112,
+    225, 132,   1,   0,   0,   0,
+    116,   7,   0,   0,   6,   0,
+      0,   0,  56,   0,   0,   0,
+    248,   1,   0,   0, 204,   3,
+      0,   0,  72,   4,   0,   0,
+    204,   6,   0,   0,  64,   7,
+      0,   0,  65, 111, 110,  57,
+    184,   1,   0,   0, 184,   1,
+      0,   0,   0,   2, 255, 255,
+    136,   1,   0,   0,  48,   0,
+      0,   0,   1,   0,  36,   0,
+      0,   0,  48,   0,   0,   0,
+     48,   0,   0,   0,  36,   0,
+      0,   0,  48,   0,   0,   0,
+      0,   0,   1,   0,   0,   0,
+      0,   0,   0,   0,   0,   2,
+    255, 255,  81,   0,   0,   5,
+      1,   0,  15, 160, 110,  18,
+      3,  60,   0,  32,  35,  62,
+      0, 208, 150,  65,   0,   0,
+     86,  63,  81,   0,   0,   5,
+      2,   0,  15, 160,   0, 128,
+    149,  65,   0,   0, 128,  63,
+      0, 176, 157,  66,   0,   0,
+      0,   0,  31,   0,   0,   2,
+      0,   0,   0, 128,   1,   0,
+     15, 176,   5,   0,   0,   3,
+      0,   0,   7, 128,   1,   0,
+    228, 176,   0,   0, 255, 160,
+      5,   0,   0,   3,   0,   0,
+     23, 128,   0,   0, 228, 128,
+      1,   0,   0, 160,  15,   0,
+      0,   2,   1,   0,   1, 128,
+      0,   0,   0, 128,  15,   0,
+      0,   2,   1,   0,   2, 128,
+      0,   0,  85, 128,  15,   0,
+      0,   2,   1,   0,   4, 128,
+      0,   0, 170, 128,   5,   0,
+      0,   3,   0,   0,   7, 128,
+      1,   0, 228, 128,   1,   0,
+     85, 160,  14,   0,   0,   2,
+      1,   0,   1, 128,   0,   0,
+      0, 128,  14,   0,   0,   2,
+      1,   0,   2, 128,   0,   0,
+     85, 128,  14,   0,   0,   2,
+      1,   0,   4, 128,   0,   0,
+    170, 128,   4,   0,   0,   4,
+      0,   0,   7, 128,   1,   0,
+    228, 128,   1,   0, 170, 160,
+      1,   0, 255, 160,   4,   0,
+      0,   4,   1,   0,   7, 128,
+      1,   0, 228, 128,   2,   0,
+      0, 160,   2,   0,  85, 160,
+      6,   0,   0,   2,   2,   0,
+      1, 128,   1,   0,   0, 128,
+      6,   0,   0,   2,   2,   0,
+      2, 128,   1,   0,  85, 128,
+      6,   0,   0,   2,   2,   0,
+      4, 128,   1,   0, 170, 128,
+      5,   0,   0,   3,   0,   0,
+      7, 128,   0,   0, 228, 128,
+      2,   0, 228, 128,  15,   0,
+      0,   2,   1,   0,   1, 128,
+      0,   0,   0, 128,  15,   0,
+      0,   2,   1,   0,   2, 128,
+      0,   0,  85, 128,  15,   0,
+      0,   2,   1,   0,   4, 128,
+      0,   0, 170, 128,   5,   0,
+      0,   3,   0,   0,   7, 128,
+      1,   0, 228, 128,   2,   0,
+    170, 160,  14,   0,   0,   2,
+      1,   0,   1, 128,   0,   0,
+      0, 128,  14,   0,   0,   2,
+      1,   0,   2, 128,   0,   0,
+     85, 128,  14,   0,   0,   2,
+      1,   0,   4, 128,   0,   0,
+    170, 128,   1,   0,   0,   2,
+      1,   0,   8, 128,   1,   0,
+    255, 176,   1,   0,   0,   2,
+      0,   8,  15, 128,   1,   0,
+    228, 128, 255, 255,   0,   0,
+     83,  72,  68,  82, 204,   1,
+      0,   0,  64,   0,   0,   0,
+    115,   0,   0,   0,  89,   0,
+      0,   4,  70, 142,  32,   0,
+      0,   0,   0,   0,   1,   0,
+      0,   0,  98,  16,   0,   3,
+    242,  16,  16,   0,   2,   0,
+      0,   0, 101,   0,   0,   3,
+    242,  32,  16,   0,   0,   0,
+      0,   0, 104,   0,   0,   2,
+      2,   0,   0,   0,  56,   0,
+      0,   8, 114,   0,  16,   0,
+      0,   0,   0,   0,  70,  18,
+     16,   0,   2,   0,   0,   0,
+    246, 143,  32,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,
+     56,  32,   0,  10, 114,   0,
+     16,   0,   0,   0,   0,   0,
+     70,   2,  16,   0,   0,   0,
+      0,   0,   2,  64,   0,   0,
+    110,  18,   3,  60, 110,  18,
+      3,  60, 110,  18,   3,  60,
+      0,   0,   0,   0,  47,   0,
+      0,   5, 114,   0,  16,   0,
+      0,   0,   0,   0,  70,   2,
+     16,   0,   0,   0,   0,   0,
+     56,   0,   0,  10, 114,   0,
+     16,   0,   0,   0,   0,   0,
+     70,   2,  16,   0,   0,   0,
+      0,   0,   2,  64,   0,   0,
+      0,  32,  35,  62,   0,  32,
+     35,  62,   0,  32,  35,  62,
+      0,   0,   0,   0,  25,   0,
+      0,   5, 114,   0,  16,   0,
+      0,   0,   0,   0,  70,   2,
+     16,   0,   0,   0,   0,   0,
+     50,   0,   0,  15, 114,   0,
+     16,   0,   1,   0,   0,   0,
+     70,   2,  16,   0,   0,   0,
+      0,   0,   2,  64,   0,   0,
+      0, 208, 150,  65,   0, 208,
+    150,  65,   0, 208, 150,  65,
+      0,   0,   0,   0,   2,  64,
+      0,   0,   0,   0,  86,  63,
+      0,   0,  86,  63,   0,   0,
+     86,  63,   0,   0,   0,   0,
+     50,   0,   0,  15, 114,   0,
+     16,   0,   0,   0,   0,   0,
+     70,   2,  16,   0,   0,   0,
+      0,   0,   2,  64,   0,   0,
+      0, 128, 149,  65,   0, 128,
+    149,  65,   0, 128, 149,  65,
+      0,   0,   0,   0,   2,  64,
+      0,   0,   0,   0, 128,  63,
+      0,   0, 128,  63,   0,   0,
+    128,  63,   0,   0,   0,   0,
+     14,   0,   0,   7, 114,   0,
+     16,   0,   0,   0,   0,   0,
+     70,   2,  16,   0,   1,   0,
+      0,   0,  70,   2,  16,   0,
+      0,   0,   0,   0,  47,   0,
+      0,   5, 114,   0,  16,   0,
+      0,   0,   0,   0,  70,   2,
+     16,   0,   0,   0,   0,   0,
+     56,   0,   0,  10, 114,   0,
+     16,   0,   0,   0,   0,   0,
+     70,   2,  16,   0,   0,   0,
+      0,   0,   2,  64,   0,   0,
+      0, 176, 157,  66,   0, 176,
+    157,  66,   0, 176, 157,  66,
+      0,   0,   0,   0,  25,   0,
+      0,   5, 114,  32,  16,   0,
+      0,   0,   0,   0,  70,   2,
+     16,   0,   0,   0,   0,   0,
+     54,   0,   0,   5, 130,  32,
+     16,   0,   0,   0,   0,   0,
+     58,  16,  16,   0,   2,   0,
+      0,   0,  62,   0,   0,   1,
+     83,  84,  65,  84, 116,   0,
+      0,   0,  13,   0,   0,   0,
+      2,   0,   0,   0,   0,   0,
+      0,   0,   2,   0,   0,   0,
+     11,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,
+      1,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,
+      1,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,  82,  68,
+     69,  70, 124,   2,   0,   0,
+      1,   0,   0,   0,  72,   0,
+      0,   0,   1,   0,   0,   0,
+     28,   0,   0,   0,   0,   4,
+    255, 255,   0,   1,   0,   0,
+     84,   2,   0,   0,  60,   0,
+      0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   1,   0,
+      0,   0,   1,   0,   0,   0,
+     67, 111, 110, 115, 116,  97,
+    110, 116, 115,   0, 171, 171,
+     60,   0,   0,   0,  13,   0,
+      0,   0,  96,   0,   0,   0,
+    112,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,
+    152,   1,   0,   0,   0,   0,
+      0,   0,   4,   0,   0,   0,
+      0,   0,   0,   0, 168,   1,
+      0,   0,   0,   0,   0,   0,
+    184,   1,   0,   0,   4,   0,
+      0,   0,   4,   0,   0,   0,
+      0,   0,   0,   0, 168,   1,
+      0,   0,   0,   0,   0,   0,
+    197,   1,   0,   0,   8,   0,
+      0,   0,   4,   0,   0,   0,
+      0,   0,   0,   0, 168,   1,
+      0,   0,   0,   0,   0,   0,
+    208,   1,   0,   0,  12,   0,
+      0,   0,   4,   0,   0,   0,
+      2,   0,   0,   0, 168,   1,
+      0,   0,   0,   0,   0,   0,
+    220,   1,   0,   0,  16,   0,
+      0,   0,  16,   0,   0,   0,
+      0,   0,   0,   0, 232,   1,
+      0,   0,   0,   0,   0,   0,
+    248,   1,   0,   0,  32,   0,
+      0,   0,   4,   0,   0,   0,
+      0,   0,   0,   0, 168,   1,
+      0,   0,   0,   0,   0,   0,
+      7,   2,   0,   0,  36,   0,
+      0,   0,   4,   0,   0,   0,
+      0,   0,   0,   0, 168,   1,
+      0,   0,   0,   0,   0,   0,
+     23,   2,   0,   0,  40,   0,
+      0,   0,   4,   0,   0,   0,
+      0,   0,   0,   0, 168,   1,
+      0,   0,   0,   0,   0,   0,
+     39,   2,   0,   0,  44,   0,
+      0,   0,   4,   0,   0,   0,
+      0,   0,   0,   0, 168,   1,
+      0,   0,   0,   0,   0,   0,
+     55,   2,   0,   0,  48,   0,
+      0,   0,  16,   0,   0,   0,
+      0,   0,   0,   0, 232,   1,
+      0,   0,   0,   0,   0,   0,
+     63,   2,   0,   0,  64,   0,
+      0,   0,  16,   0,   0,   0,
+      0,   0,   0,   0, 232,   1,
+      0,   0,   0,   0,   0,   0,
+     70,   2,   0,   0,  80,   0,
+      0,   0,  16,   0,   0,   0,
+      0,   0,   0,   0, 232,   1,
+      0,   0,   0,   0,   0,   0,
+     77,   2,   0,   0,  96,   0,
+      0,   0,  16,   0,   0,   0,
+      0,   0,   0,   0, 232,   1,
+      0,   0,   0,   0,   0,   0,
+    115,  99,  82,  71,  66,  95,
+    111, 117, 116, 112, 117, 116,
+      0, 171, 171, 171,   0,   0,
+      3,   0,   1,   0,   1,   0,
+      0,   0,   0,   0,   0,   0,
+      0,   0, 116, 101, 120, 116,
+    117, 114, 101,  95, 116, 121,
+    112, 101,   0, 105, 110, 112,
+    117, 116,  95, 116, 121, 112,
+    101,   0,  99, 111, 108, 111,
+    114,  95, 115,  99,  97, 108,
+    101,   0, 116, 101, 120, 101,
+    108,  95, 115, 105, 122, 101,
+      0, 171,   1,   0,   3,   0,
+      1,   0,   4,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,
+    116, 111, 110, 101, 109,  97,
+    112,  95, 109, 101, 116, 104,
+    111, 100,   0, 116, 111, 110,
+    101, 109,  97, 112,  95, 102,
+     97,  99, 116, 111, 114,  49,
+      0, 116, 111, 110, 101, 109,
+     97, 112,  95, 102,  97,  99,
+    116, 111, 114,  50,   0, 115,
+    100, 114,  95, 119, 104, 105,
+    116, 101,  95, 112, 111, 105,
+    110, 116,   0,  89, 111, 102,
+    102, 115, 101, 116,   0,  82,
+     99, 111, 101, 102, 102,   0,
+     71,  99, 111, 101, 102, 102,
+      0,  66,  99, 111, 101, 102,
+    102,   0,  77, 105,  99, 114,
+    111, 115, 111, 102, 116,  32,
+     40,  82,  41,  32,  72,  76,
+     83,  76,  32,  83, 104,  97,
+    100, 101, 114,  32,  67, 111,
+    109, 112, 105, 108, 101, 114,
+     32,  49,  48,  46,  49,   0,
+     73,  83,  71,  78, 108,   0,
+      0,   0,   3,   0,   0,   0,
+      8,   0,   0,   0,  80,   0,
+      0,   0,   0,   0,   0,   0,
+      1,   0,   0,   0,   3,   0,
+      0,   0,   0,   0,   0,   0,
+     15,   0,   0,   0,  92,   0,
+      0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   3,   0,
+      0,   0,   1,   0,   0,   0,
+      3,   0,   0,   0, 101,   0,
+      0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   3,   0,
+      0,   0,   2,   0,   0,   0,
+     15,  15,   0,   0,  83,  86,
+     95,  80,  79,  83,  73,  84,
+     73,  79,  78,   0,  84,  69,
+     88,  67,  79,  79,  82,  68,
+      0,  67,  79,  76,  79,  82,
+      0, 171,  79,  83,  71,  78,
+     44,   0,   0,   0,   1,   0,
+      0,   0,   8,   0,   0,   0,
+     32,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,
+      3,   0,   0,   0,   0,   0,
+      0,   0,  15,   0,   0,   0,
+     83,  86,  95,  84,  65,  82,
+     71,  69,  84,   0, 171, 171
+};
diff --git a/src/render/direct3d11/D3D11_PixelShader_Colors_PQ.hlsl b/src/render/direct3d11/D3D11_PixelShader_Colors_PQ.hlsl
new file mode 100644
index 0000000000000..583c140eafb78
--- /dev/null
+++ b/src/render/direct3d11/D3D11_PixelShader_Colors_PQ.hlsl
@@ -0,0 +1,7 @@
+
+#include "D3D11_PixelShader_Common.hlsli"
+
+float4 main(PixelShaderInput input) : SV_TARGET
+{
+    return PQColorPixelShader(input);
+}
diff --git a/src/render/direct3d11/D3D11_PixelShader_Common.hlsli b/src/render/direct3d11/D3D11_PixelShader_Common.hlsli
index 4a4cedd06ba25..14f9aafec9adf 100644
--- a/src/render/direct3d11/D3D11_PixelShader_Common.hlsli
+++ b/src/render/direct3d11/D3D11_PixelShader_Common.hlsli
@@ -63,6 +63,16 @@ static const float3x3 mat2020to709 = {
     { -0.018154, -0.100597, 1.118751 }
 };
 
+float3 scRGBtoNits( float3 v )
+{
+    return v * 80.0;
+}
+
+float3 scRGBfromNits(float3 v)
+{
+    return v / 80.0;
+}
+
 float sRGBtoLinear(float v)
 {
     if (v <= 0.04045) {
@@ -73,6 +83,14 @@ float sRGBtoLinear(float v)
     return v;
 }
 
+float3 sRGBtoLinear(float3 v)
+{
+    v.r = sRGBtoLinear(v.r);
+    v.g = sRGBtoLinear(v.g);
+    v.b = sRGBtoLinear(v.b);
+    return v;
+}
+
 float sRGBfromLinear(float v)
 {
     if (v <= 0.0031308) {
@@ -83,7 +101,15 @@ float sRGBfromLinear(float v)
     return v;
 }
 
-float3 PQtoLinear(float3 v)
+float3 sRGBfromLinear(float3 v)
+{
+    v.r = sRGBfromLinear(v.r);
+    v.g = sRGBfromLinear(v.g);
+    v.b = sRGBfromLinear(v.b);
+    return v;
+}
+
+float3 PQtoNits(float3 v)
 {
     const float c1 = 0.8359375;
     const float c2 = 18.8515625;
@@ -93,7 +119,26 @@ float3 PQtoLinear(float3 v)
 
     float3 num = max(pow(abs(v), oo_m2) - c1, 0.0);
     float3 den = c2 - c3 * pow(abs(v), oo_m2);
-    return (10000.0 * pow(abs(num / den), oo_m1) / sdr_white_point);
+    return 10000.0 * pow(abs(num / den), oo_m1);
+}
+
+float3 PQtoLinear(float3 v)
+{
+    return PQtoNits(v) / sdr_white_point;
+}
+
+float3 PQFromNits(float3 v)
+{
+    const float c1 = 0.8359375;
+    const float c2 = 18.8515625;
+    const float c3 = 18.6875;
+    const float m1 = 0.1593017578125;
+    const float m2 = 78.84375;
+
+    float3 y = saturate( v / 10000.0 );
+    float3 num = c1 + c2 * pow( abs( y ), m1 );
+    float3 den = 1.0 + c3 * pow( abs( y ), m1 );
+    return pow( abs( num / den ), m2 );
 }
 
 float3 ApplyTonemap(float3 v)
@@ -120,6 +165,20 @@ float3 ApplyTonemap(float3 v)
     return v;
 }
 
+float3 ApplyTonemap2020(float3 v)
+{
+    if (tonemap_method == TONEMAP_LINEAR) {
+        v *= tonemap_factor1;
+    } else if (tonemap_method == TONEMAP_CHROME) {
+        float vmax = max(v.r, max(v.g, v.b));
+        if (vmax > 0.0) {
+            float scale = (1.0 + tonemap_factor1 * vmax) / (1.0 + tonemap_factor2 * vmax);
+            v *= scale;
+        }
+    }
+    return v;
+}
+
 float4 SamplePaletteNearest(float2 uv)
 {
     float index = texture0.Sample(sampler0, uv).r * 255;
@@ -238,9 +297,7 @@ float3 GetOutputColorFromSRGB(float3 rgb)
     float3 output;
 
     if (scRGB_output) {
-        rgb.r = sRGBtoLinear(rgb.r);
-        rgb.g = sRGBtoLinear(rgb.g);
-        rgb.b = sRGBtoLinear(rgb.b);
+        rgb = sRGBtoLinear(rgb);
     }
 
     output.rgb = rgb * color_scale;
@@ -255,15 +312,59 @@ float3 GetOutputColorFromLinear(float3 rgb)
     output.rgb = rgb * color_scale;
 
     if (!scRGB_output) {
-        output.r = sRGBfromLinear(output.r);
-        output.g = sRGBfromLinear(output.g);
-        output.b = sRGBfromLinear(output.b);
-        output.rgb = saturate(output.rgb);
+        output.rgb = saturate(sRGBfromLinear(output.rgb));
     }
 
     return output;
 }
 
+float4 PQColorPixelShader(PixelShaderInput input)
+{
+    float3 rgb = float3(1.0, 1.0, 1.0);
+    float4 output;
+
+    // Apply color scaling, which will include display SDR white level
+    rgb = rgb * input.color.rgb * color_scale;
+
+    // Convert to nits
+    rgb = scRGBtoNits(rgb);
+
+    output.rgb = PQFromNits(rgb);
+    output.a = input.color.a;
+
+    return output;
+}
+
+float4 PQTexturePixelShader(PixelShaderInput input)
+{
+    float4 rgba = GetInputColor(input);
+    float4 output;
+
+    if (input_type == INPUTTYPE_HDR10) {
+        rgba.rgb = PQtoLinear(rgba.rgb);
+    } else {
+        if (input_type == INPUTTYPE_SRGB) {
+            rgba.rgb = sRGBtoLinear(rgba.rgb);
+        }
+        rgba.rgb = mul(mat709to2020, rgba.rgb);
+    }
+
+    if (tonemap_method != TONEMAP_NONE) {
+        rgba.rgb = ApplyTonemap2020(rgba.rgb);
+    }
+
+    // Apply color scaling, which will include display SDR white level
+    rgba.rgb = rgba.rgb * input.color.rgb * color_scale;
+
+    // Convert to nits
+    rgba.rgb = scRGBtoNits(rgba.rgb);
+
+    output.rgb = PQFromNits(rgba.rgb);
+    output.a = rgba.a * input.color.a;
+
+    return output;
+}
+
 float4 AdvancedPixelShader(PixelShaderInput input)
 {
     float4 rgba = GetInputColor(input);
diff --git a/src/render/direct3d11/D3D11_PixelShader_Textures_PQ.h b/src/render/direct3d11/D3D11_PixelShader_Textures_PQ.h
new file mode 100644
index 0000000000000..3acd1be41dcec
--- /dev/null
+++ b/src/render/dir

(Patch may be truncated, please check the link at the top of this post.)