446 lines
15 KiB
C++
446 lines
15 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "LyraSettingValueDiscrete_Resolution.h"
|
|
#include "GameFramework/GameUserSettings.h"
|
|
#include "RHI.h"
|
|
#include "Framework/Application/SlateApplication.h"
|
|
#include "Engine/Engine.h"
|
|
#include "UnrealEngine.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "LyraSettings"
|
|
|
|
ULyraSettingValueDiscrete_Resolution::ULyraSettingValueDiscrete_Resolution()
|
|
{
|
|
}
|
|
|
|
void ULyraSettingValueDiscrete_Resolution::OnInitialized()
|
|
{
|
|
Super::OnInitialized();
|
|
|
|
InitializeResolutions();
|
|
}
|
|
|
|
void ULyraSettingValueDiscrete_Resolution::StoreInitial()
|
|
{
|
|
// Ignored
|
|
}
|
|
|
|
void ULyraSettingValueDiscrete_Resolution::ResetToDefault()
|
|
{
|
|
// Ignored
|
|
}
|
|
|
|
void ULyraSettingValueDiscrete_Resolution::RestoreToInitial()
|
|
{
|
|
// Ignored
|
|
}
|
|
|
|
void ULyraSettingValueDiscrete_Resolution::SetDiscreteOptionByIndex(int32 Index)
|
|
{
|
|
if (Resolutions.IsValidIndex(Index) && Resolutions[Index].IsValid())
|
|
{
|
|
GEngine->GetGameUserSettings()->SetScreenResolution(Resolutions[Index]->GetResolution());
|
|
NotifySettingChanged(EGameSettingChangeReason::Change);
|
|
}
|
|
}
|
|
|
|
int32 ULyraSettingValueDiscrete_Resolution::GetDiscreteOptionIndex() const
|
|
{
|
|
const UGameUserSettings* UserSettings = CastChecked<const UGameUserSettings>(GEngine->GetGameUserSettings());
|
|
|
|
return FindIndexOfDisplayResolutionForceValid(UserSettings->GetScreenResolution());
|
|
}
|
|
|
|
TArray<FText> ULyraSettingValueDiscrete_Resolution::GetDiscreteOptions() const
|
|
{
|
|
TArray<FText> ReturnResolutionTexts;
|
|
|
|
for (int32 i = 0; i < Resolutions.Num(); ++i)
|
|
{
|
|
ReturnResolutionTexts.Add(Resolutions[i]->GetDisplayText());
|
|
}
|
|
|
|
return ReturnResolutionTexts;
|
|
}
|
|
|
|
void ULyraSettingValueDiscrete_Resolution::OnDependencyChanged()
|
|
{
|
|
const FIntPoint CurrentResolution = GEngine->GetGameUserSettings()->GetScreenResolution();
|
|
SelectAppropriateResolutions();
|
|
SetDiscreteOptionByIndex(FindClosestResolutionIndex(CurrentResolution));
|
|
}
|
|
|
|
void ULyraSettingValueDiscrete_Resolution::InitializeResolutions()
|
|
{
|
|
Resolutions.Empty();
|
|
ResolutionsFullscreen.Empty();
|
|
ResolutionsWindowed.Empty();
|
|
ResolutionsWindowedFullscreen.Empty();
|
|
|
|
FDisplayMetrics InitialDisplayMetrics;
|
|
FSlateApplication::Get().GetInitialDisplayMetrics(InitialDisplayMetrics);
|
|
|
|
FScreenResolutionArray ResArray;
|
|
RHIGetAvailableResolutions(ResArray, true);
|
|
|
|
// Determine available windowed modes
|
|
{
|
|
TArray<FIntPoint> WindowedResolutions;
|
|
const FIntPoint MinResolution(1280, 720);
|
|
// Use the primary display resolution minus 1 to exclude the primary display resolution from the list.
|
|
// This is so you don't make a window so large that part of the game is off screen and you are unable to change resolutions back.
|
|
const FIntPoint MaxResolution(InitialDisplayMetrics.PrimaryDisplayWidth - 1, InitialDisplayMetrics.PrimaryDisplayHeight - 1);
|
|
// Excluding 4:3 and below
|
|
const float MinAspectRatio = 16 / 10.f;
|
|
|
|
if (MaxResolution.X >= MinResolution.X && MaxResolution.Y >= MinResolution.Y)
|
|
{
|
|
GetStandardWindowResolutions(MinResolution, MaxResolution, MinAspectRatio, WindowedResolutions);
|
|
}
|
|
|
|
if (GSystemResolution.WindowMode == EWindowMode::Windowed)
|
|
{
|
|
WindowedResolutions.AddUnique(FIntPoint(GSystemResolution.ResX, GSystemResolution.ResY));
|
|
WindowedResolutions.Sort([](const FIntPoint& A, const FIntPoint& B) { return A.X != B.X ? A.X < B.X : A.Y < B.Y; });
|
|
}
|
|
|
|
// If there were no standard resolutions. Add the primary display size, just so one exists.
|
|
// This might happen if we are running on a non-standard device.
|
|
if (WindowedResolutions.Num() == 0)
|
|
{
|
|
WindowedResolutions.Add(FIntPoint(InitialDisplayMetrics.PrimaryDisplayWidth, InitialDisplayMetrics.PrimaryDisplayHeight));
|
|
}
|
|
|
|
ResolutionsWindowed.Empty(WindowedResolutions.Num());
|
|
for (const FIntPoint& Res : WindowedResolutions)
|
|
{
|
|
TSharedRef<FScreenResolutionEntry> Entry = MakeShared<FScreenResolutionEntry>();
|
|
Entry->Width = Res.X;
|
|
Entry->Height = Res.Y;
|
|
|
|
ResolutionsWindowed.Add(Entry);
|
|
}
|
|
}
|
|
|
|
// Determine available windowed full-screen modes
|
|
{
|
|
FScreenResolutionRHI* RHIInitialResolution = ResArray.FindByPredicate([InitialDisplayMetrics](const FScreenResolutionRHI& ScreenRes) {
|
|
return ScreenRes.Width == InitialDisplayMetrics.PrimaryDisplayWidth && ScreenRes.Height == InitialDisplayMetrics.PrimaryDisplayHeight;
|
|
});
|
|
|
|
TSharedRef<FScreenResolutionEntry> Entry = MakeShared<FScreenResolutionEntry>();
|
|
if (RHIInitialResolution)
|
|
{
|
|
// If this is in the official list use that
|
|
Entry->Width = RHIInitialResolution->Width;
|
|
Entry->Height = RHIInitialResolution->Height;
|
|
Entry->RefreshRate = RHIInitialResolution->RefreshRate;
|
|
}
|
|
else
|
|
{
|
|
// Custom resolution the RHI doesn't expect
|
|
Entry->Width = InitialDisplayMetrics.PrimaryDisplayWidth;
|
|
Entry->Height = InitialDisplayMetrics.PrimaryDisplayHeight;
|
|
|
|
// TODO: Unsure how to calculate refresh rate
|
|
Entry->RefreshRate = FPlatformMisc::GetMaxRefreshRate();
|
|
}
|
|
|
|
ResolutionsWindowedFullscreen.Add(Entry);
|
|
}
|
|
|
|
// Determine available full-screen modes
|
|
if (ResArray.Num() > 0)
|
|
{
|
|
// try more strict first then more relaxed, we want at least one resolution to remain
|
|
for (int32 FilterThreshold = 0; FilterThreshold < 3; ++FilterThreshold)
|
|
{
|
|
for (int32 ModeIndex = 0; ModeIndex < ResArray.Num(); ModeIndex++)
|
|
{
|
|
const FScreenResolutionRHI& ScreenRes = ResArray[ModeIndex];
|
|
|
|
// first try with struct test, than relaxed test
|
|
if (ShouldAllowFullScreenResolution(ScreenRes, FilterThreshold))
|
|
{
|
|
TSharedRef<FScreenResolutionEntry> Entry = MakeShared<FScreenResolutionEntry>();
|
|
Entry->Width = ScreenRes.Width;
|
|
Entry->Height = ScreenRes.Height;
|
|
Entry->RefreshRate = ScreenRes.RefreshRate;
|
|
|
|
ResolutionsFullscreen.Add(Entry);
|
|
}
|
|
}
|
|
|
|
if (ResolutionsFullscreen.Num())
|
|
{
|
|
// we found some resolutions, otherwise we try with more relaxed tests
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
SelectAppropriateResolutions();
|
|
}
|
|
|
|
void ULyraSettingValueDiscrete_Resolution::SelectAppropriateResolutions()
|
|
{
|
|
EWindowMode::Type const WindowMode = GEngine->GetGameUserSettings()->GetFullscreenMode();
|
|
if (LastWindowMode != WindowMode)
|
|
{
|
|
LastWindowMode = WindowMode;
|
|
|
|
Resolutions.Empty();
|
|
switch (WindowMode)
|
|
{
|
|
case EWindowMode::Windowed:
|
|
Resolutions.Append(ResolutionsWindowed);
|
|
break;
|
|
case EWindowMode::WindowedFullscreen:
|
|
Resolutions.Append(ResolutionsWindowedFullscreen);
|
|
break;
|
|
case EWindowMode::Fullscreen:
|
|
Resolutions.Append(ResolutionsFullscreen);
|
|
break;
|
|
}
|
|
|
|
NotifyEditConditionsChanged();
|
|
}
|
|
}
|
|
|
|
// To filter out odd resolution so UI and testing has less issues. This is game specific.
|
|
// @param ScreenRes resolution and
|
|
// @param FilterThreshold 0/1/2 to make sure we get at least some resolutions (might be an issues with UI but at least we get some resolution entries)
|
|
bool ULyraSettingValueDiscrete_Resolution::ShouldAllowFullScreenResolution(const FScreenResolutionRHI& SrcScreenRes, int32 FilterThreshold) const
|
|
{
|
|
FScreenResolutionRHI ScreenRes = SrcScreenRes;
|
|
|
|
// expected: 4:3=1.333, 16:9=1.777, 16:10=1.6, multi-monitor-wide: >2
|
|
bool bIsPortrait = ScreenRes.Width < ScreenRes.Height;
|
|
float AspectRatio = (float)ScreenRes.Width / (float)ScreenRes.Height;
|
|
|
|
// If portrait, flip values back to landscape so we can don't have to special case all the tests below
|
|
if (bIsPortrait)
|
|
{
|
|
AspectRatio = 1.0f / AspectRatio;
|
|
ScreenRes.Width = SrcScreenRes.Height;
|
|
ScreenRes.Height = SrcScreenRes.Width;
|
|
}
|
|
|
|
// Filter out resolutions that don't match the native aspect ratio of the primary monitor
|
|
// TODO: Other games allow the user to choose which monitor the games goes fullscreen on. This would allow
|
|
// this filtering to be correct when the users monitors are of different types! ATM, the game can change
|
|
// which monitor it uses based on other factors (max window overlap etc.) so we could end up choosing a
|
|
// resolution which the target monitor doesn't support.
|
|
if (FilterThreshold < 1)
|
|
{
|
|
FDisplayMetrics DisplayMetrics;
|
|
FSlateApplication::Get().GetInitialDisplayMetrics(DisplayMetrics);
|
|
|
|
// Default display aspect to required aspect in case this platform can't provide the information. Forces acceptance of this resolution.
|
|
float DisplayAspect = AspectRatio;
|
|
|
|
// Some platforms might not be able to detect the native resolution of the display device, so don't filter in that case
|
|
for (int32 MonitorIndex = 0; MonitorIndex < DisplayMetrics.MonitorInfo.Num(); ++MonitorIndex)
|
|
{
|
|
FMonitorInfo& MonitorInfo = DisplayMetrics.MonitorInfo[MonitorIndex];
|
|
|
|
if (MonitorInfo.bIsPrimary)
|
|
{
|
|
DisplayAspect = (float)MonitorInfo.NativeWidth / (float)MonitorInfo.NativeHeight;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If aspects are not almost exactly equal, reject
|
|
if (FMath::Abs(DisplayAspect - AspectRatio) > KINDA_SMALL_NUMBER)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// more relaxed tests have a larger FilterThreshold
|
|
|
|
// minimum is 1280x720
|
|
if (FilterThreshold < 2 && (ScreenRes.Width < 1280 || ScreenRes.Height < 720))
|
|
{
|
|
// filter resolutions that are too small
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int32 ULyraSettingValueDiscrete_Resolution::FindIndexOfDisplayResolution(const FIntPoint& InPoint) const
|
|
{
|
|
// find the current res
|
|
for (int32 i = 0, Num = Resolutions.Num(); i < Num; ++i)
|
|
{
|
|
if (Resolutions[i]->GetResolution() == InPoint)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return INDEX_NONE;
|
|
}
|
|
|
|
int32 ULyraSettingValueDiscrete_Resolution::FindIndexOfDisplayResolutionForceValid(const FIntPoint& InPoint) const
|
|
{
|
|
int32 Result = FindIndexOfDisplayResolution(InPoint);
|
|
if (Result == INDEX_NONE && Resolutions.Num() > 0)
|
|
{
|
|
Result = Resolutions.Num() - 1;
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
int32 ULyraSettingValueDiscrete_Resolution::FindClosestResolutionIndex(const FIntPoint& Resolution) const
|
|
{
|
|
int32 Index = 0;
|
|
int32 LastDiff = Resolution.SizeSquared();
|
|
|
|
for (int32 i = 0, Num = Resolutions.Num(); i < Num; ++i)
|
|
{
|
|
// We compare the squared diagonals
|
|
int32 Diff = FMath::Abs(Resolution.SizeSquared() - Resolutions[i]->GetResolution().SizeSquared());
|
|
if (Diff <= LastDiff)
|
|
{
|
|
Index = i;
|
|
}
|
|
LastDiff = Diff;
|
|
}
|
|
|
|
return Index;
|
|
}
|
|
|
|
void ULyraSettingValueDiscrete_Resolution::GetStandardWindowResolutions(const FIntPoint& MinResolution, const FIntPoint& MaxResolution, float MinAspectRatio, TArray<FIntPoint>& OutResolutions)
|
|
{
|
|
static TArray<FIntPoint> StandardResolutions;
|
|
if (StandardResolutions.Num() == 0)
|
|
{
|
|
// Standard resolutions as provided by Wikipedia (http://en.wikipedia.org/wiki/Graphics_display_resolution)
|
|
|
|
// Extended Graphics Array
|
|
{
|
|
new(StandardResolutions) FIntPoint(1024, 768); // XGA
|
|
|
|
// WXGA (3 versions)
|
|
new(StandardResolutions) FIntPoint(1366, 768); // FWXGA
|
|
new(StandardResolutions) FIntPoint(1360, 768);
|
|
new(StandardResolutions) FIntPoint(1280, 800);
|
|
|
|
new(StandardResolutions) FIntPoint(1152, 864); // XGA+
|
|
new(StandardResolutions) FIntPoint(1440, 900); // WXGA+
|
|
new(StandardResolutions) FIntPoint(1280, 1024); // SXGA
|
|
new(StandardResolutions) FIntPoint(1400, 1050); // SXGA+
|
|
new(StandardResolutions) FIntPoint(1680, 1050); // WSXGA+
|
|
new(StandardResolutions) FIntPoint(1600, 1200); // UXGA
|
|
new(StandardResolutions) FIntPoint(1920, 1200); // WUXGA
|
|
}
|
|
|
|
// Quad Extended Graphics Array
|
|
{
|
|
new(StandardResolutions) FIntPoint(2048, 1152); // QWXGA
|
|
new(StandardResolutions) FIntPoint(2048, 1536); // QXGA
|
|
new(StandardResolutions) FIntPoint(2560, 1600); // WQXGA
|
|
new(StandardResolutions) FIntPoint(2560, 2048); // QSXGA
|
|
new(StandardResolutions) FIntPoint(3200, 2048); // WQSXGA
|
|
new(StandardResolutions) FIntPoint(3200, 2400); // QUXGA
|
|
new(StandardResolutions) FIntPoint(3840, 2400); // WQUXGA
|
|
}
|
|
|
|
// Hyper Extended Graphics Array
|
|
{
|
|
new(StandardResolutions) FIntPoint(4096, 3072); // HXGA
|
|
new(StandardResolutions) FIntPoint(5120, 3200); // WHXGA
|
|
new(StandardResolutions) FIntPoint(5120, 4096); // HSXGA
|
|
new(StandardResolutions) FIntPoint(6400, 4096); // WHSXGA
|
|
new(StandardResolutions) FIntPoint(6400, 4800); // HUXGA
|
|
new(StandardResolutions) FIntPoint(7680, 4800); // WHUXGA
|
|
}
|
|
|
|
// High-Definition
|
|
{
|
|
new(StandardResolutions) FIntPoint(640, 360); // nHD
|
|
new(StandardResolutions) FIntPoint(960, 540); // qHD
|
|
new(StandardResolutions) FIntPoint(1280, 720); // HD
|
|
new(StandardResolutions) FIntPoint(1920, 1080); // FHD
|
|
new(StandardResolutions) FIntPoint(2560, 1440); // QHD
|
|
new(StandardResolutions) FIntPoint(3200, 1800); // WQXGA+
|
|
new(StandardResolutions) FIntPoint(3840, 2160); // UHD 4K
|
|
new(StandardResolutions) FIntPoint(4096, 2160); // Digital Cinema Initiatives 4K
|
|
new(StandardResolutions) FIntPoint(7680, 4320); // FUHD
|
|
new(StandardResolutions) FIntPoint(5120, 2160); // UHD 5K
|
|
new(StandardResolutions) FIntPoint(5120, 2880); // UHD+
|
|
new(StandardResolutions) FIntPoint(15360, 8640); // QUHD
|
|
}
|
|
|
|
// Sort the list by total resolution size
|
|
StandardResolutions.Sort([](const FIntPoint& A, const FIntPoint& B) { return (A.X * A.Y) < (B.X * B.Y); });
|
|
}
|
|
|
|
// Return all standard resolutions that are within the size constraints
|
|
for (const auto& Resolution : StandardResolutions)
|
|
{
|
|
if (Resolution.X >= MinResolution.X && Resolution.Y >= MinResolution.Y && Resolution.X <= MaxResolution.X && Resolution.Y <= MaxResolution.Y)
|
|
{
|
|
const float AspectRatio = Resolution.X / (float)Resolution.Y;
|
|
if (AspectRatio > MinAspectRatio || FMath::IsNearlyEqual(AspectRatio, MinAspectRatio))
|
|
{
|
|
OutResolutions.Add(Resolution);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FText ULyraSettingValueDiscrete_Resolution::FScreenResolutionEntry::GetDisplayText() const
|
|
{
|
|
if (!OverrideText.IsEmpty())
|
|
{
|
|
return OverrideText;
|
|
}
|
|
|
|
FText Aspect = FText::GetEmpty();
|
|
|
|
// expected: 4:3=1.333, 16:9=1.777, 16:10=1.6, multi-monitor-wide: >2
|
|
float AspectRatio = (float)Width / (float)Height;
|
|
|
|
if (FMath::Abs(AspectRatio - (4.0f / 3.0f)) < KINDA_SMALL_NUMBER)
|
|
{
|
|
Aspect = LOCTEXT("AspectRatio-4:3", "4:3");
|
|
}
|
|
else if (FMath::Abs(AspectRatio - (16.0f / 9.0f)) < KINDA_SMALL_NUMBER)
|
|
{
|
|
Aspect = LOCTEXT("AspectRatio-16:9", "16:9");
|
|
}
|
|
else if (FMath::Abs(AspectRatio - (16.0f / 10.0f)) < KINDA_SMALL_NUMBER)
|
|
{
|
|
Aspect = LOCTEXT("AspectRatio-16:10", "16:10");
|
|
}
|
|
else if (FMath::Abs(AspectRatio - (3.0f / 4.0f)) < KINDA_SMALL_NUMBER)
|
|
{
|
|
Aspect = LOCTEXT("AspectRatio-3:4", "3:4");
|
|
}
|
|
else if (FMath::Abs(AspectRatio - (9.0f / 16.0f)) < KINDA_SMALL_NUMBER)
|
|
{
|
|
Aspect = LOCTEXT("AspectRatio-9:16", "9:16");
|
|
}
|
|
else if (FMath::Abs(AspectRatio - (10.0f / 16.0f)) < KINDA_SMALL_NUMBER)
|
|
{
|
|
Aspect = LOCTEXT("AspectRatio-10:16", "10:16");
|
|
}
|
|
|
|
FNumberFormattingOptions Options;
|
|
Options.UseGrouping = false;
|
|
|
|
FFormatNamedArguments Args;
|
|
Args.Add(TEXT("X"), FText::AsNumber(Width, &Options));
|
|
Args.Add(TEXT("Y"), FText::AsNumber(Height, &Options));
|
|
Args.Add(TEXT("AspectRatio"), Aspect);
|
|
Args.Add(TEXT("RefreshRate"), RefreshRate);
|
|
|
|
return FText::Format(LOCTEXT("AspectRatio", "{X} x {Y}"), Args);
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE |