From 6c0f084aae15de35a2fe6f245a5358ae6e609f36 Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Fri, 21 Oct 2022 21:53:26 +0100 Subject: [PATCH] Introduce hack to ignore frequently read-back textures Readback can be especially slow on mobile due to the varying load pattern it creates which often prevents the CPU/GPU from clocking up. Since some games perform texture readback but don't actually use it for anything significant implement a hack to skip it and significantly improve performance in such cases. --- app/src/main/cpp/skyline/common/android_settings.h | 1 + app/src/main/cpp/skyline/common/settings.h | 1 + app/src/main/cpp/skyline/gpu/texture/texture.cpp | 14 +++++++++++++- app/src/main/cpp/skyline/gpu/texture/texture.h | 5 +++++ .../main/java/emu/skyline/utils/NativeSettings.kt | 1 + .../java/emu/skyline/utils/PreferenceSettings.kt | 1 + app/src/main/res/values/strings.xml | 3 +++ app/src/main/res/xml/preferences.xml | 6 ++++++ 8 files changed, 31 insertions(+), 1 deletion(-) diff --git a/app/src/main/cpp/skyline/common/android_settings.h b/app/src/main/cpp/skyline/common/android_settings.h index 14472209..249d169b 100644 --- a/app/src/main/cpp/skyline/common/android_settings.h +++ b/app/src/main/cpp/skyline/common/android_settings.h @@ -40,6 +40,7 @@ namespace skyline { gpuDriver = ktSettings.GetString("gpuDriver"); gpuDriverLibraryName = ktSettings.GetString("gpuDriverLibraryName"); executorSlotCount = ktSettings.GetInt("executorSlotCount"); + enableTextureReadbackHack = ktSettings.GetBool("enableTextureReadbackHack"); validationLayer = ktSettings.GetBool("validationLayer"); }; }; diff --git a/app/src/main/cpp/skyline/common/settings.h b/app/src/main/cpp/skyline/common/settings.h index c128bcef..8fd0354b 100644 --- a/app/src/main/cpp/skyline/common/settings.h +++ b/app/src/main/cpp/skyline/common/settings.h @@ -72,6 +72,7 @@ namespace skyline { Setting gpuDriver; //!< The label of the GPU driver to use Setting gpuDriverLibraryName; //!< The name of the GPU driver library to use Setting executorSlotCount; //!< Number of GPU executor slots that can be used concurrently + Setting enableTextureReadbackHack; //!< If the CPU texture readback skipping hack should be used // Debug Setting validationLayer; //!< If the vulkan validation layer is enabled diff --git a/app/src/main/cpp/skyline/gpu/texture/texture.cpp b/app/src/main/cpp/skyline/gpu/texture/texture.cpp index 0bea16db..ba4b48e1 100644 --- a/app/src/main/cpp/skyline/gpu/texture/texture.cpp +++ b/app/src/main/cpp/skyline/gpu/texture/texture.cpp @@ -167,8 +167,15 @@ namespace skyline::gpu { // If this mutex would cause other callbacks to be blocked then we should block on this mutex in advance std::shared_ptr waitCycle{}; do { - if (waitCycle) + // We need to do a loop here since we can't wait with the texture locked but not doing so means that the texture could have it's cycle changed which we wouldn't wait on, loop until we are sure the cycle hasn't changed to avoid that + if (waitCycle) { + i64 startNs{texture->accumulatedGuestWaitCounter > SkipReadbackHackWaitCountThreshold ? util::GetTimeNs() : 0}; waitCycle->Wait(); + if (startNs) + texture->accumulatedGuestWaitTime += std::chrono::nanoseconds(util::GetTimeNs() - startNs); + + texture->accumulatedGuestWaitCounter++; + } std::scoped_lock lock{*texture}; if (waitCycle && texture->cycle == waitCycle) { @@ -218,6 +225,11 @@ namespace skyline::gpu { return true; // If the texture is already CPU dirty or we can transition it to being CPU dirty then we don't need to do anything } + if (texture->accumulatedGuestWaitTime > SkipReadbackHackWaitTimeThreshold && *texture->gpu.state.settings->enableTextureReadbackHack) { + texture->dirtyState = DirtyState::Clean; + return true; + } + std::unique_lock lock{*texture, std::try_to_lock}; if (!lock) return false; diff --git a/app/src/main/cpp/skyline/gpu/texture/texture.h b/app/src/main/cpp/skyline/gpu/texture/texture.h index 0eb1d42a..b8b38150 100644 --- a/app/src/main/cpp/skyline/gpu/texture/texture.h +++ b/app/src/main/cpp/skyline/gpu/texture/texture.h @@ -440,6 +440,11 @@ namespace skyline::gpu { static constexpr size_t FrequentlyLockedThreshold{2}; //!< Threshold for the number of times a texture can be locked (not from context locks, only normal) before it should be considered frequently locked size_t accumulatedCpuLockCounter{}; + static constexpr size_t SkipReadbackHackWaitCountThreshold{8}; //!< Threshold for the number of times a texture can be waited on before it should be considered for the readback hack + static constexpr std::chrono::nanoseconds SkipReadbackHackWaitTimeThreshold{constant::NsInSecond / 2}; //!< Threshold for the amount of time a texture can be waited on before it should be considered for the readback hack, `SkipReadbackHackWaitCountThreshold` needs to be hit before this + size_t accumulatedGuestWaitCounter{}; //!< Total number of times the texture has been waited on + std::chrono::nanoseconds accumulatedGuestWaitTime{}; //!< Amount of time the texture has been waited on for since the `SkipReadbackHackWaitCountThreshold`th wait on it by the guest + public: std::shared_ptr cycle; //!< A fence cycle for when any host operation mutating the texture has completed, it must be waited on prior to any mutations to the backing std::optional guest; diff --git a/app/src/main/java/emu/skyline/utils/NativeSettings.kt b/app/src/main/java/emu/skyline/utils/NativeSettings.kt index bd60e259..90d3b6f0 100644 --- a/app/src/main/java/emu/skyline/utils/NativeSettings.kt +++ b/app/src/main/java/emu/skyline/utils/NativeSettings.kt @@ -26,6 +26,7 @@ class NativeSettings(context : Context, pref : PreferenceSettings) { var gpuDriver : String = if (pref.gpuDriver == PreferenceSettings.SYSTEM_GPU_DRIVER) "" else pref.gpuDriver var gpuDriverLibraryName : String = if (pref.gpuDriver == PreferenceSettings.SYSTEM_GPU_DRIVER) "" else GpuDriverHelper.getLibraryName(context, pref.gpuDriver) var executorSlotCount : Int = pref.executorSlotCount + var enableTextureReadbackHack : Boolean = pref.enableTextureReadbackHack // Debug var validationLayer : Boolean = BuildConfig.BUILD_TYPE != "release" && pref.validationLayer diff --git a/app/src/main/java/emu/skyline/utils/PreferenceSettings.kt b/app/src/main/java/emu/skyline/utils/PreferenceSettings.kt index 8c490322..c2b5cc6d 100644 --- a/app/src/main/java/emu/skyline/utils/PreferenceSettings.kt +++ b/app/src/main/java/emu/skyline/utils/PreferenceSettings.kt @@ -39,6 +39,7 @@ class PreferenceSettings @Inject constructor(@ApplicationContext private val con // GPU var gpuDriver by sharedPreferences(context, SYSTEM_GPU_DRIVER) var executorSlotCount by sharedPreferences(context, 6) + var enableTextureReadbackHack by sharedPreferences(context, false) // Debug var validationLayer by sharedPreferences(context, false) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a25dd499..d869e628 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -72,6 +72,9 @@ Allow UI elements to be drawn in the cutout area Executor Slot Count Maximum number of simultaneous GPU executions (Higher may sometimes perform better but will use more RAM) + Enable Texture Readback Hack + Texture readback hack is enabled (Will break some games but others will have higher performance) + Texture readback hack is disabled (Ensures highest accuracy) Debug Enable validation layer diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index f4a7cc0a..e409e232 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -135,6 +135,12 @@ app:key="executor_slot_count" app:title="@string/executor_slot_count" app:showSeekBarValue="true" /> +