Compare commits

...

12 Commits

Author SHA1 Message Date
Allison Cunha
adcab5767b recursive file writing via SAF with permission request intent example 2025-11-22 18:24:54 -03:00
lizzie
fe13539d72 [meta] remove stale/dead links in help/about (#3064)
removes old links to dead wiki

Signed-off-by: lizzie lizzie@eden-emu.dev

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3064
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2025-11-22 21:08:55 +01:00
John
be218cc020 [vk] Fixes regression of PR #180 vk_scheduler.cpp for AMD GPU and Windows OS (#3071)
Fixes AMD + Windows because it forces barriers to include the fragment test and color output stages explicitly, ensuring that all render pass writes are visible before later commands. Without it, AMD’s driver sometimes skipped synchronization, causing broken rendering in Final Fantasy Tactics.

PR #3069 also fixes this regression by reverting vk_scheduler.cpp in PR #180.
This PR fixes PR #180 and may be the better solution.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3071
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Co-authored-by: John <john@eden-emu.dev>
Co-committed-by: John <john@eden-emu.dev>
2025-11-22 21:06:30 +01:00
PavelBARABANOV
c3cbe2d4d0 [android] fixing virtual keyboard in dark souls (#3061)
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3061
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Co-authored-by: PavelBARABANOV <pavelbarabanov94@gmail.com>
Co-committed-by: PavelBARABANOV <pavelbarabanov94@gmail.com>
2025-11-22 21:06:24 +01:00
kleidis
79b162a37c [android] Automatic update fetcher and APK installer (#2987)
This might need a test run before merging. Just to make sure.

Co-authored-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2987
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Co-authored-by: kleidis <kleidis1@protonmail.com>
Co-committed-by: kleidis <kleidis1@protonmail.com>
2025-11-22 21:01:19 +01:00
PavelBARABANOV
f3fbb3812f [android] Localize play time units (#3045)
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3045
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: crueter <crueter@eden-emu.dev>
Co-authored-by: PavelBARABANOV <pavelbarabanov94@gmail.com>
Co-committed-by: PavelBARABANOV <pavelbarabanov94@gmail.com>
2025-11-22 20:54:40 +01:00
MaranBr
d8caa74233 [video_core] Fix regressions introduced in #3015 (#3068)
This change is intended to fix two regressions:

1. Fixes the issue where `EDS3` + `Vertex Input Dynamic State` being enabled prevented some games from launching correctly.

2. Fixes the issue with broken water in `Super Mario Party Jamboree`.

This complements #3042.

Co-authored-by: Caio Oliveira <caiooliveirafarias0@gmail.com>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3068
Co-authored-by: MaranBr <maranbr@outlook.com>
Co-committed-by: MaranBr <maranbr@outlook.com>
2025-11-22 04:10:06 +01:00
lizzie
17fe74ef11 [vk] Fix 20xx flipped screen (#3058)
flip_y means "flip the Y coordinate of the triangles"; however, right now we just update the front face... this "emulates" the raster flip in the viewport itself, not the best solution but it's one solution :)

Signed-off-by: lizzie lizzie@eden-emu.dev

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3058
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: Caio Oliveira <caiooliveirafarias0@gmail.com>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2025-11-22 02:23:05 +01:00
lizzie
73713737c6 [frontend] use hh:mm:ss for playtime so we don't have to translate h,m or s suffixes (#3065)
Signed-off-by: lizzie lizzie@eden-emu.dev
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3065
Reviewed-by: Caio Oliveira <caiooliveirafarias0@gmail.com>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2025-11-21 19:28:26 +01:00
Caio Oliveira
61f3ce643c [android] Fix build id (#3066)
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3066
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: Caio Oliveira <caiooliveirafarias0@gmail.com>
Co-committed-by: Caio Oliveira <caiooliveirafarias0@gmail.com>
2025-11-21 04:07:27 +01:00
MaranBr
f7f6a4cde4 [video_core] Improve EDS logic and fix a lot of inconsistencies (#3042)
Improves EDS logic and fix some inconsistencies.

Removes a lot of unneeded code.

Adds an option to control the `Vertex Input Dynamic State` extension.

Fixes issues in Pokémon Legends: Z-A on any EDS level.

Co-authored-by: JPikachu <jpikachu.eden@gmail.com>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3042
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: Caio Oliveira <caiooliveirafarias0@gmail.com>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Co-authored-by: MaranBr <maranbr@outlook.com>
Co-committed-by: MaranBr <maranbr@outlook.com>
2025-11-21 02:00:24 +01:00
xbzk
65fa1a37e2 READY TO MERGE [android] fix for carousel late bottominset and one single game bugs (#3028)
kleidis found a rare condition that pops when using gesture navigation, in which by the lack of bottom inset availability in time, carousel sizes get oversized. then i've put some non zero value backup to cover.

Co-authored-by: Allison Cunha <allisonbzk@gmail.com>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3028
Reviewed-by: Caio Oliveira <caiooliveirafarias0@gmail.com>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Co-authored-by: xbzk <xbzk@eden-emu.dev>
Co-committed-by: xbzk <xbzk@eden-emu.dev>
2025-11-20 19:19:14 +01:00
38 changed files with 703 additions and 382 deletions

View File

@@ -1,8 +1,4 @@
blank_issues_enabled: false
contact_links:
- name: yuzu Discord
url: https://discord.com/invite/u77vRWY
about: If you are experiencing an issue with yuzu, and you need tech support, or if you have a general question, try asking in the official yuzu Discord linked here. Piracy is not allowed.
- name: Community forums
url: https://community.citra-emu.org
about: This is an alternative place for tech support, however helpers there are not as active.
- name: Eden Discord
url: https://discord.gg/HstXbPch7X

View File

@@ -1,6 +1,5 @@
<!--
SPDX-FileCopyrightText: 2018 yuzu Emulator Project
SPDX-License-Identifier: GPL-2.0-or-later
-->
# Contributing
**The Contributor's Guide has moved to [the yuzu wiki](https://github.com/yuzu-emu/yuzu/wiki/Contributing).**
You want to contribute? Please consult [the development guide](./docs/Development.md).
Don't forget to [get a git account](./docs/SIGNUP.md) - not a requirement per se but it's highly recommended.

View File

@@ -21,7 +21,7 @@ It is written in C++ with portability in mind, and we actively maintain builds f
<p align="center">
</a>
<a href="https://discord.gg/kXAmGCXBGD">
<a href="https://discord.gg/HstXbPch7X">
<img src="https://img.shields.io/discord/1367654015269339267?color=5865F2&label=Eden&logo=discord&logoColor=white"
alt="Discord">
</a>
@@ -52,8 +52,8 @@ Check out our [website](https://eden-emu.dev) for the latest news on exciting fe
## Development
Most of the development happens on our Git server. It is also where [our central repository](https://git.eden-emu.dev/eden-emu/eden) is hosted. For development discussions, please join us on [Discord](https://discord.gg/kXAmGCXBGD) or [Revolt](https://rvlt.gg/qKgFEAbH).
You can also follow us on [X (Twitter)](https://x.com/edenemuofficial) for updates and announcements.
Most of the development happens on our Git server. It is also where [our central repository](https://git.eden-emu.dev/eden-emu/eden) is hosted. For development discussions, please join us on [Discord](https://discord.gg/HstXbPch7X) or [Revolt](https://rvlt.gg/qKgFEAbH).
You can also follow us on [X (Twitter)](https://nitter.poast.org/edenemuofficial) for updates and announcements.
If you would like to contribute, we are open to new developers and pull requests. Please ensure that your work is of a high standard and properly documented. You can also contact any of the developers on Discord or Revolt to learn more about the current state of the emulator.
@@ -82,7 +82,7 @@ Any donations received will go towards things such as:
* Additional hardware (e.g. GPUs as needed to improve rendering support, other peripherals to add support for, etc.)
* CI Infrastructure
If you would prefer to support us in a different way, please join our [Discord](https://discord.gg/edenemu) and talk to Camille or any of our other developers.
If you would prefer to support us in a different way, please join our [Discord](https://discord.gg/HstXbPch7X) and talk to Camille or any of our other developers.
## License

View File

@@ -61,7 +61,6 @@ android {
minSdk = 24
targetSdk = 36
versionName = getGitVersion()
versionCode = autoVersion
ndk {
@@ -69,9 +68,6 @@ android {
abiFilters += listOf("arm64-v8a")
}
buildConfigField("String", "GIT_HASH", "\"${getGitHash()}\"")
buildConfigField("String", "BRANCH", "\"${getBranch()}\"")
externalNativeBuild {
cmake {
val extraCMakeArgs =
@@ -92,14 +88,14 @@ android {
"-DYUZU_TESTS=OFF",
"-DDYNARMIC_TESTS=OFF",
*extraCMakeArgs.toTypedArray()
))
)
)
abiFilters("arm64-v8a")
}
}
}
val keystoreFile = System.getenv("ANDROID_KEYSTORE_FILE")
signingConfigs {
if (keystoreFile != null) {
@@ -162,7 +158,39 @@ android {
}
}
// this is really annoying but idk any other ways to fix this behavior
flavorDimensions.add("version")
productFlavors {
create("mainline") {
dimension = "version"
resValue("string", "app_name_suffixed", "Eden")
}
create("genshinSpoof") {
dimension = "version"
resValue("string", "app_name_suffixed", "Eden Optimized")
applicationId = "com.miHoYo.Yuanshen"
}
create("legacy") {
dimension = "version"
resValue("string", "app_name_suffixed", "Eden Legacy")
applicationId = "dev.legacy.eden_emulator"
externalNativeBuild {
cmake {
arguments.add("-DYUZU_LEGACY=ON")
}
}
sourceSets {
getByName("legacy") {
res.srcDirs("src/main/legacy")
}
}
}
}
// this is really annoying but idk any other ways to fix this behavior
applicationVariants.all {
val variant = this
when {
@@ -187,40 +215,6 @@ android {
}
}
android {
flavorDimensions.add("version")
productFlavors {
create("mainline") {
dimension = "version"
resValue("string", "app_name_suffixed", "Eden")
}
create("genshinSpoof") {
dimension = "version"
resValue("string", "app_name_suffixed", "Eden Optimized")
applicationId = "com.miHoYo.Yuanshen"
}
create("legacy") {
dimension = "version"
resValue("string", "app_name_suffixed", "Eden Legacy")
applicationId = "dev.legacy.eden_emulator"
externalNativeBuild {
cmake {
arguments.add("-DYUZU_LEGACY=ON")
}
}
sourceSets {
getByName("legacy") {
res.srcDirs("src/main/legacy")
}
}
}
}
}
externalNativeBuild {
cmake {
version = "3.22.1"
@@ -284,7 +278,6 @@ dependencies {
implementation("androidx.core:core-splashscreen:1.0.1")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.17.2")
implementation("androidx.window:window:1.3.0")
implementation("androidx.constraintlayout:constraintlayout:2.2.1")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
implementation("org.commonmark:commonmark:0.22.0")
implementation("androidx.navigation:navigation-fragment-ktx:2.8.9")
@@ -302,7 +295,9 @@ fun runGitCommand(command: List<String>): String {
.directory(project.rootDir)
.redirectOutput(ProcessBuilder.Redirect.PIPE)
.redirectError(ProcessBuilder.Redirect.PIPE)
.start().inputStream.bufferedReader().use { it.readText() }
.start()
.inputStream.bufferedReader()
.use { it.readText() }
.trim()
} catch (e: Exception) {
logger.error("Cannot find git")
@@ -326,9 +321,3 @@ fun getGitVersion(): String {
}
return versionName.ifEmpty { "0.0" }
}
fun getGitHash(): String =
runGitCommand(listOf("git", "rev-parse", "--short", "HEAD")).ifEmpty { "dummy-hash" }
fun getBranch(): String =
runGitCommand(listOf("git", "rev-parse", "--abbrev-ref", "HEAD")).ifEmpty { "dummy-hash" }

View File

@@ -29,8 +29,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<application
android:name="org.yuzu.yuzu_emu.YuzuApplication"
@@ -110,5 +109,15 @@ SPDX-License-Identifier: GPL-3.0-or-later
</intent-filter>
</provider>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
</manifest>

View File

@@ -222,11 +222,21 @@ object NativeLibrary {
*/
external fun getUpdateUrl(version: String): String
/**
* Return the URL to download the APK for the given version
*/
external fun getUpdateApkUrl(version: String, packageId: String): String
/**
* Returns whether the update checker is enabled through CMAKE options.
*/
external fun isUpdateCheckerEnabled(): Boolean
/**
* Returns the build version generated by CMake (BUILD_VERSION).
*/
external fun getBuildVersion(): String
enum class CoreError {
ErrorSystemFiles,
ErrorSavestate,

View File

@@ -235,10 +235,13 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
}
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
val isPhysicalKeyboard = event.source and InputDevice.SOURCE_KEYBOARD == InputDevice.SOURCE_KEYBOARD &&
event.device?.isVirtual == false
if (event.source and InputDevice.SOURCE_JOYSTICK != InputDevice.SOURCE_JOYSTICK &&
event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD &&
event.source and InputDevice.SOURCE_KEYBOARD != InputDevice.SOURCE_KEYBOARD &&
event.source and InputDevice.SOURCE_MOUSE != InputDevice.SOURCE_MOUSE
event.source and InputDevice.SOURCE_MOUSE != InputDevice.SOURCE_MOUSE &&
!isPhysicalKeyboard
) {
return super.dispatchKeyEvent(event)
}

View File

@@ -29,6 +29,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
SYNC_MEMORY_OPERATIONS("sync_memory_operations"),
BUFFER_REORDER_DISABLE("disable_buffer_reorder"),
RENDERER_DEBUG("debug"),
RENDERER_VERTEX_INPUT_DYNAMIC_STATE("vertex_input_dynamic_state"),
RENDERER_PROVOKING_VERTEX("provoking_vertex"),
RENDERER_DESCRIPTOR_INDEXING("descriptor_indexing"),
RENDERER_SAMPLE_SHADING("sample_shading"),

View File

@@ -146,6 +146,13 @@ abstract class SettingsItem(
descriptionId = R.string.provoking_vertex_description
)
)
put(
SwitchSetting(
BooleanSetting.RENDERER_VERTEX_INPUT_DYNAMIC_STATE,
titleId = R.string.vertex_input_dynamic_state,
descriptionId = R.string.vertex_input_dynamic_state_description
)
)
put(
SwitchSetting(
BooleanSetting.RENDERER_DESCRIPTOR_INDEXING,
@@ -755,6 +762,7 @@ abstract class SettingsItem(
SwitchSetting(
BooleanSetting.ENABLE_UPDATE_CHECKS,
titleId = R.string.enable_update_checks,
descriptionId = R.string.enable_update_checks_description,
)
)
put(

View File

@@ -453,6 +453,7 @@ class SettingsFragmentPresenter(
sl.apply {
add(HeaderSetting(R.string.veil_extensions))
add(ByteSetting.RENDERER_DYNA_STATE.key)
add(BooleanSetting.RENDERER_VERTEX_INPUT_DYNAMIC_STATE.key)
add(BooleanSetting.RENDERER_PROVOKING_VERTEX.key)
add(BooleanSetting.RENDERER_DESCRIPTOR_INDEXING.key)
add(BooleanSetting.RENDERER_SAMPLE_SHADING.key)

View File

@@ -29,6 +29,7 @@ import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.FragmentAboutBinding
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
import org.yuzu.yuzu_emu.NativeLibrary
class AboutFragment : Fragment() {
private var _binding: FragmentAboutBinding? = null
@@ -78,11 +79,15 @@ class AboutFragment : Fragment() {
binding.root.findNavController().navigate(R.id.action_aboutFragment_to_licensesFragment)
}
binding.textVersionName.text = BuildConfig.VERSION_NAME
val buildName = getString(R.string.app_name_suffixed)
val buildVersion = NativeLibrary.getBuildVersion()
val fullVersionText = "$buildName ($buildVersion)"
binding.textVersionName.text = fullVersionText
binding.buttonVersionName.setOnClickListener {
val clipBoard =
requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText(getString(R.string.build), BuildConfig.GIT_HASH)
val clip = ClipData.newPlainText(getString(R.string.build), fullVersionText)
clipBoard.setPrimaryClip(clip)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {

View File

@@ -145,13 +145,12 @@ class GamePropertiesFragment : Fragment() {
val seconds = playTimeSeconds % 60
val readablePlayTime = when {
hours > 0 -> "${hours}h ${minutes}m ${seconds}s"
minutes > 0 -> "${minutes}m ${seconds}s"
else -> "${seconds}s"
}
hours > 0 -> "$hours${getString(R.string.hours_abbr)} $minutes${getString(R.string.minutes_abbr)} $seconds${getString(R.string.seconds_abbr)}"
minutes > 0 -> "$minutes${getString(R.string.minutes_abbr)} $seconds${getString(R.string.seconds_abbr)}"
else -> "$seconds${getString(R.string.seconds_abbr)}"
}
append(getString(R.string.playtime))
append(readablePlayTime)
append(getString(R.string.playtime) + " " + readablePlayTime)
}
binding.playtime.setOnClickListener {

View File

@@ -44,6 +44,17 @@ import androidx.core.content.edit
import androidx.core.view.doOnNextLayout
class GamesFragment : Fragment() {
private lateinit var safRecursiveTimestampWriter: org.yuzu.yuzu_emu.utils.SAFWriter
private val REQUEST_CODE_OPEN_DOCUMENT_TREE = 1001
override fun onActivityResult(requestCode: Int, resultCode: Int, data: android.content.Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_CODE_OPEN_DOCUMENT_TREE && resultCode == android.app.Activity.RESULT_OK) {
val uri = data?.data ?: return
safRecursiveTimestampWriter.handlePermissionResult(uri)
}
}
private var _binding: FragmentGamesBinding? = null
private val binding get() = _binding!!
@@ -53,6 +64,7 @@ class GamesFragment : Fragment() {
private var originalHeaderLeftMargin: Int? = null
private var lastViewType: Int = GameAdapter.VIEW_TYPE_GRID
private var fallbackBottomInset: Int = 0
companion object {
private const val SEARCH_TEXT = "SearchText"
@@ -111,9 +123,25 @@ class GamesFragment : Fragment() {
applyGridGamesBinding()
safRecursiveTimestampWriter = org.yuzu.yuzu_emu.utils.SAFWriter(requireContext())
binding.swipeRefresh.apply {
(binding.swipeRefresh as? SwipeRefreshLayout)?.setOnRefreshListener {
gamesViewModel.reloadGames(false)
safRecursiveTimestampWriter.refreshGamesFolder(
folders = gamesViewModel.folders.value,
requestPermission = { uri ->
val intent = android.content.Intent(android.content.Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.putExtra(android.provider.DocumentsContract.EXTRA_INITIAL_URI, uri)
intent.addFlags(
android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION or
android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION or
android.content.Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or
android.content.Intent.FLAG_GRANT_PREFIX_URI_PERMISSION
)
startActivityForResult(intent, REQUEST_CODE_OPEN_DOCUMENT_TREE)
},
reloadGames = { gamesViewModel.reloadGames(false) }
)
}
(binding.swipeRefresh as? SwipeRefreshLayout)?.setProgressBackgroundColorSchemeColor(
com.google.android.material.color.MaterialColors.getColor(
@@ -208,12 +236,12 @@ class GamesFragment : Fragment() {
else -> throw IllegalArgumentException("Invalid view type: $savedViewType")
}
if (savedViewType == GameAdapter.VIEW_TYPE_CAROUSEL) {
doOnNextLayout {
(this as? CarouselRecyclerView)?.setCarouselMode(true, gameAdapter)
adapter = gameAdapter
(binding.gridGames as? View)?.let { it -> ViewCompat.requestApplyInsets(it)}
doOnNextLayout { //Carousel: important to avoid overlap issues
(this as? CarouselRecyclerView)?.notifyLaidOut(fallbackBottomInset)
}
} else {
(this as? CarouselRecyclerView)?.setCarouselMode(false)
(this as? CarouselRecyclerView)?.setupCarousel(false)
}
adapter = gameAdapter
lastViewType = savedViewType
@@ -237,9 +265,8 @@ class GamesFragment : Fragment() {
override fun onResume() {
super.onResume()
if (getCurrentViewType() == GameAdapter.VIEW_TYPE_CAROUSEL) {
(binding.gridGames as? CarouselRecyclerView)?.restoreScrollState(
gamesViewModel.lastScrollPosition
)
(binding.gridGames as? CarouselRecyclerView)?.setupCarousel(true)
(binding.gridGames as? CarouselRecyclerView)?.restoreScrollState(gamesViewModel.lastScrollPosition)
}
}
@@ -494,6 +521,11 @@ class GamesFragment : Fragment() {
mlpFab.rightMargin = rightInset + fabPadding
binding.addDirectory.layoutParams = mlpFab
val navInsets = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars())
val gestureInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemGestures())
val bottomInset = maxOf(navInsets.bottom, gestureInsets.bottom, cutoutInsets.bottom)
fallbackBottomInset = bottomInset
(binding.gridGames as? CarouselRecyclerView)?.notifyInsetsReady(bottomInset)
windowInsets
}
}

View File

@@ -53,8 +53,16 @@ import androidx.core.content.edit
import org.yuzu.yuzu_emu.activities.EmulationActivity
import kotlin.text.compareTo
import androidx.core.net.toUri
import com.google.android.material.progressindicator.LinearProgressIndicator
import com.google.android.material.textview.MaterialTextView
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.updater.APKDownloader
import org.yuzu.yuzu_emu.updater.APKInstaller
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class MainActivity : AppCompatActivity(), ThemeProvider {
private lateinit var binding: ActivityMainBinding
@@ -186,9 +194,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
.setTitle(R.string.update_available)
.setMessage(getString(R.string.update_available_description, version))
.setPositiveButton(android.R.string.ok) { _, _ ->
val url = NativeLibrary.getUpdateUrl(version)
val intent = Intent(Intent.ACTION_VIEW, url.toUri())
startActivity(intent)
downloadAndInstallUpdate(version)
}
.setNeutralButton(R.string.cancel) { dialog, _ ->
dialog.dismiss()
@@ -201,6 +207,87 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
.show()
}
private fun downloadAndInstallUpdate(version: String) {
CoroutineScope(Dispatchers.IO).launch {
val packageId = applicationContext.packageName
val apkUrl = NativeLibrary.getUpdateApkUrl(version, packageId)
val apkFile = File(cacheDir, "update-$version.apk")
withContext(Dispatchers.Main) {
showDownloadProgressDialog()
}
val downloader = APKDownloader(apkUrl, apkFile)
downloader.download(
onProgress = { progress ->
runOnUiThread {
updateDownloadProgress(progress)
}
},
onComplete = { success ->
runOnUiThread {
dismissDownloadProgressDialog()
if (success) {
val installer = APKInstaller(this@MainActivity)
installer.install(
apkFile,
onComplete = {
Toast.makeText(
this@MainActivity,
R.string.update_installed_successfully,
Toast.LENGTH_LONG
).show()
},
onFailure = { exception ->
Toast.makeText(
this@MainActivity,
getString(R.string.update_install_failed, exception.message),
Toast.LENGTH_LONG
).show()
}
)
} else {
Toast.makeText(
this@MainActivity,
getString(R.string.update_download_failed) + "\n\nURL: $apkUrl",
Toast.LENGTH_LONG
).show()
}
}
}
)
}
}
private var progressDialog: androidx.appcompat.app.AlertDialog? = null
private var progressBar: LinearProgressIndicator? = null
private var progressMessage: MaterialTextView? = null
private fun showDownloadProgressDialog() {
val progressView = layoutInflater.inflate(R.layout.dialog_download_progress, null)
progressBar = progressView.findViewById(R.id.download_progress_bar)
progressMessage = progressView.findViewById(R.id.download_progress_message)
progressDialog = MaterialAlertDialogBuilder(this)
.setTitle(R.string.downloading_update)
.setView(progressView)
.setCancelable(false)
.create()
progressDialog?.show()
}
private fun updateDownloadProgress(progress: Int) {
progressBar?.progress = progress
progressMessage?.text = "$progress%"
}
private fun dismissDownloadProgressDialog() {
progressDialog?.dismiss()
progressDialog = null
progressBar = null
progressMessage = null
}
fun displayMultiplayerDialog() {
val dialog = NetPlayDialog(this)

View File

@@ -0,0 +1,61 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.updater
import okhttp3.Call
import okhttp3.Callback
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
class APKDownloader(private val url: String, private val outputFile: File) {
fun download(onProgress: (Int) -> Unit, onComplete: (Boolean) -> Unit) {
val client = OkHttpClient()
val request = Request.Builder().url(url).build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
e.printStackTrace()
onComplete(false)
}
override fun onResponse(call: Call, response: Response) {
if (response.isSuccessful) {
response.body?.let { body ->
val contentLength = body.contentLength()
try {
val inputStream = body.byteStream()
val outputStream = FileOutputStream(outputFile)
val buffer = ByteArray(4096)
var bytesRead: Int
var totalBytesRead: Long = 0
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
outputStream.write(buffer, 0, bytesRead)
totalBytesRead += bytesRead
val progress = (totalBytesRead * 100 / contentLength).toInt()
onProgress(progress)
}
outputStream.flush()
outputStream.close()
inputStream.close()
onComplete(true)
} catch (e: IOException) {
e.printStackTrace()
onComplete(false)
}
} ?: run {
onComplete(false)
}
} else {
onComplete(false)
}
}
})
}
}

View File

@@ -0,0 +1,41 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.updater
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.Uri
import androidx.core.content.FileProvider
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.io.File
class APKInstaller(private val context: Context) {
fun install(apkFile: File, onComplete: () -> Unit, onFailure: (Exception) -> Unit) {
try {
val apkUri: Uri = FileProvider.getUriForFile(
context,
context.applicationContext.packageName + ".provider",
apkFile
)
val intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(apkUri, "application/vnd.android.package-archive")
intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(intent)
GlobalScope.launch {
val receiver = AppInstallReceiver(onComplete, onFailure)
context.registerReceiver(receiver, IntentFilter().apply {
addAction(Intent.ACTION_PACKAGE_ADDED)
addAction(Intent.ACTION_PACKAGE_REPLACED)
addDataScheme("package")
})
}
} catch (e: Exception) {
onFailure(e)
}
}
}

View File

@@ -0,0 +1,30 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.updater
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
class AppInstallReceiver(
private val onComplete: () -> Unit,
private val onFailure: (Exception) -> Unit
) : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val packageName = intent.data?.schemeSpecificPart
when (intent.action) {
Intent.ACTION_PACKAGE_ADDED, Intent.ACTION_PACKAGE_REPLACED -> {
Log.i("AppInstallReceiver", "Package installed or updated: $packageName")
onComplete()
context.unregisterReceiver(this)
}
else -> {
onFailure(Exception("Installation failed for package: $packageName"))
context.unregisterReceiver(this)
}
}
}
}

View File

@@ -0,0 +1,87 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.utils
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.util.Log
import androidx.documentfile.provider.DocumentFile
class SAFWriter(private val context: Context) {
fun refreshGamesFolder(
folders: List<Any>,
requestPermission: (Uri) -> Unit,
reloadGames: () -> Unit
) {
if (folders.isNotEmpty()) {
val gamesDirUriString = try {
val uriField = folders[0]::class.java.getDeclaredField("uriString")
uriField.isAccessible = true
uriField.get(folders[0]) as? String
} catch (e: Exception) {
Log.e("YuzuDebug", "[SAFWriter] [SAF] Error accessing uriString: $e")
null
}
if (gamesDirUriString != null) {
val gamesRootDocFile = DocumentFile.fromTreeUri(context, Uri.parse(gamesDirUriString))
Log.i("YuzuDebug", "[SAFWriter] [SAF] Refresh triggered. gamesDirUri: $gamesDirUriString, gamesRootDocFile: $gamesRootDocFile")
if (gamesRootDocFile != null && gamesRootDocFile.isDirectory) {
val dirtreeDocFile = gamesRootDocFile.findFile("dirtree")
if (dirtreeDocFile != null && dirtreeDocFile.isDirectory) {
createRecursiveTimestampFilesInSAF(dirtreeDocFile, requestPermission)
} else {
Log.e("YuzuDebug", "[SAFWriter] [SAF] 'dirtree' subfolder not found or not a directory.")
}
} else {
Log.e("YuzuDebug", "[SAFWriter] [SAF] Invalid games folder DocumentFile.")
}
} else {
Log.e("YuzuDebug", "[SAFWriter] [SAF] Could not get gamesDirUriString from folders.")
}
} else {
Log.e("YuzuDebug", "[SAFWriter] [SAF] No games folder found in gamesViewModel.folders.")
}
reloadGames()
}
private var pendingSAFAction: (() -> Unit)? = null
private val REQUEST_CODE_OPEN_DOCUMENT_TREE = 1001
// Recursively create a timestamp file in each subfolder using SAF
fun createRecursiveTimestampFilesInSAF(root: DocumentFile, requestPermission: (Uri) -> Unit, onPermissionGranted: (() -> Unit)? = null) {
Log.i("YuzuDebug", "[SAFWriter] [SAF] Processing folder: ${root.uri}, name: ${root.name}, canWrite: ${root.canWrite()}, isDirectory: ${root.isDirectory}, mimeType: ${root.type}")
if (!root.isDirectory) return
if (!root.canWrite()) {
Log.w("YuzuDebug", "[SAFWriter] [SAF] Cannot write to folder: ${root.uri}, requesting permission...")
// Save the action to retry after permission
pendingSAFAction = { createRecursiveTimestampFilesInSAF(root, requestPermission, onPermissionGranted) }
requestPermission(root.uri)
return
}
val timestamp = java.text.SimpleDateFormat("yyyyMMddHHmmss", java.util.Locale.US).format(java.util.Date())
try {
val exists = root.findFile(timestamp)
if (exists == null) {
val file = root.createFile("text/plain", timestamp)
Log.i("YuzuDebug", "[SAFWriter] [SAF] Attempted to create file: $timestamp, result: ${file?.uri}")
} else {
Log.i("YuzuDebug", "[SAFWriter] [SAF] File already exists: ${exists.uri}")
}
} catch (e: Exception) {
Log.e("YuzuDebug", "[SAFWriter] [SAF] Error creating file in ${root.uri}: $e")
}
root.listFiles()?.filter { it.isDirectory }?.forEach { createRecursiveTimestampFilesInSAF(it, requestPermission, onPermissionGranted) }
}
fun handlePermissionResult(uri: Uri) {
context.contentResolver.takePersistableUriPermission(
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
Log.i("YuzuDebug", "[SAFWriter] [SAF] Permission granted for uri: $uri")
// Retry the pending action if any
pendingSAFAction?.invoke()
pendingSAFAction = null
}
}

View File

@@ -20,9 +20,8 @@ import androidx.core.view.doOnNextLayout
import org.yuzu.yuzu_emu.YuzuApplication
import androidx.preference.PreferenceManager
import androidx.core.view.WindowInsetsCompat
/**
* CarouselRecyclerView encapsulates all carousel logic for the games UI.
* CarouselRecyclerView encapsulates all carousel content for the games UI.
* It manages overlapping cards, center snapping, custom drawing order,
* joypad & fling navigation and mid-screen swipe-to-refresh.
*/
@@ -34,6 +33,7 @@ class CarouselRecyclerView @JvmOverloads constructor(
private var overlapFactor: Float = 0f
private var overlapPx: Int = 0
private var bottomInset: Int = -1
private var overlapDecoration: OverlappingDecoration? = null
private var pagerSnapHelper: PagerSnapHelper? = null
private var scalingScrollListener: OnScrollListener? = null
@@ -202,46 +202,61 @@ class CarouselRecyclerView @JvmOverloads constructor(
}
}
fun setCarouselMode(enabled: Boolean, gameAdapter: GameAdapter? = null) {
fun refreshView() {
updateChildScalesAndAlpha()
focusCenteredCard()
}
fun notifyInsetsReady(newBottomInset: Int) {
if (bottomInset != newBottomInset) {
bottomInset = newBottomInset
}
setupCarousel(true)
}
fun notifyLaidOut(fallBackBottomInset: Int) {
if (bottomInset < 0) bottomInset = fallBackBottomInset
var gameAdapter = adapter as? GameAdapter ?: return
var newCardSize = cardSize(bottomInset)
if (gameAdapter.cardSize != newCardSize) {
gameAdapter.setCardSize(newCardSize)
}
setupCarousel(true)
}
fun cardSize(bottomInset: Int): Int {
val internalFactor = resources.getFraction(R.fraction.carousel_card_size_factor, 1, 1)
val userFactor = preferences.getFloat(CAROUSEL_CARD_SIZE_FACTOR, internalFactor).coerceIn(
0f,
1f
)
return (userFactor * (height - bottomInset)).toInt()
}
fun setupCarousel(enabled: Boolean) {
if (enabled) {
val gameAdapter = adapter as? GameAdapter ?: return
if (gameAdapter.cardSize == 0) return
if (bottomInset < 0) return
useCustomDrawingOrder = true
val cardSize = gameAdapter.cardSize
val insets = rootWindowInsets?.let { WindowInsetsCompat.toWindowInsetsCompat(it, this) }
val bottomInset = insets?.getInsets(WindowInsetsCompat.Type.systemBars())?.bottom ?: 0
val internalFactor = resources.getFraction(R.fraction.carousel_card_size_factor, 1, 1)
val userFactor = preferences.getFloat(CAROUSEL_CARD_SIZE_FACTOR, internalFactor).coerceIn(
0f,
1f
)
val cardSize = (userFactor * (height - bottomInset)).toInt()
gameAdapter?.setCardSize(cardSize)
val internalOverlapFactor = resources.getFraction(
R.fraction.carousel_overlap_factor,
1,
1
)
overlapFactor = preferences.getFloat(CAROUSEL_OVERLAP_FACTOR, internalOverlapFactor).coerceIn(
0f,
1f
)
val internalOverlapFactor = resources.getFraction(R.fraction.carousel_overlap_factor,1,1)
overlapFactor = preferences.getFloat(CAROUSEL_OVERLAP_FACTOR, internalOverlapFactor).coerceIn(0f,1f)
overlapPx = (cardSize * overlapFactor).toInt()
val internalFlingMultiplier = resources.getFraction(
R.fraction.carousel_fling_multiplier,
1,
1
)
val internalFlingMultiplier = resources.getFraction(R.fraction.carousel_fling_multiplier,1,1)
flingMultiplier = preferences.getFloat(
CAROUSEL_FLING_MULTIPLIER,
internalFlingMultiplier
).coerceIn(1f, 5f)
gameAdapter?.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
gameAdapter .registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onChanged() {
if (pendingScrollAfterReload) {
post {
jigglyScroll()
doOnNextLayout {
refreshView()
pendingScrollAfterReload = false
}
}
@@ -257,7 +272,7 @@ class CarouselRecyclerView @JvmOverloads constructor(
addItemDecoration(overlapDecoration!!)
}
// Gradual scalingAdd commentMore actions
// Gradual scaling on scroll
if (scalingScrollListener == null) {
scalingScrollListener = object : OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
@@ -315,8 +330,7 @@ class CarouselRecyclerView @JvmOverloads constructor(
super.scrollToPosition(position)
(layoutManager as? LinearLayoutManager)?.scrollToPositionWithOffset(position, overlapPx)
doOnNextLayout {
updateChildScalesAndAlpha()
focusCenteredCard()
refreshView()
}
}
@@ -382,12 +396,6 @@ class CarouselRecyclerView @JvmOverloads constructor(
return sorted[i].first
}
fun jigglyScroll() {
scrollBy(-1, 0)
scrollBy(1, 0)
focusCenteredCard()
}
inner class OverlappingDecoration(private val overlap: Int) : ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,

View File

@@ -1613,6 +1613,43 @@ JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUpdateUrl(
env->ReleaseStringUTFChars(version, version_str);
return env->NewStringUTF(url.c_str());
}
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUpdateApkUrl(
JNIEnv* env,
jobject obj,
jstring version,
jstring packageId) {
const char* version_str = env->GetStringUTFChars(version, nullptr);
const char* package_id_str = env->GetStringUTFChars(packageId, nullptr);
std::string variant;
std::string package_id(package_id_str);
if (package_id.find("dev.legacy.eden_emulator") != std::string::npos) {
variant = "legacy";
} else if (package_id.find("com.miHoYo.Yuanshen") != std::string::npos) {
variant = "optimized";
} else {
variant = "standard";
}
const std::string apk_filename = fmt::format("Eden-Android-{}-{}.apk", version_str, variant);
const std::string url = fmt::format("{}/{}/releases/download/{}/{}",
std::string{Common::g_build_auto_update_website},
std::string{Common::g_build_auto_update_repo},
version_str,
apk_filename);
env->ReleaseStringUTFChars(version, version_str);
env->ReleaseStringUTFChars(packageId, package_id_str);
return env->NewStringUTF(url.c_str());
}
#endif
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getBuildVersion(
JNIEnv* env,
[[maybe_unused]] jobject obj) {
return env->NewStringUTF(Common::g_build_version);
}
} // extern "C"

View File

@@ -0,0 +1,31 @@
<?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="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/download_progress_message"
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="0%"
android:textAlignment="center"
android:textSize="18sp"
android:textStyle="bold" />
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/download_progress_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="100"
android:progress="0"
app:indicatorColor="?attr/colorPrimary"
app:trackColor="?attr/colorSurfaceVariant"
app:trackCornerRadius="4dp"
app:trackThickness="8dp" />
</LinearLayout>

View File

@@ -94,6 +94,8 @@
<string name="dyna_state">Extended Dynamic State</string>
<string name="dyna_state_description">Controls the number of features that can be used in Extended Dynamic State. Higher numbers allow for more features and can increase performance, but may cause issues with some drivers and vendors. The default value may vary depending on your system and hardware capabilities. This value can be changed until stability and a better visual quality are achieved.</string>
<string name="disabled">Disabled</string>
<string name="vertex_input_dynamic_state">Vertex Input Dynamic State</string>
<string name="vertex_input_dynamic_state_description">Enables vertex input dynamic state feature for better quality and performance.</string>
<string name="provoking_vertex">Provoking Vertex</string>
<string name="provoking_vertex_description">Improves lighting and vertex handling in certain games. Only supported on Vulkan 1.0+ GPUs.</string>
<string name="descriptor_indexing">Descriptor Indexing</string>
@@ -269,9 +271,14 @@
<string name="folder">Folder</string>
<string name="dont_show_again">Don\'t Show Again</string>
<string name="add_directory_success">New game directory added successfully </string>
<string name="enable_update_checks">Check for updates on app startup.</string>
<string name="enable_update_checks">Check for Updates</string>
<string name="enable_update_checks_description">Check for updates on launch, and optionally download and install the new update.</string>
<string name="update_available">Update Available</string>
<string name="update_available_description">A new version is available: %1$s\n\nWould you like to download it?</string>
<string name="downloading_update">Downloading Update</string>
<string name="update_download_failed">Failed to download update</string>
<string name="update_installed_successfully">Update installed successfully</string>
<string name="update_install_failed">Failed to install update: %1$s</string>
<string name="home_search">Search</string>
<string name="home_settings">Settings</string>
<string name="empty_gamelist">No files were found or no game directory has been selected yet.</string>
@@ -441,9 +448,9 @@
<string name="user_data_import_success">User data imported successfully</string>
<string name="user_data_export_cancelled">Export cancelled</string>
<string name="user_data_import_failed_description">Make sure the user data folders are at the root of the zip folder and contain a config file at config/config.ini and try again.</string>
<string name="discord_link" translatable="false">https://discord.gg/kXAmGCXBGD</string>
<string name="discord_link" translatable="false">https://discord.gg/HstXbPch7X</string>
<string name="revolt_link" translatable="false">https://rvlt.gg/qKgFEAbH</string>
<string name="x_link" translatable="false">https://x.com/edenemuofficial</string>
<string name="x_link" translatable="false">https://nitter.poast.org/edenemuofficial</string>
<string name="website_link" translatable="false">https://eden-emu.dev</string>
<string name="github_link" translatable="false">https://git.eden-emu.dev/eden-emu</string>
@@ -706,7 +713,7 @@
<string name="copy_details">Copy details</string>
<string name="add_ons">Add-ons</string>
<string name="add_ons_description">Toggle mods, updates and DLC</string>
<string name="playtime">Playtime: </string>
<string name="playtime">Playtime:</string>
<string name="reset_playtime">Clear Playtime</string>
<string name="reset_playtime_description">Reset the current game\'s playtime back to 0 seconds</string>
<string name="reset_playtime_warning_description">This will clear the current game\'s playtime data. Are you sure?</string>
@@ -714,6 +721,9 @@
<string name="edit_playtime">Edit Playtime</string>
<string name="hours">Hours</string>
<string name="minutes">Minutes</string>
<string name="hours_abbr">h</string>
<string name="minutes_abbr">m</string>
<string name="seconds_abbr">s</string>
<string name="hours_must_be_between_0_and_9999">Hours must be between 0 and 9999</string>
<string name="minutes_must_be_between_0_and_59">Minutes must be between 0 and 59</string>
<string name="seconds_must_be_between_0_and_59">Seconds must be between 0 and 59</string>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<!-- this is required to share files in the app's internal storage -->
<cache-path name="apk_cache" path="." />
<external-cache-path name="external_apk_cache" path="." />
<files-path name="files" path="." />
<external-files-path name="external_files" path="." />
</paths>

View File

@@ -546,6 +546,7 @@ struct Values {
Category::RendererExtensions,
Specialization::Scalar};
SwitchableSetting<bool> vertex_input_dynamic_state{linkage, true, "vertex_input_dynamic_state", Category::RendererExtensions};
SwitchableSetting<bool> provoking_vertex{linkage, false, "provoking_vertex", Category::RendererExtensions};
SwitchableSetting<bool> descriptor_indexing{linkage, false, "descriptor_indexing", Category::RendererExtensions};
SwitchableSetting<bool> sample_shading{linkage, false, "sample_shading", Category::RendererExtensions, Specialization::Paired};

View File

@@ -167,25 +167,9 @@ void PlayTimeManager::ResetProgramPlayTime(u64 program_id) {
Save();
}
std::string PlayTimeManager::GetReadablePlayTime(u64 time_seconds) {
if (time_seconds == 0) {
return {};
}
const auto time_minutes = std::max(static_cast<double>(time_seconds) / 60.0, 1.0);
const auto time_hours = static_cast<double>(time_seconds) / 3600.0;
const bool is_minutes = time_minutes < 60.0;
if (is_minutes) {
return fmt::format("{:.0f} m", time_minutes);
} else {
const bool has_remainder = time_seconds % 60 != 0;
if (has_remainder) {
return fmt::format("{:.1f} h", time_hours);
} else {
return fmt::format("{:.0f} h", time_hours);
}
}
std::string PlayTimeManager::GetReadablePlayTime(u64 t) {
return t > 0 ? fmt::format("{:02}:{:02}:{:02}", t / 3600, (t / 60) % 60, t % 60)
: std::string{};
}
std::string PlayTimeManager::GetPlayTimeHours(u64 time_seconds) {

View File

@@ -329,6 +329,11 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
tr("Extended Dynamic State"),
tr("Controls the number of features that can be used in Extended Dynamic State.\nHigher numbers allow for more features and can increase performance, but may cause issues.\nThe default value is per-system."));
INSERT(Settings,
vertex_input_dynamic_state,
tr("Vertex Input Dynamic State"),
tr("Enables vertex input dynamic state feature for better quality and performance."));
INSERT(Settings,
provoking_vertex,
tr("Provoking Vertex"),

View File

@@ -1196,17 +1196,15 @@ void RasterizerOpenGL::SyncLogicOpState() {
if (device.IsAmd()) {
using namespace Tegra::Engines;
struct In {
const Maxwell3D::Regs::VertexAttribute::Type d;
In(Maxwell3D::Regs::VertexAttribute::Type n) : d(n) {}
bool operator()(Maxwell3D::Regs::VertexAttribute n) const {
return n.type == d;
}
};
bool has_float =
std::any_of(regs.vertex_attrib_format.begin(), regs.vertex_attrib_format.end(),
In(Maxwell3D::Regs::VertexAttribute::Type::Float));
bool has_float = std::any_of(
regs.vertex_attrib_format.begin(),
regs.vertex_attrib_format.end(),
[](const auto& n) {
return n.type == Maxwell3D::Regs::VertexAttribute::Type::Float;
}
);
regs.logic_op.enable = static_cast<u32>(!has_float);
}

View File

@@ -579,8 +579,7 @@ void BufferCacheRuntime::BindQuadIndexBuffer(PrimitiveTopology topology, u32 fir
}
}
void BufferCacheRuntime::BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size,
u32 stride) {
void BufferCacheRuntime::BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size, u32 stride) {
if (index >= device.GetMaxVertexInputBindings()) {
return;
}

View File

@@ -845,10 +845,9 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
VK_DYNAMIC_STATE_LINE_WIDTH,
};
if (key.state.extended_dynamic_state) {
static constexpr std::array extended{
std::vector<VkDynamicState> extended{
VK_DYNAMIC_STATE_CULL_MODE_EXT,
VK_DYNAMIC_STATE_FRONT_FACE_EXT,
//VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT, //Disabled for VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME
VK_DYNAMIC_STATE_DEPTH_TEST_ENABLE_EXT,
VK_DYNAMIC_STATE_DEPTH_WRITE_ENABLE_EXT,
VK_DYNAMIC_STATE_DEPTH_COMPARE_OP_EXT,
@@ -856,6 +855,9 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
VK_DYNAMIC_STATE_STENCIL_TEST_ENABLE_EXT,
VK_DYNAMIC_STATE_STENCIL_OP_EXT,
};
if (!device.IsExtVertexInputDynamicStateSupported()) {
extended.push_back(VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT);
}
if (key.state.dynamic_vertex_input) {
dynamic_states.push_back(VK_DYNAMIC_STATE_VERTEX_INPUT_EXT);
}

View File

@@ -404,17 +404,13 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
device.GetMaxVertexInputBindings(), Maxwell::NumVertexArrays);
}
const u8 dynamic_state = Settings::values.dyna_state.GetValue();
LOG_INFO(Render_Vulkan, "DynamicState value is set to {}", (u32) dynamic_state);
dynamic_features = DynamicFeatures{
.has_extended_dynamic_state = device.IsExtExtendedDynamicStateSupported() && dynamic_state > 0,
.has_extended_dynamic_state_2 = device.IsExtExtendedDynamicState2Supported() && dynamic_state > 1,
.has_extended_dynamic_state_2_extra = device.IsExtExtendedDynamicState2ExtrasSupported() && dynamic_state > 1,
.has_extended_dynamic_state_3_blend = device.IsExtExtendedDynamicState3BlendingSupported() && dynamic_state > 2,
.has_extended_dynamic_state_3_enables = device.IsExtExtendedDynamicState3EnablesSupported() && dynamic_state > 2,
.has_dynamic_vertex_input = device.IsExtVertexInputDynamicStateSupported() && dynamic_state > 0,
.has_extended_dynamic_state = device.IsExtExtendedDynamicStateSupported(),
.has_extended_dynamic_state_2 = device.IsExtExtendedDynamicState2Supported(),
.has_extended_dynamic_state_2_extra = device.IsExtExtendedDynamicState2ExtrasSupported(),
.has_extended_dynamic_state_3_blend = device.IsExtExtendedDynamicState3BlendingSupported(),
.has_extended_dynamic_state_3_enables = device.IsExtExtendedDynamicState3EnablesSupported(),
.has_dynamic_vertex_input = device.IsExtVertexInputDynamicStateSupported(),
};
}

View File

@@ -62,41 +62,29 @@ struct DrawParams {
VkViewport GetViewportState(const Device& device, const Maxwell& regs, size_t index, float scale) {
const auto& src = regs.viewport_transform[index];
const auto conv = [scale](float value) {
float new_value = value * scale;
if (scale < 1.0f) {
const bool sign = std::signbit(value);
new_value = std::round(std::abs(new_value));
new_value = sign ? -new_value : new_value;
}
return new_value;
float const new_value = value * scale;
return scale < 1.0f
? std::round(std::abs(new_value)) * (std::signbit(new_value) ? -1.f : 1.f)
: new_value;
};
const float x = conv(src.translate_x - src.scale_x);
const float width = conv(src.scale_x * 2.0f);
float y = conv(src.translate_y - src.scale_y);
float height = conv(src.scale_y * 2.0f);
const bool lower_left = regs.window_origin.mode != Maxwell::WindowOrigin::Mode::UpperLeft;
const bool y_negate = !device.IsNvViewportSwizzleSupported() &&
src.swizzle.y == Maxwell::ViewportSwizzle::NegativeY;
if (lower_left) {
// Flip by surface clip height
y += conv(static_cast<f32>(regs.surface_clip.height));
height = -height;
}
if (y_negate) {
// Flip by viewport height
y += height;
height = -height;
}
const float reduce_z = regs.depth_mode == Maxwell::DepthMode::MinusOneToOne ? 1.0f : 0.0f;
float const w = src.scale_x;
float h = src.scale_y;
if (regs.window_origin.mode == Maxwell::WindowOrigin::Mode::LowerLeft) // Flip by surface clip height
h = -h;
if (!device.IsNvViewportSwizzleSupported() && src.swizzle.y == Maxwell::ViewportSwizzle::NegativeY) // Flip by viewport height
h = -h;
// In theory, a raster flip is equivalent to a texture flip for a whole square viewport
// TODO: one day implement this properly and raster flip the triangles, not the whole viewport... guh
if(regs.viewport_transform[1].scale_y == 0 && regs.window_origin.flip_y != 0)
h = -h;
float const x = src.translate_x - w;
float const y = src.translate_y - h;
float const reduce_z = regs.depth_mode == Maxwell::DepthMode::MinusOneToOne ? 1.0f : 0.0f;
VkViewport viewport{
.x = x,
.y = y,
.width = width != 0.0f ? width : 1.0f,
.height = height != 0.0f ? height : 1.0f,
.x = conv(x),
.y = conv(y),
.width = w != 0.0f ? conv(w * 2.f) : 1.0f,
.height = h != 0.0f ? conv(h * 2.f) : 1.0f,
.minDepth = src.translate_z - src.scale_z * reduce_z,
.maxDepth = src.translate_z + src.scale_z,
};
@@ -945,7 +933,6 @@ bool AccelerateDMA::BufferToImage(const Tegra::DMA::ImageCopy& copy_info,
void RasterizerVulkan::UpdateDynamicStates() {
auto& regs = maxwell3d->regs;
UpdateViewportsState(regs);
UpdateScissorsState(regs);
UpdateDepthBias(regs);
@@ -953,10 +940,7 @@ void RasterizerVulkan::UpdateDynamicStates() {
UpdateDepthBounds(regs);
UpdateStencilFaces(regs);
UpdateLineWidth(regs);
const u8 dynamic_state = Settings::values.dyna_state.GetValue();
if (device.IsExtExtendedDynamicStateSupported() && dynamic_state > 0) {
if (device.IsExtExtendedDynamicStateSupported()) {
UpdateCullMode(regs);
UpdateDepthCompareOp(regs);
UpdateFrontFace(regs);
@@ -966,42 +950,45 @@ void RasterizerVulkan::UpdateDynamicStates() {
UpdateDepthTestEnable(regs);
UpdateDepthWriteEnable(regs);
UpdateStencilTestEnable(regs);
if (device.IsExtExtendedDynamicState2Supported() && dynamic_state > 1) {
if (device.IsExtExtendedDynamicState2Supported()) {
UpdatePrimitiveRestartEnable(regs);
UpdateRasterizerDiscardEnable(regs);
UpdateDepthBiasEnable(regs);
}
if (device.IsExtExtendedDynamicState3EnablesSupported() && dynamic_state > 2) {
if (device.IsExtExtendedDynamicState3EnablesSupported()) {
using namespace Tegra::Engines;
if (device.GetDriverID() == VkDriverIdKHR::VK_DRIVER_ID_AMD_OPEN_SOURCE || device.GetDriverID() == VkDriverIdKHR::VK_DRIVER_ID_AMD_PROPRIETARY) {
struct In {
const Maxwell3D::Regs::VertexAttribute::Type d;
In(Maxwell3D::Regs::VertexAttribute::Type n) : d(n) {}
bool operator()(Maxwell3D::Regs::VertexAttribute n) const {
return n.type == d;
const auto has_float = std::any_of(
regs.vertex_attrib_format.begin(),
regs.vertex_attrib_format.end(),
[](const auto& attrib) {
return attrib.type == Maxwell3D::Regs::VertexAttribute::Type::Float;
}
};
auto has_float = std::any_of(regs.vertex_attrib_format.begin(), regs.vertex_attrib_format.end(), In(Maxwell3D::Regs::VertexAttribute::Type::Float));
);
if (regs.logic_op.enable) {
regs.logic_op.enable = static_cast<u32>(!has_float);
}
}
UpdateLogicOpEnable(regs);
UpdateDepthClampEnable(regs);
UpdateLineStippleEnable(regs);
UpdateConservativeRasterizationMode(regs);
}
}
if (device.IsExtExtendedDynamicState2ExtrasSupported() && dynamic_state > 1) {
if (device.IsExtExtendedDynamicState2ExtrasSupported()) {
UpdateLogicOp(regs);
}
if (device.IsExtExtendedDynamicState3BlendingSupported() && dynamic_state > 2) {
if (device.IsExtExtendedDynamicState3BlendingSupported()) {
UpdateBlending(regs);
}
if (device.IsExtExtendedDynamicState3EnablesSupported()) {
UpdateLineStippleEnable(regs);
UpdateConservativeRasterizationMode(regs);
}
}
if (device.IsExtVertexInputDynamicStateSupported() && dynamic_state > 0)
if (auto* gp = pipeline_cache.CurrentGraphicsPipeline(); gp && gp->HasDynamicVertexInput())
if (device.IsExtVertexInputDynamicStateSupported()) {
if (auto* gp = pipeline_cache.CurrentGraphicsPipeline(); gp && gp->HasDynamicVertexInput()) {
UpdateVertexInput(regs);
}
}
}
void RasterizerVulkan::HandleTransformFeedback() {
@@ -1027,10 +1014,10 @@ void RasterizerVulkan::UpdateViewportsState(Tegra::Engines::Maxwell3D::Regs& reg
return;
}
if (!regs.viewport_scale_offset_enabled) {
float x = static_cast<float>(regs.surface_clip.x);
float y = static_cast<float>(regs.surface_clip.y);
float width = (std::max)(1.0f, static_cast<float>(regs.surface_clip.width));
float height = (std::max)(1.0f, static_cast<float>(regs.surface_clip.height));
float x = float(regs.surface_clip.x);
float y = float(regs.surface_clip.y);
float width = (std::max)(1.0f, float(regs.surface_clip.width));
float height = (std::max)(1.0f, float(regs.surface_clip.height));
if (regs.window_origin.mode != Maxwell::WindowOrigin::Mode::UpperLeft) {
y += height;
height = -height;

View File

@@ -287,10 +287,10 @@ void Scheduler::EndRenderPass()
for (size_t i = 0; i < num_images; ++i) {
const VkImageSubresourceRange& range = ranges[i];
const bool is_color = range.aspectMask & VK_IMAGE_ASPECT_COLOR_BIT;
const bool is_depth_stencil = range.aspectMask
const bool is_color = (range.aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) != 0;
const bool is_depth_stencil = (range.aspectMask
& (VK_IMAGE_ASPECT_DEPTH_BIT
| VK_IMAGE_ASPECT_STENCIL_BIT);
| VK_IMAGE_ASPECT_STENCIL_BIT)) !=0;
VkAccessFlags src_access = 0;
VkPipelineStageFlags this_stage = 0;
@@ -326,14 +326,19 @@ void Scheduler::EndRenderPass()
};
}
// Graft: ensure explicit fragment tests + color output stages are always synchronized (AMD/Windows fix)
src_stages |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
cmdbuf.EndRenderPass();
cmdbuf.PipelineBarrier(src_stages,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
0,
{},
{},
{barriers.data(), num_images} // Batched image barriers
nullptr,
nullptr,
vk::Span(barriers.data(), num_images) // Batched image barriers
);
});

View File

@@ -494,18 +494,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
CollectPhysicalMemoryInfo();
CollectToolingInfo();
if (is_qualcomm || is_turnip) {
LOG_WARNING(Render_Vulkan,
"Qualcomm and Turnip drivers have broken VK_EXT_custom_border_color");
//RemoveExtensionFeature(extensions.custom_border_color, features.custom_border_color,
//VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME);
}
if (is_qualcomm) {
LOG_WARNING(Render_Vulkan,
"Qualcomm drivers have a slow VK_KHR_push_descriptor implementation");
//RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
LOG_WARNING(Render_Vulkan,
"Disabling shader float controls and 64-bit integer features on Qualcomm proprietary drivers");
RemoveExtension(extensions.shader_float_controls, VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME);
@@ -544,93 +533,36 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
if (arch >= NvidiaArchitecture::Arch_AmpereOrNewer) {
LOG_WARNING(Render_Vulkan, "Ampere and newer have broken float16 math");
features.shader_float16_int8.shaderFloat16 = false;
} else if (arch <= NvidiaArchitecture::Arch_Volta) {
if (nv_major_version < 527) {
LOG_WARNING(Render_Vulkan, "Volta and older have broken VK_KHR_push_descriptor");
//RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
}
}
if (nv_major_version >= 510) {
LOG_WARNING(Render_Vulkan, "NVIDIA Drivers >= 510 do not support MSAA image blits");
cant_blit_msaa = true;
}
}
if (extensions.extended_dynamic_state && is_radv) {
// Mask driver version variant
const u32 version = (properties.properties.driverVersion << 3) >> 3;
if (version < VK_MAKE_API_VERSION(0, 21, 2, 0)) {
LOG_WARNING(Render_Vulkan,
"RADV versions older than 21.2 have broken VK_EXT_extended_dynamic_state");
//RemoveExtensionFeature(extensions.extended_dynamic_state,
//features.extended_dynamic_state,
//VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
}
}
if (extensions.extended_dynamic_state2 && is_radv) {
const u32 version = (properties.properties.driverVersion << 3) >> 3;
if (version < VK_MAKE_API_VERSION(0, 22, 3, 1)) {
LOG_WARNING(
Render_Vulkan,
"RADV versions older than 22.3.1 have broken VK_EXT_extended_dynamic_state2");
// RemoveExtensionFeature(extensions.extended_dynamic_state2,
// features.extended_dynamic_state2,
// VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
}
}
if (extensions.extended_dynamic_state2 && is_qualcomm) {
const u32 version = (properties.properties.driverVersion << 3) >> 3;
if (version >= VK_MAKE_API_VERSION(0, 0, 676, 0) &&
version < VK_MAKE_API_VERSION(0, 0, 680, 0)) {
// Qualcomm Adreno 7xx drivers do not properly support extended_dynamic_state2.
LOG_WARNING(Render_Vulkan,
"Qualcomm Adreno 7xx drivers have broken VK_EXT_extended_dynamic_state2");
//RemoveExtensionFeature(extensions.extended_dynamic_state2,
//features.extended_dynamic_state2,
//VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
}
}
if (extensions.extended_dynamic_state3 && is_radv) {
LOG_WARNING(Render_Vulkan, "RADV has broken extendedDynamicState3ColorBlendEquation");
features.extended_dynamic_state3.extendedDynamicState3ColorBlendEnable = true;
features.extended_dynamic_state3.extendedDynamicState3ColorBlendEquation = true;
dynamic_state3_blending = true;
features.extended_dynamic_state3.extendedDynamicState3ColorBlendEnable = false;
features.extended_dynamic_state3.extendedDynamicState3ColorBlendEquation = false;
dynamic_state3_blending = false;
const u32 version = (properties.properties.driverVersion << 3) >> 3;
if (version < VK_MAKE_API_VERSION(0, 23, 1, 0)) {
LOG_WARNING(Render_Vulkan,
"RADV versions older than 23.1.0 have broken depth clamp dynamic state");
features.extended_dynamic_state3.extendedDynamicState3DepthClampEnable = true;
dynamic_state3_enables = true;
features.extended_dynamic_state3.extendedDynamicState3DepthClampEnable = false;
dynamic_state3_enables = false;
}
}
if (extensions.extended_dynamic_state3 && (is_amd_driver || driver_id == VK_DRIVER_ID_SAMSUNG_PROPRIETARY)) {
// AMD and Samsung drivers have broken extendedDynamicState3ColorBlendEquation
LOG_WARNING(Render_Vulkan,
"AMD and Samsung drivers have broken extendedDynamicState3ColorBlendEquation");
features.extended_dynamic_state3.extendedDynamicState3ColorBlendEnable = true;
features.extended_dynamic_state3.extendedDynamicState3ColorBlendEquation = true;
dynamic_state3_blending = true;
}
if (extensions.vertex_input_dynamic_state && is_radv) {
// TODO(ameerj): Blacklist only offending driver versions
// TODO(ameerj): Confirm if RDNA1 is affected
const bool is_rdna2 =
supported_extensions.contains(VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME);
if (is_rdna2) {
LOG_WARNING(Render_Vulkan,
"RADV has broken VK_EXT_vertex_input_dynamic_state on RDNA2 hardware");
// RemoveExtensionFeature(extensions.vertex_input_dynamic_state,
// features.vertex_input_dynamic_state,
// VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
}
}
if (extensions.vertex_input_dynamic_state && is_qualcomm) {
// Qualcomm drivers do not properly support vertex_input_dynamic_state.
LOG_WARNING(Render_Vulkan,
"Qualcomm drivers have broken VK_EXT_vertex_input_dynamic_state");
//RemoveExtensionFeature(extensions.vertex_input_dynamic_state,
// features.vertex_input_dynamic_state,
// VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
features.extended_dynamic_state3.extendedDynamicState3ColorBlendEnable = false;
features.extended_dynamic_state3.extendedDynamicState3ColorBlendEquation = false;
dynamic_state3_blending = false;
}
sets_per_pool = 64;
@@ -644,12 +576,14 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
has_broken_cube_compatibility = true;
}
}
if (is_qualcomm) {
const u32 version = (properties.properties.driverVersion << 3) >> 3;
if (version < VK_MAKE_API_VERSION(0, 255, 615, 512)) {
has_broken_parallel_compiling = true;
}
}
if (extensions.sampler_filter_minmax && is_amd) {
// Disable ext_sampler_filter_minmax on AMD GCN4 and lower as it is broken.
if (!features.shader_float16_int8.shaderFloat16) {
@@ -660,24 +594,17 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
}
}
if (extensions.vertex_input_dynamic_state && is_intel_windows) {
const u32 version = (properties.properties.driverVersion << 3) >> 3;
if (version < VK_MAKE_API_VERSION(27, 20, 100, 0)) {
LOG_WARNING(Render_Vulkan, "Intel has broken VK_EXT_vertex_input_dynamic_state");
//RemoveExtensionFeature(extensions.vertex_input_dynamic_state,
//features.vertex_input_dynamic_state,
//VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
}
}
if (features.shader_float16_int8.shaderFloat16 && is_intel_windows) {
// Intel's compiler crashes when using fp16 on Astral Chain, disable it for the time being.
LOG_WARNING(Render_Vulkan, "Intel has broken float16 math");
features.shader_float16_int8.shaderFloat16 = false;
}
if (is_intel_windows) {
LOG_WARNING(Render_Vulkan, "Intel proprietary drivers do not support MSAA image blits");
cant_blit_msaa = true;
}
has_broken_compute =
CheckBrokenCompute(properties.driver.driverID, properties.properties.driverVersion) &&
!Settings::values.enable_compute_pipelines.GetValue();
@@ -685,24 +612,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
LOG_WARNING(Render_Vulkan, "Driver does not support native BGR format");
must_emulate_bgr565 = true;
}
if (extensions.push_descriptor && is_intel_anv) {
const u32 version = (properties.properties.driverVersion << 3) >> 3;
if (version >= VK_MAKE_API_VERSION(0, 22, 3, 0) &&
version < VK_MAKE_API_VERSION(0, 23, 2, 0)) {
// Disable VK_KHR_push_descriptor due to
// mesa/mesa/-/commit/ff91c5ca42bc80aa411cb3fd8f550aa6fdd16bdc
LOG_WARNING(Render_Vulkan,
"ANV drivers 22.3.0 to 23.1.0 have broken VK_KHR_push_descriptor");
//RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
}
} else if (extensions.push_descriptor && is_nvidia) {
const auto arch = GetNvidiaArch();
if (arch <= NvidiaArchitecture::Arch_Pascal) {
LOG_WARNING(Render_Vulkan,
"Pascal and older architectures have broken VK_KHR_push_descriptor");
//RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
}
}
if (is_mvk) {
LOG_WARNING(Render_Vulkan,
@@ -730,8 +639,8 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
"Removing extendedDynamicState3 due to missing extendedDynamicState2");
RemoveExtensionFeature(extensions.extended_dynamic_state3, features.extended_dynamic_state3,
VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME);
dynamic_state3_blending = true;
dynamic_state3_enables = true;
dynamic_state3_blending = false;
dynamic_state3_enables = false;
}
// Mesa Intel drivers on UHD 620 have broken EDS causing extreme flickering - unknown if it affects other iGPUs
@@ -743,22 +652,26 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
Settings::values.dyna_state.SetValue(0);
}
if (Settings::values.dyna_state.GetValue() == 0) {
must_emulate_scaled_formats = true;
LOG_INFO(Render_Vulkan, "Extended dynamic state is fully disabled, scaled format emulation is ON");
RemoveExtensionFeature(extensions.custom_border_color, features.custom_border_color, VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME);
switch (Settings::values.dyna_state.GetValue()) {
case 0:
RemoveExtensionFeature(extensions.extended_dynamic_state, features.extended_dynamic_state, VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
[[fallthrough]];
case 1:
RemoveExtensionFeature(extensions.extended_dynamic_state2, features.extended_dynamic_state2, VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
[[fallthrough]];
case 2:
RemoveExtensionFeature(extensions.extended_dynamic_state3, features.extended_dynamic_state3, VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME);
RemoveExtensionFeature(extensions.vertex_input_dynamic_state, features.vertex_input_dynamic_state, VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
dynamic_state3_blending = false;
dynamic_state3_enables = false;
break;
}
LOG_INFO(Render_Vulkan, "All dynamic state extensions and features have been disabled");
} else {
must_emulate_scaled_formats = false;
LOG_INFO(Render_Vulkan, "Extended dynamic state is enabled, scaled format emulation is OFF");
if (!extensions.extended_dynamic_state) {
Settings::values.vertex_input_dynamic_state.SetValue(false);
}
if (!Settings::values.vertex_input_dynamic_state.GetValue()) {
RemoveExtensionFeature(extensions.vertex_input_dynamic_state, features.vertex_input_dynamic_state, VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
}
logical = vk::Device::Create(physical, queue_cis, ExtensionListForVulkan(loaded_extensions), first_next, dld);

View File

@@ -128,7 +128,7 @@ li.checked::marker { content: &quot;\2612&quot;; }
<item>
<widget class="QLabel" name="labelLinks">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://eden-emulator.github.io/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Website&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://git.eden-emu.dev&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Source Code&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://git.eden-emu.dev/eden-emu/eden/activity/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contributors&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://discord.gg/kXAmGCXBGD&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Discord&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://rvlt.gg/qKgFEAbH&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Revolt&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://nitter.poast.org/edenemuofficial&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Twitter&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://git.eden-emu.dev/eden-emu/eden/src/branch/master/LICENSE.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;License&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://eden-emulator.github.io/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Website&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://git.eden-emu.dev&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Source Code&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://git.eden-emu.dev/eden-emu/eden/activity/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contributors&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://discord.gg/HstXbPch7X&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Discord&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://rvlt.gg/qKgFEAbH&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Revolt&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://nitter.poast.org/edenemuofficial&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Twitter&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://git.eden-emu.dev/eden-emu/eden/src/branch/master/LICENSE.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;License&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="openExternalLinks">
<bool>true</bool>

View File

@@ -83,14 +83,6 @@ ConfigureMotionTouch::ConfigureMotionTouch(QWidget* parent,
: QDialog(parent), input_subsystem{input_subsystem_},
ui(std::make_unique<Ui::ConfigureMotionTouch>()) {
ui->setupUi(this);
ui->udp_learn_more->setOpenExternalLinks(true);
ui->udp_learn_more->setText(
tr("<a "
"href='https://eden-emulator.github.io/wiki/"
"using-a-controller-or-android-phone-for-motion-or-touch-input'><span "
"style=\"text-decoration: underline; color:#039be5;\">Learn More</span></a>"));
SetConfiguration();
UpdateUiDisplay();
ConnectEvents();

View File

@@ -182,13 +182,6 @@
<property name="rightMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="udp_learn_more">
<property name="text">
<string>Learn More</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="udp_test">
<property name="sizePolicy">

View File

@@ -14,7 +14,7 @@
<item row="0" column="0" colspan="4">
<widget class="QLabel" name="label_1">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Reads controller input from scripts in the same format as TAS-nx scripts.&lt;br/&gt;For a more detailed explanation, please consult the &lt;a href=&quot;https://eden-emulator.github.io/help/feature/tas/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;help page&lt;/span&gt;&lt;/a&gt; on the Eden website.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Reads controller input from scripts in the same format as TAS-nx scripts.&lt;br/&gt;For a more detailed explanation, please consult the user handbook.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="openExternalLinks">
<bool>true</bool>

View File

@@ -577,10 +577,7 @@ MainWindow::MainWindow(bool has_broken_vulkan)
UISettings::values.has_broken_vulkan = true;
QMessageBox::warning(this, tr("Broken Vulkan Installation Detected"),
tr("Vulkan initialization failed during boot.<br><br>Click <a "
"href='https://eden-emulator.github.io/wiki/faq/"
"#yuzu-starts-with-the-error-broken-vulkan-installation-detected'>"
"here for instructions to fix the issue</a>."));
tr("Vulkan initialization failed during boot."));
#ifdef HAS_OPENGL
Settings::values.renderer_backend = Settings::RendererBackend::OpenGL;
@@ -1913,10 +1910,8 @@ bool MainWindow::LoadROM(const QString& filename, Service::AM::FrontendAppletPar
tr("You are using the deconstructed ROM directory format for this game, which is an "
"outdated format that has been superseded by others such as NCA, NAX, XCI, or "
"NSP. Deconstructed ROM directories lack icons, metadata, and update "
"support.<br><br>For an explanation of the various Switch formats Eden supports, <a "
"href='https://eden-emulator.github.io/wiki/overview-of-switch-game-formats'>check "
"out our "
"wiki</a>. This message will not be shown again."));
"support.<br>For an explanation of the various Switch formats Eden supports, "
"out our user handbook. This message will not be shown again."));
}
if (result != Core::SystemResultStatus::Success) {
@@ -2738,13 +2733,12 @@ void MainWindow::OnGameListNavigateToGamedbEntry(u64 program_id,
directory = it->second.second;
}
QDesktopServices::openUrl(
QUrl(QStringLiteral("https://eden-emulator.github.io/game/") + directory));
QDesktopServices::openUrl(QUrl(QStringLiteral("https://www.emuready.com/listings?emulatorIds=43bfc023-ec22-422d-8324-048a8ec9f28f") + directory));
}
void MainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& game_path,
const QtCommon::Game::ShortcutTarget target) {
// Create shortcut
// Create shortcu
std::string arguments = fmt::format("-g \"{:s}\"", game_path);
QtCommon::Game::CreateShortcut(game_path, program_id, "", target, arguments, true);