[turnip/android] Add environment variables settings for turnip drivers (#3205)
This PR brings a feature that has been needed for some time in the Android Switch emulation community: environment variables for Turnip/Freedreno drivers. These are available in PC emulators and can help fix some problems, especially the TU_DEBUG function, which can be set to gmem (thus allowing Adreno 710/720 users to run Turnip correctly), and noubwc, which fixes some problems for OneUI users. This could also help us debug Turnip in a "better way" in the future. Attached is a screenshot of a user, Ivan albio, using the gmem function on Adreno 710. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3205 Reviewed-by: CamilleLaVey <camillelavey99@gmail.com> Reviewed-by: DraVee <dravee@eden-emu.dev> Co-authored-by: MrPurple666 <antoniosacramento666usa@gmail.com> Co-committed-by: MrPurple666 <antoniosacramento666usa@gmail.com>
This commit is contained in:
parent
1370f23675
commit
87d4c67386
|
|
@ -71,9 +71,9 @@
|
|||
"hash": "9697e80a7d5d9bcb3ce51051a9a24962fb90ca79d215f1f03ae6b58da8ba13a63b5dda1b4dde3d26ac6445029696b8ef2883f4e5a777b342bba01283ed293856"
|
||||
},
|
||||
"libadrenotools": {
|
||||
"repo": "bylaws/libadrenotools",
|
||||
"sha": "8fae8ce254",
|
||||
"hash": "db4a74ce15559c75e01d1868a90701519b655d77f2a343bbee283a42f8332dc9046960fb022dc969f205e457348a3f99cb8be6e1cd91264d2ae1235294b9f9b2",
|
||||
"repo": "eden-emulator/libadrenotools",
|
||||
"sha": "8ba23b42d7",
|
||||
"hash": "f6526620cb752876edc5ed4c0925d57b873a8218ee09ad10859ee476e9333259784f61c1dcc55a2bcba597352d18aff22cd2e4c1925ec2ae94074e09d7da2265",
|
||||
"patches": [
|
||||
"0001-linkerns-cpm.patch"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -61,6 +61,11 @@ class YuzuApplication : Application() {
|
|||
application = this
|
||||
documentsTree = DocumentsTree()
|
||||
DirectoryInitialization.start()
|
||||
|
||||
// Initialize Freedreno config BEFORE loading native library
|
||||
// This ensures GPU driver environment variables are set before adrenotools initializes
|
||||
GpuDriverHelper.initializeFreedrenoConfigEarly()
|
||||
|
||||
NativeLibrary.playTimeManagerInit()
|
||||
GpuDriverHelper.initializeDriverParameters()
|
||||
NativeInput.reloadInputDevices()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.adapters
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.yuzu.yuzu_emu.utils.FreedrenoPreset
|
||||
import org.yuzu.yuzu_emu.databinding.ListItemFreedrenoPresetBinding
|
||||
|
||||
/**
|
||||
* Adapter for displaying Freedreno preset configurations in a horizontal list.
|
||||
*/
|
||||
class FreedrenoPresetAdapter(
|
||||
private val onPresetClicked: (FreedrenoPreset) -> Unit
|
||||
) : ListAdapter<FreedrenoPreset, FreedrenoPresetAdapter.PresetViewHolder>(DiffCallback) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PresetViewHolder {
|
||||
val binding = ListItemFreedrenoPresetBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
return PresetViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: PresetViewHolder, position: Int) {
|
||||
holder.bind(getItem(position))
|
||||
}
|
||||
|
||||
inner class PresetViewHolder(private val binding: ListItemFreedrenoPresetBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(preset: FreedrenoPreset) {
|
||||
binding.presetButton.apply {
|
||||
text = preset.name
|
||||
setOnClickListener {
|
||||
onPresetClicked(preset)
|
||||
}
|
||||
contentDescription = "${preset.name}: ${preset.description}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val DiffCallback = object : DiffUtil.ItemCallback<FreedrenoPreset>() {
|
||||
override fun areItemsTheSame(oldItem: FreedrenoPreset, newItem: FreedrenoPreset): Boolean =
|
||||
oldItem.name == newItem.name
|
||||
|
||||
override fun areContentsTheSame(oldItem: FreedrenoPreset, newItem: FreedrenoPreset): Boolean =
|
||||
oldItem == newItem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.adapters
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.yuzu.yuzu_emu.databinding.ListItemFreedrenoVariableBinding
|
||||
import org.yuzu.yuzu_emu.fragments.FreedrenoVariable
|
||||
import org.yuzu.yuzu_emu.utils.NativeFreedrenoConfig
|
||||
|
||||
/**
|
||||
* Adapter for displaying currently set Freedreno environment variables in a list.
|
||||
*/
|
||||
class FreedrenoVariableAdapter(
|
||||
private val context: Context,
|
||||
private val onItemClicked: (FreedrenoVariable, () -> Unit) -> Unit
|
||||
) : ListAdapter<FreedrenoVariable, FreedrenoVariableAdapter.VariableViewHolder>(DiffCallback) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VariableViewHolder {
|
||||
val binding = ListItemFreedrenoVariableBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
return VariableViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: VariableViewHolder, position: Int) {
|
||||
holder.bind(getItem(position))
|
||||
}
|
||||
|
||||
inner class VariableViewHolder(private val binding: ListItemFreedrenoVariableBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(variable: FreedrenoVariable) {
|
||||
binding.variableName.text = variable.name
|
||||
binding.variableValue.text = variable.value
|
||||
|
||||
binding.buttonDelete.setOnClickListener {
|
||||
onItemClicked(variable) {
|
||||
NativeFreedrenoConfig.clearFreedrenoEnv(variable.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val DiffCallback = object : DiffUtil.ItemCallback<FreedrenoVariable>() {
|
||||
override fun areItemsTheSame(oldItem: FreedrenoVariable, newItem: FreedrenoVariable): Boolean =
|
||||
oldItem.name == newItem.name
|
||||
|
||||
override fun areContentsTheSame(oldItem: FreedrenoVariable, newItem: FreedrenoVariable): Boolean =
|
||||
oldItem == newItem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -27,6 +27,7 @@ object Settings {
|
|||
SECTION_APP_SETTINGS(R.string.app_settings),
|
||||
SECTION_CUSTOM_PATHS(R.string.preferences_custom_paths),
|
||||
SECTION_DEBUG(R.string.preferences_debug),
|
||||
SECTION_FREEDRENO(R.string.gpu_driver_settings),
|
||||
SECTION_APPLETS(R.string.applets_menu);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import org.yuzu.yuzu_emu.databinding.ListItemSettingsHeaderBinding
|
|||
import org.yuzu.yuzu_emu.features.input.NativeInput
|
||||
import org.yuzu.yuzu_emu.features.input.model.AnalogDirection
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.*
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.viewholder.*
|
||||
import org.yuzu.yuzu_emu.utils.ParamPackage
|
||||
|
|
@ -212,8 +213,15 @@ class SettingsAdapter(
|
|||
}
|
||||
|
||||
fun onSubmenuClick(item: SubmenuSetting) {
|
||||
val action = SettingsNavigationDirections.actionGlobalSettingsFragment(item.menuKey, null)
|
||||
fragment.view?.findNavController()?.navigate(action)
|
||||
// Check if this is the Freedreno Settings submenu
|
||||
if (item.menuKey == Settings.MenuTag.SECTION_FREEDRENO) {
|
||||
fragment.view?.findNavController()?.navigate(
|
||||
R.id.action_settingsFragment_to_freedrenoSettingsFragment
|
||||
)
|
||||
} else {
|
||||
val action = SettingsNavigationDirections.actionGlobalSettingsFragment(item.menuKey, null)
|
||||
fragment.view?.findNavController()?.navigate(action)
|
||||
}
|
||||
}
|
||||
|
||||
fun onLaunchableClick(item: LaunchableSetting) {
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import org.yuzu.yuzu_emu.features.settings.model.Settings.MenuTag
|
|||
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.*
|
||||
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
|
||||
import org.yuzu.yuzu_emu.utils.InputHandler
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
||||
|
|
@ -109,6 +110,7 @@ class SettingsFragmentPresenter(
|
|||
MenuTag.SECTION_INPUT_PLAYER_EIGHT -> addInputPlayer(sl, 7)
|
||||
MenuTag.SECTION_APP_SETTINGS -> addThemeSettings(sl)
|
||||
MenuTag.SECTION_DEBUG -> addDebugSettings(sl)
|
||||
MenuTag.SECTION_FREEDRENO -> addFreedrenoSettings(sl)
|
||||
MenuTag.SECTION_APPLETS -> addAppletSettings(sl)
|
||||
MenuTag.SECTION_CUSTOM_PATHS -> addCustomPathsSettings(sl)
|
||||
}
|
||||
|
|
@ -181,6 +183,16 @@ class SettingsFragmentPresenter(
|
|||
menuKey = MenuTag.SECTION_DEBUG
|
||||
)
|
||||
)
|
||||
if (GpuDriverHelper.isAdrenoGpu() && !NativeConfig.isPerGameConfigLoaded()) {
|
||||
add(
|
||||
SubmenuSetting(
|
||||
titleId = R.string.gpu_driver_settings,
|
||||
descriptionId = R.string.freedreno_settings_title,
|
||||
iconId = R.drawable.ic_graphics,
|
||||
menuKey = MenuTag.SECTION_FREEDRENO
|
||||
)
|
||||
)
|
||||
}
|
||||
add(
|
||||
SubmenuSetting(
|
||||
titleId = R.string.applets_menu,
|
||||
|
|
@ -498,6 +510,11 @@ class SettingsFragmentPresenter(
|
|||
}
|
||||
}
|
||||
|
||||
private fun addFreedrenoSettings(sl: ArrayList<SettingsItem>) {
|
||||
// No additional settings needed here - the SubmenuSetting handles navigation
|
||||
// This method is kept for consistency with other menu sections
|
||||
}
|
||||
|
||||
private fun addAppletSettings(sl: ArrayList<SettingsItem>) {
|
||||
sl.apply {
|
||||
add(IntSetting.SWKBD_APPLET.key)
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ class DriverFetcherFragment : Fragment() {
|
|||
private val client = OkHttpClient()
|
||||
|
||||
private val gpuModel: String?
|
||||
get() = GpuDriverHelper.getGpuModel()
|
||||
get() = GpuDriverHelper.hookLibPath?.let { GpuDriverHelper.getGpuModel(hookLibPath = it) }
|
||||
|
||||
private val adrenoModel: Int
|
||||
get() = parseAdrenoModel()
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ import org.yuzu.yuzu_emu.utils.GameIconUtils
|
|||
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
|
||||
import org.yuzu.yuzu_emu.utils.Log
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
import org.yuzu.yuzu_emu.utils.NativeFreedrenoConfig
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
|
|
@ -303,6 +304,18 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
throw fallbackException
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (GpuDriverHelper.isAdrenoGpu()) {
|
||||
val programIdHex = game!!.programIdHex
|
||||
if (NativeFreedrenoConfig.loadPerGameConfigWithGlobalFallback(programIdHex)) {
|
||||
Log.info("[EmulationFragment] Loaded per-game Freedreno config for $programIdHex")
|
||||
} else {
|
||||
Log.info("[EmulationFragment] Using global Freedreno config for $programIdHex")
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.warning("[EmulationFragment] Failed to load Freedreno config: ${e.message}")
|
||||
}
|
||||
|
||||
emulationState = EmulationState(game!!.path) {
|
||||
return@EmulationState driverViewModel.isInteractionAllowed.value
|
||||
|
|
@ -616,7 +629,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
}
|
||||
Log.info("[EmulationFragment] Starting view setup for game: ${game?.title}")
|
||||
|
||||
gpuModel = GpuDriverHelper.getGpuModel().toString()
|
||||
gpuModel = GpuDriverHelper.hookLibPath?.let { GpuDriverHelper.getGpuModel(hookLibPath = it).toString() } ?: "Unknown"
|
||||
fwVersion = NativeLibrary.firmwareVersion()
|
||||
|
||||
updateQuickOverlayMenuEntry(BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean())
|
||||
|
|
|
|||
|
|
@ -0,0 +1,204 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.android.material.transition.MaterialSharedAxis
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.adapters.FreedrenoPresetAdapter
|
||||
import org.yuzu.yuzu_emu.adapters.FreedrenoVariableAdapter
|
||||
import org.yuzu.yuzu_emu.databinding.FragmentFreedrenoSettingsBinding
|
||||
import org.yuzu.yuzu_emu.model.Game
|
||||
import org.yuzu.yuzu_emu.utils.NativeFreedrenoConfig
|
||||
import org.yuzu.yuzu_emu.utils.FreedrenoPresets
|
||||
|
||||
|
||||
class FreedrenoSettingsFragment : Fragment() {
|
||||
private var _binding: FragmentFreedrenoSettingsBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
private val args by navArgs<FreedrenoSettingsFragmentArgs>()
|
||||
private val game: Game? get() = args.game
|
||||
private val isPerGameConfig: Boolean get() = game != null
|
||||
|
||||
private lateinit var presetAdapter: FreedrenoPresetAdapter
|
||||
private lateinit var settingsAdapter: FreedrenoVariableAdapter
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
||||
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
|
||||
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
|
||||
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = FragmentFreedrenoSettingsBinding.inflate(layoutInflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
NativeFreedrenoConfig.setFreedrenoBasePath(requireContext().cacheDir.absolutePath)
|
||||
NativeFreedrenoConfig.initializeFreedrenoConfig()
|
||||
|
||||
if (isPerGameConfig) {
|
||||
NativeFreedrenoConfig.loadPerGameConfig(game!!.programIdHex)
|
||||
} else {
|
||||
NativeFreedrenoConfig.reloadFreedrenoConfig()
|
||||
}
|
||||
|
||||
setupToolbar()
|
||||
setupAdapters()
|
||||
loadCurrentSettings()
|
||||
setupButtonListeners()
|
||||
setupWindowInsets()
|
||||
}
|
||||
|
||||
private fun setupToolbar() {
|
||||
binding.toolbarFreedreno.setNavigationOnClickListener {
|
||||
requireActivity().onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
if (isPerGameConfig) {
|
||||
binding.toolbarFreedreno.title = getString(R.string.freedreno_per_game_title)
|
||||
binding.toolbarFreedreno.subtitle = game!!.title
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupAdapters() {
|
||||
// Setup presets adapter (horizontal list)
|
||||
presetAdapter = FreedrenoPresetAdapter { preset ->
|
||||
applyPreset(preset)
|
||||
}
|
||||
binding.listFreedrenoPresets.apply {
|
||||
adapter = presetAdapter
|
||||
layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false)
|
||||
}
|
||||
presetAdapter.submitList(FreedrenoPresets.ALL_PRESETS)
|
||||
|
||||
// Setup current settings adapter (vertical list)
|
||||
settingsAdapter = FreedrenoVariableAdapter(requireContext()) { variable, onDelete ->
|
||||
onDelete()
|
||||
loadCurrentSettings() // Refresh list after deletion
|
||||
}
|
||||
binding.listFreedrenoSettings.apply {
|
||||
adapter = settingsAdapter
|
||||
layoutManager = LinearLayoutManager(requireContext())
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadCurrentSettings() {
|
||||
// Load all currently set environment variables
|
||||
val variables = mutableListOf<FreedrenoVariable>()
|
||||
|
||||
// Common variables to check
|
||||
val commonVars = listOf(
|
||||
"TU_DEBUG", "FD_MESA_DEBUG", "IR3_SHADER_DEBUG",
|
||||
"FD_RD_DUMP", "FD_RD_DUMP_FRAMES", "FD_RD_DUMP_TESTNAME",
|
||||
"TU_BREADCRUMBS"
|
||||
)
|
||||
|
||||
for (varName in commonVars) {
|
||||
if (NativeFreedrenoConfig.isFreedrenoEnvSet(varName)) {
|
||||
val value = NativeFreedrenoConfig.getFreedrenoEnv(varName)
|
||||
variables.add(FreedrenoVariable(varName, value))
|
||||
}
|
||||
}
|
||||
|
||||
settingsAdapter.submitList(variables)
|
||||
}
|
||||
|
||||
private fun setupButtonListeners() {
|
||||
binding.buttonAddVariable.setOnClickListener {
|
||||
val varName = binding.variableNameInput.text.toString().trim()
|
||||
val varValue = binding.variableValueInput.text.toString().trim()
|
||||
|
||||
if (varName.isEmpty()) {
|
||||
showSnackbar(getString(R.string.freedreno_error_empty_name))
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
if (NativeFreedrenoConfig.setFreedrenoEnv(varName, varValue)) {
|
||||
showSnackbar(getString(R.string.freedreno_variable_added, varName))
|
||||
binding.variableNameInput.text?.clear()
|
||||
binding.variableValueInput.text?.clear()
|
||||
loadCurrentSettings()
|
||||
} else {
|
||||
showSnackbar(getString(R.string.freedreno_error_setting_variable))
|
||||
}
|
||||
}
|
||||
|
||||
binding.buttonClearAll.setOnClickListener {
|
||||
NativeFreedrenoConfig.clearAllFreedrenoEnv()
|
||||
showSnackbar(getString(R.string.freedreno_cleared_all))
|
||||
loadCurrentSettings()
|
||||
}
|
||||
|
||||
binding.buttonSave.setOnClickListener {
|
||||
if (isPerGameConfig) {
|
||||
NativeFreedrenoConfig.savePerGameConfig(game!!.programIdHex)
|
||||
showSnackbar(getString(R.string.freedreno_per_game_saved))
|
||||
} else {
|
||||
NativeFreedrenoConfig.saveFreedrenoConfig()
|
||||
showSnackbar(getString(R.string.freedreno_saved))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyPreset(preset: org.yuzu.yuzu_emu.utils.FreedrenoPreset) {
|
||||
// Clear all first for consistency
|
||||
NativeFreedrenoConfig.clearAllFreedrenoEnv()
|
||||
|
||||
// Apply all variables in the preset
|
||||
for ((varName, varValue) in preset.variables) {
|
||||
NativeFreedrenoConfig.setFreedrenoEnv(varName, varValue)
|
||||
}
|
||||
|
||||
showSnackbar(getString(R.string.freedreno_preset_applied, preset.name))
|
||||
loadCurrentSettings()
|
||||
}
|
||||
|
||||
private fun setupWindowInsets() {
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, insets ->
|
||||
val systemInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
binding.root.updatePadding(
|
||||
left = systemInsets.left,
|
||||
right = systemInsets.right,
|
||||
bottom = systemInsets.bottom
|
||||
)
|
||||
insets
|
||||
}
|
||||
}
|
||||
|
||||
private fun showSnackbar(message: String) {
|
||||
Snackbar.make(binding.root, message, Snackbar.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data class representing a Freedreno environment variable.
|
||||
*/
|
||||
data class FreedrenoVariable(
|
||||
val name: String,
|
||||
val value: String
|
||||
)
|
||||
|
|
@ -325,6 +325,20 @@ class GamePropertiesFragment : Fragment() {
|
|||
)
|
||||
)
|
||||
}
|
||||
if (GpuDriverHelper.isAdrenoGpu()) {
|
||||
add(
|
||||
SubmenuProperty(
|
||||
R.string.freedreno_per_game_title,
|
||||
R.string.freedreno_per_game_description,
|
||||
R.drawable.ic_graphics,
|
||||
action = {
|
||||
val action = GamePropertiesFragmentDirections
|
||||
.actionPerGamePropertiesFragmentToFreedrenoSettingsFragment(args.game)
|
||||
binding.root.findNavController().navigate(action)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if (!args.game.isHomebrew) {
|
||||
add(
|
||||
|
|
|
|||
|
|
@ -23,10 +23,16 @@ object GpuDriverHelper {
|
|||
private const val META_JSON_FILENAME = "meta.json"
|
||||
private var fileRedirectionPath: String? = null
|
||||
var driverInstallationPath: String? = null
|
||||
private var hookLibPath: String? = null
|
||||
internal var hookLibPath: String? = null
|
||||
|
||||
val driverStoragePath get() = DirectoryInitialization.userDirectory!! + "/gpu_drivers/"
|
||||
|
||||
fun initializeFreedrenoConfigEarly() {
|
||||
NativeFreedrenoConfig.setFreedrenoBasePath(YuzuApplication.appContext.cacheDir.absolutePath)
|
||||
NativeFreedrenoConfig.initializeFreedrenoConfig()
|
||||
NativeFreedrenoConfig.reloadFreedrenoConfig()
|
||||
}
|
||||
|
||||
fun initializeDriverParameters() {
|
||||
try {
|
||||
// Initialize the file redirection directory.
|
||||
|
|
@ -40,11 +46,9 @@ object GpuDriverHelper {
|
|||
throw RuntimeException(e)
|
||||
}
|
||||
|
||||
// Initialize directories.
|
||||
initializeDirectories()
|
||||
|
||||
// Initialize hook libraries directory.
|
||||
hookLibPath = YuzuApplication.appContext.applicationInfo.nativeLibraryDir + "/"
|
||||
NativeFreedrenoConfig.reloadFreedrenoConfig()
|
||||
|
||||
// Initialize GPU driver.
|
||||
NativeLibrary.initializeGpuDriver(
|
||||
|
|
@ -211,9 +215,17 @@ object GpuDriverHelper {
|
|||
|
||||
external fun getGpuModel(
|
||||
surface: Surface = Surface(SurfaceTexture(true)),
|
||||
hookLibPath: String = GpuDriverHelper.hookLibPath!!
|
||||
hookLibPath: String
|
||||
): String?
|
||||
|
||||
fun isAdrenoGpu(): Boolean {
|
||||
return try {
|
||||
supportsCustomDriverLoading()
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the custom driver metadata to retrieve the name.
|
||||
val installedCustomDriverData: GpuDriverMetadata
|
||||
get() = GpuDriverMetadata(File(driverInstallationPath + META_JSON_FILENAME))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,164 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
|
||||
/**
|
||||
* Provides access to Freedreno/Turnip driver configuration through JNI bindings.
|
||||
*
|
||||
* This class allows Java/Kotlin code to configure Freedreno environment variables
|
||||
* for the GPU driver (Turnip/Freedreno) that runs in the emulator on Android.
|
||||
*
|
||||
* Variables must be set BEFORE starting emulation for them to take effect.
|
||||
*
|
||||
* See https://docs.mesa3d.org/drivers/freedreno.html for documentation.
|
||||
*/
|
||||
object NativeFreedrenoConfig {
|
||||
|
||||
@Synchronized
|
||||
external fun setFreedrenoBasePath(basePath: String)
|
||||
|
||||
@Synchronized
|
||||
external fun initializeFreedrenoConfig()
|
||||
|
||||
@Synchronized
|
||||
external fun saveFreedrenoConfig()
|
||||
|
||||
@Synchronized
|
||||
external fun reloadFreedrenoConfig()
|
||||
|
||||
@Synchronized
|
||||
external fun setFreedrenoEnv(varName: String, value: String): Boolean
|
||||
|
||||
@Synchronized
|
||||
external fun getFreedrenoEnv(varName: String): String
|
||||
|
||||
@Synchronized
|
||||
external fun isFreedrenoEnvSet(varName: String): Boolean
|
||||
|
||||
@Synchronized
|
||||
external fun clearFreedrenoEnv(varName: String): Boolean
|
||||
|
||||
@Synchronized
|
||||
external fun clearAllFreedrenoEnv()
|
||||
|
||||
@Synchronized
|
||||
external fun getFreedrenoEnvSummary(): String
|
||||
|
||||
@Synchronized
|
||||
external fun setCurrentProgramId(programId: String)
|
||||
|
||||
@Synchronized
|
||||
external fun loadPerGameConfig(programId: String): Boolean
|
||||
|
||||
@Synchronized
|
||||
external fun loadPerGameConfigWithGlobalFallback(programId: String): Boolean
|
||||
|
||||
@Synchronized
|
||||
external fun savePerGameConfig(programId: String): Boolean
|
||||
|
||||
@Synchronized
|
||||
external fun hasPerGameConfig(programId: String): Boolean
|
||||
|
||||
@Synchronized
|
||||
external fun deletePerGameConfig(programId: String): Boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Data class representing a Freedreno preset configuration.
|
||||
* Presets are commonly used debugging/profiling configurations.
|
||||
*/
|
||||
data class FreedrenoPreset(
|
||||
val name: String, // Display name (e.g., "Debug - CPU Memory")
|
||||
val description: String, // Description of what this preset does
|
||||
val icon: String, // Icon identifier
|
||||
val variables: Map<String, String> // Map of env vars to set
|
||||
)
|
||||
|
||||
/**
|
||||
* Predefined Freedreno presets for quick configuration.
|
||||
*/
|
||||
object FreedrenoPresets {
|
||||
|
||||
val DEBUG_CPU_MEMORY = FreedrenoPreset(
|
||||
name = "Debug - CPU Memory",
|
||||
description = "Use CPU memory (slower but more stable)",
|
||||
icon = "ic_debug_cpu",
|
||||
variables = mapOf(
|
||||
"TU_DEBUG" to "sysmem"
|
||||
)
|
||||
)
|
||||
|
||||
val DEBUG_UBWC_DISABLED = FreedrenoPreset(
|
||||
name = "Debug - No UBWC",
|
||||
description = "Disable UBWC compression for debugging",
|
||||
icon = "ic_debug_ubwc",
|
||||
variables = mapOf(
|
||||
"TU_DEBUG" to "noubwc"
|
||||
)
|
||||
)
|
||||
|
||||
val DEBUG_NO_BINNING = FreedrenoPreset(
|
||||
name = "Debug - No Binning",
|
||||
description = "Disable binning optimization",
|
||||
icon = "ic_debug_bin",
|
||||
variables = mapOf(
|
||||
"TU_DEBUG" to "nobin"
|
||||
)
|
||||
)
|
||||
|
||||
val CAPTURE_RENDERPASS = FreedrenoPreset(
|
||||
name = "Capture - Renderpass",
|
||||
description = "Capture command stream data for debugging",
|
||||
icon = "ic_capture",
|
||||
variables = mapOf(
|
||||
"FD_RD_DUMP" to "enable"
|
||||
)
|
||||
)
|
||||
|
||||
val CAPTURE_FRAMES = FreedrenoPreset(
|
||||
name = "Capture - First 100 Frames",
|
||||
description = "Capture command stream for first 100 frames only",
|
||||
icon = "ic_capture",
|
||||
variables = mapOf(
|
||||
"FD_RD_DUMP" to "enable",
|
||||
"FD_RD_DUMP_FRAMES" to "0-100"
|
||||
)
|
||||
)
|
||||
|
||||
val SHADER_DEBUG = FreedrenoPreset(
|
||||
name = "Shader Debug",
|
||||
description = "Enable IR3 shader compiler debugging",
|
||||
icon = "ic_shader",
|
||||
variables = mapOf(
|
||||
"IR3_SHADER_DEBUG" to "nouboopt,spillall"
|
||||
)
|
||||
)
|
||||
|
||||
val GPU_HANG_TRACE = FreedrenoPreset(
|
||||
name = "GPU Hang Trace",
|
||||
description = "Trace GPU progress for debugging hangs",
|
||||
icon = "ic_hang_trace",
|
||||
variables = mapOf(
|
||||
"TU_BREADCRUMBS" to "1"
|
||||
)
|
||||
)
|
||||
|
||||
val PERFORMANCE_DEFAULT = FreedrenoPreset(
|
||||
name = "Performance - Default",
|
||||
description = "Clear all debug options for performance",
|
||||
icon = "ic_performance",
|
||||
variables = emptyMap() // Clears all when applied
|
||||
)
|
||||
|
||||
val ALL_PRESETS = listOf(
|
||||
DEBUG_CPU_MEMORY,
|
||||
DEBUG_UBWC_DISABLED,
|
||||
DEBUG_NO_BINNING,
|
||||
CAPTURE_RENDERPASS,
|
||||
CAPTURE_FRAMES,
|
||||
SHADER_DEBUG,
|
||||
GPU_HANG_TRACE,
|
||||
PERFORMANCE_DEFAULT
|
||||
)
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ add_library(yuzu-android SHARED
|
|||
native.cpp
|
||||
native.h
|
||||
native_config.cpp
|
||||
native_freedreno.cpp
|
||||
android_settings.cpp
|
||||
game_metadata.cpp
|
||||
native_log.cpp
|
||||
|
|
|
|||
|
|
@ -730,11 +730,28 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_doesUpdateMatchProgram(JNIEnv* en
|
|||
return false;
|
||||
}
|
||||
|
||||
void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeGpuDriver(JNIEnv* env, jclass clazz,
|
||||
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) {
|
||||
// Log active Freedreno environment variables
|
||||
const char* tu_debug = getenv("TU_DEBUG");
|
||||
const char* fd_debug = getenv("FD_MESA_DEBUG");
|
||||
const char* ir3_debug = getenv("IR3_SHADER_DEBUG");
|
||||
const char* fd_rd_dump = getenv("FD_RD_DUMP");
|
||||
const char* tu_breadcrumbs = getenv("TU_BREADCRUMBS");
|
||||
|
||||
if (tu_debug || fd_debug || ir3_debug || fd_rd_dump || tu_breadcrumbs) {
|
||||
LOG_INFO(Frontend, "[Freedreno] Initializing GPU driver with configuration:");
|
||||
if (tu_debug) LOG_INFO(Frontend, "[Freedreno] TU_DEBUG={}", tu_debug);
|
||||
if (fd_debug) LOG_INFO(Frontend, "[Freedreno] FD_MESA_DEBUG={}", fd_debug);
|
||||
if (ir3_debug) LOG_INFO(Frontend, "[Freedreno] IR3_SHADER_DEBUG={}", ir3_debug);
|
||||
if (fd_rd_dump) LOG_INFO(Frontend, "[Freedreno] FD_RD_DUMP={}", fd_rd_dump);
|
||||
if (tu_breadcrumbs) LOG_INFO(Frontend, "[Freedreno] TU_BREADCRUMBS={}", tu_breadcrumbs);
|
||||
}
|
||||
|
||||
EmulationSession::GetInstance().InitializeGpuDriver(
|
||||
Common::Android::GetJString(env, hook_lib_dir),
|
||||
Common::Android::GetJString(env, custom_driver_dir),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,477 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/**
|
||||
* @file native_freedreno.cpp
|
||||
* @brief JNI bindings for Freedreno/Turnip GPU driver configuration.
|
||||
*
|
||||
* Provides runtime configuration of Mesa Freedreno environment variables
|
||||
* for the Turnip Vulkan driver on Adreno GPUs.
|
||||
*
|
||||
* @see https://docs.mesa3d.org/drivers/freedreno.html
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <cerrno>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include "common/android/android_common.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "native.h"
|
||||
|
||||
namespace {
|
||||
|
||||
struct FreedrenoConfig {
|
||||
std::map<std::string, std::string> env_vars;
|
||||
std::string config_file_path;
|
||||
};
|
||||
|
||||
std::unique_ptr<FreedrenoConfig> g_config;
|
||||
std::string g_base_path;
|
||||
std::string g_current_program_id;
|
||||
|
||||
constexpr const char* kConfigFileName = ".freedreno.conf";
|
||||
constexpr const char* kPerGameConfigDir = "freedreno_games";
|
||||
|
||||
void LogActiveVariables() {
|
||||
if (!g_config || g_config->env_vars.empty()) {
|
||||
return;
|
||||
}
|
||||
for (const auto& [key, value] : g_config->env_vars) {
|
||||
LOG_INFO(Frontend, "[Freedreno] {}={}", key, value);
|
||||
}
|
||||
}
|
||||
|
||||
bool ApplyEnvironmentVariable(const std::string& key, const std::string& value) {
|
||||
if (setenv(key.c_str(), value.c_str(), 1) != 0) {
|
||||
LOG_ERROR(Frontend, "[Freedreno] Failed to set {}={} (errno: {})", key, value, errno);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ClearAllEnvironmentVariables() {
|
||||
if (!g_config) return;
|
||||
for (const auto& [key, value] : g_config->env_vars) {
|
||||
unsetenv(key.c_str());
|
||||
}
|
||||
g_config->env_vars.clear();
|
||||
}
|
||||
|
||||
std::string GetConfigPath() {
|
||||
return g_base_path + "/" + kConfigFileName;
|
||||
}
|
||||
|
||||
std::string GetPerGameConfigPath(const std::string& program_id) {
|
||||
return g_base_path + "/" + kPerGameConfigDir + "/" + program_id + ".conf";
|
||||
}
|
||||
|
||||
void EnsurePerGameConfigDir() {
|
||||
std::string dir_path = g_base_path + "/" + kPerGameConfigDir;
|
||||
mkdir(dir_path.c_str(), 0755);
|
||||
}
|
||||
|
||||
bool LoadConfigFromFile(const std::string& config_path) {
|
||||
if (!g_config) return false;
|
||||
|
||||
FILE* file = fopen(config_path.c_str(), "r");
|
||||
if (!file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char line[512];
|
||||
int count = 0;
|
||||
while (fgets(line, sizeof(line), file)) {
|
||||
size_t len = strlen(line);
|
||||
if (len > 0 && line[len - 1] == '\n') {
|
||||
line[len - 1] = '\0';
|
||||
len--;
|
||||
}
|
||||
|
||||
if (len == 0 || line[0] == '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const char* eq = strchr(line, '=');
|
||||
if (!eq) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string key(line, eq - line);
|
||||
std::string value(eq + 1);
|
||||
|
||||
g_config->env_vars[key] = value;
|
||||
ApplyEnvironmentVariable(key, value);
|
||||
count++;
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
bool SaveConfigToFile(const std::string& config_path) {
|
||||
if (!g_config) return false;
|
||||
|
||||
FILE* file = fopen(config_path.c_str(), "w");
|
||||
if (!file) {
|
||||
LOG_ERROR(Frontend, "[Freedreno] Failed to open {} for writing", config_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
fprintf(file, "# Freedreno/Turnip Configuration\n");
|
||||
fprintf(file, "# Auto-generated by Eden Emulator\n\n");
|
||||
|
||||
for (const auto& [key, value] : g_config->env_vars) {
|
||||
fprintf(file, "%s=%s\n", key.c_str(), value.c_str());
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
extern "C" {
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_yuzu_yuzu_1emu_utils_NativeFreedrenoConfig_setFreedrenoBasePath(
|
||||
JNIEnv* env, [[maybe_unused]] jobject obj, jstring jbasePath) {
|
||||
g_base_path = Common::Android::GetJString(env, jbasePath);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_yuzu_yuzu_1emu_utils_NativeFreedrenoConfig_initializeFreedrenoConfig(
|
||||
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj) {
|
||||
if (!g_config) {
|
||||
g_config = std::make_unique<FreedrenoConfig>();
|
||||
LOG_INFO(Frontend, "[Freedreno] Configuration system initialized");
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_yuzu_yuzu_1emu_utils_NativeFreedrenoConfig_saveFreedrenoConfig(
|
||||
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj) {
|
||||
if (!g_config) {
|
||||
LOG_WARNING(Frontend, "[Freedreno] Cannot save: not initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string config_path = GetConfigPath();
|
||||
FILE* file = fopen(config_path.c_str(), "w");
|
||||
if (!file) {
|
||||
LOG_ERROR(Frontend, "[Freedreno] Failed to open {} for writing", config_path);
|
||||
return;
|
||||
}
|
||||
|
||||
fprintf(file, "# Freedreno/Turnip Configuration\n");
|
||||
fprintf(file, "# Auto-generated by Eden Emulator\n\n");
|
||||
|
||||
for (const auto& [key, value] : g_config->env_vars) {
|
||||
fprintf(file, "%s=%s\n", key.c_str(), value.c_str());
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
g_config->config_file_path = config_path;
|
||||
|
||||
LOG_INFO(Frontend, "[Freedreno] Saved {} variables to {}",
|
||||
g_config->env_vars.size(), config_path);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_yuzu_yuzu_1emu_utils_NativeFreedrenoConfig_reloadFreedrenoConfig(
|
||||
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj) {
|
||||
if (!g_config) {
|
||||
LOG_WARNING(Frontend, "[Freedreno] Cannot reload: not initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string config_path = GetConfigPath();
|
||||
g_config->env_vars.clear();
|
||||
|
||||
FILE* file = fopen(config_path.c_str(), "r");
|
||||
if (!file) {
|
||||
LOG_DEBUG(Frontend, "[Freedreno] No config file found at {}", config_path);
|
||||
return;
|
||||
}
|
||||
|
||||
char line[512];
|
||||
while (fgets(line, sizeof(line), file)) {
|
||||
// Remove trailing newline
|
||||
size_t len = strlen(line);
|
||||
if (len > 0 && line[len - 1] == '\n') {
|
||||
line[len - 1] = '\0';
|
||||
len--;
|
||||
}
|
||||
|
||||
// Skip empty lines and comments
|
||||
if (len == 0 || line[0] == '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse key=value
|
||||
const char* eq = strchr(line, '=');
|
||||
if (!eq) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string key(line, eq - line);
|
||||
std::string value(eq + 1);
|
||||
|
||||
g_config->env_vars[key] = value;
|
||||
ApplyEnvironmentVariable(key, value);
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
g_config->config_file_path = config_path;
|
||||
|
||||
if (!g_config->env_vars.empty()) {
|
||||
LOG_INFO(Frontend, "[Freedreno] Loaded {} variables:", g_config->env_vars.size());
|
||||
LogActiveVariables();
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_org_yuzu_yuzu_1emu_utils_NativeFreedrenoConfig_setFreedrenoEnv(
|
||||
JNIEnv* env, [[maybe_unused]] jobject obj, jstring jvarName, jstring jvalue) {
|
||||
if (!g_config) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
auto var_name = Common::Android::GetJString(env, jvarName);
|
||||
auto value = Common::Android::GetJString(env, jvalue);
|
||||
|
||||
if (var_name.empty()) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
g_config->env_vars[var_name] = value;
|
||||
|
||||
if (!ApplyEnvironmentVariable(var_name, value)) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
LOG_INFO(Frontend, "[Freedreno] Set {}={}", var_name, value);
|
||||
return JNI_TRUE;
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_org_yuzu_yuzu_1emu_utils_NativeFreedrenoConfig_getFreedrenoEnv(
|
||||
JNIEnv* env, [[maybe_unused]] jobject obj, jstring jvarName) {
|
||||
if (!g_config) {
|
||||
return env->NewStringUTF("");
|
||||
}
|
||||
|
||||
auto var_name = Common::Android::GetJString(env, jvarName);
|
||||
auto it = g_config->env_vars.find(var_name);
|
||||
|
||||
if (it != g_config->env_vars.end()) {
|
||||
return env->NewStringUTF(it->second.c_str());
|
||||
}
|
||||
|
||||
return env->NewStringUTF("");
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_org_yuzu_yuzu_1emu_utils_NativeFreedrenoConfig_isFreedrenoEnvSet(
|
||||
JNIEnv* env, [[maybe_unused]] jobject obj, jstring jvarName) {
|
||||
if (!g_config) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
auto var_name = Common::Android::GetJString(env, jvarName);
|
||||
auto it = g_config->env_vars.find(var_name);
|
||||
|
||||
return (it != g_config->env_vars.end() && !it->second.empty()) ? JNI_TRUE : JNI_FALSE;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_org_yuzu_yuzu_1emu_utils_NativeFreedrenoConfig_clearFreedrenoEnv(
|
||||
JNIEnv* env, [[maybe_unused]] jobject obj, jstring jvarName) {
|
||||
if (!g_config) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
auto var_name = Common::Android::GetJString(env, jvarName);
|
||||
auto it = g_config->env_vars.find(var_name);
|
||||
|
||||
if (it != g_config->env_vars.end()) {
|
||||
g_config->env_vars.erase(it);
|
||||
unsetenv(var_name.c_str());
|
||||
LOG_INFO(Frontend, "[Freedreno] Cleared {}", var_name);
|
||||
return JNI_TRUE;
|
||||
}
|
||||
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_yuzu_yuzu_1emu_utils_NativeFreedrenoConfig_clearAllFreedrenoEnv(
|
||||
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj) {
|
||||
if (!g_config) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& [key, value] : g_config->env_vars) {
|
||||
unsetenv(key.c_str());
|
||||
}
|
||||
|
||||
size_t count = g_config->env_vars.size();
|
||||
g_config->env_vars.clear();
|
||||
|
||||
if (count > 0) {
|
||||
LOG_INFO(Frontend, "[Freedreno] Cleared all {} variables", count);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_org_yuzu_yuzu_1emu_utils_NativeFreedrenoConfig_getFreedrenoEnvSummary(
|
||||
JNIEnv* env, [[maybe_unused]] jobject obj) {
|
||||
if (!g_config || g_config->env_vars.empty()) {
|
||||
return env->NewStringUTF("");
|
||||
}
|
||||
|
||||
std::string summary;
|
||||
for (const auto& [key, value] : g_config->env_vars) {
|
||||
if (!summary.empty()) {
|
||||
summary += ",";
|
||||
}
|
||||
summary += key + "=" + value;
|
||||
}
|
||||
|
||||
return env->NewStringUTF(summary.c_str());
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_yuzu_yuzu_1emu_utils_NativeFreedrenoConfig_setCurrentProgramId(
|
||||
JNIEnv* env, [[maybe_unused]] jobject obj, jstring jprogramId) {
|
||||
g_current_program_id = Common::Android::GetJString(env, jprogramId);
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_org_yuzu_yuzu_1emu_utils_NativeFreedrenoConfig_loadPerGameConfig(
|
||||
JNIEnv* env, [[maybe_unused]] jobject obj, jstring jprogramId) {
|
||||
if (!g_config) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
auto program_id = Common::Android::GetJString(env, jprogramId);
|
||||
if (program_id.empty()) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
// Clear current environment variables first
|
||||
ClearAllEnvironmentVariables();
|
||||
g_current_program_id = program_id;
|
||||
|
||||
// Try to load per-game config - do NOT fall back to global
|
||||
// Per-game config should start empty if no config exists yet
|
||||
std::string per_game_path = GetPerGameConfigPath(program_id);
|
||||
if (LoadConfigFromFile(per_game_path)) {
|
||||
LOG_INFO(Frontend, "[Freedreno] Loaded per-game config for {}", program_id);
|
||||
LogActiveVariables();
|
||||
return JNI_TRUE;
|
||||
}
|
||||
|
||||
// No per-game config exists - start with empty config
|
||||
LOG_INFO(Frontend, "[Freedreno] No per-game config for {}, starting empty", program_id);
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_org_yuzu_yuzu_1emu_utils_NativeFreedrenoConfig_loadPerGameConfigWithGlobalFallback(
|
||||
JNIEnv* env, [[maybe_unused]] jobject obj, jstring jprogramId) {
|
||||
if (!g_config) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
auto program_id = Common::Android::GetJString(env, jprogramId);
|
||||
if (program_id.empty()) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
// Clear current environment variables first
|
||||
ClearAllEnvironmentVariables();
|
||||
g_current_program_id = program_id;
|
||||
|
||||
// Try to load per-game config first
|
||||
std::string per_game_path = GetPerGameConfigPath(program_id);
|
||||
if (LoadConfigFromFile(per_game_path)) {
|
||||
LOG_INFO(Frontend, "[Freedreno] Loaded per-game config for {}", program_id);
|
||||
LogActiveVariables();
|
||||
return JNI_TRUE;
|
||||
}
|
||||
|
||||
// Fall back to global config for emulation
|
||||
std::string global_path = GetConfigPath();
|
||||
if (LoadConfigFromFile(global_path)) {
|
||||
LOG_INFO(Frontend, "[Freedreno] No per-game config for {}, using global for emulation", program_id);
|
||||
LogActiveVariables();
|
||||
}
|
||||
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_org_yuzu_yuzu_1emu_utils_NativeFreedrenoConfig_savePerGameConfig(
|
||||
JNIEnv* env, [[maybe_unused]] jobject obj, jstring jprogramId) {
|
||||
if (!g_config) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
auto program_id = Common::Android::GetJString(env, jprogramId);
|
||||
if (program_id.empty()) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
EnsurePerGameConfigDir();
|
||||
std::string config_path = GetPerGameConfigPath(program_id);
|
||||
|
||||
if (SaveConfigToFile(config_path)) {
|
||||
LOG_INFO(Frontend, "[Freedreno] Saved per-game config for {}", program_id);
|
||||
return JNI_TRUE;
|
||||
}
|
||||
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_org_yuzu_yuzu_1emu_utils_NativeFreedrenoConfig_hasPerGameConfig(
|
||||
JNIEnv* env, [[maybe_unused]] jobject obj, jstring jprogramId) {
|
||||
auto program_id = Common::Android::GetJString(env, jprogramId);
|
||||
if (program_id.empty()) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
std::string config_path = GetPerGameConfigPath(program_id);
|
||||
FILE* file = fopen(config_path.c_str(), "r");
|
||||
if (file) {
|
||||
fclose(file);
|
||||
return JNI_TRUE;
|
||||
}
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_org_yuzu_yuzu_1emu_utils_NativeFreedrenoConfig_deletePerGameConfig(
|
||||
JNIEnv* env, [[maybe_unused]] jobject obj, jstring jprogramId) {
|
||||
auto program_id = Common::Android::GetJString(env, jprogramId);
|
||||
if (program_id.empty()) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
std::string config_path = GetPerGameConfigPath(program_id);
|
||||
if (remove(config_path.c_str()) == 0) {
|
||||
LOG_INFO(Frontend, "[Freedreno] Deleted per-game config for {}", program_id);
|
||||
return JNI_TRUE;
|
||||
}
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
|
@ -0,0 +1,196 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/coordinator_main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/colorSurface">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appbar_freedreno"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true"
|
||||
android:touchscreenBlocksFocus="false"
|
||||
app:elevation="0dp">
|
||||
|
||||
<com.google.android.material.appbar.CollapsingToolbarLayout
|
||||
android:id="@+id/toolbar_freedreno_layout"
|
||||
style="?attr/collapsingToolbarLayoutMediumStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/collapsingToolbarLayoutMediumSize"
|
||||
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
|
||||
app:contentScrim="?attr/colorSurface"
|
||||
app:scrimVisibleHeightTrigger="100dp">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar_freedreno"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:touchscreenBlocksFocus="false"
|
||||
app:layout_collapseMode="pin"
|
||||
app:navigationIcon="@drawable/ic_back"
|
||||
app:title="@string/gpu_driver_settings" />
|
||||
|
||||
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<!-- Presets Section -->
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="@string/freedreno_presets"
|
||||
android:textAppearance="?attr/textAppearanceTitleMedium" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/list_freedreno_presets"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:scrollbars="horizontal"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||
|
||||
<!-- Current Settings Section -->
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="@string/freedreno_current_settings"
|
||||
android:textAppearance="?attr/textAppearanceTitleMedium" />
|
||||
|
||||
<!-- Settings List -->
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/list_freedreno_settings"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||
|
||||
<!-- Debug Section -->
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="@string/freedreno_debug"
|
||||
android:textAppearance="?attr/textAppearanceTitleMedium" />
|
||||
|
||||
<!-- Manual Variable Input -->
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/variable_name_input_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:hint="@string/freedreno_var_name">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/variable_name_input"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="text" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/variable_value_input_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:hint="@string/freedreno_var_value">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/variable_value_input"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="text" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_add_variable"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="@string/freedreno_add_variable" />
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginTop="16dp"
|
||||
android:spacing="8dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_clear_all"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/freedreno_clear_all"
|
||||
style="?attr/materialButtonOutlinedStyle" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_save"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/save" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Info Section -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
app:cardElevation="0dp"
|
||||
app:strokeWidth="1dp"
|
||||
app:strokeColor="?attr/colorOutline">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="12dp">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/freedreno_info_title"
|
||||
android:textAppearance="?attr/textAppearanceTitleSmall" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/freedreno_info_description"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall"
|
||||
android:textColor="?attr/colorOnSurfaceVariant" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="4dp"
|
||||
android:paddingEnd="4dp">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/preset_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minWidth="100dp"
|
||||
android:text="Preset"
|
||||
style="?attr/materialButtonOutlinedStyle" />
|
||||
|
||||
</LinearLayout>
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="0dp"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
app:cardElevation="0dp"
|
||||
app:strokeWidth="1dp"
|
||||
app:strokeColor="?attr/colorOutline">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="12dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/variable_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="VARIABLE_NAME"
|
||||
android:textAppearance="?attr/textAppearanceTitleSmall"
|
||||
android:textColor="?attr/colorOnSurface" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/variable_value"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="variable_value"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall"
|
||||
android:textColor="?attr/colorOnSurfaceVariant"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/button_delete"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="@string/delete"
|
||||
android:minWidth="0dp"
|
||||
android:minHeight="36dp"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
style="?attr/materialButtonOutlinedStyle" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
|
@ -148,6 +148,9 @@
|
|||
<action
|
||||
android:id="@+id/action_perGamePropertiesFragment_to_driverManagerFragment"
|
||||
app:destination="@id/driverManagerFragment" />
|
||||
<action
|
||||
android:id="@+id/action_perGamePropertiesFragment_to_freedrenoSettingsFragment"
|
||||
app:destination="@id/freedrenoSettingsFragment" />
|
||||
</fragment>
|
||||
<action
|
||||
android:id="@+id/action_global_perGamePropertiesFragment"
|
||||
|
|
@ -173,5 +176,15 @@
|
|||
android:name="org.yuzu.yuzu_emu.fragments.DriverFetcherFragment"
|
||||
android:label="fragment_driver_fetcher"
|
||||
tools:layout="@layout/fragment_driver_fetcher" />
|
||||
<fragment
|
||||
android:id="@+id/freedrenoSettingsFragment"
|
||||
android:name="org.yuzu.yuzu_emu.fragments.FreedrenoSettingsFragment"
|
||||
android:label="@string/freedreno_settings_title">
|
||||
<argument
|
||||
android:name="game"
|
||||
app:argType="org.yuzu.yuzu_emu.model.Game"
|
||||
app:nullable="true"
|
||||
android:defaultValue="@null" />
|
||||
</fragment>
|
||||
|
||||
</navigation>
|
||||
|
|
|
|||
|
|
@ -29,4 +29,19 @@
|
|||
android:name="org.yuzu.yuzu_emu.features.settings.ui.SettingsSearchFragment"
|
||||
android:label="SettingsSearchFragment" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/freedrenoSettingsFragment"
|
||||
android:name="org.yuzu.yuzu_emu.fragments.FreedrenoSettingsFragment"
|
||||
android:label="@string/freedreno_settings_title">
|
||||
<argument
|
||||
android:name="game"
|
||||
app:argType="org.yuzu.yuzu_emu.model.Game"
|
||||
app:nullable="true"
|
||||
android:defaultValue="@null" />
|
||||
</fragment>
|
||||
|
||||
<action
|
||||
android:id="@+id/action_settingsFragment_to_freedrenoSettingsFragment"
|
||||
app:destination="@id/freedrenoSettingsFragment" />
|
||||
|
||||
</navigation>
|
||||
|
|
|
|||
|
|
@ -1051,6 +1051,28 @@
|
|||
<string name="cpu_accuracy_paranoid">Paranoid</string>
|
||||
<string name="cpu_accuracy_debugging">Debugging</string>
|
||||
|
||||
<!-- Freedreno Settings -->
|
||||
<string name="freedreno_settings_title">Freedreno Settings</string>
|
||||
<string name="gpu_driver_settings">GPU Driver Settings</string>
|
||||
<string name="freedreno_presets">Quick Presets</string>
|
||||
<string name="freedreno_current_settings">Current Settings</string>
|
||||
<string name="freedreno_debug">Advanced Settings</string>
|
||||
<string name="freedreno_var_name">Variable Name (e.g., TU_DEBUG)</string>
|
||||
<string name="freedreno_var_value">Variable Value</string>
|
||||
<string name="freedreno_add_variable">Add Variable</string>
|
||||
<string name="freedreno_clear_all">Clear All</string>
|
||||
<string name="freedreno_saved">Freedreno configuration saved</string>
|
||||
<string name="freedreno_cleared_all">All Freedreno variables cleared</string>
|
||||
<string name="freedreno_variable_added">Variable %1$s added</string>
|
||||
<string name="freedreno_preset_applied">Preset \'%1$s\' applied</string>
|
||||
<string name="freedreno_error_empty_name">Variable name cannot be empty</string>
|
||||
<string name="freedreno_error_setting_variable">Failed to set variable</string>
|
||||
<string name="freedreno_info_title">About Freedreno Configuration</string>
|
||||
<string name="freedreno_info_description">Configure Freedreno/Turnip GPU driver options for debugging, profiling, and performance optimization. Changes are saved automatically. See https://docs.mesa3d.org/drivers/freedreno.html for detailed documentation.</string>
|
||||
<string name="freedreno_per_game_title">Freedreno Settings</string>
|
||||
<string name="freedreno_per_game_description">Configure GPU driver settings for this game</string>
|
||||
<string name="freedreno_per_game_saved">Freedreno configuration saved</string>
|
||||
|
||||
<!-- Gamepad Buttons -->
|
||||
<string name="gamepad_d_pad">D-pad</string>
|
||||
<string name="gamepad_left_stick">Left stick</string>
|
||||
|
|
|
|||
Loading…
Reference in New Issue