From 77d6b754134177bb76354b4b3bbf66878f306865 Mon Sep 17 00:00:00 2001 From: Kaldaien Date: Wed, 27 Nov 2024 17:54:41 -0500 Subject: [PATCH 1/3] Add reshade::api::display, an AddOn event to be notified of display changes, and HDR mastering metadata support to sk_hdr_png --- ReShade.vcxproj | 3 +- ReShade.vcxproj.filters | 9 + deps/sk_hdr_png/include/sk_hdr_png.hpp | 14 +- include/reshade_api.hpp | 7 + include/reshade_api_device.hpp | 1 + include/reshade_api_display.hpp | 153 ++++++++++++ include/reshade_events.hpp | 10 +- source/dxgi/dxgi_impl_display.cpp | 317 +++++++++++++++++++++++++ source/dxgi/dxgi_impl_display.hpp | 132 ++++++++++ source/input.cpp | 9 + source/runtime.cpp | 84 ++++++- source/runtime.hpp | 9 + 12 files changed, 736 insertions(+), 12 deletions(-) create mode 100644 include/reshade_api_display.hpp create mode 100644 source/dxgi/dxgi_impl_display.cpp create mode 100644 source/dxgi/dxgi_impl_display.hpp diff --git a/ReShade.vcxproj b/ReShade.vcxproj index ac5adbd1c2..f84b4e01c7 100644 --- a/ReShade.vcxproj +++ b/ReShade.vcxproj @@ -100,7 +100,6 @@ - tools;$(ExecutablePath) @@ -600,6 +599,7 @@ + @@ -718,6 +718,7 @@ + diff --git a/ReShade.vcxproj.filters b/ReShade.vcxproj.filters index a66812e24d..395a56bd10 100644 --- a/ReShade.vcxproj.filters +++ b/ReShade.vcxproj.filters @@ -79,6 +79,9 @@ {9232c43b-559d-4435-b309-c8bbf4e6477b} + + {e8eebdc2-4ab7-47d5-b053-e2dda568614e} + @@ -423,6 +426,9 @@ hooks\windows + + api\dxgi + @@ -722,6 +728,9 @@ api\vulkan + + api\dxgi + diff --git a/deps/sk_hdr_png/include/sk_hdr_png.hpp b/deps/sk_hdr_png/include/sk_hdr_png.hpp index 3d21afdf74..2f5fd1835b 100644 --- a/deps/sk_hdr_png/include/sk_hdr_png.hpp +++ b/deps/sk_hdr_png/include/sk_hdr_png.hpp @@ -25,7 +25,7 @@ #pragma once #include "reshade_api.hpp" -//#include "reshade_api_display.hpp" +#include "reshade_api_display.hpp" #include #include @@ -310,8 +310,8 @@ namespace sk_hdr_png }; }; - bool write_image_to_disk (const wchar_t* image_path, unsigned int width, unsigned int height, const void* pixels, int quantization_bits, format fmt, bool use_clipboard);//, reshade::api::display* display); - bool write_hdr_chunks (const wchar_t* image_path, unsigned int width, unsigned int height, const float* luminance_array, int quantization_bits);//, reshade::api::display* display); + bool write_image_to_disk (const wchar_t* image_path, unsigned int width, unsigned int height, const void* pixels, int quantization_bits, format fmt, bool use_clipboard, reshade::api::display* display); + bool write_hdr_chunks (const wchar_t* image_path, unsigned int width, unsigned int height, const float* luminance_array, int quantization_bits, reshade::api::display* display); cLLi_Payload calculate_content_light_info (const float* luminance, unsigned int width, unsigned int height); bool copy_to_clipboard (const wchar_t* image_path); bool remove_chunk (const char* chunk_name, void* data, size_t& size); @@ -586,7 +586,7 @@ sk_hdr_png::remove_chunk (const char* chunk_name, void* data, size_t& size) } bool -sk_hdr_png::write_hdr_chunks (const wchar_t* image_path, unsigned int width, unsigned int height, const float* luminance, int quantization_bits)//, reshade::api::display* display) +sk_hdr_png::write_hdr_chunks (const wchar_t* image_path, unsigned int width, unsigned int height, const float* luminance, int quantization_bits, reshade::api::display* display) { if (image_path == nullptr || width == 0 || height == 0 || quantization_bits < 6) { @@ -706,7 +706,6 @@ sk_hdr_png::write_hdr_chunks (const wchar_t* image_path, unsigned int width, uns sbit_chunk.write(fPNG); chrm_chunk.write(fPNG); -#if 0 /// /// Mastering metadata can be added, provided you are able to read this info /// from the user's EDID. @@ -744,7 +743,6 @@ sk_hdr_png::write_hdr_chunks (const wchar_t* image_path, unsigned int width, uns Chunk mdcv_chunk = {sizeof(mdcv_data), {'m','D','C','v'}, &mdcv_data}; mdcv_chunk.write(fPNG); } -#endif // Write the remainder of the original file fwrite(insert_ptr, size - insert_pos, 1, fPNG); @@ -814,7 +812,7 @@ sk_hdr_png::copy_to_clipboard (const wchar_t* image_path) } bool -sk_hdr_png::write_image_to_disk (const wchar_t* image_path, unsigned int width, unsigned int height, const void* pixels, int quantization_bits, format fmt, bool use_clipboard)//, reshade::api::display* display) +sk_hdr_png::write_image_to_disk (const wchar_t* image_path, unsigned int width, unsigned int height, const void* pixels, int quantization_bits, format fmt, bool use_clipboard, reshade::api::display* display) { using namespace DirectX; using namespace DirectX::PackedVector; @@ -988,7 +986,7 @@ sk_hdr_png::write_image_to_disk (const wchar_t* image_path, unsigned int width, if (SUCCEEDED(hr)) hr = encoder->Commit(); if (SUCCEEDED(hr)) { - hr = write_hdr_chunks(image_path, width, height, luminance, quantization_bits/*, display*/) ? S_OK : E_FAIL; + hr = write_hdr_chunks(image_path, width, height, luminance, quantization_bits, display) ? S_OK : E_FAIL; if (SUCCEEDED(hr)) { diff --git a/include/reshade_api.hpp b/include/reshade_api.hpp index 15f5ba7cb3..06d878d6cb 100644 --- a/include/reshade_api.hpp +++ b/include/reshade_api.hpp @@ -31,6 +31,8 @@ namespace reshade { namespace api /// RESHADE_DEFINE_HANDLE(effect_uniform_variable); + struct display; + /// /// Input source for events triggered by user input. /// @@ -806,5 +808,10 @@ namespace reshade { namespace api /// /// File name of the effect file that should be reloaded. virtual void reload_effect_next_frame(const char *effect_name) = 0; + + /// + /// Gets the active display, which may change between frames or return nullptr if the swapchain is offscreen. + /// + virtual display* get_active_display() const = 0; }; } } diff --git a/include/reshade_api_device.hpp b/include/reshade_api_device.hpp index 3deed53323..df9de88d46 100644 --- a/include/reshade_api_device.hpp +++ b/include/reshade_api_device.hpp @@ -250,6 +250,7 @@ namespace reshade { namespace api /// For this will be a pointer to a 'ID3D11DeviceContext' (when recording), 'ID3D11CommandList' (when executing) or 'ID3D12GraphicsCommandList' object or a 'VkCommandBuffer' handle.
/// For this will be a pointer to a 'ID3D11DeviceContext' or 'ID3D12CommandQueue' object or a 'VkQueue' handle.
/// For this will be a pointer to a 'IDirect3DSwapChain9' or 'IDXGISwapChain' object or a 'HDC' or 'VkSwapchainKHR' handle. + /// For this will be a pointer to a 'IDXGIOutput'; DXGI is used even for runtimes that do not have a DXGI-based swapchain. /// virtual uint64_t get_native() const = 0; diff --git a/include/reshade_api_display.hpp b/include/reshade_api_display.hpp new file mode 100644 index 0000000000..3f8ed4eb11 --- /dev/null +++ b/include/reshade_api_display.hpp @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2024 Patrick Mours + * SPDX-License-Identifier: BSD-3-Clause OR MIT + */ + +#pragma once + +#include "reshade_api_device.hpp" +#include +#include + +namespace reshade { namespace api +{ + /// + /// Describes quantities defined precisely as the ratio of two integers, such as refresh rates. + /// + struct rational + { + uint32_t numerator = 0; + uint32_t denomenator = 0; + + constexpr float as_float() const { return denomenator != 0 ? static_cast (numerator)/static_cast(denomenator) : 0.0f; } + }; + + /// + /// Describes the colorimetry of a colorspace. + /// + struct colorimetry + { + float red [2] = { 0.0f, 0.0f }; + float green [2] = { 0.0f, 0.0f }; + float blue [2] = { 0.0f, 0.0f }; + float white [2] = { 0.0f, 0.0f }; + }; + + /// + /// Describes the dynamic range of a display or image. + /// + struct luminance_levels + { + float min_nits = 0.0f; + float max_nits = 0.0f; + float max_avg_nits = 0.0f; + }; + + /// + /// An output display. + /// Functionally equivalent to a 'IDXGIOutput'. + /// + struct __declspec(novtable) display : public api_object + { + /// + /// Indicates if cached properties are valid or potentially stale (i.e. refresh rate or resolution have changed). + /// + /// + /// If this returns false, wait one frame and call runtime::get_active_display() to get updated data. + /// + virtual bool is_current() const = 0; + + using monitor = void*; + + /// + /// Gets the handle of the monitor this display encapsulates. + /// + virtual monitor get_monitor() const = 0; + + /// + /// Gets the (GDI) device name (i.e. \\DISPLAY1) of the the monitor; do not use this as a persistent display identifier! + /// + /// + /// This device name is not valid as a persistent display identifier for storage in configuration files, it may not refer to the same display device after a reboot. + /// + virtual const wchar_t* get_device_name() const = 0; + + /// + /// Gets the device path (i.e. \\?\DISPLAY#DELA1E4#5&d93f871&0&UID33025#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}) of the the monitor. + /// + /// + /// The device path uniquely identifies a specific monitor (down to its serial number and the port it is attached to) and remains valid across system reboots. + /// This identifier is suitable for persistent user-defined display configuration, partial name matches can be used to identify instances of the same monitor when the port it attaches to is unimportant. + /// + virtual const wchar_t* get_device_path() const = 0; + + /// + /// Gets the human readable name (i.e. Dell AW3423DW) of the the monitor. + /// + virtual const wchar_t* get_display_name() const = 0; + + /// + /// Gets the size and location of the display on the desktop. + /// + virtual rect get_desktop_coords() const = 0; + + /// + /// Gets the current color depth of the display. + /// + virtual uint32_t get_color_depth() const = 0; + + /// + /// Gets the current refresh rate of the display. + /// + virtual rational get_refresh_rate() const = 0; + + /// + /// Gets the current color space used for desktop composition. + /// + /// + /// This is independent from swap chain colorspace, it identifies a display as HDR capable even when not rendering in HDR. + /// + virtual color_space get_color_space() const = 0; + + /// + /// Gets the display's reported native colorimetry (the data provided are often invalid or placeholders). + /// + /// + /// Users running Windows 11 or newer are encouraged to run "Windows HDR Calibration" to ensure the values reported to ReShade and games are accurate. + /// + virtual colorimetry get_colorimetry() const = 0; + + /// + /// Gets the display's light output capabilities (the data provided are often invalid or placeholders). + /// + /// + /// Users running Windows 11 or newer are encouraged to run "Windows HDR Calibration" to ensure the values reported to ReShade and games are accurate. + /// + virtual luminance_levels get_luminance_caps() const = 0; + + /// + /// Gets the desktop compositor's whitelevel for mapping SDR content to HDR. + /// + virtual float get_sdr_white_nits() const = 0; + + /// + /// Checks if HDR is supported on the display, even if it is not currently enabled. + /// + virtual bool is_hdr_supported() const = 0; + + /// + /// Checks if HDR is enabled on the display. + /// + virtual bool is_hdr_enabled() const = 0; + + /// + /// Enables HDR on the display. + /// + /// + /// Be aware that this is a global display setting, and will not automatically revert to its original state when the application exits. + /// + virtual bool enable_hdr(bool enable) = 0; + }; + + using display_cache = std::unordered_map>; +} } diff --git a/include/reshade_events.hpp b/include/reshade_events.hpp index e34777d1cb..964fe6575d 100644 --- a/include/reshade_events.hpp +++ b/include/reshade_events.hpp @@ -1705,8 +1705,14 @@ namespace reshade /// reshade_overlay_technique, + /// + /// Called when the active display changes for a runtime. + /// Callback function signature: void (api::effect_runtime *runtime, api::display *display) + /// + display_change = 95, + #if RESHADE_ADDON - max = 95 // Last value used internally by ReShade to determine number of events in this enum + max = 96 // Last value used internally by ReShade to determine number of events in this enum #endif }; @@ -1848,4 +1854,6 @@ namespace reshade RESHADE_DEFINE_ADDON_EVENT_TRAITS(addon_event::reshade_overlay_uniform_variable, bool, api::effect_runtime *runtime, api::effect_uniform_variable variable); RESHADE_DEFINE_ADDON_EVENT_TRAITS(addon_event::reshade_overlay_technique, bool, api::effect_runtime *runtime, api::effect_technique technique); + + RESHADE_DEFINE_ADDON_EVENT_TRAITS(addon_event::display_change, void, api::effect_runtime *runtime, api::display *display); } diff --git a/source/dxgi/dxgi_impl_display.cpp b/source/dxgi/dxgi_impl_display.cpp new file mode 100644 index 0000000000..9146b589df --- /dev/null +++ b/source/dxgi/dxgi_impl_display.cpp @@ -0,0 +1,317 @@ +/* + * Copyright (C) 2024 Patrick Mours + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "dxgi_impl_display.hpp" + +reshade::dxgi::display_impl::display_impl(IDXGIOutput6 *output, DISPLAYCONFIG_PATH_TARGET_INFO *target) : + api_object_impl(output) +{ + assert(output != nullptr && target != nullptr); + + if (output == nullptr || target == nullptr) + return; + + _target_info = *target; + + com_ptr adapter; + com_ptr factory; + + if (SUCCEEDED(output->GetParent(IID_IDXGIAdapter,reinterpret_cast(&adapter))) && + SUCCEEDED(adapter->GetParent(IID_IDXGIFactory,reinterpret_cast(&factory)))) + { + factory->QueryInterface(&_parent); + } + + _output = output; + output->GetDesc1(&_desc); + + _refresh_rate = { + _target_info.refreshRate.Numerator, + _target_info.refreshRate.Denominator + }; + + DISPLAYCONFIG_TARGET_DEVICE_NAME + target_name = {}; + target_name.header.adapterId = _target_info.adapterId; + target_name.header.id = _target_info.id; + target_name.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME; + target_name.header.size = sizeof (DISPLAYCONFIG_TARGET_DEVICE_NAME); + + if (ERROR_SUCCESS == DisplayConfigGetDeviceInfo(&target_name.header)) + { + _name = target_name.monitorFriendlyDeviceName; + _path = target_name.monitorDevicePath; + } + + DISPLAYCONFIG_SDR_WHITE_LEVEL + sdr_white_level = {}; + sdr_white_level.header.adapterId = _target_info.adapterId; + sdr_white_level.header.id = _target_info.id; + sdr_white_level.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SDR_WHITE_LEVEL; + sdr_white_level.header.size = sizeof (DISPLAYCONFIG_SDR_WHITE_LEVEL); + + if (ERROR_SUCCESS == DisplayConfigGetDeviceInfo(&sdr_white_level.header)) + { + _sdr_nits = (static_cast(sdr_white_level.SDRWhiteLevel) / 1000.0f) * 80.0f; + } + +#if (NTDDI_VERSION >= NTDDI_WIN11_GA) + // Prefer this API if the build environment and current OS support it, it can distinguish WCG from HDR + DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO_2 + get_color_info2 = {}; + get_color_info2.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO_2; + get_color_info2.header.size = sizeof (DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO_2); + get_color_info2.header.adapterId = _target_info.adapterId; + get_color_info2.header.id = _target_info.id; + + if (ERROR_SUCCESS == DisplayConfigGetDeviceInfo(&get_color_info2.header)) + { + _hdr_supported = get_color_info2.highDynamicRangeSupported && !get_color_info2.advancedColorLimitedByPolicy; + _hdr_enabled = get_color_info2.advancedColorActive && get_color_info2.highDynamicRangeUserEnabled && get_color_info2.activeColorMode == DISPLAYCONFIG_ADVANCED_COLOR_MODE_HDR; + } + + else +#endif + { + DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO + get_color_info = {}; + get_color_info.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO; + get_color_info.header.size = sizeof (DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO); + get_color_info.header.adapterId = _target_info.adapterId; + get_color_info.header.id = _target_info.id; + + if (ERROR_SUCCESS == DisplayConfigGetDeviceInfo(&get_color_info.header)) + { + _hdr_supported = get_color_info.advancedColorSupported; + _hdr_enabled = get_color_info.advancedColorEnabled; + } + } +}; + +const wchar_t* reshade::dxgi::display_impl::get_device_name() const +{ + return _desc.DeviceName; +}; + +const wchar_t* reshade::dxgi::display_impl::get_device_path() const +{ + return _path.c_str(); +}; + +const wchar_t* reshade::dxgi::display_impl::get_display_name() const +{ + return _name.c_str(); +}; + +reshade::api::display::monitor reshade::dxgi::display_impl::get_monitor() const +{ + return _desc.Monitor; +}; + +reshade::api::rect reshade::dxgi::display_impl::get_desktop_coords() const +{ + return { _desc.DesktopCoordinates.left, _desc.DesktopCoordinates.top, + _desc.DesktopCoordinates.right, _desc.DesktopCoordinates.bottom }; +}; + +uint32_t reshade::dxgi::display_impl::get_color_depth() const +{ + return _desc.BitsPerColor; +}; + +reshade::api::rational reshade::dxgi::display_impl::get_refresh_rate() const +{ + return _refresh_rate; +}; + +reshade::api::color_space reshade::dxgi::display_impl::get_color_space() const +{ + switch (_desc.ColorSpace) + { + case DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709: + return api::color_space::srgb_nonlinear; + case DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709: + return api::color_space::extended_srgb_linear; + case DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020: + return api::color_space::hdr10_st2084; + case DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P2020: + return api::color_space::hdr10_hlg; + default: + return api::color_space::unknown; + } +}; + +reshade::api::colorimetry reshade::dxgi::display_impl::get_colorimetry() const +{ + return api::colorimetry { + _desc.RedPrimary[0], _desc.RedPrimary[1], + _desc.GreenPrimary[0], _desc.GreenPrimary[1], + _desc.BluePrimary[0], _desc.BluePrimary[1], + _desc.WhitePoint[0], _desc.WhitePoint[1] + }; +}; + +reshade::api::luminance_levels reshade::dxgi::display_impl::get_luminance_caps() const +{ + return api::luminance_levels { + _desc.MinLuminance, + _desc.MaxLuminance, + _desc.MaxFullFrameLuminance + }; +}; + +float reshade::dxgi::display_impl::get_sdr_white_nits() const +{ + return _sdr_nits; +} + +bool reshade::dxgi::display_impl::is_current() const +{ + return _parent->IsCurrent(); +} + +bool reshade::dxgi::display_impl::is_hdr_supported() const +{ + return _hdr_supported; +} + +bool reshade::dxgi::display_impl::is_hdr_enabled() const +{ + return _hdr_enabled; +} + +bool reshade::dxgi::display_impl::enable_hdr(bool enable) +{ + if (!_hdr_supported) + return false; + +#if (NTDDI_VERSION >= NTDDI_WIN11_GA) + // Prefer this API if the build environment and current OS support it, it can distinguish WCG from HDR + DISPLAYCONFIG_SET_HDR_STATE + set_hdr_state = {}; + set_hdr_state.header.type = DISPLAYCONFIG_DEVICE_INFO_SET_HDR_STATE; + set_hdr_state.header.size = sizeof (DISPLAYCONFIG_SET_HDR_STATE); + set_hdr_state.header.adapterId = _target_info.adapterId; + set_hdr_state.header.id = _target_info.id; + + set_hdr_state.enableHdr = enable; + + if (ERROR_SUCCESS == DisplayConfigSetDeviceInfo(&set_hdr_state.header)) + { + _hdr_enabled = set_hdr_state.enableHdr; + return true; + } + + else +#endif + { + DISPLAYCONFIG_SET_ADVANCED_COLOR_STATE + set_advanced_color_state = {}; + set_advanced_color_state.header.type = DISPLAYCONFIG_DEVICE_INFO_SET_ADVANCED_COLOR_STATE; + set_advanced_color_state.header.size = sizeof (DISPLAYCONFIG_SET_ADVANCED_COLOR_STATE); + set_advanced_color_state.header.adapterId = _target_info.adapterId; + set_advanced_color_state.header.id = _target_info.id; + + set_advanced_color_state.enableAdvancedColor = enable; + + if (ERROR_SUCCESS == DisplayConfigSetDeviceInfo(&set_advanced_color_state.header)) + { + _hdr_enabled = set_advanced_color_state.enableAdvancedColor; + return true; + } + } + + return false; +} + +void reshade::dxgi::display_impl::flush_cache(reshade::api::display_cache& displays) +{ + // Detect spurious cache flushes to eliminate sending false display change events to add-ons. + bool flush_required = displays.empty(); + if (!flush_required) + { + for (const auto &display : displays) + if (flush_required = !display.second->is_current()) + break; + + // If nothing has actually changed, then we are done here... + if (!flush_required) + return; + } + + displays.clear(); + + std::vector paths; + std::vector modes; + UINT32 flags = QDC_ONLY_ACTIVE_PATHS | QDC_VIRTUAL_MODE_AWARE | QDC_VIRTUAL_REFRESH_RATE_AWARE; + LONG result = ERROR_SUCCESS; + + do + { + UINT32 pathCount, modeCount; + if (GetDisplayConfigBufferSizes(flags, &pathCount, &modeCount) != ERROR_SUCCESS) + break; + + paths.resize(pathCount); + modes.resize(modeCount); + + result = QueryDisplayConfig(flags, &pathCount, paths.data(), &modeCount, modes.data(), nullptr); + + paths.resize(pathCount); + modes.resize(modeCount); + } while (result == ERROR_INSUFFICIENT_BUFFER); + + const auto CreateDXGIFactory1 = reinterpret_cast( + GetProcAddress(GetModuleHandleW(L"dxgi.dll"), "CreateDXGIFactory1")); + + if (CreateDXGIFactory1 == nullptr) + return; + + com_ptr factory; + CreateDXGIFactory1(IID_IDXGIFactory, reinterpret_cast(&factory)); + + if (factory != nullptr) + { + com_ptr adapter; + UINT adapter_idx = 0; + while (SUCCEEDED(factory->EnumAdapters(adapter_idx++, &adapter))) + { + com_ptr output; + UINT output_idx = 0; + while (SUCCEEDED(adapter->EnumOutputs(output_idx++, &output))) + { + com_ptr output6; + if (SUCCEEDED(output->QueryInterface(&output6))) + { + DXGI_OUTPUT_DESC1 desc = {}; + output6->GetDesc1(&desc); + + for (auto &path : paths) + { + DISPLAYCONFIG_SOURCE_DEVICE_NAME + source_name = {}; + source_name.header.adapterId = path.sourceInfo.adapterId; + source_name.header.id = path.sourceInfo.id; + source_name.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME; + source_name.header.size = sizeof (DISPLAYCONFIG_SOURCE_DEVICE_NAME); + + if (DisplayConfigGetDeviceInfo(&source_name.header) != ERROR_SUCCESS) + continue; + + if (_wcsicmp(desc.DeviceName, source_name.viewGdiDeviceName)) + continue; + + displays.emplace(desc.Monitor, + std::make_unique(output6.get(),&path.targetInfo)); + + break; + } + } + output.reset(); + } + adapter.reset(); + } + } +} diff --git a/source/dxgi/dxgi_impl_display.hpp b/source/dxgi/dxgi_impl_display.hpp new file mode 100644 index 0000000000..a24f4b4e89 --- /dev/null +++ b/source/dxgi/dxgi_impl_display.hpp @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2024 Patrick Mours + * SPDX-License-Identifier: BSD-3-Clause + */ + +#pragma once + +#include +#include +#include "com_ptr.hpp" +#include "reshade_api_object_impl.hpp" +#include "reshade_api_display.hpp" + +namespace reshade::dxgi +{ + class display_impl : public api::api_object_impl + { + public: + explicit display_impl(IDXGIOutput6 *output, DISPLAYCONFIG_PATH_TARGET_INFO *target); + + /// + /// Indicates if cached properties are valid or potentially stale (i.e. refresh rate or resolution have changed). + /// + /// + /// If this returns false, wait one frame and call runtime::get_active_display() to get updated data. + /// + virtual bool is_current() const final; + + /// + /// Gets the handle of the monitor this display encapsulates. + /// + virtual monitor get_monitor() const final; + + /// + /// Gets the (GDI) device name (i.e. \\DISPLAY1) of the the monitor; do not use this as a persistent display identifier! + /// + /// + /// This device name is not valid as a persistent display identifier for storage in configuration files, it may not refer to the same display device after a reboot. + /// + virtual const wchar_t* get_device_name() const final; + + /// + /// Gets the device path (i.e. \\?\DISPLAY#DELA1E4#5&d93f871&0&UID33025#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}) of the the monitor. + /// + /// + /// The device path uniquely identifies a specific monitor (down to its serial number and the port it is attached to) and remains valid across system reboots. + /// This identifier is suitable for persistent user-defined display configuration, partial name matches can be used to identify instances of the same monitor when the port it attaches to is unimportant. + /// + virtual const wchar_t* get_device_path() const final; + + /// + /// Gets the human readable name (i.e. Dell AW3423DW) of the the monitor. + /// + virtual const wchar_t* get_display_name() const final; + + /// + /// Gets the size and location of the display on the desktop. + /// + virtual api::rect get_desktop_coords() const final; + + /// + /// Gets the current color depth of the display. + /// + virtual uint32_t get_color_depth() const final; + + /// + /// Gets the current refresh rate of the display. + /// + virtual api::rational get_refresh_rate() const final; + + /// + /// Gets the current color space used for desktop composition. + /// + /// + /// This is independent from swap chain colorspace, it identifies a display as HDR capable even when not rendering in HDR. + /// + virtual api::color_space get_color_space() const final; + + /// + /// Gets the display's reported native colorimetry (the data provided are often invalid or placeholders). + /// + /// + /// Users running Windows 11 or newer are encouraged to run "Windows HDR Calibration" to ensure the values reported to ReShade and games are accurate. + /// + virtual api::colorimetry get_colorimetry() const final; + + /// + /// Gets the display's light output capabilities (the data provided are often invalid or placeholders). + /// + /// + /// Users running Windows 11 or newer are encouraged to run "Windows HDR Calibration" to ensure the values reported to ReShade and games are accurate. + /// + virtual api::luminance_levels get_luminance_caps() const final; + + /// + /// Gets the desktop compositor's whitelevel for mapping SDR content to HDR. + /// + virtual float get_sdr_white_nits() const final; + + /// + /// Checks if HDR is supported on the display, even if it is not currently enabled. + /// + virtual bool is_hdr_supported() const final; + + /// + /// Checks if HDR is enabled on the display. + /// + virtual bool is_hdr_enabled() const final; + + /// + /// Enables HDR on the display. + /// + /// + /// Be aware that this is a global display setting, and will not revert to its original state when the application exits. + /// + virtual bool enable_hdr(bool enable) final; + + static void flush_cache(reshade::api::display_cache& displays); + + protected: + DISPLAYCONFIG_PATH_TARGET_INFO _target_info = {}; + com_ptr _parent; + com_ptr _output; + DXGI_OUTPUT_DESC1 _desc; + std::wstring _name = L""; + std::wstring _path = L""; + bool _hdr_supported = false; + bool _hdr_enabled = false; + float _sdr_nits = 0.0f; + api::rational _refresh_rate = {0}; + }; +}; diff --git a/source/input.cpp b/source/input.cpp index ef440c121c..bede8aadad 100644 --- a/source/input.cpp +++ b/source/input.cpp @@ -24,6 +24,8 @@ static std::chrono::high_resolution_clock::time_point s_last_cursor_warp = {}; static std::atomic s_block_mouse = false; static std::atomic s_block_keyboard = false; static std::atomic s_block_cursor_warping = false; +static std::atomic_uint32_t s_last_desktop_change = 1; // All runtimes are initialized to 0, and will establish a desktop cache on the first frame drawn +bool is_desktop_current(std::atomic_uint32_t& timestamp) { return std::atomic_exchange(×tamp,s_last_desktop_change) >= s_last_desktop_change; } reshade::input::input(window_handle window) : _window(window) @@ -83,6 +85,13 @@ bool reshade::input::handle_window_message(const void *message_data) MSG details = *static_cast(message_data); + // The desktop display topology has changed, desktop runtimes should invalidate cached display properties + if (details.message == WM_DISPLAYCHANGE) + { + s_last_desktop_change = std::max(s_last_desktop_change.load(), static_cast(details.time)); + return false; + } + bool is_mouse_message = details.message >= WM_MOUSEFIRST && details.message <= WM_MOUSELAST; bool is_keyboard_message = details.message >= WM_KEYFIRST && details.message <= WM_KEYLAST; diff --git a/source/runtime.cpp b/source/runtime.cpp index 7803d67b52..e85b5f9125 100644 --- a/source/runtime.cpp +++ b/source/runtime.cpp @@ -34,7 +34,8 @@ #include #include #include -#include +#include +#include <../deps/sk_hdr_png/include/sk_hdr_png.hpp> // Something broke and now it has to be included this way bool resolve_path(std::filesystem::path &path, std::error_code &ec) { @@ -474,6 +475,9 @@ bool reshade::runtime::on_init() invoke_addon_event(this); #endif + if (_containing_output == nullptr) + on_display_change(); + log::message(log::level::info, "Recreated runtime environment on runtime %p ('%s').", this, _config_path.u8string().c_str()); return true; @@ -943,6 +947,82 @@ void reshade::runtime::on_present(api::command_queue *present_queue) if (std::numeric_limits::max() != g_network_traffic) g_network_traffic = 0; #endif + + HWND window = static_cast(_swapchain->get_hwnd()); + if (window != 0) + { + extern bool is_desktop_current(std::atomic_uint32_t& timestamp); + if (!is_desktop_current(_last_desktop_change) || (_containing_output == nullptr || !_containing_output->is_current())) + { + // Flush the entire desktop display cache + on_display_change(); + } + + RECT window_rect = {}; + + if ((GetWindowRect(window,&window_rect) && + ((std::exchange(_last_desktop_x,window_rect.left) != window_rect.left) | + (std::exchange(_last_desktop_y,window_rect.top) != window_rect.top))) + || _containing_output == nullptr) + { + const auto monitor = MonitorFromWindow(window,MONITOR_DEFAULTTONEAREST); + + if (monitor != nullptr && _displays.count(monitor)) + { + if (_containing_output == nullptr || _containing_output->get_monitor() != monitor) + { + // The monitor the window is attached to has changed + on_display_change(); + } + } + } + } +} + +void reshade::runtime::on_display_change() +{ + const bool flush = _displays.empty() || (_containing_output != nullptr && !_containing_output->is_current()); + + auto original_output = std::exchange(_containing_output, nullptr); + + if (flush) + { + reshade::dxgi::display_impl::flush_cache(_displays); + } + + if (HWND window = reinterpret_cast(get_hwnd())) + { + api::display::monitor monitor {MonitorFromWindow(window,MONITOR_DEFAULTTONEAREST)}; + if (_displays.count(monitor)) + { + RECT window_rect = {}; + if (GetWindowRect(window,&window_rect)) + { + _last_desktop_x = window_rect.left; + _last_desktop_y = window_rect.top; + } + + if (std::exchange(_containing_output,_displays[monitor].get()) != original_output) + { +#if RESHADE_ADDON + invoke_addon_event(this,_containing_output); +#endif + +#if RESHADE_VERBOSE_LOG + auto& luminance_caps = _containing_output->get_luminance_caps(); + + log::message(log::level::info, "Swap chain is displaying at %5.2f Hz on '%ws' (%ws) - MinNits = %3.1f, MaxNits = %3.1f, MaxAvgNits = %3.1f.", + _containing_output->get_refresh_rate().as_float(), _containing_output->get_display_name(), _containing_output->get_device_name(), + luminance_caps.min_nits, luminance_caps.max_nits, luminance_caps.max_avg_nits); +#endif + } + } + } + + else + { + log::message(log::level::warning, "Unable to map runtime %p's swap chain to a containing display output!", this); + } } void reshade::runtime::load_config() @@ -4877,7 +4957,7 @@ void reshade::runtime::save_screenshot(const std::string_view postfix) // Implicit HDR PNG when running in HDR case 3: - save_success = sk_hdr_png::write_image_to_disk(screenshot_path.c_str (), _width, _height, pixels.data(), _screenshot_hdr_bits, _back_buffer_format, _screenshot_clipboard_copy); + save_success = sk_hdr_png::write_image_to_disk(screenshot_path.c_str (), _width, _height, pixels.data(), _screenshot_hdr_bits, _back_buffer_format, _screenshot_clipboard_copy, _containing_output); break; } diff --git a/source/runtime.hpp b/source/runtime.hpp index 49a492b876..83f0435b70 100644 --- a/source/runtime.hpp +++ b/source/runtime.hpp @@ -6,6 +6,7 @@ #pragma once #include "reshade_api.hpp" +#include "reshade_api_display.hpp" #include "state_block.hpp" #include "imgui_code_editor.hpp" #include @@ -36,6 +37,7 @@ namespace reshade bool on_init(); void on_reset(); void on_present(api::command_queue *present_queue); + void on_display_change(); uint64_t get_native() const final { return _swapchain->get_native(); } @@ -170,6 +172,8 @@ namespace reshade void reload_effect_next_frame(const char *effect_name) final; + api::display* get_active_display() const final { return _containing_output; } + private: static void check_for_update(); @@ -252,6 +256,11 @@ namespace reshade uint16_t _back_buffer_samples = 1; api::format _back_buffer_format = api::format::unknown; api::color_space _back_buffer_color_space = api::color_space::unknown; + api::display* _containing_output = nullptr; // The display that currently owns the swapchain + api::display_cache _displays; // Cached mapping of all monitors attached to the desktop and their display properties + std::atomic_uint32_t _last_desktop_change = 0; // Invalidate the display cache when necessary + int _last_desktop_x = 0; + int _last_desktop_y = 0; bool _is_vr = false; #if RESHADE_ADDON From b0eee9e872675fbf08f3d688395cb2260e1b4729 Mon Sep 17 00:00:00 2001 From: Kaldaien Date: Wed, 27 Nov 2024 18:12:23 -0500 Subject: [PATCH 2/3] Correction required so that resolves to the correct path --- ReShade.vcxproj | 1 + source/runtime.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ReShade.vcxproj b/ReShade.vcxproj index f84b4e01c7..9eaac3648b 100644 --- a/ReShade.vcxproj +++ b/ReShade.vcxproj @@ -100,6 +100,7 @@ + tools;$(ExecutablePath) diff --git a/source/runtime.cpp b/source/runtime.cpp index e85b5f9125..c6d7ed56ab 100644 --- a/source/runtime.cpp +++ b/source/runtime.cpp @@ -35,7 +35,7 @@ #include #include #include -#include <../deps/sk_hdr_png/include/sk_hdr_png.hpp> // Something broke and now it has to be included this way +#include bool resolve_path(std::filesystem::path &path, std::error_code &ec) { From b5d4329e56a7c0ec2bf1ee1c7758599d01c185cd Mon Sep 17 00:00:00 2001 From: Kaldaien Date: Wed, 27 Nov 2024 20:23:28 -0500 Subject: [PATCH 3/3] Add check for F16C CPU instruction support when taking scRGB screenshots --- deps/sk_hdr_png/include/sk_hdr_png.hpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/deps/sk_hdr_png/include/sk_hdr_png.hpp b/deps/sk_hdr_png/include/sk_hdr_png.hpp index 2f5fd1835b..eaad64790f 100644 --- a/deps/sk_hdr_png/include/sk_hdr_png.hpp +++ b/deps/sk_hdr_png/include/sk_hdr_png.hpp @@ -26,6 +26,7 @@ #include "reshade_api.hpp" #include "reshade_api_display.hpp" +#include #include #include @@ -814,6 +815,18 @@ sk_hdr_png::copy_to_clipboard (const wchar_t* image_path) bool sk_hdr_png::write_image_to_disk (const wchar_t* image_path, unsigned int width, unsigned int height, const void* pixels, int quantization_bits, format fmt, bool use_clipboard, reshade::api::display* display) { + if (fmt == format::r16g16b16a16_float) + { + int cpu_info [4] = { }; + + CpuIdEx(cpu_info, 1, 0); + if (!std::bitset<32>(cpu_info[2])[29]) + { + reshade::log::message(reshade::log::level::error, "CPU does not support AVX/F16C, required for scRGB screenshots!"); + return false; + } + } + using namespace DirectX; using namespace DirectX::PackedVector;