diff --git a/app/build.gradle b/app/build.gradle index c0f01108..24deda51 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -189,6 +189,7 @@ dependencies { /* Other Java */ implementation 'info.debatty:java-string-similarity:2.0.0' implementation 'com.github.KikiManjaro:colorpicker:v1.1.12' + implementation 'com.github.android:renderscript-intrinsics-replacement-toolkit:344be3f' } kapt { diff --git a/app/src/main/cpp/skyline/jvm.cpp b/app/src/main/cpp/skyline/jvm.cpp index 5af9c305..e41f5caa 100644 --- a/app/src/main/cpp/skyline/jvm.cpp +++ b/app/src/main/cpp/skyline/jvm.cpp @@ -63,8 +63,11 @@ namespace skyline { waitForSubmitOrCancelId{environ->GetMethodID(instanceClass, "waitForSubmitOrCancel", "(Lemu/skyline/applet/swkbd/SoftwareKeyboardDialog;)[Ljava/lang/Object;")}, closeKeyboardId{environ->GetMethodID(instanceClass, "closeKeyboard", "(Lemu/skyline/applet/swkbd/SoftwareKeyboardDialog;)V")}, showValidationResultId{environ->GetMethodID(instanceClass, "showValidationResult", "(Lemu/skyline/applet/swkbd/SoftwareKeyboardDialog;ILjava/lang/String;)I")}, - getVersionCodeId{environ->GetMethodID(instanceClass, "getVersionCode", "()I")}, - getIntegerValueId{environ->GetMethodID(environ->FindClass("java/lang/Integer"), "intValue", "()I")} { + getIntegerValueId{environ->GetMethodID(environ->FindClass("java/lang/Integer"), "intValue", "()I")}, + showPipelineLoadingScreenId{environ->GetMethodID(instanceClass, "showPipelineLoadingScreen", "(I)V")}, + updatePipelineLoadingProgressId{environ->GetMethodID(instanceClass, "updatePipelineLoadingProgress", "(I)V")}, + hidePipelineLoadingScreenId{environ->GetMethodID(instanceClass, "hidePipelineLoadingScreen", "()V")}, + getVersionCodeId{environ->GetMethodID(instanceClass, "getVersionCode", "()I")} { env.Initialize(environ); } @@ -137,14 +140,26 @@ namespace skyline { env->DeleteGlobalRef(dialog); } - i32 JvmManager::GetVersionCode() { - return env->CallIntMethod(instance, getVersionCodeId); - } - JvmManager::KeyboardCloseResult JvmManager::ShowValidationResult(jobject dialog, KeyboardTextCheckResult checkResult, std::u16string message) { auto str{env->NewString(reinterpret_cast(message.data()), static_cast(message.length()))}; auto result{static_cast(env->CallIntMethod(instance, showValidationResultId, dialog, checkResult, str))}; env->DeleteLocalRef(str); return result; } + + void JvmManager::ShowPipelineLoadingScreen(u32 totalPipelineCount) { + env->CallVoidMethod(instance, showPipelineLoadingScreenId, static_cast(totalPipelineCount)); + } + + void JvmManager::UpdatePipelineLoadingProgress(u32 progress) { + env->CallVoidMethod(instance, updatePipelineLoadingProgressId, static_cast(progress)); + } + + void JvmManager::HidePipelineLoadingScreen() { + env->CallVoidMethod(instance, hidePipelineLoadingScreenId); + } + + i32 JvmManager::GetVersionCode() { + return env->CallIntMethod(instance, getVersionCodeId); + } } diff --git a/app/src/main/cpp/skyline/jvm.h b/app/src/main/cpp/skyline/jvm.h index 535a307a..c5e271d6 100644 --- a/app/src/main/cpp/skyline/jvm.h +++ b/app/src/main/cpp/skyline/jvm.h @@ -176,6 +176,21 @@ namespace skyline { */ KeyboardCloseResult ShowValidationResult(KeyboardHandle dialog, KeyboardTextCheckResult checkResult, std::u16string message); + /** + * @brief A call to EmulationActivity.showPipelineLoadingScreen in Kotlin + */ + void ShowPipelineLoadingScreen(u32 totalPipelineCount); + + /** + * @brief A call to EmulationActivity.updatePipelineLoadingProgress in Kotlin + */ + void UpdatePipelineLoadingProgress(u32 progress); + + /** + * @brief A call to EmulationActivity.hidePipelineLoadingScreen in Kotlin + */ + void HidePipelineLoadingScreen(); + /** * @brief A call to EmulationActivity.getVersionCode in Kotlin * @return A version code in Vulkan's format with 14-bit patch + 10-bit major and minor components @@ -186,12 +201,17 @@ namespace skyline { jmethodID initializeControllersId; jmethodID vibrateDeviceId; jmethodID clearVibrationDeviceId; + jmethodID showKeyboardId; jmethodID waitForSubmitOrCancelId; jmethodID closeKeyboardId; jmethodID showValidationResultId; - jmethodID getVersionCodeId; - jmethodID getIntegerValueId; + + jmethodID showPipelineLoadingScreenId; + jmethodID updatePipelineLoadingProgressId; + jmethodID hidePipelineLoadingScreenId; + + jmethodID getVersionCodeId; }; } diff --git a/app/src/main/java/emu/skyline/EmulationActivity.kt b/app/src/main/java/emu/skyline/EmulationActivity.kt index 768df54c..78a25153 100644 --- a/app/src/main/java/emu/skyline/EmulationActivity.kt +++ b/app/src/main/java/emu/skyline/EmulationActivity.kt @@ -32,12 +32,12 @@ import androidx.core.view.updateMargins import androidx.fragment.app.FragmentTransaction import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint -import emu.skyline.BuildConfig import emu.skyline.applet.swkbd.SoftwareKeyboardConfig import emu.skyline.applet.swkbd.SoftwareKeyboardDialog import emu.skyline.data.AppItem import emu.skyline.data.AppItemTag import emu.skyline.databinding.EmuActivityBinding +import emu.skyline.emulation.PipelineLoadingFragment import emu.skyline.input.* import emu.skyline.loader.RomFile import emu.skyline.loader.getRomFormat @@ -446,7 +446,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo } } - override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration) { + override fun onPictureInPictureModeChanged(isInPictureInPictureMode : Boolean, newConfig : Configuration) { super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig) if (isInPictureInPictureMode) { @@ -463,10 +463,11 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo } else { try { unregisterReceiver(pictureInPictureReceiver) - } catch (ignored : Exception) { } + } catch (ignored : Exception) { + } resumeEmulator() - + binding.onScreenControllerView.apply { controllerType = inputHandler.getFirstControllerType() isGone = controllerType == ControllerType.None || !appSettings.onScreenControl @@ -511,6 +512,44 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo vibrators.clear() } + private lateinit var pipelineLoadingFragment : PipelineLoadingFragment + + /** + * Called by native code to show the pipeline loading progress screen + */ + @Suppress("unused") + fun showPipelineLoadingScreen(totalPipelineCount : Int) { + pipelineLoadingFragment = PipelineLoadingFragment.newInstance(item, totalPipelineCount) + + supportFragmentManager + .beginTransaction() + .setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out) + .replace(R.id.emulation_fragment, pipelineLoadingFragment) + .commit() + } + + /** + * Called by native code to update the pipeline loading progress + */ + @Suppress("unused") + fun updatePipelineLoadingProgress(progress : Int) { + runOnUiThread { + pipelineLoadingFragment.updateProgress(progress) + } + } + + /** + * Called by native code to hide the pipeline loading progress screen + */ + @Suppress("unused") + fun hidePipelineLoadingScreen() { + supportFragmentManager + .beginTransaction() + .setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out) + .remove(pipelineLoadingFragment) + .commit() + } + override fun surfaceCreated(holder : SurfaceHolder) { Log.d(Tag, "surfaceCreated Holder: $holder") diff --git a/app/src/main/java/emu/skyline/emulation/PipelineLoadingFragment.kt b/app/src/main/java/emu/skyline/emulation/PipelineLoadingFragment.kt new file mode 100644 index 00000000..0c8f3f51 --- /dev/null +++ b/app/src/main/java/emu/skyline/emulation/PipelineLoadingFragment.kt @@ -0,0 +1,79 @@ +/* + * SPDX-License-Identifier: MPL-2.0 + * Copyright © 2023 Skyline Team and Contributors (https://github.com/skyline-emu/) + */ + +package emu.skyline.emulation + +import android.annotation.SuppressLint +import android.graphics.RenderEffect +import android.graphics.Shader +import android.os.Build +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.google.android.renderscript.Toolkit +import emu.skyline.data.AppItem +import emu.skyline.data.AppItemTag +import emu.skyline.databinding.PipelineLoadingBinding +import emu.skyline.utils.serializable + +private const val TotalPipelineCountTag = "PipelineLoadingFragment::TotalCount" +private const val PipelineProgressTag = "PipelineLoadingFragment::Progress" + +class PipelineLoadingFragment : Fragment() { + private val item by lazy { requireArguments().serializable(AppItemTag)!! } + private val totalPipelineCount by lazy { requireArguments().getInt(TotalPipelineCountTag) } + + private lateinit var binding : PipelineLoadingBinding + + override fun onCreateView(inflater : LayoutInflater, container : ViewGroup?, savedInstanceState : Bundle?) = PipelineLoadingBinding.inflate(inflater).also { binding = it }.root + + @SuppressLint("SetTextI18n") + override fun onViewCreated(view : View, savedInstanceState : Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.gameTitle.apply { + text = item.title + isSelected = true + } + binding.gameVersion.text = item.version + binding.gameIcon.setImageBitmap(item.bitmapIcon) + + val progress = savedInstanceState?.getInt(PipelineProgressTag) ?: 0 + binding.progressBar.max = totalPipelineCount + updateProgress(progress) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + binding.backgroundImage.setImageBitmap(item.bitmapIcon) + binding.backgroundImage.setRenderEffect(RenderEffect.createBlurEffect(75f, 75f, Shader.TileMode.MIRROR)) + } else { + binding.backgroundImage.setImageBitmap(Toolkit.blur(item.bitmapIcon, 15)) + } + } + + override fun onSaveInstanceState(outState : Bundle) { + super.onSaveInstanceState(outState) + outState.putInt(PipelineProgressTag, binding.progressBar.progress) + } + + @SuppressLint("SetTextI18n") + fun updateProgress(progress : Int) { + if (!this::binding.isInitialized) + return + + binding.progressBar.progress = progress + binding.progressLabel.text = "$progress/$totalPipelineCount (${(progress.toFloat() / totalPipelineCount * 100).toInt()}%)" + } + + companion object { + fun newInstance(item : AppItem, totalPipelineCount : Int) = PipelineLoadingFragment().apply { + arguments = Bundle().apply { + putSerializable(AppItemTag, item) + putInt(TotalPipelineCountTag, totalPipelineCount) + } + } + } +} diff --git a/app/src/main/res/layout/emu_activity.xml b/app/src/main/res/layout/emu_activity.xml index 47db2aec..34809649 100644 --- a/app/src/main/res/layout/emu_activity.xml +++ b/app/src/main/res/layout/emu_activity.xml @@ -27,8 +27,8 @@ android:layout_gravity="top|left" android:layout_marginLeft="@dimen/onScreenItemHorizontalMargin" android:layout_marginTop="5dp" - tools:text="60 FPS\n16.6±0.10ms" - android:textColor="@color/colorPerfStatsPrimary" /> + android:textColor="@color/colorPerfStatsPrimary" + tools:text="60 FPS\n16.6±0.10ms" /> + + diff --git a/app/src/main/res/layout/shader_loading.xml b/app/src/main/res/layout/pipeline_loading.xml similarity index 94% rename from app/src/main/res/layout/shader_loading.xml rename to app/src/main/res/layout/pipeline_loading.xml index 20275d46..e6c08758 100644 --- a/app/src/main/res/layout/shader_loading.xml +++ b/app/src/main/res/layout/pipeline_loading.xml @@ -6,7 +6,7 @@ android:layout_height="match_parent"> @@ -80,7 +80,7 @@ Input Text - Compiling cached shaders... + Compiling cached pipelines... Expand Undo