Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Brightness slider not working when Windows 11 HDR is on #411

Open
Diti opened this issue Feb 1, 2023 · 31 comments
Open

Brightness slider not working when Windows 11 HDR is on #411

Diti opened this issue Feb 1, 2023 · 31 comments

Comments

@Diti
Copy link

Diti commented Feb 1, 2023

The Monitorian slider for brightness does not change the brightness of my HDR-enabled monitors − tested on a Iiyama G-Master GB3467WQSU (400 cd/m² DisplayHDR through DisplayPort 1.4) and on an Atomos Ninja V+ (1000 cd/m² HDR through HDMI 2.0b).

(DisplayHDR 400 is not 10-bit HDR, but Windows 11 and Monitorian behave the same anyway.)

From my understanding of color science, I do believe Windows 11 must be using HLG, which enables SDR content (like the Windows UI itself) to be displayed with a relative base brightness; said base brightness can be changed in the Windows settings for displays, in the Use HDR section.

Currently, the brightness slider for Monitorian does not change brightness when the Use HDR option is On.
At the very least, I believe the brightness slider for Monitorian should change brightness the exact same way the SDR content brightess slider of Windows 11 does.
Optimally, Monitorian should be handling both regular brightness and SDR content brightness with different sliders (from my understanding, the latter slider only works when the monitor uses HLG, and should do nothing when the monitor uses PQ).

For screenshots and understanding of these Windows 11 menus, head over to Change HDR or SDR Content Brightness for HDR Display in Windows 11.

I would be happy to pay for a Monitorian subscription if HDR support was a paid option.

@emoacht
Copy link
Owner

emoacht commented Feb 1, 2023

Thanks for reporting.
I tested with Dell S2721QS on Windows 11 and the brightness slider of this app works as always regardless of whether HDR is on or off. I have no idea what is wrong with your monitors.

@emoacht emoacht closed this as completed Feb 8, 2023
@fireph
Copy link

fireph commented Feb 15, 2023

@emoacht The original comment is extremely detailed and accurate on how HDR brightness works in windows. When HDR is enabled, monitor brightness should not be available by design. It would be great if Monitorian handled this case by having the brightness slider reflect what exists in the Windows HDR settings menu for SDR brightness (or at least have a different slider for SDR brightness when HDR is enabled).

I saw someone on stackoverflow already ask about how to do this programmatically: https://stackoverflow.com/questions/74594751/controlling-sdr-content-brightness-programmatically-in-windows-11

And twinkle tray already has a pinned issue about this exact thing as well where they have made some progress:
xanderfrangos/twinkle-tray#97

@emoacht
Copy link
Owner

emoacht commented Feb 15, 2023

@fireph It primarily depends on whether the necessary information is made publicly available and techinicall feasible.
I have not seen any useful information on HDR and DDC/CI to date.

accurate on how HDR brightness works in windows. When HDR is enabled, monitor brightness should not be available by design

Could you give me a link to official documentation on this?

@emoacht
Copy link
Owner

emoacht commented Mar 3, 2023

The only available API related to SDR brightness which I could find is Windows.Graphics.Display.AdvancedColorInfo.SdrWhiteLevelInNits property which provides the current SDR luminance of a monitor where the window locates. However, it requires DisplayInformation instance which can only be obtained by DisplayInformation.GetForCurrentView method and thus it is only usable in an UWP app.

@lulle2007200
Copy link

@emoacht That is not true anymore, winrt can be used in non packaged/non uwp apps just fine. You can also use DisplayConfigGetDeviceInfo to get DISPLAYCONFIG_SDR_WHITE_LEVEL. Brightness for SDR content can be set programmatically (see my answer on SO)

@emoacht
Copy link
Owner

emoacht commented Jun 22, 2023

@lulle2007200 Thanks for the information.

That is not true anymore, winrt can be used in non packaged/non uwp apps just fine.

Could you elaborate how to call DisplayInformation.GetForCurrentView in an app other than UWP?

Also, could you elaborate how to get DISPLAYCONFIG_SDR_WHITE_LEVEL? It seems a little complicated.

Regarding SO's answer, it is interesting. But that method is undocumented and unnamed. Could you elaborate how did you find the method and its signature?

@lulle2007200
Copy link

lulle2007200 commented Jun 23, 2023

Could you elaborate how did you find the method and its signature?

Display settings are implemented in a DLL, separate from the settings app. I ran the settings app under debugger to find the event handler in the display settings DLL that gets called when the SDR brightness slider changes value, from there, i stepped through that event handler until i found the call to dwmapi.dll. The function name comes from debug symbols, the signature i found by reverse engineering.

Also, could you elaborate how to get DISPLAYCONFIG_SDR_WHITE_LEVEL? It seems a little complicated.

This is not very useful as it only allows you to get the current white level, but not set it, but here is how you'd do it (error handling and cleanup omitted):

#include <Windows.h>
#include <iostream>

int main()
{
	UINT32 numPathArrayElems; 
	UINT32 numModeInfoArrayElems;
	GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &numPathArrayElems, &numModeInfoArrayElems);

	DISPLAYCONFIG_PATH_INFO *pathArray = new DISPLAYCONFIG_PATH_INFO[numPathArrayElems];
	DISPLAYCONFIG_MODE_INFO *modeInfoArray = new DISPLAYCONFIG_MODE_INFO[numModeInfoArrayElems];

	QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &numPathArrayElems, pathArray, &numModeInfoArrayElems, modeInfoArray, nullptr);

	// print white level for all displays
	for(size_t i = 0; i < numPathArrayElems; i++){
		DISPLAYCONFIG_SDR_WHITE_LEVEL request;
		request.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SDR_WHITE_LEVEL;
		request.header.size = sizeof(request);
		request.header.adapterId = pathArray->targetInfo.adapterId;
		request.header.id = pathArray->targetInfo.id;

		DisplayConfigGetDeviceInfo(reinterpret_cast<DISPLAYCONFIG_DEVICE_INFO_HEADER*>(&request));
		
		std::cout << "White level: " << request.SDRWhiteLevel << std::endl;
	}
}

Could you elaborate how to call DisplayInformation.GetForCurrentView in an app other than UWP?

Basically exactly the same way as you would do it in an UWP app, e.g.

#include "winrt/base.h"
#include "winrt/Windows.Graphics.Display.h"

using namespace winrt;
using namespace Windows::Graphics::Display;

//...

auto displayInfo = DisplayInformation::GetForCurrentView();
auto white_level = displayInfo.GetAdvancedColorInfo().SdrWhiteLevelInNits();

The catch with this is that it is tied to a specific window. E.g. the thread you call it from has to be associated to a window. GetForCurrentView() then returns a DisplayInformation object that holds display information for that specific window (and it changes when you move the window from one screen to another, change scaling, etc.). Other than that, displayInfo.GetAdvancedColorInfo().SdrWhiteLevelInNits(); essentially does the same as DisplayConfigGetDeviceInfo to get DISPLAYCONFIG_SDR_WHITE_LEVEL

@emoacht
Copy link
Owner

emoacht commented Jun 25, 2023

@lulle2007200 Thank you for the information again.

I tried to call the function of dwmapi.dll by name but failed. Then I checked dwmapi.dll with Dumpin and found that the method is unnamed. So the only way to call it is by its index number 171.

Regarding DISPLAYCONFIG_SDR_WHITE_LEVEL with DisplayConfigGetDeviceInfo, I confirmed that the white level matches SdrWhiteLevelInNits obtained on UWP.

Regarding DisplayInformation.GetForCurrentView on other platform, if I call it on WPF, it causes COMException and I have no idea to avoid it. Thus it is unusable.

System.Runtime.InteropServices.COMException
  HResult=0x80070490
  Message=Element not found.(0x80070490)
  Source=WinRT.Runtime

@lulle2007200
Copy link

lulle2007200 commented Jun 25, 2023

Yeah, currently DwmpSDRToHDRBoost is only exported by ordinal 171 and not by name, so you have to load it by ordinal.

Maybe i put that badly. Generally you can use all of winrt in "normal" non UWP apps, however DisplayInformation.GetForCurrentView is only available when the calling thread is associated to a CoreApplicationView, CoreApplicationView is primarily used in UWP apps where there can only be a single window per thread (which is what CoreApplicationView models). In fact, usage of CoreApplicationView was deprecated in more recent versions of winrt.

The exception rises, because the thread doesnt have an associated CoreApplicationView.

@emoacht
Copy link
Owner

emoacht commented Jun 25, 2023

@lulle2007200 This app has been using some of WinRT APIs from early version. Some APIs that requires GetForCurrentView like method are exceptions. If we were able to use AdvancedColorInfo, we can make use of other useful APIs.

@lulle2007200
Copy link

lulle2007200 commented Jun 25, 2023

@emoacht There is a way to do that in non UWP apps/apps that don't use CoreApplicationView, but only if you are targeting Windows 11 build 22621 or later, otherwise you'd have to use the classic Win32 APIs.

You can obtain a DisplayInformation interface for a specific display instead of a specific window as follows:

#include "winrt/base.h"
#include "winrt/Windows.Graphics.Display.h"
#include <windows.graphics.display.interop.h>

#include <iostream>

using namespace winrt;
using namespace Windows::Graphics::Display;

HMONITOR getCurrentMonitor() {
	return MonitorFromWindow(nullptr, MONITOR_DEFAULTTOPRIMARY);
}

int main()
{
	auto display_info_factory = get_activation_factory<DisplayInformation, IDisplayInformationStaticsInterop>();

	HMONITOR h_monitor = getCurrentMonitor();
	DisplayInformation display_info = nullptr;
	display_info_factory->GetForMonitor(h_monitor, guid_of<DisplayInformation>(), put_abi(display_info));

	AdvancedColorInfo advanced_color_info = display_info.GetAdvancedColorInfo();

	std::cout << "max. nits: " << advanced_color_info.MaxLuminanceInNits() << std::endl;
}

@emoacht
Copy link
Owner

emoacht commented Jun 25, 2023

Thanks. I will wait for Microsoft to release C# equivalent.

@lulle2007200
Copy link

I'm not too familiar with c#, but this should work pretty much the same in c#. As for the DwmpSDRToHDRBoost thing, someone posted an answer on the SO post with a c# version. I hope they make the HDR stuff more accessible eventually...

@emoacht emoacht reopened this Jun 26, 2023
@emoacht
Copy link
Owner

emoacht commented Aug 19, 2023

I figured out the way to obtain DisplayInformation on WPF and incorporated the function to capture advanced color information in Ver 4.4.8. It can be enabled with /advancedcolor command-line option.

The next challenge is the maximum value. As far as I tested it with Dell U2720QM and S2721QS, the AdvancedColorInfo.SdrWhiteLevelInNits value when the slider is at 100 is always 480 whereas the AdvancedColorInfo.MaxLuminanceInNits value varies at 270, 350 or 400. I have no idea whether we can regard 480 as the common maximum value.

@lulle2007200
Copy link

lulle2007200 commented Aug 19, 2023

AdvancedColorInfo.MaxLuminanceInNits is defined in the color profile that is active for the monitor, it is supposed to report the monitors actual maximum brightness, but is basically meaningless if a wrong color profile is loaded. Also, the value that is reported for that is always for the primary monitor iirc. Same for AdvancedColorInfo.MaxAverageFullFrameLuminanceInNits. AdvancedColorInfo.SdrWhiteLevelInNits reports the absolute brightness level that pure white should be displayed at in SDR content. Windows defines the "reference" SDR white level to be 80 nits (or value 1.0f in FP16 HDR image data) and doesn't allow setting it any lower (therefore the minimum reported value for AdvancedColorInfo.SdrWhiteLevelInNits is always 80). Technically, it makes sense to allow setting AdvancedColorInfo.SdrWhiteLevelInNits up to AdvancedColorInfo.MaxAverageFullFrameLuminanceInNits, i don't know why windows limits it to 480 (6.0f in FP16 HDR) in the settings app. If the maximum is variable and depends on the monitor you use, i don't know how it calculates the maximum possible value. I've tried a few monitors and a bunch of (bogus) color profiles, it seems to always be 480.

When using DwmpSDRToHDRBoost, you can set the SDR white level from 80 nits up to whatever you want. However, if you set it higher than what your monitor actually can display, you get clipping.

@emoacht
Copy link
Owner

emoacht commented Aug 20, 2023

Also, the value that is reported for that is always for the primary monitor iirc. Same for AdvancedColorInfo.MaxAverageFullFrameLuminanceInNits.

What I have observed is different. The following is the values of each monitor when HDR is on for that monitor.

Monitor AdvancedColorInfo.MaxLuminanceInNits AdvancedColorInfo.MaxAverageFullFrameLuminanceInNits
U2720QM 400 400
S2721QS 351.2764 351.2764

Both monitors are connected to Surface Pro and Surface Pro's internal monitor is the primary. I confirmed each of them is associated with correct .icm file (Its file name includes each model name). This may depend on the devices.

I thought the actual brightness of a monitor could saturate at its AdvancedColorInfo.MaxLuminanceInNits but I noticed with my eyes that the brightness became higher even after it reached that value.

@lulle2007200
Copy link

lulle2007200 commented Aug 20, 2023

What I have observed is different.
Yeah, i might be wrong on that. I just vaguely remember reading something like that on MSDN, might be in some other context.

AdvancedColorInfo.MaxLuminanceInNits is supposed to be the peak brightness of the monitor (for only a small area), AdvancedColorInfo.MaxAverageFullFrameLuminanceInNits is supposed to be the maximum brightness for full white frame (as the name suggests). In practice, they aren't always accurate, for one of the monitors i tested, the profile it loaded reports 10000 nits for both. So i can imagine that in practice in some cases the monitor might be more or less bright than what is reported in the profile.

I noticed with my eyes that the brightness became higher even after it reached that value.

If the monitor is actually a bit brighter than what is reported, that makes sense. I guess its simple to test. Just look at some black to white gradient while increasing SDR brightness (in settings or using DwmpSdrToHdrBoost when you want to go beyond 480 nits). Once you loose contrast on the white side of the gradient, you reached maximum supported brightness.

As for why the max. in settings is 480, maybe because minimum (vesa) HDR standard is HDR 400 which requires min. brightness of 400 nits?

@emoacht
Copy link
Owner

emoacht commented Aug 20, 2023

If the monitor is actually a bit brighter than what is reported, that makes sense.

Yes, I am sure.

As for why the max. in settings is 480, maybe because minimum (vesa) HDR standard is HDR 400 which requires min. brightness of 400 nits?

That is the question. I wish Microsoft could provide more information.

@Draghmar
Copy link

Draghmar commented Jan 8, 2024

I have PG49WCD and noticed the same issues with HDR mode set to on as others. Does the experimental changes from above should work in the 4.6.1.0 version? I tried to set brightness from cli with the /advancedcolor switch but it didn't do anything regardless if I have Brightness Adjustable turned on or off - this options allows to change brightness even though it shouldn't be allowed, which really saves my eyes when doing basically anything - I don't know how people can play games when things tends to be so bright it's stupid. XD

@emoacht
Copy link
Owner

emoacht commented Jan 9, 2024

@Draghmar Thank you for your interest.
The /advancedcolor option has been there. It is only valid on Windows 22H2 (build 22621) or newer.
If the conditions for HDR are met, the current value of HDR settings should be shown under the slider for a while after it changes.

Screenshot

@Draghmar
Copy link

Draghmar commented Jan 9, 2024

Hm...I've updated to the 4.6.3.0. I'm using MS Store for that. How can I launch app with the switch present?
And using this way:

%LocalAppData%\Microsoft\WindowsApps\Monitorian.exe /advancedcolor /set "DISPLAY\AUS4921\5&1a8dbdb9&0&UID4355" 25

still doesn't do anything. I'm getting this in response:

DISPLAY\AUS4921\5&1a8dbdb9&0&UID4355 "PG49WCD" 25 B *

Launching app only with:

%LocalAppData%\Microsoft\WindowsApps\Monitorian.exe /advancedcolor

doesn't show mi those additional informations.
obraz

@emoacht
Copy link
Owner

emoacht commented Jan 9, 2024

/advancedcolor option is to show the current HDR settings. Nothing else. /set option has nothing to do with it.
Microsoft has not released enough information on HDR to make use of this settings' information.
Anyway, your monitor seems not compatible with this function.

@arduinka55055
Copy link

Hello. I really like Monitorian app, I've got an HDR monitor, and I've noticed that the slider actually changes brightness (Samsung lu28r550)
But I'd like to have SDR content brightness slider as was mentioned before.

I really want to have SDR content slider, but I don't have experience in XAML, here's what I've found:
https://stackoverflow.com/a/76516363
the code does exactly that, 6 is max value, I'd be happy if you add this to Monitorian app.

@emoacht
Copy link
Owner

emoacht commented May 30, 2024

That SO question is alreadly discussed in this issue.
To date, I have not found a reliable way to change SDR level in a manner consistend with the OS's SDR level settings.

@arduinka55055
Copy link

I've checked 6 is max SDR brightness (around 480 nits), that stackoverflow code worked for me well.

@emoacht
Copy link
Owner

emoacht commented May 31, 2024

Does it match the OS's SDR settings?

@arduinka55055
Copy link

Interestingly, it changes monitor brightness well, but not OS's SDR settings, slider is stuck at previous value.
Need to debug Settings, I think it is cached somewhere

@lulle2007200
Copy link

Does it match the OS's SDR settings?

It used to in some previous windows version. Now it is cached in multiple places and registry.

There is also DISPLAYCONFIG_DEVICE_INFO_SET_SDR_WHITE_LEVEL that can be used with DisplayConfigSetDeviceInfo, that seems to do exactly what the OS setting does, but it's also undocumented and therefore subject to change.

@arduinka55055
Copy link

Yeah, that's the issue.

@lulle2007200
Copy link

lulle2007200 commented Jul 9, 2024

@emoacht I did some more REing and found how to set it properly, see xanderfrangos/twinkle-tray#97 (comment)

API's are still undocumented, but this sets the brightness properly.

@emoacht
Copy link
Owner

emoacht commented Aug 3, 2024

@lulle2007200 So have you found the new member of DISPLAYCONFIG_DEVICE_INFO_TYPE enumeration?

The following is derived from wingdi.h of Windows SDK 10.0.26100.1.

typedef enum
{
    DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME                 = 1,
    DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME                 = 2,
    DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_PREFERRED_MODE       = 3,
    DISPLAYCONFIG_DEVICE_INFO_GET_ADAPTER_NAME                = 4,
    DISPLAYCONFIG_DEVICE_INFO_SET_TARGET_PERSISTENCE          = 5,
    DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_BASE_TYPE            = 6,
    DISPLAYCONFIG_DEVICE_INFO_GET_SUPPORT_VIRTUAL_RESOLUTION  = 7,
    DISPLAYCONFIG_DEVICE_INFO_SET_SUPPORT_VIRTUAL_RESOLUTION  = 8,
    DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO         = 9,
    DISPLAYCONFIG_DEVICE_INFO_SET_ADVANCED_COLOR_STATE        = 10,
    DISPLAYCONFIG_DEVICE_INFO_GET_SDR_WHITE_LEVEL             = 11,
    DISPLAYCONFIG_DEVICE_INFO_GET_MONITOR_SPECIALIZATION      = 12,
    DISPLAYCONFIG_DEVICE_INFO_SET_MONITOR_SPECIALIZATION      = 13,
    DISPLAYCONFIG_DEVICE_INFO_SET_RESERVED1                   = 14,
    DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO_2       = 15,
    DISPLAYCONFIG_DEVICE_INFO_SET_HDR_STATE                   = 16,
    DISPLAYCONFIG_DEVICE_INFO_SET_WCG_STATE                   = 17,

      DISPLAYCONFIG_DEVICE_INFO_FORCE_UINT32                = 0xFFFFFFFF
} DISPLAYCONFIG_DEVICE_INFO_TYPE;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants