diff --git a/src/core/gpu.cpp b/src/core/gpu.cpp index c6e22cf78..21ea01d71 100644 --- a/src/core/gpu.cpp +++ b/src/core/gpu.cpp @@ -28,6 +28,7 @@ bool GPU::Initialize(HostDisplay* host_display, System* system, DMA* dma, Interr m_interrupt_controller = interrupt_controller; m_timers = timers; m_force_progressive_scan = m_system->GetSettings().display_force_progressive_scan; + m_force_ntsc_timings = m_system->GetSettings().gpu_force_ntsc_timings; m_tick_event = m_system->CreateTimingEvent("GPU Tick", 1, 1, std::bind(&GPU::Execute, this, std::placeholders::_1), true); return true; @@ -37,6 +38,12 @@ void GPU::UpdateSettings() { m_force_progressive_scan = m_system->GetSettings().display_force_progressive_scan; + if (m_force_ntsc_timings != m_system->GetSettings().gpu_force_ntsc_timings) + { + m_force_ntsc_timings = m_system->GetSettings().gpu_force_ntsc_timings; + UpdateCRTCConfig(); + } + // Crop mode calls this, so recalculate the display area UpdateCRTCDisplayParameters(); } @@ -342,11 +349,6 @@ void GPU::UpdateCRTCConfig() cs.current_tick_in_scanline %= NTSC_TICKS_PER_LINE; } - const TickCount ticks_per_frame = cs.horizontal_total * cs.vertical_total; - const float vertical_frequency = - static_cast<float>(static_cast<double>((u64(MASTER_CLOCK) * 11) / 7) / static_cast<double>(ticks_per_frame)); - m_system->SetThrottleFrequency(vertical_frequency); - const u8 horizontal_resolution_index = m_GPUSTAT.horizontal_resolution_1 | (m_GPUSTAT.horizontal_resolution_2 << 2); cs.dot_clock_divider = dot_clock_dividers[horizontal_resolution_index]; cs.horizontal_display_start = std::min<u16>(cs.regs.X1, cs.horizontal_total); @@ -354,6 +356,30 @@ void GPU::UpdateCRTCConfig() cs.vertical_display_start = std::min<u16>(cs.regs.Y1, cs.vertical_total); cs.vertical_display_end = std::min<u16>(cs.regs.Y2, cs.vertical_total); + if (m_GPUSTAT.pal_mode && m_force_ntsc_timings) + { + // scale to NTSC parameters + cs.horizontal_display_start = + static_cast<u16>((static_cast<u32>(cs.horizontal_display_start) * NTSC_TICKS_PER_LINE) / PAL_TICKS_PER_LINE); + cs.horizontal_display_end = static_cast<u16>( + ((static_cast<u32>(cs.horizontal_display_end) * NTSC_TICKS_PER_LINE) + (PAL_TICKS_PER_LINE - 1)) / + PAL_TICKS_PER_LINE); + cs.vertical_display_start = + static_cast<u16>((static_cast<u32>(cs.vertical_display_start) * NTSC_TOTAL_LINES) / PAL_TOTAL_LINES); + cs.vertical_display_end = static_cast<u16>( + ((static_cast<u32>(cs.vertical_display_end) * NTSC_TOTAL_LINES) + (PAL_TOTAL_LINES - 1)) / PAL_TOTAL_LINES); + + cs.vertical_total = NTSC_TOTAL_LINES; + cs.current_scanline %= NTSC_TOTAL_LINES; + cs.horizontal_total = NTSC_TICKS_PER_LINE; + cs.current_tick_in_scanline %= NTSC_TICKS_PER_LINE; + } + + const TickCount ticks_per_frame = cs.horizontal_total * cs.vertical_total; + const float vertical_frequency = + static_cast<float>(static_cast<double>((u64(MASTER_CLOCK) * 11) / 7) / static_cast<double>(ticks_per_frame)); + m_system->SetThrottleFrequency(vertical_frequency); + UpdateCRTCDisplayParameters(); UpdateSliceTicks(); } @@ -363,33 +389,40 @@ void GPU::UpdateCRTCDisplayParameters() CRTCState& cs = m_crtc_state; const DisplayCropMode crop_mode = m_system->GetSettings().display_crop_mode; - u16 horizontal_display_start_tick, horizontal_display_end_tick; - u16 vertical_display_start_line, vertical_display_end_line; + const u16 horizontal_total = m_GPUSTAT.pal_mode ? PAL_TICKS_PER_LINE : NTSC_TICKS_PER_LINE; + const u16 vertical_total = m_GPUSTAT.pal_mode ? PAL_TOTAL_LINES : NTSC_TOTAL_LINES; + const u16 horizontal_display_start = std::min<u16>(cs.regs.X1, horizontal_total); + const u16 horizontal_display_end = std::min<u16>(cs.regs.X2, horizontal_total); + const u16 vertical_display_start = std::min<u16>(cs.regs.Y1, vertical_total); + const u16 vertical_display_end = std::min<u16>(cs.regs.Y2, vertical_total); + + u16 horizontal_visible_start_tick, horizontal_visible_end_tick; + u16 vertical_visible_start_line, vertical_visible_end_line; if (m_GPUSTAT.pal_mode) { // TODO: Verify PAL numbers. switch (crop_mode) { case DisplayCropMode::None: - horizontal_display_start_tick = 487; - horizontal_display_end_tick = 3282; - vertical_display_start_line = 20; - vertical_display_end_line = 308; + horizontal_visible_start_tick = 487; + horizontal_visible_end_tick = 3282; + vertical_visible_start_line = 20; + vertical_visible_end_line = 308; break; case DisplayCropMode::Overscan: - horizontal_display_start_tick = 628; - horizontal_display_end_tick = 3188; - vertical_display_start_line = 30; - vertical_display_end_line = 298; + horizontal_visible_start_tick = 628; + horizontal_visible_end_tick = 3188; + vertical_visible_start_line = 30; + vertical_visible_end_line = 298; break; case DisplayCropMode::Borders: default: - horizontal_display_start_tick = m_crtc_state.horizontal_display_start; - horizontal_display_end_tick = m_crtc_state.horizontal_display_end; - vertical_display_start_line = m_crtc_state.vertical_display_start; - vertical_display_end_line = m_crtc_state.vertical_display_end; + horizontal_visible_start_tick = horizontal_display_start; + horizontal_visible_end_tick = horizontal_display_end; + vertical_visible_start_line = vertical_display_start; + vertical_visible_end_line = vertical_display_end; break; } } @@ -398,25 +431,25 @@ void GPU::UpdateCRTCDisplayParameters() switch (crop_mode) { case DisplayCropMode::None: - horizontal_display_start_tick = 488; - horizontal_display_end_tick = 3288; - vertical_display_start_line = 16; - vertical_display_end_line = 256; + horizontal_visible_start_tick = 488; + horizontal_visible_end_tick = 3288; + vertical_visible_start_line = 16; + vertical_visible_end_line = 256; break; case DisplayCropMode::Overscan: - horizontal_display_start_tick = 608; - horizontal_display_end_tick = 3168; - vertical_display_start_line = 24; - vertical_display_end_line = 248; + horizontal_visible_start_tick = 608; + horizontal_visible_end_tick = 3168; + vertical_visible_start_line = 24; + vertical_visible_end_line = 248; break; case DisplayCropMode::Borders: default: - horizontal_display_start_tick = m_crtc_state.horizontal_display_start; - horizontal_display_end_tick = m_crtc_state.horizontal_display_end; - vertical_display_start_line = m_crtc_state.vertical_display_start; - vertical_display_end_line = m_crtc_state.vertical_display_end; + horizontal_visible_start_tick = horizontal_display_start; + horizontal_visible_end_tick = horizontal_display_end; + vertical_visible_start_line = vertical_display_start; + vertical_visible_end_line = vertical_display_end; break; } } @@ -425,69 +458,67 @@ void GPU::UpdateCRTCDisplayParameters() // Determine screen size. cs.display_width = std::max<u16>( - ((horizontal_display_end_tick - horizontal_display_start_tick) + (cs.dot_clock_divider - 1)) / cs.dot_clock_divider, + ((horizontal_visible_end_tick - horizontal_visible_start_tick) + (cs.dot_clock_divider - 1)) / cs.dot_clock_divider, 1u); - cs.display_height = std::max<u16>((vertical_display_end_line - vertical_display_start_line) << height_shift, 1u); + cs.display_height = std::max<u16>((vertical_visible_end_line - vertical_visible_start_line) << height_shift, 1u); // Determine if we need to adjust the VRAM rectangle (because the display is starting outside the visible area) or add // padding. - if (cs.horizontal_display_start >= horizontal_display_start_tick) + if (horizontal_display_start >= horizontal_visible_start_tick) { - cs.display_origin_left = (cs.horizontal_display_start - horizontal_display_start_tick) / cs.dot_clock_divider; + cs.display_origin_left = (horizontal_display_start - horizontal_visible_start_tick) / cs.dot_clock_divider; cs.display_vram_left = m_crtc_state.regs.X; } else { cs.display_origin_left = 0; cs.display_vram_left = std::min<u16>( - m_crtc_state.regs.X + ((horizontal_display_start_tick - cs.horizontal_display_start) / cs.dot_clock_divider), + m_crtc_state.regs.X + ((horizontal_visible_start_tick - horizontal_display_start) / cs.dot_clock_divider), VRAM_WIDTH - 1); } - if (cs.horizontal_display_end <= horizontal_display_end_tick) + if (horizontal_display_end <= horizontal_visible_end_tick) { cs.display_vram_width = std::min<u16>( - std::max<u16>( - (((cs.horizontal_display_end - std::max(cs.horizontal_display_start, horizontal_display_start_tick)) + - (cs.dot_clock_divider - 1)) / - cs.dot_clock_divider), - 1u), + std::max<u16>((((horizontal_display_end - std::max(horizontal_display_start, horizontal_visible_start_tick)) + + (cs.dot_clock_divider - 1)) / + cs.dot_clock_divider), + 1u), VRAM_WIDTH - cs.display_vram_left); } else { cs.display_vram_width = std::min<u16>( std::max<u16>( - (((horizontal_display_end_tick - std::max(cs.horizontal_display_start, horizontal_display_start_tick)) + + (((horizontal_visible_end_tick - std::max(horizontal_display_start, horizontal_visible_start_tick)) + (cs.dot_clock_divider - 1)) / cs.dot_clock_divider), 1u), VRAM_WIDTH - cs.display_vram_left); } - if (cs.vertical_display_start >= vertical_display_start_line) + if (vertical_display_start >= vertical_visible_start_line) { - cs.display_origin_top = (cs.vertical_display_start - vertical_display_start_line) << height_shift; + cs.display_origin_top = (vertical_display_start - vertical_visible_start_line) << height_shift; cs.display_vram_top = m_crtc_state.regs.Y; } else { cs.display_origin_top = 0; - cs.display_vram_top = - std::min<u16>(m_crtc_state.regs.Y + ((vertical_display_start_line - cs.vertical_display_start) << height_shift), - VRAM_HEIGHT - 1); + cs.display_vram_top = std::min<u16>( + m_crtc_state.regs.Y + ((vertical_visible_start_line - vertical_display_start) << height_shift), VRAM_HEIGHT - 1); } - if (cs.vertical_display_end <= vertical_display_end_line) + if (vertical_display_end <= vertical_visible_end_line) { cs.display_vram_height = std::min<u16>( - (cs.vertical_display_end - std::max(cs.vertical_display_start, vertical_display_start_line)) << height_shift, + (vertical_display_end - std::max(vertical_display_start, vertical_visible_start_line)) << height_shift, VRAM_HEIGHT - cs.display_vram_top); } else { cs.display_vram_height = std::min<u16>( - (vertical_display_end_line - std::max(cs.vertical_display_start, vertical_display_start_line)) << height_shift, + (vertical_visible_end_line - std::max(vertical_display_start, vertical_visible_start_line)) << height_shift, VRAM_HEIGHT - cs.display_vram_top); } diff --git a/src/core/gpu.h b/src/core/gpu.h index 21d39c734..9a81eb244 100644 --- a/src/core/gpu.h +++ b/src/core/gpu.h @@ -95,6 +95,10 @@ public: DOT_TIMER_INDEX = 0, HBLANK_TIMER_INDEX = 1, MAX_RESOLUTION_SCALE = 16, + }; + + enum : u16 + { NTSC_TICKS_PER_LINE = 3413, NTSC_TOTAL_LINES = 263, PAL_TICKS_PER_LINE = 3406, @@ -531,6 +535,7 @@ protected: bool m_set_texture_disable_mask = false; bool m_drawing_area_changed = false; bool m_force_progressive_scan = false; + bool m_force_ntsc_timings = false; struct CRTCState { diff --git a/src/core/host_interface.cpp b/src/core/host_interface.cpp index 8eea861d3..ec6c06a3c 100644 --- a/src/core/host_interface.cpp +++ b/src/core/host_interface.cpp @@ -877,6 +877,7 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si) si.SetBoolValue("GPU", "ScaledDithering", false); si.SetBoolValue("GPU", "TextureFiltering", false); si.SetBoolValue("GPU", "UseDebugDevice", false); + si.SetBoolValue("GPU", "ForceNTSCTimings", false); si.SetStringValue("Display", "CropMode", "Overscan"); si.SetBoolValue("Display", "ForceProgressiveScan", true); @@ -926,6 +927,7 @@ void HostInterface::UpdateSettings(const std::function<void()>& apply_callback) const bool old_gpu_true_color = m_settings.gpu_true_color; const bool old_gpu_scaled_dithering = m_settings.gpu_scaled_dithering; const bool old_gpu_texture_filtering = m_settings.gpu_texture_filtering; + const bool old_gpu_force_ntsc_timings = m_settings.gpu_force_ntsc_timings; const bool old_display_force_progressive_scan = m_settings.display_force_progressive_scan; const bool old_gpu_debug_device = m_settings.gpu_use_debug_device; const bool old_vsync_enabled = m_settings.video_sync_enabled; @@ -979,6 +981,7 @@ void HostInterface::UpdateSettings(const std::function<void()>& apply_callback) m_settings.gpu_true_color != old_gpu_true_color || m_settings.gpu_scaled_dithering != old_gpu_scaled_dithering || m_settings.gpu_texture_filtering != old_gpu_texture_filtering || + m_settings.gpu_force_ntsc_timings != old_gpu_force_ntsc_timings || m_settings.display_force_progressive_scan != old_display_force_progressive_scan || m_settings.display_crop_mode != old_display_crop_mode) { diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 635e4ed29..f48841e08 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -27,6 +27,7 @@ void Settings::Load(SettingsInterface& si) gpu_scaled_dithering = si.GetBoolValue("GPU", "ScaledDithering", false); gpu_texture_filtering = si.GetBoolValue("GPU", "TextureFiltering", false); gpu_use_debug_device = si.GetBoolValue("GPU", "UseDebugDevice", false); + gpu_force_ntsc_timings = si.GetBoolValue("GPU", "ForceNTSCTimings", false); display_crop_mode = ParseDisplayCropMode( si.GetStringValue("Display", "CropMode", GetDisplayCropModeName(DisplayCropMode::None)).c_str()) @@ -89,6 +90,7 @@ void Settings::Save(SettingsInterface& si) const si.SetBoolValue("GPU", "ScaledDithering", gpu_scaled_dithering); si.SetBoolValue("GPU", "TextureFiltering", gpu_texture_filtering); si.SetBoolValue("GPU", "UseDebugDevice", gpu_use_debug_device); + si.SetBoolValue("GPU", "ForceNTSCTimings", gpu_force_ntsc_timings); si.SetStringValue("Display", "CropMode", GetDisplayCropModeName(display_crop_mode)); si.SetBoolValue("Display", "ForceProgressiveScan", display_force_progressive_scan); diff --git a/src/core/settings.h b/src/core/settings.h index 93fdb9c6e..3f1dd5897 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -51,6 +51,7 @@ struct Settings bool gpu_scaled_dithering = false; bool gpu_texture_filtering = false; bool gpu_use_debug_device = false; + bool gpu_force_ntsc_timings = false; DisplayCropMode display_crop_mode = DisplayCropMode::None; bool display_force_progressive_scan = false; bool display_linear_filtering = true; diff --git a/src/duckstation-qt/gpusettingswidget.cpp b/src/duckstation-qt/gpusettingswidget.cpp index 3ac7026b4..661364c0b 100644 --- a/src/duckstation-qt/gpusettingswidget.cpp +++ b/src/duckstation-qt/gpusettingswidget.cpp @@ -22,6 +22,7 @@ GPUSettingsWidget::GPUSettingsWidget(QtHostInterface* host_interface, QWidget* p SettingWidgetBinder::BindWidgetToIntSetting(m_host_interface, m_ui.resolutionScale, "GPU/ResolutionScale"); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.trueColor, "GPU/TrueColor"); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.scaledDithering, "GPU/ScaledDithering"); + SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.forceNTSCTimings, "GPU/ForceNTSCTimings"); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.linearTextureFiltering, "GPU/TextureFiltering"); connect(m_ui.resolutionScale, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, diff --git a/src/duckstation-qt/gpusettingswidget.ui b/src/duckstation-qt/gpusettingswidget.ui index 2f0bd235f..ea1507bfa 100644 --- a/src/duckstation-qt/gpusettingswidget.ui +++ b/src/duckstation-qt/gpusettingswidget.ui @@ -69,20 +69,13 @@ <widget class="QComboBox" name="cropMode"/> </item> <item row="1" column="0" colspan="2"> - <widget class="QCheckBox" name="forceProgressiveScan"> - <property name="text"> - <string>Force Progressive Scan</string> - </property> - </widget> - </item> - <item row="2" column="0" colspan="2"> <widget class="QCheckBox" name="displayLinearFiltering"> <property name="text"> <string>Linear Upscaling</string> </property> </widget> </item> - <item row="3" column="0" colspan="2"> + <item row="2" column="0" colspan="2"> <widget class="QCheckBox" name="vsync"> <property name="text"> <string>VSync</string> @@ -123,6 +116,20 @@ </widget> </item> <item row="3" column="0" colspan="2"> + <widget class="QCheckBox" name="forceProgressiveScan"> + <property name="text"> + <string>Force Progressive Scan</string> + </property> + </widget> + </item> + <item row="4" column="0" colspan="2"> + <widget class="QCheckBox" name="forceNTSCTimings"> + <property name="text"> + <string>Force NTSC Timings (60hz-on-PAL)</string> + </property> + </widget> + </item> + <item row="5" column="0" colspan="2"> <widget class="QCheckBox" name="linearTextureFiltering"> <property name="text"> <string>Bilinear Texture Filtering</string> diff --git a/src/duckstation-sdl/sdl_host_interface.cpp b/src/duckstation-sdl/sdl_host_interface.cpp index be1524de5..f4d0e64e4 100644 --- a/src/duckstation-sdl/sdl_host_interface.cpp +++ b/src/duckstation-sdl/sdl_host_interface.cpp @@ -1302,6 +1302,7 @@ void SDLHostInterface::DrawSettingsWindow() settings_changed |= ImGui::Checkbox("True 24-bit Color (disables dithering)", &m_settings_copy.gpu_true_color); settings_changed |= ImGui::Checkbox("Texture Filtering", &m_settings_copy.gpu_texture_filtering); + settings_changed |= ImGui::Checkbox("Force NTSC Timings", &m_settings_copy.gpu_force_ntsc_timings); settings_changed |= ImGui::Checkbox("Force Progressive Scan", &m_settings_copy.display_force_progressive_scan); }