Merge remote-tracking branch 'origin/master' into ssl
This commit is contained in:
commit
98685d48e3
|
@ -12,7 +12,7 @@
|
|||
url = https://github.com/mozilla/cubeb.git
|
||||
[submodule "dynarmic"]
|
||||
path = externals/dynarmic
|
||||
url = https://github.com/MerryMage/dynarmic.git
|
||||
url = https://github.com/merryhime/dynarmic.git
|
||||
[submodule "libusb"]
|
||||
path = externals/libusb/libusb
|
||||
url = https://github.com/libusb/libusb.git
|
||||
|
@ -24,10 +24,10 @@
|
|||
url = https://github.com/KhronosGroup/Vulkan-Headers.git
|
||||
[submodule "sirit"]
|
||||
path = externals/sirit
|
||||
url = https://github.com/yuzu-emu/sirit
|
||||
url = https://github.com/yuzu-emu/sirit.git
|
||||
[submodule "mbedtls"]
|
||||
path = externals/mbedtls
|
||||
url = https://github.com/yuzu-emu/mbedtls
|
||||
url = https://github.com/yuzu-emu/mbedtls.git
|
||||
[submodule "xbyak"]
|
||||
path = externals/xbyak
|
||||
url = https://github.com/herumi/xbyak.git
|
||||
|
@ -45,13 +45,16 @@
|
|||
url = https://github.com/FFmpeg/FFmpeg.git
|
||||
[submodule "vcpkg"]
|
||||
path = externals/vcpkg
|
||||
url = https://github.com/Microsoft/vcpkg.git
|
||||
url = https://github.com/microsoft/vcpkg.git
|
||||
[submodule "cpp-jwt"]
|
||||
path = externals/cpp-jwt
|
||||
url = https://github.com/arun11299/cpp-jwt.git
|
||||
[submodule "libadrenotools"]
|
||||
path = externals/libadrenotools
|
||||
url = https://github.com/bylaws/libadrenotools
|
||||
url = https://github.com/bylaws/libadrenotools.git
|
||||
[submodule "tzdb_to_nx"]
|
||||
path = externals/nx_tzdb/tzdb_to_nx
|
||||
url = https://github.com/lat9nq/tzdb_to_nx.git
|
||||
[submodule "VulkanMemoryAllocator"]
|
||||
path = externals/vma/VulkanMemoryAllocator
|
||||
url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git
|
||||
|
|
|
@ -504,7 +504,7 @@ if (ENABLE_SDL2)
|
|||
if (YUZU_USE_BUNDLED_SDL2)
|
||||
# Detect toolchain and platform
|
||||
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()
|
||||
message(FATAL_ERROR "No bundled SDL2 binaries for your toolchain. Disable YUZU_USE_BUNDLED_SDL2 and provide your own.")
|
||||
endif()
|
||||
|
@ -524,7 +524,7 @@ if (ENABLE_SDL2)
|
|||
elseif (YUZU_USE_EXTERNAL_SDL2)
|
||||
message(STATUS "Using SDL2 from externals.")
|
||||
else()
|
||||
find_package(SDL2 2.0.18 REQUIRED)
|
||||
find_package(SDL2 2.26.4 REQUIRED)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -143,6 +143,11 @@ endif()
|
|||
# TZDB (Time Zone Database)
|
||||
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)
|
||||
add_library(demangle demangle/ItaniumDemangle.cpp)
|
||||
target_include_directories(demangle PUBLIC ./demangle)
|
||||
|
@ -152,6 +157,9 @@ endif()
|
|||
add_library(stb stb/stb_dxt.cpp)
|
||||
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 (ARCHITECTURE_arm64)
|
||||
add_subdirectory(libadrenotools)
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit f17058b562c8a1090c0c996b42982721ace90903
|
||||
Subproject commit 491fba1d06a4810645092b2559b9cc94abeb23bb
|
File diff suppressed because it is too large
Load Diff
|
@ -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);
|
||||
}
|
|
@ -7,7 +7,7 @@ add_library(nx_tzdb INTERFACE)
|
|||
|
||||
find_program(GIT git)
|
||||
find_program(GNU_MAKE make)
|
||||
find_program(GNU_DATE date)
|
||||
find_program(DATE_PROG date)
|
||||
|
||||
set(CAN_BUILD_NX_TZDB true)
|
||||
|
||||
|
@ -17,7 +17,7 @@ endif()
|
|||
if (NOT GNU_MAKE)
|
||||
set(CAN_BUILD_NX_TZDB false)
|
||||
endif()
|
||||
if (NOT GNU_DATE)
|
||||
if (NOT DATE_PROG)
|
||||
set(CAN_BUILD_NX_TZDB false)
|
||||
endif()
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Windows" OR ANDROID)
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 34df65eff295c2bd9ee9e6a077d662486d5cabb3
|
||||
Subproject commit 212afa2394a74226dcf1b7996a570aae17debb69
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 0aa3989b8f382f185fdf646cc83a1d16fa31d6ab
|
|
@ -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>
|
|
@ -26,7 +26,7 @@ val autoVersion = (((System.currentTimeMillis() / 1000) - 1451606400) / 10).toIn
|
|||
android {
|
||||
namespace = "org.yuzu.yuzu_emu"
|
||||
|
||||
compileSdkVersion = "android-33"
|
||||
compileSdkVersion = "android-34"
|
||||
ndkVersion = "25.2.9519653"
|
||||
|
||||
buildFeatures {
|
||||
|
@ -51,7 +51,7 @@ android {
|
|||
// TODO If this is ever modified, change application_id in strings.xml
|
||||
applicationId = "org.yuzu.yuzu_emu"
|
||||
minSdk = 30
|
||||
targetSdk = 33
|
||||
targetSdk = 34
|
||||
versionName = getGitVersion()
|
||||
|
||||
// If you want to use autoVersion for the versionCode, create a property in local.properties
|
||||
|
|
|
@ -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.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.POST_NOTIFICATIONS" />
|
||||
|
||||
|
@ -69,7 +70,9 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||
android:resource="@xml/nfc_tech_filter" />
|
||||
</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
|
||||
android:name=".features.DocumentProvider"
|
||||
|
|
|
@ -286,7 +286,7 @@ object NativeLibrary {
|
|||
/**
|
||||
* Unpauses emulation from a paused state.
|
||||
*/
|
||||
external fun unPauseEmulation()
|
||||
external fun unpauseEmulation()
|
||||
|
||||
/**
|
||||
* Pauses emulation.
|
||||
|
@ -313,6 +313,21 @@ object NativeLibrary {
|
|||
*/
|
||||
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
|
||||
*/
|
||||
|
|
|
@ -27,13 +27,13 @@ import android.view.MotionEvent
|
|||
import android.view.Surface
|
||||
import android.view.View
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Toast
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import kotlin.math.roundToInt
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
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.ForegroundService
|
||||
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.ThemeHelper
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||
private lateinit var binding: ActivityEmulationBinding
|
||||
|
@ -63,6 +65,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||
|
||||
private val actionPause = "ACTION_EMULATOR_PAUSE"
|
||||
private val actionPlay = "ACTION_EMULATOR_PLAY"
|
||||
private val actionMute = "ACTION_EMULATOR_MUTE"
|
||||
private val actionUnmute = "ACTION_EMULATOR_UNMUTE"
|
||||
|
||||
private val settingsViewModel: SettingsViewModel by viewModels()
|
||||
|
||||
|
@ -102,6 +106,19 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||
inputHandler = InputHandler()
|
||||
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
|
||||
val startIntent = Intent(this, ForegroundService::class.java)
|
||||
startForegroundService(startIntent)
|
||||
|
@ -305,6 +322,41 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||
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) }
|
||||
}
|
||||
|
||||
|
@ -322,10 +374,15 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||
private var pictureInPictureReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent) {
|
||||
if (intent.action == actionPlay) {
|
||||
if (NativeLibrary.isPaused()) NativeLibrary.unPauseEmulation()
|
||||
if (NativeLibrary.isPaused()) NativeLibrary.unpauseEmulation()
|
||||
} else if (intent.action == actionPause) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@ -339,6 +396,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||
IntentFilter().apply {
|
||||
addAction(actionPause)
|
||||
addAction(actionPlay)
|
||||
addAction(actionMute)
|
||||
addAction(actionUnmute)
|
||||
}.also {
|
||||
registerReceiver(pictureInPictureReceiver, it)
|
||||
}
|
||||
|
@ -347,6 +406,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||
unregisterReceiver(pictureInPictureReceiver)
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
// Always resume audio, since there is no UI button
|
||||
if (NativeLibrary.isMuted()) NativeLibrary.unmuteAudio()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -714,7 +714,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
State.PAUSED -> {
|
||||
Log.debug("[EmulationFragment] Resuming emulation.")
|
||||
NativeLibrary.surfaceChanged(surface)
|
||||
NativeLibrary.unPauseEmulation()
|
||||
NativeLibrary.unpauseEmulation()
|
||||
}
|
||||
|
||||
else -> Log.debug("[EmulationFragment] Bug, run called while already running.")
|
||||
|
|
|
@ -68,44 +68,65 @@ class HomeSettingsFragment : Fragment() {
|
|||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
mainActivity = requireActivity() as MainActivity
|
||||
|
||||
val optionsList: MutableList<HomeSetting> = mutableListOf(
|
||||
val optionsList: MutableList<HomeSetting> = mutableListOf<HomeSetting>().apply {
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.advanced_settings,
|
||||
R.string.settings_description,
|
||||
R.drawable.ic_settings
|
||||
) { SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") },
|
||||
) { SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") }
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.open_user_folder,
|
||||
R.string.open_user_folder_description,
|
||||
R.drawable.ic_folder_open
|
||||
) { openFileManager() },
|
||||
) { openFileManager() }
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.preferences_theme,
|
||||
R.string.theme_and_color_description,
|
||||
R.drawable.ic_palette
|
||||
) { SettingsActivity.launch(requireContext(), Settings.SECTION_THEME, "") },
|
||||
) { SettingsActivity.launch(requireContext(), Settings.SECTION_THEME, "") }
|
||||
)
|
||||
|
||||
if (GpuDriverHelper.supportsCustomDriverLoading()) {
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.install_gpu_driver,
|
||||
R.string.install_gpu_driver_description,
|
||||
R.drawable.ic_exit
|
||||
) { driverInstaller() },
|
||||
) { driverInstaller() }
|
||||
)
|
||||
}
|
||||
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.install_amiibo_keys,
|
||||
R.string.install_amiibo_keys_description,
|
||||
R.drawable.ic_nfc
|
||||
) { mainActivity.getAmiiboKey.launch(arrayOf("*/*")) },
|
||||
) { mainActivity.getAmiiboKey.launch(arrayOf("*/*")) }
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.install_game_content,
|
||||
R.string.install_game_content_description,
|
||||
R.drawable.ic_system_update_alt
|
||||
) { mainActivity.installGameUpdate.launch(arrayOf("*/*")) },
|
||||
) { mainActivity.installGameUpdate.launch(arrayOf("*/*")) }
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.select_games_folder,
|
||||
R.string.select_games_folder_description,
|
||||
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(
|
||||
R.string.manage_save_data,
|
||||
R.string.import_export_saves_description,
|
||||
|
@ -115,22 +136,30 @@ class HomeSettingsFragment : Fragment() {
|
|||
parentFragmentManager,
|
||||
ImportExportSavesFragment.TAG
|
||||
)
|
||||
},
|
||||
}
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.install_prod_keys,
|
||||
R.string.install_prod_keys_description,
|
||||
R.drawable.ic_unlock
|
||||
) { mainActivity.getProdKey.launch(arrayOf("*/*")) },
|
||||
) { mainActivity.getProdKey.launch(arrayOf("*/*")) }
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.install_firmware,
|
||||
R.string.install_firmware_description,
|
||||
R.drawable.ic_firmware
|
||||
) { mainActivity.getFirmware.launch(arrayOf("application/zip")) },
|
||||
) { mainActivity.getFirmware.launch(arrayOf("application/zip")) }
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.share_log,
|
||||
R.string.share_log_description,
|
||||
R.drawable.ic_log
|
||||
) { shareLog() },
|
||||
) { shareLog() }
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.about,
|
||||
R.string.about_description,
|
||||
|
@ -141,6 +170,7 @@ class HomeSettingsFragment : Fragment() {
|
|||
?.navigate(R.id.action_homeSettingsFragment_to_aboutFragment)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (!BuildConfig.PREMIUM) {
|
||||
optionsList.add(
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -29,7 +29,6 @@ import org.yuzu.yuzu_emu.layout.AutofitGridLayoutManager
|
|||
import org.yuzu.yuzu_emu.model.Game
|
||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||
import org.yuzu.yuzu_emu.utils.FileUtil
|
||||
|
||||
class SearchFragment : Fragment() {
|
||||
private var _binding: FragmentSearchBinding? = null
|
||||
|
@ -128,10 +127,7 @@ class SearchFragment : Fragment() {
|
|||
|
||||
R.id.chip_homebrew -> baseList.filter { it.isHomebrew }
|
||||
|
||||
R.id.chip_retail -> baseList.filter {
|
||||
FileUtil.hasExtension(it.path, "xci") ||
|
||||
FileUtil.hasExtension(it.path, "nsp")
|
||||
}
|
||||
R.id.chip_retail -> baseList.filter { !it.isHomebrew }
|
||||
|
||||
else -> baseList
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ class Game(
|
|||
|
||||
companion object {
|
||||
val extensions: Set<String> = HashSet(
|
||||
listOf(".xci", ".nsp", ".nca", ".nro")
|
||||
listOf("xci", "nsp", "nca", "nro")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
package org.yuzu.yuzu_emu.ui.main
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
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.utils.SettingsFile
|
||||
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.model.GamesViewModel
|
||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||
|
@ -294,7 +296,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
if (!FileUtil.hasExtension(result, "keys")) {
|
||||
if (FileUtil.getExtension(result) != "keys") {
|
||||
MessageDialogFragment.newInstance(
|
||||
R.string.reading_keys_failure,
|
||||
R.string.install_prod_keys_failure_extension_description
|
||||
|
@ -391,7 +393,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
if (!FileUtil.hasExtension(result, "bin")) {
|
||||
if (FileUtil.getExtension(result) != "bin") {
|
||||
MessageDialogFragment.newInstance(
|
||||
R.string.reading_keys_failure,
|
||||
R.string.install_amiibo_keys_failure_extension_description
|
||||
|
@ -481,62 +483,110 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||
}
|
||||
}
|
||||
|
||||
val installGameUpdate =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) {
|
||||
if (it == null) {
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
val installGameUpdate = registerForActivityResult(
|
||||
ActivityResultContracts.OpenMultipleDocuments()
|
||||
) { documents: List<Uri> ->
|
||||
if (documents.isNotEmpty()) {
|
||||
IndeterminateProgressDialogFragment.newInstance(
|
||||
this@MainActivity,
|
||||
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 {
|
||||
withContext(Dispatchers.Main) {
|
||||
when (result) {
|
||||
documents.forEach {
|
||||
when (NativeLibrary.installFileToNand(it.toString())) {
|
||||
NativeLibrary.InstallFileToNandResult.Success -> {
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
R.string.install_game_content_success,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
installSuccess += 1
|
||||
}
|
||||
|
||||
NativeLibrary.InstallFileToNandResult.SuccessFileOverwritten -> {
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
R.string.install_game_content_success_overwrite,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
installOverwrite += 1
|
||||
}
|
||||
|
||||
NativeLibrary.InstallFileToNandResult.ErrorBaseGame -> {
|
||||
MessageDialogFragment.newInstance(
|
||||
R.string.install_game_content_failure,
|
||||
R.string.install_game_content_failure_base
|
||||
).show(supportFragmentManager, MessageDialogFragment.TAG)
|
||||
errorBaseGame += 1
|
||||
}
|
||||
|
||||
NativeLibrary.InstallFileToNandResult.ErrorFilenameExtension -> {
|
||||
MessageDialogFragment.newInstance(
|
||||
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)
|
||||
errorExtension += 1
|
||||
}
|
||||
|
||||
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_description,
|
||||
installResult.toString().trim(),
|
||||
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 result
|
||||
return@newInstance installSuccess + installOverwrite + errorTotal
|
||||
}.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import android.content.Context
|
|||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import android.provider.DocumentsContract
|
||||
import android.provider.OpenableColumns
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.File
|
||||
|
@ -185,19 +184,18 @@ object FileUtil {
|
|||
|
||||
/**
|
||||
* Get file display name from given path
|
||||
* @param path content uri path
|
||||
* @param uri content uri
|
||||
* @return String display name
|
||||
*/
|
||||
fun getFilename(context: Context, path: String): String {
|
||||
val resolver = context.contentResolver
|
||||
fun getFilename(uri: Uri): String {
|
||||
val resolver = YuzuApplication.appContext.contentResolver
|
||||
val columns = arrayOf(
|
||||
DocumentsContract.Document.COLUMN_DISPLAY_NAME
|
||||
)
|
||||
var filename = ""
|
||||
var c: Cursor? = null
|
||||
try {
|
||||
val mUri = Uri.parse(path)
|
||||
c = resolver.query(mUri, columns, null, null, null)
|
||||
c = resolver.query(uri, columns, null, null, null)
|
||||
c!!.moveToNext()
|
||||
filename = c.getString(0)
|
||||
} catch (e: Exception) {
|
||||
|
@ -326,25 +324,9 @@ object FileUtil {
|
|||
}
|
||||
}
|
||||
|
||||
fun hasExtension(path: String, extension: String): Boolean =
|
||||
path.substring(path.lastIndexOf(".") + 1).contains(extension)
|
||||
|
||||
fun hasExtension(uri: Uri, extension: String): Boolean {
|
||||
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)
|
||||
fun getExtension(uri: Uri): String {
|
||||
val fileName = getFilename(uri)
|
||||
return fileName.substring(fileName.lastIndexOf(".") + 1)
|
||||
.lowercase()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ package org.yuzu.yuzu_emu.utils
|
|||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import androidx.preference.PreferenceManager
|
||||
import java.util.*
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
|
@ -33,15 +32,9 @@ object GameHelper {
|
|||
val children = FileUtil.listFiles(context, gamesUri)
|
||||
for (file in children) {
|
||||
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.
|
||||
if (Game.extensions.contains(fileExtension.lowercase(Locale.getDefault()))) {
|
||||
games.add(getGame(filename))
|
||||
}
|
||||
if (Game.extensions.contains(FileUtil.getExtension(file.uri))) {
|
||||
games.add(getGame(file.uri))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -59,21 +52,19 @@ object GameHelper {
|
|||
return games.toList()
|
||||
}
|
||||
|
||||
private fun getGame(filePath: String): Game {
|
||||
private fun getGame(uri: Uri): Game {
|
||||
val filePath = uri.toString()
|
||||
var name = NativeLibrary.getTitle(filePath)
|
||||
|
||||
// If the game's title field is empty, use the filename.
|
||||
if (name.isEmpty()) {
|
||||
name = filePath.substring(filePath.lastIndexOf("/") + 1)
|
||||
name = FileUtil.getFilename(uri)
|
||||
}
|
||||
var gameId = NativeLibrary.getGameId(filePath)
|
||||
|
||||
// If the game's ID field is empty, use the filename without extension.
|
||||
if (gameId.isEmpty()) {
|
||||
gameId = filePath.substring(
|
||||
filePath.lastIndexOf("/") + 1,
|
||||
filePath.lastIndexOf(".")
|
||||
)
|
||||
gameId = name.substring(0, name.lastIndexOf("."))
|
||||
}
|
||||
|
||||
val newGame = Game(
|
||||
|
|
|
@ -113,6 +113,8 @@ object GpuDriverHelper {
|
|||
initializeDriverParameters(context)
|
||||
}
|
||||
|
||||
external fun supportsCustomDriverLoading(): Boolean
|
||||
|
||||
// Parse the custom driver metadata to retrieve the name.
|
||||
val customDriverName: String?
|
||||
get() {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -14,7 +14,6 @@ add_library(yuzu-android SHARED
|
|||
id_cache.cpp
|
||||
id_cache.h
|
||||
native.cpp
|
||||
native.h
|
||||
)
|
||||
|
||||
set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR})
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include <android/api-level.h>
|
||||
#include <android/native_window_jni.h>
|
||||
#include <core/loader/nro.h>
|
||||
#include <jni.h>
|
||||
|
||||
#include "common/detached_tasks.h"
|
||||
#include "common/dynamic_library.h"
|
||||
|
@ -59,6 +60,9 @@
|
|||
#include "video_core/rasterizer_interface.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
|
||||
#define jconst [[maybe_unused]] const auto
|
||||
#define jauto [[maybe_unused]] auto
|
||||
|
||||
namespace {
|
||||
|
||||
class EmulationSession final {
|
||||
|
@ -98,7 +102,7 @@ public:
|
|||
}
|
||||
|
||||
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) {
|
||||
if (src == nullptr || dest == nullptr) {
|
||||
return false;
|
||||
|
@ -108,10 +112,10 @@ public:
|
|||
}
|
||||
|
||||
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()) {
|
||||
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);
|
||||
}
|
||||
return true;
|
||||
|
@ -128,14 +132,14 @@ public:
|
|||
m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
|
||||
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")) {
|
||||
nsp = std::make_shared<FileSys::NSP>(m_vfs->OpenFile(filename, FileSys::Mode::Read));
|
||||
if (nsp->IsExtractedType()) {
|
||||
return InstallError;
|
||||
}
|
||||
} else if (filename.ends_with("xci")) {
|
||||
const auto xci =
|
||||
jconst xci =
|
||||
std::make_shared<FileSys::XCI>(m_vfs->OpenFile(filename, FileSys::Mode::Read));
|
||||
nsp = xci->GetSecurePartitionNSP();
|
||||
} else {
|
||||
|
@ -150,7 +154,7 @@ public:
|
|||
return InstallError;
|
||||
}
|
||||
|
||||
const auto res = m_system.GetFileSystemController().GetUserNANDContents()->InstallEntry(
|
||||
jconst res = m_system.GetFileSystemController().GetUserNANDContents()->InstallEntry(
|
||||
*nsp, true, copy_func);
|
||||
|
||||
switch (res) {
|
||||
|
@ -233,10 +237,11 @@ public:
|
|||
m_system.SetFilesystem(m_vfs);
|
||||
|
||||
// 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_system.SetShuttingDown(false);
|
||||
m_system.ApplySettings();
|
||||
Settings::LogSettings();
|
||||
m_system.HIDCore().ReloadInputDevices();
|
||||
m_system.SetAppletFrontendSet({
|
||||
nullptr, // Amiibo Settings
|
||||
|
@ -330,7 +335,7 @@ public:
|
|||
|
||||
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),
|
||||
[&]() { return !m_is_running; })) {
|
||||
// Emulation halted.
|
||||
|
@ -362,7 +367,7 @@ public:
|
|||
}
|
||||
|
||||
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) {
|
||||
return false;
|
||||
|
@ -375,17 +380,17 @@ public:
|
|||
return !Settings::values.use_docked_mode.GetValue();
|
||||
}
|
||||
|
||||
void SetDeviceType(int index, int type) {
|
||||
auto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
|
||||
void SetDeviceType([[maybe_unused]] int index, int type) {
|
||||
jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
|
||||
controller->SetNpadStyleIndex(static_cast<Core::HID::NpadStyleIndex>(type));
|
||||
}
|
||||
|
||||
void OnGamepadConnectEvent(int index) {
|
||||
auto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
|
||||
void OnGamepadConnectEvent([[maybe_unused]] int index) {
|
||||
jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
|
||||
|
||||
// Ensure that player1 is configured correctly and handheld disconnected
|
||||
if (controller->GetNpadIdType() == Core::HID::NpadIdType::Player1) {
|
||||
auto handheld =
|
||||
jauto handheld =
|
||||
m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
|
||||
|
||||
if (controller->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::Handheld) {
|
||||
|
@ -397,7 +402,8 @@ public:
|
|||
|
||||
// Ensure that handheld is configured correctly and player 1 disconnected
|
||||
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) {
|
||||
player1->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
|
||||
|
@ -411,8 +417,8 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
void OnGamepadDisconnectEvent(int index) {
|
||||
auto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
|
||||
void OnGamepadDisconnectEvent([[maybe_unused]] int index) {
|
||||
jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
|
||||
controller->Disconnect();
|
||||
}
|
||||
|
||||
|
@ -428,7 +434,7 @@ private:
|
|||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -436,14 +442,14 @@ private:
|
|||
}
|
||||
|
||||
RomMetadata CacheRomMetadata(const std::string& path) {
|
||||
const auto file = Core::GetGameFileFromPath(m_vfs, path);
|
||||
auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0);
|
||||
jconst file = Core::GetGameFileFromPath(m_vfs, path);
|
||||
jauto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0);
|
||||
|
||||
RomMetadata entry;
|
||||
loader->ReadTitle(entry.title);
|
||||
loader->ReadIcon(entry.icon);
|
||||
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();
|
||||
} else {
|
||||
entry.isHomebrew = false;
|
||||
|
@ -514,7 +520,7 @@ static Core::SystemResultStatus RunEmulation(const std::string& filepath) {
|
|||
|
||||
SCOPE_EXIT({ EmulationSession::GetInstance().ShutdownEmulation(); });
|
||||
|
||||
const auto result = EmulationSession::GetInstance().InitializeEmulation(filepath);
|
||||
jconst result = EmulationSession::GetInstance().InitializeEmulation(filepath);
|
||||
if (result != Core::SystemResultStatus::Success) {
|
||||
return result;
|
||||
}
|
||||
|
@ -526,83 +532,104 @@ static Core::SystemResultStatus RunEmulation(const std::string& filepath) {
|
|||
|
||||
extern "C" {
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceChanged(JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
jobject surf) {
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceChanged(JNIEnv* env, jobject instance,
|
||||
[[maybe_unused]] jobject surf) {
|
||||
EmulationSession::GetInstance().SetNativeWindow(ANativeWindow_fromSurface(env, surf));
|
||||
EmulationSession::GetInstance().SurfaceChanged();
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceDestroyed(JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceDestroyed(JNIEnv* env, jobject instance) {
|
||||
ANativeWindow_release(EmulationSession::GetInstance().NativeWindow());
|
||||
EmulationSession::GetInstance().SetNativeWindow(nullptr);
|
||||
EmulationSession::GetInstance().SurfaceChanged();
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
jstring j_directory) {
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env, jobject instance,
|
||||
[[maybe_unused]] jstring j_directory) {
|
||||
Common::FS::SetAppDirectory(GetJString(env, j_directory));
|
||||
}
|
||||
|
||||
int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
jstring j_file) {
|
||||
int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env, jobject instance,
|
||||
[[maybe_unused]] jstring j_file) {
|
||||
return EmulationSession::GetInstance().InstallFileToNand(GetJString(env, j_file));
|
||||
}
|
||||
|
||||
void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeGpuDriver(
|
||||
JNIEnv* env, [[maybe_unused]] jclass clazz, jstring hook_lib_dir, jstring custom_driver_dir,
|
||||
jstring custom_driver_name, jstring file_redirect_dir) {
|
||||
void JNICALL 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) {
|
||||
EmulationSession::GetInstance().InitializeGpuDriver(
|
||||
GetJString(env, hook_lib_dir), GetJString(env, custom_driver_dir),
|
||||
GetJString(env, custom_driver_name), GetJString(env, file_redirect_dir));
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadKeys(JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
[[maybe_unused]] static bool CheckKgslPresent() {
|
||||
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();
|
||||
return static_cast<jboolean>(Core::Crypto::KeyManager::Instance().AreKeysLoaded());
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_unPauseEmulation([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_unpauseEmulation(JNIEnv* env, jclass clazz) {
|
||||
EmulationSession::GetInstance().UnPauseEmulation();
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_pauseEmulation([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_pauseEmulation(JNIEnv* env, jclass clazz) {
|
||||
EmulationSession::GetInstance().PauseEmulation();
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_stopEmulation([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_stopEmulation(JNIEnv* env, jclass clazz) {
|
||||
EmulationSession::GetInstance().HaltEmulation();
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_resetRomMetadata([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_resetRomMetadata(JNIEnv* env, jclass clazz) {
|
||||
EmulationSession::GetInstance().ResetRomMetadata();
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isRunning([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isRunning(JNIEnv* env, jclass clazz) {
|
||||
return static_cast<jboolean>(EmulationSession::GetInstance().IsRunning());
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isPaused([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isPaused(JNIEnv* env, jclass clazz) {
|
||||
return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused());
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_muteAduio(JNIEnv* env, 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();
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_setDeviceType([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_setDeviceType(JNIEnv* env, jclass clazz,
|
||||
jint j_device, jint j_type) {
|
||||
if (EmulationSession::GetInstance().IsRunning()) {
|
||||
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);
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadConnectEvent([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadConnectEvent(JNIEnv* env, jclass clazz,
|
||||
jint j_device) {
|
||||
if (EmulationSession::GetInstance().IsRunning()) {
|
||||
EmulationSession::GetInstance().OnGamepadConnectEvent(j_device);
|
||||
|
@ -619,17 +645,16 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadConnectEvent([[maybe_unu
|
|||
return static_cast<jboolean>(true);
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadDisconnectEvent(
|
||||
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jint j_device) {
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadDisconnectEvent(JNIEnv* env, jclass clazz,
|
||||
jint j_device) {
|
||||
if (EmulationSession::GetInstance().IsRunning()) {
|
||||
EmulationSession::GetInstance().OnGamepadDisconnectEvent(j_device);
|
||||
}
|
||||
return static_cast<jboolean>(true);
|
||||
}
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadButtonEvent([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
[[maybe_unused]] jint j_device,
|
||||
jint j_button, jint action) {
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadButtonEvent(JNIEnv* env, jclass clazz,
|
||||
jint j_device, jint j_button,
|
||||
jint action) {
|
||||
if (EmulationSession::GetInstance().IsRunning()) {
|
||||
// Ensure gamepad is connected
|
||||
EmulationSession::GetInstance().OnGamepadConnectEvent(j_device);
|
||||
|
@ -639,8 +664,7 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadButtonEvent([[maybe_unus
|
|||
return static_cast<jboolean>(true);
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadJoystickEvent([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadJoystickEvent(JNIEnv* env, jclass clazz,
|
||||
jint j_device, jint stick_id,
|
||||
jfloat x, jfloat y) {
|
||||
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(
|
||||
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jint j_device,
|
||||
jlong delta_timestamp, jfloat gyro_x, jfloat gyro_y, jfloat gyro_z, jfloat accel_x,
|
||||
jfloat accel_y, jfloat accel_z) {
|
||||
JNIEnv* env, jclass clazz, jint j_device, jlong delta_timestamp, jfloat gyro_x, jfloat gyro_y,
|
||||
jfloat gyro_z, jfloat accel_x, jfloat accel_y, jfloat accel_z) {
|
||||
if (EmulationSession::GetInstance().IsRunning()) {
|
||||
EmulationSession::GetInstance().Window().OnGamepadMotionEvent(
|
||||
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);
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onReadNfcTag([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onReadNfcTag(JNIEnv* env, jclass clazz,
|
||||
jbyteArray j_data) {
|
||||
jboolean isCopy{false};
|
||||
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);
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onRemoveNfcTag([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onRemoveNfcTag(JNIEnv* env, jclass clazz) {
|
||||
if (EmulationSession::GetInstance().IsRunning()) {
|
||||
EmulationSession::GetInstance().Window().OnRemoveNfcTag();
|
||||
}
|
||||
return static_cast<jboolean>(true);
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchPressed([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz, jint id,
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchPressed(JNIEnv* env, jclass clazz, jint id,
|
||||
jfloat x, jfloat y) {
|
||||
if (EmulationSession::GetInstance().IsRunning()) {
|
||||
EmulationSession::GetInstance().Window().OnTouchPressed(id, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchMoved([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz, jint id,
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchMoved(JNIEnv* env, jclass clazz, jint id,
|
||||
jfloat x, jfloat y) {
|
||||
if (EmulationSession::GetInstance().IsRunning()) {
|
||||
EmulationSession::GetInstance().Window().OnTouchMoved(id, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchReleased([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz, jint id) {
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchReleased(JNIEnv* env, jclass clazz, jint id) {
|
||||
if (EmulationSession::GetInstance().IsRunning()) {
|
||||
EmulationSession::GetInstance().Window().OnTouchReleased(id);
|
||||
}
|
||||
}
|
||||
|
||||
jbyteArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getIcon([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
[[maybe_unused]] jstring j_filename) {
|
||||
auto icon_data = EmulationSession::GetInstance().GetRomIcon(GetJString(env, j_filename));
|
||||
jbyteArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getIcon(JNIEnv* env, jclass clazz,
|
||||
jstring j_filename) {
|
||||
jauto icon_data = EmulationSession::GetInstance().GetRomIcon(GetJString(env, j_filename));
|
||||
jbyteArray icon = env->NewByteArray(static_cast<jsize>(icon_data.size()));
|
||||
env->SetByteArrayRegion(icon, 0, env->GetArrayLength(icon),
|
||||
reinterpret_cast<jbyte*>(icon_data.data()));
|
||||
return icon;
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getTitle([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
[[maybe_unused]] jstring j_filename) {
|
||||
auto title = EmulationSession::GetInstance().GetRomTitle(GetJString(env, j_filename));
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getTitle(JNIEnv* env, jclass clazz,
|
||||
jstring j_filename) {
|
||||
jauto title = EmulationSession::GetInstance().GetRomTitle(GetJString(env, j_filename));
|
||||
return env->NewStringUTF(title.c_str());
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getDescription([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getDescription(JNIEnv* env, jclass clazz,
|
||||
jstring j_filename) {
|
||||
return j_filename;
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getGameId([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getGameId(JNIEnv* env, jclass clazz,
|
||||
jstring j_filename) {
|
||||
return j_filename;
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getRegions([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
[[maybe_unused]] jstring j_filename) {
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getRegions(JNIEnv* env, jclass clazz,
|
||||
jstring j_filename) {
|
||||
return env->NewStringUTF("");
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCompany([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
[[maybe_unused]] jstring j_filename) {
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCompany(JNIEnv* env, jclass clazz,
|
||||
jstring j_filename) {
|
||||
return env->NewStringUTF("");
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHomebrew([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
[[maybe_unused]] jstring j_filename) {
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHomebrew(JNIEnv* env, jclass clazz,
|
||||
jstring j_filename) {
|
||||
return EmulationSession::GetInstance().GetIsHomebrew(GetJString(env, j_filename));
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmulation
|
||||
[[maybe_unused]] (JNIEnv* env, [[maybe_unused]] jclass clazz) {
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmulation(JNIEnv* env, jclass clazz) {
|
||||
// Create the default config.ini.
|
||||
Config{};
|
||||
// Initialize the emulated system.
|
||||
EmulationSession::GetInstance().System().Initialize();
|
||||
}
|
||||
|
||||
jint Java_org_yuzu_yuzu_1emu_NativeLibrary_defaultCPUCore([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
jint Java_org_yuzu_yuzu_1emu_NativeLibrary_defaultCPUCore(JNIEnv* env, jclass clazz) {
|
||||
return {};
|
||||
}
|
||||
|
||||
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,
|
||||
[[maybe_unused]] jstring j_savestate, [[maybe_unused]] jboolean j_delete_savestate) {}
|
||||
JNIEnv* env, jclass clazz, jstring j_file, jstring j_savestate, jboolean j_delete_savestate) {}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadSettings([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadSettings(JNIEnv* env, jclass clazz) {
|
||||
Config{};
|
||||
}
|
||||
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getUserSetting([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getUserSetting(JNIEnv* env, jclass clazz,
|
||||
jstring j_game_id, jstring j_section,
|
||||
jstring j_key) {
|
||||
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("");
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setUserSetting([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setUserSetting(JNIEnv* env, jclass clazz,
|
||||
jstring j_game_id, jstring j_section,
|
||||
jstring j_key, jstring j_value) {
|
||||
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());
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_initGameIni([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_initGameIni(JNIEnv* env, jclass clazz,
|
||||
jstring j_game_id) {
|
||||
std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);
|
||||
|
||||
env->ReleaseStringUTFChars(j_game_id, game_id.data());
|
||||
}
|
||||
|
||||
jdoubleArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPerfStats([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
jdoubleArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPerfStats(JNIEnv* env, jclass clazz) {
|
||||
jdoubleArray j_stats = env->NewDoubleArray(4);
|
||||
|
||||
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
|
||||
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;
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_utils_DirectoryInitialization_setSysDirectory(
|
||||
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jstring j_path) {}
|
||||
void Java_org_yuzu_yuzu_1emu_utils_DirectoryInitialization_setSysDirectory(JNIEnv* env,
|
||||
jclass clazz,
|
||||
jstring j_path) {}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_run__Ljava_lang_String_2([[maybe_unused]] JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_run__Ljava_lang_String_2(JNIEnv* env, jclass clazz,
|
||||
jstring 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,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_logDeviceInfo(JNIEnv* env, jclass clazz) {
|
||||
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());
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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>
|
|
@ -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>
|
|
@ -104,12 +104,14 @@
|
|||
<string name="share_log_missing">No log file found</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_failure">Error installing file 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_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_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_success">Game content installed successfully</string>
|
||||
<string name="install_game_content_success_overwrite">Game content was overwritten successfully</string>
|
||||
<string name="install_game_content_failure">Error installing file(s) to NAND</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.</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_failed_count">%1$d installation error(s)</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>
|
||||
|
||||
<!-- About screen strings -->
|
||||
|
@ -270,6 +272,7 @@
|
|||
<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="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 -->
|
||||
<string name="region_japan">Japan</string>
|
||||
|
@ -300,6 +303,15 @@
|
|||
<string name="language_traditional_chinese">Traditional Chinese (正體中文)</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 -->
|
||||
<string name="renderer_vulkan">Vulkan</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="pause">Pause</string>
|
||||
<string name="play">Play</string>
|
||||
<string name="mute">Mute</string>
|
||||
<string name="unmute">Unmute</string>
|
||||
|
||||
<!-- Licenses screen strings -->
|
||||
<string name="licenses">Licenses</string>
|
||||
|
|
|
@ -15,3 +15,6 @@ android.useAndroidX=true
|
|||
kotlin.code.style=official
|
||||
kotlin.parallel.tasks.in.project=true
|
||||
android.defaults.buildfeatures.buildconfig=true
|
||||
|
||||
# Android Gradle plugin 8.0.2
|
||||
android.suppressUnsupportedCompileSdk=34
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <mutex>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
#include <boost/container/static_vector.hpp>
|
||||
|
||||
#include "audio_buffer.h"
|
||||
#include "audio_core/device/device_session.h"
|
||||
|
@ -48,7 +49,7 @@ public:
|
|||
*
|
||||
* @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};
|
||||
const s32 to_register{std::min(std::min(appended_count, BufferAppendLimit),
|
||||
BufferAppendLimit - registered_count)};
|
||||
|
@ -162,7 +163,8 @@ public:
|
|||
* @param max_buffers - Maximum number of buffers to 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};
|
||||
if (registered_count + appended_count == 0) {
|
||||
return 0;
|
||||
|
@ -270,7 +272,7 @@ public:
|
|||
*/
|
||||
bool FlushBuffers(u32& buffers_released) {
|
||||
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);
|
||||
|
||||
|
|
|
@ -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) {
|
||||
Sink::SinkBuffer new_buffer{
|
||||
.frames = buffer.size / (channel_count * sizeof(s16)),
|
||||
|
@ -88,13 +88,13 @@ void DeviceSession::AppendBuffers(std::span<const AudioBuffer> buffers) const {
|
|||
.consumed = false,
|
||||
};
|
||||
|
||||
tmp_samples.resize_destructive(buffer.size / sizeof(s16));
|
||||
if (type == Sink::StreamType::In) {
|
||||
std::vector<s16> samples{};
|
||||
stream->AppendBuffer(new_buffer, samples);
|
||||
stream->AppendBuffer(new_buffer, tmp_samples);
|
||||
} else {
|
||||
std::vector<s16> samples(buffer.size / sizeof(s16));
|
||||
system.ApplicationMemory().ReadBlockUnsafe(buffer.samples, samples.data(), buffer.size);
|
||||
stream->AppendBuffer(new_buffer, samples);
|
||||
system.ApplicationMemory().ReadBlockUnsafe(buffer.samples, tmp_samples.data(),
|
||||
buffer.size);
|
||||
stream->AppendBuffer(new_buffer, tmp_samples);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "audio_core/sink/sink.h"
|
||||
#include "common/scratch_buffer.h"
|
||||
#include "core/hle/service/audio/errors.h"
|
||||
|
||||
namespace Core {
|
||||
|
@ -62,7 +63,7 @@ public:
|
|||
*
|
||||
* @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.
|
||||
|
@ -146,8 +147,8 @@ private:
|
|||
std::shared_ptr<Core::Timing::EventType> thread_event;
|
||||
/// Is this session initialised?
|
||||
bool initialized{};
|
||||
/// Buffer queue
|
||||
std::vector<AudioBuffer> buffer_queue{};
|
||||
/// Temporary sample buffer
|
||||
Common::ScratchBuffer<s16> tmp_samples{};
|
||||
};
|
||||
|
||||
} // namespace AudioCore
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include "audio_core/audio_event.h"
|
||||
#include "audio_core/audio_manager.h"
|
||||
#include "audio_core/in/audio_in_system.h"
|
||||
|
@ -89,7 +90,7 @@ Result System::Start() {
|
|||
session->Start();
|
||||
state = State::Started;
|
||||
|
||||
std::vector<AudioBuffer> buffers_to_flush{};
|
||||
boost::container::static_vector<AudioBuffer, BufferCount> buffers_to_flush{};
|
||||
buffers.RegisterBuffers(buffers_to_flush);
|
||||
session->AppendBuffers(buffers_to_flush);
|
||||
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() {
|
||||
if (state == State::Started) {
|
||||
std::vector<AudioBuffer> registered_buffers{};
|
||||
boost::container::static_vector<AudioBuffer, BufferCount> registered_buffers{};
|
||||
buffers.RegisterBuffers(registered_buffers);
|
||||
session->AppendBuffers(registered_buffers);
|
||||
}
|
||||
|
|
|
@ -89,7 +89,7 @@ Result System::Start() {
|
|||
session->Start();
|
||||
state = State::Started;
|
||||
|
||||
std::vector<AudioBuffer> buffers_to_flush{};
|
||||
boost::container::static_vector<AudioBuffer, BufferCount> buffers_to_flush{};
|
||||
buffers.RegisterBuffers(buffers_to_flush);
|
||||
session->AppendBuffers(buffers_to_flush);
|
||||
session->SetRingSize(static_cast<u32>(buffers_to_flush.size()));
|
||||
|
@ -134,7 +134,7 @@ bool System::AppendBuffer(const AudioOutBuffer& buffer, u64 tag) {
|
|||
|
||||
void System::RegisterBuffers() {
|
||||
if (state == State::Started) {
|
||||
std::vector<AudioBuffer> registered_buffers{};
|
||||
boost::container::static_vector<AudioBuffer, BufferCount> registered_buffers{};
|
||||
buffers.RegisterBuffers(registered_buffers);
|
||||
session->AppendBuffers(registered_buffers);
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer::ADSP {
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
#include "common/thread.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
|
||||
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);
|
||||
|
||||
// 0.12 seconds (2304000 / 19200000)
|
||||
constexpr u64 max_process_time{2'304'000ULL};
|
||||
|
||||
while (!stop_token.stop_requested()) {
|
||||
|
@ -184,8 +184,7 @@ void AudioRenderer::ThreadFunc(std::stop_token stop_token) {
|
|||
u64 max_time{max_process_time};
|
||||
if (index == 1 && command_buffer.applet_resource_user_id ==
|
||||
mailbox->GetCommandBuffer(0).applet_resource_user_id) {
|
||||
max_time = max_process_time -
|
||||
Core::Timing::CyclesToNs(render_times_taken[0]).count();
|
||||
max_time = max_process_time - render_times_taken[0];
|
||||
if (render_times_taken[0] > max_process_time) {
|
||||
max_time = 0;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer::ADSP {
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "audio_core/renderer/command/resample/resample.h"
|
||||
#include "common/fixed_point.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/scratch_buffer.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
@ -27,6 +28,7 @@ constexpr std::array<u8, 3> PitchBySrcQuality = {4, 8, 4};
|
|||
template <typename T>
|
||||
static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
const DecodeArg& req) {
|
||||
std::array<T, TempBufferSize> tmp_samples{};
|
||||
constexpr s32 min{std::numeric_limits<s16>::min()};
|
||||
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_bytes{size * sizeof(T)};
|
||||
|
||||
std::vector<T> samples(size);
|
||||
memory.ReadBlockUnsafe(source, samples.data(), size_bytes);
|
||||
memory.ReadBlockUnsafe(source, tmp_samples.data(), size_bytes);
|
||||
|
||||
if constexpr (std::is_floating_point_v<T>) {
|
||||
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())};
|
||||
out_buffer[i] = static_cast<s16>(std::clamp(sample, min, max));
|
||||
}
|
||||
} else {
|
||||
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;
|
||||
|
@ -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))};
|
||||
std::vector<T> samples(samples_to_decode);
|
||||
memory.ReadBlockUnsafe(source, samples.data(), samples_to_decode * sizeof(T));
|
||||
memory.ReadBlockUnsafe(source, tmp_samples.data(), samples_to_decode * sizeof(T));
|
||||
|
||||
if constexpr (std::is_floating_point_v<T>) {
|
||||
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())};
|
||||
out_buffer[i] = static_cast<s16>(std::clamp(sample, min, max));
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
|
@ -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,
|
||||
const DecodeArg& req) {
|
||||
std::array<u8, TempBufferSize> wavebuffer{};
|
||||
constexpr u32 SamplesPerFrame{14};
|
||||
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)};
|
||||
std::vector<u8> wavebuffer(size);
|
||||
memory.ReadBlockUnsafe(req.buffer + position_in_frame / 2, wavebuffer.data(),
|
||||
wavebuffer.size());
|
||||
memory.ReadBlockUnsafe(req.buffer + position_in_frame / 2, wavebuffer.data(), size);
|
||||
|
||||
auto context{req.adpcm_context};
|
||||
auto header{context->header};
|
||||
|
@ -258,7 +257,7 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
|
|||
u32 offset{voice_state.offset};
|
||||
|
||||
auto output_buffer{args.output};
|
||||
std::vector<s16> temp_buffer(TempBufferSize, 0);
|
||||
std::array<s16, TempBufferSize> temp_buffer{};
|
||||
|
||||
while (remaining_sample_count > 0) {
|
||||
const auto samples_to_write{std::min(remaining_sample_count, max_remaining_sample_count)};
|
||||
|
|
|
@ -44,8 +44,8 @@ static void InitializeCompressorEffect(const CompressorInfo::ParameterVersion2&
|
|||
|
||||
static void ApplyCompressorEffect(const CompressorInfo::ParameterVersion2& params,
|
||||
CompressorInfo::State& state, bool enabled,
|
||||
std::vector<std::span<const s32>> input_buffers,
|
||||
std::vector<std::span<s32>> output_buffers, u32 sample_count) {
|
||||
std::span<std::span<const s32>> input_buffers,
|
||||
std::span<std::span<s32>> output_buffers, u32 sample_count) {
|
||||
if (enabled) {
|
||||
auto state_00{state.unk_00};
|
||||
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) {
|
||||
std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
|
||||
std::vector<std::span<s32>> output_buffers(parameter.channel_count);
|
||||
std::array<std::span<const s32>, MaxChannels> input_buffers{};
|
||||
std::array<std::span<s32>, MaxChannels> output_buffers{};
|
||||
|
||||
for (s16 i = 0; i < parameter.channel_count; i++) {
|
||||
input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
|
||||
|
|
|
@ -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 = sample_count.to_int_floor();
|
||||
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_pos = 0;
|
||||
|
@ -74,8 +74,8 @@ static void InitializeDelayEffect(const DelayInfo::ParameterVersion1& params,
|
|||
*/
|
||||
template <size_t NumChannels>
|
||||
static void ApplyDelay(const DelayInfo::ParameterVersion1& params, DelayInfo::State& state,
|
||||
std::vector<std::span<const s32>>& inputs,
|
||||
std::vector<std::span<s32>>& outputs, const u32 sample_count) {
|
||||
std::span<std::span<const s32>> inputs, std::span<std::span<s32>> outputs,
|
||||
const u32 sample_count) {
|
||||
for (u32 sample_index = 0; sample_index < sample_count; sample_index++) {
|
||||
std::array<Common::FixedPoint<50, 14>, NumChannels> input_samples{};
|
||||
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.
|
||||
*/
|
||||
static void ApplyDelayEffect(const DelayInfo::ParameterVersion1& params, DelayInfo::State& state,
|
||||
const bool enabled, std::vector<std::span<const s32>>& inputs,
|
||||
std::vector<std::span<s32>>& outputs, const u32 sample_count) {
|
||||
const bool enabled, std::span<std::span<const s32>> inputs,
|
||||
std::span<std::span<s32>> outputs, const u32 sample_count) {
|
||||
|
||||
if (!IsChannelCountValid(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) {
|
||||
std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
|
||||
std::vector<std::span<s32>> output_buffers(parameter.channel_count);
|
||||
std::array<std::span<const s32>, MaxChannels> input_buffers{};
|
||||
std::array<std::span<s32>, MaxChannels> output_buffers{};
|
||||
|
||||
for (s16 i = 0; i < parameter.channel_count; i++) {
|
||||
input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
|
||||
|
|
|
@ -408,8 +408,8 @@ void I3dl2ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor&
|
|||
}
|
||||
|
||||
void I3dl2ReverbCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
|
||||
std::vector<std::span<s32>> output_buffers(parameter.channel_count);
|
||||
std::array<std::span<const s32>, MaxChannels> input_buffers{};
|
||||
std::array<std::span<s32>, MaxChannels> output_buffers{};
|
||||
|
||||
for (u32 i = 0; i < parameter.channel_count; i++) {
|
||||
input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
|
||||
|
|
|
@ -47,8 +47,8 @@ static void InitializeLightLimiterEffect(const LightLimiterInfo::ParameterVersio
|
|||
*/
|
||||
static void ApplyLightLimiterEffect(const LightLimiterInfo::ParameterVersion2& params,
|
||||
LightLimiterInfo::State& state, const bool enabled,
|
||||
std::vector<std::span<const s32>>& inputs,
|
||||
std::vector<std::span<s32>>& outputs, const u32 sample_count,
|
||||
std::span<std::span<const s32>> inputs,
|
||||
std::span<std::span<s32>> outputs, const u32 sample_count,
|
||||
LightLimiterInfo::StatisticsInternal* statistics) {
|
||||
constexpr s64 min{std::numeric_limits<s32>::min()};
|
||||
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) {
|
||||
std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
|
||||
std::vector<std::span<s32>> output_buffers(parameter.channel_count);
|
||||
std::array<std::span<const s32>, MaxChannels> input_buffers{};
|
||||
std::array<std::span<s32>, MaxChannels> output_buffers{};
|
||||
|
||||
for (u32 i = 0; i < parameter.channel_count; i++) {
|
||||
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) {
|
||||
std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
|
||||
std::vector<std::span<s32>> output_buffers(parameter.channel_count);
|
||||
std::array<std::span<const s32>, MaxChannels> input_buffers{};
|
||||
std::array<std::span<s32>, MaxChannels> output_buffers{};
|
||||
|
||||
for (u32 i = 0; i < parameter.channel_count; i++) {
|
||||
input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
|
||||
|
|
|
@ -250,8 +250,8 @@ static Common::FixedPoint<50, 14> Axfx2AllPassTick(ReverbInfo::ReverbDelayLine&
|
|||
*/
|
||||
template <size_t NumChannels>
|
||||
static void ApplyReverbEffect(const ReverbInfo::ParameterVersion2& params, ReverbInfo::State& state,
|
||||
std::vector<std::span<const s32>>& inputs,
|
||||
std::vector<std::span<s32>>& outputs, const u32 sample_count) {
|
||||
std::span<std::span<const s32>> inputs,
|
||||
std::span<std::span<s32>> outputs, const u32 sample_count) {
|
||||
static constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes1Ch{
|
||||
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.
|
||||
*/
|
||||
static void ApplyReverbEffect(const ReverbInfo::ParameterVersion2& params, ReverbInfo::State& state,
|
||||
const bool enabled, std::vector<std::span<const s32>>& inputs,
|
||||
std::vector<std::span<s32>>& outputs, const u32 sample_count) {
|
||||
const bool enabled, std::span<std::span<const s32>> inputs,
|
||||
std::span<std::span<s32>> outputs, const u32 sample_count) {
|
||||
if (enabled) {
|
||||
switch (params.channel_count) {
|
||||
case 0:
|
||||
|
@ -412,8 +412,8 @@ void ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& proc
|
|||
}
|
||||
|
||||
void ReverbCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
|
||||
std::vector<std::span<s32>> output_buffers(parameter.channel_count);
|
||||
std::array<std::span<const s32>, MaxChannels> input_buffers{};
|
||||
std::array<std::span<s32>, MaxChannels> output_buffers{};
|
||||
|
||||
for (u32 i = 0; i < parameter.channel_count; i++) {
|
||||
input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
#include "audio_core/renderer/command/performance/performance.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
|
@ -18,20 +17,18 @@ void PerformanceCommand::Process(const ADSP::CommandListProcessor& processor) {
|
|||
auto base{entry_address.translated_address};
|
||||
if (state == PerformanceState::Start) {
|
||||
auto start_time_ptr{reinterpret_cast<u32*>(base + entry_address.entry_start_time_offset)};
|
||||
*start_time_ptr = static_cast<u32>(
|
||||
Core::Timing::CyclesToUs(processor.system->CoreTiming().GetClockTicks() -
|
||||
processor.start_time - processor.current_processing_time)
|
||||
.count());
|
||||
*start_time_ptr =
|
||||
static_cast<u32>(processor.system->CoreTiming().GetClockTicks() - processor.start_time -
|
||||
processor.current_processing_time);
|
||||
} else if (state == PerformanceState::Stop) {
|
||||
auto processed_time_ptr{
|
||||
reinterpret_cast<u32*>(base + entry_address.entry_processed_time_offset)};
|
||||
auto entry_count_ptr{
|
||||
reinterpret_cast<u32*>(base + entry_address.header_entry_count_offset)};
|
||||
|
||||
*processed_time_ptr = static_cast<u32>(
|
||||
Core::Timing::CyclesToUs(processor.system->CoreTiming().GetClockTicks() -
|
||||
processor.start_time - processor.current_processing_time)
|
||||
.count());
|
||||
*processed_time_ptr =
|
||||
static_cast<u32>(processor.system->CoreTiming().GetClockTicks() - processor.start_time -
|
||||
processor.current_processing_time);
|
||||
(*entry_count_ptr)++;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ void CircularBufferSinkCommand::Process(const ADSP::CommandListProcessor& proces
|
|||
constexpr s32 min{std::numeric_limits<s16>::min()};
|
||||
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++) {
|
||||
auto input{processor.mix_buffers.subspan(inputs[channel] * processor.sample_count,
|
||||
processor.sample_count)};
|
||||
|
@ -33,7 +33,7 @@ void CircularBufferSinkCommand::Process(const ADSP::CommandListProcessor& proces
|
|||
}
|
||||
|
||||
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));
|
||||
if (pos >= size) {
|
||||
pos = 0;
|
||||
|
|
|
@ -33,8 +33,7 @@ void DeviceSinkCommand::Process(const ADSP::CommandListProcessor& processor) {
|
|||
.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++) {
|
||||
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());
|
||||
stream->AppendBuffer(out_buffer, samples);
|
||||
stream->AppendBuffer(out_buffer, {samples.data(), out_buffer.frames * input_count});
|
||||
|
||||
if (stream->IsPaused()) {
|
||||
stream->Start();
|
||||
|
|
|
@ -125,10 +125,10 @@ bool MixContext::TSortInfo(const SplitterContext& splitter_context) {
|
|||
return false;
|
||||
}
|
||||
|
||||
std::vector<s32> sorted_results{node_states.GetSortedResuls()};
|
||||
const auto result_size{std::min(count, static_cast<s32>(sorted_results.size()))};
|
||||
auto sorted_results{node_states.GetSortedResuls()};
|
||||
const auto result_size{std::min(count, static_cast<s32>(sorted_results.second))};
|
||||
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();
|
||||
|
|
|
@ -134,8 +134,8 @@ u32 NodeStates::GetNodeCount() const {
|
|||
return node_count;
|
||||
}
|
||||
|
||||
std::vector<s32> NodeStates::GetSortedResuls() const {
|
||||
return {results.rbegin(), results.rbegin() + result_pos};
|
||||
std::pair<std::span<u32>::reverse_iterator, size_t> NodeStates::GetSortedResuls() const {
|
||||
return {results.rbegin(), result_pos};
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
|
|
@ -175,7 +175,7 @@ public:
|
|||
*
|
||||
* @return Vector of nodes in reverse order.
|
||||
*/
|
||||
std::vector<s32> GetSortedResuls() const;
|
||||
std::pair<std::span<u32>::reverse_iterator, size_t> GetSortedResuls() const;
|
||||
|
||||
private:
|
||||
/// Number of nodes in the graph
|
||||
|
|
|
@ -444,6 +444,7 @@ Result System::Update(std::span<const u8> input, std::span<u8> performance, std:
|
|||
std::scoped_lock l{lock};
|
||||
|
||||
const auto start_time{core.CoreTiming().GetClockTicks()};
|
||||
std::memset(output.data(), 0, output.size());
|
||||
|
||||
InfoUpdater info_updater(input, output, process_handle, behavior);
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ public:
|
|||
explicit NullSinkStreamImpl(Core::System& system_, StreamType type_)
|
||||
: SinkStream{system_, type_} {}
|
||||
~NullSinkStreamImpl() override {}
|
||||
void AppendBuffer(SinkBuffer&, std::vector<s16>&) override {}
|
||||
void AppendBuffer(SinkBuffer&, std::span<s16>) override {}
|
||||
std::vector<s16> ReleaseBuffer(u64) override {
|
||||
return {};
|
||||
}
|
||||
|
|
|
@ -15,11 +15,10 @@
|
|||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
|
||||
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) {
|
||||
queue.enqueue(buffer);
|
||||
queued_buffers++;
|
||||
|
@ -67,15 +66,16 @@ void SinkStream::AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples) {
|
|||
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) {
|
||||
// We need moar samples! Not all games will provide 6 channel audio.
|
||||
// TODO: Implement some upmixing here. Currently just passthrough, with other
|
||||
// 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) {
|
||||
const auto left_sample{static_cast<s16>(std::clamp(
|
||||
static_cast<s32>(
|
||||
|
@ -83,7 +83,7 @@ void SinkStream::AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples) {
|
|||
volume),
|
||||
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(
|
||||
static_cast<s32>(
|
||||
|
@ -91,9 +91,9 @@ void SinkStream::AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples) {
|
|||
volume),
|
||||
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) {
|
||||
for (u32 i = 0; i < samples.size(); i++) {
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "common/polyfill_thread.h"
|
||||
#include "common/reader_writer_queue.h"
|
||||
#include "common/ring_buffer.h"
|
||||
#include "common/scratch_buffer.h"
|
||||
#include "common/thread.h"
|
||||
|
||||
namespace Core {
|
||||
|
@ -170,7 +171,7 @@ public:
|
|||
* @param buffer - Audio buffer information to be queued.
|
||||
* @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.
|
||||
|
@ -255,6 +256,8 @@ private:
|
|||
/// Signalled when ring buffer entries are consumed
|
||||
std::condition_variable_any release_cv;
|
||||
std::mutex release_mutex;
|
||||
/// Temporary buffer for appending samples when upmixing
|
||||
Common::ScratchBuffer<s16> tmp_samples{};
|
||||
};
|
||||
|
||||
using SinkStreamPtr = std::unique_ptr<SinkStream>;
|
||||
|
|
|
@ -172,6 +172,8 @@ if(ARCHITECTURE_x86_64)
|
|||
x64/cpu_wait.h
|
||||
x64/native_clock.cpp
|
||||
x64/native_clock.h
|
||||
x64/rdtsc.cpp
|
||||
x64/rdtsc.h
|
||||
x64/xbyak_abi.h
|
||||
x64/xbyak_util.h
|
||||
)
|
||||
|
|
|
@ -436,7 +436,7 @@ void IterateDirEntries(const std::filesystem::path& path, const DirEntryCallable
|
|||
|
||||
if (True(filter & DirEntryFilter::File) &&
|
||||
entry.status().type() == fs::file_type::regular) {
|
||||
if (!callback(entry.path())) {
|
||||
if (!callback(entry)) {
|
||||
callback_error = true;
|
||||
break;
|
||||
}
|
||||
|
@ -444,7 +444,7 @@ void IterateDirEntries(const std::filesystem::path& path, const DirEntryCallable
|
|||
|
||||
if (True(filter & DirEntryFilter::Directory) &&
|
||||
entry.status().type() == fs::file_type::directory) {
|
||||
if (!callback(entry.path())) {
|
||||
if (!callback(entry)) {
|
||||
callback_error = true;
|
||||
break;
|
||||
}
|
||||
|
@ -493,7 +493,7 @@ void IterateDirEntriesRecursively(const std::filesystem::path& path,
|
|||
|
||||
if (True(filter & DirEntryFilter::File) &&
|
||||
entry.status().type() == fs::file_type::regular) {
|
||||
if (!callback(entry.path())) {
|
||||
if (!callback(entry)) {
|
||||
callback_error = true;
|
||||
break;
|
||||
}
|
||||
|
@ -501,7 +501,7 @@ void IterateDirEntriesRecursively(const std::filesystem::path& path,
|
|||
|
||||
if (True(filter & DirEntryFilter::Directory) &&
|
||||
entry.status().type() == fs::file_type::directory) {
|
||||
if (!callback(entry.path())) {
|
||||
if (!callback(entry)) {
|
||||
callback_error = true;
|
||||
break;
|
||||
}
|
||||
|
@ -605,6 +605,12 @@ fs::file_type GetEntryType(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;
|
||||
|
||||
const auto file_size = fs::file_size(path, ec);
|
||||
|
|
|
@ -66,6 +66,6 @@ DECLARE_ENUM_FLAG_OPERATORS(DirEntryFilter);
|
|||
* @returns A boolean value.
|
||||
* 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
|
||||
|
|
|
@ -75,8 +75,10 @@ enum class DriverResult {
|
|||
ErrorWritingData,
|
||||
NoDeviceDetected,
|
||||
InvalidHandle,
|
||||
InvalidParameters,
|
||||
NotSupported,
|
||||
Disabled,
|
||||
Delayed,
|
||||
Unknown,
|
||||
};
|
||||
|
||||
|
@ -86,7 +88,7 @@ enum class NfcState {
|
|||
NewAmiibo,
|
||||
WaitingForAmiibo,
|
||||
AmiiboRemoved,
|
||||
NotAnAmiibo,
|
||||
InvalidTagType,
|
||||
NotSupported,
|
||||
WrongDeviceState,
|
||||
WriteFailed,
|
||||
|
@ -218,8 +220,22 @@ struct CameraStatus {
|
|||
};
|
||||
|
||||
struct NfcStatus {
|
||||
NfcState state{};
|
||||
std::vector<u8> data{};
|
||||
NfcState state{NfcState::Unknown};
|
||||
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
|
||||
|
@ -294,7 +310,7 @@ struct CallbackStatus {
|
|||
BatteryStatus battery_status{};
|
||||
VibrationStatus vibration_status{};
|
||||
CameraFormat camera_status{CameraFormat::None};
|
||||
NfcState nfc_status{NfcState::Unknown};
|
||||
NfcStatus nfc_status{};
|
||||
std::vector<u8> raw_data{};
|
||||
};
|
||||
|
||||
|
@ -356,9 +372,30 @@ public:
|
|||
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) {
|
||||
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.
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <new>
|
||||
#include <span>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
|
@ -53,7 +54,7 @@ public:
|
|||
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());
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <iterator>
|
||||
|
||||
#include "common/concepts.h"
|
||||
#include "common/make_unique_for_overwrite.h"
|
||||
|
||||
namespace Common {
|
||||
|
@ -16,6 +19,12 @@ namespace Common {
|
|||
template <typename T>
|
||||
class ScratchBuffer {
|
||||
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;
|
||||
|
||||
explicit ScratchBuffer(size_t initial_capacity)
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <version>
|
||||
#if __cpp_lib_chrono >= 201907L
|
||||
#include <chrono>
|
||||
#include <exception>
|
||||
#include <stdexcept>
|
||||
#endif
|
||||
#include <string_view>
|
||||
|
||||
|
@ -25,9 +28,19 @@ std::string GetTimeZoneString() {
|
|||
if (time_zone_index == 0) { // Auto
|
||||
#if __cpp_lib_chrono >= 201907L
|
||||
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();
|
||||
std::string_view current_zone_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
|
||||
location_name = Common::TimeZone::FindSystemTimeZone();
|
||||
#endif
|
||||
|
|
|
@ -483,6 +483,7 @@ struct Values {
|
|||
AstcRecompression::Uncompressed, AstcRecompression::Uncompressed, AstcRecompression::Bc3,
|
||||
"astc_recompression"};
|
||||
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_green{0, "bg_green"};
|
||||
|
@ -524,9 +525,16 @@ struct Values {
|
|||
Setting<bool> tas_loop{false, "tas_loop"};
|
||||
|
||||
Setting<bool> mouse_panning{false, "mouse_panning"};
|
||||
Setting<u8, true> mouse_panning_sensitivity{50, 1, 100, "mouse_panning_sensitivity"};
|
||||
Setting<bool> mouse_enabled{false, "mouse_enabled"};
|
||||
Setting<u8, true> mouse_panning_x_sensitivity{50, 1, 100, "mouse_panning_x_sensitivity"};
|
||||
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> keyboard_enabled{false, "keyboard_enabled"};
|
||||
|
||||
|
|
|
@ -28,13 +28,12 @@ static s64 GetSystemTimeNS() {
|
|||
// GetSystemTimePreciseAsFileTime returns the file time in 100ns units.
|
||||
static constexpr s64 Multiplier = 100;
|
||||
// Convert Windows epoch to Unix epoch.
|
||||
static constexpr s64 WindowsEpochToUnixEpochNS = 0x19DB1DED53E8000LL;
|
||||
static constexpr s64 WindowsEpochToUnixEpoch = 0x19DB1DED53E8000LL;
|
||||
|
||||
FILETIME filetime;
|
||||
GetSystemTimePreciseAsFileTime(&filetime);
|
||||
return Multiplier * ((static_cast<s64>(filetime.dwHighDateTime) << 32) +
|
||||
static_cast<s64>(filetime.dwLowDateTime)) -
|
||||
WindowsEpochToUnixEpochNS;
|
||||
static_cast<s64>(filetime.dwLowDateTime) - WindowsEpochToUnixEpoch);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
|
@ -93,6 +93,7 @@ void AppendCPUInfo(FieldCollection& fc) {
|
|||
add_field("CPU_Extension_x64_GFNI", caps.gfni);
|
||||
add_field("CPU_Extension_x64_INVARIANT_TSC", caps.invariant_tsc);
|
||||
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_PCLMULQDQ", caps.pclmulqdq);
|
||||
add_field("CPU_Extension_x64_POPCNT", caps.popcnt);
|
||||
|
|
|
@ -55,7 +55,7 @@ public:
|
|||
is_set = false;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsSet() {
|
||||
[[nodiscard]] bool IsSet() const {
|
||||
return is_set;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,88 +2,75 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/steady_clock.h"
|
||||
#include "common/uint128.h"
|
||||
#include "common/wall_clock.h"
|
||||
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
#include "common/x64/cpu_detect.h"
|
||||
#include "common/x64/native_clock.h"
|
||||
#include "common/x64/rdtsc.h"
|
||||
#endif
|
||||
|
||||
namespace Common {
|
||||
|
||||
class StandardWallClock final : public WallClock {
|
||||
public:
|
||||
explicit StandardWallClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_)
|
||||
: WallClock{emulated_cpu_frequency_, emulated_clock_frequency_, false},
|
||||
start_time{SteadyClock::Now()} {}
|
||||
explicit StandardWallClock() : start_time{SteadyClock::Now()} {}
|
||||
|
||||
std::chrono::nanoseconds GetTimeNS() override {
|
||||
std::chrono::nanoseconds GetTimeNS() const override {
|
||||
return SteadyClock::Now() - start_time;
|
||||
}
|
||||
|
||||
std::chrono::microseconds GetTimeUS() override {
|
||||
return std::chrono::duration_cast<std::chrono::microseconds>(GetTimeNS());
|
||||
std::chrono::microseconds GetTimeUS() const override {
|
||||
return static_cast<std::chrono::microseconds>(GetHostTicksElapsed() / NsToUsRatio::den);
|
||||
}
|
||||
|
||||
std::chrono::milliseconds GetTimeMS() override {
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(GetTimeNS());
|
||||
std::chrono::milliseconds GetTimeMS() const override {
|
||||
return static_cast<std::chrono::milliseconds>(GetHostTicksElapsed() / NsToMsRatio::den);
|
||||
}
|
||||
|
||||
u64 GetClockCycles() override {
|
||||
const u128 temp = Common::Multiply64Into128(GetTimeNS().count(), emulated_clock_frequency);
|
||||
return Common::Divide128On32(temp, NS_RATIO).first;
|
||||
u64 GetCNTPCT() const override {
|
||||
return GetHostTicksElapsed() * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den;
|
||||
}
|
||||
|
||||
u64 GetCPUCycles() override {
|
||||
const u128 temp = Common::Multiply64Into128(GetTimeNS().count(), emulated_cpu_frequency);
|
||||
return Common::Divide128On32(temp, NS_RATIO).first;
|
||||
u64 GetGPUTick() const override {
|
||||
return GetHostTicksElapsed() * NsToGPUTickRatio::num / NsToGPUTickRatio::den;
|
||||
}
|
||||
|
||||
void Pause([[maybe_unused]] bool is_paused) override {
|
||||
// Do nothing in this clock type.
|
||||
u64 GetHostTicksNow() const override {
|
||||
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:
|
||||
SteadyClock::time_point start_time;
|
||||
};
|
||||
|
||||
std::unique_ptr<WallClock> CreateOptimalClock() {
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
|
||||
std::unique_ptr<WallClock> CreateBestMatchingClock(u64 emulated_cpu_frequency,
|
||||
u64 emulated_clock_frequency) {
|
||||
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:
|
||||
// - A nanosecond
|
||||
// - 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);
|
||||
if (caps.invariant_tsc && caps.tsc_frequency >= WallClock::GPUTickFreq) {
|
||||
return std::make_unique<X64::NativeClock>(caps.tsc_frequency);
|
||||
} else {
|
||||
return std::make_unique<X64::NativeClock>(emulated_cpu_frequency, emulated_clock_frequency,
|
||||
rtsc_frequency);
|
||||
// Fallback to StandardWallClock if the hardware TSC
|
||||
// - Is not invariant
|
||||
// - Is not more precise than GPUTickFreq
|
||||
return std::make_unique<StandardWallClock>();
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
std::unique_ptr<WallClock> CreateBestMatchingClock(u64 emulated_cpu_frequency,
|
||||
u64 emulated_clock_frequency) {
|
||||
return std::make_unique<StandardWallClock>(emulated_cpu_frequency, emulated_clock_frequency);
|
||||
return std::make_unique<StandardWallClock>();
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
std::unique_ptr<WallClock> CreateStandardWallClock(u64 emulated_cpu_frequency,
|
||||
u64 emulated_clock_frequency) {
|
||||
return std::make_unique<StandardWallClock>(emulated_cpu_frequency, emulated_clock_frequency);
|
||||
std::unique_ptr<WallClock> CreateStandardWallClock() {
|
||||
return std::make_unique<StandardWallClock>();
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <ratio>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
|
@ -12,50 +13,82 @@ namespace Common {
|
|||
|
||||
class WallClock {
|
||||
public:
|
||||
static constexpr u64 NS_RATIO = 1'000'000'000;
|
||||
static constexpr u64 US_RATIO = 1'000'000;
|
||||
static constexpr u64 MS_RATIO = 1'000;
|
||||
static constexpr u64 CNTFRQ = 19'200'000; // CNTPCT_EL0 Frequency = 19.2 MHz
|
||||
static constexpr u64 GPUTickFreq = 614'400'000; // GM20B GPU Tick Frequency = 614.4 MHz
|
||||
static constexpr u64 CPUTickFreq = 1'020'000'000; // T210/4 A57 CPU Tick Frequency = 1020.0 MHz
|
||||
|
||||
virtual ~WallClock() = default;
|
||||
|
||||
/// Returns current wall time in nanoseconds
|
||||
[[nodiscard]] virtual std::chrono::nanoseconds GetTimeNS() = 0;
|
||||
/// @returns The time in nanoseconds since the construction of this clock.
|
||||
virtual std::chrono::nanoseconds GetTimeNS() const = 0;
|
||||
|
||||
/// Returns current wall time in microseconds
|
||||
[[nodiscard]] virtual std::chrono::microseconds GetTimeUS() = 0;
|
||||
/// @returns The time in microseconds since the construction of this clock.
|
||||
virtual std::chrono::microseconds GetTimeUS() const = 0;
|
||||
|
||||
/// Returns current wall time in milliseconds
|
||||
[[nodiscard]] virtual std::chrono::milliseconds GetTimeMS() = 0;
|
||||
/// @returns The time in milliseconds since the construction of this clock.
|
||||
virtual std::chrono::milliseconds GetTimeMS() const = 0;
|
||||
|
||||
/// Returns current wall time in emulated clock cycles
|
||||
[[nodiscard]] virtual u64 GetClockCycles() = 0;
|
||||
/// @returns The guest CNTPCT ticks since the construction of this clock.
|
||||
virtual u64 GetCNTPCT() const = 0;
|
||||
|
||||
/// Returns current wall time in emulated cpu cycles
|
||||
[[nodiscard]] virtual u64 GetCPUCycles() = 0;
|
||||
/// @returns The guest GPU ticks since the construction of this clock.
|
||||
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
|
||||
[[nodiscard]] bool IsNative() const {
|
||||
return is_native;
|
||||
/// @returns The raw host timer ticks since the construction of this clock.
|
||||
virtual u64 GetHostTicksElapsed() const = 0;
|
||||
|
||||
/// @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:
|
||||
explicit WallClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_, bool is_native_)
|
||||
: emulated_cpu_frequency{emulated_cpu_frequency_},
|
||||
emulated_clock_frequency{emulated_clock_frequency_}, is_native{is_native_} {}
|
||||
using NsRatio = std::nano;
|
||||
using UsRatio = std::micro;
|
||||
using MsRatio = std::milli;
|
||||
|
||||
u64 emulated_cpu_frequency;
|
||||
u64 emulated_clock_frequency;
|
||||
using NsToUsRatio = std::ratio_divide<std::nano, std::micro>;
|
||||
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:
|
||||
bool is_native;
|
||||
// Cycle Timing
|
||||
|
||||
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,
|
||||
u64 emulated_clock_frequency);
|
||||
std::unique_ptr<WallClock> CreateOptimalClock();
|
||||
|
||||
[[nodiscard]] std::unique_ptr<WallClock> CreateStandardWallClock(u64 emulated_cpu_frequency,
|
||||
u64 emulated_clock_frequency);
|
||||
std::unique_ptr<WallClock> CreateStandardWallClock();
|
||||
|
||||
} // namespace Common
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/x64/cpu_detect.h"
|
||||
#include "common/x64/rdtsc.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
|
@ -167,6 +168,7 @@ static CPUCaps Detect() {
|
|||
__cpuid(cpu_id, 0x80000001);
|
||||
caps.lzcnt = Common::Bit<5>(cpu_id[2]);
|
||||
caps.fma4 = Common::Bit<16>(cpu_id[2]);
|
||||
caps.monitorx = Common::Bit<29>(cpu_id[2]);
|
||||
}
|
||||
|
||||
if (max_ex_fn >= 0x80000007) {
|
||||
|
@ -187,6 +189,8 @@ static CPUCaps Detect() {
|
|||
caps.tsc_frequency = static_cast<u64>(caps.crystal_frequency) *
|
||||
caps.tsc_crystal_ratio_numerator /
|
||||
caps.tsc_crystal_ratio_denominator;
|
||||
} else {
|
||||
caps.tsc_frequency = X64::EstimateRDTSCFrequency();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -63,6 +63,7 @@ struct CPUCaps {
|
|||
bool gfni : 1;
|
||||
bool invariant_tsc : 1;
|
||||
bool lzcnt : 1;
|
||||
bool monitorx : 1;
|
||||
bool movbe : 1;
|
||||
bool pclmulqdq : 1;
|
||||
bool popcnt : 1;
|
||||
|
|
|
@ -9,58 +9,64 @@
|
|||
|
||||
#include "common/x64/cpu_detect.h"
|
||||
#include "common/x64/cpu_wait.h"
|
||||
#include "common/x64/rdtsc.h"
|
||||
|
||||
namespace Common::X64 {
|
||||
|
||||
namespace {
|
||||
|
||||
// 100,000 cycles is a reasonable amount of time to wait to save on CPU resources.
|
||||
// For reference:
|
||||
// At 1 GHz, 100K cycles is 100us
|
||||
// At 2 GHz, 100K cycles is 50us
|
||||
// At 4 GHz, 100K cycles is 25us
|
||||
constexpr auto PauseCycles = 100'000U;
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
#ifdef _MSC_VER
|
||||
__forceinline static u64 FencedRDTSC() {
|
||||
_mm_lfence();
|
||||
_ReadWriteBarrier();
|
||||
const u64 result = __rdtsc();
|
||||
_mm_lfence();
|
||||
_ReadWriteBarrier();
|
||||
return result;
|
||||
__forceinline static void TPAUSE() {
|
||||
static constexpr auto RequestC02State = 0U;
|
||||
_tpause(RequestC02State, FencedRDTSC() + PauseCycles);
|
||||
}
|
||||
|
||||
__forceinline static void TPAUSE() {
|
||||
// 100,000 cycles is a reasonable amount of time to wait to save on CPU resources.
|
||||
// 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;
|
||||
_tpause(0, 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
|
||||
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() {
|
||||
// 100,000 cycles is a reasonable amount of time to wait to save on CPU resources.
|
||||
// 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;
|
||||
static constexpr auto RequestC02State = 0U;
|
||||
const auto tsc = FencedRDTSC() + PauseCycles;
|
||||
const auto eax = static_cast<u32>(tsc & 0xFFFFFFFF);
|
||||
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
|
||||
|
||||
void MicroSleep() {
|
||||
static const bool has_waitpkg = GetCPUCaps().waitpkg;
|
||||
static const bool has_monitorx = GetCPUCaps().monitorx;
|
||||
|
||||
if (has_waitpkg) {
|
||||
TPAUSE();
|
||||
} else if (has_monitorx) {
|
||||
MWAITX();
|
||||
} else {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
|
|
|
@ -1,164 +1,50 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// 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/x64/native_clock.h"
|
||||
#include "common/x64/rdtsc.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
namespace Common::X64 {
|
||||
|
||||
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
|
||||
__forceinline static u64 FencedRDTSC() {
|
||||
_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);
|
||||
std::chrono::nanoseconds NativeClock::GetTimeNS() const {
|
||||
return std::chrono::nanoseconds{MultiplyHigh(GetHostTicksElapsed(), ns_rdtsc_factor)};
|
||||
}
|
||||
|
||||
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 = 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);
|
||||
std::chrono::microseconds NativeClock::GetTimeUS() const {
|
||||
return std::chrono::microseconds{MultiplyHigh(GetHostTicksElapsed(), us_rdtsc_factor)};
|
||||
}
|
||||
|
||||
namespace X64 {
|
||||
NativeClock::NativeClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_,
|
||||
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();
|
||||
std::chrono::milliseconds NativeClock::GetTimeMS() const {
|
||||
return std::chrono::milliseconds{MultiplyHigh(GetHostTicksElapsed(), ms_rdtsc_factor)};
|
||||
}
|
||||
|
||||
u64 NativeClock::GetRTSC() {
|
||||
TimePoint new_time_point{};
|
||||
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;
|
||||
u64 NativeClock::GetCNTPCT() const {
|
||||
return MultiplyHigh(GetHostTicksElapsed(), cntpct_rdtsc_factor);
|
||||
}
|
||||
|
||||
void NativeClock::Pause(bool is_paused) {
|
||||
if (!is_paused) {
|
||||
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));
|
||||
}
|
||||
u64 NativeClock::GetGPUTick() const {
|
||||
return MultiplyHigh(GetHostTicksElapsed(), gputick_rdtsc_factor);
|
||||
}
|
||||
|
||||
std::chrono::nanoseconds NativeClock::GetTimeNS() {
|
||||
const u64 rtsc_value = GetRTSC();
|
||||
return std::chrono::nanoseconds{MultiplyHigh(rtsc_value, ns_rtsc_factor)};
|
||||
u64 NativeClock::GetHostTicksNow() const {
|
||||
return FencedRDTSC();
|
||||
}
|
||||
|
||||
std::chrono::microseconds NativeClock::GetTimeUS() {
|
||||
const u64 rtsc_value = GetRTSC();
|
||||
return std::chrono::microseconds{MultiplyHigh(rtsc_value, us_rtsc_factor)};
|
||||
u64 NativeClock::GetHostTicksElapsed() const {
|
||||
return FencedRDTSC() - start_ticks;
|
||||
}
|
||||
|
||||
std::chrono::milliseconds NativeClock::GetTimeMS() {
|
||||
const u64 rtsc_value = GetRTSC();
|
||||
return std::chrono::milliseconds{MultiplyHigh(rtsc_value, ms_rtsc_factor)};
|
||||
bool NativeClock::IsNative() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
u64 NativeClock::GetClockCycles() {
|
||||
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
|
||||
} // namespace Common::X64
|
||||
|
|
|
@ -3,58 +3,39 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "common/polyfill_thread.h"
|
||||
#include "common/wall_clock.h"
|
||||
|
||||
namespace Common {
|
||||
namespace Common::X64 {
|
||||
|
||||
namespace X64 {
|
||||
class NativeClock final : public WallClock {
|
||||
public:
|
||||
explicit NativeClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_,
|
||||
u64 rtsc_frequency_);
|
||||
explicit NativeClock(u64 rdtsc_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:
|
||||
u64 GetRTSC();
|
||||
u64 start_ticks;
|
||||
u64 rdtsc_frequency;
|
||||
|
||||
void CalculateAndSetFactors();
|
||||
|
||||
union alignas(16) TimePoint {
|
||||
TimePoint() : pack{} {}
|
||||
u128 pack{};
|
||||
struct Inner {
|
||||
u64 last_measure{};
|
||||
u64 accumulated_ticks{};
|
||||
} inner;
|
||||
};
|
||||
|
||||
TimePoint time_point;
|
||||
|
||||
// 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;
|
||||
u64 ns_rdtsc_factor;
|
||||
u64 us_rdtsc_factor;
|
||||
u64 ms_rdtsc_factor;
|
||||
u64 cntpct_rdtsc_factor;
|
||||
u64 gputick_rdtsc_factor;
|
||||
};
|
||||
} // namespace X64
|
||||
|
||||
u64 EstimateRDTSCFrequency();
|
||||
|
||||
} // namespace Common
|
||||
} // namespace Common::X64
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -14,7 +14,6 @@ add_library(core STATIC
|
|||
core.h
|
||||
core_timing.cpp
|
||||
core_timing.h
|
||||
core_timing_util.h
|
||||
cpu_manager.cpp
|
||||
cpu_manager.h
|
||||
crypto/aes_util.cpp
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue