mirror of
https://github.com/izzy2lost/xemu.git
synced 2026-07-06 00:20:22 -07:00
added fps counter
This commit is contained in:
@@ -387,8 +387,44 @@ static struct {
|
||||
.cond = PTHREAD_COND_INITIALIZER,
|
||||
};
|
||||
|
||||
static struct {
|
||||
pthread_mutex_t lock;
|
||||
uint64_t window_start_ms;
|
||||
uint64_t last_frame_ms;
|
||||
uint32_t frame_count;
|
||||
float fps;
|
||||
} g_fps_state = {
|
||||
.lock = PTHREAD_MUTEX_INITIALIZER,
|
||||
};
|
||||
|
||||
static void update_android_fps_counter(void)
|
||||
{
|
||||
const uint64_t now_ms = (uint64_t)SDL_GetTicks64();
|
||||
|
||||
pthread_mutex_lock(&g_fps_state.lock);
|
||||
|
||||
if (g_fps_state.window_start_ms == 0) {
|
||||
g_fps_state.window_start_ms = now_ms;
|
||||
}
|
||||
|
||||
g_fps_state.last_frame_ms = now_ms;
|
||||
g_fps_state.frame_count++;
|
||||
|
||||
const uint64_t elapsed_ms = now_ms - g_fps_state.window_start_ms;
|
||||
if (elapsed_ms >= 500) {
|
||||
g_fps_state.fps = ((float)g_fps_state.frame_count * 1000.0f) /
|
||||
(float)elapsed_ms;
|
||||
g_fps_state.frame_count = 0;
|
||||
g_fps_state.window_start_ms = now_ms;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&g_fps_state.lock);
|
||||
}
|
||||
|
||||
void xemu_android_process_snapshot_request(void)
|
||||
{
|
||||
update_android_fps_counter();
|
||||
|
||||
if (pthread_mutex_trylock(&g_snap_req.lock) != 0) {
|
||||
return;
|
||||
}
|
||||
@@ -460,3 +496,27 @@ Java_com_izzy2lost_x1box_MainActivity_nativeLoadSnapshot(
|
||||
(void)obj;
|
||||
return dispatch_snapshot(env, name, SNAP_LOAD);
|
||||
}
|
||||
|
||||
JNIEXPORT jfloat JNICALL
|
||||
Java_com_izzy2lost_x1box_MainActivity_nativeGetFps(
|
||||
JNIEnv *env, jobject obj)
|
||||
{
|
||||
float fps = 0.0f;
|
||||
const uint64_t now_ms = (uint64_t)SDL_GetTicks64();
|
||||
(void)env;
|
||||
(void)obj;
|
||||
|
||||
pthread_mutex_lock(&g_fps_state.lock);
|
||||
fps = g_fps_state.fps;
|
||||
if (g_fps_state.last_frame_ms == 0 ||
|
||||
(now_ms - g_fps_state.last_frame_ms) > 1500) {
|
||||
fps = 0.0f;
|
||||
}
|
||||
pthread_mutex_unlock(&g_fps_state.lock);
|
||||
|
||||
if (fps < 0.0f) {
|
||||
fps = 0.0f;
|
||||
}
|
||||
|
||||
return (jfloat)fps;
|
||||
}
|
||||
|
||||
@@ -3,10 +3,16 @@ package com.izzy2lost.x1box
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.graphics.Typeface
|
||||
import android.hardware.input.InputManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.TypedValue
|
||||
import android.view.Gravity
|
||||
import android.view.InputDevice
|
||||
import android.view.KeyEvent
|
||||
import android.view.View
|
||||
@@ -17,6 +23,7 @@ import android.widget.BaseAdapter
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import android.widget.ListView
|
||||
import android.widget.RelativeLayout
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
@@ -25,12 +32,14 @@ import org.libsdl.app.SDLActivity
|
||||
import java.io.File
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.util.Locale
|
||||
|
||||
class MainActivity : SDLActivity(), InputManager.InputDeviceListener {
|
||||
companion object {
|
||||
const val EXTRA_AUTO_LOAD_SNAPSHOT_SLOT = "com.izzy2lost.x1box.extra.AUTO_LOAD_SNAPSHOT_SLOT"
|
||||
private const val SNAPSHOT_PREVIEW_HEADER_SIZE = 12
|
||||
private const val TOTAL_SNAPSHOT_SLOTS = 10
|
||||
private const val PREF_SHOW_FPS_COUNTER = "setting_show_fps_counter"
|
||||
}
|
||||
|
||||
private data class SnapshotSlotPreview(
|
||||
@@ -51,6 +60,17 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener {
|
||||
private var comboTriggered = false
|
||||
private var startupSnapshotSlot: Int? = null
|
||||
private var startupSnapshotLoadScheduled = false
|
||||
private var showFpsCounter = false
|
||||
private var fpsCounterView: TextView? = null
|
||||
private val fpsUpdateHandler = Handler(Looper.getMainLooper())
|
||||
private val fpsUpdateRunnable = object : Runnable {
|
||||
override fun run() {
|
||||
val view = fpsCounterView ?: return
|
||||
val fps = nativeGetFps().coerceAtLeast(0f)
|
||||
view.text = String.format(Locale.US, "FPS %.1f", fps)
|
||||
fpsUpdateHandler.postDelayed(this, 500L)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -58,7 +78,11 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener {
|
||||
if (requestedSlot in 1..TOTAL_SNAPSHOT_SLOTS) {
|
||||
startupSnapshotSlot = requestedSlot
|
||||
}
|
||||
showFpsCounter = isFpsCounterEnabled()
|
||||
setupOnScreenController()
|
||||
if (showFpsCounter) {
|
||||
setupFpsCounter()
|
||||
}
|
||||
setupControllerDetection()
|
||||
hideSystemUI()
|
||||
}
|
||||
@@ -141,6 +165,107 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener {
|
||||
updateControllerVisibility()
|
||||
}
|
||||
|
||||
private fun dpToPx(dp: Int): Int = (dp * resources.displayMetrics.density).toInt()
|
||||
|
||||
private fun isFpsCounterEnabled(): Boolean {
|
||||
return getSharedPreferences("x1box_prefs", MODE_PRIVATE)
|
||||
.getBoolean(PREF_SHOW_FPS_COUNTER, false)
|
||||
}
|
||||
|
||||
private fun applyFpsCounterPreference() {
|
||||
val shouldShow = isFpsCounterEnabled()
|
||||
if (shouldShow == showFpsCounter) {
|
||||
if (showFpsCounter && fpsCounterView == null) {
|
||||
setupFpsCounter()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
showFpsCounter = shouldShow
|
||||
if (showFpsCounter) {
|
||||
setupFpsCounter()
|
||||
startFpsUpdates()
|
||||
} else {
|
||||
stopFpsUpdates()
|
||||
fpsCounterView?.let { view ->
|
||||
(view.parent as? ViewGroup)?.removeView(view)
|
||||
}
|
||||
fpsCounterView = null
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupFpsCounter() {
|
||||
if (fpsCounterView != null) {
|
||||
return
|
||||
}
|
||||
|
||||
val parent = mLayout ?: return
|
||||
val baseTopMargin = dpToPx(12)
|
||||
val counter = TextView(this).apply {
|
||||
text = "FPS 0.0"
|
||||
setTextColor(Color.WHITE)
|
||||
setTypeface(Typeface.MONOSPACE, Typeface.BOLD)
|
||||
setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f)
|
||||
setPadding(dpToPx(10), dpToPx(4), dpToPx(10), dpToPx(4))
|
||||
setBackgroundColor(Color.argb(150, 0, 0, 0))
|
||||
isClickable = false
|
||||
isFocusable = false
|
||||
importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
|
||||
}
|
||||
|
||||
counter.layoutParams = if (parent is RelativeLayout) {
|
||||
RelativeLayout.LayoutParams(
|
||||
RelativeLayout.LayoutParams.WRAP_CONTENT,
|
||||
RelativeLayout.LayoutParams.WRAP_CONTENT
|
||||
).apply {
|
||||
addRule(RelativeLayout.ALIGN_PARENT_TOP)
|
||||
addRule(RelativeLayout.CENTER_HORIZONTAL)
|
||||
topMargin = baseTopMargin
|
||||
}
|
||||
} else {
|
||||
FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.WRAP_CONTENT,
|
||||
FrameLayout.LayoutParams.WRAP_CONTENT
|
||||
).apply {
|
||||
gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
|
||||
topMargin = baseTopMargin
|
||||
}
|
||||
}
|
||||
|
||||
counter.setOnApplyWindowInsetsListener { view, insets ->
|
||||
val topInset = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
insets.getInsets(WindowInsets.Type.statusBars() or WindowInsets.Type.displayCutout()).top
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
insets.systemWindowInsetTop
|
||||
}
|
||||
|
||||
val layoutParams = view.layoutParams
|
||||
if (layoutParams is ViewGroup.MarginLayoutParams) {
|
||||
val wantedTopMargin = baseTopMargin + topInset
|
||||
if (layoutParams.topMargin != wantedTopMargin) {
|
||||
layoutParams.topMargin = wantedTopMargin
|
||||
view.layoutParams = layoutParams
|
||||
}
|
||||
}
|
||||
insets
|
||||
}
|
||||
|
||||
fpsCounterView = counter
|
||||
parent.addView(counter)
|
||||
counter.bringToFront()
|
||||
counter.requestApplyInsets()
|
||||
}
|
||||
|
||||
private fun startFpsUpdates() {
|
||||
fpsUpdateHandler.removeCallbacks(fpsUpdateRunnable)
|
||||
fpsUpdateRunnable.run()
|
||||
}
|
||||
|
||||
private fun stopFpsUpdates() {
|
||||
fpsUpdateHandler.removeCallbacks(fpsUpdateRunnable)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
@@ -150,7 +275,16 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener {
|
||||
registerVirtualController()
|
||||
}, 1000)
|
||||
|
||||
applyFpsCounterPreference()
|
||||
scheduleStartupSnapshotLoadIfRequested()
|
||||
if (showFpsCounter) {
|
||||
startFpsUpdates()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
stopFpsUpdates()
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
private fun scheduleStartupSnapshotLoadIfRequested() {
|
||||
@@ -260,6 +394,7 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener {
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
stopFpsUpdates()
|
||||
inGameMenuDialog?.dismiss()
|
||||
inGameMenuDialog = null
|
||||
|
||||
@@ -271,6 +406,7 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener {
|
||||
}
|
||||
|
||||
inputManager?.unregisterInputDeviceListener(this)
|
||||
fpsCounterView = null
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
@@ -346,6 +482,7 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener {
|
||||
|
||||
private external fun nativeSaveSnapshot(name: String): Boolean
|
||||
private external fun nativeLoadSnapshot(name: String): Boolean
|
||||
private external fun nativeGetFps(): Float
|
||||
|
||||
private fun slotName(slot: Int) = "android_slot_$slot"
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ class SettingsActivity : AppCompatActivity() {
|
||||
val switchHrtf = findViewById<MaterialSwitch>(R.id.switch_hrtf)
|
||||
val switchShaders = findViewById<MaterialSwitch>(R.id.switch_cache_shaders)
|
||||
val switchFpu = findViewById<MaterialSwitch>(R.id.switch_hard_fpu)
|
||||
val switchShowFps = findViewById<MaterialSwitch>(R.id.switch_show_fps_counter)
|
||||
val toggleAudioDriver = findViewById<MaterialButtonToggleGroup>(R.id.toggle_audio_driver)
|
||||
val btnSave = findViewById<MaterialButton>(R.id.btn_settings_save)
|
||||
tvVulkanDriverName = findViewById(R.id.tv_vulkan_driver_name)
|
||||
@@ -83,6 +84,7 @@ class SettingsActivity : AppCompatActivity() {
|
||||
switchHrtf.isChecked = prefs.getBoolean("setting_hrtf", true)
|
||||
switchShaders.isChecked = prefs.getBoolean("setting_cache_shaders", true)
|
||||
switchFpu.isChecked = prefs.getBoolean("setting_hard_fpu", true)
|
||||
switchShowFps.isChecked = prefs.getBoolean("setting_show_fps_counter", false)
|
||||
|
||||
val audioDriver = prefs.getString("setting_audio_driver", "openslES") ?: "openslES"
|
||||
when (audioDriver) {
|
||||
@@ -131,6 +133,7 @@ class SettingsActivity : AppCompatActivity() {
|
||||
.putBoolean("setting_hrtf", switchHrtf.isChecked)
|
||||
.putBoolean("setting_cache_shaders", switchShaders.isChecked)
|
||||
.putBoolean("setting_hard_fpu", switchFpu.isChecked)
|
||||
.putBoolean("setting_show_fps_counter", switchShowFps.isChecked)
|
||||
.putString("setting_audio_driver", selectedAudioDriver)
|
||||
|
||||
when {
|
||||
|
||||
@@ -140,6 +140,14 @@
|
||||
|
||||
</com.google.android.material.button.MaterialButtonToggleGroup>
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/switch_show_fps_counter"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@string/settings_show_fps_counter"
|
||||
android:textColor="@color/xemu_text_muted" />
|
||||
|
||||
<!-- ── VULKAN DRIVER ── -->
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -89,6 +89,7 @@
|
||||
<string name="settings_display_mode_stretch">Stretch</string>
|
||||
<string name="settings_display_mode_4_3">4:3</string>
|
||||
<string name="settings_display_mode_16_9">16:9</string>
|
||||
<string name="settings_show_fps_counter">Show FPS Counter</string>
|
||||
|
||||
<string name="settings_resolution_scale">Internal Resolution Scale</string>
|
||||
<string name="settings_resolution_scale_1x">1x (Native 480p)</string>
|
||||
|
||||
Reference in New Issue
Block a user