diff --git a/src/core/fullscreen_ui.cpp b/src/core/fullscreen_ui.cpp
index fc9d44d6a..72b5541ae 100644
--- a/src/core/fullscreen_ui.cpp
+++ b/src/core/fullscreen_ui.cpp
@@ -2484,7 +2484,7 @@ void FullscreenUI::DrawSettingsWindow()
     static constexpr std::array<const char*, static_cast<u32>(SettingsPage::Count)> titles = {
       {FSUI_NSTR("Summary"), FSUI_NSTR("Interface Settings"), FSUI_NSTR("Console Settings"),
        FSUI_NSTR("Emulation Settings"), FSUI_NSTR("BIOS Settings"), FSUI_NSTR("Controller Settings"),
-       FSUI_NSTR("Hotkey Settings"), FSUI_NSTR("Memory Card Settings"), FSUI_NSTR("Display Settings"),
+       FSUI_NSTR("Hotkey Settings"), FSUI_NSTR("Memory Card Settings"), FSUI_NSTR("Graphics Settings"),
        FSUI_NSTR("Post-Processing Settings"), FSUI_NSTR("Audio Settings"), FSUI_NSTR("Achievements Settings"),
        FSUI_NSTR("Advanced Settings")}};
 
@@ -3898,7 +3898,7 @@ void FullscreenUI::DrawDisplaySettingsPage()
   MenuHeading(FSUI_CSTR("Rendering"));
 
   DrawIntListSetting(
-    bsi, FSUI_CSTR("Internal Resolution Scale"),
+    bsi, FSUI_CSTR("Internal Resolution"),
     FSUI_CSTR("Scales internal VRAM resolution by the specified multiplier. Some games require 1x VRAM resolution."),
     "GPU", "ResolutionScale", 1, resolution_scales.data(), resolution_scales.size(), true, 0, is_hardware);
 
@@ -3975,9 +3975,19 @@ void FullscreenUI::DrawDisplaySettingsPage()
     "Display", "Scaling", Settings::DEFAULT_DISPLAY_SCALING, &Settings::ParseDisplayScaling,
     &Settings::GetDisplayScalingName, &Settings::GetDisplayScalingDisplayName, DisplayScalingMode::Count);
 
-  DrawToggleSetting(bsi, FSUI_CSTR("Internal Resolution Screenshots"),
-                    FSUI_CSTR("Saves screenshots at internal render resolution and without postprocessing."), "Display",
-                    "InternalResolutionScreenshots", false);
+  DrawEnumSetting(bsi, FSUI_CSTR("Screenshot Size"),
+                  FSUI_CSTR("Determines the size of screenshots created by DuckStation."), "Display", "ScreenshotMode",
+                  Settings::DEFAULT_DISPLAY_SCREENSHOT_MODE, &Settings::ParseDisplayScreenshotMode,
+                  &Settings::GetDisplayScreenshotModeName, &Settings::GetDisplayScreenshotModeDisplayName,
+                  DisplayScreenshotMode::Count);
+  DrawEnumSetting(bsi, FSUI_CSTR("Screenshot Format"),
+                  FSUI_CSTR("Determines the format that screenshots will be saved/compressed with."), "Display",
+                  "ScreenshotFormat", Settings::DEFAULT_DISPLAY_SCREENSHOT_FORMAT,
+                  &Settings::ParseDisplayScreenshotFormat, &Settings::GetDisplayScreenshotFormatName,
+                  &Settings::GetDisplayScreenshotFormatDisplayName, DisplayScreenshotFormat::Count);
+  DrawIntRangeSetting(bsi, FSUI_CSTR("Screenshot Quality"),
+                      FSUI_CSTR("Selects the quality at which screenshots will be compressed."), "Display",
+                      "ScreenshotQuality", Settings::DEFAULT_DISPLAY_SCREENSHOT_QUALITY, 1, 100, "%d%%");
 
   MenuHeading(FSUI_CSTR("Enhancements"));
   DrawToggleSetting(
@@ -6651,7 +6661,9 @@ TRANSLATE_NOOP("FullscreenUI", "Determines quality of audio when not running at
 TRANSLATE_NOOP("FullscreenUI", "Determines that field that the game list will be sorted by.");
 TRANSLATE_NOOP("FullscreenUI", "Determines the amount of audio buffered before being pulled by the host API.");
 TRANSLATE_NOOP("FullscreenUI", "Determines the emulated hardware type.");
+TRANSLATE_NOOP("FullscreenUI", "Determines the format that screenshots will be saved/compressed with.");
 TRANSLATE_NOOP("FullscreenUI", "Determines the position on the screen when black borders must be added.");
+TRANSLATE_NOOP("FullscreenUI", "Determines the size of screenshots created by DuckStation.");
 TRANSLATE_NOOP("FullscreenUI", "Determines whether a prompt will be displayed to confirm shutting down the emulator/game when the hotkey is pressed.");
 TRANSLATE_NOOP("FullscreenUI", "Device Settings");
 TRANSLATE_NOOP("FullscreenUI", "Disable All Enhancements");
@@ -6754,6 +6766,7 @@ TRANSLATE_NOOP("FullscreenUI", "Genre: %s");
 TRANSLATE_NOOP("FullscreenUI", "GitHub Repository");
 TRANSLATE_NOOP("FullscreenUI", "Global Slot {0} - {1}##global_slot_{0}");
 TRANSLATE_NOOP("FullscreenUI", "Global Slot {0}##global_slot_{0}");
+TRANSLATE_NOOP("FullscreenUI", "Graphics Settings");
 TRANSLATE_NOOP("FullscreenUI", "Hardcore Mode");
 TRANSLATE_NOOP("FullscreenUI", "Hardcore mode will be enabled on next game restart.");
 TRANSLATE_NOOP("FullscreenUI", "Hide Cursor In Fullscreen");
@@ -6773,8 +6786,7 @@ TRANSLATE_NOOP("FullscreenUI", "Input profile '{}' loaded.");
 TRANSLATE_NOOP("FullscreenUI", "Input profile '{}' saved.");
 TRANSLATE_NOOP("FullscreenUI", "Integration");
 TRANSLATE_NOOP("FullscreenUI", "Interface Settings");
-TRANSLATE_NOOP("FullscreenUI", "Internal Resolution Scale");
-TRANSLATE_NOOP("FullscreenUI", "Internal Resolution Screenshots");
+TRANSLATE_NOOP("FullscreenUI", "Internal Resolution");
 TRANSLATE_NOOP("FullscreenUI", "Issue Tracker");
 TRANSLATE_NOOP("FullscreenUI", "Last Played");
 TRANSLATE_NOOP("FullscreenUI", "Last Played: %s");
@@ -6926,7 +6938,6 @@ TRANSLATE_NOOP("FullscreenUI", "Save Screenshot");
 TRANSLATE_NOOP("FullscreenUI", "Save State");
 TRANSLATE_NOOP("FullscreenUI", "Save State On Exit");
 TRANSLATE_NOOP("FullscreenUI", "Saved {:%c}");
-TRANSLATE_NOOP("FullscreenUI", "Saves screenshots at internal render resolution and without postprocessing.");
 TRANSLATE_NOOP("FullscreenUI", "Saves state periodically so you can rewind any mistakes while playing.");
 TRANSLATE_NOOP("FullscreenUI", "Scaled Dithering");
 TRANSLATE_NOOP("FullscreenUI", "Scales internal VRAM resolution by the specified multiplier. Some games require 1x VRAM resolution.");
@@ -6935,6 +6946,9 @@ TRANSLATE_NOOP("FullscreenUI", "Scaling");
 TRANSLATE_NOOP("FullscreenUI", "Scan For New Games");
 TRANSLATE_NOOP("FullscreenUI", "Scanning Subdirectories");
 TRANSLATE_NOOP("FullscreenUI", "Screen Display");
+TRANSLATE_NOOP("FullscreenUI", "Screenshot Format");
+TRANSLATE_NOOP("FullscreenUI", "Screenshot Quality");
+TRANSLATE_NOOP("FullscreenUI", "Screenshot Size");
 TRANSLATE_NOOP("FullscreenUI", "Search Directories");
 TRANSLATE_NOOP("FullscreenUI", "Seek Speedup");
 TRANSLATE_NOOP("FullscreenUI", "Select Device");
@@ -6942,6 +6956,7 @@ TRANSLATE_NOOP("FullscreenUI", "Select Disc Image");
 TRANSLATE_NOOP("FullscreenUI", "Select Macro {} Binds");
 TRANSLATE_NOOP("FullscreenUI", "Selects the GPU to use for rendering.");
 TRANSLATE_NOOP("FullscreenUI", "Selects the percentage of the normal clock speed the emulated hardware will run at.");
+TRANSLATE_NOOP("FullscreenUI", "Selects the quality at which screenshots will be compressed.");
 TRANSLATE_NOOP("FullscreenUI", "Selects the resolution scale that will be applied to the final image. 1x will downsample to the original console resolution.");
 TRANSLATE_NOOP("FullscreenUI", "Selects the resolution to use in fullscreen modes.");
 TRANSLATE_NOOP("FullscreenUI", "Selects the view that the game list will open to.");
diff --git a/src/core/gpu.cpp b/src/core/gpu.cpp
index 64cff9f10..9fdfeda77 100644
--- a/src/core/gpu.cpp
+++ b/src/core/gpu.cpp
@@ -1906,8 +1906,8 @@ Common::Rectangle<s32> GPU::CalculateDrawRect(s32 window_width, s32 window_heigh
 }
 
 static bool CompressAndWriteTextureToFile(u32 width, u32 height, std::string filename, FileSystem::ManagedCFilePtr fp,
-                                          bool clear_alpha, bool flip_y, u32 resize_width, u32 resize_height,
-                                          std::vector<u8> texture_data, u32 texture_data_stride,
+                                          u8 quality, bool clear_alpha, bool flip_y, u32 resize_width,
+                                          u32 resize_height, std::vector<u8> texture_data, u32 texture_data_stride,
                                           GPUTexture::Format texture_format)
 {
 
@@ -1964,12 +1964,13 @@ static bool CompressAndWriteTextureToFile(u32 width, u32 height, std::string fil
   bool result = false;
   if (StringUtil::Strcasecmp(extension, ".png") == 0)
   {
+    // TODO: Use quality... libpng is better.
     result =
       (stbi_write_png_to_func(write_func, fp.get(), width, height, 4, texture_data.data(), texture_data_stride) != 0);
   }
   else if (StringUtil::Strcasecmp(extension, ".jpg") == 0)
   {
-    result = (stbi_write_jpg_to_func(write_func, fp.get(), width, height, 4, texture_data.data(), 95) != 0);
+    result = (stbi_write_jpg_to_func(write_func, fp.get(), width, height, 4, texture_data.data(), quality) != 0);
   }
   else if (StringUtil::Strcasecmp(extension, ".tga") == 0)
   {
@@ -2072,14 +2073,16 @@ bool GPU::WriteDisplayTextureToFile(std::string filename, bool full_resolution /
 
   if (!compress_on_thread)
   {
-    return CompressAndWriteTextureToFile(read_width, read_height, std::move(filename), std::move(fp), clear_alpha,
-                                         flip_y, resize_width, resize_height, std::move(texture_data),
-                                         texture_data_stride, m_display_texture->GetFormat());
+    return CompressAndWriteTextureToFile(read_width, read_height, std::move(filename), std::move(fp),
+                                         g_settings.display_screenshot_quality, clear_alpha, flip_y, resize_width,
+                                         resize_height, std::move(texture_data), texture_data_stride,
+                                         m_display_texture->GetFormat());
   }
 
   std::thread compress_thread(CompressAndWriteTextureToFile, read_width, read_height, std::move(filename),
-                              std::move(fp), clear_alpha, flip_y, resize_width, resize_height, std::move(texture_data),
-                              texture_data_stride, m_display_texture->GetFormat());
+                              std::move(fp), g_settings.display_screenshot_quality, clear_alpha, flip_y, resize_width,
+                              resize_height, std::move(texture_data), texture_data_stride,
+                              m_display_texture->GetFormat());
   compress_thread.detach();
   return true;
 }
@@ -2131,53 +2134,61 @@ bool GPU::RenderScreenshotToBuffer(u32 width, u32 height, const Common::Rectangl
   return true;
 }
 
-bool GPU::RenderScreenshotToFile(std::string filename, bool internal_resolution /* = false */,
-                                 bool compress_on_thread /* = false */)
+bool GPU::RenderScreenshotToFile(std::string filename, DisplayScreenshotMode mode, u8 quality, bool compress_on_thread)
 {
   u32 width = g_gpu_device->GetWindowWidth();
   u32 height = g_gpu_device->GetWindowHeight();
   Common::Rectangle<s32> draw_rect = CalculateDrawRect(width, height);
 
+  const bool internal_resolution = (mode != DisplayScreenshotMode::ScreenResolution);
   if (internal_resolution && m_display_texture_view_width != 0 && m_display_texture_view_height != 0)
   {
-    const u32 draw_width = static_cast<u32>(draw_rect.GetWidth());
-    const u32 draw_height = static_cast<u32>(draw_rect.GetHeight());
-
-    // If internal res, scale the computed draw rectangle to the internal res.
-    // We re-use the draw rect because it's already been AR corrected.
-    const float sar =
-      static_cast<float>(m_display_texture_view_width) / static_cast<float>(m_display_texture_view_height);
-    const float dar = static_cast<float>(draw_width) / static_cast<float>(draw_height);
-    if (sar >= dar)
+    if (mode == DisplayScreenshotMode::InternalResolution)
+    {
+      const u32 draw_width = static_cast<u32>(draw_rect.GetWidth());
+      const u32 draw_height = static_cast<u32>(draw_rect.GetHeight());
+
+      // If internal res, scale the computed draw rectangle to the internal res.
+      // We re-use the draw rect because it's already been AR corrected.
+      const float sar =
+        static_cast<float>(m_display_texture_view_width) / static_cast<float>(m_display_texture_view_height);
+      const float dar = static_cast<float>(draw_width) / static_cast<float>(draw_height);
+      if (sar >= dar)
+      {
+        // stretch height, preserve width
+        const float scale = static_cast<float>(m_display_texture_view_width) / static_cast<float>(draw_width);
+        width = m_display_texture_view_width;
+        height = static_cast<u32>(std::round(static_cast<float>(draw_height) * scale));
+      }
+      else
+      {
+        // stretch width, preserve height
+        const float scale = static_cast<float>(m_display_texture_view_height) / static_cast<float>(draw_height);
+        width = static_cast<u32>(std::round(static_cast<float>(draw_width) * scale));
+        height = m_display_texture_view_height;
+      }
+
+      // DX11 won't go past 16K texture size.
+      const u32 max_texture_size = g_gpu_device->GetMaxTextureSize();
+      if (width > max_texture_size)
+      {
+        height = static_cast<u32>(static_cast<float>(height) /
+                                  (static_cast<float>(width) / static_cast<float>(max_texture_size)));
+        width = max_texture_size;
+      }
+      if (height > max_texture_size)
+      {
+        height = max_texture_size;
+        width = static_cast<u32>(static_cast<float>(width) /
+                                 (static_cast<float>(height) / static_cast<float>(max_texture_size)));
+      }
+    }
+    else // if (mode == DisplayScreenshotMode::UncorrectedInternalResolution)
     {
-      // stretch height, preserve width
-      const float scale = static_cast<float>(m_display_texture_view_width) / static_cast<float>(draw_width);
       width = m_display_texture_view_width;
-      height = static_cast<u32>(std::round(static_cast<float>(draw_height) * scale));
-    }
-    else
-    {
-      // stretch width, preserve height
-      const float scale = static_cast<float>(m_display_texture_view_height) / static_cast<float>(draw_height);
-      width = static_cast<u32>(std::round(static_cast<float>(draw_width) * scale));
       height = m_display_texture_view_height;
     }
 
-    // DX11 won't go past 16K texture size.
-    constexpr u32 MAX_TEXTURE_SIZE = 16384;
-    if (width > MAX_TEXTURE_SIZE)
-    {
-      height = static_cast<u32>(static_cast<float>(height) /
-                                (static_cast<float>(width) / static_cast<float>(MAX_TEXTURE_SIZE)));
-      width = MAX_TEXTURE_SIZE;
-    }
-    if (height > MAX_TEXTURE_SIZE)
-    {
-      height = MAX_TEXTURE_SIZE;
-      width = static_cast<u32>(static_cast<float>(width) /
-                               (static_cast<float>(height) / static_cast<float>(MAX_TEXTURE_SIZE)));
-    }
-
     // Remove padding, it's not part of the framebuffer.
     draw_rect.Set(0, 0, static_cast<s32>(width), static_cast<s32>(height));
   }
@@ -2203,14 +2214,14 @@ bool GPU::RenderScreenshotToFile(std::string filename, bool internal_resolution
 
   if (!compress_on_thread)
   {
-    return CompressAndWriteTextureToFile(width, height, std::move(filename), std::move(fp), true,
+    return CompressAndWriteTextureToFile(width, height, std::move(filename), std::move(fp), quality, true,
                                          g_gpu_device->UsesLowerLeftOrigin(), width, height, std::move(pixels),
                                          pixels_stride, pixels_format);
   }
 
-  std::thread compress_thread(CompressAndWriteTextureToFile, width, height, std::move(filename), std::move(fp), true,
-                              g_gpu_device->UsesLowerLeftOrigin(), width, height, std::move(pixels), pixels_stride,
-                              pixels_format);
+  std::thread compress_thread(CompressAndWriteTextureToFile, width, height, std::move(filename), std::move(fp), quality,
+                              true, g_gpu_device->UsesLowerLeftOrigin(), width, height, std::move(pixels),
+                              pixels_stride, pixels_format);
   compress_thread.detach();
   return true;
 }
diff --git a/src/core/gpu.h b/src/core/gpu.h
index 9e5ad40b9..02ca0f0b3 100644
--- a/src/core/gpu.h
+++ b/src/core/gpu.h
@@ -209,7 +209,7 @@ public:
                                 std::vector<u8>* out_pixels, u32* out_stride, GPUTexture::Format* out_format);
 
   /// Helper function to save screenshot to PNG.
-  bool RenderScreenshotToFile(std::string filename, bool internal_resolution = false, bool compress_on_thread = false);
+  bool RenderScreenshotToFile(std::string filename, DisplayScreenshotMode mode, u8 quality, bool compress_on_thread);
 
   /// Draws the current display texture, with any post-processing.
   bool PresentDisplay();
diff --git a/src/core/host.cpp b/src/core/host.cpp
index 0c4d685fa..73d618eb7 100644
--- a/src/core/host.cpp
+++ b/src/core/host.cpp
@@ -180,6 +180,12 @@ bool Host::RemoveValueFromBaseStringListSetting(const char* section, const char*
     ->RemoveFromStringList(section, key, value);
 }
 
+bool Host::ContainsBaseSettingValue(const char* section, const char* key)
+{
+  std::unique_lock lock(s_settings_mutex);
+  return s_layered_settings_interface.GetLayer(LayeredSettingsInterface::LAYER_BASE)->ContainsValue(section, key);
+}
+
 void Host::DeleteBaseSettingValue(const char* section, const char* key)
 {
   std::unique_lock lock(s_settings_mutex);
diff --git a/src/core/host.h b/src/core/host.h
index 3cebf13e2..36b5ede06 100644
--- a/src/core/host.h
+++ b/src/core/host.h
@@ -47,6 +47,7 @@ void SetBaseStringSettingValue(const char* section, const char* key, const char*
 void SetBaseStringListSettingValue(const char* section, const char* key, const std::vector<std::string>& values);
 bool AddValueToBaseStringListSetting(const char* section, const char* key, const char* value);
 bool RemoveValueFromBaseStringListSetting(const char* section, const char* key, const char* value);
+bool ContainsBaseSettingValue(const char* section, const char* key);
 void DeleteBaseSettingValue(const char* section, const char* key);
 void CommitBaseSettingChanges();
 
diff --git a/src/core/settings.cpp b/src/core/settings.cpp
index b8f9e9246..448858ca3 100644
--- a/src/core/settings.cpp
+++ b/src/core/settings.cpp
@@ -249,6 +249,18 @@ void Settings::Load(SettingsInterface& si)
                         GetDisplayExclusiveFullscreenControlName(DEFAULT_DISPLAY_EXCLUSIVE_FULLSCREEN_CONTROL))
         .c_str())
       .value_or(DEFAULT_DISPLAY_EXCLUSIVE_FULLSCREEN_CONTROL);
+  display_screenshot_mode =
+    ParseDisplayScreenshotMode(
+      si.GetStringValue("Display", "ScreenshotMode", GetDisplayScreenshotModeName(DEFAULT_DISPLAY_SCREENSHOT_MODE))
+        .c_str())
+      .value_or(DEFAULT_DISPLAY_SCREENSHOT_MODE);
+  display_screenshot_format =
+    ParseDisplayScreenshotFormat(si.GetStringValue("Display", "ScreenshotFormat",
+                                                   GetDisplayScreenshotFormatName(DEFAULT_DISPLAY_SCREENSHOT_FORMAT))
+                                   .c_str())
+      .value_or(DEFAULT_DISPLAY_SCREENSHOT_FORMAT);
+  display_screenshot_quality = static_cast<u8>(
+    std::clamp<u32>(si.GetUIntValue("Display", "ScreenshotQuality", DEFAULT_DISPLAY_SCREENSHOT_QUALITY), 1, 100));
   display_force_4_3_for_24bit = si.GetBoolValue("Display", "Force4_3For24Bit", false);
   display_active_start_offset = static_cast<s16>(si.GetIntValue("Display", "ActiveStartOffset", 0));
   display_active_end_offset = static_cast<s16>(si.GetIntValue("Display", "ActiveEndOffset", 0));
@@ -266,7 +278,6 @@ void Settings::Load(SettingsInterface& si)
   display_show_inputs = si.GetBoolValue("Display", "ShowInputs", false);
   display_show_enhancements = si.GetBoolValue("Display", "ShowEnhancements", false);
   display_all_frames = si.GetBoolValue("Display", "DisplayAllFrames", false);
-  display_internal_resolution_screenshots = si.GetBoolValue("Display", "InternalResolutionScreenshots", false);
   display_stretch_vertically = si.GetBoolValue("Display", "StretchVertically", false);
   video_sync_enabled = si.GetBoolValue("Display", "VSync", DEFAULT_VSYNC_VALUE);
   display_max_fps = si.GetFloatValue("Display", "MaxFPS", DEFAULT_DISPLAY_MAX_FPS);
@@ -495,6 +506,9 @@ void Settings::Save(SettingsInterface& si) const
   si.SetStringValue("Display", "Scaling", GetDisplayScalingName(display_scaling));
   si.SetStringValue("Display", "ExclusiveFullscreenControl",
                     GetDisplayExclusiveFullscreenControlName(display_exclusive_fullscreen_control));
+  si.SetStringValue("Display", "ScreenshotMode", GetDisplayScreenshotModeName(display_screenshot_mode));
+  si.SetStringValue("Display", "ScreenshotFormat", GetDisplayScreenshotFormatName(display_screenshot_format));
+  si.SetUIntValue("Display", "ScreenshotQuality", display_screenshot_quality);
   si.SetIntValue("Display", "CustomAspectRatioNumerator", display_aspect_ratio_custom_numerator);
   si.GetIntValue("Display", "CustomAspectRatioDenominator", display_aspect_ratio_custom_denominator);
   si.SetBoolValue("Display", "ShowOSDMessages", display_show_osd_messages);
@@ -509,7 +523,6 @@ void Settings::Save(SettingsInterface& si) const
   si.SetBoolValue("Display", "ShowInputs", display_show_inputs);
   si.SetBoolValue("Display", "ShowEnhancements", display_show_enhancements);
   si.SetBoolValue("Display", "DisplayAllFrames", display_all_frames);
-  si.SetBoolValue("Display", "InternalResolutionScreenshots", display_internal_resolution_screenshots);
   si.SetBoolValue("Display", "StretchVertically", display_stretch_vertically);
   si.SetBoolValue("Display", "VSync", video_sync_enabled);
   si.SetFloatValue("Display", "MaxFPS", display_max_fps);
@@ -945,16 +958,16 @@ static constexpr const std::array s_gpu_renderer_names = {
 static constexpr const std::array s_gpu_renderer_display_names = {
   TRANSLATE_NOOP("GPURenderer", "Automatic"),
 #ifdef _WIN32
-  TRANSLATE_NOOP("GPURenderer", "Hardware (D3D11)"),  TRANSLATE_NOOP("GPURenderer", "Hardware (D3D12)"),
+  TRANSLATE_NOOP("GPURenderer", "Direct3D 11"), TRANSLATE_NOOP("GPURenderer", "Direct3D 12"),
 #endif
 #ifdef __APPLE__
-  TRANSLATE_NOOP("GPURenderer", "Hardware (Metal)"),
+  TRANSLATE_NOOP("GPURenderer", "Metal"),
 #endif
 #ifdef ENABLE_VULKAN
-  TRANSLATE_NOOP("GPURenderer", "Hardware (Vulkan)"),
+  TRANSLATE_NOOP("GPURenderer", "Vulkan"),
 #endif
 #ifdef ENABLE_OPENGL
-  TRANSLATE_NOOP("GPURenderer", "Hardware (OpenGL)"),
+  TRANSLATE_NOOP("GPURenderer", "OpenGL"),
 #endif
   TRANSLATE_NOOP("GPURenderer", "Software"),
 };
@@ -1011,6 +1024,44 @@ RenderAPI Settings::GetRenderAPIForRenderer(GPURenderer renderer)
   }
 }
 
+GPURenderer Settings::GetRendererForRenderAPI(RenderAPI api)
+{
+  switch (api)
+  {
+#ifdef _WIN32
+    case RenderAPI::D3D11:
+      return GPURenderer::HardwareD3D11;
+
+    case RenderAPI::D3D12:
+      return GPURenderer::HardwareD3D12;
+#endif
+
+#ifdef __APPLE__
+    case RenderAPI::Metal:
+      return GPURenderer::HardwareMetal;
+#endif
+
+#ifdef ENABLE_VULKAN
+    case RenderAPI::Vulkan:
+      return GPURenderer::HardwareVulkan;
+#endif
+
+#ifdef ENABLE_OPENGL
+    case RenderAPI::OpenGL:
+    case RenderAPI::OpenGLES:
+      return GPURenderer::HardwareOpenGL;
+#endif
+
+    default:
+      return GPURenderer::Automatic;
+  }
+}
+
+GPURenderer Settings::GetAutomaticRenderer()
+{
+  return GetRendererForRenderAPI(GPUDevice::GetPreferredAPI());
+}
+
 static constexpr const std::array s_texture_filter_names = {
   "Nearest", "Bilinear", "BilinearBinAlpha", "JINC2", "JINC2BinAlpha", "xBR", "xBRBinAlpha",
 };
@@ -1312,7 +1363,7 @@ static constexpr const std::array s_display_exclusive_fullscreen_mode_names = {
   "Allowed",
 };
 static constexpr const std::array s_display_exclusive_fullscreen_mode_display_names = {
-  TRANSLATE_NOOP("Settings", "Automatic (Default)"),
+  TRANSLATE_NOOP("Settings", "Automatic"),
   TRANSLATE_NOOP("Settings", "Disallowed"),
   TRANSLATE_NOOP("Settings", "Allowed"),
 };
@@ -1342,6 +1393,89 @@ const char* Settings::GetDisplayExclusiveFullscreenControlDisplayName(DisplayExc
                                   s_display_exclusive_fullscreen_mode_display_names[static_cast<int>(mode)]);
 }
 
+static constexpr const std::array s_display_screenshot_mode_names = {
+  "ScreenResolution",
+  "InternalResolution",
+  "UncorrectedInternalResolution",
+};
+static constexpr const std::array s_display_screenshot_mode_display_names = {
+  TRANSLATE_NOOP("Settings", "Screen Resolution"),
+  TRANSLATE_NOOP("Settings", "Internal Resolution"),
+  TRANSLATE_NOOP("Settings", "Internal Resolution (Aspect Uncorrected)"),
+};
+
+std::optional<DisplayScreenshotMode> Settings::ParseDisplayScreenshotMode(const char* str)
+{
+  int index = 0;
+  for (const char* name : s_display_screenshot_mode_names)
+  {
+    if (StringUtil::Strcasecmp(name, str) == 0)
+      return static_cast<DisplayScreenshotMode>(index);
+
+    index++;
+  }
+
+  return std::nullopt;
+}
+
+const char* Settings::GetDisplayScreenshotModeName(DisplayScreenshotMode mode)
+{
+  return s_display_screenshot_mode_names[static_cast<size_t>(mode)];
+}
+
+const char* Settings::GetDisplayScreenshotModeDisplayName(DisplayScreenshotMode mode)
+{
+  return Host::TranslateToCString("Settings", s_display_screenshot_mode_display_names[static_cast<size_t>(mode)]);
+}
+
+static constexpr const std::array s_display_screenshot_format_names = {
+  "PNG",
+  "JPEG",
+  "TGA",
+  "BMP",
+};
+static constexpr const std::array s_display_screenshot_format_display_names = {
+  TRANSLATE_NOOP("Settings", "PNG"),
+  TRANSLATE_NOOP("Settings", "JPEG"),
+  TRANSLATE_NOOP("Settings", "TGA"),
+  TRANSLATE_NOOP("Settings", "BMP"),
+};
+static constexpr const std::array s_display_screenshot_format_extensions = {
+  "png",
+  "jpg",
+  "tga",
+  "bmp",
+};
+
+std::optional<DisplayScreenshotFormat> Settings::ParseDisplayScreenshotFormat(const char* str)
+{
+  int index = 0;
+  for (const char* name : s_display_screenshot_format_names)
+  {
+    if (StringUtil::Strcasecmp(name, str) == 0)
+      return static_cast<DisplayScreenshotFormat>(index);
+
+    index++;
+  }
+
+  return std::nullopt;
+}
+
+const char* Settings::GetDisplayScreenshotFormatName(DisplayScreenshotFormat format)
+{
+  return s_display_screenshot_format_names[static_cast<size_t>(format)];
+}
+
+const char* Settings::GetDisplayScreenshotFormatDisplayName(DisplayScreenshotFormat mode)
+{
+  return Host::TranslateToCString("Settings", s_display_screenshot_format_display_names[static_cast<size_t>(mode)]);
+}
+
+const char* Settings::GetDisplayScreenshotFormatExtension(DisplayScreenshotFormat format)
+{
+  return s_display_screenshot_format_extensions[static_cast<size_t>(format)];
+}
+
 static constexpr const std::array s_audio_backend_names = {
   "Null",
 #ifdef ENABLE_CUBEB
diff --git a/src/core/settings.h b/src/core/settings.h
index d0c028327..73febe371 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -135,6 +135,9 @@ struct Settings
   DisplayAlignment display_alignment = DEFAULT_DISPLAY_ALIGNMENT;
   DisplayScalingMode display_scaling = DEFAULT_DISPLAY_SCALING;
   DisplayExclusiveFullscreenControl display_exclusive_fullscreen_control = DEFAULT_DISPLAY_EXCLUSIVE_FULLSCREEN_CONTROL;
+  DisplayScreenshotMode display_screenshot_mode = DEFAULT_DISPLAY_SCREENSHOT_MODE;
+  DisplayScreenshotFormat display_screenshot_format = DEFAULT_DISPLAY_SCREENSHOT_FORMAT;
+  u8 display_screenshot_quality = DEFAULT_DISPLAY_SCREENSHOT_QUALITY;
   u16 display_aspect_ratio_custom_numerator = 0;
   u16 display_aspect_ratio_custom_denominator = 0;
   s16 display_active_start_offset = 0;
@@ -155,7 +158,6 @@ struct Settings
   bool display_show_inputs : 1 = false;
   bool display_show_enhancements : 1 = false;
   bool display_all_frames : 1 = false;
-  bool display_internal_resolution_screenshots : 1 = false;
   bool display_stretch_vertically : 1 = false;
   bool video_sync_enabled = DEFAULT_VSYNC_VALUE;
   float display_osd_scale = 100.0f;
@@ -374,6 +376,8 @@ struct Settings
   static const char* GetRendererName(GPURenderer renderer);
   static const char* GetRendererDisplayName(GPURenderer renderer);
   static RenderAPI GetRenderAPIForRenderer(GPURenderer renderer);
+  static GPURenderer GetRendererForRenderAPI(RenderAPI api);
+  static GPURenderer GetAutomaticRenderer();
 
   static std::optional<GPUTextureFilter> ParseTextureFilterName(const char* str);
   static const char* GetTextureFilterName(GPUTextureFilter filter);
@@ -411,6 +415,15 @@ struct Settings
   static const char* GetDisplayExclusiveFullscreenControlName(DisplayExclusiveFullscreenControl mode);
   static const char* GetDisplayExclusiveFullscreenControlDisplayName(DisplayExclusiveFullscreenControl mode);
 
+  static std::optional<DisplayScreenshotMode> ParseDisplayScreenshotMode(const char* str);
+  static const char* GetDisplayScreenshotModeName(DisplayScreenshotMode mode);
+  static const char* GetDisplayScreenshotModeDisplayName(DisplayScreenshotMode mode);
+
+  static std::optional<DisplayScreenshotFormat> ParseDisplayScreenshotFormat(const char* str);
+  static const char* GetDisplayScreenshotFormatName(DisplayScreenshotFormat mode);
+  static const char* GetDisplayScreenshotFormatDisplayName(DisplayScreenshotFormat mode);
+  static const char* GetDisplayScreenshotFormatExtension(DisplayScreenshotFormat mode);
+
   static std::optional<AudioBackend> ParseAudioBackend(const char* str);
   static const char* GetAudioBackendName(AudioBackend backend);
   static const char* GetAudioBackendDisplayName(AudioBackend backend);
@@ -473,6 +486,9 @@ struct Settings
   static constexpr DisplayScalingMode DEFAULT_DISPLAY_SCALING = DisplayScalingMode::BilinearSmooth;
   static constexpr DisplayExclusiveFullscreenControl DEFAULT_DISPLAY_EXCLUSIVE_FULLSCREEN_CONTROL =
     DisplayExclusiveFullscreenControl::Automatic;
+  static constexpr DisplayScreenshotMode DEFAULT_DISPLAY_SCREENSHOT_MODE = DisplayScreenshotMode::ScreenResolution;
+  static constexpr DisplayScreenshotFormat DEFAULT_DISPLAY_SCREENSHOT_FORMAT = DisplayScreenshotFormat::PNG;
+  static constexpr u8 DEFAULT_DISPLAY_SCREENSHOT_QUALITY = 85;
   static constexpr float DEFAULT_OSD_SCALE = 100.0f;
 
   static constexpr u8 DEFAULT_CDROM_READAHEAD_SECTORS = 8;
diff --git a/src/core/system.cpp b/src/core/system.cpp
index bd82b83fc..0dc922386 100644
--- a/src/core/system.cpp
+++ b/src/core/system.cpp
@@ -4306,8 +4306,8 @@ void System::StopDumpingAudio()
   Host::AddOSDMessage(TRANSLATE_STR("OSDMessage", "Stopped dumping audio."), 5.0f);
 }
 
-bool System::SaveScreenshot(const char* filename /* = nullptr */, bool full_resolution /* = true */,
-                            bool apply_aspect_ratio /* = true */, bool compress_on_thread /* = true */)
+bool System::SaveScreenshot(const char* filename, DisplayScreenshotMode mode, DisplayScreenshotFormat format,
+                            u8 quality, bool compress_on_thread)
 {
   if (!System::IsValid())
     return false;
@@ -4316,7 +4316,7 @@ bool System::SaveScreenshot(const char* filename /* = nullptr */, bool full_reso
   if (!filename)
   {
     const auto& code = System::GetGameSerial();
-    const char* extension = "png";
+    const char* extension = Settings::GetDisplayScreenshotFormatExtension(format);
     if (code.empty())
     {
       auto_filename =
@@ -4337,10 +4337,7 @@ bool System::SaveScreenshot(const char* filename /* = nullptr */, bool full_reso
     return false;
   }
 
-  const bool screenshot_saved =
-    g_gpu->RenderScreenshotToFile(filename, g_settings.display_internal_resolution_screenshots, compress_on_thread);
-
-  if (!screenshot_saved)
+  if (!g_gpu->RenderScreenshotToFile(filename, mode, quality, compress_on_thread))
   {
     Host::AddFormattedOSDMessage(10.0f, TRANSLATE("OSDMessage", "Failed to save screenshot to '%s'"), filename);
     return false;
diff --git a/src/core/system.h b/src/core/system.h
index f847e62f9..0aa7b6f66 100644
--- a/src/core/system.h
+++ b/src/core/system.h
@@ -416,9 +416,10 @@ bool StartDumpingAudio(const char* filename = nullptr);
 /// Stops dumping audio to file if it has been started.
 void StopDumpingAudio();
 
-/// Saves a screenshot to the specified file. IF no file name is provided, one will be generated automatically.
-bool SaveScreenshot(const char* filename = nullptr, bool full_resolution = true, bool apply_aspect_ratio = true,
-                    bool compress_on_thread = true);
+/// Saves a screenshot to the specified file. If no file name is provided, one will be generated automatically.
+bool SaveScreenshot(const char* filename = nullptr, DisplayScreenshotMode mode = g_settings.display_screenshot_mode,
+                    DisplayScreenshotFormat format = g_settings.display_screenshot_format,
+                    u8 quality = g_settings.display_screenshot_quality, bool compress_on_thread = true);
 
 /// Loads the cheat list from the specified file.
 bool LoadCheatList(const char* filename);
diff --git a/src/core/types.h b/src/core/types.h
index 8138fe313..4832b0eb9 100644
--- a/src/core/types.h
+++ b/src/core/types.h
@@ -160,6 +160,23 @@ enum class DisplayExclusiveFullscreenControl : u8
   Count
 };
 
+enum class DisplayScreenshotMode : u8
+{
+  ScreenResolution,
+  InternalResolution,
+  UncorrectedInternalResolution,
+  Count
+};
+
+enum class DisplayScreenshotFormat : u8
+{
+  PNG,
+  JPEG,
+  TGA,
+  BMP,
+  Count
+};
+
 enum class AudioBackend : u8
 {
   Null,
diff --git a/src/duckstation-qt/CMakeLists.txt b/src/duckstation-qt/CMakeLists.txt
index 32bf78998..c587687b6 100644
--- a/src/duckstation-qt/CMakeLists.txt
+++ b/src/duckstation-qt/CMakeLists.txt
@@ -70,18 +70,12 @@ set(SRCS
   debuggerwindow.cpp
   debuggerwindow.h
   debuggerwindow.ui
-  displaysettingswidget.cpp
-  displaysettingswidget.h
-  displaysettingswidget.ui
   displaywidget.cpp
   displaywidget.h
   emptygamelistwidget.ui
   emulationsettingswidget.cpp
   emulationsettingswidget.h
   emulationsettingswidget.ui
-  enhancementsettingswidget.cpp
-  enhancementsettingswidget.h
-  enhancementsettingswidget.ui
   foldersettingswidget.cpp
   foldersettingswidget.h
   foldersettingswidget.ui
@@ -104,9 +98,9 @@ set(SRCS
   gdbconnection.h
   gdbserver.cpp
   gdbserver.h
-  generalsettingswidget.cpp
-  generalsettingswidget.h
-  generalsettingswidget.ui
+  graphicssettingswidget.cpp
+  graphicssettingswidget.h
+  graphicssettingswidget.ui
   hotkeysettingswidget.cpp
   hotkeysettingswidget.h
   inputbindingdialog.cpp
@@ -114,6 +108,9 @@ set(SRCS
   inputbindingdialog.ui
   inputbindingwidgets.cpp
   inputbindingwidgets.h
+  interfacesettingswidget.cpp
+  interfacesettingswidget.h
+  interfacesettingswidget.ui
   logwindow.cpp
   logwindow.h
   mainwindow.cpp
diff --git a/src/duckstation-qt/advancedsettingswidget.cpp b/src/duckstation-qt/advancedsettingswidget.cpp
index 14d7d3140..f1f95568f 100644
--- a/src/duckstation-qt/advancedsettingswidget.cpp
+++ b/src/duckstation-qt/advancedsettingswidget.cpp
@@ -72,42 +72,6 @@ static QSpinBox* setIntRangeTweakOption(QTableWidget* table, int row, int value)
   return cb;
 }
 
-static QDoubleSpinBox* addFloatRangeTweakOption(SettingsWindow* dialog, QTableWidget* table, QString name,
-                                                std::string section, std::string key, float min_value, float max_value,
-                                                float step_value, float default_value)
-{
-  const int row = table->rowCount();
-
-  table->insertRow(row);
-
-  QTableWidgetItem* name_item = new QTableWidgetItem(name);
-  name_item->setFlags(name_item->flags() & ~(Qt::ItemIsEditable | Qt::ItemIsSelectable));
-  table->setItem(row, 0, name_item);
-
-  QDoubleSpinBox* cb = new QDoubleSpinBox(table);
-  cb->setMinimum(min_value);
-  cb->setMaximum(max_value);
-  cb->setSingleStep(step_value);
-
-  if (!section.empty() || !key.empty())
-  {
-    SettingWidgetBinder::BindWidgetToFloatSetting(dialog->getSettingsInterface(), cb, std::move(section),
-                                                  std::move(key), default_value);
-  }
-
-  table->setCellWidget(row, 1, cb);
-  return cb;
-}
-
-static QDoubleSpinBox* setFloatRangeTweakOption(QTableWidget* table, int row, float value)
-{
-  QWidget* widget = table->cellWidget(row, 1);
-  QDoubleSpinBox* cb = qobject_cast<QDoubleSpinBox*>(widget);
-  Assert(cb);
-  cb->setValue(value);
-  return cb;
-}
-
 template<typename T>
 static QComboBox* addChoiceTweakOption(SettingsWindow* dialog, QTableWidget* table, QString name, std::string section,
                                        std::string key, std::optional<T> (*parse_callback)(const char*),
@@ -145,36 +109,6 @@ static void setChoiceTweakOption(QTableWidget* table, int row, T value)
   cb->setCurrentIndex(static_cast<int>(value));
 }
 
-static void addMSAATweakOption(SettingsWindow* dialog, QTableWidget* table, const QString& name)
-{
-  const int row = table->rowCount();
-
-  table->insertRow(row);
-
-  QTableWidgetItem* name_item = new QTableWidgetItem(name);
-  name_item->setFlags(name_item->flags() & ~(Qt::ItemIsEditable | Qt::ItemIsSelectable));
-  table->setItem(row, 0, name_item);
-
-  QComboBox* msaa = new QComboBox(table);
-  QtUtils::FillComboBoxWithMSAAModes(msaa);
-  const QVariant current_msaa_mode(
-    QtUtils::GetMSAAModeValue(static_cast<uint>(dialog->getEffectiveIntValue("GPU", "Multisamples", 1)),
-                              dialog->getEffectiveBoolValue("GPU", "PerSampleShading", false)));
-  const int current_msaa_index = msaa->findData(current_msaa_mode);
-  if (current_msaa_index >= 0)
-    msaa->setCurrentIndex(current_msaa_index);
-  msaa->connect(msaa, QOverload<int>::of(&QComboBox::currentIndexChanged), [dialog, msaa](int index) {
-    uint multisamples;
-    bool ssaa;
-    QtUtils::DecodeMSAAModeValue(msaa->itemData(index), &multisamples, &ssaa);
-    dialog->setIntSettingValue("GPU", "Multisamples", static_cast<int>(multisamples));
-    dialog->setBoolSettingValue("GPU", "PerSampleShading", ssaa);
-    g_emu_thread->applySettings(false);
-  });
-
-  table->setCellWidget(row, 1, msaa);
-}
-
 static void addDirectoryOption(SettingsWindow* dialog, QTableWidget* table, const QString& name, std::string section,
                                std::string key)
 {
@@ -242,8 +176,9 @@ AdvancedSettingsWidget::AdvancedSettingsWidget(SettingsWindow* dialog, QWidget*
   SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showDebugMenu, "Main", "ShowDebugMenu", false);
 
   connect(m_ui.resetToDefaultButton, &QPushButton::clicked, this, &AdvancedSettingsWidget::onResetToDefaultClicked);
-  connect(m_ui.showDebugMenu, &QCheckBox::toggled, g_main_window, &MainWindow::updateDebugMenuVisibility,
+  connect(m_ui.showDebugMenu, &QCheckBox::stateChanged, g_main_window, &MainWindow::updateDebugMenuVisibility,
           Qt::QueuedConnection);
+  connect(m_ui.showDebugMenu, &QCheckBox::stateChanged, this, &AdvancedSettingsWidget::onShowDebugOptionsStateChanged);
 
   m_ui.tweakOptionTable->setColumnWidth(0, 380);
   m_ui.tweakOptionTable->setColumnWidth(1, 170);
@@ -266,29 +201,18 @@ AdvancedSettingsWidget::AdvancedSettingsWidget(SettingsWindow* dialog, QWidget*
 
 AdvancedSettingsWidget::~AdvancedSettingsWidget() = default;
 
+void AdvancedSettingsWidget::onShowDebugOptionsStateChanged()
+{
+  const bool enabled = QtHost::ShouldShowDebugOptions();
+  emit onShowDebugOptionsChanged(enabled);
+}
+
 void AdvancedSettingsWidget::addTweakOptions()
 {
-  addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Disable All Enhancements"), "Main",
-                        "DisableAllEnhancements", false);
-  addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Show Status Indicators"), "Display",
-                        "ShowStatusIndicators", true);
-  addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Show Frame Times"), "Display", "ShowFrameTimes", false);
-  addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Show Settings Overlay"), "Display", "ShowEnhancements", false);
   addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Apply Compatibility Settings"), "Main",
                         "ApplyCompatibilitySettings", true);
-  addIntRangeTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Display FPS Limit"), "Display", "MaxFPS", 0, 1000, 0);
-  addChoiceTweakOption(
-    m_dialog, m_ui.tweakOptionTable, tr("Exclusive Fullscreen Control"), "Display", "ExclusiveFullscreenControl",
-    &Settings::ParseDisplayExclusiveFullscreenControl, &Settings::GetDisplayExclusiveFullscreenControlName,
-    &Settings::GetDisplayExclusiveFullscreenControlDisplayName,
-    static_cast<u32>(DisplayExclusiveFullscreenControl::Count), Settings::DEFAULT_DISPLAY_EXCLUSIVE_FULLSCREEN_CONTROL);
-
-  addMSAATweakOption(m_dialog, m_ui.tweakOptionTable, tr("Multisample Antialiasing"));
-
-  addChoiceTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Wireframe Mode"), "GPU", "WireframeMode",
-                       Settings::ParseGPUWireframeMode, Settings::GetGPUWireframeModeName,
-                       Settings::GetGPUWireframeModeDisplayName, static_cast<u32>(GPUWireframeMode::Count),
-                       GPUWireframeMode::Disabled);
+  addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Increase Timer Resolution"), "Main",
+                        "IncreaseTimerResolution", true);
 
   if (m_dialog->isPerGameSettings())
   {
@@ -302,11 +226,14 @@ void AdvancedSettingsWidget::addTweakOptions()
                            -128, 127, 0);
   }
 
-  addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("PGXP Vertex Cache"), "GPU", "PGXPVertexCache", false);
-  addFloatRangeTweakOption(m_dialog, m_ui.tweakOptionTable, tr("PGXP Geometry Tolerance"), "GPU", "PGXPTolerance",
-                           -1.0f, 100.0f, 0.25f, -1.0f);
-  addFloatRangeTweakOption(m_dialog, m_ui.tweakOptionTable, tr("PGXP Depth Clear Threshold"), "GPU",
-                           "PGXPDepthClearThreshold", 0.0f, 4096.0f, 1.0f, Settings::DEFAULT_GPU_PGXP_DEPTH_THRESHOLD);
+  addIntRangeTweakOption(m_dialog, m_ui.tweakOptionTable, tr("DMA Max Slice Ticks"), "Hacks", "DMAMaxSliceTicks", 100,
+                         10000, Settings::DEFAULT_DMA_MAX_SLICE_TICKS);
+  addIntRangeTweakOption(m_dialog, m_ui.tweakOptionTable, tr("DMA Halt Ticks"), "Hacks", "DMAHaltTicks", 100, 10000,
+                         Settings::DEFAULT_DMA_HALT_TICKS);
+  addIntRangeTweakOption(m_dialog, m_ui.tweakOptionTable, tr("GPU FIFO Size"), "Hacks", "GPUFIFOSize", 16, 4096,
+                         Settings::DEFAULT_GPU_FIFO_SIZE);
+  addIntRangeTweakOption(m_dialog, m_ui.tweakOptionTable, tr("GPU Max Run-Ahead"), "Hacks", "GPUMaxRunAhead", 0, 1000,
+                         Settings::DEFAULT_GPU_MAX_RUN_AHEAD);
 
   addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Enable Recompiler Memory Exceptions"), "CPU",
                         "RecompilerMemoryExceptions", false);
@@ -317,50 +244,6 @@ void AdvancedSettingsWidget::addTweakOptions()
                        Settings::GetCPUFastmemModeDisplayName, static_cast<u32>(CPUFastmemMode::Count),
                        Settings::DEFAULT_CPU_FASTMEM_MODE);
 
-  addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Use Old MDEC Routines"), "Hacks", "UseOldMDECRoutines",
-                        false);
-  addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Enable VRAM Write Texture Replacement"),
-                        "TextureReplacements", "EnableVRAMWriteReplacements", false);
-  addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Preload Texture Replacements"), "TextureReplacements",
-                        "PreloadTextures", false);
-  addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Dump Replaceable VRAM Writes"), "TextureReplacements",
-                        "DumpVRAMWrites", false);
-  addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Set Dumped VRAM Write Alpha Channel"),
-                        "TextureReplacements", "DumpVRAMWriteForceAlphaChannel", true);
-  addIntRangeTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Minimum Dumped VRAM Write Width"), "TextureReplacements",
-                         "DumpVRAMWriteWidthThreshold", 1, VRAM_WIDTH,
-                         Settings::DEFAULT_VRAM_WRITE_DUMP_WIDTH_THRESHOLD);
-  addIntRangeTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Minimum Dumped VRAM Write Height"), "TextureReplacements",
-                         "DumpVRAMWriteHeightThreshold", 1, VRAM_HEIGHT,
-                         Settings::DEFAULT_VRAM_WRITE_DUMP_HEIGHT_THRESHOLD);
-
-  addIntRangeTweakOption(m_dialog, m_ui.tweakOptionTable, tr("DMA Max Slice Ticks"), "Hacks", "DMAMaxSliceTicks", 100,
-                         10000, Settings::DEFAULT_DMA_MAX_SLICE_TICKS);
-  addIntRangeTweakOption(m_dialog, m_ui.tweakOptionTable, tr("DMA Halt Ticks"), "Hacks", "DMAHaltTicks", 100, 10000,
-                         Settings::DEFAULT_DMA_HALT_TICKS);
-  addIntRangeTweakOption(m_dialog, m_ui.tweakOptionTable, tr("GPU FIFO Size"), "Hacks", "GPUFIFOSize", 16, 4096,
-                         Settings::DEFAULT_GPU_FIFO_SIZE);
-  addIntRangeTweakOption(m_dialog, m_ui.tweakOptionTable, tr("GPU Max Run-Ahead"), "Hacks", "GPUMaxRunAhead", 0, 1000,
-                         Settings::DEFAULT_GPU_MAX_RUN_AHEAD);
-  addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Use Debug Host GPU Device"), "GPU", "UseDebugDevice",
-                        false);
-  addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Disable Shader Cache"), "GPU", "DisableShaderCache",
-                        false);
-  addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Disable Dual-Source Blend"), "GPU",
-                        "DisableDualSourceBlend", false);
-  addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Disable Framebuffer Fetch"), "GPU",
-                        "DisableFramebufferFetch", false);
-  addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Disable Texture Buffers"), "GPU", "DisableTextureBuffers",
-                        false);
-  addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Disable Texture Copy To Self"), "GPU",
-                        "DisableTextureCopyToSelf", false);
-
-  addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Stretch Display Vertically"), "Display",
-                        "StretchVertically", false);
-
-  addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Increase Timer Resolution"), "Main",
-                        "IncreaseTimerResolution", true);
-
   addChoiceTweakOption(m_dialog, m_ui.tweakOptionTable, tr("CD-ROM Mechacon Version"), "CDROM", "MechaconVersion",
                        Settings::ParseCDROMMechVersionName, Settings::GetCDROMMechVersionName,
                        Settings::GetCDROMMechVersionDisplayName, static_cast<u8>(CDROMMechaconVersion::Count),
@@ -368,9 +251,6 @@ void AdvancedSettingsWidget::addTweakOptions()
   addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Allow Booting Without SBI File"), "CDROM",
                         "AllowBootingWithoutSBIFile", false);
 
-  addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Create Save State Backups"), "General",
-                        "CreateSaveStateBackups", false);
-
   addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Enable PCDrv"), "PCDrv", "Enabled", false);
   addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Enable PCDrv Writes"), "PCDrv", "EnableWrites", false);
   addDirectoryOption(m_dialog, m_ui.tweakOptionTable, tr("PCDrv Root Directory"), "PCDrv", "Root");
@@ -382,31 +262,8 @@ void AdvancedSettingsWidget::onResetToDefaultClicked()
   {
     int i = 0;
 
-    setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // Disable all enhancements
-    setBooleanTweakOption(m_ui.tweakOptionTable, i++, true);  // Show status indicators
-    setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // Show frame times
-    setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // Show settings overlay
-    setBooleanTweakOption(m_ui.tweakOptionTable, i++, true);  // Apply compatibility settings
-    setIntRangeTweakOption(m_ui.tweakOptionTable, i++, 0);    // Display FPS limit
-    setChoiceTweakOption(m_ui.tweakOptionTable, i++, Settings::DEFAULT_DISPLAY_EXCLUSIVE_FULLSCREEN_CONTROL);
-    setChoiceTweakOption(m_ui.tweakOptionTable, i++, 0);         // Multisample antialiasing
-    setChoiceTweakOption(m_ui.tweakOptionTable, i++, 0);         // Wireframe mode
-    setBooleanTweakOption(m_ui.tweakOptionTable, i++, false);    // PGXP vertex cache
-    setFloatRangeTweakOption(m_ui.tweakOptionTable, i++, -1.0f); // PGXP geometry tolerance
-    setFloatRangeTweakOption(m_ui.tweakOptionTable, i++,
-                             Settings::DEFAULT_GPU_PGXP_DEPTH_THRESHOLD); // PGXP depth clear threshold
-    setBooleanTweakOption(m_ui.tweakOptionTable, i++, false);             // Recompiler memory exceptions
-    setBooleanTweakOption(m_ui.tweakOptionTable, i++, true);              // Recompiler block linking
-    setChoiceTweakOption(m_ui.tweakOptionTable, i++, Settings::DEFAULT_CPU_FASTMEM_MODE); // Recompiler fastmem mode
-    setBooleanTweakOption(m_ui.tweakOptionTable, i++, false);                             // Use Old MDEC Routines
-    setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // VRAM write texture replacement
-    setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // Preload texture replacements
-    setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // Dump replacable VRAM writes
-    setBooleanTweakOption(m_ui.tweakOptionTable, i++, true);  // Set dumped VRAM write alpha channel
-    setIntRangeTweakOption(m_ui.tweakOptionTable, i++,
-                           Settings::DEFAULT_VRAM_WRITE_DUMP_WIDTH_THRESHOLD); // Minimum dumped VRAM width
-    setIntRangeTweakOption(m_ui.tweakOptionTable, i++,
-                           Settings::DEFAULT_VRAM_WRITE_DUMP_HEIGHT_THRESHOLD); // Minimum dumped VRAM height
+    setBooleanTweakOption(m_ui.tweakOptionTable, i++, true); // Apply compatibility settings
+    setBooleanTweakOption(m_ui.tweakOptionTable, i++, true); // Increase Timer Resolution
     setIntRangeTweakOption(m_ui.tweakOptionTable, i++,
                            static_cast<int>(Settings::DEFAULT_DMA_MAX_SLICE_TICKS)); // DMA max slice ticks
     setIntRangeTweakOption(m_ui.tweakOptionTable, i++,
@@ -415,18 +272,13 @@ void AdvancedSettingsWidget::onResetToDefaultClicked()
                            static_cast<int>(Settings::DEFAULT_GPU_FIFO_SIZE)); // GPU FIFO size
     setIntRangeTweakOption(m_ui.tweakOptionTable, i++,
                            static_cast<int>(Settings::DEFAULT_GPU_MAX_RUN_AHEAD)); // GPU max run-ahead
-    setBooleanTweakOption(m_ui.tweakOptionTable, i++, false);                      // Use debug host GPU device
-    setBooleanTweakOption(m_ui.tweakOptionTable, i++, false);                      // Disable Shader Cache
-    setBooleanTweakOption(m_ui.tweakOptionTable, i++, false);                      // Disable Dual-Source Blend
-    setBooleanTweakOption(m_ui.tweakOptionTable, i++, false);                      // Disable Framebuffer Fetch
-    setBooleanTweakOption(m_ui.tweakOptionTable, i++, false);                      // Disable Texture Buffers
-    setBooleanTweakOption(m_ui.tweakOptionTable, i++, false);                      // Disable Texture Copy To Self
-    setBooleanTweakOption(m_ui.tweakOptionTable, i++, false);                      // Stretch Display Vertically
-    setBooleanTweakOption(m_ui.tweakOptionTable, i++, true);                       // Increase Timer Resolution
+    setBooleanTweakOption(m_ui.tweakOptionTable, i++, false);                      // Recompiler memory exceptions
+    setBooleanTweakOption(m_ui.tweakOptionTable, i++, true);                       // Recompiler block linking
+    setChoiceTweakOption(m_ui.tweakOptionTable, i++,
+                         Settings::DEFAULT_CPU_FASTMEM_MODE); // Recompiler fastmem mode
     setChoiceTweakOption(m_ui.tweakOptionTable, i++,
                          Settings::DEFAULT_CDROM_MECHACON_VERSION); // CDROM Mechacon Version
     setBooleanTweakOption(m_ui.tweakOptionTable, i++, false);       // Allow booting without SBI file
-    setBooleanTweakOption(m_ui.tweakOptionTable, i++, false);       // Create save state backups
     setBooleanTweakOption(m_ui.tweakOptionTable, i++, false);       // Enable PCDRV
     setBooleanTweakOption(m_ui.tweakOptionTable, i++, false);       // Enable PCDRV Writes
     setDirectoryOption(m_ui.tweakOptionTable, i++, "");             // PCDrv Root Directory
@@ -436,49 +288,21 @@ void AdvancedSettingsWidget::onResetToDefaultClicked()
 
   // for per-game it's easier to just clear and recreate
   SettingsInterface* sif = m_dialog->getSettingsInterface();
-  sif->DeleteValue("Main", "DisableAllEnhancements");
-  sif->DeleteValue("Display", "ShowEnhancements");
-  sif->DeleteValue("Display", "ShowStatusIndicators");
-  sif->DeleteValue("Display", "ShowFrameTimes");
-  sif->DeleteValue("Display", "ShowEnhancements");
   sif->DeleteValue("Main", "ApplyCompatibilitySettings");
-  sif->DeleteValue("Display", "MaxFPS");
+  sif->DeleteValue("Main", "IncreaseTimerResolution");
   sif->DeleteValue("Display", "ActiveStartOffset");
   sif->DeleteValue("Display", "ActiveEndOffset");
   sif->DeleteValue("Display", "LineStartOffset");
   sif->DeleteValue("Display", "LineEndOffset");
-  sif->DeleteValue("Display", "StretchVertically");
-  sif->DeleteValue("Display", "ExclusiveFullscreenControl");
-  sif->DeleteValue("GPU", "Multisamples");
-  sif->DeleteValue("GPU", "PerSampleShading");
-  sif->DeleteValue("GPU", "PGXPVertexCache");
-  sif->DeleteValue("GPU", "PGXPTolerance");
-  sif->DeleteValue("GPU", "PGXPDepthClearThreshold");
-  sif->DeleteValue("CPU", "RecompilerMemoryExceptions");
-  sif->DeleteValue("CPU", "RecompilerBlockLinking");
-  sif->DeleteValue("CPU", "FastmemMode");
-  sif->DeleteValue("TextureReplacements", "EnableVRAMWriteReplacements");
-  sif->DeleteValue("TextureReplacements", "PreloadTextures");
-  sif->DeleteValue("TextureReplacements", "DumpVRAMWrites");
-  sif->DeleteValue("TextureReplacements", "DumpVRAMWriteForceAlphaChannel");
-  sif->DeleteValue("TextureReplacements", "DumpVRAMWriteWidthThreshold");
-  sif->DeleteValue("TextureReplacements", "DumpVRAMWriteHeightThreshold");
-  sif->DeleteValue("Hacks", "UseOldMDECRoutines");
   sif->DeleteValue("Hacks", "DMAMaxSliceTicks");
   sif->DeleteValue("Hacks", "DMAHaltTicks");
   sif->DeleteValue("Hacks", "GPUFIFOSize");
   sif->DeleteValue("Hacks", "GPUMaxRunAhead");
-  sif->DeleteValue("GPU", "UseDebugDevice");
-  sif->DeleteValue("GPU", "DisableShaderCache");
-  sif->DeleteValue("GPU", "DisableDualSourceBlend");
-  sif->DeleteValue("GPU", "DisableFramebufferFetch");
-  sif->DeleteValue("GPU", "DisableTextureBuffers");
-  sif->DeleteValue("GPU", "DisableTextureCopyToSelf");
-  sif->DeleteValue("Display", "StretchVertically");
-  sif->DeleteValue("Main", "IncreaseTimerResolution");
+  sif->DeleteValue("CPU", "RecompilerMemoryExceptions");
+  sif->DeleteValue("CPU", "RecompilerBlockLinking");
+  sif->DeleteValue("CPU", "FastmemMode");
   sif->DeleteValue("CDROM", "MechaconVersion");
   sif->DeleteValue("CDROM", "AllowBootingWithoutSBIFile");
-  sif->DeleteValue("General", "CreateSaveStateBackups");
   sif->DeleteValue("PCDrv", "Enabled");
   sif->DeleteValue("PCDrv", "EnableWrites");
   sif->DeleteValue("PCDrv", "Root");
@@ -486,4 +310,4 @@ void AdvancedSettingsWidget::onResetToDefaultClicked()
   while (m_ui.tweakOptionTable->rowCount() > 0)
     m_ui.tweakOptionTable->removeRow(m_ui.tweakOptionTable->rowCount() - 1);
   addTweakOptions();
-}
\ No newline at end of file
+}
diff --git a/src/duckstation-qt/advancedsettingswidget.h b/src/duckstation-qt/advancedsettingswidget.h
index 964836d8c..f30d79275 100644
--- a/src/duckstation-qt/advancedsettingswidget.h
+++ b/src/duckstation-qt/advancedsettingswidget.h
@@ -17,6 +17,12 @@ public:
   explicit AdvancedSettingsWidget(SettingsWindow* dialog, QWidget* parent);
   ~AdvancedSettingsWidget();
 
+Q_SIGNALS:
+  void onShowDebugOptionsChanged(bool enabled);
+
+private Q_SLOTS:
+  void onShowDebugOptionsStateChanged();
+
 private:
   struct TweakOption
   {
diff --git a/src/duckstation-qt/consolesettingswidget.cpp b/src/duckstation-qt/consolesettingswidget.cpp
index f49d07b93..08adb8b74 100644
--- a/src/duckstation-qt/consolesettingswidget.cpp
+++ b/src/duckstation-qt/consolesettingswidget.cpp
@@ -40,6 +40,8 @@ ConsoleSettingsWidget::ConsoleSettingsWidget(SettingsWindow* dialog, QWidget* pa
   SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.region, "Console", "Region", &Settings::ParseConsoleRegionName,
                                                &Settings::GetConsoleRegionName, Settings::DEFAULT_CONSOLE_REGION);
   SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enable8MBRAM, "Console", "Enable8MBRAM", false);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.disableAllEnhancements, "Main", "DisableAllEnhancements",
+                                               false);
   SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.cpuExecutionMode, "CPU", "ExecutionMode",
                                                &Settings::ParseCPUExecutionMode, &Settings::GetCPUExecutionModeName,
                                                Settings::DEFAULT_CPU_EXECUTION_MODE);
@@ -72,6 +74,9 @@ ConsoleSettingsWidget::ConsoleSettingsWidget(SettingsWindow* dialog, QWidget* pa
        "to use a larger heap size for "
        "this additional RAM to be usable. Titles which rely on memory mirrors may break, so it should only be used "
        "with compatible mods."));
+  dialog->registerWidgetHelp(m_ui.disableAllEnhancements, tr("Disable All Enhancements"), tr("Unchecked"),
+                             tr("Disables all enhancement options, simulating the system as accurately as possible. "
+                                "Use to quickly determine whether an enhancement is responsible for game bugs."));
   dialog->registerWidgetHelp(
     m_ui.cdromLoadImageToRAM, tr("Preload Image to RAM"), tr("Unchecked"),
     tr("Loads the game image into RAM. Useful for network paths that may become unreliable during gameplay. In some "
diff --git a/src/duckstation-qt/consolesettingswidget.ui b/src/duckstation-qt/consolesettingswidget.ui
index 9a4a8d1cf..eac5327ce 100644
--- a/src/duckstation-qt/consolesettingswidget.ui
+++ b/src/duckstation-qt/consolesettingswidget.ui
@@ -40,11 +40,22 @@
        <widget class="QComboBox" name="region"/>
       </item>
       <item row="1" column="0" colspan="2">
-       <widget class="QCheckBox" name="enable8MBRAM">
-        <property name="text">
-         <string>Enable 8MB RAM (Dev Console)</string>
-        </property>
-       </widget>
+       <layout class="QGridLayout" name="gridLayout_2">
+        <item row="0" column="0">
+         <widget class="QCheckBox" name="enable8MBRAM">
+          <property name="text">
+           <string>Enable 8MB RAM (Dev Console)</string>
+          </property>
+         </widget>
+        </item>
+        <item row="0" column="1">
+         <widget class="QCheckBox" name="disableAllEnhancements">
+          <property name="text">
+           <string>Disable All Enhancements</string>
+          </property>
+         </widget>
+        </item>
+       </layout>
       </item>
      </layout>
     </widget>
diff --git a/src/duckstation-qt/displaysettingswidget.cpp b/src/duckstation-qt/displaysettingswidget.cpp
deleted file mode 100644
index afb41d32d..000000000
--- a/src/duckstation-qt/displaysettingswidget.cpp
+++ /dev/null
@@ -1,305 +0,0 @@
-// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
-// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
-
-#include "displaysettingswidget.h"
-#include "core/gpu.h"
-#include "core/settings.h"
-#include "qtutils.h"
-#include "settingswindow.h"
-#include "settingwidgetbinder.h"
-#include <QtWidgets/QMessageBox>
-
-// For enumerating adapters.
-#ifdef _WIN32
-#include "util/d3d11_device.h"
-#include "util/d3d12_device.h"
-#endif
-#ifdef ENABLE_VULKAN
-#include "util/vulkan_device.h"
-#endif
-
-DisplaySettingsWidget::DisplaySettingsWidget(SettingsWindow* dialog, QWidget* parent)
-  : QWidget(parent), m_dialog(dialog)
-{
-  SettingsInterface* sif = dialog->getSettingsInterface();
-
-  m_ui.setupUi(this);
-  setupAdditionalUi();
-
-  SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.renderer, "GPU", "Renderer", &Settings::ParseRendererName,
-                                               &Settings::GetRendererName, Settings::DEFAULT_GPU_RENDERER);
-  SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.displayAspectRatio, "Display", "AspectRatio",
-                                               &Settings::ParseDisplayAspectRatio, &Settings::GetDisplayAspectRatioName,
-                                               Settings::DEFAULT_DISPLAY_ASPECT_RATIO);
-  SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.customAspectRatioNumerator, "Display",
-                                              "CustomAspectRatioNumerator", 1);
-  SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.customAspectRatioDenominator, "Display",
-                                              "CustomAspectRatioDenominator", 1);
-  SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.displayCropMode, "Display", "CropMode",
-                                               &Settings::ParseDisplayCropMode, &Settings::GetDisplayCropModeName,
-                                               Settings::DEFAULT_DISPLAY_CROP_MODE);
-  SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.displayAlignment, "Display", "Alignment",
-                                               &Settings::ParseDisplayAlignment, &Settings::GetDisplayAlignmentName,
-                                               Settings::DEFAULT_DISPLAY_ALIGNMENT);
-  SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.displayScaling, "Display", "Scaling",
-                                               &Settings::ParseDisplayScaling, &Settings::GetDisplayScalingName,
-                                               Settings::DEFAULT_DISPLAY_SCALING);
-  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.internalResolutionScreenshots, "Display",
-                                               "InternalResolutionScreenshots", false);
-  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.vsync, "Display", "VSync", false);
-  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.gpuThread, "GPU", "UseThread", true);
-  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.threadedPresentation, "GPU", "ThreadedPresentation", true);
-  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showOSDMessages, "Display", "ShowOSDMessages", true);
-  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showFPS, "Display", "ShowFPS", false);
-  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showSpeed, "Display", "ShowSpeed", false);
-  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showResolution, "Display", "ShowResolution", false);
-  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showCPU, "Display", "ShowCPU", false);
-  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showGPU, "Display", "ShowGPU", false);
-  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showInput, "Display", "ShowInputs", false);
-  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showGPUStatistics, "Display", "ShowGPUStatistics", false);
-
-  connect(m_ui.renderer, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
-          &DisplaySettingsWidget::populateGPUAdaptersAndResolutions);
-  connect(m_ui.adapter, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
-          &DisplaySettingsWidget::onGPUAdapterIndexChanged);
-  connect(m_ui.fullscreenMode, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
-          &DisplaySettingsWidget::onGPUFullscreenModeIndexChanged);
-  connect(m_ui.displayAspectRatio, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
-          &DisplaySettingsWidget::onAspectRatioChanged);
-  populateGPUAdaptersAndResolutions();
-  onAspectRatioChanged();
-
-  dialog->registerWidgetHelp(
-    m_ui.renderer, tr("Renderer"), QString::fromUtf8(Settings::GetRendererDisplayName(Settings::DEFAULT_GPU_RENDERER)),
-    tr("Chooses the backend to use for rendering the console/game visuals. <br>Depending on your system and hardware, "
-       "Direct3D 11 and OpenGL hardware backends may be available. <br>The software renderer offers the best "
-       "compatibility, but is the slowest and does not offer any enhancements."));
-  dialog->registerWidgetHelp(
-    m_ui.adapter, tr("Adapter"), tr("(Default)"),
-    tr("If your system contains multiple GPUs or adapters, you can select which GPU you wish to use for the hardware "
-       "renderers. <br>This option is only supported in Direct3D and Vulkan. OpenGL will always use the default "
-       "device."));
-  dialog->registerWidgetHelp(m_ui.fullscreenMode, tr("Fullscreen Mode"), tr("Borderless Fullscreen"),
-                             tr("Chooses the fullscreen resolution and frequency."));
-  dialog->registerWidgetHelp(
-    m_ui.displayAspectRatio, tr("Aspect Ratio"),
-    QString::fromUtf8(Settings::GetDisplayAspectRatioDisplayName(Settings::DEFAULT_DISPLAY_ASPECT_RATIO)),
-    tr("Changes the aspect ratio used to display the console's output to the screen. The default is Auto (Game Native) "
-       "which automatically adjusts the aspect ratio to match how a game would be shown on a typical TV of the era."));
-  dialog->registerWidgetHelp(
-    m_ui.displayCropMode, tr("Crop Mode"),
-    QString::fromUtf8(Settings::GetDisplayCropModeDisplayName(Settings::DEFAULT_DISPLAY_CROP_MODE)),
-    tr("Determines how much of the area typically not visible on a consumer TV set to crop/hide. <br>"
-       "Some games display content in the overscan area, or use it for screen effects. <br>May "
-       "not display correctly with the \"All Borders\" setting. \"Only Overscan\" offers a good "
-       "compromise between stability and hiding black borders."));
-  dialog->registerWidgetHelp(
-    m_ui.displayAlignment, tr("Position"),
-    QString::fromUtf8(Settings::GetDisplayAlignmentDisplayName(Settings::DEFAULT_DISPLAY_ALIGNMENT)),
-    tr("Determines the position on the screen when black borders must be added."));
-  dialog->registerWidgetHelp(
-    m_ui.displayScaling, tr("Scaling"), tr("Bilinear (Smooth)"),
-    tr("Determines how the emulated console's output is upscaled or downscaled to your monitor's resolution."));
-  dialog->registerWidgetHelp(m_ui.internalResolutionScreenshots, tr("Internal Resolution Screenshots"), tr("Unchecked"),
-                             tr("Saves screenshots at internal render resolution and without postprocessing. If this "
-                                "option is disabled, the screenshots will be taken at the window's resolution. "
-                                "Internal resolution screenshots can be very large at high rendering scales."));
-  dialog->registerWidgetHelp(
-    m_ui.vsync, tr("VSync"), tr("Unchecked"),
-    tr("Enable this option to match DuckStation's refresh rate with your current monitor or screen. "
-       "VSync is automatically disabled when it is not possible (e.g. running at non-100% speed)."));
-  dialog->registerWidgetHelp(m_ui.threadedPresentation, tr("Threaded Presentation"), tr("Checked"),
-                             tr("Presents frames on a background thread when fast forwarding or vsync is disabled. "
-                                "This can measurably improve performance in the Vulkan renderer."));
-  dialog->registerWidgetHelp(m_ui.gpuThread, tr("Threaded Rendering"), tr("Checked"),
-                             tr("Uses a second thread for drawing graphics. Currently only available for the software "
-                                "renderer, but can provide a significant speed improvement, and is safe to use."));
-  dialog->registerWidgetHelp(m_ui.showOSDMessages, tr("Show OSD Messages"), tr("Checked"),
-                             tr("Shows on-screen-display messages when events occur such as save states being "
-                                "created/loaded, screenshots being taken, etc."));
-  dialog->registerWidgetHelp(m_ui.showFPS, tr("Show FPS"), tr("Unchecked"),
-                             tr("Shows the internal frame rate of the game in the top-right corner of the display."));
-  dialog->registerWidgetHelp(
-    m_ui.showSpeed, tr("Show Emulation Speed"), tr("Unchecked"),
-    tr("Shows the current emulation speed of the system in the top-right corner of the display as a percentage."));
-  dialog->registerWidgetHelp(m_ui.showResolution, tr("Show Resolution"), tr("Unchecked"),
-                             tr("Shows the resolution of the game in the top-right corner of the display."));
-  dialog->registerWidgetHelp(
-    m_ui.showCPU, tr("Show CPU Usage"), tr("Unchecked"),
-    tr("Shows the host's CPU usage based on threads in the top-right corner of the display. This does not display the "
-       "emulated system CPU's usage. If a value close to 100% is being displayed, this means your host's CPU is likely "
-       "the bottleneck. In this case, you should reduce enhancement-related settings such as overclocking."));
-  dialog->registerWidgetHelp(m_ui.showGPUStatistics, tr("Show GPU Statistics"), tr("Unchecked"),
-                             tr("Shows information about the emulated GPU in the top-right corner of the display."));
-  dialog->registerWidgetHelp(m_ui.showGPU, tr("Show GPU Usage"), tr("Unchecked"),
-                             tr("Shows the host's GPU usage in the top-right corner of the display."));
-  dialog->registerWidgetHelp(
-    m_ui.showInput, tr("Show Controller Input"), tr("Unchecked"),
-    tr("Shows the current controller state of the system in the bottom-left corner of the display."));
-
-#ifdef _WIN32
-  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.blitSwapChain, "Display", "UseBlitSwapChain", false);
-  dialog->registerWidgetHelp(m_ui.blitSwapChain, tr("Use Blit Swap Chain"), tr("Unchecked"),
-                             tr("Uses a blit presentation model instead of flipping when using the Direct3D 11 "
-                                "renderer. This usually results in slower performance, but may be required for some "
-                                "streaming applications, or to uncap framerates on some systems."));
-#else
-  m_ui.blitSwapChain->setEnabled(false);
-#endif
-}
-
-DisplaySettingsWidget::~DisplaySettingsWidget() = default;
-
-void DisplaySettingsWidget::setupAdditionalUi()
-{
-  for (u32 i = 0; i < static_cast<u32>(GPURenderer::Count); i++)
-  {
-    m_ui.renderer->addItem(QString::fromUtf8(Settings::GetRendererDisplayName(static_cast<GPURenderer>(i))));
-  }
-
-  for (u32 i = 0; i < static_cast<u32>(DisplayAspectRatio::Count); i++)
-  {
-    m_ui.displayAspectRatio->addItem(
-      QString::fromUtf8(Settings::GetDisplayAspectRatioDisplayName(static_cast<DisplayAspectRatio>(i))));
-  }
-
-  for (u32 i = 0; i < static_cast<u32>(DisplayCropMode::Count); i++)
-  {
-    m_ui.displayCropMode->addItem(
-      QString::fromUtf8(Settings::GetDisplayCropModeDisplayName(static_cast<DisplayCropMode>(i))));
-  }
-
-  for (u32 i = 0; i < static_cast<u32>(DisplayScalingMode::Count); i++)
-  {
-    m_ui.displayScaling->addItem(
-      QString::fromUtf8(Settings::GetDisplayScalingDisplayName(static_cast<DisplayScalingMode>(i))));
-  }
-
-  for (u32 i = 0; i < static_cast<u32>(DisplayAlignment::Count); i++)
-  {
-    m_ui.displayAlignment->addItem(
-      QString::fromUtf8(Settings::GetDisplayAlignmentDisplayName(static_cast<DisplayAlignment>(i))));
-  }
-}
-
-void DisplaySettingsWidget::populateGPUAdaptersAndResolutions()
-{
-  GPUDevice::AdapterAndModeList aml;
-  bool thread_supported = false;
-  bool threaded_presentation_supported = false;
-  switch (static_cast<GPURenderer>(m_ui.renderer->currentIndex()))
-  {
-#ifdef _WIN32
-    case GPURenderer::HardwareD3D11:
-      aml = D3D11Device::StaticGetAdapterAndModeList();
-      break;
-
-    case GPURenderer::HardwareD3D12:
-      aml = D3D12Device::StaticGetAdapterAndModeList();
-      break;
-#endif
-#ifdef __APPLE__
-    case GPURenderer::HardwareMetal:
-      aml = GPUDevice::WrapGetMetalAdapterAndModeList();
-      break;
-#endif
-#ifdef ENABLE_VULKAN
-    case GPURenderer::HardwareVulkan:
-      aml = VulkanDevice::StaticGetAdapterAndModeList();
-      threaded_presentation_supported = true;
-      break;
-#endif
-
-    case GPURenderer::Software:
-      thread_supported = true;
-      break;
-
-    default:
-      break;
-  }
-
-  {
-    const std::string current_adapter(m_dialog->getEffectiveStringValue("GPU", "Adapter", ""));
-    QSignalBlocker blocker(m_ui.adapter);
-
-    // add the default entry - we'll fall back to this if the GPU no longer exists, or there's no options
-    m_ui.adapter->clear();
-    m_ui.adapter->addItem(tr("(Default)"));
-
-    // add the other adapters
-    for (const std::string& adapter_name : aml.adapter_names)
-    {
-      m_ui.adapter->addItem(QString::fromStdString(adapter_name));
-
-      if (adapter_name == current_adapter)
-        m_ui.adapter->setCurrentIndex(m_ui.adapter->count() - 1);
-    }
-
-    // disable it if we don't have a choice
-    m_ui.adapter->setEnabled(!aml.adapter_names.empty());
-  }
-
-  {
-    const std::string current_mode(m_dialog->getEffectiveStringValue("GPU", "FullscreenMode", ""));
-    QSignalBlocker blocker(m_ui.fullscreenMode);
-
-    m_ui.fullscreenMode->clear();
-    m_ui.fullscreenMode->addItem(tr("Borderless Fullscreen"));
-    m_ui.fullscreenMode->setCurrentIndex(0);
-
-    for (const std::string& mode_name : aml.fullscreen_modes)
-    {
-      m_ui.fullscreenMode->addItem(QString::fromStdString(mode_name));
-
-      if (mode_name == current_mode)
-        m_ui.fullscreenMode->setCurrentIndex(m_ui.fullscreenMode->count() - 1);
-    }
-
-    // disable it if we don't have a choice
-    m_ui.fullscreenMode->setEnabled(!aml.fullscreen_modes.empty());
-  }
-
-  m_ui.gpuThread->setEnabled(thread_supported);
-  m_ui.threadedPresentation->setEnabled(threaded_presentation_supported);
-}
-
-void DisplaySettingsWidget::onGPUAdapterIndexChanged()
-{
-  if (m_ui.adapter->currentIndex() == 0)
-  {
-    // default
-    m_dialog->removeSettingValue("GPU", "Adapter");
-    return;
-  }
-
-  m_dialog->setStringSettingValue("GPU", "Adapter", m_ui.adapter->currentText().toUtf8().constData());
-}
-
-void DisplaySettingsWidget::onGPUFullscreenModeIndexChanged()
-{
-  if (m_ui.fullscreenMode->currentIndex() == 0)
-  {
-    // default
-    m_dialog->removeSettingValue("GPU", "FullscreenMode");
-    return;
-  }
-
-  m_dialog->setStringSettingValue("GPU", "FullscreenMode", m_ui.fullscreenMode->currentText().toUtf8().constData());
-}
-
-void DisplaySettingsWidget::onAspectRatioChanged()
-{
-  const DisplayAspectRatio ratio =
-    Settings::ParseDisplayAspectRatio(
-      m_dialog
-        ->getEffectiveStringValue("Display", "AspectRatio",
-                                  Settings::GetDisplayAspectRatioName(Settings::DEFAULT_DISPLAY_ASPECT_RATIO))
-        .c_str())
-      .value_or(Settings::DEFAULT_DISPLAY_ASPECT_RATIO);
-
-  const bool is_custom = (ratio == DisplayAspectRatio::Custom);
-
-  m_ui.customAspectRatioNumerator->setVisible(is_custom);
-  m_ui.customAspectRatioDenominator->setVisible(is_custom);
-  m_ui.customAspectRatioSeparator->setVisible(is_custom);
-}
diff --git a/src/duckstation-qt/displaysettingswidget.h b/src/duckstation-qt/displaysettingswidget.h
deleted file mode 100644
index d8d648186..000000000
--- a/src/duckstation-qt/displaysettingswidget.h
+++ /dev/null
@@ -1,33 +0,0 @@
-// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
-// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
-
-#pragma once
-
-#include <QtWidgets/QWidget>
-
-#include "ui_displaysettingswidget.h"
-
-class PostProcessingChainConfigWidget;
-class SettingsWindow;
-
-class DisplaySettingsWidget : public QWidget
-{
-  Q_OBJECT
-
-public:
-  DisplaySettingsWidget(SettingsWindow* dialog, QWidget* parent);
-  ~DisplaySettingsWidget();
-
-private Q_SLOTS:
-  void populateGPUAdaptersAndResolutions();
-  void onGPUAdapterIndexChanged();
-  void onGPUFullscreenModeIndexChanged();
-  void onAspectRatioChanged();
-
-private:
-  void setupAdditionalUi();
-
-  Ui::DisplaySettingsWidget m_ui;
-
-  SettingsWindow* m_dialog;
-};
diff --git a/src/duckstation-qt/displaysettingswidget.ui b/src/duckstation-qt/displaysettingswidget.ui
deleted file mode 100644
index 86284d7da..000000000
--- a/src/duckstation-qt/displaysettingswidget.ui
+++ /dev/null
@@ -1,270 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>DisplaySettingsWidget</class>
- <widget class="QWidget" name="DisplaySettingsWidget">
-  <property name="geometry">
-   <rect>
-    <x>0</x>
-    <y>0</y>
-    <width>485</width>
-    <height>525</height>
-   </rect>
-  </property>
-  <layout class="QVBoxLayout" name="verticalLayout_2">
-   <property name="leftMargin">
-    <number>0</number>
-   </property>
-   <property name="topMargin">
-    <number>0</number>
-   </property>
-   <property name="rightMargin">
-    <number>0</number>
-   </property>
-   <property name="bottomMargin">
-    <number>0</number>
-   </property>
-   <item>
-    <widget class="QGroupBox" name="basicGroupBox">
-     <property name="title">
-      <string>Basic</string>
-     </property>
-     <layout class="QFormLayout" name="formLayout_2">
-      <item row="0" column="0">
-       <widget class="QLabel" name="label">
-        <property name="text">
-         <string>Renderer:</string>
-        </property>
-       </widget>
-      </item>
-      <item row="0" column="1">
-       <widget class="QComboBox" name="renderer"/>
-      </item>
-      <item row="1" column="0">
-       <widget class="QLabel" name="label_5">
-        <property name="text">
-         <string>Adapter:</string>
-        </property>
-       </widget>
-      </item>
-      <item row="1" column="1">
-       <widget class="QComboBox" name="adapter"/>
-      </item>
-      <item row="2" column="0">
-       <widget class="QLabel" name="label_2">
-        <property name="text">
-         <string>Fullscreen Mode:</string>
-        </property>
-       </widget>
-      </item>
-      <item row="2" column="1">
-       <widget class="QComboBox" name="fullscreenMode"/>
-      </item>
-      <item row="3" column="0" colspan="2">
-       <layout class="QGridLayout" name="basicCheckboxGridLayout">
-        <item row="1" column="0">
-         <widget class="QCheckBox" name="vsync">
-          <property name="text">
-           <string>VSync</string>
-          </property>
-         </widget>
-        </item>
-        <item row="0" column="0">
-         <widget class="QCheckBox" name="gpuThread">
-          <property name="text">
-           <string>Threaded Rendering</string>
-          </property>
-         </widget>
-        </item>
-        <item row="0" column="1">
-         <widget class="QCheckBox" name="threadedPresentation">
-          <property name="text">
-           <string>Threaded Presentation</string>
-          </property>
-         </widget>
-        </item>
-        <item row="1" column="1">
-         <widget class="QCheckBox" name="blitSwapChain">
-          <property name="text">
-           <string>Use Blit Swap Chain</string>
-          </property>
-         </widget>
-        </item>
-       </layout>
-      </item>
-     </layout>
-    </widget>
-   </item>
-   <item>
-    <widget class="QGroupBox" name="groupBox_3">
-     <property name="title">
-      <string>Screen Display</string>
-     </property>
-     <layout class="QFormLayout" name="formLayout">
-      <item row="0" column="0">
-       <widget class="QLabel" name="label_4">
-        <property name="text">
-         <string>Aspect Ratio:</string>
-        </property>
-       </widget>
-      </item>
-      <item row="0" column="1">
-       <layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0,0,0">
-        <item>
-         <widget class="QComboBox" name="displayAspectRatio"/>
-        </item>
-        <item>
-         <widget class="QSpinBox" name="customAspectRatioNumerator">
-          <property name="minimum">
-           <number>1</number>
-          </property>
-          <property name="maximum">
-           <number>9999</number>
-          </property>
-         </widget>
-        </item>
-        <item>
-         <widget class="QLabel" name="customAspectRatioSeparator">
-          <property name="text">
-           <string>:</string>
-          </property>
-         </widget>
-        </item>
-        <item>
-         <widget class="QSpinBox" name="customAspectRatioDenominator">
-          <property name="minimum">
-           <number>1</number>
-          </property>
-          <property name="maximum">
-           <number>9999</number>
-          </property>
-         </widget>
-        </item>
-       </layout>
-      </item>
-      <item row="1" column="0">
-       <widget class="QLabel" name="label_3">
-        <property name="text">
-         <string>Crop:</string>
-        </property>
-       </widget>
-      </item>
-      <item row="1" column="1">
-       <widget class="QComboBox" name="displayCropMode"/>
-      </item>
-      <item row="3" column="0">
-       <widget class="QLabel" name="label_7">
-        <property name="text">
-         <string>Position:</string>
-        </property>
-       </widget>
-      </item>
-      <item row="3" column="1">
-       <widget class="QComboBox" name="displayAlignment"/>
-      </item>
-      <item row="4" column="0" colspan="2">
-       <layout class="QGridLayout" name="gridLayout">
-        <item row="1" column="0">
-         <widget class="QCheckBox" name="internalResolutionScreenshots">
-          <property name="text">
-           <string>Internal Resolution Screenshots</string>
-          </property>
-         </widget>
-        </item>
-       </layout>
-      </item>
-      <item row="2" column="0">
-       <widget class="QLabel" name="label_6">
-        <property name="text">
-         <string>Scaling:</string>
-        </property>
-       </widget>
-      </item>
-      <item row="2" column="1">
-       <widget class="QComboBox" name="displayScaling"/>
-      </item>
-     </layout>
-    </widget>
-   </item>
-   <item>
-    <widget class="QGroupBox" name="groupBox_5">
-     <property name="title">
-      <string>On-Screen Display</string>
-     </property>
-     <layout class="QGridLayout" name="formLayout_5">
-      <item row="3" column="0">
-       <widget class="QCheckBox" name="showSpeed">
-        <property name="text">
-         <string>Show Emulation Speed</string>
-        </property>
-       </widget>
-      </item>
-      <item row="4" column="0">
-       <widget class="QCheckBox" name="showCPU">
-        <property name="text">
-         <string>Show CPU Usage</string>
-        </property>
-       </widget>
-      </item>
-      <item row="2" column="0">
-       <widget class="QCheckBox" name="showOSDMessages">
-        <property name="text">
-         <string>Show OSD Messages</string>
-        </property>
-       </widget>
-      </item>
-      <item row="3" column="1">
-       <widget class="QCheckBox" name="showFPS">
-        <property name="text">
-         <string>Show FPS</string>
-        </property>
-       </widget>
-      </item>
-      <item row="4" column="1">
-       <widget class="QCheckBox" name="showInput">
-        <property name="text">
-         <string>Show Controller Input</string>
-        </property>
-       </widget>
-      </item>
-      <item row="2" column="1">
-       <widget class="QCheckBox" name="showResolution">
-        <property name="text">
-         <string>Show Resolution</string>
-        </property>
-       </widget>
-      </item>
-      <item row="5" column="0">
-       <widget class="QCheckBox" name="showGPU">
-        <property name="text">
-         <string>Show GPU Usage</string>
-        </property>
-       </widget>
-      </item>
-      <item row="5" column="1">
-       <widget class="QCheckBox" name="showGPUStatistics">
-        <property name="text">
-         <string>Show GPU Statistics</string>
-        </property>
-       </widget>
-      </item>
-     </layout>
-    </widget>
-   </item>
-   <item>
-    <spacer name="verticalSpacer">
-     <property name="orientation">
-      <enum>Qt::Vertical</enum>
-     </property>
-     <property name="sizeHint" stdset="0">
-      <size>
-       <width>20</width>
-       <height>40</height>
-      </size>
-     </property>
-    </spacer>
-   </item>
-  </layout>
- </widget>
- <resources/>
- <connections/>
-</ui>
diff --git a/src/duckstation-qt/duckstation-qt.vcxproj b/src/duckstation-qt/duckstation-qt.vcxproj
index 7a0b22aa9..d584fd5a0 100644
--- a/src/duckstation-qt/duckstation-qt.vcxproj
+++ b/src/duckstation-qt/duckstation-qt.vcxproj
@@ -21,12 +21,11 @@
     <ClCompile Include="emulationsettingswidget.cpp" />
     <ClCompile Include="debuggermodels.cpp" />
     <ClCompile Include="debuggerwindow.cpp" />
-    <ClCompile Include="enhancementsettingswidget.cpp" />
     <ClCompile Include="foldersettingswidget.cpp" />
     <ClCompile Include="gamelistmodel.cpp" />
     <ClCompile Include="gamelistsearchdirectoriesmodel.cpp" />
-    <ClCompile Include="generalsettingswidget.cpp" />
-    <ClCompile Include="displaysettingswidget.cpp" />
+    <ClCompile Include="interfacesettingswidget.cpp" />
+    <ClCompile Include="graphicssettingswidget.cpp" />
     <ClCompile Include="hotkeysettingswidget.cpp" />
     <ClCompile Include="inputbindingdialog.cpp" />
     <ClCompile Include="inputbindingwidgets.cpp" />
@@ -62,12 +61,10 @@
     <QtMoc Include="cheatmanagerdialog.h" />
     <QtMoc Include="cheatcodeeditordialog.h" />
     <QtMoc Include="coverdownloaddialog.h" />
-    <QtMoc Include="enhancementsettingswidget.h" />
     <QtMoc Include="memorycardsettingswidget.h" />
     <QtMoc Include="memorycardeditorwindow.h" />
     <QtMoc Include="displaywidget.h" />
-    <QtMoc Include="generalsettingswidget.h" />
-    <QtMoc Include="displaysettingswidget.h" />
+    <QtMoc Include="interfacesettingswidget.h" />
     <QtMoc Include="hotkeysettingswidget.h" />
     <QtMoc Include="inputbindingwidgets.h" />
     <QtMoc Include="advancedsettingswidget.h" />
@@ -88,6 +85,7 @@
     <ClInclude Include="controllersettingwidgetbinder.h" />
     <QtMoc Include="memoryviewwidget.h" />
     <QtMoc Include="logwindow.h" />
+    <QtMoc Include="graphicssettingswidget.h" />
     <ClInclude Include="pch.h" />
     <ClInclude Include="resource.h" />
     <ClInclude Include="settingwidgetbinder.h" />
@@ -122,13 +120,10 @@
     <QtUi Include="emulationsettingswidget.ui">
       <FileType>Document</FileType>
     </QtUi>
-    <QtUi Include="enhancementsettingswidget.ui">
-      <FileType>Document</FileType>
-    </QtUi>
     <QtUi Include="gamelistsettingswidget.ui">
       <FileType>Document</FileType>
     </QtUi>
-    <QtUi Include="generalsettingswidget.ui">
+    <QtUi Include="interfacesettingswidget.ui">
       <FileType>Document</FileType>
     </QtUi>
     <QtUi Include="mainwindow.ui">
@@ -137,9 +132,6 @@
     <QtUi Include="settingswindow.ui">
       <FileType>Document</FileType>
     </QtUi>
-    <QtUi Include="displaysettingswidget.ui">
-      <FileType>Document</FileType>
-    </QtUi>
     <QtUi Include="advancedsettingswidget.ui">
       <FileType>Document</FileType>
     </QtUi>
@@ -243,7 +235,6 @@
     <ClCompile Include="$(IntDir)moc_coverdownloaddialog.cpp" />
     <ClCompile Include="$(IntDir)moc_displaywidget.cpp" />
     <ClCompile Include="$(IntDir)moc_emulationsettingswidget.cpp" />
-    <ClCompile Include="$(IntDir)moc_enhancementsettingswidget.cpp" />
     <ClCompile Include="$(IntDir)moc_foldersettingswidget.cpp" />
     <ClCompile Include="$(IntDir)moc_gamelistmodel.cpp" />
     <ClCompile Include="$(IntDir)moc_gamelistrefreshthread.cpp" />
@@ -253,13 +244,13 @@
     <ClCompile Include="$(IntDir)moc_gamesummarywidget.cpp" />
     <ClCompile Include="$(IntDir)moc_gdbconnection.cpp" />
     <ClCompile Include="$(IntDir)moc_gdbserver.cpp" />
-    <ClCompile Include="$(IntDir)moc_generalsettingswidget.cpp" />
+    <ClCompile Include="$(IntDir)moc_graphicssettingswidget.cpp" />
     <ClCompile Include="$(IntDir)moc_debuggermodels.cpp" />
     <ClCompile Include="$(IntDir)moc_debuggerwindow.cpp" />
-    <ClCompile Include="$(IntDir)moc_displaysettingswidget.cpp" />
     <ClCompile Include="$(IntDir)moc_hotkeysettingswidget.cpp" />
     <ClCompile Include="$(IntDir)moc_inputbindingdialog.cpp" />
     <ClCompile Include="$(IntDir)moc_inputbindingwidgets.cpp" />
+    <ClCompile Include="$(IntDir)moc_interfacesettingswidget.cpp" />
     <ClCompile Include="$(IntDir)moc_logwindow.cpp" />
     <ClCompile Include="$(IntDir)moc_mainwindow.cpp" />
     <ClCompile Include="$(IntDir)moc_memorycardsettingswidget.cpp" />
@@ -335,6 +326,9 @@
     <QtUi Include="debuggeraddbreakpointdialog.ui">
       <FileType>Document</FileType>
     </QtUi>
+    <QtUi Include="graphicssettingswidget.ui">
+      <FileType>Document</FileType>
+    </QtUi>
     <None Include="translations\duckstation-qt_es-es.ts" />
     <None Include="translations\duckstation-qt_tr.ts" />
   </ItemGroup>
diff --git a/src/duckstation-qt/duckstation-qt.vcxproj.filters b/src/duckstation-qt/duckstation-qt.vcxproj.filters
index b54f2db03..7fd2885b4 100644
--- a/src/duckstation-qt/duckstation-qt.vcxproj.filters
+++ b/src/duckstation-qt/duckstation-qt.vcxproj.filters
@@ -13,7 +13,7 @@
     <ClCompile Include="audiosettingswidget.cpp" />
     <ClCompile Include="displaywidget.cpp" />
     <ClCompile Include="qtprogresscallback.cpp" />
-    <ClCompile Include="generalsettingswidget.cpp" />
+    <ClCompile Include="interfacesettingswidget.cpp" />
     <ClCompile Include="advancedsettingswidget.cpp" />
     <ClCompile Include="gdbconnection.cpp" />
     <ClCompile Include="gdbserver.cpp" />
@@ -25,8 +25,6 @@
     <ClCompile Include="gamelistsearchdirectoriesmodel.cpp" />
     <ClCompile Include="autoupdaterdialog.cpp" />
     <ClCompile Include="biossettingswidget.cpp" />
-    <ClCompile Include="enhancementsettingswidget.cpp" />
-    <ClCompile Include="displaysettingswidget.cpp" />
     <ClCompile Include="memorycardeditorwindow.cpp" />
     <ClCompile Include="postprocessingsettingswidget.cpp" />
     <ClCompile Include="cheatmanagerdialog.cpp" />
@@ -105,18 +103,12 @@
     <ClCompile Include="$(IntDir)moc_debuggerwindow.cpp">
       <Filter>moc</Filter>
     </ClCompile>
-    <ClCompile Include="$(IntDir)moc_displaysettingswidget.cpp">
-      <Filter>moc</Filter>
-    </ClCompile>
     <ClCompile Include="$(IntDir)moc_displaywidget.cpp">
       <Filter>moc</Filter>
     </ClCompile>
     <ClCompile Include="$(IntDir)moc_emulationsettingswidget.cpp">
       <Filter>moc</Filter>
     </ClCompile>
-    <ClCompile Include="$(IntDir)moc_enhancementsettingswidget.cpp">
-      <Filter>moc</Filter>
-    </ClCompile>
     <ClCompile Include="$(IntDir)moc_foldersettingswidget.cpp">
       <Filter>moc</Filter>
     </ClCompile>
@@ -144,9 +136,6 @@
     <ClCompile Include="$(IntDir)moc_gdbserver.cpp">
       <Filter>moc</Filter>
     </ClCompile>
-    <ClCompile Include="$(IntDir)moc_generalsettingswidget.cpp">
-      <Filter>moc</Filter>
-    </ClCompile>
     <ClCompile Include="$(IntDir)moc_hotkeysettingswidget.cpp">
       <Filter>moc</Filter>
     </ClCompile>
@@ -186,6 +175,13 @@
     <ClCompile Include="$(IntDir)moc_setupwizarddialog.cpp">
       <Filter>moc</Filter>
     </ClCompile>
+    <ClCompile Include="graphicssettingswidget.cpp" />
+    <ClCompile Include="$(IntDir)moc_graphicssettingswidget.cpp">
+      <Filter>moc</Filter>
+    </ClCompile>
+    <ClCompile Include="$(IntDir)moc_interfacesettingswidget.cpp">
+      <Filter>moc</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="qtutils.h" />
@@ -216,7 +212,7 @@
     <QtMoc Include="inputbindingwidgets.h" />
     <QtMoc Include="audiosettingswidget.h" />
     <QtMoc Include="displaywidget.h" />
-    <QtMoc Include="generalsettingswidget.h" />
+    <QtMoc Include="interfacesettingswidget.h" />
     <QtMoc Include="qtprogresscallback.h" />
     <QtMoc Include="advancedsettingswidget.h" />
     <QtMoc Include="gdbconnection.h" />
@@ -228,9 +224,7 @@
     <QtMoc Include="gamelistsearchdirectoriesmodel.h" />
     <QtMoc Include="autoupdaterdialog.h" />
     <QtMoc Include="biossettingswidget.h" />
-    <QtMoc Include="enhancementsettingswidget.h" />
     <QtMoc Include="memorycardeditorwindow.h" />
-    <QtMoc Include="displaysettingswidget.h" />
     <QtMoc Include="postprocessingsettingswidget.h" />
     <QtMoc Include="cheatmanagerdialog.h" />
     <QtMoc Include="cheatcodeeditordialog.h" />
@@ -251,6 +245,7 @@
     <QtMoc Include="colorpickerbutton.h" />
     <QtMoc Include="setupwizarddialog.h" />
     <QtMoc Include="logwindow.h" />
+    <QtMoc Include="graphicssettingswidget.h" />
   </ItemGroup>
   <ItemGroup>
     <QtUi Include="consolesettingswidget.ui" />
@@ -258,14 +253,12 @@
     <QtUi Include="mainwindow.ui" />
     <QtUi Include="settingswindow.ui" />
     <QtUi Include="audiosettingswidget.ui" />
-    <QtUi Include="generalsettingswidget.ui" />
+    <QtUi Include="interfacesettingswidget.ui" />
     <QtUi Include="advancedsettingswidget.ui" />
     <QtUi Include="aboutdialog.ui" />
     <QtUi Include="inputbindingdialog.ui" />
     <QtUi Include="autoupdaterdialog.ui" />
     <QtUi Include="biossettingswidget.ui" />
-    <QtUi Include="enhancementsettingswidget.ui" />
-    <QtUi Include="displaysettingswidget.ui" />
     <QtUi Include="postprocessingsettingswidget.ui" />
     <QtUi Include="memorycardeditorwindow.ui" />
     <QtUi Include="cheatmanagerdialog.ui" />
@@ -293,6 +286,7 @@
     <QtUi Include="setupwizarddialog.ui" />
     <QtUi Include="controllerledsettingsdialog.ui" />
     <QtUi Include="debuggeraddbreakpointdialog.ui" />
+    <QtUi Include="graphicssettingswidget.ui" />
   </ItemGroup>
   <ItemGroup>
     <Natvis Include="qt5.natvis" />
diff --git a/src/duckstation-qt/enhancementsettingswidget.cpp b/src/duckstation-qt/enhancementsettingswidget.cpp
deleted file mode 100644
index 4ffb7a782..000000000
--- a/src/duckstation-qt/enhancementsettingswidget.cpp
+++ /dev/null
@@ -1,223 +0,0 @@
-// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
-// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
-
-#include "enhancementsettingswidget.h"
-#include "core/gpu.h"
-#include "core/settings.h"
-#include "qtutils.h"
-#include "settingswindow.h"
-#include "settingwidgetbinder.h"
-
-EnhancementSettingsWidget::EnhancementSettingsWidget(SettingsWindow* dialog, QWidget* parent)
-  : QWidget(parent), m_dialog(dialog)
-{
-  SettingsInterface* sif = dialog->getSettingsInterface();
-
-  m_ui.setupUi(this);
-  setupAdditionalUi();
-
-  SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.resolutionScale, "GPU", "ResolutionScale", 1);
-  SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.textureFiltering, "GPU", "TextureFilter",
-                                               &Settings::ParseTextureFilterName, &Settings::GetTextureFilterName,
-                                               Settings::DEFAULT_GPU_TEXTURE_FILTER);
-  SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.gpuLineDetectMode, "GPU", "LineDetectMode",
-                                               &Settings::ParseLineDetectModeName, &Settings::GetLineDetectModeName,
-                                               Settings::DEFAULT_GPU_LINE_DETECT_MODE);
-  SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.gpuDownsampleMode, "GPU", "DownsampleMode",
-                                               &Settings::ParseDownsampleModeName, &Settings::GetDownsampleModeName,
-                                               Settings::DEFAULT_GPU_DOWNSAMPLE_MODE);
-  SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.gpuDownsampleScale, "GPU", "DownsampleScale", 1);
-  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.trueColor, "GPU", "TrueColor", false);
-  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.debanding, "GPU", "Debanding", false);
-  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.scaledDithering, "GPU", "ScaledDithering", false);
-  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.disableInterlacing, "GPU", "DisableInterlacing", true);
-  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.forceNTSCTimings, "GPU", "ForceNTSCTimings", false);
-  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.force43For24Bit, "Display", "Force4_3For24Bit", false);
-  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.chromaSmoothingFor24Bit, "GPU", "ChromaSmoothing24Bit", false);
-  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.widescreenHack, "GPU", "WidescreenHack", false);
-  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.useSoftwareRendererForReadbacks, "GPU",
-                                               "UseSoftwareRendererForReadbacks", false);
-
-  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pgxpEnable, "GPU", "PGXPEnable", false);
-  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pgxpCulling, "GPU", "PGXPCulling", true);
-  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pgxpTextureCorrection, "GPU", "PGXPTextureCorrection", true);
-  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pgxpColorCorrection, "GPU", "PGXPColorCorrection", false);
-  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pgxpDepthBuffer, "GPU", "PGXPDepthBuffer", false);
-  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pgxpPreserveProjPrecision, "GPU", "PGXPPreserveProjFP", false);
-  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pgxpCPU, "GPU", "PGXPCPU", false);
-
-  connect(m_ui.resolutionScale, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
-          &EnhancementSettingsWidget::onTrueColorChanged);
-  connect(m_ui.gpuDownsampleMode, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
-          &EnhancementSettingsWidget::updateDownsampleScaleVisible);
-  connect(m_ui.trueColor, &QCheckBox::stateChanged, this, &EnhancementSettingsWidget::onTrueColorChanged);
-  updateDownsampleScaleVisible();
-  onTrueColorChanged();
-
-  connect(m_ui.pgxpEnable, &QCheckBox::stateChanged, this, &EnhancementSettingsWidget::updatePGXPSettingsEnabled);
-  connect(m_ui.pgxpTextureCorrection, &QCheckBox::stateChanged, this,
-          &EnhancementSettingsWidget::updatePGXPSettingsEnabled);
-  updatePGXPSettingsEnabled();
-
-  dialog->registerWidgetHelp(
-    m_ui.gpuDownsampleMode, tr("Downsampling"), tr("Disabled"),
-    tr("Downsamples the rendered image prior to displaying it. Can improve overall image quality in mixed 2D/3D games, "
-       "but should be disabled for pure 3D games. Only applies to the hardware renderers."));
-  dialog->registerWidgetHelp(m_ui.gpuDownsampleScale, tr("Downsampling Display Scale"), tr("1x"),
-                             tr("Selects the resolution scale that will be applied to the final image. 1x will "
-                                "downsample to the original console resolution."));
-  dialog->registerWidgetHelp(
-    m_ui.disableInterlacing, tr("Disable Interlacing"), tr("Checked"),
-    tr(
-      "Forces the rendering and display of frames to progressive mode. <br>This removes the \"combing\" effect seen in "
-      "480i games by rendering them in 480p. Usually safe to enable.<br> "
-      "<b><u>May not be compatible with all games.</u></b>"));
-  dialog->registerWidgetHelp(
-    m_ui.resolutionScale, tr("Resolution Scale"), "1x",
-    tr("Setting this beyond 1x will enhance the resolution of rendered 3D polygons and lines. Only applies "
-       "to the hardware backends. <br>This option is usually safe, with most games looking fine at "
-       "higher resolutions. Higher resolutions require a more powerful GPU."));
-  dialog->registerWidgetHelp(
-    m_ui.trueColor, tr("True Color Rendering"), tr("Checked"),
-    tr("Forces the precision of colours output to the console's framebuffer to use the full 8 bits of precision per "
-       "channel. This produces nicer looking gradients at the cost of making some colours look slightly different. "
-       "Disabling the option also enables dithering, which makes the transition between colours less sharp by applying "
-       "a pattern around those pixels. Most games are compatible with this option, but there is a number which aren't "
-       "and will have broken effects with it enabled. Only applies to the hardware renderers."));
-  dialog->registerWidgetHelp(
-    m_ui.debanding, tr("True Color Debanding"), tr("Unchecked"),
-    tr("Applies modern dithering techniques to further smooth out gradients when true color is enabled. "
-       "This debanding is performed during rendering (as opposed to a post-processing step), which allows it to be "
-       "fast while preserving detail. "
-       "Debanding increases the file size of screenshots due to the subtle dithering pattern present in screenshots."));
-  dialog->registerWidgetHelp(
-    m_ui.scaledDithering, tr("Scaled Dithering"), tr("Checked"),
-    tr("Scales the dither pattern to the resolution scale of the emulated GPU. This makes the dither pattern much less "
-       "obvious at higher resolutions. <br>Usually safe to enable, and only supported by the hardware renderers."));
-  dialog->registerWidgetHelp(m_ui.forceNTSCTimings, tr("Force NTSC Timings (60hz-on-PAL)"), tr("Unchecked"),
-                             tr("Uses NTSC frame timings when the console is in PAL mode, forcing PAL games to run at "
-                                "60hz. <br>For most games which "
-                                "have a speed tied to the framerate, this will result in the game running "
-                                "approximately 17% faster. <br>For variable "
-                                "frame rate games, it may not affect the speed."));
-  dialog->registerWidgetHelp(
-    m_ui.force43For24Bit, tr("Force 4:3 For 24-bit Display"), tr("Unchecked"),
-    tr("Switches back to 4:3 display aspect ratio when displaying 24-bit content, usually FMVs."));
-  dialog->registerWidgetHelp(m_ui.chromaSmoothingFor24Bit, tr("Chroma Smoothing For 24-Bit Display"), tr("Unchecked"),
-                             tr("Smooths out blockyness between colour transitions in 24-bit content, usually FMVs. "
-                                "Only applies to the hardware renderers."));
-  dialog->registerWidgetHelp(
-    m_ui.textureFiltering, tr("Texture Filtering"),
-    QString::fromUtf8(Settings::GetTextureFilterDisplayName(Settings::DEFAULT_GPU_TEXTURE_FILTER)),
-    tr("Smooths out the blockiness of magnified textures on 3D object by using filtering. <br>Will have a "
-       "greater effect on higher resolution scales. Only applies to the hardware renderers. <br>The JINC2 and "
-       "especially xBR filtering modes are very demanding, and may not be worth the speed penalty."));
-  dialog->registerWidgetHelp(m_ui.gpuLineDetectMode, tr("Line Detection"),
-                             QString::fromUtf8(Settings::GetLineDetectModeName(Settings::DEFAULT_GPU_LINE_DETECT_MODE)),
-                             tr("Attempts to detect one pixel high/wide lines that rely on non-upscaled rasterization "
-                                "behavior, filling in gaps introduced by upscaling."));
-  dialog->registerWidgetHelp(
-    m_ui.widescreenHack, tr("Widescreen Hack"), tr("Unchecked"),
-    tr("Scales vertex positions in screen-space to a widescreen aspect ratio, essentially "
-       "increasing the field of view from 4:3 to the chosen display aspect ratio in 3D games. <br>For 2D games, or "
-       "games which use pre-rendered backgrounds, this enhancement will not work as expected. <br><b><u>May not be "
-       "compatible with all games.</u></b>"));
-  dialog->registerWidgetHelp(
-    m_ui.useSoftwareRendererForReadbacks, tr("Use Software Renderer For Readbacks"), tr("Unchecked"),
-    tr("Runs the software renderer in parallel for VRAM readbacks. On some systems, this may result in greater "
-       "performance when using graphical enhancements with the hardware renderer."));
-  dialog->registerWidgetHelp(
-    m_ui.pgxpEnable, tr("Geometry Correction"), tr("Unchecked"),
-    tr("Reduces \"wobbly\" polygons and \"warping\" textures that are common in PS1 games. <br>Only "
-       "works with the hardware renderers. <b><u>May not be compatible with all games.</u></b>"));
-  dialog->registerWidgetHelp(m_ui.pgxpCulling, tr("Culling Correction"), tr("Checked"),
-                             tr("Increases the precision of polygon culling, reducing the number of holes in geometry. "
-                                "Requires geometry correction enabled."));
-  dialog->registerWidgetHelp(m_ui.pgxpTextureCorrection, tr("Perspective Correct Textures"), tr("Checked"),
-                             tr("Uses perspective-correct interpolation for texture coordinates, straightening out "
-                                "warped textures. Requires geometry correction enabled."));
-  dialog->registerWidgetHelp(
-    m_ui.pgxpColorCorrection, tr("Perspective Correct Colors"), tr("Unchecked"),
-    tr("Uses perspective-correct interpolation for vertex colors, which can improve visuals in some games, but cause "
-       "rendering errors in others. Requires geometry correction enabled."));
-  dialog->registerWidgetHelp(
-    m_ui.pgxpDepthBuffer, tr("Depth Buffer (Low Compatibility)"), tr("Unchecked"),
-    tr("Attempts to reduce polygon Z-fighting by testing pixels against the depth values from PGXP. Low compatibility, "
-       "but can work well in some games. Other games may need a threshold adjustment."));
-  dialog->registerWidgetHelp(
-    m_ui.pgxpPreserveProjPrecision, tr("Preserve Projection Precision"), tr("Unchecked"),
-    tr("Adds additional precision to PGXP data post-projection. May improve visuals in some games."));
-  dialog->registerWidgetHelp(m_ui.pgxpCPU, tr("CPU Mode (Very Slow)"), tr("Unchecked"),
-                             tr("Uses PGXP for all instructions, not just memory operations. Required for PGXP to "
-                                "correct wobble in some games, but has a very high performance cost."));
-}
-
-EnhancementSettingsWidget::~EnhancementSettingsWidget() = default;
-
-void EnhancementSettingsWidget::onTrueColorChanged()
-{
-  const int resolution_scale = m_ui.resolutionScale->currentIndex();
-  const bool true_color = m_ui.trueColor->isChecked();
-  const bool allow_scaled_dithering = (resolution_scale != 1 && !true_color);
-  const bool allow_debanding = true_color;
-  m_ui.scaledDithering->setEnabled(allow_scaled_dithering);
-  m_ui.debanding->setEnabled(allow_debanding);
-}
-
-void EnhancementSettingsWidget::updateDownsampleScaleVisible()
-{
-  const GPUDownsampleMode mode =
-    Settings::ParseDownsampleModeName(
-      m_dialog
-        ->getEffectiveStringValue("GPU", "DownsampleMode",
-                                  Settings::GetDownsampleModeName(Settings::DEFAULT_GPU_DOWNSAMPLE_MODE))
-        .c_str())
-      .value_or(Settings::DEFAULT_GPU_DOWNSAMPLE_MODE);
-
-  const bool visible = (mode == GPUDownsampleMode::Box);
-  if (visible && m_ui.gpuDownsampleLayout->indexOf(m_ui.gpuDownsampleScale) < 0)
-  {
-    m_ui.gpuDownsampleScale->setVisible(true);
-    m_ui.gpuDownsampleLayout->addWidget(m_ui.gpuDownsampleScale, 0);
-  }
-  else if (!visible && m_ui.gpuDownsampleLayout->indexOf(m_ui.gpuDownsampleScale) >= 0)
-  {
-    m_ui.gpuDownsampleScale->setVisible(false);
-    m_ui.gpuDownsampleLayout->removeWidget(m_ui.gpuDownsampleScale);
-  }
-}
-
-void EnhancementSettingsWidget::setupAdditionalUi()
-{
-  QtUtils::FillComboBoxWithResolutionScales(m_ui.resolutionScale);
-
-  for (u32 i = 0; i < static_cast<u32>(GPUTextureFilter::Count); i++)
-  {
-    m_ui.textureFiltering->addItem(
-      QString::fromUtf8(Settings::GetTextureFilterDisplayName(static_cast<GPUTextureFilter>(i))));
-  }
-
-  for (u32 i = 0; i < static_cast<u32>(GPULineDetectMode::Count); i++)
-  {
-    m_ui.gpuLineDetectMode->addItem(
-      QString::fromUtf8(Settings::GetLineDetectModeDisplayName(static_cast<GPULineDetectMode>(i))));
-  }
-
-  for (u32 i = 0; i < static_cast<u32>(GPUDownsampleMode::Count); i++)
-  {
-    m_ui.gpuDownsampleMode->addItem(
-      QString::fromUtf8(Settings::GetDownsampleModeDisplayName(static_cast<GPUDownsampleMode>(i))));
-  }
-}
-
-void EnhancementSettingsWidget::updatePGXPSettingsEnabled()
-{
-  const bool enabled = m_dialog->getEffectiveBoolValue("GPU", "PGXPEnable", false);
-  const bool tc_enabled = enabled && m_dialog->getEffectiveBoolValue("GPU", "PGXPTextureCorrection", true);
-  m_ui.pgxpCulling->setEnabled(enabled);
-  m_ui.pgxpTextureCorrection->setEnabled(enabled);
-  m_ui.pgxpColorCorrection->setEnabled(tc_enabled);
-  m_ui.pgxpDepthBuffer->setEnabled(enabled);
-  m_ui.pgxpPreserveProjPrecision->setEnabled(enabled);
-  m_ui.pgxpCPU->setEnabled(enabled);
-}
diff --git a/src/duckstation-qt/enhancementsettingswidget.h b/src/duckstation-qt/enhancementsettingswidget.h
deleted file mode 100644
index 448956a3f..000000000
--- a/src/duckstation-qt/enhancementsettingswidget.h
+++ /dev/null
@@ -1,31 +0,0 @@
-// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
-// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
-
-#pragma once
-
-#include <QtWidgets/QWidget>
-
-#include "ui_enhancementsettingswidget.h"
-
-class SettingsWindow;
-
-class EnhancementSettingsWidget : public QWidget
-{
-  Q_OBJECT
-
-public:
-  EnhancementSettingsWidget(SettingsWindow* dialog, QWidget* parent);
-  ~EnhancementSettingsWidget();
-
-private Q_SLOTS:
-  void onTrueColorChanged();
-  void updateDownsampleScaleVisible();
-  void updatePGXPSettingsEnabled();
-
-private:
-  void setupAdditionalUi();
-
-  Ui::EnhancementSettingsWidget m_ui;
-
-  SettingsWindow* m_dialog;
-};
diff --git a/src/duckstation-qt/enhancementsettingswidget.ui b/src/duckstation-qt/enhancementsettingswidget.ui
deleted file mode 100644
index 9cf7cb407..000000000
--- a/src/duckstation-qt/enhancementsettingswidget.ui
+++ /dev/null
@@ -1,243 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>EnhancementSettingsWidget</class>
- <widget class="QWidget" name="EnhancementSettingsWidget">
-  <property name="geometry">
-   <rect>
-    <x>0</x>
-    <y>0</y>
-    <width>640</width>
-    <height>480</height>
-   </rect>
-  </property>
-  <layout class="QVBoxLayout" name="verticalLayout_2">
-   <property name="leftMargin">
-    <number>0</number>
-   </property>
-   <property name="topMargin">
-    <number>0</number>
-   </property>
-   <property name="rightMargin">
-    <number>0</number>
-   </property>
-   <property name="bottomMargin">
-    <number>0</number>
-   </property>
-   <item>
-    <widget class="QGroupBox" name="groupBox_2">
-     <property name="title">
-      <string>Rendering Enhancements</string>
-     </property>
-     <layout class="QFormLayout" name="formLayout_2">
-      <item row="0" column="0">
-       <widget class="QLabel" name="label_2">
-        <property name="text">
-         <string>Internal Resolution Scale:</string>
-        </property>
-       </widget>
-      </item>
-      <item row="0" column="1">
-       <widget class="QComboBox" name="resolutionScale"/>
-      </item>
-      <item row="2" column="0">
-       <widget class="QLabel" name="label_3">
-        <property name="text">
-         <string>Texture Filtering:</string>
-        </property>
-       </widget>
-      </item>
-      <item row="2" column="1">
-       <widget class="QComboBox" name="textureFiltering"/>
-      </item>
-      <item row="4" column="0">
-       <widget class="QLabel" name="label">
-        <property name="text">
-         <string>Downsampling:</string>
-        </property>
-       </widget>
-      </item>
-      <item row="4" column="1">
-       <layout class="QHBoxLayout" name="gpuDownsampleLayout" stretch="1,0">
-        <item>
-         <widget class="QComboBox" name="gpuDownsampleMode"/>
-        </item>
-        <item>
-         <widget class="QSpinBox" name="gpuDownsampleScale">
-          <property name="suffix">
-           <string>x</string>
-          </property>
-          <property name="minimum">
-           <number>1</number>
-          </property>
-          <property name="maximum">
-           <number>16</number>
-          </property>
-         </widget>
-        </item>
-       </layout>
-      </item>
-      <item row="5" column="0" colspan="2">
-       <layout class="QGridLayout" name="gridLayout_2">
-        <item row="0" column="0">
-         <widget class="QCheckBox" name="trueColor">
-          <property name="text">
-           <string>True Color Rendering</string>
-          </property>
-         </widget>
-        </item>
-        <item row="0" column="1">
-         <widget class="QCheckBox" name="debanding">
-          <property name="text">
-           <string>True Color Debanding</string>
-          </property>
-         </widget>
-        </item>
-        <item row="1" column="0">
-         <widget class="QCheckBox" name="scaledDithering">
-          <property name="text">
-           <string>Scaled Dithering</string>
-          </property>
-         </widget>
-        </item>
-        <item row="1" column="1">
-         <widget class="QCheckBox" name="widescreenHack">
-          <property name="text">
-           <string>Widescreen Hack</string>
-          </property>
-         </widget>
-        </item>
-        <item row="2" column="1">
-         <widget class="QCheckBox" name="useSoftwareRendererForReadbacks">
-          <property name="text">
-           <string>Software Renderer Readbacks</string>
-          </property>
-         </widget>
-        </item>
-        <item row="2" column="0">
-         <widget class="QCheckBox" name="disableInterlacing">
-          <property name="text">
-           <string>Disable Interlacing</string>
-          </property>
-         </widget>
-        </item>
-       </layout>
-      </item>
-      <item row="3" column="0">
-       <widget class="QLabel" name="label_4">
-        <property name="text">
-         <string>Line Detection:</string>
-        </property>
-       </widget>
-      </item>
-      <item row="3" column="1">
-       <widget class="QComboBox" name="gpuLineDetectMode"/>
-      </item>
-     </layout>
-    </widget>
-   </item>
-   <item>
-    <widget class="QGroupBox" name="groupBox_3">
-     <property name="title">
-      <string>Display Enhancements</string>
-     </property>
-     <layout class="QGridLayout" name="gridLayout_3">
-      <item row="0" column="0">
-       <widget class="QCheckBox" name="force43For24Bit">
-        <property name="text">
-         <string>Force 4:3 For FMVs</string>
-        </property>
-       </widget>
-      </item>
-      <item row="0" column="1">
-       <widget class="QCheckBox" name="chromaSmoothingFor24Bit">
-        <property name="text">
-         <string>FMV Chroma Smoothing</string>
-        </property>
-       </widget>
-      </item>
-      <item row="1" column="0">
-       <widget class="QCheckBox" name="forceNTSCTimings">
-        <property name="text">
-         <string>Force NTSC Timings</string>
-        </property>
-       </widget>
-      </item>
-     </layout>
-    </widget>
-   </item>
-   <item>
-    <widget class="QGroupBox" name="groupBox_4">
-     <property name="title">
-      <string>PGXP (Precision Geometry Transform Pipeline)</string>
-     </property>
-     <layout class="QGridLayout" name="gridLayout">
-      <item row="0" column="1">
-       <widget class="QCheckBox" name="pgxpCulling">
-        <property name="text">
-         <string>Culling Correction</string>
-        </property>
-       </widget>
-      </item>
-      <item row="1" column="0">
-       <widget class="QCheckBox" name="pgxpTextureCorrection">
-        <property name="text">
-         <string>Perspective Correct Textures</string>
-        </property>
-       </widget>
-      </item>
-      <item row="0" column="0">
-       <widget class="QCheckBox" name="pgxpEnable">
-        <property name="text">
-         <string>Geometry Correction</string>
-        </property>
-       </widget>
-      </item>
-      <item row="3" column="0">
-       <widget class="QCheckBox" name="pgxpCPU">
-        <property name="text">
-         <string>CPU Mode (Very Slow)</string>
-        </property>
-       </widget>
-      </item>
-      <item row="2" column="1">
-       <widget class="QCheckBox" name="pgxpDepthBuffer">
-        <property name="text">
-         <string>Depth Buffer (Low Compatibility)</string>
-        </property>
-       </widget>
-      </item>
-      <item row="2" column="0">
-       <widget class="QCheckBox" name="pgxpPreserveProjPrecision">
-        <property name="text">
-         <string>Preserve Projection Precision</string>
-        </property>
-       </widget>
-      </item>
-      <item row="1" column="1">
-       <widget class="QCheckBox" name="pgxpColorCorrection">
-        <property name="text">
-         <string>Perspective Correct Colors</string>
-        </property>
-       </widget>
-      </item>
-     </layout>
-    </widget>
-   </item>
-   <item>
-    <spacer name="verticalSpacer">
-     <property name="orientation">
-      <enum>Qt::Vertical</enum>
-     </property>
-     <property name="sizeHint" stdset="0">
-      <size>
-       <width>20</width>
-       <height>40</height>
-      </size>
-     </property>
-    </spacer>
-   </item>
-  </layout>
- </widget>
- <resources/>
- <connections/>
-</ui>
diff --git a/src/duckstation-qt/graphicssettingswidget.cpp b/src/duckstation-qt/graphicssettingswidget.cpp
new file mode 100644
index 000000000..034502ea4
--- /dev/null
+++ b/src/duckstation-qt/graphicssettingswidget.cpp
@@ -0,0 +1,861 @@
+// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
+// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
+
+#include "graphicssettingswidget.h"
+#include "core/gpu.h"
+#include "core/settings.h"
+#include "qtutils.h"
+#include "settingswindow.h"
+#include "settingwidgetbinder.h"
+
+// For enumerating adapters.
+#ifdef _WIN32
+#include "util/d3d11_device.h"
+#include "util/d3d12_device.h"
+#endif
+#ifdef ENABLE_VULKAN
+#include "util/vulkan_device.h"
+#endif
+
+static QVariant GetMSAAModeValue(uint multisamples, bool ssaa)
+{
+  const uint userdata = (multisamples & 0x7FFFFFFFu) | (static_cast<uint>(ssaa) << 31);
+  return QVariant(userdata);
+}
+
+static void DecodeMSAAModeValue(const QVariant& userdata, uint* multisamples, bool* ssaa)
+{
+  bool ok;
+  const uint value = userdata.toUInt(&ok);
+  if (!ok || value == 0)
+  {
+    *multisamples = 1;
+    *ssaa = false;
+    return;
+  }
+
+  *multisamples = value & 0x7FFFFFFFu;
+  *ssaa = (value & (1u << 31)) != 0u;
+}
+
+GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget* parent)
+  : QWidget(parent), m_dialog(dialog)
+{
+  SettingsInterface* sif = dialog->getSettingsInterface();
+
+  m_ui.setupUi(this);
+  setupAdditionalUi();
+  removePlatformSpecificUi();
+
+  // Rendering Tab
+
+  SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.renderer, "GPU", "Renderer", &Settings::ParseRendererName,
+                                               &Settings::GetRendererName, Settings::DEFAULT_GPU_RENDERER);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.vsync, "Display", "VSync", false);
+
+  SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.displayAspectRatio, "Display", "AspectRatio",
+                                               &Settings::ParseDisplayAspectRatio, &Settings::GetDisplayAspectRatioName,
+                                               Settings::DEFAULT_DISPLAY_ASPECT_RATIO);
+  SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.customAspectRatioNumerator, "Display",
+                                              "CustomAspectRatioNumerator", 1);
+  SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.customAspectRatioDenominator, "Display",
+                                              "CustomAspectRatioDenominator", 1);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.widescreenHack, "GPU", "WidescreenHack", false);
+  SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.displayCropMode, "Display", "CropMode",
+                                               &Settings::ParseDisplayCropMode, &Settings::GetDisplayCropModeName,
+                                               Settings::DEFAULT_DISPLAY_CROP_MODE);
+  SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.displayScaling, "Display", "Scaling",
+                                               &Settings::ParseDisplayScaling, &Settings::GetDisplayScalingName,
+                                               Settings::DEFAULT_DISPLAY_SCALING);
+  SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.resolutionScale, "GPU", "ResolutionScale", 1);
+  SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.textureFiltering, "GPU", "TextureFilter",
+                                               &Settings::ParseTextureFilterName, &Settings::GetTextureFilterName,
+                                               Settings::DEFAULT_GPU_TEXTURE_FILTER);
+  SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.gpuDownsampleMode, "GPU", "DownsampleMode",
+                                               &Settings::ParseDownsampleModeName, &Settings::GetDownsampleModeName,
+                                               Settings::DEFAULT_GPU_DOWNSAMPLE_MODE);
+  SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.gpuDownsampleScale, "GPU", "DownsampleScale", 1);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.trueColor, "GPU", "TrueColor", false);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.disableInterlacing, "GPU", "DisableInterlacing", true);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pgxpEnable, "GPU", "PGXPEnable", false);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pgxpDepthBuffer, "GPU", "PGXPDepthBuffer", false);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.force43For24Bit, "Display", "Force4_3For24Bit", false);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.chromaSmoothingFor24Bit, "GPU", "ChromaSmoothing24Bit", false);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.forceNTSCTimings, "GPU", "ForceNTSCTimings", false);
+
+  connect(m_ui.renderer, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
+          &GraphicsSettingsWidget::updateRendererDependentOptions);
+  connect(m_ui.adapter, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
+          &GraphicsSettingsWidget::onAdapterChanged);
+  connect(m_ui.resolutionScale, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
+          &GraphicsSettingsWidget::onTrueColorChanged);
+  connect(m_ui.displayAspectRatio, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
+          &GraphicsSettingsWidget::onAspectRatioChanged);
+  connect(m_ui.gpuDownsampleMode, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
+          &GraphicsSettingsWidget::onDownsampleModeChanged);
+  connect(m_ui.trueColor, &QCheckBox::stateChanged, this, &GraphicsSettingsWidget::onTrueColorChanged);
+  connect(m_ui.pgxpEnable, &QCheckBox::stateChanged, this, &GraphicsSettingsWidget::updatePGXPSettingsEnabled);
+
+  if (!dialog->isPerGameSettings() ||
+      (dialog->containsSettingValue("GPU", "Multisamples") || dialog->containsSettingValue("GPU", "PerSampleShading")))
+  {
+    const QVariant current_msaa_mode(
+      GetMSAAModeValue(static_cast<uint>(dialog->getEffectiveIntValue("GPU", "Multisamples", 1)),
+                       dialog->getEffectiveBoolValue("GPU", "PerSampleShading", false)));
+    const int current_msaa_index = m_ui.msaaMode->findData(current_msaa_mode);
+    if (current_msaa_index >= 0)
+      m_ui.msaaMode->setCurrentIndex(current_msaa_index);
+  }
+  else
+  {
+    m_ui.msaaMode->setCurrentIndex(0);
+  }
+  connect(m_ui.msaaMode, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
+          &GraphicsSettingsWidget::onMSAAModeChanged);
+
+  // Advanced Tab
+
+  SettingWidgetBinder::BindWidgetToEnumSetting(
+    sif, m_ui.exclusiveFullscreenControl, "Display", "ExclusiveFullscreenControl",
+    &Settings::ParseDisplayExclusiveFullscreenControl, &Settings::GetDisplayExclusiveFullscreenControlName,
+    Settings::DEFAULT_DISPLAY_EXCLUSIVE_FULLSCREEN_CONTROL);
+  SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.displayAlignment, "Display", "Alignment",
+                                               &Settings::ParseDisplayAlignment, &Settings::GetDisplayAlignmentName,
+                                               Settings::DEFAULT_DISPLAY_ALIGNMENT);
+  SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.displayFPSLimit, "Display", "MaxFPS",
+                                              Settings::DEFAULT_DISPLAY_MAX_FPS);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.gpuThread, "GPU", "UseThread", true);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.threadedPresentation, "GPU", "ThreadedPresentation", true);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.stretchDisplayVertically, "Display", "StretchVertically",
+                                               false);
+#ifdef _WIN32
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.blitSwapChain, "Display", "UseBlitSwapChain", false);
+#endif
+  SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.gpuLineDetectMode, "GPU", "LineDetectMode",
+                                               &Settings::ParseLineDetectModeName, &Settings::GetLineDetectModeName,
+                                               Settings::DEFAULT_GPU_LINE_DETECT_MODE);
+  SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.gpuWireframeMode, "GPU", "WireframeMode",
+                                               Settings::ParseGPUWireframeMode, Settings::GetGPUWireframeModeName,
+                                               Settings::DEFAULT_GPU_WIREFRAME_MODE);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.debanding, "GPU", "Debanding", false);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.scaledDithering, "GPU", "ScaledDithering", false);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.useSoftwareRendererForReadbacks, "GPU",
+                                               "UseSoftwareRendererForReadbacks", false);
+
+  connect(m_ui.fullscreenMode, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
+          &GraphicsSettingsWidget::onFullscreenModeChanged);
+
+  // PGXP Tab
+
+  SettingWidgetBinder::BindWidgetToFloatSetting(sif, m_ui.pgxpGeometryTolerance, "GPU", "PGXPTolerance", -1.0f);
+  SettingWidgetBinder::BindWidgetToFloatSetting(sif, m_ui.pgxpDepthClearThreshold, "GPU", "PGXPDepthClearThreshold",
+                                                Settings::DEFAULT_GPU_PGXP_DEPTH_THRESHOLD);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pgxpTextureCorrection, "GPU", "PGXPTextureCorrection", true);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pgxpColorCorrection, "GPU", "PGXPColorCorrection", false);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pgxpCulling, "GPU", "PGXPCulling", true);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pgxpPreserveProjPrecision, "GPU", "PGXPPreserveProjFP", false);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pgxpCPU, "GPU", "PGXPCPU", false);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pgxpVertexCache, "GPU", "PGXPVertexCache", false);
+
+  connect(m_ui.pgxpTextureCorrection, &QCheckBox::stateChanged, this,
+          &GraphicsSettingsWidget::updatePGXPSettingsEnabled);
+  connect(m_ui.pgxpDepthBuffer, &QCheckBox::stateChanged, this, &GraphicsSettingsWidget::updatePGXPSettingsEnabled);
+
+  // OSD Tab
+
+  SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.osdScale, "Display", "OSDScale", 100);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showOSDMessages, "Display", "ShowOSDMessages", true);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showFPS, "Display", "ShowFPS", false);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showSpeed, "Display", "ShowSpeed", false);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showResolution, "Display", "ShowResolution", false);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showCPU, "Display", "ShowCPU", false);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showGPU, "Display", "ShowGPU", false);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showInput, "Display", "ShowInputs", false);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showGPUStatistics, "Display", "ShowGPUStatistics", false);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showStatusIndicators, "Display", "ShowStatusIndicators", true);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showFrameTimes, "Display", "ShowFrameTimes", false);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showSettings, "Display", "ShowEnhancements", false);
+
+  // Capture Tab
+
+  SettingWidgetBinder::BindWidgetToEnumSetting(
+    sif, m_ui.screenshotSize, "Display", "ScreenshotMode", &Settings::ParseDisplayScreenshotMode,
+    &Settings::GetDisplayScreenshotModeName, Settings::DEFAULT_DISPLAY_SCREENSHOT_MODE);
+  SettingWidgetBinder::BindWidgetToEnumSetting(
+    sif, m_ui.screenshotFormat, "Display", "ScreenshotFormat", &Settings::ParseDisplayScreenshotFormat,
+    &Settings::GetDisplayScreenshotFormatName, Settings::DEFAULT_DISPLAY_SCREENSHOT_FORMAT);
+  SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.screenshotQuality, "Display", "ScreenshotQuality",
+                                              Settings::DEFAULT_DISPLAY_SCREENSHOT_QUALITY);
+
+  // Texture Replacements Tab
+
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.vramWriteReplacement, "TextureReplacements",
+                                               "EnableVRAMWriteReplacements", false);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.preloadTextureReplacements, "TextureReplacements",
+                                               "PreloadTextures", false);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.useOldMDECRoutines, "Hacks", "UseOldMDECRoutines", false);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.vramWriteDumping, "TextureReplacements", "DumpVRAMWrites",
+                                               false);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.setVRAMWriteAlphaChannel, "TextureReplacements",
+                                               "DumpVRAMWriteForceAlphaChannel", true);
+  SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.minDumpedVRAMWriteWidth, "TextureReplacements",
+                                              "DumpVRAMWriteWidthThreshold",
+                                              Settings::DEFAULT_VRAM_WRITE_DUMP_WIDTH_THRESHOLD);
+  SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.minDumpedVRAMWriteHeight, "TextureReplacements",
+                                              "DumpVRAMWriteHeightThreshold",
+                                              Settings::DEFAULT_VRAM_WRITE_DUMP_HEIGHT_THRESHOLD);
+
+  connect(m_ui.vramWriteReplacement, &QCheckBox::stateChanged, this,
+          &GraphicsSettingsWidget::onEnableAnyTextureReplacementsChanged);
+  connect(m_ui.vramWriteDumping, &QCheckBox::stateChanged, this,
+          &GraphicsSettingsWidget::onEnableVRAMWriteDumpingChanged);
+
+  // Debugging Tab
+
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.useDebugDevice, "GPU", "UseDebugDevice", false);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.disableShaderCache, "GPU", "DisableShaderCache", false);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.disableDualSource, "GPU", "DisableDualSourceBlend", false);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.disableFramebufferFetch, "GPU", "DisableFramebufferFetch",
+                                               false);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.disableTextureBuffers, "GPU", "DisableTextureBuffers", false);
+  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.disableTextureCopyToSelf, "GPU", "DisableTextureCopyToSelf",
+                                               false);
+
+  // Init all dependent options.
+  updateRendererDependentOptions();
+  onAspectRatioChanged();
+  onDownsampleModeChanged();
+  onTrueColorChanged();
+  onEnableAnyTextureReplacementsChanged();
+  onEnableVRAMWriteDumpingChanged();
+  onShowDebugSettingsChanged(QtHost::ShouldShowDebugOptions());
+
+  // Rendering Tab
+
+  dialog->registerWidgetHelp(
+    m_ui.renderer, tr("Renderer"), QString::fromUtf8(Settings::GetRendererDisplayName(Settings::DEFAULT_GPU_RENDERER)),
+    tr("Chooses the backend to use for rendering the console/game visuals. <br>Depending on your system and hardware, "
+       "Direct3D 11 and OpenGL hardware backends may be available. <br>The software renderer offers the best "
+       "compatibility, but is the slowest and does not offer any enhancements."));
+  dialog->registerWidgetHelp(
+    m_ui.adapter, tr("Adapter"), tr("(Default)"),
+    tr("If your system contains multiple GPUs or adapters, you can select which GPU you wish to use for the hardware "
+       "renderers. <br>This option is only supported in Direct3D and Vulkan. OpenGL will always use the default "
+       "device."));
+  dialog->registerWidgetHelp(
+    m_ui.vsync, tr("VSync"), tr("Unchecked"),
+    tr("Enable this option to match DuckStation's refresh rate with your current monitor or screen. "
+       "VSync is automatically disabled when it is not possible (e.g. running at non-100% speed)."));
+  dialog->registerWidgetHelp(
+    m_ui.displayAspectRatio, tr("Aspect Ratio"),
+    QString::fromUtf8(Settings::GetDisplayAspectRatioDisplayName(Settings::DEFAULT_DISPLAY_ASPECT_RATIO)),
+    tr("Changes the aspect ratio used to display the console's output to the screen. The default is Auto (Game Native) "
+       "which automatically adjusts the aspect ratio to match how a game would be shown on a typical TV of the era."));
+  dialog->registerWidgetHelp(
+    m_ui.displayCropMode, tr("Crop Mode"),
+    QString::fromUtf8(Settings::GetDisplayCropModeDisplayName(Settings::DEFAULT_DISPLAY_CROP_MODE)),
+    tr("Determines how much of the area typically not visible on a consumer TV set to crop/hide. Some games display "
+       "content in the overscan area, or use it for screen effects. May not display correctly with the \"All Borders\" "
+       "setting. \"Only Overscan\" offers a good compromise between stability and hiding black borders."));
+  dialog->registerWidgetHelp(
+    m_ui.displayScaling, tr("Scaling"), tr("Bilinear (Smooth)"),
+    tr("Determines how the emulated console's output is upscaled or downscaled to your monitor's resolution."));
+  dialog->registerWidgetHelp(
+    m_ui.resolutionScale, tr("Internal Resolution"), "1x",
+    tr("Setting this beyond 1x will enhance the resolution of rendered 3D polygons and lines. Only applies "
+       "to the hardware backends. <br>This option is usually safe, with most games looking fine at "
+       "higher resolutions. Higher resolutions require a more powerful GPU."));
+  dialog->registerWidgetHelp(
+    m_ui.msaaMode, tr("Multi-Sampling"), tr("Disabled"),
+    tr("Uses multi-sampled anti-aliasing when rendering 3D polygons. Can improve visuals with a lower performance "
+       "requirement compared to upscaling, <strong>but often introduces rendering errors.</strong>"));
+  dialog->registerWidgetHelp(
+    m_ui.gpuDownsampleMode, tr("Down-Sampling"), tr("Disabled"),
+    tr("Downsamples the rendered image prior to displaying it. Can improve overall image quality in mixed 2D/3D games, "
+       "but should be disabled for pure 3D games. Only applies to the hardware renderers."));
+  dialog->registerWidgetHelp(m_ui.gpuDownsampleScale, tr("Down-Sampling Display Scale"), tr("1x"),
+                             tr("Selects the resolution scale that will be applied to the final image. 1x will "
+                                "downsample to the original console resolution."));
+  dialog->registerWidgetHelp(
+    m_ui.textureFiltering, tr("Texture Filtering"),
+    QString::fromUtf8(Settings::GetTextureFilterDisplayName(Settings::DEFAULT_GPU_TEXTURE_FILTER)),
+    tr("Smooths out the blockiness of magnified textures on 3D object by using filtering. <br>Will have a "
+       "greater effect on higher resolution scales. Only applies to the hardware renderers. <br>The JINC2 and "
+       "especially xBR filtering modes are very demanding, and may not be worth the speed penalty."));
+  dialog->registerWidgetHelp(
+    m_ui.trueColor, tr("True Color Rendering"), tr("Checked"),
+    tr("Forces the precision of colours output to the console's framebuffer to use the full 8 bits of precision per "
+       "channel. This produces nicer looking gradients at the cost of making some colours look slightly different. "
+       "Disabling the option also enables dithering, which makes the transition between colours less sharp by applying "
+       "a pattern around those pixels. Most games are compatible with this option, but there is a number which aren't "
+       "and will have broken effects with it enabled. Only applies to the hardware renderers."));
+  dialog->registerWidgetHelp(
+    m_ui.widescreenHack, tr("Widescreen Rendering"), tr("Unchecked"),
+    tr("Scales vertex positions in screen-space to a widescreen aspect ratio, essentially "
+       "increasing the field of view from 4:3 to the chosen display aspect ratio in 3D games. <b><u>May not be "
+       "compatible with all games.</u></b>"));
+  dialog->registerWidgetHelp(
+    m_ui.pgxpEnable, tr("PGXP Geometry Correction"), tr("Unchecked"),
+    tr("Reduces \"wobbly\" polygons and \"warping\" textures that are common in PS1 games. <br>Only "
+       "works with the hardware renderers. <b><u>May not be compatible with all games.</u></b>"));
+  dialog->registerWidgetHelp(
+    m_ui.pgxpDepthBuffer, tr("PGXP Depth Buffer"), tr("Unchecked"),
+    tr("Attempts to reduce polygon Z-fighting by testing pixels against the depth values from PGXP. Low compatibility, "
+       "but can work well in some games. Other games may need a threshold adjustment."));
+  dialog->registerWidgetHelp(
+    m_ui.force43For24Bit, tr("Force 4:3 For FMVs"), tr("Unchecked"),
+    tr("Switches back to 4:3 display aspect ratio when displaying 24-bit content, usually FMVs."));
+  dialog->registerWidgetHelp(m_ui.chromaSmoothingFor24Bit, tr("FMV Chroma Smoothing"), tr("Unchecked"),
+                             tr("Smooths out blockyness between colour transitions in 24-bit content, usually FMVs. "
+                                "Only applies to the hardware renderers."));
+  dialog->registerWidgetHelp(
+    m_ui.disableInterlacing, tr("Disable Interlacing"), tr("Checked"),
+    tr(
+      "Forces the rendering and display of frames to progressive mode. <br>This removes the \"combing\" effect seen in "
+      "480i games by rendering them in 480p. Usually safe to enable.<br><b><u>May not be compatible with all "
+      "games.</u></b>"));
+  dialog->registerWidgetHelp(
+    m_ui.forceNTSCTimings, tr("Force NTSC Timings"), tr("Unchecked"),
+    tr("Uses NTSC frame timings when the console is in PAL mode, forcing PAL games to run at 60hz. <br>For most games "
+       "which have a speed tied to the framerate, this will result in the game running approximately 17% faster. "
+       "<br>For variable frame rate games, it may not affect the speed."));
+
+  // Advanced Tab
+
+  dialog->registerWidgetHelp(m_ui.fullscreenMode, tr("Fullscreen Mode"), tr("Borderless Fullscreen"),
+                             tr("Chooses the fullscreen resolution and frequency."));
+  dialog->registerWidgetHelp(m_ui.exclusiveFullscreenControl, tr("Exclusive Fullscreen Control"), tr("Automatic"),
+                             tr("Controls whether exclusive fullscreen can be utilized by Vulkan drivers."));
+  dialog->registerWidgetHelp(
+    m_ui.displayAlignment, tr("Position"),
+    QString::fromUtf8(Settings::GetDisplayAlignmentDisplayName(Settings::DEFAULT_DISPLAY_ALIGNMENT)),
+    tr("Determines the position on the screen when black borders must be added."));
+  dialog->registerWidgetHelp(
+    m_ui.displayFPSLimit, tr("Display FPS Limit"), tr("0"),
+    tr("Limits the number of frames that are <strong>displayed</strong> every second. Discard frames are <strong>still "
+       "rendered.</strong> This option can increase frame rates when fast forwarding on some systems."));
+  dialog->registerWidgetHelp(m_ui.gpuThread, tr("Threaded Rendering"), tr("Checked"),
+                             tr("Uses a second thread for drawing graphics. Currently only available for the software "
+                                "renderer, but can provide a significant speed improvement, and is safe to use."));
+  dialog->registerWidgetHelp(m_ui.threadedPresentation, tr("Threaded Presentation"), tr("Checked"),
+                             tr("Presents frames on a background thread when fast forwarding or vsync is disabled. "
+                                "This can measurably improve performance in the Vulkan renderer."));
+  dialog->registerWidgetHelp(
+    m_ui.stretchDisplayVertically, tr("Stretch Vertically"), tr("Unchecked"),
+    tr("Prefers stretching the display vertically instead of horizontally, wheen applying the display aspect ratio."));
+#ifdef _WIN32
+  dialog->registerWidgetHelp(m_ui.blitSwapChain, tr("Use Blit Swap Chain"), tr("Unchecked"),
+                             tr("Uses a blit presentation model instead of flipping when using the Direct3D 11 "
+                                "renderer. This usually results in slower performance, but may be required for some "
+                                "streaming applications, or to uncap framerates on some systems."));
+#endif
+
+  dialog->registerWidgetHelp(m_ui.gpuLineDetectMode, tr("Line Detection"),
+                             QString::fromUtf8(Settings::GetLineDetectModeName(Settings::DEFAULT_GPU_LINE_DETECT_MODE)),
+                             tr("Attempts to detect one pixel high/wide lines that rely on non-upscaled rasterization "
+                                "behavior, filling in gaps introduced by upscaling."));
+  dialog->registerWidgetHelp(m_ui.gpuWireframeMode, tr("Wireframe Mode"), tr("Disabled"),
+                             tr("Draws a wireframe outline of the triangles rendered by the console's GPU, either as a "
+                                "replacement or an overlay."));
+  dialog->registerWidgetHelp(
+    m_ui.debanding, tr("True Color Debanding"), tr("Unchecked"),
+    tr("Applies modern dithering techniques to further smooth out gradients when true color is enabled. "
+       "This debanding is performed during rendering (as opposed to a post-processing step), which allows it to be "
+       "fast while preserving detail. "
+       "Debanding increases the file size of screenshots due to the subtle dithering pattern present in screenshots."));
+  dialog->registerWidgetHelp(
+    m_ui.scaledDithering, tr("Scaled Dithering"), tr("Checked"),
+    tr("Scales the dither pattern to the resolution scale of the emulated GPU. This makes the dither pattern much less "
+       "obvious at higher resolutions. <br>Usually safe to enable, and only supported by the hardware renderers."));
+  dialog->registerWidgetHelp(
+    m_ui.useSoftwareRendererForReadbacks, tr("Software Renderer Readbacks"), tr("Unchecked"),
+    tr("Runs the software renderer in parallel for VRAM readbacks. On some systems, this may result in greater "
+       "performance when using graphical enhancements with the hardware renderer."));
+
+  // PGXP Tab
+
+  dialog->registerWidgetHelp(
+    m_ui.pgxpGeometryTolerance, tr("Geometry Tolerance"), tr("-1.00px (Disabled)"),
+    tr("Discards precise geometry when it is found to be offset past the specified threshold. This can help with games "
+       "that have vertices significantly moved by PGXP, but is still a hack/workaround."));
+  dialog->registerWidgetHelp(m_ui.pgxpDepthClearThreshold, tr("Depth Clear Threshold"),
+                             QStringLiteral("%1").arg(Settings::DEFAULT_GPU_PGXP_DEPTH_THRESHOLD),
+                             tr("Determines the increase in depth that will result in the depth buffer being cleared. "
+                                "Can help with depth issues in some games, but is still a hack/workaround."));
+  dialog->registerWidgetHelp(m_ui.pgxpTextureCorrection, tr("Perspective Correct Textures"), tr("Checked"),
+                             tr("Uses perspective-correct interpolation for texture coordinates, straightening out "
+                                "warped textures. Requires geometry correction enabled."));
+  dialog->registerWidgetHelp(
+    m_ui.pgxpColorCorrection, tr("Perspective Correct Colors"), tr("Unchecked"),
+    tr("Uses perspective-correct interpolation for vertex colors, which can improve visuals in some games, but cause "
+       "rendering errors in others. Requires geometry correction enabled."));
+  dialog->registerWidgetHelp(m_ui.pgxpCulling, tr("Culling Correction"), tr("Checked"),
+                             tr("Increases the precision of polygon culling, reducing the number of holes in geometry. "
+                                "Requires geometry correction enabled."));
+  dialog->registerWidgetHelp(
+    m_ui.pgxpPreserveProjPrecision, tr("Preserve Projection Precision"), tr("Unchecked"),
+    tr("Adds additional precision to PGXP data post-projection. May improve visuals in some games."));
+  dialog->registerWidgetHelp(m_ui.pgxpCPU, tr("CPU Mode"), tr("Unchecked"),
+                             tr("Uses PGXP for all instructions, not just memory operations. Required for PGXP to "
+                                "correct wobble in some games, but has a high performance cost."));
+  dialog->registerWidgetHelp(
+    m_ui.pgxpVertexCache, tr("Vertex Cache"), tr("Unchecked"),
+    tr("Uses screen-space vertex positions to obtain precise positions, instead of tracking memory accesses. Can "
+       "provide PGXP compatibility for some games, but <strong>generally provides no benefit.</strong>"));
+
+  // OSD Tab
+
+  dialog->registerWidgetHelp(
+    m_ui.osdScale, tr("OSD Scale"), tr("100%"),
+    tr("Changes the size at which on-screen elements, including status and messages are displayed."));
+  dialog->registerWidgetHelp(m_ui.showOSDMessages, tr("Show OSD Messages"), tr("Checked"),
+                             tr("Shows on-screen-display messages when events occur such as save states being "
+                                "created/loaded, screenshots being taken, etc."));
+  dialog->registerWidgetHelp(m_ui.showResolution, tr("Show Resolution"), tr("Unchecked"),
+                             tr("Shows the resolution of the game in the top-right corner of the display."));
+  dialog->registerWidgetHelp(
+    m_ui.showSpeed, tr("Show Emulation Speed"), tr("Unchecked"),
+    tr("Shows the current emulation speed of the system in the top-right corner of the display as a percentage."));
+  dialog->registerWidgetHelp(m_ui.showFPS, tr("Show FPS"), tr("Unchecked"),
+                             tr("Shows the internal frame rate of the game in the top-right corner of the display."));
+  dialog->registerWidgetHelp(
+    m_ui.showCPU, tr("Show CPU Usage"), tr("Unchecked"),
+    tr("Shows the host's CPU usage based on threads in the top-right corner of the display. This does not display the "
+       "emulated system CPU's usage. If a value close to 100% is being displayed, this means your host's CPU is likely "
+       "the bottleneck. In this case, you should reduce enhancement-related settings such as overclocking."));
+  dialog->registerWidgetHelp(m_ui.showGPU, tr("Show GPU Usage"), tr("Unchecked"),
+                             tr("Shows the host's GPU usage in the top-right corner of the display."));
+  dialog->registerWidgetHelp(m_ui.showGPUStatistics, tr("Show GPU Statistics"), tr("Unchecked"),
+                             tr("Shows information about the emulated GPU in the top-right corner of the display."));
+  dialog->registerWidgetHelp(
+    m_ui.showFrameTimes, tr("Show Frame Times"), tr("Unchecked"),
+    tr("Shows the history of frame rendering times as a graph in the top-right corner of the display."));
+  dialog->registerWidgetHelp(
+    m_ui.showInput, tr("Show Controller Input"), tr("Unchecked"),
+    tr("Shows the current controller state of the system in the bottom-left corner of the display."));
+  dialog->registerWidgetHelp(m_ui.showInput, tr("Show Settings"), tr("Unchecked"),
+                             tr("Shows a summary of current settings in the bottom-right corner of the display."));
+  dialog->registerWidgetHelp(m_ui.showStatusIndicators, tr("Show Status Indicators"), tr("Checked"),
+                             tr("Shows indicators on screen when the system is not running in its \"normal\" state. "
+                                "For example, fast forwarding, or being paused."));
+
+  // Capture Tab
+
+  dialog->registerWidgetHelp(m_ui.screenshotSize, tr("Screenshot Size"), tr("Screen Resolution"),
+                             tr("Determines the resolution at which screenshots will be saved. Internal resolutions "
+                                "preserve more detail at the cost of file size."));
+  dialog->registerWidgetHelp(
+    m_ui.screenshotFormat, tr("Screenshot Format"), tr("PNG"),
+    tr("Selects the format which will be used to save screenshots. JPEG produces smaller files, but loses detail."));
+  dialog->registerWidgetHelp(m_ui.screenshotQuality, tr("Screenshot Quality"),
+                             QStringLiteral("%1%%").arg(Settings::DEFAULT_DISPLAY_SCREENSHOT_QUALITY),
+                             tr("Selects the quality at which screenshots will be compressed. Higher values preserve "
+                                "more detail for JPEG, and reduce file size for PNG."));
+
+  // Texture Replacements Tab
+
+  dialog->registerWidgetHelp(m_ui.vramWriteReplacement, tr("Enable VRAM Write Replacement"), tr("Unchecked"),
+                             tr("Enables the replacement of background textures in supported games. <strong>This is "
+                                "not general texture replacement.</strong>"));
+  dialog->registerWidgetHelp(m_ui.preloadTextureReplacements, tr("Preload Texture Replacements"), tr("Unchecked"),
+                             tr("Loads all replacement texture to RAM, reducing stuttering at runtime."));
+  dialog->registerWidgetHelp(m_ui.useOldMDECRoutines, tr("Use Old MDEC Routines"), tr("Unchecked"),
+                             tr("Enables the older, less accurate MDEC decoding routines. May be required for old "
+                                "replacement backgrounds to match/load."));
+  dialog->registerWidgetHelp(m_ui.setVRAMWriteAlphaChannel, tr("Set Alpha Channel"), tr("Checked"),
+                             tr("Clears the mask/transparency bit in VRAM write dumps."));
+  dialog->registerWidgetHelp(m_ui.vramWriteDumping, tr("Enable VRAM Write Dumping"), tr("Unchecked"),
+                             tr("Writes backgrounds that can be replaced to the dump directory."));
+  dialog->registerWidgetHelp(m_ui.minDumpedVRAMWriteWidth, tr("Dump Size Threshold"), tr("128px"),
+                             tr("Determines the threshold that triggers a VRAM write to be dumped."));
+  dialog->registerWidgetHelp(m_ui.minDumpedVRAMWriteHeight, tr("Dump Size Threshold"), tr("128px"),
+                             tr("Determines the threshold that triggers a VRAM write to be dumped."));
+
+  // Debugging Tab
+
+  dialog->registerWidgetHelp(
+    m_ui.useDebugDevice, tr("Use Debug Device"), tr("Unchecked"),
+    tr("Enable debugging when supported by the host's renderer API. <strong>Only for developer use.</strong>"));
+  dialog->registerWidgetHelp(
+    m_ui.disableShaderCache, tr("Disable Shader Cache"), tr("Unchecked"),
+    tr("Forces shaders to be compiled for every run of the program. <strong>Only for developer use.</strong>"));
+  dialog->registerWidgetHelp(m_ui.disableDualSource, tr("Disable Dual-Source Blending"), tr("Unchecked"),
+                             tr("Prevents dual-source blending from being used. Useful for testing broken graphics "
+                                "drivers. <strong>Only for developer use.</strong>"));
+  dialog->registerWidgetHelp(m_ui.disableFramebufferFetch, tr("Disable Framebuffer Fetch"), tr("Unchecked"),
+                             tr("Prevents the framebuffer fetch extensions from being used. Useful for testing broken "
+                                "graphics drivers. <strong>Only for developer use.</strong>"));
+  dialog->registerWidgetHelp(
+    m_ui.disableTextureBuffers, tr("Disable Texture Buffers"), tr("Unchecked"),
+    tr("Forces VRAM updates through texture updates, instead of texture buffers and draws. Useful for testing broken "
+       "graphics drivers. <strong>Only for developer use.</strong>"));
+  dialog->registerWidgetHelp(m_ui.disableTextureCopyToSelf, tr("Disable Texture Copies To Self"), tr("Unchecked"),
+                             tr("Disables the use of self-copy updates for the VRAM texture. Useful for testing broken "
+                                "graphics drivers. <strong>Only for developer use.</strong>"));
+}
+
+GraphicsSettingsWidget::~GraphicsSettingsWidget() = default;
+
+void GraphicsSettingsWidget::setupAdditionalUi()
+{
+  // Rendering Tab
+
+  for (u32 i = 0; i < static_cast<u32>(GPURenderer::Count); i++)
+  {
+    m_ui.renderer->addItem(QString::fromUtf8(Settings::GetRendererDisplayName(static_cast<GPURenderer>(i))));
+  }
+
+  for (u32 i = 0; i < static_cast<u32>(DisplayAspectRatio::Count); i++)
+  {
+    m_ui.displayAspectRatio->addItem(
+      QString::fromUtf8(Settings::GetDisplayAspectRatioDisplayName(static_cast<DisplayAspectRatio>(i))));
+  }
+
+  for (u32 i = 0; i < static_cast<u32>(DisplayCropMode::Count); i++)
+  {
+    m_ui.displayCropMode->addItem(
+      QString::fromUtf8(Settings::GetDisplayCropModeDisplayName(static_cast<DisplayCropMode>(i))));
+  }
+
+  for (u32 i = 0; i < static_cast<u32>(DisplayScalingMode::Count); i++)
+  {
+    m_ui.displayScaling->addItem(
+      QString::fromUtf8(Settings::GetDisplayScalingDisplayName(static_cast<DisplayScalingMode>(i))));
+  }
+
+  {
+    if (m_dialog->isPerGameSettings())
+      m_ui.msaaMode->addItem(tr("Use Global Setting"));
+    m_ui.msaaMode->addItem(tr("Disabled"), GetMSAAModeValue(1, false));
+    for (uint i = 2; i <= 32; i *= 2)
+      m_ui.msaaMode->addItem(tr("%1x MSAA").arg(i), GetMSAAModeValue(i, false));
+    for (uint i = 2; i <= 32; i *= 2)
+      m_ui.msaaMode->addItem(tr("%1x SSAA").arg(i), GetMSAAModeValue(i, true));
+  }
+
+  for (u32 i = 0; i < static_cast<u32>(GPUTextureFilter::Count); i++)
+  {
+    m_ui.textureFiltering->addItem(
+      QString::fromUtf8(Settings::GetTextureFilterDisplayName(static_cast<GPUTextureFilter>(i))));
+  }
+
+  for (u32 i = 0; i < static_cast<u32>(GPUDownsampleMode::Count); i++)
+  {
+    m_ui.gpuDownsampleMode->addItem(
+      QString::fromUtf8(Settings::GetDownsampleModeDisplayName(static_cast<GPUDownsampleMode>(i))));
+  }
+
+  // Advanced Tab
+
+  for (u32 i = 0; i < static_cast<u32>(DisplayExclusiveFullscreenControl::Count); i++)
+  {
+    m_ui.exclusiveFullscreenControl->addItem(QString::fromUtf8(
+      Settings::GetDisplayExclusiveFullscreenControlDisplayName(static_cast<DisplayExclusiveFullscreenControl>(i))));
+  }
+
+  for (u32 i = 0; i < static_cast<u32>(DisplayAlignment::Count); i++)
+  {
+    m_ui.displayAlignment->addItem(
+      QString::fromUtf8(Settings::GetDisplayAlignmentDisplayName(static_cast<DisplayAlignment>(i))));
+  }
+
+  for (u32 i = 0; i < static_cast<u32>(GPULineDetectMode::Count); i++)
+  {
+    m_ui.gpuLineDetectMode->addItem(
+      QString::fromUtf8(Settings::GetLineDetectModeDisplayName(static_cast<GPULineDetectMode>(i))));
+  }
+
+  for (u32 i = 0; i < static_cast<u32>(GPUWireframeMode::Count); i++)
+  {
+    m_ui.gpuWireframeMode->addItem(
+      QString::fromUtf8(Settings::GetGPUWireframeModeDisplayName(static_cast<GPUWireframeMode>(i))));
+  }
+
+  // Capture Tab
+
+  for (u32 i = 0; i < static_cast<u32>(DisplayScreenshotMode::Count); i++)
+  {
+    m_ui.screenshotSize->addItem(
+      QString::fromUtf8(Settings::GetDisplayScreenshotModeDisplayName(static_cast<DisplayScreenshotMode>(i))));
+  }
+
+  for (u32 i = 0; i < static_cast<u32>(DisplayScreenshotFormat::Count); i++)
+  {
+    m_ui.screenshotFormat->addItem(
+      QString::fromUtf8(Settings::GetDisplayScreenshotFormatDisplayName(static_cast<DisplayScreenshotFormat>(i))));
+  }
+}
+
+void GraphicsSettingsWidget::removePlatformSpecificUi()
+{
+#ifndef _WIN32
+  m_ui.advancedDisplayOptionsLayout->removeWidget(m_ui.blitSwapChain);
+  delete m_ui.blitSwapChain;
+  m_ui.blitSwapChain = nullptr;
+#endif
+}
+
+GPURenderer GraphicsSettingsWidget::getEffectiveRenderer() const
+{
+  return Settings::ParseRendererName(
+           m_dialog
+             ->getEffectiveStringValue("GPU", "Renderer", Settings::GetRendererName(Settings::DEFAULT_GPU_RENDERER))
+             .c_str())
+    .value_or(Settings::DEFAULT_GPU_RENDERER);
+}
+
+bool GraphicsSettingsWidget::effectiveRendererIsHardware() const
+{
+  return (getEffectiveRenderer() != GPURenderer::Software);
+}
+
+void GraphicsSettingsWidget::onShowDebugSettingsChanged(bool enabled)
+{
+  m_ui.tabs->setTabVisible(TAB_INDEX_DEBUGGING, enabled);
+}
+
+void GraphicsSettingsWidget::updateRendererDependentOptions()
+{
+  const GPURenderer renderer = getEffectiveRenderer();
+  const RenderAPI render_api = Settings::GetRenderAPIForRenderer(renderer);
+  const bool is_hardware = (renderer != GPURenderer::Software);
+
+  m_ui.resolutionScale->setEnabled(is_hardware);
+  m_ui.resolutionScaleLabel->setEnabled(is_hardware);
+  m_ui.msaaMode->setEnabled(is_hardware);
+  m_ui.msaaModeLabel->setEnabled(is_hardware);
+  m_ui.textureFiltering->setEnabled(is_hardware);
+  m_ui.textureFilteringLabel->setEnabled(is_hardware);
+  m_ui.gpuDownsampleLabel->setEnabled(is_hardware);
+  m_ui.gpuDownsampleMode->setEnabled(is_hardware);
+  m_ui.gpuDownsampleScale->setEnabled(is_hardware);
+  m_ui.trueColor->setEnabled(is_hardware);
+  m_ui.pgxpEnable->setEnabled(is_hardware);
+  m_ui.chromaSmoothingFor24Bit->setEnabled(is_hardware);
+
+  m_ui.gpuLineDetectMode->setEnabled(is_hardware);
+  m_ui.gpuLineDetectModeLabel->setEnabled(is_hardware);
+  m_ui.gpuWireframeMode->setEnabled(is_hardware);
+  m_ui.gpuWireframeModeLabel->setEnabled(is_hardware);
+  m_ui.debanding->setEnabled(is_hardware);
+  m_ui.scaledDithering->setEnabled(is_hardware);
+  m_ui.useSoftwareRendererForReadbacks->setEnabled(is_hardware);
+
+  m_ui.tabs->setTabEnabled(TAB_INDEX_TEXTURE_REPLACEMENTS, is_hardware);
+
+#ifdef _WIN32
+  m_ui.blitSwapChain->setEnabled(render_api == RenderAPI::D3D11);
+#endif
+
+  m_ui.gpuThread->setEnabled(!is_hardware);
+  m_ui.threadedPresentation->setEnabled(render_api == RenderAPI::Vulkan);
+
+  m_ui.exclusiveFullscreenLabel->setEnabled(render_api == RenderAPI::D3D11 || render_api == RenderAPI::D3D12 ||
+                                            render_api == RenderAPI::Vulkan);
+  m_ui.exclusiveFullscreenControl->setEnabled(render_api == RenderAPI::Vulkan);
+
+  populateGPUAdaptersAndResolutions(render_api);
+  updatePGXPSettingsEnabled();
+}
+
+void GraphicsSettingsWidget::populateGPUAdaptersAndResolutions(RenderAPI render_api)
+{
+  GPUDevice::AdapterAndModeList aml;
+  switch (render_api)
+  {
+#ifdef _WIN32
+    case RenderAPI::D3D11:
+      aml = D3D11Device::StaticGetAdapterAndModeList();
+      break;
+
+    case RenderAPI::D3D12:
+      aml = D3D12Device::StaticGetAdapterAndModeList();
+      break;
+#endif
+#ifdef __APPLE__
+    case RenderAPI::Metal:
+      aml = GPUDevice::WrapGetMetalAdapterAndModeList();
+      break;
+#endif
+#ifdef ENABLE_VULKAN
+    case RenderAPI::Vulkan:
+      aml = VulkanDevice::StaticGetAdapterAndModeList();
+      break;
+#endif
+
+    default:
+      break;
+  }
+
+  {
+    const std::string current_adapter(m_dialog->getEffectiveStringValue("GPU", "Adapter", ""));
+    QSignalBlocker blocker(m_ui.adapter);
+
+    // add the default entry - we'll fall back to this if the GPU no longer exists, or there's no options
+    m_ui.adapter->clear();
+    m_ui.adapter->addItem(tr("(Default)"));
+
+    // add the other adapters
+    for (const std::string& adapter_name : aml.adapter_names)
+    {
+      m_ui.adapter->addItem(QString::fromStdString(adapter_name));
+
+      if (adapter_name == current_adapter)
+        m_ui.adapter->setCurrentIndex(m_ui.adapter->count() - 1);
+    }
+
+    // disable it if we don't have a choice
+    m_ui.adapter->setEnabled(!aml.adapter_names.empty());
+  }
+
+  {
+    const std::string current_mode(m_dialog->getEffectiveStringValue("GPU", "FullscreenMode", ""));
+    QSignalBlocker blocker(m_ui.fullscreenMode);
+
+    m_ui.fullscreenMode->clear();
+    m_ui.fullscreenMode->addItem(tr("Borderless Fullscreen"));
+    m_ui.fullscreenMode->setCurrentIndex(0);
+
+    for (const std::string& mode_name : aml.fullscreen_modes)
+    {
+      m_ui.fullscreenMode->addItem(QString::fromStdString(mode_name));
+
+      if (mode_name == current_mode)
+        m_ui.fullscreenMode->setCurrentIndex(m_ui.fullscreenMode->count() - 1);
+    }
+
+    // disable it if we don't have a choice
+    m_ui.fullscreenMode->setEnabled(!aml.fullscreen_modes.empty());
+  }
+
+  // TODO: MSAA modes
+}
+
+void GraphicsSettingsWidget::updatePGXPSettingsEnabled()
+{
+  const bool enabled = (effectiveRendererIsHardware() && m_dialog->getEffectiveBoolValue("GPU", "PGXPEnable", false));
+  const bool tc_enabled = (enabled && m_dialog->getEffectiveBoolValue("GPU", "PGXPTextureCorrection", true));
+  const bool depth_enabled = (enabled && m_dialog->getEffectiveBoolValue("GPU", "PGXPDepthBuffer", false));
+  m_ui.tabs->setTabEnabled(TAB_INDEX_PGXP, enabled);
+  m_ui.pgxpTab->setEnabled(enabled);
+  m_ui.pgxpCulling->setEnabled(enabled);
+  m_ui.pgxpTextureCorrection->setEnabled(enabled);
+  m_ui.pgxpColorCorrection->setEnabled(tc_enabled);
+  m_ui.pgxpDepthBuffer->setEnabled(enabled);
+  m_ui.pgxpPreserveProjPrecision->setEnabled(enabled);
+  m_ui.pgxpCPU->setEnabled(enabled);
+  m_ui.pgxpVertexCache->setEnabled(enabled);
+  m_ui.pgxpGeometryTolerance->setEnabled(enabled);
+  m_ui.pgxpGeometryToleranceLabel->setEnabled(enabled);
+  m_ui.pgxpDepthClearThreshold->setEnabled(depth_enabled);
+  m_ui.pgxpDepthClearThresholdLabel->setEnabled(depth_enabled);
+}
+
+void GraphicsSettingsWidget::onAdapterChanged()
+{
+  if (m_ui.adapter->currentIndex() == 0)
+  {
+    // default
+    m_dialog->removeSettingValue("GPU", "Adapter");
+    return;
+  }
+
+  m_dialog->setStringSettingValue("GPU", "Adapter", m_ui.adapter->currentText().toUtf8().constData());
+}
+
+void GraphicsSettingsWidget::onAspectRatioChanged()
+{
+  const DisplayAspectRatio ratio =
+    Settings::ParseDisplayAspectRatio(
+      m_dialog
+        ->getEffectiveStringValue("Display", "AspectRatio",
+                                  Settings::GetDisplayAspectRatioName(Settings::DEFAULT_DISPLAY_ASPECT_RATIO))
+        .c_str())
+      .value_or(Settings::DEFAULT_DISPLAY_ASPECT_RATIO);
+
+  const bool is_custom = (ratio == DisplayAspectRatio::Custom);
+
+  m_ui.customAspectRatioNumerator->setVisible(is_custom);
+  m_ui.customAspectRatioDenominator->setVisible(is_custom);
+  m_ui.customAspectRatioSeparator->setVisible(is_custom);
+}
+
+void GraphicsSettingsWidget::onMSAAModeChanged()
+{
+  const int index = m_ui.msaaMode->currentIndex();
+  if (m_dialog->isPerGameSettings() && index == 0)
+  {
+    m_dialog->removeSettingValue("GPU", "Multisamples");
+    m_dialog->removeSettingValue("GPU", "PerSampleShading");
+  }
+  else
+  {
+    uint multisamples;
+    bool ssaa;
+    DecodeMSAAModeValue(m_ui.msaaMode->itemData(index), &multisamples, &ssaa);
+    m_dialog->setIntSettingValue("GPU", "Multisamples", static_cast<int>(multisamples));
+    m_dialog->setBoolSettingValue("GPU", "PerSampleShading", ssaa);
+  }
+}
+
+void GraphicsSettingsWidget::onTrueColorChanged()
+{
+  const int resolution_scale = m_ui.resolutionScale->currentIndex();
+  const bool true_color = m_ui.trueColor->isChecked();
+  const bool allow_scaled_dithering = (resolution_scale != 1 && !true_color);
+  const bool allow_debanding = true_color;
+  m_ui.scaledDithering->setEnabled(allow_scaled_dithering);
+  m_ui.debanding->setEnabled(allow_debanding);
+}
+
+void GraphicsSettingsWidget::onDownsampleModeChanged()
+{
+  const GPUDownsampleMode mode =
+    Settings::ParseDownsampleModeName(
+      m_dialog
+        ->getEffectiveStringValue("GPU", "DownsampleMode",
+                                  Settings::GetDownsampleModeName(Settings::DEFAULT_GPU_DOWNSAMPLE_MODE))
+        .c_str())
+      .value_or(Settings::DEFAULT_GPU_DOWNSAMPLE_MODE);
+
+  const bool visible = (mode == GPUDownsampleMode::Box);
+  if (visible && m_ui.gpuDownsampleLayout->indexOf(m_ui.gpuDownsampleScale) < 0)
+  {
+    m_ui.gpuDownsampleScale->setVisible(true);
+    m_ui.gpuDownsampleLayout->addWidget(m_ui.gpuDownsampleScale, 0);
+  }
+  else if (!visible && m_ui.gpuDownsampleLayout->indexOf(m_ui.gpuDownsampleScale) >= 0)
+  {
+    m_ui.gpuDownsampleScale->setVisible(false);
+    m_ui.gpuDownsampleLayout->removeWidget(m_ui.gpuDownsampleScale);
+  }
+}
+
+void GraphicsSettingsWidget::onFullscreenModeChanged()
+{
+  if (m_ui.fullscreenMode->currentIndex() == 0)
+  {
+    // default
+    m_dialog->removeSettingValue("GPU", "FullscreenMode");
+    return;
+  }
+
+  m_dialog->setStringSettingValue("GPU", "FullscreenMode", m_ui.fullscreenMode->currentText().toUtf8().constData());
+}
+
+void GraphicsSettingsWidget::onEnableAnyTextureReplacementsChanged()
+{
+  const bool any_replacements_enabled =
+    m_dialog->getEffectiveBoolValue("TextureReplacements", "EnableVRAMWriteReplacements", false);
+  m_ui.preloadTextureReplacements->setEnabled(any_replacements_enabled);
+}
+
+void GraphicsSettingsWidget::onEnableVRAMWriteDumpingChanged()
+{
+  const bool enabled = m_dialog->getEffectiveBoolValue("TextureReplacements", "DumpVRAMWrites", false);
+  m_ui.setVRAMWriteAlphaChannel->setEnabled(enabled);
+  m_ui.minDumpedVRAMWriteWidth->setEnabled(enabled);
+  m_ui.minDumpedVRAMWriteHeight->setEnabled(enabled);
+  m_ui.vramWriteDumpThresholdLabel->setEnabled(enabled);
+  m_ui.vramWriteDumpThresholdSeparator->setEnabled(enabled);
+}
diff --git a/src/duckstation-qt/graphicssettingswidget.h b/src/duckstation-qt/graphicssettingswidget.h
new file mode 100644
index 000000000..d976915c7
--- /dev/null
+++ b/src/duckstation-qt/graphicssettingswidget.h
@@ -0,0 +1,59 @@
+// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
+// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
+
+#pragma once
+
+#include <QtWidgets/QWidget>
+
+#include "ui_graphicssettingswidget.h"
+
+enum class RenderAPI : u32;
+enum class GPURenderer : u8;
+
+class SettingsWindow;
+
+class GraphicsSettingsWidget : public QWidget
+{
+  Q_OBJECT
+
+public:
+  GraphicsSettingsWidget(SettingsWindow* dialog, QWidget* parent);
+  ~GraphicsSettingsWidget();
+
+public Q_SLOTS:
+  void onShowDebugSettingsChanged(bool enabled);
+
+private Q_SLOTS:
+  void updateRendererDependentOptions();
+  void updatePGXPSettingsEnabled();
+
+  void onAdapterChanged();
+  void onAspectRatioChanged();
+  void onMSAAModeChanged();
+  void onTrueColorChanged();
+  void onDownsampleModeChanged();
+  void onFullscreenModeChanged();
+  void onEnableAnyTextureReplacementsChanged();
+  void onEnableVRAMWriteDumpingChanged();
+
+private:
+  static constexpr int TAB_INDEX_RENDERING = 0;
+  static constexpr int TAB_INDEX_ADVANCED = 1;
+  static constexpr int TAB_INDEX_PGXP = 2;
+  static constexpr int TAB_INDEX_OSD = 3;
+  static constexpr int TAB_INDEX_CAPTURE = 4;
+  static constexpr int TAB_INDEX_TEXTURE_REPLACEMENTS = 5;
+  static constexpr int TAB_INDEX_DEBUGGING = 6;
+
+  void setupAdditionalUi();
+  void removePlatformSpecificUi();
+
+  GPURenderer getEffectiveRenderer() const;
+  bool effectiveRendererIsHardware() const;
+
+  void populateGPUAdaptersAndResolutions(RenderAPI render_api);
+
+  Ui::GraphicsSettingsWidget m_ui;
+
+  SettingsWindow* m_dialog;
+};
diff --git a/src/duckstation-qt/graphicssettingswidget.ui b/src/duckstation-qt/graphicssettingswidget.ui
new file mode 100644
index 000000000..e7c556b99
--- /dev/null
+++ b/src/duckstation-qt/graphicssettingswidget.ui
@@ -0,0 +1,1114 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>GraphicsSettingsWidget</class>
+ <widget class="QWidget" name="GraphicsSettingsWidget">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>584</width>
+    <height>446</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout_3">
+   <property name="leftMargin">
+    <number>0</number>
+   </property>
+   <property name="topMargin">
+    <number>0</number>
+   </property>
+   <property name="rightMargin">
+    <number>0</number>
+   </property>
+   <property name="bottomMargin">
+    <number>0</number>
+   </property>
+   <item>
+    <widget class="QGroupBox" name="groupBox_3">
+     <property name="title">
+      <string/>
+     </property>
+     <layout class="QFormLayout" name="formLayout">
+      <item row="0" column="0">
+       <widget class="QLabel" name="label">
+        <property name="text">
+         <string>Renderer:</string>
+        </property>
+       </widget>
+      </item>
+      <item row="0" column="1">
+       <widget class="QComboBox" name="renderer"/>
+      </item>
+      <item row="1" column="0">
+       <widget class="QLabel" name="label_5">
+        <property name="text">
+         <string>Adapter:</string>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="1">
+       <layout class="QHBoxLayout" name="horizontalLayout_3" stretch="1,0">
+        <item>
+         <widget class="QComboBox" name="adapter"/>
+        </item>
+        <item>
+         <widget class="QCheckBox" name="vsync">
+          <property name="text">
+           <string>VSync</string>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QTabWidget" name="tabs">
+     <property name="currentIndex">
+      <number>0</number>
+     </property>
+     <property name="documentMode">
+      <bool>true</bool>
+     </property>
+     <widget class="QWidget" name="basicTab">
+      <attribute name="title">
+       <string>Rendering</string>
+      </attribute>
+      <layout class="QVBoxLayout" name="verticalLayout_4">
+       <property name="leftMargin">
+        <number>0</number>
+       </property>
+       <property name="topMargin">
+        <number>0</number>
+       </property>
+       <property name="rightMargin">
+        <number>0</number>
+       </property>
+       <property name="bottomMargin">
+        <number>0</number>
+       </property>
+       <item>
+        <widget class="QGroupBox" name="groupBox_6">
+         <property name="title">
+          <string/>
+         </property>
+         <layout class="QFormLayout" name="formLayout_2">
+          <item row="0" column="0">
+           <widget class="QLabel" name="label_6">
+            <property name="text">
+             <string>Aspect Ratio:</string>
+            </property>
+           </widget>
+          </item>
+          <item row="0" column="1">
+           <layout class="QHBoxLayout" name="horizontalLayout_2" stretch="1,0,0,0">
+            <item>
+             <widget class="QComboBox" name="displayAspectRatio"/>
+            </item>
+            <item>
+             <widget class="QSpinBox" name="customAspectRatioNumerator">
+              <property name="minimum">
+               <number>1</number>
+              </property>
+              <property name="maximum">
+               <number>9999</number>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QLabel" name="customAspectRatioSeparator">
+              <property name="text">
+               <string>:</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QSpinBox" name="customAspectRatioDenominator">
+              <property name="minimum">
+               <number>1</number>
+              </property>
+              <property name="maximum">
+               <number>9999</number>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </item>
+          <item row="1" column="0">
+           <widget class="QLabel" name="label_7">
+            <property name="text">
+             <string>Crop:</string>
+            </property>
+           </widget>
+          </item>
+          <item row="1" column="1">
+           <widget class="QComboBox" name="displayCropMode"/>
+          </item>
+          <item row="2" column="0">
+           <widget class="QLabel" name="label_8">
+            <property name="text">
+             <string>Scaling:</string>
+            </property>
+           </widget>
+          </item>
+          <item row="2" column="1">
+           <widget class="QComboBox" name="displayScaling"/>
+          </item>
+          <item row="3" column="0">
+           <widget class="QLabel" name="resolutionScaleLabel">
+            <property name="text">
+             <string>Internal Resolution:</string>
+            </property>
+           </widget>
+          </item>
+          <item row="3" column="1">
+           <widget class="QComboBox" name="resolutionScale">
+            <item>
+             <property name="text">
+              <string>Automatic based on window size</string>
+             </property>
+            </item>
+            <item>
+             <property name="text">
+              <string>1x</string>
+             </property>
+            </item>
+            <item>
+             <property name="text">
+              <string>2x</string>
+             </property>
+            </item>
+            <item>
+             <property name="text">
+              <string>3x (for 720p)</string>
+             </property>
+            </item>
+            <item>
+             <property name="text">
+              <string>4x</string>
+             </property>
+            </item>
+            <item>
+             <property name="text">
+              <string>5x (for 1080p)</string>
+             </property>
+            </item>
+            <item>
+             <property name="text">
+              <string>6x (for 1440p)</string>
+             </property>
+            </item>
+            <item>
+             <property name="text">
+              <string>7x</string>
+             </property>
+            </item>
+            <item>
+             <property name="text">
+              <string>8x</string>
+             </property>
+            </item>
+            <item>
+             <property name="text">
+              <string>9x (for 4K)</string>
+             </property>
+            </item>
+            <item>
+             <property name="text">
+              <string>10x</string>
+             </property>
+            </item>
+            <item>
+             <property name="text">
+              <string>11x</string>
+             </property>
+            </item>
+            <item>
+             <property name="text">
+              <string>12x</string>
+             </property>
+            </item>
+            <item>
+             <property name="text">
+              <string>13x</string>
+             </property>
+            </item>
+            <item>
+             <property name="text">
+              <string>14x</string>
+             </property>
+            </item>
+            <item>
+             <property name="text">
+              <string>15x</string>
+             </property>
+            </item>
+            <item>
+             <property name="text">
+              <string>16x</string>
+             </property>
+            </item>
+           </widget>
+          </item>
+          <item row="4" column="0">
+           <widget class="QLabel" name="msaaModeLabel">
+            <property name="text">
+             <string>Multi-Sampling:</string>
+            </property>
+           </widget>
+          </item>
+          <item row="4" column="1">
+           <widget class="QComboBox" name="msaaMode"/>
+          </item>
+          <item row="6" column="0">
+           <widget class="QLabel" name="textureFilteringLabel">
+            <property name="text">
+             <string>Texture Filtering:</string>
+            </property>
+           </widget>
+          </item>
+          <item row="6" column="1">
+           <widget class="QComboBox" name="textureFiltering"/>
+          </item>
+          <item row="8" column="0" colspan="2">
+           <layout class="QGridLayout" name="gridLayout_2">
+            <item row="1" column="0">
+             <widget class="QCheckBox" name="pgxpEnable">
+              <property name="text">
+               <string>PGXP Geometry Correction</string>
+              </property>
+             </widget>
+            </item>
+            <item row="1" column="1">
+             <widget class="QCheckBox" name="pgxpDepthBuffer">
+              <property name="text">
+               <string>PGXP Depth Buffer (Low Compatibility)</string>
+              </property>
+             </widget>
+            </item>
+            <item row="2" column="0">
+             <widget class="QCheckBox" name="force43For24Bit">
+              <property name="text">
+               <string>Force 4:3 For FMVs</string>
+              </property>
+             </widget>
+            </item>
+            <item row="2" column="1">
+             <widget class="QCheckBox" name="chromaSmoothingFor24Bit">
+              <property name="text">
+               <string>FMV Chroma Smoothing</string>
+              </property>
+             </widget>
+            </item>
+            <item row="0" column="0">
+             <widget class="QCheckBox" name="trueColor">
+              <property name="text">
+               <string>True Color Rendering</string>
+              </property>
+             </widget>
+            </item>
+            <item row="3" column="1">
+             <widget class="QCheckBox" name="forceNTSCTimings">
+              <property name="text">
+               <string>Force NTSC Timings</string>
+              </property>
+             </widget>
+            </item>
+            <item row="3" column="0">
+             <widget class="QCheckBox" name="disableInterlacing">
+              <property name="text">
+               <string>Disable Interlacing</string>
+              </property>
+             </widget>
+            </item>
+            <item row="0" column="1">
+             <widget class="QCheckBox" name="widescreenHack">
+              <property name="text">
+               <string>Widescreen Rendering</string>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </item>
+          <item row="5" column="0">
+           <widget class="QLabel" name="gpuDownsampleLabel">
+            <property name="text">
+             <string>Down-Sampling:</string>
+            </property>
+           </widget>
+          </item>
+          <item row="5" column="1">
+           <layout class="QHBoxLayout" name="gpuDownsampleLayout" stretch="1,0">
+            <item>
+             <widget class="QComboBox" name="gpuDownsampleMode"/>
+            </item>
+            <item>
+             <widget class="QSpinBox" name="gpuDownsampleScale">
+              <property name="suffix">
+               <string>x</string>
+              </property>
+              <property name="minimum">
+               <number>1</number>
+              </property>
+              <property name="maximum">
+               <number>16</number>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </item>
+         </layout>
+        </widget>
+       </item>
+       <item>
+        <spacer name="verticalSpacer_3">
+         <property name="orientation">
+          <enum>Qt::Vertical</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>20</width>
+           <height>75</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="advancedTab">
+      <attribute name="title">
+       <string>Advanced</string>
+      </attribute>
+      <layout class="QVBoxLayout" name="verticalLayout">
+       <property name="leftMargin">
+        <number>0</number>
+       </property>
+       <property name="rightMargin">
+        <number>0</number>
+       </property>
+       <property name="bottomMargin">
+        <number>0</number>
+       </property>
+       <item>
+        <widget class="QGroupBox" name="groupBox_2">
+         <property name="title">
+          <string>Display Options</string>
+         </property>
+         <layout class="QFormLayout" name="formLayout_7">
+          <item row="0" column="0">
+           <widget class="QLabel" name="exclusiveFullscreenLabel">
+            <property name="text">
+             <string>Exclusive Fullscreen:</string>
+            </property>
+           </widget>
+          </item>
+          <item row="0" column="1">
+           <layout class="QHBoxLayout" name="horizontalLayout_8" stretch="1,0">
+            <item>
+             <widget class="QComboBox" name="fullscreenMode"/>
+            </item>
+            <item>
+             <widget class="QComboBox" name="exclusiveFullscreenControl"/>
+            </item>
+           </layout>
+          </item>
+          <item row="1" column="0">
+           <widget class="QLabel" name="label_11">
+            <property name="text">
+             <string>Screen Position:</string>
+            </property>
+           </widget>
+          </item>
+          <item row="1" column="1">
+           <widget class="QComboBox" name="displayAlignment"/>
+          </item>
+          <item row="2" column="0">
+           <widget class="QLabel" name="label_13">
+            <property name="text">
+             <string>Display FPS Limit:</string>
+            </property>
+           </widget>
+          </item>
+          <item row="2" column="1">
+           <widget class="QSpinBox" name="displayFPSLimit"/>
+          </item>
+          <item row="3" column="0" colspan="2">
+           <layout class="QGridLayout" name="advancedDisplayOptionsLayout">
+            <item row="0" column="0">
+             <widget class="QCheckBox" name="gpuThread">
+              <property name="text">
+               <string>Threaded Rendering</string>
+              </property>
+             </widget>
+            </item>
+            <item row="0" column="1">
+             <widget class="QCheckBox" name="threadedPresentation">
+              <property name="text">
+               <string>Threaded Presentation</string>
+              </property>
+             </widget>
+            </item>
+            <item row="1" column="1">
+             <widget class="QCheckBox" name="blitSwapChain">
+              <property name="text">
+               <string>Use Blit Swap Chain</string>
+              </property>
+             </widget>
+            </item>
+            <item row="1" column="0">
+             <widget class="QCheckBox" name="stretchDisplayVertically">
+              <property name="text">
+               <string>Stretch Vertically</string>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </item>
+         </layout>
+        </widget>
+       </item>
+       <item>
+        <widget class="QGroupBox" name="groupBox">
+         <property name="title">
+          <string>Rendering Options</string>
+         </property>
+         <layout class="QFormLayout" name="formLayout_6">
+          <item row="2" column="0" colspan="2">
+           <layout class="QGridLayout" name="gridLayout_5">
+            <item row="0" column="1">
+             <widget class="QCheckBox" name="scaledDithering">
+              <property name="text">
+               <string>Scaled Dithering</string>
+              </property>
+             </widget>
+            </item>
+            <item row="0" column="0">
+             <widget class="QCheckBox" name="debanding">
+              <property name="text">
+               <string>True Color Debanding</string>
+              </property>
+             </widget>
+            </item>
+            <item row="1" column="0">
+             <widget class="QCheckBox" name="useSoftwareRendererForReadbacks">
+              <property name="text">
+               <string>Software Renderer Readbacks</string>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </item>
+          <item row="0" column="0">
+           <widget class="QLabel" name="gpuLineDetectModeLabel">
+            <property name="text">
+             <string>Line Detection:</string>
+            </property>
+           </widget>
+          </item>
+          <item row="0" column="1">
+           <widget class="QComboBox" name="gpuLineDetectMode"/>
+          </item>
+          <item row="1" column="0">
+           <widget class="QLabel" name="gpuWireframeModeLabel">
+            <property name="text">
+             <string>Wireframe Mode:</string>
+            </property>
+           </widget>
+          </item>
+          <item row="1" column="1">
+           <widget class="QComboBox" name="gpuWireframeMode"/>
+          </item>
+         </layout>
+        </widget>
+       </item>
+       <item>
+        <spacer name="verticalSpacer">
+         <property name="orientation">
+          <enum>Qt::Vertical</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>20</width>
+           <height>40</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="pgxpTab">
+      <attribute name="title">
+       <string>PGXP</string>
+      </attribute>
+      <layout class="QVBoxLayout" name="verticalLayout_5">
+       <property name="leftMargin">
+        <number>0</number>
+       </property>
+       <property name="topMargin">
+        <number>0</number>
+       </property>
+       <property name="rightMargin">
+        <number>0</number>
+       </property>
+       <property name="bottomMargin">
+        <number>0</number>
+       </property>
+       <item>
+        <widget class="QGroupBox" name="groupBox_7">
+         <property name="title">
+          <string/>
+         </property>
+         <layout class="QFormLayout" name="formLayout_4">
+          <item row="0" column="0">
+           <widget class="QLabel" name="pgxpGeometryToleranceLabel">
+            <property name="text">
+             <string>Geometry Tolerance:</string>
+            </property>
+           </widget>
+          </item>
+          <item row="0" column="1">
+           <widget class="QDoubleSpinBox" name="pgxpGeometryTolerance">
+            <property name="prefix">
+             <string/>
+            </property>
+            <property name="suffix">
+             <string>px</string>
+            </property>
+            <property name="minimum">
+             <double>-1.000000000000000</double>
+            </property>
+            <property name="maximum">
+             <double>100.000000000000000</double>
+            </property>
+            <property name="singleStep">
+             <double>0.250000000000000</double>
+            </property>
+            <property name="value">
+             <double>-1.000000000000000</double>
+            </property>
+           </widget>
+          </item>
+          <item row="1" column="0">
+           <widget class="QLabel" name="pgxpDepthClearThresholdLabel">
+            <property name="text">
+             <string>Depth Clear Threshold:</string>
+            </property>
+           </widget>
+          </item>
+          <item row="1" column="1">
+           <widget class="QDoubleSpinBox" name="pgxpDepthClearThreshold">
+            <property name="maximum">
+             <double>4096.000000000000000</double>
+            </property>
+           </widget>
+          </item>
+          <item row="2" column="0" colspan="2">
+           <layout class="QGridLayout" name="gridLayout">
+            <item row="0" column="0">
+             <widget class="QCheckBox" name="pgxpTextureCorrection">
+              <property name="text">
+               <string>Perspective Correct Textures</string>
+              </property>
+             </widget>
+            </item>
+            <item row="0" column="1">
+             <widget class="QCheckBox" name="pgxpColorCorrection">
+              <property name="text">
+               <string>Perspective Correct Colors</string>
+              </property>
+             </widget>
+            </item>
+            <item row="1" column="0">
+             <widget class="QCheckBox" name="pgxpCulling">
+              <property name="text">
+               <string>Culling Correction</string>
+              </property>
+             </widget>
+            </item>
+            <item row="1" column="1">
+             <widget class="QCheckBox" name="pgxpPreserveProjPrecision">
+              <property name="text">
+               <string>Preserve Projection Precision</string>
+              </property>
+             </widget>
+            </item>
+            <item row="2" column="0">
+             <widget class="QCheckBox" name="pgxpCPU">
+              <property name="text">
+               <string>CPU Mode</string>
+              </property>
+             </widget>
+            </item>
+            <item row="2" column="1">
+             <widget class="QCheckBox" name="pgxpVertexCache">
+              <property name="text">
+               <string>Vertex Cache</string>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </item>
+         </layout>
+        </widget>
+       </item>
+       <item>
+        <spacer name="verticalSpacer_4">
+         <property name="orientation">
+          <enum>Qt::Vertical</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>20</width>
+           <height>215</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="osdTab">
+      <attribute name="title">
+       <string>OSD</string>
+      </attribute>
+      <layout class="QVBoxLayout" name="verticalLayout_6">
+       <property name="leftMargin">
+        <number>0</number>
+       </property>
+       <property name="topMargin">
+        <number>0</number>
+       </property>
+       <property name="rightMargin">
+        <number>0</number>
+       </property>
+       <property name="bottomMargin">
+        <number>0</number>
+       </property>
+       <item>
+        <widget class="QGroupBox" name="groupBox_8">
+         <property name="title">
+          <string/>
+         </property>
+         <layout class="QFormLayout" name="formLayout_5">
+          <item row="0" column="0">
+           <widget class="QLabel" name="label_29">
+            <property name="text">
+             <string>OSD Scale:</string>
+            </property>
+           </widget>
+          </item>
+          <item row="0" column="1">
+           <widget class="QSpinBox" name="osdScale">
+            <property name="suffix">
+             <string>%</string>
+            </property>
+            <property name="minimum">
+             <number>25</number>
+            </property>
+            <property name="maximum">
+             <number>500</number>
+            </property>
+           </widget>
+          </item>
+          <item row="1" column="0" colspan="2">
+           <layout class="QGridLayout" name="gridLayout_3">
+            <item row="4" column="0">
+             <widget class="QCheckBox" name="showInput">
+              <property name="text">
+               <string>Show Controller Input</string>
+              </property>
+             </widget>
+            </item>
+            <item row="5" column="0">
+             <widget class="QCheckBox" name="showStatusIndicators">
+              <property name="text">
+               <string>Show Status Indicators</string>
+              </property>
+             </widget>
+            </item>
+            <item row="1" column="1">
+             <widget class="QCheckBox" name="showFPS">
+              <property name="text">
+               <string>Show FPS</string>
+              </property>
+             </widget>
+            </item>
+            <item row="0" column="0">
+             <widget class="QCheckBox" name="showOSDMessages">
+              <property name="text">
+               <string>Show OSD Messages</string>
+              </property>
+             </widget>
+            </item>
+            <item row="0" column="1">
+             <widget class="QCheckBox" name="showResolution">
+              <property name="text">
+               <string>Show Resolution</string>
+              </property>
+             </widget>
+            </item>
+            <item row="4" column="1">
+             <widget class="QCheckBox" name="showSettings">
+              <property name="text">
+               <string>Show Settings</string>
+              </property>
+             </widget>
+            </item>
+            <item row="2" column="0">
+             <widget class="QCheckBox" name="showCPU">
+              <property name="text">
+               <string>Show CPU Usage</string>
+              </property>
+             </widget>
+            </item>
+            <item row="3" column="0">
+             <widget class="QCheckBox" name="showGPUStatistics">
+              <property name="text">
+               <string>Show GPU Statistics</string>
+              </property>
+             </widget>
+            </item>
+            <item row="1" column="0">
+             <widget class="QCheckBox" name="showSpeed">
+              <property name="text">
+               <string>Show Emulation Speed</string>
+              </property>
+             </widget>
+            </item>
+            <item row="3" column="1">
+             <widget class="QCheckBox" name="showFrameTimes">
+              <property name="text">
+               <string>Show Frame Times</string>
+              </property>
+             </widget>
+            </item>
+            <item row="2" column="1">
+             <widget class="QCheckBox" name="showGPU">
+              <property name="text">
+               <string>Show GPU Usage</string>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </item>
+         </layout>
+        </widget>
+       </item>
+       <item>
+        <spacer name="verticalSpacer_5">
+         <property name="orientation">
+          <enum>Qt::Vertical</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>20</width>
+           <height>164</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="captureTab">
+      <attribute name="title">
+       <string>Capture</string>
+      </attribute>
+      <layout class="QVBoxLayout" name="verticalLayout_8">
+       <property name="leftMargin">
+        <number>0</number>
+       </property>
+       <property name="rightMargin">
+        <number>0</number>
+       </property>
+       <property name="bottomMargin">
+        <number>0</number>
+       </property>
+       <item>
+        <widget class="QGroupBox" name="groupBox_10">
+         <property name="title">
+          <string>Screenshots</string>
+         </property>
+         <layout class="QFormLayout" name="formLayout_3">
+          <item row="0" column="0">
+           <widget class="QLabel" name="label_21">
+            <property name="text">
+             <string>Screenshot Size:</string>
+            </property>
+           </widget>
+          </item>
+          <item row="0" column="1">
+           <layout class="QHBoxLayout" name="horizontalLayout_5" stretch="1,0,0,0">
+            <item>
+             <widget class="QComboBox" name="screenshotSize"/>
+            </item>
+            <item>
+             <widget class="QComboBox" name="screenshotFormat"/>
+            </item>
+            <item>
+             <widget class="QLabel" name="label_44">
+              <property name="text">
+               <string>Quality:</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QSpinBox" name="screenshotQuality">
+              <property name="suffix">
+               <string>%</string>
+              </property>
+              <property name="minimum">
+               <number>1</number>
+              </property>
+              <property name="maximum">
+               <number>100</number>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </item>
+         </layout>
+        </widget>
+       </item>
+       <item>
+        <spacer name="verticalSpacer_7">
+         <property name="orientation">
+          <enum>Qt::Vertical</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>20</width>
+           <height>295</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="tabTextureReplacements">
+      <attribute name="title">
+       <string>Texture Replacements</string>
+      </attribute>
+      <layout class="QVBoxLayout" name="verticalLayout_2">
+       <property name="leftMargin">
+        <number>0</number>
+       </property>
+       <property name="rightMargin">
+        <number>0</number>
+       </property>
+       <property name="bottomMargin">
+        <number>0</number>
+       </property>
+       <item>
+        <widget class="QGroupBox" name="groupBox_5">
+         <property name="title">
+          <string>General Settings</string>
+         </property>
+         <layout class="QFormLayout" name="formLayout_8">
+          <item row="0" column="0" colspan="2">
+           <layout class="QGridLayout" name="gridLayout_6">
+            <item row="0" column="0">
+             <widget class="QCheckBox" name="vramWriteReplacement">
+              <property name="text">
+               <string>Enable VRAM Write Replacement</string>
+              </property>
+             </widget>
+            </item>
+            <item row="0" column="1">
+             <widget class="QCheckBox" name="preloadTextureReplacements">
+              <property name="text">
+               <string>Preload Texture Replacements</string>
+              </property>
+             </widget>
+            </item>
+            <item row="1" column="0">
+             <widget class="QCheckBox" name="useOldMDECRoutines">
+              <property name="text">
+               <string>Use Old MDEC Routines</string>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </item>
+         </layout>
+        </widget>
+       </item>
+       <item>
+        <widget class="QGroupBox" name="groupBox_4">
+         <property name="title">
+          <string>VRAM Write Dumping</string>
+         </property>
+         <layout class="QFormLayout" name="formLayout_9">
+          <item row="0" column="0" colspan="2">
+           <layout class="QGridLayout" name="gridLayout_7">
+            <item row="0" column="0">
+             <widget class="QCheckBox" name="vramWriteDumping">
+              <property name="text">
+               <string>Enable VRAM Write Dumping</string>
+              </property>
+             </widget>
+            </item>
+            <item row="0" column="1">
+             <widget class="QCheckBox" name="setVRAMWriteAlphaChannel">
+              <property name="text">
+               <string>Set Alpha Channel</string>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </item>
+          <item row="1" column="0">
+           <widget class="QLabel" name="vramWriteDumpThresholdLabel">
+            <property name="text">
+             <string>Dump Size Threshold:</string>
+            </property>
+           </widget>
+          </item>
+          <item row="1" column="1">
+           <layout class="QHBoxLayout" name="horizontalLayout_6" stretch="1,0,1">
+            <item>
+             <widget class="QSpinBox" name="minDumpedVRAMWriteWidth">
+              <property name="suffix">
+               <string>px</string>
+              </property>
+              <property name="minimum">
+               <number>1</number>
+              </property>
+              <property name="maximum">
+               <number>1024</number>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QLabel" name="vramWriteDumpThresholdSeparator">
+              <property name="text">
+               <string>x</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QSpinBox" name="minDumpedVRAMWriteHeight">
+              <property name="suffix">
+               <string>px</string>
+              </property>
+              <property name="minimum">
+               <number>1</number>
+              </property>
+              <property name="maximum">
+               <number>512</number>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </item>
+         </layout>
+        </widget>
+       </item>
+       <item>
+        <spacer name="verticalSpacer_2">
+         <property name="orientation">
+          <enum>Qt::Vertical</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>20</width>
+           <height>40</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="debugTab">
+      <attribute name="title">
+       <string>Debugging</string>
+      </attribute>
+      <layout class="QVBoxLayout" name="verticalLayout_7">
+       <property name="leftMargin">
+        <number>0</number>
+       </property>
+       <property name="rightMargin">
+        <number>0</number>
+       </property>
+       <property name="bottomMargin">
+        <number>0</number>
+       </property>
+       <item>
+        <widget class="QGroupBox" name="groupBox_9">
+         <property name="title">
+          <string>Device Options</string>
+         </property>
+         <layout class="QFormLayout" name="formLayout_10">
+          <item row="0" column="0" colspan="2">
+           <layout class="QGridLayout" name="gridLayout_8">
+            <item row="1" column="1">
+             <widget class="QCheckBox" name="disableFramebufferFetch">
+              <property name="text">
+               <string>Disable Framebuffer Fetch</string>
+              </property>
+             </widget>
+            </item>
+            <item row="2" column="1">
+             <widget class="QCheckBox" name="disableTextureCopyToSelf">
+              <property name="text">
+               <string>Disable Texture Copy To Self</string>
+              </property>
+             </widget>
+            </item>
+            <item row="1" column="0">
+             <widget class="QCheckBox" name="disableDualSource">
+              <property name="text">
+               <string>Disable Dual-Source Blending</string>
+              </property>
+             </widget>
+            </item>
+            <item row="0" column="0">
+             <widget class="QCheckBox" name="useDebugDevice">
+              <property name="text">
+               <string>Use Debug Device</string>
+              </property>
+             </widget>
+            </item>
+            <item row="0" column="1">
+             <widget class="QCheckBox" name="disableShaderCache">
+              <property name="text">
+               <string>Disable Shader Cache</string>
+              </property>
+             </widget>
+            </item>
+            <item row="2" column="0">
+             <widget class="QCheckBox" name="disableTextureBuffers">
+              <property name="text">
+               <string>Disable Texture Buffers</string>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </item>
+         </layout>
+        </widget>
+       </item>
+       <item>
+        <spacer name="verticalSpacer_6">
+         <property name="orientation">
+          <enum>Qt::Vertical</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>20</width>
+           <height>244</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+      </layout>
+     </widget>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/duckstation-qt/generalsettingswidget.cpp b/src/duckstation-qt/interfacesettingswidget.cpp
similarity index 92%
rename from src/duckstation-qt/generalsettingswidget.cpp
rename to src/duckstation-qt/interfacesettingswidget.cpp
index 83590dc6c..ef01a77ea 100644
--- a/src/duckstation-qt/generalsettingswidget.cpp
+++ b/src/duckstation-qt/interfacesettingswidget.cpp
@@ -1,16 +1,15 @@
-// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
+// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
 
-#include "generalsettingswidget.h"
+#include "interfacesettingswidget.h"
 #include "autoupdaterdialog.h"
-#include "generalsettingswidget.h"
 #include "mainwindow.h"
 #include "qtutils.h"
 #include "scmversion/scmversion.h"
 #include "settingswindow.h"
 #include "settingwidgetbinder.h"
 
-const char* GeneralSettingsWidget::THEME_NAMES[] = {
+const char* InterfaceSettingsWidget::THEME_NAMES[] = {
   QT_TRANSLATE_NOOP("MainWindow", "Native"),
   QT_TRANSLATE_NOOP("MainWindow", "Fusion"),
   QT_TRANSLATE_NOOP("MainWindow", "Dark Fusion (Gray)"),
@@ -20,13 +19,13 @@ const char* GeneralSettingsWidget::THEME_NAMES[] = {
   nullptr,
 };
 
-const char* GeneralSettingsWidget::THEME_VALUES[] = {
+const char* InterfaceSettingsWidget::THEME_VALUES[] = {
   "", "fusion", "darkfusion", "darkfusionblue", "greymatter", "qdarkstyle", nullptr,
 };
 
-const char* GeneralSettingsWidget::DEFAULT_THEME_NAME = "darkfusion";
+const char* InterfaceSettingsWidget::DEFAULT_THEME_NAME = "darkfusion";
 
-GeneralSettingsWidget::GeneralSettingsWidget(SettingsWindow* dialog, QWidget* parent)
+InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsWindow* dialog, QWidget* parent)
   : QWidget(parent), m_dialog(dialog)
 {
   SettingsInterface* sif = dialog->getSettingsInterface();
@@ -57,7 +56,7 @@ GeneralSettingsWidget::GeneralSettingsWidget(SettingsWindow* dialog, QWidget* pa
                                                Settings::DEFAULT_SAVE_STATE_COMPRESSION);
   SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableDiscordPresence, "Main", "EnableDiscordPresence", false);
   connect(m_ui.renderToSeparateWindow, &QCheckBox::stateChanged, this,
-          &GeneralSettingsWidget::onRenderToSeparateWindowChanged);
+          &InterfaceSettingsWidget::onRenderToSeparateWindowChanged);
 
   onRenderToSeparateWindowChanged();
 
@@ -124,9 +123,9 @@ GeneralSettingsWidget::GeneralSettingsWidget(SettingsWindow* dialog, QWidget* pa
   }
 }
 
-GeneralSettingsWidget::~GeneralSettingsWidget() = default;
+InterfaceSettingsWidget::~InterfaceSettingsWidget() = default;
 
-void GeneralSettingsWidget::onRenderToSeparateWindowChanged()
+void InterfaceSettingsWidget::onRenderToSeparateWindowChanged()
 {
   m_ui.hideMainWindow->setEnabled(m_ui.renderToSeparateWindow->isChecked());
 }
diff --git a/src/duckstation-qt/generalsettingswidget.h b/src/duckstation-qt/interfacesettingswidget.h
similarity index 56%
rename from src/duckstation-qt/generalsettingswidget.h
rename to src/duckstation-qt/interfacesettingswidget.h
index a2c745030..7474b5cf4 100644
--- a/src/duckstation-qt/generalsettingswidget.h
+++ b/src/duckstation-qt/interfacesettingswidget.h
@@ -1,27 +1,27 @@
-// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
+// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
 
 #pragma once
 
 #include <QtWidgets/QWidget>
 
-#include "ui_generalsettingswidget.h"
+#include "ui_interfacesettingswidget.h"
 
 class SettingsWindow;
 
-class GeneralSettingsWidget : public QWidget
+class InterfaceSettingsWidget : public QWidget
 {
   Q_OBJECT
 
 public:
-  explicit GeneralSettingsWidget(SettingsWindow* dialog, QWidget* parent);
-  ~GeneralSettingsWidget();
+  explicit InterfaceSettingsWidget(SettingsWindow* dialog, QWidget* parent);
+  ~InterfaceSettingsWidget();
 
 private Q_SLOTS:
   void onRenderToSeparateWindowChanged();
 
 private:
-  Ui::GeneralSettingsWidget m_ui;
+  Ui::InterfaceSettingsWidget m_ui;
 
   SettingsWindow* m_dialog;
 
diff --git a/src/duckstation-qt/generalsettingswidget.ui b/src/duckstation-qt/interfacesettingswidget.ui
similarity index 98%
rename from src/duckstation-qt/generalsettingswidget.ui
rename to src/duckstation-qt/interfacesettingswidget.ui
index 03ac719a9..c6a18d85e 100644
--- a/src/duckstation-qt/generalsettingswidget.ui
+++ b/src/duckstation-qt/interfacesettingswidget.ui
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <ui version="4.0">
- <class>GeneralSettingsWidget</class>
- <widget class="QWidget" name="GeneralSettingsWidget">
+ <class>InterfaceSettingsWidget</class>
+ <widget class="QWidget" name="InterfaceSettingsWidget">
   <property name="geometry">
    <rect>
     <x>0</x>
diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp
index aaa035332..021e57012 100644
--- a/src/duckstation-qt/mainwindow.cpp
+++ b/src/duckstation-qt/mainwindow.cpp
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
+// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
 
 #include "mainwindow.h"
@@ -11,7 +11,7 @@
 #include "displaywidget.h"
 #include "gamelistsettingswidget.h"
 #include "gamelistwidget.h"
-#include "generalsettingswidget.h"
+#include "interfacesettingswidget.h"
 #include "logwindow.h"
 #include "memorycardeditorwindow.h"
 #include "qthost.h"
@@ -1985,7 +1985,7 @@ void MainWindow::connectSignals()
   connect(m_ui.actionFullscreen, &QAction::triggered, g_emu_thread, &EmuThread::toggleFullscreen);
   connect(m_ui.actionSettings, &QAction::triggered, [this]() { doSettings(); });
   connect(m_ui.actionSettings2, &QAction::triggered, this, &MainWindow::onSettingsTriggeredFromToolbar);
-  connect(m_ui.actionGeneralSettings, &QAction::triggered, [this]() { doSettings("General"); });
+  connect(m_ui.actionInterfaceSettings, &QAction::triggered, [this]() { doSettings("Interface"); });
   connect(m_ui.actionBIOSSettings, &QAction::triggered, [this]() { doSettings("BIOS"); });
   connect(m_ui.actionConsoleSettings, &QAction::triggered, [this]() { doSettings("Console"); });
   connect(m_ui.actionEmulationSettings, &QAction::triggered, [this]() { doSettings("Emulation"); });
@@ -1995,8 +1995,7 @@ void MainWindow::connectSignals()
   connect(m_ui.actionControllerSettings, &QAction::triggered,
           [this]() { doControllerSettings(ControllerSettingsWindow::Category::GlobalSettings); });
   connect(m_ui.actionMemoryCardSettings, &QAction::triggered, [this]() { doSettings("Memory Cards"); });
-  connect(m_ui.actionDisplaySettings, &QAction::triggered, [this]() { doSettings("Display"); });
-  connect(m_ui.actionEnhancementSettings, &QAction::triggered, [this]() { doSettings("Enhancements"); });
+  connect(m_ui.actionGraphicsSettings, &QAction::triggered, [this]() { doSettings("Graphics"); });
   connect(m_ui.actionPostProcessingSettings, &QAction::triggered, [this]() { doSettings("Post-Processing"); });
   connect(m_ui.actionAudioSettings, &QAction::triggered, [this]() { doSettings("Audio"); });
   connect(m_ui.actionAchievementSettings, &QAction::triggered, [this]() { doSettings("Achievements"); });
@@ -2120,11 +2119,11 @@ void MainWindow::connectSignals()
   SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionDebugShowMDECState, "Debug", "ShowMDECState", false);
   SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionDebugShowDMAState, "Debug", "ShowDMAState", false);
 
-  for (u32 i = 0; GeneralSettingsWidget::THEME_NAMES[i]; i++)
+  for (u32 i = 0; InterfaceSettingsWidget::THEME_NAMES[i]; i++)
   {
-    const QString key = QString::fromUtf8(GeneralSettingsWidget::THEME_VALUES[i]);
+    const QString key = QString::fromUtf8(InterfaceSettingsWidget::THEME_VALUES[i]);
     QAction* action =
-      m_ui.menuSettingsTheme->addAction(qApp->translate("MainWindow", GeneralSettingsWidget::THEME_NAMES[i]));
+      m_ui.menuSettingsTheme->addAction(qApp->translate("MainWindow", InterfaceSettingsWidget::THEME_NAMES[i]));
     action->setCheckable(true);
     action->setData(key);
     connect(action, &QAction::toggled, [this, key](bool) { setTheme(key); });
@@ -2154,7 +2153,7 @@ void MainWindow::reloadThemeSpecificImages()
 
 void MainWindow::setStyleFromSettings()
 {
-  const std::string theme(Host::GetBaseStringSettingValue("UI", "Theme", GeneralSettingsWidget::DEFAULT_THEME_NAME));
+  const std::string theme(Host::GetBaseStringSettingValue("UI", "Theme", InterfaceSettingsWidget::DEFAULT_THEME_NAME));
 
   // setPalette() shouldn't be necessary, as the documentation claims that setStyle() resets the palette, but it
   // is here, to work around a bug in 6.4.x and 6.5.x where the palette doesn't restore after changing themes.
@@ -2471,7 +2470,7 @@ void MainWindow::updateDebugMenuCropMode()
 void MainWindow::updateMenuSelectedTheme()
 {
   QString theme =
-    QString::fromStdString(Host::GetBaseStringSettingValue("UI", "Theme", GeneralSettingsWidget::DEFAULT_THEME_NAME));
+    QString::fromStdString(Host::GetBaseStringSettingValue("UI", "Theme", InterfaceSettingsWidget::DEFAULT_THEME_NAME));
 
   for (QObject* obj : m_ui.menuSettingsTheme->children())
   {
@@ -2613,7 +2612,7 @@ void MainWindow::startupUpdateCheck()
 
 void MainWindow::updateDebugMenuVisibility()
 {
-  const bool visible = Host::GetBaseBoolSettingValue("Main", "ShowDebugMenu", false);
+  const bool visible = QtHost::ShouldShowDebugOptions();
   m_ui.menuDebug->menuAction()->setVisible(visible);
 }
 
diff --git a/src/duckstation-qt/mainwindow.ui b/src/duckstation-qt/mainwindow.ui
index afa8d67ac..d1c521be9 100644
--- a/src/duckstation-qt/mainwindow.ui
+++ b/src/duckstation-qt/mainwindow.ui
@@ -123,14 +123,13 @@
     </widget>
     <addaction name="actionViewGameProperties"/>
     <addaction name="separator"/>
-    <addaction name="actionGeneralSettings"/>
+    <addaction name="actionInterfaceSettings"/>
     <addaction name="actionGameListSettings"/>
     <addaction name="actionBIOSSettings"/>
     <addaction name="actionConsoleSettings"/>
     <addaction name="actionEmulationSettings"/>
     <addaction name="actionMemoryCardSettings"/>
-    <addaction name="actionDisplaySettings"/>
-    <addaction name="actionEnhancementSettings"/>
+    <addaction name="actionGraphicsSettings"/>
     <addaction name="actionPostProcessingSettings"/>
     <addaction name="actionAudioSettings"/>
     <addaction name="actionAchievementSettings"/>
@@ -433,22 +432,13 @@
     <string>&amp;Hotkeys</string>
    </property>
   </action>
-  <action name="actionDisplaySettings">
+  <action name="actionGraphicsSettings">
    <property name="icon">
     <iconset theme="image-fill">
      <normaloff>.</normaloff>.</iconset>
    </property>
    <property name="text">
-    <string>&amp;Display</string>
-   </property>
-  </action>
-  <action name="actionEnhancementSettings">
-   <property name="icon">
-    <iconset theme="sparkle-fill">
-     <normaloff>.</normaloff>.</iconset>
-   </property>
-   <property name="text">
-    <string>&amp;Enhancements</string>
+    <string>&amp;Graphics</string>
    </property>
   </action>
   <action name="actionPostProcessingSettings">
@@ -587,13 +577,13 @@
     <string>Game List</string>
    </property>
   </action>
-  <action name="actionGeneralSettings">
+  <action name="actionInterfaceSettings">
    <property name="icon">
     <iconset theme="settings-3-line">
      <normaloff>.</normaloff>.</iconset>
    </property>
    <property name="text">
-    <string>General</string>
+    <string>&amp;Interface</string>
    </property>
   </action>
   <action name="actionAdvancedSettings">
diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp
index 94f0b46b5..6a9cbadfb 100644
--- a/src/duckstation-qt/qthost.cpp
+++ b/src/duckstation-qt/qthost.cpp
@@ -1373,7 +1373,7 @@ void EmuThread::saveScreenshot()
     return;
   }
 
-  System::SaveScreenshot(nullptr, true, true);
+  System::SaveScreenshot();
 }
 
 void Host::OnAchievementsLoginRequested(Achievements::LoginRequestReason reason)
@@ -1830,6 +1830,11 @@ void QtHost::QueueSettingsSave()
   s_settings_save_timer->start(SETTINGS_SAVE_DELAY);
 }
 
+bool QtHost::ShouldShowDebugOptions()
+{
+  return Host::GetBaseBoolSettingValue("Main", "ShowDebugMenu", false);
+}
+
 void Host::RequestSystemShutdown(bool allow_confirm, bool save_state)
 {
   if (!System::IsValid())
diff --git a/src/duckstation-qt/qthost.h b/src/duckstation-qt/qthost.h
index 97d768158..907d390a9 100644
--- a/src/duckstation-qt/qthost.h
+++ b/src/duckstation-qt/qthost.h
@@ -286,6 +286,9 @@ bool DownloadFileFromZip(QWidget* parent, const QString& title, std::string url,
 /// Thread-safe settings access.
 void QueueSettingsSave();
 
+/// Returns true if the debug menu and functionality should be shown.
+bool ShouldShowDebugOptions();
+
 /// VM state, safe to access on UI thread.
 bool IsSystemValid();
 bool IsSystemPaused();
diff --git a/src/duckstation-qt/qtutils.cpp b/src/duckstation-qt/qtutils.cpp
index e010c92e4..23c443bbb 100644
--- a/src/duckstation-qt/qtutils.cpp
+++ b/src/duckstation-qt/qtutils.cpp
@@ -693,59 +693,6 @@ void OpenURL(QWidget* parent, const char* url)
   return OpenURL(parent, QUrl::fromEncoded(QByteArray(url, static_cast<int>(std::strlen(url)))));
 }
 
-void FillComboBoxWithResolutionScales(QComboBox* cb)
-{
-  cb->addItem(qApp->translate("GPUSettingsWidget", "Automatic based on window size"));
-  cb->addItem(qApp->translate("GPUSettingsWidget", "1x"));
-  cb->addItem(qApp->translate("GPUSettingsWidget", "2x"));
-  cb->addItem(qApp->translate("GPUSettingsWidget", "3x (for 720p)"));
-  cb->addItem(qApp->translate("GPUSettingsWidget", "4x"));
-  cb->addItem(qApp->translate("GPUSettingsWidget", "5x (for 1080p)"));
-  cb->addItem(qApp->translate("GPUSettingsWidget", "6x (for 1440p)"));
-  cb->addItem(qApp->translate("GPUSettingsWidget", "7x"));
-  cb->addItem(qApp->translate("GPUSettingsWidget", "8x"));
-  cb->addItem(qApp->translate("GPUSettingsWidget", "9x (for 4K)"));
-  cb->addItem(qApp->translate("GPUSettingsWidget", "10x"));
-  cb->addItem(qApp->translate("GPUSettingsWidget", "11x"));
-  cb->addItem(qApp->translate("GPUSettingsWidget", "12x"));
-  cb->addItem(qApp->translate("GPUSettingsWidget", "13x"));
-  cb->addItem(qApp->translate("GPUSettingsWidget", "14x"));
-  cb->addItem(qApp->translate("GPUSettingsWidget", "15x"));
-  cb->addItem(qApp->translate("GPUSettingsWidget", "16x"));
-}
-
-QVariant GetMSAAModeValue(uint multisamples, bool ssaa)
-{
-  const uint userdata = (multisamples & 0x7FFFFFFFu) | (static_cast<uint>(ssaa) << 31);
-  return QVariant(userdata);
-}
-
-void DecodeMSAAModeValue(const QVariant& userdata, uint* multisamples, bool* ssaa)
-{
-  bool ok;
-  const uint value = userdata.toUInt(&ok);
-  if (!ok || value == 0)
-  {
-    *multisamples = 1;
-    *ssaa = false;
-    return;
-  }
-
-  *multisamples = value & 0x7FFFFFFFu;
-  *ssaa = (value & (1u << 31)) != 0u;
-}
-
-void FillComboBoxWithMSAAModes(QComboBox* cb)
-{
-  cb->addItem(qApp->translate("GPUSettingsWidget", "Disabled"), GetMSAAModeValue(1, false));
-
-  for (uint i = 2; i <= 32; i *= 2)
-    cb->addItem(qApp->translate("GPUSettingsWidget", "%1x MSAA").arg(i), GetMSAAModeValue(i, false));
-
-  for (uint i = 2; i <= 32; i *= 2)
-    cb->addItem(qApp->translate("GPUSettingsWidget", "%1x SSAA").arg(i), GetMSAAModeValue(i, true));
-}
-
 std::optional<unsigned> PromptForAddress(QWidget* parent, const QString& title, const QString& label, bool code)
 {
   const QString address_str(
diff --git a/src/duckstation-qt/qtutils.h b/src/duckstation-qt/qtutils.h
index 8ed8c66c1..44a8e8447 100644
--- a/src/duckstation-qt/qtutils.h
+++ b/src/duckstation-qt/qtutils.h
@@ -67,14 +67,6 @@ void OpenURL(QWidget* parent, const QUrl& qurl);
 /// Opens a URL string with the default handler.
 void OpenURL(QWidget* parent, const char* url);
 
-/// Fills a combo box with resolution scale options.
-void FillComboBoxWithResolutionScales(QComboBox* cb);
-
-/// Fills a combo box with multisampling options.
-QVariant GetMSAAModeValue(uint multisamples, bool ssaa);
-void DecodeMSAAModeValue(const QVariant& userdata, uint* multisamples, bool* ssaa);
-void FillComboBoxWithMSAAModes(QComboBox* cb);
-
 /// Prompts for an address in hex.
 std::optional<unsigned> PromptForAddress(QWidget* parent, const QString& title, const QString& label, bool code);
 
diff --git a/src/duckstation-qt/settingswindow.cpp b/src/duckstation-qt/settingswindow.cpp
index 020b1efbe..51721cdfb 100644
--- a/src/duckstation-qt/settingswindow.cpp
+++ b/src/duckstation-qt/settingswindow.cpp
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
+// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
 
 #include "settingswindow.h"
@@ -8,13 +8,12 @@
 #include "consolesettingswidget.h"
 
 #include "achievementsettingswidget.h"
-#include "displaysettingswidget.h"
 #include "emulationsettingswidget.h"
-#include "enhancementsettingswidget.h"
 #include "foldersettingswidget.h"
 #include "gamelistsettingswidget.h"
 #include "gamesummarywidget.h"
-#include "generalsettingswidget.h"
+#include "graphicssettingswidget.h"
+#include "interfacesettingswidget.h"
 #include "mainwindow.h"
 #include "memorycardsettingswidget.h"
 #include "postprocessingsettingswidget.h"
@@ -78,9 +77,9 @@ void SettingsWindow::closeEvent(QCloseEvent* event)
 void SettingsWindow::addPages()
 {
   addWidget(
-    m_general_settings = new GeneralSettingsWidget(this, m_ui.settingsContainer), tr("General"),
+    m_general_settings = new InterfaceSettingsWidget(this, m_ui.settingsContainer), tr("Interface"),
     QStringLiteral("settings-3-line"),
-    tr("<strong>General Settings</strong><hr>These options control how the emulator looks and "
+    tr("<strong>Interface Settings</strong><hr>These options control how the emulator looks and "
        "behaves.<br><br>Mouse over an option for additional information, and Shift+Wheel to scroll this panel."));
 
   if (!isPerGameSettings())
@@ -112,17 +111,11 @@ void SettingsWindow::addPages()
     QStringLiteral("memcard-line"),
     tr("<strong>Memory Card Settings</strong><hr>This page lets you control what mode the memory card emulation will "
        "function in, and where the images for these cards will be stored on disk."));
-  addWidget(
-    m_display_settings = new DisplaySettingsWidget(this, m_ui.settingsContainer), tr("Display"),
-    QStringLiteral("image-fill"),
-    tr("<strong>Display Settings</strong><hr>These options control the how the frames generated by the console are "
-       "displayed on the screen."));
-  addWidget(
-    m_enhancement_settings = new EnhancementSettingsWidget(this, m_ui.settingsContainer), tr("Enhancements"),
-    QStringLiteral("sparkle-fill"),
-    tr("<strong>Enhancement Settings</strong><hr>These options control enhancements which can improve visuals compared "
-       "to the original console. Mouse over each option for additional information, and Shift+Wheel to scroll this "
-       "panel."));
+  addWidget(m_graphics_settings = new GraphicsSettingsWidget(this, m_ui.settingsContainer), tr("Graphics"),
+            QStringLiteral("image-fill"),
+            tr("<strong>Graphics Settings</strong><hr>These options control how the graphics of the emulated console "
+               "are rendered. Not all options are available for the software renderer. Mouse over each option for "
+               "additional information, and Shift+Wheel to scroll this panel."));
   addWidget(
     m_post_processing_settings = new PostProcessingSettingsWidget(this, m_ui.settingsContainer), tr("Post-Processing"),
     QStringLiteral("sun-fill"),
@@ -168,6 +161,9 @@ void SettingsWindow::addPages()
             tr("<strong>Advanced Settings</strong><hr>These options control logging and internal behavior of the "
                "emulator. Mouse over an option for additional information, and Shift+Wheel to scroll this panel."));
 
+  connect(m_advanced_settings, &AdvancedSettingsWidget::onShowDebugOptionsChanged, m_graphics_settings,
+          &GraphicsSettingsWidget::onShowDebugSettingsChanged);
+
   if (isPerGameSettings())
   {
     m_ui.buttonBox->button(QDialogButtonBox::RestoreDefaults)->setVisible(false);
@@ -500,6 +496,14 @@ void SettingsWindow::setStringSettingValue(const char* section, const char* key,
   }
 }
 
+bool SettingsWindow::containsSettingValue(const char* section, const char* key) const
+{
+  if (m_sif)
+    return m_sif->ContainsValue(section, key);
+  else
+    return Host::ContainsBaseSettingValue(section, key);
+}
+
 void SettingsWindow::removeSettingValue(const char* section, const char* key)
 {
   if (m_sif)
diff --git a/src/duckstation-qt/settingswindow.h b/src/duckstation-qt/settingswindow.h
index 8cb0fa6db..bfb1f718e 100644
--- a/src/duckstation-qt/settingswindow.h
+++ b/src/duckstation-qt/settingswindow.h
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
+// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
 
 #pragma once
@@ -21,14 +21,13 @@ namespace GameDatabase {
 struct Entry;
 }
 
-class GeneralSettingsWidget;
+class InterfaceSettingsWidget;
 class BIOSSettingsWidget;
 class GameListSettingsWidget;
 class ConsoleSettingsWidget;
 class EmulationSettingsWidget;
 class MemoryCardSettingsWidget;
-class DisplaySettingsWidget;
-class EnhancementSettingsWidget;
+class GraphicsSettingsWidget;
 class PostProcessingSettingsWidget;
 class AudioSettingsWidget;
 class AchievementSettingsWidget;
@@ -51,14 +50,13 @@ public:
   ALWAYS_INLINE bool isPerGameSettings() const { return static_cast<bool>(m_sif); }
   ALWAYS_INLINE SettingsInterface* getSettingsInterface() const { return m_sif.get(); }
 
-  ALWAYS_INLINE GeneralSettingsWidget* getGeneralSettingsWidget() const { return m_general_settings; }
+  ALWAYS_INLINE InterfaceSettingsWidget* getGeneralSettingsWidget() const { return m_general_settings; }
   ALWAYS_INLINE BIOSSettingsWidget* getBIOSSettingsWidget() const { return m_bios_settings; }
   ALWAYS_INLINE ConsoleSettingsWidget* getConsoleSettingsWidget() const { return m_console_settings; }
   ALWAYS_INLINE EmulationSettingsWidget* getEmulationSettingsWidget() const { return m_emulation_settings; }
   ALWAYS_INLINE GameListSettingsWidget* getGameListSettingsWidget() const { return m_game_list_settings; }
   ALWAYS_INLINE MemoryCardSettingsWidget* getMemoryCardSettingsWidget() const { return m_memory_card_settings; }
-  ALWAYS_INLINE DisplaySettingsWidget* getDisplaySettingsWidget() const { return m_display_settings; }
-  ALWAYS_INLINE EnhancementSettingsWidget* getEnhancementSettingsWidget() const { return m_enhancement_settings; }
+  ALWAYS_INLINE GraphicsSettingsWidget* getGraphicsSettingsWidget() const { return m_graphics_settings; }
   ALWAYS_INLINE AudioSettingsWidget* getAudioSettingsWidget() const { return m_audio_settings; }
   ALWAYS_INLINE AchievementSettingsWidget* getAchievementSettingsWidget() const { return m_achievement_settings; }
   ALWAYS_INLINE AdvancedSettingsWidget* getAdvancedSettingsWidget() const { return m_advanced_settings; }
@@ -85,6 +83,7 @@ public:
   void setIntSettingValue(const char* section, const char* key, std::optional<int> value);
   void setFloatSettingValue(const char* section, const char* key, std::optional<float> value);
   void setStringSettingValue(const char* section, const char* key, std::optional<const char*> value);
+  bool containsSettingValue(const char* section, const char* key) const;
   void removeSettingValue(const char* section, const char* key);
 
 Q_SIGNALS:
@@ -104,7 +103,7 @@ protected:
 private:
   enum : u32
   {
-    MAX_SETTINGS_WIDGETS = 13
+    MAX_SETTINGS_WIDGETS = 12
   };
 
   void addPages();
@@ -115,14 +114,13 @@ private:
 
   std::unique_ptr<SettingsInterface> m_sif;
 
-  GeneralSettingsWidget* m_general_settings = nullptr;
+  InterfaceSettingsWidget* m_general_settings = nullptr;
   BIOSSettingsWidget* m_bios_settings = nullptr;
   ConsoleSettingsWidget* m_console_settings = nullptr;
   EmulationSettingsWidget* m_emulation_settings = nullptr;
   GameListSettingsWidget* m_game_list_settings = nullptr;
   MemoryCardSettingsWidget* m_memory_card_settings = nullptr;
-  DisplaySettingsWidget* m_display_settings = nullptr;
-  EnhancementSettingsWidget* m_enhancement_settings = nullptr;
+  GraphicsSettingsWidget* m_graphics_settings = nullptr;
   PostProcessingSettingsWidget* m_post_processing_settings = nullptr;
   AudioSettingsWidget* m_audio_settings = nullptr;
   AchievementSettingsWidget* m_achievement_settings = nullptr;
diff --git a/src/duckstation-qt/setupwizarddialog.cpp b/src/duckstation-qt/setupwizarddialog.cpp
index adb7c95f5..cc770e845 100644
--- a/src/duckstation-qt/setupwizarddialog.cpp
+++ b/src/duckstation-qt/setupwizarddialog.cpp
@@ -1,9 +1,9 @@
-// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>.
+// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>.
 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
 
 #include "setupwizarddialog.h"
 #include "controllersettingwidgetbinder.h"
-#include "generalsettingswidget.h"
+#include "interfacesettingswidget.h"
 #include "mainwindow.h"
 #include "qthost.h"
 #include "qtutils.h"
@@ -181,9 +181,9 @@ void SetupWizardDialog::setupUi()
 
 void SetupWizardDialog::setupLanguagePage()
 {
-  SettingWidgetBinder::BindWidgetToEnumSetting(nullptr, m_ui.theme, "UI", "Theme", GeneralSettingsWidget::THEME_NAMES,
-                                               GeneralSettingsWidget::THEME_VALUES,
-                                               GeneralSettingsWidget::DEFAULT_THEME_NAME, "InterfaceSettingsWidget");
+  SettingWidgetBinder::BindWidgetToEnumSetting(nullptr, m_ui.theme, "UI", "Theme", InterfaceSettingsWidget::THEME_NAMES,
+                                               InterfaceSettingsWidget::THEME_VALUES,
+                                               InterfaceSettingsWidget::DEFAULT_THEME_NAME, "InterfaceSettingsWidget");
   connect(m_ui.theme, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &SetupWizardDialog::themeChanged);
 
   for (const auto& [language, code] : Host::GetAvailableLanguageList())