Resolution Scaling Bug

To recreate:

New, blank project. On BeginPlay of the level blueprint, print out all four values of the GetResolutionScaleInformation node. I get:

Current Scale Normalized:inf

Current Scale Value: inf

Min Scale Value: 40.0

Max Scale Value: 100.0

The two inf values indicate to me that something is wrong. In my active project the user is unable to adjust resolution scaling, and so this bug is quite serious but should be easy to fix.

Same problem over here.
Has it probably something to do with these two values in my GameUserSettings.ini in “Saved/Config/Windows”?

DesiredScreenWidth=-2147483648
DesiredScreenHeight=-2147483648

I’ve seen that these two values are getting calculated in GameUserSettings.cpp in SetResolutionScaleValue(…). Any help would be appreciated.

In a packaged build it also doesn’t work.

The config files that are in that folder get created at runtime. Those values shouldn’t be like that by default. You can delete that file and when you open the project again the editor will generate a new file with default settings.

As far as standalone returning incorrect values that may be expected since it is independent of the editor although I may need research that.

I was unable to reproduce an issue with a packaged build. All the values printed as expected. Could you try deleting your saved folder or specifically that file you pointed out and see if the issue persists?

I’ve deleted the saved folder and at first everything went normal. But when i execute UGameUserSettings::SetResolutionScaleValue(float NewScale Value) the scaling gets messed up again and i get an infinite screen scale value from UGameUserSettings::GetResolutionScaleInformation(). For now i set “ScalabilityQuality.ResolutionQuality” directly which works like expected.

Cheers

After discussing this issue with a coworker we decided it best to log a report for it. Here is the link: Unreal Engine Issues and Bug Tracker (UE-39463)

You can track the report’s status as the issue is reviewed by our development staff. Please be aware that this issue may not be prioritized or fixed soon.

You calling that function didn’t actually cause the erroneous values. That seemed to occur simply by running the project a second time. That has been mentioned in the JIRA

Cheers,

We were encountering this issue, and I believe I’ve found an appropriate solution.

The root cause

All of this is discussing the code in Runtime/Engine/Private/GameUserSettings.cpp. When UGameUserSettings is first constructed, code in UGameUserSettings::SetToDefaults() sets the properties ResolutionSizeX and ResolutionSizeY to 0.

void UGameUserSettings::SetToDefaults()
{
	ResolutionSizeX = LastUserConfirmedResolutionSizeX = GetDefaultResolution().X;
	ResolutionSizeY = LastUserConfirmedResolutionSizeY = GetDefaultResolution().Y;
	...
	if (FApp::CanEverRender())
	{
	    	UpdateResolutionQuality();
	}
}

FIntPoint UGameUserSettings::GetDefaultResolution()
{
	return FIntPoint::ZeroValue;
}

UpdateResolutionQuality() then uses ResolutionSizeX and ResolutionSizeY to calculate the MinResolutionScale which is used throughout the rest of the settings code to calculate the lower bound for resolution ratios.

void UGameUserSettings::UpdateResolutionQuality()
{
	const int32 MinHeight = UKismetSystemLibrary::GetMinYResolutionFor3DView();
	const int32 ScreenWidth = (FullscreenMode == EWindowMode::WindowedFullscreen) ? GetDesktopResolution().X : ResolutionSizeX;
	const int32 ScreenHeight = (FullscreenMode == EWindowMode::WindowedFullscreen) ? GetDesktopResolution().Y : ResolutionSizeY;
	MinResolutionScale = FMath::Max<float>(Scalability::MinResolutionScale, ((float)MinHeight / (float)ScreenHeight) * 100.0f);
	...
}

If FullscreenMode is not WindowedFullscreen (in my testing, I used Fullscreen and Windowed), then the ResolutionSize* properties are used to calculate the Screen* dimensions. When the game or editor is first loaded, because the ResolutionSize* properties are initialized to zero, ScreenWidth and ScreenHeight are both set to zero. This results in ((float)MinHeight / (float)ScreenHeight) * 100.0f evaluating to infinity because of the division by zero. Consequently, FMath::Max returns inf instead of the default Scalability::MinResolutionScale, thus, MinResolutionScale gets set to positive infinity.

Further down in UpdateResolutionQuality(), this value is assigned to ScalabilityQuality.ResolutionQuality:

void UGameUserSettings::UpdateResolutionQuality()
{
	...
	if (bUseDesiredScreenHeight)
	{
		ScalabilityQuality.ResolutionQuality = UGameUserSettings::GetDefaultResolutionScale();
	}
	else
	{
		ScalabilityQuality.ResolutionQuality = FMath::Max(ScalabilityQuality.ResolutionQuality, MinResolutionScale);
	}
}

Note: bUseDesiredScreenHeight is initialized to false, and is only true if manually changed in GameUserSettings.ini.

Thus the results are obvious when examining the implementation of GetResolutionScaleInformationEx:

void UGameUserSettings::GetResolutionScaleInformationEx(float& CurrentScaleNormalized, float& CurrentScaleValue, float& MinScaleValue, float& MaxScaleValue) const
{
	CurrentScaleValue = ScalabilityQuality.ResolutionQuality;
	MinScaleValue = MinResolutionScale;
	MaxScaleValue = Scalability::MaxResolutionScale;
	CurrentScaleNormalized = ((float)CurrentScaleValue - (float)MinScaleValue) / (float)(MaxScaleValue - MinScaleValue);
}

Albeit, slightly different from the original post: CurrentScaleValue will be inf or sometimes a valid value, MinScaleValue is infinity, MaxScaleValue is 100.f, and CurrentScaleNormalized becomes -nan(ind).

So, essentially, the problem is ResolutionSizeX and ResolutionSizeY being zero at startup, when first querying Game User Settings.

Potential/Attempted Solutions

I searched through and attempted several different solutions, in the end I found only two which actually worked. However, for the sake of completeness, I’ll attempt to enumerate all of the ideas I attempted

Changing the default value for GetDefaultResolution()

Since GetDefaultResolution() was returning (0,0), I figured I’d try setting it to some extremes that were actual resolutions. If it’s set small, eg 640x480, then MinResolutionScale becomes to large for any screen (around 0.75), resulting in invalid bounds when first queried. If it were set to a very large default, then the MinResolutionScale value would be too small for smaller screens. Basically, it fixes the invalid numbers, but results in bad values elsewhere. Not a good option.

Detect zero values in UpdateResolutionQuality()

I attempted to replace the ScreenWidth and ScreenHeight calculations with additional conditions:

const int32 ScreenWidth = (FullscreenMode == EWindowMode::WindowedFullscreen && ResolutionSizeX != 0) ? GetDesktopResolution().X : ResolutionSizeX;
const int32 ScreenHeight = (FullscreenMode == EWindowMode::WindowedFullscreen && ResolutionSizeY != 0) ? GetDesktopResolution().Y : ResolutionSizeY;

This resulted in weird behavior on Windowed games, as it set the default resolution too high for the actual requested dimensions. Not a good option.

Utilize ValidateSettings()

ValidateSettings() is one of the two methods which actually set the values of ResolutionSizeX and ResolutionSizeY. I considered two places where a to this could be logically placed: Inside LoadSettings() or SetToDefaults().

LoadSettings()

I never actually tried this. ValidateSettings() itself includes multiple settings to LoadSettings() and this seemed like an invitation for recursion, so nixed that idea.

SetToDefaults()

I placed a to ValidateSettings() in SetToDefaults() right before the to UpdateResolutionQuality().

void UGameUserSettings::SetToDefaults()
{
	...
	if (FApp::CanEverRender())
	{
	    	ValidateSettings();
	    	UpdateResolutionQuality();
	}
}

This was the first solution that worked well. It solved all problems, and never results in the zero values for ResolutionSizeX and ResolutionSizeY, even during editor startup. The potential drawback I saw is that ValidateSettings() includes multiple calls to LoadSettings() and itself could SetToDefaults() which might result in recursion—to be honest, I didn’t even notice the SetToDefaults until I was typing this out. Semantically, it also doesn’t seem like a good fit, including non-deterministic code in SetToDefaults().

Call UpdateResolutionQuality() from LoadSettings()

This is the best solution I’ve found. ResolutionSizeX and ResolutionSizeY are loaded from config values within LoadSettings(), which itself is called in the UEngine singleton access method. By placing UpdateResolutionQuality() in LoadSettings(), it ensures that MinResolutionScale has is valid as soon as the singleton is loaded. It also seems quite logical to perform the update after loading the config values, so it seems like a good logical location for such a.

The only potential issue I see is that MinResolutionScale does still receive infinity during editor startup when it’s loading class defaults. I doubt this will pose a significant issue, though.

That said, it seems to work well, so this is the fix I’m going with for now.

My Solution

void UGameUserSettings::LoadSettings( bool bForceReload/*=false*/ )
{
	...
	// Ensure that MinResolutionScale is updated with the new values of ResolutionSizeX/Y
	UpdateResolutionQuality();
}

Edit: I’ve submitted a pull request for this on Github.