Compare commits
75 Commits
stuffmadef
...
true-eds-g
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5046f84da8 | ||
|
|
58b2a8a810 | ||
|
|
b50ace27ee | ||
|
|
93eb41e531 | ||
|
|
d8acbdb71b | ||
|
|
dcc95778fd | ||
|
|
2392a1cfb6 | ||
|
|
abd88d5498 | ||
|
|
e60e448b10 | ||
|
|
0bb092b0d9 | ||
|
|
3928495e7e | ||
|
|
2a6e734fa2 | ||
|
|
239841ec93 | ||
|
|
629d1f3609 | ||
|
|
e4bb934438 | ||
|
|
54bb5b79f8 | ||
|
|
a18f78367d | ||
|
|
29dfd96ae6 | ||
|
|
0d92c21ca7 | ||
|
|
b4f4ec7cbb | ||
|
|
7034974dce | ||
|
|
7d41cc6a72 | ||
|
|
8315f15cc1 | ||
|
|
f63818be3c | ||
|
|
8cce12f3d3 | ||
|
|
2abf124f09 | ||
|
|
2afdafc5cb | ||
|
|
6a230bec1a | ||
|
|
18dabbaaff | ||
|
|
f24b7015be | ||
|
|
5738b48694 | ||
|
|
3adf212933 | ||
|
|
60645dc51c | ||
|
|
b9f3e3b258 | ||
|
|
f6287ea7cd | ||
|
|
7867c19b2c | ||
|
|
b1ec27dfce | ||
|
|
3760b8c097 | ||
|
|
34530edd39 | ||
|
|
a3c0e0ac2d | ||
|
|
39e3a56575 | ||
|
|
66c26e39fe | ||
|
|
7389b35a98 | ||
|
|
a96b601cb5 | ||
|
|
567a203f56 | ||
|
|
1cd270cf4f | ||
|
|
b053517c67 | ||
|
|
1f5b866290 | ||
|
|
57d56086c4 | ||
|
|
ad4ceaf7e3 | ||
|
|
b0d0b61f88 | ||
|
|
e7eb4dba78 | ||
|
|
d82b333383 | ||
|
|
8e90752f62 | ||
|
|
c28c19dffa | ||
|
|
3cadb3715b | ||
|
|
a17e295a13 | ||
|
|
d7c932cc93 | ||
|
|
3334e9aafb | ||
|
|
21a4752401 | ||
|
|
e537cba7f3 | ||
|
|
9578c4b29c | ||
|
|
963fb7c803 | ||
|
|
3ba40e7274 | ||
|
|
fe13539d72 | ||
|
|
be218cc020 | ||
|
|
c3cbe2d4d0 | ||
|
|
79b162a37c | ||
|
|
f3fbb3812f | ||
|
|
d8caa74233 | ||
|
|
17fe74ef11 | ||
|
|
73713737c6 | ||
|
|
61f3ce643c | ||
|
|
f7f6a4cde4 | ||
|
|
65fa1a37e2 |
8
.github/ISSUE_TEMPLATE/config.yml
vendored
8
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -53,6 +53,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"
|
||||
@@ -208,12 +209,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 +238,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 +494,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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
8
src/android/app/src/main/res/xml/file_paths.xml
Normal file
8
src/android/app/src/main/res/xml/file_paths.xml
Normal 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>
|
||||
@@ -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};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -542,8 +545,9 @@ Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id c
|
||||
lod = Id{};
|
||||
}
|
||||
const ImageOperands operands(lod, ms);
|
||||
return Emit(&EmitContext::OpImageSparseFetch, &EmitContext::OpImageFetch, ctx, inst, ctx.F32[4],
|
||||
TextureImage(ctx, info, index), coords, operands.MaskOptional(), operands.Span());
|
||||
return Emit(&EmitContext::OpImageSparseFetch, &EmitContext::OpImageFetch, ctx, inst,
|
||||
ctx.F32[4], TextureImage(ctx, info, index), coords, operands.MaskOptional(),
|
||||
operands.Span());
|
||||
}
|
||||
|
||||
Id EmitImageQueryDimensions(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id lod,
|
||||
|
||||
@@ -315,6 +315,9 @@ void DefineSsbos(EmitContext& ctx, StorageTypeDefinition& type_def,
|
||||
ctx.Decorate(id, spv::Decoration::Binding, binding);
|
||||
ctx.Decorate(id, spv::Decoration::DescriptorSet, 0U);
|
||||
ctx.Name(id, fmt::format("ssbo{}", index));
|
||||
if (!desc.is_written) {
|
||||
ctx.Decorate(id, spv::Decoration::NonWritable);
|
||||
}
|
||||
if (ctx.profile.supported_spirv >= 0x00010400) {
|
||||
ctx.interfaces.push_back(id);
|
||||
}
|
||||
@@ -1432,6 +1435,9 @@ void EmitContext::DefineInputs(const IR::Program& program) {
|
||||
}
|
||||
if (info.uses_sample_id) {
|
||||
sample_id = DefineInput(*this, U32[1], false, spv::BuiltIn::SampleId);
|
||||
if (stage == Stage::Fragment) {
|
||||
Decorate(sample_id, spv::Decoration::Flat);
|
||||
}
|
||||
}
|
||||
if (info.uses_is_helper_invocation) {
|
||||
is_helper_invocation = DefineInput(*this, U1, false, spv::BuiltIn::HelperInvocation);
|
||||
@@ -1442,6 +1448,13 @@ void EmitContext::DefineInputs(const IR::Program& program) {
|
||||
subgroup_mask_le = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupLeMaskKHR);
|
||||
subgroup_mask_gt = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupGtMaskKHR);
|
||||
subgroup_mask_ge = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupGeMaskKHR);
|
||||
if (stage == Stage::Fragment) {
|
||||
Decorate(subgroup_mask_eq, spv::Decoration::Flat);
|
||||
Decorate(subgroup_mask_lt, spv::Decoration::Flat);
|
||||
Decorate(subgroup_mask_le, spv::Decoration::Flat);
|
||||
Decorate(subgroup_mask_gt, spv::Decoration::Flat);
|
||||
Decorate(subgroup_mask_ge, spv::Decoration::Flat);
|
||||
}
|
||||
}
|
||||
if (info.uses_fswzadd || info.uses_subgroup_invocation_id || info.uses_subgroup_shuffles ||
|
||||
(profile.warp_size_potentially_larger_than_guest &&
|
||||
@@ -1449,7 +1462,9 @@ void EmitContext::DefineInputs(const IR::Program& program) {
|
||||
AddCapability(spv::Capability::GroupNonUniform);
|
||||
subgroup_local_invocation_id =
|
||||
DefineInput(*this, U32[1], false, spv::BuiltIn::SubgroupLocalInvocationId);
|
||||
Decorate(subgroup_local_invocation_id, spv::Decoration::Flat);
|
||||
if (stage == Stage::Fragment) {
|
||||
Decorate(subgroup_local_invocation_id, spv::Decoration::Flat);
|
||||
}
|
||||
}
|
||||
if (info.uses_fswzadd) {
|
||||
const Id f32_one{Const(1.0f)};
|
||||
@@ -1461,6 +1476,9 @@ void EmitContext::DefineInputs(const IR::Program& program) {
|
||||
}
|
||||
if (loads[IR::Attribute::PrimitiveId]) {
|
||||
primitive_id = DefineInput(*this, U32[1], false, spv::BuiltIn::PrimitiveId);
|
||||
if (stage == Stage::Fragment) {
|
||||
Decorate(primitive_id, spv::Decoration::Flat);
|
||||
}
|
||||
}
|
||||
if (loads[IR::Attribute::Layer]) {
|
||||
AddCapability(spv::Capability::Geometry);
|
||||
@@ -1552,17 +1570,21 @@ void EmitContext::DefineInputs(const IR::Program& program) {
|
||||
if (stage != Stage::Fragment) {
|
||||
continue;
|
||||
}
|
||||
switch (info.interpolation[index]) {
|
||||
case Interpolation::Smooth:
|
||||
// Default
|
||||
// Decorate(id, spv::Decoration::Smooth);
|
||||
break;
|
||||
case Interpolation::NoPerspective:
|
||||
Decorate(id, spv::Decoration::NoPerspective);
|
||||
break;
|
||||
case Interpolation::Flat:
|
||||
const bool is_integer = input_type == AttributeType::SignedInt ||
|
||||
input_type == AttributeType::UnsignedInt;
|
||||
if (is_integer) {
|
||||
Decorate(id, spv::Decoration::Flat);
|
||||
break;
|
||||
} else {
|
||||
switch (info.interpolation[index]) {
|
||||
case Interpolation::Smooth:
|
||||
break;
|
||||
case Interpolation::NoPerspective:
|
||||
Decorate(id, spv::Decoration::NoPerspective);
|
||||
break;
|
||||
case Interpolation::Flat:
|
||||
Decorate(id, spv::Decoration::Flat);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (stage == Stage::TessellationEval) {
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -351,7 +354,7 @@ std::optional<StorageBufferAddr> Track(const IR::Value& value, const Bias* bias)
|
||||
.index = index.U32(),
|
||||
.offset = offset.U32(),
|
||||
};
|
||||
const u32 alignment{bias ? bias->alignment : 8U};
|
||||
const u32 alignment{bias ? bias->alignment : 16U};
|
||||
if (!Common::IsAligned(storage_buffer.offset, alignment)) {
|
||||
// The SSBO pointer has to be aligned
|
||||
return std::nullopt;
|
||||
@@ -372,9 +375,9 @@ void CollectStorageBuffers(IR::Block& block, IR::Inst& inst, StorageInfo& info)
|
||||
// avoid getting false positives
|
||||
static constexpr Bias nvn_bias{
|
||||
.index = 0,
|
||||
.offset_begin = 0x100,
|
||||
.offset_end = 0x700,
|
||||
.alignment = 16,
|
||||
.offset_begin = 0x110,
|
||||
.offset_end = 0x800,
|
||||
.alignment = 32,
|
||||
};
|
||||
// Track the low address of the instruction
|
||||
const std::optional<LowAddrInfo> low_addr_info{TrackLowAddress(&inst)};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -525,18 +525,24 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_,
|
||||
nullptr, PUSH_CONSTANT_RANGE<VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(float) * 4>))),
|
||||
full_screen_vert(BuildShader(device, FULL_SCREEN_TRIANGLE_VERT_SPV)),
|
||||
blit_color_to_color_frag(BuildShader(device, BLIT_COLOR_FLOAT_FRAG_SPV)),
|
||||
blit_depth_stencil_frag(BuildShader(device, VULKAN_BLIT_DEPTH_STENCIL_FRAG_SPV)),
|
||||
blit_depth_stencil_frag(device.IsExtShaderStencilExportSupported()
|
||||
? BuildShader(device, VULKAN_BLIT_DEPTH_STENCIL_FRAG_SPV)
|
||||
: vk::ShaderModule{}),
|
||||
clear_color_vert(BuildShader(device, VULKAN_COLOR_CLEAR_VERT_SPV)),
|
||||
clear_color_frag(BuildShader(device, VULKAN_COLOR_CLEAR_FRAG_SPV)),
|
||||
clear_stencil_frag(BuildShader(device, VULKAN_DEPTHSTENCIL_CLEAR_FRAG_SPV)),
|
||||
convert_depth_to_float_frag(BuildShader(device, CONVERT_DEPTH_TO_FLOAT_FRAG_SPV)),
|
||||
convert_float_to_depth_frag(BuildShader(device, CONVERT_FLOAT_TO_DEPTH_FRAG_SPV)),
|
||||
convert_abgr8_to_d24s8_frag(BuildShader(device, CONVERT_ABGR8_TO_D24S8_FRAG_SPV)),
|
||||
convert_abgr8_to_d24s8_frag(device.IsExtShaderStencilExportSupported()
|
||||
? BuildShader(device, CONVERT_ABGR8_TO_D24S8_FRAG_SPV)
|
||||
: vk::ShaderModule{}),
|
||||
convert_abgr8_to_d32f_frag(BuildShader(device, CONVERT_ABGR8_TO_D32F_FRAG_SPV)),
|
||||
convert_d32f_to_abgr8_frag(BuildShader(device, CONVERT_D32F_TO_ABGR8_FRAG_SPV)),
|
||||
convert_d24s8_to_abgr8_frag(BuildShader(device, CONVERT_D24S8_TO_ABGR8_FRAG_SPV)),
|
||||
convert_s8d24_to_abgr8_frag(BuildShader(device, CONVERT_S8D24_TO_ABGR8_FRAG_SPV)),
|
||||
convert_abgr8_srgb_to_d24s8_frag(BuildShader(device, CONVERT_ABGR8_SRGB_TO_D24S8_FRAG_SPV)),
|
||||
convert_abgr8_srgb_to_d24s8_frag(device.IsExtShaderStencilExportSupported()
|
||||
? BuildShader(device, CONVERT_ABGR8_SRGB_TO_D24S8_FRAG_SPV)
|
||||
: vk::ShaderModule{}),
|
||||
convert_rgba_to_bgra_frag(BuildShader(device, CONVERT_RGBA8_TO_BGRA8_FRAG_SPV)),
|
||||
convert_yuv420_to_rgb_comp(BuildShader(device, CONVERT_YUV420_TO_RGB_COMP_SPV)),
|
||||
convert_rgb_to_yuv420_comp(BuildShader(device, CONVERT_RGB_TO_YUV420_COMP_SPV)),
|
||||
@@ -667,6 +673,11 @@ void BlitImageHelper::ConvertR16ToD16(const Framebuffer* dst_framebuffer,
|
||||
|
||||
void BlitImageHelper::ConvertABGR8ToD24S8(const Framebuffer* dst_framebuffer,
|
||||
const ImageView& src_image_view) {
|
||||
if (!device.IsExtShaderStencilExportSupported()) {
|
||||
// Shader requires VK_EXT_shader_stencil_export which is not available
|
||||
LOG_WARNING(Render_Vulkan, "ConvertABGR8ToD24S8 requires shader_stencil_export, skipping");
|
||||
return;
|
||||
}
|
||||
ConvertPipelineDepthTargetEx(convert_abgr8_to_d24s8_pipeline, dst_framebuffer->RenderPass(),
|
||||
convert_abgr8_to_d24s8_frag);
|
||||
Convert(*convert_abgr8_to_d24s8_pipeline, dst_framebuffer, src_image_view);
|
||||
@@ -702,6 +713,11 @@ void BlitImageHelper::ConvertS8D24ToABGR8(const Framebuffer* dst_framebuffer,
|
||||
|
||||
void BlitImageHelper::ConvertABGR8SRGBToD24S8(const Framebuffer* dst_framebuffer,
|
||||
const ImageView& src_image_view) {
|
||||
if (!device.IsExtShaderStencilExportSupported()) {
|
||||
// Shader requires VK_EXT_shader_stencil_export which is not available
|
||||
LOG_WARNING(Render_Vulkan, "ConvertABGR8SRGBToD24S8 requires shader_stencil_export, skipping");
|
||||
return;
|
||||
}
|
||||
ConvertPipelineDepthTargetEx(convert_abgr8_srgb_to_d24s8_pipeline,
|
||||
dst_framebuffer->RenderPass(),
|
||||
convert_abgr8_srgb_to_d24s8_frag);
|
||||
|
||||
@@ -60,7 +60,7 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFe
|
||||
raw1 = 0;
|
||||
extended_dynamic_state.Assign(features.has_extended_dynamic_state ? 1 : 0);
|
||||
extended_dynamic_state_2.Assign(features.has_extended_dynamic_state_2 ? 1 : 0);
|
||||
extended_dynamic_state_2_extra.Assign(features.has_extended_dynamic_state_2_extra ? 1 : 0);
|
||||
extended_dynamic_state_2_logic_op.Assign(features.has_extended_dynamic_state_2_logic_op ? 1 : 0);
|
||||
extended_dynamic_state_3_blend.Assign(features.has_extended_dynamic_state_3_blend ? 1 : 0);
|
||||
extended_dynamic_state_3_enables.Assign(features.has_extended_dynamic_state_3_enables ? 1 : 0);
|
||||
dynamic_vertex_input.Assign(features.has_dynamic_vertex_input ? 1 : 0);
|
||||
@@ -158,7 +158,7 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFe
|
||||
return static_cast<u16>(array.stride.Value());
|
||||
});
|
||||
}
|
||||
if (!extended_dynamic_state_2_extra) {
|
||||
if (!extended_dynamic_state_2_logic_op) {
|
||||
dynamic_state.Refresh2(regs, topology_, extended_dynamic_state_2);
|
||||
}
|
||||
if (!extended_dynamic_state_3_blend) {
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -20,7 +23,8 @@ using Maxwell = Tegra::Engines::Maxwell3D::Regs;
|
||||
struct DynamicFeatures {
|
||||
bool has_extended_dynamic_state;
|
||||
bool has_extended_dynamic_state_2;
|
||||
bool has_extended_dynamic_state_2_extra;
|
||||
bool has_extended_dynamic_state_2_logic_op;
|
||||
bool has_extended_dynamic_state_2_patch_control_points;
|
||||
bool has_extended_dynamic_state_3_blend;
|
||||
bool has_extended_dynamic_state_3_enables;
|
||||
bool has_dynamic_vertex_input;
|
||||
@@ -186,7 +190,7 @@ struct FixedPipelineState {
|
||||
u32 raw1;
|
||||
BitField<0, 1, u32> extended_dynamic_state;
|
||||
BitField<1, 1, u32> extended_dynamic_state_2;
|
||||
BitField<2, 1, u32> extended_dynamic_state_2_extra;
|
||||
BitField<2, 1, u32> extended_dynamic_state_2_logic_op;
|
||||
BitField<3, 1, u32> extended_dynamic_state_3_blend;
|
||||
BitField<4, 1, u32> extended_dynamic_state_3_enables;
|
||||
BitField<5, 1, u32> dynamic_vertex_input;
|
||||
|
||||
@@ -579,12 +579,13 @@ 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;
|
||||
}
|
||||
if (device.IsExtExtendedDynamicStateSupported()) {
|
||||
// Use BindVertexBuffers2EXT only if EDS1 is supported AND VIDS is not active
|
||||
// When VIDS is active, the pipeline doesn't declare VERTEX_INPUT_BINDING_STRIDE as dynamic
|
||||
if (device.IsExtExtendedDynamicStateSupported() && !device.IsExtVertexInputDynamicStateSupported()) {
|
||||
scheduler.Record([index, buffer, offset, size, stride](vk::CommandBuffer cmdbuf) {
|
||||
const VkDeviceSize vk_offset = buffer != VK_NULL_HANDLE ? offset : 0;
|
||||
const VkDeviceSize vk_size = buffer != VK_NULL_HANDLE ? size : VK_WHOLE_SIZE;
|
||||
@@ -624,7 +625,8 @@ void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bi
|
||||
if (binding_count == 0) {
|
||||
return;
|
||||
}
|
||||
if (device.IsExtExtendedDynamicStateSupported()) {
|
||||
// Use BindVertexBuffers2EXT only if EDS1 is supported AND VIDS is not active
|
||||
if (device.IsExtExtendedDynamicStateSupported() && !device.IsExtVertexInputDynamicStateSupported()) {
|
||||
scheduler.Record([bindings_ = std::move(bindings),
|
||||
buffer_handles_ = std::move(buffer_handles),
|
||||
binding_count](vk::CommandBuffer cmdbuf) {
|
||||
|
||||
@@ -418,6 +418,9 @@ ConditionalRenderingResolvePass::ConditionalRenderingResolvePass(
|
||||
|
||||
void ConditionalRenderingResolvePass::Resolve(VkBuffer dst_buffer, VkBuffer src_buffer,
|
||||
u32 src_offset, bool compare_to_zero) {
|
||||
if (!device.IsExtConditionalRendering()) {
|
||||
return;
|
||||
}
|
||||
const size_t compare_size = compare_to_zero ? 8 : 24;
|
||||
|
||||
compute_pass_descriptor_queue.Acquire();
|
||||
@@ -448,7 +451,7 @@ void ConditionalRenderingResolvePass::Resolve(VkBuffer dst_buffer, VkBuffer src_
|
||||
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
|
||||
cmdbuf.Dispatch(1, 1, 1);
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
VK_PIPELINE_STAGE_CONDITIONAL_RENDERING_BIT_EXT, 0, write_barrier);
|
||||
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, write_barrier);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -470,6 +473,14 @@ QueriesPrefixScanPass::QueriesPrefixScanPass(
|
||||
void QueriesPrefixScanPass::Run(VkBuffer accumulation_buffer, VkBuffer dst_buffer,
|
||||
VkBuffer src_buffer, size_t number_of_sums,
|
||||
size_t min_accumulation_limit, size_t max_accumulation_limit) {
|
||||
constexpr VkAccessFlags BASE_DST_ACCESS = VK_ACCESS_SHADER_READ_BIT |
|
||||
VK_ACCESS_TRANSFER_READ_BIT |
|
||||
VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT |
|
||||
VK_ACCESS_INDIRECT_COMMAND_READ_BIT |
|
||||
VK_ACCESS_INDEX_READ_BIT |
|
||||
VK_ACCESS_UNIFORM_READ_BIT;
|
||||
const VkAccessFlags conditional_access =
|
||||
device.IsExtConditionalRendering() ? VK_ACCESS_CONDITIONAL_RENDERING_READ_BIT_EXT : 0;
|
||||
size_t current_runs = number_of_sums;
|
||||
size_t offset = 0;
|
||||
while (current_runs != 0) {
|
||||
@@ -486,22 +497,18 @@ void QueriesPrefixScanPass::Run(VkBuffer accumulation_buffer, VkBuffer dst_buffe
|
||||
|
||||
scheduler.RequestOutsideRenderPassOperationContext();
|
||||
scheduler.Record([this, descriptor_data, min_accumulation_limit, max_accumulation_limit,
|
||||
runs_to_do, used_offset](vk::CommandBuffer cmdbuf) {
|
||||
runs_to_do, used_offset, conditional_access](vk::CommandBuffer cmdbuf) {
|
||||
static constexpr VkMemoryBarrier read_barrier{
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
||||
.pNext = nullptr,
|
||||
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT,
|
||||
};
|
||||
static constexpr VkMemoryBarrier write_barrier{
|
||||
const VkMemoryBarrier write_barrier{
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
||||
.pNext = nullptr,
|
||||
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
|
||||
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT |
|
||||
VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT |
|
||||
VK_ACCESS_INDIRECT_COMMAND_READ_BIT | VK_ACCESS_INDEX_READ_BIT |
|
||||
VK_ACCESS_UNIFORM_READ_BIT |
|
||||
VK_ACCESS_CONDITIONAL_RENDERING_READ_BIT_EXT,
|
||||
.dstAccessMask = BASE_DST_ACCESS | conditional_access,
|
||||
};
|
||||
const QueriesPrefixScanPushConstants uniforms{
|
||||
.min_accumulation_base = static_cast<u32>(min_accumulation_limit),
|
||||
@@ -519,8 +526,7 @@ void QueriesPrefixScanPass::Run(VkBuffer accumulation_buffer, VkBuffer dst_buffe
|
||||
cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, uniforms);
|
||||
cmdbuf.Dispatch(1, 1, 1);
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
VK_PIPELINE_STAGE_CONDITIONAL_RENDERING_BIT_EXT, 0,
|
||||
write_barrier);
|
||||
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, write_barrier);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,6 +265,7 @@ GraphicsPipeline::GraphicsPipeline(
|
||||
std::ranges::copy(info->constant_buffer_used_sizes, uniform_buffer_sizes[stage].begin());
|
||||
num_textures += Shader::NumDescriptors(info->texture_descriptors);
|
||||
}
|
||||
fragment_has_color0_output = stage_infos[NUM_STAGES - 1].stores_frag_color[0];
|
||||
auto func{[this, shader_notify, &render_pass_cache, &descriptor_pool, pipeline_statistics] {
|
||||
DescriptorLayoutBuilder builder{MakeBuilder(device, stage_infos)};
|
||||
uses_push_descriptor = builder.CanUsePushDescriptor();
|
||||
@@ -734,13 +735,18 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
|
||||
.lineWidth = 1.0f,
|
||||
// TODO(alekpop): Transfer from regs
|
||||
};
|
||||
const bool smooth_lines_supported =
|
||||
device.IsExtLineRasterizationSupported() && device.SupportsSmoothLines();
|
||||
const bool stippled_lines_supported =
|
||||
device.IsExtLineRasterizationSupported() && device.SupportsStippledRectangularLines();
|
||||
VkPipelineRasterizationLineStateCreateInfoEXT line_state{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_LINE_STATE_CREATE_INFO_EXT,
|
||||
.pNext = nullptr,
|
||||
.lineRasterizationMode = key.state.smooth_lines != 0
|
||||
.lineRasterizationMode = key.state.smooth_lines != 0 && smooth_lines_supported
|
||||
? VK_LINE_RASTERIZATION_MODE_RECTANGULAR_SMOOTH_EXT
|
||||
: VK_LINE_RASTERIZATION_MODE_RECTANGULAR_EXT,
|
||||
.stippledLineEnable = dynamic.line_stipple_enable ? VK_TRUE : VK_FALSE,
|
||||
.stippledLineEnable =
|
||||
(dynamic.line_stipple_enable && stippled_lines_supported) ? VK_TRUE : VK_FALSE,
|
||||
.lineStippleFactor = key.state.line_stipple_factor,
|
||||
.lineStipplePattern = static_cast<uint16_t>(key.state.line_stipple_pattern),
|
||||
};
|
||||
@@ -771,6 +777,8 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
|
||||
provoking_vertex.pNext = std::exchange(rasterization_ci.pNext, &provoking_vertex);
|
||||
}
|
||||
|
||||
const bool supports_alpha_output = fragment_has_color0_output;
|
||||
const bool alpha_to_one_supported = device.SupportsAlphaToOne();
|
||||
const VkPipelineMultisampleStateCreateInfo multisample_ci{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
@@ -779,8 +787,10 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
|
||||
.sampleShadingEnable = Settings::values.sample_shading.GetValue() ? VK_TRUE : VK_FALSE,
|
||||
.minSampleShading = static_cast<float>(Settings::values.sample_shading_fraction.GetValue()) / 100.0f,
|
||||
.pSampleMask = nullptr,
|
||||
.alphaToCoverageEnable = key.state.alpha_to_coverage_enabled != 0 ? VK_TRUE : VK_FALSE,
|
||||
.alphaToOneEnable = key.state.alpha_to_one_enabled != 0 ? VK_TRUE : VK_FALSE,
|
||||
.alphaToCoverageEnable =
|
||||
supports_alpha_output && key.state.alpha_to_coverage_enabled != 0 ? VK_TRUE : VK_FALSE,
|
||||
.alphaToOneEnable = supports_alpha_output && alpha_to_one_supported &&
|
||||
key.state.alpha_to_one_enabled != 0 ? VK_TRUE : VK_FALSE,
|
||||
};
|
||||
const VkPipelineDepthStencilStateCreateInfo depth_stencil_ci{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
|
||||
@@ -838,17 +848,27 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
|
||||
.blendConstants = {}
|
||||
};
|
||||
static_vector<VkDynamicState, 34> dynamic_states{
|
||||
VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR,
|
||||
VK_DYNAMIC_STATE_DEPTH_BIAS, VK_DYNAMIC_STATE_BLEND_CONSTANTS,
|
||||
VK_DYNAMIC_STATE_DEPTH_BOUNDS, VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK,
|
||||
VK_DYNAMIC_STATE_STENCIL_WRITE_MASK, VK_DYNAMIC_STATE_STENCIL_REFERENCE,
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR,
|
||||
VK_DYNAMIC_STATE_DEPTH_BIAS,
|
||||
VK_DYNAMIC_STATE_LINE_WIDTH,
|
||||
};
|
||||
|
||||
if (device.UsesAdvancedCoreDynamicState()) {
|
||||
static constexpr std::array core_dynamic_states{
|
||||
VK_DYNAMIC_STATE_BLEND_CONSTANTS,
|
||||
VK_DYNAMIC_STATE_DEPTH_BOUNDS,
|
||||
VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK,
|
||||
VK_DYNAMIC_STATE_STENCIL_WRITE_MASK,
|
||||
VK_DYNAMIC_STATE_STENCIL_REFERENCE,
|
||||
};
|
||||
dynamic_states.insert(dynamic_states.end(), core_dynamic_states.begin(),
|
||||
core_dynamic_states.end());
|
||||
}
|
||||
if (key.state.extended_dynamic_state) {
|
||||
static constexpr std::array 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,48 +876,68 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
|
||||
VK_DYNAMIC_STATE_STENCIL_TEST_ENABLE_EXT,
|
||||
VK_DYNAMIC_STATE_STENCIL_OP_EXT,
|
||||
};
|
||||
if (key.state.dynamic_vertex_input) {
|
||||
dynamic_states.push_back(VK_DYNAMIC_STATE_VERTEX_INPUT_EXT);
|
||||
}
|
||||
dynamic_states.insert(dynamic_states.end(), extended.begin(), extended.end());
|
||||
if (key.state.extended_dynamic_state_2) {
|
||||
static constexpr std::array extended2{
|
||||
VK_DYNAMIC_STATE_DEPTH_BIAS_ENABLE_EXT,
|
||||
VK_DYNAMIC_STATE_PRIMITIVE_RESTART_ENABLE_EXT,
|
||||
VK_DYNAMIC_STATE_RASTERIZER_DISCARD_ENABLE_EXT,
|
||||
};
|
||||
dynamic_states.insert(dynamic_states.end(), extended2.begin(), extended2.end());
|
||||
|
||||
// VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT is part of EDS1
|
||||
// Only use it if VIDS is not active (VIDS replaces it with full vertex input control)
|
||||
if (!key.state.dynamic_vertex_input) {
|
||||
dynamic_states.push_back(VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT);
|
||||
}
|
||||
if (key.state.extended_dynamic_state_2_extra) {
|
||||
dynamic_states.push_back(VK_DYNAMIC_STATE_LOGIC_OP_EXT);
|
||||
}
|
||||
|
||||
// VK_DYNAMIC_STATE_VERTEX_INPUT_EXT (VIDS) - Independent from EDS
|
||||
// Provides full dynamic vertex input control, replaces VERTEX_INPUT_BINDING_STRIDE
|
||||
if (key.state.dynamic_vertex_input) {
|
||||
dynamic_states.push_back(VK_DYNAMIC_STATE_VERTEX_INPUT_EXT);
|
||||
}
|
||||
|
||||
// EDS2 - Core (3 states)
|
||||
if (key.state.extended_dynamic_state_2) {
|
||||
static constexpr std::array extended2{
|
||||
VK_DYNAMIC_STATE_DEPTH_BIAS_ENABLE_EXT,
|
||||
VK_DYNAMIC_STATE_PRIMITIVE_RESTART_ENABLE_EXT,
|
||||
VK_DYNAMIC_STATE_RASTERIZER_DISCARD_ENABLE_EXT,
|
||||
};
|
||||
dynamic_states.insert(dynamic_states.end(), extended2.begin(), extended2.end());
|
||||
}
|
||||
|
||||
// EDS2 - LogicOp (granular)
|
||||
if (key.state.extended_dynamic_state_2_logic_op) {
|
||||
dynamic_states.push_back(VK_DYNAMIC_STATE_LOGIC_OP_EXT);
|
||||
}
|
||||
|
||||
// EDS3 - Blending (composite: 3 states)
|
||||
if (key.state.extended_dynamic_state_3_blend) {
|
||||
static constexpr std::array extended3{
|
||||
VK_DYNAMIC_STATE_COLOR_BLEND_ENABLE_EXT,
|
||||
VK_DYNAMIC_STATE_COLOR_BLEND_EQUATION_EXT,
|
||||
VK_DYNAMIC_STATE_COLOR_WRITE_MASK_EXT,
|
||||
};
|
||||
dynamic_states.insert(dynamic_states.end(), extended3.begin(), extended3.end());
|
||||
}
|
||||
|
||||
// EDS3 - Enables (composite: per-feature)
|
||||
if (key.state.extended_dynamic_state_3_enables) {
|
||||
if (device.SupportsDynamicState3DepthClampEnable()) {
|
||||
dynamic_states.push_back(VK_DYNAMIC_STATE_DEPTH_CLAMP_ENABLE_EXT);
|
||||
}
|
||||
if (key.state.extended_dynamic_state_3_blend) {
|
||||
static constexpr std::array extended3{
|
||||
VK_DYNAMIC_STATE_COLOR_BLEND_ENABLE_EXT,
|
||||
VK_DYNAMIC_STATE_COLOR_BLEND_EQUATION_EXT,
|
||||
VK_DYNAMIC_STATE_COLOR_WRITE_MASK_EXT,
|
||||
|
||||
// VK_DYNAMIC_STATE_COLOR_BLEND_ADVANCED_EXT,
|
||||
};
|
||||
dynamic_states.insert(dynamic_states.end(), extended3.begin(), extended3.end());
|
||||
if (device.SupportsDynamicState3LogicOpEnable()) {
|
||||
dynamic_states.push_back(VK_DYNAMIC_STATE_LOGIC_OP_ENABLE_EXT);
|
||||
}
|
||||
if (key.state.extended_dynamic_state_3_enables) {
|
||||
static constexpr std::array extended3{
|
||||
VK_DYNAMIC_STATE_DEPTH_CLAMP_ENABLE_EXT,
|
||||
VK_DYNAMIC_STATE_LOGIC_OP_ENABLE_EXT,
|
||||
|
||||
// additional state3 extensions
|
||||
VK_DYNAMIC_STATE_LINE_RASTERIZATION_MODE_EXT,
|
||||
|
||||
VK_DYNAMIC_STATE_CONSERVATIVE_RASTERIZATION_MODE_EXT,
|
||||
|
||||
VK_DYNAMIC_STATE_LINE_STIPPLE_ENABLE_EXT,
|
||||
VK_DYNAMIC_STATE_ALPHA_TO_COVERAGE_ENABLE_EXT,
|
||||
VK_DYNAMIC_STATE_ALPHA_TO_ONE_ENABLE_EXT,
|
||||
VK_DYNAMIC_STATE_DEPTH_CLIP_ENABLE_EXT,
|
||||
VK_DYNAMIC_STATE_PROVOKING_VERTEX_MODE_EXT,
|
||||
};
|
||||
dynamic_states.insert(dynamic_states.end(), extended3.begin(), extended3.end());
|
||||
if (device.SupportsDynamicState3LineRasterizationMode()) {
|
||||
dynamic_states.push_back(VK_DYNAMIC_STATE_LINE_RASTERIZATION_MODE_EXT);
|
||||
}
|
||||
if (device.SupportsDynamicState3ConservativeRasterizationMode()) {
|
||||
dynamic_states.push_back(VK_DYNAMIC_STATE_CONSERVATIVE_RASTERIZATION_MODE_EXT);
|
||||
}
|
||||
if (device.SupportsDynamicState3LineStippleEnable()) {
|
||||
dynamic_states.push_back(VK_DYNAMIC_STATE_LINE_STIPPLE_ENABLE_EXT);
|
||||
}
|
||||
if (device.SupportsDynamicState3AlphaToCoverageEnable()) {
|
||||
dynamic_states.push_back(VK_DYNAMIC_STATE_ALPHA_TO_COVERAGE_ENABLE_EXT);
|
||||
}
|
||||
if (device.SupportsDynamicState3AlphaToOneEnable()) {
|
||||
dynamic_states.push_back(VK_DYNAMIC_STATE_ALPHA_TO_ONE_ENABLE_EXT);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -82,6 +82,17 @@ public:
|
||||
const std::array<const Shader::Info*, NUM_STAGES>& infos);
|
||||
|
||||
bool HasDynamicVertexInput() const noexcept { return key.state.dynamic_vertex_input; }
|
||||
bool SupportsAlphaToCoverage() const noexcept {
|
||||
return fragment_has_color0_output;
|
||||
}
|
||||
|
||||
bool SupportsAlphaToOne() const noexcept {
|
||||
return fragment_has_color0_output;
|
||||
}
|
||||
|
||||
bool UsesExtendedDynamicState() const noexcept {
|
||||
return key.state.extended_dynamic_state != 0;
|
||||
}
|
||||
GraphicsPipeline& operator=(GraphicsPipeline&&) noexcept = delete;
|
||||
GraphicsPipeline(GraphicsPipeline&&) noexcept = delete;
|
||||
|
||||
@@ -149,6 +160,7 @@ private:
|
||||
std::array<u32, 5> enabled_uniform_buffer_masks{};
|
||||
VideoCommon::UniformBufferSizes uniform_buffer_sizes{};
|
||||
u32 num_textures{};
|
||||
bool fragment_has_color0_output{};
|
||||
|
||||
vk::DescriptorSetLayout descriptor_set_layout;
|
||||
DescriptorAllocator descriptor_allocator;
|
||||
|
||||
@@ -404,18 +404,35 @@ 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,
|
||||
};
|
||||
LOG_INFO(Render_Vulkan, "DynamicState setting value: {}", Settings::values.dyna_state.GetValue());
|
||||
|
||||
dynamic_features = {};
|
||||
|
||||
// User granularity enforced in vulkan_device.cpp switch statement:
|
||||
// Level 0: Core Dynamic States only
|
||||
// Level 1: Core + EDS1
|
||||
// Level 2: Core + EDS1 + EDS2 (accumulative)
|
||||
// Level 3: Core + EDS1 + EDS2 + EDS3 (accumulative)
|
||||
// Here we only verify if extensions were successfully loaded by the device
|
||||
|
||||
dynamic_features.has_extended_dynamic_state =
|
||||
device.IsExtExtendedDynamicStateSupported();
|
||||
|
||||
dynamic_features.has_extended_dynamic_state_2 =
|
||||
device.IsExtExtendedDynamicState2Supported();
|
||||
dynamic_features.has_extended_dynamic_state_2_logic_op =
|
||||
device.IsExtExtendedDynamicState2ExtrasSupported();
|
||||
dynamic_features.has_extended_dynamic_state_2_patch_control_points = false;
|
||||
|
||||
dynamic_features.has_extended_dynamic_state_3_blend =
|
||||
device.IsExtExtendedDynamicState3BlendingSupported();
|
||||
dynamic_features.has_extended_dynamic_state_3_enables =
|
||||
device.IsExtExtendedDynamicState3EnablesSupported();
|
||||
|
||||
// VIDS: Independent toggle (not affected by dyna_state levels)
|
||||
dynamic_features.has_dynamic_vertex_input =
|
||||
device.IsExtVertexInputDynamicStateSupported() &&
|
||||
Settings::values.vertex_input_dynamic_state.GetValue();
|
||||
}
|
||||
|
||||
PipelineCache::~PipelineCache() {
|
||||
@@ -520,8 +537,8 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading
|
||||
dynamic_features.has_extended_dynamic_state ||
|
||||
(key.state.extended_dynamic_state_2 != 0) !=
|
||||
dynamic_features.has_extended_dynamic_state_2 ||
|
||||
(key.state.extended_dynamic_state_2_extra != 0) !=
|
||||
dynamic_features.has_extended_dynamic_state_2_extra ||
|
||||
(key.state.extended_dynamic_state_2_logic_op != 0) !=
|
||||
dynamic_features.has_extended_dynamic_state_2_logic_op ||
|
||||
(key.state.extended_dynamic_state_3_blend != 0) !=
|
||||
dynamic_features.has_extended_dynamic_state_3_blend ||
|
||||
(key.state.extended_dynamic_state_3_enables != 0) !=
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -214,6 +202,11 @@ RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& emu_window_, Tegra
|
||||
fence_manager(*this, gpu, texture_cache, buffer_cache, query_cache, device, scheduler),
|
||||
wfi_event(device.GetLogical().CreateEvent()) {
|
||||
scheduler.SetQueryCache(query_cache);
|
||||
|
||||
// Log multi-draw support
|
||||
if (device.IsExtMultiDrawSupported()) {
|
||||
LOG_INFO(Render_Vulkan, "VK_EXT_multi_draw is enabled for optimized draw calls");
|
||||
}
|
||||
}
|
||||
|
||||
RasterizerVulkan::~RasterizerVulkan() = default;
|
||||
@@ -255,16 +248,44 @@ void RasterizerVulkan::Draw(bool is_indexed, u32 instance_count) {
|
||||
const u32 num_instances{instance_count};
|
||||
const auto polygon_mode = VideoCore::EffectivePolygonMode(maxwell3d->regs);
|
||||
const DrawParams draw_params{MakeDrawParams(draw_state, num_instances, is_indexed, polygon_mode)};
|
||||
scheduler.Record([draw_params](vk::CommandBuffer cmdbuf) {
|
||||
if (draw_params.is_indexed) {
|
||||
cmdbuf.DrawIndexed(draw_params.num_vertices, draw_params.num_instances,
|
||||
draw_params.first_index, draw_params.base_vertex,
|
||||
draw_params.base_instance);
|
||||
} else {
|
||||
cmdbuf.Draw(draw_params.num_vertices, draw_params.num_instances,
|
||||
draw_params.base_vertex, draw_params.base_instance);
|
||||
}
|
||||
});
|
||||
|
||||
// Use VK_EXT_multi_draw if available (single draw becomes multi-draw with count=1)
|
||||
if (device.IsExtMultiDrawSupported()) {
|
||||
scheduler.Record([draw_params](vk::CommandBuffer cmdbuf) {
|
||||
if (draw_params.is_indexed) {
|
||||
// Use multi-draw indexed with single draw
|
||||
const VkMultiDrawIndexedInfoEXT multi_draw_info{
|
||||
.firstIndex = draw_params.first_index,
|
||||
.indexCount = draw_params.num_vertices,
|
||||
};
|
||||
const int32_t vertex_offset = static_cast<int32_t>(draw_params.base_vertex);
|
||||
cmdbuf.DrawMultiIndexedEXT(1, &multi_draw_info, draw_params.num_instances,
|
||||
draw_params.base_instance,
|
||||
sizeof(VkMultiDrawIndexedInfoEXT), &vertex_offset);
|
||||
} else {
|
||||
// Use multi-draw with single draw
|
||||
const VkMultiDrawInfoEXT multi_draw_info{
|
||||
.firstVertex = draw_params.base_vertex,
|
||||
.vertexCount = draw_params.num_vertices,
|
||||
};
|
||||
cmdbuf.DrawMultiEXT(1, &multi_draw_info, draw_params.num_instances,
|
||||
draw_params.base_instance,
|
||||
sizeof(VkMultiDrawInfoEXT));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Fallback to standard draw calls
|
||||
scheduler.Record([draw_params](vk::CommandBuffer cmdbuf) {
|
||||
if (draw_params.is_indexed) {
|
||||
cmdbuf.DrawIndexed(draw_params.num_vertices, draw_params.num_instances,
|
||||
draw_params.first_index, draw_params.base_vertex,
|
||||
draw_params.base_instance);
|
||||
} else {
|
||||
cmdbuf.Draw(draw_params.num_vertices, draw_params.num_instances,
|
||||
draw_params.base_vertex, draw_params.base_instance);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -407,13 +428,48 @@ void RasterizerVulkan::Clear(u32 layer_count) {
|
||||
.baseArrayLayer = regs.clear_surface.layer,
|
||||
.layerCount = layer_count,
|
||||
};
|
||||
if (clear_rect.rect.extent.width == 0 || clear_rect.rect.extent.height == 0) {
|
||||
const auto clamp_rect_to_render_area = [render_area](VkRect2D& rect) -> bool {
|
||||
const auto clamp_axis = [](s32& offset, u32& extent, u32 limit) {
|
||||
auto clamp_offset = [&offset, limit]() {
|
||||
if (limit == 0) {
|
||||
offset = 0;
|
||||
return;
|
||||
}
|
||||
offset = std::clamp(offset, 0, static_cast<s32>(limit));
|
||||
};
|
||||
|
||||
if (extent == 0) {
|
||||
clamp_offset();
|
||||
return;
|
||||
}
|
||||
if (offset < 0) {
|
||||
const u32 shrink = (std::min)(extent, static_cast<u32>(-offset));
|
||||
extent -= shrink;
|
||||
offset = 0;
|
||||
}
|
||||
if (limit == 0) {
|
||||
extent = 0;
|
||||
offset = 0;
|
||||
return;
|
||||
}
|
||||
if (offset >= static_cast<s32>(limit)) {
|
||||
offset = static_cast<s32>(limit);
|
||||
extent = 0;
|
||||
return;
|
||||
}
|
||||
const u64 end_coord = static_cast<u64>(offset) + extent;
|
||||
if (end_coord > limit) {
|
||||
extent = limit - static_cast<u32>(offset);
|
||||
}
|
||||
};
|
||||
|
||||
clamp_axis(rect.offset.x, rect.extent.width, render_area.width);
|
||||
clamp_axis(rect.offset.y, rect.extent.height, render_area.height);
|
||||
return rect.extent.width != 0 && rect.extent.height != 0;
|
||||
};
|
||||
if (!clamp_rect_to_render_area(clear_rect.rect)) {
|
||||
return;
|
||||
}
|
||||
clear_rect.rect.extent = VkExtent2D{
|
||||
.width = (std::min)(clear_rect.rect.extent.width, render_area.width),
|
||||
.height = (std::min)(clear_rect.rect.extent.height, render_area.height),
|
||||
};
|
||||
|
||||
const u32 color_attachment = regs.clear_surface.RT;
|
||||
if (use_color && framebuffer->HasAspectColorBit(color_attachment)) {
|
||||
@@ -863,23 +919,21 @@ void RasterizerVulkan::LoadDiskResources(u64 title_id, std::stop_token stop_load
|
||||
|
||||
void RasterizerVulkan::FlushWork() {
|
||||
#ifdef ANDROID
|
||||
static constexpr u32 DRAWS_TO_DISPATCH = 1024;
|
||||
static constexpr u32 DRAWS_TO_DISPATCH = 512;
|
||||
static constexpr u32 CHECK_MASK = 3;
|
||||
#else
|
||||
static constexpr u32 DRAWS_TO_DISPATCH = 4096;
|
||||
static constexpr u32 CHECK_MASK = 7;
|
||||
#endif // ANDROID
|
||||
|
||||
// Only check multiples of 8 draws
|
||||
static_assert(DRAWS_TO_DISPATCH % 8 == 0);
|
||||
if ((++draw_counter & 7) != 7) {
|
||||
static_assert(DRAWS_TO_DISPATCH % (CHECK_MASK + 1) == 0);
|
||||
if ((++draw_counter & CHECK_MASK) != CHECK_MASK) {
|
||||
return;
|
||||
}
|
||||
if (draw_counter < DRAWS_TO_DISPATCH) {
|
||||
// Send recorded tasks to the worker thread
|
||||
scheduler.DispatchWork();
|
||||
return;
|
||||
}
|
||||
// Otherwise (every certain number of draws) flush execution.
|
||||
// This submits commands to the Vulkan driver.
|
||||
scheduler.Flush();
|
||||
draw_counter = 0;
|
||||
}
|
||||
@@ -945,7 +999,8 @@ bool AccelerateDMA::BufferToImage(const Tegra::DMA::ImageCopy& copy_info,
|
||||
|
||||
void RasterizerVulkan::UpdateDynamicStates() {
|
||||
auto& regs = maxwell3d->regs;
|
||||
|
||||
|
||||
// Core Dynamic States (Vulkan 1.0) - Always active regardless of dyna_state setting
|
||||
UpdateViewportsState(regs);
|
||||
UpdateScissorsState(regs);
|
||||
UpdateDepthBias(regs);
|
||||
@@ -953,10 +1008,9 @@ void RasterizerVulkan::UpdateDynamicStates() {
|
||||
UpdateDepthBounds(regs);
|
||||
UpdateStencilFaces(regs);
|
||||
UpdateLineWidth(regs);
|
||||
|
||||
const u8 dynamic_state = Settings::values.dyna_state.GetValue();
|
||||
|
||||
if (device.IsExtExtendedDynamicStateSupported() && dynamic_state > 0) {
|
||||
|
||||
// EDS1: CullMode, DepthCompare, FrontFace, StencilOp, DepthBoundsTest, DepthTest, DepthWrite, StencilTest
|
||||
if (device.IsExtExtendedDynamicStateSupported()) {
|
||||
UpdateCullMode(regs);
|
||||
UpdateDepthCompareOp(regs);
|
||||
UpdateFrontFace(regs);
|
||||
@@ -966,42 +1020,57 @@ void RasterizerVulkan::UpdateDynamicStates() {
|
||||
UpdateDepthTestEnable(regs);
|
||||
UpdateDepthWriteEnable(regs);
|
||||
UpdateStencilTestEnable(regs);
|
||||
if (device.IsExtExtendedDynamicState2Supported() && dynamic_state > 1) {
|
||||
UpdatePrimitiveRestartEnable(regs);
|
||||
UpdateRasterizerDiscardEnable(regs);
|
||||
UpdateDepthBiasEnable(regs);
|
||||
}
|
||||
if (device.IsExtExtendedDynamicState3EnablesSupported() && dynamic_state > 2) {
|
||||
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;
|
||||
}
|
||||
};
|
||||
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) {
|
||||
UpdateLogicOp(regs);
|
||||
}
|
||||
if (device.IsExtExtendedDynamicState3BlendingSupported() && dynamic_state > 2) {
|
||||
UpdateBlending(regs);
|
||||
}
|
||||
}
|
||||
if (device.IsExtVertexInputDynamicStateSupported() && dynamic_state > 0)
|
||||
if (auto* gp = pipeline_cache.CurrentGraphicsPipeline(); gp && gp->HasDynamicVertexInput())
|
||||
|
||||
// EDS2: PrimitiveRestart, RasterizerDiscard, DepthBias enable/disable
|
||||
if (device.IsExtExtendedDynamicState2Supported()) {
|
||||
UpdatePrimitiveRestartEnable(regs);
|
||||
UpdateRasterizerDiscardEnable(regs);
|
||||
UpdateDepthBiasEnable(regs);
|
||||
}
|
||||
|
||||
// EDS2 Extras: LogicOp operation selection
|
||||
if (device.IsExtExtendedDynamicState2ExtrasSupported()) {
|
||||
UpdateLogicOp(regs);
|
||||
}
|
||||
|
||||
// EDS3 Enables: LogicOpEnable, DepthClamp, LineStipple, ConservativeRaster
|
||||
if (device.IsExtExtendedDynamicState3EnablesSupported()) {
|
||||
using namespace Tegra::Engines;
|
||||
// AMD Workaround: LogicOp incompatible with float render targets
|
||||
if (device.GetDriverID() == VkDriverIdKHR::VK_DRIVER_ID_AMD_OPEN_SOURCE ||
|
||||
device.GetDriverID() == VkDriverIdKHR::VK_DRIVER_ID_AMD_PROPRIETARY) {
|
||||
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;
|
||||
}
|
||||
);
|
||||
if (regs.logic_op.enable) {
|
||||
regs.logic_op.enable = static_cast<u32>(!has_float);
|
||||
}
|
||||
}
|
||||
UpdateLogicOpEnable(regs);
|
||||
UpdateDepthClampEnable(regs);
|
||||
UpdateLineRasterizationMode(regs);
|
||||
UpdateLineStippleEnable(regs);
|
||||
UpdateConservativeRasterizationMode(regs);
|
||||
UpdateAlphaToCoverageEnable(regs);
|
||||
UpdateAlphaToOneEnable(regs);
|
||||
}
|
||||
|
||||
// EDS3 Blending: ColorBlendEnable, ColorBlendEquation, ColorWriteMask
|
||||
if (device.IsExtExtendedDynamicState3BlendingSupported()) {
|
||||
UpdateBlending(regs);
|
||||
}
|
||||
|
||||
// Vertex Input Dynamic State: Independent from EDS levels
|
||||
if (device.IsExtVertexInputDynamicStateSupported()) {
|
||||
if (auto* gp = pipeline_cache.CurrentGraphicsPipeline(); gp && gp->HasDynamicVertexInput()) {
|
||||
UpdateVertexInput(regs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerVulkan::HandleTransformFeedback() {
|
||||
@@ -1027,10 +1096,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;
|
||||
@@ -1169,6 +1238,9 @@ void RasterizerVulkan::UpdateBlendConstants(Tegra::Engines::Maxwell3D::Regs& reg
|
||||
if (!state_tracker.TouchBlendConstants()) {
|
||||
return;
|
||||
}
|
||||
if (!device.UsesAdvancedCoreDynamicState()) {
|
||||
return;
|
||||
}
|
||||
const std::array blend_color = {regs.blend_color.r, regs.blend_color.g, regs.blend_color.b,
|
||||
regs.blend_color.a};
|
||||
scheduler.Record(
|
||||
@@ -1179,6 +1251,9 @@ void RasterizerVulkan::UpdateDepthBounds(Tegra::Engines::Maxwell3D::Regs& regs)
|
||||
if (!state_tracker.TouchDepthBounds()) {
|
||||
return;
|
||||
}
|
||||
if (!device.UsesAdvancedCoreDynamicState() || !device.IsDepthBoundsSupported()) {
|
||||
return;
|
||||
}
|
||||
scheduler.Record([min = regs.depth_bounds[0], max = regs.depth_bounds[1]](
|
||||
vk::CommandBuffer cmdbuf) { cmdbuf.SetDepthBounds(min, max); });
|
||||
}
|
||||
@@ -1187,6 +1262,10 @@ void RasterizerVulkan::UpdateStencilFaces(Tegra::Engines::Maxwell3D::Regs& regs)
|
||||
if (!state_tracker.TouchStencilProperties()) {
|
||||
return;
|
||||
}
|
||||
if (!device.UsesAdvancedCoreDynamicState()) {
|
||||
state_tracker.ClearStencilReset();
|
||||
return;
|
||||
}
|
||||
bool update_references = state_tracker.TouchStencilReference();
|
||||
bool update_write_mask = state_tracker.TouchStencilWriteMask();
|
||||
bool update_compare_masks = state_tracker.TouchStencilCompare();
|
||||
@@ -1349,6 +1428,10 @@ void RasterizerVulkan::UpdateConservativeRasterizationMode(Tegra::Engines::Maxwe
|
||||
return;
|
||||
}
|
||||
|
||||
if (!device.SupportsDynamicState3ConservativeRasterizationMode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
scheduler.Record([enable = regs.conservative_raster_enable](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.SetConservativeRasterizationModeEXT(
|
||||
enable ? VK_CONSERVATIVE_RASTERIZATION_MODE_OVERESTIMATE_EXT
|
||||
@@ -1361,23 +1444,50 @@ void RasterizerVulkan::UpdateLineStippleEnable(Tegra::Engines::Maxwell3D::Regs&
|
||||
return;
|
||||
}
|
||||
|
||||
if (!device.SupportsDynamicState3LineStippleEnable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
scheduler.Record([enable = regs.line_stipple_enable](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.SetLineStippleEnableEXT(enable);
|
||||
});
|
||||
}
|
||||
|
||||
void RasterizerVulkan::UpdateLineRasterizationMode(Tegra::Engines::Maxwell3D::Regs& regs) {
|
||||
// if (!state_tracker.TouchLi()) {
|
||||
// return;
|
||||
// }
|
||||
if (!device.IsExtLineRasterizationSupported()) {
|
||||
return;
|
||||
}
|
||||
if (!state_tracker.TouchLineRasterizationMode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: The maxwell emulator does not capture line rasters
|
||||
if (!device.SupportsDynamicState3LineRasterizationMode()) {
|
||||
static std::once_flag warn_missing_rect;
|
||||
std::call_once(warn_missing_rect, [] {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"Driver lacks rectangular line rasterization support; skipping dynamic "
|
||||
"line state updates");
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// scheduler.Record([enable = regs.line](vk::CommandBuffer cmdbuf) {
|
||||
// cmdbuf.SetConservativeRasterizationModeEXT(
|
||||
// enable ? VK_CONSERVATIVE_RASTERIZATION_MODE_UNDERESTIMATE_EXT
|
||||
// : VK_CONSERVATIVE_RASTERIZATION_MODE_DISABLED_EXT);
|
||||
// });
|
||||
const bool wants_smooth = regs.line_anti_alias_enable != 0;
|
||||
VkLineRasterizationModeEXT mode = VK_LINE_RASTERIZATION_MODE_RECTANGULAR_EXT;
|
||||
if (wants_smooth) {
|
||||
if (device.SupportsSmoothLines()) {
|
||||
mode = VK_LINE_RASTERIZATION_MODE_RECTANGULAR_SMOOTH_EXT;
|
||||
} else {
|
||||
static std::once_flag warn_missing_smooth;
|
||||
std::call_once(warn_missing_smooth, [] {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"Line anti-aliasing requested but smoothLines feature unavailable; "
|
||||
"using rectangular rasterization");
|
||||
});
|
||||
}
|
||||
}
|
||||
scheduler.Record([mode](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.SetLineRasterizationModeEXT(mode);
|
||||
});
|
||||
}
|
||||
|
||||
void RasterizerVulkan::UpdateDepthBiasEnable(Tegra::Engines::Maxwell3D::Regs& regs) {
|
||||
@@ -1419,6 +1529,9 @@ void RasterizerVulkan::UpdateLogicOpEnable(Tegra::Engines::Maxwell3D::Regs& regs
|
||||
if (!state_tracker.TouchLogicOpEnable()) {
|
||||
return;
|
||||
}
|
||||
if (!device.SupportsDynamicState3LogicOpEnable()) {
|
||||
return;
|
||||
}
|
||||
scheduler.Record([enable = regs.logic_op.enable](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.SetLogicOpEnableEXT(enable != 0);
|
||||
});
|
||||
@@ -1428,6 +1541,9 @@ void RasterizerVulkan::UpdateDepthClampEnable(Tegra::Engines::Maxwell3D::Regs& r
|
||||
if (!state_tracker.TouchDepthClampEnable()) {
|
||||
return;
|
||||
}
|
||||
if (!device.SupportsDynamicState3DepthClampEnable()) {
|
||||
return;
|
||||
}
|
||||
bool is_enabled = !(regs.viewport_clip_control.geometry_clip ==
|
||||
Maxwell::ViewportClipControl::GeometryClip::Passthrough ||
|
||||
regs.viewport_clip_control.geometry_clip ==
|
||||
@@ -1438,6 +1554,41 @@ void RasterizerVulkan::UpdateDepthClampEnable(Tegra::Engines::Maxwell3D::Regs& r
|
||||
[is_enabled](vk::CommandBuffer cmdbuf) { cmdbuf.SetDepthClampEnableEXT(is_enabled); });
|
||||
}
|
||||
|
||||
void RasterizerVulkan::UpdateAlphaToCoverageEnable(Tegra::Engines::Maxwell3D::Regs& regs) {
|
||||
if (!state_tracker.TouchAlphaToCoverageEnable()) {
|
||||
return;
|
||||
}
|
||||
if (!device.SupportsDynamicState3AlphaToCoverageEnable()) {
|
||||
return;
|
||||
}
|
||||
GraphicsPipeline* const pipeline = pipeline_cache.CurrentGraphicsPipeline();
|
||||
const bool enable = pipeline != nullptr && pipeline->SupportsAlphaToCoverage() &&
|
||||
regs.anti_alias_alpha_control.alpha_to_coverage != 0;
|
||||
scheduler.Record([enable](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.SetAlphaToCoverageEnableEXT(enable ? VK_TRUE : VK_FALSE);
|
||||
});
|
||||
}
|
||||
|
||||
void RasterizerVulkan::UpdateAlphaToOneEnable(Tegra::Engines::Maxwell3D::Regs& regs) {
|
||||
if (!state_tracker.TouchAlphaToOneEnable()) {
|
||||
return;
|
||||
}
|
||||
if (!device.SupportsDynamicState3AlphaToOneEnable()) {
|
||||
static std::once_flag warn_alpha_to_one;
|
||||
std::call_once(warn_alpha_to_one, [] {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"Alpha-to-one is not supported on this device; forcing it disabled");
|
||||
});
|
||||
return;
|
||||
}
|
||||
GraphicsPipeline* const pipeline = pipeline_cache.CurrentGraphicsPipeline();
|
||||
const bool enable = pipeline != nullptr && pipeline->SupportsAlphaToOne() &&
|
||||
regs.anti_alias_alpha_control.alpha_to_one != 0;
|
||||
scheduler.Record([enable](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.SetAlphaToOneEnableEXT(enable ? VK_TRUE : VK_FALSE);
|
||||
});
|
||||
}
|
||||
|
||||
void RasterizerVulkan::UpdateDepthCompareOp(Tegra::Engines::Maxwell3D::Regs& regs) {
|
||||
if (!state_tracker.TouchDepthCompareOp()) {
|
||||
return;
|
||||
|
||||
@@ -183,6 +183,8 @@ private:
|
||||
void UpdateDepthBiasEnable(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||
void UpdateLogicOpEnable(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||
void UpdateDepthClampEnable(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||
void UpdateAlphaToCoverageEnable(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||
void UpdateAlphaToOneEnable(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||
void UpdateFrontFace(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||
void UpdateStencilOp(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||
void UpdateStencilTestEnable(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
#include "common/thread.h"
|
||||
#include "video_core/renderer_vulkan/vk_command_pool.h"
|
||||
#include "video_core/renderer_vulkan/vk_graphics_pipeline.h"
|
||||
#include "video_core/renderer_vulkan/vk_master_semaphore.h"
|
||||
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
||||
#include "video_core/renderer_vulkan/vk_state_tracker.h"
|
||||
@@ -130,9 +131,27 @@ void Scheduler::RequestOutsideRenderPassOperationContext() {
|
||||
|
||||
bool Scheduler::UpdateGraphicsPipeline(GraphicsPipeline* pipeline) {
|
||||
if (state.graphics_pipeline == pipeline) {
|
||||
if (pipeline && pipeline->UsesExtendedDynamicState() &&
|
||||
state.needs_state_enable_refresh) {
|
||||
state_tracker.InvalidateStateEnableFlag();
|
||||
state.needs_state_enable_refresh = false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
state.graphics_pipeline = pipeline;
|
||||
|
||||
if (!pipeline) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!pipeline->UsesExtendedDynamicState()) {
|
||||
state.needs_state_enable_refresh = true;
|
||||
} else if (state.needs_state_enable_refresh) {
|
||||
state_tracker.InvalidateStateEnableFlag();
|
||||
state.needs_state_enable_refresh = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -277,6 +296,7 @@ void Scheduler::EndRenderPass()
|
||||
}
|
||||
|
||||
query_cache->CounterEnable(VideoCommon::QueryType::ZPassPixelCount64, false);
|
||||
query_cache->CounterEnable(VideoCommon::QueryType::StreamingByteCount, false);
|
||||
query_cache->NotifySegment(false);
|
||||
|
||||
Record([num_images = num_renderpass_images,
|
||||
@@ -287,10 +307,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 +346,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
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -214,6 +217,7 @@ private:
|
||||
GraphicsPipeline* graphics_pipeline = nullptr;
|
||||
bool is_rescaling = false;
|
||||
bool rescaling_defined = false;
|
||||
bool needs_state_enable_refresh = false;
|
||||
};
|
||||
|
||||
void WorkerThread(std::stop_token stop_token);
|
||||
|
||||
@@ -48,6 +48,7 @@ Flags MakeInvalidationFlags() {
|
||||
FrontFace,
|
||||
StencilOp,
|
||||
StencilTestEnable,
|
||||
RasterizerDiscardEnable,
|
||||
VertexBuffers,
|
||||
VertexInput,
|
||||
StateEnable,
|
||||
@@ -55,6 +56,9 @@ Flags MakeInvalidationFlags() {
|
||||
DepthBiasEnable,
|
||||
LogicOpEnable,
|
||||
DepthClampEnable,
|
||||
AlphaToCoverageEnable,
|
||||
AlphaToOneEnable,
|
||||
LineRasterizationMode,
|
||||
LogicOp,
|
||||
Blending,
|
||||
ColorMask,
|
||||
@@ -148,6 +152,8 @@ void SetupDirtyStateEnable(Tables& tables) {
|
||||
setup(OFF(logic_op.enable), LogicOpEnable);
|
||||
setup(OFF(viewport_clip_control.geometry_clip), DepthClampEnable);
|
||||
setup(OFF(line_stipple_enable), LineStippleEnable);
|
||||
setup(OFF(anti_alias_alpha_control.alpha_to_coverage), AlphaToCoverageEnable);
|
||||
setup(OFF(anti_alias_alpha_control.alpha_to_one), AlphaToOneEnable);
|
||||
}
|
||||
|
||||
void SetupDirtyDepthCompareOp(Tables& tables) {
|
||||
@@ -226,6 +232,7 @@ void SetupRasterModes(Tables &tables) {
|
||||
|
||||
table[OFF(line_stipple_params)] = LineStippleParams;
|
||||
table[OFF(conservative_raster_enable)] = ConservativeRasterizationMode;
|
||||
table[OFF(line_anti_alias_enable)] = LineRasterizationMode;
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ enum : u8 {
|
||||
PrimitiveRestartEnable,
|
||||
RasterizerDiscardEnable,
|
||||
ConservativeRasterizationMode,
|
||||
LineRasterizationMode,
|
||||
LineStippleEnable,
|
||||
LineStippleParams,
|
||||
DepthBiasEnable,
|
||||
@@ -61,6 +62,8 @@ enum : u8 {
|
||||
LogicOp,
|
||||
LogicOpEnable,
|
||||
DepthClampEnable,
|
||||
AlphaToCoverageEnable,
|
||||
AlphaToOneEnable,
|
||||
|
||||
Blending,
|
||||
BlendEnable,
|
||||
@@ -94,6 +97,10 @@ public:
|
||||
(*flags)[Dirty::Scissors] = true;
|
||||
}
|
||||
|
||||
void InvalidateStateEnableFlag() {
|
||||
(*flags)[Dirty::StateEnable] = true;
|
||||
}
|
||||
|
||||
bool TouchViewports() {
|
||||
const bool dirty_viewports = Exchange(Dirty::Viewports, false);
|
||||
const bool rescale_viewports = Exchange(VideoCommon::Dirty::RescaleViewports, false);
|
||||
@@ -225,6 +232,14 @@ public:
|
||||
return Exchange(Dirty::DepthClampEnable, false);
|
||||
}
|
||||
|
||||
bool TouchAlphaToCoverageEnable() {
|
||||
return Exchange(Dirty::AlphaToCoverageEnable, false);
|
||||
}
|
||||
|
||||
bool TouchAlphaToOneEnable() {
|
||||
return Exchange(Dirty::AlphaToOneEnable, false);
|
||||
}
|
||||
|
||||
bool TouchDepthCompareOp() {
|
||||
return Exchange(Dirty::DepthCompareOp, false);
|
||||
}
|
||||
@@ -261,6 +276,10 @@ public:
|
||||
return Exchange(Dirty::LogicOp, false);
|
||||
}
|
||||
|
||||
bool TouchLineRasterizationMode() {
|
||||
return Exchange(Dirty::LineRasterizationMode, false);
|
||||
}
|
||||
|
||||
bool ChangePrimitiveTopology(Maxwell::PrimitiveTopology new_topology) {
|
||||
const bool has_changed = current_topology != new_topology;
|
||||
current_topology = new_topology;
|
||||
|
||||
@@ -306,7 +306,17 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities) {
|
||||
swapchain_ci.queueFamilyIndexCount = static_cast<u32>(queue_indices.size());
|
||||
swapchain_ci.pQueueFamilyIndices = queue_indices.data();
|
||||
}
|
||||
static constexpr std::array view_formats{VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_B8G8R8A8_SRGB};
|
||||
// According to Vulkan spec, when using VK_SWAPCHAIN_CREATE_MUTABLE_FORMAT_BIT_KHR,
|
||||
// the base format (imageFormat) MUST be included in pViewFormats
|
||||
const std::array view_formats{
|
||||
swapchain_ci.imageFormat, // Base format MUST be first
|
||||
VK_FORMAT_B8G8R8A8_UNORM,
|
||||
VK_FORMAT_B8G8R8A8_SRGB,
|
||||
#ifdef ANDROID
|
||||
VK_FORMAT_R8G8B8A8_UNORM, // Android may use RGBA
|
||||
VK_FORMAT_R8G8B8A8_SRGB,
|
||||
#endif
|
||||
};
|
||||
VkImageFormatListCreateInfo format_list{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO_KHR,
|
||||
.pNext = nullptr,
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -101,7 +104,7 @@ public:
|
||||
}
|
||||
|
||||
VkSemaphore CurrentRenderSemaphore() const {
|
||||
return *render_semaphores[frame_index];
|
||||
return *render_semaphores[image_index];
|
||||
}
|
||||
|
||||
u32 GetWidth() const {
|
||||
|
||||
@@ -1104,6 +1104,8 @@ void TextureCacheRuntime::BlitImage(Framebuffer* dst_framebuffer, ImageView& dst
|
||||
UNREACHABLE();
|
||||
}
|
||||
}();
|
||||
// Use shader-based depth/stencil blits if hardware doesn't support the format
|
||||
// Note: MSAA resolves (MSAA->single) use vkCmdResolveImage which works fine
|
||||
if (!can_blit_depth_stencil) {
|
||||
UNIMPLEMENTED_IF(is_src_msaa || is_dst_msaa);
|
||||
blit_image_helper.BlitDepthStencil(dst_framebuffer, src, dst_region, src_region,
|
||||
@@ -1118,6 +1120,15 @@ void TextureCacheRuntime::BlitImage(Framebuffer* dst_framebuffer, ImageView& dst
|
||||
const VkImage src_image = src.ImageHandle();
|
||||
const VkImageSubresourceLayers dst_layers = MakeSubresourceLayers(&dst);
|
||||
const VkImageSubresourceLayers src_layers = MakeSubresourceLayers(&src);
|
||||
const bool is_msaa_to_msaa = is_src_msaa && is_dst_msaa;
|
||||
|
||||
// NVIDIA 510+ and Intel crash on MSAA->MSAA blits (scaling operations)
|
||||
// Fall back to 3D helpers for MSAA scaling
|
||||
if (is_msaa_to_msaa && device.CantBlitMSAA()) {
|
||||
// This should be handled by NeedsScaleHelper() and use 3D helpers instead
|
||||
UNIMPLEMENTED_MSG("MSAA to MSAA blit not supported on this driver");
|
||||
return;
|
||||
}
|
||||
const bool is_resolve = is_src_msaa && !is_dst_msaa;
|
||||
scheduler.RequestOutsideRenderPassOperationContext();
|
||||
scheduler.Record([filter, dst_region, src_region, dst_image, src_image, dst_layers, src_layers,
|
||||
@@ -2211,18 +2222,26 @@ vk::ImageView ImageView::MakeView(VkFormat vk_format, VkImageAspectFlags aspect_
|
||||
|
||||
Sampler::Sampler(TextureCacheRuntime& runtime, const Tegra::Texture::TSCEntry& tsc) {
|
||||
const auto& device = runtime.device;
|
||||
const bool arbitrary_borders = runtime.device.IsExtCustomBorderColorSupported();
|
||||
// Check if custom border colors are supported
|
||||
const bool has_custom_border_colors = runtime.device.IsCustomBorderColorsSupported();
|
||||
const bool has_format_undefined = runtime.device.IsCustomBorderColorWithoutFormatSupported();
|
||||
const auto color = tsc.BorderColor();
|
||||
|
||||
// Determine border format based on available features:
|
||||
// - If customBorderColorWithoutFormat is available: use VK_FORMAT_UNDEFINED (most flexible)
|
||||
// - If only customBorderColors is available: use concrete format (R8G8B8A8_UNORM)
|
||||
// - If neither is available: use standard border colors (handled by ConvertBorderColor)
|
||||
const VkFormat border_format = has_format_undefined ? VK_FORMAT_UNDEFINED
|
||||
: VK_FORMAT_R8G8B8A8_UNORM;
|
||||
|
||||
const VkSamplerCustomBorderColorCreateInfoEXT border_ci{
|
||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_CUSTOM_BORDER_COLOR_CREATE_INFO_EXT,
|
||||
.pNext = nullptr,
|
||||
// TODO: Make use of std::bit_cast once libc++ supports it.
|
||||
.customBorderColor = std::bit_cast<VkClearColorValue>(color),
|
||||
.format = VK_FORMAT_UNDEFINED,
|
||||
.format = border_format,
|
||||
};
|
||||
const void* pnext = nullptr;
|
||||
if (arbitrary_borders) {
|
||||
if (has_custom_border_colors) {
|
||||
pnext = &border_ci;
|
||||
}
|
||||
const VkSamplerReductionModeCreateInfoEXT reduction_ci{
|
||||
@@ -2256,8 +2275,8 @@ Sampler::Sampler(TextureCacheRuntime& runtime, const Tegra::Texture::TSCEntry& t
|
||||
.compareOp = MaxwellToVK::Sampler::DepthCompareFunction(tsc.depth_compare_func),
|
||||
.minLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.0f : tsc.MinLod(),
|
||||
.maxLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.25f : tsc.MaxLod(),
|
||||
.borderColor =
|
||||
arbitrary_borders ? VK_BORDER_COLOR_FLOAT_CUSTOM_EXT : ConvertBorderColor(color),
|
||||
.borderColor = has_custom_border_colors ? VK_BORDER_COLOR_FLOAT_CUSTOM_EXT
|
||||
: ConvertBorderColor(color),
|
||||
.unnormalizedCoordinates = VK_FALSE,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
@@ -241,7 +244,6 @@ public:
|
||||
[[nodiscard]] VkImageView Handle(Shader::TextureType texture_type) const noexcept {
|
||||
return *image_views[static_cast<size_t>(texture_type)];
|
||||
}
|
||||
|
||||
[[nodiscard]] VkImage ImageHandle() const noexcept {
|
||||
return image_handle;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ ImageInfo::ImageInfo(const TICEntry& config) noexcept {
|
||||
dma_downloaded = forced_flushed;
|
||||
format = PixelFormatFromTextureInfo(config.format, config.r_type, config.g_type, config.b_type,
|
||||
config.a_type, config.srgb_conversion);
|
||||
|
||||
num_samples = NumSamples(config.msaa_mode);
|
||||
resources.levels = config.max_mip_level + 1;
|
||||
if (config.IsPitchLinear()) {
|
||||
|
||||
@@ -22,6 +22,17 @@
|
||||
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
// Define maintenance 7-9 extension names (not yet in official Vulkan headers)
|
||||
#ifndef VK_KHR_MAINTENANCE_7_EXTENSION_NAME
|
||||
#define VK_KHR_MAINTENANCE_7_EXTENSION_NAME "VK_KHR_maintenance7"
|
||||
#endif
|
||||
#ifndef VK_KHR_MAINTENANCE_8_EXTENSION_NAME
|
||||
#define VK_KHR_MAINTENANCE_8_EXTENSION_NAME "VK_KHR_maintenance8"
|
||||
#endif
|
||||
#ifndef VK_KHR_MAINTENANCE_9_EXTENSION_NAME
|
||||
#define VK_KHR_MAINTENANCE_9_EXTENSION_NAME "VK_KHR_maintenance9"
|
||||
#endif
|
||||
|
||||
// Sanitize macros
|
||||
#undef CreateEvent
|
||||
#undef CreateSemaphore
|
||||
|
||||
@@ -292,9 +292,10 @@ std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties(vk::Physica
|
||||
#if defined(ANDROID) && defined(ARCHITECTURE_arm64)
|
||||
void OverrideBcnFormats(std::unordered_map<VkFormat, VkFormatProperties>& format_properties) {
|
||||
// These properties are extracted from Adreno driver 512.687.0
|
||||
constexpr VkFormatFeatureFlags tiling_features{
|
||||
VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT |
|
||||
VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT |
|
||||
constexpr VkFormatFeatureFlags tiling_features{VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT |
|
||||
VK_FORMAT_FEATURE_BLIT_SRC_BIT |
|
||||
VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT |
|
||||
VK_FORMAT_FEATURE_TRANSFER_SRC_BIT |
|
||||
VK_FORMAT_FEATURE_TRANSFER_DST_BIT};
|
||||
|
||||
constexpr VkFormatFeatureFlags buffer_features{VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT};
|
||||
@@ -416,7 +417,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
||||
const bool is_suitable = GetSuitability(surface != nullptr);
|
||||
|
||||
const VkDriverId driver_id = properties.driver.driverID;
|
||||
const auto device_id = properties.properties.deviceID;
|
||||
const bool is_radv = driver_id == VK_DRIVER_ID_MESA_RADV;
|
||||
const bool is_amd_driver =
|
||||
driver_id == VK_DRIVER_ID_AMD_PROPRIETARY || driver_id == VK_DRIVER_ID_AMD_OPEN_SOURCE;
|
||||
@@ -427,7 +427,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
||||
const bool is_mvk = driver_id == VK_DRIVER_ID_MOLTENVK;
|
||||
const bool is_qualcomm = driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY;
|
||||
const bool is_turnip = driver_id == VK_DRIVER_ID_MESA_TURNIP;
|
||||
const bool is_s8gen2 = device_id == 0x43050a01;
|
||||
const bool is_arm = driver_id == VK_DRIVER_ID_ARM_PROPRIETARY;
|
||||
|
||||
if ((is_mvk || is_qualcomm || is_turnip || is_arm) && !is_suitable) {
|
||||
@@ -494,17 +493,11 @@ 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) {
|
||||
// Qualcomm Adreno GPUs doesn't handle scaled vertex attributes; keep emulation enabled
|
||||
must_emulate_scaled_formats = true;
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"Qualcomm drivers have a slow VK_KHR_push_descriptor implementation");
|
||||
//RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
|
||||
"Qualcomm drivers require scaled vertex format emulation; forcing fallback");
|
||||
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"Disabling shader float controls and 64-bit integer features on Qualcomm proprietary drivers");
|
||||
@@ -544,94 +537,33 @@ 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");
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"NVIDIA Drivers >= 510 do not support MSAA->MSAA image blits. "
|
||||
"MSAA scaling will use 3D helpers. MSAA resolves work normally.");
|
||||
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;
|
||||
|
||||
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;
|
||||
// Mali/ NVIDIA proprietary drivers: Shader stencil export not supported
|
||||
// Use hardware depth/stencil blits instead when available
|
||||
if (!extensions.shader_stencil_export) {
|
||||
LOG_INFO(Render_Vulkan,
|
||||
"NVIDIA: VK_EXT_shader_stencil_export not supported, using hardware blits "
|
||||
"for depth/stencil operations");
|
||||
LOG_INFO(Render_Vulkan, " D24S8 hardware blit support: {}",
|
||||
is_blit_depth24_stencil8_supported);
|
||||
LOG_INFO(Render_Vulkan, " D32S8 hardware blit support: {}",
|
||||
is_blit_depth32_stencil8_supported);
|
||||
|
||||
if (!is_blit_depth24_stencil8_supported && !is_blit_depth32_stencil8_supported) {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"NVIDIA: Neither shader export nor hardware blits available for "
|
||||
"depth/stencil. Performance may be degraded.");
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
sets_per_pool = 64;
|
||||
if (is_amd_driver) {
|
||||
@@ -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,48 +594,35 @@ 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");
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"Intel proprietary drivers do not support MSAA->MSAA image blits. "
|
||||
"MSAA scaling will use 3D helpers. MSAA resolves work normally.");
|
||||
cant_blit_msaa = true;
|
||||
}
|
||||
|
||||
has_broken_compute =
|
||||
CheckBrokenCompute(properties.driver.driverID, properties.properties.driverVersion) &&
|
||||
!Settings::values.enable_compute_pipelines.GetValue();
|
||||
if (is_intel_anv || (is_qualcomm && !is_s8gen2)) {
|
||||
LOG_WARNING(Render_Vulkan, "Driver does not support native BGR format");
|
||||
must_emulate_bgr565 = false; // Default: assume emulation isn't required
|
||||
|
||||
if (is_intel_anv) {
|
||||
LOG_WARNING(Render_Vulkan, "Intel ANV driver does not support native BGR format");
|
||||
must_emulate_bgr565 = true;
|
||||
} else if (is_qualcomm) {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"Qualcomm driver mishandles BGR5 formats even with VK_KHR_maintenance5, forcing emulation");
|
||||
must_emulate_bgr565 = true;
|
||||
} else if (is_arm) {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"ARM Mali driver mishandles BGR5 formats even with VK_KHR_maintenance5, forcing emulation");
|
||||
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) {
|
||||
@@ -713,52 +634,56 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
||||
(std::min)(properties.properties.limits.maxVertexInputBindings, 16U);
|
||||
}
|
||||
|
||||
if (is_turnip) {
|
||||
LOG_WARNING(Render_Vulkan, "Turnip requires higher-than-reported binding limits");
|
||||
if (is_turnip || is_qualcomm) {
|
||||
LOG_WARNING(Render_Vulkan, "Driver requires higher-than-reported binding limits");
|
||||
properties.properties.limits.maxVertexInputBindings = 32;
|
||||
}
|
||||
|
||||
if (!extensions.extended_dynamic_state && extensions.extended_dynamic_state2) {
|
||||
LOG_INFO(Render_Vulkan,
|
||||
"Removing extendedDynamicState2 due to missing extendedDynamicState");
|
||||
RemoveExtensionFeature(extensions.extended_dynamic_state2, features.extended_dynamic_state2,
|
||||
VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
if (!extensions.extended_dynamic_state2 && extensions.extended_dynamic_state3) {
|
||||
LOG_INFO(Render_Vulkan,
|
||||
"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;
|
||||
}
|
||||
|
||||
// Mesa Intel drivers on UHD 620 have broken EDS causing extreme flickering - unknown if it affects other iGPUs
|
||||
// ALSO affects ALL versions of UHD drivers on Windows 10+, seems to cause even worse issues like straight up crashing
|
||||
// So... Yeah, UHD drivers fucking suck -- maybe one day we can work past this, maybe; some driver hacking?
|
||||
// And then we can rest in peace by doing `< VK_MAKE_API_VERSION(26, 0, 0)` for our beloved mesa drivers... one day
|
||||
if ((is_mvk || (is_integrated && is_intel_anv) || (is_integrated && is_intel_windows)) && Settings::values.dyna_state.GetValue() != 0) {
|
||||
LOG_WARNING(Render_Vulkan, "Driver has broken dynamic state, forcing to 0 to prevent graphical issues");
|
||||
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);
|
||||
RemoveExtensionFeature(extensions.extended_dynamic_state, features.extended_dynamic_state, VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
|
||||
RemoveExtensionFeature(extensions.extended_dynamic_state2, features.extended_dynamic_state2, VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
|
||||
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);
|
||||
// Base dynamic states (VIEWPORT, SCISSOR, DEPTH_BIAS, etc.) are ALWAYS active in vk_graphics_pipeline.cpp
|
||||
// This slider controls EXTENDED dynamic states with accumulative levels per Vulkan specs:
|
||||
// Level 0 = Core Dynamic States only (Vulkan 1.0)
|
||||
// Level 1 = Core + VK_EXT_extended_dynamic_state
|
||||
// Level 2 = Core + VK_EXT_extended_dynamic_state + VK_EXT_extended_dynamic_state2
|
||||
// Level 3 = Core + VK_EXT_extended_dynamic_state + VK_EXT_extended_dynamic_state2 + VK_EXT_extended_dynamic_state3
|
||||
|
||||
switch (Settings::values.dyna_state.GetValue()) {
|
||||
case 0:
|
||||
// Level 0: Disable all extended dynamic state extensions
|
||||
RemoveExtensionFeature(extensions.extended_dynamic_state, features.extended_dynamic_state,
|
||||
VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
|
||||
RemoveExtensionFeature(extensions.extended_dynamic_state2, features.extended_dynamic_state2,
|
||||
VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
|
||||
RemoveExtensionFeature(extensions.extended_dynamic_state3, features.extended_dynamic_state3,
|
||||
VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME);
|
||||
dynamic_state3_blending = false;
|
||||
dynamic_state3_enables = false;
|
||||
break;
|
||||
case 1:
|
||||
// Level 1: Enable EDS1, disable EDS2 and EDS3
|
||||
RemoveExtensionFeature(extensions.extended_dynamic_state2, features.extended_dynamic_state2,
|
||||
VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
|
||||
RemoveExtensionFeature(extensions.extended_dynamic_state3, features.extended_dynamic_state3,
|
||||
VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME);
|
||||
dynamic_state3_blending = false;
|
||||
dynamic_state3_enables = false;
|
||||
break;
|
||||
case 2:
|
||||
// Level 2: Enable EDS1 + EDS2, disable EDS3
|
||||
RemoveExtensionFeature(extensions.extended_dynamic_state3, features.extended_dynamic_state3,
|
||||
VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME);
|
||||
dynamic_state3_blending = false;
|
||||
dynamic_state3_enables = false;
|
||||
break;
|
||||
case 3:
|
||||
default:
|
||||
// Level 3: Enable all (EDS1 + EDS2 + EDS3)
|
||||
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");
|
||||
// VK_EXT_vertex_input_dynamic_state is independent from EDS
|
||||
// It can be enabled even without extended_dynamic_state
|
||||
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);
|
||||
@@ -845,8 +770,8 @@ void Device::SaveShader(std::span<const u32> spirv) const {
|
||||
}
|
||||
|
||||
bool Device::ComputeIsOptimalAstcSupported() const {
|
||||
// Disable for now to avoid converting ASTC twice.
|
||||
static constexpr std::array astc_formats = {
|
||||
// Verify hardware supports all ASTC formats with optimal tiling to avoid software conversion
|
||||
static constexpr std::array<VkFormat, 28> astc_formats = {
|
||||
VK_FORMAT_ASTC_4x4_UNORM_BLOCK, VK_FORMAT_ASTC_4x4_SRGB_BLOCK,
|
||||
VK_FORMAT_ASTC_5x4_UNORM_BLOCK, VK_FORMAT_ASTC_5x4_SRGB_BLOCK,
|
||||
VK_FORMAT_ASTC_5x5_UNORM_BLOCK, VK_FORMAT_ASTC_5x5_SRGB_BLOCK,
|
||||
@@ -865,9 +790,10 @@ bool Device::ComputeIsOptimalAstcSupported() const {
|
||||
if (!features.features.textureCompressionASTC_LDR) {
|
||||
return false;
|
||||
}
|
||||
const auto format_feature_usage{
|
||||
VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT |
|
||||
VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT |
|
||||
const auto format_feature_usage{VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT |
|
||||
VK_FORMAT_FEATURE_BLIT_SRC_BIT |
|
||||
VK_FORMAT_FEATURE_BLIT_DST_BIT |
|
||||
VK_FORMAT_FEATURE_TRANSFER_SRC_BIT |
|
||||
VK_FORMAT_FEATURE_TRANSFER_DST_BIT};
|
||||
for (const auto format : astc_formats) {
|
||||
const auto physical_format_properties{physical.GetFormatProperties(format)};
|
||||
@@ -1064,7 +990,7 @@ bool Device::GetSuitability(bool requires_swapchain) {
|
||||
// Set next pointer.
|
||||
void** next = &features2.pNext;
|
||||
|
||||
// Vulkan 1.2, 1.3 and 1.4 features
|
||||
// Vulkan 1.2 and 1.3 features
|
||||
if (instance_version >= VK_API_VERSION_1_2) {
|
||||
features_1_2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES;
|
||||
features_1_3.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES;
|
||||
@@ -1170,6 +1096,16 @@ bool Device::GetSuitability(bool requires_swapchain) {
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TRANSFORM_FEEDBACK_PROPERTIES_EXT;
|
||||
SetNext(next, properties.transform_feedback);
|
||||
}
|
||||
if (extensions.maintenance5) {
|
||||
properties.maintenance5.sType =
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_5_PROPERTIES_KHR;
|
||||
SetNext(next, properties.maintenance5);
|
||||
}
|
||||
if (extensions.multi_draw) {
|
||||
properties.multi_draw.sType =
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTI_DRAW_PROPERTIES_EXT;
|
||||
SetNext(next, properties.multi_draw);
|
||||
}
|
||||
|
||||
// Perform the property fetch.
|
||||
physical.GetProperties2(properties2);
|
||||
@@ -1202,14 +1138,75 @@ bool Device::GetSuitability(bool requires_swapchain) {
|
||||
}
|
||||
}
|
||||
|
||||
// VK_DYNAMIC_STATE
|
||||
|
||||
// Driver detection variables for workarounds in GetSuitability
|
||||
const VkDriverId driver_id = properties.driver.driverID;
|
||||
[[maybe_unused]] const bool is_amd_driver =
|
||||
driver_id == VK_DRIVER_ID_AMD_PROPRIETARY || driver_id == VK_DRIVER_ID_AMD_OPEN_SOURCE;
|
||||
const bool is_intel_windows = driver_id == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS;
|
||||
[[maybe_unused]] const bool is_qualcomm = driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY;
|
||||
|
||||
// VK_EXT_extended_dynamic_state2 below this will appear drivers that need workarounds.
|
||||
|
||||
// VK_EXT_extended_dynamic_state3 below this will appear drivers that need workarounds.
|
||||
|
||||
[[maybe_unused]] const auto device_id = properties.properties.deviceID;
|
||||
|
||||
// Samsung: Broken extendedDynamicState3ColorBlendEquation
|
||||
// Disable blend equation dynamic state, force static pipeline state
|
||||
if (extensions.extended_dynamic_state3 &&
|
||||
(driver_id == VK_DRIVER_ID_SAMSUNG_PROPRIETARY)) {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"Samsung: Disabling broken extendedDynamicState3ColorBlendEquation");
|
||||
features.extended_dynamic_state3.extendedDynamicState3ColorBlendEnable = false;
|
||||
features.extended_dynamic_state3.extendedDynamicState3ColorBlendEquation = false;
|
||||
}
|
||||
|
||||
// Intel Windows < 27.20.100.0: Broken VertexInputDynamicState
|
||||
// Disable VertexInputDynamicState on old Intel Windows drivers
|
||||
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 Windows < 27.20.100.0: Disabling 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 (Settings::values.dyna_state.GetValue() == 0) {
|
||||
LOG_INFO(Render_Vulkan, "Extended Dynamic State disabled by user setting, clearing all EDS features");
|
||||
features.custom_border_color.customBorderColors = false;
|
||||
features.custom_border_color.customBorderColorWithoutFormat = false;
|
||||
features.extended_dynamic_state.extendedDynamicState = false;
|
||||
features.extended_dynamic_state2.extendedDynamicState2 = false;
|
||||
features.extended_dynamic_state3.extendedDynamicState3ColorBlendEnable = false;
|
||||
features.extended_dynamic_state3.extendedDynamicState3ColorBlendEquation = false;
|
||||
features.extended_dynamic_state3.extendedDynamicState3ColorWriteMask = false;
|
||||
features.extended_dynamic_state3.extendedDynamicState3DepthClampEnable = false;
|
||||
features.extended_dynamic_state3.extendedDynamicState3LogicOpEnable = false;
|
||||
}
|
||||
|
||||
// Return whether we were suitable.
|
||||
return suitable;
|
||||
}
|
||||
|
||||
void Device::RemoveUnsuitableExtensions() {
|
||||
// VK_EXT_custom_border_color
|
||||
extensions.custom_border_color = features.custom_border_color.customBorderColors &&
|
||||
features.custom_border_color.customBorderColorWithoutFormat;
|
||||
// Enable extension if driver supports it, then check individual features
|
||||
// - customBorderColors: Required to use VK_BORDER_COLOR_FLOAT_CUSTOM_EXT
|
||||
// - customBorderColorWithoutFormat: Optional, allows VK_FORMAT_UNDEFINED
|
||||
// If only customBorderColors is available, we must provide a specific format
|
||||
if (extensions.custom_border_color) {
|
||||
// Verify that at least customBorderColors is available
|
||||
if (!features.custom_border_color.customBorderColors) {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"VK_EXT_custom_border_color reported but customBorderColors feature not available, disabling");
|
||||
extensions.custom_border_color = false;
|
||||
}
|
||||
}
|
||||
RemoveExtensionFeatureIfUnsuitable(extensions.custom_border_color, features.custom_border_color,
|
||||
VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME);
|
||||
|
||||
@@ -1238,21 +1235,79 @@ void Device::RemoveUnsuitableExtensions() {
|
||||
VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
|
||||
|
||||
// VK_EXT_extended_dynamic_state3
|
||||
dynamic_state3_blending =
|
||||
features.extended_dynamic_state3.extendedDynamicState3ColorBlendEnable &&
|
||||
features.extended_dynamic_state3.extendedDynamicState3ColorBlendEquation &&
|
||||
const bool supports_color_blend_enable =
|
||||
features.extended_dynamic_state3.extendedDynamicState3ColorBlendEnable;
|
||||
const bool supports_color_blend_equation =
|
||||
features.extended_dynamic_state3.extendedDynamicState3ColorBlendEquation;
|
||||
const bool supports_color_write_mask =
|
||||
features.extended_dynamic_state3.extendedDynamicState3ColorWriteMask;
|
||||
dynamic_state3_enables =
|
||||
features.extended_dynamic_state3.extendedDynamicState3DepthClampEnable &&
|
||||
dynamic_state3_blending = supports_color_blend_enable && supports_color_blend_equation &&
|
||||
supports_color_write_mask;
|
||||
|
||||
const bool supports_depth_clamp_enable =
|
||||
features.extended_dynamic_state3.extendedDynamicState3DepthClampEnable;
|
||||
const bool supports_logic_op_enable =
|
||||
features.extended_dynamic_state3.extendedDynamicState3LogicOpEnable;
|
||||
const bool supports_line_raster_mode =
|
||||
features.extended_dynamic_state3.extendedDynamicState3LineRasterizationMode &&
|
||||
extensions.line_rasterization && features.line_rasterization.rectangularLines;
|
||||
const bool supports_conservative_raster_mode =
|
||||
features.extended_dynamic_state3.extendedDynamicState3ConservativeRasterizationMode &&
|
||||
extensions.conservative_rasterization;
|
||||
const bool supports_line_stipple_enable =
|
||||
features.extended_dynamic_state3.extendedDynamicState3LineStippleEnable &&
|
||||
extensions.line_rasterization && features.line_rasterization.stippledRectangularLines;
|
||||
const bool supports_alpha_to_coverage =
|
||||
features.extended_dynamic_state3.extendedDynamicState3AlphaToCoverageEnable;
|
||||
const bool supports_alpha_to_one =
|
||||
features.extended_dynamic_state3.extendedDynamicState3AlphaToOneEnable &&
|
||||
features.features.alphaToOne;
|
||||
|
||||
dynamic_state3_depth_clamp_enable = supports_depth_clamp_enable;
|
||||
dynamic_state3_logic_op_enable = supports_logic_op_enable;
|
||||
dynamic_state3_line_raster_mode = supports_line_raster_mode;
|
||||
dynamic_state3_conservative_raster_mode = supports_conservative_raster_mode;
|
||||
dynamic_state3_line_stipple_enable = supports_line_stipple_enable;
|
||||
dynamic_state3_alpha_to_coverage = supports_alpha_to_coverage;
|
||||
dynamic_state3_alpha_to_one = supports_alpha_to_one;
|
||||
|
||||
dynamic_state3_enables = dynamic_state3_depth_clamp_enable || dynamic_state3_logic_op_enable ||
|
||||
dynamic_state3_line_raster_mode ||
|
||||
dynamic_state3_conservative_raster_mode ||
|
||||
dynamic_state3_line_stipple_enable ||
|
||||
dynamic_state3_alpha_to_coverage || dynamic_state3_alpha_to_one;
|
||||
|
||||
extensions.extended_dynamic_state3 = dynamic_state3_blending || dynamic_state3_enables;
|
||||
dynamic_state3_blending = dynamic_state3_blending && extensions.extended_dynamic_state3;
|
||||
dynamic_state3_enables = dynamic_state3_enables && extensions.extended_dynamic_state3;
|
||||
if (!extensions.extended_dynamic_state3) {
|
||||
dynamic_state3_blending = false;
|
||||
dynamic_state3_enables = false;
|
||||
dynamic_state3_depth_clamp_enable = false;
|
||||
dynamic_state3_logic_op_enable = false;
|
||||
dynamic_state3_line_raster_mode = false;
|
||||
dynamic_state3_conservative_raster_mode = false;
|
||||
dynamic_state3_line_stipple_enable = false;
|
||||
dynamic_state3_alpha_to_coverage = false;
|
||||
dynamic_state3_alpha_to_one = false;
|
||||
}
|
||||
RemoveExtensionFeatureIfUnsuitable(extensions.extended_dynamic_state3,
|
||||
features.extended_dynamic_state3,
|
||||
VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME);
|
||||
|
||||
// VK_EXT_robustness2
|
||||
// Enable if at least one robustness2 feature is available
|
||||
extensions.robustness_2 = features.robustness2.robustBufferAccess2 ||
|
||||
features.robustness2.robustImageAccess2 ||
|
||||
features.robustness2.nullDescriptor;
|
||||
|
||||
RemoveExtensionFeatureIfUnsuitable(extensions.robustness_2, features.robustness2,
|
||||
VK_EXT_ROBUSTNESS_2_EXTENSION_NAME);
|
||||
|
||||
// VK_EXT_image_robustness
|
||||
// Enable if robustImageAccess is available
|
||||
extensions.image_robustness = features.image_robustness.robustImageAccess;
|
||||
RemoveExtensionFeatureIfUnsuitable(extensions.image_robustness, features.image_robustness,
|
||||
VK_EXT_IMAGE_ROBUSTNESS_EXTENSION_NAME);
|
||||
|
||||
// VK_EXT_provoking_vertex
|
||||
if (Settings::values.provoking_vertex.GetValue()) {
|
||||
extensions.provoking_vertex = features.provoking_vertex.provokingVertexLast
|
||||
@@ -1307,6 +1362,17 @@ void Device::RemoveUnsuitableExtensions() {
|
||||
features.vertex_input_dynamic_state,
|
||||
VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
|
||||
|
||||
// VK_EXT_multi_draw
|
||||
extensions.multi_draw = features.multi_draw.multiDraw;
|
||||
|
||||
if (extensions.multi_draw) {
|
||||
LOG_INFO(Render_Vulkan, "VK_EXT_multi_draw: maxMultiDrawCount={}",
|
||||
properties.multi_draw.maxMultiDrawCount);
|
||||
}
|
||||
|
||||
RemoveExtensionFeatureIfUnsuitable(extensions.multi_draw, features.multi_draw,
|
||||
VK_EXT_MULTI_DRAW_EXTENSION_NAME);
|
||||
|
||||
// VK_KHR_pipeline_executable_properties
|
||||
if (Settings::values.renderer_shader_feedback.GetValue()) {
|
||||
extensions.pipeline_executable_properties =
|
||||
@@ -1330,6 +1396,76 @@ void Device::RemoveUnsuitableExtensions() {
|
||||
RemoveExtensionFeatureIfUnsuitable(extensions.workgroup_memory_explicit_layout,
|
||||
features.workgroup_memory_explicit_layout,
|
||||
VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME);
|
||||
|
||||
// VK_EXT_swapchain_maintenance1 (extension only, has features)
|
||||
// Requires VK_EXT_surface_maintenance1 instance extension
|
||||
extensions.swapchain_maintenance1 = features.swapchain_maintenance1.swapchainMaintenance1;
|
||||
if (extensions.swapchain_maintenance1) {
|
||||
// Check if VK_EXT_surface_maintenance1 instance extension is available
|
||||
const auto instance_extensions = vk::EnumerateInstanceExtensionProperties(dld);
|
||||
const bool has_surface_maintenance1 = instance_extensions && std::ranges::any_of(*instance_extensions,
|
||||
[](const VkExtensionProperties& prop) {
|
||||
return std::strcmp(prop.extensionName, VK_EXT_SURFACE_MAINTENANCE_1_EXTENSION_NAME) == 0;
|
||||
});
|
||||
if (!has_surface_maintenance1) {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"VK_EXT_swapchain_maintenance1 requires VK_EXT_surface_maintenance1, disabling");
|
||||
extensions.swapchain_maintenance1 = false;
|
||||
features.swapchain_maintenance1.swapchainMaintenance1 = false;
|
||||
}
|
||||
}
|
||||
RemoveExtensionFeatureIfUnsuitable(extensions.swapchain_maintenance1, features.swapchain_maintenance1,
|
||||
VK_EXT_SWAPCHAIN_MAINTENANCE_1_EXTENSION_NAME);
|
||||
|
||||
// VK_KHR_maintenance1 (core in Vulkan 1.1, no features)
|
||||
extensions.maintenance1 = loaded_extensions.contains(VK_KHR_MAINTENANCE_1_EXTENSION_NAME);
|
||||
RemoveExtensionIfUnsuitable(extensions.maintenance1, VK_KHR_MAINTENANCE_1_EXTENSION_NAME);
|
||||
|
||||
// VK_KHR_maintenance2 (core in Vulkan 1.1, no features)
|
||||
extensions.maintenance2 = loaded_extensions.contains(VK_KHR_MAINTENANCE_2_EXTENSION_NAME);
|
||||
RemoveExtensionIfUnsuitable(extensions.maintenance2, VK_KHR_MAINTENANCE_2_EXTENSION_NAME);
|
||||
|
||||
// VK_KHR_maintenance3 (core in Vulkan 1.1, no features)
|
||||
extensions.maintenance3 = loaded_extensions.contains(VK_KHR_MAINTENANCE_3_EXTENSION_NAME);
|
||||
RemoveExtensionIfUnsuitable(extensions.maintenance3, VK_KHR_MAINTENANCE_3_EXTENSION_NAME);
|
||||
|
||||
// VK_KHR_maintenance4
|
||||
extensions.maintenance4 = features.maintenance4.maintenance4;
|
||||
RemoveExtensionFeatureIfUnsuitable(extensions.maintenance4, features.maintenance4,
|
||||
VK_KHR_MAINTENANCE_4_EXTENSION_NAME);
|
||||
|
||||
// VK_KHR_maintenance5
|
||||
extensions.maintenance5 = features.maintenance5.maintenance5;
|
||||
|
||||
if (extensions.maintenance5) {
|
||||
LOG_INFO(Render_Vulkan, "VK_KHR_maintenance5 properties: polygonModePointSize={} "
|
||||
"depthStencilSwizzleOne={} earlyFragmentTests={} nonStrictWideLines={}",
|
||||
properties.maintenance5.polygonModePointSize,
|
||||
properties.maintenance5.depthStencilSwizzleOneSupport,
|
||||
properties.maintenance5.earlyFragmentMultisampleCoverageAfterSampleCounting &&
|
||||
properties.maintenance5.earlyFragmentSampleMaskTestBeforeSampleCounting,
|
||||
properties.maintenance5.nonStrictWideLinesUseParallelogram);
|
||||
}
|
||||
|
||||
RemoveExtensionFeatureIfUnsuitable(extensions.maintenance5, features.maintenance5,
|
||||
VK_KHR_MAINTENANCE_5_EXTENSION_NAME);
|
||||
|
||||
// VK_KHR_maintenance6
|
||||
extensions.maintenance6 = features.maintenance6.maintenance6;
|
||||
RemoveExtensionFeatureIfUnsuitable(extensions.maintenance6, features.maintenance6,
|
||||
VK_KHR_MAINTENANCE_6_EXTENSION_NAME);
|
||||
|
||||
// VK_KHR_maintenance7 (proposed for Vulkan 1.4, no features)
|
||||
extensions.maintenance7 = loaded_extensions.contains(VK_KHR_MAINTENANCE_7_EXTENSION_NAME);
|
||||
RemoveExtensionIfUnsuitable(extensions.maintenance7, VK_KHR_MAINTENANCE_7_EXTENSION_NAME);
|
||||
|
||||
// VK_KHR_maintenance8 (proposed for Vulkan 1.4, no features)
|
||||
extensions.maintenance8 = loaded_extensions.contains(VK_KHR_MAINTENANCE_8_EXTENSION_NAME);
|
||||
RemoveExtensionIfUnsuitable(extensions.maintenance8, VK_KHR_MAINTENANCE_8_EXTENSION_NAME);
|
||||
|
||||
// VK_KHR_maintenance9 (proposed for Vulkan 1.4, no features)
|
||||
extensions.maintenance9 = loaded_extensions.contains(VK_KHR_MAINTENANCE_9_EXTENSION_NAME);
|
||||
RemoveExtensionIfUnsuitable(extensions.maintenance9, VK_KHR_MAINTENANCE_9_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
void Device::SetupFamilies(VkSurfaceKHR surface) {
|
||||
|
||||
@@ -37,9 +37,13 @@ VK_DEFINE_HANDLE(VmaAllocator)
|
||||
FEATURE(KHR, TimelineSemaphore, TIMELINE_SEMAPHORE, timeline_semaphore)
|
||||
|
||||
#define FOR_EACH_VK_FEATURE_1_3(FEATURE) \
|
||||
FEATURE(EXT, ImageRobustness, IMAGE_ROBUSTNESS, image_robustness) \
|
||||
FEATURE(EXT, ShaderDemoteToHelperInvocation, SHADER_DEMOTE_TO_HELPER_INVOCATION, \
|
||||
shader_demote_to_helper_invocation) \
|
||||
FEATURE(EXT, SubgroupSizeControl, SUBGROUP_SIZE_CONTROL, subgroup_size_control)
|
||||
FEATURE(EXT, SubgroupSizeControl, SUBGROUP_SIZE_CONTROL, subgroup_size_control) \
|
||||
FEATURE(KHR, Maintenance4, MAINTENANCE_4, maintenance4)
|
||||
|
||||
#define FOR_EACH_VK_FEATURE_1_4(FEATURE)
|
||||
|
||||
// Define all features which may be used by the implementation and require an extension here.
|
||||
#define FOR_EACH_VK_FEATURE_EXT(FEATURE) \
|
||||
@@ -52,12 +56,16 @@ VK_DEFINE_HANDLE(VmaAllocator)
|
||||
FEATURE(EXT, 4444Formats, 4444_FORMATS, format_a4b4g4r4) \
|
||||
FEATURE(EXT, IndexTypeUint8, INDEX_TYPE_UINT8, index_type_uint8) \
|
||||
FEATURE(EXT, LineRasterization, LINE_RASTERIZATION, line_rasterization) \
|
||||
FEATURE(EXT, MultiDraw, MULTI_DRAW, multi_draw) \
|
||||
FEATURE(EXT, PrimitiveTopologyListRestart, PRIMITIVE_TOPOLOGY_LIST_RESTART, \
|
||||
primitive_topology_list_restart) \
|
||||
FEATURE(EXT, ProvokingVertex, PROVOKING_VERTEX, provoking_vertex) \
|
||||
FEATURE(EXT, Robustness2, ROBUSTNESS_2, robustness2) \
|
||||
FEATURE(EXT, TransformFeedback, TRANSFORM_FEEDBACK, transform_feedback) \
|
||||
FEATURE(EXT, VertexInputDynamicState, VERTEX_INPUT_DYNAMIC_STATE, vertex_input_dynamic_state) \
|
||||
FEATURE(EXT, SwapchainMaintenance1, SWAPCHAIN_MAINTENANCE_1, swapchain_maintenance1) \
|
||||
FEATURE(KHR, Maintenance5, MAINTENANCE_5, maintenance5) \
|
||||
FEATURE(KHR, Maintenance6, MAINTENANCE_6, maintenance6) \
|
||||
FEATURE(KHR, PipelineExecutableProperties, PIPELINE_EXECUTABLE_PROPERTIES, \
|
||||
pipeline_executable_properties) \
|
||||
FEATURE(KHR, WorkgroupMemoryExplicitLayout, WORKGROUP_MEMORY_EXPLICIT_LAYOUT, \
|
||||
@@ -84,6 +92,12 @@ VK_DEFINE_HANDLE(VmaAllocator)
|
||||
EXTENSION(KHR, SWAPCHAIN, swapchain) \
|
||||
EXTENSION(KHR, SWAPCHAIN_MUTABLE_FORMAT, swapchain_mutable_format) \
|
||||
EXTENSION(KHR, IMAGE_FORMAT_LIST, image_format_list) \
|
||||
EXTENSION(KHR, MAINTENANCE_1, maintenance1) \
|
||||
EXTENSION(KHR, MAINTENANCE_2, maintenance2) \
|
||||
EXTENSION(KHR, MAINTENANCE_3, maintenance3) \
|
||||
EXTENSION(KHR, MAINTENANCE_7, maintenance7) \
|
||||
EXTENSION(KHR, MAINTENANCE_8, maintenance8) \
|
||||
EXTENSION(KHR, MAINTENANCE_9, maintenance9) \
|
||||
EXTENSION(NV, DEVICE_DIAGNOSTICS_CONFIG, device_diagnostics_config) \
|
||||
EXTENSION(NV, GEOMETRY_SHADER_PASSTHROUGH, geometry_shader_passthrough) \
|
||||
EXTENSION(NV, VIEWPORT_ARRAY2, viewport_array2) \
|
||||
@@ -110,6 +124,7 @@ VK_DEFINE_HANDLE(VmaAllocator)
|
||||
EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME) \
|
||||
EXTENSION_NAME(VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME) \
|
||||
EXTENSION_NAME(VK_EXT_4444_FORMATS_EXTENSION_NAME) \
|
||||
EXTENSION_NAME(VK_EXT_IMAGE_ROBUSTNESS_EXTENSION_NAME) \
|
||||
EXTENSION_NAME(VK_EXT_LINE_RASTERIZATION_EXTENSION_NAME) \
|
||||
EXTENSION_NAME(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME) \
|
||||
EXTENSION_NAME(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME) \
|
||||
@@ -161,6 +176,7 @@ VK_DEFINE_HANDLE(VmaAllocator)
|
||||
FEATURE_NAME(depth_bias_control, depthBiasExact) \
|
||||
FEATURE_NAME(extended_dynamic_state, extendedDynamicState) \
|
||||
FEATURE_NAME(format_a4b4g4r4, formatA4B4G4R4) \
|
||||
FEATURE_NAME(image_robustness, robustImageAccess) \
|
||||
FEATURE_NAME(index_type_uint8, indexTypeUint8) \
|
||||
FEATURE_NAME(primitive_topology_list_restart, primitiveTopologyListRestart) \
|
||||
FEATURE_NAME(provoking_vertex, provokingVertexLast) \
|
||||
@@ -440,6 +456,11 @@ public:
|
||||
return extensions.swapchain_mutable_format;
|
||||
}
|
||||
|
||||
/// Returns true if VK_EXT_swapchain_maintenance1 is enabled.
|
||||
bool IsExtSwapchainMaintenance1Enabled() const {
|
||||
return extensions.swapchain_maintenance1;
|
||||
}
|
||||
|
||||
/// Returns true if VK_KHR_shader_float_controls is enabled.
|
||||
bool IsKhrShaderFloatControlsSupported() const {
|
||||
return extensions.shader_float_controls;
|
||||
@@ -476,10 +497,18 @@ public:
|
||||
}
|
||||
|
||||
/// Returns true if the device supports VK_EXT_shader_stencil_export.
|
||||
/// Note: Most Mali/NVIDIA drivers don't support this. Use hardware blits as fallback.
|
||||
bool IsExtShaderStencilExportSupported() const {
|
||||
return extensions.shader_stencil_export;
|
||||
}
|
||||
|
||||
/// Returns true if depth/stencil operations can be performed efficiently.
|
||||
/// Either through shader export or hardware blits.
|
||||
bool CanPerformDepthStencilOperations() const {
|
||||
return extensions.shader_stencil_export || is_blit_depth24_stencil8_supported ||
|
||||
is_blit_depth32_stencil8_supported;
|
||||
}
|
||||
|
||||
/// Returns true if the device supports VK_EXT_depth_range_unrestricted.
|
||||
bool IsExtDepthRangeUnrestrictedSupported() const {
|
||||
return extensions.depth_range_unrestricted;
|
||||
@@ -520,6 +549,46 @@ public:
|
||||
return extensions.custom_border_color;
|
||||
}
|
||||
|
||||
/// Returns true if the device supports VK_EXT_image_robustness.
|
||||
bool IsExtImageRobustnessSupported() const {
|
||||
return extensions.image_robustness;
|
||||
}
|
||||
|
||||
/// Returns true if robustImageAccess is supported.
|
||||
bool IsRobustImageAccessSupported() const {
|
||||
return features.image_robustness.robustImageAccess;
|
||||
}
|
||||
|
||||
/// Returns true if the device supports VK_EXT_robustness2.
|
||||
bool IsExtRobustness2Supported() const {
|
||||
return extensions.robustness_2;
|
||||
}
|
||||
|
||||
/// Returns true if robustBufferAccess2 is supported.
|
||||
bool IsRobustBufferAccess2Supported() const {
|
||||
return features.robustness2.robustBufferAccess2;
|
||||
}
|
||||
|
||||
/// Returns true if robustImageAccess2 is supported.
|
||||
bool IsRobustImageAccess2Supported() const {
|
||||
return features.robustness2.robustImageAccess2;
|
||||
}
|
||||
|
||||
/// Returns true if nullDescriptor is supported.
|
||||
bool IsNullDescriptorSupported() const {
|
||||
return features.robustness2.nullDescriptor;
|
||||
}
|
||||
|
||||
/// Returns true if customBorderColors feature is available.
|
||||
bool IsCustomBorderColorsSupported() const {
|
||||
return features.custom_border_color.customBorderColors;
|
||||
}
|
||||
|
||||
/// Returns true if customBorderColorWithoutFormat feature is available.
|
||||
bool IsCustomBorderColorWithoutFormatSupported() const {
|
||||
return features.custom_border_color.customBorderColorWithoutFormat;
|
||||
}
|
||||
|
||||
/// Returns true if the device supports VK_EXT_extended_dynamic_state.
|
||||
bool IsExtExtendedDynamicStateSupported() const {
|
||||
return extensions.extended_dynamic_state;
|
||||
@@ -569,6 +638,55 @@ public:
|
||||
return extensions.line_rasterization;
|
||||
}
|
||||
|
||||
bool SupportsRectangularLines() const {
|
||||
return features.line_rasterization.rectangularLines != VK_FALSE;
|
||||
}
|
||||
|
||||
bool SupportsSmoothLines() const {
|
||||
return features.line_rasterization.smoothLines != VK_FALSE;
|
||||
}
|
||||
|
||||
bool SupportsStippledRectangularLines() const {
|
||||
return features.line_rasterization.stippledRectangularLines != VK_FALSE;
|
||||
}
|
||||
|
||||
bool SupportsAlphaToOne() const {
|
||||
return features.features.alphaToOne != VK_FALSE;
|
||||
}
|
||||
|
||||
bool SupportsDynamicState3DepthClampEnable() const {
|
||||
return dynamic_state3_depth_clamp_enable;
|
||||
}
|
||||
|
||||
bool SupportsDynamicState3LogicOpEnable() const {
|
||||
return dynamic_state3_logic_op_enable;
|
||||
}
|
||||
|
||||
bool SupportsDynamicState3LineRasterizationMode() const {
|
||||
return dynamic_state3_line_raster_mode;
|
||||
}
|
||||
|
||||
bool SupportsDynamicState3ConservativeRasterizationMode() const {
|
||||
return dynamic_state3_conservative_raster_mode;
|
||||
}
|
||||
|
||||
bool SupportsDynamicState3LineStippleEnable() const {
|
||||
return dynamic_state3_line_stipple_enable;
|
||||
}
|
||||
|
||||
bool SupportsDynamicState3AlphaToCoverageEnable() const {
|
||||
return dynamic_state3_alpha_to_coverage;
|
||||
}
|
||||
|
||||
bool SupportsDynamicState3AlphaToOneEnable() const {
|
||||
return dynamic_state3_alpha_to_one;
|
||||
}
|
||||
|
||||
/// Returns true when the user enabled extended core dynamic states (level > 0).
|
||||
bool UsesAdvancedCoreDynamicState() const {
|
||||
return Settings::values.dyna_state.GetValue() > 0;
|
||||
}
|
||||
|
||||
/// Returns true if the device supports VK_EXT_vertex_input_dynamic_state.
|
||||
bool IsExtVertexInputDynamicStateSupported() const {
|
||||
return extensions.vertex_input_dynamic_state;
|
||||
@@ -703,6 +821,73 @@ public:
|
||||
return features2.features.multiViewport;
|
||||
}
|
||||
|
||||
/// Returns true if the device supports VK_KHR_maintenance1.
|
||||
bool IsKhrMaintenance1Supported() const {
|
||||
return extensions.maintenance1;
|
||||
}
|
||||
|
||||
/// Returns true if the device supports VK_KHR_maintenance2.
|
||||
bool IsKhrMaintenance2Supported() const {
|
||||
return extensions.maintenance2;
|
||||
}
|
||||
|
||||
/// Returns true if the device supports VK_KHR_maintenance3.
|
||||
bool IsKhrMaintenance3Supported() const {
|
||||
return extensions.maintenance3;
|
||||
}
|
||||
|
||||
/// Returns true if the device supports VK_KHR_maintenance4.
|
||||
bool IsKhrMaintenance4Supported() const {
|
||||
return extensions.maintenance4;
|
||||
}
|
||||
|
||||
/// Returns true if the device supports VK_KHR_maintenance5.
|
||||
bool IsKhrMaintenance5Supported() const {
|
||||
return extensions.maintenance5;
|
||||
}
|
||||
|
||||
/// Returns true if polygon mode POINT supports gl_PointSize.
|
||||
bool SupportsPolygonModePointSize() const {
|
||||
return extensions.maintenance5 && properties.maintenance5.polygonModePointSize;
|
||||
}
|
||||
|
||||
/// Returns true if depth/stencil swizzle ONE is supported.
|
||||
bool SupportsDepthStencilSwizzleOne() const {
|
||||
return extensions.maintenance5 && properties.maintenance5.depthStencilSwizzleOneSupport;
|
||||
}
|
||||
|
||||
/// Returns true if early fragment tests optimizations are available.
|
||||
bool SupportsEarlyFragmentTests() const {
|
||||
return extensions.maintenance5 &&
|
||||
properties.maintenance5.earlyFragmentMultisampleCoverageAfterSampleCounting &&
|
||||
properties.maintenance5.earlyFragmentSampleMaskTestBeforeSampleCounting;
|
||||
}
|
||||
|
||||
/// Returns true if the device supports VK_KHR_maintenance6.
|
||||
bool IsKhrMaintenance6Supported() const {
|
||||
return extensions.maintenance6;
|
||||
}
|
||||
|
||||
/// Returns true if the device supports VK_EXT_multi_draw.
|
||||
bool IsExtMultiDrawSupported() const {
|
||||
return extensions.multi_draw;
|
||||
}
|
||||
|
||||
/// Returns true if the device supports VK_KHR_maintenance7.
|
||||
bool IsKhrMaintenance7Supported() const {
|
||||
return extensions.maintenance7;
|
||||
}
|
||||
|
||||
/// Returns true if the device supports VK_KHR_maintenance8.
|
||||
bool IsKhrMaintenance8Supported() const {
|
||||
return extensions.maintenance8;
|
||||
}
|
||||
|
||||
/// Returns true if the device supports VK_KHR_maintenance9.
|
||||
bool IsKhrMaintenance9Supported() const {
|
||||
return extensions.maintenance9;
|
||||
}
|
||||
|
||||
[[nodiscard]] static constexpr bool CheckBrokenCompute(VkDriverId driver_id,
|
||||
u32 driver_version) {
|
||||
if (driver_id == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS) {
|
||||
@@ -786,6 +971,7 @@ private:
|
||||
FOR_EACH_VK_FEATURE_1_1(FEATURE);
|
||||
FOR_EACH_VK_FEATURE_1_2(FEATURE);
|
||||
FOR_EACH_VK_FEATURE_1_3(FEATURE);
|
||||
FOR_EACH_VK_FEATURE_1_4(FEATURE);
|
||||
FOR_EACH_VK_FEATURE_EXT(FEATURE);
|
||||
FOR_EACH_VK_EXTENSION(EXTENSION);
|
||||
|
||||
@@ -802,6 +988,7 @@ private:
|
||||
FOR_EACH_VK_FEATURE_1_1(FEATURE_CORE);
|
||||
FOR_EACH_VK_FEATURE_1_2(FEATURE_CORE);
|
||||
FOR_EACH_VK_FEATURE_1_3(FEATURE_CORE);
|
||||
FOR_EACH_VK_FEATURE_1_4(FEATURE_CORE);
|
||||
FOR_EACH_VK_FEATURE_EXT(FEATURE_EXT);
|
||||
|
||||
#undef FEATURE_CORE
|
||||
@@ -817,6 +1004,8 @@ private:
|
||||
VkPhysicalDevicePushDescriptorPropertiesKHR push_descriptor{};
|
||||
VkPhysicalDeviceSubgroupSizeControlProperties subgroup_size_control{};
|
||||
VkPhysicalDeviceTransformFeedbackPropertiesEXT transform_feedback{};
|
||||
VkPhysicalDeviceMaintenance5PropertiesKHR maintenance5{};
|
||||
VkPhysicalDeviceMultiDrawPropertiesEXT multi_draw{};
|
||||
|
||||
VkPhysicalDeviceProperties properties{};
|
||||
};
|
||||
@@ -846,8 +1035,15 @@ private:
|
||||
bool cant_blit_msaa{}; ///< Does not support MSAA<->MSAA blitting.
|
||||
bool must_emulate_scaled_formats{}; ///< Requires scaled vertex format emulation
|
||||
bool must_emulate_bgr565{}; ///< Emulates BGR565 by swizzling RGB565 format.
|
||||
bool dynamic_state3_blending{}; ///< Has all blending features of dynamic_state3.
|
||||
bool dynamic_state3_enables{}; ///< Has all enables features of dynamic_state3.
|
||||
bool dynamic_state3_blending{}; ///< Has blending features of dynamic_state3.
|
||||
bool dynamic_state3_enables{}; ///< Has at least one enable feature of dynamic_state3.
|
||||
bool dynamic_state3_depth_clamp_enable{};
|
||||
bool dynamic_state3_logic_op_enable{};
|
||||
bool dynamic_state3_line_raster_mode{};
|
||||
bool dynamic_state3_conservative_raster_mode{};
|
||||
bool dynamic_state3_line_stipple_enable{};
|
||||
bool dynamic_state3_alpha_to_coverage{};
|
||||
bool dynamic_state3_alpha_to_one{};
|
||||
bool supports_conditional_barriers{}; ///< Allows barriers in conditional control flow.
|
||||
u64 device_access_memory{}; ///< Total size of device local memory in bytes.
|
||||
u32 sets_per_pool{}; ///< Sets per Description Pool
|
||||
|
||||
@@ -87,6 +87,11 @@ namespace {
|
||||
AreExtensionsSupported(dld, std::array{VK_EXT_DEBUG_UTILS_EXTENSION_NAME})) {
|
||||
extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
||||
}
|
||||
// VK_EXT_surface_maintenance1 is required for VK_EXT_swapchain_maintenance1
|
||||
if (window_type != Core::Frontend::WindowSystemType::Headless &&
|
||||
AreExtensionsSupported(dld, std::array{VK_EXT_SURFACE_MAINTENANCE_1_EXTENSION_NAME})) {
|
||||
extensions.push_back(VK_EXT_SURFACE_MAINTENANCE_1_EXTENSION_NAME);
|
||||
}
|
||||
return extensions;
|
||||
}
|
||||
|
||||
|
||||
@@ -116,6 +116,8 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
|
||||
X(vkCmdDrawIndirectCount);
|
||||
X(vkCmdDrawIndexedIndirectCount);
|
||||
X(vkCmdDrawIndirectByteCountEXT);
|
||||
X(vkCmdDrawMultiEXT);
|
||||
X(vkCmdDrawMultiIndexedEXT);
|
||||
X(vkCmdEndConditionalRenderingEXT);
|
||||
X(vkCmdEndQuery);
|
||||
X(vkCmdEndRenderPass);
|
||||
@@ -145,6 +147,8 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
|
||||
X(vkCmdSetDepthWriteEnableEXT);
|
||||
X(vkCmdSetPrimitiveRestartEnableEXT);
|
||||
X(vkCmdSetRasterizerDiscardEnableEXT);
|
||||
X(vkCmdSetAlphaToCoverageEnableEXT);
|
||||
X(vkCmdSetAlphaToOneEnableEXT);
|
||||
X(vkCmdSetConservativeRasterizationModeEXT);
|
||||
X(vkCmdSetLineRasterizationModeEXT);
|
||||
X(vkCmdSetLineStippleEnableEXT);
|
||||
|
||||
@@ -216,6 +216,8 @@ struct DeviceDispatch : InstanceDispatch {
|
||||
PFN_vkCmdDrawIndirectCount vkCmdDrawIndirectCount{};
|
||||
PFN_vkCmdDrawIndexedIndirectCount vkCmdDrawIndexedIndirectCount{};
|
||||
PFN_vkCmdDrawIndirectByteCountEXT vkCmdDrawIndirectByteCountEXT{};
|
||||
PFN_vkCmdDrawMultiEXT vkCmdDrawMultiEXT{};
|
||||
PFN_vkCmdDrawMultiIndexedEXT vkCmdDrawMultiIndexedEXT{};
|
||||
PFN_vkCmdEndConditionalRenderingEXT vkCmdEndConditionalRenderingEXT{};
|
||||
PFN_vkCmdEndDebugUtilsLabelEXT vkCmdEndDebugUtilsLabelEXT{};
|
||||
PFN_vkCmdEndQuery vkCmdEndQuery{};
|
||||
@@ -238,6 +240,8 @@ struct DeviceDispatch : InstanceDispatch {
|
||||
PFN_vkCmdSetDepthWriteEnableEXT vkCmdSetDepthWriteEnableEXT{};
|
||||
PFN_vkCmdSetPrimitiveRestartEnableEXT vkCmdSetPrimitiveRestartEnableEXT{};
|
||||
PFN_vkCmdSetRasterizerDiscardEnableEXT vkCmdSetRasterizerDiscardEnableEXT{};
|
||||
PFN_vkCmdSetAlphaToCoverageEnableEXT vkCmdSetAlphaToCoverageEnableEXT{};
|
||||
PFN_vkCmdSetAlphaToOneEnableEXT vkCmdSetAlphaToOneEnableEXT{};
|
||||
PFN_vkCmdSetConservativeRasterizationModeEXT vkCmdSetConservativeRasterizationModeEXT{};
|
||||
PFN_vkCmdSetLineRasterizationModeEXT vkCmdSetLineRasterizationModeEXT{};
|
||||
PFN_vkCmdSetLineStippleEnableEXT vkCmdSetLineStippleEnableEXT{};
|
||||
@@ -1239,6 +1243,19 @@ public:
|
||||
counter_buffer_offset, counter_offset, stride);
|
||||
}
|
||||
|
||||
void DrawMultiEXT(u32 draw_count, const VkMultiDrawInfoEXT* vertex_info,
|
||||
u32 instance_count, u32 first_instance, u32 stride) const noexcept {
|
||||
dld->vkCmdDrawMultiEXT(handle, draw_count, vertex_info, instance_count, first_instance,
|
||||
stride);
|
||||
}
|
||||
|
||||
void DrawMultiIndexedEXT(u32 draw_count, const VkMultiDrawIndexedInfoEXT* index_info,
|
||||
u32 instance_count, u32 first_instance, u32 stride,
|
||||
const int32_t* vertex_offset) const noexcept {
|
||||
dld->vkCmdDrawMultiIndexedEXT(handle, draw_count, index_info, instance_count,
|
||||
first_instance, stride, vertex_offset);
|
||||
}
|
||||
|
||||
void ClearAttachments(Span<VkClearAttachment> attachments,
|
||||
Span<VkClearRect> rects) const noexcept {
|
||||
dld->vkCmdClearAttachments(handle, attachments.size(), attachments.data(), rects.size(),
|
||||
@@ -1471,6 +1488,14 @@ public:
|
||||
dld->vkCmdSetLogicOpEnableEXT(handle, enable ? VK_TRUE : VK_FALSE);
|
||||
}
|
||||
|
||||
void SetAlphaToCoverageEnableEXT(bool enable) const noexcept {
|
||||
dld->vkCmdSetAlphaToCoverageEnableEXT(handle, enable ? VK_TRUE : VK_FALSE);
|
||||
}
|
||||
|
||||
void SetAlphaToOneEnableEXT(bool enable) const noexcept {
|
||||
dld->vkCmdSetAlphaToOneEnableEXT(handle, enable ? VK_TRUE : VK_FALSE);
|
||||
}
|
||||
|
||||
void SetDepthClampEnableEXT(bool enable) const noexcept {
|
||||
dld->vkCmdSetDepthClampEnableEXT(handle, enable ? VK_TRUE : VK_FALSE);
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ li.checked::marker { content: "\2612"; }
|
||||
<item>
|
||||
<widget class="QLabel" name="labelLinks">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p><a href="https://eden-emulator.github.io/"><span style=" text-decoration: underline; color:#039be5;">Website</span></a> | <a href="https://git.eden-emu.dev"><span style=" text-decoration: underline; color:#039be5;">Source Code</span></a> | <a href="https://git.eden-emu.dev/eden-emu/eden/activity/contributors"><span style=" text-decoration: underline; color:#039be5;">Contributors</span></a> | <a href="https://discord.gg/kXAmGCXBGD"><span style=" text-decoration: underline; color:#039be5;">Discord</span></a> | <a href="https://rvlt.gg/qKgFEAbH"><span style=" text-decoration: underline; color:#039be5;">Revolt</span></a> | <a href="https://nitter.poast.org/edenemuofficial"><span style=" text-decoration: underline; color:#039be5;">Twitter</span></a> | <a href="https://git.eden-emu.dev/eden-emu/eden/src/branch/master/LICENSE.txt"><span style=" text-decoration: underline; color:#039be5;">License</span></a></p></body></html></string>
|
||||
<string><html><head/><body><p><a href="https://eden-emulator.github.io/"><span style=" text-decoration: underline; color:#039be5;">Website</span></a> | <a href="https://git.eden-emu.dev"><span style=" text-decoration: underline; color:#039be5;">Source Code</span></a> | <a href="https://git.eden-emu.dev/eden-emu/eden/activity/contributors"><span style=" text-decoration: underline; color:#039be5;">Contributors</span></a> | <a href="https://discord.gg/HstXbPch7X"><span style=" text-decoration: underline; color:#039be5;">Discord</span></a> | <a href="https://rvlt.gg/qKgFEAbH"><span style=" text-decoration: underline; color:#039be5;">Revolt</span></a> | <a href="https://nitter.poast.org/edenemuofficial"><span style=" text-decoration: underline; color:#039be5;">Twitter</span></a> | <a href="https://git.eden-emu.dev/eden-emu/eden/src/branch/master/LICENSE.txt"><span style=" text-decoration: underline; color:#039be5;">License</span></a></p></body></html></string>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<item row="0" column="0" colspan="4">
|
||||
<widget class="QLabel" name="label_1">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>Reads controller input from scripts in the same format as TAS-nx scripts.<br/>For a more detailed explanation, please consult the <a href="https://eden-emulator.github.io/help/feature/tas/"><span style=" text-decoration: underline; color:#039be5;">help page</span></a> on the Eden website.</p></body></html></string>
|
||||
<string><html><head/><body><p>Reads controller input from scripts in the same format as TAS-nx scripts.<br/>For a more detailed explanation, please consult the user handbook.</p></body></html></string>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user