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.
This commit is contained in:
Billy Laws 2022-10-21 21:53:26 +01:00
parent e45e7546c8
commit 6c0f084aae
8 changed files with 31 additions and 1 deletions

View File

@ -40,6 +40,7 @@ namespace skyline {
gpuDriver = ktSettings.GetString("gpuDriver"); gpuDriver = ktSettings.GetString("gpuDriver");
gpuDriverLibraryName = ktSettings.GetString("gpuDriverLibraryName"); gpuDriverLibraryName = ktSettings.GetString("gpuDriverLibraryName");
executorSlotCount = ktSettings.GetInt<u32>("executorSlotCount"); executorSlotCount = ktSettings.GetInt<u32>("executorSlotCount");
enableTextureReadbackHack = ktSettings.GetBool("enableTextureReadbackHack");
validationLayer = ktSettings.GetBool("validationLayer"); validationLayer = ktSettings.GetBool("validationLayer");
}; };
}; };

View File

@ -72,6 +72,7 @@ namespace skyline {
Setting<std::string> gpuDriver; //!< The label of the GPU driver to use Setting<std::string> gpuDriver; //!< The label of the GPU driver to use
Setting<std::string> gpuDriverLibraryName; //!< The name of the GPU driver library to use Setting<std::string> gpuDriverLibraryName; //!< The name of the GPU driver library to use
Setting<u32> executorSlotCount; //!< Number of GPU executor slots that can be used concurrently Setting<u32> executorSlotCount; //!< Number of GPU executor slots that can be used concurrently
Setting<bool> enableTextureReadbackHack; //!< If the CPU texture readback skipping hack should be used
// Debug // Debug
Setting<bool> validationLayer; //!< If the vulkan validation layer is enabled Setting<bool> validationLayer; //!< If the vulkan validation layer is enabled

View File

@ -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 // If this mutex would cause other callbacks to be blocked then we should block on this mutex in advance
std::shared_ptr<FenceCycle> waitCycle{}; std::shared_ptr<FenceCycle> waitCycle{};
do { 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(); waitCycle->Wait();
if (startNs)
texture->accumulatedGuestWaitTime += std::chrono::nanoseconds(util::GetTimeNs() - startNs);
texture->accumulatedGuestWaitCounter++;
}
std::scoped_lock lock{*texture}; std::scoped_lock lock{*texture};
if (waitCycle && texture->cycle == waitCycle) { 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 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}; std::unique_lock lock{*texture, std::try_to_lock};
if (!lock) if (!lock)
return false; return false;

View File

@ -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 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{}; 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: public:
std::shared_ptr<FenceCycle> 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::shared_ptr<FenceCycle> 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<GuestTexture> guest; std::optional<GuestTexture> guest;

View File

@ -26,6 +26,7 @@ class NativeSettings(context : Context, pref : PreferenceSettings) {
var gpuDriver : String = if (pref.gpuDriver == PreferenceSettings.SYSTEM_GPU_DRIVER) "" else pref.gpuDriver 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 gpuDriverLibraryName : String = if (pref.gpuDriver == PreferenceSettings.SYSTEM_GPU_DRIVER) "" else GpuDriverHelper.getLibraryName(context, pref.gpuDriver)
var executorSlotCount : Int = pref.executorSlotCount var executorSlotCount : Int = pref.executorSlotCount
var enableTextureReadbackHack : Boolean = pref.enableTextureReadbackHack
// Debug // Debug
var validationLayer : Boolean = BuildConfig.BUILD_TYPE != "release" && pref.validationLayer var validationLayer : Boolean = BuildConfig.BUILD_TYPE != "release" && pref.validationLayer

View File

@ -39,6 +39,7 @@ class PreferenceSettings @Inject constructor(@ApplicationContext private val con
// GPU // GPU
var gpuDriver by sharedPreferences(context, SYSTEM_GPU_DRIVER) var gpuDriver by sharedPreferences(context, SYSTEM_GPU_DRIVER)
var executorSlotCount by sharedPreferences(context, 6) var executorSlotCount by sharedPreferences(context, 6)
var enableTextureReadbackHack by sharedPreferences(context, false)
// Debug // Debug
var validationLayer by sharedPreferences(context, false) var validationLayer by sharedPreferences(context, false)

View File

@ -72,6 +72,9 @@
<string name="respect_display_cutout_disabled">Allow UI elements to be drawn in the cutout area</string> <string name="respect_display_cutout_disabled">Allow UI elements to be drawn in the cutout area</string>
<string name="executor_slot_count">Executor Slot Count</string> <string name="executor_slot_count">Executor Slot Count</string>
<string name="executor_slot_count_desc">Maximum number of simultaneous GPU executions (Higher may sometimes perform better but will use more RAM)</string> <string name="executor_slot_count_desc">Maximum number of simultaneous GPU executions (Higher may sometimes perform better but will use more RAM)</string>
<string name="enable_texture_readback_hack">Enable Texture Readback Hack</string>
<string name="enable_texture_readback_hack_enabled">Texture readback hack is enabled (Will break some games but others will have higher performance)</string>
<string name="enable_texture_readback_hack_disabled">Texture readback hack is disabled (Ensures highest accuracy)</string>
<!-- Settings - Debug --> <!-- Settings - Debug -->
<string name="debug">Debug</string> <string name="debug">Debug</string>
<string name="validation_layer">Enable validation layer</string> <string name="validation_layer">Enable validation layer</string>

View File

@ -135,6 +135,12 @@
app:key="executor_slot_count" app:key="executor_slot_count"
app:title="@string/executor_slot_count" app:title="@string/executor_slot_count"
app:showSeekBarValue="true" /> app:showSeekBarValue="true" />
<CheckBoxPreference
android:defaultValue="false"
android:summaryOff="@string/enable_texture_readback_hack_disabled"
android:summaryOn="@string/enable_texture_readback_hack_enabled"
app:key="enable_texture_readback_hack"
app:title="@string/enable_texture_readback_hack" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory
android:key="category_debug" android:key="category_debug"