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 ConsoleDevice = 8 | ||||
|  | ||||
|     @JvmField | ||||
|     var sEmulationActivity = WeakReference<EmulationActivity?>(null) | ||||
|  | ||||
|     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 rendererAspectRatio = | ||||
|             rendererSection.getSetting(SettingsFile.KEY_RENDERER_ASPECT_RATIO) | ||||
|         val rendererUseDiskShaderCache = | ||||
|             rendererSection.getSetting(SettingsFile.KEY_RENDERER_USE_DISK_SHADER_CACHE) | ||||
|         val rendererForceMaxClocks = | ||||
|             rendererSection.getSetting(SettingsFile.KEY_RENDERER_FORCE_MAX_CLOCK) | ||||
|         val rendererAsynchronousShaders = | ||||
| @@ -250,6 +252,16 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) | ||||
|                     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( | ||||
|                 SwitchSetting( | ||||
|                     SettingsFile.KEY_RENDERER_FORCE_MAX_CLOCK, | ||||
|   | ||||
| @@ -36,6 +36,7 @@ object SettingsFile { | ||||
|     const val KEY_RENDERER_RESOLUTION = "resolution_setup" | ||||
|     const val KEY_RENDERER_ASPECT_RATIO = "aspect_ratio" | ||||
|     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_FORCE_MAX_CLOCK = "force_max_clock" | ||||
|     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_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 | ||||
|     Settings::values.renderer_force_max_clock = | ||||
|         config->GetBoolean("Renderer", "force_max_clock", true); | ||||
|   | ||||
| @@ -3,13 +3,18 @@ | ||||
|  | ||||
| #include <jni.h> | ||||
|  | ||||
| #include "common/assert.h" | ||||
| #include "common/fs/fs_android.h" | ||||
| #include "jni/applets/software_keyboard.h" | ||||
| #include "jni/id_cache.h" | ||||
| #include "video_core/rasterizer_interface.h" | ||||
|  | ||||
| static JavaVM* s_java_vm; | ||||
| 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_disk_cache_load_progress; | ||||
|  | ||||
| static constexpr jint JNI_VERSION = JNI_VERSION_1_6; | ||||
|  | ||||
| @@ -38,10 +43,22 @@ jclass GetNativeLibraryClass() { | ||||
|     return s_native_library_class; | ||||
| } | ||||
|  | ||||
| jclass GetDiskCacheProgressClass() { | ||||
|     return s_disk_cache_progress_class; | ||||
| } | ||||
|  | ||||
| jclass GetDiskCacheLoadCallbackStageClass() { | ||||
|     return s_load_callback_stage_class; | ||||
| } | ||||
|  | ||||
| jmethodID GetExitEmulationActivity() { | ||||
|     return s_exit_emulation_activity; | ||||
| } | ||||
|  | ||||
| jmethodID GetDiskCacheLoadProgress() { | ||||
|     return s_disk_cache_load_progress; | ||||
| } | ||||
|  | ||||
| } // namespace IDCache | ||||
|  | ||||
| #ifdef __cplusplus | ||||
| @@ -58,8 +75,16 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { | ||||
|     // Initialize Java classes | ||||
|     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_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 = | ||||
|         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 | ||||
|     Common::FS::Android::RegisterCallbacks(env, s_native_library_class); | ||||
| @@ -79,6 +104,8 @@ void JNI_OnUnload(JavaVM* vm, void* reserved) { | ||||
|     // UnInitialize Android Storage | ||||
|     Common::FS::Android::UnRegisterCallbacks(); | ||||
|     env->DeleteGlobalRef(s_native_library_class); | ||||
|     env->DeleteGlobalRef(s_disk_cache_progress_class); | ||||
|     env->DeleteGlobalRef(s_load_callback_stage_class); | ||||
|  | ||||
|     // UnInitialze applets | ||||
|     SoftwareKeyboard::CleanupJNI(env); | ||||
|   | ||||
| @@ -2,10 +2,15 @@ | ||||
|  | ||||
| #include <jni.h> | ||||
|  | ||||
| #include "video_core/rasterizer_interface.h" | ||||
|  | ||||
| namespace IDCache { | ||||
|  | ||||
| JNIEnv* GetEnvForThread(); | ||||
| jclass GetNativeLibraryClass(); | ||||
| jclass GetDiskCacheProgressClass(); | ||||
| jclass GetDiskCacheLoadCallbackStageClass(); | ||||
| jmethodID GetExitEmulationActivity(); | ||||
| jmethodID GetDiskCacheLoadProgress(); | ||||
|  | ||||
| } // namespace IDCache | ||||
|   | ||||
| @@ -51,6 +51,7 @@ | ||||
| #include "jni/emu_window/emu_window.h" | ||||
| #include "jni/id_cache.h" | ||||
| #include "video_core/rasterizer_interface.h" | ||||
| #include "video_core/renderer_base.h" | ||||
|  | ||||
| namespace { | ||||
|  | ||||
| @@ -229,6 +230,15 @@ public: | ||||
|             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()); | ||||
|  | ||||
|         if (m_system.DebuggerEnabled()) { | ||||
| @@ -295,6 +305,14 @@ private: | ||||
|         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: | ||||
|     static EmulationSession s_instance; | ||||
|  | ||||
|   | ||||
| @@ -12,4 +12,13 @@ | ||||
|         android:layout_margin="24dp" | ||||
|         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> | ||||
|   | ||||
| @@ -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_debug">Enable graphics debugging</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 --> | ||||
|     <string name="audio_volume">Volume</string> | ||||
| @@ -45,6 +47,7 @@ | ||||
|     <string name="ini_saved">Saved settings</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="loading">Loading...</string> | ||||
|  | ||||
|     <!-- Game Grid Screen--> | ||||
|     <string name="grid_menu_core_settings">Settings</string> | ||||
| @@ -183,4 +186,8 @@ | ||||
|     <string name="gamepad_home">Home</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> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user