From e17d37b8bc6c1fd7fdb50b8ebbbe9a0cc4c15d6f Mon Sep 17 00:00:00 2001
From: Connor McLaughlin <stenzek@gmail.com>
Date: Sat, 27 Feb 2021 13:53:03 +1000
Subject: [PATCH] HostDisplay: Add stretch option

---
 android/app/src/main/res/values/strings.xml   |  2 ++
 .../src/main/res/xml/display_preferences.xml  | 17 ++++++++++---
 src/core/host_display.cpp                     |  6 ++---
 src/core/host_display.h                       |  2 ++
 src/core/host_interface.cpp                   | 25 ++++++++++++++-----
 src/core/settings.cpp                         |  2 ++
 src/core/settings.h                           |  1 +
 src/duckstation-qt/displaysettingswidget.cpp  |  8 +++++-
 src/duckstation-qt/displaysettingswidget.ui   | 21 ++++++++++------
 src/duckstation-qt/qthostinterface.cpp        |  2 +-
 10 files changed, 65 insertions(+), 21 deletions(-)

diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
index a6ef6a3ad..8bbb7e618 100644
--- a/android/app/src/main/res/values/strings.xml
+++ b/android/app/src/main/res/values/strings.xml
@@ -212,4 +212,6 @@
     <string name="save_state_info_quick_save">Quick Save</string>
     <string name="settings_osd_show_show_resolution">Show Resolution</string>
     <string name="settings_summary_osd_show_resolution">Shows the resolution the game is rendering at in the top-right corner of the display.</string>
+    <string name="settings_summary_display_stretch">Stretches the active display to fill the screen.</string>
+    <string name="settings_display_stretch">Stretch To Fill</string>
 </resources>
diff --git a/android/app/src/main/res/xml/display_preferences.xml b/android/app/src/main/res/xml/display_preferences.xml
index aedd2a050..e2f87fc98 100644
--- a/android/app/src/main/res/xml/display_preferences.xml
+++ b/android/app/src/main/res/xml/display_preferences.xml
@@ -43,18 +43,28 @@
         app:useSimpleSummaryProvider="true"
         app:iconSpaceReserved="false" />
 
+    <SwitchPreferenceCompat
+        app:key="Display/IntegerScaling"
+        app:title="@string/settings_integer_upscaling"
+        app:defaultValue="false"
+        app:disableDependentsState="true"
+        app:summary="@string/settings_summary_integer_upscaling"
+        app:iconSpaceReserved="false" />
+
     <SwitchPreferenceCompat
         app:key="Display/LinearFiltering"
         app:title="@string/settings_linear_upscaling"
         app:defaultValue="true"
+        app:dependency="Display/IntegerScaling"
         app:summary="@string/settings_summary_linear_upscaling"
         app:iconSpaceReserved="false" />
 
     <SwitchPreferenceCompat
-        app:key="Display/IntegerScaling"
-        app:title="@string/settings_integer_upscaling"
+        app:key="Display/Stretch"
+        app:title="@string/settings_display_stretch"
         app:defaultValue="false"
-        app:summary="@string/settings_summary_integer_upscaling"
+        app:dependency="Display/IntegerScaling"
+        app:summary="@string/settings_summary_display_stretch"
         app:iconSpaceReserved="false" />
 
     <SwitchPreferenceCompat
@@ -63,6 +73,7 @@
         app:defaultValue="true"
         app:summary="@string/settings_summary_osd_show_messages"
         app:iconSpaceReserved="false" />
+
     <SwitchPreferenceCompat
         app:key="Display/ShowSpeed"
         app:title="@string/settings_osd_show_speed"
diff --git a/src/core/host_display.cpp b/src/core/host_display.cpp
index 9d8758bae..b7af94a29 100644
--- a/src/core/host_display.cpp
+++ b/src/core/host_display.cpp
@@ -148,9 +148,11 @@ void HostDisplay::CalculateDrawRect(s32 window_width, s32 window_height, float*
                                     float* out_top_padding, float* out_scale, float* out_x_scale,
                                     bool apply_aspect_ratio /* = true */) const
 {
+  const float window_ratio = static_cast<float>(window_width) / static_cast<float>(window_height);
+  const float display_aspect_ratio = m_display_stretch ? window_ratio : m_display_aspect_ratio;
   const float x_scale =
     apply_aspect_ratio ?
-      (m_display_aspect_ratio / (static_cast<float>(m_display_width) / static_cast<float>(m_display_height))) :
+      (display_aspect_ratio / (static_cast<float>(m_display_width) / static_cast<float>(m_display_height))) :
       1.0f;
   const float display_width = static_cast<float>(m_display_width) * x_scale;
   const float display_height = static_cast<float>(m_display_height);
@@ -162,8 +164,6 @@ void HostDisplay::CalculateDrawRect(s32 window_width, s32 window_height, float*
     *out_x_scale = x_scale;
 
   // now fit it within the window
-  const float window_ratio = static_cast<float>(window_width) / static_cast<float>(window_height);
-
   float scale;
   if ((display_width / display_height) >= window_ratio)
   {
diff --git a/src/core/host_display.h b/src/core/host_display.h
index 9f46246d4..03601c4d6 100644
--- a/src/core/host_display.h
+++ b/src/core/host_display.h
@@ -196,6 +196,7 @@ public:
   void SetDisplayTopMargin(s32 height) { m_display_top_margin = height; }
   void SetDisplayIntegerScaling(bool enabled) { m_display_integer_scaling = enabled; }
   void SetDisplayAlignment(Alignment alignment) { m_display_alignment = alignment; }
+  void SetDisplayStretch(bool stretch) { m_display_stretch = stretch; }
 
   /// Sets the software cursor to the specified texture. Ownership of the texture is transferred.
   void SetSoftwareCursor(std::unique_ptr<HostDisplayTexture> texture, float scale = 1.0f);
@@ -276,4 +277,5 @@ protected:
   bool m_display_linear_filtering = false;
   bool m_display_changed = false;
   bool m_display_integer_scaling = false;
+  bool m_display_stretch = false;
 };
diff --git a/src/core/host_interface.cpp b/src/core/host_interface.cpp
index 750d3a185..88ec11d21 100644
--- a/src/core/host_interface.cpp
+++ b/src/core/host_interface.cpp
@@ -109,6 +109,7 @@ bool HostInterface::BootSystem(const SystemBootParameters& parameters)
   // set host display settings
   m_display->SetDisplayLinearFiltering(g_settings.display_linear_filtering);
   m_display->SetDisplayIntegerScaling(g_settings.display_integer_scaling);
+  m_display->SetDisplayStretch(g_settings.display_stretch);
 
   // create the audio stream. this will never fail, since we'll just fall back to null
   CreateAudioStream();
@@ -545,6 +546,7 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si)
   si.SetBoolValue("Display", "Force4_3For24Bit", false);
   si.SetBoolValue("Display", "LinearFiltering", true);
   si.SetBoolValue("Display", "IntegerScaling", false);
+  si.SetBoolValue("Display", "Stretch", false);
   si.SetBoolValue("Display", "PostProcessing", false);
   si.SetBoolValue("Display", "ShowOSDMessages", true);
   si.SetBoolValue("Display", "ShowFPS", false);
@@ -593,8 +595,7 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si)
   si.SetStringValue("MemoryCards", "Card2Path", "memcards" FS_OSPATH_SEPARATOR_STR "shared_card_2.mcd");
   si.SetBoolValue("MemoryCards", "UsePlaylistTitle", true);
 
-  si.SetStringValue("ControllerPorts", "MultitapMode",
-                    Settings::GetMultitapModeName(Settings::DEFAULT_MULTITAP_MODE));
+  si.SetStringValue("ControllerPorts", "MultitapMode", Settings::GetMultitapModeName(Settings::DEFAULT_MULTITAP_MODE));
 
   si.SetStringValue("Logging", "LogLevel", Settings::GetLogLevelName(Settings::DEFAULT_LOG_LEVEL));
   si.SetStringValue("Logging", "LogFilter", "");
@@ -655,6 +656,12 @@ void HostInterface::FixIncompatibleSettings(bool display_osd_messages)
     g_settings.display_linear_filtering = false;
   }
 
+  if (g_settings.display_stretch && g_settings.display_linear_filtering)
+  {
+    Log_WarningPrintf("Disabling stretch due to integer upscaling.");
+    g_settings.display_stretch = false;
+  }
+
   if (g_settings.gpu_pgxp_enable)
   {
     if (g_settings.gpu_renderer == GPURenderer::Software)
@@ -884,11 +891,17 @@ void HostInterface::CheckForSettingsChanges(const Settings& old_settings)
   if (g_settings.multitap_mode != old_settings.multitap_mode)
     System::UpdateMultitaps();
 
-  if (m_display && g_settings.display_linear_filtering != old_settings.display_linear_filtering)
-    m_display->SetDisplayLinearFiltering(g_settings.display_linear_filtering);
+  if (m_display)
+  {
+    if (g_settings.display_linear_filtering != old_settings.display_linear_filtering)
+      m_display->SetDisplayLinearFiltering(g_settings.display_linear_filtering);
 
-  if (m_display && g_settings.display_integer_scaling != old_settings.display_integer_scaling)
-    m_display->SetDisplayIntegerScaling(g_settings.display_integer_scaling);
+    if (g_settings.display_integer_scaling != old_settings.display_integer_scaling)
+      m_display->SetDisplayIntegerScaling(g_settings.display_integer_scaling);
+
+    if (g_settings.display_stretch != old_settings.display_stretch)
+      m_display->SetDisplayStretch(g_settings.display_stretch);
+  }
 }
 
 void HostInterface::SetUserDirectoryToProgramDirectory()
diff --git a/src/core/settings.cpp b/src/core/settings.cpp
index 937ca5992..c3816ec43 100644
--- a/src/core/settings.cpp
+++ b/src/core/settings.cpp
@@ -214,6 +214,7 @@ void Settings::Load(SettingsInterface& si)
   display_line_end_offset = static_cast<s8>(si.GetIntValue("Display", "LineEndOffset", 0));
   display_linear_filtering = si.GetBoolValue("Display", "LinearFiltering", true);
   display_integer_scaling = si.GetBoolValue("Display", "IntegerScaling", false);
+  display_stretch = si.GetBoolValue("Display", "Stretch", false);
   display_post_processing = si.GetBoolValue("Display", "PostProcessing", false);
   display_show_osd_messages = si.GetBoolValue("Display", "ShowOSDMessages", true);
   display_show_fps = si.GetBoolValue("Display", "ShowFPS", false);
@@ -382,6 +383,7 @@ void Settings::Save(SettingsInterface& si) const
   si.SetStringValue("Display", "AspectRatio", GetDisplayAspectRatioName(display_aspect_ratio));
   si.SetBoolValue("Display", "LinearFiltering", display_linear_filtering);
   si.SetBoolValue("Display", "IntegerScaling", display_integer_scaling);
+  si.SetBoolValue("Display", "Stretch", display_stretch);
   si.SetBoolValue("Display", "PostProcessing", display_post_processing);
   si.SetBoolValue("Display", "ShowOSDMessages", display_show_osd_messages);
   si.SetBoolValue("Display", "ShowFPS", display_show_fps);
diff --git a/src/core/settings.h b/src/core/settings.h
index 21b2b8562..24f931439 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -134,6 +134,7 @@ struct Settings
   bool gpu_24bit_chroma_smoothing = false;
   bool display_linear_filtering = true;
   bool display_integer_scaling = false;
+  bool display_stretch = false;
   bool display_post_processing = false;
   bool display_show_osd_messages = false;
   bool display_show_fps = false;
diff --git a/src/duckstation-qt/displaysettingswidget.cpp b/src/duckstation-qt/displaysettingswidget.cpp
index 36d0028b2..e647a893a 100644
--- a/src/duckstation-qt/displaysettingswidget.cpp
+++ b/src/duckstation-qt/displaysettingswidget.cpp
@@ -35,6 +35,8 @@ DisplaySettingsWidget::DisplaySettingsWidget(QtHostInterface* host_interface, QW
                                                "LinearFiltering");
   SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.displayIntegerScaling, "Display",
                                                "IntegerScaling");
+  SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.displayStretch, "Display",
+                                               "Stretch");
   SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.vsync, "Display", "VSync");
   SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.displayAllFrames, "Display", "DisplayAllFrames",
                                                false);
@@ -99,6 +101,9 @@ DisplaySettingsWidget::DisplaySettingsWidget(QtHostInterface* host_interface, QW
     m_ui.displayIntegerScaling, tr("Integer Upscaling"), tr("Unchecked"),
     tr("Adds padding to the display area to ensure that the ratio between pixels on the host to "
        "pixels in the console is an integer number. <br>May result in a sharper image in some 2D games."));
+  dialog->registerWidgetHelp(
+    m_ui.displayIntegerScaling, tr("Stretch To Fill"), tr("Unchecked"),
+    tr("Fills the window with the active display area, regardless of the aspect ratio."));
   dialog->registerWidgetHelp(
     m_ui.vsync, tr("VSync"), tr("Checked"),
     tr("Enable this option to match DuckStation's refresh rate with your current monitor or screen. "
@@ -268,4 +273,5 @@ void DisplaySettingsWidget::onGPUFullscreenModeIndexChanged()
 void DisplaySettingsWidget::onIntegerFilteringChanged()
 {
   m_ui.displayLinearFiltering->setEnabled(!m_ui.displayIntegerScaling->isChecked());
-}
\ No newline at end of file
+  m_ui.displayStretch->setEnabled(!m_ui.displayIntegerScaling->isChecked());
+}
diff --git a/src/duckstation-qt/displaysettingswidget.ui b/src/duckstation-qt/displaysettingswidget.ui
index 4ddc08a8a..5a5d315db 100644
--- a/src/duckstation-qt/displaysettingswidget.ui
+++ b/src/duckstation-qt/displaysettingswidget.ui
@@ -6,8 +6,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>448</width>
-    <height>430</height>
+    <width>485</width>
+    <height>525</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -134,18 +134,25 @@
        <widget class="QComboBox" name="gpuDownsampleMode"/>
       </item>
       <item row="3" column="0" colspan="2">
-       <layout class="QGridLayout" name="gridLayout_2">
+       <layout class="QGridLayout" name="gridLayout">
         <item row="0" column="0">
-         <widget class="QCheckBox" name="displayLinearFiltering">
+         <widget class="QCheckBox" name="displayIntegerScaling">
           <property name="text">
-           <string>Linear Upscaling</string>
+           <string>Integer Upscaling</string>
           </property>
          </widget>
         </item>
         <item row="0" column="1">
-         <widget class="QCheckBox" name="displayIntegerScaling">
+         <widget class="QCheckBox" name="displayStretch">
           <property name="text">
-           <string>Integer Upscaling</string>
+           <string>Stretch To Fill</string>
+          </property>
+         </widget>
+        </item>
+        <item row="1" column="0">
+         <widget class="QCheckBox" name="displayLinearFiltering">
+          <property name="text">
+           <string>Linear Upscaling</string>
           </property>
          </widget>
         </item>
diff --git a/src/duckstation-qt/qthostinterface.cpp b/src/duckstation-qt/qthostinterface.cpp
index 9e69505fb..960862a7f 100644
--- a/src/duckstation-qt/qthostinterface.cpp
+++ b/src/duckstation-qt/qthostinterface.cpp
@@ -447,11 +447,11 @@ void QtHostInterface::onDisplayWindowMouseWheelEvent(const QPoint& delta_angle)
 
 void QtHostInterface::onDisplayWindowResized(int width, int height)
 {
-  Log_WarningPrintf("resize %dx%d", width, height);
   // this can be null if it was destroyed and the main thread is late catching up
   if (!m_display)
     return;
 
+  Log_DevPrintf("Display window resized to %dx%d", width, height);
   m_display->ResizeRenderWindow(width, height);
   OnHostDisplayResized(width, height, m_display->GetWindowScale());