Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions src/platform/windows/display_device/hdr_state_api.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#pragma once

#include <windows.h>

namespace display_device::hdr_state_api {

// Keep the Win32 ABI local so older Windows SDKs can still build the host.
// These requests are probed at runtime; no OS version check is needed.
struct advanced_color_info_2_t {
DISPLAYCONFIG_DEVICE_INFO_HEADER header;
UINT32 flags;
DISPLAYCONFIG_COLOR_ENCODING color_encoding;
UINT32 bits_per_color_channel;
UINT32 active_color_mode;
};

struct hdr_state_packet_t {
DISPLAYCONFIG_DEVICE_INFO_HEADER header;
UINT32 enable_hdr;
};

static_assert(sizeof(advanced_color_info_2_t) == 36);
static_assert(sizeof(hdr_state_packet_t) == 24);

struct state_t {
LONG status;
bool supported;
bool enabled;
bool dedicated_hdr_api;
};

template <class GetInfo>
state_t
query(const DISPLAYCONFIG_PATH_TARGET_INFO &target, GetInfo get_info) {
advanced_color_info_2_t info {};
info.header = { static_cast<DISPLAYCONFIG_DEVICE_INFO_TYPE>(15), sizeof(info), target.adapterId, target.id };
const LONG status = get_info(&info.header);
if (status == ERROR_SUCCESS) {
// Persist the user's HDR switch, even if policy temporarily prevents HDR
// from becoming active. WCG/ACM is an independent SDR color mode.
return { status, (info.flags & (1u << 4)) != 0, (info.flags & (1u << 5)) != 0, true };
}

// Older systems reject the newer request. Do not hide access, topology or
// driver failures by retrying them through an API with different semantics.
if (status != ERROR_INVALID_PARAMETER && status != ERROR_NOT_SUPPORTED) {
return { status, false, false, true };
}

DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO legacy {};
legacy.header = { DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO, sizeof(legacy), target.adapterId, target.id };
const LONG legacy_status = get_info(&legacy.header);
return { legacy_status, legacy.advancedColorSupported != 0, legacy.advancedColorEnabled != 0, false };
}

template <class GetInfo, class SetInfo>
LONG
set(const DISPLAYCONFIG_PATH_TARGET_INFO &target, bool enabled, GetInfo get_info, SetInfo set_info) {
const auto state = query(target, get_info);
if (state.status != ERROR_SUCCESS) {
return state.status;
}
if (!state.supported) {
return ERROR_NOT_SUPPORTED;
}

if (state.dedicated_hdr_api) {
hdr_state_packet_t packet {};
packet.header = { static_cast<DISPLAYCONFIG_DEVICE_INFO_TYPE>(16), sizeof(packet), target.adapterId, target.id };
packet.enable_hdr = enabled ? 1u : 0u;
// A failed HDR write must not fall back to changing WCG/advanced color.
return set_info(&packet.header);
}

DISPLAYCONFIG_SET_ADVANCED_COLOR_STATE legacy {};
legacy.header = { DISPLAYCONFIG_DEVICE_INFO_SET_ADVANCED_COLOR_STATE, sizeof(legacy), target.adapterId, target.id };
legacy.enableAdvancedColor = enabled ? 1u : 0u;
return set_info(&legacy.header);
}

} // namespace display_device::hdr_state_api
29 changes: 9 additions & 20 deletions src/platform/windows/display_device/windows_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
#include <SetupApi.h>
#include <wtsapi32.h>

// Keep Win32-dependent helpers after the host headers above.
#include "hdr_state_api.h"

namespace display_device::w_utils {

namespace {
Expand Down Expand Up @@ -582,34 +585,20 @@ namespace display_device::w_utils {
return hdr_state_e::unknown;
}

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

LONG result { DisplayConfigGetDeviceInfo(&color_info.header) };
if (result != ERROR_SUCCESS) {
BOOST_LOG(error) << get_error_string(result) << " failed to get advanced color info! ";
const auto state = hdr_state_api::query(path.targetInfo, DisplayConfigGetDeviceInfo);
if (state.status != ERROR_SUCCESS) {
BOOST_LOG(error) << get_error_string(state.status) << " failed to get HDR state!";
return hdr_state_e::unknown;
}

return color_info.advancedColorSupported ? color_info.advancedColorEnabled ? hdr_state_e::enabled : hdr_state_e::disabled : hdr_state_e::unknown;
return state.supported ? state.enabled ? hdr_state_e::enabled : hdr_state_e::disabled : hdr_state_e::unknown;
}

bool
set_hdr_state(const DISPLAYCONFIG_PATH_INFO &path, bool enable) {
DISPLAYCONFIG_SET_ADVANCED_COLOR_STATE color_state = {};
color_state.header.adapterId = path.targetInfo.adapterId;
color_state.header.id = path.targetInfo.id;
color_state.header.type = DISPLAYCONFIG_DEVICE_INFO_SET_ADVANCED_COLOR_STATE;
color_state.header.size = sizeof(color_state);

color_state.enableAdvancedColor = enable ? 1 : 0;

LONG result { DisplayConfigSetDeviceInfo(&color_state.header) };
const LONG result = hdr_state_api::set(path.targetInfo, enable, DisplayConfigGetDeviceInfo, DisplayConfigSetDeviceInfo);
if (result != ERROR_SUCCESS) {
BOOST_LOG(error) << get_error_string(result) << " failed to set advanced color info!";
BOOST_LOG(error) << get_error_string(result) << " failed to set HDR state!";
return false;
}

Expand Down
162 changes: 162 additions & 0 deletions tests/unit/platform/windows/test_hdr_state_api.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
#ifdef _WIN32

#include <gtest/gtest.h>

#include <vector>

#include "src/platform/windows/display_device/hdr_state_api.h"

namespace {

namespace hdr = display_device::hdr_state_api;

class HdrStateApiTest: public testing::Test {
protected:
DISPLAYCONFIG_PATH_TARGET_INFO target {};
LONG modern_status = ERROR_SUCCESS;
LONG legacy_status = ERROR_SUCCESS;
LONG write_status = ERROR_SUCCESS;
UINT32 modern_flags = 1u << 4;
UINT32 active_mode = 0;
std::vector<int> reads;
std::vector<int> writes;
bool written_enabled = false;

void
SetUp() override {
target.adapterId = { 42, 7 };
target.id = 9;
}

LONG
get(DISPLAYCONFIG_DEVICE_INFO_HEADER *header) {
EXPECT_EQ(header->adapterId.LowPart, target.adapterId.LowPart);
EXPECT_EQ(header->adapterId.HighPart, target.adapterId.HighPart);
EXPECT_EQ(header->id, target.id);
reads.push_back(static_cast<int>(header->type));
if (header->type == static_cast<DISPLAYCONFIG_DEVICE_INFO_TYPE>(15)) {
EXPECT_EQ(header->size, sizeof(hdr::advanced_color_info_2_t));
auto &info = *reinterpret_cast<hdr::advanced_color_info_2_t *>(header);
info.flags = modern_flags;
info.active_color_mode = active_mode;
return modern_status;
}
EXPECT_EQ(header->type, DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO);
EXPECT_EQ(header->size, sizeof(DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO));
auto &info = *reinterpret_cast<DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO *>(header);
info.advancedColorSupported = 1;
info.advancedColorEnabled = 1;
return legacy_status;
}

LONG
put(DISPLAYCONFIG_DEVICE_INFO_HEADER *header) {
EXPECT_EQ(header->adapterId.LowPart, target.adapterId.LowPart);
EXPECT_EQ(header->adapterId.HighPart, target.adapterId.HighPart);
EXPECT_EQ(header->id, target.id);
writes.push_back(static_cast<int>(header->type));
if (header->type == static_cast<DISPLAYCONFIG_DEVICE_INFO_TYPE>(16)) {
EXPECT_EQ(header->size, sizeof(hdr::hdr_state_packet_t));
written_enabled = reinterpret_cast<hdr::hdr_state_packet_t *>(header)->enable_hdr != 0;
}
else {
EXPECT_EQ(header->type, DISPLAYCONFIG_DEVICE_INFO_SET_ADVANCED_COLOR_STATE);
written_enabled = reinterpret_cast<DISPLAYCONFIG_SET_ADVANCED_COLOR_STATE *>(header)->enableAdvancedColor != 0;
}
return write_status;
}

hdr::state_t
query() {
return hdr::query(target, [this](auto *header) { return get(header); });
}

LONG
set(bool enabled) {
return hdr::set(target, enabled, [this](auto *header) { return get(header); }, [this](auto *header) { return put(header); });
}
};

TEST_F(HdrStateApiTest, AcmWithoutHdrIsNotSavedAsHdr) {
modern_flags |= (1u << 1) | (1u << 6) | (1u << 7);
active_mode = 1; // WCG, with advanced color active and ACM enabled.
const auto state = query();
ASSERT_EQ(state.status, ERROR_SUCCESS);
EXPECT_TRUE(state.supported);
EXPECT_FALSE(state.enabled);
EXPECT_EQ(reads, std::vector<int>({ 15 }));
// Applying/restoring HDR-off never writes the advanced-color/WCG switch.
EXPECT_EQ(set(state.enabled), ERROR_SUCCESS);
EXPECT_EQ(writes, std::vector<int>({ 16 }));
EXPECT_FALSE(written_enabled);
}

TEST_F(HdrStateApiTest, PreservesHdrUserSwitchEvenWhenHdrIsTemporarilyInactive) {
modern_flags |= (1u << 3) | (1u << 5); // Policy-limited, user HDR on.
active_mode = 0;
EXPECT_TRUE(query().enabled);
EXPECT_EQ(set(true), ERROR_SUCCESS);
EXPECT_TRUE(written_enabled);
EXPECT_EQ(writes, std::vector<int>({ 16 }));
}

TEST_F(HdrStateApiTest, HdrAndAcmCanBothBeEnabled) {
modern_flags |= (1u << 1) | (1u << 5) | (1u << 6) | (1u << 7);
active_mode = 2;
const auto state = query();
EXPECT_TRUE(state.supported);
EXPECT_TRUE(state.enabled);
EXPECT_EQ(set(state.enabled), ERROR_SUCCESS);
EXPECT_EQ(writes, std::vector<int>({ 16 }));
EXPECT_TRUE(written_enabled);
}

TEST_F(HdrStateApiTest, WcgOnlyDisplayDoesNotFallBackOrWriteHdr) {
modern_flags = (1u << 0) | (1u << 1) | (1u << 6) | (1u << 7);
EXPECT_FALSE(query().supported);
EXPECT_EQ(set(true), ERROR_NOT_SUPPORTED);
EXPECT_EQ(reads, std::vector<int>({ 15, 15 }));
EXPECT_TRUE(writes.empty());
}

TEST_F(HdrStateApiTest, OlderSystemsRetainLegacyQueryAndSet) {
for (const LONG status : { ERROR_INVALID_PARAMETER, ERROR_NOT_SUPPORTED }) {
modern_status = status;
reads.clear();
writes.clear();
EXPECT_TRUE(query().enabled);
EXPECT_EQ(set(false), ERROR_SUCCESS);
EXPECT_FALSE(written_enabled);
EXPECT_EQ(reads, std::vector<int>({ 15, 9, 15, 9 }));
EXPECT_EQ(writes, std::vector<int>({ 10 }));
}
}

TEST_F(HdrStateApiTest, QueryFailuresAreNotMaskedByLegacyFallback) {
for (const LONG status : { ERROR_ACCESS_DENIED, ERROR_GEN_FAILURE, ERROR_INSUFFICIENT_BUFFER }) {
modern_status = status;
reads.clear();
EXPECT_EQ(query().status, status);
EXPECT_EQ(set(true), status);
EXPECT_EQ(reads, std::vector<int>({ 15, 15 }));
EXPECT_TRUE(writes.empty());
}
modern_status = ERROR_INVALID_PARAMETER;
legacy_status = ERROR_GEN_FAILURE;
EXPECT_EQ(query().status, ERROR_GEN_FAILURE);
EXPECT_EQ(set(false), ERROR_GEN_FAILURE);
EXPECT_TRUE(writes.empty());
}

TEST_F(HdrStateApiTest, FailedHdrWritesNeverFallBackToAdvancedColor) {
for (const LONG status : { ERROR_NOT_SUPPORTED, ERROR_INVALID_PARAMETER, ERROR_ACCESS_DENIED }) {
write_status = status;
writes.clear();
EXPECT_EQ(set(true), status);
EXPECT_EQ(writes, std::vector<int>({ 16 }));
}
}

} // namespace

#endif
Loading