Add skip presenting duplicate frames option

This commit is contained in:
Connor McLaughlin 2021-03-16 02:25:33 +10:00
parent a34f0d5599
commit df16444e35
18 changed files with 152 additions and 42 deletions

View File

@ -485,4 +485,16 @@
<item>Port2Only</item>
<item>BothPorts</item>
</string-array>
<string-array name="settings_display_frame_pacing_entries">
<item>Always Display All Frames</item>
<item>Display All Frames When Possible</item>
<item>Skip Duplicate Frames</item>
<item>Clamp To Max FPS</item>
</string-array>
<string-array name="settings_display_frame_pacing_values">
<item>DisplayAllFrames</item>
<item>DisplayAllFramesWhenPossible</item>
<item>SkipDuplicateFrames</item>
<item>ClampToMaxFPS</item>
</string-array>
</resources>

View File

@ -276,4 +276,5 @@
<string name="controller_settings_category_ports">Ports</string>
<string name="controller_settings_main_port_format">Port %d</string>
<string name="controller_settings_sub_port_format">Port %1$d%2$c</string>
<string name="settings_display_frame_pacing">Frame Pacing</string>
</resources>

View File

@ -56,11 +56,13 @@
app:summary="@string/settings_summary_general_sync_to_host_refresh_rate"
app:dependency="Display/VSync"
app:iconSpaceReserved="false" />
<SwitchPreferenceCompat
app:key="Display/DisplayAllFrames"
app:title="@string/settings_display_all_frames"
app:defaultValue="false"
app:summary="@string/settings_summary_display_all_frames"
<ListPreference
app:key="Display/FramePacingMode"
app:entries="@array/settings_display_frame_pacing_entries"
app:entryValues="@array/settings_display_frame_pacing_values"
app:title="@string/settings_display_frame_pacing"
app:defaultValue="DisplayAllFramesWhenPossible"
app:useSimpleSummaryProvider="true"
app:iconSpaceReserved="false" />
<ListPreference
app:key="Display/MaxFPS"

View File

@ -855,7 +855,14 @@ void GPU::CRTCTickEvent(TickCount ticks)
// flush any pending draws and "scan out" the image
FlushRender();
UpdateDisplay();
if (IsInterlacedDisplayEnabled() || m_crtc_state.display_changed ||
g_settings.display_frame_pacing_mode != DisplayFramePacingMode::SkipDuplicateFrames ||
g_settings.debugging.show_vram)
{
m_crtc_state.display_changed = false;
UpdateDisplay();
}
System::FrameDone();
// switch fields early. this is needed so we draw to the correct one.
@ -1073,6 +1080,7 @@ void GPU::WriteGP1(u32 value)
Log_DebugPrintf("Display address start <- 0x%08X", new_value);
System::IncrementInternalFrameNumber();
m_crtc_state.display_changed = true;
if (m_crtc_state.regs.display_address_start != new_value)
{
SynchronizeCRTC();

View File

@ -484,7 +484,13 @@ protected:
BitField<u32, u16, 0, 10> Y1;
BitField<u32, u16, 10, 10> Y2;
};
} regs;
};
bool in_hblank;
bool in_vblank;
bool display_changed;
Regs regs;
u16 dot_clock_divider;
@ -523,9 +529,6 @@ protected:
TickCount fractional_dot_ticks; // only used when timer0 is enabled
bool in_hblank;
bool in_vblank;
u8 interlaced_field; // 0 = odd, 1 = even
u8 interlaced_display_field;
u8 active_line_lsb;

View File

@ -5,6 +5,7 @@
#include "common/log.h"
#include "common/string_util.h"
#include "common/timer.h"
#include "settings.h"
#include "host_interface.h"
#include "stb_image.h"
#include "stb_image_resize.h"
@ -27,6 +28,9 @@ void HostDisplay::SetDisplayMaxFPS(float max_fps)
bool HostDisplay::ShouldSkipDisplayingFrame()
{
if (!m_display_duplicate_frames && !m_display_changed)
return true;
if (m_display_frame_interval == 0.0f)
return false;

View File

@ -8,7 +8,7 @@
#include <tuple>
#include <vector>
enum class HostDisplayPixelFormat : u32
enum class HostDisplayPixelFormat : u8
{
Unknown,
RGBA8,
@ -46,7 +46,7 @@ public:
OpenGLES
};
enum class Alignment
enum class Alignment : u8
{
LeftOrTop,
Center,
@ -135,6 +135,7 @@ public:
const float GetDisplayAspectRatio() const { return m_display_aspect_ratio; }
void SetDisplayMaxFPS(float max_fps);
void SetDisplayDuplicateFrames(bool enabled) { m_display_duplicate_frames = enabled; }
bool ShouldSkipDisplayingFrame();
void ClearDisplayTexture()
@ -267,7 +268,7 @@ protected:
float m_display_frame_interval = 0.0f;
void* m_display_texture_handle = nullptr;
HostDisplayPixelFormat m_display_texture_format = HostDisplayPixelFormat::Count;
std::unique_ptr<HostDisplayTexture> m_cursor_texture;
s32 m_display_texture_width = 0;
s32 m_display_texture_height = 0;
s32 m_display_texture_view_x = 0;
@ -276,13 +277,14 @@ protected:
s32 m_display_texture_view_height = 0;
s32 m_display_top_margin = 0;
Alignment m_display_alignment = Alignment::Center;
std::unique_ptr<HostDisplayTexture> m_cursor_texture;
float m_cursor_texture_scale = 1.0f;
bool m_display_linear_filtering = false;
HostDisplayPixelFormat m_display_texture_format = HostDisplayPixelFormat::Count;
Alignment m_display_alignment = Alignment::Center;
bool m_display_changed = false;
bool m_display_linear_filtering = false;
bool m_display_integer_scaling = false;
bool m_display_stretch = false;
bool m_display_duplicate_frames = false;
};

View File

@ -539,6 +539,8 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si)
si.SetIntValue("Display", "LineEndOffset", 0);
si.SetStringValue("Display", "AspectRatio",
Settings::GetDisplayAspectRatioName(Settings::DEFAULT_DISPLAY_ASPECT_RATIO));
si.SetStringValue("Display", "FramePacingMode",
Settings::GetDisplayFramePacingModeName(Settings::DEFAULT_DISPLAY_FRAME_PACING_MODE));
si.SetBoolValue("Display", "Force4_3For24Bit", false);
si.SetBoolValue("Display", "LinearFiltering", true);
si.SetBoolValue("Display", "IntegerScaling", false);
@ -551,7 +553,6 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si)
si.SetBoolValue("Display", "ShowResolution", false);
si.SetBoolValue("Display", "Fullscreen", false);
si.SetBoolValue("Display", "VSync", true);
si.SetBoolValue("Display", "DisplayAllFrames", false);
si.SetStringValue("Display", "PostProcessChain", "");
si.SetFloatValue("Display", "MaxFPS", 0.0f);

View File

@ -227,7 +227,11 @@ void Settings::Load(SettingsInterface& si)
display_show_vps = si.GetBoolValue("Display", "ShowVPS", false);
display_show_speed = si.GetBoolValue("Display", "ShowSpeed", false);
display_show_resolution = si.GetBoolValue("Display", "ShowResolution", false);
display_all_frames = si.GetBoolValue("Display", "DisplayAllFrames", false);
display_frame_pacing_mode =
ParseDisplayFramePacingMode(
si.GetStringValue("Display", "FramePacingMode", GetDisplayFramePacingModeName(DEFAULT_DISPLAY_FRAME_PACING_MODE))
.c_str())
.value_or(DEFAULT_DISPLAY_FRAME_PACING_MODE);
video_sync_enabled = si.GetBoolValue("Display", "VSync", true);
display_post_process_chain = si.GetStringValue("Display", "PostProcessChain", "");
display_max_fps = si.GetFloatValue("Display", "MaxFPS", 0.0f);
@ -387,6 +391,7 @@ void Settings::Save(SettingsInterface& si) const
si.SetIntValue("Display", "LineEndOffset", display_line_end_offset);
si.SetBoolValue("Display", "Force4_3For24Bit", display_force_4_3_for_24bit);
si.SetStringValue("Display", "AspectRatio", GetDisplayAspectRatioName(display_aspect_ratio));
si.SetStringValue("Display", "FramePacingMode", GetDisplayAspectRatioName(display_aspect_ratio));
si.SetBoolValue("Display", "LinearFiltering", display_linear_filtering);
si.SetBoolValue("Display", "IntegerScaling", display_integer_scaling);
si.SetBoolValue("Display", "Stretch", display_stretch);
@ -396,7 +401,6 @@ void Settings::Save(SettingsInterface& si) const
si.SetBoolValue("Display", "ShowVPS", display_show_vps);
si.SetBoolValue("Display", "ShowSpeed", display_show_speed);
si.SetBoolValue("Display", "ShowResolution", display_show_resolution);
si.SetBoolValue("Display", "DisplayAllFrames", display_all_frames);
si.SetBoolValue("Display", "VSync", video_sync_enabled);
if (display_post_process_chain.empty())
si.DeleteValue("Display", "PostProcessChain");
@ -778,6 +782,38 @@ float Settings::GetDisplayAspectRatioValue(DisplayAspectRatio ar)
return s_display_aspect_ratio_values[static_cast<int>(ar)];
}
static std::array<const char*, 4> s_display_frame_pacing_mode_names = {
{"DisplayAllFrames", "DisplayAllFramesWhenPossible", "SkipDuplicateFrames", "ClampToMaxFPS"}};
static std::array<const char*, 4> s_display_frame_pacing_mode_display_names = {
{TRANSLATABLE("DisplayFramePacingMode", "Display All Frames"),
TRANSLATABLE("DisplayFramePacingMode", "Display All Frames When Possible"),
TRANSLATABLE("DisplayFramePacingMode", "Skip Duplicate Frames"),
TRANSLATABLE("DisplayFramePacingMode", "Clamp To Max FPS")}};
std::optional<DisplayFramePacingMode> Settings::ParseDisplayFramePacingMode(const char* str)
{
int index = 0;
for (const char* name : s_display_frame_pacing_mode_names)
{
if (StringUtil::Strcasecmp(name, str) == 0)
return static_cast<DisplayFramePacingMode>(index);
index++;
}
return std::nullopt;
}
const char* Settings::GetDisplayFramePacingModeName(DisplayFramePacingMode mode)
{
return s_display_frame_pacing_mode_names[static_cast<int>(mode)];
}
const char* Settings::GetDisplayFramePacingModeDisplayName(DisplayFramePacingMode mode)
{
return s_display_frame_pacing_mode_display_names[static_cast<int>(mode)];
}
static std::array<const char*, 3> s_audio_backend_names = {{
"Null",
"Cubeb",

View File

@ -127,6 +127,7 @@ struct Settings
bool gpu_pgxp_depth_buffer = false;
DisplayCropMode display_crop_mode = DisplayCropMode::None;
DisplayAspectRatio display_aspect_ratio = DisplayAspectRatio::Auto;
DisplayFramePacingMode display_frame_pacing_mode = DisplayFramePacingMode::DisplayAllFramesWhenPossible;
s16 display_active_start_offset = 0;
s16 display_active_end_offset = 0;
s8 display_line_start_offset = 0;
@ -142,7 +143,6 @@ struct Settings
bool display_show_vps = false;
bool display_show_speed = false;
bool display_show_resolution = false;
bool display_all_frames = false;
bool video_sync_enabled = true;
float display_max_fps = 0.0f;
float gpu_pgxp_tolerance = -1.0f;
@ -317,6 +317,10 @@ struct Settings
static const char* GetDisplayAspectRatioName(DisplayAspectRatio ar);
static float GetDisplayAspectRatioValue(DisplayAspectRatio ar);
static std::optional<DisplayFramePacingMode> ParseDisplayFramePacingMode(const char* str);
static const char* GetDisplayFramePacingModeName(DisplayFramePacingMode mode);
static const char* GetDisplayFramePacingModeDisplayName(DisplayFramePacingMode mode);
static std::optional<AudioBackend> ParseAudioBackend(const char* str);
static const char* GetAudioBackendName(AudioBackend backend);
static const char* GetAudioBackendDisplayName(AudioBackend backend);
@ -364,6 +368,8 @@ struct Settings
static constexpr DisplayCropMode DEFAULT_DISPLAY_CROP_MODE = DisplayCropMode::Overscan;
static constexpr DisplayAspectRatio DEFAULT_DISPLAY_ASPECT_RATIO = DisplayAspectRatio::Auto;
static constexpr DisplayFramePacingMode DEFAULT_DISPLAY_FRAME_PACING_MODE =
DisplayFramePacingMode::DisplayAllFramesWhenPossible;
static constexpr ControllerType DEFAULT_CONTROLLER_1_TYPE = ControllerType::DigitalController;
static constexpr ControllerType DEFAULT_CONTROLLER_2_TYPE = ControllerType::None;
static constexpr MemoryCardType DEFAULT_MEMORY_CARD_1_TYPE = MemoryCardType::PerGameTitle;

View File

@ -110,6 +110,15 @@ enum class DisplayAspectRatio : u8
Count
};
enum class DisplayFramePacingMode : u8
{
DisplayAllFrames,
DisplayAllFramesWhenPossible,
SkipDuplicateFrames,
ClampToFPS,
Count
};
enum class AudioBackend : u8
{
Null,

View File

@ -28,6 +28,9 @@ DisplaySettingsWidget::DisplaySettingsWidget(QtHostInterface* host_interface, QW
SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.displayCropMode, "Display", "CropMode",
&Settings::ParseDisplayCropMode, &Settings::GetDisplayCropModeName,
Settings::DEFAULT_DISPLAY_CROP_MODE);
SettingWidgetBinder::BindWidgetToEnumSetting(
m_host_interface, m_ui.framePacingMode, "Display", "FramePacingMode", &Settings::ParseDisplayFramePacingMode,
&Settings::GetDisplayFramePacingModeName, Settings::DEFAULT_DISPLAY_FRAME_PACING_MODE);
SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.gpuDownsampleMode, "GPU", "DownsampleMode",
&Settings::ParseDownsampleModeName, &Settings::GetDownsampleModeName,
Settings::DEFAULT_GPU_DOWNSAMPLE_MODE);
@ -39,8 +42,6 @@ DisplaySettingsWidget::DisplaySettingsWidget(QtHostInterface* host_interface, QW
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.internalResolutionScreenshots, "Display",
"InternalResolutionScreenshots", false);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.vsync, "Display", "VSync");
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.displayAllFrames, "Display", "DisplayAllFrames",
false);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.gpuThread, "GPU", "UseThread", true);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.threadedPresentation, "GPU",
"ThreadedPresentation", true);
@ -112,7 +113,7 @@ DisplaySettingsWidget::DisplaySettingsWidget(QtHostInterface* host_interface, QW
m_ui.vsync, tr("VSync"), tr("Checked"),
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.displayAllFrames, tr("Optimal Frame Pacing"), tr("Unchecked"),
dialog->registerWidgetHelp(m_ui.framePacingMode, tr("Optimal Frame Pacing"), tr("Unchecked"),
tr("Enable this option will ensure every frame the console renders is displayed to the "
"screen, for optimal frame pacing. If you are having difficulties maintaining full "
"speed, or are getting audio glitches, try disabling this option."));
@ -140,7 +141,7 @@ DisplaySettingsWidget::DisplaySettingsWidget(QtHostInterface* host_interface, QW
{
QCheckBox* cb = new QCheckBox(tr("Use Blit Swap Chain"), m_ui.basicGroupBox);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, cb, "Display", "UseBlitSwapChain", false);
m_ui.basicCheckboxGridLayout->addWidget(cb, 2, 0, 1, 1);
m_ui.basicCheckboxGridLayout->addWidget(cb, 1, 1, 1, 1);
dialog->registerWidgetHelp(cb, 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 "
@ -171,6 +172,13 @@ void DisplaySettingsWidget::setupAdditionalUi()
qApp->translate("DisplayCropMode", Settings::GetDisplayCropModeDisplayName(static_cast<DisplayCropMode>(i))));
}
for (u32 i = 0; i < static_cast<u32>(DisplayFramePacingMode::Count); i++)
{
m_ui.framePacingMode->addItem(
qApp->translate("DisplayFramePacingMode",
Settings::GetDisplayFramePacingModeDisplayName(static_cast<DisplayFramePacingMode>(i))));
}
for (u32 i = 0; i < static_cast<u32>(GPUDownsampleMode::Count); i++)
{
m_ui.gpuDownsampleMode->addItem(

View File

@ -62,7 +62,17 @@
<item row="2" column="1">
<widget class="QComboBox" name="fullscreenMode"/>
</item>
<item row="3" column="0" colspan="2">
<item row="3" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Frame Pacing Mode:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="framePacingMode"/>
</item>
<item row="4" column="0" colspan="2">
<layout class="QGridLayout" name="basicCheckboxGridLayout">
<item row="0" column="0">
<widget class="QCheckBox" name="gpuThread">
@ -85,13 +95,6 @@
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="displayAllFrames">
<property name="text">
<string>Optimal Frame Pacing</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>

View File

@ -792,7 +792,8 @@ void CommonHostInterface::UpdateSpeedLimiterState()
g_settings.turbo_speed :
(m_fast_forward_enabled ? g_settings.fast_forward_speed : g_settings.emulation_speed);
m_throttler_enabled = (target_speed != 0.0f);
m_display_all_frames = !m_throttler_enabled || g_settings.display_all_frames;
m_display_all_frames =
(!m_throttler_enabled || (g_settings.display_frame_pacing_mode == DisplayFramePacingMode::DisplayAllFrames));
bool syncing_to_host = false;
if (g_settings.sync_to_host_refresh_rate && g_settings.audio_resampling && target_speed == 1.0f && m_display &&
@ -815,12 +816,18 @@ void CommonHostInterface::UpdateSpeedLimiterState()
!System::IsRunning() || (m_throttler_enabled && g_settings.audio_sync_enabled && !is_non_standard_speed);
const bool video_sync_enabled =
!System::IsRunning() || (m_throttler_enabled && g_settings.video_sync_enabled && !is_non_standard_speed);
const float max_display_fps = (!System::IsValid() || m_throttler_enabled) ? 0.0f : g_settings.display_max_fps;
const float max_display_fps = (g_settings.display_frame_pacing_mode != DisplayFramePacingMode::ClampToFPS &&
(!System::IsValid() || m_throttler_enabled)) ?
0.0f :
g_settings.display_max_fps;
const bool display_duplicate_frames =
(g_settings.display_frame_pacing_mode != DisplayFramePacingMode::SkipDuplicateFrames) || !System::IsRunning();
Log_InfoPrintf("Target speed: %f%%", target_speed * 100.0f);
Log_InfoPrintf("Syncing to %s%s", audio_sync_enabled ? "audio" : "",
(audio_sync_enabled && video_sync_enabled) ? " and video" : (video_sync_enabled ? "video" : ""));
Log_InfoPrintf("Max display fps: %f (%s)", max_display_fps,
m_display_all_frames ? "displaying all frames" : "skipping displaying frames when needed");
Log_InfoPrintf("Max display fps: %f", max_display_fps);
Log_InfoPrint(m_display_all_frames ? "Displaying all frames" : "Skipping displaying frames when needed");
Log_InfoPrint(display_duplicate_frames ? "Displaying duplicate frames" : "Skipping displaying duplicate frames");
if (System::IsValid())
{
@ -979,7 +986,10 @@ void CommonHostInterface::OnSystemDestroyed()
{
// Restore present-all-frames behavior.
if (m_display)
{
m_display->SetDisplayMaxFPS(0.0f);
m_display->SetDisplayDuplicateFrames(true);
}
HostInterface::OnSystemDestroyed();
@ -2697,7 +2707,7 @@ void CommonHostInterface::CheckForSettingsChanges(const Settings& old_settings)
g_settings.emulation_speed != old_settings.emulation_speed ||
g_settings.fast_forward_speed != old_settings.fast_forward_speed ||
g_settings.display_max_fps != old_settings.display_max_fps ||
g_settings.display_all_frames != old_settings.display_all_frames ||
g_settings.display_frame_pacing_mode != old_settings.display_frame_pacing_mode ||
g_settings.audio_resampling != old_settings.audio_resampling ||
g_settings.sync_to_host_refresh_rate != old_settings.sync_to_host_refresh_rate)
{

View File

@ -706,6 +706,7 @@ bool D3D11HostDisplay::Render()
else
m_swap_chain->Present(BoolToUInt32(m_vsync), 0);
m_display_changed = false;
return true;
}

View File

@ -1895,10 +1895,12 @@ void DrawSettingsWindow()
break;
}
settings_changed |= ToggleButton("Optimal Frame Pacing",
"Ensures every frame generated is displayed for optimal pacing. Disable if "
"you are having speed or sound issues.",
&s_settings_copy.display_all_frames);
settings_changed |=
EnumChoiceButton("Frame Pacing Mode",
"Ensures every frame generated is displayed for optimal pacing. Disable if "
"you are having speed or sound issues.",
&s_settings_copy.display_frame_pacing_mode, &Settings::GetDisplayFramePacingModeDisplayName,
DisplayFramePacingMode::Count);
MenuHeading("Screen Display");

View File

@ -761,6 +761,7 @@ bool OpenGLHostDisplay::Render()
RenderSoftwareCursor();
m_gl_context->SwapBuffers();
m_display_changed = false;
return true;
}

View File

@ -636,6 +636,7 @@ bool VulkanHostDisplay::Render()
m_swap_chain->GetRenderingFinishedSemaphore(), m_swap_chain->GetSwapChain(),
m_swap_chain->GetCurrentImageIndex(), !m_swap_chain->IsVSyncEnabled());
g_vulkan_context->MoveToNextCommandBuffer();
m_display_changed = false;
return true;
}