mirror of
https://git.eden-emu.dev/eden-emu/eden
synced 2026-04-10 05:28:56 +02:00
[android] fix persist manual game pause after android sleep/wake (#3651)
if user invokes the "pause game" option from the menu while in game, as expected this suspends the process till user manually hits resume.. except for one case: Android sleep/wake lifecycle. If user manually pauses a running game, then sleeps their device, then wakes their device; the game will self-resume without user pressing "resume game". Expected behavior IMO is that if user left the game process in manually paused state, app should respect this and persist the pause on system wake, so that user may manually press "resume game" to unfreeze the process. Simple fix is to have a few params for user initiated pause and resume, and update the pause and run methods to handle as described above. Please let me know if there is a cleaner way to implement! Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3651 Reviewed-by: crueter <crueter@eden-emu.dev> Reviewed-by: CamilleLaVey <camillelavey99@gmail.com> Reviewed-by: DraVee <dravee@eden-emu.dev> Co-authored-by: xXJSONDeruloXx <danielhimebauch@gmail.com> Co-committed-by: xXJSONDeruloXx <danielhimebauch@gmail.com>
This commit is contained in:
parent
ac181b756f
commit
7de5eb6884
8 changed files with 220 additions and 27 deletions
|
|
@ -152,6 +152,10 @@ object NativeLibrary {
|
||||||
|
|
||||||
external fun surfaceDestroyed()
|
external fun surfaceDestroyed()
|
||||||
|
|
||||||
|
external fun getAppletCaptureBuffer(): ByteArray
|
||||||
|
external fun getAppletCaptureWidth(): Int
|
||||||
|
external fun getAppletCaptureHeight(): Int
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unpauses emulation from a paused state.
|
* Unpauses emulation from a paused state.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
|
|
@ -204,9 +204,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause()
|
|
||||||
nfcReader.stopScanning()
|
nfcReader.stopScanning()
|
||||||
stopMotionSensorListener()
|
stopMotionSensorListener()
|
||||||
|
super.onPause()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
|
@ -339,6 +339,10 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSensorChanged(event: SensorEvent) {
|
override fun onSensorChanged(event: SensorEvent) {
|
||||||
|
if (!NativeLibrary.isRunning() || NativeLibrary.isPaused()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
val rotation = this.display?.rotation
|
val rotation = this.display?.rotation
|
||||||
if (rotation == Surface.ROTATION_90) {
|
if (rotation == Surface.ROTATION_90) {
|
||||||
flipMotionOrientation = true
|
flipMotionOrientation = true
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import android.content.Intent
|
||||||
import android.content.IntentFilter
|
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.Bitmap
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.BatteryManager
|
import android.os.BatteryManager
|
||||||
import android.os.BatteryManager.*
|
import android.os.BatteryManager.*
|
||||||
|
|
@ -97,6 +98,7 @@ import org.yuzu.yuzu_emu.utils.collect
|
||||||
import org.yuzu.yuzu_emu.utils.CustomSettingsHandler
|
import org.yuzu.yuzu_emu.utils.CustomSettingsHandler
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.nio.ByteBuffer
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.coroutines.suspendCoroutine
|
import kotlin.coroutines.suspendCoroutine
|
||||||
import kotlin.or
|
import kotlin.or
|
||||||
|
|
@ -141,6 +143,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
|
|
||||||
private var wasInputOverlayAutoHidden = false
|
private var wasInputOverlayAutoHidden = false
|
||||||
private var overlayTouchActive = false
|
private var overlayTouchActive = false
|
||||||
|
private var pausedFrameBitmap: Bitmap? = null
|
||||||
|
|
||||||
var shouldUseCustom = false
|
var shouldUseCustom = false
|
||||||
private var isQuickSettingsMenuOpen = false
|
private var isQuickSettingsMenuOpen = false
|
||||||
|
|
@ -703,6 +706,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
binding.inGameMenu.menu.findItem(R.id.menu_quick_settings)?.isVisible =
|
binding.inGameMenu.menu.findItem(R.id.menu_quick_settings)?.isVisible =
|
||||||
BooleanSetting.ENABLE_QUICK_SETTINGS.getBoolean()
|
BooleanSetting.ENABLE_QUICK_SETTINGS.getBoolean()
|
||||||
|
|
||||||
|
binding.pausedIcon.setOnClickListener {
|
||||||
|
if (this::emulationState.isInitialized && emulationState.isPaused) {
|
||||||
|
resumeEmulationFromUi()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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()
|
||||||
val titleId = if (lockMode == DrawerLayout.LOCK_MODE_LOCKED_CLOSED) {
|
val titleId = if (lockMode == DrawerLayout.LOCK_MODE_LOCKED_CLOSED) {
|
||||||
|
|
@ -728,11 +737,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
when (it.itemId) {
|
when (it.itemId) {
|
||||||
R.id.menu_pause_emulation -> {
|
R.id.menu_pause_emulation -> {
|
||||||
if (emulationState.isPaused) {
|
if (emulationState.isPaused) {
|
||||||
emulationState.run(false)
|
resumeEmulationFromUi()
|
||||||
updatePauseMenuEntry(false)
|
|
||||||
} else {
|
} else {
|
||||||
emulationState.pause()
|
pauseEmulationAndCaptureFrame()
|
||||||
updatePauseMenuEntry(true)
|
|
||||||
}
|
}
|
||||||
binding.inGameMenu.requestFocus()
|
binding.inGameMenu.requestFocus()
|
||||||
true
|
true
|
||||||
|
|
@ -826,6 +833,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.menu_exit -> {
|
R.id.menu_exit -> {
|
||||||
|
clearPausedFrame()
|
||||||
emulationState.stop()
|
emulationState.stop()
|
||||||
NativeConfig.reloadGlobalConfig()
|
NativeConfig.reloadGlobalConfig()
|
||||||
emulationViewModel.setIsEmulationStopping(true)
|
emulationViewModel.setIsEmulationStopping(true)
|
||||||
|
|
@ -1197,6 +1205,71 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun pauseEmulationAndCaptureFrame() {
|
||||||
|
emulationState.pause()
|
||||||
|
updatePauseMenuEntry(true)
|
||||||
|
capturePausedFrameFromCore()
|
||||||
|
updatePausedFrameVisibility()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun capturePausedFrameFromCore() {
|
||||||
|
lifecycleScope.launch(Dispatchers.Default) {
|
||||||
|
val frameData = NativeLibrary.getAppletCaptureBuffer()
|
||||||
|
val width = NativeLibrary.getAppletCaptureWidth()
|
||||||
|
val height = NativeLibrary.getAppletCaptureHeight()
|
||||||
|
if (frameData.isEmpty() || width <= 0 || height <= 0) {
|
||||||
|
Log.warning(
|
||||||
|
"[EmulationFragment] Paused frame capture returned empty/invalid data. " +
|
||||||
|
"size=${frameData.size}, width=$width, height=$height"
|
||||||
|
)
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
val expectedSize = width * height * 4
|
||||||
|
if (frameData.size < expectedSize) {
|
||||||
|
Log.warning(
|
||||||
|
"[EmulationFragment] Paused frame buffer smaller than expected. " +
|
||||||
|
"size=${frameData.size}, expected=$expectedSize"
|
||||||
|
)
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||||
|
bitmap.copyPixelsFromBuffer(ByteBuffer.wrap(frameData, 0, expectedSize))
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
pausedFrameBitmap?.recycle()
|
||||||
|
pausedFrameBitmap = bitmap
|
||||||
|
updatePausedFrameVisibility()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updatePausedFrameVisibility() {
|
||||||
|
val b = _binding ?: return
|
||||||
|
val showPausedUi = this::emulationState.isInitialized && emulationState.isPaused
|
||||||
|
b.pausedIcon.setVisible(showPausedUi)
|
||||||
|
|
||||||
|
val bitmap = if (showPausedUi) pausedFrameBitmap else null
|
||||||
|
b.pausedFrameImage.setImageBitmap(bitmap)
|
||||||
|
b.pausedFrameImage.setVisible(bitmap != null)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resumeEmulationFromUi() {
|
||||||
|
clearPausedFrame()
|
||||||
|
emulationState.resume()
|
||||||
|
updatePauseMenuEntry(emulationState.isPaused)
|
||||||
|
updatePausedFrameVisibility()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun clearPausedFrame() {
|
||||||
|
val b = _binding
|
||||||
|
b?.pausedFrameImage?.setVisible(false)
|
||||||
|
b?.pausedFrameImage?.setImageDrawable(null)
|
||||||
|
pausedFrameBitmap?.recycle()
|
||||||
|
pausedFrameBitmap = null
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleLoadAmiiboSelection(): Boolean {
|
private fun handleLoadAmiiboSelection(): Boolean {
|
||||||
val binding = _binding ?: return true
|
val binding = _binding ?: return true
|
||||||
|
|
||||||
|
|
@ -1290,8 +1363,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
if (this::emulationState.isInitialized) {
|
if (this::emulationState.isInitialized) {
|
||||||
if (emulationState.isRunning && emulationActivity?.isInPictureInPictureMode != true) {
|
if (emulationState.isRunning && emulationActivity?.isInPictureInPictureMode != true) {
|
||||||
emulationState.pause()
|
pauseEmulationAndCaptureFrame()
|
||||||
updatePauseMenuEntry(true)
|
} else {
|
||||||
|
updatePausedFrameVisibility()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
super.onPause()
|
super.onPause()
|
||||||
|
|
@ -1301,6 +1375,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
amiiboLoadJob?.cancel()
|
amiiboLoadJob?.cancel()
|
||||||
amiiboLoadJob = null
|
amiiboLoadJob = null
|
||||||
|
clearPausedFrame()
|
||||||
_binding?.surfaceInputOverlay?.touchEventListener = null
|
_binding?.surfaceInputOverlay?.touchEventListener = null
|
||||||
_binding = null
|
_binding = null
|
||||||
isAmiiboPickerOpen = false
|
isAmiiboPickerOpen = false
|
||||||
|
|
@ -1321,6 +1396,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
b.inGameMenu.post {
|
b.inGameMenu.post {
|
||||||
if (!this::emulationState.isInitialized || _binding == null) return@post
|
if (!this::emulationState.isInitialized || _binding == null) return@post
|
||||||
updatePauseMenuEntry(emulationState.isPaused)
|
updatePauseMenuEntry(emulationState.isPaused)
|
||||||
|
updatePausedFrameVisibility()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1760,6 +1836,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
// Only update surface reference, don't trigger state changes
|
// Only update surface reference, don't trigger state changes
|
||||||
emulationState.updateSurfaceReference(holder.surface)
|
emulationState.updateSurfaceReference(holder.surface)
|
||||||
}
|
}
|
||||||
|
updatePausedFrameVisibility()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun surfaceDestroyed(holder: SurfaceHolder) {
|
override fun surfaceDestroyed(holder: SurfaceHolder) {
|
||||||
|
|
@ -2090,6 +2167,29 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun resume() {
|
||||||
|
if (state != State.PAUSED) {
|
||||||
|
Log.warning("[EmulationFragment] Resume called while emulation is not paused.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!emulationCanStart.invoke()) {
|
||||||
|
Log.warning("[EmulationFragment] Resume blocked by emulationCanStart check.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val currentSurface = surface
|
||||||
|
if (currentSurface == null || !currentSurface.isValid) {
|
||||||
|
Log.debug("[EmulationFragment] Resume requested with invalid surface.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
NativeLibrary.surfaceChanged(currentSurface)
|
||||||
|
Log.debug("[EmulationFragment] Resuming emulation.")
|
||||||
|
NativeLibrary.unpauseEmulation()
|
||||||
|
NativeLibrary.playTimeManagerStart()
|
||||||
|
state = State.RUNNING
|
||||||
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun changeProgram(programIndex: Int) {
|
fun changeProgram(programIndex: Int) {
|
||||||
emulationThread.join()
|
emulationThread.join()
|
||||||
|
|
@ -2111,7 +2211,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun updateSurface() {
|
fun updateSurface() {
|
||||||
if (surface != null) {
|
if (surface != null && state == State.RUNNING) {
|
||||||
NativeLibrary.surfaceChanged(surface)
|
NativeLibrary.surfaceChanged(surface)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2127,20 +2227,20 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun clearSurface() {
|
fun clearSurface() {
|
||||||
if (surface == null) {
|
if (surface == null) {
|
||||||
Log.warning("[EmulationFragment] clearSurface called, but surface already null.")
|
Log.debug("[EmulationFragment] clearSurface called, but surface already null.")
|
||||||
} else {
|
} else {
|
||||||
|
if (state == State.RUNNING) {
|
||||||
|
pause()
|
||||||
|
}
|
||||||
|
NativeLibrary.surfaceDestroyed()
|
||||||
surface = null
|
surface = null
|
||||||
Log.debug("[EmulationFragment] Surface destroyed.")
|
Log.debug("[EmulationFragment] Surface destroyed.")
|
||||||
when (state) {
|
when (state) {
|
||||||
State.RUNNING -> {
|
State.PAUSED -> Log.debug(
|
||||||
state = State.PAUSED
|
|
||||||
}
|
|
||||||
|
|
||||||
State.PAUSED -> Log.warning(
|
|
||||||
"[EmulationFragment] Surface cleared while emulation paused."
|
"[EmulationFragment] Surface cleared while emulation paused."
|
||||||
)
|
)
|
||||||
|
|
||||||
else -> Log.warning(
|
else -> Log.debug(
|
||||||
"[EmulationFragment] Surface cleared while emulation stopped."
|
"[EmulationFragment] Surface cleared while emulation stopped."
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -2148,29 +2248,35 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun runWithValidSurface(programIndex: Int = 0) {
|
private fun runWithValidSurface(programIndex: Int = 0) {
|
||||||
NativeLibrary.surfaceChanged(surface)
|
|
||||||
if (!emulationCanStart.invoke()) {
|
if (!emulationCanStart.invoke()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
val currentSurface = surface
|
||||||
|
if (currentSurface == null || !currentSurface.isValid) {
|
||||||
|
Log.debug("[EmulationFragment] runWithValidSurface called with invalid surface.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
when (state) {
|
when (state) {
|
||||||
State.STOPPED -> {
|
State.STOPPED -> {
|
||||||
|
NativeLibrary.surfaceChanged(currentSurface)
|
||||||
emulationThread = Thread({
|
emulationThread = Thread({
|
||||||
Log.debug("[EmulationFragment] Starting emulation thread.")
|
Log.debug("[EmulationFragment] Starting emulation thread.")
|
||||||
NativeLibrary.run(gamePath, programIndex, true)
|
NativeLibrary.run(gamePath, programIndex, true)
|
||||||
}, "NativeEmulation")
|
}, "NativeEmulation")
|
||||||
emulationThread.start()
|
emulationThread.start()
|
||||||
|
state = State.RUNNING
|
||||||
}
|
}
|
||||||
|
|
||||||
State.PAUSED -> {
|
State.PAUSED -> {
|
||||||
Log.debug("[EmulationFragment] Resuming emulation.")
|
Log.debug(
|
||||||
NativeLibrary.unpauseEmulation()
|
"[EmulationFragment] Surface restored while emulation paused; " +
|
||||||
NativeLibrary.playTimeManagerStart()
|
"waiting for explicit resume."
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> Log.debug("[EmulationFragment] Bug, run called while already running.")
|
else -> Log.debug("[EmulationFragment] Bug, run called while already running.")
|
||||||
}
|
}
|
||||||
state = State.RUNNING
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum class State {
|
private enum class State {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.overlay
|
package org.yuzu.yuzu_emu.overlay
|
||||||
|
|
@ -20,7 +20,6 @@ import android.os.Looper
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.HapticFeedbackConstants
|
import android.view.HapticFeedbackConstants
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.SurfaceView
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.View.OnTouchListener
|
import android.view.View.OnTouchListener
|
||||||
import android.view.WindowInsets
|
import android.view.WindowInsets
|
||||||
|
|
@ -42,10 +41,10 @@ import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws the interactive input overlay on top of the
|
* Draws the interactive input overlay on top of the
|
||||||
* [SurfaceView] that is rendering emulation.
|
* emulation rendering surface.
|
||||||
*/
|
*/
|
||||||
class InputOverlay(context: Context, attrs: AttributeSet?) :
|
class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||||
SurfaceView(context, attrs),
|
View(context, attrs),
|
||||||
OnTouchListener {
|
OnTouchListener {
|
||||||
private val overlayButtons: MutableSet<InputOverlayDrawableButton> = HashSet()
|
private val overlayButtons: MutableSet<InputOverlayDrawableButton> = HashSet()
|
||||||
private val overlayDpads: MutableSet<InputOverlayDrawableDpad> = HashSet()
|
private val overlayDpads: MutableSet<InputOverlayDrawableDpad> = HashSet()
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
|
@ -14,6 +17,14 @@
|
||||||
#include "jni/native.h"
|
#include "jni/native.h"
|
||||||
|
|
||||||
void EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) {
|
void EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) {
|
||||||
|
if (!surface) {
|
||||||
|
LOG_INFO(Frontend, "EmuWindow_Android::OnSurfaceChanged received null surface");
|
||||||
|
m_window_width = 0;
|
||||||
|
m_window_height = 0;
|
||||||
|
window_info.render_surface = nullptr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
m_window_width = ANativeWindow_getWidth(surface);
|
m_window_width = ANativeWindow_getWidth(surface);
|
||||||
m_window_height = ANativeWindow_getHeight(surface);
|
m_window_height = ANativeWindow_getHeight(surface);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,8 @@
|
||||||
#include "jni/native.h"
|
#include "jni/native.h"
|
||||||
#include "video_core/renderer_base.h"
|
#include "video_core/renderer_base.h"
|
||||||
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
||||||
|
#include "video_core/capture.h"
|
||||||
|
#include "video_core/textures/decoders.h"
|
||||||
#include "video_core/vulkan_common/vulkan_instance.h"
|
#include "video_core/vulkan_common/vulkan_instance.h"
|
||||||
#include "video_core/vulkan_common/vulkan_surface.h"
|
#include "video_core/vulkan_common/vulkan_surface.h"
|
||||||
#include "video_core/shader_notify.h"
|
#include "video_core/shader_notify.h"
|
||||||
|
|
@ -780,9 +782,10 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceChanged(JNIEnv* env, jobject i
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceDestroyed(JNIEnv* env, jobject instance) {
|
void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceDestroyed(JNIEnv* env, jobject instance) {
|
||||||
ANativeWindow_release(EmulationSession::GetInstance().NativeWindow());
|
if (auto* native_window = EmulationSession::GetInstance().NativeWindow(); native_window) {
|
||||||
|
ANativeWindow_release(native_window);
|
||||||
|
}
|
||||||
EmulationSession::GetInstance().SetNativeWindow(nullptr);
|
EmulationSession::GetInstance().SetNativeWindow(nullptr);
|
||||||
EmulationSession::GetInstance().SurfaceChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env, jobject instance,
|
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env, jobject instance,
|
||||||
|
|
@ -969,6 +972,40 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isPaused(JNIEnv* env, jclass claz
|
||||||
return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused());
|
return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jbyteArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getAppletCaptureBuffer(JNIEnv* env, jclass clazz) {
|
||||||
|
using namespace VideoCore::Capture;
|
||||||
|
|
||||||
|
if (!EmulationSession::GetInstance().IsRunning()) {
|
||||||
|
return env->NewByteArray(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto tiled = EmulationSession::GetInstance().System().GPU().GetAppletCaptureBuffer();
|
||||||
|
if (tiled.size() < TiledSize) {
|
||||||
|
return env->NewByteArray(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u8> linear(LinearWidth * LinearHeight * BytesPerPixel);
|
||||||
|
Tegra::Texture::UnswizzleTexture(linear, tiled, BytesPerPixel, LinearWidth, LinearHeight,
|
||||||
|
LinearDepth, BlockHeight, BlockDepth);
|
||||||
|
|
||||||
|
auto buffer = env->NewByteArray(static_cast<jsize>(linear.size()));
|
||||||
|
if (!buffer) {
|
||||||
|
return env->NewByteArray(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
env->SetByteArrayRegion(buffer, 0, static_cast<jsize>(linear.size()),
|
||||||
|
reinterpret_cast<const jbyte*>(linear.data()));
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
jint Java_org_yuzu_yuzu_1emu_NativeLibrary_getAppletCaptureWidth(JNIEnv* env, jclass clazz) {
|
||||||
|
return static_cast<jint>(VideoCore::Capture::LinearWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
jint Java_org_yuzu_yuzu_1emu_NativeLibrary_getAppletCaptureHeight(JNIEnv* env, jclass clazz) {
|
||||||
|
return static_cast<jint>(VideoCore::Capture::LinearHeight);
|
||||||
|
}
|
||||||
|
|
||||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeSystem(JNIEnv* env, jclass clazz,
|
void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeSystem(JNIEnv* env, jclass clazz,
|
||||||
jboolean reload) {
|
jboolean reload) {
|
||||||
// Initialize the emulated system.
|
// Initialize the emulated system.
|
||||||
|
|
|
||||||
4
src/android/app/src/main/res/drawable/circle_white.xml
Normal file
4
src/android/app/src/main/res/drawable/circle_white.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
|
||||||
|
<solid android:color="#E6FFFFFF" />
|
||||||
|
</shape>
|
||||||
|
|
@ -108,6 +108,22 @@
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/paused_frame_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:fitsSystemWindows="false">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/paused_frame_image"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/input_container"
|
android:id="@+id/input_container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
@ -142,6 +158,18 @@
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:fitsSystemWindows="false">
|
android:fitsSystemWindows="false">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/paused_icon"
|
||||||
|
android:layout_width="64dp"
|
||||||
|
android:layout_height="64dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:background="@drawable/circle_white"
|
||||||
|
android:contentDescription="@string/emulation_unpause"
|
||||||
|
android:padding="14dp"
|
||||||
|
android:src="@drawable/ic_play"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:tint="@android:color/black" />
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
<com.google.android.material.textview.MaterialTextView
|
||||||
android:id="@+id/show_stats_overlay_text"
|
android:id="@+id/show_stats_overlay_text"
|
||||||
style="@style/TextAppearance.Material3.BodySmall"
|
style="@style/TextAppearance.Material3.BodySmall"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue