From 4f0007dd5522627636880109bb0d3c74486fb0b7 Mon Sep 17 00:00:00 2001
From: Connor McLaughlin <stenzek@gmail.com>
Date: Sat, 10 Oct 2020 00:07:07 +1000
Subject: [PATCH] PGXP: Make preserving pre-divide fractional coordinates an
 option

Fixes holes in geometry in Crash Team Racing with PGXP on.
---
 src/core/gte.cpp                              | 43 +++++++++++++++----
 src/core/host_interface.cpp                   |  1 +
 src/core/settings.cpp                         |  2 +
 src/core/settings.h                           |  1 +
 src/duckstation-qt/advancedsettingswidget.cpp |  2 +
 src/duckstation-sdl/sdl_host_interface.cpp    |  3 ++
 6 files changed, 43 insertions(+), 9 deletions(-)

diff --git a/src/core/gte.cpp b/src/core/gte.cpp
index 54694587a..006d0aa2c 100644
--- a/src/core/gte.cpp
+++ b/src/core/gte.cpp
@@ -624,16 +624,41 @@ static void RTPS(const s16 V[3], u8 shift, bool lm, bool last)
 
   if (g_settings.gpu_pgxp_enable)
   {
-    // this can potentially use increased precision on Z
-    const float precise_z = std::max<float>((float)REGS.H / 2.f, (float)z / 4096.0f);
-    const float precise_h_div_sz = (float)REGS.H / precise_z;
-    const float fofx = ((float)REGS.OFX / (float)(1 << 16));
-    const float fofy = ((float)REGS.OFY / (float)(1 << 16));
-    float precise_x = fofx + ((float)REGS.IR1 * precise_h_div_sz) * ((g_settings.gpu_widescreen_hack) ? 0.75f : 1.00f);
-    float precise_y = fofy + ((float)REGS.IR2 * precise_h_div_sz);
+    float precise_sz3, precise_ir1, precise_ir2;
 
-    precise_x = std::clamp<float>(precise_x, -0x400, 0x3ff);
-    precise_y = std::clamp<float>(precise_y, -0x400, 0x3ff);
+    if (g_settings.gpu_pgxp_preserve_proj_fp)
+    {
+      precise_sz3 = float(z) / 4096.0f;
+      precise_ir1 = float(x) / (static_cast<float>(1 << shift));
+      precise_ir2 = float(y) / (static_cast<float>(1 << shift));
+      if (lm)
+      {
+        precise_ir1 = std::clamp(precise_ir1, float(IR123_MIN_VALUE), float(IR123_MAX_VALUE));
+        precise_ir2 = std::clamp(precise_ir2, float(IR123_MIN_VALUE), float(IR123_MAX_VALUE));
+      }
+      else
+      {
+        precise_ir1 = std::min(precise_ir1, float(IR123_MAX_VALUE));
+        precise_ir2 = std::min(precise_ir2, float(IR123_MAX_VALUE));
+      }
+    }
+    else
+    {
+      precise_sz3 = float(REGS.SZ3);
+      precise_ir1 = float(REGS.IR1);
+      precise_ir2 = float(REGS.IR2);
+    }
+
+    // this can potentially use increased precision on Z
+    const float precise_z = std::max<float>(float(REGS.H) / 2.0f, precise_sz3);
+    const float precise_h_div_sz = float(REGS.H) / precise_z;
+    const float fofx = float(REGS.OFX) / float(1 << 16);
+    const float fofy = float(REGS.OFY) / float(1 << 16);
+    float precise_x = fofx + (precise_ir1 * precise_h_div_sz) * ((g_settings.gpu_widescreen_hack) ? 0.75f : 1.00f);
+    float precise_y = fofy + (precise_ir2 * precise_h_div_sz);
+
+    precise_x = std::clamp<float>(precise_x, -1024.0f, 1023.0f);
+    precise_y = std::clamp<float>(precise_y, -1024.0f, 1023.0f);
     PGXP::GTE_PushSXYZ2f(precise_x, precise_y, precise_z, REGS.dr32[14]);
   }
 
diff --git a/src/core/host_interface.cpp b/src/core/host_interface.cpp
index 8ced54443..1795d51f1 100644
--- a/src/core/host_interface.cpp
+++ b/src/core/host_interface.cpp
@@ -434,6 +434,7 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si)
   si.SetBoolValue("GPU", "PGXPTextureCorrection", true);
   si.SetBoolValue("GPU", "PGXPVertexCache", false);
   si.SetBoolValue("GPU", "PGXPCPU", false);
+  si.SetBoolValue("GPU", "PGXPPreserveProjFP", false);
 
   si.SetStringValue("Display", "CropMode", Settings::GetDisplayCropModeName(Settings::DEFAULT_DISPLAY_CROP_MODE));
   si.SetIntValue("Display", "ActiveStartOffset", 0);
diff --git a/src/core/settings.cpp b/src/core/settings.cpp
index 3f9ee7994..2e2ca2bfa 100644
--- a/src/core/settings.cpp
+++ b/src/core/settings.cpp
@@ -149,6 +149,7 @@ void Settings::Load(SettingsInterface& si)
   gpu_pgxp_texture_correction = si.GetBoolValue("GPU", "PGXPTextureCorrection", true);
   gpu_pgxp_vertex_cache = si.GetBoolValue("GPU", "PGXPVertexCache", false);
   gpu_pgxp_cpu = si.GetBoolValue("GPU", "PGXPCPU", false);
+  gpu_pgxp_preserve_proj_fp = si.GetBoolValue("GPU", "PGXPPreserveProjFP", false);
 
   display_crop_mode =
     ParseDisplayCropMode(
@@ -273,6 +274,7 @@ void Settings::Save(SettingsInterface& si) const
   si.SetBoolValue("GPU", "PGXPTextureCorrection", gpu_pgxp_texture_correction);
   si.SetBoolValue("GPU", "PGXPVertexCache", gpu_pgxp_vertex_cache);
   si.SetBoolValue("GPU", "PGXPCPU", gpu_pgxp_cpu);
+  si.SetBoolValue("GPU", "PGXPPreserveProjFP", gpu_pgxp_preserve_proj_fp);
 
   si.SetStringValue("Display", "CropMode", GetDisplayCropModeName(display_crop_mode));
   si.SetIntValue("Display", "ActiveStartOffset", display_active_start_offset);
diff --git a/src/core/settings.h b/src/core/settings.h
index 01a5a37cc..d5d589c44 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -104,6 +104,7 @@ struct Settings
   bool gpu_pgxp_texture_correction = true;
   bool gpu_pgxp_vertex_cache = false;
   bool gpu_pgxp_cpu = false;
+  bool gpu_pgxp_preserve_proj_fp = false;
   DisplayCropMode display_crop_mode = DisplayCropMode::None;
   s16 display_active_start_offset = 0;
   s16 display_active_end_offset = 0;
diff --git a/src/duckstation-qt/advancedsettingswidget.cpp b/src/duckstation-qt/advancedsettingswidget.cpp
index 518b505bd..e31d36048 100644
--- a/src/duckstation-qt/advancedsettingswidget.cpp
+++ b/src/duckstation-qt/advancedsettingswidget.cpp
@@ -85,6 +85,8 @@ AdvancedSettingsWidget::AdvancedSettingsWidget(QtHostInterface* host_interface,
   addBooleanTweakOption(m_host_interface, m_ui.tweakOptionTable, tr("PGXP Vertex Cache"), "GPU", "PGXPVertexCache",
                         false);
   addBooleanTweakOption(m_host_interface, m_ui.tweakOptionTable, tr("PGXP CPU Mode"), "GPU", "PGXPCPU", false);
+  addBooleanTweakOption(m_host_interface, m_ui.tweakOptionTable, tr("PGXP Preserve Projection Precision"), "GPU",
+                        "PGXPPreserveProjFP", false);
 
   addBooleanTweakOption(m_host_interface, m_ui.tweakOptionTable, tr("Enable Recompiler Memory Exceptions"), "CPU",
                         "RecompilerMemoryExceptions", false);
diff --git a/src/duckstation-sdl/sdl_host_interface.cpp b/src/duckstation-sdl/sdl_host_interface.cpp
index 555e2a8e1..fdacf579d 100644
--- a/src/duckstation-sdl/sdl_host_interface.cpp
+++ b/src/duckstation-sdl/sdl_host_interface.cpp
@@ -959,6 +959,9 @@ void SDLHostInterface::DrawQuickSettingsMenu()
                                         m_settings_copy.gpu_pgxp_enable);
     settings_changed |=
       ImGui::MenuItem("PGXP CPU Instructions", nullptr, &m_settings_copy.gpu_pgxp_cpu, m_settings_copy.gpu_pgxp_enable);
+    settings_changed |=
+      ImGui::MenuItem("PGXP Preserve Projection Precision", nullptr, &m_settings_copy.gpu_pgxp_preserve_proj_fp,
+                      m_settings_copy.gpu_pgxp_enable);
     ImGui::EndMenu();
   }