mirror of
https://github.com/Lime3DS/Lime3DS.git
synced 2025-03-13 09:12:27 +01:00
Merge 3929d448156e75c74c145e9984d8d4a0642bbe9d into 26ce7e4f2844a445bf77b4b14977d62e6434df08
This commit is contained in:
commit
ac5b74685c
@ -125,6 +125,10 @@ object NativeLibrary {
|
|||||||
external fun surfaceDestroyed()
|
external fun surfaceDestroyed()
|
||||||
external fun doFrame()
|
external fun doFrame()
|
||||||
|
|
||||||
|
//Second window
|
||||||
|
external fun secondarySurfaceChanged(secondary_surface: Surface)
|
||||||
|
external fun secondarySurfaceDestroyed()
|
||||||
|
external fun disableSecondaryScreen()
|
||||||
/**
|
/**
|
||||||
* Unpauses emulation from a paused state.
|
* Unpauses emulation from a paused state.
|
||||||
*/
|
*/
|
||||||
|
@ -4,14 +4,18 @@
|
|||||||
|
|
||||||
package org.citra.citra_emu.activities
|
package org.citra.citra_emu.activities
|
||||||
|
|
||||||
|
import SecondScreenPresentation
|
||||||
import android.Manifest.permission
|
import android.Manifest.permission
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Presentation
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
|
import android.hardware.display.DisplayManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.Display
|
||||||
import android.view.InputDevice
|
import android.view.InputDevice
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
@ -32,6 +36,7 @@ import org.citra.citra_emu.camera.StillImageCameraHelper.OnFilePickerResult
|
|||||||
import org.citra.citra_emu.contracts.OpenFileResultContract
|
import org.citra.citra_emu.contracts.OpenFileResultContract
|
||||||
import org.citra.citra_emu.databinding.ActivityEmulationBinding
|
import org.citra.citra_emu.databinding.ActivityEmulationBinding
|
||||||
import org.citra.citra_emu.display.ScreenAdjustmentUtil
|
import org.citra.citra_emu.display.ScreenAdjustmentUtil
|
||||||
|
import org.citra.citra_emu.display.SecondaryScreenLayout
|
||||||
import org.citra.citra_emu.features.hotkeys.HotkeyUtility
|
import org.citra.citra_emu.features.hotkeys.HotkeyUtility
|
||||||
import org.citra.citra_emu.features.settings.model.BooleanSetting
|
import org.citra.citra_emu.features.settings.model.BooleanSetting
|
||||||
import org.citra.citra_emu.features.settings.model.IntSetting
|
import org.citra.citra_emu.features.settings.model.IntSetting
|
||||||
@ -56,6 +61,34 @@ class EmulationActivity : AppCompatActivity() {
|
|||||||
private lateinit var binding: ActivityEmulationBinding
|
private lateinit var binding: ActivityEmulationBinding
|
||||||
private lateinit var screenAdjustmentUtil: ScreenAdjustmentUtil
|
private lateinit var screenAdjustmentUtil: ScreenAdjustmentUtil
|
||||||
private lateinit var hotkeyUtility: HotkeyUtility
|
private lateinit var hotkeyUtility: HotkeyUtility
|
||||||
|
private var secondScreenPresentation: Presentation? = null
|
||||||
|
private lateinit var displayManager: DisplayManager
|
||||||
|
|
||||||
|
private fun updatePresentation() {
|
||||||
|
displayManager = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
||||||
|
val display = getCustomerDisplay();
|
||||||
|
if (secondScreenPresentation != null && (IntSetting.SECONDARY_SCREEN_LAYOUT.int == SecondaryScreenLayout.NONE.int || display == null)) {
|
||||||
|
releasePresentation();
|
||||||
|
}
|
||||||
|
if (secondScreenPresentation == null || secondScreenPresentation?.display != display) {
|
||||||
|
secondScreenPresentation?.dismiss()
|
||||||
|
if (display != null && IntSetting.SECONDARY_SCREEN_LAYOUT.int != SecondaryScreenLayout.NONE.int) {
|
||||||
|
secondScreenPresentation = SecondScreenPresentation(this, display)
|
||||||
|
secondScreenPresentation?.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private fun releasePresentation() {
|
||||||
|
if (secondScreenPresentation != null) {
|
||||||
|
NativeLibrary.disableSecondaryScreen()
|
||||||
|
secondScreenPresentation?.dismiss();
|
||||||
|
secondScreenPresentation = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCustomerDisplay(): Display? {
|
||||||
|
return displayManager?.displays?.firstOrNull { it.isValid && it.displayId != Display.DEFAULT_DISPLAY }
|
||||||
|
}
|
||||||
|
|
||||||
private val emulationFragment: EmulationFragment
|
private val emulationFragment: EmulationFragment
|
||||||
get() {
|
get() {
|
||||||
@ -68,10 +101,9 @@ class EmulationActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
ThemeUtil.setTheme(this)
|
ThemeUtil.setTheme(this)
|
||||||
|
|
||||||
settingsViewModel.settings.loadSettings()
|
settingsViewModel.settings.loadSettings()
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
updatePresentation();
|
||||||
|
|
||||||
binding = ActivityEmulationBinding.inflate(layoutInflater)
|
binding = ActivityEmulationBinding.inflate(layoutInflater)
|
||||||
screenAdjustmentUtil = ScreenAdjustmentUtil(this, windowManager, settingsViewModel.settings)
|
screenAdjustmentUtil = ScreenAdjustmentUtil(this, windowManager, settingsViewModel.settings)
|
||||||
@ -117,6 +149,11 @@ class EmulationActivity : AppCompatActivity() {
|
|||||||
applyOrientationSettings() // Check for orientation settings changes on runtime
|
applyOrientationSettings() // Check for orientation settings changes on runtime
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onStop() {
|
||||||
|
releasePresentation()
|
||||||
|
super.onStop()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||||
super.onWindowFocusChanged(hasFocus)
|
super.onWindowFocusChanged(hasFocus)
|
||||||
enableFullscreenImmersive()
|
enableFullscreenImmersive()
|
||||||
@ -124,6 +161,7 @@ class EmulationActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
public override fun onRestart() {
|
public override fun onRestart() {
|
||||||
super.onRestart()
|
super.onRestart()
|
||||||
|
updatePresentation()
|
||||||
NativeLibrary.reloadCameraDevices()
|
NativeLibrary.reloadCameraDevices()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,6 +179,7 @@ class EmulationActivity : AppCompatActivity() {
|
|||||||
EmulationLifecycleUtil.clear()
|
EmulationLifecycleUtil.clear()
|
||||||
isEmulationRunning = false
|
isEmulationRunning = false
|
||||||
instance = null
|
instance = null
|
||||||
|
releasePresentation()
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,4 +48,18 @@ enum class PortraitScreenLayout(val int: Int) {
|
|||||||
return entries.firstOrNull { it.int == int } ?: TOP_FULL_WIDTH
|
return entries.firstOrNull { it.int == int } ?: TOP_FULL_WIDTH
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class SecondaryScreenLayout(val int: Int) {
|
||||||
|
// These must match what is defined in src/common/settings.h
|
||||||
|
NONE(0),
|
||||||
|
TOP_SCREEN(1),
|
||||||
|
BOTTOM_SCREEN(2),
|
||||||
|
SIDE_BY_SIDE(3);
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun from(int: Int): SecondaryScreenLayout {
|
||||||
|
return entries.firstOrNull { it.int == int } ?: NONE
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
import android.app.Presentation
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.Display
|
||||||
|
import android.view.SurfaceHolder
|
||||||
|
import android.view.SurfaceView
|
||||||
|
import org.citra.citra_emu.NativeLibrary
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class SecondScreenPresentation(
|
||||||
|
context: Context,
|
||||||
|
display: Display,
|
||||||
|
) : Presentation(context, display) {
|
||||||
|
private lateinit var surfaceView: SurfaceView
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
// Initialize SurfaceView
|
||||||
|
surfaceView = SurfaceView(context)
|
||||||
|
surfaceView.holder.addCallback(object : SurfaceHolder.Callback {
|
||||||
|
override fun surfaceCreated(holder: SurfaceHolder) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun surfaceChanged(
|
||||||
|
holder: SurfaceHolder,
|
||||||
|
format: Int,
|
||||||
|
width: Int,
|
||||||
|
height: Int
|
||||||
|
) {
|
||||||
|
NativeLibrary.secondarySurfaceChanged(holder.surface)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun surfaceDestroyed(holder: SurfaceHolder) {
|
||||||
|
NativeLibrary.secondarySurfaceDestroyed()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
setContentView(surfaceView) // Set SurfaceView as content
|
||||||
|
}
|
||||||
|
|
||||||
|
// Publicly accessible method to get the SurfaceHolder
|
||||||
|
fun getSurfaceHolder(): SurfaceHolder {
|
||||||
|
return surfaceView.holder
|
||||||
|
}
|
||||||
|
}
|
@ -34,6 +34,7 @@ enum class IntSetting(
|
|||||||
LANDSCAPE_BOTTOM_WIDTH("custom_bottom_width",Settings.SECTION_LAYOUT,640),
|
LANDSCAPE_BOTTOM_WIDTH("custom_bottom_width",Settings.SECTION_LAYOUT,640),
|
||||||
LANDSCAPE_BOTTOM_HEIGHT("custom_bottom_height",Settings.SECTION_LAYOUT,480),
|
LANDSCAPE_BOTTOM_HEIGHT("custom_bottom_height",Settings.SECTION_LAYOUT,480),
|
||||||
PORTRAIT_SCREEN_LAYOUT("portrait_layout_option",Settings.SECTION_LAYOUT,0),
|
PORTRAIT_SCREEN_LAYOUT("portrait_layout_option",Settings.SECTION_LAYOUT,0),
|
||||||
|
SECONDARY_SCREEN_LAYOUT("secondary_screen_layout",Settings.SECTION_LAYOUT,0),
|
||||||
PORTRAIT_TOP_X("custom_portrait_top_x",Settings.SECTION_LAYOUT,0),
|
PORTRAIT_TOP_X("custom_portrait_top_x",Settings.SECTION_LAYOUT,0),
|
||||||
PORTRAIT_TOP_Y("custom_portrait_top_y",Settings.SECTION_LAYOUT,0),
|
PORTRAIT_TOP_Y("custom_portrait_top_y",Settings.SECTION_LAYOUT,0),
|
||||||
PORTRAIT_TOP_WIDTH("custom_portrait_top_width",Settings.SECTION_LAYOUT,800),
|
PORTRAIT_TOP_WIDTH("custom_portrait_top_width",Settings.SECTION_LAYOUT,800),
|
||||||
|
@ -962,6 +962,17 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||||||
IntSetting.PORTRAIT_SCREEN_LAYOUT.defaultValue
|
IntSetting.PORTRAIT_SCREEN_LAYOUT.defaultValue
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
add(
|
||||||
|
SingleChoiceSetting(
|
||||||
|
IntSetting.SECONDARY_SCREEN_LAYOUT,
|
||||||
|
R.string.emulation_switch_secondary_layout,
|
||||||
|
0,
|
||||||
|
R.array.secondaryLayouts,
|
||||||
|
R.array.secondaryLayoutValues,
|
||||||
|
IntSetting.SECONDARY_SCREEN_LAYOUT.key,
|
||||||
|
IntSetting.SECONDARY_SCREEN_LAYOUT.defaultValue
|
||||||
|
)
|
||||||
|
)
|
||||||
add(
|
add(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
IntSetting.SMALL_SCREEN_POSITION,
|
IntSetting.SMALL_SCREEN_POSITION,
|
||||||
|
@ -272,6 +272,9 @@ object SettingsFile {
|
|||||||
val settings = section.settings
|
val settings = section.settings
|
||||||
val keySet: Set<String> = settings.keys
|
val keySet: Set<String> = settings.keys
|
||||||
for (key in keySet) {
|
for (key in keySet) {
|
||||||
|
if (key.equals("secondary_screen_layout")) {
|
||||||
|
println("hi");
|
||||||
|
}
|
||||||
val setting = settings[key]
|
val setting = settings[key]
|
||||||
parser.put(header, setting!!.key, setting.valueAsString)
|
parser.put(header, setting!!.key, setting.valueAsString)
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,12 @@
|
|||||||
package org.citra.citra_emu.fragments
|
package org.citra.citra_emu.fragments
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Presentation
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
import android.hardware.display.DisplayManager
|
||||||
|
import android.media.MediaRouter
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
@ -16,6 +19,7 @@ import android.os.SystemClock
|
|||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
import android.view.Choreographer
|
import android.view.Choreographer
|
||||||
|
import android.view.Display
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.Surface
|
import android.view.Surface
|
||||||
@ -26,6 +30,7 @@ import android.widget.PopupMenu
|
|||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.OnBackPressedCallback
|
import androidx.activity.OnBackPressedCallback
|
||||||
|
import androidx.core.content.ContextCompat.getSystemService
|
||||||
import androidx.core.content.res.ResourcesCompat
|
import androidx.core.content.res.ResourcesCompat
|
||||||
import androidx.core.graphics.Insets
|
import androidx.core.graphics.Insets
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
@ -79,6 +84,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||||||
private lateinit var emulationState: EmulationState
|
private lateinit var emulationState: EmulationState
|
||||||
private var perfStatsUpdater: Runnable? = null
|
private var perfStatsUpdater: Runnable? = null
|
||||||
|
|
||||||
|
|
||||||
private lateinit var emulationActivity: EmulationActivity
|
private lateinit var emulationActivity: EmulationActivity
|
||||||
|
|
||||||
private var _binding: FragmentEmulationBinding? = null
|
private var _binding: FragmentEmulationBinding? = null
|
||||||
@ -146,6 +152,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||||||
retainInstance = true
|
retainInstance = true
|
||||||
emulationState = EmulationState(game.path)
|
emulationState = EmulationState(game.path)
|
||||||
emulationActivity = requireActivity() as EmulationActivity
|
emulationActivity = requireActivity() as EmulationActivity
|
||||||
|
|
||||||
screenAdjustmentUtil = ScreenAdjustmentUtil(requireContext(), requireActivity().windowManager, settingsViewModel.settings)
|
screenAdjustmentUtil = ScreenAdjustmentUtil(requireContext(), requireActivity().windowManager, settingsViewModel.settings)
|
||||||
EmulationLifecycleUtil.addShutdownHook(hook = { emulationState.stop() })
|
EmulationLifecycleUtil.addShutdownHook(hook = { emulationState.stop() })
|
||||||
EmulationLifecycleUtil.addPauseResumeHook(hook = { togglePause() })
|
EmulationLifecycleUtil.addPauseResumeHook(hook = { togglePause() })
|
||||||
@ -1214,6 +1221,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||||||
private class EmulationState(private val gamePath: String) {
|
private class EmulationState(private val gamePath: String) {
|
||||||
private var state: State
|
private var state: State
|
||||||
private var surface: Surface? = null
|
private var surface: Surface? = null
|
||||||
|
private var surface2: Surface? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// Starting state is stopped.
|
// Starting state is stopped.
|
||||||
|
@ -204,6 +204,10 @@ void Config::ReadValues() {
|
|||||||
static_cast<Settings::PortraitLayoutOption>(sdl2_config->GetInteger(
|
static_cast<Settings::PortraitLayoutOption>(sdl2_config->GetInteger(
|
||||||
"Layout", "portrait_layout_option",
|
"Layout", "portrait_layout_option",
|
||||||
static_cast<int>(Settings::PortraitLayoutOption::PortraitTopFullWidth)));
|
static_cast<int>(Settings::PortraitLayoutOption::PortraitTopFullWidth)));
|
||||||
|
Settings::values.secondary_screen_layout =
|
||||||
|
static_cast<Settings::SecondaryScreenLayout>(sdl2_config->GetInteger(
|
||||||
|
"Layout", "secondary_screen_layout",
|
||||||
|
static_cast<int>(Settings::SecondaryScreenLayout::None)));
|
||||||
ReadSetting("Layout", Settings::values.custom_portrait_top_x);
|
ReadSetting("Layout", Settings::values.custom_portrait_top_x);
|
||||||
ReadSetting("Layout", Settings::values.custom_portrait_top_y);
|
ReadSetting("Layout", Settings::values.custom_portrait_top_y);
|
||||||
ReadSetting("Layout", Settings::values.custom_portrait_top_width);
|
ReadSetting("Layout", Settings::values.custom_portrait_top_width);
|
||||||
|
@ -251,6 +251,15 @@ custom_portrait_bottom_height =
|
|||||||
# 0 (default): Top Screen is prominent, 1: Bottom Screen is prominent
|
# 0 (default): Top Screen is prominent, 1: Bottom Screen is prominent
|
||||||
swap_screen =
|
swap_screen =
|
||||||
|
|
||||||
|
# Secondary Screen Layout
|
||||||
|
# What the game should do if a secondary screen is connected physically or using
|
||||||
|
# Miracast / Chromecast screen mirroring
|
||||||
|
# 0 (default) - Use System Default (mirror)
|
||||||
|
# 1 - Show Top Screen Only
|
||||||
|
# 2 - Show Bottom Screen Only
|
||||||
|
# 3 - Show both screens side by side
|
||||||
|
secondary_screen_layout =
|
||||||
|
|
||||||
# Screen placement settings when using Cardboard VR (render3d = 4)
|
# Screen placement settings when using Cardboard VR (render3d = 4)
|
||||||
# 30 - 100: Screen size as a percentage of the viewport. 85 (default)
|
# 30 - 100: Screen size as a percentage of the viewport. 85 (default)
|
||||||
cardboard_screen_size =
|
cardboard_screen_size =
|
||||||
|
@ -49,16 +49,17 @@ void EmuWindow_Android::OnFramebufferSizeChanged() {
|
|||||||
|
|
||||||
const int bigger{window_width > window_height ? window_width : window_height};
|
const int bigger{window_width > window_height ? window_width : window_height};
|
||||||
const int smaller{window_width < window_height ? window_width : window_height};
|
const int smaller{window_width < window_height ? window_width : window_height};
|
||||||
if (is_portrait_mode) {
|
if (is_portrait_mode && !is_secondary) {
|
||||||
UpdateCurrentFramebufferLayout(smaller, bigger, is_portrait_mode);
|
UpdateCurrentFramebufferLayout(smaller, bigger, is_portrait_mode);
|
||||||
} else {
|
} else {
|
||||||
UpdateCurrentFramebufferLayout(bigger, smaller, is_portrait_mode);
|
UpdateCurrentFramebufferLayout(bigger, smaller, is_portrait_mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EmuWindow_Android::EmuWindow_Android(ANativeWindow* surface) : host_window{surface} {
|
EmuWindow_Android::EmuWindow_Android(ANativeWindow *surface, bool is_secondary) : EmuWindow{
|
||||||
|
is_secondary}, host_window(surface) {
|
||||||
LOG_DEBUG(Frontend, "Initializing EmuWindow_Android");
|
LOG_DEBUG(Frontend, "Initializing EmuWindow_Android");
|
||||||
|
if (is_secondary) LOG_DEBUG(Frontend, "Initializing secondary window Android");
|
||||||
if (!surface) {
|
if (!surface) {
|
||||||
LOG_CRITICAL(Frontend, "surface is nullptr");
|
LOG_CRITICAL(Frontend, "surface is nullptr");
|
||||||
return;
|
return;
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <EGL/egl.h>
|
||||||
#include "core/frontend/emu_window.h"
|
#include "core/frontend/emu_window.h"
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
@ -13,7 +14,7 @@ class System;
|
|||||||
|
|
||||||
class EmuWindow_Android : public Frontend::EmuWindow {
|
class EmuWindow_Android : public Frontend::EmuWindow {
|
||||||
public:
|
public:
|
||||||
EmuWindow_Android(ANativeWindow* surface);
|
EmuWindow_Android(ANativeWindow* surface, bool is_secondary = false);
|
||||||
~EmuWindow_Android();
|
~EmuWindow_Android();
|
||||||
|
|
||||||
/// Called by the onSurfaceChanges() method to change the surface
|
/// Called by the onSurfaceChanges() method to change the surface
|
||||||
@ -30,7 +31,10 @@ public:
|
|||||||
void DoneCurrent() override;
|
void DoneCurrent() override;
|
||||||
|
|
||||||
virtual void TryPresenting() {}
|
virtual void TryPresenting() {}
|
||||||
|
// EGL Context must be shared
|
||||||
|
// could probably use the existing
|
||||||
|
// SharedContext for this instead, this is maybe temporary
|
||||||
|
virtual EGLContext* GetEGLContext() {return NULL;}
|
||||||
virtual void StopPresenting() {}
|
virtual void StopPresenting() {}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -72,8 +72,8 @@ private:
|
|||||||
EGLContext egl_context{};
|
EGLContext egl_context{};
|
||||||
};
|
};
|
||||||
|
|
||||||
EmuWindow_Android_OpenGL::EmuWindow_Android_OpenGL(Core::System& system_, ANativeWindow* surface)
|
EmuWindow_Android_OpenGL::EmuWindow_Android_OpenGL(Core::System& system_, ANativeWindow* surface, bool is_secondary, EGLContext* sharedContext)
|
||||||
: EmuWindow_Android{surface}, system{system_} {
|
: EmuWindow_Android{surface,is_secondary}, system{system_} {
|
||||||
if (egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); egl_display == EGL_NO_DISPLAY) {
|
if (egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); egl_display == EGL_NO_DISPLAY) {
|
||||||
LOG_CRITICAL(Frontend, "eglGetDisplay() failed");
|
LOG_CRITICAL(Frontend, "eglGetDisplay() failed");
|
||||||
return;
|
return;
|
||||||
@ -96,8 +96,9 @@ EmuWindow_Android_OpenGL::EmuWindow_Android_OpenGL(Core::System& system_, ANativ
|
|||||||
if (eglQuerySurface(egl_display, egl_surface, EGL_HEIGHT, &window_height) != EGL_TRUE) {
|
if (eglQuerySurface(egl_display, egl_surface, EGL_HEIGHT, &window_height) != EGL_TRUE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (sharedContext) {
|
||||||
if (egl_context = eglCreateContext(egl_display, egl_config, 0, egl_context_attribs.data());
|
egl_context = *sharedContext;
|
||||||
|
}else if (egl_context = eglCreateContext(egl_display, egl_config, 0, egl_context_attribs.data());
|
||||||
egl_context == EGL_NO_CONTEXT) {
|
egl_context == EGL_NO_CONTEXT) {
|
||||||
LOG_CRITICAL(Frontend, "eglCreateContext() failed");
|
LOG_CRITICAL(Frontend, "eglCreateContext() failed");
|
||||||
return;
|
return;
|
||||||
@ -127,6 +128,10 @@ EmuWindow_Android_OpenGL::EmuWindow_Android_OpenGL(Core::System& system_, ANativ
|
|||||||
OnFramebufferSizeChanged();
|
OnFramebufferSizeChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EGLContext* EmuWindow_Android_OpenGL::GetEGLContext() {
|
||||||
|
return &egl_context;
|
||||||
|
}
|
||||||
|
|
||||||
bool EmuWindow_Android_OpenGL::CreateWindowSurface() {
|
bool EmuWindow_Android_OpenGL::CreateWindowSurface() {
|
||||||
if (!host_window) {
|
if (!host_window) {
|
||||||
return true;
|
return true;
|
||||||
@ -204,14 +209,15 @@ void EmuWindow_Android_OpenGL::TryPresenting() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (presenting_state == PresentingState::Initial) [[unlikely]] {
|
if (presenting_state == PresentingState::Initial) [[unlikely]] {
|
||||||
eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context);
|
|
||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
|
||||||
presenting_state = PresentingState::Running;
|
presenting_state = PresentingState::Running;
|
||||||
}
|
}
|
||||||
if (presenting_state != PresentingState::Running) [[unlikely]] {
|
// if (presenting_state != PresentingState::Running) [[unlikely]] {
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context);
|
||||||
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||||
eglSwapInterval(egl_display, Settings::values.use_vsync_new ? 1 : 0);
|
eglSwapInterval(egl_display, Settings::values.use_vsync_new ? 1 : 0);
|
||||||
system.GPU().Renderer().TryPresent(0);
|
system.GPU().Renderer().TryPresent(100,is_secondary);
|
||||||
eglSwapBuffers(egl_display, egl_surface);
|
eglSwapBuffers(egl_display, egl_surface);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -19,13 +19,13 @@ struct ANativeWindow;
|
|||||||
|
|
||||||
class EmuWindow_Android_OpenGL : public EmuWindow_Android {
|
class EmuWindow_Android_OpenGL : public EmuWindow_Android {
|
||||||
public:
|
public:
|
||||||
EmuWindow_Android_OpenGL(Core::System& system, ANativeWindow* surface);
|
EmuWindow_Android_OpenGL(Core::System& system, ANativeWindow* surface, bool is_secondary, EGLContext* sharedContext = NULL);
|
||||||
~EmuWindow_Android_OpenGL() override = default;
|
~EmuWindow_Android_OpenGL() override = default;
|
||||||
|
|
||||||
void TryPresenting() override;
|
void TryPresenting() override;
|
||||||
void StopPresenting() override;
|
void StopPresenting() override;
|
||||||
void PollEvents() override;
|
void PollEvents() override;
|
||||||
|
EGLContext* GetEGLContext() override;
|
||||||
std::unique_ptr<GraphicsContext> CreateSharedContext() const override;
|
std::unique_ptr<GraphicsContext> CreateSharedContext() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -24,8 +24,8 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
EmuWindow_Android_Vulkan::EmuWindow_Android_Vulkan(
|
EmuWindow_Android_Vulkan::EmuWindow_Android_Vulkan(
|
||||||
ANativeWindow* surface, std::shared_ptr<Common::DynamicLibrary> driver_library_)
|
ANativeWindow* surface, std::shared_ptr<Common::DynamicLibrary> driver_library_, bool is_secondary)
|
||||||
: EmuWindow_Android{surface}, driver_library{driver_library_} {
|
: EmuWindow_Android{surface,is_secondary}, driver_library{driver_library_} {
|
||||||
CreateWindowSurface();
|
CreateWindowSurface();
|
||||||
|
|
||||||
if (core_context = CreateSharedContext(); !core_context) {
|
if (core_context = CreateSharedContext(); !core_context) {
|
||||||
|
@ -11,7 +11,7 @@ struct ANativeWindow;
|
|||||||
class EmuWindow_Android_Vulkan : public EmuWindow_Android {
|
class EmuWindow_Android_Vulkan : public EmuWindow_Android {
|
||||||
public:
|
public:
|
||||||
EmuWindow_Android_Vulkan(ANativeWindow* surface,
|
EmuWindow_Android_Vulkan(ANativeWindow* surface,
|
||||||
std::shared_ptr<Common::DynamicLibrary> driver_library);
|
std::shared_ptr<Common::DynamicLibrary> driver_library, bool is_secondary);
|
||||||
~EmuWindow_Android_Vulkan() override = default;
|
~EmuWindow_Android_Vulkan() override = default;
|
||||||
|
|
||||||
void PollEvents() override {}
|
void PollEvents() override {}
|
||||||
|
@ -16,11 +16,15 @@
|
|||||||
#include <core/hle/service/cfg/cfg.h>
|
#include <core/hle/service/cfg/cfg.h>
|
||||||
#include "audio_core/dsp_interface.h"
|
#include "audio_core/dsp_interface.h"
|
||||||
#include "common/arch.h"
|
#include "common/arch.h"
|
||||||
|
|
||||||
#if CITRA_ARCH(arm64)
|
#if CITRA_ARCH(arm64)
|
||||||
|
|
||||||
#include "common/aarch64/cpu_detect.h"
|
#include "common/aarch64/cpu_detect.h"
|
||||||
|
|
||||||
#elif CITRA_ARCH(x86_64)
|
#elif CITRA_ARCH(x86_64)
|
||||||
#include "common/x64/cpu_detect.h"
|
#include "common/x64/cpu_detect.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "common/common_paths.h"
|
#include "common/common_paths.h"
|
||||||
#include "common/dynamic_library/dynamic_library.h"
|
#include "common/dynamic_library/dynamic_library.h"
|
||||||
#include "common/file_util.h"
|
#include "common/file_util.h"
|
||||||
@ -44,12 +48,18 @@
|
|||||||
#include "jni/camera/ndk_camera.h"
|
#include "jni/camera/ndk_camera.h"
|
||||||
#include "jni/camera/still_image_camera.h"
|
#include "jni/camera/still_image_camera.h"
|
||||||
#include "jni/config.h"
|
#include "jni/config.h"
|
||||||
|
|
||||||
#ifdef ENABLE_OPENGL
|
#ifdef ENABLE_OPENGL
|
||||||
|
|
||||||
#include "jni/emu_window/emu_window_gl.h"
|
#include "jni/emu_window/emu_window_gl.h"
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
#ifdef ENABLE_VULKAN
|
#ifdef ENABLE_VULKAN
|
||||||
|
|
||||||
#include "jni/emu_window/emu_window_vk.h"
|
#include "jni/emu_window/emu_window_vk.h"
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "jni/id_cache.h"
|
#include "jni/id_cache.h"
|
||||||
#include "jni/input_manager.h"
|
#include "jni/input_manager.h"
|
||||||
#include "jni/ndk_motion.h"
|
#include "jni/ndk_motion.h"
|
||||||
@ -59,58 +69,63 @@
|
|||||||
#include "video_core/renderer_base.h"
|
#include "video_core/renderer_base.h"
|
||||||
|
|
||||||
#if defined(ENABLE_VULKAN) && CITRA_ARCH(arm64)
|
#if defined(ENABLE_VULKAN) && CITRA_ARCH(arm64)
|
||||||
|
|
||||||
#include <adrenotools/driver.h>
|
#include <adrenotools/driver.h>
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
ANativeWindow* s_surf;
|
ANativeWindow *s_surf;
|
||||||
|
ANativeWindow *s_secondary_surface;
|
||||||
|
bool secondary_enabled = false;
|
||||||
|
|
||||||
std::shared_ptr<Common::DynamicLibrary> vulkan_library{};
|
std::shared_ptr<Common::DynamicLibrary> vulkan_library{};
|
||||||
std::unique_ptr<EmuWindow_Android> window;
|
std::unique_ptr<EmuWindow_Android> window;
|
||||||
|
std::unique_ptr<EmuWindow_Android> second_window;
|
||||||
|
|
||||||
std::atomic<bool> stop_run{true};
|
std::atomic<bool> stop_run{true};
|
||||||
std::atomic<bool> pause_emulation{false};
|
std::atomic<bool> pause_emulation{false};
|
||||||
|
|
||||||
std::mutex paused_mutex;
|
std::mutex paused_mutex;
|
||||||
std::mutex running_mutex;
|
std::mutex running_mutex;
|
||||||
std::condition_variable running_cv;
|
std::condition_variable running_cv;
|
||||||
|
|
||||||
} // Anonymous namespace
|
} // Anonymous namespace
|
||||||
|
|
||||||
static jobject ToJavaCoreError(Core::System::ResultStatus result) {
|
static jobject ToJavaCoreError(Core::System::ResultStatus result) {
|
||||||
static const std::map<Core::System::ResultStatus, const char*> CoreErrorNameMap{
|
static const std::map<Core::System::ResultStatus, const char *> CoreErrorNameMap{
|
||||||
{Core::System::ResultStatus::ErrorSystemFiles, "ErrorSystemFiles"},
|
{Core::System::ResultStatus::ErrorSystemFiles, "ErrorSystemFiles"},
|
||||||
{Core::System::ResultStatus::ErrorSavestate, "ErrorSavestate"},
|
{Core::System::ResultStatus::ErrorSavestate, "ErrorSavestate"},
|
||||||
{Core::System::ResultStatus::ErrorArticDisconnected, "ErrorArticDisconnected"},
|
{Core::System::ResultStatus::ErrorArticDisconnected, "ErrorArticDisconnected"},
|
||||||
{Core::System::ResultStatus::ErrorUnknown, "ErrorUnknown"},
|
{Core::System::ResultStatus::ErrorUnknown, "ErrorUnknown"},
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto name = CoreErrorNameMap.count(result) ? CoreErrorNameMap.at(result) : "ErrorUnknown";
|
const auto name = CoreErrorNameMap.count(result) ? CoreErrorNameMap.at(result) : "ErrorUnknown";
|
||||||
|
|
||||||
JNIEnv* env = IDCache::GetEnvForThread();
|
JNIEnv *env = IDCache::GetEnvForThread();
|
||||||
const jclass core_error_class = IDCache::GetCoreErrorClass();
|
const jclass core_error_class = IDCache::GetCoreErrorClass();
|
||||||
return env->GetStaticObjectField(
|
return env->GetStaticObjectField(
|
||||||
core_error_class, env->GetStaticFieldID(core_error_class, name,
|
core_error_class, env->GetStaticFieldID(core_error_class, name,
|
||||||
"Lorg/citra/citra_emu/NativeLibrary$CoreError;"));
|
"Lorg/citra/citra_emu/NativeLibrary$CoreError;"));
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool HandleCoreError(Core::System::ResultStatus result, const std::string& details) {
|
static bool HandleCoreError(Core::System::ResultStatus result, const std::string &details) {
|
||||||
JNIEnv* env = IDCache::GetEnvForThread();
|
JNIEnv *env = IDCache::GetEnvForThread();
|
||||||
return env->CallStaticBooleanMethod(IDCache::GetNativeLibraryClass(), IDCache::GetOnCoreError(),
|
return env->CallStaticBooleanMethod(IDCache::GetNativeLibraryClass(), IDCache::GetOnCoreError(),
|
||||||
ToJavaCoreError(result),
|
ToJavaCoreError(result),
|
||||||
env->NewStringUTF(details.c_str())) != JNI_FALSE;
|
env->NewStringUTF(details.c_str())) != JNI_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max) {
|
static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max) {
|
||||||
JNIEnv* env = IDCache::GetEnvForThread();
|
JNIEnv *env = IDCache::GetEnvForThread();
|
||||||
env->CallStaticVoidMethod(IDCache::GetDiskCacheProgressClass(),
|
env->CallStaticVoidMethod(IDCache::GetDiskCacheProgressClass(),
|
||||||
IDCache::GetDiskCacheLoadProgress(),
|
IDCache::GetDiskCacheLoadProgress(),
|
||||||
IDCache::GetJavaLoadCallbackStage(stage), static_cast<jint>(progress),
|
IDCache::GetJavaLoadCallbackStage(stage), static_cast<jint>(progress),
|
||||||
static_cast<jint>(max));
|
static_cast<jint>(max));
|
||||||
}
|
}
|
||||||
|
|
||||||
static Camera::NDK::Factory* g_ndk_factory{};
|
static Camera::NDK::Factory *g_ndk_factory{};
|
||||||
|
|
||||||
static void TryShutdown() {
|
static void TryShutdown() {
|
||||||
if (!window) {
|
if (!window) {
|
||||||
@ -118,8 +133,10 @@ static void TryShutdown() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
window->DoneCurrent();
|
window->DoneCurrent();
|
||||||
|
if (second_window) second_window->DoneCurrent();
|
||||||
Core::System::GetInstance().Shutdown();
|
Core::System::GetInstance().Shutdown();
|
||||||
window.reset();
|
window.reset();
|
||||||
|
if (second_window) second_window.reset();
|
||||||
InputManager::Shutdown();
|
InputManager::Shutdown();
|
||||||
MicroProfileShutdown();
|
MicroProfileShutdown();
|
||||||
}
|
}
|
||||||
@ -129,7 +146,7 @@ static bool CheckMicPermission() {
|
|||||||
IDCache::GetRequestMicPermission());
|
IDCache::GetRequestMicPermission());
|
||||||
}
|
}
|
||||||
|
|
||||||
static Core::System::ResultStatus RunCitra(const std::string& filepath) {
|
static Core::System::ResultStatus RunCitra(const std::string &filepath) {
|
||||||
// Citra core only supports a single running instance
|
// Citra core only supports a single running instance
|
||||||
std::scoped_lock lock(running_mutex);
|
std::scoped_lock lock(running_mutex);
|
||||||
|
|
||||||
@ -142,33 +159,49 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
|
|||||||
return Core::System::ResultStatus::ErrorLoader;
|
return Core::System::ResultStatus::ErrorLoader;
|
||||||
}
|
}
|
||||||
|
|
||||||
Core::System& system{Core::System::GetInstance()};
|
Core::System &system{Core::System::GetInstance()};
|
||||||
|
|
||||||
const auto graphics_api = Settings::values.graphics_api.GetValue();
|
const auto graphics_api = Settings::values.graphics_api.GetValue();
|
||||||
switch (graphics_api) {
|
switch (graphics_api) {
|
||||||
#ifdef ENABLE_OPENGL
|
#ifdef ENABLE_OPENGL
|
||||||
case Settings::GraphicsAPI::OpenGL:
|
case Settings::GraphicsAPI::OpenGL:
|
||||||
window = std::make_unique<EmuWindow_Android_OpenGL>(system, s_surf);
|
window = std::make_unique<EmuWindow_Android_OpenGL>(system, s_surf, false);
|
||||||
break;
|
if (secondary_enabled) {
|
||||||
|
EGLContext *c = window->GetEGLContext();
|
||||||
|
second_window = std::make_unique<EmuWindow_Android_OpenGL>(system,
|
||||||
|
s_secondary_surface,
|
||||||
|
true, c);
|
||||||
|
}
|
||||||
|
break;
|
||||||
#endif
|
#endif
|
||||||
#ifdef ENABLE_VULKAN
|
#ifdef ENABLE_VULKAN
|
||||||
case Settings::GraphicsAPI::Vulkan:
|
case Settings::GraphicsAPI::Vulkan:
|
||||||
window = std::make_unique<EmuWindow_Android_Vulkan>(s_surf, vulkan_library);
|
window = std::make_unique<EmuWindow_Android_Vulkan>(s_surf, vulkan_library, false);
|
||||||
break;
|
if (secondary_enabled)
|
||||||
|
second_window = std::make_unique<EmuWindow_Android_Vulkan>(s_secondary_surface,
|
||||||
|
vulkan_library, true);
|
||||||
|
break;
|
||||||
#endif
|
#endif
|
||||||
default:
|
default:
|
||||||
LOG_CRITICAL(Frontend,
|
LOG_CRITICAL(Frontend,
|
||||||
"Unknown or unsupported graphics API {}, falling back to available default",
|
"Unknown or unsupported graphics API {}, falling back to available default",
|
||||||
graphics_api);
|
graphics_api);
|
||||||
#ifdef ENABLE_OPENGL
|
#ifdef ENABLE_OPENGL
|
||||||
window = std::make_unique<EmuWindow_Android_OpenGL>(system, s_surf);
|
window = std::make_unique<EmuWindow_Android_OpenGL>(system, s_surf, false);
|
||||||
|
if (secondary_enabled) {
|
||||||
|
EGLContext *c = window->GetEGLContext();
|
||||||
|
second_window = std::make_unique<EmuWindow_Android_OpenGL>(system,
|
||||||
|
s_secondary_surface,
|
||||||
|
true, c);
|
||||||
|
}
|
||||||
#elif ENABLE_VULKAN
|
#elif ENABLE_VULKAN
|
||||||
window = std::make_unique<EmuWindow_Android_Vulkan>(s_surf, vulkan_library);
|
window = std::make_unique<EmuWindow_Android_Vulkan>(s_surf, vulkan_library);
|
||||||
|
if (secondary_enabled) second_window = std::make_unique<EmuWindow_Android_Vulkan>(s_secondary_surface, vulkan_library, true);
|
||||||
#else
|
#else
|
||||||
// TODO: Add a null renderer backend for this, perhaps.
|
// TODO: Add a null renderer backend for this, perhaps.
|
||||||
#error "At least one renderer must be enabled."
|
#error "At least one renderer must be enabled."
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Forces a config reload on game boot, if the user changed settings in the UI
|
// Forces a config reload on game boot, if the user changed settings in the UI
|
||||||
@ -202,7 +235,8 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
|
|||||||
InputManager::Init();
|
InputManager::Init();
|
||||||
|
|
||||||
window->MakeCurrent();
|
window->MakeCurrent();
|
||||||
const Core::System::ResultStatus load_result{system.Load(*window, filepath)};
|
const Core::System::ResultStatus load_result{
|
||||||
|
system.Load(*window, filepath, second_window.get())};
|
||||||
if (load_result != Core::System::ResultStatus::Success) {
|
if (load_result != Core::System::ResultStatus::Success) {
|
||||||
return load_result;
|
return load_result;
|
||||||
}
|
}
|
||||||
@ -249,18 +283,19 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
|
|||||||
std::unique_lock pause_lock{paused_mutex};
|
std::unique_lock pause_lock{paused_mutex};
|
||||||
running_cv.wait(pause_lock, [] { return !pause_emulation || stop_run; });
|
running_cv.wait(pause_lock, [] { return !pause_emulation || stop_run; });
|
||||||
window->PollEvents();
|
window->PollEvents();
|
||||||
|
//if (second_window) second_window->PollEvents();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Core::System::ResultStatus::Success;
|
return Core::System::ResultStatus::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& custom_driver_dir,
|
void InitializeGpuDriver(const std::string &hook_lib_dir, const std::string &custom_driver_dir,
|
||||||
const std::string& custom_driver_name,
|
const std::string &custom_driver_name,
|
||||||
const std::string& file_redirect_dir) {
|
const std::string &file_redirect_dir) {
|
||||||
#if defined(ENABLE_VULKAN) && CITRA_ARCH(arm64)
|
#if defined(ENABLE_VULKAN) && CITRA_ARCH(arm64)
|
||||||
void* handle{};
|
void *handle{};
|
||||||
const char* file_redirect_dir_{};
|
const char *file_redirect_dir_{};
|
||||||
int featureFlags{};
|
int featureFlags{};
|
||||||
|
|
||||||
// Enable driver file redirection when renderer debugging is enabled.
|
// Enable driver file redirection when renderer debugging is enabled.
|
||||||
@ -272,8 +307,8 @@ void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& cus
|
|||||||
// Try to load a custom driver.
|
// Try to load a custom driver.
|
||||||
if (custom_driver_name.size()) {
|
if (custom_driver_name.size()) {
|
||||||
handle = adrenotools_open_libvulkan(
|
handle = adrenotools_open_libvulkan(
|
||||||
RTLD_NOW, featureFlags | ADRENOTOOLS_DRIVER_CUSTOM, nullptr, hook_lib_dir.c_str(),
|
RTLD_NOW, featureFlags | ADRENOTOOLS_DRIVER_CUSTOM, nullptr, hook_lib_dir.c_str(),
|
||||||
custom_driver_dir.c_str(), custom_driver_name.c_str(), file_redirect_dir_, nullptr);
|
custom_driver_dir.c_str(), custom_driver_name.c_str(), file_redirect_dir_, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to load the system driver.
|
// Try to load the system driver.
|
||||||
@ -288,7 +323,7 @@ void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& cus
|
|||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
void Java_org_citra_citra_1emu_NativeLibrary_surfaceChanged(JNIEnv* env,
|
void Java_org_citra_citra_1emu_NativeLibrary_surfaceChanged(JNIEnv *env,
|
||||||
[[maybe_unused]] jobject obj,
|
[[maybe_unused]] jobject obj,
|
||||||
jobject surf) {
|
jobject surf) {
|
||||||
s_surf = ANativeWindow_fromSurface(env, surf);
|
s_surf = ANativeWindow_fromSurface(env, surf);
|
||||||
@ -298,15 +333,86 @@ void Java_org_citra_citra_1emu_NativeLibrary_surfaceChanged(JNIEnv* env,
|
|||||||
notify = window->OnSurfaceChanged(s_surf);
|
notify = window->OnSurfaceChanged(s_surf);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto& system = Core::System::GetInstance();
|
auto &system = Core::System::GetInstance();
|
||||||
if (notify && system.IsPoweredOn()) {
|
if (notify && system.IsPoweredOn()) {
|
||||||
system.GPU().Renderer().NotifySurfaceChanged();
|
system.GPU().Renderer().NotifySurfaceChanged(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_INFO(Frontend, "Surface changed");
|
LOG_INFO(Frontend, "Surface changed");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_citra_citra_1emu_NativeLibrary_surfaceDestroyed([[maybe_unused]] JNIEnv* env,
|
void Java_org_citra_citra_1emu_NativeLibrary_secondarySurfaceChanged(JNIEnv *env,
|
||||||
|
[[maybe_unused]] jobject obj,
|
||||||
|
jobject surf) {
|
||||||
|
auto &system = Core::System::GetInstance();
|
||||||
|
s_secondary_surface = ANativeWindow_fromSurface(env, surf);
|
||||||
|
secondary_enabled = true;
|
||||||
|
bool notify = false;
|
||||||
|
if (!s_secondary_surface) {
|
||||||
|
// did not create the surface, so disable second screen
|
||||||
|
secondary_enabled = false;
|
||||||
|
if (system.IsPoweredOn()) {
|
||||||
|
system.GPU().Renderer().setSecondaryWindow(nullptr);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (second_window) {
|
||||||
|
//second window already created, so update it
|
||||||
|
notify = second_window->OnSurfaceChanged(s_secondary_surface);
|
||||||
|
} else if (system.IsPoweredOn() && window) {
|
||||||
|
// emulation running, window is new
|
||||||
|
// create a new window and set it
|
||||||
|
const auto graphics_api = Settings::values.graphics_api.GetValue();
|
||||||
|
if (graphics_api == Settings::GraphicsAPI::OpenGL) {
|
||||||
|
EGLContext *c = window->GetEGLContext();
|
||||||
|
second_window = std::make_unique<EmuWindow_Android_OpenGL>(system,
|
||||||
|
s_secondary_surface,true, c);
|
||||||
|
}else{
|
||||||
|
second_window = std::make_unique<EmuWindow_Android_Vulkan>(s_secondary_surface,
|
||||||
|
vulkan_library, true);
|
||||||
|
}
|
||||||
|
system.GPU().Renderer().setSecondaryWindow(second_window.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notify && system.IsPoweredOn()) {
|
||||||
|
system.GPU().Renderer().NotifySurfaceChanged(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_INFO(Frontend, "Secondary Surface changed");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Java_org_citra_citra_1emu_NativeLibrary_secondarySurfaceDestroyed(JNIEnv *env,
|
||||||
|
[[maybe_unused]] jobject obj) {
|
||||||
|
//auto &system = Core::System::GetInstance();
|
||||||
|
secondary_enabled = false;
|
||||||
|
if (s_secondary_surface != nullptr) {
|
||||||
|
ANativeWindow_release(s_secondary_surface);
|
||||||
|
s_secondary_surface = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_INFO(Frontend, "Secondary Surface Destroyed");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Java_org_citra_citra_1emu_NativeLibrary_disableSecondaryScreen(JNIEnv *env,
|
||||||
|
[[maybe_unused]] jobject obj) {
|
||||||
|
auto &system = Core::System::GetInstance();
|
||||||
|
secondary_enabled = false;
|
||||||
|
if (s_secondary_surface != nullptr) {
|
||||||
|
ANativeWindow_release(s_secondary_surface);
|
||||||
|
s_secondary_surface = nullptr;
|
||||||
|
}
|
||||||
|
if (system.IsPoweredOn()) {
|
||||||
|
system.GPU().Renderer().setSecondaryWindow(nullptr);
|
||||||
|
}
|
||||||
|
if (second_window) {
|
||||||
|
second_window.release();
|
||||||
|
second_window = nullptr;
|
||||||
|
}
|
||||||
|
LOG_INFO(Frontend, "Secondary Window Disabled");
|
||||||
|
|
||||||
|
}
|
||||||
|
void Java_org_citra_citra_1emu_NativeLibrary_surfaceDestroyed([[maybe_unused]] JNIEnv *env,
|
||||||
[[maybe_unused]] jobject obj) {
|
[[maybe_unused]] jobject obj) {
|
||||||
if (s_surf != nullptr) {
|
if (s_surf != nullptr) {
|
||||||
ANativeWindow_release(s_surf);
|
ANativeWindow_release(s_surf);
|
||||||
@ -317,7 +423,7 @@ void Java_org_citra_citra_1emu_NativeLibrary_surfaceDestroyed([[maybe_unused]] J
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_citra_citra_1emu_NativeLibrary_doFrame([[maybe_unused]] JNIEnv* env,
|
void Java_org_citra_citra_1emu_NativeLibrary_doFrame([[maybe_unused]] JNIEnv *env,
|
||||||
[[maybe_unused]] jobject obj) {
|
[[maybe_unused]] jobject obj) {
|
||||||
if (stop_run || pause_emulation) {
|
if (stop_run || pause_emulation) {
|
||||||
return;
|
return;
|
||||||
@ -325,36 +431,39 @@ void Java_org_citra_citra_1emu_NativeLibrary_doFrame([[maybe_unused]] JNIEnv* en
|
|||||||
if (window) {
|
if (window) {
|
||||||
window->TryPresenting();
|
window->TryPresenting();
|
||||||
}
|
}
|
||||||
|
if (second_window) {
|
||||||
|
second_window->TryPresenting();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void JNICALL Java_org_citra_citra_1emu_NativeLibrary_initializeGpuDriver(
|
void JNICALL Java_org_citra_citra_1emu_NativeLibrary_initializeGpuDriver(
|
||||||
JNIEnv* env, jobject obj, jstring hook_lib_dir, jstring custom_driver_dir,
|
JNIEnv *env, jobject obj, jstring hook_lib_dir, jstring custom_driver_dir,
|
||||||
jstring custom_driver_name, jstring file_redirect_dir) {
|
jstring custom_driver_name, jstring file_redirect_dir) {
|
||||||
InitializeGpuDriver(GetJString(env, hook_lib_dir), GetJString(env, custom_driver_dir),
|
InitializeGpuDriver(GetJString(env, hook_lib_dir), GetJString(env, custom_driver_dir),
|
||||||
GetJString(env, custom_driver_name), GetJString(env, file_redirect_dir));
|
GetJString(env, custom_driver_name), GetJString(env, file_redirect_dir));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_citra_citra_1emu_NativeLibrary_notifyOrientationChange([[maybe_unused]] JNIEnv* env,
|
void Java_org_citra_citra_1emu_NativeLibrary_notifyOrientationChange([[maybe_unused]] JNIEnv *env,
|
||||||
[[maybe_unused]] jobject obj,
|
[[maybe_unused]] jobject obj,
|
||||||
jint layout_option,
|
jint layout_option,
|
||||||
jint rotation,
|
jint rotation,
|
||||||
jboolean portrait) {
|
jboolean portrait) {
|
||||||
Settings::values.layout_option = static_cast<Settings::LayoutOption>(layout_option);
|
Settings::values.layout_option = static_cast<Settings::LayoutOption>(layout_option);
|
||||||
}
|
}
|
||||||
void Java_org_citra_citra_1emu_NativeLibrary_updateFramebuffer([[maybe_unused]] JNIEnv* env,
|
void Java_org_citra_citra_1emu_NativeLibrary_updateFramebuffer([[maybe_unused]] JNIEnv *env,
|
||||||
[[maybe_unused]] jobject obj,
|
[[maybe_unused]] jobject obj,
|
||||||
jboolean is_portrait_mode) {
|
jboolean is_portrait_mode) {
|
||||||
auto& system = Core::System::GetInstance();
|
auto &system = Core::System::GetInstance();
|
||||||
if (system.IsPoweredOn()) {
|
if (system.IsPoweredOn()) {
|
||||||
system.GPU().Renderer().UpdateCurrentFramebufferLayout(is_portrait_mode);
|
system.GPU().Renderer().UpdateCurrentFramebufferLayout(is_portrait_mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_citra_citra_1emu_NativeLibrary_swapScreens([[maybe_unused]] JNIEnv* env,
|
void Java_org_citra_citra_1emu_NativeLibrary_swapScreens([[maybe_unused]] JNIEnv *env,
|
||||||
[[maybe_unused]] jobject obj,
|
[[maybe_unused]] jobject obj,
|
||||||
jboolean swap_screens, jint rotation) {
|
jboolean swap_screens, jint rotation) {
|
||||||
Settings::values.swap_screen = swap_screens;
|
Settings::values.swap_screen = swap_screens;
|
||||||
auto& system = Core::System::GetInstance();
|
auto &system = Core::System::GetInstance();
|
||||||
if (system.IsPoweredOn()) {
|
if (system.IsPoweredOn()) {
|
||||||
system.GPU().Renderer().UpdateCurrentFramebufferLayout(IsPortraitMode());
|
system.GPU().Renderer().UpdateCurrentFramebufferLayout(IsPortraitMode());
|
||||||
}
|
}
|
||||||
@ -362,14 +471,14 @@ void Java_org_citra_citra_1emu_NativeLibrary_swapScreens([[maybe_unused]] JNIEnv
|
|||||||
Camera::NDK::g_rotation = rotation;
|
Camera::NDK::g_rotation = rotation;
|
||||||
}
|
}
|
||||||
|
|
||||||
jboolean Java_org_citra_citra_1emu_NativeLibrary_areKeysAvailable([[maybe_unused]] JNIEnv* env,
|
jboolean Java_org_citra_citra_1emu_NativeLibrary_areKeysAvailable([[maybe_unused]] JNIEnv *env,
|
||||||
[[maybe_unused]] jobject obj) {
|
[[maybe_unused]] jobject obj) {
|
||||||
HW::AES::InitKeys();
|
HW::AES::InitKeys();
|
||||||
return HW::AES::IsKeyXAvailable(HW::AES::KeySlotID::NCCHSecure1) &&
|
return HW::AES::IsKeyXAvailable(HW::AES::KeySlotID::NCCHSecure1) &&
|
||||||
HW::AES::IsKeyXAvailable(HW::AES::KeySlotID::NCCHSecure2);
|
HW::AES::IsKeyXAvailable(HW::AES::KeySlotID::NCCHSecure2);
|
||||||
}
|
}
|
||||||
|
|
||||||
jstring Java_org_citra_citra_1emu_NativeLibrary_getHomeMenuPath(JNIEnv* env,
|
jstring Java_org_citra_citra_1emu_NativeLibrary_getHomeMenuPath(JNIEnv *env,
|
||||||
[[maybe_unused]] jobject obj,
|
[[maybe_unused]] jobject obj,
|
||||||
jint region) {
|
jint region) {
|
||||||
const std::string path = Core::GetHomeMenuNcchPath(region);
|
const std::string path = Core::GetHomeMenuNcchPath(region);
|
||||||
@ -379,43 +488,44 @@ jstring Java_org_citra_citra_1emu_NativeLibrary_getHomeMenuPath(JNIEnv* env,
|
|||||||
return ToJString(env, "");
|
return ToJString(env, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_citra_citra_1emu_NativeLibrary_setUserDirectory(JNIEnv* env,
|
void Java_org_citra_citra_1emu_NativeLibrary_setUserDirectory(JNIEnv *env,
|
||||||
[[maybe_unused]] jobject obj,
|
[[maybe_unused]] jobject obj,
|
||||||
jstring j_directory) {
|
jstring j_directory) {
|
||||||
FileUtil::SetCurrentDir(GetJString(env, j_directory));
|
FileUtil::SetCurrentDir(GetJString(env, j_directory));
|
||||||
}
|
}
|
||||||
|
|
||||||
jobjectArray Java_org_citra_citra_1emu_NativeLibrary_getInstalledGamePaths(
|
jobjectArray Java_org_citra_citra_1emu_NativeLibrary_getInstalledGamePaths(
|
||||||
JNIEnv* env, [[maybe_unused]] jclass clazz) {
|
JNIEnv *env, [[maybe_unused]] jclass clazz) {
|
||||||
std::vector<std::string> games;
|
std::vector<std::string> games;
|
||||||
const FileUtil::DirectoryEntryCallable ScanDir =
|
const FileUtil::DirectoryEntryCallable ScanDir =
|
||||||
[&games, &ScanDir](u64*, const std::string& directory, const std::string& virtual_name) {
|
[&games, &ScanDir](u64 *, const std::string &directory,
|
||||||
std::string path = directory + virtual_name;
|
const std::string &virtual_name) {
|
||||||
if (FileUtil::IsDirectory(path)) {
|
std::string path = directory + virtual_name;
|
||||||
path += '/';
|
if (FileUtil::IsDirectory(path)) {
|
||||||
FileUtil::ForeachDirectoryEntry(nullptr, path, ScanDir);
|
path += '/';
|
||||||
} else {
|
FileUtil::ForeachDirectoryEntry(nullptr, path, ScanDir);
|
||||||
if (!FileUtil::Exists(path))
|
} else {
|
||||||
return false;
|
if (!FileUtil::Exists(path))
|
||||||
auto loader = Loader::GetLoader(path);
|
return false;
|
||||||
if (loader) {
|
auto loader = Loader::GetLoader(path);
|
||||||
bool executable{};
|
if (loader) {
|
||||||
const Loader::ResultStatus result = loader->IsExecutable(executable);
|
bool executable{};
|
||||||
if (Loader::ResultStatus::Success == result && executable) {
|
const Loader::ResultStatus result = loader->IsExecutable(executable);
|
||||||
games.emplace_back(path);
|
if (Loader::ResultStatus::Success == result && executable) {
|
||||||
|
games.emplace_back(path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
return true;
|
||||||
return true;
|
};
|
||||||
};
|
|
||||||
ScanDir(nullptr, "",
|
ScanDir(nullptr, "",
|
||||||
FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) +
|
FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) +
|
||||||
"Nintendo "
|
"Nintendo "
|
||||||
"3DS/00000000000000000000000000000000/"
|
"3DS/00000000000000000000000000000000/"
|
||||||
"00000000000000000000000000000000/title/00040000");
|
"00000000000000000000000000000000/title/00040000");
|
||||||
ScanDir(nullptr, "",
|
ScanDir(nullptr, "",
|
||||||
FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
|
FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
|
||||||
"00000000000000000000000000000000/title/00040010");
|
"00000000000000000000000000000000/title/00040010");
|
||||||
jobjectArray jgames = env->NewObjectArray(static_cast<jsize>(games.size()),
|
jobjectArray jgames = env->NewObjectArray(static_cast<jsize>(games.size()),
|
||||||
env->FindClass("java/lang/String"), nullptr);
|
env->FindClass("java/lang/String"), nullptr);
|
||||||
for (jsize i = 0; i < games.size(); ++i)
|
for (jsize i = 0; i < games.size(); ++i)
|
||||||
@ -423,7 +533,7 @@ jobjectArray Java_org_citra_citra_1emu_NativeLibrary_getInstalledGamePaths(
|
|||||||
return jgames;
|
return jgames;
|
||||||
}
|
}
|
||||||
|
|
||||||
jlongArray Java_org_citra_citra_1emu_NativeLibrary_getSystemTitleIds(JNIEnv* env,
|
jlongArray Java_org_citra_citra_1emu_NativeLibrary_getSystemTitleIds(JNIEnv *env,
|
||||||
[[maybe_unused]] jobject obj,
|
[[maybe_unused]] jobject obj,
|
||||||
jint system_type,
|
jint system_type,
|
||||||
jint region) {
|
jint region) {
|
||||||
@ -431,11 +541,11 @@ jlongArray Java_org_citra_citra_1emu_NativeLibrary_getSystemTitleIds(JNIEnv* env
|
|||||||
const std::vector<u64> titles = Core::GetSystemTitleIds(mode, region);
|
const std::vector<u64> titles = Core::GetSystemTitleIds(mode, region);
|
||||||
jlongArray jTitles = env->NewLongArray(titles.size());
|
jlongArray jTitles = env->NewLongArray(titles.size());
|
||||||
env->SetLongArrayRegion(jTitles, 0, titles.size(),
|
env->SetLongArrayRegion(jTitles, 0, titles.size(),
|
||||||
reinterpret_cast<const jlong*>(titles.data()));
|
reinterpret_cast<const jlong *>(titles.data()));
|
||||||
return jTitles;
|
return jTitles;
|
||||||
}
|
}
|
||||||
|
|
||||||
jobject Java_org_citra_citra_1emu_NativeLibrary_downloadTitleFromNus([[maybe_unused]] JNIEnv* env,
|
jobject Java_org_citra_citra_1emu_NativeLibrary_downloadTitleFromNus([[maybe_unused]] JNIEnv *env,
|
||||||
[[maybe_unused]] jobject obj,
|
[[maybe_unused]] jobject obj,
|
||||||
jlong title) {
|
jlong title) {
|
||||||
[[maybe_unused]] const auto title_id = static_cast<u64>(title);
|
[[maybe_unused]] const auto title_id = static_cast<u64>(title);
|
||||||
@ -453,7 +563,7 @@ jobject Java_org_citra_citra_1emu_NativeLibrary_downloadTitleFromNus([[maybe_unu
|
|||||||
}
|
}
|
||||||
|
|
||||||
jboolean JNICALL Java_org_citra_citra_1emu_utils_GpuDriverHelper_supportsCustomDriverLoading(
|
jboolean JNICALL Java_org_citra_citra_1emu_utils_GpuDriverHelper_supportsCustomDriverLoading(
|
||||||
JNIEnv* env, jobject instance) {
|
JNIEnv *env, jobject instance) {
|
||||||
#ifdef CITRA_ARCH_arm64
|
#ifdef CITRA_ARCH_arm64
|
||||||
// If the KGSL device exists custom drivers can be loaded using adrenotools
|
// If the KGSL device exists custom drivers can be loaded using adrenotools
|
||||||
return SupportsCustomDriver();
|
return SupportsCustomDriver();
|
||||||
@ -463,40 +573,41 @@ jboolean JNICALL Java_org_citra_citra_1emu_utils_GpuDriverHelper_supportsCustomD
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO(xperia64): ensure these cannot be called in an invalid state (e.g. after StopEmulation)
|
// TODO(xperia64): ensure these cannot be called in an invalid state (e.g. after StopEmulation)
|
||||||
void Java_org_citra_citra_1emu_NativeLibrary_unPauseEmulation([[maybe_unused]] JNIEnv* env,
|
void Java_org_citra_citra_1emu_NativeLibrary_unPauseEmulation([[maybe_unused]] JNIEnv *env,
|
||||||
[[maybe_unused]] jobject obj) {
|
[[maybe_unused]] jobject obj) {
|
||||||
pause_emulation = false;
|
pause_emulation = false;
|
||||||
running_cv.notify_all();
|
running_cv.notify_all();
|
||||||
InputManager::NDKMotionHandler()->EnableSensors();
|
InputManager::NDKMotionHandler()->EnableSensors();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_citra_citra_1emu_NativeLibrary_pauseEmulation([[maybe_unused]] JNIEnv* env,
|
void Java_org_citra_citra_1emu_NativeLibrary_pauseEmulation([[maybe_unused]] JNIEnv *env,
|
||||||
[[maybe_unused]] jobject obj) {
|
[[maybe_unused]] jobject obj) {
|
||||||
pause_emulation = true;
|
pause_emulation = true;
|
||||||
InputManager::NDKMotionHandler()->DisableSensors();
|
InputManager::NDKMotionHandler()->DisableSensors();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_citra_citra_1emu_NativeLibrary_stopEmulation([[maybe_unused]] JNIEnv* env,
|
void Java_org_citra_citra_1emu_NativeLibrary_stopEmulation([[maybe_unused]] JNIEnv *env,
|
||||||
[[maybe_unused]] jobject obj) {
|
[[maybe_unused]] jobject obj) {
|
||||||
stop_run = true;
|
stop_run = true;
|
||||||
pause_emulation = false;
|
pause_emulation = false;
|
||||||
window->StopPresenting();
|
window->StopPresenting();
|
||||||
|
if (second_window) second_window->StopPresenting();
|
||||||
running_cv.notify_all();
|
running_cv.notify_all();
|
||||||
}
|
}
|
||||||
|
|
||||||
jboolean Java_org_citra_citra_1emu_NativeLibrary_isRunning([[maybe_unused]] JNIEnv* env,
|
jboolean Java_org_citra_citra_1emu_NativeLibrary_isRunning([[maybe_unused]] JNIEnv *env,
|
||||||
[[maybe_unused]] jobject obj) {
|
[[maybe_unused]] jobject obj) {
|
||||||
return static_cast<jboolean>(!stop_run);
|
return static_cast<jboolean>(!stop_run);
|
||||||
}
|
}
|
||||||
|
|
||||||
jlong Java_org_citra_citra_1emu_NativeLibrary_getRunningTitleId([[maybe_unused]] JNIEnv* env,
|
jlong Java_org_citra_citra_1emu_NativeLibrary_getRunningTitleId([[maybe_unused]] JNIEnv *env,
|
||||||
[[maybe_unused]] jobject obj) {
|
[[maybe_unused]] jobject obj) {
|
||||||
u64 title_id{};
|
u64 title_id{};
|
||||||
Core::System::GetInstance().GetAppLoader().ReadProgramId(title_id);
|
Core::System::GetInstance().GetAppLoader().ReadProgramId(title_id);
|
||||||
return static_cast<jlong>(title_id);
|
return static_cast<jlong>(title_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
jboolean Java_org_citra_citra_1emu_NativeLibrary_onGamePadEvent([[maybe_unused]] JNIEnv* env,
|
jboolean Java_org_citra_citra_1emu_NativeLibrary_onGamePadEvent([[maybe_unused]] JNIEnv *env,
|
||||||
[[maybe_unused]] jobject obj,
|
[[maybe_unused]] jobject obj,
|
||||||
[[maybe_unused]] jstring j_device,
|
[[maybe_unused]] jstring j_device,
|
||||||
jint j_button, jint action) {
|
jint j_button, jint action) {
|
||||||
@ -511,8 +622,9 @@ jboolean Java_org_citra_citra_1emu_NativeLibrary_onGamePadEvent([[maybe_unused]]
|
|||||||
}
|
}
|
||||||
|
|
||||||
jboolean Java_org_citra_citra_1emu_NativeLibrary_onGamePadMoveEvent(
|
jboolean Java_org_citra_citra_1emu_NativeLibrary_onGamePadMoveEvent(
|
||||||
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj, [[maybe_unused]] jstring j_device,
|
[[maybe_unused]] JNIEnv *env, [[maybe_unused]] jobject obj,
|
||||||
jint axis, jfloat x, jfloat y) {
|
[[maybe_unused]] jstring j_device,
|
||||||
|
jint axis, jfloat x, jfloat y) {
|
||||||
// Clamp joystick movement to supported minimum and maximum
|
// Clamp joystick movement to supported minimum and maximum
|
||||||
// Citra uses an inverted y axis sent by the frontend
|
// Citra uses an inverted y axis sent by the frontend
|
||||||
x = std::clamp(x, -1.f, 1.f);
|
x = std::clamp(x, -1.f, 1.f);
|
||||||
@ -530,27 +642,28 @@ jboolean Java_org_citra_citra_1emu_NativeLibrary_onGamePadMoveEvent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
jboolean Java_org_citra_citra_1emu_NativeLibrary_onGamePadAxisEvent(
|
jboolean Java_org_citra_citra_1emu_NativeLibrary_onGamePadAxisEvent(
|
||||||
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj, [[maybe_unused]] jstring j_device,
|
[[maybe_unused]] JNIEnv *env, [[maybe_unused]] jobject obj,
|
||||||
jint axis_id, jfloat axis_val) {
|
[[maybe_unused]] jstring j_device,
|
||||||
|
jint axis_id, jfloat axis_val) {
|
||||||
return static_cast<jboolean>(
|
return static_cast<jboolean>(
|
||||||
InputManager::ButtonHandler()->AnalogButtonEvent(axis_id, axis_val));
|
InputManager::ButtonHandler()->AnalogButtonEvent(axis_id, axis_val));
|
||||||
}
|
}
|
||||||
|
|
||||||
jboolean Java_org_citra_citra_1emu_NativeLibrary_onTouchEvent([[maybe_unused]] JNIEnv* env,
|
jboolean Java_org_citra_citra_1emu_NativeLibrary_onTouchEvent([[maybe_unused]] JNIEnv *env,
|
||||||
[[maybe_unused]] jobject obj,
|
[[maybe_unused]] jobject obj,
|
||||||
jfloat x, jfloat y,
|
jfloat x, jfloat y,
|
||||||
jboolean pressed) {
|
jboolean pressed) {
|
||||||
return static_cast<jboolean>(
|
return static_cast<jboolean>(
|
||||||
window->OnTouchEvent(static_cast<int>(x + 0.5), static_cast<int>(y + 0.5), pressed));
|
window->OnTouchEvent(static_cast<int>(x + 0.5), static_cast<int>(y + 0.5), pressed));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_citra_citra_1emu_NativeLibrary_onTouchMoved([[maybe_unused]] JNIEnv* env,
|
void Java_org_citra_citra_1emu_NativeLibrary_onTouchMoved([[maybe_unused]] JNIEnv *env,
|
||||||
[[maybe_unused]] jobject obj, jfloat x,
|
[[maybe_unused]] jobject obj, jfloat x,
|
||||||
jfloat y) {
|
jfloat y) {
|
||||||
window->OnTouchMoved((int)x, (int)y);
|
window->OnTouchMoved((int) x, (int) y);
|
||||||
}
|
}
|
||||||
|
|
||||||
jlong Java_org_citra_citra_1emu_NativeLibrary_getTitleId(JNIEnv* env, [[maybe_unused]] jobject obj,
|
jlong Java_org_citra_citra_1emu_NativeLibrary_getTitleId(JNIEnv *env, [[maybe_unused]] jobject obj,
|
||||||
jstring j_filename) {
|
jstring j_filename) {
|
||||||
std::string filepath = GetJString(env, j_filename);
|
std::string filepath = GetJString(env, j_filename);
|
||||||
const auto loader = Loader::GetLoader(filepath);
|
const auto loader = Loader::GetLoader(filepath);
|
||||||
@ -562,7 +675,7 @@ jlong Java_org_citra_citra_1emu_NativeLibrary_getTitleId(JNIEnv* env, [[maybe_un
|
|||||||
return static_cast<jlong>(title_id);
|
return static_cast<jlong>(title_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
jboolean Java_org_citra_citra_1emu_NativeLibrary_getIsSystemTitle(JNIEnv* env,
|
jboolean Java_org_citra_citra_1emu_NativeLibrary_getIsSystemTitle(JNIEnv *env,
|
||||||
[[maybe_unused]] jobject obj,
|
[[maybe_unused]] jobject obj,
|
||||||
jstring path) {
|
jstring path) {
|
||||||
const std::string filepath = GetJString(env, path);
|
const std::string filepath = GetJString(env, path);
|
||||||
@ -578,19 +691,19 @@ jboolean Java_org_citra_citra_1emu_NativeLibrary_getIsSystemTitle(JNIEnv* env,
|
|||||||
return ((program_id >> 32) & 0xFFFFFFFF) == 0x00040010;
|
return ((program_id >> 32) & 0xFFFFFFFF) == 0x00040010;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_citra_citra_1emu_NativeLibrary_createConfigFile([[maybe_unused]] JNIEnv* env,
|
void Java_org_citra_citra_1emu_NativeLibrary_createConfigFile([[maybe_unused]] JNIEnv *env,
|
||||||
[[maybe_unused]] jobject obj) {
|
[[maybe_unused]] jobject obj) {
|
||||||
Config{};
|
Config{};
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_citra_citra_1emu_NativeLibrary_createLogFile([[maybe_unused]] JNIEnv* env,
|
void Java_org_citra_citra_1emu_NativeLibrary_createLogFile([[maybe_unused]] JNIEnv *env,
|
||||||
[[maybe_unused]] jobject obj) {
|
[[maybe_unused]] jobject obj) {
|
||||||
Common::Log::Initialize();
|
Common::Log::Initialize();
|
||||||
Common::Log::Start();
|
Common::Log::Start();
|
||||||
LOG_INFO(Frontend, "Logging backend initialised");
|
LOG_INFO(Frontend, "Logging backend initialised");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_citra_citra_1emu_NativeLibrary_logUserDirectory(JNIEnv* env,
|
void Java_org_citra_citra_1emu_NativeLibrary_logUserDirectory(JNIEnv *env,
|
||||||
[[maybe_unused]] jobject obj,
|
[[maybe_unused]] jobject obj,
|
||||||
jstring j_path) {
|
jstring j_path) {
|
||||||
std::string_view path = env->GetStringUTFChars(j_path, 0);
|
std::string_view path = env->GetStringUTFChars(j_path, 0);
|
||||||
@ -598,10 +711,10 @@ void Java_org_citra_citra_1emu_NativeLibrary_logUserDirectory(JNIEnv* env,
|
|||||||
env->ReleaseStringUTFChars(j_path, path.data());
|
env->ReleaseStringUTFChars(j_path, path.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_citra_citra_1emu_NativeLibrary_reloadSettings([[maybe_unused]] JNIEnv* env,
|
void Java_org_citra_citra_1emu_NativeLibrary_reloadSettings([[maybe_unused]] JNIEnv *env,
|
||||||
[[maybe_unused]] jobject obj) {
|
[[maybe_unused]] jobject obj) {
|
||||||
Config{};
|
Config{};
|
||||||
Core::System& system{Core::System::GetInstance()};
|
Core::System &system{Core::System::GetInstance()};
|
||||||
|
|
||||||
// Replace with game-specific settings
|
// Replace with game-specific settings
|
||||||
if (system.IsPoweredOn()) {
|
if (system.IsPoweredOn()) {
|
||||||
@ -612,9 +725,9 @@ void Java_org_citra_citra_1emu_NativeLibrary_reloadSettings([[maybe_unused]] JNI
|
|||||||
system.ApplySettings();
|
system.ApplySettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
jdoubleArray Java_org_citra_citra_1emu_NativeLibrary_getPerfStats(JNIEnv* env,
|
jdoubleArray Java_org_citra_citra_1emu_NativeLibrary_getPerfStats(JNIEnv *env,
|
||||||
[[maybe_unused]] jobject obj) {
|
[[maybe_unused]] jobject obj) {
|
||||||
auto& core = Core::System::GetInstance();
|
auto &core = Core::System::GetInstance();
|
||||||
jdoubleArray j_stats = env->NewDoubleArray(4);
|
jdoubleArray j_stats = env->NewDoubleArray(4);
|
||||||
|
|
||||||
if (core.IsPoweredOn()) {
|
if (core.IsPoweredOn()) {
|
||||||
@ -630,7 +743,7 @@ jdoubleArray Java_org_citra_citra_1emu_NativeLibrary_getPerfStats(JNIEnv* env,
|
|||||||
return j_stats;
|
return j_stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_citra_citra_1emu_NativeLibrary_run__Ljava_lang_String_2(JNIEnv* env,
|
void Java_org_citra_citra_1emu_NativeLibrary_run__Ljava_lang_String_2(JNIEnv *env,
|
||||||
[[maybe_unused]] jobject obj,
|
[[maybe_unused]] jobject obj,
|
||||||
jstring j_path) {
|
jstring j_path) {
|
||||||
const std::string path = GetJString(env, j_path);
|
const std::string path = GetJString(env, j_path);
|
||||||
@ -647,19 +760,19 @@ void Java_org_citra_citra_1emu_NativeLibrary_run__Ljava_lang_String_2(JNIEnv* en
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_citra_citra_1emu_NativeLibrary_reloadCameraDevices([[maybe_unused]] JNIEnv* env,
|
void Java_org_citra_citra_1emu_NativeLibrary_reloadCameraDevices([[maybe_unused]] JNIEnv *env,
|
||||||
[[maybe_unused]] jobject obj) {
|
[[maybe_unused]] jobject obj) {
|
||||||
if (g_ndk_factory) {
|
if (g_ndk_factory) {
|
||||||
g_ndk_factory->ReloadCameraDevices();
|
g_ndk_factory->ReloadCameraDevices();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
jboolean Java_org_citra_citra_1emu_NativeLibrary_loadAmiibo(JNIEnv* env,
|
jboolean Java_org_citra_citra_1emu_NativeLibrary_loadAmiibo(JNIEnv *env,
|
||||||
[[maybe_unused]] jobject obj,
|
[[maybe_unused]] jobject obj,
|
||||||
jstring j_file) {
|
jstring j_file) {
|
||||||
std::string filepath = GetJString(env, j_file);
|
std::string filepath = GetJString(env, j_file);
|
||||||
Core::System& system{Core::System::GetInstance()};
|
Core::System &system{Core::System::GetInstance()};
|
||||||
Service::SM::ServiceManager& sm = system.ServiceManager();
|
Service::SM::ServiceManager &sm = system.ServiceManager();
|
||||||
auto nfc = sm.GetService<Service::NFC::Module::Interface>("nfc:u");
|
auto nfc = sm.GetService<Service::NFC::Module::Interface>("nfc:u");
|
||||||
if (nfc == nullptr) {
|
if (nfc == nullptr) {
|
||||||
return static_cast<jboolean>(false);
|
return static_cast<jboolean>(false);
|
||||||
@ -668,10 +781,10 @@ jboolean Java_org_citra_citra_1emu_NativeLibrary_loadAmiibo(JNIEnv* env,
|
|||||||
return static_cast<jboolean>(nfc->LoadAmiibo(filepath));
|
return static_cast<jboolean>(nfc->LoadAmiibo(filepath));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_citra_citra_1emu_NativeLibrary_removeAmiibo([[maybe_unused]] JNIEnv* env,
|
void Java_org_citra_citra_1emu_NativeLibrary_removeAmiibo([[maybe_unused]] JNIEnv *env,
|
||||||
[[maybe_unused]] jobject obj) {
|
[[maybe_unused]] jobject obj) {
|
||||||
Core::System& system{Core::System::GetInstance()};
|
Core::System &system{Core::System::GetInstance()};
|
||||||
Service::SM::ServiceManager& sm = system.ServiceManager();
|
Service::SM::ServiceManager &sm = system.ServiceManager();
|
||||||
auto nfc = sm.GetService<Service::NFC::Module::Interface>("nfc:u");
|
auto nfc = sm.GetService<Service::NFC::Module::Interface>("nfc:u");
|
||||||
if (nfc == nullptr) {
|
if (nfc == nullptr) {
|
||||||
return;
|
return;
|
||||||
@ -681,19 +794,20 @@ void Java_org_citra_citra_1emu_NativeLibrary_removeAmiibo([[maybe_unused]] JNIEn
|
|||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jobject JNICALL Java_org_citra_citra_1emu_utils_CiaInstallWorker_installCIA(
|
JNIEXPORT jobject JNICALL Java_org_citra_citra_1emu_utils_CiaInstallWorker_installCIA(
|
||||||
JNIEnv* env, jobject jobj, jstring jpath) {
|
JNIEnv *env, jobject jobj, jstring jpath) {
|
||||||
std::string path = GetJString(env, jpath);
|
std::string path = GetJString(env, jpath);
|
||||||
Service::AM::InstallStatus res = Service::AM::InstallCIA(
|
Service::AM::InstallStatus res = Service::AM::InstallCIA(
|
||||||
path, [env, jobj](std::size_t total_bytes_read, std::size_t file_size) {
|
path, [env, jobj](std::size_t total_bytes_read, std::size_t file_size) {
|
||||||
env->CallVoidMethod(jobj, IDCache::GetCiaInstallHelperSetProgress(),
|
env->CallVoidMethod(jobj, IDCache::GetCiaInstallHelperSetProgress(),
|
||||||
static_cast<jint>(file_size), static_cast<jint>(total_bytes_read));
|
static_cast<jint>(file_size),
|
||||||
});
|
static_cast<jint>(total_bytes_read));
|
||||||
|
});
|
||||||
|
|
||||||
return IDCache::GetJavaCiaInstallStatus(res);
|
return IDCache::GetJavaCiaInstallStatus(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
jobjectArray Java_org_citra_citra_1emu_NativeLibrary_getSavestateInfo(
|
jobjectArray Java_org_citra_citra_1emu_NativeLibrary_getSavestateInfo(
|
||||||
JNIEnv* env, [[maybe_unused]] jobject obj) {
|
JNIEnv *env, [[maybe_unused]] jobject obj) {
|
||||||
const jclass date_class = env->FindClass("java/util/Date");
|
const jclass date_class = env->FindClass("java/util/Date");
|
||||||
const auto date_constructor = env->GetMethodID(date_class, "<init>", "(J)V");
|
const auto date_constructor = env->GetMethodID(date_class, "<init>", "(J)V");
|
||||||
|
|
||||||
@ -701,7 +815,7 @@ jobjectArray Java_org_citra_citra_1emu_NativeLibrary_getSavestateInfo(
|
|||||||
const auto slot_field = env->GetFieldID(savestate_info_class, "slot", "I");
|
const auto slot_field = env->GetFieldID(savestate_info_class, "slot", "I");
|
||||||
const auto date_field = env->GetFieldID(savestate_info_class, "time", "Ljava/util/Date;");
|
const auto date_field = env->GetFieldID(savestate_info_class, "time", "Ljava/util/Date;");
|
||||||
|
|
||||||
const Core::System& system{Core::System::GetInstance()};
|
const Core::System &system{Core::System::GetInstance()};
|
||||||
if (!system.IsPoweredOn()) {
|
if (!system.IsPoweredOn()) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@ -713,7 +827,8 @@ jobjectArray Java_org_citra_citra_1emu_NativeLibrary_getSavestateInfo(
|
|||||||
|
|
||||||
const auto savestates = Core::ListSaveStates(title_id, system.Movie().GetCurrentMovieID());
|
const auto savestates = Core::ListSaveStates(title_id, system.Movie().GetCurrentMovieID());
|
||||||
const jobjectArray array =
|
const jobjectArray array =
|
||||||
env->NewObjectArray(static_cast<jsize>(savestates.size()), savestate_info_class, nullptr);
|
env->NewObjectArray(static_cast<jsize>(savestates.size()), savestate_info_class,
|
||||||
|
nullptr);
|
||||||
for (std::size_t i = 0; i < savestates.size(); ++i) {
|
for (std::size_t i = 0; i < savestates.size(); ++i) {
|
||||||
const jobject object = env->AllocObject(savestate_info_class);
|
const jobject object = env->AllocObject(savestate_info_class);
|
||||||
env->SetIntField(object, slot_field, static_cast<jint>(savestates[i].slot));
|
env->SetIntField(object, slot_field, static_cast<jint>(savestates[i].slot));
|
||||||
@ -726,17 +841,17 @@ jobjectArray Java_org_citra_citra_1emu_NativeLibrary_getSavestateInfo(
|
|||||||
return array;
|
return array;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_citra_citra_1emu_NativeLibrary_saveState([[maybe_unused]] JNIEnv* env,
|
void Java_org_citra_citra_1emu_NativeLibrary_saveState([[maybe_unused]] JNIEnv *env,
|
||||||
[[maybe_unused]] jobject obj, jint slot) {
|
[[maybe_unused]] jobject obj, jint slot) {
|
||||||
Core::System::GetInstance().SendSignal(Core::System::Signal::Save, slot);
|
Core::System::GetInstance().SendSignal(Core::System::Signal::Save, slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_citra_citra_1emu_NativeLibrary_loadState([[maybe_unused]] JNIEnv* env,
|
void Java_org_citra_citra_1emu_NativeLibrary_loadState([[maybe_unused]] JNIEnv *env,
|
||||||
[[maybe_unused]] jobject obj, jint slot) {
|
[[maybe_unused]] jobject obj, jint slot) {
|
||||||
Core::System::GetInstance().SendSignal(Core::System::Signal::Load, slot);
|
Core::System::GetInstance().SendSignal(Core::System::Signal::Load, slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_citra_citra_1emu_NativeLibrary_logDeviceInfo([[maybe_unused]] JNIEnv* env,
|
void Java_org_citra_citra_1emu_NativeLibrary_logDeviceInfo([[maybe_unused]] JNIEnv *env,
|
||||||
[[maybe_unused]] jobject obj) {
|
[[maybe_unused]] jobject obj) {
|
||||||
LOG_INFO(Frontend, "Azahar Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch,
|
LOG_INFO(Frontend, "Azahar Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch,
|
||||||
Common::g_scm_desc);
|
Common::g_scm_desc);
|
||||||
@ -745,4 +860,4 @@ void Java_org_citra_citra_1emu_NativeLibrary_logDeviceInfo([[maybe_unused]] JNIE
|
|||||||
LOG_INFO(Frontend, "Host OS: Android API level {}", android_get_device_api_level());
|
LOG_INFO(Frontend, "Host OS: Android API level {}", android_get_device_api_level());
|
||||||
}
|
}
|
||||||
|
|
||||||
} // extern "C"
|
}
|
@ -34,11 +34,25 @@
|
|||||||
<item>@string/emulation_screen_layout_custom</item>
|
<item>@string/emulation_screen_layout_custom</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="secondaryLayouts">
|
||||||
|
<item>@string/emulation_secondary_screen_default</item>
|
||||||
|
<item>@string/emulation_top_screen</item>
|
||||||
|
<item>@string/emulation_bottom_screen</item>
|
||||||
|
<item>@string/emulation_screen_layout_sidebyside</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
<integer-array name="portraitLayoutValues">
|
<integer-array name="portraitLayoutValues">
|
||||||
<item>0</item>
|
<item>0</item>
|
||||||
<item>1</item>
|
<item>1</item>
|
||||||
</integer-array>
|
</integer-array>
|
||||||
|
|
||||||
|
<integer-array name="secondaryLayoutValues">
|
||||||
|
<item>0</item>
|
||||||
|
<item>1</item>
|
||||||
|
<item>2</item>
|
||||||
|
<item>3</item>
|
||||||
|
</integer-array>
|
||||||
|
|
||||||
<string-array name="smallScreenPositions">
|
<string-array name="smallScreenPositions">
|
||||||
<item>@string/small_screen_position_top_right</item>
|
<item>@string/small_screen_position_top_right</item>
|
||||||
<item>@string/small_screen_position_middle_right</item>
|
<item>@string/small_screen_position_middle_right</item>
|
||||||
|
@ -373,6 +373,7 @@
|
|||||||
<string name="emulation_open_cheats">Open Cheats</string>
|
<string name="emulation_open_cheats">Open Cheats</string>
|
||||||
<string name="emulation_switch_screen_layout">Landscape Screen Layout</string>
|
<string name="emulation_switch_screen_layout">Landscape Screen Layout</string>
|
||||||
<string name="emulation_switch_portrait_layout">Portrait Screen Layout</string>
|
<string name="emulation_switch_portrait_layout">Portrait Screen Layout</string>
|
||||||
|
<string name="emulation_switch_secondary_layout">Secondary Screen Layout</string>
|
||||||
<string name="emulation_screen_layout_largescreen">Large Screen</string>
|
<string name="emulation_screen_layout_largescreen">Large Screen</string>
|
||||||
<string name="emulation_screen_layout_portrait">Portrait</string>
|
<string name="emulation_screen_layout_portrait">Portrait</string>
|
||||||
<string name="emulation_screen_layout_single">Single Screen</string>
|
<string name="emulation_screen_layout_single">Single Screen</string>
|
||||||
@ -380,6 +381,7 @@
|
|||||||
<string name="emulation_screen_layout_hybrid">Hybrid Screens</string>
|
<string name="emulation_screen_layout_hybrid">Hybrid Screens</string>
|
||||||
<string name="emulation_screen_layout_original">Original</string>
|
<string name="emulation_screen_layout_original">Original</string>
|
||||||
<string name="emulation_portrait_layout_top_full">Default</string>
|
<string name="emulation_portrait_layout_top_full">Default</string>
|
||||||
|
<string name="emulation_secondary_screen_default">System Default (mirror)</string>
|
||||||
<string name="emulation_screen_layout_custom">Custom Layout</string>
|
<string name="emulation_screen_layout_custom">Custom Layout</string>
|
||||||
<string name="emulation_small_screen_position">Small Screen Position</string>
|
<string name="emulation_small_screen_position">Small Screen Position</string>
|
||||||
<string name="small_screen_position_description">Where should the small screen appear relative to the large one in Large Screen Layout?</string>
|
<string name="small_screen_position_description">Where should the small screen appear relative to the large one in Large Screen Layout?</string>
|
||||||
|
@ -113,6 +113,7 @@ void LogSettings() {
|
|||||||
}
|
}
|
||||||
log_setting("Layout_LayoutOption", values.layout_option.GetValue());
|
log_setting("Layout_LayoutOption", values.layout_option.GetValue());
|
||||||
log_setting("Layout_PortraitLayoutOption", values.portrait_layout_option.GetValue());
|
log_setting("Layout_PortraitLayoutOption", values.portrait_layout_option.GetValue());
|
||||||
|
log_setting("Layout_SecondScreenLayout",values.secondary_screen_layout.GetValue());
|
||||||
log_setting("Layout_SwapScreen", values.swap_screen.GetValue());
|
log_setting("Layout_SwapScreen", values.swap_screen.GetValue());
|
||||||
log_setting("Layout_UprightScreen", values.upright_screen.GetValue());
|
log_setting("Layout_UprightScreen", values.upright_screen.GetValue());
|
||||||
log_setting("Layout_LargeScreenProportion", values.large_screen_proportion.GetValue());
|
log_setting("Layout_LargeScreenProportion", values.large_screen_proportion.GetValue());
|
||||||
@ -205,6 +206,7 @@ void RestoreGlobalState(bool is_powered_on) {
|
|||||||
values.delay_game_render_thread_us.SetGlobal(true);
|
values.delay_game_render_thread_us.SetGlobal(true);
|
||||||
values.layout_option.SetGlobal(true);
|
values.layout_option.SetGlobal(true);
|
||||||
values.portrait_layout_option.SetGlobal(true);
|
values.portrait_layout_option.SetGlobal(true);
|
||||||
|
values.secondary_screen_layout.SetGlobal(true);
|
||||||
values.swap_screen.SetGlobal(true);
|
values.swap_screen.SetGlobal(true);
|
||||||
values.upright_screen.SetGlobal(true);
|
values.upright_screen.SetGlobal(true);
|
||||||
values.large_screen_proportion.SetGlobal(true);
|
values.large_screen_proportion.SetGlobal(true);
|
||||||
|
@ -53,6 +53,12 @@ enum class PortraitLayoutOption : u32 {
|
|||||||
PortraitCustomLayout,
|
PortraitCustomLayout,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class SecondaryScreenLayout : u32 {
|
||||||
|
None,
|
||||||
|
TopScreenOnly,
|
||||||
|
BottomScreenOnly,
|
||||||
|
SideBySide
|
||||||
|
};
|
||||||
/** Defines where the small screen will appear relative to the large screen
|
/** Defines where the small screen will appear relative to the large screen
|
||||||
* when in Large Screen mode
|
* when in Large Screen mode
|
||||||
*/
|
*/
|
||||||
@ -503,6 +509,7 @@ struct Values {
|
|||||||
SwitchableSetting<LayoutOption> layout_option{LayoutOption::Default, "layout_option"};
|
SwitchableSetting<LayoutOption> layout_option{LayoutOption::Default, "layout_option"};
|
||||||
SwitchableSetting<bool> swap_screen{false, "swap_screen"};
|
SwitchableSetting<bool> swap_screen{false, "swap_screen"};
|
||||||
SwitchableSetting<bool> upright_screen{false, "upright_screen"};
|
SwitchableSetting<bool> upright_screen{false, "upright_screen"};
|
||||||
|
SwitchableSetting<SecondaryScreenLayout> secondary_screen_layout{SecondaryScreenLayout::None, "secondary_screen_layout"};
|
||||||
SwitchableSetting<float, true> large_screen_proportion{4.f, 1.f, 16.f,
|
SwitchableSetting<float, true> large_screen_proportion{4.f, 1.f, 16.f,
|
||||||
"large_screen_proportion"};
|
"large_screen_proportion"};
|
||||||
SwitchableSetting<SmallScreenPosition> small_screen_position{SmallScreenPosition::BottomRight,
|
SwitchableSetting<SmallScreenPosition> small_screen_position{SmallScreenPosition::BottomRight,
|
||||||
|
@ -251,6 +251,9 @@ void EmuWindow::UpdateCurrentFramebufferLayout(u32 width, u32 height, bool is_po
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#ifdef ANDROID
|
||||||
|
if (is_secondary) layout = Layout::AndroidSecondaryLayout(width,height);
|
||||||
|
#endif
|
||||||
UpdateMinimumWindowSize(min_size);
|
UpdateMinimumWindowSize(min_size);
|
||||||
|
|
||||||
if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::CardboardVR) {
|
if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::CardboardVR) {
|
||||||
|
@ -71,7 +71,7 @@ FramebufferLayout SingleFrameLayout(u32 width, u32 height, bool swapped, bool up
|
|||||||
|
|
||||||
const bool stretched = (Settings::values.screen_top_stretch.GetValue() && !swapped) ||
|
const bool stretched = (Settings::values.screen_top_stretch.GetValue() && !swapped) ||
|
||||||
(Settings::values.screen_bottom_stretch.GetValue() && swapped);
|
(Settings::values.screen_bottom_stretch.GetValue() && swapped);
|
||||||
const float window_aspect_ratio = static_cast<float>(height) / width;
|
const float window_aspect_ratio = static_cast<float>(height) / static_cast<float>(width);
|
||||||
|
|
||||||
if (stretched) {
|
if (stretched) {
|
||||||
top_screen = {Settings::values.screen_top_leftright_padding.GetValue(),
|
top_screen = {Settings::values.screen_top_leftright_padding.GetValue(),
|
||||||
@ -113,7 +113,7 @@ FramebufferLayout LargeFrameLayout(u32 width, u32 height, bool swapped, bool upr
|
|||||||
FramebufferLayout res{width, height, true, true, {}, {}, !upright};
|
FramebufferLayout res{width, height, true, true, {}, {}, !upright};
|
||||||
// Split the window into two parts. Give proportional width to the smaller screen
|
// Split the window into two parts. Give proportional width to the smaller screen
|
||||||
// To do that, find the total emulation box and maximize that based on window size
|
// To do that, find the total emulation box and maximize that based on window size
|
||||||
const float window_aspect_ratio = static_cast<float>(height) / width;
|
const float window_aspect_ratio = static_cast<float>(height) / static_cast<float>(width);
|
||||||
float emulation_aspect_ratio;
|
float emulation_aspect_ratio;
|
||||||
|
|
||||||
float large_height =
|
float large_height =
|
||||||
@ -297,6 +297,22 @@ FramebufferLayout SeparateWindowsLayout(u32 width, u32 height, bool is_secondary
|
|||||||
return SingleFrameLayout(width, height, is_secondary, upright);
|
return SingleFrameLayout(width, height, is_secondary, upright);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FramebufferLayout AndroidSecondaryLayout(u32 width, u32 height) {
|
||||||
|
const Settings::SecondaryScreenLayout layout = Settings::values.secondary_screen_layout.GetValue();
|
||||||
|
switch (layout) {
|
||||||
|
|
||||||
|
case Settings::SecondaryScreenLayout::BottomScreenOnly:
|
||||||
|
return SingleFrameLayout(width, height, true, Settings::values.upright_screen.GetValue());
|
||||||
|
case Settings::SecondaryScreenLayout::SideBySide:
|
||||||
|
return LargeFrameLayout(width,height,false,Settings::values.upright_screen.GetValue(),1.0f,Settings::SmallScreenPosition::MiddleRight);
|
||||||
|
case Settings::SecondaryScreenLayout::None:
|
||||||
|
// this should never happen, but if it does, somehow, send the top screen
|
||||||
|
case Settings::SecondaryScreenLayout::TopScreenOnly:
|
||||||
|
default:
|
||||||
|
return SingleFrameLayout(width, height, false, Settings::values.upright_screen.GetValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
FramebufferLayout CustomFrameLayout(u32 width, u32 height, bool is_swapped, bool is_portrait_mode) {
|
FramebufferLayout CustomFrameLayout(u32 width, u32 height, bool is_swapped, bool is_portrait_mode) {
|
||||||
ASSERT(width > 0);
|
ASSERT(width > 0);
|
||||||
ASSERT(height > 0);
|
ASSERT(height > 0);
|
||||||
|
@ -119,6 +119,16 @@ FramebufferLayout HybridScreenLayout(u32 width, u32 height, bool swapped, bool u
|
|||||||
*/
|
*/
|
||||||
FramebufferLayout SeparateWindowsLayout(u32 width, u32 height, bool is_secondary, bool upright);
|
FramebufferLayout SeparateWindowsLayout(u32 width, u32 height, bool is_secondary, bool upright);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method for constructing the secondary layout for Android, based on
|
||||||
|
* the appropriate setting.
|
||||||
|
* @param width Window framebuffer width in pixels
|
||||||
|
* @param height Window framebuffer height in pixels
|
||||||
|
*/
|
||||||
|
FramebufferLayout AndroidSecondaryLayout(u32 width, u32 height);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory method for constructing a custom FramebufferLayout
|
* Factory method for constructing a custom FramebufferLayout
|
||||||
* @param width Window framebuffer width in pixels
|
* @param width Window framebuffer width in pixels
|
||||||
|
@ -35,7 +35,7 @@ void RendererBase::UpdateCurrentFramebufferLayout(bool is_portrait_mode) {
|
|||||||
window.UpdateCurrentFramebufferLayout(layout.width, layout.height, is_portrait_mode);
|
window.UpdateCurrentFramebufferLayout(layout.width, layout.height, is_portrait_mode);
|
||||||
};
|
};
|
||||||
update_layout(render_window);
|
update_layout(render_window);
|
||||||
if (secondary_window) {
|
if (secondary_window != nullptr) {
|
||||||
update_layout(*secondary_window);
|
update_layout(*secondary_window);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -67,4 +67,13 @@ void RendererBase::RequestScreenshot(void* data, std::function<void(bool)> callb
|
|||||||
settings.screenshot_requested = true;
|
settings.screenshot_requested = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Frontend::EmuWindow *RendererBase::getSecondaryWindow() const {
|
||||||
|
return secondary_window;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RendererBase::setSecondaryWindow(Frontend::EmuWindow *secondaryWindow) {
|
||||||
|
secondary_window = secondaryWindow;
|
||||||
|
if (secondary_window) secondary_window->PollEvents();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace VideoCore
|
} // namespace VideoCore
|
||||||
|
@ -64,7 +64,8 @@ public:
|
|||||||
virtual void Sync() {}
|
virtual void Sync() {}
|
||||||
|
|
||||||
/// This is called to notify the rendering backend of a surface change
|
/// This is called to notify the rendering backend of a surface change
|
||||||
virtual void NotifySurfaceChanged() {}
|
// if second == true then it is the second screen
|
||||||
|
virtual void NotifySurfaceChanged(bool second) {}
|
||||||
|
|
||||||
/// Returns the resolution scale factor relative to the native 3DS screen resolution
|
/// Returns the resolution scale factor relative to the native 3DS screen resolution
|
||||||
u32 GetResolutionScaleFactor();
|
u32 GetResolutionScaleFactor();
|
||||||
@ -110,7 +111,14 @@ protected:
|
|||||||
Core::System& system;
|
Core::System& system;
|
||||||
RendererSettings settings;
|
RendererSettings settings;
|
||||||
Frontend::EmuWindow& render_window; ///< Reference to the render window handle.
|
Frontend::EmuWindow& render_window; ///< Reference to the render window handle.
|
||||||
Frontend::EmuWindow* secondary_window; ///< Reference to the secondary render window handle.
|
Frontend::EmuWindow* secondary_window;
|
||||||
|
public:
|
||||||
|
Frontend::EmuWindow *getSecondaryWindow() const;
|
||||||
|
|
||||||
|
virtual void setSecondaryWindow(Frontend::EmuWindow *secondaryWindow);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
///< Reference to the secondary render window handle.
|
||||||
f32 current_fps = 0.0f; ///< Current framerate, should be set by the renderer
|
f32 current_fps = 0.0f; ///< Current framerate, should be set by the renderer
|
||||||
s32 current_frame = 0; ///< Current frame, should be set by the renderer
|
s32 current_frame = 0; ///< Current frame, should be set by the renderer
|
||||||
};
|
};
|
||||||
|
@ -87,6 +87,15 @@ RendererOpenGL::RendererOpenGL(Core::System& system, Pica::PicaCore& pica_,
|
|||||||
}
|
}
|
||||||
|
|
||||||
RendererOpenGL::~RendererOpenGL() = default;
|
RendererOpenGL::~RendererOpenGL() = default;
|
||||||
|
void RendererOpenGL::setSecondaryWindow(Frontend::EmuWindow *secondaryWindow) {
|
||||||
|
if (secondaryWindow) {
|
||||||
|
secondary_window = secondaryWindow;
|
||||||
|
secondary_window->mailbox = std::make_unique<OGLTextureMailbox>(driver.HasDebugTool());
|
||||||
|
}else {
|
||||||
|
secondary_window = nullptr;
|
||||||
|
// should I release something here? The mailbox??
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void RendererOpenGL::SwapBuffers() {
|
void RendererOpenGL::SwapBuffers() {
|
||||||
// Maintain the rasterizer's state as a priority
|
// Maintain the rasterizer's state as a priority
|
||||||
@ -96,7 +105,7 @@ void RendererOpenGL::SwapBuffers() {
|
|||||||
PrepareRendertarget();
|
PrepareRendertarget();
|
||||||
RenderScreenshot();
|
RenderScreenshot();
|
||||||
|
|
||||||
const auto& main_layout = render_window.GetFramebufferLayout();
|
const auto &main_layout = render_window.GetFramebufferLayout();
|
||||||
RenderToMailbox(main_layout, render_window.mailbox, false);
|
RenderToMailbox(main_layout, render_window.mailbox, false);
|
||||||
|
|
||||||
#ifndef ANDROID
|
#ifndef ANDROID
|
||||||
@ -106,6 +115,15 @@ void RendererOpenGL::SwapBuffers() {
|
|||||||
RenderToMailbox(secondary_layout, secondary_window->mailbox, false);
|
RenderToMailbox(secondary_layout, secondary_window->mailbox, false);
|
||||||
secondary_window->PollEvents();
|
secondary_window->PollEvents();
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef ANDROID
|
||||||
|
// on android, if secondary_window is defined at all
|
||||||
|
// it means we have a second display
|
||||||
|
if (secondary_window) {
|
||||||
|
const auto &secondary_layout = secondary_window->GetFramebufferLayout();
|
||||||
|
RenderToMailbox(secondary_layout, secondary_window->mailbox, false);
|
||||||
|
secondary_window->PollEvents();
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
if (frame_dumper.IsDumping()) {
|
if (frame_dumper.IsDumping()) {
|
||||||
try {
|
try {
|
||||||
@ -553,7 +571,7 @@ void RendererOpenGL::DrawSingleScreen(const ScreenInfo& screen_info, float x, fl
|
|||||||
state.texture_units[0].texture_2d = 0;
|
state.texture_units[0].texture_2d = 0;
|
||||||
state.texture_units[0].sampler = 0;
|
state.texture_units[0].sampler = 0;
|
||||||
state.Apply();
|
state.Apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws a single texture to the emulator window, rotating the texture to correct for the 3DS's LCD
|
* Draws a single texture to the emulator window, rotating the texture to correct for the 3DS's LCD
|
||||||
|
@ -54,6 +54,7 @@ public:
|
|||||||
void PrepareVideoDumping() override;
|
void PrepareVideoDumping() override;
|
||||||
void CleanupVideoDumping() override;
|
void CleanupVideoDumping() override;
|
||||||
void Sync() override;
|
void Sync() override;
|
||||||
|
void setSecondaryWindow(Frontend::EmuWindow *secondaryWindow) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void InitOpenGLObjects();
|
void InitOpenGLObjects();
|
||||||
|
@ -829,6 +829,16 @@ void RendererVulkan::SwapBuffers() {
|
|||||||
RenderToWindow(*second_window, secondary_layout, false);
|
RenderToWindow(*second_window, secondary_layout, false);
|
||||||
secondary_window->PollEvents();
|
secondary_window->PollEvents();
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef ANDROID
|
||||||
|
if (secondary_window) {
|
||||||
|
const auto &secondary_layout = secondary_window->GetFramebufferLayout();
|
||||||
|
if (!second_window) {
|
||||||
|
second_window = std::make_unique<PresentWindow>(*secondary_window, instance, scheduler);
|
||||||
|
}
|
||||||
|
RenderToWindow(*second_window, secondary_layout, false);
|
||||||
|
secondary_window->PollEvents();
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
rasterizer.TickFrame();
|
rasterizer.TickFrame();
|
||||||
EndFrame();
|
EndFrame();
|
||||||
@ -1119,4 +1129,10 @@ bool RendererVulkan::TryRenderScreenshotWithHostMemory() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RendererVulkan::NotifySurfaceChanged(bool second) {
|
||||||
|
if (second && second_window) second_window->NotifySurfaceChanged();
|
||||||
|
if (!second) main_window.NotifySurfaceChanged();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Vulkan
|
} // namespace Vulkan
|
||||||
|
@ -74,9 +74,7 @@ public:
|
|||||||
return &rasterizer;
|
return &rasterizer;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NotifySurfaceChanged() override {
|
void NotifySurfaceChanged(bool second) override;
|
||||||
main_window.NotifySurfaceChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SwapBuffers() override;
|
void SwapBuffers() override;
|
||||||
void TryPresent(int timeout_ms, bool is_secondary) override {}
|
void TryPresent(int timeout_ms, bool is_secondary) override {}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user