mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-14 21:04:41 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1b54224d07 | |||
| 2a105c2869 | |||
| 4b243b17df | |||
| c0a34acc5c | |||
| ca5a3b6bbd | |||
| 12a9e9bd7c |
@@ -607,12 +607,6 @@ object NativeLibrary {
|
||||
*/
|
||||
external fun addFileToFilesystemProvider(path: String)
|
||||
|
||||
/**
|
||||
* Adds a game-folder file to the manual filesystem provider, respecting the internal gate for
|
||||
* game-folder external-content mounting.
|
||||
*/
|
||||
external fun addGameFolderFileToFilesystemProvider(path: String)
|
||||
|
||||
/**
|
||||
* Clears all files added to the manual filesystem provider in our EmulationSession instance
|
||||
*/
|
||||
|
||||
+2
-15
@@ -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
|
||||
|
||||
package org.yuzu.yuzu_emu.features.fetcher
|
||||
|
||||
import android.graphics.Rect
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
class SpacingItemDecoration(private val spacing: Int) : RecyclerView.ItemDecoration() {
|
||||
@@ -16,20 +15,8 @@ class SpacingItemDecoration(private val spacing: Int) : RecyclerView.ItemDecorat
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
outRect.bottom = spacing
|
||||
|
||||
val position = parent.getChildAdapterPosition(view)
|
||||
if (position == RecyclerView.NO_POSITION) return
|
||||
|
||||
if (position == 0) {
|
||||
if (parent.getChildAdapterPosition(view) == 0) {
|
||||
outRect.top = spacing
|
||||
return
|
||||
}
|
||||
|
||||
// If the item is in the first row, but NOT in first column add top spacing as well
|
||||
val layoutManager = parent.layoutManager
|
||||
if (layoutManager is GridLayoutManager && layoutManager.spanSizeLookup.getSpanGroupIndex(position, layoutManager.spanCount) == 0) {
|
||||
outRect.top = spacing
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -30,6 +30,8 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
||||
BUFFER_REORDER_DISABLE("disable_buffer_reorder"),
|
||||
RENDERER_DEBUG("debug"),
|
||||
RENDERER_PATCH_OLD_QCOM_DRIVERS("patch_old_qcom_drivers"),
|
||||
RENDERER_VERTEX_INPUT_DYNAMIC_STATE("vertex_input_dynamic_state"),
|
||||
RENDERER_PROVOKING_VERTEX("provoking_vertex"),
|
||||
RENDERER_DESCRIPTOR_INDEXING("descriptor_indexing"),
|
||||
RENDERER_SAMPLE_SHADING("sample_shading"),
|
||||
GPU_UNSWIZZLE_ENABLED("gpu_unswizzle_enabled"),
|
||||
|
||||
+32
@@ -141,6 +141,20 @@ abstract class SettingsItem(
|
||||
valuesId = R.array.dynaStateValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.RENDERER_PROVOKING_VERTEX,
|
||||
titleId = R.string.provoking_vertex,
|
||||
descriptionId = R.string.provoking_vertex_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.RENDERER_VERTEX_INPUT_DYNAMIC_STATE,
|
||||
titleId = R.string.vertex_input_dynamic_state,
|
||||
descriptionId = R.string.vertex_input_dynamic_state_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.RENDERER_DESCRIPTOR_INDEXING,
|
||||
@@ -335,6 +349,15 @@ abstract class SettingsItem(
|
||||
valuesId = R.array.astcDecodingMethodValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.RENDERER_ASTC_RECOMPRESSION,
|
||||
titleId = R.string.astc_recompression,
|
||||
descriptionId = R.string.astc_recompression_description,
|
||||
choicesId = R.array.astcRecompressionMethodNames,
|
||||
valuesId = R.array.astcRecompressionMethodValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.RENDERER_VRAM_USAGE_MODE,
|
||||
@@ -647,6 +670,15 @@ abstract class SettingsItem(
|
||||
valuesId = R.array.dmaAccuracyValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.FRAME_PACING_MODE,
|
||||
titleId = R.string.frame_pacing_mode,
|
||||
descriptionId = R.string.frame_pacing_mode_description,
|
||||
choicesId = R.array.framePacingModeNames,
|
||||
valuesId = R.array.framePacingModeValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS,
|
||||
|
||||
+26
-16
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
@@ -68,9 +68,7 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setMessage(R.string.reset_setting_confirmation)
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
|
||||
val item = settingsViewModel.clickedItem ?: return@setPositiveButton
|
||||
clearDialogState()
|
||||
when (item) {
|
||||
when (val item = settingsViewModel.clickedItem) {
|
||||
is AnalogInputSetting -> {
|
||||
val stickParam = NativeInput.getStickParam(
|
||||
item.playerIndex,
|
||||
@@ -109,17 +107,12 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
||||
}
|
||||
|
||||
else -> {
|
||||
item.setting.reset()
|
||||
settingsViewModel.clickedItem!!.setting.reset()
|
||||
settingsViewModel.setAdapterItemChanged(position)
|
||||
}
|
||||
}
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _: DialogInterface, _: Int ->
|
||||
clearDialogState()
|
||||
}
|
||||
.setOnCancelListener {
|
||||
clearDialogState()
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.create()
|
||||
}
|
||||
|
||||
@@ -193,6 +186,27 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
||||
updateButtonState(isValid)
|
||||
}
|
||||
|
||||
/*
|
||||
* xbzk: these two events, along with attachRepeat feature,
|
||||
* were causing spinbox buttons to respond twice per press
|
||||
* cutting these out to retain accelerated press functionality
|
||||
* TODO: clean this out later if no issues arise
|
||||
*
|
||||
spinboxBinding.buttonDecrement.setOnClickListener {
|
||||
val current = spinboxBinding.editValue.text.toString().toIntOrNull() ?: currentValue
|
||||
val newValue = current - 1
|
||||
spinboxBinding.editValue.setText(newValue.toString())
|
||||
updateValidity(newValue)
|
||||
}
|
||||
|
||||
spinboxBinding.buttonIncrement.setOnClickListener {
|
||||
val current = spinboxBinding.editValue.text.toString().toIntOrNull() ?: currentValue
|
||||
val newValue = current + 1
|
||||
spinboxBinding.editValue.setText(newValue.toString())
|
||||
updateValidity(newValue)
|
||||
}
|
||||
*/
|
||||
|
||||
fun attachRepeat(button: View, delta: Int) {
|
||||
val handler = Handler(Looper.getMainLooper())
|
||||
var runnable: Runnable? = null
|
||||
@@ -425,13 +439,9 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
||||
|
||||
private fun closeDialog() {
|
||||
settingsViewModel.setAdapterItemChanged(position)
|
||||
clearDialogState()
|
||||
dismiss()
|
||||
}
|
||||
|
||||
private fun clearDialogState() {
|
||||
settingsViewModel.clickedItem = null
|
||||
settingsViewModel.setSliderProgress(-1f)
|
||||
dismiss()
|
||||
}
|
||||
|
||||
private fun getValueForSingleChoiceSelection(item: SingleChoiceSetting, which: Int): Int {
|
||||
|
||||
+5
-4
@@ -267,9 +267,11 @@ class SettingsFragmentPresenter(
|
||||
|
||||
add(IntSetting.RENDERER_ACCURACY.key)
|
||||
add(IntSetting.DMA_ACCURACY.key)
|
||||
add(IntSetting.FRAME_PACING_MODE.key)
|
||||
add(IntSetting.MAX_ANISOTROPY.key)
|
||||
add(IntSetting.RENDERER_VRAM_USAGE_MODE.key)
|
||||
add(IntSetting.RENDERER_ASTC_DECODE_METHOD.key)
|
||||
add(IntSetting.RENDERER_ASTC_RECOMPRESSION.key)
|
||||
|
||||
add(BooleanSetting.SYNC_MEMORY_OPERATIONS.key)
|
||||
add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key)
|
||||
@@ -289,6 +291,8 @@ class SettingsFragmentPresenter(
|
||||
add(HeaderSetting(R.string.extensions))
|
||||
|
||||
add(IntSetting.RENDERER_DYNA_STATE.key)
|
||||
add(BooleanSetting.RENDERER_VERTEX_INPUT_DYNAMIC_STATE.key)
|
||||
add(BooleanSetting.RENDERER_PROVOKING_VERTEX.key)
|
||||
add(BooleanSetting.RENDERER_DESCRIPTOR_INDEXING.key)
|
||||
add(IntSetting.RENDERER_SAMPLE_SHADING.key)
|
||||
|
||||
@@ -1062,10 +1066,7 @@ class SettingsFragmentPresenter(
|
||||
IntSetting.THEME.getValueAsString()
|
||||
|
||||
override val defaultValue: Int = IntSetting.THEME.defaultValue
|
||||
override fun reset() {
|
||||
IntSetting.THEME.setInt(defaultValue)
|
||||
settingsViewModel.setShouldRecreate(true)
|
||||
}
|
||||
override fun reset() = IntSetting.THEME.setInt(defaultValue)
|
||||
}
|
||||
|
||||
add(HeaderSetting(R.string.app_settings))
|
||||
|
||||
@@ -127,6 +127,10 @@ class AddonViewModel : ViewModel() {
|
||||
return
|
||||
}
|
||||
|
||||
// Check if there are multiple update versions
|
||||
val updates = _patchList.value.filter { PatchType.from(it.type) == PatchType.Update }
|
||||
val hasMultipleUpdates = updates.size > 1
|
||||
|
||||
NativeConfig.setDisabledAddons(
|
||||
game!!.programId,
|
||||
_patchList.value.mapNotNull {
|
||||
@@ -136,7 +140,7 @@ class AddonViewModel : ViewModel() {
|
||||
if (PatchType.from(it.type) == PatchType.Update) {
|
||||
if (it.name.contains("(NAND)") || it.name.contains("(SDMC)")) {
|
||||
it.name
|
||||
} else if (it.numericVersion != 0L) {
|
||||
} else if (hasMultipleUpdates) {
|
||||
"Update@${it.numericVersion}"
|
||||
} else {
|
||||
it.name
|
||||
|
||||
@@ -94,6 +94,8 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
|
||||
// Since Android 15, google automatically forces "games" to be 60 hrz
|
||||
// This ensures the display's max refresh rate is actually used
|
||||
display?.let {
|
||||
val supportedModes = it.supportedModes
|
||||
val maxRefreshRate = supportedModes.maxByOrNull { mode -> mode.refreshRate }
|
||||
@@ -422,9 +424,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
)
|
||||
|
||||
val uriString = result.toString()
|
||||
val folder = gamesViewModel.folders.value.firstOrNull {
|
||||
it.uriString == uriString && it.type == org.yuzu.yuzu_emu.model.DirectoryType.EXTERNAL_CONTENT
|
||||
}
|
||||
val folder = gamesViewModel.folders.value.firstOrNull { it.uriString == uriString }
|
||||
if (folder != null) {
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
|
||||
@@ -51,24 +51,11 @@ object GameHelper {
|
||||
|
||||
// Scan External Content directories and register all NSP/XCI files
|
||||
val externalContentDirs = NativeConfig.getExternalContentDirs()
|
||||
val uniqueExternalContentDirs = linkedSetOf<String>()
|
||||
externalContentDirs.forEach { externalDir ->
|
||||
if (externalDir.isNotEmpty()) {
|
||||
uniqueExternalContentDirs.add(externalDir)
|
||||
}
|
||||
}
|
||||
|
||||
val mountedContainerUris = mutableSetOf<String>()
|
||||
for (externalDir in uniqueExternalContentDirs) {
|
||||
for (externalDir in externalContentDirs) {
|
||||
if (externalDir.isNotEmpty()) {
|
||||
val externalDirUri = externalDir.toUri()
|
||||
if (FileUtil.isTreeUriValid(externalDirUri)) {
|
||||
scanContentContainersRecursive(FileUtil.listFiles(externalDirUri), 3) {
|
||||
val containerUri = it.uri.toString()
|
||||
if (mountedContainerUris.add(containerUri)) {
|
||||
NativeLibrary.addFileToFilesystemProvider(containerUri)
|
||||
}
|
||||
}
|
||||
scanExternalContentRecursive(FileUtil.listFiles(externalDirUri), 3)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -78,13 +65,10 @@ object GameHelper {
|
||||
val gameDirUri = gameDir.uriString.toUri()
|
||||
val isValid = FileUtil.isTreeUriValid(gameDirUri)
|
||||
if (isValid) {
|
||||
val scanDepth = if (gameDir.deepScan) 3 else 1
|
||||
|
||||
addGamesRecursive(
|
||||
games,
|
||||
FileUtil.listFiles(gameDirUri),
|
||||
scanDepth,
|
||||
mountedContainerUris
|
||||
if (gameDir.deepScan) 3 else 1
|
||||
)
|
||||
} else {
|
||||
badDirs.add(index)
|
||||
@@ -119,10 +103,9 @@ object GameHelper {
|
||||
// be done better imo.
|
||||
private val externalContentExtensions = setOf("nsp", "xci")
|
||||
|
||||
private fun scanContentContainersRecursive(
|
||||
private fun scanExternalContentRecursive(
|
||||
files: Array<MinimalDocumentFile>,
|
||||
depth: Int,
|
||||
onContainerFound: (MinimalDocumentFile) -> Unit
|
||||
depth: Int
|
||||
) {
|
||||
if (depth <= 0) {
|
||||
return
|
||||
@@ -130,15 +113,14 @@ object GameHelper {
|
||||
|
||||
files.forEach {
|
||||
if (it.isDirectory) {
|
||||
scanContentContainersRecursive(
|
||||
scanExternalContentRecursive(
|
||||
FileUtil.listFiles(it.uri),
|
||||
depth - 1,
|
||||
onContainerFound
|
||||
depth - 1
|
||||
)
|
||||
} else {
|
||||
val extension = FileUtil.getExtension(it.uri).lowercase()
|
||||
if (externalContentExtensions.contains(extension)) {
|
||||
onContainerFound(it)
|
||||
NativeLibrary.addFileToFilesystemProvider(it.uri.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -147,8 +129,7 @@ object GameHelper {
|
||||
private fun addGamesRecursive(
|
||||
games: MutableList<Game>,
|
||||
files: Array<MinimalDocumentFile>,
|
||||
depth: Int,
|
||||
mountedContainerUris: MutableSet<String>
|
||||
depth: Int
|
||||
) {
|
||||
if (depth <= 0) {
|
||||
return
|
||||
@@ -159,20 +140,11 @@ object GameHelper {
|
||||
addGamesRecursive(
|
||||
games,
|
||||
FileUtil.listFiles(it.uri),
|
||||
depth - 1,
|
||||
mountedContainerUris
|
||||
depth - 1
|
||||
)
|
||||
} else {
|
||||
val extension = FileUtil.getExtension(it.uri).lowercase()
|
||||
val filePath = it.uri.toString()
|
||||
|
||||
if (externalContentExtensions.contains(extension) &&
|
||||
mountedContainerUris.add(filePath)) {
|
||||
NativeLibrary.addGameFolderFileToFilesystemProvider(filePath)
|
||||
}
|
||||
|
||||
if (Game.extensions.contains(extension)) {
|
||||
val game = getGame(it.uri, true, false)
|
||||
if (Game.extensions.contains(FileUtil.getExtension(it.uri))) {
|
||||
val game = getGame(it.uri, true)
|
||||
if (game != null) {
|
||||
games.add(game)
|
||||
}
|
||||
@@ -181,20 +153,14 @@ object GameHelper {
|
||||
}
|
||||
}
|
||||
|
||||
fun getGame(
|
||||
uri: Uri,
|
||||
addedToLibrary: Boolean,
|
||||
registerFilesystemProvider: Boolean = true
|
||||
): Game? {
|
||||
fun getGame(uri: Uri, addedToLibrary: Boolean): Game? {
|
||||
val filePath = uri.toString()
|
||||
if (!GameMetadata.getIsValid(filePath)) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (registerFilesystemProvider) {
|
||||
// Needed to update installed content information
|
||||
NativeLibrary.addFileToFilesystemProvider(filePath)
|
||||
}
|
||||
// Needed to update installed content information
|
||||
NativeLibrary.addFileToFilesystemProvider(filePath)
|
||||
|
||||
var name = GameMetadata.getTitle(filePath)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
@@ -80,14 +80,16 @@ object PathUtil {
|
||||
}
|
||||
}
|
||||
|
||||
// This really shouldn't be necessary, but the Android API seemingly
|
||||
// doesn't have a way of doing this?
|
||||
// Apparently, on certain devices the mount location can vary, so add
|
||||
// extra cases here if we discover any new ones.
|
||||
fun getRemovableStoragePath(idString: String): String? {
|
||||
val possibleMountPaths = listOf("/mnt/media_rw/$idString", "/storage/$idString")
|
||||
var pathFile: File
|
||||
|
||||
for (mountPath in possibleMountPaths) {
|
||||
val pathFile = File(mountPath);
|
||||
if (pathFile.exists()) {
|
||||
return pathFile.absolutePath
|
||||
}
|
||||
pathFile = File("/mnt/media_rw/$idString");
|
||||
if (pathFile.exists()) {
|
||||
return pathFile.absolutePath
|
||||
}
|
||||
|
||||
return null
|
||||
|
||||
@@ -33,12 +33,6 @@ void AndroidConfig::ReadAndroidValues() {
|
||||
if (global) {
|
||||
ReadAndroidUIValues();
|
||||
ReadUIValues();
|
||||
BeginGroup(Settings::TranslateCategory(Settings::Category::DataStorage));
|
||||
Settings::values.ext_content_from_game_dirs = ReadBooleanSetting(
|
||||
std::string("ext_content_from_game_dirs"),
|
||||
std::make_optional(
|
||||
Settings::values.ext_content_from_game_dirs.GetDefault()));
|
||||
EndGroup();
|
||||
ReadOverlayValues();
|
||||
}
|
||||
ReadDriverValues();
|
||||
|
||||
@@ -6,15 +6,8 @@
|
||||
|
||||
#include <android/native_window_jni.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <dlfcn.h>
|
||||
|
||||
#include "common/android/id_cache.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#include "input_common/drivers/android.h"
|
||||
#include "input_common/drivers/touch_screen.h"
|
||||
#include "input_common/drivers/virtual_amiibo.h"
|
||||
@@ -29,12 +22,6 @@ void EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) {
|
||||
m_window_width = 0;
|
||||
m_window_height = 0;
|
||||
window_info.render_surface = nullptr;
|
||||
m_last_frame_rate_hint = -1.0f;
|
||||
m_pending_frame_rate_hint = -1.0f;
|
||||
m_pending_frame_rate_hint_votes = 0;
|
||||
m_smoothed_present_rate = 0.0f;
|
||||
m_last_frame_display_time = {};
|
||||
m_pending_frame_rate_since = {};
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -45,7 +32,6 @@ void EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) {
|
||||
UpdateCurrentFramebufferLayout(m_window_width, m_window_height);
|
||||
|
||||
window_info.render_surface = reinterpret_cast<void*>(surface);
|
||||
UpdateFrameRateHint();
|
||||
}
|
||||
|
||||
void EmuWindow_Android::OnTouchPressed(int id, float x, float y) {
|
||||
@@ -65,9 +51,6 @@ void EmuWindow_Android::OnTouchReleased(int id) {
|
||||
}
|
||||
|
||||
void EmuWindow_Android::OnFrameDisplayed() {
|
||||
UpdateObservedFrameRate();
|
||||
UpdateFrameRateHint();
|
||||
|
||||
if (!m_first_frame) {
|
||||
Common::Android::RunJNIOnFiber<void>(
|
||||
[&](JNIEnv* env) { EmulationSession::GetInstance().OnEmulationStarted(); });
|
||||
@@ -75,175 +58,6 @@ void EmuWindow_Android::OnFrameDisplayed() {
|
||||
}
|
||||
}
|
||||
|
||||
void EmuWindow_Android::UpdateObservedFrameRate() {
|
||||
const auto now = Clock::now();
|
||||
if (m_last_frame_display_time.time_since_epoch().count() != 0) {
|
||||
const auto frame_time = std::chrono::duration<float>(now - m_last_frame_display_time);
|
||||
const float seconds = frame_time.count();
|
||||
if (seconds > 0.0f) {
|
||||
const float instantaneous_rate = 1.0f / seconds;
|
||||
if (std::isfinite(instantaneous_rate) && instantaneous_rate >= 1.0f &&
|
||||
instantaneous_rate <= 240.0f) {
|
||||
constexpr float SmoothingFactor = 0.15f;
|
||||
if (m_smoothed_present_rate <= 0.0f) {
|
||||
m_smoothed_present_rate = instantaneous_rate;
|
||||
} else {
|
||||
m_smoothed_present_rate +=
|
||||
(instantaneous_rate - m_smoothed_present_rate) * SmoothingFactor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
m_last_frame_display_time = now;
|
||||
}
|
||||
|
||||
float EmuWindow_Android::QuantizeFrameRateHint(float frame_rate) {
|
||||
if (!std::isfinite(frame_rate) || frame_rate <= 0.0f) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
frame_rate = std::clamp(frame_rate, 1.0f, 240.0f);
|
||||
|
||||
constexpr float Step = 0.5f;
|
||||
return std::round(frame_rate / Step) * Step;
|
||||
}
|
||||
|
||||
float EmuWindow_Android::GetFrameTimeVerifiedHint() const {
|
||||
if (!EmulationSession::GetInstance().IsRunning()) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
const double frame_time_scale =
|
||||
EmulationSession::GetInstance().System().GetPerfStats().GetLastFrameTimeScale();
|
||||
if (!std::isfinite(frame_time_scale) || frame_time_scale <= 0.0) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
const float verified_rate =
|
||||
std::clamp(60.0f / static_cast<float>(frame_time_scale), 0.0f, 240.0f);
|
||||
return QuantizeFrameRateHint(verified_rate);
|
||||
}
|
||||
|
||||
float EmuWindow_Android::GetFrameRateHint() const {
|
||||
const float observed_rate = std::clamp(m_smoothed_present_rate, 0.0f, 240.0f);
|
||||
const float frame_time_verified_hint = GetFrameTimeVerifiedHint();
|
||||
|
||||
if (m_last_frame_rate_hint > 0.0f && observed_rate > 0.0f) {
|
||||
const float tolerance = std::max(m_last_frame_rate_hint * 0.12f, 4.0f);
|
||||
if (std::fabs(observed_rate - m_last_frame_rate_hint) <= tolerance) {
|
||||
return m_last_frame_rate_hint;
|
||||
}
|
||||
}
|
||||
|
||||
const float observed_hint = QuantizeFrameRateHint(observed_rate);
|
||||
if (observed_hint > 0.0f) {
|
||||
if (frame_time_verified_hint > 0.0f) {
|
||||
const float tolerance = std::max(observed_hint * 0.20f, 3.0f);
|
||||
if (std::fabs(observed_hint - frame_time_verified_hint) <= tolerance) {
|
||||
return QuantizeFrameRateHint((observed_hint + frame_time_verified_hint) * 0.5f);
|
||||
}
|
||||
}
|
||||
return observed_hint;
|
||||
}
|
||||
|
||||
if (frame_time_verified_hint > 0.0f) {
|
||||
return frame_time_verified_hint;
|
||||
}
|
||||
|
||||
constexpr float NominalFrameRate = 60.0f;
|
||||
if (!Settings::values.use_speed_limit.GetValue()) {
|
||||
return NominalFrameRate;
|
||||
}
|
||||
|
||||
const u16 speed_limit = Settings::SpeedLimit();
|
||||
if (speed_limit == 0) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
const float speed_limited_rate =
|
||||
NominalFrameRate * (static_cast<float>(std::min<u16>(speed_limit, 100)) / 100.0f);
|
||||
return QuantizeFrameRateHint(speed_limited_rate);
|
||||
}
|
||||
|
||||
void EmuWindow_Android::UpdateFrameRateHint() {
|
||||
auto* const surface = reinterpret_cast<ANativeWindow*>(window_info.render_surface);
|
||||
if (!surface) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto now = Clock::now();
|
||||
const float frame_rate_hint = GetFrameRateHint();
|
||||
if (std::fabs(frame_rate_hint - m_last_frame_rate_hint) < 0.01f) {
|
||||
m_pending_frame_rate_hint = frame_rate_hint;
|
||||
m_pending_frame_rate_hint_votes = 0;
|
||||
m_pending_frame_rate_since = {};
|
||||
return;
|
||||
}
|
||||
|
||||
if (frame_rate_hint == 0.0f) {
|
||||
m_pending_frame_rate_hint = frame_rate_hint;
|
||||
m_pending_frame_rate_hint_votes = 0;
|
||||
m_pending_frame_rate_since = now;
|
||||
} else if (m_last_frame_rate_hint >= 0.0f) {
|
||||
if (std::fabs(frame_rate_hint - m_pending_frame_rate_hint) >= 0.01f) {
|
||||
m_pending_frame_rate_hint = frame_rate_hint;
|
||||
m_pending_frame_rate_hint_votes = 1;
|
||||
m_pending_frame_rate_since = now;
|
||||
return;
|
||||
}
|
||||
|
||||
++m_pending_frame_rate_hint_votes;
|
||||
if (m_pending_frame_rate_since.time_since_epoch().count() == 0) {
|
||||
m_pending_frame_rate_since = now;
|
||||
}
|
||||
|
||||
const auto stable_for = now - m_pending_frame_rate_since;
|
||||
const float reference_rate = std::max(frame_rate_hint, 1.0f);
|
||||
const auto stable_duration = std::chrono::duration_cast<Clock::duration>(
|
||||
std::chrono::duration<float>(std::clamp(3.0f / reference_rate, 0.15f, 0.40f)));
|
||||
constexpr std::uint32_t MinStableVotes = 3;
|
||||
|
||||
if (m_pending_frame_rate_hint_votes < MinStableVotes || stable_for < stable_duration) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
m_pending_frame_rate_since = now;
|
||||
}
|
||||
|
||||
using SetFrameRateWithChangeStrategyFn =
|
||||
int32_t (*)(ANativeWindow*, float, int8_t, int8_t);
|
||||
using SetFrameRateFn = int32_t (*)(ANativeWindow*, float, int8_t);
|
||||
static const auto set_frame_rate_with_change_strategy =
|
||||
reinterpret_cast<SetFrameRateWithChangeStrategyFn>(
|
||||
dlsym(RTLD_DEFAULT, "ANativeWindow_setFrameRateWithChangeStrategy"));
|
||||
static const auto set_frame_rate = reinterpret_cast<SetFrameRateFn>(
|
||||
dlsym(RTLD_DEFAULT, "ANativeWindow_setFrameRate"));
|
||||
|
||||
constexpr int8_t FrameRateCompatibilityDefault = 0;
|
||||
constexpr int8_t ChangeFrameRateOnlyIfSeamless = 0;
|
||||
|
||||
int32_t result = -1;
|
||||
if (set_frame_rate_with_change_strategy) {
|
||||
result = set_frame_rate_with_change_strategy(surface, frame_rate_hint,
|
||||
FrameRateCompatibilityDefault,
|
||||
ChangeFrameRateOnlyIfSeamless);
|
||||
} else if (set_frame_rate) {
|
||||
result = set_frame_rate(surface, frame_rate_hint, FrameRateCompatibilityDefault);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (result != 0) {
|
||||
LOG_DEBUG(Frontend, "Failed to update Android surface frame rate hint: {}", result);
|
||||
return;
|
||||
}
|
||||
|
||||
m_last_frame_rate_hint = frame_rate_hint;
|
||||
m_pending_frame_rate_hint = frame_rate_hint;
|
||||
m_pending_frame_rate_hint_votes = 0;
|
||||
m_pending_frame_rate_since = {};
|
||||
}
|
||||
|
||||
EmuWindow_Android::EmuWindow_Android(ANativeWindow* surface,
|
||||
std::shared_ptr<Common::DynamicLibrary> driver_library)
|
||||
: m_driver_library{driver_library} {
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
|
||||
@@ -55,24 +50,10 @@ public:
|
||||
};
|
||||
|
||||
private:
|
||||
using Clock = std::chrono::steady_clock;
|
||||
|
||||
void UpdateFrameRateHint();
|
||||
void UpdateObservedFrameRate();
|
||||
[[nodiscard]] float GetFrameRateHint() const;
|
||||
[[nodiscard]] float GetFrameTimeVerifiedHint() const;
|
||||
[[nodiscard]] static float QuantizeFrameRateHint(float frame_rate);
|
||||
|
||||
float m_window_width{};
|
||||
float m_window_height{};
|
||||
|
||||
std::shared_ptr<Common::DynamicLibrary> m_driver_library;
|
||||
|
||||
bool m_first_frame = false;
|
||||
float m_last_frame_rate_hint = -1.0f;
|
||||
float m_pending_frame_rate_hint = -1.0f;
|
||||
float m_smoothed_present_rate = 0.0f;
|
||||
Clock::time_point m_last_frame_display_time{};
|
||||
Clock::time_point m_pending_frame_rate_since{};
|
||||
std::uint32_t m_pending_frame_rate_hint_votes = 0;
|
||||
};
|
||||
|
||||
@@ -96,11 +96,6 @@ jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsValid(JNIEnv* env, jobj
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((file_type == Loader::FileType::NSP || file_type == Loader::FileType::XCI) &&
|
||||
!Loader::IsBootableGameContainer(file, file_type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
u64 program_id = 0;
|
||||
Loader::ResultStatus res = loader->ReadProgramId(program_id);
|
||||
if (res != Loader::ResultStatus::Success) {
|
||||
|
||||
@@ -217,8 +217,107 @@ void EmulationSession::ConfigureFilesystemProvider(const std::string& filepath)
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_manual_provider->AddEntriesFromContainer(file)) {
|
||||
return;
|
||||
const auto extension = Common::ToLower(filepath.substr(filepath.find_last_of('.') + 1));
|
||||
|
||||
if (extension == "nsp") {
|
||||
auto nsp = std::make_shared<FileSys::NSP>(file);
|
||||
if (nsp->GetStatus() == Loader::ResultStatus::Success) {
|
||||
std::map<u64, u32> nsp_versions;
|
||||
std::map<u64, std::string> nsp_version_strings;
|
||||
|
||||
for (const auto& [title_id, nca_map] : nsp->GetNCAs()) {
|
||||
for (const auto& [type_pair, nca] : nca_map) {
|
||||
const auto& [title_type, content_type] = type_pair;
|
||||
|
||||
if (content_type == FileSys::ContentRecordType::Meta) {
|
||||
const auto meta_nca = std::make_shared<FileSys::NCA>(nca->GetBaseFile());
|
||||
if (meta_nca->GetStatus() == Loader::ResultStatus::Success) {
|
||||
const auto section0 = meta_nca->GetSubdirectories();
|
||||
if (!section0.empty()) {
|
||||
for (const auto& meta_file : section0[0]->GetFiles()) {
|
||||
if (meta_file->GetExtension() == "cnmt") {
|
||||
FileSys::CNMT cnmt(meta_file);
|
||||
nsp_versions[cnmt.GetTitleID()] = cnmt.GetTitleVersion();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (content_type == FileSys::ContentRecordType::Control &&
|
||||
title_type == FileSys::TitleType::Update) {
|
||||
auto romfs = nca->GetRomFS();
|
||||
if (romfs) {
|
||||
auto extracted = FileSys::ExtractRomFS(romfs);
|
||||
if (extracted) {
|
||||
auto nacp_file = extracted->GetFile("control.nacp");
|
||||
if (!nacp_file) {
|
||||
nacp_file = extracted->GetFile("Control.nacp");
|
||||
}
|
||||
if (nacp_file) {
|
||||
FileSys::NACP nacp(nacp_file);
|
||||
auto ver_str = nacp.GetVersionString();
|
||||
if (!ver_str.empty()) {
|
||||
nsp_version_strings[title_id] = ver_str;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [title_id, nca_map] : nsp->GetNCAs()) {
|
||||
for (const auto& [type_pair, nca] : nca_map) {
|
||||
const auto& [title_type, content_type] = type_pair;
|
||||
|
||||
if (title_type == FileSys::TitleType::Update) {
|
||||
u32 version = 0;
|
||||
auto ver_it = nsp_versions.find(title_id);
|
||||
if (ver_it != nsp_versions.end()) {
|
||||
version = ver_it->second;
|
||||
}
|
||||
|
||||
std::string version_string;
|
||||
auto str_it = nsp_version_strings.find(title_id);
|
||||
if (str_it != nsp_version_strings.end()) {
|
||||
version_string = str_it->second;
|
||||
}
|
||||
|
||||
m_manual_provider->AddEntryWithVersion(
|
||||
title_type, content_type, title_id, version, version_string,
|
||||
nca->GetBaseFile());
|
||||
|
||||
LOG_DEBUG(Frontend, "Added NSP update entry - TitleID: {:016X}, Version: {}, VersionStr: {}",
|
||||
title_id, version, version_string);
|
||||
} else {
|
||||
// Use regular AddEntry for non-updates
|
||||
m_manual_provider->AddEntry(title_type, content_type, title_id,
|
||||
nca->GetBaseFile());
|
||||
LOG_DEBUG(Frontend, "Added NSP entry - TitleID: {:016X}, TitleType: {}, ContentType: {}",
|
||||
title_id, static_cast<int>(title_type), static_cast<int>(content_type));
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle XCI files
|
||||
if (extension == "xci") {
|
||||
FileSys::XCI xci{file};
|
||||
if (xci.GetStatus() == Loader::ResultStatus::Success) {
|
||||
const auto nsp = xci.GetSecurePartitionNSP();
|
||||
if (nsp) {
|
||||
for (const auto& title : nsp->GetNCAs()) {
|
||||
for (const auto& entry : title.second) {
|
||||
m_manual_provider->AddEntry(entry.first.first, entry.first.second, title.first,
|
||||
entry.second->GetBaseFile());
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto loader = Loader::GetLoader(m_system, file);
|
||||
@@ -240,13 +339,6 @@ void EmulationSession::ConfigureFilesystemProvider(const std::string& filepath)
|
||||
}
|
||||
}
|
||||
|
||||
void EmulationSession::ConfigureFilesystemProviderFromGameFolder(const std::string& filepath) {
|
||||
if (!Settings::values.ext_content_from_game_dirs.GetValue()) {
|
||||
return;
|
||||
}
|
||||
ConfigureFilesystemProvider(filepath);
|
||||
}
|
||||
|
||||
void EmulationSession::InitializeSystem(bool reload) {
|
||||
if (!reload) {
|
||||
// Initialize logging system
|
||||
@@ -1517,12 +1609,6 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_addFileToFilesystemProvider(JNIEnv* e
|
||||
Common::Android::GetJString(env, jpath));
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_addGameFolderFileToFilesystemProvider(
|
||||
JNIEnv* env, jobject jobj, jstring jpath) {
|
||||
EmulationSession::GetInstance().ConfigureFilesystemProviderFromGameFolder(
|
||||
Common::Android::GetJString(env, jpath));
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_clearFilesystemProvider(JNIEnv* env, jobject jobj) {
|
||||
EmulationSession::GetInstance().GetContentProvider()->ClearAllEntries();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// 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
|
||||
|
||||
@@ -49,7 +46,6 @@ public:
|
||||
const Core::PerfStatsResults& PerfStats();
|
||||
int ShadersBuilding();
|
||||
void ConfigureFilesystemProvider(const std::string& filepath);
|
||||
void ConfigureFilesystemProviderFromGameFolder(const std::string& filepath);
|
||||
void InitializeSystem(bool reload);
|
||||
void SetAppletId(int applet_id);
|
||||
Core::SystemResultStatus InitializeEmulation(const std::string& filepath,
|
||||
|
||||
@@ -506,6 +506,8 @@
|
||||
<string name="dyna_state">الحالة الديناميكية الموسعة</string>
|
||||
<string name="dyna_state_description">يتحكم هذا الخيار في عدد الميزات التي يمكن استخدامها في حالة الديناميكية الموسعة. تسمح الأرقام الأعلى بمزيد من الميزات ويمكن أن تزيد من الأداء، ولكنها قد تسبب مشاكل مع بعض برامج التشغيل والأجهزة.</string>
|
||||
<string name="disabled">معطل</string>
|
||||
<string name="vertex_input_dynamic_state">حالة ديناميكية لإدخال الرأس</string>
|
||||
<string name="vertex_input_dynamic_state_description">يتيح ميزة الحالة الديناميكية لإدخال الرأس لتحسين الجودة والأداء.</string>
|
||||
<string name="provoking_vertex">الرأس المثير</string>
|
||||
<string name="provoking_vertex_description">يحسن الإضاءة ومعالجة الرؤوس في بعض الألعاب. مدعوم فقط على وحدات معالجة الرسومات Vulkan 1.0+.</string>
|
||||
<string name="descriptor_indexing">فهرسة الوصف</string>
|
||||
|
||||
@@ -488,6 +488,8 @@
|
||||
<string name="dyna_state">Úroveň EDS</string>
|
||||
<string name="dyna_state_description">Určuje počet funkcí využívaných v rámci rozšířeného dynamického stavu API Vulkan (Extended Dynamic State). Vyšší hodnoty umožňují využít více funkcí a mohou zvýšit výkon, ale u některých ovladačů a výrobců grafických karet mohou způsobovat problémy s kompatibilitou.</string>
|
||||
<string name="disabled">Vypnuto</string>
|
||||
<string name="vertex_input_dynamic_state">Dynamický stav vstupu vrcholů (Vertex Input)</string>
|
||||
<string name="vertex_input_dynamic_state_description">Aktivuje funkci dynamického stavu vstupu vrcholů (Vertex Input Dynamic State) pro lepší kvalitu a výkon.</string>
|
||||
<string name="provoking_vertex">Určující vrchol</string>
|
||||
<string name="provoking_vertex_description">Zlepšuje osvětlení a zpracování vrcholů v některých hrách. Podporováno pouze na GPU s API Vulkan 1.0+.</string>
|
||||
<string name="descriptor_indexing">Indexování deskriptorů</string>
|
||||
|
||||
@@ -486,6 +486,8 @@ Wird der Handheld-Modus verwendet, verringert es die Auflösung und erhöht die
|
||||
<string name="dyna_state">Erweiterter dynamischer Status</string>
|
||||
<string name="dyna_state_description">Steuert die Anzahl der Funktionen, die im \"Vertex Input Dynamic State\" werden können. Höhere Werte ermöglichen mehr Funktionen und können die Leistung steigern, können aber bei einigen Treibern und Anbietern zu Problemen führen.</string>
|
||||
<string name="disabled">Deaktiviert</string>
|
||||
<string name="vertex_input_dynamic_state">Vertex Input Dynamic State</string>
|
||||
<string name="vertex_input_dynamic_state_description">Aktiviert die Funktion \"Vertex Input Dynamic State\" für bessere Qualität und Leistung.</string>
|
||||
<string name="provoking_vertex">Provokanter Vertex</string>
|
||||
<string name="provoking_vertex_description">Verbessert die Beleuchtung und die Vertex-Verarbeitung in einigen Spielen. Wird nur von GPUs mit Vulkan 1.0+ unterstützt.</string>
|
||||
<string name="descriptor_indexing">Deskriptor-Indizierung</string>
|
||||
|
||||
@@ -436,6 +436,8 @@
|
||||
<string name="renderer_asynchronous_shaders_description">Compile les shaders de manière asynchrone. Cela peut réduire les saccades mais peut aussi provoquer des problèmes graphiques.</string>
|
||||
<string name="dyna_state">État dynamique étendu</string>
|
||||
<string name="disabled">Désactivé</string>
|
||||
<string name="vertex_input_dynamic_state">État dynamique d\'entrée de sommet</string>
|
||||
<string name="vertex_input_dynamic_state_description">Active la fonctionnalité d\'état dynamique des entrées de sommets pour une meilleure qualité et de meilleures performances.</string>
|
||||
<string name="provoking_vertex">Provoque des Vertex</string>
|
||||
<string name="provoking_vertex_description">Améliore l`éclairage et la gestion des vertex dans certains jeux. Pris en charge uniquement par les GPU Vulkan 1.0+.</string>
|
||||
<string name="descriptor_indexing">Indexation des descripteurs</string>
|
||||
|
||||
@@ -488,6 +488,8 @@
|
||||
<string name="dyna_state">Rozszerzony stan dynamiczny</string>
|
||||
<string name="dyna_state_description">Kontroluje liczbę funkcji, które mogą być używane w Extended Dynamic State. Wyższe wartości pozwalają na użycie większej liczby funkcji i mogą zwiększyć wydajność, ale mogą powodować problemy z niektórymi sterownikami i u niektórych producentów.</string>
|
||||
<string name="disabled">Wyłączone</string>
|
||||
<string name="vertex_input_dynamic_state">Dynamiczny stan wejścia wierzchołków</string>
|
||||
<string name="vertex_input_dynamic_state_description">Włącza funkcję dynamicznego stanu wejścia wierzchołków, poprawiając jakość i wydajność.</string>
|
||||
<string name="provoking_vertex">Wierzchołek prowokujący</string>
|
||||
<string name="provoking_vertex_description">Poprawia oświetlenie i obsługę wierzchołków w niektórych grach. Obsługiwane tylko przez GPU Vulkan 1.0+.</string>
|
||||
<string name="descriptor_indexing">Indeksowanie deskryptorów</string>
|
||||
|
||||
@@ -471,6 +471,8 @@
|
||||
<string name="renderer_asynchronous_shaders_description">Compila shaders de forma assíncrona. Isso pode reduzir engasgos, mas também pode introduzir falhas gráficas.</string>
|
||||
<string name="dyna_state">Extended Dynamic State</string>
|
||||
<string name="disabled">Desativado</string>
|
||||
<string name="vertex_input_dynamic_state">Vertex Input Dynamic State</string>
|
||||
<string name="vertex_input_dynamic_state_description">Ativa o recurso de vertex input dynamic state para melhor qualidade e desempenho.</string>
|
||||
<string name="provoking_vertex">Provoking Vertex</string>
|
||||
<string name="provoking_vertex_description">Vértice Provocante: Melhora a iluminação e o processamento de vértices em certos jogos. Suportado apenas em GPUs com Vulkan 1.0 ou superior.</string>
|
||||
<string name="descriptor_indexing">Descriptor Indexing</string>
|
||||
|
||||
@@ -498,6 +498,8 @@
|
||||
<string name="dyna_state">Расширенное динамическое состояние</string>
|
||||
<string name="dyna_state_description">Управляет количеством функций, доступных в режиме «Расширенное динамическое состояние». Большее число позволяет задействовать больше функций и может повысить производительность, но способно вызывать проблемы с некоторыми драйверами и графикой.</string>
|
||||
<string name="disabled">Отключено</string>
|
||||
<string name="vertex_input_dynamic_state">Динамическое состояние ввода вершин</string>
|
||||
<string name="vertex_input_dynamic_state_description">Включает функцию динамического состояния ввода вершин для повышения качества и производительности</string>
|
||||
<string name="provoking_vertex">Определяющая вершина</string>
|
||||
<string name="provoking_vertex_description">Улучшает освещение и обработку вершин в некоторых играх. Поддерживается только ГПУ с Vulkan 1.0+.</string>
|
||||
<string name="descriptor_indexing">Индексирование дескрипторов</string>
|
||||
|
||||
@@ -502,6 +502,8 @@
|
||||
<string name="dyna_state">Розширений динамічний стан</string>
|
||||
<string name="dyna_state_description">Керує кількістю функцій, які можна використовувати в «Розширеному динамічному стані». Вище число дозволяє більше функцій і може покращити продуктивність, але може спричинити проблеми з деякими драйверами й виробниками.</string>
|
||||
<string name="disabled">Вимкнено</string>
|
||||
<string name="vertex_input_dynamic_state">Динамічний стан введення вершин</string>
|
||||
<string name="vertex_input_dynamic_state_description">Вмикає можливість динамічного стану введення вершин для кращих якості й продуктивності.</string>
|
||||
<string name="provoking_vertex">Провокативна вершина</string>
|
||||
<string name="provoking_vertex_description">Покращує освітлення та взаємодію з вершинами у деяких іграх. Лише для ГП з підтримкою Vulkan 1.0+.</string>
|
||||
<string name="descriptor_indexing">Індексація дескрипторів</string>
|
||||
|
||||
@@ -496,6 +496,8 @@
|
||||
<string name="dyna_state">扩展动态状态</string>
|
||||
<string name="dyna_state_description">控制在扩展动态状态中可使用的函数数量。更高的数值允许启用更多功能,并可能提升性能,但同时也可能导致额外的图形问题。</string>
|
||||
<string name="disabled">已禁用</string>
|
||||
<string name="vertex_input_dynamic_state">顶点输入动态状态</string>
|
||||
<string name="vertex_input_dynamic_state_description">开启顶点输入动态状态功能来获得更好的质量和性能。</string>
|
||||
<string name="provoking_vertex">引发顶点</string>
|
||||
<string name="provoking_vertex_description">改善某些游戏中的光照和顶点处理。仅支持Vulkan 1.0+ GPU。</string>
|
||||
<string name="descriptor_indexing">描述符索引</string>
|
||||
|
||||
@@ -467,6 +467,8 @@
|
||||
<string name="renderer_asynchronous_shaders_description">非同步編譯著色器。這可能會減少卡頓,但也可能導致圖形錯誤。</string>
|
||||
<string name="dyna_state">擴展動態狀態</string>
|
||||
<string name="disabled">已停用</string>
|
||||
<string name="vertex_input_dynamic_state">頂點輸入動態狀態</string>
|
||||
<string name="vertex_input_dynamic_state_description">啟用頂點輸入動態狀態以取得更佳的品質及性能</string>
|
||||
<string name="provoking_vertex">引發頂點</string>
|
||||
<string name="provoking_vertex_description">改善某些遊戲中的光照和頂點處理。僅支援Vulkan 1.0+ GPU。</string>
|
||||
<string name="descriptor_indexing">描述符索引</string>
|
||||
|
||||
@@ -632,12 +632,14 @@
|
||||
<item>@string/disabled</item>
|
||||
<item>ExtendedDynamicState 1</item>
|
||||
<item>ExtendedDynamicState 2</item>
|
||||
<item>ExtendedDynamicState 3</item>
|
||||
</string-array>
|
||||
|
||||
<integer-array name="dynaStateValues">
|
||||
<item>0</item>
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
<item>3</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="installKeysResults">
|
||||
|
||||
@@ -531,6 +531,8 @@
|
||||
<string name="dyna_state">Extended Dynamic State</string>
|
||||
<string name="dyna_state_description">Controls the number of features that can be used in Extended Dynamic State. Higher numbers allow for more features and can increase performance, but may cause issues with some drivers and vendors.</string>
|
||||
<string name="disabled">Disabled</string>
|
||||
<string name="vertex_input_dynamic_state">Vertex Input Dynamic State</string>
|
||||
<string name="vertex_input_dynamic_state_description">Enables vertex input dynamic state feature for better quality and performance.</string>
|
||||
<string name="provoking_vertex">Provoking Vertex</string>
|
||||
<string name="provoking_vertex_description">Improves lighting and vertex handling in certain games. Only supported on Vulkan 1.0+ GPUs.</string>
|
||||
<string name="descriptor_indexing">Descriptor Indexing</string>
|
||||
|
||||
+10
-7
@@ -453,7 +453,7 @@ struct Values {
|
||||
Category::RendererAdvanced};
|
||||
SwitchableSetting<AstcDecodeMode, true> accelerate_astc{linkage,
|
||||
#ifdef ANDROID
|
||||
AstcDecodeMode::Gpu,
|
||||
AstcDecodeMode::Cpu,
|
||||
#else
|
||||
AstcDecodeMode::Gpu,
|
||||
#endif
|
||||
@@ -557,9 +557,6 @@ struct Values {
|
||||
SwitchableSetting<bool> fix_bloom_effects{linkage, false, "fix_bloom_effects",
|
||||
Category::RendererHacks};
|
||||
|
||||
SwitchableSetting<bool> rescale_hack{linkage, false, "rescale_hack",
|
||||
Category::RendererHacks};
|
||||
|
||||
SwitchableSetting<bool> use_asynchronous_shaders{linkage, false, "use_asynchronous_shaders",
|
||||
Category::RendererHacks};
|
||||
|
||||
@@ -586,7 +583,7 @@ struct Values {
|
||||
|
||||
SwitchableSetting<ExtendedDynamicState> dyna_state{linkage,
|
||||
#if defined (ANDROID) || defined (__APPLE__)
|
||||
ExtendedDynamicState::EDS1,
|
||||
ExtendedDynamicState::Disabled,
|
||||
#else
|
||||
ExtendedDynamicState::EDS2,
|
||||
#endif
|
||||
@@ -601,6 +598,14 @@ struct Values {
|
||||
Category::RendererExtensions,
|
||||
Specialization::Scalar};
|
||||
|
||||
SwitchableSetting<bool> vertex_input_dynamic_state{linkage,
|
||||
#if defined (ANDROID)
|
||||
false,
|
||||
#else
|
||||
true,
|
||||
#endif
|
||||
"vertex_input_dynamic_state", Category::RendererExtensions};
|
||||
SwitchableSetting<bool> provoking_vertex{linkage, false, "provoking_vertex", Category::RendererExtensions};
|
||||
SwitchableSetting<bool> descriptor_indexing{linkage, false, "descriptor_indexing", Category::RendererExtensions};
|
||||
|
||||
Setting<bool> renderer_debug{linkage, false, "debug", Category::RendererDebug};
|
||||
@@ -748,8 +753,6 @@ struct Values {
|
||||
Category::DataStorage};
|
||||
Setting<std::string> gamecard_path{linkage, std::string(), "gamecard_path",
|
||||
Category::DataStorage};
|
||||
Setting<bool> ext_content_from_game_dirs{linkage, true, "ext_content_from_game_dirs",
|
||||
Category::DataStorage};
|
||||
std::vector<std::string> external_content_dirs;
|
||||
|
||||
// Debugging
|
||||
|
||||
@@ -154,7 +154,7 @@ ENUM(GpuUnswizzleSize, VerySmall, Small, Normal, Large, VeryLarge)
|
||||
ENUM(GpuUnswizzle, VeryLow, Low, Normal, Medium, High)
|
||||
ENUM(GpuUnswizzleChunk, VeryLow, Low, Normal, Medium, High)
|
||||
ENUM(TemperatureUnits, Celsius, Fahrenheit)
|
||||
ENUM(ExtendedDynamicState, Disabled, EDS1, EDS2);
|
||||
ENUM(ExtendedDynamicState, Disabled, EDS1, EDS2, EDS3);
|
||||
ENUM(GpuLogLevel, Off, Errors, Standard, Verbose, All)
|
||||
ENUM(GameListMode, TreeView, GridView);
|
||||
ENUM(SpeedMode, Standard, Turbo, Slow);
|
||||
|
||||
@@ -117,12 +117,6 @@ void AppendCommaIfNotEmpty(std::string& to, std::string_view with) {
|
||||
bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
|
||||
return dir != nullptr && (!dir->GetFiles().empty() || !dir->GetSubdirectories().empty());
|
||||
}
|
||||
|
||||
bool IsVersionedExternalUpdateDisabled(const std::vector<std::string>& disabled, u32 version) {
|
||||
const std::string disabled_key = fmt::format("Update@{}", version);
|
||||
return std::find(disabled.cbegin(), disabled.cend(), disabled_key) != disabled.cend() ||
|
||||
std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend();
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
PatchManager::PatchManager(u64 title_id_,
|
||||
@@ -161,7 +155,8 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
|
||||
if (!update_versions.empty()) {
|
||||
checked_external = true;
|
||||
for (const auto& update_entry : update_versions) {
|
||||
if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) {
|
||||
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
|
||||
if (std::find(disabled.cbegin(), disabled.cend(), disabled_key) == disabled.cend()) {
|
||||
update_disabled = false;
|
||||
enabled_version = update_entry.version;
|
||||
break;
|
||||
@@ -180,7 +175,8 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
|
||||
if (!manual_update_versions.empty()) {
|
||||
checked_manual = true;
|
||||
for (const auto& update_entry : manual_update_versions) {
|
||||
if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) {
|
||||
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
|
||||
if (std::find(disabled.cbegin(), disabled.cend(), disabled_key) == disabled.cend()) {
|
||||
update_disabled = false;
|
||||
enabled_version = update_entry.version;
|
||||
break;
|
||||
@@ -584,7 +580,8 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs
|
||||
if (!update_versions.empty()) {
|
||||
checked_external = true;
|
||||
for (const auto& update_entry : update_versions) {
|
||||
if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) {
|
||||
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
|
||||
if (std::find(disabled.cbegin(), disabled.cend(), disabled_key) == disabled.cend()) {
|
||||
update_disabled = false;
|
||||
enabled_version = update_entry.version;
|
||||
update_raw = external_provider->GetEntryForVersion(update_tid, type, update_entry.version);
|
||||
@@ -603,7 +600,8 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs
|
||||
if (!manual_update_versions.empty()) {
|
||||
checked_manual = true;
|
||||
for (const auto& update_entry : manual_update_versions) {
|
||||
if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) {
|
||||
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
|
||||
if (std::find(disabled.cbegin(), disabled.cend(), disabled_key) == disabled.cend()) {
|
||||
update_disabled = false;
|
||||
enabled_version = update_entry.version;
|
||||
update_raw = manual_provider->GetEntryForVersion(update_tid, type, update_entry.version);
|
||||
@@ -706,8 +704,9 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
|
||||
version_str = FormatTitleVersion(update_entry.version);
|
||||
}
|
||||
|
||||
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
|
||||
const auto update_disabled =
|
||||
IsVersionedExternalUpdateDisabled(disabled, update_entry.version);
|
||||
std::find(disabled.cbegin(), disabled.cend(), disabled_key) != disabled.cend();
|
||||
|
||||
Patch update_patch = {.enabled = !update_disabled,
|
||||
.name = "Update",
|
||||
@@ -733,8 +732,9 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
|
||||
version_str = FormatTitleVersion(update_entry.version);
|
||||
}
|
||||
|
||||
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
|
||||
const auto update_disabled =
|
||||
IsVersionedExternalUpdateDisabled(disabled, update_entry.version);
|
||||
std::find(disabled.cbegin(), disabled.cend(), disabled_key) != disabled.cend();
|
||||
|
||||
|
||||
Patch update_patch = {.enabled = !update_disabled,
|
||||
@@ -771,8 +771,7 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
|
||||
std::nullopt, std::nullopt, ContentRecordType::Program, update_tid);
|
||||
|
||||
for (const auto& [slot, entry] : all_updates) {
|
||||
if (slot == ContentProviderUnionSlot::External ||
|
||||
slot == ContentProviderUnionSlot::FrontendManual) {
|
||||
if (slot == ContentProviderUnionSlot::External) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -104,206 +104,6 @@ static std::string GetCNMTName(TitleType type, u64 title_id) {
|
||||
return fmt::format("{}_{:016x}.cnmt", TITLE_TYPE_NAMES[index], title_id);
|
||||
}
|
||||
|
||||
static std::shared_ptr<NSP> OpenContainerAsNsp(const VirtualFile& file, Loader::FileType type) {
|
||||
if (!file) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (type == Loader::FileType::Unknown || type == Loader::FileType::Error) {
|
||||
type = Loader::IdentifyFile(file);
|
||||
if (type == Loader::FileType::Unknown) {
|
||||
type = Loader::GuessFromFilename(file->GetName());
|
||||
}
|
||||
}
|
||||
|
||||
if (type == Loader::FileType::NSP) {
|
||||
auto nsp = std::make_shared<NSP>(file);
|
||||
return nsp->GetStatus() == Loader::ResultStatus::Success ? nsp : nullptr;
|
||||
}
|
||||
|
||||
if (type == Loader::FileType::XCI) {
|
||||
XCI xci(file);
|
||||
if (xci.GetStatus() != Loader::ResultStatus::Success) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto secure_partition = xci.GetSecurePartitionNSP();
|
||||
if (secure_partition == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return secure_partition;
|
||||
}
|
||||
|
||||
// SAF-backed files can occasionally fail type-guessing despite being valid NSP/XCI.
|
||||
// As a last resort, probe both container parsers directly.
|
||||
{
|
||||
auto nsp = std::make_shared<NSP>(file);
|
||||
if (nsp->GetStatus() == Loader::ResultStatus::Success) {
|
||||
return nsp;
|
||||
}
|
||||
}
|
||||
{
|
||||
XCI xci(file);
|
||||
if (xci.GetStatus() == Loader::ResultStatus::Success) {
|
||||
auto secure_partition = xci.GetSecurePartitionNSP();
|
||||
if (secure_partition != nullptr) {
|
||||
return secure_partition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename Callback>
|
||||
bool ForEachContainerEntry(const std::shared_ptr<NSP>& nsp, bool only_content,
|
||||
std::optional<u64> base_program_id, Callback&& on_entry) {
|
||||
if (!nsp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& ncas = nsp->GetNCAs();
|
||||
if (ncas.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::map<u64, u32> versions;
|
||||
std::map<u64, std::string> version_strings;
|
||||
|
||||
for (const auto& [title_id, nca_map] : ncas) {
|
||||
for (const auto& [type_pair, nca] : nca_map) {
|
||||
if (!nca) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& [title_type, content_type] = type_pair;
|
||||
|
||||
if (content_type == ContentRecordType::Meta) {
|
||||
const auto subdirs = nca->GetSubdirectories();
|
||||
if (!subdirs.empty()) {
|
||||
for (const auto& inner_file : subdirs[0]->GetFiles()) {
|
||||
if (inner_file->GetExtension() == "cnmt") {
|
||||
const CNMT cnmt(inner_file);
|
||||
versions[cnmt.GetTitleID()] = cnmt.GetTitleVersion();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (title_type == TitleType::Update && content_type == ContentRecordType::Control) {
|
||||
const auto romfs = nca->GetRomFS();
|
||||
if (!romfs) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto extracted = ExtractRomFS(romfs);
|
||||
if (!extracted) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto nacp_file = extracted->GetFile("control.nacp");
|
||||
if (!nacp_file) {
|
||||
nacp_file = extracted->GetFile("Control.nacp");
|
||||
}
|
||||
if (!nacp_file) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const NACP nacp(nacp_file);
|
||||
auto version_string = nacp.GetVersionString();
|
||||
if (!version_string.empty()) {
|
||||
version_strings[title_id] = std::move(version_string);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool added_entries = false;
|
||||
for (const auto& [title_id, nca_map] : ncas) {
|
||||
if (base_program_id.has_value() && GetBaseTitleID(title_id) != *base_program_id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const auto& [type_pair, nca] : nca_map) {
|
||||
const auto& [title_type, content_type] = type_pair;
|
||||
if (only_content && title_type != TitleType::Update && title_type != TitleType::AOC) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto entry_file = nca ? nca->GetBaseFile() : nullptr;
|
||||
if (!entry_file) {
|
||||
continue;
|
||||
}
|
||||
|
||||
u32 version = 0;
|
||||
std::string version_string;
|
||||
|
||||
if (title_type == TitleType::Update) {
|
||||
if (const auto version_it = versions.find(title_id); version_it != versions.end()) {
|
||||
version = version_it->second;
|
||||
}
|
||||
|
||||
if (const auto version_str_it = version_strings.find(title_id);
|
||||
version_str_it != version_strings.end()) {
|
||||
version_string = version_str_it->second;
|
||||
}
|
||||
}
|
||||
|
||||
on_entry(title_type, content_type, title_id, entry_file, version, version_string);
|
||||
added_entries = true;
|
||||
}
|
||||
}
|
||||
|
||||
return added_entries;
|
||||
}
|
||||
|
||||
static void UpsertExternalVersionEntry(std::vector<ExternalUpdateEntry>& multi_version_entries,
|
||||
u64 title_id, u32 version,
|
||||
const std::string& version_string,
|
||||
ContentRecordType content_type, const VirtualFile& file) {
|
||||
auto it = std::find_if(multi_version_entries.begin(), multi_version_entries.end(),
|
||||
[title_id, version](const ExternalUpdateEntry& entry) {
|
||||
return entry.title_id == title_id && entry.version == version;
|
||||
});
|
||||
|
||||
if (it == multi_version_entries.end()) {
|
||||
ExternalUpdateEntry update_entry;
|
||||
update_entry.title_id = title_id;
|
||||
update_entry.version = version;
|
||||
update_entry.version_string = version_string;
|
||||
update_entry.files[static_cast<std::size_t>(content_type)] = file;
|
||||
multi_version_entries.push_back(std::move(update_entry));
|
||||
return;
|
||||
}
|
||||
|
||||
it->files[static_cast<std::size_t>(content_type)] = file;
|
||||
if (it->version_string.empty() && !version_string.empty()) {
|
||||
it->version_string = version_string;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename EntryMap, typename VersionMap>
|
||||
static bool AddExternalEntriesFromContainer(const std::shared_ptr<NSP>& nsp, EntryMap& entries,
|
||||
VersionMap& versions,
|
||||
std::vector<ExternalUpdateEntry>& multi_version_entries) {
|
||||
return ForEachContainerEntry(
|
||||
nsp, true, std::nullopt,
|
||||
[&entries, &versions,
|
||||
&multi_version_entries](TitleType title_type, ContentRecordType content_type, u64 title_id,
|
||||
const VirtualFile& file, u32 version,
|
||||
const std::string& version_string) {
|
||||
entries[{title_id, content_type, title_type}] = file;
|
||||
|
||||
if (title_type == TitleType::Update) {
|
||||
versions[title_id] = version;
|
||||
UpsertExternalVersionEntry(multi_version_entries, title_id, version, version_string,
|
||||
content_type, file);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ContentRecordType GetCRTypeFromNCAType(NCAContentType type) {
|
||||
switch (type) {
|
||||
case NCAContentType::Program:
|
||||
@@ -1208,26 +1008,6 @@ void ManualContentProvider::AddEntryWithVersion(TitleType title_type, ContentRec
|
||||
}
|
||||
}
|
||||
|
||||
bool ManualContentProvider::AddEntriesFromContainer(VirtualFile file, bool only_content,
|
||||
std::optional<u64> base_program_id) {
|
||||
const auto nsp = OpenContainerAsNsp(file, Loader::FileType::Unknown);
|
||||
if (!nsp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ForEachContainerEntry(
|
||||
nsp, only_content, base_program_id,
|
||||
[this](TitleType title_type, ContentRecordType content_type, u64 title_id,
|
||||
const VirtualFile& entry_file, u32 version, const std::string& version_string) {
|
||||
if (title_type == TitleType::Update) {
|
||||
AddEntryWithVersion(title_type, content_type, title_id, version, version_string,
|
||||
entry_file);
|
||||
} else {
|
||||
AddEntry(title_type, content_type, title_id, entry_file);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void ManualContentProvider::ClearAllEntries() {
|
||||
entries.clear();
|
||||
multi_version_entries.clear();
|
||||
@@ -1311,6 +1091,14 @@ VirtualFile ManualContentProvider::GetEntryForVersion(u64 title_id, ContentRecor
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool ManualContentProvider::HasMultipleVersions(u64 title_id, ContentRecordType type) const {
|
||||
size_t count = 0;
|
||||
for (const auto& entry : multi_version_entries)
|
||||
if (entry.title_id == title_id && entry.files[size_t(type)])
|
||||
++count;
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
ExternalContentProvider::ExternalContentProvider(std::vector<VirtualDir> load_directories)
|
||||
: load_dirs(std::move(load_directories)) {
|
||||
ExternalContentProvider::Refresh();
|
||||
@@ -1371,22 +1159,247 @@ void ExternalContentProvider::ScanDirectory(const VirtualDir& dir) {
|
||||
}
|
||||
|
||||
void ExternalContentProvider::ProcessNSP(const VirtualFile& file) {
|
||||
const auto nsp = OpenContainerAsNsp(file, Loader::FileType::NSP);
|
||||
if (!nsp) {
|
||||
auto nsp = NSP(file);
|
||||
if (nsp.GetStatus() != Loader::ResultStatus::Success) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Service_FS, "Processing NSP file: {}", file->GetName());
|
||||
AddExternalEntriesFromContainer(nsp, entries, versions, multi_version_entries);
|
||||
|
||||
const auto ncas = nsp.GetNCAs();
|
||||
|
||||
std::map<u64, u32> nsp_versions;
|
||||
std::map<u64, std::string> nsp_version_strings; // title_id -> NACP version string
|
||||
|
||||
for (const auto& [title_id, nca_map] : ncas) {
|
||||
for (const auto& [type_pair, nca] : nca_map) {
|
||||
const auto& [title_type, content_type] = type_pair;
|
||||
|
||||
if (content_type == ContentRecordType::Meta) {
|
||||
const auto subdirs = nca->GetSubdirectories();
|
||||
if (!subdirs.empty()) {
|
||||
const auto section0 = subdirs[0];
|
||||
const auto files = section0->GetFiles();
|
||||
for (const auto& inner_file : files) {
|
||||
if (inner_file->GetExtension() == "cnmt") {
|
||||
const CNMT cnmt(inner_file);
|
||||
const auto cnmt_title_id = cnmt.GetTitleID();
|
||||
const auto version = cnmt.GetTitleVersion();
|
||||
nsp_versions[cnmt_title_id] = version;
|
||||
versions[cnmt_title_id] = version;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (content_type == ContentRecordType::Control && title_type == TitleType::Update) {
|
||||
auto romfs = nca->GetRomFS();
|
||||
if (romfs) {
|
||||
auto extracted = ExtractRomFS(romfs);
|
||||
if (extracted) {
|
||||
auto nacp_file = extracted->GetFile("control.nacp");
|
||||
if (!nacp_file) {
|
||||
nacp_file = extracted->GetFile("Control.nacp");
|
||||
}
|
||||
if (nacp_file) {
|
||||
NACP nacp(nacp_file);
|
||||
auto ver_str = nacp.GetVersionString();
|
||||
if (!ver_str.empty()) {
|
||||
nsp_version_strings[title_id] = ver_str;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::map<std::pair<u64, u32>, std::array<VirtualFile, size_t(ContentRecordType::Count)>> version_files;
|
||||
|
||||
for (const auto& [title_id, nca_map] : ncas) {
|
||||
for (const auto& [type_pair, nca] : nca_map) {
|
||||
const auto& [title_type, content_type] = type_pair;
|
||||
|
||||
if (title_type != TitleType::AOC && title_type != TitleType::Update) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto nca_file = nsp.GetNCAFile(title_id, content_type, title_type);
|
||||
if (nca_file != nullptr) {
|
||||
entries[{title_id, content_type, title_type}] = nca_file;
|
||||
|
||||
if (title_type == TitleType::Update) {
|
||||
u32 version = 0;
|
||||
auto ver_it = nsp_versions.find(title_id);
|
||||
if (ver_it != nsp_versions.end()) {
|
||||
version = ver_it->second;
|
||||
}
|
||||
|
||||
version_files[{title_id, version}][size_t(content_type)] = nca_file;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Service_FS, "Added entry - Title ID: {:016X}, Type: {}, Content: {}",
|
||||
title_id, static_cast<int>(title_type), static_cast<int>(content_type));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [key, files_map] : version_files) {
|
||||
const auto& [title_id, version] = key;
|
||||
|
||||
std::string ver_str;
|
||||
auto str_it = nsp_version_strings.find(title_id);
|
||||
if (str_it != nsp_version_strings.end()) {
|
||||
ver_str = str_it->second;
|
||||
}
|
||||
|
||||
bool version_exists = false;
|
||||
for (auto& existing : multi_version_entries) {
|
||||
if (existing.title_id == title_id && existing.version == version) {
|
||||
existing.files = files_map;
|
||||
if (existing.version_string.empty() && !ver_str.empty()) {
|
||||
existing.version_string = ver_str;
|
||||
}
|
||||
version_exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!version_exists && !files_map.empty()) {
|
||||
ExternalUpdateEntry update_entry{
|
||||
.title_id = title_id,
|
||||
.version = version,
|
||||
.version_string = ver_str,
|
||||
.files = files_map
|
||||
};
|
||||
multi_version_entries.push_back(update_entry);
|
||||
LOG_DEBUG(Service_FS, "Added multi-version update - Title ID: {:016X}, Version: {}, VersionStr: {}, Content types: {}",
|
||||
title_id, version, ver_str, files_map.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ExternalContentProvider::ProcessXCI(const VirtualFile& file) {
|
||||
const auto nsp = OpenContainerAsNsp(file, Loader::FileType::XCI);
|
||||
if (!nsp) {
|
||||
auto xci = XCI(file);
|
||||
if (xci.GetStatus() != Loader::ResultStatus::Success) {
|
||||
return;
|
||||
}
|
||||
|
||||
AddExternalEntriesFromContainer(nsp, entries, versions, multi_version_entries);
|
||||
auto nsp = xci.GetSecurePartitionNSP();
|
||||
if (nsp == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto ncas = nsp->GetNCAs();
|
||||
|
||||
std::map<u64, u32> xci_versions;
|
||||
std::map<u64, std::string> xci_version_strings;
|
||||
|
||||
for (const auto& [title_id, nca_map] : ncas) {
|
||||
for (const auto& [type_pair, nca] : nca_map) {
|
||||
const auto& [title_type, content_type] = type_pair;
|
||||
|
||||
if (content_type == ContentRecordType::Meta) {
|
||||
const auto subdirs = nca->GetSubdirectories();
|
||||
if (!subdirs.empty()) {
|
||||
const auto section0 = subdirs[0];
|
||||
const auto files = section0->GetFiles();
|
||||
for (const auto& inner_file : files) {
|
||||
if (inner_file->GetExtension() == "cnmt") {
|
||||
const CNMT cnmt(inner_file);
|
||||
const auto cnmt_title_id = cnmt.GetTitleID();
|
||||
const auto version = cnmt.GetTitleVersion();
|
||||
xci_versions[cnmt_title_id] = version;
|
||||
versions[cnmt_title_id] = version;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (content_type == ContentRecordType::Control && title_type == TitleType::Update) {
|
||||
auto romfs = nca->GetRomFS();
|
||||
if (romfs) {
|
||||
auto extracted = ExtractRomFS(romfs);
|
||||
if (extracted) {
|
||||
auto nacp_file = extracted->GetFile("control.nacp");
|
||||
if (!nacp_file) {
|
||||
nacp_file = extracted->GetFile("Control.nacp");
|
||||
}
|
||||
if (nacp_file) {
|
||||
NACP nacp(nacp_file);
|
||||
auto ver_str = nacp.GetVersionString();
|
||||
if (!ver_str.empty()) {
|
||||
xci_version_strings[title_id] = ver_str;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::map<std::pair<u64, u32>, std::array<VirtualFile, size_t(ContentRecordType::Count)>> version_files;
|
||||
|
||||
for (const auto& [title_id, nca_map] : ncas) {
|
||||
for (const auto& [type_pair, nca] : nca_map) {
|
||||
const auto& [title_type, content_type] = type_pair;
|
||||
|
||||
if (title_type != TitleType::AOC && title_type != TitleType::Update) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto nca_file = nsp->GetNCAFile(title_id, content_type, title_type);
|
||||
if (nca_file != nullptr) {
|
||||
entries[{title_id, content_type, title_type}] = nca_file;
|
||||
|
||||
if (title_type == TitleType::Update) {
|
||||
u32 version = 0;
|
||||
auto ver_it = xci_versions.find(title_id);
|
||||
if (ver_it != xci_versions.end()) {
|
||||
version = ver_it->second;
|
||||
}
|
||||
|
||||
version_files[{title_id, version}][size_t(content_type)] = nca_file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [key, files_map] : version_files) {
|
||||
const auto& [title_id, version] = key;
|
||||
|
||||
std::string ver_str;
|
||||
auto str_it = xci_version_strings.find(title_id);
|
||||
if (str_it != xci_version_strings.end()) {
|
||||
ver_str = str_it->second;
|
||||
}
|
||||
|
||||
bool version_exists = false;
|
||||
for (auto& existing : multi_version_entries) {
|
||||
if (existing.title_id == title_id && existing.version == version) {
|
||||
existing.files = files_map;
|
||||
if (existing.version_string.empty() && !ver_str.empty()) {
|
||||
existing.version_string = ver_str;
|
||||
}
|
||||
version_exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!version_exists && !files_map.empty()) {
|
||||
ExternalUpdateEntry update_entry{
|
||||
.title_id = title_id,
|
||||
.version = version,
|
||||
.version_string = ver_str,
|
||||
.files = files_map
|
||||
};
|
||||
multi_version_entries.push_back(update_entry);
|
||||
LOG_DEBUG(Service_FS, "Added multi-version update from XCI - Title ID: {:016X}, Version: {}, VersionStr: {}, Content types: {}",
|
||||
title_id, version, ver_str, files_map.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ExternalContentProvider::HasEntry(u64 title_id, ContentRecordType type) const {
|
||||
@@ -1478,4 +1491,12 @@ VirtualFile ExternalContentProvider::GetEntryForVersion(u64 title_id, ContentRec
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool ExternalContentProvider::HasMultipleVersions(u64 title_id, ContentRecordType type) const {
|
||||
size_t count = 0;
|
||||
for (const auto& entry : multi_version_entries)
|
||||
if (entry.title_id == title_id && entry.files[size_t(type)])
|
||||
++count;
|
||||
return count > 1;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <ankerl/unordered_dense.h>
|
||||
@@ -263,8 +262,6 @@ public:
|
||||
VirtualFile file);
|
||||
void AddEntryWithVersion(TitleType title_type, ContentRecordType content_type, u64 title_id,
|
||||
u32 version, const std::string& version_string, VirtualFile file);
|
||||
bool AddEntriesFromContainer(VirtualFile file, bool only_content = false,
|
||||
std::optional<u64> base_program_id = std::nullopt);
|
||||
void ClearAllEntries();
|
||||
|
||||
void Refresh() override;
|
||||
@@ -279,6 +276,7 @@ public:
|
||||
|
||||
std::vector<ExternalUpdateEntry> ListUpdateVersions(u64 title_id) const;
|
||||
VirtualFile GetEntryForVersion(u64 title_id, ContentRecordType type, u32 version) const;
|
||||
bool HasMultipleVersions(u64 title_id, ContentRecordType type) const;
|
||||
|
||||
private:
|
||||
std::map<std::tuple<TitleType, ContentRecordType, u64>, VirtualFile> entries;
|
||||
@@ -305,6 +303,7 @@ public:
|
||||
|
||||
std::vector<ExternalUpdateEntry> ListUpdateVersions(u64 title_id) const;
|
||||
VirtualFile GetEntryForVersion(u64 title_id, ContentRecordType type, u32 version) const;
|
||||
bool HasMultipleVersions(u64 title_id, ContentRecordType type) const;
|
||||
|
||||
private:
|
||||
void ScanDirectory(const VirtualDir& dir);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
@@ -28,10 +28,8 @@ public:
|
||||
{10101, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::Old>, "SaveReportWithUserOld"},
|
||||
{10102, &PlayReport::SaveReport<Core::Reporter::PlayReportType::Old2>, "SaveReportOld2"},
|
||||
{10103, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::Old2>, "SaveReportWithUserOld2"},
|
||||
{10104, &PlayReport::SaveReport<Core::Reporter::PlayReportType::Old3>, "SaveReportOld3"},
|
||||
{10105, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::Old3>, "SaveReportWithUserOld3"},
|
||||
{10106, &PlayReport::SaveReport<Core::Reporter::PlayReportType::New>, "SaveReport"},
|
||||
{10107, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::New>, "SaveReportWithUser"},
|
||||
{10104, &PlayReport::SaveReport<Core::Reporter::PlayReportType::New>, "SaveReport"},
|
||||
{10105, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::New>, "SaveReportWithUser"},
|
||||
{10200, &PlayReport::RequestImmediateTransmission, "RequestImmediateTransmission"},
|
||||
{10300, &PlayReport::GetTransmissionStatus, "GetTransmissionStatus"},
|
||||
{10400, &PlayReport::GetSystemSessionId, "GetSystemSessionId"},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
@@ -9,15 +9,11 @@
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <concepts>
|
||||
#include <algorithm>
|
||||
#include "common/concepts.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/card_image.h"
|
||||
#include "core/file_sys/common_funcs.h"
|
||||
#include "core/file_sys/submission_package.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/loader/deconstructed_rom_directory.h"
|
||||
#include "core/loader/kip.h"
|
||||
@@ -41,49 +37,6 @@ std::optional<FileType> IdentifyFileLoader(FileSys::VirtualFile file) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::shared_ptr<FileSys::NSP> OpenContainerAsNsp(FileSys::VirtualFile file, FileType type,
|
||||
u64 program_id = 0,
|
||||
std::size_t program_index = 0) {
|
||||
if (!file) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (type == FileType::NSP) {
|
||||
auto nsp = std::make_shared<FileSys::NSP>(file, program_id, program_index);
|
||||
return nsp->GetStatus() == ResultStatus::Success ? nsp : nullptr;
|
||||
}
|
||||
|
||||
if (type == FileType::XCI) {
|
||||
FileSys::XCI xci{file, program_id, program_index};
|
||||
if (xci.GetStatus() != ResultStatus::Success) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto secure_nsp = xci.GetSecurePartitionNSP();
|
||||
if (secure_nsp == nullptr || secure_nsp->GetStatus() != ResultStatus::Success) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return secure_nsp;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool HasApplicationProgramContent(const std::shared_ptr<FileSys::NSP>& nsp) {
|
||||
if (!nsp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& ncas = nsp->GetNCAs();
|
||||
return std::any_of(ncas.cbegin(), ncas.cend(), [](const auto& title_entry) {
|
||||
const auto& nca_map = title_entry.second;
|
||||
return nca_map.find(
|
||||
{FileSys::TitleType::Application, FileSys::ContentRecordType::Program}) !=
|
||||
nca_map.end();
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
FileType IdentifyFile(FileSys::VirtualFile file) {
|
||||
@@ -109,27 +62,6 @@ FileType IdentifyFile(FileSys::VirtualFile file) {
|
||||
}
|
||||
}
|
||||
|
||||
bool IsContainerType(FileType type) {
|
||||
return type == FileType::NSP || type == FileType::XCI;
|
||||
}
|
||||
|
||||
bool IsBootableGameContainer(FileSys::VirtualFile file, FileType type, u64 program_id,
|
||||
std::size_t program_index) {
|
||||
if (!file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type == FileType::Unknown) {
|
||||
type = IdentifyFile(file);
|
||||
}
|
||||
|
||||
if (!IsContainerType(type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return HasApplicationProgramContent(OpenContainerAsNsp(file, type, program_id, program_index));
|
||||
}
|
||||
|
||||
FileType GuessFromFilename(const std::string& name) {
|
||||
if (name == "main")
|
||||
return FileType::DeconstructedRomDirectory;
|
||||
|
||||
@@ -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-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -49,29 +46,12 @@ enum class FileType {
|
||||
};
|
||||
|
||||
/**
|
||||
* Identifies the type of a supported file/container based on its structure.
|
||||
* Identifies the type of a bootable file based on the magic value in its header.
|
||||
* @param file open file
|
||||
* @return FileType of file
|
||||
*/
|
||||
FileType IdentifyFile(FileSys::VirtualFile file);
|
||||
|
||||
/**
|
||||
* Returns whether the file type represents a container format that can bundle multiple titles
|
||||
* (currently NSP/XCI).
|
||||
*/
|
||||
bool IsContainerType(FileType type);
|
||||
|
||||
/**
|
||||
* Returns whether a container file is bootable as a game (has Application/Program content).
|
||||
*
|
||||
* @param file open file
|
||||
* @param type optional file type; if Unknown it is auto-detected.
|
||||
* @param program_id optional program id hint for multi-program containers.
|
||||
* @param program_index optional program index hint for multi-program containers.
|
||||
*/
|
||||
bool IsBootableGameContainer(FileSys::VirtualFile file, FileType type = FileType::Unknown,
|
||||
u64 program_id = 0, std::size_t program_index = 0);
|
||||
|
||||
/**
|
||||
* Guess the type of a bootable file from its name
|
||||
* @param name String name of bootable file
|
||||
|
||||
+11
-25
@@ -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-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -58,30 +55,19 @@ AppLoader_NSP::~AppLoader_NSP() = default;
|
||||
FileType AppLoader_NSP::IdentifyType(const FileSys::VirtualFile& nsp_file) {
|
||||
const FileSys::NSP nsp(nsp_file);
|
||||
|
||||
if (nsp.GetStatus() != ResultStatus::Success) {
|
||||
return FileType::Error;
|
||||
}
|
||||
|
||||
// Extracted Type case
|
||||
if (nsp.IsExtractedType() && nsp.GetExeFS() != nullptr &&
|
||||
FileSys::IsDirectoryExeFS(nsp.GetExeFS())) {
|
||||
return FileType::NSP;
|
||||
}
|
||||
|
||||
// Non-extracted NSPs can legitimately contain only update/DLC content.
|
||||
// Identify the container format itself; bootability is validated by Load().
|
||||
if (!nsp.GetNCAs().empty()) {
|
||||
return FileType::NSP;
|
||||
}
|
||||
|
||||
// Fallback when NCAs couldn't be parsed (e.g. missing keys) but the PFS still contains NCAs.
|
||||
for (const auto& entry : nsp.GetFiles()) {
|
||||
if (entry == nullptr) {
|
||||
continue;
|
||||
if (nsp.GetStatus() == ResultStatus::Success) {
|
||||
// Extracted Type case
|
||||
if (nsp.IsExtractedType() && nsp.GetExeFS() != nullptr &&
|
||||
FileSys::IsDirectoryExeFS(nsp.GetExeFS())) {
|
||||
return FileType::NSP;
|
||||
}
|
||||
|
||||
const auto& name = entry->GetName();
|
||||
if (name.size() >= 4 && name.substr(name.size() - 4) == ".nca") {
|
||||
// Non-Extracted Type case
|
||||
const auto program_id = nsp.GetProgramTitleID();
|
||||
if (!nsp.IsExtractedType() &&
|
||||
nsp.GetNCA(program_id, FileSys::ContentRecordType::Program) != nullptr &&
|
||||
AppLoader_NCA::IdentifyType(
|
||||
nsp.GetNCAFile(program_id, FileSys::ContentRecordType::Program)) == FileType::NCA) {
|
||||
return FileType::NSP;
|
||||
}
|
||||
}
|
||||
|
||||
+4
-10
@@ -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-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -47,13 +44,10 @@ AppLoader_XCI::~AppLoader_XCI() = default;
|
||||
FileType AppLoader_XCI::IdentifyType(const FileSys::VirtualFile& xci_file) {
|
||||
const FileSys::XCI xci(xci_file);
|
||||
|
||||
if (xci.GetStatus() != ResultStatus::Success) {
|
||||
return FileType::Error;
|
||||
}
|
||||
|
||||
// Identify XCI as a valid container even when it does not include a bootable Program NCA.
|
||||
// Bootability is handled by AppLoader_XCI::Load().
|
||||
if (xci.GetSecurePartitionNSP() != nullptr) {
|
||||
if (xci.GetStatus() == ResultStatus::Success &&
|
||||
xci.GetNCAByType(FileSys::NCAContentType::Program) != nullptr &&
|
||||
AppLoader_NCA::IdentifyType(xci.GetNCAFileByType(FileSys::NCAContentType::Program)) ==
|
||||
FileType::NCA) {
|
||||
return FileType::XCI;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -56,7 +53,6 @@ public:
|
||||
enum class PlayReportType {
|
||||
Old,
|
||||
Old2,
|
||||
Old3,
|
||||
New,
|
||||
System,
|
||||
};
|
||||
|
||||
@@ -356,18 +356,23 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
|
||||
tr("Fix bloom effects"),
|
||||
tr("Removes bloom in Burnout."));
|
||||
|
||||
INSERT(Settings,
|
||||
rescale_hack,
|
||||
tr("Enable Legacy Rescale Pass"),
|
||||
tr("May fix rescale issues in some games by relying on behavior from the previous implementation.\n"
|
||||
"Legacy behavior workaround that fixes line artifacts on AMD and Intel GPUs, and grey texture flicker on Nvidia GPUs in Luigis Mansion 3."));
|
||||
|
||||
// Renderer (Extensions)
|
||||
INSERT(Settings, dyna_state, tr("Extended Dynamic State"),
|
||||
tr("Controls the number of features that can be used in Extended Dynamic State.\n"
|
||||
"Higher states allow for more features and can increase performance, but may cause "
|
||||
"additional graphical issues."));
|
||||
|
||||
INSERT(Settings,
|
||||
vertex_input_dynamic_state,
|
||||
tr("Vertex Input Dynamic State"),
|
||||
tr("Enables vertex input dynamic state feature for better quality and performance."));
|
||||
|
||||
INSERT(Settings,
|
||||
provoking_vertex,
|
||||
tr("Provoking Vertex"),
|
||||
tr("Improves lighting and vertex handling in some games.\n"
|
||||
"Only Vulkan 1.0+ devices support this extension."));
|
||||
|
||||
INSERT(Settings,
|
||||
descriptor_indexing,
|
||||
tr("Descriptor Indexing"),
|
||||
@@ -414,9 +419,6 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
|
||||
"their resolution, details and supported controllers and depending on this setting.\n"
|
||||
"Setting to Handheld can help improve performance for low end systems."));
|
||||
INSERT(Settings, current_user, QString(), QString());
|
||||
INSERT(Settings, serial_unit, tr("Unit Serial"), QString());
|
||||
INSERT(Settings, serial_battery, tr("Battery Serial"), QString());
|
||||
INSERT(Settings, debug_knobs, tr("Debug knobs"), QString());
|
||||
|
||||
// Controls
|
||||
|
||||
@@ -788,6 +790,7 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent)
|
||||
PAIR(ExtendedDynamicState, Disabled, tr("Disabled")),
|
||||
PAIR(ExtendedDynamicState, EDS1, tr("ExtendedDynamicState 1")),
|
||||
PAIR(ExtendedDynamicState, EDS2, tr("ExtendedDynamicState 2")),
|
||||
PAIR(ExtendedDynamicState, EDS3, tr("ExtendedDynamicState 3")),
|
||||
}});
|
||||
|
||||
translations->insert({Settings::EnumMetadata<Settings::GameListMode>::Index(),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
@@ -31,7 +31,7 @@ struct RenderAreaLayout {
|
||||
};
|
||||
constexpr u32 RESCALING_LAYOUT_WORDS_OFFSET = offsetof(RescalingLayout, rescaling_textures);
|
||||
constexpr u32 RESCALING_LAYOUT_DOWN_FACTOR_OFFSET = offsetof(RescalingLayout, down_factor);
|
||||
constexpr u32 RENDERAREA_LAYOUT_OFFSET = sizeof(RescalingLayout);
|
||||
constexpr u32 RENDERAREA_LAYOUT_OFFSET = offsetof(RenderAreaLayout, render_area);
|
||||
|
||||
[[nodiscard]] std::vector<u32> EmitSPIRV(const Profile& profile, const RuntimeInfo& runtime_info,
|
||||
IR::Program& program, Bindings& bindings, bool optimize);
|
||||
|
||||
@@ -491,9 +491,6 @@ void EmitSetPatch(EmitContext& ctx, IR::Patch patch, Id value) {
|
||||
}
|
||||
|
||||
void EmitSetFragColor(EmitContext& ctx, u32 index, u32 component, Id value) {
|
||||
if (!ctx.runtime_info.active_color_outputs[index]) {
|
||||
return;
|
||||
}
|
||||
const Id component_id{ctx.Const(component)};
|
||||
const AttributeType type{ctx.runtime_info.color_output_types[index]};
|
||||
if (type == AttributeType::Float) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -94,17 +91,6 @@ void AlphaTest(EmitContext& ctx) {
|
||||
} // Anonymous namespace
|
||||
|
||||
void EmitPrologue(EmitContext& ctx) {
|
||||
if (ctx.stage == Stage::Fragment && ctx.runtime_info.dual_source_blend) {
|
||||
// Initialize dual-source blending outputs - prevents MoltenVK crash.
|
||||
const Id zero{ctx.Const(0.0f)};
|
||||
const Id one{ctx.Const(1.0f)};
|
||||
const Id default_color{ctx.ConstantComposite(ctx.F32[4], zero, zero, zero, one)};
|
||||
for (u32 i = 0; i < 2; ++i) {
|
||||
if (Sirit::ValidId(ctx.frag_color[i])) {
|
||||
ctx.OpStore(ctx.frag_color[i], default_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ctx.stage == Stage::VertexB) {
|
||||
const Id zero{ctx.Const(0.0f)};
|
||||
const Id one{ctx.Const(1.0f)};
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#include "common/div_ceil.h"
|
||||
#include "shader_recompiler/backend/spirv/emit_spirv.h"
|
||||
#include "shader_recompiler/backend/spirv/spirv_emit_context.h"
|
||||
#include "shader_recompiler/frontend/ir/modifiers.h"
|
||||
|
||||
namespace Shader::Backend::SPIRV {
|
||||
namespace {
|
||||
@@ -1096,8 +1095,7 @@ void EmitContext::DefineRenderArea(const Info& info) {
|
||||
Decorate(push_constant_struct, spv::Decoration::Block);
|
||||
Name(push_constant_struct, "RenderAreaInfo");
|
||||
|
||||
MemberDecorate(push_constant_struct, render_are_member_index, spv::Decoration::Offset,
|
||||
RENDERAREA_LAYOUT_OFFSET);
|
||||
MemberDecorate(push_constant_struct, render_are_member_index, spv::Decoration::Offset, 0);
|
||||
MemberName(push_constant_struct, render_are_member_index, "render_area");
|
||||
|
||||
const Id pointer_type{TypePointer(spv::StorageClass::PushConstant, push_constant_struct)};
|
||||
@@ -1672,24 +1670,13 @@ void EmitContext::DefineOutputs(const IR::Program& program) {
|
||||
break;
|
||||
case Stage::Fragment:
|
||||
for (u32 index = 0; index < 8; ++index) {
|
||||
const bool need_dual_source = runtime_info.dual_source_blend && index <= 1;
|
||||
const bool should_declare = runtime_info.active_color_outputs[index] &&
|
||||
(info.stores_frag_color[index] ||
|
||||
profile.need_declared_frag_colors);
|
||||
if (!need_dual_source && !should_declare) {
|
||||
if (!info.stores_frag_color[index] && !profile.need_declared_frag_colors) {
|
||||
continue;
|
||||
}
|
||||
const Id type{GetAttributeType(*this, runtime_info.color_output_types[index])};
|
||||
frag_color[index] = DefineOutput(*this, type, std::nullopt);
|
||||
// Correct mapping for dual-source blending
|
||||
if (runtime_info.dual_source_blend && index <= 1) {
|
||||
Decorate(frag_color[index], spv::Decoration::Location, 0u);
|
||||
Decorate(frag_color[index], spv::Decoration::Index, index);
|
||||
Name(frag_color[index], index == 0 ? "frag_color0" : "frag_color0_secondary");
|
||||
} else {
|
||||
Decorate(frag_color[index], spv::Decoration::Location, index);
|
||||
Name(frag_color[index], fmt::format("frag_color{}", index));
|
||||
}
|
||||
Decorate(frag_color[index], spv::Decoration::Location, index);
|
||||
Name(frag_color[index], fmt::format("frag_color{}", index));
|
||||
}
|
||||
if (info.stores_frag_depth) {
|
||||
frag_depth = DefineOutput(*this, F32[1], std::nullopt);
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
||||
@@ -304,7 +304,7 @@ IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Blo
|
||||
Optimization::GlobalMemoryToStorageBufferPass(program, host_info);
|
||||
Optimization::TexturePass(env, program, host_info);
|
||||
|
||||
if (Settings::values.resolution_info.active || Settings::values.rescale_hack.GetValue()) {
|
||||
if (Settings::values.resolution_info.active || Optimization::FragmentShaderNeedsRescalingPass(program)) {
|
||||
Optimization::RescalingPass(program);
|
||||
}
|
||||
Optimization::DeadCodeEliminationPass(program);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -22,6 +25,7 @@ void LowerFp64ToFp32(IR::Program& program);
|
||||
void LowerFp16ToFp32(IR::Program& program);
|
||||
void LowerInt64ToInt32(IR::Program& program);
|
||||
void RescalingPass(IR::Program& program);
|
||||
bool FragmentShaderNeedsRescalingPass(const IR::Program& program);
|
||||
void SsaRewritePass(IR::Program& program);
|
||||
void PositionPass(Environment& env, IR::Program& program);
|
||||
void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo& host_info);
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace {
|
||||
return false;
|
||||
}
|
||||
|
||||
void VisitMark(IR::Block& block, IR::Inst& inst) {
|
||||
void VisitMark(IR::Block& block, IR::Inst& inst, bool needs_hack) {
|
||||
switch (inst.GetOpcode()) {
|
||||
case IR::Opcode::ShuffleIndex:
|
||||
case IR::Opcode::ShuffleUp:
|
||||
@@ -54,20 +54,16 @@ void VisitMark(IR::Block& block, IR::Inst& inst) {
|
||||
bool must_patch_outside = false;
|
||||
if (bitcast_inst->GetOpcode() == IR::Opcode::GetAttribute) {
|
||||
const IR::Attribute attr{bitcast_inst->Arg(0).Attribute()};
|
||||
switch (attr) {
|
||||
case IR::Attribute::PositionX:
|
||||
case IR::Attribute::PositionY:
|
||||
if (attr >= IR::Attribute::PositionX && attr <= IR::Attribute::PositionW) {
|
||||
bitcast_inst->SetFlags<u32>(0xDEADBEEF);
|
||||
must_patch_outside = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (must_patch_outside) {
|
||||
const auto it{IR::Block::InstructionList::s_iterator_to(inst)};
|
||||
IR::IREmitter ir{block, it};
|
||||
if (Settings::values.rescale_hack.GetValue()) {
|
||||
|
||||
if (needs_hack) {
|
||||
const IR::F32 new_inst{&*block.PrependNewInst(it, inst)};
|
||||
const IR::F32 up_factor{ir.FPRecip(ir.ResolutionDownFactor())};
|
||||
const IR::Value converted{ir.FPMul(new_inst, up_factor)};
|
||||
@@ -347,12 +343,49 @@ void Visit(const IR::Program& program, IR::Block& block, IR::Inst& inst) {
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
bool FragmentShaderNeedsRescalingPass(const IR::Program& program) {
|
||||
#ifdef __ANDROID__
|
||||
// Disable this workaround on Android to preserve performance
|
||||
return false;
|
||||
#endif
|
||||
|
||||
if (program.stage != Stage::Fragment) return false;
|
||||
|
||||
for (const IR::Block* block : program.post_order_blocks) {
|
||||
for (const IR::Inst& inst : block->Instructions()) {
|
||||
const auto op = inst.GetOpcode();
|
||||
if (op != IR::Opcode::ShuffleIndex && op != IR::Opcode::ShuffleUp &&
|
||||
op != IR::Opcode::ShuffleDown && op != IR::Opcode::ShuffleButterfly) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inst.Arg(0).IsImmediate()) continue;
|
||||
const IR::Inst* arg_inst = inst.Arg(0).InstRecursive();
|
||||
|
||||
if (arg_inst->GetOpcode() != IR::Opcode::BitCastU32F32 || arg_inst->Arg(0).IsImmediate()) continue;
|
||||
const IR::Inst* bitcast_inst = arg_inst->Arg(0).InstRecursive();
|
||||
|
||||
if (bitcast_inst->GetOpcode() == IR::Opcode::GetAttribute) {
|
||||
const auto attr = bitcast_inst->Arg(0).Attribute();
|
||||
if (attr >= IR::Attribute::PositionX && attr <= IR::Attribute::PositionW) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void RescalingPass(IR::Program& program) {
|
||||
const bool is_fragment_shader{program.stage == Stage::Fragment};
|
||||
const bool needs_hack{FragmentShaderNeedsRescalingPass(program)};
|
||||
if (needs_hack) {
|
||||
LOG_WARNING(Shader, "F32/U32 bitcast detected. Applying rescaling workaround.");
|
||||
}
|
||||
if (is_fragment_shader) {
|
||||
for (IR::Block* const block : program.post_order_blocks) {
|
||||
for (IR::Inst& inst : block->Instructions()) {
|
||||
VisitMark(*block, inst);
|
||||
VisitMark(*block, inst, needs_hack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,10 +200,11 @@ static inline TexturePixelFormat ReadTexturePixelFormatCached(Environment& env,
|
||||
return env.ReadTexturePixelFormat(GetTextureHandleCached(env, cbuf));
|
||||
}
|
||||
static inline bool IsTexturePixelFormatIntegerCached(Environment& env,
|
||||
const ConstBufferAddr& cbuf) {
|
||||
const ConstBufferAddr& cbuf) {
|
||||
return env.IsTexturePixelFormatInteger(GetTextureHandleCached(env, cbuf));
|
||||
}
|
||||
|
||||
|
||||
std::optional<ConstBufferAddr> Track(const IR::Value& value, Environment& env);
|
||||
static inline std::optional<ConstBufferAddr> TrackCached(const IR::Value& v, Environment& env) {
|
||||
if (const IR::Inst* key = v.InstRecursive()) {
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
||||
@@ -110,12 +110,6 @@ struct RuntimeInfo {
|
||||
|
||||
/// Output types for each color attachment
|
||||
std::array<AttributeType, 8> color_output_types{};
|
||||
|
||||
/// Fragment color outputs that are active for the current pipeline.
|
||||
std::array<bool, 8> active_color_outputs{true, true, true, true, true, true, true, true};
|
||||
|
||||
/// Dual source blending
|
||||
bool dual_source_blend{};
|
||||
};
|
||||
|
||||
} // namespace Shader
|
||||
|
||||
@@ -356,22 +356,11 @@ void BufferCache<P>::BindHostGeometryBuffers(bool is_indexed) {
|
||||
if (is_indexed) {
|
||||
BindHostIndexBuffer();
|
||||
} else if constexpr (!HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT) {
|
||||
const auto& regs = maxwell3d->regs;
|
||||
const auto& draw_state = maxwell3d->draw_manager->GetDrawState();
|
||||
const bool allow_polygon_mode_emulation =
|
||||
regs.transform_feedback_enabled == 0 &&
|
||||
!regs.IsShaderConfigEnabled(Maxwell::ShaderType::Geometry) &&
|
||||
!regs.IsShaderConfigEnabled(Maxwell::ShaderType::Tessellation);
|
||||
if (draw_state.topology == Maxwell::PrimitiveTopology::Quads ||
|
||||
draw_state.topology == Maxwell::PrimitiveTopology::QuadStrip) {
|
||||
const auto polygon_mode = allow_polygon_mode_emulation
|
||||
? regs.polygon_mode_front
|
||||
: Maxwell::PolygonMode::Fill;
|
||||
if (polygon_mode != Maxwell::PolygonMode::Point) {
|
||||
runtime.BindQuadIndexBuffer(draw_state.topology, polygon_mode,
|
||||
draw_state.vertex_buffer.first,
|
||||
draw_state.vertex_buffer.count);
|
||||
}
|
||||
runtime.BindQuadIndexBuffer(draw_state.topology, draw_state.vertex_buffer.first,
|
||||
draw_state.vertex_buffer.count);
|
||||
}
|
||||
}
|
||||
BindHostVertexBuffers();
|
||||
@@ -771,35 +760,8 @@ void BufferCache<P>::BindHostIndexBuffer() {
|
||||
offset + draw_state.index_buffer.first * draw_state.index_buffer.FormatSizeInBytes();
|
||||
runtime.BindIndexBuffer(buffer, new_offset, size);
|
||||
} else {
|
||||
const auto& regs = maxwell3d->regs;
|
||||
const bool allow_polygon_mode_emulation =
|
||||
regs.transform_feedback_enabled == 0 &&
|
||||
!regs.IsShaderConfigEnabled(Maxwell::ShaderType::Geometry) &&
|
||||
!regs.IsShaderConfigEnabled(Maxwell::ShaderType::Tessellation);
|
||||
auto effective_topology = draw_state.topology;
|
||||
auto polygon_mode = allow_polygon_mode_emulation ? regs.polygon_mode_front
|
||||
: Maxwell::PolygonMode::Fill;
|
||||
if (allow_polygon_mode_emulation) {
|
||||
switch (draw_state.topology) {
|
||||
case Maxwell::PrimitiveTopology::Quads:
|
||||
case Maxwell::PrimitiveTopology::QuadStrip:
|
||||
case Maxwell::PrimitiveTopology::Polygon:
|
||||
if (polygon_mode == Maxwell::PolygonMode::Point) {
|
||||
effective_topology = Maxwell::PrimitiveTopology::Points;
|
||||
} else if (draw_state.topology == Maxwell::PrimitiveTopology::Polygon &&
|
||||
polygon_mode == Maxwell::PolygonMode::Line) {
|
||||
effective_topology = Maxwell::PrimitiveTopology::LineStrip;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
polygon_mode = Maxwell::PolygonMode::Fill;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
polygon_mode = Maxwell::PolygonMode::Fill;
|
||||
}
|
||||
buffer.MarkUsage(offset, size);
|
||||
runtime.BindIndexBuffer(effective_topology, polygon_mode, draw_state.index_buffer.format,
|
||||
runtime.BindIndexBuffer(draw_state.topology, draw_state.index_buffer.format,
|
||||
draw_state.index_buffer.first, draw_state.index_buffer.count,
|
||||
buffer, offset, size);
|
||||
}
|
||||
@@ -1102,23 +1064,13 @@ void BufferCache<P>::BindHostTransformFeedbackBuffers() {
|
||||
if (maxwell3d->regs.transform_feedback_enabled == 0) {
|
||||
return;
|
||||
}
|
||||
Buffer& null_buffer = slot_buffers[NULL_BUFFER_ID];
|
||||
bool reached_end = false;
|
||||
HostBindings<typename P::Buffer> host_bindings;
|
||||
for (u32 index = 0; index < NUM_TRANSFORM_FEEDBACK_BUFFERS; ++index) {
|
||||
const auto& control = maxwell3d->regs.transform_feedback.controls[index];
|
||||
if (reached_end ||
|
||||
(control.varying_count == 0 && control.stride == 0)) {
|
||||
reached_end = true;
|
||||
runtime.BindTransformFeedbackBuffer(index, null_buffer, 0, 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
const Binding& binding = channel_state->transform_feedback_buffers[index];
|
||||
if (binding.size == 0 || binding.buffer_id == NULL_BUFFER_ID) {
|
||||
runtime.BindTransformFeedbackBuffer(index, null_buffer, 0, 0);
|
||||
continue;
|
||||
if (maxwell3d->regs.transform_feedback.controls[index].varying_count == 0 &&
|
||||
maxwell3d->regs.transform_feedback.controls[index].stride == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
Buffer& buffer = slot_buffers[binding.buffer_id];
|
||||
TouchBuffer(buffer, binding.buffer_id);
|
||||
const u32 size = binding.size;
|
||||
@@ -1128,7 +1080,12 @@ void BufferCache<P>::BindHostTransformFeedbackBuffers() {
|
||||
|
||||
const u32 offset = buffer.Offset(binding.device_addr);
|
||||
buffer.MarkUsage(offset, size);
|
||||
runtime.BindTransformFeedbackBuffer(index, buffer, offset, size);
|
||||
host_bindings.buffers.push_back(&buffer);
|
||||
host_bindings.offsets.push_back(offset);
|
||||
host_bindings.sizes.push_back(size);
|
||||
}
|
||||
if (host_bindings.buffers.size() > 0) {
|
||||
runtime.BindTransformFeedbackBuffers(host_bindings);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,11 +14,8 @@
|
||||
#include <mutex>
|
||||
#include <numeric>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
#include <ankerl/unordered_dense.h>
|
||||
#include <boost/container/static_vector.hpp>
|
||||
#include <boost/container/small_vector.hpp>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/div_ceil.h"
|
||||
@@ -97,10 +94,10 @@ static constexpr Binding NULL_BINDING{
|
||||
|
||||
template <typename Buffer>
|
||||
struct HostBindings {
|
||||
boost::container::static_vector<Buffer*, NUM_VERTEX_BUFFERS> buffers;
|
||||
boost::container::static_vector<u64, NUM_VERTEX_BUFFERS> offsets;
|
||||
boost::container::static_vector<u64, NUM_VERTEX_BUFFERS> sizes;
|
||||
boost::container::static_vector<u64, NUM_VERTEX_BUFFERS> strides;
|
||||
boost::container::small_vector<Buffer*, NUM_VERTEX_BUFFERS> buffers;
|
||||
boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> offsets;
|
||||
boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> sizes;
|
||||
boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> strides;
|
||||
u32 min_index{NUM_VERTEX_BUFFERS};
|
||||
u32 max_index{0};
|
||||
};
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
@@ -22,12 +19,12 @@ ChannelState::ChannelState(s32 bind_id_) : bind_id{bind_id_}, initialized{} {}
|
||||
void ChannelState::Init(Core::System& system, GPU& gpu, u64 program_id_) {
|
||||
ASSERT(memory_manager);
|
||||
program_id = program_id_;
|
||||
dma_pusher.emplace(system, gpu, *memory_manager, *this);
|
||||
maxwell_3d.emplace(system, *memory_manager);
|
||||
fermi_2d.emplace(*memory_manager);
|
||||
kepler_compute.emplace(system, *memory_manager);
|
||||
maxwell_dma.emplace(system, *memory_manager);
|
||||
kepler_memory.emplace(system, *memory_manager);
|
||||
dma_pusher = std::make_unique<Tegra::DmaPusher>(system, gpu, *memory_manager, *this);
|
||||
maxwell_3d = std::make_unique<Engines::Maxwell3D>(system, *memory_manager);
|
||||
fermi_2d = std::make_unique<Engines::Fermi2D>(*memory_manager);
|
||||
kepler_compute = std::make_unique<Engines::KeplerCompute>(system, *memory_manager);
|
||||
maxwell_dma = std::make_unique<Engines::MaxwellDMA>(system, *memory_manager);
|
||||
kepler_memory = std::make_unique<Engines::KeplerMemory>(system, *memory_manager);
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
@@ -9,12 +6,6 @@
|
||||
#include <memory>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/engines/fermi_2d.h"
|
||||
#include "video_core/engines/kepler_memory.h"
|
||||
#include "video_core/engines/kepler_compute.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/engines/maxwell_dma.h"
|
||||
#include "video_core/dma_pusher.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
@@ -27,34 +18,49 @@ class RasterizerInterface;
|
||||
namespace Tegra {
|
||||
|
||||
class GPU;
|
||||
|
||||
namespace Engines {
|
||||
class Puller;
|
||||
class Fermi2D;
|
||||
class Maxwell3D;
|
||||
class MaxwellDMA;
|
||||
class KeplerCompute;
|
||||
class KeplerMemory;
|
||||
} // namespace Engines
|
||||
|
||||
class MemoryManager;
|
||||
class DmaPusher;
|
||||
|
||||
namespace Control {
|
||||
|
||||
struct ChannelState {
|
||||
explicit ChannelState(s32 bind_id);
|
||||
ChannelState(const ChannelState& state) = delete;
|
||||
ChannelState& operator=(const ChannelState&) = delete;
|
||||
ChannelState(ChannelState&& other) noexcept = default;
|
||||
ChannelState& operator=(ChannelState&& other) noexcept = default;
|
||||
|
||||
void Init(Core::System& system, GPU& gpu, u64 program_id);
|
||||
|
||||
void BindRasterizer(VideoCore::RasterizerInterface* rasterizer);
|
||||
|
||||
/// 3D engine
|
||||
std::optional<Engines::Maxwell3D> maxwell_3d;
|
||||
/// 2D engine
|
||||
std::optional<Engines::Fermi2D> fermi_2d;
|
||||
/// Compute engine
|
||||
std::optional<Engines::KeplerCompute> kepler_compute;
|
||||
/// DMA engine
|
||||
std::optional<Engines::MaxwellDMA> maxwell_dma;
|
||||
/// Inline memory engine
|
||||
std::optional<Engines::KeplerMemory> kepler_memory;
|
||||
/// NV01 Timer
|
||||
std::optional<Engines::KeplerMemory> nv01_timer;
|
||||
std::optional<DmaPusher> dma_pusher;
|
||||
std::shared_ptr<MemoryManager> memory_manager;
|
||||
|
||||
s32 bind_id = -1;
|
||||
u64 program_id = 0;
|
||||
/// 3D engine
|
||||
std::unique_ptr<Engines::Maxwell3D> maxwell_3d;
|
||||
/// 2D engine
|
||||
std::unique_ptr<Engines::Fermi2D> fermi_2d;
|
||||
/// Compute engine
|
||||
std::unique_ptr<Engines::KeplerCompute> kepler_compute;
|
||||
/// DMA engine
|
||||
std::unique_ptr<Engines::MaxwellDMA> maxwell_dma;
|
||||
/// Inline memory engine
|
||||
std::unique_ptr<Engines::KeplerMemory> kepler_memory;
|
||||
|
||||
std::shared_ptr<MemoryManager> memory_manager;
|
||||
|
||||
std::unique_ptr<DmaPusher> dma_pusher;
|
||||
|
||||
bool initialized{};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
@@ -15,7 +15,6 @@
|
||||
namespace Tegra::Engines {
|
||||
|
||||
enum class EngineTypes : u32 {
|
||||
Nv01Timer,
|
||||
KeplerCompute,
|
||||
Maxwell3D,
|
||||
Fermi2D,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
@@ -12,7 +12,6 @@
|
||||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "shader_recompiler/program_header.h"
|
||||
#include "video_core/dirty_flags.h"
|
||||
#include "video_core/engines/draw_manager.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
@@ -23,50 +22,12 @@
|
||||
|
||||
namespace Tegra::Engines {
|
||||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] Maxwell3D::Regs::PrimitiveTopology PrimitiveTopologyFromGeometryOutput(
|
||||
Shader::OutputTopology topology) {
|
||||
switch (topology) {
|
||||
case Shader::OutputTopology::PointList:
|
||||
return Maxwell3D::Regs::PrimitiveTopology::Points;
|
||||
case Shader::OutputTopology::LineStrip:
|
||||
return Maxwell3D::Regs::PrimitiveTopology::LineStrip;
|
||||
case Shader::OutputTopology::TriangleStrip:
|
||||
return Maxwell3D::Regs::PrimitiveTopology::TriangleStrip;
|
||||
}
|
||||
return Maxwell3D::Regs::PrimitiveTopology::Triangles;
|
||||
}
|
||||
|
||||
[[nodiscard]] Maxwell3D::Regs::PrimitiveTopology PrimitiveTopologyFromTessellationOutput(
|
||||
Maxwell3D::Regs::Tessellation::OutputPrimitives topology) {
|
||||
switch (topology) {
|
||||
case Maxwell3D::Regs::Tessellation::OutputPrimitives::Points:
|
||||
return Maxwell3D::Regs::PrimitiveTopology::Points;
|
||||
case Maxwell3D::Regs::Tessellation::OutputPrimitives::Lines:
|
||||
return Maxwell3D::Regs::PrimitiveTopology::Lines;
|
||||
case Maxwell3D::Regs::Tessellation::OutputPrimitives::Triangles_CW:
|
||||
case Maxwell3D::Regs::Tessellation::OutputPrimitives::Triangles_CCW:
|
||||
return Maxwell3D::Regs::PrimitiveTopology::Triangles;
|
||||
}
|
||||
return Maxwell3D::Regs::PrimitiveTopology::Triangles;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/// First register id that is actually a Macro call.
|
||||
constexpr u32 MacroRegistersStart = 0xE00;
|
||||
|
||||
Maxwell3D::Maxwell3D(Core::System& system_, MemoryManager& memory_manager_)
|
||||
: draw_manager{std::make_unique<DrawManager>(this)}, system{system_}
|
||||
, memory_manager{memory_manager_}
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
, macro_engine(bool(Settings::values.disable_macro_jit))
|
||||
#else
|
||||
, macro_engine(true)
|
||||
#endif
|
||||
, upload_state{memory_manager, regs.upload}
|
||||
{
|
||||
: draw_manager{std::make_unique<DrawManager>(this)}, system{system_},
|
||||
memory_manager{memory_manager_}, macro_engine{GetMacroEngine(*this)}, upload_state{memory_manager, regs.upload} {
|
||||
dirty.flags.flip();
|
||||
InitializeRegisterDefaults();
|
||||
execution_mask.reset();
|
||||
@@ -367,9 +328,9 @@ void Maxwell3D::ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argume
|
||||
shadow_state.shadow_ram_control = static_cast<Regs::ShadowRamControl>(nonshadow_argument);
|
||||
return;
|
||||
case MAXWELL3D_REG_INDEX(load_mme.instruction_ptr):
|
||||
return macro_engine.ClearCode(regs.load_mme.instruction_ptr);
|
||||
return macro_engine->ClearCode(regs.load_mme.instruction_ptr);
|
||||
case MAXWELL3D_REG_INDEX(load_mme.instruction):
|
||||
return macro_engine.AddCode(regs.load_mme.instruction_ptr, argument);
|
||||
return macro_engine->AddCode(regs.load_mme.instruction_ptr, argument);
|
||||
case MAXWELL3D_REG_INDEX(load_mme.start_address):
|
||||
return ProcessMacroBind(argument);
|
||||
case MAXWELL3D_REG_INDEX(falcon[4]):
|
||||
@@ -437,7 +398,7 @@ void Maxwell3D::CallMacroMethod(u32 method, const std::vector<u32>& parameters)
|
||||
((method - MacroRegistersStart) >> 1) % static_cast<u32>(macro_positions.size());
|
||||
|
||||
// Execute the current macro.
|
||||
macro_engine.Execute(*this, macro_positions[entry], parameters);
|
||||
macro_engine->Execute(macro_positions[entry], parameters);
|
||||
|
||||
draw_manager->DrawDeferred();
|
||||
}
|
||||
@@ -503,7 +464,7 @@ void Maxwell3D::CallMultiMethod(u32 method, const u32* base_start, u32 amount,
|
||||
}
|
||||
|
||||
void Maxwell3D::ProcessMacroUpload(u32 data) {
|
||||
macro_engine.AddCode(regs.load_mme.instruction_ptr++, data);
|
||||
macro_engine->AddCode(regs.load_mme.instruction_ptr++, data);
|
||||
}
|
||||
|
||||
void Maxwell3D::ProcessMacroBind(u32 data) {
|
||||
@@ -614,8 +575,6 @@ void Maxwell3D::ProcessQueryCondition() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
rasterizer->SetConditionalRenderingPredicate(execute_on);
|
||||
}
|
||||
|
||||
void Maxwell3D::ProcessCounterReset() {
|
||||
@@ -698,22 +657,6 @@ Texture::TSCEntry Maxwell3D::GetTSCEntry(u32 tsc_index) const {
|
||||
return tsc_entry;
|
||||
}
|
||||
|
||||
Maxwell3D::Regs::PrimitiveTopology Maxwell3D::GetTransformFeedbackOutputTopology() const {
|
||||
if (regs.IsShaderConfigEnabled(Regs::ShaderType::Geometry)) {
|
||||
const GPUVAddr shader_addr = regs.program_region.Address() +
|
||||
regs.pipelines[static_cast<size_t>(Regs::ShaderType::Geometry)]
|
||||
.offset;
|
||||
Shader::ProgramHeader sph{};
|
||||
memory_manager.ReadBlockUnsafe(shader_addr, &sph, sizeof(sph));
|
||||
return PrimitiveTopologyFromGeometryOutput(sph.common3.output_topology.Value());
|
||||
}
|
||||
if (regs.IsShaderConfigEnabled(Regs::ShaderType::Tessellation)) {
|
||||
return PrimitiveTopologyFromTessellationOutput(
|
||||
regs.tessellation.params.output_primitives.Value());
|
||||
}
|
||||
return draw_manager->GetDrawState().topology;
|
||||
}
|
||||
|
||||
u32 Maxwell3D::GetRegisterValue(u32 method) const {
|
||||
ASSERT(method < Regs::NUM_REGS && "Invalid Maxwell3D register");
|
||||
return regs.reg_array[method];
|
||||
|
||||
@@ -2258,7 +2258,7 @@ public:
|
||||
/// Returns whether the vertex array specified by index is supposed to be
|
||||
/// accessed per instance or not.
|
||||
bool IsInstancingEnabled(std::size_t index) const {
|
||||
return bool(is_instanced[index]); //FUCK YOU MSVC
|
||||
return is_instanced[index];
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3093,8 +3093,6 @@ public:
|
||||
return *rasterizer;
|
||||
}
|
||||
|
||||
[[nodiscard]] Regs::PrimitiveTopology GetTransformFeedbackOutputTopology() const;
|
||||
|
||||
struct DirtyState {
|
||||
using Flags = std::bitset<(std::numeric_limits<u8>::max)()>;
|
||||
using Table = std::array<u8, Regs::NUM_REGS>;
|
||||
@@ -3205,7 +3203,7 @@ private:
|
||||
std::vector<u32> macro_params;
|
||||
|
||||
/// Interpreter for the macro codes uploaded to the GPU.
|
||||
MacroEngine macro_engine;
|
||||
std::optional<MacroEngine> macro_engine;
|
||||
|
||||
Upload::State upload_state;
|
||||
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "video_core/engines/engine_interface.h"
|
||||
#include "video_core/engines/engine_upload.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Tegra {
|
||||
class MemoryManager;
|
||||
}
|
||||
|
||||
namespace Tegra::Engines {
|
||||
class Nv01Timer final : public EngineInterface {
|
||||
public:
|
||||
explicit Nv01Timer(Core::System& system_, MemoryManager& memory_manager)
|
||||
: system{system_}
|
||||
{}
|
||||
~Nv01Timer() override;
|
||||
|
||||
/// Write the value to the register identified by method.
|
||||
void CallMethod(u32 method, u32 method_argument, bool is_last_call) override {
|
||||
LOG_DEBUG(HW_GPU, "method={}, argument={}, is_last_call={}", method, method_argument, is_last_call);
|
||||
}
|
||||
|
||||
/// Write multiple values to the register identified by method.
|
||||
void CallMultiMethod(u32 method, const u32* base_start, u32 amount, u32 methods_pending) override {
|
||||
LOG_DEBUG(HW_GPU, "method={}, base_start={}, amount={}, pending={}", method, fmt::ptr(base_start), amount, methods_pending);
|
||||
}
|
||||
|
||||
struct Regs {
|
||||
// No fucking idea
|
||||
INSERT_PADDING_BYTES_NOINIT(0x48);
|
||||
} regs{};
|
||||
private:
|
||||
void ConsumeSinkImpl() override {}
|
||||
Core::System& system;
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
@@ -37,22 +34,24 @@ void Puller::ProcessBindMethod(const MethodCall& method_call) {
|
||||
bound_engines[method_call.subchannel] = engine_id;
|
||||
switch (engine_id) {
|
||||
case EngineID::FERMI_TWOD_A:
|
||||
dma_pusher.BindSubchannel(&*channel_state.fermi_2d, method_call.subchannel, EngineTypes::Fermi2D);
|
||||
dma_pusher.BindSubchannel(channel_state.fermi_2d.get(), method_call.subchannel,
|
||||
EngineTypes::Fermi2D);
|
||||
break;
|
||||
case EngineID::MAXWELL_B:
|
||||
dma_pusher.BindSubchannel(&*channel_state.maxwell_3d, method_call.subchannel, EngineTypes::Maxwell3D);
|
||||
dma_pusher.BindSubchannel(channel_state.maxwell_3d.get(), method_call.subchannel,
|
||||
EngineTypes::Maxwell3D);
|
||||
break;
|
||||
case EngineID::KEPLER_COMPUTE_B:
|
||||
dma_pusher.BindSubchannel(&*channel_state.kepler_compute, method_call.subchannel, EngineTypes::KeplerCompute);
|
||||
dma_pusher.BindSubchannel(channel_state.kepler_compute.get(), method_call.subchannel,
|
||||
EngineTypes::KeplerCompute);
|
||||
break;
|
||||
case EngineID::MAXWELL_DMA_COPY_A:
|
||||
dma_pusher.BindSubchannel(&*channel_state.maxwell_dma, method_call.subchannel, EngineTypes::MaxwellDMA);
|
||||
dma_pusher.BindSubchannel(channel_state.maxwell_dma.get(), method_call.subchannel,
|
||||
EngineTypes::MaxwellDMA);
|
||||
break;
|
||||
case EngineID::KEPLER_INLINE_TO_MEMORY_B:
|
||||
dma_pusher.BindSubchannel(&*channel_state.kepler_memory, method_call.subchannel, EngineTypes::KeplerMemory);
|
||||
break;
|
||||
case EngineID::NV01_TIMER:
|
||||
dma_pusher.BindSubchannel(&*channel_state.nv01_timer, method_call.subchannel, EngineTypes::Nv01Timer);
|
||||
dma_pusher.BindSubchannel(channel_state.kepler_memory.get(), method_call.subchannel,
|
||||
EngineTypes::KeplerMemory);
|
||||
break;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented engine {:04X}", engine_id);
|
||||
@@ -210,22 +209,24 @@ void Puller::CallEngineMethod(const MethodCall& method_call) {
|
||||
|
||||
switch (engine) {
|
||||
case EngineID::FERMI_TWOD_A:
|
||||
channel_state.fermi_2d->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall());
|
||||
channel_state.fermi_2d->CallMethod(method_call.method, method_call.argument,
|
||||
method_call.IsLastCall());
|
||||
break;
|
||||
case EngineID::MAXWELL_B:
|
||||
channel_state.maxwell_3d->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall());
|
||||
channel_state.maxwell_3d->CallMethod(method_call.method, method_call.argument,
|
||||
method_call.IsLastCall());
|
||||
break;
|
||||
case EngineID::KEPLER_COMPUTE_B:
|
||||
channel_state.kepler_compute->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall());
|
||||
channel_state.kepler_compute->CallMethod(method_call.method, method_call.argument,
|
||||
method_call.IsLastCall());
|
||||
break;
|
||||
case EngineID::MAXWELL_DMA_COPY_A:
|
||||
channel_state.maxwell_dma->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall());
|
||||
channel_state.maxwell_dma->CallMethod(method_call.method, method_call.argument,
|
||||
method_call.IsLastCall());
|
||||
break;
|
||||
case EngineID::KEPLER_INLINE_TO_MEMORY_B:
|
||||
channel_state.kepler_memory->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall());
|
||||
break;
|
||||
case EngineID::NV01_TIMER:
|
||||
channel_state.nv01_timer->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall());
|
||||
channel_state.kepler_memory->CallMethod(method_call.method, method_call.argument,
|
||||
method_call.IsLastCall());
|
||||
break;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented engine");
|
||||
@@ -254,9 +255,6 @@ void Puller::CallEngineMultiMethod(u32 method, u32 subchannel, const u32* base_s
|
||||
case EngineID::KEPLER_INLINE_TO_MEMORY_B:
|
||||
channel_state.kepler_memory->CallMultiMethod(method, base_start, amount, methods_pending);
|
||||
break;
|
||||
case EngineID::NV01_TIMER:
|
||||
channel_state.nv01_timer->CallMultiMethod(method, base_start, amount, methods_pending);
|
||||
break;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented engine");
|
||||
break;
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
@@ -23,7 +20,6 @@ class MemoryManager;
|
||||
class DmaPusher;
|
||||
|
||||
enum class EngineID {
|
||||
NV01_TIMER = 0x0004,
|
||||
FERMI_TWOD_A = 0x902D, // 2D Engine
|
||||
MAXWELL_B = 0xB197, // 3D Engine
|
||||
KEPLER_COMPUTE_B = 0xB1C0,
|
||||
|
||||
+661
-452
File diff suppressed because it is too large
Load Diff
+42
-124
@@ -7,10 +7,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
#include <ankerl/unordered_dense.h>
|
||||
#include <vector>
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
@@ -100,142 +98,62 @@ union MethodAddress {
|
||||
|
||||
} // namespace Macro
|
||||
|
||||
struct HLEMacro {
|
||||
};
|
||||
/// @note: these macros have two versions, a normal and extended version, with the extended version
|
||||
/// also assigning the base vertex/instance.
|
||||
struct HLE_DrawArraysIndirect final {
|
||||
HLE_DrawArraysIndirect(bool extended_) noexcept : extended{extended_} {}
|
||||
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
|
||||
void Fallback(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters);
|
||||
bool extended;
|
||||
};
|
||||
/// @note: these macros have two versions, a normal and extended version, with the extended version
|
||||
/// also assigning the base vertex/instance.
|
||||
struct HLE_DrawIndexedIndirect final {
|
||||
explicit HLE_DrawIndexedIndirect(bool extended_) noexcept : extended{extended_} {}
|
||||
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
|
||||
void Fallback(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters);
|
||||
bool extended;
|
||||
};
|
||||
struct HLE_MultiLayerClear final {
|
||||
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
|
||||
};
|
||||
struct HLE_MultiDrawIndexedIndirectCount final {
|
||||
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
|
||||
void Fallback(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters);
|
||||
};
|
||||
struct HLE_DrawIndirectByteCount final {
|
||||
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
|
||||
void Fallback(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters);
|
||||
};
|
||||
struct HLE_C713C83D8F63CCF3 final {
|
||||
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
|
||||
};
|
||||
struct HLE_D7333D26E0A93EDE final {
|
||||
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
|
||||
};
|
||||
struct HLE_BindShader final {
|
||||
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
|
||||
};
|
||||
struct HLE_SetRasterBoundingBox final {
|
||||
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
|
||||
};
|
||||
struct HLE_ClearConstBuffer final {
|
||||
HLE_ClearConstBuffer(size_t base_size_) noexcept : base_size{base_size_} {}
|
||||
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
|
||||
size_t base_size;
|
||||
};
|
||||
struct HLE_ClearMemory final {
|
||||
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
|
||||
std::vector<u32> zero_memory;
|
||||
};
|
||||
struct HLE_TransformFeedbackSetup final {
|
||||
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
|
||||
};
|
||||
struct MacroInterpreterImpl final {
|
||||
MacroInterpreterImpl() {}
|
||||
MacroInterpreterImpl(std::span<const u32> code_) : code{code_} {}
|
||||
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> params, u32 method);
|
||||
void Reset();
|
||||
bool Step(Engines::Maxwell3D& maxwell3d, bool is_delay_slot);
|
||||
u32 GetALUResult(Macro::ALUOperation operation, u32 src_a, u32 src_b);
|
||||
void ProcessResult(Engines::Maxwell3D& maxwell3d, Macro::ResultOperation operation, u32 reg, u32 result);
|
||||
bool EvaluateBranchCondition(Macro::BranchCondition cond, u32 value) const;
|
||||
Macro::Opcode GetOpcode() const;
|
||||
u32 GetRegister(u32 register_id) const;
|
||||
void SetRegister(u32 register_id, u32 value);
|
||||
/// Sets the method address to use for the next Send instruction.
|
||||
[[nodiscard]] inline void SetMethodAddress(u32 address) noexcept {
|
||||
method_address.raw = address;
|
||||
}
|
||||
void Send(Engines::Maxwell3D& maxwell3d, u32 value);
|
||||
u32 Read(Engines::Maxwell3D& maxwell3d, u32 method) const;
|
||||
u32 FetchParameter();
|
||||
/// General purpose macro registers.
|
||||
std::array<u32, Macro::NUM_MACRO_REGISTERS> registers = {};
|
||||
/// Input parameters of the current macro.
|
||||
std::vector<u32> parameters;
|
||||
std::span<const u32> code;
|
||||
/// Program counter to execute at after the delay slot is executed.
|
||||
std::optional<u32> delayed_pc;
|
||||
/// Method address to use for the next Send instruction.
|
||||
Macro::MethodAddress method_address = {};
|
||||
/// Current program counter
|
||||
u32 pc{};
|
||||
/// Index of the next parameter that will be fetched by the 'parm' instruction.
|
||||
u32 next_parameter_index = 0;
|
||||
bool carry_flag = false;
|
||||
};
|
||||
struct DynamicCachedMacro {
|
||||
virtual ~DynamicCachedMacro() = default;
|
||||
class CachedMacro {
|
||||
public:
|
||||
CachedMacro(Engines::Maxwell3D& maxwell3d_)
|
||||
: maxwell3d{maxwell3d_}
|
||||
{}
|
||||
virtual ~CachedMacro() = default;
|
||||
/// Executes the macro code with the specified input parameters.
|
||||
/// @param parameters The parameters of the macro
|
||||
/// @param method The method to execute
|
||||
virtual void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, u32 method) = 0;
|
||||
virtual void Execute(const std::vector<u32>& parameters, u32 method) = 0;
|
||||
Engines::Maxwell3D& maxwell3d;
|
||||
};
|
||||
|
||||
using AnyCachedMacro = std::variant<
|
||||
std::monostate,
|
||||
HLEMacro,
|
||||
HLE_DrawArraysIndirect,
|
||||
HLE_DrawIndexedIndirect,
|
||||
HLE_MultiDrawIndexedIndirectCount,
|
||||
HLE_MultiLayerClear,
|
||||
HLE_C713C83D8F63CCF3,
|
||||
HLE_D7333D26E0A93EDE,
|
||||
HLE_BindShader,
|
||||
HLE_SetRasterBoundingBox,
|
||||
HLE_ClearConstBuffer,
|
||||
HLE_ClearMemory,
|
||||
HLE_TransformFeedbackSetup,
|
||||
HLE_DrawIndirectByteCount,
|
||||
MacroInterpreterImpl,
|
||||
// Used for JIT x86 macro
|
||||
std::unique_ptr<DynamicCachedMacro>
|
||||
>;
|
||||
class HLEMacro {
|
||||
public:
|
||||
explicit HLEMacro(Engines::Maxwell3D& maxwell3d_);
|
||||
~HLEMacro();
|
||||
// Allocates and returns a cached macro if the hash matches a known function.
|
||||
// Returns nullptr otherwise.
|
||||
[[nodiscard]] std::unique_ptr<CachedMacro> GetHLEProgram(u64 hash) const;
|
||||
private:
|
||||
Engines::Maxwell3D& maxwell3d;
|
||||
};
|
||||
|
||||
class MacroEngine {
|
||||
public:
|
||||
explicit MacroEngine(Engines::Maxwell3D& maxwell3d, bool is_interpreted);
|
||||
~MacroEngine();
|
||||
|
||||
struct MacroEngine {
|
||||
MacroEngine(bool is_interpreted_) noexcept : is_interpreted{is_interpreted_} {}
|
||||
// Store the uploaded macro code to compile them when they're called.
|
||||
inline void AddCode(u32 method, u32 data) noexcept {
|
||||
uploaded_macro_code[method].push_back(data);
|
||||
}
|
||||
void AddCode(u32 method, u32 data);
|
||||
|
||||
// Clear the code associated with a method.
|
||||
inline void ClearCode(u32 method) noexcept {
|
||||
macro_cache.erase(method);
|
||||
uploaded_macro_code.erase(method);
|
||||
}
|
||||
void ClearCode(u32 method);
|
||||
|
||||
// Compiles the macro if its not in the cache, and executes the compiled macro
|
||||
void Execute(Engines::Maxwell3D& maxwell3d, u32 method, std::span<const u32> parameters);
|
||||
AnyCachedMacro Compile(Engines::Maxwell3D& maxwell3d, std::span<const u32> code);
|
||||
void Execute(u32 method, const std::vector<u32>& parameters);
|
||||
|
||||
protected:
|
||||
std::unique_ptr<CachedMacro> Compile(const std::vector<u32>& code);
|
||||
|
||||
private:
|
||||
struct CacheInfo {
|
||||
AnyCachedMacro program;
|
||||
std::unique_ptr<CachedMacro> lle_program{};
|
||||
std::unique_ptr<CachedMacro> hle_program{};
|
||||
u64 hash{};
|
||||
bool has_hle_program{};
|
||||
};
|
||||
|
||||
ankerl::unordered_dense::map<u32, CacheInfo> macro_cache;
|
||||
ankerl::unordered_dense::map<u32, std::vector<u32>> uploaded_macro_code;
|
||||
std::optional<HLEMacro> hle_macros;
|
||||
Engines::Maxwell3D& maxwell3d;
|
||||
bool is_interpreted;
|
||||
};
|
||||
|
||||
std::optional<MacroEngine> GetMacroEngine(Engines::Maxwell3D& maxwell3d);
|
||||
|
||||
} // namespace Tegra
|
||||
|
||||
@@ -381,11 +381,6 @@ void QueryCacheBase<Traits>::NotifySegment(bool resume) {
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Traits>
|
||||
void QueryCacheBase<Traits>::SetHostConditionalRenderingResult(bool condition_passed) {
|
||||
impl->runtime.SetHostConditionalRenderingResult(condition_passed);
|
||||
}
|
||||
|
||||
template <typename Traits>
|
||||
bool QueryCacheBase<Traits>::AccelerateHostConditionalRendering() {
|
||||
bool qc_dirty = false;
|
||||
|
||||
@@ -115,8 +115,6 @@ public:
|
||||
|
||||
void NotifySegment(bool resume);
|
||||
|
||||
void SetHostConditionalRenderingResult(bool condition_passed);
|
||||
|
||||
void BindToChannel(s32 id) override;
|
||||
|
||||
protected:
|
||||
|
||||
@@ -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-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -149,8 +146,6 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual void SetConditionalRenderingPredicate(bool enabled) {}
|
||||
|
||||
/// Attempt to use a faster method to perform a surface copy
|
||||
[[nodiscard]] virtual bool AccelerateSurfaceCopy(
|
||||
const Tegra::Engines::Fermi2D::Surface& src, const Tegra::Engines::Fermi2D::Surface& dst,
|
||||
|
||||
@@ -56,23 +56,16 @@ namespace {
|
||||
|
||||
[[nodiscard]] VkImageSubresourceRange SubresourceRangeFromView(const ImageView& image_view) {
|
||||
auto range = image_view.range;
|
||||
const bool is_3d_image = image_view.type == VideoCommon::ImageViewType::e3D ||
|
||||
(image_view.flags & VideoCommon::ImageViewFlagBits::Slice) !=
|
||||
VideoCommon::ImageViewFlagBits{};
|
||||
if ((image_view.flags & VideoCommon::ImageViewFlagBits::Slice) != VideoCommon::ImageViewFlagBits{}) {
|
||||
range.base.layer = 0;
|
||||
range.extent.layers = 1;
|
||||
}
|
||||
u32 layer_count = static_cast<u32>(range.extent.layers);
|
||||
if (is_3d_image && layer_count == 1) {
|
||||
layer_count = VK_REMAINING_ARRAY_LAYERS;
|
||||
}
|
||||
return VkImageSubresourceRange{
|
||||
.aspectMask = AspectMaskFromFormat(image_view.format),
|
||||
.baseMipLevel = static_cast<u32>(range.base.level),
|
||||
.levelCount = static_cast<u32>(range.extent.levels),
|
||||
.baseArrayLayer = static_cast<u32>(range.base.layer),
|
||||
.layerCount = layer_count,
|
||||
.layerCount = static_cast<u32>(range.extent.layers),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -177,14 +170,8 @@ constexpr VkPipelineMultisampleStateCreateInfo PIPELINE_MULTISAMPLE_STATE_CREATE
|
||||
.alphaToCoverageEnable = VK_FALSE,
|
||||
.alphaToOneEnable = VK_FALSE,
|
||||
};
|
||||
constexpr std::array DYNAMIC_STATES{
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR,
|
||||
VK_DYNAMIC_STATE_BLEND_CONSTANTS,
|
||||
VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK,
|
||||
VK_DYNAMIC_STATE_STENCIL_WRITE_MASK,
|
||||
VK_DYNAMIC_STATE_STENCIL_REFERENCE,
|
||||
};
|
||||
constexpr std::array DYNAMIC_STATES{VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR,
|
||||
VK_DYNAMIC_STATE_BLEND_CONSTANTS};
|
||||
constexpr VkPipelineDynamicStateCreateInfo PIPELINE_DYNAMIC_STATE_CREATE_INFO{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
@@ -602,7 +589,6 @@ void BlitImageHelper::BlitColor(const Framebuffer* dst_framebuffer, VkImageView
|
||||
cmdbuf.Draw(3, 1, 0, 0);
|
||||
cmdbuf.EndRenderPass();
|
||||
});
|
||||
scheduler.InvalidateState();
|
||||
}
|
||||
|
||||
void BlitImageHelper::BlitDepthStencil(const Framebuffer* dst_framebuffer,
|
||||
@@ -1046,7 +1032,7 @@ void BlitImageHelper::ConvertDepthToColorPipeline(vk::Pipeline& pipeline, VkRend
|
||||
VkShaderModule frag_shader = *convert_float_to_depth_frag;
|
||||
const std::array stages = MakeStages(*full_screen_vert, frag_shader);
|
||||
const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device);
|
||||
pipeline = device.GetLogical().CreateGraphicsPipeline(VkGraphicsPipelineCreateInfo{
|
||||
pipeline = device.GetLogical().CreateGraphicsPipeline({
|
||||
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
@@ -1076,7 +1062,7 @@ void BlitImageHelper::ConvertColorToDepthPipeline(vk::Pipeline& pipeline, VkRend
|
||||
VkShaderModule frag_shader = *convert_depth_to_float_frag;
|
||||
const std::array stages = MakeStages(*full_screen_vert, frag_shader);
|
||||
const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device);
|
||||
pipeline = device.GetLogical().CreateGraphicsPipeline(VkGraphicsPipelineCreateInfo{
|
||||
pipeline = device.GetLogical().CreateGraphicsPipeline({
|
||||
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
@@ -1107,7 +1093,7 @@ void BlitImageHelper::ConvertPipelineEx(vk::Pipeline& pipeline, VkRenderPass ren
|
||||
}
|
||||
const std::array stages = MakeStages(*full_screen_vert, *module);
|
||||
const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device);
|
||||
pipeline = device.GetLogical().CreateGraphicsPipeline(VkGraphicsPipelineCreateInfo{
|
||||
pipeline = device.GetLogical().CreateGraphicsPipeline({
|
||||
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
@@ -1149,7 +1135,7 @@ void BlitImageHelper::ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass rende
|
||||
is_target_depth ? *convert_float_to_depth_frag : *convert_depth_to_float_frag;
|
||||
const std::array stages = MakeStages(*full_screen_vert, frag_shader);
|
||||
const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device);
|
||||
pipeline = device.GetLogical().CreateGraphicsPipeline(VkGraphicsPipelineCreateInfo{
|
||||
pipeline = device.GetLogical().CreateGraphicsPipeline({
|
||||
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
@@ -50,7 +50,6 @@ void RefreshXfbState(VideoCommon::TransformFeedbackState& state, const Maxwell&
|
||||
});
|
||||
state.varyings = regs.stream_out_layout;
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFeatures& features) {
|
||||
@@ -61,9 +60,9 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFe
|
||||
extended_dynamic_state.Assign(features.has_extended_dynamic_state ? 1 : 0);
|
||||
extended_dynamic_state_2.Assign(features.has_extended_dynamic_state_2 ? 1 : 0);
|
||||
extended_dynamic_state_2_logic_op.Assign(features.has_extended_dynamic_state_2_logic_op ? 1 : 0);
|
||||
reserved_dynamic_state_3_blend.Assign(0);
|
||||
reserved_dynamic_state_3_enables.Assign(0);
|
||||
reserved_bit_5.Assign(0);
|
||||
extended_dynamic_state_3_blend.Assign(features.has_extended_dynamic_state_3_blend ? 1 : 0);
|
||||
extended_dynamic_state_3_enables.Assign(features.has_extended_dynamic_state_3_enables ? 1 : 0);
|
||||
dynamic_vertex_input.Assign(features.has_dynamic_vertex_input ? 1 : 0);
|
||||
xfb_enabled.Assign(regs.transform_feedback_enabled != 0);
|
||||
ndc_minus_one_to_one.Assign(regs.depth_mode == Maxwell::DepthMode::MinusOneToOne ? 1 : 0);
|
||||
polygon_mode.Assign(PackPolygonMode(regs.polygon_mode_front));
|
||||
@@ -104,22 +103,43 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFe
|
||||
point_size = std::bit_cast<u32>(regs.point_size);
|
||||
|
||||
if (maxwell3d.dirty.flags[Dirty::VertexInput]) {
|
||||
maxwell3d.dirty.flags[Dirty::VertexInput] = false;
|
||||
enabled_divisors = 0;
|
||||
for (size_t index = 0; index < Maxwell::NumVertexArrays; ++index) {
|
||||
const bool is_enabled = regs.vertex_stream_instances.IsInstancingEnabled(index);
|
||||
binding_divisors[index] = is_enabled ? regs.vertex_streams[index].frequency : 0;
|
||||
enabled_divisors |= (is_enabled ? u64{1} : 0) << index;
|
||||
}
|
||||
for (size_t index = 0; index < Maxwell::NumVertexAttributes; ++index) {
|
||||
const auto& input = regs.vertex_attrib_format[index];
|
||||
auto& attribute = attributes[index];
|
||||
attribute.raw = 0;
|
||||
attribute.enabled.Assign(input.constant ? 0 : 1);
|
||||
attribute.buffer.Assign(input.buffer);
|
||||
attribute.offset.Assign(input.offset);
|
||||
attribute.type.Assign(static_cast<u32>(input.type.Value()));
|
||||
attribute.size.Assign(static_cast<u32>(input.size.Value()));
|
||||
if (features.has_dynamic_vertex_input) {
|
||||
// Dirty flag will be reset by the command buffer update
|
||||
static constexpr std::array LUT{
|
||||
0u, // Invalid
|
||||
1u, // SignedNorm
|
||||
1u, // UnsignedNorm
|
||||
2u, // SignedInt
|
||||
3u, // UnsignedInt
|
||||
1u, // UnsignedScaled
|
||||
1u, // SignedScaled
|
||||
1u, // Float
|
||||
};
|
||||
const auto& attrs = regs.vertex_attrib_format;
|
||||
attribute_types = 0;
|
||||
for (size_t i = 0; i < Maxwell::NumVertexAttributes; ++i) {
|
||||
const u32 mask = attrs[i].constant != 0 ? 0 : 3;
|
||||
const u32 type = LUT[static_cast<size_t>(attrs[i].type.Value())];
|
||||
attribute_types |= static_cast<u64>(type & mask) << (i * 2);
|
||||
}
|
||||
} else {
|
||||
maxwell3d.dirty.flags[Dirty::VertexInput] = false;
|
||||
enabled_divisors = 0;
|
||||
for (size_t index = 0; index < Maxwell::NumVertexArrays; ++index) {
|
||||
const bool is_enabled = regs.vertex_stream_instances.IsInstancingEnabled(index);
|
||||
binding_divisors[index] = is_enabled ? regs.vertex_streams[index].frequency : 0;
|
||||
enabled_divisors |= (is_enabled ? u64{1} : 0) << index;
|
||||
}
|
||||
for (size_t index = 0; index < Maxwell::NumVertexAttributes; ++index) {
|
||||
const auto& input = regs.vertex_attrib_format[index];
|
||||
auto& attribute = attributes[index];
|
||||
attribute.raw = 0;
|
||||
attribute.enabled.Assign(input.constant ? 0 : 1);
|
||||
attribute.buffer.Assign(input.buffer);
|
||||
attribute.offset.Assign(input.offset);
|
||||
attribute.type.Assign(static_cast<u32>(input.type.Value()));
|
||||
attribute.size.Assign(static_cast<u32>(input.size.Value()));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (maxwell3d.dirty.flags[Dirty::ViewportSwizzles]) {
|
||||
@@ -131,18 +151,26 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFe
|
||||
}
|
||||
dynamic_state.raw1 = 0;
|
||||
dynamic_state.raw2 = 0;
|
||||
dynamic_state.Refresh(regs);
|
||||
std::ranges::transform(regs.vertex_streams, vertex_strides.begin(), [](const auto& array) {
|
||||
return static_cast<u16>(array.stride.Value());
|
||||
});
|
||||
dynamic_state.Refresh2(regs, topology_, false);
|
||||
if (maxwell3d.dirty.flags[Dirty::Blending]) {
|
||||
maxwell3d.dirty.flags[Dirty::Blending] = false;
|
||||
for (size_t index = 0; index < attachments.size(); ++index) {
|
||||
attachments[index].Refresh(regs, index);
|
||||
if (!extended_dynamic_state) {
|
||||
dynamic_state.Refresh(regs);
|
||||
std::ranges::transform(regs.vertex_streams, vertex_strides.begin(), [](const auto& array) {
|
||||
return static_cast<u16>(array.stride.Value());
|
||||
});
|
||||
}
|
||||
if (!extended_dynamic_state_2_logic_op) {
|
||||
dynamic_state.Refresh2(regs, topology_, extended_dynamic_state_2);
|
||||
}
|
||||
if (!extended_dynamic_state_3_blend) {
|
||||
if (maxwell3d.dirty.flags[Dirty::Blending]) {
|
||||
maxwell3d.dirty.flags[Dirty::Blending] = false;
|
||||
for (size_t index = 0; index < attachments.size(); ++index) {
|
||||
attachments[index].Refresh(regs, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
dynamic_state.Refresh3(regs);
|
||||
if (!extended_dynamic_state_3_enables) {
|
||||
dynamic_state.Refresh3(regs);
|
||||
}
|
||||
if (xfb_enabled) {
|
||||
RefreshXfbState(xfb_state, regs);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
@@ -25,6 +25,9 @@ struct DynamicFeatures {
|
||||
bool has_extended_dynamic_state_2;
|
||||
bool has_extended_dynamic_state_2_logic_op;
|
||||
bool has_extended_dynamic_state_2_patch_control_points;
|
||||
bool has_extended_dynamic_state_3_blend;
|
||||
bool has_extended_dynamic_state_3_enables;
|
||||
bool has_dynamic_vertex_input;
|
||||
};
|
||||
|
||||
struct FixedPipelineState {
|
||||
@@ -188,9 +191,9 @@ struct FixedPipelineState {
|
||||
BitField<0, 1, u32> extended_dynamic_state;
|
||||
BitField<1, 1, u32> extended_dynamic_state_2;
|
||||
BitField<2, 1, u32> extended_dynamic_state_2_logic_op;
|
||||
BitField<3, 1, u32> reserved_dynamic_state_3_blend;
|
||||
BitField<4, 1, u32> reserved_dynamic_state_3_enables;
|
||||
BitField<5, 1, u32> reserved_bit_5;
|
||||
BitField<3, 1, u32> extended_dynamic_state_3_blend;
|
||||
BitField<4, 1, u32> extended_dynamic_state_3_enables;
|
||||
BitField<5, 1, u32> dynamic_vertex_input;
|
||||
BitField<6, 1, u32> xfb_enabled;
|
||||
BitField<7, 1, u32> ndc_minus_one_to_one;
|
||||
BitField<8, 2, u32> polygon_mode;
|
||||
@@ -222,7 +225,10 @@ struct FixedPipelineState {
|
||||
u32 point_size;
|
||||
|
||||
std::array<u16, Maxwell::NumViewports> viewport_swizzles;
|
||||
u64 enabled_divisors;
|
||||
union {
|
||||
u64 attribute_types; // Used with VK_EXT_vertex_input_dynamic_state
|
||||
u64 enabled_divisors;
|
||||
};
|
||||
|
||||
DynamicState dynamic_state;
|
||||
std::array<BlendingAttachment, Maxwell::NumRenderTargets> attachments;
|
||||
@@ -254,11 +260,25 @@ struct FixedPipelineState {
|
||||
// When transform feedback is enabled, use the whole struct
|
||||
return sizeof(*this);
|
||||
}
|
||||
// Always include the cached dynamic-state payload in the key. Some members of
|
||||
// `dynamic_state` still feed static pipeline state even when EDS is enabled,
|
||||
// and excluding the whole block causes incorrect pipeline reuse.
|
||||
if (dynamic_vertex_input && extended_dynamic_state_3_blend) {
|
||||
// Exclude dynamic state and attributes
|
||||
return offsetof(FixedPipelineState, dynamic_state);
|
||||
}
|
||||
if (dynamic_vertex_input) {
|
||||
// Exclude dynamic state
|
||||
return offsetof(FixedPipelineState, attributes);
|
||||
}
|
||||
if (extended_dynamic_state) {
|
||||
// Exclude dynamic state
|
||||
return offsetof(FixedPipelineState, vertex_strides);
|
||||
}
|
||||
// Default
|
||||
return offsetof(FixedPipelineState, xfb_state);
|
||||
}
|
||||
|
||||
u32 DynamicAttributeType(size_t index) const noexcept {
|
||||
return (attribute_types >> (index * 2)) & 0b11;
|
||||
}
|
||||
};
|
||||
static_assert(std::has_unique_object_representations_v<FixedPipelineState>);
|
||||
static_assert(std::is_trivially_copyable_v<FixedPipelineState>);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
@@ -315,40 +315,15 @@ VkShaderStageFlagBits ShaderStage(Shader::Stage stage) {
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]] static bool IsUnsupportedAreaTopology(Maxwell::PrimitiveTopology topology) {
|
||||
switch (topology) {
|
||||
case Maxwell::PrimitiveTopology::Quads:
|
||||
case Maxwell::PrimitiveTopology::QuadStrip:
|
||||
case Maxwell::PrimitiveTopology::Polygon:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
VkPrimitiveTopology PrimitiveTopology([[maybe_unused]] const Device& device,
|
||||
Maxwell::PrimitiveTopology topology,
|
||||
Maxwell::PolygonMode polygon_mode,
|
||||
bool allow_polygon_mode_emulation) {
|
||||
if (allow_polygon_mode_emulation && IsUnsupportedAreaTopology(topology)) {
|
||||
switch (polygon_mode) {
|
||||
case Maxwell::PolygonMode::Point:
|
||||
return VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
|
||||
case Maxwell::PolygonMode::Line:
|
||||
return topology == Maxwell::PrimitiveTopology::Polygon
|
||||
? VK_PRIMITIVE_TOPOLOGY_LINE_STRIP
|
||||
: VK_PRIMITIVE_TOPOLOGY_LINE_LIST;
|
||||
case Maxwell::PolygonMode::Fill:
|
||||
break;
|
||||
}
|
||||
}
|
||||
Maxwell::PrimitiveTopology topology) {
|
||||
switch (topology) {
|
||||
case Maxwell::PrimitiveTopology::Points:
|
||||
return VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
|
||||
case Maxwell::PrimitiveTopology::Lines:
|
||||
return VK_PRIMITIVE_TOPOLOGY_LINE_LIST;
|
||||
case Maxwell::PrimitiveTopology::LineLoop:
|
||||
return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP;
|
||||
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||
case Maxwell::PrimitiveTopology::LineStrip:
|
||||
return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP;
|
||||
case Maxwell::PrimitiveTopology::Triangles:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
@@ -49,10 +49,7 @@ struct FormatInfo {
|
||||
|
||||
VkShaderStageFlagBits ShaderStage(Shader::Stage stage);
|
||||
|
||||
VkPrimitiveTopology PrimitiveTopology(const Device& device, Maxwell::PrimitiveTopology topology,
|
||||
Maxwell::PolygonMode polygon_mode =
|
||||
Maxwell::PolygonMode::Fill,
|
||||
bool allow_polygon_mode_emulation = false);
|
||||
VkPrimitiveTopology PrimitiveTopology(const Device& device, Maxwell::PrimitiveTopology topology);
|
||||
|
||||
VkFormat VertexFormat(const Device& device, Maxwell::VertexAttribute::Type type,
|
||||
Maxwell::VertexAttribute::Size size);
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -15,7 +12,6 @@
|
||||
#include "shader_recompiler/shader_info.h"
|
||||
#include "video_core/renderer_vulkan/vk_texture_cache.h"
|
||||
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
|
||||
#include "video_core/surface.h"
|
||||
#include "video_core/texture_cache/types.h"
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
|
||||
@@ -31,58 +27,23 @@ public:
|
||||
return device->IsKhrPushDescriptorSupported() &&
|
||||
num_descriptors <= device->MaxPushDescriptors();
|
||||
}
|
||||
|
||||
|
||||
// TODO(crueter): utilize layout binding flags
|
||||
vk::DescriptorSetLayout CreateDescriptorSetLayout(bool use_push_descriptor) const {
|
||||
if (bindings.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
variable_descriptor_count = 0;
|
||||
binding_flags.clear();
|
||||
|
||||
VkDescriptorSetLayoutBindingFlagsCreateInfo binding_flags_ci{
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.bindingCount = 0,
|
||||
.pBindingFlags = nullptr,
|
||||
};
|
||||
|
||||
const bool use_descriptor_indexing =
|
||||
!use_push_descriptor && device->isExtDescriptorIndexingSupported();
|
||||
const void* layout_next = nullptr;
|
||||
if (use_descriptor_indexing) {
|
||||
binding_flags.assign(bindings.size(), 0);
|
||||
for (size_t i = 0; i < bindings.size(); ++i) {
|
||||
if (bindings[i].descriptorCount > 1) {
|
||||
binding_flags[i] |= VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT;
|
||||
}
|
||||
}
|
||||
|
||||
if (bindings.back().descriptorCount > 1) {
|
||||
binding_flags.back() |= VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT;
|
||||
variable_descriptor_count = bindings.back().descriptorCount;
|
||||
}
|
||||
|
||||
binding_flags_ci.bindingCount = static_cast<u32>(binding_flags.size());
|
||||
binding_flags_ci.pBindingFlags = binding_flags.data();
|
||||
layout_next = &binding_flags_ci;
|
||||
}
|
||||
|
||||
const VkDescriptorSetLayoutCreateFlags flags =
|
||||
use_push_descriptor ? VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR : 0;
|
||||
return device->GetLogical().CreateDescriptorSetLayout({
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
|
||||
.pNext = layout_next,
|
||||
.pNext = nullptr,
|
||||
.flags = flags,
|
||||
.bindingCount = static_cast<u32>(bindings.size()),
|
||||
.pBindings = bindings.data(),
|
||||
});
|
||||
}
|
||||
|
||||
u32 VariableDescriptorCount() const noexcept {
|
||||
return variable_descriptor_count;
|
||||
}
|
||||
|
||||
vk::DescriptorUpdateTemplate CreateTemplate(VkDescriptorSetLayout descriptor_set_layout,
|
||||
VkPipelineLayout pipeline_layout,
|
||||
bool use_push_descriptor) const {
|
||||
@@ -100,8 +61,7 @@ public:
|
||||
.pDescriptorUpdateEntries = entries.data(),
|
||||
.templateType = type,
|
||||
.descriptorSetLayout = descriptor_set_layout,
|
||||
.pipelineBindPoint =
|
||||
is_compute ? VK_PIPELINE_BIND_POINT_COMPUTE : VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
.pipelineLayout = pipeline_layout,
|
||||
.set = 0,
|
||||
});
|
||||
@@ -162,7 +122,7 @@ private:
|
||||
});
|
||||
++binding;
|
||||
num_descriptors += descriptors[i].count;
|
||||
offset += sizeof(DescriptorUpdateEntry) * descriptors[i].count;
|
||||
offset += sizeof(DescriptorUpdateEntry);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,10 +130,8 @@ private:
|
||||
bool is_compute{};
|
||||
boost::container::small_vector<VkDescriptorSetLayoutBinding, 32> bindings;
|
||||
boost::container::small_vector<VkDescriptorUpdateTemplateEntry, 32> entries;
|
||||
mutable boost::container::small_vector<VkDescriptorBindingFlags, 32> binding_flags;
|
||||
u32 binding{};
|
||||
u32 num_descriptors{};
|
||||
mutable u32 variable_descriptor_count{};
|
||||
size_t offset{};
|
||||
};
|
||||
|
||||
@@ -233,16 +191,10 @@ inline void PushImageDescriptors(TextureCache& texture_cache,
|
||||
ImageView& image_view{texture_cache.GetImageView(image_view_id)};
|
||||
const VkImageView vk_image_view{image_view.Handle(desc.type)};
|
||||
const Sampler& sampler{texture_cache.GetSampler(sampler_id)};
|
||||
const auto surface_type{VideoCore::Surface::GetFormatType(image_view.format)};
|
||||
const bool allow_depth_compare =
|
||||
desc.is_depth && (surface_type == VideoCore::Surface::SurfaceType::Depth ||
|
||||
surface_type == VideoCore::Surface::SurfaceType::DepthStencil);
|
||||
const bool use_fallback_sampler{sampler.HasAddedAnisotropy() &&
|
||||
!image_view.SupportsAnisotropy()};
|
||||
const VkSampler vk_sampler{use_fallback_sampler
|
||||
? sampler.HandleWithDefaultAnisotropy(
|
||||
allow_depth_compare)
|
||||
: sampler.Handle(allow_depth_compare)};
|
||||
const VkSampler vk_sampler{use_fallback_sampler ? sampler.HandleWithDefaultAnisotropy()
|
||||
: sampler.Handle()};
|
||||
guest_descriptor_queue.AddSampledImage(vk_image_view, vk_sampler);
|
||||
rescaling.PushTexture(texture_cache.IsRescaling(image_view));
|
||||
}
|
||||
|
||||
@@ -206,7 +206,6 @@ VkImageView FSR::Draw(Scheduler& scheduler, size_t image_index, VkImage source_i
|
||||
|
||||
TransitionImageLayout(cmdbuf, rcas_image, VK_IMAGE_LAYOUT_GENERAL);
|
||||
});
|
||||
scheduler.InvalidateState();
|
||||
|
||||
return *images.image_views[Rcas];
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -143,7 +140,6 @@ void FXAA::Draw(Scheduler& scheduler, size_t image_index, VkImage* inout_image,
|
||||
cmdbuf.EndRenderPass();
|
||||
TransitionImageLayout(cmdbuf, output_image, VK_IMAGE_LAYOUT_GENERAL);
|
||||
});
|
||||
scheduler.InvalidateState();
|
||||
|
||||
*inout_image = *image.image;
|
||||
*inout_image_view = *image.image_view;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
@@ -272,7 +272,6 @@ void SMAA::Draw(Scheduler& scheduler, size_t image_index, VkImage* inout_image,
|
||||
cmdbuf.EndRenderPass();
|
||||
TransitionImageLayout(cmdbuf, output_image, VK_IMAGE_LAYOUT_GENERAL);
|
||||
});
|
||||
scheduler.InvalidateState();
|
||||
|
||||
*inout_image = *images.images[Output];
|
||||
*inout_image_view = *images.image_views[Output];
|
||||
|
||||
@@ -461,9 +461,6 @@ static vk::Pipeline CreateWrappedPipelineImpl(
|
||||
constexpr std::array dynamic_states{
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR,
|
||||
VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK,
|
||||
VK_DYNAMIC_STATE_STENCIL_WRITE_MASK,
|
||||
VK_DYNAMIC_STATE_STENCIL_REFERENCE,
|
||||
};
|
||||
|
||||
const VkPipelineDynamicStateCreateInfo dynamic_state_ci{
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -104,7 +101,6 @@ void WindowAdaptPass::Draw(RasterizerVulkan& rasterizer, Scheduler& scheduler, s
|
||||
|
||||
cmdbuf.EndRenderPass();
|
||||
});
|
||||
scheduler.InvalidateState();
|
||||
}
|
||||
|
||||
VkDescriptorSetLayout WindowAdaptPass::GetDescriptorSetLayout() {
|
||||
|
||||
@@ -137,8 +137,14 @@ try
|
||||
memory_allocator,
|
||||
scheduler,
|
||||
swapchain,
|
||||
#ifdef ANDROID
|
||||
surface)
|
||||
, blit_swapchain(device_memory,
|
||||
,
|
||||
#else
|
||||
*surface)
|
||||
,
|
||||
#endif
|
||||
blit_swapchain(device_memory,
|
||||
device,
|
||||
memory_allocator,
|
||||
present_manager,
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
#include "video_core/buffer_cache/buffer_cache_base.h"
|
||||
#include "video_core/renderer_vulkan/vk_buffer_cache.h"
|
||||
|
||||
#include "video_core/renderer_vulkan/maxwell_to_vk.h"
|
||||
@@ -109,14 +108,6 @@ VkBufferView Buffer::View(u32 offset, u32 size, VideoCore::Surface::PixelFormat
|
||||
// Null buffer not supported, adjust offset and size
|
||||
offset = 0;
|
||||
size = 0;
|
||||
} else {
|
||||
// Align offset down to minTexelBufferOffsetAlignment
|
||||
const u32 alignment = static_cast<u32>(device->GetMinTexelBufferOffsetAlignment());
|
||||
if (alignment > 1) {
|
||||
const u32 aligned_offset = offset & ~(alignment - 1);
|
||||
size += offset - aligned_offset;
|
||||
offset = aligned_offset;
|
||||
}
|
||||
}
|
||||
const auto it{std::ranges::find_if(views, [offset, size, format](const BufferView& view) {
|
||||
return offset == view.offset && size == view.size && format == view.format;
|
||||
@@ -161,11 +152,10 @@ public:
|
||||
index_type = IndexTypeFromNumElements(device, num_indices);
|
||||
|
||||
const u32 num_quads = GetQuadsNum(num_indices);
|
||||
const u32 num_generated_indices = num_quads * GetIndicesPerPrimitive();
|
||||
const u32 num_triangle_indices = num_quads * 6;
|
||||
const u32 num_first_offset_copies = 4;
|
||||
const size_t bytes_per_index = BytesPerIndex(index_type);
|
||||
const size_t size_bytes =
|
||||
num_generated_indices * bytes_per_index * num_first_offset_copies;
|
||||
const size_t size_bytes = num_triangle_indices * bytes_per_index * num_first_offset_copies;
|
||||
const VkBufferCreateInfo buffer_ci = {
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
@@ -190,12 +180,12 @@ public:
|
||||
}();
|
||||
|
||||
u8* staging_data = host_visible ? buffer.Mapped().data() : staging.mapped_span.data();
|
||||
const size_t primitive_size = bytes_per_index * GetIndicesPerPrimitive();
|
||||
const size_t quad_size = bytes_per_index * 6;
|
||||
|
||||
for (u32 first = 0; first < num_first_offset_copies; ++first) {
|
||||
for (u32 quad = 0; quad < num_quads; ++quad) {
|
||||
MakeAndUpdateIndices(staging_data, primitive_size, quad, first);
|
||||
staging_data += primitive_size;
|
||||
MakeAndUpdateIndices(staging_data, quad_size, quad, first);
|
||||
staging_data += quad_size;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,20 +222,15 @@ public:
|
||||
const VkIndexType index_type_ = index_type;
|
||||
const size_t sub_first_offset = static_cast<size_t>(first % 4) * GetQuadsNum(num_indices);
|
||||
const size_t offset =
|
||||
(sub_first_offset + GetFirstOffsetQuads(first)) *
|
||||
static_cast<size_t>(GetIndicesPerPrimitive()) * BytesPerIndex(index_type);
|
||||
(sub_first_offset + GetQuadsNum(first)) * 6ULL * BytesPerIndex(index_type);
|
||||
scheduler.Record([buffer_ = *buffer, index_type_, offset](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.BindIndexBuffer(buffer_, offset, index_type_);
|
||||
});
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual u32 GetIndicesPerPrimitive() const = 0;
|
||||
|
||||
virtual u32 GetQuadsNum(u32 num_indices) const = 0;
|
||||
|
||||
virtual u32 GetFirstOffsetQuads(u32 first) const = 0;
|
||||
|
||||
virtual void MakeAndUpdateIndices(u8* staging_data, size_t quad_size, u32 quad, u32 first) = 0;
|
||||
|
||||
const Device& device;
|
||||
@@ -268,18 +253,10 @@ public:
|
||||
~QuadArrayIndexBuffer() = default;
|
||||
|
||||
private:
|
||||
u32 GetIndicesPerPrimitive() const override {
|
||||
return 6;
|
||||
}
|
||||
|
||||
u32 GetQuadsNum(u32 num_indices_) const override {
|
||||
return num_indices_ / 4;
|
||||
}
|
||||
|
||||
u32 GetFirstOffsetQuads(u32 first) const override {
|
||||
return first / 4;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static std::array<T, 6> MakeIndices(u32 quad, u32 first) {
|
||||
std::array<T, 6> indices{0, 1, 2, 0, 2, 3};
|
||||
@@ -316,18 +293,10 @@ public:
|
||||
~QuadStripIndexBuffer() = default;
|
||||
|
||||
private:
|
||||
u32 GetIndicesPerPrimitive() const override {
|
||||
return 6;
|
||||
}
|
||||
|
||||
u32 GetQuadsNum(u32 num_indices_) const override {
|
||||
return num_indices_ >= 4 ? (num_indices_ - 2) / 2 : 0;
|
||||
}
|
||||
|
||||
u32 GetFirstOffsetQuads(u32 first) const override {
|
||||
return (first / 4) * 2;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static std::array<T, 6> MakeIndices(u32 quad, u32 first) {
|
||||
std::array<T, 6> indices{0, 3, 1, 0, 2, 3};
|
||||
@@ -355,104 +324,6 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
class QuadArrayLineIndexBuffer : public QuadIndexBuffer {
|
||||
public:
|
||||
QuadArrayLineIndexBuffer(const Device& device_, MemoryAllocator& memory_allocator_,
|
||||
Scheduler& scheduler_, StagingBufferPool& staging_pool_)
|
||||
: QuadIndexBuffer(device_, memory_allocator_, scheduler_, staging_pool_) {}
|
||||
|
||||
~QuadArrayLineIndexBuffer() = default;
|
||||
|
||||
private:
|
||||
u32 GetIndicesPerPrimitive() const override {
|
||||
return 8;
|
||||
}
|
||||
|
||||
u32 GetQuadsNum(u32 num_indices_) const override {
|
||||
return num_indices_ / 4;
|
||||
}
|
||||
|
||||
u32 GetFirstOffsetQuads(u32 first) const override {
|
||||
return first / 4;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static std::array<T, 8> MakeIndices(u32 quad, u32 first) {
|
||||
std::array<T, 8> indices{0, 1, 1, 2, 2, 3, 3, 0};
|
||||
for (T& index : indices) {
|
||||
index = static_cast<T>(first + index + quad * 4);
|
||||
}
|
||||
return indices;
|
||||
}
|
||||
|
||||
void MakeAndUpdateIndices(u8* staging_data, size_t primitive_size, u32 quad,
|
||||
u32 first) override {
|
||||
switch (index_type) {
|
||||
case VK_INDEX_TYPE_UINT8_EXT:
|
||||
std::memcpy(staging_data, MakeIndices<u8>(quad, first).data(), primitive_size);
|
||||
break;
|
||||
case VK_INDEX_TYPE_UINT16:
|
||||
std::memcpy(staging_data, MakeIndices<u16>(quad, first).data(), primitive_size);
|
||||
break;
|
||||
case VK_INDEX_TYPE_UINT32:
|
||||
std::memcpy(staging_data, MakeIndices<u32>(quad, first).data(), primitive_size);
|
||||
break;
|
||||
default:
|
||||
ASSERT(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class QuadStripLineIndexBuffer : public QuadIndexBuffer {
|
||||
public:
|
||||
QuadStripLineIndexBuffer(const Device& device_, MemoryAllocator& memory_allocator_,
|
||||
Scheduler& scheduler_, StagingBufferPool& staging_pool_)
|
||||
: QuadIndexBuffer(device_, memory_allocator_, scheduler_, staging_pool_) {}
|
||||
|
||||
~QuadStripLineIndexBuffer() = default;
|
||||
|
||||
private:
|
||||
u32 GetIndicesPerPrimitive() const override {
|
||||
return 8;
|
||||
}
|
||||
|
||||
u32 GetQuadsNum(u32 num_indices_) const override {
|
||||
return num_indices_ >= 4 ? (num_indices_ - 2) / 2 : 0;
|
||||
}
|
||||
|
||||
u32 GetFirstOffsetQuads(u32 first) const override {
|
||||
return (first / 4) * 2;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static std::array<T, 8> MakeIndices(u32 quad, u32 first) {
|
||||
std::array<T, 8> indices{0, 1, 1, 3, 3, 2, 2, 0};
|
||||
for (T& index : indices) {
|
||||
index = static_cast<T>(first + index + quad * 2);
|
||||
}
|
||||
return indices;
|
||||
}
|
||||
|
||||
void MakeAndUpdateIndices(u8* staging_data, size_t primitive_size, u32 quad,
|
||||
u32 first) override {
|
||||
switch (index_type) {
|
||||
case VK_INDEX_TYPE_UINT8_EXT:
|
||||
std::memcpy(staging_data, MakeIndices<u8>(quad, first).data(), primitive_size);
|
||||
break;
|
||||
case VK_INDEX_TYPE_UINT16:
|
||||
std::memcpy(staging_data, MakeIndices<u16>(quad, first).data(), primitive_size);
|
||||
break;
|
||||
case VK_INDEX_TYPE_UINT32:
|
||||
std::memcpy(staging_data, MakeIndices<u32>(quad, first).data(), primitive_size);
|
||||
break;
|
||||
default:
|
||||
ASSERT(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
BufferCacheRuntime::BufferCacheRuntime(const Device& device_, MemoryAllocator& memory_allocator_,
|
||||
Scheduler& scheduler_, StagingBufferPool& staging_pool_,
|
||||
GuestDescriptorQueue& guest_descriptor_queue_,
|
||||
@@ -467,7 +338,7 @@ BufferCacheRuntime::BufferCacheRuntime(const Device& device_, MemoryAllocator& m
|
||||
driver_id == VK_DRIVER_ID_ARM_PROPRIETARY;
|
||||
if (limit_dynamic_storage_buffers) {
|
||||
max_dynamic_storage_buffers = device.GetMaxDescriptorSetStorageBuffersDynamic();
|
||||
}
|
||||
}
|
||||
if (device.SupportsUint8Indices()) {
|
||||
uint8_pass = std::make_unique<Uint8Pass>(device, scheduler, descriptor_pool, staging_pool,
|
||||
compute_pass_descriptor_queue);
|
||||
@@ -476,10 +347,6 @@ BufferCacheRuntime::BufferCacheRuntime(const Device& device_, MemoryAllocator& m
|
||||
scheduler_, staging_pool_);
|
||||
quad_strip_index_buffer = std::make_shared<QuadStripIndexBuffer>(device_, memory_allocator_,
|
||||
scheduler_, staging_pool_);
|
||||
quad_array_line_index_buffer = std::make_shared<QuadArrayLineIndexBuffer>(
|
||||
device_, memory_allocator_, scheduler_, staging_pool_);
|
||||
quad_strip_line_index_buffer = std::make_shared<QuadStripLineIndexBuffer>(
|
||||
device_, memory_allocator_, scheduler_, staging_pool_);
|
||||
}
|
||||
|
||||
StagingBufferRef BufferCacheRuntime::UploadStagingBuffer(size_t size) {
|
||||
@@ -574,16 +441,8 @@ void BufferCacheRuntime::CopyBuffer(VkBuffer dst_buffer, VkBuffer src_buffer,
|
||||
}
|
||||
cmdbuf.CopyBuffer(src_buffer, dst_buffer, VideoCommon::FixSmallVectorADL(vk_copies));
|
||||
if (barrier) {
|
||||
// Buffer reads can go to vertex input, shaders, or compute
|
||||
const VkPipelineStageFlags dst_stages =
|
||||
VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT |
|
||||
VK_PIPELINE_STAGE_VERTEX_INPUT_BIT |
|
||||
VK_PIPELINE_STAGE_VERTEX_SHADER_BIT |
|
||||
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
dst_stages, 0, WRITE_BARRIER);
|
||||
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, WRITE_BARRIER);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -611,14 +470,7 @@ void BufferCacheRuntime::PostCopyBarrier() {
|
||||
};
|
||||
scheduler.RequestOutsideRenderPassOperationContext();
|
||||
scheduler.Record([](vk::CommandBuffer cmdbuf) {
|
||||
const VkPipelineStageFlags dst_stages =
|
||||
VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT |
|
||||
VK_PIPELINE_STAGE_VERTEX_INPUT_BIT |
|
||||
VK_PIPELINE_STAGE_VERTEX_SHADER_BIT |
|
||||
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, dst_stages,
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||
0, WRITE_BARRIER);
|
||||
});
|
||||
}
|
||||
@@ -645,37 +497,17 @@ void BufferCacheRuntime::ClearBuffer(VkBuffer dest_buffer, u32 offset, size_t si
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
0, READ_BARRIER);
|
||||
cmdbuf.FillBuffer(dest_buffer, offset, size, value);
|
||||
// Buffer reads can go to vertex input, shaders, or compute
|
||||
const VkPipelineStageFlags dst_stages_clear =
|
||||
VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT |
|
||||
VK_PIPELINE_STAGE_VERTEX_INPUT_BIT |
|
||||
VK_PIPELINE_STAGE_VERTEX_SHADER_BIT |
|
||||
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, dst_stages_clear,
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||
0, WRITE_BARRIER);
|
||||
});
|
||||
}
|
||||
|
||||
void BufferCacheRuntime::BindIndexBuffer(PrimitiveTopology topology, PolygonMode polygon_mode,
|
||||
IndexFormat index_format, u32 base_vertex,
|
||||
u32 num_indices, VkBuffer buffer, u32 offset,
|
||||
[[maybe_unused]] u32 size) {
|
||||
void BufferCacheRuntime::BindIndexBuffer(PrimitiveTopology topology, IndexFormat index_format,
|
||||
u32 base_vertex, u32 num_indices, VkBuffer buffer,
|
||||
u32 offset, [[maybe_unused]] u32 size) {
|
||||
VkIndexType vk_index_type = MaxwellToVK::IndexFormat(index_format);
|
||||
VkDeviceSize vk_offset = offset;
|
||||
VkBuffer vk_buffer = buffer;
|
||||
if (polygon_mode == PolygonMode::Line &&
|
||||
(topology == PrimitiveTopology::Quads || topology == PrimitiveTopology::QuadStrip)) {
|
||||
if (topology == PrimitiveTopology::Quads) {
|
||||
quad_array_line_index_buffer->UpdateBuffer(num_indices);
|
||||
quad_array_line_index_buffer->BindBuffer(base_vertex);
|
||||
} else {
|
||||
quad_strip_line_index_buffer->UpdateBuffer(num_indices);
|
||||
quad_strip_line_index_buffer->BindBuffer(base_vertex);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (topology == PrimitiveTopology::Quads || topology == PrimitiveTopology::QuadStrip) {
|
||||
vk_index_type = VK_INDEX_TYPE_UINT32;
|
||||
std::tie(vk_buffer, vk_offset) =
|
||||
@@ -701,8 +533,7 @@ void BufferCacheRuntime::BindIndexBuffer(PrimitiveTopology topology, PolygonMode
|
||||
});
|
||||
}
|
||||
|
||||
void BufferCacheRuntime::BindQuadIndexBuffer(PrimitiveTopology topology, PolygonMode polygon_mode,
|
||||
u32 first, u32 count) {
|
||||
void BufferCacheRuntime::BindQuadIndexBuffer(PrimitiveTopology topology, u32 first, u32 count) {
|
||||
if (count == 0) {
|
||||
ReserveNullBuffer();
|
||||
scheduler.Record([this](vk::CommandBuffer cmdbuf) {
|
||||
@@ -711,17 +542,6 @@ void BufferCacheRuntime::BindQuadIndexBuffer(PrimitiveTopology topology, Polygon
|
||||
return;
|
||||
}
|
||||
|
||||
if (polygon_mode == PolygonMode::Line) {
|
||||
if (topology == PrimitiveTopology::Quads) {
|
||||
quad_array_line_index_buffer->UpdateBuffer(first + count);
|
||||
quad_array_line_index_buffer->BindBuffer(first);
|
||||
} else if (topology == PrimitiveTopology::QuadStrip) {
|
||||
quad_strip_line_index_buffer->UpdateBuffer(first + count);
|
||||
quad_strip_line_index_buffer->BindBuffer(first);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (topology == PrimitiveTopology::Quads) {
|
||||
quad_array_index_buffer->UpdateBuffer(first + count);
|
||||
quad_array_index_buffer->BindBuffer(first);
|
||||
@@ -735,29 +555,38 @@ void BufferCacheRuntime::BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset
|
||||
if (index >= device.GetMaxVertexInputBindings()) {
|
||||
return;
|
||||
}
|
||||
if (!device.HasNullDescriptor() && buffer == VK_NULL_HANDLE) {
|
||||
ReserveNullBuffer();
|
||||
buffer = *null_buffer;
|
||||
offset = 0;
|
||||
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);
|
||||
});
|
||||
}
|
||||
scheduler.Record([index, buffer, offset](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.BindVertexBuffer(index, buffer, offset);
|
||||
});
|
||||
}
|
||||
|
||||
void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings) {
|
||||
boost::container::static_vector<VkBuffer, VideoCommon::NUM_VERTEX_BUFFERS> buffer_handles(bindings.buffers.size());
|
||||
for (u32 i = 0; i < bindings.buffers.size(); ++i) {
|
||||
auto handle = bindings.buffers[i]->Handle();
|
||||
boost::container::small_vector<VkBuffer, 32> buffer_handles;
|
||||
for (u32 index = 0; index < bindings.buffers.size(); ++index) {
|
||||
auto handle = bindings.buffers[index]->Handle();
|
||||
if (handle == VK_NULL_HANDLE) {
|
||||
bindings.offsets[i] = 0;
|
||||
bindings.sizes[i] = VK_WHOLE_SIZE;
|
||||
bindings.offsets[index] = 0;
|
||||
bindings.sizes[index] = VK_WHOLE_SIZE;
|
||||
if (!device.HasNullDescriptor()) {
|
||||
ReserveNullBuffer();
|
||||
handle = *null_buffer;
|
||||
}
|
||||
}
|
||||
buffer_handles[i] = handle;
|
||||
buffer_handles.push_back(handle);
|
||||
}
|
||||
const u32 device_max = device.GetMaxVertexInputBindings();
|
||||
const u32 min_binding = (std::min)(bindings.min_index, device_max);
|
||||
@@ -766,12 +595,22 @@ void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bi
|
||||
if (binding_count == 0) {
|
||||
return;
|
||||
}
|
||||
scheduler.Record([bindings_ = std::move(bindings),
|
||||
buffer_handles_ = std::move(buffer_handles),
|
||||
binding_count](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.BindVertexBuffers(bindings_.min_index, binding_count, buffer_handles_.data(),
|
||||
bindings_.offsets.data());
|
||||
});
|
||||
if (device.IsExtExtendedDynamicStateSupported()) {
|
||||
scheduler.Record([bindings_ = std::move(bindings),
|
||||
buffer_handles_ = std::move(buffer_handles),
|
||||
binding_count](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.BindVertexBuffers2EXT(bindings_.min_index, binding_count, buffer_handles_.data(),
|
||||
bindings_.offsets.data(), bindings_.sizes.data(),
|
||||
bindings_.strides.data());
|
||||
});
|
||||
} else {
|
||||
scheduler.Record([bindings_ = std::move(bindings),
|
||||
buffer_handles_ = std::move(buffer_handles),
|
||||
binding_count](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.BindVertexBuffers(bindings_.min_index, binding_count, buffer_handles_.data(),
|
||||
bindings_.offsets.data());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void BufferCacheRuntime::BindTransformFeedbackBuffer(u32 index, VkBuffer buffer, u32 offset,
|
||||
@@ -800,21 +639,15 @@ void BufferCacheRuntime::BindTransformFeedbackBuffers(VideoCommon::HostBindings<
|
||||
// Already logged in the rasterizer
|
||||
return;
|
||||
}
|
||||
boost::container::static_vector<VkBuffer, VideoCommon::NUM_VERTEX_BUFFERS> buffer_handles(bindings.buffers.size());
|
||||
for (u32 i = 0; i < bindings.buffers.size(); ++i) {
|
||||
auto handle = bindings.buffers[i]->Handle();
|
||||
if (handle == VK_NULL_HANDLE) {
|
||||
bindings.offsets[i] = 0;
|
||||
bindings.sizes[i] = VK_WHOLE_SIZE;
|
||||
if (!device.HasNullDescriptor()) {
|
||||
ReserveNullBuffer();
|
||||
handle = *null_buffer;
|
||||
}
|
||||
}
|
||||
buffer_handles[i] = handle;
|
||||
boost::container::small_vector<VkBuffer, 4> buffer_handles;
|
||||
for (u32 index = 0; index < bindings.buffers.size(); ++index) {
|
||||
buffer_handles.push_back(bindings.buffers[index]->Handle());
|
||||
}
|
||||
scheduler.Record([bindings_ = std::move(bindings), buffer_handles_ = std::move(buffer_handles)](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.BindTransformFeedbackBuffersEXT(0, u32(buffer_handles_.size()), buffer_handles_.data(), bindings_.offsets.data(), bindings_.sizes.data());
|
||||
scheduler.Record([bindings_ = std::move(bindings),
|
||||
buffer_handles_ = std::move(buffer_handles)](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.BindTransformFeedbackBuffersEXT(0, static_cast<u32>(buffer_handles_.size()),
|
||||
buffer_handles_.data(), bindings_.offsets.data(),
|
||||
bindings_.sizes.data());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
@@ -72,15 +72,12 @@ private:
|
||||
|
||||
class QuadArrayIndexBuffer;
|
||||
class QuadStripIndexBuffer;
|
||||
class QuadArrayLineIndexBuffer;
|
||||
class QuadStripLineIndexBuffer;
|
||||
|
||||
class BufferCacheRuntime {
|
||||
friend Buffer;
|
||||
|
||||
using PrimitiveTopology = Tegra::Engines::Maxwell3D::Regs::PrimitiveTopology;
|
||||
using IndexFormat = Tegra::Engines::Maxwell3D::Regs::IndexFormat;
|
||||
using PolygonMode = Tegra::Engines::Maxwell3D::Regs::PolygonMode;
|
||||
|
||||
public:
|
||||
explicit BufferCacheRuntime(const Device& device_, MemoryAllocator& memory_manager_,
|
||||
@@ -121,21 +118,15 @@ public:
|
||||
|
||||
void ClearBuffer(VkBuffer dest_buffer, u32 offset, size_t size, u32 value);
|
||||
|
||||
void BindIndexBuffer(PrimitiveTopology topology, PolygonMode polygon_mode,
|
||||
IndexFormat index_format, u32 base_vertex, u32 num_indices,
|
||||
VkBuffer buffer, u32 offset, u32 size);
|
||||
void BindIndexBuffer(PrimitiveTopology topology, IndexFormat index_format, u32 num_indices,
|
||||
u32 base_vertex, VkBuffer buffer, u32 offset, u32 size);
|
||||
|
||||
void BindQuadIndexBuffer(PrimitiveTopology topology, PolygonMode polygon_mode, 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 BindTransformFeedbackBuffer(u32 index, Buffer& buffer, u32 offset, u32 size) {
|
||||
BindTransformFeedbackBuffer(index, buffer.Handle(), offset, size);
|
||||
}
|
||||
|
||||
void BindTransformFeedbackBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size);
|
||||
|
||||
void BindTransformFeedbackBuffers(VideoCommon::HostBindings<Buffer>& bindings);
|
||||
@@ -186,13 +177,11 @@ private:
|
||||
|
||||
std::shared_ptr<QuadArrayIndexBuffer> quad_array_index_buffer;
|
||||
std::shared_ptr<QuadStripIndexBuffer> quad_strip_index_buffer;
|
||||
std::shared_ptr<QuadArrayLineIndexBuffer> quad_array_line_index_buffer;
|
||||
std::shared_ptr<QuadStripLineIndexBuffer> quad_strip_line_index_buffer;
|
||||
|
||||
vk::Buffer null_buffer;
|
||||
|
||||
std::unique_ptr<Uint8Pass> uint8_pass;
|
||||
QuadIndexedPass quad_index_pass;
|
||||
QuadIndexedPass quad_index_pass;
|
||||
|
||||
bool limit_dynamic_storage_buffers = false;
|
||||
u32 max_dynamic_storage_buffers = (std::numeric_limits<u32>::max)();
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include <numeric>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "video_core/renderer_vulkan/vk_texture_cache.h"
|
||||
|
||||
@@ -238,37 +237,9 @@ ComputePass::ComputePass(const Device& device_, DescriptorPool& descriptor_pool,
|
||||
vk::Span<VkPushConstantRange> push_constants, std::span<const u32> code,
|
||||
std::optional<u32> optional_subgroup_size)
|
||||
: device{device_} {
|
||||
u32 variable_descriptor_count{};
|
||||
std::vector<VkDescriptorBindingFlags> binding_flags;
|
||||
VkDescriptorSetLayoutBindingFlagsCreateInfo binding_flags_ci{
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.bindingCount = 0,
|
||||
.pBindingFlags = nullptr,
|
||||
};
|
||||
const bool use_descriptor_indexing = device.isExtDescriptorIndexingSupported();
|
||||
const void* layout_next = nullptr;
|
||||
if (use_descriptor_indexing && !bindings.empty()) {
|
||||
binding_flags.assign(bindings.size(), 0);
|
||||
for (size_t i = 0; i < bindings.size(); ++i) {
|
||||
if (bindings[i].descriptorCount > 1) {
|
||||
binding_flags[i] |= VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT;
|
||||
}
|
||||
}
|
||||
const size_t last_binding = bindings.size() - 1;
|
||||
if (bindings[last_binding].descriptorCount > 1) {
|
||||
binding_flags[last_binding] |= VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT;
|
||||
variable_descriptor_count = bindings[last_binding].descriptorCount;
|
||||
}
|
||||
|
||||
binding_flags_ci.bindingCount = static_cast<u32>(binding_flags.size());
|
||||
binding_flags_ci.pBindingFlags = binding_flags.data();
|
||||
layout_next = &binding_flags_ci;
|
||||
}
|
||||
|
||||
descriptor_set_layout = device.GetLogical().CreateDescriptorSetLayout({
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
|
||||
.pNext = layout_next,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.bindingCount = bindings.size(),
|
||||
.pBindings = bindings.data(),
|
||||
@@ -295,8 +266,7 @@ ComputePass::ComputePass(const Device& device_, DescriptorPool& descriptor_pool,
|
||||
.pipelineLayout = *layout,
|
||||
.set = 0,
|
||||
});
|
||||
descriptor_allocator =
|
||||
descriptor_pool.Allocator(*descriptor_set_layout, bank_info, variable_descriptor_count);
|
||||
descriptor_allocator = descriptor_pool.Allocator(*descriptor_set_layout, bank_info);
|
||||
}
|
||||
if (code.empty()) {
|
||||
return;
|
||||
@@ -315,7 +285,7 @@ ComputePass::ComputePass(const Device& device_, DescriptorPool& descriptor_pool,
|
||||
.requiredSubgroupSize = optional_subgroup_size ? *optional_subgroup_size : 32U,
|
||||
};
|
||||
bool use_setup_size = device.IsExtSubgroupSizeControlSupported() && optional_subgroup_size;
|
||||
pipeline = device.GetLogical().CreateComputePipeline(VkComputePipelineCreateInfo{
|
||||
pipeline = device.GetLogical().CreateComputePipeline({
|
||||
.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
@@ -329,7 +299,7 @@ ComputePass::ComputePass(const Device& device_, DescriptorPool& descriptor_pool,
|
||||
.pSpecializationInfo = nullptr,
|
||||
},
|
||||
.layout = *layout,
|
||||
.basePipelineHandle = {},
|
||||
.basePipelineHandle = nullptr,
|
||||
.basePipelineIndex = 0,
|
||||
});
|
||||
}
|
||||
@@ -974,7 +944,7 @@ MSAACopyPass::MSAACopyPass(const Device& device_, Scheduler& scheduler_,
|
||||
.codeSize = static_cast<u32>(code.size_bytes()),
|
||||
.pCode = code.data(),
|
||||
});
|
||||
pipelines[i] = device.GetLogical().CreateComputePipeline(VkComputePipelineCreateInfo{
|
||||
pipelines[i] = device.GetLogical().CreateComputePipeline({
|
||||
.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
@@ -988,7 +958,7 @@ MSAACopyPass::MSAACopyPass(const Device& device_, Scheduler& scheduler_,
|
||||
.pSpecializationInfo = nullptr,
|
||||
},
|
||||
.layout = *layout,
|
||||
.basePipelineHandle = {},
|
||||
.basePipelineHandle = nullptr,
|
||||
.basePipelineIndex = 0,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -50,14 +50,11 @@ ComputePipeline::ComputePipeline(const Device& device_, vk::PipelineCache& pipel
|
||||
DescriptorLayoutBuilder builder{device};
|
||||
builder.Add(info, VK_SHADER_STAGE_COMPUTE_BIT);
|
||||
|
||||
uses_push_descriptor = builder.CanUsePushDescriptor();
|
||||
descriptor_set_layout = builder.CreateDescriptorSetLayout(uses_push_descriptor);
|
||||
descriptor_set_layout = builder.CreateDescriptorSetLayout(false);
|
||||
pipeline_layout = builder.CreatePipelineLayout(*descriptor_set_layout);
|
||||
descriptor_update_template =
|
||||
builder.CreateTemplate(*descriptor_set_layout, *pipeline_layout, uses_push_descriptor);
|
||||
if (!uses_push_descriptor) {
|
||||
descriptor_allocator = descriptor_pool.Allocator(*descriptor_set_layout, info);
|
||||
}
|
||||
builder.CreateTemplate(*descriptor_set_layout, *pipeline_layout, false);
|
||||
descriptor_allocator = descriptor_pool.Allocator(*descriptor_set_layout, info);
|
||||
const VkPipelineShaderStageRequiredSubgroupSizeCreateInfoEXT subgroup_size_ci{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_REQUIRED_SUBGROUP_SIZE_CREATE_INFO_EXT,
|
||||
.pNext = nullptr,
|
||||
@@ -67,24 +64,26 @@ ComputePipeline::ComputePipeline(const Device& device_, vk::PipelineCache& pipel
|
||||
if (device.IsKhrPipelineExecutablePropertiesEnabled() && Settings::values.renderer_debug.GetValue()) {
|
||||
flags |= VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR;
|
||||
}
|
||||
pipeline = device.GetLogical().CreateComputePipeline(VkComputePipelineCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = flags,
|
||||
.stage{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
|
||||
.pNext =
|
||||
device.IsExtSubgroupSizeControlSupported() ? &subgroup_size_ci : nullptr,
|
||||
.flags = 0,
|
||||
.stage = VK_SHADER_STAGE_COMPUTE_BIT,
|
||||
.module = *spv_module,
|
||||
.pName = "main",
|
||||
.pSpecializationInfo = nullptr,
|
||||
pipeline = device.GetLogical().CreateComputePipeline(
|
||||
{
|
||||
.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = flags,
|
||||
.stage{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
|
||||
.pNext =
|
||||
device.IsExtSubgroupSizeControlSupported() ? &subgroup_size_ci : nullptr,
|
||||
.flags = 0,
|
||||
.stage = VK_SHADER_STAGE_COMPUTE_BIT,
|
||||
.module = *spv_module,
|
||||
.pName = "main",
|
||||
.pSpecializationInfo = nullptr,
|
||||
},
|
||||
.layout = *pipeline_layout,
|
||||
.basePipelineHandle = 0,
|
||||
.basePipelineIndex = 0,
|
||||
},
|
||||
.layout = *pipeline_layout,
|
||||
.basePipelineHandle = 0,
|
||||
.basePipelineIndex = 0,
|
||||
}, *pipeline_cache);
|
||||
*pipeline_cache);
|
||||
|
||||
// Log compute pipeline creation
|
||||
if (Settings::values.gpu_logging_enabled.GetValue()) {
|
||||
@@ -242,16 +241,11 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
|
||||
RESCALING_LAYOUT_WORDS_OFFSET, sizeof(rescaling_data),
|
||||
rescaling_data.data());
|
||||
}
|
||||
if (uses_push_descriptor) {
|
||||
cmdbuf.PushDescriptorSetWithTemplateKHR(*descriptor_update_template, *pipeline_layout,
|
||||
0, descriptor_data);
|
||||
} else {
|
||||
const VkDescriptorSet descriptor_set{descriptor_allocator.Commit()};
|
||||
const vk::Device& dev{device.GetLogical()};
|
||||
dev.UpdateDescriptorSet(descriptor_set, *descriptor_update_template, descriptor_data);
|
||||
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline_layout, 0,
|
||||
descriptor_set, nullptr);
|
||||
}
|
||||
const VkDescriptorSet descriptor_set{descriptor_allocator.Commit()};
|
||||
const vk::Device& dev{device.GetLogical()};
|
||||
dev.UpdateDescriptorSet(descriptor_set, *descriptor_update_template, descriptor_data);
|
||||
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline_layout, 0,
|
||||
descriptor_set, nullptr);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -58,7 +55,6 @@ private:
|
||||
|
||||
vk::ShaderModule spv_module;
|
||||
vk::DescriptorSetLayout descriptor_set_layout;
|
||||
bool uses_push_descriptor{false};
|
||||
DescriptorAllocator descriptor_allocator;
|
||||
vk::PipelineLayout pipeline_layout;
|
||||
vk::DescriptorUpdateTemplate descriptor_update_template;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
@@ -88,10 +88,9 @@ static void AllocatePool(const Device& device, DescriptorBank& bank) {
|
||||
}
|
||||
|
||||
DescriptorAllocator::DescriptorAllocator(const Device& device_, MasterSemaphore& master_semaphore_,
|
||||
DescriptorBank& bank_, VkDescriptorSetLayout layout_,
|
||||
u32 variable_descriptor_count_)
|
||||
DescriptorBank& bank_, VkDescriptorSetLayout layout_)
|
||||
: ResourcePool(master_semaphore_, SETS_GROW_RATE), device{&device_}, bank{&bank_},
|
||||
layout{layout_}, variable_descriptor_count{variable_descriptor_count_} {}
|
||||
layout{layout_} {}
|
||||
|
||||
VkDescriptorSet DescriptorAllocator::Commit() {
|
||||
const size_t index = CommitResource();
|
||||
@@ -104,25 +103,9 @@ void DescriptorAllocator::Allocate(size_t begin, size_t end) {
|
||||
|
||||
vk::DescriptorSets DescriptorAllocator::AllocateDescriptors(size_t count) {
|
||||
const std::vector<VkDescriptorSetLayout> layouts(count, layout);
|
||||
|
||||
std::vector<u32> variable_descriptor_counts;
|
||||
VkDescriptorSetVariableDescriptorCountAllocateInfo variable_descriptor_count_info{
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_ALLOCATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.descriptorSetCount = 0,
|
||||
.pDescriptorCounts = nullptr,
|
||||
};
|
||||
const void* allocate_next = nullptr;
|
||||
if (variable_descriptor_count != 0) {
|
||||
variable_descriptor_counts.assign(count, variable_descriptor_count);
|
||||
variable_descriptor_count_info.descriptorSetCount = static_cast<u32>(count);
|
||||
variable_descriptor_count_info.pDescriptorCounts = variable_descriptor_counts.data();
|
||||
allocate_next = &variable_descriptor_count_info;
|
||||
}
|
||||
|
||||
VkDescriptorSetAllocateInfo allocate_info{
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
|
||||
.pNext = allocate_next,
|
||||
.pNext = nullptr,
|
||||
.descriptorPool = *bank->pools.back(),
|
||||
.descriptorSetCount = static_cast<u32>(count),
|
||||
.pSetLayouts = layouts.data(),
|
||||
@@ -148,22 +131,18 @@ DescriptorPool::DescriptorPool(const Device& device_, Scheduler& scheduler)
|
||||
DescriptorPool::~DescriptorPool() = default;
|
||||
|
||||
DescriptorAllocator DescriptorPool::Allocator(VkDescriptorSetLayout layout,
|
||||
std::span<const Shader::Info> infos,
|
||||
u32 variable_descriptor_count) {
|
||||
return Allocator(layout, MakeBankInfo(infos), variable_descriptor_count);
|
||||
std::span<const Shader::Info> infos) {
|
||||
return Allocator(layout, MakeBankInfo(infos));
|
||||
}
|
||||
|
||||
DescriptorAllocator DescriptorPool::Allocator(VkDescriptorSetLayout layout,
|
||||
const Shader::Info& info,
|
||||
u32 variable_descriptor_count) {
|
||||
return Allocator(layout, MakeBankInfo(std::array{info}), variable_descriptor_count);
|
||||
const Shader::Info& info) {
|
||||
return Allocator(layout, MakeBankInfo(std::array{info}));
|
||||
}
|
||||
|
||||
DescriptorAllocator DescriptorPool::Allocator(VkDescriptorSetLayout layout,
|
||||
const DescriptorBankInfo& info,
|
||||
u32 variable_descriptor_count) {
|
||||
return DescriptorAllocator(device, master_semaphore, Bank(info), layout,
|
||||
variable_descriptor_count);
|
||||
const DescriptorBankInfo& info) {
|
||||
return DescriptorAllocator(device, master_semaphore, Bank(info), layout);
|
||||
}
|
||||
|
||||
DescriptorBank& DescriptorPool::Bank(const DescriptorBankInfo& reqs) {
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -50,8 +47,7 @@ public:
|
||||
|
||||
private:
|
||||
explicit DescriptorAllocator(const Device& device_, MasterSemaphore& master_semaphore_,
|
||||
DescriptorBank& bank_, VkDescriptorSetLayout layout_,
|
||||
u32 variable_descriptor_count_);
|
||||
DescriptorBank& bank_, VkDescriptorSetLayout layout_);
|
||||
|
||||
void Allocate(size_t begin, size_t end) override;
|
||||
|
||||
@@ -60,7 +56,6 @@ private:
|
||||
const Device* device{};
|
||||
DescriptorBank* bank{};
|
||||
VkDescriptorSetLayout layout{};
|
||||
u32 variable_descriptor_count{};
|
||||
|
||||
std::vector<vk::DescriptorSets> sets;
|
||||
};
|
||||
@@ -74,12 +69,9 @@ public:
|
||||
DescriptorPool(const DescriptorPool&) = delete;
|
||||
|
||||
DescriptorAllocator Allocator(VkDescriptorSetLayout layout,
|
||||
std::span<const Shader::Info> infos,
|
||||
u32 variable_descriptor_count = 0);
|
||||
DescriptorAllocator Allocator(VkDescriptorSetLayout layout, const Shader::Info& info,
|
||||
u32 variable_descriptor_count = 0);
|
||||
DescriptorAllocator Allocator(VkDescriptorSetLayout layout, const DescriptorBankInfo& info,
|
||||
u32 variable_descriptor_count = 0);
|
||||
std::span<const Shader::Info> infos);
|
||||
DescriptorAllocator Allocator(VkDescriptorSetLayout layout, const Shader::Info& info);
|
||||
DescriptorAllocator Allocator(VkDescriptorSetLayout layout, const DescriptorBankInfo& info);
|
||||
|
||||
private:
|
||||
DescriptorBank& Bank(const DescriptorBankInfo& reqs);
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
||||
#include "video_core/renderer_vulkan/vk_texture_cache.h"
|
||||
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
|
||||
#include "video_core/surface.h"
|
||||
#include "video_core/shader_notify.h"
|
||||
#include "video_core/texture_cache/texture_cache.h"
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
@@ -101,37 +100,6 @@ bool IsLine(VkPrimitiveTopology topology) {
|
||||
return std::ranges::find(line_topologies, topology) != line_topologies.end();
|
||||
}
|
||||
|
||||
VkPrimitiveTopology DynamicTopologyClassRepresentative(VkPrimitiveTopology topology) {
|
||||
switch (topology) {
|
||||
case VK_PRIMITIVE_TOPOLOGY_POINT_LIST:
|
||||
return VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
|
||||
case VK_PRIMITIVE_TOPOLOGY_LINE_LIST:
|
||||
case VK_PRIMITIVE_TOPOLOGY_LINE_STRIP:
|
||||
return VK_PRIMITIVE_TOPOLOGY_LINE_LIST;
|
||||
case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST:
|
||||
case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP:
|
||||
case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN:
|
||||
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||
case VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY:
|
||||
case VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY:
|
||||
return VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY;
|
||||
case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY:
|
||||
case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY:
|
||||
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY;
|
||||
case VK_PRIMITIVE_TOPOLOGY_PATCH_LIST:
|
||||
return VK_PRIMITIVE_TOPOLOGY_PATCH_LIST;
|
||||
default:
|
||||
return topology;
|
||||
}
|
||||
}
|
||||
|
||||
bool SupportsStaticPrimitiveRestart(const Device& device, VkPrimitiveTopology topology) {
|
||||
if (topology == VK_PRIMITIVE_TOPOLOGY_PATCH_LIST) {
|
||||
return device.IsPatchListPrimitiveRestartSupported();
|
||||
}
|
||||
return SupportsPrimitiveRestart(topology) || device.IsTopologyListPrimitiveRestartSupported();
|
||||
}
|
||||
|
||||
VkViewportSwizzleNV UnpackViewportSwizzle(u16 swizzle) {
|
||||
union Swizzle {
|
||||
u32 raw;
|
||||
@@ -305,8 +273,7 @@ GraphicsPipeline::GraphicsPipeline(
|
||||
descriptor_set_layout = builder.CreateDescriptorSetLayout(uses_push_descriptor);
|
||||
|
||||
if (!uses_push_descriptor) {
|
||||
descriptor_allocator = descriptor_pool.Allocator(
|
||||
*descriptor_set_layout, stage_infos, builder.VariableDescriptorCount());
|
||||
descriptor_allocator = descriptor_pool.Allocator(*descriptor_set_layout, stage_infos);
|
||||
}
|
||||
|
||||
const VkDescriptorSetLayout set_layout{*descriptor_set_layout};
|
||||
@@ -507,18 +474,11 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||
buffer_cache.BindHostStageBuffers(stage);
|
||||
PushImageDescriptors(texture_cache, guest_descriptor_queue, stage_infos[stage], rescaling,
|
||||
samplers_it, views_it);
|
||||
const auto& info{stage_infos[stage]};
|
||||
const auto& info{stage_infos[0]};
|
||||
if (info.uses_render_area) {
|
||||
render_area.uses_render_area = true;
|
||||
const bool is_rescaling{texture_cache.IsRescaling()};
|
||||
const auto& resolution = Settings::values.resolution_info;
|
||||
const float render_area_width = static_cast<float>(
|
||||
is_rescaling ? resolution.ScaleUp(static_cast<u32>(regs.surface_clip.width))
|
||||
: static_cast<u32>(regs.surface_clip.width));
|
||||
const float render_area_height = static_cast<float>(
|
||||
is_rescaling ? resolution.ScaleUp(static_cast<u32>(regs.surface_clip.height))
|
||||
: static_cast<u32>(regs.surface_clip.height));
|
||||
render_area.words = {render_area_width, render_area_height};
|
||||
render_area.words = {static_cast<float>(regs.surface_clip.width),
|
||||
static_cast<float>(regs.surface_clip.height)};
|
||||
}
|
||||
}};
|
||||
if constexpr (Spec::enabled_stages[0]) {
|
||||
@@ -608,39 +568,47 @@ void GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling,
|
||||
}
|
||||
|
||||
void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
|
||||
const FixedPipelineState::DynamicState dynamic{key.state.dynamic_state};
|
||||
FixedPipelineState::DynamicState dynamic{};
|
||||
if (!key.state.extended_dynamic_state) {
|
||||
dynamic = key.state.dynamic_state;
|
||||
} else {
|
||||
dynamic.raw1 = key.state.dynamic_state.raw1;
|
||||
}
|
||||
static_vector<VkVertexInputBindingDescription, 32> vertex_bindings;
|
||||
static_vector<VkVertexInputBindingDivisorDescriptionEXT, 32> vertex_binding_divisors;
|
||||
static_vector<VkVertexInputAttributeDescription, 32> vertex_attributes;
|
||||
const size_t num_vertex_arrays =
|
||||
(std::min)(Maxwell::NumVertexArrays, static_cast<size_t>(device.GetMaxVertexInputBindings()));
|
||||
for (size_t index = 0; index < num_vertex_arrays; ++index) {
|
||||
const bool instanced = key.state.binding_divisors[index] != 0;
|
||||
const auto rate = instanced ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
vertex_bindings.push_back({
|
||||
.binding = static_cast<u32>(index),
|
||||
.stride = key.state.vertex_strides[index],
|
||||
.inputRate = rate,
|
||||
});
|
||||
if (instanced) {
|
||||
vertex_binding_divisors.push_back({
|
||||
if (!key.state.dynamic_vertex_input) {
|
||||
const size_t num_vertex_arrays = (std::min)(
|
||||
Maxwell::NumVertexArrays, static_cast<size_t>(device.GetMaxVertexInputBindings()));
|
||||
for (size_t index = 0; index < num_vertex_arrays; ++index) {
|
||||
const bool instanced = key.state.binding_divisors[index] != 0;
|
||||
const auto rate =
|
||||
instanced ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
vertex_bindings.push_back({
|
||||
.binding = static_cast<u32>(index),
|
||||
.divisor = key.state.binding_divisors[index],
|
||||
.stride = key.state.vertex_strides[index],
|
||||
.inputRate = rate,
|
||||
});
|
||||
if (instanced) {
|
||||
vertex_binding_divisors.push_back({
|
||||
.binding = static_cast<u32>(index),
|
||||
.divisor = key.state.binding_divisors[index],
|
||||
});
|
||||
}
|
||||
}
|
||||
for (size_t index = 0; index < key.state.attributes.size(); ++index) {
|
||||
const auto& attribute = key.state.attributes[index];
|
||||
if (!attribute.enabled || !stage_infos[0].loads.Generic(index)) {
|
||||
continue;
|
||||
}
|
||||
vertex_attributes.push_back({
|
||||
.location = static_cast<u32>(index),
|
||||
.binding = attribute.buffer,
|
||||
.format = MaxwellToVK::VertexFormat(device, attribute.Type(), attribute.Size()),
|
||||
.offset = attribute.offset,
|
||||
});
|
||||
}
|
||||
}
|
||||
for (size_t index = 0; index < key.state.attributes.size(); ++index) {
|
||||
const auto& attribute = key.state.attributes[index];
|
||||
if (!attribute.enabled || !stage_infos[0].loads.Generic(index)) {
|
||||
continue;
|
||||
}
|
||||
vertex_attributes.push_back({
|
||||
.location = static_cast<u32>(index),
|
||||
.binding = attribute.buffer,
|
||||
.format = MaxwellToVK::VertexFormat(device, attribute.Type(), attribute.Size()),
|
||||
.offset = attribute.offset,
|
||||
});
|
||||
}
|
||||
ASSERT(vertex_attributes.size() <= device.GetMaxVertexInputAttributes());
|
||||
|
||||
VkPipelineVertexInputStateCreateInfo vertex_input_ci{
|
||||
@@ -661,20 +629,12 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
|
||||
if (!vertex_binding_divisors.empty()) {
|
||||
vertex_input_ci.pNext = &input_divisor_ci;
|
||||
}
|
||||
const bool has_tess_stages = static_cast<bool>(spv_modules[1]) || static_cast<bool>(spv_modules[2]);
|
||||
const bool has_geometry_stage = static_cast<bool>(spv_modules[3]);
|
||||
const bool dynamic_topology = key.state.extended_dynamic_state != 0;
|
||||
const bool dynamic_primitive_restart = key.state.extended_dynamic_state_2 != 0;
|
||||
const auto polygon_mode =
|
||||
FixedPipelineState::UnpackPolygonMode(key.state.polygon_mode);
|
||||
const bool allow_polygon_mode_emulation =
|
||||
!has_tess_stages && !has_geometry_stage && key.state.xfb_enabled == 0;
|
||||
auto exact_input_assembly_topology = MaxwellToVK::PrimitiveTopology(
|
||||
device, key.state.topology, polygon_mode, allow_polygon_mode_emulation);
|
||||
if (exact_input_assembly_topology == VK_PRIMITIVE_TOPOLOGY_PATCH_LIST) {
|
||||
const bool has_tess_stages = spv_modules[1] || spv_modules[2];
|
||||
auto input_assembly_topology = MaxwellToVK::PrimitiveTopology(device, key.state.topology);
|
||||
if (input_assembly_topology == VK_PRIMITIVE_TOPOLOGY_PATCH_LIST) {
|
||||
if (!has_tess_stages) {
|
||||
LOG_WARNING(Render_Vulkan, "Patch topology used without tessellation, using points");
|
||||
exact_input_assembly_topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
|
||||
input_assembly_topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
|
||||
}
|
||||
} else {
|
||||
if (has_tess_stages) {
|
||||
@@ -682,29 +642,25 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
|
||||
// shader stages. Forcing it fixes a crash on some drivers
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"Patch topology not used with tessellation, using patch list");
|
||||
exact_input_assembly_topology = VK_PRIMITIVE_TOPOLOGY_PATCH_LIST;
|
||||
input_assembly_topology = VK_PRIMITIVE_TOPOLOGY_PATCH_LIST;
|
||||
}
|
||||
}
|
||||
const VkPrimitiveTopology input_assembly_topology =
|
||||
dynamic_topology && dynamic_primitive_restart
|
||||
? DynamicTopologyClassRepresentative(exact_input_assembly_topology)
|
||||
: exact_input_assembly_topology;
|
||||
const VkBool32 primitive_restart_enable =
|
||||
// MoltenVK/Metal always has primitive restart enabled and cannot disable it
|
||||
device.IsMoltenVK()
|
||||
? VK_TRUE
|
||||
: (dynamic_primitive_restart
|
||||
? VK_FALSE
|
||||
: (dynamic.primitive_restart_enable != 0 &&
|
||||
SupportsStaticPrimitiveRestart(device, input_assembly_topology)
|
||||
? VK_TRUE
|
||||
: VK_FALSE));
|
||||
const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.topology = input_assembly_topology,
|
||||
.primitiveRestartEnable = primitive_restart_enable,
|
||||
.primitiveRestartEnable =
|
||||
// MoltenVK/Metal always has primitive restart enabled and cannot disable it
|
||||
device.IsMoltenVK() ? VK_TRUE :
|
||||
(dynamic.primitive_restart_enable != 0 &&
|
||||
((input_assembly_topology != VK_PRIMITIVE_TOPOLOGY_PATCH_LIST &&
|
||||
device.IsTopologyListPrimitiveRestartSupported()) ||
|
||||
SupportsPrimitiveRestart(input_assembly_topology) ||
|
||||
(input_assembly_topology == VK_PRIMITIVE_TOPOLOGY_PATCH_LIST &&
|
||||
device.IsPatchListPrimitiveRestartSupported()))
|
||||
? VK_TRUE
|
||||
: VK_FALSE),
|
||||
};
|
||||
const VkPipelineTessellationStateCreateInfo tessellation_ci{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO,
|
||||
@@ -731,9 +687,9 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.viewportCount = key.state.extended_dynamic_state ? 0u : num_viewports,
|
||||
.viewportCount = num_viewports,
|
||||
.pViewports = nullptr,
|
||||
.scissorCount = key.state.extended_dynamic_state ? 0u : num_viewports,
|
||||
.scissorCount = num_viewports,
|
||||
.pScissors = nullptr,
|
||||
};
|
||||
if (device.IsNvViewportSwizzleSupported()) {
|
||||
@@ -760,54 +716,20 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
|
||||
.depthBiasClamp = 0.0f,
|
||||
.depthBiasSlopeFactor = 0.0f,
|
||||
.lineWidth = 1.0f,
|
||||
// TODO(alekpop): Transfer from regs
|
||||
};
|
||||
const bool line_rasterization_supported = device.IsExtLineRasterizationSupported();
|
||||
const bool any_stippled_lines_supported =
|
||||
line_rasterization_supported &&
|
||||
(device.SupportsStippledRectangularLines() || device.SupportsStippledBresenhamLines() ||
|
||||
device.SupportsStippledSmoothLines());
|
||||
const bool line_stipple_dynamic_state_supported =
|
||||
IsLine(input_assembly_topology) && any_stippled_lines_supported;
|
||||
const bool supports_rectangular_lines =
|
||||
line_rasterization_supported && device.SupportsRectangularLines();
|
||||
const bool supports_smooth_lines = line_rasterization_supported && device.SupportsSmoothLines();
|
||||
|
||||
VkLineRasterizationModeEXT line_rasterization_mode = VK_LINE_RASTERIZATION_MODE_DEFAULT_EXT;
|
||||
if (line_rasterization_supported) {
|
||||
if (key.state.smooth_lines != 0) {
|
||||
if (supports_smooth_lines) {
|
||||
line_rasterization_mode = VK_LINE_RASTERIZATION_MODE_RECTANGULAR_SMOOTH_EXT;
|
||||
} else if (supports_rectangular_lines) {
|
||||
line_rasterization_mode = VK_LINE_RASTERIZATION_MODE_RECTANGULAR_EXT;
|
||||
}
|
||||
} else {
|
||||
if (supports_rectangular_lines) {
|
||||
line_rasterization_mode = VK_LINE_RASTERIZATION_MODE_RECTANGULAR_EXT;
|
||||
} else if (supports_smooth_lines) {
|
||||
line_rasterization_mode = VK_LINE_RASTERIZATION_MODE_RECTANGULAR_SMOOTH_EXT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bool stippled_lines_supported = [&]() {
|
||||
if (!line_rasterization_supported || !dynamic.line_stipple_enable) {
|
||||
return false;
|
||||
}
|
||||
switch (line_rasterization_mode) {
|
||||
case VK_LINE_RASTERIZATION_MODE_RECTANGULAR_EXT:
|
||||
return device.SupportsStippledRectangularLines();
|
||||
case VK_LINE_RASTERIZATION_MODE_RECTANGULAR_SMOOTH_EXT:
|
||||
return device.SupportsStippledSmoothLines();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}();
|
||||
|
||||
const bool smooth_lines_supported =
|
||||
device.IsExtLineRasterizationSupported() && device.SupportsSmoothLines();
|
||||
const bool stippled_lines_supported =
|
||||
device.IsExtLineRasterizationSupported() && device.SupportsStippledRectangularLines();
|
||||
VkPipelineRasterizationLineStateCreateInfoEXT line_state{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_LINE_STATE_CREATE_INFO_EXT,
|
||||
.pNext = nullptr,
|
||||
.lineRasterizationMode = line_rasterization_mode,
|
||||
.stippledLineEnable = stippled_lines_supported ? VK_TRUE : VK_FALSE,
|
||||
.lineRasterizationMode = key.state.smooth_lines != 0 && smooth_lines_supported
|
||||
? VK_LINE_RASTERIZATION_MODE_RECTANGULAR_SMOOTH_EXT
|
||||
: VK_LINE_RASTERIZATION_MODE_RECTANGULAR_EXT,
|
||||
.stippledLineEnable =
|
||||
(dynamic.line_stipple_enable && stippled_lines_supported) ? VK_TRUE : VK_FALSE,
|
||||
.lineStippleFactor = key.state.line_stipple_factor,
|
||||
.lineStipplePattern = static_cast<uint16_t>(key.state.line_stipple_pattern),
|
||||
};
|
||||
@@ -815,31 +737,15 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_CONSERVATIVE_STATE_CREATE_INFO_EXT,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.conservativeRasterizationMode = VK_CONSERVATIVE_RASTERIZATION_MODE_DISABLED_EXT,
|
||||
.conservativeRasterizationMode = key.state.conservative_raster_enable != 0
|
||||
? VK_CONSERVATIVE_RASTERIZATION_MODE_OVERESTIMATE_EXT
|
||||
: VK_CONSERVATIVE_RASTERIZATION_MODE_DISABLED_EXT,
|
||||
.extraPrimitiveOverestimationSize = 0.0f,
|
||||
};
|
||||
const bool conservative_requested = key.state.conservative_raster_enable != 0;
|
||||
if (conservative_requested) {
|
||||
const bool is_point_topology = input_assembly_topology == VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
|
||||
const bool is_line_topology = IsLine(input_assembly_topology);
|
||||
const bool needs_point_or_line_support = is_point_topology || is_line_topology;
|
||||
const bool supports_requested_topology =
|
||||
!needs_point_or_line_support || device.SupportsConservativePointAndLineRasterization();
|
||||
|
||||
conservative_raster.conservativeRasterizationMode =
|
||||
supports_requested_topology ? VK_CONSERVATIVE_RASTERIZATION_MODE_OVERESTIMATE_EXT
|
||||
: VK_CONSERVATIVE_RASTERIZATION_MODE_DISABLED_EXT;
|
||||
}
|
||||
const bool preserve_provoking_vertex_for_xfb =
|
||||
!key.state.xfb_enabled || device.IsTransformFeedbackProvokingVertexPreserved();
|
||||
const bool use_last_provoking_vertex =
|
||||
key.state.provoking_vertex_last != 0 && preserve_provoking_vertex_for_xfb &&
|
||||
device.IsProvokingVertexLastSupported();
|
||||
|
||||
VkPipelineRasterizationProvokingVertexStateCreateInfoEXT provoking_vertex{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_PROVOKING_VERTEX_STATE_CREATE_INFO_EXT,
|
||||
.pNext = nullptr,
|
||||
.provokingVertexMode = use_last_provoking_vertex
|
||||
.provokingVertexMode = key.state.provoking_vertex_last != 0
|
||||
? VK_PROVOKING_VERTEX_MODE_LAST_VERTEX_EXT
|
||||
: VK_PROVOKING_VERTEX_MODE_FIRST_VERTEX_EXT,
|
||||
};
|
||||
@@ -850,7 +756,7 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
|
||||
if (device.IsExtConservativeRasterizationSupported()) {
|
||||
conservative_raster.pNext = std::exchange(rasterization_ci.pNext, &conservative_raster);
|
||||
}
|
||||
if (device.IsExtProvokingVertexSupported()) {
|
||||
if (device.IsExtProvokingVertexSupported() && Settings::values.provoking_vertex.GetValue()) {
|
||||
provoking_vertex.pNext = std::exchange(rasterization_ci.pNext, &provoking_vertex);
|
||||
}
|
||||
|
||||
@@ -898,15 +804,13 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
|
||||
VK_COLOR_COMPONENT_A_BIT,
|
||||
};
|
||||
const auto& blend{key.state.attachments[index]};
|
||||
const bool supports_blending =
|
||||
!VideoCore::Surface::IsPixelFormatInteger(DecodeFormat(key.state.color_formats[index]));
|
||||
const std::array mask{blend.Mask()};
|
||||
VkColorComponentFlags write_mask{};
|
||||
for (size_t i = 0; i < mask_table.size(); ++i) {
|
||||
write_mask |= mask[i] ? mask_table[i] : 0;
|
||||
}
|
||||
cb_attachments.push_back({
|
||||
.blendEnable = supports_blending && blend.enable != 0,
|
||||
.blendEnable = blend.enable != 0,
|
||||
.srcColorBlendFactor = MaxwellToVK::BlendFactor(blend.SourceRGBFactor()),
|
||||
.dstColorBlendFactor = MaxwellToVK::BlendFactor(blend.DestRGBFactor()),
|
||||
.colorBlendOp = MaxwellToVK::BlendEquation(blend.EquationRGB()),
|
||||
@@ -926,22 +830,17 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
|
||||
.pAttachments = cb_attachments.data(),
|
||||
.blendConstants = {}
|
||||
};
|
||||
static_vector<VkDynamicState, 35> dynamic_states{
|
||||
static_vector<VkDynamicState, 34> dynamic_states{
|
||||
VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR,
|
||||
VK_DYNAMIC_STATE_DEPTH_BIAS, VK_DYNAMIC_STATE_BLEND_CONSTANTS,
|
||||
VK_DYNAMIC_STATE_DEPTH_BOUNDS, VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK,
|
||||
VK_DYNAMIC_STATE_STENCIL_WRITE_MASK, VK_DYNAMIC_STATE_STENCIL_REFERENCE,
|
||||
VK_DYNAMIC_STATE_LINE_WIDTH,
|
||||
};
|
||||
if (line_stipple_dynamic_state_supported) {
|
||||
dynamic_states.push_back(VK_DYNAMIC_STATE_LINE_STIPPLE_EXT);
|
||||
}
|
||||
if (key.state.extended_dynamic_state) {
|
||||
static constexpr std::array extended{
|
||||
VK_DYNAMIC_STATE_VIEWPORT_WITH_COUNT_EXT,
|
||||
VK_DYNAMIC_STATE_SCISSOR_WITH_COUNT_EXT,
|
||||
VK_DYNAMIC_STATE_CULL_MODE_EXT,
|
||||
VK_DYNAMIC_STATE_FRONT_FACE_EXT,
|
||||
VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY_EXT,
|
||||
VK_DYNAMIC_STATE_DEPTH_TEST_ENABLE_EXT,
|
||||
VK_DYNAMIC_STATE_DEPTH_WRITE_ENABLE_EXT,
|
||||
VK_DYNAMIC_STATE_DEPTH_COMPARE_OP_EXT,
|
||||
@@ -950,9 +849,18 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
|
||||
VK_DYNAMIC_STATE_STENCIL_OP_EXT,
|
||||
};
|
||||
dynamic_states.insert(dynamic_states.end(), extended.begin(), extended.end());
|
||||
} else {
|
||||
dynamic_states.push_back(VK_DYNAMIC_STATE_VIEWPORT);
|
||||
dynamic_states.push_back(VK_DYNAMIC_STATE_SCISSOR);
|
||||
|
||||
// VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT is part of EDS1
|
||||
// Only use it if VIDS is not active (VIDS replaces it with full vertex input control)
|
||||
if (!key.state.dynamic_vertex_input) {
|
||||
dynamic_states.push_back(VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT);
|
||||
}
|
||||
}
|
||||
|
||||
// VK_DYNAMIC_STATE_VERTEX_INPUT_EXT (VIDS) - Independent from EDS
|
||||
// Provides full dynamic vertex input control, replaces VERTEX_INPUT_BINDING_STRIDE
|
||||
if (key.state.dynamic_vertex_input) {
|
||||
dynamic_states.push_back(VK_DYNAMIC_STATE_VERTEX_INPUT_EXT);
|
||||
}
|
||||
|
||||
// EDS2 - Core (3 states)
|
||||
@@ -970,6 +878,41 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
|
||||
dynamic_states.push_back(VK_DYNAMIC_STATE_LOGIC_OP_EXT);
|
||||
}
|
||||
|
||||
// EDS3 - Blending (composite: 3 states)
|
||||
if (key.state.extended_dynamic_state_3_blend) {
|
||||
static constexpr std::array extended3{
|
||||
VK_DYNAMIC_STATE_COLOR_BLEND_ENABLE_EXT,
|
||||
VK_DYNAMIC_STATE_COLOR_BLEND_EQUATION_EXT,
|
||||
VK_DYNAMIC_STATE_COLOR_WRITE_MASK_EXT,
|
||||
};
|
||||
dynamic_states.insert(dynamic_states.end(), extended3.begin(), extended3.end());
|
||||
}
|
||||
|
||||
// EDS3 - Enables (composite: per-feature)
|
||||
if (key.state.extended_dynamic_state_3_enables) {
|
||||
if (device.SupportsDynamicState3DepthClampEnable()) {
|
||||
dynamic_states.push_back(VK_DYNAMIC_STATE_DEPTH_CLAMP_ENABLE_EXT);
|
||||
}
|
||||
if (device.SupportsDynamicState3LogicOpEnable()) {
|
||||
dynamic_states.push_back(VK_DYNAMIC_STATE_LOGIC_OP_ENABLE_EXT);
|
||||
}
|
||||
if (device.SupportsDynamicState3LineRasterizationMode()) {
|
||||
dynamic_states.push_back(VK_DYNAMIC_STATE_LINE_RASTERIZATION_MODE_EXT);
|
||||
}
|
||||
if (device.SupportsDynamicState3ConservativeRasterizationMode()) {
|
||||
dynamic_states.push_back(VK_DYNAMIC_STATE_CONSERVATIVE_RASTERIZATION_MODE_EXT);
|
||||
}
|
||||
if (device.SupportsDynamicState3LineStippleEnable()) {
|
||||
dynamic_states.push_back(VK_DYNAMIC_STATE_LINE_STIPPLE_ENABLE_EXT);
|
||||
}
|
||||
if (device.SupportsDynamicState3AlphaToCoverageEnable()) {
|
||||
dynamic_states.push_back(VK_DYNAMIC_STATE_ALPHA_TO_COVERAGE_ENABLE_EXT);
|
||||
}
|
||||
if (device.SupportsDynamicState3AlphaToOneEnable()) {
|
||||
dynamic_states.push_back(VK_DYNAMIC_STATE_ALPHA_TO_ONE_ENABLE_EXT);
|
||||
}
|
||||
}
|
||||
|
||||
const VkPipelineDynamicStateCreateInfo dynamic_state_ci{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
@@ -1003,27 +946,29 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
|
||||
flags |= VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR;
|
||||
}
|
||||
|
||||
pipeline = device.GetLogical().CreateGraphicsPipeline({
|
||||
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = flags,
|
||||
.stageCount = static_cast<u32>(shader_stages.size()),
|
||||
.pStages = shader_stages.data(),
|
||||
.pVertexInputState = &vertex_input_ci,
|
||||
.pInputAssemblyState = &input_assembly_ci,
|
||||
.pTessellationState = &tessellation_ci,
|
||||
.pViewportState = &viewport_ci,
|
||||
.pRasterizationState = &rasterization_ci,
|
||||
.pMultisampleState = &multisample_ci,
|
||||
.pDepthStencilState = &depth_stencil_ci,
|
||||
.pColorBlendState = &color_blend_ci,
|
||||
.pDynamicState = &dynamic_state_ci,
|
||||
.layout = *pipeline_layout,
|
||||
.renderPass = render_pass,
|
||||
.subpass = 0,
|
||||
.basePipelineHandle = nullptr,
|
||||
.basePipelineIndex = 0,
|
||||
}, *pipeline_cache);
|
||||
pipeline = device.GetLogical().CreateGraphicsPipeline(
|
||||
{
|
||||
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = flags,
|
||||
.stageCount = static_cast<u32>(shader_stages.size()),
|
||||
.pStages = shader_stages.data(),
|
||||
.pVertexInputState = &vertex_input_ci,
|
||||
.pInputAssemblyState = &input_assembly_ci,
|
||||
.pTessellationState = &tessellation_ci,
|
||||
.pViewportState = &viewport_ci,
|
||||
.pRasterizationState = &rasterization_ci,
|
||||
.pMultisampleState = &multisample_ci,
|
||||
.pDepthStencilState = &depth_stencil_ci,
|
||||
.pColorBlendState = &color_blend_ci,
|
||||
.pDynamicState = &dynamic_state_ci,
|
||||
.layout = *pipeline_layout,
|
||||
.renderPass = render_pass,
|
||||
.subpass = 0,
|
||||
.basePipelineHandle = nullptr,
|
||||
.basePipelineIndex = 0,
|
||||
},
|
||||
*pipeline_cache);
|
||||
|
||||
// Log graphics pipeline creation
|
||||
if (Settings::values.gpu_logging_enabled.GetValue()) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
@@ -81,6 +81,7 @@ public:
|
||||
const GraphicsPipelineCacheKey& key, std::array<vk::ShaderModule, NUM_STAGES> stages,
|
||||
const std::array<const Shader::Info*, NUM_STAGES>& infos);
|
||||
|
||||
bool HasDynamicVertexInput() const noexcept { return key.state.dynamic_vertex_input; }
|
||||
bool SupportsAlphaToCoverage() const noexcept {
|
||||
return fragment_has_color0_output;
|
||||
}
|
||||
@@ -92,18 +93,6 @@ public:
|
||||
bool UsesExtendedDynamicState() const noexcept {
|
||||
return key.state.extended_dynamic_state != 0;
|
||||
}
|
||||
bool UsesExtendedDynamicState2() const noexcept {
|
||||
return key.state.extended_dynamic_state_2 != 0;
|
||||
}
|
||||
bool UsesExtendedDynamicState2LogicOp() const noexcept {
|
||||
return key.state.extended_dynamic_state_2_logic_op != 0;
|
||||
}
|
||||
bool HasTessellationStages() const noexcept {
|
||||
return static_cast<bool>(spv_modules[1]) || static_cast<bool>(spv_modules[2]);
|
||||
}
|
||||
bool HasGeometryStage() const noexcept {
|
||||
return static_cast<bool>(spv_modules[3]);
|
||||
}
|
||||
GraphicsPipeline& operator=(GraphicsPipeline&&) noexcept = delete;
|
||||
GraphicsPipeline(GraphicsPipeline&&) noexcept = delete;
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ using VideoCommon::FileEnvironment;
|
||||
using VideoCommon::GenericEnvironment;
|
||||
using VideoCommon::GraphicsEnvironment;
|
||||
|
||||
constexpr u32 CACHE_VERSION = 17;
|
||||
constexpr u32 CACHE_VERSION = 16;
|
||||
constexpr std::array<char, 8> VULKAN_CACHE_MAGIC_NUMBER{'y', 'u', 'z', 'u', 'v', 'k', 'c', 'h'};
|
||||
|
||||
template <typename Container>
|
||||
@@ -132,6 +132,20 @@ Shader::AttributeType CastAttributeType(const FixedPipelineState::VertexAttribut
|
||||
return Shader::AttributeType::Float;
|
||||
}
|
||||
|
||||
Shader::AttributeType AttributeType(const FixedPipelineState& state, size_t index) {
|
||||
switch (state.DynamicAttributeType(index)) {
|
||||
case 0:
|
||||
return Shader::AttributeType::Disabled;
|
||||
case 1:
|
||||
return Shader::AttributeType::Float;
|
||||
case 2:
|
||||
return Shader::AttributeType::SignedInt;
|
||||
case 3:
|
||||
return Shader::AttributeType::UnsignedInt;
|
||||
}
|
||||
return Shader::AttributeType::Disabled;
|
||||
}
|
||||
|
||||
Shader::RuntimeInfo MakeRuntimeInfo(std::span<const Shader::IR::Program> programs,
|
||||
const GraphicsPipelineCacheKey& key,
|
||||
const Shader::IR::Program& program,
|
||||
@@ -169,8 +183,14 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span<const Shader::IR::Program> program
|
||||
}
|
||||
info.convert_depth_mode = gl_ndc;
|
||||
}
|
||||
std::ranges::transform(key.state.attributes, info.generic_input_types.begin(),
|
||||
&CastAttributeType);
|
||||
if (key.state.dynamic_vertex_input) {
|
||||
for (size_t index = 0; index < Maxwell::NumVertexAttributes; ++index) {
|
||||
info.generic_input_types[index] = AttributeType(key.state, index);
|
||||
}
|
||||
} else {
|
||||
std::ranges::transform(key.state.attributes, info.generic_input_types.begin(),
|
||||
&CastAttributeType);
|
||||
}
|
||||
break;
|
||||
case Shader::Stage::TessellationEval:
|
||||
info.tess_clockwise = key.state.tessellation_clockwise != 0;
|
||||
@@ -217,64 +237,27 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span<const Shader::IR::Program> program
|
||||
}
|
||||
info.convert_depth_mode = gl_ndc;
|
||||
break;
|
||||
case Shader::Stage::Fragment: {
|
||||
case Shader::Stage::Fragment:
|
||||
info.alpha_test_func = MaxwellToCompareFunction(
|
||||
key.state.UnpackComparisonOp(key.state.alpha_test_func.Value()));
|
||||
info.alpha_test_reference = std::bit_cast<float>(key.state.alpha_test_ref);
|
||||
|
||||
// Check for dual source blending
|
||||
const auto& blend0 = key.state.attachments[0];
|
||||
if (blend0.enable != 0) {
|
||||
using F = Maxwell::Blend::Factor;
|
||||
const auto src_rgb = blend0.SourceRGBFactor();
|
||||
const auto dst_rgb = blend0.DestRGBFactor();
|
||||
const auto src_a = blend0.SourceAlphaFactor();
|
||||
const auto dst_a = blend0.DestAlphaFactor();
|
||||
info.dual_source_blend =
|
||||
src_rgb == F::Source1Color_D3D || src_rgb == F::OneMinusSource1Color_D3D ||
|
||||
src_rgb == F::Source1Alpha_D3D || src_rgb == F::OneMinusSource1Alpha_D3D ||
|
||||
src_rgb == F::Source1Color_GL || src_rgb == F::OneMinusSource1Color_GL ||
|
||||
src_rgb == F::Source1Alpha_GL || src_rgb == F::OneMinusSource1Alpha_GL ||
|
||||
dst_rgb == F::Source1Color_D3D || dst_rgb == F::OneMinusSource1Color_D3D ||
|
||||
dst_rgb == F::Source1Alpha_D3D || dst_rgb == F::OneMinusSource1Alpha_D3D ||
|
||||
dst_rgb == F::Source1Color_GL || dst_rgb == F::OneMinusSource1Color_GL ||
|
||||
dst_rgb == F::Source1Alpha_GL || dst_rgb == F::OneMinusSource1Alpha_GL ||
|
||||
src_a == F::Source1Color_D3D || src_a == F::OneMinusSource1Color_D3D ||
|
||||
src_a == F::Source1Alpha_D3D || src_a == F::OneMinusSource1Alpha_D3D ||
|
||||
src_a == F::Source1Color_GL || src_a == F::OneMinusSource1Color_GL ||
|
||||
src_a == F::Source1Alpha_GL || src_a == F::OneMinusSource1Alpha_GL ||
|
||||
dst_a == F::Source1Color_D3D || dst_a == F::OneMinusSource1Color_D3D ||
|
||||
dst_a == F::Source1Alpha_D3D || dst_a == F::OneMinusSource1Alpha_D3D ||
|
||||
dst_a == F::Source1Color_GL || dst_a == F::OneMinusSource1Color_GL ||
|
||||
dst_a == F::Source1Alpha_GL || dst_a == F::OneMinusSource1Alpha_GL;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < info.active_color_outputs.size(); ++i) {
|
||||
const auto format = static_cast<Tegra::RenderTargetFormat>(key.state.color_formats[i]);
|
||||
info.active_color_outputs[i] = format != Tegra::RenderTargetFormat::NONE;
|
||||
}
|
||||
if (info.dual_source_blend && info.active_color_outputs[0]) {
|
||||
info.active_color_outputs[1] = true;
|
||||
}
|
||||
if (info.alpha_test_func && *info.alpha_test_func != Shader::CompareFunction::Always) {
|
||||
info.active_color_outputs[0] = true;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < 8; ++i) {
|
||||
const auto format = static_cast<Tegra::RenderTargetFormat>(key.state.color_formats[i]);
|
||||
const auto pixel_format = VideoCore::Surface::PixelFormatFromRenderTargetFormat(format);
|
||||
if (VideoCore::Surface::IsPixelFormatInteger(pixel_format)) {
|
||||
if (VideoCore::Surface::IsPixelFormatSignedInteger(pixel_format)) {
|
||||
info.color_output_types[i] = Shader::AttributeType::SignedInt;
|
||||
if (device.IsMoltenVK()) {
|
||||
for (size_t i = 0; i < 8; ++i) {
|
||||
const auto format = static_cast<Tegra::RenderTargetFormat>(key.state.color_formats[i]);
|
||||
const auto pixel_format = VideoCore::Surface::PixelFormatFromRenderTargetFormat(format);
|
||||
if (VideoCore::Surface::IsPixelFormatInteger(pixel_format)) {
|
||||
if (VideoCore::Surface::IsPixelFormatSignedInteger(pixel_format)) {
|
||||
info.color_output_types[i] = Shader::AttributeType::SignedInt;
|
||||
} else {
|
||||
info.color_output_types[i] = Shader::AttributeType::UnsignedInt;
|
||||
}
|
||||
} else {
|
||||
info.color_output_types[i] = Shader::AttributeType::UnsignedInt;
|
||||
info.color_output_types[i] = Shader::AttributeType::Float;
|
||||
}
|
||||
} else {
|
||||
info.color_output_types[i] = Shader::AttributeType::Float;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -458,7 +441,7 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
|
||||
// Level 0: Core Dynamic States only
|
||||
// Level 1: Core + EDS1
|
||||
// Level 2: Core + EDS1 + EDS2 (accumulative)
|
||||
// Level 2: Core + EDS1 + EDS2 (accumulative)
|
||||
// Level 3: Core + EDS1 + EDS2 + EDS3 (accumulative)
|
||||
// Here we only verify if extensions were successfully loaded by the device
|
||||
|
||||
dynamic_features.has_extended_dynamic_state =
|
||||
@@ -469,6 +452,16 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
|
||||
dynamic_features.has_extended_dynamic_state_2_logic_op =
|
||||
device.IsExtExtendedDynamicState2ExtrasSupported();
|
||||
dynamic_features.has_extended_dynamic_state_2_patch_control_points = false;
|
||||
|
||||
dynamic_features.has_extended_dynamic_state_3_blend =
|
||||
device.IsExtExtendedDynamicState3BlendingSupported();
|
||||
dynamic_features.has_extended_dynamic_state_3_enables =
|
||||
device.IsExtExtendedDynamicState3EnablesSupported();
|
||||
|
||||
// VIDS: Independent toggle (not affected by dyna_state levels)
|
||||
dynamic_features.has_dynamic_vertex_input =
|
||||
device.IsExtVertexInputDynamicStateSupported() &&
|
||||
Settings::values.vertex_input_dynamic_state.GetValue();
|
||||
}
|
||||
|
||||
PipelineCache::~PipelineCache() {
|
||||
@@ -574,7 +567,12 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading
|
||||
(key.state.extended_dynamic_state_2 != 0) !=
|
||||
dynamic_features.has_extended_dynamic_state_2 ||
|
||||
(key.state.extended_dynamic_state_2_logic_op != 0) !=
|
||||
dynamic_features.has_extended_dynamic_state_2_logic_op) {
|
||||
dynamic_features.has_extended_dynamic_state_2_logic_op ||
|
||||
(key.state.extended_dynamic_state_3_blend != 0) !=
|
||||
dynamic_features.has_extended_dynamic_state_3_blend ||
|
||||
(key.state.extended_dynamic_state_3_enables != 0) !=
|
||||
dynamic_features.has_extended_dynamic_state_3_enables ||
|
||||
(key.state.dynamic_vertex_input != 0) != dynamic_features.has_dynamic_vertex_input) {
|
||||
return;
|
||||
}
|
||||
workers.QueueWork([this, key, envs_ = std::move(envs), &state, &callback]() mutable {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
@@ -101,14 +101,22 @@ PresentManager::PresentManager(const vk::Instance& instance_,
|
||||
MemoryAllocator& memory_allocator_,
|
||||
Scheduler& scheduler_,
|
||||
Swapchain& swapchain_,
|
||||
#ifdef ANDROID
|
||||
vk::SurfaceKHR& surface_)
|
||||
#else
|
||||
VkSurfaceKHR_T* surface_handle_)
|
||||
#endif
|
||||
: instance{instance_}
|
||||
, render_window{render_window_}
|
||||
, device{device_}
|
||||
, memory_allocator{memory_allocator_}
|
||||
, scheduler{scheduler_}
|
||||
, swapchain{swapchain_}
|
||||
#ifdef ANDROID
|
||||
, surface{surface_}
|
||||
#else
|
||||
, surface_handle{surface_handle_}
|
||||
#endif
|
||||
, blit_supported{CanBlitToSwapchain(device.GetPhysical(), swapchain.GetImageViewFormat())}
|
||||
, use_present_thread{Settings::values.async_presentation.GetValue()}
|
||||
{
|
||||
@@ -291,7 +299,11 @@ void PresentManager::PresentThread(std::stop_token token) {
|
||||
}
|
||||
|
||||
void PresentManager::RecreateSwapchain(Frame* frame) {
|
||||
#ifndef ANDROID
|
||||
swapchain.Create(surface_handle, frame->width, frame->height); // Pass raw pointer
|
||||
#else
|
||||
swapchain.Create(*surface, frame->width, frame->height); // Pass raw pointer
|
||||
#endif
|
||||
SetImageCount();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
@@ -15,6 +15,8 @@
|
||||
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
|
||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||
|
||||
struct VkSurfaceKHR_T;
|
||||
|
||||
namespace Core::Frontend {
|
||||
class EmuWindow;
|
||||
} // namespace Core::Frontend
|
||||
@@ -44,7 +46,11 @@ public:
|
||||
MemoryAllocator& memory_allocator,
|
||||
Scheduler& scheduler,
|
||||
Swapchain& swapchain,
|
||||
#ifdef ANDROID
|
||||
vk::SurfaceKHR& surface);
|
||||
#else
|
||||
VkSurfaceKHR_T* surface_handle);
|
||||
#endif
|
||||
~PresentManager();
|
||||
|
||||
/// Returns the last used presentation frame
|
||||
@@ -78,7 +84,11 @@ private:
|
||||
MemoryAllocator& memory_allocator;
|
||||
Scheduler& scheduler;
|
||||
Swapchain& swapchain;
|
||||
#ifdef ANDROID
|
||||
vk::SurfaceKHR& surface;
|
||||
#else
|
||||
VkSurfaceKHR_T* surface_handle;
|
||||
#endif
|
||||
vk::CommandPool cmdpool;
|
||||
std::vector<Frame> frames;
|
||||
boost::container::deque<Frame*> present_queue;
|
||||
|
||||
@@ -145,7 +145,6 @@ public:
|
||||
scheduler.Record([buffer = *accumulation_buffer](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.FillBuffer(buffer, 0, 8, 0);
|
||||
});
|
||||
ReserveBank();
|
||||
}
|
||||
|
||||
~SamplesStreamer() = default;
|
||||
@@ -203,11 +202,6 @@ public:
|
||||
}
|
||||
|
||||
void SyncWrites() override {
|
||||
if (!direct_sync_values.empty()) {
|
||||
runtime.template SyncValues<VideoCommon::SyncValuesStruct>(direct_sync_values);
|
||||
direct_sync_values.clear();
|
||||
}
|
||||
|
||||
if (sync_values_stash.empty()) {
|
||||
return;
|
||||
}
|
||||
@@ -226,55 +220,10 @@ public:
|
||||
}
|
||||
PauseCounter();
|
||||
const auto driver_id = device.GetDriverID();
|
||||
if (driver_id == VK_DRIVER_ID_ARM_PROPRIETARY) {
|
||||
ApplyBanksWideOp<false>(
|
||||
pending_sync,
|
||||
[](SamplesQueryBank* bank, size_t start, size_t amount) { bank->Sync(start, amount); });
|
||||
|
||||
direct_sync_values.clear();
|
||||
direct_sync_values.reserve(pending_sync.size());
|
||||
|
||||
bool has_multi_queries = accumulation_since_last_sync;
|
||||
for (auto q : pending_sync) {
|
||||
auto* query = GetQuery(q);
|
||||
if (True(query->flags & VideoCommon::QueryFlagBits::IsRewritten)) {
|
||||
continue;
|
||||
}
|
||||
if (True(query->flags & VideoCommon::QueryFlagBits::IsInvalidated)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
u64 total = 0;
|
||||
ApplyBankOp(query, [&total](SamplesQueryBank* bank, size_t start, size_t amount) {
|
||||
const auto& results = bank->GetResults();
|
||||
for (size_t i = 0; i < amount; i++) {
|
||||
total += results[start + i];
|
||||
}
|
||||
});
|
||||
|
||||
const u64 final_total = total + GetAmendValue();
|
||||
query->value = total;
|
||||
query->flags |= VideoCommon::QueryFlagBits::IsHostSynced;
|
||||
query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced;
|
||||
direct_sync_values.emplace_back(VideoCommon::SyncValuesStruct{
|
||||
.address = query->guest_address,
|
||||
.value = final_total,
|
||||
.size = SamplesQueryBank::QUERY_SIZE,
|
||||
});
|
||||
|
||||
has_multi_queries |= query->size_slots > 1;
|
||||
}
|
||||
|
||||
ReplicateCurrentQueryIfNeeded();
|
||||
std::function<void()> func([this] { amend_value = accumulation_value; });
|
||||
rasterizer->SyncOperation(std::move(func));
|
||||
AbandonCurrentQuery();
|
||||
num_slots_used = 0;
|
||||
first_accumulation_checkpoint = (std::numeric_limits<size_t>::max)();
|
||||
last_accumulation_checkpoint = 0;
|
||||
accumulation_since_last_sync = has_multi_queries;
|
||||
sync_values_stash.clear();
|
||||
if (driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY ||
|
||||
driver_id == VK_DRIVER_ID_ARM_PROPRIETARY || driver_id == VK_DRIVER_ID_MESA_TURNIP) {
|
||||
pending_sync.clear();
|
||||
sync_values_stash.clear();
|
||||
return;
|
||||
}
|
||||
sync_values_stash.clear();
|
||||
@@ -620,7 +569,6 @@ private:
|
||||
std::array<size_t, 32> resolve_table{};
|
||||
std::array<size_t, 32> intermediary_table{};
|
||||
vk::Buffer accumulation_buffer;
|
||||
std::vector<VideoCommon::SyncValuesStruct> direct_sync_values;
|
||||
std::deque<std::vector<HostSyncValues>> sync_values_stash;
|
||||
std::vector<size_t> resolve_buffers;
|
||||
|
||||
@@ -632,16 +580,16 @@ private:
|
||||
std::deque<std::vector<size_t>> pending_flush_sets;
|
||||
|
||||
// State Machine
|
||||
size_t current_bank_slot{};
|
||||
size_t current_bank_id{};
|
||||
SamplesQueryBank* current_bank{};
|
||||
VkQueryPool current_query_pool{};
|
||||
size_t current_query_id{};
|
||||
size_t current_bank_slot;
|
||||
size_t current_bank_id;
|
||||
SamplesQueryBank* current_bank;
|
||||
VkQueryPool current_query_pool;
|
||||
size_t current_query_id;
|
||||
size_t num_slots_used{};
|
||||
size_t first_accumulation_checkpoint{};
|
||||
size_t last_accumulation_checkpoint{};
|
||||
bool accumulation_since_last_sync{};
|
||||
VideoCommon::HostQueryBase* current_query{};
|
||||
VideoCommon::HostQueryBase* current_query;
|
||||
bool has_started{};
|
||||
std::mutex flush_guard;
|
||||
|
||||
@@ -717,7 +665,7 @@ public:
|
||||
counter_buffers.fill(VK_NULL_HANDLE);
|
||||
offsets.fill(0);
|
||||
last_queries.fill(0);
|
||||
last_queries_stride.fill(0);
|
||||
last_queries_stride.fill(1);
|
||||
const VkBufferCreateInfo buffer_ci = {
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
@@ -913,16 +861,14 @@ private:
|
||||
has_flushed_end_pending = true;
|
||||
// Refresh buffers state before beginning transform feedback so counters are up-to-date
|
||||
UpdateBuffers();
|
||||
active_buffers_count = has_started ? buffers_count : 0;
|
||||
if (active_buffers_count == 0) {
|
||||
if (!has_started || buffers_count == 0) {
|
||||
// No counter buffers available: begin without counters
|
||||
scheduler.Record([](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.BeginTransformFeedbackEXT(0, 0, nullptr, nullptr);
|
||||
});
|
||||
return;
|
||||
}
|
||||
scheduler.Record(
|
||||
[this, total = static_cast<u32>(active_buffers_count)](vk::CommandBuffer cmdbuf) {
|
||||
scheduler.Record([this, total = static_cast<u32>(buffers_count)](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.BeginTransformFeedbackEXT(0, total, counter_buffers.data(), offsets.data());
|
||||
});
|
||||
}
|
||||
@@ -933,31 +879,30 @@ private:
|
||||
return;
|
||||
}
|
||||
has_flushed_end_pending = false;
|
||||
if (active_buffers_count == 0) {
|
||||
LOG_DEBUG(Render_Vulkan,
|
||||
"EndTransformFeedbackEXT called with no counters (active_buffers_count=0)");
|
||||
|
||||
// Refresh buffer state before ending transform feedback to ensure counters_count is up-to-date.
|
||||
UpdateBuffers();
|
||||
if (buffers_count == 0) {
|
||||
LOG_DEBUG(Render_Vulkan, "EndTransformFeedbackEXT called with no counters (buffers_count=0)");
|
||||
scheduler.Record([](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.EndTransformFeedbackEXT(0, 0, nullptr, nullptr);
|
||||
});
|
||||
} else {
|
||||
LOG_DEBUG(Render_Vulkan,
|
||||
"EndTransformFeedbackEXT called with counters (active_buffers_count={})",
|
||||
active_buffers_count);
|
||||
LOG_DEBUG(Render_Vulkan, "EndTransformFeedbackEXT called with counters (buffers_count={})", buffers_count);
|
||||
scheduler.Record([this,
|
||||
total = static_cast<u32>(active_buffers_count)](vk::CommandBuffer cmdbuf) {
|
||||
total = static_cast<u32>(buffers_count)](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.EndTransformFeedbackEXT(0, total, counter_buffers.data(), offsets.data());
|
||||
});
|
||||
}
|
||||
active_buffers_count = 0;
|
||||
}
|
||||
|
||||
void UpdateBuffers() {
|
||||
last_queries.fill(0);
|
||||
last_queries_stride.fill(0);
|
||||
last_queries_stride.fill(1);
|
||||
streams_mask = 0; // reset previously recorded streams
|
||||
runtime.View3DRegs([this](Maxwell3D& maxwell3d) {
|
||||
buffers_count = 0;
|
||||
out_topology = maxwell3d.GetTransformFeedbackOutputTopology();
|
||||
out_topology = maxwell3d.draw_manager->GetDrawState().topology;
|
||||
for (size_t i = 0; i < Maxwell3D::Regs::NumTransformFeedbackBuffers; i++) {
|
||||
const auto& tf = maxwell3d.regs.transform_feedback;
|
||||
if (tf.buffers[i].enable == 0) {
|
||||
@@ -968,9 +913,7 @@ private:
|
||||
LOG_WARNING(Render_Vulkan, "TransformFeedback stream {} out of range", stream);
|
||||
continue;
|
||||
}
|
||||
if (last_queries_stride[stream] == 0) {
|
||||
last_queries_stride[stream] = tf.controls[i].stride;
|
||||
}
|
||||
last_queries_stride[stream] = tf.controls[i].stride;
|
||||
streams_mask |= 1ULL << stream;
|
||||
buffers_count = std::max<size_t>(buffers_count, stream + 1);
|
||||
}
|
||||
@@ -1047,7 +990,6 @@ private:
|
||||
bool has_started{};
|
||||
bool has_flushed_end_pending{};
|
||||
size_t buffers_count{};
|
||||
size_t active_buffers_count{};
|
||||
std::array<VkBuffer, NUM_STREAMS> counter_buffers{};
|
||||
std::array<VkDeviceSize, NUM_STREAMS> offsets{};
|
||||
std::array<DAddr, NUM_STREAMS> last_queries;
|
||||
@@ -1073,41 +1015,10 @@ public:
|
||||
u64 stride{};
|
||||
DAddr dependant_address{};
|
||||
Maxwell3D::Regs::PrimitiveTopology topology{Maxwell3D::Regs::PrimitiveTopology::Points};
|
||||
u32 patch_vertices{1};
|
||||
size_t dependant_index{};
|
||||
bool dependant_manage{};
|
||||
};
|
||||
|
||||
[[nodiscard]] constexpr u64 PrimitiveCountFromVertices(
|
||||
Maxwell3D::Regs::PrimitiveTopology topology, u64 num_vertices, u32 patch_vertices) {
|
||||
switch (topology) {
|
||||
case Maxwell3D::Regs::PrimitiveTopology::Points:
|
||||
return num_vertices;
|
||||
case Maxwell3D::Regs::PrimitiveTopology::Lines:
|
||||
return num_vertices / 2;
|
||||
case Maxwell3D::Regs::PrimitiveTopology::LinesAdjacency:
|
||||
return num_vertices / 4;
|
||||
case Maxwell3D::Regs::PrimitiveTopology::Triangles:
|
||||
return num_vertices / 3;
|
||||
case Maxwell3D::Regs::PrimitiveTopology::TrianglesAdjacency:
|
||||
return num_vertices / 6;
|
||||
case Maxwell3D::Regs::PrimitiveTopology::Patches:
|
||||
return patch_vertices != 0 ? num_vertices / patch_vertices : 0;
|
||||
case Maxwell3D::Regs::PrimitiveTopology::LineLoop:
|
||||
case Maxwell3D::Regs::PrimitiveTopology::LineStrip:
|
||||
case Maxwell3D::Regs::PrimitiveTopology::LineStripAdjacency:
|
||||
case Maxwell3D::Regs::PrimitiveTopology::TriangleFan:
|
||||
case Maxwell3D::Regs::PrimitiveTopology::TriangleStrip:
|
||||
case Maxwell3D::Regs::PrimitiveTopology::TriangleStripAdjacency:
|
||||
case Maxwell3D::Regs::PrimitiveTopology::Quads:
|
||||
case Maxwell3D::Regs::PrimitiveTopology::QuadStrip:
|
||||
case Maxwell3D::Regs::PrimitiveTopology::Polygon:
|
||||
return 0;
|
||||
default:
|
||||
return num_vertices;
|
||||
}
|
||||
}
|
||||
|
||||
class PrimitivesSucceededStreamer : public VideoCommon::SimpleStreamer<PrimitivesQueryBase> {
|
||||
public:
|
||||
explicit PrimitivesSucceededStreamer(size_t id_, QueryCacheRuntime& runtime_,
|
||||
@@ -1136,10 +1047,7 @@ public:
|
||||
const size_t subreport = static_cast<size_t>(*subreport_);
|
||||
auto dependant_address_opt = tfb_streamer.GetLastQueryStream(subreport);
|
||||
bool must_manage_dependance = false;
|
||||
runtime.View3DRegs([new_query, this](Maxwell3D& maxwell3d) {
|
||||
new_query->topology = tfb_streamer.GetOutputTopology();
|
||||
new_query->patch_vertices = std::max<u32>(maxwell3d.regs.patch_vertices, 1);
|
||||
});
|
||||
new_query->topology = tfb_streamer.GetOutputTopology();
|
||||
if (dependant_address_opt) {
|
||||
auto [dep_address, stride] = *dependant_address_opt;
|
||||
new_query->dependant_address = dep_address;
|
||||
@@ -1158,7 +1066,7 @@ public:
|
||||
}
|
||||
return index;
|
||||
}
|
||||
new_query->stride = 0;
|
||||
new_query->stride = 1;
|
||||
runtime.View3DRegs([new_query, subreport](Maxwell3D& maxwell3d) {
|
||||
for (size_t i = 0; i < Maxwell3D::Regs::NumTransformFeedbackBuffers; i++) {
|
||||
const auto& tf = maxwell3d.regs.transform_feedback;
|
||||
@@ -1222,8 +1130,32 @@ public:
|
||||
num_vertices = static_cast<u64>(result) / safe_stride;
|
||||
}
|
||||
}
|
||||
query->value =
|
||||
PrimitiveCountFromVertices(query->topology, num_vertices, query->patch_vertices);
|
||||
query->value = [&]() -> u64 {
|
||||
switch (query->topology) {
|
||||
case Maxwell3D::Regs::PrimitiveTopology::Points:
|
||||
return num_vertices;
|
||||
case Maxwell3D::Regs::PrimitiveTopology::Lines:
|
||||
return num_vertices / 2;
|
||||
case Maxwell3D::Regs::PrimitiveTopology::LineLoop:
|
||||
return (num_vertices / 2) + 1;
|
||||
case Maxwell3D::Regs::PrimitiveTopology::LineStrip:
|
||||
return num_vertices - 1;
|
||||
case Maxwell3D::Regs::PrimitiveTopology::Patches:
|
||||
case Maxwell3D::Regs::PrimitiveTopology::Triangles:
|
||||
case Maxwell3D::Regs::PrimitiveTopology::TrianglesAdjacency:
|
||||
return num_vertices / 3;
|
||||
case Maxwell3D::Regs::PrimitiveTopology::TriangleFan:
|
||||
case Maxwell3D::Regs::PrimitiveTopology::TriangleStrip:
|
||||
case Maxwell3D::Regs::PrimitiveTopology::TriangleStripAdjacency:
|
||||
return num_vertices - 2;
|
||||
case Maxwell3D::Regs::PrimitiveTopology::Quads:
|
||||
return num_vertices / 4;
|
||||
case Maxwell3D::Regs::PrimitiveTopology::Polygon:
|
||||
return 1U;
|
||||
default:
|
||||
return num_vertices;
|
||||
}
|
||||
}();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1273,18 +1205,13 @@ struct QueryCacheRuntimeImpl {
|
||||
conditional_resolve_pass = std::make_unique<ConditionalRenderingResolvePass>(
|
||||
device, scheduler, descriptor_pool, compute_pass_descriptor_queue);
|
||||
|
||||
VkBufferUsageFlags conditional_usage =
|
||||
VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
|
||||
if (device.IsExtConditionalRendering()) {
|
||||
conditional_usage |= VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT;
|
||||
}
|
||||
|
||||
const VkBufferCreateInfo buffer_ci = {
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.size = sizeof(u32),
|
||||
.usage = conditional_usage,
|
||||
.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
|
||||
VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT,
|
||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||
.queueFamilyIndexCount = 0,
|
||||
.pQueueFamilyIndices = nullptr,
|
||||
@@ -1353,7 +1280,7 @@ void QueryCacheRuntime::EndHostConditionalRendering() {
|
||||
PauseHostConditionalRendering();
|
||||
impl->hcr_is_set = false;
|
||||
impl->is_hcr_running = false;
|
||||
impl->hcr_buffer = VkBuffer{};
|
||||
impl->hcr_buffer = nullptr;
|
||||
impl->hcr_offset = 0;
|
||||
}
|
||||
|
||||
@@ -1380,35 +1307,6 @@ void QueryCacheRuntime::ResumeHostConditionalRendering() {
|
||||
impl->is_hcr_running = true;
|
||||
}
|
||||
|
||||
void QueryCacheRuntime::SetHostConditionalRenderingResult(bool condition_passed) {
|
||||
if (!impl->device.IsExtConditionalRendering()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool was_running = impl->is_hcr_running;
|
||||
if (was_running) {
|
||||
PauseHostConditionalRendering();
|
||||
}
|
||||
|
||||
impl->scheduler.RequestOutsideRenderPassOperationContext();
|
||||
impl->scheduler.Record(
|
||||
[buffer = *impl->hcr_resolve_buffer, value = condition_passed ? 1u : 0u](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.FillBuffer(buffer, 0, sizeof(u32), value);
|
||||
});
|
||||
|
||||
impl->hcr_setup.buffer = *impl->hcr_resolve_buffer;
|
||||
impl->hcr_setup.offset = 0;
|
||||
impl->hcr_setup.flags = 0;
|
||||
impl->hcr_buffer = *impl->hcr_resolve_buffer;
|
||||
impl->hcr_offset = 0;
|
||||
impl->hcr_is_set = true;
|
||||
impl->is_hcr_running = false;
|
||||
|
||||
if (was_running) {
|
||||
ResumeHostConditionalRendering();
|
||||
}
|
||||
}
|
||||
|
||||
void QueryCacheRuntime::HostConditionalRenderingCompareValueImpl(VideoCommon::LookupData object,
|
||||
bool is_equal) {
|
||||
{
|
||||
@@ -1519,6 +1417,13 @@ bool QueryCacheRuntime::HostConditionalRenderingCompareValues(VideoCommon::Looku
|
||||
return false;
|
||||
}
|
||||
|
||||
auto driver_id = impl->device.GetDriverID();
|
||||
const bool is_gpu_high = Settings::IsGPULevelHigh();
|
||||
|
||||
if ((!is_gpu_high && driver_id == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS) || driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY || driver_id == VK_DRIVER_ID_ARM_PROPRIETARY || driver_id == VK_DRIVER_ID_MESA_TURNIP) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < 2; i++) {
|
||||
is_null[i] = !is_in_ac[i] && check_value(objects[i]->address);
|
||||
}
|
||||
@@ -1531,22 +1436,12 @@ bool QueryCacheRuntime::HostConditionalRenderingCompareValues(VideoCommon::Looku
|
||||
}
|
||||
}
|
||||
|
||||
auto driver_id = impl->device.GetDriverID();
|
||||
const bool is_gpu_high = Settings::IsGPULevelHigh();
|
||||
const bool driver_blocks_pair_resolve =
|
||||
((!is_gpu_high && driver_id == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS) ||
|
||||
driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY ||
|
||||
driver_id == VK_DRIVER_ID_ARM_PROPRIETARY ||
|
||||
driver_id == VK_DRIVER_ID_MESA_TURNIP);
|
||||
|
||||
if (driver_blocks_pair_resolve || !is_gpu_high) {
|
||||
EndHostConditionalRendering();
|
||||
return false;
|
||||
if (!is_gpu_high) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!is_in_bc[0] && !is_in_bc[1]) {
|
||||
EndHostConditionalRendering();
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
HostConditionalRenderingCompareBCImpl(object_1.address, equal_check);
|
||||
return true;
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// 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-3.0-or-later
|
||||
|
||||
@@ -38,7 +35,7 @@ public:
|
||||
~QueryCacheRuntime();
|
||||
|
||||
template <typename SyncValuesType>
|
||||
void SyncValues(std::span<SyncValuesType> values, VkBuffer base_src_buffer = VkBuffer{});
|
||||
void SyncValues(std::span<SyncValuesType> values, VkBuffer base_src_buffer = nullptr);
|
||||
|
||||
void Barriers(bool is_prebarrier);
|
||||
|
||||
@@ -48,8 +45,6 @@ public:
|
||||
|
||||
void ResumeHostConditionalRendering();
|
||||
|
||||
void SetHostConditionalRenderingResult(bool condition_passed);
|
||||
|
||||
bool HostConditionalRenderingCompareValue(VideoCommon::LookupData object_1, bool qc_dirty);
|
||||
|
||||
bool HostConditionalRenderingCompareValues(VideoCommon::LookupData object_1,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
@@ -122,7 +122,6 @@ public:
|
||||
void FlushCommands() override;
|
||||
void TickFrame() override;
|
||||
bool AccelerateConditionalRendering() override;
|
||||
void SetConditionalRenderingPredicate(bool enabled) override;
|
||||
bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Surface& src,
|
||||
const Tegra::Engines::Fermi2D::Surface& dst,
|
||||
const Tegra::Engines::Fermi2D::Config& copy_config) override;
|
||||
@@ -156,17 +155,6 @@ private:
|
||||
template <typename Func>
|
||||
void PrepareDraw(bool is_indexed, Func&&);
|
||||
|
||||
bool EmulateLineLoopDraw(const Tegra::Engines::DrawManager::State& draw_state,
|
||||
u32 base_instance, u32 num_instances, s32 base_vertex,
|
||||
u32 num_vertices, u32 first_index, bool is_indexed);
|
||||
bool EmulateIndirectLineLoopDraw(const Tegra::Engines::DrawManager::State& draw_state,
|
||||
const Tegra::Engines::DrawManager::IndirectParams& params);
|
||||
bool DrawLineLoopClosure(const Tegra::Engines::DrawManager::State& draw_state,
|
||||
u32 base_instance, u32 num_instances, s32 base_vertex,
|
||||
u32 num_vertices, u32 first_index, bool is_indexed);
|
||||
void DrawIndirectLineLoopClosures(const Tegra::Engines::DrawManager::State& draw_state,
|
||||
const Tegra::Engines::DrawManager::IndirectParams& params);
|
||||
|
||||
void FlushWork();
|
||||
|
||||
void UpdateDynamicStates();
|
||||
@@ -180,17 +168,23 @@ private:
|
||||
void UpdateDepthBounds(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||
void UpdateStencilFaces(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||
void UpdateLineWidth(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||
void UpdateLineStipple(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||
|
||||
void UpdateCullMode(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||
void UpdateDepthBoundsTestEnable(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||
void UpdateDepthTestEnable(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||
void UpdateDepthWriteEnable(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||
void UpdateDepthCompareOp(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||
void UpdatePrimitiveTopology(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||
void UpdatePrimitiveRestartEnable(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||
void UpdateRasterizerDiscardEnable(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||
void UpdateConservativeRasterizationMode(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||
void UpdateLineStippleEnable(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||
void UpdateLineStipple(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||
void UpdateLineRasterizationMode(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||
void UpdateDepthBiasEnable(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||
void UpdateLogicOpEnable(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||
void UpdateDepthClampEnable(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||
void UpdateAlphaToCoverageEnable(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||
void UpdateAlphaToOneEnable(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||
void UpdateFrontFace(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||
void UpdateStencilOp(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||
void UpdateStencilTestEnable(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||
|
||||
@@ -154,11 +154,9 @@ void Scheduler::RequestOutsideRenderPassOperationContext() {
|
||||
|
||||
bool Scheduler::UpdateGraphicsPipeline(GraphicsPipeline* pipeline) {
|
||||
if (state.graphics_pipeline == pipeline) {
|
||||
if (pipeline &&
|
||||
(pipeline->UsesExtendedDynamicState() || pipeline->UsesExtendedDynamicState2() ||
|
||||
pipeline->UsesExtendedDynamicState2LogicOp()) &&
|
||||
if (pipeline && pipeline->UsesExtendedDynamicState() &&
|
||||
state.needs_state_enable_refresh) {
|
||||
state_tracker.InvalidateExtendedDynamicStates();
|
||||
state_tracker.InvalidateStateEnableFlag();
|
||||
state.needs_state_enable_refresh = false;
|
||||
}
|
||||
return false;
|
||||
@@ -170,16 +168,10 @@ bool Scheduler::UpdateGraphicsPipeline(GraphicsPipeline* pipeline) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pipeline->UsesExtendedDynamicState() || pipeline->UsesExtendedDynamicState2() ||
|
||||
pipeline->UsesExtendedDynamicState2LogicOp()) {
|
||||
state_tracker.InvalidateExtendedDynamicStates();
|
||||
}
|
||||
|
||||
if (!pipeline->UsesExtendedDynamicState() && !pipeline->UsesExtendedDynamicState2() &&
|
||||
!pipeline->UsesExtendedDynamicState2LogicOp()) {
|
||||
if (!pipeline->UsesExtendedDynamicState()) {
|
||||
state.needs_state_enable_refresh = true;
|
||||
} else if (state.needs_state_enable_refresh) {
|
||||
state_tracker.InvalidateExtendedDynamicStates();
|
||||
state_tracker.InvalidateStateEnableFlag();
|
||||
state.needs_state_enable_refresh = false;
|
||||
}
|
||||
|
||||
@@ -345,13 +337,6 @@ void Scheduler::EndRenderPass()
|
||||
images = renderpass_images,
|
||||
ranges = renderpass_image_ranges](vk::CommandBuffer cmdbuf) {
|
||||
std::array<VkImageMemoryBarrier, 9> barriers;
|
||||
constexpr VkPipelineStageFlags src_stages =
|
||||
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
|
||||
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
constexpr VkPipelineStageFlags dst_stages =
|
||||
VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT |
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
|
||||
for (size_t i = 0; i < num_images; ++i) {
|
||||
const VkImageSubresourceRange& range = ranges[i];
|
||||
const bool is_color = (range.aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) != 0;
|
||||
@@ -387,11 +372,12 @@ void Scheduler::EndRenderPass()
|
||||
};
|
||||
}
|
||||
cmdbuf.EndRenderPass();
|
||||
cmdbuf.PipelineBarrier(src_stages, dst_stages, 0, nullptr, nullptr,
|
||||
vk::Span(barriers.data(), num_images));
|
||||
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_FRAGMENT_SHADER_BIT |
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, nullptr, nullptr, vk::Span(barriers.data(), num_images));
|
||||
});
|
||||
|
||||
state.renderpass = VkRenderPass{};
|
||||
state.renderpass = nullptr;
|
||||
num_renderpass_images = 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -44,10 +44,10 @@ public:
|
||||
~Scheduler();
|
||||
|
||||
/// Sends the current execution context to the GPU.
|
||||
u64 Flush(VkSemaphore signal_semaphore = {}, VkSemaphore wait_semaphore = {});
|
||||
u64 Flush(VkSemaphore signal_semaphore = nullptr, VkSemaphore wait_semaphore = nullptr);
|
||||
|
||||
/// Sends the current execution context to the GPU and waits for it to complete.
|
||||
void Finish(VkSemaphore signal_semaphore = {}, VkSemaphore wait_semaphore = {});
|
||||
void Finish(VkSemaphore signal_semaphore = nullptr, VkSemaphore wait_semaphore = nullptr);
|
||||
|
||||
/// Waits for the worker thread to finish executing everything. After this function returns it's
|
||||
/// safe to touch worker resources.
|
||||
@@ -237,8 +237,8 @@ private:
|
||||
};
|
||||
|
||||
struct State {
|
||||
VkRenderPass renderpass{};
|
||||
VkFramebuffer framebuffer{};
|
||||
VkRenderPass renderpass = nullptr;
|
||||
VkFramebuffer framebuffer = nullptr;
|
||||
VkExtent2D render_area = {0, 0};
|
||||
GraphicsPipeline* graphics_pipeline = nullptr;
|
||||
bool is_rescaling = false;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
@@ -40,7 +40,6 @@ Flags MakeInvalidationFlags() {
|
||||
StencilWriteMask,
|
||||
StencilCompare,
|
||||
LineWidth,
|
||||
LineStipple,
|
||||
CullMode,
|
||||
DepthBoundsEnable,
|
||||
DepthTestEnable,
|
||||
@@ -55,11 +54,19 @@ Flags MakeInvalidationFlags() {
|
||||
StateEnable,
|
||||
PrimitiveRestartEnable,
|
||||
DepthBiasEnable,
|
||||
LogicOpEnable,
|
||||
DepthClampEnable,
|
||||
AlphaToCoverageEnable,
|
||||
AlphaToOneEnable,
|
||||
LineRasterizationMode,
|
||||
LogicOp,
|
||||
Blending,
|
||||
ColorMask,
|
||||
BlendEquations,
|
||||
BlendEnable,
|
||||
ConservativeRasterizationMode,
|
||||
LineStippleEnable,
|
||||
LineStippleParams,
|
||||
};
|
||||
Flags flags{};
|
||||
for (const int flag : INVALIDATION_FLAGS) {
|
||||
@@ -120,13 +127,6 @@ void SetupDirtyStencilProperties(Tables& tables) {
|
||||
void SetupDirtyLineWidth(Tables& tables) {
|
||||
tables[0][OFF(line_width_smooth)] = LineWidth;
|
||||
tables[0][OFF(line_width_aliased)] = LineWidth;
|
||||
tables[0][OFF(line_anti_alias_enable)] = LineWidth;
|
||||
}
|
||||
|
||||
void SetupDirtyLineStipple(Tables& tables) {
|
||||
tables[0][OFF(line_stipple_enable)] = LineStipple;
|
||||
FillBlock(tables[0], OFF(line_stipple_params), NUM(line_stipple_params), LineStipple);
|
||||
tables[1][OFF(line_anti_alias_enable)] = LineStipple;
|
||||
}
|
||||
|
||||
void SetupDirtyCullMode(Tables& tables) {
|
||||
@@ -149,6 +149,11 @@ void SetupDirtyStateEnable(Tables& tables) {
|
||||
setup(OFF(polygon_offset_point_enable), DepthBiasEnable);
|
||||
setup(OFF(polygon_offset_line_enable), DepthBiasEnable);
|
||||
setup(OFF(polygon_offset_fill_enable), DepthBiasEnable);
|
||||
setup(OFF(logic_op.enable), LogicOpEnable);
|
||||
setup(OFF(viewport_clip_control.geometry_clip), DepthClampEnable);
|
||||
setup(OFF(line_stipple_enable), LineStippleEnable);
|
||||
setup(OFF(anti_alias_alpha_control.alpha_to_coverage), AlphaToCoverageEnable);
|
||||
setup(OFF(anti_alias_alpha_control.alpha_to_one), AlphaToOneEnable);
|
||||
}
|
||||
|
||||
void SetupDirtyDepthCompareOp(Tables& tables) {
|
||||
@@ -222,6 +227,13 @@ void SetupDirtyVertexBindings(Tables& tables) {
|
||||
}
|
||||
}
|
||||
|
||||
void SetupRasterModes(Tables &tables) {
|
||||
auto& table = tables[0];
|
||||
|
||||
table[OFF(line_stipple_params)] = LineStippleParams;
|
||||
table[OFF(conservative_raster_enable)] = ConservativeRasterizationMode;
|
||||
table[OFF(line_anti_alias_enable)] = LineRasterizationMode;
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
void StateTracker::SetupTables(Tegra::Control::ChannelState& channel_state) {
|
||||
@@ -234,7 +246,6 @@ void StateTracker::SetupTables(Tegra::Control::ChannelState& channel_state) {
|
||||
SetupDirtyDepthBounds(tables);
|
||||
SetupDirtyStencilProperties(tables);
|
||||
SetupDirtyLineWidth(tables);
|
||||
SetupDirtyLineStipple(tables);
|
||||
SetupDirtyCullMode(tables);
|
||||
SetupDirtyStateEnable(tables);
|
||||
SetupDirtyDepthCompareOp(tables);
|
||||
@@ -245,6 +256,7 @@ void StateTracker::SetupTables(Tegra::Control::ChannelState& channel_state) {
|
||||
SetupDirtyVertexAttributes(tables);
|
||||
SetupDirtyVertexBindings(tables);
|
||||
SetupDirtySpecialOps(tables);
|
||||
SetupRasterModes(tables);
|
||||
}
|
||||
|
||||
void StateTracker::ChangeChannel(Tegra::Control::ChannelState& channel_state) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user