Compare commits

..

13 Commits

Author SHA1 Message Date
CamilleLaVey f78581ae22 [shader_recompiler, spir-v] Coalesce storage emission 2026-01-11 21:21:43 -04:00
CamilleLaVey 39732ae6d5 [shader_recompiler, spir-v] Implement iteration limit in DefineWriteStorageCasLoop 2026-01-11 21:03:48 -04:00
lizzie 1cb8bcf531 [dynarmic] disable extra verbose debugging on release builds (#3293)
user doesn't need it and just wastes resources
Signed-off-by: lizzie <lizzie@eden-emu.dev>

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3293
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Reviewed-by: DraVee <dravee@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-01-11 05:32:24 +01:00
CamilleLaVey a27d35362d [vk, qcom] Memory, Samplers, BindingBuffers and UniformBufferAlignment set by hardware capabilities for QCOM. (#3280)
This PR adjust resources usage of Eden based on driver specification, improving stability and smarter resources usage, extends the amount of TotalPipelineWorkers on Android.

Co-authored-by: Caio Oliveira <caiooliveirafarias0@gmail.com>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3280
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: DraVee <dravee@eden-emu.dev>
Co-authored-by: CamilleLaVey <camillelavey99@gmail.com>
Co-committed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-01-11 05:31:23 +01:00
festivity a2236c1a43 [discord] show current game in main status instead of "Eden" (#2828)
"Eden" is still shown when user is idle

![image](/attachments/1386f581-db71-42db-b75a-01dba5ee95d7)

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2828
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: festivity <festivity@eden-emu.dev>
Co-committed-by: festivity <festivity@eden-emu.dev>
2026-01-10 23:42:49 +01:00
lizzie fafded39ef [externals] mbedtls chromeos fix arm_neon.h (#3290)
Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3290
Reviewed-by: DraVee <dravee@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-01-10 23:38:44 +01:00
DraVee 6b36c6deb6 Revert "[vk, spir-v] Adding decoration for NonWritable buffers if vertexPipelineStoresAndAtomics" (#3292)
(Hopefully last regression from #3074)

Fixes Super Mario RPG and rain on Pokemon Arceus

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3292
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: DraVee <dravee@eden-emu.dev>
Co-committed-by: DraVee <dravee@eden-emu.dev>
2026-01-10 19:33:12 +01:00
lizzie c21f92340b [core/hle/kernel] coalesce TLS from KernelCore to reduce query times (#3283)
- each time you reference TLS data the compiler generates calls to register _atexit() for them
- it also uses `mov %fs:%rax` or whatever, segmented moves are EXPENSIVE since they break pipeline
- occupies less TLS slots for windows :)

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3283
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-01-10 12:36:19 +01:00
lizzie ea29f169e6 [video_core] remove decoders.cpp template<A,B> spam (16 copies of the same function) (#3263)
16 for SwizzleImpl
another 16 for SwizzleSubrectImpl
another 2 for Swizzle

while yes, inlining can be good, I think this kind of templatery fuckery is a bit excessive

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3263
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-01-10 12:27:57 +01:00
lizzie 4d7b51d798 [host1x/vic] fix bad vectorization codegen in (windows) (#3285)
clang and gcc appropriatedly generate paths... but you msvc? you generate NOTHING

* This PR fixed regression introduced on #2856 and fixed stuttering/low fps on Megaman Battle Network Legacy Collection v2 and also Sonic Racing Crossworld.

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3285
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: DraVee <dravee@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-01-10 09:58:28 +01:00
lizzie cad77dfac6 [meta] remove UTF8-hyphen on readme, rewrite to be more neutral (#3273)
Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3273
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-01-09 23:24:57 +01:00
MrPurple666 87d4c67386 [turnip/android] Add environment variables settings for turnip drivers (#3205)
This PR brings a feature that has been needed for some time in the Android Switch emulation community: environment variables for Turnip/Freedreno drivers. These are available in PC emulators and can help fix some problems, especially the TU_DEBUG function, which can be set to gmem (thus allowing Adreno 710/720 users to run Turnip correctly), and noubwc, which fixes some problems for OneUI users.
This could also help us debug Turnip in a "better way" in the future.

Attached is a screenshot of a user, Ivan albio, using the gmem function on Adreno 710.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3205
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: DraVee <dravee@eden-emu.dev>
Co-authored-by: MrPurple666 <antoniosacramento666usa@gmail.com>
Co-committed-by: MrPurple666 <antoniosacramento666usa@gmail.com>
2026-01-09 23:22:59 +01:00
Caio Oliveira 1370f23675 [vk, settings] Disable VIDS on Android and Ungate Advanced EDS (#3281)
* the issue fixed by this PR (EDS Disabled) is the same as the one on 3096 (All EDS)

Signed-off-by: Caio Oliveira <caiooliveirafarias0@gmail.com>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3281
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Co-authored-by: Caio Oliveira <caiooliveirafarias0@gmail.com>
Co-committed-by: Caio Oliveira <caiooliveirafarias0@gmail.com>
2026-01-08 00:51:26 +01:00
102 changed files with 3089 additions and 3296 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ index 50f2a29..c60d9dc 100644
#define MBEDTLS_HAVE_NEON_INTRINSICS
-#elif defined(MBEDTLS_PLATFORM_IS_WINDOWS_ON_ARM64)
-#include <arm64_neon.h>
+#elif defined(__ANDROID__) || defined(__ARM_NEON)
+#elif (defined(__ANDROID__) && defined(__ARM_FP)) || defined(__ARM_NEON)
+#include <arm_neon.h>
#define MBEDTLS_HAVE_NEON_INTRINSICS
#endif
+2 -2
View File
@@ -15,8 +15,8 @@
<br>
</h1>
<h4 align="center"><b>Eden</b> is an open-source Nintendo Switch emulator, forked from the Yuzu emulator — started by former Citron developer Camille LaVey and the Eden team.
It is written in C++ with portability in mind, and we actively maintain builds for Windows, Linux and Android.
<h4 align="center"><b>Eden</b> is a free and opensource (FOSS) Switch 1 emulator, derived from Yuzu and Sudachi - started by developer Camille LaVey.
It's written in C++ with portability in mind, with builds for Windows, Linux, macOS, Android, FreeBSD and more.
</h4>
<p align="center">
+1 -1
View File
@@ -9,7 +9,7 @@ Version=1.0
Type=Application
Name=Eden
GenericName=Switch Emulator
Comment=Nintendo Switch video game console emulator
Comment=Multiplatform FOSS Switch 1 emulator written in C++, derived from Yuzu and Sudachi
Icon=dev.eden_emu.eden
TryExec=eden
Exec=eden %f
+1 -1
View File
@@ -16,7 +16,7 @@ SPDX-License-Identifier: CC0-1.0
<name>eden</name>
<summary>Nintendo Switch emulator</summary>
<description>
<p>Nintendo Switch video game console emulator</p>
<p>Multiplatform FOSS Switch 1 emulator written in C++, derived from Yuzu and Sudachi</p>
</description>
<categories>
<category>Game</category>
+5 -5
View File
@@ -71,9 +71,9 @@
"hash": "9697e80a7d5d9bcb3ce51051a9a24962fb90ca79d215f1f03ae6b58da8ba13a63b5dda1b4dde3d26ac6445029696b8ef2883f4e5a777b342bba01283ed293856"
},
"libadrenotools": {
"repo": "bylaws/libadrenotools",
"sha": "8fae8ce254",
"hash": "db4a74ce15559c75e01d1868a90701519b655d77f2a343bbee283a42f8332dc9046960fb022dc969f205e457348a3f99cb8be6e1cd91264d2ae1235294b9f9b2",
"repo": "eden-emulator/libadrenotools",
"sha": "8ba23b42d7",
"hash": "f6526620cb752876edc5ed4c0925d57b873a8218ee09ad10859ee476e9333259784f61c1dcc55a2bcba597352d18aff22cd2e4c1925ec2ae94074e09d7da2265",
"patches": [
"0001-linkerns-cpm.patch"
]
@@ -180,8 +180,8 @@
"discord-rpc": {
"package": "DiscordRPC",
"repo": "eden-emulator/discord-rpc",
"sha": "1cf7772bb6",
"hash": "9a6c35887dcacceb4ba1bf3141edb73b05b2abc719a8d81dad9cb9dd5b039ce203946787335d9d738af669c10cf2534638b645635a22096fc28dcae2475e0cbe",
"sha": "0d8b2d6a37",
"hash": "8213c43dcb0f7d479f5861091d111ed12fbdec1e62e6d729d65a4bc181d82f48a35d5fd3cd5c291f2393ac7c9681eabc6b76609755f55376284c8a8d67e148f3",
"find_args": "MODULE"
},
"simpleini": {
+1 -2
View File
@@ -7,8 +7,7 @@
include_directories(.)
# Dynarmic
if ((ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64))
set(DYNARMIC_IGNORE_ASSERTS ON)
if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64)
add_subdirectory(dynarmic)
add_library(dynarmic::dynarmic ALIAS dynarmic)
endif()
@@ -61,6 +61,11 @@ class YuzuApplication : Application() {
application = this
documentsTree = DocumentsTree()
DirectoryInitialization.start()
// Initialize Freedreno config BEFORE loading native library
// This ensures GPU driver environment variables are set before adrenotools initializes
GpuDriverHelper.initializeFreedrenoConfigEarly()
NativeLibrary.playTimeManagerInit()
GpuDriverHelper.initializeDriverParameters()
NativeInput.reloadInputDevices()
@@ -0,0 +1,58 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.adapters
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import org.yuzu.yuzu_emu.utils.FreedrenoPreset
import org.yuzu.yuzu_emu.databinding.ListItemFreedrenoPresetBinding
/**
* Adapter for displaying Freedreno preset configurations in a horizontal list.
*/
class FreedrenoPresetAdapter(
private val onPresetClicked: (FreedrenoPreset) -> Unit
) : ListAdapter<FreedrenoPreset, FreedrenoPresetAdapter.PresetViewHolder>(DiffCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PresetViewHolder {
val binding = ListItemFreedrenoPresetBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return PresetViewHolder(binding)
}
override fun onBindViewHolder(holder: PresetViewHolder, position: Int) {
holder.bind(getItem(position))
}
inner class PresetViewHolder(private val binding: ListItemFreedrenoPresetBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(preset: FreedrenoPreset) {
binding.presetButton.apply {
text = preset.name
setOnClickListener {
onPresetClicked(preset)
}
contentDescription = "${preset.name}: ${preset.description}"
}
}
}
companion object {
private val DiffCallback = object : DiffUtil.ItemCallback<FreedrenoPreset>() {
override fun areItemsTheSame(oldItem: FreedrenoPreset, newItem: FreedrenoPreset): Boolean =
oldItem.name == newItem.name
override fun areContentsTheSame(oldItem: FreedrenoPreset, newItem: FreedrenoPreset): Boolean =
oldItem == newItem
}
}
}
@@ -0,0 +1,61 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.adapters
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import org.yuzu.yuzu_emu.databinding.ListItemFreedrenoVariableBinding
import org.yuzu.yuzu_emu.fragments.FreedrenoVariable
import org.yuzu.yuzu_emu.utils.NativeFreedrenoConfig
/**
* Adapter for displaying currently set Freedreno environment variables in a list.
*/
class FreedrenoVariableAdapter(
private val context: Context,
private val onItemClicked: (FreedrenoVariable, () -> Unit) -> Unit
) : ListAdapter<FreedrenoVariable, FreedrenoVariableAdapter.VariableViewHolder>(DiffCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VariableViewHolder {
val binding = ListItemFreedrenoVariableBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return VariableViewHolder(binding)
}
override fun onBindViewHolder(holder: VariableViewHolder, position: Int) {
holder.bind(getItem(position))
}
inner class VariableViewHolder(private val binding: ListItemFreedrenoVariableBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(variable: FreedrenoVariable) {
binding.variableName.text = variable.name
binding.variableValue.text = variable.value
binding.buttonDelete.setOnClickListener {
onItemClicked(variable) {
NativeFreedrenoConfig.clearFreedrenoEnv(variable.name)
}
}
}
}
companion object {
private val DiffCallback = object : DiffUtil.ItemCallback<FreedrenoVariable>() {
override fun areItemsTheSame(oldItem: FreedrenoVariable, newItem: FreedrenoVariable): Boolean =
oldItem.name == newItem.name
override fun areContentsTheSame(oldItem: FreedrenoVariable, newItem: FreedrenoVariable): Boolean =
oldItem == newItem
}
}
}
@@ -27,6 +27,7 @@ object Settings {
SECTION_APP_SETTINGS(R.string.app_settings),
SECTION_CUSTOM_PATHS(R.string.preferences_custom_paths),
SECTION_DEBUG(R.string.preferences_debug),
SECTION_FREEDRENO(R.string.gpu_driver_settings),
SECTION_APPLETS(R.string.applets_menu);
}
@@ -29,6 +29,7 @@ import org.yuzu.yuzu_emu.databinding.ListItemSettingsHeaderBinding
import org.yuzu.yuzu_emu.features.input.NativeInput
import org.yuzu.yuzu_emu.features.input.model.AnalogDirection
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.model.view.*
import org.yuzu.yuzu_emu.features.settings.ui.viewholder.*
import org.yuzu.yuzu_emu.utils.ParamPackage
@@ -212,8 +213,15 @@ class SettingsAdapter(
}
fun onSubmenuClick(item: SubmenuSetting) {
val action = SettingsNavigationDirections.actionGlobalSettingsFragment(item.menuKey, null)
fragment.view?.findNavController()?.navigate(action)
// Check if this is the Freedreno Settings submenu
if (item.menuKey == Settings.MenuTag.SECTION_FREEDRENO) {
fragment.view?.findNavController()?.navigate(
R.id.action_settingsFragment_to_freedrenoSettingsFragment
)
} else {
val action = SettingsNavigationDirections.actionGlobalSettingsFragment(item.menuKey, null)
fragment.view?.findNavController()?.navigate(action)
}
}
fun onLaunchableClick(item: LaunchableSetting) {
@@ -27,6 +27,7 @@ import org.yuzu.yuzu_emu.features.settings.model.Settings.MenuTag
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
import org.yuzu.yuzu_emu.features.settings.model.view.*
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
import org.yuzu.yuzu_emu.utils.InputHandler
import org.yuzu.yuzu_emu.utils.NativeConfig
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
@@ -109,6 +110,7 @@ class SettingsFragmentPresenter(
MenuTag.SECTION_INPUT_PLAYER_EIGHT -> addInputPlayer(sl, 7)
MenuTag.SECTION_APP_SETTINGS -> addThemeSettings(sl)
MenuTag.SECTION_DEBUG -> addDebugSettings(sl)
MenuTag.SECTION_FREEDRENO -> addFreedrenoSettings(sl)
MenuTag.SECTION_APPLETS -> addAppletSettings(sl)
MenuTag.SECTION_CUSTOM_PATHS -> addCustomPathsSettings(sl)
}
@@ -181,6 +183,16 @@ class SettingsFragmentPresenter(
menuKey = MenuTag.SECTION_DEBUG
)
)
if (GpuDriverHelper.isAdrenoGpu() && !NativeConfig.isPerGameConfigLoaded()) {
add(
SubmenuSetting(
titleId = R.string.gpu_driver_settings,
descriptionId = R.string.freedreno_settings_title,
iconId = R.drawable.ic_graphics,
menuKey = MenuTag.SECTION_FREEDRENO
)
)
}
add(
SubmenuSetting(
titleId = R.string.applets_menu,
@@ -498,6 +510,11 @@ class SettingsFragmentPresenter(
}
}
private fun addFreedrenoSettings(sl: ArrayList<SettingsItem>) {
// No additional settings needed here - the SubmenuSetting handles navigation
// This method is kept for consistency with other menu sections
}
private fun addAppletSettings(sl: ArrayList<SettingsItem>) {
sl.apply {
add(IntSetting.SWKBD_APPLET.key)
@@ -45,7 +45,7 @@ class DriverFetcherFragment : Fragment() {
private val client = OkHttpClient()
private val gpuModel: String?
get() = GpuDriverHelper.getGpuModel()
get() = GpuDriverHelper.hookLibPath?.let { GpuDriverHelper.getGpuModel(hookLibPath = it) }
private val adrenoModel: Int
get() = parseAdrenoModel()
@@ -87,6 +87,7 @@ import org.yuzu.yuzu_emu.utils.GameIconUtils
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
import org.yuzu.yuzu_emu.utils.Log
import org.yuzu.yuzu_emu.utils.NativeConfig
import org.yuzu.yuzu_emu.utils.NativeFreedrenoConfig
import org.yuzu.yuzu_emu.utils.ViewUtils
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
import org.yuzu.yuzu_emu.utils.collect
@@ -303,6 +304,18 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
throw fallbackException
}
}
try {
if (GpuDriverHelper.isAdrenoGpu()) {
val programIdHex = game!!.programIdHex
if (NativeFreedrenoConfig.loadPerGameConfigWithGlobalFallback(programIdHex)) {
Log.info("[EmulationFragment] Loaded per-game Freedreno config for $programIdHex")
} else {
Log.info("[EmulationFragment] Using global Freedreno config for $programIdHex")
}
}
} catch (e: Exception) {
Log.warning("[EmulationFragment] Failed to load Freedreno config: ${e.message}")
}
emulationState = EmulationState(game!!.path) {
return@EmulationState driverViewModel.isInteractionAllowed.value
@@ -616,7 +629,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
Log.info("[EmulationFragment] Starting view setup for game: ${game?.title}")
gpuModel = GpuDriverHelper.getGpuModel().toString()
gpuModel = GpuDriverHelper.hookLibPath?.let { GpuDriverHelper.getGpuModel(hookLibPath = it).toString() } ?: "Unknown"
fwVersion = NativeLibrary.firmwareVersion()
updateQuickOverlayMenuEntry(BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean())
@@ -0,0 +1,204 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.transition.MaterialSharedAxis
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.adapters.FreedrenoPresetAdapter
import org.yuzu.yuzu_emu.adapters.FreedrenoVariableAdapter
import org.yuzu.yuzu_emu.databinding.FragmentFreedrenoSettingsBinding
import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.utils.NativeFreedrenoConfig
import org.yuzu.yuzu_emu.utils.FreedrenoPresets
class FreedrenoSettingsFragment : Fragment() {
private var _binding: FragmentFreedrenoSettingsBinding? = null
private val binding get() = _binding!!
private val args by navArgs<FreedrenoSettingsFragmentArgs>()
private val game: Game? get() = args.game
private val isPerGameConfig: Boolean get() = game != null
private lateinit var presetAdapter: FreedrenoPresetAdapter
private lateinit var settingsAdapter: FreedrenoVariableAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentFreedrenoSettingsBinding.inflate(layoutInflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
NativeFreedrenoConfig.setFreedrenoBasePath(requireContext().cacheDir.absolutePath)
NativeFreedrenoConfig.initializeFreedrenoConfig()
if (isPerGameConfig) {
NativeFreedrenoConfig.loadPerGameConfig(game!!.programIdHex)
} else {
NativeFreedrenoConfig.reloadFreedrenoConfig()
}
setupToolbar()
setupAdapters()
loadCurrentSettings()
setupButtonListeners()
setupWindowInsets()
}
private fun setupToolbar() {
binding.toolbarFreedreno.setNavigationOnClickListener {
requireActivity().onBackPressedDispatcher.onBackPressed()
}
if (isPerGameConfig) {
binding.toolbarFreedreno.title = getString(R.string.freedreno_per_game_title)
binding.toolbarFreedreno.subtitle = game!!.title
}
}
private fun setupAdapters() {
// Setup presets adapter (horizontal list)
presetAdapter = FreedrenoPresetAdapter { preset ->
applyPreset(preset)
}
binding.listFreedrenoPresets.apply {
adapter = presetAdapter
layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false)
}
presetAdapter.submitList(FreedrenoPresets.ALL_PRESETS)
// Setup current settings adapter (vertical list)
settingsAdapter = FreedrenoVariableAdapter(requireContext()) { variable, onDelete ->
onDelete()
loadCurrentSettings() // Refresh list after deletion
}
binding.listFreedrenoSettings.apply {
adapter = settingsAdapter
layoutManager = LinearLayoutManager(requireContext())
}
}
private fun loadCurrentSettings() {
// Load all currently set environment variables
val variables = mutableListOf<FreedrenoVariable>()
// Common variables to check
val commonVars = listOf(
"TU_DEBUG", "FD_MESA_DEBUG", "IR3_SHADER_DEBUG",
"FD_RD_DUMP", "FD_RD_DUMP_FRAMES", "FD_RD_DUMP_TESTNAME",
"TU_BREADCRUMBS"
)
for (varName in commonVars) {
if (NativeFreedrenoConfig.isFreedrenoEnvSet(varName)) {
val value = NativeFreedrenoConfig.getFreedrenoEnv(varName)
variables.add(FreedrenoVariable(varName, value))
}
}
settingsAdapter.submitList(variables)
}
private fun setupButtonListeners() {
binding.buttonAddVariable.setOnClickListener {
val varName = binding.variableNameInput.text.toString().trim()
val varValue = binding.variableValueInput.text.toString().trim()
if (varName.isEmpty()) {
showSnackbar(getString(R.string.freedreno_error_empty_name))
return@setOnClickListener
}
if (NativeFreedrenoConfig.setFreedrenoEnv(varName, varValue)) {
showSnackbar(getString(R.string.freedreno_variable_added, varName))
binding.variableNameInput.text?.clear()
binding.variableValueInput.text?.clear()
loadCurrentSettings()
} else {
showSnackbar(getString(R.string.freedreno_error_setting_variable))
}
}
binding.buttonClearAll.setOnClickListener {
NativeFreedrenoConfig.clearAllFreedrenoEnv()
showSnackbar(getString(R.string.freedreno_cleared_all))
loadCurrentSettings()
}
binding.buttonSave.setOnClickListener {
if (isPerGameConfig) {
NativeFreedrenoConfig.savePerGameConfig(game!!.programIdHex)
showSnackbar(getString(R.string.freedreno_per_game_saved))
} else {
NativeFreedrenoConfig.saveFreedrenoConfig()
showSnackbar(getString(R.string.freedreno_saved))
}
}
}
private fun applyPreset(preset: org.yuzu.yuzu_emu.utils.FreedrenoPreset) {
// Clear all first for consistency
NativeFreedrenoConfig.clearAllFreedrenoEnv()
// Apply all variables in the preset
for ((varName, varValue) in preset.variables) {
NativeFreedrenoConfig.setFreedrenoEnv(varName, varValue)
}
showSnackbar(getString(R.string.freedreno_preset_applied, preset.name))
loadCurrentSettings()
}
private fun setupWindowInsets() {
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, insets ->
val systemInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars())
binding.root.updatePadding(
left = systemInsets.left,
right = systemInsets.right,
bottom = systemInsets.bottom
)
insets
}
}
private fun showSnackbar(message: String) {
Snackbar.make(binding.root, message, Snackbar.LENGTH_SHORT).show()
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
/**
* Data class representing a Freedreno environment variable.
*/
data class FreedrenoVariable(
val name: String,
val value: String
)
@@ -325,6 +325,20 @@ class GamePropertiesFragment : Fragment() {
)
)
}
if (GpuDriverHelper.isAdrenoGpu()) {
add(
SubmenuProperty(
R.string.freedreno_per_game_title,
R.string.freedreno_per_game_description,
R.drawable.ic_graphics,
action = {
val action = GamePropertiesFragmentDirections
.actionPerGamePropertiesFragmentToFreedrenoSettingsFragment(args.game)
binding.root.findNavController().navigate(action)
}
)
)
}
if (!args.game.isHomebrew) {
add(
@@ -23,10 +23,16 @@ object GpuDriverHelper {
private const val META_JSON_FILENAME = "meta.json"
private var fileRedirectionPath: String? = null
var driverInstallationPath: String? = null
private var hookLibPath: String? = null
internal var hookLibPath: String? = null
val driverStoragePath get() = DirectoryInitialization.userDirectory!! + "/gpu_drivers/"
fun initializeFreedrenoConfigEarly() {
NativeFreedrenoConfig.setFreedrenoBasePath(YuzuApplication.appContext.cacheDir.absolutePath)
NativeFreedrenoConfig.initializeFreedrenoConfig()
NativeFreedrenoConfig.reloadFreedrenoConfig()
}
fun initializeDriverParameters() {
try {
// Initialize the file redirection directory.
@@ -40,11 +46,9 @@ object GpuDriverHelper {
throw RuntimeException(e)
}
// Initialize directories.
initializeDirectories()
// Initialize hook libraries directory.
hookLibPath = YuzuApplication.appContext.applicationInfo.nativeLibraryDir + "/"
NativeFreedrenoConfig.reloadFreedrenoConfig()
// Initialize GPU driver.
NativeLibrary.initializeGpuDriver(
@@ -211,9 +215,17 @@ object GpuDriverHelper {
external fun getGpuModel(
surface: Surface = Surface(SurfaceTexture(true)),
hookLibPath: String = GpuDriverHelper.hookLibPath!!
hookLibPath: String
): String?
fun isAdrenoGpu(): Boolean {
return try {
supportsCustomDriverLoading()
} catch (e: Exception) {
false
}
}
// Parse the custom driver metadata to retrieve the name.
val installedCustomDriverData: GpuDriverMetadata
get() = GpuDriverMetadata(File(driverInstallationPath + META_JSON_FILENAME))
@@ -0,0 +1,164 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.utils
/**
* Provides access to Freedreno/Turnip driver configuration through JNI bindings.
*
* This class allows Java/Kotlin code to configure Freedreno environment variables
* for the GPU driver (Turnip/Freedreno) that runs in the emulator on Android.
*
* Variables must be set BEFORE starting emulation for them to take effect.
*
* See https://docs.mesa3d.org/drivers/freedreno.html for documentation.
*/
object NativeFreedrenoConfig {
@Synchronized
external fun setFreedrenoBasePath(basePath: String)
@Synchronized
external fun initializeFreedrenoConfig()
@Synchronized
external fun saveFreedrenoConfig()
@Synchronized
external fun reloadFreedrenoConfig()
@Synchronized
external fun setFreedrenoEnv(varName: String, value: String): Boolean
@Synchronized
external fun getFreedrenoEnv(varName: String): String
@Synchronized
external fun isFreedrenoEnvSet(varName: String): Boolean
@Synchronized
external fun clearFreedrenoEnv(varName: String): Boolean
@Synchronized
external fun clearAllFreedrenoEnv()
@Synchronized
external fun getFreedrenoEnvSummary(): String
@Synchronized
external fun setCurrentProgramId(programId: String)
@Synchronized
external fun loadPerGameConfig(programId: String): Boolean
@Synchronized
external fun loadPerGameConfigWithGlobalFallback(programId: String): Boolean
@Synchronized
external fun savePerGameConfig(programId: String): Boolean
@Synchronized
external fun hasPerGameConfig(programId: String): Boolean
@Synchronized
external fun deletePerGameConfig(programId: String): Boolean
}
/**
* Data class representing a Freedreno preset configuration.
* Presets are commonly used debugging/profiling configurations.
*/
data class FreedrenoPreset(
val name: String, // Display name (e.g., "Debug - CPU Memory")
val description: String, // Description of what this preset does
val icon: String, // Icon identifier
val variables: Map<String, String> // Map of env vars to set
)
/**
* Predefined Freedreno presets for quick configuration.
*/
object FreedrenoPresets {
val DEBUG_CPU_MEMORY = FreedrenoPreset(
name = "Debug - CPU Memory",
description = "Use CPU memory (slower but more stable)",
icon = "ic_debug_cpu",
variables = mapOf(
"TU_DEBUG" to "sysmem"
)
)
val DEBUG_UBWC_DISABLED = FreedrenoPreset(
name = "Debug - No UBWC",
description = "Disable UBWC compression for debugging",
icon = "ic_debug_ubwc",
variables = mapOf(
"TU_DEBUG" to "noubwc"
)
)
val DEBUG_NO_BINNING = FreedrenoPreset(
name = "Debug - No Binning",
description = "Disable binning optimization",
icon = "ic_debug_bin",
variables = mapOf(
"TU_DEBUG" to "nobin"
)
)
val CAPTURE_RENDERPASS = FreedrenoPreset(
name = "Capture - Renderpass",
description = "Capture command stream data for debugging",
icon = "ic_capture",
variables = mapOf(
"FD_RD_DUMP" to "enable"
)
)
val CAPTURE_FRAMES = FreedrenoPreset(
name = "Capture - First 100 Frames",
description = "Capture command stream for first 100 frames only",
icon = "ic_capture",
variables = mapOf(
"FD_RD_DUMP" to "enable",
"FD_RD_DUMP_FRAMES" to "0-100"
)
)
val SHADER_DEBUG = FreedrenoPreset(
name = "Shader Debug",
description = "Enable IR3 shader compiler debugging",
icon = "ic_shader",
variables = mapOf(
"IR3_SHADER_DEBUG" to "nouboopt,spillall"
)
)
val GPU_HANG_TRACE = FreedrenoPreset(
name = "GPU Hang Trace",
description = "Trace GPU progress for debugging hangs",
icon = "ic_hang_trace",
variables = mapOf(
"TU_BREADCRUMBS" to "1"
)
)
val PERFORMANCE_DEFAULT = FreedrenoPreset(
name = "Performance - Default",
description = "Clear all debug options for performance",
icon = "ic_performance",
variables = emptyMap() // Clears all when applied
)
val ALL_PRESETS = listOf(
DEBUG_CPU_MEMORY,
DEBUG_UBWC_DISABLED,
DEBUG_NO_BINNING,
CAPTURE_RENDERPASS,
CAPTURE_FRAMES,
SHADER_DEBUG,
GPU_HANG_TRACE,
PERFORMANCE_DEFAULT
)
}
@@ -10,6 +10,7 @@ add_library(yuzu-android SHARED
native.cpp
native.h
native_config.cpp
native_freedreno.cpp
android_settings.cpp
game_metadata.cpp
native_log.cpp
+18 -1
View File
@@ -730,11 +730,28 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_doesUpdateMatchProgram(JNIEnv* en
return false;
}
void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeGpuDriver(JNIEnv* env, jclass clazz,
void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeGpuDriver(JNIEnv* env,
[[maybe_unused]] jclass clazz,
jstring hook_lib_dir,
jstring custom_driver_dir,
jstring custom_driver_name,
jstring file_redirect_dir) {
// Log active Freedreno environment variables
const char* tu_debug = getenv("TU_DEBUG");
const char* fd_debug = getenv("FD_MESA_DEBUG");
const char* ir3_debug = getenv("IR3_SHADER_DEBUG");
const char* fd_rd_dump = getenv("FD_RD_DUMP");
const char* tu_breadcrumbs = getenv("TU_BREADCRUMBS");
if (tu_debug || fd_debug || ir3_debug || fd_rd_dump || tu_breadcrumbs) {
LOG_INFO(Frontend, "[Freedreno] Initializing GPU driver with configuration:");
if (tu_debug) LOG_INFO(Frontend, "[Freedreno] TU_DEBUG={}", tu_debug);
if (fd_debug) LOG_INFO(Frontend, "[Freedreno] FD_MESA_DEBUG={}", fd_debug);
if (ir3_debug) LOG_INFO(Frontend, "[Freedreno] IR3_SHADER_DEBUG={}", ir3_debug);
if (fd_rd_dump) LOG_INFO(Frontend, "[Freedreno] FD_RD_DUMP={}", fd_rd_dump);
if (tu_breadcrumbs) LOG_INFO(Frontend, "[Freedreno] TU_BREADCRUMBS={}", tu_breadcrumbs);
}
EmulationSession::GetInstance().InitializeGpuDriver(
Common::Android::GetJString(env, hook_lib_dir),
Common::Android::GetJString(env, custom_driver_dir),
@@ -0,0 +1,477 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/**
* @file native_freedreno.cpp
* @brief JNI bindings for Freedreno/Turnip GPU driver configuration.
*
* Provides runtime configuration of Mesa Freedreno environment variables
* for the Turnip Vulkan driver on Adreno GPUs.
*
* @see https://docs.mesa3d.org/drivers/freedreno.html
*/
#include <algorithm>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <map>
#include <memory>
#include <string>
#include <sys/stat.h>
#include <jni.h>
#include "common/android/android_common.h"
#include "common/logging/log.h"
#include "native.h"
namespace {
struct FreedrenoConfig {
std::map<std::string, std::string> env_vars;
std::string config_file_path;
};
std::unique_ptr<FreedrenoConfig> g_config;
std::string g_base_path;
std::string g_current_program_id;
constexpr const char* kConfigFileName = ".freedreno.conf";
constexpr const char* kPerGameConfigDir = "freedreno_games";
void LogActiveVariables() {
if (!g_config || g_config->env_vars.empty()) {
return;
}
for (const auto& [key, value] : g_config->env_vars) {
LOG_INFO(Frontend, "[Freedreno] {}={}", key, value);
}
}
bool ApplyEnvironmentVariable(const std::string& key, const std::string& value) {
if (setenv(key.c_str(), value.c_str(), 1) != 0) {
LOG_ERROR(Frontend, "[Freedreno] Failed to set {}={} (errno: {})", key, value, errno);
return false;
}
return true;
}
void ClearAllEnvironmentVariables() {
if (!g_config) return;
for (const auto& [key, value] : g_config->env_vars) {
unsetenv(key.c_str());
}
g_config->env_vars.clear();
}
std::string GetConfigPath() {
return g_base_path + "/" + kConfigFileName;
}
std::string GetPerGameConfigPath(const std::string& program_id) {
return g_base_path + "/" + kPerGameConfigDir + "/" + program_id + ".conf";
}
void EnsurePerGameConfigDir() {
std::string dir_path = g_base_path + "/" + kPerGameConfigDir;
mkdir(dir_path.c_str(), 0755);
}
bool LoadConfigFromFile(const std::string& config_path) {
if (!g_config) return false;
FILE* file = fopen(config_path.c_str(), "r");
if (!file) {
return false;
}
char line[512];
int count = 0;
while (fgets(line, sizeof(line), file)) {
size_t len = strlen(line);
if (len > 0 && line[len - 1] == '\n') {
line[len - 1] = '\0';
len--;
}
if (len == 0 || line[0] == '#') {
continue;
}
const char* eq = strchr(line, '=');
if (!eq) {
continue;
}
std::string key(line, eq - line);
std::string value(eq + 1);
g_config->env_vars[key] = value;
ApplyEnvironmentVariable(key, value);
count++;
}
fclose(file);
return count > 0;
}
bool SaveConfigToFile(const std::string& config_path) {
if (!g_config) return false;
FILE* file = fopen(config_path.c_str(), "w");
if (!file) {
LOG_ERROR(Frontend, "[Freedreno] Failed to open {} for writing", config_path);
return false;
}
fprintf(file, "# Freedreno/Turnip Configuration\n");
fprintf(file, "# Auto-generated by Eden Emulator\n\n");
for (const auto& [key, value] : g_config->env_vars) {
fprintf(file, "%s=%s\n", key.c_str(), value.c_str());
}
fclose(file);
return true;
}
} // anonymous namespace
extern "C" {
JNIEXPORT void JNICALL
Java_org_yuzu_yuzu_1emu_utils_NativeFreedrenoConfig_setFreedrenoBasePath(
JNIEnv* env, [[maybe_unused]] jobject obj, jstring jbasePath) {
g_base_path = Common::Android::GetJString(env, jbasePath);
}
JNIEXPORT void JNICALL
Java_org_yuzu_yuzu_1emu_utils_NativeFreedrenoConfig_initializeFreedrenoConfig(
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj) {
if (!g_config) {
g_config = std::make_unique<FreedrenoConfig>();
LOG_INFO(Frontend, "[Freedreno] Configuration system initialized");
}
}
JNIEXPORT void JNICALL
Java_org_yuzu_yuzu_1emu_utils_NativeFreedrenoConfig_saveFreedrenoConfig(
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj) {
if (!g_config) {
LOG_WARNING(Frontend, "[Freedreno] Cannot save: not initialized");
return;
}
const std::string config_path = GetConfigPath();
FILE* file = fopen(config_path.c_str(), "w");
if (!file) {
LOG_ERROR(Frontend, "[Freedreno] Failed to open {} for writing", config_path);
return;
}
fprintf(file, "# Freedreno/Turnip Configuration\n");
fprintf(file, "# Auto-generated by Eden Emulator\n\n");
for (const auto& [key, value] : g_config->env_vars) {
fprintf(file, "%s=%s\n", key.c_str(), value.c_str());
}
fclose(file);
g_config->config_file_path = config_path;
LOG_INFO(Frontend, "[Freedreno] Saved {} variables to {}",
g_config->env_vars.size(), config_path);
}
JNIEXPORT void JNICALL
Java_org_yuzu_yuzu_1emu_utils_NativeFreedrenoConfig_reloadFreedrenoConfig(
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj) {
if (!g_config) {
LOG_WARNING(Frontend, "[Freedreno] Cannot reload: not initialized");
return;
}
const std::string config_path = GetConfigPath();
g_config->env_vars.clear();
FILE* file = fopen(config_path.c_str(), "r");
if (!file) {
LOG_DEBUG(Frontend, "[Freedreno] No config file found at {}", config_path);
return;
}
char line[512];
while (fgets(line, sizeof(line), file)) {
// Remove trailing newline
size_t len = strlen(line);
if (len > 0 && line[len - 1] == '\n') {
line[len - 1] = '\0';
len--;
}
// Skip empty lines and comments
if (len == 0 || line[0] == '#') {
continue;
}
// Parse key=value
const char* eq = strchr(line, '=');
if (!eq) {
continue;
}
std::string key(line, eq - line);
std::string value(eq + 1);
g_config->env_vars[key] = value;
ApplyEnvironmentVariable(key, value);
}
fclose(file);
g_config->config_file_path = config_path;
if (!g_config->env_vars.empty()) {
LOG_INFO(Frontend, "[Freedreno] Loaded {} variables:", g_config->env_vars.size());
LogActiveVariables();
}
}
JNIEXPORT jboolean JNICALL
Java_org_yuzu_yuzu_1emu_utils_NativeFreedrenoConfig_setFreedrenoEnv(
JNIEnv* env, [[maybe_unused]] jobject obj, jstring jvarName, jstring jvalue) {
if (!g_config) {
return JNI_FALSE;
}
auto var_name = Common::Android::GetJString(env, jvarName);
auto value = Common::Android::GetJString(env, jvalue);
if (var_name.empty()) {
return JNI_FALSE;
}
g_config->env_vars[var_name] = value;
if (!ApplyEnvironmentVariable(var_name, value)) {
return JNI_FALSE;
}
LOG_INFO(Frontend, "[Freedreno] Set {}={}", var_name, value);
return JNI_TRUE;
}
JNIEXPORT jstring JNICALL
Java_org_yuzu_yuzu_1emu_utils_NativeFreedrenoConfig_getFreedrenoEnv(
JNIEnv* env, [[maybe_unused]] jobject obj, jstring jvarName) {
if (!g_config) {
return env->NewStringUTF("");
}
auto var_name = Common::Android::GetJString(env, jvarName);
auto it = g_config->env_vars.find(var_name);
if (it != g_config->env_vars.end()) {
return env->NewStringUTF(it->second.c_str());
}
return env->NewStringUTF("");
}
JNIEXPORT jboolean JNICALL
Java_org_yuzu_yuzu_1emu_utils_NativeFreedrenoConfig_isFreedrenoEnvSet(
JNIEnv* env, [[maybe_unused]] jobject obj, jstring jvarName) {
if (!g_config) {
return JNI_FALSE;
}
auto var_name = Common::Android::GetJString(env, jvarName);
auto it = g_config->env_vars.find(var_name);
return (it != g_config->env_vars.end() && !it->second.empty()) ? JNI_TRUE : JNI_FALSE;
}
JNIEXPORT jboolean JNICALL
Java_org_yuzu_yuzu_1emu_utils_NativeFreedrenoConfig_clearFreedrenoEnv(
JNIEnv* env, [[maybe_unused]] jobject obj, jstring jvarName) {
if (!g_config) {
return JNI_FALSE;
}
auto var_name = Common::Android::GetJString(env, jvarName);
auto it = g_config->env_vars.find(var_name);
if (it != g_config->env_vars.end()) {
g_config->env_vars.erase(it);
unsetenv(var_name.c_str());
LOG_INFO(Frontend, "[Freedreno] Cleared {}", var_name);
return JNI_TRUE;
}
return JNI_FALSE;
}
JNIEXPORT void JNICALL
Java_org_yuzu_yuzu_1emu_utils_NativeFreedrenoConfig_clearAllFreedrenoEnv(
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj) {
if (!g_config) {
return;
}
for (const auto& [key, value] : g_config->env_vars) {
unsetenv(key.c_str());
}
size_t count = g_config->env_vars.size();
g_config->env_vars.clear();
if (count > 0) {
LOG_INFO(Frontend, "[Freedreno] Cleared all {} variables", count);
}
}
JNIEXPORT jstring JNICALL
Java_org_yuzu_yuzu_1emu_utils_NativeFreedrenoConfig_getFreedrenoEnvSummary(
JNIEnv* env, [[maybe_unused]] jobject obj) {
if (!g_config || g_config->env_vars.empty()) {
return env->NewStringUTF("");
}
std::string summary;
for (const auto& [key, value] : g_config->env_vars) {
if (!summary.empty()) {
summary += ",";
}
summary += key + "=" + value;
}
return env->NewStringUTF(summary.c_str());
}
JNIEXPORT void JNICALL
Java_org_yuzu_yuzu_1emu_utils_NativeFreedrenoConfig_setCurrentProgramId(
JNIEnv* env, [[maybe_unused]] jobject obj, jstring jprogramId) {
g_current_program_id = Common::Android::GetJString(env, jprogramId);
}
JNIEXPORT jboolean JNICALL
Java_org_yuzu_yuzu_1emu_utils_NativeFreedrenoConfig_loadPerGameConfig(
JNIEnv* env, [[maybe_unused]] jobject obj, jstring jprogramId) {
if (!g_config) {
return JNI_FALSE;
}
auto program_id = Common::Android::GetJString(env, jprogramId);
if (program_id.empty()) {
return JNI_FALSE;
}
// Clear current environment variables first
ClearAllEnvironmentVariables();
g_current_program_id = program_id;
// Try to load per-game config - do NOT fall back to global
// Per-game config should start empty if no config exists yet
std::string per_game_path = GetPerGameConfigPath(program_id);
if (LoadConfigFromFile(per_game_path)) {
LOG_INFO(Frontend, "[Freedreno] Loaded per-game config for {}", program_id);
LogActiveVariables();
return JNI_TRUE;
}
// No per-game config exists - start with empty config
LOG_INFO(Frontend, "[Freedreno] No per-game config for {}, starting empty", program_id);
return JNI_FALSE;
}
JNIEXPORT jboolean JNICALL
Java_org_yuzu_yuzu_1emu_utils_NativeFreedrenoConfig_loadPerGameConfigWithGlobalFallback(
JNIEnv* env, [[maybe_unused]] jobject obj, jstring jprogramId) {
if (!g_config) {
return JNI_FALSE;
}
auto program_id = Common::Android::GetJString(env, jprogramId);
if (program_id.empty()) {
return JNI_FALSE;
}
// Clear current environment variables first
ClearAllEnvironmentVariables();
g_current_program_id = program_id;
// Try to load per-game config first
std::string per_game_path = GetPerGameConfigPath(program_id);
if (LoadConfigFromFile(per_game_path)) {
LOG_INFO(Frontend, "[Freedreno] Loaded per-game config for {}", program_id);
LogActiveVariables();
return JNI_TRUE;
}
// Fall back to global config for emulation
std::string global_path = GetConfigPath();
if (LoadConfigFromFile(global_path)) {
LOG_INFO(Frontend, "[Freedreno] No per-game config for {}, using global for emulation", program_id);
LogActiveVariables();
}
return JNI_FALSE;
}
JNIEXPORT jboolean JNICALL
Java_org_yuzu_yuzu_1emu_utils_NativeFreedrenoConfig_savePerGameConfig(
JNIEnv* env, [[maybe_unused]] jobject obj, jstring jprogramId) {
if (!g_config) {
return JNI_FALSE;
}
auto program_id = Common::Android::GetJString(env, jprogramId);
if (program_id.empty()) {
return JNI_FALSE;
}
EnsurePerGameConfigDir();
std::string config_path = GetPerGameConfigPath(program_id);
if (SaveConfigToFile(config_path)) {
LOG_INFO(Frontend, "[Freedreno] Saved per-game config for {}", program_id);
return JNI_TRUE;
}
return JNI_FALSE;
}
JNIEXPORT jboolean JNICALL
Java_org_yuzu_yuzu_1emu_utils_NativeFreedrenoConfig_hasPerGameConfig(
JNIEnv* env, [[maybe_unused]] jobject obj, jstring jprogramId) {
auto program_id = Common::Android::GetJString(env, jprogramId);
if (program_id.empty()) {
return JNI_FALSE;
}
std::string config_path = GetPerGameConfigPath(program_id);
FILE* file = fopen(config_path.c_str(), "r");
if (file) {
fclose(file);
return JNI_TRUE;
}
return JNI_FALSE;
}
JNIEXPORT jboolean JNICALL
Java_org_yuzu_yuzu_1emu_utils_NativeFreedrenoConfig_deletePerGameConfig(
JNIEnv* env, [[maybe_unused]] jobject obj, jstring jprogramId) {
auto program_id = Common::Android::GetJString(env, jprogramId);
if (program_id.empty()) {
return JNI_FALSE;
}
std::string config_path = GetPerGameConfigPath(program_id);
if (remove(config_path.c_str()) == 0) {
LOG_INFO(Frontend, "[Freedreno] Deleted per-game config for {}", program_id);
return JNI_TRUE;
}
return JNI_FALSE;
}
} // extern "C"
@@ -0,0 +1,196 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/coordinator_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorSurface">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar_freedreno"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:touchscreenBlocksFocus="false"
app:elevation="0dp">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/toolbar_freedreno_layout"
style="?attr/collapsingToolbarLayoutMediumStyle"
android:layout_width="match_parent"
android:layout_height="?attr/collapsingToolbarLayoutMediumSize"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
app:contentScrim="?attr/colorSurface"
app:scrimVisibleHeightTrigger="100dp">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar_freedreno"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:touchscreenBlocksFocus="false"
app:layout_collapseMode="pin"
app:navigationIcon="@drawable/ic_back"
app:title="@string/gpu_driver_settings" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingTop="8dp"
android:paddingBottom="16dp">
<!-- Presets Section -->
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="8dp"
android:text="@string/freedreno_presets"
android:textAppearance="?attr/textAppearanceTitleMedium" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list_freedreno_presets"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:scrollbars="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
<!-- Current Settings Section -->
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginBottom="8dp"
android:text="@string/freedreno_current_settings"
android:textAppearance="?attr/textAppearanceTitleMedium" />
<!-- Settings List -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list_freedreno_settings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
<!-- Debug Section -->
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginBottom="8dp"
android:text="@string/freedreno_debug"
android:textAppearance="?attr/textAppearanceTitleMedium" />
<!-- Manual Variable Input -->
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/variable_name_input_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:hint="@string/freedreno_var_name">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/variable_name_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/variable_value_input_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:hint="@string/freedreno_var_value">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/variable_value_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text" />
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/button_add_variable"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="@string/freedreno_add_variable" />
<!-- Action Buttons -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="16dp"
android:spacing="8dp">
<Button
android:id="@+id/button_clear_all"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/freedreno_clear_all"
style="?attr/materialButtonOutlinedStyle" />
<Button
android:id="@+id/button_save"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/save" />
</LinearLayout>
<!-- Info Section -->
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
app:cardElevation="0dp"
app:strokeWidth="1dp"
app:strokeColor="?attr/colorOutline">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="12dp">
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/freedreno_info_title"
android:textAppearance="?attr/textAppearanceTitleSmall" />
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/freedreno_info_description"
android:textAppearance="?attr/textAppearanceBodySmall"
android:textColor="?attr/colorOnSurfaceVariant" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="4dp"
android:paddingEnd="4dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/preset_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="100dp"
android:text="Preset"
style="?attr/materialButtonOutlinedStyle" />
</LinearLayout>
@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
app:cardElevation="0dp"
app:strokeWidth="1dp"
app:strokeColor="?attr/colorOutline">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="12dp"
android:gravity="center_vertical">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/variable_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="VARIABLE_NAME"
android:textAppearance="?attr/textAppearanceTitleSmall"
android:textColor="?attr/colorOnSurface" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/variable_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="variable_value"
android:textAppearance="?attr/textAppearanceBodySmall"
android:textColor="?attr/colorOnSurfaceVariant"
android:maxLines="1"
android:ellipsize="end" />
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/button_delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="@string/delete"
android:minWidth="0dp"
android:minHeight="36dp"
android:paddingStart="8dp"
android:paddingEnd="8dp"
style="?attr/materialButtonOutlinedStyle" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
@@ -148,6 +148,9 @@
<action
android:id="@+id/action_perGamePropertiesFragment_to_driverManagerFragment"
app:destination="@id/driverManagerFragment" />
<action
android:id="@+id/action_perGamePropertiesFragment_to_freedrenoSettingsFragment"
app:destination="@id/freedrenoSettingsFragment" />
</fragment>
<action
android:id="@+id/action_global_perGamePropertiesFragment"
@@ -173,5 +176,15 @@
android:name="org.yuzu.yuzu_emu.fragments.DriverFetcherFragment"
android:label="fragment_driver_fetcher"
tools:layout="@layout/fragment_driver_fetcher" />
<fragment
android:id="@+id/freedrenoSettingsFragment"
android:name="org.yuzu.yuzu_emu.fragments.FreedrenoSettingsFragment"
android:label="@string/freedreno_settings_title">
<argument
android:name="game"
app:argType="org.yuzu.yuzu_emu.model.Game"
app:nullable="true"
android:defaultValue="@null" />
</fragment>
</navigation>
@@ -29,4 +29,19 @@
android:name="org.yuzu.yuzu_emu.features.settings.ui.SettingsSearchFragment"
android:label="SettingsSearchFragment" />
<fragment
android:id="@+id/freedrenoSettingsFragment"
android:name="org.yuzu.yuzu_emu.fragments.FreedrenoSettingsFragment"
android:label="@string/freedreno_settings_title">
<argument
android:name="game"
app:argType="org.yuzu.yuzu_emu.model.Game"
app:nullable="true"
android:defaultValue="@null" />
</fragment>
<action
android:id="@+id/action_settingsFragment_to_freedrenoSettingsFragment"
app:destination="@id/freedrenoSettingsFragment" />
</navigation>
@@ -1051,6 +1051,28 @@
<string name="cpu_accuracy_paranoid">Paranoid</string>
<string name="cpu_accuracy_debugging">Debugging</string>
<!-- Freedreno Settings -->
<string name="freedreno_settings_title">Freedreno Settings</string>
<string name="gpu_driver_settings">GPU Driver Settings</string>
<string name="freedreno_presets">Quick Presets</string>
<string name="freedreno_current_settings">Current Settings</string>
<string name="freedreno_debug">Advanced Settings</string>
<string name="freedreno_var_name">Variable Name (e.g., TU_DEBUG)</string>
<string name="freedreno_var_value">Variable Value</string>
<string name="freedreno_add_variable">Add Variable</string>
<string name="freedreno_clear_all">Clear All</string>
<string name="freedreno_saved">Freedreno configuration saved</string>
<string name="freedreno_cleared_all">All Freedreno variables cleared</string>
<string name="freedreno_variable_added">Variable %1$s added</string>
<string name="freedreno_preset_applied">Preset \'%1$s\' applied</string>
<string name="freedreno_error_empty_name">Variable name cannot be empty</string>
<string name="freedreno_error_setting_variable">Failed to set variable</string>
<string name="freedreno_info_title">About Freedreno Configuration</string>
<string name="freedreno_info_description">Configure Freedreno/Turnip GPU driver options for debugging, profiling, and performance optimization. Changes are saved automatically. See https://docs.mesa3d.org/drivers/freedreno.html for detailed documentation.</string>
<string name="freedreno_per_game_title">Freedreno Settings</string>
<string name="freedreno_per_game_description">Configure GPU driver settings for this game</string>
<string name="freedreno_per_game_saved">Freedreno configuration saved</string>
<!-- Gamepad Buttons -->
<string name="gamepad_d_pad">D-pad</string>
<string name="gamepad_left_stick">Left stick</string>
+1 -4
View File
@@ -578,10 +578,7 @@ public:
#endif
int flags = (fd > 0 ? MAP_SHARED : MAP_PRIVATE) | MAP_FIXED;
void* ret = mmap(virtual_base + virtual_offset, length, prot_flags, flags, fd, host_offset);
ASSERT_MSG(ret != MAP_FAILED,
"mmap(virt_off=0x{:X}, host_off=0x{:X}, len=0x{:X}, virt_size=0x{:X}, backing_size=0x{:X}, perms=0x{:X}) failed: {}",
virtual_offset, host_offset, length, virtual_size, backing_size,
static_cast<u32>(perms), strerror(errno));
ASSERT_MSG(ret != MAP_FAILED, "mmap: {}", strerror(errno));
}
void Unmap(size_t virtual_offset, size_t length) {
+7 -1
View File
@@ -536,7 +536,13 @@ struct Values {
Category::RendererExtensions,
Specialization::Scalar};
SwitchableSetting<bool> vertex_input_dynamic_state{linkage, false, "vertex_input_dynamic_state", Category::RendererExtensions};
SwitchableSetting<bool> vertex_input_dynamic_state{linkage,
#if defined (ANDROID)
false,
#else
true,
#endif
"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};
-11
View File
@@ -130,17 +130,6 @@ public:
ResetStorageBit(id.index);
}
[[nodiscard]] bool Contains(SlotId id) const noexcept {
if (!id) {
return false;
}
const size_t word = id.index / 64;
if (word >= stored_bitset.size()) {
return false;
}
return ((stored_bitset[word] >> (id.index % 64)) & 1) != 0;
}
[[nodiscard]] Iterator begin() noexcept {
const auto it = std::ranges::find_if(stored_bitset, [](u64 value) { return value != 0; });
if (it == stored_bitset.end()) {
+77 -82
View File
@@ -50,13 +50,38 @@
namespace Kernel {
// Can only be used by a single implementation PER THREAD
struct ThreadLocalData {
std::optional<KThread> raw_thread;
KThread* current_thread = nullptr;
KThread* thread = nullptr;
u8 host_thread_id = UINT8_MAX;
bool is_phantom_mode_for_singlecore = false;
bool lock = false;
};
struct KernelCore::Impl {
static constexpr size_t ApplicationMemoryBlockSlabHeapSize = 20000;
static constexpr size_t SystemMemoryBlockSlabHeapSize = 10000;
static constexpr size_t BlockInfoSlabHeapSize = 4000;
static constexpr size_t ReservedDynamicPageCount = 64;
// Be very careful when handling TLS data
// We do not want to concern ourselves with the appropriate way to manage them
// across **all** threads, we just need these for a few spare threads (+host/guest threads)
//
// Do not just read straight from here, use a reference beforehand, the cost of reading
// from TLS is greater than the cost of reading normal variables.
// But account that this Impl() is instanced once per program, and shared across threads
// so we can't use a reference for now.
//
// And we have the guarantee that the data won't move out of the way so we can safely
// take a reference to it. This isn't always universally true but this is "global" data
// so it will be statically given a TLS slot anyways.
static inline thread_local ThreadLocalData tls_data = {};
explicit Impl(Core::System& system_, KernelCore& kernel_) : system{system_} {}
explicit Impl(Core::System& system_, KernelCore& kernel_) : system{system_} {
tls_data.lock = true;
}
void SetMulticore(bool is_multi) {
is_multicore = is_multi;
@@ -69,8 +94,6 @@ struct KernelCore::Impl {
global_object_list_container = std::make_unique<KAutoObjectWithListContainer>(kernel);
global_scheduler_context = std::make_unique<Kernel::GlobalSchedulerContext>(kernel);
is_phantom_mode_for_singlecore = false;
// Derive the initial memory layout from the emulated board
Init::InitializeSlabResourceCounts(kernel);
DeriveInitialMemoryLayout();
@@ -88,9 +111,7 @@ struct KernelCore::Impl {
{
const auto& pt_heap_region = memory_layout->GetPageTableHeapRegion();
ASSERT(pt_heap_region.GetEndAddress() != 0);
InitializeResourceManagers(kernel, pt_heap_region.GetAddress(),
pt_heap_region.GetSize());
InitializeResourceManagers(kernel, pt_heap_region.GetAddress(), pt_heap_region.GetSize());
}
InitializeHackSharedMemory(kernel);
@@ -222,17 +243,11 @@ struct KernelCore::Impl {
const auto kernel_size{sizes.second};
// If setting the default system values fails, then something seriously wrong has occurred.
ASSERT(
system_resource_limit->SetLimitValue(LimitableResource::PhysicalMemoryMax, total_size)
.IsSuccess());
ASSERT(system_resource_limit->SetLimitValue(LimitableResource::ThreadCountMax, 800)
.IsSuccess());
ASSERT(system_resource_limit->SetLimitValue(LimitableResource::EventCountMax, 900)
.IsSuccess());
ASSERT(system_resource_limit->SetLimitValue(LimitableResource::TransferMemoryCountMax, 200)
.IsSuccess());
ASSERT(system_resource_limit->SetLimitValue(LimitableResource::SessionCountMax, 1133)
.IsSuccess());
ASSERT(system_resource_limit->SetLimitValue(LimitableResource::PhysicalMemoryMax, total_size).IsSuccess());
ASSERT(system_resource_limit->SetLimitValue(LimitableResource::ThreadCountMax, 800).IsSuccess());
ASSERT(system_resource_limit->SetLimitValue(LimitableResource::EventCountMax, 900).IsSuccess());
ASSERT(system_resource_limit->SetLimitValue(LimitableResource::TransferMemoryCountMax, 200).IsSuccess());
ASSERT(system_resource_limit->SetLimitValue(LimitableResource::SessionCountMax, 1133).IsSuccess());
system_resource_limit->Reserve(LimitableResource::PhysicalMemoryMax, kernel_size);
// Reserve secure applet memory, introduced in firmware 5.0.0
@@ -242,16 +257,13 @@ struct KernelCore::Impl {
}
void InitializePreemption(KernelCore& kernel) {
preemption_event = Core::Timing::CreateEvent(
"PreemptionCallback",
[this, &kernel](s64 time,
std::chrono::nanoseconds) -> std::optional<std::chrono::nanoseconds> {
{
KScopedSchedulerLock lock(kernel);
global_scheduler_context->PreemptThreads();
}
return std::nullopt;
});
preemption_event = Core::Timing::CreateEvent("PreemptionCallback", [this, &kernel](s64 time, std::chrono::nanoseconds) -> std::optional<std::chrono::nanoseconds> {
{
KScopedSchedulerLock lock(kernel);
global_scheduler_context->PreemptThreads();
}
return std::nullopt;
});
const auto time_interval = std::chrono::nanoseconds{std::chrono::milliseconds(10)};
system.CoreTiming().ScheduleLoopingEvent(time_interval, time_interval, preemption_event);
@@ -263,15 +275,13 @@ struct KernelCore::Impl {
ASSERT(Common::IsAligned(size, PageSize));
// Ensure that we have space for our reference counts.
const size_t rc_size =
Common::AlignUp(KPageTableSlabHeap::CalculateReferenceCountSize(size), PageSize);
const size_t rc_size = Common::AlignUp(KPageTableSlabHeap::CalculateReferenceCountSize(size), PageSize);
ASSERT(rc_size < size);
size -= rc_size;
// Initialize the resource managers' shared page manager.
resource_manager_page_manager = std::make_unique<KDynamicPageManager>();
resource_manager_page_manager->Initialize(
address, size, std::max<size_t>(PageSize, KPageBufferSlabHeap::BufferSize));
resource_manager_page_manager->Initialize(address, size, std::max<size_t>(PageSize, KPageBufferSlabHeap::BufferSize));
// Initialize the KPageBuffer slab heap.
page_buffer_slab_heap.Initialize(system);
@@ -280,16 +290,12 @@ struct KernelCore::Impl {
app_memory_block_heap = std::make_unique<KMemoryBlockSlabHeap>();
sys_memory_block_heap = std::make_unique<KMemoryBlockSlabHeap>();
block_info_heap = std::make_unique<KBlockInfoSlabHeap>();
app_memory_block_heap->Initialize(resource_manager_page_manager.get(),
ApplicationMemoryBlockSlabHeapSize);
sys_memory_block_heap->Initialize(resource_manager_page_manager.get(),
SystemMemoryBlockSlabHeapSize);
app_memory_block_heap->Initialize(resource_manager_page_manager.get(), ApplicationMemoryBlockSlabHeapSize);
sys_memory_block_heap->Initialize(resource_manager_page_manager.get(), SystemMemoryBlockSlabHeapSize);
block_info_heap->Initialize(resource_manager_page_manager.get(), BlockInfoSlabHeapSize);
// Reserve all but a fixed number of remaining pages for the page table heap.
const size_t num_pt_pages = resource_manager_page_manager->GetCount() -
resource_manager_page_manager->GetUsed() -
ReservedDynamicPageCount;
const size_t num_pt_pages = resource_manager_page_manager->GetCount() - resource_manager_page_manager->GetUsed() - ReservedDynamicPageCount;
page_table_heap = std::make_unique<KPageTableSlabHeap>();
// TODO(bunnei): Pass in address once we support kernel virtual memory allocations.
@@ -301,8 +307,8 @@ struct KernelCore::Impl {
KDynamicPageManager* const app_dynamic_page_manager = nullptr;
KDynamicPageManager* const sys_dynamic_page_manager =
/*KTargetSystem::IsDynamicResourceLimitsEnabled()*/ true
? resource_manager_page_manager.get()
: nullptr;
? resource_manager_page_manager.get()
: nullptr;
app_memory_block_manager = std::make_unique<KMemoryBlockSlabManager>();
sys_memory_block_manager = std::make_unique<KMemoryBlockSlabManager>();
app_block_info_manager = std::make_unique<KBlockInfoManager>();
@@ -320,9 +326,7 @@ struct KernelCore::Impl {
sys_page_table_manager->Initialize(sys_dynamic_page_manager, page_table_heap.get());
// Check that we have the correct number of dynamic pages available.
ASSERT(resource_manager_page_manager->GetCount() -
resource_manager_page_manager->GetUsed() ==
ReservedDynamicPageCount);
ASSERT(resource_manager_page_manager->GetCount() - resource_manager_page_manager->GetUsed() == ReservedDynamicPageCount);
// Create the system page table managers.
app_system_resource = std::make_unique<KSystemResource>(kernel);
@@ -331,18 +335,15 @@ struct KernelCore::Impl {
KAutoObject::Create(std::addressof(*sys_system_resource));
// Set the managers for the system resources.
app_system_resource->SetManagers(*app_memory_block_manager, *app_block_info_manager,
*app_page_table_manager);
sys_system_resource->SetManagers(*sys_memory_block_manager, *sys_block_info_manager,
*sys_page_table_manager);
app_system_resource->SetManagers(*app_memory_block_manager, *app_block_info_manager, *app_page_table_manager);
sys_system_resource->SetManagers(*sys_memory_block_manager, *sys_block_info_manager, *sys_page_table_manager);
}
void InitializeShutdownThreads() {
for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
shutdown_threads[core_id] = KThread::Create(system.Kernel());
ASSERT(KThread::InitializeHighPriorityThread(system, shutdown_threads[core_id], {}, {},
core_id)
.IsSuccess());
ASSERT(KThread::InitializeHighPriorityThread(system, shutdown_threads[core_id], {}, {}, core_id)
.IsSuccess());
KThread::Register(system.Kernel(), shutdown_threads[core_id]);
}
}
@@ -356,83 +357,77 @@ struct KernelCore::Impl {
application_process->Open();
}
static inline thread_local u8 host_thread_id = UINT8_MAX;
/// Sets the host thread ID for the caller.
u32 SetHostThreadId(std::size_t core_id) {
// This should only be called during core init.
ASSERT(host_thread_id == UINT8_MAX);
ASSERT(tls_data.host_thread_id == UINT8_MAX);
// The first four slots are reserved for CPU core threads
ASSERT(core_id < Core::Hardware::NUM_CPU_CORES);
host_thread_id = static_cast<u8>(core_id);
return host_thread_id;
tls_data.host_thread_id = u8(core_id);
return tls_data.host_thread_id;
}
/// Gets the host thread ID for the caller
u32 GetHostThreadId() const {
return host_thread_id;
return tls_data.host_thread_id;
}
// Gets the dummy KThread for the caller, allocating a new one if this is the first time
KThread* GetHostDummyThread(KThread* existing_thread) {
const auto initialize{[](KThread* thread) {
ASSERT(KThread::InitializeDummyThread(thread, nullptr).IsSuccess());
return thread;
}};
thread_local KThread raw_thread{system.Kernel()};
thread_local KThread* thread = existing_thread ? existing_thread : initialize(&raw_thread);
return thread;
if (tls_data.thread == nullptr) {
auto const initialize{[](KThread* thread) {
ASSERT(KThread::InitializeDummyThread(thread, nullptr).IsSuccess());
return thread;
}};
tls_data.raw_thread.emplace(system.Kernel());
tls_data.thread = existing_thread ? existing_thread : initialize(&*tls_data.raw_thread);
ASSERT(tls_data.thread != nullptr);
}
return tls_data.thread;
}
/// Registers a CPU core thread by allocating a host thread ID for it
void RegisterCoreThread(std::size_t core_id) {
ASSERT(core_id < Core::Hardware::NUM_CPU_CORES);
const auto this_id = SetHostThreadId(core_id);
if (!is_multicore) {
if (!is_multicore)
single_core_thread_id = this_id;
}
}
/// Registers a new host thread by allocating a host thread ID for it
void RegisterHostThread(KThread* existing_thread) {
[[maybe_unused]] const auto dummy_thread = GetHostDummyThread(existing_thread);
(void)GetHostDummyThread(existing_thread);
}
[[nodiscard]] u32 GetCurrentHostThreadID() {
const auto this_id = GetHostThreadId();
if (!is_multicore && single_core_thread_id == this_id) {
return static_cast<u32>(system.GetCpuManager().CurrentCore());
}
auto const this_id = GetHostThreadId();
if (!is_multicore && single_core_thread_id == this_id)
return u32(system.GetCpuManager().CurrentCore());
return this_id;
}
static inline thread_local bool is_phantom_mode_for_singlecore{false};
// Forces singlecore
bool IsPhantomModeForSingleCore() const {
return is_phantom_mode_for_singlecore;
return tls_data.is_phantom_mode_for_singlecore;
}
void SetIsPhantomModeForSingleCore(bool value) {
ASSERT(!is_multicore);
is_phantom_mode_for_singlecore = value;
tls_data.is_phantom_mode_for_singlecore = value;
}
bool IsShuttingDown() const {
return is_shutting_down.load(std::memory_order_relaxed);
}
static inline thread_local KThread* current_thread{nullptr};
KThread* GetCurrentEmuThread() {
if (!current_thread) {
current_thread = GetHostDummyThread(nullptr);
}
return current_thread;
if (!tls_data.current_thread)
tls_data.current_thread = GetHostDummyThread(nullptr);
return tls_data.current_thread;
}
void SetCurrentEmuThread(KThread* thread) {
current_thread = thread;
tls_data.current_thread = thread;
}
void DeriveInitialMemoryLayout() {
+15 -59
View File
@@ -1,12 +1,8 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
#include <cstring>
#include <sstream>
#include <boost/range/algorithm_ext/erase.hpp>
@@ -191,7 +187,7 @@ void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) {
buffer_w_descriptors.push_back(rp.PopRaw<IPC::BufferDescriptorABW>());
}
buffer_c_offset = rp.GetCurrentOffset() + command_header->data_size;
const auto buffer_c_offset = rp.GetCurrentOffset() + command_header->data_size;
if (!command_header->IsTipc()) {
// Padding to align to 16 bytes
@@ -298,15 +294,7 @@ Result HLERequestContext::WriteToOutgoingCommandBuffer() {
}
// Write the domain objects to the command buffer, these go after the raw untranslated data.
if (buffer_c_offset != 0 && !buffer_c_descriptors.empty()) {
constexpr u32 WORDS_PER_DESCRIPTOR = sizeof(IPC::BufferDescriptorC) / sizeof(u32);
u32 descriptor_offset = buffer_c_offset;
for (const auto& descriptor : buffer_c_descriptors) {
std::memcpy(&cmd_buf[descriptor_offset], &descriptor, sizeof(descriptor));
descriptor_offset += WORDS_PER_DESCRIPTOR;
}
}
// TODO(Subv): This completely ignores C buffers.
if (GetManager()->IsDomain()) {
current_offset = domain_offset - static_cast<u32>(outgoing_domain_objects.size());
@@ -405,14 +393,10 @@ std::size_t HLERequestContext::WriteBuffer(const void* buffer, std::size_t size,
const bool is_buffer_b{BufferDescriptorB().size() > buffer_index &&
BufferDescriptorB()[buffer_index].Size()};
const std::size_t buffer_size{GetWriteBufferSize(buffer_index)};
if (buffer_size == 0) {
LOG_WARNING(Core, "WriteBuffer target index {} has zero capacity", buffer_index);
return 0;
}
if (size > buffer_size) {
LOG_WARNING(Core, "size ({:016X}) is greater than buffer_size ({:016X}); clamping",
size, buffer_size);
size = buffer_size;
LOG_CRITICAL(Core, "size ({:016X}) is greater than buffer_size ({:016X})", size,
buffer_size);
size = buffer_size; // TODO(bunnei): This needs to be HW tested
}
if (is_buffer_b) {
@@ -434,25 +418,15 @@ std::size_t HLERequestContext::WriteBuffer(const void* buffer, std::size_t size,
std::size_t HLERequestContext::WriteBufferB(const void* buffer, std::size_t size,
std::size_t buffer_index) const {
if (buffer_index >= BufferDescriptorB().size()) {
LOG_WARNING(Core, "WriteBufferB invalid buffer index {}", buffer_index);
return 0;
}
if (size == 0) {
LOG_WARNING(Core, "skip empty buffer write (B)");
if (buffer_index >= BufferDescriptorB().size() || size == 0) {
return 0;
}
const auto buffer_size{BufferDescriptorB()[buffer_index].Size()};
if (buffer_size == 0) {
LOG_WARNING(Core, "WriteBufferB target index {} has zero capacity", buffer_index);
return 0;
}
if (size > buffer_size) {
LOG_WARNING(Core, "size ({:016X}) is greater than buffer_size ({:016X}); clamping",
size, buffer_size);
size = buffer_size;
LOG_CRITICAL(Core, "size ({:016X}) is greater than buffer_size ({:016X})", size,
buffer_size);
size = buffer_size; // TODO(bunnei): This needs to be HW tested
}
memory.WriteBlock(BufferDescriptorB()[buffer_index].Address(), buffer, size);
@@ -461,25 +435,15 @@ std::size_t HLERequestContext::WriteBufferB(const void* buffer, std::size_t size
std::size_t HLERequestContext::WriteBufferC(const void* buffer, std::size_t size,
std::size_t buffer_index) const {
if (buffer_index >= BufferDescriptorC().size()) {
LOG_WARNING(Core, "WriteBufferC invalid buffer index {}", buffer_index);
return 0;
}
if (size == 0) {
LOG_WARNING(Core, "skip empty buffer write (C)");
if (buffer_index >= BufferDescriptorC().size() || size == 0) {
return 0;
}
const auto buffer_size{BufferDescriptorC()[buffer_index].Size()};
if (buffer_size == 0) {
LOG_WARNING(Core, "WriteBufferC target index {} has zero capacity", buffer_index);
return 0;
}
if (size > buffer_size) {
LOG_WARNING(Core, "size ({:016X}) is greater than buffer_size ({:016X}); clamping",
size, buffer_size);
size = buffer_size;
LOG_CRITICAL(Core, "size ({:016X}) is greater than buffer_size ({:016X})", size,
buffer_size);
size = buffer_size; // TODO(bunnei): This needs to be HW tested
}
memory.WriteBlock(BufferDescriptorC()[buffer_index].Address(), buffer, size);
@@ -509,20 +473,12 @@ std::size_t HLERequestContext::GetWriteBufferSize(std::size_t buffer_index) cons
ASSERT_OR_EXECUTE_MSG(
BufferDescriptorB().size() > buffer_index, { return 0; },
"BufferDescriptorB invalid buffer_index {}", buffer_index);
const auto size = BufferDescriptorB()[buffer_index].Size();
if (size == 0) {
LOG_WARNING(Core, "BufferDescriptorB index {} has zero size", buffer_index);
}
return size;
return BufferDescriptorB()[buffer_index].Size();
} else {
ASSERT_OR_EXECUTE_MSG(
BufferDescriptorC().size() > buffer_index, { return 0; },
"BufferDescriptorC invalid buffer_index {}", buffer_index);
const auto size = BufferDescriptorC()[buffer_index].Size();
if (size == 0) {
LOG_WARNING(Core, "BufferDescriptorC index {} has zero size", buffer_index);
}
return size;
return BufferDescriptorC()[buffer_index].Size();
}
return 0;
}
-4
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -425,7 +422,6 @@ private:
u32 data_payload_offset{};
u32 handles_offset{};
u32 domain_offset{};
u32 buffer_c_offset{};
std::weak_ptr<SessionRequestManager> manager{};
bool is_deferred{false};
+1 -1
View File
@@ -32,7 +32,7 @@ else()
endif()
option(DYNARMIC_ENABLE_NO_EXECUTE_SUPPORT "Enables support for systems that require W^X" ${REQUIRE_WX})
option(DYNARMIC_IGNORE_ASSERTS "Ignore asserts" OFF)
option(DYNARMIC_IGNORE_ASSERTS "Ignore asserts" ON)
option(DYNARMIC_TESTS_USE_UNICORN "Enable fuzzing tests against unicorn" OFF)
CMAKE_DEPENDENT_OPTION(DYNARMIC_USE_LLVM "Support disassembly of jitted x86_64 code using LLVM" OFF "NOT YUZU_DISABLE_LLVM" OFF)
@@ -102,9 +102,6 @@ A32EmitX64::BlockDescriptor A32EmitX64::Emit(IR::Block& block) {
}
code.EnableWriting();
SCOPE_EXIT {
code.DisableWriting();
};
const boost::container::static_vector<HostLoc, 28> gpr_order = [this] {
boost::container::static_vector<HostLoc, 28> gprs{any_gpr};
@@ -126,37 +123,31 @@ A32EmitX64::BlockDescriptor A32EmitX64::Emit(IR::Block& block) {
EmitCondPrelude(ctx);
auto const loop_all_inst = [this, &block, &ctx](auto const func) {
for (auto iter = block.begin(); iter != block.end(); ++iter) [[likely]] {
auto* inst = &*iter;
// Call the relevant Emit* member function.
switch (inst->GetOpcode()) {
for (auto iter = block.begin(); iter != block.end(); ++iter) [[likely]] {
auto* inst = &*iter;
// Call the relevant Emit* member function.
switch (inst->GetOpcode()) {
#define OPCODE(name, type, ...) \
case IR::Opcode::name: \
A32EmitX64::Emit##name(ctx, inst); \
break;
case IR::Opcode::name: \
A32EmitX64::Emit##name(ctx, inst); \
break;
#define A32OPC(name, type, ...) \
case IR::Opcode::A32##name: \
A32EmitX64::EmitA32##name(ctx, inst);\
break;
case IR::Opcode::A32##name: \
A32EmitX64::EmitA32##name(ctx, inst);\
break;
#define A64OPC(...)
#include "dynarmic/ir/opcodes.inc"
#undef OPCODE
#undef A32OPC
#undef A64OPC
default:
UNREACHABLE();
}
reg_alloc.EndOfAllocScope();
func(reg_alloc);
default:
UNREACHABLE();
}
};
if (!conf.very_verbose_debugging_output) [[likely]] {
loop_all_inst([](auto&) { /*noop*/ });
} else [[unlikely]] {
loop_all_inst([this](auto& reg_alloc) {
reg_alloc.EndOfAllocScope();
#ifndef NDEBUG
if (conf.very_verbose_debugging_output)
EmitVerboseDebuggingOutput(reg_alloc);
});
#endif
}
reg_alloc.AssertNoMoreUses();
@@ -172,7 +163,7 @@ A32EmitX64::BlockDescriptor A32EmitX64::Emit(IR::Block& block) {
}
code.int3();
const size_t size = static_cast<size_t>(code.getCurr() - entrypoint);
const size_t size = size_t(code.getCurr() - entrypoint);
const A32::LocationDescriptor descriptor{block.Location()};
const A32::LocationDescriptor end_location{block.EndLocation()};
@@ -180,7 +171,9 @@ A32EmitX64::BlockDescriptor A32EmitX64::Emit(IR::Block& block) {
const auto range = boost::icl::discrete_interval<u32>::closed(descriptor.PC(), end_location.PC() - 1);
block_ranges.AddRange(range, descriptor);
return RegisterBlock(descriptor, entrypoint, size);
auto const bdesc = RegisterBlock(descriptor, entrypoint, size);
code.DisableWriting();
return bdesc;
}
void A32EmitX64::ClearCache() {
@@ -76,10 +76,6 @@ A64EmitX64::BlockDescriptor A64EmitX64::Emit(IR::Block& block) noexcept {
}
code.EnableWriting();
SCOPE_EXIT {
code.DisableWriting();
};
const boost::container::static_vector<HostLoc, 28> gpr_order = [this] {
boost::container::static_vector<HostLoc, 28> gprs{any_gpr};
if (conf.fastmem_pointer) {
@@ -141,9 +137,10 @@ a64_branch:
(this->*a64_handlers[size_t(opcode) - std::size(opcode_handlers)])(ctx, &inst);
finish_this_inst:
ctx.reg_alloc.EndOfAllocScope();
if (conf.very_verbose_debugging_output) [[unlikely]] {
#ifndef NDEBUG
if (conf.very_verbose_debugging_output)
EmitVerboseDebuggingOutput(reg_alloc);
}
#endif
}
reg_alloc.AssertNoMoreUses();
@@ -167,7 +164,9 @@ finish_this_inst:
const auto range = boost::icl::discrete_interval<u64>::closed(descriptor.PC(), end_location.PC() - 1);
block_ranges.AddRange(range, descriptor);
return RegisterBlock(descriptor, entrypoint, size);
auto bdesc = RegisterBlock(descriptor, entrypoint, size);
code.DisableWriting();
return bdesc;
}
void A64EmitX64::ClearCache() {
@@ -105,6 +105,7 @@ void EmitX64::PushRSBHelper(Xbyak::Reg64 loc_desc_reg, Xbyak::Reg64 index_reg, I
code.mov(dword[code.ABI_JIT_PTR + code.GetJitStateInfo().offsetof_rsb_ptr], index_reg.cvt32());
}
#ifndef NDEBUG
void EmitX64::EmitVerboseDebuggingOutput(RegAlloc& reg_alloc) {
code.lea(rsp, ptr[rsp - sizeof(RegisterData)]);
code.stmxcsr(dword[rsp + offsetof(RegisterData, mxcsr)]);
@@ -134,6 +135,7 @@ void EmitX64::EmitVerboseDebuggingOutput(RegAlloc& reg_alloc) {
code.ldmxcsr(dword[rsp + offsetof(RegisterData, mxcsr)]);
code.add(rsp, sizeof(RegisterData));
}
#endif
void EmitX64::EmitPushRSB(EmitContext& ctx, IR::Inst* inst) {
auto args = ctx.reg_alloc.GetArgumentInfo(inst);
@@ -113,7 +113,9 @@ public:
BlockDescriptor RegisterBlock(const IR::LocationDescriptor& location_descriptor, CodePtr entrypoint, size_t size);
void PushRSBHelper(Xbyak::Reg64 loc_desc_reg, Xbyak::Reg64 index_reg, IR::LocationDescriptor target);
#ifndef NDEBUG
void EmitVerboseDebuggingOutput(RegAlloc& reg_alloc);
#endif
virtual void EmitTerminal(IR::Terminal terminal, IR::LocationDescriptor initial_location, bool is_single_step) noexcept = 0;
// Patching
@@ -58,7 +58,7 @@ enum class HostLoc : std::uint8_t {
FirstSpill,
};
constexpr size_t NonSpillHostLocCount = static_cast<size_t>(HostLoc::FirstSpill);
constexpr size_t NonSpillHostLocCount = size_t(HostLoc::FirstSpill);
constexpr bool HostLocIsGPR(HostLoc reg) {
return reg >= HostLoc::RAX && reg <= HostLoc::R15;
@@ -56,26 +56,23 @@ static inline bool IsValuelessType(const IR::Type type) noexcept {
}
void HostLocInfo::ReleaseOne() noexcept {
is_being_used_count--;
ASSERT(is_being_used_count > 0);
--is_being_used_count;
is_scratch = false;
if (current_references == 0)
return;
ASSERT(size_t(accumulated_uses) + 1 < (std::numeric_limits<uint16_t>::max)());
accumulated_uses++;
current_references--;
if (current_references == 0)
ReleaseAll();
if (current_references > 0) {
ASSERT(size_t(accumulated_uses) + 1 < (std::numeric_limits<decltype(accumulated_uses)>::max)());
++accumulated_uses;
--current_references;
if (current_references == 0)
ReleaseAll();
}
}
void HostLocInfo::ReleaseAll() noexcept {
ASSERT(size_t(accumulated_uses) + current_references < (std::numeric_limits<decltype(accumulated_uses)>::max)());
accumulated_uses += current_references;
current_references = 0;
is_set_last_use = false;
if (total_uses == accumulated_uses) {
values.clear();
accumulated_uses = 0;
@@ -87,17 +84,19 @@ void HostLocInfo::ReleaseAll() noexcept {
is_scratch = false;
}
void HostLocInfo::AddValue(IR::Inst* inst) noexcept {
void HostLocInfo::AddValue(HostLoc loc, IR::Inst* inst) noexcept {
if (is_set_last_use) {
is_set_last_use = false;
values.clear();
}
values.push_back(inst);
ASSERT(size_t(total_uses) + inst->UseCount() < (std::numeric_limits<uint16_t>::max)());
ASSERT(size_t(total_uses) + inst->UseCount() < (std::numeric_limits<decltype(total_uses)>::max)());
total_uses += inst->UseCount();
max_bit_width = std::max<uint8_t>(max_bit_width, std::countr_zero(GetBitWidth(inst->GetType())));
}
#ifndef NDEBUG
void HostLocInfo::EmitVerboseDebuggingOutput(BlockOfCode& code, size_t host_loc_index) const noexcept {
using namespace Xbyak::util;
for (auto const value : values) {
@@ -108,6 +107,7 @@ void HostLocInfo::EmitVerboseDebuggingOutput(BlockOfCode& code, size_t host_loc_
code.CallFunction(PrintVerboseDebuggingOutputLine);
}
}
#endif
bool Argument::FitsInImmediateU32() const noexcept {
if (!IsImmediate())
@@ -197,12 +197,13 @@ RegAlloc::ArgumentInfo RegAlloc::GetArgumentInfo(const IR::Inst* inst) noexcept
Argument{},
Argument{}
};
for (size_t i = 0; i < inst->NumArgs(); i++) {
for (size_t i = 0; i < inst->NumArgs() && i < 4; i++) {
const auto arg = inst->GetArg(i);
ret[i].value = arg;
if (!arg.IsImmediate() && !IsValuelessType(arg.GetType())) {
ASSERT(ValueLocation(arg.GetInst()) && "argument must already been defined");
LocInfo(*ValueLocation(arg.GetInst())).AddArgReference();
auto const loc = ValueLocation(arg.GetInst());
ASSERT(loc && "argument must already been defined");
LocInfo(*loc).AddArgReference();
}
}
return ret;
@@ -211,9 +212,9 @@ RegAlloc::ArgumentInfo RegAlloc::GetArgumentInfo(const IR::Inst* inst) noexcept
void RegAlloc::RegisterPseudoOperation(const IR::Inst* inst) noexcept {
ASSERT(IsValueLive(inst) || !inst->HasUses());
for (size_t i = 0; i < inst->NumArgs(); i++) {
const auto arg = inst->GetArg(i);
auto const arg = inst->GetArg(i);
if (!arg.IsImmediate() && !IsValuelessType(arg.GetType())) {
if (const auto loc = ValueLocation(arg.GetInst())) {
if (auto const loc = ValueLocation(arg.GetInst())) {
// May not necessarily have a value (e.g. CMP variant of Sub32).
LocInfo(*loc).AddArgReference();
}
@@ -262,9 +263,8 @@ HostLoc RegAlloc::UseImpl(BlockOfCode& code, IR::Value use_value, const boost::c
return LoadImmediate(code, use_value, ScratchImpl(code, desired_locations));
}
const auto* use_inst = use_value.GetInst();
const HostLoc current_location = *ValueLocation(use_inst);
const size_t max_bit_width = LocInfo(current_location).GetMaxBitWidth();
auto const* use_inst = use_value.GetInst();
HostLoc const current_location = *ValueLocation(use_inst);
const bool can_use_current_location = std::find(desired_locations.begin(), desired_locations.end(), current_location) != desired_locations.end();
if (can_use_current_location) {
@@ -276,7 +276,8 @@ HostLoc RegAlloc::UseImpl(BlockOfCode& code, IR::Value use_value, const boost::c
return UseScratchImpl(code, use_value, desired_locations);
}
const HostLoc destination_location = SelectARegister(desired_locations);
size_t const max_bit_width = LocInfo(current_location).GetMaxBitWidth();
HostLoc const destination_location = SelectARegister(desired_locations);
if (max_bit_width > HostLocBitWidth(destination_location)) {
return UseScratchImpl(code, use_value, desired_locations);
} else if (CanExchange(destination_location, current_location)) {
@@ -300,10 +301,10 @@ HostLoc RegAlloc::UseScratchImpl(BlockOfCode& code, IR::Value use_value, const b
const bool can_use_current_location = std::find(desired_locations.begin(), desired_locations.end(), current_location) != desired_locations.end();
if (can_use_current_location && !LocInfo(current_location).IsLocked()) {
if (!LocInfo(current_location).IsLastUse()) {
MoveOutOfTheWay(code, current_location);
if (LocInfo(current_location).IsLastUse()) {
LocInfo(current_location).is_set_last_use = true;
} else {
LocInfo(current_location).SetLastUse();
MoveOutOfTheWay(code, current_location);
}
LocInfo(current_location).WriteLock();
return current_location;
@@ -455,29 +456,31 @@ HostLoc RegAlloc::SelectARegister(const boost::container::static_vector<HostLoc,
std::optional<HostLoc> RegAlloc::ValueLocation(const IR::Inst* value) const noexcept {
for (size_t i = 0; i < hostloc_info.size(); i++)
if (hostloc_info[i].ContainsValue(value))
if (hostloc_info[i].ContainsValue(value)) {
//for (size_t j = 0; j < hostloc_info.size(); ++j)
// ASSERT((i == j || !hostloc_info[j].ContainsValue(value)) && "duplicate defs");
return HostLoc(i);
}
return std::nullopt;
}
void RegAlloc::DefineValueImpl(BlockOfCode& code, IR::Inst* def_inst, HostLoc host_loc) noexcept {
ASSERT(!ValueLocation(def_inst) && "def_inst has already been defined");
LocInfo(host_loc).AddValue(def_inst);
LocInfo(host_loc).AddValue(host_loc, def_inst);
ASSERT(*ValueLocation(def_inst) == host_loc);
}
void RegAlloc::DefineValueImpl(BlockOfCode& code, IR::Inst* def_inst, const IR::Value& use_inst) noexcept {
ASSERT(!ValueLocation(def_inst) && "def_inst has already been defined");
if (use_inst.IsImmediate()) {
const HostLoc location = ScratchImpl(code, gpr_order);
DefineValueImpl(code, def_inst, location);
LoadImmediate(code, use_inst, location);
return;
} else {
ASSERT(ValueLocation(use_inst.GetInst()) && "use_inst must already be defined");
const HostLoc location = *ValueLocation(use_inst.GetInst());
DefineValueImpl(code, def_inst, location);
}
ASSERT(ValueLocation(use_inst.GetInst()) && "use_inst must already be defined");
const HostLoc location = *ValueLocation(use_inst.GetInst());
DefineValueImpl(code, def_inst, location);
}
void RegAlloc::Move(BlockOfCode& code, HostLoc to, HostLoc from) noexcept {
@@ -46,61 +46,55 @@ public:
return is_being_used_count == 0 && values.empty();
}
inline bool IsLastUse() const {
return is_being_used_count == 0 && current_references == 1 && accumulated_uses + 1 == total_uses;
}
inline void SetLastUse() noexcept {
ASSERT(IsLastUse());
is_set_last_use = true;
return is_being_used_count == 0 && current_references == 1 && size_t(accumulated_uses) + 1 == size_t(total_uses);
}
inline void ReadLock() noexcept {
ASSERT(size_t(is_being_used_count) + 1 < (std::numeric_limits<uint16_t>::max)());
ASSERT(size_t(is_being_used_count) + 1 < (std::numeric_limits<decltype(is_being_used_count)>::max)());
ASSERT(!is_scratch);
is_being_used_count++;
}
inline void WriteLock() noexcept {
ASSERT(size_t(is_being_used_count) + 1 < (std::numeric_limits<uint16_t>::max)());
ASSERT(is_being_used_count == 0);
is_being_used_count++;
is_scratch = true;
}
inline void AddArgReference() noexcept {
ASSERT(size_t(current_references) + 1 < (std::numeric_limits<uint16_t>::max)());
current_references++;
ASSERT(accumulated_uses + current_references <= total_uses);
ASSERT(size_t(current_references) + 1 < (std::numeric_limits<decltype(current_references)>::max)());
++current_references;
ASSERT(size_t(accumulated_uses) + current_references <= size_t(total_uses));
}
void ReleaseOne() noexcept;
void ReleaseAll() noexcept;
constexpr size_t GetMaxBitWidth() const noexcept { return 1 << max_bit_width; }
void AddValue(HostLoc loc, IR::Inst* inst) noexcept;
/// Checks if the given instruction is in our values set
/// SAFETY: Const is casted away, irrelevant since this is only used for checking
inline bool ContainsValue(const IR::Inst* inst) const noexcept {
//return values.contains(const_cast<IR::Inst*>(inst));
[[nodiscard]] bool ContainsValue(const IR::Inst* inst) const noexcept {
return std::find(values.begin(), values.end(), inst) != values.end();
}
inline size_t GetMaxBitWidth() const noexcept {
return 1 << max_bit_width;
}
void AddValue(IR::Inst* inst) noexcept;
#ifndef NDEBUG
void EmitVerboseDebuggingOutput(BlockOfCode& code, size_t host_loc_index) const noexcept;
#endif
private:
//non trivial
boost::container::small_vector<IR::Inst*, 3> values; //24
// Block state
uint16_t total_uses = 0; //8
//sometimes zeroed
uint16_t accumulated_uses = 0; //8
//non trivial
// Block state, the total amount of uses for this particular arg
uint16_t total_uses = 0; //2
// Sometimes zeroed, accumulated (non referenced) uses
uint16_t accumulated_uses = 0; //2
//always zeroed
// Current instruction state
uint16_t is_being_used_count = 0; //8
uint16_t current_references = 0; //8
// Value state
uint8_t current_references = 0; //1
uint8_t is_being_used_count = 0; //1
// Value state, count for LRU selection in registers
uint8_t lru_counter : 2 = 0; //1
uint8_t max_bit_width : 4 = 0; //Valid values: log2(1,2,4,8,16,32,128) = (0, 1, 2, 3, 4, 5, 6)
// Log 2 of bit width, valid values: log2(1,2,4,8,16,32,128) = (0, 1, 2, 3, 4, 5, 6)
uint8_t max_bit_width : 4 = 0;
bool is_scratch : 1 = false; //1
bool is_set_last_use : 1 = false; //1
friend class RegAlloc;
};
static_assert(sizeof(HostLocInfo) == 64);
//static_assert(sizeof(HostLocInfo) == 64);
struct Argument {
public:
@@ -213,10 +207,12 @@ public:
inline void AssertNoMoreUses() noexcept {
ASSERT(std::all_of(hostloc_info.begin(), hostloc_info.end(), [](const auto& i) noexcept { return i.IsEmpty(); }));
}
#ifndef NDEBUG
inline void EmitVerboseDebuggingOutput(BlockOfCode& code) noexcept {
for (size_t i = 0; i < hostloc_info.size(); i++)
hostloc_info[i].EmitVerboseDebuggingOutput(code, i);
}
#endif
private:
friend struct Argument;
@@ -238,11 +234,11 @@ private:
HostLoc FindFreeSpill(bool is_xmm) const noexcept;
inline HostLocInfo& LocInfo(const HostLoc loc) noexcept {
ASSERT(loc != HostLoc::RSP && loc != ABI_JIT_PTR);
DEBUG_ASSERT(loc != HostLoc::RSP && loc != ABI_JIT_PTR);
return hostloc_info[size_t(loc)];
}
inline const HostLocInfo& LocInfo(const HostLoc loc) const noexcept {
ASSERT(loc != HostLoc::RSP && loc != ABI_JIT_PTR);
DEBUG_ASSERT(loc != HostLoc::RSP && loc != ABI_JIT_PTR);
return hostloc_info[size_t(loc)];
}
@@ -256,6 +252,6 @@ private:
size_t reserved_stack_space = 0;
};
// Ensure a cache line (or less) is used, this is primordial
static_assert(sizeof(boost::container::static_vector<HostLoc, 28>) == 40);
static_assert(sizeof(boost::container::static_vector<HostLoc, 28>) < 64);
} // namespace Dynarmic::Backend::X64
@@ -74,7 +74,6 @@ public:
void SetName(unsigned value) { name = value; }
unsigned GetName() const { return name; }
private:
void Use(const Value& value);
void UndoUse(const Value& value);
@@ -87,6 +86,6 @@ private:
unsigned name = 0; //4 (4)
alignas(64) std::array<Value, max_arg_count> args; //16 * 4 = 64 (1 cache line)
};
static_assert(sizeof(Inst) == 128);
//static_assert(sizeof(Inst) == 128);
} // namespace Dynarmic::IR
+2
View File
@@ -84,6 +84,7 @@ void DiscordImpl::UpdateGameStatus(bool use_default) {
presence.smallImageKey = DEFAULT_DISCORD_IMAGE;
presence.smallImageText = DEFAULT_DISCORD_TEXT;
presence.state = game_title.c_str();
presence.status_display_type = DiscordStatusDisplayType_State;
presence.details = "Currently in game";
presence.startTimestamp = start_time;
Discord_UpdatePresence(&presence);
@@ -123,6 +124,7 @@ void DiscordImpl::Update() {
DiscordRichPresence presence{};
presence.largeImageKey = DEFAULT_DISCORD_IMAGE;
presence.largeImageText = DEFAULT_DISCORD_TEXT;
presence.status_display_type = DiscordStatusDisplayType_Name;
presence.details = "Currently not in game";
presence.startTimestamp = start_time;
Discord_UpdatePresence(&presence);
@@ -4,6 +4,7 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <span>
#include <tuple>
#include <type_traits>
@@ -11,7 +12,6 @@
#include <vector>
#include <spirv-tools/optimizer.hpp>
#include "common/logging/log.h"
#include "common/settings.h"
#include "shader_recompiler/backend/spirv/emit_spirv.h"
#include "shader_recompiler/backend/spirv/emit_spirv_instructions.h"
@@ -25,6 +25,120 @@ template <class Func>
struct FuncTraits {};
thread_local std::unique_ptr<spvtools::Optimizer> thread_optimizer;
static bool TryEmitCoalescedStorage(EmitContext& ctx, IR::Block::iterator& it,
IR::Block::iterator end) {
IR::Inst& inst = *it;
const auto opcode = inst.GetOpcode();
const bool is_u8 = opcode == IR::Opcode::WriteStorageU8 || opcode == IR::Opcode::WriteStorageS8;
const bool is_u16 = opcode == IR::Opcode::WriteStorageU16 || opcode == IR::Opcode::WriteStorageS16;
if ((is_u8 && ctx.profile.support_int8) || (is_u16 && ctx.profile.support_int16)) {
return false;
}
if (!is_u8 && !is_u16) {
return false;
}
if (!inst.Arg(0).IsImmediate() || !inst.Arg(1).IsImmediate()) {
return false;
}
const u32 binding = inst.Arg(0).U32();
const u32 base_offset = inst.Arg(1).U32();
const u32 base_word = base_offset / 4;
const u32 base_byte = base_offset % 4;
const u32 step_bytes = is_u8 ? 1u : 2u;
const u32 max_bytes = is_u8 ? 4u : 4u;
if (is_u16 && (base_offset % 2u) != 0) {
return false;
}
std::array<IR::Inst*, 4> grouped{};
grouped[0] = &inst;
u32 grouped_bytes = step_bytes;
auto look_ahead = it;
while (grouped_bytes < max_bytes) {
auto next = std::next(look_ahead);
if (next == end) {
break;
}
const auto next_opcode = next->GetOpcode();
const bool next_is_u8 = next_opcode == IR::Opcode::WriteStorageU8 || next_opcode == IR::Opcode::WriteStorageS8;
const bool next_is_u16 = next_opcode == IR::Opcode::WriteStorageU16 || next_opcode == IR::Opcode::WriteStorageS16;
if (next_is_u8 != is_u8 || next_is_u16 != is_u16) {
break;
}
if (!next->Arg(0).IsImmediate() || !next->Arg(1).IsImmediate()) {
break;
}
const u32 next_binding = next->Arg(0).U32();
const u32 next_offset = next->Arg(1).U32();
if (next_binding != binding) {
break;
}
if (next_offset / 4 != base_word) {
break;
}
const u32 expected_offset = base_offset + grouped_bytes;
if (next_offset != expected_offset) {
break;
}
grouped[grouped_bytes / step_bytes] = &*next;
grouped_bytes += step_bytes;
look_ahead = next;
}
const u32 count = grouped_bytes / step_bytes;
if (count <= 1) {
return false;
}
Id combined = ctx.u32_zero_value;
for (u32 i = 0; i < count; ++i) {
IR::Inst* current = grouped[i];
Id raw = ctx.Def(current->Arg(2));
if (is_u8) {
raw = ctx.OpBitwiseAnd(ctx.U32[1], raw, ctx.Const(0xFFu));
} else {
raw = ctx.OpBitwiseAnd(ctx.U32[1], raw, ctx.Const(0xFFFFu));
}
const u32 shift_bits = (is_u8 ? i * 8u : i * 16u);
Id shifted = raw;
if (shift_bits != 0) {
shifted = ctx.OpShiftLeftLogical(ctx.U32[1], raw, ctx.Const(shift_bits));
}
if (i == 0) {
combined = shifted;
} else {
combined = ctx.OpBitwiseOr(ctx.U32[1], combined, shifted);
}
}
const u32 bit_offset_val = is_u8 ? base_byte * 8u : ((base_offset / 2u) % 2u) * 16u;
const u32 bit_count_val = grouped_bytes * 8u;
const Id ssbo = ctx.ssbos[binding].U32;
const Id index = ctx.Const(base_word);
const Id pointer = ctx.OpAccessChain(ctx.storage_types.U32.element, ssbo, ctx.u32_zero_value, index);
if (bit_count_val == 32 && bit_offset_val == 0) {
ctx.OpStore(pointer, combined);
} else {
ctx.OpFunctionCall(ctx.TypeVoid(), ctx.write_storage_cas_loop_func, pointer, combined,
ctx.Const(bit_offset_val), ctx.Const(bit_count_val));
}
it = look_ahead;
return true;
}
spvtools::Optimizer& GetThreadOptimizer() {
if (!thread_optimizer) {
thread_optimizer = std::make_unique<spvtools::Optimizer>(SPV_ENV_VULKAN_1_3);
@@ -141,8 +255,11 @@ void Traverse(EmitContext& ctx, IR::Program& program) {
}
current_block = node.data.block;
ctx.AddLabel(label);
for (IR::Inst& inst : node.data.block->Instructions()) {
EmitInst(ctx, &inst);
for (auto it = node.data.block->begin(); it != node.data.block->end(); ++it) {
if (TryEmitCoalescedStorage(ctx, it, node.data.block->end())) {
continue;
}
EmitInst(ctx, &*it);
}
break;
}
@@ -440,23 +557,15 @@ void SetupCapabilities(const Profile& profile, const Info& info, EmitContext& ct
ctx.AddExtension("SPV_KHR_shader_draw_parameters");
ctx.AddCapability(spv::Capability::DrawParameters);
}
const bool stage_supports_warp = profile.SupportsWarpIntrinsics(ctx.stage);
const bool needs_warp_intrinsics = info.uses_subgroup_vote ||
info.uses_subgroup_invocation_id ||
info.uses_subgroup_shuffles;
if (needs_warp_intrinsics && profile.support_vote && stage_supports_warp) {
if ((info.uses_subgroup_vote || info.uses_subgroup_invocation_id ||
info.uses_subgroup_shuffles) &&
profile.support_vote) {
ctx.AddCapability(spv::Capability::GroupNonUniformBallot);
ctx.AddCapability(spv::Capability::GroupNonUniformShuffle);
if (!profile.warp_size_potentially_larger_than_guest) {
// vote ops are only used when not taking the long path
ctx.AddCapability(spv::Capability::GroupNonUniformVote);
}
} else if (needs_warp_intrinsics && !stage_supports_warp) {
LOG_WARNING(Shader,
"Warp intrinsics requested in stage {} but the device does not report subgroup "
"support; falling back to scalar approximations",
static_cast<u32>(ctx.stage));
}
if (info.uses_int64_bit_atomics && profile.support_int64_atomics) {
ctx.AddCapability(spv::Capability::Int64Atomics);
@@ -491,24 +491,9 @@ void EmitSetPatch(EmitContext& ctx, IR::Patch patch, Id value) {
}
void EmitSetFragColor(EmitContext& ctx, u32 index, u32 component, Id value) {
const AttributeType output_type{ctx.runtime_info.color_output_types[index]};
Id pointer_type{ctx.output_f32};
Id store_value{value};
switch (output_type) {
case AttributeType::SignedInt:
pointer_type = ctx.output_s32;
store_value = ctx.OpBitcast(ctx.S32[1], value);
break;
case AttributeType::UnsignedInt:
pointer_type = ctx.output_u32;
store_value = ctx.OpBitcast(ctx.U32[1], value);
break;
default:
break;
}
const Id component_id{ctx.Const(component)};
const Id pointer{ctx.OpAccessChain(pointer_type, ctx.frag_color.at(index), component_id)};
ctx.OpStore(pointer, store_value);
const Id pointer{ctx.OpAccessChain(ctx.output_f32, ctx.frag_color.at(index), component_id)};
ctx.OpStore(pointer, value);
}
void EmitSetSampleMask(EmitContext& ctx, Id value) {
@@ -195,41 +195,6 @@ Id Texture(EmitContext& ctx, IR::TextureInstInfo info, [[maybe_unused]] const IR
}
}
Id TextureColorResultType(EmitContext& ctx, const TextureDefinition& def) {
switch (def.component_type) {
case SamplerComponentType::Float:
case SamplerComponentType::Depth:
return ctx.F32[4];
case SamplerComponentType::Sint:
return ctx.S32[4];
case SamplerComponentType::Stencil:
return ctx.U32[4];
case SamplerComponentType::Uint:
return ctx.U32[4];
}
throw InvalidArgument("Invalid sampler component type {}", def.component_type);
}
Id TextureSampleResultToFloat(EmitContext& ctx, const TextureDefinition& def, Id color) {
switch (def.component_type) {
case SamplerComponentType::Float:
case SamplerComponentType::Depth:
return color;
case SamplerComponentType::Sint:
return ctx.OpConvertSToF(ctx.F32[4], color);
case SamplerComponentType::Stencil:
{
const Id converted{ctx.OpConvertUToF(ctx.F32[4], color)};
const Id inv255{ctx.Const(1.0f / 255.0f)};
const Id scale{ctx.ConstantComposite(ctx.F32[4], inv255, inv255, inv255, inv255)};
return ctx.OpFMul(ctx.F32[4], converted, scale);
}
case SamplerComponentType::Uint:
return ctx.OpConvertUToF(ctx.F32[4], color);
}
throw InvalidArgument("Invalid sampler component type {}", def.component_type);
}
Id TextureImage(EmitContext& ctx, IR::TextureInstInfo info, const IR::Value& index) {
if (!index.IsImmediate() || index.U32() != 0) {
throw NotImplementedException("Indirect image indexing");
@@ -484,39 +449,31 @@ Id EmitBoundImageWrite(EmitContext&) {
Id EmitImageSampleImplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
Id bias_lc, const IR::Value& offset) {
const auto info{inst->Flags<IR::TextureInstInfo>()};
const TextureDefinition& def{ctx.textures.at(info.descriptor_index)};
const Id color_type{TextureColorResultType(ctx, def)};
const Id texture{Texture(ctx, info, index)};
Id color{};
if (ctx.stage == Stage::Fragment) {
const ImageOperands operands(ctx, info.has_bias != 0, false, info.has_lod_clamp != 0,
bias_lc, offset);
color = Emit(&EmitContext::OpImageSparseSampleImplicitLod,
&EmitContext::OpImageSampleImplicitLod, ctx, inst, color_type, texture,
coords, operands.MaskOptional(), operands.Span());
return Emit(&EmitContext::OpImageSparseSampleImplicitLod,
&EmitContext::OpImageSampleImplicitLod, ctx, inst, ctx.F32[4],
Texture(ctx, info, index), coords, operands.MaskOptional(), operands.Span());
} else {
// We can't use implicit lods on non-fragment stages on SPIR-V. Maxwell hardware behaves as
// if the lod was explicitly zero. This may change on Turing with implicit compute
// derivatives
const Id lod{ctx.Const(0.0f)};
const ImageOperands operands(ctx, false, true, info.has_lod_clamp != 0, lod, offset);
color = Emit(&EmitContext::OpImageSparseSampleExplicitLod,
&EmitContext::OpImageSampleExplicitLod, ctx, inst, color_type, texture,
coords, operands.Mask(), operands.Span());
return Emit(&EmitContext::OpImageSparseSampleExplicitLod,
&EmitContext::OpImageSampleExplicitLod, ctx, inst, ctx.F32[4],
Texture(ctx, info, index), coords, operands.Mask(), operands.Span());
}
return TextureSampleResultToFloat(ctx, def, color);
}
Id EmitImageSampleExplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
Id lod, const IR::Value& offset) {
const auto info{inst->Flags<IR::TextureInstInfo>()};
const TextureDefinition& def{ctx.textures.at(info.descriptor_index)};
const Id color_type{TextureColorResultType(ctx, def)};
const ImageOperands operands(ctx, false, true, false, lod, offset);
const Id color{Emit(&EmitContext::OpImageSparseSampleExplicitLod,
&EmitContext::OpImageSampleExplicitLod, ctx, inst, color_type,
Texture(ctx, info, index), coords, operands.Mask(), operands.Span())};
return TextureSampleResultToFloat(ctx, def, color);
return Emit(&EmitContext::OpImageSparseSampleExplicitLod,
&EmitContext::OpImageSampleExplicitLod, ctx, inst, ctx.F32[4],
Texture(ctx, info, index), coords, operands.Mask(), operands.Span());
}
Id EmitImageSampleDrefImplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index,
@@ -552,18 +509,13 @@ Id EmitImageSampleDrefExplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Va
Id EmitImageGather(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
const IR::Value& offset, const IR::Value& offset2) {
const auto info{inst->Flags<IR::TextureInstInfo>()};
const TextureDefinition& def{ctx.textures.at(info.descriptor_index)};
const Id color_type{TextureColorResultType(ctx, def)};
const ImageOperands operands(ctx, offset, offset2);
const Id texture{Texture(ctx, info, index)};
if (ctx.profile.need_gather_subpixel_offset) {
coords = ImageGatherSubpixelOffset(ctx, info, TextureImage(ctx, info, index), coords);
}
const Id color{
Emit(&EmitContext::OpImageSparseGather, &EmitContext::OpImageGather, ctx, inst, color_type,
texture, coords, ctx.Const(info.gather_component), operands.MaskOptional(),
operands.Span())};
return TextureSampleResultToFloat(ctx, def, color);
return Emit(&EmitContext::OpImageSparseGather, &EmitContext::OpImageGather, ctx, inst,
ctx.F32[4], Texture(ctx, info, index), coords, ctx.Const(info.gather_component),
operands.MaskOptional(), operands.Span());
}
Id EmitImageGatherDref(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
@@ -581,9 +533,6 @@ Id EmitImageGatherDref(EmitContext& ctx, IR::Inst* inst, const IR::Value& index,
Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id offset,
Id lod, Id ms) {
const auto info{inst->Flags<IR::TextureInstInfo>()};
const TextureDefinition* def =
info.type == TextureType::Buffer ? nullptr : &ctx.textures.at(info.descriptor_index);
const Id result_type{def ? TextureColorResultType(ctx, *def) : ctx.F32[4]};
AddOffsetToCoordinates(ctx, info, coords, offset);
if (info.type == TextureType::Buffer) {
lod = Id{};
@@ -593,13 +542,8 @@ Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id c
lod = Id{};
}
const ImageOperands operands(lod, ms);
Id color{Emit(&EmitContext::OpImageSparseFetch, &EmitContext::OpImageFetch, ctx, inst,
result_type, TextureImage(ctx, info, index), coords, operands.MaskOptional(),
operands.Span())};
if (def) {
color = TextureSampleResultToFloat(ctx, *def, color);
}
return color;
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,
@@ -644,17 +588,14 @@ Id EmitImageQueryLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, I
Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords,
Id derivatives, const IR::Value& offset, Id lod_clamp) {
const auto info{inst->Flags<IR::TextureInstInfo>()};
const TextureDefinition& def{ctx.textures.at(info.descriptor_index)};
const Id color_type{TextureColorResultType(ctx, def)};
const auto operands = info.num_derivatives == 3
? ImageOperands(ctx, info.has_lod_clamp != 0, derivatives,
ctx.Def(offset), {}, lod_clamp)
: ImageOperands(ctx, info.has_lod_clamp != 0, derivatives,
info.num_derivatives, offset, lod_clamp);
const Id color{Emit(&EmitContext::OpImageSparseSampleExplicitLod,
&EmitContext::OpImageSampleExplicitLod, ctx, inst, color_type,
Texture(ctx, info, index), coords, operands.Mask(), operands.Span())};
return TextureSampleResultToFloat(ctx, def, color);
return Emit(&EmitContext::OpImageSparseSampleExplicitLod,
&EmitContext::OpImageSampleExplicitLod, ctx, inst, ctx.F32[4],
Texture(ctx, info, index), coords, operands.Mask(), operands.Span());
}
Id EmitImageRead(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords) {
@@ -1,6 +1,3 @@
// 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
@@ -81,25 +78,9 @@ Id AddPartitionBase(EmitContext& ctx, Id thread_id) {
const Id partition_base{ctx.OpShiftLeftLogical(ctx.U32[1], partition_idx, ctx.Const(5u))};
return ctx.OpIAdd(ctx.U32[1], thread_id, partition_base);
}
bool SupportsWarpIntrinsics(const EmitContext& ctx) {
return ctx.profile.SupportsWarpIntrinsics(ctx.stage);
}
void SetAlwaysInBounds(EmitContext& ctx, IR::Inst* inst) {
SetInBoundsFlag(inst, ctx.true_value);
}
Id FallbackBallotMask(EmitContext& ctx, Id pred) {
const Id full_mask{ctx.Const(0xFFFFFFFFu)};
return ctx.OpSelect(ctx.U32[1], pred, full_mask, ctx.u32_zero_value);
}
} // Anonymous namespace
Id EmitLaneId(EmitContext& ctx) {
if (!SupportsWarpIntrinsics(ctx)) {
return ctx.u32_zero_value;
}
const Id id{GetThreadId(ctx)};
if (!ctx.profile.warp_size_potentially_larger_than_guest) {
return id;
@@ -108,9 +89,6 @@ Id EmitLaneId(EmitContext& ctx) {
}
Id EmitVoteAll(EmitContext& ctx, Id pred) {
if (!SupportsWarpIntrinsics(ctx)) {
return pred;
}
if (!ctx.profile.warp_size_potentially_larger_than_guest) {
return ctx.OpGroupNonUniformAll(ctx.U1, SubgroupScope(ctx), pred);
}
@@ -124,9 +102,6 @@ Id EmitVoteAll(EmitContext& ctx, Id pred) {
}
Id EmitVoteAny(EmitContext& ctx, Id pred) {
if (!SupportsWarpIntrinsics(ctx)) {
return pred;
}
if (!ctx.profile.warp_size_potentially_larger_than_guest) {
return ctx.OpGroupNonUniformAny(ctx.U1, SubgroupScope(ctx), pred);
}
@@ -140,9 +115,6 @@ Id EmitVoteAny(EmitContext& ctx, Id pred) {
}
Id EmitVoteEqual(EmitContext& ctx, Id pred) {
if (!SupportsWarpIntrinsics(ctx)) {
return pred;
}
if (!ctx.profile.warp_size_potentially_larger_than_guest) {
return ctx.OpGroupNonUniformAllEqual(ctx.U1, SubgroupScope(ctx), pred);
}
@@ -157,9 +129,6 @@ Id EmitVoteEqual(EmitContext& ctx, Id pred) {
}
Id EmitSubgroupBallot(EmitContext& ctx, Id pred) {
if (!SupportsWarpIntrinsics(ctx)) {
return FallbackBallotMask(ctx, pred);
}
const Id ballot{ctx.OpGroupNonUniformBallot(ctx.U32[4], SubgroupScope(ctx), pred)};
if (!ctx.profile.warp_size_potentially_larger_than_guest) {
return ctx.OpCompositeExtract(ctx.U32[1], ballot, 0U);
@@ -168,46 +137,27 @@ Id EmitSubgroupBallot(EmitContext& ctx, Id pred) {
}
Id EmitSubgroupEqMask(EmitContext& ctx) {
if (!SupportsWarpIntrinsics(ctx)) {
return ctx.u32_zero_value;
}
return LoadMask(ctx, ctx.subgroup_mask_eq);
}
Id EmitSubgroupLtMask(EmitContext& ctx) {
if (!SupportsWarpIntrinsics(ctx)) {
return ctx.u32_zero_value;
}
return LoadMask(ctx, ctx.subgroup_mask_lt);
}
Id EmitSubgroupLeMask(EmitContext& ctx) {
if (!SupportsWarpIntrinsics(ctx)) {
return ctx.u32_zero_value;
}
return LoadMask(ctx, ctx.subgroup_mask_le);
}
Id EmitSubgroupGtMask(EmitContext& ctx) {
if (!SupportsWarpIntrinsics(ctx)) {
return ctx.u32_zero_value;
}
return LoadMask(ctx, ctx.subgroup_mask_gt);
}
Id EmitSubgroupGeMask(EmitContext& ctx) {
if (!SupportsWarpIntrinsics(ctx)) {
return ctx.u32_zero_value;
}
return LoadMask(ctx, ctx.subgroup_mask_ge);
}
Id EmitShuffleIndex(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
Id segmentation_mask) {
if (!SupportsWarpIntrinsics(ctx)) {
SetAlwaysInBounds(ctx, inst);
return value;
}
const Id not_seg_mask{ctx.OpNot(ctx.U32[1], segmentation_mask)};
const Id thread_id{EmitLaneId(ctx)};
const Id min_thread_id{ComputeMinThreadId(ctx, thread_id, segmentation_mask)};
@@ -227,10 +177,6 @@ Id EmitShuffleIndex(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id cla
Id EmitShuffleUp(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
Id segmentation_mask) {
if (!SupportsWarpIntrinsics(ctx)) {
SetAlwaysInBounds(ctx, inst);
return value;
}
const Id thread_id{EmitLaneId(ctx)};
const Id max_thread_id{GetMaxThreadId(ctx, thread_id, clamp, segmentation_mask)};
Id src_thread_id{ctx.OpISub(ctx.U32[1], thread_id, index)};
@@ -246,10 +192,6 @@ Id EmitShuffleUp(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
Id EmitShuffleDown(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
Id segmentation_mask) {
if (!SupportsWarpIntrinsics(ctx)) {
SetAlwaysInBounds(ctx, inst);
return value;
}
const Id thread_id{EmitLaneId(ctx)};
const Id max_thread_id{GetMaxThreadId(ctx, thread_id, clamp, segmentation_mask)};
Id src_thread_id{ctx.OpIAdd(ctx.U32[1], thread_id, index)};
@@ -265,10 +207,6 @@ Id EmitShuffleDown(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clam
Id EmitShuffleButterfly(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
Id segmentation_mask) {
if (!SupportsWarpIntrinsics(ctx)) {
SetAlwaysInBounds(ctx, inst);
return value;
}
const Id thread_id{EmitLaneId(ctx)};
const Id max_thread_id{GetMaxThreadId(ctx, thread_id, clamp, segmentation_mask)};
Id src_thread_id{ctx.OpBitwiseXor(ctx.U32[1], thread_id, index)};
@@ -28,41 +28,27 @@ enum class Operation {
FPMax,
};
Id ComponentScalarType(EmitContext& ctx, SamplerComponentType component_type) {
switch (component_type) {
case SamplerComponentType::Float:
case SamplerComponentType::Depth:
return ctx.F32[1];
case SamplerComponentType::Sint:
return ctx.S32[1];
case SamplerComponentType::Stencil:
return ctx.U32[1];
case SamplerComponentType::Uint:
return ctx.U32[1];
}
throw InvalidArgument("Invalid sampler component type {}", component_type);
}
Id ImageType(EmitContext& ctx, const TextureDescriptor& desc, Id sampled_type) {
Id ImageType(EmitContext& ctx, const TextureDescriptor& desc) {
const spv::ImageFormat format{spv::ImageFormat::Unknown};
const Id type{ctx.F32[1]};
const bool depth{desc.is_depth};
const bool ms{desc.is_multisample};
switch (desc.type) {
case TextureType::Color1D:
return ctx.TypeImage(sampled_type, spv::Dim::Dim1D, depth, false, false, 1, format);
return ctx.TypeImage(type, spv::Dim::Dim1D, depth, false, false, 1, format);
case TextureType::ColorArray1D:
return ctx.TypeImage(sampled_type, spv::Dim::Dim1D, depth, true, false, 1, format);
return ctx.TypeImage(type, spv::Dim::Dim1D, depth, true, false, 1, format);
case TextureType::Color2D:
case TextureType::Color2DRect:
return ctx.TypeImage(sampled_type, spv::Dim::Dim2D, depth, false, ms, 1, format);
return ctx.TypeImage(type, spv::Dim::Dim2D, depth, false, ms, 1, format);
case TextureType::ColorArray2D:
return ctx.TypeImage(sampled_type, spv::Dim::Dim2D, depth, true, ms, 1, format);
return ctx.TypeImage(type, spv::Dim::Dim2D, depth, true, ms, 1, format);
case TextureType::Color3D:
return ctx.TypeImage(sampled_type, spv::Dim::Dim3D, depth, false, false, 1, format);
return ctx.TypeImage(type, spv::Dim::Dim3D, depth, false, false, 1, format);
case TextureType::ColorCube:
return ctx.TypeImage(sampled_type, spv::Dim::Cube, depth, false, false, 1, format);
return ctx.TypeImage(type, spv::Dim::Cube, depth, false, false, 1, format);
case TextureType::ColorArrayCube:
return ctx.TypeImage(sampled_type, spv::Dim::Cube, depth, true, false, 1, format);
return ctx.TypeImage(type, spv::Dim::Cube, depth, true, false, 1, format);
case TextureType::Buffer:
break;
}
@@ -329,9 +315,6 @@ 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);
}
@@ -563,7 +546,6 @@ void EmitContext::DefineCommonTypes(const Info& info) {
output_f32 = Name(TypePointer(spv::StorageClass::Output, F32[1]), "output_f32");
output_u32 = Name(TypePointer(spv::StorageClass::Output, U32[1]), "output_u32");
output_s32 = Name(TypePointer(spv::StorageClass::Output, S32[1]), "output_s32");
if (info.uses_int8 && profile.support_int8) {
AddCapability(spv::Capability::Int8);
@@ -924,6 +906,9 @@ void EmitContext::DefineWriteStorageCasLoopFunction(const Info& info) {
const Id continue_label{OpLabel()};
const Id endloop_label{OpLabel()};
const Id beginloop_label{OpLabel()};
const Id max_iterations{Const(16u)};
const Id iteration_counter{OpVariable(TypePointer(spv::StorageClass::Function, U32[1]),
spv::StorageClass::Function, u32_zero_value)};
OpBranch(beginloop_label);
AddLabel(beginloop_label);
@@ -931,11 +916,20 @@ void EmitContext::DefineWriteStorageCasLoopFunction(const Info& info) {
OpBranch(body_label);
AddLabel(body_label);
const Id current_iteration{OpLoad(U32[1], iteration_counter)};
const Id iteration_exceeded{OpUGreaterThanEqual(U1, current_iteration, max_iterations)};
const Id bailout_label{OpLabel()};
OpSelectionMerge(bailout_label, spv::SelectionControlMask::MaskNone);
OpBranchConditional(iteration_exceeded, bailout_label, bailout_label);
AddLabel(bailout_label);
const Id expected_value{OpLoad(U32[1], pointer)};
const Id desired_value{OpBitFieldInsert(U32[1], expected_value, value, bit_offset, bit_count)};
const Id actual_value{OpAtomicCompareExchange(U32[1], pointer, scope_device, ordering_relaxed,
ordering_relaxed, desired_value, expected_value)};
const Id store_successful{OpIEqual(U1, expected_value, actual_value)};
const Id next_iteration{OpIAdd(U32[1], current_iteration, Const(1u))};
OpStore(iteration_counter, next_iteration);
OpBranchConditional(store_successful, endloop_label, continue_label);
AddLabel(endloop_label);
@@ -1377,8 +1371,7 @@ void EmitContext::DefineImageBuffers(const Info& info, u32& binding) {
void EmitContext::DefineTextures(const Info& info, u32& binding, u32& scaling_index) {
textures.reserve(info.texture_descriptors.size());
for (const TextureDescriptor& desc : info.texture_descriptors) {
const Id result_type{ComponentScalarType(*this, desc.component_type)};
const Id image_type{ImageType(*this, desc, result_type)};
const Id image_type{ImageType(*this, desc)};
const Id sampled_type{TypeSampledImage(image_type)};
const Id pointer_type{TypePointer(spv::StorageClass::UniformConstant, sampled_type)};
const Id desc_type{DescType(*this, sampled_type, pointer_type, desc.count)};
@@ -1391,10 +1384,8 @@ void EmitContext::DefineTextures(const Info& info, u32& binding, u32& scaling_in
.sampled_type = sampled_type,
.pointer_type = pointer_type,
.image_type = image_type,
.result_type = result_type,
.count = desc.count,
.is_multisample = desc.is_multisample,
.component_type = desc.component_type,
});
if (profile.supported_spirv >= 0x00010400) {
interfaces.push_back(id);
@@ -1437,7 +1428,6 @@ void EmitContext::DefineImages(const Info& info, u32& binding, u32& scaling_inde
void EmitContext::DefineInputs(const IR::Program& program) {
const Info& info{program.info};
const VaryingState loads{info.loads.mask | info.passthrough.mask};
const bool stage_supports_warp = profile.SupportsWarpIntrinsics(stage);
if (info.uses_workgroup_id) {
workgroup_id = DefineInput(*this, U32[3], false, spv::BuiltIn::WorkgroupId);
@@ -1461,7 +1451,7 @@ void EmitContext::DefineInputs(const IR::Program& program) {
if (info.uses_is_helper_invocation) {
is_helper_invocation = DefineInput(*this, U1, false, spv::BuiltIn::HelperInvocation);
}
if (info.uses_subgroup_mask && stage_supports_warp) {
if (info.uses_subgroup_mask) {
subgroup_mask_eq = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupEqMaskKHR);
subgroup_mask_lt = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupLtMaskKHR);
subgroup_mask_le = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupLeMaskKHR);
@@ -1475,10 +1465,9 @@ void EmitContext::DefineInputs(const IR::Program& program) {
Decorate(subgroup_mask_ge, spv::Decoration::Flat);
}
}
if (stage_supports_warp &&
(info.uses_fswzadd || info.uses_subgroup_invocation_id || info.uses_subgroup_shuffles ||
(profile.warp_size_potentially_larger_than_guest &&
(info.uses_subgroup_vote || info.uses_subgroup_mask)))) {
if (info.uses_fswzadd || info.uses_subgroup_invocation_id || info.uses_subgroup_shuffles ||
(profile.warp_size_potentially_larger_than_guest &&
(info.uses_subgroup_vote || info.uses_subgroup_mask))) {
AddCapability(spv::Capability::GroupNonUniform);
subgroup_local_invocation_id =
DefineInput(*this, U32[1], false, spv::BuiltIn::SubgroupLocalInvocationId);
@@ -1700,18 +1689,7 @@ void EmitContext::DefineOutputs(const IR::Program& program) {
if (!info.stores_frag_color[index] && !profile.need_declared_frag_colors) {
continue;
}
const AttributeType output_type{runtime_info.color_output_types[index]};
const Id vec_type = [&, output_type]() -> Id {
switch (output_type) {
case AttributeType::SignedInt:
return S32[4];
case AttributeType::UnsignedInt:
return U32[4];
default:
return F32[4];
}
}();
frag_color[index] = DefineOutput(*this, vec_type, std::nullopt);
frag_color[index] = DefineOutput(*this, F32[4], std::nullopt);
Decorate(frag_color[index], spv::Decoration::Location, index);
Name(frag_color[index], fmt::format("frag_color{}", index));
}
@@ -1,6 +1,3 @@
// 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
@@ -39,10 +36,8 @@ struct TextureDefinition {
Id sampled_type;
Id pointer_type;
Id image_type;
Id result_type;
u32 count;
bool is_multisample;
SamplerComponentType component_type;
};
struct TextureBufferDefinition {
@@ -249,7 +244,6 @@ public:
Id output_f32{};
Id output_u32{};
Id output_s32{};
Id image_buffer_type{};
Id image_u32{};
-5
View File
@@ -1,6 +1,3 @@
// 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
@@ -25,8 +22,6 @@ public:
[[nodiscard]] virtual TextureType ReadTextureType(u32 raw_handle) = 0;
[[nodiscard]] virtual SamplerComponentType ReadTextureComponentType(u32 raw_handle) = 0;
[[nodiscard]] virtual TexturePixelFormat ReadTexturePixelFormat(u32 raw_handle) = 0;
[[nodiscard]] virtual bool IsTexturePixelFormatInteger(u32 raw_handle) = 0;
@@ -396,10 +396,6 @@ bool IsTexturePixelFormatInteger(Environment& env, const ConstBufferAddr& cbuf)
return env.IsTexturePixelFormatInteger(GetTextureHandle(env, cbuf));
}
SamplerComponentType ReadTextureComponentType(Environment& env, const ConstBufferAddr& cbuf) {
return env.ReadTextureComponentType(GetTextureHandle(env, cbuf));
}
class Descriptors {
public:
explicit Descriptors(TextureBufferDescriptors& texture_buffer_descriptors_,
@@ -437,9 +433,7 @@ public:
u32 Add(const TextureDescriptor& desc) {
const u32 index{Add(texture_descriptors, desc, [&desc](const auto& existing) {
return desc.type == existing.type &&
desc.component_type == existing.component_type &&
desc.is_depth == existing.is_depth &&
return desc.type == existing.type && desc.is_depth == existing.is_depth &&
desc.has_secondary == existing.has_secondary &&
desc.cbuf_index == existing.cbuf_index &&
desc.cbuf_offset == existing.cbuf_offset &&
@@ -672,12 +666,10 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo
.secondary_shift_left = cbuf.secondary_shift_left,
.count = cbuf.count,
.size_shift = DESCRIPTOR_SIZE_SHIFT,
.component_type = ReadTextureComponentType(env, cbuf),
});
} else {
index = descriptors.Add(TextureDescriptor{
.type = flags.type,
.component_type = ReadTextureComponentType(env, cbuf),
.is_depth = flags.is_depth != 0,
.is_multisample = is_multisample,
.has_secondary = cbuf.has_secondary,
-13
View File
@@ -1,15 +1,9 @@
// 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
#pragma once
#include <limits>
#include "common/common_types.h"
#include "shader_recompiler/stage.h"
namespace Shader {
@@ -52,8 +46,6 @@ struct Profile {
bool support_multi_viewport{};
bool support_geometry_streams{};
u32 warp_stage_support_mask{std::numeric_limits<u32>::max()};
bool warp_size_potentially_larger_than_guest{};
bool lower_left_origin_mode{};
@@ -98,11 +90,6 @@ struct Profile {
u64 min_ssbo_alignment{};
u32 max_user_clip_distances{};
bool SupportsWarpIntrinsics(Stage stage) const {
const u32 bit = 1u << static_cast<u32>(stage);
return (warp_stage_support_mask & bit) != 0;
}
};
} // namespace Shader
-4
View File
@@ -1,6 +1,3 @@
// 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
@@ -83,7 +80,6 @@ struct TransformFeedbackVarying {
struct RuntimeInfo {
std::array<AttributeType, 32> generic_input_types{};
std::array<AttributeType, 8> color_output_types{};
VaryingState previous_stage_stores;
std::map<IR::Attribute, IR::Attribute> previous_stage_legacy_stores_mapping;
-13
View File
@@ -1,6 +1,3 @@
// 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
@@ -154,14 +151,6 @@ enum class ImageFormat : u32 {
R32G32B32A32_UINT,
};
enum class SamplerComponentType : u8 {
Float,
Sint,
Uint,
Depth,
Stencil,
};
enum class Interpolation {
Smooth,
Flat,
@@ -194,7 +183,6 @@ struct TextureBufferDescriptor {
u32 secondary_shift_left;
u32 count;
u32 size_shift;
SamplerComponentType component_type;
auto operator<=>(const TextureBufferDescriptor&) const = default;
};
@@ -216,7 +204,6 @@ using ImageBufferDescriptors = boost::container::small_vector<ImageBufferDescrip
struct TextureDescriptor {
TextureType type;
SamplerComponentType component_type;
bool is_depth;
bool is_multisample;
bool has_secondary;
-6
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -238,9 +235,6 @@ constexpr Table MakeViewTable() {
EnableRange(view, VIEW_CLASS_ASTC_10x10_RGBA);
EnableRange(view, VIEW_CLASS_ASTC_12x10_RGBA);
EnableRange(view, VIEW_CLASS_ASTC_12x12_RGBA);
Enable(view, PixelFormat::D24_UNORM_S8_UINT, PixelFormat::S8_UINT);
Enable(view, PixelFormat::S8_UINT_D24_UNORM, PixelFormat::S8_UINT);
Enable(view, PixelFormat::D32_FLOAT_S8_UINT, PixelFormat::S8_UINT);
return view;
}
+3 -8
View File
@@ -87,10 +87,6 @@ public:
}
pending_operations.emplace_back(std::move(uncommitted_operations));
QueueFence(new_fence);
if (!new_fence->IsStubbed()) {
std::scoped_lock lock{texture_cache.mutex};
texture_cache.CommitPendingGpuAccesses(new_fence->WaitTick());
}
if (!delay_fence) {
func();
}
@@ -182,7 +178,7 @@ private:
return;
}
}
PopAsyncFlushes(current_fence->WaitTick());
PopAsyncFlushes();
auto operations = std::move(pending_operations.front());
pending_operations.pop_front();
for (auto& operation : operations) {
@@ -217,7 +213,7 @@ private:
if (!current_fence->IsStubbed()) {
WaitFence(current_fence);
}
PopAsyncFlushes(current_fence->WaitTick());
PopAsyncFlushes();
for (auto& operation : current_operations) {
operation();
}
@@ -240,11 +236,10 @@ private:
query_cache.HasUncommittedFlushes();
}
void PopAsyncFlushes(u64 completed_tick) {
void PopAsyncFlushes() {
{
std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
texture_cache.PopAsyncFlushes();
texture_cache.CompleteGpuAccesses(completed_tick);
buffer_cache.PopAsyncFlushes();
}
query_cache.PopAsyncFlushes();
File diff suppressed because it is too large Load Diff
+11 -11
View File
@@ -605,20 +605,20 @@ public:
SetOutputSurfaceChromaUnusedOffset = offsetof(VicRegisters, output_surface.chroma_v)
};
explicit Vic(Host1x& host1x, s32 id, u32 syncpt, FrameQueue& frame_queue);
~Vic();
explicit Vic(Host1x& host1x, s32 id, u32 syncpt, FrameQueue& frame_queue) noexcept;
~Vic() noexcept;
/// Write to the device state.
void ProcessMethod(u32 method, u32 arg) override;
void ProcessMethod(u32 method, u32 arg) noexcept override;
private:
void Execute();
void Blend(const ConfigStruct& config, const SlotStruct& slot);
void ReadProgressiveY8__V8U8_N420(const SlotStruct& slot, std::span<const PlaneOffsets> offsets, std::shared_ptr<const FFmpeg::Frame> frame, bool planar, bool interlaced);
void ReadInterlacedY8__V8U8_N420(const SlotStruct& slot, std::span<const PlaneOffsets> offsets, std::shared_ptr<const FFmpeg::Frame> frame, bool planar, bool top_field);
void ReadY8__V8U8_N420(const SlotStruct& slot, std::span<const PlaneOffsets> offsets, std::shared_ptr<const FFmpeg::Frame> frame, bool planar);
void WriteY8__V8U8_N420(const OutputSurfaceConfig& output_surface_config);
void WriteABGR(const OutputSurfaceConfig& output_surface_config, VideoPixelFormat format);
void Execute() noexcept;
void Blend(const ConfigStruct& config, const SlotStruct& slot, VideoPixelFormat format) noexcept;
void ReadProgressiveY8__V8U8_N420(const SlotStruct& slot, std::span<const PlaneOffsets> offsets, std::shared_ptr<const FFmpeg::Frame> frame, bool planar, bool interlaced) noexcept;
void ReadInterlacedY8__V8U8_N420(const SlotStruct& slot, std::span<const PlaneOffsets> offsets, std::shared_ptr<const FFmpeg::Frame> frame, bool planar, bool top_field) noexcept;
void ReadY8__V8U8_N420(const SlotStruct& slot, std::span<const PlaneOffsets> offsets, std::shared_ptr<const FFmpeg::Frame> frame, bool planar) noexcept;
void WriteY8__V8U8_N420(const OutputSurfaceConfig& output_surface_config) noexcept;
void WriteABGR(const OutputSurfaceConfig& output_surface_config, VideoPixelFormat format) noexcept;
s32 id;
s32 nvdec_id{-1};
@@ -627,11 +627,11 @@ private:
VicRegisters regs{};
FrameQueue& frame_queue;
Common::ScratchBuffer<u8> swizzle_scratch;
Common::ScratchBuffer<Pixel> output_surface;
Common::ScratchBuffer<Pixel> slot_surface;
Common::ScratchBuffer<u8> luma_scratch;
Common::ScratchBuffer<u8> chroma_scratch;
Common::ScratchBuffer<u8> swizzle_scratch;
};
} // namespace Tegra::Host1x
-6
View File
@@ -211,12 +211,6 @@ void QueryCacheBase<Traits>::CounterClose(QueryType counter_type) {
streamer->CloseCounter();
}
template <typename Traits>
bool QueryCacheBase<Traits>::HasStreamer(QueryType counter_type) const {
const size_t index = static_cast<size_t>(counter_type);
return impl->streamers[index] != nullptr;
}
template <typename Traits>
void QueryCacheBase<Traits>::CounterReset(QueryType counter_type) {
size_t index = static_cast<size_t>(counter_type);
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
@@ -95,8 +92,6 @@ public:
void CounterReset(QueryType counter_type);
[[nodiscard]] bool HasStreamer(QueryType counter_type) const;
void CounterClose(QueryType counter_type);
void CounterReport(GPUVAddr addr, QueryType counter_type, QueryPropertiesFlags flags,
@@ -7,50 +7,13 @@
#include <cstring>
#include <bit>
#include <numeric>
#include <optional>
#include "common/cityhash.h"
#include "common/settings.h" // for enum class Settings::ShaderBackend
#include "video_core/renderer_opengl/gl_compute_pipeline.h"
#include "video_core/renderer_opengl/gl_shader_manager.h"
#include "video_core/renderer_opengl/gl_shader_util.h"
#include "video_core/surface.h"
namespace OpenGL {
namespace {
std::optional<VideoCore::Surface::PixelFormatNumeric>
NumericFromComponentType(Shader::SamplerComponentType component_type) {
using VideoCore::Surface::PixelFormatNumeric;
switch (component_type) {
case Shader::SamplerComponentType::Float:
return PixelFormatNumeric::Float;
case Shader::SamplerComponentType::Sint:
return PixelFormatNumeric::Sint;
case Shader::SamplerComponentType::Uint:
return PixelFormatNumeric::Uint;
default:
return std::nullopt;
}
}
VideoCore::Surface::PixelFormat ResolveTexelBufferFormat(
VideoCore::Surface::PixelFormat format, Shader::SamplerComponentType component_type) {
const auto desired_numeric = NumericFromComponentType(component_type);
if (!desired_numeric) {
return format;
}
const auto current_numeric = VideoCore::Surface::GetPixelFormatNumericType(format);
if (*desired_numeric == current_numeric) {
return format;
}
if (const auto variant =
VideoCore::Surface::FindPixelFormatVariant(format, *desired_numeric)) {
return *variant;
}
return format;
}
} // Anonymous namespace
using Shader::ImageBufferDescriptor;
using Tegra::Texture::TexturePair;
@@ -211,12 +174,8 @@ void ComputePipeline::Configure() {
is_written = desc.is_written;
}
ImageView& image_view{texture_cache.GetImageView(views[texbuf_index].id)};
auto buffer_format = image_view.format;
if constexpr (!is_image) {
buffer_format = ResolveTexelBufferFormat(buffer_format, desc.component_type);
}
buffer_cache.BindComputeTextureBuffer(texbuf_index, image_view.GpuAddr(),
image_view.BufferSize(), buffer_format,
image_view.BufferSize(), image_view.format,
is_written, is_image);
++texbuf_index;
}
@@ -246,8 +205,7 @@ void ComputePipeline::Configure() {
for (const auto& desc : info.texture_descriptors) {
for (u32 index = 0; index < desc.count; ++index) {
ImageView& image_view{texture_cache.GetImageView((views_it++)->id)};
textures[texture_binding] =
image_view.SampledView(desc.type, desc.component_type);
textures[texture_binding] = image_view.Handle(desc.type);
if (texture_cache.IsRescaling(image_view)) {
texture_scaling_mask |= 1u << texture_binding;
}
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -28,10 +25,6 @@ public:
void Wait();
[[nodiscard]] u64 WaitTick() const noexcept {
return 0;
}
private:
OGLSync sync_object;
};
@@ -6,7 +6,6 @@
#include <algorithm>
#include <array>
#include <optional>
#include <string>
#include <vector>
#include <bit>
@@ -19,7 +18,6 @@
#include "video_core/renderer_opengl/gl_shader_util.h"
#include "video_core/renderer_opengl/gl_state_tracker.h"
#include "video_core/shader_notify.h"
#include "video_core/surface.h"
#include "video_core/texture_cache/texture_cache.h"
#if defined(_MSC_VER) && defined(NDEBUG)
@@ -41,38 +39,6 @@ using VideoCommon::ImageId;
constexpr u32 MAX_TEXTURES = 64;
constexpr u32 MAX_IMAGES = 8;
std::optional<VideoCore::Surface::PixelFormatNumeric>
NumericFromComponentType(Shader::SamplerComponentType component_type) {
using VideoCore::Surface::PixelFormatNumeric;
switch (component_type) {
case Shader::SamplerComponentType::Float:
return PixelFormatNumeric::Float;
case Shader::SamplerComponentType::Sint:
return PixelFormatNumeric::Sint;
case Shader::SamplerComponentType::Uint:
return PixelFormatNumeric::Uint;
default:
return std::nullopt;
}
}
VideoCore::Surface::PixelFormat ResolveTexelBufferFormat(
VideoCore::Surface::PixelFormat format, Shader::SamplerComponentType component_type) {
const auto desired_numeric = NumericFromComponentType(component_type);
if (!desired_numeric) {
return format;
}
const auto current_numeric = VideoCore::Surface::GetPixelFormatNumericType(format);
if (*desired_numeric == current_numeric) {
return format;
}
if (const auto variant =
VideoCore::Surface::FindPixelFormatVariant(format, *desired_numeric)) {
return *variant;
}
return format;
}
GLenum Stage(size_t stage_index) {
switch (stage_index) {
case 0:
@@ -431,12 +397,8 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
is_written = desc.is_written;
}
ImageView& image_view{texture_cache.GetImageView(texture_buffer_it->id)};
auto buffer_format = image_view.format;
if constexpr (!is_image) {
buffer_format = ResolveTexelBufferFormat(buffer_format, desc.component_type);
}
buffer_cache.BindGraphicsTextureBuffer(stage, index, image_view.GpuAddr(),
image_view.BufferSize(), buffer_format,
image_view.BufferSize(), image_view.format,
is_written, is_image);
++index;
++texture_buffer_it;
@@ -521,8 +483,7 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
for (const auto& desc : info.texture_descriptors) {
for (u32 index = 0; index < desc.count; ++index) {
ImageView& image_view{texture_cache.GetImageView((views_it++)->id)};
textures[texture_binding] =
image_view.SampledView(desc.type, desc.component_type);
textures[texture_binding] = image_view.Handle(desc.type);
if (texture_cache.IsRescaling(image_view)) {
texture_scaling_mask |= 1u << stage_texture_binding;
}
@@ -54,7 +54,7 @@ using VideoCommon::LoadPipelines;
using VideoCommon::SerializePipeline;
using Context = ShaderContext::Context;
constexpr u32 CACHE_VERSION = 14;
constexpr u32 CACHE_VERSION = 13;
template <typename Container>
auto MakeSpan(Container& container) {
@@ -220,7 +220,6 @@ ShaderCache::ShaderCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
.support_gl_sparse_textures = device.HasSparseTexture2(),
.support_gl_derivative_control = device.HasDerivativeControl(),
.support_geometry_streams = true,
.warp_stage_support_mask = 0xFFFFFFFFu,
.warp_size_potentially_larger_than_guest = device.IsWarpSizePotentiallyLargerThanGuest(),
@@ -692,15 +692,6 @@ bool TextureCacheRuntime::HasNativeASTC() const noexcept {
return device.HasASTC();
}
bool TextureCacheRuntime::SupportsLinearFilter(VideoCore::Surface::PixelFormat format) const noexcept {
using VideoCore::Surface::GetFormatType;
using VideoCore::Surface::IsPixelFormatInteger;
if (IsPixelFormatInteger(format)) {
return false;
}
return GetFormatType(format) == VideoCore::Surface::SurfaceType::ColorTexture;
}
Image::Image(TextureCacheRuntime& runtime_, const VideoCommon::ImageInfo& info_, GPUVAddr gpu_addr_,
VAddr cpu_addr_)
: VideoCommon::ImageBase(info_, gpu_addr_, cpu_addr_), runtime{&runtime_} {
@@ -1238,13 +1229,6 @@ GLuint ImageView::StorageView(Shader::TextureType texture_type, Shader::ImageFor
return view;
}
GLuint ImageView::SampledView(Shader::TextureType view_type,
Shader::SamplerComponentType /*component_type*/) {
// OpenGL swizzles already configure depth/stencil selection per TIC entry,
// so fall back to the default view handle.
return Handle(view_type);
}
void ImageView::SetupView(Shader::TextureType view_type) {
views[static_cast<size_t>(view_type)] = MakeView(view_type, internal_format);
}
@@ -1,6 +1,3 @@
// 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
@@ -16,7 +13,6 @@
#include "video_core/renderer_opengl/gl_resource_manager.h"
#include "video_core/renderer_opengl/gl_staging_buffer_pool.h"
#include "video_core/renderer_opengl/util_shaders.h"
#include "video_core/surface.h"
#include "video_core/texture_cache/image_view_base.h"
#include "video_core/texture_cache/texture_cache_base.h"
@@ -133,8 +129,6 @@ public:
return false;
}
bool SupportsLinearFilter(VideoCore::Surface::PixelFormat format) const noexcept;
bool HasBrokenTextureViewFormats() const noexcept {
return has_broken_texture_view_formats;
}
@@ -143,8 +137,6 @@ public:
void TickFrame() {}
void WaitForGpuTick(u64) {}
StateTracker& GetStateTracker() {
return state_tracker;
}
@@ -272,9 +264,6 @@ public:
[[nodiscard]] GLuint StorageView(Shader::TextureType texture_type,
Shader::ImageFormat image_format);
[[nodiscard]] GLuint SampledView(Shader::TextureType view_type,
Shader::SamplerComponentType component_type);
[[nodiscard]] GLuint Handle(Shader::TextureType handle_type) const noexcept {
return views[static_cast<size_t>(handle_type)];
}
@@ -46,7 +46,7 @@ namespace Vulkan {
using VideoCommon::ImageViewType;
namespace {
[[nodiscard]] VkImageAspectFlags AspectMaskFromFormat(VideoCore::Surface::PixelFormat format) {
using VideoCore::Surface::SurfaceType;
switch (VideoCore::Surface::GetFormatType(format)) {
@@ -525,23 +525,23 @@ 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(device.IsExtShaderStencilExportSupported()
? 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(device.IsExtShaderStencilExportSupported()
? 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(device.IsExtShaderStencilExportSupported()
? 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)),
@@ -27,7 +27,6 @@ struct DynamicFeatures {
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_dual_source_blend;
bool has_dynamic_vertex_input;
};
@@ -173,7 +173,7 @@ FormatInfo SurfaceFormat(const Device& device, FormatType format_type, bool with
SURFACE_FORMAT_ELEM(VK_FORMAT_R16G16_SINT, usage_attachable | usage_storage, R16G16_SINT) \
SURFACE_FORMAT_ELEM(VK_FORMAT_R16G16_SNORM, usage_attachable | usage_storage, R16G16_SNORM) \
SURFACE_FORMAT_ELEM(VK_FORMAT_R32G32B32_SFLOAT, 0, R32G32B32_FLOAT) \
SURFACE_FORMAT_ELEM(VK_FORMAT_A8B8G8R8_SRGB_PACK32, usage_attachable | usage_storage, A8B8G8R8_SRGB) \
SURFACE_FORMAT_ELEM(VK_FORMAT_A8B8G8R8_SRGB_PACK32, usage_attachable, A8B8G8R8_SRGB) \
SURFACE_FORMAT_ELEM(VK_FORMAT_R8G8_UNORM, usage_attachable | usage_storage, R8G8_UNORM) \
SURFACE_FORMAT_ELEM(VK_FORMAT_R8G8_SNORM, usage_attachable | usage_storage, R8G8_SNORM) \
SURFACE_FORMAT_ELEM(VK_FORMAT_R8G8_SINT, usage_attachable | usage_storage, R8G8_SINT) \
@@ -185,7 +185,7 @@ FormatInfo SurfaceFormat(const Device& device, FormatType format_type, bool with
SURFACE_FORMAT_ELEM(VK_FORMAT_ASTC_8x8_UNORM_BLOCK, 0, ASTC_2D_8X8_UNORM) \
SURFACE_FORMAT_ELEM(VK_FORMAT_ASTC_8x5_UNORM_BLOCK, 0, ASTC_2D_8X5_UNORM) \
SURFACE_FORMAT_ELEM(VK_FORMAT_ASTC_5x4_UNORM_BLOCK, 0, ASTC_2D_5X4_UNORM) \
SURFACE_FORMAT_ELEM(VK_FORMAT_B8G8R8A8_SRGB, usage_attachable | usage_storage, B8G8R8A8_SRGB) \
SURFACE_FORMAT_ELEM(VK_FORMAT_B8G8R8A8_SRGB, usage_attachable, B8G8R8A8_SRGB) \
SURFACE_FORMAT_ELEM(VK_FORMAT_BC1_RGBA_SRGB_BLOCK, 0, BC1_RGBA_SRGB) \
SURFACE_FORMAT_ELEM(VK_FORMAT_BC2_SRGB_BLOCK, 0, BC2_SRGB) \
SURFACE_FORMAT_ELEM(VK_FORMAT_BC3_SRGB_BLOCK, 0, BC3_SRGB) \
@@ -189,16 +189,12 @@ inline void PushImageDescriptors(TextureCache& texture_cache,
const VideoCommon::ImageViewId image_view_id{(views++)->id};
const VideoCommon::SamplerId sampler_id{*(samplers++)};
ImageView& image_view{texture_cache.GetImageView(image_view_id)};
const VkImageView vk_image_view{
image_view.SampledView(desc.type, desc.component_type)};
const VkImageView vk_image_view{image_view.Handle(desc.type)};
const Sampler& sampler{texture_cache.GetSampler(sampler_id)};
const bool supports_linear_filter{
texture_cache.SupportsLinearFilter(image_view.format)};
const bool supports_depth_compare_sampling{
image_view.SupportsDepthCompareSampling()};
const VkSampler vk_sampler{
sampler.SelectHandle(supports_linear_filter, image_view.SupportsAnisotropy(),
supports_depth_compare_sampling)};
const bool use_fallback_sampler{sampler.HasAddedAnisotropy() &&
!image_view.SupportsAnisotropy()};
const VkSampler vk_sampler{use_fallback_sampler ? sampler.HandleWithDefaultAnisotropy()
: sampler.Handle()};
guest_descriptor_queue.AddSampledImage(vk_image_view, vk_sampler);
rescaling.PushTexture(texture_cache.IsRescaling(image_view));
}
@@ -280,6 +280,7 @@ void Layer::UpdateRawImage(const Tegra::FramebufferConfig& framebuffer, size_t i
Tegra::Texture::UnswizzleTexture(
mapped_span.subspan(image_offset, linear_size), std::span(host_ptr, tiled_size),
bytes_per_pixel, framebuffer.width, framebuffer.height, 1, block_height_log2, 0);
buffer.Flush(); // Ensure host writes are visible before the GPU copy.
}
const VkBufferImageCopy copy{
@@ -7,7 +7,6 @@
#include <algorithm>
#include <array>
#include <cstring>
#include <limits>
#include <span>
#include <vector>
@@ -554,15 +553,7 @@ void BufferCacheRuntime::BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset
if (index >= device.GetMaxVertexInputBindings()) {
return;
}
if (!device.HasNullDescriptor() && buffer == VK_NULL_HANDLE) {
ReserveNullBuffer();
buffer = *null_buffer;
offset = 0;
size = std::numeric_limits<u32>::max();
}
// 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()) {
if (device.IsExtExtendedDynamicStateSupported()) {
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;
@@ -602,8 +593,7 @@ void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bi
if (binding_count == 0) {
return;
}
// Use BindVertexBuffers2EXT only if EDS1 is supported AND VIDS is not active
if (device.IsExtExtendedDynamicStateSupported() && !device.IsExtVertexInputDynamicStateSupported()) {
if (device.IsExtExtendedDynamicStateSupported()) {
scheduler.Record([bindings_ = std::move(bindings),
buffer_handles_ = std::move(buffer_handles),
binding_count](vk::CommandBuffer cmdbuf) {
@@ -660,50 +650,27 @@ void BufferCacheRuntime::BindTransformFeedbackBuffers(VideoCommon::HostBindings<
}
void BufferCacheRuntime::ReserveNullBuffer() {
const VkBufferUsageFlags expected_usage = NullBufferUsageFlags();
if (null_buffer && null_buffer_usage_flags != expected_usage) {
RefreshNullBuffer();
}
if (!null_buffer) {
null_buffer = CreateNullBuffer();
}
}
VkBufferUsageFlags BufferCacheRuntime::NullBufferUsageFlags() const {
VkBufferUsageFlags usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT |
VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT |
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT |
VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT;
if (device.IsExtTransformFeedbackSupported()) {
usage |= VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT;
}
return usage;
}
void BufferCacheRuntime::RefreshNullBuffer() {
if (!null_buffer) {
return;
}
scheduler.Finish();
null_buffer.reset();
null_buffer = CreateNullBuffer();
}
vk::Buffer BufferCacheRuntime::CreateNullBuffer() {
VkBufferCreateInfo create_info{
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.size = 4,
.usage = NullBufferUsageFlags(),
.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT |
VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
};
if (device.IsExtTransformFeedbackSupported()) {
create_info.usage |= VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT;
}
vk::Buffer ret = memory_allocator.CreateBuffer(create_info, MemoryUsage::DeviceLocal);
null_buffer_usage_flags = create_info.usage;
if (device.HasDebuggingToolAttached()) {
ret.SetObjectNameEXT("Null buffer");
}
@@ -131,9 +131,6 @@ public:
void BindTransformFeedbackBuffers(VideoCommon::HostBindings<Buffer>& bindings);
/// Forces destruction and recreation of the shared null buffer so new usage flags take effect.
void RefreshNullBuffer();
std::span<u8> BindMappedUniformBuffer([[maybe_unused]] size_t stage,
[[maybe_unused]] u32 binding_index,
u32 size) {
@@ -171,7 +168,6 @@ private:
void ReserveNullBuffer();
vk::Buffer CreateNullBuffer();
VkBufferUsageFlags NullBufferUsageFlags() const;
const Device& device;
MemoryAllocator& memory_allocator;
@@ -183,7 +179,6 @@ private:
std::shared_ptr<QuadStripIndexBuffer> quad_strip_index_buffer;
vk::Buffer null_buffer;
VkBufferUsageFlags null_buffer_usage_flags = 0;
std::unique_ptr<Uint8Pass> uint8_pass;
QuadIndexedPass quad_index_pass;
@@ -462,14 +462,10 @@ QueriesPrefixScanPass::QueriesPrefixScanPass(
device_, descriptor_pool_, QUERIES_SCAN_DESCRIPTOR_SET_BINDINGS,
QUERIES_SCAN_DESCRIPTOR_UPDATE_TEMPLATE, QUERIES_SCAN_BANK_INFO,
COMPUTE_PUSH_CONSTANT_RANGE<sizeof(QueriesPrefixScanPushConstants)>,
device_.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_BASIC_BIT,
VK_SHADER_STAGE_COMPUTE_BIT) &&
device_.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_ARITHMETIC_BIT,
VK_SHADER_STAGE_COMPUTE_BIT) &&
device_.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_SHUFFLE_BIT,
VK_SHADER_STAGE_COMPUTE_BIT) &&
device_.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_SHUFFLE_RELATIVE_BIT,
VK_SHADER_STAGE_COMPUTE_BIT)
device_.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_BASIC_BIT) &&
device_.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_ARITHMETIC_BIT) &&
device_.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_SHUFFLE_BIT) &&
device_.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_SHUFFLE_RELATIVE_BIT)
? std::span<const u32>(QUERIES_PREFIX_SCAN_SUM_COMP_SPV)
: std::span<const u32>(QUERIES_PREFIX_SCAN_SUM_NOSUBGROUPS_COMP_SPV)),
scheduler{scheduler_}, compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {}
@@ -18,49 +18,14 @@
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
#include "video_core/shader_notify.h"
#include "video_core/surface.h"
#include "video_core/texture_cache/texture_cache.h"
#include "video_core/vulkan_common/vulkan_device.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
#include <optional>
namespace Vulkan {
using Shader::ImageBufferDescriptor;
using Shader::Backend::SPIRV::RESCALING_LAYOUT_WORDS_OFFSET;
using Tegra::Texture::TexturePair;
using VideoCore::Surface::PixelFormat;
using VideoCore::Surface::PixelFormatNumeric;
static std::optional<PixelFormatNumeric> NumericFromComponentType(
Shader::SamplerComponentType component_type) {
switch (component_type) {
case Shader::SamplerComponentType::Float:
return PixelFormatNumeric::Float;
case Shader::SamplerComponentType::Sint:
return PixelFormatNumeric::Sint;
case Shader::SamplerComponentType::Uint:
return PixelFormatNumeric::Uint;
default:
return std::nullopt;
}
}
static PixelFormat ResolveTexelBufferFormat(PixelFormat format,
Shader::SamplerComponentType component_type) {
const auto desired_numeric = NumericFromComponentType(component_type);
if (!desired_numeric) {
return format;
}
const auto current_numeric = VideoCore::Surface::GetPixelFormatNumericType(format);
if (*desired_numeric == current_numeric) {
return format;
}
if (const auto variant = VideoCore::Surface::FindPixelFormatVariant(format, *desired_numeric)) {
return *variant;
}
return format;
}
ComputePipeline::ComputePipeline(const Device& device_, vk::PipelineCache& pipeline_cache_,
DescriptorPool& descriptor_pool,
@@ -217,12 +182,8 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
is_written = desc.is_written;
}
ImageView& image_view = texture_cache.GetImageView(views[index].id);
VideoCore::Surface::PixelFormat buffer_format = image_view.format;
if constexpr (!is_image) {
buffer_format = ResolveTexelBufferFormat(buffer_format, desc.component_type);
}
buffer_cache.BindComputeTextureBuffer(index, image_view.GpuAddr(),
image_view.BufferSize(), buffer_format,
image_view.BufferSize(), image_view.format,
is_written, is_image);
++index;
}
@@ -34,10 +34,6 @@ public:
void Wait();
[[nodiscard]] u64 WaitTick() const noexcept {
return wait_tick;
}
private:
Scheduler& scheduler;
u64 wait_tick = 0;
@@ -5,8 +5,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
#include <optional>
#include <iostream>
#include <span>
@@ -25,9 +23,7 @@
#include "video_core/renderer_vulkan/vk_texture_cache.h"
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
#include "video_core/shader_notify.h"
#include "video_core/texture_cache/samples_helper.h"
#include "video_core/texture_cache/texture_cache.h"
#include "video_core/surface.h"
#include "video_core/vulkan_common/vulkan_device.h"
#if defined(_MSC_VER) && defined(NDEBUG)
@@ -48,83 +44,10 @@ using Tegra::Texture::TexturePair;
using VideoCore::Surface::PixelFormat;
using VideoCore::Surface::PixelFormatFromDepthFormat;
using VideoCore::Surface::PixelFormatFromRenderTargetFormat;
using VideoCore::Surface::PixelFormatNumeric;
constexpr size_t NUM_STAGES = Maxwell::MaxShaderStage;
constexpr size_t MAX_IMAGE_ELEMENTS = 64;
std::optional<PixelFormatNumeric> NumericFromComponentType(
Shader::SamplerComponentType component_type) {
switch (component_type) {
case Shader::SamplerComponentType::Float:
return PixelFormatNumeric::Float;
case Shader::SamplerComponentType::Sint:
return PixelFormatNumeric::Sint;
case Shader::SamplerComponentType::Uint:
return PixelFormatNumeric::Uint;
default:
return std::nullopt;
}
}
PixelFormat ResolveTexelBufferFormat(PixelFormat format,
Shader::SamplerComponentType component_type) {
const auto desired_numeric = NumericFromComponentType(component_type);
if (!desired_numeric) {
return format;
}
const auto current_numeric = VideoCore::Surface::GetPixelFormatNumericType(format);
if (*desired_numeric == current_numeric) {
return format;
}
if (const auto variant = VideoCore::Surface::FindPixelFormatVariant(format, *desired_numeric)) {
return *variant;
}
return format;
}
bool UsesDualSourceFactor(Maxwell::Blend::Factor factor) {
switch (factor) {
case Maxwell::Blend::Factor::Source1Color_D3D:
case Maxwell::Blend::Factor::Source1Color_GL:
case Maxwell::Blend::Factor::OneMinusSource1Color_D3D:
case Maxwell::Blend::Factor::OneMinusSource1Color_GL:
case Maxwell::Blend::Factor::Source1Alpha_D3D:
case Maxwell::Blend::Factor::Source1Alpha_GL:
case Maxwell::Blend::Factor::OneMinusSource1Alpha_D3D:
case Maxwell::Blend::Factor::OneMinusSource1Alpha_GL:
return true;
default:
return false;
}
}
Maxwell::Blend::Factor FallbackDualSourceFactor(Maxwell::Blend::Factor factor) {
switch (factor) {
case Maxwell::Blend::Factor::Source1Color_D3D:
case Maxwell::Blend::Factor::Source1Color_GL:
return Maxwell::Blend::Factor::SourceColor_D3D;
case Maxwell::Blend::Factor::OneMinusSource1Color_D3D:
case Maxwell::Blend::Factor::OneMinusSource1Color_GL:
return Maxwell::Blend::Factor::OneMinusSourceColor_D3D;
case Maxwell::Blend::Factor::Source1Alpha_D3D:
case Maxwell::Blend::Factor::Source1Alpha_GL:
return Maxwell::Blend::Factor::SourceAlpha_D3D;
case Maxwell::Blend::Factor::OneMinusSource1Alpha_D3D:
case Maxwell::Blend::Factor::OneMinusSource1Alpha_GL:
return Maxwell::Blend::Factor::OneMinusSourceAlpha_D3D;
default:
return factor;
}
}
bool AttachmentUsesDualSource(const FixedPipelineState::BlendingAttachment& blend) {
return UsesDualSourceFactor(blend.SourceRGBFactor()) ||
UsesDualSourceFactor(blend.DestRGBFactor()) ||
UsesDualSourceFactor(blend.SourceAlphaFactor()) ||
UsesDualSourceFactor(blend.DestAlphaFactor());
}
DescriptorLayoutBuilder MakeBuilder(const Device& device, std::span<const Shader::Info> infos) {
DescriptorLayoutBuilder builder{device};
for (size_t index = 0; index < infos.size(); ++index) {
@@ -494,12 +417,8 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
is_written = desc.is_written;
}
ImageView& image_view{texture_cache.GetImageView(texture_buffer_it->id)};
VideoCore::Surface::PixelFormat buffer_format = image_view.format;
if constexpr (!is_image) {
buffer_format = ResolveTexelBufferFormat(buffer_format, desc.component_type);
}
buffer_cache.BindGraphicsTextureBuffer(stage, index, image_view.GpuAddr(),
image_view.BufferSize(), buffer_format,
image_view.BufferSize(), image_view.format,
is_written, is_image);
++index;
++texture_buffer_it;
@@ -828,13 +747,11 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
const bool supports_alpha_output = fragment_has_color0_output;
const bool alpha_to_one_supported = device.SupportsAlphaToOne();
const auto msaa_mode = key.state.msaa_mode.Value();
const VkSampleCountFlagBits vk_samples = MaxwellToVK::MsaaMode(msaa_mode);
VkPipelineMultisampleStateCreateInfo multisample_ci{
const VkPipelineMultisampleStateCreateInfo multisample_ci{
.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.rasterizationSamples = vk_samples,
.rasterizationSamples = MaxwellToVK::MsaaMode(key.state.msaa_mode),
.sampleShadingEnable = Settings::values.sample_shading.GetValue() > 0 ? VK_TRUE : VK_FALSE,
.minSampleShading = f32(Settings::values.sample_shading.GetValue()) / 100.0f,
.pSampleMask = nullptr,
@@ -843,8 +760,6 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
.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,
.pNext = nullptr,
@@ -866,12 +781,6 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
}
static_vector<VkPipelineColorBlendAttachmentState, Maxwell::NumRenderTargets> cb_attachments;
const size_t num_attachments{NumAttachments(key.state)};
const bool supports_dual_source_blend = device.SupportsDualSourceBlend();
const u32 max_dual_source_attachments = supports_dual_source_blend
? device.MaxFragmentDualSrcAttachments()
: 0;
u32 granted_dual_source_attachments = 0;
bool logged_dual_source_warning = false;
for (size_t index = 0; index < num_attachments; ++index) {
static constexpr std::array mask_table{
VK_COLOR_COMPONENT_R_BIT,
@@ -885,30 +794,13 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
for (size_t i = 0; i < mask_table.size(); ++i) {
write_mask |= mask[i] ? mask_table[i] : 0;
}
const bool attachment_uses_dual_source = AttachmentUsesDualSource(blend);
const bool allow_dual_source = attachment_uses_dual_source && supports_dual_source_blend &&
granted_dual_source_attachments < max_dual_source_attachments;
if (allow_dual_source) {
++granted_dual_source_attachments;
} else if (attachment_uses_dual_source && !logged_dual_source_warning) {
LOG_WARNING(Render_Vulkan,
"Dual-source blend factors exceed device limit (maxFragmentDualSrcAttachments={}), falling back to single-source factors",
max_dual_source_attachments);
logged_dual_source_warning = true;
}
const auto sanitize_factor = [&](Maxwell::Blend::Factor factor) {
if (allow_dual_source || !UsesDualSourceFactor(factor)) {
return factor;
}
return FallbackDualSourceFactor(factor);
};
cb_attachments.push_back({
.blendEnable = blend.enable != 0,
.srcColorBlendFactor = MaxwellToVK::BlendFactor(sanitize_factor(blend.SourceRGBFactor())),
.dstColorBlendFactor = MaxwellToVK::BlendFactor(sanitize_factor(blend.DestRGBFactor())),
.srcColorBlendFactor = MaxwellToVK::BlendFactor(blend.SourceRGBFactor()),
.dstColorBlendFactor = MaxwellToVK::BlendFactor(blend.DestRGBFactor()),
.colorBlendOp = MaxwellToVK::BlendEquation(blend.EquationRGB()),
.srcAlphaBlendFactor = MaxwellToVK::BlendFactor(sanitize_factor(blend.SourceAlphaFactor())),
.dstAlphaBlendFactor = MaxwellToVK::BlendFactor(sanitize_factor(blend.DestAlphaFactor())),
.srcAlphaBlendFactor = MaxwellToVK::BlendFactor(blend.SourceAlphaFactor()),
.dstAlphaBlendFactor = MaxwellToVK::BlendFactor(blend.DestAlphaFactor()),
.alphaBlendOp = MaxwellToVK::BlendEquation(blend.EquationAlpha()),
.colorWriteMask = write_mask,
});
@@ -924,23 +816,12 @@ 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_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_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,
@@ -953,20 +834,20 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
VK_DYNAMIC_STATE_STENCIL_OP_EXT,
};
dynamic_states.insert(dynamic_states.end(), extended.begin(), extended.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);
}
}
// 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{
@@ -976,12 +857,12 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
};
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{
@@ -991,7 +872,7 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
};
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()) {
@@ -36,7 +36,6 @@
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_shader_util.h"
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
#include "video_core/surface.h"
#include "video_core/shader_cache.h"
#include "video_core/shader_environment.h"
#include "video_core/shader_notify.h"
@@ -106,41 +105,6 @@ Shader::CompareFunction MaxwellToCompareFunction(Maxwell::ComparisonOp compariso
return {};
}
Shader::AttributeType RenderTargetAttributeType(Tegra::RenderTargetFormat format) {
if (format == Tegra::RenderTargetFormat::NONE) {
return Shader::AttributeType::Float;
}
const auto pixel_format{
VideoCore::Surface::PixelFormatFromRenderTargetFormat(format)};
if (!VideoCore::Surface::IsPixelFormatInteger(pixel_format)) {
return Shader::AttributeType::Float;
}
if (VideoCore::Surface::IsPixelFormatSignedInteger(pixel_format)) {
return Shader::AttributeType::SignedInt;
}
return Shader::AttributeType::UnsignedInt;
}
VkShaderStageFlagBits StageToVkStage(Shader::Stage stage) {
switch (stage) {
case Shader::Stage::VertexA:
case Shader::Stage::VertexB:
return VK_SHADER_STAGE_VERTEX_BIT;
case Shader::Stage::TessellationControl:
return VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT;
case Shader::Stage::TessellationEval:
return VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT;
case Shader::Stage::Geometry:
return VK_SHADER_STAGE_GEOMETRY_BIT;
case Shader::Stage::Fragment:
return VK_SHADER_STAGE_FRAGMENT_BIT;
case Shader::Stage::Compute:
return VK_SHADER_STAGE_COMPUTE_BIT;
default:
return VK_SHADER_STAGE_VERTEX_BIT;
}
}
Shader::AttributeType CastAttributeType(const FixedPipelineState::VertexAttribute& attr) {
if (attr.enabled == 0) {
return Shader::AttributeType::Disabled;
@@ -274,10 +238,6 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span<const Shader::IR::Program> program
info.alpha_test_func = MaxwellToCompareFunction(
key.state.UnpackComparisonOp(key.state.alpha_test_func.Value()));
info.alpha_test_reference = std::bit_cast<float>(key.state.alpha_test_ref);
for (size_t index = 0; index < Maxwell::NumRenderTargets; ++index) {
const auto format = static_cast<Tegra::RenderTargetFormat>(key.state.color_formats[index]);
info.color_output_types[index] = RenderTargetAttributeType(format);
}
break;
default:
break;
@@ -318,7 +278,8 @@ size_t GetTotalPipelineWorkers() {
const size_t max_core_threads =
std::max<size_t>(static_cast<size_t>(std::thread::hardware_concurrency()), 2ULL) - 1ULL;
#ifdef ANDROID
// Leave at least one core free on Android to reduce thermal pressure.
// Leave at least one core free on Android. Previously we reserved two, but
// shipping builds benefit from one extra compilation worker.
constexpr size_t free_cores = 1ULL;
if (max_core_threads <= free_cores) {
return 1ULL;
@@ -366,7 +327,6 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
"VkPipelineBuilder"),
serialization_thread(1, "VkPipelineSerialization") {
const auto& float_control{device.FloatControlProperties()};
const bool float_controls_supported{device.IsKhrShaderFloatControlsSupported()};
const VkDriverId driver_id{device.GetDriverID()};
profile = Shader::Profile{
.supported_spirv = device.SupportedSpirvVersion(),
@@ -376,24 +336,20 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
.support_int16 = device.IsShaderInt16Supported(),
.support_int64 = device.IsShaderInt64Supported(),
.support_vertex_instance_id = false,
.support_float_controls = float_controls_supported,
.support_separate_denorm_behavior = float_controls_supported &&
.support_float_controls = device.IsKhrShaderFloatControlsSupported(),
.support_separate_denorm_behavior =
float_control.denormBehaviorIndependence == VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_ALL,
.support_separate_rounding_mode = float_controls_supported &&
.support_separate_rounding_mode =
float_control.roundingModeIndependence == VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_ALL,
.support_fp16_denorm_preserve = float_controls_supported &&
float_control.shaderDenormPreserveFloat16 != VK_FALSE,
.support_fp32_denorm_preserve = float_controls_supported &&
float_control.shaderDenormPreserveFloat32 != VK_FALSE,
.support_fp16_denorm_flush = float_controls_supported &&
float_control.shaderDenormFlushToZeroFloat16 != VK_FALSE,
.support_fp32_denorm_flush = float_controls_supported &&
float_control.shaderDenormFlushToZeroFloat32 != VK_FALSE,
.support_fp16_signed_zero_nan_preserve = float_controls_supported &&
.support_fp16_denorm_preserve = float_control.shaderDenormPreserveFloat16 != VK_FALSE,
.support_fp32_denorm_preserve = float_control.shaderDenormPreserveFloat32 != VK_FALSE,
.support_fp16_denorm_flush = float_control.shaderDenormFlushToZeroFloat16 != VK_FALSE,
.support_fp32_denorm_flush = float_control.shaderDenormFlushToZeroFloat32 != VK_FALSE,
.support_fp16_signed_zero_nan_preserve =
float_control.shaderSignedZeroInfNanPreserveFloat16 != VK_FALSE,
.support_fp32_signed_zero_nan_preserve = float_controls_supported &&
.support_fp32_signed_zero_nan_preserve =
float_control.shaderSignedZeroInfNanPreserveFloat32 != VK_FALSE,
.support_fp64_signed_zero_nan_preserve = float_controls_supported &&
.support_fp64_signed_zero_nan_preserve =
float_control.shaderSignedZeroInfNanPreserveFloat64 != VK_FALSE,
.support_explicit_workgroup_layout = device.IsKhrWorkgroupMemoryExplicitLayoutSupported(),
.support_vote = device.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_VOTE_BIT),
@@ -449,27 +405,6 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
.support_conditional_barrier = device.SupportsConditionalBarriers(),
};
profile.warp_stage_support_mask = 0;
static constexpr std::array kAllStages{
Shader::Stage::VertexA, Shader::Stage::VertexB,
Shader::Stage::TessellationControl, Shader::Stage::TessellationEval,
Shader::Stage::Geometry, Shader::Stage::Fragment,
Shader::Stage::Compute,
};
for (const auto stage : kAllStages) {
const auto vk_stage = StageToVkStage(stage);
if (device.SupportsWarpIntrinsics(vk_stage)) {
profile.warp_stage_support_mask |= 1u << static_cast<u32>(stage);
}
}
profile.support_vote = profile.warp_stage_support_mask != 0;
if (!profile.SupportsWarpIntrinsics(Shader::Stage::Fragment)) {
LOG_WARNING(Render_Vulkan,
"Fragment shaders lack subgroup support on this driver; warp intrinsics will be "
"approximated and visual artifacts may remain");
}
if (device.GetMaxVertexInputAttributes() < Maxwell::NumVertexAttributes) {
LOG_WARNING(Render_Vulkan, "maxVertexInputAttributes is too low: {} < {}",
device.GetMaxVertexInputAttributes(), Maxwell::NumVertexAttributes);
@@ -480,38 +415,33 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
}
LOG_INFO(Render_Vulkan, "DynamicState setting value: {}", u32(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 =
dynamic_features.has_extended_dynamic_state =
device.IsExtExtendedDynamicStateSupported();
dynamic_features.has_extended_dynamic_state_2 =
dynamic_features.has_extended_dynamic_state_2 =
device.IsExtExtendedDynamicState2Supported();
dynamic_features.has_extended_dynamic_state_2_logic_op =
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 =
dynamic_features.has_extended_dynamic_state_3_blend =
device.IsExtExtendedDynamicState3BlendingSupported();
dynamic_features.has_dual_source_blend = device.SupportsDualSourceBlend();
if (!dynamic_features.has_dual_source_blend) {
LOG_WARNING(Render_Vulkan, "Dual-source blending unsupported, disabling dynamic blend");
dynamic_features.has_extended_dynamic_state_3_blend = false;
}
dynamic_features.has_extended_dynamic_state_3_enables =
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() &&
dynamic_features.has_dynamic_vertex_input =
device.IsExtVertexInputDynamicStateSupported() &&
Settings::values.vertex_input_dynamic_state.GetValue();
}
@@ -522,13 +452,6 @@ PipelineCache::~PipelineCache() {
}
}
void PipelineCache::DrainPendingBuilds() {
if (!device.HasBrokenParallelShaderCompiling()) {
return;
}
workers.WaitForRequests();
}
GraphicsPipeline* PipelineCache::CurrentGraphicsPipeline() {
if (!RefreshStages(graphics_key.unique_hashes)) {
@@ -559,17 +482,12 @@ ComputePipeline* PipelineCache::CurrentComputePipeline() {
.shared_memory_size = qmd.shared_alloc,
.workgroup_size{qmd.block_dim_x, qmd.block_dim_y, qmd.block_dim_z},
};
const auto [pair, inserted]{compute_cache.try_emplace(key)};
const auto [pair, is_new]{compute_cache.try_emplace(key)};
auto& pipeline{pair->second};
if (!pipeline) {
auto [slot, should_build] = AcquireComputeBuildSlot(key);
if (!should_build) {
WaitForBuildCompletion(slot);
} else {
pipeline = CreateComputePipeline(key, shader);
ReleaseComputeBuildSlot(key, slot);
}
if (!is_new) {
return pipeline.get();
}
pipeline = CreateComputePipeline(key, shader);
return pipeline.get();
}
@@ -685,20 +603,13 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading
}
GraphicsPipeline* PipelineCache::CurrentGraphicsPipelineSlowPath() {
const auto [pair, inserted]{graphics_cache.try_emplace(graphics_key)};
const auto [pair, is_new]{graphics_cache.try_emplace(graphics_key)};
auto& pipeline{pair->second};
if (is_new) {
pipeline = CreateGraphicsPipeline();
}
if (!pipeline) {
const auto key = pair->first;
auto [slot, should_build] = AcquireGraphicsBuildSlot(key);
if (!should_build) {
WaitForBuildCompletion(slot);
} else {
pipeline = CreateGraphicsPipeline();
ReleaseGraphicsBuildSlot(key, slot);
}
if (!pipeline) {
return nullptr;
}
return nullptr;
}
if (current_pipeline) {
current_pipeline->AddTransition(pipeline.get());
@@ -721,7 +632,6 @@ GraphicsPipeline* PipelineCache::BuiltPipeline(GraphicsPipeline* pipeline) const
if (draw_state.index_buffer.count <= 6 || draw_state.vertex_buffer.count <= 6) {
return pipeline;
}
scheduler.KeepAliveTick();
return nullptr;
}
@@ -825,10 +735,6 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(
}
LOG_ERROR(Render_Vulkan, "{}", exception.what());
return nullptr;
} catch (const vk::Exception& exception) {
LOG_ERROR(Render_Vulkan, "Failed to create graphics pipeline 0x{:016x}: {}", key.Hash(),
exception.what());
return nullptr;
}
std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline() {
@@ -920,10 +826,6 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
} catch (const Shader::Exception& exception) {
LOG_ERROR(Render_Vulkan, "{}", exception.what());
return nullptr;
} catch (const vk::Exception& exception) {
LOG_ERROR(Render_Vulkan, "Failed to create compute pipeline 0x{:016x}: {}", key.Hash(),
exception.what());
return nullptr;
}
void PipelineCache::SerializeVulkanPipelineCache(const std::filesystem::path& filename,
@@ -1021,68 +923,4 @@ vk::PipelineCache PipelineCache::LoadVulkanPipelineCache(const std::filesystem::
}
}
auto PipelineCache::AcquireGraphicsBuildSlot(const GraphicsPipelineCacheKey& key)
-> std::pair<InFlightPipelinePtr, bool> {
std::scoped_lock lock(graphics_inflight_mutex);
auto [it, inserted] = graphics_inflight_builds.try_emplace(key);
if (inserted || !it->second) {
it->second = std::make_shared<InFlightPipelineBuild>();
return {it->second, true};
}
return {it->second, false};
}
auto PipelineCache::AcquireComputeBuildSlot(const ComputePipelineCacheKey& key)
-> std::pair<InFlightPipelinePtr, bool> {
std::scoped_lock lock(compute_inflight_mutex);
auto [it, inserted] = compute_inflight_builds.try_emplace(key);
if (inserted || !it->second) {
it->second = std::make_shared<InFlightPipelineBuild>();
return {it->second, true};
}
return {it->second, false};
}
void PipelineCache::ReleaseGraphicsBuildSlot(const GraphicsPipelineCacheKey& key,
const InFlightPipelinePtr& slot) {
if (!slot) {
return;
}
{
std::scoped_lock slot_lock(slot->mutex);
slot->building = false;
}
slot->cv.notify_all();
std::scoped_lock map_lock(graphics_inflight_mutex);
auto it = graphics_inflight_builds.find(key);
if (it != graphics_inflight_builds.end() && it->second == slot) {
graphics_inflight_builds.erase(it);
}
}
void PipelineCache::ReleaseComputeBuildSlot(const ComputePipelineCacheKey& key,
const InFlightPipelinePtr& slot) {
if (!slot) {
return;
}
{
std::scoped_lock slot_lock(slot->mutex);
slot->building = false;
}
slot->cv.notify_all();
std::scoped_lock map_lock(compute_inflight_mutex);
auto it = compute_inflight_builds.find(key);
if (it != compute_inflight_builds.end() && it->second == slot) {
compute_inflight_builds.erase(it);
}
}
void PipelineCache::WaitForBuildCompletion(const InFlightPipelinePtr& slot) const {
if (!slot) {
return;
}
std::unique_lock lock(slot->mutex);
slot->cv.wait(lock, [&] { return !slot->building; });
}
} // namespace Vulkan
@@ -5,10 +5,8 @@
#include <array>
#include <cstddef>
#include <condition_variable>
#include <filesystem>
#include <memory>
#include <mutex>
#include <type_traits>
#include <unordered_map>
#include <vector>
@@ -115,17 +113,7 @@ public:
void LoadDiskResources(u64 title_id, std::stop_token stop_loading,
const VideoCore::DiskResourceLoadCallback& callback);
void DrainPendingBuilds();
private:
struct InFlightPipelineBuild {
std::mutex mutex;
std::condition_variable cv;
bool building{true};
};
using InFlightPipelinePtr = std::shared_ptr<InFlightPipelineBuild>;
[[nodiscard]] GraphicsPipeline* CurrentGraphicsPipelineSlowPath();
[[nodiscard]] GraphicsPipeline* BuiltPipeline(GraphicsPipeline* pipeline) const noexcept;
@@ -152,14 +140,6 @@ private:
vk::PipelineCache LoadVulkanPipelineCache(const std::filesystem::path& filename,
u32 expected_cache_version);
std::pair<InFlightPipelinePtr, bool> AcquireGraphicsBuildSlot(
const GraphicsPipelineCacheKey& key);
std::pair<InFlightPipelinePtr, bool> AcquireComputeBuildSlot(
const ComputePipelineCacheKey& key);
void ReleaseGraphicsBuildSlot(const GraphicsPipelineCacheKey& key, const InFlightPipelinePtr& slot);
void ReleaseComputeBuildSlot(const ComputePipelineCacheKey& key, const InFlightPipelinePtr& slot);
void WaitForBuildCompletion(const InFlightPipelinePtr& slot) const;
const Device& device;
Scheduler& scheduler;
DescriptorPool& descriptor_pool;
@@ -178,11 +158,6 @@ private:
std::unordered_map<ComputePipelineCacheKey, std::unique_ptr<ComputePipeline>> compute_cache;
std::unordered_map<GraphicsPipelineCacheKey, std::unique_ptr<GraphicsPipeline>> graphics_cache;
std::mutex graphics_inflight_mutex;
std::unordered_map<GraphicsPipelineCacheKey, InFlightPipelinePtr> graphics_inflight_builds;
std::mutex compute_inflight_mutex;
std::unordered_map<ComputePipelineCacheKey, InFlightPipelinePtr> compute_inflight_builds;
ShaderPools main_pools;
Shader::Profile profile;
@@ -42,8 +42,7 @@ public:
static constexpr size_t BANK_SIZE = 256;
static constexpr size_t QUERY_SIZE = 8;
explicit SamplesQueryBank(const Device& device_, size_t index_)
: BankBase(BANK_SIZE), device{device_}, index{index_},
supports_host_query_reset{device_.SupportsHostQueryReset()} {
: BankBase(BANK_SIZE), device{device_}, index{index_} {
const auto& dev = device.GetLogical();
query_pool = dev.CreateQueryPool({
.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO,
@@ -61,10 +60,8 @@ public:
void Reset() override {
ASSERT(references == 0);
VideoCommon::BankBase::Reset();
if (supports_host_query_reset) {
const auto& dev = device.GetLogical();
dev.ResetQueryPool(*query_pool, 0, BANK_SIZE);
}
const auto& dev = device.GetLogical();
dev.ResetQueryPool(*query_pool, 0, BANK_SIZE);
host_results.fill(0ULL);
next_bank = 0;
}
@@ -102,7 +99,6 @@ public:
private:
const Device& device;
const size_t index;
const bool supports_host_query_reset;
vk::QueryPool query_pool;
std::array<u64, BANK_SIZE> host_results;
};
@@ -1206,24 +1202,21 @@ struct QueryCacheRuntimeImpl {
hcr_setup.pNext = nullptr;
hcr_setup.flags = 0;
if (device.IsExtConditionalRendering()) {
conditional_resolve_pass = std::make_unique<ConditionalRenderingResolvePass>(
device, scheduler, descriptor_pool, compute_pass_descriptor_queue);
conditional_resolve_pass = std::make_unique<ConditionalRenderingResolvePass>(
device, scheduler, descriptor_pool, compute_pass_descriptor_queue);
const VkBufferCreateInfo buffer_ci = {
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.size = sizeof(u32),
.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
};
hcr_resolve_buffer =
memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal);
}
const VkBufferCreateInfo buffer_ci = {
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.size = sizeof(u32),
.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
};
hcr_resolve_buffer = memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal);
}
VideoCore::RasterizerInterface* rasterizer;
+144 -178
View File
@@ -197,7 +197,7 @@ 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");
@@ -215,10 +215,6 @@ void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) {
FlushWork();
gpu_memory->FlushCaching();
if (device.HasBrokenParallelShaderCompiling()) {
pipeline_cache.DrainPendingBuilds();
}
GraphicsPipeline* const pipeline{pipeline_cache.CurrentGraphicsPipeline()};
if (!pipeline) {
return;
@@ -243,7 +239,7 @@ void RasterizerVulkan::Draw(bool is_indexed, u32 instance_count) {
const auto& draw_state = maxwell3d->draw_manager->GetDrawState();
const u32 num_instances{instance_count};
const DrawParams draw_params{MakeDrawParams(draw_state, num_instances, is_indexed)};
// 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) {
@@ -991,7 +987,7 @@ 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);
@@ -1000,6 +996,7 @@ void RasterizerVulkan::UpdateDynamicStates() {
UpdateDepthBounds(regs);
UpdateStencilFaces(regs);
UpdateLineWidth(regs);
// EDS1: CullMode, DepthCompare, FrontFace, StencilOp, DepthBoundsTest, DepthTest, DepthWrite, StencilTest
if (device.IsExtExtendedDynamicStateSupported()) {
UpdateCullMode(regs);
@@ -1013,19 +1010,19 @@ void RasterizerVulkan::UpdateDynamicStates() {
UpdateStencilTestEnable(regs);
}
}
// 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;
@@ -1050,12 +1047,12 @@ void RasterizerVulkan::UpdateDynamicStates() {
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()) {
@@ -1087,7 +1084,7 @@ void RasterizerVulkan::HandleTransformFeedback() {
UNIMPLEMENTED_IF(regs.IsShaderConfigEnabled(Maxwell::ShaderType::TessellationInit) ||
regs.IsShaderConfigEnabled(Maxwell::ShaderType::Tessellation));
}
}
}
void RasterizerVulkan::UpdateViewportsState(Tegra::Engines::Maxwell3D::Regs& regs) {
if (!state_tracker.TouchViewports()) {
@@ -1236,141 +1233,109 @@ void RasterizerVulkan::UpdateBlendConstants(Tegra::Engines::Maxwell3D::Regs& reg
if (!state_tracker.TouchBlendConstants()) {
return;
}
if (!device.UsesAdvancedCoreDynamicState()) {
return;
}
const std::array<float, 4> blend_constants{
regs.blend_color.r,
regs.blend_color.g,
regs.blend_color.b,
regs.blend_color.a,
};
scheduler.Record([blend_constants](vk::CommandBuffer cmdbuf) {
cmdbuf.SetBlendConstants(blend_constants.data());
});
const std::array blend_color = {regs.blend_color.r, regs.blend_color.g, regs.blend_color.b,
regs.blend_color.a};
scheduler.Record(
[blend_color](vk::CommandBuffer cmdbuf) { cmdbuf.SetBlendConstants(blend_color.data()); });
}
void RasterizerVulkan::UpdateDepthBounds(Tegra::Engines::Maxwell3D::Regs& regs) {
if (!state_tracker.TouchDepthBounds()) {
return;
}
if (!device.IsDepthBoundsSupported()) {
return;
}
if (!device.UsesAdvancedCoreDynamicState()) {
return;
}
const bool unrestricted = device.IsExtDepthRangeUnrestrictedSupported();
const float min_depth = unrestricted ? regs.depth_bounds[0]
: std::clamp(regs.depth_bounds[0], 0.0f, 1.0f);
const float max_depth = unrestricted ? regs.depth_bounds[1]
: std::clamp(regs.depth_bounds[1], 0.0f, 1.0f);
scheduler.Record([min_depth, max_depth](vk::CommandBuffer cmdbuf) {
cmdbuf.SetDepthBounds(min_depth, max_depth);
});
scheduler.Record([min = regs.depth_bounds[0], max = regs.depth_bounds[1]](
vk::CommandBuffer cmdbuf) { cmdbuf.SetDepthBounds(min, max); });
}
void RasterizerVulkan::UpdateStencilFaces(Tegra::Engines::Maxwell3D::Regs& regs) {
const bool two_sided = regs.stencil_two_side_enable != 0;
const bool update_properties = state_tracker.TouchStencilProperties();
const bool update_side = state_tracker.TouchStencilSide(two_sided);
const bool refs_dirty = state_tracker.TouchStencilReference();
const bool write_dirty = state_tracker.TouchStencilWriteMask();
const bool compare_dirty = state_tracker.TouchStencilCompare();
const bool update_references = update_properties || update_side || refs_dirty;
const bool update_write_masks = update_properties || update_side || write_dirty;
const bool update_compare_masks = update_properties || update_side || compare_dirty;
if (!update_references && !update_write_masks && !update_compare_masks) {
state_tracker.ClearStencilReset();
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();
if (state_tracker.TouchStencilSide(regs.stencil_two_side_enable != 0)) {
update_references = true;
update_write_mask = true;
update_compare_masks = true;
}
if (update_references) {
if (two_sided) {
const bool front_dirty =
state_tracker.CheckStencilReferenceFront(regs.stencil_front_ref);
const bool back_dirty =
state_tracker.CheckStencilReferenceBack(regs.stencil_back_ref);
if (front_dirty || back_dirty) {
scheduler.Record([front_ref = regs.stencil_front_ref,
back_ref = regs.stencil_back_ref,
is_two_sided = two_sided](vk::CommandBuffer cmdbuf) {
const bool set_back = is_two_sided && front_ref != back_ref;
cmdbuf.SetStencilReference(set_back ? VK_STENCIL_FACE_FRONT_BIT
: VK_STENCIL_FACE_FRONT_AND_BACK,
front_ref);
if (set_back) {
cmdbuf.SetStencilReference(VK_STENCIL_FACE_BACK_BIT, back_ref);
}
});
[&]() {
if (regs.stencil_two_side_enable) {
if (!state_tracker.CheckStencilReferenceFront(regs.stencil_front_ref) &&
!state_tracker.CheckStencilReferenceBack(regs.stencil_back_ref)) {
return;
}
} else {
if (!state_tracker.CheckStencilReferenceFront(regs.stencil_front_ref)) {
return;
}
}
} else if (state_tracker.CheckStencilReferenceFront(regs.stencil_front_ref)) {
const u32 reference = regs.stencil_front_ref;
scheduler.Record([reference](vk::CommandBuffer cmdbuf) {
cmdbuf.SetStencilReference(VK_STENCIL_FACE_FRONT_AND_BACK, reference);
scheduler.Record([front_ref = regs.stencil_front_ref, back_ref = regs.stencil_back_ref,
two_sided = regs.stencil_two_side_enable](vk::CommandBuffer cmdbuf) {
const bool set_back = two_sided && front_ref != back_ref;
// Front face
cmdbuf.SetStencilReference(set_back ? VK_STENCIL_FACE_FRONT_BIT
: VK_STENCIL_FACE_FRONT_AND_BACK,
front_ref);
if (set_back) {
cmdbuf.SetStencilReference(VK_STENCIL_FACE_BACK_BIT, back_ref);
}
});
}
}();
}
if (update_write_masks) {
if (two_sided) {
if (state_tracker.CheckStencilWriteMaskFront(regs.stencil_front_mask) ||
state_tracker.CheckStencilWriteMaskBack(regs.stencil_back_mask)) {
scheduler.Record([
front_write_mask = regs.stencil_front_mask,
back_write_mask = regs.stencil_back_mask,
is_two_sided = regs.stencil_two_side_enable
](vk::CommandBuffer cmdbuf) {
const bool set_back = is_two_sided && front_write_mask != back_write_mask;
cmdbuf.SetStencilWriteMask(set_back ? VK_STENCIL_FACE_FRONT_BIT
: VK_STENCIL_FACE_FRONT_AND_BACK,
front_write_mask);
if (set_back) {
cmdbuf.SetStencilWriteMask(VK_STENCIL_FACE_BACK_BIT, back_write_mask);
}
});
if (update_write_mask) {
[&]() {
if (regs.stencil_two_side_enable) {
if (!state_tracker.CheckStencilWriteMaskFront(regs.stencil_front_mask) &&
!state_tracker.CheckStencilWriteMaskBack(regs.stencil_back_mask)) {
return;
}
} else {
if (!state_tracker.CheckStencilWriteMaskFront(regs.stencil_front_mask)) {
return;
}
}
} else if (state_tracker.CheckStencilWriteMaskFront(regs.stencil_front_mask)) {
const u32 front_write_mask = regs.stencil_front_mask;
scheduler.Record([front_write_mask](vk::CommandBuffer cmdbuf) {
cmdbuf.SetStencilWriteMask(VK_STENCIL_FACE_FRONT_AND_BACK, front_write_mask);
scheduler.Record([front_write_mask = regs.stencil_front_mask,
back_write_mask = regs.stencil_back_mask,
two_sided = regs.stencil_two_side_enable](vk::CommandBuffer cmdbuf) {
const bool set_back = two_sided && front_write_mask != back_write_mask;
// Front face
cmdbuf.SetStencilWriteMask(set_back ? VK_STENCIL_FACE_FRONT_BIT
: VK_STENCIL_FACE_FRONT_AND_BACK,
front_write_mask);
if (set_back) {
cmdbuf.SetStencilWriteMask(VK_STENCIL_FACE_BACK_BIT, back_write_mask);
}
});
}
}();
}
if (update_compare_masks) {
if (two_sided) {
if (state_tracker.CheckStencilCompareMaskFront(regs.stencil_front_func_mask) ||
state_tracker.CheckStencilCompareMaskBack(regs.stencil_back_func_mask)) {
scheduler.Record([
front_test_mask = regs.stencil_front_func_mask,
back_test_mask = regs.stencil_back_func_mask,
is_two_sided = regs.stencil_two_side_enable
](vk::CommandBuffer cmdbuf) {
const bool set_back = is_two_sided && front_test_mask != back_test_mask;
cmdbuf.SetStencilCompareMask(set_back ? VK_STENCIL_FACE_FRONT_BIT
: VK_STENCIL_FACE_FRONT_AND_BACK,
front_test_mask);
if (set_back) {
cmdbuf.SetStencilCompareMask(VK_STENCIL_FACE_BACK_BIT, back_test_mask);
}
});
[&]() {
if (regs.stencil_two_side_enable) {
if (!state_tracker.CheckStencilCompareMaskFront(regs.stencil_front_func_mask) &&
!state_tracker.CheckStencilCompareMaskBack(regs.stencil_back_func_mask)) {
return;
}
} else {
if (!state_tracker.CheckStencilCompareMaskFront(regs.stencil_front_func_mask)) {
return;
}
}
} else if (state_tracker.CheckStencilCompareMaskFront(regs.stencil_front_func_mask)) {
const u32 front_test_mask = regs.stencil_front_func_mask;
scheduler.Record([front_test_mask](vk::CommandBuffer cmdbuf) {
cmdbuf.SetStencilCompareMask(VK_STENCIL_FACE_FRONT_AND_BACK, front_test_mask);
scheduler.Record([front_test_mask = regs.stencil_front_func_mask,
back_test_mask = regs.stencil_back_func_mask,
two_sided = regs.stencil_two_side_enable](vk::CommandBuffer cmdbuf) {
const bool set_back = two_sided && front_test_mask != back_test_mask;
// Front face
cmdbuf.SetStencilCompareMask(set_back ? VK_STENCIL_FACE_FRONT_BIT
: VK_STENCIL_FACE_FRONT_AND_BACK,
front_test_mask);
if (set_back) {
cmdbuf.SetStencilCompareMask(VK_STENCIL_FACE_BACK_BIT, back_test_mask);
}
});
}
}();
}
state_tracker.ClearStencilReset();
}
@@ -1393,15 +1358,65 @@ void RasterizerVulkan::UpdateCullMode(Tegra::Engines::Maxwell3D::Regs& regs) {
});
}
void RasterizerVulkan::UpdateConservativeRasterizationMode(
Tegra::Engines::Maxwell3D::Regs& regs) {
void RasterizerVulkan::UpdateDepthBoundsTestEnable(Tegra::Engines::Maxwell3D::Regs& regs) {
if (!state_tracker.TouchDepthBoundsTestEnable()) {
return;
}
bool enabled = regs.depth_bounds_enable;
if (enabled && !device.IsDepthBoundsSupported()) {
LOG_WARNING(Render_Vulkan, "Depth bounds is enabled but not supported");
enabled = false;
}
scheduler.Record([enable = enabled](vk::CommandBuffer cmdbuf) {
cmdbuf.SetDepthBoundsTestEnableEXT(enable);
});
}
void RasterizerVulkan::UpdateDepthTestEnable(Tegra::Engines::Maxwell3D::Regs& regs) {
if (!state_tracker.TouchDepthTestEnable()) {
return;
}
scheduler.Record([enable = regs.depth_test_enable](vk::CommandBuffer cmdbuf) {
cmdbuf.SetDepthTestEnableEXT(enable);
});
}
void RasterizerVulkan::UpdateDepthWriteEnable(Tegra::Engines::Maxwell3D::Regs& regs) {
if (!state_tracker.TouchDepthWriteEnable()) {
return;
}
scheduler.Record([enable = regs.depth_write_enabled](vk::CommandBuffer cmdbuf) {
cmdbuf.SetDepthWriteEnableEXT(enable);
});
}
void RasterizerVulkan::UpdatePrimitiveRestartEnable(Tegra::Engines::Maxwell3D::Regs& regs) {
if (!state_tracker.TouchPrimitiveRestartEnable()) {
return;
}
scheduler.Record([enable = regs.primitive_restart.enabled](vk::CommandBuffer cmdbuf) {
cmdbuf.SetPrimitiveRestartEnableEXT(enable);
});
}
void RasterizerVulkan::UpdateRasterizerDiscardEnable(Tegra::Engines::Maxwell3D::Regs& regs) {
if (!state_tracker.TouchRasterizerDiscardEnable()) {
return;
}
scheduler.Record([disable = regs.rasterize_enable](vk::CommandBuffer cmdbuf) {
cmdbuf.SetRasterizerDiscardEnableEXT(disable == 0);
});
}
void RasterizerVulkan::UpdateConservativeRasterizationMode(Tegra::Engines::Maxwell3D::Regs& regs) {
if (!state_tracker.TouchConservativeRasterizationMode()) {
return;
}
if (!device.SupportsDynamicState3ConservativeRasterizationMode() ||
!device.IsExtConservativeRasterizationSupported()) {
if (!device.SupportsDynamicState3ConservativeRasterizationMode()) {
return;
}
scheduler.Record([enable = regs.conservative_raster_enable](vk::CommandBuffer cmdbuf) {
cmdbuf.SetConservativeRasterizationModeEXT(
enable ? VK_CONSERVATIVE_RASTERIZATION_MODE_OVERESTIMATE_EXT
@@ -1460,25 +1475,6 @@ void RasterizerVulkan::UpdateLineRasterizationMode(Tegra::Engines::Maxwell3D::Re
});
}
void RasterizerVulkan::UpdatePrimitiveRestartEnable(Tegra::Engines::Maxwell3D::Regs& regs) {
if (!state_tracker.TouchPrimitiveRestartEnable()) {
return;
}
scheduler.Record([enable = regs.primitive_restart.enabled != 0](vk::CommandBuffer cmdbuf) {
cmdbuf.SetPrimitiveRestartEnableEXT(enable);
});
}
void RasterizerVulkan::UpdateRasterizerDiscardEnable(Tegra::Engines::Maxwell3D::Regs& regs) {
if (!state_tracker.TouchRasterizerDiscardEnable()) {
return;
}
const bool discard = regs.rasterize_enable == 0;
scheduler.Record([discard](vk::CommandBuffer cmdbuf) {
cmdbuf.SetRasterizerDiscardEnableEXT(discard);
});
}
void RasterizerVulkan::UpdateDepthBiasEnable(Tegra::Engines::Maxwell3D::Regs& regs) {
if (!state_tracker.TouchDepthBiasEnable()) {
return;
@@ -1587,36 +1583,6 @@ void RasterizerVulkan::UpdateDepthCompareOp(Tegra::Engines::Maxwell3D::Regs& reg
});
}
void RasterizerVulkan::UpdateDepthBoundsTestEnable(Tegra::Engines::Maxwell3D::Regs& regs) {
if (!state_tracker.TouchDepthBoundsTestEnable()) {
return;
}
if (!device.IsDepthBoundsSupported()) {
return;
}
scheduler.Record([enable = regs.depth_bounds_enable != 0](vk::CommandBuffer cmdbuf) {
cmdbuf.SetDepthBoundsTestEnableEXT(enable);
});
}
void RasterizerVulkan::UpdateDepthTestEnable(Tegra::Engines::Maxwell3D::Regs& regs) {
if (!state_tracker.TouchDepthTestEnable()) {
return;
}
scheduler.Record([enable = regs.depth_test_enable != 0](vk::CommandBuffer cmdbuf) {
cmdbuf.SetDepthTestEnableEXT(enable);
});
}
void RasterizerVulkan::UpdateDepthWriteEnable(Tegra::Engines::Maxwell3D::Regs& regs) {
if (!state_tracker.TouchDepthWriteEnable()) {
return;
}
scheduler.Record([enable = regs.depth_write_enabled != 0](vk::CommandBuffer cmdbuf) {
cmdbuf.SetDepthWriteEnableEXT(enable);
});
}
void RasterizerVulkan::UpdateFrontFace(Tegra::Engines::Maxwell3D::Regs& regs) {
if (!state_tracker.TouchFrontFace()) {
return;
@@ -25,7 +25,6 @@
#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
#include "video_core/renderer_vulkan/vk_texture_cache.h"
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
#include "video_core/texture_cache/samples_helper.h"
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
@@ -169,6 +168,7 @@ private:
void UpdateDepthBounds(Tegra::Engines::Maxwell3D::Regs& regs);
void UpdateStencilFaces(Tegra::Engines::Maxwell3D::Regs& regs);
void UpdateLineWidth(Tegra::Engines::Maxwell3D::Regs& regs);
void UpdateCullMode(Tegra::Engines::Maxwell3D::Regs& regs);
void UpdateDepthBoundsTestEnable(Tegra::Engines::Maxwell3D::Regs& regs);
void UpdateDepthTestEnable(Tegra::Engines::Maxwell3D::Regs& regs);
@@ -4,7 +4,6 @@
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <chrono>
#include <memory>
#include <mutex>
#include <thread>
@@ -79,14 +78,6 @@ void Scheduler::WaitWorker() {
std::scoped_lock el{execution_mutex};
}
void Scheduler::KeepAliveTick() {
const auto now = Clock::now();
if (now - last_submission_time < KEEPALIVE_INTERVAL) {
return;
}
Flush();
}
void Scheduler::DispatchWork() {
if (chunk->Empty()) {
return;
@@ -280,7 +271,6 @@ u64 Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_se
});
chunk->MarkSubmit();
DispatchWork();
last_submission_time = Clock::now();
return signal_value;
}
@@ -305,13 +295,8 @@ void Scheduler::EndRenderPass()
return;
}
if (query_cache) {
query_cache->CounterEnable(VideoCommon::QueryType::ZPassPixelCount64, false);
if (query_cache->HasStreamer(VideoCommon::QueryType::StreamingByteCount)) {
query_cache->CounterEnable(VideoCommon::QueryType::StreamingByteCount, false);
}
query_cache->NotifySegment(false);
}
query_cache->CounterEnable(VideoCommon::QueryType::ZPassPixelCount64, false);
query_cache->NotifySegment(false);
Record([num_images = num_renderpass_images,
images = renderpass_images,
@@ -6,7 +6,6 @@
#pragma once
#include <chrono>
#include <condition_variable>
#include <cstddef>
#include <functional>
@@ -53,9 +52,6 @@ public:
/// safe to touch worker resources.
void WaitWorker();
/// Submits a tiny chunk of work if recent GPU submissions are stale.
void KeepAliveTick();
/// Sends currently recorded work to the worker thread.
void DispatchWork();
@@ -132,8 +128,6 @@ public:
std::mutex submit_mutex;
private:
using Clock = std::chrono::steady_clock;
class Command {
public:
virtual ~Command() = default;
@@ -256,9 +250,6 @@ private:
State state;
Clock::time_point last_submission_time{Clock::time_point::min()};
static constexpr std::chrono::milliseconds KEEPALIVE_INTERVAL{4};
u32 num_renderpass_images = 0;
std::array<VkImage, 9> renderpass_images{};
std::array<VkImageSubresourceRange, 9> renderpass_image_ranges{};
@@ -5,7 +5,6 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include <optional>
#include <utility>
#include <vector>
@@ -50,7 +49,6 @@ size_t GetStreamBufferSize(const Device& device) {
}
} // Anonymous namespace
StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& memory_allocator_,
Scheduler& scheduler_)
: device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_},
@@ -76,16 +74,13 @@ StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& mem
}
stream_pointer = stream_buffer.Mapped();
ASSERT_MSG(!stream_pointer.empty(), "Stream buffer must be host visible!");
}
StagingBufferPool::~StagingBufferPool() = default;
StagingBufferRef StagingBufferPool::Request(size_t size, MemoryUsage usage, bool deferred) {
if (!deferred && usage == MemoryUsage::Upload) {
if (size <= region_size) {
return GetStreamBuffer(size);
}
if (!deferred && usage == MemoryUsage::Upload && size <= region_size) {
return GetStreamBuffer(size);
}
return GetStagingBuffer(size, usage, deferred);
}
@@ -147,7 +142,6 @@ StagingBufferRef StagingBufferPool::GetStreamBuffer(size_t size) {
}
bool StagingBufferPool::AreRegionsActive(size_t region_begin, size_t region_end) const {
scheduler.GetMasterSemaphore().Refresh();
const u64 gpu_tick = scheduler.GetMasterSemaphore().KnownGpuTick();
return std::any_of(sync_ticks.begin() + region_begin, sync_ticks.begin() + region_end,
[gpu_tick](u64 sync_tick) { return gpu_tick < sync_tick; });
@@ -1,13 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <climits>
#include <optional>
#include <vector>
#include "common/common_types.h"
File diff suppressed because it is too large Load Diff
@@ -1,9 +1,11 @@
// 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
#pragma once
#include <optional>
#include <span>
#include "video_core/texture_cache/texture_cache_base.h"
@@ -57,8 +59,6 @@ public:
void TickFrame();
void WaitForGpuTick(u64 tick);
u64 GetDeviceLocalMemory() const;
u64 GetDeviceMemoryUsage() const;
@@ -113,15 +113,10 @@ public:
return view_formats[static_cast<std::size_t>(format)];
}
bool RequiresBlockCompatibleViewFormats(PixelFormat format) const noexcept {
return requires_block_view_formats[static_cast<std::size_t>(format)];
}
void BarrierFeedbackLoop();
bool IsFormatDitherable(VideoCore::Surface::PixelFormat format);
bool IsFormatScalable(VideoCore::Surface::PixelFormat format);
bool SupportsLinearFilter(VideoCore::Surface::PixelFormat format) const;
VkFormat GetSupportedFormat(VkFormat requested_format, VkFormatFeatureFlags required_features) const;
@@ -135,7 +130,6 @@ public:
std::unique_ptr<MSAACopyPass> msaa_copy_pass;
const Settings::ResolutionScalingInfo& resolution;
std::array<std::vector<VkFormat>, VideoCore::Surface::MaxPixelFormat> view_formats;
std::array<bool, VideoCore::Surface::MaxPixelFormat> requires_block_view_formats{};
static constexpr size_t indexing_slots = 8 * sizeof(size_t);
std::array<vk::Buffer, indexing_slots> buffers{};
@@ -182,30 +176,6 @@ public:
return (this->*current_image).UsageFlags();
}
void TrackGpuReadTick(u64 tick) noexcept {
TrackPendingReadTick(tick);
}
void TrackGpuWriteTick(u64 tick) noexcept {
TrackPendingWriteTick(tick);
}
void CompleteGpuReadTick(u64 completed_tick) noexcept {
ClearPendingReadTick(completed_tick);
}
void CompleteGpuWriteTick(u64 completed_tick) noexcept {
ClearPendingWriteTick(completed_tick);
}
[[nodiscard]] std::optional<u64> PendingGpuReadTick() const noexcept {
return PendingReadTick();
}
[[nodiscard]] std::optional<u64> PendingGpuWriteTick() const noexcept {
return PendingWriteTick();
}
/// Returns true when the image is already initialized and mark it as initialized
[[nodiscard]] bool ExchangeInitialization() noexcept {
return std::exchange(initialized, true);
@@ -268,20 +238,11 @@ public:
[[nodiscard]] VkImageView ColorView();
[[nodiscard]] VkImageView SampledView(Shader::TextureType texture_type,
Shader::SamplerComponentType component_type);
[[nodiscard]] VkImageView StorageView(Shader::TextureType texture_type,
Shader::ImageFormat image_format);
[[nodiscard]] bool IsRescaled() const noexcept;
[[nodiscard]] bool SupportsDepthCompareSampling() const noexcept;
[[nodiscard]] bool Is3DImage() const noexcept {
return is_3d_image;
}
[[nodiscard]] VkImageView Handle(Shader::TextureType texture_type) const noexcept {
return *image_views[static_cast<size_t>(texture_type)];
}
@@ -310,36 +271,23 @@ private:
struct StorageViews {
std::array<vk::ImageView, Shader::NUM_TEXTURE_TYPES> signeds;
std::array<vk::ImageView, Shader::NUM_TEXTURE_TYPES> unsigneds;
std::array<vk::ImageView, Shader::NUM_TEXTURE_TYPES> typeless;
};
static constexpr size_t NUMERIC_VIEW_TYPES = 3;
[[nodiscard]] Shader::TextureType BaseTextureType() const noexcept;
[[nodiscard]] std::optional<u32> LayerCountOverride(Shader::TextureType texture_type) const noexcept;
[[nodiscard]] VkImageView DepthView(Shader::TextureType texture_type);
[[nodiscard]] VkImageView StencilView(Shader::TextureType texture_type);
[[nodiscard]] vk::ImageView MakeView(VkFormat vk_format, VkImageAspectFlags aspect_mask);
[[nodiscard]] vk::ImageView MakeView(VkFormat vk_format, VkImageAspectFlags aspect_mask,
Shader::TextureType texture_type);
const Device* device = nullptr;
const SlotVector<Image>* slot_images = nullptr;
std::array<vk::ImageView, Shader::NUM_TEXTURE_TYPES> image_views;
std::unique_ptr<StorageViews> storage_views;
std::array<vk::ImageView, Shader::NUM_TEXTURE_TYPES> depth_views;
std::array<vk::ImageView, Shader::NUM_TEXTURE_TYPES> stencil_views;
std::array<std::array<vk::ImageView, Shader::NUM_TEXTURE_TYPES>, NUMERIC_VIEW_TYPES>
sampled_component_views;
vk::ImageView depth_view;
vk::ImageView stencil_view;
vk::ImageView color_view;
vk::Image null_image;
VkImage image_handle = VK_NULL_HANDLE;
VkImageView render_target = VK_NULL_HANDLE;
VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT;
u32 buffer_size = 0;
bool is_3d_image = false;
};
class ImageAlloc : public VideoCommon::ImageAllocBase {};
@@ -360,19 +308,9 @@ public:
return static_cast<bool>(sampler_default_anisotropy);
}
[[nodiscard]] VkSampler SelectHandle(bool supports_linear_filter,
bool supports_anisotropy,
bool allow_depth_compare) const noexcept;
private:
vk::Sampler sampler;
vk::Sampler sampler_default_anisotropy;
vk::Sampler sampler_force_point;
vk::Sampler sampler_compare_disabled;
vk::Sampler sampler_default_anisotropy_compare_disabled;
vk::Sampler sampler_force_point_compare_disabled;
bool uses_linear_filter = false;
bool depth_compare_enabled = false;
};
class Framebuffer {
+2 -163
View File
@@ -70,102 +70,6 @@ static Shader::TexturePixelFormat ConvertTexturePixelFormat(const Tegra::Texture
entry.a_type, entry.srgb_conversion));
}
namespace {
[[nodiscard]] bool UsesSwizzleSource(const Tegra::Texture::TICEntry& entry,
Tegra::Texture::SwizzleSource source) {
const std::array swizzles{entry.x_source.Value(), entry.y_source.Value(),
entry.z_source.Value(), entry.w_source.Value()};
return std::ranges::any_of(swizzles, [source](Tegra::Texture::SwizzleSource current) {
return current == source;
});
}
[[nodiscard]] std::optional<Shader::SamplerComponentType> DepthStencilComponentFromSwizzle(
const Tegra::Texture::TICEntry& entry, VideoCore::Surface::PixelFormat pixel_format) {
using Tegra::Texture::SwizzleSource;
const bool uses_r = UsesSwizzleSource(entry, SwizzleSource::R);
const bool uses_g = UsesSwizzleSource(entry, SwizzleSource::G);
switch (pixel_format) {
case VideoCore::Surface::PixelFormat::D24_UNORM_S8_UINT:
case VideoCore::Surface::PixelFormat::D32_FLOAT_S8_UINT:
if (uses_r != uses_g) {
return uses_r ? Shader::SamplerComponentType::Depth
: Shader::SamplerComponentType::Stencil;
}
break;
case VideoCore::Surface::PixelFormat::S8_UINT_D24_UNORM:
if (uses_r != uses_g) {
return uses_r ? Shader::SamplerComponentType::Stencil
: Shader::SamplerComponentType::Depth;
}
break;
default:
break;
}
return std::nullopt;
}
} // Anonymous namespace
static Shader::SamplerComponentType ConvertSamplerComponentType(
const Tegra::Texture::TICEntry& entry) {
const auto pixel_format = PixelFormatFromTextureInfo(entry.format, entry.r_type, entry.g_type,
entry.b_type, entry.a_type,
entry.srgb_conversion);
const auto surface_type = VideoCore::Surface::GetFormatType(pixel_format);
if (entry.depth_texture != 0 || surface_type == VideoCore::Surface::SurfaceType::Depth) {
return Shader::SamplerComponentType::Depth;
}
if (surface_type == VideoCore::Surface::SurfaceType::Stencil) {
return Shader::SamplerComponentType::Stencil;
}
if (surface_type == VideoCore::Surface::SurfaceType::DepthStencil) {
if (const auto inferred = DepthStencilComponentFromSwizzle(entry, pixel_format)) {
return *inferred;
}
return entry.depth_texture != 0 ? Shader::SamplerComponentType::Depth
: Shader::SamplerComponentType::Stencil;
}
const auto accumulate = [](const Tegra::Texture::ComponentType component,
bool& has_signed, bool& has_unsigned) {
switch (component) {
case Tegra::Texture::ComponentType::SINT:
has_signed = true;
break;
case Tegra::Texture::ComponentType::UINT:
has_unsigned = true;
break;
default:
break;
}
};
bool has_signed{};
bool has_unsigned{};
accumulate(entry.r_type, has_signed, has_unsigned);
accumulate(entry.g_type, has_signed, has_unsigned);
accumulate(entry.b_type, has_signed, has_unsigned);
accumulate(entry.a_type, has_signed, has_unsigned);
if (has_signed && !has_unsigned) {
return Shader::SamplerComponentType::Sint;
}
if (has_unsigned && !has_signed) {
return Shader::SamplerComponentType::Uint;
}
if (has_signed) {
return Shader::SamplerComponentType::Sint;
}
if (has_unsigned) {
return Shader::SamplerComponentType::Uint;
}
return Shader::SamplerComponentType::Float;
}
static std::string_view StageToPrefix(Shader::Stage stage) {
switch (stage) {
case Shader::Stage::VertexB:
@@ -296,7 +200,6 @@ void GenericEnvironment::Serialize(std::ofstream& file) const {
const u64 code_size{static_cast<u64>(CachedSizeBytes())};
const u64 num_texture_types{static_cast<u64>(texture_types.size())};
const u64 num_texture_pixel_formats{static_cast<u64>(texture_pixel_formats.size())};
const u64 num_texture_component_types{static_cast<u64>(texture_component_types.size())};
const u64 num_cbuf_values{static_cast<u64>(cbuf_values.size())};
const u64 num_cbuf_replacement_values{static_cast<u64>(cbuf_replacements.size())};
@@ -304,8 +207,6 @@ void GenericEnvironment::Serialize(std::ofstream& file) const {
.write(reinterpret_cast<const char*>(&num_texture_types), sizeof(num_texture_types))
.write(reinterpret_cast<const char*>(&num_texture_pixel_formats),
sizeof(num_texture_pixel_formats))
.write(reinterpret_cast<const char*>(&num_texture_component_types),
sizeof(num_texture_component_types))
.write(reinterpret_cast<const char*>(&num_cbuf_values), sizeof(num_cbuf_values))
.write(reinterpret_cast<const char*>(&num_cbuf_replacement_values),
sizeof(num_cbuf_replacement_values))
@@ -322,10 +223,6 @@ void GenericEnvironment::Serialize(std::ofstream& file) const {
file.write(reinterpret_cast<const char*>(&key), sizeof(key))
.write(reinterpret_cast<const char*>(&type), sizeof(type));
}
for (const auto& [key, component] : texture_component_types) {
file.write(reinterpret_cast<const char*>(&key), sizeof(key))
.write(reinterpret_cast<const char*>(&component), sizeof(component));
}
for (const auto& [key, format] : texture_pixel_formats) {
file.write(reinterpret_cast<const char*>(&key), sizeof(key))
.write(reinterpret_cast<const char*>(&format), sizeof(format));
@@ -380,17 +277,7 @@ std::optional<u64> GenericEnvironment::TryFindSize() {
Tegra::Texture::TICEntry GenericEnvironment::ReadTextureInfo(GPUVAddr tic_addr, u32 tic_limit,
bool via_header_index, u32 raw) {
const auto handle{Tegra::Texture::TexturePair(raw, via_header_index)};
if (handle.first > tic_limit) {
static std::atomic<size_t> oob_count{0};
const size_t n = ++oob_count;
if (n <= 4 || (n & 63) == 0) {
LOG_WARNING(Shader,
"TIC handle {} exceeds limit {} (via_header_index={}) — returning empty",
handle.first, tic_limit, via_header_index);
}
return {};
}
ASSERT(handle.first <= tic_limit);
const GPUVAddr descriptor_addr{tic_addr + handle.first * sizeof(Tegra::Texture::TICEntry)};
Tegra::Texture::TICEntry entry;
gpu_memory->ReadBlock(descriptor_addr, &entry, sizeof(entry));
@@ -487,21 +374,6 @@ Shader::TextureType GraphicsEnvironment::ReadTextureType(u32 handle) {
ReadTextureInfo(regs.tex_header.Address(), regs.tex_header.limit, via_header_index, handle);
const Shader::TextureType result{ConvertTextureType(entry)};
texture_types.emplace(handle, result);
texture_component_types.emplace(handle, ConvertSamplerComponentType(entry));
return result;
}
Shader::SamplerComponentType GraphicsEnvironment::ReadTextureComponentType(u32 handle) {
const auto it{texture_component_types.find(handle)};
if (it != texture_component_types.end()) {
return it->second;
}
const auto& regs{maxwell3d->regs};
const bool via_header_index{regs.sampler_binding == Maxwell::SamplerBinding::ViaHeaderBinding};
auto entry =
ReadTextureInfo(regs.tex_header.Address(), regs.tex_header.limit, via_header_index, handle);
const Shader::SamplerComponentType result{ConvertSamplerComponentType(entry)};
texture_component_types.emplace(handle, result);
return result;
}
@@ -558,20 +430,6 @@ Shader::TextureType ComputeEnvironment::ReadTextureType(u32 handle) {
auto entry = ReadTextureInfo(regs.tic.Address(), regs.tic.limit, qmd.linked_tsc != 0, handle);
const Shader::TextureType result{ConvertTextureType(entry)};
texture_types.emplace(handle, result);
texture_component_types.emplace(handle, ConvertSamplerComponentType(entry));
return result;
}
Shader::SamplerComponentType ComputeEnvironment::ReadTextureComponentType(u32 handle) {
const auto it{texture_component_types.find(handle)};
if (it != texture_component_types.end()) {
return it->second;
}
const auto& regs{kepler_compute->regs};
const auto& qmd{kepler_compute->launch_description};
auto entry = ReadTextureInfo(regs.tic.Address(), regs.tic.limit, qmd.linked_tsc != 0, handle);
const Shader::SamplerComponentType result{ConvertSamplerComponentType(entry)};
texture_component_types.emplace(handle, result);
return result;
}
@@ -597,15 +455,12 @@ void FileEnvironment::Deserialize(std::ifstream& file) {
u64 code_size{};
u64 num_texture_types{};
u64 num_texture_pixel_formats{};
u64 num_texture_component_types{};
u64 num_cbuf_values{};
u64 num_cbuf_replacement_values{};
file.read(reinterpret_cast<char*>(&code_size), sizeof(code_size))
.read(reinterpret_cast<char*>(&num_texture_types), sizeof(num_texture_types))
.read(reinterpret_cast<char*>(&num_texture_pixel_formats),
.read(reinterpret_cast<char*>(&num_texture_pixel_formats),
sizeof(num_texture_pixel_formats))
.read(reinterpret_cast<char*>(&num_texture_component_types),
sizeof(num_texture_component_types))
.read(reinterpret_cast<char*>(&num_cbuf_values), sizeof(num_cbuf_values))
.read(reinterpret_cast<char*>(&num_cbuf_replacement_values),
sizeof(num_cbuf_replacement_values))
@@ -625,13 +480,6 @@ void FileEnvironment::Deserialize(std::ifstream& file) {
.read(reinterpret_cast<char*>(&type), sizeof(type));
texture_types.emplace(key, type);
}
for (size_t i = 0; i < num_texture_component_types; ++i) {
u32 key;
Shader::SamplerComponentType component;
file.read(reinterpret_cast<char*>(&key), sizeof(key))
.read(reinterpret_cast<char*>(&component), sizeof(component));
texture_component_types.emplace(key, component);
}
for (size_t i = 0; i < num_texture_pixel_formats; ++i) {
u32 key;
Shader::TexturePixelFormat format;
@@ -686,15 +534,6 @@ u32 FileEnvironment::ReadCbufValue(u32 cbuf_index, u32 cbuf_offset) {
return it->second;
}
Shader::SamplerComponentType FileEnvironment::ReadTextureComponentType(u32 handle) {
const auto it{texture_component_types.find(handle)};
if (it == texture_component_types.end()) {
LOG_WARNING(Render_Vulkan, "Texture component descriptor {:08x} not found", handle);
return Shader::SamplerComponentType::Float;
}
return it->second;
}
Shader::TextureType FileEnvironment::ReadTextureType(u32 handle) {
const auto it{texture_types.find(handle)};
if (it == texture_types.end()) {
-8
View File
@@ -80,7 +80,6 @@ protected:
std::vector<u64> code;
std::unordered_map<u32, Shader::TextureType> texture_types;
std::unordered_map<u32, Shader::SamplerComponentType> texture_component_types;
std::unordered_map<u32, Shader::TexturePixelFormat> texture_pixel_formats;
std::unordered_map<u64, u32> cbuf_values;
std::unordered_map<u64, Shader::ReplaceConstant> cbuf_replacements;
@@ -117,8 +116,6 @@ public:
Shader::TextureType ReadTextureType(u32 handle) override;
Shader::SamplerComponentType ReadTextureComponentType(u32 handle) override;
Shader::TexturePixelFormat ReadTexturePixelFormat(u32 handle) override;
bool IsTexturePixelFormatInteger(u32 handle) override;
@@ -145,8 +142,6 @@ public:
Shader::TextureType ReadTextureType(u32 handle) override;
Shader::SamplerComponentType ReadTextureComponentType(u32 handle) override;
Shader::TexturePixelFormat ReadTexturePixelFormat(u32 handle) override;
bool IsTexturePixelFormatInteger(u32 handle) override;
@@ -181,8 +176,6 @@ public:
[[nodiscard]] Shader::TextureType ReadTextureType(u32 handle) override;
[[nodiscard]] Shader::SamplerComponentType ReadTextureComponentType(u32 handle) override;
[[nodiscard]] Shader::TexturePixelFormat ReadTexturePixelFormat(u32 handle) override;
[[nodiscard]] bool IsTexturePixelFormatInteger(u32 handle) override;
@@ -209,7 +202,6 @@ public:
private:
std::vector<u64> code;
std::unordered_map<u32, Shader::TextureType> texture_types;
std::unordered_map<u32, Shader::SamplerComponentType> texture_component_types;
std::unordered_map<u32, Shader::TexturePixelFormat> texture_pixel_formats;
std::unordered_map<u64, u32> cbuf_values;
std::unordered_map<u64, Shader::ReplaceConstant> cbuf_replacements;
-122
View File
@@ -4,8 +4,6 @@
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <optional>
#include "common/common_types.h"
#include "common/math_util.h"
#include "common/settings.h"
@@ -410,126 +408,6 @@ bool IsPixelFormatSignedInteger(PixelFormat format) {
}
}
namespace {
struct NumericVariantSet {
PixelFormat float_format = PixelFormat::Invalid;
PixelFormat uint_format = PixelFormat::Invalid;
PixelFormat sint_format = PixelFormat::Invalid;
[[nodiscard]] std::optional<PixelFormat> Select(PixelFormatNumeric numeric) const {
PixelFormat candidate = PixelFormat::Invalid;
switch (numeric) {
case PixelFormatNumeric::Float:
candidate = float_format;
break;
case PixelFormatNumeric::Uint:
candidate = uint_format;
break;
case PixelFormatNumeric::Sint:
candidate = sint_format;
break;
}
if (candidate == PixelFormat::Invalid) {
return std::nullopt;
}
return candidate;
}
};
constexpr NumericVariantSet MakeVariant(PixelFormat float_format, PixelFormat uint_format,
PixelFormat sint_format) {
return NumericVariantSet{
.float_format = float_format,
.uint_format = uint_format,
.sint_format = sint_format,
};
}
std::optional<NumericVariantSet> LookupNumericVariantSet(PixelFormat format) {
switch (format) {
case PixelFormat::R8_UNORM:
case PixelFormat::R8_SNORM:
case PixelFormat::R8_UINT:
case PixelFormat::R8_SINT:
return MakeVariant(PixelFormat::R8_UNORM, PixelFormat::R8_UINT, PixelFormat::R8_SINT);
case PixelFormat::R16_FLOAT:
case PixelFormat::R16_UNORM:
case PixelFormat::R16_SNORM:
case PixelFormat::R16_UINT:
case PixelFormat::R16_SINT:
return MakeVariant(PixelFormat::R16_FLOAT, PixelFormat::R16_UINT, PixelFormat::R16_SINT);
case PixelFormat::R32_FLOAT:
case PixelFormat::R32_UINT:
case PixelFormat::R32_SINT:
return MakeVariant(PixelFormat::R32_FLOAT, PixelFormat::R32_UINT, PixelFormat::R32_SINT);
case PixelFormat::R8G8_UNORM:
case PixelFormat::R8G8_SNORM:
case PixelFormat::R8G8_UINT:
case PixelFormat::R8G8_SINT:
return MakeVariant(PixelFormat::R8G8_UNORM, PixelFormat::R8G8_UINT, PixelFormat::R8G8_SINT);
case PixelFormat::R16G16_FLOAT:
case PixelFormat::R16G16_UNORM:
case PixelFormat::R16G16_SNORM:
case PixelFormat::R16G16_UINT:
case PixelFormat::R16G16_SINT:
return MakeVariant(PixelFormat::R16G16_FLOAT, PixelFormat::R16G16_UINT,
PixelFormat::R16G16_SINT);
case PixelFormat::R32G32_FLOAT:
case PixelFormat::R32G32_UINT:
case PixelFormat::R32G32_SINT:
return MakeVariant(PixelFormat::R32G32_FLOAT, PixelFormat::R32G32_UINT,
PixelFormat::R32G32_SINT);
case PixelFormat::R16G16B16A16_FLOAT:
case PixelFormat::R16G16B16A16_UNORM:
case PixelFormat::R16G16B16A16_SNORM:
case PixelFormat::R16G16B16A16_UINT:
case PixelFormat::R16G16B16A16_SINT:
return MakeVariant(PixelFormat::R16G16B16A16_FLOAT, PixelFormat::R16G16B16A16_UINT,
PixelFormat::R16G16B16A16_SINT);
case PixelFormat::R32G32B32A32_FLOAT:
case PixelFormat::R32G32B32A32_UINT:
case PixelFormat::R32G32B32A32_SINT:
return MakeVariant(PixelFormat::R32G32B32A32_FLOAT, PixelFormat::R32G32B32A32_UINT,
PixelFormat::R32G32B32A32_SINT);
case PixelFormat::A8B8G8R8_UNORM:
case PixelFormat::A8B8G8R8_SNORM:
case PixelFormat::A8B8G8R8_SRGB:
case PixelFormat::A8B8G8R8_UINT:
case PixelFormat::A8B8G8R8_SINT:
return MakeVariant(PixelFormat::A8B8G8R8_UNORM, PixelFormat::A8B8G8R8_UINT,
PixelFormat::A8B8G8R8_SINT);
case PixelFormat::A2B10G10R10_UNORM:
case PixelFormat::A2B10G10R10_UINT:
return MakeVariant(PixelFormat::A2B10G10R10_UNORM, PixelFormat::A2B10G10R10_UINT,
PixelFormat::Invalid);
default:
return std::nullopt;
}
}
} // Anonymous namespace
PixelFormatNumeric GetPixelFormatNumericType(PixelFormat format) {
if (IsPixelFormatInteger(format)) {
return IsPixelFormatSignedInteger(format) ? PixelFormatNumeric::Sint
: PixelFormatNumeric::Uint;
}
return PixelFormatNumeric::Float;
}
std::optional<PixelFormat> FindPixelFormatVariant(PixelFormat format,
PixelFormatNumeric target_numeric) {
const auto variants = LookupNumericVariantSet(format);
if (!variants) {
return std::nullopt;
}
if (const auto candidate = variants->Select(target_numeric)) {
return candidate;
}
return std::nullopt;
}
size_t PixelComponentSizeBitsInteger(PixelFormat format) {
switch (format) {
case PixelFormat::A8B8G8R8_SINT:
-13
View File
@@ -1,13 +1,11 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <climits>
#include <optional>
#include <utility>
#include "common/assert.h"
#include "common/common_types.h"
@@ -204,17 +202,6 @@ bool IsPixelFormatSRGB(PixelFormat format);
bool IsPixelFormatInteger(PixelFormat format);
bool IsPixelFormatSignedInteger(PixelFormat format);
size_t PixelComponentSizeBitsInteger(PixelFormat format);
enum class PixelFormatNumeric {
Float,
Uint,
Sint,
};
PixelFormatNumeric GetPixelFormatNumericType(PixelFormat format);
std::optional<PixelFormat> FindPixelFormatVariant(PixelFormat format,
PixelFormatNumeric target_numeric);
std::pair<u32, u32> GetASTCBlockSize(PixelFormat format);
u64 TranscodedAstcSize(u64 base_size, PixelFormat format);
@@ -1,6 +1,3 @@
// 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
@@ -24,27 +21,6 @@ constexpr auto FLOAT = ComponentType::FLOAT;
constexpr bool LINEAR = false;
constexpr bool SRGB = true;
constexpr TextureFormat SanitizeFormat(TextureFormat format) {
if (format == static_cast<TextureFormat>(0)) {
return TextureFormat::A8B8G8R8;
}
return format;
}
constexpr ComponentType SanitizeComponent(ComponentType component) {
if (component == static_cast<ComponentType>(0)) {
return ComponentType::UNORM;
}
switch (component) {
case ComponentType::SNORM_FORCE_FP16:
return ComponentType::SNORM;
case ComponentType::UNORM_FORCE_FP16:
return ComponentType::UNORM;
default:
return component;
}
}
constexpr u32 Hash(TextureFormat format, ComponentType red_component, ComponentType green_component,
ComponentType blue_component, ComponentType alpha_component, bool is_srgb) {
u32 hash = is_srgb ? 1 : 0;
@@ -65,11 +41,6 @@ constexpr u32 Hash(TextureFormat format, ComponentType component, bool is_srgb =
PixelFormat PixelFormatFromTextureInfo(TextureFormat format, ComponentType red, ComponentType green,
ComponentType blue, ComponentType alpha,
bool is_srgb) noexcept {
format = SanitizeFormat(format);
red = SanitizeComponent(red);
green = SanitizeComponent(green);
blue = SanitizeComponent(blue);
alpha = SanitizeComponent(alpha);
switch (Hash(format, red, green, blue, alpha, is_srgb)) {
case Hash(TextureFormat::A8B8G8R8, UNORM):
return PixelFormat::A8B8G8R8_UNORM;
-51
View File
@@ -1,12 +1,8 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <algorithm>
#include <array>
#include <optional>
#include <vector>
@@ -62,50 +58,6 @@ struct ImageBase {
explicit ImageBase(const ImageInfo& info, GPUVAddr gpu_addr, VAddr cpu_addr);
explicit ImageBase(const NullImageParams&);
void TrackPendingReadTick(u64 tick) noexcept {
if (pending_read_tick) {
*pending_read_tick = std::max(*pending_read_tick, tick);
} else {
pending_read_tick = tick;
}
}
void TrackPendingWriteTick(u64 tick) noexcept {
if (pending_write_tick) {
*pending_write_tick = std::max(*pending_write_tick, tick);
} else {
pending_write_tick = tick;
}
}
void ClearPendingReadTick(u64 completed_tick) noexcept {
if (pending_read_tick && completed_tick >= *pending_read_tick) {
pending_read_tick.reset();
}
}
void ClearPendingWriteTick(u64 completed_tick) noexcept {
if (pending_write_tick && completed_tick >= *pending_write_tick) {
pending_write_tick.reset();
}
}
[[nodiscard]] bool HasPendingReadTick() const noexcept {
return pending_read_tick.has_value();
}
[[nodiscard]] bool HasPendingWriteTick() const noexcept {
return pending_write_tick.has_value();
}
[[nodiscard]] std::optional<u64> PendingReadTick() const noexcept {
return pending_read_tick;
}
[[nodiscard]] std::optional<u64> PendingWriteTick() const noexcept {
return pending_write_tick;
}
[[nodiscard]] std::optional<SubresourceBase> TryFindBase(GPUVAddr other_addr) const noexcept;
[[nodiscard]] ImageViewId FindView(const ImageViewInfo& view_info) const noexcept;
@@ -163,9 +115,6 @@ struct ImageBase {
std::vector<AliasedImage> aliased_images;
std::vector<ImageId> overlapping_images;
ImageMapId map_view_id{};
std::optional<u64> pending_read_tick;
std::optional<u64> pending_write_tick;
};
struct ImageMapView {
@@ -1,22 +1,15 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <cstddef>
#include <utility>
#include "common/assert.h"
#include "common/common_types.h"
#include "video_core/textures/texture.h"
namespace VideoCommon {
constexpr inline std::size_t MaxSampleLocationSlots = 16;
[[nodiscard]] inline std::pair<int, int> SamplesLog2(int num_samples) {
switch (num_samples) {
case 1:
@@ -102,24 +95,4 @@ constexpr inline std::size_t MaxSampleLocationSlots = 16;
return 1;
}
// NVN guarantees up to sixteen programmable sample slots shared across a repeating pixel grid
// (per the 0.7.0 addon release notes), so the grid dimensions shrink as MSAA increases.
[[nodiscard]] inline std::pair<u32, u32> SampleLocationGridSize(Tegra::Texture::MsaaMode msaa_mode) {
const int samples = NumSamples(msaa_mode);
switch (samples) {
case 1:
return {4u, 4u};
case 2:
return {4u, 2u};
case 4:
return {2u, 2u};
case 8:
return {2u, 1u};
case 16:
return {1u, 1u};
}
ASSERT_MSG(false, "Unsupported sample count for grid size={}", samples);
return {1u, 1u};
}
} // namespace VideoCommon
+4 -103
View File
@@ -540,7 +540,6 @@ void TextureCache<P>::WriteMemory(DAddr cpu_addr, size_t size) {
if (True(image.flags & ImageFlagBits::CpuModified)) {
return;
}
EnsureImageReady(image, ImageAccessType::Write);
image.flags |= ImageFlagBits::CpuModified;
if (True(image.flags & ImageFlagBits::Tracked)) {
UntrackImage(image, image_id);
@@ -551,12 +550,11 @@ void TextureCache<P>::WriteMemory(DAddr cpu_addr, size_t size) {
template <class P>
void TextureCache<P>::DownloadMemory(DAddr cpu_addr, size_t size) {
boost::container::small_vector<ImageId, 16> images;
ForEachImageInRegion(cpu_addr, size, [this, &images](ImageId image_id, ImageBase& image) {
ForEachImageInRegion(cpu_addr, size, [&images](ImageId image_id, ImageBase& image) {
if (!image.IsSafeDownload()) {
return;
}
image.flags &= ~ImageFlagBits::GpuModified;
EnsureImageReady(this->slot_images[image_id], ImageAccessType::Read);
images.push_back(image_id);
});
if (images.empty()) {
@@ -608,7 +606,6 @@ void TextureCache<P>::UnmapMemory(DAddr cpu_addr, size_t size) {
ForEachImageInRegion(cpu_addr, size, [&](ImageId id, Image&) { deleted_images.push_back(id); });
for (const ImageId id : deleted_images) {
Image& image = slot_images[id];
EnsureImageReady(image, ImageAccessType::Write);
if (True(image.flags & ImageFlagBits::Tracked)) {
UntrackImage(image, id);
}
@@ -624,7 +621,6 @@ void TextureCache<P>::UnmapGPUMemory(size_t as_id, GPUVAddr gpu_addr, size_t siz
[&](ImageId id, Image&) { deleted_images.push_back(id); });
for (const ImageId id : deleted_images) {
Image& image = slot_images[id];
EnsureImageReady(image, ImageAccessType::Write);
if (False(image.flags & ImageFlagBits::CpuModified)) {
image.flags |= ImageFlagBits::CpuModified;
if (True(image.flags & ImageFlagBits::Tracked)) {
@@ -2427,9 +2423,6 @@ void TextureCache<P>::SynchronizeAliases(ImageId image_id) {
template <class P>
void TextureCache<P>::PrepareImage(ImageId image_id, bool is_modification, bool invalidate) {
Image& image = slot_images[image_id];
EnsureImageReady(image, is_modification ? ImageAccessType::Write : ImageAccessType::Read);
TrackGpuImageAccess(image_id, is_modification ? ImageAccessType::Write : ImageAccessType::Read);
runtime.TransitionImageLayout(image);
if (invalidate) {
image.flags &= ~(ImageFlagBits::CpuModified | ImageFlagBits::GpuModified);
if (False(image.flags & ImageFlagBits::Tracked)) {
@@ -2445,85 +2438,6 @@ void TextureCache<P>::PrepareImage(ImageId image_id, bool is_modification, bool
lru_cache.Touch(image.lru_index, frame_tick);
}
template <class P>
void TextureCache<P>::TrackGpuImageAccess(ImageId image_id, ImageAccessType access) {
staged_gpu_accesses.push_back({image_id, access});
}
template <class P>
void TextureCache<P>::CommitPendingGpuAccesses(u64 fence_value) {
if (staged_gpu_accesses.empty()) {
return;
}
if (fence_value == 0) {
return;
}
auto& batch = committed_gpu_accesses.emplace_back(std::move(staged_gpu_accesses));
staged_gpu_accesses.clear();
committed_gpu_ticks.push_back(fence_value);
for (const PendingImageAccess& access : batch) {
ImageBase& image = slot_images[access.image_id];
if (access.access == ImageAccessType::Read) {
image.TrackPendingReadTick(fence_value);
} else {
image.TrackPendingWriteTick(fence_value);
}
}
}
template <class P>
void TextureCache<P>::CompleteGpuAccesses(u64 completed_fence) {
if (completed_fence == 0) {
return;
}
while (!committed_gpu_ticks.empty() && committed_gpu_ticks.front() <= completed_fence) {
auto accesses = std::move(committed_gpu_accesses.front());
committed_gpu_accesses.pop_front();
committed_gpu_ticks.pop_front();
for (const PendingImageAccess& access : accesses) {
if (!slot_images.Contains(access.image_id)) {
continue;
}
ImageBase& image = slot_images[access.image_id];
if (access.access == ImageAccessType::Read) {
image.ClearPendingReadTick(completed_fence);
} else {
image.ClearPendingWriteTick(completed_fence);
}
}
}
}
template <class P>
void TextureCache<P>::EnsureImageReady(ImageBase& image, ImageAccessType access) {
auto wait_tick = [this](std::optional<u64> tick) -> std::optional<u64> {
if (!tick) {
return std::nullopt;
}
runtime.WaitForGpuTick(*tick);
return tick;
};
if (access == ImageAccessType::Write) {
if (const auto tick = image.PendingReadTick()) {
if (const auto waited = wait_tick(tick)) {
image.ClearPendingReadTick(*waited);
}
}
if (const auto tick = image.PendingWriteTick()) {
if (const auto waited = wait_tick(tick)) {
image.ClearPendingWriteTick(*waited);
}
}
} else {
if (const auto tick = image.PendingWriteTick()) {
if (const auto waited = wait_tick(tick)) {
image.ClearPendingWriteTick(*waited);
}
}
}
}
template <class P>
void TextureCache<P>::PrepareImageView(ImageViewId image_view_id, bool is_modification,
bool invalidate) {
@@ -2541,8 +2455,6 @@ template <class P>
void TextureCache<P>::CopyImage(ImageId dst_id, ImageId src_id, std::vector<ImageCopy> copies) {
Image& dst = slot_images[dst_id];
Image& src = slot_images[src_id];
EnsureImageReady(dst, ImageAccessType::Write);
EnsureImageReady(src, ImageAccessType::Read);
const bool is_rescaled = True(src.flags & ImageFlagBits::Rescaled);
if (is_rescaled) {
ASSERT(True(dst.flags & ImageFlagBits::Rescaled));
@@ -2559,30 +2471,20 @@ void TextureCache<P>::CopyImage(ImageId dst_id, ImageId src_id, std::vector<Imag
}
}
}
const auto TrackCopyAccesses = [this, dst_id, src_id]() {
TrackGpuImageAccess(dst_id, ImageAccessType::Write);
TrackGpuImageAccess(src_id, ImageAccessType::Read);
};
const auto dst_format_type = GetFormatType(dst.info.format);
const auto src_format_type = GetFormatType(src.info.format);
if (src_format_type == dst_format_type) {
if constexpr (HAS_EMULATED_COPIES) {
if (!runtime.CanImageBeCopied(dst, src)) {
runtime.EmulateCopyImage(dst, src, copies);
TrackCopyAccesses();
return;
return runtime.EmulateCopyImage(dst, src, copies);
}
}
runtime.CopyImage(dst, src, copies);
TrackCopyAccesses();
return;
return runtime.CopyImage(dst, src, copies);
}
UNIMPLEMENTED_IF(dst.info.type != ImageType::e2D);
UNIMPLEMENTED_IF(src.info.type != ImageType::e2D);
if (runtime.ShouldReinterpret(dst, src)) {
runtime.ReinterpretImage(dst, src, copies);
TrackCopyAccesses();
return;
return runtime.ReinterpretImage(dst, src, copies);
}
for (const ImageCopy& copy : copies) {
UNIMPLEMENTED_IF(copy.dst_subresource.num_layers != 1);
@@ -2635,7 +2537,6 @@ void TextureCache<P>::CopyImage(ImageId dst_id, ImageId src_id, std::vector<Imag
runtime.ConvertImage(dst_framebuffer, dst_view, src_view);
}
TrackCopyAccesses();
}
template <class P>
@@ -182,11 +182,6 @@ public:
/// Return a reference to the given sampler id
[[nodiscard]] Sampler& GetSampler(SamplerId id) noexcept;
/// Returns true when the runtime format supports linear filtering
[[nodiscard]] bool SupportsLinearFilter(PixelFormat format) const noexcept {
return runtime.SupportsLinearFilter(format);
}
/// Refresh the state for graphics image view and sampler descriptors
void SynchronizeGraphicsDescriptors();
@@ -263,15 +258,6 @@ public:
/// Prepare an image to be used
void PrepareImage(ImageId image_id, bool is_modification, bool invalidate);
/// Track that an image participates in upcoming GPU work with the given access type
void TrackGpuImageAccess(ImageId image_id, ImageAccessType access);
/// Notify the cache that tracked GPU work has been submitted with the specified fence value
void CommitPendingGpuAccesses(u64 fence_value);
/// Notify the cache that a fence value has completed so tracked accesses can be released
void CompleteGpuAccesses(u64 completed_fence);
std::recursive_mutex mutex;
private:
@@ -422,8 +408,6 @@ private:
/// Execute copies from one image to the other, even if they are incompatible
void CopyImage(ImageId dst_id, ImageId src_id, std::vector<ImageCopy> copies);
void EnsureImageReady(ImageBase& image, ImageAccessType access);
/// Bind an image view as render target, downloading resources preemtively if needed
void BindRenderTarget(ImageViewId* old_id, ImageViewId new_id);
@@ -481,11 +465,6 @@ private:
Common::SlotId object_id;
};
struct PendingImageAccess {
ImageId image_id;
ImageAccessType access;
};
Common::SlotVector<Image> slot_images;
Common::SlotVector<ImageMapView> slot_map_views;
Common::SlotVector<ImageView> slot_image_views;
@@ -501,9 +480,6 @@ private:
std::vector<AsyncBuffer> uncommitted_async_buffers;
std::deque<std::vector<AsyncBuffer>> async_buffers;
std::deque<AsyncBuffer> async_buffers_death_ring;
std::vector<PendingImageAccess> staged_gpu_accesses;
std::deque<std::vector<PendingImageAccess>> committed_gpu_accesses;
std::deque<u64> committed_gpu_ticks;
struct LRUItemParams {
using ObjectType = ImageId;
-5
View File
@@ -155,9 +155,4 @@ struct SwizzleParameters {
s32 level;
};
enum class ImageAccessType : u8 {
Read,
Write,
};
} // namespace VideoCommon
+67 -111
View File
@@ -18,8 +18,7 @@
namespace Tegra::Texture {
namespace {
template <u32 mask>
constexpr u32 pdep(u32 value) {
constexpr u32 pdep(u32 mask, u32 value) {
u32 result = 0;
u32 m = mask;
for (u32 bit = 1; m; bit += bit) {
@@ -30,15 +29,12 @@ constexpr u32 pdep(u32 value) {
return result;
}
template <u32 mask, u32 incr_amount>
void incrpdep(u32& value) {
static constexpr u32 swizzled_incr = pdep<mask>(incr_amount);
void incrpdep(u32 mask, u32 incr_amount, u32& value) {
u32 swizzled_incr = pdep(mask, incr_amount);
value = ((value | ~mask) + swizzled_incr) & mask;
}
template <bool TO_LINEAR, u32 BYTES_PER_PIXEL>
void SwizzleImpl(std::span<u8> output, std::span<const u8> input, u32 width, u32 height, u32 depth,
u32 block_height, u32 block_depth, u32 stride) {
void SwizzleImpl(bool to_linear, u32 bytes_per_pixel, std::span<u8> output, std::span<const u8> input, u32 width, u32 height, u32 depth, u32 block_height, u32 block_depth, u32 stride) {
// The origin of the transformation can be configured here, leave it as zero as the current API
// doesn't expose it.
static constexpr u32 origin_x = 0;
@@ -46,13 +42,12 @@ void SwizzleImpl(std::span<u8> output, std::span<const u8> input, u32 width, u32
static constexpr u32 origin_z = 0;
// We can configure here a custom pitch
// As it's not exposed 'width * BYTES_PER_PIXEL' will be the expected pitch.
const u32 pitch = width * BYTES_PER_PIXEL;
// As it's not exposed 'width * bytes_per_pixel' will be the expected pitch.
const u32 pitch = width * bytes_per_pixel;
const u32 gobs_in_x = Common::DivCeilLog2(stride, GOB_SIZE_X_SHIFT);
const u32 block_size = gobs_in_x << (GOB_SIZE_SHIFT + block_height + block_depth);
const u32 slice_size =
Common::DivCeilLog2(height, block_height + GOB_SIZE_Y_SHIFT) * block_size;
const u32 slice_size = Common::DivCeilLog2(height, block_height + GOB_SIZE_Y_SHIFT) * block_size;
const u32 block_height_mask = (1U << block_height) - 1;
const u32 block_depth_mask = (1U << block_depth) - 1;
@@ -64,50 +59,42 @@ void SwizzleImpl(std::span<u8> output, std::span<const u8> input, u32 width, u32
((z & block_depth_mask) << (GOB_SIZE_SHIFT + block_height));
for (u32 line = 0; line < height; ++line) {
const u32 y = line + origin_y;
const u32 swizzled_y = pdep<SWIZZLE_Y_BITS>(y);
const u32 swizzled_y = pdep(SWIZZLE_Y_BITS, y);
const u32 block_y = y >> GOB_SIZE_Y_SHIFT;
const u32 offset_y = (block_y >> block_height) * block_size +
((block_y & block_height_mask) << GOB_SIZE_SHIFT);
const u32 offset_y = (block_y >> block_height) * block_size + ((block_y & block_height_mask) << GOB_SIZE_SHIFT);
u32 swizzled_x = pdep<SWIZZLE_X_BITS>(origin_x * BYTES_PER_PIXEL);
for (u32 column = 0; column < width;
++column, incrpdep<SWIZZLE_X_BITS, BYTES_PER_PIXEL>(swizzled_x)) {
const u32 x = (column + origin_x) * BYTES_PER_PIXEL;
u32 swizzled_x = pdep(SWIZZLE_X_BITS, origin_x * bytes_per_pixel);
for (u32 column = 0; column < width; ++column, incrpdep(SWIZZLE_X_BITS, bytes_per_pixel, swizzled_x)) {
const u32 x = (column + origin_x) * bytes_per_pixel;
const u32 offset_x = (x >> GOB_SIZE_X_SHIFT) << x_shift;
const u32 base_swizzled_offset = offset_z + offset_y + offset_x;
const u32 swizzled_offset = base_swizzled_offset + (swizzled_x | swizzled_y);
const u32 unswizzled_offset =
slice * pitch * height + line * pitch + column * BYTES_PER_PIXEL;
const u32 unswizzled_offset = slice * pitch * height + line * pitch + column * bytes_per_pixel;
u8* const dst = &output[to_linear ? swizzled_offset : unswizzled_offset];
const u8* const src = &input[to_linear ? unswizzled_offset : swizzled_offset];
u8* const dst = &output[TO_LINEAR ? swizzled_offset : unswizzled_offset];
const u8* const src = &input[TO_LINEAR ? unswizzled_offset : swizzled_offset];
std::memcpy(dst, src, BYTES_PER_PIXEL);
std::memcpy(dst, src, bytes_per_pixel);
}
}
}
}
template <bool TO_LINEAR, u32 BYTES_PER_PIXEL>
void SwizzleSubrectImpl(std::span<u8> output, std::span<const u8> input, u32 width, u32 height,
u32 depth, u32 origin_x, u32 origin_y, u32 extent_x, u32 num_lines,
u32 block_height, u32 block_depth, u32 pitch_linear) {
void SwizzleSubrectImpl(bool to_linear, u32 bytes_per_pixel, std::span<u8> output, std::span<const u8> input, u32 width, u32 height, u32 depth, u32 origin_x, u32 origin_y, u32 extent_x, u32 num_lines, u32 block_height, u32 block_depth, u32 pitch_linear) {
// The origin of the transformation can be configured here, leave it as zero as the current API
// doesn't expose it.
static constexpr u32 origin_z = 0;
// We can configure here a custom pitch
// As it's not exposed 'width * BYTES_PER_PIXEL' will be the expected pitch.
// As it's not exposed 'width * bytes_per_pixel' will be the expected pitch.
const u32 pitch = pitch_linear;
const u32 stride = Common::AlignUpLog2(width * BYTES_PER_PIXEL, GOB_SIZE_X_SHIFT);
const u32 stride = Common::AlignUpLog2(width * bytes_per_pixel, GOB_SIZE_X_SHIFT);
const u32 gobs_in_x = Common::DivCeilLog2(stride, GOB_SIZE_X_SHIFT);
const u32 block_size = gobs_in_x << (GOB_SIZE_SHIFT + block_height + block_depth);
const u32 slice_size =
Common::DivCeilLog2(height, block_height + GOB_SIZE_Y_SHIFT) * block_size;
const u32 slice_size = Common::DivCeilLog2(height, block_height + GOB_SIZE_Y_SHIFT) * block_size;
const u32 block_height_mask = (1U << block_height) - 1;
const u32 block_depth_mask = (1U << block_depth) - 1;
@@ -123,28 +110,25 @@ void SwizzleSubrectImpl(std::span<u8> output, std::span<const u8> input, u32 wid
const u32 lines_in_y = (std::min)(unprocessed_lines, extent_y);
for (u32 line = 0; line < lines_in_y; ++line) {
const u32 y = line + origin_y;
const u32 swizzled_y = pdep<SWIZZLE_Y_BITS>(y);
const u32 swizzled_y = pdep(SWIZZLE_Y_BITS, y);
const u32 block_y = y >> GOB_SIZE_Y_SHIFT;
const u32 offset_y = (block_y >> block_height) * block_size +
((block_y & block_height_mask) << GOB_SIZE_SHIFT);
const u32 offset_y = (block_y >> block_height) * block_size + ((block_y & block_height_mask) << GOB_SIZE_SHIFT);
u32 swizzled_x = pdep<SWIZZLE_X_BITS>(origin_x * BYTES_PER_PIXEL);
for (u32 column = 0; column < extent_x;
++column, incrpdep<SWIZZLE_X_BITS, BYTES_PER_PIXEL>(swizzled_x)) {
const u32 x = (column + origin_x) * BYTES_PER_PIXEL;
u32 swizzled_x = pdep(SWIZZLE_X_BITS, origin_x * bytes_per_pixel);
for (u32 column = 0; column < extent_x; ++column, incrpdep(SWIZZLE_X_BITS, bytes_per_pixel, swizzled_x)) {
const u32 x = (column + origin_x) * bytes_per_pixel;
const u32 offset_x = (x >> GOB_SIZE_X_SHIFT) << x_shift;
const u32 base_swizzled_offset = offset_z + offset_y + offset_x;
const u32 swizzled_offset = base_swizzled_offset + (swizzled_x | swizzled_y);
const u32 unswizzled_offset =
slice * pitch * height + line * pitch + column * BYTES_PER_PIXEL;
const u32 unswizzled_offset = slice * pitch * height + line * pitch + column * bytes_per_pixel;
u8* const dst = &output[TO_LINEAR ? swizzled_offset : unswizzled_offset];
const u8* const src = &input[TO_LINEAR ? unswizzled_offset : swizzled_offset];
u8* const dst = &output[to_linear ? swizzled_offset : unswizzled_offset];
const u8* const src = &input[to_linear ? unswizzled_offset : swizzled_offset];
std::memcpy(dst, src, BYTES_PER_PIXEL);
std::memcpy(dst, src, bytes_per_pixel);
}
}
unprocessed_lines -= lines_in_y;
@@ -154,23 +138,17 @@ void SwizzleSubrectImpl(std::span<u8> output, std::span<const u8> input, u32 wid
}
}
template <bool TO_LINEAR>
void Swizzle(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel, u32 width,
u32 height, u32 depth, u32 block_height, u32 block_depth, u32 stride_alignment) {
void Swizzle(bool to_linear, std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel, u32 width, u32 height, u32 depth, u32 block_height, u32 block_depth, u32 stride_alignment) {
switch (bytes_per_pixel) {
#define BPP_CASE(x) \
case x: \
return SwizzleImpl<TO_LINEAR, x>(output, input, width, height, depth, block_height, \
block_depth, stride_alignment);
BPP_CASE(1)
BPP_CASE(2)
BPP_CASE(3)
BPP_CASE(4)
BPP_CASE(6)
BPP_CASE(8)
BPP_CASE(12)
BPP_CASE(16)
#undef BPP_CASE
case 1:
case 2:
case 3:
case 4:
case 6:
case 8:
case 12:
case 16:
return SwizzleImpl(to_linear, bytes_per_pixel, output, input, width, height, depth, block_height, block_depth, stride_alignment);
default:
ASSERT_MSG(false, "Invalid bytes_per_pixel={}", bytes_per_pixel);
break;
@@ -179,78 +157,57 @@ void Swizzle(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixe
} // Anonymous namespace
void UnswizzleTexture(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel,
u32 width, u32 height, u32 depth, u32 block_height, u32 block_depth,
u32 stride_alignment) {
void UnswizzleTexture(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel, u32 width, u32 height, u32 depth, u32 block_height, u32 block_depth, u32 stride_alignment) {
const u32 stride = Common::AlignUpLog2(width, stride_alignment) * bytes_per_pixel;
const u32 new_bpp = (std::min)(4U, static_cast<u32>(std::countr_zero(width * bytes_per_pixel)));
const u32 new_bpp = (std::min)(4U, u32(std::countr_zero(width * bytes_per_pixel)));
width = (width * bytes_per_pixel) >> new_bpp;
bytes_per_pixel = 1U << new_bpp;
Swizzle<false>(output, input, bytes_per_pixel, width, height, depth, block_height, block_depth,
stride);
Swizzle(false, output, input, bytes_per_pixel, width, height, depth, block_height, block_depth, stride);
}
void SwizzleTexture(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel, u32 width,
u32 height, u32 depth, u32 block_height, u32 block_depth,
u32 stride_alignment) {
void SwizzleTexture(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel, u32 width, u32 height, u32 depth, u32 block_height, u32 block_depth, u32 stride_alignment) {
const u32 stride = Common::AlignUpLog2(width, stride_alignment) * bytes_per_pixel;
const u32 new_bpp = (std::min)(4U, static_cast<u32>(std::countr_zero(width * bytes_per_pixel)));
const u32 new_bpp = (std::min)(4U, u32(std::countr_zero(width * bytes_per_pixel)));
width = (width * bytes_per_pixel) >> new_bpp;
bytes_per_pixel = 1U << new_bpp;
Swizzle<true>(output, input, bytes_per_pixel, width, height, depth, block_height, block_depth,
stride);
Swizzle(true, output, input, bytes_per_pixel, width, height, depth, block_height, block_depth, stride);
}
void SwizzleSubrect(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel, u32 width,
u32 height, u32 depth, u32 origin_x, u32 origin_y, u32 extent_x, u32 extent_y,
u32 block_height, u32 block_depth, u32 pitch_linear) {
void SwizzleSubrect(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel, u32 width, u32 height, u32 depth, u32 origin_x, u32 origin_y, u32 extent_x, u32 extent_y, u32 block_height, u32 block_depth, u32 pitch_linear) {
switch (bytes_per_pixel) {
#define BPP_CASE(x) \
case x: \
return SwizzleSubrectImpl<true, x>(output, input, width, height, depth, origin_x, \
origin_y, extent_x, extent_y, block_height, \
block_depth, pitch_linear);
BPP_CASE(1)
BPP_CASE(2)
BPP_CASE(3)
BPP_CASE(4)
BPP_CASE(6)
BPP_CASE(8)
BPP_CASE(12)
BPP_CASE(16)
#undef BPP_CASE
case 1:
case 2:
case 3:
case 4:
case 6:
case 8:
case 12:
case 16:
return SwizzleSubrectImpl(true, bytes_per_pixel, output, input, width, height, depth, origin_x, origin_y, extent_x, extent_y, block_height, block_depth, pitch_linear);
default:
ASSERT_MSG(false, "Invalid bytes_per_pixel={}", bytes_per_pixel);
break;
}
}
void UnswizzleSubrect(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel,
u32 width, u32 height, u32 depth, u32 origin_x, u32 origin_y, u32 extent_x,
u32 extent_y, u32 block_height, u32 block_depth, u32 pitch_linear) {
void UnswizzleSubrect(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel, u32 width, u32 height, u32 depth, u32 origin_x, u32 origin_y, u32 extent_x, u32 extent_y, u32 block_height, u32 block_depth, u32 pitch_linear) {
switch (bytes_per_pixel) {
#define BPP_CASE(x) \
case x: \
return SwizzleSubrectImpl<false, x>(output, input, width, height, depth, origin_x, \
origin_y, extent_x, extent_y, block_height, \
block_depth, pitch_linear);
BPP_CASE(1)
BPP_CASE(2)
BPP_CASE(3)
BPP_CASE(4)
BPP_CASE(6)
BPP_CASE(8)
BPP_CASE(12)
BPP_CASE(16)
#undef BPP_CASE
case 1:
case 2:
case 3:
case 4:
case 6:
case 8:
case 12:
case 16:
return SwizzleSubrectImpl(false, bytes_per_pixel, output, input, width, height, depth, origin_x, origin_y, extent_x, extent_y, block_height, block_depth, pitch_linear);
default:
ASSERT_MSG(false, "Invalid bytes_per_pixel={}", bytes_per_pixel);
break;
}
}
std::size_t CalculateSize(bool tiled, u32 bytes_per_pixel, u32 width, u32 height, u32 depth,
u32 block_height, u32 block_depth) {
std::size_t CalculateSize(bool tiled, u32 bytes_per_pixel, u32 width, u32 height, u32 depth, u32 block_height, u32 block_depth) {
if (tiled) {
const u32 aligned_width = Common::AlignUpLog2(width * bytes_per_pixel, GOB_SIZE_X_SHIFT);
const u32 aligned_height = Common::AlignUpLog2(height, GOB_SIZE_Y_SHIFT + block_height);
@@ -261,8 +218,7 @@ std::size_t CalculateSize(bool tiled, u32 bytes_per_pixel, u32 width, u32 height
}
}
u64 GetGOBOffset(u32 width, u32 height, u32 dst_x, u32 dst_y, u32 block_height,
u32 bytes_per_pixel) {
u64 GetGOBOffset(u32 width, u32 height, u32 dst_x, u32 dst_y, u32 block_height, u32 bytes_per_pixel) {
auto div_ceil = [](const u32 x, const u32 y) { return ((x + y - 1) / y); };
const u32 gobs_in_block = 1 << block_height;
const u32 y_blocks = GOB_SIZE_Y << block_height;
+3
View File
@@ -29,6 +29,9 @@
#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

Some files were not shown because too many files have changed in this diff Show More