Merge remote-tracking branch 'origin/master' into ssl

This commit is contained in:
comex 2023-07-01 15:01:11 -07:00
commit 98685d48e3
300 changed files with 24064 additions and 17224 deletions

13
.gitmodules vendored
View file

@ -12,7 +12,7 @@
url = https://github.com/mozilla/cubeb.git url = https://github.com/mozilla/cubeb.git
[submodule "dynarmic"] [submodule "dynarmic"]
path = externals/dynarmic path = externals/dynarmic
url = https://github.com/MerryMage/dynarmic.git url = https://github.com/merryhime/dynarmic.git
[submodule "libusb"] [submodule "libusb"]
path = externals/libusb/libusb path = externals/libusb/libusb
url = https://github.com/libusb/libusb.git url = https://github.com/libusb/libusb.git
@ -24,10 +24,10 @@
url = https://github.com/KhronosGroup/Vulkan-Headers.git url = https://github.com/KhronosGroup/Vulkan-Headers.git
[submodule "sirit"] [submodule "sirit"]
path = externals/sirit path = externals/sirit
url = https://github.com/yuzu-emu/sirit url = https://github.com/yuzu-emu/sirit.git
[submodule "mbedtls"] [submodule "mbedtls"]
path = externals/mbedtls path = externals/mbedtls
url = https://github.com/yuzu-emu/mbedtls url = https://github.com/yuzu-emu/mbedtls.git
[submodule "xbyak"] [submodule "xbyak"]
path = externals/xbyak path = externals/xbyak
url = https://github.com/herumi/xbyak.git url = https://github.com/herumi/xbyak.git
@ -45,13 +45,16 @@
url = https://github.com/FFmpeg/FFmpeg.git url = https://github.com/FFmpeg/FFmpeg.git
[submodule "vcpkg"] [submodule "vcpkg"]
path = externals/vcpkg path = externals/vcpkg
url = https://github.com/Microsoft/vcpkg.git url = https://github.com/microsoft/vcpkg.git
[submodule "cpp-jwt"] [submodule "cpp-jwt"]
path = externals/cpp-jwt path = externals/cpp-jwt
url = https://github.com/arun11299/cpp-jwt.git url = https://github.com/arun11299/cpp-jwt.git
[submodule "libadrenotools"] [submodule "libadrenotools"]
path = externals/libadrenotools path = externals/libadrenotools
url = https://github.com/bylaws/libadrenotools url = https://github.com/bylaws/libadrenotools.git
[submodule "tzdb_to_nx"] [submodule "tzdb_to_nx"]
path = externals/nx_tzdb/tzdb_to_nx path = externals/nx_tzdb/tzdb_to_nx
url = https://github.com/lat9nq/tzdb_to_nx.git url = https://github.com/lat9nq/tzdb_to_nx.git
[submodule "VulkanMemoryAllocator"]
path = externals/vma/VulkanMemoryAllocator
url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git

View file

@ -504,7 +504,7 @@ if (ENABLE_SDL2)
if (YUZU_USE_BUNDLED_SDL2) if (YUZU_USE_BUNDLED_SDL2)
# Detect toolchain and platform # Detect toolchain and platform
if ((MSVC_VERSION GREATER_EQUAL 1920 AND MSVC_VERSION LESS 1940) AND ARCHITECTURE_x86_64) if ((MSVC_VERSION GREATER_EQUAL 1920 AND MSVC_VERSION LESS 1940) AND ARCHITECTURE_x86_64)
set(SDL2_VER "SDL2-2.0.18") set(SDL2_VER "SDL2-2.28.0")
else() else()
message(FATAL_ERROR "No bundled SDL2 binaries for your toolchain. Disable YUZU_USE_BUNDLED_SDL2 and provide your own.") message(FATAL_ERROR "No bundled SDL2 binaries for your toolchain. Disable YUZU_USE_BUNDLED_SDL2 and provide your own.")
endif() endif()
@ -524,7 +524,7 @@ if (ENABLE_SDL2)
elseif (YUZU_USE_EXTERNAL_SDL2) elseif (YUZU_USE_EXTERNAL_SDL2)
message(STATUS "Using SDL2 from externals.") message(STATUS "Using SDL2 from externals.")
else() else()
find_package(SDL2 2.0.18 REQUIRED) find_package(SDL2 2.26.4 REQUIRED)
endif() endif()
endif() endif()

1308
dist/languages/ca.ts vendored

File diff suppressed because it is too large Load diff

1294
dist/languages/cs.ts vendored

File diff suppressed because it is too large Load diff

1284
dist/languages/da.ts vendored

File diff suppressed because it is too large Load diff

1472
dist/languages/de.ts vendored

File diff suppressed because it is too large Load diff

1294
dist/languages/el.ts vendored

File diff suppressed because it is too large Load diff

1292
dist/languages/es.ts vendored

File diff suppressed because it is too large Load diff

1289
dist/languages/fr.ts vendored

File diff suppressed because it is too large Load diff

1340
dist/languages/id.ts vendored

File diff suppressed because it is too large Load diff

1332
dist/languages/it.ts vendored

File diff suppressed because it is too large Load diff

1352
dist/languages/ja_JP.ts vendored

File diff suppressed because it is too large Load diff

1357
dist/languages/ko_KR.ts vendored

File diff suppressed because it is too large Load diff

1288
dist/languages/nb.ts vendored

File diff suppressed because it is too large Load diff

1296
dist/languages/nl.ts vendored

File diff suppressed because it is too large Load diff

1368
dist/languages/pl.ts vendored

File diff suppressed because it is too large Load diff

1342
dist/languages/pt_BR.ts vendored

File diff suppressed because it is too large Load diff

1344
dist/languages/pt_PT.ts vendored

File diff suppressed because it is too large Load diff

1314
dist/languages/ru_RU.ts vendored

File diff suppressed because it is too large Load diff

1284
dist/languages/sv.ts vendored

File diff suppressed because it is too large Load diff

1288
dist/languages/tr_TR.ts vendored

File diff suppressed because it is too large Load diff

1306
dist/languages/uk.ts vendored

File diff suppressed because it is too large Load diff

1322
dist/languages/vi.ts vendored

File diff suppressed because it is too large Load diff

1322
dist/languages/vi_VN.ts vendored

File diff suppressed because it is too large Load diff

1288
dist/languages/zh_CN.ts vendored

File diff suppressed because it is too large Load diff

1602
dist/languages/zh_TW.ts vendored

File diff suppressed because it is too large Load diff

View file

@ -143,6 +143,11 @@ endif()
# TZDB (Time Zone Database) # TZDB (Time Zone Database)
add_subdirectory(nx_tzdb) add_subdirectory(nx_tzdb)
# VMA
add_library(vma vma/vma.cpp)
target_include_directories(vma PUBLIC ./vma/VulkanMemoryAllocator/include)
target_link_libraries(vma PRIVATE Vulkan::Headers)
if (NOT TARGET LLVM::Demangle) if (NOT TARGET LLVM::Demangle)
add_library(demangle demangle/ItaniumDemangle.cpp) add_library(demangle demangle/ItaniumDemangle.cpp)
target_include_directories(demangle PUBLIC ./demangle) target_include_directories(demangle PUBLIC ./demangle)
@ -152,6 +157,9 @@ endif()
add_library(stb stb/stb_dxt.cpp) add_library(stb stb/stb_dxt.cpp)
target_include_directories(stb PUBLIC ./stb) target_include_directories(stb PUBLIC ./stb)
add_library(bc_decoder bc_decoder/bc_decoder.cpp)
target_include_directories(bc_decoder PUBLIC ./bc_decoder)
if (ANDROID) if (ANDROID)
if (ARCHITECTURE_arm64) if (ARCHITECTURE_arm64)
add_subdirectory(libadrenotools) add_subdirectory(libadrenotools)

2
externals/SDL vendored

@ -1 +1 @@
Subproject commit f17058b562c8a1090c0c996b42982721ace90903 Subproject commit 491fba1d06a4810645092b2559b9cc94abeb23bb

1522
externals/bc_decoder/bc_decoder.cpp vendored Normal file

File diff suppressed because it is too large Load diff

43
externals/bc_decoder/bc_decoder.h vendored Normal file
View file

@ -0,0 +1,43 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include <cstdint>
namespace bcn {
/**
* @brief Decodes a BC1 encoded image to R8G8B8A8
*/
void DecodeBc1(const uint8_t *src, uint8_t *dst, size_t x, size_t y, size_t width, size_t height);
/**
* @brief Decodes a BC2 encoded image to R8G8B8A8
*/
void DecodeBc2(const uint8_t *src, uint8_t *dst, size_t x, size_t y, size_t width, size_t height);
/**
* @brief Decodes a BC3 encoded image to R8G8B8A8
*/
void DecodeBc3(const uint8_t *src, uint8_t *dst, size_t x, size_t y, size_t width, size_t height);
/**
* @brief Decodes a BC4 encoded image to R8
*/
void DecodeBc4(const uint8_t *src, uint8_t *dst, size_t x, size_t y, size_t width, size_t height, bool isSigned);
/**
* @brief Decodes a BC5 encoded image to R8G8
*/
void DecodeBc5(const uint8_t *src, uint8_t *dst, size_t x, size_t y, size_t width, size_t height, bool isSigned);
/**
* @brief Decodes a BC6 encoded image to R16G16B16A16
*/
void DecodeBc6(const uint8_t *src, uint8_t *dst, size_t x, size_t y, size_t width, size_t height, bool isSigned);
/**
* @brief Decodes a BC7 encoded image to R8G8B8A8
*/
void DecodeBc7(const uint8_t *src, uint8_t *dst, size_t x, size_t y, size_t width, size_t height);
}

View file

@ -7,7 +7,7 @@ add_library(nx_tzdb INTERFACE)
find_program(GIT git) find_program(GIT git)
find_program(GNU_MAKE make) find_program(GNU_MAKE make)
find_program(GNU_DATE date) find_program(DATE_PROG date)
set(CAN_BUILD_NX_TZDB true) set(CAN_BUILD_NX_TZDB true)
@ -17,7 +17,7 @@ endif()
if (NOT GNU_MAKE) if (NOT GNU_MAKE)
set(CAN_BUILD_NX_TZDB false) set(CAN_BUILD_NX_TZDB false)
endif() endif()
if (NOT GNU_DATE) if (NOT DATE_PROG)
set(CAN_BUILD_NX_TZDB false) set(CAN_BUILD_NX_TZDB false)
endif() endif()
if (CMAKE_SYSTEM_NAME STREQUAL "Windows" OR ANDROID) if (CMAKE_SYSTEM_NAME STREQUAL "Windows" OR ANDROID)

@ -1 +1 @@
Subproject commit 34df65eff295c2bd9ee9e6a077d662486d5cabb3 Subproject commit 212afa2394a74226dcf1b7996a570aae17debb69

@ -0,0 +1 @@
Subproject commit 0aa3989b8f382f185fdf646cc83a1d16fa31d6ab

8
externals/vma/vma.cpp vendored Normal file
View file

@ -0,0 +1,8 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#define VMA_IMPLEMENTATION
#define VMA_STATIC_VULKAN_FUNCTIONS 0
#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1
#include <vk_mem_alloc.h>

View file

@ -26,7 +26,7 @@ val autoVersion = (((System.currentTimeMillis() / 1000) - 1451606400) / 10).toIn
android { android {
namespace = "org.yuzu.yuzu_emu" namespace = "org.yuzu.yuzu_emu"
compileSdkVersion = "android-33" compileSdkVersion = "android-34"
ndkVersion = "25.2.9519653" ndkVersion = "25.2.9519653"
buildFeatures { buildFeatures {
@ -51,7 +51,7 @@ android {
// TODO If this is ever modified, change application_id in strings.xml // TODO If this is ever modified, change application_id in strings.xml
applicationId = "org.yuzu.yuzu_emu" applicationId = "org.yuzu.yuzu_emu"
minSdk = 30 minSdk = 30
targetSdk = 33 targetSdk = 34
versionName = getGitVersion() versionName = getGitVersion()
// If you want to use autoVersion for the versionCode, create a property in local.properties // If you want to use autoVersion for the versionCode, create a property in local.properties

View file

@ -13,6 +13,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="android.permission.NFC" /> <uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
@ -69,7 +70,9 @@ SPDX-License-Identifier: GPL-3.0-or-later
android:resource="@xml/nfc_tech_filter" /> android:resource="@xml/nfc_tech_filter" />
</activity> </activity>
<service android:name="org.yuzu.yuzu_emu.utils.ForegroundService"/> <service android:name="org.yuzu.yuzu_emu.utils.ForegroundService" android:foregroundServiceType="specialUse">
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE" android:value="Keep emulation running in background"/>
</service>
<provider <provider
android:name=".features.DocumentProvider" android:name=".features.DocumentProvider"

View file

@ -286,7 +286,7 @@ object NativeLibrary {
/** /**
* Unpauses emulation from a paused state. * Unpauses emulation from a paused state.
*/ */
external fun unPauseEmulation() external fun unpauseEmulation()
/** /**
* Pauses emulation. * Pauses emulation.
@ -313,6 +313,21 @@ object NativeLibrary {
*/ */
external fun isPaused(): Boolean external fun isPaused(): Boolean
/**
* Mutes emulation sound
*/
external fun muteAudio(): Boolean
/**
* Unmutes emulation sound
*/
external fun unmuteAudio(): Boolean
/**
* Returns true if emulation audio is muted.
*/
external fun isMuted(): Boolean
/** /**
* Returns the performance stats for the current game * Returns the performance stats for the current game
*/ */

View file

@ -27,13 +27,13 @@ import android.view.MotionEvent
import android.view.Surface import android.view.Surface
import android.view.View import android.view.View
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat import androidx.core.view.WindowInsetsControllerCompat
import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.NavHostFragment
import kotlin.math.roundToInt
import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding
@ -44,8 +44,10 @@ import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.utils.ControllerMappingHelper import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
import org.yuzu.yuzu_emu.utils.ForegroundService import org.yuzu.yuzu_emu.utils.ForegroundService
import org.yuzu.yuzu_emu.utils.InputHandler import org.yuzu.yuzu_emu.utils.InputHandler
import org.yuzu.yuzu_emu.utils.MemoryUtil
import org.yuzu.yuzu_emu.utils.NfcReader import org.yuzu.yuzu_emu.utils.NfcReader
import org.yuzu.yuzu_emu.utils.ThemeHelper import org.yuzu.yuzu_emu.utils.ThemeHelper
import kotlin.math.roundToInt
class EmulationActivity : AppCompatActivity(), SensorEventListener { class EmulationActivity : AppCompatActivity(), SensorEventListener {
private lateinit var binding: ActivityEmulationBinding private lateinit var binding: ActivityEmulationBinding
@ -63,6 +65,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
private val actionPause = "ACTION_EMULATOR_PAUSE" private val actionPause = "ACTION_EMULATOR_PAUSE"
private val actionPlay = "ACTION_EMULATOR_PLAY" private val actionPlay = "ACTION_EMULATOR_PLAY"
private val actionMute = "ACTION_EMULATOR_MUTE"
private val actionUnmute = "ACTION_EMULATOR_UNMUTE"
private val settingsViewModel: SettingsViewModel by viewModels() private val settingsViewModel: SettingsViewModel by viewModels()
@ -102,6 +106,19 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
inputHandler = InputHandler() inputHandler = InputHandler()
inputHandler.initialize() inputHandler.initialize()
val memoryUtil = MemoryUtil(this)
if (memoryUtil.isLessThan(8, MemoryUtil.Gb)) {
Toast.makeText(
this,
getString(
R.string.device_memory_inadequate,
memoryUtil.getDeviceRAM(),
"8 ${getString(R.string.memory_gigabyte)}"
),
Toast.LENGTH_LONG
).show()
}
// Start a foreground service to prevent the app from getting killed in the background // Start a foreground service to prevent the app from getting killed in the background
val startIntent = Intent(this, ForegroundService::class.java) val startIntent = Intent(this, ForegroundService::class.java)
startForegroundService(startIntent) startForegroundService(startIntent)
@ -305,6 +322,41 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
pictureInPictureActions.add(pauseRemoteAction) pictureInPictureActions.add(pauseRemoteAction)
} }
if (NativeLibrary.isMuted()) {
val unmuteIcon = Icon.createWithResource(
this@EmulationActivity,
R.drawable.ic_pip_unmute
)
val unmutePendingIntent = PendingIntent.getBroadcast(
this@EmulationActivity,
R.drawable.ic_pip_unmute,
Intent(actionUnmute),
pendingFlags
)
val unmuteRemoteAction = RemoteAction(
unmuteIcon,
getString(R.string.unmute),
getString(R.string.unmute),
unmutePendingIntent
)
pictureInPictureActions.add(unmuteRemoteAction)
} else {
val muteIcon = Icon.createWithResource(this@EmulationActivity, R.drawable.ic_pip_mute)
val mutePendingIntent = PendingIntent.getBroadcast(
this@EmulationActivity,
R.drawable.ic_pip_mute,
Intent(actionMute),
pendingFlags
)
val muteRemoteAction = RemoteAction(
muteIcon,
getString(R.string.mute),
getString(R.string.mute),
mutePendingIntent
)
pictureInPictureActions.add(muteRemoteAction)
}
return this.apply { setActions(pictureInPictureActions) } return this.apply { setActions(pictureInPictureActions) }
} }
@ -322,10 +374,15 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
private var pictureInPictureReceiver = object : BroadcastReceiver() { private var pictureInPictureReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) { override fun onReceive(context: Context?, intent: Intent) {
if (intent.action == actionPlay) { if (intent.action == actionPlay) {
if (NativeLibrary.isPaused()) NativeLibrary.unPauseEmulation() if (NativeLibrary.isPaused()) NativeLibrary.unpauseEmulation()
} else if (intent.action == actionPause) { } else if (intent.action == actionPause) {
if (!NativeLibrary.isPaused()) NativeLibrary.pauseEmulation() if (!NativeLibrary.isPaused()) NativeLibrary.pauseEmulation()
} }
if (intent.action == actionUnmute) {
if (NativeLibrary.isMuted()) NativeLibrary.unmuteAudio()
} else if (intent.action == actionMute) {
if (!NativeLibrary.isMuted()) NativeLibrary.muteAudio()
}
buildPictureInPictureParams() buildPictureInPictureParams()
} }
} }
@ -339,6 +396,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
IntentFilter().apply { IntentFilter().apply {
addAction(actionPause) addAction(actionPause)
addAction(actionPlay) addAction(actionPlay)
addAction(actionMute)
addAction(actionUnmute)
}.also { }.also {
registerReceiver(pictureInPictureReceiver, it) registerReceiver(pictureInPictureReceiver, it)
} }
@ -347,6 +406,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
unregisterReceiver(pictureInPictureReceiver) unregisterReceiver(pictureInPictureReceiver)
} catch (ignored: Exception) { } catch (ignored: Exception) {
} }
// Always resume audio, since there is no UI button
if (NativeLibrary.isMuted()) NativeLibrary.unmuteAudio()
} }
} }

View file

@ -714,7 +714,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
State.PAUSED -> { State.PAUSED -> {
Log.debug("[EmulationFragment] Resuming emulation.") Log.debug("[EmulationFragment] Resuming emulation.")
NativeLibrary.surfaceChanged(surface) NativeLibrary.surfaceChanged(surface)
NativeLibrary.unPauseEmulation() NativeLibrary.unpauseEmulation()
} }
else -> Log.debug("[EmulationFragment] Bug, run called while already running.") else -> Log.debug("[EmulationFragment] Bug, run called while already running.")

View file

@ -68,44 +68,65 @@ class HomeSettingsFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
mainActivity = requireActivity() as MainActivity mainActivity = requireActivity() as MainActivity
val optionsList: MutableList<HomeSetting> = mutableListOf( val optionsList: MutableList<HomeSetting> = mutableListOf<HomeSetting>().apply {
add(
HomeSetting( HomeSetting(
R.string.advanced_settings, R.string.advanced_settings,
R.string.settings_description, R.string.settings_description,
R.drawable.ic_settings R.drawable.ic_settings
) { SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") }, ) { SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") }
)
add(
HomeSetting( HomeSetting(
R.string.open_user_folder, R.string.open_user_folder,
R.string.open_user_folder_description, R.string.open_user_folder_description,
R.drawable.ic_folder_open R.drawable.ic_folder_open
) { openFileManager() }, ) { openFileManager() }
)
add(
HomeSetting( HomeSetting(
R.string.preferences_theme, R.string.preferences_theme,
R.string.theme_and_color_description, R.string.theme_and_color_description,
R.drawable.ic_palette R.drawable.ic_palette
) { SettingsActivity.launch(requireContext(), Settings.SECTION_THEME, "") }, ) { SettingsActivity.launch(requireContext(), Settings.SECTION_THEME, "") }
)
if (GpuDriverHelper.supportsCustomDriverLoading()) {
add(
HomeSetting( HomeSetting(
R.string.install_gpu_driver, R.string.install_gpu_driver,
R.string.install_gpu_driver_description, R.string.install_gpu_driver_description,
R.drawable.ic_exit R.drawable.ic_exit
) { driverInstaller() }, ) { driverInstaller() }
)
}
add(
HomeSetting( HomeSetting(
R.string.install_amiibo_keys, R.string.install_amiibo_keys,
R.string.install_amiibo_keys_description, R.string.install_amiibo_keys_description,
R.drawable.ic_nfc R.drawable.ic_nfc
) { mainActivity.getAmiiboKey.launch(arrayOf("*/*")) }, ) { mainActivity.getAmiiboKey.launch(arrayOf("*/*")) }
)
add(
HomeSetting( HomeSetting(
R.string.install_game_content, R.string.install_game_content,
R.string.install_game_content_description, R.string.install_game_content_description,
R.drawable.ic_system_update_alt R.drawable.ic_system_update_alt
) { mainActivity.installGameUpdate.launch(arrayOf("*/*")) }, ) { mainActivity.installGameUpdate.launch(arrayOf("*/*")) }
)
add(
HomeSetting( HomeSetting(
R.string.select_games_folder, R.string.select_games_folder,
R.string.select_games_folder_description, R.string.select_games_folder_description,
R.drawable.ic_add R.drawable.ic_add
) { ) {
mainActivity.getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) mainActivity.getGamesDirectory.launch(
}, Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data
)
}
)
add(
HomeSetting( HomeSetting(
R.string.manage_save_data, R.string.manage_save_data,
R.string.import_export_saves_description, R.string.import_export_saves_description,
@ -115,22 +136,30 @@ class HomeSettingsFragment : Fragment() {
parentFragmentManager, parentFragmentManager,
ImportExportSavesFragment.TAG ImportExportSavesFragment.TAG
) )
}, }
)
add(
HomeSetting( HomeSetting(
R.string.install_prod_keys, R.string.install_prod_keys,
R.string.install_prod_keys_description, R.string.install_prod_keys_description,
R.drawable.ic_unlock R.drawable.ic_unlock
) { mainActivity.getProdKey.launch(arrayOf("*/*")) }, ) { mainActivity.getProdKey.launch(arrayOf("*/*")) }
)
add(
HomeSetting( HomeSetting(
R.string.install_firmware, R.string.install_firmware,
R.string.install_firmware_description, R.string.install_firmware_description,
R.drawable.ic_firmware R.drawable.ic_firmware
) { mainActivity.getFirmware.launch(arrayOf("application/zip")) }, ) { mainActivity.getFirmware.launch(arrayOf("application/zip")) }
)
add(
HomeSetting( HomeSetting(
R.string.share_log, R.string.share_log,
R.string.share_log_description, R.string.share_log_description,
R.drawable.ic_log R.drawable.ic_log
) { shareLog() }, ) { shareLog() }
)
add(
HomeSetting( HomeSetting(
R.string.about, R.string.about,
R.string.about_description, R.string.about_description,
@ -141,6 +170,7 @@ class HomeSettingsFragment : Fragment() {
?.navigate(R.id.action_homeSettingsFragment_to_aboutFragment) ?.navigate(R.id.action_homeSettingsFragment_to_aboutFragment)
} }
) )
}
if (!BuildConfig.PREMIUM) { if (!BuildConfig.PREMIUM) {
optionsList.add( optionsList.add(

View file

@ -0,0 +1,62 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.fragments
import android.app.Dialog
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.yuzu.yuzu_emu.R
class LongMessageDialogFragment : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val titleId = requireArguments().getInt(TITLE)
val description = requireArguments().getString(DESCRIPTION)
val helpLinkId = requireArguments().getInt(HELP_LINK)
val dialog = MaterialAlertDialogBuilder(requireContext())
.setPositiveButton(R.string.close, null)
.setTitle(titleId)
.setMessage(description)
if (helpLinkId != 0) {
dialog.setNeutralButton(R.string.learn_more) { _, _ ->
openLink(getString(helpLinkId))
}
}
return dialog.show()
}
private fun openLink(link: String) {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
startActivity(intent)
}
companion object {
const val TAG = "LongMessageDialogFragment"
private const val TITLE = "Title"
private const val DESCRIPTION = "Description"
private const val HELP_LINK = "Link"
fun newInstance(
titleId: Int,
description: String,
helpLinkId: Int = 0
): LongMessageDialogFragment {
val dialog = LongMessageDialogFragment()
val bundle = Bundle()
bundle.apply {
putInt(TITLE, titleId)
putString(DESCRIPTION, description)
putInt(HELP_LINK, helpLinkId)
}
dialog.arguments = bundle
return dialog
}
}
}

View file

@ -29,7 +29,6 @@ import org.yuzu.yuzu_emu.layout.AutofitGridLayoutManager
import org.yuzu.yuzu_emu.model.Game import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.model.GamesViewModel import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.utils.FileUtil
class SearchFragment : Fragment() { class SearchFragment : Fragment() {
private var _binding: FragmentSearchBinding? = null private var _binding: FragmentSearchBinding? = null
@ -128,10 +127,7 @@ class SearchFragment : Fragment() {
R.id.chip_homebrew -> baseList.filter { it.isHomebrew } R.id.chip_homebrew -> baseList.filter { it.isHomebrew }
R.id.chip_retail -> baseList.filter { R.id.chip_retail -> baseList.filter { !it.isHomebrew }
FileUtil.hasExtension(it.path, "xci") ||
FileUtil.hasExtension(it.path, "nsp")
}
else -> baseList else -> baseList
} }

View file

@ -43,7 +43,7 @@ class Game(
companion object { companion object {
val extensions: Set<String> = HashSet( val extensions: Set<String> = HashSet(
listOf(".xci", ".nsp", ".nca", ".nro") listOf("xci", "nsp", "nca", "nro")
) )
} }
} }

View file

@ -4,6 +4,7 @@
package org.yuzu.yuzu_emu.ui.main package org.yuzu.yuzu_emu.ui.main
import android.content.Intent import android.content.Intent
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.view.ViewGroup.MarginLayoutParams import android.view.ViewGroup.MarginLayoutParams
@ -42,6 +43,7 @@ import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
import org.yuzu.yuzu_emu.fragments.LongMessageDialogFragment
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
import org.yuzu.yuzu_emu.model.GamesViewModel import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel import org.yuzu.yuzu_emu.model.HomeViewModel
@ -294,7 +296,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
return@registerForActivityResult return@registerForActivityResult
} }
if (!FileUtil.hasExtension(result, "keys")) { if (FileUtil.getExtension(result) != "keys") {
MessageDialogFragment.newInstance( MessageDialogFragment.newInstance(
R.string.reading_keys_failure, R.string.reading_keys_failure,
R.string.install_prod_keys_failure_extension_description R.string.install_prod_keys_failure_extension_description
@ -391,7 +393,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
return@registerForActivityResult return@registerForActivityResult
} }
if (!FileUtil.hasExtension(result, "bin")) { if (FileUtil.getExtension(result) != "bin") {
MessageDialogFragment.newInstance( MessageDialogFragment.newInstance(
R.string.reading_keys_failure, R.string.reading_keys_failure,
R.string.install_amiibo_keys_failure_extension_description R.string.install_amiibo_keys_failure_extension_description
@ -481,62 +483,110 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
} }
} }
val installGameUpdate = val installGameUpdate = registerForActivityResult(
registerForActivityResult(ActivityResultContracts.OpenDocument()) { ActivityResultContracts.OpenMultipleDocuments()
if (it == null) { ) { documents: List<Uri> ->
return@registerForActivityResult if (documents.isNotEmpty()) {
}
IndeterminateProgressDialogFragment.newInstance( IndeterminateProgressDialogFragment.newInstance(
this@MainActivity, this@MainActivity,
R.string.install_game_content R.string.install_game_content
) { ) {
val result = NativeLibrary.installFileToNand(it.toString()) var installSuccess = 0
var installOverwrite = 0
var errorBaseGame = 0
var errorExtension = 0
var errorOther = 0
var errorTotal = 0
lifecycleScope.launch { lifecycleScope.launch {
withContext(Dispatchers.Main) { documents.forEach {
when (result) { when (NativeLibrary.installFileToNand(it.toString())) {
NativeLibrary.InstallFileToNandResult.Success -> { NativeLibrary.InstallFileToNandResult.Success -> {
Toast.makeText( installSuccess += 1
applicationContext,
R.string.install_game_content_success,
Toast.LENGTH_SHORT
).show()
} }
NativeLibrary.InstallFileToNandResult.SuccessFileOverwritten -> { NativeLibrary.InstallFileToNandResult.SuccessFileOverwritten -> {
Toast.makeText( installOverwrite += 1
applicationContext,
R.string.install_game_content_success_overwrite,
Toast.LENGTH_SHORT
).show()
} }
NativeLibrary.InstallFileToNandResult.ErrorBaseGame -> { NativeLibrary.InstallFileToNandResult.ErrorBaseGame -> {
MessageDialogFragment.newInstance( errorBaseGame += 1
R.string.install_game_content_failure,
R.string.install_game_content_failure_base
).show(supportFragmentManager, MessageDialogFragment.TAG)
} }
NativeLibrary.InstallFileToNandResult.ErrorFilenameExtension -> { NativeLibrary.InstallFileToNandResult.ErrorFilenameExtension -> {
MessageDialogFragment.newInstance( errorExtension += 1
R.string.install_game_content_failure,
R.string.install_game_content_failure_file_extension,
R.string.install_game_content_help_link
).show(supportFragmentManager, MessageDialogFragment.TAG)
} }
else -> { else -> {
MessageDialogFragment.newInstance( errorOther += 1
}
}
}
withContext(Dispatchers.Main) {
val separator = System.getProperty("line.separator") ?: "\n"
val installResult = StringBuilder()
if (installSuccess > 0) {
installResult.append(
getString(
R.string.install_game_content_success_install,
installSuccess
)
)
installResult.append(separator)
}
if (installOverwrite > 0) {
installResult.append(
getString(
R.string.install_game_content_success_overwrite,
installOverwrite
)
)
installResult.append(separator)
}
errorTotal = errorBaseGame + errorExtension + errorOther
if (errorTotal > 0) {
installResult.append(separator)
installResult.append(
getString(
R.string.install_game_content_failed_count,
errorTotal
)
)
installResult.append(separator)
if (errorBaseGame > 0) {
installResult.append(separator)
installResult.append(
getString(R.string.install_game_content_failure_base)
)
installResult.append(separator)
}
if (errorExtension > 0) {
installResult.append(separator)
installResult.append(
getString(R.string.install_game_content_failure_file_extension)
)
installResult.append(separator)
}
if (errorOther > 0) {
installResult.append(
getString(R.string.install_game_content_failure_description)
)
installResult.append(separator)
}
LongMessageDialogFragment.newInstance(
R.string.install_game_content_failure, R.string.install_game_content_failure,
R.string.install_game_content_failure_description, installResult.toString().trim(),
R.string.install_game_content_help_link R.string.install_game_content_help_link
).show(supportFragmentManager, MessageDialogFragment.TAG) ).show(supportFragmentManager, LongMessageDialogFragment.TAG)
} else {
LongMessageDialogFragment.newInstance(
R.string.install_game_content_success,
installResult.toString().trim()
).show(supportFragmentManager, LongMessageDialogFragment.TAG)
} }
} }
} }
} return@newInstance installSuccess + installOverwrite + errorTotal
return@newInstance result
}.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
} }
} }
}

View file

@ -7,7 +7,6 @@ import android.content.Context
import android.database.Cursor import android.database.Cursor
import android.net.Uri import android.net.Uri
import android.provider.DocumentsContract import android.provider.DocumentsContract
import android.provider.OpenableColumns
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import java.io.BufferedInputStream import java.io.BufferedInputStream
import java.io.File import java.io.File
@ -185,19 +184,18 @@ object FileUtil {
/** /**
* Get file display name from given path * Get file display name from given path
* @param path content uri path * @param uri content uri
* @return String display name * @return String display name
*/ */
fun getFilename(context: Context, path: String): String { fun getFilename(uri: Uri): String {
val resolver = context.contentResolver val resolver = YuzuApplication.appContext.contentResolver
val columns = arrayOf( val columns = arrayOf(
DocumentsContract.Document.COLUMN_DISPLAY_NAME DocumentsContract.Document.COLUMN_DISPLAY_NAME
) )
var filename = "" var filename = ""
var c: Cursor? = null var c: Cursor? = null
try { try {
val mUri = Uri.parse(path) c = resolver.query(uri, columns, null, null, null)
c = resolver.query(mUri, columns, null, null, null)
c!!.moveToNext() c!!.moveToNext()
filename = c.getString(0) filename = c.getString(0)
} catch (e: Exception) { } catch (e: Exception) {
@ -326,25 +324,9 @@ object FileUtil {
} }
} }
fun hasExtension(path: String, extension: String): Boolean = fun getExtension(uri: Uri): String {
path.substring(path.lastIndexOf(".") + 1).contains(extension) val fileName = getFilename(uri)
return fileName.substring(fileName.lastIndexOf(".") + 1)
fun hasExtension(uri: Uri, extension: String): Boolean { .lowercase()
val fileName: String?
val cursor = YuzuApplication.appContext.contentResolver.query(uri, null, null, null, null)
val nameIndex = cursor?.getColumnIndex(OpenableColumns.DISPLAY_NAME)
cursor?.moveToFirst()
if (nameIndex == null) {
return false
}
fileName = cursor.getString(nameIndex)
cursor.close()
if (fileName == null) {
return false
}
return fileName.substring(fileName.lastIndexOf(".") + 1).contains(extension)
} }
} }

View file

@ -6,7 +6,6 @@ package org.yuzu.yuzu_emu.utils
import android.content.SharedPreferences import android.content.SharedPreferences
import android.net.Uri import android.net.Uri
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import java.util.*
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.NativeLibrary
@ -33,15 +32,9 @@ object GameHelper {
val children = FileUtil.listFiles(context, gamesUri) val children = FileUtil.listFiles(context, gamesUri)
for (file in children) { for (file in children) {
if (!file.isDirectory) { if (!file.isDirectory) {
val filename = file.uri.toString()
val extensionStart = filename.lastIndexOf('.')
if (extensionStart > 0) {
val fileExtension = filename.substring(extensionStart)
// Check that the file has an extension we care about before trying to read out of it. // Check that the file has an extension we care about before trying to read out of it.
if (Game.extensions.contains(fileExtension.lowercase(Locale.getDefault()))) { if (Game.extensions.contains(FileUtil.getExtension(file.uri))) {
games.add(getGame(filename)) games.add(getGame(file.uri))
}
} }
} }
} }
@ -59,21 +52,19 @@ object GameHelper {
return games.toList() return games.toList()
} }
private fun getGame(filePath: String): Game { private fun getGame(uri: Uri): Game {
val filePath = uri.toString()
var name = NativeLibrary.getTitle(filePath) var name = NativeLibrary.getTitle(filePath)
// If the game's title field is empty, use the filename. // If the game's title field is empty, use the filename.
if (name.isEmpty()) { if (name.isEmpty()) {
name = filePath.substring(filePath.lastIndexOf("/") + 1) name = FileUtil.getFilename(uri)
} }
var gameId = NativeLibrary.getGameId(filePath) var gameId = NativeLibrary.getGameId(filePath)
// If the game's ID field is empty, use the filename without extension. // If the game's ID field is empty, use the filename without extension.
if (gameId.isEmpty()) { if (gameId.isEmpty()) {
gameId = filePath.substring( gameId = name.substring(0, name.lastIndexOf("."))
filePath.lastIndexOf("/") + 1,
filePath.lastIndexOf(".")
)
} }
val newGame = Game( val newGame = Game(

View file

@ -113,6 +113,8 @@ object GpuDriverHelper {
initializeDriverParameters(context) initializeDriverParameters(context)
} }
external fun supportsCustomDriverLoading(): Boolean
// Parse the custom driver metadata to retrieve the name. // Parse the custom driver metadata to retrieve the name.
val customDriverName: String? val customDriverName: String?
get() { get() {

View file

@ -0,0 +1,59 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.utils
import android.app.ActivityManager
import android.content.Context
import org.yuzu.yuzu_emu.R
import java.util.Locale
class MemoryUtil(val context: Context) {
private val Long.floatForm: String
get() = String.format(Locale.ROOT, "%.2f", this.toDouble())
private fun bytesToSizeUnit(size: Long): String {
return when {
size < Kb -> "${size.floatForm} ${context.getString(R.string.memory_byte)}"
size < Mb -> "${(size / Kb).floatForm} ${context.getString(R.string.memory_kilobyte)}"
size < Gb -> "${(size / Mb).floatForm} ${context.getString(R.string.memory_megabyte)}"
size < Tb -> "${(size / Gb).floatForm} ${context.getString(R.string.memory_gigabyte)}"
size < Pb -> "${(size / Tb).floatForm} ${context.getString(R.string.memory_terabyte)}"
size < Eb -> "${(size / Pb).floatForm} ${context.getString(R.string.memory_petabyte)}"
else -> "${(size / Eb).floatForm} ${context.getString(R.string.memory_exabyte)}"
}
}
private val totalMemory =
with(context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager) {
val memInfo = ActivityManager.MemoryInfo()
getMemoryInfo(memInfo)
memInfo.totalMem
}
fun isLessThan(minimum: Int, size: Long): Boolean {
return when (size) {
Kb -> totalMemory < Mb && totalMemory < minimum
Mb -> totalMemory < Gb && (totalMemory / Mb) < minimum
Gb -> totalMemory < Tb && (totalMemory / Gb) < minimum
Tb -> totalMemory < Pb && (totalMemory / Tb) < minimum
Pb -> totalMemory < Eb && (totalMemory / Pb) < minimum
Eb -> totalMemory / Eb < minimum
else -> totalMemory < Kb && totalMemory < minimum
}
}
fun getDeviceRAM(): String {
return bytesToSizeUnit(totalMemory)
}
companion object {
const val Kb: Long = 1024
const val Mb = Kb * 1024
const val Gb = Mb * 1024
const val Tb = Gb * 1024
const val Pb = Tb * 1024
const val Eb = Pb * 1024
}
}

View file

@ -14,7 +14,6 @@ add_library(yuzu-android SHARED
id_cache.cpp id_cache.cpp
id_cache.h id_cache.h
native.cpp native.cpp
native.h
) )
set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR}) set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR})

View file

@ -14,6 +14,7 @@
#include <android/api-level.h> #include <android/api-level.h>
#include <android/native_window_jni.h> #include <android/native_window_jni.h>
#include <core/loader/nro.h> #include <core/loader/nro.h>
#include <jni.h>
#include "common/detached_tasks.h" #include "common/detached_tasks.h"
#include "common/dynamic_library.h" #include "common/dynamic_library.h"
@ -59,6 +60,9 @@
#include "video_core/rasterizer_interface.h" #include "video_core/rasterizer_interface.h"
#include "video_core/renderer_base.h" #include "video_core/renderer_base.h"
#define jconst [[maybe_unused]] const auto
#define jauto [[maybe_unused]] auto
namespace { namespace {
class EmulationSession final { class EmulationSession final {
@ -98,7 +102,7 @@ public:
} }
int InstallFileToNand(std::string filename) { int InstallFileToNand(std::string filename) {
const auto copy_func = [](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest, jconst copy_func = [](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest,
std::size_t block_size) { std::size_t block_size) {
if (src == nullptr || dest == nullptr) { if (src == nullptr || dest == nullptr) {
return false; return false;
@ -108,10 +112,10 @@ public:
} }
using namespace Common::Literals; using namespace Common::Literals;
std::vector<u8> buffer(1_MiB); [[maybe_unused]] std::vector<u8> buffer(1_MiB);
for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) { for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) {
const auto read = src->Read(buffer.data(), buffer.size(), i); jconst read = src->Read(buffer.data(), buffer.size(), i);
dest->Write(buffer.data(), read, i); dest->Write(buffer.data(), read, i);
} }
return true; return true;
@ -128,14 +132,14 @@ public:
m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>()); m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
m_system.GetFileSystemController().CreateFactories(*m_vfs); m_system.GetFileSystemController().CreateFactories(*m_vfs);
std::shared_ptr<FileSys::NSP> nsp; [[maybe_unused]] std::shared_ptr<FileSys::NSP> nsp;
if (filename.ends_with("nsp")) { if (filename.ends_with("nsp")) {
nsp = std::make_shared<FileSys::NSP>(m_vfs->OpenFile(filename, FileSys::Mode::Read)); nsp = std::make_shared<FileSys::NSP>(m_vfs->OpenFile(filename, FileSys::Mode::Read));
if (nsp->IsExtractedType()) { if (nsp->IsExtractedType()) {
return InstallError; return InstallError;
} }
} else if (filename.ends_with("xci")) { } else if (filename.ends_with("xci")) {
const auto xci = jconst xci =
std::make_shared<FileSys::XCI>(m_vfs->OpenFile(filename, FileSys::Mode::Read)); std::make_shared<FileSys::XCI>(m_vfs->OpenFile(filename, FileSys::Mode::Read));
nsp = xci->GetSecurePartitionNSP(); nsp = xci->GetSecurePartitionNSP();
} else { } else {
@ -150,7 +154,7 @@ public:
return InstallError; return InstallError;
} }
const auto res = m_system.GetFileSystemController().GetUserNANDContents()->InstallEntry( jconst res = m_system.GetFileSystemController().GetUserNANDContents()->InstallEntry(
*nsp, true, copy_func); *nsp, true, copy_func);
switch (res) { switch (res) {
@ -233,10 +237,11 @@ public:
m_system.SetFilesystem(m_vfs); m_system.SetFilesystem(m_vfs);
// Initialize system. // Initialize system.
auto android_keyboard = std::make_unique<SoftwareKeyboard::AndroidKeyboard>(); jauto android_keyboard = std::make_unique<SoftwareKeyboard::AndroidKeyboard>();
m_software_keyboard = android_keyboard.get(); m_software_keyboard = android_keyboard.get();
m_system.SetShuttingDown(false); m_system.SetShuttingDown(false);
m_system.ApplySettings(); m_system.ApplySettings();
Settings::LogSettings();
m_system.HIDCore().ReloadInputDevices(); m_system.HIDCore().ReloadInputDevices();
m_system.SetAppletFrontendSet({ m_system.SetAppletFrontendSet({
nullptr, // Amiibo Settings nullptr, // Amiibo Settings
@ -330,7 +335,7 @@ public:
while (true) { while (true) {
{ {
std::unique_lock lock(m_mutex); [[maybe_unused]] std::unique_lock lock(m_mutex);
if (m_cv.wait_for(lock, std::chrono::milliseconds(800), if (m_cv.wait_for(lock, std::chrono::milliseconds(800),
[&]() { return !m_is_running; })) { [&]() { return !m_is_running; })) {
// Emulation halted. // Emulation halted.
@ -362,7 +367,7 @@ public:
} }
bool IsHandheldOnly() { bool IsHandheldOnly() {
const auto npad_style_set = m_system.HIDCore().GetSupportedStyleTag(); jconst npad_style_set = m_system.HIDCore().GetSupportedStyleTag();
if (npad_style_set.fullkey == 1) { if (npad_style_set.fullkey == 1) {
return false; return false;
@ -375,17 +380,17 @@ public:
return !Settings::values.use_docked_mode.GetValue(); return !Settings::values.use_docked_mode.GetValue();
} }
void SetDeviceType(int index, int type) { void SetDeviceType([[maybe_unused]] int index, int type) {
auto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index); jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
controller->SetNpadStyleIndex(static_cast<Core::HID::NpadStyleIndex>(type)); controller->SetNpadStyleIndex(static_cast<Core::HID::NpadStyleIndex>(type));
} }
void OnGamepadConnectEvent(int index) { void OnGamepadConnectEvent([[maybe_unused]] int index) {
auto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index); jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
// Ensure that player1 is configured correctly and handheld disconnected // Ensure that player1 is configured correctly and handheld disconnected
if (controller->GetNpadIdType() == Core::HID::NpadIdType::Player1) { if (controller->GetNpadIdType() == Core::HID::NpadIdType::Player1) {
auto handheld = jauto handheld =
m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld); m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
if (controller->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::Handheld) { if (controller->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::Handheld) {
@ -397,7 +402,8 @@ public:
// Ensure that handheld is configured correctly and player 1 disconnected // Ensure that handheld is configured correctly and player 1 disconnected
if (controller->GetNpadIdType() == Core::HID::NpadIdType::Handheld) { if (controller->GetNpadIdType() == Core::HID::NpadIdType::Handheld) {
auto player1 = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); jauto player1 =
m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
if (controller->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::Handheld) { if (controller->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::Handheld) {
player1->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld); player1->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
@ -411,8 +417,8 @@ public:
} }
} }
void OnGamepadDisconnectEvent(int index) { void OnGamepadDisconnectEvent([[maybe_unused]] int index) {
auto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index); jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
controller->Disconnect(); controller->Disconnect();
} }
@ -428,7 +434,7 @@ private:
}; };
RomMetadata GetRomMetadata(const std::string& path) { RomMetadata GetRomMetadata(const std::string& path) {
if (auto search = m_rom_metadata_cache.find(path); search != m_rom_metadata_cache.end()) { if (jauto search = m_rom_metadata_cache.find(path); search != m_rom_metadata_cache.end()) {
return search->second; return search->second;
} }
@ -436,14 +442,14 @@ private:
} }
RomMetadata CacheRomMetadata(const std::string& path) { RomMetadata CacheRomMetadata(const std::string& path) {
const auto file = Core::GetGameFileFromPath(m_vfs, path); jconst file = Core::GetGameFileFromPath(m_vfs, path);
auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0); jauto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0);
RomMetadata entry; RomMetadata entry;
loader->ReadTitle(entry.title); loader->ReadTitle(entry.title);
loader->ReadIcon(entry.icon); loader->ReadIcon(entry.icon);
if (loader->GetFileType() == Loader::FileType::NRO) { if (loader->GetFileType() == Loader::FileType::NRO) {
auto loader_nro = dynamic_cast<Loader::AppLoader_NRO*>(loader.get()); jauto loader_nro = dynamic_cast<Loader::AppLoader_NRO*>(loader.get());
entry.isHomebrew = loader_nro->IsHomebrew(); entry.isHomebrew = loader_nro->IsHomebrew();
} else { } else {
entry.isHomebrew = false; entry.isHomebrew = false;
@ -514,7 +520,7 @@ static Core::SystemResultStatus RunEmulation(const std::string& filepath) {
SCOPE_EXIT({ EmulationSession::GetInstance().ShutdownEmulation(); }); SCOPE_EXIT({ EmulationSession::GetInstance().ShutdownEmulation(); });
const auto result = EmulationSession::GetInstance().InitializeEmulation(filepath); jconst result = EmulationSession::GetInstance().InitializeEmulation(filepath);
if (result != Core::SystemResultStatus::Success) { if (result != Core::SystemResultStatus::Success) {
return result; return result;
} }
@ -526,83 +532,104 @@ static Core::SystemResultStatus RunEmulation(const std::string& filepath) {
extern "C" { extern "C" {
void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceChanged(JNIEnv* env, void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceChanged(JNIEnv* env, jobject instance,
[[maybe_unused]] jclass clazz, [[maybe_unused]] jobject surf) {
jobject surf) {
EmulationSession::GetInstance().SetNativeWindow(ANativeWindow_fromSurface(env, surf)); EmulationSession::GetInstance().SetNativeWindow(ANativeWindow_fromSurface(env, surf));
EmulationSession::GetInstance().SurfaceChanged(); EmulationSession::GetInstance().SurfaceChanged();
} }
void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceDestroyed(JNIEnv* env, void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceDestroyed(JNIEnv* env, jobject instance) {
[[maybe_unused]] jclass clazz) {
ANativeWindow_release(EmulationSession::GetInstance().NativeWindow()); ANativeWindow_release(EmulationSession::GetInstance().NativeWindow());
EmulationSession::GetInstance().SetNativeWindow(nullptr); EmulationSession::GetInstance().SetNativeWindow(nullptr);
EmulationSession::GetInstance().SurfaceChanged(); EmulationSession::GetInstance().SurfaceChanged();
} }
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env, void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env, jobject instance,
[[maybe_unused]] jclass clazz, [[maybe_unused]] jstring j_directory) {
jstring j_directory) {
Common::FS::SetAppDirectory(GetJString(env, j_directory)); Common::FS::SetAppDirectory(GetJString(env, j_directory));
} }
int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env, int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env, jobject instance,
[[maybe_unused]] jclass clazz, [[maybe_unused]] jstring j_file) {
jstring j_file) {
return EmulationSession::GetInstance().InstallFileToNand(GetJString(env, j_file)); return EmulationSession::GetInstance().InstallFileToNand(GetJString(env, j_file));
} }
void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeGpuDriver( void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeGpuDriver(JNIEnv* env, jclass clazz,
JNIEnv* env, [[maybe_unused]] jclass clazz, jstring hook_lib_dir, jstring custom_driver_dir, jstring hook_lib_dir,
jstring custom_driver_name, jstring file_redirect_dir) { jstring custom_driver_dir,
jstring custom_driver_name,
jstring file_redirect_dir) {
EmulationSession::GetInstance().InitializeGpuDriver( EmulationSession::GetInstance().InitializeGpuDriver(
GetJString(env, hook_lib_dir), GetJString(env, custom_driver_dir), 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));
} }
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadKeys(JNIEnv* env, [[maybe_unused]] static bool CheckKgslPresent() {
[[maybe_unused]] jclass clazz) { constexpr auto KgslPath{"/dev/kgsl-3d0"};
return access(KgslPath, F_OK) == 0;
}
[[maybe_unused]] bool SupportsCustomDriver() {
return android_get_device_api_level() >= 28 && CheckKgslPresent();
}
jboolean JNICALL Java_org_yuzu_yuzu_1emu_utils_GpuDriverHelper_supportsCustomDriverLoading(
JNIEnv* env, jobject instance) {
#ifdef ARCHITECTURE_arm64
// If the KGSL device exists custom drivers can be loaded using adrenotools
return SupportsCustomDriver();
#else
return false;
#endif
}
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadKeys(JNIEnv* env, jclass clazz) {
Core::Crypto::KeyManager::Instance().ReloadKeys(); Core::Crypto::KeyManager::Instance().ReloadKeys();
return static_cast<jboolean>(Core::Crypto::KeyManager::Instance().AreKeysLoaded()); return static_cast<jboolean>(Core::Crypto::KeyManager::Instance().AreKeysLoaded());
} }
void Java_org_yuzu_yuzu_1emu_NativeLibrary_unPauseEmulation([[maybe_unused]] JNIEnv* env, void Java_org_yuzu_yuzu_1emu_NativeLibrary_unpauseEmulation(JNIEnv* env, jclass clazz) {
[[maybe_unused]] jclass clazz) {
EmulationSession::GetInstance().UnPauseEmulation(); EmulationSession::GetInstance().UnPauseEmulation();
} }
void Java_org_yuzu_yuzu_1emu_NativeLibrary_pauseEmulation([[maybe_unused]] JNIEnv* env, void Java_org_yuzu_yuzu_1emu_NativeLibrary_pauseEmulation(JNIEnv* env, jclass clazz) {
[[maybe_unused]] jclass clazz) {
EmulationSession::GetInstance().PauseEmulation(); EmulationSession::GetInstance().PauseEmulation();
} }
void Java_org_yuzu_yuzu_1emu_NativeLibrary_stopEmulation([[maybe_unused]] JNIEnv* env, void Java_org_yuzu_yuzu_1emu_NativeLibrary_stopEmulation(JNIEnv* env, jclass clazz) {
[[maybe_unused]] jclass clazz) {
EmulationSession::GetInstance().HaltEmulation(); EmulationSession::GetInstance().HaltEmulation();
} }
void Java_org_yuzu_yuzu_1emu_NativeLibrary_resetRomMetadata([[maybe_unused]] JNIEnv* env, void Java_org_yuzu_yuzu_1emu_NativeLibrary_resetRomMetadata(JNIEnv* env, jclass clazz) {
[[maybe_unused]] jclass clazz) {
EmulationSession::GetInstance().ResetRomMetadata(); EmulationSession::GetInstance().ResetRomMetadata();
} }
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isRunning([[maybe_unused]] JNIEnv* env, jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isRunning(JNIEnv* env, jclass clazz) {
[[maybe_unused]] jclass clazz) {
return static_cast<jboolean>(EmulationSession::GetInstance().IsRunning()); return static_cast<jboolean>(EmulationSession::GetInstance().IsRunning());
} }
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isPaused([[maybe_unused]] JNIEnv* env, jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isPaused(JNIEnv* env, jclass clazz) {
[[maybe_unused]] jclass clazz) {
return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused()); return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused());
} }
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly([[maybe_unused]] JNIEnv* env, void Java_org_yuzu_yuzu_1emu_NativeLibrary_muteAduio(JNIEnv* env, jclass clazz) {
[[maybe_unused]] jclass clazz) { Settings::values.audio_muted = true;
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_unmuteAudio(JNIEnv* env, jclass clazz) {
Settings::values.audio_muted = false;
}
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isMuted(JNIEnv* env, jclass clazz) {
return static_cast<jboolean>(Settings::values.audio_muted.GetValue());
}
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly(JNIEnv* env, jclass clazz) {
return EmulationSession::GetInstance().IsHandheldOnly(); return EmulationSession::GetInstance().IsHandheldOnly();
} }
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_setDeviceType([[maybe_unused]] JNIEnv* env, jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_setDeviceType(JNIEnv* env, jclass clazz,
[[maybe_unused]] jclass clazz,
jint j_device, jint j_type) { jint j_device, jint j_type) {
if (EmulationSession::GetInstance().IsRunning()) { if (EmulationSession::GetInstance().IsRunning()) {
EmulationSession::GetInstance().SetDeviceType(j_device, j_type); EmulationSession::GetInstance().SetDeviceType(j_device, j_type);
@ -610,8 +637,7 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_setDeviceType([[maybe_unused]] JN
return static_cast<jboolean>(true); return static_cast<jboolean>(true);
} }
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadConnectEvent([[maybe_unused]] JNIEnv* env, jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadConnectEvent(JNIEnv* env, jclass clazz,
[[maybe_unused]] jclass clazz,
jint j_device) { jint j_device) {
if (EmulationSession::GetInstance().IsRunning()) { if (EmulationSession::GetInstance().IsRunning()) {
EmulationSession::GetInstance().OnGamepadConnectEvent(j_device); EmulationSession::GetInstance().OnGamepadConnectEvent(j_device);
@ -619,17 +645,16 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadConnectEvent([[maybe_unu
return static_cast<jboolean>(true); return static_cast<jboolean>(true);
} }
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadDisconnectEvent( jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadDisconnectEvent(JNIEnv* env, jclass clazz,
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jint j_device) { jint j_device) {
if (EmulationSession::GetInstance().IsRunning()) { if (EmulationSession::GetInstance().IsRunning()) {
EmulationSession::GetInstance().OnGamepadDisconnectEvent(j_device); EmulationSession::GetInstance().OnGamepadDisconnectEvent(j_device);
} }
return static_cast<jboolean>(true); return static_cast<jboolean>(true);
} }
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadButtonEvent([[maybe_unused]] JNIEnv* env, jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadButtonEvent(JNIEnv* env, jclass clazz,
[[maybe_unused]] jclass clazz, jint j_device, jint j_button,
[[maybe_unused]] jint j_device, jint action) {
jint j_button, jint action) {
if (EmulationSession::GetInstance().IsRunning()) { if (EmulationSession::GetInstance().IsRunning()) {
// Ensure gamepad is connected // Ensure gamepad is connected
EmulationSession::GetInstance().OnGamepadConnectEvent(j_device); EmulationSession::GetInstance().OnGamepadConnectEvent(j_device);
@ -639,8 +664,7 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadButtonEvent([[maybe_unus
return static_cast<jboolean>(true); return static_cast<jboolean>(true);
} }
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadJoystickEvent([[maybe_unused]] JNIEnv* env, jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadJoystickEvent(JNIEnv* env, jclass clazz,
[[maybe_unused]] jclass clazz,
jint j_device, jint stick_id, jint j_device, jint stick_id,
jfloat x, jfloat y) { jfloat x, jfloat y) {
if (EmulationSession::GetInstance().IsRunning()) { if (EmulationSession::GetInstance().IsRunning()) {
@ -650,9 +674,8 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadJoystickEvent([[maybe_un
} }
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadMotionEvent( jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadMotionEvent(
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jint j_device, JNIEnv* env, jclass clazz, jint j_device, jlong delta_timestamp, jfloat gyro_x, jfloat gyro_y,
jlong delta_timestamp, jfloat gyro_x, jfloat gyro_y, jfloat gyro_z, jfloat accel_x, jfloat gyro_z, jfloat accel_x, jfloat accel_y, jfloat accel_z) {
jfloat accel_y, jfloat accel_z) {
if (EmulationSession::GetInstance().IsRunning()) { if (EmulationSession::GetInstance().IsRunning()) {
EmulationSession::GetInstance().Window().OnGamepadMotionEvent( EmulationSession::GetInstance().Window().OnGamepadMotionEvent(
j_device, delta_timestamp, gyro_x, gyro_y, gyro_z, accel_x, accel_y, accel_z); j_device, delta_timestamp, gyro_x, gyro_y, gyro_z, accel_x, accel_y, accel_z);
@ -660,8 +683,7 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadMotionEvent(
return static_cast<jboolean>(true); return static_cast<jboolean>(true);
} }
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onReadNfcTag([[maybe_unused]] JNIEnv* env, jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onReadNfcTag(JNIEnv* env, jclass clazz,
[[maybe_unused]] jclass clazz,
jbyteArray j_data) { jbyteArray j_data) {
jboolean isCopy{false}; jboolean isCopy{false};
std::span<u8> data(reinterpret_cast<u8*>(env->GetByteArrayElements(j_data, &isCopy)), std::span<u8> data(reinterpret_cast<u8*>(env->GetByteArrayElements(j_data, &isCopy)),
@ -673,108 +695,92 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onReadNfcTag([[maybe_unused]] JNI
return static_cast<jboolean>(true); return static_cast<jboolean>(true);
} }
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onRemoveNfcTag([[maybe_unused]] JNIEnv* env, jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onRemoveNfcTag(JNIEnv* env, jclass clazz) {
[[maybe_unused]] jclass clazz) {
if (EmulationSession::GetInstance().IsRunning()) { if (EmulationSession::GetInstance().IsRunning()) {
EmulationSession::GetInstance().Window().OnRemoveNfcTag(); EmulationSession::GetInstance().Window().OnRemoveNfcTag();
} }
return static_cast<jboolean>(true); return static_cast<jboolean>(true);
} }
void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchPressed([[maybe_unused]] JNIEnv* env, void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchPressed(JNIEnv* env, jclass clazz, jint id,
[[maybe_unused]] jclass clazz, jint id,
jfloat x, jfloat y) { jfloat x, jfloat y) {
if (EmulationSession::GetInstance().IsRunning()) { if (EmulationSession::GetInstance().IsRunning()) {
EmulationSession::GetInstance().Window().OnTouchPressed(id, x, y); EmulationSession::GetInstance().Window().OnTouchPressed(id, x, y);
} }
} }
void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchMoved([[maybe_unused]] JNIEnv* env, void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchMoved(JNIEnv* env, jclass clazz, jint id,
[[maybe_unused]] jclass clazz, jint id,
jfloat x, jfloat y) { jfloat x, jfloat y) {
if (EmulationSession::GetInstance().IsRunning()) { if (EmulationSession::GetInstance().IsRunning()) {
EmulationSession::GetInstance().Window().OnTouchMoved(id, x, y); EmulationSession::GetInstance().Window().OnTouchMoved(id, x, y);
} }
} }
void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchReleased([[maybe_unused]] JNIEnv* env, void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchReleased(JNIEnv* env, jclass clazz, jint id) {
[[maybe_unused]] jclass clazz, jint id) {
if (EmulationSession::GetInstance().IsRunning()) { if (EmulationSession::GetInstance().IsRunning()) {
EmulationSession::GetInstance().Window().OnTouchReleased(id); EmulationSession::GetInstance().Window().OnTouchReleased(id);
} }
} }
jbyteArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getIcon([[maybe_unused]] JNIEnv* env, jbyteArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getIcon(JNIEnv* env, jclass clazz,
[[maybe_unused]] jclass clazz, jstring j_filename) {
[[maybe_unused]] jstring j_filename) { jauto icon_data = EmulationSession::GetInstance().GetRomIcon(GetJString(env, j_filename));
auto icon_data = EmulationSession::GetInstance().GetRomIcon(GetJString(env, j_filename));
jbyteArray icon = env->NewByteArray(static_cast<jsize>(icon_data.size())); jbyteArray icon = env->NewByteArray(static_cast<jsize>(icon_data.size()));
env->SetByteArrayRegion(icon, 0, env->GetArrayLength(icon), env->SetByteArrayRegion(icon, 0, env->GetArrayLength(icon),
reinterpret_cast<jbyte*>(icon_data.data())); reinterpret_cast<jbyte*>(icon_data.data()));
return icon; return icon;
} }
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getTitle([[maybe_unused]] JNIEnv* env, jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getTitle(JNIEnv* env, jclass clazz,
[[maybe_unused]] jclass clazz, jstring j_filename) {
[[maybe_unused]] jstring j_filename) { jauto title = EmulationSession::GetInstance().GetRomTitle(GetJString(env, j_filename));
auto title = EmulationSession::GetInstance().GetRomTitle(GetJString(env, j_filename));
return env->NewStringUTF(title.c_str()); return env->NewStringUTF(title.c_str());
} }
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getDescription([[maybe_unused]] JNIEnv* env, jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getDescription(JNIEnv* env, jclass clazz,
[[maybe_unused]] jclass clazz,
jstring j_filename) { jstring j_filename) {
return j_filename; return j_filename;
} }
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getGameId([[maybe_unused]] JNIEnv* env, jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getGameId(JNIEnv* env, jclass clazz,
[[maybe_unused]] jclass clazz,
jstring j_filename) { jstring j_filename) {
return j_filename; return j_filename;
} }
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getRegions([[maybe_unused]] JNIEnv* env, jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getRegions(JNIEnv* env, jclass clazz,
[[maybe_unused]] jclass clazz, jstring j_filename) {
[[maybe_unused]] jstring j_filename) {
return env->NewStringUTF(""); return env->NewStringUTF("");
} }
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCompany([[maybe_unused]] JNIEnv* env, jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCompany(JNIEnv* env, jclass clazz,
[[maybe_unused]] jclass clazz, jstring j_filename) {
[[maybe_unused]] jstring j_filename) {
return env->NewStringUTF(""); return env->NewStringUTF("");
} }
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHomebrew([[maybe_unused]] JNIEnv* env, jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHomebrew(JNIEnv* env, jclass clazz,
[[maybe_unused]] jclass clazz, jstring j_filename) {
[[maybe_unused]] jstring j_filename) {
return EmulationSession::GetInstance().GetIsHomebrew(GetJString(env, j_filename)); return EmulationSession::GetInstance().GetIsHomebrew(GetJString(env, j_filename));
} }
void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmulation void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmulation(JNIEnv* env, jclass clazz) {
[[maybe_unused]] (JNIEnv* env, [[maybe_unused]] jclass clazz) {
// Create the default config.ini. // Create the default config.ini.
Config{}; Config{};
// Initialize the emulated system. // Initialize the emulated system.
EmulationSession::GetInstance().System().Initialize(); EmulationSession::GetInstance().System().Initialize();
} }
jint Java_org_yuzu_yuzu_1emu_NativeLibrary_defaultCPUCore([[maybe_unused]] JNIEnv* env, jint Java_org_yuzu_yuzu_1emu_NativeLibrary_defaultCPUCore(JNIEnv* env, jclass clazz) {
[[maybe_unused]] jclass clazz) {
return {}; return {};
} }
void Java_org_yuzu_yuzu_1emu_NativeLibrary_run__Ljava_lang_String_2Ljava_lang_String_2Z( void Java_org_yuzu_yuzu_1emu_NativeLibrary_run__Ljava_lang_String_2Ljava_lang_String_2Z(
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, [[maybe_unused]] jstring j_file, JNIEnv* env, jclass clazz, jstring j_file, jstring j_savestate, jboolean j_delete_savestate) {}
[[maybe_unused]] jstring j_savestate, [[maybe_unused]] jboolean j_delete_savestate) {}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadSettings([[maybe_unused]] JNIEnv* env, void Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadSettings(JNIEnv* env, jclass clazz) {
[[maybe_unused]] jclass clazz) {
Config{}; Config{};
} }
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getUserSetting([[maybe_unused]] JNIEnv* env, jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getUserSetting(JNIEnv* env, jclass clazz,
[[maybe_unused]] jclass clazz,
jstring j_game_id, jstring j_section, jstring j_game_id, jstring j_section,
jstring j_key) { jstring j_key) {
std::string_view game_id = env->GetStringUTFChars(j_game_id, 0); std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);
@ -788,8 +794,7 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getUserSetting([[maybe_unused]] JN
return env->NewStringUTF(""); return env->NewStringUTF("");
} }
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setUserSetting([[maybe_unused]] JNIEnv* env, void Java_org_yuzu_yuzu_1emu_NativeLibrary_setUserSetting(JNIEnv* env, jclass clazz,
[[maybe_unused]] jclass clazz,
jstring j_game_id, jstring j_section, jstring j_game_id, jstring j_section,
jstring j_key, jstring j_value) { jstring j_key, jstring j_value) {
std::string_view game_id = env->GetStringUTFChars(j_game_id, 0); std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);
@ -803,20 +808,18 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_setUserSetting([[maybe_unused]] JNIEn
env->ReleaseStringUTFChars(j_value, value.data()); env->ReleaseStringUTFChars(j_value, value.data());
} }
void Java_org_yuzu_yuzu_1emu_NativeLibrary_initGameIni([[maybe_unused]] JNIEnv* env, void Java_org_yuzu_yuzu_1emu_NativeLibrary_initGameIni(JNIEnv* env, jclass clazz,
[[maybe_unused]] jclass clazz,
jstring j_game_id) { jstring j_game_id) {
std::string_view game_id = env->GetStringUTFChars(j_game_id, 0); std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);
env->ReleaseStringUTFChars(j_game_id, game_id.data()); env->ReleaseStringUTFChars(j_game_id, game_id.data());
} }
jdoubleArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPerfStats([[maybe_unused]] JNIEnv* env, jdoubleArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPerfStats(JNIEnv* env, jclass clazz) {
[[maybe_unused]] jclass clazz) {
jdoubleArray j_stats = env->NewDoubleArray(4); jdoubleArray j_stats = env->NewDoubleArray(4);
if (EmulationSession::GetInstance().IsRunning()) { if (EmulationSession::GetInstance().IsRunning()) {
const auto results = EmulationSession::GetInstance().PerfStats(); jconst results = EmulationSession::GetInstance().PerfStats();
// Converting the structure into an array makes it easier to pass it to the frontend // Converting the structure into an array makes it easier to pass it to the frontend
double stats[4] = {results.system_fps, results.average_game_fps, results.frametime, double stats[4] = {results.system_fps, results.average_game_fps, results.frametime,
@ -828,11 +831,11 @@ jdoubleArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPerfStats([[maybe_unused]]
return j_stats; return j_stats;
} }
void Java_org_yuzu_yuzu_1emu_utils_DirectoryInitialization_setSysDirectory( void Java_org_yuzu_yuzu_1emu_utils_DirectoryInitialization_setSysDirectory(JNIEnv* env,
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jstring j_path) {} jclass clazz,
jstring j_path) {}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_run__Ljava_lang_String_2([[maybe_unused]] JNIEnv* env, void Java_org_yuzu_yuzu_1emu_NativeLibrary_run__Ljava_lang_String_2(JNIEnv* env, jclass clazz,
[[maybe_unused]] jclass clazz,
jstring j_path) { jstring j_path) {
const std::string path = GetJString(env, j_path); const std::string path = GetJString(env, j_path);
@ -843,8 +846,7 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_run__Ljava_lang_String_2([[maybe_unus
} }
} }
void Java_org_yuzu_yuzu_1emu_NativeLibrary_logDeviceInfo([[maybe_unused]] JNIEnv* env, void Java_org_yuzu_yuzu_1emu_NativeLibrary_logDeviceInfo(JNIEnv* env, jclass clazz) {
[[maybe_unused]] jclass clazz) {
LOG_INFO(Frontend, "yuzu Version: {}-{}", Common::g_scm_branch, Common::g_scm_desc); LOG_INFO(Frontend, "yuzu Version: {}-{}", Common::g_scm_branch, Common::g_scm_desc);
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());
} }

View file

@ -1,165 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <jni.h>
// Function calls from the Java side
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_UnPauseEmulation(JNIEnv* env,
jclass clazz);
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_PauseEmulation(JNIEnv* env,
jclass clazz);
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_StopEmulation(JNIEnv* env,
jclass clazz);
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_ResetRomMetadata(JNIEnv* env,
jclass clazz);
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_IsRunning(JNIEnv* env,
jclass clazz);
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly(JNIEnv* env,
jclass clazz);
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_setDeviceType(JNIEnv* env,
jclass clazz,
jstring j_device,
jstring j_type);
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadConnectEvent(
JNIEnv* env, jclass clazz, jstring j_device);
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadDisconnectEvent(
JNIEnv* env, jclass clazz, jstring j_device);
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadEvent(
JNIEnv* env, jclass clazz, jstring j_device, jint j_button, jint action);
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadMoveEvent(
JNIEnv* env, jclass clazz, jstring j_device, jint axis, jfloat x, jfloat y);
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadAxisEvent(
JNIEnv* env, jclass clazz, jstring j_device, jint axis_id, jfloat axis_val);
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onReadNfcTag(JNIEnv* env,
jclass clazz,
jbyteArray j_data);
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onRemoveNfcTag(JNIEnv* env,
jclass clazz);
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchEvent(JNIEnv* env,
jclass clazz,
jfloat x, jfloat y,
jboolean pressed);
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchMoved(JNIEnv* env, jclass clazz,
jfloat x, jfloat y);
JNIEXPORT jbyteArray JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetIcon(JNIEnv* env,
jclass clazz,
jstring j_file);
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetTitle(JNIEnv* env, jclass clazz,
jstring j_filename);
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetDescription(JNIEnv* env,
jclass clazz,
jstring j_filename);
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetGameId(JNIEnv* env, jclass clazz,
jstring j_filename);
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetRegions(JNIEnv* env,
jclass clazz,
jstring j_filename);
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetCompany(JNIEnv* env,
jclass clazz,
jstring j_filename);
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetGitRevision(JNIEnv* env,
jclass clazz);
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_SetAppDirectory(JNIEnv* env,
jclass clazz,
jstring j_directory);
JNIEXPORT void JNICALL
Java_org_yuzu_yuzu_1emu_NativeLibrary_Java_org_yuzu_yuzu_1emu_NativeLibrary_InitializeGpuDriver(
JNIEnv* env, jclass clazz, jstring hook_lib_dir, jstring custom_driver_dir,
jstring custom_driver_name, jstring file_redirect_dir);
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_ReloadKeys(JNIEnv* env,
jclass clazz);
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_utils_DirectoryInitialization_SetSysDirectory(
JNIEnv* env, jclass clazz, jstring path_);
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_SetSysDirectory(JNIEnv* env,
jclass clazz,
jstring path);
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_InitializeEmulation(JNIEnv* env,
jclass clazz);
JNIEXPORT jint JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_DefaultCPUCore(JNIEnv* env,
jclass clazz);
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_SetProfiling(JNIEnv* env, jclass clazz,
jboolean enable);
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_WriteProfileResults(JNIEnv* env,
jclass clazz);
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_NotifyOrientationChange(
JNIEnv* env, jclass clazz, jint layout_option, jint rotation);
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_Run__Ljava_lang_String_2(
JNIEnv* env, jclass clazz, jstring j_path);
JNIEXPORT void JNICALL
Java_org_yuzu_yuzu_1emu_NativeLibrary_Run__Ljava_lang_String_2Ljava_lang_String_2Z(
JNIEnv* env, jclass clazz, jstring j_file, jstring j_savestate, jboolean j_delete_savestate);
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_SurfaceChanged(JNIEnv* env,
jclass clazz,
jobject surf);
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_SurfaceDestroyed(JNIEnv* env,
jclass clazz);
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_InitGameIni(JNIEnv* env, jclass clazz,
jstring j_game_id);
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_ReloadSettings(JNIEnv* env,
jclass clazz);
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_SetUserSetting(
JNIEnv* env, jclass clazz, jstring j_game_id, jstring j_section, jstring j_key,
jstring j_value);
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetUserSetting(
JNIEnv* env, jclass clazz, jstring game_id, jstring section, jstring key);
JNIEXPORT jdoubleArray JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetPerfStats(JNIEnv* env,
jclass clazz);
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_LogDeviceInfo(JNIEnv* env,
jclass clazz);
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_SubmitInlineKeyboardText(
JNIEnv* env, jclass clazz, jstring j_text);
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_SubmitInlineKeyboardInput(
JNIEnv* env, jclass clazz, jint j_key_code);
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="@android:color/white"
android:pathData="M7,9v6h4l5,5V4l-5,5H7z" />
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="@android:color/white"
android:pathData="M3,9v6h4l5,5L12,4L7,9L3,9zM16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM14,3.23v2.06c2.89,0.86 5,3.54 5,6.71s-2.11,5.85 -5,6.71v2.06c4.01,-0.91 7,-4.49 7,-8.77s-2.99,-7.86 -7,-8.77z" />
</vector>

View file

@ -104,12 +104,14 @@
<string name="share_log_missing">No log file found</string> <string name="share_log_missing">No log file found</string>
<string name="install_game_content">Install game content</string> <string name="install_game_content">Install game content</string>
<string name="install_game_content_description">Install game updates or DLC</string> <string name="install_game_content_description">Install game updates or DLC</string>
<string name="install_game_content_failure">Error installing file to NAND</string> <string name="install_game_content_failure">Error installing file(s) to NAND</string>
<string name="install_game_content_failure_description">Game content installation failed. Please ensure content is valid and that the prod.keys file is installed.</string> <string name="install_game_content_failure_description">Please ensure content(s) are valid and that the prod.keys file is installed.</string>
<string name="install_game_content_failure_base">Installation of base games isn\'t permitted in order to avoid possible conflicts. Please select an update or DLC instead.</string> <string name="install_game_content_failure_base">Installation of base games isn\'t permitted in order to avoid possible conflicts.</string>
<string name="install_game_content_failure_file_extension">The selected file type is not supported. Only NSP and XCI content is supported for this action. Please verify the game content is valid.</string> <string name="install_game_content_failure_file_extension">Only NSP and XCI content is supported. Please verify the game content(s) are valid.</string>
<string name="install_game_content_success">Game content installed successfully</string> <string name="install_game_content_failed_count">%1$d installation error(s)</string>
<string name="install_game_content_success_overwrite">Game content was overwritten successfully</string> <string name="install_game_content_success">Game content(s) installed successfully</string>
<string name="install_game_content_success_install">%1$d installed successfully</string>
<string name="install_game_content_success_overwrite">%1$d overwritten successfully</string>
<string name="install_game_content_help_link">https://yuzu-emu.org/help/quickstart/#dumping-installed-updates</string> <string name="install_game_content_help_link">https://yuzu-emu.org/help/quickstart/#dumping-installed-updates</string>
<!-- About screen strings --> <!-- About screen strings -->
@ -270,6 +272,7 @@
<string name="fatal_error">Fatal Error</string> <string name="fatal_error">Fatal Error</string>
<string name="fatal_error_message">A fatal error occurred. Check the log for details.\nContinuing emulation may result in crashes and bugs.</string> <string name="fatal_error_message">A fatal error occurred. Check the log for details.\nContinuing emulation may result in crashes and bugs.</string>
<string name="performance_warning">Turning off this setting will significantly reduce emulation performance! For the best experience, it is recommended that you leave this setting enabled.</string> <string name="performance_warning">Turning off this setting will significantly reduce emulation performance! For the best experience, it is recommended that you leave this setting enabled.</string>
<string name="device_memory_inadequate">Device RAM: %1$s\nRecommended: %2$s</string>
<!-- Region Names --> <!-- Region Names -->
<string name="region_japan">Japan</string> <string name="region_japan">Japan</string>
@ -300,6 +303,15 @@
<string name="language_traditional_chinese">Traditional Chinese (正體中文)</string> <string name="language_traditional_chinese">Traditional Chinese (正體中文)</string>
<string name="language_brazilian_portuguese">Brazilian Portuguese (Português do Brasil)</string> <string name="language_brazilian_portuguese">Brazilian Portuguese (Português do Brasil)</string>
<!-- Memory Sizes -->
<string name="memory_byte">Byte</string>
<string name="memory_kilobyte">KB</string>
<string name="memory_megabyte">MB</string>
<string name="memory_gigabyte">GB</string>
<string name="memory_terabyte">TB</string>
<string name="memory_petabyte">PB</string>
<string name="memory_exabyte">EB</string>
<!-- Renderer APIs --> <!-- Renderer APIs -->
<string name="renderer_vulkan">Vulkan</string> <string name="renderer_vulkan">Vulkan</string>
<string name="renderer_none">None</string> <string name="renderer_none">None</string>
@ -387,6 +399,8 @@
<string name="picture_in_picture_description">Minimize window when placed in the background</string> <string name="picture_in_picture_description">Minimize window when placed in the background</string>
<string name="pause">Pause</string> <string name="pause">Pause</string>
<string name="play">Play</string> <string name="play">Play</string>
<string name="mute">Mute</string>
<string name="unmute">Unmute</string>
<!-- Licenses screen strings --> <!-- Licenses screen strings -->
<string name="licenses">Licenses</string> <string name="licenses">Licenses</string>

View file

@ -15,3 +15,6 @@ android.useAndroidX=true
kotlin.code.style=official kotlin.code.style=official
kotlin.parallel.tasks.in.project=true kotlin.parallel.tasks.in.project=true
android.defaults.buildfeatures.buildconfig=true android.defaults.buildfeatures.buildconfig=true
# Android Gradle plugin 8.0.2
android.suppressUnsupportedCompileSdk=34

View file

@ -7,6 +7,7 @@
#include <mutex> #include <mutex>
#include <span> #include <span>
#include <vector> #include <vector>
#include <boost/container/static_vector.hpp>
#include "audio_buffer.h" #include "audio_buffer.h"
#include "audio_core/device/device_session.h" #include "audio_core/device/device_session.h"
@ -48,7 +49,7 @@ public:
* *
* @param out_buffers - The buffers which were registered. * @param out_buffers - The buffers which were registered.
*/ */
void RegisterBuffers(std::vector<AudioBuffer>& out_buffers) { void RegisterBuffers(boost::container::static_vector<AudioBuffer, N>& out_buffers) {
std::scoped_lock l{lock}; std::scoped_lock l{lock};
const s32 to_register{std::min(std::min(appended_count, BufferAppendLimit), const s32 to_register{std::min(std::min(appended_count, BufferAppendLimit),
BufferAppendLimit - registered_count)}; BufferAppendLimit - registered_count)};
@ -162,7 +163,8 @@ public:
* @param max_buffers - Maximum number of buffers to released. * @param max_buffers - Maximum number of buffers to released.
* @return The number of buffers released. * @return The number of buffers released.
*/ */
u32 GetRegisteredAppendedBuffers(std::vector<AudioBuffer>& buffers_flushed, u32 max_buffers) { u32 GetRegisteredAppendedBuffers(
boost::container::static_vector<AudioBuffer, N>& buffers_flushed, u32 max_buffers) {
std::scoped_lock l{lock}; std::scoped_lock l{lock};
if (registered_count + appended_count == 0) { if (registered_count + appended_count == 0) {
return 0; return 0;
@ -270,7 +272,7 @@ public:
*/ */
bool FlushBuffers(u32& buffers_released) { bool FlushBuffers(u32& buffers_released) {
std::scoped_lock l{lock}; std::scoped_lock l{lock};
std::vector<AudioBuffer> buffers_flushed{}; boost::container::static_vector<AudioBuffer, N> buffers_flushed{};
buffers_released = GetRegisteredAppendedBuffers(buffers_flushed, append_limit); buffers_released = GetRegisteredAppendedBuffers(buffers_flushed, append_limit);

View file

@ -79,7 +79,7 @@ void DeviceSession::ClearBuffers() {
} }
} }
void DeviceSession::AppendBuffers(std::span<const AudioBuffer> buffers) const { void DeviceSession::AppendBuffers(std::span<const AudioBuffer> buffers) {
for (const auto& buffer : buffers) { for (const auto& buffer : buffers) {
Sink::SinkBuffer new_buffer{ Sink::SinkBuffer new_buffer{
.frames = buffer.size / (channel_count * sizeof(s16)), .frames = buffer.size / (channel_count * sizeof(s16)),
@ -88,13 +88,13 @@ void DeviceSession::AppendBuffers(std::span<const AudioBuffer> buffers) const {
.consumed = false, .consumed = false,
}; };
tmp_samples.resize_destructive(buffer.size / sizeof(s16));
if (type == Sink::StreamType::In) { if (type == Sink::StreamType::In) {
std::vector<s16> samples{}; stream->AppendBuffer(new_buffer, tmp_samples);
stream->AppendBuffer(new_buffer, samples);
} else { } else {
std::vector<s16> samples(buffer.size / sizeof(s16)); system.ApplicationMemory().ReadBlockUnsafe(buffer.samples, tmp_samples.data(),
system.ApplicationMemory().ReadBlockUnsafe(buffer.samples, samples.data(), buffer.size); buffer.size);
stream->AppendBuffer(new_buffer, samples); stream->AppendBuffer(new_buffer, tmp_samples);
} }
} }
} }

View file

@ -10,6 +10,7 @@
#include "audio_core/common/common.h" #include "audio_core/common/common.h"
#include "audio_core/sink/sink.h" #include "audio_core/sink/sink.h"
#include "common/scratch_buffer.h"
#include "core/hle/service/audio/errors.h" #include "core/hle/service/audio/errors.h"
namespace Core { namespace Core {
@ -62,7 +63,7 @@ public:
* *
* @param buffers - The buffers to play. * @param buffers - The buffers to play.
*/ */
void AppendBuffers(std::span<const AudioBuffer> buffers) const; void AppendBuffers(std::span<const AudioBuffer> buffers);
/** /**
* (Audio In only) Pop samples from the backend, and write them back to this buffer's address. * (Audio In only) Pop samples from the backend, and write them back to this buffer's address.
@ -146,8 +147,8 @@ private:
std::shared_ptr<Core::Timing::EventType> thread_event; std::shared_ptr<Core::Timing::EventType> thread_event;
/// Is this session initialised? /// Is this session initialised?
bool initialized{}; bool initialized{};
/// Buffer queue /// Temporary sample buffer
std::vector<AudioBuffer> buffer_queue{}; Common::ScratchBuffer<s16> tmp_samples{};
}; };
} // namespace AudioCore } // namespace AudioCore

View file

@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <mutex> #include <mutex>
#include "audio_core/audio_event.h" #include "audio_core/audio_event.h"
#include "audio_core/audio_manager.h" #include "audio_core/audio_manager.h"
#include "audio_core/in/audio_in_system.h" #include "audio_core/in/audio_in_system.h"
@ -89,7 +90,7 @@ Result System::Start() {
session->Start(); session->Start();
state = State::Started; state = State::Started;
std::vector<AudioBuffer> buffers_to_flush{}; boost::container::static_vector<AudioBuffer, BufferCount> buffers_to_flush{};
buffers.RegisterBuffers(buffers_to_flush); buffers.RegisterBuffers(buffers_to_flush);
session->AppendBuffers(buffers_to_flush); session->AppendBuffers(buffers_to_flush);
session->SetRingSize(static_cast<u32>(buffers_to_flush.size())); session->SetRingSize(static_cast<u32>(buffers_to_flush.size()));
@ -134,7 +135,7 @@ bool System::AppendBuffer(const AudioInBuffer& buffer, const u64 tag) {
void System::RegisterBuffers() { void System::RegisterBuffers() {
if (state == State::Started) { if (state == State::Started) {
std::vector<AudioBuffer> registered_buffers{}; boost::container::static_vector<AudioBuffer, BufferCount> registered_buffers{};
buffers.RegisterBuffers(registered_buffers); buffers.RegisterBuffers(registered_buffers);
session->AppendBuffers(registered_buffers); session->AppendBuffers(registered_buffers);
} }

View file

@ -89,7 +89,7 @@ Result System::Start() {
session->Start(); session->Start();
state = State::Started; state = State::Started;
std::vector<AudioBuffer> buffers_to_flush{}; boost::container::static_vector<AudioBuffer, BufferCount> buffers_to_flush{};
buffers.RegisterBuffers(buffers_to_flush); buffers.RegisterBuffers(buffers_to_flush);
session->AppendBuffers(buffers_to_flush); session->AppendBuffers(buffers_to_flush);
session->SetRingSize(static_cast<u32>(buffers_to_flush.size())); session->SetRingSize(static_cast<u32>(buffers_to_flush.size()));
@ -134,7 +134,7 @@ bool System::AppendBuffer(const AudioOutBuffer& buffer, u64 tag) {
void System::RegisterBuffers() { void System::RegisterBuffers() {
if (state == State::Started) { if (state == State::Started) {
std::vector<AudioBuffer> registered_buffers{}; boost::container::static_vector<AudioBuffer, BufferCount> registered_buffers{};
buffers.RegisterBuffers(registered_buffers); buffers.RegisterBuffers(registered_buffers);
session->AppendBuffers(registered_buffers); session->AppendBuffers(registered_buffers);
} }

View file

@ -7,7 +7,6 @@
#include "common/logging/log.h" #include "common/logging/log.h"
#include "core/core.h" #include "core/core.h"
#include "core/core_timing.h" #include "core/core_timing.h"
#include "core/core_timing_util.h"
#include "core/memory.h" #include "core/memory.h"
namespace AudioCore::AudioRenderer::ADSP { namespace AudioCore::AudioRenderer::ADSP {

View file

@ -13,7 +13,6 @@
#include "common/thread.h" #include "common/thread.h"
#include "core/core.h" #include "core/core.h"
#include "core/core_timing.h" #include "core/core_timing.h"
#include "core/core_timing_util.h"
MICROPROFILE_DEFINE(Audio_Renderer, "Audio", "DSP", MP_RGB(60, 19, 97)); MICROPROFILE_DEFINE(Audio_Renderer, "Audio", "DSP", MP_RGB(60, 19, 97));
@ -144,6 +143,7 @@ void AudioRenderer::ThreadFunc(std::stop_token stop_token) {
mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_InitializeOK); mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_InitializeOK);
// 0.12 seconds (2304000 / 19200000)
constexpr u64 max_process_time{2'304'000ULL}; constexpr u64 max_process_time{2'304'000ULL};
while (!stop_token.stop_requested()) { while (!stop_token.stop_requested()) {
@ -184,8 +184,7 @@ void AudioRenderer::ThreadFunc(std::stop_token stop_token) {
u64 max_time{max_process_time}; u64 max_time{max_process_time};
if (index == 1 && command_buffer.applet_resource_user_id == if (index == 1 && command_buffer.applet_resource_user_id ==
mailbox->GetCommandBuffer(0).applet_resource_user_id) { mailbox->GetCommandBuffer(0).applet_resource_user_id) {
max_time = max_process_time - max_time = max_process_time - render_times_taken[0];
Core::Timing::CyclesToNs(render_times_taken[0]).count();
if (render_times_taken[0] > max_process_time) { if (render_times_taken[0] > max_process_time) {
max_time = 0; max_time = 0;
} }

View file

@ -9,7 +9,6 @@
#include "common/settings.h" #include "common/settings.h"
#include "core/core.h" #include "core/core.h"
#include "core/core_timing.h" #include "core/core_timing.h"
#include "core/core_timing_util.h"
#include "core/memory.h" #include "core/memory.h"
namespace AudioCore::AudioRenderer::ADSP { namespace AudioCore::AudioRenderer::ADSP {

View file

@ -8,6 +8,7 @@
#include "audio_core/renderer/command/resample/resample.h" #include "audio_core/renderer/command/resample/resample.h"
#include "common/fixed_point.h" #include "common/fixed_point.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/scratch_buffer.h"
#include "core/memory.h" #include "core/memory.h"
namespace AudioCore::AudioRenderer { namespace AudioCore::AudioRenderer {
@ -27,6 +28,7 @@ constexpr std::array<u8, 3> PitchBySrcQuality = {4, 8, 4};
template <typename T> template <typename T>
static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer, static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
const DecodeArg& req) { const DecodeArg& req) {
std::array<T, TempBufferSize> tmp_samples{};
constexpr s32 min{std::numeric_limits<s16>::min()}; constexpr s32 min{std::numeric_limits<s16>::min()};
constexpr s32 max{std::numeric_limits<s16>::max()}; constexpr s32 max{std::numeric_limits<s16>::max()};
@ -49,18 +51,17 @@ static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
const u64 size{channel_count * samples_to_decode}; const u64 size{channel_count * samples_to_decode};
const u64 size_bytes{size * sizeof(T)}; const u64 size_bytes{size * sizeof(T)};
std::vector<T> samples(size); memory.ReadBlockUnsafe(source, tmp_samples.data(), size_bytes);
memory.ReadBlockUnsafe(source, samples.data(), size_bytes);
if constexpr (std::is_floating_point_v<T>) { if constexpr (std::is_floating_point_v<T>) {
for (u32 i = 0; i < samples_to_decode; i++) { for (u32 i = 0; i < samples_to_decode; i++) {
auto sample{static_cast<s32>(samples[i * channel_count + req.target_channel] * auto sample{static_cast<s32>(tmp_samples[i * channel_count + req.target_channel] *
std::numeric_limits<s16>::max())}; std::numeric_limits<s16>::max())};
out_buffer[i] = static_cast<s16>(std::clamp(sample, min, max)); out_buffer[i] = static_cast<s16>(std::clamp(sample, min, max));
} }
} else { } else {
for (u32 i = 0; i < samples_to_decode; i++) { for (u32 i = 0; i < samples_to_decode; i++) {
out_buffer[i] = samples[i * channel_count + req.target_channel]; out_buffer[i] = tmp_samples[i * channel_count + req.target_channel];
} }
} }
} break; } break;
@ -73,17 +74,16 @@ static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
} }
const VAddr source{req.buffer + ((req.start_offset + req.offset) * sizeof(T))}; const VAddr source{req.buffer + ((req.start_offset + req.offset) * sizeof(T))};
std::vector<T> samples(samples_to_decode); memory.ReadBlockUnsafe(source, tmp_samples.data(), samples_to_decode * sizeof(T));
memory.ReadBlockUnsafe(source, samples.data(), samples_to_decode * sizeof(T));
if constexpr (std::is_floating_point_v<T>) { if constexpr (std::is_floating_point_v<T>) {
for (u32 i = 0; i < samples_to_decode; i++) { for (u32 i = 0; i < samples_to_decode; i++) {
auto sample{static_cast<s32>(samples[i * channel_count + req.target_channel] * auto sample{static_cast<s32>(tmp_samples[i * channel_count + req.target_channel] *
std::numeric_limits<s16>::max())}; std::numeric_limits<s16>::max())};
out_buffer[i] = static_cast<s16>(std::clamp(sample, min, max)); out_buffer[i] = static_cast<s16>(std::clamp(sample, min, max));
} }
} else { } else {
std::memcpy(out_buffer.data(), samples.data(), samples_to_decode * sizeof(s16)); std::memcpy(out_buffer.data(), tmp_samples.data(), samples_to_decode * sizeof(s16));
} }
break; break;
} }
@ -101,6 +101,7 @@ static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
*/ */
static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer, static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
const DecodeArg& req) { const DecodeArg& req) {
std::array<u8, TempBufferSize> wavebuffer{};
constexpr u32 SamplesPerFrame{14}; constexpr u32 SamplesPerFrame{14};
constexpr u32 NibblesPerFrame{16}; constexpr u32 NibblesPerFrame{16};
@ -138,9 +139,7 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
} }
const auto size{std::max((samples_to_process / 8U) * SamplesPerFrame, 8U)}; const auto size{std::max((samples_to_process / 8U) * SamplesPerFrame, 8U)};
std::vector<u8> wavebuffer(size); memory.ReadBlockUnsafe(req.buffer + position_in_frame / 2, wavebuffer.data(), size);
memory.ReadBlockUnsafe(req.buffer + position_in_frame / 2, wavebuffer.data(),
wavebuffer.size());
auto context{req.adpcm_context}; auto context{req.adpcm_context};
auto header{context->header}; auto header{context->header};
@ -258,7 +257,7 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
u32 offset{voice_state.offset}; u32 offset{voice_state.offset};
auto output_buffer{args.output}; auto output_buffer{args.output};
std::vector<s16> temp_buffer(TempBufferSize, 0); std::array<s16, TempBufferSize> temp_buffer{};
while (remaining_sample_count > 0) { while (remaining_sample_count > 0) {
const auto samples_to_write{std::min(remaining_sample_count, max_remaining_sample_count)}; const auto samples_to_write{std::min(remaining_sample_count, max_remaining_sample_count)};

View file

@ -44,8 +44,8 @@ static void InitializeCompressorEffect(const CompressorInfo::ParameterVersion2&
static void ApplyCompressorEffect(const CompressorInfo::ParameterVersion2& params, static void ApplyCompressorEffect(const CompressorInfo::ParameterVersion2& params,
CompressorInfo::State& state, bool enabled, CompressorInfo::State& state, bool enabled,
std::vector<std::span<const s32>> input_buffers, std::span<std::span<const s32>> input_buffers,
std::vector<std::span<s32>> output_buffers, u32 sample_count) { std::span<std::span<s32>> output_buffers, u32 sample_count) {
if (enabled) { if (enabled) {
auto state_00{state.unk_00}; auto state_00{state.unk_00};
auto state_04{state.unk_04}; auto state_04{state.unk_04};
@ -124,8 +124,8 @@ void CompressorCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor&
} }
void CompressorCommand::Process(const ADSP::CommandListProcessor& processor) { void CompressorCommand::Process(const ADSP::CommandListProcessor& processor) {
std::vector<std::span<const s32>> input_buffers(parameter.channel_count); std::array<std::span<const s32>, MaxChannels> input_buffers{};
std::vector<std::span<s32>> output_buffers(parameter.channel_count); std::array<std::span<s32>, MaxChannels> output_buffers{};
for (s16 i = 0; i < parameter.channel_count; i++) { for (s16 i = 0; i < parameter.channel_count; i++) {
input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count, input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,

View file

@ -51,7 +51,7 @@ static void InitializeDelayEffect(const DelayInfo::ParameterVersion1& params,
state.delay_lines[channel].sample_count_max = sample_count_max.to_int_floor(); state.delay_lines[channel].sample_count_max = sample_count_max.to_int_floor();
state.delay_lines[channel].sample_count = sample_count.to_int_floor(); state.delay_lines[channel].sample_count = sample_count.to_int_floor();
state.delay_lines[channel].buffer.resize(state.delay_lines[channel].sample_count, 0); state.delay_lines[channel].buffer.resize(state.delay_lines[channel].sample_count, 0);
if (state.delay_lines[channel].buffer.size() == 0) { if (state.delay_lines[channel].sample_count == 0) {
state.delay_lines[channel].buffer.push_back(0); state.delay_lines[channel].buffer.push_back(0);
} }
state.delay_lines[channel].buffer_pos = 0; state.delay_lines[channel].buffer_pos = 0;
@ -74,8 +74,8 @@ static void InitializeDelayEffect(const DelayInfo::ParameterVersion1& params,
*/ */
template <size_t NumChannels> template <size_t NumChannels>
static void ApplyDelay(const DelayInfo::ParameterVersion1& params, DelayInfo::State& state, static void ApplyDelay(const DelayInfo::ParameterVersion1& params, DelayInfo::State& state,
std::vector<std::span<const s32>>& inputs, std::span<std::span<const s32>> inputs, std::span<std::span<s32>> outputs,
std::vector<std::span<s32>>& outputs, const u32 sample_count) { const u32 sample_count) {
for (u32 sample_index = 0; sample_index < sample_count; sample_index++) { for (u32 sample_index = 0; sample_index < sample_count; sample_index++) {
std::array<Common::FixedPoint<50, 14>, NumChannels> input_samples{}; std::array<Common::FixedPoint<50, 14>, NumChannels> input_samples{};
for (u32 channel = 0; channel < NumChannels; channel++) { for (u32 channel = 0; channel < NumChannels; channel++) {
@ -153,8 +153,8 @@ static void ApplyDelay(const DelayInfo::ParameterVersion1& params, DelayInfo::St
* @param sample_count - Number of samples to process. * @param sample_count - Number of samples to process.
*/ */
static void ApplyDelayEffect(const DelayInfo::ParameterVersion1& params, DelayInfo::State& state, static void ApplyDelayEffect(const DelayInfo::ParameterVersion1& params, DelayInfo::State& state,
const bool enabled, std::vector<std::span<const s32>>& inputs, const bool enabled, std::span<std::span<const s32>> inputs,
std::vector<std::span<s32>>& outputs, const u32 sample_count) { std::span<std::span<s32>> outputs, const u32 sample_count) {
if (!IsChannelCountValid(params.channel_count)) { if (!IsChannelCountValid(params.channel_count)) {
LOG_ERROR(Service_Audio, "Invalid delay channels {}", params.channel_count); LOG_ERROR(Service_Audio, "Invalid delay channels {}", params.channel_count);
@ -208,8 +208,8 @@ void DelayCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& proce
} }
void DelayCommand::Process(const ADSP::CommandListProcessor& processor) { void DelayCommand::Process(const ADSP::CommandListProcessor& processor) {
std::vector<std::span<const s32>> input_buffers(parameter.channel_count); std::array<std::span<const s32>, MaxChannels> input_buffers{};
std::vector<std::span<s32>> output_buffers(parameter.channel_count); std::array<std::span<s32>, MaxChannels> output_buffers{};
for (s16 i = 0; i < parameter.channel_count; i++) { for (s16 i = 0; i < parameter.channel_count; i++) {
input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count, input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,

View file

@ -408,8 +408,8 @@ void I3dl2ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor&
} }
void I3dl2ReverbCommand::Process(const ADSP::CommandListProcessor& processor) { void I3dl2ReverbCommand::Process(const ADSP::CommandListProcessor& processor) {
std::vector<std::span<const s32>> input_buffers(parameter.channel_count); std::array<std::span<const s32>, MaxChannels> input_buffers{};
std::vector<std::span<s32>> output_buffers(parameter.channel_count); std::array<std::span<s32>, MaxChannels> output_buffers{};
for (u32 i = 0; i < parameter.channel_count; i++) { for (u32 i = 0; i < parameter.channel_count; i++) {
input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count, input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,

View file

@ -47,8 +47,8 @@ static void InitializeLightLimiterEffect(const LightLimiterInfo::ParameterVersio
*/ */
static void ApplyLightLimiterEffect(const LightLimiterInfo::ParameterVersion2& params, static void ApplyLightLimiterEffect(const LightLimiterInfo::ParameterVersion2& params,
LightLimiterInfo::State& state, const bool enabled, LightLimiterInfo::State& state, const bool enabled,
std::vector<std::span<const s32>>& inputs, std::span<std::span<const s32>> inputs,
std::vector<std::span<s32>>& outputs, const u32 sample_count, std::span<std::span<s32>> outputs, const u32 sample_count,
LightLimiterInfo::StatisticsInternal* statistics) { LightLimiterInfo::StatisticsInternal* statistics) {
constexpr s64 min{std::numeric_limits<s32>::min()}; constexpr s64 min{std::numeric_limits<s32>::min()};
constexpr s64 max{std::numeric_limits<s32>::max()}; constexpr s64 max{std::numeric_limits<s32>::max()};
@ -147,8 +147,8 @@ void LightLimiterVersion1Command::Dump([[maybe_unused]] const ADSP::CommandListP
} }
void LightLimiterVersion1Command::Process(const ADSP::CommandListProcessor& processor) { void LightLimiterVersion1Command::Process(const ADSP::CommandListProcessor& processor) {
std::vector<std::span<const s32>> input_buffers(parameter.channel_count); std::array<std::span<const s32>, MaxChannels> input_buffers{};
std::vector<std::span<s32>> output_buffers(parameter.channel_count); std::array<std::span<s32>, MaxChannels> output_buffers{};
for (u32 i = 0; i < parameter.channel_count; i++) { for (u32 i = 0; i < parameter.channel_count; i++) {
input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count, input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
@ -190,8 +190,8 @@ void LightLimiterVersion2Command::Dump([[maybe_unused]] const ADSP::CommandListP
} }
void LightLimiterVersion2Command::Process(const ADSP::CommandListProcessor& processor) { void LightLimiterVersion2Command::Process(const ADSP::CommandListProcessor& processor) {
std::vector<std::span<const s32>> input_buffers(parameter.channel_count); std::array<std::span<const s32>, MaxChannels> input_buffers{};
std::vector<std::span<s32>> output_buffers(parameter.channel_count); std::array<std::span<s32>, MaxChannels> output_buffers{};
for (u32 i = 0; i < parameter.channel_count; i++) { for (u32 i = 0; i < parameter.channel_count; i++) {
input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count, input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,

View file

@ -250,8 +250,8 @@ static Common::FixedPoint<50, 14> Axfx2AllPassTick(ReverbInfo::ReverbDelayLine&
*/ */
template <size_t NumChannels> template <size_t NumChannels>
static void ApplyReverbEffect(const ReverbInfo::ParameterVersion2& params, ReverbInfo::State& state, static void ApplyReverbEffect(const ReverbInfo::ParameterVersion2& params, ReverbInfo::State& state,
std::vector<std::span<const s32>>& inputs, std::span<std::span<const s32>> inputs,
std::vector<std::span<s32>>& outputs, const u32 sample_count) { std::span<std::span<s32>> outputs, const u32 sample_count) {
static constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes1Ch{ static constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes1Ch{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
}; };
@ -369,8 +369,8 @@ static void ApplyReverbEffect(const ReverbInfo::ParameterVersion2& params, Rever
* @param sample_count - Number of samples to process. * @param sample_count - Number of samples to process.
*/ */
static void ApplyReverbEffect(const ReverbInfo::ParameterVersion2& params, ReverbInfo::State& state, static void ApplyReverbEffect(const ReverbInfo::ParameterVersion2& params, ReverbInfo::State& state,
const bool enabled, std::vector<std::span<const s32>>& inputs, const bool enabled, std::span<std::span<const s32>> inputs,
std::vector<std::span<s32>>& outputs, const u32 sample_count) { std::span<std::span<s32>> outputs, const u32 sample_count) {
if (enabled) { if (enabled) {
switch (params.channel_count) { switch (params.channel_count) {
case 0: case 0:
@ -412,8 +412,8 @@ void ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& proc
} }
void ReverbCommand::Process(const ADSP::CommandListProcessor& processor) { void ReverbCommand::Process(const ADSP::CommandListProcessor& processor) {
std::vector<std::span<const s32>> input_buffers(parameter.channel_count); std::array<std::span<const s32>, MaxChannels> input_buffers{};
std::vector<std::span<s32>> output_buffers(parameter.channel_count); std::array<std::span<s32>, MaxChannels> output_buffers{};
for (u32 i = 0; i < parameter.channel_count; i++) { for (u32 i = 0; i < parameter.channel_count; i++) {
input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count, input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,

View file

@ -5,7 +5,6 @@
#include "audio_core/renderer/command/performance/performance.h" #include "audio_core/renderer/command/performance/performance.h"
#include "core/core.h" #include "core/core.h"
#include "core/core_timing.h" #include "core/core_timing.h"
#include "core/core_timing_util.h"
namespace AudioCore::AudioRenderer { namespace AudioCore::AudioRenderer {
@ -18,20 +17,18 @@ void PerformanceCommand::Process(const ADSP::CommandListProcessor& processor) {
auto base{entry_address.translated_address}; auto base{entry_address.translated_address};
if (state == PerformanceState::Start) { if (state == PerformanceState::Start) {
auto start_time_ptr{reinterpret_cast<u32*>(base + entry_address.entry_start_time_offset)}; auto start_time_ptr{reinterpret_cast<u32*>(base + entry_address.entry_start_time_offset)};
*start_time_ptr = static_cast<u32>( *start_time_ptr =
Core::Timing::CyclesToUs(processor.system->CoreTiming().GetClockTicks() - static_cast<u32>(processor.system->CoreTiming().GetClockTicks() - processor.start_time -
processor.start_time - processor.current_processing_time) processor.current_processing_time);
.count());
} else if (state == PerformanceState::Stop) { } else if (state == PerformanceState::Stop) {
auto processed_time_ptr{ auto processed_time_ptr{
reinterpret_cast<u32*>(base + entry_address.entry_processed_time_offset)}; reinterpret_cast<u32*>(base + entry_address.entry_processed_time_offset)};
auto entry_count_ptr{ auto entry_count_ptr{
reinterpret_cast<u32*>(base + entry_address.header_entry_count_offset)}; reinterpret_cast<u32*>(base + entry_address.header_entry_count_offset)};
*processed_time_ptr = static_cast<u32>( *processed_time_ptr =
Core::Timing::CyclesToUs(processor.system->CoreTiming().GetClockTicks() - static_cast<u32>(processor.system->CoreTiming().GetClockTicks() - processor.start_time -
processor.start_time - processor.current_processing_time) processor.current_processing_time);
.count());
(*entry_count_ptr)++; (*entry_count_ptr)++;
} }
} }

View file

@ -24,7 +24,7 @@ void CircularBufferSinkCommand::Process(const ADSP::CommandListProcessor& proces
constexpr s32 min{std::numeric_limits<s16>::min()}; constexpr s32 min{std::numeric_limits<s16>::min()};
constexpr s32 max{std::numeric_limits<s16>::max()}; constexpr s32 max{std::numeric_limits<s16>::max()};
std::vector<s16> output(processor.sample_count); std::array<s16, TargetSampleCount * MaxChannels> output{};
for (u32 channel = 0; channel < input_count; channel++) { for (u32 channel = 0; channel < input_count; channel++) {
auto input{processor.mix_buffers.subspan(inputs[channel] * processor.sample_count, auto input{processor.mix_buffers.subspan(inputs[channel] * processor.sample_count,
processor.sample_count)}; processor.sample_count)};
@ -33,7 +33,7 @@ void CircularBufferSinkCommand::Process(const ADSP::CommandListProcessor& proces
} }
processor.memory->WriteBlockUnsafe(address + pos, output.data(), processor.memory->WriteBlockUnsafe(address + pos, output.data(),
output.size() * sizeof(s16)); processor.sample_count * sizeof(s16));
pos += static_cast<u32>(processor.sample_count * sizeof(s16)); pos += static_cast<u32>(processor.sample_count * sizeof(s16));
if (pos >= size) { if (pos >= size) {
pos = 0; pos = 0;

View file

@ -33,8 +33,7 @@ void DeviceSinkCommand::Process(const ADSP::CommandListProcessor& processor) {
.consumed{false}, .consumed{false},
}; };
std::vector<s16> samples(out_buffer.frames * input_count); std::array<s16, TargetSampleCount * MaxChannels> samples{};
for (u32 channel = 0; channel < input_count; channel++) { for (u32 channel = 0; channel < input_count; channel++) {
const auto offset{inputs[channel] * out_buffer.frames}; const auto offset{inputs[channel] * out_buffer.frames};
@ -45,7 +44,7 @@ void DeviceSinkCommand::Process(const ADSP::CommandListProcessor& processor) {
} }
out_buffer.tag = reinterpret_cast<u64>(samples.data()); out_buffer.tag = reinterpret_cast<u64>(samples.data());
stream->AppendBuffer(out_buffer, samples); stream->AppendBuffer(out_buffer, {samples.data(), out_buffer.frames * input_count});
if (stream->IsPaused()) { if (stream->IsPaused()) {
stream->Start(); stream->Start();

View file

@ -125,10 +125,10 @@ bool MixContext::TSortInfo(const SplitterContext& splitter_context) {
return false; return false;
} }
std::vector<s32> sorted_results{node_states.GetSortedResuls()}; auto sorted_results{node_states.GetSortedResuls()};
const auto result_size{std::min(count, static_cast<s32>(sorted_results.size()))}; const auto result_size{std::min(count, static_cast<s32>(sorted_results.second))};
for (s32 i = 0; i < result_size; i++) { for (s32 i = 0; i < result_size; i++) {
sorted_mix_infos[i] = &mix_infos[sorted_results[i]]; sorted_mix_infos[i] = &mix_infos[sorted_results.first[i]];
} }
CalcMixBufferOffset(); CalcMixBufferOffset();

View file

@ -134,8 +134,8 @@ u32 NodeStates::GetNodeCount() const {
return node_count; return node_count;
} }
std::vector<s32> NodeStates::GetSortedResuls() const { std::pair<std::span<u32>::reverse_iterator, size_t> NodeStates::GetSortedResuls() const {
return {results.rbegin(), results.rbegin() + result_pos}; return {results.rbegin(), result_pos};
} }
} // namespace AudioCore::AudioRenderer } // namespace AudioCore::AudioRenderer

View file

@ -175,7 +175,7 @@ public:
* *
* @return Vector of nodes in reverse order. * @return Vector of nodes in reverse order.
*/ */
std::vector<s32> GetSortedResuls() const; std::pair<std::span<u32>::reverse_iterator, size_t> GetSortedResuls() const;
private: private:
/// Number of nodes in the graph /// Number of nodes in the graph

View file

@ -444,6 +444,7 @@ Result System::Update(std::span<const u8> input, std::span<u8> performance, std:
std::scoped_lock l{lock}; std::scoped_lock l{lock};
const auto start_time{core.CoreTiming().GetClockTicks()}; const auto start_time{core.CoreTiming().GetClockTicks()};
std::memset(output.data(), 0, output.size());
InfoUpdater info_updater(input, output, process_handle, behavior); InfoUpdater info_updater(input, output, process_handle, behavior);

View file

@ -20,7 +20,7 @@ public:
explicit NullSinkStreamImpl(Core::System& system_, StreamType type_) explicit NullSinkStreamImpl(Core::System& system_, StreamType type_)
: SinkStream{system_, type_} {} : SinkStream{system_, type_} {}
~NullSinkStreamImpl() override {} ~NullSinkStreamImpl() override {}
void AppendBuffer(SinkBuffer&, std::vector<s16>&) override {} void AppendBuffer(SinkBuffer&, std::span<s16>) override {}
std::vector<s16> ReleaseBuffer(u64) override { std::vector<s16> ReleaseBuffer(u64) override {
return {}; return {};
} }

View file

@ -15,11 +15,10 @@
#include "common/settings.h" #include "common/settings.h"
#include "core/core.h" #include "core/core.h"
#include "core/core_timing.h" #include "core/core_timing.h"
#include "core/core_timing_util.h"
namespace AudioCore::Sink { namespace AudioCore::Sink {
void SinkStream::AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples) { void SinkStream::AppendBuffer(SinkBuffer& buffer, std::span<s16> samples) {
if (type == StreamType::In) { if (type == StreamType::In) {
queue.enqueue(buffer); queue.enqueue(buffer);
queued_buffers++; queued_buffers++;
@ -67,15 +66,16 @@ void SinkStream::AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples) {
static_cast<s16>(std::clamp(right_sample, min, max)); static_cast<s16>(std::clamp(right_sample, min, max));
} }
samples.resize(samples.size() / system_channels * device_channels); samples = samples.subspan(0, samples.size() / system_channels * device_channels);
} else if (system_channels == 2 && device_channels == 6) { } else if (system_channels == 2 && device_channels == 6) {
// We need moar samples! Not all games will provide 6 channel audio. // We need moar samples! Not all games will provide 6 channel audio.
// TODO: Implement some upmixing here. Currently just passthrough, with other // TODO: Implement some upmixing here. Currently just passthrough, with other
// channels left as silence. // channels left as silence.
std::vector<s16> new_samples(samples.size() / system_channels * device_channels, 0); auto new_size = samples.size() / system_channels * device_channels;
tmp_samples.resize_destructive(new_size);
for (u32 read_index = 0, write_index = 0; read_index < samples.size(); for (u32 read_index = 0, write_index = 0; read_index < new_size;
read_index += system_channels, write_index += device_channels) { read_index += system_channels, write_index += device_channels) {
const auto left_sample{static_cast<s16>(std::clamp( const auto left_sample{static_cast<s16>(std::clamp(
static_cast<s32>( static_cast<s32>(
@ -83,7 +83,7 @@ void SinkStream::AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples) {
volume), volume),
min, max))}; min, max))};
new_samples[write_index + static_cast<u32>(Channels::FrontLeft)] = left_sample; tmp_samples[write_index + static_cast<u32>(Channels::FrontLeft)] = left_sample;
const auto right_sample{static_cast<s16>(std::clamp( const auto right_sample{static_cast<s16>(std::clamp(
static_cast<s32>( static_cast<s32>(
@ -91,9 +91,9 @@ void SinkStream::AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples) {
volume), volume),
min, max))}; min, max))};
new_samples[write_index + static_cast<u32>(Channels::FrontRight)] = right_sample; tmp_samples[write_index + static_cast<u32>(Channels::FrontRight)] = right_sample;
} }
samples = std::move(new_samples); samples = std::span<s16>(tmp_samples);
} else if (volume != 1.0f) { } else if (volume != 1.0f) {
for (u32 i = 0; i < samples.size(); i++) { for (u32 i = 0; i < samples.size(); i++) {

View file

@ -16,6 +16,7 @@
#include "common/polyfill_thread.h" #include "common/polyfill_thread.h"
#include "common/reader_writer_queue.h" #include "common/reader_writer_queue.h"
#include "common/ring_buffer.h" #include "common/ring_buffer.h"
#include "common/scratch_buffer.h"
#include "common/thread.h" #include "common/thread.h"
namespace Core { namespace Core {
@ -170,7 +171,7 @@ public:
* @param buffer - Audio buffer information to be queued. * @param buffer - Audio buffer information to be queued.
* @param samples - The s16 samples to be queue for playback. * @param samples - The s16 samples to be queue for playback.
*/ */
virtual void AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples); virtual void AppendBuffer(SinkBuffer& buffer, std::span<s16> samples);
/** /**
* Release a buffer. Audio In only, will fill a buffer with recorded samples. * Release a buffer. Audio In only, will fill a buffer with recorded samples.
@ -255,6 +256,8 @@ private:
/// Signalled when ring buffer entries are consumed /// Signalled when ring buffer entries are consumed
std::condition_variable_any release_cv; std::condition_variable_any release_cv;
std::mutex release_mutex; std::mutex release_mutex;
/// Temporary buffer for appending samples when upmixing
Common::ScratchBuffer<s16> tmp_samples{};
}; };
using SinkStreamPtr = std::unique_ptr<SinkStream>; using SinkStreamPtr = std::unique_ptr<SinkStream>;

View file

@ -172,6 +172,8 @@ if(ARCHITECTURE_x86_64)
x64/cpu_wait.h x64/cpu_wait.h
x64/native_clock.cpp x64/native_clock.cpp
x64/native_clock.h x64/native_clock.h
x64/rdtsc.cpp
x64/rdtsc.h
x64/xbyak_abi.h x64/xbyak_abi.h
x64/xbyak_util.h x64/xbyak_util.h
) )

View file

@ -436,7 +436,7 @@ void IterateDirEntries(const std::filesystem::path& path, const DirEntryCallable
if (True(filter & DirEntryFilter::File) && if (True(filter & DirEntryFilter::File) &&
entry.status().type() == fs::file_type::regular) { entry.status().type() == fs::file_type::regular) {
if (!callback(entry.path())) { if (!callback(entry)) {
callback_error = true; callback_error = true;
break; break;
} }
@ -444,7 +444,7 @@ void IterateDirEntries(const std::filesystem::path& path, const DirEntryCallable
if (True(filter & DirEntryFilter::Directory) && if (True(filter & DirEntryFilter::Directory) &&
entry.status().type() == fs::file_type::directory) { entry.status().type() == fs::file_type::directory) {
if (!callback(entry.path())) { if (!callback(entry)) {
callback_error = true; callback_error = true;
break; break;
} }
@ -493,7 +493,7 @@ void IterateDirEntriesRecursively(const std::filesystem::path& path,
if (True(filter & DirEntryFilter::File) && if (True(filter & DirEntryFilter::File) &&
entry.status().type() == fs::file_type::regular) { entry.status().type() == fs::file_type::regular) {
if (!callback(entry.path())) { if (!callback(entry)) {
callback_error = true; callback_error = true;
break; break;
} }
@ -501,7 +501,7 @@ void IterateDirEntriesRecursively(const std::filesystem::path& path,
if (True(filter & DirEntryFilter::Directory) && if (True(filter & DirEntryFilter::Directory) &&
entry.status().type() == fs::file_type::directory) { entry.status().type() == fs::file_type::directory) {
if (!callback(entry.path())) { if (!callback(entry)) {
callback_error = true; callback_error = true;
break; break;
} }
@ -605,6 +605,12 @@ fs::file_type GetEntryType(const fs::path& path) {
} }
u64 GetSize(const fs::path& path) { u64 GetSize(const fs::path& path) {
#ifdef ANDROID
if (Android::IsContentUri(path)) {
return Android::GetSize(path);
}
#endif
std::error_code ec; std::error_code ec;
const auto file_size = fs::file_size(path, ec); const auto file_size = fs::file_size(path, ec);

View file

@ -66,6 +66,6 @@ DECLARE_ENUM_FLAG_OPERATORS(DirEntryFilter);
* @returns A boolean value. * @returns A boolean value.
* Return true to indicate whether the callback is successful, false otherwise. * Return true to indicate whether the callback is successful, false otherwise.
*/ */
using DirEntryCallable = std::function<bool(const std::filesystem::path& path)>; using DirEntryCallable = std::function<bool(const std::filesystem::directory_entry& entry)>;
} // namespace Common::FS } // namespace Common::FS

View file

@ -75,8 +75,10 @@ enum class DriverResult {
ErrorWritingData, ErrorWritingData,
NoDeviceDetected, NoDeviceDetected,
InvalidHandle, InvalidHandle,
InvalidParameters,
NotSupported, NotSupported,
Disabled, Disabled,
Delayed,
Unknown, Unknown,
}; };
@ -86,7 +88,7 @@ enum class NfcState {
NewAmiibo, NewAmiibo,
WaitingForAmiibo, WaitingForAmiibo,
AmiiboRemoved, AmiiboRemoved,
NotAnAmiibo, InvalidTagType,
NotSupported, NotSupported,
WrongDeviceState, WrongDeviceState,
WriteFailed, WriteFailed,
@ -218,8 +220,22 @@ struct CameraStatus {
}; };
struct NfcStatus { struct NfcStatus {
NfcState state{}; NfcState state{NfcState::Unknown};
std::vector<u8> data{}; u8 uuid_length;
u8 protocol;
u8 tag_type;
std::array<u8, 10> uuid;
};
struct MifareData {
u8 command;
u8 sector;
std::array<u8, 0x6> key;
std::array<u8, 0x10> data;
};
struct MifareRequest {
std::array<MifareData, 0x10> data;
}; };
// List of buttons to be passed to Qt that can be translated // List of buttons to be passed to Qt that can be translated
@ -294,7 +310,7 @@ struct CallbackStatus {
BatteryStatus battery_status{}; BatteryStatus battery_status{};
VibrationStatus vibration_status{}; VibrationStatus vibration_status{};
CameraFormat camera_status{CameraFormat::None}; CameraFormat camera_status{CameraFormat::None};
NfcState nfc_status{NfcState::Unknown}; NfcStatus nfc_status{};
std::vector<u8> raw_data{}; std::vector<u8> raw_data{};
}; };
@ -356,9 +372,30 @@ public:
return NfcState::NotSupported; return NfcState::NotSupported;
} }
virtual NfcState StartNfcPolling() {
return NfcState::NotSupported;
}
virtual NfcState StopNfcPolling() {
return NfcState::NotSupported;
}
virtual NfcState ReadAmiiboData([[maybe_unused]] std::vector<u8>& out_data) {
return NfcState::NotSupported;
}
virtual NfcState WriteNfcData([[maybe_unused]] const std::vector<u8>& data) { virtual NfcState WriteNfcData([[maybe_unused]] const std::vector<u8>& data) {
return NfcState::NotSupported; return NfcState::NotSupported;
} }
virtual NfcState ReadMifareData([[maybe_unused]] const MifareRequest& request,
[[maybe_unused]] MifareRequest& out_data) {
return NfcState::NotSupported;
}
virtual NfcState WriteMifareData([[maybe_unused]] const MifareRequest& request) {
return NfcState::NotSupported;
}
}; };
/// An abstract class template for a factory that can create input devices. /// An abstract class template for a factory that can create input devices.

View file

@ -9,6 +9,7 @@
#include <cstddef> #include <cstddef>
#include <cstring> #include <cstring>
#include <new> #include <new>
#include <span>
#include <type_traits> #include <type_traits>
#include <vector> #include <vector>
@ -53,7 +54,7 @@ public:
return push_count; return push_count;
} }
std::size_t Push(const std::vector<T>& input) { std::size_t Push(const std::span<T> input) {
return Push(input.data(), input.size()); return Push(input.data(), input.size());
} }

View file

@ -3,6 +3,9 @@
#pragma once #pragma once
#include <iterator>
#include "common/concepts.h"
#include "common/make_unique_for_overwrite.h" #include "common/make_unique_for_overwrite.h"
namespace Common { namespace Common {
@ -16,6 +19,12 @@ namespace Common {
template <typename T> template <typename T>
class ScratchBuffer { class ScratchBuffer {
public: public:
using iterator = T*;
using const_iterator = const T*;
using value_type = T;
using element_type = T;
using iterator_category = std::contiguous_iterator_tag;
ScratchBuffer() = default; ScratchBuffer() = default;
explicit ScratchBuffer(size_t initial_capacity) explicit ScratchBuffer(size_t initial_capacity)

View file

@ -1,8 +1,11 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <version>
#if __cpp_lib_chrono >= 201907L #if __cpp_lib_chrono >= 201907L
#include <chrono> #include <chrono>
#include <exception>
#include <stdexcept>
#endif #endif
#include <string_view> #include <string_view>
@ -25,9 +28,19 @@ std::string GetTimeZoneString() {
if (time_zone_index == 0) { // Auto if (time_zone_index == 0) { // Auto
#if __cpp_lib_chrono >= 201907L #if __cpp_lib_chrono >= 201907L
const struct std::chrono::tzdb& time_zone_data = std::chrono::get_tzdb(); const struct std::chrono::tzdb& time_zone_data = std::chrono::get_tzdb();
try {
const std::chrono::time_zone* current_zone = time_zone_data.current_zone(); const std::chrono::time_zone* current_zone = time_zone_data.current_zone();
std::string_view current_zone_name = current_zone->name(); std::string_view current_zone_name = current_zone->name();
location_name = current_zone_name; location_name = current_zone_name;
} catch (std::runtime_error& runtime_error) {
// VCRUNTIME will throw a runtime_error if the operating system's selected time zone
// cannot be found
location_name = Common::TimeZone::FindSystemTimeZone();
LOG_WARNING(Common,
"Error occurred when trying to determine system time zone:\n{}\nFalling "
"back to hour offset \"{}\"",
runtime_error.what(), location_name);
}
#else #else
location_name = Common::TimeZone::FindSystemTimeZone(); location_name = Common::TimeZone::FindSystemTimeZone();
#endif #endif

View file

@ -483,6 +483,7 @@ struct Values {
AstcRecompression::Uncompressed, AstcRecompression::Uncompressed, AstcRecompression::Bc3, AstcRecompression::Uncompressed, AstcRecompression::Uncompressed, AstcRecompression::Bc3,
"astc_recompression"}; "astc_recompression"};
SwitchableSetting<bool> use_video_framerate{false, "use_video_framerate"}; SwitchableSetting<bool> use_video_framerate{false, "use_video_framerate"};
SwitchableSetting<bool> barrier_feedback_loops{true, "barrier_feedback_loops"};
SwitchableSetting<u8> bg_red{0, "bg_red"}; SwitchableSetting<u8> bg_red{0, "bg_red"};
SwitchableSetting<u8> bg_green{0, "bg_green"}; SwitchableSetting<u8> bg_green{0, "bg_green"};
@ -524,9 +525,16 @@ struct Values {
Setting<bool> tas_loop{false, "tas_loop"}; Setting<bool> tas_loop{false, "tas_loop"};
Setting<bool> mouse_panning{false, "mouse_panning"}; Setting<bool> mouse_panning{false, "mouse_panning"};
Setting<u8, true> mouse_panning_sensitivity{50, 1, 100, "mouse_panning_sensitivity"}; Setting<u8, true> mouse_panning_x_sensitivity{50, 1, 100, "mouse_panning_x_sensitivity"};
Setting<bool> mouse_enabled{false, "mouse_enabled"}; Setting<u8, true> mouse_panning_y_sensitivity{50, 1, 100, "mouse_panning_y_sensitivity"};
Setting<u8, true> mouse_panning_deadzone_x_counterweight{
0, 0, 100, "mouse_panning_deadzone_x_counterweight"};
Setting<u8, true> mouse_panning_deadzone_y_counterweight{
0, 0, 100, "mouse_panning_deadzone_y_counterweight"};
Setting<u8, true> mouse_panning_decay_strength{22, 0, 100, "mouse_panning_decay_strength"};
Setting<u8, true> mouse_panning_min_decay{5, 0, 100, "mouse_panning_min_decay"};
Setting<bool> mouse_enabled{false, "mouse_enabled"};
Setting<bool> emulate_analog_keyboard{false, "emulate_analog_keyboard"}; Setting<bool> emulate_analog_keyboard{false, "emulate_analog_keyboard"};
Setting<bool> keyboard_enabled{false, "keyboard_enabled"}; Setting<bool> keyboard_enabled{false, "keyboard_enabled"};

View file

@ -28,13 +28,12 @@ static s64 GetSystemTimeNS() {
// GetSystemTimePreciseAsFileTime returns the file time in 100ns units. // GetSystemTimePreciseAsFileTime returns the file time in 100ns units.
static constexpr s64 Multiplier = 100; static constexpr s64 Multiplier = 100;
// Convert Windows epoch to Unix epoch. // Convert Windows epoch to Unix epoch.
static constexpr s64 WindowsEpochToUnixEpochNS = 0x19DB1DED53E8000LL; static constexpr s64 WindowsEpochToUnixEpoch = 0x19DB1DED53E8000LL;
FILETIME filetime; FILETIME filetime;
GetSystemTimePreciseAsFileTime(&filetime); GetSystemTimePreciseAsFileTime(&filetime);
return Multiplier * ((static_cast<s64>(filetime.dwHighDateTime) << 32) + return Multiplier * ((static_cast<s64>(filetime.dwHighDateTime) << 32) +
static_cast<s64>(filetime.dwLowDateTime)) - static_cast<s64>(filetime.dwLowDateTime) - WindowsEpochToUnixEpoch);
WindowsEpochToUnixEpochNS;
} }
#endif #endif

View file

@ -93,6 +93,7 @@ void AppendCPUInfo(FieldCollection& fc) {
add_field("CPU_Extension_x64_GFNI", caps.gfni); add_field("CPU_Extension_x64_GFNI", caps.gfni);
add_field("CPU_Extension_x64_INVARIANT_TSC", caps.invariant_tsc); add_field("CPU_Extension_x64_INVARIANT_TSC", caps.invariant_tsc);
add_field("CPU_Extension_x64_LZCNT", caps.lzcnt); add_field("CPU_Extension_x64_LZCNT", caps.lzcnt);
add_field("CPU_Extension_x64_MONITORX", caps.monitorx);
add_field("CPU_Extension_x64_MOVBE", caps.movbe); add_field("CPU_Extension_x64_MOVBE", caps.movbe);
add_field("CPU_Extension_x64_PCLMULQDQ", caps.pclmulqdq); add_field("CPU_Extension_x64_PCLMULQDQ", caps.pclmulqdq);
add_field("CPU_Extension_x64_POPCNT", caps.popcnt); add_field("CPU_Extension_x64_POPCNT", caps.popcnt);

View file

@ -55,7 +55,7 @@ public:
is_set = false; is_set = false;
} }
[[nodiscard]] bool IsSet() { [[nodiscard]] bool IsSet() const {
return is_set; return is_set;
} }

View file

@ -2,88 +2,75 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include "common/steady_clock.h" #include "common/steady_clock.h"
#include "common/uint128.h"
#include "common/wall_clock.h" #include "common/wall_clock.h"
#ifdef ARCHITECTURE_x86_64 #ifdef ARCHITECTURE_x86_64
#include "common/x64/cpu_detect.h" #include "common/x64/cpu_detect.h"
#include "common/x64/native_clock.h" #include "common/x64/native_clock.h"
#include "common/x64/rdtsc.h"
#endif #endif
namespace Common { namespace Common {
class StandardWallClock final : public WallClock { class StandardWallClock final : public WallClock {
public: public:
explicit StandardWallClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_) explicit StandardWallClock() : start_time{SteadyClock::Now()} {}
: WallClock{emulated_cpu_frequency_, emulated_clock_frequency_, false},
start_time{SteadyClock::Now()} {}
std::chrono::nanoseconds GetTimeNS() override { std::chrono::nanoseconds GetTimeNS() const override {
return SteadyClock::Now() - start_time; return SteadyClock::Now() - start_time;
} }
std::chrono::microseconds GetTimeUS() override { std::chrono::microseconds GetTimeUS() const override {
return std::chrono::duration_cast<std::chrono::microseconds>(GetTimeNS()); return static_cast<std::chrono::microseconds>(GetHostTicksElapsed() / NsToUsRatio::den);
} }
std::chrono::milliseconds GetTimeMS() override { std::chrono::milliseconds GetTimeMS() const override {
return std::chrono::duration_cast<std::chrono::milliseconds>(GetTimeNS()); return static_cast<std::chrono::milliseconds>(GetHostTicksElapsed() / NsToMsRatio::den);
} }
u64 GetClockCycles() override { u64 GetCNTPCT() const override {
const u128 temp = Common::Multiply64Into128(GetTimeNS().count(), emulated_clock_frequency); return GetHostTicksElapsed() * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den;
return Common::Divide128On32(temp, NS_RATIO).first;
} }
u64 GetCPUCycles() override { u64 GetGPUTick() const override {
const u128 temp = Common::Multiply64Into128(GetTimeNS().count(), emulated_cpu_frequency); return GetHostTicksElapsed() * NsToGPUTickRatio::num / NsToGPUTickRatio::den;
return Common::Divide128On32(temp, NS_RATIO).first;
} }
void Pause([[maybe_unused]] bool is_paused) override { u64 GetHostTicksNow() const override {
// Do nothing in this clock type. return static_cast<u64>(SteadyClock::Now().time_since_epoch().count());
}
u64 GetHostTicksElapsed() const override {
return static_cast<u64>(GetTimeNS().count());
}
bool IsNative() const override {
return false;
} }
private: private:
SteadyClock::time_point start_time; SteadyClock::time_point start_time;
}; };
std::unique_ptr<WallClock> CreateOptimalClock() {
#ifdef ARCHITECTURE_x86_64 #ifdef ARCHITECTURE_x86_64
std::unique_ptr<WallClock> CreateBestMatchingClock(u64 emulated_cpu_frequency,
u64 emulated_clock_frequency) {
const auto& caps = GetCPUCaps(); const auto& caps = GetCPUCaps();
u64 rtsc_frequency = 0;
if (caps.invariant_tsc) {
rtsc_frequency = caps.tsc_frequency ? caps.tsc_frequency : EstimateRDTSCFrequency();
}
// Fallback to StandardWallClock if the hardware TSC does not have the precision greater than: if (caps.invariant_tsc && caps.tsc_frequency >= WallClock::GPUTickFreq) {
// - A nanosecond return std::make_unique<X64::NativeClock>(caps.tsc_frequency);
// - The emulated CPU frequency
// - The emulated clock counter frequency (CNTFRQ)
if (rtsc_frequency <= WallClock::NS_RATIO || rtsc_frequency <= emulated_cpu_frequency ||
rtsc_frequency <= emulated_clock_frequency) {
return std::make_unique<StandardWallClock>(emulated_cpu_frequency,
emulated_clock_frequency);
} else { } else {
return std::make_unique<X64::NativeClock>(emulated_cpu_frequency, emulated_clock_frequency, // Fallback to StandardWallClock if the hardware TSC
rtsc_frequency); // - Is not invariant
// - Is not more precise than GPUTickFreq
return std::make_unique<StandardWallClock>();
} }
}
#else #else
return std::make_unique<StandardWallClock>();
std::unique_ptr<WallClock> CreateBestMatchingClock(u64 emulated_cpu_frequency, #endif
u64 emulated_clock_frequency) {
return std::make_unique<StandardWallClock>(emulated_cpu_frequency, emulated_clock_frequency);
} }
#endif std::unique_ptr<WallClock> CreateStandardWallClock() {
return std::make_unique<StandardWallClock>();
std::unique_ptr<WallClock> CreateStandardWallClock(u64 emulated_cpu_frequency,
u64 emulated_clock_frequency) {
return std::make_unique<StandardWallClock>(emulated_cpu_frequency, emulated_clock_frequency);
} }
} // namespace Common } // namespace Common

View file

@ -5,6 +5,7 @@
#include <chrono> #include <chrono>
#include <memory> #include <memory>
#include <ratio>
#include "common/common_types.h" #include "common/common_types.h"
@ -12,50 +13,82 @@ namespace Common {
class WallClock { class WallClock {
public: public:
static constexpr u64 NS_RATIO = 1'000'000'000; static constexpr u64 CNTFRQ = 19'200'000; // CNTPCT_EL0 Frequency = 19.2 MHz
static constexpr u64 US_RATIO = 1'000'000; static constexpr u64 GPUTickFreq = 614'400'000; // GM20B GPU Tick Frequency = 614.4 MHz
static constexpr u64 MS_RATIO = 1'000; static constexpr u64 CPUTickFreq = 1'020'000'000; // T210/4 A57 CPU Tick Frequency = 1020.0 MHz
virtual ~WallClock() = default; virtual ~WallClock() = default;
/// Returns current wall time in nanoseconds /// @returns The time in nanoseconds since the construction of this clock.
[[nodiscard]] virtual std::chrono::nanoseconds GetTimeNS() = 0; virtual std::chrono::nanoseconds GetTimeNS() const = 0;
/// Returns current wall time in microseconds /// @returns The time in microseconds since the construction of this clock.
[[nodiscard]] virtual std::chrono::microseconds GetTimeUS() = 0; virtual std::chrono::microseconds GetTimeUS() const = 0;
/// Returns current wall time in milliseconds /// @returns The time in milliseconds since the construction of this clock.
[[nodiscard]] virtual std::chrono::milliseconds GetTimeMS() = 0; virtual std::chrono::milliseconds GetTimeMS() const = 0;
/// Returns current wall time in emulated clock cycles /// @returns The guest CNTPCT ticks since the construction of this clock.
[[nodiscard]] virtual u64 GetClockCycles() = 0; virtual u64 GetCNTPCT() const = 0;
/// Returns current wall time in emulated cpu cycles /// @returns The guest GPU ticks since the construction of this clock.
[[nodiscard]] virtual u64 GetCPUCycles() = 0; virtual u64 GetGPUTick() const = 0;
virtual void Pause(bool is_paused) = 0; /// @returns The raw host timer ticks since an indeterminate epoch.
virtual u64 GetHostTicksNow() const = 0;
/// Tells if the wall clock, uses the host CPU's hardware clock /// @returns The raw host timer ticks since the construction of this clock.
[[nodiscard]] bool IsNative() const { virtual u64 GetHostTicksElapsed() const = 0;
return is_native;
/// @returns Whether the clock directly uses the host's hardware clock.
virtual bool IsNative() const = 0;
static inline u64 NSToCNTPCT(u64 ns) {
return ns * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den;
}
static inline u64 NSToGPUTick(u64 ns) {
return ns * NsToGPUTickRatio::num / NsToGPUTickRatio::den;
}
// Cycle Timing
static inline u64 CPUTickToNS(u64 cpu_tick) {
return cpu_tick * CPUTickToNsRatio::num / CPUTickToNsRatio::den;
}
static inline u64 CPUTickToUS(u64 cpu_tick) {
return cpu_tick * CPUTickToUsRatio::num / CPUTickToUsRatio::den;
}
static inline u64 CPUTickToCNTPCT(u64 cpu_tick) {
return cpu_tick * CPUTickToCNTPCTRatio::num / CPUTickToCNTPCTRatio::den;
}
static inline u64 CPUTickToGPUTick(u64 cpu_tick) {
return cpu_tick * CPUTickToGPUTickRatio::num / CPUTickToGPUTickRatio::den;
} }
protected: protected:
explicit WallClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_, bool is_native_) using NsRatio = std::nano;
: emulated_cpu_frequency{emulated_cpu_frequency_}, using UsRatio = std::micro;
emulated_clock_frequency{emulated_clock_frequency_}, is_native{is_native_} {} using MsRatio = std::milli;
u64 emulated_cpu_frequency; using NsToUsRatio = std::ratio_divide<std::nano, std::micro>;
u64 emulated_clock_frequency; using NsToMsRatio = std::ratio_divide<std::nano, std::milli>;
using NsToCNTPCTRatio = std::ratio<CNTFRQ, std::nano::den>;
using NsToGPUTickRatio = std::ratio<GPUTickFreq, std::nano::den>;
private: // Cycle Timing
bool is_native;
using CPUTickToNsRatio = std::ratio<std::nano::den, CPUTickFreq>;
using CPUTickToUsRatio = std::ratio<std::micro::den, CPUTickFreq>;
using CPUTickToCNTPCTRatio = std::ratio<CNTFRQ, CPUTickFreq>;
using CPUTickToGPUTickRatio = std::ratio<GPUTickFreq, CPUTickFreq>;
}; };
[[nodiscard]] std::unique_ptr<WallClock> CreateBestMatchingClock(u64 emulated_cpu_frequency, std::unique_ptr<WallClock> CreateOptimalClock();
u64 emulated_clock_frequency);
[[nodiscard]] std::unique_ptr<WallClock> CreateStandardWallClock(u64 emulated_cpu_frequency, std::unique_ptr<WallClock> CreateStandardWallClock();
u64 emulated_clock_frequency);
} // namespace Common } // namespace Common

View file

@ -14,6 +14,7 @@
#include "common/common_types.h" #include "common/common_types.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/x64/cpu_detect.h" #include "common/x64/cpu_detect.h"
#include "common/x64/rdtsc.h"
#ifdef _WIN32 #ifdef _WIN32
#include <windows.h> #include <windows.h>
@ -167,6 +168,7 @@ static CPUCaps Detect() {
__cpuid(cpu_id, 0x80000001); __cpuid(cpu_id, 0x80000001);
caps.lzcnt = Common::Bit<5>(cpu_id[2]); caps.lzcnt = Common::Bit<5>(cpu_id[2]);
caps.fma4 = Common::Bit<16>(cpu_id[2]); caps.fma4 = Common::Bit<16>(cpu_id[2]);
caps.monitorx = Common::Bit<29>(cpu_id[2]);
} }
if (max_ex_fn >= 0x80000007) { if (max_ex_fn >= 0x80000007) {
@ -187,6 +189,8 @@ static CPUCaps Detect() {
caps.tsc_frequency = static_cast<u64>(caps.crystal_frequency) * caps.tsc_frequency = static_cast<u64>(caps.crystal_frequency) *
caps.tsc_crystal_ratio_numerator / caps.tsc_crystal_ratio_numerator /
caps.tsc_crystal_ratio_denominator; caps.tsc_crystal_ratio_denominator;
} else {
caps.tsc_frequency = X64::EstimateRDTSCFrequency();
} }
} }

View file

@ -63,6 +63,7 @@ struct CPUCaps {
bool gfni : 1; bool gfni : 1;
bool invariant_tsc : 1; bool invariant_tsc : 1;
bool lzcnt : 1; bool lzcnt : 1;
bool monitorx : 1;
bool movbe : 1; bool movbe : 1;
bool pclmulqdq : 1; bool pclmulqdq : 1;
bool popcnt : 1; bool popcnt : 1;

View file

@ -9,58 +9,64 @@
#include "common/x64/cpu_detect.h" #include "common/x64/cpu_detect.h"
#include "common/x64/cpu_wait.h" #include "common/x64/cpu_wait.h"
#include "common/x64/rdtsc.h"
namespace Common::X64 { namespace Common::X64 {
#ifdef _MSC_VER namespace {
__forceinline static u64 FencedRDTSC() {
_mm_lfence();
_ReadWriteBarrier();
const u64 result = __rdtsc();
_mm_lfence();
_ReadWriteBarrier();
return result;
}
__forceinline static void TPAUSE() {
// 100,000 cycles is a reasonable amount of time to wait to save on CPU resources. // 100,000 cycles is a reasonable amount of time to wait to save on CPU resources.
// For reference: // For reference:
// At 1 GHz, 100K cycles is 100us // At 1 GHz, 100K cycles is 100us
// At 2 GHz, 100K cycles is 50us // At 2 GHz, 100K cycles is 50us
// At 4 GHz, 100K cycles is 25us // At 4 GHz, 100K cycles is 25us
static constexpr auto PauseCycles = 100'000; constexpr auto PauseCycles = 100'000U;
_tpause(0, FencedRDTSC() + PauseCycles);
} // Anonymous namespace
#ifdef _MSC_VER
__forceinline static void TPAUSE() {
static constexpr auto RequestC02State = 0U;
_tpause(RequestC02State, FencedRDTSC() + PauseCycles);
}
__forceinline static void MWAITX() {
static constexpr auto EnableWaitTimeFlag = 1U << 1;
static constexpr auto RequestC1State = 0U;
// monitor_var should be aligned to a cache line.
alignas(64) u64 monitor_var{};
_mm_monitorx(&monitor_var, 0, 0);
_mm_mwaitx(EnableWaitTimeFlag, RequestC1State, PauseCycles);
} }
#else #else
static u64 FencedRDTSC() {
u64 eax;
u64 edx;
asm volatile("lfence\n\t"
"rdtsc\n\t"
"lfence\n\t"
: "=a"(eax), "=d"(edx));
return (edx << 32) | eax;
}
static void TPAUSE() { static void TPAUSE() {
// 100,000 cycles is a reasonable amount of time to wait to save on CPU resources. static constexpr auto RequestC02State = 0U;
// For reference:
// At 1 GHz, 100K cycles is 100us
// At 2 GHz, 100K cycles is 50us
// At 4 GHz, 100K cycles is 25us
static constexpr auto PauseCycles = 100'000;
const auto tsc = FencedRDTSC() + PauseCycles; const auto tsc = FencedRDTSC() + PauseCycles;
const auto eax = static_cast<u32>(tsc & 0xFFFFFFFF); const auto eax = static_cast<u32>(tsc & 0xFFFFFFFF);
const auto edx = static_cast<u32>(tsc >> 32); const auto edx = static_cast<u32>(tsc >> 32);
asm volatile("tpause %0" : : "r"(0), "d"(edx), "a"(eax)); asm volatile("tpause %0" : : "r"(RequestC02State), "d"(edx), "a"(eax));
}
static void MWAITX() {
static constexpr auto EnableWaitTimeFlag = 1U << 1;
static constexpr auto RequestC1State = 0U;
// monitor_var should be aligned to a cache line.
alignas(64) u64 monitor_var{};
asm volatile("monitorx" : : "a"(&monitor_var), "c"(0), "d"(0));
asm volatile("mwaitx" : : "a"(RequestC1State), "b"(PauseCycles), "c"(EnableWaitTimeFlag));
} }
#endif #endif
void MicroSleep() { void MicroSleep() {
static const bool has_waitpkg = GetCPUCaps().waitpkg; static const bool has_waitpkg = GetCPUCaps().waitpkg;
static const bool has_monitorx = GetCPUCaps().monitorx;
if (has_waitpkg) { if (has_waitpkg) {
TPAUSE(); TPAUSE();
} else if (has_monitorx) {
MWAITX();
} else { } else {
std::this_thread::yield(); std::this_thread::yield();
} }

View file

@ -1,164 +1,50 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <chrono>
#include <thread>
#include "common/atomic_ops.h"
#include "common/steady_clock.h"
#include "common/uint128.h" #include "common/uint128.h"
#include "common/x64/native_clock.h" #include "common/x64/native_clock.h"
#include "common/x64/rdtsc.h"
#ifdef _MSC_VER namespace Common::X64 {
#include <intrin.h>
#endif
namespace Common { NativeClock::NativeClock(u64 rdtsc_frequency_)
: start_ticks{FencedRDTSC()}, rdtsc_frequency{rdtsc_frequency_},
ns_rdtsc_factor{GetFixedPoint64Factor(NsRatio::den, rdtsc_frequency)},
us_rdtsc_factor{GetFixedPoint64Factor(UsRatio::den, rdtsc_frequency)},
ms_rdtsc_factor{GetFixedPoint64Factor(MsRatio::den, rdtsc_frequency)},
cntpct_rdtsc_factor{GetFixedPoint64Factor(CNTFRQ, rdtsc_frequency)},
gputick_rdtsc_factor{GetFixedPoint64Factor(GPUTickFreq, rdtsc_frequency)} {}
#ifdef _MSC_VER std::chrono::nanoseconds NativeClock::GetTimeNS() const {
__forceinline static u64 FencedRDTSC() { return std::chrono::nanoseconds{MultiplyHigh(GetHostTicksElapsed(), ns_rdtsc_factor)};
_mm_lfence();
_ReadWriteBarrier();
const u64 result = __rdtsc();
_mm_lfence();
_ReadWriteBarrier();
return result;
}
#else
static u64 FencedRDTSC() {
u64 eax;
u64 edx;
asm volatile("lfence\n\t"
"rdtsc\n\t"
"lfence\n\t"
: "=a"(eax), "=d"(edx));
return (edx << 32) | eax;
}
#endif
template <u64 Nearest>
static u64 RoundToNearest(u64 value) {
const auto mod = value % Nearest;
return mod >= (Nearest / 2) ? (value - mod + Nearest) : (value - mod);
} }
u64 EstimateRDTSCFrequency() { std::chrono::microseconds NativeClock::GetTimeUS() const {
// Discard the first result measuring the rdtsc. return std::chrono::microseconds{MultiplyHigh(GetHostTicksElapsed(), us_rdtsc_factor)};
FencedRDTSC();
std::this_thread::sleep_for(std::chrono::milliseconds{1});
FencedRDTSC();
// Get the current time.
const auto start_time = Common::RealTimeClock::Now();
const u64 tsc_start = FencedRDTSC();
// Wait for 250 milliseconds.
std::this_thread::sleep_for(std::chrono::milliseconds{250});
const auto end_time = Common::RealTimeClock::Now();
const u64 tsc_end = FencedRDTSC();
// Calculate differences.
const u64 timer_diff = static_cast<u64>(
std::chrono::duration_cast<std::chrono::nanoseconds>(end_time - start_time).count());
const u64 tsc_diff = tsc_end - tsc_start;
const u64 tsc_freq = MultiplyAndDivide64(tsc_diff, 1000000000ULL, timer_diff);
return RoundToNearest<1000>(tsc_freq);
} }
namespace X64 { std::chrono::milliseconds NativeClock::GetTimeMS() const {
NativeClock::NativeClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_, return std::chrono::milliseconds{MultiplyHigh(GetHostTicksElapsed(), ms_rdtsc_factor)};
u64 rtsc_frequency_)
: WallClock(emulated_cpu_frequency_, emulated_clock_frequency_, true), rtsc_frequency{
rtsc_frequency_} {
// Thread to re-adjust the RDTSC frequency after 10 seconds has elapsed.
time_sync_thread = std::jthread{[this](std::stop_token token) {
// Get the current time.
const auto start_time = Common::RealTimeClock::Now();
const u64 tsc_start = FencedRDTSC();
// Wait for 10 seconds.
if (!Common::StoppableTimedWait(token, std::chrono::seconds{10})) {
return;
}
const auto end_time = Common::RealTimeClock::Now();
const u64 tsc_end = FencedRDTSC();
// Calculate differences.
const u64 timer_diff = static_cast<u64>(
std::chrono::duration_cast<std::chrono::nanoseconds>(end_time - start_time).count());
const u64 tsc_diff = tsc_end - tsc_start;
const u64 tsc_freq = MultiplyAndDivide64(tsc_diff, 1000000000ULL, timer_diff);
rtsc_frequency = tsc_freq;
CalculateAndSetFactors();
}};
time_point.inner.last_measure = FencedRDTSC();
time_point.inner.accumulated_ticks = 0U;
CalculateAndSetFactors();
} }
u64 NativeClock::GetRTSC() { u64 NativeClock::GetCNTPCT() const {
TimePoint new_time_point{}; return MultiplyHigh(GetHostTicksElapsed(), cntpct_rdtsc_factor);
TimePoint current_time_point{};
current_time_point.pack = Common::AtomicLoad128(time_point.pack.data());
do {
const u64 current_measure = FencedRDTSC();
u64 diff = current_measure - current_time_point.inner.last_measure;
diff = diff & ~static_cast<u64>(static_cast<s64>(diff) >> 63); // max(diff, 0)
new_time_point.inner.last_measure = current_measure > current_time_point.inner.last_measure
? current_measure
: current_time_point.inner.last_measure;
new_time_point.inner.accumulated_ticks = current_time_point.inner.accumulated_ticks + diff;
} while (!Common::AtomicCompareAndSwap(time_point.pack.data(), new_time_point.pack,
current_time_point.pack, current_time_point.pack));
return new_time_point.inner.accumulated_ticks;
} }
void NativeClock::Pause(bool is_paused) { u64 NativeClock::GetGPUTick() const {
if (!is_paused) { return MultiplyHigh(GetHostTicksElapsed(), gputick_rdtsc_factor);
TimePoint current_time_point{};
TimePoint new_time_point{};
current_time_point.pack = Common::AtomicLoad128(time_point.pack.data());
do {
new_time_point.pack = current_time_point.pack;
new_time_point.inner.last_measure = FencedRDTSC();
} while (!Common::AtomicCompareAndSwap(time_point.pack.data(), new_time_point.pack,
current_time_point.pack, current_time_point.pack));
}
} }
std::chrono::nanoseconds NativeClock::GetTimeNS() { u64 NativeClock::GetHostTicksNow() const {
const u64 rtsc_value = GetRTSC(); return FencedRDTSC();
return std::chrono::nanoseconds{MultiplyHigh(rtsc_value, ns_rtsc_factor)};
} }
std::chrono::microseconds NativeClock::GetTimeUS() { u64 NativeClock::GetHostTicksElapsed() const {
const u64 rtsc_value = GetRTSC(); return FencedRDTSC() - start_ticks;
return std::chrono::microseconds{MultiplyHigh(rtsc_value, us_rtsc_factor)};
} }
std::chrono::milliseconds NativeClock::GetTimeMS() { bool NativeClock::IsNative() const {
const u64 rtsc_value = GetRTSC(); return true;
return std::chrono::milliseconds{MultiplyHigh(rtsc_value, ms_rtsc_factor)};
} }
u64 NativeClock::GetClockCycles() { } // namespace Common::X64
const u64 rtsc_value = GetRTSC();
return MultiplyHigh(rtsc_value, clock_rtsc_factor);
}
u64 NativeClock::GetCPUCycles() {
const u64 rtsc_value = GetRTSC();
return MultiplyHigh(rtsc_value, cpu_rtsc_factor);
}
void NativeClock::CalculateAndSetFactors() {
ns_rtsc_factor = GetFixedPoint64Factor(NS_RATIO, rtsc_frequency);
us_rtsc_factor = GetFixedPoint64Factor(US_RATIO, rtsc_frequency);
ms_rtsc_factor = GetFixedPoint64Factor(MS_RATIO, rtsc_frequency);
clock_rtsc_factor = GetFixedPoint64Factor(emulated_clock_frequency, rtsc_frequency);
cpu_rtsc_factor = GetFixedPoint64Factor(emulated_cpu_frequency, rtsc_frequency);
}
} // namespace X64
} // namespace Common

View file

@ -3,58 +3,39 @@
#pragma once #pragma once
#include "common/polyfill_thread.h"
#include "common/wall_clock.h" #include "common/wall_clock.h"
namespace Common { namespace Common::X64 {
namespace X64 {
class NativeClock final : public WallClock { class NativeClock final : public WallClock {
public: public:
explicit NativeClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_, explicit NativeClock(u64 rdtsc_frequency_);
u64 rtsc_frequency_);
std::chrono::nanoseconds GetTimeNS() override; std::chrono::nanoseconds GetTimeNS() const override;
std::chrono::microseconds GetTimeUS() override; std::chrono::microseconds GetTimeUS() const override;
std::chrono::milliseconds GetTimeMS() override; std::chrono::milliseconds GetTimeMS() const override;
u64 GetClockCycles() override; u64 GetCNTPCT() const override;
u64 GetCPUCycles() override; u64 GetGPUTick() const override;
void Pause(bool is_paused) override; u64 GetHostTicksNow() const override;
u64 GetHostTicksElapsed() const override;
bool IsNative() const override;
private: private:
u64 GetRTSC(); u64 start_ticks;
u64 rdtsc_frequency;
void CalculateAndSetFactors(); u64 ns_rdtsc_factor;
u64 us_rdtsc_factor;
union alignas(16) TimePoint { u64 ms_rdtsc_factor;
TimePoint() : pack{} {} u64 cntpct_rdtsc_factor;
u128 pack{}; u64 gputick_rdtsc_factor;
struct Inner {
u64 last_measure{};
u64 accumulated_ticks{};
} inner;
}; };
TimePoint time_point; } // namespace Common::X64
// factors
u64 clock_rtsc_factor{};
u64 cpu_rtsc_factor{};
u64 ns_rtsc_factor{};
u64 us_rtsc_factor{};
u64 ms_rtsc_factor{};
u64 rtsc_frequency;
std::jthread time_sync_thread;
};
} // namespace X64
u64 EstimateRDTSCFrequency();
} // namespace Common

39
src/common/x64/rdtsc.cpp Normal file
View file

@ -0,0 +1,39 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <thread>
#include "common/steady_clock.h"
#include "common/uint128.h"
#include "common/x64/rdtsc.h"
namespace Common::X64 {
template <u64 Nearest>
static u64 RoundToNearest(u64 value) {
const auto mod = value % Nearest;
return mod >= (Nearest / 2) ? (value - mod + Nearest) : (value - mod);
}
u64 EstimateRDTSCFrequency() {
// Discard the first result measuring the rdtsc.
FencedRDTSC();
std::this_thread::sleep_for(std::chrono::milliseconds{1});
FencedRDTSC();
// Get the current time.
const auto start_time = RealTimeClock::Now();
const u64 tsc_start = FencedRDTSC();
// Wait for 100 milliseconds.
std::this_thread::sleep_for(std::chrono::milliseconds{100});
const auto end_time = RealTimeClock::Now();
const u64 tsc_end = FencedRDTSC();
// Calculate differences.
const u64 timer_diff = static_cast<u64>(
std::chrono::duration_cast<std::chrono::nanoseconds>(end_time - start_time).count());
const u64 tsc_diff = tsc_end - tsc_start;
const u64 tsc_freq = MultiplyAndDivide64(tsc_diff, 1000000000ULL, timer_diff);
return RoundToNearest<100'000>(tsc_freq);
}
} // namespace Common::X64

37
src/common/x64/rdtsc.h Normal file
View file

@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#ifdef _MSC_VER
#include <intrin.h>
#endif
#include "common/common_types.h"
namespace Common::X64 {
#ifdef _MSC_VER
__forceinline static u64 FencedRDTSC() {
_mm_lfence();
_ReadWriteBarrier();
const u64 result = __rdtsc();
_mm_lfence();
_ReadWriteBarrier();
return result;
}
#else
static inline u64 FencedRDTSC() {
u64 eax;
u64 edx;
asm volatile("lfence\n\t"
"rdtsc\n\t"
"lfence\n\t"
: "=a"(eax), "=d"(edx));
return (edx << 32) | eax;
}
#endif
u64 EstimateRDTSCFrequency();
} // namespace Common::X64

View file

@ -14,7 +14,6 @@ add_library(core STATIC
core.h core.h
core_timing.cpp core_timing.cpp
core_timing.h core_timing.h
core_timing_util.h
cpu_manager.cpp cpu_manager.cpp
cpu_manager.h cpu_manager.h
crypto/aes_util.cpp crypto/aes_util.cpp

Some files were not shown because too many files have changed in this diff Show more