Compare commits

..

10 Commits

Author SHA1 Message Date
lizzie 42dc3ae3d3 ok but fixed for realsies? 2026-08-18 21:21:11 +02:00
lizzie b9c1b8c5e2 better ips layer 2026-08-18 21:21:11 +02:00
lizzie 08c7037454 ffs 2026-08-18 21:21:11 +02:00
lizzie 4010ad60c7 ok fix 2026-08-18 21:21:11 +02:00
lizzie bc082c47fa fx 2026-08-18 21:21:11 +02:00
lizzie 3636792b29 more typs 2026-08-18 21:21:11 +02:00
lizzie b39fde257b wa 2026-08-18 21:21:11 +02:00
lizzie 814268097b ips stuffs2 2026-08-18 21:21:11 +02:00
lizzie 61c57646ac qt fix for freebsd
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-08-18 21:21:11 +02:00
lizzie f07d147579 [file_sys] rework IPS compiler
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-08-18 21:21:11 +02:00
117 changed files with 1363 additions and 2971 deletions
+11 -27
View File
@@ -43,7 +43,6 @@ This guide will walk you through adding a new boolean toggle setting to Eden's c
Firstly add your desired toggle:
Example: `src/common/setting.h`
```cpp
SwitchableSetting<bool> your_setting_name{linkage, false, "your_setting_name", Category::RendererExtensions};
```
@@ -68,7 +67,6 @@ Common Categories:
Add the toggle to the Qt UI, where you wish for it to appear and place it there.
Example: `src/qt_common/config/shared_translation.cpp`
```cpp
INSERT(Settings,
your_setting_name,
@@ -93,7 +91,6 @@ INSERT(Settings,
Add where it should be in the settings.
Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt`
```kts
RENDERER_YOUR_SETTING_NAME("your_setting_name"),
```
@@ -109,7 +106,6 @@ RENDERER_YOUR_SETTING_NAME("your_setting_name"),
Add the toggle to the Kotlin (Android) UI
Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt`
```kts
put(
SwitchSetting(
@@ -127,7 +123,6 @@ put(
Add your setting within the right category.
Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt`
```kts
add(BooleanSetting.RENDERER_YOUR_SETTING_NAME.key)
```
@@ -142,7 +137,6 @@ add(BooleanSetting.RENDERER_YOUR_SETTING_NAME.key)
Add your setting and description in the appropriate place.
Example: `src/android/app/src/main/res/values/strings.xml`
```xml
<string name="your_setting_name">Your Setting Display Name</string>
<string name="your_setting_name_description">Detailed description of what this setting does. Explain any caveats, requirements, or warnings here.</string>
@@ -156,7 +150,6 @@ Now the UI part is done find a place in the code for the toggle,
And use it to your heart's desire!
Example:
```cpp
const bool your_value = Settings::values.your_setting_name.GetValue();
@@ -203,31 +196,25 @@ Common advantages recap:
#### Accessing Debug Knobs (dev side)
Use the `Settings::GetDebugKnobAt(u8 i)` function to check if a specific bit is set:
Use the `Settings::getDebugKnobAt(u8 i)` function to check if a specific bit is set:
```cpp
//cpp side
#include "common/settings.h"
//To use it as a general purpose uint var:
unsigned int debug_knobs = Settings::values.debug_knobs.GetValue();
// Check if bit 0 is set
bool feature_enabled = Settings::GetDebugKnobAt(0);
bool feature_enabled = Settings::getDebugKnobAt(0);
// Check if bit 15 is set
bool another_feature = Settings::GetDebugKnobAt(15);
bool another_feature = Settings::getDebugKnobAt(15);
```
```kts
//kotlin side
import org.yuzu.yuzu_emu.features.settings.model.Settings
//To use it as a general purpose uint var
val debug_knobs: Int = UShortSetting.DEBUG_KNOBS.getInt()
// Check if bit x is set
bool feature_enabled = Settings.GetDebugKnobAt(x); //x as integer from 0 to 15
bool feature_enabled = Settings.getDebugKnobAt(x); //x as integer from 0 to 15
```
The function returns `true` if the specified bit (0-15) is set in the `debug_knobs` value, `false` otherwise.
@@ -260,7 +247,6 @@ There are two main confusions when talking about knobs:
Sometimes when an user reports: knobs 1 and 2 gets better performance, dev may get confuse whether he means the knobs 1 and 2 literally, or the 1st and 2nd knobs (knobs 0 and 1).
Debug knobs are **zero-based**, which means:
* The first knob is the knob(0) (or knob0 henceforth), and the last one is the 15 (knob15, likewise)
* You can talk: "knob0 is enabled/disabled", "In this video i was using only knobs 0 and 2", etc.
@@ -273,7 +259,6 @@ Whenever you're instructing tests or reporting results, be precise about whether
ALWAYS use the word in PLURAL (knobs), without mentioning which one, to refer to the setting, aka multiple knobs at once:
Examples:
- **knobs=0**: no knobs enabled
- **knobs=1**: knob0 enabled, others disabled
- **knobs=2**: knob1 enabled, others disabled
@@ -285,7 +270,6 @@ Examples:
Use the word in SINGULAR (knob), or in plural but referring which ones, when meaning multiple knobs at once:
Examples:
- **knob0**: knob 0 enabled, others disabled
- **knob1**: knob 1 enabled, others disabled
- **knobs 0 and 1**: knobs 0 and 1 enabled, others disabled
@@ -298,12 +282,12 @@ Examples:
```cpp
void SomeFunction() {
if (Settings::GetDebugKnobAt(0)) {
if (Settings::getDebugKnobAt(0)) {
LOG_DEBUG(Common, "Debug feature 0 is enabled");
// Additional debug code here
}
if (Settings::GetDebugKnobAt(1)) {
if (Settings::getDebugKnobAt(1)) {
LOG_DEBUG(Common, "Debug feature 1 is enabled");
// Different debug behavior
}
@@ -315,7 +299,7 @@ void SomeFunction() {
```cpp
bool UseOptimizedPath() {
// Skip optimization if debug bit 2 is set for testing
return !Settings::GetDebugKnobAt(2);
return !Settings::getDebugKnobAt(2);
}
```
@@ -324,13 +308,13 @@ bool UseOptimizedPath() {
```cpp
void ExperimentalFeature() {
static constexpr u8 EXPERIMENTAL_FEATURE_BIT = 3;
if (!Settings::GetDebugKnobAt(EXPERIMENTAL_FEATURE_BIT)) {
if (!Settings::getDebugKnobAt(EXPERIMENTAL_FEATURE_BIT)) {
// Fallback to stable implementation
StableImplementation();
return;
}
// Experimental implementation
ExperimentalImplementation();
}
@@ -220,7 +220,7 @@ object NativeLibrary {
external fun refreshThreadPolicies()
external fun GetDebugKnobAt(index: Int): Boolean
external fun getDebugKnobAt(index: Int): Boolean
/**
* Set the current speed limit to the configured turbo speed.
@@ -35,8 +35,8 @@ object Settings {
fun getPlayerString(player: Int): String =
YuzuApplication.appContext.getString(R.string.preferences_player, player)
fun GetDebugKnobAt(index: Int): Boolean {
return org.yuzu.yuzu_emu.NativeLibrary.GetDebugKnobAt(index)
fun getDebugKnobAt(index: Int): Boolean {
return org.yuzu.yuzu_emu.NativeLibrary.getDebugKnobAt(index)
}
const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
@@ -11,7 +11,8 @@ import org.yuzu.yuzu_emu.utils.NativeConfig
enum class ShortSetting(override val key: String) : AbstractShortSetting {
RENDERER_SPEED_LIMIT("speed_limit"),
RENDERER_TURBO_SPEED_LIMIT("turbo_speed_limit"),
RENDERER_SLOW_SPEED_LIMIT("slow_speed_limit")
RENDERER_SLOW_SPEED_LIMIT("slow_speed_limit"),
DEBUG_KNOBS("debug_knobs")
;
override fun getShort(needsGlobal: Boolean): Short = NativeConfig.getShort(key, needsGlobal)
@@ -28,4 +29,4 @@ enum class ShortSetting(override val key: String) : AbstractShortSetting {
override fun getValueAsString(needsGlobal: Boolean): String = getShort(needsGlobal).toString()
override fun reset() = NativeConfig.setShort(key, defaultValue)
}
}
@@ -1,30 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.features.settings.model
import org.yuzu.yuzu_emu.utils.NativeConfig
enum class UShortSetting(override val key: String) : AbstractIntSetting {
DEBUG_KNOBS("debug_knobs")
;
override fun getInt(needsGlobal: Boolean): Int =
NativeConfig.getUnsignedShort(key, needsGlobal)
override fun setInt(value: Int) {
if (NativeConfig.isPerGameConfigLoaded()) {
global = false
}
NativeConfig.setUnsignedShort(key, value)
}
override val defaultValue: Int by lazy { NativeConfig.getDefaultToString(key).toInt() }
override fun getValueAsString(needsGlobal: Boolean): String = getInt(needsGlobal).toString()
override fun reset() = NativeConfig.setUnsignedShort(key, defaultValue)
}
@@ -20,7 +20,6 @@ import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.features.settings.model.LongSetting
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.UShortSetting
import org.yuzu.yuzu_emu.network.NetDataValidators
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
import org.yuzu.yuzu_emu.utils.NativeConfig
@@ -625,7 +624,6 @@ abstract class SettingsItem(
IntSetting.FSR_SHARPENING_SLIDER,
titleId = R.string.fsr_sharpness,
descriptionId = R.string.fsr_sharpness_description,
max = 200,
units = "%"
)
)
@@ -1035,7 +1033,7 @@ abstract class SettingsItem(
)
put(
SpinBoxSetting(
UShortSetting.DEBUG_KNOBS,
ShortSetting.DEBUG_KNOBS,
titleId = R.string.debug_knobs,
descriptionId = R.string.debug_knobs_description,
valueHint = R.string.debug_knobs_hint,
@@ -25,7 +25,6 @@ import org.yuzu.yuzu_emu.features.settings.model.Settings
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.UShortSetting
import org.yuzu.yuzu_emu.features.settings.model.view.*
import org.yuzu.yuzu_emu.utils.InputHandler
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
@@ -100,12 +99,7 @@ class SettingsFragmentPresenter(
add(BooleanSetting.RENDERER_FRAME_GEN.key)
add(IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.key)
if (IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.getInt(
getNeedsGlobalForKey(IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.key)
) == 0
) {
add(IntSetting.RENDERER_FRAME_GEN_MULTIPLIER.key)
}
add(IntSetting.RENDERER_FRAME_GEN_MULTIPLIER.key)
add(IntSetting.RENDERER_FRAME_GEN_QUEUE_TARGET.key)
add(BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.key)
if (!BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.getBoolean(
@@ -313,6 +307,8 @@ class SettingsFragmentPresenter(
// TODO(crueter): sub-submenus?
private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) {
sl.apply {
// add(IntSetting.RENDERER_NVDEC_EMULATION.key)
add(IntSetting.RENDERER_RESOLUTION.key)
add(IntSetting.RENDERER_VSYNC.key)
add(IntSetting.RENDERER_SCALING_FILTER.key)
@@ -329,7 +325,6 @@ class SettingsFragmentPresenter(
add(IntSetting.MAX_ANISOTROPY.key)
add(IntSetting.RENDERER_VRAM_USAGE_MODE.key)
add(IntSetting.RENDERER_ASTC_DECODE_METHOD.key)
add(IntSetting.RENDERER_NVDEC_EMULATION.key)
add(BooleanSetting.SYNC_MEMORY_OPERATIONS.key)
add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key)
@@ -1327,7 +1322,7 @@ class SettingsFragmentPresenter(
add(HeaderSetting(R.string.general))
add(UShortSetting.DEBUG_KNOBS.key)
add(ShortSetting.DEBUG_KNOBS.key)
add(StringSetting.PROGRAM_ARGS.key)
if (!NativeConfig.isPerGameConfigLoaded()) {
@@ -1182,7 +1182,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
container,
IntSetting.FSR_SHARPENING_SLIDER,
minValue = 0,
maxValue = 200,
maxValue = 100,
units = "%"
)
}
@@ -18,7 +18,6 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.doOnPreDraw
import androidx.core.view.updatePadding
import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.Fragment
@@ -60,10 +59,6 @@ class GamesFragment : Fragment() {
private var lastViewType: Int = GameAdapter.VIEW_TYPE_GRID
private var fallbackBottomInset: Int = 0
private var pendingPostReloadListSettle = false
private var pendingPostReloadListSettleGeneration = 0
private var gameListSubmitGeneration = 0
private var committedGameListSubmitGeneration = 0
companion object {
private const val SEARCH_TEXT = "SearchText"
@@ -173,9 +168,10 @@ class GamesFragment : Fragment() {
gamesViewModel.shouldScrollAfterReload.collect(viewLifecycleOwner) { shouldScroll ->
if (shouldScroll) {
pendingPostReloadListSettle = true
pendingPostReloadListSettleGeneration = gameListSubmitGeneration
schedulePostReloadListSettle()
binding.gridGames.post {
(binding.gridGames as? CarouselRecyclerView)?.pendingScrollAfterReload = true
gameAdapter.notifyDataSetChanged()
}
gamesViewModel.setShouldScrollAfterReload(false)
}
}
@@ -277,42 +273,11 @@ class GamesFragment : Fragment() {
lastSearchText = currentSearchText
lastFilter = currentFilter
} else {
submitGameList(games)
((binding.gridGames as? RecyclerView)?.adapter as? GameAdapter)?.submitList(games)
gamesViewModel.setFilteredGames(games)
}
}
private fun submitGameList(games: List<Game>) {
val adapter = (binding.gridGames as? RecyclerView)?.adapter as? GameAdapter
if (adapter == null) {
schedulePostReloadListSettle()
return
}
val submitGeneration = ++gameListSubmitGeneration
adapter.submitList(games) {
if (committedGameListSubmitGeneration < submitGeneration) {
committedGameListSubmitGeneration = submitGeneration
}
schedulePostReloadListSettle()
}
}
private fun schedulePostReloadListSettle() {
if (!pendingPostReloadListSettle || _binding == null) return
binding.gridGames.doOnPreDraw {
if (!pendingPostReloadListSettle || _binding == null) return@doOnPreDraw
if (committedGameListSubmitGeneration < pendingPostReloadListSettleGeneration) {
schedulePostReloadListSettle()
return@doOnPreDraw
}
pendingPostReloadListSettle = false
(binding.gridGames as? CarouselRecyclerView)?.refreshView()
}
}
private fun setupTopView() {
binding.searchText.doOnTextChanged() { text: CharSequence?, _: Int, _: Int, _: Int ->
if (text.toString().isNotEmpty()) {
@@ -449,7 +414,9 @@ class GamesFragment : Fragment() {
val searchTerm = binding.searchText.text.toString().lowercase(Locale.getDefault())
if (searchTerm.isEmpty()) {
submitGameList(filteredList)
((binding.gridGames as? RecyclerView)?.adapter as? GameAdapter)?.submitList(
filteredList
)
gamesViewModel.setFilteredGames(filteredList)
return
}
@@ -465,7 +432,7 @@ class GamesFragment : Fragment() {
}
}.sortedByDescending { it.score }.map { it.item }
submitGameList(sortedList)
((binding.gridGames as? RecyclerView)?.adapter as? GameAdapter)?.submitList(sortedList)
gamesViewModel.setFilteredGames(sortedList)
}
@@ -31,7 +31,6 @@ object GameHelper {
fun getGames(): List<Game> {
val games = mutableListOf<Game>()
val gamesByProgramId = mutableMapOf<String, Game>()
val context = YuzuApplication.appContext
preferences = PreferenceManager.getDefaultSharedPreferences(context)
@@ -64,7 +63,6 @@ object GameHelper {
addGamesRecursive(
games,
gamesByProgramId,
FileUtil.listFiles(gameDirUri),
scanDepth,
mountedContainerUris
@@ -138,7 +136,6 @@ object GameHelper {
private fun addGamesRecursive(
games: MutableList<Game>,
gamesByProgramId: MutableMap<String, Game>,
files: Array<MinimalDocumentFile>,
depth: Int,
mountedContainerUris: MutableSet<String>
@@ -151,7 +148,6 @@ object GameHelper {
if (it.isDirectory) {
addGamesRecursive(
games,
gamesByProgramId,
FileUtil.listFiles(it.uri),
depth - 1,
mountedContainerUris
@@ -160,9 +156,8 @@ object GameHelper {
val extension = FileUtil.getExtension(it.uri).lowercase()
val filePath = it.uri.toString()
val mountedContainer = externalContentExtensions.contains(extension) &&
mountedContainerUris.add(filePath)
if (mountedContainer) {
if (externalContentExtensions.contains(extension) &&
mountedContainerUris.add(filePath)) {
NativeLibrary.addGameFolderFileToFilesystemProvider(filePath)
}
@@ -170,20 +165,6 @@ object GameHelper {
val game = getGame(it.uri, true, false)
if (game != null) {
games.add(game)
if (game.programId != "0") {
gamesByProgramId[game.programId] = game
}
} else if (mountedContainer) {
GameMetadata.getProgramId(filePath).toLongOrNull()?.let { programId ->
gamesByProgramId[(programId and 0x800L.inv()).toString()]
}?.let { existingGame ->
NativeLibrary.getPatchesForFile(existingGame.path, existingGame.programId)
existingGame.version = GameMetadata.getVersion(
existingGame.path,
true
)
GameIconUtils.refreshGameIcon(existingGame)
}
}
}
}
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -27,15 +24,6 @@ import coil.request.Options
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.model.Game
import java.util.Collections
import java.util.WeakHashMap
private val gameIconHashes = Collections.synchronizedMap(mutableMapOf<String, Int>())
private val gameIconTargets = Collections.synchronizedMap(WeakHashMap<ImageView, GameIconTarget>())
private fun Game.iconCacheKey(): String = "$path|$version"
private data class GameIconTarget(val game: Game, var iconHash: Int? = null)
class GameIconFetcher(
private val game: Game,
@@ -43,15 +31,14 @@ class GameIconFetcher(
) : Fetcher {
override suspend fun fetch(): FetchResult {
return DrawableResult(
drawable = decodeGameIcon(game)!!.toDrawable(options.context.resources),
drawable = decodeGameIcon(game.path)!!.toDrawable(options.context.resources),
isSampled = false,
dataSource = DataSource.DISK
)
}
private fun decodeGameIcon(game: Game): Bitmap? {
val data = GameMetadata.getIcon(game.path)
gameIconHashes[game.iconCacheKey()] = data.contentHashCode()
private fun decodeGameIcon(uri: String): Bitmap? {
val data = GameMetadata.getIcon(uri)
return BitmapFactory.decodeByteArray(
data,
0,
@@ -67,7 +54,7 @@ class GameIconFetcher(
}
class GameIconKeyer : Keyer<Game> {
override fun key(data: Game, options: Options): String = data.iconCacheKey()
override fun key(data: Game, options: Options): String = data.path
}
object GameIconUtils {
@@ -84,58 +71,14 @@ object GameIconUtils {
.build()
fun loadGameIcon(game: Game, imageView: ImageView) {
gameIconTargets[imageView] = GameIconTarget(game)
val request = ImageRequest.Builder(YuzuApplication.appContext)
.data(game)
.target(imageView)
.error(R.drawable.default_icon)
.listener(
onSuccess = { _, _ ->
val target = gameIconTargets[imageView]
if (target?.game?.iconCacheKey() == game.iconCacheKey()) {
gameIconHashes[game.iconCacheKey()]?.let {
target.iconHash = it
}
}
},
onError = { _, _ ->
gameIconTargets[imageView]?.iconHash = null
}
)
.build()
imageLoader.enqueue(request)
}
fun refreshGameIcon(game: Game) {
val targets = synchronized(gameIconTargets) {
gameIconTargets
.filterValues { it.game.path == game.path && it.game.programId == game.programId }
.keys
.toList()
}
if (targets.isEmpty()) {
return
}
val iconHash = GameMetadata.getIcon(game.path).contentHashCode()
val targetsToRefresh = targets.filter { gameIconTargets[it]?.iconHash != iconHash }
if (targetsToRefresh.isEmpty()) {
return
}
imageLoader.memoryCache?.remove(MemoryCache.Key(game.iconCacheKey()))
targetsToRefresh.forEach { imageView ->
imageView.post {
val target = gameIconTargets[imageView] ?: return@post
if (target.game.path == game.path && target.game.programId == game.programId) {
if (target.iconHash != iconHash) {
loadGameIcon(game, imageView)
}
}
}
}
}
suspend fun getGameIcon(lifecycleOwner: LifecycleOwner, game: Game): Bitmap {
val request = ImageRequest.Builder(YuzuApplication.appContext)
.data(game)
@@ -80,12 +80,6 @@ object NativeConfig {
@Synchronized
external fun setShort(key: String, value: Short)
@Synchronized
external fun getUnsignedShort(key: String, needsGlobal: Boolean): Int
@Synchronized
external fun setUnsignedShort(key: String, value: Int)
@Synchronized
external fun getInt(key: String, needsGlobal: Boolean): Int
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.ui
@@ -11,8 +11,6 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.PagerSnapHelper
import androidx.recyclerview.widget.RecyclerView
import kotlin.math.abs
import kotlin.math.cos
import kotlin.math.sin
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.adapters.GameAdapter
import androidx.core.view.doOnNextLayout
@@ -36,7 +34,6 @@ class CarouselRecyclerView @JvmOverloads constructor(
private var overlapDecoration: OverlappingDecoration? = null
private var pagerSnapHelper: PagerSnapHelper? = null
private var scalingScrollListener: OnScrollListener? = null
private var savedItemAnimator: RecyclerView.ItemAnimator? = null
companion object {
private const val CAROUSEL_CARD_SIZE_FACTOR = "CarouselCardSizeMultiplier"
@@ -45,13 +42,8 @@ class CarouselRecyclerView @JvmOverloads constructor(
private const val CAROUSEL_OVERLAP_FACTOR = "CarouselOverlapFactor"
private const val CAROUSEL_MAX_FLING_COUNT = "CarouselMaxFlingCount"
private const val CAROUSEL_FLING_MULTIPLIER = "CarouselFlingMultiplier"
private const val CAROUSEL_ARC_ANGLE_STEP_DEGREES = 15.0
private const val CAROUSEL_ARC_MAX_ANGLE_DEGREES = 165.0
private const val CAROUSEL_ARC_DEPTH_MAX_ANGLE_DEGREES = 85.0
private const val CAROUSEL_ARC_DEPTH_STRETCH = 5.0f
private const val CAROUSEL_ARC_X_DEPTH_FACTOR = 0.55f
private const val CAROUSEL_ARC_FADE_OUT_START_DEGREES = 60.0
private const val CAROUSEL_ARC_FADE_OUT_END_DEGREES = 95.0
private const val CAROUSEL_CARDS_SCALING_SHAPE = "CarouselCardsScalingShape"
private const val CAROUSEL_CARDS_ALPHA_SHAPE = "CarouselCardsAlphaShape"
const val CAROUSEL_LAST_SCROLL_POSITION = "CarouselLastScrollPosition"
const val CAROUSEL_VIEW_TYPE_PORTRAIT = "GamesViewTypePortrait"
const val CAROUSEL_VIEW_TYPE_LANDSCAPE = "GamesViewTypeLandscape"
@@ -168,52 +160,46 @@ class CarouselRecyclerView @JvmOverloads constructor(
}
}
fun shapingFunction(x: Float, option: Int = 0): Float {
return when (option) {
0 -> 1f // Off
1 -> 1f - x // linear descending
2 -> (1f - x) * (1f - x) // Ease out
3 -> if (x < 0.05f) 1f else (1f - x) * 0.8f
4 -> kotlin.math.cos(x * Math.PI).toFloat() // Cosine
5 -> kotlin.math.cos((1.5f * x).coerceIn(0f, 1f) * Math.PI).toFloat() // Cosine 1.5x trimmed
else -> 1f // Default to Off
}
}
fun updateChildScaleAndAlphaForPosition(child: View) {
val cardSize = (adapter as? GameAdapter ?: return).cardSize
val position = getChildViewHolder(child).bindingAdapterPosition
if (position == RecyclerView.NO_POSITION || cardSize <= 0) {
return // No valid position or card size
}
val layoutParams = child.layoutParams
if (layoutParams.width != cardSize || layoutParams.height != cardSize) {
child.layoutParams = layoutParams.apply {
width = cardSize
height = cardSize
}
}
val signedDistance = getChildDistanceToCenter(child)
val itemStep = (cardSize - overlapPx).toFloat().coerceAtLeast(1f)
val angleStep = Math.toRadians(CAROUSEL_ARC_ANGLE_STEP_DEGREES).toFloat()
val maxAngle = Math.toRadians(CAROUSEL_ARC_MAX_ANGLE_DEGREES).toFloat()
val depthMaxAngle = Math.toRadians(CAROUSEL_ARC_DEPTH_MAX_ANGLE_DEGREES).toFloat()
val fadeOutStartAngle = Math.toRadians(CAROUSEL_ARC_FADE_OUT_START_DEGREES).toFloat()
val fadeOutEndAngle = Math.toRadians(CAROUSEL_ARC_FADE_OUT_END_DEGREES).toFloat()
val angle = (signedDistance / itemStep * angleStep).coerceIn(-maxAngle, maxAngle)
val arcRadius = itemStep / angleStep
val arcX = sin(angle) * arcRadius
val absoluteAngle = abs(angle)
val rawDepthInput = ((1f - cos(absoluteAngle)) / (1f - cos(depthMaxAngle)))
.coerceIn(0f, 1f)
val easedDepthTail = Math.pow(
(1f - rawDepthInput).toDouble(),
CAROUSEL_ARC_DEPTH_STRETCH.toDouble()
).toFloat()
val depthInput = (1f - easedDepthTail).coerceIn(0f, 1f)
val projectedArcX = arcX * (1f - rawDepthInput * CAROUSEL_ARC_X_DEPTH_FACTOR)
child.animate().cancel()
child.translationX = projectedArcX - signedDistance
child.layoutParams.width = cardSize
child.layoutParams.height = cardSize
val center = getRecyclerViewCenter()
val distance = abs(getChildDistanceToCenter(child))
val internalBorderScale = resources.getFraction(R.fraction.carousel_bordercards_scale, 1, 1)
val borderScale = preferences.getFloat(CAROUSEL_BORDERCARDS_SCALE, internalBorderScale).coerceIn(
0f,
1f
)
val shapedScaling = 1f - depthInput
val shapeInput = (distance / center).coerceIn(0f, 1f)
val internalShapeSetting = resources.getInteger(R.integer.carousel_cards_scaling_shape)
val scalingShapeSetting = preferences.getInt(
CAROUSEL_CARDS_SCALING_SHAPE,
internalShapeSetting
)
val shapedScaling = shapingFunction(shapeInput, scalingShapeSetting)
val scale = (borderScale + (1f - borderScale) * shapedScaling).coerceIn(0f, 1f)
val maxDistance = width / 2f
val alphaInput = (distance / maxDistance).coerceIn(0f, 1f)
val internalBordersAlpha = resources.getFraction(
R.fraction.carousel_bordercards_alpha,
1,
@@ -223,12 +209,15 @@ class CarouselRecyclerView @JvmOverloads constructor(
0f,
1f
)
val shapedAlpha = cos(depthInput * Math.PI).toFloat()
val baseAlpha = (borderAlpha + (1f - borderAlpha) * shapedAlpha).coerceIn(0f, 1f)
val rearPresence = (1f - (absoluteAngle - fadeOutStartAngle) /
(fadeOutEndAngle - fadeOutStartAngle)).coerceIn(0f, 1f)
val alpha = (baseAlpha * rearPresence).coerceIn(0f, 1f)
val internalAlphaShapeSetting = resources.getInteger(R.integer.carousel_cards_alpha_shape)
val alphaShapeSetting = preferences.getInt(
CAROUSEL_CARDS_ALPHA_SHAPE,
internalAlphaShapeSetting
)
val shapedAlpha = shapingFunction(alphaInput, alphaShapeSetting)
val alpha = (borderAlpha + (1f - borderAlpha) * shapedAlpha).coerceIn(0f, 1f)
child.animate().cancel()
child.alpha = alpha
child.scaleX = scale
child.scaleY = scale
@@ -284,9 +273,7 @@ class CarouselRecyclerView @JvmOverloads constructor(
0f,
1f
)
val scaledHeight = height * userFactor
val availableHeight = height - bottomInset
return minOf(scaledHeight.toInt(), availableHeight.toInt())
return (userFactor * (height - bottomInset)).toInt()
}
fun setupCarousel(enabled: Boolean) {
@@ -295,13 +282,6 @@ class CarouselRecyclerView @JvmOverloads constructor(
if (gameAdapter.cardSize == 0) return
if (bottomInset < 0) return
itemAnimator?.let {
if (savedItemAnimator == null) {
savedItemAnimator = it
}
itemAnimator = null
}
useCustomDrawingOrder = true
val cardSize = gameAdapter.cardSize
@@ -356,12 +336,6 @@ class CarouselRecyclerView @JvmOverloads constructor(
// Detach PagerSnapHelper
pagerSnapHelper?.attachToRecyclerView(null)
pagerSnapHelper = null
savedItemAnimator?.let {
if (itemAnimator == null) {
itemAnimator = it
}
savedItemAnimator = null
}
useCustomDrawingOrder = false
// Reset padding and fling
setPadding(0, 0, 0, 0)
@@ -370,7 +344,6 @@ class CarouselRecyclerView @JvmOverloads constructor(
// Reset scaling
for (i in 0 until childCount) {
val child = getChildAt(i)
child?.translationX = 0f
child?.scaleX = 1f
child?.scaleY = 1f
child?.alpha = 1f
+2 -2
View File
@@ -1245,8 +1245,8 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_refreshThreadPolicies(JNIEnv* env, jo
Common::RefreshThreadPolicies();
}
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_GetDebugKnobAt(JNIEnv* env, jobject jobj, jint index) {
return static_cast<jboolean>(Settings::GetDebugKnobAt(static_cast<u8>(index)));
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_getDebugKnobAt(JNIEnv* env, jobject jobj, jint index) {
return static_cast<jboolean>(Settings::getDebugKnobAt(static_cast<u8>(index)));
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setTurboSpeedLimit(JNIEnv *env, jobject jobj, jboolean enabled) {
@@ -130,25 +130,6 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setShort(JNIEnv* env, jobject ob
setting->SetValue(value);
}
jint Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getUnsignedShort(JNIEnv* env, jobject obj,
jstring jkey,
jboolean needGlobal) {
auto setting = getSetting<u16>(env, jkey);
if (setting == nullptr) {
return -1;
}
return static_cast<jint>(setting->GetValue(static_cast<bool>(needGlobal)));
}
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setUnsignedShort(JNIEnv* env, jobject obj,
jstring jkey, jint value) {
auto setting = getSetting<u16>(env, jkey);
if (setting == nullptr) {
return;
}
setting->SetValue(static_cast<u16>(value));
}
jint Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getInt(JNIEnv* env, jobject obj, jstring jkey,
jboolean needGlobal) {
auto setting = getSetting<int>(env, jkey);
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:color="?attr/colorControlHighlight" />
<item android:state_focused="true" android:color="@android:color/transparent" />
<item android:state_selected="true" android:color="@android:color/transparent" />
<item android:state_hovered="true" android:color="@android:color/transparent" />
<item android:color="@android:color/transparent" />
</selector>
@@ -10,7 +10,6 @@
android:clipChildren="true"
android:layout_margin="0dp"
app:cardBackgroundColor="@color/eden_card_background"
app:rippleColor="@color/game_card_ripple"
app:strokeWidth="1dp"
app:strokeColor="@color/eden_border">
@@ -11,7 +11,6 @@
app:cardCornerRadius="16dp"
app:cardPreventCornerOverlap="true"
android:clipChildren="true"
app:rippleColor="@color/game_card_ripple"
android:layout_margin="4dp">
<androidx.constraintlayout.widget.ConstraintLayout
@@ -22,7 +22,6 @@
android:focusable="true"
android:transitionName="card_game"
app:cardCornerRadius="16dp"
app:rippleColor="@color/game_card_ripple"
android:foreground="@color/eden_border_gradient_start">
<androidx.constraintlayout.widget.ConstraintLayout
@@ -22,7 +22,6 @@
android:focusable="true"
android:transitionName="card_game_compact"
app:cardCornerRadius="16dp"
app:rippleColor="@color/game_card_ripple"
android:foreground="@color/eden_border_gradient_start">
<androidx.constraintlayout.widget.ConstraintLayout
@@ -12,7 +12,6 @@
app:cardCornerRadius="16dp"
app:cardElevation="0dp"
app:cardBackgroundColor="@android:color/transparent"
app:rippleColor="@color/game_card_ripple"
app:strokeWidth="0dp">
<androidx.constraintlayout.widget.ConstraintLayout
@@ -1107,6 +1107,7 @@
<string name="theme_mode_light">فاتح</string>
<string name="theme_mode_dark">داكن</string>
<string name="multiplier_none">لا شيء</string>
<!-- Black backgrounds theme -->
<string name="use_black_backgrounds">خلفيات سوداء</string>
@@ -991,6 +991,7 @@ Wirklich fortfahren?</string>
<string name="theme_mode_light">Hell</string>
<string name="theme_mode_dark">Dunkel</string>
<string name="multiplier_none">Keine</string>
<!-- Black backgrounds theme -->
<string name="use_black_backgrounds">Schwarze Hintergründe</string>
@@ -1092,6 +1092,7 @@
<string name="theme_mode_light">Claro</string>
<string name="theme_mode_dark">Oscuro</string>
<string name="multiplier_none">Nada</string>
<!-- Black backgrounds theme -->
<string name="use_black_backgrounds">Fondos oscuros</string>
@@ -808,6 +808,9 @@
<string name="multiplier_x4">x4</string>
<string name="multiplier_x8">x8</string>
<string name="multiplier_x16">x16</string>
<string name="multiplier_x32">x32</string>
<string name="multiplier_x64">x64</string>
<string name="multiplier_none">None</string>
<!-- Black backgrounds theme -->
<string name="use_black_backgrounds">پس‌زمینه مشکی</string>
@@ -1004,6 +1004,7 @@
<string name="theme_mode_light">Lumineux</string>
<string name="theme_mode_dark">Sombre</string>
<string name="multiplier_none">Aucun</string>
<!-- Black backgrounds theme -->
<string name="use_black_backgrounds">Arrière-plan noir</string>
@@ -935,6 +935,7 @@
<string name="theme_mode_light">Jasny</string>
<string name="theme_mode_dark">Ciemny</string>
<string name="multiplier_none">Brak</string>
<!-- Black backgrounds theme -->
<string name="use_black_backgrounds">Czarne tła</string>
@@ -891,6 +891,7 @@
<string name="theme_mode_light">Claro</string>
<string name="theme_mode_dark">Escuro</string>
<string name="multiplier_none">Nenhum</string>
<!-- Black backgrounds theme -->
<string name="use_black_backgrounds">Planos de fundo pretos</string>
@@ -1071,6 +1071,7 @@
<string name="theme_mode_light">Светлая</string>
<string name="theme_mode_dark">Темная</string>
<string name="multiplier_none">Отключено</string>
<!-- Black backgrounds theme -->
<string name="use_black_backgrounds">Чёрный фон</string>
@@ -1053,6 +1053,7 @@
<string name="theme_mode_light">Світла</string>
<string name="theme_mode_dark">Темна</string>
<string name="multiplier_none">Жодного</string>
<!-- Black backgrounds theme -->
<string name="use_black_backgrounds">Чорний фон</string>
@@ -1081,6 +1081,7 @@
<string name="theme_mode_light">浅色</string>
<string name="theme_mode_dark">深色</string>
<string name="multiplier_none"></string>
<!-- Black backgrounds theme -->
<string name="use_black_backgrounds">使用黑色背景</string>
@@ -1006,6 +1006,7 @@
<string name="theme_mode_light">淺色</string>
<string name="theme_mode_dark">深色</string>
<string name="multiplier_none"></string>
<!-- Black backgrounds theme -->
<string name="use_black_backgrounds">黑色背景</string>
+23 -12
View File
@@ -111,38 +111,43 @@
<item>1</item>
</integer-array>
<!-- VRAM USAGE MODE CHOICES -->
<string-array name="vramUsageMethodNames">
<item>@string/vram_usage_conservative</item>
<item>@string/vram_usage_aggressive</item>
</string-array>
<!-- VRAM USAGE MODE VALUES -->
<integer-array name="vramUsageMethodValues">
<item>0</item>
<item>1</item>
<item>0</item> <!-- Conservative -->
<item>1</item> <!-- Aggressive -->
</integer-array>
<!-- ASTC Decoding Method Choices -->
<string-array name="astcDecodingMethodNames">
<item>@string/accelerate_astc_cpu</item>
<item>@string/accelerate_astc_gpu</item>
<item>@string/accelerate_astc_async</item>
</string-array>
<!-- ASTC Decoding Method Values -->
<integer-array name="astcDecodingMethodValues">
<item>0</item>
<item>1</item>
<item>2</item>
<item>0</item> <!-- CPU -->
<item>1</item> <!-- GPU -->
<item>2</item> <!-- CPU Asynchronously -->
</integer-array>
<!-- NVDEC Emulation Choices -->
<string-array name="rendererNvdecNames">
<item>@string/nvdec_emulation_none</item>
<item>@string/nvdec_emulation_cpu</item>
<item>@string/nvdec_emulation_gpu</item>
<item>@string/nvdec_emulation_none</item> <!-- Off -->
<item>@string/nvdec_emulation_cpu</item> <!-- Cpu -->
<item>@string/nvdec_emulation_gpu</item> <!-- Gpu -->
</string-array>
<!-- NVDEC Emulation Values -->
<integer-array name="rendererNvdecValues">
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item> <!-- Off value -->
<item>1</item> <!-- CPU value -->
<item>2</item> <!-- GPU value -->
</integer-array>
<string-array name="rendererResolutionNames">
@@ -508,6 +513,9 @@
<item>@string/multiplier_x4</item>
<item>@string/multiplier_x8</item>
<item>@string/multiplier_x16</item>
<item>@string/multiplier_x32</item>
<item>@string/multiplier_x64</item>
<item>@string/multiplier_none</item>
</string-array>
<integer-array name="anisoValues">
<item>0</item>
@@ -516,6 +524,9 @@
<item>3</item>
<item>4</item>
<item>5</item>
<item>6</item>
<item>7</item>
<item>8</item>
</integer-array>
<string-array name="verticalAlignmentEntries">
@@ -5,6 +5,8 @@
<integer name="game_columns_grid">2</integer>
<integer name="carousel_max_fling_count">4</integer>
<integer name="carousel_focus_search_repeat_threshold_ms">100</integer>
<integer name="carousel_cards_scaling_shape">1</integer>
<integer name="carousel_cards_alpha_shape">4</integer>
<!-- Default SWITCH landscape layout -->
<integer name="BUTTON_A_X">760</integer>
@@ -111,7 +111,7 @@
<!-- NVDEC Emulation -->
<string name="nvdec_emulation">NVDEC Emulation</string>
<string name="nvdec_emulation_description">Change to CPU if a crash occurs on cinematics.</string>
<string name="nvdec_emulation_description">Select how video decoding (NVDEC) is handled during cutscenes and intros.</string>
<string name="nvdec_emulation_cpu" translatable="false">CPU</string>
<string name="nvdec_emulation_gpu" translatable="false">GPU</string>
<string name="nvdec_emulation_none">None</string>
@@ -1246,6 +1246,9 @@
<string name="multiplier_x4" translatable="false">x4</string>
<string name="multiplier_x8" translatable="false">x8</string>
<string name="multiplier_x16" translatable="false">x16</string>
<string name="multiplier_x32" translatable="false">x32</string>
<string name="multiplier_x64" translatable="false">x64</string>
<string name="multiplier_none">None</string>
<!-- Black backgrounds theme -->
<string name="use_black_backgrounds">Black backgrounds</string>
+27 -28
View File
@@ -19,23 +19,6 @@
namespace AudioCore::Sink {
namespace {
[[nodiscard]] bool InitializeAudio() {
if (!SDL_WasInit(SDL_INIT_AUDIO)) {
// See https://github.com/PCSX2/pcsx2/pull/12312
// "SDL and cubeb backends previously resulted in different names for the output which
// caused them be identified as different applications by the OS."
//
// Keep in sync with cubeb_sink.cpp name.
SDL_SetHint("SDL_AUDIO_DEVICE_APP_NAME", "yuzu Latency Getter");
if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) {
LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError());
return false;
}
}
return true;
}
SDL_AudioDeviceID FindAudioDeviceByName(const std::string& device_name, bool capture) {
int device_count = 0;
SDL_AudioDeviceID* devices = capture ? SDL_GetAudioRecordingDevices(&device_count)
@@ -221,14 +204,20 @@ private:
};
SDLSink::SDLSink(std::string_view target_device_name) {
if (InitializeAudio()) {
if (target_device_name != auto_device_name && !target_device_name.empty()) {
output_device = target_device_name;
} else {
output_device.clear();
if (!SDL_WasInit(SDL_INIT_AUDIO)) {
if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) {
LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError());
return;
}
device_channels = 2;
}
if (target_device_name != auto_device_name && !target_device_name.empty()) {
output_device = target_device_name;
} else {
output_device.clear();
}
device_channels = 2;
}
SDLSink::~SDLSink() = default;
@@ -276,10 +265,15 @@ void SDLSink::SetSystemVolume(f32 volume) {
}
std::vector<std::string> ListSDLSinkDevices(bool capture) {
if (!InitializeAudio())
return {}; //no devices
std::vector<std::string> device_list;
if (!SDL_WasInit(SDL_INIT_AUDIO)) {
if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) {
LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError());
return {};
}
}
int device_count = 0;
SDL_AudioDeviceID* devices =
capture ? SDL_GetAudioRecordingDevices(&device_count)
@@ -310,8 +304,13 @@ bool IsSDLSuitable() {
return false;
#else
// Check SDL can init
if (!InitializeAudio()!
return false;
if (!SDL_WasInit(SDL_INIT_AUDIO)) {
if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
LOG_ERROR(Audio_Sink, "SDL failed to init, it is not suitable. Error: {}",
SDL_GetError());
return false;
}
}
// We can set any latency frequency we want with SDL, so no need to check that.
+45 -56
View File
@@ -39,19 +39,6 @@
namespace Common::Log {
/// @brief A log entry. Log entries are store in a structured format to permit more varied output
/// formatting on different frontends, as well as facilitating filtering and aggregation.
struct Entry {
char const* message = nullptr;
size_t message_len = 0;
std::chrono::microseconds timestamp;
Class log_class{};
Level log_level{};
const char* filename = nullptr;
const char* function = nullptr;
uint32_t line_num = 0;
};
namespace {
/// @brief Returns the name of the passed log class as a C-string. Subclasses are separated by periods
@@ -83,6 +70,8 @@ const char* GetLevelName(Level log_level) {
}
}
}
// Some IDEs prefer <file>:<line> instead, so let's just do that :)
std::string FormatLogMessage(const Entry& entry) noexcept {
if (!entry.filename) return "";
@@ -90,9 +79,10 @@ std::string FormatLogMessage(const Entry& entry) noexcept {
auto const time_fractional = uint32_t(entry.timestamp.count() % 1000000);
auto const class_name = GetLogClassName(entry.log_class);
auto const level_name = GetLevelName(entry.log_level);
return fmt::format("[{:4d}.{:06d}] {} <{}> {}:{}:{}: {}\n", time_seconds, time_fractional, class_name, level_name, entry.filename, entry.line_num, entry.function, entry.message);
return fmt::format("[{:4d}.{:06d}] {} <{}> {}:{}:{}: {}", time_seconds, time_fractional, class_name, level_name, entry.filename, entry.line_num, entry.function, entry.message);
}
namespace {
template <typename It>
Level GetLevelByName(const It begin, const It end) {
for (u32 i = 0; i < u32(Level::Count); ++i) {
@@ -137,6 +127,25 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
instance.SetClassLevel(log_class, level);
return true;
}
} // Anonymous namespace
void Filter::ParseFilterString(std::string_view filter_view) {
auto clause_begin = filter_view.cbegin();
while (clause_begin != filter_view.cend()) {
auto clause_end = std::find(clause_begin, filter_view.cend(), ' ');
// If clause isn't empty
if (clause_end != clause_begin) {
ParseFilterRule(*this, clause_begin, clause_end);
}
if (clause_end != filter_view.cend()) {
// Skip over the whitespace
++clause_end;
}
clause_begin = clause_end;
}
}
namespace {
/// @brief Trims up to and including the last of ../, ..\, src/, src\ in a string
/// do not be fooled this isn't generating new strings on .rodata :)
@@ -199,7 +208,7 @@ struct ColorConsoleBackend final : public Backend {
}());
SetConsoleTextAttribute(console_handle, color);
auto const df = GetDirectFormatArgs(entry);
std::fprintf(stdout, CCB_PRINTF_FMT "\n", df.time_seconds, df.time_fractional, df.class_name, df.level_name, entry.filename, entry.line_num, entry.function, entry.message);
std::fprintf(stdout, CCB_PRINTF_FMT "\n", df.time_seconds, df.time_fractional, df.class_name, df.level_name, entry.filename, entry.line_num, entry.function, entry.message.c_str());
}
}
void Flush() noexcept override {}
@@ -211,24 +220,22 @@ struct ColorConsoleBackend final : public Backend {
~ColorConsoleBackend() noexcept override {}
void Write(const Entry& entry) noexcept override {
if (enabled) {
#define ESC "\x1b"
auto const color_str = [&entry]() -> const char* {
switch (entry.log_level) {
case Level::Debug: return "[0;36m"; // Cyan
case Level::Info: return "[0;37m"; // Bright gray
case Level::Warning: return "[1;33m"; // Bright yellow
case Level::Error: return "[1;31m"; // Bright red
case Level::Critical: return "[1;35m"; // Bright magenta
default: return "[1;30m"; // Grey
#define CCB_MAKE_COLOR_FMT(X) ESC X CCB_PRINTF_FMT ESC "[0m\n"
case Level::Debug: return CCB_MAKE_COLOR_FMT("[0;36m"); // Cyan
case Level::Info: return CCB_MAKE_COLOR_FMT("[0;37m"); // Bright gray
case Level::Warning: return CCB_MAKE_COLOR_FMT("[1;33m"); // Bright yellow
case Level::Error: return CCB_MAKE_COLOR_FMT("[1;31m"); // Bright red
case Level::Critical: return CCB_MAKE_COLOR_FMT("[1;35m"); // Bright magenta
default: return CCB_MAKE_COLOR_FMT("[1;30m"); // Grey
#undef CCB_MAKE_COLOR_FMT
}
}();
auto const df = GetDirectFormatArgs(entry);
// more restrictive, because take for example this simple prelude:
// [ 50.872256] Config <Info> common/settings.cpp:142:LogSettings:
char buffer[256];
auto result = fmt::format_to_n(buffer, sizeof(buffer) - 1, "\x1b{}[{:4d}.{:06d}] {} <{}> {}:{}:{}: ", color_str, df.time_seconds, df.time_fractional, df.class_name, df.level_name, entry.filename, entry.line_num, entry.function, entry.message);
std::fwrite(buffer, 1, (std::min)(sizeof(buffer) - 1, result.size), stdout);
std::fwrite(entry.message, 1, entry.message_len, stdout);
std::fwrite("\x1b[0m\n", 1, sizeof("\x1b[0m\n"), stdout);
std::fprintf(stdout, color_str, df.time_seconds, df.time_fractional, df.class_name, df.level_name, entry.filename, entry.line_num, entry.function, entry.message.c_str());
#undef ESC
}
}
void Flush() noexcept override {}
@@ -239,7 +246,7 @@ struct ColorConsoleBackend final : public Backend {
#ifndef __OPENORBIS__
/// @brief Backend that writes to a file passed into the constructor
struct FileBackend final : public Backend {
explicit FileBackend(const std::filesystem::path filename) noexcept {
explicit FileBackend(const std::filesystem::path& filename) noexcept {
auto old_filename = filename;
old_filename += ".old.txt";
// Existence checks are done within the functions themselves.
@@ -254,7 +261,7 @@ struct FileBackend final : public Backend {
if (!enabled)
return;
auto message = FormatLogMessage(entry);
auto message = FormatLogMessage(entry).append(1, '\n');
#ifndef __ANDROID__
if (Settings::values.censor_username.GetValue()) {
// This must be a static otherwise it would get checked on EVERY
@@ -262,7 +269,8 @@ struct FileBackend final : public Backend {
static std::string username = []() -> std::string {
// in order of precedence
// LOGNAME usually works on UNIX, USERNAME on Windows
// Some UNIX systems suck and don't use LOGNAME so we also need USER :(
// Some UNIX systems suck and don't use LOGNAME so we also
// need USER :(
for (auto const var : { "LOGNAME", "USERNAME", "USER", })
if (auto const s = ::getenv(var); s != nullptr)
return std::string{s};
@@ -272,7 +280,7 @@ struct FileBackend final : public Backend {
boost::replace_all(message, username, "user");
}
#endif
bytes_written += file->WriteSpan(std::span<const char>{message.begin(), message.end()});
bytes_written += file->WriteString(message);
// Option to log each line rather than 4k buffers
if (Settings::values.log_flush_line.GetValue())
@@ -300,13 +308,14 @@ private:
bool enabled = true;
};
#endif
#ifdef _WIN32
/// @brief Backend that writes to Visual Studio's output window
struct DebuggerBackend final : public Backend {
explicit DebuggerBackend() noexcept = default;
~DebuggerBackend() noexcept override = default;
void Write(const Entry& entry) noexcept override {
::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry)).c_str());
::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str());
}
void Flush() noexcept override {}
};
@@ -329,7 +338,7 @@ struct LogcatBackend : public Backend {
}
}();
auto const df = GetDirectFormatArgs(entry);
__android_log_print(android_log_priority, "YuzuNative", CCB_PRINTF_FMT, df.time_seconds, df.time_fractional, df.class_name, df.level_name, entry.filename, entry.line_num, entry.function, entry.message);
__android_log_print(android_log_priority, "YuzuNative", CCB_PRINTF_FMT, df.time_seconds, df.time_fractional, df.class_name, df.level_name, entry.filename, entry.line_num, entry.function, entry.message.c_str());
}
void Flush() noexcept override {}
};
@@ -368,23 +377,7 @@ struct Impl {
#endif
std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()};
};
} // Anonymous namespace
void Filter::ParseFilterString(std::string_view filter_view) {
auto clause_begin = filter_view.cbegin();
while (clause_begin < filter_view.cend()) {
auto clause_end = std::find(clause_begin, filter_view.cend(), ' ');
// If clause isn't empty
if (clause_end != clause_begin) {
ParseFilterRule(*this, clause_begin, clause_end);
}
if (clause_end != filter_view.cend()) {
// Skip over the whitespace
++clause_end;
}
clause_begin = clause_end;
}
}
} // namespace
// Constructor shall NOT depend upon Settings() or whatever
// it's ran at global static ctor() time... so BE CAREFUL MFER!
@@ -426,13 +419,9 @@ void SetColorConsoleBackendEnabled(bool enabled) {
void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, unsigned int line_num, const char* function, fmt::string_view format, const fmt::format_args& args) {
if (logging_instance && logging_instance->filter.CheckMessage(log_class, log_level)) {
auto const flush = ::Settings::values.log_flush_line.GetValue();
char buffer[BUFSIZ];
auto result = fmt::vformat_to_n(buffer, sizeof(buffer) - 1, format, args);
buffer[(std::min)(result.size, sizeof(buffer) - 1)] = '\0';
logging_instance->ForEachBackend([=](Backend& backend) {
backend.Write(Entry{
.message = buffer,
.message_len = (std::min)(result.size, sizeof(buffer) - 1),
.message = fmt::vformat(format, args),
.timestamp = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - logging_instance->time_origin),
.log_class = log_class,
.log_level = log_level,
+21
View File
@@ -140,4 +140,25 @@ void Stop();
void SetGlobalFilter(const Filter& filter);
void SetColorConsoleBackendEnabled(bool enabled);
/// @brief A log entry. Log entries are store in a structured format to permit more varied output
/// formatting on different frontends, as well as facilitating filtering and aggregation.
struct Entry {
std::string message;
std::chrono::microseconds timestamp;
Class log_class{};
Level log_level{};
const char* filename = nullptr;
const char* function = nullptr;
unsigned int line_num = 0;
};
/// Formats a log entry into the provided text buffer.
std::string FormatLogMessage(const Entry& entry) noexcept;
/// Prints the same message as `PrintMessage`, but colored according to the severity level.
void PrintColoredMessage(const Entry& entry) noexcept;
/// Formats and prints a log entry to the android logcat.
void PrintMessageToLogcat(const Entry& entry) noexcept;
} // namespace Common::Log
+1 -1
View File
@@ -241,7 +241,7 @@ std::optional<std::string> MakeRequest(const std::string& url, const std::string
response.status);
return {};
}
if (!response.has_header("content-type")) {
if (!response.headers.contains("content-type")) {
LOG_ERROR(Common, "GET to {}{} returned no content", url, path);
return {};
}
+5 -3
View File
@@ -132,9 +132,11 @@ void LogSettings() {
}
}
}
LOG_INFO(Config, "Eden Configuration:");
std::string settings_str{};
for (auto const& e : settings_list)
LOG_INFO(Config, "{}", e);
settings_str += e;
LOG_INFO(Config, "Eden Configuration:\n{}", settings_str);
#define LOG_PATH(NAME) \
LOG_INFO(Config, #NAME ": {}", Common::FS::PathToUTF8String(Common::FS::GetEdenPath(Common::FS::EdenPath::NAME)))
LOG_PATH(CacheDir);
@@ -146,7 +148,7 @@ void LogSettings() {
#undef LOG_PATH
}
bool GetDebugKnobAt(u8 i) {
bool getDebugKnobAt(u8 i) {
return (values.debug_knobs.GetValue() & (1 << (i & 0xF))) != 0;
}
+1 -1
View File
@@ -904,7 +904,7 @@ struct Values {
0,
65535,
"debug_knobs",
Category::System,
Category::Debugging,
Specialization::Countable,
true,
true};
+1 -1
View File
@@ -128,7 +128,7 @@ ENUM(TimeZone, Auto, Default, Cet, Cst6Cdt, Cuba, Eet, Egypt, Eire, Est, Est5Edt
GmtPlusZero, GmtMinusZero, GmtZero, Greenwich, Hongkong, Hst, Iceland, Iran, Israel, Jamaica,
Japan, Kwajalein, Libya, Met, Mst, Mst7Mdt, Navajo, Nz, NzChat, Poland, Portugal, Prc, Pst8Pdt,
Roc, Rok, Singapore, Turkey, Uct, Universal, Utc, WSu, Wet, Zulu);
ENUM(AnisotropyMode, Automatic, Default, X2, X4, X8, X16);
ENUM(AnisotropyMode, Automatic, Default, X2, X4, X8, X16, X32, X64, None);
ENUM(AstcDecodeMode, Cpu, Gpu, CpuAsynchronous);
ENUM(AstcRecompression, Uncompressed, Bc1, Bc3);
ENUM(FramePacingMode, Target_Auto, Target_30, Target_60, Target_90, Target_120);
+2
View File
@@ -7,4 +7,6 @@
#define STB_IMAGE_IMPLEMENTATION 1
#define STB_IMAGE_RESIZE_IMPLEMENTATION 1
#define STB_IMAGE_WRITE_IMPLEMENTATION 1
#define STBI_ONLY_JPEG 1
#include "common/stb.h"
-1
View File
@@ -7,7 +7,6 @@
#pragma once
#define STBI_ONLY_JPEG 1
#define STBI_WRITE_NO_STDIO 1
#include <stb_image.h>
#include <stb_image_resize.h>
#include <stb_image_write.h>
@@ -286,7 +286,6 @@ void ArmDynarmic32::MakeJit(Common::PageTable* page_table) {
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreStandardFPCRValue;
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN;
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreGlobalMonitor;
break;
// Paranoia mode for debugging optimizations
case Settings::CpuAccuracy::Paranoid:
@@ -338,7 +338,6 @@ void ArmDynarmic64::MakeJit(Common::PageTable* page_table, std::size_t address_s
config.unsafe_optimizations = true;
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
config.fastmem_address_space_bits = 64;
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreGlobalMonitor;
break;
// Paranoia mode for debugging optimizations
case Settings::CpuAccuracy::Paranoid:
+160 -233
View File
@@ -3,10 +3,12 @@
#include <algorithm>
#include <cstring>
#include <map>
#include <sstream>
#include <string>
#include <utility>
#include <span>
#include <cctype>
#include <ankerl/unordered_dense.h>
#include "common/hex_util.h"
#include "common/logging.h"
@@ -22,61 +24,30 @@ enum class IPSFileType {
Error,
};
constexpr std::array<std::pair<const char*, const char*>, 11> ESCAPE_CHARACTER_MAP{{
{"\\a", "\a"},
{"\\b", "\b"},
{"\\f", "\f"},
{"\\n", "\n"},
{"\\r", "\r"},
{"\\t", "\t"},
{"\\v", "\v"},
{"\\\\", "\\"},
{"\\\'", "\'"},
{"\\\"", "\""},
{"\\\?", "\?"},
}};
static IPSFileType IdentifyMagic(const std::vector<u8>& magic) {
if (magic.size() != 5) {
return IPSFileType::Error;
static IPSFileType IdentifyMagic(std::span<const u8> magic) {
if (magic.size() >= 5) {
if (std::memcmp(magic.data(), "PATCH", 5) == 0)
return IPSFileType::IPS;
if (std::memcmp(magic.data(), "IPS32", 5) == 0)
return IPSFileType::IPS32;
}
static constexpr std::array<u8, 5> patch_magic{{'P', 'A', 'T', 'C', 'H'}};
if (std::equal(magic.begin(), magic.end(), patch_magic.begin())) {
return IPSFileType::IPS;
}
static constexpr std::array<u8, 5> ips32_magic{{'I', 'P', 'S', '3', '2'}};
if (std::equal(magic.begin(), magic.end(), ips32_magic.begin())) {
return IPSFileType::IPS32;
}
return IPSFileType::Error;
}
static bool IsEOF(IPSFileType type, const std::vector<u8>& data) {
static constexpr std::array<u8, 3> eof{{'E', 'O', 'F'}};
if (type == IPSFileType::IPS && std::equal(data.begin(), data.end(), eof.begin())) {
return true;
}
static constexpr std::array<u8, 4> eeof{{'E', 'E', 'O', 'F'}};
return type == IPSFileType::IPS32 && std::equal(data.begin(), data.end(), eeof.begin());
static bool IsEOF(IPSFileType type, std::span<const u8> magic) {
return (type == IPSFileType::IPS && magic.size() > 3 && std::memcmp(magic.data(), "EOF", 3) == 0)
|| (type == IPSFileType::IPS32 && magic.size() > 4 && std::memcmp(magic.data(), "EEOF", 4) == 0);
}
VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
if (in == nullptr || ips == nullptr)
return nullptr;
const auto type = IdentifyMagic(ips->ReadBytes(0x5));
auto in_data = in->ReadAllBytes();
auto const type = IdentifyMagic(in_data);
if (type == IPSFileType::Error)
return nullptr;
auto in_data = in->ReadAllBytes();
if (in_data.size() == 0) {
return nullptr;
}
std::vector<u8> temp(type == IPSFileType::IPS ? 3 : 4);
u64 offset = 5; // After header
while (ips->Read(temp.data(), temp.size(), offset) == temp.size()) {
@@ -85,12 +56,9 @@ VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
break;
}
u32 real_offset{};
if (type == IPSFileType::IPS32)
real_offset = (temp[0] << 24) | (temp[1] << 16) | (temp[2] << 8) | temp[3];
else
real_offset = (temp[0] << 16) | (temp[1] << 8) | temp[2];
u32 real_offset = (type == IPSFileType::IPS32)
? ((temp[0] << 24) | (temp[1] << 16) | (temp[2] << 8) | temp[3])
: ((temp[0] << 16) | (temp[1] << 8) | temp[2]);
if (real_offset > in_data.size()) {
return nullptr;
}
@@ -113,34 +81,35 @@ VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
return nullptr;
if (real_offset + rle_size > in_data.size())
rle_size = static_cast<u16>(in_data.size() - real_offset);
rle_size = u16(in_data.size() - real_offset);
std::memset(in_data.data() + real_offset, *data, rle_size);
} else { // Standard Patch
auto read = data_size;
if (real_offset + read > in_data.size())
read = static_cast<u16>(in_data.size() - real_offset);
read = u16(in_data.size() - real_offset);
if (ips->Read(in_data.data() + real_offset, read, offset) != data_size)
return nullptr;
offset += data_size;
}
}
if (!IsEOF(type, temp)) {
return nullptr;
if (IsEOF(type, temp)) {
return std::make_shared<VectorVfsFile>(std::move(in_data), in->GetName(), in->GetContainingDirectory());
}
return std::make_shared<VectorVfsFile>(std::move(in_data), in->GetName(),
in->GetContainingDirectory());
return nullptr;
}
struct IPSwitchRecord {
std::array<uint8_t, 256 - sizeof(size_t)> data;
size_t count;
};
struct IPSwitchCompiler::IPSwitchPatch {
std::string name;
ankerl::unordered_dense::map<u32, IPSwitchRecord> records;
bool enabled;
std::map<u32, std::vector<u8>> records;
};
IPSwitchCompiler::IPSwitchCompiler(VirtualFile patch_text_) : patch_text(std::move(patch_text_)) {
Parse();
Parse(patch_text->ReadAllBytes());
}
IPSwitchCompiler::~IPSwitchCompiler() = default;
@@ -149,201 +118,159 @@ std::array<u8, 32> IPSwitchCompiler::GetBuildID() const {
return nso_build_id;
}
bool IPSwitchCompiler::IsValid() const {
return valid;
}
static bool StartsWith(std::string_view base, std::string_view check) {
return base.size() >= check.size() && base.substr(0, check.size()) == check;
}
static std::string EscapeStringSequences(std::string in) {
for (const auto& seq : ESCAPE_CHARACTER_MAP) {
for (auto index = in.find(seq.first); index != std::string::npos;
index = in.find(seq.first, index)) {
in.replace(index, std::strlen(seq.first), seq.second);
index += std::strlen(seq.second);
static IPSwitchRecord EscapeStringSequences(std::string_view sv) {
IPSwitchRecord r{};
for (auto it = sv.cbegin(); it != sv.cend(); ) {
if (*it == '\\' && it + 1 < sv.cend()) {
switch (it[1]) {
case 'n': r.data[r.count] = '\n'; break;
case 't': r.data[r.count] = '\t'; break;
case 'b': r.data[r.count] = '\b'; break;
case 'r': r.data[r.count] = '\r'; break;
case 'e': r.data[r.count] = '\e'; break;
case 'v': r.data[r.count] = '\v'; break;
case '?': r.data[r.count] = '\?'; break;
default: r.data[r.count] = it[1]; break;
}
++r.count;
it += 2;
} else {
++r.count;
++it;
}
}
return in;
return r;
}
void IPSwitchCompiler::ParseFlag(const std::string& line) {
if (StartsWith(line, "@flag offset_shift ")) {
// Offset Shift Flag
offset_shift = std::strtoll(line.substr(19).c_str(), nullptr, 0);
} else if (StartsWith(line, "@little-endian")) {
// Set values to read as little endian
is_little_endian = true;
} else if (StartsWith(line, "@big-endian")) {
// Set values to read as big endian
is_little_endian = false;
} else if (StartsWith(line, "@flag print_values")) {
// Force printing of applied values
print_values = true;
}
[[nodiscard]] static inline std::array<u8, 32> ReadNSOBuildId(std::string_view const s) {
std::array<u8, 32> r{};
for (std::size_t i = 0; i < s.size(); ++i)
r[i / 2] |= u8(u8(Common::ToHexNibble(s[i])) << u8((i % 2) * 4));
return r;
}
void IPSwitchCompiler::Parse() {
const auto bytes = patch_text->ReadAllBytes();
std::stringstream s;
s.write(reinterpret_cast<const char*>(bytes.data()), bytes.size());
void IPSwitchCompiler::Parse(std::span<u8 const> bytes) {
LOG_INFO(Loader, "IPSwitchCompiler: '{}'", patch_text->GetName());
bool is_little_endian = false;
s64 offset_shift = 0;
//bool print_values = false;
std::vector<std::string> lines;
std::string stream_line;
while (std::getline(s, stream_line)) {
// Remove a trailing \r
if (!stream_line.empty() && stream_line.back() == '\r')
stream_line.pop_back();
lines.push_back(std::move(stream_line));
}
auto const parse_line = [&](std::string_view const line) {
// Keep in mind lines have trimmed spaces (at the end & start)!
LOG_INFO(Loader, "<{}>", line);
if (line.starts_with("@stop")) {
return false; // Force stop
} else if (line.starts_with("@nsobid-")) { // NSO Build ID Specifier
nso_build_id = ReadNSOBuildId(line.substr(8));
} else if (line.starts_with("@enabled")) {
patches.push_back({{}, true}); //enabled patch
} else if (line.starts_with("@disabled")) {
patches.push_back({{}, false}); //disabled patch
} else if (line.starts_with("@flag offset_shift ")) {
offset_shift = std::strtoll(line.data() + 19, nullptr, 0); // Offset Shift Flag
} else if (line.starts_with("@little-endian")) {
is_little_endian = true; // Set values to read as little endian
} else if (line.starts_with("@big-endian")) {
is_little_endian = false; // Set values to read as big endian
} else if (line.starts_with("@flag print_values")) {
//print_values = true; // Force printing of applied values
} else if (line.starts_with("@")) {
LOG_WARNING(Loader, "Unknown flag {}", line);
} else {
size_t offset = size_t(std::strtoul(line.data(), nullptr, 16));
offset += size_t(offset_shift);
if (auto const first_quote = line.find_first_of("\"\'"); first_quote != std::string::npos) {
// string replacement
char quote = line[first_quote];
auto const start = line.cbegin() + first_quote + 1;
auto end = start;
for (; end < line.cend() && *end != quote; )
end += (*end == '\\') ? 2 : 1;
if (start <= line.cend() && end <= line.cend()) {
LOG_INFO(Loader, "[S] value @ {:#08X} ", offset);
patches.back().records.insert_or_assign(u32(offset), EscapeStringSequences({start, end}));
} else {
LOG_WARNING(Loader, "invalid string");
}
} else if (auto const first_space = line.find_last_of(" /\t\r\n"); first_space != std::string::npos) {
IPSwitchRecord r{}; // hex replacement
auto const start = line.cbegin() + first_space + 1;
auto const end = line.cend();
if (start <= line.cend() && end <= line.cend()) {
auto const hs = Common::HexStringToVector({start, end}, is_little_endian);
std::memcpy(r.data.data(), hs.data(), hs.size());
r.count = hs.size();
LOG_INFO(Loader, "[H] value @ {:#08X}", offset);
patches.back().records.insert_or_assign(u32(offset), std::move(r));
} else {
LOG_WARNING(Loader, "invalid line");
}
} else {
LOG_WARNING(Loader, "unhandled line!");
}
}
return true; //continue
};
for (std::size_t i = 0; i < lines.size(); ++i) {
auto line = lines[i];
// Remove midline comments
std::size_t comment_index = std::string::npos;
bool within_string = false;
for (std::size_t k = 0; k < line.size(); ++k) {
if (line[k] == '\"' && (k > 0 && line[k - 1] != '\\')) {
within_string = !within_string;
} else if (line[k] == '\\' && (k < line.size() - 1 && line[k + 1] == '\\')) {
comment_index = k;
for (auto it = bytes.begin(); it < bytes.end(); ) {
auto const start = it;
auto end = start;
for (; end < bytes.end() && *end != '\n' && *end != '\r'; ++end)
;
it = end + 1; //prepare for next line
std::string_view const sline{
reinterpret_cast<const char*>(bytes.data() + std::distance(bytes.begin(), start)),
size_t(std::distance(start, end))
};
if (sline.size() > 0) {
auto p = sline.cbegin();
// skip space off line
for (; p < sline.cend() && std::isspace(*p); ++p)
;
// now make a nominal preprocessed line: remove comments
char quote = '\0';
auto const sline_start = p;
for (; p < sline.cend(); ) {
if ((!quote && p + 1 < sline.cend() && p[0] == '/' && p[1] == '/')
|| (!quote && p[0] == '#')) {
break;
} else if (p[0] == '\"' || p[0] == '\'') {
quote = (p[0] == quote) ? '\0' : p[0];
++p;
} else if (p + 1 < sline.cend() && p[0] == '\\') {
p += 2;
} else {
++p;
}
}
// now we have the preprocessed string ;)
std::string_view pp_str(sline_start, p);
if (pp_str.size() > 0 && !parse_line(pp_str)) {
break;
}
}
if (!StartsWith(line, "//") && comment_index != std::string::npos) {
last_comment = line.substr(comment_index + 2);
line = line.substr(0, comment_index);
}
if (StartsWith(line, "@stop")) {
// Force stop
break;
} else if (StartsWith(line, "@nsobid-")) {
// NSO Build ID Specifier
const auto raw_build_id = fmt::format("{:0<64}", line.substr(8));
nso_build_id = Common::HexStringToArray<0x20>(raw_build_id);
} else if (StartsWith(line, "#")) {
// Mandatory Comment
LOG_INFO(Loader, "[IPSwitchCompiler ('{}')] Forced output comment: {}",
patch_text->GetName(), line.substr(1));
} else if (StartsWith(line, "//")) {
// Normal Comment
last_comment = line.substr(2);
if (last_comment.find_first_not_of(' ') == std::string::npos)
continue;
if (last_comment.find_first_not_of(' ') != 0)
last_comment = last_comment.substr(last_comment.find_first_not_of(' '));
} else if (StartsWith(line, "@enabled") || StartsWith(line, "@disabled")) {
// Start of patch
const auto enabled = StartsWith(line, "@enabled");
if (i == 0)
return;
LOG_INFO(Loader, "[IPSwitchCompiler ('{}')] Parsing patch '{}' ({})",
patch_text->GetName(), last_comment, line.substr(1));
IPSwitchPatch patch{last_comment, enabled, {}};
// Read rest of patch
while (true) {
if (i + 1 >= lines.size()) {
break;
}
const auto& patch_line = lines[++i];
// Patch line may contain comments
if (StartsWith(patch_line, "//") || StartsWith(patch_line, "#")) {
continue;
}
// Start of new patch
if (StartsWith(patch_line, "@enabled") || StartsWith(patch_line, "@disabled")) {
--i;
break;
}
// Check for a flag
if (StartsWith(patch_line, "@")) {
ParseFlag(patch_line);
continue;
}
// 11 - 8 hex digit offset + space + minimum two digit overwrite val
if (patch_line.length() < 11)
break;
auto offset = std::strtoul(patch_line.substr(0, 8).c_str(), nullptr, 16);
offset += static_cast<unsigned long>(offset_shift);
std::vector<u8> replace;
// 9 - first char of replacement val
if (patch_line[9] == '\"') {
// string replacement
auto end_index = patch_line.find('\"', 10);
if (end_index == std::string::npos || end_index < 10)
return;
while (patch_line[end_index - 1] == '\\') {
end_index = patch_line.find('\"', end_index + 1);
if (end_index == std::string::npos || end_index < 10)
return;
}
auto value = patch_line.substr(10, end_index - 10);
value = EscapeStringSequences(value);
replace.reserve(value.size());
std::copy(value.begin(), value.end(), std::back_inserter(replace));
} else {
// hex replacement
const auto value =
patch_line.substr(9, patch_line.find_first_of(" /\r\n", 9) - 9);
replace = Common::HexStringToVector(value, is_little_endian);
}
if (print_values) {
LOG_INFO(Loader,
"[IPSwitchCompiler ('{}')] - Patching value at offset {:#08x} "
"with byte string '{}'",
patch_text->GetName(), offset, Common::HexToString(replace));
}
patch.records.insert_or_assign(static_cast<u32>(offset), std::move(replace));
}
patches.push_back(std::move(patch));
} else if (StartsWith(line, "@")) {
ParseFlag(line);
}
}
valid = true;
}
VirtualFile IPSwitchCompiler::Apply(const VirtualFile& in) const {
if (in == nullptr || !valid)
if (in == nullptr)
return nullptr;
auto in_data = in->ReadAllBytes();
for (const auto& patch : patches) {
if (!patch.enabled)
continue;
for (const auto& record : patch.records) {
if (record.first >= in_data.size())
continue;
auto replace_size = record.second.size();
if (record.first + replace_size > in_data.size())
replace_size = in_data.size() - record.first;
for (std::size_t i = 0; i < replace_size; ++i)
in_data[i + record.first] = record.second[i];
if (patch.enabled) {
for (const auto& record : patch.records) {
if (record.first < in_data.size()) {
auto replace_size = record.second.count;
if (record.first + replace_size > in_data.size())
replace_size = in_data.size() - record.first;
std::memcpy(in_data.data() + record.first, record.second.data.data(), replace_size);
}
}
}
}
return std::make_shared<VectorVfsFile>(std::move(in_data), in->GetName(),
in->GetContainingDirectory());
return std::make_shared<VectorVfsFile>(std::move(in_data), in->GetName(), in->GetContainingDirectory());
}
} // namespace FileSys
+5 -9
View File
@@ -1,11 +1,14 @@
// SPDX-FileCopyrightText: Copyright 2026 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
#pragma once
#include <array>
#include <memory>
#include <vector>
#include <span>
#include "common/common_types.h"
#include "core/file_sys/vfs/vfs.h"
@@ -20,24 +23,17 @@ public:
~IPSwitchCompiler();
std::array<u8, 0x20> GetBuildID() const;
bool IsValid() const;
VirtualFile Apply(const VirtualFile& in) const;
private:
struct IPSwitchPatch;
void ParseFlag(const std::string& flag);
void Parse();
bool valid = false;
void Parse(std::span<u8 const> bytes);
VirtualFile patch_text;
std::vector<IPSwitchPatch> patches;
std::array<u8, 0x20> nso_build_id{};
bool is_little_endian = false;
s64 offset_shift = 0;
bool print_values = false;
std::string last_comment = "";
};
} // namespace FileSys
+2 -9
View File
@@ -345,8 +345,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
return exefs;
}
std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualDir>& patch_dirs,
const std::string& build_id) const {
std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualDir>& patch_dirs, const std::string& build_id) const {
const auto& disabled = Settings::values.disabled_addons[title_id];
const auto nso_build_id = fmt::format("{:0<64}", build_id);
@@ -361,16 +360,11 @@ std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualD
for (const auto& file : exefs_dir->GetFiles()) {
if (file->GetExtension() == "ips") {
auto name = file->GetName();
const auto this_build_id =
fmt::format("{:0<64}", name.substr(0, name.find('.')));
const auto this_build_id = fmt::format("{:0<64}", name.substr(0, name.find('.')));
if (nso_build_id == this_build_id)
out.push_back(file);
} else if (file->GetExtension() == "pchtxt") {
IPSwitchCompiler compiler{file};
if (!compiler.IsValid())
continue;
const auto this_build_id = Common::HexToString(compiler.GetBuildID());
if (nso_build_id == this_build_id)
out.push_back(file);
@@ -378,7 +372,6 @@ std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualD
}
}
}
return out;
}
@@ -324,7 +324,6 @@ Result IApplicationFunctions::NotifyRunning(Out<bool> out_became_running) {
Result IApplicationFunctions::GetPseudoDeviceId(Out<Common::UUID> out_pseudo_device_id) {
LOG_WARNING(Service_AM, "(stubbed)");
R_UNLESS(out_pseudo_device_id, ResultUnknown);
// This should be hashed with the device specific hash
// for now this will do
+13 -118
View File
@@ -5,136 +5,33 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <chrono>
#include <mutex>
#include <string>
#include <vector>
#include <ctime>
#include "core/core.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/cmif_types.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/nim/nim.h"
#include "core/hle/service/os/event.h"
#include "core/hle/service/server_manager.h"
#include "core/hle/service/service.h"
namespace Service::NIM {
class IShopServiceAsync final : public ServiceFramework<IShopServiceAsync> {
public:
explicit IShopServiceAsync(Core::System& system_)
: ServiceFramework{system_, "IShopServiceAsync"},
service_context{system_, "IShopServiceAsync"} {
: ServiceFramework{system_, "IShopServiceAsync"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, D<&IShopServiceAsync::Cancel>, "Cancel"},
{1, D<&IShopServiceAsync::GetSize>, "GetSize"},
{2, D<&IShopServiceAsync::Read>, "Read"},
{3, D<&IShopServiceAsync::GetErrorCode>, "GetErrorCode"},
{4, D<&IShopServiceAsync::Request>, "Request"},
{5, D<&IShopServiceAsync::Prepare>, "Prepare"},
{0, nullptr, "Cancel"},
{1, nullptr, "GetSize"},
{2, nullptr, "Read"},
{3, nullptr, "GetErrorCode"},
{4, nullptr, "Request"},
{5, nullptr, "Prepare"},
};
// clang-format on
RegisterHandlers(functions);
completion_event = service_context.CreateEvent("IShopServiceAsync:Completion");
}
~IShopServiceAsync() override {
CancelImpl();
service_context.CloseEvent(completion_event);
}
Kernel::KReadableEvent* GetEvent() const {
return &completion_event->GetReadableEvent();
}
private:
KernelHelpers::ServiceContext service_context;
Kernel::KEvent* completion_event;
std::jthread worker;
std::atomic<u32> error_code{0};
std::mutex data_mutex;
std::vector<u8> download_data;
void CancelImpl() {
worker.request_stop();
if (worker.joinable()) {
worker.join();
}
}
Result Cancel() {
LOG_DEBUG(Service_NIM, "called");
CancelImpl();
R_SUCCEED();
}
Result GetSize(Out<u64> out_size) {
LOG_DEBUG(Service_NIM, "called");
std::scoped_lock lock{data_mutex};
*out_size = download_data.size();
R_SUCCEED();
}
Result Read(Out<u64> out_size, u64 offset, OutBuffer<BufferAttr_HipcAutoSelect> out_buffer) {
std::scoped_lock lock{data_mutex};
u64 actual_read = 0;
if (offset < download_data.size()) {
actual_read = std::min<u64>(out_buffer.size(), download_data.size() - offset);
std::memcpy(out_buffer.data(), download_data.data() + offset, actual_read);
}
*out_size = actual_read;
R_SUCCEED();
}
Result GetErrorCode(Out<u32> out_error_code) {
LOG_DEBUG(Service_NIM, "called");
*out_error_code = error_code.load();
R_SUCCEED();
}
Result Request() {
LOG_DEBUG(Service_NIM, "(STUBBED) called");
CancelImpl();
error_code.store(0);
completion_event->Clear(system.Kernel());
{
std::scoped_lock lock{data_mutex};
download_data.clear();
}
worker = std::jthread([this](const std::stop_token& stop_token) {
if (stop_token.stop_requested()) {
error_code.store(1);
} else {
std::scoped_lock lock{data_mutex};
// Dummy JSON response, else it fails...
const std::string dummy_response = "{}";
download_data.assign(dummy_response.begin(), dummy_response.end());
error_code.store(0);
}
completion_event->Signal(system.Kernel());
});
R_SUCCEED();
}
Result Prepare(InArray<char, BufferAttr_HipcMapAlias> in_path, InArray<char, BufferAttr_HipcMapAlias> in_post) {
LOG_DEBUG(Service_NIM, "called");
if (!in_path.empty()) {
std::string url(in_path.data(), in_path.size());
LOG_INFO(Service_NIM, "Preparing request for URL: {}", url);
}
R_SUCCEED();
}
};
@@ -152,13 +49,11 @@ public:
}
private:
void CreateAsyncInterface(HLERequestContext& ctx) {LOG_DEBUG(Service_NIM, "called");
auto async_interface = std::make_shared<IShopServiceAsync>(system);
IPC::ResponseBuilder rb{ctx, 2, 1, 1};
void CreateAsyncInterface(HLERequestContext& ctx) {
LOG_WARNING(Service_NIM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
rb.PushCopyObjects(ctx, async_interface->GetEvent());
rb.PushIpcInterface<IShopServiceAsync>(ctx, std::move(async_interface));
rb.PushIpcInterface<IShopServiceAsync>(ctx, system);
}
};
@@ -9,7 +9,11 @@
#include <optional>
#include <string>
#include "common/stb.h"
#define STBI_ONLY_JPEG 1
#include <stb_image.h>
#include <stb_image_resize.h>
#include <stb_image_write.h>
#include "common/settings.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
+1 -6
View File
@@ -105,12 +105,7 @@ void LoopProcess(Core::System& system) {
auto server_manager = std::make_unique<ServerManager>(system);
auto module = std::make_shared<Module>(system);
server_manager->RegisterNamedService("nvdrv", std::make_shared<NVDRV>(system, module, "nvdrv"));
const auto NvdrvInterfaceFactoryForApplets = [&, module] {
return std::make_shared<NVDRV>(system, module, "nvdrv:a");
};
server_manager->RegisterNamedService("nvdrv:a", NvdrvInterfaceFactoryForApplets);
server_manager->RegisterNamedService("nvdrv:a", std::make_shared<NVDRV>(system, module, "nvdrv:a"));
server_manager->RegisterNamedService("nvdrv:s", std::make_shared<NVDRV>(system, module, "nvdrv:s"));
server_manager->RegisterNamedService("nvdrv:t", std::make_shared<NVDRV>(system, module, "nvdrv:t"));
server_manager->RegisterNamedService("nvmemp", std::make_shared<NVMEMP>(system));
+1 -6
View File
@@ -588,12 +588,7 @@ void LoopProcess(Core::System& system) {
auto server_manager = std::make_unique<ServerManager>(system);
auto ro = std::make_shared<RoContext>();
const auto RoInterfaceFactoryForUser = [&, ro] {
return std::make_shared<RoInterface>(system, "ldr:ro", ro, NrrKind::User);
};
server_manager->RegisterNamedService("ldr:ro", std::move(RoInterfaceFactoryForUser));
server_manager->RegisterNamedService("ldr:ro", std::make_shared<RoInterface>(system, "ldr:ro", ro, NrrKind::User));
server_manager->RegisterNamedService("ro:1", std::make_shared<RoInterface>(system, "ro:1", ro, NrrKind::JitPlugin));
server_manager->RegisterNamedService("ro:dmnt", std::make_shared<IDebugMonitorInterface>(system));
ServerManager::RunServer(std::move(server_manager));
+5 -5
View File
@@ -114,13 +114,13 @@ std::vector<Network::ScanData> ScanWifiNetworks(std::chrono::milliseconds deadli
char ifname[IFNAMSIZ] = {0};
char *args[1] = {ifname};
iw_enum_devices(sock, [](int f_skfd, char* f_ifname, char* f_args[], int) -> int {
iw_enum_devices(sock, [](int skfd, char* ifname, char* args[], int count) -> int {
iwrange range;
int res = iw_get_range_info(f_skfd, f_ifname, &range);
LOG_INFO(Network, "ifname {} returned {} on iw_get_range_info", f_ifname, res);
int res = iw_get_range_info(skfd, ifname, &range);
LOG_INFO(Network, "ifname {} returned {} on iw_get_range_info", ifname, res);
if (res >= 0) {
strncpy(f_args[0], f_ifname, IFNAMSIZ - 1);
f_args[0][IFNAMSIZ - 1] = 0;
strncpy(args[0], ifname, IFNAMSIZ - 1);
args[0][IFNAMSIZ - 1] = 0;
return 1;
}
return 0;
@@ -22,7 +22,6 @@
#include <sys/mman.h>
#include "common/assert.h"
#include "common/logging.h"
#include "common/common_types.h"
#include "dynarmic/backend/exception_handler.h"
#include "dynarmic/common/context.h"
@@ -54,21 +53,23 @@ class SigHandler {
return e.first <= offset && e.first + e.second.size > offset;
});
}
static void SigAction(int sig, siginfo_t* info, void* raw_context);
bool supports_fast_mem = true;
void* signal_stack_memory = nullptr;
ankerl::unordered_dense::map<u64, CodeBlockInfo> code_block_infos;
std::shared_mutex code_block_infos_mutex;
struct sigaction old_sa_segv;
struct sigaction old_sa_bus;
std::unique_ptr<uint8_t[]> signal_stack_memory;
bool supports_fast_mem = true;
std::size_t signal_stack_size;
public:
SigHandler() noexcept {
auto const stack_size = std::max<size_t>(SIGSTKSZ, 2 * 1024 * 1024);
signal_stack_memory = std::make_unique<uint8_t[]>(stack_size);
signal_stack_size = std::max<size_t>(SIGSTKSZ, 2 * 1024 * 1024);
signal_stack_memory = mmap(nullptr, signal_stack_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
stack_t signal_stack{};
signal_stack.ss_sp = signal_stack_memory.get();
signal_stack.ss_size = stack_size;
signal_stack.ss_sp = signal_stack_memory;
signal_stack.ss_size = signal_stack_size;
signal_stack.ss_flags = 0;
if (sigaltstack(&signal_stack, nullptr) != 0) {
fmt::print(stderr, "dynarmic: POSIX SigHandler: init failure at sigaltstack\n");
@@ -86,7 +87,7 @@ public:
supports_fast_mem = false;
return;
}
#if defined(__APPLE__)
#ifdef __APPLE__
if (sigaction(SIGBUS, &sa, &old_sa_bus) != 0) {
fmt::print(stderr, "dynarmic: POSIX SigHandler: could not set SIGBUS handler\n");
supports_fast_mem = false;
@@ -95,6 +96,10 @@ public:
#endif
}
~SigHandler() noexcept {
munmap(signal_stack_memory, signal_stack_size);
}
void AddCodeBlock(u64 offset, CodeBlockInfo cbi) noexcept {
std::unique_lock guard(code_block_infos_mutex);
code_block_infos.insert_or_assign(offset, cbi);
@@ -104,17 +109,14 @@ public:
code_block_infos.erase(offset);
}
[[nodiscard]] inline bool SupportsFastmem() const noexcept {
return supports_fast_mem;
}
static void RegisterHandler();
static void SigAction(int sig, siginfo_t* info, void* raw_context);
bool SupportsFastmem() const noexcept { return supports_fast_mem; }
};
std::mutex handler_lock;
std::optional<SigHandler> sig_handler;
void SigHandler::RegisterHandler() {
void RegisterHandler() {
std::lock_guard<std::mutex> guard(handler_lock);
if (!sig_handler) {
sig_handler.emplace();
}
@@ -123,27 +125,51 @@ void SigHandler::RegisterHandler() {
void SigHandler::SigAction(int sig, siginfo_t* info, void* raw_context) {
DEBUG_ASSERT(sig == SIGSEGV || sig == SIGBUS);
CTX_DECLARE(raw_context);
#if defined(ARCHITECTURE_x86_64)
{
std::shared_lock guard(sig_handler->code_block_infos_mutex);
if (auto const iter = sig_handler->FindCodeBlockInfo(CTX_PC); iter != sig_handler->code_block_infos.end()) {
FakeCall fc = iter->second.cb(CTX_PC);
#if defined(ARCHITECTURE_x86_64)
CTX_SP -= sizeof(u64);
*std::bit_cast<u64*>(CTX_SP) = fc.ret_rip;
CTX_PC = fc.call_rip;
#elif defined(ARCHITECTURE_arm64)
CTX_PC = fc.call_pc;
#elif defined(ARCHITECTURE_riscv64)
CTX_PC = fc.call_sepc;
#elif defined(ARCHITECTURE_loongarch64)
CTX_PC = fc.call_pc;
#else
ASSERT(false);
#endif
return;
}
}
LOG_ERROR(Core, "Unhandled {} at {:#018x}\n", sig == SIGSEGV ? "SIGSEGV" : "SIGBUS", CTX_PC);
fmt::print(stderr, "Unhandled {} at rip {:#018x}\n", sig == SIGSEGV ? "SIGSEGV" : "SIGBUS", CTX_PC);
#elif defined(ARCHITECTURE_arm64)
{
std::shared_lock guard(sig_handler->code_block_infos_mutex);
if (const auto iter = sig_handler->FindCodeBlockInfo(CTX_PC); iter != sig_handler->code_block_infos.end()) {
FakeCall fc = iter->second.cb(CTX_PC);
CTX_PC = fc.call_pc;
return;
}
}
fmt::print(stderr, "Unhandled {} at pc {:#018x}\n", sig == SIGSEGV ? "SIGSEGV" : "SIGBUS", CTX_PC);
#elif defined(ARCHITECTURE_riscv64)
{
std::shared_lock guard(sig_handler->code_block_infos_mutex);
if (const auto iter = sig_handler->FindCodeBlockInfo(CTX_SEPC); iter != sig_handler->code_block_infos.end()) {
FakeCall fc = iter->second.cb(CTX_SEPC);
CTX_SEPC = fc.call_sepc;
return;
}
}
fmt::print(stderr, "Unhandled {} at pc {:#018x}\n", sig == SIGSEGV ? "SIGSEGV" : "SIGBUS", CTX_SEPC);
#elif defined(ARCHITECTURE_loongarch64)
{
std::shared_lock guard(sig_handler->code_block_infos_mutex);
if (const auto iter = sig_handler->FindCodeBlockInfo(CTX_PC); iter != sig_handler->code_block_infos.end()) {
FakeCall fc = iter->second.cb(CTX_PC);
CTX_PC = fc.call_pc;
return;
}
}
fmt::print(stderr, "Unhandled {} at pc {:#018x}\n", sig == SIGSEGV ? "SIGSEGV" : "SIGBUS", CTX_PC);
#else
# error "Invalid architecture"
#endif
struct sigaction* retry_sa = sig == SIGSEGV ? &sig_handler->old_sa_segv : &sig_handler->old_sa_bus;
if (retry_sa->sa_flags & SA_SIGINFO) {
@@ -164,10 +190,9 @@ void SigHandler::SigAction(int sig, siginfo_t* info, void* raw_context) {
struct ExceptionHandler::Impl final {
Impl(u64 offset_, u64 size_)
: offset(offset_)
, size(size_)
{
SigHandler::RegisterHandler();
: offset(offset_)
, size(size_) {
RegisterHandler();
}
void SetCallback(std::function<FakeCall(u64)> cb) {
@@ -1,7 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <variant>
#include "dynarmic/backend/loongarch64/emit_loongarch64.h"
#include "dynarmic/backend/loongarch64/a32_jitstate.h"
@@ -96,8 +95,7 @@ EmittedBlockInfo EmitLoongArch64(lagoon_assembler_t& as, IR::Block block, const
// TODO: Emit Terminal
const auto term = block.GetTerminal();
const IR::Term::LeafTerminal* leaft_term = std::get_if<IR::Term::LeafTerminal>(&term);
const IR::Term::LinkBlock* link_block_term = std::get_if<IR::Term::LinkBlock>(leaft_term);
const IR::Term::LinkBlock* link_block_term = boost::get<IR::Term::LinkBlock>(&term);
ASSERT(link_block_term);
la_load_immediate64(&as, Xscratch0, link_block_term->next.Value());
la_st_w(&as, Xscratch0, Xstate, static_cast<int32_t>(offsetof(A32JitState, regs) + sizeof(u32) * 15));
+3 -3
View File
@@ -136,14 +136,14 @@
# endif
#elif defined(ARCHITECTURE_riscv64)
# if defined(__FreeBSD__)
# define CTX_PC (mctx.mc_gpregs.gp_sepc)
# define CTX_SEPC (mctx.mc_gpregs.gp_sepc)
# define CTX_SP (mctx.mc_gpregs.gp_sp)
# elif defined(__linux__)
# define CTX_PC (mctx.__gregs[REG_PC])
# define CTX_SEPC (mctx.__gregs[REG_PC])
# define CTX_SP (mctx.__gregs[REG_SP])
# elif defined(__OpenBSD__)
// https://github.com/openbsd/src/blob/master/sys/arch/riscv64/include/signal.h
# define CTX_PC (ucontext->sc_sepc)
# define CTX_SEPC (ucontext->sc_sepc)
# define CTX_SP (ucontext->sc_sp)
# else
# error "unknown platform"
@@ -522,6 +522,9 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent) {
PAIR(AnisotropyMode, X4, tr("4x")),
PAIR(AnisotropyMode, X8, tr("8x")),
PAIR(AnisotropyMode, X16, tr("16x")),
PAIR(AnisotropyMode, X32, tr("32x")),
PAIR(AnisotropyMode, X64, tr("64x")),
PAIR(AnisotropyMode, None, tr("None")),
}});
translations->insert(
{Settings::EnumMetadata<Settings::Language>::Index(),
+4 -4
View File
@@ -74,8 +74,8 @@ static constexpr char DEFAULT_DISCORD_IMAGE[] =
"https://git.eden-emu.dev/eden-emu/eden/raw/branch/master/dist/qt_themes/default/icons/256x256/"
"eden.png";
void DiscordImpl::UpdateGameStatus(std::string_view game_url, bool has_boxart) {
const std::string url = std::string{has_boxart ? game_url : DEFAULT_DISCORD_IMAGE};
void DiscordImpl::UpdateGameStatus(bool use_default) {
const std::string url = use_default ? std::string{DEFAULT_DISCORD_IMAGE} : game_url;
s64 start_time = std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
@@ -98,7 +98,7 @@ void DiscordImpl::Update() {
// Used to format Icon URL for yuzu website game compatibility page
std::string icon_name = GetGameString(game_title);
auto const game_url = fmt::format(
game_url = fmt::format(
"https://raw.githubusercontent.com/eden-emulator/boxart/refs/heads/master/img/{}.png",
icon_name);
@@ -117,7 +117,7 @@ void DiscordImpl::Update() {
};
auto res = client.send(request);
UpdateGameStatus(game_url, res && res->status == 200);
UpdateGameStatus(res && res->status == 200);
return;
}
+4 -2
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2018 Citra Emulator Project
@@ -26,9 +26,11 @@ public:
private:
std::string GetGameString(const std::string& title);
void UpdateGameStatus(std::string_view game_url, bool use_default);
void UpdateGameStatus(bool use_default);
std::string game_url{};
std::string game_title{};
Core::System& system;
};
+3 -2
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#include <filesystem>
#include <system_error>
#include <JlCompress.h>
#include "frontend_common/mod_manager.h"
#include "mod.h"
@@ -124,8 +125,8 @@ const QString ExtractMod(const QString& path) {
fs::remove_all(tmp, ec);
if (!fs::create_directories(tmp, ec)) {
QtCommon::Frontend::Critical(tr("Mod Extract Failed"),
tr("Failed to create temporary directory %1")
.arg(QString::fromStdString(tmp.string())));
tr("Failed to create temporary directory %1")
.arg(QString::fromStdString(tmp.string())));
return QString();
}
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
@@ -665,8 +665,6 @@ void EmitShuffleDown(EmitContext& ctx, IR::Inst& inst, ScalarU32 value, ScalarU3
const IR::Value& clamp, const IR::Value& segmentation_mask);
void EmitShuffleButterfly(EmitContext& ctx, IR::Inst& inst, ScalarU32 value, ScalarU32 index,
const IR::Value& clamp, const IR::Value& segmentation_mask);
void EmitQuadBroadcast(EmitContext& ctx, IR::Inst& inst, ScalarU32 value, ScalarU32 lane);
void EmitQuadSwap(EmitContext& ctx, IR::Inst& inst, ScalarU32 value, ScalarU32 direction);
void EmitFSwizzleAdd(EmitContext& ctx, IR::Inst& inst, ScalarF32 op_a, ScalarF32 op_b,
ScalarU32 swizzle);
void EmitDPdxFine(EmitContext& ctx, IR::Inst& inst, ScalarF32 op_a);
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 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
@@ -100,24 +97,6 @@ void EmitShuffleButterfly(EmitContext& ctx, IR::Inst& inst, ScalarU32 value, Sca
Shuffle(ctx, inst, value, index, clamp, segmentation_mask, "XOR");
}
void EmitQuadBroadcast(EmitContext& ctx, IR::Inst& inst, ScalarU32 value, ScalarU32 lane) {
const Register ret{ctx.reg_alloc.Define(inst)};
ctx.Add("AND.U RC.x,{}.threadid,~3;"
"AND.U RC.y,{},3;"
"OR.U RC.x,RC.x,RC.y;"
"SHFIDX.U {},{},RC.x,0x1C03;"
"MOV.U {}.x,{}.y;",
ctx.stage_name, lane, ret, value, ret, ret);
}
void EmitQuadSwap(EmitContext& ctx, IR::Inst& inst, ScalarU32 value, ScalarU32 direction) {
const Register ret{ctx.reg_alloc.Define(inst)};
ctx.Add("ADD.U RC.x,{},1;"
"SHFXOR.U {},{},RC.x,0x1C03;"
"MOV.U {}.x,{}.y;",
direction, ret, value, ret, ret);
}
void EmitFSwizzleAdd(EmitContext& ctx, IR::Inst& inst, ScalarF32 op_a, ScalarF32 op_b,
ScalarU32 swizzle) {
const auto ret{ctx.reg_alloc.Define(inst)};
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
@@ -743,10 +743,6 @@ void EmitShuffleDown(EmitContext& ctx, IR::Inst& inst, std::string_view value,
void EmitShuffleButterfly(EmitContext& ctx, IR::Inst& inst, std::string_view value,
std::string_view index, std::string_view clamp,
std::string_view segmentation_mask);
void EmitQuadBroadcast(EmitContext& ctx, IR::Inst& inst, std::string_view value,
std::string_view lane);
void EmitQuadSwap(EmitContext& ctx, IR::Inst& inst, std::string_view value,
std::string_view direction);
void EmitFSwizzleAdd(EmitContext& ctx, IR::Inst& inst, std::string_view op_a, std::string_view op_b,
std::string_view swizzle);
void EmitDPdxFine(EmitContext& ctx, IR::Inst& inst, std::string_view op_a);
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 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
@@ -203,18 +200,6 @@ void EmitShuffleButterfly(EmitContext& ctx, IR::Inst& inst, std::string_view val
ctx.AddU32("{}=shfl_in_bounds?shfl_result:{};", inst, value);
}
void EmitQuadBroadcast(EmitContext& ctx, IR::Inst& inst, std::string_view value,
std::string_view lane) {
const auto src_thread_id{fmt::format("(({}&~3)|({}& 3))", THREAD_ID, lane)};
ctx.AddU32("{}=readInvocationARB({},{});", inst, value, src_thread_id);
}
void EmitQuadSwap(EmitContext& ctx, IR::Inst& inst, std::string_view value,
std::string_view direction) {
const auto src_thread_id{fmt::format("({}^({}+1))", THREAD_ID, direction)};
ctx.AddU32("{}=readInvocationARB({},{});", inst, value, src_thread_id);
}
void EmitFSwizzleAdd(EmitContext& ctx, IR::Inst& inst, std::string_view op_a, std::string_view op_b,
std::string_view swizzle) {
const auto mask{fmt::format("({}>>((gl_SubGroupInvocationARB&3)<<1))&3", swizzle)};
@@ -322,11 +322,6 @@ void DefineEntryPoint(const IR::Program& program, EmitContext& ctx, Id main) {
if (ctx.runtime_info.force_early_z) {
ctx.AddExecutionMode(main, spv::ExecutionMode::EarlyFragmentTests);
}
if (ctx.profile.support_shader_quad_control && program.info.uses_quad_shuffles) {
ctx.AddExtension("SPV_KHR_quad_control");
ctx.AddCapability(spv::Capability::QuadControlKHR);
ctx.AddExecutionMode(main, spv::ExecutionMode::RequireFullQuadsKHR);
}
break;
default:
throw NotImplementedException("Stage {}", program.stage);
@@ -340,7 +335,7 @@ void SetupDenormControl(const Profile& profile, const IR::Program& program, Emit
if (info.uses_fp32_denorms_flush && info.uses_fp32_denorms_preserve) {
LOG_DEBUG(Shader_SPIRV, "Fp32 denorm flush and preserve on the same shader");
} else if (info.uses_fp32_denorms_flush) {
if (profile.support_fp32_denorm_flush && !profile.has_broken_fp32_denorm_flush) {
if (profile.support_fp32_denorm_flush) {
ctx.AddCapability(spv::Capability::DenormFlushToZero);
ctx.AddExecutionMode(main_func, spv::ExecutionMode::DenormFlushToZero, 32U);
} else {
@@ -448,12 +443,6 @@ void SetupCapabilities(const Profile& profile, const Info& info, EmitContext& ct
ctx.AddCapability(spv::Capability::GroupNonUniformVote);
}
}
if (info.uses_quad_shuffles) {
if (profile.support_quad_shuffles) {
ctx.AddCapability(spv::Capability::GroupNonUniformQuad);
}
ctx.AddCapability(spv::Capability::GroupNonUniformShuffle);
}
if (info.uses_int64_bit_atomics && profile.support_int64_atomics) {
ctx.AddCapability(spv::Capability::Int64Atomics);
}
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
@@ -622,8 +622,6 @@ Id EmitShuffleDown(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clam
Id segmentation_mask);
Id EmitShuffleButterfly(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp,
Id segmentation_mask);
Id EmitQuadBroadcast(EmitContext& ctx, Id value, Id lane);
Id EmitQuadSwap(EmitContext& ctx, Id value, Id direction);
Id EmitFSwizzleAdd(EmitContext& ctx, Id op_a, Id op_b, Id swizzle);
Id EmitDPdxFine(EmitContext& ctx, Id op_a);
Id EmitDPdyFine(EmitContext& ctx, Id op_a);
@@ -260,21 +260,6 @@ Id EmitShuffleButterfly(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id
return SelectValue(ctx, in_range, value, src_thread_id);
}
Id EmitQuadBroadcast(EmitContext& ctx, Id value, Id lane) {
if (ctx.profile.support_quad_shuffles) {
return ctx.OpGroupNonUniformQuadBroadcast(ctx.U32[1], SubgroupScope(ctx), value, lane);
}
const Id base{ctx.OpBitwiseAnd(ctx.U32[1], GetThreadId(ctx), ctx.Const(~3u))};
const Id local_lane{ctx.OpBitwiseAnd(ctx.U32[1], lane, ctx.Const(3u))};
const Id src_thread_id{ctx.OpBitwiseOr(ctx.U32[1], base, local_lane)};
return ctx.OpGroupNonUniformShuffle(ctx.U32[1], SubgroupScope(ctx), value, src_thread_id);
}
Id EmitQuadSwap(EmitContext& ctx, Id value, Id direction) {
const Id xor_mask{ctx.OpIAdd(ctx.U32[1], direction, ctx.Const(1u))};
return ctx.OpGroupNonUniformShuffleXor(ctx.U32[1], SubgroupScope(ctx), value, xor_mask);
}
Id EmitFSwizzleAdd(EmitContext& ctx, Id op_a, Id op_b, Id swizzle) {
const Id three{ctx.Const(3U)};
Id mask{GetThreadId(ctx)};
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
@@ -2100,14 +2100,6 @@ U32 IREmitter::ShuffleButterfly(const IR::U32& value, const IR::U32& index, cons
return Inst<U32>(Opcode::ShuffleButterfly, value, index, clamp, seg_mask);
}
U32 IREmitter::QuadBroadcast(const IR::U32& value, const IR::U32& lane) {
return Inst<U32>(Opcode::QuadBroadcast, value, lane);
}
U32 IREmitter::QuadSwap(const IR::U32& value, const IR::U32& direction) {
return Inst<U32>(Opcode::QuadSwap, value, direction);
}
F32 IREmitter::FSwizzleAdd(const F32& a, const F32& b, const U32& swizzle, FpControl control) {
return Inst<F32>(Opcode::FSwizzleAdd, Flags{control}, a, b, swizzle);
}
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
@@ -394,8 +394,6 @@ public:
const IR::U32& seg_mask);
[[nodiscard]] U32 ShuffleButterfly(const IR::U32& value, const IR::U32& index,
const IR::U32& clamp, const IR::U32& seg_mask);
[[nodiscard]] U32 QuadBroadcast(const IR::U32& value, const IR::U32& lane);
[[nodiscard]] U32 QuadSwap(const IR::U32& value, const IR::U32& direction);
[[nodiscard]] F32 FSwizzleAdd(const F32& a, const F32& b, const U32& swizzle,
FpControl control = {});
@@ -10,7 +10,7 @@ namespace Shader::IR {
namespace Detail {
OpcodeMeta META_TABLE[534] = {
OpcodeMeta META_TABLE[532] = {
#define OPCODE(name_token, type_token, ...) \
{ \
.name{#name_token}, \
@@ -21,7 +21,7 @@ OpcodeMeta META_TABLE[534] = {
#undef OPCODE
};
u8 NUM_ARGS[534] = {
u8 NUM_ARGS[532] = {
#define OPCODE(name_token, type_token, ...) u8(CalculateNumArgsOf(Opcode::name_token)),
#include "opcodes.inc"
#undef OPCODE
+2 -2
View File
@@ -57,12 +57,12 @@ static constexpr Type F64x2{Type::F64x2};
static constexpr Type F64x3{Type::F64x3};
static constexpr Type F64x4{Type::F64x4};
extern OpcodeMeta META_TABLE[534];
extern OpcodeMeta META_TABLE[532];
constexpr size_t CalculateNumArgsOf(Opcode op) noexcept {
const auto& arg_types = META_TABLE[size_t(op)].arg_types;
return size_t(std::distance(arg_types.begin(), std::ranges::find(arg_types, Type::Void)));
}
extern u8 NUM_ARGS[534];
extern u8 NUM_ARGS[532];
} // namespace Detail
/// Get return type of an opcode
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 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
@@ -582,8 +579,6 @@ OPCODE(ShuffleIndex, U32, U32,
OPCODE(ShuffleUp, U32, U32, U32, U32, U32, )
OPCODE(ShuffleDown, U32, U32, U32, U32, U32, )
OPCODE(ShuffleButterfly, U32, U32, U32, U32, U32, )
OPCODE(QuadBroadcast, U32, U32, U32, )
OPCODE(QuadSwap, U32, U32, U32, )
OPCODE(FSwizzleAdd, F32, F32, F32, U32, )
OPCODE(DPdxFine, F32, F32, )
OPCODE(DPdyFine, F32, F32, )
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
@@ -36,10 +36,7 @@ enum class ShuffleMode : u64 {
}
}
constexpr u32 QUAD_MASK = (28u << 8) | 3u;
void Shuffle(TranslatorVisitor& v, u64 insn, const IR::U32& index, const IR::U32& mask,
bool index_is_imm, u32 index_imm, bool mask_is_imm, u32 mask_imm) {
void Shuffle(TranslatorVisitor& v, u64 insn, const IR::U32& index, const IR::U32& mask) {
union {
u64 insn;
BitField<0, 8, IR::Reg> dest_reg;
@@ -48,21 +45,6 @@ void Shuffle(TranslatorVisitor& v, u64 insn, const IR::U32& index, const IR::U32
BitField<48, 3, IR::Pred> pred;
} const shfl{insn};
const bool is_quad_candidate{mask_is_imm && mask_imm == QUAD_MASK && index_is_imm &&
v.env.ShaderStage() == Stage::Fragment};
if (is_quad_candidate) {
if (shfl.mode == ShuffleMode::IDX && index_imm <= 3) {
v.X(shfl.dest_reg, v.ir.QuadBroadcast(v.X(shfl.src_reg), v.ir.Imm32(index_imm)));
v.ir.SetPred(shfl.pred, v.ir.Imm1(true));
return;
}
if (shfl.mode == ShuffleMode::BFLY && index_imm >= 1 && index_imm <= 3) {
v.X(shfl.dest_reg, v.ir.QuadSwap(v.X(shfl.src_reg), v.ir.Imm32(index_imm - 1)));
v.ir.SetPred(shfl.pred, v.ir.Imm1(true));
return;
}
}
const IR::U32 result{ShuffleOperation(v.ir, v.X(shfl.src_reg), index, mask, shfl.mode)};
v.ir.SetPred(shfl.pred, v.ir.GetInBoundsFromOp(result));
v.X(shfl.dest_reg, result);
@@ -77,14 +59,11 @@ void TranslatorVisitor::SHFL(u64 insn) {
BitField<29, 1, u64> src_b_flag;
BitField<34, 13, u64> src_b_imm;
} const flags{insn};
const bool index_is_imm{flags.src_a_flag != 0};
const bool mask_is_imm{flags.src_b_flag != 0};
const IR::U32 src_a{index_is_imm ? ir.Imm32(static_cast<u32>(flags.src_a_imm))
: GetReg20(insn)};
const IR::U32 src_b{mask_is_imm ? ir.Imm32(static_cast<u32>(flags.src_b_imm))
: GetReg39(insn)};
Shuffle(*this, insn, src_a, src_b, index_is_imm, static_cast<u32>(flags.src_a_imm),
mask_is_imm, static_cast<u32>(flags.src_b_imm));
const IR::U32 src_a{flags.src_a_flag != 0 ? ir.Imm32(static_cast<u32>(flags.src_a_imm))
: GetReg20(insn)};
const IR::U32 src_b{flags.src_b_flag != 0 ? ir.Imm32(static_cast<u32>(flags.src_b_imm))
: GetReg39(insn)};
Shuffle(*this, insn, src_a, src_b);
}
} // namespace Shader::Maxwell
@@ -498,10 +498,6 @@ void VisitUsages(Info& info, IR::Inst& inst) {
case IR::Opcode::ShuffleButterfly:
info.uses_subgroup_shuffles = true;
break;
case IR::Opcode::QuadBroadcast:
case IR::Opcode::QuadSwap:
info.uses_quad_shuffles = true;
break;
case IR::Opcode::GetCbufU8:
case IR::Opcode::GetCbufS8:
case IR::Opcode::GetCbufU16:
-4
View File
@@ -37,8 +37,6 @@ struct Profile {
bool support_explicit_workgroup_layout{};
bool support_workgroup_layout_8bit_access{};
bool support_workgroup_layout_16bit_access{};
bool support_shader_quad_control{};
bool support_quad_shuffles{};
bool support_vote{};
u32 supported_subgroup_stages{0x7F};
bool support_viewport_index_layer_non_geometry{};
@@ -88,8 +86,6 @@ struct Profile {
bool has_broken_signed_operations{};
/// Float controls break when fp16 is enabled
bool has_broken_fp16_float_controls{};
/// Declaring fp32 denorm flush to zero miscompiles on some drivers
bool has_broken_fp32_denorm_flush{};
/// Dynamic vec4 indexing is broken on some OpenGL drivers
bool has_gl_component_indexing_bug{};
/// The precise type qualifier is broken in the fragment stage of some drivers
-1
View File
@@ -252,7 +252,6 @@ struct Info {
bool uses_is_helper_invocation{};
bool uses_subgroup_invocation_id{};
bool uses_subgroup_shuffles{};
bool uses_quad_shuffles{};
std::array<bool, 30> uses_patches{};
std::array<Interpolation, 32> interpolation{};
@@ -18,7 +18,6 @@ set(SHADER_FILES
${CMAKE_CURRENT_SOURCE_DIR}/blit_color_float.frag
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_2d.comp
${CMAKE_CURRENT_SOURCE_DIR}/blit_color_msaa.frag
${CMAKE_CURRENT_SOURCE_DIR}/blit_depth.frag
${CMAKE_CURRENT_SOURCE_DIR}/blit_depth_msaa.frag
${CMAKE_CURRENT_SOURCE_DIR}/blit_depth_stencil_msaa.frag
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_3d.comp
@@ -31,12 +30,8 @@ set(SHADER_FILES
${CMAKE_CURRENT_SOURCE_DIR}/convert_float_to_depth.frag
${CMAKE_CURRENT_SOURCE_DIR}/convert_msaa_to_non_msaa.comp
${CMAKE_CURRENT_SOURCE_DIR}/convert_msaa_to_non_msaa.frag
${CMAKE_CURRENT_SOURCE_DIR}/convert_msaa_to_non_msaa_depth.frag
${CMAKE_CURRENT_SOURCE_DIR}/convert_msaa_to_non_msaa_depth_stencil.frag
${CMAKE_CURRENT_SOURCE_DIR}/convert_non_msaa_to_msaa.comp
${CMAKE_CURRENT_SOURCE_DIR}/convert_non_msaa_to_msaa.frag
${CMAKE_CURRENT_SOURCE_DIR}/convert_non_msaa_to_msaa_depth.frag
${CMAKE_CURRENT_SOURCE_DIR}/convert_non_msaa_to_msaa_depth_stencil.frag
${CMAKE_CURRENT_SOURCE_DIR}/convert_s8d24_to_abgr8.frag
${CMAKE_CURRENT_SOURCE_DIR}/full_screen_triangle.vert
${CMAKE_CURRENT_SOURCE_DIR}/fxaa.frag
@@ -168,44 +163,6 @@ foreach(SOURCE_FILE IN ITEMS ${SHADER_FILES})
endif()
endforeach()
# Integer variants of the MSAA conversion shaders. They only differ from the float
# source in the sampler and output types, so they are generated from it via defines.
set(SHADER_TYPE_VARIANTS
"convert_msaa_to_non_msaa.frag|sint|isampler2DMS|ivec4"
"convert_msaa_to_non_msaa.frag|uint|usampler2DMS|uvec4"
"convert_non_msaa_to_msaa.frag|sint|isampler2D|ivec4"
"convert_non_msaa_to_msaa.frag|uint|usampler2D|uvec4"
)
foreach(VARIANT IN ITEMS ${SHADER_TYPE_VARIANTS})
string(REPLACE "|" ";" VARIANT_PARTS ${VARIANT})
list(GET VARIANT_PARTS 0 VARIANT_FILENAME)
list(GET VARIANT_PARTS 1 VARIANT_SUFFIX)
list(GET VARIANT_PARTS 2 VARIANT_SAMPLER)
list(GET VARIANT_PARTS 3 VARIANT_TEXEL)
set(VARIANT_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/${VARIANT_FILENAME})
get_filename_component(VARIANT_STEM ${VARIANT_FILENAME} NAME_WE)
get_filename_component(VARIANT_EXT ${VARIANT_FILENAME} EXT)
string(REPLACE "." "" VARIANT_EXT ${VARIANT_EXT})
set(VARIANT_NAME ${VARIANT_STEM}_${VARIANT_SUFFIX}_${VARIANT_EXT})
string(TOUPPER ${VARIANT_NAME}_SPV VARIANT_VARIABLE_NAME)
set(VARIANT_HEADER_FILE ${SHADER_DIR}/${VARIANT_NAME}_spv.h)
add_custom_command(
OUTPUT
${VARIANT_HEADER_FILE}
COMMAND
${GLSLANGVALIDATOR} -V ${QUIET_FLAG} -I"${FIDELITYFX_INCLUDE_DIR}" ${GLSL_FLAGS}
-DSAMPLER_TYPE=${VARIANT_SAMPLER} -DTEXEL_TYPE=${VARIANT_TEXEL}
--variable-name ${VARIANT_VARIABLE_NAME} -o ${VARIANT_HEADER_FILE} ${VARIANT_SOURCE}
--target-env ${SPIR_V_VERSION}
MAIN_DEPENDENCY
${VARIANT_SOURCE}
)
set(SHADER_HEADERS ${SHADER_HEADERS} ${VARIANT_HEADER_FILE})
endforeach()
foreach(FILEPATH IN ITEMS ${FIDELITYFX_FILES})
get_filename_component(FILENAME ${FILEPATH} NAME)
string(REPLACE "." "_" HEADER_NAME ${FILENAME})
@@ -1,12 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#version 450 core
layout(binding = 0) uniform sampler2D depth_tex;
layout(location = 0) in vec2 texcoord;
void main() {
gl_FragDepth = textureLod(depth_tex, texcoord, 0).r;
}
@@ -8,5 +8,5 @@ layout(binding = 0) uniform sampler2DMS depth_tex;
layout(location = 0) in vec2 texcoord;
void main() {
gl_FragDepth = texelFetch(depth_tex, ivec2(texcoord), gl_SampleID).r;
gl_FragDepth = texelFetch(depth_tex, ivec2(texcoord), 0).r;
}
@@ -10,6 +10,6 @@ layout(binding = 1) uniform usampler2DMS stencil_tex;
layout(location = 0) in vec2 texcoord;
void main() {
gl_FragDepth = texelFetch(depth_tex, ivec2(texcoord), gl_SampleID).r;
gl_FragStencilRefARB = int(texelFetch(stencil_tex, ivec2(texcoord), gl_SampleID).r);
gl_FragDepth = texelFetch(depth_tex, ivec2(texcoord), 0).r;
gl_FragStencilRefARB = int(texelFetch(stencil_tex, ivec2(texcoord), 0).r);
}
@@ -3,14 +3,7 @@
#version 450 core
#ifndef SAMPLER_TYPE
#define SAMPLER_TYPE sampler2DMS
#endif
#ifndef TEXEL_TYPE
#define TEXEL_TYPE vec4
#endif
layout(binding = 0) uniform SAMPLER_TYPE msaa_in;
layout(binding = 0) uniform sampler2DMS msaa_in;
layout(push_constant) uniform PushConstants {
ivec2 dst_offset;
@@ -18,7 +11,7 @@ layout(push_constant) uniform PushConstants {
ivec2 scale;
};
layout(location = 0) out TEXEL_TYPE frag_color;
layout(location = 0) out vec4 frag_color;
void main() {
const ivec2 coord = ivec2(gl_FragCoord.xy) - dst_offset + src_offset;
@@ -1,20 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#version 450 core
layout(binding = 0) uniform sampler2DMS msaa_in;
layout(push_constant) uniform PushConstants {
ivec2 dst_offset;
ivec2 src_offset;
ivec2 scale;
};
void main() {
const ivec2 coord = ivec2(gl_FragCoord.xy) - dst_offset + src_offset;
const ivec2 msaa_coord = coord / scale;
const ivec2 sample_offset = coord % scale;
const int sample_id = sample_offset.x + scale.x * sample_offset.y;
gl_FragDepth = texelFetch(msaa_in, msaa_coord, sample_id).r;
}
@@ -1,23 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#version 450 core
#extension GL_ARB_shader_stencil_export : require
layout(binding = 0) uniform sampler2DMS depth_tex;
layout(binding = 1) uniform usampler2DMS stencil_tex;
layout(push_constant) uniform PushConstants {
ivec2 dst_offset;
ivec2 src_offset;
ivec2 scale;
};
void main() {
const ivec2 coord = ivec2(gl_FragCoord.xy) - dst_offset + src_offset;
const ivec2 msaa_coord = coord / scale;
const ivec2 sample_offset = coord % scale;
const int sample_id = sample_offset.x + scale.x * sample_offset.y;
gl_FragDepth = texelFetch(depth_tex, msaa_coord, sample_id).r;
gl_FragStencilRefARB = int(texelFetch(stencil_tex, msaa_coord, sample_id).r);
}
@@ -3,14 +3,7 @@
#version 450 core
#ifndef SAMPLER_TYPE
#define SAMPLER_TYPE sampler2D
#endif
#ifndef TEXEL_TYPE
#define TEXEL_TYPE vec4
#endif
layout(binding = 0) uniform SAMPLER_TYPE img_in;
layout(binding = 0) uniform sampler2D img_in;
layout(push_constant) uniform PushConstants {
ivec2 dst_offset;
@@ -18,7 +11,7 @@ layout(push_constant) uniform PushConstants {
ivec2 scale;
};
layout(location = 0) out TEXEL_TYPE frag_color;
layout(location = 0) out vec4 frag_color;
void main() {
const ivec2 msaa_coord = ivec2(gl_FragCoord.xy) - dst_offset;
@@ -1,19 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#version 450 core
layout(binding = 0) uniform sampler2D img_in;
layout(push_constant) uniform PushConstants {
ivec2 dst_offset;
ivec2 src_offset;
ivec2 scale;
};
void main() {
const ivec2 msaa_coord = ivec2(gl_FragCoord.xy) - dst_offset;
const ivec2 sample_offset = ivec2(gl_SampleID % scale.x, gl_SampleID / scale.x);
const ivec2 coord = msaa_coord * scale + sample_offset + src_offset;
gl_FragDepth = texelFetch(img_in, coord, 0).r;
}
@@ -1,22 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#version 450 core
#extension GL_ARB_shader_stencil_export : require
layout(binding = 0) uniform sampler2D depth_tex;
layout(binding = 1) uniform usampler2D stencil_tex;
layout(push_constant) uniform PushConstants {
ivec2 dst_offset;
ivec2 src_offset;
ivec2 scale;
};
void main() {
const ivec2 msaa_coord = ivec2(gl_FragCoord.xy) - dst_offset;
const ivec2 sample_offset = ivec2(gl_SampleID % scale.x, gl_SampleID / scale.x);
const ivec2 coord = msaa_coord * scale + sample_offset + src_offset;
gl_FragDepth = texelFetch(depth_tex, coord, 0).r;
gl_FragStencilRefARB = int(texelFetch(stencil_tex, coord, 0).r);
}
@@ -7,7 +7,6 @@ layout(push_constant) uniform constants {
vec2 scale;
vec2 size;
vec2 resize_factor;
vec2 crop_offset;
float edge_sharpness;
};
layout(location = 0) out highp vec2 texcoord;
@@ -16,5 +15,5 @@ void main() {
float x = float((gl_VertexIndex & 1) << 2);
float y = float((gl_VertexIndex & 2) << 1);
gl_Position = vec4(x - 1.0f, y - 1.0f, 0.0, 1.0f) * vec4(sign(resize_factor), 1.f, 1.f);
texcoord = crop_offset + vec2(x, y) * abs(resize_factor) * 0.5;
texcoord = vec2(x, y) * abs(resize_factor) * 0.5;
}
@@ -14,7 +14,6 @@ layout(push_constant) uniform constants {
vec2 scale;
vec2 size;
vec2 resize_factor;
vec2 crop_offset;
float edge_sharpness;
};
layout(set = 0, binding = 0) uniform sampler2D sampler0;
@@ -13,7 +13,6 @@
layout( push_constant ) uniform constants {
vec4 ViewportInfo[1];
vec2 ResizeFactor;
vec2 CropOffset;
float EdgeSharpness;
};
layout(set = 0, binding = 0) uniform sampler2D ps0;
@@ -231,7 +231,6 @@ ShaderCache::ShaderCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
.has_broken_unsigned_image_offsets = true,
.has_broken_signed_operations = true,
.has_broken_fp16_float_controls = false,
.has_broken_fp32_denorm_flush = false,
.has_gl_component_indexing_bug = device.HasComponentIndexingBug(),
.has_gl_precise_bug = device.HasPreciseBug(),
.has_gl_cbuf_ftou_bug = device.HasCbufFtouBug(),
@@ -75,8 +75,6 @@ public:
void Finish();
void FlushDeferredClear() {}
StagingBufferMap UploadStagingBuffer(size_t size, bool deferred = false);
StagingBufferMap DownloadStagingBuffer(size_t size, bool deferred = false);
@@ -372,7 +370,6 @@ struct TextureCacheParams {
static constexpr bool HAS_EMULATED_COPIES = true;
static constexpr bool HAS_DEVICE_MEMORY_INFO = true;
static constexpr bool IMPLEMENTS_ASYNC_DOWNLOADS = true;
static constexpr bool HAS_MSAA_DOWNLOADS = false;
using Runtime = OpenGL::TextureCacheRuntime;
using Image = OpenGL::Image;
+235 -553
View File
@@ -12,7 +12,6 @@
#include "common/settings.h"
#include "video_core/host_shaders/blit_color_float_frag_spv.h"
#include "video_core/host_shaders/blit_color_msaa_frag_spv.h"
#include "video_core/host_shaders/blit_depth_frag_spv.h"
#include "video_core/host_shaders/blit_depth_msaa_frag_spv.h"
#include "video_core/host_shaders/blit_depth_stencil_msaa_frag_spv.h"
#include "video_core/host_shaders/convert_abgr8_to_d24s8_frag_spv.h"
@@ -22,15 +21,7 @@
#include "video_core/host_shaders/convert_depth_to_float_frag_spv.h"
#include "video_core/host_shaders/convert_float_to_depth_frag_spv.h"
#include "video_core/host_shaders/convert_msaa_to_non_msaa_frag_spv.h"
#include "video_core/host_shaders/convert_msaa_to_non_msaa_depth_frag_spv.h"
#include "video_core/host_shaders/convert_msaa_to_non_msaa_depth_stencil_frag_spv.h"
#include "video_core/host_shaders/convert_msaa_to_non_msaa_sint_frag_spv.h"
#include "video_core/host_shaders/convert_msaa_to_non_msaa_uint_frag_spv.h"
#include "video_core/host_shaders/convert_non_msaa_to_msaa_frag_spv.h"
#include "video_core/host_shaders/convert_non_msaa_to_msaa_sint_frag_spv.h"
#include "video_core/host_shaders/convert_non_msaa_to_msaa_uint_frag_spv.h"
#include "video_core/host_shaders/convert_non_msaa_to_msaa_depth_frag_spv.h"
#include "video_core/host_shaders/convert_non_msaa_to_msaa_depth_stencil_frag_spv.h"
#include "video_core/host_shaders/convert_s8d24_to_abgr8_frag_spv.h"
#include "video_core/host_shaders/full_screen_triangle_vert_spv.h"
#include "video_core/host_shaders/vulkan_blit_depth_stencil_frag_spv.h"
@@ -527,19 +518,8 @@ void RecordShaderReadBarrier(Scheduler& scheduler, const ImageView& image_view)
}
}
[[nodiscard]] MSAACopyFormatClass FormatClass(VideoCore::Surface::PixelFormat format) {
if (!VideoCore::Surface::IsPixelFormatInteger(format)) {
return MSAACopyFormatClass::Float;
}
if (VideoCore::Surface::IsPixelFormatSignedInteger(format)) {
return MSAACopyFormatClass::SignedInteger;
}
return MSAACopyFormatClass::UnsignedInteger;
}
[[nodiscard]] vk::ImageView MakeMSAACopyView(const vk::Device& device, VkImage image,
VkFormat format, u32 base_level, u32 base_layer,
VkImageAspectFlags aspect_mask) {
VkFormat format, u32 base_level) {
return device.CreateImageView(VkImageViewCreateInfo{
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.pNext = nullptr,
@@ -554,10 +534,10 @@ void RecordShaderReadBarrier(Scheduler& scheduler, const ImageView& image_view)
.a = VK_COMPONENT_SWIZZLE_IDENTITY,
},
.subresourceRange{
.aspectMask = aspect_mask,
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = base_level,
.levelCount = 1,
.baseArrayLayer = base_layer,
.baseArrayLayer = 0,
.layerCount = 1,
},
});
@@ -606,17 +586,12 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_,
msaa_copy_pipeline_layout(device.GetLogical().CreatePipelineLayout(PipelineLayoutCreateInfo(
one_texture_set_layout.address(),
PUSH_CONSTANT_RANGE<VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(MSAACopyPushConstants)>))),
msaa_copy_depth_stencil_pipeline_layout(
device.GetLogical().CreatePipelineLayout(PipelineLayoutCreateInfo(
two_textures_set_layout.address(),
PUSH_CONSTANT_RANGE<VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(MSAACopyPushConstants)>))),
full_screen_vert(BuildShader(device, FULL_SCREEN_TRIANGLE_VERT_SPV)),
blit_color_to_color_frag(BuildShader(device, BLIT_COLOR_FLOAT_FRAG_SPV)),
blit_color_msaa_frag(BuildShader(device, BLIT_COLOR_MSAA_FRAG_SPV)),
blit_depth_stencil_frag(device.IsExtShaderStencilExportSupported()
? BuildShader(device, VULKAN_BLIT_DEPTH_STENCIL_FRAG_SPV)
: vk::ShaderModule{}),
blit_depth_frag(BuildShader(device, BLIT_DEPTH_FRAG_SPV)),
blit_depth_msaa_frag(BuildShader(device, BLIT_DEPTH_MSAA_FRAG_SPV)),
blit_depth_stencil_msaa_frag(device.IsExtShaderStencilExportSupported()
? BuildShader(device, BLIT_DEPTH_STENCIL_MSAA_FRAG_SPV)
@@ -634,25 +609,7 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_,
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_msaa_to_non_msaa_frag(BuildShader(device, CONVERT_MSAA_TO_NON_MSAA_FRAG_SPV)),
convert_msaa_to_non_msaa_sint_frag(
BuildShader(device, CONVERT_MSAA_TO_NON_MSAA_SINT_FRAG_SPV)),
convert_msaa_to_non_msaa_uint_frag(
BuildShader(device, CONVERT_MSAA_TO_NON_MSAA_UINT_FRAG_SPV)),
convert_msaa_to_non_msaa_depth_frag(
BuildShader(device, CONVERT_MSAA_TO_NON_MSAA_DEPTH_FRAG_SPV)),
convert_msaa_to_non_msaa_depth_stencil_frag(
BuildShader(device, CONVERT_MSAA_TO_NON_MSAA_DEPTH_STENCIL_FRAG_SPV)),
convert_non_msaa_to_msaa_frag(BuildShader(device, CONVERT_NON_MSAA_TO_MSAA_FRAG_SPV)),
convert_non_msaa_to_msaa_sint_frag(
BuildShader(device, CONVERT_NON_MSAA_TO_MSAA_SINT_FRAG_SPV)),
convert_non_msaa_to_msaa_uint_frag(
BuildShader(device, CONVERT_NON_MSAA_TO_MSAA_UINT_FRAG_SPV)),
convert_non_msaa_to_msaa_depth_frag(
BuildShader(device, CONVERT_NON_MSAA_TO_MSAA_DEPTH_FRAG_SPV)),
convert_non_msaa_to_msaa_depth_stencil_frag(
device.IsExtShaderStencilExportSupported()
? BuildShader(device, CONVERT_NON_MSAA_TO_MSAA_DEPTH_STENCIL_FRAG_SPV)
: vk::ShaderModule{}),
linear_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_LINEAR>)),
nearest_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_NEAREST>)) {}
@@ -667,13 +624,24 @@ void BlitImageHelper::BlitColor(const Framebuffer* dst_framebuffer, const ImageV
.renderpass = dst_framebuffer->RenderPass(),
.operation = operation,
};
VkSampler sampler = *nearest_sampler;
if (is_linear) {
sampler = *linear_sampler;
}
BlitImpl(dst_framebuffer, src_image_view, dst_region, src_region,
FindOrEmplaceColorPipeline(key), sampler,
src_image_view.Handle(Shader::TextureType::Color2D), VK_NULL_HANDLE, false);
const VkPipelineLayout layout = *one_texture_pipeline_layout;
const VkSampler sampler = is_linear ? *linear_sampler : *nearest_sampler;
const VkPipeline pipeline = FindOrEmplaceColorPipeline(key);
const VkImageView src_view = src_image_view.Handle(Shader::TextureType::Color2D);
RecordShaderReadBarrier(scheduler, src_image_view);
scheduler.RequestRenderpass(dst_framebuffer);
scheduler.Record([this, dst_region, src_region, pipeline, layout, sampler,
src_view](vk::CommandBuffer cmdbuf) {
const VkDescriptorSet descriptor_set = one_texture_descriptor_allocator.Commit();
UpdateOneTextureDescriptorSet(device, descriptor_set, sampler, src_view);
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set,
nullptr);
BindBlitState(cmdbuf, layout, dst_region, src_region);
cmdbuf.Draw(3, 1, 0, 0);
});
scheduler.InvalidateState();
}
void BlitImageHelper::BlitColor(const Framebuffer* dst_framebuffer, VkImageView src_image_view,
@@ -702,29 +670,24 @@ void BlitImageHelper::BlitColor(const Framebuffer* dst_framebuffer, VkImageView
});
}
void BlitImageHelper::BlitImpl(const Framebuffer* dst_framebuffer,
const ImageView& src_image_view, const Region2D& dst_region,
const Region2D& src_region, VkPipeline pipeline, VkSampler sampler,
VkImageView src_view, VkImageView src_stencil_view,
bool blit_stencil) {
VkPipelineLayout layout = *one_texture_pipeline_layout;
if (blit_stencil) {
layout = *two_textures_pipeline_layout;
}
void BlitImageHelper::BlitColorMSAA(const Framebuffer* dst_framebuffer,
const ImageView& src_image_view, const Region2D& dst_region,
const Region2D& src_region) {
const BlitMSAAPipelineKey key{
.renderpass = dst_framebuffer->RenderPass(),
.samples = dst_framebuffer->Samples(),
};
const VkPipelineLayout layout = *one_texture_pipeline_layout;
const VkSampler sampler = *nearest_sampler;
const VkPipeline pipeline = FindOrEmplaceBlitColorMSAAPipeline(key);
const VkImageView src_view = src_image_view.Handle(Shader::TextureType::Color2D);
RecordShaderReadBarrier(scheduler, src_image_view);
scheduler.RequestRenderpass(dst_framebuffer);
scheduler.Record([this, dst_region, src_region, pipeline, layout, sampler, src_view,
src_stencil_view, blit_stencil](vk::CommandBuffer cmdbuf) {
VkDescriptorSet descriptor_set = VK_NULL_HANDLE;
if (blit_stencil) {
descriptor_set = two_textures_descriptor_allocator.Commit();
UpdateTwoTexturesDescriptorSet(device, descriptor_set, sampler, src_view,
src_stencil_view);
} else {
descriptor_set = one_texture_descriptor_allocator.Commit();
UpdateOneTextureDescriptorSet(device, descriptor_set, sampler, src_view);
}
scheduler.Record([this, dst_region, src_region, pipeline, layout, sampler,
src_view](vk::CommandBuffer cmdbuf) {
const VkDescriptorSet descriptor_set = one_texture_descriptor_allocator.Commit();
UpdateOneTextureDescriptorSet(device, descriptor_set, sampler, src_view);
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set,
nullptr);
@@ -734,56 +697,41 @@ void BlitImageHelper::BlitImpl(const Framebuffer* dst_framebuffer,
scheduler.InvalidateState();
}
void BlitImageHelper::BlitColorMSAA(const Framebuffer* dst_framebuffer,
const ImageView& src_image_view, const Region2D& dst_region,
const Region2D& src_region) {
const BlitMSAAPipelineKey key{
.renderpass = dst_framebuffer->RenderPass(),
.samples = dst_framebuffer->Samples(),
};
BlitImpl(dst_framebuffer, src_image_view, dst_region, src_region,
FindOrEmplaceBlitColorMSAAPipeline(key), *nearest_sampler,
src_image_view.Handle(Shader::TextureType::Color2D), VK_NULL_HANDLE, false);
}
void BlitImageHelper::BlitDepthStencilMSAA(const Framebuffer* dst_framebuffer,
ImageView& src_image_view, const Region2D& dst_region,
const Region2D& src_region) {
const bool blit_stencil =
dst_framebuffer->HasAspectStencilBit() && device.IsExtShaderStencilExportSupported();
const BlitMSAAPipelineKey key{
.renderpass = dst_framebuffer->RenderPass(),
.samples = dst_framebuffer->Samples(),
};
VkImageView src_stencil_view = VK_NULL_HANDLE;
if (blit_stencil) {
src_stencil_view = src_image_view.StencilView();
}
BlitImpl(dst_framebuffer, src_image_view, dst_region, src_region,
FindOrEmplaceBlitDepthStencilMSAAPipeline(key, blit_stencil), *nearest_sampler,
src_image_view.DepthView(), src_stencil_view, blit_stencil);
}
void BlitImageHelper::BlitDepth(const Framebuffer* dst_framebuffer, ImageView& src_image_view,
const Region2D& dst_region, const Region2D& src_region) {
BlitImpl(dst_framebuffer, src_image_view, dst_region, src_region,
FindOrEmplaceBlitDepthPipeline(dst_framebuffer->RenderPass()), *nearest_sampler,
src_image_view.DepthView(), VK_NULL_HANDLE, false);
}
void BlitImageHelper::ResolveDepthStencil(const Framebuffer* dst_framebuffer,
ImageView& src_image_view, const Region2D& dst_region,
const Region2D& src_region) {
const bool resolve_stencil =
dst_framebuffer->HasAspectStencilBit() && device.IsExtShaderStencilExportSupported();
VkImageView src_stencil_view = VK_NULL_HANDLE;
if (resolve_stencil) {
src_stencil_view = src_image_view.StencilView();
}
BlitImpl(dst_framebuffer, src_image_view, dst_region, src_region,
FindOrEmplaceResolveDepthStencilPipeline(dst_framebuffer->RenderPass(),
resolve_stencil),
*nearest_sampler, src_image_view.DepthView(), src_stencil_view, resolve_stencil);
const VkPipeline pipeline =
FindOrEmplaceResolveDepthStencilPipeline(dst_framebuffer->RenderPass(), resolve_stencil);
const VkPipelineLayout layout =
resolve_stencil ? *two_textures_pipeline_layout : *one_texture_pipeline_layout;
const VkSampler sampler = *nearest_sampler;
const VkImageView src_depth_view = src_image_view.DepthView();
const VkImageView src_stencil_view =
resolve_stencil ? src_image_view.StencilView() : VK_NULL_HANDLE;
RecordShaderReadBarrier(scheduler, src_image_view);
scheduler.RequestRenderpass(dst_framebuffer);
scheduler.Record([this, dst_region, src_region, pipeline, layout, sampler, src_depth_view,
src_stencil_view, resolve_stencil](vk::CommandBuffer cmdbuf) {
if (resolve_stencil) {
const VkDescriptorSet descriptor_set = two_textures_descriptor_allocator.Commit();
UpdateTwoTexturesDescriptorSet(device, descriptor_set, sampler, src_depth_view,
src_stencil_view);
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set,
nullptr);
} else {
const VkDescriptorSet descriptor_set = one_texture_descriptor_allocator.Commit();
UpdateOneTextureDescriptorSet(device, descriptor_set, sampler, src_depth_view);
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set,
nullptr);
}
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
BindBlitState(cmdbuf, layout, dst_region, src_region);
cmdbuf.Draw(3, 1, 0, 0);
});
scheduler.InvalidateState();
}
void BlitImageHelper::BlitDepthStencil(const Framebuffer* dst_framebuffer,
@@ -791,23 +739,35 @@ void BlitImageHelper::BlitDepthStencil(const Framebuffer* dst_framebuffer,
const Region2D& dst_region, const Region2D& src_region,
Tegra::Engines::Fermi2D::Filter filter,
Tegra::Engines::Fermi2D::Operation operation) {
if (!device.IsExtShaderStencilExportSupported()) {
return;
}
ASSERT(filter == Tegra::Engines::Fermi2D::Filter::Point);
ASSERT(operation == Tegra::Engines::Fermi2D::Operation::SrcCopy);
const bool blit_stencil = device.IsExtShaderStencilExportSupported();
const BlitImagePipelineKey key{
.renderpass = dst_framebuffer->RenderPass(),
.operation = operation,
};
VkPipeline pipeline{};
VkImageView src_stencil_view = VK_NULL_HANDLE;
if (blit_stencil) {
pipeline = FindOrEmplaceDepthStencilPipeline(key);
src_stencil_view = src_image_view.StencilView();
} else {
pipeline = FindOrEmplaceBlitDepthPipeline(key.renderpass);
}
BlitImpl(dst_framebuffer, src_image_view, dst_region, src_region, pipeline, *nearest_sampler,
src_image_view.DepthView(), src_stencil_view, blit_stencil);
const VkPipelineLayout layout = *two_textures_pipeline_layout;
const VkSampler sampler = *nearest_sampler;
const VkPipeline pipeline = FindOrEmplaceDepthStencilPipeline(key);
const VkImageView src_depth_view = src_image_view.DepthView();
const VkImageView src_stencil_view = src_image_view.StencilView();
RecordShaderReadBarrier(scheduler, src_image_view);
scheduler.RequestRenderpass(dst_framebuffer);
scheduler.Record([dst_region, src_region, pipeline, layout, sampler, src_depth_view,
src_stencil_view, this](vk::CommandBuffer cmdbuf) {
const VkDescriptorSet descriptor_set = two_textures_descriptor_allocator.Commit();
UpdateTwoTexturesDescriptorSet(device, descriptor_set, sampler, src_depth_view,
src_stencil_view);
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set,
nullptr);
BindBlitState(cmdbuf, layout, dst_region, src_region);
cmdbuf.Draw(3, 1, 0, 0);
});
scheduler.InvalidateState();
}
void BlitImageHelper::ConvertD32ToR32(const Framebuffer* dst_framebuffer,
@@ -922,183 +882,19 @@ void BlitImageHelper::ClearDepthStencil(const Framebuffer* dst_framebuffer, bool
scheduler.InvalidateState();
}
void BlitImageHelper::CopyMSAAImpl(VkRenderPass renderpass, VkPipeline pipeline,
VkPipelineLayout layout, VkImage dst_image,
VkFormat dst_vk_format, VkImage src_image,
VkFormat src_vk_format, s32 scale_x, s32 scale_y,
std::span<const VideoCommon::ImageCopy> copies,
const MSAACopyAspectInfo& aspect_info, bool copy_stencil) {
while (!msaa_copy_resources.empty() && scheduler.IsFree(msaa_copy_resources.front().tick)) {
msaa_copy_resources.pop_front();
}
const VkSampler sampler = *nearest_sampler;
for (const VideoCommon::ImageCopy& copy : copies) {
const s32 num_layers = (std::min)(copy.src_subresource.num_layers,
copy.dst_subresource.num_layers);
for (s32 layer = 0; layer < num_layers; ++layer) {
const u32 src_level = static_cast<u32>(copy.src_subresource.base_level);
const u32 src_layer = static_cast<u32>(copy.src_subresource.base_layer + layer);
vk::ImageView src_view =
MakeMSAACopyView(device.GetLogical(), src_image, src_vk_format, src_level,
src_layer, aspect_info.src_view_aspect);
vk::ImageView src_stencil_view;
if (copy_stencil) {
src_stencil_view =
MakeMSAACopyView(device.GetLogical(), src_image, src_vk_format, src_level,
src_layer, VK_IMAGE_ASPECT_STENCIL_BIT);
}
vk::ImageView dst_view =
MakeMSAACopyView(device.GetLogical(), dst_image, dst_vk_format,
static_cast<u32>(copy.dst_subresource.base_level),
static_cast<u32>(copy.dst_subresource.base_layer + layer),
aspect_info.attachment_aspect);
const VkOffset2D dst_offset{copy.dst_offset.x, copy.dst_offset.y};
const VkExtent2D dst_extent{copy.extent.width, copy.extent.height};
const VkRect2D render_area{
.offset = dst_offset,
.extent = dst_extent,
};
vk::Framebuffer framebuffer = device.GetLogical().CreateFramebuffer(VkFramebufferCreateInfo{
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.renderPass = renderpass,
.attachmentCount = 1,
.pAttachments = dst_view.address(),
.width = static_cast<u32>(dst_offset.x) + dst_extent.width,
.height = static_cast<u32>(dst_offset.y) + dst_extent.height,
.layers = 1,
});
const MSAACopyPushConstants push_constants{
.dst_offset = {dst_offset.x, dst_offset.y},
.src_offset = {copy.src_offset.x, copy.src_offset.y},
.scale = {scale_x, scale_y},
};
VkImageView src_stencil_handle = VK_NULL_HANDLE;
if (copy_stencil) {
src_stencil_handle = *src_stencil_view;
}
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([this, pipeline, layout, sampler, renderpass,
framebuffer_handle = *framebuffer, src_view_handle = *src_view,
src_stencil_handle, src = src_image, dst = dst_image, render_area,
aspect_info, push_constants](vk::CommandBuffer cmdbuf) {
const VkImageSubresourceRange barrier_range{
.aspectMask = aspect_info.barrier_aspect,
.baseMipLevel = 0,
.levelCount = VK_REMAINING_MIP_LEVELS,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
};
const std::array pre_barriers{
VkImageMemoryBarrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = aspect_info.pre_src_access,
.dstAccessMask = aspect_info.pre_src_dst_access,
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = src,
.subresourceRange = barrier_range,
},
VkImageMemoryBarrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = aspect_info.pre_src_access,
.dstAccessMask = aspect_info.pre_dst_dst_access,
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = dst,
.subresourceRange = barrier_range,
},
};
cmdbuf.PipelineBarrier(aspect_info.pre_src_stages, aspect_info.pre_dst_stages, 0,
nullptr, nullptr, pre_barriers);
const VkRenderPassBeginInfo renderpass_bi{
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
.pNext = nullptr,
.renderPass = renderpass,
.framebuffer = framebuffer_handle,
.renderArea = render_area,
.clearValueCount = 0,
.pClearValues = nullptr,
};
cmdbuf.BeginRenderPass(renderpass_bi, VK_SUBPASS_CONTENTS_INLINE);
VkDescriptorSet descriptor_set = VK_NULL_HANDLE;
if (src_stencil_handle != VK_NULL_HANDLE) {
descriptor_set = two_textures_descriptor_allocator.Commit();
UpdateTwoTexturesDescriptorSet(device, descriptor_set, sampler, src_view_handle,
src_stencil_handle);
} else {
descriptor_set = one_texture_descriptor_allocator.Commit();
UpdateOneTextureDescriptorSet(device, descriptor_set, sampler, src_view_handle);
}
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set,
nullptr);
const VkViewport viewport{
.x = static_cast<float>(render_area.offset.x),
.y = static_cast<float>(render_area.offset.y),
.width = static_cast<float>(render_area.extent.width),
.height = static_cast<float>(render_area.extent.height),
.minDepth = 0.0f,
.maxDepth = 1.0f,
};
cmdbuf.SetViewport(0, viewport);
cmdbuf.SetScissor(0, render_area);
cmdbuf.PushConstants(layout, VK_SHADER_STAGE_FRAGMENT_BIT, push_constants);
cmdbuf.Draw(3, 1, 0, 0);
cmdbuf.EndRenderPass();
const VkImageMemoryBarrier post_barrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = aspect_info.post_src_access,
.dstAccessMask = aspect_info.post_dst_access,
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = dst,
.subresourceRange = barrier_range,
};
cmdbuf.PipelineBarrier(aspect_info.post_src_stages, aspect_info.post_dst_stages, 0,
post_barrier);
});
msaa_copy_resources.push_back(MSAACopyResources{
.tick = scheduler.CurrentTick(),
.src_view = std::move(src_view),
.dst_view = std::move(dst_view),
.framebuffer = std::move(framebuffer),
});
if (copy_stencil) {
msaa_copy_resources.push_back(MSAACopyResources{
.tick = scheduler.CurrentTick(),
.src_view = std::move(src_stencil_view),
.dst_view = vk::ImageView{},
.framebuffer = vk::Framebuffer{},
});
}
}
}
scheduler.InvalidateState();
}
void BlitImageHelper::CopyMSAA(RenderPassCache& render_pass_cache, VkImage dst_image,
VideoCore::Surface::PixelFormat dst_format, VkImage src_image,
VideoCore::Surface::PixelFormat src_format, u32 num_samples,
std::span<const VideoCommon::ImageCopy> copies,
bool msaa_to_non_msaa) {
while (!msaa_copy_resources.empty() && scheduler.IsFree(msaa_copy_resources.front().tick)) {
msaa_copy_resources.pop_front();
}
const auto [samples_x, samples_y] = VideoCommon::SamplesLog2(static_cast<int>(num_samples));
const s32 scale_x = 1 << samples_x;
const s32 scale_y = 1 << samples_y;
VkSampleCountFlagBits samples = SampleCountFlag(num_samples);
if (msaa_to_non_msaa) {
samples = VK_SAMPLE_COUNT_1_BIT;
}
const VkSampleCountFlagBits samples =
msaa_to_non_msaa ? VK_SAMPLE_COUNT_1_BIT : SampleCountFlag(num_samples);
RenderPassKey renderpass_key{};
renderpass_key.color_formats.fill(VideoCore::Surface::PixelFormat::Invalid);
renderpass_key.color_formats[0] = dst_format;
@@ -1109,35 +905,149 @@ void BlitImageHelper::CopyMSAA(RenderPassCache& render_pass_cache, VkImage dst_i
.renderpass = renderpass,
.samples = samples,
.msaa_to_non_msaa = msaa_to_non_msaa,
.format_class = FormatClass(dst_format),
};
const MSAACopyAspectInfo aspect_info{
.src_view_aspect = VK_IMAGE_ASPECT_COLOR_BIT,
.attachment_aspect = VK_IMAGE_ASPECT_COLOR_BIT,
.barrier_aspect = VK_IMAGE_ASPECT_COLOR_BIT,
.pre_src_access = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_WRITE_BIT |
VK_ACCESS_TRANSFER_WRITE_BIT,
.pre_src_dst_access = VK_ACCESS_SHADER_READ_BIT,
.pre_dst_dst_access =
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
.pre_src_stages = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT,
.pre_dst_stages =
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
.post_src_access = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
.post_dst_access = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT,
.post_src_stages = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
.post_dst_stages = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT,
};
const VkPipeline pipeline = FindOrEmplaceMSAACopyPipeline(key);
const VkPipelineLayout layout = *msaa_copy_pipeline_layout;
const VkSampler sampler = *nearest_sampler;
const VkFormat src_vk_format =
MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, true, src_format).format;
const VkFormat dst_vk_format =
MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, true, dst_format).format;
CopyMSAAImpl(renderpass, FindOrEmplaceMSAACopyPipeline(key), *msaa_copy_pipeline_layout,
dst_image, dst_vk_format, src_image, src_vk_format, scale_x, scale_y, copies,
aspect_info, false);
for (const VideoCommon::ImageCopy& copy : copies) {
ASSERT(copy.src_subresource.base_layer == 0);
ASSERT(copy.src_subresource.num_layers == 1);
ASSERT(copy.dst_subresource.base_layer == 0);
ASSERT(copy.dst_subresource.num_layers == 1);
vk::ImageView src_view =
MakeMSAACopyView(device.GetLogical(), src_image, src_vk_format,
static_cast<u32>(copy.src_subresource.base_level));
vk::ImageView dst_view =
MakeMSAACopyView(device.GetLogical(), dst_image, dst_vk_format,
static_cast<u32>(copy.dst_subresource.base_level));
const VkOffset2D dst_offset{copy.dst_offset.x, copy.dst_offset.y};
const VkExtent2D dst_extent{copy.extent.width, copy.extent.height};
const VkRect2D render_area{
.offset = dst_offset,
.extent = dst_extent,
};
vk::Framebuffer framebuffer = device.GetLogical().CreateFramebuffer(VkFramebufferCreateInfo{
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.renderPass = renderpass,
.attachmentCount = 1,
.pAttachments = dst_view.address(),
.width = static_cast<u32>(dst_offset.x) + dst_extent.width,
.height = static_cast<u32>(dst_offset.y) + dst_extent.height,
.layers = 1,
});
const MSAACopyPushConstants push_constants{
.dst_offset = {dst_offset.x, dst_offset.y},
.src_offset = {copy.src_offset.x, copy.src_offset.y},
.scale = {scale_x, scale_y},
};
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([this, pipeline, layout, sampler, renderpass,
framebuffer_handle = *framebuffer, src_view_handle = *src_view,
src = src_image, dst = dst_image, render_area,
push_constants](vk::CommandBuffer cmdbuf) {
constexpr VkImageSubresourceRange color_range{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0,
.levelCount = VK_REMAINING_MIP_LEVELS,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
};
const std::array pre_barriers{
VkImageMemoryBarrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = src,
.subresourceRange = color_range,
},
VkImageMemoryBarrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = dst,
.subresourceRange = color_range,
},
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
0, nullptr, nullptr, pre_barriers);
const VkRenderPassBeginInfo renderpass_bi{
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
.pNext = nullptr,
.renderPass = renderpass,
.framebuffer = framebuffer_handle,
.renderArea = render_area,
.clearValueCount = 0,
.pClearValues = nullptr,
};
cmdbuf.BeginRenderPass(renderpass_bi, VK_SUBPASS_CONTENTS_INLINE);
const VkDescriptorSet descriptor_set = one_texture_descriptor_allocator.Commit();
UpdateOneTextureDescriptorSet(device, descriptor_set, sampler, src_view_handle);
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set,
nullptr);
const VkViewport viewport{
.x = static_cast<float>(render_area.offset.x),
.y = static_cast<float>(render_area.offset.y),
.width = static_cast<float>(render_area.extent.width),
.height = static_cast<float>(render_area.extent.height),
.minDepth = 0.0f,
.maxDepth = 1.0f,
};
cmdbuf.SetViewport(0, viewport);
cmdbuf.SetScissor(0, render_area);
cmdbuf.PushConstants(layout, VK_SHADER_STAGE_FRAGMENT_BIT, push_constants);
cmdbuf.Draw(3, 1, 0, 0);
cmdbuf.EndRenderPass();
const VkImageMemoryBarrier post_barrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT,
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = dst,
.subresourceRange = color_range,
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_TRANSFER_BIT,
0, post_barrier);
});
msaa_copy_resources.push_back(MSAACopyResources{
.tick = scheduler.CurrentTick(),
.src_view = std::move(src_view),
.dst_view = std::move(dst_view),
.framebuffer = std::move(framebuffer),
});
}
scheduler.InvalidateState();
}
void BlitImageHelper::Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer,
@@ -1469,87 +1379,6 @@ VkPipeline BlitImageHelper::FindOrEmplaceBlitColorMSAAPipeline(const BlitMSAAPip
return *blit_msaa_color_pipelines.back();
}
VkPipeline BlitImageHelper::FindOrEmplaceBlitDepthStencilMSAAPipeline(
const BlitMSAAPipelineKey& key, bool blit_stencil) {
auto& keys = blit_stencil ? blit_msaa_depth_stencil_keys : blit_msaa_depth_keys;
auto& pipelines = blit_stencil ? blit_msaa_depth_stencil_pipelines : blit_msaa_depth_pipelines;
const auto it = std::ranges::find(keys, key);
if (it != keys.end()) {
return *pipelines[std::distance(keys.begin(), it)];
}
keys.push_back(key);
const std::array stages =
MakeStages(*full_screen_vert,
blit_stencil ? *blit_depth_stencil_msaa_frag : *blit_depth_msaa_frag);
const VkPipelineMultisampleStateCreateInfo multisample_ci{
.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.rasterizationSamples = key.samples,
.sampleShadingEnable = VK_TRUE,
.minSampleShading = 1.0f,
.pSampleMask = nullptr,
.alphaToCoverageEnable = VK_FALSE,
.alphaToOneEnable = VK_FALSE,
};
const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device);
pipelines.push_back(device.GetLogical().CreateGraphicsPipeline({
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.stageCount = static_cast<u32>(stages.size()),
.pStages = stages.data(),
.pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
.pInputAssemblyState = &input_assembly_ci,
.pTessellationState = nullptr,
.pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO,
.pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
.pMultisampleState = &multisample_ci,
.pDepthStencilState = blit_stencil ? &PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO
: &PIPELINE_DEPTH_ONLY_STATE_CREATE_INFO,
.pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO,
.pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO,
.layout = blit_stencil ? *two_textures_pipeline_layout : *one_texture_pipeline_layout,
.renderPass = key.renderpass,
.subpass = 0,
.basePipelineHandle = VK_NULL_HANDLE,
.basePipelineIndex = 0,
}));
return *pipelines.back();
}
VkPipeline BlitImageHelper::FindOrEmplaceBlitDepthPipeline(VkRenderPass renderpass) {
const auto it = std::ranges::find(blit_depth_keys, renderpass);
if (it != blit_depth_keys.end()) {
return *blit_depth_pipelines[std::distance(blit_depth_keys.begin(), it)];
}
blit_depth_keys.push_back(renderpass);
const std::array stages = MakeStages(*full_screen_vert, *blit_depth_frag);
const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device);
blit_depth_pipelines.push_back(device.GetLogical().CreateGraphicsPipeline({
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.stageCount = static_cast<u32>(stages.size()),
.pStages = stages.data(),
.pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
.pInputAssemblyState = &input_assembly_ci,
.pTessellationState = nullptr,
.pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO,
.pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
.pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
.pDepthStencilState = &PIPELINE_DEPTH_ONLY_STATE_CREATE_INFO,
.pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO,
.pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO,
.layout = *one_texture_pipeline_layout,
.renderPass = renderpass,
.subpass = 0,
.basePipelineHandle = VK_NULL_HANDLE,
.basePipelineIndex = 0,
}));
return *blit_depth_pipelines.back();
}
VkPipeline BlitImageHelper::FindOrEmplaceResolveDepthStencilPipeline(VkRenderPass renderpass,
bool resolve_stencil) {
auto& keys = resolve_stencil ? resolve_depth_stencil_keys : resolve_depth_keys;
@@ -1588,83 +1417,15 @@ VkPipeline BlitImageHelper::FindOrEmplaceResolveDepthStencilPipeline(VkRenderPas
return *pipelines.back();
}
void BlitImageHelper::CopyMSAADepth(RenderPassCache& render_pass_cache, VkImage dst_image,
VideoCore::Surface::PixelFormat dst_format, VkImage src_image,
VideoCore::Surface::PixelFormat src_format, u32 num_samples,
std::span<const VideoCommon::ImageCopy> copies,
bool copy_stencil, bool msaa_to_non_msaa) {
const auto [samples_x, samples_y] = VideoCommon::SamplesLog2(static_cast<int>(num_samples));
const s32 scale_x = 1 << samples_x;
const s32 scale_y = 1 << samples_y;
VkSampleCountFlagBits samples = SampleCountFlag(num_samples);
if (msaa_to_non_msaa) {
samples = VK_SAMPLE_COUNT_1_BIT;
}
RenderPassKey renderpass_key{};
renderpass_key.color_formats.fill(VideoCore::Surface::PixelFormat::Invalid);
renderpass_key.depth_format = dst_format;
renderpass_key.samples = samples;
const VkRenderPass renderpass = render_pass_cache.Get(renderpass_key);
const MSAACopyPipelineKey key{
.renderpass = renderpass,
.samples = samples,
.msaa_to_non_msaa = msaa_to_non_msaa,
.format_class = MSAACopyFormatClass::Float,
};
VkImageAspectFlags attachment_aspect = VK_IMAGE_ASPECT_DEPTH_BIT;
if (VideoCore::Surface::GetFormatType(dst_format) ==
VideoCore::Surface::SurfaceType::DepthStencil) {
attachment_aspect |= VK_IMAGE_ASPECT_STENCIL_BIT;
}
VkPipelineLayout layout = *msaa_copy_pipeline_layout;
if (copy_stencil) {
layout = *msaa_copy_depth_stencil_pipeline_layout;
}
const MSAACopyAspectInfo aspect_info{
.src_view_aspect = VK_IMAGE_ASPECT_DEPTH_BIT,
.attachment_aspect = attachment_aspect,
.barrier_aspect = attachment_aspect,
.pre_src_access =
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT,
.pre_src_dst_access = VK_ACCESS_SHADER_READ_BIT,
.pre_dst_dst_access = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
.pre_src_stages = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_TRANSFER_BIT,
.pre_dst_stages =
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT,
.post_src_access = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
.post_dst_access = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT |
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT,
.post_src_stages = VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
.post_dst_stages = vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER,
};
const VkFormat src_vk_format =
MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, true, src_format).format;
const VkFormat dst_vk_format =
MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, true, dst_format).format;
CopyMSAAImpl(renderpass, FindOrEmplaceMSAACopyDepthPipeline(key, copy_stencil), layout,
dst_image, dst_vk_format, src_image, src_vk_format, scale_x, scale_y, copies,
aspect_info, copy_stencil);
}
VkPipeline BlitImageHelper::FindOrEmplaceMSAACopyPipeline(const MSAACopyPipelineKey& key) {
const auto it = std::ranges::find(msaa_copy_keys, key);
if (it != msaa_copy_keys.end()) {
return *msaa_copy_pipelines[std::distance(msaa_copy_keys.begin(), it)];
}
msaa_copy_keys.push_back(key);
VkShaderModule frag_module = key.msaa_to_non_msaa ? *convert_msaa_to_non_msaa_frag
: *convert_non_msaa_to_msaa_frag;
if (key.format_class == MSAACopyFormatClass::SignedInteger) {
frag_module = key.msaa_to_non_msaa ? *convert_msaa_to_non_msaa_sint_frag
: *convert_non_msaa_to_msaa_sint_frag;
} else if (key.format_class == MSAACopyFormatClass::UnsignedInteger) {
frag_module = key.msaa_to_non_msaa ? *convert_msaa_to_non_msaa_uint_frag
: *convert_non_msaa_to_msaa_uint_frag;
}
const std::array stages = MakeStages(*clear_color_vert, frag_module);
const std::array stages = MakeStages(*clear_color_vert, key.msaa_to_non_msaa
? *convert_msaa_to_non_msaa_frag
: *convert_non_msaa_to_msaa_frag);
const VkPipelineMultisampleStateCreateInfo multisample_ci{
.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
.pNext = nullptr,
@@ -1701,85 +1462,6 @@ VkPipeline BlitImageHelper::FindOrEmplaceMSAACopyPipeline(const MSAACopyPipeline
return *msaa_copy_pipelines.back();
}
VkPipeline BlitImageHelper::FindOrEmplaceMSAACopyDepthPipeline(const MSAACopyPipelineKey& key,
bool copy_stencil) {
auto& keys = copy_stencil ? msaa_copy_depth_stencil_keys : msaa_copy_depth_keys;
auto& pipelines = copy_stencil ? msaa_copy_depth_stencil_pipelines : msaa_copy_depth_pipelines;
const auto it = std::ranges::find(keys, key);
if (it != keys.end()) {
return *pipelines[std::distance(keys.begin(), it)];
}
keys.push_back(key);
VkShaderModule frag_module;
if (key.msaa_to_non_msaa) {
frag_module = copy_stencil ? *convert_msaa_to_non_msaa_depth_stencil_frag
: *convert_msaa_to_non_msaa_depth_frag;
} else {
frag_module = copy_stencil ? *convert_non_msaa_to_msaa_depth_stencil_frag
: *convert_non_msaa_to_msaa_depth_frag;
}
const std::array stages = MakeStages(*clear_color_vert, frag_module);
const VkPipelineMultisampleStateCreateInfo multisample_ci{
.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.rasterizationSamples = key.samples,
.sampleShadingEnable = key.msaa_to_non_msaa ? VK_FALSE : VK_TRUE,
.minSampleShading = key.msaa_to_non_msaa ? 0.0f : 1.0f,
.pSampleMask = nullptr,
.alphaToCoverageEnable = VK_FALSE,
.alphaToOneEnable = VK_FALSE,
};
static constexpr VkStencilOpState REPLACE_STENCIL_OP{
.failOp = VK_STENCIL_OP_REPLACE,
.passOp = VK_STENCIL_OP_REPLACE,
.depthFailOp = VK_STENCIL_OP_REPLACE,
.compareOp = VK_COMPARE_OP_ALWAYS,
.compareMask = 0xFF,
.writeMask = 0xFF,
.reference = 0,
};
const VkPipelineDepthStencilStateCreateInfo depth_stencil_ci{
.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.depthTestEnable = VK_TRUE,
.depthWriteEnable = VK_TRUE,
.depthCompareOp = VK_COMPARE_OP_ALWAYS,
.depthBoundsTestEnable = VK_FALSE,
.stencilTestEnable = copy_stencil ? VK_TRUE : VK_FALSE,
.front = copy_stencil ? REPLACE_STENCIL_OP : VkStencilOpState{},
.back = copy_stencil ? REPLACE_STENCIL_OP : VkStencilOpState{},
.minDepthBounds = 0.0f,
.maxDepthBounds = 0.0f,
};
const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci =
GetPipelineInputAssemblyStateCreateInfo(device);
pipelines.push_back(device.GetLogical().CreateGraphicsPipeline({
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.stageCount = static_cast<u32>(stages.size()),
.pStages = stages.data(),
.pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
.pInputAssemblyState = &input_assembly_ci,
.pTessellationState = nullptr,
.pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO,
.pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
.pMultisampleState = &multisample_ci,
.pDepthStencilState = &depth_stencil_ci,
.pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO,
.pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO,
.layout = copy_stencil ? *msaa_copy_depth_stencil_pipeline_layout
: *msaa_copy_pipeline_layout,
.renderPass = key.renderpass,
.subpass = 0,
.basePipelineHandle = VK_NULL_HANDLE,
.basePipelineIndex = 0,
}));
return *pipelines.back();
}
void BlitImageHelper::ConvertDepthToColorPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass) {
ConvertPipeline(pipeline, renderpass, false);
}
@@ -45,19 +45,12 @@ struct BlitDepthStencilPipelineKey {
u32 stencil_ref;
};
enum class MSAACopyFormatClass : u32 {
Float,
SignedInteger,
UnsignedInteger,
};
struct MSAACopyPipelineKey {
constexpr auto operator<=>(const MSAACopyPipelineKey&) const noexcept = default;
VkRenderPass renderpass;
VkSampleCountFlagBits samples;
bool msaa_to_non_msaa;
MSAACopyFormatClass format_class;
};
struct BlitMSAAPipelineKey {
@@ -85,12 +78,6 @@ public:
void BlitColorMSAA(const Framebuffer* dst_framebuffer, const ImageView& src_image_view,
const Region2D& dst_region, const Region2D& src_region);
void BlitDepthStencilMSAA(const Framebuffer* dst_framebuffer, ImageView& src_image_view,
const Region2D& dst_region, const Region2D& src_region);
void BlitDepth(const Framebuffer* dst_framebuffer, ImageView& src_image_view,
const Region2D& dst_region, const Region2D& src_region);
void ResolveDepthStencil(const Framebuffer* dst_framebuffer, ImageView& src_image_view,
const Region2D& dst_region, const Region2D& src_region);
@@ -129,39 +116,7 @@ public:
VideoCore::Surface::PixelFormat src_format, u32 num_samples,
std::span<const VideoCommon::ImageCopy> copies, bool msaa_to_non_msaa);
void CopyMSAADepth(RenderPassCache& render_pass_cache, VkImage dst_image,
VideoCore::Surface::PixelFormat dst_format, VkImage src_image,
VideoCore::Surface::PixelFormat src_format, u32 num_samples,
std::span<const VideoCommon::ImageCopy> copies, bool copy_stencil,
bool msaa_to_non_msaa);
private:
struct MSAACopyAspectInfo {
VkImageAspectFlags src_view_aspect;
VkImageAspectFlags attachment_aspect;
VkImageAspectFlags barrier_aspect;
VkAccessFlags pre_src_access;
VkAccessFlags pre_src_dst_access;
VkAccessFlags pre_dst_dst_access;
VkPipelineStageFlags pre_src_stages;
VkPipelineStageFlags pre_dst_stages;
VkAccessFlags post_src_access;
VkAccessFlags post_dst_access;
VkPipelineStageFlags post_src_stages;
VkPipelineStageFlags post_dst_stages;
};
void BlitImpl(const Framebuffer* dst_framebuffer, const ImageView& src_image_view,
const Region2D& dst_region, const Region2D& src_region, VkPipeline pipeline,
VkSampler sampler, VkImageView src_view, VkImageView src_stencil_view,
bool blit_stencil);
void CopyMSAAImpl(VkRenderPass renderpass, VkPipeline pipeline, VkPipelineLayout layout,
VkImage dst_image, VkFormat dst_vk_format, VkImage src_image,
VkFormat src_vk_format, s32 scale_x, s32 scale_y,
std::span<const VideoCommon::ImageCopy> copies,
const MSAACopyAspectInfo& aspect_info, bool copy_stencil);
void Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer,
const ImageView& src_image_view);
@@ -176,13 +131,7 @@ private:
[[nodiscard]] VkPipeline FindOrEmplaceClearStencilPipeline(
const BlitDepthStencilPipelineKey& key);
[[nodiscard]] VkPipeline FindOrEmplaceMSAACopyPipeline(const MSAACopyPipelineKey& key);
[[nodiscard]] VkPipeline FindOrEmplaceMSAACopyDepthPipeline(const MSAACopyPipelineKey& key,
bool copy_stencil);
[[nodiscard]] VkPipeline FindOrEmplaceBlitColorMSAAPipeline(const BlitMSAAPipelineKey& key);
[[nodiscard]] VkPipeline FindOrEmplaceBlitDepthStencilMSAAPipeline(
const BlitMSAAPipelineKey& key, bool blit_stencil);
[[nodiscard]] VkPipeline FindOrEmplaceBlitDepthPipeline(VkRenderPass renderpass);
[[nodiscard]] VkPipeline FindOrEmplaceResolveDepthStencilPipeline(VkRenderPass renderpass,
bool resolve_stencil);
@@ -213,12 +162,10 @@ private:
vk::PipelineLayout two_textures_pipeline_layout;
vk::PipelineLayout clear_color_pipeline_layout;
vk::PipelineLayout msaa_copy_pipeline_layout;
vk::PipelineLayout msaa_copy_depth_stencil_pipeline_layout;
vk::ShaderModule full_screen_vert;
vk::ShaderModule blit_color_to_color_frag;
vk::ShaderModule blit_color_msaa_frag;
vk::ShaderModule blit_depth_stencil_frag;
vk::ShaderModule blit_depth_frag;
vk::ShaderModule blit_depth_msaa_frag;
vk::ShaderModule blit_depth_stencil_msaa_frag;
vk::ShaderModule clear_color_vert;
@@ -232,15 +179,7 @@ private:
vk::ShaderModule convert_d24s8_to_abgr8_frag;
vk::ShaderModule convert_s8d24_to_abgr8_frag;
vk::ShaderModule convert_msaa_to_non_msaa_frag;
vk::ShaderModule convert_msaa_to_non_msaa_sint_frag;
vk::ShaderModule convert_msaa_to_non_msaa_uint_frag;
vk::ShaderModule convert_msaa_to_non_msaa_depth_frag;
vk::ShaderModule convert_msaa_to_non_msaa_depth_stencil_frag;
vk::ShaderModule convert_non_msaa_to_msaa_frag;
vk::ShaderModule convert_non_msaa_to_msaa_sint_frag;
vk::ShaderModule convert_non_msaa_to_msaa_uint_frag;
vk::ShaderModule convert_non_msaa_to_msaa_depth_frag;
vk::ShaderModule convert_non_msaa_to_msaa_depth_stencil_frag;
vk::Sampler linear_sampler;
vk::Sampler nearest_sampler;
@@ -254,18 +193,8 @@ private:
std::vector<vk::Pipeline> clear_stencil_pipelines;
std::vector<MSAACopyPipelineKey> msaa_copy_keys;
std::vector<vk::Pipeline> msaa_copy_pipelines;
std::vector<MSAACopyPipelineKey> msaa_copy_depth_keys;
std::vector<vk::Pipeline> msaa_copy_depth_pipelines;
std::vector<MSAACopyPipelineKey> msaa_copy_depth_stencil_keys;
std::vector<vk::Pipeline> msaa_copy_depth_stencil_pipelines;
std::vector<BlitMSAAPipelineKey> blit_msaa_color_keys;
std::vector<vk::Pipeline> blit_msaa_color_pipelines;
std::vector<VkRenderPass> blit_depth_keys;
std::vector<vk::Pipeline> blit_depth_pipelines;
std::vector<BlitMSAAPipelineKey> blit_msaa_depth_keys;
std::vector<vk::Pipeline> blit_msaa_depth_pipelines;
std::vector<BlitMSAAPipelineKey> blit_msaa_depth_stencil_keys;
std::vector<vk::Pipeline> blit_msaa_depth_stencil_pipelines;
std::vector<VkRenderPass> resolve_depth_keys;
std::vector<vk::Pipeline> resolve_depth_pipelines;
std::vector<VkRenderPass> resolve_depth_stencil_keys;
@@ -370,9 +370,20 @@ inline void PushImageDescriptors(TextureCache& texture_cache,
const VkImageView null_image_view{texture_cache.GetImageView(VideoCommon::NULL_IMAGE_VIEW_ID).Handle(desc.type)};
if (null_image_view != VK_NULL_HANDLE) vk_image_view = null_image_view;
}
Sampler& sampler{texture_cache.GetSampler(sampler_id)};
guest_descriptor_queue.AddSampledImage(vk_image_view,
sampler.HandleFor(image_view, desc.is_depth));
const Sampler& sampler{texture_cache.GetSampler(sampler_id)};
const bool use_fallback_sampler{sampler.HasAddedAnisotropy() &&
!image_view.SupportsAnisotropy()};
VkSampler vk_sampler{use_fallback_sampler ? sampler.HandleWithDefaultAnisotropy()
: sampler.Handle()};
if (sampler.HasLinearFiltering() &&
VideoCore::Surface::IsPixelFormatInteger(image_view.format)) {
vk_sampler = sampler.HandleWithNearestFilter();
}
if (desc.is_depth && sampler.HasDepthComparison() &&
!image_view.SupportsDepthComparison()) {
vk_sampler = sampler.HandleWithoutDepthComparison();
}
guest_descriptor_queue.AddSampledImage(vk_image_view, vk_sampler);
const bool element_rescaled{texture_cache.IsRescaling(image_view)};
is_rescaled |= element_rescaled;
}
+14 -18
View File
@@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include "common/common_types.h"
#include "common/div_ceil.h"
#include "common/settings.h"
@@ -19,7 +17,7 @@
namespace Vulkan {
using PushConstants = std::array<u32, 4 + 2 + 2 + 1>;
using PushConstants = std::array<u32, 4 + 2 + 1>;
SGSR::SGSR(const Device& device, MemoryAllocator& memory_allocator, size_t image_count, VkExtent2D extent, bool edge_dir)
: m_memory_allocator{memory_allocator}
@@ -102,28 +100,26 @@ VkImageView SGSR::Draw(const Device& device, Scheduler& scheduler, size_t image_
const f32 input_image_width = f32(input_image_extent.width);
const f32 input_image_height = f32(input_image_extent.height);
const f32 crop_width = (crop_rect.right - crop_rect.left) * input_image_width;
const f32 crop_height = (crop_rect.bottom - crop_rect.top) * input_image_height;
static constexpr f32 EDGE_SHARPNESS_MAX = 2.0f;
const f32 edge_sharpness =
EDGE_SHARPNESS_MAX - f32(Settings::values.fsr_sharpening_slider.GetValue()) / 200.0f;
const f32 viewport_width = (crop_rect.right - crop_rect.left) * input_image_width;
const f32 viewport_height = (crop_rect.bottom - crop_rect.top) * input_image_height;
// expected [0, 2]
const f32 sharpening = f32(Settings::values.fsr_sharpening_slider.GetValue()) / 100.0f;
// p = (tex * viewport) / input = [0,n] (normalized texcoords)
// p * input = [0,1024], [0,768]
// layout( push_constant ) uniform constants {
// highp vec4 ViewportInfo[1];
// highp vec2 ResizeFactor;
// highp vec2 CropOffset;
// highp float EdgeSharpness;
// };
PushConstants viewport_con{};
viewport_con[0] = std::bit_cast<u32>(1.f / input_image_width);
viewport_con[1] = std::bit_cast<u32>(1.f / input_image_height);
viewport_con[2] = std::bit_cast<u32>(input_image_width);
viewport_con[3] = std::bit_cast<u32>(input_image_height);
viewport_con[4] = std::bit_cast<u32>(crop_width / input_image_width);
viewport_con[5] = std::bit_cast<u32>(crop_height / input_image_height);
viewport_con[6] = std::bit_cast<u32>((std::min)(crop_rect.left, crop_rect.right));
viewport_con[7] = std::bit_cast<u32>((std::min)(crop_rect.top, crop_rect.bottom));
viewport_con[8] = std::bit_cast<u32>(edge_sharpness);
viewport_con[0] = std::bit_cast<u32>(std::abs(1.f / viewport_width));
viewport_con[1] = std::bit_cast<u32>(std::abs(1.f / viewport_height));
viewport_con[2] = std::bit_cast<u32>(std::abs(viewport_width));
viewport_con[3] = std::bit_cast<u32>(std::abs(viewport_height));
viewport_con[4] = std::bit_cast<u32>(viewport_width / input_image_width);
viewport_con[5] = std::bit_cast<u32>(viewport_height / input_image_height);
viewport_con[6] = std::bit_cast<u32>(sharpening);
UploadImages(device, scheduler);
UpdateDescriptorSets(device, source_image_view, image_index);
@@ -404,8 +404,6 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
device.IsWorkgroupMemoryExplicitLayout8BitAccessSupported(),
.support_workgroup_layout_16bit_access =
device.IsWorkgroupMemoryExplicitLayout16BitAccessSupported(),
.support_shader_quad_control = device.IsKhrShaderQuadControlSupported(),
.support_quad_shuffles = device.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_QUAD_BIT),
.support_vote = device.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_VOTE_BIT),
.supported_subgroup_stages = supported_subgroup_stages,
.support_viewport_index_layer_non_geometry =
@@ -446,7 +444,6 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
.has_broken_unsigned_image_offsets = false,
.has_broken_signed_operations = false,
.has_broken_fp16_float_controls = driver_id == VK_DRIVER_ID_NVIDIA_PROPRIETARY,
.has_broken_fp32_denorm_flush = driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY,
.ignore_nan_fp_comparisons = false,
.has_broken_spirv_subgroup_mask_vector_extract_dynamic = false,
.has_broken_robust =
@@ -424,12 +424,24 @@ void RasterizerVulkan::Clear(u32 layer_count) {
const bool ds_deferrable =
!ds_used || ((!framebuffer->HasAspectDepthBit() || use_depth) &&
(!framebuffer->HasAspectStencilBit() || use_stencil) && !stencil_partial);
const bool can_defer_clear = ENABLE_DEFERRED_CLEAR && !regs.clear_control.use_scissor &&
regs.clear_surface.layer == 0 &&
!scheduler.IsRenderPassActive() &&
(!use_color || color_full_channels) && ds_deferrable;
if (!can_defer_clear) {
scheduler.RequestRenderpass(framebuffer);
}
query_cache.NotifySegment(true);
query_cache.CounterEnable(VideoCommon::QueryType::ZPassPixelCount64, maxwell3d->regs.zpass_pixel_count_enable);
u32 up_scale = 1;
u32 down_shift = 0;
if (texture_cache.IsRescaling()) {
up_scale = Settings::values.resolution_info.up_scale;
down_shift = Settings::values.resolution_info.down_shift;
}
UpdateViewportsState(regs);
VkRect2D default_scissor{};
default_scissor.offset.x = 0;
default_scissor.offset.y = 0;
@@ -485,22 +497,6 @@ void RasterizerVulkan::Clear(u32 layer_count) {
return;
}
const bool clear_covers_render_area =
clear_rect.rect.offset.x == 0 && clear_rect.rect.offset.y == 0 &&
clear_rect.rect.extent.width >= render_area.width &&
clear_rect.rect.extent.height >= render_area.height;
const bool can_defer_clear = ENABLE_DEFERRED_CLEAR && (!regs.clear_control.use_scissor || clear_covers_render_area) &&
regs.clear_surface.layer == 0 &&
!scheduler.IsRenderPassActive() &&
(!use_color || color_full_channels) && ds_deferrable;
if (!can_defer_clear) {
scheduler.RequestRenderpass(framebuffer);
}
query_cache.NotifySegment(true);
query_cache.CounterEnable(VideoCommon::QueryType::ZPassPixelCount64, maxwell3d->regs.zpass_pixel_count_enable);
UpdateViewportsState(regs);
const u32 color_attachment = regs.clear_surface.RT;
if (use_color && framebuffer->HasAspectColorBit(color_attachment)) {
const auto format = VideoCore::Surface::PixelFormatFromRenderTargetFormat(regs.rt[color_attachment].format);
@@ -65,72 +65,8 @@ using VideoCore::Surface::SurfaceType;
.finalLayout = VK_IMAGE_LAYOUT_GENERAL,
};
}
struct ResolveAspects {
bool depth;
bool stencil;
};
struct ResolveModes {
VkResolveModeFlagBits depth;
VkResolveModeFlagBits stencil;
};
constexpr ResolveAspects GetResolveAspects(PixelFormat format) {
const SurfaceType surface_type = GetSurfaceType(format);
return ResolveAspects{
.depth = surface_type == SurfaceType::Depth ||
surface_type == SurfaceType::DepthStencil,
.stencil = surface_type == SurfaceType::Stencil ||
surface_type == SurfaceType::DepthStencil,
};
}
ResolveModes PickResolveModes(const Device& device, PixelFormat format) {
constexpr VkResolveModeFlagBits mode = VK_RESOLVE_MODE_SAMPLE_ZERO_BIT;
const ResolveAspects aspects = GetResolveAspects(format);
const bool depth_mode_supported = (device.GetDepthResolveModes() & mode) != 0;
const bool stencil_mode_supported = (device.GetStencilResolveModes() & mode) != 0;
ResolveModes modes{
.depth = VK_RESOLVE_MODE_NONE,
.stencil = VK_RESOLVE_MODE_NONE,
};
if (aspects.depth && depth_mode_supported) {
modes.depth = mode;
}
if (aspects.stencil && stencil_mode_supported) {
modes.stencil = mode;
}
if (modes.depth == modes.stencil || device.SupportsIndependentResolveNone()) {
return modes;
}
if (modes.depth != VK_RESOLVE_MODE_NONE && stencil_mode_supported) {
modes.stencil = mode;
} else if (modes.stencil != VK_RESOLVE_MODE_NONE && depth_mode_supported) {
modes.depth = mode;
}
return modes;
}
} // Anonymous namespace
bool SupportsDepthStencilResolve(const Device& device, PixelFormat depth_format) {
if (depth_format == PixelFormat::Invalid || !device.IsKhrDepthStencilResolveSupported()) {
return false;
}
const ResolveAspects aspects = GetResolveAspects(depth_format);
if (!aspects.depth && !aspects.stencil) {
return false;
}
const ResolveModes modes = PickResolveModes(device, depth_format);
if ((aspects.depth && modes.depth == VK_RESOLVE_MODE_NONE) ||
(aspects.stencil && modes.stencil == VK_RESOLVE_MODE_NONE)) {
return false;
}
return modes.depth == modes.stencil || device.SupportsIndependentResolveNone();
}
RenderPassCache::RenderPassCache(const Device& device_) : device{&device_} {}
VkRenderPass RenderPassCache::Get(const RenderPassKey& key) {
@@ -139,9 +75,7 @@ VkRenderPass RenderPassCache::Get(const RenderPassKey& key) {
if (!is_new) {
return *pair->second;
}
static constexpr size_t MAX_ATTACHMENTS =
2 * std::tuple_size_v<decltype(RenderPassKey::color_formats)> + 2;
boost::container::static_vector<VkAttachmentDescription, MAX_ATTACHMENTS> descriptions;
boost::container::static_vector<VkAttachmentDescription, 9> descriptions;
std::array<VkAttachmentReference, 8> references{};
u32 num_attachments{};
u32 num_colors{};
@@ -175,11 +109,8 @@ VkRenderPass RenderPassCache::Get(const RenderPassKey& key) {
const VkAttachmentLoadOp depth_load_op = key.depth_stencil_clear
? VK_ATTACHMENT_LOAD_OP_CLEAR
: VK_ATTACHMENT_LOAD_OP_LOAD;
const VkAttachmentStoreOp depth_store_op = key.depth_stencil_discard
? VK_ATTACHMENT_STORE_OP_DONT_CARE
: VK_ATTACHMENT_STORE_OP_STORE;
descriptions.push_back(AttachmentDescription(*device, key.depth_format, key.samples,
depth_load_op, depth_store_op));
depth_load_op, VK_ATTACHMENT_STORE_OP_STORE));
}
std::array<VkAttachmentReference, 8> resolve_references{};
const bool do_resolve_color =
@@ -202,21 +133,6 @@ VkRenderPass RenderPassCache::Get(const RenderPassKey& key) {
}
}
}
const bool do_resolve_depth_stencil = key.resolve_depth_stencil && has_depth &&
key.samples != VK_SAMPLE_COUNT_1_BIT &&
SupportsDepthStencilResolve(*device, key.depth_format);
VkAttachmentReference depth_resolve_reference{};
if (do_resolve_depth_stencil) {
depth_resolve_reference = VkAttachmentReference{
.attachment = static_cast<u32>(descriptions.size()),
.layout = VK_IMAGE_LAYOUT_GENERAL,
};
VkAttachmentDescription resolve_desc =
AttachmentDescription(*device, key.depth_format, VK_SAMPLE_COUNT_1_BIT,
VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_STORE);
resolve_desc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
descriptions.push_back(resolve_desc);
}
const VkSubpassDescription subpass{
.flags = 0,
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
@@ -241,92 +157,6 @@ VkRenderPass RenderPassCache::Get(const RenderPassKey& key) {
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
.dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT
};
if (device->IsKhrCreateRenderPass2Supported()) {
boost::container::static_vector<VkAttachmentDescription2, MAX_ATTACHMENTS> descriptions2;
for (const VkAttachmentDescription& description : descriptions) {
descriptions2.push_back(VkAttachmentDescription2{
.sType = VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2,
.pNext = nullptr,
.flags = description.flags,
.format = description.format,
.samples = description.samples,
.loadOp = description.loadOp,
.storeOp = description.storeOp,
.stencilLoadOp = description.stencilLoadOp,
.stencilStoreOp = description.stencilStoreOp,
.initialLayout = description.initialLayout,
.finalLayout = description.finalLayout,
});
}
const auto promote = [](const VkAttachmentReference& reference) {
return VkAttachmentReference2{
.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2,
.pNext = nullptr,
.attachment = reference.attachment,
.layout = reference.layout,
.aspectMask = 0,
};
};
std::array<VkAttachmentReference2, 8> references2{};
std::array<VkAttachmentReference2, 8> resolve_references2{};
for (size_t index = 0; index < references.size(); ++index) {
references2[index] = promote(references[index]);
resolve_references2[index] = promote(resolve_references[index]);
}
const VkAttachmentReference2 depth_reference2 = promote(depth_reference);
const VkAttachmentReference2 depth_resolve_reference2 = promote(depth_resolve_reference);
const ResolveModes resolve_modes = PickResolveModes(*device, key.depth_format);
const VkSubpassDescriptionDepthStencilResolve depth_stencil_resolve{
.sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_DEPTH_STENCIL_RESOLVE,
.pNext = nullptr,
.depthResolveMode = resolve_modes.depth,
.stencilResolveMode = resolve_modes.stencil,
.pDepthStencilResolveAttachment = &depth_resolve_reference2,
};
const VkSubpassDescription2 subpass2{
.sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2,
.pNext = do_resolve_depth_stencil ? &depth_stencil_resolve : nullptr,
.flags = 0,
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
.viewMask = 0,
.inputAttachmentCount = 0,
.pInputAttachments = nullptr,
.colorAttachmentCount = num_attachments,
.pColorAttachments = references2.data(),
.pResolveAttachments = do_resolve_color ? resolve_references2.data() : nullptr,
.pDepthStencilAttachment = has_depth ? &depth_reference2 : nullptr,
.preserveAttachmentCount = 0,
.pPreserveAttachments = nullptr,
};
const VkSubpassDependency2 dependency2{
.sType = VK_STRUCTURE_TYPE_SUBPASS_DEPENDENCY_2,
.pNext = nullptr,
.srcSubpass = dependency.srcSubpass,
.dstSubpass = dependency.dstSubpass,
.srcStageMask = dependency.srcStageMask,
.dstStageMask = dependency.dstStageMask,
.srcAccessMask = dependency.srcAccessMask,
.dstAccessMask = dependency.dstAccessMask,
.dependencyFlags = dependency.dependencyFlags,
.viewOffset = 0,
};
pair->second = device->GetLogical().CreateRenderPass2({
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO_2,
.pNext = nullptr,
.flags = 0,
.attachmentCount = static_cast<u32>(descriptions2.size()),
.pAttachments = descriptions2.empty() ? nullptr : descriptions2.data(),
.subpassCount = 1,
.pSubpasses = &subpass2,
.dependencyCount = 1,
.pDependencies = &dependency2,
.correlatedViewMaskCount = 0,
.pCorrelatedViewMasks = nullptr,
});
return *pair->second;
}
pair->second = device->GetLogical().CreateRenderPass({
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
.pNext = nullptr,

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