mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-29 09:58:05 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 17c5325109 | |||
| 2876c4ec52 |
-1
@@ -624,7 +624,6 @@ abstract class SettingsItem(
|
|||||||
IntSetting.FSR_SHARPENING_SLIDER,
|
IntSetting.FSR_SHARPENING_SLIDER,
|
||||||
titleId = R.string.fsr_sharpness,
|
titleId = R.string.fsr_sharpness,
|
||||||
descriptionId = R.string.fsr_sharpness_description,
|
descriptionId = R.string.fsr_sharpness_description,
|
||||||
max = 200,
|
|
||||||
units = "%"
|
units = "%"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
+3
-7
@@ -99,12 +99,7 @@ class SettingsFragmentPresenter(
|
|||||||
|
|
||||||
add(BooleanSetting.RENDERER_FRAME_GEN.key)
|
add(BooleanSetting.RENDERER_FRAME_GEN.key)
|
||||||
add(IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.key)
|
add(IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.key)
|
||||||
if (IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.getInt(
|
add(IntSetting.RENDERER_FRAME_GEN_MULTIPLIER.key)
|
||||||
getNeedsGlobalForKey(IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.key)
|
|
||||||
) == 0
|
|
||||||
) {
|
|
||||||
add(IntSetting.RENDERER_FRAME_GEN_MULTIPLIER.key)
|
|
||||||
}
|
|
||||||
add(IntSetting.RENDERER_FRAME_GEN_QUEUE_TARGET.key)
|
add(IntSetting.RENDERER_FRAME_GEN_QUEUE_TARGET.key)
|
||||||
add(BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.key)
|
add(BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.key)
|
||||||
if (!BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.getBoolean(
|
if (!BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.getBoolean(
|
||||||
@@ -312,6 +307,8 @@ class SettingsFragmentPresenter(
|
|||||||
// TODO(crueter): sub-submenus?
|
// TODO(crueter): sub-submenus?
|
||||||
private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) {
|
private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) {
|
||||||
sl.apply {
|
sl.apply {
|
||||||
|
// add(IntSetting.RENDERER_NVDEC_EMULATION.key)
|
||||||
|
|
||||||
add(IntSetting.RENDERER_RESOLUTION.key)
|
add(IntSetting.RENDERER_RESOLUTION.key)
|
||||||
add(IntSetting.RENDERER_VSYNC.key)
|
add(IntSetting.RENDERER_VSYNC.key)
|
||||||
add(IntSetting.RENDERER_SCALING_FILTER.key)
|
add(IntSetting.RENDERER_SCALING_FILTER.key)
|
||||||
@@ -328,7 +325,6 @@ class SettingsFragmentPresenter(
|
|||||||
add(IntSetting.MAX_ANISOTROPY.key)
|
add(IntSetting.MAX_ANISOTROPY.key)
|
||||||
add(IntSetting.RENDERER_VRAM_USAGE_MODE.key)
|
add(IntSetting.RENDERER_VRAM_USAGE_MODE.key)
|
||||||
add(IntSetting.RENDERER_ASTC_DECODE_METHOD.key)
|
add(IntSetting.RENDERER_ASTC_DECODE_METHOD.key)
|
||||||
add(IntSetting.RENDERER_NVDEC_EMULATION.key)
|
|
||||||
|
|
||||||
add(BooleanSetting.SYNC_MEMORY_OPERATIONS.key)
|
add(BooleanSetting.SYNC_MEMORY_OPERATIONS.key)
|
||||||
add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key)
|
add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key)
|
||||||
|
|||||||
@@ -1182,7 +1182,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
container,
|
container,
|
||||||
IntSetting.FSR_SHARPENING_SLIDER,
|
IntSetting.FSR_SHARPENING_SLIDER,
|
||||||
minValue = 0,
|
minValue = 0,
|
||||||
maxValue = 200,
|
maxValue = 100,
|
||||||
units = "%"
|
units = "%"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import androidx.activity.result.contract.ActivityResultContracts
|
|||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.doOnPreDraw
|
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import androidx.core.widget.doOnTextChanged
|
import androidx.core.widget.doOnTextChanged
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
@@ -60,10 +59,6 @@ class GamesFragment : Fragment() {
|
|||||||
|
|
||||||
private var lastViewType: Int = GameAdapter.VIEW_TYPE_GRID
|
private var lastViewType: Int = GameAdapter.VIEW_TYPE_GRID
|
||||||
private var fallbackBottomInset: Int = 0
|
private var fallbackBottomInset: Int = 0
|
||||||
private var pendingPostReloadListSettle = false
|
|
||||||
private var pendingPostReloadListSettleGeneration = 0
|
|
||||||
private var gameListSubmitGeneration = 0
|
|
||||||
private var committedGameListSubmitGeneration = 0
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val SEARCH_TEXT = "SearchText"
|
private const val SEARCH_TEXT = "SearchText"
|
||||||
@@ -173,9 +168,10 @@ class GamesFragment : Fragment() {
|
|||||||
|
|
||||||
gamesViewModel.shouldScrollAfterReload.collect(viewLifecycleOwner) { shouldScroll ->
|
gamesViewModel.shouldScrollAfterReload.collect(viewLifecycleOwner) { shouldScroll ->
|
||||||
if (shouldScroll) {
|
if (shouldScroll) {
|
||||||
pendingPostReloadListSettle = true
|
binding.gridGames.post {
|
||||||
pendingPostReloadListSettleGeneration = gameListSubmitGeneration
|
(binding.gridGames as? CarouselRecyclerView)?.pendingScrollAfterReload = true
|
||||||
schedulePostReloadListSettle()
|
gameAdapter.notifyDataSetChanged()
|
||||||
|
}
|
||||||
gamesViewModel.setShouldScrollAfterReload(false)
|
gamesViewModel.setShouldScrollAfterReload(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -277,42 +273,11 @@ class GamesFragment : Fragment() {
|
|||||||
lastSearchText = currentSearchText
|
lastSearchText = currentSearchText
|
||||||
lastFilter = currentFilter
|
lastFilter = currentFilter
|
||||||
} else {
|
} else {
|
||||||
submitGameList(games)
|
((binding.gridGames as? RecyclerView)?.adapter as? GameAdapter)?.submitList(games)
|
||||||
gamesViewModel.setFilteredGames(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() {
|
private fun setupTopView() {
|
||||||
binding.searchText.doOnTextChanged() { text: CharSequence?, _: Int, _: Int, _: Int ->
|
binding.searchText.doOnTextChanged() { text: CharSequence?, _: Int, _: Int, _: Int ->
|
||||||
if (text.toString().isNotEmpty()) {
|
if (text.toString().isNotEmpty()) {
|
||||||
@@ -449,7 +414,9 @@ class GamesFragment : Fragment() {
|
|||||||
|
|
||||||
val searchTerm = binding.searchText.text.toString().lowercase(Locale.getDefault())
|
val searchTerm = binding.searchText.text.toString().lowercase(Locale.getDefault())
|
||||||
if (searchTerm.isEmpty()) {
|
if (searchTerm.isEmpty()) {
|
||||||
submitGameList(filteredList)
|
((binding.gridGames as? RecyclerView)?.adapter as? GameAdapter)?.submitList(
|
||||||
|
filteredList
|
||||||
|
)
|
||||||
gamesViewModel.setFilteredGames(filteredList)
|
gamesViewModel.setFilteredGames(filteredList)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -465,7 +432,7 @@ class GamesFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}.sortedByDescending { it.score }.map { it.item }
|
}.sortedByDescending { it.score }.map { it.item }
|
||||||
|
|
||||||
submitGameList(sortedList)
|
((binding.gridGames as? RecyclerView)?.adapter as? GameAdapter)?.submitList(sortedList)
|
||||||
gamesViewModel.setFilteredGames(sortedList)
|
gamesViewModel.setFilteredGames(sortedList)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ object GameHelper {
|
|||||||
|
|
||||||
fun getGames(): List<Game> {
|
fun getGames(): List<Game> {
|
||||||
val games = mutableListOf<Game>()
|
val games = mutableListOf<Game>()
|
||||||
val gamesByProgramId = mutableMapOf<String, Game>()
|
|
||||||
val context = YuzuApplication.appContext
|
val context = YuzuApplication.appContext
|
||||||
preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
|
||||||
@@ -64,7 +63,6 @@ object GameHelper {
|
|||||||
|
|
||||||
addGamesRecursive(
|
addGamesRecursive(
|
||||||
games,
|
games,
|
||||||
gamesByProgramId,
|
|
||||||
FileUtil.listFiles(gameDirUri),
|
FileUtil.listFiles(gameDirUri),
|
||||||
scanDepth,
|
scanDepth,
|
||||||
mountedContainerUris
|
mountedContainerUris
|
||||||
@@ -138,7 +136,6 @@ object GameHelper {
|
|||||||
|
|
||||||
private fun addGamesRecursive(
|
private fun addGamesRecursive(
|
||||||
games: MutableList<Game>,
|
games: MutableList<Game>,
|
||||||
gamesByProgramId: MutableMap<String, Game>,
|
|
||||||
files: Array<MinimalDocumentFile>,
|
files: Array<MinimalDocumentFile>,
|
||||||
depth: Int,
|
depth: Int,
|
||||||
mountedContainerUris: MutableSet<String>
|
mountedContainerUris: MutableSet<String>
|
||||||
@@ -151,7 +148,6 @@ object GameHelper {
|
|||||||
if (it.isDirectory) {
|
if (it.isDirectory) {
|
||||||
addGamesRecursive(
|
addGamesRecursive(
|
||||||
games,
|
games,
|
||||||
gamesByProgramId,
|
|
||||||
FileUtil.listFiles(it.uri),
|
FileUtil.listFiles(it.uri),
|
||||||
depth - 1,
|
depth - 1,
|
||||||
mountedContainerUris
|
mountedContainerUris
|
||||||
@@ -160,9 +156,8 @@ object GameHelper {
|
|||||||
val extension = FileUtil.getExtension(it.uri).lowercase()
|
val extension = FileUtil.getExtension(it.uri).lowercase()
|
||||||
val filePath = it.uri.toString()
|
val filePath = it.uri.toString()
|
||||||
|
|
||||||
val mountedContainer = externalContentExtensions.contains(extension) &&
|
if (externalContentExtensions.contains(extension) &&
|
||||||
mountedContainerUris.add(filePath)
|
mountedContainerUris.add(filePath)) {
|
||||||
if (mountedContainer) {
|
|
||||||
NativeLibrary.addGameFolderFileToFilesystemProvider(filePath)
|
NativeLibrary.addGameFolderFileToFilesystemProvider(filePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,20 +165,6 @@ object GameHelper {
|
|||||||
val game = getGame(it.uri, true, false)
|
val game = getGame(it.uri, true, false)
|
||||||
if (game != null) {
|
if (game != null) {
|
||||||
games.add(game)
|
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-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// 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.R
|
||||||
import org.yuzu.yuzu_emu.YuzuApplication
|
import org.yuzu.yuzu_emu.YuzuApplication
|
||||||
import org.yuzu.yuzu_emu.model.Game
|
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(
|
class GameIconFetcher(
|
||||||
private val game: Game,
|
private val game: Game,
|
||||||
@@ -43,15 +31,14 @@ class GameIconFetcher(
|
|||||||
) : Fetcher {
|
) : Fetcher {
|
||||||
override suspend fun fetch(): FetchResult {
|
override suspend fun fetch(): FetchResult {
|
||||||
return DrawableResult(
|
return DrawableResult(
|
||||||
drawable = decodeGameIcon(game)!!.toDrawable(options.context.resources),
|
drawable = decodeGameIcon(game.path)!!.toDrawable(options.context.resources),
|
||||||
isSampled = false,
|
isSampled = false,
|
||||||
dataSource = DataSource.DISK
|
dataSource = DataSource.DISK
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun decodeGameIcon(game: Game): Bitmap? {
|
private fun decodeGameIcon(uri: String): Bitmap? {
|
||||||
val data = GameMetadata.getIcon(game.path)
|
val data = GameMetadata.getIcon(uri)
|
||||||
gameIconHashes[game.iconCacheKey()] = data.contentHashCode()
|
|
||||||
return BitmapFactory.decodeByteArray(
|
return BitmapFactory.decodeByteArray(
|
||||||
data,
|
data,
|
||||||
0,
|
0,
|
||||||
@@ -67,7 +54,7 @@ class GameIconFetcher(
|
|||||||
}
|
}
|
||||||
|
|
||||||
class GameIconKeyer : Keyer<Game> {
|
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 {
|
object GameIconUtils {
|
||||||
@@ -84,58 +71,14 @@ object GameIconUtils {
|
|||||||
.build()
|
.build()
|
||||||
|
|
||||||
fun loadGameIcon(game: Game, imageView: ImageView) {
|
fun loadGameIcon(game: Game, imageView: ImageView) {
|
||||||
gameIconTargets[imageView] = GameIconTarget(game)
|
|
||||||
val request = ImageRequest.Builder(YuzuApplication.appContext)
|
val request = ImageRequest.Builder(YuzuApplication.appContext)
|
||||||
.data(game)
|
.data(game)
|
||||||
.target(imageView)
|
.target(imageView)
|
||||||
.error(R.drawable.default_icon)
|
.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()
|
.build()
|
||||||
imageLoader.enqueue(request)
|
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 {
|
suspend fun getGameIcon(lifecycleOwner: LifecycleOwner, game: Game): Bitmap {
|
||||||
val request = ImageRequest.Builder(YuzuApplication.appContext)
|
val request = ImageRequest.Builder(YuzuApplication.appContext)
|
||||||
.data(game)
|
.data(game)
|
||||||
|
|||||||
@@ -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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.ui
|
package org.yuzu.yuzu_emu.ui
|
||||||
@@ -11,8 +11,6 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
|||||||
import androidx.recyclerview.widget.PagerSnapHelper
|
import androidx.recyclerview.widget.PagerSnapHelper
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.math.cos
|
|
||||||
import kotlin.math.sin
|
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.adapters.GameAdapter
|
import org.yuzu.yuzu_emu.adapters.GameAdapter
|
||||||
import androidx.core.view.doOnNextLayout
|
import androidx.core.view.doOnNextLayout
|
||||||
@@ -36,7 +34,6 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
|||||||
private var overlapDecoration: OverlappingDecoration? = null
|
private var overlapDecoration: OverlappingDecoration? = null
|
||||||
private var pagerSnapHelper: PagerSnapHelper? = null
|
private var pagerSnapHelper: PagerSnapHelper? = null
|
||||||
private var scalingScrollListener: OnScrollListener? = null
|
private var scalingScrollListener: OnScrollListener? = null
|
||||||
private var savedItemAnimator: RecyclerView.ItemAnimator? = null
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val CAROUSEL_CARD_SIZE_FACTOR = "CarouselCardSizeMultiplier"
|
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_OVERLAP_FACTOR = "CarouselOverlapFactor"
|
||||||
private const val CAROUSEL_MAX_FLING_COUNT = "CarouselMaxFlingCount"
|
private const val CAROUSEL_MAX_FLING_COUNT = "CarouselMaxFlingCount"
|
||||||
private const val CAROUSEL_FLING_MULTIPLIER = "CarouselFlingMultiplier"
|
private const val CAROUSEL_FLING_MULTIPLIER = "CarouselFlingMultiplier"
|
||||||
private const val CAROUSEL_ARC_ANGLE_STEP_DEGREES = 15.0
|
private const val CAROUSEL_CARDS_SCALING_SHAPE = "CarouselCardsScalingShape"
|
||||||
private const val CAROUSEL_ARC_MAX_ANGLE_DEGREES = 165.0
|
private const val CAROUSEL_CARDS_ALPHA_SHAPE = "CarouselCardsAlphaShape"
|
||||||
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
|
|
||||||
const val CAROUSEL_LAST_SCROLL_POSITION = "CarouselLastScrollPosition"
|
const val CAROUSEL_LAST_SCROLL_POSITION = "CarouselLastScrollPosition"
|
||||||
const val CAROUSEL_VIEW_TYPE_PORTRAIT = "GamesViewTypePortrait"
|
const val CAROUSEL_VIEW_TYPE_PORTRAIT = "GamesViewTypePortrait"
|
||||||
const val CAROUSEL_VIEW_TYPE_LANDSCAPE = "GamesViewTypeLandscape"
|
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) {
|
fun updateChildScaleAndAlphaForPosition(child: View) {
|
||||||
val cardSize = (adapter as? GameAdapter ?: return).cardSize
|
val cardSize = (adapter as? GameAdapter ?: return).cardSize
|
||||||
val position = getChildViewHolder(child).bindingAdapterPosition
|
val position = getChildViewHolder(child).bindingAdapterPosition
|
||||||
if (position == RecyclerView.NO_POSITION || cardSize <= 0) {
|
if (position == RecyclerView.NO_POSITION || cardSize <= 0) {
|
||||||
return // No valid position or card size
|
return // No valid position or card size
|
||||||
}
|
}
|
||||||
val layoutParams = child.layoutParams
|
child.layoutParams.width = cardSize
|
||||||
if (layoutParams.width != cardSize || layoutParams.height != cardSize) {
|
child.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
|
|
||||||
|
|
||||||
|
val center = getRecyclerViewCenter()
|
||||||
|
val distance = abs(getChildDistanceToCenter(child))
|
||||||
val internalBorderScale = resources.getFraction(R.fraction.carousel_bordercards_scale, 1, 1)
|
val internalBorderScale = resources.getFraction(R.fraction.carousel_bordercards_scale, 1, 1)
|
||||||
val borderScale = preferences.getFloat(CAROUSEL_BORDERCARDS_SCALE, internalBorderScale).coerceIn(
|
val borderScale = preferences.getFloat(CAROUSEL_BORDERCARDS_SCALE, internalBorderScale).coerceIn(
|
||||||
0f,
|
0f,
|
||||||
1f
|
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 scale = (borderScale + (1f - borderScale) * shapedScaling).coerceIn(0f, 1f)
|
||||||
|
|
||||||
|
val maxDistance = width / 2f
|
||||||
|
val alphaInput = (distance / maxDistance).coerceIn(0f, 1f)
|
||||||
val internalBordersAlpha = resources.getFraction(
|
val internalBordersAlpha = resources.getFraction(
|
||||||
R.fraction.carousel_bordercards_alpha,
|
R.fraction.carousel_bordercards_alpha,
|
||||||
1,
|
1,
|
||||||
@@ -223,12 +209,15 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
|||||||
0f,
|
0f,
|
||||||
1f
|
1f
|
||||||
)
|
)
|
||||||
val shapedAlpha = cos(depthInput * Math.PI).toFloat()
|
val internalAlphaShapeSetting = resources.getInteger(R.integer.carousel_cards_alpha_shape)
|
||||||
val baseAlpha = (borderAlpha + (1f - borderAlpha) * shapedAlpha).coerceIn(0f, 1f)
|
val alphaShapeSetting = preferences.getInt(
|
||||||
val rearPresence = (1f - (absoluteAngle - fadeOutStartAngle) /
|
CAROUSEL_CARDS_ALPHA_SHAPE,
|
||||||
(fadeOutEndAngle - fadeOutStartAngle)).coerceIn(0f, 1f)
|
internalAlphaShapeSetting
|
||||||
val alpha = (baseAlpha * rearPresence).coerceIn(0f, 1f)
|
)
|
||||||
|
val shapedAlpha = shapingFunction(alphaInput, alphaShapeSetting)
|
||||||
|
val alpha = (borderAlpha + (1f - borderAlpha) * shapedAlpha).coerceIn(0f, 1f)
|
||||||
|
|
||||||
|
child.animate().cancel()
|
||||||
child.alpha = alpha
|
child.alpha = alpha
|
||||||
child.scaleX = scale
|
child.scaleX = scale
|
||||||
child.scaleY = scale
|
child.scaleY = scale
|
||||||
@@ -284,9 +273,7 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
|||||||
0f,
|
0f,
|
||||||
1f
|
1f
|
||||||
)
|
)
|
||||||
val scaledHeight = height * userFactor
|
return (userFactor * (height - bottomInset)).toInt()
|
||||||
val availableHeight = height - bottomInset
|
|
||||||
return minOf(scaledHeight.toInt(), availableHeight.toInt())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setupCarousel(enabled: Boolean) {
|
fun setupCarousel(enabled: Boolean) {
|
||||||
@@ -295,13 +282,6 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
|||||||
if (gameAdapter.cardSize == 0) return
|
if (gameAdapter.cardSize == 0) return
|
||||||
if (bottomInset < 0) return
|
if (bottomInset < 0) return
|
||||||
|
|
||||||
itemAnimator?.let {
|
|
||||||
if (savedItemAnimator == null) {
|
|
||||||
savedItemAnimator = it
|
|
||||||
}
|
|
||||||
itemAnimator = null
|
|
||||||
}
|
|
||||||
|
|
||||||
useCustomDrawingOrder = true
|
useCustomDrawingOrder = true
|
||||||
val cardSize = gameAdapter.cardSize
|
val cardSize = gameAdapter.cardSize
|
||||||
|
|
||||||
@@ -356,12 +336,6 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
|||||||
// Detach PagerSnapHelper
|
// Detach PagerSnapHelper
|
||||||
pagerSnapHelper?.attachToRecyclerView(null)
|
pagerSnapHelper?.attachToRecyclerView(null)
|
||||||
pagerSnapHelper = null
|
pagerSnapHelper = null
|
||||||
savedItemAnimator?.let {
|
|
||||||
if (itemAnimator == null) {
|
|
||||||
itemAnimator = it
|
|
||||||
}
|
|
||||||
savedItemAnimator = null
|
|
||||||
}
|
|
||||||
useCustomDrawingOrder = false
|
useCustomDrawingOrder = false
|
||||||
// Reset padding and fling
|
// Reset padding and fling
|
||||||
setPadding(0, 0, 0, 0)
|
setPadding(0, 0, 0, 0)
|
||||||
@@ -370,7 +344,6 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
|||||||
// Reset scaling
|
// Reset scaling
|
||||||
for (i in 0 until childCount) {
|
for (i in 0 until childCount) {
|
||||||
val child = getChildAt(i)
|
val child = getChildAt(i)
|
||||||
child?.translationX = 0f
|
|
||||||
child?.scaleX = 1f
|
child?.scaleX = 1f
|
||||||
child?.scaleY = 1f
|
child?.scaleY = 1f
|
||||||
child?.alpha = 1f
|
child?.alpha = 1f
|
||||||
|
|||||||
@@ -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:clipChildren="true"
|
||||||
android:layout_margin="0dp"
|
android:layout_margin="0dp"
|
||||||
app:cardBackgroundColor="@color/eden_card_background"
|
app:cardBackgroundColor="@color/eden_card_background"
|
||||||
app:rippleColor="@color/game_card_ripple"
|
|
||||||
app:strokeWidth="1dp"
|
app:strokeWidth="1dp"
|
||||||
app:strokeColor="@color/eden_border">
|
app:strokeColor="@color/eden_border">
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
app:cardCornerRadius="16dp"
|
app:cardCornerRadius="16dp"
|
||||||
app:cardPreventCornerOverlap="true"
|
app:cardPreventCornerOverlap="true"
|
||||||
android:clipChildren="true"
|
android:clipChildren="true"
|
||||||
app:rippleColor="@color/game_card_ripple"
|
|
||||||
android:layout_margin="4dp">
|
android:layout_margin="4dp">
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:transitionName="card_game"
|
android:transitionName="card_game"
|
||||||
app:cardCornerRadius="16dp"
|
app:cardCornerRadius="16dp"
|
||||||
app:rippleColor="@color/game_card_ripple"
|
|
||||||
android:foreground="@color/eden_border_gradient_start">
|
android:foreground="@color/eden_border_gradient_start">
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:transitionName="card_game_compact"
|
android:transitionName="card_game_compact"
|
||||||
app:cardCornerRadius="16dp"
|
app:cardCornerRadius="16dp"
|
||||||
app:rippleColor="@color/game_card_ripple"
|
|
||||||
android:foreground="@color/eden_border_gradient_start">
|
android:foreground="@color/eden_border_gradient_start">
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
app:cardCornerRadius="16dp"
|
app:cardCornerRadius="16dp"
|
||||||
app:cardElevation="0dp"
|
app:cardElevation="0dp"
|
||||||
app:cardBackgroundColor="@android:color/transparent"
|
app:cardBackgroundColor="@android:color/transparent"
|
||||||
app:rippleColor="@color/game_card_ripple"
|
|
||||||
app:strokeWidth="0dp">
|
app:strokeWidth="0dp">
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
|||||||
@@ -1107,6 +1107,7 @@
|
|||||||
<string name="theme_mode_light">فاتح</string>
|
<string name="theme_mode_light">فاتح</string>
|
||||||
<string name="theme_mode_dark">داكن</string>
|
<string name="theme_mode_dark">داكن</string>
|
||||||
|
|
||||||
|
<string name="multiplier_none">لا شيء</string>
|
||||||
|
|
||||||
<!-- Black backgrounds theme -->
|
<!-- Black backgrounds theme -->
|
||||||
<string name="use_black_backgrounds">خلفيات سوداء</string>
|
<string name="use_black_backgrounds">خلفيات سوداء</string>
|
||||||
|
|||||||
@@ -991,6 +991,7 @@ Wirklich fortfahren?</string>
|
|||||||
<string name="theme_mode_light">Hell</string>
|
<string name="theme_mode_light">Hell</string>
|
||||||
<string name="theme_mode_dark">Dunkel</string>
|
<string name="theme_mode_dark">Dunkel</string>
|
||||||
|
|
||||||
|
<string name="multiplier_none">Keine</string>
|
||||||
|
|
||||||
<!-- Black backgrounds theme -->
|
<!-- Black backgrounds theme -->
|
||||||
<string name="use_black_backgrounds">Schwarze Hintergründe</string>
|
<string name="use_black_backgrounds">Schwarze Hintergründe</string>
|
||||||
|
|||||||
@@ -1092,6 +1092,7 @@
|
|||||||
<string name="theme_mode_light">Claro</string>
|
<string name="theme_mode_light">Claro</string>
|
||||||
<string name="theme_mode_dark">Oscuro</string>
|
<string name="theme_mode_dark">Oscuro</string>
|
||||||
|
|
||||||
|
<string name="multiplier_none">Nada</string>
|
||||||
|
|
||||||
<!-- Black backgrounds theme -->
|
<!-- Black backgrounds theme -->
|
||||||
<string name="use_black_backgrounds">Fondos oscuros</string>
|
<string name="use_black_backgrounds">Fondos oscuros</string>
|
||||||
|
|||||||
@@ -808,6 +808,9 @@
|
|||||||
<string name="multiplier_x4">x4</string>
|
<string name="multiplier_x4">x4</string>
|
||||||
<string name="multiplier_x8">x8</string>
|
<string name="multiplier_x8">x8</string>
|
||||||
<string name="multiplier_x16">x16</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 -->
|
<!-- Black backgrounds theme -->
|
||||||
<string name="use_black_backgrounds">پسزمینه مشکی</string>
|
<string name="use_black_backgrounds">پسزمینه مشکی</string>
|
||||||
|
|||||||
@@ -1004,6 +1004,7 @@
|
|||||||
<string name="theme_mode_light">Lumineux</string>
|
<string name="theme_mode_light">Lumineux</string>
|
||||||
<string name="theme_mode_dark">Sombre</string>
|
<string name="theme_mode_dark">Sombre</string>
|
||||||
|
|
||||||
|
<string name="multiplier_none">Aucun</string>
|
||||||
|
|
||||||
<!-- Black backgrounds theme -->
|
<!-- Black backgrounds theme -->
|
||||||
<string name="use_black_backgrounds">Arrière-plan noir</string>
|
<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_light">Jasny</string>
|
||||||
<string name="theme_mode_dark">Ciemny</string>
|
<string name="theme_mode_dark">Ciemny</string>
|
||||||
|
|
||||||
|
<string name="multiplier_none">Brak</string>
|
||||||
|
|
||||||
<!-- Black backgrounds theme -->
|
<!-- Black backgrounds theme -->
|
||||||
<string name="use_black_backgrounds">Czarne tła</string>
|
<string name="use_black_backgrounds">Czarne tła</string>
|
||||||
|
|||||||
@@ -891,6 +891,7 @@
|
|||||||
<string name="theme_mode_light">Claro</string>
|
<string name="theme_mode_light">Claro</string>
|
||||||
<string name="theme_mode_dark">Escuro</string>
|
<string name="theme_mode_dark">Escuro</string>
|
||||||
|
|
||||||
|
<string name="multiplier_none">Nenhum</string>
|
||||||
|
|
||||||
<!-- Black backgrounds theme -->
|
<!-- Black backgrounds theme -->
|
||||||
<string name="use_black_backgrounds">Planos de fundo pretos</string>
|
<string name="use_black_backgrounds">Planos de fundo pretos</string>
|
||||||
|
|||||||
@@ -1071,6 +1071,7 @@
|
|||||||
<string name="theme_mode_light">Светлая</string>
|
<string name="theme_mode_light">Светлая</string>
|
||||||
<string name="theme_mode_dark">Темная</string>
|
<string name="theme_mode_dark">Темная</string>
|
||||||
|
|
||||||
|
<string name="multiplier_none">Отключено</string>
|
||||||
|
|
||||||
<!-- Black backgrounds theme -->
|
<!-- Black backgrounds theme -->
|
||||||
<string name="use_black_backgrounds">Чёрный фон</string>
|
<string name="use_black_backgrounds">Чёрный фон</string>
|
||||||
|
|||||||
@@ -1053,6 +1053,7 @@
|
|||||||
<string name="theme_mode_light">Світла</string>
|
<string name="theme_mode_light">Світла</string>
|
||||||
<string name="theme_mode_dark">Темна</string>
|
<string name="theme_mode_dark">Темна</string>
|
||||||
|
|
||||||
|
<string name="multiplier_none">Жодного</string>
|
||||||
|
|
||||||
<!-- Black backgrounds theme -->
|
<!-- Black backgrounds theme -->
|
||||||
<string name="use_black_backgrounds">Чорний фон</string>
|
<string name="use_black_backgrounds">Чорний фон</string>
|
||||||
|
|||||||
@@ -1081,6 +1081,7 @@
|
|||||||
<string name="theme_mode_light">浅色</string>
|
<string name="theme_mode_light">浅色</string>
|
||||||
<string name="theme_mode_dark">深色</string>
|
<string name="theme_mode_dark">深色</string>
|
||||||
|
|
||||||
|
<string name="multiplier_none">无</string>
|
||||||
|
|
||||||
<!-- Black backgrounds theme -->
|
<!-- Black backgrounds theme -->
|
||||||
<string name="use_black_backgrounds">使用黑色背景</string>
|
<string name="use_black_backgrounds">使用黑色背景</string>
|
||||||
|
|||||||
@@ -1006,6 +1006,7 @@
|
|||||||
<string name="theme_mode_light">淺色</string>
|
<string name="theme_mode_light">淺色</string>
|
||||||
<string name="theme_mode_dark">深色</string>
|
<string name="theme_mode_dark">深色</string>
|
||||||
|
|
||||||
|
<string name="multiplier_none">無</string>
|
||||||
|
|
||||||
<!-- Black backgrounds theme -->
|
<!-- Black backgrounds theme -->
|
||||||
<string name="use_black_backgrounds">黑色背景</string>
|
<string name="use_black_backgrounds">黑色背景</string>
|
||||||
|
|||||||
@@ -111,38 +111,43 @@
|
|||||||
<item>1</item>
|
<item>1</item>
|
||||||
</integer-array>
|
</integer-array>
|
||||||
|
|
||||||
|
<!-- VRAM USAGE MODE CHOICES -->
|
||||||
<string-array name="vramUsageMethodNames">
|
<string-array name="vramUsageMethodNames">
|
||||||
<item>@string/vram_usage_conservative</item>
|
<item>@string/vram_usage_conservative</item>
|
||||||
<item>@string/vram_usage_aggressive</item>
|
<item>@string/vram_usage_aggressive</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<!-- VRAM USAGE MODE VALUES -->
|
||||||
<integer-array name="vramUsageMethodValues">
|
<integer-array name="vramUsageMethodValues">
|
||||||
<item>0</item>
|
<item>0</item> <!-- Conservative -->
|
||||||
<item>1</item>
|
<item>1</item> <!-- Aggressive -->
|
||||||
</integer-array>
|
</integer-array>
|
||||||
|
|
||||||
|
<!-- ASTC Decoding Method Choices -->
|
||||||
<string-array name="astcDecodingMethodNames">
|
<string-array name="astcDecodingMethodNames">
|
||||||
<item>@string/accelerate_astc_cpu</item>
|
<item>@string/accelerate_astc_cpu</item>
|
||||||
<item>@string/accelerate_astc_gpu</item>
|
<item>@string/accelerate_astc_gpu</item>
|
||||||
<item>@string/accelerate_astc_async</item>
|
<item>@string/accelerate_astc_async</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<!-- ASTC Decoding Method Values -->
|
||||||
<integer-array name="astcDecodingMethodValues">
|
<integer-array name="astcDecodingMethodValues">
|
||||||
<item>0</item>
|
<item>0</item> <!-- CPU -->
|
||||||
<item>1</item>
|
<item>1</item> <!-- GPU -->
|
||||||
<item>2</item>
|
<item>2</item> <!-- CPU Asynchronously -->
|
||||||
</integer-array>
|
</integer-array>
|
||||||
|
<!-- NVDEC Emulation Choices -->
|
||||||
<string-array name="rendererNvdecNames">
|
<string-array name="rendererNvdecNames">
|
||||||
<item>@string/nvdec_emulation_none</item>
|
<item>@string/nvdec_emulation_none</item> <!-- Off -->
|
||||||
<item>@string/nvdec_emulation_cpu</item>
|
<item>@string/nvdec_emulation_cpu</item> <!-- Cpu -->
|
||||||
<item>@string/nvdec_emulation_gpu</item>
|
<item>@string/nvdec_emulation_gpu</item> <!-- Gpu -->
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<!-- NVDEC Emulation Values -->
|
||||||
<integer-array name="rendererNvdecValues">
|
<integer-array name="rendererNvdecValues">
|
||||||
<item>0</item>
|
<item>3</item> <!-- Off value -->
|
||||||
<item>1</item>
|
<item>1</item> <!-- CPU value -->
|
||||||
<item>2</item>
|
<item>2</item> <!-- GPU value -->
|
||||||
</integer-array>
|
</integer-array>
|
||||||
|
|
||||||
<string-array name="rendererResolutionNames">
|
<string-array name="rendererResolutionNames">
|
||||||
@@ -508,6 +513,9 @@
|
|||||||
<item>@string/multiplier_x4</item>
|
<item>@string/multiplier_x4</item>
|
||||||
<item>@string/multiplier_x8</item>
|
<item>@string/multiplier_x8</item>
|
||||||
<item>@string/multiplier_x16</item>
|
<item>@string/multiplier_x16</item>
|
||||||
|
<item>@string/multiplier_x32</item>
|
||||||
|
<item>@string/multiplier_x64</item>
|
||||||
|
<item>@string/multiplier_none</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<integer-array name="anisoValues">
|
<integer-array name="anisoValues">
|
||||||
<item>0</item>
|
<item>0</item>
|
||||||
@@ -516,6 +524,9 @@
|
|||||||
<item>3</item>
|
<item>3</item>
|
||||||
<item>4</item>
|
<item>4</item>
|
||||||
<item>5</item>
|
<item>5</item>
|
||||||
|
<item>6</item>
|
||||||
|
<item>7</item>
|
||||||
|
<item>8</item>
|
||||||
</integer-array>
|
</integer-array>
|
||||||
|
|
||||||
<string-array name="verticalAlignmentEntries">
|
<string-array name="verticalAlignmentEntries">
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
<integer name="game_columns_grid">2</integer>
|
<integer name="game_columns_grid">2</integer>
|
||||||
<integer name="carousel_max_fling_count">4</integer>
|
<integer name="carousel_max_fling_count">4</integer>
|
||||||
<integer name="carousel_focus_search_repeat_threshold_ms">100</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 -->
|
<!-- Default SWITCH landscape layout -->
|
||||||
<integer name="BUTTON_A_X">760</integer>
|
<integer name="BUTTON_A_X">760</integer>
|
||||||
|
|||||||
@@ -111,7 +111,7 @@
|
|||||||
|
|
||||||
<!-- NVDEC Emulation -->
|
<!-- NVDEC Emulation -->
|
||||||
<string name="nvdec_emulation">NVDEC Emulation</string>
|
<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_cpu" translatable="false">CPU</string>
|
||||||
<string name="nvdec_emulation_gpu" translatable="false">GPU</string>
|
<string name="nvdec_emulation_gpu" translatable="false">GPU</string>
|
||||||
<string name="nvdec_emulation_none">None</string>
|
<string name="nvdec_emulation_none">None</string>
|
||||||
@@ -1246,6 +1246,9 @@
|
|||||||
<string name="multiplier_x4" translatable="false">x4</string>
|
<string name="multiplier_x4" translatable="false">x4</string>
|
||||||
<string name="multiplier_x8" translatable="false">x8</string>
|
<string name="multiplier_x8" translatable="false">x8</string>
|
||||||
<string name="multiplier_x16" translatable="false">x16</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 -->
|
<!-- Black backgrounds theme -->
|
||||||
<string name="use_black_backgrounds">Black backgrounds</string>
|
<string name="use_black_backgrounds">Black backgrounds</string>
|
||||||
|
|||||||
+45
-56
@@ -39,19 +39,6 @@
|
|||||||
|
|
||||||
namespace Common::Log {
|
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 {
|
namespace {
|
||||||
|
|
||||||
/// @brief Returns the name of the passed log class as a C-string. Subclasses are separated by periods
|
/// @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 :)
|
// Some IDEs prefer <file>:<line> instead, so let's just do that :)
|
||||||
std::string FormatLogMessage(const Entry& entry) noexcept {
|
std::string FormatLogMessage(const Entry& entry) noexcept {
|
||||||
if (!entry.filename) return "";
|
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 time_fractional = uint32_t(entry.timestamp.count() % 1000000);
|
||||||
auto const class_name = GetLogClassName(entry.log_class);
|
auto const class_name = GetLogClassName(entry.log_class);
|
||||||
auto const level_name = GetLevelName(entry.log_level);
|
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>
|
template <typename It>
|
||||||
Level GetLevelByName(const It begin, const It end) {
|
Level GetLevelByName(const It begin, const It end) {
|
||||||
for (u32 i = 0; i < u32(Level::Count); ++i) {
|
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);
|
instance.SetClassLevel(log_class, level);
|
||||||
return true;
|
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
|
/// @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 :)
|
/// 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);
|
SetConsoleTextAttribute(console_handle, color);
|
||||||
auto const df = GetDirectFormatArgs(entry);
|
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 {}
|
void Flush() noexcept override {}
|
||||||
@@ -211,24 +220,22 @@ struct ColorConsoleBackend final : public Backend {
|
|||||||
~ColorConsoleBackend() noexcept override {}
|
~ColorConsoleBackend() noexcept override {}
|
||||||
void Write(const Entry& entry) noexcept override {
|
void Write(const Entry& entry) noexcept override {
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
|
#define ESC "\x1b"
|
||||||
auto const color_str = [&entry]() -> const char* {
|
auto const color_str = [&entry]() -> const char* {
|
||||||
switch (entry.log_level) {
|
switch (entry.log_level) {
|
||||||
case Level::Debug: return "[0;36m"; // Cyan
|
#define CCB_MAKE_COLOR_FMT(X) ESC X CCB_PRINTF_FMT ESC "[0m\n"
|
||||||
case Level::Info: return "[0;37m"; // Bright gray
|
case Level::Debug: return CCB_MAKE_COLOR_FMT("[0;36m"); // Cyan
|
||||||
case Level::Warning: return "[1;33m"; // Bright yellow
|
case Level::Info: return CCB_MAKE_COLOR_FMT("[0;37m"); // Bright gray
|
||||||
case Level::Error: return "[1;31m"; // Bright red
|
case Level::Warning: return CCB_MAKE_COLOR_FMT("[1;33m"); // Bright yellow
|
||||||
case Level::Critical: return "[1;35m"; // Bright magenta
|
case Level::Error: return CCB_MAKE_COLOR_FMT("[1;31m"); // Bright red
|
||||||
default: return "[1;30m"; // Grey
|
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);
|
auto const df = GetDirectFormatArgs(entry);
|
||||||
// more restrictive, because take for example this simple prelude:
|
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());
|
||||||
// [ 50.872256] Config <Info> common/settings.cpp:142:LogSettings:
|
#undef ESC
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void Flush() noexcept override {}
|
void Flush() noexcept override {}
|
||||||
@@ -239,7 +246,7 @@ struct ColorConsoleBackend final : public Backend {
|
|||||||
#ifndef __OPENORBIS__
|
#ifndef __OPENORBIS__
|
||||||
/// @brief Backend that writes to a file passed into the constructor
|
/// @brief Backend that writes to a file passed into the constructor
|
||||||
struct FileBackend final : public Backend {
|
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;
|
auto old_filename = filename;
|
||||||
old_filename += ".old.txt";
|
old_filename += ".old.txt";
|
||||||
// Existence checks are done within the functions themselves.
|
// Existence checks are done within the functions themselves.
|
||||||
@@ -254,7 +261,7 @@ struct FileBackend final : public Backend {
|
|||||||
if (!enabled)
|
if (!enabled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto message = FormatLogMessage(entry);
|
auto message = FormatLogMessage(entry).append(1, '\n');
|
||||||
#ifndef __ANDROID__
|
#ifndef __ANDROID__
|
||||||
if (Settings::values.censor_username.GetValue()) {
|
if (Settings::values.censor_username.GetValue()) {
|
||||||
// This must be a static otherwise it would get checked on EVERY
|
// 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 {
|
static std::string username = []() -> std::string {
|
||||||
// in order of precedence
|
// in order of precedence
|
||||||
// LOGNAME usually works on UNIX, USERNAME on Windows
|
// 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", })
|
for (auto const var : { "LOGNAME", "USERNAME", "USER", })
|
||||||
if (auto const s = ::getenv(var); s != nullptr)
|
if (auto const s = ::getenv(var); s != nullptr)
|
||||||
return std::string{s};
|
return std::string{s};
|
||||||
@@ -272,7 +280,7 @@ struct FileBackend final : public Backend {
|
|||||||
boost::replace_all(message, username, "user");
|
boost::replace_all(message, username, "user");
|
||||||
}
|
}
|
||||||
#endif
|
#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
|
// Option to log each line rather than 4k buffers
|
||||||
if (Settings::values.log_flush_line.GetValue())
|
if (Settings::values.log_flush_line.GetValue())
|
||||||
@@ -300,13 +308,14 @@ private:
|
|||||||
bool enabled = true;
|
bool enabled = true;
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
/// @brief Backend that writes to Visual Studio's output window
|
/// @brief Backend that writes to Visual Studio's output window
|
||||||
struct DebuggerBackend final : public Backend {
|
struct DebuggerBackend final : public Backend {
|
||||||
explicit DebuggerBackend() noexcept = default;
|
explicit DebuggerBackend() noexcept = default;
|
||||||
~DebuggerBackend() noexcept override = default;
|
~DebuggerBackend() noexcept override = default;
|
||||||
void Write(const Entry& entry) noexcept override {
|
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 {}
|
void Flush() noexcept override {}
|
||||||
};
|
};
|
||||||
@@ -329,7 +338,7 @@ struct LogcatBackend : public Backend {
|
|||||||
}
|
}
|
||||||
}();
|
}();
|
||||||
auto const df = GetDirectFormatArgs(entry);
|
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 {}
|
void Flush() noexcept override {}
|
||||||
};
|
};
|
||||||
@@ -368,23 +377,7 @@ struct Impl {
|
|||||||
#endif
|
#endif
|
||||||
std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()};
|
std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()};
|
||||||
};
|
};
|
||||||
} // Anonymous namespace
|
} // 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Constructor shall NOT depend upon Settings() or whatever
|
// Constructor shall NOT depend upon Settings() or whatever
|
||||||
// it's ran at global static ctor() time... so BE CAREFUL MFER!
|
// 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) {
|
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)) {
|
if (logging_instance && logging_instance->filter.CheckMessage(log_class, log_level)) {
|
||||||
auto const flush = ::Settings::values.log_flush_line.GetValue();
|
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) {
|
logging_instance->ForEachBackend([=](Backend& backend) {
|
||||||
backend.Write(Entry{
|
backend.Write(Entry{
|
||||||
.message = buffer,
|
.message = fmt::vformat(format, args),
|
||||||
.message_len = (std::min)(result.size, sizeof(buffer) - 1),
|
|
||||||
.timestamp = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - logging_instance->time_origin),
|
.timestamp = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - logging_instance->time_origin),
|
||||||
.log_class = log_class,
|
.log_class = log_class,
|
||||||
.log_level = log_level,
|
.log_level = log_level,
|
||||||
|
|||||||
@@ -140,4 +140,25 @@ void Stop();
|
|||||||
void SetGlobalFilter(const Filter& filter);
|
void SetGlobalFilter(const Filter& filter);
|
||||||
void SetColorConsoleBackendEnabled(bool enabled);
|
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
|
} // namespace Common::Log
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ std::optional<std::string> MakeRequest(const std::string& url, const std::string
|
|||||||
response.status);
|
response.status);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
if (!response.has_header("content-type")) {
|
if (!response.headers.contains("content-type")) {
|
||||||
LOG_ERROR(Common, "GET to {}{} returned no content", url, path);
|
LOG_ERROR(Common, "GET to {}{} returned no content", url, path);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,9 +132,11 @@ void LogSettings() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LOG_INFO(Config, "Eden Configuration:");
|
|
||||||
|
std::string settings_str{};
|
||||||
for (auto const& e : settings_list)
|
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) \
|
#define LOG_PATH(NAME) \
|
||||||
LOG_INFO(Config, #NAME ": {}", Common::FS::PathToUTF8String(Common::FS::GetEdenPath(Common::FS::EdenPath::NAME)))
|
LOG_INFO(Config, #NAME ": {}", Common::FS::PathToUTF8String(Common::FS::GetEdenPath(Common::FS::EdenPath::NAME)))
|
||||||
LOG_PATH(CacheDir);
|
LOG_PATH(CacheDir);
|
||||||
|
|||||||
@@ -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,
|
GmtPlusZero, GmtMinusZero, GmtZero, Greenwich, Hongkong, Hst, Iceland, Iran, Israel, Jamaica,
|
||||||
Japan, Kwajalein, Libya, Met, Mst, Mst7Mdt, Navajo, Nz, NzChat, Poland, Portugal, Prc, Pst8Pdt,
|
Japan, Kwajalein, Libya, Met, Mst, Mst7Mdt, Navajo, Nz, NzChat, Poland, Portugal, Prc, Pst8Pdt,
|
||||||
Roc, Rok, Singapore, Turkey, Uct, Universal, Utc, WSu, Wet, Zulu);
|
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(AstcDecodeMode, Cpu, Gpu, CpuAsynchronous);
|
||||||
ENUM(AstcRecompression, Uncompressed, Bc1, Bc3);
|
ENUM(AstcRecompression, Uncompressed, Bc1, Bc3);
|
||||||
ENUM(FramePacingMode, Target_Auto, Target_30, Target_60, Target_90, Target_120);
|
ENUM(FramePacingMode, Target_Auto, Target_30, Target_60, Target_90, Target_120);
|
||||||
|
|||||||
@@ -1267,23 +1267,6 @@ if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64 OR ARCHITECTURE_riscv64 OR ARCHITE
|
|||||||
target_link_libraries(core PRIVATE dynarmic::dynarmic)
|
target_link_libraries(core PRIVATE dynarmic::dynarmic)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_sources(core PRIVATE hle/service/ssl/ssl_backend_openssl.cpp)
|
|
||||||
|
|
||||||
target_link_libraries(core PRIVATE OpenSSL::SSL OpenSSL::Crypto)
|
target_link_libraries(core PRIVATE OpenSSL::SSL OpenSSL::Crypto)
|
||||||
|
|
||||||
# TODO
|
|
||||||
|
|
||||||
# elseif (APPLE)
|
|
||||||
# target_sources(core PRIVATE
|
|
||||||
# hle/service/ssl/ssl_backend_securetransport.cpp)
|
|
||||||
# target_link_libraries(core PRIVATE "-framework Security")
|
|
||||||
# elseif (WIN32)
|
|
||||||
# target_sources(core PRIVATE
|
|
||||||
# hle/service/ssl/ssl_backend_schannel.cpp)
|
|
||||||
# target_link_libraries(core PRIVATE crypt32 secur32)
|
|
||||||
# else()
|
|
||||||
# target_sources(core PRIVATE
|
|
||||||
# hle/service/ssl/ssl_backend_none.cpp)
|
|
||||||
# endif()
|
|
||||||
|
|
||||||
create_target_directory_groups(core)
|
create_target_directory_groups(core)
|
||||||
|
|||||||
@@ -286,7 +286,6 @@ void ArmDynarmic32::MakeJit(Common::PageTable* page_table) {
|
|||||||
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
|
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
|
||||||
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreStandardFPCRValue;
|
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreStandardFPCRValue;
|
||||||
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN;
|
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN;
|
||||||
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreGlobalMonitor;
|
|
||||||
break;
|
break;
|
||||||
// Paranoia mode for debugging optimizations
|
// Paranoia mode for debugging optimizations
|
||||||
case Settings::CpuAccuracy::Paranoid:
|
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.unsafe_optimizations = true;
|
||||||
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
|
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
|
||||||
config.fastmem_address_space_bits = 64;
|
config.fastmem_address_space_bits = 64;
|
||||||
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreGlobalMonitor;
|
|
||||||
break;
|
break;
|
||||||
// Paranoia mode for debugging optimizations
|
// Paranoia mode for debugging optimizations
|
||||||
case Settings::CpuAccuracy::Paranoid:
|
case Settings::CpuAccuracy::Paranoid:
|
||||||
|
|||||||
@@ -5,136 +5,33 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <mutex>
|
#include <ctime>
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/hle/kernel/k_event.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/ipc_helpers.h"
|
||||||
#include "core/hle/service/kernel_helpers.h"
|
#include "core/hle/service/kernel_helpers.h"
|
||||||
#include "core/hle/service/nim/nim.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/server_manager.h"
|
||||||
#include "core/hle/service/service.h"
|
#include "core/hle/service/service.h"
|
||||||
|
|
||||||
namespace Service::NIM {
|
namespace Service::NIM {
|
||||||
|
|
||||||
class IShopServiceAsync final : public ServiceFramework<IShopServiceAsync> {
|
class IShopServiceAsync final : public ServiceFramework<IShopServiceAsync> {
|
||||||
public:
|
public:
|
||||||
explicit IShopServiceAsync(Core::System& system_)
|
explicit IShopServiceAsync(Core::System& system_)
|
||||||
: ServiceFramework{system_, "IShopServiceAsync"},
|
: ServiceFramework{system_, "IShopServiceAsync"} {
|
||||||
service_context{system_, "IShopServiceAsync"} {
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
static const FunctionInfo functions[] = {
|
static const FunctionInfo functions[] = {
|
||||||
{0, D<&IShopServiceAsync::Cancel>, "Cancel"},
|
{0, nullptr, "Cancel"},
|
||||||
{1, D<&IShopServiceAsync::GetSize>, "GetSize"},
|
{1, nullptr, "GetSize"},
|
||||||
{2, D<&IShopServiceAsync::Read>, "Read"},
|
{2, nullptr, "Read"},
|
||||||
{3, D<&IShopServiceAsync::GetErrorCode>, "GetErrorCode"},
|
{3, nullptr, "GetErrorCode"},
|
||||||
{4, D<&IShopServiceAsync::Request>, "Request"},
|
{4, nullptr, "Request"},
|
||||||
{5, D<&IShopServiceAsync::Prepare>, "Prepare"},
|
{5, nullptr, "Prepare"},
|
||||||
};
|
};
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
RegisterHandlers(functions);
|
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:
|
private:
|
||||||
void CreateAsyncInterface(HLERequestContext& ctx) {LOG_DEBUG(Service_NIM, "called");
|
void CreateAsyncInterface(HLERequestContext& ctx) {
|
||||||
auto async_interface = std::make_shared<IShopServiceAsync>(system);
|
LOG_WARNING(Service_NIM, "(STUBBED) called");
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||||
IPC::ResponseBuilder rb{ctx, 2, 1, 1};
|
|
||||||
rb.Push(ResultSuccess);
|
rb.Push(ResultSuccess);
|
||||||
rb.PushCopyObjects(ctx, async_interface->GetEvent());
|
rb.PushIpcInterface<IShopServiceAsync>(ctx, system);
|
||||||
rb.PushIpcInterface<IShopServiceAsync>(ctx, std::move(async_interface));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -105,12 +105,7 @@ void LoopProcess(Core::System& system) {
|
|||||||
auto server_manager = std::make_unique<ServerManager>(system);
|
auto server_manager = std::make_unique<ServerManager>(system);
|
||||||
auto module = std::make_shared<Module>(system);
|
auto module = std::make_shared<Module>(system);
|
||||||
server_manager->RegisterNamedService("nvdrv", std::make_shared<NVDRV>(system, module, "nvdrv"));
|
server_manager->RegisterNamedService("nvdrv", std::make_shared<NVDRV>(system, module, "nvdrv"));
|
||||||
|
server_manager->RegisterNamedService("nvdrv:a", std::make_shared<NVDRV>(system, module, "nvdrv:a"));
|
||||||
const auto NvdrvInterfaceFactoryForApplets = [&, module] {
|
|
||||||
return std::make_shared<NVDRV>(system, module, "nvdrv:a");
|
|
||||||
};
|
|
||||||
|
|
||||||
server_manager->RegisterNamedService("nvdrv:a", NvdrvInterfaceFactoryForApplets);
|
|
||||||
server_manager->RegisterNamedService("nvdrv:s", std::make_shared<NVDRV>(system, module, "nvdrv:s"));
|
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("nvdrv:t", std::make_shared<NVDRV>(system, module, "nvdrv:t"));
|
||||||
server_manager->RegisterNamedService("nvmemp", std::make_shared<NVMEMP>(system));
|
server_manager->RegisterNamedService("nvmemp", std::make_shared<NVMEMP>(system));
|
||||||
|
|||||||
@@ -588,12 +588,7 @@ void LoopProcess(Core::System& system) {
|
|||||||
auto server_manager = std::make_unique<ServerManager>(system);
|
auto server_manager = std::make_unique<ServerManager>(system);
|
||||||
|
|
||||||
auto ro = std::make_shared<RoContext>();
|
auto ro = std::make_shared<RoContext>();
|
||||||
|
server_manager->RegisterNamedService("ldr:ro", std::make_shared<RoInterface>(system, "ldr:ro", ro, NrrKind::User));
|
||||||
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("ro:1", std::make_shared<RoInterface>(system, "ro:1", ro, NrrKind::JitPlugin));
|
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));
|
server_manager->RegisterNamedService("ro:dmnt", std::make_shared<IDebugMonitorInterface>(system));
|
||||||
ServerManager::RunServer(std::move(server_manager));
|
ServerManager::RunServer(std::move(server_manager));
|
||||||
|
|||||||
@@ -4,6 +4,18 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
#include <openssl/bio.h>
|
||||||
|
#include <openssl/err.h>
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
#include <openssl/x509.h>
|
||||||
|
#ifdef YUZU_BUNDLED_OPENSSL
|
||||||
|
#include <openssl/cert.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "common/fs/file.h"
|
||||||
|
#include "common/hex_util.h"
|
||||||
#include "common/string_util.h"
|
#include "common/string_util.h"
|
||||||
|
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
@@ -22,6 +34,377 @@
|
|||||||
|
|
||||||
namespace Service::SSL {
|
namespace Service::SSL {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::once_flag one_time_init_flag;
|
||||||
|
bool one_time_init_success = false;
|
||||||
|
SSL_CTX* ssl_ctx = nullptr;
|
||||||
|
BIO_METHOD* bio_meth = nullptr;
|
||||||
|
Common::FS::IOFile key_log_file; // only open if SSLKEYLOGFILE set in environment
|
||||||
|
|
||||||
|
Result CheckOpenSSLErrors();
|
||||||
|
void OneTimeInit();
|
||||||
|
void OneTimeInitLogFile();
|
||||||
|
bool OneTimeInitBIO();
|
||||||
|
|
||||||
|
#ifdef YUZU_BUNDLED_OPENSSL
|
||||||
|
// This is ported from httplib
|
||||||
|
struct scope_exit {
|
||||||
|
explicit scope_exit(std::function<void(void)> &&f)
|
||||||
|
: exit_function(std::move(f)), execute_on_destruction{true} {}
|
||||||
|
|
||||||
|
scope_exit(scope_exit &&rhs) noexcept
|
||||||
|
: exit_function(std::move(rhs.exit_function)),
|
||||||
|
execute_on_destruction{rhs.execute_on_destruction} {
|
||||||
|
rhs.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
~scope_exit() {
|
||||||
|
if (execute_on_destruction) { this->exit_function(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
void release() { this->execute_on_destruction = false; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
scope_exit(const scope_exit &) = delete;
|
||||||
|
void operator=(const scope_exit &) = delete;
|
||||||
|
scope_exit &operator=(scope_exit &&) = delete;
|
||||||
|
|
||||||
|
std::function<void(void)> exit_function;
|
||||||
|
bool execute_on_destruction;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline X509_STORE *CreateCaCertStore(const char *ca_cert,
|
||||||
|
std::size_t size) {
|
||||||
|
auto mem = BIO_new_mem_buf(ca_cert, static_cast<int>(size));
|
||||||
|
auto se = scope_exit([&] { BIO_free_all(mem); });
|
||||||
|
if (!mem) { return nullptr; }
|
||||||
|
|
||||||
|
auto inf = PEM_X509_INFO_read_bio(mem, nullptr, nullptr, nullptr);
|
||||||
|
if (!inf) { return nullptr; }
|
||||||
|
|
||||||
|
auto cts = X509_STORE_new();
|
||||||
|
if (cts) {
|
||||||
|
for (auto i = 0; i < static_cast<int>(sk_X509_INFO_num(inf)); i++) {
|
||||||
|
auto itmp = sk_X509_INFO_value(inf, i);
|
||||||
|
if (!itmp) { continue; }
|
||||||
|
|
||||||
|
if (itmp->x509) { X509_STORE_add_cert(cts, itmp->x509); }
|
||||||
|
if (itmp->crl) { X509_STORE_add_crl(cts, itmp->crl); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sk_X509_INFO_pop_free(inf, X509_INFO_free);
|
||||||
|
return cts;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void SetCaCertStore(SSL_CTX *ctx, X509_STORE *ca_cert_store) {
|
||||||
|
if (ca_cert_store) {
|
||||||
|
if (ctx) {
|
||||||
|
if (SSL_CTX_get_cert_store(ctx) != ca_cert_store) {
|
||||||
|
// Free memory allocated for old cert and use new store `ca_cert_store`
|
||||||
|
SSL_CTX_set_cert_store(ctx, ca_cert_store);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
X509_STORE_free(ca_cert_store);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void LoadCaCertStore(SSL_CTX* ctx, const char* ca_cert, std::size_t size)
|
||||||
|
{
|
||||||
|
SetCaCertStore(ctx, CreateCaCertStore(ca_cert, size));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
class SSLConnectionBackend final {
|
||||||
|
public:
|
||||||
|
Result Init() {
|
||||||
|
// on bundled OpenSSL, load ca cert store
|
||||||
|
#ifdef YUZU_BUNDLED_OPENSSL
|
||||||
|
LoadCaCertStore(ssl_ctx, kCert, sizeof(kCert));
|
||||||
|
#endif
|
||||||
|
std::call_once(one_time_init_flag, OneTimeInit);
|
||||||
|
|
||||||
|
if (!one_time_init_success) {
|
||||||
|
LOG_ERROR(Service_SSL, "Can't create SSL connection because OpenSSL one-time initialization failed");
|
||||||
|
return ResultInternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssl = SSL_new(ssl_ctx);
|
||||||
|
if (!ssl) {
|
||||||
|
LOG_ERROR(Service_SSL, "SSL_new failed");
|
||||||
|
return CheckOpenSSLErrors();
|
||||||
|
}
|
||||||
|
SSL_set_connect_state(ssl);
|
||||||
|
bio = BIO_new(bio_meth);
|
||||||
|
if (!bio) {
|
||||||
|
LOG_ERROR(Service_SSL, "BIO_new failed");
|
||||||
|
return CheckOpenSSLErrors();
|
||||||
|
}
|
||||||
|
BIO_set_data(bio, this);
|
||||||
|
BIO_set_init(bio, 1);
|
||||||
|
SSL_set_bio(ssl, bio, bio);
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result SetHostName(const std::string& hostname) {
|
||||||
|
if (!skip_cert_verification) {
|
||||||
|
if (!SSL_set1_host(ssl, hostname.c_str())) {
|
||||||
|
LOG_ERROR(Service_SSL, "SSL_set1_host({}) failed", hostname);
|
||||||
|
return CheckOpenSSLErrors();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!SSL_set_tlsext_host_name(ssl, hostname.c_str())) { // hostname for SNI
|
||||||
|
LOG_ERROR(Service_SSL, "SSL_set_tlsext_host_name({}) failed", hostname);
|
||||||
|
return CheckOpenSSLErrors();
|
||||||
|
}
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetVerifyOption(u32 option) {
|
||||||
|
skip_cert_verification = (option == 0);
|
||||||
|
LOG_WARNING(Service_SSL, "option={} skip_verification={}", option,
|
||||||
|
skip_cert_verification);
|
||||||
|
if (skip_cert_verification) {
|
||||||
|
SSL_set_verify(ssl, SSL_VERIFY_NONE, nullptr);
|
||||||
|
SSL_set1_host(ssl, nullptr);
|
||||||
|
SSL_set_hostflags(ssl, 0);
|
||||||
|
} else {
|
||||||
|
SSL_set_verify(ssl, SSL_VERIFY_PEER, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DoHandshake() {
|
||||||
|
SSL_set_verify_result(ssl, X509_V_OK);
|
||||||
|
const int ret = SSL_do_handshake(ssl);
|
||||||
|
|
||||||
|
if (!skip_cert_verification) {
|
||||||
|
const long verify_result = SSL_get_verify_result(ssl);
|
||||||
|
if (verify_result != X509_V_OK) {
|
||||||
|
LOG_ERROR(Service_SSL, "SSL cert verification failed because: {}",
|
||||||
|
X509_verify_cert_error_string(verify_result));
|
||||||
|
return CheckOpenSSLErrors();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret <= 0) {
|
||||||
|
const int ssl_err = SSL_get_error(ssl, ret);
|
||||||
|
if (ssl_err == SSL_ERROR_ZERO_RETURN ||
|
||||||
|
(ssl_err == SSL_ERROR_SYSCALL && got_read_eof)) {
|
||||||
|
LOG_ERROR(Service_SSL, "SSL handshake failed because server hung up");
|
||||||
|
return ResultInternalError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return HandleReturn("SSL_do_handshake", 0, ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result HandleReturn(const char* what, size_t* actual, int ret) {
|
||||||
|
const int ssl_err = SSL_get_error(ssl, ret);
|
||||||
|
CheckOpenSSLErrors();
|
||||||
|
switch (ssl_err) {
|
||||||
|
case SSL_ERROR_NONE:
|
||||||
|
return ResultSuccess;
|
||||||
|
case SSL_ERROR_ZERO_RETURN:
|
||||||
|
LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_ZERO_RETURN", what);
|
||||||
|
// DoHandshake special-cases this, but for Read and Write:
|
||||||
|
*actual = 0;
|
||||||
|
return ResultSuccess;
|
||||||
|
case SSL_ERROR_WANT_READ:
|
||||||
|
LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_WANT_READ", what);
|
||||||
|
return ResultWouldBlock;
|
||||||
|
case SSL_ERROR_WANT_WRITE:
|
||||||
|
LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_WANT_WRITE", what);
|
||||||
|
return ResultWouldBlock;
|
||||||
|
default:
|
||||||
|
if (ssl_err == SSL_ERROR_SYSCALL && got_read_eof) {
|
||||||
|
LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_SYSCALL because server hung up", what);
|
||||||
|
*actual = 0;
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
LOG_ERROR(Service_SSL, "{} => other SSL_get_error return value {}", what, ssl_err);
|
||||||
|
return ResultInternalError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~SSLConnectionBackend() {
|
||||||
|
// this is null-tolerant:
|
||||||
|
SSL_free(ssl);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void KeyLogCallback(const ::SSL* ssl, const char* line) {
|
||||||
|
std::string str(line);
|
||||||
|
str.push_back('\n');
|
||||||
|
// Do this in a single WriteString for atomicity if multiple instances
|
||||||
|
// are running on different threads (though that can't currently
|
||||||
|
// happen).
|
||||||
|
if (key_log_file.WriteString(str) != str.size() || !key_log_file.Flush()) {
|
||||||
|
LOG_CRITICAL(Service_SSL, "Failed to write to SSLKEYLOGFILE");
|
||||||
|
}
|
||||||
|
LOG_DEBUG(Service_SSL, "Wrote to SSLKEYLOGFILE: {}", line);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int WriteCallback(BIO* bio, const char* buf, size_t len, size_t* actual_p) {
|
||||||
|
auto self = static_cast<SSLConnectionBackend*>(BIO_get_data(bio));
|
||||||
|
ASSERT_OR_EXECUTE_MSG(
|
||||||
|
self->socket, { return 0; }, "OpenSSL asked to send but we have no socket");
|
||||||
|
BIO_clear_retry_flags(bio);
|
||||||
|
auto [actual, err] = self->socket->Send({reinterpret_cast<const u8*>(buf), len}, 0);
|
||||||
|
switch (err) {
|
||||||
|
case Network::Errno::SUCCESS:
|
||||||
|
*actual_p = actual;
|
||||||
|
return 1;
|
||||||
|
case Network::Errno::AGAIN:
|
||||||
|
BIO_set_flags(bio, BIO_FLAGS_WRITE | BIO_FLAGS_SHOULD_RETRY);
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
LOG_ERROR(Service_SSL, "Socket send returned Network::Errno {}", err);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ReadCallback(BIO* bio, char* buf, size_t len, size_t* actual_p) {
|
||||||
|
auto self = static_cast<SSLConnectionBackend*>(BIO_get_data(bio));
|
||||||
|
ASSERT_OR_EXECUTE_MSG(
|
||||||
|
self->socket, { return 0; }, "OpenSSL asked to recv but we have no socket");
|
||||||
|
BIO_clear_retry_flags(bio);
|
||||||
|
auto [actual, err] = self->socket->Recv(0, {reinterpret_cast<u8*>(buf), len});
|
||||||
|
switch (err) {
|
||||||
|
case Network::Errno::SUCCESS:
|
||||||
|
*actual_p = actual;
|
||||||
|
if (actual == 0) {
|
||||||
|
self->got_read_eof = true;
|
||||||
|
}
|
||||||
|
return actual ? 1 : 0;
|
||||||
|
case Network::Errno::AGAIN:
|
||||||
|
BIO_set_flags(bio, BIO_FLAGS_READ | BIO_FLAGS_SHOULD_RETRY);
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
LOG_ERROR(Service_SSL, "Socket recv returned Network::Errno {}", err);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static long CtrlCallback(BIO* bio, int cmd, long l_arg, void* p_arg) {
|
||||||
|
switch (cmd) {
|
||||||
|
case BIO_CTRL_FLUSH:
|
||||||
|
// Nothing to flush.
|
||||||
|
return 1;
|
||||||
|
case BIO_CTRL_PUSH:
|
||||||
|
case BIO_CTRL_POP:
|
||||||
|
#ifdef BIO_CTRL_GET_KTLS_SEND
|
||||||
|
case BIO_CTRL_GET_KTLS_SEND:
|
||||||
|
case BIO_CTRL_GET_KTLS_RECV:
|
||||||
|
#endif
|
||||||
|
// We don't support these operations, but don't bother logging them
|
||||||
|
// as they're nothing unusual.
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
LOG_DEBUG(Service_SSL, "OpenSSL BIO got ctrl({}, {}, {})", cmd, l_arg, p_arg);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::SSL* ssl = nullptr;
|
||||||
|
BIO* bio = nullptr;
|
||||||
|
bool got_read_eof = false;
|
||||||
|
bool skip_cert_verification = false;
|
||||||
|
std::shared_ptr<Network::SocketBase> socket;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend) {
|
||||||
|
auto conn = std::make_unique<SSLConnectionBackend>();
|
||||||
|
R_TRY(conn->Init());
|
||||||
|
*out_backend = std::move(conn);
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result CheckOpenSSLErrors() {
|
||||||
|
unsigned long rc;
|
||||||
|
const char* file;
|
||||||
|
int line;
|
||||||
|
const char* func;
|
||||||
|
const char* data;
|
||||||
|
int flags;
|
||||||
|
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
|
||||||
|
while ((rc = ERR_get_error_all(&file, &line, &func, &data, &flags)))
|
||||||
|
#else
|
||||||
|
// Can't get function names from OpenSSL on this version, so use mine:
|
||||||
|
func = __func__;
|
||||||
|
while ((rc = ERR_get_error_line_data(&file, &line, &data, &flags)))
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
std::string msg;
|
||||||
|
msg.resize(1024, '\0');
|
||||||
|
ERR_error_string_n(rc, msg.data(), msg.size());
|
||||||
|
msg.resize(strlen(msg.data()), '\0');
|
||||||
|
if (flags & ERR_TXT_STRING) {
|
||||||
|
msg.append(" | ");
|
||||||
|
msg.append(data);
|
||||||
|
}
|
||||||
|
Common::Log::FmtLogMessage(Common::Log::Class::Service_SSL, Common::Log::Level::Error,
|
||||||
|
file, line, func, "OpenSSL: {}",
|
||||||
|
msg);
|
||||||
|
}
|
||||||
|
return ResultInternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OneTimeInit() {
|
||||||
|
ssl_ctx = SSL_CTX_new(TLS_client_method());
|
||||||
|
if (!ssl_ctx) {
|
||||||
|
LOG_ERROR(Service_SSL, "SSL_CTX_new failed");
|
||||||
|
CheckOpenSSLErrors();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, nullptr);
|
||||||
|
|
||||||
|
if (!SSL_CTX_set_default_verify_paths(ssl_ctx)) {
|
||||||
|
LOG_ERROR(Service_SSL, "SSL_CTX_set_default_verify_paths failed");
|
||||||
|
CheckOpenSSLErrors();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
OneTimeInitLogFile();
|
||||||
|
|
||||||
|
if (!OneTimeInitBIO()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
one_time_init_success = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OneTimeInitLogFile() {
|
||||||
|
const char* logfile = getenv("SSLKEYLOGFILE");
|
||||||
|
if (logfile) {
|
||||||
|
key_log_file.Open(logfile, Common::FS::FileAccessMode::Append, Common::FS::FileType::TextFile, Common::FS::FileShareFlag::ShareWriteOnly);
|
||||||
|
if (key_log_file.IsOpen()) {
|
||||||
|
SSL_CTX_set_keylog_callback(ssl_ctx, &SSLConnectionBackend::KeyLogCallback);
|
||||||
|
} else {
|
||||||
|
LOG_CRITICAL(Service_SSL, "SSLKEYLOGFILE was set but file could not be opened; not logging keys!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OneTimeInitBIO() {
|
||||||
|
bio_meth =
|
||||||
|
BIO_meth_new(BIO_get_new_index() | BIO_TYPE_SOURCE_SINK, "SSLConnectionBackend");
|
||||||
|
if (!bio_meth ||
|
||||||
|
!BIO_meth_set_write_ex(bio_meth, &SSLConnectionBackend::WriteCallback) ||
|
||||||
|
!BIO_meth_set_read_ex(bio_meth, &SSLConnectionBackend::ReadCallback) ||
|
||||||
|
!BIO_meth_set_ctrl(bio_meth, &SSLConnectionBackend::CtrlCallback)) {
|
||||||
|
LOG_ERROR(Service_SSL, "Failed to create BIO_METHOD");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
// This is nn::ssl::sf::CertificateFormat
|
// This is nn::ssl::sf::CertificateFormat
|
||||||
enum class CertificateFormat : u32 {
|
enum class CertificateFormat : u32 {
|
||||||
Pem = 1,
|
Pem = 1,
|
||||||
@@ -162,20 +545,17 @@ private:
|
|||||||
|
|
||||||
auto const res_v = bsd->DuplicateSocketImpl(fd);
|
auto const res_v = bsd->DuplicateSocketImpl(fd);
|
||||||
if (auto *res = std::get_if<s32>(&res_v)) {
|
if (auto *res = std::get_if<s32>(&res_v)) {
|
||||||
const s32 duplicated_fd = *res;
|
const s32 dup_fd = *res;
|
||||||
if (do_not_close_socket) {
|
*out_fd = do_not_close_socket ? dup_fd : -1;
|
||||||
*out_fd = duplicated_fd;
|
if (!do_not_close_socket)
|
||||||
} else {
|
fd_to_close = dup_fd;
|
||||||
*out_fd = -1;
|
auto const sock = bsd->GetSocket(dup_fd);
|
||||||
fd_to_close = duplicated_fd;
|
|
||||||
}
|
|
||||||
std::optional<std::shared_ptr<Network::SocketBase>> sock = bsd->GetSocket(duplicated_fd);
|
|
||||||
if (!sock.has_value()) {
|
if (!sock.has_value()) {
|
||||||
LOG_ERROR(Service_SSL, "invalid socket fd {} after duplication", duplicated_fd);
|
LOG_ERROR(Service_SSL, "invalid socket fd {} after duplication", dup_fd);
|
||||||
return ResultInvalidSocket;
|
return ResultInvalidSocket;
|
||||||
}
|
}
|
||||||
socket = std::move(*sock);
|
socket = std::move(*sock);
|
||||||
backend->SetSocket(socket);
|
backend->socket = std::move(socket);
|
||||||
return ResultSuccess;
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
LOG_ERROR(Service_SSL, "Failed to duplicate socket with fd {}", fd);
|
LOG_ERROR(Service_SSL, "Failed to duplicate socket with fd {}", fd);
|
||||||
@@ -189,11 +569,11 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
Result SetVerifyOptionImpl(u32 option) {
|
Result SetVerifyOptionImpl(u32 option) {
|
||||||
ASSERT(!did_handshake);
|
|
||||||
LOG_DEBUG(Service_SSL, "called. option={} (forcing 0)", option);
|
LOG_DEBUG(Service_SSL, "called. option={} (forcing 0)", option);
|
||||||
|
ASSERT(!did_handshake);
|
||||||
verify_option = 0;
|
verify_option = 0;
|
||||||
backend->SetVerifyOption(0);
|
backend->SetVerifyOption(0);
|
||||||
return ResultSuccess;
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
Result SetIoModeImpl(u32 input_mode) {
|
Result SetIoModeImpl(u32 input_mode) {
|
||||||
@@ -206,13 +586,13 @@ private:
|
|||||||
if (error != Network::Errno::SUCCESS) {
|
if (error != Network::Errno::SUCCESS) {
|
||||||
LOG_ERROR(Service_SSL, "Failed to set native socket non-block flag to {}", non_block);
|
LOG_ERROR(Service_SSL, "Failed to set native socket non-block flag to {}", non_block);
|
||||||
}
|
}
|
||||||
return ResultSuccess;
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
Result SetSessionCacheModeImpl(u32 mode) {
|
Result SetSessionCacheModeImpl(u32 mode) {
|
||||||
ASSERT(!did_handshake);
|
ASSERT(!did_handshake);
|
||||||
LOG_WARNING(Service_SSL, "(STUBBED) called. value={}", mode);
|
LOG_WARNING(Service_SSL, "(STUBBED) called. value={}", mode);
|
||||||
return ResultSuccess;
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
Result DoHandshakeImpl() {
|
Result DoHandshakeImpl() {
|
||||||
@@ -234,19 +614,17 @@ private:
|
|||||||
};
|
};
|
||||||
if (!get_server_cert_chain) {
|
if (!get_server_cert_chain) {
|
||||||
// Just return the first one, unencoded.
|
// Just return the first one, unencoded.
|
||||||
ASSERT_OR_EXECUTE_MSG(
|
ASSERT_OR_EXECUTE_MSG(!certs.empty(), { return {}; }, "Should be at least one server cert");
|
||||||
!certs.empty(), { return {}; }, "Should be at least one server cert");
|
|
||||||
return certs[0];
|
return certs[0];
|
||||||
}
|
}
|
||||||
std::vector<u8> ret;
|
std::vector<u8> ret;
|
||||||
Header header{0x4E4D684374726543, static_cast<u32>(certs.size()), 0};
|
Header header{0x4E4D684374726543, u32(certs.size()), 0};
|
||||||
ret.insert(ret.end(), reinterpret_cast<u8*>(&header), reinterpret_cast<u8*>(&header + 1));
|
ret.insert(ret.end(), reinterpret_cast<u8*>(&header), reinterpret_cast<u8*>(&header + 1));
|
||||||
size_t data_offset = sizeof(Header) + certs.size() * sizeof(EntryHeader);
|
size_t data_offset = sizeof(Header) + certs.size() * sizeof(EntryHeader);
|
||||||
for (auto& cert : certs) {
|
for (auto& cert : certs) {
|
||||||
EntryHeader entry_header{static_cast<u32>(cert.size()), static_cast<u32>(data_offset)};
|
EntryHeader entry_header{u32(cert.size()), u32(data_offset)};
|
||||||
data_offset += cert.size();
|
data_offset += cert.size();
|
||||||
ret.insert(ret.end(), reinterpret_cast<u8*>(&entry_header),
|
ret.insert(ret.end(), reinterpret_cast<u8*>(&entry_header), reinterpret_cast<u8*>(&entry_header + 1));
|
||||||
reinterpret_cast<u8*>(&entry_header + 1));
|
|
||||||
}
|
}
|
||||||
for (auto& cert : certs) {
|
for (auto& cert : certs) {
|
||||||
ret.insert(ret.end(), cert.begin(), cert.end());
|
ret.insert(ret.end(), cert.begin(), cert.end());
|
||||||
@@ -257,7 +635,8 @@ private:
|
|||||||
Result ReadImpl(std::vector<u8>* out_data) {
|
Result ReadImpl(std::vector<u8>* out_data) {
|
||||||
ASSERT_OR_EXECUTE(did_handshake, { return ResultInternalError; });
|
ASSERT_OR_EXECUTE(did_handshake, { return ResultInternalError; });
|
||||||
size_t actual_size{};
|
size_t actual_size{};
|
||||||
Result res = backend->Read(&actual_size, *out_data);
|
const int ret = SSL_read_ex(backend->ssl, out_data->data(), out_data->size(), &actual_size);
|
||||||
|
Result res = backend->HandleReturn("SSL_read_ex", &actual_size, ret);
|
||||||
if (res != ResultSuccess) {
|
if (res != ResultSuccess) {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@@ -267,12 +646,13 @@ private:
|
|||||||
|
|
||||||
Result WriteImpl(size_t* out_size, std::span<const u8> data) {
|
Result WriteImpl(size_t* out_size, std::span<const u8> data) {
|
||||||
ASSERT_OR_EXECUTE(did_handshake, { return ResultInternalError; });
|
ASSERT_OR_EXECUTE(did_handshake, { return ResultInternalError; });
|
||||||
return backend->Write(out_size, data);
|
const int ret = SSL_write_ex(backend->ssl, data.data(), data.size(), out_size);
|
||||||
|
return backend->HandleReturn("SSL_write_ex", out_size, ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result PendingImpl(s32* out_pending) {
|
Result PendingImpl(s32* out_pending) {
|
||||||
LOG_WARNING(Service_SSL, "(STUBBED) called.");
|
LOG_WARNING(Service_SSL, "(STUBBED) called.");
|
||||||
*out_pending = 0;
|
*out_pending = SSL_pending(backend->ssl);
|
||||||
return ResultSuccess;
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,24 +706,39 @@ private:
|
|||||||
OutputParameters out{};
|
OutputParameters out{};
|
||||||
if (res == ResultSuccess) {
|
if (res == ResultSuccess) {
|
||||||
std::vector<std::vector<u8>> certs;
|
std::vector<std::vector<u8>> certs;
|
||||||
res = backend->GetServerCerts(&certs);
|
STACK_OF(X509)* chain = SSL_get_peer_cert_chain(backend->ssl);
|
||||||
if (res == ResultSuccess) {
|
if (chain) {
|
||||||
|
int count = sk_X509_num(chain);
|
||||||
|
ASSERT(count >= 0);
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
X509* x509 = sk_X509_value(chain, i);
|
||||||
|
ASSERT_OR_EXECUTE(x509 != nullptr, { continue; });
|
||||||
|
unsigned char* buf = nullptr;
|
||||||
|
int len = i2d_X509(x509, &buf);
|
||||||
|
ASSERT_OR_EXECUTE(len >= 0 && buf, { continue; });
|
||||||
|
certs.emplace_back(buf, buf + len);
|
||||||
|
OPENSSL_free(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
// succeed!
|
||||||
const std::vector<u8> certs_buf = SerializeServerCerts(certs);
|
const std::vector<u8> certs_buf = SerializeServerCerts(certs);
|
||||||
if (ctx.CanWriteBuffer()) {
|
if (ctx.CanWriteBuffer()) {
|
||||||
const size_t buffer_size = ctx.GetWriteBufferSize();
|
const size_t buffer_size = ctx.GetWriteBufferSize();
|
||||||
if (certs_buf.size() <= buffer_size) {
|
if (certs_buf.size() <= buffer_size) {
|
||||||
ctx.WriteBuffer(certs_buf);
|
ctx.WriteBuffer(certs_buf);
|
||||||
} else {
|
} else {
|
||||||
LOG_WARNING(Service_SSL, "Certificate buffer too small: {} bytes needed, {} bytes available",
|
LOG_WARNING(Service_SSL, "Certificate buffer too small: {} bytes needed, {} bytes available", certs_buf.size(), buffer_size);
|
||||||
certs_buf.size(), buffer_size);
|
|
||||||
ctx.WriteBuffer(std::span<const u8>(certs_buf.data(), buffer_size));
|
ctx.WriteBuffer(std::span<const u8>(certs_buf.data(), buffer_size));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LOG_DEBUG(Service_SSL, "No output buffer provided for certificates ({} bytes)", certs_buf.size());
|
LOG_DEBUG(Service_SSL, "No output buffer provided for certificates ({} bytes)", certs_buf.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
out.certs_count = static_cast<u32>(certs.size());
|
out.certs_count = u32(certs.size());
|
||||||
out.certs_size = static_cast<u32>(certs_buf.size());
|
out.certs_size = u32(certs_buf.size());
|
||||||
|
} else {
|
||||||
|
LOG_ERROR(Service_SSL, "SSL_get_peer_cert_chain returned nullptr");
|
||||||
|
res = ResultInternalError;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
IPC::ResponseBuilder rb{ctx, 4};
|
IPC::ResponseBuilder rb{ctx, 4};
|
||||||
|
|||||||
@@ -32,18 +32,4 @@ constexpr Result ResultInternalError{ErrorModule::SSLSrv, 999}; // made up
|
|||||||
// polling for read (with a timeout).
|
// polling for read (with a timeout).
|
||||||
constexpr Result ResultWouldBlock{ErrorModule::SSLSrv, 204};
|
constexpr Result ResultWouldBlock{ErrorModule::SSLSrv, 204};
|
||||||
|
|
||||||
class SSLConnectionBackend {
|
|
||||||
public:
|
|
||||||
virtual ~SSLConnectionBackend() {}
|
|
||||||
virtual void SetSocket(std::shared_ptr<Network::SocketBase> socket) = 0;
|
|
||||||
virtual Result SetHostName(const std::string& hostname) = 0;
|
|
||||||
virtual void SetVerifyOption(u32 option) = 0;
|
|
||||||
virtual Result DoHandshake() = 0;
|
|
||||||
virtual Result Read(size_t* out_size, std::span<u8> data) = 0;
|
|
||||||
virtual Result Write(size_t* out_size, std::span<const u8> data) = 0;
|
|
||||||
virtual Result GetServerCerts(std::vector<std::vector<u8>>* out_certs) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend);
|
|
||||||
|
|
||||||
} // namespace Service::SSL
|
} // namespace Service::SSL
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#include "common/logging.h"
|
|
||||||
|
|
||||||
#include "core/hle/service/ssl/ssl_backend.h"
|
|
||||||
|
|
||||||
namespace Service::SSL {
|
|
||||||
|
|
||||||
Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend) {
|
|
||||||
LOG_ERROR(Service_SSL,
|
|
||||||
"Can't create SSL connection because no SSL backend is available on this platform");
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Service::SSL
|
|
||||||
@@ -1,450 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
#include <openssl/bio.h>
|
|
||||||
#include <openssl/err.h>
|
|
||||||
#include <openssl/ssl.h>
|
|
||||||
#include <openssl/x509.h>
|
|
||||||
|
|
||||||
#include "common/fs/file.h"
|
|
||||||
#include "common/hex_util.h"
|
|
||||||
#include "common/string_util.h"
|
|
||||||
|
|
||||||
#include "core/hle/service/ssl/ssl_backend.h"
|
|
||||||
#include "core/internal_network/network.h"
|
|
||||||
#include "core/internal_network/sockets.h"
|
|
||||||
|
|
||||||
#ifdef YUZU_BUNDLED_OPENSSL
|
|
||||||
#include <openssl/cert.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
using namespace Common::FS;
|
|
||||||
|
|
||||||
namespace Service::SSL {
|
|
||||||
|
|
||||||
// Import OpenSSL's `SSL` type into the namespace. This is needed because the
|
|
||||||
// namespace is also named `SSL`.
|
|
||||||
using ::SSL;
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
std::once_flag one_time_init_flag;
|
|
||||||
bool one_time_init_success = false;
|
|
||||||
|
|
||||||
SSL_CTX* ssl_ctx;
|
|
||||||
IOFile key_log_file; // only open if SSLKEYLOGFILE set in environment
|
|
||||||
BIO_METHOD* bio_meth;
|
|
||||||
|
|
||||||
Result CheckOpenSSLErrors();
|
|
||||||
void OneTimeInit();
|
|
||||||
void OneTimeInitLogFile();
|
|
||||||
bool OneTimeInitBIO();
|
|
||||||
|
|
||||||
#ifdef YUZU_BUNDLED_OPENSSL
|
|
||||||
// This is ported from httplib
|
|
||||||
struct scope_exit {
|
|
||||||
explicit scope_exit(std::function<void(void)> &&f)
|
|
||||||
: exit_function(std::move(f)), execute_on_destruction{true} {}
|
|
||||||
|
|
||||||
scope_exit(scope_exit &&rhs) noexcept
|
|
||||||
: exit_function(std::move(rhs.exit_function)),
|
|
||||||
execute_on_destruction{rhs.execute_on_destruction} {
|
|
||||||
rhs.release();
|
|
||||||
}
|
|
||||||
|
|
||||||
~scope_exit() {
|
|
||||||
if (execute_on_destruction) { this->exit_function(); }
|
|
||||||
}
|
|
||||||
|
|
||||||
void release() { this->execute_on_destruction = false; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
scope_exit(const scope_exit &) = delete;
|
|
||||||
void operator=(const scope_exit &) = delete;
|
|
||||||
scope_exit &operator=(scope_exit &&) = delete;
|
|
||||||
|
|
||||||
std::function<void(void)> exit_function;
|
|
||||||
bool execute_on_destruction;
|
|
||||||
};
|
|
||||||
|
|
||||||
inline X509_STORE *CreateCaCertStore(const char *ca_cert,
|
|
||||||
std::size_t size) {
|
|
||||||
auto mem = BIO_new_mem_buf(ca_cert, static_cast<int>(size));
|
|
||||||
auto se = scope_exit([&] { BIO_free_all(mem); });
|
|
||||||
if (!mem) { return nullptr; }
|
|
||||||
|
|
||||||
auto inf = PEM_X509_INFO_read_bio(mem, nullptr, nullptr, nullptr);
|
|
||||||
if (!inf) { return nullptr; }
|
|
||||||
|
|
||||||
auto cts = X509_STORE_new();
|
|
||||||
if (cts) {
|
|
||||||
for (auto i = 0; i < static_cast<int>(sk_X509_INFO_num(inf)); i++) {
|
|
||||||
auto itmp = sk_X509_INFO_value(inf, i);
|
|
||||||
if (!itmp) { continue; }
|
|
||||||
|
|
||||||
if (itmp->x509) { X509_STORE_add_cert(cts, itmp->x509); }
|
|
||||||
if (itmp->crl) { X509_STORE_add_crl(cts, itmp->crl); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sk_X509_INFO_pop_free(inf, X509_INFO_free);
|
|
||||||
return cts;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void SetCaCertStore(SSL_CTX *ctx, X509_STORE *ca_cert_store) {
|
|
||||||
if (ca_cert_store) {
|
|
||||||
if (ctx) {
|
|
||||||
if (SSL_CTX_get_cert_store(ctx) != ca_cert_store) {
|
|
||||||
// Free memory allocated for old cert and use new store `ca_cert_store`
|
|
||||||
SSL_CTX_set_cert_store(ctx, ca_cert_store);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
X509_STORE_free(ca_cert_store);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void LoadCaCertStore(SSL_CTX* ctx, const char* ca_cert, std::size_t size)
|
|
||||||
{
|
|
||||||
SetCaCertStore(ctx, CreateCaCertStore(ca_cert, size));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
class SSLConnectionBackendOpenSSL final : public SSLConnectionBackend {
|
|
||||||
public:
|
|
||||||
Result Init() {
|
|
||||||
// on bundled OpenSSL, load ca cert store
|
|
||||||
#ifdef YUZU_BUNDLED_OPENSSL
|
|
||||||
LoadCaCertStore(ssl_ctx, kCert, sizeof(kCert));
|
|
||||||
#endif
|
|
||||||
std::call_once(one_time_init_flag, OneTimeInit);
|
|
||||||
|
|
||||||
if (!one_time_init_success) {
|
|
||||||
LOG_ERROR(Service_SSL,
|
|
||||||
"Can't create SSL connection because OpenSSL one-time initialization failed");
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
|
|
||||||
ssl = SSL_new(ssl_ctx);
|
|
||||||
if (!ssl) {
|
|
||||||
LOG_ERROR(Service_SSL, "SSL_new failed");
|
|
||||||
return CheckOpenSSLErrors();
|
|
||||||
}
|
|
||||||
|
|
||||||
SSL_set_connect_state(ssl);
|
|
||||||
|
|
||||||
bio = BIO_new(bio_meth);
|
|
||||||
if (!bio) {
|
|
||||||
LOG_ERROR(Service_SSL, "BIO_new failed");
|
|
||||||
return CheckOpenSSLErrors();
|
|
||||||
}
|
|
||||||
|
|
||||||
BIO_set_data(bio, this);
|
|
||||||
BIO_set_init(bio, 1);
|
|
||||||
SSL_set_bio(ssl, bio, bio);
|
|
||||||
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetSocket(std::shared_ptr<Network::SocketBase> socket_in) override {
|
|
||||||
socket = std::move(socket_in);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result SetHostName(const std::string& hostname) override {
|
|
||||||
if (!skip_cert_verification) {
|
|
||||||
if (!SSL_set1_host(ssl, hostname.c_str())) {
|
|
||||||
LOG_ERROR(Service_SSL, "SSL_set1_host({}) failed", hostname);
|
|
||||||
return CheckOpenSSLErrors();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!SSL_set_tlsext_host_name(ssl, hostname.c_str())) { // hostname for SNI
|
|
||||||
LOG_ERROR(Service_SSL, "SSL_set_tlsext_host_name({}) failed", hostname);
|
|
||||||
return CheckOpenSSLErrors();
|
|
||||||
}
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetVerifyOption(u32 option) override {
|
|
||||||
skip_cert_verification = (option == 0);
|
|
||||||
LOG_WARNING(Service_SSL, "option={} skip_verification={}", option,
|
|
||||||
skip_cert_verification);
|
|
||||||
if (skip_cert_verification) {
|
|
||||||
SSL_set_verify(ssl, SSL_VERIFY_NONE, nullptr);
|
|
||||||
SSL_set1_host(ssl, nullptr);
|
|
||||||
SSL_set_hostflags(ssl, 0);
|
|
||||||
} else {
|
|
||||||
SSL_set_verify(ssl, SSL_VERIFY_PEER, nullptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DoHandshake() override {
|
|
||||||
SSL_set_verify_result(ssl, X509_V_OK);
|
|
||||||
const int ret = SSL_do_handshake(ssl);
|
|
||||||
|
|
||||||
if (!skip_cert_verification) {
|
|
||||||
const long verify_result = SSL_get_verify_result(ssl);
|
|
||||||
if (verify_result != X509_V_OK) {
|
|
||||||
LOG_ERROR(Service_SSL, "SSL cert verification failed because: {}",
|
|
||||||
X509_verify_cert_error_string(verify_result));
|
|
||||||
return CheckOpenSSLErrors();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ret <= 0) {
|
|
||||||
const int ssl_err = SSL_get_error(ssl, ret);
|
|
||||||
if (ssl_err == SSL_ERROR_ZERO_RETURN ||
|
|
||||||
(ssl_err == SSL_ERROR_SYSCALL && got_read_eof)) {
|
|
||||||
LOG_ERROR(Service_SSL, "SSL handshake failed because server hung up");
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return HandleReturn("SSL_do_handshake", 0, ret);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result Read(size_t* out_size, std::span<u8> data) override {
|
|
||||||
const int ret = SSL_read_ex(ssl, data.data(), data.size(), out_size);
|
|
||||||
return HandleReturn("SSL_read_ex", out_size, ret);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result Write(size_t* out_size, std::span<const u8> data) override {
|
|
||||||
const int ret = SSL_write_ex(ssl, data.data(), data.size(), out_size);
|
|
||||||
return HandleReturn("SSL_write_ex", out_size, ret);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result HandleReturn(const char* what, size_t* actual, int ret) {
|
|
||||||
const int ssl_err = SSL_get_error(ssl, ret);
|
|
||||||
CheckOpenSSLErrors();
|
|
||||||
switch (ssl_err) {
|
|
||||||
case SSL_ERROR_NONE:
|
|
||||||
return ResultSuccess;
|
|
||||||
case SSL_ERROR_ZERO_RETURN:
|
|
||||||
LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_ZERO_RETURN", what);
|
|
||||||
// DoHandshake special-cases this, but for Read and Write:
|
|
||||||
*actual = 0;
|
|
||||||
return ResultSuccess;
|
|
||||||
case SSL_ERROR_WANT_READ:
|
|
||||||
LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_WANT_READ", what);
|
|
||||||
return ResultWouldBlock;
|
|
||||||
case SSL_ERROR_WANT_WRITE:
|
|
||||||
LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_WANT_WRITE", what);
|
|
||||||
return ResultWouldBlock;
|
|
||||||
default:
|
|
||||||
if (ssl_err == SSL_ERROR_SYSCALL && got_read_eof) {
|
|
||||||
LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_SYSCALL because server hung up", what);
|
|
||||||
*actual = 0;
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
LOG_ERROR(Service_SSL, "{} => other SSL_get_error return value {}", what, ssl_err);
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Result GetServerCerts(std::vector<std::vector<u8>>* out_certs) override {
|
|
||||||
STACK_OF(X509)* chain = SSL_get_peer_cert_chain(ssl);
|
|
||||||
if (!chain) {
|
|
||||||
LOG_ERROR(Service_SSL, "SSL_get_peer_cert_chain returned nullptr");
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
int count = sk_X509_num(chain);
|
|
||||||
ASSERT(count >= 0);
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
X509* x509 = sk_X509_value(chain, i);
|
|
||||||
ASSERT_OR_EXECUTE(x509 != nullptr, { continue; });
|
|
||||||
unsigned char* buf = nullptr;
|
|
||||||
int len = i2d_X509(x509, &buf);
|
|
||||||
ASSERT_OR_EXECUTE(len >= 0 && buf, { continue; });
|
|
||||||
out_certs->emplace_back(buf, buf + len);
|
|
||||||
OPENSSL_free(buf);
|
|
||||||
}
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
~SSLConnectionBackendOpenSSL() {
|
|
||||||
// this is null-tolerant:
|
|
||||||
SSL_free(ssl);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void KeyLogCallback(const SSL* ssl, const char* line) {
|
|
||||||
std::string str(line);
|
|
||||||
str.push_back('\n');
|
|
||||||
// Do this in a single WriteString for atomicity if multiple instances
|
|
||||||
// are running on different threads (though that can't currently
|
|
||||||
// happen).
|
|
||||||
if (key_log_file.WriteString(str) != str.size() || !key_log_file.Flush()) {
|
|
||||||
LOG_CRITICAL(Service_SSL, "Failed to write to SSLKEYLOGFILE");
|
|
||||||
}
|
|
||||||
LOG_DEBUG(Service_SSL, "Wrote to SSLKEYLOGFILE: {}", line);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int WriteCallback(BIO* bio, const char* buf, size_t len, size_t* actual_p) {
|
|
||||||
auto self = static_cast<SSLConnectionBackendOpenSSL*>(BIO_get_data(bio));
|
|
||||||
ASSERT_OR_EXECUTE_MSG(
|
|
||||||
self->socket, { return 0; }, "OpenSSL asked to send but we have no socket");
|
|
||||||
BIO_clear_retry_flags(bio);
|
|
||||||
auto [actual, err] = self->socket->Send({reinterpret_cast<const u8*>(buf), len}, 0);
|
|
||||||
switch (err) {
|
|
||||||
case Network::Errno::SUCCESS:
|
|
||||||
*actual_p = actual;
|
|
||||||
return 1;
|
|
||||||
case Network::Errno::AGAIN:
|
|
||||||
BIO_set_flags(bio, BIO_FLAGS_WRITE | BIO_FLAGS_SHOULD_RETRY);
|
|
||||||
return 0;
|
|
||||||
default:
|
|
||||||
LOG_ERROR(Service_SSL, "Socket send returned Network::Errno {}", err);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int ReadCallback(BIO* bio, char* buf, size_t len, size_t* actual_p) {
|
|
||||||
auto self = static_cast<SSLConnectionBackendOpenSSL*>(BIO_get_data(bio));
|
|
||||||
ASSERT_OR_EXECUTE_MSG(
|
|
||||||
self->socket, { return 0; }, "OpenSSL asked to recv but we have no socket");
|
|
||||||
BIO_clear_retry_flags(bio);
|
|
||||||
auto [actual, err] = self->socket->Recv(0, {reinterpret_cast<u8*>(buf), len});
|
|
||||||
switch (err) {
|
|
||||||
case Network::Errno::SUCCESS:
|
|
||||||
*actual_p = actual;
|
|
||||||
if (actual == 0) {
|
|
||||||
self->got_read_eof = true;
|
|
||||||
}
|
|
||||||
return actual ? 1 : 0;
|
|
||||||
case Network::Errno::AGAIN:
|
|
||||||
BIO_set_flags(bio, BIO_FLAGS_READ | BIO_FLAGS_SHOULD_RETRY);
|
|
||||||
return 0;
|
|
||||||
default:
|
|
||||||
LOG_ERROR(Service_SSL, "Socket recv returned Network::Errno {}", err);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static long CtrlCallback(BIO* bio, int cmd, long l_arg, void* p_arg) {
|
|
||||||
switch (cmd) {
|
|
||||||
case BIO_CTRL_FLUSH:
|
|
||||||
// Nothing to flush.
|
|
||||||
return 1;
|
|
||||||
case BIO_CTRL_PUSH:
|
|
||||||
case BIO_CTRL_POP:
|
|
||||||
#ifdef BIO_CTRL_GET_KTLS_SEND
|
|
||||||
case BIO_CTRL_GET_KTLS_SEND:
|
|
||||||
case BIO_CTRL_GET_KTLS_RECV:
|
|
||||||
#endif
|
|
||||||
// We don't support these operations, but don't bother logging them
|
|
||||||
// as they're nothing unusual.
|
|
||||||
return 0;
|
|
||||||
default:
|
|
||||||
LOG_DEBUG(Service_SSL, "OpenSSL BIO got ctrl({}, {}, {})", cmd, l_arg, p_arg);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SSL* ssl = nullptr;
|
|
||||||
BIO* bio = nullptr;
|
|
||||||
bool got_read_eof = false;
|
|
||||||
bool skip_cert_verification = false;
|
|
||||||
|
|
||||||
std::shared_ptr<Network::SocketBase> socket;
|
|
||||||
};
|
|
||||||
|
|
||||||
Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend) {
|
|
||||||
auto conn = std::make_unique<SSLConnectionBackendOpenSSL>();
|
|
||||||
|
|
||||||
R_TRY(conn->Init());
|
|
||||||
|
|
||||||
*out_backend = std::move(conn);
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
Result CheckOpenSSLErrors() {
|
|
||||||
unsigned long rc;
|
|
||||||
const char* file;
|
|
||||||
int line;
|
|
||||||
const char* func;
|
|
||||||
const char* data;
|
|
||||||
int flags;
|
|
||||||
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
|
|
||||||
while ((rc = ERR_get_error_all(&file, &line, &func, &data, &flags)))
|
|
||||||
#else
|
|
||||||
// Can't get function names from OpenSSL on this version, so use mine:
|
|
||||||
func = __func__;
|
|
||||||
while ((rc = ERR_get_error_line_data(&file, &line, &data, &flags)))
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
std::string msg;
|
|
||||||
msg.resize(1024, '\0');
|
|
||||||
ERR_error_string_n(rc, msg.data(), msg.size());
|
|
||||||
msg.resize(strlen(msg.data()), '\0');
|
|
||||||
if (flags & ERR_TXT_STRING) {
|
|
||||||
msg.append(" | ");
|
|
||||||
msg.append(data);
|
|
||||||
}
|
|
||||||
Common::Log::FmtLogMessage(Common::Log::Class::Service_SSL, Common::Log::Level::Error,
|
|
||||||
file, line, func, "OpenSSL: {}",
|
|
||||||
msg);
|
|
||||||
}
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OneTimeInit() {
|
|
||||||
ssl_ctx = SSL_CTX_new(TLS_client_method());
|
|
||||||
if (!ssl_ctx) {
|
|
||||||
LOG_ERROR(Service_SSL, "SSL_CTX_new failed");
|
|
||||||
CheckOpenSSLErrors();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, nullptr);
|
|
||||||
|
|
||||||
if (!SSL_CTX_set_default_verify_paths(ssl_ctx)) {
|
|
||||||
LOG_ERROR(Service_SSL, "SSL_CTX_set_default_verify_paths failed");
|
|
||||||
CheckOpenSSLErrors();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
OneTimeInitLogFile();
|
|
||||||
|
|
||||||
if (!OneTimeInitBIO()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
one_time_init_success = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OneTimeInitLogFile() {
|
|
||||||
const char* logfile = getenv("SSLKEYLOGFILE");
|
|
||||||
if (logfile) {
|
|
||||||
key_log_file.Open(logfile, FileAccessMode::Append, FileType::TextFile,
|
|
||||||
FileShareFlag::ShareWriteOnly);
|
|
||||||
if (key_log_file.IsOpen()) {
|
|
||||||
SSL_CTX_set_keylog_callback(ssl_ctx, &SSLConnectionBackendOpenSSL::KeyLogCallback);
|
|
||||||
} else {
|
|
||||||
LOG_CRITICAL(Service_SSL,
|
|
||||||
"SSLKEYLOGFILE was set but file could not be opened; not logging keys!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OneTimeInitBIO() {
|
|
||||||
bio_meth =
|
|
||||||
BIO_meth_new(BIO_get_new_index() | BIO_TYPE_SOURCE_SINK, "SSLConnectionBackendOpenSSL");
|
|
||||||
if (!bio_meth ||
|
|
||||||
!BIO_meth_set_write_ex(bio_meth, &SSLConnectionBackendOpenSSL::WriteCallback) ||
|
|
||||||
!BIO_meth_set_read_ex(bio_meth, &SSLConnectionBackendOpenSSL::ReadCallback) ||
|
|
||||||
!BIO_meth_set_ctrl(bio_meth, &SSLConnectionBackendOpenSSL::CtrlCallback)) {
|
|
||||||
LOG_ERROR(Service_SSL, "Failed to create BIO_METHOD");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
} // namespace Service::SSL
|
|
||||||
@@ -1,563 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
#include "common/error.h"
|
|
||||||
#include "common/fs/file.h"
|
|
||||||
#include "common/hex_util.h"
|
|
||||||
#include "common/string_util.h"
|
|
||||||
|
|
||||||
#include "core/hle/service/ssl/ssl_backend.h"
|
|
||||||
#include "core/internal_network/network.h"
|
|
||||||
#include "core/internal_network/sockets.h"
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
// These includes are inside the namespace to avoid a conflict on MinGW where
|
|
||||||
// the headers define an enum containing Network and Service as enumerators
|
|
||||||
// (which clash with the correspondingly named namespaces).
|
|
||||||
#define SECURITY_WIN32
|
|
||||||
#include <schnlsp.h>
|
|
||||||
#include <security.h>
|
|
||||||
#include <wincrypt.h>
|
|
||||||
|
|
||||||
std::once_flag one_time_init_flag;
|
|
||||||
bool one_time_init_success = false;
|
|
||||||
|
|
||||||
SCHANNEL_CRED schannel_cred{};
|
|
||||||
CredHandle cred_handle;
|
|
||||||
|
|
||||||
static void OneTimeInit() {
|
|
||||||
schannel_cred.dwVersion = SCHANNEL_CRED_VERSION;
|
|
||||||
schannel_cred.dwFlags =
|
|
||||||
SCH_USE_STRONG_CRYPTO | // don't allow insecure protocols
|
|
||||||
SCH_CRED_NO_SERVERNAME_CHECK | // don't validate server names
|
|
||||||
SCH_CRED_NO_DEFAULT_CREDS; // don't automatically present a client certificate
|
|
||||||
// ^ I'm assuming that nobody would want to connect Yuzu to a
|
|
||||||
// service that requires some OS-provided corporate client
|
|
||||||
// certificate, and presenting one to some arbitrary server
|
|
||||||
// might be a privacy concern? Who knows, though.
|
|
||||||
|
|
||||||
const SECURITY_STATUS ret =
|
|
||||||
AcquireCredentialsHandle(nullptr, const_cast<LPTSTR>(UNISP_NAME), SECPKG_CRED_OUTBOUND,
|
|
||||||
nullptr, &schannel_cred, nullptr, nullptr, &cred_handle, nullptr);
|
|
||||||
if (ret != SEC_E_OK) {
|
|
||||||
// SECURITY_STATUS codes are a type of HRESULT and can be used with NativeErrorToString.
|
|
||||||
LOG_ERROR(Service_SSL, "AcquireCredentialsHandle failed: {}",
|
|
||||||
Common::NativeErrorToString(ret));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getenv("SSLKEYLOGFILE")) {
|
|
||||||
LOG_CRITICAL(Service_SSL, "SSLKEYLOGFILE was set but Schannel does not support exporting "
|
|
||||||
"keys; not logging keys!");
|
|
||||||
// Not fatal.
|
|
||||||
}
|
|
||||||
|
|
||||||
one_time_init_success = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace Service::SSL {
|
|
||||||
|
|
||||||
class SSLConnectionBackendSchannel final : public SSLConnectionBackend {
|
|
||||||
public:
|
|
||||||
Result Init() {
|
|
||||||
std::call_once(one_time_init_flag, OneTimeInit);
|
|
||||||
|
|
||||||
if (!one_time_init_success) {
|
|
||||||
LOG_ERROR(
|
|
||||||
Service_SSL,
|
|
||||||
"Can't create SSL connection because Schannel one-time initialization failed");
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetSocket(std::shared_ptr<Network::SocketBase> socket_in) override {
|
|
||||||
socket = std::move(socket_in);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result SetHostName(const std::string& hostname_in) override {
|
|
||||||
hostname = hostname_in;
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetVerifyOption(u32 option) override {
|
|
||||||
skip_cert_verification = (option == 0);
|
|
||||||
LOG_WARNING(Service_SSL, "option={} skip_verification={}", option,
|
|
||||||
skip_cert_verification);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DoHandshake() override {
|
|
||||||
while (1) {
|
|
||||||
Result r;
|
|
||||||
switch (handshake_state) {
|
|
||||||
case HandshakeState::Initial:
|
|
||||||
if ((r = FlushCiphertextWriteBuf()) != ResultSuccess ||
|
|
||||||
(r = CallInitializeSecurityContext()) != ResultSuccess) {
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
// CallInitializeSecurityContext updated `handshake_state`.
|
|
||||||
continue;
|
|
||||||
case HandshakeState::ContinueNeeded:
|
|
||||||
case HandshakeState::IncompleteMessage:
|
|
||||||
if ((r = FlushCiphertextWriteBuf()) != ResultSuccess ||
|
|
||||||
(r = FillCiphertextReadBuf()) != ResultSuccess) {
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
if (ciphertext_read_buf.empty()) {
|
|
||||||
LOG_ERROR(Service_SSL, "SSL handshake failed because server hung up");
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
if ((r = CallInitializeSecurityContext()) != ResultSuccess) {
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
// CallInitializeSecurityContext updated `handshake_state`.
|
|
||||||
continue;
|
|
||||||
case HandshakeState::DoneAfterFlush:
|
|
||||||
if ((r = FlushCiphertextWriteBuf()) != ResultSuccess) {
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
handshake_state = HandshakeState::Connected;
|
|
||||||
return ResultSuccess;
|
|
||||||
case HandshakeState::Connected:
|
|
||||||
LOG_ERROR(Service_SSL, "Called DoHandshake but we already handshook");
|
|
||||||
return ResultInternalError;
|
|
||||||
case HandshakeState::Error:
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Result FillCiphertextReadBuf() {
|
|
||||||
const size_t fill_size = read_buf_fill_size ? read_buf_fill_size : 4096;
|
|
||||||
read_buf_fill_size = 0;
|
|
||||||
// This unnecessarily zeroes the buffer; oh well.
|
|
||||||
const size_t offset = ciphertext_read_buf.size();
|
|
||||||
ASSERT_OR_EXECUTE(offset + fill_size >= offset, { return ResultInternalError; });
|
|
||||||
ciphertext_read_buf.resize(offset + fill_size, 0);
|
|
||||||
const auto read_span = std::span(ciphertext_read_buf).subspan(offset, fill_size);
|
|
||||||
const auto [actual, err] = socket->Recv(0, read_span);
|
|
||||||
switch (err) {
|
|
||||||
case Network::Errno::SUCCESS:
|
|
||||||
ASSERT(static_cast<size_t>(actual) <= fill_size);
|
|
||||||
ciphertext_read_buf.resize(offset + actual);
|
|
||||||
return ResultSuccess;
|
|
||||||
case Network::Errno::AGAIN:
|
|
||||||
ciphertext_read_buf.resize(offset);
|
|
||||||
return ResultWouldBlock;
|
|
||||||
default:
|
|
||||||
ciphertext_read_buf.resize(offset);
|
|
||||||
LOG_ERROR(Service_SSL, "Socket recv returned Network::Errno {}", err);
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns success if the write buffer has been completely emptied.
|
|
||||||
Result FlushCiphertextWriteBuf() {
|
|
||||||
while (!ciphertext_write_buf.empty()) {
|
|
||||||
const auto [actual, err] = socket->Send(ciphertext_write_buf, 0);
|
|
||||||
switch (err) {
|
|
||||||
case Network::Errno::SUCCESS:
|
|
||||||
ASSERT(static_cast<size_t>(actual) <= ciphertext_write_buf.size());
|
|
||||||
ciphertext_write_buf.erase(ciphertext_write_buf.begin(),
|
|
||||||
ciphertext_write_buf.begin() + actual);
|
|
||||||
break;
|
|
||||||
case Network::Errno::AGAIN:
|
|
||||||
return ResultWouldBlock;
|
|
||||||
default:
|
|
||||||
LOG_ERROR(Service_SSL, "Socket send returned Network::Errno {}", err);
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result CallInitializeSecurityContext() {
|
|
||||||
unsigned long req = ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_CONFIDENTIALITY |
|
|
||||||
ISC_REQ_INTEGRITY | ISC_REQ_REPLAY_DETECT |
|
|
||||||
ISC_REQ_SEQUENCE_DETECT | ISC_REQ_STREAM |
|
|
||||||
ISC_REQ_USE_SUPPLIED_CREDS;
|
|
||||||
|
|
||||||
if (skip_cert_verification) {
|
|
||||||
req |= ISC_REQ_MANUAL_CRED_VALIDATION;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned long attr;
|
|
||||||
// https://learn.microsoft.com/en-us/windows/win32/secauthn/initializesecuritycontext--schannel
|
|
||||||
std::array<SecBuffer, 2> input_buffers{{
|
|
||||||
// only used if `initial_call_done`
|
|
||||||
{
|
|
||||||
// [0]
|
|
||||||
.cbBuffer = static_cast<unsigned long>(ciphertext_read_buf.size()),
|
|
||||||
.BufferType = SECBUFFER_TOKEN,
|
|
||||||
.pvBuffer = ciphertext_read_buf.data(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// [1] (will be replaced by SECBUFFER_MISSING when SEC_E_INCOMPLETE_MESSAGE is
|
|
||||||
// returned, or SECBUFFER_EXTRA when SEC_E_CONTINUE_NEEDED is returned if the
|
|
||||||
// whole buffer wasn't used)
|
|
||||||
.cbBuffer = 0,
|
|
||||||
.BufferType = SECBUFFER_EMPTY,
|
|
||||||
.pvBuffer = nullptr,
|
|
||||||
},
|
|
||||||
}};
|
|
||||||
std::array<SecBuffer, 2> output_buffers{{
|
|
||||||
{
|
|
||||||
.cbBuffer = 0,
|
|
||||||
.BufferType = SECBUFFER_TOKEN,
|
|
||||||
.pvBuffer = nullptr,
|
|
||||||
}, // [0]
|
|
||||||
{
|
|
||||||
.cbBuffer = 0,
|
|
||||||
.BufferType = SECBUFFER_ALERT,
|
|
||||||
.pvBuffer = nullptr,
|
|
||||||
}, // [1]
|
|
||||||
}};
|
|
||||||
SecBufferDesc input_desc{
|
|
||||||
.ulVersion = SECBUFFER_VERSION,
|
|
||||||
.cBuffers = static_cast<unsigned long>(input_buffers.size()),
|
|
||||||
.pBuffers = input_buffers.data(),
|
|
||||||
};
|
|
||||||
SecBufferDesc output_desc{
|
|
||||||
.ulVersion = SECBUFFER_VERSION,
|
|
||||||
.cBuffers = static_cast<unsigned long>(output_buffers.size()),
|
|
||||||
.pBuffers = output_buffers.data(),
|
|
||||||
};
|
|
||||||
ASSERT_OR_EXECUTE_MSG(
|
|
||||||
input_buffers[0].cbBuffer == ciphertext_read_buf.size(),
|
|
||||||
{ return ResultInternalError; }, "read buffer too large");
|
|
||||||
|
|
||||||
bool initial_call_done = handshake_state != HandshakeState::Initial;
|
|
||||||
if (initial_call_done) {
|
|
||||||
LOG_DEBUG(Service_SSL, "Passing {} bytes into InitializeSecurityContext",
|
|
||||||
ciphertext_read_buf.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
char* hostname_ptr = hostname ? const_cast<char*>(hostname->c_str()) : nullptr;
|
|
||||||
const SECURITY_STATUS ret = InitializeSecurityContextA(
|
|
||||||
&cred_handle, initial_call_done ? &ctxt : nullptr, hostname_ptr, req,
|
|
||||||
0, // Reserved1
|
|
||||||
0, // TargetDataRep not used with Schannel
|
|
||||||
initial_call_done ? &input_desc : nullptr,
|
|
||||||
0, // Reserved2
|
|
||||||
initial_call_done ? nullptr : &ctxt, &output_desc, &attr,
|
|
||||||
nullptr); // ptsExpiry
|
|
||||||
|
|
||||||
if (output_buffers[0].pvBuffer) {
|
|
||||||
const std::span span(static_cast<u8*>(output_buffers[0].pvBuffer),
|
|
||||||
output_buffers[0].cbBuffer);
|
|
||||||
ciphertext_write_buf.insert(ciphertext_write_buf.end(), span.begin(), span.end());
|
|
||||||
FreeContextBuffer(output_buffers[0].pvBuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (output_buffers[1].pvBuffer) {
|
|
||||||
const std::span span(static_cast<u8*>(output_buffers[1].pvBuffer),
|
|
||||||
output_buffers[1].cbBuffer);
|
|
||||||
// The documentation doesn't explain what format this data is in.
|
|
||||||
LOG_DEBUG(Service_SSL, "Got a {}-byte alert buffer: {}", span.size(),
|
|
||||||
Common::HexToString(span));
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (ret) {
|
|
||||||
case SEC_I_CONTINUE_NEEDED:
|
|
||||||
LOG_DEBUG(Service_SSL, "InitializeSecurityContext => SEC_I_CONTINUE_NEEDED");
|
|
||||||
if (input_buffers[1].BufferType == SECBUFFER_EXTRA) {
|
|
||||||
LOG_DEBUG(Service_SSL, "EXTRA of size {}", input_buffers[1].cbBuffer);
|
|
||||||
ASSERT(input_buffers[1].cbBuffer <= ciphertext_read_buf.size());
|
|
||||||
ciphertext_read_buf.erase(ciphertext_read_buf.begin(),
|
|
||||||
ciphertext_read_buf.end() - input_buffers[1].cbBuffer);
|
|
||||||
} else {
|
|
||||||
ASSERT(input_buffers[1].BufferType == SECBUFFER_EMPTY);
|
|
||||||
ciphertext_read_buf.clear();
|
|
||||||
}
|
|
||||||
handshake_state = HandshakeState::ContinueNeeded;
|
|
||||||
return ResultSuccess;
|
|
||||||
case SEC_E_INCOMPLETE_MESSAGE:
|
|
||||||
LOG_DEBUG(Service_SSL, "InitializeSecurityContext => SEC_E_INCOMPLETE_MESSAGE");
|
|
||||||
ASSERT(input_buffers[1].BufferType == SECBUFFER_MISSING);
|
|
||||||
read_buf_fill_size = input_buffers[1].cbBuffer;
|
|
||||||
handshake_state = HandshakeState::IncompleteMessage;
|
|
||||||
return ResultSuccess;
|
|
||||||
case SEC_E_OK:
|
|
||||||
LOG_DEBUG(Service_SSL, "InitializeSecurityContext => SEC_E_OK");
|
|
||||||
ciphertext_read_buf.clear();
|
|
||||||
handshake_state = HandshakeState::DoneAfterFlush;
|
|
||||||
return GrabStreamSizes();
|
|
||||||
default:
|
|
||||||
LOG_ERROR(Service_SSL,
|
|
||||||
"InitializeSecurityContext failed (probably certificate/protocol issue): {}",
|
|
||||||
Common::NativeErrorToString(ret));
|
|
||||||
handshake_state = HandshakeState::Error;
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Result GrabStreamSizes() {
|
|
||||||
const SECURITY_STATUS ret =
|
|
||||||
QueryContextAttributes(&ctxt, SECPKG_ATTR_STREAM_SIZES, &stream_sizes);
|
|
||||||
if (ret != SEC_E_OK) {
|
|
||||||
LOG_ERROR(Service_SSL, "QueryContextAttributes(SECPKG_ATTR_STREAM_SIZES) failed: {}",
|
|
||||||
Common::NativeErrorToString(ret));
|
|
||||||
handshake_state = HandshakeState::Error;
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result Read(size_t* out_size, std::span<u8> data) override {
|
|
||||||
*out_size = 0;
|
|
||||||
if (handshake_state != HandshakeState::Connected) {
|
|
||||||
LOG_ERROR(Service_SSL, "Called Read but we did not successfully handshake");
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
if (data.size() == 0 || got_read_eof) {
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
while (1) {
|
|
||||||
if (!cleartext_read_buf.empty()) {
|
|
||||||
*out_size = (std::min)(cleartext_read_buf.size(), data.size());
|
|
||||||
std::memcpy(data.data(), cleartext_read_buf.data(), *out_size);
|
|
||||||
cleartext_read_buf.erase(cleartext_read_buf.begin(),
|
|
||||||
cleartext_read_buf.begin() + *out_size);
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
if (!ciphertext_read_buf.empty()) {
|
|
||||||
SecBuffer empty{
|
|
||||||
.cbBuffer = 0,
|
|
||||||
.BufferType = SECBUFFER_EMPTY,
|
|
||||||
.pvBuffer = nullptr,
|
|
||||||
};
|
|
||||||
std::array<SecBuffer, 5> buffers{{
|
|
||||||
{
|
|
||||||
.cbBuffer = static_cast<unsigned long>(ciphertext_read_buf.size()),
|
|
||||||
.BufferType = SECBUFFER_DATA,
|
|
||||||
.pvBuffer = ciphertext_read_buf.data(),
|
|
||||||
},
|
|
||||||
empty,
|
|
||||||
empty,
|
|
||||||
empty,
|
|
||||||
}};
|
|
||||||
ASSERT_OR_EXECUTE_MSG(
|
|
||||||
buffers[0].cbBuffer == ciphertext_read_buf.size(),
|
|
||||||
{ return ResultInternalError; }, "read buffer too large");
|
|
||||||
SecBufferDesc desc{
|
|
||||||
.ulVersion = SECBUFFER_VERSION,
|
|
||||||
.cBuffers = static_cast<unsigned long>(buffers.size()),
|
|
||||||
.pBuffers = buffers.data(),
|
|
||||||
};
|
|
||||||
SECURITY_STATUS ret =
|
|
||||||
DecryptMessage(&ctxt, &desc, /*MessageSeqNo*/ 0, /*pfQOP*/ nullptr);
|
|
||||||
switch (ret) {
|
|
||||||
case SEC_E_OK:
|
|
||||||
ASSERT_OR_EXECUTE(buffers[0].BufferType == SECBUFFER_STREAM_HEADER,
|
|
||||||
{ return ResultInternalError; });
|
|
||||||
ASSERT_OR_EXECUTE(buffers[1].BufferType == SECBUFFER_DATA,
|
|
||||||
{ return ResultInternalError; });
|
|
||||||
ASSERT_OR_EXECUTE(buffers[2].BufferType == SECBUFFER_STREAM_TRAILER,
|
|
||||||
{ return ResultInternalError; });
|
|
||||||
cleartext_read_buf.assign(static_cast<u8*>(buffers[1].pvBuffer),
|
|
||||||
static_cast<u8*>(buffers[1].pvBuffer) +
|
|
||||||
buffers[1].cbBuffer);
|
|
||||||
if (buffers[3].BufferType == SECBUFFER_EXTRA) {
|
|
||||||
ASSERT(buffers[3].cbBuffer <= ciphertext_read_buf.size());
|
|
||||||
ciphertext_read_buf.erase(ciphertext_read_buf.begin(),
|
|
||||||
ciphertext_read_buf.end() - buffers[3].cbBuffer);
|
|
||||||
} else {
|
|
||||||
ASSERT(buffers[3].BufferType == SECBUFFER_EMPTY);
|
|
||||||
ciphertext_read_buf.clear();
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
case SEC_E_INCOMPLETE_MESSAGE:
|
|
||||||
break;
|
|
||||||
case SEC_I_CONTEXT_EXPIRED:
|
|
||||||
// Server hung up by sending close_notify.
|
|
||||||
got_read_eof = true;
|
|
||||||
*out_size = 0;
|
|
||||||
return ResultSuccess;
|
|
||||||
default:
|
|
||||||
LOG_ERROR(Service_SSL, "DecryptMessage failed: {}",
|
|
||||||
Common::NativeErrorToString(ret));
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const Result r = FillCiphertextReadBuf();
|
|
||||||
if (r != ResultSuccess) {
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
if (ciphertext_read_buf.empty()) {
|
|
||||||
got_read_eof = true;
|
|
||||||
*out_size = 0;
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Result Write(size_t* out_size, std::span<const u8> data) override {
|
|
||||||
*out_size = 0;
|
|
||||||
|
|
||||||
if (handshake_state != HandshakeState::Connected) {
|
|
||||||
LOG_ERROR(Service_SSL, "Called Write but we did not successfully handshake");
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
if (data.size() == 0) {
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
data = data.subspan(0, std::min<size_t>(data.size(), stream_sizes.cbMaximumMessage));
|
|
||||||
if (!cleartext_write_buf.empty()) {
|
|
||||||
// Already in the middle of a write. It wouldn't make sense to not
|
|
||||||
// finish sending the entire buffer since TLS has
|
|
||||||
// header/MAC/padding/etc.
|
|
||||||
if (data.size() != cleartext_write_buf.size() ||
|
|
||||||
std::memcmp(data.data(), cleartext_write_buf.data(), data.size())) {
|
|
||||||
LOG_ERROR(Service_SSL, "Called Write but buffer does not match previous buffer");
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
return WriteAlreadyEncryptedData(out_size);
|
|
||||||
} else {
|
|
||||||
cleartext_write_buf.assign(data.begin(), data.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<u8> header_buf(stream_sizes.cbHeader, 0);
|
|
||||||
std::vector<u8> tmp_data_buf = cleartext_write_buf;
|
|
||||||
std::vector<u8> trailer_buf(stream_sizes.cbTrailer, 0);
|
|
||||||
|
|
||||||
std::array<SecBuffer, 3> buffers{{
|
|
||||||
{
|
|
||||||
.cbBuffer = stream_sizes.cbHeader,
|
|
||||||
.BufferType = SECBUFFER_STREAM_HEADER,
|
|
||||||
.pvBuffer = header_buf.data(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.cbBuffer = static_cast<unsigned long>(tmp_data_buf.size()),
|
|
||||||
.BufferType = SECBUFFER_DATA,
|
|
||||||
.pvBuffer = tmp_data_buf.data(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.cbBuffer = stream_sizes.cbTrailer,
|
|
||||||
.BufferType = SECBUFFER_STREAM_TRAILER,
|
|
||||||
.pvBuffer = trailer_buf.data(),
|
|
||||||
},
|
|
||||||
}};
|
|
||||||
ASSERT_OR_EXECUTE_MSG(
|
|
||||||
buffers[1].cbBuffer == tmp_data_buf.size(), { return ResultInternalError; },
|
|
||||||
"temp buffer too large");
|
|
||||||
SecBufferDesc desc{
|
|
||||||
.ulVersion = SECBUFFER_VERSION,
|
|
||||||
.cBuffers = static_cast<unsigned long>(buffers.size()),
|
|
||||||
.pBuffers = buffers.data(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const SECURITY_STATUS ret = EncryptMessage(&ctxt, /*fQOP*/ 0, &desc, /*MessageSeqNo*/ 0);
|
|
||||||
if (ret != SEC_E_OK) {
|
|
||||||
LOG_ERROR(Service_SSL, "EncryptMessage failed: {}", Common::NativeErrorToString(ret));
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
ciphertext_write_buf.insert(ciphertext_write_buf.end(), header_buf.begin(),
|
|
||||||
header_buf.end());
|
|
||||||
ciphertext_write_buf.insert(ciphertext_write_buf.end(), tmp_data_buf.begin(),
|
|
||||||
tmp_data_buf.end());
|
|
||||||
ciphertext_write_buf.insert(ciphertext_write_buf.end(), trailer_buf.begin(),
|
|
||||||
trailer_buf.end());
|
|
||||||
return WriteAlreadyEncryptedData(out_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result WriteAlreadyEncryptedData(size_t* out_size) {
|
|
||||||
const Result r = FlushCiphertextWriteBuf();
|
|
||||||
if (r != ResultSuccess) {
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
// write buf is empty
|
|
||||||
*out_size = cleartext_write_buf.size();
|
|
||||||
cleartext_write_buf.clear();
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result GetServerCerts(std::vector<std::vector<u8>>* out_certs) override {
|
|
||||||
PCCERT_CONTEXT returned_cert = nullptr;
|
|
||||||
const SECURITY_STATUS ret =
|
|
||||||
QueryContextAttributes(&ctxt, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &returned_cert);
|
|
||||||
if (ret != SEC_E_OK) {
|
|
||||||
LOG_ERROR(Service_SSL,
|
|
||||||
"QueryContextAttributes(SECPKG_ATTR_REMOTE_CERT_CONTEXT) failed: {}",
|
|
||||||
Common::NativeErrorToString(ret));
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
PCCERT_CONTEXT some_cert = nullptr;
|
|
||||||
while ((some_cert = CertEnumCertificatesInStore(returned_cert->hCertStore, some_cert)) !=
|
|
||||||
nullptr) {
|
|
||||||
out_certs->emplace_back(static_cast<u8*>(some_cert->pbCertEncoded),
|
|
||||||
static_cast<u8*>(some_cert->pbCertEncoded) +
|
|
||||||
some_cert->cbCertEncoded);
|
|
||||||
}
|
|
||||||
std::reverse(out_certs->begin(),
|
|
||||||
out_certs->end()); // Windows returns certs in reverse order from what we want
|
|
||||||
CertFreeCertificateContext(returned_cert);
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
~SSLConnectionBackendSchannel() {
|
|
||||||
if (handshake_state != HandshakeState::Initial) {
|
|
||||||
DeleteSecurityContext(&ctxt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class HandshakeState {
|
|
||||||
// Haven't called anything yet.
|
|
||||||
Initial,
|
|
||||||
// `SEC_I_CONTINUE_NEEDED` was returned by
|
|
||||||
// `InitializeSecurityContext`; must finish sending data (if any) in
|
|
||||||
// the write buffer, then read at least one byte before calling
|
|
||||||
// `InitializeSecurityContext` again.
|
|
||||||
ContinueNeeded,
|
|
||||||
// `SEC_E_INCOMPLETE_MESSAGE` was returned by
|
|
||||||
// `InitializeSecurityContext`; hopefully the write buffer is empty;
|
|
||||||
// must read at least one byte before calling
|
|
||||||
// `InitializeSecurityContext` again.
|
|
||||||
IncompleteMessage,
|
|
||||||
// `SEC_E_OK` was returned by `InitializeSecurityContext`; must
|
|
||||||
// finish sending data in the write buffer before having `DoHandshake`
|
|
||||||
// report success.
|
|
||||||
DoneAfterFlush,
|
|
||||||
// We finished the above and are now connected. At this point, writing
|
|
||||||
// and reading are separate 'state machines' represented by the
|
|
||||||
// nonemptiness of the ciphertext and cleartext read and write buffers.
|
|
||||||
Connected,
|
|
||||||
// Another error was returned and we shouldn't allow initialization
|
|
||||||
// to continue.
|
|
||||||
Error,
|
|
||||||
} handshake_state = HandshakeState::Initial;
|
|
||||||
|
|
||||||
CtxtHandle ctxt;
|
|
||||||
SecPkgContext_StreamSizes stream_sizes;
|
|
||||||
|
|
||||||
std::shared_ptr<Network::SocketBase> socket;
|
|
||||||
std::optional<std::string> hostname;
|
|
||||||
|
|
||||||
std::vector<u8> ciphertext_read_buf;
|
|
||||||
std::vector<u8> ciphertext_write_buf;
|
|
||||||
std::vector<u8> cleartext_read_buf;
|
|
||||||
std::vector<u8> cleartext_write_buf;
|
|
||||||
|
|
||||||
bool got_read_eof = false;
|
|
||||||
bool skip_cert_verification = false;
|
|
||||||
size_t read_buf_fill_size = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend) {
|
|
||||||
auto conn = std::make_unique<SSLConnectionBackendSchannel>();
|
|
||||||
|
|
||||||
R_TRY(conn->Init());
|
|
||||||
|
|
||||||
*out_backend = std::move(conn);
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Service::SSL
|
|
||||||
@@ -1,236 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
// SecureTransport has been deprecated in its entirety in favor of
|
|
||||||
// Network.framework, but that does not allow layering TLS on top of an
|
|
||||||
// arbitrary socket.
|
|
||||||
#if defined(__GNUC__) || defined(__clang__)
|
|
||||||
#pragma GCC diagnostic push
|
|
||||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
|
||||||
#include <Security/SecureTransport.h>
|
|
||||||
#pragma GCC diagnostic pop
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "core/hle/service/ssl/ssl_backend.h"
|
|
||||||
#include "core/internal_network/network.h"
|
|
||||||
#include "core/internal_network/sockets.h"
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
struct CFReleaser {
|
|
||||||
T ptr;
|
|
||||||
|
|
||||||
YUZU_NON_COPYABLE(CFReleaser);
|
|
||||||
constexpr CFReleaser() : ptr(nullptr) {}
|
|
||||||
constexpr CFReleaser(T ptr) : ptr(ptr) {}
|
|
||||||
constexpr operator T() {
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
~CFReleaser() {
|
|
||||||
if (ptr) {
|
|
||||||
CFRelease(ptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::string CFStringToString(CFStringRef cfstr) {
|
|
||||||
CFReleaser<CFDataRef> cfdata(
|
|
||||||
CFStringCreateExternalRepresentation(nullptr, cfstr, kCFStringEncodingUTF8, 0));
|
|
||||||
ASSERT_OR_EXECUTE(cfdata, { return "???"; });
|
|
||||||
return std::string(reinterpret_cast<const char*>(CFDataGetBytePtr(cfdata)),
|
|
||||||
CFDataGetLength(cfdata));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string OSStatusToString(OSStatus status) {
|
|
||||||
CFReleaser<CFStringRef> cfstr(SecCopyErrorMessageString(status, nullptr));
|
|
||||||
if (!cfstr) {
|
|
||||||
return "[unknown error]";
|
|
||||||
}
|
|
||||||
return CFStringToString(cfstr);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace Service::SSL {
|
|
||||||
|
|
||||||
class SSLConnectionBackendSecureTransport final : public SSLConnectionBackend {
|
|
||||||
public:
|
|
||||||
Result Init() {
|
|
||||||
static std::once_flag once_flag;
|
|
||||||
std::call_once(once_flag, []() {
|
|
||||||
if (getenv("SSLKEYLOGFILE")) {
|
|
||||||
LOG_CRITICAL(Service_SSL, "SSLKEYLOGFILE was set but SecureTransport does not "
|
|
||||||
"support exporting keys; not logging keys!");
|
|
||||||
// Not fatal.
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
context.ptr = SSLCreateContext(nullptr, kSSLClientSide, kSSLStreamType);
|
|
||||||
if (!context) {
|
|
||||||
LOG_ERROR(Service_SSL, "SSLCreateContext failed");
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
|
|
||||||
OSStatus status;
|
|
||||||
if ((status = SSLSetIOFuncs(context, ReadCallback, WriteCallback)) ||
|
|
||||||
(status = SSLSetConnection(context, this))) {
|
|
||||||
LOG_ERROR(Service_SSL, "SSLContext initialization failed: {}",
|
|
||||||
OSStatusToString(status));
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetSocket(std::shared_ptr<Network::SocketBase> in_socket) override {
|
|
||||||
socket = std::move(in_socket);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result SetHostName(const std::string& hostname) override {
|
|
||||||
OSStatus status = SSLSetPeerDomainName(context, hostname.c_str(), hostname.size());
|
|
||||||
if (status) {
|
|
||||||
LOG_ERROR(Service_SSL, "SSLSetPeerDomainName failed: {}", OSStatusToString(status));
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetVerifyOption(u32 option) override {
|
|
||||||
skip_cert_verification = (option == 0);
|
|
||||||
LOG_WARNING(Service_SSL, "option={} skip_verification={}", option,
|
|
||||||
skip_cert_verification);
|
|
||||||
if (skip_cert_verification) {
|
|
||||||
SSLSetSessionOption(context, kSSLSessionOptionBreakOnServerAuth, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DoHandshake() override {
|
|
||||||
OSStatus status = SSLHandshake(context);
|
|
||||||
|
|
||||||
if (skip_cert_verification && status == errSSLServerAuthCompleted) {
|
|
||||||
LOG_DEBUG(Service_SSL, "Skipping certificate verification as requested");
|
|
||||||
status = SSLHandshake(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
return HandleReturn("SSLHandshake", 0, status);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result Read(size_t* out_size, std::span<u8> data) override {
|
|
||||||
OSStatus status = SSLRead(context, data.data(), data.size(), out_size);
|
|
||||||
return HandleReturn("SSLRead", out_size, status);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result Write(size_t* out_size, std::span<const u8> data) override {
|
|
||||||
OSStatus status = SSLWrite(context, data.data(), data.size(), out_size);
|
|
||||||
return HandleReturn("SSLWrite", out_size, status);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result HandleReturn(const char* what, size_t* actual, OSStatus status) {
|
|
||||||
switch (status) {
|
|
||||||
case 0:
|
|
||||||
return ResultSuccess;
|
|
||||||
case errSSLWouldBlock:
|
|
||||||
return ResultWouldBlock;
|
|
||||||
default: {
|
|
||||||
std::string reason;
|
|
||||||
if (got_read_eof) {
|
|
||||||
reason = "server hung up";
|
|
||||||
} else {
|
|
||||||
reason = OSStatusToString(status);
|
|
||||||
}
|
|
||||||
LOG_ERROR(Service_SSL, "{} failed: {}", what, reason);
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Result GetServerCerts(std::vector<std::vector<u8>>* out_certs) override {
|
|
||||||
CFReleaser<SecTrustRef> trust;
|
|
||||||
OSStatus status = SSLCopyPeerTrust(context, &trust.ptr);
|
|
||||||
if (status) {
|
|
||||||
LOG_ERROR(Service_SSL, "SSLCopyPeerTrust failed: {}", OSStatusToString(status));
|
|
||||||
return ResultInternalError;
|
|
||||||
}
|
|
||||||
for (CFIndex i = 0, count = SecTrustGetCertificateCount(trust); i < count; i++) {
|
|
||||||
SecCertificateRef cert = SecTrustGetCertificateAtIndex(trust, i);
|
|
||||||
CFReleaser<CFDataRef> data(SecCertificateCopyData(cert));
|
|
||||||
ASSERT_OR_EXECUTE(data, { return ResultInternalError; });
|
|
||||||
const u8* ptr = CFDataGetBytePtr(data);
|
|
||||||
out_certs->emplace_back(ptr, ptr + CFDataGetLength(data));
|
|
||||||
}
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
static OSStatus ReadCallback(SSLConnectionRef connection, void* data, size_t* dataLength) {
|
|
||||||
return ReadOrWriteCallback(connection, data, dataLength, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
static OSStatus WriteCallback(SSLConnectionRef connection, const void* data,
|
|
||||||
size_t* dataLength) {
|
|
||||||
return ReadOrWriteCallback(connection, const_cast<void*>(data), dataLength, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
static OSStatus ReadOrWriteCallback(SSLConnectionRef connection, void* data, size_t* dataLength,
|
|
||||||
bool is_read) {
|
|
||||||
auto self =
|
|
||||||
static_cast<SSLConnectionBackendSecureTransport*>(const_cast<void*>(connection));
|
|
||||||
ASSERT_OR_EXECUTE_MSG(
|
|
||||||
self->socket, { return 0; }, "SecureTransport asked to {} but we have no socket",
|
|
||||||
is_read ? "read" : "write");
|
|
||||||
|
|
||||||
// SecureTransport callbacks (unlike OpenSSL BIO callbacks) are
|
|
||||||
// expected to read/write the full requested dataLength or return an
|
|
||||||
// error, so we have to add a loop ourselves.
|
|
||||||
size_t requested_len = *dataLength;
|
|
||||||
size_t offset = 0;
|
|
||||||
while (offset < requested_len) {
|
|
||||||
std::span cur(reinterpret_cast<u8*>(data) + offset, requested_len - offset);
|
|
||||||
auto [actual, err] = is_read ? self->socket->Recv(0, cur) : self->socket->Send(cur, 0);
|
|
||||||
LOG_CRITICAL(Service_SSL, "op={}, offset={} actual={}/{} err={}", is_read, offset,
|
|
||||||
actual, cur.size(), static_cast<s32>(err));
|
|
||||||
switch (err) {
|
|
||||||
case Network::Errno::SUCCESS:
|
|
||||||
offset += actual;
|
|
||||||
if (actual == 0) {
|
|
||||||
ASSERT(is_read);
|
|
||||||
self->got_read_eof = true;
|
|
||||||
return errSecEndOfData;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case Network::Errno::AGAIN:
|
|
||||||
*dataLength = offset;
|
|
||||||
return errSSLWouldBlock;
|
|
||||||
default:
|
|
||||||
LOG_ERROR(Service_SSL, "Socket {} returned Network::Errno {}",
|
|
||||||
is_read ? "recv" : "send", err);
|
|
||||||
return errSecIO;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ASSERT(offset == requested_len);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
CFReleaser<SSLContextRef> context = nullptr;
|
|
||||||
bool got_read_eof = false;
|
|
||||||
bool skip_cert_verification = false;
|
|
||||||
|
|
||||||
std::shared_ptr<Network::SocketBase> socket;
|
|
||||||
};
|
|
||||||
|
|
||||||
Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend) {
|
|
||||||
auto conn = std::make_unique<SSLConnectionBackendSecureTransport>();
|
|
||||||
|
|
||||||
R_TRY(conn->Init());
|
|
||||||
|
|
||||||
*out_backend = std::move(conn);
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Service::SSL
|
|
||||||
@@ -114,13 +114,13 @@ std::vector<Network::ScanData> ScanWifiNetworks(std::chrono::milliseconds deadli
|
|||||||
char ifname[IFNAMSIZ] = {0};
|
char ifname[IFNAMSIZ] = {0};
|
||||||
char *args[1] = {ifname};
|
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;
|
iwrange range;
|
||||||
int res = iw_get_range_info(f_skfd, f_ifname, &range);
|
int res = iw_get_range_info(skfd, ifname, &range);
|
||||||
LOG_INFO(Network, "ifname {} returned {} on iw_get_range_info", f_ifname, res);
|
LOG_INFO(Network, "ifname {} returned {} on iw_get_range_info", ifname, res);
|
||||||
if (res >= 0) {
|
if (res >= 0) {
|
||||||
strncpy(f_args[0], f_ifname, IFNAMSIZ - 1);
|
strncpy(args[0], ifname, IFNAMSIZ - 1);
|
||||||
f_args[0][IFNAMSIZ - 1] = 0;
|
args[0][IFNAMSIZ - 1] = 0;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -522,6 +522,9 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent) {
|
|||||||
PAIR(AnisotropyMode, X4, tr("4x")),
|
PAIR(AnisotropyMode, X4, tr("4x")),
|
||||||
PAIR(AnisotropyMode, X8, tr("8x")),
|
PAIR(AnisotropyMode, X8, tr("8x")),
|
||||||
PAIR(AnisotropyMode, X16, tr("16x")),
|
PAIR(AnisotropyMode, X16, tr("16x")),
|
||||||
|
PAIR(AnisotropyMode, X32, tr("32x")),
|
||||||
|
PAIR(AnisotropyMode, X64, tr("64x")),
|
||||||
|
PAIR(AnisotropyMode, None, tr("None")),
|
||||||
}});
|
}});
|
||||||
translations->insert(
|
translations->insert(
|
||||||
{Settings::EnumMetadata<Settings::Language>::Index(),
|
{Settings::EnumMetadata<Settings::Language>::Index(),
|
||||||
|
|||||||
@@ -335,7 +335,7 @@ void SetupDenormControl(const Profile& profile, const IR::Program& program, Emit
|
|||||||
if (info.uses_fp32_denorms_flush && info.uses_fp32_denorms_preserve) {
|
if (info.uses_fp32_denorms_flush && info.uses_fp32_denorms_preserve) {
|
||||||
LOG_DEBUG(Shader_SPIRV, "Fp32 denorm flush and preserve on the same shader");
|
LOG_DEBUG(Shader_SPIRV, "Fp32 denorm flush and preserve on the same shader");
|
||||||
} else if (info.uses_fp32_denorms_flush) {
|
} 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.AddCapability(spv::Capability::DenormFlushToZero);
|
||||||
ctx.AddExecutionMode(main_func, spv::ExecutionMode::DenormFlushToZero, 32U);
|
ctx.AddExecutionMode(main_func, spv::ExecutionMode::DenormFlushToZero, 32U);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -86,8 +86,6 @@ struct Profile {
|
|||||||
bool has_broken_signed_operations{};
|
bool has_broken_signed_operations{};
|
||||||
/// Float controls break when fp16 is enabled
|
/// Float controls break when fp16 is enabled
|
||||||
bool has_broken_fp16_float_controls{};
|
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
|
/// Dynamic vec4 indexing is broken on some OpenGL drivers
|
||||||
bool has_gl_component_indexing_bug{};
|
bool has_gl_component_indexing_bug{};
|
||||||
/// The precise type qualifier is broken in the fragment stage of some drivers
|
/// The precise type qualifier is broken in the fragment stage of some drivers
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ namespace VideoCommon {
|
|||||||
|
|
||||||
enum class BufferFlagBits {
|
enum class BufferFlagBits {
|
||||||
Picked = 1 << 0,
|
Picked = 1 << 0,
|
||||||
|
CachedWrites = 1 << 1,
|
||||||
|
PreemtiveDownload = 1 << 2,
|
||||||
};
|
};
|
||||||
DECLARE_ENUM_FLAG_OPERATORS(BufferFlagBits)
|
DECLARE_ENUM_FLAG_OPERATORS(BufferFlagBits)
|
||||||
|
|
||||||
@@ -56,6 +58,15 @@ public:
|
|||||||
flags |= BufferFlagBits::Picked;
|
flags |= BufferFlagBits::Picked;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MarkPreemtiveDownload() noexcept {
|
||||||
|
flags |= BufferFlagBits::PreemtiveDownload;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unmark buffer as picked
|
||||||
|
void Unpick() noexcept {
|
||||||
|
flags &= ~BufferFlagBits::Picked;
|
||||||
|
}
|
||||||
|
|
||||||
/// Increases the likeliness of this being a stream buffer
|
/// Increases the likeliness of this being a stream buffer
|
||||||
void IncreaseStreamScore(int score) noexcept {
|
void IncreaseStreamScore(int score) noexcept {
|
||||||
stream_score += score;
|
stream_score += score;
|
||||||
@@ -76,6 +87,15 @@ public:
|
|||||||
return True(flags & BufferFlagBits::Picked);
|
return True(flags & BufferFlagBits::Picked);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true when the buffer has pending cached writes
|
||||||
|
[[nodiscard]] bool HasCachedWrites() const noexcept {
|
||||||
|
return True(flags & BufferFlagBits::CachedWrites);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsPreemtiveDownload() const noexcept {
|
||||||
|
return True(flags & BufferFlagBits::PreemtiveDownload);
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the base CPU address of the buffer
|
/// Returns the base CPU address of the buffer
|
||||||
[[nodiscard]] VAddr CpuAddr() const noexcept {
|
[[nodiscard]] VAddr CpuAddr() const noexcept {
|
||||||
return cpu_addr;
|
return cpu_addr;
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <limits>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
|
|
||||||
@@ -122,6 +121,25 @@ void BufferCache<P>::WriteMemory(DAddr device_addr, u64 size) {
|
|||||||
memory_tracker.MarkRegionAsCpuModified(device_addr, size);
|
memory_tracker.MarkRegionAsCpuModified(device_addr, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class P>
|
||||||
|
void BufferCache<P>::CachedWriteMemory(DAddr device_addr, u64 size) {
|
||||||
|
const bool is_dirty = IsRegionRegistered(device_addr, size);
|
||||||
|
if (!is_dirty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DAddr aligned_start = Common::AlignDown(device_addr, DEVICE_PAGESIZE);
|
||||||
|
DAddr aligned_end = Common::AlignUp(device_addr + size, DEVICE_PAGESIZE);
|
||||||
|
if (!IsRegionGpuModified(aligned_start, aligned_end - aligned_start)) {
|
||||||
|
WriteMemory(device_addr, size);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp_buffer.resize_destructive(size);
|
||||||
|
device_memory.ReadBlockUnsafe(device_addr, tmp_buffer.data(), size);
|
||||||
|
|
||||||
|
InlineMemoryImplementation(device_addr, size, tmp_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
bool BufferCache<P>::OnCPUWrite(DAddr device_addr, u64 size) {
|
bool BufferCache<P>::OnCPUWrite(DAddr device_addr, u64 size) {
|
||||||
const bool is_dirty = IsRegionRegistered(device_addr, size);
|
const bool is_dirty = IsRegionRegistered(device_addr, size);
|
||||||
@@ -404,7 +422,7 @@ void BufferCache<P>::UnbindGraphicsStorageBuffers(size_t stage) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
void BufferCache<P>::BindGraphicsStorageBuffer(size_t stage, size_t ssbo_index, u32 cbuf_index,
|
bool BufferCache<P>::BindGraphicsStorageBuffer(size_t stage, size_t ssbo_index, u32 cbuf_index,
|
||||||
u32 cbuf_offset, bool is_written) {
|
u32 cbuf_offset, bool is_written) {
|
||||||
const bool already_enabled =
|
const bool already_enabled =
|
||||||
((channel_state->enabled_storage_buffers[stage] >> ssbo_index) & 1U) != 0;
|
((channel_state->enabled_storage_buffers[stage] >> ssbo_index) & 1U) != 0;
|
||||||
@@ -415,7 +433,7 @@ void BufferCache<P>::BindGraphicsStorageBuffer(size_t stage, size_t ssbo_index,
|
|||||||
LOG_WARNING(HW_GPU,
|
LOG_WARNING(HW_GPU,
|
||||||
"Skipping graphics storage buffer {} due to driver limit {}",
|
"Skipping graphics storage buffer {} due to driver limit {}",
|
||||||
ssbo_index, max_bindings);
|
ssbo_index, max_bindings);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -431,6 +449,7 @@ void BufferCache<P>::BindGraphicsStorageBuffer(size_t stage, size_t ssbo_index,
|
|||||||
const GPUVAddr ssbo_addr = cbufs.const_buffers[cbuf_index].address + cbuf_offset;
|
const GPUVAddr ssbo_addr = cbufs.const_buffers[cbuf_index].address + cbuf_offset;
|
||||||
channel_state->storage_buffers[stage][ssbo_index] =
|
channel_state->storage_buffers[stage][ssbo_index] =
|
||||||
StorageBufferBinding(ssbo_addr, cbuf_index, is_written);
|
StorageBufferBinding(ssbo_addr, cbuf_index, is_written);
|
||||||
|
return (channel_state->storage_buffers[stage][ssbo_index].buffer_id != NULL_BUFFER_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
@@ -743,6 +762,16 @@ void BufferCache<P>::BindHostIndexBuffer() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class P>
|
||||||
|
void BufferCache<P>::BindHostVertexBuffer(u32 index, Buffer& buffer, u32 offset, u32 size,
|
||||||
|
u32 stride) {
|
||||||
|
if constexpr (IS_OPENGL) {
|
||||||
|
runtime.BindVertexBuffer(index, buffer, offset, size, stride);
|
||||||
|
} else {
|
||||||
|
runtime.BindVertexBuffer(index, buffer.Handle(), offset, size, stride);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
Binding& BufferCache<P>::VertexBufferSlot(u32 index) {
|
Binding& BufferCache<P>::VertexBufferSlot(u32 index) {
|
||||||
ASSERT(index < NUM_VERTEX_BUFFERS);
|
ASSERT(index < NUM_VERTEX_BUFFERS);
|
||||||
@@ -1222,14 +1251,9 @@ void BufferCache<P>::UpdateIndexBuffer() {
|
|||||||
const GPUVAddr gpu_addr_begin = index_buffer_ref.StartAddress();
|
const GPUVAddr gpu_addr_begin = index_buffer_ref.StartAddress();
|
||||||
const GPUVAddr gpu_addr_end = index_buffer_ref.EndAddress();
|
const GPUVAddr gpu_addr_end = index_buffer_ref.EndAddress();
|
||||||
const std::optional<DAddr> device_addr = gpu_memory->GpuToCpuAddress(gpu_addr_begin);
|
const std::optional<DAddr> device_addr = gpu_memory->GpuToCpuAddress(gpu_addr_begin);
|
||||||
u64 address_size = 0;
|
const u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin);
|
||||||
if (gpu_addr_end > gpu_addr_begin) {
|
const u32 draw_size = (index_buffer_ref.count + index_buffer_ref.first) * u32(index_buffer_ref.FormatSizeInBytes());
|
||||||
address_size = (std::min)(gpu_addr_end - gpu_addr_begin,
|
const u32 size = (std::min)(address_size, draw_size);
|
||||||
u64{(std::numeric_limits<u32>::max)()});
|
|
||||||
}
|
|
||||||
const u64 draw_size = (u64{index_buffer_ref.count} + u64{index_buffer_ref.first}) *
|
|
||||||
u64{index_buffer_ref.FormatSizeInBytes()};
|
|
||||||
const u32 size = static_cast<u32>((std::min)(address_size, draw_size));
|
|
||||||
if (size == 0 || !device_addr) {
|
if (size == 0 || !device_addr) {
|
||||||
channel_state->index_buffer = NULL_BINDING;
|
channel_state->index_buffer = NULL_BINDING;
|
||||||
return;
|
return;
|
||||||
@@ -1244,20 +1268,6 @@ void BufferCache<P>::UpdateIndexBuffer() {
|
|||||||
template <class P>
|
template <class P>
|
||||||
void BufferCache<P>::UpdateVertexBuffers() {
|
void BufferCache<P>::UpdateVertexBuffers() {
|
||||||
auto& flags = maxwell3d->dirty.flags;
|
auto& flags = maxwell3d->dirty.flags;
|
||||||
const u32 base_instance = maxwell3d->draw_manager.draw_state.base_instance;
|
|
||||||
if (draw_instance_count != last_draw_instance_count ||
|
|
||||||
base_instance != last_draw_base_instance) {
|
|
||||||
last_draw_instance_count = draw_instance_count;
|
|
||||||
last_draw_base_instance = base_instance;
|
|
||||||
const auto& instances = maxwell3d->regs.vertex_stream_instances;
|
|
||||||
for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) {
|
|
||||||
if (!instances.IsInstancingEnabled(index)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
flags[Dirty::VertexBuffer0 + index] = true;
|
|
||||||
flags[Dirty::VertexBuffers] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!maxwell3d->dirty.flags[Dirty::VertexBuffers]) {
|
if (!maxwell3d->dirty.flags[Dirty::VertexBuffers]) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1278,37 +1288,15 @@ void BufferCache<P>::UpdateVertexBuffer(u32 index) {
|
|||||||
const GPUVAddr gpu_addr_begin = array.Address();
|
const GPUVAddr gpu_addr_begin = array.Address();
|
||||||
const GPUVAddr gpu_addr_end = limit.Address() + 1;
|
const GPUVAddr gpu_addr_end = limit.Address() + 1;
|
||||||
const std::optional<DAddr> device_addr = gpu_memory->GpuToCpuAddress(gpu_addr_begin);
|
const std::optional<DAddr> device_addr = gpu_memory->GpuToCpuAddress(gpu_addr_begin);
|
||||||
if (array.enable == 0 || !device_addr || gpu_addr_end <= gpu_addr_begin) {
|
const u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin);
|
||||||
|
u32 size = address_size; // TODO: Analyze stride and number of vertices
|
||||||
|
if (array.enable == 0 || size == 0 || !device_addr) {
|
||||||
channel_state->vertex_buffers[index] = NULL_BINDING;
|
channel_state->vertex_buffers[index] = NULL_BINDING;
|
||||||
UpdateVertexBufferSlot(index, NULL_BINDING);
|
UpdateVertexBufferSlot(index, NULL_BINDING);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// TODO: Analyze stride and number of vertices
|
if (!gpu_memory->IsWithinGPUAddressRange(gpu_addr_end) || size >= 64_MiB) {
|
||||||
constexpr u64 implausible_size = 64_MiB;
|
size = static_cast<u32>(gpu_memory->MaxContinuousRange(gpu_addr_begin, size));
|
||||||
u64 address_size = gpu_addr_end - gpu_addr_begin;
|
|
||||||
if (address_size > u64{(std::numeric_limits<u32>::max)()}) {
|
|
||||||
address_size = implausible_size;
|
|
||||||
}
|
|
||||||
const bool is_instanced = maxwell3d->regs.vertex_stream_instances.IsInstancingEnabled(index);
|
|
||||||
const u64 stride = static_cast<u64>(array.stride);
|
|
||||||
if (is_instanced && stride != 0 && draw_instance_count != 0) {
|
|
||||||
const u64 base_instance = static_cast<u64>(maxwell3d->draw_manager.draw_state.base_instance);
|
|
||||||
u64 elements = base_instance + 1;
|
|
||||||
if (array.frequency != 0) {
|
|
||||||
elements = (base_instance + static_cast<u64>(draw_instance_count) - 1) /
|
|
||||||
static_cast<u64>(array.frequency) +
|
|
||||||
1;
|
|
||||||
}
|
|
||||||
address_size = (std::min)(address_size, (elements + 1) * stride);
|
|
||||||
}
|
|
||||||
if (!gpu_memory->IsWithinGPUAddressRange(gpu_addr_end) || address_size >= implausible_size) {
|
|
||||||
address_size = gpu_memory->MaxContinuousRange(gpu_addr_begin, address_size);
|
|
||||||
}
|
|
||||||
const u32 size = static_cast<u32>(address_size);
|
|
||||||
if (size == 0) {
|
|
||||||
channel_state->vertex_buffers[index] = NULL_BINDING;
|
|
||||||
UpdateVertexBufferSlot(index, NULL_BINDING);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
const BufferId buffer_id = FindBuffer(*device_addr, size);
|
const BufferId buffer_id = FindBuffer(*device_addr, size);
|
||||||
const Binding binding{
|
const Binding binding{
|
||||||
@@ -1590,10 +1578,9 @@ template <class P>
|
|||||||
BufferId BufferCache<P>::CreateBuffer(DAddr device_addr, u32 wanted_size) {
|
BufferId BufferCache<P>::CreateBuffer(DAddr device_addr, u32 wanted_size) {
|
||||||
DAddr device_addr_end = Common::AlignUp(device_addr + wanted_size, CACHING_PAGESIZE);
|
DAddr device_addr_end = Common::AlignUp(device_addr + wanted_size, CACHING_PAGESIZE);
|
||||||
device_addr = Common::AlignDown(device_addr, CACHING_PAGESIZE);
|
device_addr = Common::AlignDown(device_addr, CACHING_PAGESIZE);
|
||||||
constexpr u64 max_buffer_size = u64{(std::numeric_limits<u32>::max)()};
|
wanted_size = static_cast<u32>(device_addr_end - device_addr);
|
||||||
wanted_size = static_cast<u32>((std::min)(device_addr_end - device_addr, max_buffer_size));
|
|
||||||
const OverlapResult overlap = ResolveOverlaps(device_addr, wanted_size);
|
const OverlapResult overlap = ResolveOverlaps(device_addr, wanted_size);
|
||||||
const u32 size = static_cast<u32>((std::min)(overlap.end - overlap.begin, max_buffer_size));
|
const u32 size = static_cast<u32>(overlap.end - overlap.begin);
|
||||||
const BufferId new_buffer_id = slot_buffers.insert(runtime, overlap.begin, size);
|
const BufferId new_buffer_id = slot_buffers.insert(runtime, overlap.begin, size);
|
||||||
auto& new_buffer = slot_buffers[new_buffer_id];
|
auto& new_buffer = slot_buffers[new_buffer_id];
|
||||||
const size_t size_bytes = new_buffer.SizeBytes();
|
const size_t size_bytes = new_buffer.SizeBytes();
|
||||||
|
|||||||
@@ -217,6 +217,8 @@ public:
|
|||||||
|
|
||||||
void WriteMemory(DAddr device_addr, u64 size);
|
void WriteMemory(DAddr device_addr, u64 size);
|
||||||
|
|
||||||
|
void CachedWriteMemory(DAddr device_addr, u64 size);
|
||||||
|
|
||||||
bool OnCPUWrite(DAddr device_addr, u64 size);
|
bool OnCPUWrite(DAddr device_addr, u64 size);
|
||||||
|
|
||||||
void DownloadMemory(DAddr device_addr, u64 size);
|
void DownloadMemory(DAddr device_addr, u64 size);
|
||||||
@@ -246,7 +248,7 @@ public:
|
|||||||
|
|
||||||
void UnbindGraphicsStorageBuffers(size_t stage);
|
void UnbindGraphicsStorageBuffers(size_t stage);
|
||||||
|
|
||||||
void BindGraphicsStorageBuffer(size_t stage, size_t ssbo_index, u32 cbuf_index, u32 cbuf_offset,
|
bool BindGraphicsStorageBuffer(size_t stage, size_t ssbo_index, u32 cbuf_index, u32 cbuf_offset,
|
||||||
bool is_written);
|
bool is_written);
|
||||||
|
|
||||||
void UnbindGraphicsTextureBuffers(size_t stage);
|
void UnbindGraphicsTextureBuffers(size_t stage);
|
||||||
@@ -307,10 +309,6 @@ public:
|
|||||||
current_draw_indirect = current_draw_indirect_;
|
current_draw_indirect = current_draw_indirect_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetDrawInstanceCount(u32 draw_instance_count_) {
|
|
||||||
draw_instance_count = draw_instance_count_;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] std::pair<Buffer*, u32> GetDrawIndirectCount();
|
[[nodiscard]] std::pair<Buffer*, u32> GetDrawIndirectCount();
|
||||||
|
|
||||||
[[nodiscard]] std::pair<Buffer*, u32> GetDrawIndirectBuffer();
|
[[nodiscard]] std::pair<Buffer*, u32> GetDrawIndirectBuffer();
|
||||||
@@ -378,6 +376,8 @@ private:
|
|||||||
|
|
||||||
void BindHostTransformFeedbackBuffers();
|
void BindHostTransformFeedbackBuffers();
|
||||||
|
|
||||||
|
void BindHostVertexBuffer(u32 index, Buffer& buffer, u32 offset, u32 size, u32 stride);
|
||||||
|
|
||||||
void BindHostComputeUniformBuffers();
|
void BindHostComputeUniformBuffers();
|
||||||
|
|
||||||
void BindHostComputeStorageBuffers();
|
void BindHostComputeStorageBuffers();
|
||||||
@@ -484,10 +484,6 @@ private:
|
|||||||
|
|
||||||
const Tegra::Engines::Maxwell3D::DrawManager::IndirectParams* current_draw_indirect{};
|
const Tegra::Engines::Maxwell3D::DrawManager::IndirectParams* current_draw_indirect{};
|
||||||
|
|
||||||
u32 draw_instance_count = 0;
|
|
||||||
u32 last_draw_instance_count = 0;
|
|
||||||
u32 last_draw_base_instance = 0;
|
|
||||||
|
|
||||||
u32 last_index_count = 0;
|
u32 last_index_count = 0;
|
||||||
|
|
||||||
u32 enabled_vertex_buffers_mask = 0;
|
u32 enabled_vertex_buffers_mask = 0;
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ set(SHADER_FILES
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/blit_color_float.frag
|
${CMAKE_CURRENT_SOURCE_DIR}/blit_color_float.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_2d.comp
|
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_2d.comp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/blit_color_msaa.frag
|
${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_msaa.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/blit_depth_stencil_msaa.frag
|
${CMAKE_CURRENT_SOURCE_DIR}/blit_depth_stencil_msaa.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_3d.comp
|
${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_float_to_depth.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/convert_msaa_to_non_msaa.comp
|
${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.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.comp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/convert_non_msaa_to_msaa.frag
|
${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}/convert_s8d24_to_abgr8.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/full_screen_triangle.vert
|
${CMAKE_CURRENT_SOURCE_DIR}/full_screen_triangle.vert
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/fxaa.frag
|
${CMAKE_CURRENT_SOURCE_DIR}/fxaa.frag
|
||||||
@@ -168,78 +163,6 @@ foreach(SOURCE_FILE IN ITEMS ${SHADER_FILES})
|
|||||||
endif()
|
endif()
|
||||||
endforeach()
|
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()
|
|
||||||
|
|
||||||
set(SHADER_DEFINE_VARIANTS
|
|
||||||
"block_linear_unswizzle_2d.comp|nonarrow|HAS_EXTENDED_TYPES=0"
|
|
||||||
"pitch_unswizzle.comp|nonarrow|HAS_EXTENDED_TYPES=0"
|
|
||||||
"block_linear_unswizzle_3d.comp|nonarrow|HAS_EXTENDED_TYPES=0"
|
|
||||||
)
|
|
||||||
|
|
||||||
foreach(VARIANT IN ITEMS ${SHADER_DEFINE_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_DEFINE)
|
|
||||||
|
|
||||||
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}
|
|
||||||
-D${VARIANT_DEFINE}
|
|
||||||
--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})
|
foreach(FILEPATH IN ITEMS ${FIDELITYFX_FILES})
|
||||||
get_filename_component(FILENAME ${FILEPATH} NAME)
|
get_filename_component(FILENAME ${FILEPATH} NAME)
|
||||||
string(REPLACE "." "_" HEADER_NAME ${FILENAME})
|
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;
|
layout(location = 0) in vec2 texcoord;
|
||||||
|
|
||||||
void main() {
|
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;
|
layout(location = 0) in vec2 texcoord;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
gl_FragDepth = texelFetch(depth_tex, ivec2(texcoord), gl_SampleID).r;
|
gl_FragDepth = texelFetch(depth_tex, ivec2(texcoord), 0).r;
|
||||||
gl_FragStencilRefARB = int(texelFetch(stencil_tex, ivec2(texcoord), gl_SampleID).r);
|
gl_FragStencilRefARB = int(texelFetch(stencil_tex, ivec2(texcoord), 0).r);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,9 @@
|
|||||||
|
|
||||||
#ifdef VULKAN
|
#ifdef VULKAN
|
||||||
|
|
||||||
#ifndef HAS_EXTENDED_TYPES
|
|
||||||
#define HAS_EXTENDED_TYPES 1
|
|
||||||
#endif
|
|
||||||
#if HAS_EXTENDED_TYPES
|
|
||||||
#extension GL_EXT_shader_16bit_storage : require
|
#extension GL_EXT_shader_16bit_storage : require
|
||||||
#extension GL_EXT_shader_8bit_storage : require
|
#extension GL_EXT_shader_8bit_storage : require
|
||||||
#endif
|
#define HAS_EXTENDED_TYPES 1
|
||||||
#define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants {
|
#define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants {
|
||||||
#define END_PUSH_CONSTANTS };
|
#define END_PUSH_CONSTANTS };
|
||||||
#define UNIFORM(n)
|
#define UNIFORM(n)
|
||||||
|
|||||||
@@ -5,13 +5,9 @@
|
|||||||
|
|
||||||
#ifdef VULKAN
|
#ifdef VULKAN
|
||||||
|
|
||||||
#ifndef HAS_EXTENDED_TYPES
|
|
||||||
#define HAS_EXTENDED_TYPES 1
|
|
||||||
#endif
|
|
||||||
#if HAS_EXTENDED_TYPES
|
|
||||||
#extension GL_EXT_shader_16bit_storage : require
|
#extension GL_EXT_shader_16bit_storage : require
|
||||||
#extension GL_EXT_shader_8bit_storage : require
|
#extension GL_EXT_shader_8bit_storage : require
|
||||||
#endif
|
#define HAS_EXTENDED_TYPES 1
|
||||||
#define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants {
|
#define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants {
|
||||||
#define END_PUSH_CONSTANTS };
|
#define END_PUSH_CONSTANTS };
|
||||||
#define UNIFORM(n)
|
#define UNIFORM(n)
|
||||||
|
|||||||
@@ -3,14 +3,7 @@
|
|||||||
|
|
||||||
#version 450 core
|
#version 450 core
|
||||||
|
|
||||||
#ifndef SAMPLER_TYPE
|
layout(binding = 0) uniform sampler2DMS msaa_in;
|
||||||
#define SAMPLER_TYPE sampler2DMS
|
|
||||||
#endif
|
|
||||||
#ifndef TEXEL_TYPE
|
|
||||||
#define TEXEL_TYPE vec4
|
|
||||||
#endif
|
|
||||||
|
|
||||||
layout(binding = 0) uniform SAMPLER_TYPE msaa_in;
|
|
||||||
|
|
||||||
layout(push_constant) uniform PushConstants {
|
layout(push_constant) uniform PushConstants {
|
||||||
ivec2 dst_offset;
|
ivec2 dst_offset;
|
||||||
@@ -18,7 +11,7 @@ layout(push_constant) uniform PushConstants {
|
|||||||
ivec2 scale;
|
ivec2 scale;
|
||||||
};
|
};
|
||||||
|
|
||||||
layout(location = 0) out TEXEL_TYPE frag_color;
|
layout(location = 0) out vec4 frag_color;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
const ivec2 coord = ivec2(gl_FragCoord.xy) - dst_offset + src_offset;
|
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
|
#version 450 core
|
||||||
|
|
||||||
#ifndef SAMPLER_TYPE
|
layout(binding = 0) uniform sampler2D img_in;
|
||||||
#define SAMPLER_TYPE sampler2D
|
|
||||||
#endif
|
|
||||||
#ifndef TEXEL_TYPE
|
|
||||||
#define TEXEL_TYPE vec4
|
|
||||||
#endif
|
|
||||||
|
|
||||||
layout(binding = 0) uniform SAMPLER_TYPE img_in;
|
|
||||||
|
|
||||||
layout(push_constant) uniform PushConstants {
|
layout(push_constant) uniform PushConstants {
|
||||||
ivec2 dst_offset;
|
ivec2 dst_offset;
|
||||||
@@ -18,7 +11,7 @@ layout(push_constant) uniform PushConstants {
|
|||||||
ivec2 scale;
|
ivec2 scale;
|
||||||
};
|
};
|
||||||
|
|
||||||
layout(location = 0) out TEXEL_TYPE frag_color;
|
layout(location = 0) out vec4 frag_color;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
const ivec2 msaa_coord = ivec2(gl_FragCoord.xy) - dst_offset;
|
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);
|
|
||||||
}
|
|
||||||
@@ -5,13 +5,9 @@
|
|||||||
|
|
||||||
#ifdef VULKAN
|
#ifdef VULKAN
|
||||||
|
|
||||||
#ifndef HAS_EXTENDED_TYPES
|
|
||||||
#define HAS_EXTENDED_TYPES 1
|
|
||||||
#endif
|
|
||||||
#if HAS_EXTENDED_TYPES
|
|
||||||
#extension GL_EXT_shader_16bit_storage : require
|
#extension GL_EXT_shader_16bit_storage : require
|
||||||
#extension GL_EXT_shader_8bit_storage : require
|
#extension GL_EXT_shader_8bit_storage : require
|
||||||
#endif
|
#define HAS_EXTENDED_TYPES 1
|
||||||
#define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants {
|
#define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants {
|
||||||
#define END_PUSH_CONSTANTS };
|
#define END_PUSH_CONSTANTS };
|
||||||
#define UNIFORM(n)
|
#define UNIFORM(n)
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ layout(push_constant) uniform constants {
|
|||||||
vec2 scale;
|
vec2 scale;
|
||||||
vec2 size;
|
vec2 size;
|
||||||
vec2 resize_factor;
|
vec2 resize_factor;
|
||||||
vec2 crop_offset;
|
|
||||||
float edge_sharpness;
|
float edge_sharpness;
|
||||||
};
|
};
|
||||||
layout(location = 0) out highp vec2 texcoord;
|
layout(location = 0) out highp vec2 texcoord;
|
||||||
@@ -16,5 +15,5 @@ void main() {
|
|||||||
float x = float((gl_VertexIndex & 1) << 2);
|
float x = float((gl_VertexIndex & 1) << 2);
|
||||||
float y = float((gl_VertexIndex & 2) << 1);
|
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);
|
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 scale;
|
||||||
vec2 size;
|
vec2 size;
|
||||||
vec2 resize_factor;
|
vec2 resize_factor;
|
||||||
vec2 crop_offset;
|
|
||||||
float edge_sharpness;
|
float edge_sharpness;
|
||||||
};
|
};
|
||||||
layout(set = 0, binding = 0) uniform sampler2D sampler0;
|
layout(set = 0, binding = 0) uniform sampler2D sampler0;
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
layout( push_constant ) uniform constants {
|
layout( push_constant ) uniform constants {
|
||||||
vec4 ViewportInfo[1];
|
vec4 ViewportInfo[1];
|
||||||
vec2 ResizeFactor;
|
vec2 ResizeFactor;
|
||||||
vec2 CropOffset;
|
|
||||||
float EdgeSharpness;
|
float EdgeSharpness;
|
||||||
};
|
};
|
||||||
layout(set = 0, binding = 0) uniform sampler2D ps0;
|
layout(set = 0, binding = 0) uniform sampler2D ps0;
|
||||||
|
|||||||
@@ -226,6 +226,22 @@ void BufferCacheRuntime::BindIndexBuffer(Buffer& buffer, u32 offset, u32 size) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BufferCacheRuntime::BindVertexBuffer(u32 index, Buffer& buffer, u32 offset, u32 size,
|
||||||
|
u32 stride) {
|
||||||
|
if (index >= max_attributes) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (has_unified_vertex_buffers) {
|
||||||
|
buffer.MakeResident(GL_READ_ONLY);
|
||||||
|
glBindVertexBuffer(index, 0, 0, static_cast<GLsizei>(stride));
|
||||||
|
glBufferAddressRangeNV(GL_VERTEX_ATTRIB_ARRAY_ADDRESS_NV, index,
|
||||||
|
buffer.HostGpuAddr() + offset, static_cast<GLsizeiptr>(size));
|
||||||
|
} else {
|
||||||
|
glBindVertexBuffer(index, buffer.Handle(), static_cast<GLintptr>(offset),
|
||||||
|
static_cast<GLsizei>(stride));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings) {
|
void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings) {
|
||||||
// TODO: Should HostBindings provide the correct runtime types to avoid these transforms?
|
// TODO: Should HostBindings provide the correct runtime types to avoid these transforms?
|
||||||
std::array<GLuint, 32> buffer_handles;
|
std::array<GLuint, 32> buffer_handles;
|
||||||
|
|||||||
@@ -99,6 +99,8 @@ public:
|
|||||||
|
|
||||||
void BindIndexBuffer(Buffer& buffer, u32 offset, u32 size);
|
void BindIndexBuffer(Buffer& buffer, u32 offset, u32 size);
|
||||||
|
|
||||||
|
void BindVertexBuffer(u32 index, Buffer& buffer, u32 offset, u32 size, u32 stride);
|
||||||
|
|
||||||
void BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings);
|
void BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings);
|
||||||
|
|
||||||
void BindUniformBuffer(size_t stage, u32 binding_index, Buffer& buffer, u32 offset, u32 size);
|
void BindUniformBuffer(size_t stage, u32 binding_index, Buffer& buffer, u32 offset, u32 size);
|
||||||
|
|||||||
@@ -259,7 +259,6 @@ void RasterizerOpenGL::PrepareDraw(bool is_indexed, Func&& draw_func) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void RasterizerOpenGL::Draw(bool is_indexed, u32 instance_count) {
|
void RasterizerOpenGL::Draw(bool is_indexed, u32 instance_count) {
|
||||||
buffer_cache.SetDrawInstanceCount(instance_count);
|
|
||||||
PrepareDraw(is_indexed, [this, is_indexed, instance_count](GLenum primitive_mode) {
|
PrepareDraw(is_indexed, [this, is_indexed, instance_count](GLenum primitive_mode) {
|
||||||
const auto& draw_state = maxwell3d->draw_manager.draw_state;
|
const auto& draw_state = maxwell3d->draw_manager.draw_state;
|
||||||
const GLuint base_instance = GLuint(draw_state.base_instance);
|
const GLuint base_instance = GLuint(draw_state.base_instance);
|
||||||
@@ -305,7 +304,6 @@ void RasterizerOpenGL::Draw(bool is_indexed, u32 instance_count) {
|
|||||||
void RasterizerOpenGL::DrawIndirect() {
|
void RasterizerOpenGL::DrawIndirect() {
|
||||||
const auto& params = maxwell3d->draw_manager.indirect_state;
|
const auto& params = maxwell3d->draw_manager.indirect_state;
|
||||||
buffer_cache.SetDrawIndirect(¶ms);
|
buffer_cache.SetDrawIndirect(¶ms);
|
||||||
buffer_cache.SetDrawInstanceCount(0);
|
|
||||||
PrepareDraw(params.is_indexed, [this, ¶ms](GLenum primitive_mode) {
|
PrepareDraw(params.is_indexed, [this, ¶ms](GLenum primitive_mode) {
|
||||||
if (params.is_byte_count) {
|
if (params.is_byte_count) {
|
||||||
const GPUVAddr tfb_object_base_addr = params.indirect_start_address - 4U;
|
const GPUVAddr tfb_object_base_addr = params.indirect_start_address - 4U;
|
||||||
|
|||||||
@@ -231,7 +231,6 @@ ShaderCache::ShaderCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
|
|||||||
.has_broken_unsigned_image_offsets = true,
|
.has_broken_unsigned_image_offsets = true,
|
||||||
.has_broken_signed_operations = true,
|
.has_broken_signed_operations = true,
|
||||||
.has_broken_fp16_float_controls = false,
|
.has_broken_fp16_float_controls = false,
|
||||||
.has_broken_fp32_denorm_flush = false,
|
|
||||||
.has_gl_component_indexing_bug = device.HasComponentIndexingBug(),
|
.has_gl_component_indexing_bug = device.HasComponentIndexingBug(),
|
||||||
.has_gl_precise_bug = device.HasPreciseBug(),
|
.has_gl_precise_bug = device.HasPreciseBug(),
|
||||||
.has_gl_cbuf_ftou_bug = device.HasCbufFtouBug(),
|
.has_gl_cbuf_ftou_bug = device.HasCbufFtouBug(),
|
||||||
|
|||||||
@@ -75,8 +75,6 @@ public:
|
|||||||
|
|
||||||
void Finish();
|
void Finish();
|
||||||
|
|
||||||
void FlushDeferredClear() {}
|
|
||||||
|
|
||||||
StagingBufferMap UploadStagingBuffer(size_t size, bool deferred = false);
|
StagingBufferMap UploadStagingBuffer(size_t size, bool deferred = false);
|
||||||
|
|
||||||
StagingBufferMap DownloadStagingBuffer(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_EMULATED_COPIES = true;
|
||||||
static constexpr bool HAS_DEVICE_MEMORY_INFO = true;
|
static constexpr bool HAS_DEVICE_MEMORY_INFO = true;
|
||||||
static constexpr bool IMPLEMENTS_ASYNC_DOWNLOADS = true;
|
static constexpr bool IMPLEMENTS_ASYNC_DOWNLOADS = true;
|
||||||
static constexpr bool HAS_MSAA_DOWNLOADS = false;
|
|
||||||
|
|
||||||
using Runtime = OpenGL::TextureCacheRuntime;
|
using Runtime = OpenGL::TextureCacheRuntime;
|
||||||
using Image = OpenGL::Image;
|
using Image = OpenGL::Image;
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
#include "common/settings.h"
|
#include "common/settings.h"
|
||||||
#include "video_core/host_shaders/blit_color_float_frag_spv.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_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_msaa_frag_spv.h"
|
||||||
#include "video_core/host_shaders/blit_depth_stencil_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"
|
#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_depth_to_float_frag_spv.h"
|
||||||
#include "video_core/host_shaders/convert_float_to_depth_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_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_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/convert_s8d24_to_abgr8_frag_spv.h"
|
||||||
#include "video_core/host_shaders/full_screen_triangle_vert_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"
|
#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,
|
[[nodiscard]] vk::ImageView MakeMSAACopyView(const vk::Device& device, VkImage image,
|
||||||
VkFormat format, u32 base_level, u32 base_layer,
|
VkFormat format, u32 base_level) {
|
||||||
VkImageAspectFlags aspect_mask) {
|
|
||||||
return device.CreateImageView(VkImageViewCreateInfo{
|
return device.CreateImageView(VkImageViewCreateInfo{
|
||||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
@@ -554,10 +534,10 @@ void RecordShaderReadBarrier(Scheduler& scheduler, const ImageView& image_view)
|
|||||||
.a = VK_COMPONENT_SWIZZLE_IDENTITY,
|
.a = VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||||
},
|
},
|
||||||
.subresourceRange{
|
.subresourceRange{
|
||||||
.aspectMask = aspect_mask,
|
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||||
.baseMipLevel = base_level,
|
.baseMipLevel = base_level,
|
||||||
.levelCount = 1,
|
.levelCount = 1,
|
||||||
.baseArrayLayer = base_layer,
|
.baseArrayLayer = 0,
|
||||||
.layerCount = 1,
|
.layerCount = 1,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -606,17 +586,12 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_,
|
|||||||
msaa_copy_pipeline_layout(device.GetLogical().CreatePipelineLayout(PipelineLayoutCreateInfo(
|
msaa_copy_pipeline_layout(device.GetLogical().CreatePipelineLayout(PipelineLayoutCreateInfo(
|
||||||
one_texture_set_layout.address(),
|
one_texture_set_layout.address(),
|
||||||
PUSH_CONSTANT_RANGE<VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(MSAACopyPushConstants)>))),
|
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)),
|
full_screen_vert(BuildShader(device, FULL_SCREEN_TRIANGLE_VERT_SPV)),
|
||||||
blit_color_to_color_frag(BuildShader(device, BLIT_COLOR_FLOAT_FRAG_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_color_msaa_frag(BuildShader(device, BLIT_COLOR_MSAA_FRAG_SPV)),
|
||||||
blit_depth_stencil_frag(device.IsExtShaderStencilExportSupported()
|
blit_depth_stencil_frag(device.IsExtShaderStencilExportSupported()
|
||||||
? BuildShader(device, VULKAN_BLIT_DEPTH_STENCIL_FRAG_SPV)
|
? BuildShader(device, VULKAN_BLIT_DEPTH_STENCIL_FRAG_SPV)
|
||||||
: vk::ShaderModule{}),
|
: vk::ShaderModule{}),
|
||||||
blit_depth_frag(BuildShader(device, BLIT_DEPTH_FRAG_SPV)),
|
|
||||||
blit_depth_msaa_frag(BuildShader(device, BLIT_DEPTH_MSAA_FRAG_SPV)),
|
blit_depth_msaa_frag(BuildShader(device, BLIT_DEPTH_MSAA_FRAG_SPV)),
|
||||||
blit_depth_stencil_msaa_frag(device.IsExtShaderStencilExportSupported()
|
blit_depth_stencil_msaa_frag(device.IsExtShaderStencilExportSupported()
|
||||||
? BuildShader(device, BLIT_DEPTH_STENCIL_MSAA_FRAG_SPV)
|
? 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_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_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_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_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>)),
|
linear_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_LINEAR>)),
|
||||||
nearest_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_NEAREST>)) {}
|
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(),
|
.renderpass = dst_framebuffer->RenderPass(),
|
||||||
.operation = operation,
|
.operation = operation,
|
||||||
};
|
};
|
||||||
VkSampler sampler = *nearest_sampler;
|
const VkPipelineLayout layout = *one_texture_pipeline_layout;
|
||||||
if (is_linear) {
|
const VkSampler sampler = is_linear ? *linear_sampler : *nearest_sampler;
|
||||||
sampler = *linear_sampler;
|
const VkPipeline pipeline = FindOrEmplaceColorPipeline(key);
|
||||||
}
|
const VkImageView src_view = src_image_view.Handle(Shader::TextureType::Color2D);
|
||||||
BlitImpl(dst_framebuffer, src_image_view, dst_region, src_region,
|
|
||||||
FindOrEmplaceColorPipeline(key), sampler,
|
RecordShaderReadBarrier(scheduler, src_image_view);
|
||||||
src_image_view.Handle(Shader::TextureType::Color2D), VK_NULL_HANDLE, false);
|
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,
|
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,
|
void BlitImageHelper::BlitColorMSAA(const Framebuffer* dst_framebuffer,
|
||||||
const ImageView& src_image_view, const Region2D& dst_region,
|
const ImageView& src_image_view, const Region2D& dst_region,
|
||||||
const Region2D& src_region, VkPipeline pipeline, VkSampler sampler,
|
const Region2D& src_region) {
|
||||||
VkImageView src_view, VkImageView src_stencil_view,
|
const BlitMSAAPipelineKey key{
|
||||||
bool blit_stencil) {
|
.renderpass = dst_framebuffer->RenderPass(),
|
||||||
VkPipelineLayout layout = *one_texture_pipeline_layout;
|
.samples = dst_framebuffer->Samples(),
|
||||||
if (blit_stencil) {
|
};
|
||||||
layout = *two_textures_pipeline_layout;
|
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);
|
RecordShaderReadBarrier(scheduler, src_image_view);
|
||||||
scheduler.RequestRenderpass(dst_framebuffer);
|
scheduler.RequestRenderpass(dst_framebuffer);
|
||||||
scheduler.Record([this, dst_region, src_region, pipeline, layout, sampler, src_view,
|
scheduler.Record([this, dst_region, src_region, pipeline, layout, sampler,
|
||||||
src_stencil_view, blit_stencil](vk::CommandBuffer cmdbuf) {
|
src_view](vk::CommandBuffer cmdbuf) {
|
||||||
VkDescriptorSet descriptor_set = VK_NULL_HANDLE;
|
const VkDescriptorSet descriptor_set = one_texture_descriptor_allocator.Commit();
|
||||||
if (blit_stencil) {
|
UpdateOneTextureDescriptorSet(device, descriptor_set, sampler, src_view);
|
||||||
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);
|
|
||||||
}
|
|
||||||
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||||
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set,
|
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set,
|
||||||
nullptr);
|
nullptr);
|
||||||
@@ -734,56 +697,41 @@ void BlitImageHelper::BlitImpl(const Framebuffer* dst_framebuffer,
|
|||||||
scheduler.InvalidateState();
|
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,
|
void BlitImageHelper::ResolveDepthStencil(const Framebuffer* dst_framebuffer,
|
||||||
ImageView& src_image_view, const Region2D& dst_region,
|
ImageView& src_image_view, const Region2D& dst_region,
|
||||||
const Region2D& src_region) {
|
const Region2D& src_region) {
|
||||||
const bool resolve_stencil =
|
const bool resolve_stencil =
|
||||||
dst_framebuffer->HasAspectStencilBit() && device.IsExtShaderStencilExportSupported();
|
dst_framebuffer->HasAspectStencilBit() && device.IsExtShaderStencilExportSupported();
|
||||||
VkImageView src_stencil_view = VK_NULL_HANDLE;
|
const VkPipeline pipeline =
|
||||||
if (resolve_stencil) {
|
FindOrEmplaceResolveDepthStencilPipeline(dst_framebuffer->RenderPass(), resolve_stencil);
|
||||||
src_stencil_view = src_image_view.StencilView();
|
const VkPipelineLayout layout =
|
||||||
}
|
resolve_stencil ? *two_textures_pipeline_layout : *one_texture_pipeline_layout;
|
||||||
BlitImpl(dst_framebuffer, src_image_view, dst_region, src_region,
|
const VkSampler sampler = *nearest_sampler;
|
||||||
FindOrEmplaceResolveDepthStencilPipeline(dst_framebuffer->RenderPass(),
|
const VkImageView src_depth_view = src_image_view.DepthView();
|
||||||
resolve_stencil),
|
const VkImageView src_stencil_view =
|
||||||
*nearest_sampler, src_image_view.DepthView(), src_stencil_view, resolve_stencil);
|
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,
|
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,
|
const Region2D& dst_region, const Region2D& src_region,
|
||||||
Tegra::Engines::Fermi2D::Filter filter,
|
Tegra::Engines::Fermi2D::Filter filter,
|
||||||
Tegra::Engines::Fermi2D::Operation operation) {
|
Tegra::Engines::Fermi2D::Operation operation) {
|
||||||
|
if (!device.IsExtShaderStencilExportSupported()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
ASSERT(filter == Tegra::Engines::Fermi2D::Filter::Point);
|
ASSERT(filter == Tegra::Engines::Fermi2D::Filter::Point);
|
||||||
ASSERT(operation == Tegra::Engines::Fermi2D::Operation::SrcCopy);
|
ASSERT(operation == Tegra::Engines::Fermi2D::Operation::SrcCopy);
|
||||||
const bool blit_stencil = device.IsExtShaderStencilExportSupported();
|
|
||||||
const BlitImagePipelineKey key{
|
const BlitImagePipelineKey key{
|
||||||
.renderpass = dst_framebuffer->RenderPass(),
|
.renderpass = dst_framebuffer->RenderPass(),
|
||||||
.operation = operation,
|
.operation = operation,
|
||||||
};
|
};
|
||||||
VkPipeline pipeline{};
|
const VkPipelineLayout layout = *two_textures_pipeline_layout;
|
||||||
VkImageView src_stencil_view = VK_NULL_HANDLE;
|
const VkSampler sampler = *nearest_sampler;
|
||||||
if (blit_stencil) {
|
const VkPipeline pipeline = FindOrEmplaceDepthStencilPipeline(key);
|
||||||
pipeline = FindOrEmplaceDepthStencilPipeline(key);
|
const VkImageView src_depth_view = src_image_view.DepthView();
|
||||||
src_stencil_view = src_image_view.StencilView();
|
const VkImageView src_stencil_view = src_image_view.StencilView();
|
||||||
} else {
|
|
||||||
pipeline = FindOrEmplaceBlitDepthPipeline(key.renderpass);
|
RecordShaderReadBarrier(scheduler, src_image_view);
|
||||||
}
|
scheduler.RequestRenderpass(dst_framebuffer);
|
||||||
BlitImpl(dst_framebuffer, src_image_view, dst_region, src_region, pipeline, *nearest_sampler,
|
scheduler.Record([dst_region, src_region, pipeline, layout, sampler, src_depth_view,
|
||||||
src_image_view.DepthView(), src_stencil_view, blit_stencil);
|
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,
|
void BlitImageHelper::ConvertD32ToR32(const Framebuffer* dst_framebuffer,
|
||||||
@@ -922,183 +882,19 @@ void BlitImageHelper::ClearDepthStencil(const Framebuffer* dst_framebuffer, bool
|
|||||||
scheduler.InvalidateState();
|
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,
|
void BlitImageHelper::CopyMSAA(RenderPassCache& render_pass_cache, VkImage dst_image,
|
||||||
VideoCore::Surface::PixelFormat dst_format, VkImage src_image,
|
VideoCore::Surface::PixelFormat dst_format, VkImage src_image,
|
||||||
VideoCore::Surface::PixelFormat src_format, u32 num_samples,
|
VideoCore::Surface::PixelFormat src_format, u32 num_samples,
|
||||||
std::span<const VideoCommon::ImageCopy> copies,
|
std::span<const VideoCommon::ImageCopy> copies,
|
||||||
bool msaa_to_non_msaa) {
|
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 auto [samples_x, samples_y] = VideoCommon::SamplesLog2(static_cast<int>(num_samples));
|
||||||
const s32 scale_x = 1 << samples_x;
|
const s32 scale_x = 1 << samples_x;
|
||||||
const s32 scale_y = 1 << samples_y;
|
const s32 scale_y = 1 << samples_y;
|
||||||
VkSampleCountFlagBits samples = SampleCountFlag(num_samples);
|
const VkSampleCountFlagBits samples =
|
||||||
if (msaa_to_non_msaa) {
|
msaa_to_non_msaa ? VK_SAMPLE_COUNT_1_BIT : SampleCountFlag(num_samples);
|
||||||
samples = VK_SAMPLE_COUNT_1_BIT;
|
|
||||||
}
|
|
||||||
RenderPassKey renderpass_key{};
|
RenderPassKey renderpass_key{};
|
||||||
renderpass_key.color_formats.fill(VideoCore::Surface::PixelFormat::Invalid);
|
renderpass_key.color_formats.fill(VideoCore::Surface::PixelFormat::Invalid);
|
||||||
renderpass_key.color_formats[0] = dst_format;
|
renderpass_key.color_formats[0] = dst_format;
|
||||||
@@ -1109,35 +905,149 @@ void BlitImageHelper::CopyMSAA(RenderPassCache& render_pass_cache, VkImage dst_i
|
|||||||
.renderpass = renderpass,
|
.renderpass = renderpass,
|
||||||
.samples = samples,
|
.samples = samples,
|
||||||
.msaa_to_non_msaa = msaa_to_non_msaa,
|
.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 =
|
const VkFormat src_vk_format =
|
||||||
MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, true, src_format).format;
|
MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, true, src_format).format;
|
||||||
const VkFormat dst_vk_format =
|
const VkFormat dst_vk_format =
|
||||||
MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, true, dst_format).format;
|
MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, true, dst_format).format;
|
||||||
CopyMSAAImpl(renderpass, FindOrEmplaceMSAACopyPipeline(key), *msaa_copy_pipeline_layout,
|
for (const VideoCommon::ImageCopy& copy : copies) {
|
||||||
dst_image, dst_vk_format, src_image, src_vk_format, scale_x, scale_y, copies,
|
ASSERT(copy.src_subresource.base_layer == 0);
|
||||||
aspect_info, false);
|
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,
|
void BlitImageHelper::Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer,
|
||||||
@@ -1469,87 +1379,6 @@ VkPipeline BlitImageHelper::FindOrEmplaceBlitColorMSAAPipeline(const BlitMSAAPip
|
|||||||
return *blit_msaa_color_pipelines.back();
|
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,
|
VkPipeline BlitImageHelper::FindOrEmplaceResolveDepthStencilPipeline(VkRenderPass renderpass,
|
||||||
bool resolve_stencil) {
|
bool resolve_stencil) {
|
||||||
auto& keys = resolve_stencil ? resolve_depth_stencil_keys : resolve_depth_keys;
|
auto& keys = resolve_stencil ? resolve_depth_stencil_keys : resolve_depth_keys;
|
||||||
@@ -1588,83 +1417,15 @@ VkPipeline BlitImageHelper::FindOrEmplaceResolveDepthStencilPipeline(VkRenderPas
|
|||||||
return *pipelines.back();
|
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) {
|
VkPipeline BlitImageHelper::FindOrEmplaceMSAACopyPipeline(const MSAACopyPipelineKey& key) {
|
||||||
const auto it = std::ranges::find(msaa_copy_keys, key);
|
const auto it = std::ranges::find(msaa_copy_keys, key);
|
||||||
if (it != msaa_copy_keys.end()) {
|
if (it != msaa_copy_keys.end()) {
|
||||||
return *msaa_copy_pipelines[std::distance(msaa_copy_keys.begin(), it)];
|
return *msaa_copy_pipelines[std::distance(msaa_copy_keys.begin(), it)];
|
||||||
}
|
}
|
||||||
msaa_copy_keys.push_back(key);
|
msaa_copy_keys.push_back(key);
|
||||||
VkShaderModule frag_module = key.msaa_to_non_msaa ? *convert_msaa_to_non_msaa_frag
|
const std::array stages = MakeStages(*clear_color_vert, key.msaa_to_non_msaa
|
||||||
: *convert_non_msaa_to_msaa_frag;
|
? *convert_msaa_to_non_msaa_frag
|
||||||
if (key.format_class == MSAACopyFormatClass::SignedInteger) {
|
: *convert_non_msaa_to_msaa_frag);
|
||||||
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 VkPipelineMultisampleStateCreateInfo multisample_ci{
|
const VkPipelineMultisampleStateCreateInfo multisample_ci{
|
||||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
@@ -1701,85 +1462,6 @@ VkPipeline BlitImageHelper::FindOrEmplaceMSAACopyPipeline(const MSAACopyPipeline
|
|||||||
return *msaa_copy_pipelines.back();
|
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) {
|
void BlitImageHelper::ConvertDepthToColorPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass) {
|
||||||
ConvertPipeline(pipeline, renderpass, false);
|
ConvertPipeline(pipeline, renderpass, false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,19 +45,12 @@ struct BlitDepthStencilPipelineKey {
|
|||||||
u32 stencil_ref;
|
u32 stencil_ref;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class MSAACopyFormatClass : u32 {
|
|
||||||
Float,
|
|
||||||
SignedInteger,
|
|
||||||
UnsignedInteger,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct MSAACopyPipelineKey {
|
struct MSAACopyPipelineKey {
|
||||||
constexpr auto operator<=>(const MSAACopyPipelineKey&) const noexcept = default;
|
constexpr auto operator<=>(const MSAACopyPipelineKey&) const noexcept = default;
|
||||||
|
|
||||||
VkRenderPass renderpass;
|
VkRenderPass renderpass;
|
||||||
VkSampleCountFlagBits samples;
|
VkSampleCountFlagBits samples;
|
||||||
bool msaa_to_non_msaa;
|
bool msaa_to_non_msaa;
|
||||||
MSAACopyFormatClass format_class;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct BlitMSAAPipelineKey {
|
struct BlitMSAAPipelineKey {
|
||||||
@@ -85,12 +78,6 @@ public:
|
|||||||
void BlitColorMSAA(const Framebuffer* dst_framebuffer, const ImageView& src_image_view,
|
void BlitColorMSAA(const Framebuffer* dst_framebuffer, const ImageView& src_image_view,
|
||||||
const Region2D& dst_region, const Region2D& src_region);
|
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,
|
void ResolveDepthStencil(const Framebuffer* dst_framebuffer, ImageView& src_image_view,
|
||||||
const Region2D& dst_region, const Region2D& src_region);
|
const Region2D& dst_region, const Region2D& src_region);
|
||||||
|
|
||||||
@@ -129,39 +116,7 @@ public:
|
|||||||
VideoCore::Surface::PixelFormat src_format, u32 num_samples,
|
VideoCore::Surface::PixelFormat src_format, u32 num_samples,
|
||||||
std::span<const VideoCommon::ImageCopy> copies, bool msaa_to_non_msaa);
|
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:
|
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,
|
void Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer,
|
||||||
const ImageView& src_image_view);
|
const ImageView& src_image_view);
|
||||||
|
|
||||||
@@ -176,13 +131,7 @@ private:
|
|||||||
[[nodiscard]] VkPipeline FindOrEmplaceClearStencilPipeline(
|
[[nodiscard]] VkPipeline FindOrEmplaceClearStencilPipeline(
|
||||||
const BlitDepthStencilPipelineKey& key);
|
const BlitDepthStencilPipelineKey& key);
|
||||||
[[nodiscard]] VkPipeline FindOrEmplaceMSAACopyPipeline(const MSAACopyPipelineKey& key);
|
[[nodiscard]] VkPipeline FindOrEmplaceMSAACopyPipeline(const MSAACopyPipelineKey& key);
|
||||||
|
|
||||||
[[nodiscard]] VkPipeline FindOrEmplaceMSAACopyDepthPipeline(const MSAACopyPipelineKey& key,
|
|
||||||
bool copy_stencil);
|
|
||||||
[[nodiscard]] VkPipeline FindOrEmplaceBlitColorMSAAPipeline(const BlitMSAAPipelineKey& key);
|
[[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,
|
[[nodiscard]] VkPipeline FindOrEmplaceResolveDepthStencilPipeline(VkRenderPass renderpass,
|
||||||
bool resolve_stencil);
|
bool resolve_stencil);
|
||||||
|
|
||||||
@@ -213,12 +162,10 @@ private:
|
|||||||
vk::PipelineLayout two_textures_pipeline_layout;
|
vk::PipelineLayout two_textures_pipeline_layout;
|
||||||
vk::PipelineLayout clear_color_pipeline_layout;
|
vk::PipelineLayout clear_color_pipeline_layout;
|
||||||
vk::PipelineLayout msaa_copy_pipeline_layout;
|
vk::PipelineLayout msaa_copy_pipeline_layout;
|
||||||
vk::PipelineLayout msaa_copy_depth_stencil_pipeline_layout;
|
|
||||||
vk::ShaderModule full_screen_vert;
|
vk::ShaderModule full_screen_vert;
|
||||||
vk::ShaderModule blit_color_to_color_frag;
|
vk::ShaderModule blit_color_to_color_frag;
|
||||||
vk::ShaderModule blit_color_msaa_frag;
|
vk::ShaderModule blit_color_msaa_frag;
|
||||||
vk::ShaderModule blit_depth_stencil_frag;
|
vk::ShaderModule blit_depth_stencil_frag;
|
||||||
vk::ShaderModule blit_depth_frag;
|
|
||||||
vk::ShaderModule blit_depth_msaa_frag;
|
vk::ShaderModule blit_depth_msaa_frag;
|
||||||
vk::ShaderModule blit_depth_stencil_msaa_frag;
|
vk::ShaderModule blit_depth_stencil_msaa_frag;
|
||||||
vk::ShaderModule clear_color_vert;
|
vk::ShaderModule clear_color_vert;
|
||||||
@@ -232,15 +179,7 @@ private:
|
|||||||
vk::ShaderModule convert_d24s8_to_abgr8_frag;
|
vk::ShaderModule convert_d24s8_to_abgr8_frag;
|
||||||
vk::ShaderModule convert_s8d24_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_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_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 linear_sampler;
|
||||||
vk::Sampler nearest_sampler;
|
vk::Sampler nearest_sampler;
|
||||||
|
|
||||||
@@ -254,18 +193,8 @@ private:
|
|||||||
std::vector<vk::Pipeline> clear_stencil_pipelines;
|
std::vector<vk::Pipeline> clear_stencil_pipelines;
|
||||||
std::vector<MSAACopyPipelineKey> msaa_copy_keys;
|
std::vector<MSAACopyPipelineKey> msaa_copy_keys;
|
||||||
std::vector<vk::Pipeline> msaa_copy_pipelines;
|
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<BlitMSAAPipelineKey> blit_msaa_color_keys;
|
||||||
std::vector<vk::Pipeline> blit_msaa_color_pipelines;
|
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<VkRenderPass> resolve_depth_keys;
|
||||||
std::vector<vk::Pipeline> resolve_depth_pipelines;
|
std::vector<vk::Pipeline> resolve_depth_pipelines;
|
||||||
std::vector<VkRenderPass> resolve_depth_stencil_keys;
|
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)};
|
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;
|
if (null_image_view != VK_NULL_HANDLE) vk_image_view = null_image_view;
|
||||||
}
|
}
|
||||||
Sampler& sampler{texture_cache.GetSampler(sampler_id)};
|
const Sampler& sampler{texture_cache.GetSampler(sampler_id)};
|
||||||
guest_descriptor_queue.AddSampledImage(vk_image_view,
|
const bool use_fallback_sampler{sampler.HasAddedAnisotropy() &&
|
||||||
sampler.HandleFor(image_view, desc.is_depth));
|
!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)};
|
const bool element_rescaled{texture_cache.IsRescaling(image_view)};
|
||||||
is_rescaled |= element_rescaled;
|
is_rescaled |= element_rescaled;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/div_ceil.h"
|
#include "common/div_ceil.h"
|
||||||
#include "common/settings.h"
|
#include "common/settings.h"
|
||||||
@@ -19,7 +17,7 @@
|
|||||||
|
|
||||||
namespace Vulkan {
|
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)
|
SGSR::SGSR(const Device& device, MemoryAllocator& memory_allocator, size_t image_count, VkExtent2D extent, bool edge_dir)
|
||||||
: m_memory_allocator{memory_allocator}
|
: 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_width = f32(input_image_extent.width);
|
||||||
const f32 input_image_height = f32(input_image_extent.height);
|
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 viewport_width = (crop_rect.right - crop_rect.left) * input_image_width;
|
||||||
const f32 crop_height = (crop_rect.bottom - crop_rect.top) * input_image_height;
|
const f32 viewport_height = (crop_rect.bottom - crop_rect.top) * input_image_height;
|
||||||
static constexpr f32 EDGE_SHARPNESS_MAX = 2.0f;
|
// expected [0, 2]
|
||||||
const f32 edge_sharpness =
|
const f32 sharpening = f32(Settings::values.fsr_sharpening_slider.GetValue()) / 100.0f;
|
||||||
EDGE_SHARPNESS_MAX - f32(Settings::values.fsr_sharpening_slider.GetValue()) / 200.0f;
|
|
||||||
|
|
||||||
|
// p = (tex * viewport) / input = [0,n] (normalized texcoords)
|
||||||
|
// p * input = [0,1024], [0,768]
|
||||||
// layout( push_constant ) uniform constants {
|
// layout( push_constant ) uniform constants {
|
||||||
// highp vec4 ViewportInfo[1];
|
// highp vec4 ViewportInfo[1];
|
||||||
// highp vec2 ResizeFactor;
|
// highp vec2 ResizeFactor;
|
||||||
// highp vec2 CropOffset;
|
|
||||||
// highp float EdgeSharpness;
|
// highp float EdgeSharpness;
|
||||||
// };
|
// };
|
||||||
PushConstants viewport_con{};
|
PushConstants viewport_con{};
|
||||||
viewport_con[0] = std::bit_cast<u32>(1.f / input_image_width);
|
viewport_con[0] = std::bit_cast<u32>(std::abs(1.f / viewport_width));
|
||||||
viewport_con[1] = std::bit_cast<u32>(1.f / input_image_height);
|
viewport_con[1] = std::bit_cast<u32>(std::abs(1.f / viewport_height));
|
||||||
viewport_con[2] = std::bit_cast<u32>(input_image_width);
|
viewport_con[2] = std::bit_cast<u32>(std::abs(viewport_width));
|
||||||
viewport_con[3] = std::bit_cast<u32>(input_image_height);
|
viewport_con[3] = std::bit_cast<u32>(std::abs(viewport_height));
|
||||||
viewport_con[4] = std::bit_cast<u32>(crop_width / input_image_width);
|
viewport_con[4] = std::bit_cast<u32>(viewport_width / input_image_width);
|
||||||
viewport_con[5] = std::bit_cast<u32>(crop_height / input_image_height);
|
viewport_con[5] = std::bit_cast<u32>(viewport_height / input_image_height);
|
||||||
viewport_con[6] = std::bit_cast<u32>((std::min)(crop_rect.left, crop_rect.right));
|
viewport_con[6] = std::bit_cast<u32>(sharpening);
|
||||||
viewport_con[7] = std::bit_cast<u32>((std::min)(crop_rect.top, crop_rect.bottom));
|
|
||||||
viewport_con[8] = std::bit_cast<u32>(edge_sharpness);
|
|
||||||
|
|
||||||
UploadImages(device, scheduler);
|
UploadImages(device, scheduler);
|
||||||
UpdateDescriptorSets(device, source_image_view, image_index);
|
UpdateDescriptorSets(device, source_image_view, image_index);
|
||||||
|
|||||||
@@ -585,6 +585,29 @@ void BufferCacheRuntime::BindQuadIndexBuffer(PrimitiveTopology topology, u32 fir
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BufferCacheRuntime::BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size, u32 stride) {
|
||||||
|
if (index >= device.GetMaxVertexInputBindings()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (device.IsExtExtendedDynamicStateSupported()) {
|
||||||
|
scheduler.Record([index, buffer, offset, size, stride](vk::CommandBuffer cmdbuf) {
|
||||||
|
const VkDeviceSize vk_offset = buffer != VK_NULL_HANDLE ? offset : 0;
|
||||||
|
const VkDeviceSize vk_size = buffer != VK_NULL_HANDLE ? size : VK_WHOLE_SIZE;
|
||||||
|
const VkDeviceSize vk_stride = stride;
|
||||||
|
cmdbuf.BindVertexBuffers2EXT(index, 1, &buffer, &vk_offset, &vk_size, &vk_stride);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (!device.HasNullDescriptor() && buffer == VK_NULL_HANDLE) {
|
||||||
|
ReserveNullBuffer();
|
||||||
|
buffer = *null_buffer;
|
||||||
|
offset = 0;
|
||||||
|
}
|
||||||
|
scheduler.Record([index, buffer, offset](vk::CommandBuffer cmdbuf) {
|
||||||
|
cmdbuf.BindVertexBuffer(index, buffer, offset);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings) {
|
void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings) {
|
||||||
boost::container::static_vector<VkBuffer, VideoCommon::NUM_VERTEX_BUFFERS> buffer_handles(bindings.buffers.size());
|
boost::container::static_vector<VkBuffer, VideoCommon::NUM_VERTEX_BUFFERS> buffer_handles(bindings.buffers.size());
|
||||||
for (u32 i = 0; i < bindings.buffers.size(); ++i) {
|
for (u32 i = 0; i < bindings.buffers.size(); ++i) {
|
||||||
|
|||||||
@@ -138,6 +138,8 @@ public:
|
|||||||
|
|
||||||
void BindQuadIndexBuffer(PrimitiveTopology topology, u32 first, u32 count);
|
void BindQuadIndexBuffer(PrimitiveTopology topology, u32 first, u32 count);
|
||||||
|
|
||||||
|
void BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size, u32 stride);
|
||||||
|
|
||||||
void BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings);
|
void BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings);
|
||||||
|
|
||||||
void BindTransformFeedbackBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size);
|
void BindTransformFeedbackBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size);
|
||||||
|
|||||||
@@ -22,13 +22,7 @@
|
|||||||
#include "video_core/host_shaders/resolve_conditional_render_comp_spv.h"
|
#include "video_core/host_shaders/resolve_conditional_render_comp_spv.h"
|
||||||
#include "video_core/host_shaders/vulkan_quad_indexed_comp_spv.h"
|
#include "video_core/host_shaders/vulkan_quad_indexed_comp_spv.h"
|
||||||
#include "video_core/host_shaders/vulkan_uint8_comp_spv.h"
|
#include "video_core/host_shaders/vulkan_uint8_comp_spv.h"
|
||||||
#include "video_core/host_shaders/block_linear_unswizzle_2d_comp_spv.h"
|
|
||||||
#include "video_core/host_shaders/block_linear_unswizzle_2d_nonarrow_comp_spv.h"
|
|
||||||
#include "video_core/host_shaders/block_linear_unswizzle_3d_bcn_comp_spv.h"
|
#include "video_core/host_shaders/block_linear_unswizzle_3d_bcn_comp_spv.h"
|
||||||
#include "video_core/host_shaders/block_linear_unswizzle_3d_comp_spv.h"
|
|
||||||
#include "video_core/host_shaders/block_linear_unswizzle_3d_nonarrow_comp_spv.h"
|
|
||||||
#include "video_core/host_shaders/pitch_unswizzle_comp_spv.h"
|
|
||||||
#include "video_core/host_shaders/pitch_unswizzle_nonarrow_comp_spv.h"
|
|
||||||
#include "video_core/renderer_vulkan/vk_compute_pass.h"
|
#include "video_core/renderer_vulkan/vk_compute_pass.h"
|
||||||
#include "video_core/surface.h"
|
#include "video_core/surface.h"
|
||||||
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
|
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
|
||||||
@@ -878,291 +872,4 @@ void BlockLinearUnswizzle3DPass::UnswizzleChunk(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
constexpr u32 UNSWIZZLE_BINDING_INPUT_BUFFER = 0;
|
|
||||||
constexpr u32 UNSWIZZLE_BINDING_OUTPUT_IMAGE = 1;
|
|
||||||
constexpr size_t UNSWIZZLE_NUM_BINDINGS = 2;
|
|
||||||
|
|
||||||
constexpr std::array<VkDescriptorSetLayoutBinding, UNSWIZZLE_NUM_BINDINGS>
|
|
||||||
UNSWIZZLE_DESCRIPTOR_SET_BINDINGS{{
|
|
||||||
{
|
|
||||||
.binding = UNSWIZZLE_BINDING_INPUT_BUFFER,
|
|
||||||
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
|
|
||||||
.descriptorCount = 1,
|
|
||||||
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
|
|
||||||
.pImmutableSamplers = nullptr,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.binding = UNSWIZZLE_BINDING_OUTPUT_IMAGE,
|
|
||||||
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
|
|
||||||
.descriptorCount = 1,
|
|
||||||
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
|
|
||||||
.pImmutableSamplers = nullptr,
|
|
||||||
},
|
|
||||||
}};
|
|
||||||
|
|
||||||
constexpr std::array<VkDescriptorUpdateTemplateEntry, UNSWIZZLE_NUM_BINDINGS>
|
|
||||||
UNSWIZZLE_DESCRIPTOR_UPDATE_TEMPLATE{{
|
|
||||||
{
|
|
||||||
.dstBinding = UNSWIZZLE_BINDING_INPUT_BUFFER,
|
|
||||||
.dstArrayElement = 0,
|
|
||||||
.descriptorCount = 1,
|
|
||||||
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
|
|
||||||
.offset = UNSWIZZLE_BINDING_INPUT_BUFFER * sizeof(DescriptorUpdateEntry),
|
|
||||||
.stride = sizeof(DescriptorUpdateEntry),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.dstBinding = UNSWIZZLE_BINDING_OUTPUT_IMAGE,
|
|
||||||
.dstArrayElement = 0,
|
|
||||||
.descriptorCount = 1,
|
|
||||||
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
|
|
||||||
.offset = UNSWIZZLE_BINDING_OUTPUT_IMAGE * sizeof(DescriptorUpdateEntry),
|
|
||||||
.stride = sizeof(DescriptorUpdateEntry),
|
|
||||||
},
|
|
||||||
}};
|
|
||||||
|
|
||||||
constexpr DescriptorBankInfo UNSWIZZLE_BANK_INFO{
|
|
||||||
.uniform_buffers = 0,
|
|
||||||
.storage_buffers = 1,
|
|
||||||
.texture_buffers = 0,
|
|
||||||
.image_buffers = 0,
|
|
||||||
.textures = 0,
|
|
||||||
.images = 1,
|
|
||||||
.score = 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
[[nodiscard]] std::span<const u32> UnswizzleSpv(const Device& device,
|
|
||||||
std::span<const u32> extended,
|
|
||||||
std::span<const u32> narrow) {
|
|
||||||
if (device.IsStorageBuffer8BitAccessSupported() &&
|
|
||||||
device.IsStorageBuffer16BitAccessSupported()) {
|
|
||||||
return extended;
|
|
||||||
}
|
|
||||||
return narrow;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct PitchUnswizzlePushConstants {
|
|
||||||
alignas(8) std::array<u32, 2> origin;
|
|
||||||
alignas(8) std::array<s32, 2> destination;
|
|
||||||
u32 bytes_per_block;
|
|
||||||
u32 pitch;
|
|
||||||
};
|
|
||||||
|
|
||||||
void RecordUnswizzleEntryBarrier(Scheduler& scheduler, VkPipeline vk_pipeline, VkImage vk_image,
|
|
||||||
VkImageAspectFlags aspect_mask, bool is_initialized) {
|
|
||||||
scheduler.Record([vk_pipeline, vk_image, aspect_mask,
|
|
||||||
is_initialized](vk::CommandBuffer cmdbuf) {
|
|
||||||
VkAccessFlags src_access = VK_ACCESS_NONE;
|
|
||||||
VkImageLayout old_layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
||||||
if (is_initialized) {
|
|
||||||
src_access = VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT |
|
|
||||||
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
|
||||||
old_layout = VK_IMAGE_LAYOUT_GENERAL;
|
|
||||||
}
|
|
||||||
const VkImageMemoryBarrier image_barrier{
|
|
||||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
|
||||||
.pNext = nullptr,
|
|
||||||
.srcAccessMask = src_access,
|
|
||||||
.dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
|
|
||||||
.oldLayout = old_layout,
|
|
||||||
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
|
||||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
|
||||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
|
||||||
.image = vk_image,
|
|
||||||
.subresourceRange{
|
|
||||||
.aspectMask = aspect_mask,
|
|
||||||
.baseMipLevel = 0,
|
|
||||||
.levelCount = VK_REMAINING_MIP_LEVELS,
|
|
||||||
.baseArrayLayer = 0,
|
|
||||||
.layerCount = VK_REMAINING_ARRAY_LAYERS,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
VkPipelineStageFlags src_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
|
||||||
if (is_initialized) {
|
|
||||||
src_stage = vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER;
|
|
||||||
}
|
|
||||||
cmdbuf.PipelineBarrier(src_stage, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, image_barrier);
|
|
||||||
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, vk_pipeline);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void RecordUnswizzleExitBarrier(Scheduler& scheduler, VkImage vk_image,
|
|
||||||
VkImageAspectFlags aspect_mask) {
|
|
||||||
scheduler.Record([vk_image, aspect_mask](vk::CommandBuffer cmdbuf) {
|
|
||||||
const VkImageMemoryBarrier image_barrier{
|
|
||||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
|
||||||
.pNext = nullptr,
|
|
||||||
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
|
|
||||||
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT |
|
|
||||||
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT,
|
|
||||||
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
|
|
||||||
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
|
||||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
|
||||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
|
||||||
.image = vk_image,
|
|
||||||
.subresourceRange{
|
|
||||||
.aspectMask = aspect_mask,
|
|
||||||
.baseMipLevel = 0,
|
|
||||||
.levelCount = VK_REMAINING_MIP_LEVELS,
|
|
||||||
.baseArrayLayer = 0,
|
|
||||||
.layerCount = VK_REMAINING_ARRAY_LAYERS,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
|
||||||
vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER, 0, image_barrier);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
} // Anonymous namespace
|
|
||||||
|
|
||||||
BlockLinearUnswizzle2DPass::BlockLinearUnswizzle2DPass(
|
|
||||||
const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
|
|
||||||
ComputePassDescriptorQueue& compute_pass_descriptor_queue_)
|
|
||||||
: ComputePass(device_, scheduler_, descriptor_pool_, UNSWIZZLE_DESCRIPTOR_SET_BINDINGS,
|
|
||||||
UNSWIZZLE_DESCRIPTOR_UPDATE_TEMPLATE, UNSWIZZLE_BANK_INFO,
|
|
||||||
COMPUTE_PUSH_CONSTANT_RANGE<sizeof(
|
|
||||||
VideoCommon::Accelerated::BlockLinearSwizzle2DParams)>,
|
|
||||||
UnswizzleSpv(device_, BLOCK_LINEAR_UNSWIZZLE_2D_COMP_SPV,
|
|
||||||
BLOCK_LINEAR_UNSWIZZLE_2D_NONARROW_COMP_SPV)),
|
|
||||||
scheduler{scheduler_}, compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {}
|
|
||||||
|
|
||||||
BlockLinearUnswizzle2DPass::~BlockLinearUnswizzle2DPass() = default;
|
|
||||||
|
|
||||||
void BlockLinearUnswizzle2DPass::Unswizzle(
|
|
||||||
Image& image, const StagingBufferRef& map,
|
|
||||||
std::span<const VideoCommon::SwizzleParameters> swizzles) {
|
|
||||||
using namespace VideoCommon::Accelerated;
|
|
||||||
scheduler.RequestOutsideRenderPassOperationContext();
|
|
||||||
const VkPipeline vk_pipeline = *pipeline;
|
|
||||||
const VkImageAspectFlags aspect_mask = image.AspectMask();
|
|
||||||
const VkImage vk_image = image.Handle();
|
|
||||||
const bool is_initialized = image.ExchangeInitialization();
|
|
||||||
RecordUnswizzleEntryBarrier(scheduler, vk_pipeline, vk_image, aspect_mask, is_initialized);
|
|
||||||
|
|
||||||
const u32 num_layers = static_cast<u32>(image.info.resources.layers);
|
|
||||||
for (const VideoCommon::SwizzleParameters& swizzle : swizzles) {
|
|
||||||
const size_t input_offset = swizzle.buffer_offset + map.offset;
|
|
||||||
const u32 num_dispatches_x = Common::DivCeil(swizzle.num_tiles.width, 32U);
|
|
||||||
const u32 num_dispatches_y = Common::DivCeil(swizzle.num_tiles.height, 32U);
|
|
||||||
|
|
||||||
compute_pass_descriptor_queue.Acquire(scheduler, 2);
|
|
||||||
compute_pass_descriptor_queue.AddBuffer(map.buffer, input_offset,
|
|
||||||
image.guest_size_bytes - swizzle.buffer_offset);
|
|
||||||
compute_pass_descriptor_queue.AddImage(image.StorageImageView(swizzle.level));
|
|
||||||
const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()};
|
|
||||||
|
|
||||||
const auto params = MakeBlockLinearSwizzle2DParams(swizzle, image.info);
|
|
||||||
scheduler.Record([this, num_dispatches_x, num_dispatches_y, num_layers, params,
|
|
||||||
descriptor_data](vk::CommandBuffer cmdbuf) {
|
|
||||||
const VkDescriptorSet set = descriptor_allocator.Commit();
|
|
||||||
device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
|
|
||||||
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
|
|
||||||
cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, params);
|
|
||||||
cmdbuf.Dispatch(num_dispatches_x, num_dispatches_y, num_layers);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
RecordUnswizzleExitBarrier(scheduler, vk_image, aspect_mask);
|
|
||||||
}
|
|
||||||
|
|
||||||
BlockLinearUnswizzleImage3DPass::BlockLinearUnswizzleImage3DPass(
|
|
||||||
const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
|
|
||||||
ComputePassDescriptorQueue& compute_pass_descriptor_queue_)
|
|
||||||
: ComputePass(device_, scheduler_, descriptor_pool_, UNSWIZZLE_DESCRIPTOR_SET_BINDINGS,
|
|
||||||
UNSWIZZLE_DESCRIPTOR_UPDATE_TEMPLATE, UNSWIZZLE_BANK_INFO,
|
|
||||||
COMPUTE_PUSH_CONSTANT_RANGE<sizeof(BlockLinearSwizzle3DParams)>,
|
|
||||||
UnswizzleSpv(device_, BLOCK_LINEAR_UNSWIZZLE_3D_COMP_SPV,
|
|
||||||
BLOCK_LINEAR_UNSWIZZLE_3D_NONARROW_COMP_SPV)),
|
|
||||||
scheduler{scheduler_}, compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {}
|
|
||||||
|
|
||||||
BlockLinearUnswizzleImage3DPass::~BlockLinearUnswizzleImage3DPass() = default;
|
|
||||||
|
|
||||||
void BlockLinearUnswizzleImage3DPass::Unswizzle(
|
|
||||||
Image& image, const StagingBufferRef& map,
|
|
||||||
std::span<const VideoCommon::SwizzleParameters> swizzles) {
|
|
||||||
using namespace VideoCommon::Accelerated;
|
|
||||||
scheduler.RequestOutsideRenderPassOperationContext();
|
|
||||||
const VkPipeline vk_pipeline = *pipeline;
|
|
||||||
const VkImageAspectFlags aspect_mask = image.AspectMask();
|
|
||||||
const VkImage vk_image = image.Handle();
|
|
||||||
const bool is_initialized = image.ExchangeInitialization();
|
|
||||||
RecordUnswizzleEntryBarrier(scheduler, vk_pipeline, vk_image, aspect_mask, is_initialized);
|
|
||||||
|
|
||||||
for (const VideoCommon::SwizzleParameters& swizzle : swizzles) {
|
|
||||||
const size_t input_offset = swizzle.buffer_offset + map.offset;
|
|
||||||
const u32 num_dispatches_x = Common::DivCeil(swizzle.num_tiles.width, 16U);
|
|
||||||
const u32 num_dispatches_y = Common::DivCeil(swizzle.num_tiles.height, 8U);
|
|
||||||
const u32 num_dispatches_z = Common::DivCeil(swizzle.num_tiles.depth, 8U);
|
|
||||||
|
|
||||||
compute_pass_descriptor_queue.Acquire(scheduler, 2);
|
|
||||||
compute_pass_descriptor_queue.AddBuffer(map.buffer, input_offset,
|
|
||||||
image.guest_size_bytes - swizzle.buffer_offset);
|
|
||||||
compute_pass_descriptor_queue.AddImage(image.StorageImageView(swizzle.level));
|
|
||||||
const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()};
|
|
||||||
|
|
||||||
const auto params = MakeBlockLinearSwizzle3DParams(swizzle, image.info);
|
|
||||||
scheduler.Record([this, num_dispatches_x, num_dispatches_y, num_dispatches_z, params,
|
|
||||||
descriptor_data](vk::CommandBuffer cmdbuf) {
|
|
||||||
const VkDescriptorSet set = descriptor_allocator.Commit();
|
|
||||||
device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
|
|
||||||
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
|
|
||||||
cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, params);
|
|
||||||
cmdbuf.Dispatch(num_dispatches_x, num_dispatches_y, num_dispatches_z);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
RecordUnswizzleExitBarrier(scheduler, vk_image, aspect_mask);
|
|
||||||
}
|
|
||||||
|
|
||||||
PitchUnswizzlePass::PitchUnswizzlePass(
|
|
||||||
const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
|
|
||||||
ComputePassDescriptorQueue& compute_pass_descriptor_queue_)
|
|
||||||
: ComputePass(device_, scheduler_, descriptor_pool_, UNSWIZZLE_DESCRIPTOR_SET_BINDINGS,
|
|
||||||
UNSWIZZLE_DESCRIPTOR_UPDATE_TEMPLATE, UNSWIZZLE_BANK_INFO,
|
|
||||||
COMPUTE_PUSH_CONSTANT_RANGE<sizeof(PitchUnswizzlePushConstants)>,
|
|
||||||
UnswizzleSpv(device_, PITCH_UNSWIZZLE_COMP_SPV,
|
|
||||||
PITCH_UNSWIZZLE_NONARROW_COMP_SPV)),
|
|
||||||
scheduler{scheduler_}, compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {}
|
|
||||||
|
|
||||||
PitchUnswizzlePass::~PitchUnswizzlePass() = default;
|
|
||||||
|
|
||||||
void PitchUnswizzlePass::Unswizzle(Image& image, const StagingBufferRef& map,
|
|
||||||
std::span<const VideoCommon::SwizzleParameters> swizzles) {
|
|
||||||
scheduler.RequestOutsideRenderPassOperationContext();
|
|
||||||
const VkPipeline vk_pipeline = *pipeline;
|
|
||||||
const VkImageAspectFlags aspect_mask = image.AspectMask();
|
|
||||||
const VkImage vk_image = image.Handle();
|
|
||||||
const bool is_initialized = image.ExchangeInitialization();
|
|
||||||
RecordUnswizzleEntryBarrier(scheduler, vk_pipeline, vk_image, aspect_mask, is_initialized);
|
|
||||||
|
|
||||||
const u32 bytes_per_block = VideoCore::Surface::BytesPerBlock(image.info.format);
|
|
||||||
const u32 pitch = image.info.pitch;
|
|
||||||
for (const VideoCommon::SwizzleParameters& swizzle : swizzles) {
|
|
||||||
const size_t input_offset = swizzle.buffer_offset + map.offset;
|
|
||||||
const u32 num_dispatches_x = Common::DivCeil(swizzle.num_tiles.width, 32U);
|
|
||||||
const u32 num_dispatches_y = Common::DivCeil(swizzle.num_tiles.height, 32U);
|
|
||||||
|
|
||||||
compute_pass_descriptor_queue.Acquire(scheduler, 2);
|
|
||||||
compute_pass_descriptor_queue.AddBuffer(map.buffer, input_offset,
|
|
||||||
image.guest_size_bytes - swizzle.buffer_offset);
|
|
||||||
compute_pass_descriptor_queue.AddImage(image.StorageImageView(swizzle.level));
|
|
||||||
const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()};
|
|
||||||
|
|
||||||
const PitchUnswizzlePushConstants params{
|
|
||||||
.origin{0, 0},
|
|
||||||
.destination{0, 0},
|
|
||||||
.bytes_per_block = bytes_per_block,
|
|
||||||
.pitch = pitch,
|
|
||||||
};
|
|
||||||
scheduler.Record([this, num_dispatches_x, num_dispatches_y, params,
|
|
||||||
descriptor_data](vk::CommandBuffer cmdbuf) {
|
|
||||||
const VkDescriptorSet set = descriptor_allocator.Commit();
|
|
||||||
device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
|
|
||||||
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
|
|
||||||
cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, params);
|
|
||||||
cmdbuf.Dispatch(num_dispatches_x, num_dispatches_y, 1);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
RecordUnswizzleExitBarrier(scheduler, vk_image, aspect_mask);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Vulkan
|
} // namespace Vulkan
|
||||||
|
|||||||
@@ -164,49 +164,4 @@ private:
|
|||||||
ComputePassDescriptorQueue& compute_pass_descriptor_queue;
|
ComputePassDescriptorQueue& compute_pass_descriptor_queue;
|
||||||
};
|
};
|
||||||
|
|
||||||
class BlockLinearUnswizzle2DPass final : public ComputePass {
|
|
||||||
public:
|
|
||||||
explicit BlockLinearUnswizzle2DPass(
|
|
||||||
const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
|
|
||||||
ComputePassDescriptorQueue& compute_pass_descriptor_queue_);
|
|
||||||
~BlockLinearUnswizzle2DPass();
|
|
||||||
|
|
||||||
void Unswizzle(Image& image, const StagingBufferRef& map,
|
|
||||||
std::span<const VideoCommon::SwizzleParameters> swizzles);
|
|
||||||
|
|
||||||
private:
|
|
||||||
Scheduler& scheduler;
|
|
||||||
ComputePassDescriptorQueue& compute_pass_descriptor_queue;
|
|
||||||
};
|
|
||||||
|
|
||||||
class BlockLinearUnswizzleImage3DPass final : public ComputePass {
|
|
||||||
public:
|
|
||||||
explicit BlockLinearUnswizzleImage3DPass(
|
|
||||||
const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
|
|
||||||
ComputePassDescriptorQueue& compute_pass_descriptor_queue_);
|
|
||||||
~BlockLinearUnswizzleImage3DPass();
|
|
||||||
|
|
||||||
void Unswizzle(Image& image, const StagingBufferRef& map,
|
|
||||||
std::span<const VideoCommon::SwizzleParameters> swizzles);
|
|
||||||
|
|
||||||
private:
|
|
||||||
Scheduler& scheduler;
|
|
||||||
ComputePassDescriptorQueue& compute_pass_descriptor_queue;
|
|
||||||
};
|
|
||||||
|
|
||||||
class PitchUnswizzlePass final : public ComputePass {
|
|
||||||
public:
|
|
||||||
explicit PitchUnswizzlePass(const Device& device_, Scheduler& scheduler_,
|
|
||||||
DescriptorPool& descriptor_pool_,
|
|
||||||
ComputePassDescriptorQueue& compute_pass_descriptor_queue_);
|
|
||||||
~PitchUnswizzlePass();
|
|
||||||
|
|
||||||
void Unswizzle(Image& image, const StagingBufferRef& map,
|
|
||||||
std::span<const VideoCommon::SwizzleParameters> swizzles);
|
|
||||||
|
|
||||||
private:
|
|
||||||
Scheduler& scheduler;
|
|
||||||
ComputePassDescriptorQueue& compute_pass_descriptor_queue;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Vulkan
|
} // namespace Vulkan
|
||||||
|
|||||||
@@ -694,11 +694,9 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
|
|||||||
const size_t num_vertex_arrays = (std::min)(
|
const size_t num_vertex_arrays = (std::min)(
|
||||||
Maxwell::NumVertexArrays, static_cast<size_t>(device.GetMaxVertexInputBindings()));
|
Maxwell::NumVertexArrays, static_cast<size_t>(device.GetMaxVertexInputBindings()));
|
||||||
for (size_t index = 0; index < num_vertex_arrays; ++index) {
|
for (size_t index = 0; index < num_vertex_arrays; ++index) {
|
||||||
const bool instanced = ((key.state.enabled_divisors >> index) & 1) != 0;
|
const bool instanced = key.state.binding_divisors[index] != 0;
|
||||||
auto rate = VK_VERTEX_INPUT_RATE_VERTEX;
|
const auto rate =
|
||||||
if (instanced) {
|
instanced ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX;
|
||||||
rate = VK_VERTEX_INPUT_RATE_INSTANCE;
|
|
||||||
}
|
|
||||||
vertex_bindings.push_back({
|
vertex_bindings.push_back({
|
||||||
.binding = static_cast<u32>(index),
|
.binding = static_cast<u32>(index),
|
||||||
.stride = key.state.vertex_strides[index],
|
.stride = key.state.vertex_strides[index],
|
||||||
@@ -707,7 +705,7 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
|
|||||||
if (instanced) {
|
if (instanced) {
|
||||||
vertex_binding_divisors.push_back({
|
vertex_binding_divisors.push_back({
|
||||||
.binding = static_cast<u32>(index),
|
.binding = static_cast<u32>(index),
|
||||||
.divisor = device.GetVertexAttribDivisor(key.state.binding_divisors[index]),
|
.divisor = key.state.binding_divisors[index],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -444,7 +444,6 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
|
|||||||
.has_broken_unsigned_image_offsets = false,
|
.has_broken_unsigned_image_offsets = false,
|
||||||
.has_broken_signed_operations = false,
|
.has_broken_signed_operations = false,
|
||||||
.has_broken_fp16_float_controls = driver_id == VK_DRIVER_ID_NVIDIA_PROPRIETARY,
|
.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,
|
.ignore_nan_fp_comparisons = false,
|
||||||
.has_broken_spirv_subgroup_mask_vector_extract_dynamic = false,
|
.has_broken_spirv_subgroup_mask_vector_extract_dynamic = false,
|
||||||
.has_broken_robust =
|
.has_broken_robust =
|
||||||
|
|||||||
@@ -260,7 +260,6 @@ void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void RasterizerVulkan::Draw(bool is_indexed, u32 instance_count) {
|
void RasterizerVulkan::Draw(bool is_indexed, u32 instance_count) {
|
||||||
buffer_cache.SetDrawInstanceCount(instance_count);
|
|
||||||
PrepareDraw(is_indexed, [this, is_indexed, instance_count] {
|
PrepareDraw(is_indexed, [this, is_indexed, instance_count] {
|
||||||
const auto& draw_state = maxwell3d->draw_manager.draw_state;
|
const auto& draw_state = maxwell3d->draw_manager.draw_state;
|
||||||
const u32 num_instances{instance_count};
|
const u32 num_instances{instance_count};
|
||||||
@@ -296,7 +295,6 @@ void RasterizerVulkan::Draw(bool is_indexed, u32 instance_count) {
|
|||||||
void RasterizerVulkan::DrawIndirect() {
|
void RasterizerVulkan::DrawIndirect() {
|
||||||
const auto& params = maxwell3d->draw_manager.indirect_state;
|
const auto& params = maxwell3d->draw_manager.indirect_state;
|
||||||
buffer_cache.SetDrawIndirect(¶ms);
|
buffer_cache.SetDrawIndirect(¶ms);
|
||||||
buffer_cache.SetDrawInstanceCount(0);
|
|
||||||
PrepareDraw(params.is_indexed, [this, ¶ms] {
|
PrepareDraw(params.is_indexed, [this, ¶ms] {
|
||||||
const auto indirect_buffer = buffer_cache.GetDrawIndirectBuffer();
|
const auto indirect_buffer = buffer_cache.GetDrawIndirectBuffer();
|
||||||
const auto& buffer = indirect_buffer.first;
|
const auto& buffer = indirect_buffer.first;
|
||||||
@@ -426,12 +424,24 @@ void RasterizerVulkan::Clear(u32 layer_count) {
|
|||||||
const bool ds_deferrable =
|
const bool ds_deferrable =
|
||||||
!ds_used || ((!framebuffer->HasAspectDepthBit() || use_depth) &&
|
!ds_used || ((!framebuffer->HasAspectDepthBit() || use_depth) &&
|
||||||
(!framebuffer->HasAspectStencilBit() || use_stencil) && !stencil_partial);
|
(!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 up_scale = 1;
|
||||||
u32 down_shift = 0;
|
u32 down_shift = 0;
|
||||||
if (texture_cache.IsRescaling()) {
|
if (texture_cache.IsRescaling()) {
|
||||||
up_scale = Settings::values.resolution_info.up_scale;
|
up_scale = Settings::values.resolution_info.up_scale;
|
||||||
down_shift = Settings::values.resolution_info.down_shift;
|
down_shift = Settings::values.resolution_info.down_shift;
|
||||||
}
|
}
|
||||||
|
UpdateViewportsState(regs);
|
||||||
|
|
||||||
VkRect2D default_scissor{};
|
VkRect2D default_scissor{};
|
||||||
default_scissor.offset.x = 0;
|
default_scissor.offset.x = 0;
|
||||||
default_scissor.offset.y = 0;
|
default_scissor.offset.y = 0;
|
||||||
@@ -487,22 +497,6 @@ void RasterizerVulkan::Clear(u32 layer_count) {
|
|||||||
return;
|
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;
|
const u32 color_attachment = regs.clear_surface.RT;
|
||||||
if (use_color && framebuffer->HasAspectColorBit(color_attachment)) {
|
if (use_color && framebuffer->HasAspectColorBit(color_attachment)) {
|
||||||
const auto format = VideoCore::Surface::PixelFormatFromRenderTargetFormat(regs.rt[color_attachment].format);
|
const auto format = VideoCore::Surface::PixelFormatFromRenderTargetFormat(regs.rt[color_attachment].format);
|
||||||
@@ -1919,19 +1913,13 @@ void RasterizerVulkan::UpdateVertexInput(Tegra::Engines::Maxwell3D::Regs& regs)
|
|||||||
for (u32 binding = 0; binding < max_bindings; ++binding) {
|
for (u32 binding = 0; binding < max_bindings; ++binding) {
|
||||||
const auto& input_binding{regs.vertex_streams[binding]};
|
const auto& input_binding{regs.vertex_streams[binding]};
|
||||||
const bool is_instanced{regs.vertex_stream_instances.IsInstancingEnabled(binding)};
|
const bool is_instanced{regs.vertex_stream_instances.IsInstancingEnabled(binding)};
|
||||||
auto input_rate = VK_VERTEX_INPUT_RATE_VERTEX;
|
|
||||||
u32 divisor = 1;
|
|
||||||
if (is_instanced) {
|
|
||||||
input_rate = VK_VERTEX_INPUT_RATE_INSTANCE;
|
|
||||||
divisor = device.GetVertexAttribDivisor(input_binding.frequency);
|
|
||||||
}
|
|
||||||
bindings.push_back({
|
bindings.push_back({
|
||||||
.sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_BINDING_DESCRIPTION_2_EXT,
|
.sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_BINDING_DESCRIPTION_2_EXT,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
.binding = binding,
|
.binding = binding,
|
||||||
.stride = input_binding.stride,
|
.stride = input_binding.stride,
|
||||||
.inputRate = input_rate,
|
.inputRate = is_instanced ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX,
|
||||||
.divisor = divisor,
|
.divisor = is_instanced ? input_binding.frequency : 1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,72 +65,8 @@ using VideoCore::Surface::SurfaceType;
|
|||||||
.finalLayout = VK_IMAGE_LAYOUT_GENERAL,
|
.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
|
} // 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_} {}
|
RenderPassCache::RenderPassCache(const Device& device_) : device{&device_} {}
|
||||||
|
|
||||||
VkRenderPass RenderPassCache::Get(const RenderPassKey& key) {
|
VkRenderPass RenderPassCache::Get(const RenderPassKey& key) {
|
||||||
@@ -139,9 +75,7 @@ VkRenderPass RenderPassCache::Get(const RenderPassKey& key) {
|
|||||||
if (!is_new) {
|
if (!is_new) {
|
||||||
return *pair->second;
|
return *pair->second;
|
||||||
}
|
}
|
||||||
static constexpr size_t MAX_ATTACHMENTS =
|
boost::container::static_vector<VkAttachmentDescription, 9> descriptions;
|
||||||
2 * std::tuple_size_v<decltype(RenderPassKey::color_formats)> + 2;
|
|
||||||
boost::container::static_vector<VkAttachmentDescription, MAX_ATTACHMENTS> descriptions;
|
|
||||||
std::array<VkAttachmentReference, 8> references{};
|
std::array<VkAttachmentReference, 8> references{};
|
||||||
u32 num_attachments{};
|
u32 num_attachments{};
|
||||||
u32 num_colors{};
|
u32 num_colors{};
|
||||||
@@ -175,11 +109,8 @@ VkRenderPass RenderPassCache::Get(const RenderPassKey& key) {
|
|||||||
const VkAttachmentLoadOp depth_load_op = key.depth_stencil_clear
|
const VkAttachmentLoadOp depth_load_op = key.depth_stencil_clear
|
||||||
? VK_ATTACHMENT_LOAD_OP_CLEAR
|
? VK_ATTACHMENT_LOAD_OP_CLEAR
|
||||||
: VK_ATTACHMENT_LOAD_OP_LOAD;
|
: 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,
|
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{};
|
std::array<VkAttachmentReference, 8> resolve_references{};
|
||||||
const bool do_resolve_color =
|
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{
|
const VkSubpassDescription subpass{
|
||||||
.flags = 0,
|
.flags = 0,
|
||||||
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
|
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||||
@@ -241,92 +157,6 @@ VkRenderPass RenderPassCache::Get(const RenderPassKey& key) {
|
|||||||
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
|
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
|
||||||
.dependencyFlags = VK_DEPENDENCY_BY_REGION_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({
|
pair->second = device->GetLogical().CreateRenderPass({
|
||||||
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
|
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <ankerl/unordered_dense.h>
|
#include <ankerl/unordered_dense.h>
|
||||||
|
|
||||||
#include "common/container_hash.h"
|
|
||||||
#include "video_core/surface.h"
|
#include "video_core/surface.h"
|
||||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||||
|
|
||||||
@@ -22,11 +21,9 @@ struct RenderPassKey {
|
|||||||
VideoCore::Surface::PixelFormat depth_format;
|
VideoCore::Surface::PixelFormat depth_format;
|
||||||
VkSampleCountFlagBits samples;
|
VkSampleCountFlagBits samples;
|
||||||
bool resolve_color;
|
bool resolve_color;
|
||||||
bool resolve_depth_stencil;
|
|
||||||
u32 color_clear_mask;
|
u32 color_clear_mask;
|
||||||
bool depth_stencil_clear;
|
bool depth_stencil_clear;
|
||||||
u32 color_discard_mask;
|
u32 color_discard_mask;
|
||||||
bool depth_stencil_discard;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Vulkan
|
} // namespace Vulkan
|
||||||
@@ -34,28 +31,17 @@ struct RenderPassKey {
|
|||||||
namespace std {
|
namespace std {
|
||||||
template <>
|
template <>
|
||||||
struct hash<Vulkan::RenderPassKey> {
|
struct hash<Vulkan::RenderPassKey> {
|
||||||
static_assert(std::tuple_size_v<decltype(Vulkan::RenderPassKey::color_formats)> <= 8);
|
|
||||||
static_assert(static_cast<u32>(VideoCore::Surface::PixelFormat::Invalid) <= 0xFF);
|
|
||||||
static_assert(static_cast<u32>(VideoCore::Surface::PixelFormat::Max) <= 0xFF);
|
|
||||||
static_assert(VK_SAMPLE_COUNT_64_BIT <= 0xFF);
|
|
||||||
|
|
||||||
[[nodiscard]] size_t operator()(const Vulkan::RenderPassKey& key) const noexcept {
|
[[nodiscard]] size_t operator()(const Vulkan::RenderPassKey& key) const noexcept {
|
||||||
u64 formats = 0;
|
size_t value = static_cast<size_t>(key.depth_format) << 48;
|
||||||
for (size_t index = 0; index < key.color_formats.size(); ++index) {
|
value ^= static_cast<size_t>(key.samples) << 52;
|
||||||
formats |= static_cast<u64>(key.color_formats[index]) << (index * 8);
|
value ^= static_cast<size_t>(key.resolve_color) << 63;
|
||||||
|
value ^= static_cast<size_t>(key.color_clear_mask) << 54;
|
||||||
|
value ^= static_cast<size_t>(key.depth_stencil_clear) << 62;
|
||||||
|
value ^= static_cast<size_t>(key.color_discard_mask) << 24;
|
||||||
|
for (size_t i = 0; i < key.color_formats.size(); ++i) {
|
||||||
|
value ^= static_cast<size_t>(key.color_formats[i]) << (i * 6);
|
||||||
}
|
}
|
||||||
const u64 state = static_cast<u64>(key.depth_format) |
|
return value;
|
||||||
(static_cast<u64>(key.samples) << 8) |
|
|
||||||
(static_cast<u64>(key.color_clear_mask) << 16) |
|
|
||||||
(static_cast<u64>(key.color_discard_mask) << 24) |
|
|
||||||
(static_cast<u64>(key.resolve_color) << 32) |
|
|
||||||
(static_cast<u64>(key.depth_stencil_clear) << 33) |
|
|
||||||
(static_cast<u64>(key.resolve_depth_stencil) << 34) |
|
|
||||||
(static_cast<u64>(key.depth_stencil_discard) << 35);
|
|
||||||
size_t seed = 0;
|
|
||||||
Common::HashCombine(seed, formats);
|
|
||||||
Common::HashCombine(seed, state);
|
|
||||||
return seed;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} // namespace std
|
} // namespace std
|
||||||
@@ -64,9 +50,6 @@ namespace Vulkan {
|
|||||||
|
|
||||||
class Device;
|
class Device;
|
||||||
|
|
||||||
[[nodiscard]] bool SupportsDepthStencilResolve(const Device& device,
|
|
||||||
VideoCore::Surface::PixelFormat depth_format);
|
|
||||||
|
|
||||||
class RenderPassCache {
|
class RenderPassCache {
|
||||||
public:
|
public:
|
||||||
explicit RenderPassCache(const Device& device_);
|
explicit RenderPassCache(const Device& device_);
|
||||||
|
|||||||
@@ -132,7 +132,6 @@ void Scheduler::BeginRenderPassImpl(const Framebuffer* framebuffer, VkRenderPass
|
|||||||
num_renderpass_images = framebuffer->NumImages();
|
num_renderpass_images = framebuffer->NumImages();
|
||||||
renderpass_images = framebuffer->Images();
|
renderpass_images = framebuffer->Images();
|
||||||
renderpass_image_ranges = framebuffer->ImageRanges();
|
renderpass_image_ranges = framebuffer->ImageRanges();
|
||||||
framebuffer->MarkResolveShadowsUpToDate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Scheduler::RealizeDeferredClear() {
|
void Scheduler::RealizeDeferredClear() {
|
||||||
@@ -156,10 +155,8 @@ void Scheduler::RealizeDeferredClear() {
|
|||||||
}
|
}
|
||||||
const u32 color_discard_mask =
|
const u32 color_discard_mask =
|
||||||
dc.framebuffer->DiscardsMsaaColor() ? dc.color_clear_mask : 0u;
|
dc.framebuffer->DiscardsMsaaColor() ? dc.color_clear_mask : 0u;
|
||||||
const bool depth_stencil_discard =
|
|
||||||
dc.depth_stencil && dc.framebuffer->DiscardsMsaaDepthStencil();
|
|
||||||
const VkRenderPass renderpass = dc.framebuffer->RenderPassVariant(
|
const VkRenderPass renderpass = dc.framebuffer->RenderPassVariant(
|
||||||
dc.color_clear_mask, dc.depth_stencil, color_discard_mask, depth_stencil_discard);
|
dc.color_clear_mask, dc.depth_stencil, color_discard_mask);
|
||||||
EndRenderPass();
|
EndRenderPass();
|
||||||
BeginRenderPassImpl(dc.framebuffer, renderpass, clear_values.data(), count);
|
BeginRenderPassImpl(dc.framebuffer, renderpass, clear_values.data(), count);
|
||||||
}
|
}
|
||||||
@@ -193,14 +190,6 @@ bool Scheduler::DeferDepthStencilClear(const Framebuffer* framebuffer, const VkC
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Scheduler::FlushDeferredClear() {
|
|
||||||
if (deferred_clear.framebuffer == nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
RealizeDeferredClear();
|
|
||||||
EndRenderPass();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Scheduler::RequestRenderpass(const Framebuffer* framebuffer) {
|
void Scheduler::RequestRenderpass(const Framebuffer* framebuffer) {
|
||||||
if (deferred_clear.framebuffer == framebuffer) {
|
if (deferred_clear.framebuffer == framebuffer) {
|
||||||
RealizeDeferredClear();
|
RealizeDeferredClear();
|
||||||
@@ -449,9 +438,7 @@ void Scheduler::EndRenderPass()
|
|||||||
| VK_ACCESS_COLOR_ATTACHMENT_READ_BIT
|
| VK_ACCESS_COLOR_ATTACHMENT_READ_BIT
|
||||||
| VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT
|
| VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT
|
||||||
| VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT
|
| VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT
|
||||||
| VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT
|
| VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
|
||||||
| VK_ACCESS_TRANSFER_READ_BIT
|
|
||||||
| VK_ACCESS_TRANSFER_WRITE_BIT,
|
|
||||||
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
|
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||||
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||||
@@ -462,7 +449,7 @@ void Scheduler::EndRenderPass()
|
|||||||
}
|
}
|
||||||
cmdbuf.EndRenderPass();
|
cmdbuf.EndRenderPass();
|
||||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
|
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
|
||||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER,
|
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, vk::PIPELINE_STAGE_GRAPHICS_COMPUTE,
|
||||||
0, nullptr, nullptr, vk::Span(barriers.data(), num_images));
|
0, nullptr, nullptr, vk::Span(barriers.data(), num_images));
|
||||||
if (has_transform_feedback) {
|
if (has_transform_feedback) {
|
||||||
static constexpr VkMemoryBarrier XFB_OUTPUT_BARRIER{
|
static constexpr VkMemoryBarrier XFB_OUTPUT_BARRIER{
|
||||||
|
|||||||
@@ -65,9 +65,6 @@ public:
|
|||||||
/// Defers a full depth/stencil clear so it becomes the next render pass.
|
/// Defers a full depth/stencil clear so it becomes the next render pass.
|
||||||
bool DeferDepthStencilClear(const Framebuffer* framebuffer, const VkClearValue& value);
|
bool DeferDepthStencilClear(const Framebuffer* framebuffer, const VkClearValue& value);
|
||||||
|
|
||||||
/// Realizes any pending deferred clear before its framebuffer can be moved or freed.
|
|
||||||
void FlushDeferredClear();
|
|
||||||
|
|
||||||
/// Requests the current execution context to be able to execute operations only allowed outside
|
/// Requests the current execution context to be able to execute operations only allowed outside
|
||||||
/// of a renderpass.
|
/// of a renderpass.
|
||||||
void RequestOutsideRenderPassOperationContext();
|
void RequestOutsideRenderPassOperationContext();
|
||||||
|
|||||||
@@ -33,10 +33,10 @@ constexpr VkDeviceSize MAX_ALIGNMENT = 256;
|
|||||||
// Windows ones however, can intake bigger buffers and generally do not OOM.
|
// Windows ones however, can intake bigger buffers and generally do not OOM.
|
||||||
// - GTX 960 on Windows will not OOM with 256mib
|
// - GTX 960 on Windows will not OOM with 256mib
|
||||||
// - GT 1030 on ^NIX will OOM with 256mib
|
// - GT 1030 on ^NIX will OOM with 256mib
|
||||||
#if defined(__FreeBSD__)
|
#if defined(_WIN32) || defined(__ANDROID__)
|
||||||
constexpr VkDeviceSize MAX_STREAM_BUFFER_SIZE = 128_MiB;
|
|
||||||
#else
|
|
||||||
constexpr VkDeviceSize MAX_STREAM_BUFFER_SIZE = 256_MiB;
|
constexpr VkDeviceSize MAX_STREAM_BUFFER_SIZE = 256_MiB;
|
||||||
|
#else
|
||||||
|
constexpr VkDeviceSize MAX_STREAM_BUFFER_SIZE = 128_MiB;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
size_t GetStreamBufferSize(const Device& device) {
|
size_t GetStreamBufferSize(const Device& device) {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -60,19 +60,13 @@ public:
|
|||||||
|
|
||||||
void TickFrame();
|
void TickFrame();
|
||||||
|
|
||||||
void FlushDeferredClear();
|
|
||||||
|
|
||||||
u64 GetDeviceLocalMemory() const;
|
u64 GetDeviceLocalMemory() const;
|
||||||
|
|
||||||
u64 GetDeviceMemoryUsage() const;
|
u64 GetDeviceMemoryUsage() const;
|
||||||
|
|
||||||
bool CanReportMemoryUsage() const;
|
bool CanReportMemoryUsage() const;
|
||||||
|
|
||||||
bool CanDownloadMsaa(const VideoCommon::ImageInfo& info) const;
|
std::optional<size_t> GetSamplerHeapBudget() const;
|
||||||
|
|
||||||
[[nodiscard]] VkImage AcquireMsaaScratchImage(const VkImageCreateInfo& image_ci);
|
|
||||||
|
|
||||||
void ReleaseMsaaScratchImage(VkImage image);
|
|
||||||
|
|
||||||
void BlitImage(Framebuffer* dst_framebuffer, ImageView& dst, ImageView& src,
|
void BlitImage(Framebuffer* dst_framebuffer, ImageView& dst, ImageView& src,
|
||||||
const Region2D& dst_region, const Region2D& src_region,
|
const Region2D& dst_region, const Region2D& src_region,
|
||||||
@@ -123,20 +117,16 @@ public:
|
|||||||
VkFormat format = VK_FORMAT_UNDEFINED;
|
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||||
VkExtent2D extent{};
|
VkExtent2D extent{};
|
||||||
u32 layers = 0;
|
u32 layers = 0;
|
||||||
VkImageAspectFlags aspect_mask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
||||||
bool up_to_date = false;
|
bool up_to_date = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
[[nodiscard]] VkImageView GetOrCreateResolveShadow(VkImage msaa_image, VkFormat format,
|
[[nodiscard]] VkImageView GetOrCreateResolveShadow(VkImage msaa_image, VkFormat format,
|
||||||
VkExtent2D extent, u32 layers,
|
VkExtent2D extent, u32 layers);
|
||||||
VkImageAspectFlags aspect_mask);
|
|
||||||
|
|
||||||
[[nodiscard]] const ResolveShadow* GetValidResolveShadow(VkImage msaa_image) const;
|
[[nodiscard]] const ResolveShadow* GetValidResolveShadow(VkImage msaa_image) const;
|
||||||
|
|
||||||
void InvalidateResolveShadow(VkImage msaa_image);
|
void InvalidateResolveShadow(VkImage msaa_image);
|
||||||
|
|
||||||
void MarkResolveShadowUpToDate(VkImage msaa_image);
|
|
||||||
|
|
||||||
void EraseResolveShadow(VkImage msaa_image);
|
void EraseResolveShadow(VkImage msaa_image);
|
||||||
|
|
||||||
std::span<const VkFormat> ViewFormats(PixelFormat format) {
|
std::span<const VkFormat> ViewFormats(PixelFormat format) {
|
||||||
@@ -159,38 +149,13 @@ public:
|
|||||||
std::optional<ASTCDecoderPass> astc_decoder_pass;
|
std::optional<ASTCDecoderPass> astc_decoder_pass;
|
||||||
|
|
||||||
std::optional<BlockLinearUnswizzle3DPass> bl3d_unswizzle_pass;
|
std::optional<BlockLinearUnswizzle3DPass> bl3d_unswizzle_pass;
|
||||||
std::optional<BlockLinearUnswizzle2DPass> bl_unswizzle_2d_pass;
|
|
||||||
std::optional<BlockLinearUnswizzleImage3DPass> bl_unswizzle_image_3d_pass;
|
|
||||||
std::optional<PitchUnswizzlePass> pitch_unswizzle_pass;
|
|
||||||
const Settings::ResolutionScalingInfo& resolution;
|
const Settings::ResolutionScalingInfo& resolution;
|
||||||
std::array<std::vector<VkFormat>, VideoCore::Surface::MaxPixelFormat> view_formats;
|
std::array<std::vector<VkFormat>, VideoCore::Surface::MaxPixelFormat> view_formats;
|
||||||
|
|
||||||
static constexpr size_t indexing_slots = 8 * sizeof(size_t);
|
static constexpr size_t indexing_slots = 8 * sizeof(size_t);
|
||||||
std::array<vk::Buffer, indexing_slots> buffers{};
|
std::array<vk::Buffer, indexing_slots> buffers{};
|
||||||
struct MsaaScratchKey {
|
std::vector<std::pair<u64, vk::Image>> pending_msaa_images;
|
||||||
VkFormat format;
|
|
||||||
VkImageType type;
|
|
||||||
u32 width;
|
|
||||||
u32 height;
|
|
||||||
u32 depth;
|
|
||||||
u32 levels;
|
|
||||||
u32 layers;
|
|
||||||
VkImageUsageFlags usage;
|
|
||||||
VkImageCreateFlags flags;
|
|
||||||
|
|
||||||
bool operator==(const MsaaScratchKey&) const noexcept = default;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct MsaaScratchImage {
|
|
||||||
MsaaScratchKey key;
|
|
||||||
vk::Image image;
|
|
||||||
u64 tick;
|
|
||||||
u32 unused_frames;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<MsaaScratchImage> msaa_scratch_images;
|
|
||||||
ankerl::unordered_dense::map<VkImage, ResolveShadow> resolve_shadows;
|
ankerl::unordered_dense::map<VkImage, ResolveShadow> resolve_shadows;
|
||||||
std::vector<std::pair<u64, ResolveShadow>> pending_resolve_shadows;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class Framebuffer {
|
class Framebuffer {
|
||||||
@@ -226,8 +191,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] VkRenderPass RenderPassVariant(u32 color_clear_mask, bool depth_stencil_clear,
|
[[nodiscard]] VkRenderPass RenderPassVariant(u32 color_clear_mask, bool depth_stencil_clear,
|
||||||
u32 color_discard_mask,
|
u32 color_discard_mask) const;
|
||||||
bool depth_stencil_discard) const;
|
|
||||||
|
|
||||||
[[nodiscard]] VkExtent2D RenderArea() const noexcept {
|
[[nodiscard]] VkExtent2D RenderArea() const noexcept {
|
||||||
return render_area;
|
return render_area;
|
||||||
@@ -269,21 +233,19 @@ public:
|
|||||||
return is_rescaled;
|
return is_rescaled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool HasResolveColor() const noexcept {
|
||||||
|
return !resolve_images.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] VkImage ResolveColorImage(size_t index) const noexcept {
|
||||||
|
return index < resolve_images.size() ? *resolve_images[index] : VK_NULL_HANDLE;
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] bool DiscardsMsaaColor() const noexcept {
|
[[nodiscard]] bool DiscardsMsaaColor() const noexcept {
|
||||||
return discard_msaa_color;
|
return discard_msaa_color;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] bool DiscardsMsaaDepthStencil() const noexcept {
|
|
||||||
return discard_msaa_depth_stencil;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Records that a render pass has begun, so its resolve attachments will hold valid contents
|
|
||||||
/// once it ends.
|
|
||||||
void MarkResolveShadowsUpToDate() const;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr size_t NUM_MEMOIZED_RENDER_PASS_VARIANTS = 8;
|
|
||||||
|
|
||||||
vk::Framebuffer framebuffer;
|
vk::Framebuffer framebuffer;
|
||||||
VkRenderPass renderpass{};
|
VkRenderPass renderpass{};
|
||||||
VkExtent2D render_area{};
|
VkExtent2D render_area{};
|
||||||
@@ -296,16 +258,11 @@ private:
|
|||||||
bool has_depth{};
|
bool has_depth{};
|
||||||
bool has_stencil{};
|
bool has_stencil{};
|
||||||
bool is_rescaled{};
|
bool is_rescaled{};
|
||||||
std::array<VkImage, 9> resolve_shadow_images{};
|
std::vector<vk::Image> resolve_images;
|
||||||
u32 num_resolve_shadows = 0;
|
std::vector<vk::ImageView> resolve_image_views;
|
||||||
TextureCacheRuntime* runtime_ptr{nullptr};
|
|
||||||
RenderPassKey render_pass_key{};
|
RenderPassKey render_pass_key{};
|
||||||
RenderPassCache* render_pass_cache{nullptr};
|
RenderPassCache* render_pass_cache{nullptr};
|
||||||
bool discard_msaa_color{};
|
bool discard_msaa_color{};
|
||||||
bool discard_msaa_depth_stencil{};
|
|
||||||
mutable std::array<u32, NUM_MEMOIZED_RENDER_PASS_VARIANTS> variant_keys{};
|
|
||||||
mutable std::array<VkRenderPass, NUM_MEMOIZED_RENDER_PASS_VARIANTS> variant_render_passes{};
|
|
||||||
mutable u32 num_memoized_variants{};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class Image : public VideoCommon::ImageBase {
|
class Image : public VideoCommon::ImageBase {
|
||||||
@@ -353,7 +310,7 @@ public:
|
|||||||
|
|
||||||
/// Returns true when the image is already initialized and mark it as initialized
|
/// Returns true when the image is already initialized and mark it as initialized
|
||||||
[[nodiscard]] bool ExchangeInitialization() noexcept {
|
[[nodiscard]] bool ExchangeInitialization() noexcept {
|
||||||
return std::exchange(InitializationFor(current_image), true);
|
return std::exchange(initialized, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
VkImageView StorageImageView(s32 level) noexcept;
|
VkImageView StorageImageView(s32 level) noexcept;
|
||||||
@@ -373,10 +330,6 @@ private:
|
|||||||
|
|
||||||
bool NeedsScaleHelper() const;
|
bool NeedsScaleHelper() const;
|
||||||
|
|
||||||
std::vector<vk::ImageView>& StorageViewsFor(vk::Image Image::*image);
|
|
||||||
|
|
||||||
bool& InitializationFor(vk::Image Image::*image) noexcept;
|
|
||||||
|
|
||||||
Scheduler* scheduler{};
|
Scheduler* scheduler{};
|
||||||
TextureCacheRuntime* runtime{};
|
TextureCacheRuntime* runtime{};
|
||||||
|
|
||||||
@@ -394,10 +347,8 @@ private:
|
|||||||
vk::Image Image::*current_image{};
|
vk::Image Image::*current_image{};
|
||||||
|
|
||||||
std::vector<vk::ImageView> storage_image_views;
|
std::vector<vk::ImageView> storage_image_views;
|
||||||
std::vector<vk::ImageView> scaled_storage_image_views;
|
|
||||||
VkImageAspectFlags aspect_mask = 0;
|
VkImageAspectFlags aspect_mask = 0;
|
||||||
bool original_initialized = false;
|
bool initialized = false;
|
||||||
bool scaled_initialized = false;
|
|
||||||
|
|
||||||
std::optional<Framebuffer> scale_framebuffer;
|
std::optional<Framebuffer> scale_framebuffer;
|
||||||
std::optional<Framebuffer> normal_framebuffer;
|
std::optional<Framebuffer> normal_framebuffer;
|
||||||
@@ -453,22 +404,6 @@ public:
|
|||||||
return supports_depth_comparison;
|
return supports_depth_comparison;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] bool RequiresBorderColorFormat() const noexcept {
|
|
||||||
return requires_border_color_format;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] bool SupportsMinmaxFilter() const noexcept {
|
|
||||||
return supports_minmax_filter;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] const VkComponentMapping& Swizzle() const noexcept {
|
|
||||||
return swizzle_mapping;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] bool HasIdentitySwizzle() const noexcept {
|
|
||||||
return has_identity_swizzle;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] GPUVAddr GpuAddr() const noexcept {
|
[[nodiscard]] GPUVAddr GpuAddr() const noexcept {
|
||||||
return gpu_addr;
|
return gpu_addr;
|
||||||
}
|
}
|
||||||
@@ -501,91 +436,48 @@ private:
|
|||||||
VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT;
|
VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT;
|
||||||
u32 buffer_size = 0;
|
u32 buffer_size = 0;
|
||||||
|
|
||||||
VkComponentMapping swizzle_mapping{};
|
|
||||||
|
|
||||||
bool supports_depth_comparison = false;
|
bool supports_depth_comparison = false;
|
||||||
bool requires_border_color_format = false;
|
|
||||||
bool supports_minmax_filter = false;
|
|
||||||
bool has_identity_swizzle = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class ImageAlloc : public VideoCommon::ImageAllocBase {};
|
class ImageAlloc : public VideoCommon::ImageAllocBase {};
|
||||||
|
|
||||||
class CustomBorderColorBudget {
|
|
||||||
public:
|
|
||||||
CustomBorderColorBudget() = default;
|
|
||||||
~CustomBorderColorBudget();
|
|
||||||
|
|
||||||
CustomBorderColorBudget(const CustomBorderColorBudget&) = delete;
|
|
||||||
CustomBorderColorBudget& operator=(const CustomBorderColorBudget&) = delete;
|
|
||||||
|
|
||||||
CustomBorderColorBudget(CustomBorderColorBudget&& rhs) noexcept;
|
|
||||||
CustomBorderColorBudget& operator=(CustomBorderColorBudget&& rhs) noexcept;
|
|
||||||
|
|
||||||
bool TryAcquire(const Device& device, size_t count);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void Release() noexcept;
|
|
||||||
|
|
||||||
const Device* device_ptr = nullptr;
|
|
||||||
size_t held = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Sampler {
|
class Sampler {
|
||||||
public:
|
public:
|
||||||
explicit Sampler(TextureCacheRuntime&, const Tegra::Texture::TSCEntry&);
|
explicit Sampler(TextureCacheRuntime&, const Tegra::Texture::TSCEntry&);
|
||||||
|
|
||||||
[[nodiscard]] VkSampler Handle() const noexcept {
|
[[nodiscard]] VkSampler Handle() const noexcept {
|
||||||
return *variants.front().sampler;
|
return *sampler;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] VkSampler HandleFor(const ImageView& image_view, bool is_depth);
|
[[nodiscard]] VkSampler HandleWithDefaultAnisotropy() const noexcept {
|
||||||
|
return *sampler_default_anisotropy;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool HasAddedAnisotropy() const noexcept {
|
||||||
|
return static_cast<bool>(sampler_default_anisotropy);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] VkSampler HandleWithNearestFilter() const noexcept {
|
||||||
|
return *sampler_nearest;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool HasLinearFiltering() const noexcept {
|
||||||
|
return static_cast<bool>(sampler_nearest);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] VkSampler HandleWithoutDepthComparison() const noexcept {
|
||||||
|
return *sampler_noncompare;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool HasDepthComparison() const noexcept {
|
||||||
|
return static_cast<bool>(sampler_noncompare);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct VariantKey {
|
vk::Sampler sampler;
|
||||||
bool reduce_anisotropy;
|
vk::Sampler sampler_default_anisotropy;
|
||||||
bool force_nearest;
|
vk::Sampler sampler_nearest;
|
||||||
bool drop_depth_comparison;
|
vk::Sampler sampler_noncompare;
|
||||||
bool drop_reduction;
|
|
||||||
bool drop_custom_border;
|
|
||||||
bool srgb_border;
|
|
||||||
std::array<VkComponentSwizzle, 4> swizzle;
|
|
||||||
|
|
||||||
bool operator==(const VariantKey&) const noexcept = default;
|
|
||||||
|
|
||||||
[[nodiscard]] bool HasSwizzle() const noexcept {
|
|
||||||
return swizzle != std::array<VkComponentSwizzle, 4>{};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Variant {
|
|
||||||
VariantKey key;
|
|
||||||
vk::Sampler sampler;
|
|
||||||
};
|
|
||||||
|
|
||||||
static constexpr size_t MAX_VARIANTS = 32;
|
|
||||||
|
|
||||||
[[nodiscard]] VariantKey MakeKey(const ImageView& image_view, bool is_depth) const noexcept;
|
|
||||||
[[nodiscard]] VkSampler Find(const VariantKey& key) const noexcept;
|
|
||||||
VkSampler Emplace(VariantKey key);
|
|
||||||
|
|
||||||
CustomBorderColorBudget custom_border_color_budget;
|
|
||||||
std::vector<Variant> variants;
|
|
||||||
|
|
||||||
const Device* device_ptr{nullptr};
|
|
||||||
VkSamplerCreateInfo base_ci{};
|
|
||||||
VkSamplerReductionModeEXT reduction_mode{VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE_EXT};
|
|
||||||
std::array<float, 4> border_color{};
|
|
||||||
std::array<float, 4> srgb_border_color{};
|
|
||||||
f32 default_anisotropy{1.0f};
|
|
||||||
|
|
||||||
bool has_added_anisotropy{};
|
|
||||||
bool has_linear_filtering{};
|
|
||||||
bool has_depth_comparison{};
|
|
||||||
bool has_minmax_reduction{};
|
|
||||||
bool has_custom_border_colors{};
|
|
||||||
bool has_srgb_border_color{};
|
|
||||||
bool needs_swizzle_mapping{};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TextureCacheParams {
|
struct TextureCacheParams {
|
||||||
@@ -594,7 +486,6 @@ struct TextureCacheParams {
|
|||||||
static constexpr bool HAS_EMULATED_COPIES = false;
|
static constexpr bool HAS_EMULATED_COPIES = false;
|
||||||
static constexpr bool HAS_DEVICE_MEMORY_INFO = true;
|
static constexpr bool HAS_DEVICE_MEMORY_INFO = true;
|
||||||
static constexpr bool IMPLEMENTS_ASYNC_DOWNLOADS = true;
|
static constexpr bool IMPLEMENTS_ASYNC_DOWNLOADS = true;
|
||||||
static constexpr bool HAS_MSAA_DOWNLOADS = true;
|
|
||||||
|
|
||||||
using Runtime = Vulkan::TextureCacheRuntime;
|
using Runtime = Vulkan::TextureCacheRuntime;
|
||||||
using Image = Vulkan::Image;
|
using Image = Vulkan::Image;
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -26,8 +23,8 @@ struct BlockLinearSwizzle2DParams {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct BlockLinearSwizzle3DParams {
|
struct BlockLinearSwizzle3DParams {
|
||||||
alignas(16) std::array<u32, 3> origin;
|
std::array<u32, 3> origin;
|
||||||
alignas(16) std::array<s32, 3> destination;
|
std::array<s32, 3> destination;
|
||||||
u32 bytes_per_block_log2;
|
u32 bytes_per_block_log2;
|
||||||
u32 slice_size;
|
u32 slice_size;
|
||||||
u32 block_size;
|
u32 block_size;
|
||||||
|
|||||||
@@ -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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||||
@@ -121,7 +121,7 @@ void ImageBase::InsertView(const ImageViewInfo& view_info, ImageViewId image_vie
|
|||||||
image_view_ids.push_back(image_view_id);
|
image_view_ids.push_back(image_view_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ImageBase::IsSafeGpuCopy() const noexcept {
|
bool ImageBase::IsSafeDownload() const noexcept {
|
||||||
// Skip images that were not modified from the GPU
|
// Skip images that were not modified from the GPU
|
||||||
if (False(flags & ImageFlagBits::GpuModified)) {
|
if (False(flags & ImageFlagBits::GpuModified)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -131,6 +131,10 @@ bool ImageBase::IsSafeGpuCopy() const noexcept {
|
|||||||
if (True(flags & ImageFlagBits::CpuModified)) {
|
if (True(flags & ImageFlagBits::CpuModified)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (info.num_samples > 1) {
|
||||||
|
LOG_WARNING(HW_GPU, "MSAA image downloads are not implemented");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -67,7 +64,7 @@ struct ImageBase {
|
|||||||
|
|
||||||
void InsertView(const ImageViewInfo& view_info, ImageViewId image_view_id);
|
void InsertView(const ImageViewInfo& view_info, ImageViewId image_view_id);
|
||||||
|
|
||||||
[[nodiscard]] bool IsSafeGpuCopy() const noexcept;
|
[[nodiscard]] bool IsSafeDownload() const noexcept;
|
||||||
|
|
||||||
[[nodiscard]] bool Overlaps(VAddr overlap_cpu_addr, size_t overlap_size) const noexcept {
|
[[nodiscard]] bool Overlaps(VAddr overlap_cpu_addr, size_t overlap_size) const noexcept {
|
||||||
const VAddr overlap_end = overlap_cpu_addr + overlap_size;
|
const VAddr overlap_end = overlap_cpu_addr + overlap_size;
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
#include "video_core/engines/kepler_compute.h"
|
#include "video_core/engines/kepler_compute.h"
|
||||||
#include "video_core/guest_memory.h"
|
#include "video_core/guest_memory.h"
|
||||||
#include "video_core/host1x/gpu_device_memory_manager.h"
|
#include "video_core/host1x/gpu_device_memory_manager.h"
|
||||||
#include "video_core/texture_cache/accelerated_swizzle.h"
|
|
||||||
#include "video_core/texture_cache/image_view_base.h"
|
#include "video_core/texture_cache/image_view_base.h"
|
||||||
#include "video_core/texture_cache/samples_helper.h"
|
#include "video_core/texture_cache/samples_helper.h"
|
||||||
#include "video_core/texture_cache/texture_cache_base.h"
|
#include "video_core/texture_cache/texture_cache_base.h"
|
||||||
@@ -135,7 +134,7 @@ void TextureCache<P>::RunGarbageCollector() {
|
|||||||
if (True(image.flags & ImageFlagBits::IsDecoding)) {
|
if (True(image.flags & ImageFlagBits::IsDecoding)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const bool must_download = IsDownloadable(image) && False(image.flags & ImageFlagBits::BadOverlap);
|
const bool must_download = image.IsSafeDownload() && False(image.flags & ImageFlagBits::BadOverlap);
|
||||||
if ((!aggressive_mode && True(image.flags & ImageFlagBits::CostlyLoad)) || (!high_priority_mode && must_download)) {
|
if ((!aggressive_mode && True(image.flags & ImageFlagBits::CostlyLoad)) || (!high_priority_mode && must_download)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -280,11 +279,11 @@ void TextureCache<P>::CheckFeedbackLoop(std::span<const ImageViewInOut> views) {
|
|||||||
|
|
||||||
const ImageId view_image_id = slot_image_views[view.id].image_id;
|
const ImageId view_image_id = slot_image_views[view.id].image_id;
|
||||||
{
|
{
|
||||||
bool is_feedback = false;
|
bool is_continue = false;
|
||||||
for (size_t i = 0; i < 8; ++i)
|
for (size_t i = 0; i < 8; ++i)
|
||||||
is_feedback |= (rt_active_mask & (1u << i)) && view_image_id == rt_image_id[i];
|
is_continue |= (rt_active_mask & (1u << i)) && view_image_id == rt_image_id[i];
|
||||||
if (is_feedback)
|
if (is_continue)
|
||||||
return true;
|
continue;
|
||||||
}
|
}
|
||||||
if (depth_active && view_image_id == rt_depth_image_id) {
|
if (depth_active && view_image_id == rt_depth_image_id) {
|
||||||
return true;
|
return true;
|
||||||
@@ -578,7 +577,6 @@ FramebufferId TextureCache<P>::GetFramebufferId(const RenderTargets& key) {
|
|||||||
return id ? &slot_image_views[id] : nullptr;
|
return id ? &slot_image_views[id] : nullptr;
|
||||||
});
|
});
|
||||||
ImageView* const depth_buffer = key.depth_buffer_id ? &slot_image_views[key.depth_buffer_id] : nullptr;
|
ImageView* const depth_buffer = key.depth_buffer_id ? &slot_image_views[key.depth_buffer_id] : nullptr;
|
||||||
runtime.FlushDeferredClear();
|
|
||||||
framebuffer_id = slot_framebuffers.insert(runtime, color_buffers, depth_buffer, key);
|
framebuffer_id = slot_framebuffers.insert(runtime, color_buffers, depth_buffer, key);
|
||||||
return framebuffer_id;
|
return framebuffer_id;
|
||||||
}
|
}
|
||||||
@@ -596,26 +594,11 @@ void TextureCache<P>::WriteMemory(DAddr cpu_addr, size_t size) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class P>
|
|
||||||
bool TextureCache<P>::IsDownloadable(const ImageBase& image) const noexcept {
|
|
||||||
if (!image.IsSafeGpuCopy()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (image.info.num_samples == 1) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if constexpr (P::HAS_MSAA_DOWNLOADS) {
|
|
||||||
return runtime.CanDownloadMsaa(image.info);
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
void TextureCache<P>::DownloadMemory(DAddr cpu_addr, size_t size) {
|
void TextureCache<P>::DownloadMemory(DAddr cpu_addr, size_t size) {
|
||||||
boost::container::small_vector<ImageId, 16> images;
|
boost::container::small_vector<ImageId, 16> images;
|
||||||
ForEachImageInRegion(cpu_addr, size, [this, &images](ImageId image_id, ImageBase& image) {
|
ForEachImageInRegion(cpu_addr, size, [&images](ImageId image_id, ImageBase& image) {
|
||||||
if (!IsDownloadable(image)) {
|
if (!image.IsSafeDownload()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
image.flags &= ~ImageFlagBits::GpuModified;
|
image.flags &= ~ImageFlagBits::GpuModified;
|
||||||
@@ -627,25 +610,14 @@ void TextureCache<P>::DownloadMemory(DAddr cpu_addr, size_t size) {
|
|||||||
std::ranges::sort(images, [this](ImageId lhs, ImageId rhs) {
|
std::ranges::sort(images, [this](ImageId lhs, ImageId rhs) {
|
||||||
return slot_images[lhs].modification_tick < slot_images[rhs].modification_tick;
|
return slot_images[lhs].modification_tick < slot_images[rhs].modification_tick;
|
||||||
});
|
});
|
||||||
size_t total_size_bytes = 0;
|
|
||||||
for (const ImageId image_id : images) {
|
|
||||||
total_size_bytes += slot_images[image_id].unswizzled_size_bytes;
|
|
||||||
}
|
|
||||||
auto download_map = runtime.DownloadStagingBuffer(total_size_bytes);
|
|
||||||
for (const ImageId image_id : images) {
|
for (const ImageId image_id : images) {
|
||||||
Image& image = slot_images[image_id];
|
Image& image = slot_images[image_id];
|
||||||
|
auto map = runtime.DownloadStagingBuffer(image.unswizzled_size_bytes);
|
||||||
const auto copies = FixSmallVectorADL(FullDownloadCopies(image.info));
|
const auto copies = FixSmallVectorADL(FullDownloadCopies(image.info));
|
||||||
image.DownloadMemory(download_map, copies);
|
image.DownloadMemory(map, copies);
|
||||||
download_map.offset += image.unswizzled_size_bytes;
|
runtime.Finish();
|
||||||
}
|
SwizzleImage(*gpu_memory, image.gpu_addr, image.info, copies, map.mapped_span,
|
||||||
runtime.Finish();
|
|
||||||
std::span<u8> download_span = download_map.mapped_span;
|
|
||||||
for (const ImageId image_id : images) {
|
|
||||||
const ImageBase& image = slot_images[image_id];
|
|
||||||
const auto copies = FixSmallVectorADL(FullDownloadCopies(image.info));
|
|
||||||
SwizzleImage(*gpu_memory, image.gpu_addr, image.info, copies, download_span,
|
|
||||||
swizzle_data_buffer);
|
swizzle_data_buffer);
|
||||||
download_span = download_span.subspan(image.unswizzled_size_bytes);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1134,12 +1106,6 @@ void TextureCache<P>::RefreshContents(Image& image, ImageId image_id) {
|
|||||||
|
|
||||||
TrackImage(image, image_id);
|
TrackImage(image, image_id);
|
||||||
|
|
||||||
if (image.info.rescaleable &&
|
|
||||||
IsRegionGpuModified(image.cpu_addr, image.guest_size_bytes)) {
|
|
||||||
runtime.TransitionImageLayout(image);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (image.info.num_samples > 1 && !runtime.CanUploadMSAA()) {
|
if (image.info.num_samples > 1 && !runtime.CanUploadMSAA()) {
|
||||||
LOG_WARNING(HW_GPU, "MSAA image uploads are not implemented");
|
LOG_WARNING(HW_GPU, "MSAA image uploads are not implemented");
|
||||||
runtime.TransitionImageLayout(image);
|
runtime.TransitionImageLayout(image);
|
||||||
@@ -1175,7 +1141,8 @@ void TextureCache<P>::UploadImageContents(Image& image, StagingBuffer& staging)
|
|||||||
const GPUVAddr gpu_addr = image.gpu_addr;
|
const GPUVAddr gpu_addr = image.gpu_addr;
|
||||||
|
|
||||||
if (True(image.flags & ImageFlagBits::AcceleratedUpload)) {
|
if (True(image.flags & ImageFlagBits::AcceleratedUpload)) {
|
||||||
gpu_memory->ReadBlockUnsafe(gpu_addr, mapped_span.data(), image.guest_size_bytes);
|
gpu_memory->ReadBlock(gpu_addr, mapped_span.data(), mapped_span.size_bytes(),
|
||||||
|
VideoCommon::CacheType::NoTextureCache);
|
||||||
const auto uploads = FullUploadSwizzles(image.info);
|
const auto uploads = FullUploadSwizzles(image.info);
|
||||||
runtime.AccelerateImageUpload(image, staging, FixSmallVectorADL(uploads), 0, 0);
|
runtime.AccelerateImageUpload(image, staging, FixSmallVectorADL(uploads), 0, 0);
|
||||||
return;
|
return;
|
||||||
@@ -1282,9 +1249,6 @@ ImageId TextureCache<P>::FindImage(const ImageInfo& info, GPUVAddr gpu_addr,
|
|||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
bool TextureCache<P>::ImageCanRescale(ImageBase& image) {
|
bool TextureCache<P>::ImageCanRescale(ImageBase& image) {
|
||||||
if (!Settings::values.resolution_info.active) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!image.info.rescaleable) {
|
if (!image.info.rescaleable) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1372,12 +1336,12 @@ void TextureCache<P>::QueueAsyncDecode(Image& image, ImageId image_id) {
|
|||||||
decode->image_id = image_id;
|
decode->image_id = image_id;
|
||||||
async_decodes.push_back(std::move(decode));
|
async_decodes.push_back(std::move(decode));
|
||||||
|
|
||||||
Common::ScratchBuffer<u8> local_unswizzle_data_buffer(image.unswizzled_size_bytes);
|
std::vector<u8> local_unswizzle_data_buffer(image.unswizzled_size_bytes, 0);
|
||||||
Tegra::Memory::GpuGuestMemory<u8, Tegra::Memory::GuestMemoryFlags::UnsafeRead> swizzle_data(*gpu_memory, image.gpu_addr, image.guest_size_bytes, &swizzle_data_buffer);
|
Tegra::Memory::GpuGuestMemory<u8, Tegra::Memory::GuestMemoryFlags::UnsafeRead> swizzle_data(*gpu_memory, image.gpu_addr, image.guest_size_bytes, &swizzle_data_buffer);
|
||||||
auto copies = UnswizzleImage(*gpu_memory, image.gpu_addr, image.info, swizzle_data, local_unswizzle_data_buffer);
|
auto copies = UnswizzleImage(*gpu_memory, image.gpu_addr, image.info, swizzle_data, local_unswizzle_data_buffer);
|
||||||
const size_t out_size = MapSizeBytes(image);
|
const size_t out_size = MapSizeBytes(image);
|
||||||
|
|
||||||
auto func = [out_size, copies = std::move(copies), info = image.info,
|
auto func = [out_size, copies, info = image.info,
|
||||||
input = std::move(local_unswizzle_data_buffer),
|
input = std::move(local_unswizzle_data_buffer),
|
||||||
async_decode = decode_ptr]() mutable {
|
async_decode = decode_ptr]() mutable {
|
||||||
async_decode->decoded_data.resize_destructive(out_size);
|
async_decode->decoded_data.resize_destructive(out_size);
|
||||||
@@ -1446,16 +1410,18 @@ void TextureCache<P>::TickAsyncUnswizzle() {
|
|||||||
Image& image = slot_images[task.image_id];
|
Image& image = slot_images[task.image_id];
|
||||||
|
|
||||||
if (!task.initialized) {
|
if (!task.initialized) {
|
||||||
task.total_size = image.guest_size_bytes;
|
task.total_size = MapSizeBytes(image);
|
||||||
task.staging_buffer = runtime.UploadStagingBuffer(task.total_size, true);
|
task.staging_buffer = runtime.UploadStagingBuffer(task.total_size, true);
|
||||||
|
|
||||||
const auto layout = FullUploadSwizzles(task.info);
|
const auto& info = image.info;
|
||||||
const auto params =
|
const u32 bytes_per_block = BytesPerBlock(info.format);
|
||||||
VideoCommon::Accelerated::MakeBlockLinearSwizzle3DParams(layout.front(), task.info);
|
const u32 width_blocks = Common::DivCeil(info.size.width, 4u);
|
||||||
task.bytes_per_slice = params.slice_size;
|
const u32 height_blocks = Common::DivCeil(info.size.height, 4u);
|
||||||
task.chunked = task.info.block.depth == 0;
|
|
||||||
|
const u32 stride = width_blocks * bytes_per_block;
|
||||||
|
const u32 aligned_height = height_blocks;
|
||||||
|
task.bytes_per_slice = static_cast<size_t>(stride) * aligned_height;
|
||||||
task.last_submitted_offset = 0;
|
task.last_submitted_offset = 0;
|
||||||
task.slices_submitted = 0;
|
|
||||||
task.initialized = true;
|
task.initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1470,39 +1436,31 @@ void TextureCache<P>::TickAsyncUnswizzle() {
|
|||||||
if (copy_amount == 0) copy_amount = task.bytes_per_slice;
|
if (copy_amount == 0) copy_amount = task.bytes_per_slice;
|
||||||
}
|
}
|
||||||
|
|
||||||
gpu_memory->ReadBlockUnsafe(image.gpu_addr + task.current_offset,
|
gpu_memory->ReadBlock(image.gpu_addr + task.current_offset,
|
||||||
task.staging_buffer.mapped_span.data() + task.current_offset,
|
task.staging_buffer.mapped_span.data() + task.current_offset,
|
||||||
copy_amount);
|
copy_amount);
|
||||||
task.current_offset += copy_amount;
|
task.current_offset += copy_amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool is_final_batch = task.current_offset >= task.total_size;
|
const bool is_final_batch = task.current_offset >= task.total_size;
|
||||||
|
const size_t bytes_ready = task.current_offset - task.last_submitted_offset;
|
||||||
|
const u32 complete_slices = static_cast<u32>(bytes_ready / task.bytes_per_slice);
|
||||||
|
|
||||||
if (task.chunked) {
|
if (complete_slices >= swizzle_slices_per_batch || (is_final_batch && complete_slices > 0)) {
|
||||||
const size_t bytes_ready = task.current_offset - task.last_submitted_offset;
|
const u32 z_start = static_cast<u32>(task.last_submitted_offset / task.bytes_per_slice);
|
||||||
const u32 complete_slices = static_cast<u32>(bytes_ready / task.bytes_per_slice);
|
const u32 slices_to_process = (std::min)(complete_slices, swizzle_slices_per_batch);
|
||||||
|
const u32 z_count = (std::min)(slices_to_process, image.info.size.depth - z_start);
|
||||||
|
|
||||||
if (complete_slices >= swizzle_slices_per_batch || (is_final_batch && complete_slices > 0)) {
|
if (z_count > 0) {
|
||||||
const u32 z_start = task.slices_submitted;
|
const auto uploads = FullUploadSwizzles(task.info);
|
||||||
const u32 slices_to_process = (std::min)(complete_slices, swizzle_slices_per_batch);
|
runtime.AccelerateImageUpload(image, task.staging_buffer, FixSmallVectorADL(uploads), z_start, z_count);
|
||||||
const u32 z_count = (std::min)(slices_to_process, image.info.size.depth - z_start);
|
task.last_submitted_offset += (static_cast<size_t>(z_count) * task.bytes_per_slice);
|
||||||
|
|
||||||
if (z_count > 0) {
|
|
||||||
const auto uploads = FullUploadSwizzles(task.info);
|
|
||||||
runtime.AccelerateImageUpload(image, task.staging_buffer,
|
|
||||||
FixSmallVectorADL(uploads), z_start, z_count);
|
|
||||||
task.last_submitted_offset += static_cast<size_t>(z_count) * task.bytes_per_slice;
|
|
||||||
task.slices_submitted += z_count;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (is_final_batch && task.slices_submitted == 0) {
|
|
||||||
const auto uploads = FullUploadSwizzles(task.info);
|
|
||||||
runtime.AccelerateImageUpload(image, task.staging_buffer, FixSmallVectorADL(uploads), 0,
|
|
||||||
image.info.size.depth);
|
|
||||||
task.slices_submitted = image.info.size.depth;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool all_slices_submitted = task.slices_submitted >= image.info.size.depth;
|
// Check if complete
|
||||||
|
const u32 slices_submitted = static_cast<u32>(task.last_submitted_offset / task.bytes_per_slice);
|
||||||
|
const bool all_slices_submitted = slices_submitted >= image.info.size.depth;
|
||||||
|
|
||||||
if (is_final_batch && all_slices_submitted) {
|
if (is_final_batch && all_slices_submitted) {
|
||||||
runtime.FreeDeferredStagingBuffer(task.staging_buffer);
|
runtime.FreeDeferredStagingBuffer(task.staging_buffer);
|
||||||
@@ -1518,12 +1476,12 @@ template <class P>
|
|||||||
bool TextureCache<P>::ScaleUp(Image& image) {
|
bool TextureCache<P>::ScaleUp(Image& image) {
|
||||||
const bool has_copy = image.HasScaled();
|
const bool has_copy = image.HasScaled();
|
||||||
const bool rescaled = image.ScaleUp();
|
const bool rescaled = image.ScaleUp();
|
||||||
if (!has_copy && image.HasScaled()) {
|
|
||||||
total_used_memory += GetScaledImageSizeBytes(image);
|
|
||||||
}
|
|
||||||
if (!rescaled) {
|
if (!rescaled) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (!has_copy) {
|
||||||
|
total_used_memory += GetScaledImageSizeBytes(image);
|
||||||
|
}
|
||||||
InvalidateScale(image);
|
InvalidateScale(image);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1733,10 +1691,7 @@ ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, DA
|
|||||||
for (const auto& copy_object : join_copies_to_do) {
|
for (const auto& copy_object : join_copies_to_do) {
|
||||||
Image& overlap = slot_images[copy_object.id];
|
Image& overlap = slot_images[copy_object.id];
|
||||||
if (copy_object.is_alias) {
|
if (copy_object.is_alias) {
|
||||||
if (!overlap.IsSafeGpuCopy()) {
|
if (!overlap.IsSafeDownload()) {
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (overlap.info.num_samples != new_image.info.num_samples) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const auto alias_pointer = join_alias_indices.find(copy_object.id);
|
const auto alias_pointer = join_alias_indices.find(copy_object.id);
|
||||||
@@ -1922,10 +1877,67 @@ SamplerId TextureCache<P>::FindSampler(const TSCEntry& config, bool compute) {
|
|||||||
const auto [pair, is_new] = channel_state->samplers.try_emplace(config);
|
const auto [pair, is_new] = channel_state->samplers.try_emplace(config);
|
||||||
if (is_new) {
|
if (is_new) {
|
||||||
pair->second = slot_samplers.insert(runtime, config);
|
pair->second = slot_samplers.insert(runtime, config);
|
||||||
|
EnforceSamplerBudget();
|
||||||
}
|
}
|
||||||
return pair->second;
|
return pair->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class P>
|
||||||
|
std::optional<size_t> TextureCache<P>::QuerySamplerBudget() const {
|
||||||
|
if constexpr (requires { runtime.GetSamplerHeapBudget(); }) {
|
||||||
|
return runtime.GetSamplerHeapBudget();
|
||||||
|
} else {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class P>
|
||||||
|
void TextureCache<P>::EnforceSamplerBudget() {
|
||||||
|
if (auto const budget = QuerySamplerBudget(); budget) {
|
||||||
|
if (slot_samplers.size() < *budget) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!channel_state) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (last_sampler_gc_frame == frame_tick) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
last_sampler_gc_frame = frame_tick;
|
||||||
|
TrimInactiveSamplers(*budget);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class P>
|
||||||
|
void TextureCache<P>::TrimInactiveSamplers(size_t budget) {
|
||||||
|
if (channel_state->samplers.size() > 0) {
|
||||||
|
constexpr size_t SAMPLER_GC_SLACK = 1024;
|
||||||
|
ankerl::unordered_dense::set<SamplerId> active_sampler_ids;
|
||||||
|
for (auto const& e : channel_state->sampler_ids)
|
||||||
|
active_sampler_ids.insert(e.second);
|
||||||
|
// Elements in the map must be necesarily valid
|
||||||
|
size_t removed = 0;
|
||||||
|
for (auto it = channel_state->samplers.begin(); it != channel_state->samplers.end();) {
|
||||||
|
const SamplerId sampler_id = it->second;
|
||||||
|
if (!sampler_id || sampler_id == CORRUPT_ID) {
|
||||||
|
it = channel_state->samplers.erase(it);
|
||||||
|
} else if (std::ranges::find(active_sampler_ids, sampler_id) != active_sampler_ids.end()) {
|
||||||
|
++it;
|
||||||
|
} else {
|
||||||
|
slot_samplers.erase(sampler_id);
|
||||||
|
it = channel_state->samplers.erase(it);
|
||||||
|
++removed;
|
||||||
|
if (slot_samplers.size() + SAMPLER_GC_SLACK <= budget) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (removed != 0) {
|
||||||
|
LOG_WARNING(HW_GPU, "Sampler cache exceeded {} entries on this driver; reclaimed {} inactive samplers", budget, removed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
ImageViewId TextureCache<P>::FindColorBuffer(size_t index) {
|
ImageViewId TextureCache<P>::FindColorBuffer(size_t index) {
|
||||||
const auto& regs = maxwell3d->regs;
|
const auto& regs = maxwell3d->regs;
|
||||||
@@ -2449,7 +2461,6 @@ void TextureCache<P>::RemoveImageViewReferences(std::span<const ImageViewId> rem
|
|||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
void TextureCache<P>::RemoveFramebuffers(std::span<const ImageViewId> removed_views) {
|
void TextureCache<P>::RemoveFramebuffers(std::span<const ImageViewId> removed_views) {
|
||||||
runtime.FlushDeferredClear();
|
|
||||||
auto it = framebuffers.begin();
|
auto it = framebuffers.begin();
|
||||||
while (it != framebuffers.end()) {
|
while (it != framebuffers.end()) {
|
||||||
if (it->first.Contains(removed_views)) {
|
if (it->first.Contains(removed_views)) {
|
||||||
|
|||||||
@@ -138,8 +138,6 @@ class TextureCache : public VideoCommon::ChannelSetupCaches<TextureCacheChannelI
|
|||||||
AsyncBuffer staging_buffer;
|
AsyncBuffer staging_buffer;
|
||||||
size_t last_submitted_offset = 0;
|
size_t last_submitted_offset = 0;
|
||||||
size_t bytes_per_slice;
|
size_t bytes_per_slice;
|
||||||
u32 slices_submitted = 0;
|
|
||||||
bool chunked = false;
|
|
||||||
bool initialized = false;
|
bool initialized = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -305,8 +303,6 @@ private:
|
|||||||
FramebufferId GetFramebufferId(const RenderTargets& key);
|
FramebufferId GetFramebufferId(const RenderTargets& key);
|
||||||
|
|
||||||
/// Refresh the contents (pixel data) of an image
|
/// Refresh the contents (pixel data) of an image
|
||||||
[[nodiscard]] bool IsDownloadable(const ImageBase& image) const noexcept;
|
|
||||||
|
|
||||||
void RefreshContents(Image& image, ImageId image_id);
|
void RefreshContents(Image& image, ImageId image_id);
|
||||||
|
|
||||||
/// Upload data from guest to an image
|
/// Upload data from guest to an image
|
||||||
@@ -422,6 +418,9 @@ private:
|
|||||||
|
|
||||||
void QueueAsyncDecode(Image& image, ImageId image_id);
|
void QueueAsyncDecode(Image& image, ImageId image_id);
|
||||||
void TickAsyncDecode();
|
void TickAsyncDecode();
|
||||||
|
void EnforceSamplerBudget();
|
||||||
|
void TrimInactiveSamplers(size_t budget);
|
||||||
|
std::optional<size_t> QuerySamplerBudget() const;
|
||||||
|
|
||||||
void QueueAsyncUnswizzle(Image& image, ImageId image_id);
|
void QueueAsyncUnswizzle(Image& image, ImageId image_id);
|
||||||
void TickAsyncUnswizzle();
|
void TickAsyncUnswizzle();
|
||||||
@@ -508,6 +507,7 @@ private:
|
|||||||
|
|
||||||
u64 modification_tick = 0;
|
u64 modification_tick = 0;
|
||||||
u64 frame_tick = 0;
|
u64 frame_tick = 0;
|
||||||
|
u64 last_sampler_gc_frame = (std::numeric_limits<u64>::max)();
|
||||||
|
|
||||||
Common::ThreadWorker texture_decode_worker{1, "TextureDecoder", {},
|
Common::ThreadWorker texture_decode_worker{1, "TextureDecoder", {},
|
||||||
Common::ThreadPlacement::Efficiency};
|
Common::ThreadPlacement::Efficiency};
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cmath>
|
|
||||||
|
|
||||||
#include "common/cityhash.h"
|
#include "common/cityhash.h"
|
||||||
#include "common/settings.h"
|
#include "common/settings.h"
|
||||||
@@ -18,25 +17,53 @@ namespace Tegra::Texture {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
float SrgbToLinear(u32 value) {
|
[[maybe_unused]] constexpr std::array<float, 256> SRGB_CONVERSION_LUT = {
|
||||||
const float encoded = static_cast<float>(value) / 255.0f;
|
0.000000f, 0.000000f, 0.000000f, 0.000012f, 0.000021f, 0.000033f, 0.000046f, 0.000062f,
|
||||||
if (encoded <= 0.04045f) {
|
0.000081f, 0.000102f, 0.000125f, 0.000151f, 0.000181f, 0.000214f, 0.000251f, 0.000293f,
|
||||||
return encoded / 12.92f;
|
0.000338f, 0.000388f, 0.000443f, 0.000503f, 0.000568f, 0.000639f, 0.000715f, 0.000798f,
|
||||||
}
|
0.000887f, 0.000983f, 0.001085f, 0.001195f, 0.001312f, 0.001437f, 0.001569f, 0.001710f,
|
||||||
return std::pow((encoded + 0.055f) / 1.055f, 2.4f);
|
0.001860f, 0.002019f, 0.002186f, 0.002364f, 0.002551f, 0.002748f, 0.002955f, 0.003174f,
|
||||||
}
|
0.003403f, 0.003643f, 0.003896f, 0.004160f, 0.004436f, 0.004725f, 0.005028f, 0.005343f,
|
||||||
|
0.005672f, 0.006015f, 0.006372f, 0.006744f, 0.007130f, 0.007533f, 0.007950f, 0.008384f,
|
||||||
|
0.008834f, 0.009301f, 0.009785f, 0.010286f, 0.010805f, 0.011342f, 0.011898f, 0.012472f,
|
||||||
|
0.013066f, 0.013680f, 0.014313f, 0.014967f, 0.015641f, 0.016337f, 0.017054f, 0.017793f,
|
||||||
|
0.018554f, 0.019337f, 0.020144f, 0.020974f, 0.021828f, 0.022706f, 0.023609f, 0.024536f,
|
||||||
|
0.025489f, 0.026468f, 0.027473f, 0.028504f, 0.029563f, 0.030649f, 0.031762f, 0.032904f,
|
||||||
|
0.034074f, 0.035274f, 0.036503f, 0.037762f, 0.039050f, 0.040370f, 0.041721f, 0.043103f,
|
||||||
|
0.044518f, 0.045964f, 0.047444f, 0.048956f, 0.050503f, 0.052083f, 0.053699f, 0.055349f,
|
||||||
|
0.057034f, 0.058755f, 0.060513f, 0.062307f, 0.064139f, 0.066008f, 0.067915f, 0.069861f,
|
||||||
|
0.071845f, 0.073869f, 0.075933f, 0.078037f, 0.080182f, 0.082369f, 0.084597f, 0.086867f,
|
||||||
|
0.089180f, 0.091535f, 0.093935f, 0.096378f, 0.098866f, 0.101398f, 0.103977f, 0.106601f,
|
||||||
|
0.109271f, 0.111988f, 0.114753f, 0.117565f, 0.120426f, 0.123335f, 0.126293f, 0.129301f,
|
||||||
|
0.132360f, 0.135469f, 0.138629f, 0.141841f, 0.145105f, 0.148421f, 0.151791f, 0.155214f,
|
||||||
|
0.158691f, 0.162224f, 0.165810f, 0.169453f, 0.173152f, 0.176907f, 0.180720f, 0.184589f,
|
||||||
|
0.188517f, 0.192504f, 0.196549f, 0.200655f, 0.204820f, 0.209046f, 0.213334f, 0.217682f,
|
||||||
|
0.222093f, 0.226567f, 0.231104f, 0.235704f, 0.240369f, 0.245099f, 0.249894f, 0.254754f,
|
||||||
|
0.259681f, 0.264674f, 0.269736f, 0.274864f, 0.280062f, 0.285328f, 0.290664f, 0.296070f,
|
||||||
|
0.301546f, 0.307094f, 0.312713f, 0.318404f, 0.324168f, 0.330006f, 0.335916f, 0.341902f,
|
||||||
|
0.347962f, 0.354097f, 0.360309f, 0.366597f, 0.372961f, 0.379403f, 0.385924f, 0.392524f,
|
||||||
|
0.399202f, 0.405960f, 0.412798f, 0.419718f, 0.426719f, 0.433802f, 0.440967f, 0.448216f,
|
||||||
|
0.455548f, 0.462965f, 0.470465f, 0.478052f, 0.485725f, 0.493484f, 0.501329f, 0.509263f,
|
||||||
|
0.517285f, 0.525396f, 0.533595f, 0.541885f, 0.550265f, 0.558736f, 0.567299f, 0.575954f,
|
||||||
|
0.584702f, 0.593542f, 0.602477f, 0.611507f, 0.620632f, 0.629852f, 0.639168f, 0.648581f,
|
||||||
|
0.658092f, 0.667700f, 0.677408f, 0.687214f, 0.697120f, 0.707127f, 0.717234f, 0.727443f,
|
||||||
|
0.737753f, 0.748167f, 0.758685f, 0.769305f, 0.780031f, 0.790861f, 0.801798f, 0.812839f,
|
||||||
|
0.823989f, 0.835246f, 0.846611f, 0.858085f, 0.869668f, 0.881360f, 0.893164f, 0.905078f,
|
||||||
|
0.917104f, 0.929242f, 0.941493f, 0.953859f, 0.966338f, 1.000000f, 1.000000f, 1.000000f,
|
||||||
|
};
|
||||||
|
|
||||||
} // Anonymous namespace
|
} // Anonymous namespace
|
||||||
|
|
||||||
std::array<float, 4> TSCEntry::BorderColor() const noexcept {
|
std::array<float, 4> TSCEntry::BorderColor() const noexcept {
|
||||||
|
// TODO: Handle SRGB correctly. Using this breaks shadows in some games (Xenoblade).
|
||||||
|
// if (!srgb_conversion) {
|
||||||
|
// return border_color;
|
||||||
|
//}
|
||||||
|
// return {SRGB_CONVERSION_LUT[srgb_border_color_r], SRGB_CONVERSION_LUT[srgb_border_color_g],
|
||||||
|
// SRGB_CONVERSION_LUT[srgb_border_color_b], border_color[3]};
|
||||||
return border_color;
|
return border_color;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::array<float, 4> TSCEntry::SrgbBorderColor() const noexcept {
|
|
||||||
return {SrgbToLinear(srgb_border_color_r), SrgbToLinear(srgb_border_color_g),
|
|
||||||
SrgbToLinear(srgb_border_color_b), border_color[3]};
|
|
||||||
}
|
|
||||||
|
|
||||||
float TSCEntry::MaxAnisotropy() const noexcept {
|
float TSCEntry::MaxAnisotropy() const noexcept {
|
||||||
const bool is_suitable_mipmap_filter = mipmap_filter != TextureMipmapFilter::None;
|
const bool is_suitable_mipmap_filter = mipmap_filter != TextureMipmapFilter::None;
|
||||||
const bool has_regular_lods = min_lod_clamp == 0 && max_lod_clamp >= 256;
|
const bool has_regular_lods = min_lod_clamp == 0 && max_lod_clamp >= 256;
|
||||||
@@ -53,16 +80,16 @@ float TSCEntry::MaxAnisotropy() const noexcept {
|
|||||||
case Settings::AnisotropyMode::X4:
|
case Settings::AnisotropyMode::X4:
|
||||||
case Settings::AnisotropyMode::X8:
|
case Settings::AnisotropyMode::X8:
|
||||||
case Settings::AnisotropyMode::X16:
|
case Settings::AnisotropyMode::X16:
|
||||||
added_anisotropic = s32(anisotropic_settings) - 1;
|
case Settings::AnisotropyMode::X32:
|
||||||
|
case Settings::AnisotropyMode::X64:
|
||||||
|
added_anisotropic = u32(anisotropic_settings) - 1U;
|
||||||
break;
|
break;
|
||||||
case Settings::AnisotropyMode::Automatic: {
|
case Settings::AnisotropyMode::Automatic:
|
||||||
const u32 resolution_scale = Settings::values.resolution_info.up_scale >>
|
added_anisotropic = Settings::values.resolution_info.up_scale >> Settings::values.resolution_info.down_shift;
|
||||||
Settings::values.resolution_info.down_shift;
|
added_anisotropic = (std::max)(added_anisotropic - 1U, 0U);
|
||||||
if (resolution_scale > 1U) {
|
|
||||||
added_anisotropic = static_cast<s32>(resolution_scale - 1U);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
case Settings::AnisotropyMode::None:
|
||||||
|
return 1.0f; //No use of anisotropy
|
||||||
}
|
}
|
||||||
return float(1U << (max_anisotropy + added_anisotropic));
|
return float(1U << (max_anisotropy + added_anisotropic));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -381,8 +378,6 @@ struct TSCEntry {
|
|||||||
|
|
||||||
std::array<float, 4> BorderColor() const noexcept;
|
std::array<float, 4> BorderColor() const noexcept;
|
||||||
|
|
||||||
std::array<float, 4> SrgbBorderColor() const noexcept;
|
|
||||||
|
|
||||||
float MaxAnisotropy() const noexcept;
|
float MaxAnisotropy() const noexcept;
|
||||||
|
|
||||||
float MinLod() const {
|
float MinLod() const {
|
||||||
|
|||||||
@@ -508,9 +508,17 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
|||||||
LOG_WARNING(Render_Vulkan, "Qualcomm drivers require scaled vertex format emulation.");
|
LOG_WARNING(Render_Vulkan, "Qualcomm drivers require scaled vertex format emulation.");
|
||||||
has_broken_descriptor_aliasing = true;
|
has_broken_descriptor_aliasing = true;
|
||||||
LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken descriptor aliasing.");
|
LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken descriptor aliasing.");
|
||||||
|
LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken custom border color.");
|
||||||
|
RemoveExtensionFeature(extensions.custom_border_color, features.custom_border_color,
|
||||||
|
VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME);
|
||||||
|
LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken border color swizzle.");
|
||||||
|
RemoveExtensionFeature(extensions.border_color_swizzle, features.border_color_swizzle,
|
||||||
|
VK_EXT_BORDER_COLOR_SWIZZLE_EXTENSION_NAME);
|
||||||
LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken color write enable.");
|
LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken color write enable.");
|
||||||
RemoveExtensionFeature(extensions.color_write_enable, features.color_write_enable,
|
RemoveExtensionFeature(extensions.color_write_enable, features.color_write_enable,
|
||||||
VK_EXT_COLOR_WRITE_ENABLE_EXTENSION_NAME);
|
VK_EXT_COLOR_WRITE_ENABLE_EXTENSION_NAME);
|
||||||
|
LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken shader float controls.");
|
||||||
|
RemoveExtension(extensions.shader_float_controls, VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME);
|
||||||
LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken shader atomic int64.");
|
LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken shader atomic int64.");
|
||||||
RemoveExtensionFeature(extensions.shader_atomic_int64, features.shader_atomic_int64,
|
RemoveExtensionFeature(extensions.shader_atomic_int64, features.shader_atomic_int64,
|
||||||
VK_KHR_SHADER_ATOMIC_INT64_EXTENSION_NAME);
|
VK_KHR_SHADER_ATOMIC_INT64_EXTENSION_NAME);
|
||||||
@@ -611,6 +619,21 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (is_qualcomm) {
|
||||||
|
const size_t sampler_limit = properties.properties.limits.maxSamplerAllocationCount;
|
||||||
|
if (sampler_limit > 0) {
|
||||||
|
constexpr size_t MIN_SAMPLER_BUDGET = 1024U;
|
||||||
|
const size_t reserved = sampler_limit / 4U;
|
||||||
|
const size_t derived_budget =
|
||||||
|
(std::max)(MIN_SAMPLER_BUDGET, sampler_limit - reserved);
|
||||||
|
sampler_heap_budget = derived_budget;
|
||||||
|
LOG_WARNING(Render_Vulkan,
|
||||||
|
"Qualcomm driver reports max {} samplers; reserving {} (25%) and "
|
||||||
|
"allowing Eden to use {} (75%) to avoid heap exhaustion",
|
||||||
|
sampler_limit, reserved, sampler_heap_budget);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (extensions.sampler_filter_minmax && is_amd) {
|
if (extensions.sampler_filter_minmax && is_amd) {
|
||||||
// Disable ext_sampler_filter_minmax on AMD GCN4 and lower as it is broken.
|
// Disable ext_sampler_filter_minmax on AMD GCN4 and lower as it is broken.
|
||||||
if (!features.shader_float16_int8.shaderFloat16) {
|
if (!features.shader_float16_int8.shaderFloat16) {
|
||||||
@@ -627,6 +650,13 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
|||||||
features.shader_float16_int8.shaderFloat16 = false;
|
features.shader_float16_int8.shaderFloat16 = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (is_intel_windows) {
|
||||||
|
LOG_WARNING(Render_Vulkan,
|
||||||
|
"Intel proprietary drivers do not support MSAA->MSAA image blits. "
|
||||||
|
"MSAA scaling will use 3D helpers. MSAA resolves work normally.");
|
||||||
|
cant_blit_msaa = true;
|
||||||
|
}
|
||||||
|
|
||||||
has_broken_compute =
|
has_broken_compute =
|
||||||
CheckBrokenCompute(properties.driver.driverID, properties.properties.driverVersion) &&
|
CheckBrokenCompute(properties.driver.driverID, properties.properties.driverVersion) &&
|
||||||
!Settings::values.enable_compute_pipelines.GetValue();
|
!Settings::values.enable_compute_pipelines.GetValue();
|
||||||
@@ -945,12 +975,6 @@ bool Device::GetSuitability(bool requires_swapchain) {
|
|||||||
FOR_EACH_VK_FEATURE_EXT(FEATURE_EXTENSION);
|
FOR_EACH_VK_FEATURE_EXT(FEATURE_EXTENSION);
|
||||||
FOR_EACH_VK_EXTENSION(EXTENSION);
|
FOR_EACH_VK_EXTENSION(EXTENSION);
|
||||||
|
|
||||||
extensions.depth_stencil_resolve =
|
|
||||||
extensions.depth_stencil_resolve &&
|
|
||||||
(instance_version >= VK_API_VERSION_1_2 || extensions.create_renderpass2);
|
|
||||||
RemoveExtensionIfUnsuitable(extensions.depth_stencil_resolve,
|
|
||||||
VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME);
|
|
||||||
|
|
||||||
if (supported_extensions.contains(VK_KHR_ROBUSTNESS_2_EXTENSION_NAME)) {
|
if (supported_extensions.contains(VK_KHR_ROBUSTNESS_2_EXTENSION_NAME)) {
|
||||||
loaded_extensions.erase(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME);
|
loaded_extensions.erase(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME);
|
||||||
loaded_extensions.insert(VK_KHR_ROBUSTNESS_2_EXTENSION_NAME);
|
loaded_extensions.insert(VK_KHR_ROBUSTNESS_2_EXTENSION_NAME);
|
||||||
@@ -1098,11 +1122,6 @@ bool Device::GetSuitability(bool requires_swapchain) {
|
|||||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PUSH_DESCRIPTOR_PROPERTIES_KHR;
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PUSH_DESCRIPTOR_PROPERTIES_KHR;
|
||||||
SetNext(next, properties.push_descriptor);
|
SetNext(next, properties.push_descriptor);
|
||||||
}
|
}
|
||||||
if (extensions.depth_stencil_resolve || instance_version >= VK_API_VERSION_1_2) {
|
|
||||||
properties.depth_stencil_resolve.sType =
|
|
||||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_STENCIL_RESOLVE_PROPERTIES;
|
|
||||||
SetNext(next, properties.depth_stencil_resolve);
|
|
||||||
}
|
|
||||||
if (extensions.descriptor_buffer) {
|
if (extensions.descriptor_buffer) {
|
||||||
properties.descriptor_buffer.sType =
|
properties.descriptor_buffer.sType =
|
||||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_BUFFER_PROPERTIES_EXT;
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_BUFFER_PROPERTIES_EXT;
|
||||||
@@ -1118,21 +1137,11 @@ bool Device::GetSuitability(bool requires_swapchain) {
|
|||||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TRANSFORM_FEEDBACK_PROPERTIES_EXT;
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TRANSFORM_FEEDBACK_PROPERTIES_EXT;
|
||||||
SetNext(next, properties.transform_feedback);
|
SetNext(next, properties.transform_feedback);
|
||||||
}
|
}
|
||||||
if (extensions.vertex_attribute_divisor) {
|
|
||||||
properties.vertex_attribute_divisor.sType =
|
|
||||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_ATTRIBUTE_DIVISOR_PROPERTIES_EXT;
|
|
||||||
SetNext(next, properties.vertex_attribute_divisor);
|
|
||||||
}
|
|
||||||
if (extensions.maintenance5) {
|
if (extensions.maintenance5) {
|
||||||
properties.maintenance5.sType =
|
properties.maintenance5.sType =
|
||||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_5_PROPERTIES_KHR;
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_5_PROPERTIES_KHR;
|
||||||
SetNext(next, properties.maintenance5);
|
SetNext(next, properties.maintenance5);
|
||||||
}
|
}
|
||||||
if (extensions.custom_border_color) {
|
|
||||||
properties.custom_border_color.sType =
|
|
||||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_PROPERTIES_EXT;
|
|
||||||
SetNext(next, properties.custom_border_color);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Perform the property fetch.
|
// Perform the property fetch.
|
||||||
physical.GetProperties2(properties2);
|
physical.GetProperties2(properties2);
|
||||||
@@ -1229,7 +1238,9 @@ void Device::RemoveUnsuitableExtensions() {
|
|||||||
// VK_EXT_border_color_swizzle
|
// VK_EXT_border_color_swizzle
|
||||||
if (extensions.border_color_swizzle) {
|
if (extensions.border_color_swizzle) {
|
||||||
extensions.border_color_swizzle =
|
extensions.border_color_swizzle =
|
||||||
extensions.custom_border_color && features.border_color_swizzle.borderColorSwizzle;
|
extensions.custom_border_color &&
|
||||||
|
features.border_color_swizzle.borderColorSwizzle &&
|
||||||
|
features.border_color_swizzle.borderColorSwizzleFromImage;
|
||||||
}
|
}
|
||||||
RemoveExtensionFeatureIfUnsuitable(extensions.border_color_swizzle,
|
RemoveExtensionFeatureIfUnsuitable(extensions.border_color_swizzle,
|
||||||
features.border_color_swizzle,
|
features.border_color_swizzle,
|
||||||
@@ -1475,26 +1486,11 @@ void Device::SetupFamilies(VkSurfaceKHR surface) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Device::TryReserveCustomBorderColorSamplers(size_t count) const {
|
std::optional<size_t> Device::GetSamplerHeapBudget() const {
|
||||||
const size_t limit = properties.custom_border_color.maxCustomBorderColorSamplers;
|
if (sampler_heap_budget == 0) {
|
||||||
if (limit == 0) {
|
return std::nullopt;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
size_t used = custom_border_color_samplers_used.load(std::memory_order_relaxed);
|
return sampler_heap_budget;
|
||||||
while (used + count <= limit) {
|
|
||||||
if (custom_border_color_samplers_used.compare_exchange_weak(
|
|
||||||
used, used + count, std::memory_order_relaxed, std::memory_order_relaxed)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Device::ReleaseCustomBorderColorSamplers(size_t count) const {
|
|
||||||
if (count == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
custom_border_color_samplers_used.fetch_sub(count, std::memory_order_relaxed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
u64 Device::GetDeviceMemoryUsage() const {
|
u64 Device::GetDeviceMemoryUsage() const {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <span>
|
#include <span>
|
||||||
@@ -70,7 +69,6 @@ VK_DEFINE_HANDLE(VmaAllocator)
|
|||||||
FEATURE(EXT, ProvokingVertex, PROVOKING_VERTEX, provoking_vertex) \
|
FEATURE(EXT, ProvokingVertex, PROVOKING_VERTEX, provoking_vertex) \
|
||||||
FEATURE(EXT, Robustness2, ROBUSTNESS_2, robustness2) \
|
FEATURE(EXT, Robustness2, ROBUSTNESS_2, robustness2) \
|
||||||
FEATURE(EXT, TransformFeedback, TRANSFORM_FEEDBACK, transform_feedback) \
|
FEATURE(EXT, TransformFeedback, TRANSFORM_FEEDBACK, transform_feedback) \
|
||||||
FEATURE(EXT, VertexAttributeDivisor, VERTEX_ATTRIBUTE_DIVISOR, vertex_attribute_divisor) \
|
|
||||||
FEATURE(EXT, VertexInputDynamicState, VERTEX_INPUT_DYNAMIC_STATE, vertex_input_dynamic_state) \
|
FEATURE(EXT, VertexInputDynamicState, VERTEX_INPUT_DYNAMIC_STATE, vertex_input_dynamic_state) \
|
||||||
FEATURE(KHR, Maintenance5, MAINTENANCE_5, maintenance5) \
|
FEATURE(KHR, Maintenance5, MAINTENANCE_5, maintenance5) \
|
||||||
FEATURE(KHR, Maintenance6, MAINTENANCE_6, maintenance6) \
|
FEATURE(KHR, Maintenance6, MAINTENANCE_6, maintenance6) \
|
||||||
@@ -92,8 +90,7 @@ VK_DEFINE_HANDLE(VmaAllocator)
|
|||||||
EXTENSION(EXT, SHADER_STENCIL_EXPORT, shader_stencil_export) \
|
EXTENSION(EXT, SHADER_STENCIL_EXPORT, shader_stencil_export) \
|
||||||
EXTENSION(EXT, SHADER_VIEWPORT_INDEX_LAYER, shader_viewport_index_layer) \
|
EXTENSION(EXT, SHADER_VIEWPORT_INDEX_LAYER, shader_viewport_index_layer) \
|
||||||
EXTENSION(EXT, TOOLING_INFO, tooling_info) \
|
EXTENSION(EXT, TOOLING_INFO, tooling_info) \
|
||||||
EXTENSION(KHR, CREATE_RENDERPASS_2, create_renderpass2) \
|
EXTENSION(EXT, VERTEX_ATTRIBUTE_DIVISOR, vertex_attribute_divisor) \
|
||||||
EXTENSION(KHR, DEPTH_STENCIL_RESOLVE, depth_stencil_resolve) \
|
|
||||||
EXTENSION(KHR, DRAW_INDIRECT_COUNT, draw_indirect_count) \
|
EXTENSION(KHR, DRAW_INDIRECT_COUNT, draw_indirect_count) \
|
||||||
EXTENSION(KHR, DRIVER_PROPERTIES, driver_properties) \
|
EXTENSION(KHR, DRIVER_PROPERTIES, driver_properties) \
|
||||||
EXTENSION(KHR, PUSH_DESCRIPTOR, push_descriptor) \
|
EXTENSION(KHR, PUSH_DESCRIPTOR, push_descriptor) \
|
||||||
@@ -354,7 +351,6 @@ public:
|
|||||||
|
|
||||||
#define FN_MAX_LIMIT_LIST \
|
#define FN_MAX_LIMIT_LIST \
|
||||||
FN_MAX_LIMIT_ELEM(ComputeSharedMemorySize) \
|
FN_MAX_LIMIT_ELEM(ComputeSharedMemorySize) \
|
||||||
FN_MAX_LIMIT_ELEM(ComputeWorkGroupInvocations) \
|
|
||||||
FN_MAX_LIMIT_ELEM(PerStageDescriptorSampledImages) \
|
FN_MAX_LIMIT_ELEM(PerStageDescriptorSampledImages) \
|
||||||
FN_MAX_LIMIT_ELEM(PerStageResources) \
|
FN_MAX_LIMIT_ELEM(PerStageResources) \
|
||||||
FN_MAX_LIMIT_ELEM(DescriptorSetSamplers) \
|
FN_MAX_LIMIT_ELEM(DescriptorSetSamplers) \
|
||||||
@@ -617,32 +613,6 @@ FN_MAX_LIMIT_LIST
|
|||||||
return extensions.shader_stencil_export;
|
return extensions.shader_stencil_export;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if the device supports VK_KHR_create_renderpass2.
|
|
||||||
bool IsKhrCreateRenderPass2Supported() const {
|
|
||||||
return extensions.create_renderpass2 || instance_version >= VK_API_VERSION_1_2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if the device supports VK_KHR_depth_stencil_resolve.
|
|
||||||
bool IsKhrDepthStencilResolveSupported() const {
|
|
||||||
return (extensions.depth_stencil_resolve || instance_version >= VK_API_VERSION_1_2) &&
|
|
||||||
IsKhrCreateRenderPass2Supported();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the supported resolve modes for the depth aspect.
|
|
||||||
VkResolveModeFlags GetDepthResolveModes() const {
|
|
||||||
return properties.depth_stencil_resolve.supportedDepthResolveModes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the supported resolve modes for the stencil aspect.
|
|
||||||
VkResolveModeFlags GetStencilResolveModes() const {
|
|
||||||
return properties.depth_stencil_resolve.supportedStencilResolveModes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if only one of the depth and stencil aspects may be resolved.
|
|
||||||
bool SupportsIndependentResolveNone() const {
|
|
||||||
return properties.depth_stencil_resolve.independentResolveNone == VK_TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if depth/stencil operations can be performed efficiently.
|
/// Returns true if depth/stencil operations can be performed efficiently.
|
||||||
/// Either through shader export or hardware blits.
|
/// Either through shader export or hardware blits.
|
||||||
bool CanPerformDepthStencilOperations() const {
|
bool CanPerformDepthStencilOperations() const {
|
||||||
@@ -680,32 +650,6 @@ FN_MAX_LIMIT_LIST
|
|||||||
return features.host_query_reset.hostQueryReset != VK_FALSE;
|
return features.host_query_reset.hostQueryReset != VK_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 GetMaxVertexAttribDivisor() const {
|
|
||||||
const u32 reported = properties.vertex_attribute_divisor.maxVertexAttribDivisor;
|
|
||||||
if (reported == 0) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return reported;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsVertexAttributeInstanceRateZeroDivisorSupported() const {
|
|
||||||
return features.vertex_attribute_divisor.vertexAttributeInstanceRateZeroDivisor == VK_TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
u32 GetVertexAttribDivisor(u32 frequency) const {
|
|
||||||
const u32 max_divisor = GetMaxVertexAttribDivisor();
|
|
||||||
if (frequency == 0) {
|
|
||||||
if (IsVertexAttributeInstanceRateZeroDivisorSupported()) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return max_divisor;
|
|
||||||
}
|
|
||||||
if (frequency > max_divisor) {
|
|
||||||
return max_divisor;
|
|
||||||
}
|
|
||||||
return frequency;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if the device supports VK_EXT_transform_feedback.
|
/// Returns true if the device supports VK_EXT_transform_feedback.
|
||||||
bool IsExtTransformFeedbackSupported() const {
|
bool IsExtTransformFeedbackSupported() const {
|
||||||
return extensions.transform_feedback;
|
return extensions.transform_feedback;
|
||||||
@@ -727,18 +671,20 @@ FN_MAX_LIMIT_LIST
|
|||||||
return features.transform_feedback.geometryStreams;
|
return features.transform_feedback.geometryStreams;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if custom border colors can be created without a format.
|
/// Returns true if the device supports VK_EXT_custom_border_color.
|
||||||
bool IsCustomBorderColorUsable() const {
|
bool IsExtCustomBorderColorSupported() const {
|
||||||
return extensions.custom_border_color &&
|
return extensions.custom_border_color;
|
||||||
features.custom_border_color.customBorderColors &&
|
|
||||||
features.custom_border_color.customBorderColorWithoutFormat;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Takes budget for samplers carrying a custom border color, false when exhausted.
|
/// Returns true if customBorderColors feature is available.
|
||||||
bool TryReserveCustomBorderColorSamplers(size_t count) const;
|
bool IsCustomBorderColorsSupported() const {
|
||||||
|
return features.custom_border_color.customBorderColors;
|
||||||
|
}
|
||||||
|
|
||||||
/// Gives back budget taken by TryReserveCustomBorderColorSamplers.
|
/// Returns true if customBorderColorWithoutFormat feature is available.
|
||||||
void ReleaseCustomBorderColorSamplers(size_t count) const;
|
bool IsCustomBorderColorWithoutFormatSupported() const {
|
||||||
|
return features.custom_border_color.customBorderColorWithoutFormat;
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if the device supports VK_EXT_color_write_enable.
|
/// Returns true if the device supports VK_EXT_color_write_enable.
|
||||||
bool IsExtColorWriteEnableSupported() const {
|
bool IsExtColorWriteEnableSupported() const {
|
||||||
@@ -750,12 +696,6 @@ FN_MAX_LIMIT_LIST
|
|||||||
return extensions.border_color_swizzle;
|
return extensions.border_color_swizzle;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if samplers must be carried with border color swizzle mapping.
|
|
||||||
bool NeedsBorderColorSwizzleMapping() const {
|
|
||||||
return extensions.border_color_swizzle &&
|
|
||||||
!features.border_color_swizzle.borderColorSwizzleFromImage;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if borderColorSwizzleFromImage is available.
|
/// Returns true if borderColorSwizzleFromImage is available.
|
||||||
bool IsBorderColorSwizzleFromImageSupported() const {
|
bool IsBorderColorSwizzleFromImageSupported() const {
|
||||||
return features.border_color_swizzle.borderColorSwizzleFromImage;
|
return features.border_color_swizzle.borderColorSwizzleFromImage;
|
||||||
@@ -951,6 +891,8 @@ FN_MAX_LIMIT_LIST
|
|||||||
return has_broken_parallel_compiling;
|
return has_broken_parallel_compiling;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<size_t> GetSamplerHeapBudget() const;
|
||||||
|
|
||||||
/// Returns the vendor name reported from Vulkan.
|
/// Returns the vendor name reported from Vulkan.
|
||||||
std::string_view GetVendorName() const {
|
std::string_view GetVendorName() const {
|
||||||
return properties.driver.driverName;
|
return properties.driver.driverName;
|
||||||
@@ -979,6 +921,10 @@ FN_MAX_LIMIT_LIST
|
|||||||
return supports_d24_depth;
|
return supports_d24_depth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CantBlitMSAA() const {
|
||||||
|
return cant_blit_msaa;
|
||||||
|
}
|
||||||
|
|
||||||
bool MustEmulateScaledFormats() const {
|
bool MustEmulateScaledFormats() const {
|
||||||
return must_emulate_scaled_formats;
|
return must_emulate_scaled_formats;
|
||||||
}
|
}
|
||||||
@@ -1201,10 +1147,7 @@ private:
|
|||||||
VkPhysicalDeviceDescriptorBufferPropertiesEXT descriptor_buffer{};
|
VkPhysicalDeviceDescriptorBufferPropertiesEXT descriptor_buffer{};
|
||||||
VkPhysicalDeviceSubgroupSizeControlProperties subgroup_size_control{};
|
VkPhysicalDeviceSubgroupSizeControlProperties subgroup_size_control{};
|
||||||
VkPhysicalDeviceTransformFeedbackPropertiesEXT transform_feedback{};
|
VkPhysicalDeviceTransformFeedbackPropertiesEXT transform_feedback{};
|
||||||
VkPhysicalDeviceVertexAttributeDivisorPropertiesEXT vertex_attribute_divisor{};
|
|
||||||
VkPhysicalDeviceMaintenance5PropertiesKHR maintenance5{};
|
VkPhysicalDeviceMaintenance5PropertiesKHR maintenance5{};
|
||||||
VkPhysicalDeviceDepthStencilResolveProperties depth_stencil_resolve{};
|
|
||||||
VkPhysicalDeviceCustomBorderColorPropertiesEXT custom_border_color{};
|
|
||||||
|
|
||||||
VkPhysicalDeviceProperties properties{};
|
VkPhysicalDeviceProperties properties{};
|
||||||
};
|
};
|
||||||
@@ -1232,6 +1175,7 @@ private:
|
|||||||
bool has_nsight_graphics{}; ///< Has Nsight Graphics attached
|
bool has_nsight_graphics{}; ///< Has Nsight Graphics attached
|
||||||
bool has_radeon_gpu_profiler{}; ///< Has Radeon GPU Profiler attached.
|
bool has_radeon_gpu_profiler{}; ///< Has Radeon GPU Profiler attached.
|
||||||
bool supports_d24_depth{}; ///< Supports D24 depth buffers.
|
bool supports_d24_depth{}; ///< Supports D24 depth buffers.
|
||||||
|
bool cant_blit_msaa{}; ///< Does not support MSAA<->MSAA blitting.
|
||||||
bool must_emulate_scaled_formats{}; ///< Requires scaled vertex format emulation
|
bool must_emulate_scaled_formats{}; ///< Requires scaled vertex format emulation
|
||||||
bool dynamic_state3_blending{}; ///< Has blending features of dynamic_state3.
|
bool dynamic_state3_blending{}; ///< Has blending features of dynamic_state3.
|
||||||
bool dynamic_state3_enables{}; ///< Has at least one enable feature of dynamic_state3.
|
bool dynamic_state3_enables{}; ///< Has at least one enable feature of dynamic_state3.
|
||||||
@@ -1243,7 +1187,7 @@ private:
|
|||||||
bool dynamic_state3_alpha_to_coverage{};
|
bool dynamic_state3_alpha_to_coverage{};
|
||||||
bool dynamic_state3_alpha_to_one{};
|
bool dynamic_state3_alpha_to_one{};
|
||||||
bool supports_conditional_barriers{}; ///< Allows barriers in conditional control flow.
|
bool supports_conditional_barriers{}; ///< Allows barriers in conditional control flow.
|
||||||
mutable std::atomic<size_t> custom_border_color_samplers_used{};
|
size_t sampler_heap_budget{}; ///< Sampler budget for buggy drivers (0 = unlimited).
|
||||||
u64 device_access_memory{}; ///< Total size of device local memory in bytes.
|
u64 device_access_memory{}; ///< Total size of device local memory in bytes.
|
||||||
u32 sets_per_pool{}; ///< Sets per Description Pool
|
u32 sets_per_pool{}; ///< Sets per Description Pool
|
||||||
NvidiaArchitecture nvidia_arch{NvidiaArchitecture::Arch_AmpereOrNewer};
|
NvidiaArchitecture nvidia_arch{NvidiaArchitecture::Arch_AmpereOrNewer};
|
||||||
|
|||||||
@@ -184,7 +184,6 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
|
|||||||
X(vkCreatePipelineLayout);
|
X(vkCreatePipelineLayout);
|
||||||
X(vkCreateQueryPool);
|
X(vkCreateQueryPool);
|
||||||
X(vkCreateRenderPass);
|
X(vkCreateRenderPass);
|
||||||
X(vkCreateRenderPass2);
|
|
||||||
X(vkCreateSampler);
|
X(vkCreateSampler);
|
||||||
X(vkCreateSemaphore);
|
X(vkCreateSemaphore);
|
||||||
X(vkCreateShaderModule);
|
X(vkCreateShaderModule);
|
||||||
@@ -271,10 +270,6 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
|
|||||||
if (!dld.vkQueueSubmit2) {
|
if (!dld.vkQueueSubmit2) {
|
||||||
Proc(dld.vkQueueSubmit2, dld, "vkQueueSubmit2KHR", device);
|
Proc(dld.vkQueueSubmit2, dld, "vkQueueSubmit2KHR", device);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!dld.vkCreateRenderPass2) {
|
|
||||||
Proc(dld.vkCreateRenderPass2, dld, "vkCreateRenderPass2KHR", device);
|
|
||||||
}
|
|
||||||
#undef X
|
#undef X
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -730,12 +725,6 @@ RenderPass Device::CreateRenderPass(const VkRenderPassCreateInfo& ci) const {
|
|||||||
return RenderPass(object, handle, *dld);
|
return RenderPass(object, handle, *dld);
|
||||||
}
|
}
|
||||||
|
|
||||||
RenderPass Device::CreateRenderPass2(const VkRenderPassCreateInfo2& ci) const {
|
|
||||||
VkRenderPass object;
|
|
||||||
Check(dld->vkCreateRenderPass2(handle, &ci, nullptr, &object));
|
|
||||||
return RenderPass(object, handle, *dld);
|
|
||||||
}
|
|
||||||
|
|
||||||
DescriptorSetLayout Device::CreateDescriptorSetLayout(
|
DescriptorSetLayout Device::CreateDescriptorSetLayout(
|
||||||
const VkDescriptorSetLayoutCreateInfo& ci) const {
|
const VkDescriptorSetLayoutCreateInfo& ci) const {
|
||||||
VkDescriptorSetLayout object;
|
VkDescriptorSetLayout object;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user