Android: when the surface is lost, pause the game and allow coming back to it (#358)
This commit is contained in:
parent
007d540b29
commit
3ae90631bd
|
|
@ -14,8 +14,10 @@
|
|||
android:theme="@style/Theme.NxEmu.Splash">
|
||||
<activity
|
||||
android:name="org.nxemu.ui.main.MainActivity"
|
||||
android:alwaysRetainTaskState="true"
|
||||
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/Theme.NxEmu.Splash">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
|
@ -34,6 +36,7 @@
|
|||
android:name="org.nxemu.ui.emulation.EmulationActivity"
|
||||
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|smallestScreenSize|density"
|
||||
android:exported="false"
|
||||
android:launchMode="singleTop"
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:theme="@style/Theme.NxEmu" />
|
||||
<activity
|
||||
|
|
|
|||
|
|
@ -49,7 +49,14 @@ object NativeLibrary {
|
|||
|
||||
external fun emulationSurfaceReady(surface: android.view.Surface, pixelRatio: Float, romPath: String): Boolean
|
||||
external fun emulationSurfaceDestroyed()
|
||||
external fun emulationStopped()
|
||||
external fun surfaceChanged(surface: android.view.Surface)
|
||||
external fun pauseEmulation()
|
||||
external fun unpauseEmulation()
|
||||
external fun isPaused(): Boolean
|
||||
external fun getAppletCaptureBuffer(): ByteArray
|
||||
external fun getAppletCaptureWidth(): Int
|
||||
external fun getAppletCaptureHeight(): Int
|
||||
|
||||
/** Used from native [yuzu_common/fs/fs_android.cpp] via RegisterCallbacks. */
|
||||
@JvmStatic
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package org.nxemu.ui.emulation
|
||||
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.ImageDecoder
|
||||
import android.graphics.PixelFormat
|
||||
|
|
@ -50,6 +51,11 @@ class EmulationActivity : ComponentActivity(), SurfaceHolder.Callback {
|
|||
Thread(r, "nxemu-emu").apply { isDaemon = true }
|
||||
}
|
||||
private val nativeSurfaceSessionOpen = AtomicBoolean(false)
|
||||
private var explicitExit = false
|
||||
private var leavingApp = false
|
||||
private lateinit var pausedFrameImage: ImageView
|
||||
private val captureInFlight = AtomicBoolean(false)
|
||||
private val pausedFrameListener = { updatePauseLabel() }
|
||||
private val perfStatsHandler = Handler(Looper.getMainLooper())
|
||||
private var perfStatsUpdater: Runnable? = null
|
||||
private lateinit var loadingIndicator: View
|
||||
|
|
@ -61,6 +67,8 @@ class EmulationActivity : ComponentActivity(), SurfaceHolder.Callback {
|
|||
private lateinit var showDeviceText: TextView
|
||||
private lateinit var surfaceInputOverlay: InputOverlay
|
||||
private lateinit var drawerLayout: DrawerLayout
|
||||
private lateinit var pauseItem: TextView
|
||||
private lateinit var pausedIcon: ImageView
|
||||
private lateinit var lockDrawerItem: TextView
|
||||
private lateinit var overlayAppVersion: String
|
||||
private lateinit var overlayPhoneModel: String
|
||||
|
|
@ -102,6 +110,18 @@ class EmulationActivity : ComponentActivity(), SurfaceHolder.Callback {
|
|||
surfaceInputOverlay.setZOrderMediaOverlay(true)
|
||||
surfaceInputOverlay.holder.setFormat(PixelFormat.TRANSLUCENT)
|
||||
drawerLayout = findViewById(R.id.drawer_layout)
|
||||
pauseItem = findViewById(R.id.menu_pause)
|
||||
pausedIcon = findViewById(R.id.paused_icon)
|
||||
pausedFrameImage = findViewById(R.id.paused_frame_image)
|
||||
frameListener = pausedFrameListener
|
||||
pausedIcon.setOnClickListener {
|
||||
if (NativeLibrary.isPaused()) {
|
||||
resumeAfterBackground.set(false)
|
||||
NativeLibrary.unpauseEmulation()
|
||||
clearPausedFrame()
|
||||
updatePauseLabel()
|
||||
}
|
||||
}
|
||||
lockDrawerItem = findViewById(R.id.menu_lock_drawer)
|
||||
setupInGameDrawer()
|
||||
updateInputOverlayLayout()
|
||||
|
|
@ -141,8 +161,22 @@ class EmulationActivity : ComponentActivity(), SurfaceHolder.Callback {
|
|||
}
|
||||
}
|
||||
|
||||
override fun onUserLeaveHint() {
|
||||
leavingApp = true
|
||||
super.onUserLeaveHint()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
leavingApp = false
|
||||
val surface = findViewById<SurfaceView>(R.id.emulation_surface).holder.surface
|
||||
if (surface != null && surface.isValid) {
|
||||
unpauseIfBackgrounded()
|
||||
}
|
||||
updatePauseLabel()
|
||||
if (NativeLibrary.isPaused() && pausedFrame == null) {
|
||||
capturePausedFrame()
|
||||
}
|
||||
InputHandler.updateControllerData()
|
||||
if (::surfaceInputOverlay.isInitialized) {
|
||||
updateInputOverlayLayout()
|
||||
|
|
@ -150,6 +184,22 @@ class EmulationActivity : ComponentActivity(), SurfaceHolder.Callback {
|
|||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
releaseDrawerStick()
|
||||
val leftApp = leavingApp
|
||||
if (!NativeLibrary.isPaused()) {
|
||||
NativeLibrary.pauseEmulation()
|
||||
if (!leftApp) {
|
||||
resumeAfterBackground.set(true)
|
||||
}
|
||||
}
|
||||
if (leftApp) {
|
||||
capturePausedFrame()
|
||||
}
|
||||
leavingApp = false
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||
if (event.keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
return super.dispatchKeyEvent(event)
|
||||
|
|
@ -195,6 +245,7 @@ class EmulationActivity : ComponentActivity(), SurfaceHolder.Callback {
|
|||
|
||||
override fun onDrawerOpened(drawerView: View) {
|
||||
drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
|
||||
updatePauseLabel()
|
||||
drawerMenuItems().first().requestFocus()
|
||||
}
|
||||
|
||||
|
|
@ -206,14 +257,84 @@ class EmulationActivity : ComponentActivity(), SurfaceHolder.Callback {
|
|||
}
|
||||
}
|
||||
})
|
||||
pauseItem.setOnClickListener { togglePause() }
|
||||
lockDrawerItem.setOnClickListener { toggleDrawerLock() }
|
||||
findViewById<View>(R.id.menu_exit).setOnClickListener {
|
||||
drawerLayout.closeDrawers()
|
||||
explicitExit = true
|
||||
activeGamePath = null
|
||||
clearPausedFrame()
|
||||
finish()
|
||||
}
|
||||
updatePauseLabel()
|
||||
updateLockDrawerLabel()
|
||||
}
|
||||
|
||||
private fun togglePause() {
|
||||
if (NativeLibrary.isPaused()) {
|
||||
NativeLibrary.unpauseEmulation()
|
||||
clearPausedFrame()
|
||||
} else {
|
||||
NativeLibrary.pauseEmulation()
|
||||
capturePausedFrame()
|
||||
}
|
||||
updatePauseLabel()
|
||||
}
|
||||
|
||||
private fun updatePauseLabel() {
|
||||
val paused = NativeLibrary.isPaused()
|
||||
pauseItem.text = getString(
|
||||
if (paused) R.string.emulation_unpause else R.string.emulation_pause,
|
||||
)
|
||||
if (::pausedIcon.isInitialized) {
|
||||
pausedIcon.visibility = if (paused) View.VISIBLE else View.GONE
|
||||
}
|
||||
if (::pausedFrameImage.isInitialized) {
|
||||
val frame = if (paused) pausedFrame else null
|
||||
pausedFrameImage.setImageBitmap(frame)
|
||||
pausedFrameImage.visibility = if (frame != null) View.VISIBLE else View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
private fun capturePausedFrame() {
|
||||
if (!captureInFlight.compareAndSet(false, true)) {
|
||||
return
|
||||
}
|
||||
executor.execute {
|
||||
try {
|
||||
val width = NativeLibrary.getAppletCaptureWidth()
|
||||
val height = NativeLibrary.getAppletCaptureHeight()
|
||||
val frame = NativeLibrary.getAppletCaptureBuffer()
|
||||
val expected = width * height * 4
|
||||
if (width <= 0 || height <= 0 || frame.size < expected) {
|
||||
return@execute
|
||||
}
|
||||
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||
bitmap.copyPixelsFromBuffer(ByteBuffer.wrap(frame, 0, expected))
|
||||
val previous = pausedFrame
|
||||
pausedFrame = bitmap
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
frameListener?.invoke()
|
||||
if (previous != null && previous !== pausedFrame) {
|
||||
previous.recycle()
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
captureInFlight.set(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearPausedFrame() {
|
||||
val previous = pausedFrame
|
||||
pausedFrame = null
|
||||
if (::pausedFrameImage.isInitialized) {
|
||||
pausedFrameImage.setImageDrawable(null)
|
||||
pausedFrameImage.visibility = View.GONE
|
||||
}
|
||||
previous?.recycle()
|
||||
}
|
||||
|
||||
private fun releaseOverlayTouches() {
|
||||
val event = MotionEvent.obtain(
|
||||
SystemClock.uptimeMillis(),
|
||||
|
|
@ -253,6 +374,7 @@ class EmulationActivity : ComponentActivity(), SurfaceHolder.Callback {
|
|||
|
||||
private fun drawerMenuItems(): List<View> {
|
||||
return listOf(
|
||||
pauseItem,
|
||||
lockDrawerItem,
|
||||
findViewById(R.id.menu_exit),
|
||||
)
|
||||
|
|
@ -468,45 +590,43 @@ class EmulationActivity : ComponentActivity(), SurfaceHolder.Callback {
|
|||
}
|
||||
}
|
||||
|
||||
override fun surfaceCreated(holder: SurfaceHolder) {
|
||||
if (!nativeSurfaceSessionOpen.compareAndSet(false, true)) {
|
||||
Log.w(TAG, "Ignoring extra surfaceCreated")
|
||||
override fun surfaceCreated(holder: SurfaceHolder) = Unit
|
||||
|
||||
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
|
||||
if (width <= 0 || height <= 0) {
|
||||
return
|
||||
}
|
||||
val path = intent.getStringExtra(EXTRA_GAME_PATH)
|
||||
if (path.isNullOrEmpty()) {
|
||||
Log.e(TAG, "Missing EXTRA_GAME_PATH")
|
||||
nativeSurfaceSessionOpen.set(false)
|
||||
finish()
|
||||
return
|
||||
}
|
||||
val ratio = resources.displayMetrics.density
|
||||
val surface = holder.surface
|
||||
val ratio = resources.displayMetrics.density
|
||||
executor.execute {
|
||||
try {
|
||||
if (!NativeLibrary.emulationSurfaceReady(surface, ratio, path)) {
|
||||
Log.e(TAG, "emulationSurfaceReady returned false")
|
||||
nativeSurfaceSessionOpen.set(false)
|
||||
runOnUiThread { finish() }
|
||||
if (!nativeSurfaceSessionOpen.get()) {
|
||||
if (!NativeLibrary.emulationSurfaceReady(surface, ratio, path)) {
|
||||
Log.e(TAG, "emulationSurfaceReady returned false")
|
||||
runOnUiThread { finish() }
|
||||
} else {
|
||||
nativeSurfaceSessionOpen.set(true)
|
||||
activeGamePath = path
|
||||
InputHandler.updateControllerData()
|
||||
unpauseIfBackgrounded()
|
||||
runOnUiThread { updatePauseLabel() }
|
||||
}
|
||||
} else {
|
||||
NativeLibrary.surfaceChanged(surface)
|
||||
unpauseIfBackgrounded()
|
||||
runOnUiThread { updatePauseLabel() }
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
Log.e(TAG, "emulationSurfaceReady failed", e)
|
||||
nativeSurfaceSessionOpen.set(false)
|
||||
runOnUiThread { finish() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
|
||||
if (!nativeSurfaceSessionOpen.get() || width <= 0 || height <= 0) {
|
||||
return
|
||||
}
|
||||
val surface = holder.surface
|
||||
executor.execute {
|
||||
try {
|
||||
NativeLibrary.surfaceChanged(surface)
|
||||
} catch (e: Throwable) {
|
||||
Log.e(TAG, "surfaceChanged failed", e)
|
||||
if (!nativeSurfaceSessionOpen.get()) {
|
||||
runOnUiThread { finish() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -514,27 +634,25 @@ class EmulationActivity : ComponentActivity(), SurfaceHolder.Callback {
|
|||
override fun surfaceDestroyed(holder: SurfaceHolder) {
|
||||
try {
|
||||
executor.submit {
|
||||
try {
|
||||
NativeLibrary.emulationSurfaceDestroyed()
|
||||
} finally {
|
||||
nativeSurfaceSessionOpen.set(false)
|
||||
}
|
||||
NativeLibrary.emulationSurfaceDestroyed()
|
||||
}.get(120, TimeUnit.SECONDS)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "emulationSurfaceDestroyed failed", e)
|
||||
nativeSurfaceSessionOpen.set(false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
if (frameListener === pausedFrameListener) {
|
||||
frameListener = null
|
||||
}
|
||||
NativeLibrary.removeSettingChangedListener(settingChangedListener)
|
||||
stopPerfOverlay()
|
||||
stopAnimatedDrawables()
|
||||
if (nativeSurfaceSessionOpen.get()) {
|
||||
if (explicitExit && nativeSurfaceSessionOpen.get()) {
|
||||
try {
|
||||
executor.submit {
|
||||
try {
|
||||
NativeLibrary.emulationSurfaceDestroyed()
|
||||
NativeLibrary.emulationStopped()
|
||||
} finally {
|
||||
nativeSurfaceSessionOpen.set(false)
|
||||
}
|
||||
|
|
@ -555,7 +673,21 @@ class EmulationActivity : ComponentActivity(), SurfaceHolder.Callback {
|
|||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun unpauseIfBackgrounded() {
|
||||
if (resumeAfterBackground.compareAndSet(true, false)) {
|
||||
NativeLibrary.unpauseEmulation()
|
||||
runOnUiThread {
|
||||
clearPausedFrame()
|
||||
updatePauseLabel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val resumeAfterBackground = AtomicBoolean(false)
|
||||
var activeGamePath: String? = null
|
||||
var pausedFrame: Bitmap? = null
|
||||
var frameListener: (() -> Unit)? = null
|
||||
const val EXTRA_GAME_PATH = "org.nxemu.EXTRA_GAME_PATH"
|
||||
private const val TAG = "NxEmu-Emulation"
|
||||
private const val DRAWER_REPEAT_MS = 220L
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package org.nxemu.ui.main
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.hardware.input.InputManager
|
||||
import android.net.Uri
|
||||
|
|
@ -20,7 +21,10 @@ import org.json.JSONArray
|
|||
import org.json.JSONObject
|
||||
import org.nxemu.NXUISetting
|
||||
import org.nxemu.NativeLibrary
|
||||
import org.nxemu.features.input.NativeInput
|
||||
import org.nxemu.features.input.model.NativeButton
|
||||
import org.nxemu.ui.emulation.EmulationActivity
|
||||
import org.nxemu.ui.settings.ControllerSettings
|
||||
import org.nxemu.ui.settings.InputMappingSession
|
||||
import org.nxemu.ui.settings.OverlayLayoutActivity
|
||||
import org.nxemu.utils.InputHandler
|
||||
|
|
@ -143,15 +147,62 @@ open class MainActivity : ComponentActivity() {
|
|||
},
|
||||
)
|
||||
NativeLibrary.addSettingChangedListener(settingChangedForwarder)
|
||||
bootstrapControllerOnFirstRun()
|
||||
val page = if (this is SettingsActivity) "settings.html" else "index.html"
|
||||
webView.loadUrl("file:///android_asset/$page")
|
||||
setContentView(webView)
|
||||
ThemeHelper.applySystemBars(this)
|
||||
}
|
||||
|
||||
private fun bootstrapControllerOnFirstRun() {
|
||||
if (InputHandler.getDevices().isEmpty()) {
|
||||
return
|
||||
}
|
||||
if (NativeInput.getButtonParam(0, NativeButton.A).get("engine", "") != "keyboard") {
|
||||
return
|
||||
}
|
||||
InputHandler.updateControllerData()
|
||||
ControllerSettings.autoMap(this, playerIndex = 0, index = 0)
|
||||
if (NativeInput.getButtonParam(0, NativeButton.A).get("engine", "") == "keyboard") {
|
||||
return
|
||||
}
|
||||
NativeLibrary.setSettingBool(NXUISetting.ShowInputOverlay, false)
|
||||
NativeLibrary.saveSettings()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
emulationLaunchPending = false
|
||||
InputHandler.updateControllerData()
|
||||
inputManager()?.registerInputDeviceListener(inputDeviceListener, null)
|
||||
refreshConnectedControllers()
|
||||
resumeRunningGame()
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
setIntent(intent)
|
||||
resumeRunningGame()
|
||||
}
|
||||
|
||||
private fun resumeRunningGame() {
|
||||
if (this is SettingsActivity) {
|
||||
return
|
||||
}
|
||||
val path = EmulationActivity.activeGamePath ?: return
|
||||
startActivity(
|
||||
Intent(this, EmulationActivity::class.java).apply {
|
||||
putExtra(EmulationActivity.EXTRA_GAME_PATH, path)
|
||||
addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
inputMapping.dismiss()
|
||||
inputManager()?.unregisterInputDeviceListener(inputDeviceListener)
|
||||
releaseBrowserStick()
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||
|
|
@ -260,6 +311,14 @@ open class MainActivity : ComponentActivity() {
|
|||
}
|
||||
webView.evaluateJavascript("if(typeof moveGameSelection==='function'){$moves}", null)
|
||||
}
|
||||
private fun releaseBrowserStick() {
|
||||
stickX = 0
|
||||
stickY = 0
|
||||
if (::webView.isInitialized) {
|
||||
webView.removeCallbacks(stickRepeat)
|
||||
}
|
||||
}
|
||||
|
||||
fun beginControllerMap(playerIndex: Int, mapId: String, title: String, filterIndex: Int) {
|
||||
runOnUiThread {
|
||||
inputMapping.begin(playerIndex, mapId, title, filterIndex)
|
||||
|
|
@ -280,6 +339,10 @@ open class MainActivity : ComponentActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun inputManager(): InputManager? {
|
||||
return getSystemService(Context.INPUT_SERVICE) as? InputManager
|
||||
}
|
||||
|
||||
private fun refreshConnectedControllers() {
|
||||
if (!::webView.isInitialized || isDestroyed) {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ AndroidRenderWindow::~AndroidRenderWindow()
|
|||
void AndroidRenderWindow::AttachSurface(ANativeWindow * native_window, float pixel_ratio)
|
||||
{
|
||||
std::lock_guard lock(m_mutex);
|
||||
m_present = native_window != nullptr;
|
||||
m_pixel_ratio = pixel_ratio > 0.f ? pixel_ratio : 1.f;
|
||||
if (m_native_window == native_window)
|
||||
{
|
||||
|
|
@ -23,9 +24,16 @@ void AndroidRenderWindow::AttachSurface(ANativeWindow * native_window, float pix
|
|||
m_native_window = native_window;
|
||||
}
|
||||
|
||||
void AndroidRenderWindow::SuppressPresentation()
|
||||
{
|
||||
std::lock_guard lock(m_mutex);
|
||||
m_present = false;
|
||||
}
|
||||
|
||||
void AndroidRenderWindow::ClearSurface()
|
||||
{
|
||||
std::lock_guard lock(m_mutex);
|
||||
m_present = false;
|
||||
if (m_native_window != nullptr)
|
||||
{
|
||||
ANativeWindow_release(m_native_window);
|
||||
|
|
@ -37,6 +45,10 @@ void AndroidRenderWindow::ClearSurface()
|
|||
void * AndroidRenderWindow::RenderSurface() const
|
||||
{
|
||||
std::lock_guard lock(m_mutex);
|
||||
if (!m_present)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return m_native_window;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ public:
|
|||
~AndroidRenderWindow();
|
||||
|
||||
void AttachSurface(ANativeWindow * native_window, float pixel_ratio);
|
||||
void SuppressPresentation();
|
||||
void ClearSurface();
|
||||
|
||||
void * RenderSurface() const override;
|
||||
|
|
@ -21,4 +22,5 @@ private:
|
|||
mutable std::mutex m_mutex;
|
||||
ANativeWindow * m_native_window = nullptr;
|
||||
float m_pixel_ratio = 1.0f;
|
||||
bool m_present = false;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -118,7 +118,6 @@ bool EmulationSession::Run(ANativeWindow * native_window, float pixel_ratio, con
|
|||
|
||||
if (native_window == nullptr)
|
||||
{
|
||||
__android_log_print(ANDROID_LOG_ERROR, kLogTag, "Run: no native window");
|
||||
RestoreStubModulesLocked();
|
||||
return false;
|
||||
}
|
||||
|
|
@ -131,7 +130,6 @@ bool EmulationSession::Run(ANativeWindow * native_window, float pixel_ratio, con
|
|||
|
||||
if (!m_system_modules->IsValid())
|
||||
{
|
||||
__android_log_print(ANDROID_LOG_ERROR, kLogTag, "Run: SystemModules invalid after Setup");
|
||||
RestoreStubModulesLocked();
|
||||
return false;
|
||||
}
|
||||
|
|
@ -147,20 +145,61 @@ bool EmulationSession::Run(ANativeWindow * native_window, float pixel_ratio, con
|
|||
ISystemloader & loader = m_system_modules->Modules().Systemloader();
|
||||
if (!loader.LoadRom(rom_path.c_str(), 0, -1, ApplicationLaunchType::FrontendInitiated))
|
||||
{
|
||||
__android_log_print(ANDROID_LOG_ERROR, kLogTag, "Run: LoadRom failed");
|
||||
RestoreStubModulesLocked();
|
||||
return false;
|
||||
}
|
||||
|
||||
__android_log_print(ANDROID_LOG_INFO, kLogTag, "Run: LoadRom ok");
|
||||
return true;
|
||||
}
|
||||
|
||||
void EmulationSession::Stop()
|
||||
{
|
||||
std::lock_guard lock(m_mutex);
|
||||
SettingsStore::GetInstance().SetBool(NXCoreSetting::EmulationRunning, false);
|
||||
RestoreStubModulesLocked();
|
||||
}
|
||||
|
||||
void EmulationSession::SetPaused(bool paused)
|
||||
{
|
||||
std::lock_guard lock(m_mutex);
|
||||
if (!m_system_modules || !m_system_modules->IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
IOperatingSystem & os = m_system_modules->Modules().OperatingSystem();
|
||||
if (!os.IsPoweredOn())
|
||||
{
|
||||
return;
|
||||
}
|
||||
os.SetEmulationPaused(paused);
|
||||
}
|
||||
|
||||
bool EmulationSession::IsPaused()
|
||||
{
|
||||
std::lock_guard lock(m_mutex);
|
||||
if (!m_system_modules || !m_system_modules->IsValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return m_system_modules->Modules().OperatingSystem().IsEmulationPaused();
|
||||
}
|
||||
|
||||
void EmulationSession::SetNativeWindow(ANativeWindow * native_window)
|
||||
{
|
||||
std::lock_guard lock(m_mutex);
|
||||
m_render_window.AttachSurface(native_window, m_pixel_ratio);
|
||||
m_native_window = native_window;
|
||||
}
|
||||
|
||||
void EmulationSession::SurfaceDestroyed()
|
||||
{
|
||||
std::lock_guard lock(m_mutex);
|
||||
SettingsStore::GetInstance().SetBool(NXCoreSetting::EmulationRunning, false);
|
||||
RestoreStubModulesLocked();
|
||||
const bool keep_running = m_system_modules && m_system_modules->IsValid() &&
|
||||
SettingsStore::GetInstance().GetBool(NXCoreSetting::EmulationRunning);
|
||||
m_render_window.SuppressPresentation();
|
||||
if (keep_running)
|
||||
{
|
||||
m_system_modules->Modules().Video().NotifyWindowChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void EmulationSession::SurfaceChanged()
|
||||
|
|
@ -174,15 +213,57 @@ void EmulationSession::SurfaceChanged()
|
|||
{
|
||||
return;
|
||||
}
|
||||
IVideo & video = m_system_modules->Modules().Video();
|
||||
video.NotifyWindowChanged();
|
||||
const int native_w = ANativeWindow_getWidth(m_native_window);
|
||||
const int native_h = ANativeWindow_getHeight(m_native_window);
|
||||
if (native_w > 0 && native_h > 0)
|
||||
{
|
||||
m_system_modules->Modules().Video().UpdateFramebufferLayout(static_cast<uint32_t>(native_w),
|
||||
static_cast<uint32_t>(native_h));
|
||||
video.UpdateFramebufferLayout(static_cast<uint32_t>(native_w), static_cast<uint32_t>(native_h));
|
||||
}
|
||||
}
|
||||
|
||||
bool EmulationSession::ReadAppletCapture(std::vector<uint8_t> & out, uint32_t & width, uint32_t & height)
|
||||
{
|
||||
IVideo * video = nullptr;
|
||||
{
|
||||
std::lock_guard lock(m_mutex);
|
||||
if (!m_system_modules || !m_system_modules->IsValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
video = &m_system_modules->Modules().Video();
|
||||
}
|
||||
uint32_t capture_width = 0;
|
||||
uint32_t capture_height = 0;
|
||||
const uint32_t size = video->GetLinearAppletCaptureBuffer(nullptr, 0, &capture_width, &capture_height);
|
||||
if (size == 0 || capture_width == 0 || capture_height == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
out.resize(size);
|
||||
video->GetLinearAppletCaptureBuffer(out.data(), size, &capture_width, &capture_height);
|
||||
width = capture_width;
|
||||
height = capture_height;
|
||||
return width * height * 4 <= out.size();
|
||||
}
|
||||
|
||||
void EmulationSession::AppletCaptureSize(uint32_t & width, uint32_t & height)
|
||||
{
|
||||
width = 0;
|
||||
height = 0;
|
||||
IVideo * video = nullptr;
|
||||
{
|
||||
std::lock_guard lock(m_mutex);
|
||||
if (!m_system_modules || !m_system_modules->IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
video = &m_system_modules->Modules().Video();
|
||||
}
|
||||
video->GetLinearAppletCaptureBuffer(nullptr, 0, &width, &height);
|
||||
}
|
||||
|
||||
ANativeWindow * EmulationSession::NativeWindow() const
|
||||
{
|
||||
std::lock_guard lock(m_mutex);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <nxemu-core/modules/system_modules.h>
|
||||
#include <nxemu-module-spec/operating_system.h>
|
||||
|
|
@ -31,8 +33,14 @@ public:
|
|||
int GetStyleIndex(int player_index);
|
||||
|
||||
bool Run(ANativeWindow * native_window, float pixel_ratio, const std::string & rom_path);
|
||||
void Stop();
|
||||
void SetPaused(bool paused);
|
||||
bool IsPaused();
|
||||
void SetNativeWindow(ANativeWindow * native_window);
|
||||
void SurfaceDestroyed();
|
||||
void SurfaceChanged();
|
||||
bool ReadAppletCapture(std::vector<uint8_t> & out, uint32_t & width, uint32_t & height);
|
||||
void AppletCaptureSize(uint32_t & width, uint32_t & height);
|
||||
|
||||
ANativeWindow * NativeWindow() const;
|
||||
float PixelRatio() const;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@
|
|||
#include <android/native_window_jni.h>
|
||||
#include <jni.h>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <common/json.h>
|
||||
#include <nxemu-core/app_init.h>
|
||||
|
|
@ -162,6 +164,12 @@ Java_org_nxemu_NativeLibrary_emulationSurfaceDestroyed(JNIEnv * /*env*/, jclass
|
|||
EmulationSession::GetInstance().SurfaceDestroyed();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_org_nxemu_NativeLibrary_emulationStopped(JNIEnv * /*env*/, jclass /*clazz*/)
|
||||
{
|
||||
EmulationSession::GetInstance().Stop();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_org_nxemu_NativeLibrary_surfaceChanged(JNIEnv * env, jclass /*clazz*/, jobject jsurface)
|
||||
{
|
||||
|
|
@ -176,13 +184,7 @@ Java_org_nxemu_NativeLibrary_surfaceChanged(JNIEnv * env, jclass /*clazz*/, jobj
|
|||
return;
|
||||
}
|
||||
EmulationSession & session = EmulationSession::GetInstance();
|
||||
if (session.NativeWindow() == nw)
|
||||
{
|
||||
ANativeWindow_release(nw);
|
||||
session.SurfaceChanged();
|
||||
return;
|
||||
}
|
||||
ANativeWindow_release(nw);
|
||||
session.SetNativeWindow(nw);
|
||||
session.SurfaceChanged();
|
||||
}
|
||||
|
||||
|
|
@ -215,40 +217,56 @@ Java_org_nxemu_NativeLibrary_getAppVersion(JNIEnv * env, jclass /*clazz*/)
|
|||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_org_nxemu_features_input_NativeInput_onTouchPressed(JNIEnv * /*env*/, jobject /*obj*/,
|
||||
jint /*fingerId*/, jfloat /*x*/, jfloat /*y*/)
|
||||
Java_org_nxemu_NativeLibrary_pauseEmulation(JNIEnv * /*env*/, jclass /*clazz*/)
|
||||
{
|
||||
EmulationSession::GetInstance().SetPaused(true);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_org_nxemu_features_input_NativeInput_onTouchMoved(JNIEnv * /*env*/, jobject /*obj*/,
|
||||
jint /*fingerId*/, jfloat /*x*/, jfloat /*y*/)
|
||||
Java_org_nxemu_NativeLibrary_unpauseEmulation(JNIEnv * /*env*/, jclass /*clazz*/)
|
||||
{
|
||||
EmulationSession::GetInstance().SetPaused(false);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_org_nxemu_features_input_NativeInput_onTouchReleased(JNIEnv * /*env*/, jobject /*obj*/,
|
||||
jint /*fingerId*/)
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_org_nxemu_NativeLibrary_isPaused(JNIEnv * /*env*/, jclass /*clazz*/)
|
||||
{
|
||||
return EmulationSession::GetInstance().IsPaused() ? JNI_TRUE : JNI_FALSE;
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_org_nxemu_features_input_NativeInput_onOverlayButtonEventImpl(JNIEnv * /*env*/, jobject /*obj*/,
|
||||
jint port, jint buttonId, jint action)
|
||||
extern "C" JNIEXPORT jbyteArray JNICALL
|
||||
Java_org_nxemu_NativeLibrary_getAppletCaptureBuffer(JNIEnv * env, jclass /*clazz*/)
|
||||
{
|
||||
EmulationSession::GetInstance().SetOverlayButton(port, buttonId, action == 1);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_org_nxemu_features_input_NativeInput_onOverlayJoystickEventImpl(JNIEnv * /*env*/, jobject /*obj*/,
|
||||
jint port, jint stickId, jfloat x, jfloat y)
|
||||
{
|
||||
EmulationSession::GetInstance().SetOverlayJoystick(port, stickId, x, y);
|
||||
std::vector<uint8_t> bytes;
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
if (!EmulationSession::GetInstance().ReadAppletCapture(bytes, width, height) || bytes.empty())
|
||||
{
|
||||
return env->NewByteArray(0);
|
||||
}
|
||||
jbyteArray buffer = env->NewByteArray(static_cast<jsize>(bytes.size()));
|
||||
if (buffer == nullptr)
|
||||
{
|
||||
return env->NewByteArray(0);
|
||||
}
|
||||
env->SetByteArrayRegion(buffer, 0, static_cast<jsize>(bytes.size()), reinterpret_cast<const jbyte *>(bytes.data()));
|
||||
return buffer;
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
Java_org_nxemu_features_input_NativeInput_getStyleIndexImpl(JNIEnv * /*env*/, jobject /*obj*/,
|
||||
jint playerIndex)
|
||||
Java_org_nxemu_NativeLibrary_getAppletCaptureWidth(JNIEnv * /*env*/, jclass /*clazz*/)
|
||||
{
|
||||
return EmulationSession::GetInstance().GetStyleIndex(playerIndex);
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
EmulationSession::GetInstance().AppletCaptureSize(width, height);
|
||||
return static_cast<jint>(width);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
Java_org_nxemu_NativeLibrary_getAppletCaptureHeight(JNIEnv * /*env*/, jclass /*clazz*/)
|
||||
{
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
EmulationSession::GetInstance().AppletCaptureSize(width, height);
|
||||
return static_cast<jint>(height);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -450,6 +450,12 @@ JNIEXPORT jintArray JNICALL Java_org_nxemu_features_input_NativeInput_getSupport
|
|||
return result;
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL Java_org_nxemu_features_input_NativeInput_getStyleIndexImpl(
|
||||
JNIEnv * /*env*/, jobject /*obj*/, jint j_player_index)
|
||||
{
|
||||
return EmulationSession::GetInstance().GetStyleIndex(j_player_index);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_nxemu_features_input_NativeInput_setStyleIndexImpl(JNIEnv * /*env*/, jobject /*obj*/, jint j_player_index, jint j_style_index)
|
||||
{
|
||||
SystemModules * modules = EmulationSession::GetInstance().Modules();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
<?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>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<?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="#FF000000"
|
||||
android:pathData="M8,5v14l11,-7z" />
|
||||
</vector>
|
||||
|
|
@ -143,6 +143,27 @@
|
|||
android:textSize="12sp"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
|
||||
<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" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/paused_icon"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:layout_gravity="center"
|
||||
android:background="@drawable/circle_white"
|
||||
android:clickable="true"
|
||||
android:contentDescription="@string/emulation_unpause"
|
||||
android:focusable="false"
|
||||
android:padding="14dp"
|
||||
android:src="@drawable/ic_play"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
|
|
@ -156,6 +177,21 @@
|
|||
android:focusable="true"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="24dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/menu_pause"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:background="@drawable/in_game_menu_item"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingHorizontal="20dp"
|
||||
android:text="@string/emulation_pause"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/menu_lock_drawer"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@
|
|||
<string name="button_map_description">Press a button or move a trigger/stick</string>
|
||||
<string name="emulation_done">Done</string>
|
||||
<string name="emulation_touch_overlay_reset">Reset overlay</string>
|
||||
<string name="emulation_pause">Pause emulation</string>
|
||||
<string name="emulation_unpause">Unpause emulation</string>
|
||||
<string name="emulation_exit">Exit emulation</string>
|
||||
<string name="lock_drawer">Lock drawer</string>
|
||||
<string name="unlock_drawer">Unlock drawer</string>
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
enum
|
||||
{
|
||||
MODULE_LOADER_SPECS_VERSION = 0x0130,
|
||||
MODULE_VIDEO_SPECS_VERSION = 0x0121,
|
||||
MODULE_VIDEO_SPECS_VERSION = 0x0122,
|
||||
MODULE_CPU_SPECS_VERSION = 0x011d,
|
||||
MODULE_OPERATING_SYSTEM_SPECS_VERSION = 0x0124,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -196,6 +196,8 @@ nxinterface IVideo
|
|||
virtual void ClearCdmaInstance(uint32_t id) = 0;
|
||||
virtual uint32_t GetAppletCaptureBuffer(uint8_t * out, uint32_t out_size) = 0;
|
||||
virtual void InvalidateMemory(const uint8_t * pointer, uint64_t size) = 0;
|
||||
virtual void NotifyWindowChanged() = 0;
|
||||
virtual uint32_t GetLinearAppletCaptureBuffer(uint8_t * out, uint32_t out_size, uint32_t * out_width, uint32_t * out_height) = 0;
|
||||
};
|
||||
|
||||
EXPORT IVideo * CALL CreateVideo(IRenderWindow & renderWindow, ISystemModules & modules);
|
||||
|
|
|
|||
|
|
@ -323,6 +323,11 @@ RenderWindow::RenderWindow(IRenderWindow & renderWindow) :
|
|||
UpdateCurrentFramebufferLayout(640, 480);
|
||||
}
|
||||
|
||||
void RenderWindow::RefreshRenderSurface()
|
||||
{
|
||||
window_info.render_surface = m_renderWindow.RenderSurface();
|
||||
}
|
||||
|
||||
void RenderWindow::OnFrameDisplayed()
|
||||
{
|
||||
if (!m_firstFrame)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ class RenderWindow :
|
|||
public:
|
||||
RenderWindow(IRenderWindow & renderWindow);
|
||||
|
||||
void RefreshRenderSurface();
|
||||
|
||||
// EmuWindow
|
||||
void OnFrameDisplayed();
|
||||
std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
#include "yuzu_video_core/rasterizer_interface.h"
|
||||
#include <nxemu-core/settings/identifiers.h>
|
||||
#include "yuzu_video_core/shader_notify.h"
|
||||
#include "yuzu_video_core/textures/decoders.h"
|
||||
#include "yuzu_common/polyfill_thread.h"
|
||||
#include "yuzu_common/settings.h"
|
||||
#include "nxemu-video/video_settings.h"
|
||||
|
|
@ -290,9 +291,43 @@ void VideoManager::RequestComposite(VideoFramebufferConfig * layers, uint32_t la
|
|||
|
||||
void VideoManager::UpdateFramebufferLayout(uint32_t width, uint32_t height)
|
||||
{
|
||||
impl->m_emuWindow->RefreshRenderSurface();
|
||||
impl->m_emuWindow->UpdateCurrentFramebufferLayout(width, height);
|
||||
}
|
||||
|
||||
uint32_t VideoManager::GetLinearAppletCaptureBuffer(uint8_t * out, uint32_t out_size, uint32_t * out_width, uint32_t * out_height)
|
||||
{
|
||||
using namespace VideoCore::Capture;
|
||||
constexpr uint32_t linear_size = LinearWidth * LinearHeight * BytesPerPixel;
|
||||
if (out_width != nullptr)
|
||||
{
|
||||
*out_width = LinearWidth;
|
||||
}
|
||||
if (out_height != nullptr)
|
||||
{
|
||||
*out_height = LinearHeight;
|
||||
}
|
||||
if (out == nullptr || out_size < linear_size)
|
||||
{
|
||||
return linear_size;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> tiled(TiledSize);
|
||||
GetAppletCaptureBuffer(tiled.data(), static_cast<uint32_t>(tiled.size()));
|
||||
std::vector<uint8_t> linear(linear_size);
|
||||
Tegra::Texture::UnswizzleTexture(linear, tiled, BytesPerPixel, LinearWidth, LinearHeight, LinearDepth, BlockHeight, BlockDepth);
|
||||
std::memcpy(out, linear.data(), linear_size);
|
||||
return linear_size;
|
||||
}
|
||||
|
||||
void VideoManager::NotifyWindowChanged()
|
||||
{
|
||||
if (impl->m_emuWindow)
|
||||
{
|
||||
impl->m_emuWindow->RefreshRenderSurface();
|
||||
}
|
||||
}
|
||||
|
||||
IChannelState * VideoManager::AllocateChannel()
|
||||
{
|
||||
return std::make_unique<IChannelStatePtr>(*impl->m_gpuCore, impl->m_memoryManagerRegistry, std::move(impl->m_gpuCore->AllocateChannel())).release();
|
||||
|
|
|
|||
|
|
@ -51,6 +51,8 @@ public:
|
|||
void ClearCdmaInstance(uint32_t id) override;
|
||||
uint32_t GetAppletCaptureBuffer(uint8_t * out, uint32_t out_size) override;
|
||||
void InvalidateMemory(const uint8_t * pointer, uint64_t size) override;
|
||||
void NotifyWindowChanged() override;
|
||||
uint32_t GetLinearAppletCaptureBuffer(uint8_t * out, uint32_t out_size, uint32_t * out_width, uint32_t * out_height) override;
|
||||
|
||||
private:
|
||||
VideoManager() = delete;
|
||||
|
|
|
|||
|
|
@ -99,7 +99,8 @@ PresentManager::PresentManager(const vk::Instance& instance_,
|
|||
memory_allocator{memory_allocator_}, scheduler{scheduler_}, swapchain{swapchain_},
|
||||
surface{surface_}, blit_supported{CanBlitToSwapchain(device.GetPhysical(),
|
||||
swapchain.GetImageViewFormat())},
|
||||
use_present_thread{videoSettings.async_presentation} {
|
||||
use_present_thread{videoSettings.async_presentation},
|
||||
presented_window{render_window.GetWindowInfo().render_surface} {
|
||||
SetImageCount();
|
||||
|
||||
auto& dld = device.GetLogical();
|
||||
|
|
@ -296,25 +297,25 @@ void PresentManager::SetImageCount() {
|
|||
}
|
||||
|
||||
void PresentManager::CopyToSwapchain(Frame* frame) {
|
||||
bool requires_recreation = false;
|
||||
const void* window = render_window.GetWindowInfo().render_surface;
|
||||
if (window == nullptr) {
|
||||
presented_window = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
// Recreate surface and swapchain if needed.
|
||||
if (requires_recreation) {
|
||||
surface = CreateSurface(instance, render_window.GetWindowInfo());
|
||||
RecreateSwapchain(frame);
|
||||
}
|
||||
|
||||
// Draw to swapchain.
|
||||
return CopyToSwapchainImpl(frame);
|
||||
} catch (const vk::Exception& except) {
|
||||
if (except.GetResult() != VK_ERROR_SURFACE_LOST_KHR) {
|
||||
throw;
|
||||
}
|
||||
|
||||
requires_recreation = true;
|
||||
try {
|
||||
if (window != presented_window) {
|
||||
surface = CreateSurface(instance, render_window.GetWindowInfo());
|
||||
presented_window = window;
|
||||
RecreateSwapchain(frame);
|
||||
}
|
||||
CopyToSwapchainImpl(frame);
|
||||
} catch (const vk::Exception& except) {
|
||||
if (except.GetResult() != VK_ERROR_SURFACE_LOST_KHR &&
|
||||
except.GetResult() != VK_ERROR_INITIALIZATION_FAILED) {
|
||||
throw;
|
||||
}
|
||||
presented_window = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@ private:
|
|||
bool blit_supported;
|
||||
bool use_present_thread;
|
||||
std::size_t image_count{};
|
||||
const void * presented_window = nullptr;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
|
|
|
|||
Loading…
Reference in New Issue