Browse Source

android: Enhance FPS Overlay with more customizable options

- Now the fps follows theme color set in settings
- Added the ability to toggle stats on and off depending on user preference
- Now you are able to change the fps position and add a background behind it for easier reding

New added stats for the overlay are

FPS
FRAMETIME,
SPEED,
APP_RAM_USAGE,
SYSTEM_RAM_USAGE,
BATTERY_TEMPERATURE,
nce_cpp
Briar 8 months ago
parent
commit
fe51be43c1
  1. 10
      src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
  2. 1
      src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt
  3. 2
      src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
  4. 65
      src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
  5. 27
      src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
  6. 270
      src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
  7. 20
      src/android/app/src/main/jni/android_settings.h
  8. 32
      src/android/app/src/main/res/drawable/ic_frames.xml
  9. 6
      src/android/app/src/main/res/layout/fragment_emulation.xml
  10. 60
      src/android/app/src/main/res/layout/header_in_game.xml
  11. 9
      src/android/app/src/main/res/menu/menu_overlay_options.xml
  12. 1
      src/android/app/src/main/res/values-night/yuzu_colors.xml
  13. 17
      src/android/app/src/main/res/values/arrays.xml
  14. 31
      src/android/app/src/main/res/values/strings.xml
  15. 1
      src/android/app/src/main/res/values/yuzu_colors.xml

10
src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt

@ -26,7 +26,15 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
SHOW_PERFORMANCE_OVERLAY("show_performance_overlay"), SHOW_PERFORMANCE_OVERLAY("show_performance_overlay"),
SHOW_INPUT_OVERLAY("show_input_overlay"), SHOW_INPUT_OVERLAY("show_input_overlay"),
TOUCHSCREEN("touchscreen"), TOUCHSCREEN("touchscreen"),
SHOW_THERMAL_OVERLAY("show_thermal_overlay");
SHOW_THERMAL_OVERLAY("show_thermal_overlay"),
SHOW_FPS("show_fps"),
SHOW_FRAMETIME("show_frame_time"),
SHOW_SPEED("show_speed"),
SHOW_APP_RAM_USAGE("show_app_ram_usage"),
SHOW_SYSTEM_RAM_USAGE("show_system_ram_usage"),
SHOW_BAT_TEMPERATURE("show_bat_temperature"),
OVERLAY_BACKGROUND("overlay_background"),;
override fun getBoolean(needsGlobal: Boolean): Boolean = override fun getBoolean(needsGlobal: Boolean): Boolean =
NativeConfig.getBoolean(key, needsGlobal) NativeConfig.getBoolean(key, needsGlobal)

1
src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt

@ -26,6 +26,7 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
OVERLAY_OPACITY("control_opacity"), OVERLAY_OPACITY("control_opacity"),
LOCK_DRAWER("lock_drawer"), LOCK_DRAWER("lock_drawer"),
VERTICAL_ALIGNMENT("vertical_alignment"), VERTICAL_ALIGNMENT("vertical_alignment"),
PERF_OVERLAY_POSITION("perf_overlay_position"),
FSR_SHARPENING_SLIDER("fsr_sharpening_slider"); FSR_SHARPENING_SLIDER("fsr_sharpening_slider");
override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal) override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal)

2
src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt

@ -12,6 +12,7 @@ object Settings {
SECTION_ROOT(R.string.advanced_settings), SECTION_ROOT(R.string.advanced_settings),
SECTION_SYSTEM(R.string.preferences_system), SECTION_SYSTEM(R.string.preferences_system),
SECTION_RENDERER(R.string.preferences_graphics), SECTION_RENDERER(R.string.preferences_graphics),
SECTION_PERFORMANCE_STATS(R.string.show_stats_overlay),
SECTION_AUDIO(R.string.preferences_audio), SECTION_AUDIO(R.string.preferences_audio),
SECTION_INPUT(R.string.preferences_controls), SECTION_INPUT(R.string.preferences_controls),
SECTION_INPUT_PLAYER_ONE, SECTION_INPUT_PLAYER_ONE,
@ -32,6 +33,7 @@ object Settings {
const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch" const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
const val PREF_SHOULD_SHOW_PRE_ALPHA_WARNING = "ShouldShowPreAlphaWarning" const val PREF_SHOULD_SHOW_PRE_ALPHA_WARNING = "ShouldShowPreAlphaWarning"
const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown" const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown"
const val SECTION_STATS_OVERLAY = "Stats Overlay"
// Deprecated input overlay preference keys // Deprecated input overlay preference keys
const val PREF_CONTROL_SCALE = "controlScale" const val PREF_CONTROL_SCALE = "controlScale"

65
src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt

@ -220,6 +220,71 @@ abstract class SettingsItem(
valuesId = R.array.rendererResolutionValues valuesId = R.array.rendererResolutionValues
) )
) )
put(
SwitchSetting(
BooleanSetting.SHOW_PERFORMANCE_OVERLAY,
R.string.enable_stats_overlay_,
descriptionId = R.string.stats_overlay_options_description
)
)
put(
SwitchSetting(
BooleanSetting.OVERLAY_BACKGROUND,
R.string.overlay_background,
descriptionId = R.string.overlay_background_description
)
)
put(
SingleChoiceSetting(
IntSetting.PERF_OVERLAY_POSITION,
titleId = R.string.overlay_position,
descriptionId = R.string.overlay_position_description,
choicesId = R.array.statsPosition,
valuesId = R.array.staticThemeValues
)
)
put(
SwitchSetting(
BooleanSetting.SHOW_FPS,
R.string.show_fps,
descriptionId = R.string.show_fps_description
)
)
put(
SwitchSetting(
BooleanSetting.SHOW_FRAMETIME,
R.string.show_frametime,
descriptionId = R.string.show_frametime_description
)
)
put(
SwitchSetting(
BooleanSetting.SHOW_SPEED,
R.string.show_speed,
descriptionId = R.string.show_speed_description
)
)
put(
SwitchSetting(
BooleanSetting.SHOW_APP_RAM_USAGE,
R.string.show_app_ram_usage,
descriptionId = R.string.show_app_ram_usage_description
)
)
put(
SwitchSetting(
BooleanSetting.SHOW_SYSTEM_RAM_USAGE,
R.string.show_system_ram_usage,
descriptionId = R.string.show_system_ram_usage_description
)
)
put(
SwitchSetting(
BooleanSetting.SHOW_BAT_TEMPERATURE,
R.string.show_bat_temperature,
descriptionId = R.string.show_bat_temperature_description
)
)
put( put(
SingleChoiceSetting( SingleChoiceSetting(
IntSetting.RENDERER_VSYNC, IntSetting.RENDERER_VSYNC,

27
src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt

@ -88,6 +88,7 @@ class SettingsFragmentPresenter(
MenuTag.SECTION_ROOT -> addConfigSettings(sl) MenuTag.SECTION_ROOT -> addConfigSettings(sl)
MenuTag.SECTION_SYSTEM -> addSystemSettings(sl) MenuTag.SECTION_SYSTEM -> addSystemSettings(sl)
MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl) MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl)
MenuTag.SECTION_PERFORMANCE_STATS -> addPerfomanceOverlaySettings(sl)
MenuTag.SECTION_AUDIO -> addAudioSettings(sl) MenuTag.SECTION_AUDIO -> addAudioSettings(sl)
MenuTag.SECTION_INPUT -> addInputSettings(sl) MenuTag.SECTION_INPUT -> addInputSettings(sl)
MenuTag.SECTION_INPUT_PLAYER_ONE -> addInputPlayer(sl, 0) MenuTag.SECTION_INPUT_PLAYER_ONE -> addInputPlayer(sl, 0)
@ -127,6 +128,15 @@ class SettingsFragmentPresenter(
menuKey = MenuTag.SECTION_RENDERER menuKey = MenuTag.SECTION_RENDERER
) )
) )
if (!NativeConfig.isPerGameConfigLoaded())
add(
SubmenuSetting(
titleId = R.string.stats_overlay_options,
descriptionId = R.string.stats_overlay_options_description,
iconId = R.drawable.ic_frames,
menuKey = MenuTag.SECTION_PERFORMANCE_STATS
)
)
add( add(
SubmenuSetting( SubmenuSetting(
titleId = R.string.preferences_audio, titleId = R.string.preferences_audio,
@ -187,6 +197,23 @@ class SettingsFragmentPresenter(
} }
} }
private fun addPerfomanceOverlaySettings(sl: ArrayList<SettingsItem>) {
sl.apply {
add(HeaderSetting(R.string.stats_overlay_customization))
add(BooleanSetting.SHOW_PERFORMANCE_OVERLAY.key)
add(BooleanSetting.OVERLAY_BACKGROUND.key)
add(IntSetting.PERF_OVERLAY_POSITION.key)
add(HeaderSetting(R.string.stats_overlay_items))
add(BooleanSetting.SHOW_FPS.key)
add(BooleanSetting.SHOW_FRAMETIME.key)
add(BooleanSetting.SHOW_SPEED.key)
add(BooleanSetting.SHOW_APP_RAM_USAGE.key)
add(BooleanSetting.SHOW_SYSTEM_RAM_USAGE.key)
add(BooleanSetting.SHOW_BAT_TEMPERATURE.key)
}
}
private fun addAudioSettings(sl: ArrayList<SettingsItem>) { private fun addAudioSettings(sl: ArrayList<SettingsItem>) {
sl.apply { sl.apply {
add(IntSetting.AUDIO_OUTPUT_ENGINE.key) add(IntSetting.AUDIO_OUTPUT_ENGINE.key)

270
src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt

@ -4,18 +4,29 @@
package org.yuzu.yuzu_emu.fragments package org.yuzu.yuzu_emu.fragments
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.ActivityManager
import android.app.AlertDialog import android.app.AlertDialog
import android.content.Context import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.ActivityInfo import android.content.pm.ActivityInfo
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Color
import android.net.Uri import android.net.Uri
import android.os.BatteryManager
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.os.SystemClock import android.os.SystemClock
import android.util.Rational import android.util.Rational
import android.view.*
import android.view.Gravity
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.Surface
import android.view.SurfaceHolder
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
@ -26,7 +37,6 @@ import androidx.core.graphics.Insets
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding
import androidx.drawerlayout.widget.DrawerLayout import androidx.drawerlayout.widget.DrawerLayout
import androidx.drawerlayout.widget.DrawerLayout.DrawerListener import androidx.drawerlayout.widget.DrawerLayout.DrawerListener
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
@ -36,6 +46,7 @@ import androidx.navigation.fragment.navArgs
import androidx.window.layout.FoldingFeature import androidx.window.layout.FoldingFeature
import androidx.window.layout.WindowInfoTracker import androidx.window.layout.WindowInfoTracker
import androidx.window.layout.WindowLayoutInfo import androidx.window.layout.WindowLayoutInfo
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.slider.Slider import com.google.android.material.slider.Slider
import org.yuzu.yuzu_emu.HomeNavigationDirections import org.yuzu.yuzu_emu.HomeNavigationDirections
@ -51,28 +62,28 @@ import org.yuzu.yuzu_emu.features.settings.model.Settings.EmulationOrientation
import org.yuzu.yuzu_emu.features.settings.model.Settings.EmulationVerticalAlignment import org.yuzu.yuzu_emu.features.settings.model.Settings.EmulationVerticalAlignment
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.model.DriverViewModel import org.yuzu.yuzu_emu.model.DriverViewModel
import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.model.EmulationViewModel import org.yuzu.yuzu_emu.model.EmulationViewModel
import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.overlay.model.OverlayControl import org.yuzu.yuzu_emu.overlay.model.OverlayControl
import org.yuzu.yuzu_emu.overlay.model.OverlayLayout import org.yuzu.yuzu_emu.overlay.model.OverlayLayout
import org.yuzu.yuzu_emu.utils.*
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.FileUtil
import org.yuzu.yuzu_emu.utils.GameHelper
import org.yuzu.yuzu_emu.utils.GameIconUtils
import org.yuzu.yuzu_emu.utils.Log
import org.yuzu.yuzu_emu.utils.NativeConfig
import org.yuzu.yuzu_emu.utils.ViewUtils
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
import java.lang.NullPointerException
import android.content.BroadcastReceiver
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import android.util.TypedValue
import android.app.ActivityManager
import android.graphics.Color
import android.os.Debug
import org.yuzu.yuzu_emu.utils.collect
import java.io.File
class EmulationFragment : Fragment(), SurfaceHolder.Callback { class EmulationFragment : Fragment(), SurfaceHolder.Callback {
private lateinit var emulationState: EmulationState private lateinit var emulationState: EmulationState
private var emulationActivity: EmulationActivity? = null private var emulationActivity: EmulationActivity? = null
private var perfStatsUpdater: (() -> Unit)? = null private var perfStatsUpdater: (() -> Unit)? = null
private var thermalStatsUpdater: (() -> Unit)? = null
private var batteryReceiverRegistered: Boolean = false
private lateinit var cpuBackend: String
private lateinit var gpuDriver: String
private var _binding: FragmentEmulationBinding? = null private var _binding: FragmentEmulationBinding? = null
private val binding get() = _binding!! private val binding get() = _binding!!
@ -198,8 +209,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
} }
}) })
binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
binding.inGameMenu.getHeaderView(0).findViewById<TextView>(R.id.text_game_title).text =
game.title
binding.inGameMenu.getHeaderView(0).apply {
val titleView = findViewById<TextView>(R.id.text_game_title)
titleView.text = game.title
}
binding.inGameMenu.menu.findItem(R.id.menu_lock_drawer).apply { binding.inGameMenu.menu.findItem(R.id.menu_lock_drawer).apply {
val lockMode = IntSetting.LOCK_DRAWER.getInt() val lockMode = IntSetting.LOCK_DRAWER.getInt()
@ -375,9 +388,23 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
emulationState.updateSurface() emulationState.updateSurface()
// Setup overlays // Setup overlays
updateShowFpsOverlay()
val temperature = getBatteryTemperature(requireContext())
updateThermalOverlay(temperature)
updateshowStatsOvelray()
// Re update binding when the specs values get initialized properly
binding.inGameMenu.getHeaderView(0).apply {
val titleView = findViewById<TextView>(R.id.text_game_title)
val cpuBackendLabel = findViewById<TextView>(R.id.cpu_backend)
val gpuvendorLabel = findViewById<TextView>(R.id.gpu_vendor)
titleView.text = game.title
cpuBackendLabel.text = NativeLibrary.getCpuBackend()
gpuvendorLabel.text = NativeLibrary.getGpuDriver()
}
val position = IntSetting.PERF_OVERLAY_POSITION.getInt()
updateStatsPosition(position)
} }
} }
emulationViewModel.isEmulationStopping.collect(viewLifecycleOwner) { emulationViewModel.isEmulationStopping.collect(viewLifecycleOwner) {
@ -385,7 +412,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
binding.loadingText.setText(R.string.shutting_down) binding.loadingText.setText(R.string.shutting_down)
ViewUtils.showView(binding.loadingIndicator) ViewUtils.showView(binding.loadingIndicator)
ViewUtils.hideView(binding.inputContainer) ViewUtils.hideView(binding.inputContainer)
ViewUtils.hideView(binding.showFpsText)
ViewUtils.hideView(binding.showStatsOverlayText)
} }
} }
emulationViewModel.drawerOpen.collect(viewLifecycleOwner) { emulationViewModel.drawerOpen.collect(viewLifecycleOwner) {
@ -466,23 +493,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
override fun onPause() { override fun onPause() {
if (emulationState.isRunning && emulationActivity?.isInPictureInPictureMode != true) { if (emulationState.isRunning && emulationActivity?.isInPictureInPictureMode != true) {
emulationState.pause() emulationState.pause()
}
context?.let {
if (batteryReceiverRegistered) {
it.unregisterReceiver(batteryReceiver)
batteryReceiverRegistered = false
}
} }
super.onPause() super.onPause()
} }
override fun onDestroyView() { override fun onDestroyView() {
context?.let {
if (batteryReceiverRegistered) {
it.unregisterReceiver(batteryReceiver)
batteryReceiverRegistered = false
}
}
super.onDestroyView() super.onDestroyView()
_binding = null _binding = null
} }
@ -493,11 +508,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
} }
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
if (!batteryReceiverRegistered) {
val filter = IntentFilter(Intent.ACTION_BATTERY_CHANGED)
context?.registerReceiver(batteryReceiver, filter)
batteryReceiverRegistered = true
}
// If the overlay is enabled, we need to update the position if changed
val position = IntSetting.PERF_OVERLAY_POSITION.getInt()
updateStatsPosition(position)
} }
private fun resetInputOverlay() { private fun resetInputOverlay() {
@ -508,37 +521,90 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
} }
} }
@SuppressLint("DefaultLocale") @SuppressLint("DefaultLocale")
private fun updateShowFpsOverlay() {
private fun updateshowStatsOvelray() {
val showOverlay = BooleanSetting.SHOW_PERFORMANCE_OVERLAY.getBoolean() val showOverlay = BooleanSetting.SHOW_PERFORMANCE_OVERLAY.getBoolean()
binding.showFpsText.setTextColor(Color.parseColor("#A146FF"))
binding.showFpsText.setVisible(showOverlay)
binding.showStatsOverlayText.apply {
setTextColor(
MaterialColors.getColor(
this,
com.google.android.material.R.attr.colorPrimary
)
)
}
binding.showStatsOverlayText.setVisible(showOverlay)
if (showOverlay) { if (showOverlay) {
val SYSTEM_FPS = 0 val SYSTEM_FPS = 0
val FPS = 1 val FPS = 1
val FRAMETIME = 2 val FRAMETIME = 2
val SPEED = 3 val SPEED = 3
val sb = StringBuilder()
perfStatsUpdater = { perfStatsUpdater = {
if (emulationViewModel.emulationStarted.value && if (emulationViewModel.emulationStarted.value &&
!emulationViewModel.isEmulationStopping.value !emulationViewModel.isEmulationStopping.value
) { ) {
sb.setLength(0) // Clear the StringBuilder to avoid recurring appends
val perfStats = NativeLibrary.getPerfStats() val perfStats = NativeLibrary.getPerfStats()
val cpuBackend = NativeLibrary.getCpuBackend()
val gpuDriver = NativeLibrary.getGpuDriver()
// Get memory info
val mi = ActivityManager.MemoryInfo()
if (_binding != null) {
if (BooleanSetting.SHOW_FPS.getBoolean(NativeConfig.isPerGameConfigLoaded())) {
sb.append(String.format("FPS: %d", (perfStats[FPS] + 0.5).toInt()))
// perfStats[FPS], usedMegs, cpuBackend, gpuDriver
if (BooleanSetting.SHOW_FRAMETIME.getBoolean(NativeConfig.isPerGameConfigLoaded())) {
if (sb.isNotEmpty()) sb.append(" | ")
sb.append(
String.format(
"FT: %.1fms",
(perfStats[FRAMETIME] * 1000.0f).toFloat()
)
)
}
if (BooleanSetting.SHOW_SPEED.getBoolean(NativeConfig.isPerGameConfigLoaded())) {
if (sb.isNotEmpty()) sb.append(" | ")
sb.append(
String.format(
"Speed: %d%%",
(perfStats[SPEED] * 100.0 + 0.5).toInt()
)
)
}
if (BooleanSetting.SHOW_APP_RAM_USAGE.getBoolean(NativeConfig.isPerGameConfigLoaded())) {
if (sb.isNotEmpty()) sb.append(" | ")
val appRamUsage =
File("/proc/self/statm").readLines()[0].split(' ')[1].toLong() * 4096 / 1000000
sb.append("Process RAM: $appRamUsage MB")
}
if (BooleanSetting.SHOW_SYSTEM_RAM_USAGE.getBoolean(NativeConfig.isPerGameConfigLoaded())) {
if (sb.isNotEmpty()) sb.append(" | ")
context?.let { ctx ->
val activityManager = val activityManager =
requireContext().getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
activityManager.getMemoryInfo(mi)
ctx.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val memInfo = ActivityManager.MemoryInfo()
activityManager.getMemoryInfo(memInfo)
val usedRamMB = (memInfo.totalMem - memInfo.availMem) / 1048576L
sb.append("RAM: $usedRamMB MB")
}
}
// Calculate used memory
val usedMegs = (mi.totalMem - mi.availMem) / 1048576L // Convert bytes to megabytes
if (BooleanSetting.SHOW_BAT_TEMPERATURE.getBoolean(NativeConfig.isPerGameConfigLoaded())) {
if (sb.isNotEmpty()) sb.append(" | ")
val batteryTemp = getBatteryTemperature()
val tempF = celsiusToFahrenheit(batteryTemp)
sb.append(String.format("%.1f°C/%.1f°F", batteryTemp, tempF))
}
if (_binding != null) {
binding.showFpsText.text = String.format(
"%.1f FPS • %d MB • %s/%s",
perfStats[FPS], usedMegs, cpuBackend, gpuDriver
)
if (BooleanSetting.OVERLAY_BACKGROUND.getBoolean(NativeConfig.isPerGameConfigLoaded())) {
binding.showStatsOverlayText.setBackgroundResource(R.color.yuzu_transparent_black)
} else {
binding.showStatsOverlayText.setBackgroundResource(0)
}
binding.showStatsOverlayText.text = sb.toString()
}
} }
perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 800) perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 800)
} }
@ -551,18 +617,55 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
} }
} }
private val batteryReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
intent?.let {
if (it.action == Intent.ACTION_BATTERY_CHANGED) {
val temperature = getBatteryTemperature(context!!)
updateThermalOverlay(temperature)
private fun updateStatsPosition(position: Int) {
val params = binding.showStatsOverlayText.layoutParams as FrameLayout.LayoutParams
when (position) {
0 -> {
params.gravity = (Gravity.TOP or Gravity.START)
params.setMargins(resources.getDimensionPixelSize(R.dimen.spacing_large), 0, 0, 0)
} }
1 -> {
params.gravity = (Gravity.TOP or Gravity.CENTER_HORIZONTAL)
} }
2 -> {
params.gravity = (Gravity.TOP or Gravity.END)
params.setMargins(0, 0, resources.getDimensionPixelSize(R.dimen.spacing_large), 0)
} }
}
private fun updateThermalOverlay(temperature: Float) {
3 -> {
params.gravity = (Gravity.BOTTOM or Gravity.START)
params.setMargins(resources.getDimensionPixelSize(R.dimen.spacing_large), 0, 0, 0)
}
4 -> {
params.gravity = (Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL)
}
5 -> {
params.gravity = (Gravity.BOTTOM or Gravity.END)
params.setMargins(0, 0, resources.getDimensionPixelSize(R.dimen.spacing_large), 0)
}
}
}
private fun getBatteryTemperature(): Float {
try {
val batteryIntent = requireContext().registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
// Temperature in tenths of a degree Celsius
val temperature = batteryIntent?.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, 0) ?: 0
// Convert to degrees Celsius
return temperature / 10.0f
} catch (e: Exception) {
return 0.0f
}
}
private fun celsiusToFahrenheit(celsius: Float): Float {
return (celsius * 9 / 5) + 32
}
private fun updateThermalOverlay(temperature: Float) {
if (BooleanSetting.SHOW_THERMAL_OVERLAY.getBoolean() && if (BooleanSetting.SHOW_THERMAL_OVERLAY.getBoolean() &&
emulationViewModel.emulationStarted.value && emulationViewModel.emulationStarted.value &&
!emulationViewModel.isEmulationStopping.value !emulationViewModel.isEmulationStopping.value
@ -581,17 +684,9 @@ private fun updateThermalOverlay(temperature: Float) {
binding.showThermalsText.setTextColor(color) binding.showThermalsText.setTextColor(color)
binding.showThermalsText.text = String.format("%.1f°C • %.1f°F", temperature, fahrenheit) binding.showThermalsText.text = String.format("%.1f°C • %.1f°F", temperature, fahrenheit)
} }
}
private fun getBatteryTemperature(context: Context): Float {
val intent: Intent? = context.registerReceiver(
null,
IntentFilter(Intent.ACTION_BATTERY_CHANGED)
)
val temperature = intent?.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, 0) ?: 0
return temperature / 10.0f
} }
@SuppressLint("SourceLockedOrientationActivity") @SuppressLint("SourceLockedOrientationActivity")
private fun updateOrientation() { private fun updateOrientation() {
emulationActivity?.let { emulationActivity?.let {
@ -717,10 +812,8 @@ private fun getBatteryTemperature(context: Context): Float {
popup.menuInflater.inflate(R.menu.menu_overlay_options, popup.menu) popup.menuInflater.inflate(R.menu.menu_overlay_options, popup.menu)
popup.menu.apply { popup.menu.apply {
findItem(R.id.menu_toggle_fps).isChecked =
findItem(R.id.menu_show_stats_overlay).isChecked =
BooleanSetting.SHOW_PERFORMANCE_OVERLAY.getBoolean() BooleanSetting.SHOW_PERFORMANCE_OVERLAY.getBoolean()
findItem(R.id.thermal_indicator).isChecked =
BooleanSetting.SHOW_THERMAL_OVERLAY.getBoolean()
findItem(R.id.menu_rel_stick_center).isChecked = findItem(R.id.menu_rel_stick_center).isChecked =
BooleanSetting.JOYSTICK_REL_CENTER.getBoolean() BooleanSetting.JOYSTICK_REL_CENTER.getBoolean()
findItem(R.id.menu_dpad_slide).isChecked = BooleanSetting.DPAD_SLIDE.getBoolean() findItem(R.id.menu_dpad_slide).isChecked = BooleanSetting.DPAD_SLIDE.getBoolean()
@ -733,34 +826,12 @@ private fun getBatteryTemperature(context: Context): Float {
popup.setOnDismissListener { NativeConfig.saveGlobalConfig() } popup.setOnDismissListener { NativeConfig.saveGlobalConfig() }
popup.setOnMenuItemClickListener { popup.setOnMenuItemClickListener {
when (it.itemId) { when (it.itemId) {
R.id.menu_toggle_fps -> {
R.id.menu_show_stats_overlay -> {
it.isChecked = !it.isChecked it.isChecked = !it.isChecked
BooleanSetting.SHOW_PERFORMANCE_OVERLAY.setBoolean(it.isChecked) BooleanSetting.SHOW_PERFORMANCE_OVERLAY.setBoolean(it.isChecked)
updateShowFpsOverlay()
updateshowStatsOvelray()
true true
} }
R.id.thermal_indicator -> {
it.isChecked = !it.isChecked
BooleanSetting.SHOW_THERMAL_OVERLAY.setBoolean(it.isChecked)
if (it.isChecked) {
val temperature = getBatteryTemperature(requireContext())
updateThermalOverlay(temperature)
if (!batteryReceiverRegistered) {
val filter = IntentFilter(Intent.ACTION_BATTERY_CHANGED)
context?.registerReceiver(batteryReceiver, filter)
batteryReceiverRegistered = true
}
} else {
if (batteryReceiverRegistered) {
context?.unregisterReceiver(batteryReceiver)
batteryReceiverRegistered = false
}
binding.showThermalsText.text = ""
}
true
}
R.id.menu_edit_overlay -> { R.id.menu_edit_overlay -> {
binding.drawerLayout.close() binding.drawerLayout.close()
binding.surfaceInputOverlay.requestFocus() binding.surfaceInputOverlay.requestFocus()
@ -951,7 +1022,8 @@ private fun getBatteryTemperature(context: Context): Float {
right = cutInsets.right right = cutInsets.right
} }
v.updatePadding(left = left, top = cutInsets.top, right = right)
v.setPadding(left, cutInsets.top, right, 0)
windowInsets windowInsets
} }
} }

20
src/android/app/src/main/jni/android_settings.h

@ -66,9 +66,23 @@ struct Values {
Settings::Setting<bool> haptic_feedback{linkage, true, "haptic_feedback", Settings::Setting<bool> haptic_feedback{linkage, true, "haptic_feedback",
Settings::Category::Overlay}; Settings::Category::Overlay};
Settings::Setting<bool> show_performance_overlay{linkage, true, "show_performance_overlay", Settings::Setting<bool> show_performance_overlay{linkage, true, "show_performance_overlay",
Settings::Category::Overlay};
Settings::Setting<bool> show_thermal_overlay{linkage, false, "show_thermal_overlay",
Settings::Category::Overlay};
Settings::Category::Overlay, Settings::Specialization::Paired, true , true};
Settings::Setting<bool> overlay_background{linkage, false, "overlay_background",
Settings::Category::Overlay, Settings::Specialization::Default, true , true, &show_performance_overlay};
Settings::Setting<s32> perf_overlay_position{linkage, 0, "perf_overlay_position",
Settings::Category::Overlay, Settings::Specialization::Default, true , true, &show_performance_overlay};
Settings::Setting<bool> show_fps{linkage, true, "show_fps",
Settings::Category::Overlay, Settings::Specialization::Default, true , true, &show_performance_overlay};
Settings::Setting<bool> show_frame_time{linkage, false, "show_frame_time",
Settings::Category::Overlay, Settings::Specialization::Default, true , true, &show_performance_overlay};
Settings::Setting<bool> show_speed{linkage, true, "show_speed",
Settings::Category::Overlay, Settings::Specialization::Default, true , true, &show_performance_overlay};
Settings::Setting<bool> show_app_ram_usage{linkage, false, "show_app_ram_usage",
Settings::Category::Overlay, Settings::Specialization::Default, true , true, &show_performance_overlay};
Settings::Setting<bool> show_system_ram_usage{linkage, false, "show_system_ram_usage",
Settings::Category::Overlay, Settings::Specialization::Default, true , true, &show_performance_overlay};
Settings::Setting<bool> show_bat_temperature{linkage, false, "show_bat_temperature",
Settings::Category::Overlay, Settings::Specialization::Default, true , true, &show_performance_overlay};
Settings::Setting<bool> show_input_overlay{linkage, true, "show_input_overlay", Settings::Setting<bool> show_input_overlay{linkage, true, "show_input_overlay",
Settings::Category::Overlay}; Settings::Category::Overlay};
Settings::Setting<bool> touchscreen{linkage, true, "touchscreen", Settings::Category::Overlay}; Settings::Setting<bool> touchscreen{linkage, true, "touchscreen", Settings::Category::Overlay};

32
src/android/app/src/main/res/drawable/ic_frames.xml

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#00000000"
android:strokeColor="#FF000000"
android:strokeWidth="1"
android:pathData="M4,4 L4,20 L20,20" />
<path
android:fillColor="#00000000"
android:strokeColor="#FF000000"
android:strokeWidth="2"
android:pathData="M4,16 L8,12 L12,14 L16,8 L20,10" />
<path
android:fillColor="#FF000000"
android:pathData="M4,16 C3.45,16 3,15.55 3,15 C3,14.45 3.45,14 4,14 C4.55,14 5,14.45 5,15 C5,15.55 4.55,16 4,16" />
<path
android:fillColor="#FF000000"
android:pathData="M8,12 C7.45,12 7,11.55 7,11 C7,10.45 7.45,10 8,10 C8.55,10 9,10.45 9,11 C9,11.55 8.55,12 8,12" />
<path
android:fillColor="#FF000000"
android:pathData="M12,14 C11.45,14 11,13.55 11,13 C11,12.45 11.45,12 12,12 C12.55,12 13,12.45 13,13 C13,13.55 12.55,14 12,14" />
<path
android:fillColor="#FF000000"
android:pathData="M16,8 C15.45,8 15,7.55 15,7 C15,6.45 15.45,6 16,6 C16.55,6 17,6.45 17,7 C17,7.55 16.55,8 16,8" />
<path
android:fillColor="#FF000000"
android:pathData="M20,10 C19.45,10 19,9.55 19,9 C19,8.45 19.45,8 20,8 C20.55,8 21,8.45 21,9 C21,9.55 20.55,10 20,10" />
</vector>

6
src/android/app/src/main/res/layout/fragment_emulation.xml

@ -140,15 +140,13 @@
android:id="@+id/overlay_container" android:id="@+id/overlay_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginHorizontal="20dp"
android:fitsSystemWindows="true">
android:fitsSystemWindows="false">
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/show_fps_text"
android:id="@+id/show_stats_overlay_text"
style="@style/TextAppearance.Material3.BodySmall" style="@style/TextAppearance.Material3.BodySmall"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="left"
android:clickable="false" android:clickable="false"
android:focusable="false" android:focusable="false"
android:textColor="@android:color/white" android:textColor="@android:color/white"

60
src/android/app/src/main/res/layout/header_in_game.xml

@ -1,14 +1,64 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.textview.MaterialTextView
xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/text_game_title"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="24dp" android:layout_marginTop="24dp"
android:layout_marginStart="24dp" android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:layout_marginEnd="24dp">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/text_game_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceHeadlineMedium" android:textAppearance="?attr/textAppearanceHeadlineMedium"
android:textColor="?attr/colorOnSurface" android:textColor="?attr/colorOnSurface"
android:textAlignment="viewStart" android:textAlignment="viewStart"
tools:text="Super Mario Odyssey" />
android:layout_marginBottom="8dp"
tools:text="text_game_title" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.google.android.material.textview.MaterialTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceBodyMedium"
android:textColor="?attr/colorOnSurfaceVariant"
android:textAlignment="viewStart"
android:layout_marginEnd="4dp"
android:text="System Info:" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/cpu_backend"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceBodyMedium"
android:textColor="?attr/colorOnSurfaceVariant"
android:textAlignment="viewStart"
tools:text="cpu_backend" />
<com.google.android.material.textview.MaterialTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceBodyMedium"
android:textColor="?attr/colorOnSurfaceVariant"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:text="|" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/gpu_vendor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceBodyMedium"
android:textColor="?attr/colorOnSurfaceVariant"
android:textAlignment="viewStart"
tools:text="gpu_vendor" />
</LinearLayout>
</LinearLayout>

9
src/android/app/src/main/res/menu/menu_overlay_options.xml

@ -2,13 +2,8 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"> <menu xmlns:android="http://schemas.android.com/apk/res/android">
<item <item
android:id="@+id/menu_toggle_fps"
android:title="@string/emulation_fps_counter"
android:checkable="true" />
<item
android:id="@+id/thermal_indicator"
android:title="@string/emulation_thermal_indicator"
android:id="@+id/menu_show_stats_overlay"
android:title="@string/show_stats_overlay"
android:checkable="true" /> android:checkable="true" />
<item <item

1
src/android/app/src/main/res/values-night/yuzu_colors.xml

@ -227,6 +227,7 @@
<color name="yuzu_surfaceTint_gray">#B7B7B7</color> <color name="yuzu_surfaceTint_gray">#B7B7B7</color>
<!-- Common Colors Across All Themes --> <!-- Common Colors Across All Themes -->
<color name="yuzu_transparent_black">#80000000</color>
<color name="yuzu_outlineVariant">#C6C5D0</color> <color name="yuzu_outlineVariant">#C6C5D0</color>
<color name="yuzu_error">#FFB4AB</color> <color name="yuzu_error">#FFB4AB</color>
<color name="yuzu_errorContainer">#93000A</color> <color name="yuzu_errorContainer">#93000A</color>

17
src/android/app/src/main/res/values/arrays.xml

@ -183,6 +183,23 @@
<item>2</item> <item>2</item>
</integer-array> </integer-array>
<string-array name="statsPosition">
<item>@string/overlay_position_top_left</item>
<item>@string/overlay_position_center_top</item>
<item>@string/overlay_position_top_right</item>
<item>@string/overlay_position_bottom_left</item>
<item>@string/overlay_position_center_bottom</item>
<item>@string/overlay_position_bottom_right</item>
</string-array>
<integer-array name="statsPositionValues">
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
</integer-array>
<string-array name="cpuBackendArm64Names"> <string-array name="cpuBackendArm64Names">
<item>@string/cpu_backend_dynarmic</item> <item>@string/cpu_backend_dynarmic</item>
<item>@string/cpu_backend_nce</item> <item>@string/cpu_backend_nce</item>

31
src/android/app/src/main/res/values/strings.xml

@ -9,6 +9,37 @@
<string name="notice_notification_channel_description">Shows notifications when something goes wrong.</string> <string name="notice_notification_channel_description">Shows notifications when something goes wrong.</string>
<string name="notification_permission_not_granted">Notification permission not granted!</string> <string name="notification_permission_not_granted">Notification permission not granted!</string>
<!-- Stats Overlay settings -->
<string name="show_stats_overlay">ShoW Performance Stats Overlay</string>
<string name="stats_overlay_customization">Customization</string>
<string name="stats_overlay_items">Visibility</string>
<string name="stats_overlay_options">Overlay</string>
<string name="enable_stats_overlay_">Enable Performance Stats Overlay</string>
<string name="stats_overlay_options_description">Configure what information is shown in the performance stats overlay</string>
<string name="show_fps">Show FPS</string>
<string name="show_fps_description">Display current frames per second</string>
<string name="show_frametime">Show Frametime</string>
<string name="show_frametime_description">Display current frametime</string>
<string name="show_speed">Show Speed</string>
<string name="show_speed_description">Display current emulation speed percentage</string>
<string name="show_app_ram_usage">Show App Memory Usage</string>
<string name="show_app_ram_usage_description">Display the amount of RAM getting used by the emulator</string>
<string name="show_system_ram_usage">Show System Memory Usage</string>
<string name="show_system_ram_usage_description">Display the amount of RAM getting used by the system</string>
<string name="show_bat_temperature">Show Battery Temperature</string>
<string name="show_bat_temperature_description">Display current Battery temperature in Celsius and Fahrenheit</string>
<string name="overlay_position">Overlay Position</string>
<string name="overlay_position_description">Choose where the performance stats overlay is displayed on the screen</string>
<string name="overlay_position_top_left">Top Left</string>
<string name="overlay_position_top_right">Top Right</string>
<string name="overlay_position_bottom_left">Bottom Left</string>
<string name="overlay_position_bottom_right">Bottom Right</string>
<string name="overlay_position_center_top">Center Top</string>
<string name="overlay_position_center_bottom">Center Bottom</string>
<string name="overlay_background">Overlay Background</string>
<string name="overlay_background_description">Adds a background behind the overlay for easier reading</string>
<!-- Setup strings --> <!-- Setup strings -->
<string name="welcome">Welcome!</string> <string name="welcome">Welcome!</string>
<string name="welcome_description">Learn how to setup &lt;b>eden&lt;/b> and jump into emulation.</string> <string name="welcome_description">Learn how to setup &lt;b>eden&lt;/b> and jump into emulation.</string>

1
src/android/app/src/main/res/values/yuzu_colors.xml

@ -229,6 +229,7 @@
<color name="yuzu_onErrorContainer">#410002</color> <color name="yuzu_onErrorContainer">#410002</color>
<color name="yuzu_shadow">#000000</color> <color name="yuzu_shadow">#000000</color>
<color name="yuzu_scrim">#000000</color> <color name="yuzu_scrim">#000000</color>
<color name="yuzu_transparent_black">#80000000</color>
<!-- Values used in dark mode but here are jsut white / black values--> <!-- Values used in dark mode but here are jsut white / black values-->
<color name="yuzu_onPrimary_blue">#FFFFFF</color> <color name="yuzu_onPrimary_blue">#FFFFFF</color>
<color name="yuzu_onSecondary_blue">#FFFFFF</color> <color name="yuzu_onSecondary_blue">#FFFFFF</color>

Loading…
Cancel
Save