Skip to content
19 changes: 19 additions & 0 deletions video/out/d3d11/context.c
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,24 @@ static float d3d11_target_ref_luma(struct ra_swapchain *sw)
return mp_dxgi_sdr_white_level_from_hwnd(&p->dxgi_ctx, vo_w32_hwnd(sw->ctx->vo));
}

static bool d3d11_target_global_color_management_status(struct ra_swapchain *sw)
{
struct priv *p = sw->priv;
bool reliable = false;
struct mp_w32_acm_status status = {0};

// We don't have a window to match the monitor on d3d11 composition mode.
if (sw->ctx->opts.composition)
return false;

reliable = mp_dxgi_get_acm_status_from_hwnd(&p->dxgi_ctx, vo_w32_hwnd(sw->ctx->vo), &status);

if (reliable && status.acm_enabled)
return true;
else
return false;
}

static bool d3d11_start_frame(struct ra_swapchain *sw, struct ra_fbo *out_fbo)
{
struct priv *p = sw->priv;
Expand Down Expand Up @@ -496,6 +514,7 @@ static const struct ra_swapchain_fns d3d11_swapchain = {
.color_depth = d3d11_color_depth,
.target_csp = d3d11_target_color_space,
.target_ref_luma = d3d11_target_ref_luma,
.target_global_color_management_status = d3d11_target_global_color_management_status,
.start_frame = d3d11_start_frame,
.submit_frame = d3d11_submit_frame,
.swap_buffers = d3d11_swap_buffers,
Expand Down
7 changes: 7 additions & 0 deletions video/out/gpu/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ struct ra_ctx_params {
// See ra_swapchain_fns.target_ref_luma. Optional.
float (*preferred_ref_luma)(struct ra_ctx *ctx);

// See ra_swapchain_fns.target_global_color_management_status. Optional.
bool (*global_color_management_status)(struct ra_ctx *ctx);

// See ra_swapchain_fns.set_color. Optional.
bool (*set_color)(struct ra_ctx *ctx, struct mp_image_params *params);

Expand Down Expand Up @@ -122,6 +125,10 @@ struct ra_swapchain_fns {
// the system displays diffuse (SDR) white. Returns 0 if unknown. Optional.
float (*target_ref_luma)(struct ra_swapchain *sw);

// Get enable status for global color management and HDR. Returns false if
// not enabled or result is not reliable. Optional.
bool (*target_global_color_management_status)(struct ra_swapchain *sw);

// Call into backends so they can use the appropriate platform-specific
// functions to configure color spaces. Returns true if request was handled.
bool (*set_color)(struct ra_swapchain *sw, struct mp_image_params *params);
Expand Down
54 changes: 49 additions & 5 deletions video/out/gpu/d3d11_helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
#include "osdep/io.h"
#include "osdep/threads.h"
#include "osdep/windows_utils.h"
#include "video/out/win32/displayconfig.h"

#include "d3d11_helpers.h"

Expand Down Expand Up @@ -993,8 +992,11 @@ void mp_dxgi_factory_uninit(struct mp_dxgi_factory_ctx *ctx)

SAFE_RELEASE(ctx->factory);
SAFE_RELEASE(ctx->last_matched_output);
ctx->white_level_monitor = NULL;
ctx->target_monitor = NULL;
ctx->sdr_white_level = 0;
ctx->color_management_status_reliable = false;
ctx->color_management_status.acm_enabled = false;
ctx->color_management_status.hdr_enabled = false;
}

bool mp_dxgi_output_desc_from_hwnd(struct mp_dxgi_factory_ctx *ctx,
Expand All @@ -1009,7 +1011,7 @@ bool mp_dxgi_output_desc_from_hwnd(struct mp_dxgi_factory_ctx *ctx,
return false;

if (!ctx->factory || !IDXGIFactory1_IsCurrent(ctx->factory)) {
// This also clears `white_level_monitor`, to invalidate cached value.
// This also clears `target_monitor`, to invalidate cached value.
// While we are using displayconfig API to get reference luminance,
// DXGI IsCurrent() is actually tracking reference luminance changes in
// settings. There is no window message sent on this change.
Expand Down Expand Up @@ -1082,7 +1084,7 @@ float mp_dxgi_sdr_white_level_from_hwnd(struct mp_dxgi_factory_ctx *ctx,
if (!mp_dxgi_output_desc_from_hwnd(ctx, hwnd, &desc))
return 0;

if (ctx && ctx->white_level_monitor == desc.Monitor)
if (ctx && ctx->target_monitor == desc.Monitor)
return ctx->sdr_white_level;

MONITORINFOEXW mi = { .cbSize = sizeof(mi) };
Expand All @@ -1092,7 +1094,7 @@ float mp_dxgi_sdr_white_level_from_hwnd(struct mp_dxgi_factory_ctx *ctx,
float white_level = mp_w32_displayconfig_get_sdr_white_level(mi.szDevice);

if (ctx) {
ctx->white_level_monitor = desc.Monitor;
ctx->target_monitor = desc.Monitor;
ctx->sdr_white_level = white_level;
}

Expand All @@ -1102,6 +1104,48 @@ float mp_dxgi_sdr_white_level_from_hwnd(struct mp_dxgi_factory_ctx *ctx,
#endif
}

bool mp_dxgi_get_acm_status_from_hwnd(struct mp_dxgi_factory_ctx *ctx,
HWND hwnd,
struct mp_w32_acm_status *status)
{
#if HAVE_WIN32_DESKTOP
DXGI_OUTPUT_DESC1 desc;
bool reliable = false;

if (!mp_dxgi_output_desc_from_hwnd(ctx, hwnd, &desc)) {
ctx->color_management_status_reliable = false;
return false;
}

// Cache ACM status to avoid query OS API every time. Switching ACM during
// playing would still trigger re-query.
if (ctx && ctx->target_monitor == desc.Monitor) {
status->acm_enabled = ctx->color_management_status.acm_enabled;
status->hdr_enabled = ctx->color_management_status.hdr_enabled;
return ctx->color_management_status_reliable;
}

MONITORINFOEXW mi = { .cbSize = sizeof(mi) };
if (!GetMonitorInfoW(desc.Monitor, (MONITORINFO*)&mi)) {
ctx->color_management_status_reliable = false;
return false;
}

reliable = mp_w32_displayconfig_get_acm_status(mi.szDevice, status);

if (ctx) {
ctx->target_monitor = desc.Monitor;
ctx->color_management_status_reliable = reliable;
ctx->color_management_status.acm_enabled = status->acm_enabled;
ctx->color_management_status.hdr_enabled = status->hdr_enabled;
}

return reliable;
#else
return false;
#endif
}

struct pl_color_space mp_dxgi_desc_to_color_space(const DXGI_OUTPUT_DESC1 *desc)
{
struct pl_color_space ret = {0};
Expand Down
11 changes: 10 additions & 1 deletion video/out/gpu/d3d11_helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include <dxgidebug.h>

#include "video/mp_image.h"
#include "video/out/win32/displayconfig.h"

#if !HAVE_DXGI_DEBUG_D3D11
DEFINE_GUID(DXGI_DEBUG_D3D11, 0x4b99317b, 0xac39, 0x4aa6, 0xbb, 0xb, 0xba, 0xa0, 0x47, 0x84, 0x79, 0x8f);
Expand Down Expand Up @@ -65,8 +66,10 @@ struct d3d11_device_opts {
struct mp_dxgi_factory_ctx {
IDXGIFactory1 *factory;
IDXGIOutput6 *last_matched_output;
HMONITOR white_level_monitor;
HMONITOR target_monitor;
float sdr_white_level;
bool color_management_status_reliable;
struct mp_w32_acm_status color_management_status;
};

void mp_dxgi_factory_uninit(struct mp_dxgi_factory_ctx *ctx);
Expand All @@ -86,6 +89,12 @@ bool mp_dxgi_output_desc_from_swapchain(struct mp_dxgi_factory_ctx *ctx,
float mp_dxgi_sdr_white_level_from_hwnd(struct mp_dxgi_factory_ctx *ctx,
HWND hwnd);

// Get the status of ACM and HDR for the monitor the window is on. Returns
// false if related API is not supported.
bool mp_dxgi_get_acm_status_from_hwnd(struct mp_dxgi_factory_ctx *ctx,
HWND hwnd,
struct mp_w32_acm_status *status);

struct pl_color_space mp_dxgi_desc_to_color_space(const DXGI_OUTPUT_DESC1 *desc);

OPT_STRING_VALIDATE_FUNC(mp_dxgi_validate_adapter);
Expand Down
27 changes: 20 additions & 7 deletions video/out/vo_gpu_next.c
Original file line number Diff line number Diff line change
Expand Up @@ -1095,6 +1095,19 @@ static void update_tm_viz(struct pl_color_map_params *params,
static void update_hook_opts_dynamic(struct priv *p, const struct pl_hook *hook,
const struct mp_image *mpi);

// Currently only used by Windows implementation
#ifdef _WIN32
static bool get_global_color_management_status(struct priv *p)
{
struct ra_swapchain *sw = p->ra_ctx->swapchain;

if (sw->fns->target_global_color_management_status)
return sw->fns->target_global_color_management_status(sw);
else
return false;
}
#endif

static bool draw_frame(struct vo *vo, struct vo_frame *frame)
{
struct priv *p = vo->priv;
Expand Down Expand Up @@ -1377,18 +1390,18 @@ static bool draw_frame(struct vo *vo, struct vo_frame *frame)
#ifdef _WIN32
// Windows uses the sRGB piecewise function. Send piecewise sRGB to
// Windows in HDR mode so that it can be converted to PQ, the same way
// as mpv does internally. Note that in SDR mode, even with ACM enabled,
// Windows assumes the display is sRGB. It doesn't perform gamma
// conversion, or any conversions would roundtrip back to sRGB.
// In which case the EOTF depends on the display.
// Ideally, compositors would agree on how to handle sRGB, but I’ll
// as mpv does internally. This happens on both ACM and HDR.
// For non-HDR/ACM cases, use power 2.2 since most monitors default to
// power 2.2.
// Ideally, compositors would agree on how to handle sRGB, but I'll
// leave that part of the story for the reader to explore.
// Note: Older Windows versions, without ACM, were not able to convert
// sRGB to PQ output. We are not concerned about this case, as it would
// look wrong anyway.
bool target_pq = !target_unknown && target_csp.transfer == PL_COLOR_TRC_PQ;
if (opts->treat_srgb_as_power22 & 4 && target_pq)
target.color.transfer = PL_COLOR_TRC_SRGB;
if (opts->treat_srgb_as_power22 & 4)
if (get_global_color_management_status(p) || target_pq)
target.color.transfer = PL_COLOR_TRC_SRGB;
#endif
}
stats_time_start(p->stats, "osd-update");
Expand Down
11 changes: 11 additions & 0 deletions video/out/vulkan/context.c
Original file line number Diff line number Diff line change
Expand Up @@ -591,11 +591,22 @@ static float target_ref_luma(struct ra_swapchain *sw)
return 0;
}

static bool target_global_color_management_status(struct ra_swapchain *sw)
{
struct priv *p = sw->priv;

if (p->params.global_color_management_status)
return p->params.global_color_management_status(sw->ctx);
else
return false;
}

static const struct ra_swapchain_fns vulkan_swapchain = {
.color_depth = color_depth,
.set_color = set_color,
.target_csp = target_csp,
.target_ref_luma = target_ref_luma,
.target_global_color_management_status = target_global_color_management_status,
.start_frame = start_frame,
.submit_frame = submit_frame,
.swap_buffers = swap_buffers,
Expand Down
15 changes: 15 additions & 0 deletions video/out/vulkan/context_win.c
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,20 @@ static float preferred_ref_luma(struct ra_ctx *ctx)
return mp_dxgi_sdr_white_level_from_hwnd(&p->dxgi_ctx, vo_w32_hwnd(ctx->vo));
}

static bool global_color_management_status(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
bool reliable = false;
struct mp_w32_acm_status status = {0};

reliable = mp_dxgi_get_acm_status_from_hwnd(&p->dxgi_ctx, vo_w32_hwnd(ctx->vo), &status);

if (reliable && status.acm_enabled)
return true;
else
return false;
}

static bool win_init(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
Expand All @@ -94,6 +108,7 @@ static bool win_init(struct ra_ctx *ctx)
.color_depth = color_depth,
.preferred_csp = preferred_csp,
.preferred_ref_luma = preferred_ref_luma,
.global_color_management_status = global_color_management_status,
};

VkInstance inst = vk->vkinst->instance;
Expand Down
49 changes: 49 additions & 0 deletions video/out/win32/displayconfig.c
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,52 @@ double mp_w32_displayconfig_get_sdr_white_level(const wchar_t *device)
talloc_free(ctx);
return white_level;
}

bool mp_w32_displayconfig_get_acm_status(const wchar_t *device,
struct mp_w32_acm_status *status)
{
void *ctx = talloc_new(NULL);

bool acm_status_unknown = true;
status->acm_enabled = false;
status->hdr_enabled = false;

UINT32 num_paths;
DISPLAYCONFIG_PATH_INFO *paths;
UINT32 num_modes;
DISPLAYCONFIG_MODE_INFO *modes;

if (get_config(ctx, &num_paths, &paths, &num_modes, &modes))
goto end;

DISPLAYCONFIG_PATH_INFO* path;
if (!(path = get_path(num_paths, paths, device)))
goto end;

DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO color_info = {
.header = {
.size = sizeof(color_info),
.type = DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO,
.adapterId = path->targetInfo.adapterId,
.id = path->targetInfo.id,
}
};

if (DisplayConfigGetDeviceInfo(&color_info.header) != ERROR_SUCCESS)
goto end;

acm_status_unknown = false;
// This particular combination indicates HDR mode is enabled. This is
// undocumented but used by WinRT. When wideColorEnforced is true we are in
//SDR mode with advanced color.
// Reference:
// <https://projects.blender.org/blender/blender/src/commit/4c920e3b7fadeaac45c3d30b1beeec4e80d3883b/intern/ghost/intern/GHOST_WindowWin32.cc#L1341>
status->acm_enabled = color_info.advancedColorSupported &&
color_info.advancedColorEnabled;
status->hdr_enabled = status->acm_enabled &&
!color_info.wideColorEnforced;

end:
talloc_free(ctx);
return !acm_status_unknown;
}
11 changes: 11 additions & 0 deletions video/out/win32/displayconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@

#include <wchar.h>

// Struct describing current host OS's global color management and HDR status
struct mp_w32_acm_status {
bool acm_enabled;
bool hdr_enabled;
};

// Given a GDI monitor device name, get the precise refresh rate using the
// Windows 7 DisplayConfig API. Returns 0.0 on failure.
double mp_w32_displayconfig_get_refresh_rate(const wchar_t *device);
Expand All @@ -28,4 +34,9 @@ double mp_w32_displayconfig_get_refresh_rate(const wchar_t *device);
// in nits, using the Windows 10 DisplayConfig API. Returns 0.0 on failure.
double mp_w32_displayconfig_get_sdr_white_level(const wchar_t *device);

// Given a GDI monitor device name, get the current ACM and HDR status.
// Returns false if related API is not supported.
bool mp_w32_displayconfig_get_acm_status(const wchar_t *device,
struct mp_w32_acm_status *status);

#endif
Loading