mirror of
https://git.citron-emu.org/Citron/Citron.git
synced 2025-03-16 05:11:49 +00:00
Compare commits
5 commits
v0.6-canar
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
21594b73aa | ||
|
d869045b77 | ||
|
f2931c7566 | ||
|
12c63997d2 | ||
|
1023125be5 |
15 changed files with 513 additions and 76 deletions
|
@ -17,6 +17,45 @@ if (MSVC)
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W3 /WX-")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W3 /WX-")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# PGO Configuration
|
||||||
|
option(CITRON_ENABLE_PGO_INSTRUMENT "Enable Profile-Guided Optimization instrumentation build" OFF)
|
||||||
|
option(CITRON_ENABLE_PGO_OPTIMIZE "Enable Profile-Guided Optimization optimization build" OFF)
|
||||||
|
|
||||||
|
if(MSVC)
|
||||||
|
if(CITRON_ENABLE_PGO_INSTRUMENT)
|
||||||
|
string(APPEND CMAKE_CXX_FLAGS_RELEASE " /GL /LTCG:PGINSTRUMENT")
|
||||||
|
string(APPEND CMAKE_EXE_LINKER_FLAGS_RELEASE " /LTCG:PGINSTRUMENT")
|
||||||
|
string(APPEND CMAKE_SHARED_LINKER_FLAGS_RELEASE " /LTCG:PGINSTRUMENT")
|
||||||
|
elseif(CITRON_ENABLE_PGO_OPTIMIZE)
|
||||||
|
string(APPEND CMAKE_CXX_FLAGS_RELEASE " /GL /LTCG:PGOPTIMIZE")
|
||||||
|
string(APPEND CMAKE_EXE_LINKER_FLAGS_RELEASE " /LTCG:PGOPTIMIZE")
|
||||||
|
string(APPEND CMAKE_SHARED_LINKER_FLAGS_RELEASE " /LTCG:PGOPTIMIZE")
|
||||||
|
endif()
|
||||||
|
else()
|
||||||
|
# GCC and Clang PGO flags
|
||||||
|
if(CITRON_ENABLE_PGO_INSTRUMENT)
|
||||||
|
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||||
|
string(APPEND CMAKE_CXX_FLAGS_RELEASE " -fprofile-instr-generate")
|
||||||
|
string(APPEND CMAKE_EXE_LINKER_FLAGS_RELEASE " -fprofile-instr-generate")
|
||||||
|
string(APPEND CMAKE_SHARED_LINKER_FLAGS_RELEASE " -fprofile-instr-generate")
|
||||||
|
else() # GCC
|
||||||
|
string(APPEND CMAKE_CXX_FLAGS_RELEASE " -fprofile-generate")
|
||||||
|
string(APPEND CMAKE_EXE_LINKER_FLAGS_RELEASE " -fprofile-generate")
|
||||||
|
string(APPEND CMAKE_SHARED_LINKER_FLAGS_RELEASE " -fprofile-generate")
|
||||||
|
endif()
|
||||||
|
elseif(CITRON_ENABLE_PGO_OPTIMIZE)
|
||||||
|
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||||
|
string(APPEND CMAKE_CXX_FLAGS_RELEASE " -fprofile-instr-use=default.profdata")
|
||||||
|
string(APPEND CMAKE_EXE_LINKER_FLAGS_RELEASE " -fprofile-instr-use=default.profdata")
|
||||||
|
string(APPEND CMAKE_SHARED_LINKER_FLAGS_RELEASE " -fprofile-instr-use=default.profdata")
|
||||||
|
else() # GCC
|
||||||
|
string(APPEND CMAKE_CXX_FLAGS_RELEASE " -fprofile-use")
|
||||||
|
string(APPEND CMAKE_EXE_LINKER_FLAGS_RELEASE " -fprofile-use")
|
||||||
|
string(APPEND CMAKE_SHARED_LINKER_FLAGS_RELEASE " -fprofile-use")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
# Check if SDL2::SDL2 target exists; if not, create an alias
|
# Check if SDL2::SDL2 target exists; if not, create an alias
|
||||||
if (TARGET SDL2::SDL2-static)
|
if (TARGET SDL2::SDL2-static)
|
||||||
add_library(SDL2::SDL2 ALIAS SDL2::SDL2-static)
|
add_library(SDL2::SDL2 ALIAS SDL2::SDL2-static)
|
||||||
|
@ -684,42 +723,3 @@ if(ENABLE_QT AND UNIX AND NOT APPLE)
|
||||||
install(FILES "dist/org.citron_emu.citron.metainfo.xml"
|
install(FILES "dist/org.citron_emu.citron.metainfo.xml"
|
||||||
DESTINATION "share/metainfo")
|
DESTINATION "share/metainfo")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# PGO Configuration
|
|
||||||
option(CITRON_ENABLE_PGO_INSTRUMENT "Enable Profile-Guided Optimization instrumentation build" OFF)
|
|
||||||
option(CITRON_ENABLE_PGO_OPTIMIZE "Enable Profile-Guided Optimization optimization build" OFF)
|
|
||||||
|
|
||||||
if(MSVC)
|
|
||||||
if(CITRON_ENABLE_PGO_INSTRUMENT)
|
|
||||||
string(APPEND CMAKE_CXX_FLAGS_RELEASE " /GL /LTCG:PGINSTRUMENT")
|
|
||||||
string(APPEND CMAKE_EXE_LINKER_FLAGS_RELEASE " /LTCG:PGINSTRUMENT")
|
|
||||||
string(APPEND CMAKE_SHARED_LINKER_FLAGS_RELEASE " /LTCG:PGINSTRUMENT")
|
|
||||||
elseif(CITRON_ENABLE_PGO_OPTIMIZE)
|
|
||||||
string(APPEND CMAKE_CXX_FLAGS_RELEASE " /GL /LTCG:PGOPTIMIZE")
|
|
||||||
string(APPEND CMAKE_EXE_LINKER_FLAGS_RELEASE " /LTCG:PGOPTIMIZE")
|
|
||||||
string(APPEND CMAKE_SHARED_LINKER_FLAGS_RELEASE " /LTCG:PGOPTIMIZE")
|
|
||||||
endif()
|
|
||||||
else()
|
|
||||||
# GCC and Clang PGO flags
|
|
||||||
if(CITRON_ENABLE_PGO_INSTRUMENT)
|
|
||||||
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
|
||||||
string(APPEND CMAKE_CXX_FLAGS_RELEASE " -fprofile-instr-generate")
|
|
||||||
string(APPEND CMAKE_EXE_LINKER_FLAGS_RELEASE " -fprofile-instr-generate")
|
|
||||||
string(APPEND CMAKE_SHARED_LINKER_FLAGS_RELEASE " -fprofile-instr-generate")
|
|
||||||
else() # GCC
|
|
||||||
string(APPEND CMAKE_CXX_FLAGS_RELEASE " -fprofile-generate")
|
|
||||||
string(APPEND CMAKE_EXE_LINKER_FLAGS_RELEASE " -fprofile-generate")
|
|
||||||
string(APPEND CMAKE_SHARED_LINKER_FLAGS_RELEASE " -fprofile-generate")
|
|
||||||
endif()
|
|
||||||
elseif(CITRON_ENABLE_PGO_OPTIMIZE)
|
|
||||||
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
|
||||||
string(APPEND CMAKE_CXX_FLAGS_RELEASE " -fprofile-instr-use=default.profdata")
|
|
||||||
string(APPEND CMAKE_EXE_LINKER_FLAGS_RELEASE " -fprofile-instr-use=default.profdata")
|
|
||||||
string(APPEND CMAKE_SHARED_LINKER_FLAGS_RELEASE " -fprofile-instr-use=default.profdata")
|
|
||||||
else() # GCC
|
|
||||||
string(APPEND CMAKE_CXX_FLAGS_RELEASE " -fprofile-use")
|
|
||||||
string(APPEND CMAKE_EXE_LINKER_FLAGS_RELEASE " -fprofile-use")
|
|
||||||
string(APPEND CMAKE_SHARED_LINKER_FLAGS_RELEASE " -fprofile-use")
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
|
@ -11,10 +11,10 @@ plugins {
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
id("org.jetbrains.kotlin.android")
|
id("org.jetbrains.kotlin.android")
|
||||||
id("kotlin-parcelize")
|
id("kotlin-parcelize")
|
||||||
kotlin("plugin.serialization") version "2.1.20-RC2"
|
kotlin("plugin.serialization") version "1.9.20"
|
||||||
id("androidx.navigation.safeargs.kotlin")
|
id("androidx.navigation.safeargs.kotlin")
|
||||||
id("org.jlleitschuh.gradle.ktlint") version "12.2.0"
|
id("org.jlleitschuh.gradle.ktlint") version "11.4.0"
|
||||||
id("com.github.triplet.play") version "3.12.1"
|
id("com.github.triplet.play") version "3.8.6"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -203,7 +203,7 @@ tasks.getByPath("ktlintMainSourceSetCheck").doFirst { showFormatHelp.invoke() }
|
||||||
tasks.getByPath("loadKtlintReporters").dependsOn("ktlintReset")
|
tasks.getByPath("loadKtlintReporters").dependsOn("ktlintReset")
|
||||||
|
|
||||||
ktlint {
|
ktlint {
|
||||||
version.set("0.49.1")
|
version.set("0.47.1")
|
||||||
android.set(true)
|
android.set(true)
|
||||||
ignoreFailures.set(false)
|
ignoreFailures.set(false)
|
||||||
disabledRules.set(
|
disabledRules.set(
|
||||||
|
@ -228,24 +228,23 @@ play {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("androidx.core:core-ktx:1.15.0")
|
implementation("androidx.core:core-ktx:1.12.0")
|
||||||
implementation("androidx.appcompat:appcompat:1.7.0")
|
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||||
implementation("androidx.recyclerview:recyclerview:1.4.0")
|
implementation("androidx.recyclerview:recyclerview:1.3.1")
|
||||||
implementation("androidx.constraintlayout:constraintlayout:2.2.1")
|
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
||||||
implementation("androidx.fragment:fragment-ktx:1.8.6")
|
implementation("androidx.fragment:fragment-ktx:1.6.1")
|
||||||
implementation("androidx.documentfile:documentfile:1.0.1")
|
implementation("androidx.documentfile:documentfile:1.0.1")
|
||||||
implementation("com.google.android.material:material:1.12.0")
|
implementation("com.google.android.material:material:1.9.0")
|
||||||
implementation("androidx.preference:preference-ktx:1.2.1")
|
implementation("androidx.preference:preference-ktx:1.2.1")
|
||||||
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7")
|
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
|
||||||
implementation("io.coil-kt:coil:2.7.0")
|
implementation("io.coil-kt:coil:2.2.2")
|
||||||
implementation("androidx.core:core-splashscreen:1.0.1")
|
implementation("androidx.core:core-splashscreen:1.0.1")
|
||||||
implementation("androidx.window:window:1.3.0")
|
implementation("androidx.window:window:1.2.0-beta03")
|
||||||
implementation("androidx.constraintlayout:constraintlayout:2.2.1")
|
|
||||||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
||||||
implementation("androidx.navigation:navigation-fragment-ktx:2.8.8")
|
implementation("androidx.navigation:navigation-fragment-ktx:2.7.4")
|
||||||
implementation("androidx.navigation:navigation-ui-ktx:2.8.8")
|
implementation("androidx.navigation:navigation-ui-ktx:2.7.4")
|
||||||
implementation("info.debatty:java-string-similarity:2.0.0")
|
implementation("info.debatty:java-string-similarity:2.0.0")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun runGitCommand(command: List<String>): String {
|
fun runGitCommand(command: List<String>): String {
|
||||||
|
|
|
@ -180,6 +180,62 @@ class SetupFragment : Fragment() {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Add title.keys installation page
|
||||||
|
add(
|
||||||
|
SetupPage(
|
||||||
|
R.drawable.ic_key,
|
||||||
|
R.string.install_title_keys,
|
||||||
|
R.string.install_title_keys_description,
|
||||||
|
R.drawable.ic_add,
|
||||||
|
true,
|
||||||
|
R.string.select_keys,
|
||||||
|
{
|
||||||
|
titleKeyCallback = it
|
||||||
|
getTitleKey.launch(arrayOf("*/*"))
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
R.string.install_title_keys_warning,
|
||||||
|
R.string.install_title_keys_warning_description,
|
||||||
|
R.string.install_title_keys_warning_help,
|
||||||
|
{
|
||||||
|
val file = File(DirectoryInitialization.userDirectory + "/keys/title.keys")
|
||||||
|
if (file.exists()) {
|
||||||
|
StepState.COMPLETE
|
||||||
|
} else {
|
||||||
|
StepState.INCOMPLETE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Add firmware installation page (mandatory)
|
||||||
|
add(
|
||||||
|
SetupPage(
|
||||||
|
R.drawable.ic_key,
|
||||||
|
R.string.install_firmware,
|
||||||
|
R.string.install_firmware_description,
|
||||||
|
R.drawable.ic_add,
|
||||||
|
true,
|
||||||
|
R.string.select_firmware,
|
||||||
|
{
|
||||||
|
firmwareCallback = it
|
||||||
|
getFirmware.launch(arrayOf("application/zip"))
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
R.string.install_firmware_warning,
|
||||||
|
R.string.install_firmware_warning_description,
|
||||||
|
R.string.install_firmware_warning_help,
|
||||||
|
{
|
||||||
|
if (NativeLibrary.isFirmwareAvailable()) {
|
||||||
|
StepState.COMPLETE
|
||||||
|
} else {
|
||||||
|
StepState.INCOMPLETE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
add(
|
add(
|
||||||
SetupPage(
|
SetupPage(
|
||||||
R.drawable.ic_controller,
|
R.drawable.ic_controller,
|
||||||
|
@ -268,6 +324,18 @@ class SetupFragment : Fragment() {
|
||||||
return@setOnClickListener
|
return@setOnClickListener
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Special handling for firmware page - don't allow skipping
|
||||||
|
if (currentPage.titleId == R.string.install_firmware && !NativeLibrary.isFirmwareAvailable()) {
|
||||||
|
SetupWarningDialogFragment.newInstance(
|
||||||
|
currentPage.warningTitleId,
|
||||||
|
currentPage.warningDescriptionId,
|
||||||
|
currentPage.warningHelpLinkId,
|
||||||
|
index,
|
||||||
|
allowSkip = false
|
||||||
|
).show(childFragmentManager, SetupWarningDialogFragment.TAG)
|
||||||
|
return@setOnClickListener
|
||||||
|
}
|
||||||
|
|
||||||
if (!hasBeenWarned[index]) {
|
if (!hasBeenWarned[index]) {
|
||||||
SetupWarningDialogFragment.newInstance(
|
SetupWarningDialogFragment.newInstance(
|
||||||
currentPage.warningTitleId,
|
currentPage.warningTitleId,
|
||||||
|
@ -346,6 +414,30 @@ class SetupFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private lateinit var titleKeyCallback: SetupCallback
|
||||||
|
|
||||||
|
val getTitleKey =
|
||||||
|
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
||||||
|
if (result != null) {
|
||||||
|
mainActivity.processTitleKey(result)
|
||||||
|
titleKeyCallback.onStepCompleted()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var firmwareCallback: SetupCallback
|
||||||
|
|
||||||
|
val getFirmware =
|
||||||
|
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
||||||
|
if (result != null) {
|
||||||
|
mainActivity.getFirmware.launch(arrayOf("application/zip"))
|
||||||
|
binding.root.postDelayed({
|
||||||
|
if (NativeLibrary.isFirmwareAvailable()) {
|
||||||
|
firmwareCallback.onStepCompleted()
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private lateinit var gamesDirCallback: SetupCallback
|
private lateinit var gamesDirCallback: SetupCallback
|
||||||
|
|
||||||
val getGamesDirectory =
|
val getGamesDirectory =
|
||||||
|
|
|
@ -17,6 +17,7 @@ class SetupWarningDialogFragment : DialogFragment() {
|
||||||
private var descriptionId: Int = 0
|
private var descriptionId: Int = 0
|
||||||
private var helpLinkId: Int = 0
|
private var helpLinkId: Int = 0
|
||||||
private var page: Int = 0
|
private var page: Int = 0
|
||||||
|
private var allowSkip: Boolean = true
|
||||||
|
|
||||||
private lateinit var setupFragment: SetupFragment
|
private lateinit var setupFragment: SetupFragment
|
||||||
|
|
||||||
|
@ -26,17 +27,24 @@ class SetupWarningDialogFragment : DialogFragment() {
|
||||||
descriptionId = requireArguments().getInt(DESCRIPTION)
|
descriptionId = requireArguments().getInt(DESCRIPTION)
|
||||||
helpLinkId = requireArguments().getInt(HELP_LINK)
|
helpLinkId = requireArguments().getInt(HELP_LINK)
|
||||||
page = requireArguments().getInt(PAGE)
|
page = requireArguments().getInt(PAGE)
|
||||||
|
allowSkip = requireArguments().getBoolean(ALLOW_SKIP, true)
|
||||||
|
|
||||||
setupFragment = requireParentFragment() as SetupFragment
|
setupFragment = requireParentFragment() as SetupFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
val builder = MaterialAlertDialogBuilder(requireContext())
|
val builder = MaterialAlertDialogBuilder(requireContext())
|
||||||
.setPositiveButton(R.string.warning_skip) { _: DialogInterface?, _: Int ->
|
|
||||||
|
if (allowSkip) {
|
||||||
|
builder.setPositiveButton(R.string.warning_skip) { _: DialogInterface?, _: Int ->
|
||||||
setupFragment.pageForward()
|
setupFragment.pageForward()
|
||||||
setupFragment.setPageWarned(page)
|
setupFragment.setPageWarned(page)
|
||||||
}
|
}
|
||||||
.setNegativeButton(R.string.warning_cancel, null)
|
builder.setNegativeButton(R.string.warning_cancel, null)
|
||||||
|
} else {
|
||||||
|
// For mandatory steps, only show an OK button that dismisses the dialog
|
||||||
|
builder.setPositiveButton(R.string.ok, null)
|
||||||
|
}
|
||||||
|
|
||||||
if (titleId != 0) {
|
if (titleId != 0) {
|
||||||
builder.setTitle(titleId)
|
builder.setTitle(titleId)
|
||||||
|
@ -48,7 +56,7 @@ class SetupWarningDialogFragment : DialogFragment() {
|
||||||
}
|
}
|
||||||
if (helpLinkId != 0) {
|
if (helpLinkId != 0) {
|
||||||
builder.setNeutralButton(R.string.warning_help) { _: DialogInterface?, _: Int ->
|
builder.setNeutralButton(R.string.warning_help) { _: DialogInterface?, _: Int ->
|
||||||
val helpLink = resources.getString(R.string.install_prod_keys_warning_help)
|
val helpLink = resources.getString(helpLinkId)
|
||||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(helpLink))
|
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(helpLink))
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
}
|
}
|
||||||
|
@ -64,12 +72,14 @@ class SetupWarningDialogFragment : DialogFragment() {
|
||||||
private const val DESCRIPTION = "Description"
|
private const val DESCRIPTION = "Description"
|
||||||
private const val HELP_LINK = "HelpLink"
|
private const val HELP_LINK = "HelpLink"
|
||||||
private const val PAGE = "Page"
|
private const val PAGE = "Page"
|
||||||
|
private const val ALLOW_SKIP = "AllowSkip"
|
||||||
|
|
||||||
fun newInstance(
|
fun newInstance(
|
||||||
titleId: Int,
|
titleId: Int,
|
||||||
descriptionId: Int,
|
descriptionId: Int,
|
||||||
helpLinkId: Int,
|
helpLinkId: Int,
|
||||||
page: Int
|
page: Int,
|
||||||
|
allowSkip: Boolean = true
|
||||||
): SetupWarningDialogFragment {
|
): SetupWarningDialogFragment {
|
||||||
val dialog = SetupWarningDialogFragment()
|
val dialog = SetupWarningDialogFragment()
|
||||||
val bundle = Bundle()
|
val bundle = Bundle()
|
||||||
|
@ -78,6 +88,7 @@ class SetupWarningDialogFragment : DialogFragment() {
|
||||||
putInt(DESCRIPTION, descriptionId)
|
putInt(DESCRIPTION, descriptionId)
|
||||||
putInt(HELP_LINK, helpLinkId)
|
putInt(HELP_LINK, helpLinkId)
|
||||||
putInt(PAGE, page)
|
putInt(PAGE, page)
|
||||||
|
putBoolean(ALLOW_SKIP, allowSkip)
|
||||||
}
|
}
|
||||||
dialog.arguments = bundle
|
dialog.arguments = bundle
|
||||||
return dialog
|
return dialog
|
||||||
|
|
|
@ -377,6 +377,57 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val getTitleKey =
|
||||||
|
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
||||||
|
if (result != null) {
|
||||||
|
processTitleKey(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun processTitleKey(result: Uri): Boolean {
|
||||||
|
if (FileUtil.getExtension(result) != "keys") {
|
||||||
|
MessageDialogFragment.newInstance(
|
||||||
|
this,
|
||||||
|
titleId = R.string.reading_keys_failure,
|
||||||
|
descriptionId = R.string.install_title_keys_failure_extension_description
|
||||||
|
).show(supportFragmentManager, MessageDialogFragment.TAG)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
contentResolver.takePersistableUriPermission(
|
||||||
|
result,
|
||||||
|
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||||
|
)
|
||||||
|
|
||||||
|
val dstPath = DirectoryInitialization.userDirectory + "/keys/"
|
||||||
|
if (FileUtil.copyUriToInternalStorage(
|
||||||
|
result,
|
||||||
|
dstPath,
|
||||||
|
"title.keys"
|
||||||
|
) != null
|
||||||
|
) {
|
||||||
|
if (NativeLibrary.reloadKeys()) {
|
||||||
|
Toast.makeText(
|
||||||
|
applicationContext,
|
||||||
|
R.string.install_keys_success,
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
homeViewModel.setCheckKeys(true)
|
||||||
|
gamesViewModel.reloadGames(true)
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
MessageDialogFragment.newInstance(
|
||||||
|
this,
|
||||||
|
titleId = R.string.invalid_keys_error,
|
||||||
|
descriptionId = R.string.install_keys_failure_description,
|
||||||
|
helpLinkId = R.string.dumping_keys_quickstart_link
|
||||||
|
).show(supportFragmentManager, MessageDialogFragment.TAG)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
val getFirmware =
|
val getFirmware =
|
||||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
|
|
|
@ -108,11 +108,15 @@
|
||||||
<string name="import_saves">Import</string>
|
<string name="import_saves">Import</string>
|
||||||
<string name="export_saves">Export</string>
|
<string name="export_saves">Export</string>
|
||||||
<string name="install_firmware">Install firmware</string>
|
<string name="install_firmware">Install firmware</string>
|
||||||
<string name="install_firmware_description">Firmware must be in a ZIP archive and is needed to boot some games</string>
|
<string name="install_firmware_description">Required for emulation of system features</string>
|
||||||
|
<string name="install_firmware_warning">Firmware installation is mandatory</string>
|
||||||
|
<string name="install_firmware_warning_description">Firmware is required for proper emulation. You must install firmware to continue.</string>
|
||||||
|
<string name="install_firmware_warning_help">https://citron-emu.org/help/quickstart/#dumping-system-firmware</string>
|
||||||
<string name="firmware_installing">Installing firmware</string>
|
<string name="firmware_installing">Installing firmware</string>
|
||||||
<string name="firmware_installed_success">Firmware installed successfully</string>
|
<string name="firmware_installed_success">Firmware successfully installed</string>
|
||||||
<string name="firmware_installed_failure">Firmware installation failed</string>
|
<string name="firmware_installed_failure">Failed to install firmware</string>
|
||||||
<string name="firmware_installed_failure_description">Make sure the firmware nca files are at the root of the zip and try again.</string>
|
<string name="firmware_installed_failure_description">The selected file is not a valid firmware archive or is corrupt.</string>
|
||||||
|
<string name="select_firmware">Select Firmware</string>
|
||||||
<string name="share_log">Share debug logs</string>
|
<string name="share_log">Share debug logs</string>
|
||||||
<string name="share_log_description">Share citron\'s log file to debug issues</string>
|
<string name="share_log_description">Share citron\'s log file to debug issues</string>
|
||||||
<string name="share_log_missing">No log file found</string>
|
<string name="share_log_missing">No log file found</string>
|
||||||
|
@ -172,6 +176,14 @@
|
||||||
<string name="cabinet_restorer">Restorer</string>
|
<string name="cabinet_restorer">Restorer</string>
|
||||||
<string name="cabinet_formatter">Formatter</string>
|
<string name="cabinet_formatter">Formatter</string>
|
||||||
|
|
||||||
|
<!-- Title keys strings -->
|
||||||
|
<string name="install_title_keys">Install title.keys</string>
|
||||||
|
<string name="install_title_keys_description">Required for additional game compatibility</string>
|
||||||
|
<string name="install_title_keys_warning">Skip adding title keys?</string>
|
||||||
|
<string name="install_title_keys_warning_description">Title keys may be required for some games to function properly.</string>
|
||||||
|
<string name="install_title_keys_warning_help">https://citron-emu.org/</string>
|
||||||
|
<string name="install_title_keys_failure_extension_description">Verify your title keys file has a .keys extension and try again.</string>
|
||||||
|
|
||||||
<!-- About screen strings -->
|
<!-- About screen strings -->
|
||||||
<string name="gaia_is_not_real">Gaia isn\'t real</string>
|
<string name="gaia_is_not_real">Gaia isn\'t real</string>
|
||||||
<string name="copied_to_clipboard">Copied to clipboard</string>
|
<string name="copied_to_clipboard">Copied to clipboard</string>
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.application") version "8.9.0" apply false
|
id("com.android.application") version "8.9.0" apply false
|
||||||
id("com.android.library") version "8.9.0" apply false
|
id("com.android.library") version "8.9.0" apply false
|
||||||
id("org.jetbrains.kotlin.android") version "2.1.20-RC2" apply false
|
id("org.jetbrains.kotlin.android") version "1.9.20" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register("clean").configure {
|
tasks.register("clean").configure {
|
||||||
|
@ -18,6 +18,6 @@ buildscript {
|
||||||
google()
|
google()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath("androidx.navigation:navigation-safe-args-gradle-plugin:2.8.8")
|
classpath("androidx.navigation:navigation-safe-args-gradle-plugin:2.6.0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||||
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
@ -649,13 +648,17 @@ void KeyManager::ReloadKeys() {
|
||||||
|
|
||||||
if (Settings::values.use_dev_keys) {
|
if (Settings::values.use_dev_keys) {
|
||||||
dev_mode = true;
|
dev_mode = true;
|
||||||
|
LoadFromFile(citron_keys_dir / "dev.keys_autogenerated", false);
|
||||||
LoadFromFile(citron_keys_dir / "dev.keys", false);
|
LoadFromFile(citron_keys_dir / "dev.keys", false);
|
||||||
} else {
|
} else {
|
||||||
dev_mode = false;
|
dev_mode = false;
|
||||||
|
LoadFromFile(citron_keys_dir / "prod.keys_autogenerated", false);
|
||||||
LoadFromFile(citron_keys_dir / "prod.keys", false);
|
LoadFromFile(citron_keys_dir / "prod.keys", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LoadFromFile(citron_keys_dir / "title.keys_autogenerated", true);
|
||||||
LoadFromFile(citron_keys_dir / "title.keys", true);
|
LoadFromFile(citron_keys_dir / "title.keys", true);
|
||||||
|
LoadFromFile(citron_keys_dir / "console.keys_autogenerated", false);
|
||||||
LoadFromFile(citron_keys_dir / "console.keys", false);
|
LoadFromFile(citron_keys_dir / "console.keys", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -844,15 +847,87 @@ Key256 KeyManager::GetBISKey(u8 partition_id) const {
|
||||||
template <size_t Size>
|
template <size_t Size>
|
||||||
void KeyManager::WriteKeyToFile(KeyCategory category, std::string_view keyname,
|
void KeyManager::WriteKeyToFile(KeyCategory category, std::string_view keyname,
|
||||||
const std::array<u8, Size>& key) {
|
const std::array<u8, Size>& key) {
|
||||||
// Function is now a no-op - keys are no longer written to autogenerated files
|
const auto citron_keys_dir = Common::FS::GetCitronPath(Common::FS::CitronPath::KeysDir);
|
||||||
|
|
||||||
|
std::string filename = "title.keys_autogenerated";
|
||||||
|
|
||||||
|
if (category == KeyCategory::Standard) {
|
||||||
|
filename = dev_mode ? "dev.keys_autogenerated" : "prod.keys_autogenerated";
|
||||||
|
} else if (category == KeyCategory::Console) {
|
||||||
|
filename = "console.keys_autogenerated";
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto path = citron_keys_dir / filename;
|
||||||
|
const auto add_info_text = !Common::FS::Exists(path);
|
||||||
|
|
||||||
|
Common::FS::IOFile file{path, Common::FS::FileAccessMode::Append,
|
||||||
|
Common::FS::FileType::TextFile};
|
||||||
|
|
||||||
|
if (!file.IsOpen()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (add_info_text) {
|
||||||
|
void(file.WriteString(
|
||||||
|
"# This file is autogenerated by Citron\n"
|
||||||
|
"# It serves to store keys that were automatically generated from the normal keys\n"
|
||||||
|
"# If you are experiencing issues involving keys, it may help to delete this file\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void(file.WriteString(fmt::format("\n{} = {}", keyname, Common::HexToString(key))));
|
||||||
|
LoadFromFile(path, category == KeyCategory::Title);
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
|
void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
|
||||||
if (s128_keys.find({id, field1, field2}) != s128_keys.end() || key == Key128{}) {
|
if (s128_keys.find({id, field1, field2}) != s128_keys.end() || key == Key128{}) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (id == S128KeyType::Titlekey) {
|
||||||
|
Key128 rights_id;
|
||||||
|
std::memcpy(rights_id.data(), &field2, sizeof(u64));
|
||||||
|
std::memcpy(rights_id.data() + sizeof(u64), &field1, sizeof(u64));
|
||||||
|
WriteKeyToFile(KeyCategory::Title, Common::HexToString(rights_id), key);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto category = KeyCategory::Standard;
|
||||||
|
if (id == S128KeyType::Keyblob || id == S128KeyType::KeyblobMAC || id == S128KeyType::TSEC ||
|
||||||
|
id == S128KeyType::SecureBoot || id == S128KeyType::SDSeed || id == S128KeyType::BIS) {
|
||||||
|
category = KeyCategory::Console;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto iter2 = std::find_if(
|
||||||
|
s128_file_id.begin(), s128_file_id.end(), [&id, &field1, &field2](const auto& elem) {
|
||||||
|
return std::tie(elem.second.type, elem.second.field1, elem.second.field2) ==
|
||||||
|
std::tie(id, field1, field2);
|
||||||
|
});
|
||||||
|
if (iter2 != s128_file_id.end()) {
|
||||||
|
WriteKeyToFile(category, iter2->first, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variable cases
|
||||||
|
if (id == S128KeyType::KeyArea) {
|
||||||
|
static constexpr std::array<const char*, 3> kak_names = {
|
||||||
|
"key_area_key_application_{:02X}",
|
||||||
|
"key_area_key_ocean_{:02X}",
|
||||||
|
"key_area_key_system_{:02X}",
|
||||||
|
};
|
||||||
|
WriteKeyToFile(category, fmt::format(fmt::runtime(kak_names.at(field2)), field1), key);
|
||||||
|
} else if (id == S128KeyType::Master) {
|
||||||
|
WriteKeyToFile(category, fmt::format("master_key_{:02X}", field1), key);
|
||||||
|
} else if (id == S128KeyType::Package1) {
|
||||||
|
WriteKeyToFile(category, fmt::format("package1_key_{:02X}", field1), key);
|
||||||
|
} else if (id == S128KeyType::Package2) {
|
||||||
|
WriteKeyToFile(category, fmt::format("package2_key_{:02X}", field1), key);
|
||||||
|
} else if (id == S128KeyType::Titlekek) {
|
||||||
|
WriteKeyToFile(category, fmt::format("titlekek_{:02X}", field1), key);
|
||||||
|
} else if (id == S128KeyType::Keyblob) {
|
||||||
|
WriteKeyToFile(category, fmt::format("keyblob_key_{:02X}", field1), key);
|
||||||
|
} else if (id == S128KeyType::KeyblobMAC) {
|
||||||
|
WriteKeyToFile(category, fmt::format("keyblob_mac_key_{:02X}", field1), key);
|
||||||
|
} else if (id == S128KeyType::Source && field1 == static_cast<u64>(SourceKeyType::Keyblob)) {
|
||||||
|
WriteKeyToFile(category, fmt::format("keyblob_key_source_{:02X}", field2), key);
|
||||||
|
}
|
||||||
|
|
||||||
// Store the key in memory but don't write to file
|
|
||||||
s128_keys[{id, field1, field2}] = key;
|
s128_keys[{id, field1, field2}] = key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -860,8 +935,14 @@ void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) {
|
||||||
if (s256_keys.find({id, field1, field2}) != s256_keys.end() || key == Key256{}) {
|
if (s256_keys.find({id, field1, field2}) != s256_keys.end() || key == Key256{}) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const auto iter = std::find_if(
|
||||||
// Store the key in memory but don't write to file
|
s256_file_id.begin(), s256_file_id.end(), [&id, &field1, &field2](const auto& elem) {
|
||||||
|
return std::tie(elem.second.type, elem.second.field1, elem.second.field2) ==
|
||||||
|
std::tie(id, field1, field2);
|
||||||
|
});
|
||||||
|
if (iter != s256_file_id.end()) {
|
||||||
|
WriteKeyToFile(KeyCategory::Standard, iter->first, key);
|
||||||
|
}
|
||||||
s256_keys[{id, field1, field2}] = key;
|
s256_keys[{id, field1, field2}] = key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -971,6 +1052,8 @@ void KeyManager::DeriveBase() {
|
||||||
// Decrypt keyblob
|
// Decrypt keyblob
|
||||||
if (keyblobs[i] == std::array<u8, 0x90>{}) {
|
if (keyblobs[i] == std::array<u8, 0x90>{}) {
|
||||||
keyblobs[i] = DecryptKeyblob(encrypted_keyblobs[i], key);
|
keyblobs[i] = DecryptKeyblob(encrypted_keyblobs[i], key);
|
||||||
|
WriteKeyToFile<0x90>(KeyCategory::Console, fmt::format("keyblob_{:02X}", i),
|
||||||
|
keyblobs[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
Key128 package1;
|
Key128 package1;
|
||||||
|
@ -1100,6 +1183,7 @@ void KeyManager::DeriveETicket(PartitionDataManager& data,
|
||||||
data.DecryptProdInfo(GetBISKey(0));
|
data.DecryptProdInfo(GetBISKey(0));
|
||||||
|
|
||||||
eticket_extended_kek = data.GetETicketExtendedKek();
|
eticket_extended_kek = data.GetETicketExtendedKek();
|
||||||
|
WriteKeyToFile(KeyCategory::Console, "eticket_extended_kek", eticket_extended_kek);
|
||||||
DeriveETicketRSAKey();
|
DeriveETicketRSAKey();
|
||||||
PopulateTickets();
|
PopulateTickets();
|
||||||
}
|
}
|
||||||
|
@ -1177,6 +1261,8 @@ void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
encrypted_keyblobs[i] = data.GetEncryptedKeyblob(i);
|
encrypted_keyblobs[i] = data.GetEncryptedKeyblob(i);
|
||||||
|
WriteKeyToFile<0xB0>(KeyCategory::Console, fmt::format("encrypted_keyblob_{:02X}", i),
|
||||||
|
encrypted_keyblobs[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
SetKeyWrapped(S128KeyType::Source, data.GetPackage2KeySource(),
|
SetKeyWrapped(S128KeyType::Source, data.GetPackage2KeySource(),
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||||
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
|
@ -14,7 +14,7 @@ namespace Core {
|
||||||
|
|
||||||
namespace Hardware {
|
namespace Hardware {
|
||||||
|
|
||||||
constexpr u64 BASE_CLOCK_RATE = 1'785'000'000; // Default CPU Frequency = 1785 MHz
|
constexpr u64 BASE_CLOCK_RATE = 1'020'000'000; // Default CPU Frequency = 1020 MHz
|
||||||
constexpr u64 CNTFREQ = 19'200'000; // CNTPCT_EL0 Frequency = 19.2 MHz
|
constexpr u64 CNTFREQ = 19'200'000; // CNTPCT_EL0 Frequency = 19.2 MHz
|
||||||
constexpr u32 NUM_CPU_CORES = 4; // Number of CPU Cores
|
constexpr u32 NUM_CPU_CORES = 4; // Number of CPU Cores
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
@ -327,8 +328,11 @@ BufferCacheRuntime::BufferCacheRuntime(const Device& device_, MemoryAllocator& m
|
||||||
DescriptorPool& descriptor_pool)
|
DescriptorPool& descriptor_pool)
|
||||||
: device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_},
|
: device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_},
|
||||||
staging_pool{staging_pool_}, guest_descriptor_queue{guest_descriptor_queue_},
|
staging_pool{staging_pool_}, guest_descriptor_queue{guest_descriptor_queue_},
|
||||||
|
accelerate{nullptr},
|
||||||
quad_index_pass(device, scheduler, descriptor_pool, staging_pool,
|
quad_index_pass(device, scheduler, descriptor_pool, staging_pool,
|
||||||
compute_pass_descriptor_queue) {
|
compute_pass_descriptor_queue) {
|
||||||
|
accelerate = new BufferCacheAccelerator();
|
||||||
|
|
||||||
if (device.GetDriverID() != VK_DRIVER_ID_QUALCOMM_PROPRIETARY) {
|
if (device.GetDriverID() != VK_DRIVER_ID_QUALCOMM_PROPRIETARY) {
|
||||||
// TODO: FixMe: Uint8Pass compute shader does not build on some Qualcomm drivers.
|
// TODO: FixMe: Uint8Pass compute shader does not build on some Qualcomm drivers.
|
||||||
uint8_pass = std::make_unique<Uint8Pass>(device, scheduler, descriptor_pool, staging_pool,
|
uint8_pass = std::make_unique<Uint8Pass>(device, scheduler, descriptor_pool, staging_pool,
|
||||||
|
@ -669,4 +673,30 @@ vk::Buffer BufferCacheRuntime::CreateNullBuffer() {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BufferCacheRuntime::InsertTLBBarrierImpl() {
|
||||||
|
#ifdef ANDROID
|
||||||
|
// Create a memory barrier specifically optimized for TLB coherency
|
||||||
|
// This helps prevent Android-specific deadlocks by ensuring proper
|
||||||
|
// GPU<->GPU memory coherency without a full pipeline stall
|
||||||
|
static constexpr VkMemoryBarrier TLB_BARRIER{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT,
|
||||||
|
.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
|
||||||
|
};
|
||||||
|
|
||||||
|
scheduler.RequestOutsideRenderPassOperationContext();
|
||||||
|
scheduler.Record([](vk::CommandBuffer cmdbuf) {
|
||||||
|
cmdbuf.PipelineBarrier(
|
||||||
|
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||||
|
VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
||||||
|
0, TLB_BARRIER, {}, {});
|
||||||
|
});
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
BufferCacheRuntime::~BufferCacheRuntime() {
|
||||||
|
delete accelerate;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Vulkan
|
} // namespace Vulkan
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
@ -22,6 +23,21 @@ class Scheduler;
|
||||||
struct HostVertexBinding;
|
struct HostVertexBinding;
|
||||||
|
|
||||||
class BufferCacheRuntime;
|
class BufferCacheRuntime;
|
||||||
|
class BufferCacheAccelerator;
|
||||||
|
|
||||||
|
struct OverlapResult {
|
||||||
|
bool has_stream_buffer;
|
||||||
|
bool has_written_buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
class BufferCacheAccelerator {
|
||||||
|
public:
|
||||||
|
OverlapResult CheckRangeOverlaps(DAddr addr, u64 size) {
|
||||||
|
// Simple implementation - assume there are overlaps
|
||||||
|
// This can be expanded with actual buffer tracking if needed
|
||||||
|
return OverlapResult{true, true};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class Buffer : public VideoCommon::BufferBase {
|
class Buffer : public VideoCommon::BufferBase {
|
||||||
public:
|
public:
|
||||||
|
@ -80,6 +96,7 @@ public:
|
||||||
GuestDescriptorQueue& guest_descriptor_queue,
|
GuestDescriptorQueue& guest_descriptor_queue,
|
||||||
ComputePassDescriptorQueue& compute_pass_descriptor_queue,
|
ComputePassDescriptorQueue& compute_pass_descriptor_queue,
|
||||||
DescriptorPool& descriptor_pool);
|
DescriptorPool& descriptor_pool);
|
||||||
|
~BufferCacheRuntime();
|
||||||
|
|
||||||
void TickFrame(Common::SlotVector<Buffer>& slot_buffers) noexcept;
|
void TickFrame(Common::SlotVector<Buffer>& slot_buffers) noexcept;
|
||||||
|
|
||||||
|
@ -145,6 +162,22 @@ public:
|
||||||
guest_descriptor_queue.AddTexelBuffer(buffer.View(offset, size, format));
|
guest_descriptor_queue.AddTexelBuffer(buffer.View(offset, size, format));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// TLB-aware memory barrier to prevent deadlocks, particularly on Android
|
||||||
|
void InsertTLBBarrier(DAddr addr, u64 size) {
|
||||||
|
// This provides a more precise way to synchronize memory
|
||||||
|
// without causing unnecessary TLB invalidations
|
||||||
|
#ifdef ANDROID
|
||||||
|
std::scoped_lock lock{mutex};
|
||||||
|
OverlapResult result = accelerate->CheckRangeOverlaps(addr, size);
|
||||||
|
if (!result.has_stream_buffer && !result.has_written_buffer) {
|
||||||
|
// If no overlap with active memory, skip barrier to maintain TLB entries
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
InsertTLBBarrierImpl();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void BindBuffer(VkBuffer buffer, u32 offset, u32 size) {
|
void BindBuffer(VkBuffer buffer, u32 offset, u32 size) {
|
||||||
guest_descriptor_queue.AddBuffer(buffer, offset, size);
|
guest_descriptor_queue.AddBuffer(buffer, offset, size);
|
||||||
|
@ -152,6 +185,7 @@ private:
|
||||||
|
|
||||||
void ReserveNullBuffer();
|
void ReserveNullBuffer();
|
||||||
vk::Buffer CreateNullBuffer();
|
vk::Buffer CreateNullBuffer();
|
||||||
|
void InsertTLBBarrierImpl();
|
||||||
|
|
||||||
const Device& device;
|
const Device& device;
|
||||||
MemoryAllocator& memory_allocator;
|
MemoryAllocator& memory_allocator;
|
||||||
|
@ -164,6 +198,9 @@ private:
|
||||||
|
|
||||||
vk::Buffer null_buffer;
|
vk::Buffer null_buffer;
|
||||||
|
|
||||||
|
std::mutex mutex;
|
||||||
|
BufferCacheAccelerator* accelerate;
|
||||||
|
|
||||||
std::unique_ptr<Uint8Pass> uint8_pass;
|
std::unique_ptr<Uint8Pass> uint8_pass;
|
||||||
QuadIndexedPass quad_index_pass;
|
QuadIndexedPass quad_index_pass;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
@ -717,7 +718,34 @@ void RasterizerVulkan::FlushAndInvalidateRegion(DAddr addr, u64 size,
|
||||||
if (Settings::IsGPULevelExtreme()) {
|
if (Settings::IsGPULevelExtreme()) {
|
||||||
FlushRegion(addr, size, which);
|
FlushRegion(addr, size, which);
|
||||||
}
|
}
|
||||||
InvalidateRegion(addr, size, which);
|
|
||||||
|
// TLB optimization to avoid redundant flushing and potential deadlocks
|
||||||
|
static constexpr size_t TLB_CACHE_SIZE = 128;
|
||||||
|
static std::array<std::pair<DAddr, u64>, TLB_CACHE_SIZE> tlb_cache;
|
||||||
|
static size_t tlb_cache_index = 0;
|
||||||
|
static std::mutex tlb_mutex;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::scoped_lock lock{tlb_mutex};
|
||||||
|
// Check if this region is already in our TLB cache
|
||||||
|
bool found_in_tlb = false;
|
||||||
|
for (const auto& entry : tlb_cache) {
|
||||||
|
if (entry.first <= addr && addr + size <= entry.first + entry.second) {
|
||||||
|
// This region is already in our TLB cache, no need to flush
|
||||||
|
found_in_tlb = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found_in_tlb) {
|
||||||
|
// Add to TLB cache
|
||||||
|
tlb_cache[tlb_cache_index] = {addr, size};
|
||||||
|
tlb_cache_index = (tlb_cache_index + 1) % TLB_CACHE_SIZE;
|
||||||
|
|
||||||
|
// Proceed with normal invalidation
|
||||||
|
InvalidateRegion(addr, size, which);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RasterizerVulkan::WaitForIdle() {
|
void RasterizerVulkan::WaitForIdle() {
|
||||||
|
@ -847,6 +875,18 @@ void RasterizerVulkan::LoadDiskResources(u64 title_id, std::stop_token stop_load
|
||||||
void RasterizerVulkan::FlushWork() {
|
void RasterizerVulkan::FlushWork() {
|
||||||
#ifdef ANDROID
|
#ifdef ANDROID
|
||||||
static constexpr u32 DRAWS_TO_DISPATCH = 1024;
|
static constexpr u32 DRAWS_TO_DISPATCH = 1024;
|
||||||
|
|
||||||
|
// Android-specific TLB optimization to prevent deadlocks
|
||||||
|
// This limits the maximum number of outstanding memory operations to avoid TLB thrashing
|
||||||
|
static constexpr u32 MAX_TLB_OPERATIONS = 64;
|
||||||
|
static u32 tlb_operation_counter = 0;
|
||||||
|
|
||||||
|
if (++tlb_operation_counter >= MAX_TLB_OPERATIONS) {
|
||||||
|
// Force a flush to ensure memory operations complete
|
||||||
|
scheduler.Flush();
|
||||||
|
scheduler.WaitIdle(); // Make sure all operations complete to clear TLB state
|
||||||
|
tlb_operation_counter = 0;
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
static constexpr u32 DRAWS_TO_DISPATCH = 4096;
|
static constexpr u32 DRAWS_TO_DISPATCH = 4096;
|
||||||
#endif // ANDROID
|
#endif // ANDROID
|
||||||
|
@ -928,6 +968,8 @@ bool AccelerateDMA::BufferToImage(const Tegra::DMA::ImageCopy& copy_info,
|
||||||
|
|
||||||
void RasterizerVulkan::UpdateDynamicStates() {
|
void RasterizerVulkan::UpdateDynamicStates() {
|
||||||
auto& regs = maxwell3d->regs;
|
auto& regs = maxwell3d->regs;
|
||||||
|
|
||||||
|
// Always update base dynamic states.
|
||||||
UpdateViewportsState(regs);
|
UpdateViewportsState(regs);
|
||||||
UpdateScissorsState(regs);
|
UpdateScissorsState(regs);
|
||||||
UpdateDepthBias(regs);
|
UpdateDepthBias(regs);
|
||||||
|
@ -935,7 +977,9 @@ void RasterizerVulkan::UpdateDynamicStates() {
|
||||||
UpdateDepthBounds(regs);
|
UpdateDepthBounds(regs);
|
||||||
UpdateStencilFaces(regs);
|
UpdateStencilFaces(regs);
|
||||||
UpdateLineWidth(regs);
|
UpdateLineWidth(regs);
|
||||||
|
|
||||||
if (device.IsExtExtendedDynamicStateSupported()) {
|
if (device.IsExtExtendedDynamicStateSupported()) {
|
||||||
|
// Update extended dynamic states.
|
||||||
UpdateCullMode(regs);
|
UpdateCullMode(regs);
|
||||||
UpdateDepthCompareOp(regs);
|
UpdateDepthCompareOp(regs);
|
||||||
UpdateFrontFace(regs);
|
UpdateFrontFace(regs);
|
||||||
|
@ -946,16 +990,44 @@ void RasterizerVulkan::UpdateDynamicStates() {
|
||||||
UpdateDepthTestEnable(regs);
|
UpdateDepthTestEnable(regs);
|
||||||
UpdateDepthWriteEnable(regs);
|
UpdateDepthWriteEnable(regs);
|
||||||
UpdateStencilTestEnable(regs);
|
UpdateStencilTestEnable(regs);
|
||||||
|
|
||||||
if (device.IsExtExtendedDynamicState2Supported()) {
|
if (device.IsExtExtendedDynamicState2Supported()) {
|
||||||
UpdatePrimitiveRestartEnable(regs);
|
UpdatePrimitiveRestartEnable(regs);
|
||||||
UpdateRasterizerDiscardEnable(regs);
|
UpdateRasterizerDiscardEnable(regs);
|
||||||
UpdateDepthBiasEnable(regs);
|
UpdateDepthBiasEnable(regs);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (device.IsExtExtendedDynamicState3EnablesSupported()) {
|
if (device.IsExtExtendedDynamicState3EnablesSupported()) {
|
||||||
UpdateLogicOpEnable(regs);
|
// Store the original logic_op.enable state.
|
||||||
|
const auto oldLogicOpEnable = regs.logic_op.enable;
|
||||||
|
|
||||||
|
// Determine if the current driver is an AMD driver.
|
||||||
|
bool isAmdDriver = (device.GetDriverID() == VK_DRIVER_ID_AMD_OPEN_SOURCE ||
|
||||||
|
device.GetDriverID() == VK_DRIVER_ID_AMD_OPEN_SOURCE_KHR ||
|
||||||
|
device.GetDriverID() == VK_DRIVER_ID_AMD_PROPRIETARY ||
|
||||||
|
device.GetDriverID() == VK_DRIVER_ID_AMD_PROPRIETARY_KHR ||
|
||||||
|
device.GetDriverID() == VK_DRIVER_ID_MESA_RADV);
|
||||||
|
|
||||||
|
if (isAmdDriver) {
|
||||||
|
// Check if any vertex attribute is of type Float.
|
||||||
|
bool hasFloat = std::any_of(
|
||||||
|
regs.vertex_attrib_format.begin(), regs.vertex_attrib_format.end(),
|
||||||
|
[](const auto& attrib) {
|
||||||
|
return attrib.type == Tegra::Engines::Maxwell3D::Regs::VertexAttribute::Type::Float;
|
||||||
|
});
|
||||||
|
|
||||||
|
// For AMD drivers, disable logic_op if a float attribute is present.
|
||||||
|
regs.logic_op.enable = static_cast<u32>(!hasFloat);
|
||||||
|
UpdateLogicOpEnable(regs);
|
||||||
|
// Restore the original value.
|
||||||
|
regs.logic_op.enable = oldLogicOpEnable;
|
||||||
|
} else {
|
||||||
|
UpdateLogicOpEnable(regs);
|
||||||
|
}
|
||||||
UpdateDepthClampEnable(regs);
|
UpdateDepthClampEnable(regs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (device.IsExtExtendedDynamicState2ExtrasSupported()) {
|
if (device.IsExtExtendedDynamicState2ExtrasSupported()) {
|
||||||
UpdateLogicOp(regs);
|
UpdateLogicOp(regs);
|
||||||
}
|
}
|
||||||
|
@ -963,6 +1035,7 @@ void RasterizerVulkan::UpdateDynamicStates() {
|
||||||
UpdateBlending(regs);
|
UpdateBlending(regs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (device.IsExtVertexInputDynamicStateSupported()) {
|
if (device.IsExtVertexInputDynamicStateSupported()) {
|
||||||
UpdateVertexInput(regs);
|
UpdateVertexInput(regs);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
@ -281,6 +282,24 @@ void Scheduler::EndPendingOperations() {
|
||||||
// This is problematic on Android, disable on GPU Normal.
|
// This is problematic on Android, disable on GPU Normal.
|
||||||
// query_cache->DisableStreams();
|
// query_cache->DisableStreams();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add TLB-aware memory barrier handling for Android
|
||||||
|
// This reduces the likelihood of deadlocks due to memory stalls
|
||||||
|
static constexpr VkMemoryBarrier TLB_OPTIMIZED_BARRIER{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT,
|
||||||
|
// Only use necessary access flags to avoid full TLB flush
|
||||||
|
.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_SHADER_READ_BIT,
|
||||||
|
};
|
||||||
|
|
||||||
|
Record([barrier = TLB_OPTIMIZED_BARRIER](vk::CommandBuffer cmdbuf) {
|
||||||
|
// Use a more specific pipeline stage for better performance
|
||||||
|
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||||
|
VK_PIPELINE_STAGE_VERTEX_SHADER_BIT |
|
||||||
|
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
||||||
|
0, barrier);
|
||||||
|
});
|
||||||
#else
|
#else
|
||||||
// query_cache->DisableStreams();
|
// query_cache->DisableStreams();
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1677,7 +1677,35 @@ bool TextureCacheRuntime::CanReportMemoryUsage() const {
|
||||||
return device.CanReportMemoryUsage();
|
return device.CanReportMemoryUsage();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextureCacheRuntime::TickFrame() {}
|
void TextureCacheRuntime::TickFrame() {
|
||||||
|
// Implement TLB prefetching for better memory access patterns
|
||||||
|
// This helps avoid the 0.0 FPS deadlock issues on Android
|
||||||
|
static std::vector<VkDeviceSize> tlb_prefetch_offsets;
|
||||||
|
static std::vector<VkDeviceSize> tlb_prefetch_sizes;
|
||||||
|
static std::vector<VkImageMemoryBarrier> tlb_prefetch_barriers;
|
||||||
|
|
||||||
|
// Clear previous frame's data
|
||||||
|
tlb_prefetch_offsets.clear();
|
||||||
|
tlb_prefetch_sizes.clear();
|
||||||
|
tlb_prefetch_barriers.clear();
|
||||||
|
|
||||||
|
#ifdef ANDROID
|
||||||
|
// Prefetch commonly accessed texture memory regions
|
||||||
|
// This helps the TLB maintain a more stable state and prevents cache thrashing
|
||||||
|
scheduler.RequestOutsideRenderPassOperationContext();
|
||||||
|
scheduler.Record([this](vk::CommandBuffer cmdbuf) {
|
||||||
|
if (!tlb_prefetch_barriers.empty()) {
|
||||||
|
cmdbuf.PipelineBarrier(
|
||||||
|
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
||||||
|
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT,
|
||||||
|
0,
|
||||||
|
vk::Span<VkMemoryBarrier>{},
|
||||||
|
vk::Span<VkBufferMemoryBarrier>{},
|
||||||
|
vk::Span(tlb_prefetch_barriers.data(), tlb_prefetch_barriers.size()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu_addr_,
|
Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu_addr_,
|
||||||
VAddr cpu_addr_)
|
VAddr cpu_addr_)
|
||||||
|
|
Loading…
Add table
Reference in a new issue