android: video_core: Add support for disk shader cache. (#64)
This commit is contained in:
		| @@ -42,6 +42,7 @@ object NativeLibrary { | |||||||
|     const val Player8Device = 7 |     const val Player8Device = 7 | ||||||
|     const val ConsoleDevice = 8 |     const val ConsoleDevice = 8 | ||||||
|  |  | ||||||
|  |     @JvmField | ||||||
|     var sEmulationActivity = WeakReference<EmulationActivity?>(null) |     var sEmulationActivity = WeakReference<EmulationActivity?>(null) | ||||||
|  |  | ||||||
|     init { |     init { | ||||||
|   | |||||||
| @@ -0,0 +1,46 @@ | |||||||
|  | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||||||
|  | // SPDX-License-Identifier: GPL-2.0-or-later | ||||||
|  |  | ||||||
|  | package org.yuzu.yuzu_emu.disk_shader_cache | ||||||
|  |  | ||||||
|  | import org.yuzu.yuzu_emu.NativeLibrary | ||||||
|  | import org.yuzu.yuzu_emu.R | ||||||
|  | import org.yuzu.yuzu_emu.disk_shader_cache.ui.ShaderProgressDialogFragment | ||||||
|  |  | ||||||
|  | object DiskShaderCacheProgress { | ||||||
|  |     val finishLock = Object() | ||||||
|  |     private lateinit var fragment: ShaderProgressDialogFragment | ||||||
|  |  | ||||||
|  |     private fun prepareDialog() { | ||||||
|  |         val emulationActivity = NativeLibrary.sEmulationActivity.get()!! | ||||||
|  |         emulationActivity.runOnUiThread { | ||||||
|  |             fragment = ShaderProgressDialogFragment.newInstance( | ||||||
|  |                 emulationActivity.getString(R.string.loading), | ||||||
|  |                 emulationActivity.getString(R.string.preparing_shaders) | ||||||
|  |             ) | ||||||
|  |             fragment.show(emulationActivity.supportFragmentManager, ShaderProgressDialogFragment.TAG) | ||||||
|  |         } | ||||||
|  |         synchronized(finishLock) { finishLock.wait() } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @JvmStatic | ||||||
|  |     fun loadProgress(stage: Int, progress: Int, max: Int) { | ||||||
|  |         val emulationActivity = NativeLibrary.sEmulationActivity.get() | ||||||
|  |             ?: error("[DiskShaderCacheProgress] EmulationActivity not present") | ||||||
|  |  | ||||||
|  |         when (LoadCallbackStage.values()[stage]) { | ||||||
|  |             LoadCallbackStage.Prepare -> prepareDialog() | ||||||
|  |             LoadCallbackStage.Build -> fragment.onUpdateProgress( | ||||||
|  |                 emulationActivity.getString(R.string.building_shaders), | ||||||
|  |                 progress, | ||||||
|  |                 max | ||||||
|  |             ) | ||||||
|  |             LoadCallbackStage.Complete -> fragment.dismiss() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Equivalent to VideoCore::LoadCallbackStage | ||||||
|  |     enum class LoadCallbackStage { | ||||||
|  |         Prepare, Build, Complete | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,31 @@ | |||||||
|  | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||||||
|  | // SPDX-License-Identifier: GPL-2.0-or-later | ||||||
|  |  | ||||||
|  | package org.yuzu.yuzu_emu.disk_shader_cache | ||||||
|  |  | ||||||
|  | import androidx.lifecycle.LiveData | ||||||
|  | import androidx.lifecycle.MutableLiveData | ||||||
|  | import androidx.lifecycle.ViewModel | ||||||
|  |  | ||||||
|  | class ShaderProgressViewModel : ViewModel() { | ||||||
|  |     private val _progress = MutableLiveData(0) | ||||||
|  |     val progress: LiveData<Int> get() = _progress | ||||||
|  |  | ||||||
|  |     private val _max = MutableLiveData(0) | ||||||
|  |     val max: LiveData<Int> get() = _max | ||||||
|  |  | ||||||
|  |     private val _message = MutableLiveData("") | ||||||
|  |     val message: LiveData<String> get() = _message | ||||||
|  |  | ||||||
|  |     fun setProgress(progress: Int) { | ||||||
|  |         _progress.postValue(progress) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun setMax(max: Int) { | ||||||
|  |         _max.postValue(max) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun setMessage(msg: String) { | ||||||
|  |         _message.postValue(msg) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,101 @@ | |||||||
|  | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||||||
|  | // SPDX-License-Identifier: GPL-2.0-or-later | ||||||
|  |  | ||||||
|  | package org.yuzu.yuzu_emu.disk_shader_cache.ui | ||||||
|  |  | ||||||
|  | import android.app.Dialog | ||||||
|  | import android.os.Bundle | ||||||
|  | import android.view.LayoutInflater | ||||||
|  | import android.view.View | ||||||
|  | import android.view.ViewGroup | ||||||
|  | import androidx.appcompat.app.AlertDialog | ||||||
|  | import androidx.fragment.app.DialogFragment | ||||||
|  | import androidx.lifecycle.ViewModelProvider | ||||||
|  | import com.google.android.material.dialog.MaterialAlertDialogBuilder | ||||||
|  | import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding | ||||||
|  | import org.yuzu.yuzu_emu.disk_shader_cache.DiskShaderCacheProgress | ||||||
|  | import org.yuzu.yuzu_emu.disk_shader_cache.ShaderProgressViewModel | ||||||
|  |  | ||||||
|  | class ShaderProgressDialogFragment : DialogFragment() { | ||||||
|  |     private var _binding: DialogProgressBarBinding? = null | ||||||
|  |     private val binding get() = _binding!! | ||||||
|  |  | ||||||
|  |     private lateinit var alertDialog: AlertDialog | ||||||
|  |  | ||||||
|  |     private lateinit var shaderProgressViewModel: ShaderProgressViewModel | ||||||
|  |  | ||||||
|  |     override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { | ||||||
|  |         _binding = DialogProgressBarBinding.inflate(layoutInflater) | ||||||
|  |         shaderProgressViewModel = | ||||||
|  |             ViewModelProvider(requireActivity())[ShaderProgressViewModel::class.java] | ||||||
|  |  | ||||||
|  |         val title = requireArguments().getString(TITLE) | ||||||
|  |         val message = requireArguments().getString(MESSAGE) | ||||||
|  |  | ||||||
|  |         isCancelable = false | ||||||
|  |         alertDialog = MaterialAlertDialogBuilder(requireActivity()) | ||||||
|  |             .setView(binding.root) | ||||||
|  |             .setTitle(title) | ||||||
|  |             .setMessage(message) | ||||||
|  |             .create() | ||||||
|  |         return alertDialog | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun onCreateView( | ||||||
|  |         inflater: LayoutInflater, | ||||||
|  |         container: ViewGroup?, | ||||||
|  |         savedInstanceState: Bundle? | ||||||
|  |     ): View { | ||||||
|  |         return binding.root | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||||||
|  |         super.onViewCreated(view, savedInstanceState) | ||||||
|  |         shaderProgressViewModel.progress.observe(viewLifecycleOwner) { progress -> | ||||||
|  |             binding.progressBar.progress = progress | ||||||
|  |             setUpdateText() | ||||||
|  |         } | ||||||
|  |         shaderProgressViewModel.max.observe(viewLifecycleOwner) { max -> | ||||||
|  |             binding.progressBar.max = max | ||||||
|  |             setUpdateText() | ||||||
|  |         } | ||||||
|  |         shaderProgressViewModel.message.observe(viewLifecycleOwner) { msg -> | ||||||
|  |             alertDialog.setMessage(msg) | ||||||
|  |         } | ||||||
|  |         synchronized(DiskShaderCacheProgress.finishLock) { DiskShaderCacheProgress.finishLock.notifyAll() } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun onDestroyView() { | ||||||
|  |         super.onDestroyView() | ||||||
|  |         _binding = null | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun onUpdateProgress(msg: String, progress: Int, max: Int) { | ||||||
|  |         shaderProgressViewModel.setProgress(progress) | ||||||
|  |         shaderProgressViewModel.setMax(max) | ||||||
|  |         shaderProgressViewModel.setMessage(msg) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun setUpdateText() { | ||||||
|  |         binding.progressText.text = String.format( | ||||||
|  |             "%d/%d", | ||||||
|  |             shaderProgressViewModel.progress.value, | ||||||
|  |             shaderProgressViewModel.max.value | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |         const val TAG = "ProgressDialogFragment" | ||||||
|  |         const val TITLE = "title" | ||||||
|  |         const val MESSAGE = "message" | ||||||
|  |  | ||||||
|  |         fun newInstance(title: String, message: String): ShaderProgressDialogFragment { | ||||||
|  |             val frag = ShaderProgressDialogFragment() | ||||||
|  |             val args = Bundle() | ||||||
|  |             args.putString(TITLE, title) | ||||||
|  |             args.putString(MESSAGE, message) | ||||||
|  |             frag.arguments = args | ||||||
|  |             return frag | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -196,6 +196,8 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) | |||||||
|         val rendererResolution = rendererSection.getSetting(SettingsFile.KEY_RENDERER_RESOLUTION) |         val rendererResolution = rendererSection.getSetting(SettingsFile.KEY_RENDERER_RESOLUTION) | ||||||
|         val rendererAspectRatio = |         val rendererAspectRatio = | ||||||
|             rendererSection.getSetting(SettingsFile.KEY_RENDERER_ASPECT_RATIO) |             rendererSection.getSetting(SettingsFile.KEY_RENDERER_ASPECT_RATIO) | ||||||
|  |         val rendererUseDiskShaderCache = | ||||||
|  |             rendererSection.getSetting(SettingsFile.KEY_RENDERER_USE_DISK_SHADER_CACHE) | ||||||
|         val rendererForceMaxClocks = |         val rendererForceMaxClocks = | ||||||
|             rendererSection.getSetting(SettingsFile.KEY_RENDERER_FORCE_MAX_CLOCK) |             rendererSection.getSetting(SettingsFile.KEY_RENDERER_FORCE_MAX_CLOCK) | ||||||
|         val rendererAsynchronousShaders = |         val rendererAsynchronousShaders = | ||||||
| @@ -250,6 +252,16 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) | |||||||
|                     0 |                     0 | ||||||
|                 ) |                 ) | ||||||
|             ) |             ) | ||||||
|  |             add( | ||||||
|  |                 SwitchSetting( | ||||||
|  |                     SettingsFile.KEY_RENDERER_USE_DISK_SHADER_CACHE, | ||||||
|  |                     Settings.SECTION_RENDERER, | ||||||
|  |                     rendererUseDiskShaderCache, | ||||||
|  |                     R.string.use_disk_shader_cache, | ||||||
|  |                     R.string.use_disk_shader_cache_description, | ||||||
|  |                     true | ||||||
|  |                 ) | ||||||
|  |             ) | ||||||
|             add( |             add( | ||||||
|                 SwitchSetting( |                 SwitchSetting( | ||||||
|                     SettingsFile.KEY_RENDERER_FORCE_MAX_CLOCK, |                     SettingsFile.KEY_RENDERER_FORCE_MAX_CLOCK, | ||||||
|   | |||||||
| @@ -36,6 +36,7 @@ object SettingsFile { | |||||||
|     const val KEY_RENDERER_RESOLUTION = "resolution_setup" |     const val KEY_RENDERER_RESOLUTION = "resolution_setup" | ||||||
|     const val KEY_RENDERER_ASPECT_RATIO = "aspect_ratio" |     const val KEY_RENDERER_ASPECT_RATIO = "aspect_ratio" | ||||||
|     const val KEY_RENDERER_ACCURACY = "gpu_accuracy" |     const val KEY_RENDERER_ACCURACY = "gpu_accuracy" | ||||||
|  |     const val KEY_RENDERER_USE_DISK_SHADER_CACHE = "use_disk_shader_cache" | ||||||
|     const val KEY_RENDERER_ASYNCHRONOUS_SHADERS = "use_asynchronous_shaders" |     const val KEY_RENDERER_ASYNCHRONOUS_SHADERS = "use_asynchronous_shaders" | ||||||
|     const val KEY_RENDERER_FORCE_MAX_CLOCK = "force_max_clock" |     const val KEY_RENDERER_FORCE_MAX_CLOCK = "force_max_clock" | ||||||
|     const val KEY_RENDERER_USE_SPEED_LIMIT = "use_speed_limit" |     const val KEY_RENDERER_USE_SPEED_LIMIT = "use_speed_limit" | ||||||
|   | |||||||
| @@ -226,10 +226,6 @@ void Config::ReadValues() { | |||||||
|     ReadSetting("Renderer", Settings::values.bg_green); |     ReadSetting("Renderer", Settings::values.bg_green); | ||||||
|     ReadSetting("Renderer", Settings::values.bg_blue); |     ReadSetting("Renderer", Settings::values.bg_blue); | ||||||
|  |  | ||||||
|     // Disable shader cache by default on Android |  | ||||||
|     Settings::values.use_disk_shader_cache = |  | ||||||
|         config->GetBoolean("Renderer", "use_disk_shader_cache", false); |  | ||||||
|  |  | ||||||
|     // Enable force_max_clock by default on Android |     // Enable force_max_clock by default on Android | ||||||
|     Settings::values.renderer_force_max_clock = |     Settings::values.renderer_force_max_clock = | ||||||
|         config->GetBoolean("Renderer", "force_max_clock", true); |         config->GetBoolean("Renderer", "force_max_clock", true); | ||||||
|   | |||||||
| @@ -3,13 +3,18 @@ | |||||||
|  |  | ||||||
| #include <jni.h> | #include <jni.h> | ||||||
|  |  | ||||||
|  | #include "common/assert.h" | ||||||
| #include "common/fs/fs_android.h" | #include "common/fs/fs_android.h" | ||||||
| #include "jni/applets/software_keyboard.h" | #include "jni/applets/software_keyboard.h" | ||||||
| #include "jni/id_cache.h" | #include "jni/id_cache.h" | ||||||
|  | #include "video_core/rasterizer_interface.h" | ||||||
|  |  | ||||||
| static JavaVM* s_java_vm; | static JavaVM* s_java_vm; | ||||||
| static jclass s_native_library_class; | static jclass s_native_library_class; | ||||||
|  | static jclass s_disk_cache_progress_class; | ||||||
|  | static jclass s_load_callback_stage_class; | ||||||
| static jmethodID s_exit_emulation_activity; | static jmethodID s_exit_emulation_activity; | ||||||
|  | static jmethodID s_disk_cache_load_progress; | ||||||
|  |  | ||||||
| static constexpr jint JNI_VERSION = JNI_VERSION_1_6; | static constexpr jint JNI_VERSION = JNI_VERSION_1_6; | ||||||
|  |  | ||||||
| @@ -38,10 +43,22 @@ jclass GetNativeLibraryClass() { | |||||||
|     return s_native_library_class; |     return s_native_library_class; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | jclass GetDiskCacheProgressClass() { | ||||||
|  |     return s_disk_cache_progress_class; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | jclass GetDiskCacheLoadCallbackStageClass() { | ||||||
|  |     return s_load_callback_stage_class; | ||||||
|  | } | ||||||
|  |  | ||||||
| jmethodID GetExitEmulationActivity() { | jmethodID GetExitEmulationActivity() { | ||||||
|     return s_exit_emulation_activity; |     return s_exit_emulation_activity; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | jmethodID GetDiskCacheLoadProgress() { | ||||||
|  |     return s_disk_cache_load_progress; | ||||||
|  | } | ||||||
|  |  | ||||||
| } // namespace IDCache | } // namespace IDCache | ||||||
|  |  | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| @@ -58,8 +75,16 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { | |||||||
|     // Initialize Java classes |     // Initialize Java classes | ||||||
|     const jclass native_library_class = env->FindClass("org/yuzu/yuzu_emu/NativeLibrary"); |     const jclass native_library_class = env->FindClass("org/yuzu/yuzu_emu/NativeLibrary"); | ||||||
|     s_native_library_class = reinterpret_cast<jclass>(env->NewGlobalRef(native_library_class)); |     s_native_library_class = reinterpret_cast<jclass>(env->NewGlobalRef(native_library_class)); | ||||||
|  |     s_disk_cache_progress_class = reinterpret_cast<jclass>(env->NewGlobalRef( | ||||||
|  |         env->FindClass("org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress"))); | ||||||
|  |     s_load_callback_stage_class = reinterpret_cast<jclass>(env->NewGlobalRef(env->FindClass( | ||||||
|  |         "org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress$LoadCallbackStage"))); | ||||||
|  |  | ||||||
|  |     // Initialize methods | ||||||
|     s_exit_emulation_activity = |     s_exit_emulation_activity = | ||||||
|         env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V"); |         env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V"); | ||||||
|  |     s_disk_cache_load_progress = | ||||||
|  |         env->GetStaticMethodID(s_disk_cache_progress_class, "loadProgress", "(III)V"); | ||||||
|  |  | ||||||
|     // Initialize Android Storage |     // Initialize Android Storage | ||||||
|     Common::FS::Android::RegisterCallbacks(env, s_native_library_class); |     Common::FS::Android::RegisterCallbacks(env, s_native_library_class); | ||||||
| @@ -79,6 +104,8 @@ void JNI_OnUnload(JavaVM* vm, void* reserved) { | |||||||
|     // UnInitialize Android Storage |     // UnInitialize Android Storage | ||||||
|     Common::FS::Android::UnRegisterCallbacks(); |     Common::FS::Android::UnRegisterCallbacks(); | ||||||
|     env->DeleteGlobalRef(s_native_library_class); |     env->DeleteGlobalRef(s_native_library_class); | ||||||
|  |     env->DeleteGlobalRef(s_disk_cache_progress_class); | ||||||
|  |     env->DeleteGlobalRef(s_load_callback_stage_class); | ||||||
|  |  | ||||||
|     // UnInitialze applets |     // UnInitialze applets | ||||||
|     SoftwareKeyboard::CleanupJNI(env); |     SoftwareKeyboard::CleanupJNI(env); | ||||||
|   | |||||||
| @@ -2,10 +2,15 @@ | |||||||
|  |  | ||||||
| #include <jni.h> | #include <jni.h> | ||||||
|  |  | ||||||
|  | #include "video_core/rasterizer_interface.h" | ||||||
|  |  | ||||||
| namespace IDCache { | namespace IDCache { | ||||||
|  |  | ||||||
| JNIEnv* GetEnvForThread(); | JNIEnv* GetEnvForThread(); | ||||||
| jclass GetNativeLibraryClass(); | jclass GetNativeLibraryClass(); | ||||||
|  | jclass GetDiskCacheProgressClass(); | ||||||
|  | jclass GetDiskCacheLoadCallbackStageClass(); | ||||||
| jmethodID GetExitEmulationActivity(); | jmethodID GetExitEmulationActivity(); | ||||||
|  | jmethodID GetDiskCacheLoadProgress(); | ||||||
|  |  | ||||||
| } // namespace IDCache | } // namespace IDCache | ||||||
|   | |||||||
| @@ -51,6 +51,7 @@ | |||||||
| #include "jni/emu_window/emu_window.h" | #include "jni/emu_window/emu_window.h" | ||||||
| #include "jni/id_cache.h" | #include "jni/id_cache.h" | ||||||
| #include "video_core/rasterizer_interface.h" | #include "video_core/rasterizer_interface.h" | ||||||
|  | #include "video_core/renderer_base.h" | ||||||
|  |  | ||||||
| namespace { | namespace { | ||||||
|  |  | ||||||
| @@ -229,6 +230,15 @@ public: | |||||||
|             m_is_running = true; |             m_is_running = true; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         // Load the disk shader cache. | ||||||
|  |         if (Settings::values.use_disk_shader_cache.GetValue()) { | ||||||
|  |             LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0); | ||||||
|  |             m_system.Renderer().ReadRasterizer()->LoadDiskResources( | ||||||
|  |                 m_system.GetApplicationProcessProgramID(), std::stop_token{}, | ||||||
|  |                 LoadDiskCacheProgress); | ||||||
|  |             LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Complete, 0, 0); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         void(m_system.Run()); |         void(m_system.Run()); | ||||||
|  |  | ||||||
|         if (m_system.DebuggerEnabled()) { |         if (m_system.DebuggerEnabled()) { | ||||||
| @@ -295,6 +305,14 @@ private: | |||||||
|         return entry; |         return entry; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |     static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max) { | ||||||
|  |         JNIEnv* env = IDCache::GetEnvForThread(); | ||||||
|  |         env->CallStaticVoidMethod(IDCache::GetDiskCacheProgressClass(), | ||||||
|  |                                   IDCache::GetDiskCacheLoadProgress(), static_cast<jint>(stage), | ||||||
|  |                                   static_cast<jint>(progress), static_cast<jint>(max)); | ||||||
|  |     } | ||||||
|  |  | ||||||
| private: | private: | ||||||
|     static EmulationSession s_instance; |     static EmulationSession s_instance; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -12,4 +12,13 @@ | |||||||
|         android:layout_margin="24dp" |         android:layout_margin="24dp" | ||||||
|         app:trackCornerRadius="4dp" /> |         app:trackCornerRadius="4dp" /> | ||||||
|  |  | ||||||
|  |     <TextView | ||||||
|  |         android:id="@+id/progress_text" | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:layout_marginLeft="24dp" | ||||||
|  |         android:layout_marginRight="24dp" | ||||||
|  |         android:layout_marginBottom="24dp" | ||||||
|  |         android:gravity="end" /> | ||||||
|  |  | ||||||
| </LinearLayout> | </LinearLayout> | ||||||
|   | |||||||
| @@ -35,6 +35,8 @@ | |||||||
|     <string name="renderer_asynchronous_shaders_description">Compiles shaders asynchronously, which will reduce stutter but may introduce glitches.</string> |     <string name="renderer_asynchronous_shaders_description">Compiles shaders asynchronously, which will reduce stutter but may introduce glitches.</string> | ||||||
|     <string name="renderer_debug">Enable graphics debugging</string> |     <string name="renderer_debug">Enable graphics debugging</string> | ||||||
|     <string name="renderer_debug_description">When checked, the graphics API enters a slower debugging mode.</string> |     <string name="renderer_debug_description">When checked, the graphics API enters a slower debugging mode.</string> | ||||||
|  |     <string name="use_disk_shader_cache">Use disk shader cache</string> | ||||||
|  |     <string name="use_disk_shader_cache_description">Reduce stuttering by storing and loading generated shaders to disk.</string> | ||||||
|  |  | ||||||
|     <!-- Audio settings strings --> |     <!-- Audio settings strings --> | ||||||
|     <string name="audio_volume">Volume</string> |     <string name="audio_volume">Volume</string> | ||||||
| @@ -45,6 +47,7 @@ | |||||||
|     <string name="ini_saved">Saved settings</string> |     <string name="ini_saved">Saved settings</string> | ||||||
|     <string name="gameid_saved">Saved settings for %1$s</string> |     <string name="gameid_saved">Saved settings for %1$s</string> | ||||||
|     <string name="error_saving">Error saving %1$s.ini: %2$s</string> |     <string name="error_saving">Error saving %1$s.ini: %2$s</string> | ||||||
|  |     <string name="loading">Loading...</string> | ||||||
|  |  | ||||||
|     <!-- Game Grid Screen--> |     <!-- Game Grid Screen--> | ||||||
|     <string name="grid_menu_core_settings">Settings</string> |     <string name="grid_menu_core_settings">Settings</string> | ||||||
| @@ -183,4 +186,8 @@ | |||||||
|     <string name="gamepad_home">Home</string> |     <string name="gamepad_home">Home</string> | ||||||
|     <string name="gamepad_screenshot">Screenshot</string> |     <string name="gamepad_screenshot">Screenshot</string> | ||||||
|  |  | ||||||
|  |     <!-- Disk shader cache --> | ||||||
|  |     <string name="preparing_shaders">Preparing shaders</string> | ||||||
|  |     <string name="building_shaders">Building shaders</string> | ||||||
|  |  | ||||||
| </resources> | </resources> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user