Просмотр исходного кода

android: video_core: Add support for disk shader cache. (#64)

bunnei 3 лет назад
Родитель
Сommit
4006468f73

+ 1 - 0
src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt

@@ -42,6 +42,7 @@ object NativeLibrary {
     const val Player8Device = 7
     const val ConsoleDevice = 8
 
+    @JvmField
     var sEmulationActivity = WeakReference<EmulationActivity?>(null)
 
     init {

+ 46 - 0
src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress.kt

@@ -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
+    }
+}

+ 31 - 0
src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ShaderProgressViewModel.kt

@@ -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)
+    }
+}

+ 101 - 0
src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ui/ShaderProgressDialogFragment.kt

@@ -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
+        }
+    }
+}

+ 12 - 0
src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt

@@ -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,

+ 1 - 0
src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt

@@ -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"

+ 0 - 4
src/android/app/src/main/jni/config.cpp

@@ -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);

+ 27 - 0
src/android/app/src/main/jni/id_cache.cpp

@@ -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);

+ 5 - 0
src/android/app/src/main/jni/id_cache.h

@@ -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

+ 18 - 0
src/android/app/src/main/jni/native.cpp

@@ -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;
 

+ 9 - 0
src/android/app/src/main/res/layout/dialog_progress_bar.xml

@@ -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>

+ 7 - 0
src/android/app/src/main/res/values/strings.xml

@@ -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>