Browse Source

Merge pull request #4704 from IvanSavenko/video_autodetect

Enable autodetection of video scaling parameters
Ivan Savenko 1 year ago
parent
commit
3113b7f60a

+ 2 - 2
client/gui/CGuiHandler.cpp

@@ -250,8 +250,8 @@ void CGuiHandler::setStatusbar(std::shared_ptr<IStatusBar> newStatusBar)
 void CGuiHandler::onScreenResize(bool resolutionChanged)
 {
 	if(resolutionChanged)
-	{
 		screenHandler().onScreenResize();
-	}
+
 	windows().onScreenResize();
+	CCS->curh->onScreenResize();
 }

+ 5 - 0
client/gui/CursorHandler.cpp

@@ -312,3 +312,8 @@ void CursorHandler::changeCursor(Cursor::ShowType newShowType)
 			break;
 	}
 }
+
+void CursorHandler::onScreenResize()
+{
+	cursor->setImage(getCurrentImage(), getPivotOffset());
+}

+ 1 - 0
client/gui/CursorHandler.h

@@ -182,6 +182,7 @@ public:
 
 	void hide();
 	void show();
+	void onScreenResize();
 
 	/// change cursor's positions to (x, y)
 	void cursorMove(const int & x, const int & y);

+ 2 - 0
client/render/IScreenHandler.h

@@ -44,6 +44,8 @@ public:
 	/// Dimensions of logical output. Can be different if scaling is used
 	virtual Point getLogicalResolution() const = 0;
 
+	virtual int getInterfaceScalingPercentage() const = 0;
+
 	virtual int getScalingFactor() const = 0;
 
 	/// Window has focus

+ 15 - 3
client/renderSDL/CursorHardware.cpp

@@ -11,11 +11,14 @@
 #include "StdInc.h"
 #include "CursorHardware.h"
 
+#include "SDL_Extensions.h"
+
 #include "../gui/CGuiHandler.h"
 #include "../render/IScreenHandler.h"
 #include "../render/Colors.h"
 #include "../render/IImage.h"
-#include "SDL_Extensions.h"
+
+#include "../../lib/CConfigHandler.h"
 
 #include <SDL_render.h>
 #include <SDL_events.h>
@@ -45,19 +48,28 @@ void CursorHardware::setVisible(bool on)
 
 void CursorHardware::setImage(std::shared_ptr<IImage> image, const Point & pivotOffset)
 {
-	auto cursorSurface = CSDL_Ext::newSurface(image->dimensions() * GH.screenHandler().getScalingFactor());
+	int videoScalingSettings = GH.screenHandler().getInterfaceScalingPercentage();
+	float cursorScalingSettings = settings["video"]["cursorScalingFactor"].Float();
+	int cursorScalingPercent = videoScalingSettings * cursorScalingSettings;
+	Point cursorDimensions = image->dimensions() * GH.screenHandler().getScalingFactor();
+	Point cursorDimensionsScaled = image->dimensions() * cursorScalingPercent / 100;
+	Point pivotOffsetScaled = pivotOffset * cursorScalingPercent / 100 / GH.screenHandler().getScalingFactor();
+
+	auto cursorSurface = CSDL_Ext::newSurface(cursorDimensions);
 
 	CSDL_Ext::fillSurface(cursorSurface, CSDL_Ext::toSDL(Colors::TRANSPARENCY));
 
 	image->draw(cursorSurface, Point(0,0));
+	auto cursorSurfaceScaled = CSDL_Ext::scaleSurface(cursorSurface, cursorDimensionsScaled.x, cursorDimensionsScaled.y );
 
 	auto oldCursor = cursor;
-	cursor = SDL_CreateColorCursor(cursorSurface, pivotOffset.x, pivotOffset.y);
+	cursor = SDL_CreateColorCursor(cursorSurfaceScaled, pivotOffsetScaled.x, pivotOffsetScaled.y);
 
 	if (!cursor)
 		logGlobal->error("Failed to set cursor! SDL says %s", SDL_GetError());
 
 	SDL_FreeSurface(cursorSurface);
+	SDL_FreeSurface(cursorSurfaceScaled);
 
 	GH.dispatchMainThread([this, oldCursor](){
 		SDL_SetCursor(cursor);

+ 41 - 24
client/renderSDL/ScreenHandler.cpp

@@ -84,19 +84,39 @@ Rect ScreenHandler::convertLogicalPointsToWindow(const Rect & input) const
 	return result;
 }
 
-Point ScreenHandler::getPreferredLogicalResolution() const
+int ScreenHandler::getInterfaceScalingPercentage() const
 {
-	Point renderResolution = getRenderResolution();
-	double reservedAreaWidth = settings["video"]["reservedWidth"].Float();
-	Point availableResolution = Point(renderResolution.x * (1 - reservedAreaWidth), renderResolution.y);
-
 	auto [minimalScaling, maximalScaling] = getSupportedScalingRange();
 
 	int userScaling = settings["video"]["resolution"]["scaling"].Integer();
+
+	if (userScaling == 0) // autodetection
+	{
+#ifdef VCMI_MOBILE
+		// for mobiles - stay at maximum scaling unless we have large screen
+		// might be better to check screen DPI / physical dimensions, but way more complex, and may result in different edge cases, e.g. chromebooks / tv's
+		int preferredMinimalScaling = 200;
+#else
+		// for PC - avoid downscaling if possible
+		int preferredMinimalScaling = 100;
+#endif
+		// prefer a little below maximum - to give space for extended UI
+		int preferredMaximalScaling = maximalScaling * 10 / 12;
+		userScaling = std::max(std::min(maximalScaling, preferredMinimalScaling), preferredMaximalScaling);
+	}
+
 	int scaling = std::clamp(userScaling, minimalScaling, maximalScaling);
+	return scaling;
+}
 
-	Point logicalResolution = availableResolution * 100.0 / scaling;
+Point ScreenHandler::getPreferredLogicalResolution() const
+{
+	Point renderResolution = getRenderResolution();
+	double reservedAreaWidth = settings["video"]["reservedWidth"].Float();
 
+	int scaling = getInterfaceScalingPercentage();
+	Point availableResolution = Point(renderResolution.x * (1 - reservedAreaWidth), renderResolution.y);
+	Point logicalResolution = availableResolution * 100.0 / scaling;
 	return logicalResolution;
 }
 
@@ -335,25 +355,22 @@ EUpscalingFilter ScreenHandler::loadUpscalingFilter() const
 	if (filter != EUpscalingFilter::AUTO)
 		return filter;
 
-	// for now - always fallback to no filter
-	return EUpscalingFilter::NONE;
-
 	// else - autoselect
-//	Point outputResolution = getRenderResolution();
-//	Point logicalResolution = getPreferredLogicalResolution();
-//
-//	float scaleX = static_cast<float>(outputResolution.x) / logicalResolution.x;
-//	float scaleY = static_cast<float>(outputResolution.x) / logicalResolution.x;
-//	float scaling = std::min(scaleX, scaleY);
-//
-//	if (scaling <= 1.0f)
-//		return EUpscalingFilter::NONE;
-//	if (scaling <= 2.0f)
-//		return EUpscalingFilter::XBRZ_2;
-//	if (scaling <= 3.0f)
-//		return EUpscalingFilter::XBRZ_3;
-//
-//	return EUpscalingFilter::XBRZ_4;
+	Point outputResolution = getRenderResolution();
+	Point logicalResolution = getPreferredLogicalResolution();
+
+	float scaleX = static_cast<float>(outputResolution.x) / logicalResolution.x;
+	float scaleY = static_cast<float>(outputResolution.x) / logicalResolution.x;
+	float scaling = std::min(scaleX, scaleY);
+
+	if (scaling <= 1.001f)
+		return EUpscalingFilter::NONE; // running at original resolution or even lower than that - no need for xbrz
+	if (scaling <= 2.001f)
+		return EUpscalingFilter::XBRZ_2; // resolutions below 1200p (including 1080p / FullHD)
+	if (scaling <= 3.001f)
+		return EUpscalingFilter::XBRZ_3; // resolutions below 2400p (including 1440p and 2160p / 4K)
+
+	return EUpscalingFilter::XBRZ_4; // Only for massive displays, e.g. 8K
 }
 
 void ScreenHandler::selectUpscalingFilter()

+ 2 - 0
client/renderSDL/ScreenHandler.h

@@ -112,6 +112,8 @@ public:
 
 	int getScalingFactor() const final;
 
+	int getInterfaceScalingPercentage() const final;
+
 	std::vector<Point> getSupportedResolutions() const final;
 	std::vector<Point> getSupportedResolutions(int displayIndex) const;
 	std::tuple<int, int> getSupportedScalingRange() const final;

+ 1 - 3
client/windows/settings/GeneralOptionsTab.cpp

@@ -194,10 +194,8 @@ GeneralOptionsTab::GeneralOptionsTab()
 
 	build(config);
 
-	const auto & currentResolution = settings["video"]["resolution"];
-
 	std::shared_ptr<CLabel> scalingLabel = widget<CLabel>("scalingLabel");
-	scalingLabel->setText(scalingToLabelString(currentResolution["scaling"].Integer()));
+	scalingLabel->setText(scalingToLabelString(GH.screenHandler().getInterfaceScalingPercentage()));
 
 	std::shared_ptr<CLabel> longTouchLabel = widget<CLabel>("longTouchLabel");
 	if (longTouchLabel)

+ 11 - 9
config/schemas/settings.json

@@ -184,6 +184,7 @@
 				"targetfps",
 				"vsync",
 				"fontsType",
+				"cursorScalingFactor",
 				"fontScalingFactor",
 				"upscalingFilter",
 				"fontUpscalingFilter",
@@ -195,22 +196,19 @@
 					"additionalProperties" : false,
 					"required" : [ "width", "height", "scaling" ],
 					"properties" : {
-						"width"  : { "type" : "number" },
-						"height" : { "type" : "number" },
-						"scaling" : { "type" : "number" }
-					},
-					"defaultIOS" : {"width" : 800, "height" : 600, "scaling" : 200 },
-					"defaultAndroid" : {"width" : 800, "height" : 600, "scaling" : 200 },
-					"default" : {"width" : 800, "height" : 600, "scaling" : 100 }
+						"width"  :  { "type" : "number", "default" : 1280 },
+						"height" :  { "type" : "number", "default" :  720 },
+						"scaling" : { "type" : "number", "default" :    0 }
+					}
 				},
 				"reservedWidth" : {
 					"type" : "number",
-					"defaultIOS" : 0.1, // iOS camera cutout / notch is excluded from available area by SDL
+					"defaultIOS" : 0.1, // iOS camera cutout / notch is not excluded from available area by SDL, handle it this way
 					"default" : 0
 				},
 				"fullscreen" : {
 					"type" : "boolean",
-					"default" : false
+					"default" : true
 				},
 				"realFullscreen" : {
 					"type" : "boolean",
@@ -256,6 +254,10 @@
 					"enum" : [ "auto", "original", "scalable" ],
 					"default" : "auto"
 				},
+				"cursorScalingFactor" : {
+					"type" : "number",
+					"default" : 1
+				},
 				"fontScalingFactor" : {
 					"type" : "number",
 					"default" : 1

+ 32 - 3
launcher/settingsView/csettingsview_moc.cpp

@@ -127,7 +127,12 @@ void CSettingsView::loadSettings()
 #endif
 	fillValidScalingRange();
 
-	ui->spinBoxInterfaceScaling->setValue(settings["video"]["resolution"]["scaling"].Float());
+	ui->buttonScalingAuto->setChecked(settings["video"]["resolution"]["scaling"].Integer() == 0);
+	if (settings["video"]["resolution"]["scaling"].Integer() == 0)
+		ui->spinBoxInterfaceScaling->setValue(100);
+	else
+		ui->spinBoxInterfaceScaling->setValue(settings["video"]["resolution"]["scaling"].Float());
+
 	ui->spinBoxFramerateLimit->setValue(settings["video"]["targetfps"].Float());
 	ui->spinBoxFramerateLimit->setDisabled(settings["video"]["vsync"].Bool());
 	ui->sliderReservedArea->setValue(std::round(settings["video"]["reservedWidth"].Float() * 100));
@@ -174,6 +179,7 @@ void CSettingsView::loadSettings()
 	ui->sliderControllerSticksAcceleration->setValue(settings["input"]["controllerAxisScale"].Float() * 100);
 	ui->lineEditGameLobbyHost->setText(QString::fromStdString(settings["lobby"]["hostname"].String()));
 	ui->spinBoxNetworkPortLobby->setValue(settings["lobby"]["port"].Integer());
+	ui->buttonVSync->setChecked(settings["video"]["vsync"].Bool());
 
 	if (settings["video"]["fontsType"].String() == "auto")
 		ui->buttonFontAuto->setChecked(true);
@@ -195,7 +201,6 @@ void CSettingsView::loadSettings()
 void CSettingsView::loadToggleButtonSettings()
 {
 	setCheckbuttonState(ui->buttonShowIntro, settings["video"]["showIntro"].Bool());
-	setCheckbuttonState(ui->buttonVSync, settings["video"]["vsync"].Bool());
 	setCheckbuttonState(ui->buttonAutoCheck, settings["launcher"]["autoCheckRepositories"].Bool());
 
 	setCheckbuttonState(ui->buttonRepositoryDefault, settings["launcher"]["defaultRepositoryEnabled"].Bool());
@@ -212,10 +217,15 @@ void CSettingsView::loadToggleButtonSettings()
 	std::string cursorType = settings["video"]["cursor"].String();
 	int cursorTypeIndex = vstd::find_pos(cursorTypesList, cursorType);
 	setCheckbuttonState(ui->buttonCursorType, cursorTypeIndex);
+	ui->sliderScalingCursor->setDisabled(cursorType == "software"); // Not supported
+	ui->labelScalingCursorValue->setDisabled(cursorType == "software"); // Not supported
 
 	int fontScalingPercentage = settings["video"]["fontScalingFactor"].Float() * 100;
 	ui->sliderScalingFont->setValue(fontScalingPercentage / 5);
 
+	int cursorScalingPercentage = settings["video"]["cursorScalingFactor"].Float() * 100;
+	ui->sliderScalingCursor->setValue(cursorScalingPercentage / 5);
+
 }
 
 void CSettingsView::fillValidResolutions()
@@ -494,6 +504,8 @@ void CSettingsView::on_buttonCursorType_toggled(bool value)
 	Settings node = settings.write["video"]["cursor"];
 	node->String() = cursorTypesList[value ? 1 : 0];
 	updateCheckbuttonText(ui->buttonCursorType);
+	ui->sliderScalingCursor->setDisabled(value == 1); // Not supported
+	ui->labelScalingCursorValue->setDisabled(value == 1); // Not supported
 }
 
 void CSettingsView::loadTranslation()
@@ -627,7 +639,6 @@ void CSettingsView::on_buttonVSync_toggled(bool value)
 	Settings node = settings.write["video"]["vsync"];
 	node->Bool() = value;
 	ui->spinBoxFramerateLimit->setDisabled(settings["video"]["vsync"].Bool());
-	updateCheckbuttonText(ui->buttonVSync);
 }
 
 void CSettingsView::on_comboBoxEnemyPlayerAI_currentTextChanged(const QString &arg1)
@@ -816,3 +827,21 @@ void CSettingsView::on_buttonValidationFull_clicked(bool checked)
 	Settings node = settings.write["mods"]["validation"];
 	node->String() = "full";
 }
+
+void CSettingsView::on_sliderScalingCursor_valueChanged(int value)
+{
+	int actualValuePercentage = value * 5;
+	ui->labelScalingCursorValue->setText(QString("%1%").arg(actualValuePercentage));
+	Settings node = settings.write["video"]["cursorScalingFactor"];
+	node->Float() = actualValuePercentage / 100.0;
+}
+
+void CSettingsView::on_buttonScalingAuto_toggled(bool checked)
+{
+	ui->spinBoxInterfaceScaling->setDisabled(checked);
+	ui->spinBoxInterfaceScaling->setValue(100);
+
+	Settings node = settings.write["video"]["resolution"]["scaling"];
+	node->Integer() = checked ? 0 : 100;
+}
+

+ 4 - 0
launcher/settingsView/csettingsview_moc.h

@@ -97,6 +97,10 @@ private slots:
 
 	void on_buttonValidationFull_clicked(bool checked);
 
+	void on_sliderScalingCursor_valueChanged(int value);
+
+	void on_buttonScalingAuto_toggled(bool checked);
+
 private:
 	Ui::CSettingsView * ui;
 

File diff suppressed because it is too large
+ 445 - 370
launcher/settingsView/csettingsview_moc.ui


Some files were not shown because too many files changed in this diff