diff --git a/README.md b/README.md index e0ee7f745..f7099b747 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ A "BIOS" ROM image is required to to start the emulator and to play games. You c ## Latest News +- 2020/07/18: Widescreen hack enhancement added. - 2020/07/04: Vulkan renderer now available in libretro core. - 2020/07/02: Now available as a libretro core. - 2020/07/01: Lightgun support with custom crosshairs. diff --git a/src/core/bus.h b/src/core/bus.h index de50b8287..6e126885a 100644 --- a/src/core/bus.h +++ b/src/core/bus.h @@ -29,6 +29,55 @@ class Bus friend DMA; public: + enum : u32 + { + RAM_BASE = 0x00000000, + RAM_SIZE = 0x200000, + RAM_MASK = RAM_SIZE - 1, + RAM_MIRROR_END = 0x800000, + EXP1_BASE = 0x1F000000, + EXP1_SIZE = 0x800000, + EXP1_MASK = EXP1_SIZE - 1, + MEMCTRL_BASE = 0x1F801000, + MEMCTRL_SIZE = 0x40, + MEMCTRL_MASK = MEMCTRL_SIZE - 1, + PAD_BASE = 0x1F801040, + PAD_SIZE = 0x10, + PAD_MASK = PAD_SIZE - 1, + SIO_BASE = 0x1F801050, + SIO_SIZE = 0x10, + SIO_MASK = SIO_SIZE - 1, + MEMCTRL2_BASE = 0x1F801060, + MEMCTRL2_SIZE = 0x10, + MEMCTRL2_MASK = MEMCTRL_SIZE - 1, + INTERRUPT_CONTROLLER_BASE = 0x1F801070, + INTERRUPT_CONTROLLER_SIZE = 0x10, + INTERRUPT_CONTROLLER_MASK = INTERRUPT_CONTROLLER_SIZE - 1, + DMA_BASE = 0x1F801080, + DMA_SIZE = 0x80, + DMA_MASK = DMA_SIZE - 1, + TIMERS_BASE = 0x1F801100, + TIMERS_SIZE = 0x40, + TIMERS_MASK = TIMERS_SIZE - 1, + CDROM_BASE = 0x1F801800, + CDROM_SIZE = 0x10, + CDROM_MASK = CDROM_SIZE - 1, + GPU_BASE = 0x1F801810, + GPU_SIZE = 0x10, + GPU_MASK = GPU_SIZE - 1, + MDEC_BASE = 0x1F801820, + MDEC_SIZE = 0x10, + MDEC_MASK = MDEC_SIZE - 1, + SPU_BASE = 0x1F801C00, + SPU_SIZE = 0x400, + SPU_MASK = 0x3FF, + EXP2_BASE = 0x1F802000, + EXP2_SIZE = 0x2000, + EXP2_MASK = EXP2_SIZE - 1, + BIOS_BASE = 0x1FC00000, + BIOS_SIZE = 0x80000 + }; + Bus(); ~Bus(); @@ -85,56 +134,10 @@ public: /// Clears all code bits for RAM regions. ALWAYS_INLINE void ClearRAMCodePageFlags() { m_ram_code_bits.reset(); } -private: - enum : u32 - { - RAM_BASE = 0x00000000, - RAM_SIZE = 0x200000, - RAM_MASK = RAM_SIZE - 1, - RAM_MIRROR_END = 0x800000, - EXP1_BASE = 0x1F000000, - EXP1_SIZE = 0x800000, - EXP1_MASK = EXP1_SIZE - 1, - MEMCTRL_BASE = 0x1F801000, - MEMCTRL_SIZE = 0x40, - MEMCTRL_MASK = MEMCTRL_SIZE - 1, - PAD_BASE = 0x1F801040, - PAD_SIZE = 0x10, - PAD_MASK = PAD_SIZE - 1, - SIO_BASE = 0x1F801050, - SIO_SIZE = 0x10, - SIO_MASK = SIO_SIZE - 1, - MEMCTRL2_BASE = 0x1F801060, - MEMCTRL2_SIZE = 0x10, - MEMCTRL2_MASK = MEMCTRL_SIZE - 1, - INTERRUPT_CONTROLLER_BASE = 0x1F801070, - INTERRUPT_CONTROLLER_SIZE = 0x10, - INTERRUPT_CONTROLLER_MASK = INTERRUPT_CONTROLLER_SIZE - 1, - DMA_BASE = 0x1F801080, - DMA_SIZE = 0x80, - DMA_MASK = DMA_SIZE - 1, - TIMERS_BASE = 0x1F801100, - TIMERS_SIZE = 0x40, - TIMERS_MASK = TIMERS_SIZE - 1, - CDROM_BASE = 0x1F801800, - CDROM_SIZE = 0x10, - CDROM_MASK = CDROM_SIZE - 1, - GPU_BASE = 0x1F801810, - GPU_SIZE = 0x10, - GPU_MASK = GPU_SIZE - 1, - MDEC_BASE = 0x1F801820, - MDEC_SIZE = 0x10, - MDEC_MASK = MDEC_SIZE - 1, - SPU_BASE = 0x1F801C00, - SPU_SIZE = 0x400, - SPU_MASK = 0x3FF, - EXP2_BASE = 0x1F802000, - EXP2_SIZE = 0x2000, - EXP2_MASK = EXP2_SIZE - 1, - BIOS_BASE = 0x1FC00000, - BIOS_SIZE = 0x80000 - }; + /// Direct access to RAM - used by DMA. + ALWAYS_INLINE u8* GetRAM() { return m_ram; } +private: enum : u32 { MEMCTRL_REG_COUNT = 9 @@ -238,9 +241,6 @@ private: void DoInvalidateCodeCache(u32 page_index); - /// Direct access to RAM - used by DMA. - ALWAYS_INLINE u8* GetRAM() { return m_ram; } - /// Returns the number of cycles stolen by DMA RAM access. ALWAYS_INLINE static TickCount GetDMARAMTickCount(u32 word_count) { diff --git a/src/core/cpu_core.h b/src/core/cpu_core.h index ce07632be..b097f4dd4 100644 --- a/src/core/cpu_core.h +++ b/src/core/cpu_core.h @@ -54,6 +54,9 @@ public: ALWAYS_INLINE TickCount GetDowncount() const { return m_downcount; } ALWAYS_INLINE void SetDowncount(TickCount downcount) { m_downcount = downcount; } + ALWAYS_INLINE const GTE::Core& GetCop2() const { return m_cop2; } + ALWAYS_INLINE GTE::Core& GetCop2() { return m_cop2; } + // Sets the PC and flushes the pipeline. void SetPC(u32 new_pc); diff --git a/src/core/gte.cpp b/src/core/gte.cpp index 10c68dbf6..e7fa80c7c 100644 --- a/src/core/gte.cpp +++ b/src/core/gte.cpp @@ -578,7 +578,10 @@ void Core::RTPS(const s16 V[3], u8 shift, bool lm, bool last) // MAC0=(((H*20000h/SZ3)+1)/2)*IR1+OFX, SX2=MAC0/10000h ;ScrX FIFO -400h..+3FFh // MAC0=(((H*20000h/SZ3)+1)/2)*IR2+OFY, SY2=MAC0/10000h ;ScrY FIFO -400h..+3FFh const s64 result = static_cast(ZeroExtend64(UNRDivide(m_regs.H, m_regs.SZ3))); - const s64 Sx = s64(result) * s64(m_regs.IR1) + s64(m_regs.OFX); + + // (4 / 3) / (16 / 9) -> 0.75 -> (3 / 4) + const s64 Sx = m_widescreen_hack ? ((((s64(result) * s64(m_regs.IR1)) * s64(3)) / s64(4)) + s64(m_regs.OFX)) : + (s64(result) * s64(m_regs.IR1) + s64(m_regs.OFX)); const s64 Sy = s64(result) * s64(m_regs.IR2) + s64(m_regs.OFY); CheckMACOverflow<0>(Sx); CheckMACOverflow<0>(Sy); diff --git a/src/core/gte.h b/src/core/gte.h index bd5a91e60..bdc37b295 100644 --- a/src/core/gte.h +++ b/src/core/gte.h @@ -21,6 +21,8 @@ public: Core(); ~Core(); + ALWAYS_INLINE void SetWidescreenHack(bool enabled) { m_widescreen_hack = enabled; } + void Initialize(); void Reset(); bool DoState(StateWrapper& sw); @@ -109,6 +111,7 @@ private: void Execute_GPF(Instruction inst); Regs m_regs = {}; + bool m_widescreen_hack = false; }; #include "gte.inl" diff --git a/src/core/host_interface.cpp b/src/core/host_interface.cpp index e5aa47bff..466217b7a 100644 --- a/src/core/host_interface.cpp +++ b/src/core/host_interface.cpp @@ -8,6 +8,7 @@ #include "common/log.h" #include "common/string_util.h" #include "controller.h" +#include "cpu_core.h" #include "dma.h" #include "gpu.h" #include "host_display.h" @@ -345,6 +346,7 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si) si.SetBoolValue("GPU", "TextureFiltering", false); si.SetBoolValue("GPU", "DisableInterlacing", false); si.SetBoolValue("GPU", "ForceNTSCTimings", false); + si.SetBoolValue("GPU", "WidescreenHack", false); si.SetStringValue("Display", "CropMode", Settings::GetDisplayCropModeName(Settings::DEFAULT_DISPLAY_CROP_MODE)); si.SetStringValue("Display", "AspectRatio", @@ -473,6 +475,9 @@ void HostInterface::CheckForSettingsChanges(const Settings& old_settings) m_system->GetDMA()->SetMaxSliceTicks(m_settings.dma_max_slice_ticks); m_system->GetDMA()->SetHaltTicks(m_settings.dma_halt_ticks); + + if (m_settings.gpu_widescreen_hack != old_settings.gpu_widescreen_hack) + m_system->GetCPU()->GetCop2().SetWidescreenHack(m_settings.gpu_widescreen_hack); } bool controllers_updated = false; diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 03ed63600..afa5e3359 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -98,6 +98,7 @@ void Settings::Load(SettingsInterface& si) gpu_texture_filtering = si.GetBoolValue("GPU", "TextureFiltering", false); gpu_disable_interlacing = si.GetBoolValue("GPU", "DisableInterlacing", false); gpu_force_ntsc_timings = si.GetBoolValue("GPU", "ForceNTSCTimings", false); + gpu_widescreen_hack = si.GetBoolValue("GPU", "WidescreenHack", false); display_crop_mode = ParseDisplayCropMode( @@ -200,6 +201,7 @@ void Settings::Save(SettingsInterface& si) const si.SetBoolValue("GPU", "TextureFiltering", gpu_texture_filtering); si.SetBoolValue("GPU", "DisableInterlacing", gpu_disable_interlacing); si.SetBoolValue("GPU", "ForceNTSCTimings", gpu_force_ntsc_timings); + si.SetBoolValue("GPU", "WidescreenHack", gpu_widescreen_hack); si.SetStringValue("Display", "CropMode", GetDisplayCropModeName(display_crop_mode)); si.SetStringValue("Display", "AspectRatio", GetDisplayAspectRatioName(display_aspect_ratio)); @@ -437,10 +439,10 @@ const char* Settings::GetDisplayCropModeDisplayName(DisplayCropMode crop_mode) return s_display_crop_mode_display_names[static_cast(crop_mode)]; } -static std::array s_display_aspect_ratio_names = - {{"4:3", "16:9", "8:7", "2:1 (VRAM 1:1)", "1:1", "PAR 1:1"}}; -static constexpr std::array s_display_aspect_ratio_values = - {{4.0f / 3.0f, 16.0f / 9.0f, 8.0f / 7.0f, 2.0f / 1.0f, 1.0f, -1.0f}}; +static std::array s_display_aspect_ratio_names = { + {"4:3", "16:9", "8:7", "2:1 (VRAM 1:1)", "1:1", "PAR 1:1"}}; +static constexpr std::array s_display_aspect_ratio_values = { + {4.0f / 3.0f, 16.0f / 9.0f, 8.0f / 7.0f, 2.0f / 1.0f, 1.0f, -1.0f}}; std::optional Settings::ParseDisplayAspectRatio(const char* str) { diff --git a/src/core/settings.h b/src/core/settings.h index ad9067fcf..f78721989 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -88,6 +88,7 @@ struct Settings bool gpu_texture_filtering = false; bool gpu_disable_interlacing = false; bool gpu_force_ntsc_timings = false; + bool gpu_widescreen_hack = false; DisplayCropMode display_crop_mode = DisplayCropMode::None; DisplayAspectRatio display_aspect_ratio = DisplayAspectRatio::R4_3; bool display_linear_filtering = true; diff --git a/src/core/system.cpp b/src/core/system.cpp index 6f4012353..73ac290ef 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -242,7 +242,8 @@ bool System::Boot(const SystemBootParameters& params) bool System::InitializeComponents(bool force_software_renderer) { - if (!CreateGPU(force_software_renderer ? GPURenderer::Software : GetSettings().gpu_renderer)) + const Settings& settings = GetSettings(); + if (!CreateGPU(force_software_renderer ? GPURenderer::Software : settings.gpu_renderer)) return false; m_cpu->Initialize(m_bus.get()); @@ -261,6 +262,9 @@ bool System::InitializeComponents(bool force_software_renderer) m_spu->Initialize(this, m_dma.get(), m_interrupt_controller.get()); m_mdec->Initialize(this, m_dma.get()); + // load settings + m_cpu->GetCop2().SetWidescreenHack(settings.gpu_widescreen_hack); + UpdateThrottlePeriod(); return true; } diff --git a/src/duckstation-libretro/libretro_host_interface.cpp b/src/duckstation-libretro/libretro_host_interface.cpp index 64af34bb9..892b8a73a 100644 --- a/src/duckstation-libretro/libretro_host_interface.cpp +++ b/src/duckstation-libretro/libretro_host_interface.cpp @@ -5,6 +5,7 @@ #include "common/log.h" #include "common/string_util.h" #include "core/analog_controller.h" +#include "core/bus.h" #include "core/digital_controller.h" #include "core/game_list.h" #include "core/gpu.h" @@ -299,6 +300,30 @@ bool LibretroHostInterface::retro_unserialize(const void* data, size_t size) return true; } +void* LibretroHostInterface::retro_get_memory_data(unsigned id) +{ + switch (id) + { + case RETRO_MEMORY_SYSTEM_RAM: + return m_system ? m_system->GetBus()->GetRAM() : nullptr; + + default: + return nullptr; + } +} + +size_t LibretroHostInterface::retro_get_memory_size(unsigned id) +{ + switch (id) + { + case RETRO_MEMORY_SYSTEM_RAM: + return Bus::RAM_SIZE; + + default: + return 0; + } +} + bool LibretroHostInterface::AcquireHostDisplay() { // start in software mode, switch to hardware later @@ -323,7 +348,7 @@ void LibretroHostInterface::OnSystemDestroyed() m_using_hardware_renderer = false; } -static std::array s_option_definitions = {{ +static std::array s_option_definitions = {{ {"Console.Region", "Console Region", "Determines which region/hardware to emulate. Auto-Detect will use the region of the disc inserted.", @@ -412,6 +437,12 @@ static std::array s_option_definitions = {{ "others will break.", {{"true", "Enabled"}, {"false", "Disabled"}}, "false"}, + {"GPU.WidescreenHack", + "Widescreen Hack", + "Increases the field of view from 4:3 to 16:9 in 3D games. For 2D games, or games which use pre-rendered " + "backgrounds, this enhancement will not work as expected.", + {{"true", "Enabled"}, {"false", "Disabled"}}, + "false"}, {"Display.CropMode", "Crop Mode", "Changes how much of the image is cropped. Some games display garbage in the overscan area which is typically " diff --git a/src/duckstation-libretro/libretro_host_interface.h b/src/duckstation-libretro/libretro_host_interface.h index 030704fb3..ff1ab4f6d 100644 --- a/src/duckstation-libretro/libretro_host_interface.h +++ b/src/duckstation-libretro/libretro_host_interface.h @@ -37,6 +37,8 @@ public: size_t retro_serialize_size(); bool retro_serialize(void* data, size_t size); bool retro_unserialize(const void* data, size_t size); + void* retro_get_memory_data(unsigned id); + size_t retro_get_memory_size(unsigned id); protected: bool AcquireHostDisplay() override; diff --git a/src/duckstation-libretro/main.cpp b/src/duckstation-libretro/main.cpp index 41d6f4e5d..14bbc10d1 100644 --- a/src/duckstation-libretro/main.cpp +++ b/src/duckstation-libretro/main.cpp @@ -111,12 +111,12 @@ RETRO_API unsigned retro_get_region(void) RETRO_API void* retro_get_memory_data(unsigned id) { - return nullptr; + return g_libretro_host_interface.retro_get_memory_data(id); } RETRO_API size_t retro_get_memory_size(unsigned id) { - return 0; + return g_libretro_host_interface.retro_get_memory_size(id); } RETRO_API void retro_set_environment(retro_environment_t f) diff --git a/src/duckstation-qt/gamelistsettingswidget.cpp b/src/duckstation-qt/gamelistsettingswidget.cpp index f020d6db6..9aff85bb5 100644 --- a/src/duckstation-qt/gamelistsettingswidget.cpp +++ b/src/duckstation-qt/gamelistsettingswidget.cpp @@ -214,14 +214,14 @@ GameListSettingsWidget::GameListSettingsWidget(QtHostInterface* host_interface, m_ui.searchDirectoryList->setCurrentIndex({}); connect(m_ui.searchDirectoryList, &QTableView::clicked, this, &GameListSettingsWidget::onDirectoryListItemClicked); - connect(m_ui.addSearchDirectoryButton, &QToolButton::pressed, this, - &GameListSettingsWidget::onAddSearchDirectoryButtonPressed); - connect(m_ui.removeSearchDirectoryButton, &QToolButton::pressed, this, - &GameListSettingsWidget::onRemoveSearchDirectoryButtonPressed); - connect(m_ui.rescanAllGames, &QToolButton::pressed, this, &GameListSettingsWidget::onRescanAllGamesPressed); - connect(m_ui.scanForNewGames, &QToolButton::pressed, this, &GameListSettingsWidget::onScanForNewGamesPressed); - connect(m_ui.updateRedumpDatabase, &QToolButton::pressed, this, - &GameListSettingsWidget::onUpdateRedumpDatabaseButtonPressed); + connect(m_ui.addSearchDirectoryButton, &QPushButton::clicked, this, + &GameListSettingsWidget::onAddSearchDirectoryButtonClicked); + connect(m_ui.removeSearchDirectoryButton, &QPushButton::clicked, this, + &GameListSettingsWidget::onRemoveSearchDirectoryButtonClicked); + connect(m_ui.rescanAllGames, &QPushButton::clicked, this, &GameListSettingsWidget::onRescanAllGamesClicked); + connect(m_ui.scanForNewGames, &QPushButton::clicked, this, &GameListSettingsWidget::onScanForNewGamesClicked); + connect(m_ui.updateRedumpDatabase, &QPushButton::clicked, this, + &GameListSettingsWidget::onUpdateRedumpDatabaseButtonClicked); } GameListSettingsWidget::~GameListSettingsWidget() = default; @@ -248,7 +248,9 @@ void GameListSettingsWidget::onDirectoryListItemClicked(const QModelIndex& index void GameListSettingsWidget::addSearchDirectory(QWidget* parent_widget) { - QString dir = QFileDialog::getExistingDirectory(parent_widget, tr("Select Search Directory")); + QString dir = + QDir::toNativeSeparators(QFileDialog::getExistingDirectory(parent_widget, tr("Select Search Directory"))); + if (dir.isEmpty()) return; @@ -265,12 +267,12 @@ void GameListSettingsWidget::addSearchDirectory(QWidget* parent_widget) m_search_directories_model->addEntry(dir, recursive); } -void GameListSettingsWidget::onAddSearchDirectoryButtonPressed() +void GameListSettingsWidget::onAddSearchDirectoryButtonClicked() { addSearchDirectory(this); } -void GameListSettingsWidget::onRemoveSearchDirectoryButtonPressed() +void GameListSettingsWidget::onRemoveSearchDirectoryButtonClicked() { QModelIndexList selection = m_ui.searchDirectoryList->selectionModel()->selectedIndexes(); if (selection.size() < 1) @@ -280,17 +282,17 @@ void GameListSettingsWidget::onRemoveSearchDirectoryButtonPressed() m_search_directories_model->removeEntry(row); } -void GameListSettingsWidget::onRescanAllGamesPressed() +void GameListSettingsWidget::onRescanAllGamesClicked() { m_host_interface->refreshGameList(true, false); } -void GameListSettingsWidget::onScanForNewGamesPressed() +void GameListSettingsWidget::onScanForNewGamesClicked() { m_host_interface->refreshGameList(false, false); } -void GameListSettingsWidget::onUpdateRedumpDatabaseButtonPressed() +void GameListSettingsWidget::onUpdateRedumpDatabaseButtonClicked() { if (QMessageBox::question(this, tr("Download database from redump.org?"), tr("Do you wish to download the disc database from redump.org?\n\nThis will download " diff --git a/src/duckstation-qt/gamelistsettingswidget.h b/src/duckstation-qt/gamelistsettingswidget.h index d8df48010..59880d0a3 100644 --- a/src/duckstation-qt/gamelistsettingswidget.h +++ b/src/duckstation-qt/gamelistsettingswidget.h @@ -21,11 +21,11 @@ public Q_SLOTS: private Q_SLOTS: void onDirectoryListItemClicked(const QModelIndex& index); - void onAddSearchDirectoryButtonPressed(); - void onRemoveSearchDirectoryButtonPressed(); - void onScanForNewGamesPressed(); - void onRescanAllGamesPressed(); - void onUpdateRedumpDatabaseButtonPressed(); + void onAddSearchDirectoryButtonClicked(); + void onRemoveSearchDirectoryButtonClicked(); + void onScanForNewGamesClicked(); + void onRescanAllGamesClicked(); + void onUpdateRedumpDatabaseButtonClicked(); protected: void resizeEvent(QResizeEvent* event); diff --git a/src/duckstation-qt/gamelistsettingswidget.ui b/src/duckstation-qt/gamelistsettingswidget.ui index f12a5b987..f94080e60 100644 --- a/src/duckstation-qt/gamelistsettingswidget.ui +++ b/src/duckstation-qt/gamelistsettingswidget.ui @@ -41,7 +41,7 @@ - + Add @@ -49,13 +49,10 @@ :/icons/list-add.png:/icons/list-add.png - - Qt::ToolButtonTextBesideIcon - - + Remove @@ -63,9 +60,6 @@ :/icons/list-remove.png:/icons/list-remove.png - - Qt::ToolButtonTextBesideIcon - @@ -82,7 +76,7 @@ - + Scan New @@ -90,13 +84,10 @@ :/icons/folder-open.png:/icons/folder-open.png - - Qt::ToolButtonTextBesideIcon - - + Rescan All @@ -104,9 +95,6 @@ :/icons/view-refresh.png:/icons/view-refresh.png - - Qt::ToolButtonTextBesideIcon - diff --git a/src/duckstation-qt/gpusettingswidget.cpp b/src/duckstation-qt/gpusettingswidget.cpp index 1d8022fd2..1dcc4b2d9 100644 --- a/src/duckstation-qt/gpusettingswidget.cpp +++ b/src/duckstation-qt/gpusettingswidget.cpp @@ -45,6 +45,8 @@ GPUSettingsWidget::GPUSettingsWidget(QtHostInterface* host_interface, QWidget* p QStringLiteral("ForceNTSCTimings")); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.linearTextureFiltering, QStringLiteral("GPU"), QStringLiteral("TextureFiltering")); + SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.widescreenHack, QStringLiteral("GPU"), + QStringLiteral("WidescreenHack")); connect(m_ui.resolutionScale, QOverload::of(&QComboBox::currentIndexChanged), this, &GPUSettingsWidget::updateScaledDitheringEnabled); @@ -73,7 +75,7 @@ GPUSettingsWidget::GPUSettingsWidget(QtHostInterface* host_interface, QWidget* p "Some games display content in the overscan area, or use it for screen effects and 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.disableInterlacing, "Disable Interlacing (force progressive render/scan)", "Checked", + dialog->registerWidgetHelp(m_ui.disableInterlacing, "Disable Interlacing (force progressive render/scan)", "Unchecked", "Forces the display of frames to progressive mode. This only affects the displayed image, " "the console will be unaware of the setting. If the game is internally producing " "interlaced frames, this option may not have any effect. Usually safe to enable."); @@ -115,6 +117,10 @@ GPUSettingsWidget::GPUSettingsWidget(QtHostInterface* host_interface, QWidget* p "Smooths out the blockyness of magnified textures on 3D object by using bilinear " "filtering. Will have a greater effect on higher resolution scales. Currently this option " "produces artifacts around objects in many games and needs further work. Only applies to the hardware renderers."); + dialog->registerWidgetHelp(m_ui.widescreenHack, "Widescreen Hack", "Unchecked", + "Scales vertex positions in screen-space to a widescreen aspect ratio, essentially " + "increasing the field of view from 4:3 to 16:9 in 3D games. For 2D games, or games which " + "use pre-rendered backgrounds, this enhancement will not work as expected."); } GPUSettingsWidget::~GPUSettingsWidget() = default; diff --git a/src/duckstation-qt/gpusettingswidget.ui b/src/duckstation-qt/gpusettingswidget.ui index 1f89c4a66..43a299d77 100644 --- a/src/duckstation-qt/gpusettingswidget.ui +++ b/src/duckstation-qt/gpusettingswidget.ui @@ -163,6 +163,13 @@ + + + + Widescreen Hack + + + diff --git a/src/duckstation-qt/settingsdialog.cpp b/src/duckstation-qt/settingsdialog.cpp index 5fbdac13a..d1ab694bb 100644 --- a/src/duckstation-qt/settingsdialog.cpp +++ b/src/duckstation-qt/settingsdialog.cpp @@ -22,9 +22,10 @@ static constexpr std::array(SettingsDialog::Catego "to populate the game list. Search directories can be added, removed, and switched to recursive/non-recursive. " "Additionally, the redump.org database can be downloaded or updated to provide titles for discs, as the discs " "themselves do not provide title information.", - "Hotkey Settings
Binding a hotkey allows you to trigger events such as a resetting, powering " - "off, taking screenshots or saving/loading states at the press of a key/controller button. Hotkey titles are " - "self-explanatory.", + "Hotkey Settings
Binding a hotkey allows you to trigger events such as a resetting or taking " + "screenshots at the press of a key/controller button. Hotkey titles are self-explanatory. Clicking a binding will " + "start a countdown, in which case you should press the key or controller button/axis you wish to bind. If no button " + "is pressed and the timer lapses, the binding will be unchanged. To clear a binding, right-click the button.", "Controller Settings
This page lets you choose the type of controller you wish to simulate for " "the console, and rebind the keys or host game controller buttons to your choosing. Clicking a binding will start a " "countdown, in which case you should press the key or controller button/axis you wish to bind. (For rumble, press " diff --git a/src/duckstation-sdl/sdl_host_interface.cpp b/src/duckstation-sdl/sdl_host_interface.cpp index 14b179cf5..7bdcfd126 100644 --- a/src/duckstation-sdl/sdl_host_interface.cpp +++ b/src/duckstation-sdl/sdl_host_interface.cpp @@ -845,6 +845,7 @@ void SDLHostInterface::DrawQuickSettingsMenu() settings_changed |= ImGui::MenuItem("Scaled Dithering", nullptr, &m_settings_copy.gpu_scaled_dithering); settings_changed |= ImGui::MenuItem("Texture Filtering", nullptr, &m_settings_copy.gpu_texture_filtering); settings_changed |= ImGui::MenuItem("Disable Interlacing", nullptr, &m_settings_copy.gpu_disable_interlacing); + settings_changed |= ImGui::MenuItem("Widescreen Hack", nullptr, &m_settings_copy.gpu_widescreen_hack); settings_changed |= ImGui::MenuItem("Display Linear Filtering", nullptr, &m_settings_copy.display_linear_filtering); settings_changed |= ImGui::MenuItem("Display Integer Scaling", nullptr, &m_settings_copy.display_integer_scaling); @@ -1296,6 +1297,7 @@ void SDLHostInterface::DrawSettingsWindow() settings_changed |= ImGui::Checkbox("Texture Filtering", &m_settings_copy.gpu_texture_filtering); settings_changed |= ImGui::Checkbox("Disable Interlacing", &m_settings_copy.gpu_disable_interlacing); settings_changed |= ImGui::Checkbox("Force NTSC Timings", &m_settings_copy.gpu_force_ntsc_timings); + settings_changed |= ImGui::Checkbox("Widescreen Hack", &m_settings_copy.gpu_widescreen_hack); } ImGui::EndTabItem();