diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
index 119517d99b..f6c222479c 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
@@ -51,7 +51,6 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
ENABLE_RAII("enable_raii"),
FRAME_INTERPOLATION("frame_interpolation"),
-// FRAME_SKIPPING("frame_skipping"),
PERF_OVERLAY_BACKGROUND("perf_overlay_background"),
SHOW_PERFORMANCE_OVERLAY("show_performance_overlay"),
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt
index 02950484ac..e8ccf8ad0a 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt
@@ -41,6 +41,7 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
FSR_SHARPENING_SLIDER("fsr_sharpening_slider"),
FAST_CPU_TIME("fast_cpu_time"),
CPU_TICKS("cpu_ticks"),
+ FRAME_SKIPPING("frame_skipping"),
FAST_GPU_TIME("fast_gpu_time"),
BAT_TEMPERATURE_UNIT("bat_temperature_unit"),
CABINET_APPLET("cabinet_applet_mode"),
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
index 13dd32fcdd..e324ecf049 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
@@ -234,15 +234,15 @@ abstract class SettingsItem(
descriptionId = R.string.frame_interpolation_description
)
)
-
-// put(
-// SwitchSetting(
-// BooleanSetting.FRAME_SKIPPING,
-// titleId = R.string.frame_skipping,
-// descriptionId = R.string.frame_skipping_description
-// )
-// )
-
+ put(
+ SingleChoiceSetting(
+ IntSetting.FRAME_SKIPPING,
+ titleId = R.string.frame_skipping,
+ descriptionId = R.string.frame_skipping_description,
+ choicesId = R.array.frameSkippingNames,
+ valuesId = R.array.frameSkippingValues
+ )
+ )
put(
SwitchSetting(
dockedModeSetting,
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
index f4b6f1b4ed..46091fb62d 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
@@ -449,6 +449,7 @@ class SettingsFragmentPresenter(
add(BooleanSetting.RENDERER_EARLY_RELEASE_FENCES.key)
add(BooleanSetting.BUFFER_REORDER_DISABLE.key)
add(BooleanSetting.FRAME_INTERPOLATION.key)
+ add(IntSetting.FRAME_SKIPPING.key)
add(BooleanSetting.RENDERER_FAST_GPU.key)
add(IntSetting.FAST_GPU_TIME.key)
add(IntSetting.RENDERER_SHADER_BACKEND.key)
diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml
index 602003cf8a..a5ab9bc3d9 100644
--- a/src/android/app/src/main/res/values/arrays.xml
+++ b/src/android/app/src/main/res/values/arrays.xml
@@ -21,6 +21,18 @@
- 1
+
+ - @string/disabled
+ - 1
+ - 2
+
+
+
+ - 0
+ - 1
+ - 2
+
+
- @string/memory_4gb
- @string/memory_6gb
diff --git a/src/common/settings.h b/src/common/settings.h
index 41c042bcf1..74a70afa99 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -325,9 +325,9 @@ struct Values {
#ifdef __ANDROID__
SwitchableSetting frame_interpolation{linkage, true, "frame_interpolation", Category::Renderer,
Specialization::RuntimeList};
+#endif
SwitchableSetting frame_skipping{linkage, false, "frame_skipping", Category::Renderer,
Specialization::RuntimeList};
-#endif
SwitchableSetting use_disk_shader_cache{linkage, true, "use_disk_shader_cache",
Category::Renderer};
SwitchableSetting optimize_spirv_output{linkage,
diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp
index d97620ce91..327536661c 100644
--- a/src/video_core/gpu.cpp
+++ b/src/video_core/gpu.cpp
@@ -223,17 +223,23 @@ struct GPU::Impl {
system.GetPerfStats().EndGameFrame();
}
+ void ResetFrameCounter() {
+ frame_count.store(0, std::memory_order_relaxed);
+ }
+
/// Performs any additional setup necessary in order to begin GPU emulation.
/// This can be used to launch any necessary threads and register any necessary
/// core timing events.
void Start() {
Settings::UpdateGPUAccuracy();
+ ResetFrameCounter();
gpu_thread.StartThread(*renderer, renderer->Context(), *scheduler);
}
void NotifyShutdown() {
std::unique_lock lk{sync_mutex};
shutting_down.store(true, std::memory_order::relaxed);
+ ResetFrameCounter();
sync_cv.notify_all();
}
@@ -291,10 +297,16 @@ struct GPU::Impl {
void RequestComposite(std::vector&& layers,
std::vector&& fences) {
+ // Increment frame counter
+ const u64 current_frame = frame_count.fetch_add(1, std::memory_order_relaxed) + 1;
+ const u32 frame_skip_value = Settings::values.frame_skipping.GetValue();
+ const bool skip_frame = frame_skip_value > 0 &&
+ (current_frame % (static_cast(frame_skip_value) + 1) != 0);
+
size_t num_fences{fences.size()};
size_t current_request_counter{};
{
- std::unique_lock lk(request_swap_mutex);
+ std::unique_lock lk{request_swap_mutex};
if (free_swap_counters.empty()) {
current_request_counter = request_swap_counters.size();
request_swap_counters.emplace_back(num_fences);
@@ -305,23 +317,33 @@ struct GPU::Impl {
}
}
const auto wait_fence =
- RequestSyncOperation([this, current_request_counter, &layers, &fences, num_fences] {
+ RequestSyncOperation([this, current_request_counter, &layers, &fences, num_fences, skip_frame] {
auto& syncpoint_manager = host1x.GetSyncpointManager();
if (num_fences == 0) {
- renderer->Composite(layers);
- }
- const auto executer = [this, current_request_counter, layers_copy = layers]() {
- {
- std::unique_lock lk(request_swap_mutex);
- if (--request_swap_counters[current_request_counter] != 0) {
- return;
- }
- free_swap_counters.push_back(current_request_counter);
+ if (!skip_frame) {
+ renderer->Composite(layers);
+ } else {
+ system.GetPerfStats().EndGameFrame();
+ }
+ } else {
+ const auto executer = [this, current_request_counter, layers_copy = std::move(layers), skip_frame]() {
+ {
+ std::unique_lock lk{request_swap_mutex};
+ if (--request_swap_counters[current_request_counter] != 0) {
+ return;
+ }
+ free_swap_counters.push_back(current_request_counter);
+ }
+ // Handle frame skipping in executer
+ if (!skip_frame) {
+ renderer->Composite(layers_copy);
+ } else {
+ system.GetPerfStats().EndGameFrame();
+ }
+ };
+ for (size_t i = 0; i < num_fences; i++) {
+ syncpoint_manager.RegisterGuestAction(fences[i].id, fences[i].value, executer);
}
- renderer->Composite(layers_copy);
- };
- for (size_t i = 0; i < num_fences; i++) {
- syncpoint_manager.RegisterGuestAction(fences[i].id, fences[i].value, executer);
}
});
gpu_thread.TickGPU();
@@ -353,6 +375,8 @@ struct GPU::Impl {
/// When true, we are about to shut down emulation session, so terminate outstanding tasks
std::atomic_bool shutting_down{};
+ std::atomic frame_count{0};
+
std::array, Service::Nvidia::MaxSyncPoints> syncpoints{};
std::array, Service::Nvidia::MaxSyncPoints> syncpt_interrupts;
diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h
index 538c4da85a..b8acd98a60 100644
--- a/src/video_core/gpu.h
+++ b/src/video_core/gpu.h
@@ -220,6 +220,9 @@ public:
std::vector GetAppletCaptureBuffer();
+ // Reset frame counter
+ void ResetFrameCounter();
+
/// Performs any additional setup necessary in order to begin GPU emulation.
/// This can be used to launch any necessary threads and register any necessary
/// core timing events.